@fedify/fedify 2.2.0-pr.709.20 → 2.2.0-pr.710.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/{builder-6NAlCn-K.mjs → builder-CKJTVRhn.mjs} +3 -3
  2. package/dist/compat/public-audience.test.d.mts +2 -0
  3. package/dist/compat/public-audience.test.mjs +162 -0
  4. package/dist/compat/transformers.test.mjs +2 -2
  5. package/dist/{deno-CWoVKoBl.mjs → deno-cvysscWX.mjs} +1 -1
  6. package/dist/{docloader-z8BYCZvY.mjs → docloader-BuEazLeK.mjs} +2 -2
  7. package/dist/federation/builder.test.mjs +2 -2
  8. package/dist/federation/collection.test.mjs +1 -1
  9. package/dist/federation/handler.test.mjs +4 -4
  10. package/dist/federation/idempotency.test.mjs +3 -3
  11. package/dist/federation/inbox.test.mjs +1 -1
  12. package/dist/federation/keycache.test.mjs +2 -2
  13. package/dist/federation/kv.test.mjs +1 -1
  14. package/dist/federation/middleware.test.mjs +17 -7
  15. package/dist/federation/mod.cjs +1 -1
  16. package/dist/federation/mod.js +1 -1
  17. package/dist/federation/negotiation.test.mjs +1 -1
  18. package/dist/federation/retry.test.mjs +1 -1
  19. package/dist/federation/send.test.mjs +3 -3
  20. package/dist/federation/webfinger.test.mjs +2 -2
  21. package/dist/{http-cyerYP3k.js → http-B0Tu7TW-.js} +1 -1
  22. package/dist/{http-B0CGWLm2.cjs → http-BJ7XtRRp.cjs} +1 -1
  23. package/dist/{http-Cl7YijGX.mjs → http-Dy22pK-t.mjs} +2 -2
  24. package/dist/{key-DgOxeSHC.mjs → key-DXBoO6CC.mjs} +1 -1
  25. package/dist/{kv-cache-BhCk5dX_.js → kv-cache-Bxbq9e39.js} +1 -1
  26. package/dist/{kv-cache-BzAyTibN.cjs → kv-cache-x4KOTSSC.cjs} +1 -1
  27. package/dist/{ld-BqJAwT8x.mjs → ld-BW_1mHgq.mjs} +2 -2
  28. package/dist/{middleware-DeRKEGtq.mjs → middleware-BdpMBegR.mjs} +1 -1
  29. package/dist/{middleware-Dlfri269.mjs → middleware-Bu0zxHoX.mjs} +17 -15
  30. package/dist/{middleware-CmlDuNnT.js → middleware-COjNuA6A.js} +4 -3
  31. package/dist/{middleware-CPDkGhht.cjs → middleware-CRFf7Wb6.cjs} +1 -1
  32. package/dist/{middleware-Dr7gPlVB.cjs → middleware-QpqLtpW9.cjs} +5 -4
  33. package/dist/mod.cjs +4 -4
  34. package/dist/mod.js +4 -4
  35. package/dist/nodeinfo/client.test.mjs +1 -1
  36. package/dist/nodeinfo/handler.test.mjs +2 -2
  37. package/dist/nodeinfo/types.test.mjs +1 -1
  38. package/dist/otel/exporter.test.mjs +1 -1
  39. package/dist/{owner-CBLQ-vDw.mjs → owner-CBdNJRwZ.mjs} +2 -2
  40. package/dist/{proof-DZpF7E4g.mjs → proof-7MA0dp2c.mjs} +36 -31
  41. package/dist/{proof-CchoRjI8.cjs → proof-B__J6A1_.cjs} +221 -38
  42. package/dist/{proof-sJxm6hBB.js → proof-Bfx0NdYz.js} +218 -41
  43. package/dist/public-audience-5WWE-JTr.mjs +181 -0
  44. package/dist/{send-Ckd6J9RF.mjs → send-9BckNAN-.mjs} +2 -2
  45. package/dist/sig/http.test.mjs +2 -2
  46. package/dist/sig/key.test.mjs +1 -1
  47. package/dist/sig/ld.test.mjs +2 -2
  48. package/dist/sig/mod.cjs +2 -2
  49. package/dist/sig/mod.js +2 -2
  50. package/dist/sig/owner.test.mjs +1 -1
  51. package/dist/sig/proof.test.mjs +60 -2
  52. package/dist/utils/docloader.test.mjs +2 -2
  53. package/dist/utils/mod.cjs +1 -1
  54. package/dist/utils/mod.js +1 -1
  55. package/package.json +16 -7
  56. package/skills/fedify/SKILL.md +462 -0
  57. /package/dist/{activity-listener-Ck3JZ_hR.mjs → activity-listener-CFzUqoCS.mjs} +0 -0
  58. /package/dist/{client-DEpOVgY1.mjs → client-DVu6Fmom.mjs} +0 -0
  59. /package/dist/{collection-BD6-SZ6O.mjs → collection-BQRKGS7L.mjs} +0 -0
  60. /package/dist/{keycache-CCSwkQcY.mjs → keycache-C2t1kvP5.mjs} +0 -0
  61. /package/dist/{kv-tL2TOE9X.mjs → kv-C-TG81Sv.mjs} +0 -0
  62. /package/dist/{negotiation-DnsfFF8I.mjs → negotiation-xb0QR3u_.mjs} +0 -0
  63. /package/dist/{retry-B_E3V_Dx.mjs → retry-CJL0poaU.mjs} +0 -0
  64. /package/dist/{types-DCP0WLdt.mjs → types-CGUnLkU3.mjs} +0 -0
