diff --git a/migrations/0011_perf_indexes.sql b/migrations/0011_perf_indexes.sql new file mode 100644 index 0000000..81c5e21 --- /dev/null +++ b/migrations/0011_perf_indexes.sql @@ -0,0 +1,4 @@ +-- Keep federation delivery and home timeline lookups indexed as caches grow. + +CREATE INDEX IF NOT EXISTS idx_actor_cache_inbox ON actor_cache(inbox); +CREATE INDEX IF NOT EXISTS idx_outgoing_follows_target_accepted ON outgoing_follows(target_actor, accepted); diff --git a/package-lock.json b/package-lock.json index 68a5118..182e5a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "toot-worker", - "version": "0.1.0", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "toot-worker", - "version": "0.1.0", + "version": "0.3.0", "devDependencies": { - "@cloudflare/workers-types": "^4.20260507.0", + "@cloudflare/workers-types": "^4.20260617.1", "typescript": "^5.9.3", - "wrangler": "^4.37.0" + "wrangler": "^4.102.0" } }, "node_modules/@cloudflare/kv-asset-handler": { @@ -40,9 +40,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20260508.1", - "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260508.1.tgz", - "integrity": "sha512-IT3r6VgiSwIesL4AJbxjgxvIxwWZqM7BKkhYAzOKHl4GF2M0TxeOahUIXd+CYXVZgHX8ceEg+MXbEehPelJyNg==", + "version": "1.20260617.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260617.1.tgz", + "integrity": "sha512-jWwmgEVVWbsHNrLSNXzwjJaH90VzRxq1cWkQFUidxyeUPnMxemeNE8I9qFAfrpzGgE11e9sKDcE3ettJW08swQ==", "cpu": [ "x64" ], @@ -57,9 +57,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20260508.1", - "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260508.1.tgz", - "integrity": "sha512-JTVsisOJPcNKw0qovPjqyBWYahfdhUh7/9NICiG5wxaEQ45PYKdoqNq0hOAAIqvqoxsKZBvTgcPTJREPqk7avA==", + "version": "1.20260617.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260617.1.tgz", + "integrity": "sha512-LHH7b565g9znfCUOkwbec6FG2rmRbsgCy6aJiU9KN662mNheWl5sw/iKleiFSiljPKQQP3HkjnC/NSkdgi/aSA==", "cpu": [ "arm64" ], @@ -74,9 +74,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20260508.1", - "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260508.1.tgz", - "integrity": "sha512-zO38pCc27YlsZiPYcaZnosy0/t7abXrRU3VEO1oKfUvnaCpHgphDG+VsrmHL+kntda6hrtNwg2jLeMAqqIjnjw==", + "version": "1.20260617.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260617.1.tgz", + "integrity": "sha512-FMnaAKXe4Cfd8TQurCVd9fs2XQVBFRCsP+Id/SRdUv89MlwYu9zXfoyx6BxM+brPTIUK38SHbo8iaxiwzLi9JQ==", "cpu": [ "x64" ], @@ -91,9 +91,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20260508.1", - "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260508.1.tgz", - "integrity": "sha512-XhJa780Ia6MNIrtxn/ruZHS79b9pu5EKPfRNReaUqxy8erPT2fs93axMfFoS9kIkcaRRj/1TOUKcTeAMoywY7w==", + "version": "1.20260617.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260617.1.tgz", + "integrity": "sha512-MRoifFYcqbxxIIQy7PqO5tFY/qPFSnjXzakWl0sO93l+HLyG35jRAgOi6jfqa4kBxc7gKKtH861DcewjxUfkjA==", "cpu": [ "arm64" ], @@ -108,9 +108,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20260508.1", - "resolved": "https://registry.npmmirror.com/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260508.1.tgz", - "integrity": "sha512-QdDOK3B/Ul1s3QmIwDrFyx9230to6LsNmWcVR8w+TYjNZuRPzqQBgusp78LO7MlqCoEl9dvIcN00jkJnLtBSfw==", + "version": "1.20260617.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260617.1.tgz", + "integrity": "sha512-rgBV9wQrv0OSKgCTTbhFUFY3sLGNANZ88aqaLvtmEn2gmbFVb1J4PDGochVUdB7NSEp4D/ghHva6/8SZmbONpw==", "cpu": [ "x64" ], @@ -125,15 +125,15 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20260511.1", - "resolved": "https://registry.npmmirror.com/@cloudflare/workers-types/-/workers-types-4.20260511.1.tgz", - "integrity": "sha512-FA+si7cOq9i/gtCHhIc0XJL0l1F/ApF+m00752Aj7WZFJrj3ZulT2T8/+rT3BabMT0QEnqFEGIqCgrmqhgEfMg==", + "version": "4.20260617.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20260617.1.tgz", + "integrity": "sha512-HdbP3CNcdMZBwegitFDjWvzv+6wPkFXvV9gBXMnf6RjV2Cy3W8TJL3IhSEGul0S6F1DHjnucP7lrpIsvkzNEjA==", "dev": true, "license": "MIT OR Apache-2.0" }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", - "resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "license": "MIT", @@ -145,9 +145,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "dev": true, "license": "MIT", "optional": true, @@ -156,9 +156,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -173,9 +173,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -190,9 +190,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -207,9 +207,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -224,9 +224,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -241,9 +241,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -258,9 +258,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -275,9 +275,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -292,9 +292,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -309,9 +309,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -326,9 +326,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -343,9 +343,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -360,9 +360,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -377,9 +377,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -394,9 +394,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -411,9 +411,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -428,9 +428,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -445,9 +445,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -462,9 +462,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -479,9 +479,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -496,9 +496,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -513,9 +513,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -530,9 +530,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -547,9 +547,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -564,9 +564,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -581,9 +581,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -599,7 +599,7 @@ }, "node_modules/@img/colour": { "version": "1.1.0", - "resolved": "https://registry.npmmirror.com/@img/colour/-/colour-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", "dev": true, "license": "MIT", @@ -609,7 +609,7 @@ }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" @@ -632,7 +632,7 @@ }, "node_modules/@img/sharp-darwin-x64": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" @@ -655,7 +655,7 @@ }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" @@ -672,7 +672,7 @@ }, "node_modules/@img/sharp-libvips-darwin-x64": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" @@ -689,15 +689,12 @@ }, "node_modules/@img/sharp-libvips-linux-arm": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -709,15 +706,12 @@ }, "node_modules/@img/sharp-libvips-linux-arm64": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -729,15 +723,12 @@ }, "node_modules/@img/sharp-libvips-linux-ppc64": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", "cpu": [ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -749,15 +740,12 @@ }, "node_modules/@img/sharp-libvips-linux-riscv64": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", "cpu": [ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -769,15 +757,12 @@ }, "node_modules/@img/sharp-libvips-linux-s390x": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", "cpu": [ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -789,15 +774,12 @@ }, "node_modules/@img/sharp-libvips-linux-x64": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -809,15 +791,12 @@ }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -829,15 +808,12 @@ }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { "version": "1.2.4", - "resolved": "https://registry.npmmirror.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -849,15 +825,12 @@ }, "node_modules/@img/sharp-linux-arm": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -875,15 +848,12 @@ }, "node_modules/@img/sharp-linux-arm64": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -901,15 +871,12 @@ }, "node_modules/@img/sharp-linux-ppc64": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", "cpu": [ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -927,15 +894,12 @@ }, "node_modules/@img/sharp-linux-riscv64": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", "cpu": [ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -953,15 +917,12 @@ }, "node_modules/@img/sharp-linux-s390x": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", "cpu": [ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -979,15 +940,12 @@ }, "node_modules/@img/sharp-linux-x64": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1005,15 +963,12 @@ }, "node_modules/@img/sharp-linuxmusl-arm64": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", "cpu": [ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1031,15 +986,12 @@ }, "node_modules/@img/sharp-linuxmusl-x64": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", "cpu": [ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1057,7 +1009,7 @@ }, "node_modules/@img/sharp-wasm32": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", "cpu": [ "wasm32" @@ -1077,7 +1029,7 @@ }, "node_modules/@img/sharp-win32-arm64": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", "cpu": [ "arm64" @@ -1097,7 +1049,7 @@ }, "node_modules/@img/sharp-win32-ia32": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", "cpu": [ "ia32" @@ -1117,7 +1069,7 @@ }, "node_modules/@img/sharp-win32-x64": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", "cpu": [ "x64" @@ -1137,7 +1089,7 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", @@ -1147,14 +1099,14 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", - "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, "license": "MIT", @@ -1165,7 +1117,7 @@ }, "node_modules/@poppinss/colors": { "version": "4.1.6", - "resolved": "https://registry.npmmirror.com/@poppinss/colors/-/colors-4.1.6.tgz", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", "dev": true, "license": "MIT", @@ -1175,7 +1127,7 @@ }, "node_modules/@poppinss/dumper": { "version": "0.6.5", - "resolved": "https://registry.npmmirror.com/@poppinss/dumper/-/dumper-0.6.5.tgz", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", "dev": true, "license": "MIT", @@ -1187,14 +1139,14 @@ }, "node_modules/@poppinss/exception": { "version": "1.2.3", - "resolved": "https://registry.npmmirror.com/@poppinss/exception/-/exception-1.2.3.tgz", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", "dev": true, "license": "MIT" }, "node_modules/@sindresorhus/is": { "version": "7.2.0", - "resolved": "https://registry.npmmirror.com/@sindresorhus/is/-/is-7.2.0.tgz", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", "dev": true, "license": "MIT", @@ -1206,9 +1158,9 @@ } }, "node_modules/@speed-highlight/core": { - "version": "1.2.15", - "resolved": "https://registry.npmmirror.com/@speed-highlight/core/-/core-1.2.15.tgz", - "integrity": "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw==", + "version": "1.2.17", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.17.tgz", + "integrity": "sha512-Z92FwKpCtfaW1V0jTU/fh3QzYEZN8wDwrzRIBoADCJfn4mJCNcJN/XegifX7BDrQ8/h9Xh/JnbyMchL0FqXrkg==", "dev": true, "license": "CC0-1.0" }, @@ -1221,7 +1173,7 @@ }, "node_modules/cookie": { "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/cookie/-/cookie-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "dev": true, "license": "MIT", @@ -1235,7 +1187,7 @@ }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", @@ -1245,7 +1197,7 @@ }, "node_modules/error-stack-parser-es": { "version": "1.0.5", - "resolved": "https://registry.npmmirror.com/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", "dev": true, "license": "MIT", @@ -1254,9 +1206,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1267,32 +1219,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/fsevents": { @@ -1312,7 +1264,7 @@ }, "node_modules/kleur": { "version": "4.1.5", - "resolved": "https://registry.npmmirror.com/kleur/-/kleur-4.1.5.tgz", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", "dev": true, "license": "MIT", @@ -1321,17 +1273,17 @@ } }, "node_modules/miniflare": { - "version": "4.20260508.0", - "resolved": "https://registry.npmmirror.com/miniflare/-/miniflare-4.20260508.0.tgz", - "integrity": "sha512-h3aG+PA8jEH76V4ZtBAbs3g7kjMfHJUF8hPvxeeajLTKwir+G+dqfBODg5yF9MT29LqrZKCRQRqzfHPWX4kCIg==", + "version": "4.20260617.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20260617.0.tgz", + "integrity": "sha512-A+H5gcOCQZsKFg7/daZUtx8WHn4gGxwUfH1jnNDAisyAWSvvSZHe+GCeQWs16uthnUDcm72UQIQ1NXDJtnuo9Q==", "dev": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "0.8.1", - "sharp": "^0.34.5", - "undici": "7.24.8", - "workerd": "1.20260508.1", - "ws": "8.18.0", + "sharp": "0.34.5", + "undici": "7.28.0", + "workerd": "1.20260617.1", + "ws": "8.21.0", "youch": "4.1.0-beta.10" }, "bin": { @@ -1356,9 +1308,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "dev": true, "license": "ISC", "bin": { @@ -1370,7 +1322,7 @@ }, "node_modules/sharp": { "version": "0.34.5", - "resolved": "https://registry.npmmirror.com/sharp/-/sharp-0.34.5.tgz", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", "dev": true, "hasInstallScript": true, @@ -1415,7 +1367,7 @@ }, "node_modules/supports-color": { "version": "10.2.2", - "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-10.2.2.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true, "license": "MIT", @@ -1428,7 +1380,7 @@ }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD", @@ -1449,9 +1401,9 @@ } }, "node_modules/undici": { - "version": "7.24.8", - "resolved": "https://registry.npmmirror.com/undici/-/undici-7.24.8.tgz", - "integrity": "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "dev": true, "license": "MIT", "engines": { @@ -1469,9 +1421,9 @@ } }, "node_modules/workerd": { - "version": "1.20260508.1", - "resolved": "https://registry.npmmirror.com/workerd/-/workerd-1.20260508.1.tgz", - "integrity": "sha512-VlnjyH3AjVddpSK7J54nsCVgf8i2733pl8GjKttfNi7vN/hEjjAk20d2b1nDToOLKvRQpTewRnVkqaaeGHCaAw==", + "version": "1.20260617.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260617.1.tgz", + "integrity": "sha512-Re5pl6pdowt3ZmWUzGlOuB7jbRIIPetgKalmo4cYmucQnVhpo7/3e4MfpekbhLi2EhZZz5EY9NWRu8zFzuEZew==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -1482,30 +1434,31 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20260508.1", - "@cloudflare/workerd-darwin-arm64": "1.20260508.1", - "@cloudflare/workerd-linux-64": "1.20260508.1", - "@cloudflare/workerd-linux-arm64": "1.20260508.1", - "@cloudflare/workerd-windows-64": "1.20260508.1" + "@cloudflare/workerd-darwin-64": "1.20260617.1", + "@cloudflare/workerd-darwin-arm64": "1.20260617.1", + "@cloudflare/workerd-linux-64": "1.20260617.1", + "@cloudflare/workerd-linux-arm64": "1.20260617.1", + "@cloudflare/workerd-windows-64": "1.20260617.1" } }, "node_modules/wrangler": { - "version": "4.90.1", - "resolved": "https://registry.npmmirror.com/wrangler/-/wrangler-4.90.1.tgz", - "integrity": "sha512-u2KrieKSMfRM0toTst/CfDtcRraeoVjmcExcMWgILM/ytq3qcDhuOAULoZSyPHzma43lfLJy1BC544drFyqe1A==", + "version": "4.102.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.102.0.tgz", + "integrity": "sha512-GPljlQs9a+/Ai2h0TdEUYaaWv9upK4fUteSWTPlruas7tdixiIwr74CZSWHcEPuRgZYkyYKHIBbT1w+BYPkPrw==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", - "esbuild": "0.27.3", - "miniflare": "4.20260508.0", + "esbuild": "0.28.1", + "miniflare": "4.20260617.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", - "workerd": "1.20260508.1" + "workerd": "1.20260617.1" }, "bin": { + "cf-wrangler": "bin/cf-wrangler.js", "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" }, @@ -1513,10 +1466,10 @@ "node": ">=22.0.0" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "2.3.3" }, "peerDependencies": { - "@cloudflare/workers-types": "^4.20260508.1" + "@cloudflare/workers-types": "^4.20260617.1" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { @@ -1525,9 +1478,9 @@ } }, "node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "dev": true, "license": "MIT", "engines": { @@ -1548,7 +1501,7 @@ }, "node_modules/youch": { "version": "4.1.0-beta.10", - "resolved": "https://registry.npmmirror.com/youch/-/youch-4.1.0-beta.10.tgz", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", "dev": true, "license": "MIT", @@ -1562,7 +1515,7 @@ }, "node_modules/youch-core": { "version": "0.3.3", - "resolved": "https://registry.npmmirror.com/youch-core/-/youch-core-0.3.3.tgz", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", "dev": true, "license": "MIT", diff --git a/package.json b/package.json index 571792c..36d5bfd 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "db:remote": "wrangler d1 migrations apply toot_db --remote" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20260507.0", + "@cloudflare/workers-types": "^4.20260617.1", "typescript": "^5.9.3", - "wrangler": "^4.37.0" + "wrangler": "^4.102.0" } } diff --git a/src/db.ts b/src/db.ts index 9b99f81..6abd5ba 100644 --- a/src/db.ts +++ b/src/db.ts @@ -13,6 +13,7 @@ import type { Marker, OutgoingDelivery, OutgoingFollow, + OAuthToken, Reblog, RemoteActor, Status, @@ -21,7 +22,17 @@ import type { import { ACTOR_CACHE_TTL_MS } from "./types"; import { id } from "./util"; +let adminUserReady: Promise | null = null; + export async function ensureAdminUser(env: Env): Promise { + adminUserReady ??= ensureAdminUserOnce(env).catch((error) => { + adminUserReady = null; + throw error; + }); + await adminUserReady; +} + +async function ensureAdminUserOnce(env: Env): Promise { const existing = await env.DB.prepare("SELECT id FROM users WHERE username = ?").bind(env.ADMIN_USERNAME).first<{ id: string }>(); if (existing) return; const adminPassword = env.ADMIN_PASSWORD; @@ -306,6 +317,10 @@ export async function insertOAuthToken(env: Env, token: string, userId: string, .bind(token, userId, appId, scopes, new Date().toISOString()).run(); } +export async function getOAuthToken(env: Env, token: string): Promise { + return env.DB.prepare("SELECT * FROM oauth_tokens WHERE token = ?").bind(token).first(); +} + export async function deleteOAuthToken(env: Env, token: string): Promise { await env.DB.prepare("DELETE FROM oauth_tokens WHERE token = ?").bind(token).run(); } diff --git a/src/federation.ts b/src/federation.ts index d8bb0c3..5c9a56d 100644 --- a/src/federation.ts +++ b/src/federation.ts @@ -25,9 +25,11 @@ import { actorUrl, base64Decode, encoder, hostFromBaseUrl, parseAcctFromActor } const ACTIVITY_HEADERS = "application/activity+json, application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""; const DELIVERY_BATCH_SIZE = 20; +const DELIVERY_CONCURRENCY = 4; const DELIVERY_MAX_ATTEMPTS = 8; const DELIVERY_LEASE_MS = 60_000; const DELIVERY_MAX_BACKOFF_SECONDS = 60 * 60; +const REMOTE_FETCH_TIMEOUT_MS = 10_000; export async function resolveRemoteActor(env: Env, actorId: string, opts: { force?: boolean } = {}): Promise { if (!actorId) return null; @@ -42,6 +44,7 @@ export async function fetchRemoteActor(actorId: string): Promise ""); if (!response.ok) console.warn("signed-delivery", inboxUrl, response.status, text.slice(0, 200)); @@ -169,9 +173,18 @@ export async function deliverToInboxes(env: Env, user: User, inboxes: Iterable { const now = new Date().toISOString(); const deliveries = await listDueOutgoingDeliveries(env, now, DELIVERY_BATCH_SIZE); - for (const delivery of deliveries) { - await processOutgoingDelivery(env, delivery); - } + await runWithConcurrency(deliveries, DELIVERY_CONCURRENCY, (delivery) => processOutgoingDelivery(env, delivery)); +} + +async function runWithConcurrency(items: T[], concurrency: number, worker: (item: T) => Promise): Promise { + let cursor = 0; + const runners = Array.from({ length: Math.min(concurrency, items.length) }, async () => { + while (cursor < items.length) { + const item = items[cursor++]; + await worker(item); + } + }); + await Promise.all(runners); } async function processOutgoingDelivery(env: Env, delivery: OutgoingDelivery): Promise { @@ -218,17 +231,12 @@ function nextDeliveryAttemptAt(attempts: number): string { export async function gatherFollowerInboxes(env: Env, userId: string): Promise { const rows = await env.DB.prepare( - "SELECT inbox FROM follows WHERE local_user_id = ? AND accepted = 1" + `SELECT COALESCE(ac.shared_inbox, f.inbox) AS inbox + FROM follows f + LEFT JOIN actor_cache ac ON ac.inbox = f.inbox + WHERE f.local_user_id = ? AND f.accepted = 1` ).bind(userId).all<{ inbox: string }>(); - const inboxes = new Set(); - for (const row of rows.results) { - if (row.inbox) { - const actor = await getActorFromCache(env, row.inbox); - const shared = (await getActorByKeyId(env, row.inbox))?.shared_inbox; - inboxes.add(actor?.shared_inbox ?? shared ?? row.inbox); - } - } - return [...inboxes]; + return [...new Set(rows.results.map((row) => row.inbox).filter(Boolean))]; } export async function resolveDeliveryInboxes(env: Env, actorIds: Iterable): Promise { diff --git a/src/index.ts b/src/index.ts index 5ff214f..7edf25a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -97,7 +97,9 @@ export default { try { await ensureAdminUser(env); const response = await route(request, env); - ctx.waitUntil(processOutgoingDeliveries(env)); + if (shouldDrainOutgoingDeliveries(request)) { + ctx.waitUntil(processOutgoingDeliveries(env)); + } return response; } catch (error) { if (error instanceof HttpError) return json({ error: error.message }, error.status); @@ -252,3 +254,8 @@ function wantsHtmlDocument(request: Request): boolean { const accept = request.headers.get("accept") ?? ""; return /\btext\/html\b/i.test(accept) && !/(application\/activity\+json|application\/ld\+json)/i.test(accept); } + +function shouldDrainOutgoingDeliveries(request: Request): boolean { + const method = request.method.toUpperCase(); + return method === "POST" || method === "PUT" || method === "PATCH" || method === "DELETE"; +} diff --git a/src/mastodon.ts b/src/mastodon.ts index 7fc8e3f..2d2ca1b 100644 --- a/src/mastodon.ts +++ b/src/mastodon.ts @@ -27,6 +27,7 @@ import { getActorFromCache, getAdminUser, getAppByClientId, + getOAuthToken, getCachedStatusByObjectId, getStatus, getUserById, @@ -43,7 +44,8 @@ import { saveMarker, setUserAvatarKey, setUserHeaderKey, - takeOAuthCode + takeOAuthCode, + touchOAuthToken } from "./db"; import { deliverToInboxes, @@ -103,7 +105,7 @@ import { const TOKEN_TTL_SECONDS = 60 * 60 * 24 * 90; const MAX_STATUS_CHARS = 5000; -const REPORTED_MEDIA_ATTACHMENTS_LIMIT = 9999; +const MAX_MEDIA_ATTACHMENTS = 20; const MAX_MEDIA_BYTES = 10 * 1024 * 1024; const SUPPORTED_MIME = ["image/jpeg", "image/png", "image/gif", "image/webp"]; @@ -175,7 +177,7 @@ export async function instance(env: Env): Promise { approval_required: false, invites_enabled: false, configuration: { - statuses: { max_characters: MAX_STATUS_CHARS, max_media_attachments: REPORTED_MEDIA_ATTACHMENTS_LIMIT, characters_reserved_per_url: 23 }, + statuses: { max_characters: MAX_STATUS_CHARS, max_media_attachments: MAX_MEDIA_ATTACHMENTS, characters_reserved_per_url: 23 }, media_attachments: { supported_mime_types: SUPPORTED_MIME, image_size_limit: MAX_MEDIA_BYTES, image_matrix_limit: 16777216 }, polls: { max_options: MAX_POLL_OPTIONS, max_characters_per_option: MAX_POLL_OPTION_CHARS, min_expiration: MIN_POLL_EXPIRATION_SECONDS, max_expiration: MAX_POLL_EXPIRATION_SECONDS } }, @@ -198,7 +200,7 @@ export async function instanceV2(env: Env): Promise { configuration: { urls: { streaming: `wss://${hostFromBaseUrl(env)}` }, accounts: { max_featured_tags: 0 }, - statuses: { max_characters: MAX_STATUS_CHARS, max_media_attachments: REPORTED_MEDIA_ATTACHMENTS_LIMIT, characters_reserved_per_url: 23 }, + statuses: { max_characters: MAX_STATUS_CHARS, max_media_attachments: MAX_MEDIA_ATTACHMENTS, characters_reserved_per_url: 23 }, media_attachments: { supported_mime_types: SUPPORTED_MIME, image_size_limit: MAX_MEDIA_BYTES, image_matrix_limit: 16777216 }, polls: { max_options: MAX_POLL_OPTIONS, max_characters_per_option: MAX_POLL_OPTION_CHARS, min_expiration: MIN_POLL_EXPIRATION_SECONDS, max_expiration: MAX_POLL_EXPIRATION_SECONDS } }, @@ -242,8 +244,9 @@ export async function verifyAppCredentials(request: Request, env: Env): Promise< const auth = request.headers.get("authorization") ?? ""; const token = auth.match(/^Bearer\s+(.+)$/i)?.[1]; if (!token) throw new HttpError(401, "The access token is invalid"); - const session = await env.KV.get(`token:${token}`, "json"); + const session = await loadSession(env, token); if (!session) throw new HttpError(401, "The access token is invalid"); + requireScopes(session, ["read"]); const app = await env.DB.prepare("SELECT * FROM oauth_apps WHERE id = ?").bind(session.appId).first<{ name: string; website: string | null }>(); return json({ name: app?.name ?? "Mastodon App", website: app?.website ?? null, vapid_key: "" }); } @@ -285,7 +288,7 @@ export async function authorize(request: Request, env: Env): Promise { } const code = tokenString(32); - const scope = bodyString(body, "scope", app.scopes); + const scope = requestedScopesWithinApp(bodyString(body, "scope", app.scopes), app.scopes); await env.DB.prepare("INSERT INTO oauth_codes (code, app_id, user_id, redirect_uri, scopes, expires_at) VALUES (?, ?, ?, ?, ?, ?)") .bind(code, app.id, user.id, redirectUri, scope, Math.floor(Date.now() / 1000) + 600) .run(); @@ -311,9 +314,9 @@ export async function token(request: Request, env: Env): Promise { const user = await getUserByUsername(env, bodyString(body, "username")); if (!user || !(await verifyPassword(bodyString(body, "password"), user.password_hash))) return json({ error: "invalid_grant" }, 400); userId = user.id; - scopes = bodyString(body, "scope", app.scopes); + scopes = requestedScopesWithinApp(bodyString(body, "scope", app.scopes), app.scopes); } else if (grantType === "client_credentials") { - scopes = bodyString(body, "scope", "read"); + scopes = requestedScopesWithinApp(bodyString(body, "scope", "read"), app.scopes); } else { const row = await takeOAuthCode(env, bodyString(body, "code")); if (!row || row.app_id !== app.id) return json({ error: "invalid_grant" }, 400); @@ -407,12 +410,17 @@ export async function updateCredentials(request: Request, env: Env): Promise { + if (!isSupportedImageMime(file.type)) throw new HttpError(415, "unsupported media type"); const ext = mimeExtension(file.type) ?? safeFileName(file.name).split(".").pop() ?? "bin"; const key = `${userId}/${kind}-${id()}.${ext}`; await env.MEDIA.put(key, file.stream(), { httpMetadata: { contentType: file.type || "application/octet-stream" } }); return key; } +function isSupportedImageMime(mime: string): boolean { + return SUPPORTED_MIME.includes(mime) || mime === "image/jpg"; +} + function mimeExtension(mime: string): string | null { switch (mime) { case "image/jpeg": case "image/jpg": return "jpg"; @@ -740,6 +748,8 @@ function parseStatusCreateInput(body: ParsedBody): StatusCreateInput { if (pollOptions.some((option) => option.length > MAX_POLL_OPTION_CHARS)) throw new HttpError(422, "poll_option_too_long"); const pollExpiresIn = pollOptions.length > 0 ? parsePollExpiresIn(bodyString(body, "poll[expires_in]", String(MIN_POLL_EXPIRATION_SECONDS))) : null; + const mediaIds = bodyArray(body, "media_ids"); + if (mediaIds.length > MAX_MEDIA_ATTACHMENTS) throw new HttpError(422, "too_many_media_attachments"); return { statusText, @@ -748,7 +758,7 @@ function parseStatusCreateInput(body: ParsedBody): StatusCreateInput { visibility, inReplyTo: bodyString(body, "in_reply_to_id"), language: bodyString(body, "language", "en"), - mediaIds: bodyArray(body, "media_ids"), + mediaIds, pollOptions, pollExpiresIn, pollMultiple: bodyString(body, "poll[multiple]") === "true", @@ -766,6 +776,8 @@ function parseStatusEditInput(body: ParsedBody, existing: Status): StatusEditInp const visibility = bodyString(body, "visibility", existing.visibility); if (!isStatusVisibility(visibility)) throw new HttpError(422, "invalid_visibility"); + const mediaIds = Object.prototype.hasOwnProperty.call(body, "media_ids") ? bodyArray(body, "media_ids") : null; + if (mediaIds && mediaIds.length > MAX_MEDIA_ATTACHMENTS) throw new HttpError(422, "too_many_media_attachments"); return { statusText, @@ -775,7 +787,7 @@ function parseStatusEditInput(body: ParsedBody, existing: Status): StatusEditInp : Boolean(existing.sensitive), visibility, language: bodyString(body, "language", existing.language || "en"), - mediaIds: Object.prototype.hasOwnProperty.call(body, "media_ids") ? bodyArray(body, "media_ids") : null + mediaIds }; } @@ -1381,6 +1393,7 @@ export async function uploadMedia(request: Request, env: Env): Promise const file = form.get("file"); if (!(file instanceof File)) return json({ error: "file is required" }, 422); if (file.size > MAX_MEDIA_BYTES) return json({ error: "file too large" }, 413); + if (!isSupportedImageMime(file.type)) return json({ error: "unsupported media type" }, 415); const mediaId = id(); const key = `${user.id}/${mediaId}/${safeFileName(file.name || "upload")}`; @@ -1606,7 +1619,7 @@ export async function listTimeline(request: Request, env: Env, listId: string): } export async function followAccount(request: Request, env: Env, accountId: string): Promise { - const user = await requireUser(request, env); + const user = await requireUser(request, env, ["follow"]); const target = await resolveAccountTarget(env, accountId); if (!target) return json({ error: "Record not found" }, 404); @@ -1630,7 +1643,7 @@ export async function followAccount(request: Request, env: Env, accountId: strin } export async function unfollowAccount(request: Request, env: Env, accountId: string): Promise { - const user = await requireUser(request, env); + const user = await requireUser(request, env, ["follow"]); const target = await resolveAccountTarget(env, accountId); if (!target) return json({ error: "Record not found" }, 404); @@ -1649,23 +1662,23 @@ export async function unfollowAccount(request: Request, env: Env, accountId: str } export async function followRequestsList(request: Request, env: Env): Promise { - await requireUser(request, env); + await requireUser(request, env, ["follow"]); return json([]); } export async function authorizeFollowRequest(request: Request, env: Env, _accountId: string): Promise { - await requireUser(request, env); + await requireUser(request, env, ["follow"]); return json({ id: _accountId, following: true, requested: false }); } export async function rejectFollowRequest(request: Request, env: Env, _accountId: string): Promise { - await requireUser(request, env); + await requireUser(request, env, ["follow"]); return json({ id: _accountId, following: false, requested: false }); } export async function search(request: Request, env: Env): Promise { const url = new URL(request.url); - const q = (url.searchParams.get("q") ?? "").trim(); + const q = (url.searchParams.get("q") ?? "").trim().slice(0, 128); const type = url.searchParams.get("type"); const accounts: unknown[] = []; const statuses: unknown[] = []; @@ -1686,7 +1699,7 @@ export async function search(request: Request, env: Env): Promise { } } } else { - const rows = await env.DB.prepare("SELECT * FROM users WHERE username LIKE ? LIMIT 20").bind(`%${q}%`).all(); + const rows = await env.DB.prepare("SELECT * FROM users WHERE username LIKE ? ESCAPE '\\' LIMIT 20").bind(likeContains(q)).all(); for (const row of rows.results) accounts.push(await accountJson(env, row)); } } @@ -1697,20 +1710,24 @@ export async function search(request: Request, env: Env): Promise { if (remoteStatus && await canViewerViewCachedStatus(env, remoteStatus, viewer)) { statuses.push(await cachedStatusToMastodon(env, remoteStatus)); } - const rows = await env.DB.prepare("SELECT * FROM statuses WHERE content LIKE ? ORDER BY created_at DESC LIMIT 100").bind(`%${escapeHtml(q)}%`).all(); + const rows = await env.DB.prepare("SELECT * FROM statuses WHERE content LIKE ? ESCAPE '\\' ORDER BY created_at DESC LIMIT 100").bind(likeContains(q)).all(); const visibleRows = await filterStatusesForViewer(env, rows.results, viewer); statuses.push(...await serializeStatuses(env, visibleRows.slice(0, 20), request)); } if (!type || type === "hashtags") { const tag = q.replace(/^#/, ""); - const rows = await env.DB.prepare("SELECT tag, COUNT(*) AS count FROM hashtags WHERE tag LIKE ? GROUP BY tag LIMIT 20").bind(`%${tag}%`).all<{ tag: string; count: number }>(); + const rows = await env.DB.prepare("SELECT tag, COUNT(*) AS count FROM hashtags WHERE tag LIKE ? ESCAPE '\\' GROUP BY tag LIMIT 20").bind(likeContains(tag)).all<{ tag: string; count: number }>(); for (const row of rows.results) hashtags.push({ name: row.tag, url: `${baseUrl(env)}/tags/${encodeURIComponent(row.tag)}`, history: [] }); } return json({ accounts, statuses, hashtags }); } +function likeContains(value: string): string { + return `%${value.replace(/[\\%_]/g, (char) => `\\${char}`)}%`; +} + async function resolveRemoteStatusSearch(env: Env, q: string): Promise { const url = parseSearchUrl(q); if (!url || url.host.toLowerCase() === hostFromBaseUrl(env).toLowerCase()) return null; @@ -2369,7 +2386,8 @@ async function resolveAcct(env: Env, acct: string): Promise<{ acct: string; acto } try { const wf = await fetch(`https://${targetHost}/.well-known/webfinger?resource=acct:${name}@${targetHost}`, { - headers: { accept: "application/jrd+json, application/json" } + headers: { accept: "application/jrd+json, application/json" }, + signal: AbortSignal.timeout(10_000) }); if (!wf.ok) return null; const doc = await wf.json() as { links?: { rel: string; type?: string; href: string }[] }; @@ -2750,8 +2768,9 @@ async function viewerUser(request: Request, env: Env): Promise { const auth = request.headers.get("authorization") ?? ""; const token = auth.match(/^Bearer\s+(.+)$/i)?.[1]; if (!token) return null; - const session = await env.KV.get(`token:${token}`, "json"); + const session = await loadSession(env, token); if (!session) return null; + if (!hasAnyScope(session, ["read"])) return null; return getUserById(env, session.userId); } @@ -2773,15 +2792,71 @@ async function loadPinnedStatusIds(env: Env, userId: string, statusIds: string[] return new Set(rows.results.map((row) => row.status_id)); } -async function requireUser(request: Request, env: Env): Promise { +async function requireUser(request: Request, env: Env, scopes = defaultRequiredScopes(request)): Promise { const auth = request.headers.get("authorization") ?? ""; const token = auth.match(/^Bearer\s+(.+)$/i)?.[1]; if (!token) throw new HttpError(401, "The access token is invalid"); - const session = await env.KV.get(`token:${token}`, "json"); + const session = await loadSession(env, token); if (!session) throw new HttpError(401, "The access token is invalid"); + requireScopes(session, scopes); const user = await getUserById(env, session.userId); if (!user) throw new HttpError(401, "The access token is invalid"); return user; } +async function loadSession(env: Env, token: string): Promise { + const session = await env.KV.get(`token:${token}`, "json"); + if (session) return session; + + const row = await getOAuthToken(env, token); + if (!row) return null; + const createdAt = Date.parse(row.created_at); + if (!Number.isFinite(createdAt) || Date.now() - createdAt > TOKEN_TTL_SECONDS * 1000) { + await deleteOAuthToken(env, token).catch(() => undefined); + return null; + } + const restored = { userId: row.user_id, appId: row.app_id, scopes: row.scopes } satisfies Session; + await Promise.allSettled([ + env.KV.put(`token:${token}`, JSON.stringify(restored), { expirationTtl: TOKEN_TTL_SECONDS }), + touchOAuthToken(env, token) + ]); + return restored; +} + +function defaultRequiredScopes(request: Request): string[] { + return request.method.toUpperCase() === "GET" ? ["read"] : ["write"]; +} + +function requestedScopesWithinApp(requested: string, appScopes: string): string { + const requestedScopes = normalizeScopes(requested || appScopes); + const allowed = normalizeScopes(appScopes); + if (requestedScopes.length === 0) return appScopes; + if (requestedScopes.every((scope) => scopeAllowed(scope, allowed))) return requestedScopes.join(" "); + throw new HttpError(400, "invalid_scope"); +} + +function requireScopes(session: Session, required: string[]): void { + if (!hasAllScopes(session, required)) throw new HttpError(403, "insufficient_scope"); +} + +function hasAllScopes(session: Session, required: string[]): boolean { + return required.every((scope) => hasAnyScope(session, [scope])); +} + +function hasAnyScope(session: Session, required: string[]): boolean { + const granted = normalizeScopes(session.scopes); + return required.some((scope) => scopeAllowed(scope, granted)); +} + +function scopeAllowed(required: string, granted: string[]): boolean { + if (granted.includes(required)) return true; + if (!required.includes(":") && granted.some((scope) => scope.startsWith(`${required}:`))) return true; + const root = required.split(":")[0]; + return granted.includes(root); +} + +function normalizeScopes(scopes: string): string[] { + return [...new Set(scopes.split(/\s+/).map((scope) => scope.trim()).filter(Boolean))]; +} + export { requireUser };