Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place

Compare changes

Choose any two refs to compare.

Changed files
+9619 -6249
.tangled
apps
binaries
cli
docs
lexicons
packages
scripts
+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 + .research/ 1 2 # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 3 .env 3 4 # dependencies
+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 -2
.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 - bun install @oven/bun-linux-aarch64 20 + find . -type f \( -name "bun.lock" -o -name "package-lock.json" \) -delete 21 21 bun install 22 22 23 23 - name: run all tests
+74
.wispignore.json
··· 1 + { 2 + "version": "1.0.0", 3 + "description": "Default ignore patterns for wisp.place uploads", 4 + "patterns": [ 5 + ".git", 6 + ".git/**", 7 + ".github", 8 + ".github/**", 9 + ".gitlab", 10 + ".gitlab/**", 11 + ".DS_Store", 12 + ".wisp.metadata.json", 13 + ".env", 14 + ".env.*", 15 + "node_modules", 16 + "node_modules/**", 17 + "Thumbs.db", 18 + "desktop.ini", 19 + "._*", 20 + ".Spotlight-V100", 21 + ".Spotlight-V100/**", 22 + ".Trashes", 23 + ".Trashes/**", 24 + ".fseventsd", 25 + ".fseventsd/**", 26 + ".cache", 27 + ".cache/**", 28 + ".temp", 29 + ".temp/**", 30 + ".tmp", 31 + ".tmp/**", 32 + "__pycache__", 33 + "__pycache__/**", 34 + "*.pyc", 35 + ".venv", 36 + ".venv/**", 37 + "venv", 38 + "venv/**", 39 + "env", 40 + "env/**", 41 + "*.swp", 42 + "*.swo", 43 + "*~", 44 + ".tangled", 45 + ".tangled/**" 46 + ], 47 + "comments": { 48 + ".git": "Version control - thousands of files", 49 + ".github": "GitHub workflows and configuration", 50 + ".gitlab": "GitLab CI/CD configuration", 51 + ".DS_Store": "macOS metadata - can leak info", 52 + ".wisp.metadata.json": "Wisp internal metadata", 53 + ".env": "Environment variables with secrets", 54 + "node_modules": "Dependency folder - can be 100,000+ files", 55 + "Thumbs.db": "Windows thumbnail cache", 56 + "desktop.ini": "Windows folder config", 57 + "._*": "macOS resource fork files", 58 + ".Spotlight-V100": "macOS Spotlight index", 59 + ".Trashes": "macOS trash folder", 60 + ".fseventsd": "macOS filesystem events", 61 + ".cache": "Cache directories", 62 + ".temp": "Temporary directories", 63 + ".tmp": "Temporary directories", 64 + "__pycache__": "Python cache", 65 + "*.pyc": "Python compiled files", 66 + ".venv": "Python virtual environments", 67 + "venv": "Python virtual environments", 68 + "env": "Python virtual environments", 69 + "*.swp": "Vim swap files", 70 + "*.swo": "Vim swap files", 71 + "*~": "Editor backup files", 72 + ".tangled": "Tangled directory" 73 + } 74 + }
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 42 42 * Returns true if site is successfully cached, false otherwise 43 43 */ 44 44 export async function ensureSiteCached(did: string, rkey: string): Promise<boolean> { 45 - if (isCached(did, rkey)) { 45 + if (await isCached(did, rkey)) { 46 46 return true; 47 47 } 48 48
+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
··· 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
··· 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
··· 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
+20 -15
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 - "@wisp/lexicons": "workspace:*", 14 - "@wisp/constants": "workspace:*", 15 - "@wisp/observability": "workspace:*", 16 - "@wisp/atproto-utils": "workspace:*", 17 - "@wisp/database": "workspace:*", 18 - "@wisp/fs-utils": "workspace:*", 19 - "@atproto/api": "^0.17.3", 20 - "@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", 21 17 "@atproto/jwk-jose": "^0.1.11", 22 - "@atproto/lex-cli": "^0.9.5", 23 - "@atproto/oauth-client-node": "^0.3.9", 24 - "@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", 25 21 "@elysiajs/cors": "^1.4.0", 26 22 "@elysiajs/eden": "^1.4.3", 27 23 "@elysiajs/openapi": "^1.4.11", ··· 34 30 "@radix-ui/react-slot": "^1.2.3", 35 31 "@radix-ui/react-tabs": "^1.1.13", 36 32 "@tanstack/react-query": "^5.90.2", 33 + "@wisp/atproto-utils": "workspace:*", 34 + "@wisp/constants": "workspace:*", 35 + "@wisp/database": "workspace:*", 36 + "@wisp/fs-utils": "workspace:*", 37 + "@wisp/lexicons": "workspace:*", 38 + "@wisp/observability": "workspace:*", 37 39 "actor-typeahead": "^0.1.1", 38 - "atproto-ui": "^0.11.3", 40 + "atproto-ui": "^0.12.0", 41 + "bun-plugin-tailwind": "^0.1.2", 39 42 "class-variance-authority": "^0.7.1", 40 43 "clsx": "^2.1.1", 41 - "elysia": "latest", 44 + "elysia": "^1.4.18", 45 + "ignore": "^7.0.5", 42 46 "iron-session": "^8.0.4", 43 47 "lucide-react": "^0.546.0", 44 48 "multiformats": "^13.4.1", ··· 48 52 "tailwind-merge": "^3.3.1", 49 53 "tailwindcss": "4", 50 54 "tw-animate-css": "^1.4.0", 51 - "zlib": "^1.0.5", 52 - "bun-plugin-tailwind": "^0.1.2" 55 + "zlib": "^1.0.5" 53 56 }, 54 57 "devDependencies": { 58 + "@atproto-labs/handle-resolver": "^0.3.4", 59 + "@atproto/did": "^0.2.3", 55 60 "@types/react": "^19.2.2", 56 61 "@types/react-dom": "^19.2.1", 57 62 "bun-types": "latest",
+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
··· 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
··· 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
··· 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">&gt;_</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
··· 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
··· 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
··· 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
··· 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 + };
+127
apps/main-app/src/lib/ignore-patterns.ts
··· 1 + import ignore, { type Ignore } from 'ignore' 2 + import { readFileSync, existsSync } from 'fs' 3 + import { join } from 'path' 4 + 5 + interface IgnoreConfig { 6 + version: string 7 + description: string 8 + patterns: string[] 9 + } 10 + 11 + /** 12 + * Load default ignore patterns from the .wispignore.json file in the monorepo root 13 + */ 14 + function loadDefaultPatterns(): string[] { 15 + try { 16 + // Path to the default ignore patterns JSON file (monorepo root, 3 levels up from this file) 17 + const defaultJsonPath = join(__dirname, '../../../../.wispignore.json') 18 + 19 + if (!existsSync(defaultJsonPath)) { 20 + console.warn('โš ๏ธ Default .wispignore.json not found, using hardcoded patterns') 21 + return getHardcodedPatterns() 22 + } 23 + 24 + const contents = readFileSync(defaultJsonPath, 'utf-8') 25 + const config: IgnoreConfig = JSON.parse(contents) 26 + return config.patterns 27 + } catch (error) { 28 + console.error('Failed to load default ignore patterns:', error) 29 + return getHardcodedPatterns() 30 + } 31 + } 32 + 33 + /** 34 + * Hardcoded fallback patterns (same as in .wispignore.json) 35 + */ 36 + function getHardcodedPatterns(): string[] { 37 + return [ 38 + '.git', 39 + '.git/**', 40 + '.github', 41 + '.github/**', 42 + '.gitlab', 43 + '.gitlab/**', 44 + '.DS_Store', 45 + '.wisp.metadata.json', 46 + '.env', 47 + '.env.*', 48 + 'node_modules', 49 + 'node_modules/**', 50 + 'Thumbs.db', 51 + 'desktop.ini', 52 + '._*', 53 + '.Spotlight-V100', 54 + '.Spotlight-V100/**', 55 + '.Trashes', 56 + '.Trashes/**', 57 + '.fseventsd', 58 + '.fseventsd/**', 59 + '.cache', 60 + '.cache/**', 61 + '.temp', 62 + '.temp/**', 63 + '.tmp', 64 + '.tmp/**', 65 + '__pycache__', 66 + '__pycache__/**', 67 + '*.pyc', 68 + '.venv', 69 + '.venv/**', 70 + 'venv', 71 + 'venv/**', 72 + 'env', 73 + 'env/**', 74 + '*.swp', 75 + '*.swo', 76 + '*~', 77 + '.tangled', 78 + '.tangled/**', 79 + ] 80 + } 81 + 82 + /** 83 + * Load custom ignore patterns from a .wispignore file 84 + * @param wispignoreContent - Content of the .wispignore file (one pattern per line) 85 + */ 86 + function loadWispignorePatterns(wispignoreContent: string): string[] { 87 + return wispignoreContent 88 + .split('\n') 89 + .map(line => line.trim()) 90 + .filter(line => line && !line.startsWith('#')) // Skip empty lines and comments 91 + } 92 + 93 + /** 94 + * Create an ignore matcher 95 + * @param customPatterns - Optional custom patterns from a .wispignore file 96 + */ 97 + export function createIgnoreMatcher(customPatterns?: string[]): Ignore { 98 + const ig = ignore() 99 + 100 + // Add default patterns 101 + const defaultPatterns = loadDefaultPatterns() 102 + ig.add(defaultPatterns) 103 + 104 + // Add custom patterns if provided 105 + if (customPatterns && customPatterns.length > 0) { 106 + ig.add(customPatterns) 107 + console.log(`Loaded ${customPatterns.length} custom patterns from .wispignore`) 108 + } 109 + 110 + return ig 111 + } 112 + 113 + /** 114 + * Check if a file path should be ignored 115 + * @param matcher - The ignore matcher 116 + * @param filePath - The file path to check (relative to site root) 117 + */ 118 + export function shouldIgnore(matcher: Ignore, filePath: string): boolean { 119 + return matcher.ignores(filePath) 120 + } 121 + 122 + /** 123 + * Parse .wispignore content and return patterns 124 + */ 125 + export function parseWispignore(content: string): string[] { 126 + return loadWispignorePatterns(content) 127 + }
+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
··· 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 {
+31 -112
apps/main-app/src/routes/wisp.ts
··· 34 34 failUploadJob, 35 35 addJobListener 36 36 } from '../lib/upload-jobs' 37 + import { createIgnoreMatcher, shouldIgnore, parseWispignore } from '../lib/ignore-patterns' 38 + import type { Ignore } from 'ignore' 37 39 38 40 const logger = createLogger('main-app') 39 41 40 - function isValidSiteName(siteName: string): boolean { 42 + export function isValidSiteName(siteName: string): boolean { 41 43 if (!siteName || typeof siteName !== 'string') return false; 42 44 43 45 // Length check (AT Protocol rkey limit) ··· 145 147 } 146 148 } 147 149 150 + // Check for .wispignore file in uploaded files 151 + let customIgnorePatterns: string[] = []; 152 + const wispignoreFile = fileArray.find(f => f && f.name && f.name.endsWith('.wispignore')); 153 + if (wispignoreFile) { 154 + try { 155 + const content = await wispignoreFile.text(); 156 + customIgnorePatterns = parseWispignore(content); 157 + console.log(`Found .wispignore file with ${customIgnorePatterns.length} custom patterns`); 158 + } catch (err) { 159 + console.warn('Failed to parse .wispignore file:', err); 160 + } 161 + } 162 + 163 + // Create ignore matcher with default and custom patterns 164 + const ignoreMatcher = createIgnoreMatcher(customIgnorePatterns); 165 + 148 166 // Convert File objects to UploadedFile format 149 167 const uploadedFiles: UploadedFile[] = []; 150 168 const skippedFiles: Array<{ name: string; reason: string }> = []; ··· 165 183 continue; 166 184 } 167 185 168 - 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 + 169 190 updateJobProgress(jobId, { 170 191 filesProcessed: i + 1, 171 - currentFile: file.name 192 + currentFile: filePath 172 193 }); 173 194 174 - // Skip unwanted files and directories 175 - const normalizedPath = file.name.replace(/^[^\/]*\//, ''); 176 - const fileName = normalizedPath.split('/').pop() || ''; 177 - const pathParts = normalizedPath.split('/'); 178 - 179 - // .git directory (version control - thousands of files) 180 - if (normalizedPath.startsWith('.git/') || normalizedPath === '.git') { 181 - console.log(`Skipping .git file: ${file.name}`); 182 - skippedFiles.push({ 183 - name: file.name, 184 - reason: '.git directory excluded' 185 - }); 186 - continue; 187 - } 188 - 189 - // .DS_Store (macOS metadata - can leak info) 190 - if (fileName === '.DS_Store') { 191 - console.log(`Skipping .DS_Store file: ${file.name}`); 192 - skippedFiles.push({ 193 - name: file.name, 194 - reason: '.DS_Store file excluded' 195 - }); 196 - continue; 197 - } 198 - 199 - // .env files (environment variables with secrets) 200 - if (fileName.startsWith('.env')) { 201 - console.log(`Skipping .env file: ${file.name}`); 202 - skippedFiles.push({ 203 - name: file.name, 204 - reason: 'environment files excluded for security' 205 - }); 206 - continue; 207 - } 195 + // Skip files that match ignore patterns 196 + const normalizedPath = filePath.replace(/^[^\/]*\//, ''); 208 197 209 - // node_modules (dependency folder - can be 100,000+ files) 210 - if (pathParts.includes('node_modules')) { 211 - console.log(`Skipping node_modules file: ${file.name}`); 198 + if (shouldIgnore(ignoreMatcher, normalizedPath)) { 212 199 skippedFiles.push({ 213 - name: file.name, 214 - reason: 'node_modules excluded' 215 - }); 216 - continue; 217 - } 218 - 219 - // OS metadata files 220 - if (fileName === 'Thumbs.db' || fileName === 'desktop.ini' || fileName.startsWith('._')) { 221 - console.log(`Skipping OS metadata file: ${file.name}`); 222 - skippedFiles.push({ 223 - name: file.name, 224 - reason: 'OS metadata file excluded' 225 - }); 226 - continue; 227 - } 228 - 229 - // macOS system directories 230 - if (pathParts.includes('.Spotlight-V100') || pathParts.includes('.Trashes') || pathParts.includes('.fseventsd')) { 231 - console.log(`Skipping macOS system file: ${file.name}`); 232 - skippedFiles.push({ 233 - name: file.name, 234 - reason: 'macOS system directory excluded' 235 - }); 236 - continue; 237 - } 238 - 239 - // Cache and temp directories 240 - if (pathParts.some(part => part === '.cache' || part === '.temp' || part === '.tmp')) { 241 - console.log(`Skipping cache/temp file: ${file.name}`); 242 - skippedFiles.push({ 243 - name: file.name, 244 - reason: 'cache/temp directory excluded' 245 - }); 246 - continue; 247 - } 248 - 249 - // Python cache 250 - if (pathParts.includes('__pycache__') || fileName.endsWith('.pyc')) { 251 - console.log(`Skipping Python cache file: ${file.name}`); 252 - skippedFiles.push({ 253 - name: file.name, 254 - reason: 'Python cache excluded' 255 - }); 256 - continue; 257 - } 258 - 259 - // Python virtual environments 260 - if (pathParts.some(part => part === '.venv' || part === 'venv' || part === 'env')) { 261 - console.log(`Skipping Python venv file: ${file.name}`); 262 - skippedFiles.push({ 263 - name: file.name, 264 - reason: 'Python virtual environment excluded' 265 - }); 266 - continue; 267 - } 268 - 269 - // Editor swap files 270 - if (fileName.endsWith('.swp') || fileName.endsWith('.swo') || fileName.endsWith('~')) { 271 - console.log(`Skipping editor swap file: ${file.name}`); 272 - skippedFiles.push({ 273 - name: file.name, 274 - reason: 'editor swap file excluded' 200 + name: filePath, 201 + reason: 'matched ignore pattern' 275 202 }); 276 203 continue; 277 204 } ··· 280 207 const maxSize = MAX_FILE_SIZE; 281 208 if (file.size > maxSize) { 282 209 skippedFiles.push({ 283 - name: file.name, 210 + name: filePath, 284 211 reason: `file too large (${(file.size / 1024 / 1024).toFixed(2)}MB, max 100MB)` 285 212 }); 286 213 continue; ··· 313 240 // Text files: compress AND base64 encode 314 241 finalContent = Buffer.from(compressedContent.toString('base64'), 'binary'); 315 242 base64Encoded = true; 316 - const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1); 317 - console.log(`Compressing+base64 ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${finalContent.length} bytes`); 318 - logger.info(`Compressing+base64 ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%), base64: ${finalContent.length} bytes`); 319 243 } else { 320 244 // Audio files: just compress, no base64 321 245 finalContent = compressedContent; 322 - const compressionRatio = (compressedContent.length / originalContent.length * 100).toFixed(1); 323 - console.log(`Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%)`); 324 - logger.info(`Compressing ${file.name}: ${originalContent.length} -> ${compressedContent.length} bytes (${compressionRatio}%)`); 325 246 } 326 247 } else { 327 248 // Binary files: upload directly 328 249 finalContent = originalContent; 329 - console.log(`Uploading ${file.name} directly: ${originalContent.length} bytes (no compression)`); 330 - logger.info(`Uploading ${file.name} directly: ${originalContent.length} bytes (binary)`); 331 250 } 332 251 333 252 uploadedFiles.push({ 334 - name: file.name, 253 + name: filePath, 335 254 content: finalContent, 336 255 mimeType: originalMimeType, 337 256 size: finalContent.length,
+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
··· 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);
+544 -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", 84 + "ignore": "^7.0.5", 76 85 "iron-session": "^8.0.4", 77 86 "lucide-react": "^0.546.0", 78 87 "multiformats": "^13.4.1", ··· 85 94 "zlib": "^1.0.5", 86 95 }, 87 96 "devDependencies": { 97 + "@atproto-labs/handle-resolver": "^0.3.4", 98 + "@atproto/did": "^0.2.3", 88 99 "@types/react": "^19.2.2", 89 100 "@types/react-dom": "^19.2.1", 90 101 "bun-types": "latest", ··· 100 111 "@atproto/api": "^0.14.1", 101 112 "@wisp/lexicons": "workspace:*", 102 113 "multiformats": "^13.3.1", 114 + }, 115 + "devDependencies": { 116 + "@atproto/lexicon": "^0.5.2", 103 117 }, 104 118 }, 105 119 "packages/@wisp/constants": { ··· 130 144 }, 131 145 "devDependencies": { 132 146 "@atproto/lex-cli": "^0.9.5", 147 + "multiformats": "^13.4.1", 133 148 }, 134 149 }, 135 150 "packages/@wisp/observability": { 136 151 "name": "@wisp/observability", 137 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 + }, 138 165 "peerDependencies": { 139 - "hono": "^4.0.0", 166 + "hono": "^4.10.7", 140 167 }, 141 168 "optionalPeers": [ 142 169 "hono", ··· 148 175 }, 149 176 }, 150 177 "trustedDependencies": [ 151 - "core-js", 178 + "esbuild", 152 179 "cbor-extract", 153 180 "protobufjs", 181 + "core-js", 182 + "bun", 183 + "@parcel/watcher", 154 184 ], 155 185 "packages": { 156 186 "@atcute/atproto": ["@atcute/atproto@3.1.9", "", { "dependencies": { "@atcute/lexicons": "^1.2.2" } }, "sha512-DyWwHCTdR4hY2BPNbLXgVmm7lI+fceOwWbE4LXbGvbvVtSn+ejSVFaAv01Ra3kWDha0whsOmbJL8JP0QPpf1+w=="], 157 187 158 - "@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=="], 159 189 160 - "@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=="], 161 191 162 - "@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=="], 163 193 164 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=="], 165 195 166 - "@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=="], 167 197 168 - "@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=="], 169 199 170 - "@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=="], 171 201 172 - "@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=="], 173 203 174 204 "@atproto-labs/fetch": ["@atproto-labs/fetch@0.2.3", "", { "dependencies": { "@atproto-labs/pipe": "0.1.1" } }, "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw=="], 175 205 176 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=="], 177 207 178 - "@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=="], 179 209 180 - "@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=="], 181 211 182 - "@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=="], 183 213 184 214 "@atproto-labs/pipe": ["@atproto-labs/pipe@0.1.1", "", {}, "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg=="], 185 215 ··· 191 221 192 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=="], 193 223 194 - "@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=="], 195 225 196 - "@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=="], 197 227 198 - "@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=="], 199 229 200 230 "@atproto/identity": ["@atproto/identity@0.4.10", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4" } }, "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ=="], 201 231 ··· 205 235 206 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=="], 207 237 208 - "@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=="], 209 239 210 - "@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=="], 211 241 212 - "@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=="], 213 243 214 - "@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=="], 215 245 216 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=="], 217 247 218 - "@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=="], 219 249 220 - "@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=="], 221 251 222 - "@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=="], 223 253 224 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=="], 225 255 226 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=="], 227 257 228 - "@atproto/syntax": ["@atproto/syntax@0.4.1", "", {}, "sha512-CJdImtLAiFO+0z3BWTtxwk6aY5w4t8orHTMVJgkf++QRJWTxPbIFko/0hrkADB7n2EruDxDSeAgfUGehpH6ngw=="], 258 + "@atproto/syntax": ["@atproto/syntax@0.4.2", "", {}, "sha512-X9XSRPinBy/0VQ677j8VXlBsYSsUXaiqxWVpGGxJYsAhugdQRb0jqaVKJFtm6RskeNkV6y9xclSUi9UYG/COrA=="], 229 259 230 - "@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=="], 231 261 232 262 "@atproto/xrpc": ["@atproto/xrpc@0.7.6", "", { "dependencies": { "@atproto/lexicon": "^0.5.2", "zod": "^3.23.8" } }, "sha512-RvCf4j0JnKYWuz3QzsYCntJi3VuiAAybQsMIUw2wLWcHhchO9F7UaBZINLL2z0qc/cYWPv5NSwcVydMseoCZLA=="], 233 263 234 - "@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=="], 235 349 236 350 "@badrap/valita": ["@badrap/valita@0.4.6", "", {}, "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg=="], 237 351 ··· 251 365 252 366 "@elysiajs/cors": ["@elysiajs/cors@1.4.0", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-pb0SCzBfFbFSYA/U40HHO7R+YrcXBJXOWgL20eSViK33ol1e20ru2/KUaZYo5IMUn63yaTJI/bQERuQ+77ND8g=="], 253 367 254 - "@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=="], 255 369 256 370 "@elysiajs/openapi": ["@elysiajs/openapi@1.4.11", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-d75bMxYJpN6qSDi/z9L1S7SLk1S/8Px+cTb3W2lrYzU8uQ5E0kXdy1oOMJEfTyVsz3OA19NP9KNxE7ztSbLBLg=="], 257 371 258 - "@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=="], 259 373 260 - "@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=="], 261 375 262 376 "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.26.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-hj0sKNCQOOo2fgyII3clmJXP28VhgDfU5iy3GNHlWO76KG6N7x4D9ezH5lJtQTG+1J6MFDAJXC1qsI+W+LvZoA=="], 263 377 ··· 311 425 312 426 "@esbuild/win32-x64": ["@esbuild/win32-x64@0.26.0", "", { "os": "win32", "cpu": "x64" }, "sha512-WAckBKaVnmFqbEhbymrPK7M086DQMpL1XoRbpmN0iW8k5JSXjDRQBhcZNa0VweItknLq9eAeCL34jK7/CDcw7A=="], 313 427 314 - "@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=="], 315 429 316 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=="], 317 431 ··· 341 455 342 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=="], 343 457 344 - "@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=="], 345 459 346 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=="], 347 461 ··· 351 465 352 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=="], 353 467 354 - "@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=="], 355 469 356 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=="], 357 471 ··· 367 481 368 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=="], 369 483 370 - "@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=="], 371 485 372 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=="], 373 487 374 - "@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=="], 375 489 376 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=="], 377 491 378 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=="], 379 493 380 - "@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=="], 381 495 382 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=="], 383 497 384 - "@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=="], 385 499 386 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=="], 387 501 ··· 391 505 392 506 "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.38.0", "", {}, "sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg=="], 393 507 394 - "@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=="], 395 509 396 - "@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=="], 397 511 398 - "@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=="], 399 513 400 - "@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=="], 401 515 402 - "@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=="], 403 517 404 - "@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=="], 405 519 406 - "@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=="], 407 521 408 - "@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=="], 409 523 410 - "@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=="], 411 525 412 - "@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=="], 413 527 414 - "@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=="], 415 529 416 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=="], 417 531 ··· 515 629 516 630 "@sinclair/typebox": ["@sinclair/typebox@0.34.41", "", {}, "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g=="], 517 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 + 518 734 "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], 519 735 520 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=="], ··· 547 763 548 764 "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="], 549 765 550 - "@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=="], 551 767 552 - "@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=="], 553 769 554 - "@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=="], 555 771 556 772 "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], 557 773 558 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=="], 559 775 560 - "@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=="], 561 777 562 778 "@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="], 563 779 564 780 "@types/node": ["@types/node@22.19.1", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ=="], 565 781 566 - "@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=="], 567 783 568 - "@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=="], 569 785 570 786 "@types/shimmer": ["@types/shimmer@1.2.0", "", {}, "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg=="], 571 787 ··· 593 809 594 810 "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], 595 811 596 - "actor-typeahead": ["actor-typeahead@0.1.1", "", {}, "sha512-ilsBwzplKwMSBiO6Tg6RdaZ5xxqgXds5jCQuHV+ib9Aq3ja9g0T7u2Y1PmihotmS7l5RxhpGI/tPm3ljoRDRwg=="], 812 + "actor-typeahead": ["actor-typeahead@0.1.2", "", {}, "sha512-I97YqqNl7Kar0J/bIJvgY/KmHpssHcDElhfwVTLP7wRFlkxso2ZLBqiS2zol5A8UVUJbQK2JXYaqNpZXz8Uk2A=="], 597 813 598 814 "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], 599 815 ··· 605 821 606 822 "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], 607 823 608 - "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=="], 609 825 610 826 "await-lock": ["await-lock@2.2.2", "", {}, "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="], 611 827 ··· 613 829 614 830 "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], 615 831 616 - "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=="], 617 835 618 836 "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], 619 837 620 838 "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 621 839 622 - "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=="], 623 841 624 - "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=="], 625 843 626 844 "bun-plugin-tailwind": ["bun-plugin-tailwind@0.1.2", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="], 627 845 628 - "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=="], 629 847 630 848 "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], 631 849 ··· 661 879 662 880 "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], 663 881 664 - "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], 882 + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], 665 883 666 - "cookie-signature": ["cookie-signature@1.0.6", "", {}, "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="], 884 + "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], 667 885 668 - "core-js": ["core-js@3.46.0", "", {}, "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA=="], 886 + "core-js": ["core-js@3.47.0", "", {}, "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg=="], 669 887 670 - "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], 888 + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], 671 889 672 890 "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 673 891 ··· 683 901 684 902 "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], 685 903 686 - "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=="], 687 905 688 906 "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], 689 907 ··· 713 931 714 932 "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], 715 933 716 - "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=="], 717 935 718 - "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=="], 719 937 720 938 "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], 721 939 722 940 "fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="], 723 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=="], 943 + 724 944 "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], 725 945 726 - "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], 727 - 728 - "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=="], 729 947 730 948 "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 731 949 732 - "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=="], 733 951 734 952 "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], 735 953 ··· 753 971 754 972 "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 755 973 756 - "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], 757 - 758 974 "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 759 975 760 976 "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], 761 977 762 978 "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 763 979 764 - "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=="], 765 981 766 - "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=="], 767 983 768 984 "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], 769 985 770 986 "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 987 + 988 + "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], 771 989 772 990 "import-in-the-middle": ["import-in-the-middle@1.15.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^1.2.2", "module-details-from-path": "^1.0.3" } }, "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA=="], 773 991 ··· 895 1113 896 1114 "pino-std-serializers": ["pino-std-serializers@6.2.2", "", {}, "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA=="], 897 1115 898 - "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=="], 899 1117 900 - "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=="], 901 1119 902 1120 "postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="], 903 1121 904 - "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=="], 905 1123 906 1124 "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], 907 1125 ··· 913 1131 914 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=="], 915 1133 916 - "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=="], 917 1135 918 1136 "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], 919 1137 ··· 921 1139 922 1140 "rate-limiter-flexible": ["rate-limiter-flexible@2.4.2", "", {}, "sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw=="], 923 1141 924 - "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=="], 925 1143 926 - "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="], 1144 + "react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="], 927 1145 928 - "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=="], 929 1147 930 - "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=="], 931 1149 932 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=="], 933 1151 934 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=="], 935 1153 936 - "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=="], 937 1155 938 1156 "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], 939 1157 ··· 953 1171 954 1172 "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], 955 1173 956 - "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=="], 957 1175 958 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=="], 959 1177 ··· 975 1193 976 1194 "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], 977 1195 978 - "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=="], 979 1199 980 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=="], 981 1201 ··· 983 1203 984 1204 "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], 985 1205 1206 + "strnum": ["strnum@2.1.2", "", {}, "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ=="], 1207 + 986 1208 "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], 987 1209 988 1210 "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], 989 1211 990 1212 "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], 991 1213 992 - "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=="], 993 1215 994 1216 "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], 995 1217 ··· 997 1219 998 1220 "thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw=="], 999 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=="], 1225 + 1000 1226 "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], 1001 1227 1002 1228 "tlds": ["tlds@1.261.0", "", { "bin": { "tlds": "bin.js" } }, "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA=="], ··· 1011 1237 1012 1238 "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 1013 1239 1014 - "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=="], 1015 1241 1016 1242 "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], 1017 1243 ··· 1036 1262 "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "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-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], 1037 1263 1038 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=="], 1265 + 1266 + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], 1039 1267 1040 1268 "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], 1041 1269 ··· 1061 1289 1062 1290 "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 1063 1291 1064 - "@atproto-labs/fetch-node/ipaddr.js": ["ipaddr.js@2.2.0", "", {}, "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA=="], 1065 - 1066 - "@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=="], 1067 1293 1068 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=="], 1069 1295 1070 1296 "@atproto/api/@atproto/xrpc": ["@atproto/xrpc@0.6.12", "", { "dependencies": { "@atproto/lexicon": "^0.4.10", "zod": "^3.23.8" } }, "sha512-Ut3iISNLujlmY9Gu8sNU+SPDJDvqlVzWddU8qUr0Yae5oD4SguaUFjjhireMGhQ3M5E0KljQgDbTmnBo1kIZ3w=="], 1071 1297 1072 1298 "@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1073 - 1074 - "@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=="], 1075 1299 1076 1300 "@atproto/common/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1077 1301 ··· 1079 1303 1080 1304 "@atproto/lex-cbor/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1081 1305 1082 - "@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=="], 1083 - 1084 1306 "@atproto/lex-data/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1085 1307 1086 1308 "@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1087 1309 1088 - "@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=="], 1089 - 1090 1310 "@atproto/oauth-client/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1091 1311 1092 - "@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=="], 1093 1313 1094 1314 "@atproto/repo/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1095 1315 1096 - "@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=="], 1097 1317 1098 - "@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=="], 1099 1319 1100 1320 "@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1101 1321 1102 - "@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=="], 1103 1323 1104 - "@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=="], 1105 1325 1106 - "@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=="], 1107 1327 1108 1328 "@ipld/dag-cbor/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1109 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 + 1110 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=="], 1111 1459 1112 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=="], ··· 1121 1469 1122 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=="], 1123 1471 1124 - "@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=="], 1125 1473 1126 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=="], 1127 1475 ··· 1129 1477 1130 1478 "@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 1131 1479 1132 - "@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=="], 1133 1481 1134 - "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=="], 1135 1483 1136 - "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=="], 1137 1485 1138 - "fdir/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 1486 + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 1139 1487 1140 1488 "iron-session/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 1141 1489 ··· 1143 1491 1144 1492 "node-gyp-build-optional-packages/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 1145 1493 1146 - "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=="], 1147 1495 1148 1496 "require-in-the-middle/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 1149 1497 1150 - "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=="], 1151 1499 1152 1500 "send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 1153 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 + 1154 1508 "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 1155 1509 1156 - "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=="], 1157 1511 1158 1512 "tsx/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 1159 1513 ··· 1161 1515 1162 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=="], 1163 1517 1164 - "@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=="], 1165 1519 1166 - "@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=="], 1167 1521 1168 - "@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=="], 1169 1523 1170 - "@atproto/ws-client/@atproto/common/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 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=="], 1171 1525 1172 - "@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=="], 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=="], 1173 1527 1174 - "@atproto/xrpc-server/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 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=="], 1175 1529 1176 - "@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 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=="], 1177 1531 1178 - "@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=="], 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=="], 1179 1533 1180 - "@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=="], 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=="], 1181 1535 1182 - "@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=="], 1536 + "@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], 1183 1537 1184 - "@wisp/main-app/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 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=="], 1185 1539 1186 - "bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 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=="], 1187 1541 1188 - "protobufjs/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 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=="], 1545 + 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=="], 1547 + 1548 + "@opentelemetry/otlp-transformer/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], 1549 + 1550 + "@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], 1551 + 1552 + "@opentelemetry/sdk-metrics/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], 1553 + 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=="], 1555 + 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=="], 1557 + 1558 + "@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 1559 + 1560 + "@wisp/main-app/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1561 + 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=="], 1189 1563 1190 1564 "require-in-the-middle/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 1191 1565 1192 - "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=="], 1193 1567 1194 - "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=="], 1195 1569 1196 - "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=="], 1197 1571 1198 - "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=="], 1199 1573 1200 - "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=="], 1201 1575 1202 - "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=="], 1203 1577 1204 - "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=="], 1205 1579 1206 - "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=="], 1207 1581 1208 - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], 1582 + "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.1", "", { "os": "android", "cpu": "x64" }, "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ=="], 1209 1583 1210 - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], 1584 + "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ=="], 1211 1585 1212 - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], 1586 + "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ=="], 1213 1587 1214 - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], 1588 + "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg=="], 1215 1589 1216 - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], 1590 + "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ=="], 1217 1591 1218 - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], 1592 + "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.1", "", { "os": "linux", "cpu": "arm" }, "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA=="], 1219 1593 1220 - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], 1594 + "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q=="], 1221 1595 1222 - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], 1596 + "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw=="], 1223 1597 1224 - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], 1598 + "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg=="], 1225 1599 1226 - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], 1600 + "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA=="], 1227 1601 1228 - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], 1602 + "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ=="], 1229 1603 1230 - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], 1604 + "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.1", "", { "os": "linux", "cpu": "none" }, "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ=="], 1231 1605 1232 - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], 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=="], 1233 1607 1234 - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], 1608 + "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.1", "", { "os": "linux", "cpu": "x64" }, "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA=="], 1235 1609 1236 - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], 1610 + "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.1", "", { "os": "none", "cpu": "arm64" }, "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ=="], 1237 1611 1238 - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], 1612 + "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.1", "", { "os": "none", "cpu": "x64" }, "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg=="], 1239 1613 1240 - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], 1614 + "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g=="], 1241 1615 1242 - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], 1616 + "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg=="], 1243 1617 1244 - "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=="], 1618 + "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg=="], 1245 1619 1246 - "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=="], 1620 + "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA=="], 1247 1621 1248 - "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=="], 1622 + "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg=="], 1623 + 1624 + "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ=="], 1625 + 1626 + "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.1", "", { "os": "win32", "cpu": "x64" }, "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw=="], 1249 1627 1250 1628 "wisp-hosting-service/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1251 1629 1252 - "@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=="], 1253 1635 } 1254 1636 }
+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
+467 -494
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" ··· 73 54 ] 74 55 75 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" 61 + 62 + [[package]] 76 63 name = "android_system_properties" 77 64 version = "0.1.5" 78 65 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 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 ] ··· 360 356 ] 361 357 362 358 [[package]] 363 - name = "btree-range-map" 364 - version = "0.7.2" 359 + name = "bstr" 360 + version = "1.12.1" 365 361 source = "registry+https://github.com/rust-lang/crates.io-index" 366 - checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33" 362 + checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" 367 363 dependencies = [ 368 - "btree-slab", 369 - "cc-traits", 370 - "range-traits", 364 + "memchr", 371 365 "serde", 372 - "slab", 373 - ] 374 - 375 - [[package]] 376 - name = "btree-slab" 377 - version = "0.6.1" 378 - source = "registry+https://github.com/rust-lang/crates.io-index" 379 - checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c" 380 - dependencies = [ 381 - "cc-traits", 382 - "slab", 383 - "smallvec", 384 366 ] 385 367 386 368 [[package]] ··· 395 377 396 378 [[package]] 397 379 name = "bumpalo" 398 - version = "3.19.0" 380 + version = "3.19.1" 399 381 source = "registry+https://github.com/rust-lang/crates.io-index" 400 - checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" 382 + checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" 401 383 402 384 [[package]] 403 385 name = "byteorder" ··· 425 407 426 408 [[package]] 427 409 name = "cc" 428 - version = "1.2.47" 410 + version = "1.2.51" 429 411 source = "registry+https://github.com/rust-lang/crates.io-index" 430 - checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" 412 + checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" 431 413 dependencies = [ 432 414 "find-msvc-tools", 433 415 "shlex", 434 416 ] 435 417 436 418 [[package]] 437 - name = "cc-traits" 438 - version = "2.0.0" 439 - source = "registry+https://github.com/rust-lang/crates.io-index" 440 - checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5" 441 - dependencies = [ 442 - "slab", 443 - ] 444 - 445 - [[package]] 446 419 name = "cesu8" 447 420 version = "1.1.0" 448 421 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 471 444 "num-traits", 472 445 "serde", 473 446 "wasm-bindgen", 474 - "windows-link 0.2.1", 447 + "windows-link", 475 448 ] 476 449 477 450 [[package]] ··· 523 496 524 497 [[package]] 525 498 name = "clap" 526 - version = "4.5.53" 499 + version = "4.5.54" 527 500 source = "registry+https://github.com/rust-lang/crates.io-index" 528 - checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" 501 + checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" 529 502 dependencies = [ 530 503 "clap_builder", 531 504 "clap_derive", ··· 533 506 534 507 [[package]] 535 508 name = "clap_builder" 536 - version = "4.5.53" 509 + version = "4.5.54" 537 510 source = "registry+https://github.com/rust-lang/crates.io-index" 538 - checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" 511 + checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" 539 512 dependencies = [ 540 513 "anstream", 541 514 "anstyle", ··· 552 525 "heck 0.5.0", 553 526 "proc-macro2", 554 527 "quote", 555 - "syn 2.0.111", 528 + "syn 2.0.113", 556 529 ] 557 530 558 531 [[package]] ··· 562 535 checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" 563 536 564 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]] 565 547 name = "colorchoice" 566 548 version = "1.0.4" 567 549 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 579 561 580 562 [[package]] 581 563 name = "compression-codecs" 582 - version = "0.4.33" 564 + version = "0.4.35" 583 565 source = "registry+https://github.com/rust-lang/crates.io-index" 584 - checksum = "302266479cb963552d11bd042013a58ef1adc56768016c8b82b4199488f2d4ad" 566 + checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2" 585 567 dependencies = [ 586 568 "compression-core", 587 569 "flate2", ··· 593 575 version = "0.4.31" 594 576 source = "registry+https://github.com/rust-lang/crates.io-index" 595 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 + ] 596 591 597 592 [[package]] 598 593 name = "const-oid" ··· 670 665 ] 671 666 672 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" 672 + 673 + [[package]] 673 674 name = "crossbeam-channel" 674 675 version = "0.5.15" 675 676 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 679 680 ] 680 681 681 682 [[package]] 683 + name = "crossbeam-deque" 684 + version = "0.8.6" 685 + source = "registry+https://github.com/rust-lang/crates.io-index" 686 + checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 687 + dependencies = [ 688 + "crossbeam-epoch", 689 + "crossbeam-utils", 690 + ] 691 + 692 + [[package]] 693 + name = "crossbeam-epoch" 694 + version = "0.9.18" 695 + source = "registry+https://github.com/rust-lang/crates.io-index" 696 + checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 697 + dependencies = [ 698 + "crossbeam-utils", 699 + ] 700 + 701 + [[package]] 682 702 name = "crossbeam-utils" 683 703 version = "0.8.21" 684 704 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 733 753 "proc-macro2", 734 754 "quote", 735 755 "strsim", 736 - "syn 2.0.111", 756 + "syn 2.0.113", 737 757 ] 738 758 739 759 [[package]] ··· 744 764 dependencies = [ 745 765 "darling_core", 746 766 "quote", 747 - "syn 2.0.111", 767 + "syn 2.0.113", 748 768 ] 749 769 750 770 [[package]] ··· 784 804 checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" 785 805 dependencies = [ 786 806 "data-encoding", 787 - "syn 2.0.111", 807 + "syn 2.0.113", 788 808 ] 789 809 790 810 [[package]] ··· 815 835 checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" 816 836 dependencies = [ 817 837 "powerfmt", 818 - "serde_core", 819 838 ] 820 839 821 840 [[package]] ··· 835 854 dependencies = [ 836 855 "proc-macro2", 837 856 "quote", 838 - "syn 2.0.111", 857 + "syn 2.0.113", 839 858 "unicode-xid", 840 859 ] 841 860 ··· 886 905 dependencies = [ 887 906 "proc-macro2", 888 907 "quote", 889 - "syn 2.0.111", 908 + "syn 2.0.113", 890 909 ] 891 910 892 911 [[package]] 893 - name = "dyn-clone" 894 - version = "1.0.20" 895 - source = "registry+https://github.com/rust-lang/crates.io-index" 896 - checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" 897 - 898 - [[package]] 899 912 name = "ecdsa" 900 913 version = "0.16.9" 901 914 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 930 943 ] 931 944 932 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]] 933 964 name = "encoding_rs" 934 965 version = "0.8.35" 935 966 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 947 978 "heck 0.5.0", 948 979 "proc-macro2", 949 980 "quote", 950 - "syn 2.0.111", 981 + "syn 2.0.113", 951 982 ] 952 983 953 984 [[package]] ··· 996 1027 997 1028 [[package]] 998 1029 name = "find-msvc-tools" 999 - version = "0.1.5" 1030 + version = "0.1.6" 1000 1031 source = "registry+https://github.com/rust-lang/crates.io-index" 1001 - checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" 1032 + checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" 1002 1033 1003 1034 [[package]] 1004 1035 name = "flate2" ··· 1015 1046 version = "1.0.7" 1016 1047 source = "registry+https://github.com/rust-lang/crates.io-index" 1017 1048 checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 1049 + 1050 + [[package]] 1051 + name = "foldhash" 1052 + version = "0.1.5" 1053 + source = "registry+https://github.com/rust-lang/crates.io-index" 1054 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 1018 1055 1019 1056 [[package]] 1020 1057 name = "form_urlencoded" ··· 1117 1154 dependencies = [ 1118 1155 "proc-macro2", 1119 1156 "quote", 1120 - "syn 2.0.111", 1157 + "syn 2.0.113", 1121 1158 ] 1122 1159 1123 1160 [[package]] ··· 1152 1189 1153 1190 [[package]] 1154 1191 name = "generator" 1155 - version = "0.8.7" 1192 + version = "0.8.8" 1156 1193 source = "registry+https://github.com/rust-lang/crates.io-index" 1157 - checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" 1194 + checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" 1158 1195 dependencies = [ 1159 1196 "cc", 1160 1197 "cfg-if", 1161 1198 "libc", 1162 1199 "log", 1163 1200 "rustversion", 1164 - "windows", 1201 + "windows-link", 1202 + "windows-result", 1165 1203 ] 1166 1204 1167 1205 [[package]] ··· 1209 1247 checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" 1210 1248 1211 1249 [[package]] 1250 + name = "globset" 1251 + version = "0.4.18" 1252 + source = "registry+https://github.com/rust-lang/crates.io-index" 1253 + checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" 1254 + dependencies = [ 1255 + "aho-corasick", 1256 + "bstr", 1257 + "log", 1258 + "regex-automata", 1259 + "regex-syntax", 1260 + ] 1261 + 1262 + [[package]] 1212 1263 name = "gloo-storage" 1213 1264 version = "0.3.0" 1214 1265 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1258 1309 1259 1310 [[package]] 1260 1311 name = "h2" 1261 - version = "0.4.12" 1312 + version = "0.4.13" 1262 1313 source = "registry+https://github.com/rust-lang/crates.io-index" 1263 - checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" 1314 + checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" 1264 1315 dependencies = [ 1265 1316 "atomic-waker", 1266 1317 "bytes", ··· 1268 1319 "futures-core", 1269 1320 "futures-sink", 1270 1321 "http", 1271 - "indexmap 2.12.1", 1322 + "indexmap", 1272 1323 "slab", 1273 1324 "tokio", 1274 1325 "tokio-util", ··· 1287 1338 ] 1288 1339 1289 1340 [[package]] 1290 - name = "hashbrown" 1291 - version = "0.12.3" 1341 + name = "hash32" 1342 + version = "0.2.1" 1292 1343 source = "registry+https://github.com/rust-lang/crates.io-index" 1293 - checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 1344 + checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" 1345 + dependencies = [ 1346 + "byteorder", 1347 + ] 1294 1348 1295 1349 [[package]] 1296 1350 name = "hashbrown" ··· 1300 1354 1301 1355 [[package]] 1302 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" 1303 1368 version = "0.16.1" 1304 1369 source = "registry+https://github.com/rust-lang/crates.io-index" 1305 1370 checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 1371 + 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 + ] 1306 1385 1307 1386 [[package]] 1308 1387 name = "heck" ··· 1329 1408 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1330 1409 1331 1410 [[package]] 1332 - name = "hex_fmt" 1333 - version = "0.3.0" 1334 - source = "registry+https://github.com/rust-lang/crates.io-index" 1335 - checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" 1336 - 1337 - [[package]] 1338 1411 name = "hickory-proto" 1339 1412 version = "0.24.4" 1340 1413 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1399 1472 "markup5ever", 1400 1473 "proc-macro2", 1401 1474 "quote", 1402 - "syn 2.0.111", 1475 + "syn 2.0.113", 1403 1476 ] 1404 1477 1405 1478 [[package]] ··· 1495 1568 1496 1569 [[package]] 1497 1570 name = "hyper-util" 1498 - version = "0.1.18" 1571 + version = "0.1.19" 1499 1572 source = "registry+https://github.com/rust-lang/crates.io-index" 1500 - checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" 1573 + checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" 1501 1574 dependencies = [ 1502 1575 "base64 0.22.1", 1503 1576 "bytes", ··· 1531 1604 "js-sys", 1532 1605 "log", 1533 1606 "wasm-bindgen", 1534 - "windows-core 0.62.2", 1607 + "windows-core", 1535 1608 ] 1536 1609 1537 1610 [[package]] ··· 1591 1664 1592 1665 [[package]] 1593 1666 name = "icu_properties" 1594 - version = "2.1.1" 1667 + version = "2.1.2" 1595 1668 source = "registry+https://github.com/rust-lang/crates.io-index" 1596 - checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" 1669 + checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" 1597 1670 dependencies = [ 1598 1671 "icu_collections", 1599 1672 "icu_locale_core", ··· 1605 1678 1606 1679 [[package]] 1607 1680 name = "icu_properties_data" 1608 - version = "2.1.1" 1681 + version = "2.1.2" 1609 1682 source = "registry+https://github.com/rust-lang/crates.io-index" 1610 - checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" 1683 + checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" 1611 1684 1612 1685 [[package]] 1613 1686 name = "icu_provider" ··· 1652 1725 ] 1653 1726 1654 1727 [[package]] 1655 - name = "indexmap" 1656 - version = "1.9.3" 1728 + name = "ignore" 1729 + version = "0.4.25" 1657 1730 source = "registry+https://github.com/rust-lang/crates.io-index" 1658 - checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 1731 + checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" 1659 1732 dependencies = [ 1660 - "autocfg", 1661 - "hashbrown 0.12.3", 1662 - "serde", 1733 + "crossbeam-deque", 1734 + "globset", 1735 + "log", 1736 + "memchr", 1737 + "regex-automata", 1738 + "same-file", 1739 + "walkdir", 1740 + "winapi-util", 1663 1741 ] 1664 1742 1665 1743 [[package]] ··· 1670 1748 dependencies = [ 1671 1749 "equivalent", 1672 1750 "hashbrown 0.16.1", 1673 - "serde", 1674 - "serde_core", 1675 1751 ] 1676 1752 1677 1753 [[package]] 1678 - name = "indoc" 1679 - version = "2.0.7" 1754 + name = "indicatif" 1755 + version = "0.17.11" 1680 1756 source = "registry+https://github.com/rust-lang/crates.io-index" 1681 - checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" 1757 + checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" 1682 1758 dependencies = [ 1683 - "rustversion", 1759 + "console", 1760 + "number_prefix", 1761 + "portable-atomic", 1762 + "unicode-width 0.2.2", 1763 + "web-time", 1684 1764 ] 1685 1765 1686 1766 [[package]] ··· 1723 1803 1724 1804 [[package]] 1725 1805 name = "iri-string" 1726 - version = "0.7.9" 1806 + version = "0.7.10" 1727 1807 source = "registry+https://github.com/rust-lang/crates.io-index" 1728 - checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" 1808 + checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" 1729 1809 dependencies = [ 1730 1810 "memchr", 1731 1811 "serde", ··· 1745 1825 1746 1826 [[package]] 1747 1827 name = "itoa" 1748 - version = "1.0.15" 1828 + version = "1.0.17" 1749 1829 source = "registry+https://github.com/rust-lang/crates.io-index" 1750 - checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1830 + checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" 1751 1831 1752 1832 [[package]] 1753 1833 name = "jacquard" 1754 - version = "0.9.3" 1755 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1834 + version = "0.9.5" 1835 + source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b" 1756 1836 dependencies = [ 1757 1837 "bytes", 1758 1838 "getrandom 0.2.16", ··· 1781 1861 1782 1862 [[package]] 1783 1863 name = "jacquard-api" 1784 - version = "0.9.2" 1785 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1864 + version = "0.9.5" 1865 + source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b" 1786 1866 dependencies = [ 1787 1867 "bon", 1788 1868 "bytes", ··· 1792 1872 "miette", 1793 1873 "rustversion", 1794 1874 "serde", 1875 + "serde_bytes", 1795 1876 "serde_ipld_dagcbor", 1796 1877 "thiserror 2.0.17", 1797 1878 "unicode-segmentation", ··· 1799 1880 1800 1881 [[package]] 1801 1882 name = "jacquard-common" 1802 - version = "0.9.2" 1803 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1883 + version = "0.9.5" 1884 + source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b" 1804 1885 dependencies = [ 1805 1886 "base64 0.22.1", 1806 1887 "bon", 1807 1888 "bytes", 1808 1889 "chrono", 1809 1890 "ciborium", 1891 + "ciborium-io", 1810 1892 "cid", 1811 1893 "futures", 1812 1894 "getrandom 0.2.16", 1813 1895 "getrandom 0.3.4", 1896 + "hashbrown 0.15.5", 1814 1897 "http", 1815 1898 "ipld-core", 1816 1899 "k256", 1817 - "langtag", 1900 + "maitake-sync", 1818 1901 "miette", 1819 1902 "multibase", 1820 1903 "multihash", 1821 1904 "n0-future 0.1.3", 1822 1905 "ouroboros", 1906 + "oxilangtag", 1823 1907 "p256", 1908 + "postcard", 1824 1909 "rand 0.9.2", 1825 1910 "regex", 1911 + "regex-automata", 1826 1912 "regex-lite", 1827 1913 "reqwest", 1828 1914 "serde", 1915 + "serde_bytes", 1829 1916 "serde_html_form", 1830 1917 "serde_ipld_dagcbor", 1831 1918 "serde_json", 1832 1919 "signature", 1833 1920 "smol_str", 1921 + "spin 0.10.0", 1834 1922 "thiserror 2.0.17", 1835 1923 "tokio", 1836 1924 "tokio-tungstenite-wasm", ··· 1841 1929 1842 1930 [[package]] 1843 1931 name = "jacquard-derive" 1844 - version = "0.9.3" 1845 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1932 + version = "0.9.5" 1933 + source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b" 1846 1934 dependencies = [ 1847 1935 "heck 0.5.0", 1848 1936 "jacquard-lexicon", 1849 1937 "proc-macro2", 1850 1938 "quote", 1851 - "syn 2.0.111", 1939 + "syn 2.0.113", 1852 1940 ] 1853 1941 1854 1942 [[package]] 1855 1943 name = "jacquard-identity" 1856 - version = "0.9.2" 1857 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1944 + version = "0.9.5" 1945 + source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b" 1858 1946 dependencies = [ 1859 1947 "bon", 1860 1948 "bytes", ··· 1864 1952 "jacquard-common", 1865 1953 "jacquard-lexicon", 1866 1954 "miette", 1867 - "mini-moka", 1955 + "mini-moka-wasm", 1956 + "n0-future 0.1.3", 1868 1957 "percent-encoding", 1869 1958 "reqwest", 1870 1959 "serde", ··· 1879 1968 1880 1969 [[package]] 1881 1970 name = "jacquard-lexicon" 1882 - version = "0.9.2" 1883 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1971 + version = "0.9.5" 1972 + source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b" 1884 1973 dependencies = [ 1885 1974 "cid", 1886 1975 "dashmap", ··· 1898 1987 "serde_repr", 1899 1988 "serde_with", 1900 1989 "sha2", 1901 - "syn 2.0.111", 1990 + "syn 2.0.113", 1902 1991 "thiserror 2.0.17", 1903 1992 "unicode-segmentation", 1904 1993 ] 1905 1994 1906 1995 [[package]] 1907 1996 name = "jacquard-oauth" 1908 - version = "0.9.2" 1909 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 1997 + version = "0.9.6" 1998 + source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b" 1910 1999 dependencies = [ 1911 2000 "base64 0.22.1", 1912 2001 "bytes", ··· 1994 2083 1995 2084 [[package]] 1996 2085 name = "js-sys" 1997 - version = "0.3.82" 2086 + version = "0.3.83" 1998 2087 source = "registry+https://github.com/rust-lang/crates.io-index" 1999 - checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" 2088 + checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" 2000 2089 dependencies = [ 2001 2090 "once_cell", 2002 2091 "wasm-bindgen", ··· 2015 2104 ] 2016 2105 2017 2106 [[package]] 2018 - name = "langtag" 2019 - version = "0.4.0" 2020 - source = "registry+https://github.com/rust-lang/crates.io-index" 2021 - checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600" 2022 - dependencies = [ 2023 - "serde", 2024 - "static-regular-grammar", 2025 - "thiserror 1.0.69", 2026 - ] 2027 - 2028 - [[package]] 2029 2107 name = "lazy_static" 2030 2108 version = "1.5.0" 2031 2109 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2036 2114 2037 2115 [[package]] 2038 2116 name = "libc" 2039 - version = "0.2.177" 2117 + version = "0.2.179" 2040 2118 source = "registry+https://github.com/rust-lang/crates.io-index" 2041 - checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" 2119 + checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" 2042 2120 2043 2121 [[package]] 2044 2122 name = "libm" ··· 2048 2126 2049 2127 [[package]] 2050 2128 name = "libredox" 2051 - version = "0.1.10" 2129 + version = "0.1.12" 2052 2130 source = "registry+https://github.com/rust-lang/crates.io-index" 2053 - checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" 2131 + checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" 2054 2132 dependencies = [ 2055 2133 "bitflags", 2056 2134 "libc", 2057 - "redox_syscall", 2135 + "redox_syscall 0.7.0", 2058 2136 ] 2059 2137 2060 2138 [[package]] ··· 2086 2164 2087 2165 [[package]] 2088 2166 name = "log" 2089 - version = "0.4.28" 2167 + version = "0.4.29" 2090 2168 source = "registry+https://github.com/rust-lang/crates.io-index" 2091 - checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 2169 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 2092 2170 2093 2171 [[package]] 2094 2172 name = "loom" ··· 2125 2203 checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 2126 2204 2127 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]] 2128 2219 name = "markup5ever" 2129 2220 version = "0.12.1" 2130 2221 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2209 2300 dependencies = [ 2210 2301 "proc-macro2", 2211 2302 "quote", 2212 - "syn 2.0.111", 2303 + "syn 2.0.113", 2213 2304 ] 2214 2305 2215 2306 [[package]] ··· 2229 2320 ] 2230 2321 2231 2322 [[package]] 2232 - name = "mini-moka" 2323 + name = "mini-moka-wasm" 2233 2324 version = "0.10.99" 2234 - source = "git+https://tangled.org/nekomimi.pet/jacquard#e1b90160d4026e036ab5b797e56ddd7ae5c5537e" 2325 + source = "git+https://tangled.org/nonbinary.computer/jacquard#5bcf7f8e87324b8e67fc273c678d0490c9c6d15b" 2235 2326 dependencies = [ 2236 2327 "crossbeam-channel", 2237 2328 "crossbeam-utils", ··· 2243 2334 ] 2244 2335 2245 2336 [[package]] 2246 - name = "minimal-lexical" 2247 - version = "0.2.1" 2248 - source = "registry+https://github.com/rust-lang/crates.io-index" 2249 - checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 2250 - 2251 - [[package]] 2252 2337 name = "miniz_oxide" 2253 2338 version = "0.8.9" 2254 2339 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2260 2345 2261 2346 [[package]] 2262 2347 name = "mio" 2263 - version = "1.1.0" 2348 + version = "1.1.1" 2264 2349 source = "registry+https://github.com/rust-lang/crates.io-index" 2265 - checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" 2350 + checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" 2266 2351 dependencies = [ 2267 2352 "libc", 2268 2353 "wasi", ··· 2311 2396 ] 2312 2397 2313 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]] 2314 2405 name = "n0-future" 2315 2406 version = "0.1.3" 2316 2407 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2363 2454 version = "1.0.6" 2364 2455 source = "registry+https://github.com/rust-lang/crates.io-index" 2365 2456 checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 2366 - 2367 - [[package]] 2368 - name = "nom" 2369 - version = "7.1.3" 2370 - source = "registry+https://github.com/rust-lang/crates.io-index" 2371 - checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 2372 - dependencies = [ 2373 - "memchr", 2374 - "minimal-lexical", 2375 - ] 2376 2457 2377 2458 [[package]] 2378 2459 name = "nu-ansi-term" ··· 2455 2536 ] 2456 2537 2457 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]] 2458 2545 name = "objc2" 2459 2546 version = "0.6.3" 2460 2547 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2502 2589 2503 2590 [[package]] 2504 2591 name = "openssl-probe" 2505 - version = "0.1.6" 2592 + version = "0.2.0" 2506 2593 source = "registry+https://github.com/rust-lang/crates.io-index" 2507 - checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 2594 + checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" 2508 2595 2509 2596 [[package]] 2510 2597 name = "option-ext" ··· 2533 2620 "proc-macro2", 2534 2621 "proc-macro2-diagnostics", 2535 2622 "quote", 2536 - "syn 2.0.111", 2623 + "syn 2.0.113", 2537 2624 ] 2538 2625 2539 2626 [[package]] ··· 2543 2630 checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" 2544 2631 2545 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]] 2546 2642 name = "p256" 2547 2643 version = "0.13.2" 2548 2644 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2588 2684 dependencies = [ 2589 2685 "cfg-if", 2590 2686 "libc", 2591 - "redox_syscall", 2687 + "redox_syscall 0.5.18", 2592 2688 "smallvec", 2593 - "windows-link 0.2.1", 2689 + "windows-link", 2594 2690 ] 2595 2691 2596 2692 [[package]] ··· 2663 2759 dependencies = [ 2664 2760 "proc-macro2", 2665 2761 "quote", 2666 - "syn 2.0.111", 2762 + "syn 2.0.113", 2667 2763 ] 2668 2764 2669 2765 [[package]] ··· 2700 2796 ] 2701 2797 2702 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", 2815 + ] 2816 + 2817 + [[package]] 2703 2818 name = "potential_utf" 2704 2819 version = "0.1.4" 2705 2820 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2736 2851 checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 2737 2852 dependencies = [ 2738 2853 "proc-macro2", 2739 - "syn 2.0.111", 2854 + "syn 2.0.113", 2740 2855 ] 2741 2856 2742 2857 [[package]] ··· 2749 2864 ] 2750 2865 2751 2866 [[package]] 2752 - name = "proc-macro-error" 2753 - version = "1.0.4" 2754 - source = "registry+https://github.com/rust-lang/crates.io-index" 2755 - checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 2756 - dependencies = [ 2757 - "proc-macro-error-attr", 2758 - "proc-macro2", 2759 - "quote", 2760 - "syn 1.0.109", 2761 - "version_check", 2762 - ] 2763 - 2764 - [[package]] 2765 - name = "proc-macro-error-attr" 2766 - version = "1.0.4" 2767 - source = "registry+https://github.com/rust-lang/crates.io-index" 2768 - checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 2769 - dependencies = [ 2770 - "proc-macro2", 2771 - "quote", 2772 - "version_check", 2773 - ] 2774 - 2775 - [[package]] 2776 2867 name = "proc-macro2" 2777 - version = "1.0.103" 2868 + version = "1.0.105" 2778 2869 source = "registry+https://github.com/rust-lang/crates.io-index" 2779 - checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" 2870 + checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" 2780 2871 dependencies = [ 2781 2872 "unicode-ident", 2782 2873 ] ··· 2789 2880 dependencies = [ 2790 2881 "proc-macro2", 2791 2882 "quote", 2792 - "syn 2.0.111", 2883 + "syn 2.0.113", 2793 2884 "version_check", 2794 2885 "yansi", 2795 2886 ] ··· 2857 2948 2858 2949 [[package]] 2859 2950 name = "quote" 2860 - version = "1.0.42" 2951 + version = "1.0.43" 2861 2952 source = "registry+https://github.com/rust-lang/crates.io-index" 2862 - checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" 2953 + checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" 2863 2954 dependencies = [ 2864 2955 "proc-macro2", 2865 2956 ] ··· 2930 3021 ] 2931 3022 2932 3023 [[package]] 2933 - name = "range-traits" 2934 - version = "0.3.2" 3024 + name = "redox_syscall" 3025 + version = "0.5.18" 2935 3026 source = "registry+https://github.com/rust-lang/crates.io-index" 2936 - checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" 3027 + checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 3028 + dependencies = [ 3029 + "bitflags", 3030 + ] 2937 3031 2938 3032 [[package]] 2939 3033 name = "redox_syscall" 2940 - version = "0.5.18" 3034 + version = "0.7.0" 2941 3035 source = "registry+https://github.com/rust-lang/crates.io-index" 2942 - checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 3036 + checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" 2943 3037 dependencies = [ 2944 3038 "bitflags", 2945 3039 ] ··· 2956 3050 ] 2957 3051 2958 3052 [[package]] 2959 - name = "ref-cast" 2960 - version = "1.0.25" 2961 - source = "registry+https://github.com/rust-lang/crates.io-index" 2962 - checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" 2963 - dependencies = [ 2964 - "ref-cast-impl", 2965 - ] 2966 - 2967 - [[package]] 2968 - name = "ref-cast-impl" 2969 - version = "1.0.25" 2970 - source = "registry+https://github.com/rust-lang/crates.io-index" 2971 - checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" 2972 - dependencies = [ 2973 - "proc-macro2", 2974 - "quote", 2975 - "syn 2.0.111", 2976 - ] 2977 - 2978 - [[package]] 2979 3053 name = "regex" 2980 3054 version = "1.12.2" 2981 3055 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3012 3086 3013 3087 [[package]] 3014 3088 name = "reqwest" 3015 - version = "0.12.24" 3089 + version = "0.12.28" 3016 3090 source = "registry+https://github.com/rust-lang/crates.io-index" 3017 - checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" 3091 + checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" 3018 3092 dependencies = [ 3019 - "async-compression", 3020 3093 "base64 0.22.1", 3021 3094 "bytes", 3022 3095 "encoding_rs", ··· 3111 3184 3112 3185 [[package]] 3113 3186 name = "rsa" 3114 - version = "0.9.9" 3187 + version = "0.9.10" 3115 3188 source = "registry+https://github.com/rust-lang/crates.io-index" 3116 - checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" 3189 + checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" 3117 3190 dependencies = [ 3118 3191 "const-oid", 3119 3192 "digest", ··· 3142 3215 checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 3143 3216 3144 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 + ] 3225 + 3226 + [[package]] 3145 3227 name = "rustix" 3146 - version = "1.1.2" 3228 + version = "1.1.3" 3147 3229 source = "registry+https://github.com/rust-lang/crates.io-index" 3148 - checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 3230 + checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" 3149 3231 dependencies = [ 3150 3232 "bitflags", 3151 3233 "errno", ··· 3156 3238 3157 3239 [[package]] 3158 3240 name = "rustls" 3159 - version = "0.23.35" 3241 + version = "0.23.36" 3160 3242 source = "registry+https://github.com/rust-lang/crates.io-index" 3161 - checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" 3243 + checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" 3162 3244 dependencies = [ 3163 3245 "once_cell", 3164 3246 "ring", ··· 3170 3252 3171 3253 [[package]] 3172 3254 name = "rustls-native-certs" 3173 - version = "0.8.2" 3255 + version = "0.8.3" 3174 3256 source = "registry+https://github.com/rust-lang/crates.io-index" 3175 - checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" 3257 + checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" 3176 3258 dependencies = [ 3177 3259 "openssl-probe", 3178 3260 "rustls-pki-types", ··· 3182 3264 3183 3265 [[package]] 3184 3266 name = "rustls-pki-types" 3185 - version = "1.13.0" 3267 + version = "1.13.2" 3186 3268 source = "registry+https://github.com/rust-lang/crates.io-index" 3187 - checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" 3269 + checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" 3188 3270 dependencies = [ 3189 3271 "web-time", 3190 3272 "zeroize", ··· 3209 3291 3210 3292 [[package]] 3211 3293 name = "ryu" 3212 - version = "1.0.20" 3294 + version = "1.0.22" 3213 3295 source = "registry+https://github.com/rust-lang/crates.io-index" 3214 - checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 3296 + checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" 3215 3297 3216 3298 [[package]] 3217 3299 name = "safemem" ··· 3235 3317 checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 3236 3318 dependencies = [ 3237 3319 "windows-sys 0.61.2", 3238 - ] 3239 - 3240 - [[package]] 3241 - name = "schemars" 3242 - version = "0.9.0" 3243 - source = "registry+https://github.com/rust-lang/crates.io-index" 3244 - checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" 3245 - dependencies = [ 3246 - "dyn-clone", 3247 - "ref-cast", 3248 - "serde", 3249 - "serde_json", 3250 - ] 3251 - 3252 - [[package]] 3253 - name = "schemars" 3254 - version = "1.1.0" 3255 - source = "registry+https://github.com/rust-lang/crates.io-index" 3256 - checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" 3257 - dependencies = [ 3258 - "dyn-clone", 3259 - "ref-cast", 3260 - "serde", 3261 - "serde_json", 3262 3320 ] 3263 3321 3264 3322 [[package]] ··· 3311 3369 ] 3312 3370 3313 3371 [[package]] 3372 + name = "semver" 3373 + version = "1.0.27" 3374 + source = "registry+https://github.com/rust-lang/crates.io-index" 3375 + checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" 3376 + 3377 + [[package]] 3314 3378 name = "send_wrapper" 3315 3379 version = "0.6.0" 3316 3380 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3353 3417 dependencies = [ 3354 3418 "proc-macro2", 3355 3419 "quote", 3356 - "syn 2.0.111", 3420 + "syn 2.0.113", 3357 3421 ] 3358 3422 3359 3423 [[package]] 3360 3424 name = "serde_html_form" 3361 - version = "0.2.8" 3425 + version = "0.3.2" 3362 3426 source = "registry+https://github.com/rust-lang/crates.io-index" 3363 - checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" 3427 + checksum = "2acf96b1d9364968fce46ebb548f1c0e1d7eceae27bdff73865d42e6c7369d94" 3364 3428 dependencies = [ 3365 3429 "form_urlencoded", 3366 - "indexmap 2.12.1", 3430 + "indexmap", 3367 3431 "itoa", 3368 - "ryu", 3369 3432 "serde_core", 3370 3433 ] 3371 3434 ··· 3383 3446 3384 3447 [[package]] 3385 3448 name = "serde_json" 3386 - version = "1.0.145" 3449 + version = "1.0.149" 3387 3450 source = "registry+https://github.com/rust-lang/crates.io-index" 3388 - checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 3451 + checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 3389 3452 dependencies = [ 3390 3453 "itoa", 3391 3454 "memchr", 3392 - "ryu", 3393 3455 "serde", 3394 3456 "serde_core", 3457 + "zmij", 3395 3458 ] 3396 3459 3397 3460 [[package]] ··· 3413 3476 dependencies = [ 3414 3477 "proc-macro2", 3415 3478 "quote", 3416 - "syn 2.0.111", 3479 + "syn 2.0.113", 3417 3480 ] 3418 3481 3419 3482 [[package]] ··· 3430 3493 3431 3494 [[package]] 3432 3495 name = "serde_with" 3433 - version = "3.16.0" 3496 + version = "3.16.1" 3434 3497 source = "registry+https://github.com/rust-lang/crates.io-index" 3435 - checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" 3498 + checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" 3436 3499 dependencies = [ 3437 3500 "base64 0.22.1", 3438 3501 "chrono", 3439 3502 "hex", 3440 - "indexmap 1.9.3", 3441 - "indexmap 2.12.1", 3442 - "schemars 0.9.0", 3443 - "schemars 1.1.0", 3444 3503 "serde_core", 3445 3504 "serde_json", 3446 3505 "serde_with_macros", ··· 3449 3508 3450 3509 [[package]] 3451 3510 name = "serde_with_macros" 3452 - version = "3.16.0" 3511 + version = "3.16.1" 3453 3512 source = "registry+https://github.com/rust-lang/crates.io-index" 3454 - checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" 3513 + checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" 3455 3514 dependencies = [ 3456 3515 "darling", 3457 3516 "proc-macro2", 3458 3517 "quote", 3459 - "syn 2.0.111", 3518 + "syn 2.0.113", 3460 3519 ] 3461 3520 3462 3521 [[package]] ··· 3513 3572 3514 3573 [[package]] 3515 3574 name = "signal-hook-registry" 3516 - version = "1.4.7" 3575 + version = "1.4.8" 3517 3576 source = "registry+https://github.com/rust-lang/crates.io-index" 3518 - checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" 3577 + checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" 3519 3578 dependencies = [ 3579 + "errno", 3520 3580 "libc", 3521 3581 ] 3522 3582 ··· 3532 3592 3533 3593 [[package]] 3534 3594 name = "simd-adler32" 3535 - version = "0.3.7" 3595 + version = "0.3.8" 3536 3596 source = "registry+https://github.com/rust-lang/crates.io-index" 3537 - checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 3597 + checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" 3538 3598 3539 3599 [[package]] 3540 3600 name = "siphasher" ··· 3589 3649 version = "0.9.8" 3590 3650 source = "registry+https://github.com/rust-lang/crates.io-index" 3591 3651 checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 3652 + dependencies = [ 3653 + "lock_api", 3654 + ] 3592 3655 3593 3656 [[package]] 3594 3657 name = "spin" ··· 3613 3676 checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 3614 3677 3615 3678 [[package]] 3616 - name = "static-regular-grammar" 3617 - version = "2.0.2" 3618 - source = "registry+https://github.com/rust-lang/crates.io-index" 3619 - checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957" 3620 - dependencies = [ 3621 - "abnf", 3622 - "btree-range-map", 3623 - "ciborium", 3624 - "hex_fmt", 3625 - "indoc", 3626 - "proc-macro-error", 3627 - "proc-macro2", 3628 - "quote", 3629 - "serde", 3630 - "sha2", 3631 - "syn 2.0.111", 3632 - "thiserror 1.0.69", 3633 - ] 3634 - 3635 - [[package]] 3636 3679 name = "static_assertions" 3637 3680 version = "1.1.0" 3638 3681 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3686 3729 3687 3730 [[package]] 3688 3731 name = "supports-hyperlinks" 3689 - version = "3.1.0" 3732 + version = "3.2.0" 3690 3733 source = "registry+https://github.com/rust-lang/crates.io-index" 3691 - checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" 3734 + checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" 3692 3735 3693 3736 [[package]] 3694 3737 name = "supports-unicode" ··· 3709 3752 3710 3753 [[package]] 3711 3754 name = "syn" 3712 - version = "2.0.111" 3755 + version = "2.0.113" 3713 3756 source = "registry+https://github.com/rust-lang/crates.io-index" 3714 - checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" 3757 + checksum = "678faa00651c9eb72dd2020cbdf275d92eccb2400d568e419efdd64838145cb4" 3715 3758 dependencies = [ 3716 3759 "proc-macro2", 3717 3760 "quote", ··· 3735 3778 dependencies = [ 3736 3779 "proc-macro2", 3737 3780 "quote", 3738 - "syn 2.0.111", 3781 + "syn 2.0.113", 3739 3782 ] 3740 3783 3741 3784 [[package]] ··· 3767 3810 3768 3811 [[package]] 3769 3812 name = "tempfile" 3770 - version = "3.23.0" 3813 + version = "3.24.0" 3771 3814 source = "registry+https://github.com/rust-lang/crates.io-index" 3772 - checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" 3815 + checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" 3773 3816 dependencies = [ 3774 3817 "fastrand", 3775 3818 "getrandom 0.3.4", ··· 3835 3878 dependencies = [ 3836 3879 "proc-macro2", 3837 3880 "quote", 3838 - "syn 2.0.111", 3881 + "syn 2.0.113", 3839 3882 ] 3840 3883 3841 3884 [[package]] ··· 3846 3889 dependencies = [ 3847 3890 "proc-macro2", 3848 3891 "quote", 3849 - "syn 2.0.111", 3892 + "syn 2.0.113", 3850 3893 ] 3851 3894 3852 3895 [[package]] ··· 3874 3917 checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 3875 3918 dependencies = [ 3876 3919 "deranged", 3877 - "itoa", 3878 3920 "libc", 3879 3921 "num-conv", 3880 3922 "num_threads", 3881 3923 "powerfmt", 3882 3924 "serde", 3883 3925 "time-core", 3884 - "time-macros", 3885 3926 ] 3886 3927 3887 3928 [[package]] ··· 3891 3932 checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 3892 3933 3893 3934 [[package]] 3894 - name = "time-macros" 3895 - version = "0.2.24" 3896 - source = "registry+https://github.com/rust-lang/crates.io-index" 3897 - checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 3898 - dependencies = [ 3899 - "num-conv", 3900 - "time-core", 3901 - ] 3902 - 3903 - [[package]] 3904 3935 name = "tiny_http" 3905 3936 version = "0.12.0" 3906 3937 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3939 3970 3940 3971 [[package]] 3941 3972 name = "tokio" 3942 - version = "1.48.0" 3973 + version = "1.49.0" 3943 3974 source = "registry+https://github.com/rust-lang/crates.io-index" 3944 - checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" 3975 + checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" 3945 3976 dependencies = [ 3946 3977 "bytes", 3947 3978 "libc", ··· 3962 3993 dependencies = [ 3963 3994 "proc-macro2", 3964 3995 "quote", 3965 - "syn 2.0.111", 3996 + "syn 2.0.113", 3966 3997 ] 3967 3998 3968 3999 [[package]] ··· 4012 4043 4013 4044 [[package]] 4014 4045 name = "tokio-util" 4015 - version = "0.7.17" 4046 + version = "0.7.18" 4016 4047 source = "registry+https://github.com/rust-lang/crates.io-index" 4017 - checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 4048 + checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" 4018 4049 dependencies = [ 4019 4050 "bytes", 4020 4051 "futures-core", ··· 4042 4073 4043 4074 [[package]] 4044 4075 name = "tower-http" 4045 - version = "0.6.7" 4076 + version = "0.6.8" 4046 4077 source = "registry+https://github.com/rust-lang/crates.io-index" 4047 - checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" 4078 + checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" 4048 4079 dependencies = [ 4049 4080 "async-compression", 4050 4081 "bitflags", ··· 4083 4114 4084 4115 [[package]] 4085 4116 name = "tracing" 4086 - version = "0.1.41" 4117 + version = "0.1.44" 4087 4118 source = "registry+https://github.com/rust-lang/crates.io-index" 4088 - checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 4119 + checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 4089 4120 dependencies = [ 4090 4121 "log", 4091 4122 "pin-project-lite", ··· 4095 4126 4096 4127 [[package]] 4097 4128 name = "tracing-attributes" 4098 - version = "0.1.30" 4129 + version = "0.1.31" 4099 4130 source = "registry+https://github.com/rust-lang/crates.io-index" 4100 - checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" 4131 + checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 4101 4132 dependencies = [ 4102 4133 "proc-macro2", 4103 4134 "quote", 4104 - "syn 2.0.111", 4135 + "syn 2.0.113", 4105 4136 ] 4106 4137 4107 4138 [[package]] 4108 4139 name = "tracing-core" 4109 - version = "0.1.34" 4140 + version = "0.1.36" 4110 4141 source = "registry+https://github.com/rust-lang/crates.io-index" 4111 - checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" 4142 + checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 4112 4143 dependencies = [ 4113 4144 "once_cell", 4114 4145 "valuable", ··· 4127 4158 4128 4159 [[package]] 4129 4160 name = "tracing-subscriber" 4130 - version = "0.3.20" 4161 + version = "0.3.22" 4131 4162 source = "registry+https://github.com/rust-lang/crates.io-index" 4132 - checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" 4163 + checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" 4133 4164 dependencies = [ 4134 4165 "matchers", 4135 4166 "nu-ansi-term", ··· 4151 4182 dependencies = [ 4152 4183 "proc-macro2", 4153 4184 "quote", 4154 - "syn 2.0.111", 4185 + "syn 2.0.113", 4155 4186 ] 4156 4187 4157 4188 [[package]] ··· 4203 4234 4204 4235 [[package]] 4205 4236 name = "unicase" 4206 - version = "2.8.1" 4237 + version = "2.9.0" 4207 4238 source = "registry+https://github.com/rust-lang/crates.io-index" 4208 - checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 4239 + checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" 4209 4240 4210 4241 [[package]] 4211 4242 name = "unicode-ident" ··· 4257 4288 4258 4289 [[package]] 4259 4290 name = "url" 4260 - version = "2.5.7" 4291 + version = "2.5.8" 4261 4292 source = "registry+https://github.com/rust-lang/crates.io-index" 4262 - checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 4293 + checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 4263 4294 dependencies = [ 4264 4295 "form_urlencoded", 4265 4296 "idna", 4266 4297 "percent-encoding", 4267 4298 "serde", 4299 + "serde_derive", 4268 4300 ] 4269 4301 4270 4302 [[package]] ··· 4339 4371 4340 4372 [[package]] 4341 4373 name = "wasm-bindgen" 4342 - version = "0.2.105" 4374 + version = "0.2.106" 4343 4375 source = "registry+https://github.com/rust-lang/crates.io-index" 4344 - checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" 4376 + checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" 4345 4377 dependencies = [ 4346 4378 "cfg-if", 4347 4379 "once_cell", ··· 4352 4384 4353 4385 [[package]] 4354 4386 name = "wasm-bindgen-futures" 4355 - version = "0.4.55" 4387 + version = "0.4.56" 4356 4388 source = "registry+https://github.com/rust-lang/crates.io-index" 4357 - checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" 4389 + checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" 4358 4390 dependencies = [ 4359 4391 "cfg-if", 4360 4392 "js-sys", ··· 4365 4397 4366 4398 [[package]] 4367 4399 name = "wasm-bindgen-macro" 4368 - version = "0.2.105" 4400 + version = "0.2.106" 4369 4401 source = "registry+https://github.com/rust-lang/crates.io-index" 4370 - checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" 4402 + checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" 4371 4403 dependencies = [ 4372 4404 "quote", 4373 4405 "wasm-bindgen-macro-support", ··· 4375 4407 4376 4408 [[package]] 4377 4409 name = "wasm-bindgen-macro-support" 4378 - version = "0.2.105" 4410 + version = "0.2.106" 4379 4411 source = "registry+https://github.com/rust-lang/crates.io-index" 4380 - checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" 4412 + checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" 4381 4413 dependencies = [ 4382 4414 "bumpalo", 4383 4415 "proc-macro2", 4384 4416 "quote", 4385 - "syn 2.0.111", 4417 + "syn 2.0.113", 4386 4418 "wasm-bindgen-shared", 4387 4419 ] 4388 4420 4389 4421 [[package]] 4390 4422 name = "wasm-bindgen-shared" 4391 - version = "0.2.105" 4423 + version = "0.2.106" 4392 4424 source = "registry+https://github.com/rust-lang/crates.io-index" 4393 - checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" 4425 + checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" 4394 4426 dependencies = [ 4395 4427 "unicode-ident", 4396 4428 ] ··· 4410 4442 4411 4443 [[package]] 4412 4444 name = "web-sys" 4413 - version = "0.3.82" 4445 + version = "0.3.83" 4414 4446 source = "registry+https://github.com/rust-lang/crates.io-index" 4415 - checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" 4447 + checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" 4416 4448 dependencies = [ 4417 4449 "js-sys", 4418 4450 "wasm-bindgen", ··· 4458 4490 4459 4491 [[package]] 4460 4492 name = "webpki-roots" 4461 - version = "1.0.4" 4493 + version = "1.0.5" 4462 4494 source = "registry+https://github.com/rust-lang/crates.io-index" 4463 - checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" 4495 + checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" 4464 4496 dependencies = [ 4465 4497 "rustls-pki-types", 4466 4498 ] ··· 4481 4513 ] 4482 4514 4483 4515 [[package]] 4484 - name = "windows" 4485 - version = "0.61.3" 4486 - source = "registry+https://github.com/rust-lang/crates.io-index" 4487 - checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" 4488 - dependencies = [ 4489 - "windows-collections", 4490 - "windows-core 0.61.2", 4491 - "windows-future", 4492 - "windows-link 0.1.3", 4493 - "windows-numerics", 4494 - ] 4495 - 4496 - [[package]] 4497 - name = "windows-collections" 4498 - version = "0.2.0" 4499 - source = "registry+https://github.com/rust-lang/crates.io-index" 4500 - checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" 4501 - dependencies = [ 4502 - "windows-core 0.61.2", 4503 - ] 4504 - 4505 - [[package]] 4506 - name = "windows-core" 4507 - version = "0.61.2" 4508 - source = "registry+https://github.com/rust-lang/crates.io-index" 4509 - checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 4510 - dependencies = [ 4511 - "windows-implement", 4512 - "windows-interface", 4513 - "windows-link 0.1.3", 4514 - "windows-result 0.3.4", 4515 - "windows-strings 0.4.2", 4516 - ] 4517 - 4518 - [[package]] 4519 4516 name = "windows-core" 4520 4517 version = "0.62.2" 4521 4518 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4523 4520 dependencies = [ 4524 4521 "windows-implement", 4525 4522 "windows-interface", 4526 - "windows-link 0.2.1", 4527 - "windows-result 0.4.1", 4528 - "windows-strings 0.5.1", 4529 - ] 4530 - 4531 - [[package]] 4532 - name = "windows-future" 4533 - version = "0.2.1" 4534 - source = "registry+https://github.com/rust-lang/crates.io-index" 4535 - checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" 4536 - dependencies = [ 4537 - "windows-core 0.61.2", 4538 - "windows-link 0.1.3", 4539 - "windows-threading", 4523 + "windows-link", 4524 + "windows-result", 4525 + "windows-strings", 4540 4526 ] 4541 4527 4542 4528 [[package]] ··· 4547 4533 dependencies = [ 4548 4534 "proc-macro2", 4549 4535 "quote", 4550 - "syn 2.0.111", 4536 + "syn 2.0.113", 4551 4537 ] 4552 4538 4553 4539 [[package]] ··· 4558 4544 dependencies = [ 4559 4545 "proc-macro2", 4560 4546 "quote", 4561 - "syn 2.0.111", 4547 + "syn 2.0.113", 4562 4548 ] 4563 4549 4564 4550 [[package]] 4565 4551 name = "windows-link" 4566 - version = "0.1.3" 4567 - source = "registry+https://github.com/rust-lang/crates.io-index" 4568 - checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 4569 - 4570 - [[package]] 4571 - name = "windows-link" 4572 4552 version = "0.2.1" 4573 4553 source = "registry+https://github.com/rust-lang/crates.io-index" 4574 4554 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 4575 4555 4576 4556 [[package]] 4577 - name = "windows-numerics" 4578 - version = "0.2.0" 4579 - source = "registry+https://github.com/rust-lang/crates.io-index" 4580 - checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" 4581 - dependencies = [ 4582 - "windows-core 0.61.2", 4583 - "windows-link 0.1.3", 4584 - ] 4585 - 4586 - [[package]] 4587 4557 name = "windows-registry" 4588 4558 version = "0.6.1" 4589 4559 source = "registry+https://github.com/rust-lang/crates.io-index" 4590 4560 checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 4591 4561 dependencies = [ 4592 - "windows-link 0.2.1", 4593 - "windows-result 0.4.1", 4594 - "windows-strings 0.5.1", 4595 - ] 4596 - 4597 - [[package]] 4598 - name = "windows-result" 4599 - version = "0.3.4" 4600 - source = "registry+https://github.com/rust-lang/crates.io-index" 4601 - checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 4602 - dependencies = [ 4603 - "windows-link 0.1.3", 4562 + "windows-link", 4563 + "windows-result", 4564 + "windows-strings", 4604 4565 ] 4605 4566 4606 4567 [[package]] ··· 4609 4570 source = "registry+https://github.com/rust-lang/crates.io-index" 4610 4571 checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 4611 4572 dependencies = [ 4612 - "windows-link 0.2.1", 4613 - ] 4614 - 4615 - [[package]] 4616 - name = "windows-strings" 4617 - version = "0.4.2" 4618 - source = "registry+https://github.com/rust-lang/crates.io-index" 4619 - checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 4620 - dependencies = [ 4621 - "windows-link 0.1.3", 4573 + "windows-link", 4622 4574 ] 4623 4575 4624 4576 [[package]] ··· 4627 4579 source = "registry+https://github.com/rust-lang/crates.io-index" 4628 4580 checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 4629 4581 dependencies = [ 4630 - "windows-link 0.2.1", 4582 + "windows-link", 4631 4583 ] 4632 4584 4633 4585 [[package]] ··· 4659 4611 4660 4612 [[package]] 4661 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" 4662 4623 version = "0.60.2" 4663 4624 source = "registry+https://github.com/rust-lang/crates.io-index" 4664 4625 checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" ··· 4672 4633 source = "registry+https://github.com/rust-lang/crates.io-index" 4673 4634 checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 4674 4635 dependencies = [ 4675 - "windows-link 0.2.1", 4636 + "windows-link", 4676 4637 ] 4677 4638 4678 4639 [[package]] ··· 4727 4688 source = "registry+https://github.com/rust-lang/crates.io-index" 4728 4689 checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 4729 4690 dependencies = [ 4730 - "windows-link 0.2.1", 4691 + "windows-link", 4731 4692 "windows_aarch64_gnullvm 0.53.1", 4732 4693 "windows_aarch64_msvc 0.53.1", 4733 4694 "windows_i686_gnu 0.53.1", ··· 4736 4697 "windows_x86_64_gnu 0.53.1", 4737 4698 "windows_x86_64_gnullvm 0.53.1", 4738 4699 "windows_x86_64_msvc 0.53.1", 4739 - ] 4740 - 4741 - [[package]] 4742 - name = "windows-threading" 4743 - version = "0.1.0" 4744 - source = "registry+https://github.com/rust-lang/crates.io-index" 4745 - checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" 4746 - dependencies = [ 4747 - "windows-link 0.1.3", 4748 4700 ] 4749 4701 4750 4702 [[package]] ··· 4939 4891 4940 4892 [[package]] 4941 4893 name = "wisp-cli" 4942 - version = "0.4.1" 4894 + version = "0.4.2" 4943 4895 dependencies = [ 4944 4896 "axum", 4945 4897 "base64 0.22.1", ··· 4948 4900 "clap", 4949 4901 "flate2", 4950 4902 "futures", 4903 + "globset", 4904 + "ignore", 4905 + "indicatif", 4951 4906 "jacquard", 4952 4907 "jacquard-api", 4953 4908 "jacquard-common", ··· 4972 4927 "tower-http", 4973 4928 "url", 4974 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", 4975 4942 ] 4976 4943 4977 4944 [[package]] ··· 5022 4989 dependencies = [ 5023 4990 "proc-macro2", 5024 4991 "quote", 5025 - "syn 2.0.111", 4992 + "syn 2.0.113", 5026 4993 "synstructure", 5027 4994 ] 5028 4995 5029 4996 [[package]] 5030 4997 name = "zerocopy" 5031 - version = "0.8.30" 4998 + version = "0.8.32" 5032 4999 source = "registry+https://github.com/rust-lang/crates.io-index" 5033 - checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" 5000 + checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56" 5034 5001 dependencies = [ 5035 5002 "zerocopy-derive", 5036 5003 ] 5037 5004 5038 5005 [[package]] 5039 5006 name = "zerocopy-derive" 5040 - version = "0.8.30" 5007 + version = "0.8.32" 5041 5008 source = "registry+https://github.com/rust-lang/crates.io-index" 5042 - checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" 5009 + checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90" 5043 5010 dependencies = [ 5044 5011 "proc-macro2", 5045 5012 "quote", 5046 - "syn 2.0.111", 5013 + "syn 2.0.113", 5047 5014 ] 5048 5015 5049 5016 [[package]] ··· 5063 5030 dependencies = [ 5064 5031 "proc-macro2", 5065 5032 "quote", 5066 - "syn 2.0.111", 5033 + "syn 2.0.113", 5067 5034 "synstructure", 5068 5035 ] 5069 5036 ··· 5106 5073 dependencies = [ 5107 5074 "proc-macro2", 5108 5075 "quote", 5109 - "syn 2.0.111", 5076 + "syn 2.0.113", 5110 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"
+19 -8
cli/Cargo.toml
··· 1 1 [package] 2 2 name = "wisp-cli" 3 - version = "0.4.1" 3 + version = "0.4.2" 4 4 edition = "2024" 5 5 6 6 [features] ··· 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"] } ··· 40 48 chrono = "0.4" 41 49 url = "2.5" 42 50 regex = "1.11" 51 + ignore = "0.4" 52 + globset = "0.4" 53 + indicatif = "0.17"
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 + pub mod fs; 7 + pub mod settings; 8 + pub mod subfs;
+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
··· 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
··· 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
··· 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
··· 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 - }
+149
cli/src/ignore_patterns.rs
··· 1 + use globset::{Glob, GlobSet, GlobSetBuilder}; 2 + use serde::{Deserialize, Serialize}; 3 + use std::path::Path; 4 + use miette::IntoDiagnostic; 5 + 6 + #[derive(Debug, Deserialize, Serialize)] 7 + struct IgnoreConfig { 8 + patterns: Vec<String>, 9 + } 10 + 11 + /// Load ignore patterns from the default .wispignore.json file 12 + fn load_default_patterns() -> miette::Result<Vec<String>> { 13 + // Path to the default ignore patterns JSON file (in the monorepo root) 14 + let default_json_path = concat!(env!("CARGO_MANIFEST_DIR"), "/../.wispignore.json"); 15 + 16 + match std::fs::read_to_string(default_json_path) { 17 + Ok(contents) => { 18 + let config: IgnoreConfig = serde_json::from_str(&contents).into_diagnostic()?; 19 + Ok(config.patterns) 20 + } 21 + Err(_) => { 22 + // If the default file doesn't exist, return hardcoded patterns as fallback 23 + eprintln!("โš ๏ธ Default .wispignore.json not found, using hardcoded patterns"); 24 + Ok(get_hardcoded_patterns()) 25 + } 26 + } 27 + } 28 + 29 + /// Hardcoded fallback patterns (same as in .wispignore.json) 30 + fn get_hardcoded_patterns() -> Vec<String> { 31 + vec![ 32 + ".git".to_string(), 33 + ".git/**".to_string(), 34 + ".github".to_string(), 35 + ".github/**".to_string(), 36 + ".gitlab".to_string(), 37 + ".gitlab/**".to_string(), 38 + ".DS_Store".to_string(), 39 + ".wisp.metadata.json".to_string(), 40 + ".env".to_string(), 41 + ".env.*".to_string(), 42 + "node_modules".to_string(), 43 + "node_modules/**".to_string(), 44 + "Thumbs.db".to_string(), 45 + "desktop.ini".to_string(), 46 + "._*".to_string(), 47 + ".Spotlight-V100".to_string(), 48 + ".Spotlight-V100/**".to_string(), 49 + ".Trashes".to_string(), 50 + ".Trashes/**".to_string(), 51 + ".fseventsd".to_string(), 52 + ".fseventsd/**".to_string(), 53 + ".cache".to_string(), 54 + ".cache/**".to_string(), 55 + ".temp".to_string(), 56 + ".temp/**".to_string(), 57 + ".tmp".to_string(), 58 + ".tmp/**".to_string(), 59 + "__pycache__".to_string(), 60 + "__pycache__/**".to_string(), 61 + "*.pyc".to_string(), 62 + ".venv".to_string(), 63 + ".venv/**".to_string(), 64 + "venv".to_string(), 65 + "venv/**".to_string(), 66 + "env".to_string(), 67 + "env/**".to_string(), 68 + "*.swp".to_string(), 69 + "*.swo".to_string(), 70 + "*~".to_string(), 71 + ".tangled".to_string(), 72 + ".tangled/**".to_string(), 73 + ] 74 + } 75 + 76 + /// Load custom ignore patterns from a .wispignore file in the given directory 77 + fn load_wispignore_file(dir_path: &Path) -> miette::Result<Vec<String>> { 78 + let wispignore_path = dir_path.join(".wispignore"); 79 + 80 + if !wispignore_path.exists() { 81 + return Ok(Vec::new()); 82 + } 83 + 84 + let contents = std::fs::read_to_string(&wispignore_path).into_diagnostic()?; 85 + 86 + // Parse gitignore-style file (one pattern per line, # for comments) 87 + let patterns: Vec<String> = contents 88 + .lines() 89 + .filter_map(|line| { 90 + let line = line.trim(); 91 + // Skip empty lines and comments 92 + if line.is_empty() || line.starts_with('#') { 93 + None 94 + } else { 95 + Some(line.to_string()) 96 + } 97 + }) 98 + .collect(); 99 + 100 + if !patterns.is_empty() { 101 + println!("Loaded {} custom patterns from .wispignore", patterns.len()); 102 + } 103 + 104 + Ok(patterns) 105 + } 106 + 107 + /// Build a GlobSet from a list of patterns 108 + fn build_globset(patterns: Vec<String>) -> miette::Result<GlobSet> { 109 + let mut builder = GlobSetBuilder::new(); 110 + 111 + for pattern in patterns { 112 + let glob = Glob::new(&pattern).into_diagnostic()?; 113 + builder.add(glob); 114 + } 115 + 116 + let globset = builder.build().into_diagnostic()?; 117 + Ok(globset) 118 + } 119 + 120 + /// IgnoreMatcher handles checking if paths should be ignored 121 + pub struct IgnoreMatcher { 122 + globset: GlobSet, 123 + } 124 + 125 + impl IgnoreMatcher { 126 + /// Create a new IgnoreMatcher for the given directory 127 + /// Loads default patterns and any custom .wispignore file 128 + pub fn new(dir_path: &Path) -> miette::Result<Self> { 129 + let mut all_patterns = load_default_patterns()?; 130 + 131 + // Load custom patterns from .wispignore 132 + let custom_patterns = load_wispignore_file(dir_path)?; 133 + all_patterns.extend(custom_patterns); 134 + 135 + let globset = build_globset(all_patterns)?; 136 + 137 + Ok(Self { globset }) 138 + } 139 + 140 + /// Check if the given path (relative to site root) should be ignored 141 + pub fn is_ignored(&self, path: &str) -> bool { 142 + self.globset.is_match(path) 143 + } 144 + 145 + /// Check if a filename should be ignored (checks just the filename, not full path) 146 + pub fn is_filename_ignored(&self, filename: &str) -> bool { 147 + self.globset.is_match(filename) 148 + } 149 + }
-9
cli/src/lib.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 - pub mod builder_types; 7 - 8 - #[cfg(feature = "place_wisp")] 9 - pub mod place_wisp;
+200 -106
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; ··· 8 6 mod serve; 9 7 mod subfs_utils; 10 8 mod redirects; 9 + mod ignore_patterns; 11 10 12 11 use clap::{Parser, Subcommand}; 13 12 use jacquard::CowStr; ··· 25 24 use std::io::Write; 26 25 use base64::Engine; 27 26 use futures::stream::{self, StreamExt}; 27 + use indicatif::{ProgressBar, ProgressStyle, MultiProgress}; 28 28 29 - use place_wisp::fs::*; 30 - 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 31 38 32 39 #[derive(Parser, Debug)] 33 40 #[command(author, version, about = "wisp.place CLI tool")] 34 41 struct Args { 35 42 #[command(subcommand)] 36 43 command: Option<Commands>, 37 - 44 + 38 45 // Deploy arguments (when no subcommand is specified) 39 46 /// Handle (e.g., alice.bsky.social), DID, or PDS URL 40 - #[arg(global = true, conflicts_with = "command")] 41 47 input: Option<CowStr<'static>>, 42 48 43 49 /// Path to the directory containing your static site 44 - #[arg(short, long, global = true, conflicts_with = "command")] 50 + #[arg(short, long)] 45 51 path: Option<PathBuf>, 46 52 47 53 /// Site name (defaults to directory name) 48 - #[arg(short, long, global = true, conflicts_with = "command")] 54 + #[arg(short, long)] 49 55 site: Option<String>, 50 56 51 57 /// Path to auth store file 52 - #[arg(long, global = true, conflicts_with = "command")] 58 + #[arg(long)] 53 59 store: Option<String>, 54 60 55 61 /// App Password for authentication 56 - #[arg(long, global = true, conflicts_with = "command")] 62 + #[arg(long)] 57 63 password: Option<CowStr<'static>>, 58 64 59 65 /// Enable directory listing mode for paths without index files 60 - #[arg(long, global = true, conflicts_with = "command")] 66 + #[arg(long)] 61 67 directory: bool, 62 68 63 69 /// Enable SPA mode (serve index.html for all routes) 64 - #[arg(long, global = true, conflicts_with = "command")] 70 + #[arg(long)] 65 71 spa: bool, 72 + 73 + /// Skip confirmation prompts (automatically accept warnings) 74 + #[arg(short = 'y', long)] 75 + yes: bool, 66 76 } 67 77 68 78 #[derive(Subcommand, Debug)] ··· 95 105 /// Enable SPA mode (serve index.html for all routes) 96 106 #[arg(long)] 97 107 spa: bool, 108 + 109 + /// Skip confirmation prompts (automatically accept warnings) 110 + #[arg(short = 'y', long)] 111 + yes: bool, 98 112 }, 99 113 /// Pull a site from the PDS to a local directory 100 114 Pull { ··· 107 121 108 122 /// Output directory for the downloaded site 109 123 #[arg(short, long, default_value = ".")] 110 - output: PathBuf, 124 + path: PathBuf, 111 125 }, 112 126 /// Serve a site locally with real-time firehose updates 113 127 Serve { ··· 120 134 121 135 /// Output directory for the site files 122 136 #[arg(short, long, default_value = ".")] 123 - output: PathBuf, 137 + path: PathBuf, 124 138 125 139 /// Port to serve on 126 - #[arg(short, long, default_value = "8080")] 140 + #[arg(short = 'P', long, default_value = "8080")] 127 141 port: u16, 128 142 }, 129 143 } ··· 133 147 let args = Args::parse(); 134 148 135 149 let result = match args.command { 136 - Some(Commands::Deploy { input, path, site, store, password, directory, spa }) => { 150 + Some(Commands::Deploy { input, path, site, store, password, directory, spa, yes }) => { 137 151 // Dispatch to appropriate authentication method 138 152 if let Some(password) = password { 139 - run_with_app_password(input, password, path, site, directory, spa).await 153 + run_with_app_password(input, password, path, site, directory, spa, yes).await 140 154 } else { 141 - run_with_oauth(input, store, path, site, directory, spa).await 155 + run_with_oauth(input, store, path, site, directory, spa, yes).await 142 156 } 143 157 } 144 - Some(Commands::Pull { input, site, output }) => { 145 - 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 146 160 } 147 - Some(Commands::Serve { input, site, output, port }) => { 148 - 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 149 163 } 150 164 None => { 151 165 // Legacy mode: if input is provided, assume deploy command ··· 155 169 156 170 // Dispatch to appropriate authentication method 157 171 if let Some(password) = args.password { 158 - 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 159 173 } else { 160 - 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 161 175 } 162 176 } else { 163 177 // No command and no input, show help ··· 186 200 site: Option<String>, 187 201 directory: bool, 188 202 spa: bool, 203 + yes: bool, 189 204 ) -> miette::Result<()> { 190 205 let (session, auth) = 191 206 MemoryCredentialSession::authenticated(input, password, None, None).await?; 192 207 println!("Signed in as {}", auth.handle); 193 208 194 209 let agent: Agent<_> = Agent::from(session); 195 - deploy_site(&agent, path, site, directory, spa).await 210 + deploy_site(&agent, path, site, directory, spa, yes).await 196 211 } 197 212 198 213 /// Run deployment with OAuth authentication ··· 203 218 site: Option<String>, 204 219 directory: bool, 205 220 spa: bool, 221 + yes: bool, 206 222 ) -> miette::Result<()> { 207 223 use jacquard::oauth::scopes::Scope; 208 224 use jacquard::oauth::atproto::AtprotoClientMetadata; ··· 235 251 .await?; 236 252 237 253 let agent: Agent<_> = Agent::from(session); 238 - 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)) 239 308 } 240 309 241 310 /// Deploy the site using the provided agent ··· 245 314 site: Option<String>, 246 315 directory_listing: bool, 247 316 spa_mode: bool, 317 + skip_prompts: bool, 248 318 ) -> miette::Result<()> { 249 319 // Verify the path exists 250 320 if !path.exists() { ··· 262 332 263 333 println!("Deploying site '{}'...", site_name); 264 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 + 265 385 // Try to fetch existing manifest for incremental updates 266 386 let (existing_blob_map, old_subfs_uris): (HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, Vec<(String, String)>) = { 267 387 use jacquard_common::types::string::AtUri; ··· 323 443 } 324 444 }; 325 445 326 - // Build directory tree 327 - let (root_dir, total_files, reused_count) = build_directory(agent, &path, &existing_blob_map, String::new()).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?; 328 459 let uploaded_count = total_files - reused_count; 460 + 461 + progress.finish_with_message(format!("โœ“ {} files ({} uploaded, {} reused)", total_files, uploaded_count, reused_count)); 329 462 330 463 // Check if we need to split into subfs records 331 464 const MAX_MANIFEST_SIZE: usize = 140 * 1024; // 140KB (PDS limit is 150KB) ··· 376 509 let chunk_file_count = subfs_utils::count_files_in_directory(chunk); 377 510 let chunk_size = subfs_utils::estimate_directory_size(chunk); 378 511 379 - let chunk_manifest = crate::place_wisp::subfs::SubfsRecord::new() 512 + let chunk_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new() 380 513 .root(convert_fs_dir_to_subfs_dir(chunk.clone())) 381 514 .file_count(Some(chunk_file_count as i64)) 382 515 .created_at(Datetime::now()) ··· 399 532 // Each chunk reference MUST have flat: true to merge chunk contents 400 533 println!(" โ†’ Creating parent subfs with {} chunk references...", chunk_uris.len()); 401 534 use jacquard_common::CowStr; 402 - use crate::place_wisp::fs::{Subfs}; 535 + use wisp_lexicons::place_wisp::fs::{Subfs}; 403 536 404 537 // Convert to fs::Subfs (which has the 'flat' field) instead of subfs::Subfs 405 538 let parent_entries_fs: Vec<Entry> = chunk_uris.iter().enumerate().map(|(i, (uri, _))| { ··· 429 562 let parent_tid = Tid::now_0(); 430 563 let parent_rkey = parent_tid.to_string(); 431 564 432 - let parent_manifest = crate::place_wisp::subfs::SubfsRecord::new() 565 + let parent_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new() 433 566 .root(parent_root_subfs) 434 567 .file_count(Some(largest_dir.file_count as i64)) 435 568 .created_at(Datetime::now()) ··· 448 581 let subfs_tid = Tid::now_0(); 449 582 let subfs_rkey = subfs_tid.to_string(); 450 583 451 - let subfs_manifest = crate::place_wisp::subfs::SubfsRecord::new() 584 + let subfs_manifest = wisp_lexicons::place_wisp::subfs::SubfsRecord::new() 452 585 .root(convert_fs_dir_to_subfs_dir(largest_dir.directory.clone())) 453 586 .file_count(Some(largest_dir.file_count as i64)) 454 587 .created_at(Datetime::now()) ··· 603 736 dir_path: &'a Path, 604 737 existing_blobs: &'a HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, 605 738 current_path: String, 739 + ignore_matcher: &'a ignore_patterns::IgnoreMatcher, 740 + progress: &'a ProgressBar, 606 741 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = miette::Result<(Directory<'static>, usize, usize)>> + 'a>> 607 742 { 608 743 Box::pin(async move { ··· 623 758 .ok_or_else(|| miette::miette!("Invalid filename: {:?}", name))? 624 759 .to_string(); 625 760 626 - // Skip unwanted files and directories 761 + // Construct full path for ignore checking 762 + let full_path = if current_path.is_empty() { 763 + name_str.clone() 764 + } else { 765 + format!("{}/{}", current_path, name_str) 766 + }; 627 767 628 - // .git directory (version control - thousands of files) 629 - if name_str == ".git" { 630 - continue; 631 - } 632 - 633 - // .DS_Store (macOS metadata - can leak info) 634 - if name_str == ".DS_Store" { 635 - continue; 636 - } 637 - 638 - // .wisp.metadata.json (wisp internal metadata - should not be uploaded) 639 - if name_str == ".wisp.metadata.json" { 640 - continue; 641 - } 642 - 643 - // .env files (environment variables with secrets) 644 - if name_str.starts_with(".env") { 645 - continue; 646 - } 647 - 648 - // node_modules (dependency folder - can be 100,000+ files) 649 - if name_str == "node_modules" { 650 - continue; 651 - } 652 - 653 - // OS metadata files 654 - if name_str == "Thumbs.db" || name_str == "desktop.ini" || name_str.starts_with("._") { 655 - continue; 656 - } 657 - 658 - // macOS system directories 659 - if name_str == ".Spotlight-V100" || name_str == ".Trashes" || name_str == ".fseventsd" { 660 - continue; 661 - } 662 - 663 - // Cache and temp directories 664 - if name_str == ".cache" || name_str == ".temp" || name_str == ".tmp" { 665 - continue; 666 - } 667 - 668 - // Python cache 669 - if name_str == "__pycache__" || name_str.ends_with(".pyc") { 670 - continue; 671 - } 672 - 673 - // Python virtual environments 674 - if name_str == ".venv" || name_str == "venv" || name_str == "env" { 675 - continue; 676 - } 677 - 678 - // Editor swap files 679 - if name_str.ends_with(".swp") || name_str.ends_with(".swo") || name_str.ends_with("~") { 768 + // Skip files/directories that match ignore patterns 769 + if ignore_matcher.is_ignored(&full_path) || ignore_matcher.is_filename_ignored(&name_str) { 680 770 continue; 681 771 } 682 772 ··· 695 785 } 696 786 } 697 787 698 - // Process files concurrently with a limit of 5 788 + // Process files concurrently with a limit of 2 699 789 let file_results: Vec<(Entry<'static>, bool)> = stream::iter(file_tasks) 700 790 .map(|(name, path, full_path)| async move { 701 - 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); 702 793 let entry = Entry::new() 703 794 .name(CowStr::from(name)) 704 795 .node(EntryNode::File(Box::new(file_node))) 705 796 .build(); 706 797 Ok::<_, miette::Report>((entry, reused)) 707 798 }) 708 - .buffer_unordered(5) 799 + .buffer_unordered(MAX_CONCURRENT_UPLOADS) 709 800 .collect::<Vec<_>>() 710 801 .await 711 802 .into_iter() ··· 732 823 } else { 733 824 format!("{}/{}", current_path, name) 734 825 }; 735 - let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs, subdir_path).await?; 826 + let (subdir, sub_total, sub_reused) = build_directory(agent, &path, existing_blobs, subdir_path, ignore_matcher, progress).await?; 736 827 dir_entries.push(Entry::new() 737 828 .name(CowStr::from(name)) 738 829 .node(EntryNode::Directory(Box::new(subdir))) ··· 764 855 file_path: &Path, 765 856 file_path_key: &str, 766 857 existing_blobs: &HashMap<String, (jacquard_common::types::blob::BlobRef<'static>, String)>, 858 + progress: &ProgressBar, 767 859 ) -> miette::Result<(File<'static>, bool)> 768 860 { 769 861 // Read file ··· 803 895 if let Some((existing_blob_ref, existing_cid)) = existing_blob { 804 896 if existing_cid == &file_cid { 805 897 // CIDs match - reuse existing blob 806 - println!(" โœ“ Reusing blob for {} (CID: {})", file_path_key, file_cid); 898 + progress.set_message(format!("โœ“ Reused {}", file_path_key)); 807 899 let mut file_builder = File::new() 808 900 .r#type(CowStr::from("file")) 809 901 .blob(existing_blob_ref.clone()) ··· 817 909 } 818 910 819 911 return Ok((file_builder.build(), true)); 820 - } else { 821 - // CID mismatch - file changed 822 - println!(" โ†’ File changed: {} (old CID: {}, new CID: {})", file_path_key, existing_cid, file_cid); 823 - } 824 - } else { 825 - // File not in existing blob map 826 - if file_path_key.starts_with("imgs/") { 827 - println!(" โ†’ New file (not in blob map): {}", file_path_key); 828 912 } 829 913 } 830 914 ··· 835 919 MimeType::new_static("application/octet-stream") 836 920 }; 837 921 838 - 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)); 839 932 let blob = agent.upload_blob(upload_bytes, mime_type).await?; 933 + progress.set_message(format!("โœ“ Uploaded {}", file_path_key)); 840 934 841 935 let mut file_builder = File::new() 842 936 .r#type(CowStr::from("file")) ··· 855 949 856 950 /// Convert fs::Directory to subfs::Directory 857 951 /// They have the same structure, but different types 858 - fn convert_fs_dir_to_subfs_dir(fs_dir: place_wisp::fs::Directory<'static>) -> place_wisp::subfs::Directory<'static> { 859 - 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}; 860 954 861 955 let subfs_entries: Vec<SubfsEntry> = fs_dir.entries.into_iter().map(|entry| { 862 956 let node = match entry.node { 863 - place_wisp::fs::EntryNode::File(file) => { 957 + wisp_lexicons::place_wisp::fs::EntryNode::File(file) => { 864 958 SubfsEntryNode::File(Box::new(SubfsFile::new() 865 959 .r#type(file.r#type) 866 960 .blob(file.blob) ··· 869 963 .base64(file.base64) 870 964 .build())) 871 965 } 872 - place_wisp::fs::EntryNode::Directory(dir) => { 966 + wisp_lexicons::place_wisp::fs::EntryNode::Directory(dir) => { 873 967 SubfsEntryNode::Directory(Box::new(convert_fs_dir_to_subfs_dir(*dir))) 874 968 } 875 - place_wisp::fs::EntryNode::Subfs(subfs) => { 969 + wisp_lexicons::place_wisp::fs::EntryNode::Subfs(subfs) => { 876 970 // Nested subfs in the directory we're converting 877 971 // Note: subfs::Subfs doesn't have the 'flat' field - that's only in fs::Subfs 878 - SubfsEntryNode::Subfs(Box::new(place_wisp::subfs::Subfs::new() 972 + SubfsEntryNode::Subfs(Box::new(wisp_lexicons::place_wisp::subfs::Subfs::new() 879 973 .r#type(subfs.r#type) 880 974 .subject(subfs.subject) 881 975 .build())) 882 976 } 883 - place_wisp::fs::EntryNode::Unknown(unknown) => { 977 + wisp_lexicons::place_wisp::fs::EntryNode::Unknown(unknown) => { 884 978 SubfsEntryNode::Unknown(unknown) 885 979 } 886 980 };
-9
cli/src/mod.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 - pub mod builder_types; 7 - 8 - #[cfg(feature = "place_wisp")] 9 - pub mod place_wisp;
-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
··· 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
··· 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
··· 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 - pub mod fs; 7 - pub mod settings; 8 - pub mod subfs;
+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 use crate::pull::pull_site; 2 2 use crate::redirects::{load_redirect_rules, match_redirect_rule, RedirectRule}; 3 - use crate::place_wisp::settings::Settings; 3 + use wisp_lexicons::place_wisp::settings::Settings; 4 4 use axum::{ 5 5 Router, 6 6 extract::Request,
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 27 27 "@atproto/api": "^0.14.1", 28 28 "@wisp/lexicons": "workspace:*", 29 29 "multiformats": "^13.3.1" 30 + }, 31 + "devDependencies": { 32 + "@atproto/lexicon": "^0.5.2" 30 33 } 31 34 }
+1 -1
packages/@wisp/constants/src/index.ts
··· 14 14 15 15 // File size limits 16 16 export const MAX_SITE_SIZE = 300 * 1024 * 1024; // 300MB 17 - export const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB 17 + export const MAX_FILE_SIZE = 200 * 1024 * 1024; // 200MB 18 18 export const MAX_FILE_COUNT = 1000; 19 19 20 20 // Cache configuration
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 7 7 ValidationError, 8 8 type ValidationResult, 9 9 } from '@atproto/lexicon' 10 - import { type $Typed, is$typed, maybe$typed } from './util' 10 + import { type $Typed, is$typed, maybe$typed } from './util.js' 11 11 12 12 export const schemaDict = { 13 13 PlaceWispFs: {
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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. */