@fedify/fedify 2.4.0-pr.934.40 → 2.4.0

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 (244) hide show
  1. package/README.md +48 -40
  2. package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
  3. package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
  4. package/dist/assert_exists-cffSpJ7X.mjs +28 -0
  5. package/dist/assert_false-CPdRnc3E.mjs +23 -0
  6. package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
  7. package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
  8. package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
  9. package/dist/assert_rejects-CTptdYck.mjs +39 -0
  10. package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
  11. package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
  12. package/dist/assertion_error-B-cx0fFA.mjs +31 -0
  13. package/dist/audience-CHQ-FlLD.mjs +95 -0
  14. package/dist/{builder-BBPwtB2v.mjs → builder-lJDh222H.mjs} +169 -14
  15. package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
  16. package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
  17. package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
  18. package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
  19. package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
  20. package/dist/compat/mod.cjs +1 -1
  21. package/dist/compat/mod.d.cts +1 -1
  22. package/dist/compat/mod.d.ts +1 -1
  23. package/dist/compat/mod.js +1 -1
  24. package/dist/compat/outgoing-jsonld.test.mjs +71 -3
  25. package/dist/compat/public-audience.test.mjs +14 -5
  26. package/dist/compat/secured-document.test.d.mts +2 -0
  27. package/dist/compat/secured-document.test.mjs +58 -0
  28. package/dist/compat/transformers.test.mjs +5 -5
  29. package/dist/compound-proof-zAjqsHiN.mjs +989 -0
  30. package/dist/{context-Cjnr0ig1.d.ts → context-7NyiqIcg.d.ts} +1600 -25
  31. package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
  32. package/dist/{context-C05Rgxmu.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
  33. package/dist/{deno-D3c0o_N0.mjs → deno-D8wr4ikF.mjs} +1 -1
  34. package/dist/dist-DJQM4u7i.mjs +189 -0
  35. package/dist/docloader-TCvsU9vF.mjs +87 -0
  36. package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
  37. package/dist/federation/audience.test.d.mts +2 -0
  38. package/dist/federation/audience.test.mjs +130 -0
  39. package/dist/federation/builder.test.mjs +54 -5
  40. package/dist/federation/circuit-breaker.test.mjs +838 -7
  41. package/dist/federation/collection.test.mjs +3 -3
  42. package/dist/federation/compound-fanout.test.d.mts +2 -0
  43. package/dist/federation/compound-fanout.test.mjs +228 -0
  44. package/dist/federation/handler-compound-proof.test.d.mts +2 -0
  45. package/dist/federation/handler-compound-proof.test.mjs +384 -0
  46. package/dist/federation/handler.test.mjs +387 -15
  47. package/dist/federation/hashlink-media.test.d.mts +2 -0
  48. package/dist/federation/hashlink-media.test.mjs +422 -0
  49. package/dist/federation/idempotency.test.mjs +6 -6
  50. package/dist/federation/inbox-report.test.d.mts +2 -0
  51. package/dist/federation/inbox-report.test.mjs +475 -0
  52. package/dist/federation/inbox.test.mjs +3 -3
  53. package/dist/federation/keycache.test.mjs +215 -14
  54. package/dist/federation/kv.test.mjs +3 -3
  55. package/dist/federation/metrics.test.mjs +5 -5
  56. package/dist/federation/middleware.test.mjs +967 -35
  57. package/dist/federation/mod.cjs +18 -540
  58. package/dist/federation/mod.d.cts +5 -5
  59. package/dist/federation/mod.d.ts +5 -5
  60. package/dist/federation/mod.js +1 -1
  61. package/dist/federation/mq.test.mjs +104 -47
  62. package/dist/federation/negotiation.test.mjs +4 -4
  63. package/dist/federation/outgoing-proof.test.d.mts +2 -0
  64. package/dist/federation/outgoing-proof.test.mjs +931 -0
  65. package/dist/federation/portable-actor.test.d.mts +2 -0
  66. package/dist/federation/portable-actor.test.mjs +496 -0
  67. package/dist/federation/portable-chain.test.d.mts +2 -0
  68. package/dist/federation/portable-chain.test.mjs +370 -0
  69. package/dist/federation/portable-collection.test.d.mts +2 -0
  70. package/dist/federation/portable-collection.test.mjs +832 -0
  71. package/dist/federation/portable-delivery.test.d.mts +2 -0
  72. package/dist/federation/portable-delivery.test.mjs +746 -0
  73. package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
  74. package/dist/federation/portable-gateway-keys.test.mjs +686 -0
  75. package/dist/federation/portable-inbox.test.d.mts +2 -0
  76. package/dist/federation/portable-inbox.test.mjs +1535 -0
  77. package/dist/federation/portable-parse-uri.test.d.mts +2 -0
  78. package/dist/federation/portable-parse-uri.test.mjs +348 -0
  79. package/dist/federation/portable-route-activity.test.d.mts +2 -0
  80. package/dist/federation/portable-route-activity.test.mjs +259 -0
  81. package/dist/federation/portable.test.d.mts +2 -0
  82. package/dist/federation/portable.test.mjs +1072 -0
  83. package/dist/federation/retry.test.mjs +4 -3
  84. package/dist/federation/router.test.mjs +9 -159
  85. package/dist/federation/send.test.mjs +245 -27
  86. package/dist/federation/tasks/codec.test.mjs +4 -4
  87. package/dist/federation/tasks/enqueue.test.mjs +5 -4
  88. package/dist/federation/tasks/tasks.test.mjs +7 -6
  89. package/dist/federation/temporal.test.mjs +4 -4
  90. package/dist/federation/webfinger.test.mjs +275 -12
  91. package/dist/{middleware-BW7WQltk.cjs → federation-Dfl-i7Jt.cjs} +6098 -895
  92. package/dist/{http-Dm0JAUa_.mjs → http-BAgKAAGY.mjs} +397 -168
  93. package/dist/http-CYO4wjB5.cjs +10630 -0
  94. package/dist/http-RW_i_Nkq.js +6527 -0
  95. package/dist/key-C1nv7_jD.d.ts +1125 -0
  96. package/dist/key-D979vduq.mjs +951 -0
  97. package/dist/key-DYxuyd-s.d.cts +1125 -0
  98. package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
  99. package/dist/keycache-Da8A8mZa.mjs +200 -0
  100. package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
  101. package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
  102. package/dist/kv-DBRWVx3Q.mjs +104 -0
  103. package/dist/{kv-cache-BxUZRBC2.js → kv-cache-Cqfdytdd.js} +54 -16
  104. package/dist/{kv-cache-B7H7lZ84.mjs → kv-cache-Gh05Xv-P.mjs} +1 -1
  105. package/dist/{kv-cache-D-hbjJAD.cjs → kv-cache-zd0dNGxi.cjs} +55 -19
  106. package/dist/{ld-DAPAVL_7.mjs → ld-wmqC-95l.mjs} +44 -12
  107. package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
  108. package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
  109. package/dist/{metrics-BMaatuZi.mjs → metrics-DPdi_XK7.mjs} +3 -2
  110. package/dist/metrics-DVKYY6K1.d.cts +36 -0
  111. package/dist/metrics-DoRtI0qU.d.ts +36 -0
  112. package/dist/{middleware-O2LHXFEk.mjs → middleware-CqQ5Qahj.mjs} +3155 -269
  113. package/dist/{middleware-CfZR_riY.js → middleware-DbCI_eni.js} +5527 -865
  114. package/dist/{middleware-DfPBAh9L.mjs → middleware-DjEMIAP5.mjs} +1 -1
  115. package/dist/{mod-DdONo-ep.d.cts → mod-BojP2Bw3.d.cts} +1 -1
  116. package/dist/mod-CavhAgLw.d.cts +320 -0
  117. package/dist/mod-CtpF2BU5.d.ts +320 -0
  118. package/dist/{mod-CNef4in0.d.ts → mod-DEZXwV6J.d.ts} +9 -3
  119. package/dist/{mod-BrpVsSfY.d.cts → mod-Dw-nPxQh.d.cts} +9 -3
  120. package/dist/{mod-BsCE5Ihq.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
  121. package/dist/mod.cjs +40 -39
  122. package/dist/mod.d.cts +10 -11
  123. package/dist/mod.d.ts +10 -11
  124. package/dist/mod.js +8 -8
  125. package/dist/nodeinfo/client.test.mjs +139 -4
  126. package/dist/nodeinfo/handler.test.mjs +5 -5
  127. package/dist/nodeinfo/mod.cjs +1 -1
  128. package/dist/nodeinfo/mod.d.cts +1 -1
  129. package/dist/nodeinfo/mod.d.ts +1 -1
  130. package/dist/nodeinfo/mod.js +1 -1
  131. package/dist/nodeinfo/types.test.mjs +4 -4
  132. package/dist/otel/exporter.test.mjs +3 -3
  133. package/dist/otel/mod.cjs +0 -1
  134. package/dist/otel/mod.d.cts +1 -1
  135. package/dist/otel/mod.d.ts +1 -1
  136. package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
  137. package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
  138. package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
  139. package/dist/owner-BfygwLNy.mjs +267 -0
  140. package/dist/portable-collection-DSrR0BSV.js +670 -0
  141. package/dist/portable-collection-L8jCHlci.cjs +686 -0
  142. package/dist/portable-collection-idA006Sj.mjs +418 -0
  143. package/dist/portable-key-CA6u-dST.mjs +549 -0
  144. package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
  145. package/dist/proof-DeC9pb6t.mjs +1299 -0
  146. package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
  147. package/dist/router-u5O5KCQr.mjs +156 -0
  148. package/dist/runtime/mod.cjs +1 -1
  149. package/dist/runtime/mod.js +1 -1
  150. package/dist/secured-document-CxzZZ9nL.mjs +35 -0
  151. package/dist/send-1tcGhW1h.mjs +495 -0
  152. package/dist/sig/accept.test.mjs +1 -1
  153. package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
  154. package/dist/sig/compound-proof-discovery.test.mjs +331 -0
  155. package/dist/sig/compound-proof-producer.test.d.mts +2 -0
  156. package/dist/sig/compound-proof-producer.test.mjs +107 -0
  157. package/dist/sig/compound-proof-verification.test.d.mts +2 -0
  158. package/dist/sig/compound-proof-verification.test.mjs +833 -0
  159. package/dist/sig/compound-proof.test.d.mts +2 -0
  160. package/dist/sig/compound-proof.test.mjs +411 -0
  161. package/dist/sig/http.test.mjs +702 -31
  162. package/dist/sig/key-owner-evidence.test.d.mts +2 -0
  163. package/dist/sig/key-owner-evidence.test.mjs +100 -0
  164. package/dist/sig/key.test.mjs +220 -8
  165. package/dist/sig/ld.test.mjs +65 -7
  166. package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
  167. package/dist/sig/mitra-receive-interop.test.mjs +260 -0
  168. package/dist/sig/mitra-send-interop.test.d.mts +2 -0
  169. package/dist/sig/mitra-send-interop.test.mjs +461 -0
  170. package/dist/sig/mod.cjs +18 -16
  171. package/dist/sig/mod.d.cts +4 -4
  172. package/dist/sig/mod.d.ts +4 -4
  173. package/dist/sig/mod.js +3 -3
  174. package/dist/sig/owner.test.mjs +108 -9
  175. package/dist/sig/portable-collection.test.d.mts +2 -0
  176. package/dist/sig/portable-collection.test.mjs +697 -0
  177. package/dist/sig/portable-interop.test.d.mts +2 -0
  178. package/dist/sig/portable-interop.test.mjs +156 -0
  179. package/dist/sig/portable-key-id.test.d.mts +2 -0
  180. package/dist/sig/portable-key-id.test.mjs +67 -0
  181. package/dist/sig/portable-key.test.d.mts +2 -0
  182. package/dist/sig/portable-key.test.mjs +1439 -0
  183. package/dist/sig/proof.test.mjs +1753 -16
  184. package/dist/sig/tootik-request-interop.test.d.mts +2 -0
  185. package/dist/sig/tootik-request-interop.test.mjs +190 -0
  186. package/dist/sig/tootik-send-interop.test.d.mts +2 -0
  187. package/dist/sig/tootik-send-interop.test.mjs +331 -0
  188. package/dist/sig/verification.test.d.mts +2 -0
  189. package/dist/sig/verification.test.mjs +310 -0
  190. package/dist/std__assert-DvMDRPes.mjs +8 -0
  191. package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
  192. package/dist/{temporal-yE5EcfNX.mjs → temporal-DA7r1fm3.mjs} +3 -3
  193. package/dist/testing/context.test.d.mts +2 -0
  194. package/dist/testing/context.test.mjs +19 -0
  195. package/dist/testing/kv.test.d.mts +2 -0
  196. package/dist/testing/kv.test.mjs +101 -0
  197. package/dist/testing/mod.d.mts +1604 -48
  198. package/dist/testing/mod.mjs +6 -3
  199. package/dist/testing-t8xDeJZ5.mjs +8 -0
  200. package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
  201. package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
  202. package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
  203. package/dist/utils/docloader.test.mjs +530 -8
  204. package/dist/utils/kv-cache.test.mjs +2 -2
  205. package/dist/utils/mod.cjs +1 -1
  206. package/dist/utils/mod.d.cts +1 -1
  207. package/dist/utils/mod.d.ts +1 -1
  208. package/dist/utils/mod.js +1 -1
  209. package/dist/verification-C3954hQh.mjs +5 -0
  210. package/dist/verification-Dpfcw8ml.mjs +105 -0
  211. package/dist/vocab/cjs.test.mjs +1 -1
  212. package/package.json +9 -9
  213. package/dist/assert_rejects-EgstcQy5.mjs +0 -82
  214. package/dist/docloader-BCecHIxU.mjs +0 -49
  215. package/dist/http-Ctb_KhZ8.js +0 -2483
  216. package/dist/http-VyDTd4G3.d.cts +0 -553
  217. package/dist/http-jr0TkgZe.cjs +0 -2694
  218. package/dist/http-lf8Hsd91.d.ts +0 -553
  219. package/dist/key-C_gNKtxm.mjs +0 -453
  220. package/dist/keycache-_DHqz5z_.mjs +0 -97
  221. package/dist/metrics-DN13mCAw.d.cts +0 -294
  222. package/dist/metrics-v_e0n5GQ.d.ts +0 -294
  223. package/dist/mod-B0hW12_O.d.cts +0 -307
  224. package/dist/mod-COIAjwRS.d.ts +0 -307
  225. package/dist/owner-Dcx-czqn.mjs +0 -120
  226. package/dist/proof-BkAxvrjt.mjs +0 -316
  227. package/dist/proof-D-H_PnJW.js +0 -1415
  228. package/dist/proof-NY0D8Oux.cjs +0 -1562
  229. package/dist/send-D_j7E-oC.mjs +0 -239
  230. package/dist/std__assert-B2NnY9bq.mjs +0 -79
  231. package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
  232. /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
  233. /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
  234. /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
  235. /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
  236. /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
  237. /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
  238. /package/dist/{mq-Bo7Etgh6.mjs → mq-Dm1Wmtm-.mjs} +0 -0
  239. /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
  240. /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
  241. /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
  242. /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
  243. /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
  244. /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
@@ -2,10 +2,10 @@ import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { ExportResultCode } from "@opentelemetry/core";
4
4
  import { Router } from "@fedify/uri-template";
5
- import { Activity, Actor, Collection, CryptographicKey, Hashtag, Link, LookupObjectOptions, Multikey, Note, Object as Object$1, Recipient, Tombstone, TraverseCollectionOptions } from "@fedify/vocab";
5
+ import { Activity, Actor, Collection, CryptographicKey, DataIntegrityProof, Hashtag, Link, LookupObjectOptions, Multikey, Note, Object as Object$1, Recipient, Tombstone, TraverseCollectionOptions } from "@fedify/vocab";
6
6
  import { Link as Link$1, LookupWebFingerOptions, ResourceDescriptor } from "@fedify/webfinger";
7
7
  import { MeterProvider, TracerProvider } from "@opentelemetry/api";
8
- import { AuthenticatedDocumentLoaderFactory, DocumentLoader, DocumentLoaderFactory, GetUserAgentOptions } from "@fedify/vocab-runtime";
8
+ import { AuthenticatedDocumentLoaderFactory, DocumentLoader, DocumentLoaderFactory, GetUserAgentOptions, PortableObjectVerifier } from "@fedify/vocab-runtime";
9
9
  import { MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
10
10
  import { StandardSchemaV1 } from "@standard-schema/spec";
11
11
 
@@ -169,6 +169,215 @@ interface GetNodeInfoOptions {
169
169
  userAgent?: GetUserAgentOptions | string;
170
170
  }
171
171
  //#endregion
172
+ //#region src/sig/proof.d.ts
173
+ /**
174
+ * The reason why {@link verifyPortableObjectProof} could not verify a portable
175
+ * object proof.
176
+ * @since 2.4.0
177
+ */
178
+ type VerifyPortableObjectProofFailureReason = {
179
+ /**
180
+ * The document has neither a portable `ap:` or `ap+ef61:` ID nor an
181
+ * FEP-ef61 compatible identifier.
182
+ */
183
+ readonly type: "notPortableObject";
184
+ } | {
185
+ /**
186
+ * The document is a portable collection without an Object Integrity
187
+ * Proof. Its trust policy is outside this verifier.
188
+ */
189
+ readonly type: "unsecuredCollection";
190
+ } | {
191
+ /** The portable document is a core type outside FEP-ef61 proof policy. */readonly type: "unsupportedObjectType"; /** The FEP-2277 core type of the document. */
192
+ readonly objectType: "verificationMethod" | "publicKey" | "link";
193
+ } | {
194
+ /** A portable actor, activity, or object has no proof. */readonly type: "missingProof";
195
+ } | {
196
+ /**
197
+ * A portable actor's `gateways` is missing or empty, or has an item that
198
+ * is not an HTTP(S) URI with an empty path, query, and fragment.
199
+ * The document is rejected before its proofs are verified, so this
200
+ * reason does not mean that the proofs are valid.
201
+ */
202
+ readonly type: "invalidGateways";
203
+ } | {
204
+ /** The proof is malformed, unsupported, or cryptographically invalid. */readonly type: "invalidProof"; /** The zero-based index of the invalid proof. */
205
+ readonly proofIndex: number;
206
+ } | {
207
+ /** The proof's verification method is not a valid DID URL. */readonly type: "unsupportedVerificationMethod"; /** The zero-based index of the proof. */
208
+ readonly proofIndex: number; /** The unsupported verification method. */
209
+ readonly verificationMethod: URL;
210
+ } | {
211
+ /** The verification method DID does not match the portable ID authority. */readonly type: "verificationMethodMismatch"; /** The zero-based index of the proof. */
212
+ readonly proofIndex: number; /** The portable object ID. */
213
+ readonly objectId: URL; /** The mismatching verification method. */
214
+ readonly verificationMethod: URL;
215
+ };
216
+ //#endregion
217
+ //#region src/sig/compound-proof.d.ts
218
+ /** Why compound-proof discovery could not produce an atomic result. */
219
+ type CompoundProofDiscoveryFailureReason = {
220
+ readonly type: "invalidJsonTree";
221
+ readonly path: string;
222
+ } | {
223
+ readonly type: "unsupportedProofShape";
224
+ readonly path: string;
225
+ } | {
226
+ readonly type: "limitExceeded";
227
+ readonly limit: "depth" | "maps" | "proofs" | "bytes";
228
+ readonly maximum: number;
229
+ readonly actual: number;
230
+ readonly path: string;
231
+ };
232
+ /** Why a discovered map-local proof did not verify. */
233
+ type CompoundProofVerificationFailureReason = {
234
+ /** A nested secured map does not carry its own JSON-LD context. */readonly type: "missingContext";
235
+ } | {
236
+ /** The direct proof is malformed, unsupported, or invalid. */readonly type: "invalidProof";
237
+ };
238
+ /** Why one portable map did not pass compound proof policy. */
239
+ type CompoundPortableObjectFailureReason = CompoundProofVerificationFailureReason | VerifyPortableObjectProofFailureReason | {
240
+ /** The portable map could not be interpreted by the policy layer. */readonly type: "invalidPortableObject";
241
+ };
242
+ //#endregion
243
+ //#region src/sig/http.d.ts
244
+ /**
245
+ * The standard to use for signing and verifying HTTP signatures.
246
+ * @since 1.6.0
247
+ */
248
+ type HttpMessageSignaturesSpec =
249
+ /**
250
+ * The Signing HTTP Messages (draft-cavage-http-signatures-12) specification,
251
+ * which is widely adopted and used in the fediverse (as of May 2025).
252
+ */
253
+ "draft-cavage-http-signatures-12"
254
+ /**
255
+ * The HTTP Message Signatures (RFC 9421) specification, which is the
256
+ * finalized standard but not widely adopted yet (as of May 2025).
257
+ */
258
+ | "rfc9421";
259
+ /**
260
+ * The reason why {@link verifyRequestDetailed} could not verify a request.
261
+ * @since 2.1.0
262
+ */
263
+ type VerifyRequestFailureReason = {
264
+ readonly type: "keyFetchError";
265
+ readonly keyId: URL;
266
+ readonly result: FetchKeyErrorResult;
267
+ } | {
268
+ readonly type: "invalidSignature";
269
+ readonly keyId?: URL;
270
+ } | {
271
+ readonly type: "noSignature";
272
+ };
273
+ //#endregion
274
+ //#region src/sig/verification.d.ts
275
+ /**
276
+ * A snapshot of a key actually used to check a signature.
277
+ * @since 2.4.0
278
+ */
279
+ type InboxVerificationKey = {
280
+ readonly type: "cryptographicKey";
281
+ readonly id: URL | null;
282
+ readonly ownerId: URL | null;
283
+ readonly publicKey: CryptoKey;
284
+ } | {
285
+ readonly type: "multikey";
286
+ readonly id: URL | null;
287
+ readonly controllerId: URL | null;
288
+ readonly publicKey: CryptoKey;
289
+ };
290
+ /**
291
+ * Why a cryptographic check failed.
292
+ * @since 2.4.0
293
+ */
294
+ type InboxSignatureFailureReason = {
295
+ readonly type: "invalidSignature";
296
+ } | {
297
+ readonly type: "keyFetchError";
298
+ readonly keyId: URL;
299
+ readonly result: FetchKeyErrorResult;
300
+ };
301
+ /**
302
+ * The signature or proof being checked.
303
+ * @since 2.4.0
304
+ */
305
+ type InboxSignatureIdentity = {
306
+ readonly mechanism: "http";
307
+ readonly spec: HttpMessageSignaturesSpec;
308
+ readonly label: string | null;
309
+ } | {
310
+ readonly mechanism: "linkedData";
311
+ } | {
312
+ readonly mechanism: "objectIntegrity";
313
+ readonly proofId: string | null; /** Index within this evaluation, not a global proof identifier. */
314
+ readonly proofIndex: number | null;
315
+ };
316
+ /**
317
+ * Evidence from one signature/proof, including cached-key retries.
318
+ * @since 2.4.0
319
+ */
320
+ type InboxSignatureCheck = InboxSignatureIdentity & {
321
+ /** The declaration as received, including invalid IRIs. */readonly declaredKeyId: string | null; /** Keys actually tried, in use order; refresh details are not a retry API. */
322
+ readonly triedKeys: readonly InboxVerificationKey[];
323
+ } & ({
324
+ readonly status: "verified";
325
+ readonly key: InboxVerificationKey;
326
+ } | {
327
+ readonly status: "rejected";
328
+ readonly reason: InboxSignatureFailureReason;
329
+ } | {
330
+ readonly status: "error";
331
+ readonly error: unknown;
332
+ });
333
+ /**
334
+ * A bounded proof-policy diagnostic.
335
+ * @since 2.4.0
336
+ */
337
+ type InboxProofPolicyFailureReason = VerifyPortableObjectProofFailureReason | CompoundPortableObjectFailureReason | CompoundProofDiscoveryFailureReason | {
338
+ readonly type: "invalidObjectId" | "invalidDocument" | "subjectMismatch";
339
+ };
340
+ /**
341
+ * Why a complete verification evaluation was rejected.
342
+ * @since 2.4.0
343
+ */
344
+ type InboxVerificationFailureReason = {
345
+ readonly type: "noSignature";
346
+ } | {
347
+ readonly type: "signatureVerificationFailed";
348
+ } | {
349
+ readonly type: "uncoveredAttribution";
350
+ readonly attributionIds: readonly URL[];
351
+ } | {
352
+ readonly type: "missingOwner";
353
+ } | {
354
+ readonly type: "proofPolicy";
355
+ readonly reason: InboxProofPolicyFailureReason;
356
+ };
357
+ /**
358
+ * A logical verification evaluation, separate from inbox authentication.
359
+ * @since 2.4.0
360
+ */
361
+ type InboxVerificationAttempt = {
362
+ readonly mechanism: "http" | "linkedData" | "objectIntegrity";
363
+ readonly subject: {
364
+ readonly id: URL | null; /** RFC 6901 pointer; empty for the root, null when unavailable. */
365
+ readonly pointer: string | null;
366
+ };
367
+ readonly checks: readonly InboxSignatureCheck[];
368
+ } & ({
369
+ readonly status: "verified"; /** Direct references to successful members of checks. */
370
+ readonly signatures: readonly Extract<InboxSignatureCheck, {
371
+ status: "verified";
372
+ }>[];
373
+ } | {
374
+ readonly status: "rejected";
375
+ readonly reason: InboxVerificationFailureReason;
376
+ } | {
377
+ readonly status: "error";
378
+ readonly error: unknown;
379
+ });
380
+ //#endregion
172
381
  //#region src/sig/key.d.ts
173
382
  /**
174
383
  * Detailed fetch failure information from {@link fetchKeyDetailed}.
@@ -203,43 +412,111 @@ interface GetKeyOwnerOptions {
203
412
  tracerProvider?: TracerProvider;
204
413
  }
205
414
  //#endregion
206
- //#region src/compat/types.d.ts
415
+ //#region src/federation/inbox-report.d.ts
207
416
  /**
208
- * A function that transforms an activity object.
209
- * @since 1.4.0
417
+ * A JSON value retained from the incoming request.
418
+ * @since 2.4.0
210
419
  */
211
- type ActivityTransformer<TContextData> = (activity: Activity, context: Context<TContextData>) => Activity;
212
- //#endregion
213
- //#region src/sig/http.d.ts
420
+ type InboxJsonValue = null | boolean | number | string | readonly InboxJsonValue[] | {
421
+ readonly [key: string]: InboxJsonValue;
422
+ };
214
423
  /**
215
- * The standard to use for signing and verifying HTTP signatures.
216
- * @since 1.6.0
424
+ * The final inbox authentication decision.
425
+ * @since 2.4.0
217
426
  */
218
- type HttpMessageSignaturesSpec =
427
+ type InboxAuthentication = {
428
+ readonly status: "verified"; /** Direct references to the attempts that authenticated this delivery. */
429
+ readonly attempts: readonly Extract<InboxVerificationAttempt, {
430
+ status: "verified";
431
+ }>[];
432
+ } | {
433
+ readonly status: "rejected";
434
+ readonly reason: {
435
+ readonly type: "verificationFailed";
436
+ } | {
437
+ readonly type: "actorKeyMismatch";
438
+ readonly key: InboxVerificationKey;
439
+ readonly actorIds: readonly URL[];
440
+ } | {
441
+ readonly type: "proofPolicy";
442
+ readonly policy: "portableActor" | "portableActivity" | "compound";
443
+ readonly detail?: InboxProofPolicyFailureReason;
444
+ } | {
445
+ readonly type: "invalidNonce";
446
+ };
447
+ } | {
448
+ readonly status: "skipped";
449
+ } | {
450
+ readonly status: "notDetermined";
451
+ };
219
452
  /**
220
- * The Signing HTTP Messages (draft-cavage-http-signatures-12) specification,
221
- * which is widely adopted and used in the fediverse (as of May 2025).
453
+ * How an inbox request finished.
454
+ * @since 2.4.0
222
455
  */
223
- "draft-cavage-http-signatures-12"
456
+ type InboxRequestOutcome = {
457
+ readonly type: "response";
458
+ readonly status: number;
459
+ readonly disposition: "processed" | "enqueued" | "duplicate" | "unhandled" | "customResponse";
460
+ } | {
461
+ readonly type: "response";
462
+ readonly status: number;
463
+ readonly disposition: "rejected";
464
+ readonly reason: "recipientNotFound" | "invalidJson" | "invalidJsonLd" | "invalidActivity" | "bodyTooLarge" | "missingActor" | "authentication";
465
+ } | {
466
+ readonly type: "response";
467
+ readonly status: number;
468
+ readonly disposition: "failed";
469
+ readonly reason: "bodyUnavailable" | "enqueueError" | "listenerError";
470
+ readonly error?: unknown;
471
+ } | {
472
+ readonly type: "exception";
473
+ readonly stage: "prepare" | "parse" | "verify" | "policy" | "dispatch" | "respond"; /** The original thrown value, including null/undefined. */
474
+ readonly error: unknown;
475
+ };
224
476
  /**
225
- * The HTTP Message Signatures (RFC 9421) specification, which is the
226
- * finalized standard but not widely adopted yet (as of May 2025).
477
+ * Observations of a single inbox delivery request.
478
+ * @since 2.4.0
227
479
  */
228
- | "rfc9421";
480
+ interface InboxRequestReport {
481
+ /** The delivery route, including a portable gateway route. */
482
+ readonly inbox: {
483
+ readonly kind: "personal" | "shared" | "portable";
484
+ readonly recipient: string | null;
485
+ };
486
+ /** Unavailable differs from a successfully parsed JSON null. */
487
+ readonly payload: {
488
+ readonly status: "unavailable";
489
+ } | {
490
+ readonly status: "parsed";
491
+ readonly value: InboxJsonValue;
492
+ };
493
+ /** The Activity obtained by the normal processing flow, if any. */
494
+ readonly activity: Activity | null;
495
+ /** Logical evaluations, including unsuccessful fallback paths. */
496
+ readonly attempts: readonly InboxVerificationAttempt[];
497
+ /** Cryptographic success alone does not imply this decision is verified. */
498
+ readonly authentication: InboxAuthentication;
499
+ /** Response status snapshot or the original exception. */
500
+ readonly outcome: InboxRequestOutcome;
501
+ }
229
502
  /**
230
- * The reason why {@link verifyRequestDetailed} could not verify a request.
231
- * @since 2.1.0
503
+ * Observes an inbox delivery after processing, before fetch resolves/rethrows.
504
+ * The callback is awaited. Its errors are logged and swallowed, preserving
505
+ * the original response/exception. The last registered callback replaces the
506
+ * previous one. It excludes inbox collection GETs, unmatched routes,
507
+ * programmatic routing, and queue workers. This is not an atomic persistence
508
+ * guarantee. Keys/owner/controller declarations remain untrusted unless the
509
+ * final authentication decision verifies them.
510
+ * @since 2.4.0
232
511
  */
233
- type VerifyRequestFailureReason = {
234
- readonly type: "keyFetchError";
235
- readonly keyId: URL;
236
- readonly result: FetchKeyErrorResult;
237
- } | {
238
- readonly type: "invalidSignature";
239
- readonly keyId?: URL;
240
- } | {
241
- readonly type: "noSignature";
242
- };
512
+ type InboxRequestFinishedHandler<TContextData> = (context: RequestContext<TContextData>, report: InboxRequestReport) => void | Promise<void>;
513
+ //#endregion
514
+ //#region src/compat/types.d.ts
515
+ /**
516
+ * A function that transforms an activity object.
517
+ * @since 1.4.0
518
+ */
519
+ type ActivityTransformer<TContextData> = (activity: Activity, context: Context<TContextData>) => Activity;
243
520
  //#endregion
244
521
  //#region src/federation/collection.d.ts
245
522
  /**
@@ -348,6 +625,25 @@ type ActorKeyPairsDispatcher<TContextData> = (context: Context<TContextData>, id
348
625
  * @since 0.15.0
349
626
  */
350
627
  type ActorHandleMapper<TContextData> = (context: Context<TContextData>, username: string) => string | null | Promise<string | null>;
628
+ /**
629
+ * A callback that maps an actor's internal identifier to the ID of the
630
+ * [FEP-ef61] portable actor it dispatches, or `null` if the actor is not
631
+ * portable.
632
+ *
633
+ * The returned ID is an `ap:` or `ap+ef61:` URI, e.g.,
634
+ * `ap+ef61://did:key:z6Mk.../actors/alice`, or its compatible identifier.
635
+ * It must be the same ID that the actor dispatcher puts in the actor's `id`,
636
+ * and must not have a fragment.
637
+ *
638
+ * [FEP-ef61]: https://w3id.org/fep/ef61
639
+ *
640
+ * @template TContextData The context data to pass to the {@link Context}.
641
+ * @param context The context.
642
+ * @param identifier The actor's internal identifier.
643
+ * @returns The portable actor's ID, or `null` if the actor is not portable.
644
+ * @since 2.4.0
645
+ */
646
+ type PortableActorIdMapper<TContextData> = (context: Context<TContextData>, identifier: string) => URL | null | Promise<URL | null>;
351
647
  /**
352
648
  * A callback that maps a WebFinger query to the corresponding actor's
353
649
  * internal identifier or username, or `null` if the query is not found.
@@ -368,14 +664,18 @@ type ActorAliasMapper<TContextData> = (context: RequestContext<TContextData>, re
368
664
  username: string;
369
665
  } | null>;
370
666
  /**
371
- * A callback that dispatches an object.
667
+ * A callback that dispatches an object or a {@link Tombstone}.
668
+ *
669
+ * Return a {@link Tombstone} if the object has been deleted; Fedify then
670
+ * responds with `410 Gone` and the serialized tombstone. Return `null` if
671
+ * the object is not found.
372
672
  *
373
673
  * @template TContextData The context data to pass to the {@link Context}.
374
674
  * @template TObject The type of object to dispatch.
375
675
  * @template TParam The parameter names of the requested URL.
376
676
  * @since 0.7.0
377
677
  */
378
- type ObjectDispatcher<TContextData, TObject extends Object$1, TParam extends string> = (context: RequestContext<TContextData>, values: Record<TParam, string>) => TObject | null | Promise<TObject | null>;
678
+ type ObjectDispatcher<TContextData, TObject extends Object$1, TParam extends string> = (context: RequestContext<TContextData>, values: Record<TParam, string>) => TObject | Tombstone | null | Promise<TObject | Tombstone | null>;
379
679
  /**
380
680
  * A callback that dispatches a collection.
381
681
  *
@@ -463,6 +763,60 @@ type OutboxListener<TContextData, TActivity extends Activity> = (context: Outbox
463
763
  * @since 2.4.0
464
764
  */
465
765
  type MediaUploaderCallback<TContextData> = (context: RequestContext<TContextData>, identifier: string, file: File, object: Object$1) => Object$1 | URL | Promise<Object$1 | URL>;
766
+ /**
767
+ * A request for a resource addressed by a hashlink, which an
768
+ * [FEP-ef61](https://w3id.org/fep/ef61) gateway serves at
769
+ * `/.well-known/apgateway/hl:<digestMultibase>`.
770
+ *
771
+ * Fedify validates the hashlink before creating this object, so the digest is
772
+ * always a well-formed SHA-256 multihash.
773
+ *
774
+ * @since 2.4.0
775
+ */
776
+ interface HashlinkMediaRequest {
777
+ /**
778
+ * The requested hashlink, i.e., `hl:` followed by
779
+ * {@link HashlinkMediaRequest.digestMultibase}, after percent-decoding and
780
+ * with its scheme lowercased.
781
+ */
782
+ readonly hashlink: `hl:${string}`;
783
+ /**
784
+ * The multibase-encoded multihash as requested, which is the value that
785
+ * publishers put in the `digestMultibase` property. The same digest can be
786
+ * encoded in several multibase encodings; use
787
+ * {@link HashlinkMediaRequest.digest} as a storage key to find a resource
788
+ * regardless of the encoding.
789
+ */
790
+ readonly digestMultibase: string;
791
+ /** The hash algorithm of the digest. */
792
+ readonly algorithm: "sha2-256";
793
+ /** The raw 32-byte SHA-256 digest of the resource. */
794
+ readonly digest: Uint8Array;
795
+ /**
796
+ * The multihash decoded from
797
+ * {@link HashlinkMediaRequest.digestMultibase}, i.e., the hash algorithm
798
+ * code and the digest length followed by the digest.
799
+ */
800
+ readonly multihash: Uint8Array;
801
+ }
802
+ /**
803
+ * A callback that serves a resource addressed by a hashlink through the
804
+ * [FEP-ef61](https://w3id.org/fep/ef61) gateway endpoint.
805
+ *
806
+ * The returned response is sent as is, so the callback can stream its body
807
+ * and set headers such as `Content-Type` and `Cache-Control`. Fedify does not
808
+ * verify the response body against the digest, so the complete
809
+ * representation must be the exact bytes that hash to the requested digest.
810
+ *
811
+ * @template TContextData The context data to pass to the {@link Context}.
812
+ * @param context The request context.
813
+ * @param media The requested hashlink and its digest.
814
+ * @returns The response to send, or `null` if the resource is not stored on
815
+ * this server, which results in `404 Not Found`. It may be returned
816
+ * synchronously or wrapped in a `Promise`.
817
+ * @since 2.4.0
818
+ */
819
+ type HashlinkMediaDispatcher<TContextData> = (context: RequestContext<TContextData>, media: HashlinkMediaRequest) => Response | null | Promise<Response | null>;
466
820
  /**
467
821
  * The reason why an incoming activity could not be verified.
468
822
  *
@@ -617,6 +971,28 @@ type ObjectAuthorizePredicate<TContextData, TParam extends string> = (context: R
617
971
  * @since 1.8.0
618
972
  */
619
973
  type CustomCollectionDispatcher<TItem, TParam extends string, TContext extends Context<TContextData>, TContextData> = (context: TContext, values: Record<TParam, string>, cursor: string | null) => PageItems<TItem> | null | Promise<PageItems<TItem> | null>;
974
+ /**
975
+ * A callback that maps a custom collection to the identifier of the actor
976
+ * that owns it, so that the collection can be served as an [FEP-ef61]
977
+ * portable collection through the gateway endpoint, e.g.,
978
+ * `GET /.well-known/apgateway/did:key:z6Mk.../users/alice/bookmarks`.
979
+ *
980
+ * Fedify serves such a request only if the returned actor is a portable actor
981
+ * whose ID is under the requested DID. The owner has to come from
982
+ * the application's data, not from the request, since the DID in the request
983
+ * path is not evidence that this server hosts collections for it.
984
+ *
985
+ * [FEP-ef61]: https://w3id.org/fep/ef61
986
+ *
987
+ * @template TContextData The context data to pass to the {@link Context}.
988
+ * @template TParam The parameter names of the requested URL.
989
+ * @param context The context.
990
+ * @param values The parameters of the requested URL.
991
+ * @returns The internal identifier of the actor that owns the collection, or
992
+ * `null` if the collection is not a portable collection.
993
+ * @since 2.4.0
994
+ */
995
+ type PortableCollectionOwnerMapper<TContextData, TParam extends string> = (context: Context<TContextData>, values: Record<TParam, string>) => string | null | Promise<string | null>;
620
996
  /**
621
997
  * A callback that counts the number of items in a custom collection.
622
998
  *
@@ -683,14 +1059,15 @@ interface KvStore {
683
1059
  /**
684
1060
  * Gets the value for the given key.
685
1061
  * @param key The key to get the value for.
686
- * @returns The value for the key, or `undefined` if the key does not exist.
1062
+ * @returns The value for the key, including `null` if stored, or `undefined`
1063
+ * if the key does not exist.
687
1064
  * @template T The type of the value to get.
688
1065
  */
689
1066
  get<T = unknown>(key: KvKey): Promise<T | undefined>;
690
1067
  /**
691
1068
  * Sets the value for the given key.
692
1069
  * @param key The key to set the value for.
693
- * @param value The value to set.
1070
+ * @param value The value to set. `null` is a storable value.
694
1071
  * @param options Additional options for setting the value.
695
1072
  */
696
1073
  set(key: KvKey, value: unknown, options?: KvStoreSetOptions): Promise<void>;
@@ -782,6 +1159,17 @@ type CircuitBreakerOptions = CircuitBreakerFailurePolicy & {
782
1159
  * @default `{ seconds: 1 }`
783
1160
  */
784
1161
  readonly releaseInterval?: Temporal.Duration | Temporal.DurationLike;
1162
+ /**
1163
+ * How long Fedify keeps circuit breaker state in the configured key-value
1164
+ * store.
1165
+ *
1166
+ * When omitted, Fedify derives this from `failureWindow`, `recoveryDelay`, and
1167
+ * `heldActivityTtl` for the default numeric failure policy. Custom `failure`
1168
+ * callbacks do not have an inspectable time window, so their state expires
1169
+ * after `recoveryDelay` plus `heldActivityTtl` unless this option overrides
1170
+ * it.
1171
+ */
1172
+ readonly stateTtl?: Temporal.Duration | Temporal.DurationLike;
785
1173
  /**
786
1174
  * Called whenever the circuit state changes.
787
1175
  */
@@ -1143,6 +1531,18 @@ interface FanoutMessage {
1143
1531
  readonly inboxes: Readonly<Record<string, {
1144
1532
  readonly actorIds: readonly string[];
1145
1533
  readonly sharedInbox: boolean;
1534
+ /**
1535
+ * The canonical form of the FEP-ef61 portable inbox that the inbox
1536
+ * stands for.
1537
+ * @internal
1538
+ */
1539
+ readonly portableInbox?: string;
1540
+ /**
1541
+ * The compatible identifiers of the portable inbox on each gateway to
1542
+ * deliver through, in order.
1543
+ * @internal
1544
+ */
1545
+ readonly gatewayInboxes?: readonly string[];
1146
1546
  }>>;
1147
1547
  readonly activity: unknown;
1148
1548
  readonly activityId?: string;
@@ -1186,6 +1586,27 @@ interface OutboxMessage {
1186
1586
  * @internal
1187
1587
  */
1188
1588
  readonly circuitHeldSince?: string;
1589
+ /**
1590
+ * The canonical form of the FEP-ef61 portable inbox that {@link inbox}, its
1591
+ * compatible identifier on a gateway, stands for.
1592
+ * @internal
1593
+ */
1594
+ readonly portableInbox?: string;
1595
+ /**
1596
+ * The compatible identifiers of the FEP-ef61 portable inbox on each gateway
1597
+ * that is still to be tried, in order. {@link inbox} is the first one.
1598
+ * If there is more than one, the activity is delivered through the first
1599
+ * gateway that accepts it.
1600
+ * @internal
1601
+ */
1602
+ readonly gatewayInboxes?: readonly string[];
1603
+ /**
1604
+ * The times before which each of {@link gatewayInboxes} must not be tried
1605
+ * again, as their responses' `Retry-After` headers requested, keyed by
1606
+ * the inbox URL, in ISO 8601.
1607
+ * @internal
1608
+ */
1609
+ readonly gatewayNotBefore?: Readonly<Record<string, string>>;
1189
1610
  readonly traceContext: Readonly<Record<string, string>>;
1190
1611
  }
1191
1612
  /**
@@ -1334,6 +1755,16 @@ interface FederationKvPrefixes {
1334
1755
  * @since 2.4.0
1335
1756
  */
1336
1757
  readonly taskDeduplication: KvKey;
1758
+ /**
1759
+ * The key prefix used for remembering which activities received in
1760
+ * [FEP-ef61] portable inboxes have been forwarded to which gateways, so that
1761
+ * each activity is forwarded at most once.
1762
+ *
1763
+ * [FEP-ef61]: https://w3id.org/fep/ef61
1764
+ * @default `["_fedify", "portableInboxForwarding"]`
1765
+ * @since 2.4.0
1766
+ */
1767
+ readonly portableInboxForwarding: KvKey;
1337
1768
  }
1338
1769
  /**
1339
1770
  * Options for {@link FederationOptions.origin} when it is not a string.
@@ -1431,7 +1862,9 @@ interface Federatable<TContextData> extends TaskRegistry<TContextData> {
1431
1862
  * based on URI Template
1432
1863
  * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
1433
1864
  * must have one or more variables.
1434
- * @param dispatcher An object dispatcher callback to register.
1865
+ * @param dispatcher An object dispatcher callback to register. It may
1866
+ * return an object, a `Tombstone` if the object has been
1867
+ * deleted, or `null` if the object is not found.
1435
1868
  */
1436
1869
  setObjectDispatcher<TObject extends Object$1, TParam extends string>(cls: ConstructorWithTypeId<TObject>, path: `${string}{${TParam}}${string}{${TParam}}${string}{${TParam}}${string}{${TParam}}${string}{${TParam}}${string}{${TParam}}${string}`, dispatcher: ObjectDispatcher<TContextData, TObject, TParam>): ObjectCallbackSetters<TContextData, TObject, TParam>;
1437
1870
  /**
@@ -1445,7 +1878,9 @@ interface Federatable<TContextData> extends TaskRegistry<TContextData> {
1445
1878
  * based on URI Template
1446
1879
  * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
1447
1880
  * must have one or more variables.
1448
- * @param dispatcher An object dispatcher callback to register.
1881
+ * @param dispatcher An object dispatcher callback to register. It may
1882
+ * return an object, a `Tombstone` if the object has been
1883
+ * deleted, or `null` if the object is not found.
1449
1884
  */
1450
1885
  setObjectDispatcher<TObject extends Object$1, TParam extends string>(cls: ConstructorWithTypeId<TObject>, path: `${string}{${TParam}}${string}{${TParam}}${string}{${TParam}}${string}{${TParam}}${string}{${TParam}}${string}`, dispatcher: ObjectDispatcher<TContextData, TObject, TParam>): ObjectCallbackSetters<TContextData, TObject, TParam>;
1451
1886
  /**
@@ -1459,7 +1894,9 @@ interface Federatable<TContextData> extends TaskRegistry<TContextData> {
1459
1894
  * based on URI Template
1460
1895
  * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
1461
1896
  * must have one or more variables.
1462
- * @param dispatcher An object dispatcher callback to register.
1897
+ * @param dispatcher An object dispatcher callback to register. It may
1898
+ * return an object, a `Tombstone` if the object has been
1899
+ * deleted, or `null` if the object is not found.
1463
1900
  */
1464
1901
  setObjectDispatcher<TObject extends Object$1, TParam extends string>(cls: ConstructorWithTypeId<TObject>, path: `${string}{${TParam}}${string}{${TParam}}${string}{${TParam}}${string}{${TParam}}${string}`, dispatcher: ObjectDispatcher<TContextData, TObject, TParam>): ObjectCallbackSetters<TContextData, TObject, TParam>;
1465
1902
  /**
@@ -1473,7 +1910,9 @@ interface Federatable<TContextData> extends TaskRegistry<TContextData> {
1473
1910
  * based on URI Template
1474
1911
  * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
1475
1912
  * must have one or more variables.
1476
- * @param dispatcher An object dispatcher callback to register.
1913
+ * @param dispatcher An object dispatcher callback to register. It may
1914
+ * return an object, a `Tombstone` if the object has been
1915
+ * deleted, or `null` if the object is not found.
1477
1916
  */
1478
1917
  setObjectDispatcher<TObject extends Object$1, TParam extends string>(cls: ConstructorWithTypeId<TObject>, path: `${string}${Rfc6570Expression<TParam>}${string}${Rfc6570Expression<TParam>}${string}${Rfc6570Expression<TParam>}${string}`, dispatcher: ObjectDispatcher<TContextData, TObject, TParam>): ObjectCallbackSetters<TContextData, TObject, TParam>;
1479
1918
  /**
@@ -1487,7 +1926,9 @@ interface Federatable<TContextData> extends TaskRegistry<TContextData> {
1487
1926
  * based on URI Template
1488
1927
  * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
1489
1928
  * must have one or more variables.
1490
- * @param dispatcher An object dispatcher callback to register.
1929
+ * @param dispatcher An object dispatcher callback to register. It may
1930
+ * return an object, a `Tombstone` if the object has been
1931
+ * deleted, or `null` if the object is not found.
1491
1932
  */
1492
1933
  setObjectDispatcher<TObject extends Object$1, TParam extends string>(cls: ConstructorWithTypeId<TObject>, path: `${string}${Rfc6570Expression<TParam>}${string}${Rfc6570Expression<TParam>}${string}`, dispatcher: ObjectDispatcher<TContextData, TObject, TParam>): ObjectCallbackSetters<TContextData, TObject, TParam>;
1493
1934
  /**
@@ -1501,7 +1942,9 @@ interface Federatable<TContextData> extends TaskRegistry<TContextData> {
1501
1942
  * based on URI Template
1502
1943
  * ([RFC 6570](https://tools.ietf.org/html/rfc6570)). The path
1503
1944
  * must have one or more variables.
1504
- * @param dispatcher An object dispatcher callback to register.
1945
+ * @param dispatcher An object dispatcher callback to register. It may
1946
+ * return an object, a `Tombstone` if the object has been
1947
+ * deleted, or `null` if the object is not found.
1505
1948
  */
1506
1949
  setObjectDispatcher<TObject extends Object$1, TParam extends string>(cls: ConstructorWithTypeId<TObject>, path: `${string}${Rfc6570Expression<TParam>}${string}`, dispatcher: ObjectDispatcher<TContextData, TObject, TParam>): ObjectCallbackSetters<TContextData, TObject, TParam>;
1507
1950
  /**
@@ -1608,6 +2051,53 @@ interface Federatable<TContextData> extends TaskRegistry<TContextData> {
1608
2051
  * @since 2.4.0
1609
2052
  */
1610
2053
  setMediaUploader(path: `${string}${Rfc6570Expression<"identifier">}${string}`, callback: MediaUploaderCallback<TContextData>): MediaUploaderSetters<TContextData>;
2054
+ /**
2055
+ * Registers a dispatcher that serves resources addressed by hashlinks, such
2056
+ * as media attached to portable objects, through the
2057
+ * [FEP-ef61](https://w3id.org/fep/ef61) gateway endpoint, e.g.,
2058
+ * `GET /.well-known/apgateway/hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n`.
2059
+ *
2060
+ * Fedify responds with `400 Bad Request` without calling the dispatcher if
2061
+ * the hashlink is malformed or its digest is not a SHA-256 multihash, and
2062
+ * with `404 Not Found` if the dispatcher returns `null`. Otherwise, the
2063
+ * response returned by the dispatcher is sent as is. Requests with methods
2064
+ * other than `GET` and `HEAD` get `405 Method Not Allowed`.
2065
+ *
2066
+ * Fedify does not verify the served bytes against the digest, as that would
2067
+ * require buffering the whole response body. The dispatcher must serve only
2068
+ * the resource whose complete representation hashes to the requested
2069
+ * digest, e.g., by using the digest as the storage key.
2070
+ *
2071
+ * This is not the upload API of the ActivityPub Media Upload extension,
2072
+ * which is registered through {@link Federatable.setMediaUploader}.
2073
+ *
2074
+ * @example
2075
+ * ``` typescript
2076
+ * federation.setHashlinkMediaDispatcher(async (ctx, media) => {
2077
+ * const file = await findPublicMedia(media.digest);
2078
+ * if (file == null) return null;
2079
+ * return new Response(
2080
+ * ctx.request.method === "HEAD" ? null : file.stream(),
2081
+ * {
2082
+ * headers: {
2083
+ * "Content-Type": file.mediaType,
2084
+ * "Content-Length": file.size.toString(),
2085
+ * "Cache-Control": "public, max-age=31536000, immutable",
2086
+ * "X-Content-Type-Options": "nosniff",
2087
+ * },
2088
+ * },
2089
+ * );
2090
+ * });
2091
+ * ```
2092
+ *
2093
+ * @param dispatcher A callback that returns the response serving the
2094
+ * requested resource, or `null` if it is not stored on
2095
+ * this server.
2096
+ * @throws {RouterError} Thrown if a hashlink media dispatcher is already
2097
+ * registered.
2098
+ * @since 2.4.0
2099
+ */
2100
+ setHashlinkMediaDispatcher(dispatcher: HashlinkMediaDispatcher<TContextData>): void;
1611
2101
  /**
1612
2102
  * Registers a following collection dispatcher.
1613
2103
  * @param path The URI path pattern for the following collection. The syntax
@@ -1865,6 +2355,47 @@ interface Federation<TContextData> extends Federatable<TContextData> {
1865
2355
  */
1866
2356
  fetch(request: Request, options: FederationFetchOptions<TContextData>): Promise<Response>;
1867
2357
  }
2358
+ /**
2359
+ * Options for forwarding activities received in [FEP-ef61] portable inboxes to
2360
+ * the other gateways of their actors. See
2361
+ * {@link FederationOptions.portableInboxForwarding}.
2362
+ *
2363
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2364
+ * @since 2.4.0
2365
+ */
2366
+ interface PortableInboxForwardingOptions {
2367
+ /**
2368
+ * The maximum number of other gateways that a single delivery is forwarded
2369
+ * to. Gateways beyond it are skipped with a warning. `0` turns off
2370
+ * forwarding.
2371
+ * @default `10`
2372
+ */
2373
+ maxTargets?: number;
2374
+ /**
2375
+ * How long Fedify remembers that it has forwarded an activity from
2376
+ * a portable inbox to a gateway, under
2377
+ * {@link FederationKvPrefixes.portableInboxForwarding}. Within it, the same
2378
+ * activity is never forwarded to the same gateway again; once it expires,
2379
+ * a redelivery of the activity may be forwarded again. It cannot have
2380
+ * calendar units, i.e., weeks, months, or years.
2381
+ * @default `{ days: 30 }`
2382
+ */
2383
+ ttl?: Temporal.DurationLike;
2384
+ /**
2385
+ * How long a delivery waits for forwarding requests made immediately, i.e.,
2386
+ * when no outbox queue is configured, before Fedify responds to it.
2387
+ * Requests still running afterwards continue in the background. It also
2388
+ * bounds verifying the HTTP Signature that identifies the gateway which
2389
+ * forwarded the delivery, with or without an outbox queue; if the
2390
+ * verification does not finish in time, the activity is forwarded to that
2391
+ * gateway as well. It cannot have calendar units,
2392
+ * i.e., weeks, months, or years, and cannot be longer than
2393
+ * 2,147,483,647 milliseconds (about 24.8 days), the longest delay timers
2394
+ * support.
2395
+ * @default `{ seconds: 10 }`
2396
+ */
2397
+ deadline?: Temporal.DurationLike;
2398
+ }
1868
2399
  /**
1869
2400
  * Policy for emitting `Accept-Signature` challenges on inbox `401`
1870
2401
  * responses, as defined in
@@ -1932,6 +2463,41 @@ interface FederationOptions<TContextData> {
1932
2463
  * are prefixed with `["_fedify"]`.
1933
2464
  */
1934
2465
  kvPrefixes?: Partial<FederationKvPrefixes>;
2466
+ /**
2467
+ * The time-to-live for a remote actor's public key cached under
2468
+ * {@link FederationKvPrefixes.publicKey}. Once it expires, the next
2469
+ * signature verification that needs the key refetches it from the remote
2470
+ * server and caches it again.
2471
+ *
2472
+ * Shortening it bounds how long a revoked or rotated key stays in the cache,
2473
+ * at the cost of more requests to remote servers; refetching an expired key
2474
+ * fails while the peer is unavailable, so a very short value makes
2475
+ * verification depend on the peer being reachable.
2476
+ * @default `{ days: 30 }`
2477
+ * @since 2.4.0
2478
+ */
2479
+ publicKeyTtl?: Temporal.DurationLike;
2480
+ /**
2481
+ * The time-to-live for a remote origin's remembered HTTP Message Signatures
2482
+ * spec cached under {@link FederationKvPrefixes.httpMessageSignaturesSpec}.
2483
+ * Once it expires, the next delivery to that origin relearns the spec by
2484
+ * double-knocking and remembers it again.
2485
+ *
2486
+ * Shortening it makes Fedify notice a peer's spec upgrade sooner, at the
2487
+ * cost of an extra signed request per delivery whenever the first spec tried
2488
+ * is rejected.
2489
+ * @default `{ days: 90 }`
2490
+ * @since 2.4.0
2491
+ */
2492
+ httpMessageSignaturesSpecTtl?: Temporal.DurationLike;
2493
+ /**
2494
+ * Options for forwarding activities received in [FEP-ef61] portable inboxes
2495
+ * to the other gateways of their actors.
2496
+ *
2497
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2498
+ * @since 2.4.0
2499
+ */
2500
+ portableInboxForwarding?: PortableInboxForwardingOptions;
1935
2501
  /**
1936
2502
  * The message queue for sending and receiving activities. If not provided,
1937
2503
  * activities will not be queued and will be processed immediately.
@@ -1983,7 +2549,8 @@ interface FederationOptions<TContextData> {
1983
2549
  */
1984
2550
  authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory;
1985
2551
  /**
1986
- * Whether to allow fetching private network addresses in the document loader.
2552
+ * Whether to allow private network addresses in the document loader and
2553
+ * outbound activity delivery, including redirects.
1987
2554
  *
1988
2555
  * If turned on, {@link FederationOptions.documentLoader},
1989
2556
  * {@link FederationOptions.contextLoader}, and
@@ -2019,6 +2586,35 @@ interface FederationOptions<TContextData> {
2019
2586
  * @since 1.3.0
2020
2587
  */
2021
2588
  userAgent?: GetUserAgentOptions | string;
2589
+ /**
2590
+ * The timeout for each call of the built-in document loader, context
2591
+ * loader, and authenticated document loader, e.g., when Fedify fetches
2592
+ * a key to verify a signature. The timeout is shared by all the steps of
2593
+ * a call, including every redirect it follows, retries, and reading the
2594
+ * response body. It does not cover the time spent reading from or
2595
+ * writing to the cache.
2596
+ *
2597
+ * A timed-out call throws a `FetchError` without a response, whose
2598
+ * `cause` is a `DOMException` named `"TimeoutError"`, so that, e.g.,
2599
+ * a key fetch that times out is reported as a `keyFetchError` and cached
2600
+ * like other failures to fetch a key.
2601
+ *
2602
+ * It does not affect loaders made by
2603
+ * {@link FederationOptions.documentLoaderFactory},
2604
+ * {@link FederationOptions.contextLoaderFactory}, or
2605
+ * {@link FederationOptions.authenticatedDocumentLoaderFactory}.
2606
+ * Note that if only {@link FederationOptions.documentLoaderFactory} is
2607
+ * set, it also makes context loaders.
2608
+ *
2609
+ * Set it to `null` to turn off the timeout.
2610
+ *
2611
+ * 10 seconds by default.
2612
+ * @throws {RangeError} If the duration is not positive, is longer than
2613
+ * about 24.8 days, or is given in calendar units
2614
+ * such as months.
2615
+ * @since 2.4.0
2616
+ */
2617
+ documentLoaderTimeout?: Temporal.Duration | Temporal.DurationLike | null;
2022
2618
  /**
2023
2619
  * A callback that handles errors during outbox processing. Note that this
2024
2620
  * callback can be called multiple times for the same activity, because
@@ -2048,8 +2644,31 @@ interface FederationOptions<TContextData> {
2048
2644
  */
2049
2645
  signatureTimeWindow?: Temporal.Duration | Temporal.DurationLike | false;
2050
2646
  /**
2051
- * Whether to skip HTTP Signatures verification for incoming activities.
2052
- * This is useful for testing purposes, but should not be used in production.
2647
+ * The maximum number of [RFC 9421] signatures of an incoming request to
2648
+ * verify, e.g., in the inbox and in
2649
+ * {@link RequestContext.getSignedKey}. A request can carry several
2650
+ * signatures, each of which may make Fedify fetch the key that it names,
2651
+ * so only the first ones in the order of the `Signature-Input` header are
2652
+ * verified, and the rest are ignored as if they were absent. Every
2653
+ * signature among the first ones counts, even if it fails before its key
2654
+ * is fetched. See also the `maxSignatures` option of `verifyRequest()`.
2655
+ *
2656
+ * It has to be a positive integer, or `Infinity` to verify every
2657
+ * signature, which lets a single request make Fedify fetch any number of
2658
+ * keys. Draft-cavage HTTP Signatures carry a single signature, so this
2659
+ * option does not affect them.
2660
+ *
2661
+ * Three by default.
2662
+ *
2663
+ * [RFC 9421]: https://www.rfc-editor.org/rfc/rfc9421
2664
+ * @throws {RangeError} Thrown when the federation is created if the value
2665
+ * is not a positive integer or `Infinity`.
2666
+ * @since 2.4.0
2667
+ */
2668
+ maxHttpSignatures?: number;
2669
+ /**
2670
+ * Whether to skip HTTP Signatures verification for incoming activities.
2671
+ * This is useful for testing purposes, but should not be used in production.
2053
2672
  *
2054
2673
  * By default, this is `false` (i.e., signatures are verified).
2055
2674
  * @since 0.13.0
@@ -2225,6 +2844,30 @@ interface ActorCallbackSetters<TContextData> {
2225
2844
  * @since 1.4.0
2226
2845
  */
2227
2846
  mapAlias(mapper: ActorAliasMapper<TContextData>): ActorCallbackSetters<TContextData>;
2847
+ /**
2848
+ * Sets the callback function that maps an actor's identifier to the ID of
2849
+ * the [FEP-ef61] portable actor it dispatches. If the callback returns
2850
+ * a portable ID for an identifier, the key pairs of that actor are treated
2851
+ * as this server's *gateway keys* for the portable actor:
2852
+ * {@link Context.getActorKeyPairs} derives their key IDs from the actor's
2853
+ * compatible identifier on this server, e.g.,
2854
+ * `https://example.com/.well-known/apgateway/did:key:z6Mk.../actors/alice#main-key`,
2855
+ * and makes the portable actor their owner, so that requests this server
2856
+ * makes on behalf of the actor, such as activity deliveries and signed
2857
+ * fetches, are signed with them. Gateway keys are only used for HTTP
2858
+ * Signatures; they never make Object Integrity Proofs or Linked Data
2859
+ * Signatures.
2860
+ *
2861
+ * If it's omitted, or it returns `null`, the actor's keys are derived from
2862
+ * {@link Context.getActorUri} as usual.
2863
+ *
2864
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2865
+ * @param mapper A callback that maps an actor's identifier to its portable
2866
+ * ID, or `null` if the actor is not portable.
2867
+ * @returns The setters object so that settings can be chained.
2868
+ * @since 2.4.0
2869
+ */
2870
+ mapPortableActorId(mapper: PortableActorIdMapper<TContextData>): ActorCallbackSetters<TContextData>;
2228
2871
  /**
2229
2872
  * Maps a fixed path to a sentinel identifier. It is useful for exposing
2230
2873
  * a single, instance-level actor at a fixed path, such as `/actor` for
@@ -2389,6 +3032,16 @@ interface InboxListenerSetters<TContextData> {
2389
3032
  * @returns The setters object so that settings can be chained.
2390
3033
  */
2391
3034
  onError(handler: InboxErrorHandler<TContextData>): InboxListenerSetters<TContextData>;
3035
+ /**
3036
+ * Observes each configured inbox delivery once, before fetch finishes.
3037
+ * The callback is awaited and its errors do not alter delivery processing.
3038
+ * Calling this again replaces the previous callback. Built federations
3039
+ * retain the callback registered at build time.
3040
+ * @param handler The request completion observer.
3041
+ * @returns This setter for chaining.
3042
+ * @since 2.4.0
3043
+ */
3044
+ onRequestFinished(handler: InboxRequestFinishedHandler<TContextData>): InboxListenerSetters<TContextData>;
2392
3045
  /**
2393
3046
  * Registers a callback for incoming activities whose HTTP signatures could
2394
3047
  * not be verified.
@@ -2528,6 +3181,21 @@ interface CustomCollectionCallbackSetters<TParam extends string, TContext extend
2528
3181
  * @since 0.7.0
2529
3182
  */
2530
3183
  authorize(predicate: ObjectAuthorizePredicate<TContextData, string>): CustomCollectionCallbackSetters<TParam, TContext, TContextData>;
3184
+ /**
3185
+ * Maps the custom collection to the actor that owns it, so that it is also
3186
+ * served as an [FEP-ef61] portable collection through the gateway endpoint,
3187
+ * e.g., `GET /.well-known/apgateway/did:key:z6Mk.../users/alice/bookmarks`,
3188
+ * if the owner is a portable actor under the requested DID. Without it,
3189
+ * the custom collection is not served through the gateway endpoint.
3190
+ *
3191
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3192
+ * @param mapper A callback that returns the identifier of the actor that
3193
+ * owns the collection, or `null` if the collection is not
3194
+ * portable.
3195
+ * @returns The setters object so that settings can be chained.
3196
+ * @since 2.4.0
3197
+ */
3198
+ mapPortableOwner(mapper: PortableCollectionOwnerMapper<TContextData, TParam>): CustomCollectionCallbackSetters<TParam, TContext, TContextData>;
2531
3199
  }
2532
3200
  /**
2533
3201
  * Represents an object with a type ID, which is either a constructor or an
@@ -2623,6 +3291,28 @@ interface Context<TContextData> {
2623
3291
  * The context loader for loading remote JSON-LD contexts.
2624
3292
  */
2625
3293
  readonly contextLoader: DocumentLoader;
3294
+ /**
3295
+ * The [FEP-ef61] portable object policy that this context applies to
3296
+ * portable objects it dereferences. It is `verifyPortableObject()` with
3297
+ * this context's document loader, context loader, and tracer provider as
3298
+ * defaults, which the options passed to it override.
3299
+ *
3300
+ * Since its name is the same as the `verifyPortableObject` option of
3301
+ * property accessors, `lookupObject()`, and `traverseCollection()`, passing
3302
+ * a context as their options applies the policy, e.g.,
3303
+ * `await create.getObject(ctx)`. Objects parsed with a context as options,
3304
+ * such as activities that inboxes receive, and objects that accessors
3305
+ * fetch with a context as options also use it by default for their
3306
+ * property accessors. It never verifies an object by itself being
3307
+ * there: it only applies to objects that are dereferenced.
3308
+ *
3309
+ * It is optional only so that custom implementations of this interface
3310
+ * keep working; contexts that Fedify creates always have it.
3311
+ *
3312
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3313
+ * @since 2.4.0
3314
+ */
3315
+ readonly verifyPortableObject?: PortableObjectVerifier;
2626
3316
  /**
2627
3317
  * The federation object that this context belongs to.
2628
3318
  * @since 1.6.0
@@ -2651,6 +3341,37 @@ interface Context<TContextData> {
2651
3341
  * @throws {RouterError} If no actor dispatcher is available.
2652
3342
  */
2653
3343
  getActorUri(identifier: string): URL;
3344
+ /**
3345
+ * Builds the [FEP-ef61] portable ID of an actor with the given identifier
3346
+ * under the given DID authority.
3347
+ *
3348
+ * The path is the same as the one {@link Context.getActorUri} builds, but
3349
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3350
+ * `ap+ef61://did:key:z6Mk.../users/alice`. Such an actor can be served
3351
+ * through the FEP-ef61 gateway endpoint,
3352
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice`, by the same actor
3353
+ * dispatcher.
3354
+ *
3355
+ * The returned `URL` keeps the DID authority percent-encoded, because
3356
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3357
+ * from `@fedify/vocab-runtime` to get the canonical string, e.g.,
3358
+ * `ap+ef61://did:key:z6Mk.../users/alice`.
3359
+ *
3360
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3361
+ *
3362
+ * @param identifier The actor's identifier.
3363
+ * @param authority The DID that controls the actor, e.g.,
3364
+ * `did:key:z6Mk...`. It must be a bare DID, without
3365
+ * a path, query, or fragment. A `did:key` DID must be
3366
+ * encoded in base58-btc, as FEP-ef61 requires; see
3367
+ * `exportDidKey()` from `@fedify/vocab-runtime`.
3368
+ * @returns The actor's portable ID.
3369
+ * @throws {RouterError} If no actor dispatcher is available.
3370
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3371
+ * DID that is not encoded in base58-btc.
3372
+ * @since 2.4.0
3373
+ */
3374
+ getPortableActorUri(identifier: string, authority: string): URL;
2654
3375
  /**
2655
3376
  * Builds the URI of an object with the given class and values.
2656
3377
  * @param cls The class of the object.
@@ -2661,6 +3382,67 @@ interface Context<TContextData> {
2661
3382
  * @since 0.7.0
2662
3383
  */
2663
3384
  getObjectUri<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>): URL;
3385
+ /**
3386
+ * Builds the [FEP-ef61] portable ID of an object with the given class and
3387
+ * values under the given DID authority.
3388
+ *
3389
+ * The path is the same as the one {@link Context.getObjectUri} builds from
3390
+ * the object dispatcher's path, but the result is an `ap+ef61:` URI whose
3391
+ * authority is the DID, e.g., `ap+ef61://did:key:z6Mk.../notes/123`.
3392
+ * Such an object can be served through the FEP-ef61 gateway endpoint,
3393
+ * `/.well-known/apgateway/did:key:z6Mk.../notes/123`, by the same object
3394
+ * dispatcher.
3395
+ *
3396
+ * The returned `URL` keeps the DID authority percent-encoded, because
3397
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3398
+ * from `@fedify/vocab-runtime` to get the canonical string, e.g.,
3399
+ * `ap+ef61://did:key:z6Mk.../notes/123`.
3400
+ *
3401
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3402
+ *
3403
+ * @param cls The class of the object.
3404
+ * @param values The values to pass to the object dispatcher.
3405
+ * @param authority The DID that controls the object, e.g.,
3406
+ * `did:key:z6Mk...`. It must be a bare DID, without
3407
+ * a path, query, or fragment.
3408
+ * @returns The object's portable ID.
3409
+ * @throws {RouterError} If no object dispatcher is available for the class.
3410
+ * @throws {TypeError} If values are invalid, or the authority is not a DID
3411
+ * or is a `did:key` DID that is not encoded in
3412
+ * base58-btc.
3413
+ * @since 2.4.0
3414
+ */
3415
+ getPortableObjectUri<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>, authority: string): URL;
3416
+ /**
3417
+ * Builds the [FEP-ef61] portable ID of an actor's inbox with the given
3418
+ * identifier under the given DID authority.
3419
+ *
3420
+ * The path is the same as the one {@link Context.getInboxUri} builds from
3421
+ * the inbox path, but the result is an `ap+ef61:` URI whose authority is
3422
+ * the DID, e.g., `ap+ef61://did:key:z6Mk.../users/alice/inbox`. If
3423
+ * a portable actor has it as its `inbox` and lists this server in its
3424
+ * `gateways`, this server accepts deliveries to the inbox through
3425
+ * the FEP-ef61 gateway endpoint, e.g.,
3426
+ * `POST /.well-known/apgateway/did:key:z6Mk.../users/alice/inbox`, and
3427
+ * dispatches them to the inbox listeners.
3428
+ *
3429
+ * The returned `URL` keeps the DID authority percent-encoded, because
3430
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3431
+ * from `@fedify/vocab-runtime` to get the canonical string.
3432
+ *
3433
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3434
+ *
3435
+ * @param identifier The actor's identifier.
3436
+ * @param authority The DID that controls the actor, e.g.,
3437
+ * `did:key:z6Mk...`. It must be a bare DID, without
3438
+ * a path, query, or fragment.
3439
+ * @returns The inbox's portable ID.
3440
+ * @throws {RouterError} If no inbox path is registered.
3441
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3442
+ * DID that is not encoded in base58-btc.
3443
+ * @since 2.4.0
3444
+ */
3445
+ getPortableInboxUri(identifier: string, authority: string): URL;
2664
3446
  /**
2665
3447
  * Builds the URI of an actor's outbox with the given identifier.
2666
3448
  * @param identifier The actor's identifier.
@@ -2668,6 +3450,35 @@ interface Context<TContextData> {
2668
3450
  * @throws {RouterError} If no outbox dispatcher is available.
2669
3451
  */
