🌿 Collaborative wiki on ATProto lichen.wiki
atproto
14
fork

Configure Feed

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

Add Write and Read from firehose draft

juprodh 70670fe0 0a1da0b7

+1897 -22
+274 -2
bun.lock
··· 6 6 "name": "atwiki", 7 7 "dependencies": { 8 8 "@atproto/api": "^0.13.0", 9 + "@atproto/identity": "^0.4.12", 9 10 "@atproto/jwk-jose": "^0.1.0", 10 11 "@atproto/oauth-client-node": "^0.1.0", 12 + "@atproto/sync": "^0.1.40", 11 13 "@codemirror/lang-markdown": "^6.5.0", 12 14 "@elysiajs/static": "^1.4.7", 13 15 "@sindresorhus/slugify": "^3.0.0", ··· 51 53 52 54 "@atproto/api": ["@atproto/api@0.13.35", "", { "dependencies": { "@atproto/common-web": "^0.4.0", "@atproto/lexicon": "^0.4.6", "@atproto/syntax": "^0.3.2", "@atproto/xrpc": "^0.6.8", "await-lock": "^2.2.2", "multiformats": "^9.9.0", "tlds": "^1.234.0", "zod": "^3.23.8" } }, "sha512-vsEfBj0C333TLjDppvTdTE0IdKlXuljKSveAeI4PPx/l6eUKNnDTsYxvILtXUVzwUlTDmSRqy5O4Ryh78n1b7g=="], 53 55 56 + "@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=="], 57 + 54 58 "@atproto/common-web": ["@atproto/common-web@0.4.17", "", { "dependencies": { "@atproto/lex-data": "^0.0.12", "@atproto/lex-json": "^0.0.12", "@atproto/syntax": "^0.4.3", "zod": "^3.23.8" } }, "sha512-sfxD8NGxyoxhxmM9EUshEFbWcJ3+JHEOZF4Quk6HsCh1UxpHBmLabT/vEsAkDWl+C/8U0ine0+c/gHyE/OZiQQ=="], 59 + 60 + "@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=="], 55 61 56 62 "@atproto/did": ["@atproto/did@0.1.2", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-gmY1SyAuqfmsFbIXkUIScfnULqn39FoUNz4oE0fUuMu9in6PEyoxlmD2lAo7Q3KMy3X/hvTn2u5f8W/2KuDg1w=="], 57 63 64 + "@atproto/identity": ["@atproto/identity@0.4.12", "", { "dependencies": { "@atproto/common-web": "^0.4.17", "@atproto/crypto": "^0.4.5" } }, "sha512-P+Jn0HvKhIh1tps5n3xGrCxt+XiFWzp4kdgloyFhFmVLwjDU547DQkWx4r5Vhuiah7fRZGVSlk39R4U6SPrACg=="], 65 + 58 66 "@atproto/jwk": ["@atproto/jwk@0.6.0", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-bDoJPvt7TrQVi/rBfBrSSpGykhtIriKxeYCYQTiPRKFfyRhbgpElF0wPXADjIswnbzZdOwbY63az4E/CFVT3Tw=="], 59 67 60 68 "@atproto/jwk-jose": ["@atproto/jwk-jose@0.1.11", "", { "dependencies": { "@atproto/jwk": "0.6.0", "jose": "^5.2.0" } }, "sha512-i4Fnr2sTBYmMmHXl7NJh8GrCH+tDQEVWrcDMDnV5DjJfkgT17wIqvojIw9SNbSL4Uf0OtfEv6AgG0A+mgh8b5Q=="], 61 69 62 70 "@atproto/jwk-webcrypto": ["@atproto/jwk-webcrypto@0.1.2", "", { "dependencies": { "@atproto/jwk": "0.1.1", "@atproto/jwk-jose": "0.1.2" } }, "sha512-vTBUbUZXh0GI+6KJiPGukmI4BQEHFAij8fJJ4WnReF/hefAs3ISZtrWZHGBebz+q2EcExYlnhhlmxvDzV7veGw=="], 63 71 72 + "@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=="], 73 + 74 + "@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=="], 75 + 64 76 "@atproto/lex-data": ["@atproto/lex-data@0.0.12", "", { "dependencies": { "multiformats": "^9.9.0", "tslib": "^2.8.1", "uint8arrays": "3.0.0", "unicode-segmenter": "^0.14.0" } }, "sha512-aekJudcK1p6sbTqUv2bJMJBAGZaOJS0mgDclpK3U6VuBREK/au4B6ffunBFWgrDfg0Vwj2JGyEA7E51WZkJcRw=="], 65 77 66 78 "@atproto/lex-json": ["@atproto/lex-json@0.0.12", "", { "dependencies": { "@atproto/lex-data": "^0.0.12", "tslib": "^2.8.1" } }, "sha512-XlEpnWWZdDJ5BIgG25GyH+6iBfyrFL18BI5JSE6rUfMObbFMrQRaCuRLQfryRXNysVz3L3U+Qb9y8KcXbE8AcA=="], 79 + 80 + "@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=="], 67 81 68 82 "@atproto/lexicon": ["@atproto/lexicon@0.4.14", "", { "dependencies": { "@atproto/common-web": "^0.4.2", "@atproto/syntax": "^0.4.0", "iso-datestring-validator": "^2.2.2", "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-jiKpmH1QER3Gvc7JVY5brwrfo+etFoe57tKPQX/SmPwjvUsFnJAow5xLIryuBaJgFAhnTZViXKs41t//pahGHQ=="], 69 83 ··· 72 86 "@atproto/oauth-client-node": ["@atproto/oauth-client-node@0.1.4", "", { "dependencies": { "@atproto-labs/did-resolver": "0.1.4", "@atproto-labs/handle-resolver-node": "0.1.6", "@atproto-labs/simple-store": "0.1.1", "@atproto/did": "0.1.2", "@atproto/jwk": "0.1.1", "@atproto/jwk-jose": "0.1.2", "@atproto/jwk-webcrypto": "0.1.2", "@atproto/oauth-client": "0.2.2", "@atproto/oauth-types": "0.1.5" } }, "sha512-LKLBrvJL6gd6YqbeZsm9BZ1gqrMDKtz/77TNNjPBP+RBIV6e6Oj5uJmFMshzyJj87Rwd7Menb0niz6LhlKv/rg=="], 73 87 74 88 "@atproto/oauth-types": ["@atproto/oauth-types@0.1.5", "", { "dependencies": { "@atproto/jwk": "0.1.1", "zod": "^3.23.8" } }, "sha512-vNab/6BYUQCfmfhGc3G61EcatQxvh2d41FDWqR8CAYsblNXO6nOEVXn7cXdQUkb3K49LU0vy5Jf1+wFNcpY3IQ=="], 89 + 90 + "@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=="], 91 + 92 + "@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=="], 75 93 76 94 "@atproto/syntax": ["@atproto/syntax@0.3.4", "", {}, "sha512-8CNmi5DipOLaVeSMPggMe7FCksVag0aO6XZy9WflbduTKM4dFZVCs4686UeMLfGRXX+X966XgwECHoLYrovMMg=="], 77 95 96 + "@atproto/ws-client": ["@atproto/ws-client@0.0.4", "", { "dependencies": { "@atproto/common": "^0.5.3", "ws": "^8.12.0" } }, "sha512-dox1XIymuC7/ZRhUqKezIGgooZS45C6vHCfu0PnWjfvsLCK2kAlnvX4IBkA/WpcoijDhQ9ejChnFbo/sLmgvAg=="], 97 + 78 98 "@atproto/xrpc": ["@atproto/xrpc@0.6.12", "", { "dependencies": { "@atproto/lexicon": "^0.4.10", "zod": "^3.23.8" } }, "sha512-Ut3iISNLujlmY9Gu8sNU+SPDJDvqlVzWddU8qUr0Yae5oD4SguaUFjjhireMGhQ3M5E0KljQgDbTmnBo1kIZ3w=="], 99 + 100 + "@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=="], 79 101 80 102 "@biomejs/biome": ["@biomejs/biome@2.4.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.4", "@biomejs/cli-darwin-x64": "2.4.4", "@biomejs/cli-linux-arm64": "2.4.4", "@biomejs/cli-linux-arm64-musl": "2.4.4", "@biomejs/cli-linux-x64": "2.4.4", "@biomejs/cli-linux-x64-musl": "2.4.4", "@biomejs/cli-win32-arm64": "2.4.4", "@biomejs/cli-win32-x64": "2.4.4" }, "bin": { "biome": "bin/biome" } }, "sha512-tigwWS5KfJf0cABVd52NVaXyAVv4qpUXOWJ1rxFL8xF1RVoeS2q/LK+FHgYoKMclJCuRoCWAPy1IXaN9/mS61Q=="], 81 103 ··· 121 143 122 144 "@elysiajs/static": ["@elysiajs/static@1.4.7", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-Go4kIXZ0G3iWfkAld07HmLglqIDMVXdyRKBQK/sVEjtpDdjHNb+rUIje73aDTWpZYg4PEVHUpi9v4AlNEwrQug=="], 123 145 146 + "@ipld/dag-cbor": ["@ipld/dag-cbor@7.0.3", "", { "dependencies": { "cborg": "^1.6.0", "multiformats": "^9.5.4" } }, "sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA=="], 147 + 124 148 "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], 125 149 126 150 "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], ··· 146 170 "@lezer/markdown": ["@lezer/markdown@1.6.3", "", { "dependencies": { "@lezer/common": "^1.5.0", "@lezer/highlight": "^1.0.0" } }, "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw=="], 147 171 148 172 "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], 173 + 174 + "@noble/curves": ["@noble/curves@1.9.7", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], 175 + 176 + "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], 149 177 150 178 "@parcel/watcher": ["@parcel/watcher@2.5.6", "", { "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", "node-addon-api": "^7.0.0", "picomatch": "^4.0.3" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.6", "@parcel/watcher-darwin-arm64": "2.5.6", "@parcel/watcher-darwin-x64": "2.5.6", "@parcel/watcher-freebsd-x64": "2.5.6", "@parcel/watcher-linux-arm-glibc": "2.5.6", "@parcel/watcher-linux-arm-musl": "2.5.6", "@parcel/watcher-linux-arm64-glibc": "2.5.6", "@parcel/watcher-linux-arm64-musl": "2.5.6", "@parcel/watcher-linux-x64-glibc": "2.5.6", "@parcel/watcher-linux-x64-musl": "2.5.6", "@parcel/watcher-win32-arm64": "2.5.6", "@parcel/watcher-win32-ia32": "2.5.6", "@parcel/watcher-win32-x64": "2.5.6" } }, "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="], 151 179 ··· 291 319 292 320 "@types/node": ["@types/node@25.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="], 293 321 322 + "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], 323 + 324 + "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], 325 + 294 326 "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], 295 327 328 + "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="], 329 + 330 + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], 331 + 296 332 "await-lock": ["await-lock@2.2.2", "", {}, "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="], 297 333 334 + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], 335 + 336 + "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=="], 337 + 338 + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], 339 + 298 340 "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], 299 341 342 + "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], 343 + 344 + "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=="], 345 + 346 + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], 347 + 348 + "cborg": ["cborg@1.10.2", "", { "bin": { "cborg": "cli.js" } }, "sha512-b3tFPA9pUr2zCUiCfRd2+wok2/LBSNUMKOuRRok+WlvvAgEt/PlbgPTsZUcwCOs53IJvLgTp0eotwtosE6njug=="], 349 + 300 350 "codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="], 301 351 352 + "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="], 353 + 354 + "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], 355 + 302 356 "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], 303 357 358 + "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="], 359 + 304 360 "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], 305 361 306 - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 362 + "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], 363 + 364 + "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], 365 + 366 + "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="], 307 367 308 368 "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], 309 369 310 370 "diff-match-patch": ["diff-match-patch@1.0.5", "", {}, "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="], 311 371 372 + "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=="], 373 + 374 + "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], 375 + 312 376 "elysia": ["elysia@1.4.27", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-2UlmNEjPJVA/WZVPYKy+KdsrfFwwNlqSBW1lHz6i2AHc75k7gV4Rhm01kFeotH7PDiHIX2G8X3KnRPc33SGVIg=="], 377 + 378 + "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], 313 379 314 380 "enhanced-resolve": ["enhanced-resolve@5.20.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ=="], 315 381 316 382 "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], 317 383 384 + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], 385 + 386 + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], 387 + 388 + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], 389 + 390 + "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], 391 + 318 392 "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], 319 393 394 + "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], 395 + 396 + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], 397 + 398 + "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], 399 + 400 + "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], 401 + 320 402 "exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="], 321 403 404 + "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=="], 405 + 322 406 "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], 323 407 408 + "fast-redact": ["fast-redact@3.5.0", "", {}, "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A=="], 409 + 324 410 "file-type": ["file-type@21.3.0", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.4", "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" } }, "sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA=="], 325 411 412 + "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=="], 413 + 414 + "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], 415 + 416 + "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], 417 + 418 + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 419 + 420 + "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=="], 421 + 422 + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], 423 + 424 + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], 425 + 326 426 "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], 327 427 428 + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], 429 + 430 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 431 + 432 + "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=="], 433 + 434 + "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="], 435 + 328 436 "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], 437 + 438 + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 329 439 330 440 "ipaddr.js": ["ipaddr.js@2.3.0", "", {}, "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg=="], 331 441 ··· 371 481 372 482 "markdown-it": ["markdown-it@14.1.1", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA=="], 373 483 484 + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], 485 + 374 486 "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], 375 487 488 + "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="], 489 + 376 490 "memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="], 377 491 492 + "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="], 493 + 494 + "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], 495 + 496 + "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], 497 + 498 + "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], 499 + 500 + "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], 501 + 378 502 "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], 379 503 380 - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 504 + "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], 381 505 382 506 "multiformats": ["multiformats@9.9.0", "", {}, "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg=="], 507 + 508 + "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="], 383 509 384 510 "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], 385 511 512 + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], 513 + 514 + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], 515 + 516 + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], 517 + 386 518 "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], 387 519 520 + "p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="], 521 + 522 + "p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="], 523 + 524 + "p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="], 525 + 526 + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], 527 + 528 + "path-to-regexp": ["path-to-regexp@0.1.12", "", {}, "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="], 529 + 388 530 "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 389 531 390 532 "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], 391 533 534 + "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=="], 535 + 536 + "pino-abstract-transport": ["pino-abstract-transport@1.2.0", "", { "dependencies": { "readable-stream": "^4.0.0", "split2": "^4.0.0" } }, "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q=="], 537 + 538 + "pino-std-serializers": ["pino-std-serializers@6.2.2", "", {}, "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA=="], 539 + 540 + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], 541 + 542 + "process-warning": ["process-warning@3.0.0", "", {}, "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="], 543 + 544 + "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=="], 545 + 392 546 "psl": ["psl@1.15.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w=="], 393 547 394 548 "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], 395 549 396 550 "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], 397 551 552 + "qs": ["qs@6.14.2", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q=="], 553 + 554 + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], 555 + 556 + "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], 557 + 558 + "rate-limiter-flexible": ["rate-limiter-flexible@2.4.2", "", {}, "sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw=="], 559 + 560 + "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=="], 561 + 562 + "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=="], 563 + 564 + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], 565 + 566 + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], 567 + 568 + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], 569 + 570 + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], 571 + 572 + "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=="], 573 + 574 + "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=="], 575 + 576 + "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], 577 + 578 + "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=="], 579 + 580 + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], 581 + 582 + "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=="], 583 + 584 + "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=="], 585 + 586 + "sonic-boom": ["sonic-boom@3.8.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg=="], 587 + 398 588 "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], 399 589 590 + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], 591 + 592 + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], 593 + 594 + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], 595 + 400 596 "strtok3": ["strtok3@10.3.4", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg=="], 401 597 402 598 "style-mod": ["style-mod@4.1.3", "", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], ··· 405 601 406 602 "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], 407 603 604 + "thread-stream": ["thread-stream@2.7.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw=="], 605 + 408 606 "tlds": ["tlds@1.261.0", "", { "bin": { "tlds": "bin.js" } }, "sha512-QXqwfEl9ddlGBaRFXIvNKK6OhipSiLXuRuLJX5DErz0o0Q0rYxulWLdFryTkV5PkdZct5iMInwYEGe/eR++1AA=="], 409 607 608 + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], 609 + 410 610 "token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="], 411 611 412 612 "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 613 + 614 + "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], 413 615 414 616 "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], 415 617 ··· 425 627 426 628 "unicode-segmenter": ["unicode-segmenter@0.14.5", "", {}, "sha512-jHGmj2LUuqDcX3hqY12Ql+uhUTn8huuxNZGq7GvtF6bSybzH3aFgedYu/KTzQStEgt1Ra2F3HxadNXsNjb3m3g=="], 427 629 630 + "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], 631 + 632 + "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], 633 + 634 + "varint": ["varint@6.0.0", "", {}, "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg=="], 635 + 636 + "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], 637 + 428 638 "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], 639 + 640 + "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=="], 429 641 430 642 "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], 431 643 432 644 "@atproto-labs/identity-resolver/@atproto/syntax": ["@atproto/syntax@0.3.0", "", {}, "sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA=="], 433 645 646 + "@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=="], 647 + 648 + "@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=="], 649 + 434 650 "@atproto/common-web/@atproto/syntax": ["@atproto/syntax@0.4.3", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-YoZUz40YAJr5nPwvCDWgodEOlt5IftZqPJvA0JDWjuZKD8yXddTwSzXSaKQAzGOpuM+/A3uXRtPzJJqlScc+iA=="], 435 651 436 652 "@atproto/jwk-webcrypto/@atproto/jwk": ["@atproto/jwk@0.1.1", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-6h/bj1APUk7QcV9t/oA6+9DB5NZx9SZru9x+/pV5oHFI9Xz4ZuM5+dq1PfsJV54pZyqdnZ6W6M717cxoC7q7og=="], 437 653 438 654 "@atproto/jwk-webcrypto/@atproto/jwk-jose": ["@atproto/jwk-jose@0.1.2", "", { "dependencies": { "@atproto/jwk": "0.1.1", "jose": "^5.2.0" } }, "sha512-lDwc/6lLn2aZ/JpyyggyjLFsJPMntrVzryyGUx5aNpuTS8SIuc4Ky0REhxqfLopQXJJZCuRRjagHG3uP05/moQ=="], 439 655 656 + "@atproto/lex-cbor/@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=="], 657 + 658 + "@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=="], 659 + 660 + "@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=="], 661 + 662 + "@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=="], 663 + 664 + "@atproto/lex-schema/@atproto/syntax": ["@atproto/syntax@0.5.0", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw=="], 665 + 440 666 "@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.4.3", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-YoZUz40YAJr5nPwvCDWgodEOlt5IftZqPJvA0JDWjuZKD8yXddTwSzXSaKQAzGOpuM+/A3uXRtPzJJqlScc+iA=="], 441 667 442 668 "@atproto/oauth-client/@atproto/jwk": ["@atproto/jwk@0.1.1", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-6h/bj1APUk7QcV9t/oA6+9DB5NZx9SZru9x+/pV5oHFI9Xz4ZuM5+dq1PfsJV54pZyqdnZ6W6M717cxoC7q7og=="], ··· 449 675 450 676 "@atproto/oauth-types/@atproto/jwk": ["@atproto/jwk@0.1.1", "", { "dependencies": { "multiformats": "^9.9.0", "zod": "^3.23.8" } }, "sha512-6h/bj1APUk7QcV9t/oA6+9DB5NZx9SZru9x+/pV5oHFI9Xz4ZuM5+dq1PfsJV54pZyqdnZ6W6M717cxoC7q7og=="], 451 677 678 + "@atproto/repo/@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=="], 679 + 680 + "@atproto/sync/@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=="], 681 + 682 + "@atproto/sync/@atproto/syntax": ["@atproto/syntax@0.5.0", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw=="], 683 + 684 + "@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=="], 685 + 686 + "@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=="], 687 + 688 + "@atproto/xrpc-server/@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=="], 689 + 690 + "@atproto/xrpc-server/@atproto/xrpc": ["@atproto/xrpc@0.7.7", "", { "dependencies": { "@atproto/lexicon": "^0.6.0", "zod": "^3.23.8" } }, "sha512-K1ZyO/BU8JNtXX5dmPp7b5UrkLMMqpsIa/Lrj5D3Su+j1Xwq1m6QJ2XJ1AgjEjkI1v4Muzm7klianLE6XGxtmA=="], 691 + 452 692 "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="], 453 693 454 694 "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="], ··· 460 700 "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], 461 701 462 702 "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], 703 + 704 + "@tokenizer/inflate/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], 705 + 706 + "express/cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], 707 + 708 + "proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], 709 + 710 + "send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 711 + 712 + "@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=="], 713 + 714 + "@atproto/common/@atproto/common-web/@atproto/syntax": ["@atproto/syntax@0.5.0", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw=="], 715 + 716 + "@atproto/repo/@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=="], 717 + 718 + "@atproto/repo/@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.5.0", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw=="], 719 + 720 + "@atproto/sync/@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=="], 721 + 722 + "@atproto/xrpc-server/@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=="], 723 + 724 + "@atproto/xrpc-server/@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.5.0", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-UA2DSpGdOQzUQ4gi5SH+NEJz/YR3a3Fg3y2oh+xETDSiTRmA4VhHRCojhXAVsBxUT6EnItw190C/KN+DWW90kw=="], 725 + 726 + "@tokenizer/inflate/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 727 + 728 + "@atproto/repo/@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=="], 729 + 730 + "@atproto/repo/@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=="], 731 + 732 + "@atproto/sync/@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=="], 733 + 734 + "@atproto/sync/@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=="], 463 735 } 464 736 }
+2
bunfig.toml
··· 1 + [test] 2 + preload = ["./tests/preload.ts"]
+2
package.json
··· 31 31 }, 32 32 "dependencies": { 33 33 "@atproto/api": "^0.13.0", 34 + "@atproto/identity": "^0.4.12", 34 35 "@atproto/jwk-jose": "^0.1.0", 35 36 "@atproto/oauth-client-node": "^0.1.0", 37 + "@atproto/sync": "^0.1.40", 36 38 "@codemirror/lang-markdown": "^6.5.0", 37 39 "@elysiajs/static": "^1.4.7", 38 40 "@sindresorhus/slugify": "^3.0.0",
+4
src/atproto/env.ts
··· 17 17 export function isAuthEnabled(): boolean { 18 18 return getAtprotoEnv() !== null; 19 19 } 20 + 21 + export function getRelayUrl(): string { 22 + return process.env.RELAY_URL ?? "wss://bsky.network"; 23 + }
+172
src/atproto/pds.ts
··· 1 + import type { Agent } from "@atproto/api"; 2 + 3 + export interface PdsWriteResult { 4 + uri: string; 5 + cid: string; 6 + } 7 + 8 + export async function writeWikiRecord( 9 + agent: Agent, 10 + did: string, 11 + slug: string, 12 + name: string, 13 + visibility: "public" | "private", 14 + createdAt: string, 15 + ): Promise<PdsWriteResult> { 16 + const response = await agent.com.atproto.repo.putRecord({ 17 + repo: did, 18 + collection: "pub.coral.wiki", 19 + rkey: slug, 20 + record: { 21 + $type: "pub.coral.wiki", 22 + name, 23 + visibility, 24 + createdAt, 25 + }, 26 + }); 27 + return { uri: response.data.uri, cid: response.data.cid }; 28 + } 29 + 30 + export async function writeNoteRecord( 31 + agent: Agent, 32 + did: string, 33 + tid: string, 34 + slug: string, 35 + title: string, 36 + wikiRef: string, 37 + createdAt: string, 38 + ): Promise<PdsWriteResult> { 39 + const response = await agent.com.atproto.repo.putRecord({ 40 + repo: did, 41 + collection: "pub.coral.note", 42 + rkey: tid, 43 + record: { 44 + $type: "pub.coral.note", 45 + slug, 46 + title, 47 + wikiRef, 48 + createdAt, 49 + }, 50 + }); 51 + return { uri: response.data.uri, cid: response.data.cid }; 52 + } 53 + 54 + export interface RevisionBlob { 55 + cid: string; 56 + mimeType: string; 57 + } 58 + 59 + export async function writeRevisionRecord( 60 + agent: Agent, 61 + did: string, 62 + tid: string, 63 + noteRef: string, 64 + parentRevision: string | null, 65 + diff: string, 66 + message: string | undefined, 67 + createdAt: string, 68 + blobs?: RevisionBlob[], 69 + ): Promise<PdsWriteResult> { 70 + const record: Record<string, unknown> = { 71 + $type: "pub.coral.noteRevision", 72 + noteRef, 73 + diff, 74 + diffFormat: "diff-match-patch", 75 + createdAt, 76 + }; 77 + 78 + if (parentRevision) { 79 + record.parentRevision = parentRevision; 80 + } 81 + if (message) { 82 + record.message = message; 83 + } 84 + if (blobs && blobs.length > 0) { 85 + record.blobs = blobs; 86 + } 87 + 88 + const response = await agent.com.atproto.repo.putRecord({ 89 + repo: did, 90 + collection: "pub.coral.noteRevision", 91 + rkey: tid, 92 + record, 93 + }); 94 + return { uri: response.data.uri, cid: response.data.cid }; 95 + } 96 + 97 + export async function writeMembershipRecord( 98 + agent: Agent, 99 + did: string, 100 + tid: string, 101 + memberDid: string, 102 + wikiRef: string, 103 + role: "admin" | "viewer", 104 + createdAt: string, 105 + ): Promise<PdsWriteResult> { 106 + const response = await agent.com.atproto.repo.putRecord({ 107 + repo: did, 108 + collection: "pub.coral.membership", 109 + rkey: tid, 110 + record: { 111 + $type: "pub.coral.membership", 112 + memberDid, 113 + wikiRef, 114 + role, 115 + createdAt, 116 + }, 117 + }); 118 + return { uri: response.data.uri, cid: response.data.cid }; 119 + } 120 + 121 + export async function writeMemberRequestRecord( 122 + agent: Agent, 123 + did: string, 124 + tid: string, 125 + wikiRef: string, 126 + createdAt: string, 127 + ): Promise<PdsWriteResult> { 128 + const response = await agent.com.atproto.repo.putRecord({ 129 + repo: did, 130 + collection: "pub.coral.memberRequest", 131 + rkey: tid, 132 + record: { 133 + $type: "pub.coral.memberRequest", 134 + wikiRef, 135 + createdAt, 136 + }, 137 + }); 138 + return { uri: response.data.uri, cid: response.data.cid }; 139 + } 140 + 141 + export async function writeBookmarkRecord( 142 + agent: Agent, 143 + did: string, 144 + tid: string, 145 + noteRef: string, 146 + createdAt: string, 147 + ): Promise<PdsWriteResult> { 148 + const response = await agent.com.atproto.repo.putRecord({ 149 + repo: did, 150 + collection: "pub.coral.bookmark", 151 + rkey: tid, 152 + record: { 153 + $type: "pub.coral.bookmark", 154 + noteRef, 155 + createdAt, 156 + }, 157 + }); 158 + return { uri: response.data.uri, cid: response.data.cid }; 159 + } 160 + 161 + export async function deleteRecord( 162 + agent: Agent, 163 + did: string, 164 + collection: string, 165 + rkey: string, 166 + ): Promise<void> { 167 + await agent.com.atproto.repo.deleteRecord({ 168 + repo: did, 169 + collection, 170 + rkey, 171 + }); 172 + }
+9 -5
src/atproto/session.ts
··· 1 - import type { NodeOAuthClient } from "@atproto/oauth-client-node"; 1 + import { Agent } from "@atproto/api"; 2 + import type { NodeOAuthClient, OAuthSession } from "@atproto/oauth-client-node"; 2 3 3 4 export interface Session { 4 5 did: string; 5 6 handle: string; 7 + oauthSession: OAuthSession; 6 8 } 7 9 8 10 export async function getSession( ··· 18 20 19 21 try { 20 22 const oauthSession = await client.restore(did); 21 - // OAuthSession doesn't expose .info directly; extract handle from sub if available 22 - const sub = oauthSession.sub ?? did; 23 - // sub is the DID; we use it as fallback — handle will be resolved elsewhere if needed 24 - return { did, handle: sub }; 23 + const handle = oauthSession.sub ?? did; 24 + return { did, handle, oauthSession }; 25 25 } catch { 26 26 return null; 27 27 } 28 28 } 29 + 30 + export function createAgent(session: Session): Agent { 31 + return new Agent(session.oauthSession); 32 + }
+220
src/firehose/handlers.ts
··· 1 + import type { CommitEvt } from "@atproto/sync"; 2 + import { extractWikilinks } from "../lib/markdown.ts"; 3 + import { 4 + deleteMembershipByAtUri, 5 + deleteNoteByAtUri, 6 + deleteRequestByAtUri, 7 + deleteWikiByAtUri, 8 + getNoteByAtUri, 9 + getWikiByAtUri, 10 + insertRevisionFromFirehose, 11 + updateBacklinks, 12 + upsertMembership, 13 + upsertNote, 14 + upsertRequest, 15 + upsertWiki, 16 + } from "../server/db/queries.ts"; 17 + 18 + interface WikiRecord { 19 + name: string; 20 + visibility: string; 21 + createdAt: string; 22 + } 23 + 24 + interface NoteRecord { 25 + slug: string; 26 + title: string; 27 + wikiRef: string; 28 + createdAt: string; 29 + } 30 + 31 + interface RevisionRecord { 32 + noteRef: string; 33 + parentRevision?: string; 34 + diff: string; 35 + diffFormat: string; 36 + message?: string; 37 + createdAt: string; 38 + blobs?: { cid: string; mimeType: string }[]; 39 + } 40 + 41 + interface MembershipRecord { 42 + memberDid: string; 43 + wikiRef: string; 44 + role: string; 45 + createdAt: string; 46 + } 47 + 48 + interface MemberRequestRecord { 49 + wikiRef: string; 50 + createdAt: string; 51 + } 52 + 53 + function isValidRecord(record: unknown): record is Record<string, unknown> { 54 + return typeof record === "object" && record !== null; 55 + } 56 + 57 + function parseAtUri( 58 + atUri: string, 59 + ): { did: string; collection: string; rkey: string } | null { 60 + const match = atUri.match(/^at:\/\/([^/]+)\/([^/]+)\/([^/]+)$/); 61 + if (!match?.[1] || !match[2] || !match[3]) return null; 62 + return { did: match[1], collection: match[2], rkey: match[3] }; 63 + } 64 + 65 + function didOwnsUri(eventDid: string, atUri: string): boolean { 66 + const parsed = parseAtUri(atUri); 67 + return parsed !== null && parsed.did === eventDid; 68 + } 69 + 70 + export function handleCommitEvent(evt: CommitEvt): void { 71 + if (evt.event === "delete") { 72 + handleDelete(evt); 73 + return; 74 + } 75 + 76 + const record = evt.record; 77 + if (!isValidRecord(record)) return; 78 + 79 + switch (evt.collection) { 80 + case "pub.coral.wiki": 81 + handleWiki(evt, record as unknown as WikiRecord); 82 + break; 83 + case "pub.coral.note": 84 + handleNote(evt, record as unknown as NoteRecord); 85 + break; 86 + case "pub.coral.noteRevision": 87 + handleRevision(evt, record as unknown as RevisionRecord); 88 + break; 89 + case "pub.coral.membership": 90 + handleMembership(evt, record as unknown as MembershipRecord); 91 + break; 92 + case "pub.coral.memberRequest": 93 + handleMemberRequest(evt, record as unknown as MemberRequestRecord); 94 + break; 95 + } 96 + } 97 + 98 + function handleWiki(evt: CommitEvt, record: WikiRecord): void { 99 + if (!record.name || !record.visibility || !record.createdAt) return; 100 + 101 + const atUri = evt.uri.toString(); 102 + if (!didOwnsUri(evt.did, atUri)) return; 103 + 104 + upsertWiki( 105 + evt.rkey, 106 + evt.did, 107 + record.name, 108 + record.visibility, 109 + atUri, 110 + record.createdAt, 111 + ); 112 + } 113 + 114 + function handleNote(evt: CommitEvt, record: NoteRecord): void { 115 + if (!record.slug || !record.title || !record.wikiRef || !record.createdAt) 116 + return; 117 + 118 + const atUri = evt.uri.toString(); 119 + if (!didOwnsUri(evt.did, atUri)) return; 120 + 121 + const wikiParsed = parseAtUri(record.wikiRef); 122 + if (!wikiParsed) return; 123 + 124 + const wiki = getWikiByAtUri(record.wikiRef); 125 + if (!wiki) return; 126 + 127 + upsertNote( 128 + wiki.slug, 129 + record.slug, 130 + record.title, 131 + evt.did, 132 + atUri, 133 + record.createdAt, 134 + ); 135 + } 136 + 137 + function handleRevision(evt: CommitEvt, record: RevisionRecord): void { 138 + if ( 139 + !record.noteRef || 140 + !record.diff || 141 + !record.diffFormat || 142 + !record.createdAt 143 + ) 144 + return; 145 + 146 + const atUri = evt.uri.toString(); 147 + if (!didOwnsUri(evt.did, atUri)) return; 148 + 149 + const note = getNoteByAtUri(record.noteRef); 150 + if (!note) return; 151 + 152 + const newContent = insertRevisionFromFirehose( 153 + note.at_uri, 154 + evt.did, 155 + atUri, 156 + record.parentRevision ?? null, 157 + record.diff, 158 + record.diffFormat, 159 + record.message ?? null, 160 + ); 161 + 162 + const wikiSlugs = extractWikilinks(newContent); 163 + updateBacklinks(note.at_uri, note.wiki_slug, wikiSlugs); 164 + } 165 + 166 + function handleMembership(evt: CommitEvt, record: MembershipRecord): void { 167 + if (!record.memberDid || !record.wikiRef || !record.role || !record.createdAt) 168 + return; 169 + 170 + const atUri = evt.uri.toString(); 171 + if (!didOwnsUri(evt.did, atUri)) return; 172 + 173 + const wiki = getWikiByAtUri(record.wikiRef); 174 + if (!wiki) return; 175 + 176 + if (wiki.did !== evt.did) return; 177 + 178 + upsertMembership( 179 + wiki.slug, 180 + record.memberDid, 181 + record.role, 182 + atUri, 183 + record.createdAt, 184 + ); 185 + } 186 + 187 + function handleMemberRequest( 188 + evt: CommitEvt, 189 + record: MemberRequestRecord, 190 + ): void { 191 + if (!record.wikiRef || !record.createdAt) return; 192 + 193 + const atUri = evt.uri.toString(); 194 + if (!didOwnsUri(evt.did, atUri)) return; 195 + 196 + const wiki = getWikiByAtUri(record.wikiRef); 197 + if (!wiki) return; 198 + 199 + upsertRequest(wiki.slug, evt.did, atUri, record.createdAt); 200 + } 201 + 202 + function handleDelete(evt: CommitEvt): void { 203 + const atUri = evt.uri.toString(); 204 + if (!didOwnsUri(evt.did, atUri)) return; 205 + 206 + switch (evt.collection) { 207 + case "pub.coral.wiki": 208 + deleteWikiByAtUri(atUri); 209 + break; 210 + case "pub.coral.note": 211 + deleteNoteByAtUri(atUri); 212 + break; 213 + case "pub.coral.membership": 214 + deleteMembershipByAtUri(atUri); 215 + break; 216 + case "pub.coral.memberRequest": 217 + deleteRequestByAtUri(atUri); 218 + break; 219 + } 220 + }
+66 -6
src/firehose/index.ts
··· 1 - // Firehose subscriber — standalone process. 2 - // Will connect to the ATProto relay and ingest records. 1 + import { IdResolver } from "@atproto/identity"; 2 + import { type Event, Firehose, MemoryRunner } from "@atproto/sync"; 3 + import { getRelayUrl } from "../atproto/env.ts"; 4 + import { getCursor, setCursor } from "../server/db/queries.ts"; 5 + import { handleCommitEvent } from "./handlers.ts"; 3 6 4 - console.log("atwiki firehose subscriber starting..."); 5 - console.log("TODO: connect to relay, filter for pub.coral.* records"); 7 + const relayUrl = getRelayUrl(); 8 + const savedCursor = getCursor(); 6 9 7 - // Keep process alive 8 - setInterval(() => {}, 60_000); 10 + console.log(`Firehose connecting to ${relayUrl}`); 11 + if (savedCursor) { 12 + console.log(`Resuming from cursor ${savedCursor}`); 13 + } 14 + 15 + const idResolver = new IdResolver(); 16 + 17 + const runner = new MemoryRunner({ 18 + startCursor: savedCursor ?? undefined, 19 + setCursor: async (cursor: number) => { 20 + setCursor(cursor); 21 + }, 22 + }); 23 + 24 + const firehose = new Firehose({ 25 + idResolver, 26 + runner, 27 + service: relayUrl, 28 + filterCollections: [ 29 + "pub.coral.wiki", 30 + "pub.coral.note", 31 + "pub.coral.noteRevision", 32 + "pub.coral.membership", 33 + "pub.coral.memberRequest", 34 + "pub.coral.bookmark", 35 + ], 36 + excludeIdentity: true, 37 + excludeAccount: true, 38 + handleEvent: (evt: Event) => { 39 + if ( 40 + evt.event === "create" || 41 + evt.event === "update" || 42 + evt.event === "delete" 43 + ) { 44 + try { 45 + handleCommitEvent(evt); 46 + } catch (err) { 47 + console.error(`Error handling ${evt.collection} ${evt.event}:`, err); 48 + } 49 + } 50 + }, 51 + onError: (err: Error) => { 52 + console.error("Firehose error:", err); 53 + }, 54 + }); 55 + 56 + firehose.start(); 57 + console.log("Firehose subscriber running"); 58 + 59 + function shutdown() { 60 + console.log("Shutting down firehose..."); 61 + firehose.destroy().then(() => { 62 + console.log("Firehose stopped"); 63 + process.exit(0); 64 + }); 65 + } 66 + 67 + process.on("SIGINT", shutdown); 68 + process.on("SIGTERM", shutdown);
+212 -1
src/server/db/queries.ts
··· 1 - import { createDiff } from "../../lib/diff.ts"; 1 + import { applyDiff, createDiff } from "../../lib/diff.ts"; 2 2 import { extractWikilinks } from "../../lib/markdown.ts"; 3 3 import { generateTid } from "../../lib/tid.ts"; 4 4 import { getDb } from "./index.ts"; ··· 260 260 261 261 return { revisionAtUri }; 262 262 } 263 + 264 + // --- Firehose ingestion queries --- 265 + 266 + export function getWikiByAtUri(atUri: string): WikiRow | null { 267 + const db = getDb(); 268 + return ( 269 + (db.query("SELECT * FROM wikis WHERE at_uri = ?").get(atUri) as WikiRow) ?? 270 + null 271 + ); 272 + } 273 + 274 + export function getNoteByAtUri( 275 + atUri: string, 276 + ): (NoteRow & { wiki_slug: string }) | null { 277 + const db = getDb(); 278 + return ( 279 + (db.query("SELECT * FROM notes WHERE at_uri = ?").get(atUri) as 280 + | (NoteRow & { wiki_slug: string }) 281 + | null) ?? null 282 + ); 283 + } 284 + 285 + export function upsertWiki( 286 + slug: string, 287 + did: string, 288 + name: string, 289 + visibility: string, 290 + atUri: string, 291 + createdAt: string, 292 + ): void { 293 + const db = getDb(); 294 + db.run( 295 + `INSERT INTO wikis (slug, did, name, visibility, at_uri, created_at, updated_at) 296 + VALUES (?, ?, ?, ?, ?, ?, datetime('now')) 297 + ON CONFLICT(slug) DO UPDATE SET 298 + name = excluded.name, 299 + visibility = excluded.visibility, 300 + updated_at = datetime('now')`, 301 + [slug, did, name, visibility, atUri, createdAt], 302 + ); 303 + } 304 + 305 + export function deleteWikiByAtUri(atUri: string): void { 306 + const db = getDb(); 307 + const wiki = db 308 + .query("SELECT slug FROM wikis WHERE at_uri = ?") 309 + .get(atUri) as { slug: string } | null; 310 + if (!wiki) return; 311 + 312 + db.transaction(() => { 313 + const notes = db 314 + .query("SELECT at_uri FROM notes WHERE wiki_slug = ?") 315 + .all(wiki.slug) as { at_uri: string }[]; 316 + for (const note of notes) { 317 + db.run("DELETE FROM current_note WHERE note_at_uri = ?", [note.at_uri]); 318 + db.run("DELETE FROM revisions WHERE note_at_uri = ?", [note.at_uri]); 319 + db.run("DELETE FROM snapshots WHERE note_at_uri = ?", [note.at_uri]); 320 + db.run("DELETE FROM backlinks WHERE source_note_uri = ?", [note.at_uri]); 321 + } 322 + db.run("DELETE FROM notes WHERE wiki_slug = ?", [wiki.slug]); 323 + db.run("DELETE FROM memberships WHERE wiki_slug = ?", [wiki.slug]); 324 + db.run("DELETE FROM requests WHERE wiki_slug = ?", [wiki.slug]); 325 + db.run("DELETE FROM wikis WHERE slug = ?", [wiki.slug]); 326 + })(); 327 + } 328 + 329 + export function upsertNote( 330 + wikiSlug: string, 331 + slug: string, 332 + title: string, 333 + did: string, 334 + atUri: string, 335 + createdAt: string, 336 + ): void { 337 + const db = getDb(); 338 + db.run( 339 + `INSERT INTO notes (slug, wiki_slug, title, did, at_uri, created_at) 340 + VALUES (?, ?, ?, ?, ?, ?) 341 + ON CONFLICT(wiki_slug, slug) DO UPDATE SET 342 + title = excluded.title`, 343 + [slug, wikiSlug, title, did, atUri, createdAt], 344 + ); 345 + } 346 + 347 + export function deleteNoteByAtUri(atUri: string): void { 348 + const db = getDb(); 349 + db.transaction(() => { 350 + db.run("DELETE FROM current_note WHERE note_at_uri = ?", [atUri]); 351 + db.run("DELETE FROM revisions WHERE note_at_uri = ?", [atUri]); 352 + db.run("DELETE FROM snapshots WHERE note_at_uri = ?", [atUri]); 353 + db.run("DELETE FROM backlinks WHERE source_note_uri = ?", [atUri]); 354 + db.run("DELETE FROM notes WHERE at_uri = ?", [atUri]); 355 + })(); 356 + } 357 + 358 + export function insertRevisionFromFirehose( 359 + noteAtUri: string, 360 + did: string, 361 + revisionAtUri: string, 362 + parentRevisionUri: string | null, 363 + diff: string, 364 + diffFormat: string, 365 + message: string | null, 366 + ): string { 367 + const db = getDb(); 368 + 369 + const current = db 370 + .query("SELECT content FROM current_note WHERE note_at_uri = ?") 371 + .get(noteAtUri) as { content: string } | null; 372 + 373 + const oldContent = current?.content ?? ""; 374 + const newContent = applyDiff(oldContent, diff); 375 + 376 + db.transaction(() => { 377 + db.run( 378 + `INSERT INTO revisions (note_at_uri, did, at_uri, parent_revision_uri, diff, diff_format, message) 379 + VALUES (?, ?, ?, ?, ?, ?, ?)`, 380 + [ 381 + noteAtUri, 382 + did, 383 + revisionAtUri, 384 + parentRevisionUri, 385 + diff, 386 + diffFormat, 387 + message, 388 + ], 389 + ); 390 + db.run( 391 + `INSERT INTO current_note (note_at_uri, content, latest_revision_uri, updated_at) 392 + VALUES (?, ?, ?, datetime('now')) 393 + ON CONFLICT(note_at_uri) DO UPDATE SET 394 + content = excluded.content, 395 + latest_revision_uri = excluded.latest_revision_uri, 396 + updated_at = excluded.updated_at`, 397 + [noteAtUri, newContent, revisionAtUri], 398 + ); 399 + db.run( 400 + "INSERT INTO snapshots (note_at_uri, revision_at_uri, content) VALUES (?, ?, ?)", 401 + [noteAtUri, revisionAtUri, newContent], 402 + ); 403 + })(); 404 + 405 + capSnapshots(noteAtUri); 406 + 407 + return newContent; 408 + } 409 + 410 + export function upsertMembership( 411 + wikiSlug: string, 412 + did: string, 413 + role: string, 414 + atUri: string, 415 + createdAt: string, 416 + ): void { 417 + const db = getDb(); 418 + db.run( 419 + `INSERT INTO memberships (wiki_slug, did, role, at_uri, created_at) 420 + VALUES (?, ?, ?, ?, ?) 421 + ON CONFLICT(wiki_slug, did) DO UPDATE SET 422 + role = excluded.role`, 423 + [wikiSlug, did, role, atUri, createdAt], 424 + ); 425 + } 426 + 427 + export function deleteMembershipByAtUri(atUri: string): void { 428 + const db = getDb(); 429 + db.run("DELETE FROM memberships WHERE at_uri = ?", [atUri]); 430 + } 431 + 432 + export function upsertRequest( 433 + wikiSlug: string, 434 + did: string, 435 + atUri: string, 436 + createdAt: string, 437 + ): void { 438 + const db = getDb(); 439 + db.run( 440 + `INSERT INTO requests (wiki_slug, did, at_uri, created_at) 441 + VALUES (?, ?, ?, ?) 442 + ON CONFLICT(wiki_slug, did) DO UPDATE SET 443 + at_uri = excluded.at_uri`, 444 + [wikiSlug, did, atUri, createdAt], 445 + ); 446 + } 447 + 448 + export function deleteRequestByAtUri(atUri: string): void { 449 + const db = getDb(); 450 + db.run("DELETE FROM requests WHERE at_uri = ?", [atUri]); 451 + } 452 + 453 + // --- Cursor queries --- 454 + 455 + export function getCursor(): number | null { 456 + const db = getDb(); 457 + const row = db 458 + .query("SELECT cursor FROM firehose_cursor WHERE id = 1") 459 + .get() as { cursor: number } | null; 460 + return row?.cursor ?? null; 461 + } 462 + 463 + export function setCursor(cursor: number): void { 464 + const db = getDb(); 465 + db.run( 466 + `INSERT INTO firehose_cursor (id, cursor, updated_at) 467 + VALUES (1, ?, datetime('now')) 468 + ON CONFLICT(id) DO UPDATE SET 469 + cursor = excluded.cursor, 470 + updated_at = excluded.updated_at`, 471 + [cursor], 472 + ); 473 + }
+8
src/server/db/schema.ts
··· 104 104 `); 105 105 106 106 db.run(` 107 + CREATE TABLE IF NOT EXISTS firehose_cursor ( 108 + id INTEGER PRIMARY KEY CHECK (id = 1), 109 + cursor INTEGER NOT NULL, 110 + updated_at TEXT NOT NULL DEFAULT (datetime('now')) 111 + ) 112 + `); 113 + 114 + db.run(` 107 115 CREATE TABLE IF NOT EXISTS oauth_sessions ( 108 116 did TEXT PRIMARY KEY, 109 117 session_data TEXT NOT NULL,
+82 -5
src/server/routes/note.ts
··· 1 1 import { Elysia } from "elysia"; 2 + import { writeNoteRecord, writeRevisionRecord } from "../../atproto/pds.ts"; 2 3 import { getSessionFromRequest } from "../../atproto/routes.ts"; 3 - import type { Session } from "../../atproto/session.ts"; 4 + import { createAgent, type Session } from "../../atproto/session.ts"; 5 + import { createDiff } from "../../lib/diff.ts"; 4 6 import { renderMarkdown } from "../../lib/markdown.ts"; 5 7 import { isValidSlug, slugify } from "../../lib/slug.ts"; 8 + import { generateTid } from "../../lib/tid.ts"; 6 9 import { editNotePage } from "../../views/edit-note.ts"; 7 10 import { newNotePage } from "../../views/new-note.ts"; 8 11 import { notePage } from "../../views/note.ts"; ··· 109 112 110 113 try { 111 114 const did = getEffectiveDid(session); 112 - createNote( 115 + const editMessage = message || undefined; 116 + const { noteAtUri, revisionAtUri } = createNote( 113 117 params.wikiSlug, 114 118 noteSlug, 115 119 title, 116 120 did, 117 121 content, 118 - message || undefined, 122 + editMessage, 119 123 ); 124 + 125 + if (session) { 126 + const agent = createAgent(session); 127 + const now = new Date().toISOString(); 128 + const wikiAtUri = wiki.at_uri; 129 + const noteTid = noteAtUri.split("/").pop()!; 130 + const revisionTid = revisionAtUri.split("/").pop()!; 131 + const diff = createDiff("", content); 132 + 133 + try { 134 + await writeNoteRecord( 135 + agent, 136 + did, 137 + noteTid, 138 + noteSlug, 139 + title, 140 + wikiAtUri, 141 + now, 142 + ); 143 + await writeRevisionRecord( 144 + agent, 145 + did, 146 + revisionTid, 147 + noteAtUri, 148 + null, 149 + diff, 150 + editMessage, 151 + now, 152 + ); 153 + } catch (pdsErr) { 154 + console.error("PDS write failed (note create):", pdsErr); 155 + } 156 + } 120 157 } catch (err) { 121 158 const msg = err instanceof Error ? err.message : String(err); 122 159 return new Response(`Failed to create note: ${msg}`, { status: 500 }); ··· 182 219 183 220 try { 184 221 const did = getEffectiveDid(session); 222 + const editMessage = message || undefined; 185 223 const newTitle = title !== note.title ? title : undefined; 186 - saveNoteEdit( 224 + const { revisionAtUri } = saveNoteEdit( 187 225 params.wikiSlug, 188 226 params.noteSlug, 189 227 content, 190 228 did, 191 - message || undefined, 229 + editMessage, 192 230 newTitle, 193 231 ); 232 + 233 + if (session) { 234 + const agent = createAgent(session); 235 + const now = new Date().toISOString(); 236 + const revisionTid = revisionAtUri.split("/").pop()!; 237 + const currentNote = getCurrentNote(params.wikiSlug, params.noteSlug); 238 + const parentRevisionUri = 239 + currentNote?.latest_revision_uri !== revisionAtUri 240 + ? (currentNote?.latest_revision_uri ?? null) 241 + : null; 242 + 243 + try { 244 + await writeRevisionRecord( 245 + agent, 246 + did, 247 + revisionTid, 248 + note.at_uri, 249 + parentRevisionUri, 250 + createDiff(currentNote?.content ?? "", content), 251 + editMessage, 252 + now, 253 + ); 254 + 255 + if (newTitle) { 256 + const noteTid = note.at_uri.split("/").pop()!; 257 + await writeNoteRecord( 258 + agent, 259 + did, 260 + noteTid, 261 + note.slug, 262 + newTitle, 263 + wiki.at_uri, 264 + note.created_at, 265 + ); 266 + } 267 + } catch (pdsErr) { 268 + console.error("PDS write failed (note edit):", pdsErr); 269 + } 270 + } 194 271 } catch (err) { 195 272 const msg = err instanceof Error ? err.message : String(err); 196 273 return new Response(`Failed to save edit: ${msg}`, { status: 500 });
+262
tests/atproto/pds.test.ts
··· 1 + import { describe, expect, mock, test } from "bun:test"; 2 + import { 3 + deleteRecord, 4 + writeBookmarkRecord, 5 + writeMemberRequestRecord, 6 + writeMembershipRecord, 7 + writeNoteRecord, 8 + writeRevisionRecord, 9 + writeWikiRecord, 10 + } from "../../src/atproto/pds.ts"; 11 + 12 + function createMockAgent( 13 + returnUri = "at://did:plc:test/collection/rkey", 14 + returnCid = "bafyreiabc123", 15 + ) { 16 + const putRecordCalls: unknown[] = []; 17 + const deleteRecordCalls: unknown[] = []; 18 + 19 + const agent = { 20 + com: { 21 + atproto: { 22 + repo: { 23 + putRecord: mock(async (params: unknown) => { 24 + putRecordCalls.push(params); 25 + return { 26 + data: { uri: returnUri, cid: returnCid }, 27 + }; 28 + }), 29 + deleteRecord: mock(async (params: unknown) => { 30 + deleteRecordCalls.push(params); 31 + return { data: {} }; 32 + }), 33 + }, 34 + }, 35 + }, 36 + }; 37 + 38 + // biome-ignore lint/suspicious/noExplicitAny: mock Agent for testing 39 + return { agent: agent as any, putRecordCalls, deleteRecordCalls }; 40 + } 41 + 42 + describe("writeWikiRecord", () => { 43 + test("calls putRecord with correct collection and rkey", async () => { 44 + const { agent, putRecordCalls } = createMockAgent(); 45 + await writeWikiRecord( 46 + agent, 47 + "did:plc:alice", 48 + "my-wiki", 49 + "My Wiki", 50 + "public", 51 + "2026-01-01T00:00:00.000Z", 52 + ); 53 + 54 + expect(putRecordCalls).toHaveLength(1); 55 + const call = putRecordCalls[0] as Record<string, unknown>; 56 + expect(call.repo).toBe("did:plc:alice"); 57 + expect(call.collection).toBe("pub.coral.wiki"); 58 + expect(call.rkey).toBe("my-wiki"); 59 + }); 60 + 61 + test("record matches lexicon schema", async () => { 62 + const { agent, putRecordCalls } = createMockAgent(); 63 + await writeWikiRecord( 64 + agent, 65 + "did:plc:alice", 66 + "my-wiki", 67 + "My Wiki", 68 + "private", 69 + "2026-01-01T00:00:00.000Z", 70 + ); 71 + 72 + const call = putRecordCalls[0] as Record<string, unknown>; 73 + const record = call.record as Record<string, unknown>; 74 + expect(record.$type).toBe("pub.coral.wiki"); 75 + expect(record.name).toBe("My Wiki"); 76 + expect(record.visibility).toBe("private"); 77 + expect(record.createdAt).toBe("2026-01-01T00:00:00.000Z"); 78 + }); 79 + 80 + test("returns uri and cid from response", async () => { 81 + const { agent } = createMockAgent( 82 + "at://did:plc:alice/pub.coral.wiki/my-wiki", 83 + "bafyrei123", 84 + ); 85 + const result = await writeWikiRecord( 86 + agent, 87 + "did:plc:alice", 88 + "my-wiki", 89 + "My Wiki", 90 + "public", 91 + "2026-01-01T00:00:00.000Z", 92 + ); 93 + expect(result.uri).toBe("at://did:plc:alice/pub.coral.wiki/my-wiki"); 94 + expect(result.cid).toBe("bafyrei123"); 95 + }); 96 + }); 97 + 98 + describe("writeNoteRecord", () => { 99 + test("calls putRecord with correct collection and TID rkey", async () => { 100 + const { agent, putRecordCalls } = createMockAgent(); 101 + await writeNoteRecord( 102 + agent, 103 + "did:plc:alice", 104 + "362pbqd3tgb7z", 105 + "my-note", 106 + "My Note", 107 + "at://did:plc:alice/pub.coral.wiki/my-wiki", 108 + "2026-01-01T00:00:00.000Z", 109 + ); 110 + 111 + const call = putRecordCalls[0] as Record<string, unknown>; 112 + expect(call.collection).toBe("pub.coral.note"); 113 + expect(call.rkey).toBe("362pbqd3tgb7z"); 114 + }); 115 + 116 + test("record includes slug, title, wikiRef, createdAt", async () => { 117 + const { agent, putRecordCalls } = createMockAgent(); 118 + await writeNoteRecord( 119 + agent, 120 + "did:plc:alice", 121 + "362pbqd3tgb7z", 122 + "my-note", 123 + "My Note", 124 + "at://did:plc:alice/pub.coral.wiki/my-wiki", 125 + "2026-01-01T00:00:00.000Z", 126 + ); 127 + 128 + const record = (putRecordCalls[0] as Record<string, unknown>) 129 + .record as Record<string, unknown>; 130 + expect(record.$type).toBe("pub.coral.note"); 131 + expect(record.slug).toBe("my-note"); 132 + expect(record.title).toBe("My Note"); 133 + expect(record.wikiRef).toBe("at://did:plc:alice/pub.coral.wiki/my-wiki"); 134 + expect(record.createdAt).toBe("2026-01-01T00:00:00.000Z"); 135 + }); 136 + }); 137 + 138 + describe("writeRevisionRecord", () => { 139 + test("includes required fields only when optionals absent", async () => { 140 + const { agent, putRecordCalls } = createMockAgent(); 141 + await writeRevisionRecord( 142 + agent, 143 + "did:plc:alice", 144 + "362pbqd3tgb7z", 145 + "at://did:plc:alice/pub.coral.note/abc123", 146 + null, 147 + "@@ -0,0 +1 @@\n+hello\n", 148 + undefined, 149 + "2026-01-01T00:00:00.000Z", 150 + ); 151 + 152 + const record = (putRecordCalls[0] as Record<string, unknown>) 153 + .record as Record<string, unknown>; 154 + expect(record.$type).toBe("pub.coral.noteRevision"); 155 + expect(record.noteRef).toBe("at://did:plc:alice/pub.coral.note/abc123"); 156 + expect(record.diff).toBe("@@ -0,0 +1 @@\n+hello\n"); 157 + expect(record.diffFormat).toBe("diff-match-patch"); 158 + expect(record.createdAt).toBe("2026-01-01T00:00:00.000Z"); 159 + expect(record.parentRevision).toBeUndefined(); 160 + expect(record.message).toBeUndefined(); 161 + expect(record.blobs).toBeUndefined(); 162 + }); 163 + 164 + test("includes optional fields when provided", async () => { 165 + const { agent, putRecordCalls } = createMockAgent(); 166 + await writeRevisionRecord( 167 + agent, 168 + "did:plc:alice", 169 + "362pbqd3tgb7z", 170 + "at://did:plc:alice/pub.coral.note/abc123", 171 + "at://did:plc:alice/pub.coral.noteRevision/prev123", 172 + "some diff", 173 + "fix typo", 174 + "2026-01-01T00:00:00.000Z", 175 + [{ cid: "bafyblob1", mimeType: "image/png" }], 176 + ); 177 + 178 + const record = (putRecordCalls[0] as Record<string, unknown>) 179 + .record as Record<string, unknown>; 180 + expect(record.parentRevision).toBe( 181 + "at://did:plc:alice/pub.coral.noteRevision/prev123", 182 + ); 183 + expect(record.message).toBe("fix typo"); 184 + expect(record.blobs).toEqual([{ cid: "bafyblob1", mimeType: "image/png" }]); 185 + }); 186 + }); 187 + 188 + describe("writeMembershipRecord", () => { 189 + test("record includes memberDid, wikiRef, role", async () => { 190 + const { agent, putRecordCalls } = createMockAgent(); 191 + await writeMembershipRecord( 192 + agent, 193 + "did:plc:alice", 194 + "362pbqd3tgb7z", 195 + "did:plc:bob", 196 + "at://did:plc:alice/pub.coral.wiki/my-wiki", 197 + "admin", 198 + "2026-01-01T00:00:00.000Z", 199 + ); 200 + 201 + const record = (putRecordCalls[0] as Record<string, unknown>) 202 + .record as Record<string, unknown>; 203 + expect(record.$type).toBe("pub.coral.membership"); 204 + expect(record.memberDid).toBe("did:plc:bob"); 205 + expect(record.wikiRef).toBe("at://did:plc:alice/pub.coral.wiki/my-wiki"); 206 + expect(record.role).toBe("admin"); 207 + }); 208 + }); 209 + 210 + describe("writeMemberRequestRecord", () => { 211 + test("record includes wikiRef", async () => { 212 + const { agent, putRecordCalls } = createMockAgent(); 213 + await writeMemberRequestRecord( 214 + agent, 215 + "did:plc:bob", 216 + "362pbqd3tgb7z", 217 + "at://did:plc:alice/pub.coral.wiki/my-wiki", 218 + "2026-01-01T00:00:00.000Z", 219 + ); 220 + 221 + const record = (putRecordCalls[0] as Record<string, unknown>) 222 + .record as Record<string, unknown>; 223 + expect(record.$type).toBe("pub.coral.memberRequest"); 224 + expect(record.wikiRef).toBe("at://did:plc:alice/pub.coral.wiki/my-wiki"); 225 + }); 226 + }); 227 + 228 + describe("writeBookmarkRecord", () => { 229 + test("record includes noteRef", async () => { 230 + const { agent, putRecordCalls } = createMockAgent(); 231 + await writeBookmarkRecord( 232 + agent, 233 + "did:plc:bob", 234 + "362pbqd3tgb7z", 235 + "at://did:plc:alice/pub.coral.note/note123", 236 + "2026-01-01T00:00:00.000Z", 237 + ); 238 + 239 + const record = (putRecordCalls[0] as Record<string, unknown>) 240 + .record as Record<string, unknown>; 241 + expect(record.$type).toBe("pub.coral.bookmark"); 242 + expect(record.noteRef).toBe("at://did:plc:alice/pub.coral.note/note123"); 243 + }); 244 + }); 245 + 246 + describe("deleteRecord", () => { 247 + test("calls deleteRecord with correct params", async () => { 248 + const { agent, deleteRecordCalls } = createMockAgent(); 249 + await deleteRecord( 250 + agent, 251 + "did:plc:alice", 252 + "pub.coral.note", 253 + "362pbqd3tgb7z", 254 + ); 255 + 256 + expect(deleteRecordCalls).toHaveLength(1); 257 + const call = deleteRecordCalls[0] as Record<string, unknown>; 258 + expect(call.repo).toBe("did:plc:alice"); 259 + expect(call.collection).toBe("pub.coral.note"); 260 + expect(call.rkey).toBe("362pbqd3tgb7z"); 261 + }); 262 + });
+583
tests/firehose/handlers.test.ts
··· 1 + import { afterAll, beforeAll, describe, expect, test } from "bun:test"; 2 + import type { CommitEvt } from "@atproto/sync"; 3 + import { handleCommitEvent } from "../../src/firehose/handlers.ts"; 4 + import { getDb } from "../../src/server/db/index.ts"; 5 + import { 6 + getCurrentNote, 7 + getCursor, 8 + getNoteBySlug, 9 + getWiki, 10 + listNotes, 11 + setCursor, 12 + } from "../../src/server/db/queries.ts"; 13 + 14 + const db = getDb(); 15 + 16 + const ALICE_DID = "did:plc:alice"; 17 + const BOB_DID = "did:plc:bob"; 18 + const WIKI_AT_URI = `at://${ALICE_DID}/pub.coral.wiki/test-wiki`; 19 + 20 + interface CommitEvtInput { 21 + event: string; 22 + collection: string; 23 + rkey: string; 24 + did?: string; 25 + uri?: { toString(): string }; 26 + record?: Record<string, unknown>; 27 + } 28 + 29 + function makeCommitEvt(input: CommitEvtInput): CommitEvt { 30 + const did = input.did ?? ALICE_DID; 31 + const atUri = 32 + input.uri?.toString() ?? `at://${did}/${input.collection}/${input.rkey}`; 33 + 34 + return { 35 + seq: 1, 36 + time: new Date().toISOString(), 37 + commit: {} as never, 38 + blocks: {} as never, 39 + rev: "rev1", 40 + did, 41 + collection: input.collection, 42 + rkey: input.rkey, 43 + uri: { toString: () => atUri } as never, 44 + event: input.event, 45 + record: input.record, 46 + cid: {} as never, 47 + } as CommitEvt; 48 + } 49 + 50 + const HANDLER_TEST_WIKIS = [ 51 + "test-wiki", 52 + "delete-me", 53 + "spoofed-wiki", 54 + "evil-wiki", 55 + "incomplete-wiki", 56 + ]; 57 + 58 + beforeAll(() => { 59 + for (const slug of HANDLER_TEST_WIKIS) { 60 + const notes = db 61 + .query("SELECT at_uri FROM notes WHERE wiki_slug = ?") 62 + .all(slug) as { at_uri: string }[]; 63 + for (const note of notes) { 64 + db.run("DELETE FROM current_note WHERE note_at_uri = ?", [note.at_uri]); 65 + db.run("DELETE FROM revisions WHERE note_at_uri = ?", [note.at_uri]); 66 + db.run("DELETE FROM snapshots WHERE note_at_uri = ?", [note.at_uri]); 67 + db.run("DELETE FROM backlinks WHERE source_note_uri = ?", [note.at_uri]); 68 + } 69 + db.run("DELETE FROM notes WHERE wiki_slug = ?", [slug]); 70 + db.run("DELETE FROM memberships WHERE wiki_slug = ?", [slug]); 71 + db.run("DELETE FROM requests WHERE wiki_slug = ?", [slug]); 72 + db.run("DELETE FROM wikis WHERE slug = ?", [slug]); 73 + } 74 + db.run("DELETE FROM firehose_cursor"); 75 + }); 76 + 77 + afterAll(() => { 78 + for (const slug of HANDLER_TEST_WIKIS) { 79 + const notes = db 80 + .query("SELECT at_uri FROM notes WHERE wiki_slug = ?") 81 + .all(slug) as { at_uri: string }[]; 82 + for (const note of notes) { 83 + db.run("DELETE FROM current_note WHERE note_at_uri = ?", [note.at_uri]); 84 + db.run("DELETE FROM revisions WHERE note_at_uri = ?", [note.at_uri]); 85 + db.run("DELETE FROM snapshots WHERE note_at_uri = ?", [note.at_uri]); 86 + db.run("DELETE FROM backlinks WHERE source_note_uri = ?", [note.at_uri]); 87 + } 88 + db.run("DELETE FROM notes WHERE wiki_slug = ?", [slug]); 89 + db.run("DELETE FROM memberships WHERE wiki_slug = ?", [slug]); 90 + db.run("DELETE FROM requests WHERE wiki_slug = ?", [slug]); 91 + db.run("DELETE FROM wikis WHERE slug = ?", [slug]); 92 + } 93 + }); 94 + 95 + describe("wiki handler", () => { 96 + test("creates wiki from firehose event", () => { 97 + handleCommitEvent( 98 + makeCommitEvt({ 99 + event: "create", 100 + collection: "pub.coral.wiki", 101 + rkey: "test-wiki", 102 + did: ALICE_DID, 103 + record: { 104 + name: "Test Wiki", 105 + visibility: "public", 106 + createdAt: "2026-01-01T00:00:00.000Z", 107 + }, 108 + }), 109 + ); 110 + 111 + const wiki = getWiki("test-wiki"); 112 + expect(wiki).not.toBeNull(); 113 + expect(wiki?.name).toBe("Test Wiki"); 114 + expect(wiki?.did).toBe(ALICE_DID); 115 + expect(wiki?.visibility).toBe("public"); 116 + expect(wiki?.at_uri).toBe(WIKI_AT_URI); 117 + }); 118 + 119 + test("updates wiki on update event", () => { 120 + handleCommitEvent( 121 + makeCommitEvt({ 122 + event: "update", 123 + collection: "pub.coral.wiki", 124 + rkey: "test-wiki", 125 + did: ALICE_DID, 126 + record: { 127 + name: "Updated Wiki", 128 + visibility: "private", 129 + createdAt: "2026-01-01T00:00:00.000Z", 130 + }, 131 + }), 132 + ); 133 + 134 + const wiki = getWiki("test-wiki"); 135 + expect(wiki?.name).toBe("Updated Wiki"); 136 + expect(wiki?.visibility).toBe("private"); 137 + }); 138 + 139 + test("rejects event when DID does not match AT-URI", () => { 140 + handleCommitEvent( 141 + makeCommitEvt({ 142 + event: "create", 143 + collection: "pub.coral.wiki", 144 + rkey: "evil-wiki", 145 + did: BOB_DID, 146 + uri: { 147 + toString: () => `at://${ALICE_DID}/pub.coral.wiki/evil-wiki`, 148 + }, 149 + record: { 150 + name: "Evil Wiki", 151 + visibility: "public", 152 + createdAt: "2026-01-01T00:00:00.000Z", 153 + }, 154 + }), 155 + ); 156 + 157 + const wiki = getWiki("evil-wiki"); 158 + expect(wiki).toBeNull(); 159 + }); 160 + 161 + test("skips record with missing required fields", () => { 162 + handleCommitEvent( 163 + makeCommitEvt({ 164 + event: "create", 165 + collection: "pub.coral.wiki", 166 + rkey: "incomplete-wiki", 167 + did: ALICE_DID, 168 + record: { 169 + name: "Incomplete", 170 + }, 171 + }), 172 + ); 173 + 174 + const wiki = getWiki("incomplete-wiki"); 175 + expect(wiki).toBeNull(); 176 + }); 177 + 178 + test("deletes wiki and cascades", () => { 179 + handleCommitEvent( 180 + makeCommitEvt({ 181 + event: "create", 182 + collection: "pub.coral.wiki", 183 + rkey: "delete-me", 184 + did: ALICE_DID, 185 + record: { 186 + name: "Delete Me", 187 + visibility: "public", 188 + createdAt: "2026-01-01T00:00:00.000Z", 189 + }, 190 + }), 191 + ); 192 + expect(getWiki("delete-me")).not.toBeNull(); 193 + 194 + handleCommitEvent( 195 + makeCommitEvt({ 196 + event: "delete", 197 + collection: "pub.coral.wiki", 198 + rkey: "delete-me", 199 + did: ALICE_DID, 200 + }), 201 + ); 202 + expect(getWiki("delete-me")).toBeNull(); 203 + }); 204 + }); 205 + 206 + describe("note handler", () => { 207 + const NOTE_TID = "362pbqd3tgb7z"; 208 + const NOTE_AT_URI = `at://${ALICE_DID}/pub.coral.note/${NOTE_TID}`; 209 + 210 + test("creates note linked to existing wiki", () => { 211 + handleCommitEvent( 212 + makeCommitEvt({ 213 + event: "create", 214 + collection: "pub.coral.note", 215 + rkey: NOTE_TID, 216 + did: ALICE_DID, 217 + record: { 218 + slug: "hello-world", 219 + title: "Hello World", 220 + wikiRef: WIKI_AT_URI, 221 + createdAt: "2026-01-01T00:00:00.000Z", 222 + }, 223 + }), 224 + ); 225 + 226 + const note = getNoteBySlug("test-wiki", "hello-world"); 227 + expect(note).not.toBeNull(); 228 + expect(note?.title).toBe("Hello World"); 229 + expect(note?.at_uri).toBe(NOTE_AT_URI); 230 + }); 231 + 232 + test("skips note when wiki does not exist", () => { 233 + handleCommitEvent( 234 + makeCommitEvt({ 235 + event: "create", 236 + collection: "pub.coral.note", 237 + rkey: "362pbqd3tgb8a", 238 + did: ALICE_DID, 239 + record: { 240 + slug: "orphan-note", 241 + title: "Orphan", 242 + wikiRef: `at://${ALICE_DID}/pub.coral.wiki/nonexistent`, 243 + createdAt: "2026-01-01T00:00:00.000Z", 244 + }, 245 + }), 246 + ); 247 + 248 + const note = getNoteBySlug("nonexistent", "orphan-note"); 249 + expect(note).toBeNull(); 250 + }); 251 + 252 + test("deletes note and cascades", () => { 253 + const delNoteTid = "362pbqd3tgb9z"; 254 + handleCommitEvent( 255 + makeCommitEvt({ 256 + event: "create", 257 + collection: "pub.coral.note", 258 + rkey: delNoteTid, 259 + did: ALICE_DID, 260 + record: { 261 + slug: "delete-me-note", 262 + title: "Delete Me", 263 + wikiRef: WIKI_AT_URI, 264 + createdAt: "2026-01-01T00:00:00.000Z", 265 + }, 266 + }), 267 + ); 268 + expect(getNoteBySlug("test-wiki", "delete-me-note")).not.toBeNull(); 269 + 270 + handleCommitEvent( 271 + makeCommitEvt({ 272 + event: "delete", 273 + collection: "pub.coral.note", 274 + rkey: delNoteTid, 275 + did: ALICE_DID, 276 + }), 277 + ); 278 + expect(getNoteBySlug("test-wiki", "delete-me-note")).toBeNull(); 279 + }); 280 + }); 281 + 282 + describe("revision handler", () => { 283 + const NOTE_TID = "362pbqd3tgb7z"; 284 + const NOTE_AT_URI = `at://${ALICE_DID}/pub.coral.note/${NOTE_TID}`; 285 + 286 + test("applies diff and updates current_note", () => { 287 + const revTid = "362pbqd3tgc2a"; 288 + handleCommitEvent( 289 + makeCommitEvt({ 290 + event: "create", 291 + collection: "pub.coral.noteRevision", 292 + rkey: revTid, 293 + did: ALICE_DID, 294 + record: { 295 + noteRef: NOTE_AT_URI, 296 + diff: "@@ -0,0 +1,11 @@\n+Hello world\n", 297 + diffFormat: "diff-match-patch", 298 + createdAt: "2026-01-01T00:00:00.000Z", 299 + }, 300 + }), 301 + ); 302 + 303 + const current = getCurrentNote("test-wiki", "hello-world"); 304 + expect(current).not.toBeNull(); 305 + expect(current?.content).toBe("Hello world"); 306 + }); 307 + 308 + test("chains revisions correctly", () => { 309 + const rev2Tid = "362pbqd3tgc3b"; 310 + const prevRevUri = `at://${ALICE_DID}/pub.coral.noteRevision/362pbqd3tgc2a`; 311 + 312 + handleCommitEvent( 313 + makeCommitEvt({ 314 + event: "create", 315 + collection: "pub.coral.noteRevision", 316 + rkey: rev2Tid, 317 + did: ALICE_DID, 318 + record: { 319 + noteRef: NOTE_AT_URI, 320 + parentRevision: prevRevUri, 321 + diff: "@@ -1,11 +1,19 @@\n Hello \n+updated \n world\n", 322 + diffFormat: "diff-match-patch", 323 + createdAt: "2026-01-02T00:00:00.000Z", 324 + }, 325 + }), 326 + ); 327 + 328 + const current = getCurrentNote("test-wiki", "hello-world"); 329 + expect(current?.content).toBe("Hello updated world"); 330 + }); 331 + 332 + test("updates backlinks from revision content", () => { 333 + const linkNoteTid = "362pbqd3tgd1a"; 334 + handleCommitEvent( 335 + makeCommitEvt({ 336 + event: "create", 337 + collection: "pub.coral.note", 338 + rkey: linkNoteTid, 339 + did: ALICE_DID, 340 + record: { 341 + slug: "linked-note", 342 + title: "Linked Note", 343 + wikiRef: WIKI_AT_URI, 344 + createdAt: "2026-01-01T00:00:00.000Z", 345 + }, 346 + }), 347 + ); 348 + 349 + const linkRevTid = "362pbqd3tgd2b"; 350 + handleCommitEvent( 351 + makeCommitEvt({ 352 + event: "create", 353 + collection: "pub.coral.noteRevision", 354 + rkey: linkRevTid, 355 + did: ALICE_DID, 356 + record: { 357 + noteRef: `at://${ALICE_DID}/pub.coral.note/${linkNoteTid}`, 358 + diff: "@@ -0,0 +1,25 @@\n+Check out %5B%5Bhello-world%5D%5D\n", 359 + diffFormat: "diff-match-patch", 360 + createdAt: "2026-01-01T00:00:00.000Z", 361 + }, 362 + }), 363 + ); 364 + 365 + const backlinks = db 366 + .query( 367 + "SELECT * FROM backlinks WHERE wiki_slug = ? AND target_note_slug = ?", 368 + ) 369 + .all("test-wiki", "hello-world") as { 370 + source_note_uri: string; 371 + }[]; 372 + expect(backlinks.length).toBeGreaterThanOrEqual(1); 373 + }); 374 + 375 + test("skips revision for nonexistent note", () => { 376 + const orphanRevTid = "362pbqd3tge1a"; 377 + handleCommitEvent( 378 + makeCommitEvt({ 379 + event: "create", 380 + collection: "pub.coral.noteRevision", 381 + rkey: orphanRevTid, 382 + did: ALICE_DID, 383 + record: { 384 + noteRef: `at://${ALICE_DID}/pub.coral.note/nonexistent`, 385 + diff: "@@ -0,0 +1,6 @@\n+orphan\n", 386 + diffFormat: "diff-match-patch", 387 + createdAt: "2026-01-01T00:00:00.000Z", 388 + }, 389 + }), 390 + ); 391 + 392 + const rev = db 393 + .query("SELECT * FROM revisions WHERE at_uri = ?") 394 + .get(`at://${ALICE_DID}/pub.coral.noteRevision/${orphanRevTid}`); 395 + expect(rev).toBeNull(); 396 + }); 397 + }); 398 + 399 + describe("membership handler", () => { 400 + test("creates membership when event DID is wiki owner", () => { 401 + const memberTid = "362pbqd3tgf1a"; 402 + handleCommitEvent( 403 + makeCommitEvt({ 404 + event: "create", 405 + collection: "pub.coral.membership", 406 + rkey: memberTid, 407 + did: ALICE_DID, 408 + record: { 409 + memberDid: BOB_DID, 410 + wikiRef: WIKI_AT_URI, 411 + role: "admin", 412 + createdAt: "2026-01-01T00:00:00.000Z", 413 + }, 414 + }), 415 + ); 416 + 417 + const membership = db 418 + .query("SELECT * FROM memberships WHERE wiki_slug = ? AND did = ?") 419 + .get("test-wiki", BOB_DID) as { role: string } | null; 420 + expect(membership).not.toBeNull(); 421 + expect(membership?.role).toBe("admin"); 422 + }); 423 + 424 + test("rejects membership when event DID is not wiki owner", () => { 425 + const evilTid = "362pbqd3tgf2b"; 426 + handleCommitEvent( 427 + makeCommitEvt({ 428 + event: "create", 429 + collection: "pub.coral.membership", 430 + rkey: evilTid, 431 + did: BOB_DID, 432 + record: { 433 + memberDid: "did:plc:carol", 434 + wikiRef: WIKI_AT_URI, 435 + role: "admin", 436 + createdAt: "2026-01-01T00:00:00.000Z", 437 + }, 438 + }), 439 + ); 440 + 441 + const membership = db 442 + .query("SELECT * FROM memberships WHERE wiki_slug = ? AND did = ?") 443 + .get("test-wiki", "did:plc:carol"); 444 + expect(membership).toBeNull(); 445 + }); 446 + 447 + test("deletes membership on delete event", () => { 448 + const memberTid = "362pbqd3tgf1a"; 449 + const memberAtUri = `at://${ALICE_DID}/pub.coral.membership/${memberTid}`; 450 + 451 + handleCommitEvent( 452 + makeCommitEvt({ 453 + event: "delete", 454 + collection: "pub.coral.membership", 455 + rkey: memberTid, 456 + did: ALICE_DID, 457 + }), 458 + ); 459 + 460 + const membership = db 461 + .query("SELECT * FROM memberships WHERE at_uri = ?") 462 + .get(memberAtUri); 463 + expect(membership).toBeNull(); 464 + }); 465 + }); 466 + 467 + describe("member request handler", () => { 468 + test("creates request when user requests to join", () => { 469 + const requestTid = "362pbqd3tgg1a"; 470 + handleCommitEvent( 471 + makeCommitEvt({ 472 + event: "create", 473 + collection: "pub.coral.memberRequest", 474 + rkey: requestTid, 475 + did: BOB_DID, 476 + record: { 477 + wikiRef: WIKI_AT_URI, 478 + createdAt: "2026-01-01T00:00:00.000Z", 479 + }, 480 + }), 481 + ); 482 + 483 + const request = db 484 + .query("SELECT * FROM requests WHERE wiki_slug = ? AND did = ?") 485 + .get("test-wiki", BOB_DID) as { did: string } | null; 486 + expect(request).not.toBeNull(); 487 + expect(request?.did).toBe(BOB_DID); 488 + }); 489 + 490 + test("deletes request on delete event", () => { 491 + const requestTid = "362pbqd3tgg1a"; 492 + handleCommitEvent( 493 + makeCommitEvt({ 494 + event: "delete", 495 + collection: "pub.coral.memberRequest", 496 + rkey: requestTid, 497 + did: BOB_DID, 498 + }), 499 + ); 500 + 501 + const request = db 502 + .query("SELECT * FROM requests WHERE wiki_slug = ? AND did = ?") 503 + .get("test-wiki", BOB_DID); 504 + expect(request).toBeNull(); 505 + }); 506 + }); 507 + 508 + describe("cursor persistence", () => { 509 + test("getCursor returns null initially", () => { 510 + expect(getCursor()).toBeNull(); 511 + }); 512 + 513 + test("setCursor and getCursor roundtrip", () => { 514 + setCursor(12345); 515 + expect(getCursor()).toBe(12345); 516 + }); 517 + 518 + test("setCursor updates existing cursor", () => { 519 + setCursor(99999); 520 + expect(getCursor()).toBe(99999); 521 + }); 522 + }); 523 + 524 + describe("security validations", () => { 525 + test("rejects wiki create when DID does not own AT-URI", () => { 526 + handleCommitEvent( 527 + makeCommitEvt({ 528 + event: "create", 529 + collection: "pub.coral.wiki", 530 + rkey: "spoofed-wiki", 531 + did: BOB_DID, 532 + uri: { 533 + toString: () => `at://${ALICE_DID}/pub.coral.wiki/spoofed-wiki`, 534 + }, 535 + record: { 536 + name: "Spoofed", 537 + visibility: "public", 538 + createdAt: "2026-01-01T00:00:00.000Z", 539 + }, 540 + }), 541 + ); 542 + 543 + expect(getWiki("spoofed-wiki")).toBeNull(); 544 + }); 545 + 546 + test("rejects note create when DID does not own AT-URI", () => { 547 + handleCommitEvent( 548 + makeCommitEvt({ 549 + event: "create", 550 + collection: "pub.coral.note", 551 + rkey: "362pbqd3tgh1a", 552 + did: BOB_DID, 553 + uri: { 554 + toString: () => `at://${ALICE_DID}/pub.coral.note/362pbqd3tgh1a`, 555 + }, 556 + record: { 557 + slug: "spoofed-note", 558 + title: "Spoofed", 559 + wikiRef: WIKI_AT_URI, 560 + createdAt: "2026-01-01T00:00:00.000Z", 561 + }, 562 + }), 563 + ); 564 + 565 + expect(getNoteBySlug("test-wiki", "spoofed-note")).toBeNull(); 566 + }); 567 + 568 + test("rejects delete when DID does not own AT-URI", () => { 569 + handleCommitEvent( 570 + makeCommitEvt({ 571 + event: "delete", 572 + collection: "pub.coral.wiki", 573 + rkey: "test-wiki", 574 + did: BOB_DID, 575 + uri: { 576 + toString: () => `at://${ALICE_DID}/pub.coral.wiki/test-wiki`, 577 + }, 578 + }), 579 + ); 580 + 581 + expect(getWiki("test-wiki")).not.toBeNull(); 582 + }); 583 + });
+1
tests/preload.ts
··· 1 + process.env.DB_PATH = ":memory:";
-3
tests/server/db/queries.test.ts
··· 1 - // Set DB_PATH before any imports that trigger getDb() 2 - process.env.DB_PATH = `:memory:`; 3 - 4 1 import { afterAll, beforeAll, describe, expect, test } from "bun:test"; 5 2 import { applyDiff } from "../../../src/lib/diff.ts"; 6 3 import { getDb } from "../../../src/server/db/index.ts";