@fedify/fedify 2.4.0-dev.2141 → 2.4.0-dev.2155

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 (85) hide show
  1. package/dist/{builder-BI7H61zO.mjs → builder-CxHALSwA.mjs} +56 -3
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-D0H4LLvQ.mjs → compound-proof-nhrXTRQO.mjs} +216 -4
  6. package/dist/{context-CrDDVrbF.d.cts → context-BMggDR-l.d.cts} +394 -5
  7. package/dist/{context-DfSVPcJ8.mjs → context-OrnjnuMq.mjs} +8 -1
  8. package/dist/{context-B2C8nuN3.d.ts → context-exYu7gDU.d.ts} +394 -5
  9. package/dist/{deno-BXI_BLNy.mjs → deno-YLLbRq06.mjs} +1 -1
  10. package/dist/{docloader-B5BIo7e9.mjs → docloader-vc-pSu9-.mjs} +2 -2
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/compound-fanout.test.mjs +3 -3
  13. package/dist/federation/handler-compound-proof.test.mjs +4 -4
  14. package/dist/federation/handler.test.mjs +6 -6
  15. package/dist/federation/hashlink-media.test.mjs +2 -2
  16. package/dist/federation/idempotency.test.mjs +2 -2
  17. package/dist/federation/metrics.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +8 -8
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +3 -3
  21. package/dist/federation/mod.d.ts +3 -3
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/outgoing-proof.test.mjs +5 -5
  24. package/dist/federation/portable-actor.test.mjs +3 -3
  25. package/dist/federation/portable-chain.test.mjs +3 -3
  26. package/dist/federation/portable-collection.test.d.mts +2 -0
  27. package/dist/federation/portable-collection.test.mjs +768 -0
  28. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  29. package/dist/federation/portable-inbox.test.mjs +4 -4
  30. package/dist/federation/portable.test.mjs +4 -4
  31. package/dist/federation/send.test.mjs +3 -3
  32. package/dist/federation/tasks/enqueue.test.mjs +2 -2
  33. package/dist/federation/tasks/tasks.test.mjs +3 -3
  34. package/dist/federation/temporal.test.mjs +1 -1
  35. package/dist/federation/webfinger.test.mjs +2 -2
  36. package/dist/{federation-DqDOPjWo.cjs → federation-D9TufmIO.cjs} +684 -68
  37. package/dist/{http-CEx2SZjf.mjs → http-CXnNbRe_.mjs} +4 -4
  38. package/dist/{http-D4KW2GTi.js → http-Dk4O3WM0.js} +7 -2
  39. package/dist/{http-DSYQQn23.cjs → http-eqYXHRkz.cjs} +12 -1
  40. package/dist/{key-C0giKuki.mjs → key-BZ-Z4uez.mjs} +2 -2
  41. package/dist/{kv-cache-D_E-2K3i.js → kv-cache-3uKc0G76.js} +1 -1
  42. package/dist/{kv-cache-AWTrxTq3.mjs → kv-cache-DZr8HTel.mjs} +1 -1
  43. package/dist/{kv-cache-BcsvaCCL.cjs → kv-cache-DzClEalR.cjs} +1 -1
  44. package/dist/{ld-CWDReGMZ.mjs → ld-Djxsq6JR.mjs} +3 -3
  45. package/dist/{metrics-DxlVXwn5.mjs → metrics-CBHhbHgG.mjs} +1 -1
  46. package/dist/{middleware-1mv9QR16.js → middleware-BDUR99k3.js} +686 -70
  47. package/dist/{middleware-B7stx5gy.mjs → middleware-BahNnLfD.mjs} +1 -1
  48. package/dist/{middleware-CAWRGSwH.mjs → middleware-DDa8pCUa.mjs} +431 -80
  49. package/dist/{mod-C06GoL5Z.d.ts → mod-CnGtADvO.d.ts} +1 -1
  50. package/dist/{mod-B7SfEqIP.d.cts → mod-Decmur1U.d.cts} +1 -1
  51. package/dist/mod.cjs +4 -4
  52. package/dist/mod.d.cts +4 -4
  53. package/dist/mod.d.ts +4 -4
  54. package/dist/mod.js +4 -4
  55. package/dist/nodeinfo/handler.test.mjs +2 -2
  56. package/dist/{owner-CgoCOTUQ.mjs → owner-OdWSE8am.mjs} +3 -3
  57. package/dist/{portable-collection-CG9P_i0E.js → portable-collection-BCy06GiS.js} +1 -1
  58. package/dist/{portable-collection-0_5DyXMt.mjs → portable-collection-DAV7Ho3m.mjs} +2 -2
  59. package/dist/{portable-collection-BD8fE3j3.cjs → portable-collection-wBWbuHOH.cjs} +1 -1
  60. package/dist/{portable-key-BXkgMpw1.mjs → portable-key-BL6K88p2.mjs} +1 -1
  61. package/dist/{proof-B5s-W6Sm.mjs → proof-vZ04IAjq.mjs} +10 -5
  62. package/dist/{send-BAJvkdNL.mjs → send-ylV8KCLx.mjs} +3 -3
  63. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  64. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  65. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  66. package/dist/sig/compound-proof.test.mjs +2 -2
  67. package/dist/sig/http.test.mjs +2 -2
  68. package/dist/sig/key.test.mjs +1 -1
  69. package/dist/sig/ld.test.mjs +2 -2
  70. package/dist/sig/mod.cjs +2 -2
  71. package/dist/sig/mod.js +2 -2
  72. package/dist/sig/owner.test.mjs +1 -1
  73. package/dist/sig/portable-collection.test.mjs +1 -1
  74. package/dist/sig/portable-key.test.mjs +5 -5
  75. package/dist/sig/proof.test.mjs +3 -3
  76. package/dist/{temporal-BkCPJ_uC.mjs → temporal-BTD4f_mZ.mjs} +1 -1
  77. package/dist/testing/kv.test.mjs +1 -1
  78. package/dist/testing/mod.d.mts +393 -4
  79. package/dist/testing/mod.mjs +2 -2
  80. package/dist/{testing-otQhu4aD.mjs → testing-CKaJhcA0.mjs} +1 -1
  81. package/dist/utils/docloader.test.mjs +2 -2
  82. package/dist/utils/kv-cache.test.mjs +1 -1
  83. package/dist/utils/mod.cjs +1 -1
  84. package/dist/utils/mod.js +1 -1
  85. package/package.json +6 -6
@@ -488,6 +488,28 @@ type ObjectAuthorizePredicate<TContextData, TParam extends string> = (context: R
488
488
  */
489
489
  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>;
490
490
  /**
491
+ * A callback that maps a custom collection to the identifier of the actor
492
+ * that owns it, so that the collection can be served as an [FEP-ef61]
493
+ * portable collection through the gateway endpoint, e.g.,
494
+ * `GET /.well-known/apgateway/did:key:z6Mk.../users/alice/bookmarks`.
495
+ *
496
+ * Fedify serves such a request only if the returned actor is a portable actor
497
+ * whose ID is under the requested DID. The owner has to come from
498
+ * the application's data, not from the request, since the DID in the request
499
+ * path is not evidence that this server hosts collections for it.
500
+ *
501
+ * [FEP-ef61]: https://w3id.org/fep/ef61
502
+ *
503
+ * @template TContextData The context data to pass to the {@link Context}.
504
+ * @template TParam The parameter names of the requested URL.
505
+ * @param context The context.
506
+ * @param values The parameters of the requested URL.
507
+ * @returns The internal identifier of the actor that owns the collection, or
508
+ * `null` if the collection is not a portable collection.
509
+ * @since 2.4.0
510
+ */
511
+ type PortableCollectionOwnerMapper<TContextData, TParam extends string> = (context: Context<TContextData>, values: Record<TParam, string>) => string | null | Promise<string | null>;
512
+ /**
491
513
  * A callback that counts the number of items in a custom collection.
492
514
  *
493
515
  * @template TParams The parameter names of the requested URL.
@@ -2817,6 +2839,21 @@ interface CustomCollectionCallbackSetters<TParam extends string, TContext extend
2817
2839
  * @since 0.7.0
2818
2840
  */
2819
2841
  authorize(predicate: ObjectAuthorizePredicate<TContextData, string>): CustomCollectionCallbackSetters<TParam, TContext, TContextData>;
2842
+ /**
2843
+ * Maps the custom collection to the actor that owns it, so that it is also
2844
+ * served as an [FEP-ef61] portable collection through the gateway endpoint,
2845
+ * e.g., `GET /.well-known/apgateway/did:key:z6Mk.../users/alice/bookmarks`,
2846
+ * if the owner is a portable actor under the requested DID. Without it,
2847
+ * the custom collection is not served through the gateway endpoint.
2848
+ *
2849
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2850
+ * @param mapper A callback that returns the identifier of the actor that
2851
+ * owns the collection, or `null` if the collection is not
2852
+ * portable.
2853
+ * @returns The setters object so that settings can be chained.
2854
+ * @since 2.4.0
2855
+ */
2856
+ mapPortableOwner(mapper: PortableCollectionOwnerMapper<TContextData, TParam>): CustomCollectionCallbackSetters<TParam, TContext, TContextData>;
2820
2857
  }
2821
2858
  /**
2822
2859
  * Represents an object with a type ID, which is either a constructor or an
@@ -3072,6 +3109,35 @@ interface Context<TContextData> {
3072
3109
  */
3073
3110
  getOutboxUri(identifier: string): URL;
3074
3111
  /**
3112
+ * Builds the [FEP-ef61] portable ID of an actor's outbox with the given
3113
+ * identifier under the given DID authority.
3114
+ *
3115
+ * The path is the same as the one {@link Context.getOutboxUri} builds, but
3116
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3117
+ * `ap+ef61://did:key:z6Mk.../users/alice/outbox`. If a portable actor has
3118
+ * it as its `outbox` and its ID is under the same DID, the collection is
3119
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3120
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/outbox`, by the same
3121
+ * collection dispatcher.
3122
+ *
3123
+ * The returned `URL` keeps the DID authority percent-encoded, because
3124
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3125
+ * from `@fedify/vocab-runtime` to get the canonical string.
3126
+ *
3127
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3128
+ *
3129
+ * @param identifier The actor's identifier.
3130
+ * @param authority The DID that controls the actor, e.g.,
3131
+ * `did:key:z6Mk...`. It must be a bare DID, without
3132
+ * a path, query, or fragment.
3133
+ * @returns The outbox's portable ID.
3134
+ * @throws {RouterError} If no outbox dispatcher is available.
3135
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3136
+ * DID that is not encoded in base58-btc.
3137
+ * @since 2.4.0
3138
+ */
3139
+ getPortableOutboxUri(identifier: string, authority: string): URL;
3140
+ /**
3075
3141
  * Builds the URI of an actor's media upload endpoint with the given
3076
3142
  * identifier. This is the endpoint advertised under `endpoints.uploadMedia`
3077
3143
  * in the actor document.
@@ -3103,6 +3169,35 @@ interface Context<TContextData> {
3103
3169
  */
3104
3170
  getFollowingUri(identifier: string): URL;
3105
3171
  /**
3172
+ * Builds the [FEP-ef61] portable ID of an actor's following collection with the given
3173
+ * identifier under the given DID authority.
3174
+ *
3175
+ * The path is the same as the one {@link Context.getFollowingUri} builds, but
3176
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3177
+ * `ap+ef61://did:key:z6Mk.../users/alice/following`. If a portable actor has
3178
+ * it as its `following` and its ID is under the same DID, the collection is
3179
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3180
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/following`, by the same
3181
+ * collection dispatcher.
3182
+ *
3183
+ * The returned `URL` keeps the DID authority percent-encoded, because
3184
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3185
+ * from `@fedify/vocab-runtime` to get the canonical string.
3186
+ *
3187
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3188
+ *
3189
+ * @param identifier The actor's identifier.
3190
+ * @param authority The DID that controls the actor, e.g.,
3191
+ * `did:key:z6Mk...`. It must be a bare DID, without
3192
+ * a path, query, or fragment.
3193
+ * @returns The following collection's portable ID.
3194
+ * @throws {RouterError} If no following collection is available.
3195
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3196
+ * DID that is not encoded in base58-btc.
3197
+ * @since 2.4.0
3198
+ */
3199
+ getPortableFollowingUri(identifier: string, authority: string): URL;
3200
+ /**
3106
3201
  * Builds the URI of an actor's followers collection with the given
3107
3202
  * identifier.
3108
3203
  * @param identifier The actor's identifier.
@@ -3111,6 +3206,35 @@ interface Context<TContextData> {
3111
3206
  */
3112
3207
  getFollowersUri(identifier: string): URL;
3113
3208
  /**
3209
+ * Builds the [FEP-ef61] portable ID of an actor's followers collection with the given
3210
+ * identifier under the given DID authority.
3211
+ *
3212
+ * The path is the same as the one {@link Context.getFollowersUri} builds, but
3213
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3214
+ * `ap+ef61://did:key:z6Mk.../users/alice/followers`. If a portable actor has
3215
+ * it as its `followers` and its ID is under the same DID, the collection is
3216
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3217
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/followers`, by the same
3218
+ * collection dispatcher.
3219
+ *
3220
+ * The returned `URL` keeps the DID authority percent-encoded, because
3221
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3222
+ * from `@fedify/vocab-runtime` to get the canonical string.
3223
+ *
3224
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3225
+ *
3226
+ * @param identifier The actor's identifier.
3227
+ * @param authority The DID that controls the actor, e.g.,
3228
+ * `did:key:z6Mk...`. It must be a bare DID, without
3229
+ * a path, query, or fragment.
3230
+ * @returns The followers collection's portable ID.
3231
+ * @throws {RouterError} If no followers collection is available.
3232
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3233
+ * DID that is not encoded in base58-btc.
3234
+ * @since 2.4.0
3235
+ */
3236
+ getPortableFollowersUri(identifier: string, authority: string): URL;
3237
+ /**
3114
3238
  * Builds the URI of an actor's liked collection with the given identifier.
3115
3239
  * @param identifier The actor's identifier.
3116
3240
  * @returns The actor's liked collection URI.
@@ -3119,6 +3243,35 @@ interface Context<TContextData> {
3119
3243
  */
3120
3244
  getLikedUri(identifier: string): URL;
3121
3245
  /**
3246
+ * Builds the [FEP-ef61] portable ID of an actor's liked collection with the given
3247
+ * identifier under the given DID authority.
3248
+ *
3249
+ * The path is the same as the one {@link Context.getLikedUri} builds, but
3250
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3251
+ * `ap+ef61://did:key:z6Mk.../users/alice/liked`. If a portable actor has
3252
+ * it as its `liked` and its ID is under the same DID, the collection is
3253
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3254
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/liked`, by the same
3255
+ * collection dispatcher.
3256
+ *
3257
+ * The returned `URL` keeps the DID authority percent-encoded, because
3258
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3259
+ * from `@fedify/vocab-runtime` to get the canonical string.
3260
+ *
3261
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3262
+ *
3263
+ * @param identifier The actor's identifier.
3264
+ * @param authority The DID that controls the actor, e.g.,
3265
+ * `did:key:z6Mk...`. It must be a bare DID, without
3266
+ * a path, query, or fragment.
3267
+ * @returns The liked collection's portable ID.
3268
+ * @throws {RouterError} If no liked collection is available.
3269
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3270
+ * DID that is not encoded in base58-btc.
3271
+ * @since 2.4.0
3272
+ */
3273
+ getPortableLikedUri(identifier: string, authority: string): URL;
3274
+ /**
3122
3275
  * Builds the URI of an actor's featured collection with the given identifier.
3123
3276
  * @param identifier The actor's identifier.
3124
3277
  * @returns The actor's featured collection URI.
@@ -3127,6 +3280,35 @@ interface Context<TContextData> {
3127
3280
  */
3128
3281
  getFeaturedUri(identifier: string): URL;
3129
3282
  /**
3283
+ * Builds the [FEP-ef61] portable ID of an actor's featured collection with the given
3284
+ * identifier under the given DID authority.
3285
+ *
3286
+ * The path is the same as the one {@link Context.getFeaturedUri} builds, but
3287
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3288
+ * `ap+ef61://did:key:z6Mk.../users/alice/featured`. If a portable actor has
3289
+ * it as its `featured` and its ID is under the same DID, the collection is
3290
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3291
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/featured`, by the same
3292
+ * collection dispatcher.
3293
+ *
3294
+ * The returned `URL` keeps the DID authority percent-encoded, because
3295
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3296
+ * from `@fedify/vocab-runtime` to get the canonical string.
3297
+ *
3298
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3299
+ *
3300
+ * @param identifier The actor's identifier.
3301
+ * @param authority The DID that controls the actor, e.g.,
3302
+ * `did:key:z6Mk...`. It must be a bare DID, without
3303
+ * a path, query, or fragment.
3304
+ * @returns The featured collection's portable ID.
3305
+ * @throws {RouterError} If no featured collection is available.
3306
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3307
+ * DID that is not encoded in base58-btc.
3308
+ * @since 2.4.0
3309
+ */
3310
+ getPortableFeaturedUri(identifier: string, authority: string): URL;
3311
+ /**
3130
3312
  * Builds the URI of an actor's featured tags collection with the given
3131
3313
  * identifier.
3132
3314
  * @param identifier The actor's identifier.
@@ -3136,6 +3318,35 @@ interface Context<TContextData> {
3136
3318
  */
3137
3319
  getFeaturedTagsUri(identifier: string): URL;
3138
3320
  /**
3321
+ * Builds the [FEP-ef61] portable ID of an actor's featured tags collection with the given
3322
+ * identifier under the given DID authority.
3323
+ *
3324
+ * The path is the same as the one {@link Context.getFeaturedTagsUri} builds, but
3325
+ * the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3326
+ * `ap+ef61://did:key:z6Mk.../users/alice/tags`. If a portable actor has
3327
+ * it as its `featuredTags` and its ID is under the same DID, the collection is
3328
+ * served through the FEP-ef61 gateway endpoint, e.g.,
3329
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/tags`, by the same
3330
+ * collection dispatcher.
3331
+ *
3332
+ * The returned `URL` keeps the DID authority percent-encoded, because
3333
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3334
+ * from `@fedify/vocab-runtime` to get the canonical string.
3335
+ *
3336
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3337
+ *
3338
+ * @param identifier The actor's identifier.
3339
+ * @param authority The DID that controls the actor, e.g.,
3340
+ * `did:key:z6Mk...`. It must be a bare DID, without
3341
+ * a path, query, or fragment.
3342
+ * @returns The featured tags collection's portable ID.
3343
+ * @throws {RouterError} If no featured tags collection is available.
3344
+ * @throws {TypeError} If the authority is not a DID, or it is a `did:key`
3345
+ * DID that is not encoded in base58-btc.
3346
+ * @since 2.4.0
3347
+ */
3348
+ getPortableFeaturedTagsUri(identifier: string, authority: string): URL;
3349
+ /**
3139
3350
  * Determines the type of the URI and extracts the associated data.
3140
3351
  * @param uri The URI to parse.
3141
3352
  * @returns The result of parsing the URI. If `null` is given or
@@ -3394,6 +3605,38 @@ interface Context<TContextData> {
3394
3605
  * @since 1.8.0
3395
3606
  */
3396
3607
  getCollectionUri<TParam extends Record<string, string>>(name: string | symbol, values: TParam): URL;