2670
3452
  getOutboxUri(identifier: string): URL;
3453
+ /**
3454
+ * Builds the [FEP-ef61] portable ID of an actor's outbox with the given
3455
+ * identifier under the given DID authority.
3456
+ *
3457
+ * The path is the same as the one {@link Context.getOutboxUri} builds, but
3458
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3459
+ * `ap+ef61://did:key:z6Mk.../users/alice/outbox`. If a portable actor has
3460
+ * it as its `outbox` and its ID is under the same DID, the collection is
3461
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3462
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/outbox`, by the same
3463
+ * collection dispatcher.
3464
+ *
3465
+ * The returned `URL` keeps the DID authority percent-encoded, because
3466
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3467
+ * from `@fedify/vocab-runtime` to get the canonical string.
3468
+ *
3469
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3470
+ *
3471
+ * @param identifier The actor's identifier.
3472
+ * @param authority The DID that controls the actor, e.g.,
3473
+ * `did:key:z6Mk...`. It must be a bare DID, without
3474
+ * a path, query, or fragment.
3475
+ * @returns The outbox's portable ID.
3476
+ * @throws {RouterError} If no outbox dispatcher is available.
3477
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3478
+ * DID that is not encoded in base58-btc.
3479
+ * @since 2.4.0
3480
+ */
3481
+ getPortableOutboxUri(identifier: string, authority: string): URL;
2671
3482
  /**
2672
3483
  * Builds the URI of an actor's media upload endpoint with the given
2673
3484
  * identifier. This is the endpoint advertised under `endpoints.uploadMedia`
@@ -2699,6 +3510,35 @@ interface Context<TContextData> {
2699
3510
  * @throws {RouterError} If no following collection is available.
2700
3511
  */
2701
3512
  getFollowingUri(identifier: string): URL;
3513
+ /**
3514
+ * Builds the [FEP-ef61] portable ID of an actor's following collection with the given
3515
+ * identifier under the given DID authority.
3516
+ *
3517
+ * The path is the same as the one {@link Context.getFollowingUri} builds, but
3518
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3519
+ * `ap+ef61://did:key:z6Mk.../users/alice/following`. If a portable actor has
3520
+ * it as its `following` and its ID is under the same DID, the collection is
3521
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3522
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/following`, by the same
3523
+ * collection dispatcher.
3524
+ *
3525
+ * The returned `URL` keeps the DID authority percent-encoded, because
3526
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3527
+ * from `@fedify/vocab-runtime` to get the canonical string.
3528
+ *
3529
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3530
+ *
3531
+ * @param identifier The actor's identifier.
3532
+ * @param authority The DID that controls the actor, e.g.,
3533
+ * `did:key:z6Mk...`. It must be a bare DID, without
3534
+ * a path, query, or fragment.
3535
+ * @returns The following collection's portable ID.
3536
+ * @throws {RouterError} If no following collection is available.
3537
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3538
+ * DID that is not encoded in base58-btc.
3539
+ * @since 2.4.0
3540
+ */
3541
+ getPortableFollowingUri(identifier: string, authority: string): URL;
2702
3542
  /**
2703
3543
  * Builds the URI of an actor's followers collection with the given
2704
3544
  * identifier.
@@ -2707,6 +3547,35 @@ interface Context<TContextData> {
2707
3547
  * @throws {RouterError} If no followers collection is available.
2708
3548
  */
2709
3549
  getFollowersUri(identifier: string): URL;
3550
+ /**
3551
+ * Builds the [FEP-ef61] portable ID of an actor's followers collection with the given
3552
+ * identifier under the given DID authority.
3553
+ *
3554
+ * The path is the same as the one {@link Context.getFollowersUri} builds, but
3555
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3556
+ * `ap+ef61://did:key:z6Mk.../users/alice/followers`. If a portable actor has
3557
+ * it as its `followers` and its ID is under the same DID, the collection is
3558
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3559
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/followers`, by the same
3560
+ * collection dispatcher.
3561
+ *
3562
+ * The returned `URL` keeps the DID authority percent-encoded, because
3563
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3564
+ * from `@fedify/vocab-runtime` to get the canonical string.
3565
+ *
3566
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3567
+ *
3568
+ * @param identifier The actor's identifier.
3569
+ * @param authority The DID that controls the actor, e.g.,
3570
+ * `did:key:z6Mk...`. It must be a bare DID, without
3571
+ * a path, query, or fragment.
3572
+ * @returns The followers collection's portable ID.
3573
+ * @throws {RouterError} If no followers collection is available.
3574
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3575
+ * DID that is not encoded in base58-btc.
3576
+ * @since 2.4.0
3577
+ */
3578
+ getPortableFollowersUri(identifier: string, authority: string): URL;
2710
3579
  /**
2711
3580
  * Builds the URI of an actor's liked collection with the given identifier.
2712
3581
  * @param identifier The actor's identifier.
@@ -2715,6 +3584,35 @@ interface Context<TContextData> {
2715
3584
  * @since 0.11.0
2716
3585
  */
2717
3586
  getLikedUri(identifier: string): URL;
3587
+ /**
3588
+ * Builds the [FEP-ef61] portable ID of an actor's liked collection with the given
3589
+ * identifier under the given DID authority.
3590
+ *
3591
+ * The path is the same as the one {@link Context.getLikedUri} builds, but
3592
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3593
+ * `ap+ef61://did:key:z6Mk.../users/alice/liked`. If a portable actor has
3594
+ * it as its `liked` and its ID is under the same DID, the collection is
3595
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3596
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/liked`, by the same
3597
+ * collection dispatcher.
3598
+ *
3599
+ * The returned `URL` keeps the DID authority percent-encoded, because
3600
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3601
+ * from `@fedify/vocab-runtime` to get the canonical string.
3602
+ *
3603
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3604
+ *
3605
+ * @param identifier The actor's identifier.
3606
+ * @param authority The DID that controls the actor, e.g.,
3607
+ * `did:key:z6Mk...`. It must be a bare DID, without
3608
+ * a path, query, or fragment.
3609
+ * @returns The liked collection's portable ID.
3610
+ * @throws {RouterError} If no liked collection is available.
3611
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3612
+ * DID that is not encoded in base58-btc.
3613
+ * @since 2.4.0
3614
+ */
3615
+ getPortableLikedUri(identifier: string, authority: string): URL;
2718
3616
  /**
2719
3617
  * Builds the URI of an actor's featured collection with the given identifier.
2720
3618
  * @param identifier The actor's identifier.
@@ -2723,6 +3621,35 @@ interface Context<TContextData> {
2723
3621
  * @since 0.11.0
2724
3622
  */
2725
3623
  getFeaturedUri(identifier: string): URL;
3624
+ /**
3625
+ * Builds the [FEP-ef61] portable ID of an actor's featured collection with the given
3626
+ * identifier under the given DID authority.
3627
+ *
3628
+ * The path is the same as the one {@link Context.getFeaturedUri} builds, but
3629
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3630
+ * `ap+ef61://did:key:z6Mk.../users/alice/featured`. If a portable actor has
3631
+ * it as its `featured` and its ID is under the same DID, the collection is
3632
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3633
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/featured`, by the same
3634
+ * collection dispatcher.
3635
+ *
3636
+ * The returned `URL` keeps the DID authority percent-encoded, because
3637
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3638
+ * from `@fedify/vocab-runtime` to get the canonical string.
3639
+ *
3640
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3641
+ *
3642
+ * @param identifier The actor's identifier.
3643
+ * @param authority The DID that controls the actor, e.g.,
3644
+ * `did:key:z6Mk...`. It must be a bare DID, without
3645
+ * a path, query, or fragment.
3646
+ * @returns The featured collection's portable ID.
3647
+ * @throws {RouterError} If no featured collection is available.
3648
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3649
+ * DID that is not encoded in base58-btc.
3650
+ * @since 2.4.0
3651
+ */
3652
+ getPortableFeaturedUri(identifier: string, authority: string): URL;
2726
3653
  /**
2727
3654
  * Builds the URI of an actor's featured tags collection with the given
2728
3655
  * identifier.
@@ -2732,14 +3659,69 @@ interface Context<TContextData> {
2732
3659
  * @since 0.11.0
2733
3660
  */
2734
3661
  getFeaturedTagsUri(identifier: string): URL;
3662
+ /**
3663
+ * Builds the [FEP-ef61] portable ID of an actor's featured tags collection with the given
3664
+ * identifier under the given DID authority.
3665
+ *
3666
+ * The path is the same as the one {@link Context.getFeaturedTagsUri} builds, but
3667
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3668
+ * `ap+ef61://did:key:z6Mk.../users/alice/tags`. If a portable actor has
3669
+ * it as its `featuredTags` and its ID is under the same DID, the collection is
3670
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3671
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/tags`, by the same
3672
+ * collection dispatcher.
3673
+ *
3674
+ * The returned `URL` keeps the DID authority percent-encoded, because
3675
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3676
+ * from `@fedify/vocab-runtime` to get the canonical string.
3677
+ *
3678
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3679
+ *
3680
+ * @param identifier The actor's identifier.
3681
+ * @param authority The DID that controls the actor, e.g.,
3682
+ * `did:key:z6Mk...`. It must be a bare DID, without
3683
+ * a path, query, or fragment.
3684
+ * @returns The featured tags collection's portable ID.
3685
+ * @throws {RouterError} If no featured tags collection is available.
3686
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3687
+ * DID that is not encoded in base58-btc.
3688
+ * @since 2.4.0
3689
+ */
3690
+ getPortableFeaturedTagsUri(identifier: string, authority: string): URL;
2735
3691
  /**
2736
3692
  * Determines the type of the URI and extracts the associated data.
3693
+ *
3694
+ * By default, only URIs on this server's origin are recognized. With
3695
+ * the `portable` option, [FEP-ef61] portable IDs, e.g.,
3696
+ * `ap+ef61://did:key:z6Mk.../users/alice`, and their compatible
3697
+ * identifiers on any gateway, e.g.,
3698
+ * `https://example.com/.well-known/apgateway/did:key:z6Mk.../users/alice`,
3699
+ * are also recognized by the same paths that the gateway endpoint routes
3700
+ * to the dispatchers, and the result has the DID in its `authority`
3701
+ * property.
3702
+ *
3703
+ * The DID of a portable ID is anyone's to choose, so recognizing it does
3704
+ * not mean that this server hosts anything for the DID. Check that
3705
+ * `authority` is the DID that the application stores for the actor or
3706
+ * object before acting on it:
3707
+ *
3708
+ * ~~~~ typescript
3709
+ * const parsed = ctx.parseUri(follow.objectId, { portable: true });
3710
+ * if (parsed?.type !== "actor") return;
3711
+ * const user = await getUser(parsed.identifier);
3712
+ * if (user == null) return;
3713
+ * if (parsed.authority != null && parsed.authority !== user.did) return;
3714
+ * ~~~~
3715
+ *
3716
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3717
+ *
2737
3718
  * @param uri The URI to parse.
3719
+ * @param options Options for parsing the URI. Since 2.4.0.
2738
3720
  * @returns The result of parsing the URI. If `null` is given or
2739
3721
  * the URI is not recognized, `null` is returned.
2740
3722
  * @since 0.9.0
2741
3723
  */
2742
- parseUri(uri: URL | null): ParseUriResult | null;
3724
+ parseUri(uri: URL | null, options?: ParseUriOptions): ParseUriResult | null;
2743
3725
  /**
2744
3726
  * Gets the key pairs for an actor.
2745
3727
  * @param identifier The actor's identifier.
@@ -2807,7 +3789,16 @@ interface Context<TContextData> {
2807
3789
  * ```
2808
3790
  *
2809
3791
  * It's almost the same as the {@link lookupObject} function, but it uses
2810
- * the context's document loader and context loader by default.
3792
+ * the context's document loader and context loader by default. It also
3793
+ * uses {@link Context.verifyPortableObject} as the `verifyPortableObject`
3794
+ * option by default (since 2.4.0), so [FEP-ef61] portable objects,
3795
+ * including portable actors found through WebFinger, are looked up through
3796
+ * their gateways and returned only if they have valid proofs, or, for
3797
+ * collections without proofs, if they are served by gateways that their
3798
+ * owners list. The returned object uses the same policy by default for
3799
+ * its property accessors.
3800
+ *
3801
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2811
3802
  *
2812
3803
  * @param identifier The URI or fediverse handle to look up.
2813
3804
  * @param options Lookup options.
@@ -2831,7 +3822,9 @@ interface Context<TContextData> {
2831
3822
  * ```
2832
3823
  *
2833
3824
  * It's almost the same as the {@link traverseCollection} function, but it
2834
- * uses the context's document loader and context loader by default.
3825
+ * uses the context's document loader and context loader by default. It
3826
+ * also uses {@link Context.verifyPortableObject} as the
3827
+ * `verifyPortableObject` option by default (since 2.4.0).
2835
3828
  * @param collection The collection to traverse.
2836
3829
  * @param options Options for traversing the collection.
2837
3830
  * @returns An async iterable of each item in the collection.
@@ -2980,6 +3973,61 @@ interface Context<TContextData> {
2980
3973
  * @since 1.8.0
2981
3974
  */
2982
3975
  getCollectionUri<TParam extends Record<string, string>>(name: string | symbol, values: TParam): URL;
3976
+ /**
3977
+ * Builds the [FEP-ef61] portable ID of a custom collection with the given
3978
+ * name and values under the given DID authority.
3979
+ *
3980
+ * The path is the same as the one {@link Context.getCollectionUri} builds,
3981
+ * but the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3982
+ * `ap+ef61://did:key:z6Mk.../users/alice/bookmarks`. If the collection
3983
+ * dispatcher maps the collection to a portable actor under the same DID
3984
+ * with `CustomCollectionCallbackSetters.mapPortableOwner()`, the
3985
+ * collection is served through the FEP-ef61 gateway endpoint, e.g.,
3986
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/bookmarks`.
3987
+ *
3988
+ * The returned `URL` keeps the DID authority percent-encoded, because
3989
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3990
+ * from `@fedify/vocab-runtime` to get the canonical string.
3991
+ *
3992
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3993
+ *
3994
+ * @param name The name of the collection, which can be a string or a symbol.
3995
+ * @param values The values of the URI parameters.
3996
+ * @param authority The DID that controls the collection, e.g.,
3997
+ * `did:key:z6Mk...`. It must be a bare DID, without
3998
+ * a path, query, or fragment.
3999
+ * @returns The collection's portable ID.
4000
+ * @throws {RouterError} If no collection dispatcher is available for
4001
+ * the name.
4002
+ * @throws {TypeError} If values are invalid, or the authority is not a DID
4003
+ * or is a `did:key` DID that is not encoded in
4004
+ * base58-btc.
4005
+ * @since 2.4.0
4006
+ */
4007
+ getPortableCollectionUri<TParam extends Record<string, string>>(name: string | symbol, values: TParam, authority: string): URL;
4008
+ }
4009
+ /**
4010
+ * Information about an [FEP-ef61] portable object requested through
4011
+ * the gateway endpoint. See {@link RequestContext.portableRequest}.
4012
+ *
4013
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4014
+ * @since 2.4.0
4015
+ */
4016
+ interface PortableRequest {
4017
+ /**
4018
+ * The DID authority of the requested portable object, e.g.,
4019
+ * `did:key:z6Mk...`. It comes from the request path, so it is not
4020
+ * evidence that this server hosts objects for the DID.
4021
+ */
4022
+ readonly authority: string;
4023
+ /**
4024
+ * The ID of the requested portable object or collection, e.g.,
4025
+ * `ap+ef61://did:key:z6Mk.../notes/123`, without a query. Like other
4026
+ * portable IDs, the `URL` keeps the DID authority percent-encoded; use
4027
+ * `formatIri()` from `@fedify/vocab-runtime` to get the canonical string.
4028
+ * Each access returns a new `URL` instance.
4029
+ */
4030
+ readonly id: URL;
2983
4031
  }
2984
4032
  /**
2985
4033
  * Options for {@link RequestContext.getActor}.
@@ -2994,6 +4042,23 @@ interface GetActorOptions {
2994
4042
  */
2995
4043
  readonly tombstone?: "suppress" | "passthrough";
2996
4044
  }
4045
+ /**
4046
+ * Options for {@link RequestContext.getObject}.
4047
+ * @since 2.4.0
4048
+ */
4049
+ interface GetObjectOptions {
4050
+ /**
4051
+ * Controls how tombstoned objects are returned.
4052
+ *
4053
+ * By default, or if set to `"suppress"`, a {@link Tombstone} is returned as
4054
+ * `null`, unless it is an instance of the requested class, e.g., when
4055
+ * the requested class is `Object` or `Tombstone` itself. In that case,
4056
+ * the tombstone is returned as is, as it is an object of the requested
4057
+ * class. Set this to `"passthrough"` to always receive a {@link Tombstone}
4058
+ * result instead of `null`.
4059
+ */
4060
+ readonly tombstone?: "suppress" | "passthrough";
4061
+ }
2997
4062
  /**
2998
4063
  * A context for a request.
2999
4064
  */
@@ -3006,6 +4071,243 @@ interface RequestContext<TContextData> extends Context<TContextData> {
3006
4071
  * The URL of the request.
3007
4072
  */
3008
4073
  readonly url: URL;
4074
+ /**
4075
+ * Information about the [FEP-ef61] portable object requested through
4076
+ * the gateway endpoint, e.g.,
4077
+ * `GET /.well-known/apgateway/did:key:z6Mk.../notes/123`, which is served
4078
+ * by an object dispatcher, the actor dispatcher, or a collection
4079
+ * dispatcher. It is `undefined` for ordinary requests.
4080
+ *
4081
+ * The authority comes from the request path, so it is not evidence that
4082
+ * this server hosts objects for the DID. An object dispatcher or the actor
4083
+ * dispatcher has to check that by itself. A portable collection is served
4084
+ * only if its owner, which the actor dispatcher looks up without this
4085
+ * property, is a portable actor under the DID.
4086
+ *
4087
+ * It describes the incoming request, so it stays the same in contexts
4088
+ * derived from this one, e.g., by {@link RequestContext.getObject}.
4089
+ *
4090
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4091
+ * @since 2.4.0
4092
+ */
4093
+ readonly portableRequest?: PortableRequest;
4094
+ /**
4095
+ * Builds the [FEP-ef61] portable ID of an actor with the given identifier.
4096
+ * It works the same as {@link Context.getPortableActorUri}, except that
4097
+ * the authority can be omitted while handling a portable gateway request.
4098
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
4099
+ *
4100
+ * The DID in the request comes from the request path, so the default is
4101
+ * only a convenience for building IDs; it is not evidence that this server
4102
+ * hosts the actor for the DID.
4103
+ *
4104
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4105
+ *
4106
+ * @param identifier The actor's identifier.
4107
+ * @param authority The DID that controls the actor, e.g.,
4108
+ * `did:key:z6Mk...`. It must be a bare DID, without
4109
+ * a path, query, or fragment. Defaults to the DID of
4110
+ * the requested portable object.
4111
+ * @returns The actor's portable ID.
4112
+ * @throws {RouterError} If no actor dispatcher is available.
4113
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
4114
+ * that is not encoded in base58-btc, or the authority
4115
+ * is omitted outside a portable gateway request.
4116
+ * @since 2.4.0
4117
+ */
4118
+ getPortableActorUri(identifier: string, authority?: string): URL;
4119
+ /**
4120
+ * Builds the [FEP-ef61] portable ID of an object with the given class and
4121
+ * values. It works the same as {@link Context.getPortableObjectUri}, except
4122
+ * that the authority can be omitted while handling a portable gateway
4123
+ * request. In that case, the DID in {@link RequestContext.portableRequest}
4124
+ * is used.
4125
+ *
4126
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4127
+ *
4128
+ * @param cls The class of the object.
4129
+ * @param values The values to pass to the object dispatcher.
4130
+ * @param authority The DID that controls the object, e.g.,
4131
+ * `did:key:z6Mk...`. It must be a bare DID, without
4132
+ * a path, query, or fragment. Defaults to the DID of
4133
+ * the requested portable object.
4134
+ * @returns The object's portable ID.
4135
+ * @throws {RouterError} If no object dispatcher is available for the class.
4136
+ * @throws {TypeError} If values are invalid, the authority is not a DID,
4137
+ * it is a `did:key` DID that is not encoded in
4138
+ * base58-btc, or the authority is omitted outside
4139
+ * a portable gateway request.
4140
+ * @since 2.4.0
4141
+ */
4142
+ getPortableObjectUri<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>, authority?: string): URL;
4143
+ /**
4144
+ * Builds the [FEP-ef61] portable ID of an actor's inbox with the given
4145
+ * identifier. It works the same as {@link Context.getPortableInboxUri},
4146
+ * except that the authority can be omitted while handling a portable object
4147
+ * request, i.e., when {@link RequestContext.portableRequest} is set. In
4148
+ * that case, its DID is used. Deliveries to portable inboxes do not set
4149
+ * {@link RequestContext.portableRequest}, so pass the authority explicitly
4150
+ * while handling them.
4151
+ *
4152
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4153
+ *
4154
+ * @param identifier The actor's identifier.
4155
+ * @param authority The DID that controls the actor, e.g.,
4156
+ * `did:key:z6Mk...`. It must be a bare DID, without
4157
+ * a path, query, or fragment. Defaults to the DID of
4158
+ * the requested portable object, if any.
4159
+ * @returns The inbox's portable ID.
4160
+ * @throws {RouterError} If no inbox path is registered.
4161
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
4162
+ * that is not encoded in base58-btc, or the authority
4163
+ * is omitted outside a portable gateway request.
4164
+ * @since 2.4.0
4165
+ */
4166
+ getPortableInboxUri(identifier: string, authority?: string): URL;
4167
+ /**
4168
+ * Builds the [FEP-ef61] portable ID of an actor's outbox with the given
4169
+ * identifier. It works the same as {@link Context.getPortableOutboxUri}, except that
4170
+ * the authority can be omitted while handling a portable gateway request.
4171
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
4172
+ *
4173
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4174
+ *
4175
+ * @param identifier The actor's identifier.
4176
+ * @param authority The DID that controls the actor, e.g.,
4177
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
4178
+ * portable object or collection.
4179
+ * @returns The outbox's portable ID.
4180
+ * @throws {RouterError} If no outbox dispatcher is available.
4181
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
4182
+ * that is not encoded in base58-btc, or the authority
4183
+ * is omitted outside a portable gateway request.
4184
+ * @since 2.4.0
4185
+ */
4186
+ getPortableOutboxUri(identifier: string, authority?: string): URL;
4187
+ /**
4188
+ * Builds the [FEP-ef61] portable ID of an actor's following collection with the given
4189
+ * identifier. It works the same as {@link Context.getPortableFollowingUri}, except that
4190
+ * the authority can be omitted while handling a portable gateway request.
4191
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
4192
+ *
4193
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4194
+ *
4195
+ * @param identifier The actor's identifier.
4196
+ * @param authority The DID that controls the actor, e.g.,
4197
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
4198
+ * portable object or collection.
4199
+ * @returns The following collection's portable ID.
4200
+ * @throws {RouterError} If no following collection is available.
4201
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
4202
+ * that is not encoded in base58-btc, or the authority
4203
+ * is omitted outside a portable gateway request.
4204
+ * @since 2.4.0
4205
+ */
4206
+ getPortableFollowingUri(identifier: string, authority?: string): URL;
4207
+ /**
4208
+ * Builds the [FEP-ef61] portable ID of an actor's followers collection with the given
4209
+ * identifier. It works the same as {@link Context.getPortableFollowersUri}, except that
4210
+ * the authority can be omitted while handling a portable gateway request.
4211
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
4212
+ *
4213
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4214
+ *
4215
+ * @param identifier The actor's identifier.
4216
+ * @param authority The DID that controls the actor, e.g.,
4217
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
4218
+ * portable object or collection.
4219
+ * @returns The followers collection's portable ID.
4220
+ * @throws {RouterError} If no followers collection is available.
4221
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
4222
+ * that is not encoded in base58-btc, or the authority
4223
+ * is omitted outside a portable gateway request.
4224
+ * @since 2.4.0
4225
+ */
4226
+ getPortableFollowersUri(identifier: string, authority?: string): URL;
4227
+ /**
4228
+ * Builds the [FEP-ef61] portable ID of an actor's liked collection with the given
4229
+ * identifier. It works the same as {@link Context.getPortableLikedUri}, except that
4230
+ * the authority can be omitted while handling a portable gateway request.
4231
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
4232
+ *
4233
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4234
+ *
4235
+ * @param identifier The actor's identifier.
4236
+ * @param authority The DID that controls the actor, e.g.,
4237
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
4238
+ * portable object or collection.
4239
+ * @returns The liked collection's portable ID.
4240
+ * @throws {RouterError} If no liked collection is available.
4241
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
4242
+ * that is not encoded in base58-btc, or the authority
4243
+ * is omitted outside a portable gateway request.
4244
+ * @since 2.4.0
4245
+ */
4246
+ getPortableLikedUri(identifier: string, authority?: string): URL;
4247
+ /**
4248
+ * Builds the [FEP-ef61] portable ID of an actor's featured collection with the given
4249
+ * identifier. It works the same as {@link Context.getPortableFeaturedUri}, except that
4250
+ * the authority can be omitted while handling a portable gateway request.
4251
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
4252
+ *
4253
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4254
+ *
4255
+ * @param identifier The actor's identifier.
4256
+ * @param authority The DID that controls the actor, e.g.,
4257
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
4258
+ * portable object or collection.
4259
+ * @returns The featured collection's portable ID.
4260
+ * @throws {RouterError} If no featured collection is available.
4261
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
4262
+ * that is not encoded in base58-btc, or the authority
4263
+ * is omitted outside a portable gateway request.
4264
+ * @since 2.4.0
4265
+ */
4266
+ getPortableFeaturedUri(identifier: string, authority?: string): URL;
4267
+ /**
4268
+ * Builds the [FEP-ef61] portable ID of an actor's featured tags collection with the given
4269
+ * identifier. It works the same as {@link Context.getPortableFeaturedTagsUri}, except that
4270
+ * the authority can be omitted while handling a portable gateway request.
4271
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
4272
+ *
4273
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4274
+ *
4275
+ * @param identifier The actor's identifier.
4276
+ * @param authority The DID that controls the actor, e.g.,
4277
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
4278
+ * portable object or collection.
4279
+ * @returns The featured tags collection's portable ID.
4280
+ * @throws {RouterError} If no featured tags collection is available.
4281
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
4282
+ * that is not encoded in base58-btc, or the authority
4283
+ * is omitted outside a portable gateway request.
4284
+ * @since 2.4.0
4285
+ */
4286
+ getPortableFeaturedTagsUri(identifier: string, authority?: string): URL;
4287
+ /**
4288
+ * Builds the [FEP-ef61] portable ID of a custom collection with the given
4289
+ * name and values. It works the same as
4290
+ * {@link Context.getPortableCollectionUri}, except that the authority can
4291
+ * be omitted while handling a portable gateway request. In that case,
4292
+ * the DID in {@link RequestContext.portableRequest} is used.
4293
+ *
4294
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4295
+ *
4296
+ * @param name The name of the collection, which can be a string or a symbol.
4297
+ * @param values The values of the URI parameters.
4298
+ * @param authority The DID that controls the collection, e.g.,
4299
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
4300
+ * portable object or collection.
4301
+ * @returns The collection's portable ID.
4302
+ * @throws {RouterError} If no collection dispatcher is available for
4303
+ * the name.
4304
+ * @throws {TypeError} If values are invalid, the authority is not a DID,
4305
+ * it is a `did:key` DID that is not encoded in
4306
+ * base58-btc, or the authority is omitted outside
4307
+ * a portable gateway request.
4308
+ * @since 2.4.0
4309
+ */
4310
+ getPortableCollectionUri<TParam extends Record<string, string>>(name: string | symbol, values: TParam, authority?: string): URL;
3009
4311
  /**
3010
4312
  * Creates a new context with the same properties as this one,
3011
4313
  * but with the given data.
@@ -3068,12 +4370,61 @@ interface RequestContext<TContextData> extends Context<TContextData> {
3068
4370
  * @param cls The class to instantiate.
3069
4371
  * @param values The values to pass to the object dispatcher.
3070
4372
  * @returns The object of the given class with the given values, or `null`
3071
- * if the object is not found.
4373
+ * if the object is not found. Tombstoned objects are suppressed
4374
+ * unless the tombstone is an instance of `cls`.
3072
4375
  * @throws {Error} If no object dispatcher is available for the class.
3073
4376
  * @throws {TypeError} If values are invalid.
3074
4377
  * @since 0.7.0
3075
4378
  */
3076
4379
  getObject<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>): Promise<TObject | null>;
4380
+ /**
4381
+ * Gets an object of the given class, or a {@link Tombstone}, with the given
4382
+ * values.
4383
+ * @param cls The class to instantiate.
4384
+ * @param values The values to pass to the object dispatcher.
4385
+ * @param options Options for getting the object. Set `options.tombstone`
4386
+ * to `"passthrough"` to receive tombstoned objects instead
4387
+ * of `null`.
4388
+ * @returns The object of the given class with the given values,
4389
+ * a tombstone, or `null` if the object is not found.
4390
+ * @throws {Error} If no object dispatcher is available for the class.
4391
+ * @throws {TypeError} If values are invalid.
4392
+ * @since 2.4.0
4393
+ */
4394
+ getObject<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>, options: GetObjectOptions & {
4395
+ readonly tombstone: "passthrough";
4396
+ }): Promise<TObject | Tombstone | null>;
4397
+ /**
4398
+ * Gets an object of the given class with the given values.
4399
+ * @param cls The class to instantiate.
4400
+ * @param values The values to pass to the object dispatcher.
4401
+ * @param options Options for getting the object.
4402
+ * @returns The object of the given class with the given values, or `null`
4403
+ * if the object is not found. Tombstoned objects are suppressed
4404
+ * unless `options.tombstone` is `"passthrough"` or the tombstone
4405
+ * is an instance of `cls`.
4406
+ * @throws {Error} If no object dispatcher is available for the class.
4407
+ * @throws {TypeError} If values are invalid.
4408
+ * @since 2.4.0
4409
+ */
4410
+ getObject<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>, options: GetObjectOptions & {
4411
+ readonly tombstone?: "suppress" | undefined;
4412
+ }): Promise<TObject | null>;
4413
+ /**
4414
+ * Gets an object of the given class, or a {@link Tombstone}, with the given
4415
+ * values.
4416
+ * @param cls The class to instantiate.
4417
+ * @param values The values to pass to the object dispatcher.
4418
+ * @param options Options for getting the object.
4419
+ * @returns The object of the given class with the given values,
4420
+ * a tombstone, or `null` if the object is not found. This broad
4421
+ * overload is used when the caller passes an options value whose
4422
+ * `tombstone` mode is not known statically.
4423
+ * @throws {Error} If no object dispatcher is available for the class.
4424
+ * @throws {TypeError} If values are invalid.
4425
+ * @since 2.4.0
4426
+ */
4427
+ getObject<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>, options: GetObjectOptions): Promise<TObject | Tombstone | null>;
3077
4428
  /**
3078
4429
  * Gets the public key of the sender, if any exists and it is verified.
3079
4430
  * Otherwise, `null` is returned.
@@ -3140,6 +4491,52 @@ interface RequestContext<TContextData> extends Context<TContextData> {
3140
4491
  * [instance actor]: https://swicg.github.io/activitypub-http-signature/#instance-actor
3141
4492
  */