@@ -7,9 +7,10 @@ import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z
7
7
  import { t as assertInstanceOf } from "../assert_instance_of-C4Ri6VuN.mjs";
8
8
  import { t as assert } from "../assert-ddO5KLpe.mjs";
9
9
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
10
- import { a as verifyProof, i as verifyObject, n as hasProofLike, r as signObject, t as createProof } from "../proof-DZpF7E4g.mjs";
10
+ import { t as normalizePublicAudience } from "../public-audience-5WWE-JTr.mjs";
11
+ import { a as verifyProof, i as verifyObject, n as hasProofLike, r as signObject, t as createProof } from "../proof-7MA0dp2c.mjs";
11
12
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
- import { Create, DataIntegrityProof, Multikey, Note, Place } from "@fedify/vocab";
13
+ import { Create, DataIntegrityProof, Multikey, Note, PUBLIC_COLLECTION, Place } from "@fedify/vocab";
13
14
  import { decodeMultibase, importMultibaseKey } from "@fedify/vocab-runtime";
14
15
  import { decodeHex } from "byte-encodings/hex";
15
16
  //#region src/sig/proof.test.ts
@@ -151,6 +152,39 @@ test("signObject()", async () => {
151
152
  created,
152
153
  contextLoader: mockDocumentLoader
153
154
  }), TypeError, "Unsupported algorithm");
155
+ const signed = await signObject(new Create({
156
+ id: new URL("https://server.example/activities/2"),
157
+ actor: new URL("https://server.example/users/alice"),
158
+ object: new Note({
159
+ id: new URL("https://server.example/objects/2"),
160
+ attribution: new URL("https://server.example/users/alice"),
161
+ content: "Hello public"
162
+ }),
163
+ tos: [PUBLIC_COLLECTION]
164
+ }), fep8b32TestVectorPrivateKey, fep8b32TestVectorKeyId, {
165
+ ...options,
166
+ created
167
+ });
168
+ const [proof] = await Array.fromAsync(signed.getProofs(options));
169
+ assertInstanceOf(proof, DataIntegrityProof);
170
+ const signedJson = await normalizePublicAudience(await signed.toJsonLd(options), mockDocumentLoader);
171
+ assertEquals(signedJson.to, PUBLIC_COLLECTION.href);
172
+ const verifyCache = {};
173
+ const verifyOptions = {
174
+ contextLoader: mockDocumentLoader,
175
+ documentLoader: mockDocumentLoader,
176
+ keyCache: {
177
+ get: (keyId) => Promise.resolve(verifyCache[keyId.href]),
178
+ set: (keyId, key) => {
179
+ verifyCache[keyId.href] = key;
180
+ return Promise.resolve();
181
+ }
182
+ }
183
+ };
184
+ assertInstanceOf(await verifyProof(signedJson, proof, verifyOptions), Multikey);
185
+ const signedJsonWithCurie = await signed.toJsonLd(options);
186
+ assertEquals(signedJsonWithCurie.to, "as:Public");
187
+ assertInstanceOf(await verifyProof(signedJsonWithCurie, proof, verifyOptions), Multikey);
154
188
  });