3608
+ /**
3609
+ * Builds the [FEP-ef61] portable ID of a custom collection with the given
3610
+ * name and values under the given DID authority.
3611
+ *
3612
+ * The path is the same as the one {@link Context.getCollectionUri} builds,
3613
+ * but the result is an `ap+ef61:` URI whose authority is the DID, e.g.,
3614
+ * `ap+ef61://did:key:z6Mk.../users/alice/bookmarks`. If the collection
3615
+ * dispatcher maps the collection to a portable actor under the same DID
3616
+ * with `CustomCollectionCallbackSetters.mapPortableOwner()`, the
3617
+ * collection is served through the FEP-ef61 gateway endpoint, e.g.,
3618
+ * `/.well-known/apgateway/did:key:z6Mk.../users/alice/bookmarks`.
3619
+ *
3620
+ * The returned `URL` keeps the DID authority percent-encoded, because
3621
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
3622
+ * from `@fedify/vocab-runtime` to get the canonical string.
3623
+ *
3624
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3625
+ *
3626
+ * @param name The name of the collection, which can be a string or a symbol.
3627
+ * @param values The values of the URI parameters.
3628
+ * @param authority The DID that controls the collection, e.g.,
3629
+ * `did:key:z6Mk...`. It must be a bare DID, without
3630
+ * a path, query, or fragment.
3631
+ * @returns The collection's portable ID.
3632
+ * @throws {RouterError} If no collection dispatcher is available for
3633
+ * the name.
3634
+ * @throws {TypeError} If values are invalid, or the authority is not a DID
3635
+ * or is a `did:key` DID that is not encoded in
3636
+ * base58-btc.
3637
+ * @since 2.4.0
3638
+ */
3639
+ getPortableCollectionUri<TParam extends Record<string, string>>(name: string | symbol, values: TParam, authority: string): URL;
3397
3640
  }
3398
3641
  /**
3399
3642
  * Information about an [FEP-ef61] portable object requested through
@@ -3410,7 +3653,7 @@ interface PortableRequest {
3410
3653
  */
3411
3654
  readonly authority: string;
3412
3655
  /**
3413
- * The ID of the requested portable object, e.g.,
3656
+ * The ID of the requested portable object or collection, e.g.,
3414
3657
  * `ap+ef61://did:key:z6Mk.../notes/123`, without a query. Like other
3415
3658
  * portable IDs, the `URL` keeps the DID authority percent-encoded; use
3416
3659
  * `formatIri()` from `@fedify/vocab-runtime` to get the canonical string.
@@ -3464,12 +3707,14 @@ interface RequestContext<TContextData> extends Context<TContextData> {
3464
3707
  * Information about the [FEP-ef61] portable object requested through
3465
3708
  * the gateway endpoint, e.g.,
3466
3709
  * `GET /.well-known/apgateway/did:key:z6Mk.../notes/123`, which is served
3467
- * by an object dispatcher or the actor dispatcher. It is `undefined` for
3468
- * ordinary requests.
3710
+ * by an object dispatcher, the actor dispatcher, or a collection
3711
+ * dispatcher. It is `undefined` for ordinary requests.
3469
3712
  *
3470
3713
  * The authority comes from the request path, so it is not evidence that
3471
3714
  * this server hosts objects for the DID. An object dispatcher or the actor
3472
- * dispatcher has to check that by itself.
3715
+ * dispatcher has to check that by itself. A portable collection is served
3716
+ * only if its owner, which the actor dispatcher looks up without this
3717
+ * property, is a portable actor under the DID.
3473
3718
  *
3474
3719
  * It describes the incoming request, so it stays the same in contexts
3475
3720
  * derived from this one, e.g., by {@link RequestContext.getObject}.
@@ -3552,6 +3797,150 @@ interface RequestContext<TContextData> extends Context<TContextData> {
3552
3797
  */
3553
3798
  getPortableInboxUri(identifier: string, authority?: string): URL;
