From f48ba8f063dd5c311e5675d271ebd1261d33b87c Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Fri, 10 Apr 2026 15:28:32 +0900 Subject: [PATCH] Add advanced Context helpers guide The manual had no single place where readers could find all the advanced Context methods. parseUri(), routeActivity(), getSignedKey()/getSignedKeyOwner(), getDocumentLoader(), getActorKeyPairs(), lookupObject()/lookupWebFinger()/lookupNodeInfo(), traverseCollection(), and RequestContext.request/url were scattered across the inbox, access-control, webfinger, and nodeinfo pages. This commit adds docs/manual/context-advanced.md, a dedicated guide that gathers every advanced helper in one place with usage examples, a quick-reference table, and links back to the protocol-specific pages for deeper coverage. The existing Context page now carries a pointer to the new guide. Closes https://github.com/fedify-dev/fedify/issues/670 Co-Authored-By: Claude Sonnet 4.6 --- .zed/settings.json | 17 + deno.lock | 426 +++++++++++++++++++++- docs/.vitepress/config.mts | 1 + docs/manual/context-advanced.md | 627 ++++++++++++++++++++++++++++++++ docs/manual/context.md | 4 + 5 files changed, 1065 insertions(+), 10 deletions(-) create mode 100644 docs/manual/context-advanced.md diff --git a/.zed/settings.json b/.zed/settings.json index 651ffb48e..5090f9b40 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -31,6 +31,23 @@ "..." ] }, + "Markdown": { + "formatter": { + "external": { + "command": "hongdown", + "arguments": [ + "-" + ] + } + }, + "language_servers": [ + "!deno", + "!typescript-language-server", + "!vtsls", + "!biome", + "..." + ] + }, "TypeScript": { "code_actions_on_format": { "source.sortImports": true diff --git a/deno.lock b/deno.lock index 85f5d4c9f..cd78d61f8 100644 --- a/deno.lock +++ b/deno.lock @@ -1,13 +1,21 @@ { "version": "5", "specifiers": { + "jsr:@alinea/suite@~0.6.3": "0.6.3", "jsr:@david/console-static-text@0.3": "0.3.0", "jsr:@david/dax@~0.43.2": "0.43.2", "jsr:@david/path@0.2": "0.2.0", "jsr:@david/which@~0.4.1": "0.4.1", + "jsr:@deno/esbuild-plugin@^1.2.0": "1.2.1", + "jsr:@deno/loader@~0.3.10": "0.3.11", + "jsr:@deno/loader@~0.3.2": "0.3.11", "jsr:@fresh/build-id@1": "1.0.1", + "jsr:@fresh/core@2": "2.2.0", "jsr:@fresh/core@^2.1.4": "2.2.0", + "jsr:@fresh/core@^2.2.0": "2.2.0", + "jsr:@fresh/plugin-vite@^1.0.7": "1.0.8", "jsr:@hongminhee/localtunnel@0.3": "0.3.0", + "jsr:@hono/hono@^4.7.1": "4.12.4", "jsr:@hono/hono@^4.8.3": "4.12.4", "jsr:@logtape/file@^2.0.5": "2.0.5", "jsr:@logtape/logtape@^1.0.4": "1.3.7", @@ -15,24 +23,45 @@ "jsr:@optique/config@~0.10.7": "0.10.7", "jsr:@optique/core@~0.10.7": "0.10.7", "jsr:@optique/run@~0.10.7": "0.10.7", + "jsr:@std/assert@0.224": "0.224.0", + "jsr:@std/assert@0.226": "0.226.0", + "jsr:@std/assert@^1.0.13": "1.0.19", + "jsr:@std/async@^1.0.13": "1.2.0", "jsr:@std/bytes@^1.0.6": "1.0.6", + "jsr:@std/dotenv@~0.225.5": "0.225.6", "jsr:@std/encoding@^1.0.10": "1.0.10", + "jsr:@std/fmt@0.224": "0.224.0", "jsr:@std/fmt@1": "1.0.9", + "jsr:@std/fmt@^1.0.7": "1.0.9", "jsr:@std/fmt@^1.0.8": "1.0.9", + "jsr:@std/fs@0.224": "0.224.0", "jsr:@std/fs@1": "1.0.23", "jsr:@std/fs@^1.0.19": "1.0.23", + "jsr:@std/fs@^1.0.3": "1.0.23", "jsr:@std/html@^1.0.5": "1.0.5", "jsr:@std/http@^1.0.21": "1.0.25", + "jsr:@std/internal@0.224": "0.224.0", + "jsr:@std/internal@1": "1.0.12", "jsr:@std/internal@^1.0.12": "1.0.12", "jsr:@std/io@0.225": "0.225.3", + "jsr:@std/json@^1.0.2": "1.0.3", + "jsr:@std/jsonc@^1.0.2": "1.0.2", + "jsr:@std/media-types@^1.1.0": "1.1.0", + "jsr:@std/path@0.224": "0.224.0", "jsr:@std/path@1": "1.1.4", "jsr:@std/path@^1.0.6": "1.1.4", "jsr:@std/path@^1.1.0": "1.1.4", + "jsr:@std/path@^1.1.1": "1.1.4", "jsr:@std/path@^1.1.2": "1.1.4", "jsr:@std/path@^1.1.4": "1.1.4", + "jsr:@std/semver@^1.0.6": "1.0.8", + "jsr:@std/testing@0.224": "0.224.0", + "jsr:@std/uuid@^1.0.9": "1.1.0", "jsr:@valibot/valibot@^1.2.0": "1.2.0", "npm:@alinea/suite@~0.6.3": "0.6.3", "npm:@astrojs/node@^10.0.3": "10.0.4_astro@5.18.1__@types+node@24.12.0__ioredis@5.10.1__tsx@4.21.0__typescript@6.0.2__yaml@2.8.3_@types+node@24.12.0_ioredis@5.10.1_tsx@4.21.0_typescript@6.0.2_yaml@2.8.3", + "npm:@babel/core@^7.28.0": "7.29.0", + "npm:@babel/preset-react@^7.27.1": "7.28.5_@babel+core@7.29.0", "npm:@cfworker/json-schema@^4.1.1": "4.1.1", "npm:@cloudflare/vitest-pool-workers@~0.8.31": "0.8.71_@vitest+runner@3.2.4_@vitest+snapshot@3.2.4_vitest@3.2.4__@types+node@24.12.0__tsx@4.21.0__yaml@2.8.3_@cloudflare+workers-types@4.20260403.1_@types+node@24.12.0_tsx@4.21.0_yaml@2.8.3", "npm:@cloudflare/workers-types@^4.20250529.0": "4.20260403.1", @@ -45,6 +74,7 @@ "npm:@jimp/wasm-webp@^1.6.0": "1.6.0", "npm:@js-temporal/polyfill@~0.5.1": "0.5.1", "npm:@jsr/std__assert@0.226": "0.226.0", + "npm:@mjackson/node-fetch-server@0.7": "0.7.0", "npm:@multiformats/base-x@^4.0.1": "4.0.1", "npm:@nestjs/common@^11.0.1": "11.1.17_reflect-metadata@0.2.2_rxjs@7.8.2", "npm:@opentelemetry/api@^1.9.0": "1.9.1", @@ -55,9 +85,11 @@ "npm:@poppanator/http-constants@^1.1.1": "1.1.1", "npm:@preact/signals@^2.2.1": "2.9.0_preact@10.29.0", "npm:@preact/signals@^2.3.2": "2.9.0_preact@10.29.0", + "npm:@prefresh/vite@^2.4.8": "2.4.12_preact@10.29.0_vite@7.3.1__@types+node@24.12.0__tsx@4.21.0__yaml@2.8.3", "npm:@solidjs/start@^1.3.0": "1.3.2_vinxi@0.5.11__@emnapi+core@1.9.2__@emnapi+runtime@1.9.2__@types+node@24.12.0__ioredis@5.10.1__mysql2@3.20.0___@types+node@24.12.0__rolldown@1.0.0-rc.12___@emnapi+core@1.9.2___@emnapi+runtime@1.9.2__tsx@4.21.0__yaml@2.8.3_@emnapi+core@1.9.2_@emnapi+runtime@1.9.2_@types+node@24.12.0_ioredis@5.10.1_mysql2@3.20.0__@types+node@24.12.0_rolldown@1.0.0-rc.12__@emnapi+core@1.9.2__@emnapi+runtime@1.9.2_solid-js@1.9.12_tsx@4.21.0_vite@7.3.1__@types+node@24.12.0__tsx@4.21.0__yaml@2.8.3_yaml@2.8.3", "npm:@standard-schema/spec@^1.1.0": "1.1.0", "npm:@sveltejs/kit@2": "2.55.0_@opentelemetry+api@1.9.1_@sveltejs+vite-plugin-svelte@7.0.0__svelte@5.55.1__vite@7.3.1___@types+node@24.12.0___tsx@4.21.0___yaml@2.8.3__@types+node@24.12.0__tsx@4.21.0__yaml@2.8.3_svelte@5.55.1_typescript@6.0.2_vite@7.3.1__@types+node@24.12.0__tsx@4.21.0__yaml@2.8.3_@types+node@24.12.0_tsx@4.21.0_yaml@2.8.3", + "npm:@types/amqplib@*": "0.10.8", "npm:@types/amqplib@~0.10.7": "0.10.8", "npm:@types/eslint@9": "9.6.1", "npm:@types/estree@^1.0.8": "1.0.8", @@ -80,6 +112,9 @@ "npm:es-toolkit@^1.39.10": "1.45.1", "npm:es-toolkit@^1.42.0": "1.45.1", "npm:es-toolkit@^1.43.0": "1.45.1", + "npm:esbuild-wasm@~0.25.11": "0.25.12", + "npm:esbuild@0.25.7": "0.25.7", + "npm:esbuild@~0.25.5": "0.25.12", "npm:eslint@9": "9.39.4", "npm:express@4": "4.22.1", "npm:fast-check@^3.22.0": "3.23.2", @@ -106,7 +141,9 @@ "npm:postgres@^3.4.7": "3.4.8", "npm:preact-render-to-string@^6.6.3": "6.6.5_preact@10.29.0", "npm:preact@10.19.6": "10.19.6", + "npm:preact@^10.27.0": "10.29.0", "npm:preact@^10.27.2": "10.29.0", + "npm:rollup@^4.50.0": "4.60.1", "npm:shiki@^1.6.4": "1.29.2", "npm:smol-toml@^1.6.0": "1.6.1", "npm:srvx@~0.8.7": "0.8.16", @@ -117,12 +154,16 @@ "npm:url-template@^3.1.1": "3.1.1", "npm:valibot@^1.2.0": "1.3.1_typescript@6.0.2", "npm:vite@^7.1.3": "7.3.1_@types+node@24.12.0_tsx@4.21.0_yaml@2.8.3", + "npm:vite@^7.1.4": "7.3.1_@types+node@24.12.0_tsx@4.21.0_yaml@2.8.3", "npm:vitest@3.2": "3.2.4_@types+node@24.12.0_tsx@4.21.0_yaml@2.8.3", "npm:wrangler@^4.17.0": "4.35.0_@cloudflare+workers-types@4.20260403.1", "npm:wrangler@^4.21.1": "4.35.0_@cloudflare+workers-types@4.20260403.1", "npm:yaml@^2.8.1": "2.8.3" }, "jsr": { + "@alinea/suite@0.6.3": { + "integrity": "7d24a38729663b84d8a263d64ff7e3f8c72ac7cbb1db8ec5f414d0416b6b72e2" + }, "@david/console-static-text@0.3.0": { "integrity": "2dfb46ecee525755f7989f94ece30bba85bd8ffe3e8666abc1bf926e1ee0698d" }, @@ -148,6 +189,17 @@ "@david/which@0.4.1": { "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e" }, + "@deno/esbuild-plugin@1.2.1": { + "integrity": "df629467913adc1f960149fdfa3a3430ba8c20381c310fba096db244e6c3c9f6", + "dependencies": [ + "jsr:@deno/loader@~0.3.10", + "jsr:@std/path@^1.1.1", + "npm:esbuild@~0.25.5" + ] + }, + "@deno/loader@0.3.11": { + "integrity": "7c62f4f09cdfc34e66ba25b5a775a1830cbb5266b3e39f67b0f620c75484df8d" + }, "@fresh/build-id@1.0.1": { "integrity": "12a2ec25fd52ae9ec68c26848a5696cd1c9b537f7c983c7e56e4fb1e7e816c20", "dependencies": [ @@ -157,18 +209,44 @@ "@fresh/core@2.2.0": { "integrity": "b3c00f82288a2c4c8ec85e4abb67b080b366ec5971860f2f2898eb281ea1a80f", "dependencies": [ + "jsr:@deno/esbuild-plugin", "jsr:@fresh/build-id", + "jsr:@std/encoding", "jsr:@std/fmt@^1.0.8", "jsr:@std/fs@^1.0.19", "jsr:@std/html", "jsr:@std/http", + "jsr:@std/jsonc", + "jsr:@std/media-types", "jsr:@std/path@^1.1.2", + "jsr:@std/semver", + "jsr:@std/uuid", "npm:@opentelemetry/api", "npm:@preact/signals@^2.2.1", + "npm:esbuild-wasm", + "npm:esbuild@0.25.7", "npm:preact-render-to-string", + "npm:preact@^10.27.0", "npm:preact@^10.27.2" ] }, + "@fresh/plugin-vite@1.0.8": { + "integrity": "5780d842ed82e4cbccd93dd8ba2d54bf59dff5aee65921134aab15a4cd457c56", + "dependencies": [ + "jsr:@deno/loader@~0.3.2", + "jsr:@fresh/core@2", + "jsr:@fresh/core@^2.2.0", + "jsr:@std/dotenv", + "jsr:@std/fmt@^1.0.7", + "jsr:@std/path@1", + "npm:@babel/core", + "npm:@babel/preset-react", + "npm:@mjackson/node-fetch-server", + "npm:@prefresh/vite", + "npm:rollup", + "npm:vite@^7.1.4" + ] + }, "@hongminhee/localtunnel@0.3.0": { "integrity": "4ad858acd963b5fad45b188d54cf751ac8fbe0aae495e1d3ce607e3730270ff4", "dependencies": [ @@ -207,19 +285,53 @@ "jsr:@optique/core" ] }, + "@std/assert@0.224.0": { + "integrity": "8643233ec7aec38a940a8264a6e3eed9bfa44e7a71cc6b3c8874213ff401967f", + "dependencies": [ + "jsr:@std/fmt@0.224", + "jsr:@std/internal@0.224" + ] + }, + "@std/assert@0.226.0": { + "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3", + "dependencies": [ + "jsr:@std/internal@1" + ] + }, + "@std/assert@1.0.19": { + "integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", + "dependencies": [ + "jsr:@std/internal@^1.0.12" + ] + }, + "@std/async@1.2.0": { + "integrity": "c059c6f6d95ca7cc012ae8e8d7164d1697113d54b0b679e4372b354b11c2dee5" + }, "@std/bytes@1.0.6": { "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" }, + "@std/dotenv@0.225.6": { + "integrity": "1d6f9db72f565bd26790fa034c26e45ecb260b5245417be76c2279e5734c421b" + }, "@std/encoding@1.0.10": { "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" }, + "@std/fmt@0.224.0": { + "integrity": "e20e9a2312a8b5393272c26191c0a68eda8d2c4b08b046bad1673148f1d69851" + }, "@std/fmt@1.0.9": { "integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" }, + "@std/fs@0.224.0": { + "integrity": "52a5ec89731ac0ca8f971079339286f88c571a4d61686acf75833f03a89d8e69", + "dependencies": [ + "jsr:@std/path@0.224" + ] + }, "@std/fs@1.0.23": { "integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37", "dependencies": [ - "jsr:@std/internal", + "jsr:@std/internal@^1.0.12", "jsr:@std/path@^1.1.4" ] }, @@ -227,7 +339,16 @@ "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" }, "@std/http@1.0.25": { - "integrity": "577b4252290af1097132812b339fffdd55fb0f4aeb98ff11bdbf67998aa17193" + "integrity": "577b4252290af1097132812b339fffdd55fb0f4aeb98ff11bdbf67998aa17193", + "dependencies": [ + "jsr:@std/encoding" + ] + }, + "@std/internal@0.224.0": { + "integrity": "afc50644f9cdf4495eeb80523a8f6d27226b4b36c45c7c195dfccad4b8509291", + "dependencies": [ + "jsr:@std/fmt@0.224" + ] }, "@std/internal@1.0.12": { "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" @@ -238,10 +359,43 @@ "jsr:@std/bytes" ] }, + "@std/json@1.0.3": { + "integrity": "97d5710996293a027b7aa5f0d1f4fa29f246f269e6b5597e08807613f37d426c" + }, + "@std/jsonc@1.0.2": { + "integrity": "909605dae3af22bd75b1cbda8d64a32cf1fd2cf6efa3f9e224aba6d22c0f44c7", + "dependencies": [ + "jsr:@std/json" + ] + }, + "@std/media-types@1.1.0": { + "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" + }, + "@std/path@0.224.0": { + "integrity": "55bca6361e5a6d158b9380e82d4981d82d338ec587de02951e2b7c3a24910ee6" + }, "@std/path@1.1.4": { "integrity": "1d2d43f39efb1b42f0b1882a25486647cb851481862dc7313390b2bb044314b5", "dependencies": [ - "jsr:@std/internal" + "jsr:@std/internal@^1.0.12" + ] + }, + "@std/semver@1.0.8": { + "integrity": "dc830e8b8b6a380c895d53fbfd1258dc253704ca57bbe1629ac65fd7830179b7" + }, + "@std/testing@0.224.0": { + "integrity": "371b8a929aa7132240d5dd766a439be8f780ef5c176ab194e0bcab72370c761e", + "dependencies": [ + "jsr:@std/assert@0.224", + "jsr:@std/fmt@0.224", + "jsr:@std/fs@0.224", + "jsr:@std/path@0.224" + ] + }, + "@std/uuid@1.1.0": { + "integrity": "6268db2ccf172849c9be80763354ca305d49ef4af41fe995623d44fcc3f7457c", + "dependencies": [ + "jsr:@std/bytes" ] }, "@valibot/valibot@1.2.0": { @@ -377,6 +531,12 @@ "jsesc" ] }, + "@babel/helper-annotate-as-pure@7.27.3": { + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dependencies": [ + "@babel/types@7.29.0" + ] + }, "@babel/helper-compilation-targets@7.28.6": { "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dependencies": [ @@ -465,6 +625,51 @@ "@babel/helper-plugin-utils" ] }, + "@babel/plugin-transform-react-display-name@7.28.0_@babel+core@7.29.0": { + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils" + ] + }, + "@babel/plugin-transform-react-jsx-development@7.27.1_@babel+core@7.29.0": { + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dependencies": [ + "@babel/core", + "@babel/plugin-transform-react-jsx" + ] + }, + "@babel/plugin-transform-react-jsx@7.28.6_@babel+core@7.29.0": { + "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", + "dependencies": [ + "@babel/core", + "@babel/helper-annotate-as-pure", + "@babel/helper-module-imports@7.28.6", + "@babel/helper-plugin-utils", + "@babel/plugin-syntax-jsx", + "@babel/types@7.29.0" + ] + }, + "@babel/plugin-transform-react-pure-annotations@7.27.1_@babel+core@7.29.0": { + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "dependencies": [ + "@babel/core", + "@babel/helper-annotate-as-pure", + "@babel/helper-plugin-utils" + ] + }, + "@babel/preset-react@7.28.5_@babel+core@7.29.0": { + "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", + "dependencies": [ + "@babel/core", + "@babel/helper-plugin-utils", + "@babel/helper-validator-option", + "@babel/plugin-transform-react-display-name", + "@babel/plugin-transform-react-jsx", + "@babel/plugin-transform-react-jsx-development", + "@babel/plugin-transform-react-pure-annotations" + ] + }, "@babel/template@7.28.6": { "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dependencies": [ @@ -641,6 +846,11 @@ "os": ["aix"], "cpu": ["ppc64"] }, + "@esbuild/aix-ppc64@0.25.7": { + "integrity": "sha512-uD0kKFHh6ETr8TqEtaAcV+dn/2qnYbH/+8wGEdY70Qf7l1l/jmBUbrmQqwiPKAQE6cOQ7dTj6Xr0HzQDGHyceQ==", + "os": ["aix"], + "cpu": ["ppc64"] + }, "@esbuild/aix-ppc64@0.27.7": { "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "os": ["aix"], @@ -656,6 +866,11 @@ "os": ["android"], "cpu": ["arm64"] }, + "@esbuild/android-arm64@0.25.7": { + "integrity": "sha512-p0ohDnwyIbAtztHTNUTzN5EGD/HJLs1bwysrOPgSdlIA6NDnReoVfoCyxG6W1d85jr2X80Uq5KHftyYgaK9LPQ==", + "os": ["android"], + "cpu": ["arm64"] + }, "@esbuild/android-arm64@0.27.7": { "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "os": ["android"], @@ -671,6 +886,11 @@ "os": ["android"], "cpu": ["arm"] }, + "@esbuild/android-arm@0.25.7": { + "integrity": "sha512-Jhuet0g1k9rAJHrXGIh7sFknFuT4sfytYZpZpuZl7YKDhnPByVAm5oy2LEBmMbuYf3ejWVYCc2seX81Mk+madA==", + "os": ["android"], + "cpu": ["arm"] + }, "@esbuild/android-arm@0.27.7": { "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "os": ["android"], @@ -686,6 +906,11 @@ "os": ["android"], "cpu": ["x64"] }, + "@esbuild/android-x64@0.25.7": { + "integrity": "sha512-mMxIJFlSgVK23HSsII3ZX9T2xKrBCDGyk0qiZnIW10LLFFtZLkFD6imZHu7gUo2wkNZwS9Yj3mOtZD3ZPcjCcw==", + "os": ["android"], + "cpu": ["x64"] + }, "@esbuild/android-x64@0.27.7": { "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "os": ["android"], @@ -701,6 +926,11 @@ "os": ["darwin"], "cpu": ["arm64"] }, + "@esbuild/darwin-arm64@0.25.7": { + "integrity": "sha512-jyOFLGP2WwRwxM8F1VpP6gcdIJc8jq2CUrURbbTouJoRO7XCkU8GdnTDFIHdcifVBT45cJlOYsZ1kSlfbKjYUQ==", + "os": ["darwin"], + "cpu": ["arm64"] + }, "@esbuild/darwin-arm64@0.27.7": { "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "os": ["darwin"], @@ -716,6 +946,11 @@ "os": ["darwin"], "cpu": ["x64"] }, + "@esbuild/darwin-x64@0.25.7": { + "integrity": "sha512-m9bVWqZCwQ1BthruifvG64hG03zzz9gE2r/vYAhztBna1/+qXiHyP9WgnyZqHgGeXoimJPhAmxfbeU+nMng6ZA==", + "os": ["darwin"], + "cpu": ["x64"] + }, "@esbuild/darwin-x64@0.27.7": { "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "os": ["darwin"], @@ -731,6 +966,11 @@ "os": ["freebsd"], "cpu": ["arm64"] }, + "@esbuild/freebsd-arm64@0.25.7": { + "integrity": "sha512-Bss7P4r6uhr3kDzRjPNEnTm/oIBdTPRNQuwaEFWT/uvt6A1YzK/yn5kcx5ZxZ9swOga7LqeYlu7bDIpDoS01bA==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, "@esbuild/freebsd-arm64@0.27.7": { "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "os": ["freebsd"], @@ -746,6 +986,11 @@ "os": ["freebsd"], "cpu": ["x64"] }, + "@esbuild/freebsd-x64@0.25.7": { + "integrity": "sha512-S3BFyjW81LXG7Vqmr37ddbThrm3A84yE7ey/ERBlK9dIiaWgrjRlre3pbG7txh1Uaxz8N7wGGQXmC9zV+LIpBQ==", + "os": ["freebsd"], + "cpu": ["x64"] + }, "@esbuild/freebsd-x64@0.27.7": { "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "os": ["freebsd"], @@ -761,6 +1006,11 @@ "os": ["linux"], "cpu": ["arm64"] }, + "@esbuild/linux-arm64@0.25.7": { + "integrity": "sha512-HfQZQqrNOfS1Okn7PcsGUqHymL1cWGBslf78dGvtrj8q7cN3FkapFgNA4l/a5lXDwr7BqP2BSO6mz9UremNPbg==", + "os": ["linux"], + "cpu": ["arm64"] + }, "@esbuild/linux-arm64@0.27.7": { "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "os": ["linux"], @@ -776,6 +1026,11 @@ "os": ["linux"], "cpu": ["arm"] }, + "@esbuild/linux-arm@0.25.7": { + "integrity": "sha512-JZMIci/1m5vfQuhKoFXogCKVYVfYQmoZJg8vSIMR4TUXbF+0aNlfXH3DGFEFMElT8hOTUF5hisdZhnrZO/bkDw==", + "os": ["linux"], + "cpu": ["arm"] + }, "@esbuild/linux-arm@0.27.7": { "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "os": ["linux"], @@ -791,6 +1046,11 @@ "os": ["linux"], "cpu": ["ia32"] }, + "@esbuild/linux-ia32@0.25.7": { + "integrity": "sha512-9Jex4uVpdeofiDxnwHRgen+j6398JlX4/6SCbbEFEXN7oMO2p0ueLN+e+9DdsdPLUdqns607HmzEFnxwr7+5wQ==", + "os": ["linux"], + "cpu": ["ia32"] + }, "@esbuild/linux-ia32@0.27.7": { "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "os": ["linux"], @@ -806,6 +1066,11 @@ "os": ["linux"], "cpu": ["loong64"] }, + "@esbuild/linux-loong64@0.25.7": { + "integrity": "sha512-TG1KJqjBlN9IHQjKVUYDB0/mUGgokfhhatlay8aZ/MSORMubEvj/J1CL8YGY4EBcln4z7rKFbsH+HeAv0d471w==", + "os": ["linux"], + "cpu": ["loong64"] + }, "@esbuild/linux-loong64@0.27.7": { "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "os": ["linux"], @@ -821,6 +1086,11 @@ "os": ["linux"], "cpu": ["mips64el"] }, + "@esbuild/linux-mips64el@0.25.7": { + "integrity": "sha512-Ty9Hj/lx7ikTnhOfaP7ipEm/ICcBv94i/6/WDg0OZ3BPBHhChsUbQancoWYSO0WNkEiSW5Do4febTTy4x1qYQQ==", + "os": ["linux"], + "cpu": ["mips64el"] + }, "@esbuild/linux-mips64el@0.27.7": { "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "os": ["linux"], @@ -836,6 +1106,11 @@ "os": ["linux"], "cpu": ["ppc64"] }, + "@esbuild/linux-ppc64@0.25.7": { + "integrity": "sha512-MrOjirGQWGReJl3BNQ58BLhUBPpWABnKrnq8Q/vZWWwAB1wuLXOIxS2JQ1LT3+5T+3jfPh0tyf5CpbyQHqnWIQ==", + "os": ["linux"], + "cpu": ["ppc64"] + }, "@esbuild/linux-ppc64@0.27.7": { "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "os": ["linux"], @@ -851,6 +1126,11 @@ "os": ["linux"], "cpu": ["riscv64"] }, + "@esbuild/linux-riscv64@0.25.7": { + "integrity": "sha512-9pr23/pqzyqIZEZmQXnFyqp3vpa+KBk5TotfkzGMqpw089PGm0AIowkUppHB9derQzqniGn3wVXgck19+oqiOw==", + "os": ["linux"], + "cpu": ["riscv64"] + }, "@esbuild/linux-riscv64@0.27.7": { "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "os": ["linux"], @@ -866,6 +1146,11 @@ "os": ["linux"], "cpu": ["s390x"] }, + "@esbuild/linux-s390x@0.25.7": { + "integrity": "sha512-4dP11UVGh9O6Y47m8YvW8eoA3r8qL2toVZUbBKyGta8j6zdw1cn9F/Rt59/Mhv0OgY68pHIMjGXWOUaykCnx+w==", + "os": ["linux"], + "cpu": ["s390x"] + }, "@esbuild/linux-s390x@0.27.7": { "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "os": ["linux"], @@ -881,6 +1166,11 @@ "os": ["linux"], "cpu": ["x64"] }, + "@esbuild/linux-x64@0.25.7": { + "integrity": "sha512-ghJMAJTdw/0uhz7e7YnpdX1xVn7VqA0GrWrAO2qKMuqbvgHT2VZiBv1BQ//VcHsPir4wsL3P2oPggfKPzTKoCA==", + "os": ["linux"], + "cpu": ["x64"] + }, "@esbuild/linux-x64@0.27.7": { "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "os": ["linux"], @@ -896,6 +1186,11 @@ "os": ["netbsd"], "cpu": ["arm64"] }, + "@esbuild/netbsd-arm64@0.25.7": { + "integrity": "sha512-bwXGEU4ua45+u5Ci/a55B85KWaDSRS8NPOHtxy2e3etDjbz23wlry37Ffzapz69JAGGc4089TBo+dGzydQmydg==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, "@esbuild/netbsd-arm64@0.27.7": { "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "os": ["netbsd"], @@ -911,6 +1206,11 @@ "os": ["netbsd"], "cpu": ["x64"] }, + "@esbuild/netbsd-x64@0.25.7": { + "integrity": "sha512-tUZRvLtgLE5OyN46sPSYlgmHoBS5bx2URSrgZdW1L1teWPYVmXh+QN/sKDqkzBo/IHGcKcHLKDhBeVVkO7teEA==", + "os": ["netbsd"], + "cpu": ["x64"] + }, "@esbuild/netbsd-x64@0.27.7": { "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "os": ["netbsd"], @@ -926,6 +1226,11 @@ "os": ["openbsd"], "cpu": ["arm64"] }, + "@esbuild/openbsd-arm64@0.25.7": { + "integrity": "sha512-bTJ50aoC+WDlDGBReWYiObpYvQfMjBNlKztqoNUL0iUkYtwLkBQQeEsTq/I1KyjsKA5tyov6VZaPb8UdD6ci6Q==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, "@esbuild/openbsd-arm64@0.27.7": { "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "os": ["openbsd"], @@ -941,6 +1246,11 @@ "os": ["openbsd"], "cpu": ["x64"] }, + "@esbuild/openbsd-x64@0.25.7": { + "integrity": "sha512-TA9XfJrgzAipFUU895jd9j2SyDh9bbNkK2I0gHcvqb/o84UeQkBpi/XmYX3cO1q/9hZokdcDqQxIi6uLVrikxg==", + "os": ["openbsd"], + "cpu": ["x64"] + }, "@esbuild/openbsd-x64@0.27.7": { "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "os": ["openbsd"], @@ -951,6 +1261,11 @@ "os": ["openharmony"], "cpu": ["arm64"] }, + "@esbuild/openharmony-arm64@0.25.7": { + "integrity": "sha512-5VTtExUrWwHHEUZ/N+rPlHDwVFQ5aME7vRJES8+iQ0xC/bMYckfJ0l2n3yGIfRoXcK/wq4oXSItZAz5wslTKGw==", + "os": ["openharmony"], + "cpu": ["arm64"] + }, "@esbuild/openharmony-arm64@0.27.7": { "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "os": ["openharmony"], @@ -966,6 +1281,11 @@ "os": ["sunos"], "cpu": ["x64"] }, + "@esbuild/sunos-x64@0.25.7": { + "integrity": "sha512-umkbn7KTxsexhv2vuuJmj9kggd4AEtL32KodkJgfhNOHMPtQ55RexsaSrMb+0+jp9XL4I4o2y91PZauVN4cH3A==", + "os": ["sunos"], + "cpu": ["x64"] + }, "@esbuild/sunos-x64@0.27.7": { "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "os": ["sunos"], @@ -981,6 +1301,11 @@ "os": ["win32"], "cpu": ["arm64"] }, + "@esbuild/win32-arm64@0.25.7": { + "integrity": "sha512-j20JQGP/gz8QDgzl5No5Gr4F6hurAZvtkFxAKhiv2X49yi/ih8ECK4Y35YnjlMogSKJk931iNMcd35BtZ4ghfw==", + "os": ["win32"], + "cpu": ["arm64"] + }, "@esbuild/win32-arm64@0.27.7": { "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "os": ["win32"], @@ -996,6 +1321,11 @@ "os": ["win32"], "cpu": ["ia32"] }, + "@esbuild/win32-ia32@0.25.7": { + "integrity": "sha512-4qZ6NUfoiiKZfLAXRsvFkA0hoWVM+1y2bSHXHkpdLAs/+r0LgwqYohmfZCi985c6JWHhiXP30mgZawn/XrqAkQ==", + "os": ["win32"], + "cpu": ["ia32"] + }, "@esbuild/win32-ia32@0.27.7": { "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "os": ["win32"], @@ -1011,6 +1341,11 @@ "os": ["win32"], "cpu": ["x64"] }, + "@esbuild/win32-x64@0.25.7": { + "integrity": "sha512-FaPsAHTwm+1Gfvn37Eg3E5HIpfR3i6x1AIcla/MkqAIupD4BW3MrSeUqfoTzwwJhk3WE2/KqUn4/eenEJC76VA==", + "os": ["win32"], + "cpu": ["x64"] + }, "@esbuild/win32-x64@0.27.7": { "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "os": ["win32"], @@ -2005,6 +2340,9 @@ ], "bin": true }, + "@mjackson/node-fetch-server@0.7.0": { + "integrity": "sha512-un8diyEBKU3BTVj3GzlTPA1kIjCkGdD+AMYQy31Gf9JCkfoZzwgJ79GUtHrF2BN3XPNMLpubbzPcxys+a3uZEw==" + }, "@multiformats/base-x@4.0.1": { "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==" }, @@ -2241,6 +2579,30 @@ "preact@10.29.0" ] }, + "@prefresh/babel-plugin@0.5.3": { + "integrity": "sha512-57LX2SHs4BX2s1IwCjNzTE2OJeEepRCNf1VTEpbNcUyHfMO68eeOWGDIt4ob9aYlW6PEWZ1SuwNikuoIXANDtQ==" + }, + "@prefresh/core@1.5.9_preact@10.29.0": { + "integrity": "sha512-IKBKCPaz34OFVC+adiQ2qaTF5qdztO2/4ZPf4KsRTgjKosWqxVXmEbxCiUydYZRY8GVie+DQlKzQr9gt6HQ+EQ==", + "dependencies": [ + "preact@10.29.0" + ] + }, + "@prefresh/utils@1.2.1": { + "integrity": "sha512-vq/sIuN5nYfYzvyayXI4C2QkprfNaHUQ9ZX+3xLD8nL3rWyzpxOm1+K7RtMbhd+66QcaISViK7amjnheQ/4WZw==" + }, + "@prefresh/vite@2.4.12_preact@10.29.0_vite@7.3.1__@types+node@24.12.0__tsx@4.21.0__yaml@2.8.3": { + "integrity": "sha512-FY1fzXpUjiuosznMV0YM7XAOPZjB5FIdWS0W24+XnlxYkt9hNAwwsiKYn+cuTEoMtD/ZVazS5QVssBr9YhpCQA==", + "dependencies": [ + "@babel/core", + "@prefresh/babel-plugin", + "@prefresh/core", + "@prefresh/utils", + "@rollup/pluginutils@4.2.1", + "preact@10.29.0", + "vite@7.3.1_@types+node@24.12.0_tsx@4.21.0_yaml@2.8.3" + ] + }, "@quansync/fs@1.0.0": { "integrity": "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==", "dependencies": [ @@ -2339,7 +2701,7 @@ "@rollup/plugin-commonjs@29.0.2_rollup@4.60.1": { "integrity": "sha512-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg==", "dependencies": [ - "@rollup/pluginutils", + "@rollup/pluginutils@5.3.0_rollup@4.60.1", "commondir", "estree-walker@2.0.2", "fdir", @@ -2355,7 +2717,7 @@ "@rollup/plugin-inject@5.0.5_rollup@4.60.1": { "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", "dependencies": [ - "@rollup/pluginutils", + "@rollup/pluginutils@5.3.0_rollup@4.60.1", "estree-walker@2.0.2", "magic-string", "rollup" @@ -2367,7 +2729,7 @@ "@rollup/plugin-json@6.1.0_rollup@4.60.1": { "integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==", "dependencies": [ - "@rollup/pluginutils", + "@rollup/pluginutils@5.3.0_rollup@4.60.1", "rollup" ], "optionalPeers": [ @@ -2377,7 +2739,7 @@ "@rollup/plugin-node-resolve@16.0.3_rollup@4.60.1": { "integrity": "sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==", "dependencies": [ - "@rollup/pluginutils", + "@rollup/pluginutils@5.3.0_rollup@4.60.1", "@types/resolve", "deepmerge", "is-module", @@ -2391,7 +2753,7 @@ "@rollup/plugin-replace@6.0.3_rollup@4.60.1": { "integrity": "sha512-J4RZarRvQAm5IF0/LwUUg+obsm+xZhYnbMXmXROyoSE1ATJe3oXSb9L5MMppdxP2ylNSjv6zFBwKYjcKMucVfA==", "dependencies": [ - "@rollup/pluginutils", + "@rollup/pluginutils@5.3.0_rollup@4.60.1", "magic-string", "rollup" ], @@ -2411,6 +2773,13 @@ "rollup" ] }, + "@rollup/pluginutils@4.2.1": { + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dependencies": [ + "estree-walker@2.0.2", + "picomatch@2.3.2" + ] + }, "@rollup/pluginutils@5.3.0_rollup@4.60.1": { "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", "dependencies": [ @@ -2996,7 +3365,7 @@ "integrity": "sha512-IWTDeIoWhQ7ZtRO/JRKH+jhmeQvZYhtGPmzw/QGDY+wDCQqfm25P9yIdoAFagu4fWsK4IwZXDFIjrmp5rRm/sA==", "dependencies": [ "@mapbox/node-pre-gyp", - "@rollup/pluginutils", + "@rollup/pluginutils@5.3.0_rollup@4.60.1", "acorn@8.16.0", "acorn-import-attributes", "async-sema", @@ -3333,7 +3702,7 @@ "@astrojs/telemetry", "@capsizecss/unpack", "@oslojs/encoding", - "@rollup/pluginutils", + "@rollup/pluginutils@5.3.0_rollup@4.60.1", "acorn@8.16.0", "aria-query@5.3.2", "axobject-query", @@ -4264,6 +4633,10 @@ "es-toolkit@1.45.1": { "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==" }, + "esbuild-wasm@0.25.12": { + "integrity": "sha512-rZqkjL3Y6FwLpSHzLnaEy8Ps6veCNo1kZa9EOfJvmWtBq5dJH4iVjfmOO6Mlkv9B0tt9WFPFmb/VxlgJOnueNg==", + "bin": true + }, "esbuild@0.25.12": { "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "optionalDependencies": [ @@ -4329,6 +4702,39 @@ "scripts": true, "bin": true }, + "esbuild@0.25.7": { + "integrity": "sha512-daJB0q2dmTzo90L9NjRaohhRWrCzYxWNFTjEi72/h+p5DcY3yn4MacWfDakHmaBaDzDiuLJsCh0+6LK/iX+c+Q==", + "optionalDependencies": [ + "@esbuild/aix-ppc64@0.25.7", + "@esbuild/android-arm@0.25.7", + "@esbuild/android-arm64@0.25.7", + "@esbuild/android-x64@0.25.7", + "@esbuild/darwin-arm64@0.25.7", + "@esbuild/darwin-x64@0.25.7", + "@esbuild/freebsd-arm64@0.25.7", + "@esbuild/freebsd-x64@0.25.7", + "@esbuild/linux-arm@0.25.7", + "@esbuild/linux-arm64@0.25.7", + "@esbuild/linux-ia32@0.25.7", + "@esbuild/linux-loong64@0.25.7", + "@esbuild/linux-mips64el@0.25.7", + "@esbuild/linux-ppc64@0.25.7", + "@esbuild/linux-riscv64@0.25.7", + "@esbuild/linux-s390x@0.25.7", + "@esbuild/linux-x64@0.25.7", + "@esbuild/netbsd-arm64@0.25.7", + "@esbuild/netbsd-x64@0.25.7", + "@esbuild/openbsd-arm64@0.25.7", + "@esbuild/openbsd-x64@0.25.7", + "@esbuild/openharmony-arm64@0.25.7", + "@esbuild/sunos-x64@0.25.7", + "@esbuild/win32-arm64@0.25.7", + "@esbuild/win32-ia32@0.25.7", + "@esbuild/win32-x64@0.25.7" + ], + "scripts": true, + "bin": true + }, "esbuild@0.27.7": { "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "optionalDependencies": [ diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index be227c9d7..ab4c3410f 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -104,6 +104,7 @@ const MANUAL = { items: [ { text: "Federation", link: "/manual/federation.md" }, { text: "Context", link: "/manual/context.md" }, + { text: "Advanced context helpers", link: "/manual/context-advanced.md" }, { text: "Vocabulary", link: "/manual/vocab.md" }, { text: "Actor dispatcher", link: "/manual/actor.md" }, { text: "Inbox listeners", link: "/manual/inbox.md" }, diff --git a/docs/manual/context-advanced.md b/docs/manual/context-advanced.md new file mode 100644 index 000000000..c8dd0c992 --- /dev/null +++ b/docs/manual/context-advanced.md @@ -0,0 +1,627 @@ +--- +description: >- + The Context object exposes a rich set of methods beyond URI building. This + guide gathers the advanced Context helpers—URI parsing, manual activity + routing, signature introspection, authenticated fetching, and remote + lookups—in one place so you can discover them without hunting across multiple + pages. +--- + +Advanced context helpers +======================== + +The [`Context`] (and its subtype [`RequestContext`]) object is passed to every +callback you register on a [`Federation`] instance. The [_Context_ +guide](./context.md) explains the basics: where to get a `Context`, how to +build URIs, and how to enqueue outgoing activities. This page covers +the advanced helpers that let you _parse_ URIs, _introspect_ incoming +signatures, _load_ remote documents with authentication, and _look up_ remote +fediverse resources. + +Quick reference: + +| Method / property | Available on | Since | +| -------------------------------------------------------- | ---------------- | ------ | +| [`parseUri()`](#parsing-uris) | `Context` | 0.9.0 | +| [`routeActivity()`](#routing-activities-manually) | `Context` | 1.3.0 | +| [`getSignedKey()`](#signed-key-and-its-owner) | `RequestContext` | 0.7.0 | +| [`getSignedKeyOwner()`](#signed-key-and-its-owner) | `RequestContext` | 0.7.0 | +| [`getDocumentLoader()`](#authenticated-document-loaders) | `Context` | 0.4.0 | +| [`getActorKeyPairs()`](#actor-key-pairs) | `Context` | 0.10.0 | +| [`lookupObject()`](#looking-up-remote-objects) | `Context` | 0.15.0 | +| [`lookupWebFinger()`](#webfinger-lookups) | `Context` | 1.6.0 | +| [`lookupNodeInfo()`](#nodeinfo-lookups) | `Context` | 1.4.0 | +| [`traverseCollection()`](#traversing-collections) | `Context` | 1.1.0 | +| [`request`](#request-and-url) | `RequestContext` | 0.1.0 | +| [`url`](#request-and-url) | `RequestContext` | 0.1.0 | + +[`Context`]: https://jsr.io/@fedify/fedify/doc/federation/~/Context +[`RequestContext`]: https://jsr.io/@fedify/fedify/doc/federation/~/RequestContext +[`Federation`]: https://jsr.io/@fedify/fedify/doc/federation/~/Federation + + +Parsing URIs +------------ + +_This API is available since Fedify 0.9.0._ + +`Context` provides methods to build the canonical URIs for your actors and +objects (e.g., `~Context.getActorUri()`, `~Context.getObjectUri()`). +The inverse operation—determining _what_ a URI refers to—is handled by +[`Context.parseUri()`]: + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +const ctx = null as unknown as Context; +const someUri = new URL("https://example.com/users/alice"); +// ---cut-before--- +const result = ctx.parseUri(someUri); +if (result?.type === "actor") { + console.log(result.identifier); // e.g. "alice" +} +~~~~ + +`parseUri()` returns `null` when the argument is `null` or when the URI does +not match any route registered on the `Federation`. Otherwise it returns a +discriminated union keyed on `type`: + +| `type` | Extra fields | +| --------------------- | ------------------------------------------- | +| `"actor"` | `identifier` | +| `"object"` | `class`, `typeId`, `values` | +| `"inbox"` | `identifier` (`undefined` for shared inbox) | +| `"outbox"` | `identifier` | +| `"following"` | `identifier` | +| `"followers"` | `identifier` | +| `"liked"` | `identifier` | +| `"featured"` | `identifier` | +| `"featuredTags"` | `identifier` | +| `"collection"` | `name`, `class`, `typeId`, `values` | +| `"orderedCollection"` | `name`, `class`, `typeId`, `values` | + +A common pattern is to extract the sender identifier from an incoming activity +so you can pass it to `~Context.sendActivity()`: + +~~~~ typescript twoslash +import { type Federation } from "@fedify/fedify"; +const federation = null as unknown as Federation; +// ---cut-before--- +import { Accept, Follow } from "@fedify/vocab"; + +federation + .setInboxListeners("/users/{identifier}/inbox", "/inbox") + .on(Follow, async (ctx, follow) => { + if (follow.objectId == null) return; + const parsed = ctx.parseUri(follow.objectId); // [!code highlight] + if (parsed?.type !== "actor") return; // [!code highlight] + const recipient = await follow.getActor(ctx); + if (recipient == null) return; + await ctx.sendActivity( + { identifier: parsed.identifier }, + recipient, + new Accept({ actor: follow.objectId, object: follow }), + ); + }); +~~~~ + +[`Context.parseUri()`]: https://jsr.io/@fedify/fedify/doc/federation/~/Context#method_parseUri_0 + + +Routing activities manually +--------------------------- + +_This API is available since Fedify 1.3.0._ + +Inbox listeners normally receive activities that arrive over HTTP. Sometimes, +however, you want to dispatch an activity through the same listener logic +_without_ an actual network request—for example, when an `Announce` wraps +another `Activity`, or when you replay a remote actor's outbox locally. +[`Context.routeActivity()`] does exactly that. + +The first argument is the recipient identifier (or `null` for the shared +inbox). The second is the `Activity` to route: + +~~~~ typescript twoslash +import { type Federation } from "@fedify/fedify"; +import { Activity, Announce } from "@fedify/vocab"; +const federation = null as unknown as Federation; +// ---cut-before--- +federation + .setInboxListeners("/users/{identifier}/inbox", "/inbox") + .on(Announce, async (ctx, announce) => { + const object = await announce.getObject(); + if (object instanceof Activity) { + // Route the enclosed activity to the matching inbox listener: + await ctx.routeActivity(ctx.recipient, object); // [!code highlight] + } + }); +~~~~ + +As another example, you can replay a remote actor's outbox into your local +inbox listeners: + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +import { Activity, isActor } from "@fedify/vocab"; +async function main(context: Context) { + // ---cut-before--- + const actor = await context.lookupObject("@hongminhee@fosstodon.org"); + if (!isActor(actor)) return; + const outbox = await actor.getOutbox(); + if (outbox == null) return; + for await (const item of context.traverseCollection(outbox)) { + if (item instanceof Activity) { + await context.routeActivity(null, item); // [!code highlight] + } + } + // ---cut-after--- +} +~~~~ + +> [!CAUTION] +> `routeActivity()` verifies the activity before dispatching it. An activity +> is accepted only when _at least one_ of these conditions is met: +> +> - The activity carries valid Object Integrity Proofs signed by its actor. +> - The activity has a dereferenceable `id` whose fetched document contains +> at least one actor sharing the same origin as the `id`. +> +> If neither condition is satisfied, the activity is silently discarded and +> `routeActivity()` returns `false`. Never pass arbitrary untrusted +> `Activity` objects with the expectation that they will be accepted. + +By default, `routeActivity()` enqueues the activity for background processing, +just like activities received over HTTP. Pass `immediate: true` in the options +to invoke the matching listener synchronously instead: + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +import { Activity } from "@fedify/vocab"; +const context = null as unknown as Context; +const activity = new Activity({}); +// ---cut-before--- +await context.routeActivity(null, activity, { immediate: true }); +~~~~ + +See also the [_Manual routing_ section](./inbox.md#manual-routing) in the +_Inbox listeners_ guide for more examples. + +[`Context.routeActivity()`]: https://jsr.io/@fedify/fedify/doc/federation/~/Context#method_routeActivity_0 + + +Signed key and its owner +------------------------ + +_This API is available since Fedify 0.7.0._ + +[`RequestContext.getSignedKey()`] verifies the HTTP Signature on the current +incoming request and returns the corresponding [`CryptographicKey`], or `null` +if the request is unsigned or the signature is invalid: + +~~~~ typescript twoslash +import { type RequestContext } from "@fedify/fedify"; +const ctx = null as unknown as RequestContext; +// ---cut-before--- +const key = await ctx.getSignedKey(); +if (key != null) { + console.log("Request signed with key:", key.id?.href); +} +~~~~ + +[`RequestContext.getSignedKeyOwner()`] goes one step further: it looks up the +actor that owns the verified key and returns an [`Actor`] object, or `null` if +no valid signature is present or the owner cannot be fetched: + +~~~~ typescript twoslash +import { type RequestContext } from "@fedify/fedify"; +const ctx = null as unknown as RequestContext; +async function handleRequest() { + // ---cut-before--- + const owner = await ctx.getSignedKeyOwner(); + if (owner == null) { + // No valid signature—treat as unauthenticated. + return; + } + console.log("Request from actor:", owner.id?.href); + // ---cut-after--- +} +~~~~ + +Both results are cached: calling either method more than once in the same +request returns the same value without re-verifying. + +[`RequestContext.getSignedKey()`]: https://jsr.io/@fedify/fedify/doc/federation/~/RequestContext#method_getSignedKey_0 +[`CryptographicKey`]: https://jsr.io/@fedify/vocab/doc/~/CryptographicKey +[`RequestContext.getSignedKeyOwner()`]: https://jsr.io/@fedify/fedify/doc/federation/~/RequestContext#method_getSignedKeyOwner_0 +[`Actor`]: https://jsr.io/@fedify/vocab/doc/~/Actor + +### Instance actor and mutual authorized fetch + +When both your server and the remote server require [authorized fetch], a +naive implementation can deadlock: fetching the remote actor's public key +requires a signed request, which in turn requires the remote actor's key. +The standard solution is an [instance actor]—a special actor that represents +the whole server and is exempt from authorized fetch requirements. + +Pass an authenticated document loader (created via `getDocumentLoader()` for +your instance actor) to `getSignedKeyOwner()` so that Fedify can fetch the +remote actor's key with a valid signature: + +~~~~ typescript twoslash +// @noErrors: 2307 2345 +import type { Federation } from "@fedify/fedify"; +import type { Actor } from "@fedify/vocab"; +const federation = null as unknown as Federation; +async function isBlocked(userId: string, signedKeyOwner: Actor): Promise { + return false; +} +// ---cut-before--- +federation + .setActorDispatcher("/actors/{identifier}", async (ctx, identifier) => { + // ... actor implementation omitted ... + }) + .authorize(async (ctx, identifier) => { + if (identifier === ctx.hostname) return true; // instance actor bypass + const documentLoader = await ctx.getDocumentLoader({ + identifier: ctx.hostname, // sign as instance actor + }); + const owner = await ctx.getSignedKeyOwner({ documentLoader }); // [!code highlight] + if (owner == null) return false; + return !(await isBlocked(identifier, owner)); + }); +~~~~ + +For a complete explanation of authorized fetch and instance actors, see the +[_Access control_ guide](./access-control.md). + +[authorized fetch]: https://swicg.github.io/activitypub-http-signature/#authorized-fetch +[instance actor]: https://swicg.github.io/activitypub-http-signature/#instance-actor + + +Authenticated document loaders +------------------------------ + +_This API is available since Fedify 0.4.0._ + +The `Context.documentLoader` property holds the default (unauthenticated) +[`DocumentLoader`] configured for the federation. When you need to fetch a +private resource—such as a followers-only note or a locked collection—you +must send the request with a valid HTTP Signature. +[`Context.getDocumentLoader()`] creates an authenticated loader on your behalf. + +You can identify the signing actor by identifier, by username (if a handle +mapper is registered), or directly by key material: + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +const ctx = null as unknown as Context; +// ---cut-before--- +// Sign as an actor identified by UUID: +const loaderById = await ctx.getDocumentLoader({ + identifier: "2bd304f9-36b3-44f0-bf0b-29124aafcbb4", +}); + +// Sign as an actor identified by username: +const loaderByUsername = await ctx.getDocumentLoader({ + username: "alice", +}); + +// Sign with an explicit key pair: +const privateKey = null as unknown as CryptoKey; +const loaderByKey = ctx.getDocumentLoader({ + keyId: new URL("https://example.com/users/alice#main-key"), + privateKey, +}); +~~~~ + +Pass the resulting loader to any dereferencing accessor or to +`lookupObject()`: + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +import { type Actor } from "@fedify/vocab"; +const ctx = null as unknown as Context; +const actor = null as unknown as Actor; +// ---cut-before--- +const documentLoader = await ctx.getDocumentLoader({ identifier: "alice" }); +const followers = await actor.getFollowers({ documentLoader }); +~~~~ + +> [!NOTE] +> Authenticated document loaders intentionally do _not_ cache responses, +> because cached data might be stale or correspond to a different +> authentication context. + +> [!TIP] +> Inside a personal inbox listener, `ctx.documentLoader` is already +> pre-authenticated as the inbox owner. You do not need to call +> `getDocumentLoader()` there—just pass `ctx` directly to dereferencing +> accessors. See [_`Context.documentLoader` on an inbox +> listener_](./inbox.md#context-documentloader-on-an-inbox-listener) for +> details. + +For a deeper dive into when and why to use authenticated loaders, see +[_Getting an authenticated `DocumentLoader`_](./context.md#getting-an-authenticated-documentloader) +in the _Context_ guide. + +[`DocumentLoader`]: https://jsr.io/@fedify/fedify/doc/federation/~/DocumentLoader +[`Context.getDocumentLoader()`]: https://jsr.io/@fedify/fedify/doc/federation/~/Context#method_getDocumentLoader_0 + + +Actor key pairs +--------------- + +_This API is available since Fedify 0.10.0._ + +[`Context.getActorKeyPairs()`] dispatches the cryptographic key pairs for an +actor and returns them as an array of [`ActorKeyPair`] objects. Each entry +exposes the key in three formats: + +| Property | Format | Use case | +| ------------------ | ------------------------------- | ---------------------------- | +| `cryptographicKey` | `CryptographicKey` (vocab type) | HTTP Signatures, LD Sigs | +| `multikey` | `Multikey` (vocab type) | Object Integrity Proofs | +| `privateKey` | Web Crypto `CryptoKey` | Manual signing | +| `keyId` | `URL` | Reference in actor documents | + +The first key always gets the `#main-key` fragment for backward compatibility +with clients that look for that specific key ID. Subsequent keys are numbered +`#key-2`, `#key-3`, and so on. + +A typical use in an actor dispatcher: + +~~~~ typescript twoslash +import { type Federation } from "@fedify/fedify"; +import { Person } from "@fedify/vocab"; +const federation = null as unknown as Federation; +// ---cut-before--- +federation.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => { + const keys = await ctx.getActorKeyPairs(identifier); // [!code highlight] + return new Person({ + id: ctx.getActorUri(identifier), + preferredUsername: identifier, + publicKey: keys[0].cryptographicKey, // [!code highlight] + assertionMethods: keys.map((k) => k.multikey), // [!code highlight] + }); +}); +~~~~ + +`getActorKeyPairs()` internally calls the key pairs dispatcher you registered +with `~ActorCallbackSetters.setKeyPairsDispatcher()`. If no dispatcher is +registered, it returns an empty array. See [_Public keys of an +actor_](./actor.md#public-keys-of-an-actor) for details on registering the +dispatcher and generating key pairs. + +[`Context.getActorKeyPairs()`]: https://jsr.io/@fedify/fedify/doc/federation/~/Context#method_getActorKeyPairs_0 +[`ActorKeyPair`]: https://jsr.io/@fedify/fedify/doc/federation/~/ActorKeyPair + + +Looking up remote objects +------------------------- + +_This API is available since Fedify 0.15.0._ + +[`Context.lookupObject()`] fetches an ActivityStreams object by URI or +fediverse handle. When given a handle, it first queries WebFinger to discover +the actor URI and then fetches the actor. + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +const ctx = null as unknown as Context; +// ---cut-before--- +// All three forms are equivalent: +const actor1 = await ctx.lookupObject("@hongminhee@fosstodon.org"); +const actor2 = await ctx.lookupObject("hongminhee@fosstodon.org"); +const actor3 = await ctx.lookupObject("acct:hongminhee@fosstodon.org"); + +// Look up a post by URI: +const note = await ctx.lookupObject("https://fosstodon.org/@hongminhee/112060633798771581"); +~~~~ + +The method returns `null` when the object cannot be fetched or does not pass +validation. + +[`Context.lookupObject()`]: https://jsr.io/@fedify/fedify/doc/federation/~/Context#method_lookupObject_0 + +### Authenticated lookups + +Some resources require authorization, such as followers-only posts. Pass an +authenticated document loader (obtained from `getDocumentLoader()`) to gain +access: + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +const ctx = null as unknown as Context; +// ---cut-before--- +const loader = await ctx.getDocumentLoader({ identifier: "alice" }); +const note = await ctx.lookupObject("https://example.com/users/bob/notes/123", { + documentLoader: loader, +}); +~~~~ + +### Origin validation + +For security, `lookupObject()` follows [FEP-fe34]: if the fetched document +contains an `@id` with a different origin from the requested URL, the method +returns `null` by default to prevent content-spoofing attacks. Control this +behavior with the `crossOrigin` option: + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +const ctx = null as unknown as Context; +// ---cut-before--- +// Default: return null for cross-origin ids (recommended). +const obj = await ctx.lookupObject("https://example.com/notes/123"); + +// Throw instead of returning null: +const strict = await ctx.lookupObject("https://example.com/notes/123", { crossOrigin: "throw" }); + +// Skip origin check (use only with additional validation): +const trusted = await ctx.lookupObject("https://example.com/notes/123", { crossOrigin: "trust" }); +~~~~ + +> [!CAUTION] +> Only use `crossOrigin: "trust"` when you fully understand the security +> implications and have implemented additional validation measures. + +[FEP-fe34]: https://w3id.org/fep/fe34 + + +WebFinger lookups +----------------- + +_This API is available since Fedify 1.6.0._ + +[`Context.lookupWebFinger()`] queries a remote server's WebFinger endpoint +and returns the raw [`ResourceDescriptor`] (JRD) document, or `null` on +failure. + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +const ctx = null as unknown as Context; +// ---cut-before--- +const jrd = await ctx.lookupWebFinger("acct:fedify@hollo.social"); + +// Extract the ActivityPub actor URI: +const link = jrd?.links?.find((l) => l.rel === "self" && l.type === "application/activity+json"); +if (link?.href) { + const actor = await ctx.lookupObject(link.href); +} +~~~~ + +> [!TIP] +> In most cases, `lookupObject()` is simpler: it handles the WebFinger step +> automatically when given a handle. Use `lookupWebFinger()` when you need +> the raw JRD—for example, to inspect profile-page links or custom +> relation types. + +For more information about WebFinger, see the +[_WebFinger_ guide](./webfinger.md). + +[`Context.lookupWebFinger()`]: https://jsr.io/@fedify/fedify/doc/federation/~/Context#method_lookupWebFinger_0 +[`ResourceDescriptor`]: https://jsr.io/@fedify/webfinger/doc/~/ResourceDescriptor + + +NodeInfo lookups +---------------- + +_This API is available since Fedify 1.4.0._ + +[`Context.lookupNodeInfo()`] fetches a remote server's [NodeInfo] document. +By default it discovers the NodeInfo URL from `/.well-known/nodeinfo`; pass +`direct: true` to skip discovery and fetch the given URL directly. + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +const ctx = null as unknown as Context; +// ---cut-before--- +// Discover and fetch NodeInfo for a remote server: +const info = await ctx.lookupNodeInfo("https://mastodon.social"); +if (info != null) { + console.log("Software:", info.software.name, info.software.version); + console.log("Users:", info.usage?.users?.total); +} +~~~~ + +The method returns `undefined` when the server does not expose NodeInfo or +when the fetch fails. For the full list of options, see +[`GetNodeInfoOptions`]. + +For more information on NodeInfo, see the [_NodeInfo_ guide](./nodeinfo.md). + +[`Context.lookupNodeInfo()`]: https://jsr.io/@fedify/fedify/doc/federation/~/Context#method_lookupNodeInfo_0 +[NodeInfo]: https://nodeinfo.diaspora.software/ +[`GetNodeInfoOptions`]: https://jsr.io/@fedify/fedify/doc/nodeinfo/~/GetNodeInfoOptions + + +Traversing collections +---------------------- + +_This API is available since Fedify 1.1.0._ + +[`Context.traverseCollection()`] iterates over all items in an ActivityStreams +[`Collection`] or [`OrderedCollection`], automatically following pagination +links. + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +import { isActor } from "@fedify/vocab"; +const ctx = null as unknown as Context; +// ---cut-before--- +const actor = await ctx.lookupObject("@hongminhee@fosstodon.org"); +if (isActor(actor)) { + const outbox = await actor.getOutbox(); + if (outbox != null) { + for await (const activity of ctx.traverseCollection(outbox)) { + // [!code highlight] + console.log(activity.id?.href); + } + } +} +~~~~ + +Pass `suppressError: true` to log page-fetch errors instead of throwing, which +is useful when you want to process as many items as possible even if some pages +are unavailable: + +~~~~ typescript twoslash +import { type Context } from "@fedify/fedify"; +import { type Collection } from "@fedify/vocab"; +const ctx = null as unknown as Context; +const collection = null as unknown as Collection; +// ---cut-before--- +for await (const item of ctx.traverseCollection(collection, { + suppressError: true, +})) { + console.log(item.id?.href); +} +~~~~ + +[`Context.traverseCollection()`]: https://jsr.io/@fedify/fedify/doc/federation/~/Context#method_traverseCollection_0 +[`Collection`]: https://jsr.io/@fedify/vocab/doc/~/Collection +[`OrderedCollection`]: https://jsr.io/@fedify/vocab/doc/~/OrderedCollection + + +`request` and `url` +------------------- + +`RequestContext`—the subtype of `Context` used inside HTTP-request +callbacks—exposes two additional properties for inspecting the current +request: + +~~~~ typescript twoslash +import { type RequestContext } from "@fedify/fedify"; +const ctx = null as unknown as RequestContext; +// ---cut-before--- +// The raw Web API Request object: +const request: Request = ctx.request; + +// The parsed URL of the request: +const url: URL = ctx.url; +~~~~ + +These are distinct from `Context.origin`, which only contains the scheme and +host. `ctx.url` includes the full path and query string. + +A common use is to pass the original request along to another handler or to +read custom headers and query parameters: + +~~~~ typescript twoslash +import { type Federation } from "@fedify/fedify"; +const federation = null as unknown as Federation; +// ---cut-before--- +federation.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => { + // Read a custom header from the incoming request: + const accept = ctx.request.headers.get("Accept"); + // Inspect the full URL, including any query string: + const query = ctx.url.searchParams.get("format"); + // ... + return null; +}); +~~~~ + +`RequestContext` is used in actor dispatchers, inbox listeners, object +dispatchers, collection dispatchers, and anywhere else a live HTTP request +is in scope. Background tasks and contexts created with +`Federation.createContext()` without a `Request` argument use the base +`Context` type and do not have these properties. diff --git a/docs/manual/context.md b/docs/manual/context.md index 418fcdd3a..23c064a5f 100644 --- a/docs/manual/context.md +++ b/docs/manual/context.md @@ -24,6 +24,10 @@ The key features of the `Context` object are as follows: - [Looking up remote objects](#looking-up-remote-objects) - [NodeInfo client](./nodeinfo.md#nodeinfo-client) +For advanced helpers—URI parsing, manual activity routing, signature +introspection, and authenticated document loading—see the +[*Advanced context helpers*](./context-advanced.md) guide. + Where to get a `Context` object -------------------------------