A fullstack app for indexing standard.site documents
8
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: init

Steve a3e32071

+1813
+30
.gitignore
··· 1 + # Dependencies 2 + node_modules/ 3 + bun.lockb 4 + 5 + # Build outputs 6 + dist/ 7 + *.tsbuildinfo 8 + 9 + # Environment files 10 + .env 11 + .env.local 12 + .dev.vars 13 + 14 + # Wrangler 15 + .wrangler/ 16 + 17 + # IDE 18 + .vscode/ 19 + .idea/ 20 + *.swp 21 + *.swo 22 + *~ 23 + 24 + # OS 25 + .DS_Store 26 + Thumbs.db 27 + 28 + # Logs 29 + *.log 30 + npm-debug.log*
+211
README.md
··· 1 + # AT Feeds 2 + 3 + A monorepo for indexing and displaying [Standard.site](https://standard.site) documents from the AT Protocol, powered by Cloudflare Workers, D1, and Queues. 4 + 5 + ## Architecture 6 + 7 + ``` 8 + ┌─────────────────────────────────────────────────────────────┐ 9 + │ Cloudflare │ 10 + ├─────────────────────────────────────────────────────────────┤ 11 + │ │ 12 + │ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │ 13 + │ │ Pages │────▶│ Worker │────▶│ D1 │ │ 14 + │ │ (Client) │ │ (API) │ │ (Database) │ │ 15 + │ └──────────────┘ └──────────────┘ └─────────────┘ │ 16 + │ ▲ ▲ │ 17 + │ │ │ │ 18 + │ ┌──────┴───────┐ ┌──────┴───────┐ │ 19 + │ │ Queue │ │ Cron │ │ 20 + │ │ (Resolver) │ │ (Refresh) │ │ 21 + │ └──────┬───────┘ └──────────────┘ │ 22 + │ │ │ 23 + └──────────────────────────────┼──────────────────────────────┘ 24 + │ POST /webhook/tap 25 + ┌──────────┴───────────┐ 26 + │ Tap Instance │ 27 + │ (External VPS) │ 28 + └──────────────────────┘ 29 + ``` 30 + 31 + **Components:** 32 + 33 + 1. **Tap Indexer** (External) - Subscribes to the AT Protocol firehose and sends webhook events 34 + 2. **Server** (`packages/server`) - Cloudflare Worker with Hono API, D1 database, and Queue consumer 35 + 3. **Client** (`packages/client`) - Vite + React app deployed to Cloudflare Pages 36 + 37 + ## Quick Start 38 + 39 + ### Prerequisites 40 + 41 + - [Bun](https://bun.sh) installed 42 + - [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) installed and authenticated 43 + - A tap instance running somewhere (VPS, Fly.io, etc.) 44 + 45 + ### Setup 46 + 47 + 1. Install dependencies: 48 + 49 + ```bash 50 + bun install 51 + ``` 52 + 53 + 2. Create the D1 database: 54 + 55 + ```bash 56 + bun run db:create 57 + ``` 58 + 59 + Copy the database ID and update `packages/server/wrangler.toml`. 60 + 61 + 3. Create the queue: 62 + 63 + ```bash 64 + wrangler queues create document-resolution 65 + ``` 66 + 67 + 4. Run database migrations: 68 + 69 + ```bash 70 + # Local development 71 + bun run db:migrate 72 + 73 + # Production 74 + bun run db:migrate:prod 75 + ``` 76 + 77 + 5. (Optional) Set webhook secret: 78 + 79 + ```bash 80 + bun run secret:set 81 + ``` 82 + 83 + 6. Deploy the worker: 84 + 85 + ```bash 86 + bun run deploy 87 + ``` 88 + 89 + 7. Configure your tap instance: 90 + 91 + ```bash 92 + TAP_WEBHOOK_URL=https://your-worker.workers.dev/webhook/tap 93 + TAP_SIGNAL_COLLECTION=site.standard.document 94 + TAP_COLLECTION_FILTERS=site.standard.document 95 + ``` 96 + 97 + 8. Trigger initial resolution of existing records: 98 + 99 + ```bash 100 + curl -X POST https://your-worker.workers.dev/admin/resolve-all 101 + ``` 102 + 103 + ## Local Development 104 + 105 + 1. Start the worker locally: 106 + 107 + ```bash 108 + bun run dev:server 109 + ``` 110 + 111 + The API will run on `http://localhost:8787`. 112 + 113 + 2. Start the client (in a separate terminal): 114 + 115 + ```bash 116 + bun run dev:client 117 + ``` 118 + 119 + The client will run on `http://localhost:5173`. 120 + 121 + ## API Endpoints 122 + 123 + ### Health & Stats 124 + 125 + | Endpoint | Method | Description | 126 + |----------|--------|-------------| 127 + | `/health` | GET | Health check | 128 + | `/stats` | GET | Database statistics | 129 + 130 + ### Feed Endpoints 131 + 132 + | Endpoint | Method | Description | 133 + |----------|--------|-------------| 134 + | `/feed` | GET | Pre-resolved documents (fast) | 135 + | `/feed-raw` | GET | Raw record references (for client-side resolution) | 136 + | `/records/:did` | GET | Records by DID | 137 + 138 + ### Webhook 139 + 140 + | Endpoint | Method | Description | 141 + |----------|--------|-------------| 142 + | `/webhook/tap` | POST | Receives events from tap | 143 + | `/webhook/tap/debug` | POST | Debug endpoint (echoes payload) | 144 + 145 + ### Admin 146 + 147 + | Endpoint | Method | Description | 148 + |----------|--------|-------------| 149 + | `/admin/resolve-all` | POST | Queue unresolved records for processing | 150 + 151 + ## How It Works 152 + 153 + 1. **Tap** subscribes to the AT Protocol firehose and filters for `site.standard.document` records 154 + 2. **Webhook** receives events and stores record references in D1, then pushes to the resolution queue 155 + 3. **Queue consumer** resolves each document (PDS lookup → record fetch → publication URL) and stores in `resolved_documents` 156 + 4. **Cron job** (every 15 min) refreshes stale documents and processes any missed records 157 + 5. **`/feed` endpoint** reads directly from `resolved_documents` for instant responses 158 + 159 + ## Project Structure 160 + 161 + ``` 162 + . 163 + ├── package.json # Root workspace config 164 + └── packages/ 165 + ├── server/ # Cloudflare Worker 166 + │ ├── wrangler.toml # Worker configuration 167 + │ ├── schema.sql # D1 database schema 168 + │ ├── package.json 169 + │ └── src/ 170 + │ └── index.ts # API + Queue consumer + Cron handler 171 + └── client/ # Vite + React app 172 + ├── package.json 173 + ├── vite.config.ts 174 + └── src/ 175 + ├── main.tsx 176 + └── App.tsx 177 + ``` 178 + 179 + ## Scripts 180 + 181 + ```bash 182 + # Development 183 + bun run dev # Run all packages in dev mode 184 + bun run dev:server # Run worker locally 185 + bun run dev:client # Run client locally 186 + 187 + # Deployment 188 + bun run deploy # Deploy worker to Cloudflare 189 + bun run deploy:client # Deploy client to Cloudflare Pages 190 + 191 + # Database 192 + bun run db:create # Create D1 database 193 + bun run db:migrate # Run migrations (local) 194 + bun run db:migrate:prod # Run migrations (production) 195 + 196 + # Secrets 197 + bun run secret:set # Set TAP_WEBHOOK_SECRET 198 + ``` 199 + 200 + ## Resources 201 + 202 + - [tap Documentation](https://github.com/bluesky-social/indigo/tree/main/cmd/tap) 203 + - [AT Protocol Specs](https://atproto.com/) 204 + - [Cloudflare Workers](https://developers.cloudflare.com/workers/) 205 + - [Cloudflare D1](https://developers.cloudflare.com/d1/) 206 + - [Cloudflare Queues](https://developers.cloudflare.com/queues/) 207 + - [Hono Documentation](https://hono.dev/) 208 + 209 + ## License 210 + 211 + MIT
+494
bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "atfeeds", 7 + }, 8 + "packages/client": { 9 + "name": "@atfeeds/client", 10 + "version": "1.0.0", 11 + "dependencies": { 12 + "react": "^18.2.0", 13 + "react-dom": "^18.2.0", 14 + }, 15 + "devDependencies": { 16 + "@types/react": "^18.2.0", 17 + "@types/react-dom": "^18.2.0", 18 + "@vitejs/plugin-react": "^4.0.0", 19 + "typescript": "^5.0.0", 20 + "vite": "^5.0.0", 21 + }, 22 + }, 23 + "packages/cloudflare": { 24 + "name": "atfeeds-cloudflare", 25 + "version": "1.0.0", 26 + "dependencies": { 27 + "hono": "^4.0.0", 28 + }, 29 + "devDependencies": { 30 + "@cloudflare/workers-types": "^4.20240117.0", 31 + "wrangler": "^3.0.0", 32 + }, 33 + }, 34 + "packages/server": { 35 + "name": "@atfeeds/server", 36 + "version": "1.0.0", 37 + "dependencies": { 38 + "hono": "^4.0.0", 39 + }, 40 + "devDependencies": { 41 + "@types/bun": "^1.0.0", 42 + }, 43 + }, 44 + }, 45 + "packages": { 46 + "@atfeeds/client": ["@atfeeds/client@workspace:packages/client"], 47 + 48 + "@atfeeds/server": ["@atfeeds/server@workspace:packages/server"], 49 + 50 + "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], 51 + 52 + "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], 53 + 54 + "@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], 55 + 56 + "@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], 57 + 58 + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], 59 + 60 + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], 61 + 62 + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], 63 + 64 + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], 65 + 66 + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], 67 + 68 + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], 69 + 70 + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], 71 + 72 + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], 73 + 74 + "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], 75 + 76 + "@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], 77 + 78 + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], 79 + 80 + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], 81 + 82 + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], 83 + 84 + "@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], 85 + 86 + "@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], 87 + 88 + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.3.4", "", { "dependencies": { "mime": "^3.0.0" } }, "sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q=="], 89 + 90 + "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.0.2", "", { "peerDependencies": { "unenv": "2.0.0-rc.14", "workerd": "^1.20250124.0" }, "optionalPeers": ["workerd"] }, "sha512-nyzYnlZjjV5xT3LizahG1Iu6mnrCaxglJ04rZLpDwlDVDZ7v46lNsfxhV3A/xtfgQuSHmLnc6SVI+KwBpc3Lwg=="], 91 + 92 + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20250718.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-FHf4t7zbVN8yyXgQ/r/GqLPaYZSGUVzeR7RnL28Mwj2djyw2ZergvytVc7fdGcczl6PQh+VKGfZCfUqpJlbi9g=="], 93 + 94 + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20250718.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fUiyUJYyqqp4NqJ0YgGtp4WJh/II/YZsUnEb6vVy5Oeas8lUOxnN+ZOJ8N/6/5LQCVAtYCChRiIrBbfhTn5Z8Q=="], 95 + 96 + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20250718.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5+eb3rtJMiEwp08Kryqzzu8d1rUcK+gdE442auo5eniMpT170Dz0QxBrqkg2Z48SFUPYbj+6uknuA5tzdRSUSg=="], 97 + 98 + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20250718.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Aa2M/DVBEBQDdATMbn217zCSFKE+ud/teS+fFS+OQqKABLn0azO2qq6ANAHYOIE6Q3Sq4CxDIQr8lGdaJHwUog=="], 99 + 100 + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250718.0", "", { "os": "win32", "cpu": "x64" }, "sha512-dY16RXKffmugnc67LTbyjdDHZn5NoTF1yHEf2fN4+OaOnoGSp3N1x77QubTDwqZ9zECWxgQfDLjddcH8dWeFhg=="], 101 + 102 + "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260111.0", "", {}, "sha512-NFA2U+AqEWHkAmw6oRzNWJyc14rIvBlF/OlK3lixokunRKwyziuON07nWUZ0w0kKWlW4fJ/muA09tEUaQY07tA=="], 103 + 104 + "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], 105 + 106 + "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], 107 + 108 + "@esbuild-plugins/node-globals-polyfill": ["@esbuild-plugins/node-globals-polyfill@0.2.3", "", { "peerDependencies": { "esbuild": "*" } }, "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw=="], 109 + 110 + "@esbuild-plugins/node-modules-polyfill": ["@esbuild-plugins/node-modules-polyfill@0.2.2", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "rollup-plugin-node-polyfills": "^0.2.1" }, "peerDependencies": { "esbuild": "*" } }, "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA=="], 111 + 112 + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], 113 + 114 + "@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], 115 + 116 + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], 117 + 118 + "@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], 119 + 120 + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], 121 + 122 + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], 123 + 124 + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], 125 + 126 + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], 127 + 128 + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], 129 + 130 + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], 131 + 132 + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], 133 + 134 + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], 135 + 136 + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], 137 + 138 + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], 139 + 140 + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], 141 + 142 + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], 143 + 144 + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], 145 + 146 + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], 147 + 148 + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], 149 + 150 + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], 151 + 152 + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], 153 + 154 + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], 155 + 156 + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], 157 + 158 + "@fastify/busboy": ["@fastify/busboy@2.1.1", "", {}, "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA=="], 159 + 160 + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], 161 + 162 + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], 163 + 164 + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], 165 + 166 + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], 167 + 168 + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], 169 + 170 + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], 171 + 172 + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="], 173 + 174 + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], 175 + 176 + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], 177 + 178 + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], 179 + 180 + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], 181 + 182 + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], 183 + 184 + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="], 185 + 186 + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], 187 + 188 + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], 189 + 190 + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], 191 + 192 + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="], 193 + 194 + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="], 195 + 196 + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], 197 + 198 + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], 199 + 200 + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], 201 + 202 + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], 203 + 204 + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], 205 + 206 + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], 207 + 208 + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], 209 + 210 + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.1", "", { "os": "android", "cpu": "arm" }, "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg=="], 211 + 212 + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg=="], 213 + 214 + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg=="], 215 + 216 + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ=="], 217 + 218 + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg=="], 219 + 220 + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw=="], 221 + 222 + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ=="], 223 + 224 + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg=="], 225 + 226 + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ=="], 227 + 228 + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA=="], 229 + 230 + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g=="], 231 + 232 + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw=="], 233 + 234 + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw=="], 235 + 236 + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw=="], 237 + 238 + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw=="], 239 + 240 + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg=="], 241 + 242 + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg=="], 243 + 244 + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg=="], 245 + 246 + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w=="], 247 + 248 + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg=="], 249 + 250 + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.1", "", { "os": "none", "cpu": "arm64" }, "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw=="], 251 + 252 + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g=="], 253 + 254 + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA=="], 255 + 256 + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg=="], 257 + 258 + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw=="], 259 + 260 + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], 261 + 262 + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], 263 + 264 + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], 265 + 266 + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], 267 + 268 + "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="], 269 + 270 + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], 271 + 272 + "@types/node": ["@types/node@25.0.6", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-NNu0sjyNxpoiW3YuVFfNz7mxSQ+S4X2G28uqg2s+CzoqoQjLPsWSbsFFyztIAqt2vb8kfEAsJNepMGPTxFDx3Q=="], 273 + 274 + "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], 275 + 276 + "@types/react": ["@types/react@18.3.27", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w=="], 277 + 278 + "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], 279 + 280 + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], 281 + 282 + "acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], 283 + 284 + "acorn-walk": ["acorn-walk@8.3.2", "", {}, "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A=="], 285 + 286 + "as-table": ["as-table@1.0.55", "", { "dependencies": { "printable-characters": "^1.0.42" } }, "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ=="], 287 + 288 + "atfeeds-cloudflare": ["atfeeds-cloudflare@workspace:packages/cloudflare"], 289 + 290 + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.14", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg=="], 291 + 292 + "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], 293 + 294 + "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], 295 + 296 + "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], 297 + 298 + "caniuse-lite": ["caniuse-lite@1.0.30001764", "", {}, "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g=="], 299 + 300 + "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], 301 + 302 + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], 303 + 304 + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], 305 + 306 + "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="], 307 + 308 + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], 309 + 310 + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 311 + 312 + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], 313 + 314 + "data-uri-to-buffer": ["data-uri-to-buffer@2.0.2", "", {}, "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA=="], 315 + 316 + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 317 + 318 + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], 319 + 320 + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 321 + 322 + "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], 323 + 324 + "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], 325 + 326 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 327 + 328 + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], 329 + 330 + "estree-walker": ["estree-walker@0.6.1", "", {}, "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w=="], 331 + 332 + "exit-hook": ["exit-hook@2.2.1", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="], 333 + 334 + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], 335 + 336 + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 337 + 338 + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], 339 + 340 + "get-source": ["get-source@2.0.12", "", { "dependencies": { "data-uri-to-buffer": "^2.0.0", "source-map": "^0.6.1" } }, "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w=="], 341 + 342 + "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], 343 + 344 + "hono": ["hono@4.11.3", "", {}, "sha512-PmQi306+M/ct/m5s66Hrg+adPnkD5jiO6IjA7WhWw0gSBSo1EcRegwuI1deZ+wd5pzCGynCcn2DprnE4/yEV4w=="], 345 + 346 + "is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="], 347 + 348 + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], 349 + 350 + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], 351 + 352 + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], 353 + 354 + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], 355 + 356 + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], 357 + 358 + "magic-string": ["magic-string@0.25.9", "", { "dependencies": { "sourcemap-codec": "^1.4.8" } }, "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="], 359 + 360 + "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], 361 + 362 + "miniflare": ["miniflare@3.20250718.3", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "acorn": "8.14.0", "acorn-walk": "8.3.2", "exit-hook": "2.2.1", "glob-to-regexp": "0.4.1", "stoppable": "1.1.0", "undici": "^5.28.5", "workerd": "1.20250718.0", "ws": "8.18.0", "youch": "3.3.4", "zod": "3.22.3" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-JuPrDJhwLrNLEJiNLWO7ZzJrv/Vv9kZuwMYCfv0LskQDM6Eonw4OvywO3CH/wCGjgHzha/qyjUh8JQ068TjDgQ=="], 363 + 364 + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 365 + 366 + "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="], 367 + 368 + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 369 + 370 + "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], 371 + 372 + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], 373 + 374 + "path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="], 375 + 376 + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], 377 + 378 + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 379 + 380 + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], 381 + 382 + "printable-characters": ["printable-characters@1.0.42", "", {}, "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ=="], 383 + 384 + "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], 385 + 386 + "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], 387 + 388 + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], 389 + 390 + "rollup": ["rollup@4.55.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.1", "@rollup/rollup-android-arm64": "4.55.1", "@rollup/rollup-darwin-arm64": "4.55.1", "@rollup/rollup-darwin-x64": "4.55.1", "@rollup/rollup-freebsd-arm64": "4.55.1", "@rollup/rollup-freebsd-x64": "4.55.1", "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", "@rollup/rollup-linux-arm-musleabihf": "4.55.1", "@rollup/rollup-linux-arm64-gnu": "4.55.1", "@rollup/rollup-linux-arm64-musl": "4.55.1", "@rollup/rollup-linux-loong64-gnu": "4.55.1", "@rollup/rollup-linux-loong64-musl": "4.55.1", "@rollup/rollup-linux-ppc64-gnu": "4.55.1", "@rollup/rollup-linux-ppc64-musl": "4.55.1", "@rollup/rollup-linux-riscv64-gnu": "4.55.1", "@rollup/rollup-linux-riscv64-musl": "4.55.1", "@rollup/rollup-linux-s390x-gnu": "4.55.1", "@rollup/rollup-linux-x64-gnu": "4.55.1", "@rollup/rollup-linux-x64-musl": "4.55.1", "@rollup/rollup-openbsd-x64": "4.55.1", "@rollup/rollup-openharmony-arm64": "4.55.1", "@rollup/rollup-win32-arm64-msvc": "4.55.1", "@rollup/rollup-win32-ia32-msvc": "4.55.1", "@rollup/rollup-win32-x64-gnu": "4.55.1", "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A=="], 391 + 392 + "rollup-plugin-inject": ["rollup-plugin-inject@3.0.2", "", { "dependencies": { "estree-walker": "^0.6.1", "magic-string": "^0.25.3", "rollup-pluginutils": "^2.8.1" } }, "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w=="], 393 + 394 + "rollup-plugin-node-polyfills": ["rollup-plugin-node-polyfills@0.2.1", "", { "dependencies": { "rollup-plugin-inject": "^3.0.0" } }, "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA=="], 395 + 396 + "rollup-pluginutils": ["rollup-pluginutils@2.8.2", "", { "dependencies": { "estree-walker": "^0.6.1" } }, "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ=="], 397 + 398 + "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], 399 + 400 + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], 401 + 402 + "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], 403 + 404 + "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="], 405 + 406 + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], 407 + 408 + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 409 + 410 + "sourcemap-codec": ["sourcemap-codec@1.4.8", "", {}, "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="], 411 + 412 + "stacktracey": ["stacktracey@2.1.8", "", { "dependencies": { "as-table": "^1.0.36", "get-source": "^2.0.12" } }, "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw=="], 413 + 414 + "stoppable": ["stoppable@1.1.0", "", {}, "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw=="], 415 + 416 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 417 + 418 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 419 + 420 + "ufo": ["ufo@1.6.2", "", {}, "sha512-heMioaxBcG9+Znsda5Q8sQbWnLJSl98AFDXTO80wELWEzX3hordXsTdxrIfMQoO9IY1MEnoGoPjpoKpMj+Yx0Q=="], 421 + 422 + "undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], 423 + 424 + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], 425 + 426 + "unenv": ["unenv@2.0.0-rc.14", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.1", "ohash": "^2.0.10", "pathe": "^2.0.3", "ufo": "^1.5.4" } }, "sha512-od496pShMen7nOy5VmVJCnq8rptd45vh6Nx/r2iPbrba6pa6p+tS2ywuIHRZ/OBvSbQZB0kWvpO9XBNVFXHD3Q=="], 427 + 428 + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], 429 + 430 + "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], 431 + 432 + "workerd": ["workerd@1.20250718.0", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20250718.0", "@cloudflare/workerd-darwin-arm64": "1.20250718.0", "@cloudflare/workerd-linux-64": "1.20250718.0", "@cloudflare/workerd-linux-arm64": "1.20250718.0", "@cloudflare/workerd-windows-64": "1.20250718.0" }, "bin": { "workerd": "bin/workerd" } }, "sha512-kqkIJP/eOfDlUyBzU7joBg+tl8aB25gEAGqDap+nFWb+WHhnooxjGHgxPBy3ipw2hnShPFNOQt5lFRxbwALirg=="], 433 + 434 + "wrangler": ["wrangler@3.114.16", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.3.4", "@cloudflare/unenv-preset": "2.0.2", "@esbuild-plugins/node-globals-polyfill": "0.2.3", "@esbuild-plugins/node-modules-polyfill": "0.2.2", "blake3-wasm": "2.1.5", "esbuild": "0.17.19", "miniflare": "3.20250718.3", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.14", "workerd": "1.20250718.0" }, "optionalDependencies": { "fsevents": "~2.3.2", "sharp": "^0.33.5" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20250408.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-ve/ULRjrquu5BHNJ+1T0ipJJlJ6pD7qLmhwRkk0BsUIxatNe4HP4odX/R4Mq/RHG6LOnVAFs7SMeSHlz/1mNlQ=="], 435 + 436 + "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], 437 + 438 + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], 439 + 440 + "youch": ["youch@3.3.4", "", { "dependencies": { "cookie": "^0.7.1", "mustache": "^4.2.0", "stacktracey": "^2.1.8" } }, "sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg=="], 441 + 442 + "zod": ["zod@3.22.3", "", {}, "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug=="], 443 + 444 + "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], 445 + 446 + "sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], 447 + 448 + "wrangler/esbuild": ["esbuild@0.17.19", "", { "optionalDependencies": { "@esbuild/android-arm": "0.17.19", "@esbuild/android-arm64": "0.17.19", "@esbuild/android-x64": "0.17.19", "@esbuild/darwin-arm64": "0.17.19", "@esbuild/darwin-x64": "0.17.19", "@esbuild/freebsd-arm64": "0.17.19", "@esbuild/freebsd-x64": "0.17.19", "@esbuild/linux-arm": "0.17.19", "@esbuild/linux-arm64": "0.17.19", "@esbuild/linux-ia32": "0.17.19", "@esbuild/linux-loong64": "0.17.19", "@esbuild/linux-mips64el": "0.17.19", "@esbuild/linux-ppc64": "0.17.19", "@esbuild/linux-riscv64": "0.17.19", "@esbuild/linux-s390x": "0.17.19", "@esbuild/linux-x64": "0.17.19", "@esbuild/netbsd-x64": "0.17.19", "@esbuild/openbsd-x64": "0.17.19", "@esbuild/sunos-x64": "0.17.19", "@esbuild/win32-arm64": "0.17.19", "@esbuild/win32-ia32": "0.17.19", "@esbuild/win32-x64": "0.17.19" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw=="], 449 + 450 + "wrangler/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.17.19", "", { "os": "android", "cpu": "arm" }, "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A=="], 451 + 452 + "wrangler/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.17.19", "", { "os": "android", "cpu": "arm64" }, "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA=="], 453 + 454 + "wrangler/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.17.19", "", { "os": "android", "cpu": "x64" }, "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww=="], 455 + 456 + "wrangler/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.17.19", "", { "os": "darwin", "cpu": "arm64" }, "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg=="], 457 + 458 + "wrangler/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.17.19", "", { "os": "darwin", "cpu": "x64" }, "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw=="], 459 + 460 + "wrangler/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.17.19", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ=="], 461 + 462 + "wrangler/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.17.19", "", { "os": "freebsd", "cpu": "x64" }, "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ=="], 463 + 464 + "wrangler/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.17.19", "", { "os": "linux", "cpu": "arm" }, "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA=="], 465 + 466 + "wrangler/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.17.19", "", { "os": "linux", "cpu": "arm64" }, "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg=="], 467 + 468 + "wrangler/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.17.19", "", { "os": "linux", "cpu": "ia32" }, "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ=="], 469 + 470 + "wrangler/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ=="], 471 + 472 + "wrangler/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A=="], 473 + 474 + "wrangler/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.17.19", "", { "os": "linux", "cpu": "ppc64" }, "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg=="], 475 + 476 + "wrangler/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.17.19", "", { "os": "linux", "cpu": "none" }, "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA=="], 477 + 478 + "wrangler/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.17.19", "", { "os": "linux", "cpu": "s390x" }, "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q=="], 479 + 480 + "wrangler/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.17.19", "", { "os": "linux", "cpu": "x64" }, "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw=="], 481 + 482 + "wrangler/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.17.19", "", { "os": "none", "cpu": "x64" }, "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q=="], 483 + 484 + "wrangler/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.17.19", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g=="], 485 + 486 + "wrangler/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.17.19", "", { "os": "sunos", "cpu": "x64" }, "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg=="], 487 + 488 + "wrangler/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.17.19", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag=="], 489 + 490 + "wrangler/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.17.19", "", { "os": "win32", "cpu": "ia32" }, "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw=="], 491 + 492 + "wrangler/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.17.19", "", { "os": "win32", "cpu": "x64" }, "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA=="], 493 + } 494 + }
+19
package.json
··· 1 + { 2 + "name": "atfeeds", 3 + "version": "1.0.0", 4 + "private": true, 5 + "workspaces": [ 6 + "packages/*" 7 + ], 8 + "scripts": { 9 + "dev": "bun run --filter '*' dev", 10 + "dev:server": "cd packages/server && npm run dev", 11 + "dev:client": "cd packages/client && npm run dev", 12 + "deploy": "cd packages/server && npm run deploy", 13 + "deploy:client": "cd packages/client && npm run pages:deploy", 14 + "db:create": "cd packages/server && npm run db:create", 15 + "db:migrate": "cd packages/server && npm run db:migrate", 16 + "db:migrate:prod": "cd packages/server && npm run db:migrate:prod", 17 + "secret:set": "cd packages/server && npm run secret:set" 18 + } 19 + }
+12
packages/client/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 + <title>AT Feeds - Standard.site Documents</title> 7 + </head> 8 + <body> 9 + <div id="root"></div> 10 + <script type="module" src="/src/main.tsx"></script> 11 + </body> 12 + </html>
+22
packages/client/package.json
··· 1 + { 2 + "name": "@atfeeds/client", 3 + "version": "1.0.0", 4 + "type": "module", 5 + "scripts": { 6 + "dev": "vite", 7 + "build": "tsc && vite build", 8 + "preview": "vite preview", 9 + "pages:deploy": "vite build && wrangler pages deploy dist --project-name=atfeeds" 10 + }, 11 + "dependencies": { 12 + "react": "^18.2.0", 13 + "react-dom": "^18.2.0" 14 + }, 15 + "devDependencies": { 16 + "@types/react": "^18.2.0", 17 + "@types/react-dom": "^18.2.0", 18 + "@vitejs/plugin-react": "^4.0.0", 19 + "typescript": "^5.0.0", 20 + "vite": "^5.0.0" 21 + } 22 + }
+128
packages/client/src/App.tsx
··· 1 + import { useEffect, useState } from "react"; 2 + 3 + // API base URL - empty for same-origin (local dev), or set via env var for production 4 + const API_URL = "https://atfeeds-api.stevedsimkins.workers.dev"; 5 + 6 + interface Document { 7 + uri: string; 8 + did: string; 9 + rkey: string; 10 + title: string; 11 + path: string | null; 12 + site: string | null; 13 + content: { 14 + $type: string; 15 + markdown?: string; 16 + } | null; 17 + textContent: string | null; 18 + publishedAt: string | null; 19 + viewUrl: string | null; 20 + } 21 + 22 + interface FeedResponse { 23 + count: number; 24 + limit: number; 25 + offset: number; 26 + documents: Document[]; 27 + } 28 + 29 + function App() { 30 + const [documents, setDocuments] = useState<Document[]>([]); 31 + const [loading, setLoading] = useState(true); 32 + const [error, setError] = useState<string | null>(null); 33 + 34 + useEffect(() => { 35 + async function fetchFeed() { 36 + try { 37 + const response = await fetch(`${API_URL}/feed`); 38 + if (!response.ok) { 39 + throw new Error("Failed to fetch feed"); 40 + } 41 + const data: FeedResponse = await response.json(); 42 + setDocuments(data.documents); 43 + } catch (err) { 44 + setError(err instanceof Error ? err.message : "Unknown error"); 45 + } finally { 46 + setLoading(false); 47 + } 48 + } 49 + 50 + fetchFeed(); 51 + }, []); 52 + 53 + const formatDate = (dateString: string | null) => { 54 + if (!dateString) return "Unknown date"; 55 + return new Date(dateString).toLocaleDateString("en-US", { 56 + year: "numeric", 57 + month: "long", 58 + day: "numeric", 59 + }); 60 + }; 61 + 62 + const truncateText = (text: string | null, maxLength: number = 200) => { 63 + if (!text) return ""; 64 + if (text.length <= maxLength) return text; 65 + return text.slice(0, maxLength) + "..."; 66 + }; 67 + 68 + if (loading) { 69 + return ( 70 + <div className="container"> 71 + <h1>Standard.site Documents</h1> 72 + <p className="loading">Loading documents...</p> 73 + </div> 74 + ); 75 + } 76 + 77 + if (error) { 78 + return ( 79 + <div className="container"> 80 + <h1>Standard.site Documents</h1> 81 + <p className="error">Error: {error}</p> 82 + </div> 83 + ); 84 + } 85 + 86 + return ( 87 + <div className="container"> 88 + <h1>Standard.site Documents</h1> 89 + <p className="subtitle">{documents.length} documents found</p> 90 + 91 + <div className="feed"> 92 + {documents.map((doc) => ( 93 + <article key={doc.uri} className="document-card"> 94 + <h2> 95 + {doc.viewUrl ? ( 96 + <a href={doc.viewUrl} target="_blank" rel="noopener noreferrer"> 97 + {doc.title} 98 + </a> 99 + ) : ( 100 + doc.title 101 + )} 102 + </h2> 103 + <time dateTime={doc.publishedAt || undefined}> 104 + {formatDate(doc.publishedAt)} 105 + </time> 106 + {doc.textContent && ( 107 + <p className="excerpt">{truncateText(doc.textContent)}</p> 108 + )} 109 + {doc.viewUrl && ( 110 + <a 111 + href={doc.viewUrl} 112 + target="_blank" 113 + rel="noopener noreferrer" 114 + className="read-more" 115 + > 116 + Read on author's site 117 + </a> 118 + )} 119 + </article> 120 + ))} 121 + </div> 122 + 123 + {documents.length === 0 && <p className="empty">No documents found.</p>} 124 + </div> 125 + ); 126 + } 127 + 128 + export default App;
+92
packages/client/src/index.css
··· 1 + * { 2 + box-sizing: border-box; 3 + margin: 0; 4 + padding: 0; 5 + } 6 + 7 + body { 8 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, 9 + Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 10 + line-height: 1.6; 11 + color: #333; 12 + background: #f5f5f5; 13 + } 14 + 15 + .container { 16 + max-width: 800px; 17 + margin: 0 auto; 18 + padding: 2rem 1rem; 19 + } 20 + 21 + h1 { 22 + font-size: 2rem; 23 + margin-bottom: 0.5rem; 24 + } 25 + 26 + .subtitle { 27 + color: #666; 28 + margin-bottom: 2rem; 29 + } 30 + 31 + .loading, 32 + .error, 33 + .empty { 34 + text-align: center; 35 + padding: 2rem; 36 + color: #666; 37 + } 38 + 39 + .error { 40 + color: #c00; 41 + } 42 + 43 + .feed { 44 + display: flex; 45 + flex-direction: column; 46 + gap: 1.5rem; 47 + } 48 + 49 + .document-card { 50 + background: white; 51 + border-radius: 8px; 52 + padding: 1.5rem; 53 + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); 54 + } 55 + 56 + .document-card h2 { 57 + font-size: 1.25rem; 58 + margin-bottom: 0.5rem; 59 + } 60 + 61 + .document-card h2 a { 62 + color: #1a1a1a; 63 + text-decoration: none; 64 + } 65 + 66 + .document-card h2 a:hover { 67 + color: #0066cc; 68 + text-decoration: underline; 69 + } 70 + 71 + .document-card time { 72 + display: block; 73 + font-size: 0.875rem; 74 + color: #666; 75 + margin-bottom: 0.75rem; 76 + } 77 + 78 + .document-card .excerpt { 79 + color: #444; 80 + margin-bottom: 1rem; 81 + } 82 + 83 + .document-card .read-more { 84 + display: inline-block; 85 + color: #0066cc; 86 + text-decoration: none; 87 + font-size: 0.875rem; 88 + } 89 + 90 + .document-card .read-more:hover { 91 + text-decoration: underline; 92 + }
+10
packages/client/src/main.tsx
··· 1 + import React from "react"; 2 + import ReactDOM from "react-dom/client"; 3 + import App from "./App"; 4 + import "./index.css"; 5 + 6 + ReactDOM.createRoot(document.getElementById("root")!).render( 7 + <React.StrictMode> 8 + <App /> 9 + </React.StrictMode>, 10 + );
+9
packages/client/src/vite-env.d.ts
··· 1 + /// <reference types="vite/client" /> 2 + 3 + interface ImportMetaEnv { 4 + readonly VITE_API_URL: string 5 + } 6 + 7 + interface ImportMeta { 8 + readonly env: ImportMetaEnv 9 + }
+21
packages/client/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "useDefineForClassFields": true, 5 + "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 + "module": "ESNext", 7 + "skipLibCheck": true, 8 + "moduleResolution": "bundler", 9 + "allowImportingTsExtensions": true, 10 + "resolveJsonModule": true, 11 + "isolatedModules": true, 12 + "noEmit": true, 13 + "jsx": "react-jsx", 14 + "strict": true, 15 + "noUnusedLocals": true, 16 + "noUnusedParameters": true, 17 + "noFallthroughCasesInSwitch": true 18 + }, 19 + "include": ["src"], 20 + "references": [{ "path": "./tsconfig.node.json" }] 21 + }
+11
packages/client/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "skipLibCheck": true, 5 + "module": "ESNext", 6 + "moduleResolution": "bundler", 7 + "allowSyntheticDefaultImports": true, 8 + "strict": true 9 + }, 10 + "include": ["vite.config.ts"] 11 + }
+22
packages/client/vite.config.ts
··· 1 + import { defineConfig } from 'vite' 2 + import react from '@vitejs/plugin-react' 3 + 4 + export default defineConfig({ 5 + plugins: [react()], 6 + // Define env variables for the client 7 + define: { 8 + 'import.meta.env.VITE_API_URL': JSON.stringify(process.env.VITE_API_URL || ''), 9 + }, 10 + server: { 11 + port: 5173, 12 + proxy: { 13 + '/feed-raw': 'http://localhost:3000', 14 + '/feed': 'http://localhost:3000', 15 + '/health': 'http://localhost:3000', 16 + }, 17 + }, 18 + build: { 19 + outDir: 'dist', 20 + sourcemap: true, 21 + }, 22 + })
+20
packages/server/package.json
··· 1 + { 2 + "name": "@atfeeds/server", 3 + "version": "1.0.0", 4 + "private": true, 5 + "scripts": { 6 + "dev": "wrangler dev", 7 + "deploy": "wrangler deploy", 8 + "db:create": "wrangler d1 create atfeeds-db", 9 + "db:migrate": "wrangler d1 execute atfeeds-db --local --file=./schema.sql", 10 + "db:migrate:prod": "wrangler d1 execute atfeeds-db --remote --file=./schema.sql", 11 + "secret:set": "wrangler secret put TAP_WEBHOOK_SECRET" 12 + }, 13 + "dependencies": { 14 + "hono": "^4.0.0" 15 + }, 16 + "devDependencies": { 17 + "@cloudflare/workers-types": "^4.20240117.0", 18 + "wrangler": "^3.0.0" 19 + } 20 + }
+47
packages/server/schema.sql
··· 1 + -- Records synced from external tap instance 2 + CREATE TABLE IF NOT EXISTS repo_records ( 3 + id INTEGER PRIMARY KEY AUTOINCREMENT, 4 + did TEXT NOT NULL, 5 + rkey TEXT NOT NULL, 6 + collection TEXT NOT NULL, 7 + cid TEXT, 8 + synced_at TEXT DEFAULT (datetime('now')), 9 + UNIQUE(did, collection, rkey) 10 + ); 11 + 12 + CREATE INDEX IF NOT EXISTS idx_repo_records_collection ON repo_records(collection); 13 + CREATE INDEX IF NOT EXISTS idx_repo_records_did ON repo_records(did); 14 + CREATE INDEX IF NOT EXISTS idx_repo_records_rkey ON repo_records(rkey DESC); 15 + 16 + -- Cache for resolved PDS endpoints 17 + CREATE TABLE IF NOT EXISTS pds_cache ( 18 + did TEXT PRIMARY KEY, 19 + pds_endpoint TEXT NOT NULL, 20 + cached_at TEXT DEFAULT (datetime('now')) 21 + ); 22 + 23 + -- Sync metadata to track last sync 24 + CREATE TABLE IF NOT EXISTS sync_metadata ( 25 + key TEXT PRIMARY KEY, 26 + value TEXT NOT NULL, 27 + updated_at TEXT DEFAULT (datetime('now')) 28 + ); 29 + 30 + -- Pre-resolved documents for fast feed serving 31 + CREATE TABLE IF NOT EXISTS resolved_documents ( 32 + uri TEXT PRIMARY KEY, 33 + did TEXT NOT NULL, 34 + rkey TEXT NOT NULL, 35 + title TEXT, 36 + path TEXT, 37 + site TEXT, 38 + content TEXT, -- JSON blob 39 + text_content TEXT, 40 + published_at TEXT, 41 + view_url TEXT, 42 + resolved_at TEXT DEFAULT (datetime('now')), 43 + stale_at TEXT -- When this record should be re-resolved 44 + ); 45 + 46 + CREATE INDEX IF NOT EXISTS idx_resolved_documents_rkey ON resolved_documents(rkey DESC); 47 + CREATE INDEX IF NOT EXISTS idx_resolved_documents_stale ON resolved_documents(stale_at);
+50
packages/server/src/index.ts
··· 1 + import { Hono } from "hono"; 2 + import { cors } from "hono/cors"; 3 + import type { Bindings } from "./types"; 4 + import { health, webhook, feed, stats, records } from "./routes"; 5 + 6 + const app = new Hono<{ Bindings: Bindings }>(); 7 + 8 + // Middleware 9 + app.use("*", cors()); 10 + 11 + // Mount routes 12 + app.route("/health", health); 13 + app.route("/webhook", webhook); 14 + app.route("/feed", feed); 15 + app.route("/stats", stats); 16 + app.route("/records", records); 17 + 18 + // Legacy alias: /feed-raw -> /feed/raw 19 + app.get("/feed-raw", async (c) => { 20 + const db = c.env.DB; 21 + const limit = Math.min(Number(c.req.query("limit")) || 15, 15); 22 + const offset = Number(c.req.query("offset")) || 0; 23 + 24 + const { results } = await db 25 + .prepare( 26 + `SELECT did, rkey FROM repo_records 27 + WHERE collection = 'site.standard.document' 28 + ORDER BY rkey DESC 29 + LIMIT ? OFFSET ?`, 30 + ) 31 + .bind(limit, offset) 32 + .all<{ did: string; rkey: string }>(); 33 + 34 + return c.json({ 35 + count: results?.length || 0, 36 + limit, 37 + offset, 38 + records: results || [], 39 + }); 40 + }); 41 + 42 + // 404 handler 43 + app.notFound((c) => { 44 + return c.json({ error: "Not found" }, 404); 45 + }); 46 + 47 + // Export for Cloudflare Workers 48 + export default { 49 + fetch: app.fetch, 50 + };
+93
packages/server/src/routes/feed.ts
··· 1 + import { Hono } from "hono"; 2 + import type { Bindings } from "../types"; 3 + 4 + const feed = new Hono<{ Bindings: Bindings }>(); 5 + 6 + // Get raw feed data (for client-side fetching) 7 + // Accessible at both /feed/raw and /feed-raw (via alias in index.ts) 8 + feed.get("/raw", async (c) => { 9 + try { 10 + const db = c.env.DB; 11 + const limit = Math.min(Number(c.req.query("limit")) || 15, 15); 12 + const offset = Number(c.req.query("offset")) || 0; 13 + 14 + const { results } = await db 15 + .prepare( 16 + `SELECT did, rkey FROM repo_records 17 + WHERE collection = 'site.standard.document' 18 + ORDER BY rkey DESC 19 + LIMIT ? OFFSET ?` 20 + ) 21 + .bind(limit, offset) 22 + .all<{ did: string; rkey: string }>(); 23 + 24 + return c.json({ 25 + count: results?.length || 0, 26 + limit, 27 + offset, 28 + records: results || [], 29 + }); 30 + } catch (error) { 31 + return c.json( 32 + { error: "Failed to fetch feed", details: String(error) }, 33 + 500 34 + ); 35 + } 36 + }); 37 + 38 + // Get feed of documents with resolved URLs (server-side resolution) 39 + feed.get("/", async (c) => { 40 + try { 41 + const db = c.env.DB; 42 + const limit = Number(c.req.query("limit")) || 50; 43 + const offset = Number(c.req.query("offset")) || 0; 44 + 45 + const { results } = await db 46 + .prepare( 47 + `SELECT uri, did, rkey, title, path, site, content, text_content, published_at, view_url 48 + FROM resolved_documents 49 + ORDER BY rkey DESC 50 + LIMIT ? OFFSET ?` 51 + ) 52 + .bind(limit, offset) 53 + .all<{ 54 + uri: string; 55 + did: string; 56 + rkey: string; 57 + title: string | null; 58 + path: string | null; 59 + site: string | null; 60 + content: string | null; 61 + text_content: string | null; 62 + published_at: string | null; 63 + view_url: string | null; 64 + }>(); 65 + 66 + const documents = (results || []).map((doc) => ({ 67 + uri: doc.uri, 68 + did: doc.did, 69 + rkey: doc.rkey, 70 + title: doc.title || "Untitled", 71 + path: doc.path, 72 + site: doc.site, 73 + content: doc.content ? JSON.parse(doc.content) : null, 74 + textContent: doc.text_content, 75 + publishedAt: doc.published_at, 76 + viewUrl: doc.view_url, 77 + })); 78 + 79 + return c.json({ 80 + count: documents.length, 81 + limit, 82 + offset, 83 + documents, 84 + }); 85 + } catch (error) { 86 + return c.json( 87 + { error: "Failed to fetch feed", details: String(error) }, 88 + 500 89 + ); 90 + } 91 + }); 92 + 93 + export default feed;
+10
packages/server/src/routes/health.ts
··· 1 + import { Hono } from "hono"; 2 + import type { Bindings } from "../types"; 3 + 4 + const health = new Hono<{ Bindings: Bindings }>(); 5 + 6 + health.get("/", (c) => { 7 + return c.json({ status: "ok", timestamp: new Date().toISOString() }); 8 + }); 9 + 10 + export default health;
+5
packages/server/src/routes/index.ts
··· 1 + export { default as health } from "./health"; 2 + export { default as webhook } from "./webhook"; 3 + export { default as feed } from "./feed"; 4 + export { default as stats } from "./stats"; 5 + export { default as records } from "./records";
+38
packages/server/src/routes/records.ts
··· 1 + import { Hono } from "hono"; 2 + import type { Bindings } from "../types"; 3 + 4 + const records = new Hono<{ Bindings: Bindings }>(); 5 + 6 + records.get("/:did", async (c) => { 7 + try { 8 + const db = c.env.DB; 9 + const did = c.req.param("did"); 10 + const limit = Number(c.req.query("limit")) || 20; 11 + const offset = Number(c.req.query("offset")) || 0; 12 + 13 + const { results } = await db 14 + .prepare( 15 + `SELECT * FROM repo_records 16 + WHERE did = ? AND collection = 'site.standard.document' 17 + ORDER BY rkey DESC 18 + LIMIT ? OFFSET ?` 19 + ) 20 + .bind(did, limit, offset) 21 + .all(); 22 + 23 + return c.json({ 24 + did, 25 + count: results?.length || 0, 26 + limit, 27 + offset, 28 + records: results || [], 29 + }); 30 + } catch (error) { 31 + return c.json( 32 + { error: "Failed to fetch records", details: String(error) }, 33 + 500 34 + ); 35 + } 36 + }); 37 + 38 + export default records;
+38
packages/server/src/routes/stats.ts
··· 1 + import { Hono } from "hono"; 2 + import type { Bindings } from "../types"; 3 + 4 + const stats = new Hono<{ Bindings: Bindings }>(); 5 + 6 + stats.get("/", async (c) => { 7 + try { 8 + const db = c.env.DB; 9 + const [records, pdsCache, recordCache, pubCache] = await Promise.all([ 10 + db 11 + .prepare("SELECT COUNT(*) as count FROM repo_records") 12 + .first<{ count: number }>(), 13 + db 14 + .prepare("SELECT COUNT(*) as count FROM pds_cache") 15 + .first<{ count: number }>(), 16 + db 17 + .prepare("SELECT COUNT(*) as count FROM record_cache") 18 + .first<{ count: number }>(), 19 + db 20 + .prepare("SELECT COUNT(*) as count FROM publication_cache") 21 + .first<{ count: number }>(), 22 + ]); 23 + 24 + return c.json({ 25 + repo_records: records?.count || 0, 26 + pds_cache: pdsCache?.count || 0, 27 + record_cache: recordCache?.count || 0, 28 + publication_cache: pubCache?.count || 0, 29 + }); 30 + } catch (error) { 31 + return c.json( 32 + { error: "Failed to fetch stats", details: String(error) }, 33 + 500 34 + ); 35 + } 36 + }); 37 + 38 + export default stats;
+272
packages/server/src/routes/webhook.ts
··· 1 + import { Hono } from "hono"; 2 + import type { Bindings, TapEvent } from "../types"; 3 + import { resolvePds, parseAtUri } from "../utils"; 4 + 5 + const webhook = new Hono<{ Bindings: Bindings }>(); 6 + 7 + async function resolveViewUrl( 8 + db: D1Database, 9 + siteUri: string, 10 + path: string 11 + ): Promise<string | null> { 12 + const parsed = parseAtUri(siteUri); 13 + if (!parsed) return null; 14 + 15 + try { 16 + const pds = await resolvePds(db, parsed.did); 17 + if (!pds) return null; 18 + 19 + const url = `${pds}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(parsed.did)}&collection=${encodeURIComponent(parsed.collection)}&rkey=${encodeURIComponent(parsed.rkey)}`; 20 + const response = await fetch(url); 21 + if (!response.ok) return null; 22 + 23 + const data = (await response.json()) as { 24 + value?: { url?: string; domain?: string }; 25 + }; 26 + const siteUrl = data.value?.url || data.value?.domain; 27 + if (!siteUrl) return null; 28 + 29 + const baseUrl = siteUrl.startsWith("http") ? siteUrl : `https://${siteUrl}`; 30 + return `${baseUrl}${path}`; 31 + } catch { 32 + return null; 33 + } 34 + } 35 + 36 + webhook.post("/tap", async (c) => { 37 + try { 38 + const db = c.env.DB; 39 + 40 + const secret = c.env.TAP_WEBHOOK_SECRET; 41 + if (secret) { 42 + const auth = c.req.header("Authorization"); 43 + if (auth !== `Bearer ${secret}`) { 44 + return c.json({ error: "Unauthorized" }, 401); 45 + } 46 + } 47 + 48 + const event = (await c.req.json()) as TapEvent; 49 + 50 + if (event.type === "record") { 51 + const { record } = event; 52 + 53 + if (record.collection === "site.standard.document") { 54 + if (record.action === "create" || record.action === "update") { 55 + await db 56 + .prepare( 57 + `INSERT INTO repo_records (did, rkey, collection, cid, synced_at) 58 + VALUES (?, ?, ?, ?, datetime('now')) 59 + ON CONFLICT(did, collection, rkey) DO UPDATE SET 60 + cid = ?, 61 + synced_at = datetime('now')` 62 + ) 63 + .bind( 64 + record.did, 65 + record.rkey, 66 + record.collection, 67 + record.cid || null, 68 + record.cid || null 69 + ) 70 + .run(); 71 + 72 + if (record.record) { 73 + const uri = `at://${record.did}/${record.collection}/${record.rkey}`; 74 + const doc = record.record as { 75 + title?: string; 76 + path?: string; 77 + site?: string; 78 + content?: unknown; 79 + textContent?: string; 80 + publishedAt?: string; 81 + }; 82 + 83 + let viewUrl: string | null = null; 84 + if (doc.site && doc.path) { 85 + viewUrl = await resolveViewUrl(db, doc.site, doc.path); 86 + } 87 + 88 + await db 89 + .prepare( 90 + `INSERT INTO resolved_documents (uri, did, rkey, title, path, site, content, text_content, published_at, view_url, resolved_at) 91 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now')) 92 + ON CONFLICT(uri) DO UPDATE SET 93 + title = ?, path = ?, site = ?, content = ?, text_content = ?, published_at = ?, view_url = ?, resolved_at = datetime('now')` 94 + ) 95 + .bind( 96 + uri, 97 + record.did, 98 + record.rkey, 99 + doc.title || null, 100 + doc.path || null, 101 + doc.site || null, 102 + doc.content ? JSON.stringify(doc.content) : null, 103 + doc.textContent || null, 104 + doc.publishedAt || null, 105 + viewUrl, 106 + doc.title || null, 107 + doc.path || null, 108 + doc.site || null, 109 + doc.content ? JSON.stringify(doc.content) : null, 110 + doc.textContent || null, 111 + doc.publishedAt || null, 112 + viewUrl 113 + ) 114 + .run(); 115 + } 116 + } else if (record.action === "delete") { 117 + await db 118 + .prepare( 119 + "DELETE FROM repo_records WHERE did = ? AND collection = ? AND rkey = ?" 120 + ) 121 + .bind(record.did, record.collection, record.rkey) 122 + .run(); 123 + 124 + const uri = `at://${record.did}/${record.collection}/${record.rkey}`; 125 + await db 126 + .prepare("DELETE FROM resolved_documents WHERE uri = ?") 127 + .bind(uri) 128 + .run(); 129 + } 130 + } 131 + } 132 + 133 + return c.json({ ok: true }); 134 + } catch (error) { 135 + console.error("Webhook error:", error); 136 + return c.json( 137 + { error: "Failed to process webhook", details: String(error) }, 138 + 500 139 + ); 140 + } 141 + }); 142 + 143 + webhook.post("/tap/batch", async (c) => { 144 + try { 145 + const db = c.env.DB; 146 + 147 + const secret = c.env.TAP_WEBHOOK_SECRET; 148 + if (secret) { 149 + const auth = c.req.header("Authorization"); 150 + if (auth !== `Bearer ${secret}`) { 151 + return c.json({ error: "Unauthorized" }, 401); 152 + } 153 + } 154 + 155 + const events = (await c.req.json()) as Array<{ 156 + type: string; 157 + did: string; 158 + collection?: string; 159 + rkey?: string; 160 + cid?: string; 161 + record?: Record<string, unknown>; 162 + }>; 163 + 164 + let processed = 0; 165 + let errors = 0; 166 + 167 + for (const event of events) { 168 + try { 169 + if ( 170 + (event.type === "commit" || 171 + event.type === "create" || 172 + event.type === "update") && 173 + event.collection === "site.standard.document" && 174 + event.did && 175 + event.rkey 176 + ) { 177 + await db 178 + .prepare( 179 + `INSERT INTO repo_records (did, rkey, collection, cid, synced_at) 180 + VALUES (?, ?, ?, ?, datetime('now')) 181 + ON CONFLICT(did, collection, rkey) DO UPDATE SET cid = ?, synced_at = datetime('now')` 182 + ) 183 + .bind( 184 + event.did, 185 + event.rkey, 186 + event.collection, 187 + event.cid || null, 188 + event.cid || null 189 + ) 190 + .run(); 191 + 192 + if (event.record) { 193 + const uri = `at://${event.did}/${event.collection}/${event.rkey}`; 194 + const doc = event.record as { 195 + title?: string; 196 + path?: string; 197 + site?: string; 198 + content?: unknown; 199 + textContent?: string; 200 + publishedAt?: string; 201 + }; 202 + 203 + let viewUrl: string | null = null; 204 + if (doc.site && doc.path) { 205 + viewUrl = await resolveViewUrl(db, doc.site, doc.path); 206 + } 207 + 208 + await db 209 + .prepare( 210 + `INSERT INTO resolved_documents (uri, did, rkey, title, path, site, content, text_content, published_at, view_url, resolved_at) 211 + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now')) 212 + ON CONFLICT(uri) DO UPDATE SET 213 + title = ?, path = ?, site = ?, content = ?, text_content = ?, published_at = ?, view_url = ?, resolved_at = datetime('now')` 214 + ) 215 + .bind( 216 + uri, 217 + event.did, 218 + event.rkey, 219 + doc.title || null, 220 + doc.path || null, 221 + doc.site || null, 222 + doc.content ? JSON.stringify(doc.content) : null, 223 + doc.textContent || null, 224 + doc.publishedAt || null, 225 + viewUrl, 226 + doc.title || null, 227 + doc.path || null, 228 + doc.site || null, 229 + doc.content ? JSON.stringify(doc.content) : null, 230 + doc.textContent || null, 231 + doc.publishedAt || null, 232 + viewUrl 233 + ) 234 + .run(); 235 + } 236 + processed++; 237 + } else if ( 238 + event.type === "delete" && 239 + event.collection === "site.standard.document" && 240 + event.did && 241 + event.rkey 242 + ) { 243 + await db 244 + .prepare( 245 + "DELETE FROM repo_records WHERE did = ? AND collection = ? AND rkey = ?" 246 + ) 247 + .bind(event.did, event.collection, event.rkey) 248 + .run(); 249 + 250 + const uri = `at://${event.did}/${event.collection}/${event.rkey}`; 251 + await db 252 + .prepare("DELETE FROM resolved_documents WHERE uri = ?") 253 + .bind(uri) 254 + .run(); 255 + processed++; 256 + } 257 + } catch { 258 + errors++; 259 + } 260 + } 261 + 262 + return c.json({ ok: true, processed, errors }); 263 + } catch (error) { 264 + console.error("Batch webhook error:", error); 265 + return c.json( 266 + { error: "Failed to process batch webhook", details: String(error) }, 267 + 500 268 + ); 269 + } 270 + }); 271 + 272 + export default webhook;
+45
packages/server/src/types/index.ts
··· 1 + export type Bindings = { 2 + DB: D1Database; 3 + TAP_WEBHOOK_SECRET?: string; 4 + }; 5 + 6 + export interface TapRecordEvent { 7 + id: number; 8 + type: "record"; 9 + record: { 10 + live: boolean; 11 + rev: string; 12 + did: string; 13 + collection: string; 14 + rkey: string; 15 + action: "create" | "update" | "delete"; 16 + cid?: string; 17 + record?: Record<string, unknown>; 18 + }; 19 + } 20 + 21 + export interface TapIdentityEvent { 22 + id: number; 23 + type: "identity"; 24 + identity: { 25 + did: string; 26 + handle: string; 27 + isActive: boolean; 28 + status: string; 29 + }; 30 + } 31 + 32 + export type TapEvent = TapRecordEvent | TapIdentityEvent; 33 + 34 + export interface Document { 35 + uri: string; 36 + did: string; 37 + rkey: string; 38 + title: string; 39 + path: string | null; 40 + site: string | null; 41 + content: unknown; 42 + textContent: string | null; 43 + publishedAt: string | null; 44 + viewUrl: string | null; 45 + }
+15
packages/server/src/utils/at-uri.ts
··· 1 + export interface AtUriComponents { 2 + did: string; 3 + collection: string; 4 + rkey: string; 5 + } 6 + 7 + export function parseAtUri(uri: string): AtUriComponents | null { 8 + const match = uri.match(/^at:\/\/([^/]+)\/([^/]+)\/([^/]+)$/); 9 + if (!match) return null; 10 + return { did: match[1], collection: match[2], rkey: match[3] }; 11 + } 12 + 13 + export function buildAtUri(did: string, collection: string, rkey: string): string { 14 + return `at://${did}/${collection}/${rkey}`; 15 + }
+2
packages/server/src/utils/index.ts
··· 1 + export { parseAtUri, buildAtUri, type AtUriComponents } from "./at-uri"; 2 + export { resolvePds } from "./resolver";
+49
packages/server/src/utils/resolver.ts
··· 1 + // PDS cache TTL: 1 hour (PDS endpoints rarely change) 2 + const PDS_CACHE_TTL_MS = 60 * 60 * 1000; 3 + 4 + function isPdsCacheValid(cachedAt: string | null): boolean { 5 + if (!cachedAt) return false; 6 + const cacheTime = new Date(cachedAt).getTime(); 7 + return Date.now() - cacheTime < PDS_CACHE_TTL_MS; 8 + } 9 + 10 + export async function resolvePds( 11 + db: D1Database, 12 + did: string 13 + ): Promise<string | null> { 14 + const cached = await db 15 + .prepare("SELECT pds_endpoint, cached_at FROM pds_cache WHERE did = ?") 16 + .bind(did) 17 + .first<{ pds_endpoint: string; cached_at: string }>(); 18 + 19 + if (cached && isPdsCacheValid(cached.cached_at)) { 20 + return cached.pds_endpoint; 21 + } 22 + 23 + try { 24 + const response = await fetch(`https://plc.directory/${did}`); 25 + if (!response.ok) return null; 26 + 27 + const doc = (await response.json()) as { 28 + service?: Array<{ id: string; type: string; serviceEndpoint: string }>; 29 + }; 30 + 31 + const pds = doc.service?.find((s) => s.id === "#atproto_pds"); 32 + if (pds?.serviceEndpoint) { 33 + await db 34 + .prepare( 35 + `INSERT INTO pds_cache (did, pds_endpoint, cached_at) 36 + VALUES (?, ?, datetime('now')) 37 + ON CONFLICT(did) DO UPDATE SET pds_endpoint = ?, cached_at = datetime('now')` 38 + ) 39 + .bind(did, pds.serviceEndpoint, pds.serviceEndpoint) 40 + .run(); 41 + 42 + return pds.serviceEndpoint; 43 + } 44 + 45 + return null; 46 + } catch { 47 + return null; 48 + } 49 + }
+17
packages/server/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2022", 4 + "module": "ESNext", 5 + "moduleResolution": "bundler", 6 + "strict": true, 7 + "skipLibCheck": true, 8 + "lib": ["ES2022"], 9 + "types": ["@cloudflare/workers-types"], 10 + "noEmit": true, 11 + "isolatedModules": true, 12 + "esModuleInterop": true, 13 + "allowSyntheticDefaultImports": true 14 + }, 15 + "include": ["src/**/*"], 16 + "exclude": ["node_modules"] 17 + }
+31
packages/server/wrangler.toml
··· 1 + name = "atfeeds-api" 2 + main = "src/index.ts" 3 + compatibility_date = "2024-01-01" 4 + 5 + # D1 Database binding 6 + [[d1_databases]] 7 + binding = "DB" 8 + database_name = "atfeeds-db" 9 + database_id = "bfbb9955-1496-47e9-9602-e32c9b1fa7b2" 10 + 11 + # Queue for processing document resolution 12 + # [[queues.producers]] 13 + # queue = "document-resolution" 14 + # binding = "RESOLUTION_QUEUE" 15 + 16 + # [[queues.consumers]] 17 + # queue = "document-resolution" 18 + # max_batch_size = 10 19 + # max_batch_timeout = 30 20 + 21 + # Cron trigger to refresh stale documents 22 + [triggers] 23 + crons = ["*/15 * * * *"] # Every 15 minutes 24 + 25 + # Environment variables (secrets should be set via wrangler secret) 26 + # TAP_WEBHOOK_SECRET - Optional secret for webhook authentication 27 + # Set via: wrangler secret put TAP_WEBHOOK_SECRET 28 + 29 + # Development settings 30 + [dev] 31 + port = 8787