3554
3799
  /**
3800
+ * Builds the [FEP-ef61] portable ID of an actor's outbox with the given
3801
+ * identifier. It works the same as {@link Context.getPortableOutboxUri}, except that
3802
+ * the authority can be omitted while handling a portable gateway request.
3803
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
3804
+ *
3805
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3806
+ *
3807
+ * @param identifier The actor's identifier.
3808
+ * @param authority The DID that controls the actor, e.g.,
3809
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
3810
+ * portable object or collection.
3811
+ * @returns The outbox's portable ID.
3812
+ * @throws {RouterError} If no outbox dispatcher is available.
3813
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
3814
+ * that is not encoded in base58-btc, or the authority
3815
+ * is omitted outside a portable gateway request.
3816
+ * @since 2.4.0
3817
+ */
3818
+ getPortableOutboxUri(identifier: string, authority?: string): URL;
3819
+ /**
3820
+ * Builds the [FEP-ef61] portable ID of an actor's following collection with the given
3821
+ * identifier. It works the same as {@link Context.getPortableFollowingUri}, except that
3822
+ * the authority can be omitted while handling a portable gateway request.
3823
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
3824
+ *
3825
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3826
+ *
3827
+ * @param identifier The actor's identifier.
3828
+ * @param authority The DID that controls the actor, e.g.,
3829
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
3830
+ * portable object or collection.
3831
+ * @returns The following collection's portable ID.
3832
+ * @throws {RouterError} If no following collection is available.
3833
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
3834
+ * that is not encoded in base58-btc, or the authority
3835
+ * is omitted outside a portable gateway request.
3836
+ * @since 2.4.0
3837
+ */
3838
+ getPortableFollowingUri(identifier: string, authority?: string): URL;
3839
+ /**
3840
+ * Builds the [FEP-ef61] portable ID of an actor's followers collection with the given
3841
+ * identifier. It works the same as {@link Context.getPortableFollowersUri}, except that
3842
+ * the authority can be omitted while handling a portable gateway request.
3843
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
3844
+ *
3845
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3846
+ *
3847
+ * @param identifier The actor's identifier.
3848
+ * @param authority The DID that controls the actor, e.g.,
3849
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
3850
+ * portable object or collection.
3851
+ * @returns The followers collection's portable ID.
3852
+ * @throws {RouterError} If no followers collection is available.
3853
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
3854
+ * that is not encoded in base58-btc, or the authority
3855
+ * is omitted outside a portable gateway request.
3856
+ * @since 2.4.0
3857
+ */
3858
+ getPortableFollowersUri(identifier: string, authority?: string): URL;
3859
+ /**
3860
+ * Builds the [FEP-ef61] portable ID of an actor's liked collection with the given
3861
+ * identifier. It works the same as {@link Context.getPortableLikedUri}, except that
3862
+ * the authority can be omitted while handling a portable gateway request.
3863
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
3864
+ *
3865
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3866
+ *
3867
+ * @param identifier The actor's identifier.
3868
+ * @param authority The DID that controls the actor, e.g.,
3869
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
3870
+ * portable object or collection.
3871
+ * @returns The liked collection's portable ID.
3872
+ * @throws {RouterError} If no liked collection is available.
3873
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
3874
+ * that is not encoded in base58-btc, or the authority
3875
+ * is omitted outside a portable gateway request.
3876
+ * @since 2.4.0
3877
+ */
3878
+ getPortableLikedUri(identifier: string, authority?: string): URL;
3879
+ /**
3880
+ * Builds the [FEP-ef61] portable ID of an actor's featured collection with the given
3881
+ * identifier. It works the same as {@link Context.getPortableFeaturedUri}, except that
3882
+ * the authority can be omitted while handling a portable gateway request.
3883
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
3884
+ *
3885
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3886
+ *
3887
+ * @param identifier The actor's identifier.
3888
+ * @param authority The DID that controls the actor, e.g.,
3889
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
3890
+ * portable object or collection.
3891
+ * @returns The featured collection's portable ID.
3892
+ * @throws {RouterError} If no featured collection is available.
3893
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
3894
+ * that is not encoded in base58-btc, or the authority
3895
+ * is omitted outside a portable gateway request.
3896
+ * @since 2.4.0
3897
+ */
3898
+ getPortableFeaturedUri(identifier: string, authority?: string): URL;
3899
+ /**
3900
+ * Builds the [FEP-ef61] portable ID of an actor's featured tags collection with the given
3901
+ * identifier. It works the same as {@link Context.getPortableFeaturedTagsUri}, except that
3902
+ * the authority can be omitted while handling a portable gateway request.
3903
+ * In that case, the DID in {@link RequestContext.portableRequest} is used.
3904
+ *
3905
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3906
+ *
3907
+ * @param identifier The actor's identifier.
3908
+ * @param authority The DID that controls the actor, e.g.,
3909
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
3910
+ * portable object or collection.
3911
+ * @returns The featured tags collection's portable ID.
3912
+ * @throws {RouterError} If no featured tags collection is available.
3913
+ * @throws {TypeError} If the authority is not a DID, it is a `did:key` DID
3914
+ * that is not encoded in base58-btc, or the authority
3915
+ * is omitted outside a portable gateway request.
3916
+ * @since 2.4.0
3917
+ */
3918
+ getPortableFeaturedTagsUri(identifier: string, authority?: string): URL;
3919
+ /**
3920
+ * Builds the [FEP-ef61] portable ID of a custom collection with the given
3921
+ * name and values. It works the same as
3922
+ * {@link Context.getPortableCollectionUri}, except that the authority can
3923
+ * be omitted while handling a portable gateway request. In that case,
3924
+ * the DID in {@link RequestContext.portableRequest} is used.
3925
+ *
3926
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3927
+ *
3928
+ * @param name The name of the collection, which can be a string or a symbol.
3929
+ * @param values The values of the URI parameters.
3930
+ * @param authority The DID that controls the collection, e.g.,
3931
+ * `did:key:z6Mk...`. Defaults to the DID of the requested
3932
+ * portable object or collection.
3933
+ * @returns The collection's portable ID.
3934
+ * @throws {RouterError} If no collection dispatcher is available for
3935
+ * the name.
3936
+ * @throws {TypeError} If values are invalid, the authority is not a DID,
3937
+ * it is a `did:key` DID that is not encoded in
3938
+ * base58-btc, or the authority is omitted outside
3939
+ * a portable gateway request.
3940
+ * @since 2.4.0
3941
+ */
3942
+ getPortableCollectionUri<TParam extends Record<string, string>>(name: string | symbol, values: TParam, authority?: string): URL;
3943
+ /**
3555
3944
  * Creates a new context with the same properties as this one,
3556
3945
  * but with the given data.
3557
3946
  * @param data The new data to associate with the context.
@@ -4075,4 +4464,4 @@ interface ActorKeyPair extends CryptoKeyPair {
4075
4464
  readonly multikey: Multikey;
4076
4465
  }
4077
4466
  //#endregion
4078
- export { InProcessMessageQueue as $, MediaUploaderSetters as A, HashlinkMediaRequest as At, createFederationBuilder as B, OutboxPermanentFailureHandler as Bt, FederationFetchOptions as C, CollectionCounter as Ct, IdempotencyStrategy as D, CustomCollectionCursor as Dt, IdempotencyKeyCallback as E, CustomCollectionCounter as Et, FederationKvPrefixes as F, ObjectAuthorizePredicate as Ft, TaskRegistry as G, WebFingerLinksDispatcher as Gt, TaskDefinitionOptions as H, SharedInboxKeyDispatcher as Ht, FederationOrigin as I, ObjectDispatcher as It, RetryPolicy as J, PageItems as Jt, CreateExponentialBackoffPolicyOptions as K, SendActivityError as Kt, FederationQueueOptions as L, OutboxErrorHandler as Lt, OutboxListenerSetters as M, InboxListener as Mt, PortableInboxForwardingOptions as N, MediaUploaderCallback as Nt, InboxChallengePolicy as O, CustomCollectionDispatcher as Ot, Rfc6570Expression as P, NodeInfoDispatcher as Pt, respondWithObjectIfAcceptable as Q, createFederation as R, OutboxListener as Rt, FederationBuilder as S, AuthorizePredicate as St, FederationStartQueueOptions as T, CollectionDispatcher as Tt, TaskEnqueueOptions as U, UnverifiedActivityHandler as Ut, TaskDefinition as V, PortableActorIdMapper as Vt, TaskHandler as W, UnverifiedActivityReason as Wt, RespondWithObjectOptions as X, digest as Xt, createExponentialBackoffPolicy as Y, buildCollectionSynchronizationHeader as Yt, respondWithObject as Z, ActivityTransformer as Zt, ConstructorWithTypeId as _, parseCircuitBreakerKvState as _t, GetObjectOptions as a, ParallelMessageQueue as at, Federation as b, ActorHandleMapper as bt, OutboxContext as c, CircuitBreakerBeforeSendDecision as ct, RequestContext as d, CircuitBreakerKvState as dt, InProcessMessageQueueOptions as et, RouteActivityOptions as f, CircuitBreakerOptions as ft, CollectionCallbackSetters as g, normalizeCircuitBreakerOptions as gt, ActorCallbackSetters as h, NormalizedCircuitBreakerOptions as ht, GetActorOptions as i, MessageQueueListenOptions as it, ObjectCallbackSetters as j, InboxErrorHandler as jt, InboxListenerSetters as k, HashlinkMediaDispatcher as kt, ParseUriResult as l, CircuitBreakerCreateOptions as lt, SendActivityOptionsForCollection as m, CircuitBreakerStateChange as mt, Context as n, MessageQueueDepth as nt, GetSignedKeyOptions as o, CircuitBreaker as ot, SendActivityOptions as p, CircuitBreakerState as pt, RetryContext as q, SenderKeyPair as qt, ForwardActivityOptions as r, MessageQueueEnqueueOptions as rt, InboxContext as s, CircuitBreakerActivityDrop as st, ActorKeyPair as t, MessageQueue as tt, PortableRequest as u, CircuitBreakerFailurePolicy as ut, CustomCollectionCallbackSetters as v, ActorAliasMapper as vt, FederationOptions as w, CollectionCursor as wt, FederationBenchmarkOptions as x, ActorKeyPairsDispatcher as xt, Federatable as y, ActorDispatcher as yt, Message as z, OutboxListenerErrorHandler as zt };
4467
+ export { InProcessMessageQueue as $, MediaUploaderSetters as A, HashlinkMediaRequest as At, createFederationBuilder as B, OutboxPermanentFailureHandler as Bt, FederationFetchOptions as C, CollectionCounter as Ct, IdempotencyStrategy as D, CustomCollectionCursor as Dt, IdempotencyKeyCallback as E, CustomCollectionCounter as Et, FederationKvPrefixes as F, ObjectAuthorizePredicate as Ft, TaskRegistry as G, UnverifiedActivityReason as Gt, TaskDefinitionOptions as H, PortableCollectionOwnerMapper as Ht, FederationOrigin as I, ObjectDispatcher as It, RetryPolicy as J, SenderKeyPair as Jt, CreateExponentialBackoffPolicyOptions as K, WebFingerLinksDispatcher as Kt, FederationQueueOptions as L, OutboxErrorHandler as Lt, OutboxListenerSetters as M, InboxListener as Mt, PortableInboxForwardingOptions as N, MediaUploaderCallback as Nt, InboxChallengePolicy as O, CustomCollectionDispatcher as Ot, Rfc6570Expression as P, NodeInfoDispatcher as Pt, respondWithObjectIfAcceptable as Q, ActivityTransformer as Qt, createFederation as R, OutboxListener as Rt, FederationBuilder as S, AuthorizePredicate as St, FederationStartQueueOptions as T, CollectionDispatcher as Tt, TaskEnqueueOptions as U, SharedInboxKeyDispatcher as Ut, TaskDefinition as V, PortableActorIdMapper as Vt, TaskHandler as W, UnverifiedActivityHandler as Wt, RespondWithObjectOptions as X, buildCollectionSynchronizationHeader as Xt, createExponentialBackoffPolicy as Y, PageItems as Yt, respondWithObject as Z, digest as Zt, ConstructorWithTypeId as _, parseCircuitBreakerKvState as _t, GetObjectOptions as a, ParallelMessageQueue as at, Federation as b, ActorHandleMapper as bt, OutboxContext as c, CircuitBreakerBeforeSendDecision as ct, RequestContext as d, CircuitBreakerKvState as dt, InProcessMessageQueueOptions as et, RouteActivityOptions as f, CircuitBreakerOptions as ft, CollectionCallbackSetters as g, normalizeCircuitBreakerOptions as gt, ActorCallbackSetters as h, NormalizedCircuitBreakerOptions as ht, GetActorOptions as i, MessageQueueListenOptions as it, ObjectCallbackSetters as j, InboxErrorHandler as jt, InboxListenerSetters as k, HashlinkMediaDispatcher as kt, ParseUriResult as l, CircuitBreakerCreateOptions as lt, SendActivityOptionsForCollection as m, CircuitBreakerStateChange as mt, Context as n, MessageQueueDepth as nt, GetSignedKeyOptions as o, CircuitBreaker as ot, SendActivityOptions as p, CircuitBreakerState as pt, RetryContext as q, SendActivityError as qt, ForwardActivityOptions as r, MessageQueueEnqueueOptions as rt, InboxContext as s, CircuitBreakerActivityDrop as st, ActorKeyPair as t, MessageQueue as tt, PortableRequest as u, CircuitBreakerFailurePolicy as ut, CustomCollectionCallbackSetters as v, ActorAliasMapper as vt, FederationOptions as w, CollectionCursor as wt, FederationBenchmarkOptions as x, ActorKeyPairsDispatcher as xt, Federatable as y, ActorDispatcher as yt, Message as z, OutboxListenerErrorHandler as zt };
@@ -8,7 +8,7 @@ import { lookupWebFinger } from "@fedify/webfinger";
8
8
  import { metrics, trace } from "@opentelemetry/api";
9
9
  //#region src/testing/context.ts
10
10
  function createContext(values) {
11
- const { federation, url = new URL("http://example.com/"), canonicalOrigin, data, documentLoader, contextLoader, verifyPortableObject, meterProvider, tracerProvider, clone, getNodeInfoUri, getActorUri, getPortableActorUri, getObjectUri, getPortableObjectUri, getPortableInboxUri, getCollectionUri, getOutboxUri, getMediaUploaderUri, getInboxUri, getFollowingUri, getFollowersUri, getLikedUri, getFeaturedUri, getFeaturedTagsUri, parseUri, getActorKeyPairs, getDocumentLoader, lookupObject: lookupObject$1, traverseCollection: traverseCollection$1, lookupNodeInfo, lookupWebFinger: lookupWebFinger$1, sendActivity, routeActivity, enqueueTask, enqueueTaskMany } = values;
11
+ const { federation, url = new URL("http://example.com/"), canonicalOrigin, data, documentLoader, contextLoader, verifyPortableObject, meterProvider, tracerProvider, clone, getNodeInfoUri, getActorUri, getPortableActorUri, getObjectUri, getPortableObjectUri, getPortableInboxUri, getPortableOutboxUri, getPortableFollowingUri, getPortableFollowersUri, getPortableLikedUri, getPortableFeaturedUri, getPortableFeaturedTagsUri, getPortableCollectionUri, getCollectionUri, getOutboxUri, getMediaUploaderUri, getInboxUri, getFollowingUri, getFollowersUri, getLikedUri, getFeaturedUri, getFeaturedTagsUri, parseUri, getActorKeyPairs, getDocumentLoader, lookupObject: lookupObject$1, traverseCollection: traverseCollection$1, lookupNodeInfo, lookupWebFinger: lookupWebFinger$1, sendActivity, routeActivity, enqueueTask, enqueueTaskMany } = values;
12
12
  function throwRouterError() {
13
13
  throw new RouterError("Not implemented");
14
14
  }
@@ -34,6 +34,13 @@ function createContext(values) {
34
34
  getObjectUri: getObjectUri ?? throwRouterError,
35
35
  getPortableObjectUri: getPortableObjectUri ?? throwRouterError,
36
36
  getPortableInboxUri: getPortableInboxUri ?? throwRouterError,
37
+ getPortableOutboxUri: getPortableOutboxUri ?? throwRouterError,
38
+ getPortableFollowingUri: getPortableFollowingUri ?? throwRouterError,
39
+ getPortableFollowersUri: getPortableFollowersUri ?? throwRouterError,
40
+ getPortableLikedUri: getPortableLikedUri ?? throwRouterError,
41
+ getPortableFeaturedUri: getPortableFeaturedUri ?? throwRouterError,
42
+ getPortableFeaturedTagsUri: getPortableFeaturedTagsUri ?? throwRouterError,
43
+ getPortableCollectionUri: getPortableCollectionUri ?? throwRouterError,
37
44
  getCollectionUri: getCollectionUri ?? throwRouterError,
38
45
  getOutboxUri: getOutboxUri ?? throwRouterError,
39
46
  getMediaUploaderUri: getMediaUploaderUri ?? throwRouterError,