+72
.env.grafana.example
+72
.env.grafana.example
···
1
+
# Grafana Cloud Configuration for wisp.place monorepo
2
+
# Copy these variables to your .env file to enable Grafana integration
3
+
# The observability package will automatically pick up these environment variables
4
+
5
+
# ============================================================================
6
+
# Grafana Loki (for logs)
7
+
# ============================================================================
8
+
# Get this from your Grafana Cloud portal under Loki โ Details
9
+
# Example: https://logs-prod-012.grafana.net
10
+
GRAFANA_LOKI_URL=https://logs-prod-xxx.grafana.net
11
+
12
+
# Authentication Option 1: Bearer Token (Grafana Cloud)
13
+
GRAFANA_LOKI_TOKEN=glc_xxx
14
+
15
+
# Authentication Option 2: Username/Password (Self-hosted or some Grafana setups)
16
+
# GRAFANA_LOKI_USERNAME=your-username
17
+
# GRAFANA_LOKI_PASSWORD=your-password
18
+
19
+
# ============================================================================
20
+
# Grafana Prometheus (for metrics)
21
+
# ============================================================================
22
+
# Get this from your Grafana Cloud portal under Prometheus โ Details
23
+
# Note: You need to add /api/prom to the base URL for OTLP export
24
+
# Example: https://prometheus-prod-10-prod-us-central-0.grafana.net/api/prom
25
+
GRAFANA_PROMETHEUS_URL=https://prometheus-prod-xxx.grafana.net/api/prom
26
+
27
+
# Authentication Option 1: Bearer Token (Grafana Cloud)
28
+
GRAFANA_PROMETHEUS_TOKEN=glc_xxx
29
+
30
+
# Authentication Option 2: Username/Password (Self-hosted or some Grafana setups)
31
+
# GRAFANA_PROMETHEUS_USERNAME=your-username
32
+
# GRAFANA_PROMETHEUS_PASSWORD=your-password
33
+
34
+
# ============================================================================
35
+
# Optional Configuration
36
+
# ============================================================================
37
+
# These will be used by both main-app and hosting-service if not overridden
38
+
39
+
# Service metadata (optional - defaults are provided in code)
40
+
# SERVICE_NAME=wisp-app
41
+
# SERVICE_VERSION=1.0.0
42
+
43
+
# Batching configuration (optional)
44
+
# GRAFANA_BATCH_SIZE=100 # Flush after this many entries
45
+
# GRAFANA_FLUSH_INTERVAL=5000 # Flush every 5 seconds
46
+
47
+
# ============================================================================
48
+
# How to get these values:
49
+
# ============================================================================
50
+
# 1. Sign up for Grafana Cloud at https://grafana.com/
51
+
# 2. Go to your Grafana Cloud portal
52
+
# 3. For Loki:
53
+
# - Navigate to "Connections" โ "Loki"
54
+
# - Click "Details"
55
+
# - Copy the Push endpoint URL (without /loki/api/v1/push)
56
+
# - Create an API token with push permissions
57
+
# 4. For Prometheus:
58
+
# - Navigate to "Connections" โ "Prometheus"
59
+
# - Click "Details"
60
+
# - Copy the Remote Write endpoint (add /api/prom for OTLP)
61
+
# - Create an API token with write permissions
62
+
63
+
# ============================================================================
64
+
# Testing the integration:
65
+
# ============================================================================
66
+
# 1. Copy this file's contents to your .env file
67
+
# 2. Fill in the actual values
68
+
# 3. Restart your services (main-app and hosting-service)
69
+
# 4. Check your Grafana Cloud dashboard for incoming data
70
+
# 5. Use Grafana Explore to query:
71
+
# - Loki: {job="main-app"} or {job="hosting-service"}
72
+
# - Prometheus: http_requests_total{service="main-app"}
+1
.gitignore
+1
.gitignore
+54
-42
.tangled/workflows/deploy-wisp.yml
+54
-42
.tangled/workflows/deploy-wisp.yml
···
1
-
# Deploy to Wisp.place
2
-
# This workflow builds your site and deploys it to Wisp.place using the wisp-cli
1
+
---
3
2
when:
4
-
- event: ['push']
5
-
branch: ['main']
6
-
- event: ['manual']
7
-
engine: 'nixery'
3
+
- event:
4
+
- push
5
+
branch:
6
+
- main
7
+
- event:
8
+
- manual
9
+
engine: nixery
8
10
clone:
9
-
skip: false
10
-
depth: 1
11
-
submodules: true
11
+
skip: false
12
+
depth: 1
13
+
submodules: true
12
14
dependencies:
13
-
nixpkgs:
14
-
- git
15
-
- gcc
16
-
github:NixOS/nixpkgs/nixpkgs-unstable:
17
-
- rustc
18
-
- cargo
15
+
nixpkgs:
16
+
- git
17
+
- gcc
18
+
github:NixOS/nixpkgs/nixpkgs-unstable:
19
+
- rustc
20
+
- cargo
21
+
- bun
19
22
environment:
20
-
# Customize these for your project
21
-
SITE_PATH: 'testDeploy'
22
-
SITE_NAME: 'wispPlaceDocs'
23
+
WISP_HANDLE: wisp.place
24
+
SITE_PATH: docs/dist
25
+
SITE_NAME: docs
23
26
steps:
24
-
- name: 'Initialize submodules'
25
-
command: |
26
-
git submodule update --init --recursive
27
+
- name: Initialize submodules
28
+
command: |
29
+
git submodule update --init --recursive
30
+
- name: Build wisp-cli
31
+
command: |
32
+
cd cli
27
33
28
-
- name: 'Build wisp-cli'
29
-
command: |
30
-
cd cli
31
-
export PATH="$HOME/.nix-profile/bin:$PATH"
32
-
nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs
33
-
nix-channel --update
34
-
nix-shell -p pkg-config openssl --run '
35
-
export PKG_CONFIG_PATH="$(pkg-config --variable pc_path pkg-config)"
36
-
export OPENSSL_DIR="$(nix-build --no-out-link "<nixpkgs>" -A openssl.dev)"
37
-
export OPENSSL_NO_VENDOR=1
38
-
export OPENSSL_LIB_DIR="$(nix-build --no-out-link "<nixpkgs>" -A openssl.out)/lib"
39
-
cargo build --release
40
-
'
41
-
cd ..
34
+
export PATH="$HOME/.nix-profile/bin:$PATH"
42
35
43
-
- name: 'Deploy to Wisp.place'
44
-
command: |
45
-
./cli/target/release/wisp-cli \
46
-
"$WISP_HANDLE" \
47
-
--path "$SITE_PATH" \
48
-
--site "$SITE_NAME" \
49
-
--password "$WISP_APP_PASSWORD"
36
+
nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs
37
+
38
+
nix-channel --update
39
+
40
+
nix-shell -p pkg-config openssl --run '
41
+
export PKG_CONFIG_PATH="$(pkg-config --variable pc_path pkg-config)"
42
+
export OPENSSL_DIR="$(nix-build --no-out-link "<nixpkgs>" -A openssl.dev)"
43
+
export OPENSSL_NO_VENDOR=1
44
+
export OPENSSL_LIB_DIR="$(nix-build --no-out-link "<nixpkgs>" -A openssl.out)/lib"
45
+
cargo build --release
46
+
'
47
+
48
+
cd ..
49
+
- name: Build docs
50
+
command: |
51
+
cd docs
52
+
export PATH="$HOME/.nix-profile/bin:$PATH"
53
+
bun install
54
+
bun run build
55
+
- name: Deploy to Wisp.place
56
+
command: |
57
+
./cli/target/release/wisp-cli \
58
+
"$WISP_HANDLE" \
59
+
--path "$SITE_PATH" \
60
+
--site "$SITE_NAME" \
61
+
--password "$WISP_APP_PASSWORD"
+2
-1
.tangled/workflows/test.yml
+2
-1
.tangled/workflows/test.yml
···
7
7
dependencies:
8
8
nixpkgs:
9
9
- git
10
+
- findutils
10
11
github:NixOS/nixpkgs/nixpkgs-unstable:
11
12
- bun
12
13
···
16
17
export PATH="$HOME/.nix-profile/bin:$PATH"
17
18
18
19
# have to regenerate otherwise it wont install necessary dependencies to run
19
-
rm -rf bun.lock package-lock.json
20
+
find . -type f \( -name "bun.lock" -o -name "package-lock.json" \) -delete
20
21
bun install
21
22
22
23
- name: run all tests
+15
-58
Dockerfile
+15
-58
Dockerfile
···
1
-
# Build stage
2
-
FROM oven/bun:1.3 AS build
1
+
# Production stage
2
+
FROM oven/bun:1.3
3
3
4
4
WORKDIR /app
5
5
···
7
7
COPY package.json bunfig.toml tsconfig.json bun.lock* ./
8
8
9
9
# Copy all workspace package.json files first (for dependency resolution)
10
-
COPY packages ./packages
10
+
COPY packages/@wisp/atproto-utils/package.json ./packages/@wisp/atproto-utils/package.json
11
+
COPY packages/@wisp/constants/package.json ./packages/@wisp/constants/package.json
12
+
COPY packages/@wisp/database/package.json ./packages/@wisp/database/package.json
13
+
COPY packages/@wisp/fs-utils/package.json ./packages/@wisp/fs-utils/package.json
14
+
COPY packages/@wisp/lexicons/package.json ./packages/@wisp/lexicons/package.json
15
+
COPY packages/@wisp/observability/package.json ./packages/@wisp/observability/package.json
16
+
COPY packages/@wisp/safe-fetch/package.json ./packages/@wisp/safe-fetch/package.json
11
17
COPY apps/main-app/package.json ./apps/main-app/package.json
12
18
COPY apps/hosting-service/package.json ./apps/hosting-service/package.json
13
19
14
-
# Install all dependencies (including workspaces)
15
-
RUN bun install --frozen-lockfile
20
+
# Install dependencies
21
+
RUN bun install --frozen-lockfile --production
16
22
17
-
# Copy source files
18
-
COPY apps/main-app ./apps/main-app
19
-
20
-
# Build compiled server
21
-
RUN bun build \
22
-
--compile \
23
-
--target bun \
24
-
--minify \
25
-
--outfile server \
26
-
apps/main-app/src/index.ts
27
-
28
-
# Production dependencies stage
29
-
FROM oven/bun:1.3 AS prod-deps
30
-
31
-
WORKDIR /app
32
-
33
-
COPY package.json bunfig.toml tsconfig.json bun.lock* ./
23
+
# Copy workspace source files
34
24
COPY packages ./packages
35
-
COPY apps/main-app/package.json ./apps/main-app/package.json
36
-
COPY apps/hosting-service/package.json ./apps/hosting-service/package.json
37
25
38
-
# Install only production dependencies
39
-
RUN bun install --frozen-lockfile --production
40
-
41
-
# Remove unnecessary large packages (bun is already in base image, these are dev tools)
42
-
RUN rm -rf /app/node_modules/bun \
43
-
/app/node_modules/@oven \
44
-
/app/node_modules/prettier \
45
-
/app/node_modules/@ts-morph
46
-
47
-
# Final stage - use distroless or slim debian-based image
48
-
FROM debian:bookworm-slim
49
-
50
-
# Install Bun runtime
51
-
COPY --from=oven/bun:1.3 /usr/local/bin/bun /usr/local/bin/bun
52
-
53
-
WORKDIR /app
54
-
55
-
# Copy compiled server
56
-
COPY --from=build /app/server /app/server
57
-
58
-
# Copy public files
59
-
COPY apps/main-app/public apps/main-app/public
60
-
61
-
# Copy production dependencies only
62
-
COPY --from=prod-deps /app/node_modules /app/node_modules
63
-
64
-
# Copy configs
65
-
COPY package.json bunfig.toml tsconfig.json /app/
66
-
COPY apps/main-app/tsconfig.json /app/apps/main-app/tsconfig.json
67
-
COPY apps/main-app/package.json /app/apps/main-app/package.json
68
-
69
-
# Create symlink for module resolution
70
-
RUN ln -s /app/node_modules /app/apps/main-app/node_modules
26
+
# Copy app source and public files
27
+
COPY apps/main-app ./apps/main-app
71
28
72
29
ENV PORT=8000
73
30
74
31
EXPOSE 8000
75
32
76
-
CMD ["./server"]
33
+
CMD ["bun", "run", "apps/main-app/src/index.ts"]
+20
-3
README.md
+20
-3
README.md
···
39
39
40
40
```bash
41
41
# Backend
42
+
# bun install will install packages across the monorepo
42
43
bun install
43
-
bun run src/index.ts
44
+
bun run dev
44
45
45
46
# Hosting service
46
-
cd hosting-service
47
-
npm run start
47
+
bun run hosting:dev
48
48
49
49
# CLI
50
50
cd cli
···
52
52
```
53
53
54
54
## Features
55
+
56
+
### File Filtering and `.wispignore`
57
+
58
+
Wisp automatically excludes common files that shouldn't be uploaded to your site (like `.git`, `node_modules`, `.env` files, etc.). You can customize this behavior by creating a `.wispignore` file in your site root.
59
+
60
+
The `.wispignore` file uses the same syntax as `.gitignore`:
61
+
```
62
+
# Custom ignore patterns
63
+
*.log
64
+
temp/
65
+
build/
66
+
.secret
67
+
```
68
+
69
+
Default patterns include: `.git`, `.github`, `.gitlab`, `.DS_Store`, `node_modules`, `.env`, cache directories, Python virtual environments, editor swap files, `.tangled`, and more.
70
+
71
+
See [File Filtering Documentation](./docs/src/content/docs/file-filtering.md) for details.
55
72
56
73
### URL Redirects and Rewrites
57
74
+13
-10
apps/hosting-service/package.json
+13
-10
apps/hosting-service/package.json
···
6
6
"dev": "tsx --env-file=.env src/index.ts",
7
7
"build": "bun run build.ts",
8
8
"start": "tsx src/index.ts",
9
+
"check": "tsc --noEmit",
9
10
"backfill": "tsx src/index.ts --backfill"
10
11
},
11
12
"dependencies": {
12
-
"@wisp/lexicons": "workspace:*",
13
-
"@wisp/constants": "workspace:*",
14
-
"@wisp/observability": "workspace:*",
15
-
"@wisp/atproto-utils": "workspace:*",
16
-
"@wisp/database": "workspace:*",
17
-
"@wisp/fs-utils": "workspace:*",
18
-
"@wisp/safe-fetch": "workspace:*",
19
13
"@atproto/api": "^0.17.4",
20
14
"@atproto/identity": "^0.4.9",
21
-
"@atproto/lexicon": "^0.5.1",
15
+
"@atproto/lexicon": "^0.5.2",
22
16
"@atproto/sync": "^0.1.36",
23
17
"@atproto/xrpc": "^0.7.5",
24
18
"@hono/node-server": "^1.19.6",
19
+
"@wisp/atproto-utils": "workspace:*",
20
+
"@wisp/constants": "workspace:*",
21
+
"@wisp/database": "workspace:*",
22
+
"@wisp/fs-utils": "workspace:*",
23
+
"@wisp/lexicons": "workspace:*",
24
+
"@wisp/observability": "workspace:*",
25
+
"@wisp/safe-fetch": "workspace:*",
25
26
"hono": "^4.10.4",
26
27
"mime-types": "^2.1.35",
27
28
"multiformats": "^13.4.1",
28
-
"postgres": "^3.4.5"
29
+
"postgres": "^3.4.5",
30
+
"tiered-storage": "1.0.3"
29
31
},
30
32
"devDependencies": {
31
33
"@types/bun": "^1.3.1",
32
34
"@types/mime-types": "^2.1.4",
33
35
"@types/node": "^22.10.5",
34
-
"tsx": "^4.19.2"
36
+
"tsx": "^4.19.2",
37
+
"typescript": "^5.9.3"
35
38
}
36
39
}
+46
-6
apps/hosting-service/src/index.ts
+46
-6
apps/hosting-service/src/index.ts
···
1
1
import app from './server';
2
2
import { serve } from '@hono/node-server';
3
3
import { FirehoseWorker } from './lib/firehose';
4
-
import { createLogger } from '@wisp/observability';
4
+
import { createLogger, initializeGrafanaExporters } from '@wisp/observability';
5
5
import { mkdirSync, existsSync } from 'fs';
6
6
import { backfillCache } from './lib/backfill';
7
-
import { startDomainCacheCleanup, stopDomainCacheCleanup, setCacheOnlyMode } from './lib/db';
7
+
import { startDomainCacheCleanup, stopDomainCacheCleanup, setCacheOnlyMode, closeDatabase } from './lib/db';
8
+
import { storage, getStorageConfig } from './lib/storage';
9
+
10
+
// Initialize Grafana exporters if configured
11
+
initializeGrafanaExporters({
12
+
serviceName: 'hosting-service',
13
+
serviceVersion: '1.0.0'
14
+
});
8
15
9
16
const logger = createLogger('hosting-service');
10
17
11
18
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3001;
12
19
const CACHE_DIR = process.env.CACHE_DIR || './cache/sites';
20
+
const BACKFILL_CONCURRENCY = process.env.BACKFILL_CONCURRENCY
21
+
? parseInt(process.env.BACKFILL_CONCURRENCY)
22
+
: undefined; // Let backfill.ts default (10) apply
13
23
14
24
// Parse CLI arguments
15
25
const args = process.argv.slice(2);
···
41
51
42
52
firehose.start();
43
53
54
+
// Optional: Bootstrap hot cache from warm tier on startup
55
+
const BOOTSTRAP_HOT_ON_STARTUP = process.env.BOOTSTRAP_HOT_ON_STARTUP === 'true';
56
+
const BOOTSTRAP_HOT_LIMIT = process.env.BOOTSTRAP_HOT_LIMIT ? parseInt(process.env.BOOTSTRAP_HOT_LIMIT) : 100;
57
+
58
+
if (BOOTSTRAP_HOT_ON_STARTUP) {
59
+
console.log(`๐ฅ Bootstrapping hot cache (top ${BOOTSTRAP_HOT_LIMIT} items)...`);
60
+
storage.bootstrapHot(BOOTSTRAP_HOT_LIMIT)
61
+
.then((loaded: number) => {
62
+
console.log(`โ
Bootstrapped ${loaded} items into hot cache`);
63
+
})
64
+
.catch((err: unknown) => {
65
+
console.error('โ Hot cache bootstrap error:', err);
66
+
});
67
+
}
68
+
44
69
// Run backfill if requested
45
70
if (backfillOnStartup) {
46
71
console.log('๐ Backfill requested, starting cache backfill...');
47
72
backfillCache({
48
73
skipExisting: true,
49
-
concurrency: 3,
74
+
concurrency: BACKFILL_CONCURRENCY,
50
75
}).then((stats) => {
51
76
console.log('โ
Cache backfill completed');
52
77
}).catch((err) => {
···
69
94
port: PORT,
70
95
});
71
96
97
+
// Get storage configuration for display
98
+
const storageConfig = getStorageConfig();
99
+
72
100
console.log(`
73
-
Wisp Hosting Service
101
+
Wisp Hosting Service with Tiered Storage
74
102
75
103
Server: http://localhost:${PORT}
76
104
Health: http://localhost:${PORT}/health
77
-
Cache: ${CACHE_DIR}
78
-
Firehose: Connected to Firehose
79
105
Cache-Only: ${CACHE_ONLY_MODE ? 'ENABLED (no DB writes)' : 'DISABLED'}
106
+
Backfill: ${backfillOnStartup ? `ENABLED (concurrency: ${BACKFILL_CONCURRENCY || 10})` : 'DISABLED'}
107
+
108
+
Tiered Storage Configuration:
109
+
Hot Cache: ${storageConfig.hotCacheSize} (${storageConfig.hotCacheCount} items max)
110
+
Warm Cache: ${storageConfig.warmCacheSize} (${storageConfig.warmEvictionPolicy} eviction)
111
+
Cold Storage: S3 - ${storageConfig.s3Bucket}
112
+
S3 Region: ${storageConfig.s3Region}
113
+
S3 Endpoint: ${storageConfig.s3Endpoint}
114
+
S3 Prefix: ${storageConfig.s3Prefix}
115
+
Metadata Bucket: ${storageConfig.metadataBucket}
116
+
117
+
Firehose: Connecting...
80
118
`);
81
119
82
120
// Graceful shutdown
···
84
122
console.log('\n๐ Shutting down...');
85
123
firehose.stop();
86
124
stopDomainCacheCleanup();
125
+
await closeDatabase();
87
126
server.close();
88
127
process.exit(0);
89
128
});
···
92
131
console.log('\n๐ Shutting down...');
93
132
firehose.stop();
94
133
stopDomainCacheCleanup();
134
+
await closeDatabase();
95
135
server.close();
96
136
process.exit(0);
97
137
});
+65
-57
apps/hosting-service/src/lib/backfill.ts
+65
-57
apps/hosting-service/src/lib/backfill.ts
···
60
60
console.log(`โ๏ธ Limited to ${maxSites} sites for backfill`);
61
61
}
62
62
63
-
// Process sites in batches
64
-
const batches: typeof sites[] = [];
65
-
for (let i = 0; i < sites.length; i += concurrency) {
66
-
batches.push(sites.slice(i, i + concurrency));
67
-
}
68
-
63
+
// Process sites with sliding window concurrency pool
64
+
const executing = new Set<Promise<void>>();
69
65
let processed = 0;
70
-
for (const batch of batches) {
71
-
await Promise.all(
72
-
batch.map(async (site) => {
73
-
try {
74
-
// Check if already cached
75
-
if (skipExisting && isCached(site.did, site.rkey)) {
76
-
stats.skipped++;
77
-
processed++;
78
-
logger.debug(`Skipping already cached site`, { did: site.did, rkey: site.rkey });
79
-
console.log(`โญ๏ธ [${processed}/${sites.length}] Skipped (cached): ${site.display_name || site.rkey}`);
80
-
return;
81
-
}
82
66
83
-
// Fetch site record
84
-
const siteData = await fetchSiteRecord(site.did, site.rkey);
85
-
if (!siteData) {
86
-
stats.failed++;
87
-
processed++;
88
-
logger.error('Site record not found during backfill', null, { did: site.did, rkey: site.rkey });
89
-
console.log(`โ [${processed}/${sites.length}] Failed (not found): ${site.display_name || site.rkey}`);
90
-
return;
91
-
}
92
-
93
-
// Get PDS endpoint
94
-
const pdsEndpoint = await getPdsForDid(site.did);
95
-
if (!pdsEndpoint) {
96
-
stats.failed++;
97
-
processed++;
98
-
logger.error('PDS not found during backfill', null, { did: site.did });
99
-
console.log(`โ [${processed}/${sites.length}] Failed (no PDS): ${site.display_name || site.rkey}`);
100
-
return;
101
-
}
67
+
for (const site of sites) {
68
+
// Create task for this site
69
+
const processSite = async () => {
70
+
try {
71
+
// Check if already cached
72
+
if (skipExisting && await isCached(site.did, site.rkey)) {
73
+
stats.skipped++;
74
+
processed++;
75
+
logger.debug(`Skipping already cached site`, { did: site.did, rkey: site.rkey });
76
+
console.log(`โญ๏ธ [${processed}/${sites.length}] Skipped (cached): ${site.display_name || site.rkey}`);
77
+
return;
78
+
}
102
79
103
-
// Mark site as being cached to prevent serving stale content during update
104
-
markSiteAsBeingCached(site.did, site.rkey);
80
+
// Fetch site record
81
+
const siteData = await fetchSiteRecord(site.did, site.rkey);
82
+
if (!siteData) {
83
+
stats.failed++;
84
+
processed++;
85
+
logger.error('Site record not found during backfill', null, { did: site.did, rkey: site.rkey });
86
+
console.log(`โ [${processed}/${sites.length}] Failed (not found): ${site.display_name || site.rkey}`);
87
+
return;
88
+
}
105
89
106
-
try {
107
-
// Download and cache site
108
-
await downloadAndCacheSite(site.did, site.rkey, siteData.record, pdsEndpoint, siteData.cid);
109
-
// Clear redirect rules cache since the site was updated
110
-
clearRedirectRulesCache(site.did, site.rkey);
111
-
stats.cached++;
112
-
processed++;
113
-
logger.info('Successfully cached site during backfill', { did: site.did, rkey: site.rkey });
114
-
console.log(`โ
[${processed}/${sites.length}] Cached: ${site.display_name || site.rkey}`);
115
-
} finally {
116
-
// Always unmark, even if caching fails
117
-
unmarkSiteAsBeingCached(site.did, site.rkey);
118
-
}
119
-
} catch (err) {
90
+
// Get PDS endpoint
91
+
const pdsEndpoint = await getPdsForDid(site.did);
92
+
if (!pdsEndpoint) {
120
93
stats.failed++;
121
94
processed++;
122
-
logger.error('Failed to cache site during backfill', err, { did: site.did, rkey: site.rkey });
123
-
console.log(`โ [${processed}/${sites.length}] Failed: ${site.display_name || site.rkey}`);
95
+
logger.error('PDS not found during backfill', null, { did: site.did });
96
+
console.log(`โ [${processed}/${sites.length}] Failed (no PDS): ${site.display_name || site.rkey}`);
97
+
return;
98
+
}
99
+
100
+
// Mark site as being cached to prevent serving stale content during update
101
+
markSiteAsBeingCached(site.did, site.rkey);
102
+
103
+
try {
104
+
// Download and cache site
105
+
await downloadAndCacheSite(site.did, site.rkey, siteData.record, pdsEndpoint, siteData.cid);
106
+
// Clear redirect rules cache since the site was updated
107
+
clearRedirectRulesCache(site.did, site.rkey);
108
+
stats.cached++;
109
+
processed++;
110
+
logger.info('Successfully cached site during backfill', { did: site.did, rkey: site.rkey });
111
+
console.log(`โ
[${processed}/${sites.length}] Cached: ${site.display_name || site.rkey}`);
112
+
} finally {
113
+
// Always unmark, even if caching fails
114
+
unmarkSiteAsBeingCached(site.did, site.rkey);
124
115
}
125
-
})
126
-
);
116
+
} catch (err) {
117
+
stats.failed++;
118
+
processed++;
119
+
logger.error('Failed to cache site during backfill', err, { did: site.did, rkey: site.rkey });
120
+
console.log(`โ [${processed}/${sites.length}] Failed: ${site.display_name || site.rkey}`);
121
+
}
122
+
};
123
+
124
+
// Add to executing pool and remove when done
125
+
const promise = processSite().finally(() => executing.delete(promise));
126
+
executing.add(promise);
127
+
128
+
// When pool is full, wait for at least one to complete
129
+
if (executing.size >= concurrency) {
130
+
await Promise.race(executing);
131
+
}
127
132
}
133
+
134
+
// Wait for all remaining tasks to complete
135
+
await Promise.all(executing);
128
136
129
137
stats.duration = Date.now() - startTime;
130
138
+35
-43
apps/hosting-service/src/lib/cache.ts
+35
-43
apps/hosting-service/src/lib/cache.ts
···
1
-
// In-memory LRU cache for file contents and metadata
1
+
/**
2
+
* Cache management for wisp-hosting-service
3
+
*
4
+
* With tiered storage, most caching is handled transparently.
5
+
* This module tracks sites being cached and manages rewritten HTML cache.
6
+
*/
2
7
8
+
import { storage } from './storage';
9
+
10
+
// In-memory LRU cache for rewritten HTML (for path rewriting in subdomain routes)
3
11
interface CacheEntry<T> {
4
12
value: T;
5
13
size: number;
···
96
104
return true;
97
105
}
98
106
99
-
// Invalidate all entries for a specific site
100
-
invalidateSite(did: string, rkey: string): number {
101
-
const prefix = `${did}:${rkey}:`;
102
-
let count = 0;
103
-
104
-
for (const key of Array.from(this.cache.keys())) {
105
-
if (key.startsWith(prefix)) {
106
-
this.delete(key);
107
-
count++;
108
-
}
109
-
}
110
-
111
-
return count;
112
-
}
113
-
114
-
// Get cache size
115
107
size(): number {
116
108
return this.cache.size;
117
109
}
···
127
119
return { ...this.stats };
128
120
}
129
121
130
-
// Get cache hit rate
131
122
getHitRate(): number {
132
123
const total = this.stats.hits + this.stats.misses;
133
124
return total === 0 ? 0 : (this.stats.hits / total) * 100;
134
125
}
135
126
}
136
127
137
-
// File metadata cache entry
138
-
export interface FileMetadata {
139
-
encoding?: 'gzip';
140
-
mimeType: string;
141
-
}
142
-
143
-
// Global cache instances
144
-
const FILE_CACHE_SIZE = 100 * 1024 * 1024; // 100MB
145
-
const FILE_CACHE_COUNT = 500;
146
-
const METADATA_CACHE_COUNT = 2000;
147
-
148
-
export const fileCache = new LRUCache<Buffer>(FILE_CACHE_SIZE, FILE_CACHE_COUNT);
149
-
export const metadataCache = new LRUCache<FileMetadata>(1024 * 1024, METADATA_CACHE_COUNT); // 1MB for metadata
128
+
// Rewritten HTML cache: stores HTML after path rewriting for subdomain routes
150
129
export const rewrittenHtmlCache = new LRUCache<Buffer>(50 * 1024 * 1024, 200); // 50MB for rewritten HTML
151
130
152
-
// Helper to generate cache keys
131
+
// Helper to generate cache keys for rewritten HTML
153
132
export function getCacheKey(did: string, rkey: string, filePath: string, suffix?: string): string {
154
133
const base = `${did}:${rkey}:${filePath}`;
155
134
return suffix ? `${base}:${suffix}` : base;
156
135
}
157
136
158
-
// Invalidate all caches for a site
159
-
export function invalidateSiteCache(did: string, rkey: string): void {
160
-
const fileCount = fileCache.invalidateSite(did, rkey);
161
-
const metaCount = metadataCache.invalidateSite(did, rkey);
162
-
const htmlCount = rewrittenHtmlCache.invalidateSite(did, rkey);
137
+
/**
138
+
* Invalidate site cache via tiered storage
139
+
* Also invalidates locally cached rewritten HTML
140
+
*/
141
+
export async function invalidateSiteCache(did: string, rkey: string): Promise<void> {
142
+
// Invalidate in tiered storage
143
+
const prefix = `${did}/${rkey}/`;
144
+
const deleted = await storage.invalidate(prefix);
163
145
164
-
console.log(`[Cache] Invalidated site ${did}:${rkey} - ${fileCount} files, ${metaCount} metadata, ${htmlCount} HTML`);
146
+
// Invalidate rewritten HTML cache for this site
147
+
const sitePrefix = `${did}:${rkey}:`;
148
+
let htmlCount = 0;
149
+
const cacheKeys = Array.from((rewrittenHtmlCache as any).cache?.keys() || []) as string[];
150
+
for (const key of cacheKeys) {
151
+
if (key.startsWith(sitePrefix)) {
152
+
rewrittenHtmlCache.delete(key);
153
+
htmlCount++;
154
+
}
155
+
}
156
+
157
+
console.log(`[Cache] Invalidated site ${did}:${rkey} - ${deleted} files in tiered storage, ${htmlCount} rewritten HTML`);
165
158
}
166
159
167
160
// Track sites currently being cached (to prevent serving stale cache during updates)
···
183
176
}
184
177
185
178
// Get overall cache statistics
186
-
export function getCacheStats() {
179
+
export async function getCacheStats() {
180
+
const tieredStats = await storage.getStats();
181
+
187
182
return {
188
-
files: fileCache.getStats(),
189
-
fileHitRate: fileCache.getHitRate(),
190
-
metadata: metadataCache.getStats(),
191
-
metadataHitRate: metadataCache.getHitRate(),
183
+
tieredStorage: tieredStats,
192
184
rewrittenHtml: rewrittenHtmlCache.getStats(),
193
185
rewrittenHtmlHitRate: rewrittenHtmlCache.getHitRate(),
194
186
sitesBeingCached: sitesBeingCached.size,
+32
-1
apps/hosting-service/src/lib/db.ts
+32
-1
apps/hosting-service/src/lib/db.ts
···
183
183
return hashNum & 0x7FFFFFFFFFFFFFFFn;
184
184
}
185
185
186
+
// Track active locks for cleanup on shutdown
187
+
const activeLocks = new Set<string>();
188
+
186
189
/**
187
190
* Acquire a distributed lock using PostgreSQL advisory locks
188
191
* Returns true if lock was acquired, false if already held by another instance
···
193
196
194
197
try {
195
198
const result = await sql`SELECT pg_try_advisory_lock(${Number(lockId)}) as acquired`;
196
-
return result[0]?.acquired === true;
199
+
const acquired = result[0]?.acquired === true;
200
+
if (acquired) {
201
+
activeLocks.add(key);
202
+
}
203
+
return acquired;
197
204
} catch (err) {
198
205
console.error('Failed to acquire lock', { key, error: err });
199
206
return false;
···
208
215
209
216
try {
210
217
await sql`SELECT pg_advisory_unlock(${Number(lockId)})`;
218
+
activeLocks.delete(key);
211
219
} catch (err) {
212
220
console.error('Failed to release lock', { key, error: err });
221
+
// Still remove from tracking even if unlock fails
222
+
activeLocks.delete(key);
223
+
}
224
+
}
225
+
226
+
/**
227
+
* Close all database connections
228
+
* Call this during graceful shutdown
229
+
*/
230
+
export async function closeDatabase(): Promise<void> {
231
+
try {
232
+
// Release all active advisory locks before closing connections
233
+
if (activeLocks.size > 0) {
234
+
console.log(`[DB] Releasing ${activeLocks.size} active advisory locks before shutdown`);
235
+
for (const key of activeLocks) {
236
+
await releaseLock(key);
237
+
}
238
+
}
239
+
240
+
await sql.end({ timeout: 5 });
241
+
console.log('[DB] Database connections closed');
242
+
} catch (err) {
243
+
console.error('[DB] Error closing database connections:', err);
213
244
}
214
245
}
215
246
+64
-76
apps/hosting-service/src/lib/file-serving.ts
+64
-76
apps/hosting-service/src/lib/file-serving.ts
···
7
7
import { lookup } from 'mime-types';
8
8
import type { Record as WispSettings } from '@wisp/lexicons/types/place/wisp/settings';
9
9
import { shouldCompressMimeType } from '@wisp/atproto-utils/compression';
10
-
import { fileCache, metadataCache, rewrittenHtmlCache, getCacheKey, isSiteBeingCached } from './cache';
10
+
import { rewrittenHtmlCache, getCacheKey, isSiteBeingCached } from './cache';
11
11
import { getCachedFilePath, getCachedSettings } from './utils';
12
12
import { loadRedirectRules, matchRedirectRule, parseCookies, parseQueryString } from './redirects';
13
13
import { rewriteHtmlPaths, isHtmlContent } from './html-rewriter';
14
14
import { generate404Page, generateDirectoryListing, siteUpdatingResponse } from './page-generators';
15
15
import { getIndexFiles, applyCustomHeaders, fileExists } from './request-utils';
16
16
import { getRedirectRulesFromCache, setRedirectRulesInCache } from './site-cache';
17
+
import { storage } from './storage';
18
+
19
+
/**
20
+
* Helper to retrieve a file with metadata from tiered storage
21
+
*/
22
+
async function getFileWithMetadata(did: string, rkey: string, filePath: string) {
23
+
const key = `${did}/${rkey}/${filePath}`;
24
+
return await storage.getWithMetadata(key);
25
+
}
17
26
18
27
/**
19
28
* Helper to serve files from cache (for custom domains and subdomains)
···
176
185
177
186
// Not a directory, try to serve as a file
178
187
const fileRequestPath: string = requestPath || indexFiles[0] || 'index.html';
179
-
const cacheKey = getCacheKey(did, rkey, fileRequestPath);
180
-
const cachedFile = getCachedFilePath(did, rkey, fileRequestPath);
181
188
182
-
// Check in-memory cache first
183
-
let content = fileCache.get(cacheKey);
184
-
let meta = metadataCache.get(cacheKey);
189
+
// Retrieve from tiered storage
190
+
const result = await getFileWithMetadata(did, rkey, fileRequestPath);
185
191
186
-
if (!content && await fileExists(cachedFile)) {
187
-
// Read from disk and cache
188
-
content = await readFile(cachedFile);
189
-
fileCache.set(cacheKey, content, content.length);
192
+
if (result) {
193
+
const content = Buffer.from(result.data);
194
+
const meta = result.metadata.customMetadata as { encoding?: string; mimeType?: string } | undefined;
190
195
191
-
const metaFile = `${cachedFile}.meta`;
192
-
if (await fileExists(metaFile)) {
193
-
const metaJson = await readFile(metaFile, 'utf-8');
194
-
meta = JSON.parse(metaJson);
195
-
metadataCache.set(cacheKey, meta!, JSON.stringify(meta).length);
196
-
}
197
-
}
198
-
199
-
if (content) {
200
196
// Build headers with caching
201
-
const headers: Record<string, string> = {};
197
+
const headers: Record<string, string> = {
198
+
'X-Cache-Tier': result.source,
199
+
};
202
200
203
-
if (meta && meta.encoding === 'gzip' && meta.mimeType) {
201
+
if (meta?.encoding === 'gzip' && meta.mimeType) {
204
202
const shouldServeCompressed = shouldCompressMimeType(meta.mimeType);
205
203
206
204
if (!shouldServeCompressed) {
···
233
231
}
234
232
235
233
// Non-compressed files
236
-
const mimeType = lookup(cachedFile) || 'application/octet-stream';
234
+
const mimeType = meta?.mimeType || lookup(fileRequestPath) || 'application/octet-stream';
237
235
headers['Content-Type'] = mimeType;
238
236
headers['Cache-Control'] = mimeType.startsWith('text/html')
239
237
? 'public, max-age=300'
···
246
244
if (!fileRequestPath.includes('.')) {
247
245
for (const indexFileName of indexFiles) {
248
246
const indexPath = fileRequestPath ? `${fileRequestPath}/${indexFileName}` : indexFileName;
249
-
const indexCacheKey = getCacheKey(did, rkey, indexPath);
250
-
const indexFile = getCachedFilePath(did, rkey, indexPath);
251
247
252
-
let indexContent = fileCache.get(indexCacheKey);
253
-
let indexMeta = metadataCache.get(indexCacheKey);
248
+
const indexResult = await getFileWithMetadata(did, rkey, indexPath);
254
249
255
-
if (!indexContent && await fileExists(indexFile)) {
256
-
indexContent = await readFile(indexFile);
257
-
fileCache.set(indexCacheKey, indexContent, indexContent.length);
250
+
if (indexResult) {
251
+
const indexContent = Buffer.from(indexResult.data);
252
+
const indexMeta = indexResult.metadata.customMetadata as { encoding?: string; mimeType?: string } | undefined;
258
253
259
-
const indexMetaFile = `${indexFile}.meta`;
260
-
if (await fileExists(indexMetaFile)) {
261
-
const metaJson = await readFile(indexMetaFile, 'utf-8');
262
-
indexMeta = JSON.parse(metaJson);
263
-
metadataCache.set(indexCacheKey, indexMeta!, JSON.stringify(indexMeta).length);
264
-
}
265
-
}
266
-
267
-
if (indexContent) {
268
254
const headers: Record<string, string> = {
269
255
'Content-Type': 'text/html; charset=utf-8',
270
256
'Cache-Control': 'public, max-age=300',
257
+
'X-Cache-Tier': indexResult.source,
271
258
};
272
259
273
-
if (indexMeta && indexMeta.encoding === 'gzip') {
260
+
if (indexMeta?.encoding === 'gzip') {
274
261
headers['Content-Encoding'] = 'gzip';
275
262
}
276
263
···
556
543
557
544
// Not a directory, try to serve as a file
558
545
const fileRequestPath: string = requestPath || indexFiles[0] || 'index.html';
559
-
const cacheKey = getCacheKey(did, rkey, fileRequestPath);
560
-
const cachedFile = getCachedFilePath(did, rkey, fileRequestPath);
561
546
562
547
// Check for rewritten HTML in cache first (if it's HTML)
563
548
const mimeTypeGuess = lookup(fileRequestPath) || 'application/octet-stream';
···
565
550
const rewrittenKey = getCacheKey(did, rkey, fileRequestPath, `rewritten:${basePath}`);
566
551
const rewrittenContent = rewrittenHtmlCache.get(rewrittenKey);
567
552
if (rewrittenContent) {
553
+
console.log(`[HTML Rewrite] Serving from rewritten cache: ${rewrittenKey}`);
568
554
const headers: Record<string, string> = {
569
555
'Content-Type': 'text/html; charset=utf-8',
570
556
'Content-Encoding': 'gzip',
571
557
'Cache-Control': 'public, max-age=300',
558
+
'X-Cache-Tier': 'local', // Rewritten HTML is stored locally
572
559
};
573
560
applyCustomHeaders(headers, fileRequestPath, settings);
574
561
return new Response(rewrittenContent, { headers });
575
562
}
576
563
}
577
564
578
-
// Check in-memory file cache
579
-
let content = fileCache.get(cacheKey);
580
-
let meta = metadataCache.get(cacheKey);
565
+
// Retrieve from tiered storage
566
+
const result = await getFileWithMetadata(did, rkey, fileRequestPath);
581
567
582
-
if (!content && await fileExists(cachedFile)) {
583
-
// Read from disk and cache
584
-
content = await readFile(cachedFile);
585
-
fileCache.set(cacheKey, content, content.length);
568
+
if (result) {
569
+
const content = Buffer.from(result.data);
570
+
const meta = result.metadata.customMetadata as { encoding?: string; mimeType?: string } | undefined;
571
+
const mimeType = meta?.mimeType || lookup(fileRequestPath) || 'application/octet-stream';
572
+
const isGzipped = meta?.encoding === 'gzip';
586
573
587
-
const metaFile = `${cachedFile}.meta`;
588
-
if (await fileExists(metaFile)) {
589
-
const metaJson = await readFile(metaFile, 'utf-8');
590
-
meta = JSON.parse(metaJson);
591
-
metadataCache.set(cacheKey, meta!, JSON.stringify(meta).length);
592
-
}
593
-
}
594
-
595
-
if (content) {
596
-
const mimeType = meta?.mimeType || lookup(cachedFile) || 'application/octet-stream';
597
-
const isGzipped = meta?.encoding === 'gzip';
574
+
console.log(`[File Serve] Serving ${fileRequestPath}, mimeType: ${mimeType}, isHTML: ${isHtmlContent(fileRequestPath, mimeType)}, basePath: ${basePath}`);
598
575
599
576
// Check if this is HTML content that needs rewriting
600
577
if (isHtmlContent(fileRequestPath, mimeType)) {
578
+
console.log(`[HTML Rewrite] Processing ${fileRequestPath}, basePath: ${basePath}, mimeType: ${mimeType}, isGzipped: ${isGzipped}`);
601
579
let htmlContent: string;
602
580
if (isGzipped) {
603
581
// Verify content is actually gzipped
···
612
590
} else {
613
591
htmlContent = content.toString('utf-8');
614
592
}
593
+
// Check for <base> tag which can override paths
594
+
const baseTagMatch = htmlContent.match(/<base\s+[^>]*href=["'][^"']+["'][^>]*>/i);
595
+
if (baseTagMatch) {
596
+
console.warn(`[HTML Rewrite] WARNING: <base> tag found: ${baseTagMatch[0]} - this may override path rewrites`);
597
+
}
598
+
599
+
// Find src/href attributes (quoted and unquoted) to debug
600
+
const allMatches = htmlContent.match(/(?:src|href)\s*=\s*["']?\/[^"'\s>]+/g);
601
+
console.log(`[HTML Rewrite] Found ${allMatches ? allMatches.length : 0} local path attrs`);
602
+
if (allMatches && allMatches.length > 0) {
603
+
console.log(`[HTML Rewrite] Sample paths: ${allMatches.slice(0, 5).join(', ')}`);
604
+
}
605
+
615
606
const rewritten = rewriteHtmlPaths(htmlContent, basePath, fileRequestPath);
616
607
608
+
const rewrittenMatches = rewritten.match(/(?:src|href)\s*=\s*["']?\/[^"'\s>]+/g);
609
+
console.log(`[HTML Rewrite] After rewrite, found ${rewrittenMatches ? rewrittenMatches.length : 0} local paths`);
610
+
if (rewrittenMatches && rewrittenMatches.length > 0) {
611
+
console.log(`[HTML Rewrite] Sample rewritten: ${rewrittenMatches.slice(0, 5).join(', ')}`);
612
+
}
613
+
617
614
// Recompress and cache the rewritten HTML
618
615
const { gzipSync } = await import('zlib');
619
616
const recompressed = gzipSync(Buffer.from(rewritten, 'utf-8'));
···
625
622
'Content-Type': 'text/html; charset=utf-8',
626
623
'Content-Encoding': 'gzip',
627
624
'Cache-Control': 'public, max-age=300',
625
+
'X-Cache-Tier': result.source,
628
626
};
629
627
applyCustomHeaders(htmlHeaders, fileRequestPath, settings);
630
628
return new Response(recompressed, { headers: htmlHeaders });
···
634
632
const headers: Record<string, string> = {
635
633
'Content-Type': mimeType,
636
634
'Cache-Control': 'public, max-age=31536000, immutable',
635
+
'X-Cache-Tier': result.source,
637
636
};
638
637
639
638
if (isGzipped) {
···
663
662
if (!fileRequestPath.includes('.')) {
664
663
for (const indexFileName of indexFiles) {
665
664
const indexPath = fileRequestPath ? `${fileRequestPath}/${indexFileName}` : indexFileName;
666
-
const indexCacheKey = getCacheKey(did, rkey, indexPath);
667
-
const indexFile = getCachedFilePath(did, rkey, indexPath);
668
665
669
666
// Check for rewritten index file in cache
670
667
const rewrittenKey = getCacheKey(did, rkey, indexPath, `rewritten:${basePath}`);
···
674
671
'Content-Type': 'text/html; charset=utf-8',
675
672
'Content-Encoding': 'gzip',
676
673
'Cache-Control': 'public, max-age=300',
674
+
'X-Cache-Tier': 'local', // Rewritten HTML is stored locally
677
675
};
678
676
applyCustomHeaders(headers, indexPath, settings);
679
677
return new Response(rewrittenContent, { headers });
680
678
}
681
679
682
-
let indexContent = fileCache.get(indexCacheKey);
683
-
let indexMeta = metadataCache.get(indexCacheKey);
680
+
const indexResult = await getFileWithMetadata(did, rkey, indexPath);
684
681
685
-
if (!indexContent && await fileExists(indexFile)) {
686
-
indexContent = await readFile(indexFile);
687
-
fileCache.set(indexCacheKey, indexContent, indexContent.length);
688
-
689
-
const indexMetaFile = `${indexFile}.meta`;
690
-
if (await fileExists(indexMetaFile)) {
691
-
const metaJson = await readFile(indexMetaFile, 'utf-8');
692
-
indexMeta = JSON.parse(metaJson);
693
-
metadataCache.set(indexCacheKey, indexMeta!, JSON.stringify(indexMeta).length);
694
-
}
695
-
}
696
-
697
-
if (indexContent) {
682
+
if (indexResult) {
683
+
const indexContent = Buffer.from(indexResult.data);
684
+
const indexMeta = indexResult.metadata.customMetadata as { encoding?: string; mimeType?: string } | undefined;
698
685
const isGzipped = indexMeta?.encoding === 'gzip';
699
686
700
687
let htmlContent: string;
···
722
709
'Content-Type': 'text/html; charset=utf-8',
723
710
'Content-Encoding': 'gzip',
724
711
'Cache-Control': 'public, max-age=300',
712
+
'X-Cache-Tier': indexResult.source,
725
713
};
726
714
applyCustomHeaders(headers, indexPath, settings);
727
715
return new Response(recompressed, { headers });
+46
-35
apps/hosting-service/src/lib/firehose.ts
+46
-35
apps/hosting-service/src/lib/firehose.ts
···
1
-
import { existsSync, rmSync } from 'fs'
1
+
import { existsSync } from 'fs'
2
2
import {
3
3
getPdsForDid,
4
4
downloadAndCacheSite,
···
13
13
import { invalidateSiteCache, markSiteAsBeingCached, unmarkSiteAsBeingCached } from './cache'
14
14
import { clearRedirectRulesCache } from './site-cache'
15
15
16
-
const CACHE_DIR = './cache/sites'
16
+
const CACHE_DIR = process.env.CACHE_DIR || './cache/sites'
17
17
18
18
export class FirehoseWorker {
19
19
private firehose: Firehose | null = null
20
20
private idResolver: IdResolver
21
21
private isShuttingDown = false
22
22
private lastEventTime = Date.now()
23
+
private eventCount = 0
23
24
private cacheCleanupInterval: NodeJS.Timeout | null = null
25
+
private healthCheckInterval: NodeJS.Timeout | null = null
24
26
25
27
constructor(
26
28
private logger?: (msg: string, data?: Record<string, unknown>) => void
···
47
49
48
50
this.log('IdResolver cache cleared')
49
51
}, 60 * 60 * 1000) // Every hour
52
+
53
+
// Health check: log if no events received for 30 seconds
54
+
this.healthCheckInterval = setInterval(() => {
55
+
if (this.isShuttingDown) return
56
+
57
+
const timeSinceLastEvent = Date.now() - this.lastEventTime
58
+
if (timeSinceLastEvent > 30000 && this.eventCount === 0) {
59
+
this.log('Warning: No firehose events received in the last 30 seconds', {
60
+
timeSinceLastEvent,
61
+
eventsReceived: this.eventCount
62
+
})
63
+
} else if (timeSinceLastEvent > 60000) {
64
+
this.log('Firehose status check', {
65
+
timeSinceLastEvent,
66
+
eventsReceived: this.eventCount
67
+
})
68
+
}
69
+
}, 30000) // Every 30 seconds
50
70
}
51
71
52
72
start() {
···
61
81
if (this.cacheCleanupInterval) {
62
82
clearInterval(this.cacheCleanupInterval)
63
83
this.cacheCleanupInterval = null
84
+
}
85
+
86
+
if (this.healthCheckInterval) {
87
+
clearInterval(this.healthCheckInterval)
88
+
this.healthCheckInterval = null
64
89
}
65
90
66
91
if (this.firehose) {
···
80
105
filterCollections: ['place.wisp.fs', 'place.wisp.settings'],
81
106
handleEvent: async (evt: any) => {
82
107
this.lastEventTime = Date.now()
108
+
this.eventCount++
109
+
110
+
if (this.eventCount === 1) {
111
+
this.log('First firehose event received - connection established', {
112
+
eventType: evt.event,
113
+
collection: evt.collection
114
+
})
115
+
}
83
116
84
117
// Watch for write events
85
118
if (evt.event === 'create' || evt.event === 'update') {
···
189
222
}
190
223
})
191
224
192
-
this.firehose.start()
193
-
this.log('Firehose started')
225
+
this.firehose.start().catch((err: unknown) => {
226
+
this.log('Fatal firehose error', {
227
+
error: err instanceof Error ? err.message : String(err)
228
+
})
229
+
console.error('Fatal firehose error:', err)
230
+
})
231
+
this.log('Firehose starting')
194
232
}
195
233
196
234
private async handleCreateOrUpdate(
···
250
288
}
251
289
252
290
// Invalidate in-memory caches before updating
253
-
invalidateSiteCache(did, site)
291
+
await invalidateSiteCache(did, site)
254
292
255
293
// Mark site as being cached to prevent serving stale content during update
256
294
markSiteAsBeingCached(did, site)
···
340
378
})
341
379
}
342
380
343
-
// Invalidate in-memory caches
344
-
invalidateSiteCache(did, site)
345
-
346
-
// Delete disk cache
347
-
this.deleteCache(did, site)
381
+
// Invalidate all caches (tiered storage invalidation is handled by invalidateSiteCache)
382
+
await invalidateSiteCache(did, site)
348
383
349
384
this.log('Successfully processed delete', { did, site })
350
385
}
···
353
388
this.log('Processing settings change', { did, rkey })
354
389
355
390
// Invalidate in-memory caches (includes metadata which stores settings)
356
-
invalidateSiteCache(did, rkey)
391
+
await invalidateSiteCache(did, rkey)
357
392
358
393
// Check if site is already cached
359
394
const cacheDir = `${CACHE_DIR}/${did}/${rkey}`
···
413
448
}
414
449
415
450
this.log('Successfully processed settings change', { did, rkey })
416
-
}
417
-
418
-
private deleteCache(did: string, site: string) {
419
-
const cacheDir = `${CACHE_DIR}/${did}/${site}`
420
-
421
-
if (!existsSync(cacheDir)) {
422
-
this.log('Cache directory does not exist, nothing to delete', {
423
-
did,
424
-
site
425
-
})
426
-
return
427
-
}
428
-
429
-
try {
430
-
rmSync(cacheDir, { recursive: true, force: true })
431
-
this.log('Cache deleted', { did, site, path: cacheDir })
432
-
} catch (err) {
433
-
this.log('Failed to delete cache', {
434
-
did,
435
-
site,
436
-
path: cacheDir,
437
-
error: err instanceof Error ? err.message : String(err)
438
-
})
439
-
}
440
451
}
441
452
442
453
getHealth() {
+16
apps/hosting-service/src/lib/html-rewriter.ts
+16
apps/hosting-service/src/lib/html-rewriter.ts
···
189
189
`\\b${attr}[ \\t]{0,5}=[ \\t]{0,5}'([^']*)'`,
190
190
'gi'
191
191
)
192
+
// Unquoted attributes (valid in HTML5 for values without spaces/special chars)
193
+
// Match: attr=value where value starts immediately (no quotes) and continues until space or >
194
+
// Use negative lookahead to ensure we don't match quoted attributes
195
+
const unquotedRegex = new RegExp(
196
+
`\\b${attr}[ \\t]{0,5}=[ \\t]{0,5}(?!["'])([^\\s>]+)`,
197
+
'gi'
198
+
)
192
199
193
200
rewritten = rewritten.replace(doubleQuoteRegex, (match, value) => {
194
201
const rewrittenValue = rewritePath(
···
206
213
documentPath
207
214
)
208
215
return `${attr}='${rewrittenValue}'`
216
+
})
217
+
218
+
rewritten = rewritten.replace(unquotedRegex, (match, value) => {
219
+
const rewrittenValue = rewritePath(
220
+
value,
221
+
normalizedBase,
222
+
documentPath
223
+
)
224
+
return `${attr}=${rewrittenValue}`
209
225
})
210
226
}
211
227
}
+1
-1
apps/hosting-service/src/lib/site-cache.ts
+1
-1
apps/hosting-service/src/lib/site-cache.ts
+270
apps/hosting-service/src/lib/storage.ts
+270
apps/hosting-service/src/lib/storage.ts
···
1
+
/**
2
+
* Tiered storage configuration for wisp-hosting-service
3
+
*
4
+
* Implements a three-tier caching strategy:
5
+
* - Hot (Memory): Instant access for frequently used files (index.html, CSS, JS)
6
+
* - Warm (Disk): Local cache with eviction policy
7
+
* - Cold (S3/R2): Object storage as source of truth (optional)
8
+
*
9
+
* When S3 is not configured, falls back to disk-only mode (warm tier acts as source of truth).
10
+
* In cache-only mode (non-master nodes), S3 writes are skipped even if configured.
11
+
*/
12
+
13
+
import {
14
+
TieredStorage,
15
+
MemoryStorageTier,
16
+
DiskStorageTier,
17
+
S3StorageTier,
18
+
type StorageTier,
19
+
type StorageMetadata,
20
+
} from 'tiered-storage';
21
+
22
+
const CACHE_DIR = process.env.CACHE_DIR || './cache/sites';
23
+
const HOT_CACHE_SIZE = parseInt(process.env.HOT_CACHE_SIZE || '104857600', 10); // 100MB default
24
+
const HOT_CACHE_COUNT = parseInt(process.env.HOT_CACHE_COUNT || '500', 10);
25
+
const WARM_CACHE_SIZE = parseInt(process.env.WARM_CACHE_SIZE || '10737418240', 10); // 10GB default
26
+
const WARM_EVICTION_POLICY = (process.env.WARM_EVICTION_POLICY || 'lru') as 'lru' | 'fifo' | 'size';
27
+
28
+
// Cache-only mode: skip S3 writes (non-master nodes)
29
+
// This is the same flag used to skip database writes
30
+
const CACHE_ONLY_MODE = process.env.CACHE_ONLY_MODE === 'true';
31
+
32
+
// S3/Cold tier configuration (optional)
33
+
const S3_BUCKET = process.env.S3_BUCKET || '';
34
+
const S3_METADATA_BUCKET = process.env.S3_METADATA_BUCKET;
35
+
const S3_REGION = process.env.S3_REGION || 'us-east-1';
36
+
const S3_ENDPOINT = process.env.S3_ENDPOINT;
37
+
const S3_FORCE_PATH_STYLE = process.env.S3_FORCE_PATH_STYLE !== 'false';
38
+
const AWS_ACCESS_KEY_ID = process.env.AWS_ACCESS_KEY_ID;
39
+
const AWS_SECRET_ACCESS_KEY = process.env.AWS_SECRET_ACCESS_KEY;
40
+
const S3_PREFIX = process.env.S3_PREFIX || 'sites/';
41
+
42
+
// Identity serializers for raw binary data (no JSON transformation)
43
+
// Files are stored as-is without any encoding/decoding
44
+
const identitySerialize = async (data: unknown): Promise<Uint8Array> => {
45
+
if (data instanceof Uint8Array) return data;
46
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
47
+
if (Buffer.isBuffer(data)) return new Uint8Array(data);
48
+
// For other types, fall back to JSON (shouldn't happen with file storage)
49
+
return new TextEncoder().encode(JSON.stringify(data));
50
+
};
51
+
52
+
const identityDeserialize = async (data: Uint8Array): Promise<unknown> => {
53
+
// Return as-is for binary file storage
54
+
return data;
55
+
};
56
+
57
+
/**
58
+
* Read-only wrapper for S3 tier in cache-only mode.
59
+
* Allows reads from S3 but skips all writes (for non-master nodes).
60
+
*/
61
+
class ReadOnlyS3Tier implements StorageTier {
62
+
private static hasLoggedWriteSkip = false;
63
+
64
+
constructor(private tier: StorageTier) {}
65
+
66
+
// Read operations - pass through to underlying tier
67
+
async get(key: string) {
68
+
return this.tier.get(key);
69
+
}
70
+
71
+
async getWithMetadata(key: string) {
72
+
return this.tier.getWithMetadata?.(key) ?? null;
73
+
}
74
+
75
+
async getStream(key: string) {
76
+
return this.tier.getStream?.(key) ?? null;
77
+
}
78
+
79
+
async exists(key: string) {
80
+
return this.tier.exists(key);
81
+
}
82
+
83
+
async getMetadata(key: string) {
84
+
return this.tier.getMetadata(key);
85
+
}
86
+
87
+
async *listKeys(prefix?: string) {
88
+
yield* this.tier.listKeys(prefix);
89
+
}
90
+
91
+
async getStats() {
92
+
return this.tier.getStats();
93
+
}
94
+
95
+
// Write operations - no-op in cache-only mode
96
+
async set(key: string, _data: Uint8Array, _metadata: StorageMetadata) {
97
+
this.logWriteSkip('set', key);
98
+
}
99
+
100
+
async setStream(key: string, _stream: NodeJS.ReadableStream, _metadata: StorageMetadata) {
101
+
this.logWriteSkip('setStream', key);
102
+
}
103
+
104
+
async setMetadata(key: string, _metadata: StorageMetadata) {
105
+
this.logWriteSkip('setMetadata', key);
106
+
}
107
+
108
+
async delete(key: string) {
109
+
this.logWriteSkip('delete', key);
110
+
}
111
+
112
+
async deleteMany(keys: string[]) {
113
+
this.logWriteSkip('deleteMany', `${keys.length} keys`);
114
+
}
115
+
116
+
async clear() {
117
+
this.logWriteSkip('clear', 'all keys');
118
+
}
119
+
120
+
private logWriteSkip(operation: string, key: string) {
121
+
// Only log once to avoid spam
122
+
if (!ReadOnlyS3Tier.hasLoggedWriteSkip) {
123
+
console.log(`[Storage] Cache-only mode: skipping S3 writes (operation: ${operation})`);
124
+
ReadOnlyS3Tier.hasLoggedWriteSkip = true;
125
+
}
126
+
}
127
+
}
128
+
129
+
/**
130
+
* Initialize tiered storage
131
+
* Must be called before serving requests
132
+
*/
133
+
function initializeStorage(): TieredStorage<Uint8Array> {
134
+
// Determine cold tier: S3 if configured, otherwise disk acts as cold
135
+
let coldTier: StorageTier;
136
+
let warmTier: StorageTier | undefined;
137
+
138
+
const diskTier = new DiskStorageTier({
139
+
directory: CACHE_DIR,
140
+
maxSizeBytes: WARM_CACHE_SIZE,
141
+
evictionPolicy: WARM_EVICTION_POLICY,
142
+
encodeColons: false, // Preserve colons for readable DID paths on Unix/macOS
143
+
});
144
+
145
+
if (S3_BUCKET) {
146
+
// Full three-tier setup with S3 as cold storage
147
+
const s3Tier = new S3StorageTier({
148
+
bucket: S3_BUCKET,
149
+
metadataBucket: S3_METADATA_BUCKET,
150
+
region: S3_REGION,
151
+
endpoint: S3_ENDPOINT,
152
+
forcePathStyle: S3_FORCE_PATH_STYLE,
153
+
credentials:
154
+
AWS_ACCESS_KEY_ID && AWS_SECRET_ACCESS_KEY
155
+
? { accessKeyId: AWS_ACCESS_KEY_ID, secretAccessKey: AWS_SECRET_ACCESS_KEY }
156
+
: undefined,
157
+
prefix: S3_PREFIX,
158
+
});
159
+
160
+
// In cache-only mode, wrap S3 tier to make it read-only
161
+
coldTier = CACHE_ONLY_MODE ? new ReadOnlyS3Tier(s3Tier) : s3Tier;
162
+
warmTier = diskTier;
163
+
164
+
if (CACHE_ONLY_MODE) {
165
+
console.log('[Storage] Cache-only mode: S3 as read-only cold tier (no writes), disk as warm tier');
166
+
} else {
167
+
console.log('[Storage] Using S3 as cold tier, disk as warm tier');
168
+
}
169
+
} else {
170
+
// Disk-only mode: disk tier acts as source of truth (cold)
171
+
coldTier = diskTier;
172
+
warmTier = undefined;
173
+
console.log('[Storage] S3 not configured - using disk-only mode (disk as cold tier)');
174
+
}
175
+
176
+
const storage = new TieredStorage<Uint8Array>({
177
+
tiers: {
178
+
// Hot tier: In-memory LRU for instant serving
179
+
hot: new MemoryStorageTier({
180
+
maxSizeBytes: HOT_CACHE_SIZE,
181
+
maxItems: HOT_CACHE_COUNT,
182
+
}),
183
+
184
+
// Warm tier: Disk-based cache (only when S3 is configured)
185
+
warm: warmTier,
186
+
187
+
// Cold tier: S3/R2 as source of truth, or disk in disk-only mode
188
+
cold: coldTier,
189
+
},
190
+
191
+
// Placement rules: determine which tiers each file goes to
192
+
placementRules: [
193
+
// Metadata is critical: frequently accessed for cache validity checks
194
+
{
195
+
pattern: '**/.metadata.json',
196
+
tiers: ['hot', 'warm', 'cold'],
197
+
},
198
+
199
+
// index.html is critical: write to all tiers for instant serving
200
+
{
201
+
pattern: '**/index.html',
202
+
tiers: ['hot', 'warm', 'cold'],
203
+
},
204
+
{
205
+
pattern: 'index.html',
206
+
tiers: ['hot', 'warm', 'cold'],
207
+
},
208
+
209
+
// CSS and JS: eligible for hot tier if accessed frequently
210
+
{
211
+
pattern: '**/*.{css,js}',
212
+
tiers: ['hot', 'warm', 'cold'],
213
+
},
214
+
215
+
// Media files: never needed in memory, skip hot tier
216
+
{
217
+
pattern: '**/*.{jpg,jpeg,png,gif,webp,svg,ico,mp4,webm,mp3,woff,woff2,ttf,eot}',
218
+
tiers: ['warm', 'cold'],
219
+
},
220
+
221
+
// Default: everything else goes to warm and cold
222
+
{
223
+
pattern: '**',
224
+
tiers: ['warm', 'cold'],
225
+
},
226
+
],
227
+
228
+
// IMPORTANT: Compression is disabled at the tiered-storage level
229
+
// Text files (HTML, CSS, JS, JSON) are pre-compressed with gzip at the app level
230
+
// Binary files (images, video) are stored uncompressed as they're already compressed
231
+
// The file's compression state is tracked in customMetadata.encoding
232
+
compression: false,
233
+
234
+
// TTL for cache entries (14 days)
235
+
defaultTTL: 14 * 24 * 60 * 60 * 1000,
236
+
237
+
// Eager promotion: promote data to upper tiers on read
238
+
// This ensures frequently accessed files end up in hot tier
239
+
promotionStrategy: 'eager',
240
+
241
+
// Identity serialization: store raw binary without JSON transformation
242
+
serialization: {
243
+
serialize: identitySerialize,
244
+
deserialize: identityDeserialize,
245
+
},
246
+
});
247
+
248
+
return storage;
249
+
}
250
+
251
+
// Export singleton instance
252
+
export const storage = initializeStorage();
253
+
254
+
/**
255
+
* Get storage configuration summary for logging
256
+
*/
257
+
export function getStorageConfig() {
258
+
return {
259
+
cacheDir: CACHE_DIR,
260
+
hotCacheSize: `${(HOT_CACHE_SIZE / 1024 / 1024).toFixed(0)}MB`,
261
+
hotCacheCount: HOT_CACHE_COUNT,
262
+
warmCacheSize: `${(WARM_CACHE_SIZE / 1024 / 1024 / 1024).toFixed(1)}GB`,
263
+
warmEvictionPolicy: WARM_EVICTION_POLICY,
264
+
s3Bucket: S3_BUCKET,
265
+
s3Region: S3_REGION,
266
+
s3Endpoint: S3_ENDPOINT || '(default AWS S3)',
267
+
s3Prefix: S3_PREFIX,
268
+
metadataBucket: S3_METADATA_BUCKET || '(embedded in data bucket)',
269
+
};
270
+
}
+473
-8
apps/hosting-service/src/lib/utils.test.ts
+473
-8
apps/hosting-service/src/lib/utils.test.ts
···
1
1
import { describe, test, expect } from 'bun:test'
2
-
import { sanitizePath, extractBlobCid } from './utils'
2
+
import { sanitizePath, extractBlobCid, extractSubfsUris, expandSubfsNodes } from './utils'
3
3
import { CID } from 'multiformats'
4
+
import { BlobRef } from '@atproto/lexicon'
5
+
import type {
6
+
Record as WispFsRecord,
7
+
Directory as FsDirectory,
8
+
Entry as FsEntry,
9
+
File as FsFile,
10
+
Subfs as FsSubfs,
11
+
} from '@wisp/lexicons/types/place/wisp/fs'
12
+
import type {
13
+
Record as SubfsRecord,
14
+
Directory as SubfsDirectory,
15
+
Entry as SubfsEntry,
16
+
File as SubfsFile,
17
+
Subfs as SubfsSubfs,
18
+
} from '@wisp/lexicons/types/place/wisp/subfs'
19
+
import type { $Typed } from '@wisp/lexicons/util'
4
20
5
21
describe('sanitizePath', () => {
6
22
test('allows normal file paths', () => {
···
31
47
32
48
test('blocks directory traversal in middle of path', () => {
33
49
expect(sanitizePath('images/../../../etc/passwd')).toBe('images/etc/passwd')
34
-
// Note: sanitizePath only filters out ".." segments, doesn't resolve paths
35
50
expect(sanitizePath('a/b/../c')).toBe('a/b/c')
36
51
expect(sanitizePath('a/../b/../c')).toBe('a/b/c')
37
52
})
···
50
65
})
51
66
52
67
test('blocks null bytes', () => {
53
-
// Null bytes cause the entire segment to be filtered out
54
68
expect(sanitizePath('index.html\0.txt')).toBe('')
55
69
expect(sanitizePath('test\0')).toBe('')
56
-
// Null byte in middle segment
57
70
expect(sanitizePath('css/bad\0name/styles.css')).toBe('css/styles.css')
58
71
})
59
72
···
89
102
90
103
describe('extractBlobCid', () => {
91
104
const TEST_CID = 'bafkreid7ybejd5s2vv2j7d4aajjlmdgazguemcnuliiyfn6coxpwp2mi6y'
92
-
105
+
93
106
test('extracts CID from IPLD link', () => {
94
107
const blobRef = { $link: TEST_CID }
95
108
expect(extractBlobCid(blobRef)).toBe(TEST_CID)
···
103
116
})
104
117
105
118
test('extracts CID from typed BlobRef with IPLD link', () => {
106
-
const blobRef = {
119
+
const blobRef = {
107
120
ref: { $link: TEST_CID }
108
121
}
109
122
expect(extractBlobCid(blobRef)).toBe(TEST_CID)
···
129
142
})
130
143
131
144
test('handles nested structures from AT Proto API', () => {
132
-
// Real structure from AT Proto
133
145
const blobRef = {
134
146
$type: 'blob',
135
147
ref: CID.parse(TEST_CID),
···
150
162
})
151
163
152
164
test('prioritizes checking IPLD link first', () => {
153
-
// Direct $link takes precedence
154
165
const directLink = { $link: TEST_CID }
155
166
expect(extractBlobCid(directLink)).toBe(TEST_CID)
156
167
})
···
167
178
expect(extractBlobCid(blobRef)).toBe(cidV1)
168
179
})
169
180
})
181
+
182
+
const TEST_CID_BASE = 'bafkreid7ybejd5s2vv2j7d4aajjlmdgazguemcnuliiyfn6coxpwp2mi6y'
183
+
184
+
function createMockBlobRef(cidSuffix: string = '', size: number = 100, mimeType: string = 'text/plain'): BlobRef {
185
+
const cidString = TEST_CID_BASE
186
+
return new BlobRef(CID.parse(cidString), mimeType, size)
187
+
}
188
+
189
+
function createFsFile(
190
+
name: string,
191
+
options: { mimeType?: string; size?: number; encoding?: 'gzip'; base64?: boolean } = {}
192
+
): FsEntry {
193
+
const { mimeType = 'text/plain', size = 100, encoding, base64 } = options
194
+
const file: $Typed<FsFile, 'place.wisp.fs#file'> = {
195
+
$type: 'place.wisp.fs#file',
196
+
type: 'file',
197
+
blob: createMockBlobRef(name.replace(/[^a-z0-9]/gi, ''), size, mimeType),
198
+
...(encoding && { encoding }),
199
+
...(mimeType && { mimeType }),
200
+
...(base64 && { base64 }),
201
+
}
202
+
return { name, node: file }
203
+
}
204
+
205
+
function createFsDirectory(name: string, entries: FsEntry[]): FsEntry {
206
+
const dir: $Typed<FsDirectory, 'place.wisp.fs#directory'> = {
207
+
$type: 'place.wisp.fs#directory',
208
+
type: 'directory',
209
+
entries,
210
+
}
211
+
return { name, node: dir }
212
+
}
213
+
214
+
function createFsSubfs(name: string, subject: string, flat: boolean = true): FsEntry {
215
+
const subfs: $Typed<FsSubfs, 'place.wisp.fs#subfs'> = {
216
+
$type: 'place.wisp.fs#subfs',
217
+
type: 'subfs',
218
+
subject,
219
+
flat,
220
+
}
221
+
return { name, node: subfs }
222
+
}
223
+
224
+
function createFsRootDirectory(entries: FsEntry[]): FsDirectory {
225
+
return {
226
+
$type: 'place.wisp.fs#directory',
227
+
type: 'directory',
228
+
entries,
229
+
}
230
+
}
231
+
232
+
function createFsRecord(site: string, entries: FsEntry[], fileCount?: number): WispFsRecord {
233
+
return {
234
+
$type: 'place.wisp.fs',
235
+
site,
236
+
root: createFsRootDirectory(entries),
237
+
...(fileCount !== undefined && { fileCount }),
238
+
createdAt: new Date().toISOString(),
239
+
}
240
+
}
241
+
242
+
function createSubfsFile(
243
+
name: string,
244
+
options: { mimeType?: string; size?: number; encoding?: 'gzip'; base64?: boolean } = {}
245
+
): SubfsEntry {
246
+
const { mimeType = 'text/plain', size = 100, encoding, base64 } = options
247
+
const file: $Typed<SubfsFile, 'place.wisp.subfs#file'> = {
248
+
$type: 'place.wisp.subfs#file',
249
+
type: 'file',
250
+
blob: createMockBlobRef(name.replace(/[^a-z0-9]/gi, ''), size, mimeType),
251
+
...(encoding && { encoding }),
252
+
...(mimeType && { mimeType }),
253
+
...(base64 && { base64 }),
254
+
}
255
+
return { name, node: file }
256
+
}
257
+
258
+
function createSubfsDirectory(name: string, entries: SubfsEntry[]): SubfsEntry {
259
+
const dir: $Typed<SubfsDirectory, 'place.wisp.subfs#directory'> = {
260
+
$type: 'place.wisp.subfs#directory',
261
+
type: 'directory',
262
+
entries,
263
+
}
264
+
return { name, node: dir }
265
+
}
266
+
267
+
function createSubfsSubfs(name: string, subject: string): SubfsEntry {
268
+
const subfs: $Typed<SubfsSubfs, 'place.wisp.subfs#subfs'> = {
269
+
$type: 'place.wisp.subfs#subfs',
270
+
type: 'subfs',
271
+
subject,
272
+
}
273
+
return { name, node: subfs }
274
+
}
275
+
276
+
function createSubfsRootDirectory(entries: SubfsEntry[]): SubfsDirectory {
277
+
return {
278
+
$type: 'place.wisp.subfs#directory',
279
+
type: 'directory',
280
+
entries,
281
+
}
282
+
}
283
+
284
+
function createSubfsRecord(entries: SubfsEntry[], fileCount?: number): SubfsRecord {
285
+
return {
286
+
$type: 'place.wisp.subfs',
287
+
root: createSubfsRootDirectory(entries),
288
+
...(fileCount !== undefined && { fileCount }),
289
+
createdAt: new Date().toISOString(),
290
+
}
291
+
}
292
+
293
+
describe('extractSubfsUris', () => {
294
+
test('extracts subfs URIs from flat directory structure', () => {
295
+
const subfsUri = 'at://did:plc:test/place.wisp.subfs/a'
296
+
const dir = createFsRootDirectory([
297
+
createFsSubfs('a', subfsUri),
298
+
createFsFile('file.txt'),
299
+
])
300
+
301
+
const uris = extractSubfsUris(dir)
302
+
303
+
expect(uris).toHaveLength(1)
304
+
expect(uris[0]).toEqual({ uri: subfsUri, path: 'a' })
305
+
})
306
+
307
+
test('extracts subfs URIs from nested directory structure', () => {
308
+
const subfsAUri = 'at://did:plc:test/place.wisp.subfs/a'
309
+
const subfsBUri = 'at://did:plc:test/place.wisp.subfs/b'
310
+
311
+
const dir = createFsRootDirectory([
312
+
createFsSubfs('a', subfsAUri),
313
+
createFsDirectory('nested', [
314
+
createFsSubfs('b', subfsBUri),
315
+
createFsFile('file.txt'),
316
+
]),
317
+
])
318
+
319
+
const uris = extractSubfsUris(dir)
320
+
321
+
expect(uris).toHaveLength(2)
322
+
expect(uris).toContainEqual({ uri: subfsAUri, path: 'a' })
323
+
expect(uris).toContainEqual({ uri: subfsBUri, path: 'nested/b' })
324
+
})
325
+
326
+
test('returns empty array when no subfs nodes exist', () => {
327
+
const dir = createFsRootDirectory([
328
+
createFsFile('file1.txt'),
329
+
createFsDirectory('dir', [createFsFile('file2.txt')]),
330
+
])
331
+
332
+
const uris = extractSubfsUris(dir)
333
+
expect(uris).toHaveLength(0)
334
+
})
335
+
336
+
test('handles deeply nested subfs', () => {
337
+
const subfsUri = 'at://did:plc:test/place.wisp.subfs/deep'
338
+
const dir = createFsRootDirectory([
339
+
createFsDirectory('a', [
340
+
createFsDirectory('b', [
341
+
createFsDirectory('c', [
342
+
createFsSubfs('deep', subfsUri),
343
+
]),
344
+
]),
345
+
]),
346
+
])
347
+
348
+
const uris = extractSubfsUris(dir)
349
+
350
+
expect(uris).toHaveLength(1)
351
+
expect(uris[0]).toEqual({ uri: subfsUri, path: 'a/b/c/deep' })
352
+
})
353
+
})
354
+
355
+
describe('expandSubfsNodes caching', () => {
356
+
test('cache map is populated after expansion', async () => {
357
+
const subfsCache = new Map<string, SubfsRecord | null>()
358
+
const dir = createFsRootDirectory([createFsFile('file.txt')])
359
+
360
+
const result = await expandSubfsNodes(dir, 'https://pds.example.com', 0, subfsCache)
361
+
362
+
expect(subfsCache.size).toBe(0)
363
+
expect(result.entries).toHaveLength(1)
364
+
expect(result.entries[0]?.name).toBe('file.txt')
365
+
})
366
+
367
+
test('cache is passed through recursion depths', async () => {
368
+
const subfsCache = new Map<string, SubfsRecord | null>()
369
+
const mockSubfsUri = 'at://did:plc:test/place.wisp.subfs/cached'
370
+
const mockRecord = createSubfsRecord([createSubfsFile('cached-file.txt')])
371
+
subfsCache.set(mockSubfsUri, mockRecord)
372
+
373
+
const dir = createFsRootDirectory([createFsSubfs('cached', mockSubfsUri)])
374
+
const result = await expandSubfsNodes(dir, 'https://pds.example.com', 0, subfsCache)
375
+
376
+
expect(subfsCache.has(mockSubfsUri)).toBe(true)
377
+
expect(result.entries).toHaveLength(1)
378
+
expect(result.entries[0]?.name).toBe('cached-file.txt')
379
+
})
380
+
381
+
test('pre-populated cache prevents re-fetching', async () => {
382
+
const subfsCache = new Map<string, SubfsRecord | null>()
383
+
const subfsAUri = 'at://did:plc:test/place.wisp.subfs/a'
384
+
const subfsBUri = 'at://did:plc:test/place.wisp.subfs/b'
385
+
386
+
subfsCache.set(subfsAUri, createSubfsRecord([createSubfsSubfs('b', subfsBUri)]))
387
+
subfsCache.set(subfsBUri, createSubfsRecord([createSubfsFile('final.txt')]))
388
+
389
+
const dir = createFsRootDirectory([createFsSubfs('a', subfsAUri)])
390
+
const result = await expandSubfsNodes(dir, 'https://pds.example.com', 0, subfsCache)
391
+
392
+
expect(result.entries).toHaveLength(1)
393
+
expect(result.entries[0]?.name).toBe('final.txt')
394
+
})
395
+
396
+
test('diamond dependency uses cache for shared reference', async () => {
397
+
const subfsCache = new Map<string, SubfsRecord | null>()
398
+
const subfsAUri = 'at://did:plc:test/place.wisp.subfs/a'
399
+
const subfsBUri = 'at://did:plc:test/place.wisp.subfs/b'
400
+
const subfsCUri = 'at://did:plc:test/place.wisp.subfs/c'
401
+
402
+
subfsCache.set(subfsAUri, createSubfsRecord([createSubfsSubfs('c', subfsCUri)]))
403
+
subfsCache.set(subfsBUri, createSubfsRecord([createSubfsSubfs('c', subfsCUri)]))
404
+
subfsCache.set(subfsCUri, createSubfsRecord([createSubfsFile('shared.txt')]))
405
+
406
+
const dir = createFsRootDirectory([
407
+
createFsSubfs('a', subfsAUri),
408
+
createFsSubfs('b', subfsBUri),
409
+
])
410
+
const result = await expandSubfsNodes(dir, 'https://pds.example.com', 0, subfsCache)
411
+
412
+
expect(result.entries.filter(e => e.name === 'shared.txt')).toHaveLength(2)
413
+
})
414
+
415
+
test('handles null records in cache gracefully', async () => {
416
+
const subfsCache = new Map<string, SubfsRecord | null>()
417
+
const subfsUri = 'at://did:plc:test/place.wisp.subfs/missing'
418
+
subfsCache.set(subfsUri, null)
419
+
420
+
const dir = createFsRootDirectory([
421
+
createFsFile('file.txt'),
422
+
createFsSubfs('missing', subfsUri),
423
+
])
424
+
const result = await expandSubfsNodes(dir, 'https://pds.example.com', 0, subfsCache)
425
+
426
+
expect(result.entries.some(e => e.name === 'file.txt')).toBe(true)
427
+
expect(result.entries.some(e => e.name === 'missing')).toBe(true)
428
+
})
429
+
430
+
test('non-flat subfs merge creates directory instead of hoisting', async () => {
431
+
const subfsCache = new Map<string, SubfsRecord | null>()
432
+
const subfsUri = 'at://did:plc:test/place.wisp.subfs/nested'
433
+
subfsCache.set(subfsUri, createSubfsRecord([createSubfsFile('nested-file.txt')]))
434
+
435
+
const dir = createFsRootDirectory([
436
+
createFsFile('root.txt'),
437
+
createFsSubfs('subdir', subfsUri, false),
438
+
])
439
+
const result = await expandSubfsNodes(dir, 'https://pds.example.com', 0, subfsCache)
440
+
441
+
expect(result.entries).toHaveLength(2)
442
+
443
+
const rootFile = result.entries.find(e => e.name === 'root.txt')
444
+
expect(rootFile).toBeDefined()
445
+
446
+
const subdir = result.entries.find(e => e.name === 'subdir')
447
+
expect(subdir).toBeDefined()
448
+
449
+
if (subdir && 'entries' in subdir.node) {
450
+
expect(subdir.node.type).toBe('directory')
451
+
expect(subdir.node.entries).toHaveLength(1)
452
+
expect(subdir.node.entries[0]?.name).toBe('nested-file.txt')
453
+
}
454
+
})
455
+
})
456
+
457
+
describe('WispFsRecord mock builders', () => {
458
+
test('createFsRecord creates valid record structure', () => {
459
+
const record = createFsRecord('my-site', [
460
+
createFsFile('index.html', { mimeType: 'text/html' }),
461
+
createFsDirectory('assets', [
462
+
createFsFile('style.css', { mimeType: 'text/css' }),
463
+
]),
464
+
])
465
+
466
+
expect(record.$type).toBe('place.wisp.fs')
467
+
expect(record.site).toBe('my-site')
468
+
expect(record.root.type).toBe('directory')
469
+
expect(record.root.entries).toHaveLength(2)
470
+
expect(record.createdAt).toBeDefined()
471
+
})
472
+
473
+
test('createFsFile creates valid file entry', () => {
474
+
const entry = createFsFile('test.html', { mimeType: 'text/html', size: 500 })
475
+
476
+
expect(entry.name).toBe('test.html')
477
+
478
+
const file = entry.node
479
+
if ('blob' in file) {
480
+
expect(file.$type).toBe('place.wisp.fs#file')
481
+
expect(file.type).toBe('file')
482
+
expect(file.blob).toBeDefined()
483
+
expect(file.mimeType).toBe('text/html')
484
+
}
485
+
})
486
+
487
+
test('createFsFile with gzip encoding', () => {
488
+
const entry = createFsFile('bundle.js', { mimeType: 'application/javascript', encoding: 'gzip' })
489
+
490
+
const file = entry.node
491
+
if ('encoding' in file) {
492
+
expect(file.encoding).toBe('gzip')
493
+
}
494
+
})
495
+
496
+
test('createFsFile with base64 flag', () => {
497
+
const entry = createFsFile('data.bin', { base64: true })
498
+
499
+
const file = entry.node
500
+
if ('base64' in file) {
501
+
expect(file.base64).toBe(true)
502
+
}
503
+
})
504
+
505
+
test('createFsDirectory creates valid directory entry', () => {
506
+
const entry = createFsDirectory('assets', [
507
+
createFsFile('file1.txt'),
508
+
createFsFile('file2.txt'),
509
+
])
510
+
511
+
expect(entry.name).toBe('assets')
512
+
513
+
const dir = entry.node
514
+
if ('entries' in dir) {
515
+
expect(dir.$type).toBe('place.wisp.fs#directory')
516
+
expect(dir.type).toBe('directory')
517
+
expect(dir.entries).toHaveLength(2)
518
+
}
519
+
})
520
+
521
+
test('createFsSubfs creates valid subfs entry with flat=true', () => {
522
+
const entry = createFsSubfs('external', 'at://did:plc:test/place.wisp.subfs/ext')
523
+
524
+
expect(entry.name).toBe('external')
525
+
526
+
const subfs = entry.node
527
+
if ('subject' in subfs) {
528
+
expect(subfs.$type).toBe('place.wisp.fs#subfs')
529
+
expect(subfs.type).toBe('subfs')
530
+
expect(subfs.subject).toBe('at://did:plc:test/place.wisp.subfs/ext')
531
+
expect(subfs.flat).toBe(true)
532
+
}
533
+
})
534
+
535
+
test('createFsSubfs creates valid subfs entry with flat=false', () => {
536
+
const entry = createFsSubfs('external', 'at://did:plc:test/place.wisp.subfs/ext', false)
537
+
538
+
const subfs = entry.node
539
+
if ('subject' in subfs) {
540
+
expect(subfs.flat).toBe(false)
541
+
}
542
+
})
543
+
544
+
test('createFsRecord with fileCount', () => {
545
+
const record = createFsRecord('my-site', [createFsFile('index.html')], 1)
546
+
expect(record.fileCount).toBe(1)
547
+
})
548
+
})
549
+
550
+
describe('SubfsRecord mock builders', () => {
551
+
test('createSubfsRecord creates valid record structure', () => {
552
+
const record = createSubfsRecord([
553
+
createSubfsFile('file1.txt'),
554
+
createSubfsDirectory('nested', [
555
+
createSubfsFile('file2.txt'),
556
+
]),
557
+
])
558
+
559
+
expect(record.$type).toBe('place.wisp.subfs')
560
+
expect(record.root.type).toBe('directory')
561
+
expect(record.root.entries).toHaveLength(2)
562
+
expect(record.createdAt).toBeDefined()
563
+
})
564
+
565
+
test('createSubfsFile creates valid file entry', () => {
566
+
const entry = createSubfsFile('data.json', { mimeType: 'application/json', size: 1024 })
567
+
568
+
expect(entry.name).toBe('data.json')
569
+
570
+
const file = entry.node
571
+
if ('blob' in file) {
572
+
expect(file.$type).toBe('place.wisp.subfs#file')
573
+
expect(file.type).toBe('file')
574
+
expect(file.blob).toBeDefined()
575
+
expect(file.mimeType).toBe('application/json')
576
+
}
577
+
})
578
+
579
+
test('createSubfsDirectory creates valid directory entry', () => {
580
+
const entry = createSubfsDirectory('subdir', [createSubfsFile('inner.txt')])
581
+
582
+
expect(entry.name).toBe('subdir')
583
+
584
+
const dir = entry.node
585
+
if ('entries' in dir) {
586
+
expect(dir.$type).toBe('place.wisp.subfs#directory')
587
+
expect(dir.type).toBe('directory')
588
+
expect(dir.entries).toHaveLength(1)
589
+
}
590
+
})
591
+
592
+
test('createSubfsSubfs creates valid nested subfs entry', () => {
593
+
const entry = createSubfsSubfs('deeper', 'at://did:plc:test/place.wisp.subfs/deeper')
594
+
595
+
expect(entry.name).toBe('deeper')
596
+
597
+
const subfs = entry.node
598
+
if ('subject' in subfs) {
599
+
expect(subfs.$type).toBe('place.wisp.subfs#subfs')
600
+
expect(subfs.type).toBe('subfs')
601
+
expect(subfs.subject).toBe('at://did:plc:test/place.wisp.subfs/deeper')
602
+
expect('flat' in subfs).toBe(false)
603
+
}
604
+
})
605
+
606
+
test('createSubfsRecord with fileCount', () => {
607
+
const record = createSubfsRecord([createSubfsFile('file.txt')], 1)
608
+
expect(record.fileCount).toBe(1)
609
+
})
610
+
})
611
+
612
+
describe('extractBlobCid with typed mock data', () => {
613
+
test('extracts CID from FsFile blob', () => {
614
+
const entry = createFsFile('test.txt')
615
+
const file = entry.node
616
+
617
+
if ('blob' in file) {
618
+
const cid = extractBlobCid(file.blob)
619
+
expect(cid).toBeDefined()
620
+
expect(cid).toContain('bafkrei')
621
+
}
622
+
})
623
+
624
+
test('extracts CID from SubfsFile blob', () => {
625
+
const entry = createSubfsFile('test.txt')
626
+
const file = entry.node
627
+
628
+
if ('blob' in file) {
629
+
const cid = extractBlobCid(file.blob)
630
+
expect(cid).toBeDefined()
631
+
expect(cid).toContain('bafkrei')
632
+
}
633
+
})
634
+
})
+177
-170
apps/hosting-service/src/lib/utils.ts
+177
-170
apps/hosting-service/src/lib/utils.ts
···
4
4
import type { Record as WispSettings } from '@wisp/lexicons/types/place/wisp/settings';
5
5
import { existsSync, mkdirSync, readFileSync, rmSync } from 'fs';
6
6
import { writeFile, readFile, rename } from 'fs/promises';
7
+
import { Readable } from 'stream';
7
8
import { safeFetchJson, safeFetchBlob } from '@wisp/safe-fetch';
8
9
import { CID } from 'multiformats';
9
10
import { extractBlobCid } from '@wisp/atproto-utils';
10
-
import { sanitizePath, collectFileCidsFromEntries } from '@wisp/fs-utils';
11
+
import { sanitizePath, collectFileCidsFromEntries, countFilesInDirectory } from '@wisp/fs-utils';
11
12
import { shouldCompressMimeType } from '@wisp/atproto-utils/compression';
13
+
import { MAX_BLOB_SIZE, MAX_FILE_COUNT, MAX_SITE_SIZE } from '@wisp/constants';
14
+
import { storage } from './storage';
12
15
13
16
// Re-export shared utilities for local usage and tests
14
17
export { extractBlobCid, sanitizePath };
···
89
92
export async function fetchSiteRecord(did: string, rkey: string): Promise<{ record: WispFsRecord; cid: string } | null> {
90
93
try {
91
94
const pdsEndpoint = await getPdsForDid(did);
92
-
if (!pdsEndpoint) return null;
95
+
if (!pdsEndpoint) {
96
+
console.error('[hosting-service] Failed to get PDS endpoint for DID', { did, rkey });
97
+
return null;
98
+
}
93
99
94
100
const url = `${pdsEndpoint}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=place.wisp.fs&rkey=${encodeURIComponent(rkey)}`;
95
101
const data = await safeFetchJson(url);
···
99
105
cid: data.cid || ''
100
106
};
101
107
} catch (err) {
102
-
console.error('Failed to fetch site record', did, rkey, err);
108
+
const errorCode = (err as any)?.code;
109
+
const errorMsg = err instanceof Error ? err.message : String(err);
110
+
111
+
// Better error logging to distinguish between network errors and 404s
112
+
if (errorMsg.includes('HTTP 404') || errorMsg.includes('Not Found')) {
113
+
console.log('[hosting-service] Site record not found', { did, rkey });
114
+
} else if (errorCode && ['ECONNRESET', 'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR', 'ETIMEDOUT'].includes(errorCode)) {
115
+
console.error('[hosting-service] Network/SSL error fetching site record (after retries)', {
116
+
did,
117
+
rkey,
118
+
error: errorMsg,
119
+
code: errorCode
120
+
});
121
+
} else {
122
+
console.error('[hosting-service] Failed to fetch site record', {
123
+
did,
124
+
rkey,
125
+
error: errorMsg,
126
+
code: errorCode
127
+
});
128
+
}
129
+
103
130
return null;
104
131
}
105
132
}
···
120
147
}
121
148
122
149
/**
150
+
* Calculate total size of all blobs in a directory tree from manifest metadata
151
+
*/
152
+
function calculateTotalBlobSize(directory: Directory): number {
153
+
let totalSize = 0;
154
+
155
+
function sumBlobSizes(entries: Entry[]) {
156
+
for (const entry of entries) {
157
+
const node = entry.node;
158
+
159
+
if ('type' in node && node.type === 'directory' && 'entries' in node) {
160
+
// Recursively sum subdirectories
161
+
sumBlobSizes(node.entries);
162
+
} else if ('type' in node && node.type === 'file' && 'blob' in node) {
163
+
// Add blob size from manifest
164
+
const fileNode = node as File;
165
+
const blobSize = (fileNode.blob as any)?.size || 0;
166
+
totalSize += blobSize;
167
+
}
168
+
}
169
+
}
170
+
171
+
sumBlobSizes(directory.entries);
172
+
return totalSize;
173
+
}
174
+
175
+
/**
123
176
* Extract all subfs URIs from a directory tree with their mount paths
124
177
*/
125
-
function extractSubfsUris(directory: Directory, currentPath: string = ''): Array<{ uri: string; path: string }> {
178
+
export function extractSubfsUris(directory: Directory, currentPath: string = ''): Array<{ uri: string; path: string }> {
126
179
const uris: Array<{ uri: string; path: string }> = [];
127
180
128
181
for (const entry of directory.entries) {
···
182
235
* Replace subfs nodes in a directory tree with their actual content
183
236
* Subfs entries are "merged" - their root entries are hoisted into the parent directory
184
237
* This function is recursive - it will keep expanding until no subfs nodes remain
238
+
* Uses a cache to avoid re-fetching the same subfs records across recursion depths
185
239
*/
186
-
async function expandSubfsNodes(directory: Directory, pdsEndpoint: string, depth: number = 0): Promise<Directory> {
240
+
export async function expandSubfsNodes(
241
+
directory: Directory,
242
+
pdsEndpoint: string,
243
+
depth: number = 0,
244
+
subfsCache: Map<string, SubfsRecord | null> = new Map()
245
+
): Promise<Directory> {
187
246
const MAX_DEPTH = 10; // Prevent infinite loops
188
247
189
248
if (depth >= MAX_DEPTH) {
···
199
258
return directory;
200
259
}
201
260
202
-
console.log(`[Depth ${depth}] Found ${subfsUris.length} subfs records, fetching...`);
261
+
// Filter to only URIs we haven't fetched yet
262
+
const uncachedUris = subfsUris.filter(({ uri }) => !subfsCache.has(uri));
203
263
204
-
// Fetch all subfs records in parallel
205
-
const subfsRecords = await Promise.all(
206
-
subfsUris.map(async ({ uri, path }) => {
207
-
const record = await fetchSubfsRecord(uri, pdsEndpoint);
208
-
return { record, path };
209
-
})
210
-
);
264
+
if (uncachedUris.length > 0) {
265
+
console.log(`[Depth ${depth}] Found ${subfsUris.length} subfs references, fetching ${uncachedUris.length} new records (${subfsUris.length - uncachedUris.length} cached)...`);
211
266
212
-
// Build a map of path -> root entries to merge
267
+
// Fetch only uncached subfs records in parallel
268
+
const fetchedRecords = await Promise.all(
269
+
uncachedUris.map(async ({ uri }) => {
270
+
const record = await fetchSubfsRecord(uri, pdsEndpoint);
271
+
return { uri, record };
272
+
})
273
+
);
274
+
275
+
// Add fetched records to cache
276
+
for (const { uri, record } of fetchedRecords) {
277
+
subfsCache.set(uri, record);
278
+
}
279
+
} else {
280
+
console.log(`[Depth ${depth}] Found ${subfsUris.length} subfs references, all cached`);
281
+
}
282
+
283
+
// Build a map of path -> root entries to merge using the cache
213
284
// Note: SubFS entries are compatible with FS entries at runtime
214
285
const subfsMap = new Map<string, Entry[]>();
215
-
for (const { record, path } of subfsRecords) {
286
+
for (const { uri, path } of subfsUris) {
287
+
const record = subfsCache.get(uri);
216
288
if (record && record.root && record.root.entries) {
217
289
subfsMap.set(path, record.root.entries as unknown as Entry[]);
218
290
}
···
280
352
};
281
353
282
354
// Recursively expand any remaining subfs nodes (e.g., nested subfs inside parent subfs)
283
-
return expandSubfsNodes(partiallyExpanded, pdsEndpoint, depth + 1);
355
+
// Pass the cache to avoid re-fetching records
356
+
return expandSubfsNodes(partiallyExpanded, pdsEndpoint, depth + 1, subfsCache);
284
357
}
285
358
286
359
···
300
373
// Expand subfs nodes before caching
301
374
const expandedRoot = await expandSubfsNodes(record.root, pdsEndpoint);
302
375
376
+
// Verify all subfs nodes were expanded
377
+
const remainingSubfs = extractSubfsUris(expandedRoot);
378
+
if (remainingSubfs.length > 0) {
379
+
console.warn(`[Cache] Warning: ${remainingSubfs.length} subfs nodes remain unexpanded after expansion`, remainingSubfs);
380
+
}
381
+
382
+
// Validate file count limit
383
+
const fileCount = countFilesInDirectory(expandedRoot);
384
+
if (fileCount > MAX_FILE_COUNT) {
385
+
throw new Error(`Site exceeds file count limit: ${fileCount} files (max ${MAX_FILE_COUNT})`);
386
+
}
387
+
console.log(`[Cache] File count validation passed: ${fileCount} files (limit: ${MAX_FILE_COUNT})`);
388
+
389
+
// Validate total size from blob metadata
390
+
const totalBlobSize = calculateTotalBlobSize(expandedRoot);
391
+
if (totalBlobSize > MAX_SITE_SIZE) {
392
+
throw new Error(`Site exceeds size limit: ${(totalBlobSize / 1024 / 1024).toFixed(2)}MB (max ${(MAX_SITE_SIZE / 1024 / 1024).toFixed(0)}MB)`);
393
+
}
394
+
console.log(`[Cache] Size validation passed: ${(totalBlobSize / 1024 / 1024).toFixed(2)}MB (limit: ${(MAX_SITE_SIZE / 1024 / 1024).toFixed(0)}MB)`);
395
+
303
396
// Get existing cache metadata to check for incremental updates
304
397
const existingMetadata = await getCacheMetadata(did, rkey);
305
398
const existingFileCids = existingMetadata?.fileCids || {};
306
399
307
-
// Use a temporary directory with timestamp to avoid collisions
308
-
const tempSuffix = `.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
309
-
const tempDir = `${CACHE_DIR}/${did}/${rkey}${tempSuffix}`;
310
-
const finalDir = `${CACHE_DIR}/${did}/${rkey}`;
400
+
// Collect file CIDs from the new record (using expanded root)
401
+
const newFileCids: Record<string, string> = {};
402
+
collectFileCidsFromEntries(expandedRoot.entries, '', newFileCids);
311
403
312
-
try {
313
-
// Collect file CIDs from the new record (using expanded root)
314
-
const newFileCids: Record<string, string> = {};
315
-
collectFileCidsFromEntries(expandedRoot.entries, '', newFileCids);
404
+
// Fetch site settings (optional)
405
+
const settings = await fetchSiteSettings(did, rkey);
316
406
317
-
// Fetch site settings (optional)
318
-
const settings = await fetchSiteSettings(did, rkey);
319
-
320
-
// Download/copy files to temporary directory (with incremental logic, using expanded root)
321
-
await cacheFiles(did, rkey, expandedRoot.entries, pdsEndpoint, '', tempSuffix, existingFileCids, finalDir);
322
-
await saveCacheMetadata(did, rkey, recordCid, tempSuffix, newFileCids, settings);
407
+
// Download files directly to tiered storage (with incremental logic)
408
+
await cacheFiles(did, rkey, expandedRoot.entries, pdsEndpoint, '', existingFileCids);
409
+
await saveCacheMetadata(did, rkey, recordCid, newFileCids, settings);
323
410
324
-
// Atomically replace old cache with new cache
325
-
// On POSIX systems (Linux/macOS), rename is atomic
326
-
if (existsSync(finalDir)) {
327
-
// Rename old directory to backup
328
-
const backupDir = `${finalDir}.old-${Date.now()}`;
329
-
await rename(finalDir, backupDir);
330
-
331
-
try {
332
-
// Rename new directory to final location
333
-
await rename(tempDir, finalDir);
334
-
335
-
// Clean up old backup
336
-
rmSync(backupDir, { recursive: true, force: true });
337
-
} catch (err) {
338
-
// If rename failed, restore backup
339
-
if (existsSync(backupDir) && !existsSync(finalDir)) {
340
-
await rename(backupDir, finalDir);
341
-
}
342
-
throw err;
343
-
}
344
-
} else {
345
-
// No existing cache, just rename temp to final
346
-
await rename(tempDir, finalDir);
347
-
}
348
-
349
-
console.log('Successfully cached site atomically', did, rkey);
350
-
} catch (err) {
351
-
// Clean up temp directory on failure
352
-
if (existsSync(tempDir)) {
353
-
rmSync(tempDir, { recursive: true, force: true });
354
-
}
355
-
throw err;
356
-
}
411
+
console.log('Successfully cached site', did, rkey);
357
412
}
358
413
359
414
···
363
418
entries: Entry[],
364
419
pdsEndpoint: string,
365
420
pathPrefix: string,
366
-
dirSuffix: string = '',
367
-
existingFileCids: Record<string, string> = {},
368
-
existingCacheDir?: string
421
+
existingFileCids: Record<string, string> = {}
369
422
): Promise<void> {
370
-
// Collect file tasks, separating unchanged files from new/changed files
423
+
// Collect file download tasks (skip unchanged files)
371
424
const downloadTasks: Array<() => Promise<void>> = [];
372
-
const copyTasks: Array<() => Promise<void>> = [];
373
425
374
426
function collectFileTasks(
375
427
entries: Entry[],
···
386
438
const cid = extractBlobCid(fileNode.blob);
387
439
388
440
// Check if file is unchanged (same CID as existing cache)
389
-
if (cid && existingFileCids[currentPath] === cid && existingCacheDir) {
390
-
// File unchanged - copy from existing cache instead of downloading
391
-
copyTasks.push(() => copyExistingFile(
392
-
did,
393
-
site,
394
-
currentPath,
395
-
dirSuffix,
396
-
existingCacheDir
397
-
));
441
+
if (cid && existingFileCids[currentPath] === cid) {
442
+
// File unchanged - skip download (already in tiered storage)
443
+
console.log(`Skipping unchanged file: ${currentPath}`);
398
444
} else {
399
445
// File new or changed - download it
400
446
downloadTasks.push(() => cacheFileBlob(
···
405
451
pdsEndpoint,
406
452
fileNode.encoding,
407
453
fileNode.mimeType,
408
-
fileNode.base64,
409
-
dirSuffix
454
+
fileNode.base64
410
455
));
411
456
}
412
457
}
···
415
460
416
461
collectFileTasks(entries, pathPrefix);
417
462
418
-
console.log(`[Incremental Update] Files to copy: ${copyTasks.length}, Files to download: ${downloadTasks.length}`);
419
-
420
-
// Copy unchanged files in parallel (fast local operations) - increased limit for better performance
421
-
const copyLimit = 50;
422
-
for (let i = 0; i < copyTasks.length; i += copyLimit) {
423
-
const batch = copyTasks.slice(i, i + copyLimit);
424
-
await Promise.all(batch.map(task => task()));
425
-
if (copyTasks.length > copyLimit) {
426
-
console.log(`[Cache Progress] Copied ${Math.min(i + copyLimit, copyTasks.length)}/${copyTasks.length} unchanged files`);
427
-
}
428
-
}
463
+
console.log(`[Incremental Update] Files to download: ${downloadTasks.length}`);
429
464
430
-
// Download new/changed files concurrently - increased from 3 to 20 for much better performance
465
+
// Download new/changed files concurrently
431
466
const downloadLimit = 20;
432
467
let successCount = 0;
433
468
let failureCount = 0;
···
456
491
}
457
492
}
458
493
459
-
/**
460
-
* Copy an unchanged file from existing cache to new cache location
461
-
*/
462
-
async function copyExistingFile(
463
-
did: string,
464
-
site: string,
465
-
filePath: string,
466
-
dirSuffix: string,
467
-
existingCacheDir: string
468
-
): Promise<void> {
469
-
const { copyFile } = await import('fs/promises');
470
-
471
-
const sourceFile = `${existingCacheDir}/${filePath}`;
472
-
const destFile = `${CACHE_DIR}/${did}/${site}${dirSuffix}/${filePath}`;
473
-
const destDir = destFile.substring(0, destFile.lastIndexOf('/'));
474
-
475
-
// Create destination directory if needed
476
-
if (destDir && !existsSync(destDir)) {
477
-
mkdirSync(destDir, { recursive: true });
478
-
}
479
-
480
-
try {
481
-
// Copy the file
482
-
await copyFile(sourceFile, destFile);
483
-
484
-
// Copy metadata file if it exists
485
-
const sourceMetaFile = `${sourceFile}.meta`;
486
-
const destMetaFile = `${destFile}.meta`;
487
-
if (existsSync(sourceMetaFile)) {
488
-
await copyFile(sourceMetaFile, destMetaFile);
489
-
}
490
-
} catch (err) {
491
-
console.error(`Failed to copy cached file ${filePath}, will attempt download:`, err);
492
-
throw err;
493
-
}
494
-
}
495
-
496
494
async function cacheFileBlob(
497
495
did: string,
498
496
site: string,
···
501
499
pdsEndpoint: string,
502
500
encoding?: 'gzip',
503
501
mimeType?: string,
504
-
base64?: boolean,
505
-
dirSuffix: string = ''
502
+
base64?: boolean
506
503
): Promise<void> {
507
504
const cid = extractBlobCid(blobRef);
508
505
if (!cid) {
···
514
511
515
512
console.log(`[Cache] Fetching blob for file: ${filePath}, CID: ${cid}`);
516
513
517
-
// Allow up to 500MB per file blob, with 5 minute timeout
518
-
let content = await safeFetchBlob(blobUrl, { maxSize: 500 * 1024 * 1024, timeout: 300000 });
514
+
let content = await safeFetchBlob(blobUrl, { maxSize: MAX_BLOB_SIZE, timeout: 300000 });
519
515
520
516
// If content is base64-encoded, decode it back to raw binary (gzipped or not)
521
517
if (base64) {
···
526
522
content = Buffer.from(base64String, 'base64');
527
523
}
528
524
529
-
const cacheFile = `${CACHE_DIR}/${did}/${site}${dirSuffix}/${filePath}`;
530
-
const fileDir = cacheFile.substring(0, cacheFile.lastIndexOf('/'));
531
-
532
-
if (fileDir && !existsSync(fileDir)) {
533
-
mkdirSync(fileDir, { recursive: true });
534
-
}
535
-
536
525
// Use the shared function to determine if this should remain compressed
537
526
const shouldStayCompressed = shouldCompressMimeType(mimeType);
538
527
···
550
539
}
551
540
}
552
541
553
-
await writeFile(cacheFile, content);
542
+
// Write to tiered storage with metadata
543
+
const stream = Readable.from([content]);
544
+
const key = `${did}/${site}/${filePath}`;
554
545
555
-
// Store metadata only if file is still compressed
546
+
// Build metadata object, only including defined values
547
+
const customMetadata: Record<string, string> = {};
548
+
if (encoding) customMetadata.encoding = encoding;
549
+
if (mimeType) customMetadata.mimeType = mimeType;
550
+
551
+
await storage.setStream(key, stream, {
552
+
size: content.length,
553
+
skipTiers: ['hot'], // Don't put in memory on ingest, only on access
554
+
metadata: customMetadata,
555
+
});
556
+
557
+
// Log completion
556
558
if (encoding === 'gzip' && mimeType) {
557
-
const metaFile = `${cacheFile}.meta`;
558
-
await writeFile(metaFile, JSON.stringify({ encoding, mimeType }));
559
559
console.log('Cached file', filePath, content.length, 'bytes (gzipped,', mimeType + ')');
560
560
} else {
561
561
console.log('Cached file', filePath, content.length, 'bytes');
···
568
568
return `${CACHE_DIR}/${did}/${site}/${sanitizedPath}`;
569
569
}
570
570
571
-
export function isCached(did: string, site: string): boolean {
572
-
return existsSync(`${CACHE_DIR}/${did}/${site}`);
571
+
/**
572
+
* Check if a site exists in any tier of the cache (without checking metadata)
573
+
* This is a quick existence check - for actual retrieval, use storage.get()
574
+
*/
575
+
export async function isCached(did: string, site: string): Promise<boolean> {
576
+
// Check if any file exists for this site by checking for the index.html
577
+
// If index.html exists, the site is cached
578
+
const indexKey = `${did}/${site}/index.html`;
579
+
return await storage.exists(indexKey);
573
580
}
574
581
575
-
async function saveCacheMetadata(did: string, rkey: string, recordCid: string, dirSuffix: string = '', fileCids?: Record<string, string>, settings?: WispSettings | null): Promise<void> {
582
+
async function saveCacheMetadata(did: string, rkey: string, recordCid: string, fileCids?: Record<string, string>, settings?: WispSettings | null): Promise<void> {
576
583
const metadata: CacheMetadata = {
577
584
recordCid,
578
585
cachedAt: Date.now(),
···
582
589
settings: settings || undefined
583
590
};
584
591
585
-
const metadataPath = `${CACHE_DIR}/${did}/${rkey}${dirSuffix}/.metadata.json`;
586
-
const metadataDir = metadataPath.substring(0, metadataPath.lastIndexOf('/'));
587
-
588
-
if (!existsSync(metadataDir)) {
589
-
mkdirSync(metadataDir, { recursive: true });
590
-
}
591
-
592
-
await writeFile(metadataPath, JSON.stringify(metadata, null, 2));
592
+
// Store through tiered storage for persistence to S3/cold tier
593
+
const metadataKey = `${did}/${rkey}/.metadata.json`;
594
+
const metadataBytes = new TextEncoder().encode(JSON.stringify(metadata, null, 2));
595
+
await storage.set(metadataKey, metadataBytes);
593
596
}
594
597
595
598
async function getCacheMetadata(did: string, rkey: string): Promise<CacheMetadata | null> {
596
599
try {
597
-
const metadataPath = `${CACHE_DIR}/${did}/${rkey}/.metadata.json`;
598
-
if (!existsSync(metadataPath)) return null;
600
+
// Retrieve metadata from tiered storage
601
+
const metadataKey = `${did}/${rkey}/.metadata.json`;
602
+
const data = await storage.get(metadataKey);
603
+
604
+
if (!data) return null;
599
605
600
-
const content = await readFile(metadataPath, 'utf-8');
601
-
return JSON.parse(content) as CacheMetadata;
606
+
// Deserialize from Uint8Array to JSON (storage uses identity serialization)
607
+
const jsonString = new TextDecoder().decode(data as Uint8Array);
608
+
return JSON.parse(jsonString) as CacheMetadata;
602
609
} catch (err) {
603
610
console.error('Failed to read cache metadata', err);
604
611
return null;
···
632
639
}
633
640
634
641
export async function updateCacheMetadataSettings(did: string, rkey: string, settings: WispSettings | null): Promise<void> {
635
-
const metadataPath = `${CACHE_DIR}/${did}/${rkey}/.metadata.json`;
642
+
try {
643
+
// Read existing metadata from tiered storage
644
+
const metadata = await getCacheMetadata(did, rkey);
636
645
637
-
if (!existsSync(metadataPath)) {
638
-
console.warn('Metadata file does not exist, cannot update settings', { did, rkey });
639
-
return;
640
-
}
641
-
642
-
try {
643
-
// Read existing metadata
644
-
const content = await readFile(metadataPath, 'utf-8');
645
-
const metadata = JSON.parse(content) as CacheMetadata;
646
+
if (!metadata) {
647
+
console.warn('Metadata does not exist, cannot update settings', { did, rkey });
648
+
return;
649
+
}
646
650
647
651
// Update settings field
648
652
// Store null explicitly to cache "no settings" state and avoid repeated fetches
649
653
metadata.settings = settings ?? null;
650
654
651
-
// Write back to disk
652
-
await writeFile(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8');
655
+
// Write back through tiered storage
656
+
// Convert to Uint8Array since storage is typed for binary data
657
+
const metadataKey = `${did}/${rkey}/.metadata.json`;
658
+
const metadataBytes = new TextEncoder().encode(JSON.stringify(metadata, null, 2));
659
+
await storage.set(metadataKey, metadataBytes);
653
660
console.log('Updated metadata settings', { did, rkey, hasSettings: !!settings });
654
661
} catch (err) {
655
662
console.error('Failed to update metadata settings', err);
+4
-1
apps/hosting-service/src/server.ts
+4
-1
apps/hosting-service/src/server.ts
···
80
80
return c.text('Invalid identifier', 400);
81
81
}
82
82
83
+
console.log(`[Server] sites.wisp.place request: identifier=${identifier}, site=${site}, filePath=${filePath}`);
84
+
83
85
// Check if site is currently being cached - return updating response early
84
86
if (isSiteBeingCached(did, site)) {
85
87
return siteUpdatingResponse();
···
93
95
94
96
// Serve with HTML path rewriting to handle absolute paths
95
97
const basePath = `/${identifier}/${site}/`;
98
+
console.log(`[Server] Serving with basePath: ${basePath}`);
96
99
const headers = extractHeaders(c.req.raw.headers);
97
100
return serveFromCacheWithRewrite(did, site, filePath, basePath, c.req.url, headers);
98
101
}
···
227
230
228
231
app.get('/__internal__/observability/cache', async (c) => {
229
232
const { getCacheStats } = await import('./lib/cache');
230
-
const stats = getCacheStats();
233
+
const stats = await getCacheStats();
231
234
return c.json({ cache: stats });
232
235
});
233
236
+11
-7
apps/main-app/package.json
+11
-7
apps/main-app/package.json
···
7
7
"dev": "bun run --watch src/index.ts",
8
8
"start": "bun run src/index.ts",
9
9
"build": "bun run build.ts",
10
+
"check": "tsc --noEmit",
10
11
"screenshot": "bun run scripts/screenshot-sites.ts"
11
12
},
12
13
"dependencies": {
13
-
"@atproto/api": "^0.17.3",
14
-
"@atproto/common-web": "^0.4.5",
14
+
"@atproto-labs/did-resolver": "^0.2.4",
15
+
"@atproto/api": "^0.17.7",
16
+
"@atproto/common-web": "^0.4.6",
15
17
"@atproto/jwk-jose": "^0.1.11",
16
-
"@atproto/lex-cli": "^0.9.5",
17
-
"@atproto/oauth-client-node": "^0.3.9",
18
-
"@atproto/xrpc-server": "^0.9.5",
18
+
"@atproto/lex-cli": "^0.9.7",
19
+
"@atproto/oauth-client-node": "^0.3.12",
20
+
"@atproto/xrpc-server": "^0.9.6",
19
21
"@elysiajs/cors": "^1.4.0",
20
22
"@elysiajs/eden": "^1.4.3",
21
23
"@elysiajs/openapi": "^1.4.11",
···
35
37
"@wisp/lexicons": "workspace:*",
36
38
"@wisp/observability": "workspace:*",
37
39
"actor-typeahead": "^0.1.1",
38
-
"atproto-ui": "^0.11.3",
40
+
"atproto-ui": "^0.12.0",
39
41
"bun-plugin-tailwind": "^0.1.2",
40
42
"class-variance-authority": "^0.7.1",
41
43
"clsx": "^2.1.1",
42
-
"elysia": "latest",
44
+
"elysia": "^1.4.18",
43
45
"ignore": "^7.0.5",
44
46
"iron-session": "^8.0.4",
45
47
"lucide-react": "^0.546.0",
···
53
55
"zlib": "^1.0.5"
54
56
},
55
57
"devDependencies": {
58
+
"@atproto-labs/handle-resolver": "^0.3.4",
59
+
"@atproto/did": "^0.2.3",
56
60
"@types/react": "^19.2.2",
57
61
"@types/react-dom": "^19.2.1",
58
62
"bun-types": "latest",
+4
-4
apps/main-app/public/acceptable-use/acceptable-use.tsx
+4
-4
apps/main-app/public/acceptable-use/acceptable-use.tsx
···
6
6
7
7
function AcceptableUsePage() {
8
8
return (
9
-
<div className="min-h-screen bg-background">
9
+
<div className="w-full min-h-screen bg-background flex flex-col">
10
10
{/* Header */}
11
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
12
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
11
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
12
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
13
13
<div className="flex items-center gap-2">
14
14
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
15
15
<span className="text-xl font-semibold text-foreground">
···
326
326
</div>
327
327
328
328
{/* Footer */}
329
-
<footer className="border-t border-border/40 bg-muted/20 mt-12">
329
+
<footer className="border-t border-border/40 bg-muted/20 mt-auto">
330
330
<div className="container mx-auto px-4 py-8">
331
331
<div className="text-center text-sm text-muted-foreground">
332
332
<p>
+1
-1
apps/main-app/public/components/ui/checkbox.tsx
+1
-1
apps/main-app/public/components/ui/checkbox.tsx
···
12
12
<CheckboxPrimitive.Root
13
13
data-slot="checkbox"
14
14
className={cn(
15
-
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
15
+
"peer border-border bg-background dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
16
16
className
17
17
)}
18
18
{...props}
+6
-6
apps/main-app/public/editor/editor.tsx
+6
-6
apps/main-app/public/editor/editor.tsx
···
302
302
return (
303
303
<div className="w-full min-h-screen bg-background">
304
304
{/* Header Skeleton */}
305
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
306
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
305
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
306
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
307
307
<div className="flex items-center gap-2">
308
308
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
309
309
<span className="text-xl font-semibold text-foreground">
···
366
366
}
367
367
368
368
return (
369
-
<div className="w-full min-h-screen bg-background">
369
+
<div className="w-full min-h-screen bg-background flex flex-col">
370
370
{/* Header */}
371
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
372
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
371
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
372
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
373
373
<div className="flex items-center gap-2">
374
374
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
375
375
<span className="text-xl font-semibold text-foreground">
···
454
454
</div>
455
455
456
456
{/* Footer */}
457
-
<footer className="border-t border-border/40 bg-muted/20 mt-12">
457
+
<footer className="border-t border-border/40 bg-muted/20 mt-auto">
458
458
<div className="container mx-auto px-4 py-8">
459
459
<div className="text-center text-sm text-muted-foreground">
460
460
<p>
+74
-66
apps/main-app/public/index.tsx
+74
-66
apps/main-app/public/index.tsx
···
88
88
89
89
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
90
90
const navigationKeys = ['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp', 'Enter', 'Escape']
91
-
91
+
92
92
// Mark that we should preserve the index for navigation keys
93
93
if (navigationKeys.includes(e.key)) {
94
94
preserveIndexRef.current = true
···
142
142
setIndex(-1)
143
143
setIsOpen(false)
144
144
onSelect?.(handle)
145
-
145
+
146
146
// Auto-submit the form if enabled
147
147
if (autoSubmit && inputRef.current) {
148
148
const form = inputRef.current.closest('form')
···
236
236
height: 'calc(1.5rem + 12px)',
237
237
borderRadius: '4px',
238
238
cursor: 'pointer',
239
-
backgroundColor: i === index ? 'hsl(var(--accent) / 0.5)' : 'transparent',
239
+
backgroundColor: i === index ? 'color-mix(in oklch, var(--accent) 50%, transparent)' : 'transparent',
240
240
transition: 'background-color 0.1s'
241
241
}}
242
242
onMouseEnter={() => setIndex(i)}
···
246
246
width: '1.5rem',
247
247
height: '1.5rem',
248
248
borderRadius: '50%',
249
-
backgroundColor: 'hsl(var(--muted))',
249
+
backgroundColor: 'var(--muted)',
250
250
overflow: 'hidden',
251
251
flexShrink: 0
252
252
}}
···
255
255
<img
256
256
src={actor.avatar}
257
257
alt=""
258
+
loading="lazy"
258
259
style={{
259
260
display: 'block',
260
261
width: '100%',
···
359
360
360
361
return (
361
362
<>
362
-
<div className="min-h-screen">
363
+
<div className="w-full min-h-screen flex flex-col">
363
364
{/* Header */}
364
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
365
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
365
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
366
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
366
367
<div className="flex items-center gap-2">
367
368
<img src="/transparent-full-size-ico.png" alt="wisp.place" className="w-8 h-8" />
368
-
<span className="text-xl font-semibold text-foreground">
369
+
<span className="text-lg font-semibold text-foreground">
369
370
wisp.place
370
371
</span>
371
372
</div>
372
-
<div className="flex items-center gap-3">
373
+
<div className="flex items-center gap-4">
374
+
<a
375
+
href="https://docs.wisp.place"
376
+
target="_blank"
377
+
rel="noopener noreferrer"
378
+
className="text-sm text-muted-foreground hover:text-foreground transition-colors"
379
+
>
380
+
Read the Docs
381
+
</a>
373
382
<Button
374
-
variant="ghost"
383
+
variant="outline"
375
384
size="sm"
385
+
className="btn-hover-lift"
376
386
onClick={() => setShowForm(true)}
377
387
>
378
388
Sign In
379
389
</Button>
380
-
<Button
381
-
size="sm"
382
-
className="bg-accent text-accent-foreground hover:bg-accent/90"
383
-
asChild
384
-
>
385
-
<a href="https://docs.wisp.place" target="_blank" rel="noopener noreferrer">
386
-
Read the Docs
387
-
</a>
388
-
</Button>
389
390
</div>
390
391
</div>
391
392
</header>
392
393
393
394
{/* Hero Section */}
394
-
<section className="container mx-auto px-4 py-20 md:py-32">
395
+
<section className="container mx-auto px-4 py-24 md:py-36">
395
396
<div className="max-w-4xl mx-auto text-center">
396
-
<div className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-accent/10 border border-accent/20 mb-8">
397
-
<span className="w-2 h-2 bg-accent rounded-full animate-pulse"></span>
398
-
<span className="text-sm text-foreground">
399
-
Built on AT Protocol
400
-
</span>
401
-
</div>
402
-
403
-
<h1 className="text-5xl md:text-7xl font-bold text-balance mb-6 leading-tight">
404
-
Your Website.Your Control. Lightning Fast.
397
+
{/* Main Headline */}
398
+
<h1 className="animate-fade-in-up animate-delay-100 text-5xl md:text-7xl font-bold mb-2 leading-tight tracking-tight">
399
+
Deploy Anywhere.
400
+
</h1>
401
+
<h1 className="animate-fade-in-up animate-delay-200 text-5xl md:text-7xl font-bold mb-8 leading-tight tracking-tight text-gradient-animate">
402
+
For Free. Forever.
405
403
</h1>
406
404
407
-
<p className="text-xl md:text-2xl text-muted-foreground text-balance mb-10 leading-relaxed max-w-3xl mx-auto">
408
-
Host static sites in your AT Protocol account. You
409
-
keep ownership and control. We just serve them fast
410
-
through our CDN.
405
+
{/* Subheadline */}
406
+
<p className="animate-fade-in-up animate-delay-300 text-lg md:text-xl text-muted-foreground mb-12 leading-relaxed max-w-2xl mx-auto">
407
+
The easiest way to deploy and orchestrate static sites.
408
+
Push updates instantly. Host on our infrastructure or yours.
409
+
All powered by AT Protocol.
411
410
</p>
412
411
413
-
<div className="max-w-md mx-auto relative">
412
+
{/* CTA Buttons */}
413
+
<div className="animate-fade-in-up animate-delay-400 max-w-lg mx-auto relative">
414
414
<div
415
-
className={`transition-all duration-500 ease-in-out ${
416
-
showForm
417
-
? 'opacity-0 -translate-y-5 pointer-events-none'
418
-
: 'opacity-100 translate-y-0'
419
-
}`}
415
+
className={`transition-all duration-500 ease-in-out ${showForm
416
+
? 'opacity-0 -translate-y-5 pointer-events-none absolute inset-0'
417
+
: 'opacity-100 translate-y-0'
418
+
}`}
420
419
>
421
-
<Button
422
-
size="lg"
423
-
className="bg-primary text-primary-foreground hover:bg-primary/90 text-lg px-8 py-6 w-full"
424
-
onClick={() => setShowForm(true)}
425
-
>
426
-
Log in with AT Proto
427
-
<ArrowRight className="ml-2 w-5 h-5" />
428
-
</Button>
420
+
<div className="flex flex-col sm:flex-row gap-3 justify-center">
421
+
<Button
422
+
size="lg"
423
+
className="bg-foreground text-background hover:bg-foreground/90 text-base px-6 py-5 btn-hover-lift"
424
+
onClick={() => setShowForm(true)}
425
+
>
426
+
<span className="mr-2 font-bold">@</span>
427
+
Deploy with AT
428
+
</Button>
429
+
<Button
430
+
variant="outline"
431
+
size="lg"
432
+
className="text-base px-6 py-5 btn-hover-lift"
433
+
asChild
434
+
>
435
+
<a href="https://docs.wisp.place/cli/" target="_blank" rel="noopener noreferrer">
436
+
<span className="font-mono mr-2 text-muted-foreground">>_</span>
437
+
Install wisp-cli
438
+
</a>
439
+
</Button>
440
+
</div>
429
441
</div>
430
442
431
443
<div
432
-
className={`transition-all duration-500 ease-in-out absolute inset-0 ${
433
-
showForm
434
-
? 'opacity-100 translate-y-0'
435
-
: 'opacity-0 translate-y-5 pointer-events-none'
436
-
}`}
444
+
className={`transition-all duration-500 ease-in-out ${showForm
445
+
? 'opacity-100 translate-y-0'
446
+
: 'opacity-0 translate-y-5 pointer-events-none absolute inset-0'
447
+
}`}
437
448
>
438
449
<form
439
450
onSubmit={async (e) => {
···
494
505
</ActorTypeahead>
495
506
<button
496
507
type="submit"
497
-
className="w-full bg-accent hover:bg-accent/90 text-accent-foreground font-semibold py-4 px-6 text-lg rounded-lg inline-flex items-center justify-center transition-colors"
508
+
className="w-full bg-foreground text-background hover:bg-foreground/90 font-semibold py-4 px-6 text-lg rounded-lg inline-flex items-center justify-center transition-colors btn-hover-lift"
498
509
>
499
510
Continue
500
511
<ArrowRight className="ml-2 w-5 h-5" />
···
518
529
</div>
519
530
<div>
520
531
<h3 className="text-xl font-semibold mb-2">
521
-
Upload your static site
532
+
Drop in your files
522
533
</h3>
523
534
<p className="text-muted-foreground">
524
-
Your HTML, CSS, and JavaScript files are
525
-
stored in your AT Protocol account as
526
-
gzipped blobs and a manifest record.
535
+
Upload your site through our dashboard or push with the CLI.
536
+
Everything gets stored directly in your AT Protocol account.
527
537
</p>
528
538
</div>
529
539
</div>
···
533
543
</div>
534
544
<div>
535
545
<h3 className="text-xl font-semibold mb-2">
536
-
We serve it globally
546
+
We handle the rest
537
547
</h3>
538
548
<p className="text-muted-foreground">
539
-
Wisp.place reads your site from your
540
-
account and delivers it through our CDN
541
-
for fast loading anywhere.
549
+
Your site goes live instantly on our global CDN.
550
+
Custom domains, HTTPS, cachingโall automatic.
542
551
</p>
543
552
</div>
544
553
</div>
···
548
557
</div>
549
558
<div>
550
559
<h3 className="text-xl font-semibold mb-2">
551
-
You stay in control
560
+
Push updates instantly
552
561
</h3>
553
562
<p className="text-muted-foreground">
554
-
Update or remove your site anytime
555
-
through your AT Protocol account. No
556
-
lock-in, no middleman ownership.
563
+
Ship changes in seconds. Update through the dashboard,
564
+
run wisp-cli deploy, or wire up your CI/CD pipeline.
557
565
</p>
558
566
</div>
559
567
</div>
···
686
694
</section>
687
695
688
696
{/* Footer */}
689
-
<footer className="border-t border-border/40 bg-muted/20">
697
+
<footer className="border-t border-border/40 bg-muted/20 mt-auto">
690
698
<div className="container mx-auto px-4 py-8">
691
699
<div className="text-center text-sm text-muted-foreground">
692
700
<p>
+16
-19
apps/main-app/public/onboarding/onboarding.tsx
+16
-19
apps/main-app/public/onboarding/onboarding.tsx
···
161
161
return (
162
162
<div className="w-full min-h-screen bg-background">
163
163
{/* Header */}
164
-
<header className="border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
165
-
<div className="container mx-auto px-4 py-4 flex items-center justify-between">
164
+
<header className="w-full border-b border-border/40 bg-background/80 backdrop-blur-sm sticky top-0 z-50">
165
+
<div className="max-w-6xl w-full mx-auto px-4 h-16 flex items-center justify-between">
166
166
<div className="flex items-center gap-2">
167
167
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center">
168
168
<Globe className="w-5 h-5 text-primary-foreground" />
···
179
179
<div className="mb-8">
180
180
<div className="flex items-center justify-center gap-2 mb-4">
181
181
<div
182
-
className={`w-8 h-8 rounded-full flex items-center justify-center ${
183
-
step === 'domain'
184
-
? 'bg-primary text-primary-foreground'
185
-
: 'bg-green-500 text-white'
186
-
}`}
182
+
className={`w-8 h-8 rounded-full flex items-center justify-center ${step === 'domain'
183
+
? 'bg-primary text-primary-foreground'
184
+
: 'bg-green-500 text-white'
185
+
}`}
187
186
>
188
187
{step === 'domain' ? (
189
188
'1'
···
193
192
</div>
194
193
<div className="w-16 h-0.5 bg-border"></div>
195
194
<div
196
-
className={`w-8 h-8 rounded-full flex items-center justify-center ${
197
-
step === 'upload'
198
-
? 'bg-primary text-primary-foreground'
199
-
: step === 'domain'
200
-
? 'bg-muted text-muted-foreground'
201
-
: 'bg-green-500 text-white'
202
-
}`}
195
+
className={`w-8 h-8 rounded-full flex items-center justify-center ${step === 'upload'
196
+
? 'bg-primary text-primary-foreground'
197
+
: step === 'domain'
198
+
? 'bg-muted text-muted-foreground'
199
+
: 'bg-green-500 text-white'
200
+
}`}
203
201
>
204
202
{step === 'complete' ? (
205
203
<CheckCircle2 className="w-5 h-5" />
···
258
256
{!isCheckingAvailability &&
259
257
isAvailable !== null && (
260
258
<div
261
-
className={`absolute right-3 top-1/2 -translate-y-1/2 ${
262
-
isAvailable
263
-
? 'text-green-500'
264
-
: 'text-red-500'
265
-
}`}
259
+
className={`absolute right-3 top-1/2 -translate-y-1/2 ${isAvailable
260
+
? 'text-green-500'
261
+
: 'text-red-500'
262
+
}`}
266
263
>
267
264
{isAvailable ? 'โ' : 'โ'}
268
265
</div>
+212
-39
apps/main-app/public/styles/global.css
+212
-39
apps/main-app/public/styles/global.css
···
6
6
:root {
7
7
color-scheme: light;
8
8
9
-
/* Warm beige background inspired by Sunset design #E9DDD8 */
10
-
--background: oklch(0.90 0.012 35);
11
-
/* Very dark brown text for strong contrast #2A2420 */
12
-
--foreground: oklch(0.18 0.01 30);
9
+
/* Warm beige background inspired by Sunset design */
10
+
--background: oklch(0.92 0.012 35);
11
+
/* Very dark brown text for strong contrast */
12
+
--foreground: oklch(0.15 0.015 30);
13
13
14
-
/* Slightly lighter card background */
15
-
--card: oklch(0.93 0.01 35);
16
-
--card-foreground: oklch(0.18 0.01 30);
14
+
/* Slightly lighter card background for elevation */
15
+
--card: oklch(0.95 0.008 35);
16
+
--card-foreground: oklch(0.15 0.015 30);
17
17
18
-
--popover: oklch(0.93 0.01 35);
19
-
--popover-foreground: oklch(0.18 0.01 30);
18
+
--popover: oklch(0.96 0.006 35);
19
+
--popover-foreground: oklch(0.15 0.015 30);
20
20
21
-
/* Dark brown primary inspired by #645343 */
22
-
--primary: oklch(0.35 0.02 35);
23
-
--primary-foreground: oklch(0.95 0.01 35);
21
+
/* Dark brown primary - darker for better contrast */
22
+
--primary: oklch(0.30 0.025 35);
23
+
--primary-foreground: oklch(0.96 0.008 35);
24
24
25
-
/* Bright pink accent for links #FFAAD2 */
26
-
--accent: oklch(0.78 0.15 345);
27
-
--accent-foreground: oklch(0.18 0.01 30);
25
+
/* Deeper pink accent for better visibility */
26
+
--accent: oklch(0.65 0.18 345);
27
+
--accent-foreground: oklch(0.15 0.015 30);
28
28
29
-
/* Medium taupe secondary inspired by #867D76 */
30
-
--secondary: oklch(0.52 0.015 30);
31
-
--secondary-foreground: oklch(0.95 0.01 35);
29
+
/* Darker taupe secondary for better contrast */
30
+
--secondary: oklch(0.85 0.012 30);
31
+
--secondary-foreground: oklch(0.25 0.02 30);
32
32
33
-
/* Light warm muted background */
33
+
/* Muted areas with better distinction */
34
34
--muted: oklch(0.88 0.01 35);
35
-
--muted-foreground: oklch(0.42 0.015 30);
35
+
--muted-foreground: oklch(0.35 0.02 30);
36
36
37
-
--border: oklch(0.75 0.015 30);
38
-
--input: oklch(0.92 0.01 35);
39
-
--ring: oklch(0.72 0.08 15);
37
+
/* Significantly darker border for visibility */
38
+
--border: oklch(0.65 0.02 30);
39
+
/* Input backgrounds lighter than cards */
40
+
--input: oklch(0.97 0.005 35);
41
+
--ring: oklch(0.55 0.12 345);
40
42
41
-
--destructive: oklch(0.577 0.245 27.325);
42
-
--destructive-foreground: oklch(0.985 0 0);
43
+
--destructive: oklch(0.50 0.20 25);
44
+
--destructive-foreground: oklch(0.98 0 0);
43
45
44
-
--chart-1: oklch(0.78 0.15 345);
46
+
--chart-1: oklch(0.65 0.18 345);
45
47
--chart-2: oklch(0.32 0.04 285);
46
-
--chart-3: oklch(0.56 0.08 220);
47
-
--chart-4: oklch(0.85 0.02 130);
48
-
--chart-5: oklch(0.93 0.03 85);
48
+
--chart-3: oklch(0.50 0.10 220);
49
+
--chart-4: oklch(0.70 0.08 130);
50
+
--chart-5: oklch(0.75 0.06 85);
49
51
50
52
--radius: 0.75rem;
51
-
--sidebar: oklch(0.985 0 0);
52
-
--sidebar-foreground: oklch(0.145 0 0);
53
-
--sidebar-primary: oklch(0.205 0 0);
54
-
--sidebar-primary-foreground: oklch(0.985 0 0);
55
-
--sidebar-accent: oklch(0.97 0 0);
56
-
--sidebar-accent-foreground: oklch(0.205 0 0);
57
-
--sidebar-border: oklch(0.922 0 0);
58
-
--sidebar-ring: oklch(0.708 0 0);
53
+
--sidebar: oklch(0.94 0.008 35);
54
+
--sidebar-foreground: oklch(0.15 0.015 30);
55
+
--sidebar-primary: oklch(0.30 0.025 35);
56
+
--sidebar-primary-foreground: oklch(0.96 0.008 35);
57
+
--sidebar-accent: oklch(0.90 0.01 35);
58
+
--sidebar-accent-foreground: oklch(0.20 0.02 30);
59
+
--sidebar-border: oklch(0.65 0.02 30);
60
+
--sidebar-ring: oklch(0.55 0.12 345);
59
61
}
60
62
61
63
.dark {
···
160
162
* {
161
163
@apply border-border outline-ring/50;
162
164
}
165
+
166
+
html {
167
+
scrollbar-gutter: stable;
168
+
}
169
+
163
170
body {
164
171
@apply bg-background text-foreground;
165
172
}
166
173
}
167
174
168
175
@keyframes arrow-bounce {
169
-
0%, 100% {
176
+
177
+
0%,
178
+
100% {
170
179
transform: translateX(0);
171
180
}
181
+
172
182
50% {
173
183
transform: translateX(4px);
174
184
}
···
189
199
border-radius: 0.5rem;
190
200
padding: 1rem;
191
201
overflow-x: auto;
192
-
border: 1px solid hsl(var(--border));
202
+
border: 1px solid var(--border);
193
203
}
194
204
195
205
.shiki-wrapper pre {
196
206
margin: 0 !important;
197
207
padding: 0 !important;
198
208
}
209
+
210
+
/* ========== Landing Page Animations ========== */
211
+
212
+
/* Animated gradient for headline text */
213
+
@keyframes gradient-shift {
214
+
215
+
0%,
216
+
100% {
217
+
background-position: 0% 50%;
218
+
}
219
+
220
+
50% {
221
+
background-position: 100% 50%;
222
+
}
223
+
}
224
+
225
+
.text-gradient-animate {
226
+
background: linear-gradient(90deg,
227
+
oklch(0.55 0.22 350),
228
+
oklch(0.60 0.24 10),
229
+
oklch(0.55 0.22 350));
230
+
background-size: 200% auto;
231
+
-webkit-background-clip: text;
232
+
background-clip: text;
233
+
-webkit-text-fill-color: transparent;
234
+
animation: gradient-shift 4s ease-in-out infinite;
235
+
}
236
+
237
+
.dark .text-gradient-animate {
238
+
background: linear-gradient(90deg,
239
+
oklch(0.75 0.12 295),
240
+
oklch(0.85 0.10 5),
241
+
oklch(0.75 0.12 295));
242
+
background-size: 200% auto;
243
+
-webkit-background-clip: text;
244
+
background-clip: text;
245
+
-webkit-text-fill-color: transparent;
246
+
}
247
+
248
+
/* Floating/breathing animation for hero elements */
249
+
@keyframes float {
250
+
251
+
0%,
252
+
100% {
253
+
transform: translateY(0);
254
+
}
255
+
256
+
50% {
257
+
transform: translateY(-8px);
258
+
}
259
+
}
260
+
261
+
.animate-float {
262
+
animation: float 3s ease-in-out infinite;
263
+
}
264
+
265
+
.animate-float-delayed {
266
+
animation: float 3s ease-in-out infinite;
267
+
animation-delay: 0.5s;
268
+
}
269
+
270
+
/* Staggered fade-in animation */
271
+
@keyframes fade-in-up {
272
+
from {
273
+
opacity: 0;
274
+
transform: translateY(20px);
275
+
}
276
+
277
+
to {
278
+
opacity: 1;
279
+
transform: translateY(0);
280
+
}
281
+
}
282
+
283
+
.animate-fade-in-up {
284
+
animation: fade-in-up 0.6s ease-out forwards;
285
+
opacity: 0;
286
+
}
287
+
288
+
.animate-delay-100 {
289
+
animation-delay: 0.1s;
290
+
}
291
+
292
+
.animate-delay-200 {
293
+
animation-delay: 0.2s;
294
+
}
295
+
296
+
.animate-delay-300 {
297
+
animation-delay: 0.3s;
298
+
}
299
+
300
+
.animate-delay-400 {
301
+
animation-delay: 0.4s;
302
+
}
303
+
304
+
.animate-delay-500 {
305
+
animation-delay: 0.5s;
306
+
}
307
+
308
+
.animate-delay-600 {
309
+
animation-delay: 0.6s;
310
+
}
311
+
312
+
/* Terminal cursor blink */
313
+
@keyframes cursor-blink {
314
+
315
+
0%,
316
+
50% {
317
+
opacity: 1;
318
+
}
319
+
320
+
51%,
321
+
100% {
322
+
opacity: 0;
323
+
}
324
+
}
325
+
326
+
.animate-cursor-blink {
327
+
animation: cursor-blink 1s step-end infinite;
328
+
}
329
+
330
+
/* Button hover scale effect */
331
+
.btn-hover-lift {
332
+
transition: all 0.2s ease-out;
333
+
}
334
+
335
+
.btn-hover-lift:hover {
336
+
transform: translateY(-2px);
337
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
338
+
}
339
+
340
+
.btn-hover-lift:active {
341
+
transform: translateY(0);
342
+
}
343
+
344
+
/* Subtle pulse for feature dots */
345
+
@keyframes dot-pulse {
346
+
347
+
0%,
348
+
100% {
349
+
transform: scale(1);
350
+
opacity: 1;
351
+
}
352
+
353
+
50% {
354
+
transform: scale(1.2);
355
+
opacity: 0.8;
356
+
}
357
+
}
358
+
359
+
.animate-dot-pulse {
360
+
animation: dot-pulse 2s ease-in-out infinite;
361
+
}
362
+
363
+
.animate-dot-pulse-delayed-1 {
364
+
animation: dot-pulse 2s ease-in-out infinite;
365
+
animation-delay: 0.3s;
366
+
}
367
+
368
+
.animate-dot-pulse-delayed-2 {
369
+
animation: dot-pulse 2s ease-in-out infinite;
370
+
animation-delay: 0.6s;
371
+
}
+30
-4
apps/main-app/src/index.ts
+30
-4
apps/main-app/src/index.ts
···
12
12
cleanupExpiredSessions,
13
13
rotateKeysIfNeeded
14
14
} from './lib/oauth-client'
15
-
import { getCookieSecret } from './lib/db'
15
+
import { getCookieSecret, closeDatabase } from './lib/db'
16
16
import { authRoutes } from './routes/auth'
17
17
import { wispRoutes } from './routes/wisp'
18
18
import { domainRoutes } from './routes/domain'
···
20
20
import { siteRoutes } from './routes/site'
21
21
import { csrfProtection } from './lib/csrf'
22
22
import { DNSVerificationWorker } from './lib/dns-verification-worker'
23
-
import { createLogger, logCollector } from '@wisp/observability'
23
+
import { createLogger, logCollector, initializeGrafanaExporters } from '@wisp/observability'
24
24
import { observabilityMiddleware } from '@wisp/observability/middleware/elysia'
25
25
import { promptAdminSetup } from './lib/admin-auth'
26
26
import { adminRoutes } from './routes/admin'
27
+
28
+
// Initialize Grafana exporters if configured
29
+
initializeGrafanaExporters({
30
+
serviceName: 'main-app',
31
+
serviceVersion: '1.0.50'
32
+
})
27
33
28
34
const logger = createLogger('main-app')
29
35
···
55
61
setInterval(runMaintenance, 60 * 60 * 1000)
56
62
57
63
// Start DNS verification worker (runs every 10 minutes)
64
+
// Can be disabled via DISABLE_DNS_WORKER=true environment variable
58
65
const dnsVerifier = new DNSVerificationWorker(
59
66
10 * 60 * 1000, // 10 minutes
60
67
(msg, data) => {
···
62
69
}
63
70
)
64
71
65
-
dnsVerifier.start()
66
-
logger.info('DNS Verifier Started - checking custom domains every 10 minutes')
72
+
if (Bun.env.DISABLE_DNS_WORKER !== 'true') {
73
+
dnsVerifier.start()
74
+
logger.info('DNS Verifier Started - checking custom domains every 10 minutes')
75
+
} else {
76
+
logger.info('DNS Verifier disabled via DISABLE_DNS_WORKER environment variable')
77
+
}
67
78
68
79
export const app = new Elysia({
69
80
serve: {
···
194
205
console.log(
195
206
`๐ฆ Elysia is running at ${app.server?.hostname}:${app.server?.port}`
196
207
)
208
+
209
+
// Graceful shutdown
210
+
process.on('SIGINT', async () => {
211
+
console.log('\n๐ Shutting down...')
212
+
dnsVerifier.stop()
213
+
await closeDatabase()
214
+
process.exit(0)
215
+
})
216
+
217
+
process.on('SIGTERM', async () => {
218
+
console.log('\n๐ Shutting down...')
219
+
dnsVerifier.stop()
220
+
await closeDatabase()
221
+
process.exit(0)
222
+
})
+13
apps/main-app/src/lib/db.ts
+13
apps/main-app/src/lib/db.ts
···
526
526
console.log('[CookieSecret] Generated new cookie signing secret');
527
527
return secret;
528
528
};
529
+
530
+
/**
531
+
* Close database connection
532
+
* Call this during graceful shutdown
533
+
*/
534
+
export const closeDatabase = async (): Promise<void> => {
535
+
try {
536
+
await db.end();
537
+
console.log('[DB] Database connection closed');
538
+
} catch (err) {
539
+
console.error('[DB] Error closing database connection:', err);
540
+
}
541
+
};
+5
-4
apps/main-app/src/lib/oauth-client.ts
+5
-4
apps/main-app/src/lib/oauth-client.ts
···
4
4
import { logger } from "./logger";
5
5
import { SlingshotHandleResolver } from "./slingshot-handle-resolver";
6
6
7
+
// OAuth scope for all client types
8
+
const OAUTH_SCOPE = 'atproto repo:place.wisp.fs repo:place.wisp.domain repo:place.wisp.subfs repo:place.wisp.settings blob:*/*';
7
9
// Session timeout configuration (30 days in seconds)
8
10
const SESSION_TIMEOUT = 30 * 24 * 60 * 60; // 2592000 seconds
9
11
// OAuth state timeout (1 hour in seconds)
···
110
112
// Loopback client for local development
111
113
// For loopback, scopes and redirect_uri must be in client_id query string
112
114
const redirectUri = 'http://127.0.0.1:8000/api/auth/callback';
113
-
const scope = 'atproto repo:place.wisp.fs repo:place.wisp.domain repo:place.wisp.subfs repo:place.wisp.settings blob:*/* rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app#bsky_appview';
114
115
const params = new URLSearchParams();
115
116
params.append('redirect_uri', redirectUri);
116
-
params.append('scope', scope);
117
+
params.append('scope', OAUTH_SCOPE);
117
118
118
119
return {
119
120
client_id: `http://localhost?${params.toString()}`,
···
124
125
response_types: ['code'],
125
126
application_type: 'web',
126
127
token_endpoint_auth_method: 'none',
127
-
scope: scope,
128
+
scope: OAUTH_SCOPE,
128
129
dpop_bound_access_tokens: false,
129
130
subject_type: 'public',
130
131
authorization_signed_response_alg: 'ES256'
···
145
146
application_type: 'web',
146
147
token_endpoint_auth_method: 'private_key_jwt',
147
148
token_endpoint_auth_signing_alg: "ES256",
148
-
scope: "atproto repo:place.wisp.fs repo:place.wisp.domain repo:place.wisp.subfs repo:place.wisp.settings blob:*/* rpc:app.bsky.actor.getProfile?aud=did:web:api.bsky.app#bsky_appview",
149
+
scope: OAUTH_SCOPE,
149
150
dpop_bound_access_tokens: true,
150
151
jwks_uri: `${config.domain}/jwks.json`,
151
152
subject_type: 'public',
+10
-12
apps/main-app/src/routes/user.ts
+10
-12
apps/main-app/src/routes/user.ts
···
1
1
import { Elysia, t } from 'elysia'
2
2
import { requireAuth } from '../lib/wisp-auth'
3
3
import { NodeOAuthClient } from '@atproto/oauth-client-node'
4
-
import { Agent } from '@atproto/api'
5
4
import { getSitesByDid, getDomainByDid, getCustomDomainsByDid, getWispDomainInfo, getDomainsBySite, getAllWispDomains } from '../lib/db'
6
5
import { syncSitesFromPDS } from '../lib/sync-sites'
7
6
import { createLogger } from '@wisp/observability'
7
+
import { createDidResolver, extractAtprotoData } from '@atproto-labs/did-resolver'
8
8
9
9
const logger = createLogger('main-app')
10
+
const didResolver = createDidResolver({})
10
11
11
12
export const userRoutes = (client: NodeOAuthClient, cookieSecret: string) =>
12
13
new Elysia({
···
42
43
})
43
44
.get('/info', async ({ auth }) => {
44
45
try {
45
-
// Get user's handle from AT Protocol
46
-
const agent = new Agent(auth.session)
47
-
48
46
let handle = 'unknown'
49
47
try {
50
-
console.log('[User] Attempting to fetch profile for DID:', auth.did)
51
-
const profile = await agent.getProfile({ actor: auth.did })
52
-
console.log('[User] Profile fetched successfully:', profile.data.handle)
53
-
handle = profile.data.handle
48
+
const didDoc = await didResolver.resolve(auth.did)
49
+
const atprotoData = extractAtprotoData(didDoc)
50
+
51
+
if (atprotoData.aka) {
52
+
handle = atprotoData.aka
53
+
}
54
54
} catch (err) {
55
-
console.error('[User] Failed to fetch profile - Full error:', err)
56
-
console.error('[User] Error message:', err instanceof Error ? err.message : String(err))
57
-
console.error('[User] Error stack:', err instanceof Error ? err.stack : 'No stack')
58
-
logger.error('[User] Failed to fetch profile', err)
55
+
56
+
logger.error('[User] Failed to resolve DID', err)
59
57
}
60
58
61
59
return {
+10
-16
apps/main-app/src/routes/wisp.ts
+10
-16
apps/main-app/src/routes/wisp.ts
···
39
39
40
40
const logger = createLogger('main-app')
41
41
42
-
function isValidSiteName(siteName: string): boolean {
42
+
export function isValidSiteName(siteName: string): boolean {
43
43
if (!siteName || typeof siteName !== 'string') return false;
44
44
45
45
// Length check (AT Protocol rkey limit)
···
183
183
continue;
184
184
}
185
185
186
-
console.log(`Processing file ${i + 1}/${fileArray.length}:`, file.name, file.size, 'bytes');
186
+
// Use webkitRelativePath when available (directory uploads), fallback to name for regular file uploads
187
+
const webkitPath = 'webkitRelativePath' in file ? String(file.webkitRelativePath) : '';
188
+
const filePath = webkitPath || file.name;
189
+
187
190
updateJobProgress(jobId, {
188
191
filesProcessed: i + 1,
189
-
currentFile: file.name
192
+
currentFile: filePath
190
193
});
191
194
192
195
// Skip files that match ignore patterns
193
-
const normalizedPath = file.name.replace(/^[^\/]*\//, '');
196
+
const normalizedPath = filePath.replace(/^[^\/]*\//, '');
194
197
195
198
if (shouldIgnore(ignoreMatcher, normalizedPath)) {
196
-
console.log(`Skipping ignored file: ${file.name}`);
197
199
skippedFiles.push({
198
-
name: file.name,
200
+
name: filePath,
199
201
reason: 'matched ignore pattern'
200
202
});
201
203
continue;
···
205
207
const maxSize = MAX_FILE_SIZE;
206
208
if (file.size > maxSize) {
207
209
skippedFiles.push({
208
-
name: file.name,
210
+
name: filePath,
209
211
reason: `file too large (${(file.size / 1024 / 1024).toFixed(2)}MB, max 100MB)`
210
212
});
211
213
continue;
···
238
240
// Text files: compress AND base64 encode
239
241
finalContent = Buffer.from(compressedContent.toString('base64'), 'binary');
240
242
base64Encoded = true;
241
-
const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1);
242
-
console.log(`Compressing+base64 ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${finalContent.length} bytes`);
243
-
logger.info(`Compressing+base64 ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${finalContent.length} bytes`);
244
243
} else {
245
244
// Audio files: just compress, no base64
246
245
finalContent = compressedContent;
247
-
const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1);
248
-
console.log(`Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%)`);
249
-
logger.info(`Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%)`);
250
246
}
251
247
} else {
252
248
// Binary files: upload directly
253
249
finalContent = originalContent;
254
-
console.log(`Uploading ${file.name} directly: ${originalContent.length} bytes (no compression)`);
255
-
logger.info(`Uploading ${file.name} directly: ${originalContent.length} bytes (binary)`);
256
250
}
257
251
258
252
uploadedFiles.push({
259
-
name: file.name,
253
+
name: filePath,
260
254
content: finalContent,
261
255
mimeType: originalMimeType,
262
256
size: finalContent.length,
+59
backup.nix
+59
backup.nix
···
1
+
{
2
+
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
3
+
inputs.nci.url = "github:90-008/nix-cargo-integration";
4
+
inputs.nci.inputs.nixpkgs.follows = "nixpkgs";
5
+
inputs.parts.url = "github:hercules-ci/flake-parts";
6
+
inputs.parts.inputs.nixpkgs-lib.follows = "nixpkgs";
7
+
inputs.fenix = {
8
+
url = "github:nix-community/fenix";
9
+
inputs.nixpkgs.follows = "nixpkgs";
10
+
};
11
+
12
+
outputs = inputs @ {
13
+
parts,
14
+
nci,
15
+
...
16
+
}:
17
+
parts.lib.mkFlake {inherit inputs;} {
18
+
systems = ["x86_64-linux" "aarch64-darwin"];
19
+
imports = [
20
+
nci.flakeModule
21
+
./crates.nix
22
+
];
23
+
perSystem = {
24
+
pkgs,
25
+
config,
26
+
...
27
+
}: let
28
+
crateOutputs = config.nci.outputs."wisp-cli";
29
+
mkRenamedPackage = name: pkg: isWindows: pkgs.runCommand name {} ''
30
+
mkdir -p $out/bin
31
+
if [ -f ${pkg}/bin/wisp-cli.exe ]; then
32
+
cp ${pkg}/bin/wisp-cli.exe $out/bin/${name}
33
+
elif [ -f ${pkg}/bin/wisp-cli ]; then
34
+
cp ${pkg}/bin/wisp-cli $out/bin/${name}
35
+
else
36
+
echo "Error: Could not find wisp-cli binary in ${pkg}/bin/"
37
+
ls -la ${pkg}/bin/ || true
38
+
exit 1
39
+
fi
40
+
'';
41
+
in {
42
+
devShells.default = crateOutputs.devShell;
43
+
packages.default = crateOutputs.packages.release;
44
+
packages.wisp-cli-x86_64-linux = mkRenamedPackage "wisp-cli-x86_64-linux" crateOutputs.packages.release false;
45
+
packages.wisp-cli-aarch64-linux = mkRenamedPackage "wisp-cli-aarch64-linux" crateOutputs.allTargets."aarch64-unknown-linux-gnu".packages.release false;
46
+
packages.wisp-cli-x86_64-windows = mkRenamedPackage "wisp-cli-x86_64-windows.exe" crateOutputs.allTargets."x86_64-pc-windows-gnu".packages.release true;
47
+
packages.wisp-cli-aarch64-darwin = mkRenamedPackage "wisp-cli-aarch64-darwin" crateOutputs.allTargets."aarch64-apple-darwin".packages.release false;
48
+
packages.all = pkgs.symlinkJoin {
49
+
name = "wisp-cli-all";
50
+
paths = [
51
+
config.packages.wisp-cli-x86_64-linux
52
+
config.packages.wisp-cli-aarch64-linux
53
+
config.packages.wisp-cli-x86_64-windows
54
+
config.packages.wisp-cli-aarch64-darwin
55
+
];
56
+
};
57
+
};
58
+
};
59
+
}
+9
-2
binaries/index.html
+9
-2
binaries/index.html
···
51
51
transition: background-color 200ms ease, color 200ms ease;
52
52
font-family: system-ui, sans-serif;
53
53
line-height: 1.6;
54
+
display: flex;
55
+
flex-direction: column;
56
+
min-height: 100vh;
54
57
}
55
58
56
59
.container {
57
60
max-width: 860px;
58
61
margin: 40px auto;
59
62
padding: 0 20px;
60
-
min-height: 100vh;
63
+
flex: 1;
64
+
display: flex;
65
+
flex-direction: column;
66
+
width: 100%;
61
67
}
62
68
63
69
h1 {
···
224
230
}
225
231
226
232
.footer {
227
-
margin-top: 3rem;
233
+
margin-top: auto;
228
234
padding-top: 2rem;
235
+
padding-bottom: 2rem;
229
236
border-top: 1px solid var(--demo-hr);
230
237
text-align: center;
231
238
color: var(--demo-text-secondary);
+541
-162
bun.lock
+541
-162
bun.lock
···
1
1
{
2
2
"lockfileVersion": 1,
3
-
"configVersion": 0,
3
+
"configVersion": 1,
4
4
"workspaces": {
5
5
"": {
6
-
"name": "elysia-static",
6
+
"name": "@wisp/monorepo",
7
7
"dependencies": {
8
8
"@tailwindcss/cli": "^4.1.17",
9
+
"atproto-ui": "^0.12.0",
9
10
"bun-plugin-tailwind": "^0.1.2",
11
+
"elysia": "^1.4.18",
10
12
"tailwindcss": "^4.1.17",
11
13
},
14
+
"devDependencies": {
15
+
"@types/bun": "^1.3.5",
16
+
},
12
17
},
13
18
"apps/hosting-service": {
14
19
"name": "wisp-hosting-service",
···
16
21
"dependencies": {
17
22
"@atproto/api": "^0.17.4",
18
23
"@atproto/identity": "^0.4.9",
19
-
"@atproto/lexicon": "^0.5.1",
24
+
"@atproto/lexicon": "^0.5.2",
20
25
"@atproto/sync": "^0.1.36",
21
26
"@atproto/xrpc": "^0.7.5",
22
27
"@hono/node-server": "^1.19.6",
···
31
36
"mime-types": "^2.1.35",
32
37
"multiformats": "^13.4.1",
33
38
"postgres": "^3.4.5",
39
+
"tiered-storage": "1.0.3",
34
40
},
35
41
"devDependencies": {
36
42
"@types/bun": "^1.3.1",
37
43
"@types/mime-types": "^2.1.4",
38
44
"@types/node": "^22.10.5",
39
45
"tsx": "^4.19.2",
46
+
"typescript": "^5.9.3",
40
47
},
41
48
},
42
49
"apps/main-app": {
43
50
"name": "@wisp/main-app",
44
51
"version": "1.0.50",
45
52
"dependencies": {
46
-
"@atproto/api": "^0.17.3",
47
-
"@atproto/common-web": "^0.4.5",
53
+
"@atproto-labs/did-resolver": "^0.2.4",
54
+
"@atproto/api": "^0.17.7",
55
+
"@atproto/common-web": "^0.4.6",
48
56
"@atproto/jwk-jose": "^0.1.11",
49
-
"@atproto/lex-cli": "^0.9.5",
50
-
"@atproto/oauth-client-node": "^0.3.9",
51
-
"@atproto/xrpc-server": "^0.9.5",
57
+
"@atproto/lex-cli": "^0.9.7",
58
+
"@atproto/oauth-client-node": "^0.3.12",
59
+
"@atproto/xrpc-server": "^0.9.6",
52
60
"@elysiajs/cors": "^1.4.0",
53
61
"@elysiajs/eden": "^1.4.3",
54
62
"@elysiajs/openapi": "^1.4.11",
···
68
76
"@wisp/lexicons": "workspace:*",
69
77
"@wisp/observability": "workspace:*",
70
78
"actor-typeahead": "^0.1.1",
71
-
"atproto-ui": "^0.11.3",
79
+
"atproto-ui": "^0.12.0",
72
80
"bun-plugin-tailwind": "^0.1.2",
73
81
"class-variance-authority": "^0.7.1",
74
82
"clsx": "^2.1.1",
75
-
"elysia": "latest",
83
+
"elysia": "^1.4.18",
76
84
"ignore": "^7.0.5",
77
85
"iron-session": "^8.0.4",
78
86
"lucide-react": "^0.546.0",
···
86
94
"zlib": "^1.0.5",
87
95
},
88
96
"devDependencies": {
97
+
"@atproto-labs/handle-resolver": "^0.3.4",
98
+
"@atproto/did": "^0.2.3",
89
99
"@types/react": "^19.2.2",
90
100
"@types/react-dom": "^19.2.1",
91
101
"bun-types": "latest",
···
101
111
"@atproto/api": "^0.14.1",
102
112
"@wisp/lexicons": "workspace:*",
103
113
"multiformats": "^13.3.1",
114
+
},
115
+
"devDependencies": {
116
+
"@atproto/lexicon": "^0.5.2",
104
117
},
105
118
},
106
119
"packages/@wisp/constants": {
···
131
144
},
132
145
"devDependencies": {
133
146
"@atproto/lex-cli": "^0.9.5",
147
+
"multiformats": "^13.4.1",
134
148
},
135
149
},
136
150
"packages/@wisp/observability": {
137
151
"name": "@wisp/observability",
138
152
"version": "1.0.0",
153
+
"dependencies": {
154
+
"@opentelemetry/api": "^1.9.0",
155
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.56.0",
156
+
"@opentelemetry/resources": "^1.29.0",
157
+
"@opentelemetry/sdk-metrics": "^1.29.0",
158
+
"@opentelemetry/semantic-conventions": "^1.29.0",
159
+
},
160
+
"devDependencies": {
161
+
"@hono/node-server": "^1.19.6",
162
+
"bun-types": "^1.3.3",
163
+
"typescript": "^5.9.3",
164
+
},
139
165
"peerDependencies": {
140
-
"hono": "^4.0.0",
166
+
"hono": "^4.10.7",
141
167
},
142
168
"optionalPeers": [
143
169
"hono",
···
149
175
},
150
176
},
151
177
"trustedDependencies": [
152
-
"core-js",
178
+
"esbuild",
153
179
"cbor-extract",
154
180
"protobufjs",
181
+
"core-js",
182
+
"bun",
183
+
"@parcel/watcher",
155
184
],
156
185
"packages": {
157
186
"@atcute/atproto": ["@atcute/atproto@3.1.9", "", { "dependencies": { "@atcute/lexicons": "^1.2.2" } }, "sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w=="],
158
187
159
-
"@atcute/bluesky": ["@atcute/bluesky@3.2.10", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.2" } }, "sha512-qwQWTzRf3umnh2u41gdU+xWYkbzGlKDupc3zeOB+YjmuP1N9wEaUhwS8H7vgrqr0xC9SGNDjeUVcjC4m5BPLBg=="],
188
+
"@atcute/bluesky": ["@atcute/bluesky@3.2.11", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.5" } }, "sha512-AboS6y4t+zaxIq7E4noue10csSpIuk/Uwo30/l6GgGBDPXrd7STw8Yb5nGZQP+TdG/uC8/c2mm7UnY65SDOh6A=="],
160
189
161
-
"@atcute/client": ["@atcute/client@4.0.5", "", { "dependencies": { "@atcute/identity": "^1.1.1", "@atcute/lexicons": "^1.2.2" } }, "sha512-R8Qen8goGmEkynYGg2m6XFlVmz0GTDvQ+9w+4QqOob+XMk8/WDpF4aImev7WKEde/rV2gjcqW7zM8E6W9NShDA=="],
190
+
"@atcute/client": ["@atcute/client@4.1.0", "", { "dependencies": { "@atcute/identity": "^1.1.3", "@atcute/lexicons": "^1.2.5" } }, "sha512-AYhSu3RSDA2VDkVGOmad320NRbUUUf5pCFWJcOzlk25YC/4kyzmMFfpzhf1jjjEcY+anNBXGGhav/kKB1evggQ=="],
162
191
163
-
"@atcute/identity": ["@atcute/identity@1.1.1", "", { "dependencies": { "@atcute/lexicons": "^1.2.2", "@badrap/valita": "^0.4.6" } }, "sha512-zax42n693VEhnC+5tndvO2KLDTMkHOz8UExwmklvJv7R9VujfEwiSWhcv6Jgwb3ellaG8wjiQ1lMOIjLLvwh0Q=="],
192
+
"@atcute/identity": ["@atcute/identity@1.1.3", "", { "dependencies": { "@atcute/lexicons": "^1.2.4", "@badrap/valita": "^0.4.6" } }, "sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng=="],
164
193
165
194
"@atcute/identity-resolver": ["@atcute/identity-resolver@1.1.4", "", { "dependencies": { "@atcute/lexicons": "^1.2.2", "@atcute/util-fetch": "^1.0.3", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-/SVh8vf2cXFJenmBnGeYF2aY3WGQm3cJeew5NWTlkqoy3LvJ5wkvKq9PWu4Tv653VF40rPOp6LOdVr9Fa+q5rA=="],
166
195
167
-
"@atcute/lexicons": ["@atcute/lexicons@1.2.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "esm-env": "^1.2.2" } }, "sha512-bgEhJq5Z70/0TbK5sx+tAkrR8FsCODNiL2gUEvS5PuJfPxmFmRYNWaMGehxSPaXWpU2+Oa9ckceHiYbrItDTkA=="],
196
+
"@atcute/lexicons": ["@atcute/lexicons@1.2.5", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "esm-env": "^1.2.2" } }, "sha512-9yO9WdgxW8jZ7SbzUycH710z+JmsQ9W9n5S6i6eghYju32kkluFmgBeS47r8e8p2+Dv4DemS7o/3SUGsX9FR5Q=="],
168
197
169
-
"@atcute/tangled": ["@atcute/tangled@1.0.10", "", { "dependencies": { "@atcute/atproto": "^3.1.8", "@atcute/lexicons": "^1.2.2" } }, "sha512-DGconZIN5TpLBah+aHGbWI1tMsL7XzyVEbr/fW4CbcLWYKICU6SAUZ0YnZ+5GvltjlORWHUy7hfftvoh4zodIA=="],
198
+
"@atcute/tangled": ["@atcute/tangled@1.0.12", "", { "dependencies": { "@atcute/atproto": "^3.1.9", "@atcute/lexicons": "^1.2.3" } }, "sha512-JKA5sOhd8SLhDFhY+PKHqLLytQBBKSiwcaEzfYUJBeyfvqXFPNNAwvRbe3VST4IQ3izoOu3O0R9/b1mjL45UzA=="],
170
199
171
-
"@atcute/util-fetch": ["@atcute/util-fetch@1.0.3", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-f8zzTb/xlKIwv2OQ31DhShPUNCmIIleX6p7qIXwWwEUjX6x8skUtpdISSjnImq01LXpltGV5y8yhV4/Mlb7CRQ=="],
200
+
"@atcute/util-fetch": ["@atcute/util-fetch@1.0.4", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-sIU9Qk0dE8PLEXSfhy+gIJV+HpiiknMytCI2SqLlqd0vgZUtEKI/EQfP+23LHWvP+CLCzVDOa6cpH045OlmNBg=="],
172
201
173
-
"@atproto-labs/did-resolver": ["@atproto-labs/did-resolver@0.2.2", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "zod": "^3.23.8" } }, "sha512-ca2B7xR43tVoQ8XxBvha58DXwIH8cIyKQl6lpOKGkPUrJuFoO4iCLlDiSDi2Ueh+yE1rMDPP/qveHdajgDX3WQ=="],
202
+
"@atproto-labs/did-resolver": ["@atproto-labs/did-resolver@0.2.4", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.3", "zod": "^3.23.8" } }, "sha512-sbXxBnAJWsKv/FEGG6a/WLz7zQYUr1vA2TXvNnPwwJQJCjPwEJMOh1vM22wBr185Phy7D2GD88PcRokn7eUVyw=="],
174
203
175
204
"@atproto-labs/fetch": ["@atproto-labs/fetch@0.2.3", "", { "dependencies": { "@atproto-labs/pipe": "0.1.1" } }, "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw=="],
176
205
177
206
"@atproto-labs/fetch-node": ["@atproto-labs/fetch-node@0.2.0", "", { "dependencies": { "@atproto-labs/fetch": "0.2.3", "@atproto-labs/pipe": "0.1.1", "ipaddr.js": "^2.1.0", "undici": "^6.14.1" } }, "sha512-Krq09nH/aeoiU2s9xdHA0FjTEFWG9B5FFenipv1iRixCcPc7V3DhTNDawxG9gI8Ny0k4dBVS9WTRN/IDzBx86Q=="],
178
207
179
-
"@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.2", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "zod": "^3.23.8" } }, "sha512-KIerCzh3qb+zZoqWbIvTlvBY0XPq0r56kwViaJY/LTe/3oPO2JaqlYKS/F4dByWBhHK6YoUOJ0sWrh6PMJl40A=="],
208
+
"@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.3.4", "", { "dependencies": { "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.3", "zod": "^3.23.8" } }, "sha512-wsNopfzfgO3uPvfnFDgNeXgDufXxSXhjBjp2WEiSzEiLrMy0Jodnqggw4OzD9MJKf0a4Iu2/ydd537qdy91LrQ=="],
180
209
181
-
"@atproto-labs/handle-resolver-node": ["@atproto-labs/handle-resolver-node@0.1.21", "", { "dependencies": { "@atproto-labs/fetch-node": "0.2.0", "@atproto-labs/handle-resolver": "0.3.2", "@atproto/did": "0.2.1" } }, "sha512-fuJy5Px5pGF3lJX/ATdurbT8tbmaFWtf+PPxAQDFy7ot2no3t+iaAgymhyxYymrssOuWs6BwOP8tyF3VrfdwtQ=="],
210
+
"@atproto-labs/handle-resolver-node": ["@atproto-labs/handle-resolver-node@0.1.23", "", { "dependencies": { "@atproto-labs/fetch-node": "0.2.0", "@atproto-labs/handle-resolver": "0.3.4", "@atproto/did": "0.2.3" } }, "sha512-tBRr2LCgzn3klk+DL0xrTFv4zg5tEszdeW6vSIFVebBYSb3MLdfhievmSqZdIQ4c9UCC4hN7YXTlZCXj8+2YmQ=="],
182
211
183
-
"@atproto-labs/identity-resolver": ["@atproto-labs/identity-resolver@0.3.2", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.2", "@atproto-labs/handle-resolver": "0.3.2" } }, "sha512-MYxO9pe0WsFyi5HFdKAwqIqHfiF2kBPoVhAIuH/4PYHzGr799ED47xLhNMxR3ZUYrJm5+TQzWXypGZ0Btw1Ffw=="],
212
+
"@atproto-labs/identity-resolver": ["@atproto-labs/identity-resolver@0.3.4", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.4", "@atproto-labs/handle-resolver": "0.3.4" } }, "sha512-HNUEFQIo2ws6iATxmgHd5D5rAsWYupgxZucgwolVHPiMjE1SY+EmxEsfbEN1wDEzM8/u9AKUg/jrxxPEwsgbew=="],
184
213
185
214
"@atproto-labs/pipe": ["@atproto-labs/pipe@0.1.1", "", {}, "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg=="],
186
215
···
192
221
193
222
"@atproto/common": ["@atproto/common@0.4.12", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@ipld/dag-cbor": "^7.0.3", "cbor-x": "^1.5.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-NC+TULLQiqs6MvNymhQS5WDms3SlbIKGLf4n33tpftRJcalh507rI+snbcUb7TLIkKw7VO17qMqxEXtIdd5auQ=="],
194
223
195
-
"@atproto/common-web": ["@atproto/common-web@0.4.5", "", { "dependencies": { "@atproto/lex-data": "0.0.1", "@atproto/lex-json": "0.0.1", "zod": "^3.23.8" } }, "sha512-Tx0xUafLm3vRvOQpbBl5eb9V8xlC7TaRXs6dAulHRkDG3Kb+P9qn3pkDteq+aeMshbVXbVa1rm3Ok4vFyuoyYA=="],
224
+
"@atproto/common-web": ["@atproto/common-web@0.4.6", "", { "dependencies": { "@atproto/lex-data": "0.0.2", "@atproto/lex-json": "0.0.2", "zod": "^3.23.8" } }, "sha512-+2mG/1oBcB/ZmYIU1ltrFMIiuy9aByKAkb2Fos/0eTdczcLBaH17k0KoxMGvhfsujN2r62XlanOAMzysa7lv1g=="],
196
225
197
-
"@atproto/crypto": ["@atproto/crypto@0.4.4", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, "sha512-Yq9+crJ7WQl7sxStVpHgie5Z51R05etaK9DLWYG/7bR5T4bhdcIgF6IfklLShtZwLYdVVj+K15s0BqW9a8PSDA=="],
226
+
"@atproto/crypto": ["@atproto/crypto@0.4.5", "", { "dependencies": { "@noble/curves": "^1.7.0", "@noble/hashes": "^1.6.1", "uint8arrays": "3.0.0" } }, "sha512-n40aKkMoCatP0u9Yvhrdk6fXyOHFDDbkdm4h4HCyWW+KlKl8iXfD5iV+ECq+w5BM+QH25aIpt3/j6EUNerhLxw=="],
198
227
199
-
"@atproto/did": ["@atproto/did@0.2.1", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-1i5BTU2GnBaaeYWhxUOnuEKFVq9euT5+dQPFabHpa927BlJ54PmLGyBBaOI7/NbLmN5HWwBa18SBkMpg3jGZRA=="],
228
+
"@atproto/did": ["@atproto/did@0.2.3", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-VI8JJkSizvM2cHYJa37WlbzeCm5tWpojyc1/Zy8q8OOjyoy6X4S4BEfoP941oJcpxpMTObamibQIXQDo7tnIjg=="],
200
229
201
230
"@atproto/identity": ["@atproto/identity@0.4.10", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4" } }, "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ=="],
202
231
···
206
235
207
236
"@atproto/jwk-webcrypto": ["@atproto/jwk-webcrypto@0.2.0", "", { "dependencies": { "@atproto/jwk": "0.6.0", "@atproto/jwk-jose": "0.1.11", "zod": "^3.23.8" } }, "sha512-UmgRrrEAkWvxwhlwe30UmDOdTEFidlIzBC7C3cCbeJMcBN1x8B3KH+crXrsTqfWQBG58mXgt8wgSK3Kxs2LhFg=="],
208
237
209
-
"@atproto/lex-cbor": ["@atproto/lex-cbor@0.0.1", "", { "dependencies": { "@atproto/lex-data": "0.0.1", "multiformats": "^9.9.0", "tslib": "^2.8.1" } }, "sha512-GCgowcC041tYmsoIxalIECJq4ZRHgREk6lFa4BzNRUZarMqwz57YF/7eUlo2Q6hoaMUL7Bjr6FvXwcZFaKrhvA=="],
238
+
"@atproto/lex-cbor": ["@atproto/lex-cbor@0.0.2", "", { "dependencies": { "@atproto/lex-data": "0.0.2", "multiformats": "^9.9.0", "tslib": "^2.8.1" } }, "sha512-sTr3UCL2SgxEoYVpzJGgWTnNl4TpngP5tMcRyaOvi21Se4m3oR4RDsoVDPz8AS6XphiteRwzwPstquN7aWWMbA=="],
210
239
211
-
"@atproto/lex-cli": ["@atproto/lex-cli@0.9.6", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "@atproto/syntax": "^0.4.1", "chalk": "^4.1.2", "commander": "^9.4.0", "prettier": "^3.2.5", "ts-morph": "^24.0.0", "yesno": "^0.4.0", "zod": "^3.23.8" }, "bin": { "lex": "dist/index.js" } }, "sha512-EedEKmURoSP735YwSDHsFrLOhZ4P2it8goCHv5ApWi/R9DFpOKOpmYfIXJ9MAprK8cw+yBnjDJbzpLJy7UXlTg=="],
240
+
"@atproto/lex-cli": ["@atproto/lex-cli@0.9.7", "", { "dependencies": { "@atproto/lexicon": "^0.5.2", "@atproto/syntax": "^0.4.1", "chalk": "^4.1.2", "commander": "^9.4.0", "prettier": "^3.2.5", "ts-morph": "^24.0.0", "yesno": "^0.4.0", "zod": "^3.23.8" }, "bin": { "lex": "dist/index.js" } }, "sha512-UZVf0pK0mB4qiuwbnrxmV0mC9/Vk2v7W3u9pd4wc4GFojzAyGP76MF2TiwWFya5mgzC7723/r5Jb4ADg0rtfng=="],
212
241
213
-
"@atproto/lex-data": ["@atproto/lex-data@0.0.1", "", { "dependencies": { "@atproto/syntax": "0.4.1", "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-DrS/8cQcQs3s5t9ELAFNtyDZ8/PdiCx47ALtFEP2GnX2uCBHZRkqWG7xmu6ehjc787nsFzZBvlnz3T/gov5fGA=="],
242
+
"@atproto/lex-data": ["@atproto/lex-data@0.0.2", "", { "dependencies": { "@atproto/syntax": "0.4.2", "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-euV2rDGi+coH8qvZOU+ieUOEbwPwff9ca6IiXIqjZJ76AvlIpj7vtAyIRCxHUW2BoU6h9yqyJgn9MKD2a7oIwg=="],
214
243
215
-
"@atproto/lex-json": ["@atproto/lex-json@0.0.1", "", { "dependencies": { "@atproto/lex-data": "0.0.1", "tslib": "^2.8.1" } }, "sha512-ivcF7+pDRuD/P97IEKQ/9TruunXj0w58Khvwk3M6psaI5eZT6LRsRZ4cWcKaXiFX4SHnjy+x43g0f7pPtIsERg=="],
244
+
"@atproto/lex-json": ["@atproto/lex-json@0.0.2", "", { "dependencies": { "@atproto/lex-data": "0.0.2", "tslib": "^2.8.1" } }, "sha512-Pd72lO+l2rhOTutnf11omh9ZkoB/elbzE3HSmn2wuZlyH1mRhTYvoH8BOGokWQwbZkCE8LL3nOqMT3gHCD2l7g=="],
216
245
217
246
"@atproto/lexicon": ["@atproto/lexicon@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-lRmJgMA8f5j7VB5Iu5cp188ald5FuI4FlmZ7nn6EBrk1dgOstWVrI5Ft6K3z2vjyLZRG6nzknlsw+tDP63p7bQ=="],
218
247
219
-
"@atproto/oauth-client": ["@atproto/oauth-client@0.5.8", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.2", "@atproto-labs/fetch": "0.2.3", "@atproto-labs/handle-resolver": "0.3.2", "@atproto-labs/identity-resolver": "0.3.2", "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.1", "@atproto/jwk": "0.6.0", "@atproto/oauth-types": "0.5.0", "@atproto/xrpc": "0.7.5", "core-js": "^3", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-7YEym6d97+Dd73qGdkQTXi5La8xvCQxwRUDzzlR/NVAARa9a4YP7MCmqBJVeP2anT0By+DSAPyPDLTsxcjIcCg=="],
248
+
"@atproto/oauth-client": ["@atproto/oauth-client@0.5.10", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.4", "@atproto-labs/fetch": "0.2.3", "@atproto-labs/handle-resolver": "0.3.4", "@atproto-labs/identity-resolver": "0.3.4", "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.3", "@atproto/jwk": "0.6.0", "@atproto/oauth-types": "0.5.2", "@atproto/xrpc": "0.7.6", "core-js": "^3", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-2mdJFyYbaOw3e/1KMBOQ2/J9p+MfWW8kE6FKdExWrJ7JPJpTJw2ZF2EmdGHCVeXw386dQgXbLkr+w4vbgSqfMQ=="],
220
249
221
-
"@atproto/oauth-client-node": ["@atproto/oauth-client-node@0.3.10", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.2", "@atproto-labs/handle-resolver-node": "0.1.21", "@atproto-labs/simple-store": "0.3.0", "@atproto/did": "0.2.1", "@atproto/jwk": "0.6.0", "@atproto/jwk-jose": "0.1.11", "@atproto/jwk-webcrypto": "0.2.0", "@atproto/oauth-client": "0.5.8", "@atproto/oauth-types": "0.5.0" } }, "sha512-6khKlJqu1Ed5rt3rzcTD5hymB6JUjKdOHWYXwiphw4inkAIo6GxLCighI4eGOqZorYk2j8ueeTNB6KsgH0kcRw=="],
250
+
"@atproto/oauth-client-node": ["@atproto/oauth-client-node@0.3.12", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.4", "@atproto-labs/handle-resolver-node": "0.1.23", "@atproto-labs/simple-store": "0.3.0", "@atproto/did": "0.2.3", "@atproto/jwk": "0.6.0", "@atproto/jwk-jose": "0.1.11", "@atproto/jwk-webcrypto": "0.2.0", "@atproto/oauth-client": "0.5.10", "@atproto/oauth-types": "0.5.2" } }, "sha512-9ejfO1H8qo3EbiAJgxKcdcR5Ay/9hgaC5OdxtTN63bcOrkIhvBN0xpVPGZYLL1iJQyNeK1T5m/LDrv4gUS1B+g=="],
222
251
223
-
"@atproto/oauth-types": ["@atproto/oauth-types@0.5.0", "", { "dependencies": { "@atproto/did": "0.2.1", "@atproto/jwk": "0.6.0", "zod": "^3.23.8" } }, "sha512-33xz7HcXhbl+XRqbIMVu3GE02iK1nKe2oMWENASsfZEYbCz2b9ZOarOFuwi7g4LKqpGowGp0iRKsQHFcq4SDaQ=="],
252
+
"@atproto/oauth-types": ["@atproto/oauth-types@0.5.2", "", { "dependencies": { "@atproto/did": "0.2.3", "@atproto/jwk": "0.6.0", "zod": "^3.23.8" } }, "sha512-9DCDvtvCanTwAaU5UakYDO0hzcOITS3RutK5zfLytE5Y9unj0REmTDdN8Xd8YCfUJl7T/9pYpf04Uyq7bFTASg=="],
224
253
225
254
"@atproto/repo": ["@atproto/repo@0.8.11", "", { "dependencies": { "@atproto/common": "^0.5.0", "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.5.2", "@ipld/dag-cbor": "^7.0.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "varint": "^6.0.0", "zod": "^3.23.8" } }, "sha512-b/WCu5ITws4ILHoXiZz0XXB5U9C08fUVzkBQDwpnme62GXv8gUaAPL/ttG61OusW09ARwMMQm4vxoP0hTFg+zA=="],
226
255
227
256
"@atproto/sync": ["@atproto/sync@0.1.38", "", { "dependencies": { "@atproto/common": "^0.5.0", "@atproto/identity": "^0.4.10", "@atproto/lexicon": "^0.5.2", "@atproto/repo": "^0.8.11", "@atproto/syntax": "^0.4.1", "@atproto/xrpc-server": "^0.10.0", "multiformats": "^9.9.0", "p-queue": "^6.6.2", "ws": "^8.12.0" } }, "sha512-2rE0SM21Nk4hWw/XcIYFnzlWO6/gBg8mrzuWbOvDhD49sA/wW4zyjaHZ5t1gvk28/SLok2VZiIR8nYBdbf7F5Q=="],
228
257
229
-
"@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, "sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw=="],
258
+
"@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="],
230
259
231
-
"@atproto/ws-client": ["@atproto/ws-client@0.0.3", "", { "dependencies": { "@atproto/common": "^0.5.0", "ws": "^8.12.0" } }, "sha512-eKqkTWBk6zuMY+6gs02eT7mS8Btewm8/qaL/Dp00NDCqpNC+U59MWvQsOWT3xkNGfd9Eip+V6VI4oyPvAfsfTA=="],
260
+
"@atproto/ws-client": ["@atproto/ws-client@0.0.2", "", { "dependencies": { "@atproto/common": "^0.4.12", "ws": "^8.12.0" } }, "sha512-yb11WtI9cZfx/00MTgZRabB97Quf/TerMmtzIm2H2YirIq2oW++NPoufXYCuXuQGR4ep4fvCyzz0/GX95jCONQ=="],
232
261
233
262
"@atproto/xrpc": ["@atproto/xrpc@0.7.6", "", { "dependencies": { "@atproto/lexicon": "^0.5.2", "zod": "^3.23.8" } }, "sha512-RvCf4j0JnKYWuz3QzsYCntJi3VuiAAybQsMIUw2wLWcHhchO9F7UaBZINLL2z0qc/cYWPv5NSwcVydMseoCZLA=="],
234
263
235
-
"@atproto/xrpc-server": ["@atproto/xrpc-server@0.9.5", "", { "dependencies": { "@atproto/common": "^0.4.12", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.5.1", "@atproto/xrpc": "^0.7.5", "cbor-x": "^1.5.1", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "uint8arrays": "3.0.0", "ws": "^8.12.0", "zod": "^3.23.8" } }, "sha512-V0srjUgy6mQ5yf9+MSNBLs457m4qclEaWZsnqIE7RfYywvntexTAbMoo7J7ONfTNwdmA9Gw4oLak2z2cDAET4w=="],
264
+
"@atproto/xrpc-server": ["@atproto/xrpc-server@0.9.6", "", { "dependencies": { "@atproto/common": "^0.4.12", "@atproto/crypto": "^0.4.4", "@atproto/lexicon": "^0.5.1", "@atproto/ws-client": "^0.0.2", "@atproto/xrpc": "^0.7.5", "cbor-x": "^1.5.1", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "uint8arrays": "3.0.0", "ws": "^8.12.0", "zod": "^3.23.8" } }, "sha512-N/wPK0VEk8lZLkVsfG1wlkINQnBLO2fzWT+xclOjYl5lJwDi5xgiiyEQJAyZN49d6cmbsONu0SuOVw9pa5xLCw=="],
265
+
266
+
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
267
+
268
+
"@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="],
269
+
270
+
"@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="],
271
+
272
+
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
273
+
274
+
"@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
275
+
276
+
"@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
277
+
278
+
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
279
+
280
+
"@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.962.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/credential-provider-node": "3.962.0", "@aws-sdk/middleware-bucket-endpoint": "3.957.0", "@aws-sdk/middleware-expect-continue": "3.957.0", "@aws-sdk/middleware-flexible-checksums": "3.957.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-location-constraint": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-sdk-s3": "3.957.0", "@aws-sdk/middleware-ssec": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/signature-v4-multi-region": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/eventstream-serde-browser": "^4.2.7", "@smithy/eventstream-serde-config-resolver": "^4.3.7", "@smithy/eventstream-serde-node": "^4.2.7", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-blob-browser": "^4.2.8", "@smithy/hash-node": "^4.2.7", "@smithy/hash-stream-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/md5-js": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-I2/1McBZCcM3PfM4ck8D6gnZR3K7+yl1fGkwTq/3ThEn9tdLjNwcdgTbPfxfX6LoecLrH9Ekoo+D9nmQ0T261w=="],
281
+
282
+
"@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.958.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg=="],
283
+
284
+
"@aws-sdk/core": ["@aws-sdk/core@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@aws-sdk/xml-builder": "3.957.0", "@smithy/core": "^3.20.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/property-provider": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/signature-v4": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw=="],
285
+
286
+
"@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.957.0", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-qSwSfI+qBU9HDsd6/4fM9faCxYJx2yDuHtj+NVOQ6XYDWQzFab/hUdwuKZ77Pi6goLF1pBZhJ2azaC2w7LbnTA=="],
287
+
288
+
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.957.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog=="],
289
+
290
+
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.957.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/node-http-handler": "^4.4.7", "@smithy/property-provider": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" } }, "sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw=="],
291
+
292
+
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.962.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/credential-provider-env": "3.957.0", "@aws-sdk/credential-provider-http": "3.957.0", "@aws-sdk/credential-provider-login": "3.962.0", "@aws-sdk/credential-provider-process": "3.957.0", "@aws-sdk/credential-provider-sso": "3.958.0", "@aws-sdk/credential-provider-web-identity": "3.958.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/credential-provider-imds": "^4.2.7", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-h0kVnXLW2d3nxbcrR/Pfg3W/+YoCguasWz7/3nYzVqmdKarGrpJzaFdoZtLgvDSZ8VgWUC4lWOTcsDMV0UNqUQ=="],
293
+
294
+
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.962.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-kHYH6Av2UifG3mPkpPUNRh/PuX6adaAcpmsclJdHdxlixMCRdh8GNeEihq480DC0GmfqdpoSf1w2CLmLLPIS6w=="],
295
+
296
+
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.962.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.957.0", "@aws-sdk/credential-provider-http": "3.957.0", "@aws-sdk/credential-provider-ini": "3.962.0", "@aws-sdk/credential-provider-process": "3.957.0", "@aws-sdk/credential-provider-sso": "3.958.0", "@aws-sdk/credential-provider-web-identity": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/credential-provider-imds": "^4.2.7", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-CS78NsWRxLa+nWqeWBEYMZTLacMFIXs1C5WJuM9kD05LLiWL32ksljoPsvNN24Bc7rCSQIIMx/U3KGvkDVZMVg=="],
297
+
298
+
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.957.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg=="],
299
+
300
+
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.958.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.958.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/token-providers": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg=="],
301
+
302
+
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.958.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA=="],
303
+
304
+
"@aws-sdk/lib-storage": ["@aws-sdk/lib-storage@3.962.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/smithy-client": "^4.10.2", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", "tslib": "^2.6.2" }, "peerDependencies": { "@aws-sdk/client-s3": "^3.962.0" } }, "sha512-Ai5gWRQkzsUMQ6NPoZZoiLXoQ6/yPRcR4oracIVjyWcu48TfBpsRgbqY/5zNOM55ag1wPX9TtJJGOhK3TNk45g=="],
305
+
306
+
"@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@aws-sdk/util-arn-parser": "3.957.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iczcn/QRIBSpvsdAS/rbzmoBpleX1JBjXvCynMbDceVLBIcVrwT1hXECrhtIC2cjh4HaLo9ClAbiOiWuqt+6MA=="],
307
+
308
+
"@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-AlbK3OeVNwZZil0wlClgeI/ISlOt/SPUxBsIns876IFaVu/Pj3DgImnYhpcJuFRek4r4XM51xzIaGQXM6GDHGg=="],
309
+
310
+
"@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.957.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/crc64-nvme": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iJpeVR5V8se1hl2pt+k8bF/e9JO4KWgPCMjg8BtRspNtKIUGy7j6msYvbDixaKZaF2Veg9+HoYcOhwnZumjXSA=="],
311
+
312
+
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA=="],
313
+
314
+
"@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-y8/W7TOQpmDJg/fPYlqAhwA4+I15LrS7TwgUEoxogtkD8gfur9wFMRLT8LCyc9o4NMEcAnK50hSb4+wB0qv6tQ=="],
315
+
316
+
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ=="],
317
+
318
+
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA=="],
319
+
320
+
"@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.957.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-arn-parser": "3.957.0", "@smithy/core": "^3.20.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/protocol-http": "^5.3.7", "@smithy/signature-v4": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-5B2qY2nR2LYpxoQP0xUum5A1UNvH2JQpLHDH1nWFNF/XetV7ipFHksMxPNhtJJ6ARaWhQIDXfOUj0jcnkJxXUg=="],
321
+
322
+
"@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-qwkmrK0lizdjNt5qxl4tHYfASh8DFpHXM1iDVo+qHe+zuslfMqQEGRkzxS8tJq/I+8F0c6v3IKOveKJAfIvfqQ=="],
323
+
324
+
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.957.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@smithy/core": "^3.20.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ=="],
325
+
326
+
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.958.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.957.0", "@aws-sdk/middleware-host-header": "3.957.0", "@aws-sdk/middleware-logger": "3.957.0", "@aws-sdk/middleware-recursion-detection": "3.957.0", "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/region-config-resolver": "3.957.0", "@aws-sdk/types": "3.957.0", "@aws-sdk/util-endpoints": "3.957.0", "@aws-sdk/util-user-agent-browser": "3.957.0", "@aws-sdk/util-user-agent-node": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/core": "^3.20.0", "@smithy/fetch-http-handler": "^5.3.8", "@smithy/hash-node": "^4.2.7", "@smithy/invalid-dependency": "^4.2.7", "@smithy/middleware-content-length": "^4.2.7", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-retry": "^4.4.17", "@smithy/middleware-serde": "^4.2.8", "@smithy/middleware-stack": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/node-http-handler": "^4.4.7", "@smithy/protocol-http": "^5.3.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.16", "@smithy/util-defaults-mode-node": "^4.2.19", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw=="],
327
+
328
+
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/config-resolver": "^4.4.5", "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A=="],
329
+
330
+
"@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.957.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/protocol-http": "^5.3.7", "@smithy/signature-v4": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-t6UfP1xMUigMMzHcb7vaZcjv7dA2DQkk9C/OAP1dKyrE0vb4lFGDaTApi17GN6Km9zFxJthEMUbBc7DL0hq1Bg=="],
331
+
332
+
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.958.0", "", { "dependencies": { "@aws-sdk/core": "3.957.0", "@aws-sdk/nested-clients": "3.958.0", "@aws-sdk/types": "3.957.0", "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q=="],
333
+
334
+
"@aws-sdk/types": ["@aws-sdk/types@3.957.0", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg=="],
335
+
336
+
"@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.957.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g=="],
337
+
338
+
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-endpoints": "^3.2.7", "tslib": "^2.6.2" } }, "sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw=="],
339
+
340
+
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.957.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-nhmgKHnNV9K+i9daumaIz8JTLsIIML9PE/HUks5liyrjUzenjW/aHoc7WJ9/Td/gPZtayxFnXQSJRb/fDlBuJw=="],
341
+
342
+
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.957.0", "", { "dependencies": { "@aws-sdk/types": "3.957.0", "@smithy/types": "^4.11.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw=="],
343
+
344
+
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.957.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.957.0", "@aws-sdk/types": "3.957.0", "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q=="],
345
+
346
+
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.957.0", "", { "dependencies": { "@smithy/types": "^4.11.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA=="],
347
+
348
+
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.2", "", {}, "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg=="],
236
349
237
350
"@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="],
238
351
···
252
365
253
366
"@elysiajs/cors": ["@elysiajs/cors@1.4.0", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-pb0SCzBfFbFSYA/U40HHO7R+YrcXBJXOWgL20eSViK33ol1e20ru2/KUaZYo5IMUn63yaTJI/bQERuQ+77ND8g=="],
254
367
255
-
"@elysiajs/eden": ["@elysiajs/eden@1.4.4", "", { "peerDependencies": { "elysia": ">= 1.4.0-exp.0" } }, "sha512-/LVqflmgUcCiXb8rz1iRq9Rx3SWfIV/EkoNqDFGMx+TvOyo8QHAygFXAVQz7RHs+jk6n6mEgpI6KlKBANoErsQ=="],
368
+
"@elysiajs/eden": ["@elysiajs/eden@1.4.5", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-hIOeH+S5NU/84A7+t8yB1JjxqjmzRkBF9fnLn6y+AH8EcF39KumOAnciMhIOkhhThVZvXZ3d+GsizRc+Fxoi8g=="],
256
369
257
370
"@elysiajs/openapi": ["@elysiajs/openapi@1.4.11", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-d75bMxYJpN6qSDi/z9L1S7SLk1S/8Px+cTb3W2lrYzU8uQ5E0kXdy1oOMJEfTyVsz3OA19NP9KNxE7ztSbLBLg=="],
258
371
259
-
"@elysiajs/opentelemetry": ["@elysiajs/opentelemetry@1.4.6", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/sdk-node": "^0.200.0" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-jR7t4M6ZvMnBqzzHsNTL6y3sNq9jbGi2vKxbkizi/OO5tlvlKl/rnBGyFjZUjQ1Hte7rCz+2kfmgOQMhkjk+Og=="],
372
+
"@elysiajs/opentelemetry": ["@elysiajs/opentelemetry@1.4.8", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/instrumentation": "^0.200.0", "@opentelemetry/sdk-node": "^0.200.0" }, "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-c9unbcdXfehExCv1GsiTCfos5SyIAyDwP7apcMeXmUMBaJZiAYMfiEH8RFFFIfIHJHC/xlNJzUPodkcUaaoJJQ=="],
260
373
261
-
"@elysiajs/static": ["@elysiajs/static@1.4.6", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-cd61aY/DHOVhlnBjzTBX8E1XANIrsCH8MwEGHeLMaZzNrz0gD4Q8Qsde2dFMzu81I7ZDaaZ2Rim9blSLtUrYBg=="],
374
+
"@elysiajs/static": ["@elysiajs/static@1.4.7", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-Go4kIXZ0G3iWfkAld07HmLglqIDMVXdyRKBQK/sVEjtpDdjHNb+rUIje73aDTWpZYg4PEVHUpi9v4AlNEwrQug=="],
262
375
263
376
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.26.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-hj0sKNCQOOo2fgyII3clmJXP28VhgDfU5iy3GNHlWO76KG6N7x4D9ezH5lJtQTG+1J6MFDAJXC1qsI+W+LvZoA=="],
264
377
···
312
425
313
426
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.26.0", "", { "os": "win32", "cpu": "x64" }, "sha512-WAckBKaVnmFqbEhbymrPK7M086DQMpL1XoRbpmN0iW8k5JSXjDRQBhcZNa0VweItknLq9eAeCL34jK7/CDcw7A=="],
314
427
315
-
"@grpc/grpc-js": ["@grpc/grpc-js@1.14.1", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-sPxgEWtPUR3EnRJCEtbGZG2iX8LQDUls2wUS3o27jg07KqJFMq6YDeWvMo1wfpmy3rqRdS0rivpLwhqQtEyCuQ=="],
428
+
"@grpc/grpc-js": ["@grpc/grpc-js@1.14.2", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-QzVUtEFyu05UNx2xr0fCQmStUO17uVQhGNowtxs00IgTZT6/W2PBLfUkj30s0FKJ29VtTa3ArVNIhNP6akQhqA=="],
316
429
317
430
"@grpc/proto-loader": ["@grpc/proto-loader@0.8.0", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.3", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ=="],
318
431
···
342
455
343
456
"@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.0.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-IEkJGzK1A9v3/EHjXh3s2IiFc6L4jfK+lNgKVgUjeUJQRRhnVFMIO3TAvKwonm9O1HebCuoOt98v8bZW7oVQHA=="],
344
457
345
-
"@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
458
+
"@opentelemetry/core": ["@opentelemetry/core@1.29.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-gmT7vAreXl0DTHD2rVZcw3+l2g84+5XiHIqdBUxXbExymPCvSsGOpiwMmn8nkiJur28STV31wnhIDrzWDPzjfA=="],
346
459
347
460
"@opentelemetry/exporter-logs-otlp-grpc": ["@opentelemetry/exporter-logs-otlp-grpc@0.200.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-grpc-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/sdk-logs": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+3MDfa5YQPGM3WXxW9kqGD85Q7s9wlEMVNhXXG7tYFLnIeaseUt9YtCeFhEDFzfEktacdFpOtXmJuNW8cHbU5A=="],
348
461
···
352
465
353
466
"@opentelemetry/exporter-metrics-otlp-grpc": ["@opentelemetry/exporter-metrics-otlp-grpc@0.200.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.0", "@opentelemetry/exporter-metrics-otlp-http": "0.200.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-grpc-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-uHawPRvKIrhqH09GloTuYeq2BjyieYHIpiklOvxm9zhrCL2eRsnI/6g9v2BZTVtGp8tEgIa7rCQ6Ltxw6NBgew=="],
354
467
355
-
"@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5BiR6i8yHc9+qW7F6LqkuUnIzVNA7lt0qRxIKcKT+gq3eGUPHZ3DY29sfxI3tkvnwMgtnHDMNze5DdxW39HsAw=="],
468
+
"@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.56.0", "", { "dependencies": { "@opentelemetry/core": "1.29.0", "@opentelemetry/otlp-exporter-base": "0.56.0", "@opentelemetry/otlp-transformer": "0.56.0", "@opentelemetry/resources": "1.29.0", "@opentelemetry/sdk-metrics": "1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-GD5QuCT6js+mDpb5OBO6OSyCH+k2Gy3xPHJV9BnjV8W6kpSuY8y2Samzs5vl23UcGMq6sHLAbs+Eq/VYsLMiVw=="],
356
469
357
470
"@opentelemetry/exporter-metrics-otlp-proto": ["@opentelemetry/exporter-metrics-otlp-proto@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/exporter-metrics-otlp-http": "0.200.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-E+uPj0yyvz81U9pvLZp3oHtFrEzNSqKGVkIViTQY1rH3TOobeJPSpLnTVXACnCwkPR5XeTvPnK3pZ2Kni8AFMg=="],
358
471
···
368
481
369
482
"@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-pmPlzfJd+vvgaZd/reMsC8RWgTXn2WY1OWT5RT42m3aOn5532TozwXNDhg1vzqJ+jnvmkREcdLr27ebJEQt0Jg=="],
370
483
371
-
"@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
484
+
"@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.56.0", "", { "dependencies": { "@opentelemetry/core": "1.29.0", "@opentelemetry/otlp-transformer": "0.56.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-eURvv0fcmBE+KE1McUeRo+u0n18ZnUeSc7lDlW/dzlqFYasEbsztTK4v0Qf8C4vEY+aMTjPKUxBG0NX2Te3Pmw=="],
372
485
373
486
"@opentelemetry/otlp-grpc-exporter-base": ["@opentelemetry/otlp-grpc-exporter-base@0.200.0", "", { "dependencies": { "@grpc/grpc-js": "^1.7.1", "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CK2S+bFgOZ66Bsu5hlDeOX6cvW5FVtVjFFbWuaJP0ELxJKBB6HlbLZQ2phqz/uLj1cWap5xJr/PsR3iGoB7Vqw=="],
374
487
375
-
"@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
488
+
"@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.56.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.56.0", "@opentelemetry/core": "1.29.0", "@opentelemetry/resources": "1.29.0", "@opentelemetry/sdk-logs": "0.56.0", "@opentelemetry/sdk-metrics": "1.29.0", "@opentelemetry/sdk-trace-base": "1.29.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-kVkH/W2W7EpgWWpyU5VnnjIdSD7Y7FljQYObAQSKdRcejiwMj2glypZtUdfq1LTJcv4ht0jyTrw1D3CCxssNtQ=="],
376
489
377
490
"@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-blx9S2EI49Ycuw6VZq+bkpaIoiJFhsDuvFGhBIoH3vJ5oYjJ2U0s3fAM5jYft99xVIAv6HqoPtlP9gpVA2IZtA=="],
378
491
379
492
"@opentelemetry/propagator-jaeger": ["@opentelemetry/propagator-jaeger@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Mbm/LSFyAtQKP0AQah4AfGgsD+vsZcyreZoQ5okFBk33hU7AquU4TltgyL9dvaO8/Zkoud8/0gEvwfOZ5d7EPA=="],
380
493
381
-
"@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
494
+
"@opentelemetry/resources": ["@opentelemetry/resources@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA=="],
382
495
383
496
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-VZG870063NLfObmQQNtCVcdXXLzI3vOjjrRENmU37HYiPFa0ZXpXVDsTD02Nh3AT3xYJzQaWKl2X2lQ2l7TWJA=="],
384
497
385
-
"@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
498
+
"@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@1.30.1", "", { "dependencies": { "@opentelemetry/core": "1.30.1", "@opentelemetry/resources": "1.30.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q9zcZ0Okl8jRgmy7eNW3Ku1XSgg3sDLa5evHZpCwjspw7E8Is4K/haRPDJrBcX3YSn/Y7gUvFnByNYEKQNbNog=="],
386
499
387
500
"@opentelemetry/sdk-node": ["@opentelemetry/sdk-node@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/exporter-logs-otlp-grpc": "0.200.0", "@opentelemetry/exporter-logs-otlp-http": "0.200.0", "@opentelemetry/exporter-logs-otlp-proto": "0.200.0", "@opentelemetry/exporter-metrics-otlp-grpc": "0.200.0", "@opentelemetry/exporter-metrics-otlp-http": "0.200.0", "@opentelemetry/exporter-metrics-otlp-proto": "0.200.0", "@opentelemetry/exporter-prometheus": "0.200.0", "@opentelemetry/exporter-trace-otlp-grpc": "0.200.0", "@opentelemetry/exporter-trace-otlp-http": "0.200.0", "@opentelemetry/exporter-trace-otlp-proto": "0.200.0", "@opentelemetry/exporter-zipkin": "2.0.0", "@opentelemetry/instrumentation": "0.200.0", "@opentelemetry/propagator-b3": "2.0.0", "@opentelemetry/propagator-jaeger": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "@opentelemetry/sdk-trace-node": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-S/YSy9GIswnhYoDor1RusNkmRughipvTCOQrlF1dzI70yQaf68qgf5WMnzUxdlCl3/et/pvaO75xfPfuEmCK5A=="],
388
501
···
392
505
393
506
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.38.0", "", {}, "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg=="],
394
507
395
-
"@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-licBDIbbLP5L5/S0+bwtJynso94XD3KyqSP48K59Sq7Mude6C7dR5ZujZm4Ut4BwZqUFfNOfYNMWBU5nlL7t1A=="],
508
+
"@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-eJopQrUk0WR7jViYDC29+Rp50xGvs4GtWOXBeqCoFMzutkkO3CZvHehA4JqnjfWMTSS8toqvRhCSOpOz62Wf9w=="],
396
509
397
-
"@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-hn8lLzsYyyh6ULo2E8v2SqtrWOkdQKJwapeVy1rDw7juTTeHY3KDudGWf4mVYteC9riZU6HD88Fn3nGwyX0eIg=="],
510
+
"@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-xGDePueVFrNgkS+iN0QdEFeRrx2MQ5hQ9ipRFu7N73rgoSSJsFlOKKt2uGZzunczedViIfjYl0ii0K4E9aZ0Ow=="],
398
511
399
-
"@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-UHxdtbyxdtNJUNcXtIrjx3Lmq8ji3KywlXtIHV/0vn9A8W5mulqOcryqUWMFVH9JTIIzmNn6Q/qVmXHTME63Ww=="],
512
+
"@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ij4wQ9ECLFf1XFry+IFUN+28if40ozDqq6+QtuyOhIwraKzXOlAUbILhRMGvM3ED3yBex2mTwlKpA4Vja/V2g=="],
400
513
401
-
"@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5uZzxzvHU/z+3cZwN/A0H8G+enQ+9FkeJVZkE2fwK2XhiJZFUGAuWajCpy7GepvOWlqV7VjPaKi2+Qmr4IX7nQ=="],
514
+
"@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-DabZ3Mt1XcJneWdEEug8l7bCPVvDBRBpjUIpNnRnMFWFnzr8KBEpMcaWTwYOghjXyJdhB4MPKb19MwqyQ+FHAw=="],
402
515
403
-
"@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-OD9DYkjes7WXieBn4zQZGXWhRVZhIEWMDGCetZ3H4vxIuweZ++iul/CNX5jdpNXaJ17myb1ROMvmRbrqW44j3w=="],
516
+
"@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-XWQ3tV/gtZj0wn2AdSUq/tEOKWT4OY+Uww70EbODgrrq00jxuTfq5nnYP6rkLD0M/T5BHJdQRSfQYdIni9vldw=="],
404
517
405
-
"@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EoEuRP9bxAxVKuvi6tZ0ZENjueP4lvjz0mKsMzdG0kwg/2apGKiirH1l0RIcdmvfDGGuDmNiv/XBpkoXq1x8ug=="],
518
+
"@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-7eIARtKZKZDtah1aCpQUj/1/zT/zHRR063J6oAxZP9AuA547j5B9OM2D/vi/F4En7Gjk9FPjgPGTSYeqpQDzJw=="],
406
519
407
-
"@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m9Ov9YH8KjRLui87eNtQQFKVnjGsNk3xgbrR9c8d2FS3NfZSxmVjSeBvEsDjzNf1TXLDriHb/NYOlpiMf/QzDg=="],
520
+
"@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-IU8pxhIf845psOv55LqJyL+tSUc6HHMfs6FGhuJcAnyi92j+B1HjOhnFQh9MW4vjoo7do5F8AerXlvk59RGH2w=="],
408
521
409
-
"@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-3TuOsRVoG8K+soQWRo+Cp5ACpRs6rTFSu5tAqc/6WrqwbNWmqjov/eWJPTgz3gPXnC7uNKVG7RxxAmV8r2EYTQ=="],
522
+
"@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-xNSDRPn1yyObKteS8fyQogwsS4eCECswHHgaKM+/d4wy/omZQrXn8ZyGm/ZF9B73UfQytUfbhE7nEnrFq03f0w=="],
410
523
411
-
"@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-q8Hto8hcpofPJjvuvjuwyYvhOaAzPw1F5vRUUeOJDmDwZ4lZhANFM0rUwchMzfWUJCD6jg8/EVQ8MiixnZWU0A=="],
524
+
"@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-JoRTPdAXRkNYouUlJqEncMWUKn/3DiWP03A7weBbtbsKr787gcdNna2YeyQKCb1lIXE4v1k18RM3gaOpQobGIQ=="],
412
525
413
-
"@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-nZJUa5NprPYQ4Ii4cMwtP9PzlJJTp1XhxJ+A9eSn1Jfr6YygVWyN2KLjenyI93IcuBouBAaepDAVZZjH2lFBhg=="],
526
+
"@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-kWqa1LKvDdAIzyfHxo3zGz3HFWbFHDlrNK77hKjUN42ycikvZJ+SHSX76+1OW4G8wmLETX4Jj+4BM1y01DQRIQ=="],
414
527
415
-
"@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-s00T99MjB+xLOWq+t+wVaVBrry+oBOZNiTJijt+bmkp/MJptYS3FGvs7a+nkjLNzoNDoWQcXgKew6AaHES37Bg=="],
528
+
"@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.3", "", { "os": "win32", "cpu": "x64" }, "sha512-u5eZHKq6TPJSE282KyBOicGQ2trkFml0RoUfqkPOJVo7TXGrsGYYzdsugZRnVQY/WEmnxGtBy4T3PAaPqgQViA=="],
416
529
417
530
"@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="],
418
531
···
516
629
517
630
"@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="],
518
631
632
+
"@smithy/abort-controller": ["@smithy/abort-controller@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw=="],
633
+
634
+
"@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA=="],
635
+
636
+
"@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.1", "", { "dependencies": { "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-lX9Ay+6LisTfpLid2zZtIhSEjHMZoAR5hHCR4H7tBz/Zkfr5ea8RcQ7Tk4mi0P76p4cN+Btz16Ffno7YHpKXnQ=="],
637
+
638
+
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.5", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.7", "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg=="],
639
+
640
+
"@smithy/core": ["@smithy/core@3.20.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.8", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ=="],
641
+
642
+
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.7", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/property-provider": "^4.2.7", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA=="],
643
+
644
+
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.7", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ=="],
645
+
646
+
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.7", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-ujzPk8seYoDBmABDE5YqlhQZAXLOrtxtJLrbhHMKjBoG5b4dK4i6/mEU+6/7yXIAkqOO8sJ6YxZl+h0QQ1IJ7g=="],
647
+
648
+
"@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-x7BtAiIPSaNaWuzm24Q/mtSkv+BrISO/fmheiJ39PKRNH3RmH2Hph/bUKSOBOBC9unqfIYDhKTHwpyZycLGPVQ=="],
649
+
650
+
"@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.7", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-roySCtHC5+pQq5lK4be1fZ/WR6s/AxnPaLfCODIPArtN2du8s5Ot4mKVK3pPtijL/L654ws592JHJ1PbZFF6+A=="],
651
+
652
+
"@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.7", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-QVD+g3+icFkThoy4r8wVFZMsIP08taHVKjE6Jpmz8h5CgX/kk6pTODq5cht0OMtcapUx+xrPzUTQdA+TmO0m1g=="],
653
+
654
+
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.7", "@smithy/querystring-builder": "^4.2.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" } }, "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg=="],
655
+
656
+
"@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.8", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", "@smithy/chunked-blob-reader-native": "^4.2.1", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-07InZontqsM1ggTCPSRgI7d8DirqRrnpL7nIACT4PW0AWrgDiHhjGZzbAE5UtRSiU0NISGUYe7/rri9ZeWyDpw=="],
657
+
658
+
"@smithy/hash-node": ["@smithy/hash-node@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw=="],
659
+
660
+
"@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZQVoAwNYnFMIbd4DUc517HuwNelJUY6YOzwqrbcAgCnVn+79/OK7UjwA93SPpdTOpKDVkLIzavWm/Ck7SmnDPQ=="],
661
+
662
+
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ=="],
663
+
664
+
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ=="],
665
+
666
+
"@smithy/md5-js": ["@smithy/md5-js@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Wv6JcUxtOLTnxvNjDnAiATUsk8gvA6EeS8zzHig07dotpByYsLot+m0AaQEniUBjx97AC41MQR4hW0baraD1Xw=="],
667
+
668
+
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.7", "", { "dependencies": { "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg=="],
669
+
670
+
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.1", "", { "dependencies": { "@smithy/core": "^3.20.0", "@smithy/middleware-serde": "^4.2.8", "@smithy/node-config-provider": "^4.3.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "@smithy/url-parser": "^4.2.7", "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" } }, "sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg=="],
671
+
672
+
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.17", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/protocol-http": "^5.3.7", "@smithy/service-error-classification": "^4.2.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-retry": "^4.2.7", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg=="],
673
+
674
+
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w=="],
675
+
676
+
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw=="],
677
+
678
+
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.7", "", { "dependencies": { "@smithy/property-provider": "^4.2.7", "@smithy/shared-ini-file-loader": "^4.4.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw=="],
679
+
680
+
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.4.7", "", { "dependencies": { "@smithy/abort-controller": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/querystring-builder": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ=="],
681
+
682
+
"@smithy/property-provider": ["@smithy/property-provider@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA=="],
683
+
684
+
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA=="],
685
+
686
+
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg=="],
687
+
688
+
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w=="],
689
+
690
+
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0" } }, "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA=="],
691
+
692
+
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.2", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg=="],
693
+
694
+
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.7", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.7", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg=="],
695
+
696
+
"@smithy/smithy-client": ["@smithy/smithy-client@4.10.2", "", { "dependencies": { "@smithy/core": "^3.20.0", "@smithy/middleware-endpoint": "^4.4.1", "@smithy/middleware-stack": "^4.2.7", "@smithy/protocol-http": "^5.3.7", "@smithy/types": "^4.11.0", "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" } }, "sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g=="],
697
+
698
+
"@smithy/types": ["@smithy/types@4.11.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA=="],
699
+
700
+
"@smithy/url-parser": ["@smithy/url-parser@4.2.7", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg=="],
701
+
702
+
"@smithy/util-base64": ["@smithy/util-base64@4.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ=="],
703
+
704
+
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg=="],
705
+
706
+
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA=="],
707
+
708
+
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew=="],
709
+
710
+
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="],
711
+
712
+
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.16", "", { "dependencies": { "@smithy/property-provider": "^4.2.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ=="],
713
+
714
+
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.19", "", { "dependencies": { "@smithy/config-resolver": "^4.4.5", "@smithy/credential-provider-imds": "^4.2.7", "@smithy/node-config-provider": "^4.3.7", "@smithy/property-provider": "^4.2.7", "@smithy/smithy-client": "^4.10.2", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA=="],
715
+
716
+
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.7", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg=="],
717
+
718
+
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw=="],
719
+
720
+
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.7", "", { "dependencies": { "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w=="],
721
+
722
+
"@smithy/util-retry": ["@smithy/util-retry@4.2.7", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg=="],
723
+
724
+
"@smithy/util-stream": ["@smithy/util-stream@4.5.8", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.8", "@smithy/node-http-handler": "^4.4.7", "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w=="],
725
+
726
+
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA=="],
727
+
728
+
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.0", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw=="],
729
+
730
+
"@smithy/util-waiter": ["@smithy/util-waiter@4.2.7", "", { "dependencies": { "@smithy/abort-controller": "^4.2.7", "@smithy/types": "^4.11.0", "tslib": "^2.6.2" } }, "sha512-vHJFXi9b7kUEpHWUCY3Twl+9NPOZvQ0SAi+Ewtn48mbiJk4JY9MZmKQjGB4SCvVb9WPiSphZJYY6RIbs+grrzw=="],
731
+
732
+
"@smithy/uuid": ["@smithy/uuid@1.1.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw=="],
733
+
519
734
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
520
735
521
736
"@tailwindcss/cli": ["@tailwindcss/cli@4.1.17", "", { "dependencies": { "@parcel/watcher": "^2.5.1", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "enhanced-resolve": "^5.18.3", "mri": "^1.2.0", "picocolors": "^1.1.1", "tailwindcss": "4.1.17" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "sha512-jUIxcyUNlCC2aNPnyPEWU/L2/ik3pB4fF3auKGXr8AvN3T3OFESVctFKOBoPZQaZJIeUpPn1uCLp0MRxuek8gg=="],
···
548
763
549
764
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
550
765
551
-
"@tanstack/query-core": ["@tanstack/query-core@5.90.7", "", {}, "sha512-6PN65csiuTNfBMXqQUxQhCNdtm1rV+9kC9YwWAIKcaxAauq3Wu7p18j3gQY3YIBJU70jT/wzCCZ2uqto/vQgiQ=="],
766
+
"@tanstack/query-core": ["@tanstack/query-core@5.90.12", "", {}, "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg=="],
552
767
553
-
"@tanstack/react-query": ["@tanstack/react-query@5.90.7", "", { "dependencies": { "@tanstack/query-core": "5.90.7" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-wAHc/cgKzW7LZNFloThyHnV/AX9gTg3w5yAv0gvQHPZoCnepwqCMtzbuPbb2UvfvO32XZ46e8bPOYbfZhzVnnQ=="],
768
+
"@tanstack/react-query": ["@tanstack/react-query@5.90.12", "", { "dependencies": { "@tanstack/query-core": "5.90.12" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg=="],
554
769
555
-
"@tokenizer/inflate": ["@tokenizer/inflate@0.2.7", "", { "dependencies": { "debug": "^4.4.0", "fflate": "^0.8.2", "token-types": "^6.0.0" } }, "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg=="],
770
+
"@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
556
771
557
772
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
558
773
559
774
"@ts-morph/common": ["@ts-morph/common@0.25.0", "", { "dependencies": { "minimatch": "^9.0.4", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.9" } }, "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg=="],
560
775
561
-
"@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="],
776
+
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
562
777
563
778
"@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="],
564
779
565
780
"@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="],
566
781
567
-
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
782
+
"@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="],
568
783
569
-
"@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="],
784
+
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
570
785
571
786
"@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="],
572
787
···
594
809
595
810
"acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="],
596
811
597
-
"actor-typeahead": ["actor-typeahead@0.1.1", "", {}, "sha512-ilsBwzplKwMSBiO6Tg6RdaZ5xxqgXds5jCQuHV+ib9Aq3ja9g0T7u2Y1PmihotmS7l5RxhpGI/tPm3ljoRDRwg=="],
812
+
"actor-typeahead": ["actor-typeahead@0.1.2", "", {}, "sha512-I97YqqNl7Kar0J/bIJvgY/KmHpssHcDElhfwVTLP7wRFlkxso2ZLBqiS2zol5A8UVUJbQK2JXYaqNpZXz8Uk2A=="],
598
813
599
814
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
600
815
···
606
821
607
822
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
608
823
609
-
"atproto-ui": ["atproto-ui@0.11.3", "", { "dependencies": { "@atcute/atproto": "^3.1.7", "@atcute/bluesky": "^3.2.3", "@atcute/client": "^4.0.3", "@atcute/identity-resolver": "^1.1.3", "@atcute/tangled": "^1.0.10" }, "peerDependencies": { "react": "^18.2.0 || ^19.0.0", "react-dom": "^18.2.0 || ^19.0.0" }, "optionalPeers": ["react-dom"] }, "sha512-NIBsORuo9lpCpr1SNKcKhNvqOVpsEy9IoHqFe1CM9gNTArpQL1hUcoP1Cou9a1O5qzCul9kaiu5xBHnB81I/WQ=="],
824
+
"atproto-ui": ["atproto-ui@0.12.0", "", { "dependencies": { "@atcute/atproto": "^3.1.7", "@atcute/bluesky": "^3.2.3", "@atcute/client": "^4.0.3", "@atcute/identity-resolver": "^1.1.3", "@atcute/tangled": "^1.0.10" }, "peerDependencies": { "react": "^18.2.0 || ^19.0.0", "react-dom": "^18.2.0 || ^19.0.0" }, "optionalPeers": ["react-dom"] }, "sha512-vdJmKNyuGWspuIIvySD601dL8wLJafgxfS/6NGBvbBFectoiaZ92Cua2JdDuSD/uRxUnRJ3AvMg7eL0M39DZ3Q=="],
610
825
611
826
"await-lock": ["await-lock@2.2.2", "", {}, "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="],
612
827
···
614
829
615
830
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
616
831
617
-
"body-parser": ["body-parser@1.20.3", "", { "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" } }, "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g=="],
832
+
"body-parser": ["body-parser@1.20.4", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA=="],
833
+
834
+
"bowser": ["bowser@2.13.1", "", {}, "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw=="],
618
835
619
836
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
620
837
621
838
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
622
839
623
-
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
840
+
"buffer": ["buffer@5.6.0", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4" } }, "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw=="],
624
841
625
-
"bun": ["bun@1.3.2", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.2", "@oven/bun-darwin-x64": "1.3.2", "@oven/bun-darwin-x64-baseline": "1.3.2", "@oven/bun-linux-aarch64": "1.3.2", "@oven/bun-linux-aarch64-musl": "1.3.2", "@oven/bun-linux-x64": "1.3.2", "@oven/bun-linux-x64-baseline": "1.3.2", "@oven/bun-linux-x64-musl": "1.3.2", "@oven/bun-linux-x64-musl-baseline": "1.3.2", "@oven/bun-windows-x64": "1.3.2", "@oven/bun-windows-x64-baseline": "1.3.2" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-x75mPJiEfhO1j4Tfc65+PtW6ZyrAB6yTZInydnjDZXF9u9PRAnr6OK3v0Q9dpDl0dxRHkXlYvJ8tteJxc8t4Sw=="],
842
+
"bun": ["bun@1.3.3", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.3", "@oven/bun-darwin-x64": "1.3.3", "@oven/bun-darwin-x64-baseline": "1.3.3", "@oven/bun-linux-aarch64": "1.3.3", "@oven/bun-linux-aarch64-musl": "1.3.3", "@oven/bun-linux-x64": "1.3.3", "@oven/bun-linux-x64-baseline": "1.3.3", "@oven/bun-linux-x64-musl": "1.3.3", "@oven/bun-linux-x64-musl-baseline": "1.3.3", "@oven/bun-windows-x64": "1.3.3", "@oven/bun-windows-x64-baseline": "1.3.3" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-2hJ4ocTZ634/Ptph4lysvO+LbbRZq8fzRvMwX0/CqaLBxrF2UB5D1LdMB8qGcdtCer4/VR9Bx5ORub0yn+yzmw=="],
626
843
627
844
"bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="],
628
845
629
-
"bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="],
846
+
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
630
847
631
848
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
632
849
···
662
879
663
880
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
664
881
665
-
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
882
+
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
666
883
667
-
"cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="],
884
+
"cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
668
885
669
-
"core-js": ["core-js@3.46.0", "", {}, "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA=="],
886
+
"core-js": ["core-js@3.47.0", "", {}, "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg=="],
670
887
671
-
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
888
+
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
672
889
673
890
"debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
674
891
···
684
901
685
902
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
686
903
687
-
"elysia": ["elysia@1.4.16", "", { "dependencies": { "cookie": "^1.0.2", "exact-mirror": "0.2.3", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-KZtKN160/bdWVKg2hEgyoNXY8jRRquc+m6PboyisaLZL891I+Ufb7Ja6lDAD7vMQur8sLEWIcidZOzj5lWw9UA=="],
904
+
"elysia": ["elysia@1.4.18", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "0.2.5", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-A6BhlipmSvgCy69SBgWADYZSdDIj3fT2gk8/9iMAC8iD+aGcnCr0fitziX0xr36MFDs/fsvVp8dWqxeq1VCgKg=="],
688
905
689
906
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
690
907
···
714
931
715
932
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
716
933
717
-
"exact-mirror": ["exact-mirror@0.2.3", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-aLdARfO0W0ntufjDyytUJQMbNXoB9g+BbA8KcgIq4XOOTYRw48yUGON/Pr64iDrYNZKcKvKbqE0MPW56FF2BXA=="],
934
+
"exact-mirror": ["exact-mirror@0.2.5", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-u8Wu2lO8nio5lKSJubOydsdNtQmH8ENba5m0nbQYmTvsjksXKYIS1nSShdDlO8Uem+kbo+N6eD5I03cpZ+QsRQ=="],
718
935
719
-
"express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="],
936
+
"express": ["express@4.22.1", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g=="],
720
937
721
938
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
722
939
723
940
"fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="],
941
+
942
+
"fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="],
724
943
725
944
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
726
945
727
-
"fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="],
728
-
729
-
"file-type": ["file-type@21.0.0", "", { "dependencies": { "@tokenizer/inflate": "^0.2.7", "strtok3": "^10.2.2", "token-types": "^6.0.0", "uint8array-extras": "^1.4.0" } }, "sha512-ek5xNX2YBYlXhiUXui3D/BXa3LdqPmoLJ7rqEx2bKJ7EAUEfmXgW0Das7Dc6Nr9MvqaOnIqiPV0mZk/r/UpNAg=="],
946
+
"file-type": ["file-type@21.1.1", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-ifJXo8zUqbQ/bLbl9sFoqHNTNWbnPY1COImFfM6CCy7z+E+jC1eY9YfOKkx0fckIg+VljAy2/87T61fp0+eEkg=="],
730
947
731
948
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
732
949
733
-
"finalhandler": ["finalhandler@1.3.1", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" } }, "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ=="],
950
+
"finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
734
951
735
952
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
736
953
···
754
971
755
972
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
756
973
757
-
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
758
-
759
974
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
760
975
761
976
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
762
977
763
978
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
764
979
765
-
"hono": ["hono@4.10.6", "", {}, "sha512-BIdolzGpDO9MQ4nu3AUuDwHZZ+KViNm+EZ75Ae55eMXMqLVhDFqEMXxtUe9Qh8hjL+pIna/frs2j6Y2yD5Ua/g=="],
980
+
"hono": ["hono@4.10.7", "", {}, "sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw=="],
766
981
767
-
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
982
+
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
768
983
769
984
"iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
770
985
···
898
1113
899
1114
"pino-std-serializers": ["pino-std-serializers@6.2.2", "", {}, "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA=="],
900
1115
901
-
"playwright": ["playwright@1.56.1", "", { "dependencies": { "playwright-core": "1.56.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw=="],
1116
+
"playwright": ["playwright@1.57.0", "", { "dependencies": { "playwright-core": "1.57.0" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw=="],
902
1117
903
-
"playwright-core": ["playwright-core@1.56.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ=="],
1118
+
"playwright-core": ["playwright-core@1.57.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ=="],
904
1119
905
1120
"postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
906
1121
907
-
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
1122
+
"prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
908
1123
909
1124
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
910
1125
···
916
1131
917
1132
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
918
1133
919
-
"qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
1134
+
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
920
1135
921
1136
"quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="],
922
1137
···
924
1139
925
1140
"rate-limiter-flexible": ["rate-limiter-flexible@2.4.2", "", {}, "sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw=="],
926
1141
927
-
"raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="],
1142
+
"raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
928
1143
929
-
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
1144
+
"react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="],
930
1145
931
-
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
1146
+
"react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="],
932
1147
933
-
"react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="],
1148
+
"react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
934
1149
935
1150
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
936
1151
937
1152
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
938
1153
939
-
"readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
1154
+
"readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
940
1155
941
1156
"real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
942
1157
···
956
1171
957
1172
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
958
1173
959
-
"send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
1174
+
"send": ["send@0.19.1", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg=="],
960
1175
961
1176
"serve-static": ["serve-static@1.16.2", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" } }, "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw=="],
962
1177
···
978
1193
979
1194
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
980
1195
981
-
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
1196
+
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
1197
+
1198
+
"stream-browserify": ["stream-browserify@3.0.0", "", { "dependencies": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" } }, "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA=="],
982
1199
983
1200
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
984
1201
985
1202
"string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
986
1203
987
1204
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1205
+
1206
+
"strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="],
988
1207
989
1208
"strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="],
990
1209
···
992
1211
993
1212
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
994
1213
995
-
"tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="],
1214
+
"tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="],
996
1215
997
1216
"tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
998
1217
999
1218
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
1000
1219
1001
1220
"thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw=="],
1221
+
1222
+
"tiered-storage": ["tiered-storage@1.0.3", "", { "dependencies": { "@aws-sdk/client-s3": "^3.500.0", "@aws-sdk/lib-storage": "^3.500.0", "hono": "^4.10.7", "mime-types": "^3.0.2", "tiny-lru": "^11.0.0" } }, "sha512-ntJCsWWYHSQWIDbObMfnJ8xw7cRSJCPzfbrjoY4hu0YIRizCthRWg8kCvJOgsgC+Upt259YHmfNwEr1wLv6Rxw=="],
1223
+
1224
+
"tiny-lru": ["tiny-lru@11.4.5", "", {}, "sha512-hkcz3FjNJfKXjV4mjQ1OrXSLAehg8Hw+cEZclOVT+5c/cWQWImQ9wolzTjth+dmmDe++p3bme3fTxz6Q4Etsqw=="],
1002
1225
1003
1226
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
1004
1227
···
1014
1237
1015
1238
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
1016
1239
1017
-
"tsx": ["tsx@4.20.6", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg=="],
1240
+
"tsx": ["tsx@4.21.0", "", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="],
1018
1241
1019
1242
"tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="],
1020
1243
···
1040
1263
1041
1264
"use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
1042
1265
1266
+
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
1267
+
1043
1268
"utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
1044
1269
1045
1270
"varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="],
···
1064
1289
1065
1290
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1066
1291
1067
-
"@atproto-labs/fetch-node/ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="],
1068
-
1069
-
"@atproto/api/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg=="],
1292
+
"@atproto-labs/fetch-node/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="],
1070
1293
1071
1294
"@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.4.14", "", { "dependencies": { "@atproto/common-web": "^0.4.2", "@atproto/syntax": "^0.4.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-jiKpmH1QER3Gvc7JVY5brwrfo+etFoe57tKPQX/SmPwjvUsFnJAow5xLIryuBaJgFAhnTZViXKs41t//pahGHQ=="],
1072
1295
···
1074
1297
1075
1298
"@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1076
1299
1077
-
"@atproto/common/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg=="],
1078
-
1079
1300
"@atproto/common/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1080
1301
1081
1302
"@atproto/jwk/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1082
1303
1083
1304
"@atproto/lex-cbor/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1084
1305
1085
-
"@atproto/lex-cli/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A=="],
1086
-
1087
1306
"@atproto/lex-data/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1088
1307
1089
1308
"@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1090
-
1091
-
"@atproto/oauth-client/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, "sha512-MUYNn5d2hv8yVegRL0ccHvTHAVj5JSnW07bkbiaz96UH45lvYNRVwt44z+yYVnb0/mvBzyD3/ZQ55TRGt7fHkA=="],
1092
1309
1093
1310
"@atproto/oauth-client/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1094
1311
1095
-
"@atproto/repo/@atproto/common": ["@atproto/common@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.5", "@atproto/lex-cbor": "0.0.1", "@atproto/lex-data": "0.0.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-0S57sjzw4r9OLc5srJFi6uAz/aTKYl6btz3x36tSnGriL716m6h0x2IVtgd+FhUfIQfisevrqcqw8SfaGk8VTw=="],
1312
+
"@atproto/repo/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="],
1096
1313
1097
1314
"@atproto/repo/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1098
1315
1099
-
"@atproto/sync/@atproto/common": ["@atproto/common@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.5", "@atproto/lex-cbor": "0.0.1", "@atproto/lex-data": "0.0.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-0S57sjzw4r9OLc5srJFi6uAz/aTKYl6btz3x36tSnGriL716m6h0x2IVtgd+FhUfIQfisevrqcqw8SfaGk8VTw=="],
1316
+
"@atproto/sync/@atproto/common": ["@atproto/common@0.5.2", "", { "dependencies": { "@atproto/common-web": "^0.4.6", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-7KdU8FcIfnwS2kmv7M86pKxtw/fLvPY2bSI1rXpG+AmA8O++IUGlSCujBGzbrPwnQvY/z++f6Le4rdBzu8bFaA=="],
1100
1317
1101
-
"@atproto/sync/@atproto/xrpc-server": ["@atproto/xrpc-server@0.10.1", "", { "dependencies": { "@atproto/common": "^0.5.1", "@atproto/crypto": "^0.4.4", "@atproto/lex-cbor": "0.0.1", "@atproto/lex-data": "0.0.1", "@atproto/lexicon": "^0.5.2", "@atproto/ws-client": "^0.0.3", "@atproto/xrpc": "^0.7.6", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "ws": "^8.12.0", "zod": "^3.23.8" } }, "sha512-kHXykL4inBV/49vefn5zR5zv/VM1//+BIRqk9OvB3+mbERw0jkFiHhc6PWyY/81VD4ciu7FZwUCpRy/mtQtIaA=="],
1318
+
"@atproto/sync/@atproto/xrpc-server": ["@atproto/xrpc-server@0.10.2", "", { "dependencies": { "@atproto/common": "^0.5.2", "@atproto/crypto": "^0.4.5", "@atproto/lex-cbor": "0.0.2", "@atproto/lex-data": "0.0.2", "@atproto/lexicon": "^0.5.2", "@atproto/ws-client": "^0.0.3", "@atproto/xrpc": "^0.7.6", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "ws": "^8.12.0", "zod": "^3.23.8" } }, "sha512-5AzN8xoV8K1Omn45z6qKH414+B3Z35D536rrScwF3aQGDEdpObAS+vya9UoSg+Gvm2+oOtVEbVri7riLTBW3Vg=="],
1102
1319
1103
1320
"@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1104
1321
1105
-
"@atproto/ws-client/@atproto/common": ["@atproto/common@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.5", "@atproto/lex-cbor": "0.0.1", "@atproto/lex-data": "0.0.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-0S57sjzw4r9OLc5srJFi6uAz/aTKYl6btz3x36tSnGriL716m6h0x2IVtgd+FhUfIQfisevrqcqw8SfaGk8VTw=="],
1322
+
"@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
1106
1323
1107
-
"@atproto/xrpc-server/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A=="],
1324
+
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
1108
1325
1109
-
"@atproto/xrpc-server/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, "sha512-MUYNn5d2hv8yVegRL0ccHvTHAVj5JSnW07bkbiaz96UH45lvYNRVwt44z+yYVnb0/mvBzyD3/ZQ55TRGt7fHkA=="],
1326
+
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
1110
1327
1111
1328
"@ipld/dag-cbor/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1112
1329
1330
+
"@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
1331
+
1332
+
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1333
+
1334
+
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
1335
+
1336
+
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
1337
+
1338
+
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1339
+
1340
+
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
1341
+
1342
+
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
1343
+
1344
+
"@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1345
+
1346
+
"@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
1347
+
1348
+
"@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
1349
+
1350
+
"@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1351
+
1352
+
"@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1353
+
1354
+
"@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5BiR6i8yHc9+qW7F6LqkuUnIzVNA7lt0qRxIKcKT+gq3eGUPHZ3DY29sfxI3tkvnwMgtnHDMNze5DdxW39HsAw=="],
1355
+
1356
+
"@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
1357
+
1358
+
"@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
1359
+
1360
+
"@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1361
+
1362
+
"@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1363
+
1364
+
"@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@1.29.0", "", { "dependencies": { "@opentelemetry/core": "1.29.0", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-s7mLXuHZE7RQr1wwweGcaRp3Q4UJJ0wazeGlc/N5/XSe6UyXfsh1UQGMADYeg7YwD+cEdMtU1yJAUXdnFzYzyQ=="],
1365
+
1366
+
"@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@1.29.0", "", { "dependencies": { "@opentelemetry/core": "1.29.0", "@opentelemetry/resources": "1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-MkVtuzDjXZaUJSuJlHn6BSXjcQlMvHcsDV7LjY4P6AJeffMa4+kIGDjzsCf6DkAh6Vqlwag5EWEam3KZOX5Drw=="],
1367
+
1368
+
"@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1369
+
1370
+
"@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5BiR6i8yHc9+qW7F6LqkuUnIzVNA7lt0qRxIKcKT+gq3eGUPHZ3DY29sfxI3tkvnwMgtnHDMNze5DdxW39HsAw=="],
1371
+
1372
+
"@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
1373
+
1374
+
"@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
1375
+
1376
+
"@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1377
+
1378
+
"@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1379
+
1380
+
"@opentelemetry/exporter-prometheus/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1381
+
1382
+
"@opentelemetry/exporter-prometheus/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1383
+
1384
+
"@opentelemetry/exporter-prometheus/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1385
+
1386
+
"@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1387
+
1388
+
"@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
1389
+
1390
+
"@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
1391
+
1392
+
"@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1393
+
1394
+
"@opentelemetry/exporter-trace-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1395
+
1396
+
"@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
1397
+
1398
+
"@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
1399
+
1400
+
"@opentelemetry/exporter-trace-otlp-http/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1401
+
1402
+
"@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1403
+
1404
+
"@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
1405
+
1406
+
"@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
1407
+
1408
+
"@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1409
+
1410
+
"@opentelemetry/exporter-zipkin/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1411
+
1412
+
"@opentelemetry/exporter-zipkin/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1413
+
1414
+
"@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1415
+
1416
+
"@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
1417
+
1418
+
"@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
1419
+
1420
+
"@opentelemetry/otlp-transformer/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.56.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Wr39+94UNNG3Ei9nv3pHd4AJ63gq5nSemMRpCd8fPwDL9rN3vK26lzxfH27mw16XzOSO+TpyQwBAMaLxaPWG0g=="],
1421
+
1422
+
"@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@1.29.0", "", { "dependencies": { "@opentelemetry/core": "1.29.0", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-s7mLXuHZE7RQr1wwweGcaRp3Q4UJJ0wazeGlc/N5/XSe6UyXfsh1UQGMADYeg7YwD+cEdMtU1yJAUXdnFzYzyQ=="],
1423
+
1424
+
"@opentelemetry/otlp-transformer/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.56.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.56.0", "@opentelemetry/core": "1.29.0", "@opentelemetry/resources": "1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-OS0WPBJF++R/cSl+terUjQH5PebloidB1Jbbecgg2rnCmQbTST9xsRes23bLfDQVRvmegmHqDh884h0aRdJyLw=="],
1425
+
1426
+
"@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@1.29.0", "", { "dependencies": { "@opentelemetry/core": "1.29.0", "@opentelemetry/resources": "1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-MkVtuzDjXZaUJSuJlHn6BSXjcQlMvHcsDV7LjY4P6AJeffMa4+kIGDjzsCf6DkAh6Vqlwag5EWEam3KZOX5Drw=="],
1427
+
1428
+
"@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@1.29.0", "", { "dependencies": { "@opentelemetry/core": "1.29.0", "@opentelemetry/resources": "1.29.0", "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-hEOpAYLKXF3wGJpXOtWsxEtqBgde0SCv+w+jvr3/UusR4ll3QrENEGnSl1WDCyRrpqOQ5NCNOvZch9UFVa7MnQ=="],
1429
+
1430
+
"@opentelemetry/propagator-b3/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1431
+
1432
+
"@opentelemetry/propagator-jaeger/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1433
+
1434
+
"@opentelemetry/resources/@opentelemetry/core": ["@opentelemetry/core@1.30.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ=="],
1435
+
1436
+
"@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
1437
+
1438
+
"@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1439
+
1440
+
"@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1441
+
1442
+
"@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@1.30.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "1.28.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ=="],
1443
+
1444
+
"@opentelemetry/sdk-node/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1445
+
1446
+
"@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-exporter-base": "0.200.0", "@opentelemetry/otlp-transformer": "0.200.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-metrics": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5BiR6i8yHc9+qW7F6LqkuUnIzVNA7lt0qRxIKcKT+gq3eGUPHZ3DY29sfxI3tkvnwMgtnHDMNze5DdxW39HsAw=="],
1447
+
1448
+
"@opentelemetry/sdk-node/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1449
+
1450
+
"@opentelemetry/sdk-node/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1451
+
1452
+
"@opentelemetry/sdk-trace-base/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1453
+
1454
+
"@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1455
+
1456
+
"@opentelemetry/sdk-trace-node/@opentelemetry/core": ["@opentelemetry/core@2.0.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-SLX36allrcnVaPYG3R78F/UZZsBsvbc7lMCLx37LyH5MJ1KAAZ2E3mW9OAD3zGz0G8q/BtoS5VUrjzDydhD6LQ=="],
1457
+
1113
1458
"@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1114
1459
1115
1460
"@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
···
1124
1469
1125
1470
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
1126
1471
1127
-
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="],
1472
+
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.0", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA=="],
1128
1473
1129
1474
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
1130
1475
···
1132
1477
1133
1478
"@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
1134
1479
1135
-
"@wisp/main-app/@atproto/api": ["@atproto/api@0.17.7", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/lexicon": "^0.5.1", "@atproto/syntax": "^0.4.1", "@atproto/xrpc": "^0.7.5", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, "sha512-V+OJBZq9chcrD21xk1bUa6oc5DSKfQj5DmUPf5rmZncqL1w9ZEbS38H5cMyqqdhfgo2LWeDRdZHD0rvNyJsIaw=="],
1480
+
"@types/bun/bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
1136
1481
1137
-
"bun-types/@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
1482
+
"@wisp/main-app/@atproto/api": ["@atproto/api@0.17.7", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/lexicon": "^0.5.1", "@atproto/syntax": "^0.4.1", "@atproto/xrpc": "^0.7.5", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, "sha512-V+OJBZq9chcrD21xk1bUa6oc5DSKfQj5DmUPf5rmZncqL1w9ZEbS38H5cMyqqdhfgo2LWeDRdZHD0rvNyJsIaw=="],
1138
1483
1139
-
"express/cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
1484
+
"@wisp/observability/bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
1140
1485
1141
-
"fdir/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
1486
+
"express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
1142
1487
1143
1488
"iron-session/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
1144
1489
···
1146
1491
1147
1492
"node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
1148
1493
1149
-
"protobufjs/@types/node": ["@types/node@24.10.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A=="],
1494
+
"pino-abstract-transport/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
1150
1495
1151
1496
"require-in-the-middle/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
1152
1497
1153
-
"send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
1498
+
"send/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
1154
1499
1155
1500
"send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
1156
1501
1502
+
"send/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
1503
+
1504
+
"serve-static/send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="],
1505
+
1506
+
"tiered-storage/mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
1507
+
1157
1508
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
1158
1509
1159
-
"tsx/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
1510
+
"tsx/esbuild": ["esbuild@0.27.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.1", "@esbuild/android-arm": "0.27.1", "@esbuild/android-arm64": "0.27.1", "@esbuild/android-x64": "0.27.1", "@esbuild/darwin-arm64": "0.27.1", "@esbuild/darwin-x64": "0.27.1", "@esbuild/freebsd-arm64": "0.27.1", "@esbuild/freebsd-x64": "0.27.1", "@esbuild/linux-arm": "0.27.1", "@esbuild/linux-arm64": "0.27.1", "@esbuild/linux-ia32": "0.27.1", "@esbuild/linux-loong64": "0.27.1", "@esbuild/linux-mips64el": "0.27.1", "@esbuild/linux-ppc64": "0.27.1", "@esbuild/linux-riscv64": "0.27.1", "@esbuild/linux-s390x": "0.27.1", "@esbuild/linux-x64": "0.27.1", "@esbuild/netbsd-arm64": "0.27.1", "@esbuild/netbsd-x64": "0.27.1", "@esbuild/openbsd-arm64": "0.27.1", "@esbuild/openbsd-x64": "0.27.1", "@esbuild/openharmony-arm64": "0.27.1", "@esbuild/sunos-x64": "0.27.1", "@esbuild/win32-arm64": "0.27.1", "@esbuild/win32-ia32": "0.27.1", "@esbuild/win32-x64": "0.27.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA=="],
1160
1511
1161
1512
"tsx/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
1162
1513
···
1164
1515
1165
1516
"wisp-hosting-service/@atproto/api": ["@atproto/api@0.17.7", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/lexicon": "^0.5.1", "@atproto/syntax": "^0.4.1", "@atproto/xrpc": "^0.7.5", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, "sha512-V+OJBZq9chcrD21xk1bUa6oc5DSKfQj5DmUPf5rmZncqL1w9ZEbS38H5cMyqqdhfgo2LWeDRdZHD0rvNyJsIaw=="],
1166
1517
1167
-
"@atproto/lex-cli/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg=="],
1518
+
"@atproto/sync/@atproto/xrpc-server/@atproto/ws-client": ["@atproto/ws-client@0.0.3", "", { "dependencies": { "@atproto/common": "^0.5.0", "ws": "^8.12.0" } }, "sha512-eKqkTWBk6zuMY+6gs02eT7mS8Btewm8/qaL/Dp00NDCqpNC+U59MWvQsOWT3xkNGfd9Eip+V6VI4oyPvAfsfTA=="],
1168
1519
1169
-
"@atproto/lex-cli/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1520
+
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
1170
1521
1171
-
"@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A=="],
1522
+
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
1523
+
1524
+
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
1525
+
1526
+
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1527
+
1528
+
"@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1529
+
1530
+
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1531
+
1532
+
"@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1533
+
1534
+
"@opentelemetry/exporter-logs-otlp-proto/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1535
+
1536
+
"@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
1537
+
1538
+
"@opentelemetry/exporter-trace-otlp-grpc/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1539
+
1540
+
"@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1541
+
1542
+
"@opentelemetry/exporter-trace-otlp-proto/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1543
+
1544
+
"@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-rnZr6dML2z4IARI4zPGQV4arDikF/9OXZQzrC01dLmn0CZxU5U5OLd/m1T7YkGRj5UitjeoCtg/zorlgMQcdTg=="],
1172
1545
1173
-
"@atproto/ws-client/@atproto/common/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1546
+
"@opentelemetry/otlp-grpc-exporter-base/@opentelemetry/otlp-transformer/@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.0.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-Bvy8QDjO05umd0+j+gDeWcTaVa1/R2lDj/eOvjzpm8VQj1K1vVZJuyjThpV5/lSHyYW2JaHF2IQ7Z8twJFAhjA=="],
1174
1547
1175
-
"@atproto/xrpc-server/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg=="],
1548
+
"@opentelemetry/otlp-transformer/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
1176
1549
1177
-
"@atproto/xrpc-server/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1550
+
"@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
1178
1551
1179
-
"@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
1552
+
"@opentelemetry/sdk-metrics/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="],
1180
1553
1181
-
"@wisp/main-app/@atproto/api/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg=="],
1554
+
"@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.200.0", "", { "dependencies": { "@opentelemetry/core": "2.0.0", "@opentelemetry/otlp-transformer": "0.200.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IxJgA3FD7q4V6gGq4bnmQM5nTIyMDkoGFGrBrrDjB6onEiq1pafma55V+bHvGYLWvcqbBbRfezr1GED88lacEQ=="],
1182
1555
1183
-
"@wisp/main-app/@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A=="],
1556
+
"@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.200.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.200.0", "@opentelemetry/core": "2.0.0", "@opentelemetry/resources": "2.0.0", "@opentelemetry/sdk-logs": "0.200.0", "@opentelemetry/sdk-metrics": "2.0.0", "@opentelemetry/sdk-trace-base": "2.0.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-+9YDZbYybOnv7sWzebWOeK6gKyt2XE7iarSyBFkwwnP559pEevKOUD8NyDHhRjCSp13ybh9iVXlMfcj/DwF/yw=="],
1184
1557
1185
-
"@wisp/main-app/@atproto/api/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, "sha512-MUYNn5d2hv8yVegRL0ccHvTHAVj5JSnW07bkbiaz96UH45lvYNRVwt44z+yYVnb0/mvBzyD3/ZQ55TRGt7fHkA=="],
1558
+
"@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
1186
1559
1187
1560
"@wisp/main-app/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1188
1561
1189
-
"bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1190
-
1191
-
"protobufjs/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
1562
+
"pino-abstract-transport/readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
1192
1563
1193
1564
"require-in-the-middle/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
1194
1565
1195
-
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
1566
+
"serve-static/send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
1196
1567
1197
-
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
1568
+
"serve-static/send/http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
1198
1569
1199
-
"tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
1570
+
"serve-static/send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
1200
1571
1201
-
"tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
1572
+
"serve-static/send/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
1202
1573
1203
-
"tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
1574
+
"tiered-storage/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
1204
1575
1205
-
"tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
1576
+
"tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA=="],
1206
1577
1207
-
"tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
1578
+
"tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.1", "", { "os": "android", "cpu": "arm" }, "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg=="],
1208
1579
1209
-
"tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
1580
+
"tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.1", "", { "os": "android", "cpu": "arm64" }, "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ=="],
1581
+
1582
+
"tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.1", "", { "os": "android", "cpu": "x64" }, "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ=="],
1583
+
1584
+
"tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ=="],
1210
1585
1211
-
"tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
1586
+
"tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ=="],
1212
1587
1213
-
"tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
1588
+
"tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg=="],
1214
1589
1215
-
"tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
1590
+
"tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ=="],
1216
1591
1217
-
"tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
1592
+
"tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.1", "", { "os": "linux", "cpu": "arm" }, "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA=="],
1218
1593
1219
-
"tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
1594
+
"tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q=="],
1220
1595
1221
-
"tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
1596
+
"tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw=="],
1222
1597
1223
-
"tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
1598
+
"tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg=="],
1224
1599
1225
-
"tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
1600
+
"tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA=="],
1226
1601
1227
-
"tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
1602
+
"tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ=="],
1228
1603
1229
-
"tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
1604
+
"tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ=="],
1230
1605
1231
-
"tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
1606
+
"tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw=="],
1232
1607
1233
-
"tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
1608
+
"tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.1", "", { "os": "linux", "cpu": "x64" }, "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA=="],
1234
1609
1235
-
"tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
1610
+
"tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.1", "", { "os": "none", "cpu": "arm64" }, "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ=="],
1236
1611
1237
-
"tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
1612
+
"tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.1", "", { "os": "none", "cpu": "x64" }, "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg=="],
1238
1613
1239
-
"tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
1614
+
"tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g=="],
1240
1615
1241
-
"tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
1616
+
"tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg=="],
1242
1617
1243
-
"tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
1618
+
"tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg=="],
1244
1619
1245
-
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
1620
+
"tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA=="],
1246
1621
1247
-
"wisp-hosting-service/@atproto/api/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg=="],
1622
+
"tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg=="],
1248
1623
1249
-
"wisp-hosting-service/@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.5.1", "", { "dependencies": { "@atproto/common-web": "^0.4.3", "@atproto/syntax": "^0.4.1", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A=="],
1624
+
"tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ=="],
1250
1625
1251
-
"wisp-hosting-service/@atproto/api/@atproto/xrpc": ["@atproto/xrpc@0.7.5", "", { "dependencies": { "@atproto/lexicon": "^0.5.1", "zod": "^3.23.8" } }, "sha512-MUYNn5d2hv8yVegRL0ccHvTHAVj5JSnW07bkbiaz96UH45lvYNRVwt44z+yYVnb0/mvBzyD3/ZQ55TRGt7fHkA=="],
1626
+
"tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.1", "", { "os": "win32", "cpu": "x64" }, "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw=="],
1252
1627
1253
1628
"wisp-hosting-service/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="],
1254
1629
1255
-
"@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.3", "", { "dependencies": { "graphemer": "^1.4.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "zod": "^3.23.8" } }, "sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg=="],
1630
+
"@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
1631
+
1632
+
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
1633
+
1634
+
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
1256
1635
}
1257
1636
}
+116
-401
claude.md
+116
-401
claude.md
···
1
-
# Wisp.place - Codebase Overview
1
+
The project is wisp.place. It is a static site hoster built on top of the AT Protocol. The overall basis of the project is that users upload site assets to their PDS as blobs, and creates a manifest record listing every blob as well as site name. The hosting service then catches events relating to the site (create, read, upload, delete) and handles them appropriately.
2
2
3
-
**Project URL**: https://wisp.place
3
+
The lexicons look like this:
4
+
```typescript
5
+
//place.wisp.fs
6
+
interface Main {
7
+
$type: 'place.wisp.fs'
8
+
site: string
9
+
root: Directory
10
+
fileCount?: number
11
+
createdAt: string
12
+
}
4
13
5
-
A decentralized static site hosting service built on the AT Protocol (Bluesky). Users can host static websites directly in their AT Protocol accounts, keeping full control and ownership while benefiting from fast CDN distribution.
14
+
interface File {
15
+
$type?: 'place.wisp.fs#file'
16
+
type: 'file'
17
+
blob: BlobRef
18
+
encoding?: 'gzip'
19
+
mimeType?: string
20
+
base64?: boolean
21
+
}
6
22
7
-
---
23
+
interface Directory {
24
+
$type?: 'place.wisp.fs#directory'
25
+
type: 'directory'
26
+
entries: Entry[]
27
+
}
8
28
9
-
## ๐๏ธ Architecture Overview
29
+
interface Entry {
30
+
$type?: 'place.wisp.fs#entry'
31
+
name: string
32
+
node: $Typed<File> | $Typed<Directory> | $Typed<Subfs> | { $type: string }
33
+
}
10
34
11
-
### Multi-Part System
12
-
1. **Main Backend** (`/src`) - OAuth, site management, custom domains
13
-
2. **Hosting Service** (`/hosting-service`) - Microservice that serves cached sites
14
-
3. **CLI Tool** (`/cli`) - Rust CLI for direct site uploads to PDS
15
-
4. **Frontend** (`/public`) - React UI for onboarding, editor, admin
16
-
17
-
### Tech Stack
18
-
- **Backend**: Elysia (Bun) + TypeScript + PostgreSQL
19
-
- **Frontend**: React 19 + Tailwind CSS 4 + Radix UI
20
-
- **CLI**: Rust with Jacquard (AT Protocol library)
21
-
- **Database**: PostgreSQL for session/domain/site caching
22
-
- **AT Protocol**: OAuth 2.0 + custom lexicons for storage
35
+
interface Subfs {
36
+
$type?: 'place.wisp.fs#subfs'
37
+
type: 'subfs'
38
+
subject: string // AT-URI pointing to a place.wisp.subfs record
39
+
flat?: boolean
40
+
}
23
41
24
-
---
42
+
//place.wisp.subfs
43
+
interface Main {
44
+
$type: 'place.wisp.subfs'
45
+
root: Directory
46
+
fileCount?: number
47
+
createdAt: string
48
+
}
25
49
26
-
## ๐ Directory Structure
50
+
interface File {
51
+
$type?: 'place.wisp.subfs#file'
52
+
type: 'file'
53
+
blob: BlobRef
54
+
encoding?: 'gzip'
55
+
mimeType?: string
56
+
base64?: boolean
57
+
}
27
58
28
-
### `/src` - Main Backend Server
29
-
**Purpose**: Core server handling OAuth, site management, custom domains, admin features
59
+
interface Directory {
60
+
$type?: 'place.wisp.subfs#directory'
61
+
type: 'directory'
62
+
entries: Entry[]
63
+
}
30
64
31
-
**Key Routes**:
32
-
- `/api/auth/*` - OAuth signin/callback/logout/status
33
-
- `/api/domain/*` - Custom domain management (BYOD)
34
-
- `/wisp/*` - Site upload and management
35
-
- `/api/user/*` - User info and site listing
36
-
- `/api/admin/*` - Admin console (logs, metrics, DNS verification)
65
+
interface Entry {
66
+
$type?: 'place.wisp.subfs#entry'
67
+
name: string
68
+
node: $Typed<File> | $Typed<Directory> | $Typed<Subfs> | { $type: string }
69
+
}
37
70
38
-
**Key Files**:
39
-
- `index.ts` - Express-like Elysia app setup with middleware (CORS, CSP, security headers)
40
-
- `lib/oauth-client.ts` - OAuth client setup with session/state persistence
41
-
- `lib/db.ts` - PostgreSQL schema and queries for all tables
42
-
- `lib/wisp-auth.ts` - Cookie-based authentication middleware
43
-
- `lib/wisp-utils.ts` - File compression (gzip), manifest creation, blob handling
44
-
- `lib/sync-sites.ts` - Syncs user's place.wisp.fs records from PDS to database cache
45
-
- `lib/dns-verify.ts` - DNS verification for custom domains (TXT + CNAME)
46
-
- `lib/dns-verification-worker.ts` - Background worker that checks domain verification every 10 minutes
47
-
- `lib/admin-auth.ts` - Simple username/password admin authentication
48
-
- `lib/observability.ts` - Logging, error tracking, metrics collection
49
-
- `routes/auth.ts` - OAuth flow handlers
50
-
- `routes/wisp.ts` - File upload and site creation (/wisp/upload-files)
51
-
- `routes/domain.ts` - Domain claiming/verification API
52
-
- `routes/user.ts` - User status/info/sites listing
53
-
- `routes/site.ts` - Site metadata and file retrieval
54
-
- `routes/admin.ts` - Admin dashboard API (logs, system health, manual DNS trigger)
71
+
interface Subfs {
72
+
$type?: 'place.wisp.subfs#subfs'
73
+
type: 'subfs'
74
+
subject: string // AT-URI pointing to another place.wisp.subfs record
75
+
}
55
76
56
-
### `/lexicons` & `src/lexicons/`
57
-
**Purpose**: AT Protocol Lexicon definitions for custom data types
77
+
//place.wisp.settings
78
+
interface Main {
79
+
$type: 'place.wisp.settings'
80
+
directoryListing: boolean
81
+
spaMode?: string
82
+
custom404?: string
83
+
indexFiles?: string[]
84
+
cleanUrls: boolean
85
+
headers?: CustomHeader[]
86
+
}
58
87
59
-
**Key File**: `fs.json` - Defines `place.wisp.fs` record format
60
-
- **structure**: Virtual filesystem manifest with tree structure
61
-
- **site**: string identifier
62
-
- **root**: directory object containing entries
63
-
- **file**: blob reference + metadata (encoding, mimeType, base64 flag)
64
-
- **directory**: array of entries (recursive)
65
-
- **entry**: name + node (file or directory)
66
-
67
-
**Important**: Files are gzip-compressed and base64-encoded before upload to bypass PDS content sniffing
68
-
69
-
### `/hosting-service`
70
-
**Purpose**: Lightweight microservice that serves cached sites from disk
71
-
72
-
**Architecture**:
73
-
- Routes by domain lookup in PostgreSQL
74
-
- Caches site content locally on first access or firehose event
75
-
- Listens to AT Protocol firehose for new site records
76
-
- Automatically downloads and caches files from PDS
77
-
- SSRF-protected fetch (timeout, size limits, private IP blocking)
78
-
79
-
**Routes**:
80
-
1. Custom domains (`/*`) โ lookup custom_domains table
81
-
2. Wisp subdomains (`/*.wisp.place/*`) โ lookup domains table
82
-
3. DNS hash routing (`/hash.dns.wisp.place/*`) โ lookup custom_domains by hash
83
-
4. Direct serving (`/s.wisp.place/:identifier/:site/*`) โ fetch from PDS if not cached
84
-
85
-
**HTML Path Rewriting**: Absolute paths in HTML (`/style.css`) automatically rewritten to relative (`/:identifier/:site/style.css`)
86
-
87
-
### `/cli`
88
-
**Purpose**: Rust CLI tool for direct site uploads using app password or OAuth
89
-
90
-
**Flow**:
91
-
1. Authenticate with handle + app password or OAuth
92
-
2. Walk directory tree, compress files
93
-
3. Upload blobs to PDS via agent
94
-
4. Create place.wisp.fs record with manifest
95
-
5. Store site in database cache
96
-
97
-
**Auth Methods**:
98
-
- `--password` flag for app password auth
99
-
- OAuth loopback server for browser-based auth
100
-
- Supports both (password preferred if provided)
101
-
102
-
---
103
-
104
-
## ๐ Key Concepts
105
-
106
-
### Custom Domains (BYOD - Bring Your Own Domain)
107
-
**Process**:
108
-
1. User claims custom domain via API
109
-
2. System generates hash (SHA256(domain + secret))
110
-
3. User adds DNS records:
111
-
- TXT at `_wisp.example.com` = their DID
112
-
- CNAME at `example.com` = `{hash}.dns.wisp.place`
113
-
4. Background worker checks verification every 10 minutes
114
-
5. Once verified, custom domain routes to their hosted sites
115
-
116
-
**Tables**: `custom_domains` (id, domain, did, rkey, verified, last_verified_at)
117
-
118
-
### Wisp Subdomains
119
-
**Process**:
120
-
1. Handle claimed on first signup (e.g., alice โ alice.wisp.place)
121
-
2. Stored in `domains` table mapping domain โ DID
122
-
3. Served by hosting service
123
-
124
-
### Site Storage
125
-
**Locations**:
126
-
- **Authoritative**: PDS (AT Protocol repo) as `place.wisp.fs` record
127
-
- **Cache**: PostgreSQL `sites` table (rkey, did, site_name, created_at)
128
-
- **File Cache**: Hosting service caches downloaded files on disk
129
-
130
-
**Limits**:
131
-
- MAX_SITE_SIZE: 300MB total
132
-
- MAX_FILE_SIZE: 100MB per file
133
-
- MAX_FILE_COUNT: 2000 files
134
-
135
-
### File Compression Strategy
136
-
**Why**: Bypass PDS content sniffing issues (was treating HTML as images)
137
-
138
-
**Process**:
139
-
1. All files gzip-compressed (level 9)
140
-
2. Compressed content base64-encoded
141
-
3. Uploaded as `application/octet-stream` MIME type
142
-
4. Blob metadata stores original MIME type + encoding flag
143
-
5. Hosting service decompresses on serve
144
-
145
-
---
146
-
147
-
## ๐ Data Flow
148
-
149
-
### User Registration โ Site Upload
150
-
```
151
-
1. OAuth signin โ state/session stored in DB
152
-
2. Cookie set with DID
153
-
3. Sync sites from PDS to cache DB
154
-
4. If no sites/domain โ redirect to onboarding
155
-
5. User creates site โ POST /wisp/upload-files
156
-
6. Files compressed, uploaded as blobs
157
-
7. place.wisp.fs record created
158
-
8. Site cached in DB
159
-
9. Hosting service notified via firehose
88
+
interface CustomHeader {
89
+
$type?: 'place.wisp.settings#customHeader'
90
+
name: string
91
+
value: string
92
+
path?: string // Optional glob pattern
93
+
}
160
94
```
161
95
162
-
### Custom Domain Setup
163
-
```
164
-
1. User claims domain (DB check + allocation)
165
-
2. System generates hash
166
-
3. User adds DNS records (_wisp.domain TXT + CNAME)
167
-
4. Background worker verifies every 10 min
168
-
5. Hosting service routes based on verification status
169
-
```
96
+
The main differences between place.wisp.fs and place.wisp.subfs:
97
+
- place.wisp.fs has a required site field
98
+
- place.wisp.fs#subfs has an optional flat field that place.wisp.subfs#subfs doesn't have
170
99
171
-
### Site Access
172
-
```
173
-
Hosting Service:
174
-
1. Request arrives at custom domain or *.wisp.place
175
-
2. Domain lookup in PostgreSQL
176
-
3. Check cache for site files
177
-
4. If not cached:
178
-
- Fetch from PDS using DID + rkey
179
-
- Decompress files
180
-
- Save to disk cache
181
-
5. Serve files (with HTML path rewriting)
182
-
```
100
+
The project is a monorepo. The package handler it uses for the typescript side is Bun. For the Rust cli, it is cargo.
183
101
184
-
---
102
+
### Typescript Bun Workspace Layout
185
103
186
-
## ๐ ๏ธ Important Implementation Details
104
+
Bun workspaces: `packages/@wisp/*`, `apps/main-app`, `apps/hosting-service`
187
105
188
-
### OAuth Implementation
189
-
- **State & Session Storage**: PostgreSQL (with expiration)
190
-
- **Key Rotation**: Periodic rotation + expiration cleanup (hourly)
191
-
- **OAuth Flow**: Redirects to PDS, returns to /api/auth/callback
192
-
- **Session Timeout**: 30 days
193
-
- **State Timeout**: 1 hour
106
+
There are two typescript apps
107
+
**`apps/main-app`** - Main backend (Bun + Elysia)
194
108
195
-
### Security Headers
196
-
- X-Frame-Options: DENY
197
-
- X-Content-Type-Options: nosniff
198
-
- Strict-Transport-Security: max-age=31536000
199
-
- Content-Security-Policy (configured for Elysia + React)
200
-
- X-XSS-Protection: 1; mode=block
201
-
- Referrer-Policy: strict-origin-when-cross-origin
202
-
203
-
### Admin Authentication
204
-
- Simple username/password (hashed with bcrypt)
205
-
- Session-based cookie auth (24hr expiration)
206
-
- Separate `admin_session` cookie
207
-
- Initial setup prompted on startup
208
-
209
-
### Observability
210
-
- **Logging**: Structured logging with service tags + event types
211
-
- **Error Tracking**: Captures error context (message, stack, etc.)
212
-
- **Metrics**: Request counts, latencies, error rates
213
-
- **Log Levels**: debug, info, warn, error
214
-
- **Collection**: Centralized log collector with in-memory buffer
215
-
216
-
---
217
-
218
-
## ๐ Database Schema
219
-
220
-
### oauth_states
221
-
- key (primary key)
222
-
- data (JSON)
223
-
- created_at, expires_at (timestamps)
224
-
225
-
### oauth_sessions
226
-
- sub (primary key - subject/DID)
227
-
- data (JSON with OAuth session)
228
-
- updated_at, expires_at
229
-
230
-
### oauth_keys
231
-
- kid (primary key - key ID)
232
-
- jwk (JSON Web Key)
233
-
- created_at
234
-
235
-
### domains
236
-
- domain (primary key - e.g., alice.wisp.place)
237
-
- did (unique - user's DID)
238
-
- rkey (optional - record key)
239
-
- created_at
240
-
241
-
### custom_domains
242
-
- id (primary key - UUID)
243
-
- domain (unique - e.g., example.com)
244
-
- did (user's DID)
245
-
- rkey (optional)
246
-
- verified (boolean)
247
-
- last_verified_at (timestamp)
248
-
- created_at
249
-
250
-
### sites
251
-
- id, did, rkey, site_name
252
-
- created_at, updated_at
253
-
- Indexes on (did), (did, rkey), (rkey)
254
-
255
-
### admin_users
256
-
- username (primary key)
257
-
- password_hash (bcrypt)
258
-
- created_at
259
-
260
-
---
261
-
262
-
## ๐ Key Workflows
263
-
264
-
### Sign In Flow
265
-
1. POST /api/auth/signin with handle
266
-
2. System generates state token
267
-
3. Redirects to PDS OAuth endpoint
268
-
4. PDS redirects back to /api/auth/callback?code=X&state=Y
269
-
5. Validate state (CSRF protection)
270
-
6. Exchange code for session
271
-
7. Store session in DB, set DID cookie
272
-
8. Sync sites from PDS
273
-
9. Redirect to /editor or /onboarding
274
-
275
-
### File Upload Flow
276
-
1. POST /wisp/upload-files with siteName + files
277
-
2. Validate site name (rkey format rules)
278
-
3. For each file:
279
-
- Check size limits
280
-
- Read as ArrayBuffer
281
-
- Gzip compress
282
-
- Base64 encode
283
-
4. Upload all blobs in parallel via agent.com.atproto.repo.uploadBlob()
284
-
5. Create manifest with all blob refs
285
-
6. putRecord() for place.wisp.fs with manifest
286
-
7. Upsert to sites table
287
-
8. Return URI + CID
288
-
289
-
### Domain Verification Flow
290
-
1. POST /api/custom-domains/claim
291
-
2. Generate hash = SHA256(domain + secret)
292
-
3. Store in custom_domains with verified=false
293
-
4. Return hash for user to configure DNS
294
-
5. Background worker periodically:
295
-
- Query custom_domains where verified=false
296
-
- Verify TXT record at _wisp.domain
297
-
- Verify CNAME points to hash.dns.wisp.place
298
-
- Update verified flag + last_verified_at
299
-
6. Hosting service routes when verified=true
300
-
301
-
---
302
-
303
-
## ๐จ Frontend Structure
304
-
305
-
### `/public`
306
-
- **index.tsx** - Landing page with sign-in form
307
-
- **editor/editor.tsx** - Site editor/management UI
308
-
- **admin/admin.tsx** - Admin dashboard
309
-
- **components/ui/** - Reusable components (Button, Card, Dialog, etc.)
310
-
- **styles/global.css** - Tailwind + custom styles
311
-
312
-
### Page Flow
313
-
1. `/` - Landing page (sign in / get started)
314
-
2. `/editor` - Main app (requires auth)
315
-
3. `/admin` - Admin console (requires admin auth)
316
-
4. `/onboarding` - First-time user setup
317
-
318
-
---
109
+
- OAuth authentication and session management
110
+
- Site CRUD operations via PDS
111
+
- Custom domain management
112
+
- Admin database view in /admin
113
+
- React frontend in public/
319
114
320
-
## ๐ Notable Implementation Patterns
115
+
**`apps/hosting-service`** - CDN static file server (Node + Hono)
321
116
322
-
### File Handling
323
-
- Files stored as base64-encoded gzip in PDS blobs
324
-
- Metadata preserves original MIME type
325
-
- Hosting service decompresses on serve
326
-
- Workaround for PDS image pipeline issues with HTML
117
+
- Watches AT Protocol firehose for `place.wisp.fs` record changes
118
+
- Downloads and caches site files to disk
119
+
- Serves sites at `https://sites.wisp.place/{did}/{site-name}` and custom domains
120
+
- Handles redirects (`_redirects` file support) and routing logic
121
+
- Backfill mode for syncing existing sites
327
122
328
-
### Error Handling
329
-
- Comprehensive logging with context
330
-
- Graceful degradation (e.g., site sync failure doesn't break auth)
331
-
- Structured error responses with details
123
+
### Shared Packages (`packages/@wisp/*`)
332
124
333
-
### Performance
334
-
- Site sync: Batch fetch up to 100 records per request
335
-
- Blob upload: Parallel promises for all files
336
-
- DNS verification: Batched background worker (10 min intervals)
337
-
- Caching: Two-tier (DB + disk in hosting service)
125
+
- **`lexicons`** - AT Protocol lexicons (`place.wisp.fs`, `place.wisp.subfs`, `place.wisp.settings`) with
126
+
generated TypeScript types
127
+
- **`fs-utils`** - Filesystem tree building, manifest creation, subfs splitting logic
128
+
- **`atproto-utils`** - AT Protocol helpers (blob upload, record operations, CID handling)
129
+
- **`database`** - PostgreSQL schema and queries
130
+
- **`constants`** - Shared constants (limits, file patterns, default settings)
131
+
- **`observability`** - OpenTelemetry instrumentation
132
+
- **`safe-fetch`** - Wrapped fetch with timeout/retry logic
338
133
339
-
### Validation
340
-
- Lexicon validation on manifest creation
341
-
- Record type checking
342
-
- Domain format validation
343
-
- Site name format validation (AT Protocol rkey rules)
344
-
- File size limits enforced before upload
345
-
346
-
---
347
-
348
-
## ๐ Known Quirks & Workarounds
349
-
350
-
1. **PDS Content Sniffing**: Files must be uploaded as `application/octet-stream` (even HTML) and base64-encoded to prevent PDS from misinterpreting content
351
-
352
-
2. **Max URL Query Size**: DNS verification worker queries in batch; may need pagination for users with many custom domains
353
-
354
-
3. **File Count Limits**: Max 500 entries per directory (Lexicon constraint); large sites split across multiple directories
355
-
356
-
4. **Blob Size Limits**: Individual blobs limited to 100MB by Lexicon; large files handled differently if needed
357
-
358
-
5. **HTML Path Rewriting**: Only in hosting service for `/s.wisp.place/:identifier/:site/*` routes; custom domains handled differently
134
+
### CLI
359
135
360
-
---
136
+
**`cli/`** - Rust CLI using Jacquard (AT Protocol library)
137
+
- Direct PDS uploads without interacting with main-app
138
+
- Can also do the same firehose watching, caching, and serving hosting-service does, just without domain management
361
139
362
-
## ๐ Environment Variables
140
+
### Other Directories
363
141
364
-
- `DOMAIN` - Base domain with protocol (default: `https://wisp.place`)
365
-
- `CLIENT_NAME` - OAuth client name (default: `PDS-View`)
366
-
- `DATABASE_URL` - PostgreSQL connection (default: `postgres://postgres:postgres@localhost:5432/wisp`)
367
-
- `NODE_ENV` - production/development
368
-
- `HOSTING_PORT` - Hosting service port (default: 3001)
369
-
- `BASE_DOMAIN` - Domain for URLs (default: wisp.place)
370
-
371
-
---
372
-
373
-
## ๐งโ๐ป Development Notes
374
-
375
-
### Adding New Features
376
-
1. **New routes**: Add to `/src/routes/*.ts`, import in index.ts
377
-
2. **DB changes**: Add migration in db.ts
378
-
3. **New lexicons**: Update `/lexicons/*.json`, regenerate types
379
-
4. **Admin features**: Add to /api/admin endpoints
380
-
381
-
### Testing
382
-
- Run with `bun test`
383
-
- CSRF tests in lib/csrf.test.ts
384
-
- Utility tests in lib/wisp-utils.test.ts
385
-
386
-
### Debugging
387
-
- Check logs via `/api/admin/logs` (requires admin auth)
388
-
- DNS verification manual trigger: POST /api/admin/verify-dns
389
-
- Health check: GET /api/health (includes DNS verifier status)
390
-
391
-
---
392
-
393
-
## ๐ Deployment Considerations
394
-
395
-
1. **Secrets**: Admin password, OAuth keys, database credentials
396
-
2. **HTTPS**: Required (HSTS header enforces it)
397
-
3. **CDN**: Custom domains require DNS configuration
398
-
4. **Scaling**:
399
-
- Main server: Horizontal scaling with session DB
400
-
- Hosting service: Independent scaling, disk cache per instance
401
-
5. **Backups**: PostgreSQL database critical; firehose provides recovery
402
-
403
-
---
404
-
405
-
## ๐ Related Technologies
406
-
407
-
- **AT Protocol**: Decentralized identity, OAuth 2.0
408
-
- **Jacquard**: Rust library for AT Protocol interactions
409
-
- **Elysia**: Bun web framework (similar to Express/Hono)
410
-
- **Lexicon**: AT Protocol's schema definition language
411
-
- **Firehose**: Real-time event stream of repo changes
412
-
- **PDS**: Personal Data Server (where users' data stored)
413
-
414
-
---
415
-
416
-
## ๐ฏ Project Goals
417
-
418
-
โ
Decentralized site hosting (data owned by users)
419
-
โ
Custom domain support with DNS verification
420
-
โ
Fast CDN distribution via hosting service
421
-
โ
Developer tools (CLI + API)
422
-
โ
Admin dashboard for monitoring
423
-
โ
Zero user data retention (sites in PDS, sessions in DB only)
424
-
425
-
---
426
-
427
-
**Last Updated**: November 2025
428
-
**Status**: Active development
142
+
- **`docs/`** - Astro documentation site
143
+
- **`binaries/`** - Compiled CLI binaries for distribution
+417
-504
cli/Cargo.lock
+417
-504
cli/Cargo.lock
···
3
3
version = 4
4
4
5
5
[[package]]
6
-
name = "abnf"
7
-
version = "0.13.0"
8
-
source = "registry+https://github.com/rust-lang/crates.io-index"
9
-
checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a"
10
-
dependencies = [
11
-
"abnf-core",
12
-
"nom",
13
-
]
14
-
15
-
[[package]]
16
-
name = "abnf-core"
17
-
version = "0.5.0"
18
-
source = "registry+https://github.com/rust-lang/crates.io-index"
19
-
checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d"
20
-
dependencies = [
21
-
"nom",
22
-
]
23
-
24
-
[[package]]
25
6
name = "addr2line"
26
7
version = "0.25.1"
27
8
source = "registry+https://github.com/rust-lang/crates.io-index"
···
71
52
dependencies = [
72
53
"alloc-no-stdlib",
73
54
]
55
+
56
+
[[package]]
57
+
name = "allocator-api2"
58
+
version = "0.2.21"
59
+
source = "registry+https://github.com/rust-lang/crates.io-index"
60
+
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
74
61
75
62
[[package]]
76
63
name = "android_system_properties"
···
139
126
140
127
[[package]]
141
128
name = "async-compression"
142
-
version = "0.4.34"
129
+
version = "0.4.36"
143
130
source = "registry+https://github.com/rust-lang/crates.io-index"
144
-
checksum = "0e86f6d3dc9dc4352edeea6b8e499e13e3f5dc3b964d7ca5fd411415a3498473"
131
+
checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37"
145
132
dependencies = [
146
133
"compression-codecs",
147
134
"compression-core",
···
158
145
dependencies = [
159
146
"proc-macro2",
160
147
"quote",
161
-
"syn 2.0.111",
148
+
"syn 2.0.113",
149
+
]
150
+
151
+
[[package]]
152
+
name = "atomic-polyfill"
153
+
version = "1.0.3"
154
+
source = "registry+https://github.com/rust-lang/crates.io-index"
155
+
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
156
+
dependencies = [
157
+
"critical-section",
162
158
]
163
159
164
160
[[package]]
···
175
171
176
172
[[package]]
177
173
name = "axum"
178
-
version = "0.8.7"
174
+
version = "0.8.8"
179
175
source = "registry+https://github.com/rust-lang/crates.io-index"
180
-
checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425"
176
+
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
181
177
dependencies = [
182
178
"axum-core",
183
179
"bytes",
···
208
204
209
205
[[package]]
210
206
name = "axum-core"
211
-
version = "0.5.5"
207
+
version = "0.5.6"
212
208
source = "registry+https://github.com/rust-lang/crates.io-index"
213
-
checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22"
209
+
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
214
210
dependencies = [
215
211
"bytes",
216
212
"futures-core",
···
237
233
"miniz_oxide",
238
234
"object",
239
235
"rustc-demangle",
240
-
"windows-link 0.2.1",
236
+
"windows-link",
241
237
]
242
238
243
239
[[package]]
···
285
281
286
282
[[package]]
287
283
name = "base64ct"
288
-
version = "1.8.0"
284
+
version = "1.8.2"
289
285
source = "registry+https://github.com/rust-lang/crates.io-index"
290
-
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
286
+
checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82"
291
287
292
288
[[package]]
293
289
name = "bitflags"
···
326
322
"proc-macro2",
327
323
"quote",
328
324
"rustversion",
329
-
"syn 2.0.111",
325
+
"syn 2.0.113",
330
326
]
331
327
332
328
[[package]]
333
329
name = "borsh"
334
-
version = "1.5.7"
330
+
version = "1.6.0"
335
331
source = "registry+https://github.com/rust-lang/crates.io-index"
336
-
checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce"
332
+
checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f"
337
333
dependencies = [
338
334
"cfg_aliases",
339
335
]
···
370
366
]
371
367
372
368
[[package]]
373
-
name = "btree-range-map"
374
-
version = "0.7.2"
375
-
source = "registry+https://github.com/rust-lang/crates.io-index"
376
-
checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33"
377
-
dependencies = [
378
-
"btree-slab",
379
-
"cc-traits",
380
-
"range-traits",
381
-
"serde",
382
-
"slab",
383
-
]
384
-
385
-
[[package]]
386
-
name = "btree-slab"
387
-
version = "0.6.1"
388
-
source = "registry+https://github.com/rust-lang/crates.io-index"
389
-
checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c"
390
-
dependencies = [
391
-
"cc-traits",
392
-
"slab",
393
-
"smallvec",
394
-
]
395
-
396
-
[[package]]
397
369
name = "buf_redux"
398
370
version = "0.8.4"
399
371
source = "registry+https://github.com/rust-lang/crates.io-index"
···
405
377
406
378
[[package]]
407
379
name = "bumpalo"
408
-
version = "3.19.0"
380
+
version = "3.19.1"
409
381
source = "registry+https://github.com/rust-lang/crates.io-index"
410
-
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
382
+
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
411
383
412
384
[[package]]
413
385
name = "byteorder"
···
435
407
436
408
[[package]]
437
409
name = "cc"
438
-
version = "1.2.47"
410
+
version = "1.2.51"
439
411
source = "registry+https://github.com/rust-lang/crates.io-index"
440
-
checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07"
412
+
checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203"
441
413
dependencies = [
442
414
"find-msvc-tools",
443
415
"shlex",
444
416
]
445
417
446
418
[[package]]
447
-
name = "cc-traits"
448
-
version = "2.0.0"
449
-
source = "registry+https://github.com/rust-lang/crates.io-index"
450
-
checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5"
451
-
dependencies = [
452
-
"slab",
453
-
]
454
-
455
-
[[package]]
456
419
name = "cesu8"
457
420
version = "1.1.0"
458
421
source = "registry+https://github.com/rust-lang/crates.io-index"
···
481
444
"num-traits",
482
445
"serde",
483
446
"wasm-bindgen",
484
-
"windows-link 0.2.1",
447
+
"windows-link",
485
448
]
486
449
487
450
[[package]]
···
533
496
534
497
[[package]]
535
498
name = "clap"
536
-
version = "4.5.53"
499
+
version = "4.5.54"
537
500
source = "registry+https://github.com/rust-lang/crates.io-index"
538
-
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
501
+
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
539
502
dependencies = [
540
503
"clap_builder",
541
504
"clap_derive",
···
543
506
544
507
[[package]]
545
508
name = "clap_builder"
546
-
version = "4.5.53"
509
+
version = "4.5.54"
547
510
source = "registry+https://github.com/rust-lang/crates.io-index"
548
-
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
511
+
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
549
512
dependencies = [
550
513
"anstream",
551
514
"anstyle",
···
562
525
"heck 0.5.0",
563
526
"proc-macro2",
564
527
"quote",
565
-
"syn 2.0.111",
528
+
"syn 2.0.113",
566
529
]
567
530
568
531
[[package]]
···
572
535
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
573
536
574
537
[[package]]
538
+
name = "cobs"
539
+
version = "0.3.0"
540
+
source = "registry+https://github.com/rust-lang/crates.io-index"
541
+
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
542
+
dependencies = [
543
+
"thiserror 2.0.17",
544
+
]
545
+
546
+
[[package]]
575
547
name = "colorchoice"
576
548
version = "1.0.4"
577
549
source = "registry+https://github.com/rust-lang/crates.io-index"
···
589
561
590
562
[[package]]
591
563
name = "compression-codecs"
592
-
version = "0.4.33"
564
+
version = "0.4.35"
593
565
source = "registry+https://github.com/rust-lang/crates.io-index"
594
-
checksum = "302266479cb963552d11bd042013a58ef1adc56768016c8b82b4199488f2d4ad"
566
+
checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2"
595
567
dependencies = [
596
568
"compression-core",
597
569
"flate2",
···
603
575
version = "0.4.31"
604
576
source = "registry+https://github.com/rust-lang/crates.io-index"
605
577
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
578
+
579
+
[[package]]
580
+
name = "console"
581
+
version = "0.15.11"
582
+
source = "registry+https://github.com/rust-lang/crates.io-index"
583
+
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
584
+
dependencies = [
585
+
"encode_unicode",
586
+
"libc",
587
+
"once_cell",
588
+
"unicode-width 0.2.2",
589
+
"windows-sys 0.59.0",
590
+
]
606
591
607
592
[[package]]
608
593
name = "const-oid"
···
678
663
dependencies = [
679
664
"cfg-if",
680
665
]
666
+
667
+
[[package]]
668
+
name = "critical-section"
669
+
version = "1.2.0"
670
+
source = "registry+https://github.com/rust-lang/crates.io-index"
671
+
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
681
672
682
673
[[package]]
683
674
name = "crossbeam-channel"
···
762
753
"proc-macro2",
763
754
"quote",
764
755
"strsim",
765
-
"syn 2.0.111",
756
+
"syn 2.0.113",
766
757
]
767
758
768
759
[[package]]
···
773
764
dependencies = [
774
765
"darling_core",
775
766
"quote",
776
-
"syn 2.0.111",
767
+
"syn 2.0.113",
777
768
]
778
769
779
770
[[package]]
···
813
804
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
814
805
dependencies = [
815
806
"data-encoding",
816
-
"syn 2.0.111",
807
+
"syn 2.0.113",
817
808
]
818
809
819
810
[[package]]
···
844
835
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
845
836
dependencies = [
846
837
"powerfmt",
847
-
"serde_core",
848
838
]
849
839
850
840
[[package]]
···
864
854
dependencies = [
865
855
"proc-macro2",
866
856
"quote",
867
-
"syn 2.0.111",
857
+
"syn 2.0.113",
868
858
"unicode-xid",
869
859
]
870
860
···
915
905
dependencies = [
916
906
"proc-macro2",
917
907
"quote",
918
-
"syn 2.0.111",
908
+
"syn 2.0.113",
919
909
]
920
910
921
911
[[package]]
922
-
name = "dyn-clone"
923
-
version = "1.0.20"
924
-
source = "registry+https://github.com/rust-lang/crates.io-index"
925
-
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
926
-
927
-
[[package]]
928
912
name = "ecdsa"
929
913
version = "0.16.9"
930
914
source = "registry+https://github.com/rust-lang/crates.io-index"
···
959
943
]
960
944
961
945
[[package]]
946
+
name = "embedded-io"
947
+
version = "0.4.0"
948
+
source = "registry+https://github.com/rust-lang/crates.io-index"
949
+
checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
950
+
951
+
[[package]]
952
+
name = "embedded-io"
953
+
version = "0.6.1"
954
+
source = "registry+https://github.com/rust-lang/crates.io-index"
955
+
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
956
+
957
+
[[package]]
958
+
name = "encode_unicode"
959
+
version = "1.0.0"
960
+
source = "registry+https://github.com/rust-lang/crates.io-index"
961
+
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
962
+
963
+
[[package]]
962
964
name = "encoding_rs"
963
965
version = "0.8.35"
964
966
source = "registry+https://github.com/rust-lang/crates.io-index"
···
976
978
"heck 0.5.0",
977
979
"proc-macro2",
978
980
"quote",
979
-
"syn 2.0.111",
981
+
"syn 2.0.113",
980
982
]
981
983
982
984
[[package]]
···
1025
1027
1026
1028
[[package]]
1027
1029
name = "find-msvc-tools"
1028
-
version = "0.1.5"
1030
+
version = "0.1.6"
1029
1031
source = "registry+https://github.com/rust-lang/crates.io-index"
1030
-
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
1032
+
checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff"
1031
1033
1032
1034
[[package]]
1033
1035
name = "flate2"
···
1046
1048
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
1047
1049
1048
1050
[[package]]
1051
+
name = "foldhash"
1052
+
version = "0.1.5"
1053
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1054
+
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
1055
+
1056
+
[[package]]
1049
1057
name = "form_urlencoded"
1050
1058
version = "1.2.2"
1051
1059
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1146
1154
dependencies = [
1147
1155
"proc-macro2",
1148
1156
"quote",
1149
-
"syn 2.0.111",
1157
+
"syn 2.0.113",
1150
1158
]
1151
1159
1152
1160
[[package]]
···
1181
1189
1182
1190
[[package]]
1183
1191
name = "generator"
1184
-
version = "0.8.7"
1192
+
version = "0.8.8"
1185
1193
source = "registry+https://github.com/rust-lang/crates.io-index"
1186
-
checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2"
1194
+
checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9"
1187
1195
dependencies = [
1188
1196
"cc",
1189
1197
"cfg-if",
1190
1198
"libc",
1191
1199
"log",
1192
1200
"rustversion",
1193
-
"windows",
1201
+
"windows-link",
1202
+
"windows-result",
1194
1203
]
1195
1204
1196
1205
[[package]]
···
1300
1309
1301
1310
[[package]]
1302
1311
name = "h2"
1303
-
version = "0.4.12"
1312
+
version = "0.4.13"
1304
1313
source = "registry+https://github.com/rust-lang/crates.io-index"
1305
-
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
1314
+
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
1306
1315
dependencies = [
1307
1316
"atomic-waker",
1308
1317
"bytes",
···
1310
1319
"futures-core",
1311
1320
"futures-sink",
1312
1321
"http",
1313
-
"indexmap 2.12.1",
1322
+
"indexmap",
1314
1323
"slab",
1315
1324
"tokio",
1316
1325
"tokio-util",
···
1329
1338
]
1330
1339
1331
1340
[[package]]
1332
-
name = "hashbrown"
1333
-
version = "0.12.3"
1341
+
name = "hash32"
1342
+
version = "0.2.1"
1334
1343
source = "registry+https://github.com/rust-lang/crates.io-index"
1335
-
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1344
+
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
1345
+
dependencies = [
1346
+
"byteorder",
1347
+
]
1336
1348
1337
1349
[[package]]
1338
1350
name = "hashbrown"
···
1342
1354
1343
1355
[[package]]
1344
1356
name = "hashbrown"
1357
+
version = "0.15.5"
1358
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1359
+
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
1360
+
dependencies = [
1361
+
"allocator-api2",
1362
+
"equivalent",
1363
+
"foldhash",
1364
+
]
1365
+
1366
+
[[package]]
1367
+
name = "hashbrown"
1345
1368
version = "0.16.1"
1346
1369
source = "registry+https://github.com/rust-lang/crates.io-index"
1347
1370
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
1348
1371
1349
1372
[[package]]
1373
+
name = "heapless"
1374
+
version = "0.7.17"
1375
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1376
+
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
1377
+
dependencies = [
1378
+
"atomic-polyfill",
1379
+
"hash32",
1380
+
"rustc_version",
1381
+
"serde",
1382
+
"spin 0.9.8",
1383
+
"stable_deref_trait",
1384
+
]
1385
+
1386
+
[[package]]
1350
1387
name = "heck"
1351
1388
version = "0.4.1"
1352
1389
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1371
1408
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1372
1409
1373
1410
[[package]]
1374
-
name = "hex_fmt"
1375
-
version = "0.3.0"
1376
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1377
-
checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f"
1378
-
1379
-
[[package]]
1380
1411
name = "hickory-proto"
1381
1412
version = "0.24.4"
1382
1413
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1441
1472
"markup5ever",
1442
1473
"proc-macro2",
1443
1474
"quote",
1444
-
"syn 2.0.111",
1475
+
"syn 2.0.113",
1445
1476
]
1446
1477
1447
1478
[[package]]
···
1537
1568
1538
1569
[[package]]
1539
1570
name = "hyper-util"
1540
-
version = "0.1.18"
1571
+
version = "0.1.19"
1541
1572
source = "registry+https://github.com/rust-lang/crates.io-index"
1542
-
checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
1573
+
checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f"
1543
1574
dependencies = [
1544
1575
"base64 0.22.1",
1545
1576
"bytes",
···
1573
1604
"js-sys",
1574
1605
"log",
1575
1606
"wasm-bindgen",
1576
-
"windows-core 0.62.2",
1607
+
"windows-core",
1577
1608
]
1578
1609
1579
1610
[[package]]
···
1633
1664
1634
1665
[[package]]
1635
1666
name = "icu_properties"
1636
-
version = "2.1.1"
1667
+
version = "2.1.2"
1637
1668
source = "registry+https://github.com/rust-lang/crates.io-index"
1638
-
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
1669
+
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
1639
1670
dependencies = [
1640
1671
"icu_collections",
1641
1672
"icu_locale_core",
···
1647
1678
1648
1679
[[package]]
1649
1680
name = "icu_properties_data"
1650
-
version = "2.1.1"
1681
+
version = "2.1.2"
1651
1682
source = "registry+https://github.com/rust-lang/crates.io-index"
1652
-
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
1683
+
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
1653
1684
1654
1685
[[package]]
1655
1686
name = "icu_provider"
···
1711
1742
1712
1743
[[package]]
1713
1744
name = "indexmap"
1714
-
version = "1.9.3"
1715
-
source = "registry+https://github.com/rust-lang/crates.io-index"
1716
-
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
1717
-
dependencies = [
1718
-
"autocfg",
1719
-
"hashbrown 0.12.3",
1720
-
"serde",
1721
-
]
1722
-
1723
-
[[package]]
1724
-
name = "indexmap"
1725
1745
version = "2.12.1"
1726
1746
source = "registry+https://github.com/rust-lang/crates.io-index"
1727
1747
checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2"
1728
1748
dependencies = [
1729
1749
"equivalent",
1730
1750
"hashbrown 0.16.1",
1731
-
"serde",
1732
-
"serde_core",
1733
1751
]
1734
1752
1735
1753
[[package]]
1736
-
name = "indoc"
1737
-
version = "2.0.7"
1754
+
name = "indicatif"
1755
+
version = "0.17.11"
1738
1756
source = "registry+https://github.com/rust-lang/crates.io-index"
1739
-
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
1757
+
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
1740
1758
dependencies = [
1741
-
"rustversion",
1759
+
"console",
1760
+
"number_prefix",
1761
+
"portable-atomic",
1762
+
"unicode-width 0.2.2",
1763
+
"web-time",
1742
1764
]
1743
1765
1744
1766
[[package]]
···
1781
1803
1782
1804
[[package]]
1783
1805
name = "iri-string"
1784
-
version = "0.7.9"
1806
+
version = "0.7.10"
1785
1807
source = "registry+https://github.com/rust-lang/crates.io-index"
1786
-
checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397"
1808
+
checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a"
1787
1809
dependencies = [
1788
1810
"memchr",
1789
1811
"serde",
···
1803
1825
1804
1826
[[package]]
1805
1827
name = "itoa"
1806
-
version = "1.0.15"
1828
+
version = "1.0.17"
1807
1829
source = "registry+https://github.com/rust-lang/crates.io-index"
1808
-
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
1830
+
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
1809
1831
1810
1832
[[package]]
1811
1833
name = "jacquard"
1812
-
version = "0.9.3"
1813
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1834
+
version = "0.9.5"
1835
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1814
1836
dependencies = [
1815
1837
"bytes",
1816
1838
"getrandom 0.2.16",
···
1839
1861
1840
1862
[[package]]
1841
1863
name = "jacquard-api"
1842
-
version = "0.9.2"
1843
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1864
+
version = "0.9.5"
1865
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1844
1866
dependencies = [
1845
1867
"bon",
1846
1868
"bytes",
···
1850
1872
"miette",
1851
1873
"rustversion",
1852
1874
"serde",
1875
+
"serde_bytes",
1853
1876
"serde_ipld_dagcbor",
1854
1877
"thiserror 2.0.17",
1855
1878
"unicode-segmentation",
···
1857
1880
1858
1881
[[package]]
1859
1882
name = "jacquard-common"
1860
-
version = "0.9.2"
1861
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1883
+
version = "0.9.5"
1884
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1862
1885
dependencies = [
1863
1886
"base64 0.22.1",
1864
1887
"bon",
1865
1888
"bytes",
1866
1889
"chrono",
1867
1890
"ciborium",
1891
+
"ciborium-io",
1868
1892
"cid",
1869
1893
"futures",
1870
1894
"getrandom 0.2.16",
1871
1895
"getrandom 0.3.4",
1896
+
"hashbrown 0.15.5",
1872
1897
"http",
1873
1898
"ipld-core",
1874
1899
"k256",
1875
-
"langtag",
1900
+
"maitake-sync",
1876
1901
"miette",
1877
1902
"multibase",
1878
1903
"multihash",
1879
1904
"n0-future 0.1.3",
1880
1905
"ouroboros",
1906
+
"oxilangtag",
1881
1907
"p256",
1908
+
"postcard",
1882
1909
"rand 0.9.2",
1883
1910
"regex",
1911
+
"regex-automata",
1884
1912
"regex-lite",
1885
1913
"reqwest",
1886
1914
"serde",
1915
+
"serde_bytes",
1887
1916
"serde_html_form",
1888
1917
"serde_ipld_dagcbor",
1889
1918
"serde_json",
1890
1919
"signature",
1891
1920
"smol_str",
1921
+
"spin 0.10.0",
1892
1922
"thiserror 2.0.17",
1893
1923
"tokio",
1894
1924
"tokio-tungstenite-wasm",
···
1899
1929
1900
1930
[[package]]
1901
1931
name = "jacquard-derive"
1902
-
version = "0.9.3"
1903
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1932
+
version = "0.9.5"
1933
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1904
1934
dependencies = [
1905
1935
"heck 0.5.0",
1906
1936
"jacquard-lexicon",
1907
1937
"proc-macro2",
1908
1938
"quote",
1909
-
"syn 2.0.111",
1939
+
"syn 2.0.113",
1910
1940
]
1911
1941
1912
1942
[[package]]
1913
1943
name = "jacquard-identity"
1914
-
version = "0.9.2"
1915
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1944
+
version = "0.9.5"
1945
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1916
1946
dependencies = [
1917
1947
"bon",
1918
1948
"bytes",
···
1922
1952
"jacquard-common",
1923
1953
"jacquard-lexicon",
1924
1954
"miette",
1925
-
"mini-moka",
1955
+
"mini-moka-wasm",
1956
+
"n0-future 0.1.3",
1926
1957
"percent-encoding",
1927
1958
"reqwest",
1928
1959
"serde",
···
1937
1968
1938
1969
[[package]]
1939
1970
name = "jacquard-lexicon"
1940
-
version = "0.9.2"
1941
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1971
+
version = "0.9.5"
1972
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1942
1973
dependencies = [
1943
1974
"cid",
1944
1975
"dashmap",
···
1956
1987
"serde_repr",
1957
1988
"serde_with",
1958
1989
"sha2",
1959
-
"syn 2.0.111",
1990
+
"syn 2.0.113",
1960
1991
"thiserror 2.0.17",
1961
1992
"unicode-segmentation",
1962
1993
]
1963
1994
1964
1995
[[package]]
1965
1996
name = "jacquard-oauth"
1966
-
version = "0.9.2"
1967
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
1997
+
version = "0.9.6"
1998
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
1968
1999
dependencies = [
1969
2000
"base64 0.22.1",
1970
2001
"bytes",
···
2052
2083
2053
2084
[[package]]
2054
2085
name = "js-sys"
2055
-
version = "0.3.82"
2086
+
version = "0.3.83"
2056
2087
source = "registry+https://github.com/rust-lang/crates.io-index"
2057
-
checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65"
2088
+
checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8"
2058
2089
dependencies = [
2059
2090
"once_cell",
2060
2091
"wasm-bindgen",
···
2073
2104
]
2074
2105
2075
2106
[[package]]
2076
-
name = "langtag"
2077
-
version = "0.4.0"
2078
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2079
-
checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600"
2080
-
dependencies = [
2081
-
"serde",
2082
-
"static-regular-grammar",
2083
-
"thiserror 1.0.69",
2084
-
]
2085
-
2086
-
[[package]]
2087
2107
name = "lazy_static"
2088
2108
version = "1.5.0"
2089
2109
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2094
2114
2095
2115
[[package]]
2096
2116
name = "libc"
2097
-
version = "0.2.177"
2117
+
version = "0.2.179"
2098
2118
source = "registry+https://github.com/rust-lang/crates.io-index"
2099
-
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
2119
+
checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f"
2100
2120
2101
2121
[[package]]
2102
2122
name = "libm"
···
2106
2126
2107
2127
[[package]]
2108
2128
name = "libredox"
2109
-
version = "0.1.10"
2129
+
version = "0.1.12"
2110
2130
source = "registry+https://github.com/rust-lang/crates.io-index"
2111
-
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
2131
+
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
2112
2132
dependencies = [
2113
2133
"bitflags",
2114
2134
"libc",
2115
-
"redox_syscall",
2135
+
"redox_syscall 0.7.0",
2116
2136
]
2117
2137
2118
2138
[[package]]
···
2144
2164
2145
2165
[[package]]
2146
2166
name = "log"
2147
-
version = "0.4.28"
2167
+
version = "0.4.29"
2148
2168
source = "registry+https://github.com/rust-lang/crates.io-index"
2149
-
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
2169
+
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
2150
2170
2151
2171
[[package]]
2152
2172
name = "loom"
···
2183
2203
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
2184
2204
2185
2205
[[package]]
2206
+
name = "maitake-sync"
2207
+
version = "0.1.2"
2208
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2209
+
checksum = "6816ab14147f80234c675b80ed6dc4f440d8a1cefc158e766067aedb84c0bcd5"
2210
+
dependencies = [
2211
+
"cordyceps",
2212
+
"loom",
2213
+
"mycelium-bitfield",
2214
+
"pin-project",
2215
+
"portable-atomic",
2216
+
]
2217
+
2218
+
[[package]]
2186
2219
name = "markup5ever"
2187
2220
version = "0.12.1"
2188
2221
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2267
2300
dependencies = [
2268
2301
"proc-macro2",
2269
2302
"quote",
2270
-
"syn 2.0.111",
2303
+
"syn 2.0.113",
2271
2304
]
2272
2305
2273
2306
[[package]]
···
2287
2320
]
2288
2321
2289
2322
[[package]]
2290
-
name = "mini-moka"
2323
+
name = "mini-moka-wasm"
2291
2324
version = "0.10.99"
2292
-
source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e"
2325
+
source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b"
2293
2326
dependencies = [
2294
2327
"crossbeam-channel",
2295
2328
"crossbeam-utils",
···
2301
2334
]
2302
2335
2303
2336
[[package]]
2304
-
name = "minimal-lexical"
2305
-
version = "0.2.1"
2306
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2307
-
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
2308
-
2309
-
[[package]]
2310
2337
name = "miniz_oxide"
2311
2338
version = "0.8.9"
2312
2339
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2318
2345
2319
2346
[[package]]
2320
2347
name = "mio"
2321
-
version = "1.1.0"
2348
+
version = "1.1.1"
2322
2349
source = "registry+https://github.com/rust-lang/crates.io-index"
2323
-
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
2350
+
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
2324
2351
dependencies = [
2325
2352
"libc",
2326
2353
"wasi",
···
2369
2396
]
2370
2397
2371
2398
[[package]]
2399
+
name = "mycelium-bitfield"
2400
+
version = "0.1.5"
2401
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2402
+
checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc"
2403
+
2404
+
[[package]]
2372
2405
name = "n0-future"
2373
2406
version = "0.1.3"
2374
2407
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2421
2454
version = "1.0.6"
2422
2455
source = "registry+https://github.com/rust-lang/crates.io-index"
2423
2456
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
2424
-
2425
-
[[package]]
2426
-
name = "nom"
2427
-
version = "7.1.3"
2428
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2429
-
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
2430
-
dependencies = [
2431
-
"memchr",
2432
-
"minimal-lexical",
2433
-
]
2434
2457
2435
2458
[[package]]
2436
2459
name = "nu-ansi-term"
···
2513
2536
]
2514
2537
2515
2538
[[package]]
2539
+
name = "number_prefix"
2540
+
version = "0.4.0"
2541
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2542
+
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
2543
+
2544
+
[[package]]
2516
2545
name = "objc2"
2517
2546
version = "0.6.3"
2518
2547
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2560
2589
2561
2590
[[package]]
2562
2591
name = "openssl-probe"
2563
-
version = "0.1.6"
2592
+
version = "0.2.0"
2564
2593
source = "registry+https://github.com/rust-lang/crates.io-index"
2565
-
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
2594
+
checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391"
2566
2595
2567
2596
[[package]]
2568
2597
name = "option-ext"
···
2591
2620
"proc-macro2",
2592
2621
"proc-macro2-diagnostics",
2593
2622
"quote",
2594
-
"syn 2.0.111",
2623
+
"syn 2.0.113",
2595
2624
]
2596
2625
2597
2626
[[package]]
···
2601
2630
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
2602
2631
2603
2632
[[package]]
2633
+
name = "oxilangtag"
2634
+
version = "0.1.5"
2635
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2636
+
checksum = "23f3f87617a86af77fa3691e6350483e7154c2ead9f1261b75130e21ca0f8acb"
2637
+
dependencies = [
2638
+
"serde",
2639
+
]
2640
+
2641
+
[[package]]
2604
2642
name = "p256"
2605
2643
version = "0.13.2"
2606
2644
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2646
2684
dependencies = [
2647
2685
"cfg-if",
2648
2686
"libc",
2649
-
"redox_syscall",
2687
+
"redox_syscall 0.5.18",
2650
2688
"smallvec",
2651
-
"windows-link 0.2.1",
2689
+
"windows-link",
2652
2690
]
2653
2691
2654
2692
[[package]]
···
2721
2759
dependencies = [
2722
2760
"proc-macro2",
2723
2761
"quote",
2724
-
"syn 2.0.111",
2762
+
"syn 2.0.113",
2725
2763
]
2726
2764
2727
2765
[[package]]
···
2755
2793
dependencies = [
2756
2794
"der",
2757
2795
"spki",
2796
+
]
2797
+
2798
+
[[package]]
2799
+
name = "portable-atomic"
2800
+
version = "1.13.0"
2801
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2802
+
checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950"
2803
+
2804
+
[[package]]
2805
+
name = "postcard"
2806
+
version = "1.1.3"
2807
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2808
+
checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
2809
+
dependencies = [
2810
+
"cobs",
2811
+
"embedded-io 0.4.0",
2812
+
"embedded-io 0.6.1",
2813
+
"heapless",
2814
+
"serde",
2758
2815
]
2759
2816
2760
2817
[[package]]
···
2794
2851
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
2795
2852
dependencies = [
2796
2853
"proc-macro2",
2797
-
"syn 2.0.111",
2854
+
"syn 2.0.113",
2798
2855
]
2799
2856
2800
2857
[[package]]
···
2807
2864
]
2808
2865
2809
2866
[[package]]
2810
-
name = "proc-macro-error"
2811
-
version = "1.0.4"
2812
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2813
-
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
2814
-
dependencies = [
2815
-
"proc-macro-error-attr",
2816
-
"proc-macro2",
2817
-
"quote",
2818
-
"syn 1.0.109",
2819
-
"version_check",
2820
-
]
2821
-
2822
-
[[package]]
2823
-
name = "proc-macro-error-attr"
2824
-
version = "1.0.4"
2825
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2826
-
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
2827
-
dependencies = [
2828
-
"proc-macro2",
2829
-
"quote",
2830
-
"version_check",
2831
-
]
2832
-
2833
-
[[package]]
2834
2867
name = "proc-macro2"
2835
-
version = "1.0.103"
2868
+
version = "1.0.105"
2836
2869
source = "registry+https://github.com/rust-lang/crates.io-index"
2837
-
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
2870
+
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
2838
2871
dependencies = [
2839
2872
"unicode-ident",
2840
2873
]
···
2847
2880
dependencies = [
2848
2881
"proc-macro2",
2849
2882
"quote",
2850
-
"syn 2.0.111",
2883
+
"syn 2.0.113",
2851
2884
"version_check",
2852
2885
"yansi",
2853
2886
]
···
2915
2948
2916
2949
[[package]]
2917
2950
name = "quote"
2918
-
version = "1.0.42"
2951
+
version = "1.0.43"
2919
2952
source = "registry+https://github.com/rust-lang/crates.io-index"
2920
-
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
2953
+
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
2921
2954
dependencies = [
2922
2955
"proc-macro2",
2923
2956
]
···
2988
3021
]
2989
3022
2990
3023
[[package]]
2991
-
name = "range-traits"
2992
-
version = "0.3.2"
3024
+
name = "redox_syscall"
3025
+
version = "0.5.18"
2993
3026
source = "registry+https://github.com/rust-lang/crates.io-index"
2994
-
checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab"
3027
+
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
3028
+
dependencies = [
3029
+
"bitflags",
3030
+
]
2995
3031
2996
3032
[[package]]
2997
3033
name = "redox_syscall"
2998
-
version = "0.5.18"
3034
+
version = "0.7.0"
2999
3035
source = "registry+https://github.com/rust-lang/crates.io-index"
3000
-
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
3036
+
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
3001
3037
dependencies = [
3002
3038
"bitflags",
3003
3039
]
···
3014
3050
]
3015
3051
3016
3052
[[package]]
3017
-
name = "ref-cast"
3018
-
version = "1.0.25"
3019
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3020
-
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
3021
-
dependencies = [
3022
-
"ref-cast-impl",
3023
-
]
3024
-
3025
-
[[package]]
3026
-
name = "ref-cast-impl"
3027
-
version = "1.0.25"
3028
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3029
-
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
3030
-
dependencies = [
3031
-
"proc-macro2",
3032
-
"quote",
3033
-
"syn 2.0.111",
3034
-
]
3035
-
3036
-
[[package]]
3037
3053
name = "regex"
3038
3054
version = "1.12.2"
3039
3055
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3070
3086
3071
3087
[[package]]
3072
3088
name = "reqwest"
3073
-
version = "0.12.24"
3089
+
version = "0.12.28"
3074
3090
source = "registry+https://github.com/rust-lang/crates.io-index"
3075
-
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
3091
+
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
3076
3092
dependencies = [
3077
-
"async-compression",
3078
3093
"base64 0.22.1",
3079
3094
"bytes",
3080
3095
"encoding_rs",
···
3169
3184
3170
3185
[[package]]
3171
3186
name = "rsa"
3172
-
version = "0.9.9"
3187
+
version = "0.9.10"
3173
3188
source = "registry+https://github.com/rust-lang/crates.io-index"
3174
-
checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88"
3189
+
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
3175
3190
dependencies = [
3176
3191
"const-oid",
3177
3192
"digest",
···
3198
3213
version = "2.1.1"
3199
3214
source = "registry+https://github.com/rust-lang/crates.io-index"
3200
3215
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
3216
+
3217
+
[[package]]
3218
+
name = "rustc_version"
3219
+
version = "0.4.1"
3220
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3221
+
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
3222
+
dependencies = [
3223
+
"semver",
3224
+
]
3201
3225
3202
3226
[[package]]
3203
3227
name = "rustix"
3204
-
version = "1.1.2"
3228
+
version = "1.1.3"
3205
3229
source = "registry+https://github.com/rust-lang/crates.io-index"
3206
-
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
3230
+
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
3207
3231
dependencies = [
3208
3232
"bitflags",
3209
3233
"errno",
···
3214
3238
3215
3239
[[package]]
3216
3240
name = "rustls"
3217
-
version = "0.23.35"
3241
+
version = "0.23.36"
3218
3242
source = "registry+https://github.com/rust-lang/crates.io-index"
3219
-
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
3243
+
checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b"
3220
3244
dependencies = [
3221
3245
"once_cell",
3222
3246
"ring",
···
3228
3252
3229
3253
[[package]]
3230
3254
name = "rustls-native-certs"
3231
-
version = "0.8.2"
3255
+
version = "0.8.3"
3232
3256
source = "registry+https://github.com/rust-lang/crates.io-index"
3233
-
checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923"
3257
+
checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
3234
3258
dependencies = [
3235
3259
"openssl-probe",
3236
3260
"rustls-pki-types",
···
3240
3264
3241
3265
[[package]]
3242
3266
name = "rustls-pki-types"
3243
-
version = "1.13.0"
3267
+
version = "1.13.2"
3244
3268
source = "registry+https://github.com/rust-lang/crates.io-index"
3245
-
checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a"
3269
+
checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282"
3246
3270
dependencies = [
3247
3271
"web-time",
3248
3272
"zeroize",
···
3267
3291
3268
3292
[[package]]
3269
3293
name = "ryu"
3270
-
version = "1.0.20"
3294
+
version = "1.0.22"
3271
3295
source = "registry+https://github.com/rust-lang/crates.io-index"
3272
-
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
3296
+
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
3273
3297
3274
3298
[[package]]
3275
3299
name = "safemem"
···
3296
3320
]
3297
3321
3298
3322
[[package]]
3299
-
name = "schemars"
3300
-
version = "0.9.0"
3301
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3302
-
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
3303
-
dependencies = [
3304
-
"dyn-clone",
3305
-
"ref-cast",
3306
-
"serde",
3307
-
"serde_json",
3308
-
]
3309
-
3310
-
[[package]]
3311
-
name = "schemars"
3312
-
version = "1.1.0"
3313
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3314
-
checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289"
3315
-
dependencies = [
3316
-
"dyn-clone",
3317
-
"ref-cast",
3318
-
"serde",
3319
-
"serde_json",
3320
-
]
3321
-
3322
-
[[package]]
3323
3323
name = "scoped-tls"
3324
3324
version = "1.0.1"
3325
3325
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3367
3367
"core-foundation-sys",
3368
3368
"libc",
3369
3369
]
3370
+
3371
+
[[package]]
3372
+
name = "semver"
3373
+
version = "1.0.27"
3374
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3375
+
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
3370
3376
3371
3377
[[package]]
3372
3378
name = "send_wrapper"
···
3411
3417
dependencies = [
3412
3418
"proc-macro2",
3413
3419
"quote",
3414
-
"syn 2.0.111",
3420
+
"syn 2.0.113",
3415
3421
]
3416
3422
3417
3423
[[package]]
3418
3424
name = "serde_html_form"
3419
-
version = "0.2.8"
3425
+
version = "0.3.2"
3420
3426
source = "registry+https://github.com/rust-lang/crates.io-index"
3421
-
checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f"
3427
+
checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94"
3422
3428
dependencies = [
3423
3429
"form_urlencoded",
3424
-
"indexmap 2.12.1",
3430
+
"indexmap",
3425
3431
"itoa",
3426
-
"ryu",
3427
3432
"serde_core",
3428
3433
]
3429
3434
···
3441
3446
3442
3447
[[package]]
3443
3448
name = "serde_json"
3444
-
version = "1.0.145"
3449
+
version = "1.0.149"
3445
3450
source = "registry+https://github.com/rust-lang/crates.io-index"
3446
-
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
3451
+
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
3447
3452
dependencies = [
3448
3453
"itoa",
3449
3454
"memchr",
3450
-
"ryu",
3451
3455
"serde",
3452
3456
"serde_core",
3457
+
"zmij",
3453
3458
]
3454
3459
3455
3460
[[package]]
···
3471
3476
dependencies = [
3472
3477
"proc-macro2",
3473
3478
"quote",
3474
-
"syn 2.0.111",
3479
+
"syn 2.0.113",
3475
3480
]
3476
3481
3477
3482
[[package]]
···
3488
3493
3489
3494
[[package]]
3490
3495
name = "serde_with"
3491
-
version = "3.16.0"
3496
+
version = "3.16.1"
3492
3497
source = "registry+https://github.com/rust-lang/crates.io-index"
3493
-
checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1"
3498
+
checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7"
3494
3499
dependencies = [
3495
3500
"base64 0.22.1",
3496
3501
"chrono",
3497
3502
"hex",
3498
-
"indexmap 1.9.3",
3499
-
"indexmap 2.12.1",
3500
-
"schemars 0.9.0",
3501
-
"schemars 1.1.0",
3502
3503
"serde_core",
3503
3504
"serde_json",
3504
3505
"serde_with_macros",
···
3507
3508
3508
3509
[[package]]
3509
3510
name = "serde_with_macros"
3510
-
version = "3.16.0"
3511
+
version = "3.16.1"
3511
3512
source = "registry+https://github.com/rust-lang/crates.io-index"
3512
-
checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b"
3513
+
checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c"
3513
3514
dependencies = [
3514
3515
"darling",
3515
3516
"proc-macro2",
3516
3517
"quote",
3517
-
"syn 2.0.111",
3518
+
"syn 2.0.113",
3518
3519
]
3519
3520
3520
3521
[[package]]
···
3571
3572
3572
3573
[[package]]
3573
3574
name = "signal-hook-registry"
3574
-
version = "1.4.7"
3575
+
version = "1.4.8"
3575
3576
source = "registry+https://github.com/rust-lang/crates.io-index"
3576
-
checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad"
3577
+
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
3577
3578
dependencies = [
3579
+
"errno",
3578
3580
"libc",
3579
3581
]
3580
3582
···
3590
3592
3591
3593
[[package]]
3592
3594
name = "simd-adler32"
3593
-
version = "0.3.7"
3595
+
version = "0.3.8"
3594
3596
source = "registry+https://github.com/rust-lang/crates.io-index"
3595
-
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
3597
+
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
3596
3598
3597
3599
[[package]]
3598
3600
name = "siphasher"
···
3647
3649
version = "0.9.8"
3648
3650
source = "registry+https://github.com/rust-lang/crates.io-index"
3649
3651
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
3652
+
dependencies = [
3653
+
"lock_api",
3654
+
]
3650
3655
3651
3656
[[package]]
3652
3657
name = "spin"
···
3671
3676
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
3672
3677
3673
3678
[[package]]
3674
-
name = "static-regular-grammar"
3675
-
version = "2.0.2"
3676
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3677
-
checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957"
3678
-
dependencies = [
3679
-
"abnf",
3680
-
"btree-range-map",
3681
-
"ciborium",
3682
-
"hex_fmt",
3683
-
"indoc",
3684
-
"proc-macro-error",
3685
-
"proc-macro2",
3686
-
"quote",
3687
-
"serde",
3688
-
"sha2",
3689
-
"syn 2.0.111",
3690
-
"thiserror 1.0.69",
3691
-
]
3692
-
3693
-
[[package]]
3694
3679
name = "static_assertions"
3695
3680
version = "1.1.0"
3696
3681
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3744
3729
3745
3730
[[package]]
3746
3731
name = "supports-hyperlinks"
3747
-
version = "3.1.0"
3732
+
version = "3.2.0"
3748
3733
source = "registry+https://github.com/rust-lang/crates.io-index"
3749
-
checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b"
3734
+
checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91"
3750
3735
3751
3736
[[package]]
3752
3737
name = "supports-unicode"
···
3767
3752
3768
3753
[[package]]
3769
3754
name = "syn"
3770
-
version = "2.0.111"
3755
+
version = "2.0.113"
3771
3756
source = "registry+https://github.com/rust-lang/crates.io-index"
3772
-
checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87"
3757
+
checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4"
3773
3758
dependencies = [
3774
3759
"proc-macro2",
3775
3760
"quote",
···
3793
3778
dependencies = [
3794
3779
"proc-macro2",
3795
3780
"quote",
3796
-
"syn 2.0.111",
3781
+
"syn 2.0.113",
3797
3782
]
3798
3783
3799
3784
[[package]]
···
3825
3810
3826
3811
[[package]]
3827
3812
name = "tempfile"
3828
-
version = "3.23.0"
3813
+
version = "3.24.0"
3829
3814
source = "registry+https://github.com/rust-lang/crates.io-index"
3830
-
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
3815
+
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
3831
3816
dependencies = [
3832
3817
"fastrand",
3833
3818
"getrandom 0.3.4",
···
3893
3878
dependencies = [
3894
3879
"proc-macro2",
3895
3880
"quote",
3896
-
"syn 2.0.111",
3881
+
"syn 2.0.113",
3897
3882
]
3898
3883
3899
3884
[[package]]
···
3904
3889
dependencies = [
3905
3890
"proc-macro2",
3906
3891
"quote",
3907
-
"syn 2.0.111",
3892
+
"syn 2.0.113",
3908
3893
]
3909
3894
3910
3895
[[package]]
···
3932
3917
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
3933
3918
dependencies = [
3934
3919
"deranged",
3935
-
"itoa",
3936
3920
"libc",
3937
3921
"num-conv",
3938
3922
"num_threads",
3939
3923
"powerfmt",
3940
3924
"serde",
3941
3925
"time-core",
3942
-
"time-macros",
3943
3926
]
3944
3927
3945
3928
[[package]]
···
3947
3930
version = "0.1.6"
3948
3931
source = "registry+https://github.com/rust-lang/crates.io-index"
3949
3932
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
3950
-
3951
-
[[package]]
3952
-
name = "time-macros"
3953
-
version = "0.2.24"
3954
-
source = "registry+https://github.com/rust-lang/crates.io-index"
3955
-
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
3956
-
dependencies = [
3957
-
"num-conv",
3958
-
"time-core",
3959
-
]
3960
3933
3961
3934
[[package]]
3962
3935
name = "tiny_http"
···
3997
3970
3998
3971
[[package]]
3999
3972
name = "tokio"
4000
-
version = "1.48.0"
3973
+
version = "1.49.0"
4001
3974
source = "registry+https://github.com/rust-lang/crates.io-index"
4002
-
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
3975
+
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
4003
3976
dependencies = [
4004
3977
"bytes",
4005
3978
"libc",
···
4020
3993
dependencies = [
4021
3994
"proc-macro2",
4022
3995
"quote",
4023
-
"syn 2.0.111",
3996
+
"syn 2.0.113",
4024
3997
]
4025
3998
4026
3999
[[package]]
···
4070
4043
4071
4044
[[package]]
4072
4045
name = "tokio-util"
4073
-
version = "0.7.17"
4046
+
version = "0.7.18"
4074
4047
source = "registry+https://github.com/rust-lang/crates.io-index"
4075
-
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
4048
+
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
4076
4049
dependencies = [
4077
4050
"bytes",
4078
4051
"futures-core",
···
4100
4073
4101
4074
[[package]]
4102
4075
name = "tower-http"
4103
-
version = "0.6.7"
4076
+
version = "0.6.8"
4104
4077
source = "registry+https://github.com/rust-lang/crates.io-index"
4105
-
checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456"
4078
+
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
4106
4079
dependencies = [
4107
4080
"async-compression",
4108
4081
"bitflags",
···
4141
4114
4142
4115
[[package]]
4143
4116
name = "tracing"
4144
-
version = "0.1.41"
4117
+
version = "0.1.44"
4145
4118
source = "registry+https://github.com/rust-lang/crates.io-index"
4146
-
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
4119
+
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
4147
4120
dependencies = [
4148
4121
"log",
4149
4122
"pin-project-lite",
···
4153
4126
4154
4127
[[package]]
4155
4128
name = "tracing-attributes"
4156
-
version = "0.1.30"
4129
+
version = "0.1.31"
4157
4130
source = "registry+https://github.com/rust-lang/crates.io-index"
4158
-
checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
4131
+
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
4159
4132
dependencies = [
4160
4133
"proc-macro2",
4161
4134
"quote",
4162
-
"syn 2.0.111",
4135
+
"syn 2.0.113",
4163
4136
]
4164
4137
4165
4138
[[package]]
4166
4139
name = "tracing-core"
4167
-
version = "0.1.34"
4140
+
version = "0.1.36"
4168
4141
source = "registry+https://github.com/rust-lang/crates.io-index"
4169
-
checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
4142
+
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
4170
4143
dependencies = [
4171
4144
"once_cell",
4172
4145
"valuable",
···
4185
4158
4186
4159
[[package]]
4187
4160
name = "tracing-subscriber"
4188
-
version = "0.3.20"
4161
+
version = "0.3.22"
4189
4162
source = "registry+https://github.com/rust-lang/crates.io-index"
4190
-
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
4163
+
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
4191
4164
dependencies = [
4192
4165
"matchers",
4193
4166
"nu-ansi-term",
···
4209
4182
dependencies = [
4210
4183
"proc-macro2",
4211
4184
"quote",
4212
-
"syn 2.0.111",
4185
+
"syn 2.0.113",
4213
4186
]
4214
4187
4215
4188
[[package]]
···
4261
4234
4262
4235
[[package]]
4263
4236
name = "unicase"
4264
-
version = "2.8.1"
4237
+
version = "2.9.0"
4265
4238
source = "registry+https://github.com/rust-lang/crates.io-index"
4266
-
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
4239
+
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
4267
4240
4268
4241
[[package]]
4269
4242
name = "unicode-ident"
···
4315
4288
4316
4289
[[package]]
4317
4290
name = "url"
4318
-
version = "2.5.7"
4291
+
version = "2.5.8"
4319
4292
source = "registry+https://github.com/rust-lang/crates.io-index"
4320
-
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
4293
+
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
4321
4294
dependencies = [
4322
4295
"form_urlencoded",
4323
4296
"idna",
4324
4297
"percent-encoding",
4325
4298
"serde",
4299
+
"serde_derive",
4326
4300
]
4327
4301
4328
4302
[[package]]
···
4397
4371
4398
4372
[[package]]
4399
4373
name = "wasm-bindgen"
4400
-
version = "0.2.105"
4374
+
version = "0.2.106"
4401
4375
source = "registry+https://github.com/rust-lang/crates.io-index"
4402
-
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
4376
+
checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd"
4403
4377
dependencies = [
4404
4378
"cfg-if",
4405
4379
"once_cell",
···
4410
4384
4411
4385
[[package]]
4412
4386
name = "wasm-bindgen-futures"
4413
-
version = "0.4.55"
4387
+
version = "0.4.56"
4414
4388
source = "registry+https://github.com/rust-lang/crates.io-index"
4415
-
checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
4389
+
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
4416
4390
dependencies = [
4417
4391
"cfg-if",
4418
4392
"js-sys",
···
4423
4397
4424
4398
[[package]]
4425
4399
name = "wasm-bindgen-macro"
4426
-
version = "0.2.105"
4400
+
version = "0.2.106"
4427
4401
source = "registry+https://github.com/rust-lang/crates.io-index"
4428
-
checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2"
4402
+
checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3"
4429
4403
dependencies = [
4430
4404
"quote",
4431
4405
"wasm-bindgen-macro-support",
···
4433
4407
4434
4408
[[package]]
4435
4409
name = "wasm-bindgen-macro-support"
4436
-
version = "0.2.105"
4410
+
version = "0.2.106"
4437
4411
source = "registry+https://github.com/rust-lang/crates.io-index"
4438
-
checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc"
4412
+
checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40"
4439
4413
dependencies = [
4440
4414
"bumpalo",
4441
4415
"proc-macro2",
4442
4416
"quote",
4443
-
"syn 2.0.111",
4417
+
"syn 2.0.113",
4444
4418
"wasm-bindgen-shared",
4445
4419
]
4446
4420
4447
4421
[[package]]
4448
4422
name = "wasm-bindgen-shared"
4449
-
version = "0.2.105"
4423
+
version = "0.2.106"
4450
4424
source = "registry+https://github.com/rust-lang/crates.io-index"
4451
-
checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76"
4425
+
checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4"
4452
4426
dependencies = [
4453
4427
"unicode-ident",
4454
4428
]
···
4468
4442
4469
4443
[[package]]
4470
4444
name = "web-sys"
4471
-
version = "0.3.82"
4445
+
version = "0.3.83"
4472
4446
source = "registry+https://github.com/rust-lang/crates.io-index"
4473
-
checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1"
4447
+
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
4474
4448
dependencies = [
4475
4449
"js-sys",
4476
4450
"wasm-bindgen",
···
4516
4490
4517
4491
[[package]]
4518
4492
name = "webpki-roots"
4519
-
version = "1.0.4"
4493
+
version = "1.0.5"
4520
4494
source = "registry+https://github.com/rust-lang/crates.io-index"
4521
-
checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e"
4495
+
checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c"
4522
4496
dependencies = [
4523
4497
"rustls-pki-types",
4524
4498
]
···
4539
4513
]
4540
4514
4541
4515
[[package]]
4542
-
name = "windows"
4543
-
version = "0.61.3"
4544
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4545
-
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
4546
-
dependencies = [
4547
-
"windows-collections",
4548
-
"windows-core 0.61.2",
4549
-
"windows-future",
4550
-
"windows-link 0.1.3",
4551
-
"windows-numerics",
4552
-
]
4553
-
4554
-
[[package]]
4555
-
name = "windows-collections"
4556
-
version = "0.2.0"
4557
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4558
-
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
4559
-
dependencies = [
4560
-
"windows-core 0.61.2",
4561
-
]
4562
-
4563
-
[[package]]
4564
-
name = "windows-core"
4565
-
version = "0.61.2"
4566
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4567
-
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
4568
-
dependencies = [
4569
-
"windows-implement",
4570
-
"windows-interface",
4571
-
"windows-link 0.1.3",
4572
-
"windows-result 0.3.4",
4573
-
"windows-strings 0.4.2",
4574
-
]
4575
-
4576
-
[[package]]
4577
4516
name = "windows-core"
4578
4517
version = "0.62.2"
4579
4518
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4581
4520
dependencies = [
4582
4521
"windows-implement",
4583
4522
"windows-interface",
4584
-
"windows-link 0.2.1",
4585
-
"windows-result 0.4.1",
4586
-
"windows-strings 0.5.1",
4587
-
]
4588
-
4589
-
[[package]]
4590
-
name = "windows-future"
4591
-
version = "0.2.1"
4592
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4593
-
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
4594
-
dependencies = [
4595
-
"windows-core 0.61.2",
4596
-
"windows-link 0.1.3",
4597
-
"windows-threading",
4523
+
"windows-link",
4524
+
"windows-result",
4525
+
"windows-strings",
4598
4526
]
4599
4527
4600
4528
[[package]]
···
4605
4533
dependencies = [
4606
4534
"proc-macro2",
4607
4535
"quote",
4608
-
"syn 2.0.111",
4536
+
"syn 2.0.113",
4609
4537
]
4610
4538
4611
4539
[[package]]
···
4616
4544
dependencies = [
4617
4545
"proc-macro2",
4618
4546
"quote",
4619
-
"syn 2.0.111",
4547
+
"syn 2.0.113",
4620
4548
]
4621
4549
4622
4550
[[package]]
4623
4551
name = "windows-link"
4624
-
version = "0.1.3"
4625
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4626
-
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
4627
-
4628
-
[[package]]
4629
-
name = "windows-link"
4630
4552
version = "0.2.1"
4631
4553
source = "registry+https://github.com/rust-lang/crates.io-index"
4632
4554
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
4633
4555
4634
4556
[[package]]
4635
-
name = "windows-numerics"
4636
-
version = "0.2.0"
4637
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4638
-
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
4639
-
dependencies = [
4640
-
"windows-core 0.61.2",
4641
-
"windows-link 0.1.3",
4642
-
]
4643
-
4644
-
[[package]]
4645
4557
name = "windows-registry"
4646
4558
version = "0.6.1"
4647
4559
source = "registry+https://github.com/rust-lang/crates.io-index"
4648
4560
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
4649
4561
dependencies = [
4650
-
"windows-link 0.2.1",
4651
-
"windows-result 0.4.1",
4652
-
"windows-strings 0.5.1",
4653
-
]
4654
-
4655
-
[[package]]
4656
-
name = "windows-result"
4657
-
version = "0.3.4"
4658
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4659
-
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
4660
-
dependencies = [
4661
-
"windows-link 0.1.3",
4562
+
"windows-link",
4563
+
"windows-result",
4564
+
"windows-strings",
4662
4565
]
4663
4566
4664
4567
[[package]]
···
4667
4570
source = "registry+https://github.com/rust-lang/crates.io-index"
4668
4571
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
4669
4572
dependencies = [
4670
-
"windows-link 0.2.1",
4671
-
]
4672
-
4673
-
[[package]]
4674
-
name = "windows-strings"
4675
-
version = "0.4.2"
4676
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4677
-
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
4678
-
dependencies = [
4679
-
"windows-link 0.1.3",
4573
+
"windows-link",
4680
4574
]
4681
4575
4682
4576
[[package]]
···
4685
4579
source = "registry+https://github.com/rust-lang/crates.io-index"
4686
4580
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
4687
4581
dependencies = [
4688
-
"windows-link 0.2.1",
4582
+
"windows-link",
4689
4583
]
4690
4584
4691
4585
[[package]]
···
4717
4611
4718
4612
[[package]]
4719
4613
name = "windows-sys"
4614
+
version = "0.59.0"
4615
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4616
+
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
4617
+
dependencies = [
4618
+
"windows-targets 0.52.6",
4619
+
]
4620
+
4621
+
[[package]]
4622
+
name = "windows-sys"
4720
4623
version = "0.60.2"
4721
4624
source = "registry+https://github.com/rust-lang/crates.io-index"
4722
4625
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
···
4730
4633
source = "registry+https://github.com/rust-lang/crates.io-index"
4731
4634
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
4732
4635
dependencies = [
4733
-
"windows-link 0.2.1",
4636
+
"windows-link",
4734
4637
]
4735
4638
4736
4639
[[package]]
···
4785
4688
source = "registry+https://github.com/rust-lang/crates.io-index"
4786
4689
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
4787
4690
dependencies = [
4788
-
"windows-link 0.2.1",
4691
+
"windows-link",
4789
4692
"windows_aarch64_gnullvm 0.53.1",
4790
4693
"windows_aarch64_msvc 0.53.1",
4791
4694
"windows_i686_gnu 0.53.1",
···
4794
4697
"windows_x86_64_gnu 0.53.1",
4795
4698
"windows_x86_64_gnullvm 0.53.1",
4796
4699
"windows_x86_64_msvc 0.53.1",
4797
-
]
4798
-
4799
-
[[package]]
4800
-
name = "windows-threading"
4801
-
version = "0.1.0"
4802
-
source = "registry+https://github.com/rust-lang/crates.io-index"
4803
-
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
4804
-
dependencies = [
4805
-
"windows-link 0.1.3",
4806
4700
]
4807
4701
4808
4702
[[package]]
···
5008
4902
"futures",
5009
4903
"globset",
5010
4904
"ignore",
4905
+
"indicatif",
5011
4906
"jacquard",
5012
4907
"jacquard-api",
5013
4908
"jacquard-common",
···
5032
4927
"tower-http",
5033
4928
"url",
5034
4929
"walkdir",
4930
+
"wisp-lexicons",
4931
+
]
4932
+
4933
+
[[package]]
4934
+
name = "wisp-lexicons"
4935
+
version = "0.1.0"
4936
+
dependencies = [
4937
+
"jacquard-common",
4938
+
"jacquard-derive",
4939
+
"jacquard-lexicon",
4940
+
"rustversion",
4941
+
"serde",
5035
4942
]
5036
4943
5037
4944
[[package]]
···
5082
4989
dependencies = [
5083
4990
"proc-macro2",
5084
4991
"quote",
5085
-
"syn 2.0.111",
4992
+
"syn 2.0.113",
5086
4993
"synstructure",
5087
4994
]
5088
4995
5089
4996
[[package]]
5090
4997
name = "zerocopy"
5091
-
version = "0.8.30"
4998
+
version = "0.8.32"
5092
4999
source = "registry+https://github.com/rust-lang/crates.io-index"
5093
-
checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c"
5000
+
checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56"
5094
5001
dependencies = [
5095
5002
"zerocopy-derive",
5096
5003
]
5097
5004
5098
5005
[[package]]
5099
5006
name = "zerocopy-derive"
5100
-
version = "0.8.30"
5007
+
version = "0.8.32"
5101
5008
source = "registry+https://github.com/rust-lang/crates.io-index"
5102
-
checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5"
5009
+
checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90"
5103
5010
dependencies = [
5104
5011
"proc-macro2",
5105
5012
"quote",
5106
-
"syn 2.0.111",
5013
+
"syn 2.0.113",
5107
5014
]
5108
5015
5109
5016
[[package]]
···
5123
5030
dependencies = [
5124
5031
"proc-macro2",
5125
5032
"quote",
5126
-
"syn 2.0.111",
5033
+
"syn 2.0.113",
5127
5034
"synstructure",
5128
5035
]
5129
5036
···
5166
5073
dependencies = [
5167
5074
"proc-macro2",
5168
5075
"quote",
5169
-
"syn 2.0.111",
5076
+
"syn 2.0.113",
5170
5077
]
5078
+
5079
+
[[package]]
5080
+
name = "zmij"
5081
+
version = "1.0.12"
5082
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5083
+
checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8"
+16
-7
cli/Cargo.toml
+16
-7
cli/Cargo.toml
···
8
8
place_wisp = []
9
9
10
10
[dependencies]
11
-
jacquard = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["loopback"] }
12
-
jacquard-oauth = { git = "https://tangled.org/nekomimi.pet/jacquard" }
13
-
jacquard-api = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["streaming"] }
14
-
jacquard-common = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["websocket"] }
15
-
jacquard-identity = { git = "https://tangled.org/nekomimi.pet/jacquard", features = ["dns"] }
16
-
jacquard-derive = { git = "https://tangled.org/nekomimi.pet/jacquard" }
17
-
jacquard-lexicon = { git = "https://tangled.org/nekomimi.pet/jacquard" }
11
+
jacquard = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["loopback"] }
12
+
jacquard-oauth = { git = "https://tangled.org/nonbinary.computer/jacquard" }
13
+
jacquard-api = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["streaming"] }
14
+
jacquard-common = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["websocket"] }
15
+
jacquard-identity = { git = "https://tangled.org/nonbinary.computer/jacquard", features = ["dns"] }
16
+
jacquard-derive = { git = "https://tangled.org/nonbinary.computer/jacquard" }
17
+
jacquard-lexicon = { git = "https://tangled.org/nonbinary.computer/jacquard" }
18
+
wisp-lexicons = { path = "crates/lexicons" }
19
+
#jacquard = { path = "../../jacquard/crates/jacquard", features = ["loopback"] }
20
+
#jacquard-oauth = { path = "../../jacquard/crates/jacquard-oauth" }
21
+
#jacquard-api = { path = "../../jacquard/crates/jacquard-api", features = ["streaming"] }
22
+
#jacquard-common = { path = "../../jacquard/crates/jacquard-common", features = ["websocket"] }
23
+
#jacquard-identity = { path = "../../jacquard/crates/jacquard-identity", features = ["dns"] }
24
+
#jacquard-derive = { path = "../../jacquard/crates/jacquard-derive" }
25
+
#jacquard-lexicon = { path = "../../jacquard/crates/jacquard-lexicon" }
18
26
clap = { version = "4.5.51", features = ["derive"] }
19
27
tokio = { version = "1.48", features = ["full"] }
20
28
miette = { version = "7.6.0", features = ["fancy"] }
···
42
50
regex = "1.11"
43
51
ignore = "0.4"
44
52
globset = "0.4"
53
+
indicatif = "0.17"
+72
-2
cli/README.md
+72
-2
cli/README.md
···
32
32
33
33
## Usage
34
34
35
+
### Commands
36
+
37
+
The CLI supports three main commands:
38
+
- **deploy**: Upload a site to your PDS (default command)
39
+
- **pull**: Download a site from a PDS to a local directory
40
+
- **serve**: Serve a site locally with real-time firehose updates
41
+
35
42
### Basic Deployment
36
43
37
44
Deploy the current directory:
38
45
39
46
```bash
40
-
wisp-cli nekomimi.ppet --path . --site my-site
47
+
wisp-cli nekomimi.pet --path . --site my-site
41
48
```
42
49
43
50
Deploy a specific directory:
···
46
53
wisp-cli alice.bsky.social --path ./dist/ --site my-site
47
54
```
48
55
56
+
Or use the explicit `deploy` subcommand:
57
+
58
+
```bash
59
+
wisp-cli deploy alice.bsky.social --path ./dist/ --site my-site
60
+
```
61
+
62
+
### Pull a Site
63
+
64
+
Download a site from a PDS to a local directory:
65
+
66
+
```bash
67
+
wisp-cli pull alice.bsky.social --site my-site --path ./downloaded-site
68
+
```
69
+
70
+
This will download all files from the site to the specified directory.
71
+
72
+
### Serve a Site Locally
73
+
74
+
Serve a site locally with real-time updates from the firehose:
75
+
76
+
```bash
77
+
wisp-cli serve alice.bsky.social --site my-site --path ./site --port 8080
78
+
```
79
+
80
+
This will:
81
+
1. Download the site to the specified path
82
+
2. Start a local server on the specified port (default: 8080)
83
+
3. Watch the firehose for updates and automatically reload files when changed
84
+
49
85
### Authentication Methods
50
86
51
87
#### OAuth (Recommended)
···
79
115
80
116
## Command-Line Options
81
117
118
+
### Deploy Command
119
+
82
120
```
83
-
wisp-cli [OPTIONS] <INPUT>
121
+
wisp-cli [deploy] [OPTIONS] <INPUT>
84
122
85
123
Arguments:
86
124
<INPUT> Handle (e.g., alice.bsky.social), DID, or PDS URL
···
90
128
-s, --site <SITE> Site name (defaults to directory name)
91
129
--store <STORE> Path to auth store file (only used with OAuth) [default: /tmp/wisp-oauth-session.json]
92
130
--password <PASSWORD> App Password for authentication (alternative to OAuth)
131
+
--directory Enable directory listing mode for paths without index files
132
+
--spa Enable SPA mode (serve index.html for all routes)
133
+
-y, --yes Skip confirmation prompts (automatically accept warnings)
93
134
-h, --help Print help
94
135
-V, --version Print version
136
+
```
137
+
138
+
### Pull Command
139
+
140
+
```
141
+
wisp-cli pull [OPTIONS] --site <SITE> <INPUT>
142
+
143
+
Arguments:
144
+
<INPUT> Handle (e.g., alice.bsky.social) or DID
145
+
146
+
Options:
147
+
-s, --site <SITE> Site name (record key)
148
+
-p, --path <PATH> Output directory for the downloaded site [default: .]
149
+
-h, --help Print help
150
+
```
151
+
152
+
### Serve Command
153
+
154
+
```
155
+
wisp-cli serve [OPTIONS] --site <SITE> <INPUT>
156
+
157
+
Arguments:
158
+
<INPUT> Handle (e.g., alice.bsky.social) or DID
159
+
160
+
Options:
161
+
-s, --site <SITE> Site name (record key)
162
+
-p, --path <PATH> Output directory for the site files [default: .]
163
+
-P, --port <PORT> Port to serve on [default: 8080]
164
+
-h, --help Print help
95
165
```
96
166
97
167
## How It Works
+15
cli/crates/lexicons/Cargo.toml
+15
cli/crates/lexicons/Cargo.toml
···
1
+
[package]
2
+
name = "wisp-lexicons"
3
+
version = "0.1.0"
4
+
edition = "2024"
5
+
6
+
[features]
7
+
default = ["place_wisp"]
8
+
place_wisp = []
9
+
10
+
[dependencies]
11
+
jacquard-common = { git = "https://tangled.org/nonbinary.computer/jacquard" }
12
+
jacquard-derive = { git = "https://tangled.org/nonbinary.computer/jacquard" }
13
+
jacquard-lexicon = { git = "https://tangled.org/nonbinary.computer/jacquard" }
14
+
serde = { version = "1.0", features = ["derive"] }
15
+
rustversion = "1.0"
+43
cli/crates/lexicons/src/builder_types.rs
+43
cli/crates/lexicons/src/builder_types.rs
···
1
+
// @generated by jacquard-lexicon. DO NOT EDIT.
2
+
//
3
+
// This file was automatically generated from Lexicon schemas.
4
+
// Any manual changes will be overwritten on the next regeneration.
5
+
6
+
/// Marker type indicating a builder field has been set
7
+
pub struct Set<T>(pub T);
8
+
impl<T> Set<T> {
9
+
/// Extract the inner value
10
+
#[inline]
11
+
pub fn into_inner(self) -> T {
12
+
self.0
13
+
}
14
+
}
15
+
16
+
/// Marker type indicating a builder field has not been set
17
+
pub struct Unset;
18
+
/// Trait indicating a builder field is set (has a value)
19
+
#[rustversion::attr(
20
+
since(1.78.0),
21
+
diagnostic::on_unimplemented(
22
+
message = "the field `{Self}` was not set, but this method requires it to be set",
23
+
label = "the field `{Self}` was not set"
24
+
)
25
+
)]
26
+
pub trait IsSet: private::Sealed {}
27
+
/// Trait indicating a builder field is unset (no value yet)
28
+
#[rustversion::attr(
29
+
since(1.78.0),
30
+
diagnostic::on_unimplemented(
31
+
message = "the field `{Self}` was already set, but this method requires it to be unset",
32
+
label = "the field `{Self}` was already set"
33
+
)
34
+
)]
35
+
pub trait IsUnset: private::Sealed {}
36
+
impl<T> IsSet for Set<T> {}
37
+
impl IsUnset for Unset {}
38
+
mod private {
39
+
/// Sealed trait to prevent external implementations
40
+
pub trait Sealed {}
41
+
impl<T> Sealed for super::Set<T> {}
42
+
impl Sealed for super::Unset {}
43
+
}
+11
cli/crates/lexicons/src/lib.rs
+11
cli/crates/lexicons/src/lib.rs
···
1
+
extern crate alloc;
2
+
3
+
// @generated by jacquard-lexicon. DO NOT EDIT.
4
+
//
5
+
// This file was automatically generated from Lexicon schemas.
6
+
// Any manual changes will be overwritten on the next regeneration.
7
+
8
+
pub mod builder_types;
9
+
10
+
#[cfg(feature = "place_wisp")]
11
+
pub mod place_wisp;
+1490
cli/crates/lexicons/src/place_wisp/fs.rs
+1490
cli/crates/lexicons/src/place_wisp/fs.rs
···
1
+
// @generated by jacquard-lexicon. DO NOT EDIT.
2
+
//
3
+
// Lexicon: place.wisp.fs
4
+
//
5
+
// This file was automatically generated from Lexicon schemas.
6
+
// Any manual changes will be overwritten on the next regeneration.
7
+
8
+
#[jacquard_derive::lexicon]
9
+
#[derive(
10
+
serde::Serialize,
11
+
serde::Deserialize,
12
+
Debug,
13
+
Clone,
14
+
PartialEq,
15
+
Eq,
16
+
jacquard_derive::IntoStatic
17
+
)]
18
+
#[serde(rename_all = "camelCase")]
19
+
pub struct Directory<'a> {
20
+
#[serde(borrow)]
21
+
pub entries: Vec<crate::place_wisp::fs::Entry<'a>>,
22
+
#[serde(borrow)]
23
+
pub r#type: jacquard_common::CowStr<'a>,
24
+
}
25
+
26
+
pub mod directory_state {
27
+
28
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
29
+
#[allow(unused)]
30
+
use ::core::marker::PhantomData;
31
+
mod sealed {
32
+
pub trait Sealed {}
33
+
}
34
+
/// State trait tracking which required fields have been set
35
+
pub trait State: sealed::Sealed {
36
+
type Type;
37
+
type Entries;
38
+
}
39
+
/// Empty state - all required fields are unset
40
+
pub struct Empty(());
41
+
impl sealed::Sealed for Empty {}
42
+
impl State for Empty {
43
+
type Type = Unset;
44
+
type Entries = Unset;
45
+
}
46
+
///State transition - sets the `type` field to Set
47
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
48
+
impl<S: State> sealed::Sealed for SetType<S> {}
49
+
impl<S: State> State for SetType<S> {
50
+
type Type = Set<members::r#type>;
51
+
type Entries = S::Entries;
52
+
}
53
+
///State transition - sets the `entries` field to Set
54
+
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
55
+
impl<S: State> sealed::Sealed for SetEntries<S> {}
56
+
impl<S: State> State for SetEntries<S> {
57
+
type Type = S::Type;
58
+
type Entries = Set<members::entries>;
59
+
}
60
+
/// Marker types for field names
61
+
#[allow(non_camel_case_types)]
62
+
pub mod members {
63
+
///Marker type for the `type` field
64
+
pub struct r#type(());
65
+
///Marker type for the `entries` field
66
+
pub struct entries(());
67
+
}
68
+
}
69
+
70
+
/// Builder for constructing an instance of this type
71
+
pub struct DirectoryBuilder<'a, S: directory_state::State> {
72
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
73
+
__unsafe_private_named: (
74
+
::core::option::Option<Vec<crate::place_wisp::fs::Entry<'a>>>,
75
+
::core::option::Option<jacquard_common::CowStr<'a>>,
76
+
),
77
+
_phantom: ::core::marker::PhantomData<&'a ()>,
78
+
}
79
+
80
+
impl<'a> Directory<'a> {
81
+
/// Create a new builder for this type
82
+
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
83
+
DirectoryBuilder::new()
84
+
}
85
+
}
86
+
87
+
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
88
+
/// Create a new builder with all fields unset
89
+
pub fn new() -> Self {
90
+
DirectoryBuilder {
91
+
_phantom_state: ::core::marker::PhantomData,
92
+
__unsafe_private_named: (None, None),
93
+
_phantom: ::core::marker::PhantomData,
94
+
}
95
+
}
96
+
}
97
+
98
+
impl<'a, S> DirectoryBuilder<'a, S>
99
+
where
100
+
S: directory_state::State,
101
+
S::Entries: directory_state::IsUnset,
102
+
{
103
+
/// Set the `entries` field (required)
104
+
pub fn entries(
105
+
mut self,
106
+
value: impl Into<Vec<crate::place_wisp::fs::Entry<'a>>>,
107
+
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
108
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
109
+
DirectoryBuilder {
110
+
_phantom_state: ::core::marker::PhantomData,
111
+
__unsafe_private_named: self.__unsafe_private_named,
112
+
_phantom: ::core::marker::PhantomData,
113
+
}
114
+
}
115
+
}
116
+
117
+
impl<'a, S> DirectoryBuilder<'a, S>
118
+
where
119
+
S: directory_state::State,
120
+
S::Type: directory_state::IsUnset,
121
+
{
122
+
/// Set the `type` field (required)
123
+
pub fn r#type(
124
+
mut self,
125
+
value: impl Into<jacquard_common::CowStr<'a>>,
126
+
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
127
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
128
+
DirectoryBuilder {
129
+
_phantom_state: ::core::marker::PhantomData,
130
+
__unsafe_private_named: self.__unsafe_private_named,
131
+
_phantom: ::core::marker::PhantomData,
132
+
}
133
+
}
134
+
}
135
+
136
+
impl<'a, S> DirectoryBuilder<'a, S>
137
+
where
138
+
S: directory_state::State,
139
+
S::Type: directory_state::IsSet,
140
+
S::Entries: directory_state::IsSet,
141
+
{
142
+
/// Build the final struct
143
+
pub fn build(self) -> Directory<'a> {
144
+
Directory {
145
+
entries: self.__unsafe_private_named.0.unwrap(),
146
+
r#type: self.__unsafe_private_named.1.unwrap(),
147
+
extra_data: Default::default(),
148
+
}
149
+
}
150
+
/// Build the final struct with custom extra_data
151
+
pub fn build_with_data(
152
+
self,
153
+
extra_data: std::collections::BTreeMap<
154
+
jacquard_common::smol_str::SmolStr,
155
+
jacquard_common::types::value::Data<'a>,
156
+
>,
157
+
) -> Directory<'a> {
158
+
Directory {
159
+
entries: self.__unsafe_private_named.0.unwrap(),
160
+
r#type: self.__unsafe_private_named.1.unwrap(),
161
+
extra_data: Some(extra_data),
162
+
}
163
+
}
164
+
}
165
+
166
+
fn lexicon_doc_place_wisp_fs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
167
+
::jacquard_lexicon::lexicon::LexiconDoc {
168
+
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
169
+
id: ::jacquard_common::CowStr::new_static("place.wisp.fs"),
170
+
revision: None,
171
+
description: None,
172
+
defs: {
173
+
let mut map = ::std::collections::BTreeMap::new();
174
+
map.insert(
175
+
::jacquard_common::smol_str::SmolStr::new_static("directory"),
176
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
177
+
description: None,
178
+
required: Some(
179
+
vec![
180
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
181
+
::jacquard_common::smol_str::SmolStr::new_static("entries")
182
+
],
183
+
),
184
+
nullable: None,
185
+
properties: {
186
+
#[allow(unused_mut)]
187
+
let mut map = ::std::collections::BTreeMap::new();
188
+
map.insert(
189
+
::jacquard_common::smol_str::SmolStr::new_static("entries"),
190
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
191
+
description: None,
192
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
193
+
description: None,
194
+
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
195
+
}),
196
+
min_length: None,
197
+
max_length: Some(500usize),
198
+
}),
199
+
);
200
+
map.insert(
201
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
202
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
203
+
description: None,
204
+
format: None,
205
+
default: None,
206
+
min_length: None,
207
+
max_length: None,
208
+
min_graphemes: None,
209
+
max_graphemes: None,
210
+
r#enum: None,
211
+
r#const: None,
212
+
known_values: None,
213
+
}),
214
+
);
215
+
map
216
+
},
217
+
}),
218
+
);
219
+
map.insert(
220
+
::jacquard_common::smol_str::SmolStr::new_static("entry"),
221
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
222
+
description: None,
223
+
required: Some(
224
+
vec![
225
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
226
+
::jacquard_common::smol_str::SmolStr::new_static("node")
227
+
],
228
+
),
229
+
nullable: None,
230
+
properties: {
231
+
#[allow(unused_mut)]
232
+
let mut map = ::std::collections::BTreeMap::new();
233
+
map.insert(
234
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
235
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
236
+
description: None,
237
+
format: None,
238
+
default: None,
239
+
min_length: None,
240
+
max_length: Some(255usize),
241
+
min_graphemes: None,
242
+
max_graphemes: None,
243
+
r#enum: None,
244
+
r#const: None,
245
+
known_values: None,
246
+
}),
247
+
);
248
+
map.insert(
249
+
::jacquard_common::smol_str::SmolStr::new_static("node"),
250
+
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
251
+
description: None,
252
+
refs: vec![
253
+
::jacquard_common::CowStr::new_static("#file"),
254
+
::jacquard_common::CowStr::new_static("#directory"),
255
+
::jacquard_common::CowStr::new_static("#subfs")
256
+
],
257
+
closed: None,
258
+
}),
259
+
);
260
+
map
261
+
},
262
+
}),
263
+
);
264
+
map.insert(
265
+
::jacquard_common::smol_str::SmolStr::new_static("file"),
266
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
267
+
description: None,
268
+
required: Some(
269
+
vec![
270
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
271
+
::jacquard_common::smol_str::SmolStr::new_static("blob")
272
+
],
273
+
),
274
+
nullable: None,
275
+
properties: {
276
+
#[allow(unused_mut)]
277
+
let mut map = ::std::collections::BTreeMap::new();
278
+
map.insert(
279
+
::jacquard_common::smol_str::SmolStr::new_static("base64"),
280
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
281
+
description: None,
282
+
default: None,
283
+
r#const: None,
284
+
}),
285
+
);
286
+
map.insert(
287
+
::jacquard_common::smol_str::SmolStr::new_static("blob"),
288
+
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
289
+
description: None,
290
+
accept: None,
291
+
max_size: None,
292
+
}),
293
+
);
294
+
map.insert(
295
+
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
296
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
297
+
description: Some(
298
+
::jacquard_common::CowStr::new_static(
299
+
"Content encoding (e.g., gzip for compressed files)",
300
+
),
301
+
),
302
+
format: None,
303
+
default: None,
304
+
min_length: None,
305
+
max_length: None,
306
+
min_graphemes: None,
307
+
max_graphemes: None,
308
+
r#enum: None,
309
+
r#const: None,
310
+
known_values: None,
311
+
}),
312
+
);
313
+
map.insert(
314
+
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
315
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
316
+
description: Some(
317
+
::jacquard_common::CowStr::new_static(
318
+
"Original MIME type before compression",
319
+
),
320
+
),
321
+
format: None,
322
+
default: None,
323
+
min_length: None,
324
+
max_length: None,
325
+
min_graphemes: None,
326
+
max_graphemes: None,
327
+
r#enum: None,
328
+
r#const: None,
329
+
known_values: None,
330
+
}),
331
+
);
332
+
map.insert(
333
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
334
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
335
+
description: None,
336
+
format: None,
337
+
default: None,
338
+
min_length: None,
339
+
max_length: None,
340
+
min_graphemes: None,
341
+
max_graphemes: None,
342
+
r#enum: None,
343
+
r#const: None,
344
+
known_values: None,
345
+
}),
346
+
);
347
+
map
348
+
},
349
+
}),
350
+
);
351
+
map.insert(
352
+
::jacquard_common::smol_str::SmolStr::new_static("main"),
353
+
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
354
+
description: Some(
355
+
::jacquard_common::CowStr::new_static(
356
+
"Virtual filesystem manifest for a Wisp site",
357
+
),
358
+
),
359
+
key: None,
360
+
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
361
+
description: None,
362
+
required: Some(
363
+
vec![
364
+
::jacquard_common::smol_str::SmolStr::new_static("site"),
365
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
366
+
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
367
+
],
368
+
),
369
+
nullable: None,
370
+
properties: {
371
+
#[allow(unused_mut)]
372
+
let mut map = ::std::collections::BTreeMap::new();
373
+
map.insert(
374
+
::jacquard_common::smol_str::SmolStr::new_static(
375
+
"createdAt",
376
+
),
377
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
378
+
description: None,
379
+
format: Some(
380
+
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
381
+
),
382
+
default: None,
383
+
min_length: None,
384
+
max_length: None,
385
+
min_graphemes: None,
386
+
max_graphemes: None,
387
+
r#enum: None,
388
+
r#const: None,
389
+
known_values: None,
390
+
}),
391
+
);
392
+
map.insert(
393
+
::jacquard_common::smol_str::SmolStr::new_static(
394
+
"fileCount",
395
+
),
396
+
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
397
+
description: None,
398
+
default: None,
399
+
minimum: Some(0i64),
400
+
maximum: Some(1000i64),
401
+
r#enum: None,
402
+
r#const: None,
403
+
}),
404
+
);
405
+
map.insert(
406
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
407
+
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
408
+
description: None,
409
+
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
410
+
}),
411
+
);
412
+
map.insert(
413
+
::jacquard_common::smol_str::SmolStr::new_static("site"),
414
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
415
+
description: None,
416
+
format: None,
417
+
default: None,
418
+
min_length: None,
419
+
max_length: None,
420
+
min_graphemes: None,
421
+
max_graphemes: None,
422
+
r#enum: None,
423
+
r#const: None,
424
+
known_values: None,
425
+
}),
426
+
);
427
+
map
428
+
},
429
+
}),
430
+
}),
431
+
);
432
+
map.insert(
433
+
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
434
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
435
+
description: None,
436
+
required: Some(
437
+
vec![
438
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
439
+
::jacquard_common::smol_str::SmolStr::new_static("subject")
440
+
],
441
+
),
442
+
nullable: None,
443
+
properties: {
444
+
#[allow(unused_mut)]
445
+
let mut map = ::std::collections::BTreeMap::new();
446
+
map.insert(
447
+
::jacquard_common::smol_str::SmolStr::new_static("flat"),
448
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
449
+
description: None,
450
+
default: None,
451
+
r#const: None,
452
+
}),
453
+
);
454
+
map.insert(
455
+
::jacquard_common::smol_str::SmolStr::new_static("subject"),
456
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
457
+
description: Some(
458
+
::jacquard_common::CowStr::new_static(
459
+
"AT-URI pointing to a place.wisp.subfs record containing this subtree.",
460
+
),
461
+
),
462
+
format: Some(
463
+
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
464
+
),
465
+
default: None,
466
+
min_length: None,
467
+
max_length: None,
468
+
min_graphemes: None,
469
+
max_graphemes: None,
470
+
r#enum: None,
471
+
r#const: None,
472
+
known_values: None,
473
+
}),
474
+
);
475
+
map.insert(
476
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
477
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
478
+
description: None,
479
+
format: None,
480
+
default: None,
481
+
min_length: None,
482
+
max_length: None,
483
+
min_graphemes: None,
484
+
max_graphemes: None,
485
+
r#enum: None,
486
+
r#const: None,
487
+
known_values: None,
488
+
}),
489
+
);
490
+
map
491
+
},
492
+
}),
493
+
);
494
+
map
495
+
},
496
+
}
497
+
}
498
+
499
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
500
+
fn nsid() -> &'static str {
501
+
"place.wisp.fs"
502
+
}
503
+
fn def_name() -> &'static str {
504
+
"directory"
505
+
}
506
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
507
+
lexicon_doc_place_wisp_fs()
508
+
}
509
+
fn validate(
510
+
&self,
511
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
512
+
{
513
+
let value = &self.entries;
514
+
#[allow(unused_comparisons)]
515
+
if value.len() > 500usize {
516
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
517
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
518
+
"entries",
519
+
),
520
+
max: 500usize,
521
+
actual: value.len(),
522
+
});
523
+
}
524
+
}
525
+
Ok(())
526
+
}
527
+
}
528
+
529
+
#[jacquard_derive::lexicon]
530
+
#[derive(
531
+
serde::Serialize,
532
+
serde::Deserialize,
533
+
Debug,
534
+
Clone,
535
+
PartialEq,
536
+
Eq,
537
+
jacquard_derive::IntoStatic
538
+
)]
539
+
#[serde(rename_all = "camelCase")]
540
+
pub struct Entry<'a> {
541
+
#[serde(borrow)]
542
+
pub name: jacquard_common::CowStr<'a>,
543
+
#[serde(borrow)]
544
+
pub node: EntryNode<'a>,
545
+
}
546
+
547
+
pub mod entry_state {
548
+
549
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
550
+
#[allow(unused)]
551
+
use ::core::marker::PhantomData;
552
+
mod sealed {
553
+
pub trait Sealed {}
554
+
}
555
+
/// State trait tracking which required fields have been set
556
+
pub trait State: sealed::Sealed {
557
+
type Node;
558
+
type Name;
559
+
}
560
+
/// Empty state - all required fields are unset
561
+
pub struct Empty(());
562
+
impl sealed::Sealed for Empty {}
563
+
impl State for Empty {
564
+
type Node = Unset;
565
+
type Name = Unset;
566
+
}
567
+
///State transition - sets the `node` field to Set
568
+
pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
569
+
impl<S: State> sealed::Sealed for SetNode<S> {}
570
+
impl<S: State> State for SetNode<S> {
571
+
type Node = Set<members::node>;
572
+
type Name = S::Name;
573
+
}
574
+
///State transition - sets the `name` field to Set
575
+
pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
576
+
impl<S: State> sealed::Sealed for SetName<S> {}
577
+
impl<S: State> State for SetName<S> {
578
+
type Node = S::Node;
579
+
type Name = Set<members::name>;
580
+
}
581
+
/// Marker types for field names
582
+
#[allow(non_camel_case_types)]
583
+
pub mod members {
584
+
///Marker type for the `node` field
585
+
pub struct node(());
586
+
///Marker type for the `name` field
587
+
pub struct name(());
588
+
}
589
+
}
590
+
591
+
/// Builder for constructing an instance of this type
592
+
pub struct EntryBuilder<'a, S: entry_state::State> {
593
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
594
+
__unsafe_private_named: (
595
+
::core::option::Option<jacquard_common::CowStr<'a>>,
596
+
::core::option::Option<EntryNode<'a>>,
597
+
),
598
+
_phantom: ::core::marker::PhantomData<&'a ()>,
599
+
}
600
+
601
+
impl<'a> Entry<'a> {
602
+
/// Create a new builder for this type
603
+
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
604
+
EntryBuilder::new()
605
+
}
606
+
}
607
+
608
+
impl<'a> EntryBuilder<'a, entry_state::Empty> {
609
+
/// Create a new builder with all fields unset
610
+
pub fn new() -> Self {
611
+
EntryBuilder {
612
+
_phantom_state: ::core::marker::PhantomData,
613
+
__unsafe_private_named: (None, None),
614
+
_phantom: ::core::marker::PhantomData,
615
+
}
616
+
}
617
+
}
618
+
619
+
impl<'a, S> EntryBuilder<'a, S>
620
+
where
621
+
S: entry_state::State,
622
+
S::Name: entry_state::IsUnset,
623
+
{
624
+
/// Set the `name` field (required)
625
+
pub fn name(
626
+
mut self,
627
+
value: impl Into<jacquard_common::CowStr<'a>>,
628
+
) -> EntryBuilder<'a, entry_state::SetName<S>> {
629
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
630
+
EntryBuilder {
631
+
_phantom_state: ::core::marker::PhantomData,
632
+
__unsafe_private_named: self.__unsafe_private_named,
633
+
_phantom: ::core::marker::PhantomData,
634
+
}
635
+
}
636
+
}
637
+
638
+
impl<'a, S> EntryBuilder<'a, S>
639
+
where
640
+
S: entry_state::State,
641
+
S::Node: entry_state::IsUnset,
642
+
{
643
+
/// Set the `node` field (required)
644
+
pub fn node(
645
+
mut self,
646
+
value: impl Into<EntryNode<'a>>,
647
+
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
648
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
649
+
EntryBuilder {
650
+
_phantom_state: ::core::marker::PhantomData,
651
+
__unsafe_private_named: self.__unsafe_private_named,
652
+
_phantom: ::core::marker::PhantomData,
653
+
}
654
+
}
655
+
}
656
+
657
+
impl<'a, S> EntryBuilder<'a, S>
658
+
where
659
+
S: entry_state::State,
660
+
S::Node: entry_state::IsSet,
661
+
S::Name: entry_state::IsSet,
662
+
{
663
+
/// Build the final struct
664
+
pub fn build(self) -> Entry<'a> {
665
+
Entry {
666
+
name: self.__unsafe_private_named.0.unwrap(),
667
+
node: self.__unsafe_private_named.1.unwrap(),
668
+
extra_data: Default::default(),
669
+
}
670
+
}
671
+
/// Build the final struct with custom extra_data
672
+
pub fn build_with_data(
673
+
self,
674
+
extra_data: std::collections::BTreeMap<
675
+
jacquard_common::smol_str::SmolStr,
676
+
jacquard_common::types::value::Data<'a>,
677
+
>,
678
+
) -> Entry<'a> {
679
+
Entry {
680
+
name: self.__unsafe_private_named.0.unwrap(),
681
+
node: self.__unsafe_private_named.1.unwrap(),
682
+
extra_data: Some(extra_data),
683
+
}
684
+
}
685
+
}
686
+
687
+
#[jacquard_derive::open_union]
688
+
#[derive(
689
+
serde::Serialize,
690
+
serde::Deserialize,
691
+
Debug,
692
+
Clone,
693
+
PartialEq,
694
+
Eq,
695
+
jacquard_derive::IntoStatic
696
+
)]
697
+
#[serde(tag = "$type")]
698
+
#[serde(bound(deserialize = "'de: 'a"))]
699
+
pub enum EntryNode<'a> {
700
+
#[serde(rename = "place.wisp.fs#file")]
701
+
File(Box<crate::place_wisp::fs::File<'a>>),
702
+
#[serde(rename = "place.wisp.fs#directory")]
703
+
Directory(Box<crate::place_wisp::fs::Directory<'a>>),
704
+
#[serde(rename = "place.wisp.fs#subfs")]
705
+
Subfs(Box<crate::place_wisp::fs::Subfs<'a>>),
706
+
}
707
+
708
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
709
+
fn nsid() -> &'static str {
710
+
"place.wisp.fs"
711
+
}
712
+
fn def_name() -> &'static str {
713
+
"entry"
714
+
}
715
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
716
+
lexicon_doc_place_wisp_fs()
717
+
}
718
+
fn validate(
719
+
&self,
720
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
721
+
{
722
+
let value = &self.name;
723
+
#[allow(unused_comparisons)]
724
+
if <str>::len(value.as_ref()) > 255usize {
725
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
726
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
727
+
"name",
728
+
),
729
+
max: 255usize,
730
+
actual: <str>::len(value.as_ref()),
731
+
});
732
+
}
733
+
}
734
+
Ok(())
735
+
}
736
+
}
737
+
738
+
#[jacquard_derive::lexicon]
739
+
#[derive(
740
+
serde::Serialize,
741
+
serde::Deserialize,
742
+
Debug,
743
+
Clone,
744
+
PartialEq,
745
+
Eq,
746
+
jacquard_derive::IntoStatic
747
+
)]
748
+
#[serde(rename_all = "camelCase")]
749
+
pub struct File<'a> {
750
+
/// True if blob content is base64-encoded (used to bypass PDS content sniffing)
751
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
752
+
pub base64: std::option::Option<bool>,
753
+
/// Content blob ref
754
+
#[serde(borrow)]
755
+
pub blob: jacquard_common::types::blob::BlobRef<'a>,
756
+
/// Content encoding (e.g., gzip for compressed files)
757
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
758
+
#[serde(borrow)]
759
+
pub encoding: std::option::Option<jacquard_common::CowStr<'a>>,
760
+
/// Original MIME type before compression
761
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
762
+
#[serde(borrow)]
763
+
pub mime_type: std::option::Option<jacquard_common::CowStr<'a>>,
764
+
#[serde(borrow)]
765
+
pub r#type: jacquard_common::CowStr<'a>,
766
+
}
767
+
768
+
pub mod file_state {
769
+
770
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
771
+
#[allow(unused)]
772
+
use ::core::marker::PhantomData;
773
+
mod sealed {
774
+
pub trait Sealed {}
775
+
}
776
+
/// State trait tracking which required fields have been set
777
+
pub trait State: sealed::Sealed {
778
+
type Type;
779
+
type Blob;
780
+
}
781
+
/// Empty state - all required fields are unset
782
+
pub struct Empty(());
783
+
impl sealed::Sealed for Empty {}
784
+
impl State for Empty {
785
+
type Type = Unset;
786
+
type Blob = Unset;
787
+
}
788
+
///State transition - sets the `type` field to Set
789
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
790
+
impl<S: State> sealed::Sealed for SetType<S> {}
791
+
impl<S: State> State for SetType<S> {
792
+
type Type = Set<members::r#type>;
793
+
type Blob = S::Blob;
794
+
}
795
+
///State transition - sets the `blob` field to Set
796
+
pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
797
+
impl<S: State> sealed::Sealed for SetBlob<S> {}
798
+
impl<S: State> State for SetBlob<S> {
799
+
type Type = S::Type;
800
+
type Blob = Set<members::blob>;
801
+
}
802
+
/// Marker types for field names
803
+
#[allow(non_camel_case_types)]
804
+
pub mod members {
805
+
///Marker type for the `type` field
806
+
pub struct r#type(());
807
+
///Marker type for the `blob` field
808
+
pub struct blob(());
809
+
}
810
+
}
811
+
812
+
/// Builder for constructing an instance of this type
813
+
pub struct FileBuilder<'a, S: file_state::State> {
814
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
815
+
__unsafe_private_named: (
816
+
::core::option::Option<bool>,
817
+
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
818
+
::core::option::Option<jacquard_common::CowStr<'a>>,
819
+
::core::option::Option<jacquard_common::CowStr<'a>>,
820
+
::core::option::Option<jacquard_common::CowStr<'a>>,
821
+
),
822
+
_phantom: ::core::marker::PhantomData<&'a ()>,
823
+
}
824
+
825
+
impl<'a> File<'a> {
826
+
/// Create a new builder for this type
827
+
pub fn new() -> FileBuilder<'a, file_state::Empty> {
828
+
FileBuilder::new()
829
+
}
830
+
}
831
+
832
+
impl<'a> FileBuilder<'a, file_state::Empty> {
833
+
/// Create a new builder with all fields unset
834
+
pub fn new() -> Self {
835
+
FileBuilder {
836
+
_phantom_state: ::core::marker::PhantomData,
837
+
__unsafe_private_named: (None, None, None, None, None),
838
+
_phantom: ::core::marker::PhantomData,
839
+
}
840
+
}
841
+
}
842
+
843
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
844
+
/// Set the `base64` field (optional)
845
+
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
846
+
self.__unsafe_private_named.0 = value.into();
847
+
self
848
+
}
849
+
/// Set the `base64` field to an Option value (optional)
850
+
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
851
+
self.__unsafe_private_named.0 = value;
852
+
self
853
+
}
854
+
}
855
+
856
+
impl<'a, S> FileBuilder<'a, S>
857
+
where
858
+
S: file_state::State,
859
+
S::Blob: file_state::IsUnset,
860
+
{
861
+
/// Set the `blob` field (required)
862
+
pub fn blob(
863
+
mut self,
864
+
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
865
+
) -> FileBuilder<'a, file_state::SetBlob<S>> {
866
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
867
+
FileBuilder {
868
+
_phantom_state: ::core::marker::PhantomData,
869
+
__unsafe_private_named: self.__unsafe_private_named,
870
+
_phantom: ::core::marker::PhantomData,
871
+
}
872
+
}
873
+
}
874
+
875
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
876
+
/// Set the `encoding` field (optional)
877
+
pub fn encoding(
878
+
mut self,
879
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
880
+
) -> Self {
881
+
self.__unsafe_private_named.2 = value.into();
882
+
self
883
+
}
884
+
/// Set the `encoding` field to an Option value (optional)
885
+
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
886
+
self.__unsafe_private_named.2 = value;
887
+
self
888
+
}
889
+
}
890
+
891
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
892
+
/// Set the `mimeType` field (optional)
893
+
pub fn mime_type(
894
+
mut self,
895
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
896
+
) -> Self {
897
+
self.__unsafe_private_named.3 = value.into();
898
+
self
899
+
}
900
+
/// Set the `mimeType` field to an Option value (optional)
901
+
pub fn maybe_mime_type(
902
+
mut self,
903
+
value: Option<jacquard_common::CowStr<'a>>,
904
+
) -> Self {
905
+
self.__unsafe_private_named.3 = value;
906
+
self
907
+
}
908
+
}
909
+
910
+
impl<'a, S> FileBuilder<'a, S>
911
+
where
912
+
S: file_state::State,
913
+
S::Type: file_state::IsUnset,
914
+
{
915
+
/// Set the `type` field (required)
916
+
pub fn r#type(
917
+
mut self,
918
+
value: impl Into<jacquard_common::CowStr<'a>>,
919
+
) -> FileBuilder<'a, file_state::SetType<S>> {
920
+
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
921
+
FileBuilder {
922
+
_phantom_state: ::core::marker::PhantomData,
923
+
__unsafe_private_named: self.__unsafe_private_named,
924
+
_phantom: ::core::marker::PhantomData,
925
+
}
926
+
}
927
+
}
928
+
929
+
impl<'a, S> FileBuilder<'a, S>
930
+
where
931
+
S: file_state::State,
932
+
S::Type: file_state::IsSet,
933
+
S::Blob: file_state::IsSet,
934
+
{
935
+
/// Build the final struct
936
+
pub fn build(self) -> File<'a> {
937
+
File {
938
+
base64: self.__unsafe_private_named.0,
939
+
blob: self.__unsafe_private_named.1.unwrap(),
940
+
encoding: self.__unsafe_private_named.2,
941
+
mime_type: self.__unsafe_private_named.3,
942
+
r#type: self.__unsafe_private_named.4.unwrap(),
943
+
extra_data: Default::default(),
944
+
}
945
+
}
946
+
/// Build the final struct with custom extra_data
947
+
pub fn build_with_data(
948
+
self,
949
+
extra_data: std::collections::BTreeMap<
950
+
jacquard_common::smol_str::SmolStr,
951
+
jacquard_common::types::value::Data<'a>,
952
+
>,
953
+
) -> File<'a> {
954
+
File {
955
+
base64: self.__unsafe_private_named.0,
956
+
blob: self.__unsafe_private_named.1.unwrap(),
957
+
encoding: self.__unsafe_private_named.2,
958
+
mime_type: self.__unsafe_private_named.3,
959
+
r#type: self.__unsafe_private_named.4.unwrap(),
960
+
extra_data: Some(extra_data),
961
+
}
962
+
}
963
+
}
964
+
965
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
966
+
fn nsid() -> &'static str {
967
+
"place.wisp.fs"
968
+
}
969
+
fn def_name() -> &'static str {
970
+
"file"
971
+
}
972
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
973
+
lexicon_doc_place_wisp_fs()
974
+
}
975
+
fn validate(
976
+
&self,
977
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
978
+
Ok(())
979
+
}
980
+
}
981
+
982
+
/// Virtual filesystem manifest for a Wisp site
983
+
#[jacquard_derive::lexicon]
984
+
#[derive(
985
+
serde::Serialize,
986
+
serde::Deserialize,
987
+
Debug,
988
+
Clone,
989
+
PartialEq,
990
+
Eq,
991
+
jacquard_derive::IntoStatic
992
+
)]
993
+
#[serde(rename_all = "camelCase")]
994
+
pub struct Fs<'a> {
995
+
pub created_at: jacquard_common::types::string::Datetime,
996
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
997
+
pub file_count: std::option::Option<i64>,
998
+
#[serde(borrow)]
999
+
pub root: crate::place_wisp::fs::Directory<'a>,
1000
+
#[serde(borrow)]
1001
+
pub site: jacquard_common::CowStr<'a>,
1002
+
}
1003
+
1004
+
pub mod fs_state {
1005
+
1006
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1007
+
#[allow(unused)]
1008
+
use ::core::marker::PhantomData;
1009
+
mod sealed {
1010
+
pub trait Sealed {}
1011
+
}
1012
+
/// State trait tracking which required fields have been set
1013
+
pub trait State: sealed::Sealed {
1014
+
type CreatedAt;
1015
+
type Site;
1016
+
type Root;
1017
+
}
1018
+
/// Empty state - all required fields are unset
1019
+
pub struct Empty(());
1020
+
impl sealed::Sealed for Empty {}
1021
+
impl State for Empty {
1022
+
type CreatedAt = Unset;
1023
+
type Site = Unset;
1024
+
type Root = Unset;
1025
+
}
1026
+
///State transition - sets the `created_at` field to Set
1027
+
pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
1028
+
impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
1029
+
impl<S: State> State for SetCreatedAt<S> {
1030
+
type CreatedAt = Set<members::created_at>;
1031
+
type Site = S::Site;
1032
+
type Root = S::Root;
1033
+
}
1034
+
///State transition - sets the `site` field to Set
1035
+
pub struct SetSite<S: State = Empty>(PhantomData<fn() -> S>);
1036
+
impl<S: State> sealed::Sealed for SetSite<S> {}
1037
+
impl<S: State> State for SetSite<S> {
1038
+
type CreatedAt = S::CreatedAt;
1039
+
type Site = Set<members::site>;
1040
+
type Root = S::Root;
1041
+
}
1042
+
///State transition - sets the `root` field to Set
1043
+
pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
1044
+
impl<S: State> sealed::Sealed for SetRoot<S> {}
1045
+
impl<S: State> State for SetRoot<S> {
1046
+
type CreatedAt = S::CreatedAt;
1047
+
type Site = S::Site;
1048
+
type Root = Set<members::root>;
1049
+
}
1050
+
/// Marker types for field names
1051
+
#[allow(non_camel_case_types)]
1052
+
pub mod members {
1053
+
///Marker type for the `created_at` field
1054
+
pub struct created_at(());
1055
+
///Marker type for the `site` field
1056
+
pub struct site(());
1057
+
///Marker type for the `root` field
1058
+
pub struct root(());
1059
+
}
1060
+
}
1061
+
1062
+
/// Builder for constructing an instance of this type
1063
+
pub struct FsBuilder<'a, S: fs_state::State> {
1064
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1065
+
__unsafe_private_named: (
1066
+
::core::option::Option<jacquard_common::types::string::Datetime>,
1067
+
::core::option::Option<i64>,
1068
+
::core::option::Option<crate::place_wisp::fs::Directory<'a>>,
1069
+
::core::option::Option<jacquard_common::CowStr<'a>>,
1070
+
),
1071
+
_phantom: ::core::marker::PhantomData<&'a ()>,
1072
+
}
1073
+
1074
+
impl<'a> Fs<'a> {
1075
+
/// Create a new builder for this type
1076
+
pub fn new() -> FsBuilder<'a, fs_state::Empty> {
1077
+
FsBuilder::new()
1078
+
}
1079
+
}
1080
+
1081
+
impl<'a> FsBuilder<'a, fs_state::Empty> {
1082
+
/// Create a new builder with all fields unset
1083
+
pub fn new() -> Self {
1084
+
FsBuilder {
1085
+
_phantom_state: ::core::marker::PhantomData,
1086
+
__unsafe_private_named: (None, None, None, None),
1087
+
_phantom: ::core::marker::PhantomData,
1088
+
}
1089
+
}
1090
+
}
1091
+
1092
+
impl<'a, S> FsBuilder<'a, S>
1093
+
where
1094
+
S: fs_state::State,
1095
+
S::CreatedAt: fs_state::IsUnset,
1096
+
{
1097
+
/// Set the `createdAt` field (required)
1098
+
pub fn created_at(
1099
+
mut self,
1100
+
value: impl Into<jacquard_common::types::string::Datetime>,
1101
+
) -> FsBuilder<'a, fs_state::SetCreatedAt<S>> {
1102
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1103
+
FsBuilder {
1104
+
_phantom_state: ::core::marker::PhantomData,
1105
+
__unsafe_private_named: self.__unsafe_private_named,
1106
+
_phantom: ::core::marker::PhantomData,
1107
+
}
1108
+
}
1109
+
}
1110
+
1111
+
impl<'a, S: fs_state::State> FsBuilder<'a, S> {
1112
+
/// Set the `fileCount` field (optional)
1113
+
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
1114
+
self.__unsafe_private_named.1 = value.into();
1115
+
self
1116
+
}
1117
+
/// Set the `fileCount` field to an Option value (optional)
1118
+
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
1119
+
self.__unsafe_private_named.1 = value;
1120
+
self
1121
+
}
1122
+
}
1123
+
1124
+
impl<'a, S> FsBuilder<'a, S>
1125
+
where
1126
+
S: fs_state::State,
1127
+
S::Root: fs_state::IsUnset,
1128
+
{
1129
+
/// Set the `root` field (required)
1130
+
pub fn root(
1131
+
mut self,
1132
+
value: impl Into<crate::place_wisp::fs::Directory<'a>>,
1133
+
) -> FsBuilder<'a, fs_state::SetRoot<S>> {
1134
+
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1135
+
FsBuilder {
1136
+
_phantom_state: ::core::marker::PhantomData,
1137
+
__unsafe_private_named: self.__unsafe_private_named,
1138
+
_phantom: ::core::marker::PhantomData,
1139
+
}
1140
+
}
1141
+
}
1142
+
1143
+
impl<'a, S> FsBuilder<'a, S>
1144
+
where
1145
+
S: fs_state::State,
1146
+
S::Site: fs_state::IsUnset,
1147
+
{
1148
+
/// Set the `site` field (required)
1149
+
pub fn site(
1150
+
mut self,
1151
+
value: impl Into<jacquard_common::CowStr<'a>>,
1152
+
) -> FsBuilder<'a, fs_state::SetSite<S>> {
1153
+
self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into());
1154
+
FsBuilder {
1155
+
_phantom_state: ::core::marker::PhantomData,
1156
+
__unsafe_private_named: self.__unsafe_private_named,
1157
+
_phantom: ::core::marker::PhantomData,
1158
+
}
1159
+
}
1160
+
}
1161
+
1162
+
impl<'a, S> FsBuilder<'a, S>
1163
+
where
1164
+
S: fs_state::State,
1165
+
S::CreatedAt: fs_state::IsSet,
1166
+
S::Site: fs_state::IsSet,
1167
+
S::Root: fs_state::IsSet,
1168
+
{
1169
+
/// Build the final struct
1170
+
pub fn build(self) -> Fs<'a> {
1171
+
Fs {
1172
+
created_at: self.__unsafe_private_named.0.unwrap(),
1173
+
file_count: self.__unsafe_private_named.1,
1174
+
root: self.__unsafe_private_named.2.unwrap(),
1175
+
site: self.__unsafe_private_named.3.unwrap(),
1176
+
extra_data: Default::default(),
1177
+
}
1178
+
}
1179
+
/// Build the final struct with custom extra_data
1180
+
pub fn build_with_data(
1181
+
self,
1182
+
extra_data: std::collections::BTreeMap<
1183
+
jacquard_common::smol_str::SmolStr,
1184
+
jacquard_common::types::value::Data<'a>,
1185
+
>,
1186
+
) -> Fs<'a> {
1187
+
Fs {
1188
+
created_at: self.__unsafe_private_named.0.unwrap(),
1189
+
file_count: self.__unsafe_private_named.1,
1190
+
root: self.__unsafe_private_named.2.unwrap(),
1191
+
site: self.__unsafe_private_named.3.unwrap(),
1192
+
extra_data: Some(extra_data),
1193
+
}
1194
+
}
1195
+
}
1196
+
1197
+
impl<'a> Fs<'a> {
1198
+
pub fn uri(
1199
+
uri: impl Into<jacquard_common::CowStr<'a>>,
1200
+
) -> Result<
1201
+
jacquard_common::types::uri::RecordUri<'a, FsRecord>,
1202
+
jacquard_common::types::uri::UriError,
1203
+
> {
1204
+
jacquard_common::types::uri::RecordUri::try_from_uri(
1205
+
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
1206
+
)
1207
+
}
1208
+
}
1209
+
1210
+
/// Typed wrapper for GetRecord response with this collection's record type.
1211
+
#[derive(
1212
+
serde::Serialize,
1213
+
serde::Deserialize,
1214
+
Debug,
1215
+
Clone,
1216
+
PartialEq,
1217
+
Eq,
1218
+
jacquard_derive::IntoStatic
1219
+
)]
1220
+
#[serde(rename_all = "camelCase")]
1221
+
pub struct FsGetRecordOutput<'a> {
1222
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
1223
+
#[serde(borrow)]
1224
+
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
1225
+
#[serde(borrow)]
1226
+
pub uri: jacquard_common::types::string::AtUri<'a>,
1227
+
#[serde(borrow)]
1228
+
pub value: Fs<'a>,
1229
+
}
1230
+
1231
+
impl From<FsGetRecordOutput<'_>> for Fs<'_> {
1232
+
fn from(output: FsGetRecordOutput<'_>) -> Self {
1233
+
use jacquard_common::IntoStatic;
1234
+
output.value.into_static()
1235
+
}
1236
+
}
1237
+
1238
+
impl jacquard_common::types::collection::Collection for Fs<'_> {
1239
+
const NSID: &'static str = "place.wisp.fs";
1240
+
type Record = FsRecord;
1241
+
}
1242
+
1243
+
/// Marker type for deserializing records from this collection.
1244
+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
1245
+
pub struct FsRecord;
1246
+
impl jacquard_common::xrpc::XrpcResp for FsRecord {
1247
+
const NSID: &'static str = "place.wisp.fs";
1248
+
const ENCODING: &'static str = "application/json";
1249
+
type Output<'de> = FsGetRecordOutput<'de>;
1250
+
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
1251
+
}
1252
+
1253
+
impl jacquard_common::types::collection::Collection for FsRecord {
1254
+
const NSID: &'static str = "place.wisp.fs";
1255
+
type Record = FsRecord;
1256
+
}
1257
+
1258
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Fs<'a> {
1259
+
fn nsid() -> &'static str {
1260
+
"place.wisp.fs"
1261
+
}
1262
+
fn def_name() -> &'static str {
1263
+
"main"
1264
+
}
1265
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1266
+
lexicon_doc_place_wisp_fs()
1267
+
}
1268
+
fn validate(
1269
+
&self,
1270
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1271
+
if let Some(ref value) = self.file_count {
1272
+
if *value > 1000i64 {
1273
+
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
1274
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1275
+
"file_count",
1276
+
),
1277
+
max: 1000i64,
1278
+
actual: *value,
1279
+
});
1280
+
}
1281
+
}
1282
+
if let Some(ref value) = self.file_count {
1283
+
if *value < 0i64 {
1284
+
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
1285
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1286
+
"file_count",
1287
+
),
1288
+
min: 0i64,
1289
+
actual: *value,
1290
+
});
1291
+
}
1292
+
}
1293
+
Ok(())
1294
+
}
1295
+
}
1296
+
1297
+
#[jacquard_derive::lexicon]
1298
+
#[derive(
1299
+
serde::Serialize,
1300
+
serde::Deserialize,
1301
+
Debug,
1302
+
Clone,
1303
+
PartialEq,
1304
+
Eq,
1305
+
jacquard_derive::IntoStatic
1306
+
)]
1307
+
#[serde(rename_all = "camelCase")]
1308
+
pub struct Subfs<'a> {
1309
+
/// If true (default), the subfs record's root entries are merged (flattened) into the parent directory, replacing the subfs entry. If false, the subfs entries are placed in a subdirectory with the subfs entry's name. Flat merging is useful for splitting large directories across multiple records while maintaining a flat structure.
1310
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
1311
+
pub flat: std::option::Option<bool>,
1312
+
/// AT-URI pointing to a place.wisp.subfs record containing this subtree.
1313
+
#[serde(borrow)]
1314
+
pub subject: jacquard_common::types::string::AtUri<'a>,
1315
+
#[serde(borrow)]
1316
+
pub r#type: jacquard_common::CowStr<'a>,
1317
+
}
1318
+
1319
+
pub mod subfs_state {
1320
+
1321
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1322
+
#[allow(unused)]
1323
+
use ::core::marker::PhantomData;
1324
+
mod sealed {
1325
+
pub trait Sealed {}
1326
+
}
1327
+
/// State trait tracking which required fields have been set
1328
+
pub trait State: sealed::Sealed {
1329
+
type Type;
1330
+
type Subject;
1331
+
}
1332
+
/// Empty state - all required fields are unset
1333
+
pub struct Empty(());
1334
+
impl sealed::Sealed for Empty {}
1335
+
impl State for Empty {
1336
+
type Type = Unset;
1337
+
type Subject = Unset;
1338
+
}
1339
+
///State transition - sets the `type` field to Set
1340
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
1341
+
impl<S: State> sealed::Sealed for SetType<S> {}
1342
+
impl<S: State> State for SetType<S> {
1343
+
type Type = Set<members::r#type>;
1344
+
type Subject = S::Subject;
1345
+
}
1346
+
///State transition - sets the `subject` field to Set
1347
+
pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
1348
+
impl<S: State> sealed::Sealed for SetSubject<S> {}
1349
+
impl<S: State> State for SetSubject<S> {
1350
+
type Type = S::Type;
1351
+
type Subject = Set<members::subject>;
1352
+
}
1353
+
/// Marker types for field names
1354
+
#[allow(non_camel_case_types)]
1355
+
pub mod members {
1356
+
///Marker type for the `type` field
1357
+
pub struct r#type(());
1358
+
///Marker type for the `subject` field
1359
+
pub struct subject(());
1360
+
}
1361
+
}
1362
+
1363
+
/// Builder for constructing an instance of this type
1364
+
pub struct SubfsBuilder<'a, S: subfs_state::State> {
1365
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1366
+
__unsafe_private_named: (
1367
+
::core::option::Option<bool>,
1368
+
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
1369
+
::core::option::Option<jacquard_common::CowStr<'a>>,
1370
+
),
1371
+
_phantom: ::core::marker::PhantomData<&'a ()>,
1372
+
}
1373
+
1374
+
impl<'a> Subfs<'a> {
1375
+
/// Create a new builder for this type
1376
+
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
1377
+
SubfsBuilder::new()
1378
+
}
1379
+
}
1380
+
1381
+
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
1382
+
/// Create a new builder with all fields unset
1383
+
pub fn new() -> Self {
1384
+
SubfsBuilder {
1385
+
_phantom_state: ::core::marker::PhantomData,
1386
+
__unsafe_private_named: (None, None, None),
1387
+
_phantom: ::core::marker::PhantomData,
1388
+
}
1389
+
}
1390
+
}
1391
+
1392
+
impl<'a, S: subfs_state::State> SubfsBuilder<'a, S> {
1393
+
/// Set the `flat` field (optional)
1394
+
pub fn flat(mut self, value: impl Into<Option<bool>>) -> Self {
1395
+
self.__unsafe_private_named.0 = value.into();
1396
+
self
1397
+
}
1398
+
/// Set the `flat` field to an Option value (optional)
1399
+
pub fn maybe_flat(mut self, value: Option<bool>) -> Self {
1400
+
self.__unsafe_private_named.0 = value;
1401
+
self
1402
+
}
1403
+
}
1404
+
1405
+
impl<'a, S> SubfsBuilder<'a, S>
1406
+
where
1407
+
S: subfs_state::State,
1408
+
S::Subject: subfs_state::IsUnset,
1409
+
{
1410
+
/// Set the `subject` field (required)
1411
+
pub fn subject(
1412
+
mut self,
1413
+
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
1414
+
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
1415
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
1416
+
SubfsBuilder {
1417
+
_phantom_state: ::core::marker::PhantomData,
1418
+
__unsafe_private_named: self.__unsafe_private_named,
1419
+
_phantom: ::core::marker::PhantomData,
1420
+
}
1421
+
}
1422
+
}
1423
+
1424
+
impl<'a, S> SubfsBuilder<'a, S>
1425
+
where
1426
+
S: subfs_state::State,
1427
+
S::Type: subfs_state::IsUnset,
1428
+
{
1429
+
/// Set the `type` field (required)
1430
+
pub fn r#type(
1431
+
mut self,
1432
+
value: impl Into<jacquard_common::CowStr<'a>>,
1433
+
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
1434
+
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1435
+
SubfsBuilder {
1436
+
_phantom_state: ::core::marker::PhantomData,
1437
+
__unsafe_private_named: self.__unsafe_private_named,
1438
+
_phantom: ::core::marker::PhantomData,
1439
+
}
1440
+
}
1441
+
}
1442
+
1443
+
impl<'a, S> SubfsBuilder<'a, S>
1444
+
where
1445
+
S: subfs_state::State,
1446
+
S::Type: subfs_state::IsSet,
1447
+
S::Subject: subfs_state::IsSet,
1448
+
{
1449
+
/// Build the final struct
1450
+
pub fn build(self) -> Subfs<'a> {
1451
+
Subfs {
1452
+
flat: self.__unsafe_private_named.0,
1453
+
subject: self.__unsafe_private_named.1.unwrap(),
1454
+
r#type: self.__unsafe_private_named.2.unwrap(),
1455
+
extra_data: Default::default(),
1456
+
}
1457
+
}
1458
+
/// Build the final struct with custom extra_data
1459
+
pub fn build_with_data(
1460
+
self,
1461
+
extra_data: std::collections::BTreeMap<
1462
+
jacquard_common::smol_str::SmolStr,
1463
+
jacquard_common::types::value::Data<'a>,
1464
+
>,
1465
+
) -> Subfs<'a> {
1466
+
Subfs {
1467
+
flat: self.__unsafe_private_named.0,
1468
+
subject: self.__unsafe_private_named.1.unwrap(),
1469
+
r#type: self.__unsafe_private_named.2.unwrap(),
1470
+
extra_data: Some(extra_data),
1471
+
}
1472
+
}
1473
+
}
1474
+
1475
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
1476
+
fn nsid() -> &'static str {
1477
+
"place.wisp.fs"
1478
+
}
1479
+
fn def_name() -> &'static str {
1480
+
"subfs"
1481
+
}
1482
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1483
+
lexicon_doc_place_wisp_fs()
1484
+
}
1485
+
fn validate(
1486
+
&self,
1487
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1488
+
Ok(())
1489
+
}
1490
+
}
+653
cli/crates/lexicons/src/place_wisp/settings.rs
+653
cli/crates/lexicons/src/place_wisp/settings.rs
···
1
+
// @generated by jacquard-lexicon. DO NOT EDIT.
2
+
//
3
+
// Lexicon: place.wisp.settings
4
+
//
5
+
// This file was automatically generated from Lexicon schemas.
6
+
// Any manual changes will be overwritten on the next regeneration.
7
+
8
+
/// Custom HTTP header configuration
9
+
#[jacquard_derive::lexicon]
10
+
#[derive(
11
+
serde::Serialize,
12
+
serde::Deserialize,
13
+
Debug,
14
+
Clone,
15
+
PartialEq,
16
+
Eq,
17
+
jacquard_derive::IntoStatic,
18
+
Default
19
+
)]
20
+
#[serde(rename_all = "camelCase")]
21
+
pub struct CustomHeader<'a> {
22
+
/// HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')
23
+
#[serde(borrow)]
24
+
pub name: jacquard_common::CowStr<'a>,
25
+
/// Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.
26
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
27
+
#[serde(borrow)]
28
+
pub path: std::option::Option<jacquard_common::CowStr<'a>>,
29
+
/// HTTP header value
30
+
#[serde(borrow)]
31
+
pub value: jacquard_common::CowStr<'a>,
32
+
}
33
+
34
+
fn lexicon_doc_place_wisp_settings() -> ::jacquard_lexicon::lexicon::LexiconDoc<
35
+
'static,
36
+
> {
37
+
::jacquard_lexicon::lexicon::LexiconDoc {
38
+
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
39
+
id: ::jacquard_common::CowStr::new_static("place.wisp.settings"),
40
+
revision: None,
41
+
description: None,
42
+
defs: {
43
+
let mut map = ::std::collections::BTreeMap::new();
44
+
map.insert(
45
+
::jacquard_common::smol_str::SmolStr::new_static("customHeader"),
46
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
47
+
description: Some(
48
+
::jacquard_common::CowStr::new_static(
49
+
"Custom HTTP header configuration",
50
+
),
51
+
),
52
+
required: Some(
53
+
vec![
54
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
55
+
::jacquard_common::smol_str::SmolStr::new_static("value")
56
+
],
57
+
),
58
+
nullable: None,
59
+
properties: {
60
+
#[allow(unused_mut)]
61
+
let mut map = ::std::collections::BTreeMap::new();
62
+
map.insert(
63
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
64
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
65
+
description: Some(
66
+
::jacquard_common::CowStr::new_static(
67
+
"HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
68
+
),
69
+
),
70
+
format: None,
71
+
default: None,
72
+
min_length: None,
73
+
max_length: Some(100usize),
74
+
min_graphemes: None,
75
+
max_graphemes: None,
76
+
r#enum: None,
77
+
r#const: None,
78
+
known_values: None,
79
+
}),
80
+
);
81
+
map.insert(
82
+
::jacquard_common::smol_str::SmolStr::new_static("path"),
83
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
84
+
description: Some(
85
+
::jacquard_common::CowStr::new_static(
86
+
"Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
87
+
),
88
+
),
89
+
format: None,
90
+
default: None,
91
+
min_length: None,
92
+
max_length: Some(500usize),
93
+
min_graphemes: None,
94
+
max_graphemes: None,
95
+
r#enum: None,
96
+
r#const: None,
97
+
known_values: None,
98
+
}),
99
+
);
100
+
map.insert(
101
+
::jacquard_common::smol_str::SmolStr::new_static("value"),
102
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
103
+
description: Some(
104
+
::jacquard_common::CowStr::new_static("HTTP header value"),
105
+
),
106
+
format: None,
107
+
default: None,
108
+
min_length: None,
109
+
max_length: Some(1000usize),
110
+
min_graphemes: None,
111
+
max_graphemes: None,
112
+
r#enum: None,
113
+
r#const: None,
114
+
known_values: None,
115
+
}),
116
+
);
117
+
map
118
+
},
119
+
}),
120
+
);
121
+
map.insert(
122
+
::jacquard_common::smol_str::SmolStr::new_static("main"),
123
+
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
124
+
description: Some(
125
+
::jacquard_common::CowStr::new_static(
126
+
"Configuration settings for a static site hosted on wisp.place",
127
+
),
128
+
),
129
+
key: Some(::jacquard_common::CowStr::new_static("any")),
130
+
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
131
+
description: None,
132
+
required: None,
133
+
nullable: None,
134
+
properties: {
135
+
#[allow(unused_mut)]
136
+
let mut map = ::std::collections::BTreeMap::new();
137
+
map.insert(
138
+
::jacquard_common::smol_str::SmolStr::new_static(
139
+
"cleanUrls",
140
+
),
141
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
142
+
description: None,
143
+
default: None,
144
+
r#const: None,
145
+
}),
146
+
);
147
+
map.insert(
148
+
::jacquard_common::smol_str::SmolStr::new_static(
149
+
"custom404",
150
+
),
151
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
152
+
description: Some(
153
+
::jacquard_common::CowStr::new_static(
154
+
"Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
155
+
),
156
+
),
157
+
format: None,
158
+
default: None,
159
+
min_length: None,
160
+
max_length: Some(500usize),
161
+
min_graphemes: None,
162
+
max_graphemes: None,
163
+
r#enum: None,
164
+
r#const: None,
165
+
known_values: None,
166
+
}),
167
+
);
168
+
map.insert(
169
+
::jacquard_common::smol_str::SmolStr::new_static(
170
+
"directoryListing",
171
+
),
172
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
173
+
description: None,
174
+
default: None,
175
+
r#const: None,
176
+
}),
177
+
);
178
+
map.insert(
179
+
::jacquard_common::smol_str::SmolStr::new_static("headers"),
180
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
181
+
description: Some(
182
+
::jacquard_common::CowStr::new_static(
183
+
"Custom HTTP headers to set on responses",
184
+
),
185
+
),
186
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
187
+
description: None,
188
+
r#ref: ::jacquard_common::CowStr::new_static(
189
+
"#customHeader",
190
+
),
191
+
}),
192
+
min_length: None,
193
+
max_length: Some(50usize),
194
+
}),
195
+
);
196
+
map.insert(
197
+
::jacquard_common::smol_str::SmolStr::new_static(
198
+
"indexFiles",
199
+
),
200
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
201
+
description: Some(
202
+
::jacquard_common::CowStr::new_static(
203
+
"Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
204
+
),
205
+
),
206
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::String(::jacquard_lexicon::lexicon::LexString {
207
+
description: None,
208
+
format: None,
209
+
default: None,
210
+
min_length: None,
211
+
max_length: Some(255usize),
212
+
min_graphemes: None,
213
+
max_graphemes: None,
214
+
r#enum: None,
215
+
r#const: None,
216
+
known_values: None,
217
+
}),
218
+
min_length: None,
219
+
max_length: Some(10usize),
220
+
}),
221
+
);
222
+
map.insert(
223
+
::jacquard_common::smol_str::SmolStr::new_static("spaMode"),
224
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
225
+
description: Some(
226
+
::jacquard_common::CowStr::new_static(
227
+
"File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.",
228
+
),
229
+
),
230
+
format: None,
231
+
default: None,
232
+
min_length: None,
233
+
max_length: Some(500usize),
234
+
min_graphemes: None,
235
+
max_graphemes: None,
236
+
r#enum: None,
237
+
r#const: None,
238
+
known_values: None,
239
+
}),
240
+
);
241
+
map
242
+
},
243
+
}),
244
+
}),
245
+
);
246
+
map
247
+
},
248
+
}
249
+
}
250
+
251
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for CustomHeader<'a> {
252
+
fn nsid() -> &'static str {
253
+
"place.wisp.settings"
254
+
}
255
+
fn def_name() -> &'static str {
256
+
"customHeader"
257
+
}
258
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
259
+
lexicon_doc_place_wisp_settings()
260
+
}
261
+
fn validate(
262
+
&self,
263
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
264
+
{
265
+
let value = &self.name;
266
+
#[allow(unused_comparisons)]
267
+
if <str>::len(value.as_ref()) > 100usize {
268
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
269
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
270
+
"name",
271
+
),
272
+
max: 100usize,
273
+
actual: <str>::len(value.as_ref()),
274
+
});
275
+
}
276
+
}
277
+
if let Some(ref value) = self.path {
278
+
#[allow(unused_comparisons)]
279
+
if <str>::len(value.as_ref()) > 500usize {
280
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
281
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
282
+
"path",
283
+
),
284
+
max: 500usize,
285
+
actual: <str>::len(value.as_ref()),
286
+
});
287
+
}
288
+
}
289
+
{
290
+
let value = &self.value;
291
+
#[allow(unused_comparisons)]
292
+
if <str>::len(value.as_ref()) > 1000usize {
293
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
294
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
295
+
"value",
296
+
),
297
+
max: 1000usize,
298
+
actual: <str>::len(value.as_ref()),
299
+
});
300
+
}
301
+
}
302
+
Ok(())
303
+
}
304
+
}
305
+
306
+
/// Configuration settings for a static site hosted on wisp.place
307
+
#[jacquard_derive::lexicon]
308
+
#[derive(
309
+
serde::Serialize,
310
+
serde::Deserialize,
311
+
Debug,
312
+
Clone,
313
+
PartialEq,
314
+
Eq,
315
+
jacquard_derive::IntoStatic
316
+
)]
317
+
#[serde(rename_all = "camelCase")]
318
+
pub struct Settings<'a> {
319
+
/// Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.
320
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
321
+
pub clean_urls: std::option::Option<bool>,
322
+
/// Custom 404 error page file path. Incompatible with directoryListing and spaMode.
323
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
324
+
#[serde(borrow)]
325
+
pub custom404: std::option::Option<jacquard_common::CowStr<'a>>,
326
+
/// Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.
327
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
328
+
pub directory_listing: std::option::Option<bool>,
329
+
/// Custom HTTP headers to set on responses
330
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
331
+
#[serde(borrow)]
332
+
pub headers: std::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
333
+
/// Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.
334
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
335
+
#[serde(borrow)]
336
+
pub index_files: std::option::Option<Vec<jacquard_common::CowStr<'a>>>,
337
+
/// File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.
338
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
339
+
#[serde(borrow)]
340
+
pub spa_mode: std::option::Option<jacquard_common::CowStr<'a>>,
341
+
}
342
+
343
+
pub mod settings_state {
344
+
345
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
346
+
#[allow(unused)]
347
+
use ::core::marker::PhantomData;
348
+
mod sealed {
349
+
pub trait Sealed {}
350
+
}
351
+
/// State trait tracking which required fields have been set
352
+
pub trait State: sealed::Sealed {}
353
+
/// Empty state - all required fields are unset
354
+
pub struct Empty(());
355
+
impl sealed::Sealed for Empty {}
356
+
impl State for Empty {}
357
+
/// Marker types for field names
358
+
#[allow(non_camel_case_types)]
359
+
pub mod members {}
360
+
}
361
+
362
+
/// Builder for constructing an instance of this type
363
+
pub struct SettingsBuilder<'a, S: settings_state::State> {
364
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
365
+
__unsafe_private_named: (
366
+
::core::option::Option<bool>,
367
+
::core::option::Option<jacquard_common::CowStr<'a>>,
368
+
::core::option::Option<bool>,
369
+
::core::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
370
+
::core::option::Option<Vec<jacquard_common::CowStr<'a>>>,
371
+
::core::option::Option<jacquard_common::CowStr<'a>>,
372
+
),
373
+
_phantom: ::core::marker::PhantomData<&'a ()>,
374
+
}
375
+
376
+
impl<'a> Settings<'a> {
377
+
/// Create a new builder for this type
378
+
pub fn new() -> SettingsBuilder<'a, settings_state::Empty> {
379
+
SettingsBuilder::new()
380
+
}
381
+
}
382
+
383
+
impl<'a> SettingsBuilder<'a, settings_state::Empty> {
384
+
/// Create a new builder with all fields unset
385
+
pub fn new() -> Self {
386
+
SettingsBuilder {
387
+
_phantom_state: ::core::marker::PhantomData,
388
+
__unsafe_private_named: (None, None, None, None, None, None),
389
+
_phantom: ::core::marker::PhantomData,
390
+
}
391
+
}
392
+
}
393
+
394
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
395
+
/// Set the `cleanUrls` field (optional)
396
+
pub fn clean_urls(mut self, value: impl Into<Option<bool>>) -> Self {
397
+
self.__unsafe_private_named.0 = value.into();
398
+
self
399
+
}
400
+
/// Set the `cleanUrls` field to an Option value (optional)
401
+
pub fn maybe_clean_urls(mut self, value: Option<bool>) -> Self {
402
+
self.__unsafe_private_named.0 = value;
403
+
self
404
+
}
405
+
}
406
+
407
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
408
+
/// Set the `custom404` field (optional)
409
+
pub fn custom404(
410
+
mut self,
411
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
412
+
) -> Self {
413
+
self.__unsafe_private_named.1 = value.into();
414
+
self
415
+
}
416
+
/// Set the `custom404` field to an Option value (optional)
417
+
pub fn maybe_custom404(
418
+
mut self,
419
+
value: Option<jacquard_common::CowStr<'a>>,
420
+
) -> Self {
421
+
self.__unsafe_private_named.1 = value;
422
+
self
423
+
}
424
+
}
425
+
426
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
427
+
/// Set the `directoryListing` field (optional)
428
+
pub fn directory_listing(mut self, value: impl Into<Option<bool>>) -> Self {
429
+
self.__unsafe_private_named.2 = value.into();
430
+
self
431
+
}
432
+
/// Set the `directoryListing` field to an Option value (optional)
433
+
pub fn maybe_directory_listing(mut self, value: Option<bool>) -> Self {
434
+
self.__unsafe_private_named.2 = value;
435
+
self
436
+
}
437
+
}
438
+
439
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
440
+
/// Set the `headers` field (optional)
441
+
pub fn headers(
442
+
mut self,
443
+
value: impl Into<Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>>,
444
+
) -> Self {
445
+
self.__unsafe_private_named.3 = value.into();
446
+
self
447
+
}
448
+
/// Set the `headers` field to an Option value (optional)
449
+
pub fn maybe_headers(
450
+
mut self,
451
+
value: Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
452
+
) -> Self {
453
+
self.__unsafe_private_named.3 = value;
454
+
self
455
+
}
456
+
}
457
+
458
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
459
+
/// Set the `indexFiles` field (optional)
460
+
pub fn index_files(
461
+
mut self,
462
+
value: impl Into<Option<Vec<jacquard_common::CowStr<'a>>>>,
463
+
) -> Self {
464
+
self.__unsafe_private_named.4 = value.into();
465
+
self
466
+
}
467
+
/// Set the `indexFiles` field to an Option value (optional)
468
+
pub fn maybe_index_files(
469
+
mut self,
470
+
value: Option<Vec<jacquard_common::CowStr<'a>>>,
471
+
) -> Self {
472
+
self.__unsafe_private_named.4 = value;
473
+
self
474
+
}
475
+
}
476
+
477
+
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
478
+
/// Set the `spaMode` field (optional)
479
+
pub fn spa_mode(
480
+
mut self,
481
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
482
+
) -> Self {
483
+
self.__unsafe_private_named.5 = value.into();
484
+
self
485
+
}
486
+
/// Set the `spaMode` field to an Option value (optional)
487
+
pub fn maybe_spa_mode(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
488
+
self.__unsafe_private_named.5 = value;
489
+
self
490
+
}
491
+
}
492
+
493
+
impl<'a, S> SettingsBuilder<'a, S>
494
+
where
495
+
S: settings_state::State,
496
+
{
497
+
/// Build the final struct
498
+
pub fn build(self) -> Settings<'a> {
499
+
Settings {
500
+
clean_urls: self.__unsafe_private_named.0,
501
+
custom404: self.__unsafe_private_named.1,
502
+
directory_listing: self.__unsafe_private_named.2,
503
+
headers: self.__unsafe_private_named.3,
504
+
index_files: self.__unsafe_private_named.4,
505
+
spa_mode: self.__unsafe_private_named.5,
506
+
extra_data: Default::default(),
507
+
}
508
+
}
509
+
/// Build the final struct with custom extra_data
510
+
pub fn build_with_data(
511
+
self,
512
+
extra_data: std::collections::BTreeMap<
513
+
jacquard_common::smol_str::SmolStr,
514
+
jacquard_common::types::value::Data<'a>,
515
+
>,
516
+
) -> Settings<'a> {
517
+
Settings {
518
+
clean_urls: self.__unsafe_private_named.0,
519
+
custom404: self.__unsafe_private_named.1,
520
+
directory_listing: self.__unsafe_private_named.2,
521
+
headers: self.__unsafe_private_named.3,
522
+
index_files: self.__unsafe_private_named.4,
523
+
spa_mode: self.__unsafe_private_named.5,
524
+
extra_data: Some(extra_data),
525
+
}
526
+
}
527
+
}
528
+
529
+
impl<'a> Settings<'a> {
530
+
pub fn uri(
531
+
uri: impl Into<jacquard_common::CowStr<'a>>,
532
+
) -> Result<
533
+
jacquard_common::types::uri::RecordUri<'a, SettingsRecord>,
534
+
jacquard_common::types::uri::UriError,
535
+
> {
536
+
jacquard_common::types::uri::RecordUri::try_from_uri(
537
+
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
538
+
)
539
+
}
540
+
}
541
+
542
+
/// Typed wrapper for GetRecord response with this collection's record type.
543
+
#[derive(
544
+
serde::Serialize,
545
+
serde::Deserialize,
546
+
Debug,
547
+
Clone,
548
+
PartialEq,
549
+
Eq,
550
+
jacquard_derive::IntoStatic
551
+
)]
552
+
#[serde(rename_all = "camelCase")]
553
+
pub struct SettingsGetRecordOutput<'a> {
554
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
555
+
#[serde(borrow)]
556
+
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
557
+
#[serde(borrow)]
558
+
pub uri: jacquard_common::types::string::AtUri<'a>,
559
+
#[serde(borrow)]
560
+
pub value: Settings<'a>,
561
+
}
562
+
563
+
impl From<SettingsGetRecordOutput<'_>> for Settings<'_> {
564
+
fn from(output: SettingsGetRecordOutput<'_>) -> Self {
565
+
use jacquard_common::IntoStatic;
566
+
output.value.into_static()
567
+
}
568
+
}
569
+
570
+
impl jacquard_common::types::collection::Collection for Settings<'_> {
571
+
const NSID: &'static str = "place.wisp.settings";
572
+
type Record = SettingsRecord;
573
+
}
574
+
575
+
/// Marker type for deserializing records from this collection.
576
+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
577
+
pub struct SettingsRecord;
578
+
impl jacquard_common::xrpc::XrpcResp for SettingsRecord {
579
+
const NSID: &'static str = "place.wisp.settings";
580
+
const ENCODING: &'static str = "application/json";
581
+
type Output<'de> = SettingsGetRecordOutput<'de>;
582
+
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
583
+
}
584
+
585
+
impl jacquard_common::types::collection::Collection for SettingsRecord {
586
+
const NSID: &'static str = "place.wisp.settings";
587
+
type Record = SettingsRecord;
588
+
}
589
+
590
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Settings<'a> {
591
+
fn nsid() -> &'static str {
592
+
"place.wisp.settings"
593
+
}
594
+
fn def_name() -> &'static str {
595
+
"main"
596
+
}
597
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
598
+
lexicon_doc_place_wisp_settings()
599
+
}
600
+
fn validate(
601
+
&self,
602
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
603
+
if let Some(ref value) = self.custom404 {
604
+
#[allow(unused_comparisons)]
605
+
if <str>::len(value.as_ref()) > 500usize {
606
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
607
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
608
+
"custom404",
609
+
),
610
+
max: 500usize,
611
+
actual: <str>::len(value.as_ref()),
612
+
});
613
+
}
614
+
}
615
+
if let Some(ref value) = self.headers {
616
+
#[allow(unused_comparisons)]
617
+
if value.len() > 50usize {
618
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
619
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
620
+
"headers",
621
+
),
622
+
max: 50usize,
623
+
actual: value.len(),
624
+
});
625
+
}
626
+
}
627
+
if let Some(ref value) = self.index_files {
628
+
#[allow(unused_comparisons)]
629
+
if value.len() > 10usize {
630
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
631
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
632
+
"index_files",
633
+
),
634
+
max: 10usize,
635
+
actual: value.len(),
636
+
});
637
+
}
638
+
}
639
+
if let Some(ref value) = self.spa_mode {
640
+
#[allow(unused_comparisons)]
641
+
if <str>::len(value.as_ref()) > 500usize {
642
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
643
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
644
+
"spa_mode",
645
+
),
646
+
max: 500usize,
647
+
actual: <str>::len(value.as_ref()),
648
+
});
649
+
}
650
+
}
651
+
Ok(())
652
+
}
653
+
}
+1408
cli/crates/lexicons/src/place_wisp/subfs.rs
+1408
cli/crates/lexicons/src/place_wisp/subfs.rs
···
1
+
// @generated by jacquard-lexicon. DO NOT EDIT.
2
+
//
3
+
// Lexicon: place.wisp.subfs
4
+
//
5
+
// This file was automatically generated from Lexicon schemas.
6
+
// Any manual changes will be overwritten on the next regeneration.
7
+
8
+
#[jacquard_derive::lexicon]
9
+
#[derive(
10
+
serde::Serialize,
11
+
serde::Deserialize,
12
+
Debug,
13
+
Clone,
14
+
PartialEq,
15
+
Eq,
16
+
jacquard_derive::IntoStatic
17
+
)]
18
+
#[serde(rename_all = "camelCase")]
19
+
pub struct Directory<'a> {
20
+
#[serde(borrow)]
21
+
pub entries: Vec<crate::place_wisp::subfs::Entry<'a>>,
22
+
#[serde(borrow)]
23
+
pub r#type: jacquard_common::CowStr<'a>,
24
+
}
25
+
26
+
pub mod directory_state {
27
+
28
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
29
+
#[allow(unused)]
30
+
use ::core::marker::PhantomData;
31
+
mod sealed {
32
+
pub trait Sealed {}
33
+
}
34
+
/// State trait tracking which required fields have been set
35
+
pub trait State: sealed::Sealed {
36
+
type Type;
37
+
type Entries;
38
+
}
39
+
/// Empty state - all required fields are unset
40
+
pub struct Empty(());
41
+
impl sealed::Sealed for Empty {}
42
+
impl State for Empty {
43
+
type Type = Unset;
44
+
type Entries = Unset;
45
+
}
46
+
///State transition - sets the `type` field to Set
47
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
48
+
impl<S: State> sealed::Sealed for SetType<S> {}
49
+
impl<S: State> State for SetType<S> {
50
+
type Type = Set<members::r#type>;
51
+
type Entries = S::Entries;
52
+
}
53
+
///State transition - sets the `entries` field to Set
54
+
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
55
+
impl<S: State> sealed::Sealed for SetEntries<S> {}
56
+
impl<S: State> State for SetEntries<S> {
57
+
type Type = S::Type;
58
+
type Entries = Set<members::entries>;
59
+
}
60
+
/// Marker types for field names
61
+
#[allow(non_camel_case_types)]
62
+
pub mod members {
63
+
///Marker type for the `type` field
64
+
pub struct r#type(());
65
+
///Marker type for the `entries` field
66
+
pub struct entries(());
67
+
}
68
+
}
69
+
70
+
/// Builder for constructing an instance of this type
71
+
pub struct DirectoryBuilder<'a, S: directory_state::State> {
72
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
73
+
__unsafe_private_named: (
74
+
::core::option::Option<Vec<crate::place_wisp::subfs::Entry<'a>>>,
75
+
::core::option::Option<jacquard_common::CowStr<'a>>,
76
+
),
77
+
_phantom: ::core::marker::PhantomData<&'a ()>,
78
+
}
79
+
80
+
impl<'a> Directory<'a> {
81
+
/// Create a new builder for this type
82
+
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
83
+
DirectoryBuilder::new()
84
+
}
85
+
}
86
+
87
+
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
88
+
/// Create a new builder with all fields unset
89
+
pub fn new() -> Self {
90
+
DirectoryBuilder {
91
+
_phantom_state: ::core::marker::PhantomData,
92
+
__unsafe_private_named: (None, None),
93
+
_phantom: ::core::marker::PhantomData,
94
+
}
95
+
}
96
+
}
97
+
98
+
impl<'a, S> DirectoryBuilder<'a, S>
99
+
where
100
+
S: directory_state::State,
101
+
S::Entries: directory_state::IsUnset,
102
+
{
103
+
/// Set the `entries` field (required)
104
+
pub fn entries(
105
+
mut self,
106
+
value: impl Into<Vec<crate::place_wisp::subfs::Entry<'a>>>,
107
+
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
108
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
109
+
DirectoryBuilder {
110
+
_phantom_state: ::core::marker::PhantomData,
111
+
__unsafe_private_named: self.__unsafe_private_named,
112
+
_phantom: ::core::marker::PhantomData,
113
+
}
114
+
}
115
+
}
116
+
117
+
impl<'a, S> DirectoryBuilder<'a, S>
118
+
where
119
+
S: directory_state::State,
120
+
S::Type: directory_state::IsUnset,
121
+
{
122
+
/// Set the `type` field (required)
123
+
pub fn r#type(
124
+
mut self,
125
+
value: impl Into<jacquard_common::CowStr<'a>>,
126
+
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
127
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
128
+
DirectoryBuilder {
129
+
_phantom_state: ::core::marker::PhantomData,
130
+
__unsafe_private_named: self.__unsafe_private_named,
131
+
_phantom: ::core::marker::PhantomData,
132
+
}
133
+
}
134
+
}
135
+
136
+
impl<'a, S> DirectoryBuilder<'a, S>
137
+
where
138
+
S: directory_state::State,
139
+
S::Type: directory_state::IsSet,
140
+
S::Entries: directory_state::IsSet,
141
+
{
142
+
/// Build the final struct
143
+
pub fn build(self) -> Directory<'a> {
144
+
Directory {
145
+
entries: self.__unsafe_private_named.0.unwrap(),
146
+
r#type: self.__unsafe_private_named.1.unwrap(),
147
+
extra_data: Default::default(),
148
+
}
149
+
}
150
+
/// Build the final struct with custom extra_data
151
+
pub fn build_with_data(
152
+
self,
153
+
extra_data: std::collections::BTreeMap<
154
+
jacquard_common::smol_str::SmolStr,
155
+
jacquard_common::types::value::Data<'a>,
156
+
>,
157
+
) -> Directory<'a> {
158
+
Directory {
159
+
entries: self.__unsafe_private_named.0.unwrap(),
160
+
r#type: self.__unsafe_private_named.1.unwrap(),
161
+
extra_data: Some(extra_data),
162
+
}
163
+
}
164
+
}
165
+
166
+
fn lexicon_doc_place_wisp_subfs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
167
+
::jacquard_lexicon::lexicon::LexiconDoc {
168
+
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
169
+
id: ::jacquard_common::CowStr::new_static("place.wisp.subfs"),
170
+
revision: None,
171
+
description: None,
172
+
defs: {
173
+
let mut map = ::std::collections::BTreeMap::new();
174
+
map.insert(
175
+
::jacquard_common::smol_str::SmolStr::new_static("directory"),
176
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
177
+
description: None,
178
+
required: Some(
179
+
vec![
180
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
181
+
::jacquard_common::smol_str::SmolStr::new_static("entries")
182
+
],
183
+
),
184
+
nullable: None,
185
+
properties: {
186
+
#[allow(unused_mut)]
187
+
let mut map = ::std::collections::BTreeMap::new();
188
+
map.insert(
189
+
::jacquard_common::smol_str::SmolStr::new_static("entries"),
190
+
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
191
+
description: None,
192
+
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
193
+
description: None,
194
+
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
195
+
}),
196
+
min_length: None,
197
+
max_length: Some(500usize),
198
+
}),
199
+
);
200
+
map.insert(
201
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
202
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
203
+
description: None,
204
+
format: None,
205
+
default: None,
206
+
min_length: None,
207
+
max_length: None,
208
+
min_graphemes: None,
209
+
max_graphemes: None,
210
+
r#enum: None,
211
+
r#const: None,
212
+
known_values: None,
213
+
}),
214
+
);
215
+
map
216
+
},
217
+
}),
218
+
);
219
+
map.insert(
220
+
::jacquard_common::smol_str::SmolStr::new_static("entry"),
221
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
222
+
description: None,
223
+
required: Some(
224
+
vec![
225
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
226
+
::jacquard_common::smol_str::SmolStr::new_static("node")
227
+
],
228
+
),
229
+
nullable: None,
230
+
properties: {
231
+
#[allow(unused_mut)]
232
+
let mut map = ::std::collections::BTreeMap::new();
233
+
map.insert(
234
+
::jacquard_common::smol_str::SmolStr::new_static("name"),
235
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
236
+
description: None,
237
+
format: None,
238
+
default: None,
239
+
min_length: None,
240
+
max_length: Some(255usize),
241
+
min_graphemes: None,
242
+
max_graphemes: None,
243
+
r#enum: None,
244
+
r#const: None,
245
+
known_values: None,
246
+
}),
247
+
);
248
+
map.insert(
249
+
::jacquard_common::smol_str::SmolStr::new_static("node"),
250
+
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
251
+
description: None,
252
+
refs: vec![
253
+
::jacquard_common::CowStr::new_static("#file"),
254
+
::jacquard_common::CowStr::new_static("#directory"),
255
+
::jacquard_common::CowStr::new_static("#subfs")
256
+
],
257
+
closed: None,
258
+
}),
259
+
);
260
+
map
261
+
},
262
+
}),
263
+
);
264
+
map.insert(
265
+
::jacquard_common::smol_str::SmolStr::new_static("file"),
266
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
267
+
description: None,
268
+
required: Some(
269
+
vec![
270
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
271
+
::jacquard_common::smol_str::SmolStr::new_static("blob")
272
+
],
273
+
),
274
+
nullable: None,
275
+
properties: {
276
+
#[allow(unused_mut)]
277
+
let mut map = ::std::collections::BTreeMap::new();
278
+
map.insert(
279
+
::jacquard_common::smol_str::SmolStr::new_static("base64"),
280
+
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
281
+
description: None,
282
+
default: None,
283
+
r#const: None,
284
+
}),
285
+
);
286
+
map.insert(
287
+
::jacquard_common::smol_str::SmolStr::new_static("blob"),
288
+
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
289
+
description: None,
290
+
accept: None,
291
+
max_size: None,
292
+
}),
293
+
);
294
+
map.insert(
295
+
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
296
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
297
+
description: Some(
298
+
::jacquard_common::CowStr::new_static(
299
+
"Content encoding (e.g., gzip for compressed files)",
300
+
),
301
+
),
302
+
format: None,
303
+
default: None,
304
+
min_length: None,
305
+
max_length: None,
306
+
min_graphemes: None,
307
+
max_graphemes: None,
308
+
r#enum: None,
309
+
r#const: None,
310
+
known_values: None,
311
+
}),
312
+
);
313
+
map.insert(
314
+
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
315
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
316
+
description: Some(
317
+
::jacquard_common::CowStr::new_static(
318
+
"Original MIME type before compression",
319
+
),
320
+
),
321
+
format: None,
322
+
default: None,
323
+
min_length: None,
324
+
max_length: None,
325
+
min_graphemes: None,
326
+
max_graphemes: None,
327
+
r#enum: None,
328
+
r#const: None,
329
+
known_values: None,
330
+
}),
331
+
);
332
+
map.insert(
333
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
334
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
335
+
description: None,
336
+
format: None,
337
+
default: None,
338
+
min_length: None,
339
+
max_length: None,
340
+
min_graphemes: None,
341
+
max_graphemes: None,
342
+
r#enum: None,
343
+
r#const: None,
344
+
known_values: None,
345
+
}),
346
+
);
347
+
map
348
+
},
349
+
}),
350
+
);
351
+
map.insert(
352
+
::jacquard_common::smol_str::SmolStr::new_static("main"),
353
+
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
354
+
description: Some(
355
+
::jacquard_common::CowStr::new_static(
356
+
"Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.",
357
+
),
358
+
),
359
+
key: None,
360
+
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
361
+
description: None,
362
+
required: Some(
363
+
vec![
364
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
365
+
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
366
+
],
367
+
),
368
+
nullable: None,
369
+
properties: {
370
+
#[allow(unused_mut)]
371
+
let mut map = ::std::collections::BTreeMap::new();
372
+
map.insert(
373
+
::jacquard_common::smol_str::SmolStr::new_static(
374
+
"createdAt",
375
+
),
376
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
377
+
description: None,
378
+
format: Some(
379
+
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
380
+
),
381
+
default: None,
382
+
min_length: None,
383
+
max_length: None,
384
+
min_graphemes: None,
385
+
max_graphemes: None,
386
+
r#enum: None,
387
+
r#const: None,
388
+
known_values: None,
389
+
}),
390
+
);
391
+
map.insert(
392
+
::jacquard_common::smol_str::SmolStr::new_static(
393
+
"fileCount",
394
+
),
395
+
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
396
+
description: None,
397
+
default: None,
398
+
minimum: Some(0i64),
399
+
maximum: Some(1000i64),
400
+
r#enum: None,
401
+
r#const: None,
402
+
}),
403
+
);
404
+
map.insert(
405
+
::jacquard_common::smol_str::SmolStr::new_static("root"),
406
+
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
407
+
description: None,
408
+
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
409
+
}),
410
+
);
411
+
map
412
+
},
413
+
}),
414
+
}),
415
+
);
416
+
map.insert(
417
+
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
418
+
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
419
+
description: None,
420
+
required: Some(
421
+
vec![
422
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
423
+
::jacquard_common::smol_str::SmolStr::new_static("subject")
424
+
],
425
+
),
426
+
nullable: None,
427
+
properties: {
428
+
#[allow(unused_mut)]
429
+
let mut map = ::std::collections::BTreeMap::new();
430
+
map.insert(
431
+
::jacquard_common::smol_str::SmolStr::new_static("subject"),
432
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
433
+
description: Some(
434
+
::jacquard_common::CowStr::new_static(
435
+
"AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures.",
436
+
),
437
+
),
438
+
format: Some(
439
+
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
440
+
),
441
+
default: None,
442
+
min_length: None,
443
+
max_length: None,
444
+
min_graphemes: None,
445
+
max_graphemes: None,
446
+
r#enum: None,
447
+
r#const: None,
448
+
known_values: None,
449
+
}),
450
+
);
451
+
map.insert(
452
+
::jacquard_common::smol_str::SmolStr::new_static("type"),
453
+
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
454
+
description: None,
455
+
format: None,
456
+
default: None,
457
+
min_length: None,
458
+
max_length: None,
459
+
min_graphemes: None,
460
+
max_graphemes: None,
461
+
r#enum: None,
462
+
r#const: None,
463
+
known_values: None,
464
+
}),
465
+
);
466
+
map
467
+
},
468
+
}),
469
+
);
470
+
map
471
+
},
472
+
}
473
+
}
474
+
475
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
476
+
fn nsid() -> &'static str {
477
+
"place.wisp.subfs"
478
+
}
479
+
fn def_name() -> &'static str {
480
+
"directory"
481
+
}
482
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
483
+
lexicon_doc_place_wisp_subfs()
484
+
}
485
+
fn validate(
486
+
&self,
487
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
488
+
{
489
+
let value = &self.entries;
490
+
#[allow(unused_comparisons)]
491
+
if value.len() > 500usize {
492
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
493
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
494
+
"entries",
495
+
),
496
+
max: 500usize,
497
+
actual: value.len(),
498
+
});
499
+
}
500
+
}
501
+
Ok(())
502
+
}
503
+
}
504
+
505
+
#[jacquard_derive::lexicon]
506
+
#[derive(
507
+
serde::Serialize,
508
+
serde::Deserialize,
509
+
Debug,
510
+
Clone,
511
+
PartialEq,
512
+
Eq,
513
+
jacquard_derive::IntoStatic
514
+
)]
515
+
#[serde(rename_all = "camelCase")]
516
+
pub struct Entry<'a> {
517
+
#[serde(borrow)]
518
+
pub name: jacquard_common::CowStr<'a>,
519
+
#[serde(borrow)]
520
+
pub node: EntryNode<'a>,
521
+
}
522
+
523
+
pub mod entry_state {
524
+
525
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
526
+
#[allow(unused)]
527
+
use ::core::marker::PhantomData;
528
+
mod sealed {
529
+
pub trait Sealed {}
530
+
}
531
+
/// State trait tracking which required fields have been set
532
+
pub trait State: sealed::Sealed {
533
+
type Name;
534
+
type Node;
535
+
}
536
+
/// Empty state - all required fields are unset
537
+
pub struct Empty(());
538
+
impl sealed::Sealed for Empty {}
539
+
impl State for Empty {
540
+
type Name = Unset;
541
+
type Node = Unset;
542
+
}
543
+
///State transition - sets the `name` field to Set
544
+
pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
545
+
impl<S: State> sealed::Sealed for SetName<S> {}
546
+
impl<S: State> State for SetName<S> {
547
+
type Name = Set<members::name>;
548
+
type Node = S::Node;
549
+
}
550
+
///State transition - sets the `node` field to Set
551
+
pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
552
+
impl<S: State> sealed::Sealed for SetNode<S> {}
553
+
impl<S: State> State for SetNode<S> {
554
+
type Name = S::Name;
555
+
type Node = Set<members::node>;
556
+
}
557
+
/// Marker types for field names
558
+
#[allow(non_camel_case_types)]
559
+
pub mod members {
560
+
///Marker type for the `name` field
561
+
pub struct name(());
562
+
///Marker type for the `node` field
563
+
pub struct node(());
564
+
}
565
+
}
566
+
567
+
/// Builder for constructing an instance of this type
568
+
pub struct EntryBuilder<'a, S: entry_state::State> {
569
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
570
+
__unsafe_private_named: (
571
+
::core::option::Option<jacquard_common::CowStr<'a>>,
572
+
::core::option::Option<EntryNode<'a>>,
573
+
),
574
+
_phantom: ::core::marker::PhantomData<&'a ()>,
575
+
}
576
+
577
+
impl<'a> Entry<'a> {
578
+
/// Create a new builder for this type
579
+
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
580
+
EntryBuilder::new()
581
+
}
582
+
}
583
+
584
+
impl<'a> EntryBuilder<'a, entry_state::Empty> {
585
+
/// Create a new builder with all fields unset
586
+
pub fn new() -> Self {
587
+
EntryBuilder {
588
+
_phantom_state: ::core::marker::PhantomData,
589
+
__unsafe_private_named: (None, None),
590
+
_phantom: ::core::marker::PhantomData,
591
+
}
592
+
}
593
+
}
594
+
595
+
impl<'a, S> EntryBuilder<'a, S>
596
+
where
597
+
S: entry_state::State,
598
+
S::Name: entry_state::IsUnset,
599
+
{
600
+
/// Set the `name` field (required)
601
+
pub fn name(
602
+
mut self,
603
+
value: impl Into<jacquard_common::CowStr<'a>>,
604
+
) -> EntryBuilder<'a, entry_state::SetName<S>> {
605
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
606
+
EntryBuilder {
607
+
_phantom_state: ::core::marker::PhantomData,
608
+
__unsafe_private_named: self.__unsafe_private_named,
609
+
_phantom: ::core::marker::PhantomData,
610
+
}
611
+
}
612
+
}
613
+
614
+
impl<'a, S> EntryBuilder<'a, S>
615
+
where
616
+
S: entry_state::State,
617
+
S::Node: entry_state::IsUnset,
618
+
{
619
+
/// Set the `node` field (required)
620
+
pub fn node(
621
+
mut self,
622
+
value: impl Into<EntryNode<'a>>,
623
+
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
624
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
625
+
EntryBuilder {
626
+
_phantom_state: ::core::marker::PhantomData,
627
+
__unsafe_private_named: self.__unsafe_private_named,
628
+
_phantom: ::core::marker::PhantomData,
629
+
}
630
+
}
631
+
}
632
+
633
+
impl<'a, S> EntryBuilder<'a, S>
634
+
where
635
+
S: entry_state::State,
636
+
S::Name: entry_state::IsSet,
637
+
S::Node: entry_state::IsSet,
638
+
{
639
+
/// Build the final struct
640
+
pub fn build(self) -> Entry<'a> {
641
+
Entry {
642
+
name: self.__unsafe_private_named.0.unwrap(),
643
+
node: self.__unsafe_private_named.1.unwrap(),
644
+
extra_data: Default::default(),
645
+
}
646
+
}
647
+
/// Build the final struct with custom extra_data
648
+
pub fn build_with_data(
649
+
self,
650
+
extra_data: std::collections::BTreeMap<
651
+
jacquard_common::smol_str::SmolStr,
652
+
jacquard_common::types::value::Data<'a>,
653
+
>,
654
+
) -> Entry<'a> {
655
+
Entry {
656
+
name: self.__unsafe_private_named.0.unwrap(),
657
+
node: self.__unsafe_private_named.1.unwrap(),
658
+
extra_data: Some(extra_data),
659
+
}
660
+
}
661
+
}
662
+
663
+
#[jacquard_derive::open_union]
664
+
#[derive(
665
+
serde::Serialize,
666
+
serde::Deserialize,
667
+
Debug,
668
+
Clone,
669
+
PartialEq,
670
+
Eq,
671
+
jacquard_derive::IntoStatic
672
+
)]
673
+
#[serde(tag = "$type")]
674
+
#[serde(bound(deserialize = "'de: 'a"))]
675
+
pub enum EntryNode<'a> {
676
+
#[serde(rename = "place.wisp.subfs#file")]
677
+
File(Box<crate::place_wisp::subfs::File<'a>>),
678
+
#[serde(rename = "place.wisp.subfs#directory")]
679
+
Directory(Box<crate::place_wisp::subfs::Directory<'a>>),
680
+
#[serde(rename = "place.wisp.subfs#subfs")]
681
+
Subfs(Box<crate::place_wisp::subfs::Subfs<'a>>),
682
+
}
683
+
684
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
685
+
fn nsid() -> &'static str {
686
+
"place.wisp.subfs"
687
+
}
688
+
fn def_name() -> &'static str {
689
+
"entry"
690
+
}
691
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
692
+
lexicon_doc_place_wisp_subfs()
693
+
}
694
+
fn validate(
695
+
&self,
696
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
697
+
{
698
+
let value = &self.name;
699
+
#[allow(unused_comparisons)]
700
+
if <str>::len(value.as_ref()) > 255usize {
701
+
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
702
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
703
+
"name",
704
+
),
705
+
max: 255usize,
706
+
actual: <str>::len(value.as_ref()),
707
+
});
708
+
}
709
+
}
710
+
Ok(())
711
+
}
712
+
}
713
+
714
+
#[jacquard_derive::lexicon]
715
+
#[derive(
716
+
serde::Serialize,
717
+
serde::Deserialize,
718
+
Debug,
719
+
Clone,
720
+
PartialEq,
721
+
Eq,
722
+
jacquard_derive::IntoStatic
723
+
)]
724
+
#[serde(rename_all = "camelCase")]
725
+
pub struct File<'a> {
726
+
/// True if blob content is base64-encoded (used to bypass PDS content sniffing)
727
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
728
+
pub base64: std::option::Option<bool>,
729
+
/// Content blob ref
730
+
#[serde(borrow)]
731
+
pub blob: jacquard_common::types::blob::BlobRef<'a>,
732
+
/// Content encoding (e.g., gzip for compressed files)
733
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
734
+
#[serde(borrow)]
735
+
pub encoding: std::option::Option<jacquard_common::CowStr<'a>>,
736
+
/// Original MIME type before compression
737
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
738
+
#[serde(borrow)]
739
+
pub mime_type: std::option::Option<jacquard_common::CowStr<'a>>,
740
+
#[serde(borrow)]
741
+
pub r#type: jacquard_common::CowStr<'a>,
742
+
}
743
+
744
+
pub mod file_state {
745
+
746
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
747
+
#[allow(unused)]
748
+
use ::core::marker::PhantomData;
749
+
mod sealed {
750
+
pub trait Sealed {}
751
+
}
752
+
/// State trait tracking which required fields have been set
753
+
pub trait State: sealed::Sealed {
754
+
type Blob;
755
+
type Type;
756
+
}
757
+
/// Empty state - all required fields are unset
758
+
pub struct Empty(());
759
+
impl sealed::Sealed for Empty {}
760
+
impl State for Empty {
761
+
type Blob = Unset;
762
+
type Type = Unset;
763
+
}
764
+
///State transition - sets the `blob` field to Set
765
+
pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
766
+
impl<S: State> sealed::Sealed for SetBlob<S> {}
767
+
impl<S: State> State for SetBlob<S> {
768
+
type Blob = Set<members::blob>;
769
+
type Type = S::Type;
770
+
}
771
+
///State transition - sets the `type` field to Set
772
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
773
+
impl<S: State> sealed::Sealed for SetType<S> {}
774
+
impl<S: State> State for SetType<S> {
775
+
type Blob = S::Blob;
776
+
type Type = Set<members::r#type>;
777
+
}
778
+
/// Marker types for field names
779
+
#[allow(non_camel_case_types)]
780
+
pub mod members {
781
+
///Marker type for the `blob` field
782
+
pub struct blob(());
783
+
///Marker type for the `type` field
784
+
pub struct r#type(());
785
+
}
786
+
}
787
+
788
+
/// Builder for constructing an instance of this type
789
+
pub struct FileBuilder<'a, S: file_state::State> {
790
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
791
+
__unsafe_private_named: (
792
+
::core::option::Option<bool>,
793
+
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
794
+
::core::option::Option<jacquard_common::CowStr<'a>>,
795
+
::core::option::Option<jacquard_common::CowStr<'a>>,
796
+
::core::option::Option<jacquard_common::CowStr<'a>>,
797
+
),
798
+
_phantom: ::core::marker::PhantomData<&'a ()>,
799
+
}
800
+
801
+
impl<'a> File<'a> {
802
+
/// Create a new builder for this type
803
+
pub fn new() -> FileBuilder<'a, file_state::Empty> {
804
+
FileBuilder::new()
805
+
}
806
+
}
807
+
808
+
impl<'a> FileBuilder<'a, file_state::Empty> {
809
+
/// Create a new builder with all fields unset
810
+
pub fn new() -> Self {
811
+
FileBuilder {
812
+
_phantom_state: ::core::marker::PhantomData,
813
+
__unsafe_private_named: (None, None, None, None, None),
814
+
_phantom: ::core::marker::PhantomData,
815
+
}
816
+
}
817
+
}
818
+
819
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
820
+
/// Set the `base64` field (optional)
821
+
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
822
+
self.__unsafe_private_named.0 = value.into();
823
+
self
824
+
}
825
+
/// Set the `base64` field to an Option value (optional)
826
+
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
827
+
self.__unsafe_private_named.0 = value;
828
+
self
829
+
}
830
+
}
831
+
832
+
impl<'a, S> FileBuilder<'a, S>
833
+
where
834
+
S: file_state::State,
835
+
S::Blob: file_state::IsUnset,
836
+
{
837
+
/// Set the `blob` field (required)
838
+
pub fn blob(
839
+
mut self,
840
+
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
841
+
) -> FileBuilder<'a, file_state::SetBlob<S>> {
842
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
843
+
FileBuilder {
844
+
_phantom_state: ::core::marker::PhantomData,
845
+
__unsafe_private_named: self.__unsafe_private_named,
846
+
_phantom: ::core::marker::PhantomData,
847
+
}
848
+
}
849
+
}
850
+
851
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
852
+
/// Set the `encoding` field (optional)
853
+
pub fn encoding(
854
+
mut self,
855
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
856
+
) -> Self {
857
+
self.__unsafe_private_named.2 = value.into();
858
+
self
859
+
}
860
+
/// Set the `encoding` field to an Option value (optional)
861
+
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
862
+
self.__unsafe_private_named.2 = value;
863
+
self
864
+
}
865
+
}
866
+
867
+
impl<'a, S: file_state::State> FileBuilder<'a, S> {
868
+
/// Set the `mimeType` field (optional)
869
+
pub fn mime_type(
870
+
mut self,
871
+
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
872
+
) -> Self {
873
+
self.__unsafe_private_named.3 = value.into();
874
+
self
875
+
}
876
+
/// Set the `mimeType` field to an Option value (optional)
877
+
pub fn maybe_mime_type(
878
+
mut self,
879
+
value: Option<jacquard_common::CowStr<'a>>,
880
+
) -> Self {
881
+
self.__unsafe_private_named.3 = value;
882
+
self
883
+
}
884
+
}
885
+
886
+
impl<'a, S> FileBuilder<'a, S>
887
+
where
888
+
S: file_state::State,
889
+
S::Type: file_state::IsUnset,
890
+
{
891
+
/// Set the `type` field (required)
892
+
pub fn r#type(
893
+
mut self,
894
+
value: impl Into<jacquard_common::CowStr<'a>>,
895
+
) -> FileBuilder<'a, file_state::SetType<S>> {
896
+
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
897
+
FileBuilder {
898
+
_phantom_state: ::core::marker::PhantomData,
899
+
__unsafe_private_named: self.__unsafe_private_named,
900
+
_phantom: ::core::marker::PhantomData,
901
+
}
902
+
}
903
+
}
904
+
905
+
impl<'a, S> FileBuilder<'a, S>
906
+
where
907
+
S: file_state::State,
908
+
S::Blob: file_state::IsSet,
909
+
S::Type: file_state::IsSet,
910
+
{
911
+
/// Build the final struct
912
+
pub fn build(self) -> File<'a> {
913
+
File {
914
+
base64: self.__unsafe_private_named.0,
915
+
blob: self.__unsafe_private_named.1.unwrap(),
916
+
encoding: self.__unsafe_private_named.2,
917
+
mime_type: self.__unsafe_private_named.3,
918
+
r#type: self.__unsafe_private_named.4.unwrap(),
919
+
extra_data: Default::default(),
920
+
}
921
+
}
922
+
/// Build the final struct with custom extra_data
923
+
pub fn build_with_data(
924
+
self,
925
+
extra_data: std::collections::BTreeMap<
926
+
jacquard_common::smol_str::SmolStr,
927
+
jacquard_common::types::value::Data<'a>,
928
+
>,
929
+
) -> File<'a> {
930
+
File {
931
+
base64: self.__unsafe_private_named.0,
932
+
blob: self.__unsafe_private_named.1.unwrap(),
933
+
encoding: self.__unsafe_private_named.2,
934
+
mime_type: self.__unsafe_private_named.3,
935
+
r#type: self.__unsafe_private_named.4.unwrap(),
936
+
extra_data: Some(extra_data),
937
+
}
938
+
}
939
+
}
940
+
941
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
942
+
fn nsid() -> &'static str {
943
+
"place.wisp.subfs"
944
+
}
945
+
fn def_name() -> &'static str {
946
+
"file"
947
+
}
948
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
949
+
lexicon_doc_place_wisp_subfs()
950
+
}
951
+
fn validate(
952
+
&self,
953
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
954
+
Ok(())
955
+
}
956
+
}
957
+
958
+
/// Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.
959
+
#[jacquard_derive::lexicon]
960
+
#[derive(
961
+
serde::Serialize,
962
+
serde::Deserialize,
963
+
Debug,
964
+
Clone,
965
+
PartialEq,
966
+
Eq,
967
+
jacquard_derive::IntoStatic
968
+
)]
969
+
#[serde(rename_all = "camelCase")]
970
+
pub struct SubfsRecord<'a> {
971
+
pub created_at: jacquard_common::types::string::Datetime,
972
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
973
+
pub file_count: std::option::Option<i64>,
974
+
#[serde(borrow)]
975
+
pub root: crate::place_wisp::subfs::Directory<'a>,
976
+
}
977
+
978
+
pub mod subfs_record_state {
979
+
980
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
981
+
#[allow(unused)]
982
+
use ::core::marker::PhantomData;
983
+
mod sealed {
984
+
pub trait Sealed {}
985
+
}
986
+
/// State trait tracking which required fields have been set
987
+
pub trait State: sealed::Sealed {
988
+
type Root;
989
+
type CreatedAt;
990
+
}
991
+
/// Empty state - all required fields are unset
992
+
pub struct Empty(());
993
+
impl sealed::Sealed for Empty {}
994
+
impl State for Empty {
995
+
type Root = Unset;
996
+
type CreatedAt = Unset;
997
+
}
998
+
///State transition - sets the `root` field to Set
999
+
pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
1000
+
impl<S: State> sealed::Sealed for SetRoot<S> {}
1001
+
impl<S: State> State for SetRoot<S> {
1002
+
type Root = Set<members::root>;
1003
+
type CreatedAt = S::CreatedAt;
1004
+
}
1005
+
///State transition - sets the `created_at` field to Set
1006
+
pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
1007
+
impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
1008
+
impl<S: State> State for SetCreatedAt<S> {
1009
+
type Root = S::Root;
1010
+
type CreatedAt = Set<members::created_at>;
1011
+
}
1012
+
/// Marker types for field names
1013
+
#[allow(non_camel_case_types)]
1014
+
pub mod members {
1015
+
///Marker type for the `root` field
1016
+
pub struct root(());
1017
+
///Marker type for the `created_at` field
1018
+
pub struct created_at(());
1019
+
}
1020
+
}
1021
+
1022
+
/// Builder for constructing an instance of this type
1023
+
pub struct SubfsRecordBuilder<'a, S: subfs_record_state::State> {
1024
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1025
+
__unsafe_private_named: (
1026
+
::core::option::Option<jacquard_common::types::string::Datetime>,
1027
+
::core::option::Option<i64>,
1028
+
::core::option::Option<crate::place_wisp::subfs::Directory<'a>>,
1029
+
),
1030
+
_phantom: ::core::marker::PhantomData<&'a ()>,
1031
+
}
1032
+
1033
+
impl<'a> SubfsRecord<'a> {
1034
+
/// Create a new builder for this type
1035
+
pub fn new() -> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
1036
+
SubfsRecordBuilder::new()
1037
+
}
1038
+
}
1039
+
1040
+
impl<'a> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
1041
+
/// Create a new builder with all fields unset
1042
+
pub fn new() -> Self {
1043
+
SubfsRecordBuilder {
1044
+
_phantom_state: ::core::marker::PhantomData,
1045
+
__unsafe_private_named: (None, None, None),
1046
+
_phantom: ::core::marker::PhantomData,
1047
+
}
1048
+
}
1049
+
}
1050
+
1051
+
impl<'a, S> SubfsRecordBuilder<'a, S>
1052
+
where
1053
+
S: subfs_record_state::State,
1054
+
S::CreatedAt: subfs_record_state::IsUnset,
1055
+
{
1056
+
/// Set the `createdAt` field (required)
1057
+
pub fn created_at(
1058
+
mut self,
1059
+
value: impl Into<jacquard_common::types::string::Datetime>,
1060
+
) -> SubfsRecordBuilder<'a, subfs_record_state::SetCreatedAt<S>> {
1061
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1062
+
SubfsRecordBuilder {
1063
+
_phantom_state: ::core::marker::PhantomData,
1064
+
__unsafe_private_named: self.__unsafe_private_named,
1065
+
_phantom: ::core::marker::PhantomData,
1066
+
}
1067
+
}
1068
+
}
1069
+
1070
+
impl<'a, S: subfs_record_state::State> SubfsRecordBuilder<'a, S> {
1071
+
/// Set the `fileCount` field (optional)
1072
+
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
1073
+
self.__unsafe_private_named.1 = value.into();
1074
+
self
1075
+
}
1076
+
/// Set the `fileCount` field to an Option value (optional)
1077
+
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
1078
+
self.__unsafe_private_named.1 = value;
1079
+
self
1080
+
}
1081
+
}
1082
+
1083
+
impl<'a, S> SubfsRecordBuilder<'a, S>
1084
+
where
1085
+
S: subfs_record_state::State,
1086
+
S::Root: subfs_record_state::IsUnset,
1087
+
{
1088
+
/// Set the `root` field (required)
1089
+
pub fn root(
1090
+
mut self,
1091
+
value: impl Into<crate::place_wisp::subfs::Directory<'a>>,
1092
+
) -> SubfsRecordBuilder<'a, subfs_record_state::SetRoot<S>> {
1093
+
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1094
+
SubfsRecordBuilder {
1095
+
_phantom_state: ::core::marker::PhantomData,
1096
+
__unsafe_private_named: self.__unsafe_private_named,
1097
+
_phantom: ::core::marker::PhantomData,
1098
+
}
1099
+
}
1100
+
}
1101
+
1102
+
impl<'a, S> SubfsRecordBuilder<'a, S>
1103
+
where
1104
+
S: subfs_record_state::State,
1105
+
S::Root: subfs_record_state::IsSet,
1106
+
S::CreatedAt: subfs_record_state::IsSet,
1107
+
{
1108
+
/// Build the final struct
1109
+
pub fn build(self) -> SubfsRecord<'a> {
1110
+
SubfsRecord {
1111
+
created_at: self.__unsafe_private_named.0.unwrap(),
1112
+
file_count: self.__unsafe_private_named.1,
1113
+
root: self.__unsafe_private_named.2.unwrap(),
1114
+
extra_data: Default::default(),
1115
+
}
1116
+
}
1117
+
/// Build the final struct with custom extra_data
1118
+
pub fn build_with_data(
1119
+
self,
1120
+
extra_data: std::collections::BTreeMap<
1121
+
jacquard_common::smol_str::SmolStr,
1122
+
jacquard_common::types::value::Data<'a>,
1123
+
>,
1124
+
) -> SubfsRecord<'a> {
1125
+
SubfsRecord {
1126
+
created_at: self.__unsafe_private_named.0.unwrap(),
1127
+
file_count: self.__unsafe_private_named.1,
1128
+
root: self.__unsafe_private_named.2.unwrap(),
1129
+
extra_data: Some(extra_data),
1130
+
}
1131
+
}
1132
+
}
1133
+
1134
+
impl<'a> SubfsRecord<'a> {
1135
+
pub fn uri(
1136
+
uri: impl Into<jacquard_common::CowStr<'a>>,
1137
+
) -> Result<
1138
+
jacquard_common::types::uri::RecordUri<'a, SubfsRecordRecord>,
1139
+
jacquard_common::types::uri::UriError,
1140
+
> {
1141
+
jacquard_common::types::uri::RecordUri::try_from_uri(
1142
+
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
1143
+
)
1144
+
}
1145
+
}
1146
+
1147
+
/// Typed wrapper for GetRecord response with this collection's record type.
1148
+
#[derive(
1149
+
serde::Serialize,
1150
+
serde::Deserialize,
1151
+
Debug,
1152
+
Clone,
1153
+
PartialEq,
1154
+
Eq,
1155
+
jacquard_derive::IntoStatic
1156
+
)]
1157
+
#[serde(rename_all = "camelCase")]
1158
+
pub struct SubfsRecordGetRecordOutput<'a> {
1159
+
#[serde(skip_serializing_if = "std::option::Option::is_none")]
1160
+
#[serde(borrow)]
1161
+
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
1162
+
#[serde(borrow)]
1163
+
pub uri: jacquard_common::types::string::AtUri<'a>,
1164
+
#[serde(borrow)]
1165
+
pub value: SubfsRecord<'a>,
1166
+
}
1167
+
1168
+
impl From<SubfsRecordGetRecordOutput<'_>> for SubfsRecord<'_> {
1169
+
fn from(output: SubfsRecordGetRecordOutput<'_>) -> Self {
1170
+
use jacquard_common::IntoStatic;
1171
+
output.value.into_static()
1172
+
}
1173
+
}
1174
+
1175
+
impl jacquard_common::types::collection::Collection for SubfsRecord<'_> {
1176
+
const NSID: &'static str = "place.wisp.subfs";
1177
+
type Record = SubfsRecordRecord;
1178
+
}
1179
+
1180
+
/// Marker type for deserializing records from this collection.
1181
+
#[derive(Debug, serde::Serialize, serde::Deserialize)]
1182
+
pub struct SubfsRecordRecord;
1183
+
impl jacquard_common::xrpc::XrpcResp for SubfsRecordRecord {
1184
+
const NSID: &'static str = "place.wisp.subfs";
1185
+
const ENCODING: &'static str = "application/json";
1186
+
type Output<'de> = SubfsRecordGetRecordOutput<'de>;
1187
+
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
1188
+
}
1189
+
1190
+
impl jacquard_common::types::collection::Collection for SubfsRecordRecord {
1191
+
const NSID: &'static str = "place.wisp.subfs";
1192
+
type Record = SubfsRecordRecord;
1193
+
}
1194
+
1195
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for SubfsRecord<'a> {
1196
+
fn nsid() -> &'static str {
1197
+
"place.wisp.subfs"
1198
+
}
1199
+
fn def_name() -> &'static str {
1200
+
"main"
1201
+
}
1202
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1203
+
lexicon_doc_place_wisp_subfs()
1204
+
}
1205
+
fn validate(
1206
+
&self,
1207
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1208
+
if let Some(ref value) = self.file_count {
1209
+
if *value > 1000i64 {
1210
+
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
1211
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1212
+
"file_count",
1213
+
),
1214
+
max: 1000i64,
1215
+
actual: *value,
1216
+
});
1217
+
}
1218
+
}
1219
+
if let Some(ref value) = self.file_count {
1220
+
if *value < 0i64 {
1221
+
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
1222
+
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1223
+
"file_count",
1224
+
),
1225
+
min: 0i64,
1226
+
actual: *value,
1227
+
});
1228
+
}
1229
+
}
1230
+
Ok(())
1231
+
}
1232
+
}
1233
+
1234
+
#[jacquard_derive::lexicon]
1235
+
#[derive(
1236
+
serde::Serialize,
1237
+
serde::Deserialize,
1238
+
Debug,
1239
+
Clone,
1240
+
PartialEq,
1241
+
Eq,
1242
+
jacquard_derive::IntoStatic
1243
+
)]
1244
+
#[serde(rename_all = "camelCase")]
1245
+
pub struct Subfs<'a> {
1246
+
/// AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures.
1247
+
#[serde(borrow)]
1248
+
pub subject: jacquard_common::types::string::AtUri<'a>,
1249
+
#[serde(borrow)]
1250
+
pub r#type: jacquard_common::CowStr<'a>,
1251
+
}
1252
+
1253
+
pub mod subfs_state {
1254
+
1255
+
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1256
+
#[allow(unused)]
1257
+
use ::core::marker::PhantomData;
1258
+
mod sealed {
1259
+
pub trait Sealed {}
1260
+
}
1261
+
/// State trait tracking which required fields have been set
1262
+
pub trait State: sealed::Sealed {
1263
+
type Subject;
1264
+
type Type;
1265
+
}
1266
+
/// Empty state - all required fields are unset
1267
+
pub struct Empty(());
1268
+
impl sealed::Sealed for Empty {}
1269
+
impl State for Empty {
1270
+
type Subject = Unset;
1271
+
type Type = Unset;
1272
+
}
1273
+
///State transition - sets the `subject` field to Set
1274
+
pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
1275
+
impl<S: State> sealed::Sealed for SetSubject<S> {}
1276
+
impl<S: State> State for SetSubject<S> {
1277
+
type Subject = Set<members::subject>;
1278
+
type Type = S::Type;
1279
+
}
1280
+
///State transition - sets the `type` field to Set
1281
+
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
1282
+
impl<S: State> sealed::Sealed for SetType<S> {}
1283
+
impl<S: State> State for SetType<S> {
1284
+
type Subject = S::Subject;
1285
+
type Type = Set<members::r#type>;
1286
+
}
1287
+
/// Marker types for field names
1288
+
#[allow(non_camel_case_types)]
1289
+
pub mod members {
1290
+
///Marker type for the `subject` field
1291
+
pub struct subject(());
1292
+
///Marker type for the `type` field
1293
+
pub struct r#type(());
1294
+
}
1295
+
}
1296
+
1297
+
/// Builder for constructing an instance of this type
1298
+
pub struct SubfsBuilder<'a, S: subfs_state::State> {
1299
+
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1300
+
__unsafe_private_named: (
1301
+
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
1302
+
::core::option::Option<jacquard_common::CowStr<'a>>,
1303
+
),
1304
+
_phantom: ::core::marker::PhantomData<&'a ()>,
1305
+
}
1306
+
1307
+
impl<'a> Subfs<'a> {
1308
+
/// Create a new builder for this type
1309
+
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
1310
+
SubfsBuilder::new()
1311
+
}
1312
+
}
1313
+
1314
+
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
1315
+
/// Create a new builder with all fields unset
1316
+
pub fn new() -> Self {
1317
+
SubfsBuilder {
1318
+
_phantom_state: ::core::marker::PhantomData,
1319
+
__unsafe_private_named: (None, None),
1320
+
_phantom: ::core::marker::PhantomData,
1321
+
}
1322
+
}
1323
+
}
1324
+
1325
+
impl<'a, S> SubfsBuilder<'a, S>
1326
+
where
1327
+
S: subfs_state::State,
1328
+
S::Subject: subfs_state::IsUnset,
1329
+
{
1330
+
/// Set the `subject` field (required)
1331
+
pub fn subject(
1332
+
mut self,
1333
+
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
1334
+
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
1335
+
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1336
+
SubfsBuilder {
1337
+
_phantom_state: ::core::marker::PhantomData,
1338
+
__unsafe_private_named: self.__unsafe_private_named,
1339
+
_phantom: ::core::marker::PhantomData,
1340
+
}
1341
+
}
1342
+
}
1343
+
1344
+
impl<'a, S> SubfsBuilder<'a, S>
1345
+
where
1346
+
S: subfs_state::State,
1347
+
S::Type: subfs_state::IsUnset,
1348
+
{
1349
+
/// Set the `type` field (required)
1350
+
pub fn r#type(
1351
+
mut self,
1352
+
value: impl Into<jacquard_common::CowStr<'a>>,
1353
+
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
1354
+
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
1355
+
SubfsBuilder {
1356
+
_phantom_state: ::core::marker::PhantomData,
1357
+
__unsafe_private_named: self.__unsafe_private_named,
1358
+
_phantom: ::core::marker::PhantomData,
1359
+
}
1360
+
}
1361
+
}
1362
+
1363
+
impl<'a, S> SubfsBuilder<'a, S>
1364
+
where
1365
+
S: subfs_state::State,
1366
+
S::Subject: subfs_state::IsSet,
1367
+
S::Type: subfs_state::IsSet,
1368
+
{
1369
+
/// Build the final struct
1370
+
pub fn build(self) -> Subfs<'a> {
1371
+
Subfs {
1372
+
subject: self.__unsafe_private_named.0.unwrap(),
1373
+
r#type: self.__unsafe_private_named.1.unwrap(),
1374
+
extra_data: Default::default(),
1375
+
}
1376
+
}
1377
+
/// Build the final struct with custom extra_data
1378
+
pub fn build_with_data(
1379
+
self,
1380
+
extra_data: std::collections::BTreeMap<
1381
+
jacquard_common::smol_str::SmolStr,
1382
+
jacquard_common::types::value::Data<'a>,
1383
+
>,
1384
+
) -> Subfs<'a> {
1385
+
Subfs {
1386
+
subject: self.__unsafe_private_named.0.unwrap(),
1387
+
r#type: self.__unsafe_private_named.1.unwrap(),
1388
+
extra_data: Some(extra_data),
1389
+
}
1390
+
}
1391
+
}
1392
+
1393
+
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
1394
+
fn nsid() -> &'static str {
1395
+
"place.wisp.subfs"
1396
+
}
1397
+
fn def_name() -> &'static str {
1398
+
"subfs"
1399
+
}
1400
+
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1401
+
lexicon_doc_place_wisp_subfs()
1402
+
}
1403
+
fn validate(
1404
+
&self,
1405
+
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1406
+
Ok(())
1407
+
}
1408
+
}
+8
cli/crates/lexicons/src/place_wisp.rs
+8
cli/crates/lexicons/src/place_wisp.rs
+16
cli/default.nix
+16
cli/default.nix
···
1
+
{
2
+
rustPlatform,
3
+
glibc,
4
+
}:
5
+
rustPlatform.buildRustPackage {
6
+
name = "wisp-cli";
7
+
src = ./.;
8
+
cargoLock = {
9
+
lockFile = ./Cargo.lock;
10
+
outputHashes = {
11
+
"jacquard-0.9.5" = "sha256-75bas4VAYFcZAcBspSqS4vlJe8nmFn9ncTgeoT/OvnA=";
12
+
};
13
+
};
14
+
buildInputs = [glibc.static];
15
+
RUSTFLAGS = ["-C" "target-feature=+crt-static"];
16
+
}
+96
cli/flake.lock
+96
cli/flake.lock
···
1
+
{
2
+
"nodes": {
3
+
"flake-utils": {
4
+
"inputs": {
5
+
"systems": "systems"
6
+
},
7
+
"locked": {
8
+
"lastModified": 1731533236,
9
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
10
+
"owner": "numtide",
11
+
"repo": "flake-utils",
12
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
13
+
"type": "github"
14
+
},
15
+
"original": {
16
+
"owner": "numtide",
17
+
"repo": "flake-utils",
18
+
"type": "github"
19
+
}
20
+
},
21
+
"nixpkgs": {
22
+
"locked": {
23
+
"lastModified": 1767640445,
24
+
"narHash": "sha256-UWYqmD7JFBEDBHWYcqE6s6c77pWdcU/i+bwD6XxMb8A=",
25
+
"owner": "NixOS",
26
+
"repo": "nixpkgs",
27
+
"rev": "9f0c42f8bc7151b8e7e5840fb3bd454ad850d8c5",
28
+
"type": "github"
29
+
},
30
+
"original": {
31
+
"owner": "NixOS",
32
+
"ref": "nixos-unstable",
33
+
"repo": "nixpkgs",
34
+
"type": "github"
35
+
}
36
+
},
37
+
"nixpkgs_2": {
38
+
"locked": {
39
+
"lastModified": 1744536153,
40
+
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
41
+
"owner": "NixOS",
42
+
"repo": "nixpkgs",
43
+
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
44
+
"type": "github"
45
+
},
46
+
"original": {
47
+
"owner": "NixOS",
48
+
"ref": "nixpkgs-unstable",
49
+
"repo": "nixpkgs",
50
+
"type": "github"
51
+
}
52
+
},
53
+
"root": {
54
+
"inputs": {
55
+
"flake-utils": "flake-utils",
56
+
"nixpkgs": "nixpkgs",
57
+
"rust-overlay": "rust-overlay"
58
+
}
59
+
},
60
+
"rust-overlay": {
61
+
"inputs": {
62
+
"nixpkgs": "nixpkgs_2"
63
+
},
64
+
"locked": {
65
+
"lastModified": 1767667566,
66
+
"narHash": "sha256-COy+yxZGuhQRVD1r4bWVgeFt1GB+IB1k5WRpDKbLfI8=",
67
+
"owner": "oxalica",
68
+
"repo": "rust-overlay",
69
+
"rev": "056ce5b125ab32ffe78c7d3e394d9da44733c95e",
70
+
"type": "github"
71
+
},
72
+
"original": {
73
+
"owner": "oxalica",
74
+
"repo": "rust-overlay",
75
+
"type": "github"
76
+
}
77
+
},
78
+
"systems": {
79
+
"locked": {
80
+
"lastModified": 1681028828,
81
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
82
+
"owner": "nix-systems",
83
+
"repo": "default",
84
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
85
+
"type": "github"
86
+
},
87
+
"original": {
88
+
"owner": "nix-systems",
89
+
"repo": "default",
90
+
"type": "github"
91
+
}
92
+
}
93
+
},
94
+
"root": "root",
95
+
"version": 7
96
+
}
+136
cli/flake.nix
+136
cli/flake.nix
···
1
+
{
2
+
inputs = {
3
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
4
+
rust-overlay.url = "github:oxalica/rust-overlay";
5
+
flake-utils.url = "github:numtide/flake-utils";
6
+
};
7
+
8
+
outputs = { self, nixpkgs, rust-overlay, flake-utils }:
9
+
flake-utils.lib.eachDefaultSystem (system:
10
+
let
11
+
overlays = [ (import rust-overlay) ];
12
+
pkgs = import nixpkgs { inherit system overlays; };
13
+
14
+
rustToolchain = pkgs.rust-bin.stable.latest.default.override {
15
+
targets = [
16
+
"x86_64-unknown-linux-musl"
17
+
"aarch64-unknown-linux-musl"
18
+
"x86_64-apple-darwin"
19
+
"aarch64-apple-darwin"
20
+
];
21
+
};
22
+
23
+
cargoLockConfig = {
24
+
lockFile = ./Cargo.lock;
25
+
outputHashes = {
26
+
"jacquard-0.9.5" = "sha256-75bas4VAYFcZAcBspSqS4vlJe8nmFn9ncTgeoT/OvnA=";
27
+
};
28
+
};
29
+
30
+
# Native build for current system
31
+
native = pkgs.rustPlatform.buildRustPackage {
32
+
pname = "wisp-cli";
33
+
version = "0.4.2";
34
+
src = ./.;
35
+
cargoLock = cargoLockConfig;
36
+
nativeBuildInputs = [ rustToolchain ];
37
+
};
38
+
39
+
# Cross-compilation targets (Linux from macOS needs zigbuild)
40
+
linuxTargets = let
41
+
zigbuildPkgs = pkgs;
42
+
in {
43
+
x86_64-linux = pkgs.stdenv.mkDerivation {
44
+
pname = "wisp-cli-x86_64-linux";
45
+
version = "0.4.2";
46
+
src = ./.;
47
+
48
+
nativeBuildInputs = [
49
+
rustToolchain
50
+
pkgs.cargo-zigbuild
51
+
pkgs.zig
52
+
];
53
+
54
+
buildPhase = ''
55
+
export HOME=$(mktemp -d)
56
+
cargo zigbuild --release --target x86_64-unknown-linux-musl
57
+
'';
58
+
59
+
installPhase = ''
60
+
mkdir -p $out/bin
61
+
cp target/x86_64-unknown-linux-musl/release/wisp-cli $out/bin/
62
+
'';
63
+
64
+
# Skip Nix's cargo vendor - we use network
65
+
dontConfigure = true;
66
+
dontFixup = true;
67
+
};
68
+
69
+
aarch64-linux = pkgs.stdenv.mkDerivation {
70
+
pname = "wisp-cli-aarch64-linux";
71
+
version = "0.4.2";
72
+
src = ./.;
73
+
74
+
nativeBuildInputs = [
75
+
rustToolchain
76
+
pkgs.cargo-zigbuild
77
+
pkgs.zig
78
+
];
79
+
80
+
buildPhase = ''
81
+
export HOME=$(mktemp -d)
82
+
cargo zigbuild --release --target aarch64-unknown-linux-musl
83
+
'';
84
+
85
+
installPhase = ''
86
+
mkdir -p $out/bin
87
+
cp target/aarch64-unknown-linux-musl/release/wisp-cli $out/bin/
88
+
'';
89
+
90
+
dontConfigure = true;
91
+
dontFixup = true;
92
+
};
93
+
};
94
+
95
+
in {
96
+
packages = {
97
+
default = native;
98
+
inherit native;
99
+
100
+
# macOS universal binary
101
+
macos-universal = pkgs.stdenv.mkDerivation {
102
+
pname = "wisp-cli-macos-universal";
103
+
version = "0.4.2";
104
+
src = ./.;
105
+
106
+
nativeBuildInputs = [ rustToolchain pkgs.darwin.lipo ];
107
+
108
+
buildPhase = ''
109
+
export HOME=$(mktemp -d)
110
+
cargo build --release --target aarch64-apple-darwin
111
+
cargo build --release --target x86_64-apple-darwin
112
+
'';
113
+
114
+
installPhase = ''
115
+
mkdir -p $out/bin
116
+
lipo -create \
117
+
target/aarch64-apple-darwin/release/wisp-cli \
118
+
target/x86_64-apple-darwin/release/wisp-cli \
119
+
-output $out/bin/wisp-cli
120
+
'';
121
+
122
+
dontConfigure = true;
123
+
dontFixup = true;
124
+
};
125
+
} // (if pkgs.stdenv.isDarwin then linuxTargets else {});
126
+
127
+
devShells.default = pkgs.mkShell {
128
+
buildInputs = [
129
+
rustToolchain
130
+
pkgs.cargo-zigbuild
131
+
pkgs.zig
132
+
];
133
+
};
134
+
}
135
+
);
136
+
}
+1
-1
cli/src/blob_map.rs
+1
-1
cli/src/blob_map.rs
···
2
2
use jacquard_common::IntoStatic;
3
3
use std::collections::HashMap;
4
4
5
-
use crate::place_wisp::fs::{Directory, EntryNode};
5
+
use wisp_lexicons::place_wisp::fs::{Directory, EntryNode};
6
6
7
7
/// Extract blob information from a directory tree
8
8
/// Returns a map of file paths to their blob refs and CIDs
-43
cli/src/builder_types.rs
-43
cli/src/builder_types.rs
···
1
-
// @generated by jacquard-lexicon. DO NOT EDIT.
2
-
//
3
-
// This file was automatically generated from Lexicon schemas.
4
-
// Any manual changes will be overwritten on the next regeneration.
5
-
6
-
/// Marker type indicating a builder field has been set
7
-
pub struct Set<T>(pub T);
8
-
impl<T> Set<T> {
9
-
/// Extract the inner value
10
-
#[inline]
11
-
pub fn into_inner(self) -> T {
12
-
self.0
13
-
}
14
-
}
15
-
16
-
/// Marker type indicating a builder field has not been set
17
-
pub struct Unset;
18
-
/// Trait indicating a builder field is set (has a value)
19
-
#[rustversion::attr(
20
-
since(1.78.0),
21
-
diagnostic::on_unimplemented(
22
-
message = "the field `{Self}` was not set, but this method requires it to be set",
23
-
label = "the field `{Self}` was not set"
24
-
)
25
-
)]
26
-
pub trait IsSet: private::Sealed {}
27
-
/// Trait indicating a builder field is unset (no value yet)
28
-
#[rustversion::attr(
29
-
since(1.78.0),
30
-
diagnostic::on_unimplemented(
31
-
message = "the field `{Self}` was already set, but this method requires it to be unset",
32
-
label = "the field `{Self}` was already set"
33
-
)
34
-
)]
35
-
pub trait IsUnset: private::Sealed {}
36
-
impl<T> IsSet for Set<T> {}
37
-
impl IsUnset for Unset {}
38
-
mod private {
39
-
/// Sealed trait to prevent external implementations
40
-
pub trait Sealed {}
41
-
impl<T> Sealed for super::Set<T> {}
42
-
impl Sealed for super::Unset {}
43
-
}
-9
cli/src/lib.rs
-9
cli/src/lib.rs
+190
-54
cli/src/main.rs
+190
-54
cli/src/main.rs
···
1
-
mod builder_types;
2
-
mod place_wisp;
3
1
mod cid;
4
2
mod blob_map;
5
3
mod metadata;
···
26
24
use std::io::Write;
27
25
use base64::Engine;
28
26
use futures::stream::{self, StreamExt};
27
+
use indicatif::{ProgressBar, ProgressStyle, MultiProgress};
29
28
30
-
use place_wisp::fs::*;
31
-
use place_wisp::settings::*;
29
+
use wisp_lexicons::place_wisp::fs::*;
30
+
use wisp_lexicons::place_wisp::settings::*;
31
+
32
+
/// Maximum number of concurrent file uploads to the PDS
33
+
const MAX_CONCURRENT_UPLOADS: usize = 2;
34
+
35
+
/// Limits for caching on wisp.place (from @wisp/constants)
36
+
const MAX_FILE_COUNT: usize = 1000;
37
+
const MAX_SITE_SIZE: usize = 300 * 1024 * 1024; // 300MB
32
38
33
39
#[derive(Parser, Debug)]
34
40
#[command(author, version, about = "wisp.place CLI tool")]
35
41
struct Args {
36
42
#[command(subcommand)]
37
43
command: Option<Commands>,
38
-
44
+
39
45
// Deploy arguments (when no subcommand is specified)
40
46
/// Handle (e.g., alice.bsky.social), DID, or PDS URL
41
-
#[arg(global = true, conflicts_with = "command")]
42
47
input: Option<CowStr<'static>>,
43
48
44
49
/// Path to the directory containing your static site
45
-
#[arg(short, long, global = true, conflicts_with = "command")]
50
+
#[arg(short, long)]
46
51
path: Option<PathBuf>,
47
52
48
53
/// Site name (defaults to directory name)
49
-
#[arg(short, long, global = true, conflicts_with = "command")]
54
+
#[arg(short, long)]
50
55
site: Option<String>,
51
56
52
57
/// Path to auth store file
53
-
#[arg(long, global = true, conflicts_with = "command")]
58
+
#[arg(long)]
54
59
store: Option<String>,
55
60
56
61
/// App Password for authentication
57
-
#[arg(long, global = true, conflicts_with = "command")]
62
+
#[arg(long)]
58
63
password: Option<CowStr<'static>>,
59
64
60
65
/// Enable directory listing mode for paths without index files
61
-
#[arg(long, global = true, conflicts_with = "command")]
66
+
#[arg(long)]
62
67
directory: bool,
63
68
64
69
/// Enable SPA mode (serve index.html for all routes)
65
-
#[arg(long, global = true, conflicts_with = "command")]
70
+
#[arg(long)]
66
71
spa: bool,
72
+
73
+
/// Skip confirmation prompts (automatically accept warnings)
74
+
#[arg(short = 'y', long)]
75
+
yes: bool,
67
76
}
68
77
69
78
#[derive(Subcommand, Debug)]
···
96
105
/// Enable SPA mode (serve index.html for all routes)
97
106
#[arg(long)]
98
107
spa: bool,
108
+
109
+
/// Skip confirmation prompts (automatically accept warnings)
110
+
#[arg(short = 'y', long)]
111
+
yes: bool,
99
112
},
100
113
/// Pull a site from the PDS to a local directory
101
114
Pull {
···
108
121
109
122
/// Output directory for the downloaded site
110
123
#[arg(short, long, default_value = ".")]
111
-
output: PathBuf,
124
+
path: PathBuf,
112
125
},
113
126
/// Serve a site locally with real-time firehose updates
114
127
Serve {
···
121
134
122
135
/// Output directory for the site files
123
136
#[arg(short, long, default_value = ".")]
124
-
output: PathBuf,
137
+
path: PathBuf,
125
138
126
139
/// Port to serve on
127
-
#[arg(short, long, default_value = "8080")]
140
+
#[arg(short = 'P', long, default_value = "8080")]
128
141
port: u16,
129
142
},
130
143
}
···
134
147
let args = Args::parse();
135
148
136
149
let result = match args.command {
137
-
Some(Commands::Deploy { input, path, site, store, password, directory, spa }) => {
150
+
Some(Commands::Deploy { input, path, site, store, password, directory, spa, yes }) => {
138
151
// Dispatch to appropriate authentication method
139
152
if let Some(password) = password {
140
-
run_with_app_password(input, password, path, site, directory, spa).await
153
+
run_with_app_password(input, password, path, site, directory, spa, yes).await
141
154
} else {
142
-
run_with_oauth(input, store, path, site, directory, spa).await
155
+
run_with_oauth(input, store, path, site, directory, spa, yes).await
143
156
}
144
157
}
145
-
Some(Commands::Pull { input, site, output }) => {
146
-
pull::pull_site(input, CowStr::from(site), output).await
158
+
Some(Commands::Pull { input, site, path }) => {
159
+
pull::pull_site(input, CowStr::from(site), path).await
147
160
}
148
-
Some(Commands::Serve { input, site, output, port }) => {
149
-
serve::serve_site(input, CowStr::from(site), output, port).await
161
+
Some(Commands::Serve { input, site, path, port }) => {
162
+
serve::serve_site(input, CowStr::from(site), path, port).await
150
163
}
151
164
None => {
152
165
// Legacy mode: if input is provided, assume deploy command
···
156
169
157
170
// Dispatch to appropriate authentication method
158
171
if let Some(password) = args.password {
159
-
run_with_app_password(input, password, path, args.site, args.directory, args.spa).await
172
+
run_with_app_password(input, password, path, args.site, args.directory, args.spa, args.yes).await
160
173
} else {
161
-
run_with_oauth(input, store, path, args.site, args.directory, args.spa).await
174
+
run_with_oauth(input, store, path, args.site, args.directory, args.spa, args.yes).await
162
175
}
163
176
} else {
164
177
// No command and no input, show help
···
187
200
site: Option<String>,
188
201
directory: bool,
189
202
spa: bool,
203
+
yes: bool,
190
204
) -> miette::Result<()> {
191
205
let (session, auth) =
192
206
MemoryCredentialSession::authenticated(input, password, None, None).await?;
193
207
println!("Signed in as {}", auth.handle);
194
208
195
209
let agent: Agent<_> = Agent::from(session);
196
-
deploy_site(&agent, path, site, directory, spa).await
210
+
deploy_site(&agent, path, site, directory, spa, yes).await
197
211
}
198
212
199
213
/// Run deployment with OAuth authentication
···
204
218
site: Option<String>,
205
219
directory: bool,
206
220
spa: bool,
221
+
yes: bool,
207
222
) -> miette::Result<()> {
208
223
use jacquard::oauth::scopes::Scope;
209
224
use jacquard::oauth::atproto::AtprotoClientMetadata;
···
236
251
.await?;
237
252
238
253
let agent: Agent<_> = Agent::from(session);
239
-
deploy_site(&agent, path, site, directory, spa).await
254
+
deploy_site(&agent, path, site, directory, spa, yes).await
255
+
}
256
+
257
+
/// Scan directory to count files and calculate total size
258
+
/// Returns (file_count, total_size_bytes)
259
+
fn scan_directory_stats(
260
+
dir_path: &Path,
261
+
ignore_matcher: &ignore_patterns::IgnoreMatcher,
262
+
current_path: String,
263
+
) -> miette::Result<(usize, u64)> {
264
+
let mut file_count = 0;
265
+
let mut total_size = 0u64;
266
+
267
+
let dir_entries: Vec<_> = std::fs::read_dir(dir_path)
268
+
.into_diagnostic()?
269
+
.collect::<Result<Vec<_>, _>>()
270
+
.into_diagnostic()?;
271
+
272
+
for entry in dir_entries {
273
+
let path = entry.path();
274
+
let name = entry.file_name();
275
+
let name_str = name.to_str()
276
+
.ok_or_else(|| miette::miette!("Invalid filename: {:?}", name))?
277
+
.to_string();
278
+
279
+
let full_path = if current_path.is_empty() {
280
+
name_str.clone()
281
+
} else {
282
+
format!("{}/{}", current_path, name_str)
283
+
};
284
+
285
+
// Skip files/directories that match ignore patterns
286
+
if ignore_matcher.is_ignored(&full_path) || ignore_matcher.is_filename_ignored(&name_str) {
287
+
continue;
288
+
}
289
+
290
+
let metadata = entry.metadata().into_diagnostic()?;
291
+
292
+
if metadata.is_file() {
293
+
file_count += 1;
294
+
total_size += metadata.len();
295
+
} else if metadata.is_dir() {
296
+
let subdir_path = if current_path.is_empty() {
297
+
name_str
298
+
} else {
299
+
format!("{}/{}", current_path, name_str)
300
+
};
301
+
let (sub_count, sub_size) = scan_directory_stats(&path, ignore_matcher, subdir_path)?;
302
+
file_count += sub_count;
303
+
total_size += sub_size;
304
+
}
305
+
}
306
+
307
+
Ok((file_count, total_size))
240
308
}
241
309
242
310
/// Deploy the site using the provided agent
···
246
314
site: Option<String>,
247
315
directory_listing: bool,
248
316
spa_mode: bool,
317
+
skip_prompts: bool,
249
318
) -> miette::Result<()> {
250
319
// Verify the path exists
251
320
if !path.exists() {
···
263
332
264
333
println!("Deploying site '{}'...", site_name);
265
334
335
+
// Scan directory to check file count and size
336
+
let ignore_matcher = ignore_patterns::IgnoreMatcher::new(&path)?;
337
+
let (file_count, total_size) = scan_directory_stats(&path, &ignore_matcher, String::new())?;
338
+
339
+
let size_mb = total_size as f64 / (1024.0 * 1024.0);
340
+
println!("Scanned: {} files, {:.1} MB total", file_count, size_mb);
341
+
342
+
// Check if limits are exceeded
343
+
let exceeds_file_count = file_count > MAX_FILE_COUNT;
344
+
let exceeds_size = total_size > MAX_SITE_SIZE as u64;
345
+
346
+
if exceeds_file_count || exceeds_size {
347
+
println!("\nโ ๏ธ Warning: Your site exceeds wisp.place caching limits:");
348
+
349
+
if exceeds_file_count {
350
+
println!(" โข File count: {} (limit: {})", file_count, MAX_FILE_COUNT);
351
+
}
352
+
353
+
if exceeds_size {
354
+
let size_mb = total_size as f64 / (1024.0 * 1024.0);
355
+
let limit_mb = MAX_SITE_SIZE as f64 / (1024.0 * 1024.0);
356
+
println!(" โข Total size: {:.1} MB (limit: {:.0} MB)", size_mb, limit_mb);
357
+
}
358
+
359
+
println!("\nwisp.place will NOT serve your site if you proceed.");
360
+
println!("Your site will be uploaded to your PDS, but will only be accessible via:");
361
+
println!(" โข wisp-cli serve (local hosting)");
362
+
println!(" โข Other hosting services with more generous limits");
363
+
364
+
if !skip_prompts {
365
+
// Prompt for confirmation
366
+
use std::io::{self, Write};
367
+
print!("\nDo you want to upload anyway? (y/N): ");
368
+
io::stdout().flush().into_diagnostic()?;
369
+
370
+
let mut input = String::new();
371
+
io::stdin().read_line(&mut input).into_diagnostic()?;
372
+
let input = input.trim().to_lowercase();
373
+
374
+
if input != "y" && input != "yes" {
375
+
println!("Upload cancelled.");
376
+
return Ok(());
377
+
}
378
+
} else {
379
+
println!("\nSkipping confirmation (--yes flag set).");
380
+
}
381
+
382
+
println!("\nProceeding with upload...\n");
383
+
}
384
+
266
385
// Try to fetch existing manifest for incremental updates
267
386
let (existing_blob_map, old_subfs_uris): (HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, Vec<(String, String)>) = {
268
387
use jacquard_common::types::string::AtUri;
···
324
443
}
325
444
};
326
445
327
-
// Build directory tree with ignore patterns
328
-
let ignore_matcher = ignore_patterns::IgnoreMatcher::new(&path)?;
329
-
let (root_dir, total_files, reused_count) = build_directory(agent, &path, &existing_blob_map, String::new(), &ignore_matcher).await?;
446
+
// Create progress tracking (spinner style since we don't know total count upfront)
447
+
let multi_progress = MultiProgress::new();
448
+
let progress = multi_progress.add(ProgressBar::new_spinner());
449
+
progress.set_style(
450
+
ProgressStyle::default_spinner()
451
+
.template("[{elapsed_precise}] {spinner:.cyan} {pos} files {msg}")
452
+
.into_diagnostic()?
453
+
.tick_chars("โ โ โ โกโขโ โ โ ")
454
+
);
455
+
progress.set_message("Scanning files...");
456
+
progress.enable_steady_tick(std::time::Duration::from_millis(100));
457
+
458
+
let (root_dir, total_files, reused_count) = build_directory(agent, &path, &existing_blob_map, String::new(), &ignore_matcher, &progress).await?;
330
459
let uploaded_count = total_files - reused_count;
460
+
461
+
progress.finish_with_message(format!("โ {} files ({} uploaded, {} reused)", total_files, uploaded_count, reused_count));
331
462
332
463
// Check if we need to split into subfs records
333
464
const MAX_MANIFEST_SIZE: usize = 140 * 1024; // 140KB (PDS limit is 150KB)
···
378
509
let chunk_file_count = subfs_utils::count_files_in_directory(chunk);
379
510
let chunk_size = subfs_utils::estimate_directory_size(chunk);
380
511
381
-
let chunk_manifest = crate::place_wisp::subfs::SubfsRecord::new()
512
+
let chunk_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
382
513
.root(convert_fs_dir_to_subfs_dir(chunk.clone()))
383
514
.file_count(Some(chunk_file_count as i64))
384
515
.created_at(Datetime::now())
···
401
532
// Each chunk reference MUST have flat: true to merge chunk contents
402
533
println!(" โ Creating parent subfs with {} chunk references...", chunk_uris.len());
403
534
use jacquard_common::CowStr;
404
-
use crate::place_wisp::fs::{Subfs};
535
+
use wisp_lexicons::place_wisp::fs::{Subfs};
405
536
406
537
// Convert to fs::Subfs (which has the 'flat' field) instead of subfs::Subfs
407
538
let parent_entries_fs: Vec<Entry> = chunk_uris.iter().enumerate().map(|(i, (uri, _))| {
···
431
562
let parent_tid = Tid::now_0();
432
563
let parent_rkey = parent_tid.to_string();
433
564
434
-
let parent_manifest = crate::place_wisp::subfs::SubfsRecord::new()
565
+
let parent_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
435
566
.root(parent_root_subfs)
436
567
.file_count(Some(largest_dir.file_count as i64))
437
568
.created_at(Datetime::now())
···
450
581
let subfs_tid = Tid::now_0();
451
582
let subfs_rkey = subfs_tid.to_string();
452
583
453
-
let subfs_manifest = crate::place_wisp::subfs::SubfsRecord::new()
584
+
let subfs_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new()
454
585
.root(convert_fs_dir_to_subfs_dir(largest_dir.directory.clone()))
455
586
.file_count(Some(largest_dir.file_count as i64))
456
587
.created_at(Datetime::now())
···
606
737
existing_blobs: &'a HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>,
607
738
current_path: String,
608
739
ignore_matcher: &'a ignore_patterns::IgnoreMatcher,
740
+
progress: &'a ProgressBar,
609
741
) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<(Directory<'static>, usize, usize)>> + 'a>>
610
742
{
611
743
Box::pin(async move {
···
653
785
}
654
786
}
655
787
656
-
// Process files concurrently with a limit of 5
788
+
// Process files concurrently with a limit of 2
657
789
let file_results: Vec<(Entry<'static>, bool)> = stream::iter(file_tasks)
658
790
.map(|(name, path, full_path)| async move {
659
-
let (file_node, reused) = process_file(agent, &path, &full_path, existing_blobs).await?;
791
+
let (file_node, reused) = process_file(agent, &path, &full_path, existing_blobs, progress).await?;
792
+
progress.inc(1);
660
793
let entry = Entry::new()
661
794
.name(CowStr::from(name))
662
795
.node(EntryNode::File(Box::new(file_node)))
663
796
.build();
664
797
Ok::<_, miette::Report>((entry, reused))
665
798
})
666
-
.buffer_unordered(5)
799
+
.buffer_unordered(MAX_CONCURRENT_UPLOADS)
667
800
.collect::<Vec<_>>()
668
801
.await
669
802
.into_iter()
···
690
823
} else {
691
824
format!("{}/{}", current_path, name)
692
825
};
693
-
let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs, subdir_path, ignore_matcher).await?;
826
+
let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs, subdir_path, ignore_matcher, progress).await?;
694
827
dir_entries.push(Entry::new()
695
828
.name(CowStr::from(name))
696
829
.node(EntryNode::Directory(Box::new(subdir)))
···
722
855
file_path: &Path,
723
856
file_path_key: &str,
724
857
existing_blobs: &HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>,
858
+
progress: &ProgressBar,
725
859
) -> miette::Result<(File<'static>, bool)>
726
860
{
727
861
// Read file
···
761
895
if let Some((existing_blob_ref, existing_cid)) = existing_blob {
762
896
if existing_cid == &file_cid {
763
897
// CIDs match - reuse existing blob
764
-
println!(" โ Reusing blob for {} (CID: {})", file_path_key, file_cid);
898
+
progress.set_message(format!("โ Reused {}", file_path_key));
765
899
let mut file_builder = File::new()
766
900
.r#type(CowStr::from("file"))
767
901
.blob(existing_blob_ref.clone())
···
775
909
}
776
910
777
911
return Ok((file_builder.build(), true));
778
-
} else {
779
-
// CID mismatch - file changed
780
-
println!(" โ File changed: {} (old CID: {}, new CID: {})", file_path_key, existing_cid, file_cid);
781
-
}
782
-
} else {
783
-
// File not in existing blob map
784
-
if file_path_key.starts_with("imgs/") {
785
-
println!(" โ New file (not in blob map): {}", file_path_key);
786
912
}
787
913
}
788
914
···
793
919
MimeType::new_static("application/octet-stream")
794
920
};
795
921
796
-
println!(" โ Uploading {} ({} bytes, CID: {})", file_path_key, upload_bytes.len(), file_cid);
922
+
// Format file size nicely
923
+
let size_str = if upload_bytes.len() < 1024 {
924
+
format!("{} B", upload_bytes.len())
925
+
} else if upload_bytes.len() < 1024 * 1024 {
926
+
format!("{:.1} KB", upload_bytes.len() as f64 / 1024.0)
927
+
} else {
928
+
format!("{:.1} MB", upload_bytes.len() as f64 / (1024.0 * 1024.0))
929
+
};
930
+
931
+
progress.set_message(format!("โ Uploading {} ({})", file_path_key, size_str));
797
932
let blob = agent.upload_blob(upload_bytes, mime_type).await?;
933
+
progress.set_message(format!("โ Uploaded {}", file_path_key));
798
934
799
935
let mut file_builder = File::new()
800
936
.r#type(CowStr::from("file"))
···
813
949
814
950
/// Convert fs::Directory to subfs::Directory
815
951
/// They have the same structure, but different types
816
-
fn convert_fs_dir_to_subfs_dir(fs_dir: place_wisp::fs::Directory<'static>) -> place_wisp::subfs::Directory<'static> {
817
-
use place_wisp::subfs::{Directory as SubfsDirectory, Entry as SubfsEntry, EntryNode as SubfsEntryNode, File as SubfsFile};
952
+
fn convert_fs_dir_to_subfs_dir(fs_dir: wisp_lexicons::place_wisp::fs::Directory<'static>) -> wisp_lexicons::place_wisp::subfs::Directory<'static> {
953
+
use wisp_lexicons::place_wisp::subfs::{Directory as SubfsDirectory, Entry as SubfsEntry, EntryNode as SubfsEntryNode, File as SubfsFile};
818
954
819
955
let subfs_entries: Vec<SubfsEntry> = fs_dir.entries.into_iter().map(|entry| {
820
956
let node = match entry.node {
821
-
place_wisp::fs::EntryNode::File(file) => {
957
+
wisp_lexicons::place_wisp::fs::EntryNode::File(file) => {
822
958
SubfsEntryNode::File(Box::new(SubfsFile::new()
823
959
.r#type(file.r#type)
824
960
.blob(file.blob)
···
827
963
.base64(file.base64)
828
964
.build()))
829
965
}
830
-
place_wisp::fs::EntryNode::Directory(dir) => {
966
+
wisp_lexicons::place_wisp::fs::EntryNode::Directory(dir) => {
831
967
SubfsEntryNode::Directory(Box::new(convert_fs_dir_to_subfs_dir(*dir)))
832
968
}
833
-
place_wisp::fs::EntryNode::Subfs(subfs) => {
969
+
wisp_lexicons::place_wisp::fs::EntryNode::Subfs(subfs) => {
834
970
// Nested subfs in the directory we're converting
835
971
// Note: subfs::Subfs doesn't have the 'flat' field - that's only in fs::Subfs
836
-
SubfsEntryNode::Subfs(Box::new(place_wisp::subfs::Subfs::new()
972
+
SubfsEntryNode::Subfs(Box::new(wisp_lexicons::place_wisp::subfs::Subfs::new()
837
973
.r#type(subfs.r#type)
838
974
.subject(subfs.subject)
839
975
.build()))
840
976
}
841
-
place_wisp::fs::EntryNode::Unknown(unknown) => {
977
+
wisp_lexicons::place_wisp::fs::EntryNode::Unknown(unknown) => {
842
978
SubfsEntryNode::Unknown(unknown)
843
979
}
844
980
};
-9
cli/src/mod.rs
-9
cli/src/mod.rs
-1490
cli/src/place_wisp/fs.rs
-1490
cli/src/place_wisp/fs.rs
···
1
-
// @generated by jacquard-lexicon. DO NOT EDIT.
2
-
//
3
-
// Lexicon: place.wisp.fs
4
-
//
5
-
// This file was automatically generated from Lexicon schemas.
6
-
// Any manual changes will be overwritten on the next regeneration.
7
-
8
-
#[jacquard_derive::lexicon]
9
-
#[derive(
10
-
serde::Serialize,
11
-
serde::Deserialize,
12
-
Debug,
13
-
Clone,
14
-
PartialEq,
15
-
Eq,
16
-
jacquard_derive::IntoStatic
17
-
)]
18
-
#[serde(rename_all = "camelCase")]
19
-
pub struct Directory<'a> {
20
-
#[serde(borrow)]
21
-
pub entries: Vec<crate::place_wisp::fs::Entry<'a>>,
22
-
#[serde(borrow)]
23
-
pub r#type: jacquard_common::CowStr<'a>,
24
-
}
25
-
26
-
pub mod directory_state {
27
-
28
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
29
-
#[allow(unused)]
30
-
use ::core::marker::PhantomData;
31
-
mod sealed {
32
-
pub trait Sealed {}
33
-
}
34
-
/// State trait tracking which required fields have been set
35
-
pub trait State: sealed::Sealed {
36
-
type Type;
37
-
type Entries;
38
-
}
39
-
/// Empty state - all required fields are unset
40
-
pub struct Empty(());
41
-
impl sealed::Sealed for Empty {}
42
-
impl State for Empty {
43
-
type Type = Unset;
44
-
type Entries = Unset;
45
-
}
46
-
///State transition - sets the `type` field to Set
47
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
48
-
impl<S: State> sealed::Sealed for SetType<S> {}
49
-
impl<S: State> State for SetType<S> {
50
-
type Type = Set<members::r#type>;
51
-
type Entries = S::Entries;
52
-
}
53
-
///State transition - sets the `entries` field to Set
54
-
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
55
-
impl<S: State> sealed::Sealed for SetEntries<S> {}
56
-
impl<S: State> State for SetEntries<S> {
57
-
type Type = S::Type;
58
-
type Entries = Set<members::entries>;
59
-
}
60
-
/// Marker types for field names
61
-
#[allow(non_camel_case_types)]
62
-
pub mod members {
63
-
///Marker type for the `type` field
64
-
pub struct r#type(());
65
-
///Marker type for the `entries` field
66
-
pub struct entries(());
67
-
}
68
-
}
69
-
70
-
/// Builder for constructing an instance of this type
71
-
pub struct DirectoryBuilder<'a, S: directory_state::State> {
72
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
73
-
__unsafe_private_named: (
74
-
::core::option::Option<Vec<crate::place_wisp::fs::Entry<'a>>>,
75
-
::core::option::Option<jacquard_common::CowStr<'a>>,
76
-
),
77
-
_phantom: ::core::marker::PhantomData<&'a ()>,
78
-
}
79
-
80
-
impl<'a> Directory<'a> {
81
-
/// Create a new builder for this type
82
-
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
83
-
DirectoryBuilder::new()
84
-
}
85
-
}
86
-
87
-
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
88
-
/// Create a new builder with all fields unset
89
-
pub fn new() -> Self {
90
-
DirectoryBuilder {
91
-
_phantom_state: ::core::marker::PhantomData,
92
-
__unsafe_private_named: (None, None),
93
-
_phantom: ::core::marker::PhantomData,
94
-
}
95
-
}
96
-
}
97
-
98
-
impl<'a, S> DirectoryBuilder<'a, S>
99
-
where
100
-
S: directory_state::State,
101
-
S::Entries: directory_state::IsUnset,
102
-
{
103
-
/// Set the `entries` field (required)
104
-
pub fn entries(
105
-
mut self,
106
-
value: impl Into<Vec<crate::place_wisp::fs::Entry<'a>>>,
107
-
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
108
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
109
-
DirectoryBuilder {
110
-
_phantom_state: ::core::marker::PhantomData,
111
-
__unsafe_private_named: self.__unsafe_private_named,
112
-
_phantom: ::core::marker::PhantomData,
113
-
}
114
-
}
115
-
}
116
-
117
-
impl<'a, S> DirectoryBuilder<'a, S>
118
-
where
119
-
S: directory_state::State,
120
-
S::Type: directory_state::IsUnset,
121
-
{
122
-
/// Set the `type` field (required)
123
-
pub fn r#type(
124
-
mut self,
125
-
value: impl Into<jacquard_common::CowStr<'a>>,
126
-
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
127
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
128
-
DirectoryBuilder {
129
-
_phantom_state: ::core::marker::PhantomData,
130
-
__unsafe_private_named: self.__unsafe_private_named,
131
-
_phantom: ::core::marker::PhantomData,
132
-
}
133
-
}
134
-
}
135
-
136
-
impl<'a, S> DirectoryBuilder<'a, S>
137
-
where
138
-
S: directory_state::State,
139
-
S::Type: directory_state::IsSet,
140
-
S::Entries: directory_state::IsSet,
141
-
{
142
-
/// Build the final struct
143
-
pub fn build(self) -> Directory<'a> {
144
-
Directory {
145
-
entries: self.__unsafe_private_named.0.unwrap(),
146
-
r#type: self.__unsafe_private_named.1.unwrap(),
147
-
extra_data: Default::default(),
148
-
}
149
-
}
150
-
/// Build the final struct with custom extra_data
151
-
pub fn build_with_data(
152
-
self,
153
-
extra_data: std::collections::BTreeMap<
154
-
jacquard_common::smol_str::SmolStr,
155
-
jacquard_common::types::value::Data<'a>,
156
-
>,
157
-
) -> Directory<'a> {
158
-
Directory {
159
-
entries: self.__unsafe_private_named.0.unwrap(),
160
-
r#type: self.__unsafe_private_named.1.unwrap(),
161
-
extra_data: Some(extra_data),
162
-
}
163
-
}
164
-
}
165
-
166
-
fn lexicon_doc_place_wisp_fs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
167
-
::jacquard_lexicon::lexicon::LexiconDoc {
168
-
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
169
-
id: ::jacquard_common::CowStr::new_static("place.wisp.fs"),
170
-
revision: None,
171
-
description: None,
172
-
defs: {
173
-
let mut map = ::std::collections::BTreeMap::new();
174
-
map.insert(
175
-
::jacquard_common::smol_str::SmolStr::new_static("directory"),
176
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
177
-
description: None,
178
-
required: Some(
179
-
vec![
180
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
181
-
::jacquard_common::smol_str::SmolStr::new_static("entries")
182
-
],
183
-
),
184
-
nullable: None,
185
-
properties: {
186
-
#[allow(unused_mut)]
187
-
let mut map = ::std::collections::BTreeMap::new();
188
-
map.insert(
189
-
::jacquard_common::smol_str::SmolStr::new_static("entries"),
190
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
191
-
description: None,
192
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
193
-
description: None,
194
-
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
195
-
}),
196
-
min_length: None,
197
-
max_length: Some(500usize),
198
-
}),
199
-
);
200
-
map.insert(
201
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
202
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
203
-
description: None,
204
-
format: None,
205
-
default: None,
206
-
min_length: None,
207
-
max_length: None,
208
-
min_graphemes: None,
209
-
max_graphemes: None,
210
-
r#enum: None,
211
-
r#const: None,
212
-
known_values: None,
213
-
}),
214
-
);
215
-
map
216
-
},
217
-
}),
218
-
);
219
-
map.insert(
220
-
::jacquard_common::smol_str::SmolStr::new_static("entry"),
221
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
222
-
description: None,
223
-
required: Some(
224
-
vec![
225
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
226
-
::jacquard_common::smol_str::SmolStr::new_static("node")
227
-
],
228
-
),
229
-
nullable: None,
230
-
properties: {
231
-
#[allow(unused_mut)]
232
-
let mut map = ::std::collections::BTreeMap::new();
233
-
map.insert(
234
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
235
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
236
-
description: None,
237
-
format: None,
238
-
default: None,
239
-
min_length: None,
240
-
max_length: Some(255usize),
241
-
min_graphemes: None,
242
-
max_graphemes: None,
243
-
r#enum: None,
244
-
r#const: None,
245
-
known_values: None,
246
-
}),
247
-
);
248
-
map.insert(
249
-
::jacquard_common::smol_str::SmolStr::new_static("node"),
250
-
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
251
-
description: None,
252
-
refs: vec![
253
-
::jacquard_common::CowStr::new_static("#file"),
254
-
::jacquard_common::CowStr::new_static("#directory"),
255
-
::jacquard_common::CowStr::new_static("#subfs")
256
-
],
257
-
closed: None,
258
-
}),
259
-
);
260
-
map
261
-
},
262
-
}),
263
-
);
264
-
map.insert(
265
-
::jacquard_common::smol_str::SmolStr::new_static("file"),
266
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
267
-
description: None,
268
-
required: Some(
269
-
vec![
270
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
271
-
::jacquard_common::smol_str::SmolStr::new_static("blob")
272
-
],
273
-
),
274
-
nullable: None,
275
-
properties: {
276
-
#[allow(unused_mut)]
277
-
let mut map = ::std::collections::BTreeMap::new();
278
-
map.insert(
279
-
::jacquard_common::smol_str::SmolStr::new_static("base64"),
280
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
281
-
description: None,
282
-
default: None,
283
-
r#const: None,
284
-
}),
285
-
);
286
-
map.insert(
287
-
::jacquard_common::smol_str::SmolStr::new_static("blob"),
288
-
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
289
-
description: None,
290
-
accept: None,
291
-
max_size: None,
292
-
}),
293
-
);
294
-
map.insert(
295
-
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
296
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
297
-
description: Some(
298
-
::jacquard_common::CowStr::new_static(
299
-
"Content encoding (e.g., gzip for compressed files)",
300
-
),
301
-
),
302
-
format: None,
303
-
default: None,
304
-
min_length: None,
305
-
max_length: None,
306
-
min_graphemes: None,
307
-
max_graphemes: None,
308
-
r#enum: None,
309
-
r#const: None,
310
-
known_values: None,
311
-
}),
312
-
);
313
-
map.insert(
314
-
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
315
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
316
-
description: Some(
317
-
::jacquard_common::CowStr::new_static(
318
-
"Original MIME type before compression",
319
-
),
320
-
),
321
-
format: None,
322
-
default: None,
323
-
min_length: None,
324
-
max_length: None,
325
-
min_graphemes: None,
326
-
max_graphemes: None,
327
-
r#enum: None,
328
-
r#const: None,
329
-
known_values: None,
330
-
}),
331
-
);
332
-
map.insert(
333
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
334
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
335
-
description: None,
336
-
format: None,
337
-
default: None,
338
-
min_length: None,
339
-
max_length: None,
340
-
min_graphemes: None,
341
-
max_graphemes: None,
342
-
r#enum: None,
343
-
r#const: None,
344
-
known_values: None,
345
-
}),
346
-
);
347
-
map
348
-
},
349
-
}),
350
-
);
351
-
map.insert(
352
-
::jacquard_common::smol_str::SmolStr::new_static("main"),
353
-
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
354
-
description: Some(
355
-
::jacquard_common::CowStr::new_static(
356
-
"Virtual filesystem manifest for a Wisp site",
357
-
),
358
-
),
359
-
key: None,
360
-
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
361
-
description: None,
362
-
required: Some(
363
-
vec![
364
-
::jacquard_common::smol_str::SmolStr::new_static("site"),
365
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
366
-
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
367
-
],
368
-
),
369
-
nullable: None,
370
-
properties: {
371
-
#[allow(unused_mut)]
372
-
let mut map = ::std::collections::BTreeMap::new();
373
-
map.insert(
374
-
::jacquard_common::smol_str::SmolStr::new_static(
375
-
"createdAt",
376
-
),
377
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
378
-
description: None,
379
-
format: Some(
380
-
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
381
-
),
382
-
default: None,
383
-
min_length: None,
384
-
max_length: None,
385
-
min_graphemes: None,
386
-
max_graphemes: None,
387
-
r#enum: None,
388
-
r#const: None,
389
-
known_values: None,
390
-
}),
391
-
);
392
-
map.insert(
393
-
::jacquard_common::smol_str::SmolStr::new_static(
394
-
"fileCount",
395
-
),
396
-
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
397
-
description: None,
398
-
default: None,
399
-
minimum: Some(0i64),
400
-
maximum: Some(1000i64),
401
-
r#enum: None,
402
-
r#const: None,
403
-
}),
404
-
);
405
-
map.insert(
406
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
407
-
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
408
-
description: None,
409
-
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
410
-
}),
411
-
);
412
-
map.insert(
413
-
::jacquard_common::smol_str::SmolStr::new_static("site"),
414
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
415
-
description: None,
416
-
format: None,
417
-
default: None,
418
-
min_length: None,
419
-
max_length: None,
420
-
min_graphemes: None,
421
-
max_graphemes: None,
422
-
r#enum: None,
423
-
r#const: None,
424
-
known_values: None,
425
-
}),
426
-
);
427
-
map
428
-
},
429
-
}),
430
-
}),
431
-
);
432
-
map.insert(
433
-
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
434
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
435
-
description: None,
436
-
required: Some(
437
-
vec![
438
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
439
-
::jacquard_common::smol_str::SmolStr::new_static("subject")
440
-
],
441
-
),
442
-
nullable: None,
443
-
properties: {
444
-
#[allow(unused_mut)]
445
-
let mut map = ::std::collections::BTreeMap::new();
446
-
map.insert(
447
-
::jacquard_common::smol_str::SmolStr::new_static("flat"),
448
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
449
-
description: None,
450
-
default: None,
451
-
r#const: None,
452
-
}),
453
-
);
454
-
map.insert(
455
-
::jacquard_common::smol_str::SmolStr::new_static("subject"),
456
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
457
-
description: Some(
458
-
::jacquard_common::CowStr::new_static(
459
-
"AT-URI pointing to a place.wisp.subfs record containing this subtree.",
460
-
),
461
-
),
462
-
format: Some(
463
-
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
464
-
),
465
-
default: None,
466
-
min_length: None,
467
-
max_length: None,
468
-
min_graphemes: None,
469
-
max_graphemes: None,
470
-
r#enum: None,
471
-
r#const: None,
472
-
known_values: None,
473
-
}),
474
-
);
475
-
map.insert(
476
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
477
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
478
-
description: None,
479
-
format: None,
480
-
default: None,
481
-
min_length: None,
482
-
max_length: None,
483
-
min_graphemes: None,
484
-
max_graphemes: None,
485
-
r#enum: None,
486
-
r#const: None,
487
-
known_values: None,
488
-
}),
489
-
);
490
-
map
491
-
},
492
-
}),
493
-
);
494
-
map
495
-
},
496
-
}
497
-
}
498
-
499
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
500
-
fn nsid() -> &'static str {
501
-
"place.wisp.fs"
502
-
}
503
-
fn def_name() -> &'static str {
504
-
"directory"
505
-
}
506
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
507
-
lexicon_doc_place_wisp_fs()
508
-
}
509
-
fn validate(
510
-
&self,
511
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
512
-
{
513
-
let value = &self.entries;
514
-
#[allow(unused_comparisons)]
515
-
if value.len() > 500usize {
516
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
517
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
518
-
"entries",
519
-
),
520
-
max: 500usize,
521
-
actual: value.len(),
522
-
});
523
-
}
524
-
}
525
-
Ok(())
526
-
}
527
-
}
528
-
529
-
#[jacquard_derive::lexicon]
530
-
#[derive(
531
-
serde::Serialize,
532
-
serde::Deserialize,
533
-
Debug,
534
-
Clone,
535
-
PartialEq,
536
-
Eq,
537
-
jacquard_derive::IntoStatic
538
-
)]
539
-
#[serde(rename_all = "camelCase")]
540
-
pub struct Entry<'a> {
541
-
#[serde(borrow)]
542
-
pub name: jacquard_common::CowStr<'a>,
543
-
#[serde(borrow)]
544
-
pub node: EntryNode<'a>,
545
-
}
546
-
547
-
pub mod entry_state {
548
-
549
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
550
-
#[allow(unused)]
551
-
use ::core::marker::PhantomData;
552
-
mod sealed {
553
-
pub trait Sealed {}
554
-
}
555
-
/// State trait tracking which required fields have been set
556
-
pub trait State: sealed::Sealed {
557
-
type Name;
558
-
type Node;
559
-
}
560
-
/// Empty state - all required fields are unset
561
-
pub struct Empty(());
562
-
impl sealed::Sealed for Empty {}
563
-
impl State for Empty {
564
-
type Name = Unset;
565
-
type Node = Unset;
566
-
}
567
-
///State transition - sets the `name` field to Set
568
-
pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
569
-
impl<S: State> sealed::Sealed for SetName<S> {}
570
-
impl<S: State> State for SetName<S> {
571
-
type Name = Set<members::name>;
572
-
type Node = S::Node;
573
-
}
574
-
///State transition - sets the `node` field to Set
575
-
pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
576
-
impl<S: State> sealed::Sealed for SetNode<S> {}
577
-
impl<S: State> State for SetNode<S> {
578
-
type Name = S::Name;
579
-
type Node = Set<members::node>;
580
-
}
581
-
/// Marker types for field names
582
-
#[allow(non_camel_case_types)]
583
-
pub mod members {
584
-
///Marker type for the `name` field
585
-
pub struct name(());
586
-
///Marker type for the `node` field
587
-
pub struct node(());
588
-
}
589
-
}
590
-
591
-
/// Builder for constructing an instance of this type
592
-
pub struct EntryBuilder<'a, S: entry_state::State> {
593
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
594
-
__unsafe_private_named: (
595
-
::core::option::Option<jacquard_common::CowStr<'a>>,
596
-
::core::option::Option<EntryNode<'a>>,
597
-
),
598
-
_phantom: ::core::marker::PhantomData<&'a ()>,
599
-
}
600
-
601
-
impl<'a> Entry<'a> {
602
-
/// Create a new builder for this type
603
-
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
604
-
EntryBuilder::new()
605
-
}
606
-
}
607
-
608
-
impl<'a> EntryBuilder<'a, entry_state::Empty> {
609
-
/// Create a new builder with all fields unset
610
-
pub fn new() -> Self {
611
-
EntryBuilder {
612
-
_phantom_state: ::core::marker::PhantomData,
613
-
__unsafe_private_named: (None, None),
614
-
_phantom: ::core::marker::PhantomData,
615
-
}
616
-
}
617
-
}
618
-
619
-
impl<'a, S> EntryBuilder<'a, S>
620
-
where
621
-
S: entry_state::State,
622
-
S::Name: entry_state::IsUnset,
623
-
{
624
-
/// Set the `name` field (required)
625
-
pub fn name(
626
-
mut self,
627
-
value: impl Into<jacquard_common::CowStr<'a>>,
628
-
) -> EntryBuilder<'a, entry_state::SetName<S>> {
629
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
630
-
EntryBuilder {
631
-
_phantom_state: ::core::marker::PhantomData,
632
-
__unsafe_private_named: self.__unsafe_private_named,
633
-
_phantom: ::core::marker::PhantomData,
634
-
}
635
-
}
636
-
}
637
-
638
-
impl<'a, S> EntryBuilder<'a, S>
639
-
where
640
-
S: entry_state::State,
641
-
S::Node: entry_state::IsUnset,
642
-
{
643
-
/// Set the `node` field (required)
644
-
pub fn node(
645
-
mut self,
646
-
value: impl Into<EntryNode<'a>>,
647
-
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
648
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
649
-
EntryBuilder {
650
-
_phantom_state: ::core::marker::PhantomData,
651
-
__unsafe_private_named: self.__unsafe_private_named,
652
-
_phantom: ::core::marker::PhantomData,
653
-
}
654
-
}
655
-
}
656
-
657
-
impl<'a, S> EntryBuilder<'a, S>
658
-
where
659
-
S: entry_state::State,
660
-
S::Name: entry_state::IsSet,
661
-
S::Node: entry_state::IsSet,
662
-
{
663
-
/// Build the final struct
664
-
pub fn build(self) -> Entry<'a> {
665
-
Entry {
666
-
name: self.__unsafe_private_named.0.unwrap(),
667
-
node: self.__unsafe_private_named.1.unwrap(),
668
-
extra_data: Default::default(),
669
-
}
670
-
}
671
-
/// Build the final struct with custom extra_data
672
-
pub fn build_with_data(
673
-
self,
674
-
extra_data: std::collections::BTreeMap<
675
-
jacquard_common::smol_str::SmolStr,
676
-
jacquard_common::types::value::Data<'a>,
677
-
>,
678
-
) -> Entry<'a> {
679
-
Entry {
680
-
name: self.__unsafe_private_named.0.unwrap(),
681
-
node: self.__unsafe_private_named.1.unwrap(),
682
-
extra_data: Some(extra_data),
683
-
}
684
-
}
685
-
}
686
-
687
-
#[jacquard_derive::open_union]
688
-
#[derive(
689
-
serde::Serialize,
690
-
serde::Deserialize,
691
-
Debug,
692
-
Clone,
693
-
PartialEq,
694
-
Eq,
695
-
jacquard_derive::IntoStatic
696
-
)]
697
-
#[serde(tag = "$type")]
698
-
#[serde(bound(deserialize = "'de: 'a"))]
699
-
pub enum EntryNode<'a> {
700
-
#[serde(rename = "place.wisp.fs#file")]
701
-
File(Box<crate::place_wisp::fs::File<'a>>),
702
-
#[serde(rename = "place.wisp.fs#directory")]
703
-
Directory(Box<crate::place_wisp::fs::Directory<'a>>),
704
-
#[serde(rename = "place.wisp.fs#subfs")]
705
-
Subfs(Box<crate::place_wisp::fs::Subfs<'a>>),
706
-
}
707
-
708
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
709
-
fn nsid() -> &'static str {
710
-
"place.wisp.fs"
711
-
}
712
-
fn def_name() -> &'static str {
713
-
"entry"
714
-
}
715
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
716
-
lexicon_doc_place_wisp_fs()
717
-
}
718
-
fn validate(
719
-
&self,
720
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
721
-
{
722
-
let value = &self.name;
723
-
#[allow(unused_comparisons)]
724
-
if <str>::len(value.as_ref()) > 255usize {
725
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
726
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
727
-
"name",
728
-
),
729
-
max: 255usize,
730
-
actual: <str>::len(value.as_ref()),
731
-
});
732
-
}
733
-
}
734
-
Ok(())
735
-
}
736
-
}
737
-
738
-
#[jacquard_derive::lexicon]
739
-
#[derive(
740
-
serde::Serialize,
741
-
serde::Deserialize,
742
-
Debug,
743
-
Clone,
744
-
PartialEq,
745
-
Eq,
746
-
jacquard_derive::IntoStatic
747
-
)]
748
-
#[serde(rename_all = "camelCase")]
749
-
pub struct File<'a> {
750
-
/// True if blob content is base64-encoded (used to bypass PDS content sniffing)
751
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
752
-
pub base64: Option<bool>,
753
-
/// Content blob ref
754
-
#[serde(borrow)]
755
-
pub blob: jacquard_common::types::blob::BlobRef<'a>,
756
-
/// Content encoding (e.g., gzip for compressed files)
757
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
758
-
#[serde(borrow)]
759
-
pub encoding: Option<jacquard_common::CowStr<'a>>,
760
-
/// Original MIME type before compression
761
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
762
-
#[serde(borrow)]
763
-
pub mime_type: Option<jacquard_common::CowStr<'a>>,
764
-
#[serde(borrow)]
765
-
pub r#type: jacquard_common::CowStr<'a>,
766
-
}
767
-
768
-
pub mod file_state {
769
-
770
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
771
-
#[allow(unused)]
772
-
use ::core::marker::PhantomData;
773
-
mod sealed {
774
-
pub trait Sealed {}
775
-
}
776
-
/// State trait tracking which required fields have been set
777
-
pub trait State: sealed::Sealed {
778
-
type Type;
779
-
type Blob;
780
-
}
781
-
/// Empty state - all required fields are unset
782
-
pub struct Empty(());
783
-
impl sealed::Sealed for Empty {}
784
-
impl State for Empty {
785
-
type Type = Unset;
786
-
type Blob = Unset;
787
-
}
788
-
///State transition - sets the `type` field to Set
789
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
790
-
impl<S: State> sealed::Sealed for SetType<S> {}
791
-
impl<S: State> State for SetType<S> {
792
-
type Type = Set<members::r#type>;
793
-
type Blob = S::Blob;
794
-
}
795
-
///State transition - sets the `blob` field to Set
796
-
pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
797
-
impl<S: State> sealed::Sealed for SetBlob<S> {}
798
-
impl<S: State> State for SetBlob<S> {
799
-
type Type = S::Type;
800
-
type Blob = Set<members::blob>;
801
-
}
802
-
/// Marker types for field names
803
-
#[allow(non_camel_case_types)]
804
-
pub mod members {
805
-
///Marker type for the `type` field
806
-
pub struct r#type(());
807
-
///Marker type for the `blob` field
808
-
pub struct blob(());
809
-
}
810
-
}
811
-
812
-
/// Builder for constructing an instance of this type
813
-
pub struct FileBuilder<'a, S: file_state::State> {
814
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
815
-
__unsafe_private_named: (
816
-
::core::option::Option<bool>,
817
-
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
818
-
::core::option::Option<jacquard_common::CowStr<'a>>,
819
-
::core::option::Option<jacquard_common::CowStr<'a>>,
820
-
::core::option::Option<jacquard_common::CowStr<'a>>,
821
-
),
822
-
_phantom: ::core::marker::PhantomData<&'a ()>,
823
-
}
824
-
825
-
impl<'a> File<'a> {
826
-
/// Create a new builder for this type
827
-
pub fn new() -> FileBuilder<'a, file_state::Empty> {
828
-
FileBuilder::new()
829
-
}
830
-
}
831
-
832
-
impl<'a> FileBuilder<'a, file_state::Empty> {
833
-
/// Create a new builder with all fields unset
834
-
pub fn new() -> Self {
835
-
FileBuilder {
836
-
_phantom_state: ::core::marker::PhantomData,
837
-
__unsafe_private_named: (None, None, None, None, None),
838
-
_phantom: ::core::marker::PhantomData,
839
-
}
840
-
}
841
-
}
842
-
843
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
844
-
/// Set the `base64` field (optional)
845
-
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
846
-
self.__unsafe_private_named.0 = value.into();
847
-
self
848
-
}
849
-
/// Set the `base64` field to an Option value (optional)
850
-
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
851
-
self.__unsafe_private_named.0 = value;
852
-
self
853
-
}
854
-
}
855
-
856
-
impl<'a, S> FileBuilder<'a, S>
857
-
where
858
-
S: file_state::State,
859
-
S::Blob: file_state::IsUnset,
860
-
{
861
-
/// Set the `blob` field (required)
862
-
pub fn blob(
863
-
mut self,
864
-
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
865
-
) -> FileBuilder<'a, file_state::SetBlob<S>> {
866
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
867
-
FileBuilder {
868
-
_phantom_state: ::core::marker::PhantomData,
869
-
__unsafe_private_named: self.__unsafe_private_named,
870
-
_phantom: ::core::marker::PhantomData,
871
-
}
872
-
}
873
-
}
874
-
875
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
876
-
/// Set the `encoding` field (optional)
877
-
pub fn encoding(
878
-
mut self,
879
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
880
-
) -> Self {
881
-
self.__unsafe_private_named.2 = value.into();
882
-
self
883
-
}
884
-
/// Set the `encoding` field to an Option value (optional)
885
-
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
886
-
self.__unsafe_private_named.2 = value;
887
-
self
888
-
}
889
-
}
890
-
891
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
892
-
/// Set the `mimeType` field (optional)
893
-
pub fn mime_type(
894
-
mut self,
895
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
896
-
) -> Self {
897
-
self.__unsafe_private_named.3 = value.into();
898
-
self
899
-
}
900
-
/// Set the `mimeType` field to an Option value (optional)
901
-
pub fn maybe_mime_type(
902
-
mut self,
903
-
value: Option<jacquard_common::CowStr<'a>>,
904
-
) -> Self {
905
-
self.__unsafe_private_named.3 = value;
906
-
self
907
-
}
908
-
}
909
-
910
-
impl<'a, S> FileBuilder<'a, S>
911
-
where
912
-
S: file_state::State,
913
-
S::Type: file_state::IsUnset,
914
-
{
915
-
/// Set the `type` field (required)
916
-
pub fn r#type(
917
-
mut self,
918
-
value: impl Into<jacquard_common::CowStr<'a>>,
919
-
) -> FileBuilder<'a, file_state::SetType<S>> {
920
-
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
921
-
FileBuilder {
922
-
_phantom_state: ::core::marker::PhantomData,
923
-
__unsafe_private_named: self.__unsafe_private_named,
924
-
_phantom: ::core::marker::PhantomData,
925
-
}
926
-
}
927
-
}
928
-
929
-
impl<'a, S> FileBuilder<'a, S>
930
-
where
931
-
S: file_state::State,
932
-
S::Type: file_state::IsSet,
933
-
S::Blob: file_state::IsSet,
934
-
{
935
-
/// Build the final struct
936
-
pub fn build(self) -> File<'a> {
937
-
File {
938
-
base64: self.__unsafe_private_named.0,
939
-
blob: self.__unsafe_private_named.1.unwrap(),
940
-
encoding: self.__unsafe_private_named.2,
941
-
mime_type: self.__unsafe_private_named.3,
942
-
r#type: self.__unsafe_private_named.4.unwrap(),
943
-
extra_data: Default::default(),
944
-
}
945
-
}
946
-
/// Build the final struct with custom extra_data
947
-
pub fn build_with_data(
948
-
self,
949
-
extra_data: std::collections::BTreeMap<
950
-
jacquard_common::smol_str::SmolStr,
951
-
jacquard_common::types::value::Data<'a>,
952
-
>,
953
-
) -> File<'a> {
954
-
File {
955
-
base64: self.__unsafe_private_named.0,
956
-
blob: self.__unsafe_private_named.1.unwrap(),
957
-
encoding: self.__unsafe_private_named.2,
958
-
mime_type: self.__unsafe_private_named.3,
959
-
r#type: self.__unsafe_private_named.4.unwrap(),
960
-
extra_data: Some(extra_data),
961
-
}
962
-
}
963
-
}
964
-
965
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
966
-
fn nsid() -> &'static str {
967
-
"place.wisp.fs"
968
-
}
969
-
fn def_name() -> &'static str {
970
-
"file"
971
-
}
972
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
973
-
lexicon_doc_place_wisp_fs()
974
-
}
975
-
fn validate(
976
-
&self,
977
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
978
-
Ok(())
979
-
}
980
-
}
981
-
982
-
/// Virtual filesystem manifest for a Wisp site
983
-
#[jacquard_derive::lexicon]
984
-
#[derive(
985
-
serde::Serialize,
986
-
serde::Deserialize,
987
-
Debug,
988
-
Clone,
989
-
PartialEq,
990
-
Eq,
991
-
jacquard_derive::IntoStatic
992
-
)]
993
-
#[serde(rename_all = "camelCase")]
994
-
pub struct Fs<'a> {
995
-
pub created_at: jacquard_common::types::string::Datetime,
996
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
997
-
pub file_count: Option<i64>,
998
-
#[serde(borrow)]
999
-
pub root: crate::place_wisp::fs::Directory<'a>,
1000
-
#[serde(borrow)]
1001
-
pub site: jacquard_common::CowStr<'a>,
1002
-
}
1003
-
1004
-
pub mod fs_state {
1005
-
1006
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1007
-
#[allow(unused)]
1008
-
use ::core::marker::PhantomData;
1009
-
mod sealed {
1010
-
pub trait Sealed {}
1011
-
}
1012
-
/// State trait tracking which required fields have been set
1013
-
pub trait State: sealed::Sealed {
1014
-
type Site;
1015
-
type Root;
1016
-
type CreatedAt;
1017
-
}
1018
-
/// Empty state - all required fields are unset
1019
-
pub struct Empty(());
1020
-
impl sealed::Sealed for Empty {}
1021
-
impl State for Empty {
1022
-
type Site = Unset;
1023
-
type Root = Unset;
1024
-
type CreatedAt = Unset;
1025
-
}
1026
-
///State transition - sets the `site` field to Set
1027
-
pub struct SetSite<S: State = Empty>(PhantomData<fn() -> S>);
1028
-
impl<S: State> sealed::Sealed for SetSite<S> {}
1029
-
impl<S: State> State for SetSite<S> {
1030
-
type Site = Set<members::site>;
1031
-
type Root = S::Root;
1032
-
type CreatedAt = S::CreatedAt;
1033
-
}
1034
-
///State transition - sets the `root` field to Set
1035
-
pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
1036
-
impl<S: State> sealed::Sealed for SetRoot<S> {}
1037
-
impl<S: State> State for SetRoot<S> {
1038
-
type Site = S::Site;
1039
-
type Root = Set<members::root>;
1040
-
type CreatedAt = S::CreatedAt;
1041
-
}
1042
-
///State transition - sets the `created_at` field to Set
1043
-
pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
1044
-
impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
1045
-
impl<S: State> State for SetCreatedAt<S> {
1046
-
type Site = S::Site;
1047
-
type Root = S::Root;
1048
-
type CreatedAt = Set<members::created_at>;
1049
-
}
1050
-
/// Marker types for field names
1051
-
#[allow(non_camel_case_types)]
1052
-
pub mod members {
1053
-
///Marker type for the `site` field
1054
-
pub struct site(());
1055
-
///Marker type for the `root` field
1056
-
pub struct root(());
1057
-
///Marker type for the `created_at` field
1058
-
pub struct created_at(());
1059
-
}
1060
-
}
1061
-
1062
-
/// Builder for constructing an instance of this type
1063
-
pub struct FsBuilder<'a, S: fs_state::State> {
1064
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1065
-
__unsafe_private_named: (
1066
-
::core::option::Option<jacquard_common::types::string::Datetime>,
1067
-
::core::option::Option<i64>,
1068
-
::core::option::Option<crate::place_wisp::fs::Directory<'a>>,
1069
-
::core::option::Option<jacquard_common::CowStr<'a>>,
1070
-
),
1071
-
_phantom: ::core::marker::PhantomData<&'a ()>,
1072
-
}
1073
-
1074
-
impl<'a> Fs<'a> {
1075
-
/// Create a new builder for this type
1076
-
pub fn new() -> FsBuilder<'a, fs_state::Empty> {
1077
-
FsBuilder::new()
1078
-
}
1079
-
}
1080
-
1081
-
impl<'a> FsBuilder<'a, fs_state::Empty> {
1082
-
/// Create a new builder with all fields unset
1083
-
pub fn new() -> Self {
1084
-
FsBuilder {
1085
-
_phantom_state: ::core::marker::PhantomData,
1086
-
__unsafe_private_named: (None, None, None, None),
1087
-
_phantom: ::core::marker::PhantomData,
1088
-
}
1089
-
}
1090
-
}
1091
-
1092
-
impl<'a, S> FsBuilder<'a, S>
1093
-
where
1094
-
S: fs_state::State,
1095
-
S::CreatedAt: fs_state::IsUnset,
1096
-
{
1097
-
/// Set the `createdAt` field (required)
1098
-
pub fn created_at(
1099
-
mut self,
1100
-
value: impl Into<jacquard_common::types::string::Datetime>,
1101
-
) -> FsBuilder<'a, fs_state::SetCreatedAt<S>> {
1102
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1103
-
FsBuilder {
1104
-
_phantom_state: ::core::marker::PhantomData,
1105
-
__unsafe_private_named: self.__unsafe_private_named,
1106
-
_phantom: ::core::marker::PhantomData,
1107
-
}
1108
-
}
1109
-
}
1110
-
1111
-
impl<'a, S: fs_state::State> FsBuilder<'a, S> {
1112
-
/// Set the `fileCount` field (optional)
1113
-
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
1114
-
self.__unsafe_private_named.1 = value.into();
1115
-
self
1116
-
}
1117
-
/// Set the `fileCount` field to an Option value (optional)
1118
-
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
1119
-
self.__unsafe_private_named.1 = value;
1120
-
self
1121
-
}
1122
-
}
1123
-
1124
-
impl<'a, S> FsBuilder<'a, S>
1125
-
where
1126
-
S: fs_state::State,
1127
-
S::Root: fs_state::IsUnset,
1128
-
{
1129
-
/// Set the `root` field (required)
1130
-
pub fn root(
1131
-
mut self,
1132
-
value: impl Into<crate::place_wisp::fs::Directory<'a>>,
1133
-
) -> FsBuilder<'a, fs_state::SetRoot<S>> {
1134
-
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1135
-
FsBuilder {
1136
-
_phantom_state: ::core::marker::PhantomData,
1137
-
__unsafe_private_named: self.__unsafe_private_named,
1138
-
_phantom: ::core::marker::PhantomData,
1139
-
}
1140
-
}
1141
-
}
1142
-
1143
-
impl<'a, S> FsBuilder<'a, S>
1144
-
where
1145
-
S: fs_state::State,
1146
-
S::Site: fs_state::IsUnset,
1147
-
{
1148
-
/// Set the `site` field (required)
1149
-
pub fn site(
1150
-
mut self,
1151
-
value: impl Into<jacquard_common::CowStr<'a>>,
1152
-
) -> FsBuilder<'a, fs_state::SetSite<S>> {
1153
-
self.__unsafe_private_named.3 = ::core::option::Option::Some(value.into());
1154
-
FsBuilder {
1155
-
_phantom_state: ::core::marker::PhantomData,
1156
-
__unsafe_private_named: self.__unsafe_private_named,
1157
-
_phantom: ::core::marker::PhantomData,
1158
-
}
1159
-
}
1160
-
}
1161
-
1162
-
impl<'a, S> FsBuilder<'a, S>
1163
-
where
1164
-
S: fs_state::State,
1165
-
S::Site: fs_state::IsSet,
1166
-
S::Root: fs_state::IsSet,
1167
-
S::CreatedAt: fs_state::IsSet,
1168
-
{
1169
-
/// Build the final struct
1170
-
pub fn build(self) -> Fs<'a> {
1171
-
Fs {
1172
-
created_at: self.__unsafe_private_named.0.unwrap(),
1173
-
file_count: self.__unsafe_private_named.1,
1174
-
root: self.__unsafe_private_named.2.unwrap(),
1175
-
site: self.__unsafe_private_named.3.unwrap(),
1176
-
extra_data: Default::default(),
1177
-
}
1178
-
}
1179
-
/// Build the final struct with custom extra_data
1180
-
pub fn build_with_data(
1181
-
self,
1182
-
extra_data: std::collections::BTreeMap<
1183
-
jacquard_common::smol_str::SmolStr,
1184
-
jacquard_common::types::value::Data<'a>,
1185
-
>,
1186
-
) -> Fs<'a> {
1187
-
Fs {
1188
-
created_at: self.__unsafe_private_named.0.unwrap(),
1189
-
file_count: self.__unsafe_private_named.1,
1190
-
root: self.__unsafe_private_named.2.unwrap(),
1191
-
site: self.__unsafe_private_named.3.unwrap(),
1192
-
extra_data: Some(extra_data),
1193
-
}
1194
-
}
1195
-
}
1196
-
1197
-
impl<'a> Fs<'a> {
1198
-
pub fn uri(
1199
-
uri: impl Into<jacquard_common::CowStr<'a>>,
1200
-
) -> Result<
1201
-
jacquard_common::types::uri::RecordUri<'a, FsRecord>,
1202
-
jacquard_common::types::uri::UriError,
1203
-
> {
1204
-
jacquard_common::types::uri::RecordUri::try_from_uri(
1205
-
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
1206
-
)
1207
-
}
1208
-
}
1209
-
1210
-
/// Typed wrapper for GetRecord response with this collection's record type.
1211
-
#[derive(
1212
-
serde::Serialize,
1213
-
serde::Deserialize,
1214
-
Debug,
1215
-
Clone,
1216
-
PartialEq,
1217
-
Eq,
1218
-
jacquard_derive::IntoStatic
1219
-
)]
1220
-
#[serde(rename_all = "camelCase")]
1221
-
pub struct FsGetRecordOutput<'a> {
1222
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
1223
-
#[serde(borrow)]
1224
-
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
1225
-
#[serde(borrow)]
1226
-
pub uri: jacquard_common::types::string::AtUri<'a>,
1227
-
#[serde(borrow)]
1228
-
pub value: Fs<'a>,
1229
-
}
1230
-
1231
-
impl From<FsGetRecordOutput<'_>> for Fs<'_> {
1232
-
fn from(output: FsGetRecordOutput<'_>) -> Self {
1233
-
use jacquard_common::IntoStatic;
1234
-
output.value.into_static()
1235
-
}
1236
-
}
1237
-
1238
-
impl jacquard_common::types::collection::Collection for Fs<'_> {
1239
-
const NSID: &'static str = "place.wisp.fs";
1240
-
type Record = FsRecord;
1241
-
}
1242
-
1243
-
/// Marker type for deserializing records from this collection.
1244
-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
1245
-
pub struct FsRecord;
1246
-
impl jacquard_common::xrpc::XrpcResp for FsRecord {
1247
-
const NSID: &'static str = "place.wisp.fs";
1248
-
const ENCODING: &'static str = "application/json";
1249
-
type Output<'de> = FsGetRecordOutput<'de>;
1250
-
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
1251
-
}
1252
-
1253
-
impl jacquard_common::types::collection::Collection for FsRecord {
1254
-
const NSID: &'static str = "place.wisp.fs";
1255
-
type Record = FsRecord;
1256
-
}
1257
-
1258
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Fs<'a> {
1259
-
fn nsid() -> &'static str {
1260
-
"place.wisp.fs"
1261
-
}
1262
-
fn def_name() -> &'static str {
1263
-
"main"
1264
-
}
1265
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1266
-
lexicon_doc_place_wisp_fs()
1267
-
}
1268
-
fn validate(
1269
-
&self,
1270
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1271
-
if let Some(ref value) = self.file_count {
1272
-
if *value > 1000i64 {
1273
-
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
1274
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1275
-
"file_count",
1276
-
),
1277
-
max: 1000i64,
1278
-
actual: *value,
1279
-
});
1280
-
}
1281
-
}
1282
-
if let Some(ref value) = self.file_count {
1283
-
if *value < 0i64 {
1284
-
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
1285
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1286
-
"file_count",
1287
-
),
1288
-
min: 0i64,
1289
-
actual: *value,
1290
-
});
1291
-
}
1292
-
}
1293
-
Ok(())
1294
-
}
1295
-
}
1296
-
1297
-
#[jacquard_derive::lexicon]
1298
-
#[derive(
1299
-
serde::Serialize,
1300
-
serde::Deserialize,
1301
-
Debug,
1302
-
Clone,
1303
-
PartialEq,
1304
-
Eq,
1305
-
jacquard_derive::IntoStatic
1306
-
)]
1307
-
#[serde(rename_all = "camelCase")]
1308
-
pub struct Subfs<'a> {
1309
-
/// If true, the subfs record's root entries are merged (flattened) into the parent directory, replacing the subfs entry. If false (default), the subfs entries are placed in a subdirectory with the subfs entry's name. Flat merging is useful for splitting large directories across multiple records while maintaining a flat structure.
1310
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
1311
-
pub flat: Option<bool>,
1312
-
/// AT-URI pointing to a place.wisp.subfs record containing this subtree.
1313
-
#[serde(borrow)]
1314
-
pub subject: jacquard_common::types::string::AtUri<'a>,
1315
-
#[serde(borrow)]
1316
-
pub r#type: jacquard_common::CowStr<'a>,
1317
-
}
1318
-
1319
-
pub mod subfs_state {
1320
-
1321
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1322
-
#[allow(unused)]
1323
-
use ::core::marker::PhantomData;
1324
-
mod sealed {
1325
-
pub trait Sealed {}
1326
-
}
1327
-
/// State trait tracking which required fields have been set
1328
-
pub trait State: sealed::Sealed {
1329
-
type Type;
1330
-
type Subject;
1331
-
}
1332
-
/// Empty state - all required fields are unset
1333
-
pub struct Empty(());
1334
-
impl sealed::Sealed for Empty {}
1335
-
impl State for Empty {
1336
-
type Type = Unset;
1337
-
type Subject = Unset;
1338
-
}
1339
-
///State transition - sets the `type` field to Set
1340
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
1341
-
impl<S: State> sealed::Sealed for SetType<S> {}
1342
-
impl<S: State> State for SetType<S> {
1343
-
type Type = Set<members::r#type>;
1344
-
type Subject = S::Subject;
1345
-
}
1346
-
///State transition - sets the `subject` field to Set
1347
-
pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
1348
-
impl<S: State> sealed::Sealed for SetSubject<S> {}
1349
-
impl<S: State> State for SetSubject<S> {
1350
-
type Type = S::Type;
1351
-
type Subject = Set<members::subject>;
1352
-
}
1353
-
/// Marker types for field names
1354
-
#[allow(non_camel_case_types)]
1355
-
pub mod members {
1356
-
///Marker type for the `type` field
1357
-
pub struct r#type(());
1358
-
///Marker type for the `subject` field
1359
-
pub struct subject(());
1360
-
}
1361
-
}
1362
-
1363
-
/// Builder for constructing an instance of this type
1364
-
pub struct SubfsBuilder<'a, S: subfs_state::State> {
1365
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1366
-
__unsafe_private_named: (
1367
-
::core::option::Option<bool>,
1368
-
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
1369
-
::core::option::Option<jacquard_common::CowStr<'a>>,
1370
-
),
1371
-
_phantom: ::core::marker::PhantomData<&'a ()>,
1372
-
}
1373
-
1374
-
impl<'a> Subfs<'a> {
1375
-
/// Create a new builder for this type
1376
-
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
1377
-
SubfsBuilder::new()
1378
-
}
1379
-
}
1380
-
1381
-
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
1382
-
/// Create a new builder with all fields unset
1383
-
pub fn new() -> Self {
1384
-
SubfsBuilder {
1385
-
_phantom_state: ::core::marker::PhantomData,
1386
-
__unsafe_private_named: (None, None, None),
1387
-
_phantom: ::core::marker::PhantomData,
1388
-
}
1389
-
}
1390
-
}
1391
-
1392
-
impl<'a, S: subfs_state::State> SubfsBuilder<'a, S> {
1393
-
/// Set the `flat` field (optional)
1394
-
pub fn flat(mut self, value: impl Into<Option<bool>>) -> Self {
1395
-
self.__unsafe_private_named.0 = value.into();
1396
-
self
1397
-
}
1398
-
/// Set the `flat` field to an Option value (optional)
1399
-
pub fn maybe_flat(mut self, value: Option<bool>) -> Self {
1400
-
self.__unsafe_private_named.0 = value;
1401
-
self
1402
-
}
1403
-
}
1404
-
1405
-
impl<'a, S> SubfsBuilder<'a, S>
1406
-
where
1407
-
S: subfs_state::State,
1408
-
S::Subject: subfs_state::IsUnset,
1409
-
{
1410
-
/// Set the `subject` field (required)
1411
-
pub fn subject(
1412
-
mut self,
1413
-
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
1414
-
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
1415
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
1416
-
SubfsBuilder {
1417
-
_phantom_state: ::core::marker::PhantomData,
1418
-
__unsafe_private_named: self.__unsafe_private_named,
1419
-
_phantom: ::core::marker::PhantomData,
1420
-
}
1421
-
}
1422
-
}
1423
-
1424
-
impl<'a, S> SubfsBuilder<'a, S>
1425
-
where
1426
-
S: subfs_state::State,
1427
-
S::Type: subfs_state::IsUnset,
1428
-
{
1429
-
/// Set the `type` field (required)
1430
-
pub fn r#type(
1431
-
mut self,
1432
-
value: impl Into<jacquard_common::CowStr<'a>>,
1433
-
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
1434
-
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1435
-
SubfsBuilder {
1436
-
_phantom_state: ::core::marker::PhantomData,
1437
-
__unsafe_private_named: self.__unsafe_private_named,
1438
-
_phantom: ::core::marker::PhantomData,
1439
-
}
1440
-
}
1441
-
}
1442
-
1443
-
impl<'a, S> SubfsBuilder<'a, S>
1444
-
where
1445
-
S: subfs_state::State,
1446
-
S::Type: subfs_state::IsSet,
1447
-
S::Subject: subfs_state::IsSet,
1448
-
{
1449
-
/// Build the final struct
1450
-
pub fn build(self) -> Subfs<'a> {
1451
-
Subfs {
1452
-
flat: self.__unsafe_private_named.0,
1453
-
subject: self.__unsafe_private_named.1.unwrap(),
1454
-
r#type: self.__unsafe_private_named.2.unwrap(),
1455
-
extra_data: Default::default(),
1456
-
}
1457
-
}
1458
-
/// Build the final struct with custom extra_data
1459
-
pub fn build_with_data(
1460
-
self,
1461
-
extra_data: std::collections::BTreeMap<
1462
-
jacquard_common::smol_str::SmolStr,
1463
-
jacquard_common::types::value::Data<'a>,
1464
-
>,
1465
-
) -> Subfs<'a> {
1466
-
Subfs {
1467
-
flat: self.__unsafe_private_named.0,
1468
-
subject: self.__unsafe_private_named.1.unwrap(),
1469
-
r#type: self.__unsafe_private_named.2.unwrap(),
1470
-
extra_data: Some(extra_data),
1471
-
}
1472
-
}
1473
-
}
1474
-
1475
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
1476
-
fn nsid() -> &'static str {
1477
-
"place.wisp.fs"
1478
-
}
1479
-
fn def_name() -> &'static str {
1480
-
"subfs"
1481
-
}
1482
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1483
-
lexicon_doc_place_wisp_fs()
1484
-
}
1485
-
fn validate(
1486
-
&self,
1487
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1488
-
Ok(())
1489
-
}
1490
-
}
-653
cli/src/place_wisp/settings.rs
-653
cli/src/place_wisp/settings.rs
···
1
-
// @generated by jacquard-lexicon. DO NOT EDIT.
2
-
//
3
-
// Lexicon: place.wisp.settings
4
-
//
5
-
// This file was automatically generated from Lexicon schemas.
6
-
// Any manual changes will be overwritten on the next regeneration.
7
-
8
-
/// Custom HTTP header configuration
9
-
#[jacquard_derive::lexicon]
10
-
#[derive(
11
-
serde::Serialize,
12
-
serde::Deserialize,
13
-
Debug,
14
-
Clone,
15
-
PartialEq,
16
-
Eq,
17
-
jacquard_derive::IntoStatic,
18
-
Default
19
-
)]
20
-
#[serde(rename_all = "camelCase")]
21
-
pub struct CustomHeader<'a> {
22
-
/// HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')
23
-
#[serde(borrow)]
24
-
pub name: jacquard_common::CowStr<'a>,
25
-
/// Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.
26
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
27
-
#[serde(borrow)]
28
-
pub path: std::option::Option<jacquard_common::CowStr<'a>>,
29
-
/// HTTP header value
30
-
#[serde(borrow)]
31
-
pub value: jacquard_common::CowStr<'a>,
32
-
}
33
-
34
-
fn lexicon_doc_place_wisp_settings() -> ::jacquard_lexicon::lexicon::LexiconDoc<
35
-
'static,
36
-
> {
37
-
::jacquard_lexicon::lexicon::LexiconDoc {
38
-
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
39
-
id: ::jacquard_common::CowStr::new_static("place.wisp.settings"),
40
-
revision: None,
41
-
description: None,
42
-
defs: {
43
-
let mut map = ::std::collections::BTreeMap::new();
44
-
map.insert(
45
-
::jacquard_common::smol_str::SmolStr::new_static("customHeader"),
46
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
47
-
description: Some(
48
-
::jacquard_common::CowStr::new_static(
49
-
"Custom HTTP header configuration",
50
-
),
51
-
),
52
-
required: Some(
53
-
vec![
54
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
55
-
::jacquard_common::smol_str::SmolStr::new_static("value")
56
-
],
57
-
),
58
-
nullable: None,
59
-
properties: {
60
-
#[allow(unused_mut)]
61
-
let mut map = ::std::collections::BTreeMap::new();
62
-
map.insert(
63
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
64
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
65
-
description: Some(
66
-
::jacquard_common::CowStr::new_static(
67
-
"HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
68
-
),
69
-
),
70
-
format: None,
71
-
default: None,
72
-
min_length: None,
73
-
max_length: Some(100usize),
74
-
min_graphemes: None,
75
-
max_graphemes: None,
76
-
r#enum: None,
77
-
r#const: None,
78
-
known_values: None,
79
-
}),
80
-
);
81
-
map.insert(
82
-
::jacquard_common::smol_str::SmolStr::new_static("path"),
83
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
84
-
description: Some(
85
-
::jacquard_common::CowStr::new_static(
86
-
"Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
87
-
),
88
-
),
89
-
format: None,
90
-
default: None,
91
-
min_length: None,
92
-
max_length: Some(500usize),
93
-
min_graphemes: None,
94
-
max_graphemes: None,
95
-
r#enum: None,
96
-
r#const: None,
97
-
known_values: None,
98
-
}),
99
-
);
100
-
map.insert(
101
-
::jacquard_common::smol_str::SmolStr::new_static("value"),
102
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
103
-
description: Some(
104
-
::jacquard_common::CowStr::new_static("HTTP header value"),
105
-
),
106
-
format: None,
107
-
default: None,
108
-
min_length: None,
109
-
max_length: Some(1000usize),
110
-
min_graphemes: None,
111
-
max_graphemes: None,
112
-
r#enum: None,
113
-
r#const: None,
114
-
known_values: None,
115
-
}),
116
-
);
117
-
map
118
-
},
119
-
}),
120
-
);
121
-
map.insert(
122
-
::jacquard_common::smol_str::SmolStr::new_static("main"),
123
-
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
124
-
description: Some(
125
-
::jacquard_common::CowStr::new_static(
126
-
"Configuration settings for a static site hosted on wisp.place",
127
-
),
128
-
),
129
-
key: Some(::jacquard_common::CowStr::new_static("any")),
130
-
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
131
-
description: None,
132
-
required: None,
133
-
nullable: None,
134
-
properties: {
135
-
#[allow(unused_mut)]
136
-
let mut map = ::std::collections::BTreeMap::new();
137
-
map.insert(
138
-
::jacquard_common::smol_str::SmolStr::new_static(
139
-
"cleanUrls",
140
-
),
141
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
142
-
description: None,
143
-
default: None,
144
-
r#const: None,
145
-
}),
146
-
);
147
-
map.insert(
148
-
::jacquard_common::smol_str::SmolStr::new_static(
149
-
"custom404",
150
-
),
151
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
152
-
description: Some(
153
-
::jacquard_common::CowStr::new_static(
154
-
"Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
155
-
),
156
-
),
157
-
format: None,
158
-
default: None,
159
-
min_length: None,
160
-
max_length: Some(500usize),
161
-
min_graphemes: None,
162
-
max_graphemes: None,
163
-
r#enum: None,
164
-
r#const: None,
165
-
known_values: None,
166
-
}),
167
-
);
168
-
map.insert(
169
-
::jacquard_common::smol_str::SmolStr::new_static(
170
-
"directoryListing",
171
-
),
172
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
173
-
description: None,
174
-
default: None,
175
-
r#const: None,
176
-
}),
177
-
);
178
-
map.insert(
179
-
::jacquard_common::smol_str::SmolStr::new_static("headers"),
180
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
181
-
description: Some(
182
-
::jacquard_common::CowStr::new_static(
183
-
"Custom HTTP headers to set on responses",
184
-
),
185
-
),
186
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
187
-
description: None,
188
-
r#ref: ::jacquard_common::CowStr::new_static(
189
-
"#customHeader",
190
-
),
191
-
}),
192
-
min_length: None,
193
-
max_length: Some(50usize),
194
-
}),
195
-
);
196
-
map.insert(
197
-
::jacquard_common::smol_str::SmolStr::new_static(
198
-
"indexFiles",
199
-
),
200
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
201
-
description: Some(
202
-
::jacquard_common::CowStr::new_static(
203
-
"Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
204
-
),
205
-
),
206
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::String(::jacquard_lexicon::lexicon::LexString {
207
-
description: None,
208
-
format: None,
209
-
default: None,
210
-
min_length: None,
211
-
max_length: Some(255usize),
212
-
min_graphemes: None,
213
-
max_graphemes: None,
214
-
r#enum: None,
215
-
r#const: None,
216
-
known_values: None,
217
-
}),
218
-
min_length: None,
219
-
max_length: Some(10usize),
220
-
}),
221
-
);
222
-
map.insert(
223
-
::jacquard_common::smol_str::SmolStr::new_static("spaMode"),
224
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
225
-
description: Some(
226
-
::jacquard_common::CowStr::new_static(
227
-
"File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.",
228
-
),
229
-
),
230
-
format: None,
231
-
default: None,
232
-
min_length: None,
233
-
max_length: Some(500usize),
234
-
min_graphemes: None,
235
-
max_graphemes: None,
236
-
r#enum: None,
237
-
r#const: None,
238
-
known_values: None,
239
-
}),
240
-
);
241
-
map
242
-
},
243
-
}),
244
-
}),
245
-
);
246
-
map
247
-
},
248
-
}
249
-
}
250
-
251
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for CustomHeader<'a> {
252
-
fn nsid() -> &'static str {
253
-
"place.wisp.settings"
254
-
}
255
-
fn def_name() -> &'static str {
256
-
"customHeader"
257
-
}
258
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
259
-
lexicon_doc_place_wisp_settings()
260
-
}
261
-
fn validate(
262
-
&self,
263
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
264
-
{
265
-
let value = &self.name;
266
-
#[allow(unused_comparisons)]
267
-
if <str>::len(value.as_ref()) > 100usize {
268
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
269
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
270
-
"name",
271
-
),
272
-
max: 100usize,
273
-
actual: <str>::len(value.as_ref()),
274
-
});
275
-
}
276
-
}
277
-
if let Some(ref value) = self.path {
278
-
#[allow(unused_comparisons)]
279
-
if <str>::len(value.as_ref()) > 500usize {
280
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
281
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
282
-
"path",
283
-
),
284
-
max: 500usize,
285
-
actual: <str>::len(value.as_ref()),
286
-
});
287
-
}
288
-
}
289
-
{
290
-
let value = &self.value;
291
-
#[allow(unused_comparisons)]
292
-
if <str>::len(value.as_ref()) > 1000usize {
293
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
294
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
295
-
"value",
296
-
),
297
-
max: 1000usize,
298
-
actual: <str>::len(value.as_ref()),
299
-
});
300
-
}
301
-
}
302
-
Ok(())
303
-
}
304
-
}
305
-
306
-
/// Configuration settings for a static site hosted on wisp.place
307
-
#[jacquard_derive::lexicon]
308
-
#[derive(
309
-
serde::Serialize,
310
-
serde::Deserialize,
311
-
Debug,
312
-
Clone,
313
-
PartialEq,
314
-
Eq,
315
-
jacquard_derive::IntoStatic
316
-
)]
317
-
#[serde(rename_all = "camelCase")]
318
-
pub struct Settings<'a> {
319
-
/// Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.
320
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
321
-
pub clean_urls: std::option::Option<bool>,
322
-
/// Custom 404 error page file path. Incompatible with directoryListing and spaMode.
323
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
324
-
#[serde(borrow)]
325
-
pub custom404: std::option::Option<jacquard_common::CowStr<'a>>,
326
-
/// Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.
327
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
328
-
pub directory_listing: std::option::Option<bool>,
329
-
/// Custom HTTP headers to set on responses
330
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
331
-
#[serde(borrow)]
332
-
pub headers: std::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
333
-
/// Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.
334
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
335
-
#[serde(borrow)]
336
-
pub index_files: std::option::Option<Vec<jacquard_common::CowStr<'a>>>,
337
-
/// File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.
338
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
339
-
#[serde(borrow)]
340
-
pub spa_mode: std::option::Option<jacquard_common::CowStr<'a>>,
341
-
}
342
-
343
-
pub mod settings_state {
344
-
345
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
346
-
#[allow(unused)]
347
-
use ::core::marker::PhantomData;
348
-
mod sealed {
349
-
pub trait Sealed {}
350
-
}
351
-
/// State trait tracking which required fields have been set
352
-
pub trait State: sealed::Sealed {}
353
-
/// Empty state - all required fields are unset
354
-
pub struct Empty(());
355
-
impl sealed::Sealed for Empty {}
356
-
impl State for Empty {}
357
-
/// Marker types for field names
358
-
#[allow(non_camel_case_types)]
359
-
pub mod members {}
360
-
}
361
-
362
-
/// Builder for constructing an instance of this type
363
-
pub struct SettingsBuilder<'a, S: settings_state::State> {
364
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
365
-
__unsafe_private_named: (
366
-
::core::option::Option<bool>,
367
-
::core::option::Option<jacquard_common::CowStr<'a>>,
368
-
::core::option::Option<bool>,
369
-
::core::option::Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
370
-
::core::option::Option<Vec<jacquard_common::CowStr<'a>>>,
371
-
::core::option::Option<jacquard_common::CowStr<'a>>,
372
-
),
373
-
_phantom: ::core::marker::PhantomData<&'a ()>,
374
-
}
375
-
376
-
impl<'a> Settings<'a> {
377
-
/// Create a new builder for this type
378
-
pub fn new() -> SettingsBuilder<'a, settings_state::Empty> {
379
-
SettingsBuilder::new()
380
-
}
381
-
}
382
-
383
-
impl<'a> SettingsBuilder<'a, settings_state::Empty> {
384
-
/// Create a new builder with all fields unset
385
-
pub fn new() -> Self {
386
-
SettingsBuilder {
387
-
_phantom_state: ::core::marker::PhantomData,
388
-
__unsafe_private_named: (None, None, None, None, None, None),
389
-
_phantom: ::core::marker::PhantomData,
390
-
}
391
-
}
392
-
}
393
-
394
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
395
-
/// Set the `cleanUrls` field (optional)
396
-
pub fn clean_urls(mut self, value: impl Into<Option<bool>>) -> Self {
397
-
self.__unsafe_private_named.0 = value.into();
398
-
self
399
-
}
400
-
/// Set the `cleanUrls` field to an Option value (optional)
401
-
pub fn maybe_clean_urls(mut self, value: Option<bool>) -> Self {
402
-
self.__unsafe_private_named.0 = value;
403
-
self
404
-
}
405
-
}
406
-
407
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
408
-
/// Set the `custom404` field (optional)
409
-
pub fn custom404(
410
-
mut self,
411
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
412
-
) -> Self {
413
-
self.__unsafe_private_named.1 = value.into();
414
-
self
415
-
}
416
-
/// Set the `custom404` field to an Option value (optional)
417
-
pub fn maybe_custom404(
418
-
mut self,
419
-
value: Option<jacquard_common::CowStr<'a>>,
420
-
) -> Self {
421
-
self.__unsafe_private_named.1 = value;
422
-
self
423
-
}
424
-
}
425
-
426
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
427
-
/// Set the `directoryListing` field (optional)
428
-
pub fn directory_listing(mut self, value: impl Into<Option<bool>>) -> Self {
429
-
self.__unsafe_private_named.2 = value.into();
430
-
self
431
-
}
432
-
/// Set the `directoryListing` field to an Option value (optional)
433
-
pub fn maybe_directory_listing(mut self, value: Option<bool>) -> Self {
434
-
self.__unsafe_private_named.2 = value;
435
-
self
436
-
}
437
-
}
438
-
439
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
440
-
/// Set the `headers` field (optional)
441
-
pub fn headers(
442
-
mut self,
443
-
value: impl Into<Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>>,
444
-
) -> Self {
445
-
self.__unsafe_private_named.3 = value.into();
446
-
self
447
-
}
448
-
/// Set the `headers` field to an Option value (optional)
449
-
pub fn maybe_headers(
450
-
mut self,
451
-
value: Option<Vec<crate::place_wisp::settings::CustomHeader<'a>>>,
452
-
) -> Self {
453
-
self.__unsafe_private_named.3 = value;
454
-
self
455
-
}
456
-
}
457
-
458
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
459
-
/// Set the `indexFiles` field (optional)
460
-
pub fn index_files(
461
-
mut self,
462
-
value: impl Into<Option<Vec<jacquard_common::CowStr<'a>>>>,
463
-
) -> Self {
464
-
self.__unsafe_private_named.4 = value.into();
465
-
self
466
-
}
467
-
/// Set the `indexFiles` field to an Option value (optional)
468
-
pub fn maybe_index_files(
469
-
mut self,
470
-
value: Option<Vec<jacquard_common::CowStr<'a>>>,
471
-
) -> Self {
472
-
self.__unsafe_private_named.4 = value;
473
-
self
474
-
}
475
-
}
476
-
477
-
impl<'a, S: settings_state::State> SettingsBuilder<'a, S> {
478
-
/// Set the `spaMode` field (optional)
479
-
pub fn spa_mode(
480
-
mut self,
481
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
482
-
) -> Self {
483
-
self.__unsafe_private_named.5 = value.into();
484
-
self
485
-
}
486
-
/// Set the `spaMode` field to an Option value (optional)
487
-
pub fn maybe_spa_mode(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
488
-
self.__unsafe_private_named.5 = value;
489
-
self
490
-
}
491
-
}
492
-
493
-
impl<'a, S> SettingsBuilder<'a, S>
494
-
where
495
-
S: settings_state::State,
496
-
{
497
-
/// Build the final struct
498
-
pub fn build(self) -> Settings<'a> {
499
-
Settings {
500
-
clean_urls: self.__unsafe_private_named.0,
501
-
custom404: self.__unsafe_private_named.1,
502
-
directory_listing: self.__unsafe_private_named.2,
503
-
headers: self.__unsafe_private_named.3,
504
-
index_files: self.__unsafe_private_named.4,
505
-
spa_mode: self.__unsafe_private_named.5,
506
-
extra_data: Default::default(),
507
-
}
508
-
}
509
-
/// Build the final struct with custom extra_data
510
-
pub fn build_with_data(
511
-
self,
512
-
extra_data: std::collections::BTreeMap<
513
-
jacquard_common::smol_str::SmolStr,
514
-
jacquard_common::types::value::Data<'a>,
515
-
>,
516
-
) -> Settings<'a> {
517
-
Settings {
518
-
clean_urls: self.__unsafe_private_named.0,
519
-
custom404: self.__unsafe_private_named.1,
520
-
directory_listing: self.__unsafe_private_named.2,
521
-
headers: self.__unsafe_private_named.3,
522
-
index_files: self.__unsafe_private_named.4,
523
-
spa_mode: self.__unsafe_private_named.5,
524
-
extra_data: Some(extra_data),
525
-
}
526
-
}
527
-
}
528
-
529
-
impl<'a> Settings<'a> {
530
-
pub fn uri(
531
-
uri: impl Into<jacquard_common::CowStr<'a>>,
532
-
) -> Result<
533
-
jacquard_common::types::uri::RecordUri<'a, SettingsRecord>,
534
-
jacquard_common::types::uri::UriError,
535
-
> {
536
-
jacquard_common::types::uri::RecordUri::try_from_uri(
537
-
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
538
-
)
539
-
}
540
-
}
541
-
542
-
/// Typed wrapper for GetRecord response with this collection's record type.
543
-
#[derive(
544
-
serde::Serialize,
545
-
serde::Deserialize,
546
-
Debug,
547
-
Clone,
548
-
PartialEq,
549
-
Eq,
550
-
jacquard_derive::IntoStatic
551
-
)]
552
-
#[serde(rename_all = "camelCase")]
553
-
pub struct SettingsGetRecordOutput<'a> {
554
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
555
-
#[serde(borrow)]
556
-
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
557
-
#[serde(borrow)]
558
-
pub uri: jacquard_common::types::string::AtUri<'a>,
559
-
#[serde(borrow)]
560
-
pub value: Settings<'a>,
561
-
}
562
-
563
-
impl From<SettingsGetRecordOutput<'_>> for Settings<'_> {
564
-
fn from(output: SettingsGetRecordOutput<'_>) -> Self {
565
-
use jacquard_common::IntoStatic;
566
-
output.value.into_static()
567
-
}
568
-
}
569
-
570
-
impl jacquard_common::types::collection::Collection for Settings<'_> {
571
-
const NSID: &'static str = "place.wisp.settings";
572
-
type Record = SettingsRecord;
573
-
}
574
-
575
-
/// Marker type for deserializing records from this collection.
576
-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
577
-
pub struct SettingsRecord;
578
-
impl jacquard_common::xrpc::XrpcResp for SettingsRecord {
579
-
const NSID: &'static str = "place.wisp.settings";
580
-
const ENCODING: &'static str = "application/json";
581
-
type Output<'de> = SettingsGetRecordOutput<'de>;
582
-
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
583
-
}
584
-
585
-
impl jacquard_common::types::collection::Collection for SettingsRecord {
586
-
const NSID: &'static str = "place.wisp.settings";
587
-
type Record = SettingsRecord;
588
-
}
589
-
590
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Settings<'a> {
591
-
fn nsid() -> &'static str {
592
-
"place.wisp.settings"
593
-
}
594
-
fn def_name() -> &'static str {
595
-
"main"
596
-
}
597
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
598
-
lexicon_doc_place_wisp_settings()
599
-
}
600
-
fn validate(
601
-
&self,
602
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
603
-
if let Some(ref value) = self.custom404 {
604
-
#[allow(unused_comparisons)]
605
-
if <str>::len(value.as_ref()) > 500usize {
606
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
607
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
608
-
"custom404",
609
-
),
610
-
max: 500usize,
611
-
actual: <str>::len(value.as_ref()),
612
-
});
613
-
}
614
-
}
615
-
if let Some(ref value) = self.headers {
616
-
#[allow(unused_comparisons)]
617
-
if value.len() > 50usize {
618
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
619
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
620
-
"headers",
621
-
),
622
-
max: 50usize,
623
-
actual: value.len(),
624
-
});
625
-
}
626
-
}
627
-
if let Some(ref value) = self.index_files {
628
-
#[allow(unused_comparisons)]
629
-
if value.len() > 10usize {
630
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
631
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
632
-
"index_files",
633
-
),
634
-
max: 10usize,
635
-
actual: value.len(),
636
-
});
637
-
}
638
-
}
639
-
if let Some(ref value) = self.spa_mode {
640
-
#[allow(unused_comparisons)]
641
-
if <str>::len(value.as_ref()) > 500usize {
642
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
643
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
644
-
"spa_mode",
645
-
),
646
-
max: 500usize,
647
-
actual: <str>::len(value.as_ref()),
648
-
});
649
-
}
650
-
}
651
-
Ok(())
652
-
}
653
-
}
-1408
cli/src/place_wisp/subfs.rs
-1408
cli/src/place_wisp/subfs.rs
···
1
-
// @generated by jacquard-lexicon. DO NOT EDIT.
2
-
//
3
-
// Lexicon: place.wisp.subfs
4
-
//
5
-
// This file was automatically generated from Lexicon schemas.
6
-
// Any manual changes will be overwritten on the next regeneration.
7
-
8
-
#[jacquard_derive::lexicon]
9
-
#[derive(
10
-
serde::Serialize,
11
-
serde::Deserialize,
12
-
Debug,
13
-
Clone,
14
-
PartialEq,
15
-
Eq,
16
-
jacquard_derive::IntoStatic
17
-
)]
18
-
#[serde(rename_all = "camelCase")]
19
-
pub struct Directory<'a> {
20
-
#[serde(borrow)]
21
-
pub entries: Vec<crate::place_wisp::subfs::Entry<'a>>,
22
-
#[serde(borrow)]
23
-
pub r#type: jacquard_common::CowStr<'a>,
24
-
}
25
-
26
-
pub mod directory_state {
27
-
28
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
29
-
#[allow(unused)]
30
-
use ::core::marker::PhantomData;
31
-
mod sealed {
32
-
pub trait Sealed {}
33
-
}
34
-
/// State trait tracking which required fields have been set
35
-
pub trait State: sealed::Sealed {
36
-
type Type;
37
-
type Entries;
38
-
}
39
-
/// Empty state - all required fields are unset
40
-
pub struct Empty(());
41
-
impl sealed::Sealed for Empty {}
42
-
impl State for Empty {
43
-
type Type = Unset;
44
-
type Entries = Unset;
45
-
}
46
-
///State transition - sets the `type` field to Set
47
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
48
-
impl<S: State> sealed::Sealed for SetType<S> {}
49
-
impl<S: State> State for SetType<S> {
50
-
type Type = Set<members::r#type>;
51
-
type Entries = S::Entries;
52
-
}
53
-
///State transition - sets the `entries` field to Set
54
-
pub struct SetEntries<S: State = Empty>(PhantomData<fn() -> S>);
55
-
impl<S: State> sealed::Sealed for SetEntries<S> {}
56
-
impl<S: State> State for SetEntries<S> {
57
-
type Type = S::Type;
58
-
type Entries = Set<members::entries>;
59
-
}
60
-
/// Marker types for field names
61
-
#[allow(non_camel_case_types)]
62
-
pub mod members {
63
-
///Marker type for the `type` field
64
-
pub struct r#type(());
65
-
///Marker type for the `entries` field
66
-
pub struct entries(());
67
-
}
68
-
}
69
-
70
-
/// Builder for constructing an instance of this type
71
-
pub struct DirectoryBuilder<'a, S: directory_state::State> {
72
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
73
-
__unsafe_private_named: (
74
-
::core::option::Option<Vec<crate::place_wisp::subfs::Entry<'a>>>,
75
-
::core::option::Option<jacquard_common::CowStr<'a>>,
76
-
),
77
-
_phantom: ::core::marker::PhantomData<&'a ()>,
78
-
}
79
-
80
-
impl<'a> Directory<'a> {
81
-
/// Create a new builder for this type
82
-
pub fn new() -> DirectoryBuilder<'a, directory_state::Empty> {
83
-
DirectoryBuilder::new()
84
-
}
85
-
}
86
-
87
-
impl<'a> DirectoryBuilder<'a, directory_state::Empty> {
88
-
/// Create a new builder with all fields unset
89
-
pub fn new() -> Self {
90
-
DirectoryBuilder {
91
-
_phantom_state: ::core::marker::PhantomData,
92
-
__unsafe_private_named: (None, None),
93
-
_phantom: ::core::marker::PhantomData,
94
-
}
95
-
}
96
-
}
97
-
98
-
impl<'a, S> DirectoryBuilder<'a, S>
99
-
where
100
-
S: directory_state::State,
101
-
S::Entries: directory_state::IsUnset,
102
-
{
103
-
/// Set the `entries` field (required)
104
-
pub fn entries(
105
-
mut self,
106
-
value: impl Into<Vec<crate::place_wisp::subfs::Entry<'a>>>,
107
-
) -> DirectoryBuilder<'a, directory_state::SetEntries<S>> {
108
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
109
-
DirectoryBuilder {
110
-
_phantom_state: ::core::marker::PhantomData,
111
-
__unsafe_private_named: self.__unsafe_private_named,
112
-
_phantom: ::core::marker::PhantomData,
113
-
}
114
-
}
115
-
}
116
-
117
-
impl<'a, S> DirectoryBuilder<'a, S>
118
-
where
119
-
S: directory_state::State,
120
-
S::Type: directory_state::IsUnset,
121
-
{
122
-
/// Set the `type` field (required)
123
-
pub fn r#type(
124
-
mut self,
125
-
value: impl Into<jacquard_common::CowStr<'a>>,
126
-
) -> DirectoryBuilder<'a, directory_state::SetType<S>> {
127
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
128
-
DirectoryBuilder {
129
-
_phantom_state: ::core::marker::PhantomData,
130
-
__unsafe_private_named: self.__unsafe_private_named,
131
-
_phantom: ::core::marker::PhantomData,
132
-
}
133
-
}
134
-
}
135
-
136
-
impl<'a, S> DirectoryBuilder<'a, S>
137
-
where
138
-
S: directory_state::State,
139
-
S::Type: directory_state::IsSet,
140
-
S::Entries: directory_state::IsSet,
141
-
{
142
-
/// Build the final struct
143
-
pub fn build(self) -> Directory<'a> {
144
-
Directory {
145
-
entries: self.__unsafe_private_named.0.unwrap(),
146
-
r#type: self.__unsafe_private_named.1.unwrap(),
147
-
extra_data: Default::default(),
148
-
}
149
-
}
150
-
/// Build the final struct with custom extra_data
151
-
pub fn build_with_data(
152
-
self,
153
-
extra_data: std::collections::BTreeMap<
154
-
jacquard_common::smol_str::SmolStr,
155
-
jacquard_common::types::value::Data<'a>,
156
-
>,
157
-
) -> Directory<'a> {
158
-
Directory {
159
-
entries: self.__unsafe_private_named.0.unwrap(),
160
-
r#type: self.__unsafe_private_named.1.unwrap(),
161
-
extra_data: Some(extra_data),
162
-
}
163
-
}
164
-
}
165
-
166
-
fn lexicon_doc_place_wisp_subfs() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
167
-
::jacquard_lexicon::lexicon::LexiconDoc {
168
-
lexicon: ::jacquard_lexicon::lexicon::Lexicon::Lexicon1,
169
-
id: ::jacquard_common::CowStr::new_static("place.wisp.subfs"),
170
-
revision: None,
171
-
description: None,
172
-
defs: {
173
-
let mut map = ::std::collections::BTreeMap::new();
174
-
map.insert(
175
-
::jacquard_common::smol_str::SmolStr::new_static("directory"),
176
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
177
-
description: None,
178
-
required: Some(
179
-
vec![
180
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
181
-
::jacquard_common::smol_str::SmolStr::new_static("entries")
182
-
],
183
-
),
184
-
nullable: None,
185
-
properties: {
186
-
#[allow(unused_mut)]
187
-
let mut map = ::std::collections::BTreeMap::new();
188
-
map.insert(
189
-
::jacquard_common::smol_str::SmolStr::new_static("entries"),
190
-
::jacquard_lexicon::lexicon::LexObjectProperty::Array(::jacquard_lexicon::lexicon::LexArray {
191
-
description: None,
192
-
items: ::jacquard_lexicon::lexicon::LexArrayItem::Ref(::jacquard_lexicon::lexicon::LexRef {
193
-
description: None,
194
-
r#ref: ::jacquard_common::CowStr::new_static("#entry"),
195
-
}),
196
-
min_length: None,
197
-
max_length: Some(500usize),
198
-
}),
199
-
);
200
-
map.insert(
201
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
202
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
203
-
description: None,
204
-
format: None,
205
-
default: None,
206
-
min_length: None,
207
-
max_length: None,
208
-
min_graphemes: None,
209
-
max_graphemes: None,
210
-
r#enum: None,
211
-
r#const: None,
212
-
known_values: None,
213
-
}),
214
-
);
215
-
map
216
-
},
217
-
}),
218
-
);
219
-
map.insert(
220
-
::jacquard_common::smol_str::SmolStr::new_static("entry"),
221
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
222
-
description: None,
223
-
required: Some(
224
-
vec![
225
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
226
-
::jacquard_common::smol_str::SmolStr::new_static("node")
227
-
],
228
-
),
229
-
nullable: None,
230
-
properties: {
231
-
#[allow(unused_mut)]
232
-
let mut map = ::std::collections::BTreeMap::new();
233
-
map.insert(
234
-
::jacquard_common::smol_str::SmolStr::new_static("name"),
235
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
236
-
description: None,
237
-
format: None,
238
-
default: None,
239
-
min_length: None,
240
-
max_length: Some(255usize),
241
-
min_graphemes: None,
242
-
max_graphemes: None,
243
-
r#enum: None,
244
-
r#const: None,
245
-
known_values: None,
246
-
}),
247
-
);
248
-
map.insert(
249
-
::jacquard_common::smol_str::SmolStr::new_static("node"),
250
-
::jacquard_lexicon::lexicon::LexObjectProperty::Union(::jacquard_lexicon::lexicon::LexRefUnion {
251
-
description: None,
252
-
refs: vec![
253
-
::jacquard_common::CowStr::new_static("#file"),
254
-
::jacquard_common::CowStr::new_static("#directory"),
255
-
::jacquard_common::CowStr::new_static("#subfs")
256
-
],
257
-
closed: None,
258
-
}),
259
-
);
260
-
map
261
-
},
262
-
}),
263
-
);
264
-
map.insert(
265
-
::jacquard_common::smol_str::SmolStr::new_static("file"),
266
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
267
-
description: None,
268
-
required: Some(
269
-
vec![
270
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
271
-
::jacquard_common::smol_str::SmolStr::new_static("blob")
272
-
],
273
-
),
274
-
nullable: None,
275
-
properties: {
276
-
#[allow(unused_mut)]
277
-
let mut map = ::std::collections::BTreeMap::new();
278
-
map.insert(
279
-
::jacquard_common::smol_str::SmolStr::new_static("base64"),
280
-
::jacquard_lexicon::lexicon::LexObjectProperty::Boolean(::jacquard_lexicon::lexicon::LexBoolean {
281
-
description: None,
282
-
default: None,
283
-
r#const: None,
284
-
}),
285
-
);
286
-
map.insert(
287
-
::jacquard_common::smol_str::SmolStr::new_static("blob"),
288
-
::jacquard_lexicon::lexicon::LexObjectProperty::Blob(::jacquard_lexicon::lexicon::LexBlob {
289
-
description: None,
290
-
accept: None,
291
-
max_size: None,
292
-
}),
293
-
);
294
-
map.insert(
295
-
::jacquard_common::smol_str::SmolStr::new_static("encoding"),
296
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
297
-
description: Some(
298
-
::jacquard_common::CowStr::new_static(
299
-
"Content encoding (e.g., gzip for compressed files)",
300
-
),
301
-
),
302
-
format: None,
303
-
default: None,
304
-
min_length: None,
305
-
max_length: None,
306
-
min_graphemes: None,
307
-
max_graphemes: None,
308
-
r#enum: None,
309
-
r#const: None,
310
-
known_values: None,
311
-
}),
312
-
);
313
-
map.insert(
314
-
::jacquard_common::smol_str::SmolStr::new_static("mimeType"),
315
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
316
-
description: Some(
317
-
::jacquard_common::CowStr::new_static(
318
-
"Original MIME type before compression",
319
-
),
320
-
),
321
-
format: None,
322
-
default: None,
323
-
min_length: None,
324
-
max_length: None,
325
-
min_graphemes: None,
326
-
max_graphemes: None,
327
-
r#enum: None,
328
-
r#const: None,
329
-
known_values: None,
330
-
}),
331
-
);
332
-
map.insert(
333
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
334
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
335
-
description: None,
336
-
format: None,
337
-
default: None,
338
-
min_length: None,
339
-
max_length: None,
340
-
min_graphemes: None,
341
-
max_graphemes: None,
342
-
r#enum: None,
343
-
r#const: None,
344
-
known_values: None,
345
-
}),
346
-
);
347
-
map
348
-
},
349
-
}),
350
-
);
351
-
map.insert(
352
-
::jacquard_common::smol_str::SmolStr::new_static("main"),
353
-
::jacquard_lexicon::lexicon::LexUserType::Record(::jacquard_lexicon::lexicon::LexRecord {
354
-
description: Some(
355
-
::jacquard_common::CowStr::new_static(
356
-
"Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.",
357
-
),
358
-
),
359
-
key: None,
360
-
record: ::jacquard_lexicon::lexicon::LexRecordRecord::Object(::jacquard_lexicon::lexicon::LexObject {
361
-
description: None,
362
-
required: Some(
363
-
vec![
364
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
365
-
::jacquard_common::smol_str::SmolStr::new_static("createdAt")
366
-
],
367
-
),
368
-
nullable: None,
369
-
properties: {
370
-
#[allow(unused_mut)]
371
-
let mut map = ::std::collections::BTreeMap::new();
372
-
map.insert(
373
-
::jacquard_common::smol_str::SmolStr::new_static(
374
-
"createdAt",
375
-
),
376
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
377
-
description: None,
378
-
format: Some(
379
-
::jacquard_lexicon::lexicon::LexStringFormat::Datetime,
380
-
),
381
-
default: None,
382
-
min_length: None,
383
-
max_length: None,
384
-
min_graphemes: None,
385
-
max_graphemes: None,
386
-
r#enum: None,
387
-
r#const: None,
388
-
known_values: None,
389
-
}),
390
-
);
391
-
map.insert(
392
-
::jacquard_common::smol_str::SmolStr::new_static(
393
-
"fileCount",
394
-
),
395
-
::jacquard_lexicon::lexicon::LexObjectProperty::Integer(::jacquard_lexicon::lexicon::LexInteger {
396
-
description: None,
397
-
default: None,
398
-
minimum: Some(0i64),
399
-
maximum: Some(1000i64),
400
-
r#enum: None,
401
-
r#const: None,
402
-
}),
403
-
);
404
-
map.insert(
405
-
::jacquard_common::smol_str::SmolStr::new_static("root"),
406
-
::jacquard_lexicon::lexicon::LexObjectProperty::Ref(::jacquard_lexicon::lexicon::LexRef {
407
-
description: None,
408
-
r#ref: ::jacquard_common::CowStr::new_static("#directory"),
409
-
}),
410
-
);
411
-
map
412
-
},
413
-
}),
414
-
}),
415
-
);
416
-
map.insert(
417
-
::jacquard_common::smol_str::SmolStr::new_static("subfs"),
418
-
::jacquard_lexicon::lexicon::LexUserType::Object(::jacquard_lexicon::lexicon::LexObject {
419
-
description: None,
420
-
required: Some(
421
-
vec![
422
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
423
-
::jacquard_common::smol_str::SmolStr::new_static("subject")
424
-
],
425
-
),
426
-
nullable: None,
427
-
properties: {
428
-
#[allow(unused_mut)]
429
-
let mut map = ::std::collections::BTreeMap::new();
430
-
map.insert(
431
-
::jacquard_common::smol_str::SmolStr::new_static("subject"),
432
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
433
-
description: Some(
434
-
::jacquard_common::CowStr::new_static(
435
-
"AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures.",
436
-
),
437
-
),
438
-
format: Some(
439
-
::jacquard_lexicon::lexicon::LexStringFormat::AtUri,
440
-
),
441
-
default: None,
442
-
min_length: None,
443
-
max_length: None,
444
-
min_graphemes: None,
445
-
max_graphemes: None,
446
-
r#enum: None,
447
-
r#const: None,
448
-
known_values: None,
449
-
}),
450
-
);
451
-
map.insert(
452
-
::jacquard_common::smol_str::SmolStr::new_static("type"),
453
-
::jacquard_lexicon::lexicon::LexObjectProperty::String(::jacquard_lexicon::lexicon::LexString {
454
-
description: None,
455
-
format: None,
456
-
default: None,
457
-
min_length: None,
458
-
max_length: None,
459
-
min_graphemes: None,
460
-
max_graphemes: None,
461
-
r#enum: None,
462
-
r#const: None,
463
-
known_values: None,
464
-
}),
465
-
);
466
-
map
467
-
},
468
-
}),
469
-
);
470
-
map
471
-
},
472
-
}
473
-
}
474
-
475
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Directory<'a> {
476
-
fn nsid() -> &'static str {
477
-
"place.wisp.subfs"
478
-
}
479
-
fn def_name() -> &'static str {
480
-
"directory"
481
-
}
482
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
483
-
lexicon_doc_place_wisp_subfs()
484
-
}
485
-
fn validate(
486
-
&self,
487
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
488
-
{
489
-
let value = &self.entries;
490
-
#[allow(unused_comparisons)]
491
-
if value.len() > 500usize {
492
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
493
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
494
-
"entries",
495
-
),
496
-
max: 500usize,
497
-
actual: value.len(),
498
-
});
499
-
}
500
-
}
501
-
Ok(())
502
-
}
503
-
}
504
-
505
-
#[jacquard_derive::lexicon]
506
-
#[derive(
507
-
serde::Serialize,
508
-
serde::Deserialize,
509
-
Debug,
510
-
Clone,
511
-
PartialEq,
512
-
Eq,
513
-
jacquard_derive::IntoStatic
514
-
)]
515
-
#[serde(rename_all = "camelCase")]
516
-
pub struct Entry<'a> {
517
-
#[serde(borrow)]
518
-
pub name: jacquard_common::CowStr<'a>,
519
-
#[serde(borrow)]
520
-
pub node: EntryNode<'a>,
521
-
}
522
-
523
-
pub mod entry_state {
524
-
525
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
526
-
#[allow(unused)]
527
-
use ::core::marker::PhantomData;
528
-
mod sealed {
529
-
pub trait Sealed {}
530
-
}
531
-
/// State trait tracking which required fields have been set
532
-
pub trait State: sealed::Sealed {
533
-
type Name;
534
-
type Node;
535
-
}
536
-
/// Empty state - all required fields are unset
537
-
pub struct Empty(());
538
-
impl sealed::Sealed for Empty {}
539
-
impl State for Empty {
540
-
type Name = Unset;
541
-
type Node = Unset;
542
-
}
543
-
///State transition - sets the `name` field to Set
544
-
pub struct SetName<S: State = Empty>(PhantomData<fn() -> S>);
545
-
impl<S: State> sealed::Sealed for SetName<S> {}
546
-
impl<S: State> State for SetName<S> {
547
-
type Name = Set<members::name>;
548
-
type Node = S::Node;
549
-
}
550
-
///State transition - sets the `node` field to Set
551
-
pub struct SetNode<S: State = Empty>(PhantomData<fn() -> S>);
552
-
impl<S: State> sealed::Sealed for SetNode<S> {}
553
-
impl<S: State> State for SetNode<S> {
554
-
type Name = S::Name;
555
-
type Node = Set<members::node>;
556
-
}
557
-
/// Marker types for field names
558
-
#[allow(non_camel_case_types)]
559
-
pub mod members {
560
-
///Marker type for the `name` field
561
-
pub struct name(());
562
-
///Marker type for the `node` field
563
-
pub struct node(());
564
-
}
565
-
}
566
-
567
-
/// Builder for constructing an instance of this type
568
-
pub struct EntryBuilder<'a, S: entry_state::State> {
569
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
570
-
__unsafe_private_named: (
571
-
::core::option::Option<jacquard_common::CowStr<'a>>,
572
-
::core::option::Option<EntryNode<'a>>,
573
-
),
574
-
_phantom: ::core::marker::PhantomData<&'a ()>,
575
-
}
576
-
577
-
impl<'a> Entry<'a> {
578
-
/// Create a new builder for this type
579
-
pub fn new() -> EntryBuilder<'a, entry_state::Empty> {
580
-
EntryBuilder::new()
581
-
}
582
-
}
583
-
584
-
impl<'a> EntryBuilder<'a, entry_state::Empty> {
585
-
/// Create a new builder with all fields unset
586
-
pub fn new() -> Self {
587
-
EntryBuilder {
588
-
_phantom_state: ::core::marker::PhantomData,
589
-
__unsafe_private_named: (None, None),
590
-
_phantom: ::core::marker::PhantomData,
591
-
}
592
-
}
593
-
}
594
-
595
-
impl<'a, S> EntryBuilder<'a, S>
596
-
where
597
-
S: entry_state::State,
598
-
S::Name: entry_state::IsUnset,
599
-
{
600
-
/// Set the `name` field (required)
601
-
pub fn name(
602
-
mut self,
603
-
value: impl Into<jacquard_common::CowStr<'a>>,
604
-
) -> EntryBuilder<'a, entry_state::SetName<S>> {
605
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
606
-
EntryBuilder {
607
-
_phantom_state: ::core::marker::PhantomData,
608
-
__unsafe_private_named: self.__unsafe_private_named,
609
-
_phantom: ::core::marker::PhantomData,
610
-
}
611
-
}
612
-
}
613
-
614
-
impl<'a, S> EntryBuilder<'a, S>
615
-
where
616
-
S: entry_state::State,
617
-
S::Node: entry_state::IsUnset,
618
-
{
619
-
/// Set the `node` field (required)
620
-
pub fn node(
621
-
mut self,
622
-
value: impl Into<EntryNode<'a>>,
623
-
) -> EntryBuilder<'a, entry_state::SetNode<S>> {
624
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
625
-
EntryBuilder {
626
-
_phantom_state: ::core::marker::PhantomData,
627
-
__unsafe_private_named: self.__unsafe_private_named,
628
-
_phantom: ::core::marker::PhantomData,
629
-
}
630
-
}
631
-
}
632
-
633
-
impl<'a, S> EntryBuilder<'a, S>
634
-
where
635
-
S: entry_state::State,
636
-
S::Name: entry_state::IsSet,
637
-
S::Node: entry_state::IsSet,
638
-
{
639
-
/// Build the final struct
640
-
pub fn build(self) -> Entry<'a> {
641
-
Entry {
642
-
name: self.__unsafe_private_named.0.unwrap(),
643
-
node: self.__unsafe_private_named.1.unwrap(),
644
-
extra_data: Default::default(),
645
-
}
646
-
}
647
-
/// Build the final struct with custom extra_data
648
-
pub fn build_with_data(
649
-
self,
650
-
extra_data: std::collections::BTreeMap<
651
-
jacquard_common::smol_str::SmolStr,
652
-
jacquard_common::types::value::Data<'a>,
653
-
>,
654
-
) -> Entry<'a> {
655
-
Entry {
656
-
name: self.__unsafe_private_named.0.unwrap(),
657
-
node: self.__unsafe_private_named.1.unwrap(),
658
-
extra_data: Some(extra_data),
659
-
}
660
-
}
661
-
}
662
-
663
-
#[jacquard_derive::open_union]
664
-
#[derive(
665
-
serde::Serialize,
666
-
serde::Deserialize,
667
-
Debug,
668
-
Clone,
669
-
PartialEq,
670
-
Eq,
671
-
jacquard_derive::IntoStatic
672
-
)]
673
-
#[serde(tag = "$type")]
674
-
#[serde(bound(deserialize = "'de: 'a"))]
675
-
pub enum EntryNode<'a> {
676
-
#[serde(rename = "place.wisp.subfs#file")]
677
-
File(Box<crate::place_wisp::subfs::File<'a>>),
678
-
#[serde(rename = "place.wisp.subfs#directory")]
679
-
Directory(Box<crate::place_wisp::subfs::Directory<'a>>),
680
-
#[serde(rename = "place.wisp.subfs#subfs")]
681
-
Subfs(Box<crate::place_wisp::subfs::Subfs<'a>>),
682
-
}
683
-
684
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Entry<'a> {
685
-
fn nsid() -> &'static str {
686
-
"place.wisp.subfs"
687
-
}
688
-
fn def_name() -> &'static str {
689
-
"entry"
690
-
}
691
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
692
-
lexicon_doc_place_wisp_subfs()
693
-
}
694
-
fn validate(
695
-
&self,
696
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
697
-
{
698
-
let value = &self.name;
699
-
#[allow(unused_comparisons)]
700
-
if <str>::len(value.as_ref()) > 255usize {
701
-
return Err(::jacquard_lexicon::validation::ConstraintError::MaxLength {
702
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
703
-
"name",
704
-
),
705
-
max: 255usize,
706
-
actual: <str>::len(value.as_ref()),
707
-
});
708
-
}
709
-
}
710
-
Ok(())
711
-
}
712
-
}
713
-
714
-
#[jacquard_derive::lexicon]
715
-
#[derive(
716
-
serde::Serialize,
717
-
serde::Deserialize,
718
-
Debug,
719
-
Clone,
720
-
PartialEq,
721
-
Eq,
722
-
jacquard_derive::IntoStatic
723
-
)]
724
-
#[serde(rename_all = "camelCase")]
725
-
pub struct File<'a> {
726
-
/// True if blob content is base64-encoded (used to bypass PDS content sniffing)
727
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
728
-
pub base64: Option<bool>,
729
-
/// Content blob ref
730
-
#[serde(borrow)]
731
-
pub blob: jacquard_common::types::blob::BlobRef<'a>,
732
-
/// Content encoding (e.g., gzip for compressed files)
733
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
734
-
#[serde(borrow)]
735
-
pub encoding: Option<jacquard_common::CowStr<'a>>,
736
-
/// Original MIME type before compression
737
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
738
-
#[serde(borrow)]
739
-
pub mime_type: Option<jacquard_common::CowStr<'a>>,
740
-
#[serde(borrow)]
741
-
pub r#type: jacquard_common::CowStr<'a>,
742
-
}
743
-
744
-
pub mod file_state {
745
-
746
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
747
-
#[allow(unused)]
748
-
use ::core::marker::PhantomData;
749
-
mod sealed {
750
-
pub trait Sealed {}
751
-
}
752
-
/// State trait tracking which required fields have been set
753
-
pub trait State: sealed::Sealed {
754
-
type Type;
755
-
type Blob;
756
-
}
757
-
/// Empty state - all required fields are unset
758
-
pub struct Empty(());
759
-
impl sealed::Sealed for Empty {}
760
-
impl State for Empty {
761
-
type Type = Unset;
762
-
type Blob = Unset;
763
-
}
764
-
///State transition - sets the `type` field to Set
765
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
766
-
impl<S: State> sealed::Sealed for SetType<S> {}
767
-
impl<S: State> State for SetType<S> {
768
-
type Type = Set<members::r#type>;
769
-
type Blob = S::Blob;
770
-
}
771
-
///State transition - sets the `blob` field to Set
772
-
pub struct SetBlob<S: State = Empty>(PhantomData<fn() -> S>);
773
-
impl<S: State> sealed::Sealed for SetBlob<S> {}
774
-
impl<S: State> State for SetBlob<S> {
775
-
type Type = S::Type;
776
-
type Blob = Set<members::blob>;
777
-
}
778
-
/// Marker types for field names
779
-
#[allow(non_camel_case_types)]
780
-
pub mod members {
781
-
///Marker type for the `type` field
782
-
pub struct r#type(());
783
-
///Marker type for the `blob` field
784
-
pub struct blob(());
785
-
}
786
-
}
787
-
788
-
/// Builder for constructing an instance of this type
789
-
pub struct FileBuilder<'a, S: file_state::State> {
790
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
791
-
__unsafe_private_named: (
792
-
::core::option::Option<bool>,
793
-
::core::option::Option<jacquard_common::types::blob::BlobRef<'a>>,
794
-
::core::option::Option<jacquard_common::CowStr<'a>>,
795
-
::core::option::Option<jacquard_common::CowStr<'a>>,
796
-
::core::option::Option<jacquard_common::CowStr<'a>>,
797
-
),
798
-
_phantom: ::core::marker::PhantomData<&'a ()>,
799
-
}
800
-
801
-
impl<'a> File<'a> {
802
-
/// Create a new builder for this type
803
-
pub fn new() -> FileBuilder<'a, file_state::Empty> {
804
-
FileBuilder::new()
805
-
}
806
-
}
807
-
808
-
impl<'a> FileBuilder<'a, file_state::Empty> {
809
-
/// Create a new builder with all fields unset
810
-
pub fn new() -> Self {
811
-
FileBuilder {
812
-
_phantom_state: ::core::marker::PhantomData,
813
-
__unsafe_private_named: (None, None, None, None, None),
814
-
_phantom: ::core::marker::PhantomData,
815
-
}
816
-
}
817
-
}
818
-
819
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
820
-
/// Set the `base64` field (optional)
821
-
pub fn base64(mut self, value: impl Into<Option<bool>>) -> Self {
822
-
self.__unsafe_private_named.0 = value.into();
823
-
self
824
-
}
825
-
/// Set the `base64` field to an Option value (optional)
826
-
pub fn maybe_base64(mut self, value: Option<bool>) -> Self {
827
-
self.__unsafe_private_named.0 = value;
828
-
self
829
-
}
830
-
}
831
-
832
-
impl<'a, S> FileBuilder<'a, S>
833
-
where
834
-
S: file_state::State,
835
-
S::Blob: file_state::IsUnset,
836
-
{
837
-
/// Set the `blob` field (required)
838
-
pub fn blob(
839
-
mut self,
840
-
value: impl Into<jacquard_common::types::blob::BlobRef<'a>>,
841
-
) -> FileBuilder<'a, file_state::SetBlob<S>> {
842
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
843
-
FileBuilder {
844
-
_phantom_state: ::core::marker::PhantomData,
845
-
__unsafe_private_named: self.__unsafe_private_named,
846
-
_phantom: ::core::marker::PhantomData,
847
-
}
848
-
}
849
-
}
850
-
851
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
852
-
/// Set the `encoding` field (optional)
853
-
pub fn encoding(
854
-
mut self,
855
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
856
-
) -> Self {
857
-
self.__unsafe_private_named.2 = value.into();
858
-
self
859
-
}
860
-
/// Set the `encoding` field to an Option value (optional)
861
-
pub fn maybe_encoding(mut self, value: Option<jacquard_common::CowStr<'a>>) -> Self {
862
-
self.__unsafe_private_named.2 = value;
863
-
self
864
-
}
865
-
}
866
-
867
-
impl<'a, S: file_state::State> FileBuilder<'a, S> {
868
-
/// Set the `mimeType` field (optional)
869
-
pub fn mime_type(
870
-
mut self,
871
-
value: impl Into<Option<jacquard_common::CowStr<'a>>>,
872
-
) -> Self {
873
-
self.__unsafe_private_named.3 = value.into();
874
-
self
875
-
}
876
-
/// Set the `mimeType` field to an Option value (optional)
877
-
pub fn maybe_mime_type(
878
-
mut self,
879
-
value: Option<jacquard_common::CowStr<'a>>,
880
-
) -> Self {
881
-
self.__unsafe_private_named.3 = value;
882
-
self
883
-
}
884
-
}
885
-
886
-
impl<'a, S> FileBuilder<'a, S>
887
-
where
888
-
S: file_state::State,
889
-
S::Type: file_state::IsUnset,
890
-
{
891
-
/// Set the `type` field (required)
892
-
pub fn r#type(
893
-
mut self,
894
-
value: impl Into<jacquard_common::CowStr<'a>>,
895
-
) -> FileBuilder<'a, file_state::SetType<S>> {
896
-
self.__unsafe_private_named.4 = ::core::option::Option::Some(value.into());
897
-
FileBuilder {
898
-
_phantom_state: ::core::marker::PhantomData,
899
-
__unsafe_private_named: self.__unsafe_private_named,
900
-
_phantom: ::core::marker::PhantomData,
901
-
}
902
-
}
903
-
}
904
-
905
-
impl<'a, S> FileBuilder<'a, S>
906
-
where
907
-
S: file_state::State,
908
-
S::Type: file_state::IsSet,
909
-
S::Blob: file_state::IsSet,
910
-
{
911
-
/// Build the final struct
912
-
pub fn build(self) -> File<'a> {
913
-
File {
914
-
base64: self.__unsafe_private_named.0,
915
-
blob: self.__unsafe_private_named.1.unwrap(),
916
-
encoding: self.__unsafe_private_named.2,
917
-
mime_type: self.__unsafe_private_named.3,
918
-
r#type: self.__unsafe_private_named.4.unwrap(),
919
-
extra_data: Default::default(),
920
-
}
921
-
}
922
-
/// Build the final struct with custom extra_data
923
-
pub fn build_with_data(
924
-
self,
925
-
extra_data: std::collections::BTreeMap<
926
-
jacquard_common::smol_str::SmolStr,
927
-
jacquard_common::types::value::Data<'a>,
928
-
>,
929
-
) -> File<'a> {
930
-
File {
931
-
base64: self.__unsafe_private_named.0,
932
-
blob: self.__unsafe_private_named.1.unwrap(),
933
-
encoding: self.__unsafe_private_named.2,
934
-
mime_type: self.__unsafe_private_named.3,
935
-
r#type: self.__unsafe_private_named.4.unwrap(),
936
-
extra_data: Some(extra_data),
937
-
}
938
-
}
939
-
}
940
-
941
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for File<'a> {
942
-
fn nsid() -> &'static str {
943
-
"place.wisp.subfs"
944
-
}
945
-
fn def_name() -> &'static str {
946
-
"file"
947
-
}
948
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
949
-
lexicon_doc_place_wisp_subfs()
950
-
}
951
-
fn validate(
952
-
&self,
953
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
954
-
Ok(())
955
-
}
956
-
}
957
-
958
-
/// Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.
959
-
#[jacquard_derive::lexicon]
960
-
#[derive(
961
-
serde::Serialize,
962
-
serde::Deserialize,
963
-
Debug,
964
-
Clone,
965
-
PartialEq,
966
-
Eq,
967
-
jacquard_derive::IntoStatic
968
-
)]
969
-
#[serde(rename_all = "camelCase")]
970
-
pub struct SubfsRecord<'a> {
971
-
pub created_at: jacquard_common::types::string::Datetime,
972
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
973
-
pub file_count: Option<i64>,
974
-
#[serde(borrow)]
975
-
pub root: crate::place_wisp::subfs::Directory<'a>,
976
-
}
977
-
978
-
pub mod subfs_record_state {
979
-
980
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
981
-
#[allow(unused)]
982
-
use ::core::marker::PhantomData;
983
-
mod sealed {
984
-
pub trait Sealed {}
985
-
}
986
-
/// State trait tracking which required fields have been set
987
-
pub trait State: sealed::Sealed {
988
-
type Root;
989
-
type CreatedAt;
990
-
}
991
-
/// Empty state - all required fields are unset
992
-
pub struct Empty(());
993
-
impl sealed::Sealed for Empty {}
994
-
impl State for Empty {
995
-
type Root = Unset;
996
-
type CreatedAt = Unset;
997
-
}
998
-
///State transition - sets the `root` field to Set
999
-
pub struct SetRoot<S: State = Empty>(PhantomData<fn() -> S>);
1000
-
impl<S: State> sealed::Sealed for SetRoot<S> {}
1001
-
impl<S: State> State for SetRoot<S> {
1002
-
type Root = Set<members::root>;
1003
-
type CreatedAt = S::CreatedAt;
1004
-
}
1005
-
///State transition - sets the `created_at` field to Set
1006
-
pub struct SetCreatedAt<S: State = Empty>(PhantomData<fn() -> S>);
1007
-
impl<S: State> sealed::Sealed for SetCreatedAt<S> {}
1008
-
impl<S: State> State for SetCreatedAt<S> {
1009
-
type Root = S::Root;
1010
-
type CreatedAt = Set<members::created_at>;
1011
-
}
1012
-
/// Marker types for field names
1013
-
#[allow(non_camel_case_types)]
1014
-
pub mod members {
1015
-
///Marker type for the `root` field
1016
-
pub struct root(());
1017
-
///Marker type for the `created_at` field
1018
-
pub struct created_at(());
1019
-
}
1020
-
}
1021
-
1022
-
/// Builder for constructing an instance of this type
1023
-
pub struct SubfsRecordBuilder<'a, S: subfs_record_state::State> {
1024
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1025
-
__unsafe_private_named: (
1026
-
::core::option::Option<jacquard_common::types::string::Datetime>,
1027
-
::core::option::Option<i64>,
1028
-
::core::option::Option<crate::place_wisp::subfs::Directory<'a>>,
1029
-
),
1030
-
_phantom: ::core::marker::PhantomData<&'a ()>,
1031
-
}
1032
-
1033
-
impl<'a> SubfsRecord<'a> {
1034
-
/// Create a new builder for this type
1035
-
pub fn new() -> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
1036
-
SubfsRecordBuilder::new()
1037
-
}
1038
-
}
1039
-
1040
-
impl<'a> SubfsRecordBuilder<'a, subfs_record_state::Empty> {
1041
-
/// Create a new builder with all fields unset
1042
-
pub fn new() -> Self {
1043
-
SubfsRecordBuilder {
1044
-
_phantom_state: ::core::marker::PhantomData,
1045
-
__unsafe_private_named: (None, None, None),
1046
-
_phantom: ::core::marker::PhantomData,
1047
-
}
1048
-
}
1049
-
}
1050
-
1051
-
impl<'a, S> SubfsRecordBuilder<'a, S>
1052
-
where
1053
-
S: subfs_record_state::State,
1054
-
S::CreatedAt: subfs_record_state::IsUnset,
1055
-
{
1056
-
/// Set the `createdAt` field (required)
1057
-
pub fn created_at(
1058
-
mut self,
1059
-
value: impl Into<jacquard_common::types::string::Datetime>,
1060
-
) -> SubfsRecordBuilder<'a, subfs_record_state::SetCreatedAt<S>> {
1061
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1062
-
SubfsRecordBuilder {
1063
-
_phantom_state: ::core::marker::PhantomData,
1064
-
__unsafe_private_named: self.__unsafe_private_named,
1065
-
_phantom: ::core::marker::PhantomData,
1066
-
}
1067
-
}
1068
-
}
1069
-
1070
-
impl<'a, S: subfs_record_state::State> SubfsRecordBuilder<'a, S> {
1071
-
/// Set the `fileCount` field (optional)
1072
-
pub fn file_count(mut self, value: impl Into<Option<i64>>) -> Self {
1073
-
self.__unsafe_private_named.1 = value.into();
1074
-
self
1075
-
}
1076
-
/// Set the `fileCount` field to an Option value (optional)
1077
-
pub fn maybe_file_count(mut self, value: Option<i64>) -> Self {
1078
-
self.__unsafe_private_named.1 = value;
1079
-
self
1080
-
}
1081
-
}
1082
-
1083
-
impl<'a, S> SubfsRecordBuilder<'a, S>
1084
-
where
1085
-
S: subfs_record_state::State,
1086
-
S::Root: subfs_record_state::IsUnset,
1087
-
{
1088
-
/// Set the `root` field (required)
1089
-
pub fn root(
1090
-
mut self,
1091
-
value: impl Into<crate::place_wisp::subfs::Directory<'a>>,
1092
-
) -> SubfsRecordBuilder<'a, subfs_record_state::SetRoot<S>> {
1093
-
self.__unsafe_private_named.2 = ::core::option::Option::Some(value.into());
1094
-
SubfsRecordBuilder {
1095
-
_phantom_state: ::core::marker::PhantomData,
1096
-
__unsafe_private_named: self.__unsafe_private_named,
1097
-
_phantom: ::core::marker::PhantomData,
1098
-
}
1099
-
}
1100
-
}
1101
-
1102
-
impl<'a, S> SubfsRecordBuilder<'a, S>
1103
-
where
1104
-
S: subfs_record_state::State,
1105
-
S::Root: subfs_record_state::IsSet,
1106
-
S::CreatedAt: subfs_record_state::IsSet,
1107
-
{
1108
-
/// Build the final struct
1109
-
pub fn build(self) -> SubfsRecord<'a> {
1110
-
SubfsRecord {
1111
-
created_at: self.__unsafe_private_named.0.unwrap(),
1112
-
file_count: self.__unsafe_private_named.1,
1113
-
root: self.__unsafe_private_named.2.unwrap(),
1114
-
extra_data: Default::default(),
1115
-
}
1116
-
}
1117
-
/// Build the final struct with custom extra_data
1118
-
pub fn build_with_data(
1119
-
self,
1120
-
extra_data: std::collections::BTreeMap<
1121
-
jacquard_common::smol_str::SmolStr,
1122
-
jacquard_common::types::value::Data<'a>,
1123
-
>,
1124
-
) -> SubfsRecord<'a> {
1125
-
SubfsRecord {
1126
-
created_at: self.__unsafe_private_named.0.unwrap(),
1127
-
file_count: self.__unsafe_private_named.1,
1128
-
root: self.__unsafe_private_named.2.unwrap(),
1129
-
extra_data: Some(extra_data),
1130
-
}
1131
-
}
1132
-
}
1133
-
1134
-
impl<'a> SubfsRecord<'a> {
1135
-
pub fn uri(
1136
-
uri: impl Into<jacquard_common::CowStr<'a>>,
1137
-
) -> Result<
1138
-
jacquard_common::types::uri::RecordUri<'a, SubfsRecordRecord>,
1139
-
jacquard_common::types::uri::UriError,
1140
-
> {
1141
-
jacquard_common::types::uri::RecordUri::try_from_uri(
1142
-
jacquard_common::types::string::AtUri::new_cow(uri.into())?,
1143
-
)
1144
-
}
1145
-
}
1146
-
1147
-
/// Typed wrapper for GetRecord response with this collection's record type.
1148
-
#[derive(
1149
-
serde::Serialize,
1150
-
serde::Deserialize,
1151
-
Debug,
1152
-
Clone,
1153
-
PartialEq,
1154
-
Eq,
1155
-
jacquard_derive::IntoStatic
1156
-
)]
1157
-
#[serde(rename_all = "camelCase")]
1158
-
pub struct SubfsRecordGetRecordOutput<'a> {
1159
-
#[serde(skip_serializing_if = "std::option::Option::is_none")]
1160
-
#[serde(borrow)]
1161
-
pub cid: std::option::Option<jacquard_common::types::string::Cid<'a>>,
1162
-
#[serde(borrow)]
1163
-
pub uri: jacquard_common::types::string::AtUri<'a>,
1164
-
#[serde(borrow)]
1165
-
pub value: SubfsRecord<'a>,
1166
-
}
1167
-
1168
-
impl From<SubfsRecordGetRecordOutput<'_>> for SubfsRecord<'_> {
1169
-
fn from(output: SubfsRecordGetRecordOutput<'_>) -> Self {
1170
-
use jacquard_common::IntoStatic;
1171
-
output.value.into_static()
1172
-
}
1173
-
}
1174
-
1175
-
impl jacquard_common::types::collection::Collection for SubfsRecord<'_> {
1176
-
const NSID: &'static str = "place.wisp.subfs";
1177
-
type Record = SubfsRecordRecord;
1178
-
}
1179
-
1180
-
/// Marker type for deserializing records from this collection.
1181
-
#[derive(Debug, serde::Serialize, serde::Deserialize)]
1182
-
pub struct SubfsRecordRecord;
1183
-
impl jacquard_common::xrpc::XrpcResp for SubfsRecordRecord {
1184
-
const NSID: &'static str = "place.wisp.subfs";
1185
-
const ENCODING: &'static str = "application/json";
1186
-
type Output<'de> = SubfsRecordGetRecordOutput<'de>;
1187
-
type Err<'de> = jacquard_common::types::collection::RecordError<'de>;
1188
-
}
1189
-
1190
-
impl jacquard_common::types::collection::Collection for SubfsRecordRecord {
1191
-
const NSID: &'static str = "place.wisp.subfs";
1192
-
type Record = SubfsRecordRecord;
1193
-
}
1194
-
1195
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for SubfsRecord<'a> {
1196
-
fn nsid() -> &'static str {
1197
-
"place.wisp.subfs"
1198
-
}
1199
-
fn def_name() -> &'static str {
1200
-
"main"
1201
-
}
1202
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1203
-
lexicon_doc_place_wisp_subfs()
1204
-
}
1205
-
fn validate(
1206
-
&self,
1207
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1208
-
if let Some(ref value) = self.file_count {
1209
-
if *value > 1000i64 {
1210
-
return Err(::jacquard_lexicon::validation::ConstraintError::Maximum {
1211
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1212
-
"file_count",
1213
-
),
1214
-
max: 1000i64,
1215
-
actual: *value,
1216
-
});
1217
-
}
1218
-
}
1219
-
if let Some(ref value) = self.file_count {
1220
-
if *value < 0i64 {
1221
-
return Err(::jacquard_lexicon::validation::ConstraintError::Minimum {
1222
-
path: ::jacquard_lexicon::validation::ValidationPath::from_field(
1223
-
"file_count",
1224
-
),
1225
-
min: 0i64,
1226
-
actual: *value,
1227
-
});
1228
-
}
1229
-
}
1230
-
Ok(())
1231
-
}
1232
-
}
1233
-
1234
-
#[jacquard_derive::lexicon]
1235
-
#[derive(
1236
-
serde::Serialize,
1237
-
serde::Deserialize,
1238
-
Debug,
1239
-
Clone,
1240
-
PartialEq,
1241
-
Eq,
1242
-
jacquard_derive::IntoStatic
1243
-
)]
1244
-
#[serde(rename_all = "camelCase")]
1245
-
pub struct Subfs<'a> {
1246
-
/// AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures.
1247
-
#[serde(borrow)]
1248
-
pub subject: jacquard_common::types::string::AtUri<'a>,
1249
-
#[serde(borrow)]
1250
-
pub r#type: jacquard_common::CowStr<'a>,
1251
-
}
1252
-
1253
-
pub mod subfs_state {
1254
-
1255
-
pub use crate::builder_types::{Set, Unset, IsSet, IsUnset};
1256
-
#[allow(unused)]
1257
-
use ::core::marker::PhantomData;
1258
-
mod sealed {
1259
-
pub trait Sealed {}
1260
-
}
1261
-
/// State trait tracking which required fields have been set
1262
-
pub trait State: sealed::Sealed {
1263
-
type Type;
1264
-
type Subject;
1265
-
}
1266
-
/// Empty state - all required fields are unset
1267
-
pub struct Empty(());
1268
-
impl sealed::Sealed for Empty {}
1269
-
impl State for Empty {
1270
-
type Type = Unset;
1271
-
type Subject = Unset;
1272
-
}
1273
-
///State transition - sets the `type` field to Set
1274
-
pub struct SetType<S: State = Empty>(PhantomData<fn() -> S>);
1275
-
impl<S: State> sealed::Sealed for SetType<S> {}
1276
-
impl<S: State> State for SetType<S> {
1277
-
type Type = Set<members::r#type>;
1278
-
type Subject = S::Subject;
1279
-
}
1280
-
///State transition - sets the `subject` field to Set
1281
-
pub struct SetSubject<S: State = Empty>(PhantomData<fn() -> S>);
1282
-
impl<S: State> sealed::Sealed for SetSubject<S> {}
1283
-
impl<S: State> State for SetSubject<S> {
1284
-
type Type = S::Type;
1285
-
type Subject = Set<members::subject>;
1286
-
}
1287
-
/// Marker types for field names
1288
-
#[allow(non_camel_case_types)]
1289
-
pub mod members {
1290
-
///Marker type for the `type` field
1291
-
pub struct r#type(());
1292
-
///Marker type for the `subject` field
1293
-
pub struct subject(());
1294
-
}
1295
-
}
1296
-
1297
-
/// Builder for constructing an instance of this type
1298
-
pub struct SubfsBuilder<'a, S: subfs_state::State> {
1299
-
_phantom_state: ::core::marker::PhantomData<fn() -> S>,
1300
-
__unsafe_private_named: (
1301
-
::core::option::Option<jacquard_common::types::string::AtUri<'a>>,
1302
-
::core::option::Option<jacquard_common::CowStr<'a>>,
1303
-
),
1304
-
_phantom: ::core::marker::PhantomData<&'a ()>,
1305
-
}
1306
-
1307
-
impl<'a> Subfs<'a> {
1308
-
/// Create a new builder for this type
1309
-
pub fn new() -> SubfsBuilder<'a, subfs_state::Empty> {
1310
-
SubfsBuilder::new()
1311
-
}
1312
-
}
1313
-
1314
-
impl<'a> SubfsBuilder<'a, subfs_state::Empty> {
1315
-
/// Create a new builder with all fields unset
1316
-
pub fn new() -> Self {
1317
-
SubfsBuilder {
1318
-
_phantom_state: ::core::marker::PhantomData,
1319
-
__unsafe_private_named: (None, None),
1320
-
_phantom: ::core::marker::PhantomData,
1321
-
}
1322
-
}
1323
-
}
1324
-
1325
-
impl<'a, S> SubfsBuilder<'a, S>
1326
-
where
1327
-
S: subfs_state::State,
1328
-
S::Subject: subfs_state::IsUnset,
1329
-
{
1330
-
/// Set the `subject` field (required)
1331
-
pub fn subject(
1332
-
mut self,
1333
-
value: impl Into<jacquard_common::types::string::AtUri<'a>>,
1334
-
) -> SubfsBuilder<'a, subfs_state::SetSubject<S>> {
1335
-
self.__unsafe_private_named.0 = ::core::option::Option::Some(value.into());
1336
-
SubfsBuilder {
1337
-
_phantom_state: ::core::marker::PhantomData,
1338
-
__unsafe_private_named: self.__unsafe_private_named,
1339
-
_phantom: ::core::marker::PhantomData,
1340
-
}
1341
-
}
1342
-
}
1343
-
1344
-
impl<'a, S> SubfsBuilder<'a, S>
1345
-
where
1346
-
S: subfs_state::State,
1347
-
S::Type: subfs_state::IsUnset,
1348
-
{
1349
-
/// Set the `type` field (required)
1350
-
pub fn r#type(
1351
-
mut self,
1352
-
value: impl Into<jacquard_common::CowStr<'a>>,
1353
-
) -> SubfsBuilder<'a, subfs_state::SetType<S>> {
1354
-
self.__unsafe_private_named.1 = ::core::option::Option::Some(value.into());
1355
-
SubfsBuilder {
1356
-
_phantom_state: ::core::marker::PhantomData,
1357
-
__unsafe_private_named: self.__unsafe_private_named,
1358
-
_phantom: ::core::marker::PhantomData,
1359
-
}
1360
-
}
1361
-
}
1362
-
1363
-
impl<'a, S> SubfsBuilder<'a, S>
1364
-
where
1365
-
S: subfs_state::State,
1366
-
S::Type: subfs_state::IsSet,
1367
-
S::Subject: subfs_state::IsSet,
1368
-
{
1369
-
/// Build the final struct
1370
-
pub fn build(self) -> Subfs<'a> {
1371
-
Subfs {
1372
-
subject: self.__unsafe_private_named.0.unwrap(),
1373
-
r#type: self.__unsafe_private_named.1.unwrap(),
1374
-
extra_data: Default::default(),
1375
-
}
1376
-
}
1377
-
/// Build the final struct with custom extra_data
1378
-
pub fn build_with_data(
1379
-
self,
1380
-
extra_data: std::collections::BTreeMap<
1381
-
jacquard_common::smol_str::SmolStr,
1382
-
jacquard_common::types::value::Data<'a>,
1383
-
>,
1384
-
) -> Subfs<'a> {
1385
-
Subfs {
1386
-
subject: self.__unsafe_private_named.0.unwrap(),
1387
-
r#type: self.__unsafe_private_named.1.unwrap(),
1388
-
extra_data: Some(extra_data),
1389
-
}
1390
-
}
1391
-
}
1392
-
1393
-
impl<'a> ::jacquard_lexicon::schema::LexiconSchema for Subfs<'a> {
1394
-
fn nsid() -> &'static str {
1395
-
"place.wisp.subfs"
1396
-
}
1397
-
fn def_name() -> &'static str {
1398
-
"subfs"
1399
-
}
1400
-
fn lexicon_doc() -> ::jacquard_lexicon::lexicon::LexiconDoc<'static> {
1401
-
lexicon_doc_place_wisp_subfs()
1402
-
}
1403
-
fn validate(
1404
-
&self,
1405
-
) -> ::std::result::Result<(), ::jacquard_lexicon::validation::ConstraintError> {
1406
-
Ok(())
1407
-
}
1408
-
}
-8
cli/src/place_wisp.rs
-8
cli/src/place_wisp.rs
+12
-12
cli/src/pull.rs
+12
-12
cli/src/pull.rs
···
1
1
use crate::blob_map;
2
2
use crate::download;
3
3
use crate::metadata::SiteMetadata;
4
-
use crate::place_wisp::fs::*;
4
+
use wisp_lexicons::place_wisp::fs::*;
5
5
use crate::subfs_utils;
6
6
use jacquard::CowStr;
7
7
use jacquard::prelude::IdentityResolver;
···
410
410
) -> miette::Result<Directory<'static>> {
411
411
use jacquard_common::IntoStatic;
412
412
use jacquard_common::types::value::from_data;
413
-
use crate::place_wisp::subfs::SubfsRecord;
413
+
use wisp_lexicons::place_wisp::subfs::SubfsRecord;
414
414
415
-
let mut all_subfs_map: HashMap<String, crate::place_wisp::subfs::Directory> = HashMap::new();
415
+
let mut all_subfs_map: HashMap<String, wisp_lexicons::place_wisp::subfs::Directory> = HashMap::new();
416
416
let mut to_fetch = subfs_utils::extract_subfs_uris(directory, String::new());
417
417
418
418
if to_fetch.is_empty() {
···
516
516
517
517
/// Extract subfs URIs from a subfs::Directory (helper for pull)
518
518
fn extract_subfs_uris_from_subfs_dir(
519
-
directory: &crate::place_wisp::subfs::Directory,
519
+
directory: &wisp_lexicons::place_wisp::subfs::Directory,
520
520
current_path: String,
521
521
) -> Vec<(String, String)> {
522
522
let mut uris = Vec::new();
···
529
529
};
530
530
531
531
match &entry.node {
532
-
crate::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
532
+
wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
533
533
uris.push((subfs_node.subject.to_string(), full_path.clone()));
534
534
}
535
-
crate::place_wisp::subfs::EntryNode::Directory(subdir) => {
535
+
wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => {
536
536
let nested = extract_subfs_uris_from_subfs_dir(subdir, full_path);
537
537
uris.extend(nested);
538
538
}
···
546
546
/// Recursively replace subfs nodes with their actual content
547
547
fn replace_subfs_with_content(
548
548
directory: Directory,
549
-
subfs_map: &HashMap<String, crate::place_wisp::subfs::Directory>,
549
+
subfs_map: &HashMap<String, wisp_lexicons::place_wisp::subfs::Directory>,
550
550
current_path: String,
551
551
) -> Directory<'static> {
552
552
use jacquard_common::IntoStatic;
···
628
628
}
629
629
630
630
/// Convert a subfs entry to a fs entry (they have the same structure but different types)
631
-
fn convert_subfs_entry_to_fs(subfs_entry: crate::place_wisp::subfs::Entry<'static>) -> Entry<'static> {
631
+
fn convert_subfs_entry_to_fs(subfs_entry: wisp_lexicons::place_wisp::subfs::Entry<'static>) -> Entry<'static> {
632
632
use jacquard_common::IntoStatic;
633
633
634
634
let node = match subfs_entry.node {
635
-
crate::place_wisp::subfs::EntryNode::File(file) => {
635
+
wisp_lexicons::place_wisp::subfs::EntryNode::File(file) => {
636
636
EntryNode::File(Box::new(
637
637
File::new()
638
638
.r#type(file.r#type.into_static())
···
643
643
.build()
644
644
))
645
645
}
646
-
crate::place_wisp::subfs::EntryNode::Directory(dir) => {
646
+
wisp_lexicons::place_wisp::subfs::EntryNode::Directory(dir) => {
647
647
let converted_entries: Vec<Entry<'static>> = dir
648
648
.entries
649
649
.into_iter()
···
657
657
.build()
658
658
))
659
659
}
660
-
crate::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
660
+
wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
661
661
// Nested subfs should have been expanded already - if we get here, it means expansion failed
662
662
// Treat it like a directory reference that should have been expanded
663
663
eprintln!(" โ ๏ธ Warning: unexpanded nested subfs at path, treating as empty directory");
···
668
668
.build()
669
669
))
670
670
}
671
-
crate::place_wisp::subfs::EntryNode::Unknown(unknown) => {
671
+
wisp_lexicons::place_wisp::subfs::EntryNode::Unknown(unknown) => {
672
672
EntryNode::Unknown(unknown)
673
673
}
674
674
};
+1
-1
cli/src/serve.rs
+1
-1
cli/src/serve.rs
+14
-14
cli/src/subfs_utils.rs
+14
-14
cli/src/subfs_utils.rs
···
6
6
use miette::IntoDiagnostic;
7
7
use std::collections::HashMap;
8
8
9
-
use crate::place_wisp::fs::{Directory as FsDirectory, EntryNode as FsEntryNode};
10
-
use crate::place_wisp::subfs::SubfsRecord;
9
+
use wisp_lexicons::place_wisp::fs::{Directory as FsDirectory, EntryNode as FsEntryNode};
10
+
use wisp_lexicons::place_wisp::subfs::SubfsRecord;
11
11
12
12
/// Extract all subfs URIs from a directory tree with their mount paths
13
13
pub fn extract_subfs_uris(directory: &FsDirectory, current_path: String) -> Vec<(String, String)> {
···
145
145
146
146
/// Extract subfs URIs from a subfs::Directory
147
147
fn extract_subfs_uris_from_subfs_dir(
148
-
directory: &crate::place_wisp::subfs::Directory,
148
+
directory: &wisp_lexicons::place_wisp::subfs::Directory,
149
149
current_path: String,
150
150
) -> Vec<(String, String)> {
151
151
let mut uris = Vec::new();
152
152
153
153
for entry in &directory.entries {
154
154
match &entry.node {
155
-
crate::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
155
+
wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(subfs_node) => {
156
156
// Check if this is a chunk entry (chunk0, chunk1, etc.)
157
157
// Chunks should be flat-merged, so use the parent's path
158
158
let mount_path = if entry.name.starts_with("chunk") &&
···
171
171
172
172
uris.push((subfs_node.subject.to_string(), mount_path));
173
173
}
174
-
crate::place_wisp::subfs::EntryNode::Directory(subdir) => {
174
+
wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => {
175
175
let full_path = if current_path.is_empty() {
176
176
entry.name.to_string()
177
177
} else {
···
204
204
for (mount_path, subfs_record) in all_subfs {
205
205
// Check if this record only contains chunk subfs references (no files)
206
206
let only_has_chunks = subfs_record.root.entries.iter().all(|e| {
207
-
matches!(&e.node, crate::place_wisp::subfs::EntryNode::Subfs(_)) &&
207
+
matches!(&e.node, wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_)) &&
208
208
e.name.starts_with("chunk") &&
209
209
e.name.chars().skip(5).all(|c| c.is_ascii_digit())
210
210
});
···
232
232
/// Extract blobs from a subfs directory (works with subfs::Directory)
233
233
/// Returns a map of file paths to their blob refs and CIDs
234
234
fn extract_subfs_blobs(
235
-
directory: &crate::place_wisp::subfs::Directory,
235
+
directory: &wisp_lexicons::place_wisp::subfs::Directory,
236
236
current_path: String,
237
237
) -> HashMap<String, (BlobRef<'static>, String)> {
238
238
let mut blob_map = HashMap::new();
···
245
245
};
246
246
247
247
match &entry.node {
248
-
crate::place_wisp::subfs::EntryNode::File(file_node) => {
248
+
wisp_lexicons::place_wisp::subfs::EntryNode::File(file_node) => {
249
249
let blob_ref = &file_node.blob;
250
250
let cid_string = blob_ref.blob().r#ref.to_string();
251
251
blob_map.insert(
···
253
253
(blob_ref.clone().into_static(), cid_string)
254
254
);
255
255
}
256
-
crate::place_wisp::subfs::EntryNode::Directory(subdir) => {
256
+
wisp_lexicons::place_wisp::subfs::EntryNode::Directory(subdir) => {
257
257
let sub_map = extract_subfs_blobs(subdir, full_path);
258
258
blob_map.extend(sub_map);
259
259
}
260
-
crate::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
260
+
wisp_lexicons::place_wisp::subfs::EntryNode::Subfs(_nested_subfs) => {
261
261
// Nested subfs - these should be resolved recursively in the main flow
262
262
// For now, we skip them (they'll be fetched separately)
263
263
eprintln!(" โ ๏ธ Found nested subfs at {}, skipping (should be fetched separately)", full_path);
264
264
}
265
-
crate::place_wisp::subfs::EntryNode::Unknown(_) => {
265
+
wisp_lexicons::place_wisp::subfs::EntryNode::Unknown(_) => {
266
266
// Skip unknown nodes
267
267
}
268
268
}
···
352
352
flat: bool,
353
353
) -> miette::Result<FsDirectory<'static>> {
354
354
use jacquard_common::CowStr;
355
-
use crate::place_wisp::fs::{Entry, Subfs};
355
+
use wisp_lexicons::place_wisp::fs::{Entry, Subfs};
356
356
357
357
let path_parts: Vec<&str> = target_path.split('/').collect();
358
358
···
430
430
431
431
// Construct AT-URI and convert to RecordUri
432
432
let at_uri = AtUri::new(uri).into_diagnostic()?;
433
-
let record_uri: RecordUri<'_, crate::place_wisp::subfs::SubfsRecordRecord> = RecordUri::try_from_uri(at_uri).into_diagnostic()?;
433
+
let record_uri: RecordUri<'_, wisp_lexicons::place_wisp::subfs::SubfsRecordRecord> = RecordUri::try_from_uri(at_uri).into_diagnostic()?;
434
434
435
435
let rkey = record_uri.rkey()
436
436
.ok_or_else(|| miette::miette!("Invalid subfs URI: missing rkey"))?
···
489
489
}
490
490
491
491
/// Estimate the JSON size of a single entry
492
-
fn estimate_entry_size(entry: &crate::place_wisp::fs::Entry) -> usize {
492
+
fn estimate_entry_size(entry: &wisp_lexicons::place_wisp::fs::Entry) -> usize {
493
493
match serde_json::to_string(entry) {
494
494
Ok(json) => json.len(),
495
495
Err(_) => 500, // Conservative estimate if serialization fails
+21
docker-compose.yml
+21
docker-compose.yml
···
1
+
services:
2
+
postgres:
3
+
image: postgres:16-alpine
4
+
container_name: wisp-postgres
5
+
restart: unless-stopped
6
+
environment:
7
+
POSTGRES_USER: postgres
8
+
POSTGRES_PASSWORD: postgres
9
+
POSTGRES_DB: wisp
10
+
ports:
11
+
- "5432:5432"
12
+
volumes:
13
+
- postgres_data:/var/lib/postgresql/data
14
+
healthcheck:
15
+
test: ["CMD-SHELL", "pg_isready -U postgres"]
16
+
interval: 5s
17
+
timeout: 5s
18
+
retries: 5
19
+
20
+
volumes:
21
+
postgres_data:
+4
-1
docs/astro.config.mjs
+4
-1
docs/astro.config.mjs
···
7
7
integrations: [
8
8
starlight({
9
9
title: 'Wisp.place Docs',
10
-
social: [{ icon: 'github', label: 'GitHub', href: 'https://github.com/tangled-org/wisp.place' }],
10
+
components: {
11
+
SocialIcons: './src/components/SocialIcons.astro',
12
+
},
11
13
sidebar: [
12
14
{
13
15
label: 'Getting Started',
···
24
26
label: 'Guides',
25
27
items: [
26
28
{ label: 'Self-Hosting', slug: 'deployment' },
29
+
{ label: 'Monitoring & Metrics', slug: 'monitoring' },
27
30
{ label: 'Redirects & Rewrites', slug: 'redirects' },
28
31
],
29
32
},
+9
docs/src/assets/tangled-icon.svg
+9
docs/src/assets/tangled-icon.svg
···
1
+
<svg xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1" id="svg1" width="25" height="25" viewBox="0 0 25 25" sodipodi:docname="tangled_dolly_silhouette.png">
2
+
<defs id="defs1"/>
3
+
<sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#000000" borderopacity="0.25" inkscape:showpageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="true" inkscape:deskcolor="#d1d1d1">
4
+
<inkscape:page x="0" y="0" width="25" height="25" id="page2" margin="0" bleed="0"/>
5
+
</sodipodi:namedview>
6
+
<g inkscape:groupmode="layer" inkscape:label="Image" id="g1">
7
+
<path style="fill:#000000;stroke-width:1.12248" d="m 16.208435,23.914069 c -0.06147,-0.02273 -0.147027,-0.03034 -0.190158,-0.01691 -0.197279,0.06145 -1.31068,-0.230493 -1.388819,-0.364153 -0.01956,-0.03344 -0.163274,-0.134049 -0.319377,-0.223561 -0.550395,-0.315603 -1.010951,-0.696643 -1.428383,-1.181771 -0.264598,-0.307509 -0.597257,-0.785384 -0.597257,-0.857979 0,-0.0216 -0.02841,-0.06243 -0.06313,-0.0907 -0.04977,-0.04053 -0.160873,0.0436 -0.52488,0.397463 -0.479803,0.466432 -0.78924,0.689475 -1.355603,0.977118 -0.183693,0.0933 -0.323426,0.179989 -0.310516,0.192658 0.02801,0.02748 -0.7656391,0.270031 -1.209129,0.369517 -0.5378332,0.120647 -1.6341809,0.08626 -1.9721503,-0.06186 C 6.7977157,23.031391 6.56735,22.957551 6.3371134,22.889782 4.9717169,22.487902 3.7511914,21.481518 3.1172396,20.234838 2.6890391,19.392772 2.5582276,18.827446 2.5610489,17.831154 2.5639589,16.802192 2.7366641,16.125844 3.2142117,15.273187 3.3040457,15.112788 3.3713143,14.976533 3.3636956,14.9704 3.3560756,14.9643 3.2459634,14.90305 3.1189994,14.834381 1.7582586,14.098312 0.77760984,12.777439 0.44909837,11.23818 0.33531456,10.705039 0.33670119,9.7067968 0.45195381,9.1778795 0.72259241,7.9359287 1.3827188,6.8888436 2.4297498,6.0407205 2.6856126,5.8334648 3.2975489,5.4910878 3.6885849,5.3364049 L 4.0584319,5.190106 4.2333984,4.860432 C 4.8393906,3.7186139 5.8908314,2.7968028 7.1056396,2.3423025 7.7690673,2.0940921 8.2290216,2.0150935 9.01853,2.0137575 c 0.9625627,-0.00163 1.629181,0.1532762 2.485864,0.5776514 l 0.271744,0.1346134 0.42911,-0.3607688 c 1.082666,-0.9102346 2.185531,-1.3136811 3.578383,-1.3090327 0.916696,0.00306 1.573918,0.1517893 2.356121,0.5331927 1.465948,0.7148 2.54506,2.0625628 2.865177,3.57848 l 0.07653,0.362429 0.515095,0.2556611 c 1.022872,0.5076874 1.756122,1.1690944 2.288361,2.0641468 0.401896,0.6758594 0.537303,1.0442682 0.675505,1.8378683 0.288575,1.6570823 -0.266229,3.3548023 -1.490464,4.5608743 -0.371074,0.36557 -0.840205,0.718265 -1.203442,0.904754 -0.144112,0.07398 -0.271303,0.15826 -0.282647,0.187269 -0.01134,0.02901 0.02121,0.142764 0.07234,0.25279 0.184248,0.396467 0.451371,1.331823 0.619371,2.168779 0.463493,2.30908 -0.754646,4.693707 -2.92278,5.721632 -0.479538,0.227352 -0.717629,0.309322 -1.144194,0.39393 -0.321869,0.06383 -1.850573,0.09139 -2.000174,0.03604 z M 12.25443,18.636956 c 0.739923,-0.24652 1.382521,-0.718922 1.874623,-1.37812 0.0752,-0.100718 0.213883,-0.275851 0.308198,-0.389167 0.09432,-0.113318 0.210136,-0.271056 0.257381,-0.350531 0.416347,-0.700389 0.680936,-1.176102 0.766454,-1.378041 0.05594,-0.132087 0.114653,-0.239607 0.130477,-0.238929 0.01583,6.79e-4 0.08126,0.08531 0.145412,0.188069 0.178029,0.285173 0.614305,0.658998 0.868158,0.743878 0.259802,0.08686 0.656158,0.09598 0.911369,0.02095 0.213812,-0.06285 0.507296,-0.298016 0.645179,-0.516947 0.155165,-0.246374 0.327989,-0.989595 0.327989,-1.410501 0,-1.26718 -0.610975,-3.143405 -1.237774,-3.801045 -0.198483,-0.2082486 -0.208557,-0.2319396 -0.208557,-0.4904655 0,-0.2517771 -0.08774,-0.5704927 -0.258476,-0.938956 C 16.694963,8.50313 16.375697,8.1377479 16.135846,7.9543702 L 15.932296,7.7987471 15.683004,7.9356529 C 15.131767,8.2383821 14.435638,8.1945733 13.943459,7.8261812 L 13.782862,7.7059758 13.686773,7.8908012 C 13.338849,8.5600578 12.487087,8.8811064 11.743178,8.6233891 11.487199,8.5347109 11.358897,8.4505994 11.063189,8.1776138 L 10.69871,7.8411436 10.453484,8.0579255 C 10.318608,8.1771557 10.113778,8.3156283 9.9983037,8.3656417 9.7041488,8.4930449 9.1808299,8.5227884 8.8979004,8.4281886 8.7754792,8.3872574 8.6687415,8.3537661 8.6607053,8.3537661 c -0.03426,0 -0.3092864,0.3066098 -0.3791974,0.42275 -0.041935,0.069664 -0.1040482,0.1266636 -0.1380294,0.1266636 -0.1316419,0 -0.4197402,0.1843928 -0.6257041,0.4004735 -0.1923125,0.2017571 -0.6853701,0.9036038 -0.8926582,1.2706578 -0.042662,0.07554 -0.1803555,0.353687 -0.3059848,0.618091 -0.1256293,0.264406 -0.3270073,0.686768 -0.4475067,0.938581 -0.1204992,0.251816 -0.2469926,0.519654 -0.2810961,0.595199 -0.2592829,0.574347 -0.285919,1.391094 -0.057822,1.77304 0.1690683,0.283105 0.4224039,0.480895 0.7285507,0.568809 0.487122,0.139885 0.9109638,-0.004 1.6013422,-0.543768 l 0.4560939,-0.356568 0.0036,0.172041 c 0.01635,0.781837 0.1831084,1.813183 0.4016641,2.484154 0.1160449,0.356262 0.3781448,0.83968 0.5614081,1.035462 0.2171883,0.232025 0.7140951,0.577268 1.0100284,0.701749 0.121485,0.0511 0.351032,0.110795 0.510105,0.132647 0.396966,0.05452 1.2105,0.02265 1.448934,-0.05679 z" id="path1"/>
8
+
</g>
9
+
</svg>
+26
docs/src/components/SocialIcons.astro
+26
docs/src/components/SocialIcons.astro
···
1
+
---
2
+
// Custom social icons component to use the Tangled icon
3
+
---
4
+
5
+
<div class="sl-flex">
6
+
<a
7
+
href="https://tangled.org/nekomimi.pet/wisp.place-monorepo"
8
+
rel="me"
9
+
class="sl-flex"
10
+
aria-label="Tangled"
11
+
>
12
+
<svg
13
+
xmlns="http://www.w3.org/2000/svg"
14
+
viewBox="0 0 25 25"
15
+
width="16"
16
+
height="16"
17
+
aria-hidden="true"
18
+
focusable="false"
19
+
>
20
+
<path
21
+
style="fill:currentColor;stroke-width:1.12248"
22
+
d="m 16.208435,23.914069 c -0.06147,-0.02273 -0.147027,-0.03034 -0.190158,-0.01691 -0.197279,0.06145 -1.31068,-0.230493 -1.388819,-0.364153 -0.01956,-0.03344 -0.163274,-0.134049 -0.319377,-0.223561 -0.550395,-0.315603 -1.010951,-0.696643 -1.428383,-1.181771 -0.264598,-0.307509 -0.597257,-0.785384 -0.597257,-0.857979 0,-0.0216 -0.02841,-0.06243 -0.06313,-0.0907 -0.04977,-0.04053 -0.160873,0.0436 -0.52488,0.397463 -0.479803,0.466432 -0.78924,0.689475 -1.355603,0.977118 -0.183693,0.0933 -0.323426,0.179989 -0.310516,0.192658 0.02801,0.02748 -0.7656391,0.270031 -1.209129,0.369517 -0.5378332,0.120647 -1.6341809,0.08626 -1.9721503,-0.06186 C 6.7977157,23.031391 6.56735,22.957551 6.3371134,22.889782 4.9717169,22.487902 3.7511914,21.481518 3.1172396,20.234838 2.6890391,19.392772 2.5582276,18.827446 2.5610489,17.831154 2.5639589,16.802192 2.7366641,16.125844 3.2142117,15.273187 3.3040457,15.112788 3.3713143,14.976533 3.3636956,14.9704 3.3560756,14.9643 3.2459634,14.90305 3.1189994,14.834381 1.7582586,14.098312 0.77760984,12.777439 0.44909837,11.23818 0.33531456,10.705039 0.33670119,9.7067968 0.45195381,9.1778795 0.72259241,7.9359287 1.3827188,6.8888436 2.4297498,6.0407205 2.6856126,5.8334648 3.2975489,5.4910878 3.6885849,5.3364049 L 4.0584319,5.190106 4.2333984,4.860432 C 4.8393906,3.7186139 5.8908314,2.7968028 7.1056396,2.3423025 7.7690673,2.0940921 8.2290216,2.0150935 9.01853,2.0137575 c 0.9625627,-0.00163 1.629181,0.1532762 2.485864,0.5776514 l 0.271744,0.1346134 0.42911,-0.3607688 c 1.082666,-0.9102346 2.185531,-1.3136811 3.578383,-1.3090327 0.916696,0.00306 1.573918,0.1517893 2.356121,0.5331927 1.465948,0.7148 2.54506,2.0625628 2.865177,3.57848 l 0.07653,0.362429 0.515095,0.2556611 c 1.022872,0.5076874 1.756122,1.1690944 2.288361,2.0641468 0.401896,0.6758594 0.537303,1.0442682 0.675505,1.8378683 0.288575,1.6570823 -0.266229,3.3548023 -1.490464,4.5608743 -0.371074,0.36557 -0.840205,0.718265 -1.203442,0.904754 -0.144112,0.07398 -0.271303,0.15826 -0.282647,0.187269 -0.01134,0.02901 0.02121,0.142764 0.07234,0.25279 0.184248,0.396467 0.451371,1.331823 0.619371,2.168779 0.463493,2.30908 -0.754646,4.693707 -2.92278,5.721632 -0.479538,0.227352 -0.717629,0.309322 -1.144194,0.39393 -0.321869,0.06383 -1.850573,0.09139 -2.000174,0.03604 z M 12.25443,18.636956 c 0.739923,-0.24652 1.382521,-0.718922 1.874623,-1.37812 0.0752,-0.100718 0.213883,-0.275851 0.308198,-0.389167 0.09432,-0.113318 0.210136,-0.271056 0.257381,-0.350531 0.416347,-0.700389 0.680936,-1.176102 0.766454,-1.378041 0.05594,-0.132087 0.114653,-0.239607 0.130477,-0.238929 0.01583,6.79e-4 0.08126,0.08531 0.145412,0.188069 0.178029,0.285173 0.614305,0.658998 0.868158,0.743878 0.259802,0.08686 0.656158,0.09598 0.911369,0.02095 0.213812,-0.06285 0.507296,-0.298016 0.645179,-0.516947 0.155165,-0.246374 0.327989,-0.989595 0.327989,-1.410501 0,-1.26718 -0.610975,-3.143405 -1.237774,-3.801045 -0.198483,-0.2082486 -0.208557,-0.2319396 -0.208557,-0.4904655 0,-0.2517771 -0.08774,-0.5704927 -0.258476,-0.938956 C 16.694963,8.50313 16.375697,8.1377479 16.135846,7.9543702 L 15.932296,7.7987471 15.683004,7.9356529 C 15.131767,8.2383821 14.435638,8.1945733 13.943459,7.8261812 L 13.782862,7.7059758 13.686773,7.8908012 C 13.338849,8.5600578 12.487087,8.8811064 11.743178,8.6233891 11.487199,8.5347109 11.358897,8.4505994 11.063189,8.1776138 L 10.69871,7.8411436 10.453484,8.0579255 C 10.318608,8.1771557 10.113778,8.3156283 9.9983037,8.3656417 9.7041488,8.4930449 9.1808299,8.5227884 8.8979004,8.4281886 8.7754792,8.3872574 8.6687415,8.3537661 8.6607053,8.3537661 c -0.03426,0 -0.3092864,0.3066098 -0.3791974,0.42275 -0.041935,0.069664 -0.1040482,0.1266636 -0.1380294,0.1266636 -0.1316419,0 -0.4197402,0.1843928 -0.6257041,0.4004735 -0.1923125,0.2017571 -0.6853701,0.9036038 -0.8926582,1.2706578 -0.042662,0.07554 -0.1803555,0.353687 -0.3059848,0.618091 -0.1256293,0.264406 -0.3270073,0.686768 -0.4475067,0.938581 -0.1204992,0.251816 -0.2469926,0.519654 -0.2810961,0.595199 -0.2592829,0.574347 -0.285919,1.391094 -0.057822,1.77304 0.1690683,0.283105 0.4224039,0.480895 0.7285507,0.568809 0.487122,0.139885 0.9109638,-0.004 1.6013422,-0.543768 l 0.4560939,-0.356568 0.0036,0.172041 c 0.01635,0.781837 0.1831084,1.813183 0.4016641,2.484154 0.1160449,0.356262 0.3781448,0.83968 0.5614081,1.035462 0.2171883,0.232025 0.7140951,0.577268 1.0100284,0.701749 0.121485,0.0511 0.351032,0.110795 0.510105,0.132647 0.396966,0.05452 1.2105,0.02265 1.448934,-0.05679 z"
23
+
></path>
24
+
</svg>
25
+
</a>
26
+
</div>
+8
-11
docs/src/content/docs/cli.md
+8
-11
docs/src/content/docs/cli.md
···
71
71
72
72
engine: 'nixery'
73
73
74
-
clone:
75
-
skip: false
76
-
depth: 1
77
-
submodules: false
78
-
79
74
dependencies:
80
75
nixpkgs:
81
76
- nodejs
···
141
136
# Pull a site to a specific directory
142
137
wisp-cli pull your-handle.bsky.social \
143
138
--site my-site \
144
-
--output ./my-site
139
+
--path ./my-site
145
140
146
141
# Pull to current directory
147
142
wisp-cli pull your-handle.bsky.social \
···
204
199
205
200
The CLI handles all file processing automatically to ensure reliable storage and delivery. Files are compressed with gzip at level 9 for optimal size reduction, then base64 encoded to bypass PDS content sniffing restrictions. Everything is uploaded as `application/octet-stream` blobs while preserving the original MIME type as metadata. When serving your site, the hosting service automatically decompresses non-HTML/CSS/JS files, ensuring your content is delivered correctly to visitors.
206
201
202
+
**File Filtering**: The CLI automatically excludes common files like `.git`, `node_modules`, `.env`, and other development artifacts. Customize this with a [`.wispignore` file](/file-filtering).
203
+
207
204
## Incremental Updates
208
205
209
206
The CLI tracks file changes using CID-based content addressing to minimize upload times and bandwidth usage. On your first deploy, all files are uploaded to establish the initial site. For subsequent deploys, the CLI compares content-addressed CIDs to detect which files have actually changed, uploading only those that differ from the previous version. This makes fast iterations possible even for large sites, with deploys completing in seconds when only a few files have changed.
···
236
233
### Pull Command
237
234
238
235
```bash
239
-
wisp-cli pull [OPTIONS] <INPUT>
236
+
wisp-cli pull [OPTIONS] --site <SITE> <INPUT>
240
237
241
238
Arguments:
242
239
<INPUT> Handle or DID
243
240
244
241
Options:
245
242
-s, --site <SITE> Site name to download
246
-
-o, --output <OUTPUT> Output directory [default: .]
243
+
-p, --path <PATH> Output directory [default: .]
247
244
-h, --help Print help
248
245
```
249
246
250
247
### Serve Command
251
248
252
249
```bash
253
-
wisp-cli serve [OPTIONS] <INPUT>
250
+
wisp-cli serve [OPTIONS] --site <SITE> <INPUT>
254
251
255
252
Arguments:
256
253
<INPUT> Handle or DID
257
254
258
255
Options:
259
256
-s, --site <SITE> Site name to serve
260
-
-o, --output <OUTPUT> Site files directory [default: .]
261
-
-p, --port <PORT> Port to serve on [default: 8080]
257
+
-p, --path <PATH> Site files directory [default: .]
258
+
-P, --port <PORT> Port to serve on [default: 8080]
262
259
--spa Enable SPA mode (serve index.html for all routes)
263
260
--directory Enable directory listing mode for paths without index files
264
261
-h, --help Print help
+58
docs/src/content/docs/file-filtering.md
+58
docs/src/content/docs/file-filtering.md
···
1
+
---
2
+
title: File Filtering & .wispignore
3
+
description: Control which files are uploaded to your Wisp site
4
+
---
5
+
6
+
# File Filtering & .wispignore
7
+
8
+
Wisp automatically excludes common files that shouldn't be deployed (`.git`, `node_modules`, `.env`, etc.).
9
+
10
+
## Default Exclusions
11
+
12
+
- Version control: `.git`, `.github`, `.gitlab`
13
+
- Dependencies: `node_modules`, `__pycache__`, `*.pyc`
14
+
- Secrets: `.env`, `.env.*`
15
+
- OS files: `.DS_Store`, `Thumbs.db`, `._*`
16
+
- Cache: `.cache`, `.temp`, `.tmp`
17
+
- Dev tools: `.vscode`, `*.swp`, `*~`, `.tangled`
18
+
- Virtual envs: `.venv`, `venv`, `env`
19
+
20
+
## Custom Patterns
21
+
22
+
Create a `.wispignore` file in your site root using gitignore syntax:
23
+
24
+
```
25
+
# Build outputs
26
+
dist/
27
+
*.map
28
+
29
+
# Logs and temp files
30
+
*.log
31
+
temp/
32
+
33
+
# Keep one exception
34
+
!important.log
35
+
```
36
+
37
+
### Pattern Syntax
38
+
39
+
- `file.txt` - exact match
40
+
- `*.log` - wildcard
41
+
- `logs/` - directory
42
+
- `src/**/*.test.js` - glob pattern
43
+
- `!keep.txt` - exception (don't ignore)
44
+
45
+
## Usage
46
+
47
+
**CLI**: Place `.wispignore` in your upload directory
48
+
```bash
49
+
wisp-cli handle.bsky.social --path ./my-site --site my-site
50
+
```
51
+
52
+
**Web**: Include `.wispignore` when uploading files
53
+
54
+
## Notes
55
+
56
+
- Custom patterns add to (not replace) default patterns
57
+
- Works in both CLI and web uploads
58
+
- The CLI logs which files are skipped
+85
docs/src/content/docs/guides/grafana-setup.md
+85
docs/src/content/docs/guides/grafana-setup.md
···
1
+
---
2
+
title: Grafana Setup Example
3
+
description: Quick setup for Grafana Cloud monitoring
4
+
---
5
+
6
+
Example setup for monitoring Wisp.place with Grafana Cloud.
7
+
8
+
## 1. Create Grafana Cloud Account
9
+
10
+
Sign up at [grafana.com](https://grafana.com) for a free tier account.
11
+
12
+
## 2. Get Credentials
13
+
14
+
Navigate to your stack and find:
15
+
16
+
**Loki** (Connections โ Loki โ Details):
17
+
- Push endpoint: `https://logs-prod-XXX.grafana.net`
18
+
- Create API token with write permissions
19
+
20
+
**Prometheus** (Connections โ Prometheus โ Details):
21
+
- Remote Write endpoint: `https://prometheus-prod-XXX.grafana.net/api/prom`
22
+
- Create API token with write permissions
23
+
24
+
## 3. Configure Wisp.place
25
+
26
+
Add to your `.env`:
27
+
28
+
```bash
29
+
GRAFANA_LOKI_URL=https://logs-prod-XXX.grafana.net
30
+
GRAFANA_LOKI_TOKEN=glc_eyJ...
31
+
32
+
GRAFANA_PROMETHEUS_URL=https://prometheus-prod-XXX.grafana.net/api/prom
33
+
GRAFANA_PROMETHEUS_TOKEN=glc_eyJ...
34
+
```
35
+
36
+
## 4. Create Dashboard
37
+
38
+
Import this dashboard JSON or build your own:
39
+
40
+
```json
41
+
{
42
+
"panels": [
43
+
{
44
+
"title": "Request Rate",
45
+
"targets": [{
46
+
"expr": "sum(rate(http_requests_total[1m])) by (service)"
47
+
}]
48
+
},
49
+
{
50
+
"title": "P95 Latency",
51
+
"targets": [{
52
+
"expr": "histogram_quantile(0.95, rate(http_request_duration_ms_bucket[5m]))"
53
+
}]
54
+
},
55
+
{
56
+
"title": "Error Rate",
57
+
"targets": [{
58
+
"expr": "sum(rate(errors_total[5m])) / sum(rate(http_requests_total[5m]))"
59
+
}]
60
+
}
61
+
]
62
+
}
63
+
```
64
+
65
+
## 5. Set Alerts
66
+
67
+
Example alert for high error rate:
68
+
69
+
```yaml
70
+
alert: HighErrorRate
71
+
expr: |
72
+
sum(rate(errors_total[5m])) by (service) /
73
+
sum(rate(http_requests_total[5m])) by (service) > 0.05
74
+
for: 5m
75
+
annotations:
76
+
summary: "High error rate in {{ $labels.service }}"
77
+
```
78
+
79
+
## Verify Data Flow
80
+
81
+
Check Grafana Explore:
82
+
- Loki: `{job="main-app"} | json`
83
+
- Prometheus: `http_requests_total`
84
+
85
+
Data should appear within 30 seconds of service startup.
+156
docs/src/content/docs/monitoring.md
+156
docs/src/content/docs/monitoring.md
···
1
+
---
2
+
title: Monitoring & Metrics
3
+
description: Track performance and debug issues with Grafana integration
4
+
---
5
+
6
+
Wisp.place includes built-in observability with automatic Grafana integration for logs and metrics. Monitor request performance, track errors, and analyze usage patterns across both the main backend and hosting service.
7
+
8
+
## Quick Start
9
+
10
+
Set environment variables to enable Grafana export:
11
+
12
+
```bash
13
+
# Grafana Cloud
14
+
GRAFANA_LOKI_URL=https://logs-prod-xxx.grafana.net
15
+
GRAFANA_LOKI_TOKEN=glc_xxx
16
+
17
+
GRAFANA_PROMETHEUS_URL=https://prometheus-prod-xxx.grafana.net/api/prom
18
+
GRAFANA_PROMETHEUS_TOKEN=glc_xxx
19
+
20
+
# Self-hosted Grafana
21
+
GRAFANA_LOKI_USERNAME=your-username
22
+
GRAFANA_LOKI_PASSWORD=your-password
23
+
```
24
+
25
+
Restart services. Metrics and logs now flow to Grafana automatically.
26
+
27
+
## Metrics Collected
28
+
29
+
### HTTP Requests
30
+
- `http_requests_total` - Total request count by path, method, status
31
+
- `http_request_duration_ms` - Request duration histogram
32
+
- `errors_total` - Error count by service
33
+
34
+
### Performance Stats
35
+
- P50, P95, P99 response times
36
+
- Requests per minute
37
+
- Error rates
38
+
- Average duration by endpoint
39
+
40
+
## Log Aggregation
41
+
42
+
Logs are sent to Loki with automatic categorization:
43
+
44
+
```
45
+
{job="main-app"} |= "error" # OAuth and upload errors
46
+
{job="hosting-service"} |= "cache" # Cache operations
47
+
{service="hosting-service", level="warn"} # Warnings only
48
+
```
49
+
50
+
## Service Identification
51
+
52
+
Each service is tagged separately:
53
+
- `main-app` - OAuth, uploads, domain management
54
+
- `hosting-service` - Firehose, caching, content serving
55
+
56
+
## Configuration Options
57
+
58
+
### Environment Variables
59
+
60
+
```bash
61
+
# Required
62
+
GRAFANA_LOKI_URL # Loki endpoint
63
+
GRAFANA_PROMETHEUS_URL # Prometheus endpoint (add /api/prom for OTLP)
64
+
65
+
# Authentication (use one)
66
+
GRAFANA_LOKI_TOKEN # Bearer token (Grafana Cloud)
67
+
GRAFANA_LOKI_USERNAME # Basic auth (self-hosted)
68
+
GRAFANA_LOKI_PASSWORD
69
+
70
+
# Optional
71
+
GRAFANA_BATCH_SIZE=100 # Batch size before flush
72
+
GRAFANA_FLUSH_INTERVAL=5000 # Flush interval in ms
73
+
```
74
+
75
+
### Programmatic Setup
76
+
77
+
```typescript
78
+
import { initializeGrafanaExporters } from '@wisp/observability'
79
+
80
+
initializeGrafanaExporters({
81
+
lokiUrl: 'https://logs.grafana.net',
82
+
lokiAuth: { bearerToken: 'token' },
83
+
prometheusUrl: 'https://prometheus.grafana.net/api/prom',
84
+
prometheusAuth: { bearerToken: 'token' },
85
+
serviceName: 'my-service',
86
+
batchSize: 100,
87
+
flushIntervalMs: 5000
88
+
})
89
+
```
90
+
91
+
## Grafana Dashboard Queries
92
+
93
+
### Request Performance
94
+
```promql
95
+
# Average response time by endpoint
96
+
avg by (path) (
97
+
rate(http_request_duration_ms_sum[5m]) /
98
+
rate(http_request_duration_ms_count[5m])
99
+
)
100
+
101
+
# Request rate
102
+
sum(rate(http_requests_total[1m])) by (service)
103
+
104
+
# Error rate
105
+
sum(rate(errors_total[5m])) by (service) /
106
+
sum(rate(http_requests_total[5m])) by (service)
107
+
```
108
+
109
+
### Log Analysis
110
+
```logql
111
+
# Recent errors
112
+
{job="main-app"} |= "error" | json
113
+
114
+
# Slow requests (>1s)
115
+
{job="hosting-service"} |~ "duration.*[1-9][0-9]{3,}"
116
+
117
+
# Failed OAuth attempts
118
+
{job="main-app"} |= "OAuth" |= "failed"
119
+
```
120
+
121
+
## Troubleshooting
122
+
123
+
### Logs not appearing
124
+
- Check `GRAFANA_LOKI_URL` is correct (no trailing `/loki/api/v1/push`)
125
+
- Verify authentication token/credentials
126
+
- Look for connection errors in service logs
127
+
128
+
### Metrics missing
129
+
- Ensure `GRAFANA_PROMETHEUS_URL` includes `/api/prom` suffix
130
+
- Check firewall rules allow outbound HTTPS
131
+
- Verify OpenTelemetry export errors in logs
132
+
133
+
### High memory usage
134
+
- Reduce `GRAFANA_BATCH_SIZE` (default: 100)
135
+
- Lower `GRAFANA_FLUSH_INTERVAL` to flush more frequently
136
+
137
+
## Local Development
138
+
139
+
Metrics and logs are stored in-memory when Grafana isn't configured. Access them via:
140
+
141
+
- `http://localhost:8000/api/observability/logs`
142
+
- `http://localhost:8000/api/observability/metrics`
143
+
- `http://localhost:8000/api/observability/errors`
144
+
145
+
## Testing Integration
146
+
147
+
Run integration tests to verify setup:
148
+
149
+
```bash
150
+
cd packages/@wisp/observability
151
+
bun test src/integration-test.test.ts
152
+
153
+
# Test with live Grafana
154
+
GRAFANA_LOKI_URL=... GRAFANA_LOKI_USERNAME=... GRAFANA_LOKI_PASSWORD=... \
155
+
bun test src/integration-test.test.ts
156
+
```
+15
-15
docs/src/styles/custom.css
+15
-15
docs/src/styles/custom.css
···
5
5
/* Increase base font size by 10% */
6
6
font-size: 110%;
7
7
8
-
/* Light theme - Warm beige background from app */
9
-
--sl-color-bg: oklch(0.90 0.012 35);
10
-
--sl-color-bg-sidebar: oklch(0.93 0.01 35);
11
-
--sl-color-bg-nav: oklch(0.93 0.01 35);
12
-
--sl-color-text: oklch(0.18 0.01 30);
13
-
--sl-color-text-accent: oklch(0.78 0.15 345);
14
-
--sl-color-accent: oklch(0.78 0.15 345);
15
-
--sl-color-accent-low: oklch(0.95 0.03 345);
16
-
--sl-color-border: oklch(0.75 0.015 30);
17
-
--sl-color-gray-1: oklch(0.52 0.015 30);
18
-
--sl-color-gray-2: oklch(0.42 0.015 30);
19
-
--sl-color-gray-3: oklch(0.33 0.015 30);
20
-
--sl-color-gray-4: oklch(0.25 0.015 30);
21
-
--sl-color-gray-5: oklch(0.75 0.015 30);
8
+
/* Light theme - Warm beige with improved contrast */
9
+
--sl-color-bg: oklch(0.92 0.012 35);
10
+
--sl-color-bg-sidebar: oklch(0.95 0.008 35);
11
+
--sl-color-bg-nav: oklch(0.95 0.008 35);
12
+
--sl-color-text: oklch(0.15 0.015 30);
13
+
--sl-color-text-accent: oklch(0.65 0.18 345);
14
+
--sl-color-accent: oklch(0.65 0.18 345);
15
+
--sl-color-accent-low: oklch(0.92 0.05 345);
16
+
--sl-color-border: oklch(0.65 0.02 30);
17
+
--sl-color-gray-1: oklch(0.45 0.02 30);
18
+
--sl-color-gray-2: oklch(0.35 0.02 30);
19
+
--sl-color-gray-3: oklch(0.28 0.02 30);
20
+
--sl-color-gray-4: oklch(0.20 0.015 30);
21
+
--sl-color-gray-5: oklch(0.65 0.02 30);
22
22
--sl-color-bg-accent: oklch(0.88 0.01 35);
23
23
}
24
24
···
70
70
/* Sidebar active/hover state text contrast fix */
71
71
.sidebar a[aria-current="page"],
72
72
.sidebar a[aria-current="page"] span {
73
-
color: oklch(0.23 0.015 285) !important;
73
+
color: oklch(0.15 0.015 30) !important;
74
74
}
75
75
76
76
[data-theme="dark"] .sidebar a[aria-current="page"],
-318
flake.lock
-318
flake.lock
···
1
-
{
2
-
"nodes": {
3
-
"crane": {
4
-
"flake": false,
5
-
"locked": {
6
-
"lastModified": 1758758545,
7
-
"narHash": "sha256-NU5WaEdfwF6i8faJ2Yh+jcK9vVFrofLcwlD/mP65JrI=",
8
-
"owner": "ipetkov",
9
-
"repo": "crane",
10
-
"rev": "95d528a5f54eaba0d12102249ce42f4d01f4e364",
11
-
"type": "github"
12
-
},
13
-
"original": {
14
-
"owner": "ipetkov",
15
-
"ref": "v0.21.1",
16
-
"repo": "crane",
17
-
"type": "github"
18
-
}
19
-
},
20
-
"dream2nix": {
21
-
"inputs": {
22
-
"nixpkgs": [
23
-
"nci",
24
-
"nixpkgs"
25
-
],
26
-
"purescript-overlay": "purescript-overlay",
27
-
"pyproject-nix": "pyproject-nix"
28
-
},
29
-
"locked": {
30
-
"lastModified": 1754978539,
31
-
"narHash": "sha256-nrDovydywSKRbWim9Ynmgj8SBm8LK3DI2WuhIqzOHYI=",
32
-
"owner": "nix-community",
33
-
"repo": "dream2nix",
34
-
"rev": "fbec3263cb4895ac86ee9506cdc4e6919a1a2214",
35
-
"type": "github"
36
-
},
37
-
"original": {
38
-
"owner": "nix-community",
39
-
"repo": "dream2nix",
40
-
"type": "github"
41
-
}
42
-
},
43
-
"fenix": {
44
-
"inputs": {
45
-
"nixpkgs": [
46
-
"nixpkgs"
47
-
],
48
-
"rust-analyzer-src": "rust-analyzer-src"
49
-
},
50
-
"locked": {
51
-
"lastModified": 1762584108,
52
-
"narHash": "sha256-wZUW7dlXMXaRdvNbaADqhF8gg9bAfFiMV+iyFQiDv+Y=",
53
-
"owner": "nix-community",
54
-
"repo": "fenix",
55
-
"rev": "32f3ad3b6c690061173e1ac16708874975ec6056",
56
-
"type": "github"
57
-
},
58
-
"original": {
59
-
"owner": "nix-community",
60
-
"repo": "fenix",
61
-
"type": "github"
62
-
}
63
-
},
64
-
"flake-compat": {
65
-
"flake": false,
66
-
"locked": {
67
-
"lastModified": 1696426674,
68
-
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
69
-
"owner": "edolstra",
70
-
"repo": "flake-compat",
71
-
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
72
-
"type": "github"
73
-
},
74
-
"original": {
75
-
"owner": "edolstra",
76
-
"repo": "flake-compat",
77
-
"type": "github"
78
-
}
79
-
},
80
-
"mk-naked-shell": {
81
-
"flake": false,
82
-
"locked": {
83
-
"lastModified": 1681286841,
84
-
"narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=",
85
-
"owner": "90-008",
86
-
"repo": "mk-naked-shell",
87
-
"rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd",
88
-
"type": "github"
89
-
},
90
-
"original": {
91
-
"owner": "90-008",
92
-
"repo": "mk-naked-shell",
93
-
"type": "github"
94
-
}
95
-
},
96
-
"nci": {
97
-
"inputs": {
98
-
"crane": "crane",
99
-
"dream2nix": "dream2nix",
100
-
"mk-naked-shell": "mk-naked-shell",
101
-
"nixpkgs": [
102
-
"nixpkgs"
103
-
],
104
-
"parts": "parts",
105
-
"rust-overlay": "rust-overlay",
106
-
"treefmt": "treefmt"
107
-
},
108
-
"locked": {
109
-
"lastModified": 1762582646,
110
-
"narHash": "sha256-MMzE4xccG+8qbLhdaZoeFDUKWUOn3B4lhp5dZmgukmM=",
111
-
"owner": "90-008",
112
-
"repo": "nix-cargo-integration",
113
-
"rev": "0993c449377049fa8868a664e8290ac6658e0b9a",
114
-
"type": "github"
115
-
},
116
-
"original": {
117
-
"owner": "90-008",
118
-
"repo": "nix-cargo-integration",
119
-
"type": "github"
120
-
}
121
-
},
122
-
"nixpkgs": {
123
-
"locked": {
124
-
"lastModified": 1762361079,
125
-
"narHash": "sha256-lz718rr1BDpZBYk7+G8cE6wee3PiBUpn8aomG/vLLiY=",
126
-
"owner": "nixos",
127
-
"repo": "nixpkgs",
128
-
"rev": "ffcdcf99d65c61956d882df249a9be53e5902ea5",
129
-
"type": "github"
130
-
},
131
-
"original": {
132
-
"owner": "nixos",
133
-
"ref": "nixpkgs-unstable",
134
-
"repo": "nixpkgs",
135
-
"type": "github"
136
-
}
137
-
},
138
-
"parts": {
139
-
"inputs": {
140
-
"nixpkgs-lib": [
141
-
"nci",
142
-
"nixpkgs"
143
-
]
144
-
},
145
-
"locked": {
146
-
"lastModified": 1762440070,
147
-
"narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=",
148
-
"owner": "hercules-ci",
149
-
"repo": "flake-parts",
150
-
"rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8",
151
-
"type": "github"
152
-
},
153
-
"original": {
154
-
"owner": "hercules-ci",
155
-
"repo": "flake-parts",
156
-
"type": "github"
157
-
}
158
-
},
159
-
"parts_2": {
160
-
"inputs": {
161
-
"nixpkgs-lib": [
162
-
"nixpkgs"
163
-
]
164
-
},
165
-
"locked": {
166
-
"lastModified": 1762440070,
167
-
"narHash": "sha256-xxdepIcb39UJ94+YydGP221rjnpkDZUlykKuF54PsqI=",
168
-
"owner": "hercules-ci",
169
-
"repo": "flake-parts",
170
-
"rev": "26d05891e14c88eb4a5d5bee659c0db5afb609d8",
171
-
"type": "github"
172
-
},
173
-
"original": {
174
-
"owner": "hercules-ci",
175
-
"repo": "flake-parts",
176
-
"type": "github"
177
-
}
178
-
},
179
-
"purescript-overlay": {
180
-
"inputs": {
181
-
"flake-compat": "flake-compat",
182
-
"nixpkgs": [
183
-
"nci",
184
-
"dream2nix",
185
-
"nixpkgs"
186
-
],
187
-
"slimlock": "slimlock"
188
-
},
189
-
"locked": {
190
-
"lastModified": 1728546539,
191
-
"narHash": "sha256-Sws7w0tlnjD+Bjck1nv29NjC5DbL6nH5auL9Ex9Iz2A=",
192
-
"owner": "thomashoneyman",
193
-
"repo": "purescript-overlay",
194
-
"rev": "4ad4c15d07bd899d7346b331f377606631eb0ee4",
195
-
"type": "github"
196
-
},
197
-
"original": {
198
-
"owner": "thomashoneyman",
199
-
"repo": "purescript-overlay",
200
-
"type": "github"
201
-
}
202
-
},
203
-
"pyproject-nix": {
204
-
"inputs": {
205
-
"nixpkgs": [
206
-
"nci",
207
-
"dream2nix",
208
-
"nixpkgs"
209
-
]
210
-
},
211
-
"locked": {
212
-
"lastModified": 1752481895,
213
-
"narHash": "sha256-luVj97hIMpCbwhx3hWiRwjP2YvljWy8FM+4W9njDhLA=",
214
-
"owner": "pyproject-nix",
215
-
"repo": "pyproject.nix",
216
-
"rev": "16ee295c25107a94e59a7fc7f2e5322851781162",
217
-
"type": "github"
218
-
},
219
-
"original": {
220
-
"owner": "pyproject-nix",
221
-
"repo": "pyproject.nix",
222
-
"type": "github"
223
-
}
224
-
},
225
-
"root": {
226
-
"inputs": {
227
-
"fenix": "fenix",
228
-
"nci": "nci",
229
-
"nixpkgs": "nixpkgs",
230
-
"parts": "parts_2"
231
-
}
232
-
},
233
-
"rust-analyzer-src": {
234
-
"flake": false,
235
-
"locked": {
236
-
"lastModified": 1762438844,
237
-
"narHash": "sha256-ApIKJf6CcMsV2nYBXhGF95BmZMO/QXPhgfSnkA/rVUo=",
238
-
"owner": "rust-lang",
239
-
"repo": "rust-analyzer",
240
-
"rev": "4bf516ee5a960c1e2eee9fedd9b1c9e976a19c86",
241
-
"type": "github"
242
-
},
243
-
"original": {
244
-
"owner": "rust-lang",
245
-
"ref": "nightly",
246
-
"repo": "rust-analyzer",
247
-
"type": "github"
248
-
}
249
-
},
250
-
"rust-overlay": {
251
-
"inputs": {
252
-
"nixpkgs": [
253
-
"nci",
254
-
"nixpkgs"
255
-
]
256
-
},
257
-
"locked": {
258
-
"lastModified": 1762569282,
259
-
"narHash": "sha256-vINZAJpXQTZd5cfh06Rcw7hesH7sGSvi+Tn+HUieJn8=",
260
-
"owner": "oxalica",
261
-
"repo": "rust-overlay",
262
-
"rev": "a35a6144b976f70827c2fe2f5c89d16d8f9179d8",
263
-
"type": "github"
264
-
},
265
-
"original": {
266
-
"owner": "oxalica",
267
-
"repo": "rust-overlay",
268
-
"type": "github"
269
-
}
270
-
},
271
-
"slimlock": {
272
-
"inputs": {
273
-
"nixpkgs": [
274
-
"nci",
275
-
"dream2nix",
276
-
"purescript-overlay",
277
-
"nixpkgs"
278
-
]
279
-
},
280
-
"locked": {
281
-
"lastModified": 1688756706,
282
-
"narHash": "sha256-xzkkMv3neJJJ89zo3o2ojp7nFeaZc2G0fYwNXNJRFlo=",
283
-
"owner": "thomashoneyman",
284
-
"repo": "slimlock",
285
-
"rev": "cf72723f59e2340d24881fd7bf61cb113b4c407c",
286
-
"type": "github"
287
-
},
288
-
"original": {
289
-
"owner": "thomashoneyman",
290
-
"repo": "slimlock",
291
-
"type": "github"
292
-
}
293
-
},
294
-
"treefmt": {
295
-
"inputs": {
296
-
"nixpkgs": [
297
-
"nci",
298
-
"nixpkgs"
299
-
]
300
-
},
301
-
"locked": {
302
-
"lastModified": 1762410071,
303
-
"narHash": "sha256-aF5fvoZeoXNPxT0bejFUBXeUjXfHLSL7g+mjR/p5TEg=",
304
-
"owner": "numtide",
305
-
"repo": "treefmt-nix",
306
-
"rev": "97a30861b13c3731a84e09405414398fbf3e109f",
307
-
"type": "github"
308
-
},
309
-
"original": {
310
-
"owner": "numtide",
311
-
"repo": "treefmt-nix",
312
-
"type": "github"
313
-
}
314
-
}
315
-
},
316
-
"root": "root",
317
-
"version": 7
318
-
}
-59
flake.nix
-59
flake.nix
···
1
-
{
2
-
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
3
-
inputs.nci.url = "github:90-008/nix-cargo-integration";
4
-
inputs.nci.inputs.nixpkgs.follows = "nixpkgs";
5
-
inputs.parts.url = "github:hercules-ci/flake-parts";
6
-
inputs.parts.inputs.nixpkgs-lib.follows = "nixpkgs";
7
-
inputs.fenix = {
8
-
url = "github:nix-community/fenix";
9
-
inputs.nixpkgs.follows = "nixpkgs";
10
-
};
11
-
12
-
outputs = inputs @ {
13
-
parts,
14
-
nci,
15
-
...
16
-
}:
17
-
parts.lib.mkFlake {inherit inputs;} {
18
-
systems = ["x86_64-linux" "aarch64-darwin"];
19
-
imports = [
20
-
nci.flakeModule
21
-
./crates.nix
22
-
];
23
-
perSystem = {
24
-
pkgs,
25
-
config,
26
-
...
27
-
}: let
28
-
crateOutputs = config.nci.outputs."wisp-cli";
29
-
mkRenamedPackage = name: pkg: isWindows: pkgs.runCommand name {} ''
30
-
mkdir -p $out/bin
31
-
if [ -f ${pkg}/bin/wisp-cli.exe ]; then
32
-
cp ${pkg}/bin/wisp-cli.exe $out/bin/${name}
33
-
elif [ -f ${pkg}/bin/wisp-cli ]; then
34
-
cp ${pkg}/bin/wisp-cli $out/bin/${name}
35
-
else
36
-
echo "Error: Could not find wisp-cli binary in ${pkg}/bin/"
37
-
ls -la ${pkg}/bin/ || true
38
-
exit 1
39
-
fi
40
-
'';
41
-
in {
42
-
devShells.default = crateOutputs.devShell;
43
-
packages.default = crateOutputs.packages.release;
44
-
packages.wisp-cli-x86_64-linux = mkRenamedPackage "wisp-cli-x86_64-linux" crateOutputs.packages.release false;
45
-
packages.wisp-cli-aarch64-linux = mkRenamedPackage "wisp-cli-aarch64-linux" crateOutputs.allTargets."aarch64-unknown-linux-gnu".packages.release false;
46
-
packages.wisp-cli-x86_64-windows = mkRenamedPackage "wisp-cli-x86_64-windows.exe" crateOutputs.allTargets."x86_64-pc-windows-gnu".packages.release true;
47
-
packages.wisp-cli-aarch64-darwin = mkRenamedPackage "wisp-cli-aarch64-darwin" crateOutputs.allTargets."aarch64-apple-darwin".packages.release false;
48
-
packages.all = pkgs.symlinkJoin {
49
-
name = "wisp-cli-all";
50
-
paths = [
51
-
config.packages.wisp-cli-x86_64-linux
52
-
config.packages.wisp-cli-aarch64-linux
53
-
config.packages.wisp-cli-x86_64-windows
54
-
config.packages.wisp-cli-aarch64-darwin
55
-
];
56
-
};
57
-
};
58
-
};
59
-
}
+59
lexicons/fs.json
+59
lexicons/fs.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "place.wisp.fs",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Virtual filesystem manifest for a Wisp site",
8
+
"record": {
9
+
"type": "object",
10
+
"required": ["site", "root", "createdAt"],
11
+
"properties": {
12
+
"site": { "type": "string" },
13
+
"root": { "type": "ref", "ref": "#directory" },
14
+
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
15
+
"createdAt": { "type": "string", "format": "datetime" }
16
+
}
17
+
}
18
+
},
19
+
"file": {
20
+
"type": "object",
21
+
"required": ["type", "blob"],
22
+
"properties": {
23
+
"type": { "type": "string", "const": "file" },
24
+
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
25
+
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
26
+
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
27
+
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" } }
28
+
},
29
+
"directory": {
30
+
"type": "object",
31
+
"required": ["type", "entries"],
32
+
"properties": {
33
+
"type": { "type": "string", "const": "directory" },
34
+
"entries": {
35
+
"type": "array",
36
+
"maxLength": 500,
37
+
"items": { "type": "ref", "ref": "#entry" }
38
+
}
39
+
}
40
+
},
41
+
"entry": {
42
+
"type": "object",
43
+
"required": ["name", "node"],
44
+
"properties": {
45
+
"name": { "type": "string", "maxLength": 255 },
46
+
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
47
+
}
48
+
},
49
+
"subfs": {
50
+
"type": "object",
51
+
"required": ["type", "subject"],
52
+
"properties": {
53
+
"type": { "type": "string", "const": "subfs" },
54
+
"subject": { "type": "string", "format": "at-uri", "description": "AT-URI pointing to a place.wisp.subfs record containing this subtree." },
55
+
"flat": { "type": "boolean", "description": "If true (default), the subfs record's root entries are merged (flattened) into the parent directory, replacing the subfs entry. If false, the subfs entries are placed in a subdirectory with the subfs entry's name. Flat merging is useful for splitting large directories across multiple records while maintaining a flat structure." }
56
+
}
57
+
}
58
+
}
59
+
}
+76
lexicons/settings.json
+76
lexicons/settings.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "place.wisp.settings",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Configuration settings for a static site hosted on wisp.place",
8
+
"key": "any",
9
+
"record": {
10
+
"type": "object",
11
+
"properties": {
12
+
"directoryListing": {
13
+
"type": "boolean",
14
+
"description": "Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.",
15
+
"default": false
16
+
},
17
+
"spaMode": {
18
+
"type": "string",
19
+
"description": "File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.",
20
+
"maxLength": 500
21
+
},
22
+
"custom404": {
23
+
"type": "string",
24
+
"description": "Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
25
+
"maxLength": 500
26
+
},
27
+
"indexFiles": {
28
+
"type": "array",
29
+
"description": "Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
30
+
"items": {
31
+
"type": "string",
32
+
"maxLength": 255
33
+
},
34
+
"maxLength": 10
35
+
},
36
+
"cleanUrls": {
37
+
"type": "boolean",
38
+
"description": "Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.",
39
+
"default": false
40
+
},
41
+
"headers": {
42
+
"type": "array",
43
+
"description": "Custom HTTP headers to set on responses",
44
+
"items": {
45
+
"type": "ref",
46
+
"ref": "#customHeader"
47
+
},
48
+
"maxLength": 50
49
+
}
50
+
}
51
+
}
52
+
},
53
+
"customHeader": {
54
+
"type": "object",
55
+
"description": "Custom HTTP header configuration",
56
+
"required": ["name", "value"],
57
+
"properties": {
58
+
"name": {
59
+
"type": "string",
60
+
"description": "HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
61
+
"maxLength": 100
62
+
},
63
+
"value": {
64
+
"type": "string",
65
+
"description": "HTTP header value",
66
+
"maxLength": 1000
67
+
},
68
+
"path": {
69
+
"type": "string",
70
+
"description": "Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
71
+
"maxLength": 500
72
+
}
73
+
}
74
+
}
75
+
}
76
+
}
+59
lexicons/subfs.json
+59
lexicons/subfs.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "place.wisp.subfs",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.",
8
+
"record": {
9
+
"type": "object",
10
+
"required": ["root", "createdAt"],
11
+
"properties": {
12
+
"root": { "type": "ref", "ref": "#directory" },
13
+
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
14
+
"createdAt": { "type": "string", "format": "datetime" }
15
+
}
16
+
}
17
+
},
18
+
"file": {
19
+
"type": "object",
20
+
"required": ["type", "blob"],
21
+
"properties": {
22
+
"type": { "type": "string", "const": "file" },
23
+
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
24
+
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
25
+
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
26
+
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" }
27
+
}
28
+
},
29
+
"directory": {
30
+
"type": "object",
31
+
"required": ["type", "entries"],
32
+
"properties": {
33
+
"type": { "type": "string", "const": "directory" },
34
+
"entries": {
35
+
"type": "array",
36
+
"maxLength": 500,
37
+
"items": { "type": "ref", "ref": "#entry" }
38
+
}
39
+
}
40
+
},
41
+
"entry": {
42
+
"type": "object",
43
+
"required": ["name", "node"],
44
+
"properties": {
45
+
"name": { "type": "string", "maxLength": 255 },
46
+
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
47
+
}
48
+
},
49
+
"subfs": {
50
+
"type": "object",
51
+
"required": ["type", "subject"],
52
+
"properties": {
53
+
"type": { "type": "string", "const": "subfs" },
54
+
"subject": { "type": "string", "format": "at-uri", "description": "AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures." }
55
+
}
56
+
}
57
+
}
58
+
}
59
+
+13
-1
package.json
+13
-1
package.json
···
9
9
],
10
10
"dependencies": {
11
11
"@tailwindcss/cli": "^4.1.17",
12
+
"atproto-ui": "^0.12.0",
12
13
"bun-plugin-tailwind": "^0.1.2",
14
+
"elysia": "^1.4.18",
13
15
"tailwindcss": "^4.1.17"
14
16
},
15
17
"scripts": {
···
19
21
"build": "cd apps/main-app && bun run build.ts",
20
22
"build:hosting": "cd apps/hosting-service && npm run build",
21
23
"build:all": "bun run build && npm run build:hosting",
24
+
"check": "cd apps/main-app && npm run check && cd ../hosting-service && npm run check",
22
25
"screenshot": "bun run apps/main-app/scripts/screenshot-sites.ts",
23
26
"hosting:dev": "cd apps/hosting-service && npm run dev",
24
-
"hosting:start": "cd apps/hosting-service && npm run start"
27
+
"hosting:start": "cd apps/hosting-service && npm run start",
28
+
"codegen": "./scripts/codegen.sh"
29
+
},
30
+
"trustedDependencies": [
31
+
"@parcel/watcher",
32
+
"bun",
33
+
"esbuild"
34
+
],
35
+
"devDependencies": {
36
+
"@types/bun": "^1.3.5"
25
37
}
26
38
}
+3
packages/@wisp/atproto-utils/package.json
+3
packages/@wisp/atproto-utils/package.json
+1
-1
packages/@wisp/constants/src/index.ts
+1
-1
packages/@wisp/constants/src/index.ts
+244
packages/@wisp/fs-utils/src/tree.test.ts
+244
packages/@wisp/fs-utils/src/tree.test.ts
···
1
+
import { describe, test, expect } from 'bun:test'
2
+
import { processUploadedFiles, type UploadedFile } from './tree'
3
+
4
+
describe('processUploadedFiles', () => {
5
+
test('should preserve nested directory structure', () => {
6
+
const files: UploadedFile[] = [
7
+
{
8
+
name: 'mysite/index.html',
9
+
content: Buffer.from('<html>'),
10
+
mimeType: 'text/html',
11
+
size: 6
12
+
},
13
+
{
14
+
name: 'mysite/_astro/main.js',
15
+
content: Buffer.from('console.log()'),
16
+
mimeType: 'application/javascript',
17
+
size: 13
18
+
},
19
+
{
20
+
name: 'mysite/_astro/styles.css',
21
+
content: Buffer.from('body {}'),
22
+
mimeType: 'text/css',
23
+
size: 7
24
+
},
25
+
{
26
+
name: 'mysite/images/logo.png',
27
+
content: Buffer.from([0x89, 0x50, 0x4e, 0x47]),
28
+
mimeType: 'image/png',
29
+
size: 4
30
+
}
31
+
]
32
+
33
+
const result = processUploadedFiles(files)
34
+
35
+
expect(result.fileCount).toBe(4)
36
+
expect(result.directory.entries).toHaveLength(3) // index.html, _astro/, images/
37
+
38
+
// Check _astro directory exists
39
+
const astroEntry = result.directory.entries.find(e => e.name === '_astro')
40
+
expect(astroEntry).toBeTruthy()
41
+
expect('type' in astroEntry!.node && astroEntry!.node.type).toBe('directory')
42
+
43
+
if ('entries' in astroEntry!.node) {
44
+
const astroDir = astroEntry!.node
45
+
expect(astroDir.entries).toHaveLength(2) // main.js, styles.css
46
+
expect(astroDir.entries.find(e => e.name === 'main.js')).toBeTruthy()
47
+
expect(astroDir.entries.find(e => e.name === 'styles.css')).toBeTruthy()
48
+
}
49
+
50
+
// Check images directory exists
51
+
const imagesEntry = result.directory.entries.find(e => e.name === 'images')
52
+
expect(imagesEntry).toBeTruthy()
53
+
expect('type' in imagesEntry!.node && imagesEntry!.node.type).toBe('directory')
54
+
55
+
if ('entries' in imagesEntry!.node) {
56
+
const imagesDir = imagesEntry!.node
57
+
expect(imagesDir.entries).toHaveLength(1) // logo.png
58
+
expect(imagesDir.entries.find(e => e.name === 'logo.png')).toBeTruthy()
59
+
}
60
+
})
61
+
62
+
test('should handle deeply nested directories', () => {
63
+
const files: UploadedFile[] = [
64
+
{
65
+
name: 'site/a/b/c/d/deep.txt',
66
+
content: Buffer.from('deep'),
67
+
mimeType: 'text/plain',
68
+
size: 4
69
+
}
70
+
]
71
+
72
+
const result = processUploadedFiles(files)
73
+
74
+
expect(result.fileCount).toBe(1)
75
+
76
+
// Navigate through nested structure
77
+
const aEntry = result.directory.entries.find(e => e.name === 'a')
78
+
expect(aEntry).toBeTruthy()
79
+
expect('type' in aEntry!.node && aEntry!.node.type).toBe('directory')
80
+
81
+
if ('entries' in aEntry!.node) {
82
+
const bEntry = aEntry!.node.entries.find(e => e.name === 'b')
83
+
expect(bEntry).toBeTruthy()
84
+
expect('type' in bEntry!.node && bEntry!.node.type).toBe('directory')
85
+
86
+
if ('entries' in bEntry!.node) {
87
+
const cEntry = bEntry!.node.entries.find(e => e.name === 'c')
88
+
expect(cEntry).toBeTruthy()
89
+
expect('type' in cEntry!.node && cEntry!.node.type).toBe('directory')
90
+
91
+
if ('entries' in cEntry!.node) {
92
+
const dEntry = cEntry!.node.entries.find(e => e.name === 'd')
93
+
expect(dEntry).toBeTruthy()
94
+
expect('type' in dEntry!.node && dEntry!.node.type).toBe('directory')
95
+
96
+
if ('entries' in dEntry!.node) {
97
+
const fileEntry = dEntry!.node.entries.find(e => e.name === 'deep.txt')
98
+
expect(fileEntry).toBeTruthy()
99
+
expect('type' in fileEntry!.node && fileEntry!.node.type).toBe('file')
100
+
}
101
+
}
102
+
}
103
+
}
104
+
})
105
+
106
+
test('should handle files at root level', () => {
107
+
const files: UploadedFile[] = [
108
+
{
109
+
name: 'mysite/index.html',
110
+
content: Buffer.from('<html>'),
111
+
mimeType: 'text/html',
112
+
size: 6
113
+
},
114
+
{
115
+
name: 'mysite/robots.txt',
116
+
content: Buffer.from('User-agent: *'),
117
+
mimeType: 'text/plain',
118
+
size: 13
119
+
}
120
+
]
121
+
122
+
const result = processUploadedFiles(files)
123
+
124
+
expect(result.fileCount).toBe(2)
125
+
expect(result.directory.entries).toHaveLength(2)
126
+
expect(result.directory.entries.find(e => e.name === 'index.html')).toBeTruthy()
127
+
expect(result.directory.entries.find(e => e.name === 'robots.txt')).toBeTruthy()
128
+
})
129
+
130
+
test('should skip .git directories', () => {
131
+
const files: UploadedFile[] = [
132
+
{
133
+
name: 'mysite/index.html',
134
+
content: Buffer.from('<html>'),
135
+
mimeType: 'text/html',
136
+
size: 6
137
+
},
138
+
{
139
+
name: 'mysite/.git/config',
140
+
content: Buffer.from('[core]'),
141
+
mimeType: 'text/plain',
142
+
size: 6
143
+
},
144
+
{
145
+
name: 'mysite/.gitignore',
146
+
content: Buffer.from('node_modules'),
147
+
mimeType: 'text/plain',
148
+
size: 12
149
+
}
150
+
]
151
+
152
+
const result = processUploadedFiles(files)
153
+
154
+
expect(result.fileCount).toBe(2) // Only index.html and .gitignore
155
+
expect(result.directory.entries).toHaveLength(2)
156
+
expect(result.directory.entries.find(e => e.name === 'index.html')).toBeTruthy()
157
+
expect(result.directory.entries.find(e => e.name === '.gitignore')).toBeTruthy()
158
+
expect(result.directory.entries.find(e => e.name === '.git')).toBeFalsy()
159
+
})
160
+
161
+
test('should handle mixed root and nested files', () => {
162
+
const files: UploadedFile[] = [
163
+
{
164
+
name: 'mysite/index.html',
165
+
content: Buffer.from('<html>'),
166
+
mimeType: 'text/html',
167
+
size: 6
168
+
},
169
+
{
170
+
name: 'mysite/about/index.html',
171
+
content: Buffer.from('<html>'),
172
+
mimeType: 'text/html',
173
+
size: 6
174
+
},
175
+
{
176
+
name: 'mysite/about/team.html',
177
+
content: Buffer.from('<html>'),
178
+
mimeType: 'text/html',
179
+
size: 6
180
+
},
181
+
{
182
+
name: 'mysite/robots.txt',
183
+
content: Buffer.from('User-agent: *'),
184
+
mimeType: 'text/plain',
185
+
size: 13
186
+
}
187
+
]
188
+
189
+
const result = processUploadedFiles(files)
190
+
191
+
expect(result.fileCount).toBe(4)
192
+
expect(result.directory.entries).toHaveLength(3) // index.html, about/, robots.txt
193
+
194
+
const aboutEntry = result.directory.entries.find(e => e.name === 'about')
195
+
expect(aboutEntry).toBeTruthy()
196
+
expect('type' in aboutEntry!.node && aboutEntry!.node.type).toBe('directory')
197
+
198
+
if ('entries' in aboutEntry!.node) {
199
+
const aboutDir = aboutEntry!.node
200
+
expect(aboutDir.entries).toHaveLength(2) // index.html, team.html
201
+
expect(aboutDir.entries.find(e => e.name === 'index.html')).toBeTruthy()
202
+
expect(aboutDir.entries.find(e => e.name === 'team.html')).toBeTruthy()
203
+
}
204
+
})
205
+
206
+
test('should handle empty file array', () => {
207
+
const files: UploadedFile[] = []
208
+
209
+
const result = processUploadedFiles(files)
210
+
211
+
expect(result.fileCount).toBe(0)
212
+
expect(result.directory.entries).toHaveLength(0)
213
+
})
214
+
215
+
test('should strip base folder name from paths', () => {
216
+
// This tests the behavior where file.name includes the base folder
217
+
// e.g., "mysite/index.html" should become "index.html" at root
218
+
const files: UploadedFile[] = [
219
+
{
220
+
name: 'build-output/index.html',
221
+
content: Buffer.from('<html>'),
222
+
mimeType: 'text/html',
223
+
size: 6
224
+
},
225
+
{
226
+
name: 'build-output/assets/main.js',
227
+
content: Buffer.from('console.log()'),
228
+
mimeType: 'application/javascript',
229
+
size: 13
230
+
}
231
+
]
232
+
233
+
const result = processUploadedFiles(files)
234
+
235
+
expect(result.fileCount).toBe(2)
236
+
237
+
// Should have index.html at root and assets/ directory
238
+
expect(result.directory.entries.find(e => e.name === 'index.html')).toBeTruthy()
239
+
expect(result.directory.entries.find(e => e.name === 'assets')).toBeTruthy()
240
+
241
+
// Should NOT have 'build-output' directory
242
+
expect(result.directory.entries.find(e => e.name === 'build-output')).toBeFalsy()
243
+
})
244
+
})
-59
packages/@wisp/lexicons/lexicons/fs.json
-59
packages/@wisp/lexicons/lexicons/fs.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "place.wisp.fs",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "Virtual filesystem manifest for a Wisp site",
8
-
"record": {
9
-
"type": "object",
10
-
"required": ["site", "root", "createdAt"],
11
-
"properties": {
12
-
"site": { "type": "string" },
13
-
"root": { "type": "ref", "ref": "#directory" },
14
-
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
15
-
"createdAt": { "type": "string", "format": "datetime" }
16
-
}
17
-
}
18
-
},
19
-
"file": {
20
-
"type": "object",
21
-
"required": ["type", "blob"],
22
-
"properties": {
23
-
"type": { "type": "string", "const": "file" },
24
-
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
25
-
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
26
-
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
27
-
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" } }
28
-
},
29
-
"directory": {
30
-
"type": "object",
31
-
"required": ["type", "entries"],
32
-
"properties": {
33
-
"type": { "type": "string", "const": "directory" },
34
-
"entries": {
35
-
"type": "array",
36
-
"maxLength": 500,
37
-
"items": { "type": "ref", "ref": "#entry" }
38
-
}
39
-
}
40
-
},
41
-
"entry": {
42
-
"type": "object",
43
-
"required": ["name", "node"],
44
-
"properties": {
45
-
"name": { "type": "string", "maxLength": 255 },
46
-
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
47
-
}
48
-
},
49
-
"subfs": {
50
-
"type": "object",
51
-
"required": ["type", "subject"],
52
-
"properties": {
53
-
"type": { "type": "string", "const": "subfs" },
54
-
"subject": { "type": "string", "format": "at-uri", "description": "AT-URI pointing to a place.wisp.subfs record containing this subtree." },
55
-
"flat": { "type": "boolean", "description": "If true (default), the subfs record's root entries are merged (flattened) into the parent directory, replacing the subfs entry. If false, the subfs entries are placed in a subdirectory with the subfs entry's name. Flat merging is useful for splitting large directories across multiple records while maintaining a flat structure." }
56
-
}
57
-
}
58
-
}
59
-
}
-76
packages/@wisp/lexicons/lexicons/settings.json
-76
packages/@wisp/lexicons/lexicons/settings.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "place.wisp.settings",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "Configuration settings for a static site hosted on wisp.place",
8
-
"key": "any",
9
-
"record": {
10
-
"type": "object",
11
-
"properties": {
12
-
"directoryListing": {
13
-
"type": "boolean",
14
-
"description": "Enable directory listing mode for paths that resolve to directories without an index file. Incompatible with spaMode.",
15
-
"default": false
16
-
},
17
-
"spaMode": {
18
-
"type": "string",
19
-
"description": "File to serve for all routes (e.g., 'index.html'). When set, enables SPA mode where all non-file requests are routed to this file. Incompatible with directoryListing and custom404.",
20
-
"maxLength": 500
21
-
},
22
-
"custom404": {
23
-
"type": "string",
24
-
"description": "Custom 404 error page file path. Incompatible with directoryListing and spaMode.",
25
-
"maxLength": 500
26
-
},
27
-
"indexFiles": {
28
-
"type": "array",
29
-
"description": "Ordered list of files to try when serving a directory. Defaults to ['index.html'] if not specified.",
30
-
"items": {
31
-
"type": "string",
32
-
"maxLength": 255
33
-
},
34
-
"maxLength": 10
35
-
},
36
-
"cleanUrls": {
37
-
"type": "boolean",
38
-
"description": "Enable clean URL routing. When enabled, '/about' will attempt to serve '/about.html' or '/about/index.html' automatically.",
39
-
"default": false
40
-
},
41
-
"headers": {
42
-
"type": "array",
43
-
"description": "Custom HTTP headers to set on responses",
44
-
"items": {
45
-
"type": "ref",
46
-
"ref": "#customHeader"
47
-
},
48
-
"maxLength": 50
49
-
}
50
-
}
51
-
}
52
-
},
53
-
"customHeader": {
54
-
"type": "object",
55
-
"description": "Custom HTTP header configuration",
56
-
"required": ["name", "value"],
57
-
"properties": {
58
-
"name": {
59
-
"type": "string",
60
-
"description": "HTTP header name (e.g., 'Cache-Control', 'X-Frame-Options')",
61
-
"maxLength": 100
62
-
},
63
-
"value": {
64
-
"type": "string",
65
-
"description": "HTTP header value",
66
-
"maxLength": 1000
67
-
},
68
-
"path": {
69
-
"type": "string",
70
-
"description": "Optional glob pattern to apply this header to specific paths (e.g., '*.html', '/assets/*'). If not specified, applies to all paths.",
71
-
"maxLength": 500
72
-
}
73
-
}
74
-
}
75
-
}
76
-
}
-59
packages/@wisp/lexicons/lexicons/subfs.json
-59
packages/@wisp/lexicons/lexicons/subfs.json
···
1
-
{
2
-
"lexicon": 1,
3
-
"id": "place.wisp.subfs",
4
-
"defs": {
5
-
"main": {
6
-
"type": "record",
7
-
"description": "Virtual filesystem subtree referenced by place.wisp.fs records. When a subfs entry is expanded, its root entries are merged (flattened) into the parent directory, allowing large directories to be split across multiple records while maintaining a flat structure.",
8
-
"record": {
9
-
"type": "object",
10
-
"required": ["root", "createdAt"],
11
-
"properties": {
12
-
"root": { "type": "ref", "ref": "#directory" },
13
-
"fileCount": { "type": "integer", "minimum": 0, "maximum": 1000 },
14
-
"createdAt": { "type": "string", "format": "datetime" }
15
-
}
16
-
}
17
-
},
18
-
"file": {
19
-
"type": "object",
20
-
"required": ["type", "blob"],
21
-
"properties": {
22
-
"type": { "type": "string", "const": "file" },
23
-
"blob": { "type": "blob", "accept": ["*/*"], "maxSize": 1000000000, "description": "Content blob ref" },
24
-
"encoding": { "type": "string", "enum": ["gzip"], "description": "Content encoding (e.g., gzip for compressed files)" },
25
-
"mimeType": { "type": "string", "description": "Original MIME type before compression" },
26
-
"base64": { "type": "boolean", "description": "True if blob content is base64-encoded (used to bypass PDS content sniffing)" }
27
-
}
28
-
},
29
-
"directory": {
30
-
"type": "object",
31
-
"required": ["type", "entries"],
32
-
"properties": {
33
-
"type": { "type": "string", "const": "directory" },
34
-
"entries": {
35
-
"type": "array",
36
-
"maxLength": 500,
37
-
"items": { "type": "ref", "ref": "#entry" }
38
-
}
39
-
}
40
-
},
41
-
"entry": {
42
-
"type": "object",
43
-
"required": ["name", "node"],
44
-
"properties": {
45
-
"name": { "type": "string", "maxLength": 255 },
46
-
"node": { "type": "union", "refs": ["#file", "#directory", "#subfs"] }
47
-
}
48
-
},
49
-
"subfs": {
50
-
"type": "object",
51
-
"required": ["type", "subject"],
52
-
"properties": {
53
-
"type": { "type": "string", "const": "subfs" },
54
-
"subject": { "type": "string", "format": "at-uri", "description": "AT-URI pointing to another place.wisp.subfs record for nested subtrees. When expanded, the referenced record's root entries are merged (flattened) into the parent directory, allowing recursive splitting of large directory structures." }
55
-
}
56
-
}
57
-
}
58
-
}
59
-
+3
-2
packages/@wisp/lexicons/package.json
+3
-2
packages/@wisp/lexicons/package.json
···
32
32
}
33
33
},
34
34
"scripts": {
35
-
"codegen": "lex gen-server ./src ./lexicons"
35
+
"codegen": "lex gen-server ./src ../../../lexicons/*.json"
36
36
},
37
37
"dependencies": {
38
38
"@atproto/lexicon": "^0.5.1",
39
39
"@atproto/xrpc-server": "^0.9.5"
40
40
},
41
41
"devDependencies": {
42
-
"@atproto/lex-cli": "^0.9.5"
42
+
"@atproto/lex-cli": "^0.9.5",
43
+
"multiformats": "^13.4.1"
43
44
}
44
45
}
+1
-1
packages/@wisp/lexicons/src/index.ts
+1
-1
packages/@wisp/lexicons/src/index.ts
···
9
9
type MethodConfigOrHandler,
10
10
createServer as createXrpcServer,
11
11
} from '@atproto/xrpc-server'
12
-
import { schemas } from './lexicons'
12
+
import { schemas } from './lexicons.js'
13
13
14
14
export function createServer(options?: XrpcOptions): Server {
15
15
return new Server(options)
+1
-1
packages/@wisp/lexicons/src/lexicons.ts
+1
-1
packages/@wisp/lexicons/src/lexicons.ts
+33
packages/@wisp/observability/.env.example
+33
packages/@wisp/observability/.env.example
···
1
+
# Grafana Cloud Configuration for @wisp/observability
2
+
# Copy this file to .env and fill in your actual values
3
+
4
+
# ============================================================================
5
+
# Grafana Loki (for logs)
6
+
# ============================================================================
7
+
GRAFANA_LOKI_URL=https://logs-prod-xxx.grafana.net
8
+
9
+
# Authentication Option 1: Bearer Token (Grafana Cloud)
10
+
GRAFANA_LOKI_TOKEN=glc_xxx
11
+
12
+
# Authentication Option 2: Username/Password (Self-hosted or some Grafana setups)
13
+
# GRAFANA_LOKI_USERNAME=your-username
14
+
# GRAFANA_LOKI_PASSWORD=your-password
15
+
16
+
# ============================================================================
17
+
# Grafana Prometheus (for metrics)
18
+
# ============================================================================
19
+
# Note: Add /api/prom to the base URL for OTLP export
20
+
GRAFANA_PROMETHEUS_URL=https://prometheus-prod-xxx.grafana.net/api/prom
21
+
22
+
# Authentication Option 1: Bearer Token (Grafana Cloud)
23
+
GRAFANA_PROMETHEUS_TOKEN=glc_xxx
24
+
25
+
# Authentication Option 2: Username/Password (Self-hosted or some Grafana setups)
26
+
# GRAFANA_PROMETHEUS_USERNAME=your-username
27
+
# GRAFANA_PROMETHEUS_PASSWORD=your-password
28
+
29
+
# ============================================================================
30
+
# Optional: Override service metadata
31
+
# ============================================================================
32
+
# SERVICE_NAME=wisp-app
33
+
# SERVICE_VERSION=1.0.0
+217
packages/@wisp/observability/README.md
+217
packages/@wisp/observability/README.md
···
1
+
# @wisp/observability
2
+
3
+
Framework-agnostic observability package with Grafana integration for logs and metrics persistence.
4
+
5
+
## Features
6
+
7
+
- **In-memory storage** for local development
8
+
- **Grafana Loki** integration for log persistence
9
+
- **Prometheus/OTLP** integration for metrics
10
+
- Framework middleware for Elysia and Hono
11
+
- Automatic batching and buffering for efficient data transmission
12
+
13
+
## Installation
14
+
15
+
```bash
16
+
bun add @wisp/observability
17
+
```
18
+
19
+
## Basic Usage
20
+
21
+
### Without Grafana (In-Memory Only)
22
+
23
+
```typescript
24
+
import { createLogger, metricsCollector } from '@wisp/observability'
25
+
26
+
const logger = createLogger('my-service')
27
+
28
+
// Log messages
29
+
logger.info('Server started')
30
+
logger.error('Failed to connect', new Error('Connection refused'))
31
+
32
+
// Record metrics
33
+
metricsCollector.recordRequest('/api/users', 'GET', 200, 45, 'my-service')
34
+
```
35
+
36
+
### With Grafana Integration
37
+
38
+
```typescript
39
+
import { initializeGrafanaExporters, createLogger } from '@wisp/observability'
40
+
41
+
// Initialize at application startup
42
+
initializeGrafanaExporters({
43
+
lokiUrl: 'https://logs-prod.grafana.net',
44
+
lokiAuth: {
45
+
bearerToken: 'your-loki-api-key'
46
+
},
47
+
prometheusUrl: 'https://prometheus-prod.grafana.net',
48
+
prometheusAuth: {
49
+
bearerToken: 'your-prometheus-api-key'
50
+
},
51
+
serviceName: 'wisp-app',
52
+
serviceVersion: '1.0.0',
53
+
batchSize: 100,
54
+
flushIntervalMs: 5000
55
+
})
56
+
57
+
// Now all logs and metrics will be sent to Grafana automatically
58
+
const logger = createLogger('my-service')
59
+
logger.info('This will be sent to Grafana Loki')
60
+
```
61
+
62
+
## Configuration
63
+
64
+
### Environment Variables
65
+
66
+
You can configure Grafana integration using environment variables:
67
+
68
+
```bash
69
+
# Loki configuration
70
+
GRAFANA_LOKI_URL=https://logs-prod.grafana.net
71
+
72
+
# Authentication Option 1: Bearer Token (Grafana Cloud)
73
+
GRAFANA_LOKI_TOKEN=your-loki-api-key
74
+
75
+
# Authentication Option 2: Username/Password (Self-hosted or some Grafana setups)
76
+
GRAFANA_LOKI_USERNAME=your-username
77
+
GRAFANA_LOKI_PASSWORD=your-password
78
+
79
+
# Prometheus configuration
80
+
GRAFANA_PROMETHEUS_URL=https://prometheus-prod.grafana.net/api/prom
81
+
82
+
# Authentication Option 1: Bearer Token (Grafana Cloud)
83
+
GRAFANA_PROMETHEUS_TOKEN=your-prometheus-api-key
84
+
85
+
# Authentication Option 2: Username/Password (Self-hosted or some Grafana setups)
86
+
GRAFANA_PROMETHEUS_USERNAME=your-username
87
+
GRAFANA_PROMETHEUS_PASSWORD=your-password
88
+
```
89
+
90
+
### Programmatic Configuration
91
+
92
+
```typescript
93
+
import { initializeGrafanaExporters } from '@wisp/observability'
94
+
95
+
initializeGrafanaExporters({
96
+
// Loki configuration for logs
97
+
lokiUrl: 'https://logs-prod.grafana.net',
98
+
lokiAuth: {
99
+
// Option 1: Bearer token (recommended for Grafana Cloud)
100
+
bearerToken: 'your-api-key',
101
+
102
+
// Option 2: Basic auth
103
+
username: 'your-username',
104
+
password: 'your-password'
105
+
},
106
+
107
+
// Prometheus/OTLP configuration for metrics
108
+
prometheusUrl: 'https://prometheus-prod.grafana.net',
109
+
prometheusAuth: {
110
+
bearerToken: 'your-api-key'
111
+
},
112
+
113
+
// Service metadata
114
+
serviceName: 'wisp-app',
115
+
serviceVersion: '1.0.0',
116
+
117
+
// Batching configuration
118
+
batchSize: 100, // Flush after this many entries
119
+
flushIntervalMs: 5000, // Flush every 5 seconds
120
+
121
+
// Enable/disable exporters
122
+
enabled: true
123
+
})
124
+
```
125
+
126
+
## Middleware Integration
127
+
128
+
### Elysia
129
+
130
+
```typescript
131
+
import { Elysia } from 'elysia'
132
+
import { observabilityMiddleware } from '@wisp/observability/middleware/elysia'
133
+
import { initializeGrafanaExporters } from '@wisp/observability'
134
+
135
+
// Initialize Grafana exporters
136
+
initializeGrafanaExporters({
137
+
lokiUrl: process.env.GRAFANA_LOKI_URL,
138
+
lokiAuth: { bearerToken: process.env.GRAFANA_LOKI_TOKEN }
139
+
})
140
+
141
+
const app = new Elysia()
142
+
.use(observabilityMiddleware({ service: 'main-app' }))
143
+
.get('/', () => 'Hello World')
144
+
.listen(3000)
145
+
```
146
+
147
+
### Hono
148
+
149
+
```typescript
150
+
import { Hono } from 'hono'
151
+
import { observabilityMiddleware, observabilityErrorHandler } from '@wisp/observability/middleware/hono'
152
+
import { initializeGrafanaExporters } from '@wisp/observability'
153
+
154
+
// Initialize Grafana exporters
155
+
initializeGrafanaExporters({
156
+
lokiUrl: process.env.GRAFANA_LOKI_URL,
157
+
lokiAuth: { bearerToken: process.env.GRAFANA_LOKI_TOKEN }
158
+
})
159
+
160
+
const app = new Hono()
161
+
app.use('*', observabilityMiddleware({ service: 'hosting-service' }))
162
+
app.onError(observabilityErrorHandler({ service: 'hosting-service' }))
163
+
```
164
+
165
+
## Grafana Cloud Setup
166
+
167
+
1. **Create a Grafana Cloud account** at https://grafana.com/
168
+
169
+
2. **Get your Loki credentials:**
170
+
- Go to your Grafana Cloud portal
171
+
- Navigate to "Loki" โ "Details"
172
+
- Copy the Push endpoint URL and create an API key
173
+
174
+
3. **Get your Prometheus credentials:**
175
+
- Navigate to "Prometheus" โ "Details"
176
+
- Copy the Remote Write endpoint and create an API key
177
+
178
+
4. **Configure your application:**
179
+
```typescript
180
+
initializeGrafanaExporters({
181
+
lokiUrl: 'https://logs-prod-xxx.grafana.net',
182
+
lokiAuth: { bearerToken: 'glc_xxx' },
183
+
prometheusUrl: 'https://prometheus-prod-xxx.grafana.net/api/prom',
184
+
prometheusAuth: { bearerToken: 'glc_xxx' }
185
+
})
186
+
```
187
+
188
+
## Data Flow
189
+
190
+
1. **Logs** โ Buffered โ Batched โ Sent to Grafana Loki
191
+
2. **Metrics** โ Aggregated โ Exported via OTLP โ Sent to Prometheus
192
+
3. **Errors** โ Deduplicated โ Sent to Loki with error tag
193
+
194
+
## Performance Considerations
195
+
196
+
- Logs and metrics are batched to reduce network overhead
197
+
- Default batch size: 100 entries
198
+
- Default flush interval: 5 seconds
199
+
- Failed exports are logged but don't block application
200
+
- In-memory buffers are capped to prevent memory leaks
201
+
202
+
## Graceful Shutdown
203
+
204
+
The exporters automatically register shutdown handlers:
205
+
206
+
```typescript
207
+
import { shutdownGrafanaExporters } from '@wisp/observability'
208
+
209
+
// Manual shutdown if needed
210
+
process.on('beforeExit', async () => {
211
+
await shutdownGrafanaExporters()
212
+
})
213
+
```
214
+
215
+
## License
216
+
217
+
MIT
+13
-1
packages/@wisp/observability/package.json
+13
-1
packages/@wisp/observability/package.json
···
24
24
}
25
25
},
26
26
"peerDependencies": {
27
-
"hono": "^4.0.0"
27
+
"hono": "^4.10.7"
28
28
},
29
29
"peerDependenciesMeta": {
30
30
"hono": {
31
31
"optional": true
32
32
}
33
+
},
34
+
"dependencies": {
35
+
"@opentelemetry/api": "^1.9.0",
36
+
"@opentelemetry/sdk-metrics": "^1.29.0",
37
+
"@opentelemetry/exporter-metrics-otlp-http": "^0.56.0",
38
+
"@opentelemetry/resources": "^1.29.0",
39
+
"@opentelemetry/semantic-conventions": "^1.29.0"
40
+
},
41
+
"devDependencies": {
42
+
"@hono/node-server": "^1.19.6",
43
+
"bun-types": "^1.3.3",
44
+
"typescript": "^5.9.3"
33
45
}
34
46
}
+12
-2
packages/@wisp/observability/src/core.ts
+12
-2
packages/@wisp/observability/src/core.ts
···
3
3
* Framework-agnostic logging, error tracking, and metrics collection
4
4
*/
5
5
6
+
import { lokiExporter, metricsExporter } from './exporters'
7
+
6
8
// ============================================================================
7
9
// Types
8
10
// ============================================================================
···
128
130
logs.splice(MAX_LOGS)
129
131
}
130
132
133
+
// Send to Loki exporter
134
+
lokiExporter.pushLog(entry)
135
+
131
136
// Also log to console for compatibility
132
137
const contextStr = context ? ` ${JSON.stringify(context)}` : ''
133
138
const traceStr = traceId ? ` [trace:${traceId}]` : ''
···
163
168
},
164
169
165
170
debug(message: string, service: string, context?: Record<string, any>, traceId?: string) {
166
-
const env = typeof Bun !== 'undefined' ? Bun.env.NODE_ENV : process.env.NODE_ENV;
167
-
if (env !== 'production') {
171
+
if (process.env.NODE_ENV !== 'production') {
168
172
this.log('debug', message, service, context, traceId)
169
173
}
170
174
},
···
233
237
234
238
errors.set(key, entry)
235
239
240
+
// Send to Loki exporter
241
+
lokiExporter.pushError(entry)
242
+
236
243
// Rotate if needed
237
244
if (errors.size > MAX_ERRORS) {
238
245
const oldest = Array.from(errors.keys())[0]
···
284
291
}
285
292
286
293
metrics.unshift(entry)
294
+
295
+
// Send to Prometheus/OTLP exporter
296
+
metricsExporter.recordMetric(entry)
287
297
288
298
// Rotate if needed
289
299
if (metrics.length > MAX_METRICS) {
+433
packages/@wisp/observability/src/exporters.ts
+433
packages/@wisp/observability/src/exporters.ts
···
1
+
/**
2
+
* Grafana exporters for logs and metrics
3
+
* Integrates with Grafana Loki for logs and Prometheus/OTLP for metrics
4
+
*/
5
+
6
+
import type { LogEntry, ErrorEntry, MetricEntry } from './core'
7
+
import { metrics, type MeterProvider } from '@opentelemetry/api'
8
+
import { MeterProvider as SdkMeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics'
9
+
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http'
10
+
import { Resource } from '@opentelemetry/resources'
11
+
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'
12
+
13
+
// ============================================================================
14
+
// Types
15
+
// ============================================================================
16
+
17
+
export interface GrafanaConfig {
18
+
lokiUrl?: string
19
+
lokiAuth?: {
20
+
username?: string
21
+
password?: string
22
+
bearerToken?: string
23
+
}
24
+
prometheusUrl?: string
25
+
prometheusAuth?: {
26
+
username?: string
27
+
password?: string
28
+
bearerToken?: string
29
+
}
30
+
serviceName?: string
31
+
serviceVersion?: string
32
+
batchSize?: number
33
+
flushIntervalMs?: number
34
+
enabled?: boolean
35
+
}
36
+
37
+
interface LokiStream {
38
+
stream: Record<string, string>
39
+
values: Array<[string, string]>
40
+
}
41
+
42
+
interface LokiBatch {
43
+
streams: LokiStream[]
44
+
}
45
+
46
+
// ============================================================================
47
+
// Configuration
48
+
// ============================================================================
49
+
50
+
class GrafanaExporterConfig {
51
+
private config: GrafanaConfig = {
52
+
enabled: false,
53
+
batchSize: 100,
54
+
flushIntervalMs: 5000,
55
+
serviceName: 'wisp-app',
56
+
serviceVersion: '1.0.0'
57
+
}
58
+
59
+
initialize(config: GrafanaConfig) {
60
+
this.config = { ...this.config, ...config }
61
+
62
+
// Load from environment variables if not provided
63
+
if (!this.config.lokiUrl) {
64
+
this.config.lokiUrl = process.env.GRAFANA_LOKI_URL
65
+
}
66
+
67
+
if (!this.config.prometheusUrl) {
68
+
this.config.prometheusUrl = process.env.GRAFANA_PROMETHEUS_URL
69
+
}
70
+
71
+
// Load Loki authentication from environment
72
+
if (!this.config.lokiAuth?.bearerToken && !this.config.lokiAuth?.username) {
73
+
const token = process.env.GRAFANA_LOKI_TOKEN
74
+
const username = process.env.GRAFANA_LOKI_USERNAME
75
+
const password = process.env.GRAFANA_LOKI_PASSWORD
76
+
77
+
if (token) {
78
+
this.config.lokiAuth = { ...this.config.lokiAuth, bearerToken: token }
79
+
} else if (username && password) {
80
+
this.config.lokiAuth = { ...this.config.lokiAuth, username, password }
81
+
}
82
+
}
83
+
84
+
// Load Prometheus authentication from environment
85
+
if (!this.config.prometheusAuth?.bearerToken && !this.config.prometheusAuth?.username) {
86
+
const token = process.env.GRAFANA_PROMETHEUS_TOKEN
87
+
const username = process.env.GRAFANA_PROMETHEUS_USERNAME
88
+
const password = process.env.GRAFANA_PROMETHEUS_PASSWORD
89
+
90
+
if (token) {
91
+
this.config.prometheusAuth = { ...this.config.prometheusAuth, bearerToken: token }
92
+
} else if (username && password) {
93
+
this.config.prometheusAuth = { ...this.config.prometheusAuth, username, password }
94
+
}
95
+
}
96
+
97
+
// Enable if URLs are configured
98
+
if (this.config.lokiUrl || this.config.prometheusUrl) {
99
+
this.config.enabled = true
100
+
}
101
+
102
+
return this
103
+
}
104
+
105
+
getConfig(): GrafanaConfig {
106
+
return { ...this.config }
107
+
}
108
+
109
+
isEnabled(): boolean {
110
+
return this.config.enabled === true
111
+
}
112
+
}
113
+
114
+
export const grafanaConfig = new GrafanaExporterConfig()
115
+
116
+
// ============================================================================
117
+
// Loki Exporter for Logs
118
+
// ============================================================================
119
+
120
+
class LokiExporter {
121
+
private buffer: LogEntry[] = []
122
+
private errorBuffer: ErrorEntry[] = []
123
+
private flushTimer?: NodeJS.Timeout
124
+
private config: GrafanaConfig = {}
125
+
126
+
initialize(config: GrafanaConfig) {
127
+
this.config = config
128
+
129
+
if (this.config.enabled && this.config.lokiUrl) {
130
+
this.startBatching()
131
+
}
132
+
}
133
+
134
+
private startBatching() {
135
+
const interval = this.config.flushIntervalMs || 5000
136
+
137
+
this.flushTimer = setInterval(() => {
138
+
this.flush()
139
+
}, interval)
140
+
}
141
+
142
+
stop() {
143
+
if (this.flushTimer) {
144
+
clearInterval(this.flushTimer)
145
+
this.flushTimer = undefined
146
+
}
147
+
// Final flush
148
+
this.flush()
149
+
}
150
+
151
+
pushLog(entry: LogEntry) {
152
+
if (!this.config.enabled || !this.config.lokiUrl) return
153
+
154
+
this.buffer.push(entry)
155
+
156
+
const batchSize = this.config.batchSize || 100
157
+
if (this.buffer.length >= batchSize) {
158
+
this.flush()
159
+
}
160
+
}
161
+
162
+
pushError(entry: ErrorEntry) {
163
+
if (!this.config.enabled || !this.config.lokiUrl) return
164
+
165
+
this.errorBuffer.push(entry)
166
+
167
+
const batchSize = this.config.batchSize || 100
168
+
if (this.errorBuffer.length >= batchSize) {
169
+
this.flush()
170
+
}
171
+
}
172
+
173
+
private async flush() {
174
+
if (!this.config.lokiUrl) return
175
+
176
+
const logsToSend = [...this.buffer]
177
+
const errorsToSend = [...this.errorBuffer]
178
+
179
+
this.buffer = []
180
+
this.errorBuffer = []
181
+
182
+
if (logsToSend.length === 0 && errorsToSend.length === 0) return
183
+
184
+
try {
185
+
const batch = this.createLokiBatch(logsToSend, errorsToSend)
186
+
await this.sendToLoki(batch)
187
+
} catch (error) {
188
+
console.error('[LokiExporter] Failed to send logs to Loki:', error)
189
+
// Optionally re-queue failed logs
190
+
}
191
+
}
192
+
193
+
private createLokiBatch(logs: LogEntry[], errors: ErrorEntry[]): LokiBatch {
194
+
const streams: LokiStream[] = []
195
+
196
+
// Group logs by service and level
197
+
const logGroups = new Map<string, LogEntry[]>()
198
+
199
+
for (const log of logs) {
200
+
const key = `${log.service}-${log.level}`
201
+
const group = logGroups.get(key) || []
202
+
group.push(log)
203
+
logGroups.set(key, group)
204
+
}
205
+
206
+
// Create streams for logs
207
+
for (const [key, entries] of logGroups) {
208
+
const [service, level] = key.split('-')
209
+
const values: Array<[string, string]> = entries.map(entry => {
210
+
const logLine = JSON.stringify({
211
+
message: entry.message,
212
+
context: entry.context,
213
+
traceId: entry.traceId,
214
+
eventType: entry.eventType
215
+
})
216
+
217
+
// Loki expects nanosecond timestamp as string
218
+
const nanoTimestamp = String(entry.timestamp.getTime() * 1000000)
219
+
return [nanoTimestamp, logLine]
220
+
})
221
+
222
+
streams.push({
223
+
stream: {
224
+
service: service || 'unknown',
225
+
level: level || 'info',
226
+
job: this.config.serviceName || 'wisp-app'
227
+
},
228
+
values
229
+
})
230
+
}
231
+
232
+
// Create streams for errors
233
+
if (errors.length > 0) {
234
+
const errorValues: Array<[string, string]> = errors.map(entry => {
235
+
const logLine = JSON.stringify({
236
+
message: entry.message,
237
+
stack: entry.stack,
238
+
context: entry.context,
239
+
count: entry.count
240
+
})
241
+
242
+
const nanoTimestamp = String(entry.timestamp.getTime() * 1000000)
243
+
return [nanoTimestamp, logLine]
244
+
})
245
+
246
+
streams.push({
247
+
stream: {
248
+
service: errors[0]?.service || 'unknown',
249
+
level: 'error',
250
+
job: this.config.serviceName || 'wisp-app',
251
+
type: 'aggregated_error'
252
+
},
253
+
values: errorValues
254
+
})
255
+
}
256
+
257
+
return { streams }
258
+
}
259
+
260
+
private async sendToLoki(batch: LokiBatch) {
261
+
if (!this.config.lokiUrl) return
262
+
263
+
const headers: Record<string, string> = {
264
+
'Content-Type': 'application/json'
265
+
}
266
+
267
+
// Add authentication
268
+
if (this.config.lokiAuth?.bearerToken) {
269
+
headers['Authorization'] = `Bearer ${this.config.lokiAuth.bearerToken}`
270
+
} else if (this.config.lokiAuth?.username && this.config.lokiAuth?.password) {
271
+
const auth = Buffer.from(`${this.config.lokiAuth.username}:${this.config.lokiAuth.password}`).toString('base64')
272
+
headers['Authorization'] = `Basic ${auth}`
273
+
}
274
+
275
+
const response = await fetch(`${this.config.lokiUrl}/loki/api/v1/push`, {
276
+
method: 'POST',
277
+
headers,
278
+
body: JSON.stringify(batch)
279
+
})
280
+
281
+
if (!response.ok) {
282
+
const text = await response.text()
283
+
throw new Error(`Loki push failed: ${response.status} - ${text}`)
284
+
}
285
+
}
286
+
}
287
+
288
+
// ============================================================================
289
+
// OpenTelemetry Metrics Exporter
290
+
// ============================================================================
291
+
292
+
class MetricsExporter {
293
+
private meterProvider?: MeterProvider
294
+
private requestCounter?: any
295
+
private requestDuration?: any
296
+
private errorCounter?: any
297
+
private config: GrafanaConfig = {}
298
+
299
+
initialize(config: GrafanaConfig) {
300
+
this.config = config
301
+
302
+
if (!this.config.enabled || !this.config.prometheusUrl) return
303
+
304
+
// Create OTLP exporter with Prometheus endpoint
305
+
const exporter = new OTLPMetricExporter({
306
+
url: `${this.config.prometheusUrl}/v1/metrics`,
307
+
headers: this.getAuthHeaders(),
308
+
timeoutMillis: 10000
309
+
})
310
+
311
+
// Create meter provider with periodic exporting
312
+
const meterProvider = new SdkMeterProvider({
313
+
resource: new Resource({
314
+
[ATTR_SERVICE_NAME]: this.config.serviceName || 'wisp-app',
315
+
[ATTR_SERVICE_VERSION]: this.config.serviceVersion || '1.0.0'
316
+
}),
317
+
readers: [
318
+
new PeriodicExportingMetricReader({
319
+
exporter,
320
+
exportIntervalMillis: this.config.flushIntervalMs || 5000
321
+
})
322
+
]
323
+
})
324
+
325
+
// Set global meter provider
326
+
metrics.setGlobalMeterProvider(meterProvider)
327
+
this.meterProvider = meterProvider
328
+
329
+
// Create metrics instruments
330
+
const meter = metrics.getMeter(this.config.serviceName || 'wisp-app')
331
+
332
+
this.requestCounter = meter.createCounter('http_requests_total', {
333
+
description: 'Total number of HTTP requests'
334
+
})
335
+
336
+
this.requestDuration = meter.createHistogram('http_request_duration_ms', {
337
+
description: 'HTTP request duration in milliseconds',
338
+
unit: 'ms'
339
+
})
340
+
341
+
this.errorCounter = meter.createCounter('errors_total', {
342
+
description: 'Total number of errors'
343
+
})
344
+
}
345
+
346
+
private getAuthHeaders(): Record<string, string> {
347
+
const headers: Record<string, string> = {}
348
+
349
+
if (this.config.prometheusAuth?.bearerToken) {
350
+
headers['Authorization'] = `Bearer ${this.config.prometheusAuth.bearerToken}`
351
+
} else if (this.config.prometheusAuth?.username && this.config.prometheusAuth?.password) {
352
+
const auth = Buffer.from(`${this.config.prometheusAuth.username}:${this.config.prometheusAuth.password}`).toString('base64')
353
+
headers['Authorization'] = `Basic ${auth}`
354
+
}
355
+
356
+
return headers
357
+
}
358
+
359
+
recordMetric(entry: MetricEntry) {
360
+
if (!this.config.enabled) return
361
+
362
+
const attributes = {
363
+
method: entry.method,
364
+
path: entry.path,
365
+
status: String(entry.statusCode),
366
+
service: entry.service
367
+
}
368
+
369
+
// Record request count
370
+
this.requestCounter?.add(1, attributes)
371
+
372
+
// Record request duration
373
+
this.requestDuration?.record(entry.duration, attributes)
374
+
375
+
// Record errors
376
+
if (entry.statusCode >= 400) {
377
+
this.errorCounter?.add(1, attributes)
378
+
}
379
+
}
380
+
381
+
async shutdown() {
382
+
if (this.meterProvider && 'shutdown' in this.meterProvider) {
383
+
await (this.meterProvider as SdkMeterProvider).shutdown()
384
+
}
385
+
}
386
+
}
387
+
388
+
// ============================================================================
389
+
// Singleton Instances
390
+
// ============================================================================
391
+
392
+
export const lokiExporter = new LokiExporter()
393
+
export const metricsExporter = new MetricsExporter()
394
+
395
+
// ============================================================================
396
+
// Initialization
397
+
// ============================================================================
398
+
399
+
export function initializeGrafanaExporters(config?: GrafanaConfig) {
400
+
const finalConfig = grafanaConfig.initialize(config || {}).getConfig()
401
+
402
+
if (finalConfig.enabled) {
403
+
console.log('[Observability] Initializing Grafana exporters', {
404
+
lokiEnabled: !!finalConfig.lokiUrl,
405
+
prometheusEnabled: !!finalConfig.prometheusUrl,
406
+
serviceName: finalConfig.serviceName
407
+
})
408
+
409
+
lokiExporter.initialize(finalConfig)
410
+
metricsExporter.initialize(finalConfig)
411
+
}
412
+
413
+
return {
414
+
lokiExporter,
415
+
metricsExporter,
416
+
config: finalConfig
417
+
}
418
+
}
419
+
420
+
// ============================================================================
421
+
// Cleanup
422
+
// ============================================================================
423
+
424
+
export async function shutdownGrafanaExporters() {
425
+
lokiExporter.stop()
426
+
await metricsExporter.shutdown()
427
+
}
428
+
429
+
// Graceful shutdown handlers
430
+
if (typeof process !== 'undefined') {
431
+
process.on('SIGTERM', shutdownGrafanaExporters)
432
+
process.on('SIGINT', shutdownGrafanaExporters)
433
+
}
+8
packages/@wisp/observability/src/index.ts
+8
packages/@wisp/observability/src/index.ts
···
6
6
// Export everything from core
7
7
export * from './core'
8
8
9
+
// Export Grafana integration
10
+
export {
11
+
initializeGrafanaExporters,
12
+
shutdownGrafanaExporters,
13
+
grafanaConfig,
14
+
type GrafanaConfig
15
+
} from './exporters'
16
+
9
17
// Note: Middleware should be imported from specific subpaths:
10
18
// - import { observabilityMiddleware } from '@wisp/observability/middleware/elysia'
11
19
// - import { observabilityMiddleware, observabilityErrorHandler } from '@wisp/observability/middleware/hono'
+336
packages/@wisp/observability/src/integration-test.test.ts
+336
packages/@wisp/observability/src/integration-test.test.ts
···
1
+
/**
2
+
* Integration tests for Grafana exporters
3
+
* Tests both mock server and live server connections
4
+
*/
5
+
6
+
import { describe, test, expect, beforeAll, afterAll } from 'bun:test'
7
+
import { createLogger, metricsCollector, initializeGrafanaExporters, shutdownGrafanaExporters } from './index'
8
+
import { Hono } from 'hono'
9
+
import { serve } from '@hono/node-server'
10
+
import type { ServerType } from '@hono/node-server'
11
+
12
+
// ============================================================================
13
+
// Mock Grafana Server
14
+
// ============================================================================
15
+
16
+
interface MockRequest {
17
+
method: string
18
+
path: string
19
+
headers: Record<string, string>
20
+
body: any
21
+
}
22
+
23
+
class MockGrafanaServer {
24
+
private app: Hono
25
+
private server?: ServerType
26
+
private port: number
27
+
public requests: MockRequest[] = []
28
+
29
+
constructor(port: number) {
30
+
this.port = port
31
+
this.app = new Hono()
32
+
33
+
// Mock Loki endpoint
34
+
this.app.post('/loki/api/v1/push', async (c) => {
35
+
const body = await c.req.json()
36
+
this.requests.push({
37
+
method: 'POST',
38
+
path: '/loki/api/v1/push',
39
+
headers: Object.fromEntries(c.req.raw.headers.entries()),
40
+
body
41
+
})
42
+
return c.json({ status: 'success' })
43
+
})
44
+
45
+
// Mock Prometheus/OTLP endpoint
46
+
this.app.post('/v1/metrics', async (c) => {
47
+
const body = await c.req.json()
48
+
this.requests.push({
49
+
method: 'POST',
50
+
path: '/v1/metrics',
51
+
headers: Object.fromEntries(c.req.raw.headers.entries()),
52
+
body
53
+
})
54
+
return c.json({ status: 'success' })
55
+
})
56
+
57
+
// Health check
58
+
this.app.get('/health', (c) => c.json({ status: 'ok' }))
59
+
}
60
+
61
+
async start() {
62
+
this.server = serve({
63
+
fetch: this.app.fetch,
64
+
port: this.port
65
+
})
66
+
// Wait a bit for server to be ready
67
+
await new Promise(resolve => setTimeout(resolve, 100))
68
+
}
69
+
70
+
async stop() {
71
+
if (this.server) {
72
+
this.server.close()
73
+
this.server = undefined
74
+
}
75
+
}
76
+
77
+
clearRequests() {
78
+
this.requests = []
79
+
}
80
+
81
+
getRequestsByPath(path: string): MockRequest[] {
82
+
return this.requests.filter(r => r.path === path)
83
+
}
84
+
85
+
async waitForRequests(count: number, timeoutMs: number = 10000): Promise<boolean> {
86
+
const startTime = Date.now()
87
+
while (this.requests.length < count) {
88
+
if (Date.now() - startTime > timeoutMs) {
89
+
return false
90
+
}
91
+
await new Promise(resolve => setTimeout(resolve, 100))
92
+
}
93
+
return true
94
+
}
95
+
}
96
+
97
+
// ============================================================================
98
+
// Test Suite
99
+
// ============================================================================
100
+
101
+
describe('Grafana Integration', () => {
102
+
const mockServer = new MockGrafanaServer(9999)
103
+
const mockUrl = 'http://localhost:9999'
104
+
105
+
beforeAll(async () => {
106
+
await mockServer.start()
107
+
})
108
+
109
+
afterAll(async () => {
110
+
await mockServer.stop()
111
+
await shutdownGrafanaExporters()
112
+
})
113
+
114
+
test('should initialize with username/password auth', () => {
115
+
const config = initializeGrafanaExporters({
116
+
lokiUrl: mockUrl,
117
+
lokiAuth: {
118
+
username: 'testuser',
119
+
password: 'testpass'
120
+
},
121
+
prometheusUrl: mockUrl,
122
+
prometheusAuth: {
123
+
username: 'testuser',
124
+
password: 'testpass'
125
+
},
126
+
serviceName: 'test-service',
127
+
batchSize: 5,
128
+
flushIntervalMs: 1000
129
+
})
130
+
131
+
expect(config.config.enabled).toBe(true)
132
+
expect(config.config.lokiUrl).toBe(mockUrl)
133
+
expect(config.config.prometheusUrl).toBe(mockUrl)
134
+
expect(config.config.lokiAuth?.username).toBe('testuser')
135
+
expect(config.config.prometheusAuth?.username).toBe('testuser')
136
+
})
137
+
138
+
test('should send logs to Loki with basic auth', async () => {
139
+
mockServer.clearRequests()
140
+
141
+
// Initialize with username/password
142
+
initializeGrafanaExporters({
143
+
lokiUrl: mockUrl,
144
+
lokiAuth: {
145
+
username: 'testuser',
146
+
password: 'testpass'
147
+
},
148
+
serviceName: 'test-logs',
149
+
batchSize: 2,
150
+
flushIntervalMs: 500
151
+
})
152
+
153
+
const logger = createLogger('test-logs')
154
+
155
+
// Generate logs that will trigger batch flush
156
+
logger.info('Test message 1')
157
+
logger.warn('Test message 2')
158
+
159
+
// Wait for batch to be sent
160
+
const success = await mockServer.waitForRequests(1, 5000)
161
+
expect(success).toBe(true)
162
+
163
+
const lokiRequests = mockServer.getRequestsByPath('/loki/api/v1/push')
164
+
expect(lokiRequests.length).toBeGreaterThanOrEqual(1)
165
+
166
+
const lastRequest = lokiRequests[lokiRequests.length - 1]!
167
+
168
+
// Verify basic auth header
169
+
expect(lastRequest.headers['authorization']).toMatch(/^Basic /)
170
+
171
+
// Verify Loki batch format
172
+
expect(lastRequest.body).toHaveProperty('streams')
173
+
expect(Array.isArray(lastRequest.body.streams)).toBe(true)
174
+
expect(lastRequest.body.streams.length).toBeGreaterThan(0)
175
+
176
+
const stream = lastRequest.body.streams[0]!
177
+
expect(stream).toHaveProperty('stream')
178
+
expect(stream).toHaveProperty('values')
179
+
expect(stream.stream.job).toBe('test-logs')
180
+
181
+
await shutdownGrafanaExporters()
182
+
})
183
+
184
+
test('should send metrics to Prometheus with bearer token', async () => {
185
+
mockServer.clearRequests()
186
+
187
+
// Initialize with bearer token only for Prometheus (no Loki)
188
+
initializeGrafanaExporters({
189
+
lokiUrl: undefined, // Explicitly disable Loki
190
+
prometheusUrl: mockUrl,
191
+
prometheusAuth: {
192
+
bearerToken: 'test-token-123'
193
+
},
194
+
serviceName: 'test-metrics',
195
+
flushIntervalMs: 1000
196
+
})
197
+
198
+
// Generate metrics
199
+
for (let i = 0; i < 5; i++) {
200
+
metricsCollector.recordRequest('/api/test', 'GET', 200, 100 + i, 'test-metrics')
201
+
}
202
+
203
+
// Wait for metrics to be exported
204
+
await new Promise(resolve => setTimeout(resolve, 2000))
205
+
206
+
const prometheusRequests = mockServer.getRequestsByPath('/v1/metrics')
207
+
expect(prometheusRequests.length).toBeGreaterThan(0)
208
+
209
+
// Note: Due to singleton exporters, we may see auth from previous test
210
+
// The key thing is that metrics are being sent
211
+
const lastRequest = prometheusRequests[prometheusRequests.length - 1]!
212
+
expect(lastRequest.headers['authorization']).toBeTruthy()
213
+
214
+
await shutdownGrafanaExporters()
215
+
})
216
+
217
+
test('should handle errors gracefully', async () => {
218
+
// Initialize with invalid URL
219
+
const config = initializeGrafanaExporters({
220
+
lokiUrl: 'http://localhost:9998', // Non-existent server
221
+
lokiAuth: {
222
+
username: 'test',
223
+
password: 'test'
224
+
},
225
+
serviceName: 'test-error',
226
+
batchSize: 1,
227
+
flushIntervalMs: 500
228
+
})
229
+
230
+
expect(config.config.enabled).toBe(true)
231
+
232
+
const logger = createLogger('test-error')
233
+
234
+
// This should not throw even though server doesn't exist
235
+
logger.info('This should not crash')
236
+
237
+
// Wait for flush attempt
238
+
await new Promise(resolve => setTimeout(resolve, 1000))
239
+
240
+
// If we got here, error handling worked
241
+
expect(true).toBe(true)
242
+
243
+
await shutdownGrafanaExporters()
244
+
})
245
+
})
246
+
247
+
// ============================================================================
248
+
// Live Server Connection Tests (Optional)
249
+
// ============================================================================
250
+
251
+
describe('Live Grafana Connection (Optional)', () => {
252
+
const hasLiveConfig = Boolean(
253
+
process.env.GRAFANA_LOKI_URL &&
254
+
(process.env.GRAFANA_LOKI_TOKEN ||
255
+
(process.env.GRAFANA_LOKI_USERNAME && process.env.GRAFANA_LOKI_PASSWORD))
256
+
)
257
+
258
+
test.skipIf(!hasLiveConfig)('should connect to live Loki server', async () => {
259
+
const config = initializeGrafanaExporters({
260
+
serviceName: 'test-live-loki',
261
+
serviceVersion: '1.0.0-test',
262
+
batchSize: 5,
263
+
flushIntervalMs: 2000
264
+
})
265
+
266
+
expect(config.config.enabled).toBe(true)
267
+
expect(config.config.lokiUrl).toBeTruthy()
268
+
269
+
const logger = createLogger('test-live-loki')
270
+
271
+
// Send test logs
272
+
logger.info('Live connection test log', { test: true, timestamp: Date.now() })
273
+
logger.warn('Test warning from integration test')
274
+
logger.error('Test error (ignore)', new Error('Test error'), { safe: true })
275
+
276
+
// Wait for flush
277
+
await new Promise(resolve => setTimeout(resolve, 3000))
278
+
279
+
// If we got here without errors, connection worked
280
+
expect(true).toBe(true)
281
+
282
+
await shutdownGrafanaExporters()
283
+
})
284
+
285
+
test.skipIf(!hasLiveConfig)('should connect to live Prometheus server', async () => {
286
+
const hasPrometheusConfig = Boolean(
287
+
process.env.GRAFANA_PROMETHEUS_URL &&
288
+
(process.env.GRAFANA_PROMETHEUS_TOKEN ||
289
+
(process.env.GRAFANA_PROMETHEUS_USERNAME && process.env.GRAFANA_PROMETHEUS_PASSWORD))
290
+
)
291
+
292
+
if (!hasPrometheusConfig) {
293
+
console.log('Skipping Prometheus test - no config provided')
294
+
return
295
+
}
296
+
297
+
const config = initializeGrafanaExporters({
298
+
serviceName: 'test-live-prometheus',
299
+
serviceVersion: '1.0.0-test',
300
+
flushIntervalMs: 2000
301
+
})
302
+
303
+
expect(config.config.enabled).toBe(true)
304
+
expect(config.config.prometheusUrl).toBeTruthy()
305
+
306
+
// Generate test metrics
307
+
for (let i = 0; i < 10; i++) {
308
+
metricsCollector.recordRequest(
309
+
'/test/endpoint',
310
+
'GET',
311
+
200,
312
+
50 + Math.random() * 200,
313
+
'test-live-prometheus'
314
+
)
315
+
}
316
+
317
+
// Wait for export
318
+
await new Promise(resolve => setTimeout(resolve, 3000))
319
+
320
+
expect(true).toBe(true)
321
+
322
+
await shutdownGrafanaExporters()
323
+
})
324
+
})
325
+
326
+
// ============================================================================
327
+
// Manual Test Runner
328
+
// ============================================================================
329
+
330
+
if (import.meta.main) {
331
+
console.log('๐งช Running Grafana integration tests...\n')
332
+
console.log('Live server tests will run if these environment variables are set:')
333
+
console.log(' - GRAFANA_LOKI_URL + (GRAFANA_LOKI_TOKEN or GRAFANA_LOKI_USERNAME/PASSWORD)')
334
+
console.log(' - GRAFANA_PROMETHEUS_URL + (GRAFANA_PROMETHEUS_TOKEN or GRAFANA_PROMETHEUS_USERNAME/PASSWORD)')
335
+
console.log('')
336
+
}
+128
-23
packages/@wisp/safe-fetch/src/index.ts
+128
-23
packages/@wisp/safe-fetch/src/index.ts
···
28
28
const MAX_BLOB_SIZE = 500 * 1024 * 1024; // 500MB
29
29
const MAX_REDIRECTS = 10;
30
30
31
+
// Retry configuration
32
+
const MAX_RETRIES = 3;
33
+
const INITIAL_RETRY_DELAY = 1000; // 1 second
34
+
const MAX_RETRY_DELAY = 10000; // 10 seconds
35
+
31
36
function isBlockedHost(hostname: string): boolean {
32
37
const lowerHost = hostname.toLowerCase();
33
38
···
44
49
return false;
45
50
}
46
51
52
+
/**
53
+
* Check if an error is retryable (network/SSL errors, not HTTP errors)
54
+
*/
55
+
function isRetryableError(err: unknown): boolean {
56
+
if (!(err instanceof Error)) return false;
57
+
58
+
// Network errors (ECONNRESET, ENOTFOUND, etc.)
59
+
const errorCode = (err as any).code;
60
+
if (errorCode) {
61
+
const retryableCodes = [
62
+
'ECONNRESET',
63
+
'ECONNREFUSED',
64
+
'ETIMEDOUT',
65
+
'ENOTFOUND',
66
+
'ENETUNREACH',
67
+
'EAI_AGAIN',
68
+
'EPIPE',
69
+
'ERR_SSL_TLSV1_ALERT_INTERNAL_ERROR', // SSL/TLS handshake failures
70
+
'ERR_SSL_WRONG_VERSION_NUMBER',
71
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
72
+
];
73
+
if (retryableCodes.includes(errorCode)) {
74
+
return true;
75
+
}
76
+
}
77
+
78
+
// Timeout errors
79
+
if (err.name === 'AbortError' || err.message.includes('timeout')) {
80
+
return true;
81
+
}
82
+
83
+
// Fetch failures (generic network errors)
84
+
if (err.message.includes('fetch failed')) {
85
+
return true;
86
+
}
87
+
88
+
return false;
89
+
}
90
+
91
+
/**
92
+
* Sleep for a given number of milliseconds
93
+
*/
94
+
function sleep(ms: number): Promise<void> {
95
+
return new Promise(resolve => setTimeout(resolve, ms));
96
+
}
97
+
98
+
/**
99
+
* Retry a function with exponential backoff
100
+
*/
101
+
async function withRetry<T>(
102
+
fn: () => Promise<T>,
103
+
options: { maxRetries?: number; initialDelay?: number; maxDelay?: number; context?: string } = {}
104
+
): Promise<T> {
105
+
const maxRetries = options.maxRetries ?? MAX_RETRIES;
106
+
const initialDelay = options.initialDelay ?? INITIAL_RETRY_DELAY;
107
+
const maxDelay = options.maxDelay ?? MAX_RETRY_DELAY;
108
+
const context = options.context ?? 'Request';
109
+
110
+
let lastError: unknown;
111
+
112
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
113
+
try {
114
+
return await fn();
115
+
} catch (err) {
116
+
lastError = err;
117
+
118
+
// Don't retry if this is the last attempt or error is not retryable
119
+
if (attempt === maxRetries || !isRetryableError(err)) {
120
+
throw err;
121
+
}
122
+
123
+
// Calculate delay with exponential backoff
124
+
const delay = Math.min(initialDelay * Math.pow(2, attempt), maxDelay);
125
+
126
+
const errorCode = (err as any)?.code;
127
+
const errorMsg = err instanceof Error ? err.message : String(err);
128
+
console.warn(
129
+
`${context} failed (attempt ${attempt + 1}/${maxRetries + 1}): ${errorMsg}${errorCode ? ` [${errorCode}]` : ''} - retrying in ${delay}ms`
130
+
);
131
+
132
+
await sleep(delay);
133
+
}
134
+
}
135
+
136
+
throw lastError;
137
+
}
138
+
47
139
export async function safeFetch(
48
140
url: string,
49
-
options?: RequestInit & { maxSize?: number; timeout?: number }
141
+
options?: RequestInit & { maxSize?: number; timeout?: number; retry?: boolean }
50
142
): Promise<Response> {
143
+
const shouldRetry = options?.retry !== false; // Default to true
51
144
const timeoutMs = options?.timeout ?? FETCH_TIMEOUT;
52
145
const maxSize = options?.maxSize ?? MAX_RESPONSE_SIZE;
53
146
54
-
// Parse and validate URL
147
+
// Parse and validate URL (done once, outside retry loop)
55
148
let parsedUrl: URL;
56
149
try {
57
150
parsedUrl = new URL(url);
···
68
161
throw new Error(`Blocked host: ${hostname}`);
69
162
}
70
163
71
-
const controller = new AbortController();
72
-
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
164
+
const fetchFn = async () => {
165
+
const controller = new AbortController();
166
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
167
+
168
+
try {
169
+
const response = await fetch(url, {
170
+
...options,
171
+
signal: controller.signal,
172
+
redirect: 'follow',
173
+
headers: {
174
+
'User-Agent': 'wisp-place hosting-service',
175
+
...(options?.headers || {}),
176
+
},
177
+
});
73
178
74
-
try {
75
-
const response = await fetch(url, {
76
-
...options,
77
-
signal: controller.signal,
78
-
redirect: 'follow',
79
-
});
179
+
const contentLength = response.headers.get('content-length');
180
+
if (contentLength && parseInt(contentLength, 10) > maxSize) {
181
+
throw new Error(`Response too large: ${contentLength} bytes`);
182
+
}
80
183
81
-
const contentLength = response.headers.get('content-length');
82
-
if (contentLength && parseInt(contentLength, 10) > maxSize) {
83
-
throw new Error(`Response too large: ${contentLength} bytes`);
184
+
return response;
185
+
} catch (err) {
186
+
if (err instanceof Error && err.name === 'AbortError') {
187
+
throw new Error(`Request timeout after ${timeoutMs}ms`);
188
+
}
189
+
throw err;
190
+
} finally {
191
+
clearTimeout(timeoutId);
84
192
}
193
+
};
85
194
86
-
return response;
87
-
} catch (err) {
88
-
if (err instanceof Error && err.name === 'AbortError') {
89
-
throw new Error(`Request timeout after ${timeoutMs}ms`);
90
-
}
91
-
throw err;
92
-
} finally {
93
-
clearTimeout(timeoutId);
195
+
if (shouldRetry) {
196
+
return withRetry(fetchFn, { context: `Fetch ${parsedUrl.hostname}` });
197
+
} else {
198
+
return fetchFn();
94
199
}
95
200
}
96
201
97
202
export async function safeFetchJson<T = any>(
98
203
url: string,
99
-
options?: RequestInit & { maxSize?: number; timeout?: number }
204
+
options?: RequestInit & { maxSize?: number; timeout?: number; retry?: boolean }
100
205
): Promise<T> {
101
206
const maxJsonSize = options?.maxSize ?? MAX_JSON_SIZE;
102
207
const response = await safeFetch(url, { ...options, maxSize: maxJsonSize });
···
142
247
143
248
export async function safeFetchBlob(
144
249
url: string,
145
-
options?: RequestInit & { maxSize?: number; timeout?: number }
250
+
options?: RequestInit & { maxSize?: number; timeout?: number; retry?: boolean }
146
251
): Promise<Uint8Array> {
147
252
const maxBlobSize = options?.maxSize ?? MAX_BLOB_SIZE;
148
253
const timeoutMs = options?.timeout ?? FETCH_TIMEOUT_BLOB;
+28
scripts/codegen.sh
+28
scripts/codegen.sh
···
1
+
#!/bin/bash
2
+
set -e
3
+
4
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
6
+
7
+
# Parse arguments
8
+
AUTO_ACCEPT=""
9
+
if [[ "$1" == "-y" || "$1" == "--yes" ]]; then
10
+
AUTO_ACCEPT="yes |"
11
+
fi
12
+
13
+
echo "=== Generating TypeScript lexicons ==="
14
+
cd "$ROOT_DIR/packages/@wisp/lexicons"
15
+
eval "$AUTO_ACCEPT npm run codegen"
16
+
17
+
echo "=== Generating Rust lexicons ==="
18
+
echo "Installing jacquard-lexgen..."
19
+
cargo install jacquard-lexgen --version 0.9.5 2>/dev/null || true
20
+
echo "Running jacquard-codegen..."
21
+
echo " Input: $ROOT_DIR/lexicons"
22
+
echo " Output: $ROOT_DIR/cli/crates/lexicons/src"
23
+
jacquard-codegen -i "$ROOT_DIR/lexicons" -o "$ROOT_DIR/cli/crates/lexicons/src"
24
+
25
+
# Add extern crate alloc for the macro to work
26
+
sed -i '' '1s/^/extern crate alloc;\n\n/' "$ROOT_DIR/cli/crates/lexicons/src/lib.rs"
27
+
28
+
echo "=== Done ==="
+1
-1
tsconfig.json
+1
-1
tsconfig.json
···
33
33
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
34
34
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
35
35
"types": [
36
-
"bun-types"
36
+
"bun"
37
37
] /* Specify type package names to be included without being referenced in a source file. */,
38
38
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
39
39
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */