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

webhook mvp

+1322 -104
+34
apps/webhook-service/.gitignore
··· 1 + # dependencies (bun install) 2 + node_modules 3 + 4 + # output 5 + out 6 + dist 7 + *.tgz 8 + 9 + # code coverage 10 + coverage 11 + *.lcov 12 + 13 + # logs 14 + logs 15 + _.log 16 + report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 17 + 18 + # dotenv environment variable files 19 + .env 20 + .env.development.local 21 + .env.test.local 22 + .env.production.local 23 + .env.local 24 + 25 + # caches 26 + .eslintcache 27 + .cache 28 + *.tsbuildinfo 29 + 30 + # IntelliJ based IDEs 31 + .idea 32 + 33 + # Finder (MacOS) folder config 34 + .DS_Store
+106
apps/webhook-service/CLAUDE.md
··· 1 + 2 + Default to using Bun instead of Node.js. 3 + 4 + - Use `bun <file>` instead of `node <file>` or `ts-node <file>` 5 + - Use `bun test` instead of `jest` or `vitest` 6 + - Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild` 7 + - Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` 8 + - Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>` 9 + - Use `bunx <package> <command>` instead of `npx <package> <command>` 10 + - Bun automatically loads .env, so don't use dotenv. 11 + 12 + ## APIs 13 + 14 + - `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`. 15 + - `bun:sqlite` for SQLite. Don't use `better-sqlite3`. 16 + - `Bun.redis` for Redis. Don't use `ioredis`. 17 + - `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`. 18 + - `WebSocket` is built-in. Don't use `ws`. 19 + - Prefer `Bun.file` over `node:fs`'s readFile/writeFile 20 + - Bun.$`ls` instead of execa. 21 + 22 + ## Testing 23 + 24 + Use `bun test` to run tests. 25 + 26 + ```ts#index.test.ts 27 + import { test, expect } from "bun:test"; 28 + 29 + test("hello world", () => { 30 + expect(1).toBe(1); 31 + }); 32 + ``` 33 + 34 + ## Frontend 35 + 36 + Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind. 37 + 38 + Server: 39 + 40 + ```ts#index.ts 41 + import index from "./index.html" 42 + 43 + Bun.serve({ 44 + routes: { 45 + "/": index, 46 + "/api/users/:id": { 47 + GET: (req) => { 48 + return new Response(JSON.stringify({ id: req.params.id })); 49 + }, 50 + }, 51 + }, 52 + // optional websocket support 53 + websocket: { 54 + open: (ws) => { 55 + ws.send("Hello, world!"); 56 + }, 57 + message: (ws, message) => { 58 + ws.send(message); 59 + }, 60 + close: (ws) => { 61 + // handle close 62 + } 63 + }, 64 + development: { 65 + hmr: true, 66 + console: true, 67 + } 68 + }) 69 + ``` 70 + 71 + HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle. 72 + 73 + ```html#index.html 74 + <html> 75 + <body> 76 + <h1>Hello, world!</h1> 77 + <script type="module" src="./frontend.tsx"></script> 78 + </body> 79 + </html> 80 + ``` 81 + 82 + With the following `frontend.tsx`: 83 + 84 + ```tsx#frontend.tsx 85 + import React from "react"; 86 + import { createRoot } from "react-dom/client"; 87 + 88 + // import .css files directly and it works 89 + import './index.css'; 90 + 91 + const root = createRoot(document.body); 92 + 93 + export default function Frontend() { 94 + return <h1>Hello, world!</h1>; 95 + } 96 + 97 + root.render(<Frontend />); 98 + ``` 99 + 100 + Then, run index.ts 101 + 102 + ```sh 103 + bun --hot ./index.ts 104 + ``` 105 + 106 + For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
+15
apps/webhook-service/README.md
··· 1 + # webhook-service 2 + 3 + To install dependencies: 4 + 5 + ```bash 6 + bun install 7 + ``` 8 + 9 + To run: 10 + 11 + ```bash 12 + bun run index.ts 13 + ``` 14 + 15 + This project was created using `bun init` in bun v1.3.10. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
+280
apps/webhook-service/bun.lock
··· 1 + { 2 + "lockfileVersion": 1, 3 + "configVersion": 1, 4 + "workspaces": { 5 + "": { 6 + "name": "webhook-service", 7 + "dependencies": { 8 + "@atproto/identity": "^0.4.12", 9 + "@atproto/sync": "^0.1.40", 10 + "@atproto/syntax": "^0.5.0", 11 + "hono": "^4.12.5", 12 + }, 13 + "devDependencies": { 14 + "@types/bun": "latest", 15 + }, 16 + "peerDependencies": { 17 + "typescript": "^5", 18 + }, 19 + }, 20 + }, 21 + "packages": { 22 + "@atproto/common": ["@atproto/common@0.5.14", "", { "dependencies": { "@atproto/common-web": "^0.4.18", "@atproto/lex-cbor": "^0.0.14", "@atproto/lex-data": "^0.0.13", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-FnhTppvJw8I1AuvEkL9JREFwmM6ciYfSlQ0Zo6neiJIhTf1wf5/ONeFSYKu1/dxC63JEratGIAfVjSBJJZi7sg=="], 23 + 24 + "@atproto/common-web": ["@atproto/common-web@0.4.18", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "@atproto/lex-json": "^0.0.13", "@atproto/syntax": "^0.5.0", "zod": "^3.23.8" } }, "sha512-ilImzP+9N/mtse440kN60pGrEzG7wi4xsV13nGeLrS+Zocybc/ISOpKlbZM13o+twPJ+Q7veGLw9CtGg0GAFoQ=="], 25 + 26 + "@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=="], 27 + 28 + "@atproto/identity": ["@atproto/identity@0.4.12", "", { "dependencies": { "@atproto/common-web": "^0.4.17", "@atproto/crypto": "^0.4.5" } }, "sha512-P+Jn0HvKhIh1tps5n3xGrCxt+XiFWzp4kdgloyFhFmVLwjDU547DQkWx4r5Vhuiah7fRZGVSlk39R4U6SPrACg=="], 29 + 30 + "@atproto/lex-cbor": ["@atproto/lex-cbor@0.0.14", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "tslib": "^2.8.1" } }, "sha512-zeqxaKAifR8qlFKg4A6t1RCT8TcjeDnIXLtp3QnDu0QoxslxsmcsrqNrrgmka8w+bYW2+h/rT9MPWglkT7vHyw=="], 31 + 32 + "@atproto/lex-client": ["@atproto/lex-client@0.0.15", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "@atproto/lex-json": "^0.0.13", "@atproto/lex-schema": "^0.0.14", "tslib": "^2.8.1" } }, "sha512-j/eZGCdkhABU8Z868Y/gn909hS77rOCdMqtOaQdflEaKUKiAo2/gqeTpoAjHBnL5Rzz255wj9qZMqZTR/Ygwxw=="], 33 + 34 + "@atproto/lex-data": ["@atproto/lex-data@0.0.13", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-7Z7RwZ1Y/JzBF/Tcn/I4UJ/vIGfh5zn1zjv0KX+flke2JtgFkSE8uh2hOtqgBQMNqE3zdJFM+dcSWln86hR3MQ=="], 35 + 36 + "@atproto/lex-json": ["@atproto/lex-json@0.0.13", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "tslib": "^2.8.1" } }, "sha512-hwLhkKaIHulGJpt0EfXAEWdrxqM2L1tV/tvilzhMp3QxPqYgXchFnrfVmLsyFDx6P6qkH1GsX/XC2V36U0UlPQ=="], 37 + 38 + "@atproto/lex-schema": ["@atproto/lex-schema@0.0.14", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "@atproto/syntax": "^0.5.0", "tslib": "^2.8.1" } }, "sha512-xUxFuXdgVVI1IBDXcQlanH7HuEo9Pk65DYifnhqFDzNRH9SZQxPvPO+rOxMG/bRHygPaI+A+UbXr+S7qpPYOLg=="], 39 + 40 + "@atproto/lexicon": ["@atproto/lexicon@0.6.2", "", { "dependencies": { "@atproto/common-web": "^0.4.18", "@atproto/syntax": "^0.5.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-p3Ly6hinVZW0ETuAXZMeUGwuMm3g8HvQMQ41yyEE6AL0hAkfeKFaZKos6BdBrr6CjkpbrDZqE8M+5+QOceysMw=="], 41 + 42 + "@atproto/repo": ["@atproto/repo@0.8.12", "", { "dependencies": { "@atproto/common": "^0.5.3", "@atproto/common-web": "^0.4.7", "@atproto/crypto": "^0.4.5", "@atproto/lexicon": "^0.6.0", "@ipld/dag-cbor": "^7.0.0", "multiformats": "^9.9.0", "uint8arrays": "3.0.0", "varint": "^6.0.0", "zod": "^3.23.8" } }, "sha512-QpVTVulgfz5PUiCTELlDBiRvnsnwrFWi+6CfY88VwXzrRHd9NE8GItK7sfxQ6U65vD/idH8ddCgFrlrsn1REPQ=="], 43 + 44 + "@atproto/sync": ["@atproto/sync@0.1.40", "", { "dependencies": { "@atproto/common": "^0.5.14", "@atproto/identity": "^0.4.12", "@atproto/lexicon": "^0.6.2", "@atproto/repo": "^0.8.12", "@atproto/syntax": "^0.5.0", "@atproto/xrpc-server": "^0.10.15", "multiformats": "^9.9.0", "p-queue": "^6.6.2", "ws": "^8.12.0" } }, "sha512-tnzFPqKBXPXpuGvx87sjqLHgEzIOT/QfQZUp0YOUHtpipBgvijEnNXV9XeTIgiUAv69wyRR+6YjJkLCYfHpVwQ=="], 45 + 46 + "@atproto/syntax": ["@atproto/syntax@0.5.0", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw=="], 47 + 48 + "@atproto/ws-client": ["@atproto/ws-client@0.0.4", "", { "dependencies": { "@atproto/common": "^0.5.3", "ws": "^8.12.0" } }, "sha512-dox1XIymuC7/ZRhUqKezIGgooZS45C6vHCfu0PnWjfvsLCK2kAlnvX4IBkA/WpcoijDhQ9ejChnFbo/sLmgvAg=="], 49 + 50 + "@atproto/xrpc": ["@atproto/xrpc@0.7.7", "", { "dependencies": { "@atproto/lexicon": "^0.6.0", "zod": "^3.23.8" } }, "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA=="], 51 + 52 + "@atproto/xrpc-server": ["@atproto/xrpc-server@0.10.15", "", { "dependencies": { "@atproto/common": "^0.5.14", "@atproto/crypto": "^0.4.5", "@atproto/lex-cbor": "^0.0.14", "@atproto/lex-client": "^0.0.15", "@atproto/lex-data": "^0.0.13", "@atproto/lex-json": "^0.0.13", "@atproto/lex-schema": "^0.0.14", "@atproto/lexicon": "^0.6.2", "@atproto/ws-client": "^0.0.4", "@atproto/xrpc": "^0.7.7", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "ws": "^8.12.0" } }, "sha512-ryGVAKuLU0Nqkv25gsPzffJhxnCXwPOyBi+sNAfP7n+mDDwcumH6RWySEHoDDrTsGvAP2r8o2ZrLCWuzKm7vSg=="], 53 + 54 + "@ipld/dag-cbor": ["@ipld/dag-cbor@7.0.3", "", { "dependencies": { "cborg": "^1.6.0", "multiformats": "^9.5.4" } }, "sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA=="], 55 + 56 + "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], 57 + 58 + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], 59 + 60 + "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], 61 + 62 + "@types/node": ["@types/node@25.3.5", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA=="], 63 + 64 + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], 65 + 66 + "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], 67 + 68 + "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="], 69 + 70 + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], 71 + 72 + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], 73 + 74 + "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=="], 75 + 76 + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], 77 + 78 + "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], 79 + 80 + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], 81 + 82 + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], 83 + 84 + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], 85 + 86 + "cborg": ["cborg@1.10.2", "", { "bin": { "cborg": "cli.js" } }, "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug=="], 87 + 88 + "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], 89 + 90 + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], 91 + 92 + "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 93 + 94 + "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], 95 + 96 + "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 97 + 98 + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], 99 + 100 + "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], 101 + 102 + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], 103 + 104 + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], 105 + 106 + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], 107 + 108 + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], 109 + 110 + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], 111 + 112 + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], 113 + 114 + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], 115 + 116 + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], 117 + 118 + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], 119 + 120 + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], 121 + 122 + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], 123 + 124 + "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=="], 125 + 126 + "fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="], 127 + 128 + "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=="], 129 + 130 + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], 131 + 132 + "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], 133 + 134 + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 135 + 136 + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], 137 + 138 + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], 139 + 140 + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], 141 + 142 + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], 143 + 144 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 145 + 146 + "hono": ["hono@4.12.5", "", {}, "sha512-3qq+FUBtlTHhtYxbxheZgY8NIFnkkC/MR8u5TTsr7YZ3wixryQ3cCwn3iZbg8p8B88iDBBAYSfZDS75t8MN7Vg=="], 147 + 148 + "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=="], 149 + 150 + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], 151 + 152 + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 153 + 154 + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 155 + 156 + "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], 157 + 158 + "iso-datestring-validator": ["iso-datestring-validator@2.2.2", "", {}, "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA=="], 159 + 160 + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], 161 + 162 + "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], 163 + 164 + "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], 165 + 166 + "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], 167 + 168 + "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], 169 + 170 + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 171 + 172 + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], 173 + 174 + "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 175 + 176 + "multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 177 + 178 + "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], 179 + 180 + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], 181 + 182 + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], 183 + 184 + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], 185 + 186 + "p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="], 187 + 188 + "p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="], 189 + 190 + "p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="], 191 + 192 + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], 193 + 194 + "path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="], 195 + 196 + "pino": ["pino@8.21.0", "", { "dependencies": { "atomic-sleep": "^1.0.0", "fast-redact": "^3.1.1", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^1.2.0", "pino-std-serializers": "^6.0.0", "process-warning": "^3.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^3.7.0", "thread-stream": "^2.6.0" }, "bin": { "pino": "bin.js" } }, "sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q=="], 197 + 198 + "pino-abstract-transport": ["pino-abstract-transport@1.2.0", "", { "dependencies": { "readable-stream": "^4.0.0", "split2": "^4.0.0" } }, "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q=="], 199 + 200 + "pino-std-serializers": ["pino-std-serializers@6.2.2", "", {}, "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA=="], 201 + 202 + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], 203 + 204 + "process-warning": ["process-warning@3.0.0", "", {}, "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="], 205 + 206 + "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=="], 207 + 208 + "qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], 209 + 210 + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], 211 + 212 + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], 213 + 214 + "rate-limiter-flexible": ["rate-limiter-flexible@2.4.2", "", {}, "sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw=="], 215 + 216 + "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=="], 217 + 218 + "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=="], 219 + 220 + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], 221 + 222 + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], 223 + 224 + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], 225 + 226 + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], 227 + 228 + "send": ["send@0.19.2", "", { "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.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], 229 + 230 + "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], 231 + 232 + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], 233 + 234 + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], 235 + 236 + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], 237 + 238 + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], 239 + 240 + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], 241 + 242 + "sonic-boom": ["sonic-boom@3.8.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg=="], 243 + 244 + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], 245 + 246 + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], 247 + 248 + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], 249 + 250 + "thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw=="], 251 + 252 + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], 253 + 254 + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 255 + 256 + "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], 257 + 258 + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 259 + 260 + "uint8arrays": ["uint8arrays@3.0.0", "", { "dependencies": { "multiformats": "^9.4.2" } }, "sha512-HRCx0q6O9Bfbp+HHSfQQKD7wU70+lydKVt4EghkdOvlK/NlrF90z+eXV34mUd48rNvVJXwkrMSPpCATkct8fJA=="], 261 + 262 + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], 263 + 264 + "unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="], 265 + 266 + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], 267 + 268 + "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], 269 + 270 + "varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="], 271 + 272 + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], 273 + 274 + "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], 275 + 276 + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 277 + 278 + "send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 279 + } 280 + }
+23
apps/webhook-service/package.json
··· 1 + { 2 + "name": "webhook-service", 3 + "type": "module", 4 + "private": true, 5 + "scripts": { 6 + "dev": "bun --env-file=.env src/index.ts", 7 + "start": "bun src/index.ts", 8 + "check": "tsc --noEmit" 9 + }, 10 + "dependencies": { 11 + "@atproto/identity": "^0.4.9", 12 + "@atproto/sync": "^0.1.40", 13 + "@atproto/syntax": "^0.5.0", 14 + "@wispplace/bun-firehose": "workspace:*", 15 + "@wispplace/lexicons": "workspace:*", 16 + "@wispplace/observability": "workspace:*", 17 + "lru-cache": "^11.2.6" 18 + }, 19 + "devDependencies": { 20 + "@types/bun": "latest", 21 + "typescript": "^5.9.3" 22 + } 23 + }
+6
apps/webhook-service/src/config.ts
··· 1 + export const config = { 2 + firehoseService: process.env.FIREHOSE_SERVICE || 'wss://bsky.network', 3 + healthPort: parseInt(process.env.HEALTH_PORT || '3003', 10), 4 + deliveryTimeoutMs: parseInt(process.env.DELIVERY_TIMEOUT_MS || '10000', 10), 5 + deliveryMaxRetries: parseInt(process.env.DELIVERY_MAX_RETRIES || '3', 10), 6 + } as const;
+114
apps/webhook-service/src/index.ts
··· 1 + import { createLogger } from '@wispplace/observability'; 2 + import { config } from './config'; 3 + import { startFirehose, stopFirehose, getFirehoseHealth } from './lib/firehose'; 4 + import { closeDatabase, db } from './lib/db'; 5 + 6 + const logger = createLogger('webhook-service'); 7 + 8 + Bun.serve({ 9 + port: 3004, 10 + routes: { 11 + '/': { 12 + POST: async (req) => { 13 + const body = await req.json(); 14 + console.log('[webhook-receiver]', JSON.stringify(body, null, 2)); 15 + return new Response('ok'); 16 + }, 17 + }, 18 + }, 19 + fetch: () => new Response('Not Found', { status: 404 }), 20 + }); 21 + 22 + Bun.serve({ 23 + port: 3005, 24 + routes: { 25 + '/': async () => { 26 + const rows = await db`SELECT k, v, updated_at FROM webhook_records ORDER BY updated_at DESC`; 27 + const html = `<!DOCTYPE html> 28 + <html> 29 + <head><meta charset="utf-8"><title>webhook_records</title> 30 + <style> 31 + body { font-family: monospace; padding: 1rem; background: #111; color: #eee; } 32 + table { border-collapse: collapse; width: 100%; } 33 + th, td { border: 1px solid #444; padding: 0.4rem 0.6rem; text-align: left; vertical-align: top; } 34 + th { background: #222; } 35 + pre { margin: 0; white-space: pre-wrap; word-break: break-all; font-size: 0.85em; } 36 + </style> 37 + </head> 38 + <body> 39 + <h2>webhook_records (${rows.length})</h2> 40 + <p><a href="/webhooks" style="color:#aaf">webhooks</a></p> 41 + <table> 42 + <tr><th>k</th><th>v</th><th>updated_at</th></tr> 43 + ${rows.map((r: any) => `<tr><td>${r.k}</td><td><pre>${JSON.stringify(r.v, null, 2)}</pre></td><td>${new Date(Number(r.updated_at) * 1000).toISOString()}</td></tr>`).join('')} 44 + </table> 45 + </body></html>`; 46 + return new Response(html, { headers: { 'Content-Type': 'text/html' } }); 47 + }, 48 + '/webhooks': async () => { 49 + const rows = await db`SELECT did, rkey, url, scope_aturi, enabled, created_at, updated_at FROM webhooks ORDER BY updated_at DESC`; 50 + const html = `<!DOCTYPE html> 51 + <html> 52 + <head><meta charset="utf-8"><title>webhooks</title> 53 + <style> 54 + body { font-family: monospace; padding: 1rem; background: #111; color: #eee; } 55 + table { border-collapse: collapse; width: 100%; } 56 + th, td { border: 1px solid #444; padding: 0.4rem 0.6rem; text-align: left; vertical-align: top; } 57 + th { background: #222; } 58 + </style> 59 + </head> 60 + <body> 61 + <h2>webhooks (${rows.length})</h2> 62 + <p><a href="/" style="color:#aaf">webhook_records</a></p> 63 + <table> 64 + <tr><th>did</th><th>rkey</th><th>url</th><th>scope_aturi</th><th>enabled</th><th>updated_at</th></tr> 65 + ${rows.map((r: any) => `<tr><td>${r.did}</td><td>${r.rkey}</td><td>${r.url}</td><td>${r.scope_aturi}</td><td>${r.enabled}</td><td>${new Date(Number(r.updated_at) * 1000).toISOString()}</td></tr>`).join('')} 66 + </table> 67 + </body></html>`; 68 + return new Response(html, { headers: { 'Content-Type': 'text/html' } }); 69 + }, 70 + 71 + }, 72 + fetch: () => new Response('Not Found', { status: 404 }), 73 + }); 74 + 75 + Bun.serve({ 76 + port: config.healthPort, 77 + routes: { 78 + '/health': () => { 79 + const firehose = getFirehoseHealth(); 80 + return Response.json({ 81 + status: firehose.healthy ? 'healthy' : 'degraded', 82 + firehose, 83 + }); 84 + }, 85 + }, 86 + fetch: () => new Response('Not Found', { status: 404 }), 87 + }); 88 + 89 + let isShuttingDown = false; 90 + 91 + async function shutdown(signal: string) { 92 + if (isShuttingDown) return; 93 + isShuttingDown = true; 94 + logger.info(`Received ${signal}, shutting down...`); 95 + stopFirehose(); 96 + await closeDatabase(); 97 + process.exit(0); 98 + } 99 + 100 + process.on('SIGINT', () => shutdown('SIGINT')); 101 + process.on('SIGTERM', () => shutdown('SIGTERM')); 102 + 103 + async function main() { 104 + logger.info('Starting webhook-service'); 105 + logger.info(`Firehose: ${config.firehoseService}`); 106 + logger.info(`Health endpoint: http://localhost:${config.healthPort}/health`); 107 + 108 + startFirehose(); 109 + } 110 + 111 + main().catch((err) => { 112 + logger.error('Fatal error', err); 113 + process.exit(1); 114 + });
+144
apps/webhook-service/src/lib/db.ts
··· 1 + import { SQL } from 'bun'; 2 + import { createLogger } from '@wispplace/observability'; 3 + import type { Main as WhRecord } from '@wispplace/lexicons/types/place/wisp/v2/wh'; 4 + 5 + /** A webhook entry as returned from the DB, with ownership info split out from the KV key. */ 6 + export interface WebhookEntry { 7 + ownerDid: string; 8 + rkey: string; 9 + record: WhRecord; 10 + } 11 + 12 + const logger = createLogger('webhook-service:db'); 13 + 14 + export const db = new SQL( 15 + process.env.DATABASE_URL || 16 + (process.env.NODE_ENV === 'production' 17 + ? (() => { throw new Error('DATABASE_URL is required in production'); })() 18 + : 'postgres://postgres:postgres@localhost:5432/wisp') 19 + ); 20 + 21 + // Create tables on startup 22 + await db` 23 + CREATE TABLE IF NOT EXISTS webhooks ( 24 + did TEXT NOT NULL, 25 + rkey TEXT NOT NULL, 26 + url TEXT NOT NULL, 27 + scope_aturi TEXT NOT NULL, 28 + enabled BOOLEAN NOT NULL DEFAULT TRUE, 29 + created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()), 30 + updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()), 31 + PRIMARY KEY (did, rkey) 32 + ) 33 + `; 34 + 35 + await db` 36 + CREATE TABLE IF NOT EXISTS webhook_records ( 37 + k TEXT PRIMARY KEY, 38 + v JSONB NOT NULL, 39 + updated_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW()) 40 + ) 41 + `; 42 + 43 + /** 44 + * Find all webhook records whose scope AT-URI targets the given DID. 45 + * Matches exact DID scope (`at://did`) and collection/rkey sub-scopes (`at://did/...`). 46 + * Used as the primary lookup when a firehose event arrives from a DID. 47 + */ 48 + export async function findWebhooksForDid(scopeDid: string): Promise<WebhookEntry[]> { 49 + const exact = `at://${scopeDid}`; 50 + const prefix = `at://${scopeDid}/`; 51 + const rows = await db<Array<{ k: string; v: WhRecord }>>` 52 + SELECT k, v FROM webhook_records 53 + WHERE v->'scope'->>'aturi' = ${exact} 54 + OR starts_with(v->'scope'->>'aturi', ${prefix}) 55 + `; 56 + return rows.map(row => { 57 + const slash = row.k.indexOf('/'); 58 + return { 59 + ownerDid: row.k.slice(0, slash), 60 + rkey: row.k.slice(slash + 1), 61 + record: row.v, 62 + }; 63 + }); 64 + } 65 + 66 + /** 67 + * Find all webhook records that have backlinks enabled. 68 + * These are checked against every firehose event to see if the record body 69 + * references the webhook's scoped DID/collection. 70 + */ 71 + export async function findBacklinkWebhooks(): Promise<WebhookEntry[]> { 72 + const rows = await db<Array<{ k: string; v: WhRecord }>>` 73 + SELECT k, v FROM webhook_records 74 + WHERE (v->'scope'->>'backlinks')::boolean = true 75 + `; 76 + return rows.map(row => { 77 + const slash = row.k.indexOf('/'); 78 + return { 79 + ownerDid: row.k.slice(0, slash), 80 + rkey: row.k.slice(slash + 1), 81 + record: row.v, 82 + }; 83 + }); 84 + } 85 + 86 + /** Load all webhook records. Used for diagnostics/admin views. */ 87 + export async function loadAllWebhooks(): Promise<Array<{ did: string; rkey: string; record: WhRecord }>> { 88 + const rows = await db<Array<{ k: string; v: WhRecord }>>` 89 + SELECT k, v FROM webhook_records 90 + `; 91 + return rows.map(row => { 92 + const [did, rkey] = row.k.split('/') as [string, string]; 93 + return { did, rkey, record: row.v }; 94 + }); 95 + } 96 + 97 + /** 98 + * Insert or update a webhook record in both tables. 99 + * `webhooks` holds structured columns for quick filtering; `webhook_records` holds the full JSONB record. 100 + * Key is `did/rkey`. 101 + */ 102 + export async function upsertWebhookRecord(did: string, rkey: string, record: WhRecord): Promise<void> { 103 + const k = `${did}/${rkey}`; 104 + try { 105 + await db` 106 + INSERT INTO webhooks (did, rkey, url, scope_aturi, enabled, created_at, updated_at) 107 + VALUES (${did}, ${rkey}, ${record.url}, ${record.scope.aturi}, ${record.enabled ?? true}, 108 + EXTRACT(EPOCH FROM NOW()), EXTRACT(EPOCH FROM NOW())) 109 + ON CONFLICT (did, rkey) DO UPDATE SET 110 + url = EXCLUDED.url, 111 + scope_aturi = EXCLUDED.scope_aturi, 112 + enabled = EXCLUDED.enabled, 113 + updated_at = EXTRACT(EPOCH FROM NOW()) 114 + `; 115 + await db` 116 + INSERT INTO webhook_records (k, v, updated_at) 117 + VALUES (${k}, ${record}, EXTRACT(EPOCH FROM NOW())) 118 + ON CONFLICT (k) DO UPDATE SET 119 + v = EXCLUDED.v, 120 + updated_at = EXTRACT(EPOCH FROM NOW()) 121 + `; 122 + } catch (err) { 123 + logger.error(`[DB] upsertWebhookRecord error for ${k}`, err); 124 + throw err; 125 + } 126 + } 127 + 128 + /** Remove a webhook record from both tables. Called when a place.wisp.v2.wh delete event arrives. */ 129 + export async function deleteWebhookRecord(did: string, rkey: string): Promise<void> { 130 + const k = `${did}/${rkey}`; 131 + try { 132 + await db`DELETE FROM webhooks WHERE did = ${did} AND rkey = ${rkey}`; 133 + await db`DELETE FROM webhook_records WHERE k = ${k}`; 134 + } catch (err) { 135 + logger.error(`[DB] deleteWebhookRecord error for ${k}`, err); 136 + throw err; 137 + } 138 + } 139 + 140 + /** Close all database connections gracefully. */ 141 + export async function closeDatabase(): Promise<void> { 142 + await db.close(); 143 + logger.info('[DB] Database connections closed'); 144 + }
+94
apps/webhook-service/src/lib/delivery.ts
··· 1 + import { createHmac, randomUUID } from 'node:crypto'; 2 + import type { WebhookEntry } from './db'; 3 + import type { EventKind } from './matcher'; 4 + import { config } from '../config'; 5 + import { createLogger } from '@wispplace/observability'; 6 + 7 + const logger = createLogger('webhook-service:delivery'); 8 + 9 + export interface WebhookPayload { 10 + id: string; 11 + event: EventKind; 12 + did: string; 13 + collection: string; 14 + rkey: string; 15 + cid?: string; 16 + record?: unknown; 17 + timestamp: string; 18 + } 19 + 20 + /** 21 + * Signs a payload body with the webhook's shared secret using HMAC-SHA256. 22 + * Returns a `sha256=<hex>` string for the `X-Webhook-Signature` header. 23 + * Note: the secret is stored in the user's PDS record, so this provides 24 + * transport integrity rather than authentication of the sender. 25 + */ 26 + function sign(secret: string, body: string): string { 27 + return 'sha256=' + createHmac('sha256', secret).update(body).digest('hex'); 28 + } 29 + 30 + async function attempt(url: string, body: string, signature?: string): Promise<void> { 31 + const headers: Record<string, string> = { 32 + 'Content-Type': 'application/json', 33 + 'User-Agent': 'wisp.place-webhook/1.0', 34 + }; 35 + if (signature) headers['X-Webhook-Signature'] = signature; 36 + 37 + const res = await fetch(url, { 38 + method: 'POST', 39 + headers, 40 + body, 41 + signal: AbortSignal.timeout(config.deliveryTimeoutMs), 42 + }); 43 + 44 + if (!res.ok) { 45 + throw new Error(`HTTP ${res.status}`); 46 + } 47 + } 48 + 49 + /** 50 + * Delivers a firehose event to a webhook URL with exponential backoff retries. 51 + * The payload includes the event kind, AT-URI components, CID, full record, and a unique ID. 52 + * If the webhook record has a `secret`, the payload is signed and the signature is sent 53 + * in the `X-Webhook-Signature` header. 54 + */ 55 + export async function deliverWebhook( 56 + entry: WebhookEntry, 57 + eventDid: string, 58 + eventCollection: string, 59 + eventRkey: string, 60 + eventKind: EventKind, 61 + eventCid?: string, 62 + eventRecord?: unknown, 63 + ): Promise<void> { 64 + const { record, ownerDid, rkey } = entry; 65 + const payload: WebhookPayload = { 66 + id: randomUUID(), 67 + event: eventKind, 68 + did: eventDid, 69 + collection: eventCollection, 70 + rkey: eventRkey, 71 + cid: eventCid, 72 + record: eventRecord, 73 + timestamp: new Date().toISOString(), 74 + }; 75 + 76 + const body = JSON.stringify(payload); 77 + const signature = record.secret ? sign(record.secret, body) : undefined; 78 + 79 + for (let attempt_n = 1; attempt_n <= config.deliveryMaxRetries; attempt_n++) { 80 + try { 81 + await attempt(record.url, body, signature); 82 + logger.info(`[delivery] ok ${ownerDid}/${rkey} → ${record.url}`); 83 + return; 84 + } catch (err) { 85 + const isLast = attempt_n === config.deliveryMaxRetries; 86 + if (isLast) { 87 + logger.warn(`Failed to deliver webhook ${ownerDid}/${rkey} → ${record.url} after ${attempt_n} attempts`, { err }); 88 + } else { 89 + const delay = 1000 * 2 ** (attempt_n - 1); 90 + await new Promise(r => setTimeout(r, delay)); 91 + } 92 + } 93 + } 94 + }
+154
apps/webhook-service/src/lib/firehose.ts
··· 1 + import { IdResolver } from '@atproto/identity'; 2 + import { BunFirehose, isBun, type CommitEvt, type Event } from '@wispplace/bun-firehose'; 3 + import { Firehose } from '@atproto/sync'; 4 + import type { Main as WhRecord } from '@wispplace/lexicons/types/place/wisp/v2/wh'; 5 + import { createLogger } from '@wispplace/observability'; 6 + import { config } from '../config'; 7 + import { getCached, setCached, invalidate } from './registry'; 8 + import { upsertWebhookRecord, deleteWebhookRecord, findWebhooksForDid, findBacklinkWebhooks } from './db'; 9 + import { matchWebhooks } from './matcher'; 10 + import { deliverWebhook } from './delivery'; 11 + 12 + const logger = createLogger('webhook-service:firehose'); 13 + const idResolver = new IdResolver(); 14 + 15 + let lastEventTime = Date.now(); 16 + let isConnected = false; 17 + 18 + export function getFirehoseHealth() { 19 + return { 20 + connected: isConnected, 21 + lastEventTime, 22 + timeSinceLastEvent: Date.now() - lastEventTime, 23 + healthy: isConnected && (Date.now() - lastEventTime < 60000), 24 + }; 25 + } 26 + 27 + async function getWebhooksForEvent(eventDid: string) { 28 + // Direct scope matches: cached by eventDid 29 + let direct = getCached(eventDid); 30 + if (!direct) { 31 + direct = await findWebhooksForDid(eventDid); 32 + setCached(eventDid, direct); 33 + } 34 + 35 + // Backlink matches: cached under a fixed key 36 + let backlink = getCached('__backlinks__'); 37 + if (!backlink) { 38 + backlink = await findBacklinkWebhooks(); 39 + setCached('__backlinks__', backlink); 40 + } 41 + 42 + // Combine, deduplicate by ownerDid/rkey 43 + const seen = new Set(direct.map(e => `${e.ownerDid}/${e.rkey}`)); 44 + const combined = [...direct]; 45 + for (const entry of backlink) { 46 + const k = `${entry.ownerDid}/${entry.rkey}`; 47 + if (!seen.has(k)) { 48 + seen.add(k); 49 + combined.push(entry); 50 + } 51 + } 52 + return combined; 53 + } 54 + 55 + async function handleEvent(evt: Event | CommitEvt): Promise<void> { 56 + try { 57 + lastEventTime = Date.now(); 58 + 59 + if (!('event' in evt)) return; 60 + if (evt.event !== 'create' && evt.event !== 'update' && evt.event !== 'delete') return; 61 + const { did, collection, rkey, record, cid, event } = evt as CommitEvt; 62 + 63 + // Keep DB up to date and invalidate cache when webhook records change 64 + if (collection === 'place.wisp.v2.wh') { 65 + logger.info(`[wh] Received ${event} for ${did}/${rkey}`); 66 + if (event === 'delete') { 67 + deleteWebhookRecord(did, rkey).catch(err => 68 + logger.error(`[DB] Failed to delete webhook ${did}/${rkey}`, err) 69 + ); 70 + } else if (record) { 71 + logger.debug(`[wh] raw record: ${JSON.stringify(record)}`); 72 + const wh = record as WhRecord; 73 + if (!wh.scope?.aturi || !wh.url) { 74 + logger.error(`[wh] Skipping ${did}/${rkey} — record failed validation`, { record }); 75 + } else { 76 + logger.info(`[wh] scope=${wh.scope.aturi} url=${wh.url} enabled=${wh.enabled ?? true}`); 77 + upsertWebhookRecord(did, rkey, wh).catch(err => 78 + logger.error(`[DB] Failed to upsert webhook ${did}/${rkey}`, err) 79 + ); 80 + } 81 + } else { 82 + logger.warn(`[wh] ${event} ${did}/${rkey} — record missing from commit`); 83 + } 84 + invalidate(did); 85 + invalidate('__backlinks__'); 86 + return; 87 + } 88 + 89 + // Lookup webhooks for this event (cache-first) 90 + const candidates = await getWebhooksForEvent(did); 91 + if (candidates.length === 0) return; 92 + 93 + const matched = matchWebhooks(candidates, did, collection, rkey, event, record); 94 + if (matched.length === 0) return; 95 + 96 + logger.info(`[deliver] ${event} ${did}/${collection}/${rkey} → ${matched.length} webhook(s)`); 97 + 98 + await Promise.allSettled( 99 + matched.map(entry => 100 + deliverWebhook(entry, did, collection, rkey, event, cid?.toString(), record) 101 + ) 102 + ); 103 + } catch (err) { 104 + logger.error('Unexpected error in handleEvent', err); 105 + } 106 + } 107 + 108 + function handleError(err: Error): void { 109 + logger.error('Firehose error', err); 110 + } 111 + 112 + let firehoseHandle: { destroy: () => void } | null = null; 113 + 114 + export function startFirehose(): void { 115 + logger.info(`Starting firehose (runtime: ${isBun ? 'Bun' : 'Node.js'})`); 116 + isConnected = true; 117 + 118 + if (isBun) { 119 + const f = new BunFirehose({ 120 + idResolver, 121 + service: config.firehoseService, 122 + unauthenticatedCommits: true, 123 + handleEvent, 124 + onError: handleError, 125 + }); 126 + f.start(); 127 + firehoseHandle = { destroy: () => f.destroy() }; 128 + } else { 129 + const f = new Firehose({ 130 + idResolver, 131 + service: config.firehoseService, 132 + handleEvent: handleEvent as any, 133 + onError: handleError, 134 + }); 135 + f.start(); 136 + firehoseHandle = { destroy: () => f.destroy() }; 137 + } 138 + 139 + setInterval(() => { 140 + const health = getFirehoseHealth(); 141 + if (health.timeSinceLastEvent > 30000) { 142 + logger.warn(`No firehose events for ${Math.round(health.timeSinceLastEvent / 1000)}s`); 143 + } else { 144 + logger.info(`Firehose alive, last event ${Math.round(health.timeSinceLastEvent / 1000)}s ago`); 145 + } 146 + }, 30000); 147 + } 148 + 149 + export function stopFirehose(): void { 150 + logger.info('Stopping firehose'); 151 + isConnected = false; 152 + firehoseHandle?.destroy(); 153 + firehoseHandle = null; 154 + }
+117
apps/webhook-service/src/lib/matcher.ts
··· 1 + import type { WebhookEntry } from './db'; 2 + 3 + export type EventKind = 'create' | 'update' | 'delete'; 4 + 5 + interface ParsedAtUri { 6 + did: string; 7 + collection?: string; 8 + rkey?: string; 9 + } 10 + 11 + function parseAtUri(aturi: string): ParsedAtUri | null { 12 + const withoutScheme = aturi.replace(/^at:\/\//, ''); 13 + const parts = withoutScheme.split('/'); 14 + const did = parts[0]; 15 + if (!did) return null; 16 + return { 17 + did, 18 + collection: parts[1] || undefined, 19 + rkey: parts[2] || undefined, 20 + }; 21 + } 22 + 23 + /** Matches a collection segment against a glob pattern */ 24 + function matchesGlob(pattern: string, value: string): boolean { 25 + if (!pattern.includes('*')) return pattern === value; 26 + const escaped = pattern.split('*').map(s => s.replace(/[.+?^${}()|[\]\\]/g, '\\$&')); 27 + return new RegExp(`^${escaped.join('.*')}$`).test(value); 28 + } 29 + 30 + /** 31 + * Checks whether a serialised record body contains a reference to the given DID/collection. 32 + * When collection contains a glob, scans for any `at://did/<collection>` URI that matches. 33 + */ 34 + function containsReference(record: unknown, did: string, collection?: string): boolean { 35 + const json = JSON.stringify(record); 36 + 37 + if (!collection) { 38 + return json.includes(`at://${did}`) || json.includes(`"${did}"`); 39 + } 40 + 41 + if (!collection.includes('*')) { 42 + return json.includes(`at://${did}/${collection}`); 43 + } 44 + 45 + // Glob collection: scan for all at://did/... URIs and match the collection segment 46 + const prefix = `at://${did}/`; 47 + let idx = json.indexOf(prefix); 48 + while (idx !== -1) { 49 + const rest = json.slice(idx + prefix.length); 50 + const end = rest.search(/[/"\\]/); 51 + const col = end === -1 ? rest : rest.slice(0, end); 52 + if (col && matchesGlob(collection, col)) return true; 53 + idx = json.indexOf(prefix, idx + prefix.length); 54 + } 55 + return false; 56 + } 57 + 58 + /** 59 + * Filters a set of webhook candidates against a firehose event. 60 + * 61 + * A webhook matches if: 62 + * - It is enabled 63 + * - The event kind is in its `events` filter (or no filter is set) 64 + * - **Direct match**: the event DID/collection/rkey falls within the webhook's scope AT-URI 65 + * (collection supports glob patterns, e.g. `app.bsky.*`) 66 + * - **Backlink match**: `scope.backlinks` is true and the serialised record body contains 67 + * a reference to the scope DID/collection 68 + */ 69 + export function matchWebhooks( 70 + webhooks: WebhookEntry[], 71 + eventDid: string, 72 + eventCollection: string, 73 + eventRkey: string, 74 + eventKind: EventKind, 75 + eventRecord: unknown, 76 + ): WebhookEntry[] { 77 + const matched: WebhookEntry[] = []; 78 + 79 + for (const entry of webhooks) { 80 + const { record } = entry; 81 + 82 + if (record.enabled === false) continue; 83 + 84 + if (record.events && record.events.length > 0) { 85 + if (!record.events.includes(eventKind)) continue; 86 + } 87 + 88 + const scope = parseAtUri(record.scope.aturi); 89 + if (!scope) continue; 90 + 91 + const backlinks = record.scope.backlinks === true; 92 + 93 + let directMatch = false; 94 + if (scope.did === eventDid) { 95 + if (!scope.collection) { 96 + directMatch = true; 97 + } else if (matchesGlob(scope.collection, eventCollection)) { 98 + if (!scope.rkey || scope.rkey === eventRkey) { 99 + directMatch = true; 100 + } 101 + } 102 + } 103 + 104 + if (directMatch) { 105 + matched.push(entry); 106 + continue; 107 + } 108 + 109 + if (backlinks && eventDid !== scope.did && eventRecord != null) { 110 + if (containsReference(eventRecord, scope.did, scope.collection)) { 111 + matched.push(entry); 112 + } 113 + } 114 + } 115 + 116 + return matched; 117 + }
+24
apps/webhook-service/src/lib/registry.ts
··· 1 + import { LRUCache } from 'lru-cache'; 2 + import type { WebhookEntry } from './db'; 3 + 4 + /** 5 + * LRU cache of DB query results, keyed by scope DID or `'__backlinks__'`. 6 + * Avoids hitting the DB on every firehose event for DIDs with no webhooks. 7 + * Invalidated when a place.wisp.v2.wh record changes for a given DID. 8 + */ 9 + const cache = new LRUCache<string, WebhookEntry[]>({ 10 + max: parseInt(process.env.WEBHOOK_CACHE_MAX || '1000', 10), 11 + ttl: parseInt(process.env.WEBHOOK_CACHE_TTL_MS || '60000', 10), 12 + }); 13 + 14 + export function getCached(scopeDid: string): WebhookEntry[] | undefined { 15 + return cache.get(scopeDid); 16 + } 17 + 18 + export function setCached(scopeDid: string, entries: WebhookEntry[]): void { 19 + cache.set(scopeDid, entries); 20 + } 21 + 22 + export function invalidate(scopeDid: string): void { 23 + cache.delete(scopeDid); 24 + }
+36
apps/webhook-service/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + /* Base Options */ 4 + "esModuleInterop": true, 5 + "skipLibCheck": true, 6 + "target": "es2022", 7 + "allowJs": true, 8 + "resolveJsonModule": true, 9 + "moduleDetection": "force", 10 + "isolatedModules": true, 11 + "verbatimModuleSyntax": true, 12 + 13 + /* Strictness */ 14 + "strict": true, 15 + "noUncheckedIndexedAccess": true, 16 + "noImplicitOverride": true, 17 + "forceConsistentCasingInFileNames": true, 18 + 19 + /* Transpiling with TypeScript */ 20 + "module": "ESNext", 21 + "moduleResolution": "bundler", 22 + "outDir": "dist", 23 + "sourceMap": true, 24 + 25 + /* Code doesn't run in DOM */ 26 + "lib": ["es2022"], 27 + 28 + /* Workspace Paths */ 29 + "baseUrl": ".", 30 + "paths": { 31 + "@wispplace/*": ["../../packages/@wispplace/*/src"] 32 + } 33 + }, 34 + "include": ["src/**/*"], 35 + "exclude": ["node_modules", "dist"] 36 + }
+121 -3
bun.lock
··· 137 137 "typescript": "^5.9.3", 138 138 }, 139 139 }, 140 + "apps/webhook-service": { 141 + "name": "webhook-service", 142 + "dependencies": { 143 + "@atproto/identity": "^0.4.9", 144 + "@atproto/sync": "^0.1.40", 145 + "@atproto/syntax": "^0.5.0", 146 + "@wispplace/bun-firehose": "workspace:*", 147 + "@wispplace/lexicons": "workspace:*", 148 + "@wispplace/observability": "workspace:*", 149 + "lru-cache": "^11.2.6", 150 + }, 151 + "devDependencies": { 152 + "@types/bun": "latest", 153 + "typescript": "^5.9.3", 154 + }, 155 + }, 140 156 "cli": { 141 157 "name": "wispctl", 142 158 "version": "1.0.10", ··· 385 401 386 402 "@atproto/did": ["@atproto/did@0.2.4", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-nxNiCgXeo7pfjojq9fpfZxCO0X0xUipNVKW+AHNZwQKiUDt6zYL0VXEfm8HBUwQOCmKvj2pRRSM1Cur+tUWk3g=="], 387 403 388 - "@atproto/identity": ["@atproto/identity@0.4.10", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4" } }, "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ=="], 404 + "@atproto/identity": ["@atproto/identity@0.4.12", "", { "dependencies": { "@atproto/common-web": "^0.4.17", "@atproto/crypto": "^0.4.5" } }, "sha512-P+Jn0HvKhIh1tps5n3xGrCxt+XiFWzp4kdgloyFhFmVLwjDU547DQkWx4r5Vhuiah7fRZGVSlk39R4U6SPrACg=="], 389 405 390 406 "@atproto/jwk": ["@atproto/jwk@0.6.0", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw=="], 391 407 ··· 397 413 398 414 "@atproto/lex-cli": ["@atproto/lex-cli@0.9.8", "", { "dependencies": { "@atproto/lexicon": "^0.6.0", "@atproto/syntax": "^0.4.2", "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-0ebVyp12i3S8oE77+BxahbTmyrXcqeC9GTx2HGa/PA9KjnThapkGkgVQjIWw74DNQprzbg9EkiQsaKU2xFYhmA=="], 399 415 416 + "@atproto/lex-client": ["@atproto/lex-client@0.0.15", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "@atproto/lex-json": "^0.0.13", "@atproto/lex-schema": "^0.0.14", "tslib": "^2.8.1" } }, "sha512-j/eZGCdkhABU8Z868Y/gn909hS77rOCdMqtOaQdflEaKUKiAo2/gqeTpoAjHBnL5Rzz255wj9qZMqZTR/Ygwxw=="], 417 + 400 418 "@atproto/lex-data": ["@atproto/lex-data@0.0.9", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-1slwe4sG0cyWtsq16+rBoWIxNDqGPkkvN+PV6JuzA7dgUK9bjUmXBGQU4eZlUPSS43X1Nhmr/9VjgKmEzU9vDw=="], 401 419 402 420 "@atproto/lex-json": ["@atproto/lex-json@0.0.9", "", { "dependencies": { "@atproto/lex-data": "0.0.9", "tslib": "^2.8.1" } }, "sha512-Q2v1EVZcnd+ndyZj1r2UlGikA7q6It24CFPLbxokcf5Ba4RBupH8IkkQX7mqUDSRWPgQdmZYIdW9wUln+MKDqw=="], 403 421 404 - "@atproto/lexicon": ["@atproto/lexicon@0.6.1", "", { "dependencies": { "@atproto/common-web": "^0.4.13", "@atproto/syntax": "^0.4.3", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-/vI1kVlY50Si+5MXpvOucelnYwb0UJ6Qto5mCp+7Q5C+Jtp+SoSykAPVvjVtTnQUH2vrKOFOwpb3C375vSKzXw=="], 422 + "@atproto/lex-schema": ["@atproto/lex-schema@0.0.14", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "@atproto/syntax": "^0.5.0", "tslib": "^2.8.1" } }, "sha512-xUxFuXdgVVI1IBDXcQlanH7HuEo9Pk65DYifnhqFDzNRH9SZQxPvPO+rOxMG/bRHygPaI+A+UbXr+S7qpPYOLg=="], 423 + 424 + "@atproto/lexicon": ["@atproto/lexicon@0.6.2", "", { "dependencies": { "@atproto/common-web": "^0.4.18", "@atproto/syntax": "^0.5.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-p3Ly6hinVZW0ETuAXZMeUGwuMm3g8HvQMQ41yyEE6AL0hAkfeKFaZKos6BdBrr6CjkpbrDZqE8M+5+QOceysMw=="], 405 425 406 426 "@atproto/oauth-client": ["@atproto/oauth-client@0.5.13", "", { "dependencies": { "@atproto-labs/did-resolver": "0.2.5", "@atproto-labs/fetch": "0.2.3", "@atproto-labs/handle-resolver": "0.3.5", "@atproto-labs/identity-resolver": "0.3.5", "@atproto-labs/simple-store": "0.3.0", "@atproto-labs/simple-store-memory": "0.1.4", "@atproto/did": "0.2.4", "@atproto/jwk": "0.6.0", "@atproto/oauth-types": "0.6.1", "@atproto/xrpc": "0.7.7", "core-js": "^3", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-FLbqHkC7BAVZ90LHVzSxQf+s8ZNIQI4TsDuhYDyzi7lYtktFHDbgd88KuM2ClJFOtGCsSS17yR1Joy925tDSaA=="], 407 427 ··· 1449 1469 1450 1470 "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], 1451 1471 1452 - "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], 1472 + "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], 1453 1473 1454 1474 "lucide-react": ["lucide-react@0.546.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ=="], 1455 1475 ··· 1759 1779 1760 1780 "vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.18", "@vitest/browser-preview": "4.0.18", "@vitest/browser-webdriverio": "4.0.18", "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="], 1761 1781 1782 + "webhook-service": ["webhook-service@workspace:apps/webhook-service"], 1783 + 1762 1784 "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], 1763 1785 1764 1786 "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], ··· 1803 1825 1804 1826 "@atproto-labs/fetch-node/ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], 1805 1827 1828 + "@atproto-labs/simple-store-memory/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], 1829 + 1830 + "@atproto/api/@atproto/lexicon": ["@atproto/lexicon@0.6.1", "", { "dependencies": { "@atproto/common-web": "^0.4.13", "@atproto/syntax": "^0.4.3", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-/vI1kVlY50Si+5MXpvOucelnYwb0UJ6Qto5mCp+7Q5C+Jtp+SoSykAPVvjVtTnQUH2vrKOFOwpb3C375vSKzXw=="], 1831 + 1806 1832 "@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1807 1833 1808 1834 "@atproto/common/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1809 1835 1836 + "@atproto/identity/@atproto/common-web": ["@atproto/common-web@0.4.18", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "@atproto/lex-json": "^0.0.13", "@atproto/syntax": "^0.5.0", "zod": "^3.23.8" } }, "sha512-ilImzP+9N/mtse440kN60pGrEzG7wi4xsV13nGeLrS+Zocybc/ISOpKlbZM13o+twPJ+Q7veGLw9CtGg0GAFoQ=="], 1837 + 1810 1838 "@atproto/jwk/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1811 1839 1840 + "@atproto/lex-cli/@atproto/lexicon": ["@atproto/lexicon@0.6.1", "", { "dependencies": { "@atproto/common-web": "^0.4.13", "@atproto/syntax": "^0.4.3", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-/vI1kVlY50Si+5MXpvOucelnYwb0UJ6Qto5mCp+7Q5C+Jtp+SoSykAPVvjVtTnQUH2vrKOFOwpb3C375vSKzXw=="], 1841 + 1812 1842 "@atproto/lex-cli/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], 1813 1843 1844 + "@atproto/lex-client/@atproto/lex-data": ["@atproto/lex-data@0.0.13", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-7Z7RwZ1Y/JzBF/Tcn/I4UJ/vIGfh5zn1zjv0KX+flke2JtgFkSE8uh2hOtqgBQMNqE3zdJFM+dcSWln86hR3MQ=="], 1845 + 1846 + "@atproto/lex-client/@atproto/lex-json": ["@atproto/lex-json@0.0.13", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "tslib": "^2.8.1" } }, "sha512-hwLhkKaIHulGJpt0EfXAEWdrxqM2L1tV/tvilzhMp3QxPqYgXchFnrfVmLsyFDx6P6qkH1GsX/XC2V36U0UlPQ=="], 1847 + 1814 1848 "@atproto/lex-data/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1815 1849 1850 + "@atproto/lex-schema/@atproto/lex-data": ["@atproto/lex-data@0.0.13", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-7Z7RwZ1Y/JzBF/Tcn/I4UJ/vIGfh5zn1zjv0KX+flke2JtgFkSE8uh2hOtqgBQMNqE3zdJFM+dcSWln86hR3MQ=="], 1851 + 1852 + "@atproto/lex-schema/@atproto/syntax": ["@atproto/syntax@0.5.0", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw=="], 1853 + 1854 + "@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.4.18", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "@atproto/lex-json": "^0.0.13", "@atproto/syntax": "^0.5.0", "zod": "^3.23.8" } }, "sha512-ilImzP+9N/mtse440kN60pGrEzG7wi4xsV13nGeLrS+Zocybc/ISOpKlbZM13o+twPJ+Q7veGLw9CtGg0GAFoQ=="], 1855 + 1856 + "@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.5.0", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw=="], 1857 + 1816 1858 "@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1817 1859 1818 1860 "@atproto/oauth-client/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1819 1861 1862 + "@atproto/repo/@atproto/lexicon": ["@atproto/lexicon@0.6.1", "", { "dependencies": { "@atproto/common-web": "^0.4.13", "@atproto/syntax": "^0.4.3", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-/vI1kVlY50Si+5MXpvOucelnYwb0UJ6Qto5mCp+7Q5C+Jtp+SoSykAPVvjVtTnQUH2vrKOFOwpb3C375vSKzXw=="], 1863 + 1820 1864 "@atproto/repo/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1821 1865 1866 + "@atproto/sync/@atproto/identity": ["@atproto/identity@0.4.10", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4" } }, "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ=="], 1867 + 1868 + "@atproto/sync/@atproto/lexicon": ["@atproto/lexicon@0.6.1", "", { "dependencies": { "@atproto/common-web": "^0.4.13", "@atproto/syntax": "^0.4.3", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-/vI1kVlY50Si+5MXpvOucelnYwb0UJ6Qto5mCp+7Q5C+Jtp+SoSykAPVvjVtTnQUH2vrKOFOwpb3C375vSKzXw=="], 1869 + 1822 1870 "@atproto/sync/@atproto/xrpc-server": ["@atproto/xrpc-server@0.10.10", "", { "dependencies": { "@atproto/common": "^0.5.9", "@atproto/crypto": "^0.4.5", "@atproto/lex-cbor": "0.0.9", "@atproto/lex-data": "0.0.9", "@atproto/lexicon": "^0.6.1", "@atproto/ws-client": "^0.0.4", "@atproto/xrpc": "^0.7.7", "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-USDjOZGiletqzuWHC3Q2fk30hJDk4uYt6KPgvnZidShCouTf3hzwJZ8d2eOKOofSjGXW+GC0QYp7fYJFn6lZ2Q=="], 1823 1871 1824 1872 "@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 1825 1873 1826 1874 "@atproto/ws-client/@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=="], 1875 + 1876 + "@atproto/xrpc/@atproto/lexicon": ["@atproto/lexicon@0.6.1", "", { "dependencies": { "@atproto/common-web": "^0.4.13", "@atproto/syntax": "^0.4.3", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-/vI1kVlY50Si+5MXpvOucelnYwb0UJ6Qto5mCp+7Q5C+Jtp+SoSykAPVvjVtTnQUH2vrKOFOwpb3C375vSKzXw=="], 1827 1877 1828 1878 "@atproto/xrpc-server/@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=="], 1829 1879 ··· 1985 2035 1986 2036 "@wispplace/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=="], 1987 2037 2038 + "@wispplace/observability/bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], 2039 + 1988 2040 "@wispplace/tiered-storage/@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], 1989 2041 1990 2042 "accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], ··· 2000 2052 "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 2001 2053 2002 2054 "firehose-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=="], 2055 + 2056 + "firehose-service/@atproto/identity": ["@atproto/identity@0.4.10", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4" } }, "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ=="], 2003 2057 2004 2058 "firehose-service/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], 2005 2059 ··· 2027 2081 2028 2082 "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 2029 2083 2084 + "webhook-service/@atproto/sync": ["@atproto/sync@0.1.40", "", { "dependencies": { "@atproto/common": "^0.5.14", "@atproto/identity": "^0.4.12", "@atproto/lexicon": "^0.6.2", "@atproto/repo": "^0.8.12", "@atproto/syntax": "^0.5.0", "@atproto/xrpc-server": "^0.10.15", "multiformats": "^9.9.0", "p-queue": "^6.6.2", "ws": "^8.12.0" } }, "sha512-tnzFPqKBXPXpuGvx87sjqLHgEzIOT/QfQZUp0YOUHtpipBgvijEnNXV9XeTIgiUAv69wyRR+6YjJkLCYfHpVwQ=="], 2085 + 2086 + "webhook-service/@atproto/syntax": ["@atproto/syntax@0.5.0", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw=="], 2087 + 2088 + "webhook-service/@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], 2089 + 2030 2090 "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=="], 2031 2091 2092 + "wisp-hosting-service/@atproto/identity": ["@atproto/identity@0.4.10", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4" } }, "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ=="], 2093 + 2032 2094 "wisp-hosting-service/@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=="], 2033 2095 2034 2096 "wisp-hosting-service/@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], ··· 2036 2098 "wisp-hosting-service/@types/mime-types": ["@types/mime-types@2.1.4", "", {}, "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w=="], 2037 2099 2038 2100 "wisp-hosting-service/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], 2101 + 2102 + "wispctl/@atproto/identity": ["@atproto/identity@0.4.10", "", { "dependencies": { "@atproto/common-web": "^0.4.4", "@atproto/crypto": "^0.4.4" } }, "sha512-nQbzDLXOhM8p/wo0cTh5DfMSOSHzj6jizpodX37LJ4S1TZzumSxAjHEZa5Rev3JaoD5uSWMVE0MmKEGWkPPvfQ=="], 2039 2103 2040 2104 "wispctl/@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], 2041 2105 ··· 2065 2129 2066 2130 "@atcute/tangled/@atcute/lexicons/@atcute/util-text": ["@atcute/util-text@0.0.1", "", { "dependencies": { "unicode-segmenter": "^0.14.4" } }, "sha512-t1KZqvn0AYy+h2KcJyHnKF9aEqfRfMUmyY8j1ELtAEIgqN9CxINAjxnoRCJIFUlvWzb+oY3uElQL/Vyk3yss0g=="], 2067 2131 2132 + "@atproto/identity/@atproto/common-web/@atproto/lex-data": ["@atproto/lex-data@0.0.13", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-7Z7RwZ1Y/JzBF/Tcn/I4UJ/vIGfh5zn1zjv0KX+flke2JtgFkSE8uh2hOtqgBQMNqE3zdJFM+dcSWln86hR3MQ=="], 2133 + 2134 + "@atproto/identity/@atproto/common-web/@atproto/lex-json": ["@atproto/lex-json@0.0.13", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "tslib": "^2.8.1" } }, "sha512-hwLhkKaIHulGJpt0EfXAEWdrxqM2L1tV/tvilzhMp3QxPqYgXchFnrfVmLsyFDx6P6qkH1GsX/XC2V36U0UlPQ=="], 2135 + 2136 + "@atproto/identity/@atproto/common-web/@atproto/syntax": ["@atproto/syntax@0.5.0", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw=="], 2137 + 2138 + "@atproto/lex-cli/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2139 + 2140 + "@atproto/lex-client/@atproto/lex-data/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2141 + 2142 + "@atproto/lex-schema/@atproto/lex-data/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2143 + 2144 + "@atproto/lexicon/@atproto/common-web/@atproto/lex-data": ["@atproto/lex-data@0.0.13", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-7Z7RwZ1Y/JzBF/Tcn/I4UJ/vIGfh5zn1zjv0KX+flke2JtgFkSE8uh2hOtqgBQMNqE3zdJFM+dcSWln86hR3MQ=="], 2145 + 2146 + "@atproto/lexicon/@atproto/common-web/@atproto/lex-json": ["@atproto/lex-json@0.0.13", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "tslib": "^2.8.1" } }, "sha512-hwLhkKaIHulGJpt0EfXAEWdrxqM2L1tV/tvilzhMp3QxPqYgXchFnrfVmLsyFDx6P6qkH1GsX/XC2V36U0UlPQ=="], 2147 + 2068 2148 "@atproto/sync/@atproto/xrpc-server/@atproto/ws-client": ["@atproto/ws-client@0.0.4", "", { "dependencies": { "@atproto/common": "^0.5.3", "ws": "^8.12.0" } }, "sha512-dox1XIymuC7/ZRhUqKezIGgooZS45C6vHCfu0PnWjfvsLCK2kAlnvX4IBkA/WpcoijDhQ9ejChnFbo/sLmgvAg=="], 2069 2149 2070 2150 "@atproto/sync/@atproto/xrpc-server/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], ··· 2076 2156 "@atproto/xrpc-server/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2077 2157 2078 2158 "@atproto/xrpc-server/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 2159 + 2160 + "@atproto/xrpc/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2079 2161 2080 2162 "@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=="], 2081 2163 ··· 2273 2355 2274 2356 "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.2", "", { "os": "win32", "cpu": "x64" }, "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ=="], 2275 2357 2358 + "webhook-service/@atproto/sync/@atproto/common": ["@atproto/common@0.5.14", "", { "dependencies": { "@atproto/common-web": "^0.4.18", "@atproto/lex-cbor": "^0.0.14", "@atproto/lex-data": "^0.0.13", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-FnhTppvJw8I1AuvEkL9JREFwmM6ciYfSlQ0Zo6neiJIhTf1wf5/ONeFSYKu1/dxC63JEratGIAfVjSBJJZi7sg=="], 2359 + 2360 + "webhook-service/@atproto/sync/@atproto/xrpc-server": ["@atproto/xrpc-server@0.10.15", "", { "dependencies": { "@atproto/common": "^0.5.14", "@atproto/crypto": "^0.4.5", "@atproto/lex-cbor": "^0.0.14", "@atproto/lex-client": "^0.0.15", "@atproto/lex-data": "^0.0.13", "@atproto/lex-json": "^0.0.13", "@atproto/lex-schema": "^0.0.14", "@atproto/lexicon": "^0.6.2", "@atproto/ws-client": "^0.0.4", "@atproto/xrpc": "^0.7.7", "express": "^4.17.2", "http-errors": "^2.0.0", "mime-types": "^2.1.35", "rate-limiter-flexible": "^2.4.1", "ws": "^8.12.0" } }, "sha512-ryGVAKuLU0Nqkv25gsPzffJhxnCXwPOyBi+sNAfP7n+mDDwcumH6RWySEHoDDrTsGvAP2r8o2ZrLCWuzKm7vSg=="], 2361 + 2362 + "webhook-service/@atproto/sync/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2363 + 2364 + "webhook-service/@types/bun/bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], 2365 + 2276 2366 "wisp-hosting-service/@atproto/api/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2277 2367 2278 2368 "wisp-hosting-service/@atproto/lexicon/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], ··· 2280 2370 "wisp-hosting-service/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 2281 2371 2282 2372 "wispctl/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2373 + 2374 + "@atproto/identity/@atproto/common-web/@atproto/lex-data/multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 2283 2375 2284 2376 "@atproto/sync/@atproto/xrpc-server/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 2285 2377 ··· 2315 2407 2316 2408 "@types/bun/bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2317 2409 2410 + "webhook-service/@atproto/sync/@atproto/common/@atproto/common-web": ["@atproto/common-web@0.4.18", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "@atproto/lex-json": "^0.0.13", "@atproto/syntax": "^0.5.0", "zod": "^3.23.8" } }, "sha512-ilImzP+9N/mtse440kN60pGrEzG7wi4xsV13nGeLrS+Zocybc/ISOpKlbZM13o+twPJ+Q7veGLw9CtGg0GAFoQ=="], 2411 + 2412 + "webhook-service/@atproto/sync/@atproto/common/@atproto/lex-cbor": ["@atproto/lex-cbor@0.0.14", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "tslib": "^2.8.1" } }, "sha512-zeqxaKAifR8qlFKg4A6t1RCT8TcjeDnIXLtp3QnDu0QoxslxsmcsrqNrrgmka8w+bYW2+h/rT9MPWglkT7vHyw=="], 2413 + 2414 + "webhook-service/@atproto/sync/@atproto/common/@atproto/lex-data": ["@atproto/lex-data@0.0.13", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-7Z7RwZ1Y/JzBF/Tcn/I4UJ/vIGfh5zn1zjv0KX+flke2JtgFkSE8uh2hOtqgBQMNqE3zdJFM+dcSWln86hR3MQ=="], 2415 + 2416 + "webhook-service/@atproto/sync/@atproto/xrpc-server/@atproto/lex-cbor": ["@atproto/lex-cbor@0.0.14", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "tslib": "^2.8.1" } }, "sha512-zeqxaKAifR8qlFKg4A6t1RCT8TcjeDnIXLtp3QnDu0QoxslxsmcsrqNrrgmka8w+bYW2+h/rT9MPWglkT7vHyw=="], 2417 + 2418 + "webhook-service/@atproto/sync/@atproto/xrpc-server/@atproto/lex-data": ["@atproto/lex-data@0.0.13", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-7Z7RwZ1Y/JzBF/Tcn/I4UJ/vIGfh5zn1zjv0KX+flke2JtgFkSE8uh2hOtqgBQMNqE3zdJFM+dcSWln86hR3MQ=="], 2419 + 2420 + "webhook-service/@atproto/sync/@atproto/xrpc-server/@atproto/lex-json": ["@atproto/lex-json@0.0.13", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "tslib": "^2.8.1" } }, "sha512-hwLhkKaIHulGJpt0EfXAEWdrxqM2L1tV/tvilzhMp3QxPqYgXchFnrfVmLsyFDx6P6qkH1GsX/XC2V36U0UlPQ=="], 2421 + 2422 + "webhook-service/@atproto/sync/@atproto/xrpc-server/@atproto/ws-client": ["@atproto/ws-client@0.0.4", "", { "dependencies": { "@atproto/common": "^0.5.3", "ws": "^8.12.0" } }, "sha512-dox1XIymuC7/ZRhUqKezIGgooZS45C6vHCfu0PnWjfvsLCK2kAlnvX4IBkA/WpcoijDhQ9ejChnFbo/sLmgvAg=="], 2423 + 2424 + "webhook-service/@atproto/sync/@atproto/xrpc-server/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], 2425 + 2318 2426 "@opentelemetry/exporter-logs-otlp-grpc/@opentelemetry/otlp-transformer/protobufjs/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2319 2427 2320 2428 "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/protobufjs/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], ··· 2335 2443 2336 2444 "@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-transformer/protobufjs/@types/node": ["@types/node@22.19.7", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw=="], 2337 2445 2446 + "webhook-service/@atproto/sync/@atproto/common/@atproto/common-web/@atproto/lex-json": ["@atproto/lex-json@0.0.13", "", { "dependencies": { "@atproto/lex-data": "^0.0.13", "tslib": "^2.8.1" } }, "sha512-hwLhkKaIHulGJpt0EfXAEWdrxqM2L1tV/tvilzhMp3QxPqYgXchFnrfVmLsyFDx6P6qkH1GsX/XC2V36U0UlPQ=="], 2447 + 2448 + "webhook-service/@atproto/sync/@atproto/xrpc-server/@atproto/ws-client/@atproto/common": ["@atproto/common@0.5.9", "", { "dependencies": { "@atproto/common-web": "^0.4.13", "@atproto/lex-cbor": "0.0.9", "@atproto/lex-data": "0.0.9", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "pino": "^8.21.0" } }, "sha512-rzl8dB7ErpA/VUgCidahUtbxEph50J4g7j68bZmlwwrHlrtvTe8DjrwH5EUFEcegl9dadIhcVJ3qi0kPKEUr+g=="], 2449 + 2450 + "webhook-service/@atproto/sync/@atproto/xrpc-server/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 2451 + 2338 2452 "@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-transformer/protobufjs/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2339 2453 2340 2454 "@opentelemetry/sdk-node/@opentelemetry/exporter-metrics-otlp-proto/@opentelemetry/otlp-transformer/protobufjs/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], 2455 + 2456 + "webhook-service/@atproto/sync/@atproto/xrpc-server/@atproto/ws-client/@atproto/common/@atproto/lex-cbor": ["@atproto/lex-cbor@0.0.9", "", { "dependencies": { "@atproto/lex-data": "0.0.9", "tslib": "^2.8.1" } }, "sha512-szkS569j1eZsIxZKh2VZHVq7pSpewy1wHh8c6nVYekHfYcJhFkevQq/DjTeatZ7YZKNReGYthQulgaZq2ytfWQ=="], 2457 + 2458 + "webhook-service/@atproto/sync/@atproto/xrpc-server/@atproto/ws-client/@atproto/common/@atproto/lex-data": ["@atproto/lex-data@0.0.9", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-1slwe4sG0cyWtsq16+rBoWIxNDqGPkkvN+PV6JuzA7dgUK9bjUmXBGQU4eZlUPSS43X1Nhmr/9VjgKmEzU9vDw=="], 2341 2459 } 2342 2460 }
+11 -32
lexicons/webhook-v1.json
··· 4 4 "defs": { 5 5 "main": { 6 6 "type": "record", 7 - "description": "Webhook configuration for AT Protocol record events. Fires an HTTP POST to a URL before or after a matching record event is processed.", 7 + "description": "Webhook configuration for AT Protocol record events. Fires an HTTP POST to a URL when a matching record event is observed on the firehose.", 8 8 "key": "any", 9 9 "record": { 10 10 "type": "object", 11 11 "required": [ 12 12 "scope", 13 13 "url", 14 - "phase", 15 - "events", 16 14 "createdAt" 17 15 ], 18 16 "properties": { 19 17 "scope": { 20 - "type": "union", 21 - "refs": [ 22 - "#atUri", 23 - "#nsid" 24 - ], 25 - "description": "What to watch. An AT-URI scopes to a specific DID, collection, or record. An NSID watches that collection globally across all DIDs." 18 + "type": "ref", 19 + "ref": "#atUri", 20 + "description": "What to watch. An AT-URI scopes to a specific DID, collection, or record." 26 21 }, 27 22 "url": { 28 23 "type": "string", ··· 30 25 "maxLength": 2048, 31 26 "description": "HTTPS endpoint to POST the webhook payload to." 32 27 }, 33 - "phase": { 34 - "type": "string", 35 - "enum": [ 36 - "pre", 37 - "post" 38 - ], 39 - "description": "Whether the webhook should fire before ('pre') or after ('post') the record event is processed." 40 - }, 41 28 "events": { 42 29 "type": "array", 43 30 "items": { ··· 48 35 "delete" 49 36 ] 50 37 }, 51 - "description": "Which record events to trigger on. 'create' fires when a new record is created. 'update' fires when an existing record is updated. 'delete' fires when a record is deleted.", 38 + "description": "Which record events to trigger on. Defaults to all events if omitted.", 52 39 "maxLength": 3 53 40 }, 54 41 "secret": { 55 42 "type": "string", 43 + "maxLength": 256, 56 44 "description": "Optional secret used to sign the webhook payload with HMAC-SHA256. The signature is included in the 'X-Webhook-Signature' header of the webhook request." 57 45 }, 58 46 "enabled": { 59 47 "type": "boolean", 60 - "description": "Whether the webhook is active. Default to true if omitted." 48 + "description": "Whether the webhook is active. Defaults to true if omitted." 61 49 }, 62 50 "createdAt": { 63 51 "type": "string", ··· 77 65 "aturi": { 78 66 "type": "string", 79 67 "format": "at-uri" 80 - } 81 - } 82 - }, 83 - "nsid": { 84 - "type": "object", 85 - "description": "Watch all records of this collection type globally across any DID.", 86 - "required": [ 87 - "nsid" 88 - ], 89 - "properties": { 90 - "nsid": { 91 - "type": "string", 92 - "format": "nsid" 68 + }, 69 + "backlinks": { 70 + "type": "boolean", 71 + "description": "If true, also watch for records in any repo that reference this DID and collection." 93 72 } 94 73 } 95 74 }
+1
package.json
··· 7 7 "apps/main-app", 8 8 "apps/hosting-service", 9 9 "apps/firehose-service", 10 + "apps/webhook-service", 10 11 "cli" 11 12 ], 12 13 "dependencies": {
+4
packages/@wispplace/lexicons/package.json
··· 22 22 "types": "./src/types/place/wisp/subfs.ts", 23 23 "default": "./src/types/place/wisp/subfs.ts" 24 24 }, 25 + "./types/place/wisp/v2/wh": { 26 + "types": "./src/types/place/wisp/v2/wh.ts", 27 + "default": "./src/types/place/wisp/v2/wh.ts" 28 + }, 25 29 "./types/place/wisp/v2/domains": { 26 30 "types": "./src/types/place/wisp/v2/domains.ts", 27 31 "default": "./src/types/place/wisp/v2/domains.ts"
+20 -23
packages/@wispplace/lexicons/src/atcute/lexicons/types/place/wisp/v2/wh.ts
··· 7 7 /*#__PURE__*/ v.literal("place.wisp.v2.wh#atUri"), 8 8 ), 9 9 aturi: /*#__PURE__*/ v.resourceUriString(), 10 + /** 11 + * If true, also watch for records in any repo that reference this DID and collection. 12 + */ 13 + backlinks: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), 10 14 }); 11 15 const _mainSchema = /*#__PURE__*/ v.record( 12 16 /*#__PURE__*/ v.string(), ··· 17 21 */ 18 22 createdAt: /*#__PURE__*/ v.datetimeString(), 19 23 /** 20 - * Whether the webhook is active. Default to true if omitted. 24 + * Whether the webhook is active. Defaults to true if omitted. 21 25 */ 22 26 enabled: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), 23 27 /** 24 - * Which record events to trigger on. 'create' fires when a new record is created. 'update' fires when an existing record is updated. 'delete' fires when a record is deleted. 28 + * Which record events to trigger on. Defaults to all events if omitted. 25 29 * @maxLength 3 26 30 */ 27 - events: /*#__PURE__*/ v.constrain( 28 - /*#__PURE__*/ v.array( 29 - /*#__PURE__*/ v.literalEnum(["create", "delete", "update"]), 31 + events: /*#__PURE__*/ v.optional( 32 + /*#__PURE__*/ v.constrain( 33 + /*#__PURE__*/ v.array( 34 + /*#__PURE__*/ v.literalEnum(["create", "delete", "update"]), 35 + ), 36 + [/*#__PURE__*/ v.arrayLength(0, 3)], 30 37 ), 31 - [/*#__PURE__*/ v.arrayLength(0, 3)], 32 38 ), 33 39 /** 34 - * Whether the webhook should fire before ('pre') or after ('post') the record event is processed. 35 - */ 36 - phase: /*#__PURE__*/ v.literalEnum(["post", "pre"]), 37 - /** 38 - * What to watch. An AT-URI scopes to a specific DID, collection, or record. An NSID watches that collection globally across all DIDs. 40 + * What to watch. An AT-URI scopes to a specific DID, collection, or record. 39 41 */ 40 42 get scope() { 41 - return /*#__PURE__*/ v.variant([atUriSchema, nsidSchema]); 43 + return atUriSchema; 42 44 }, 43 45 /** 44 46 * Optional secret used to sign the webhook payload with HMAC-SHA256. The signature is included in the 'X-Webhook-Signature' header of the webhook request. 47 + * @maxLength 256 45 48 */ 46 - secret: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), 49 + secret: /*#__PURE__*/ v.optional( 50 + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.string(), [ 51 + /*#__PURE__*/ v.stringLength(0, 256), 52 + ]), 53 + ), 47 54 /** 48 55 * HTTPS endpoint to POST the webhook payload to. 49 56 * @maxLength 2048 ··· 53 60 ]), 54 61 }), 55 62 ); 56 - const _nsidSchema = /*#__PURE__*/ v.object({ 57 - $type: /*#__PURE__*/ v.optional( 58 - /*#__PURE__*/ v.literal("place.wisp.v2.wh#nsid"), 59 - ), 60 - nsid: /*#__PURE__*/ v.nsidString(), 61 - }); 62 63 63 64 type atUri$schematype = typeof _atUriSchema; 64 65 type main$schematype = typeof _mainSchema; 65 - type nsid$schematype = typeof _nsidSchema; 66 66 67 67 export interface atUriSchema extends atUri$schematype {} 68 68 export interface mainSchema extends main$schematype {} 69 - export interface nsidSchema extends nsid$schematype {} 70 69 71 70 export const atUriSchema = _atUriSchema as atUriSchema; 72 71 export const mainSchema = _mainSchema as mainSchema; 73 - export const nsidSchema = _nsidSchema as nsidSchema; 74 72 75 73 export interface AtUri extends v.InferInput<typeof atUriSchema> {} 76 74 export interface Main extends v.InferInput<typeof mainSchema> {} 77 - export interface Nsid extends v.InferInput<typeof nsidSchema> {} 78 75 79 76 declare module "@atcute/lexicons/ambient" { 80 77 interface Records {
+12 -24
packages/@wispplace/lexicons/src/lexicons.ts
··· 1035 1035 main: { 1036 1036 type: 'record', 1037 1037 description: 1038 - 'Webhook configuration for AT Protocol record events. Fires an HTTP POST to a URL before or after a matching record event is processed.', 1038 + 'Webhook configuration for AT Protocol record events. Fires an HTTP POST to a URL when a matching record event is observed on the firehose.', 1039 1039 key: 'any', 1040 1040 record: { 1041 1041 type: 'object', 1042 - required: ['scope', 'url', 'phase', 'events', 'createdAt'], 1042 + required: ['scope', 'url', 'createdAt'], 1043 1043 properties: { 1044 1044 scope: { 1045 - type: 'union', 1046 - refs: ['lex:place.wisp.v2.wh#atUri', 'lex:place.wisp.v2.wh#nsid'], 1045 + type: 'ref', 1046 + ref: 'lex:place.wisp.v2.wh#atUri', 1047 1047 description: 1048 - 'What to watch. An AT-URI scopes to a specific DID, collection, or record. An NSID watches that collection globally across all DIDs.', 1048 + 'What to watch. An AT-URI scopes to a specific DID, collection, or record.', 1049 1049 }, 1050 1050 url: { 1051 1051 type: 'string', ··· 1053 1053 maxLength: 2048, 1054 1054 description: 'HTTPS endpoint to POST the webhook payload to.', 1055 1055 }, 1056 - phase: { 1057 - type: 'string', 1058 - enum: ['pre', 'post'], 1059 - description: 1060 - "Whether the webhook should fire before ('pre') or after ('post') the record event is processed.", 1061 - }, 1062 1056 events: { 1063 1057 type: 'array', 1064 1058 items: { ··· 1066 1060 enum: ['create', 'update', 'delete'], 1067 1061 }, 1068 1062 description: 1069 - "Which record events to trigger on. 'create' fires when a new record is created. 'update' fires when an existing record is updated. 'delete' fires when a record is deleted.", 1063 + 'Which record events to trigger on. Defaults to all events if omitted.', 1070 1064 maxLength: 3, 1071 1065 }, 1072 1066 secret: { 1073 1067 type: 'string', 1068 + maxLength: 256, 1074 1069 description: 1075 1070 "Optional secret used to sign the webhook payload with HMAC-SHA256. The signature is included in the 'X-Webhook-Signature' header of the webhook request.", 1076 1071 }, 1077 1072 enabled: { 1078 1073 type: 'boolean', 1079 1074 description: 1080 - 'Whether the webhook is active. Default to true if omitted.', 1075 + 'Whether the webhook is active. Defaults to true if omitted.', 1081 1076 }, 1082 1077 createdAt: { 1083 1078 type: 'string', ··· 1097 1092 type: 'string', 1098 1093 format: 'at-uri', 1099 1094 }, 1100 - }, 1101 - }, 1102 - nsid: { 1103 - type: 'object', 1104 - description: 1105 - 'Watch all records of this collection type globally across any DID.', 1106 - required: ['nsid'], 1107 - properties: { 1108 - nsid: { 1109 - type: 'string', 1110 - format: 'nsid', 1095 + backlinks: { 1096 + type: 'boolean', 1097 + description: 1098 + 'If true, also watch for records in any repo that reference this DID and collection.', 1111 1099 }, 1112 1100 }, 1113 1101 },
+6 -22
packages/@wispplace/lexicons/src/types/place/wisp/v2/wh.ts
··· 16 16 17 17 export interface Main { 18 18 $type: 'place.wisp.v2.wh' 19 - scope: $Typed<AtUri> | $Typed<Nsid> | { $type: string } 19 + scope: AtUri 20 20 /** HTTPS endpoint to POST the webhook payload to. */ 21 21 url: string 22 - /** Whether the webhook should fire before ('pre') or after ('post') the record event is processed. */ 23 - phase: 'pre' | 'post' 24 - /** Which record events to trigger on. 'create' fires when a new record is created. 'update' fires when an existing record is updated. 'delete' fires when a record is deleted. */ 25 - events: ('create' | 'update' | 'delete')[] 22 + /** Which record events to trigger on. Defaults to all events if omitted. */ 23 + events?: ('create' | 'update' | 'delete')[] 26 24 /** Optional secret used to sign the webhook payload with HMAC-SHA256. The signature is included in the 'X-Webhook-Signature' header of the webhook request. */ 27 25 secret?: string 28 - /** Whether the webhook is active. Default to true if omitted. */ 26 + /** Whether the webhook is active. Defaults to true if omitted. */ 29 27 enabled?: boolean 30 28 /** Timestamp of when the webhook was created. */ 31 29 createdAt: string ··· 52 50 export interface AtUri { 53 51 $type?: 'place.wisp.v2.wh#atUri' 54 52 aturi: string 53 + /** If true, also watch for records in any repo that reference this DID and collection. */ 54 + backlinks?: boolean 55 55 } 56 56 57 57 const hashAtUri = 'atUri' ··· 63 63 export function validateAtUri<V>(v: V) { 64 64 return validate<AtUri & V>(v, id, hashAtUri) 65 65 } 66 - 67 - /** Watch all records of this collection type globally across any DID. */ 68 - export interface Nsid { 69 - $type?: 'place.wisp.v2.wh#nsid' 70 - nsid: string 71 - } 72 - 73 - const hashNsid = 'nsid' 74 - 75 - export function isNsid<V>(v: V) { 76 - return is$typed(v, id, hashNsid) 77 - } 78 - 79 - export function validateNsid<V>(v: V) { 80 - return validate<Nsid & V>(v, id, hashNsid) 81 - }