155
189
  test("hasProofLike()", () => {
156
190
  assert(hasProofLike({ proof: {
@@ -252,6 +286,30 @@ test("verifyProof()", async () => {
252
286
  }
253
287
  }, proof, options), null);
254
288
  assertEquals(await verifyProof(jsonLd, proof.clone({ created: Temporal.Now.instant() }), options), null);
289
+ assertEquals(await verifyProof({
290
+ ...jsonLd,
291
+ "https://w3id.org/security#proof": {
292
+ "@type": ["https://w3id.org/security#DataIntegrityProof"],
293
+ "https://w3id.org/security#proofValue": [{ "@value": "stale" }]
294
+ }
295
+ }, proof, options), expectedKey);
296
+ assertEquals(await verifyProof([jsonLd], proof, options), null);
297
+ assertEquals(await verifyProof({
298
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://attacker.example/ctx"],
299
+ id: "https://server.example/activities/attacker",
300
+ type: "Create",
301
+ actor: "https://server.example/users/alice",
302
+ object: {
303
+ id: "https://server.example/objects/attacker",
304
+ type: "Note",
305
+ attributedTo: "https://server.example/users/alice",
306
+ content: "n/a",
307
+ to: "as:Public"
308
+ }
309
+ }, proof, {
310
+ documentLoader: mockDocumentLoader,
311
+ keyCache: options.keyCache
312
+ }), null);
255
313
  });
256
314
  test("verifyObject()", async () => {
257
315
  const options = {
@@ -5,9 +5,9 @@ import { t as esm_default } from "../esm-DVILvP5e.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
6
  import "../std__assert-Duiq_YC9.mjs";
7
7
  import { t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
8
- import { l as verifyRequest } from "../http-Cl7YijGX.mjs";
8
+ import { l as verifyRequest } from "../http-Dy22pK-t.mjs";
9
9
  import { i as rsaPrivateKey2 } from "../keys-BAK-tUlf.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-z8BYCZvY.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-BuEazLeK.mjs";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  import { UrlError } from "@fedify/vocab-runtime";
13
13
  //#region src/utils/docloader.test.ts
@@ -1,6 +1,6 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
- const require_kv_cache = require("../kv-cache-BzAyTibN.cjs");
4
+ const require_kv_cache = require("../kv-cache-x4KOTSSC.cjs");
5
5
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
6
6
  exports.kvCache = require_kv_cache.kvCache;
package/dist/utils/mod.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-BhCk5dX_.js";
3
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-Bxbq9e39.js";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "2.2.0-pr.709.20+17eb3fa5",
3
+ "version": "2.2.0-pr.710.24+7ce4f97e",
4
4
  "description": "An ActivityPub server framework",
5
5
  "keywords": [
6
6
  "ActivityPub",
@@ -32,8 +32,17 @@
32
32
  },
33
33
  "type": "module",
34
34
  "files": [
35
- "dist"
35
+ "dist",
36
+ "skills"
36
37
  ],
38
+ "agents": {
39
+ "skills": [
40
+ {
41
+ "name": "fedify",
42
+ "path": "./skills/fedify"
43
+ }
44
+ ]
45
+ },
37
46
  "module": "./dist/mod.js",
38
47
  "main": "./dist/mod.cjs",
39
48
  "types": "./dist/mod.d.ts",
@@ -144,9 +153,9 @@
144
153
  "uri-template-router": "^1.0.0",
145
154
  "url-template": "^3.1.1",
146
155
  "urlpattern-polyfill": "^10.1.0",
147
- "@fedify/vocab": "2.2.0-pr.709.20+17eb3fa5",
148
- "@fedify/vocab-runtime": "2.2.0-pr.709.20+17eb3fa5",
149
- "@fedify/webfinger": "2.2.0-pr.709.20+17eb3fa5"
156
+ "@fedify/vocab": "2.2.0-pr.710.24+7ce4f97e",
157
+ "@fedify/vocab-runtime": "2.2.0-pr.710.24+7ce4f97e",
158
+ "@fedify/webfinger": "2.2.0-pr.710.24+7ce4f97e"
150
159
  },
151
160
  "devDependencies": {
152
161
  "@std/assert": "npm:@jsr/std__assert@^0.226.0",
@@ -158,8 +167,8 @@
158
167
  "tsx": "^4.19.4",
159
168
  "typescript": "^5.9.2",
160
169
  "wrangler": "^4.17.0",
161
- "@fedify/vocab-tools": "^2.2.0-pr.709.20+17eb3fa5",
162
- "@fedify/fixture": "2.0.0"
170
+ "@fedify/fixture": "2.0.0",
171
+ "@fedify/vocab-tools": "^2.2.0-pr.710.24+7ce4f97e"
163
172
  },
164
173
  "scripts": {
165
174
  "build:self": "tsdown",
@@ -0,0 +1,462 @@
1
+ ---
2
+ name: fedify
3
+ description: >-
4
+ Use this skill whenever writing JavaScript or TypeScript code that uses
5
+ Fedify to build an ActivityPub server, handle federation activities,
6
+ implement fediverse features, or integrate Fedify with a web framework
7
+ such as Hono, Express, Next.js, Nuxt, Fastify, Koa, NestJS, Astro,
8
+ SvelteKit, Fresh, h3, Elysia, or Cloudflare Workers. Covers the
9
+ `Federation` builder pattern, actor/inbox/outbox/collection dispatchers,
10
+ inbox listeners, vocabulary objects from `@fedify/vocab`, key pair
11
+ management, HTTP Signatures, Object Integrity Proofs, the `KvStore` and
12
+ `MessageQueue` interfaces, database adapter packages, structured logging
13
+ with LogTape, OpenTelemetry tracing, the `fedify` CLI toolchain, and
14
+ common mistakes. Also apply when the user mentions ActivityPub,
15
+ federation, fediverse, WebFinger, NodeInfo, FEPs, or Mastodon
16
+ interoperability, even if they do not name Fedify explicitly.
17
+ ---
18
+
19
+ Fedify skill
20
+ ============
21
+
22
+ Fedify is a TypeScript library for ActivityPub server applications. It
23
+ works across Deno, Node.js, and Bun. The library takes care of the fiddly
24
+ parts of the fediverse (HTTP Signatures, Object Integrity Proofs,
25
+ WebFinger, NodeInfo, JSON-LD, delivery queues) so application code can
26
+ stay focused on dispatchers and activity handlers.
27
+
28
+ Always link into the full documentation at <https://fedify.dev/>
29
+ instead of guessing. Every docs page is also served as raw Markdown
30
+ by appending `.md` to its path, so
31
+ <https://fedify.dev/manual/federation.md> returns `text/markdown`.
32
+ This skill uses the `.md` form in every fedify.dev link below so you
33
+ can read the source directly without HTML rendering; when you present
34
+ a link *to the user*, strip the `.md` suffix so browsers render the
35
+ HTML page (so `https://fedify.dev/manual/federation.md` becomes
36
+ `https://fedify.dev/manual/federation`). The index at
37
+ <https://fedify.dev/llms.txt> and the full bundle at
38
+ <https://fedify.dev/llms-full.txt> are authoritative; this skill only
39
+ points the way. Do not invent APIs; verify names against those docs
40
+ or against the installed `@fedify/fedify` types.
41
+
42
+
43
+ Builder pattern
44
+ ---------------
45
+
46
+ Two entry points reach a `Federation<TContextData>` object:
47
+
48
+ - `createFederationBuilder<TContextData>()` returns a
49
+ `FederationBuilder<TContextData>`. Register dispatchers and
50
+ listeners on it, then `await builder.build(options)` to obtain the
51
+ `Federation<TContextData>`. Prefer this in larger apps, especially
52
+ when you need to split configuration across files or avoid circular
53
+ imports. In serverless runtimes such as Cloudflare Workers,
54
+ bindings are only available per-request, so the `Federation` must be
55
+ constructed inside the request handler; the builder pattern is the
56
+ documented approach there because dispatcher registration can happen
57
+ at module load time and only the asynchronous `.build(options)` call
58
+ runs per request.
59
+ - `createFederation<TContextData>(options)` returns a
60
+ `Federation<TContextData>` directly. Appropriate when everything
61
+ fits in one module.
62
+
63
+ `.build()` is asynchronous; always `await` it. See
64
+ <https://fedify.dev/manual/federation.md>.
65
+
66
+ ~~~~ typescript
67
+ import { createFederationBuilder, MemoryKvStore } from "@fedify/fedify";
68
+
69
+ const builder = createFederationBuilder<AppState>();
70
+ // ...register dispatchers on builder...
71
+ export const federation = await builder.build({
72
+ kv: new MemoryKvStore(), // development only
73
+ });
74
+ ~~~~
75
+
76
+ > [!IMPORTANT]
77
+ > Production deployments *must* provide a real `queue` implementation.
78
+ > Without one, outgoing activities are sent synchronously and delivery
79
+ > becomes unreliable under load. See
80
+ > <https://fedify.dev/manual/federation.md>.
81
+
82
+ > [!WARNING]
83
+ > Never set `allowPrivateAddress: true` outside tests. It disables the
84
+ > SSRF guard that blocks Fedify from fetching private or loopback
85
+ > addresses. See <https://fedify.dev/manual/federation.md> and
86
+ > <https://fedify.dev/manual/deploy.md>.
87
+
88
+
89
+ Dispatchers
90
+ -----------
91
+
92
+ Every route Fedify serves is driven by a dispatcher callback registered on
93
+ the builder (or `Federation` object). Do not hand-roll these routes in
94
+ the web framework; the dispatcher signatures encode the library's URI
95
+ template guarantees.
96
+
97
+ - `setActorDispatcher(path, dispatcher)`: returns an
98
+ `ActorCallbackSetters` chain that also carries
99
+ `setKeyPairsDispatcher()`.
100
+ - `setObjectDispatcher(type, path, dispatcher)`: for individual
101
+ `Object` types such as `Note` or `Article`.
102
+ - `setInboxDispatcher(path, dispatcher)`: the inbox *collection*
103
+ endpoint. The inbox *listener* is a different API (see below).
104
+ - `setOutboxDispatcher(path, dispatcher)`.
105
+ - `setFollowingDispatcher(path, dispatcher)` /
106
+ `setFollowersDispatcher(path, dispatcher)` /
107
+ `setLikedDispatcher(path, dispatcher)` /
108
+ `setFeaturedDispatcher(path, dispatcher)` /
109
+ `setFeaturedTagsDispatcher(path, dispatcher)`.
110
+ - `setCollectionDispatcher()` and `setOrderedCollectionDispatcher()`
111
+ for custom collections.
112
+ - `setNodeInfoDispatcher(path, dispatcher)` and
113
+ `setWebFingerLinksDispatcher(dispatcher)` for protocol endpoints.
114
+
115
+ Paths use URI templates. If an identifier can contain URI characters,
116
+ switch the template variable from `{identifier}` to `{+identifier}` to
117
+ avoid double-encoding. See <https://fedify.dev/manual/uri-template.md>.
118
+
119
+ > [!WARNING]
120
+ > Simple expansion (`{identifier}`) percent-encodes reserved characters a
121
+ > second time. If actors or objects are keyed by URIs, use reserved
122
+ > expansion (`{+identifier}`).
123
+
124
+ See <https://fedify.dev/manual/actor.md>,
125
+ <https://fedify.dev/manual/object.md>, and
126
+ <https://fedify.dev/manual/collections.md>.
127
+
128
+
129
+ Inbox listeners
130
+ ---------------
131
+
132
+ `setInboxListeners(inboxPath, sharedInboxPath?)` returns an
133
+ `InboxListenerSetters` object with:
134
+
135
+ - `.on(ActivityType, handler)`: chainable, keyed by the *class*
136
+ (`Follow`, `Create`, `Undo`, etc.).
137
+ - `.onError(handler)`.
138
+ - `.onUnverifiedActivity(handler)`.
139
+ - `.setSharedKeyDispatcher(dispatcher)`.
140
+ - `.withIdempotency(strategy)`.
141
+
142
+ > [!WARNING]
143
+ > Activities of a type that is not registered via `.on()` are answered
144
+ > with HTTP 202 and logged at error level as an unsupported activity,
145
+ > but never reach a listener. To catch everything, register a listener
146
+ > for the base `Activity` class.
147
+
148
+ See <https://fedify.dev/manual/inbox.md>.
149
+
150
+
151
+ Context and `TContextData`
152
+ --------------------------
153
+
154
+ `Context<TContextData>` is the per-operation handle Fedify passes to
155
+ dispatchers and listeners. The `TContextData` generic carries
156
+ application state (database handles, request id, auth session). Treat it
157
+ as the single place to inject dependencies; do not reach for module-level
158
+ singletons inside handlers.
159
+
160
+ `RequestContext<TContextData>` extends `Context<TContextData>` with
161
+ request-scoped helpers.
162
+
163
+ Use `ctx.get…Uri()` helpers (for example `ctx.getActorUri(identifier)`)
164
+ to build canonical URIs instead of string-concatenating paths.
165
+
166
+ > [!CAUTION]
167
+ > The `crossOrigin: "trust"` option on context methods and on vocabulary
168
+ > dereferencing disables the same-origin check. Only use it when the
169
+ > remote document is known to be trustworthy; it was the source of
170
+ > prior interop bugs.
171
+
172
+ See <https://fedify.dev/manual/context.md> and
173
+ <https://fedify.dev/manual/context-advanced.md>.
174
+
175
+
176
+ Framework integrations
177
+ ----------------------
178
+
179
+ Mount Fedify through the dedicated integration package for the target
180
+ framework. Do not translate requests manually; the integration handles
181
+ content negotiation, signature verification, and response streaming.
182
+
183
+ | Framework | Package |
184
+ | ------------------ | -------------------- |
185
+ | Astro | *@fedify/astro* |
186
+ | Cloudflare Workers | *@fedify/cfworkers* |
187
+ | Elysia | *@fedify/elysia* |
188
+ | Express | *@fedify/express* |
189
+ | Fastify | *@fedify/fastify* |
190
+ | Fresh | *@fedify/fresh* |
191
+ | h3 | *@fedify/h3* |
192
+ | Hono | *@fedify/hono* |
193
+ | Koa | *@fedify/koa* |
194
+ | NestJS | *@fedify/nestjs* |
195
+ | Next.js | *@fedify/next* |
196
+ | Nuxt | *@fedify/nuxt* |
197
+ | SolidStart | *@fedify/solidstart* |
198
+ | SvelteKit | *@fedify/sveltekit* |
199
+
200
+ Two more packages are frequently useful: *@fedify/debugger* for a local
201
+ ActivityPub dashboard, and *@fedify/relay* for relay implementations.
202
+
203
+ See <https://fedify.dev/manual/integration.md>.
204
+
205
+
206
+ Built-in protocol endpoints
207
+ ---------------------------
208
+
209
+ Fedify serves these endpoints automatically as soon as the federation
210
+ handler is mounted; do not reimplement them.
211
+
212
+ - `/.well-known/webfinger` (WebFinger). Customize link output with
213
+ `setWebFingerLinksDispatcher()`. See
214
+ <https://fedify.dev/manual/webfinger.md>.
215
+ - `/.well-known/nodeinfo` and the versioned NodeInfo document.
216
+ Customize with `setNodeInfoDispatcher()`. See
217
+ <https://fedify.dev/manual/nodeinfo.md>.
218
+
219
+
220
+ Outgoing activities
221
+ -------------------
222
+
223
+ `ctx.sendActivity(sender, recipients, activity, options?)` is the single
224
+ entry point for outbound delivery. Two overloads:
225
+
226
+ - Explicit recipients: pass a single `Recipient` or an array. The
227
+ `sender` may be a `SenderKeyPair`, a `SenderKeyPair[]`, or
228
+ `{ identifier }` / `{ username }`.
229
+ - Fan-out: pass the literal `"followers"` to deliver to the sender's
230
+ `Followers` collection. In this overload the `sender` must be
231
+ `{ identifier }` or `{ username }`; a raw `SenderKeyPair` or
232
+ `SenderKeyPair[]` is rejected because Fedify needs the actor
233
+ identifier to resolve the followers collection.
234
+
235
+ Always route outbound activities through the queue in production; this is
236
+ the same `queue` provided to `createFederation()` or `.build()`. Without
237
+ a queue the call blocks until every recipient responds and failed
238
+ deliveries have no retry.
239
+
240
+ > [!CAUTION]
241
+ > Do not derive an activity's `id` from `(actor, object)`. The same
242
+ > actor can send the same activity shape to the same object more than
243
+ > once (for example `Follow` → `Undo(Follow)` → `Follow` again), and
244
+ > those must be distinct activities. Use a fresh UUID or counter in the
245
+ > fragment.
246
+
247
+ See <https://fedify.dev/manual/send.md>.
248
+
249
+
250
+ Vocabulary imports
251
+ ------------------
252
+
253
+ Import ActivityStreams and ActivityPub vocabulary types from
254
+ `@fedify/vocab`. The historical path `@fedify/fedify/vocab` is a
255
+ deprecated shim kept for backwards compatibility; new code should not use
256
+ it. Likewise, `@fedify/vocab-runtime` replaces the old
257
+ `@fedify/fedify/runtime` path, and `@fedify/webfinger` replaces the old
258
+ in-tree *src/webfinger*.
259
+
260
+ > [!CAUTION]
261
+ > Several vocabulary classes collide with JavaScript globals (notably
262
+ > `Object`). When importing, either use a namespace import
263
+ > (`import * as vocab from "@fedify/vocab"`) or alias the individual
264
+ > class.
265
+
266
+ `fromJsonLd()` and `toJsonLd()` are asynchronous; always `await` them.
267
+
268
+ > [!WARNING]
269
+ > `crossOrigin: "trust"` on vocabulary deserialization trusts embedded
270
+ > objects without re-fetching. Treat it as you would
271
+ > `dangerouslySetInnerHTML`.
272
+
273
+ See <https://fedify.dev/manual/vocab.md>.
274
+
275
+
276
+ Key pair management
277
+ -------------------
278
+
279
+ `setActorDispatcher(...).setKeyPairsDispatcher(dispatcher)` supplies the
280
+ actor's key pairs. Return *two* keys per actor:
281
+
282
+ - An RSA-PKCS#1-v1.5 key for HTTP Signatures (Mastodon interop).
283
+ - An Ed25519 key for FEP-8b32 Object Integrity Proofs.
284
+
285
+ Fedify signs outbound activities with whatever keys are available; for
286
+ interop with the widest set of peers, provide both.
287
+
288
+ > [!WARNING]
289
+ > Private keys must live in secret storage. They are not configuration;
290
+ > do not check them into repositories, embed them in container images,
291
+ > or expose them via admin endpoints.
292
+
293
+ See <https://fedify.dev/manual/actor.md>.
294
+
295
+
296
+ Persistent storage
297
+ ------------------
298
+
299
+ Fedify defines two storage interfaces: `KvStore` (key/value cache and
300
+ idempotence) and `MessageQueue` (delivery plus inbox processing), both
301
+ re-exported from `@fedify/fedify`. Use the built-in `MemoryKvStore` only
302
+ in development or tests.
303
+
304
+ | Package | `KvStore` | `MessageQueue` |
305
+ | ------------------- | --------- | -------------- |
306
+ | *@fedify/sqlite* | yes | yes |
307
+ | *@fedify/postgres* | yes | yes |
308
+ | *@fedify/mysql* | yes | yes |
309
+ | *@fedify/redis* | yes | yes |
310
+ | *@fedify/amqp* | no | yes |
311
+ | *@fedify/denokv* | yes | yes |
312
+ | *@fedify/cfworkers* | yes | yes |
313
+
314
+ > [!WARNING]
315
+ > `PostgresMessageQueue` and similar implementations require connection
316
+ > pooling sized for parallel consumers; a single shared connection will
317
+ > deadlock under `ParallelMessageQueue`. See
318
+ > <https://fedify.dev/manual/mq.md>.
319
+
320
+ > [!WARNING]
321
+ > Do not load-balance worker nodes that drain the queue. Each worker
322
+ > should take traffic independently; putting them behind a load balancer
323
+ > breaks idempotency tracking. See <https://fedify.dev/manual/deploy.md>.
324
+
325
+ See <https://fedify.dev/manual/kv.md> and <https://fedify.dev/manual/mq.md>.
326
+
327
+
328
+ Observability
329
+ -------------
330
+
331
+ ### LogTape
332
+
333
+ Fedify emits structured logs via [LogTape] under the following
334
+ categories. Configure LogTape once at application start (if this
335
+ project has a separate LogTape skill installed, defer to it for the
336
+ generic setup):
337
+
338
+ - `fedify.compat.transformers`
339
+ - `fedify.federation`, `fedify.federation.actor`,
340
+ `fedify.federation.collection`, `fedify.federation.fanout`,
341
+ `fedify.federation.http`, `fedify.federation.inbox`,
342
+ `fedify.federation.outbox`, `fedify.federation.queue`
343
+ - `fedify.nodeinfo.client`
344
+ - `fedify.otel.exporter`
345
+ - `fedify.sig.http`, `fedify.sig.key`, `fedify.sig.ld`,
346
+ `fedify.sig.proof`
347
+ - `fedify.utils.docloader`, `fedify.utils.kv-cache`
348
+ - `fedify.webfinger.server`
349
+
350
+ > [!CAUTION]
351
+ > Since LogTape 0.7.0, implicit contexts require explicit configuration.
352
+ > See <https://fedify.dev/manual/log.md>.
353
+
354
+ [LogTape]: https://logtape.org/
355
+
356
+ ### OpenTelemetry
357
+
358
+ Pass a `tracerProvider` in `FederationOptions` to have Fedify instrument
359
+ its internals. For trace persistence, `@fedify/fedify/otel` exports
360
+ `FedifySpanExporter`, which writes traces to a `KvStore` so the
361
+ *@fedify/debugger* dashboard can render them.
362
+
363
+ > [!CAUTION]
364
+ > Initialize the OpenTelemetry SDK *before* importing Fedify. Later
365
+ > registration leaves earlier spans untraced.
366
+
367
+ See <https://fedify.dev/manual/log.md> and
368
+ <https://fedify.dev/manual/opentelemetry.md>.
369
+
370
+
371
+ Looking up FEPs
372
+ ---------------
373
+
374
+ When the user references a Fediverse Enhancement Proposal (for example
375
+ `FEP-8fcf` or `FEP-1b12`), clone the proposals repository locally and
376
+ read the relevant file; Codeberg blocks web scraping and `WebFetch`-style
377
+ requests fail:
378
+
379
+ ~~~~ bash
380
+ git clone https://codeberg.org/fediverse/fep.git
381
+ ~~~~
382
+
383
+ Files are under *fep/* keyed by the four-hex-digit identifier (for
384
+ example *fep/8fcf/fep-8fcf.md*). If the project is configured with the
385
+ [FEP MCP server], prefer that instead.
386
+
387
+ [FEP MCP server]: https://github.com/dahlia/fep-mcp
388
+
389
+
390
+ CLI helpers
391
+ -----------
392
+
393
+ The `fedify` CLI (distributed as *@fedify/cli*) covers bootstrapping and
394
+ debugging:
395
+
396
+ - `fedify init`: scaffold a new project (pick web framework, package
397
+ manager, KV store, and message queue).
398
+ - `fedify lookup`: resolve a handle, URL, or WebFinger identifier and
399
+ print the dereferenced document.
400
+ - `fedify inbox`: spin up a temporary inbox with a tunnel to inspect
401
+ incoming activities from real peers.
402
+ - `fedify webfinger`, `fedify nodeinfo`, `fedify tunnel`,
403
+ `fedify relay`.
404
+
405
+ > [!WARNING]
406
+ > `fedify inbox` and `fedify tunnel` are development tools. They open a
407
+ > public tunnel to your local process; do not run them against
408
+ > production data.
409
+
410
+ See <https://fedify.dev/cli.md>.
411
+
412
+
413
+ Common mistakes to avoid
414
+ ------------------------
415
+
416
+ - Forgetting to `await builder.build(...)` or `await ctx.sendActivity(...)`.
417
+ Both are asynchronous.
418
+ - Hand-rolling `/.well-known/webfinger` or `/.well-known/nodeinfo`
419
+ routes; Fedify already serves them.
420
+ - Importing from the deprecated shims `@fedify/fedify/vocab` or
421
+ `@fedify/fedify/runtime`, or from the old in-tree *src/webfinger*
422
+ path, instead of the dedicated packages `@fedify/vocab`,
423
+ `@fedify/vocab-runtime`, and `@fedify/webfinger`.
424
+ - Omitting the `queue` option in production; outgoing delivery becomes
425
+ synchronous and unreliable.
426
+ - Running with `MemoryKvStore` in production; it evaporates on every
427
+ restart.
428
+ - Running behind a reverse proxy, a tunnel (`fedify tunnel`, ngrok,
429
+ Cloudflare Tunnel, Tailscale Funnel), or a load balancer without
430
+ propagating the original origin. Fedify reads `request.url`, so
431
+ without `X-Forwarded-*` handling it will mint actor IDs and activity
432
+ URLs using the internal origin (for example `http://localhost:3000`)
433
+ instead of the public `https://…` address that remote peers
434
+ dereference. Fix one of two ways: pin
435
+ `FederationOptions.origin` to the canonical URL, or pipe requests
436
+ through [x-forwarded-fetch] before they reach Fedify (gated on a
437
+ `BEHIND_PROXY` flag, since `X-Forwarded-Host` is spoofable from the
438
+ open internet). See <https://fedify.dev/manual/deploy.md>.
439
+ - Enabling `allowPrivateAddress: true` outside tests; that disables the
440
+ SSRF guard.
441
+ - Using `crossOrigin: "trust"` without verifying the remote is
442
+ actually trusted.
443
+ - Registering inbox handlers only for specific activity types and
444
+ expecting delivery-level error handling; unregistered types are
445
+ answered with HTTP 202 and logged at error level as unsupported,
446
+ but never reach a listener. Add a catch-all on `Activity` if you
447
+ need to observe them.
448
+ - Wiring Fedify into a web framework by writing custom routes instead
449
+ of importing the matching `@fedify/<framework>` package.
450
+ - Load-balancing queue worker nodes; each worker must take traffic
451
+ independently.
452
+ - Using simple URI-template expansion (`{identifier}`) when identifiers
453
+ contain reserved URI characters; switch to `{+identifier}`.
454
+ - Deriving an activity's `id` from `(actor, object)`; the same pair
455
+ can legitimately produce multiple activities of the same shape.
456
+ - Returning `Tombstone` from an actor dispatcher without checking
457
+ `RequestContext.getActor({ tombstone: "passthrough" })` semantics;
458
+ see <https://fedify.dev/manual/actor.md>.
459
+ - Committing private keys, embedding them in bundles, or exposing them
460
+ through admin endpoints.
461
+
462
+ [x-forwarded-fetch]: https://github.com/dahlia/x-forwarded-fetch
File without changes
File without changes
File without changes