@fedify/adonisjs 2.4.0-dev.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 (82) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +420 -0
  3. package/dist/configure.cjs +108 -0
  4. package/dist/configure.cjs.map +1 -0
  5. package/dist/configure.d.cts +14 -0
  6. package/dist/configure.d.ts +14 -0
  7. package/dist/configure.js +107 -0
  8. package/dist/configure.js.map +1 -0
  9. package/dist/index.cjs +19 -0
  10. package/dist/index.d.cts +8 -0
  11. package/dist/index.d.ts +8 -0
  12. package/dist/index.js +8 -0
  13. package/dist/package.cjs +121 -0
  14. package/dist/package.cjs.map +1 -0
  15. package/dist/package.js +115 -0
  16. package/dist/package.js.map +1 -0
  17. package/dist/providers/fedify_provider.cjs +270 -0
  18. package/dist/providers/fedify_provider.cjs.map +1 -0
  19. package/dist/providers/fedify_provider.d.cts +54 -0
  20. package/dist/providers/fedify_provider.d.ts +55 -0
  21. package/dist/providers/fedify_provider.js +269 -0
  22. package/dist/providers/fedify_provider.js.map +1 -0
  23. package/dist/services/builder.cjs +9 -0
  24. package/dist/services/builder.cjs.map +1 -0
  25. package/dist/services/builder.d.cts +7 -0
  26. package/dist/services/builder.d.ts +8 -0
  27. package/dist/services/builder.js +8 -0
  28. package/dist/services/builder.js.map +1 -0
  29. package/dist/src/builder.cjs +55 -0
  30. package/dist/src/builder.cjs.map +1 -0
  31. package/dist/src/builder.d.cts +14 -0
  32. package/dist/src/builder.d.ts +14 -0
  33. package/dist/src/builder.js +54 -0
  34. package/dist/src/builder.js.map +1 -0
  35. package/dist/src/define_config.cjs +114 -0
  36. package/dist/src/define_config.cjs.map +1 -0
  37. package/dist/src/define_config.d.cts +30 -0
  38. package/dist/src/define_config.d.ts +30 -0
  39. package/dist/src/define_config.js +113 -0
  40. package/dist/src/define_config.js.map +1 -0
  41. package/dist/src/errors.cjs +77 -0
  42. package/dist/src/errors.cjs.map +1 -0
  43. package/dist/src/errors.d.cts +56 -0
  44. package/dist/src/errors.d.ts +56 -0
  45. package/dist/src/errors.js +73 -0
  46. package/dist/src/errors.js.map +1 -0
  47. package/dist/src/fedify_middleware.cjs +507 -0
  48. package/dist/src/fedify_middleware.cjs.map +1 -0
  49. package/dist/src/fedify_middleware.d.cts +85 -0
  50. package/dist/src/fedify_middleware.d.ts +85 -0
  51. package/dist/src/fedify_middleware.js +505 -0
  52. package/dist/src/fedify_middleware.js.map +1 -0
  53. package/dist/src/http_context.cjs +79 -0
  54. package/dist/src/http_context.cjs.map +1 -0
  55. package/dist/src/http_context.d.cts +31 -0
  56. package/dist/src/http_context.d.ts +31 -0
  57. package/dist/src/http_context.js +76 -0
  58. package/dist/src/http_context.js.map +1 -0
  59. package/dist/src/logging.cjs +106 -0
  60. package/dist/src/logging.cjs.map +1 -0
  61. package/dist/src/logging.d.cts +45 -0
  62. package/dist/src/logging.d.ts +45 -0
  63. package/dist/src/logging.js +106 -0
  64. package/dist/src/logging.js.map +1 -0
  65. package/dist/src/types.cjs +1 -0
  66. package/dist/src/types.d.cts +269 -0
  67. package/dist/src/types.d.ts +269 -0
  68. package/dist/src/types.js +1 -0
  69. package/dist/src/unusable_authority.cjs +79 -0
  70. package/dist/src/unusable_authority.cjs.map +1 -0
  71. package/dist/src/unusable_authority.js +76 -0
  72. package/dist/src/unusable_authority.js.map +1 -0
  73. package/dist/stubs/config/fedify.stub +105 -0
  74. package/dist/stubs/federation/main.stub +64 -0
  75. package/dist/stubs/main.cjs +16 -0
  76. package/dist/stubs/main.cjs.map +1 -0
  77. package/dist/stubs/main.d.cts +11 -0
  78. package/dist/stubs/main.d.ts +11 -0
  79. package/dist/stubs/main.js +15 -0
  80. package/dist/stubs/main.js.map +1 -0
  81. package/dist/stubs/start/federation.stub +20 -0
  82. package/package.json +124 -0
@@ -0,0 +1,507 @@
1
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
+ const require_src_errors = require('./errors.cjs');
3
+ const require_src_http_context = require('./http_context.cjs');
4
+ const require_unusable_authority = require('./unusable_authority.cjs');
5
+ let node_stream = require("node:stream");
6
+
7
+ //#region src/fedify_middleware.ts
8
+ /**
9
+ * The AdonisJS server middleware that mounts Fedify.
10
+ *
11
+ * The service provider binds {@link FedifyMiddleware} with the `Federation`
12
+ * and `config/fedify.ts`; applications that build their own `Federation` use
13
+ * {@link fedifyMiddleware} instead. Nothing here depends on the container.
14
+ *
15
+ * @module
16
+ */
17
+ /**
18
+ * Context classes whose conflicting `federation` member was already reported.
19
+ */
20
+ const conflicting = new WeakSet();
21
+ /**
22
+ * Methods `new Request()` refuses; in practice only `TRACE` arrives here.
23
+ */
24
+ const FORBIDDEN_METHODS = new Set([
25
+ "CONNECT",
26
+ "TRACE",
27
+ "TRACK"
28
+ ]);
29
+ /**
30
+ * Wraps the raw `IncomingMessage` in a `ReadableStream` that stays inert until
31
+ * first read. The `Request` is built before Fedify decides whether it handles
32
+ * the request; an eager `Readable.toWeb()` would start draining the socket and
33
+ * hang the AdonisJS body parser on every declined `POST`.
34
+ */
35
+ function lazyRequestBody(message) {
36
+ let reader;
37
+ return new ReadableStream({
38
+ async pull(controller) {
39
+ reader ??= node_stream.Readable.toWeb(message).getReader();
40
+ const { done, value } = await reader.read();
41
+ if (done) controller.close();
42
+ else controller.enqueue(value);
43
+ },
44
+ cancel(reason) {
45
+ return reader?.cancel(reason);
46
+ }
47
+ }, { highWaterMark: 0 });
48
+ }
49
+ /**
50
+ * What an authority may not contain: `Host` admits only `uri-host [":" port]`,
51
+ * so anything that could carry a path, query, fragment or userinfo makes the
52
+ * value something other than an authority.
53
+ */
54
+ const NON_AUTHORITY = /[/?#\\@\s]/;
55
+ /**
56
+ * Keeps the first entry of a comma-separated authority (`X-Forwarded-Host`
57
+ * behind two proxies), which `new URL()` would reject.
58
+ *
59
+ * Under `trustProxy`, `authority()` returns `X-Forwarded-Host` verbatim, so a
60
+ * client could send `example.com/x` and shift the path Fedify sees to
61
+ * `/x/users/alice` while AdonisJS still routes the real path — bypassing
62
+ * `ignoreRoutePrefixes`. Anything that could carry a path, query, fragment or
63
+ * userinfo (`good.example@evil.com` parses to the host `evil.com`) is
64
+ * therefore refused outright, which leaves the {@link PLACEHOLDER_HOST}
65
+ * fallback to keep the path intact.
66
+ */
67
+ function normalizeAuthority(host) {
68
+ const comma = host.indexOf(",");
69
+ const authority = (comma < 0 ? host : host.slice(0, comma)).trim();
70
+ return NON_AUTHORITY.test(authority) ? "" : authority;
71
+ }
72
+ /**
73
+ * Reduces a request target to origin form, or `undefined` when it carries no
74
+ * path. An absolute target (`GET http://example.com/users/alice HTTP/1.1`)
75
+ * reaches `req.url` verbatim; only its path and query survive, so the request
76
+ * line cannot pick the host Fedify sees and let an inbox `POST` signed for
77
+ * another server verify here.
78
+ */
79
+ function toOriginForm(target) {
80
+ if (target.startsWith("/")) return target;
81
+ const prefix = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#]*/.exec(target);
82
+ if (prefix === null) return undefined;
83
+ const rest = target.slice(prefix[0].length);
84
+ return rest.startsWith("/") ? rest : `/${rest}`;
85
+ }
86
+ /**
87
+ * Builds the URL for one authority, or `undefined` if it cannot carry one.
88
+ * The target is interpolated, never resolved, so the authority stays the one
89
+ * {@link normalizeAuthority} passed; an empty one is refused because
90
+ * `new URL("http:///users/alice")` silently takes `users` as the host.
91
+ */
92
+ function buildRequestUrl(protocol, authority, target) {
93
+ if (authority === "") return undefined;
94
+ try {
95
+ return new URL(`${protocol}://${authority}${target}`);
96
+ } catch {
97
+ return undefined;
98
+ }
99
+ }
100
+ /**
101
+ * Builds the URL Fedify sees from the raw request target, keeping the
102
+ * percent-encoding Fedify's URI templates expect and the bytes a peer signed.
103
+ * Only the target's path and query are read (see {@link toOriginForm}); the
104
+ * authority comes from {@link normalizeAuthority}, via `authority()` so
105
+ * HTTP/2's `:authority` is covered. Every input is attacker-controlled, so
106
+ * the result only has to exist: the scheme is reduced to `http`/`https`, and
107
+ * an unusable authority falls back to {@link PLACEHOLDER_HOST} rather than a
108
+ * 500. Since that breaks signature verification, the discarded value is
109
+ * returned for the caller to log once it knows Fedify will see the request.
110
+ *
111
+ * The accepted authority is returned verbatim as well, for the `Host` header
112
+ * {@link toFetchRequest} synthesises on HTTP/2: `URL` normalises what it
113
+ * parses (case, default port, punycode) while a signature covers the bytes as
114
+ * sent. It is absent exactly when the placeholder stood in.
115
+ */
116
+ function toRequestUrl(request) {
117
+ const protocol = request.protocol().toLowerCase() === "https" ? "https" : "http";
118
+ const host = request.authority();
119
+ const authority = host == null ? "" : normalizeAuthority(host);
120
+ const target = toOriginForm(request.request.url ?? "/") ?? "/";
121
+ const parsed = buildRequestUrl(protocol, authority, target);
122
+ let url = parsed;
123
+ let unusableAuthority;
124
+ if (url === undefined) {
125
+ url = new URL(`${protocol}://${require_unusable_authority.PLACEHOLDER_HOST}${target}`);
126
+ if (host != null) unusableAuthority = host;
127
+ }
128
+ return {
129
+ url,
130
+ authority: parsed === undefined ? undefined : authority,
131
+ unusableAuthority
132
+ };
133
+ }
134
+ /**
135
+ * Converts an AdonisJS request into a WHATWG `Request` for Fedify.
136
+ *
137
+ * `intended()`, not `method()`, so `_method` spoofing cannot turn an inbox
138
+ * `POST` into a `GET`. Headers are forwarded as they arrived; HTTP/2
139
+ * pseudo-headers are skipped, and since that leaves no `Host` (which
140
+ * signatures may cover), one is synthesised only then, from the authority the
141
+ * URL was built from.
142
+ */
143
+ function toFetchRequest(ctx, url, authority) {
144
+ const request = ctx.request;
145
+ const method = request.intended().toUpperCase();
146
+ const headers = new Headers();
147
+ const rawHeaders = request.headers();
148
+ for (const key of Object.keys(rawHeaders)) {
149
+ if (key.startsWith(":")) continue;
150
+ const value = rawHeaders[key];
151
+ if (Array.isArray(value)) {
152
+ for (const item of value) headers.append(key, item);
153
+ } else if (typeof value === "string") {
154
+ headers.append(key, value);
155
+ }
156
+ }
157
+ if (authority !== undefined && !headers.has("host")) {
158
+ headers.set("host", authority);
159
+ }
160
+ const hasBody = method !== "GET" && method !== "HEAD";
161
+ return new Request(url, {
162
+ method,
163
+ headers,
164
+ body: hasBody ? lazyRequestBody(request.request) : undefined,
165
+ ...{ duplex: "half" }
166
+ });
167
+ }
168
+ /**
169
+ * Copies a WHATWG `Response` produced by Fedify onto the AdonisJS response.
170
+ * `Set-Cookie` goes through `getSetCookie()` because iterating `Headers`
171
+ * folds it; `Vary` is merged rather than copied, so a `Vary: Origin` an
172
+ * upstream middleware set survives Fedify's own `Vary: Accept`; bodyless
173
+ * responses (`204`, `304`, `HEAD`) are not streamed.
174
+ */
175
+ function writeFedifyResponse(response, ctx) {
176
+ ctx.response.status(response.status);
177
+ for (const [key, value] of response.headers) {
178
+ const name = key.toLowerCase();
179
+ if (name === "set-cookie") continue;
180
+ if (name === "vary") {
181
+ mergeIntoVary(ctx, value);
182
+ continue;
183
+ }
184
+ ctx.response.header(key, value);
185
+ }
186
+ for (const cookie of response.headers.getSetCookie()) {
187
+ ctx.response.append("set-cookie", cookie);
188
+ }
189
+ const bodyless = response.body === null || response.status === 204 || response.status === 304 || ctx.request.intended().toUpperCase() === "HEAD";
190
+ if (bodyless) {
191
+ response.body?.cancel().catch(() => {});
192
+ return;
193
+ }
194
+ ctx.response.stream(response.body, (error) => {
195
+ ctx.logger.error({ err: error }, "Failed to stream the Fedify response");
196
+ return ["Internal server error", 500];
197
+ });
198
+ }
199
+ /**
200
+ * Splits a `Vary` value into its entries.
201
+ */
202
+ function parseVary(value) {
203
+ return value.split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
204
+ }
205
+ /**
206
+ * Merges a raw `Vary` value into the response's `Vary`.
207
+ *
208
+ * `ctx.response.header()` replaces rather than appends, so writing `Vary`
209
+ * directly would drop what an upstream middleware or a route already put
210
+ * there (CORS's `Origin`, say) and let a shared cache mix representations.
211
+ *
212
+ * Both of AdonisJS's header layers are read: `header()` buffers a value that
213
+ * is copied onto the Node response when the reply is written, while
214
+ * `ctx.response.vary()` writes to the Node response directly, and
215
+ * `getHeader()` prefers the buffered value. Once `Accept` is buffered before
216
+ * `next()`, a `vary("Origin")` a route calls is therefore invisible through
217
+ * `getHeader()` alone and would be overwritten at write time. The union goes
218
+ * back to the buffered layer, since that is what goes out. `Vary: *` on any
219
+ * side wins.
220
+ */
221
+ function mergeIntoVary(ctx, rawValue) {
222
+ const vary = [];
223
+ const layers = [
224
+ String(ctx.response.getHeader("Vary") ?? ""),
225
+ String(ctx.response.response.getHeader("vary") ?? ""),
226
+ rawValue
227
+ ];
228
+ for (const layer of layers) {
229
+ for (const value of parseVary(layer)) {
230
+ if (value === "*") {
231
+ ctx.response.header("Vary", "*");
232
+ return;
233
+ }
234
+ if (!vary.some((v) => v.toLowerCase() === value.toLowerCase())) {
235
+ vary.push(value);
236
+ }
237
+ }
238
+ }
239
+ if (vary.length > 0) ctx.response.header("Vary", vary.join(", "));
240
+ }
241
+ /**
242
+ * Headers of a discarded response that must not describe the one written over
243
+ * it: cache and validator metadata, which decides how long a shared cache
244
+ * keeps the substitute, and metadata about a body nobody will see. The
245
+ * targeted cache-control family (`CDN-Cache-Control`, `Surrogate-Control`, …)
246
+ * is included because a CDN honours it over `Cache-Control` and largely
247
+ * ignores `Vary`, so a leftover lifetime would keep the 406 cached for
248
+ * ActivityPub clients too. Enumerated rather than matched on a `content-`
249
+ * prefix, which would also take `Content-Security-Policy`.
250
+ */
251
+ const DISCARDED_ENTITY_HEADERS = new Set([
252
+ "age",
253
+ "cache-control",
254
+ "cdn-cache-control",
255
+ "cloudflare-cdn-cache-control",
256
+ "content-digest",
257
+ "content-disposition",
258
+ "content-encoding",
259
+ "content-language",
260
+ "content-length",
261
+ "content-location",
262
+ "content-range",
263
+ "content-type",
264
+ "digest",
265
+ "etag",
266
+ "expires",
267
+ "last-modified",
268
+ "pragma",
269
+ "repr-digest",
270
+ "surrogate-control",
271
+ "surrogate-key"
272
+ ]);
273
+ /**
274
+ * Removes a response header around a quirk of AdonisJS's `removeHeader()`,
275
+ * which drops the buffered entry only when the current value is truthy, so one
276
+ * set to `""` would survive. It also forwards to Node unconditionally, which
277
+ * throws once the head is flushed, so this is only safe while
278
+ * `ctx.response.headersSent` is false.
279
+ */
280
+ function removeResponseHeader(ctx, key) {
281
+ ctx.response.header(key, "-");
282
+ ctx.response.removeHeader(key);
283
+ }
284
+ /**
285
+ * Drops the body a discarded response left buffered, so the one written over
286
+ * it is the one that goes out.
287
+ *
288
+ * `response.send()` assigns `lazyBody.content` alone, and `finish()` reads the
289
+ * other entries after it: a path passed to `download()` would still be
290
+ * streamed in place of the new body, and a readable passed to `stream()`
291
+ * would be forgotten — one leaked file descriptor per request. Resetting
292
+ * `lazyBody` drops both.
293
+ */
294
+ function discardPendingBody(ctx) {
295
+ const stream = ctx.response.outgoingStream;
296
+ ctx.response.lazyBody = {};
297
+ if (stream == null) return;
298
+ if ("destroy" in stream) {
299
+ stream.on("error", () => {});
300
+ if (!stream.destroyed) stream.destroy();
301
+ } else if (!stream.locked) {
302
+ stream.cancel().catch(() => {});
303
+ }
304
+ }
305
+ /**
306
+ * Undoes what a discarded response did to the headers, before another is
307
+ * written over it.
308
+ *
309
+ * `next()` mixes two populations: a header a *later middleware* set
310
+ * (`@adonisjs/cors`'s `Access-Control-Allow-Origin`, say) answers this
311
+ * request and has to survive, while one the *discarded page* set describes a
312
+ * body nobody will see. They are told apart by name: only
313
+ * {@link DISCARDED_ENTITY_HEADERS} are touched, and each is put back exactly
314
+ * as the snapshot had it — removed when the snapshot had none. Restoring
315
+ * every snapshot entry instead would undo the request's own answer, since
316
+ * `response.header()` replaces rather than merges.
317
+ *
318
+ * `Vary` is reconciled by {@link mergeIntoVary} around `next()` instead,
319
+ * since only the union of the snapshot and the current value is right.
320
+ */
321
+ function rollBackDiscardedResponse(ctx, headersBefore) {
322
+ const headersAfter = ctx.response.getHeaders();
323
+ for (const key of DISCARDED_ENTITY_HEADERS) {
324
+ const before = Object.hasOwn(headersBefore, key) ? headersBefore[key] : undefined;
325
+ if (before !== undefined) ctx.response.header(key, before);
326
+ else if (Object.hasOwn(headersAfter, key)) removeResponseHeader(ctx, key);
327
+ }
328
+ }
329
+ /**
330
+ * The response handed back to Fedify after AdonisJS answered a delegated
331
+ * request. Nothing is written from it, but Fedify logs and meters its
332
+ * status, so it reports what AdonisJS answered. `Response` only accepts
333
+ * 200-599, so anything else (a 1xx) is reported as 200.
334
+ */
335
+ function delegatedResponse(status) {
336
+ return new Response(null, { status: status >= 200 && status <= 599 ? status : 200 });
337
+ }
338
+ /**
339
+ * Mounts a `Federation` instance inside an AdonisJS application; the
340
+ * counterpart of `integrateFederation()` in `@fedify/express`.
341
+ *
342
+ * Register it in the **server** stack: Fedify serves paths the router does
343
+ * not know, and the body stream is still intact there for signature
344
+ * verification. An instance holds configuration only, so one serves every
345
+ * request.
346
+ */
347
+ var FedifyMiddleware = class {
348
+ #federation;
349
+ #contextDataFactory;
350
+ #ignoreRoutePrefixes;
351
+ /**
352
+ * @param federation The federation instance to mount.
353
+ * @param args The context-data factory and the options; see
354
+ * {@link FedifyMiddlewareArgs}. The factory sees a pre-routing
355
+ * `HttpContext`; see {@link import('./types.js').ContextDataFactory}.
356
+ */
357
+ constructor(federation, ...args) {
358
+ if (federation == null) throw new require_src_errors.E_MISSING_FEDERATION();
359
+ const [contextDataFactory, options] = args;
360
+ this.#federation = federation;
361
+ this.#contextDataFactory = contextDataFactory ?? (() => undefined);
362
+ this.#ignoreRoutePrefixes = options?.ignoreRoutePrefixes ?? [];
363
+ }
364
+ async handle(ctx, next) {
365
+ const federation = this.#federation;
366
+ const contextClass = ctx.constructor;
367
+ if (!require_src_http_context.ensureFederationGetter(contextClass) && !conflicting.has(contextClass)) {
368
+ conflicting.add(contextClass);
369
+ ctx.logger.error("Something other than @fedify/adonisjs defines \"federation\" on this " + "application's HttpContext, so \"ctx.federation\" is not the Fedify " + "request context. Federation endpoints are unaffected; rename the " + "conflicting macro or getter to use it from controllers");
370
+ }
371
+ const { url, authority, unusableAuthority } = toRequestUrl(ctx.request);
372
+ const path = url.pathname;
373
+ if (this.#ignoreRoutePrefixes.some((prefix) => path.startsWith(prefix)) || FORBIDDEN_METHODS.has(ctx.request.intended().toUpperCase())) {
374
+ require_src_http_context.setFederationContextFactory(ctx, () => {
375
+ throw new require_src_errors.E_FEDIFY_CONTEXT_UNAVAILABLE(path);
376
+ });
377
+ return next();
378
+ }
379
+ if (unusableAuthority !== undefined) {
380
+ require_unusable_authority.warnUnusableAuthority(ctx.logger, unusableAuthority);
381
+ }
382
+ const request = toFetchRequest(ctx, url, authority);
383
+ const contextData = await this.#contextDataFactory(ctx);
384
+ require_src_http_context.setFederationContextFactory(ctx, () => federation.createContext(request, contextData));
385
+ /**
386
+ * What happened while Fedify was deciding: whether a fall-through callback
387
+ * handed the request to AdonisJS, and whether AdonisJS produced nothing of
388
+ * its own so the 406 below is the answer. "Nothing" means no route
389
+ * matched (`ctx.route` is set only on a match) *and* the framework's own
390
+ * 404: a later server middleware (`@adonisjs/cors` on a preflight) or a
391
+ * downstream failure can answer without the router running, and must not
392
+ * be overwritten. A downstream middleware *deliberately* answering 404 on
393
+ * a Fedify-owned path is indistinguishable from that default and is also
394
+ * replaced by the 406.
395
+ *
396
+ * `router.match()` could say before `next()` whether a route exists and
397
+ * spare the rollback, but is deliberately not asked:
398
+ *
399
+ * 1. Every integration follows the deferred-406 contract the manual
400
+ * documents: delegate, and answer 406 only when the framework answered
401
+ * 404 (Hono reads `ctx.res.status` after `next()`, Express `req.route`).
402
+ * Deciding from the route table would give AdonisJS a narrower one.
403
+ *
404
+ * 2. Server middleware answers without a route by design (`@adonisjs/cors`
405
+ * on a preflight, `@adonisjs/static`, a maintenance page), and Fedify
406
+ * calls `onNotAcceptable` for a wildcard `Accept` too -- what curl and
407
+ * a preflight send -- so skipping `next()` would cut those off on every
408
+ * path Fedify owns.
409
+ *
410
+ * 3. `ctx.route` is the framework's own verdict. A lookup here would
411
+ * duplicate `routeFinder`'s preamble and drift silently as the router
412
+ * changes.
413
+ *
414
+ * The price is undoing a response AdonisJS was allowed to start: the
415
+ * headers in {@link DISCARDED_ENTITY_HEADERS} are put back by name, a
416
+ * pending body is dropped, and nothing is touched once the head has been
417
+ * flushed.
418
+ *
419
+ * An object rather than two `let`s so TypeScript does not narrow them.
420
+ */
421
+ const outcome = {
422
+ delegated: false,
423
+ answer406: false
424
+ };
425
+ const response = await federation.fetch(request, {
426
+ contextData,
427
+ /**
428
+ * Fedify has no route for this path: hand the request to AdonisJS.
429
+ * `await next()` resolves even on no match, since `E_ROUTE_NOT_FOUND`
430
+ * goes through the exception handler and renders a 404.
431
+ */
432
+ onNotFound: async () => {
433
+ outcome.delegated = true;
434
+ await next();
435
+ return delegatedResponse(ctx.response.getStatus());
436
+ },
437
+ /**
438
+ * Fedify has a route but cannot satisfy `Accept` (a browser at an actor
439
+ * URL). AdonisJS gets a chance to serve its own representation first;
440
+ * 406 is only the answer if it has nothing either. `Accept` is merged
441
+ * into `Vary` rather than written over it, and the snapshot's value is
442
+ * merged back after `next()`, because a route may have replaced the
443
+ * header meanwhile.
444
+ */
445
+ onNotAcceptable: async () => {
446
+ outcome.delegated = true;
447
+ mergeIntoVary(ctx, "Accept");
448
+ const headersBefore = ctx.response.getHeaders();
449
+ await next();
450
+ mergeIntoVary(ctx, String(headersBefore.vary ?? ""));
451
+ const status = ctx.response.getStatus();
452
+ outcome.answer406 = ctx.route === undefined && status === 404 && !ctx.response.headersSent;
453
+ if (outcome.answer406) rollBackDiscardedResponse(ctx, headersBefore);
454
+ return delegatedResponse(outcome.answer406 ? 406 : status);
455
+ }
456
+ });
457
+ if (outcome.answer406) {
458
+ discardPendingBody(ctx);
459
+ ctx.response.status(406).type("text/plain").send("Not acceptable");
460
+ return;
461
+ }
462
+ if (outcome.delegated) {
463
+ return;
464
+ }
465
+ writeFedifyResponse(response, ctx);
466
+ }
467
+ };
468
+ /**
469
+ * Creates a middleware class around a `Federation` the application built
470
+ * itself, with no container or `config/fedify.ts` involved. This is the
471
+ * package's default export; most applications use the provider instead.
472
+ *
473
+ * @example
474
+ * ```ts
475
+ * // start/kernel.ts
476
+ * import fedifyMiddleware from '@fedify/adonisjs'
477
+ * // A module of the application's own that default-exports its `Federation`.
478
+ * import federation from '../app/federation.js'
479
+ *
480
+ * server.use([
481
+ * async () => ({ default: fedifyMiddleware(federation, (ctx) => ctx) }),
482
+ * ])
483
+ * ```
484
+ *
485
+ * With the default `ContextData` of `HttpContext | null`, pass `(ctx) => ctx`
486
+ * to match the provider; see {@link FedifyMiddlewareArgs}.
487
+ *
488
+ * @returns A {@link FedifyMiddleware} subclass whose constructor takes no
489
+ * arguments, which is what `server.use()` constructs.
490
+ * @param federation The federation instance to mount.
491
+ * @param args The context-data factory and the options.
492
+ */
493
+ function fedifyMiddleware(federation, ...args) {
494
+ if (federation == null) {
495
+ throw new require_src_errors.E_MISSING_FEDERATION("fedifyMiddleware() was called without a Federation instance. Check " + "that the module its first argument comes from actually " + "default-exports the Federation it builds");
496
+ }
497
+ return class ConfiguredFedifyMiddleware extends FedifyMiddleware {
498
+ constructor() {
499
+ super(federation, ...args);
500
+ }
501
+ };
502
+ }
503
+
504
+ //#endregion
505
+ exports.default = FedifyMiddleware;
506
+ exports.fedifyMiddleware = fedifyMiddleware;
507
+ //# sourceMappingURL=fedify_middleware.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fedify_middleware.cjs","names":["Readable","PLACEHOLDER_HOST","E_MISSING_FEDERATION","#federation","#contextDataFactory","#ignoreRoutePrefixes","ensureFederationGetter","E_FEDIFY_CONTEXT_UNAVAILABLE"],"sources":["../../src/fedify_middleware.ts"],"sourcesContent":["/**\n * The AdonisJS server middleware that mounts Fedify.\n *\n * The service provider binds {@link FedifyMiddleware} with the `Federation`\n * and `config/fedify.ts`; applications that build their own `Federation` use\n * {@link fedifyMiddleware} instead. Nothing here depends on the container.\n *\n * @module\n */\nimport { Readable } from \"node:stream\";\n\nimport type { HttpContext } from \"@adonisjs/core/http\";\nimport type { NextFn } from \"@adonisjs/core/types/http\";\nimport type { Federation } from \"@fedify/fedify\";\n\nimport {\n E_FEDIFY_CONTEXT_UNAVAILABLE,\n E_MISSING_FEDERATION,\n} from \"./errors.ts\";\nimport {\n ensureFederationGetter,\n type HttpContextClass,\n setFederationContextFactory,\n} from \"./http_context.ts\";\nimport type { ContextData, ContextDataFactory } from \"./types.ts\";\nimport {\n PLACEHOLDER_HOST,\n warnUnusableAuthority,\n} from \"./unusable_authority.ts\";\n\n/**\n * Options accepted by {@link FedifyMiddleware} and {@link fedifyMiddleware}.\n */\nexport interface FedifyMiddlewareOptions {\n /**\n * URL path prefixes to bypass entirely. See\n * {@link import('./types.js').FedifyConfig.ignoreRoutePrefixes}.\n *\n * @default `[]`\n */\n ignoreRoutePrefixes?: string[];\n}\n\n/**\n * The instance shape of the middleware.\n */\nexport interface FedifyMiddlewareHandler {\n handle(ctx: HttpContext, next: NextFn): Promise<void>;\n}\n\n/**\n * What {@link fedifyMiddleware} returns: a middleware **class**, since\n * `server.use()` constructs entries through the container.\n */\nexport type FedifyMiddlewareClass = new () => FedifyMiddlewareHandler;\n\n/**\n * The arguments after `federation`. The factory defaults to producing\n * `undefined`, so it may only be omitted when `TContextData` admits it;\n * otherwise omitting it is a compile error. The tuple wrapper keeps the\n * conditional from distributing over a union.\n */\nexport type FedifyMiddlewareArgs<TContextData> = [undefined] extends\n [TContextData] ? [\n contextDataFactory?: ContextDataFactory<TContextData>,\n options?: FedifyMiddlewareOptions,\n ]\n : [\n contextDataFactory: ContextDataFactory<TContextData>,\n options?: FedifyMiddlewareOptions,\n ];\n\n/**\n * Context classes whose conflicting `federation` member was already reported.\n */\nconst conflicting = new WeakSet<object>();\n\n/**\n * Methods `new Request()` refuses; in practice only `TRACE` arrives here.\n */\nconst FORBIDDEN_METHODS = new Set([\"CONNECT\", \"TRACE\", \"TRACK\"]);\n\n/**\n * Wraps the raw `IncomingMessage` in a `ReadableStream` that stays inert until\n * first read. The `Request` is built before Fedify decides whether it handles\n * the request; an eager `Readable.toWeb()` would start draining the socket and\n * hang the AdonisJS body parser on every declined `POST`.\n */\nfunction lazyRequestBody(\n message: HttpContext[\"request\"][\"request\"],\n): ReadableStream<Uint8Array> {\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n\n return new ReadableStream<Uint8Array>(\n {\n async pull(controller) {\n reader ??= (Readable.toWeb(message) as ReadableStream<Uint8Array>)\n .getReader();\n\n const { done, value } = await reader.read();\n if (done) controller.close();\n else controller.enqueue(value);\n },\n cancel(reason) {\n return reader?.cancel(reason);\n },\n },\n { highWaterMark: 0 },\n );\n}\n\n/**\n * What an authority may not contain: `Host` admits only `uri-host [\":\" port]`,\n * so anything that could carry a path, query, fragment or userinfo makes the\n * value something other than an authority.\n */\nconst NON_AUTHORITY = /[/?#\\\\@\\s]/;\n\n/**\n * Keeps the first entry of a comma-separated authority (`X-Forwarded-Host`\n * behind two proxies), which `new URL()` would reject.\n *\n * Under `trustProxy`, `authority()` returns `X-Forwarded-Host` verbatim, so a\n * client could send `example.com/x` and shift the path Fedify sees to\n * `/x/users/alice` while AdonisJS still routes the real path — bypassing\n * `ignoreRoutePrefixes`. Anything that could carry a path, query, fragment or\n * userinfo (`good.example@evil.com` parses to the host `evil.com`) is\n * therefore refused outright, which leaves the {@link PLACEHOLDER_HOST}\n * fallback to keep the path intact.\n */\nfunction normalizeAuthority(host: string): string {\n const comma = host.indexOf(\",\");\n const authority = (comma < 0 ? host : host.slice(0, comma)).trim();\n return NON_AUTHORITY.test(authority) ? \"\" : authority;\n}\n\n/**\n * Reduces a request target to origin form, or `undefined` when it carries no\n * path. An absolute target (`GET http://example.com/users/alice HTTP/1.1`)\n * reaches `req.url` verbatim; only its path and query survive, so the request\n * line cannot pick the host Fedify sees and let an inbox `POST` signed for\n * another server verify here.\n */\nfunction toOriginForm(target: string): string | undefined {\n // Origin form already. It is interpolated, not resolved, so `//users/alice`\n // keeps its path instead of reading `users` as a host.\n if (target.startsWith(\"/\")) return target;\n\n // Textually rather than through `new URL()`, which would hand back the\n // *normalised* path or, for a target it refuses, nothing at all --\n // collapsing the request to `/` and hiding the real path from\n // `ignoreRoutePrefixes` and `ctx.federation`.\n const prefix = /^[A-Za-z][A-Za-z0-9+.-]*:\\/\\/[^/?#]*/.exec(target);\n // Asterisk form (`OPTIONS *`) or malformed. Authority form reaches only\n // `CONNECT`, which {@link FORBIDDEN_METHODS} refuses.\n if (prefix === null) return undefined;\n\n const rest = target.slice(prefix[0].length);\n return rest.startsWith(\"/\") ? rest : `/${rest}`;\n}\n\n/**\n * Builds the URL for one authority, or `undefined` if it cannot carry one.\n * The target is interpolated, never resolved, so the authority stays the one\n * {@link normalizeAuthority} passed; an empty one is refused because\n * `new URL(\"http:///users/alice\")` silently takes `users` as the host.\n */\nfunction buildRequestUrl(\n protocol: string,\n authority: string,\n target: string,\n): URL | undefined {\n if (authority === \"\") return undefined;\n\n try {\n return new URL(`${protocol}://${authority}${target}`);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Builds the URL Fedify sees from the raw request target, keeping the\n * percent-encoding Fedify's URI templates expect and the bytes a peer signed.\n * Only the target's path and query are read (see {@link toOriginForm}); the\n * authority comes from {@link normalizeAuthority}, via `authority()` so\n * HTTP/2's `:authority` is covered. Every input is attacker-controlled, so\n * the result only has to exist: the scheme is reduced to `http`/`https`, and\n * an unusable authority falls back to {@link PLACEHOLDER_HOST} rather than a\n * 500. Since that breaks signature verification, the discarded value is\n * returned for the caller to log once it knows Fedify will see the request.\n *\n * The accepted authority is returned verbatim as well, for the `Host` header\n * {@link toFetchRequest} synthesises on HTTP/2: `URL` normalises what it\n * parses (case, default port, punycode) while a signature covers the bytes as\n * sent. It is absent exactly when the placeholder stood in.\n */\nfunction toRequestUrl(\n request: HttpContext[\"request\"],\n): { url: URL; authority?: string; unusableAuthority?: string } {\n // `protocol()` hands back the first `X-Forwarded-Proto` token as it\n // arrived; an upper-case `HTTPS` must not be read as plain `http`.\n const protocol = request.protocol().toLowerCase() === \"https\"\n ? \"https\"\n : \"http\";\n const host = request.authority();\n const authority = host == null ? \"\" : normalizeAuthority(host);\n // A target with no origin form is routed as `/`, which matches no\n // federation route, so AdonisJS answers.\n const target = toOriginForm(request.request.url ?? \"/\") ?? \"/\";\n\n const parsed = buildRequestUrl(protocol, authority, target);\n let url = parsed;\n let unusableAuthority: string | undefined;\n if (url === undefined) {\n // Only the authority can be at fault: the target starts with `/`, and a\n // URL parser in path state takes any bytes after it.\n url = new URL(`${protocol}://${PLACEHOLDER_HOST}${target}`);\n if (host != null) unusableAuthority = host;\n }\n\n return {\n url,\n authority: parsed === undefined ? undefined : authority,\n unusableAuthority,\n };\n}\n\n/**\n * Converts an AdonisJS request into a WHATWG `Request` for Fedify.\n *\n * `intended()`, not `method()`, so `_method` spoofing cannot turn an inbox\n * `POST` into a `GET`. Headers are forwarded as they arrived; HTTP/2\n * pseudo-headers are skipped, and since that leaves no `Host` (which\n * signatures may cover), one is synthesised only then, from the authority the\n * URL was built from.\n */\nfunction toFetchRequest(\n ctx: HttpContext,\n url: URL,\n authority: string | undefined,\n): Request {\n const request = ctx.request;\n const method = request.intended().toUpperCase();\n\n const headers = new Headers();\n const rawHeaders = request.headers();\n // `Object.keys()`, not `for...in`: a polluted prototype must not be forwarded.\n for (const key of Object.keys(rawHeaders)) {\n if (key.startsWith(\":\")) continue;\n const value = rawHeaders[key];\n if (Array.isArray(value)) {\n for (const item of value) headers.append(key, item);\n } else if (typeof value === \"string\") {\n headers.append(key, value);\n }\n }\n\n // Only when absent: an HTTP/1.1 `Host` is what the peer signed and what the\n // verifier reads, so it is never touched (a proxy rewriting `Host` has to\n // preserve it; see the README). On HTTP/2 the authority travels in\n // `:authority`, which the pseudo-header skip dropped, so it is put back\n // here -- verbatim, not `url.host`, since `URL` case-folds, drops a default\n // port and punycodes while a signature covers the bytes as sent. An\n // unusable authority gets the placeholder in the URL only, never a header.\n if (authority !== undefined && !headers.has(\"host\")) {\n headers.set(\"host\", authority);\n }\n\n const hasBody = method !== \"GET\" && method !== \"HEAD\";\n\n return new Request(url, {\n method,\n headers,\n body: hasBody ? lazyRequestBody(request.request) : undefined,\n // Not in the DOM lib's `RequestInit`, but Node requires it for streams.\n ...{ duplex: \"half\" },\n });\n}\n\n/**\n * Copies a WHATWG `Response` produced by Fedify onto the AdonisJS response.\n * `Set-Cookie` goes through `getSetCookie()` because iterating `Headers`\n * folds it; `Vary` is merged rather than copied, so a `Vary: Origin` an\n * upstream middleware set survives Fedify's own `Vary: Accept`; bodyless\n * responses (`204`, `304`, `HEAD`) are not streamed.\n */\nfunction writeFedifyResponse(response: Response, ctx: HttpContext): void {\n ctx.response.status(response.status);\n\n for (const [key, value] of response.headers) {\n const name = key.toLowerCase();\n if (name === \"set-cookie\") continue;\n if (name === \"vary\") {\n mergeIntoVary(ctx, value);\n continue;\n }\n ctx.response.header(key, value);\n }\n\n for (const cookie of response.headers.getSetCookie()) {\n ctx.response.append(\"set-cookie\", cookie);\n }\n\n const bodyless = response.body === null ||\n response.status === 204 ||\n response.status === 304 ||\n // `intended()`: a spoofed `_method=HEAD` must not suppress the body.\n ctx.request.intended().toUpperCase() === \"HEAD\";\n\n if (bodyless) {\n // Do not leave a body Fedify produced anyway (usually for HEAD) dangling;\n // a failed cleanup must not become an unhandled rejection.\n response.body?.cancel().catch(() => {});\n return;\n }\n\n ctx.response.stream(response.body!, (error) => {\n ctx.logger.error({ err: error }, \"Failed to stream the Fedify response\");\n return [\"Internal server error\", 500];\n });\n}\n\n/**\n * Splits a `Vary` value into its entries.\n */\nfunction parseVary(value: string): string[] {\n return value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter((entry) => entry !== \"\");\n}\n\n/**\n * Merges a raw `Vary` value into the response's `Vary`.\n *\n * `ctx.response.header()` replaces rather than appends, so writing `Vary`\n * directly would drop what an upstream middleware or a route already put\n * there (CORS's `Origin`, say) and let a shared cache mix representations.\n *\n * Both of AdonisJS's header layers are read: `header()` buffers a value that\n * is copied onto the Node response when the reply is written, while\n * `ctx.response.vary()` writes to the Node response directly, and\n * `getHeader()` prefers the buffered value. Once `Accept` is buffered before\n * `next()`, a `vary(\"Origin\")` a route calls is therefore invisible through\n * `getHeader()` alone and would be overwritten at write time. The union goes\n * back to the buffered layer, since that is what goes out. `Vary: *` on any\n * side wins.\n */\nfunction mergeIntoVary(ctx: HttpContext, rawValue: string): void {\n const vary: string[] = [];\n const layers = [\n String(ctx.response.getHeader(\"Vary\") ?? \"\"),\n String(ctx.response.response.getHeader(\"vary\") ?? \"\"),\n rawValue,\n ];\n\n for (const layer of layers) {\n for (const value of parseVary(layer)) {\n if (value === \"*\") {\n ctx.response.header(\"Vary\", \"*\");\n return;\n }\n if (!vary.some((v) => v.toLowerCase() === value.toLowerCase())) {\n vary.push(value);\n }\n }\n }\n\n if (vary.length > 0) ctx.response.header(\"Vary\", vary.join(\", \"));\n}\n\n/**\n * Headers of a discarded response that must not describe the one written over\n * it: cache and validator metadata, which decides how long a shared cache\n * keeps the substitute, and metadata about a body nobody will see. The\n * targeted cache-control family (`CDN-Cache-Control`, `Surrogate-Control`, …)\n * is included because a CDN honours it over `Cache-Control` and largely\n * ignores `Vary`, so a leftover lifetime would keep the 406 cached for\n * ActivityPub clients too. Enumerated rather than matched on a `content-`\n * prefix, which would also take `Content-Security-Policy`.\n */\nconst DISCARDED_ENTITY_HEADERS: ReadonlySet<string> = new Set([\n \"age\",\n \"cache-control\",\n \"cdn-cache-control\",\n \"cloudflare-cdn-cache-control\",\n \"content-digest\",\n \"content-disposition\",\n \"content-encoding\",\n \"content-language\",\n \"content-length\",\n \"content-location\",\n \"content-range\",\n \"content-type\",\n \"digest\",\n \"etag\",\n \"expires\",\n \"last-modified\",\n \"pragma\",\n \"repr-digest\",\n \"surrogate-control\",\n \"surrogate-key\",\n]);\n\n/**\n * Removes a response header around a quirk of AdonisJS's `removeHeader()`,\n * which drops the buffered entry only when the current value is truthy, so one\n * set to `\"\"` would survive. It also forwards to Node unconditionally, which\n * throws once the head is flushed, so this is only safe while\n * `ctx.response.headersSent` is false.\n */\nfunction removeResponseHeader(ctx: HttpContext, key: string): void {\n // A non-empty placeholder, so the buffered entry is truthy and deleted.\n ctx.response.header(key, \"-\");\n ctx.response.removeHeader(key);\n}\n\n/**\n * Drops the body a discarded response left buffered, so the one written over\n * it is the one that goes out.\n *\n * `response.send()` assigns `lazyBody.content` alone, and `finish()` reads the\n * other entries after it: a path passed to `download()` would still be\n * streamed in place of the new body, and a readable passed to `stream()`\n * would be forgotten — one leaked file descriptor per request. Resetting\n * `lazyBody` drops both.\n */\nfunction discardPendingBody(ctx: HttpContext): void {\n const stream = ctx.response.outgoingStream;\n ctx.response.lazyBody = {};\n if (stream == null) return;\n\n // `ResponseStream` is either a Node `Readable` or a WHATWG `ReadableStream`.\n if (\"destroy\" in stream) {\n // Nothing else listens, since AdonisJS never attached to the stream, and\n // an unlistened `error` event is an uncaught exception.\n stream.on(\"error\", () => {});\n if (!stream.destroyed) stream.destroy();\n } else if (!stream.locked) {\n // Likewise, a rejected `cancel()` must not become an unhandled rejection.\n stream.cancel().catch(() => {});\n }\n}\n\n/**\n * Undoes what a discarded response did to the headers, before another is\n * written over it.\n *\n * `next()` mixes two populations: a header a *later middleware* set\n * (`@adonisjs/cors`'s `Access-Control-Allow-Origin`, say) answers this\n * request and has to survive, while one the *discarded page* set describes a\n * body nobody will see. They are told apart by name: only\n * {@link DISCARDED_ENTITY_HEADERS} are touched, and each is put back exactly\n * as the snapshot had it — removed when the snapshot had none. Restoring\n * every snapshot entry instead would undo the request's own answer, since\n * `response.header()` replaces rather than merges.\n *\n * `Vary` is reconciled by {@link mergeIntoVary} around `next()` instead,\n * since only the union of the snapshot and the current value is right.\n */\nfunction rollBackDiscardedResponse(\n ctx: HttpContext,\n headersBefore: ReturnType<HttpContext[\"response\"][\"getHeaders\"]>,\n): void {\n const headersAfter = ctx.response.getHeaders();\n\n for (const key of DISCARDED_ENTITY_HEADERS) {\n // `hasOwn()`, not `in`: `constructor` is a legal header name.\n const before = Object.hasOwn(headersBefore, key)\n ? headersBefore[key]\n : undefined;\n\n // Written back whether or not `next()` changed it: `header()` is\n // idempotent, and takes an array, so a repeated header goes back whole.\n if (before !== undefined) ctx.response.header(key, before);\n else if (Object.hasOwn(headersAfter, key)) removeResponseHeader(ctx, key);\n }\n}\n\n/**\n * The response handed back to Fedify after AdonisJS answered a delegated\n * request. Nothing is written from it, but Fedify logs and meters its\n * status, so it reports what AdonisJS answered. `Response` only accepts\n * 200-599, so anything else (a 1xx) is reported as 200.\n */\nfunction delegatedResponse(status: number): Response {\n return new Response(null, {\n status: status >= 200 && status <= 599 ? status : 200,\n });\n}\n\n/**\n * Mounts a `Federation` instance inside an AdonisJS application; the\n * counterpart of `integrateFederation()` in `@fedify/express`.\n *\n * Register it in the **server** stack: Fedify serves paths the router does\n * not know, and the body stream is still intact there for signature\n * verification. An instance holds configuration only, so one serves every\n * request.\n */\nexport default class FedifyMiddleware<TContextData = ContextData>\n implements FedifyMiddlewareHandler {\n #federation: Federation<TContextData>;\n #contextDataFactory: ContextDataFactory<TContextData>;\n #ignoreRoutePrefixes: string[];\n\n /**\n * @param federation The federation instance to mount.\n * @param args The context-data factory and the options; see\n * {@link FedifyMiddlewareArgs}. The factory sees a pre-routing\n * `HttpContext`; see {@link import('./types.js').ContextDataFactory}.\n */\n constructor(\n federation: Federation<TContextData>,\n ...args: FedifyMiddlewareArgs<TContextData>\n ) {\n // The container zero-arg-constructs this class when the object the kernel\n // resolved is not the one the provider bound (a duplicated package copy,\n // or the ESM and CJS builds mixed); fail here rather than on every request.\n if (federation == null) throw new E_MISSING_FEDERATION();\n\n // The conditional tuple is unresolved here; read it through its common shape.\n const [contextDataFactory, options] = args as [\n ContextDataFactory<TContextData>?,\n FedifyMiddlewareOptions?,\n ];\n\n this.#federation = federation;\n this.#contextDataFactory = contextDataFactory ??\n (() => undefined as TContextData);\n this.#ignoreRoutePrefixes = options?.ignoreRoutePrefixes ?? [];\n }\n\n async handle(ctx: HttpContext, next: NextFn): Promise<void> {\n const federation = this.#federation;\n\n // Install the `ctx.federation` getter on this context's class: it covers a\n // duplicated `@adonisjs/core` and the container-free path, which has no\n // provider. A conflicting `federation` member is reported once; the\n // federation endpoints do not need the accessor.\n const contextClass = ctx.constructor as unknown as HttpContextClass;\n if (\n !ensureFederationGetter(contextClass) && !conflicting.has(contextClass)\n ) {\n conflicting.add(contextClass);\n ctx.logger.error(\n 'Something other than @fedify/adonisjs defines \"federation\" on this ' +\n 'application\\'s HttpContext, so \"ctx.federation\" is not the Fedify ' +\n \"request context. Federation endpoints are unaffected; rename the \" +\n \"conflicting macro or getter to use it from controllers\",\n );\n }\n\n // The ignore check and Fedify must see the same URL, so build it once.\n const { url, authority, unusableAuthority } = toRequestUrl(ctx.request);\n const path = url.pathname;\n\n if (\n this.#ignoreRoutePrefixes.some((prefix) => path.startsWith(prefix)) ||\n FORBIDDEN_METHODS.has(ctx.request.intended().toUpperCase())\n ) {\n // Opted out, or impossible to represent as a `Request`.\n setFederationContextFactory(ctx, () => {\n throw new E_FEDIFY_CONTEXT_UNAVAILABLE(path);\n });\n return next();\n }\n\n // Only past the bypass: on an opted-out path Fedify never sees the URL,\n // so the placeholder is harmless.\n if (unusableAuthority !== undefined) {\n warnUnusableAuthority(ctx.logger, unusableAuthority);\n }\n\n const request = toFetchRequest(ctx, url, authority);\n\n // `federation.fetch()` takes `contextData` by value, so the factory runs\n // before the router.\n const contextData = await this.#contextDataFactory(ctx);\n\n // Also for requests Fedify declines: a controller serving the HTML half of\n // a shared URL needs it. The getter creates the context lazily.\n setFederationContextFactory(\n ctx,\n () => federation.createContext(request, contextData),\n );\n\n /**\n * What happened while Fedify was deciding: whether a fall-through callback\n * handed the request to AdonisJS, and whether AdonisJS produced nothing of\n * its own so the 406 below is the answer. \"Nothing\" means no route\n * matched (`ctx.route` is set only on a match) *and* the framework's own\n * 404: a later server middleware (`@adonisjs/cors` on a preflight) or a\n * downstream failure can answer without the router running, and must not\n * be overwritten. A downstream middleware *deliberately* answering 404 on\n * a Fedify-owned path is indistinguishable from that default and is also\n * replaced by the 406.\n *\n * `router.match()` could say before `next()` whether a route exists and\n * spare the rollback, but is deliberately not asked:\n *\n * 1. Every integration follows the deferred-406 contract the manual\n * documents: delegate, and answer 406 only when the framework answered\n * 404 (Hono reads `ctx.res.status` after `next()`, Express `req.route`).\n * Deciding from the route table would give AdonisJS a narrower one.\n *\n * 2. Server middleware answers without a route by design (`@adonisjs/cors`\n * on a preflight, `@adonisjs/static`, a maintenance page), and Fedify\n * calls `onNotAcceptable` for a wildcard `Accept` too -- what curl and\n * a preflight send -- so skipping `next()` would cut those off on every\n * path Fedify owns.\n *\n * 3. `ctx.route` is the framework's own verdict. A lookup here would\n * duplicate `routeFinder`'s preamble and drift silently as the router\n * changes.\n *\n * The price is undoing a response AdonisJS was allowed to start: the\n * headers in {@link DISCARDED_ENTITY_HEADERS} are put back by name, a\n * pending body is dropped, and nothing is touched once the head has been\n * flushed.\n *\n * An object rather than two `let`s so TypeScript does not narrow them.\n */\n const outcome: { delegated: boolean; answer406: boolean } = {\n delegated: false,\n answer406: false,\n };\n\n const response = await federation.fetch(request, {\n contextData,\n\n /**\n * Fedify has no route for this path: hand the request to AdonisJS.\n * `await next()` resolves even on no match, since `E_ROUTE_NOT_FOUND`\n * goes through the exception handler and renders a 404.\n */\n onNotFound: async () => {\n outcome.delegated = true;\n await next();\n return delegatedResponse(ctx.response.getStatus());\n },\n\n /**\n * Fedify has a route but cannot satisfy `Accept` (a browser at an actor\n * URL). AdonisJS gets a chance to serve its own representation first;\n * 406 is only the answer if it has nothing either. `Accept` is merged\n * into `Vary` rather than written over it, and the snapshot's value is\n * merged back after `next()`, because a route may have replaced the\n * header meanwhile.\n */\n onNotAcceptable: async () => {\n outcome.delegated = true;\n mergeIntoVary(ctx, \"Accept\");\n // A value snapshot, not a live view: `getHeaders()` builds a fresh\n // object on every call.\n const headersBefore = ctx.response.getHeaders();\n await next();\n\n // Put back what `Vary` carried before `next()`, `Accept` included. A\n // superset only costs cache hits; a missing entry lets a cache mix the\n // two representations up.\n mergeIntoVary(ctx, String(headersBefore.vary ?? \"\"));\n\n const status = ctx.response.getStatus();\n // Not once the head is flushed: the 406 could not be written over it,\n // and `removeHeader()` would raise `ERR_HTTP_HEADERS_SENT`.\n outcome.answer406 = ctx.route === undefined && status === 404 &&\n !ctx.response.headersSent;\n\n if (outcome.answer406) rollBackDiscardedResponse(ctx, headersBefore);\n\n // The 406 itself is produced below; this only feeds Fedify's log.\n return delegatedResponse(outcome.answer406 ? 406 : status);\n },\n });\n\n // Fedify owns the path, cannot satisfy `Accept`, and AdonisJS had nothing\n // either. `Vary` already carries `Accept`.\n if (outcome.answer406) {\n // Before `send()`, which only sets the buffered *content*: a 404 page\n // rendered with `download()` or `stream()` would otherwise still go out,\n // or leak its readable.\n discardPendingBody(ctx);\n ctx.response\n .status(406)\n .type(\"text/plain\")\n .send(\"Not acceptable\");\n return;\n }\n\n if (outcome.delegated) {\n // AdonisJS produced the response (a matched route or its own 404 page).\n return;\n }\n\n writeFedifyResponse(response, ctx);\n }\n}\n\n/**\n * Creates a middleware class around a `Federation` the application built\n * itself, with no container or `config/fedify.ts` involved. This is the\n * package's default export; most applications use the provider instead.\n *\n * @example\n * ```ts\n * // start/kernel.ts\n * import fedifyMiddleware from '@fedify/adonisjs'\n * // A module of the application's own that default-exports its `Federation`.\n * import federation from '../app/federation.js'\n *\n * server.use([\n * async () => ({ default: fedifyMiddleware(federation, (ctx) => ctx) }),\n * ])\n * ```\n *\n * With the default `ContextData` of `HttpContext | null`, pass `(ctx) => ctx`\n * to match the provider; see {@link FedifyMiddlewareArgs}.\n *\n * @returns A {@link FedifyMiddleware} subclass whose constructor takes no\n * arguments, which is what `server.use()` constructs.\n * @param federation The federation instance to mount.\n * @param args The context-data factory and the options.\n */\nexport function fedifyMiddleware<TContextData>(\n federation: Federation<TContextData>,\n ...args: FedifyMiddlewareArgs<TContextData>\n): FedifyMiddlewareClass {\n // Here rather than in the constructor guard, which fires only on the first\n // request and blames the provider or a duplicated package copy; a factory\n // caller's likely mistake is a module that default-exports nothing.\n if (federation == null) {\n throw new E_MISSING_FEDERATION(\n \"fedifyMiddleware() was called without a Federation instance. Check \" +\n \"that the module its first argument comes from actually \" +\n \"default-exports the Federation it builds\",\n );\n }\n\n // Named so container errors and tracing show something readable.\n return class ConfiguredFedifyMiddleware\n extends FedifyMiddleware<TContextData> {\n constructor() {\n super(federation, ...args);\n }\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA2EA,MAAM,cAAc,IAAI,QAAgB;;;;AAKxC,MAAM,oBAAoB,IAAI,IAAI;CAAC;CAAW;CAAS;AAAO,CAAC;;;;;;;AAQ/D,SAAS,gBACP,SAC4B;CAC5B,IAAI;CAEJ,OAAO,IAAI,eACT;EACE,MAAM,KAAK,YAAY;GACrB,WAAYA,qBAAS,MAAM,OAAO,CAAC,CAChC,UAAU;GAEb,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MAAM,WAAW,MAAM;QACtB,WAAW,QAAQ,KAAK;EAC/B;EACA,OAAO,QAAQ;GACb,OAAO,QAAQ,OAAO,MAAM;EAC9B;CACF,GACA,EAAE,eAAe,EAAE,CACrB;AACF;;;;;;AAOA,MAAM,gBAAgB;;;;;;;;;;;;;AActB,SAAS,mBAAmB,MAAsB;CAChD,MAAM,QAAQ,KAAK,QAAQ,GAAG;CAC9B,MAAM,aAAa,QAAQ,IAAI,OAAO,KAAK,MAAM,GAAG,KAAK,EAAC,CAAE,KAAK;CACjE,OAAO,cAAc,KAAK,SAAS,IAAI,KAAK;AAC9C;;;;;;;;AASA,SAAS,aAAa,QAAoC;CAGxD,IAAI,OAAO,WAAW,GAAG,GAAG,OAAO;CAMnC,MAAM,SAAS,uCAAuC,KAAK,MAAM;CAGjE,IAAI,WAAW,MAAM,OAAO;CAE5B,MAAM,OAAO,OAAO,MAAM,OAAO,EAAE,CAAC,MAAM;CAC1C,OAAO,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI;AAC3C;;;;;;;AAQA,SAAS,gBACP,UACA,WACA,QACiB;CACjB,IAAI,cAAc,IAAI,OAAO;CAE7B,IAAI;EACF,OAAO,IAAI,IAAI,GAAG,SAAS,KAAK,YAAY,QAAQ;CACtD,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAS,aACP,SAC8D;CAG9D,MAAM,WAAW,QAAQ,SAAS,CAAC,CAAC,YAAY,MAAM,UAClD,UACA;CACJ,MAAM,OAAO,QAAQ,UAAU;CAC/B,MAAM,YAAY,QAAQ,OAAO,KAAK,mBAAmB,IAAI;CAG7D,MAAM,SAAS,aAAa,QAAQ,QAAQ,OAAO,GAAG,KAAK;CAE3D,MAAM,SAAS,gBAAgB,UAAU,WAAW,MAAM;CAC1D,IAAI,MAAM;CACV,IAAI;CACJ,IAAI,QAAQ,WAAW;EAGrB,MAAM,IAAI,IAAI,GAAG,SAAS,KAAKC,8CAAmB,QAAQ;EAC1D,IAAI,QAAQ,MAAM,oBAAoB;CACxC;CAEA,OAAO;EACL;EACA,WAAW,WAAW,YAAY,YAAY;EAC9C;CACF;AACF;;;;;;;;;;AAWA,SAAS,eACP,KACA,KACA,WACS;CACT,MAAM,UAAU,IAAI;CACpB,MAAM,SAAS,QAAQ,SAAS,CAAC,CAAC,YAAY;CAE9C,MAAM,UAAU,IAAI,QAAQ;CAC5B,MAAM,aAAa,QAAQ,QAAQ;CAEnC,KAAK,MAAM,OAAO,OAAO,KAAK,UAAU,GAAG;EACzC,IAAI,IAAI,WAAW,GAAG,GAAG;EACzB,MAAM,QAAQ,WAAW;EACzB,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,KAAK,MAAM,QAAQ,OAAO,QAAQ,OAAO,KAAK,IAAI;EACpD,OAAO,IAAI,OAAO,UAAU,UAAU;GACpC,QAAQ,OAAO,KAAK,KAAK;EAC3B;CACF;CASA,IAAI,cAAc,aAAa,CAAC,QAAQ,IAAI,MAAM,GAAG;EACnD,QAAQ,IAAI,QAAQ,SAAS;CAC/B;CAEA,MAAM,UAAU,WAAW,SAAS,WAAW;CAE/C,OAAO,IAAI,QAAQ,KAAK;EACtB;EACA;EACA,MAAM,UAAU,gBAAgB,QAAQ,OAAO,IAAI;EAEnD,GAAG,EAAE,QAAQ,OAAO;CACtB,CAAC;AACH;;;;;;;;AASA,SAAS,oBAAoB,UAAoB,KAAwB;CACvE,IAAI,SAAS,OAAO,SAAS,MAAM;CAEnC,KAAK,MAAM,CAAC,KAAK,UAAU,SAAS,SAAS;EAC3C,MAAM,OAAO,IAAI,YAAY;EAC7B,IAAI,SAAS,cAAc;EAC3B,IAAI,SAAS,QAAQ;GACnB,cAAc,KAAK,KAAK;GACxB;EACF;EACA,IAAI,SAAS,OAAO,KAAK,KAAK;CAChC;CAEA,KAAK,MAAM,UAAU,SAAS,QAAQ,aAAa,GAAG;EACpD,IAAI,SAAS,OAAO,cAAc,MAAM;CAC1C;CAEA,MAAM,WAAW,SAAS,SAAS,QACjC,SAAS,WAAW,OACpB,SAAS,WAAW,OAEpB,IAAI,QAAQ,SAAS,CAAC,CAAC,YAAY,MAAM;CAE3C,IAAI,UAAU;EAGZ,SAAS,MAAM,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;EACtC;CACF;CAEA,IAAI,SAAS,OAAO,SAAS,OAAQ,UAAU;EAC7C,IAAI,OAAO,MAAM,EAAE,KAAK,MAAM,GAAG,sCAAsC;EACvE,OAAO,CAAC,yBAAyB,GAAG;CACtC,CAAC;AACH;;;;AAKA,SAAS,UAAU,OAAyB;CAC1C,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,KAAK,UAAU,MAAM,KAAK,CAAC,CAAC,CAC5B,QAAQ,UAAU,UAAU,EAAE;AACnC;;;;;;;;;;;;;;;;;AAkBA,SAAS,cAAc,KAAkB,UAAwB;CAC/D,MAAM,OAAiB,CAAC;CACxB,MAAM,SAAS;EACb,OAAO,IAAI,SAAS,UAAU,MAAM,KAAK,EAAE;EAC3C,OAAO,IAAI,SAAS,SAAS,UAAU,MAAM,KAAK,EAAE;EACpD;CACF;CAEA,KAAK,MAAM,SAAS,QAAQ;EAC1B,KAAK,MAAM,SAAS,UAAU,KAAK,GAAG;GACpC,IAAI,UAAU,KAAK;IACjB,IAAI,SAAS,OAAO,QAAQ,GAAG;IAC/B;GACF;GACA,IAAI,CAAC,KAAK,MAAM,MAAM,EAAE,YAAY,MAAM,MAAM,YAAY,CAAC,GAAG;IAC9D,KAAK,KAAK,KAAK;GACjB;EACF;CACF;CAEA,IAAI,KAAK,SAAS,GAAG,IAAI,SAAS,OAAO,QAAQ,KAAK,KAAK,IAAI,CAAC;AAClE;;;;;;;;;;;AAYA,MAAM,2BAAgD,IAAI,IAAI;CAC5D;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;;AASD,SAAS,qBAAqB,KAAkB,KAAmB;CAEjE,IAAI,SAAS,OAAO,KAAK,GAAG;CAC5B,IAAI,SAAS,aAAa,GAAG;AAC/B;;;;;;;;;;;AAYA,SAAS,mBAAmB,KAAwB;CAClD,MAAM,SAAS,IAAI,SAAS;CAC5B,IAAI,SAAS,WAAW,CAAC;CACzB,IAAI,UAAU,MAAM;CAGpB,IAAI,aAAa,QAAQ;EAGvB,OAAO,GAAG,eAAe,CAAC,CAAC;EAC3B,IAAI,CAAC,OAAO,WAAW,OAAO,QAAQ;CACxC,OAAO,IAAI,CAAC,OAAO,QAAQ;EAEzB,OAAO,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;CAChC;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAS,0BACP,KACA,eACM;CACN,MAAM,eAAe,IAAI,SAAS,WAAW;CAE7C,KAAK,MAAM,OAAO,0BAA0B;EAE1C,MAAM,SAAS,OAAO,OAAO,eAAe,GAAG,IAC3C,cAAc,OACd;EAIJ,IAAI,WAAW,WAAW,IAAI,SAAS,OAAO,KAAK,MAAM;OACpD,IAAI,OAAO,OAAO,cAAc,GAAG,GAAG,qBAAqB,KAAK,GAAG;CAC1E;AACF;;;;;;;AAQA,SAAS,kBAAkB,QAA0B;CACnD,OAAO,IAAI,SAAS,MAAM,EACxB,QAAQ,UAAU,OAAO,UAAU,MAAM,SAAS,IACpD,CAAC;AACH;;;;;;;;;;AAWA,IAAqB,mBAArB,MACqC;CACnC;CACA;CACA;;;;;;;CAQA,YACE,YACA,GAAG,MACH;EAIA,IAAI,cAAc,MAAM,MAAM,IAAIC,wCAAqB;EAGvD,MAAM,CAAC,oBAAoB,WAAW;EAKtC,KAAKC,cAAc;EACnB,KAAKC,sBAAsB,6BAClB;EACT,KAAKC,uBAAuB,SAAS,uBAAuB,CAAC;CAC/D;CAEA,MAAM,OAAO,KAAkB,MAA6B;EAC1D,MAAM,aAAa,KAAKF;EAMxB,MAAM,eAAe,IAAI;EACzB,IACE,CAACG,gDAAuB,YAAY,KAAK,CAAC,YAAY,IAAI,YAAY,GACtE;GACA,YAAY,IAAI,YAAY;GAC5B,IAAI,OAAO,MACT,0EACE,wEACA,sEACA,wDACJ;EACF;EAGA,MAAM,EAAE,KAAK,WAAW,sBAAsB,aAAa,IAAI,OAAO;EACtE,MAAM,OAAO,IAAI;EAEjB,IACE,KAAKD,qBAAqB,MAAM,WAAW,KAAK,WAAW,MAAM,CAAC,KAClE,kBAAkB,IAAI,IAAI,QAAQ,SAAS,CAAC,CAAC,YAAY,CAAC,GAC1D;GAEA,qDAA4B,WAAW;IACrC,MAAM,IAAIE,gDAA6B,IAAI;GAC7C,CAAC;GACD,OAAO,KAAK;EACd;EAIA,IAAI,sBAAsB,WAAW;GACnC,iDAAsB,IAAI,QAAQ,iBAAiB;EACrD;EAEA,MAAM,UAAU,eAAe,KAAK,KAAK,SAAS;EAIlD,MAAM,cAAc,MAAM,KAAKH,oBAAoB,GAAG;EAItD,qDACE,WACM,WAAW,cAAc,SAAS,WAAW,CACrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsCA,MAAM,UAAsD;GAC1D,WAAW;GACX,WAAW;EACb;EAEA,MAAM,WAAW,MAAM,WAAW,MAAM,SAAS;GAC/C;;;;;;GAOA,YAAY,YAAY;IACtB,QAAQ,YAAY;IACpB,MAAM,KAAK;IACX,OAAO,kBAAkB,IAAI,SAAS,UAAU,CAAC;GACnD;;;;;;;;;GAUA,iBAAiB,YAAY;IAC3B,QAAQ,YAAY;IACpB,cAAc,KAAK,QAAQ;IAG3B,MAAM,gBAAgB,IAAI,SAAS,WAAW;IAC9C,MAAM,KAAK;IAKX,cAAc,KAAK,OAAO,cAAc,QAAQ,EAAE,CAAC;IAEnD,MAAM,SAAS,IAAI,SAAS,UAAU;IAGtC,QAAQ,YAAY,IAAI,UAAU,aAAa,WAAW,OACxD,CAAC,IAAI,SAAS;IAEhB,IAAI,QAAQ,WAAW,0BAA0B,KAAK,aAAa;IAGnE,OAAO,kBAAkB,QAAQ,YAAY,MAAM,MAAM;GAC3D;EACF,CAAC;EAID,IAAI,QAAQ,WAAW;GAIrB,mBAAmB,GAAG;GACtB,IAAI,SACD,OAAO,GAAG,CAAC,CACX,KAAK,YAAY,CAAC,CAClB,KAAK,gBAAgB;GACxB;EACF;EAEA,IAAI,QAAQ,WAAW;GAErB;EACF;EAEA,oBAAoB,UAAU,GAAG;CACnC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,iBACd,YACA,GAAG,MACoB;CAIvB,IAAI,cAAc,MAAM;EACtB,MAAM,IAAIF,wCACR,wEACE,4DACA,0CACJ;CACF;CAGA,OAAO,MAAM,mCACH,iBAA+B;EACvC,cAAc;GACZ,MAAM,YAAY,GAAG,IAAI;EAC3B;CACF;AACF"}
@@ -0,0 +1,85 @@
1
+ import { ContextData, ContextDataFactory } from "./types.cjs";
2
+ import { HttpContext } from "@adonisjs/core/http";
3
+ import { Federation } from "@fedify/fedify";
4
+ import { NextFn } from "@adonisjs/core/types/http";
5
+
6
+ //#region src/fedify_middleware.d.ts
7
+ /**
8
+ * Options accepted by {@link FedifyMiddleware} and {@link fedifyMiddleware}.
9
+ */
10
+ interface FedifyMiddlewareOptions {
11
+ /**
12
+ * URL path prefixes to bypass entirely. See
13
+ * {@link import('./types.js').FedifyConfig.ignoreRoutePrefixes}.
14
+ *
15
+ * @default `[]`
16
+ */
17
+ ignoreRoutePrefixes?: string[];
18
+ }
19
+ /**
20
+ * The instance shape of the middleware.
21
+ */
22
+ interface FedifyMiddlewareHandler {
23
+ handle(ctx: HttpContext, next: NextFn): Promise<void>;
24
+ }
25
+ /**
26
+ * What {@link fedifyMiddleware} returns: a middleware **class**, since
27
+ * `server.use()` constructs entries through the container.
28
+ */
29
+ type FedifyMiddlewareClass = new () => FedifyMiddlewareHandler;
30
+ /**
31
+ * The arguments after `federation`. The factory defaults to producing
32
+ * `undefined`, so it may only be omitted when `TContextData` admits it;
33
+ * otherwise omitting it is a compile error. The tuple wrapper keeps the
34
+ * conditional from distributing over a union.
35
+ */
36
+ type FedifyMiddlewareArgs<TContextData> = [undefined] extends [TContextData] ? [contextDataFactory?: ContextDataFactory<TContextData>, options?: FedifyMiddlewareOptions] : [contextDataFactory: ContextDataFactory<TContextData>, options?: FedifyMiddlewareOptions];
37
+ /**
38
+ * Mounts a `Federation` instance inside an AdonisJS application; the
39
+ * counterpart of `integrateFederation()` in `@fedify/express`.
40
+ *
41
+ * Register it in the **server** stack: Fedify serves paths the router does
42
+ * not know, and the body stream is still intact there for signature
43
+ * verification. An instance holds configuration only, so one serves every
44
+ * request.
45
+ */
46
+ declare class FedifyMiddleware<TContextData = ContextData> implements FedifyMiddlewareHandler {
47
+ #private;
48
+ /**
49
+ * @param federation The federation instance to mount.
50
+ * @param args The context-data factory and the options; see
51
+ * {@link FedifyMiddlewareArgs}. The factory sees a pre-routing
52
+ * `HttpContext`; see {@link import('./types.js').ContextDataFactory}.
53
+ */
54
+ constructor(federation: Federation<TContextData>, ...args: FedifyMiddlewareArgs<TContextData>);
55
+ handle(ctx: HttpContext, next: NextFn): Promise<void>;
56
+ }
57
+ /**
58
+ * Creates a middleware class around a `Federation` the application built
59
+ * itself, with no container or `config/fedify.ts` involved. This is the
60
+ * package's default export; most applications use the provider instead.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * // start/kernel.ts
65
+ * import fedifyMiddleware from '@fedify/adonisjs'
66
+ * // A module of the application's own that default-exports its `Federation`.
67
+ * import federation from '../app/federation.js'
68
+ *
69
+ * server.use([
70
+ * async () => ({ default: fedifyMiddleware(federation, (ctx) => ctx) }),
71
+ * ])
72
+ * ```
73
+ *
74
+ * With the default `ContextData` of `HttpContext | null`, pass `(ctx) => ctx`
75
+ * to match the provider; see {@link FedifyMiddlewareArgs}.
76
+ *
77
+ * @returns A {@link FedifyMiddleware} subclass whose constructor takes no
78
+ * arguments, which is what `server.use()` constructs.
79
+ * @param federation The federation instance to mount.
80
+ * @param args The context-data factory and the options.
81
+ */
82
+ declare function fedifyMiddleware<TContextData>(federation: Federation<TContextData>, ...args: FedifyMiddlewareArgs<TContextData>): FedifyMiddlewareClass;
83
+ //#endregion
84
+ export { FedifyMiddlewareArgs, FedifyMiddlewareClass, FedifyMiddlewareHandler, FedifyMiddlewareOptions, FedifyMiddleware as default, fedifyMiddleware };
85
+ //# sourceMappingURL=fedify_middleware.d.cts.map