3142
4493
  getSignedKeyOwner(options: GetKeyOwnerOptions): Promise<Actor | null>;
4494
+ /**
4495
+ * Checks whether the request is signed by an actor who belongs to
4496
+ * the intended audience of an object, i.e., the owner of the signed key
4497
+ * (see {@link RequestContext.getSignedKeyOwner}) is one of the object's
4498
+ * `to`, `cc`, `bto`, `bcc`, and `audience`, or a member of one of them
4499
+ * according to the `isMember` option.
4500
+ *
4501
+ * Everyone belongs to the audience of a publicly addressed object, so it
4502
+ * returns `true` for such an object even if the request is not signed.
4503
+ *
4504
+ * This can be used in authorization predicates for serving non-public
4505
+ * objects, which [FEP-ef61] requires of gateways serving portable objects.
4506
+ * [FEP-ef61] portable IDs are compared by their canonical forms, so
4507
+ * an `ap:` URI, an `ap+ef61:` URI, and a compatible identifier on any
4508
+ * gateway refer to the same actor.
4509
+ *
4510
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4511
+ *
4512
+ * @param object The object whose audience is checked.
4513
+ * @param options Options for checking the audience.
4514
+ * @returns `true` if the object is publicly addressed, or the request is
4515
+ * signed by an actor who belongs to its audience.
4516
+ * @since 2.4.0
4517
+ */
4518
+ isSignedByAudience(object: Object$1, options?: IsSignedByAudienceOptions): Promise<boolean>;
4519
+ }
4520
+ /**
4521
+ * Options for {@link RequestContext.isSignedByAudience} method.
4522
+ * @since 2.4.0
4523
+ */
4524
+ interface IsSignedByAudienceOptions extends GetKeyOwnerOptions {
4525
+ /**
4526
+ * Checks whether the actor who signed the request is a member of
4527
+ * an addressee of the object that is not the actor itself, e.g.,
4528
+ * a followers collection. Fedify does not resolve collections by itself,
4529
+ * as only the application can tell their members reliably.
4530
+ *
4531
+ * It is called for each such addressee in order until it returns `true`.
4532
+ * If it is omitted, only the actors that the object addresses directly
4533
+ * belong to its audience.
4534
+ * @param addressee The ID of the addressee, e.g., the ID of a followers
4535
+ * collection.
4536
+ * @param actor The actor who signed the request.
4537
+ * @returns `true` if the actor is a member of the addressee.
4538
+ */
4539
+ isMember?: (addressee: URL, actor: Actor) => boolean | Promise<boolean>;
3143
4540
  }
3144
4541
  /**
3145
4542
  * A context for inbox listeners.
@@ -3268,6 +4665,30 @@ interface OutboxContext<TContextData> extends Context<TContextData> {
3268
4665
  */
3269
4666
  clone(data: TContextData): OutboxContext<TContextData>;
3270
4667
  }
4668
+ /**
4669
+ * Options for {@link Context.parseUri}.
4670
+ * @since 2.4.0
4671
+ */
4672
+ interface ParseUriOptions {
4673
+ /**
4674
+ * Whether to also recognize [FEP-ef61] portable IDs, i.e., `ap:` and
4675
+ * `ap+ef61:` URIs, and their compatible identifiers on any gateway.
4676
+ * The result of a portable ID has its DID, without percent-encoding, in
4677
+ * the `authority` property, which the caller has to compare with the DID
4678
+ * that it stores for the actor or object, since anyone can make a portable
4679
+ * ID with the same path under another DID.
4680
+ *
4681
+ * Like URIs on this server's origin, a portable ID is recognized by its
4682
+ * path, and its query, e.g., location hints, and fragment are ignored.
4683
+ * A shared inbox is never recognized in a portable ID, and a portable ID
4684
+ * with a malformed DID, or with a `did:key` DID that is not encoded in
4685
+ * base58-btc, is not recognized either.
4686
+ *
4687
+ * [FEP-ef61]: https://w3id.org/fep/ef61
4688
+ * @default `false`
4689
+ */
4690
+ readonly portable?: boolean;
4691
+ }
3271
4692
  /**
3272
4693
  * A result of parsing an URI.
3273
4694
  */
@@ -3278,6 +4699,12 @@ type ParseUriResult =
3278
4699
  {
3279
4700
  readonly type: "actor";
3280
4701
  readonly identifier: string;
4702
+ /**
4703
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4704
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4705
+ * @since 2.4.0
4706
+ */
4707
+ readonly authority?: string;
3281
4708
  }
3282
4709
  /**
3283
4710
  * The case of an object URI.
@@ -3287,6 +4714,12 @@ type ParseUriResult =
3287
4714
  readonly class: ConstructorWithTypeId<Object$1>;
3288
4715
  readonly typeId: URL;
3289
4716
  readonly values: Record<string, string>;
4717
+ /**
4718
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4719
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4720
+ * @since 2.4.0
4721
+ */
4722
+ readonly authority?: string;
3290
4723
  }
3291
4724
  /**
3292
4725
  * The case of an shared inbox URI.
@@ -3294,6 +4727,7 @@ type ParseUriResult =
3294
4727
  | {
3295
4728
  readonly type: "inbox";
3296
4729
  readonly identifier: undefined;
4730
+ readonly authority?: undefined;
3297
4731
  }
3298
4732
  /**
3299
4733
  * The case of an personal inbox URI.
@@ -3301,6 +4735,12 @@ type ParseUriResult =
3301
4735
  | {
3302
4736
  readonly type: "inbox";
3303
4737
  readonly identifier: string;
4738
+ /**
4739
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4740
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4741
+ * @since 2.4.0
4742
+ */
4743
+ readonly authority?: string;
3304
4744
  }
3305
4745
  /**
3306
4746
  * The case of an outbox collection URI.
@@ -3308,6 +4748,12 @@ type ParseUriResult =
3308
4748
  | {
3309
4749
  readonly type: "outbox";
3310
4750
  readonly identifier: string;
4751
+ /**
4752
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4753
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4754
+ * @since 2.4.0
4755
+ */
4756
+ readonly authority?: string;
3311
4757
  }
3312
4758
  /**
3313
4759
  * The case of a following collection URI.
@@ -3315,6 +4761,12 @@ type ParseUriResult =
3315
4761
  | {
3316
4762
  readonly type: "following";
3317
4763
  readonly identifier: string;
4764
+ /**
4765
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4766
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4767
+ * @since 2.4.0
4768
+ */
4769
+ readonly authority?: string;
3318
4770
  }
3319
4771
  /**
3320
4772
  * The case of a followers collection URI.
@@ -3322,6 +4774,12 @@ type ParseUriResult =
3322
4774
  | {
3323
4775
  readonly type: "followers";
3324
4776
  readonly identifier: string;
4777
+ /**
4778
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4779
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4780
+ * @since 2.4.0
4781
+ */
4782
+ readonly authority?: string;
3325
4783
  }
3326
4784
  /**
3327
4785
  * The case of a liked collection URI.
@@ -3330,6 +4788,12 @@ type ParseUriResult =
3330
4788
  | {
3331
4789
  readonly type: "liked";
3332
4790
  readonly identifier: string;
4791
+ /**
4792
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4793
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4794
+ * @since 2.4.0
4795
+ */
4796
+ readonly authority?: string;
3333
4797
  }
3334
4798
  /**
3335
4799
  * The case of a featured collection URI.
@@ -3338,6 +4802,12 @@ type ParseUriResult =
3338
4802
  | {
3339
4803
  readonly type: "featured";
3340
4804
  readonly identifier: string;
4805
+ /**
4806
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4807
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4808
+ * @since 2.4.0
4809
+ */
4810
+ readonly authority?: string;
3341
4811
  }
3342
4812
  /**
3343
4813
  * The case of a featured tags collection URI.
@@ -3346,6 +4816,12 @@ type ParseUriResult =
3346
4816
  | {
3347
4817
  readonly type: "featuredTags";
3348
4818
  readonly identifier: string;
4819
+ /**
4820
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4821
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4822
+ * @since 2.4.0
4823
+ */
4824
+ readonly authority?: string;
3349
4825
  }
3350
4826
  /**
3351
4827
  * The case of a custom collection URI.
@@ -3357,6 +4833,12 @@ type ParseUriResult =
3357
4833
  readonly class: ConstructorWithTypeId<Object$1>;
3358
4834
  readonly typeId: URL;
3359
4835
  readonly values: Record<string, string>;
4836
+ /**
4837
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4838
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4839
+ * @since 2.4.0
4840
+ */
4841
+ readonly authority?: string;
3360
4842
  }
3361
4843
  /**
3362
4844
  * The case of a custom ordered collection URI.
@@ -3368,6 +4850,12 @@ type ParseUriResult =
3368
4850
  readonly class: ConstructorWithTypeId<Object$1>;
3369
4851
  readonly typeId: URL;
3370
4852
  readonly values: Record<string, string>;
4853
+ /**
4854
+ * The DID of the portable ID, e.g., `did:key:z6Mk...`, or `undefined`
4855
+ * for a URI on this server's origin. See {@link ParseUriOptions.portable}.
4856
+ * @since 2.4.0
4857
+ */
4858
+ readonly authority?: string;
3371
4859
  };
3372
4860
  /**
3373
4861
  * Options for {@link Context.sendActivity} method.
@@ -3553,6 +5041,48 @@ declare function createOutboxContext<TContextData>(args: Partial<OutboxContext<T
3553
5041
  federation: Federation<TContextData>;
3554
5042
  }): OutboxContext<TContextData>;
3555
5043
  //#endregion
5044
+ //#region src/testing/kv.d.ts
5045
+ /**
5046
+ * A {@link KvStore} decorator whose entry expiry is driven by a virtual clock
5047
+ * instead of wall-clock time.
5048
+ *
5049
+ * {@link MemoryKvStore} decides expiry by comparing timestamps rather than by
5050
+ * scheduling timers, so a test that wants to observe a TTL elapsing would
5051
+ * otherwise have to sleep for longer than the TTL and hope the runner keeps
5052
+ * up. That makes such tests both slow and load-sensitive.
5053
+ *
5054
+ * This decorator keeps the TTLs itself and never passes them to the store it
5055
+ * wraps, so nothing expires until {@link advance} moves the clock. Cache
5056
+ * hits, expiry, and repopulation therefore all become deterministic, without
5057
+ * patching any global and without a test-only entry point in the library
5058
+ * itself. The clock starts at the Unix epoch and only ever moves forward.
5059
+ */
5060
+ declare class ManualClockKvStore implements KvStore {
5061
+ #private;
5062
+ /**
5063
+ * Present only when the wrapped store has its own `cas()`. Callers such as
5064
+ * the task deduplication planner test `kv.cas != null` to decide whether a
5065
+ * conditional write is available, so the decorator must not advertise a
5066
+ * capability the wrapped store lacks.
5067
+ */
5068
+ readonly cas?: (key: KvKey, expectedValue: unknown, newValue: unknown, options?: KvStoreSetOptions) => Promise<boolean>;
5069
+ constructor(inner?: KvStore);
5070
+ /**
5071
+ * The current reading of the virtual clock.
5072
+ */
5073
+ get now(): Temporal.Instant;
5074
+ /**
5075
+ * Moves the virtual clock forward. Any entry whose TTL has elapsed by the
5076
+ * new time reads as missing from this point on, exactly as it would once a
5077
+ * real `KvStore` had let it expire.
5078
+ */
5079
+ advance(duration: Temporal.DurationLike): void;
5080
+ get<T = unknown>(key: KvKey): Promise<T | undefined>;
5081
+ set(key: KvKey, value: unknown, options?: KvStoreSetOptions): Promise<void>;
5082
+ delete(key: KvKey): Promise<void>;
5083
+ list(prefix?: KvKey): AsyncIterable<KvStoreListEntry>;
5084
+ }
5085
+ //#endregion
3556
5086
  //#region src/testing/tasks.d.ts
3557
5087
  /** Federation options (sans `queue`) shared by the task suites. */
3558
5088
  declare const baseOptions: Omit<FederationOptions<void>, "queue">;
@@ -3611,6 +5141,32 @@ declare class MockQueue implements MessageQueue {
3611
5141
  //#region ../fixture/dist/mod.d.ts
3612
5142
  //#endregion
3613
5143
  //#region src/test.d.ts
3614
- declare const testDefinitions: Deno.TestDefinition[];
5144
+ /** A test callback that uses only APIs supported by every test runtime. */
5145
+ type TestFunction = (t: TestContext) => void | Promise<void>;
5146
+ /** A nested test step supported by Deno, Node.js, Bun, and Workers. */
5147
+ interface TestStepDefinition {
5148
+ name: string;
5149
+ ignore?: boolean;
5150
+ fn: TestFunction;
5151
+ }
5152
+ /** The common test context supported by every test runtime. */
5153
+ interface TestContext {
5154
+ readonly name: string;
5155
+ readonly origin: string;
5156
+ step(definition: TestStepDefinition): Promise<boolean>;
5157
+ step(name: string, fn: TestFunction): Promise<boolean>;
5158
+ step(fn: TestFunction): Promise<boolean>;
5159
+ }
5160
+ /** A test definition supported by every test runtime. */
5161
+ interface TestDefinition {
5162
+ name: string;
5163
+ ignore?: boolean;
5164
+ permissions?: Deno.TestDefinition["permissions"];
5165
+ sanitizeExit?: Deno.TestDefinition["sanitizeExit"];
5166
+ sanitizeOps?: Deno.TestDefinition["sanitizeOps"];
5167
+ sanitizeResources?: Deno.TestDefinition["sanitizeResources"];
5168
+ fn: TestFunction;
5169
+ }
5170
+ declare const testDefinitions: TestDefinition[];
3615
5171
  //#endregion
3616
- export { type Envelope, MockQueue, type MockQueueOptions, baseOptions, createInboxContext, createOutboxContext, createRequestContext, envelopeSchema, makeSchema, numberSchema, stringSchema, testDefinitions };
5172
+ export { type Envelope, ManualClockKvStore, MockQueue, type MockQueueOptions, baseOptions, createInboxContext, createOutboxContext, createRequestContext, envelopeSchema, makeSchema, numberSchema, stringSchema, testDefinitions };