@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,269 @@
1
+ import { E_FEDERATION_NOT_READY, E_INVALID_FEDIFY_CONFIG } from "../src/errors.js";
2
+ import { installFederationGetter } from "../src/http_context.js";
3
+ import FedifyMiddleware from "../src/fedify_middleware.js";
4
+ import { configureFedifyLogging } from "../src/logging.js";
5
+ import { builder } from "../src/builder.js";
6
+ import { configProvider } from "@adonisjs/core";
7
+ import { reset } from "@logtape/logtape";
8
+
9
+ //#region providers/fedify_provider.ts
10
+ /**
11
+ * The `@fedify/adonisjs` service provider; see {@link FedifyProvider}.
12
+ *
13
+ * @module
14
+ */
15
+ /**
16
+ * How long a request that arrives before `ready()` waits for the federation.
17
+ *
18
+ * Generous, since it only needs to outlast earlier providers' `ready()` hooks.
19
+ * It turns a `ready()` that never comes (an earlier provider waiting on this
20
+ * server) into an error instead of a request that never completes.
21
+ */
22
+ const READY_TIMEOUT_MS = 3e4;
23
+ /**
24
+ * Memoises an asynchronous factory, caching successes only.
25
+ *
26
+ * Used for the container bindings instead of `container.singleton()`, which
27
+ * also caches *rejections*: one early, swallowed `make('fedify')` would pin
28
+ * `E_FEDERATION_NOT_READY` for the life of the process. A failed attempt is
29
+ * forgotten instead, so the next caller runs the factory again. `forget()`
30
+ * drops a cached success once the caller knows it is stale.
31
+ */
32
+ function memoizeSuccess(factory) {
33
+ let cached;
34
+ const memo = () => {
35
+ cached ??= factory().catch((error) => {
36
+ cached = undefined;
37
+ throw error;
38
+ });
39
+ return cached;
40
+ };
41
+ return Object.assign(memo, { forget: () => {
42
+ cached = undefined;
43
+ } });
44
+ }
45
+ /**
46
+ * The service provider registered from `adonisrc.ts` as
47
+ * `@fedify/adonisjs/fedify_provider`.
48
+ *
49
+ * Binds `fedify.builder`, `fedify.config`, `fedify` and
50
+ * {@link FedifyMiddleware}, installs `ctx.federation`, bridges Fedify's logs
51
+ * into the AdonisJS logger, builds the federation after the preload files
52
+ * have run, and drains the task queue in the web process.
53
+ */
54
+ var FedifyProvider = class {
55
+ /**
56
+ * Aborts the queue worker on shutdown.
57
+ */
58
+ #queueController = new AbortController();
59
+ /**
60
+ * The `startQueue()` promise, settled once every listener has stopped.
61
+ * `shutdown()` returns it so AdonisJS waits for the workers. Undefined when
62
+ * this process does not drain the queue.
63
+ */
64
+ #queue;
65
+ /**
66
+ * Whether this provider configured LogTape and must undo it on shutdown.
67
+ *
68
+ * LogTape's configuration is process-global and its sink closes over *this*
69
+ * application's `Logger`. Left in place, a second application in the same
70
+ * process (the test runner, a dev-server restart) would find LogTape already
71
+ * configured and keep logging into the terminated application's logger.
72
+ */
73
+ #loggingConfigured = false;
74
+ /**
75
+ * Whether the builder may be sealed into a `Federation` object.
76
+ *
77
+ * Dispatchers are registered in preload files, which AdonisJS imports *after*
78
+ * every provider's `boot()` and `start()`. Building earlier would yield a
79
+ * federation with no routes and 404s as the only symptom, so an early build
80
+ * fails with an explanatory error instead.
81
+ */
82
+ #buildable = false;
83
+ /**
84
+ * Whether the HTTP server has started accepting connections.
85
+ *
86
+ * AdonisJS listens inside `start()` and runs `ready()` hooks afterwards, so a
87
+ * request (a health probe, `fedify tunnel`) can arrive before this provider
88
+ * is ready. Such a request is the one caller allowed to *wait* for the
89
+ * federation, since `ready()` is on its way; any other caller (a `start()`
90
+ * hook, a preload file, an Ace command) fails fast because nothing would
91
+ * make it ready while they wait.
92
+ */
93
+ #listening = false;
94
+ /**
95
+ * Settles once `ready()` has built the federation: resolved on success,
96
+ * rejected with the configuration error, or with `E_FEDERATION_NOT_READY`
97
+ * when the application shuts down first or {@link READY_TIMEOUT_MS} passes.
98
+ */
99
+ #ready = Promise.withResolvers();
100
+ #readyTimer;
101
+ /**
102
+ * The memoised middleware binding, kept here so that a failed `ready()` can
103
+ * forget it.
104
+ *
105
+ * A request that arrives while `ready()` is running builds its middleware
106
+ * from a federation that is buildable at that moment. Should `ready()` fail
107
+ * afterwards, that middleware would otherwise stay cached and keep serving
108
+ * federation traffic in an application whose `make('fedify')` fails fast and
109
+ * whose queue never started.
110
+ */
111
+ #middleware;
112
+ app;
113
+ /**
114
+ * Longhand rather than a parameter property, which Node's type stripping
115
+ * does not support; the tests run these sources through it.
116
+ */
117
+ constructor(app) {
118
+ this.app = app;
119
+ this.#ready.promise.catch(() => {});
120
+ }
121
+ /**
122
+ * Registers the container bindings.
123
+ */
124
+ register() {
125
+ /**
126
+ * Bound as a value: the builder is a module-level singleton that
127
+ * application code also imports directly, and both routes must reach the
128
+ * same object.
129
+ */
130
+ this.app.container.bindValue("fedify.builder", builder);
131
+ this.app.container.bind("fedify.config", memoizeSuccess(async () => {
132
+ const provider = this.app.config.get("fedify");
133
+ const config = await configProvider.resolve(this.app, provider);
134
+ if (!config) {
135
+ throw new E_INVALID_FEDIFY_CONFIG();
136
+ }
137
+ return config;
138
+ }));
139
+ const buildFederation = memoizeSuccess(async () => {
140
+ const config = await this.app.container.make("fedify.config");
141
+ const federationBuilder = await this.app.container.make("fedify.builder");
142
+ return federationBuilder.build(config);
143
+ });
144
+ this.app.container.bind("fedify", () => {
145
+ if (!this.#buildable) {
146
+ throw new E_FEDERATION_NOT_READY();
147
+ }
148
+ return buildFederation();
149
+ });
150
+ /**
151
+ * The middleware, built with the federation and configuration in scope, as
152
+ * `@adonisjs/cors` and `@adonisjs/session` do. `server.use()` resolves
153
+ * the class through the container on every request, which lands here.
154
+ *
155
+ * A plain `bind()` plus {@link memoizeSuccess} rather than `singleton()`
156
+ * (see the helper), so every request shares one instance.
157
+ *
158
+ * A request that arrives after the server listens but before `ready()` has
159
+ * built the federation waits for it (see {@link #listening}); any other
160
+ * caller gets `E_FEDERATION_NOT_READY` at once.
161
+ */
162
+ this.#middleware = memoizeSuccess(async () => {
163
+ if (!this.#buildable) {
164
+ if (!this.#listening) throw new E_FEDERATION_NOT_READY();
165
+ await this.#ready.promise;
166
+ }
167
+ const federation = await this.app.container.make("fedify");
168
+ const config = await this.app.container.make("fedify.config");
169
+ return new FedifyMiddleware(federation, config.contextDataFactory, { ignoreRoutePrefixes: config.ignoreRoutePrefixes });
170
+ });
171
+ this.app.container.bind(FedifyMiddleware, this.#middleware);
172
+ }
173
+ /**
174
+ * Installs `ctx.federation`, bridges Fedify's logs into the AdonisJS logger,
175
+ * and subscribes to the HTTP server's ready event.
176
+ */
177
+ async boot() {
178
+ installFederationGetter();
179
+ const config = await this.app.container.make("fedify.config");
180
+ if (config.logging !== false) {
181
+ const logger = await this.app.container.make("logger");
182
+ this.#loggingConfigured = await configureFedifyLogging({
183
+ logger,
184
+ categories: config.logging === true ? undefined : config.logging.categories
185
+ });
186
+ }
187
+ if (this.app.container.hasBinding("emitter")) {
188
+ const emitter = await this.app.container.make("emitter");
189
+ emitter.on("http:server_ready", () => this.#serverStarted());
190
+ }
191
+ }
192
+ /**
193
+ * Runs after the preload files have registered every dispatcher.
194
+ */
195
+ async ready() {
196
+ this.#buildable = true;
197
+ let federation;
198
+ let worker;
199
+ try {
200
+ federation = await this.app.container.make("fedify");
201
+ const config = await this.app.container.make("fedify.config");
202
+ if (typeof config.queueContextData === "function" && this.app.getEnvironment() === "web") {
203
+ worker = {
204
+ contextData: await config.queueContextData(),
205
+ logger: await this.app.container.make("logger")
206
+ };
207
+ }
208
+ } catch (error) {
209
+ this.#buildable = false;
210
+ this.#middleware?.forget();
211
+ this.#settle(error);
212
+ throw error;
213
+ }
214
+ this.#settle();
215
+ if (worker === undefined) return;
216
+ if (this.#queueController.signal.aborted) return;
217
+ const { contextData, logger } = worker;
218
+ this.#queue = federation.startQueue(contextData, { signal: this.#queueController.signal }).catch((error) => {
219
+ if (this.#queueController.signal.aborted) return;
220
+ logger.error({ err: error }, "The Fedify task queue stopped unexpectedly");
221
+ });
222
+ }
223
+ /**
224
+ * Stops the queue worker and waits for it, releases any request still
225
+ * waiting on `ready()`, and takes the LogTape configuration down.
226
+ *
227
+ * Aborting only *asks* the listeners to stop; `startQueue()` settles once
228
+ * they have, possibly one in-flight delivery later. Returning that promise
229
+ * keeps the rest of the shutdown sequence (closing the database, most
230
+ * importantly) behind it.
231
+ *
232
+ * LogTape is reset after the queue stops, so an outgoing delivery can still
233
+ * log, and only if this provider configured it (see
234
+ * {@link #loggingConfigured}); resetting the application's own
235
+ * configuration would silence its logs.
236
+ *
237
+ * The return type stays a promise even with no queue to wait for: AdonisJS
238
+ * warns when a provider hook returns a non-thenable.
239
+ */
240
+ shutdown() {
241
+ this.#settle(new E_FEDERATION_NOT_READY());
242
+ this.#queueController.abort();
243
+ const stopped = this.#queue ?? Promise.resolve();
244
+ if (!this.#loggingConfigured) return stopped;
245
+ this.#loggingConfigured = false;
246
+ return stopped.then(() => reset().catch(() => {}));
247
+ }
248
+ /**
249
+ * Called when the HTTP server starts listening and requests can arrive.
250
+ */
251
+ #serverStarted() {
252
+ this.#listening = true;
253
+ if (this.#buildable) return;
254
+ this.#readyTimer = setTimeout(() => this.#settle(new E_FEDERATION_NOT_READY()), READY_TIMEOUT_MS);
255
+ this.#readyTimer.unref();
256
+ }
257
+ /**
258
+ * Settles the `ready()` promise once; later calls are no-ops.
259
+ */
260
+ #settle(error) {
261
+ clearTimeout(this.#readyTimer);
262
+ if (error === undefined) this.#ready.resolve();
263
+ else this.#ready.reject(error);
264
+ }
265
+ };
266
+
267
+ //#endregion
268
+ export { FedifyProvider as default };
269
+ //# sourceMappingURL=fedify_provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fedify_provider.js","names":["#ready","#buildable","#middleware","#listening","#loggingConfigured","#serverStarted","#settle","#queueController","#queue","resetLogging","#readyTimer"],"sources":["../../providers/fedify_provider.ts"],"sourcesContent":["/**\n * The `@fedify/adonisjs` service provider; see {@link FedifyProvider}.\n *\n * @module\n */\nimport { configProvider } from \"@adonisjs/core\";\nimport type { Logger } from \"@adonisjs/core/logger\";\nimport type { ApplicationService, ConfigProvider } from \"@adonisjs/core/types\";\nimport type { Federation } from \"@fedify/fedify\";\nimport { reset as resetLogging } from \"@logtape/logtape\";\n\nimport { builder } from \"../src/builder.ts\";\nimport {\n E_FEDERATION_NOT_READY,\n E_INVALID_FEDIFY_CONFIG,\n} from \"../src/errors.ts\";\nimport FedifyMiddleware from \"../src/fedify_middleware.ts\";\nimport { installFederationGetter } from \"../src/http_context.ts\";\nimport { configureFedifyLogging } from \"../src/logging.ts\";\nimport type { ContextData, ResolvedFedifyConfig } from \"../src/types.ts\";\n\n/**\n * How long a request that arrives before `ready()` waits for the federation.\n *\n * Generous, since it only needs to outlast earlier providers' `ready()` hooks.\n * It turns a `ready()` that never comes (an earlier provider waiting on this\n * server) into an error instead of a request that never completes.\n */\nconst READY_TIMEOUT_MS = 30_000;\n\n/**\n * A memoised factory: the value, plus a way to drop what was cached.\n */\ntype Memoized<T> = (() => Promise<T>) & { forget(): void };\n\n/**\n * Memoises an asynchronous factory, caching successes only.\n *\n * Used for the container bindings instead of `container.singleton()`, which\n * also caches *rejections*: one early, swallowed `make('fedify')` would pin\n * `E_FEDERATION_NOT_READY` for the life of the process. A failed attempt is\n * forgotten instead, so the next caller runs the factory again. `forget()`\n * drops a cached success once the caller knows it is stale.\n */\nfunction memoizeSuccess<T>(factory: () => Promise<T>): Memoized<T> {\n let cached: Promise<T> | undefined;\n\n const memo = (): Promise<T> => {\n cached ??= factory().catch((error: unknown) => {\n cached = undefined;\n throw error;\n });\n\n return cached;\n };\n\n return Object.assign(memo, {\n forget: (): void => {\n cached = undefined;\n },\n });\n}\n\n/**\n * The service provider registered from `adonisrc.ts` as\n * `@fedify/adonisjs/fedify_provider`.\n *\n * Binds `fedify.builder`, `fedify.config`, `fedify` and\n * {@link FedifyMiddleware}, installs `ctx.federation`, bridges Fedify's logs\n * into the AdonisJS logger, builds the federation after the preload files\n * have run, and drains the task queue in the web process.\n */\nexport default class FedifyProvider {\n /**\n * Aborts the queue worker on shutdown.\n */\n #queueController = new AbortController();\n\n /**\n * The `startQueue()` promise, settled once every listener has stopped.\n * `shutdown()` returns it so AdonisJS waits for the workers. Undefined when\n * this process does not drain the queue.\n */\n #queue?: Promise<void>;\n\n /**\n * Whether this provider configured LogTape and must undo it on shutdown.\n *\n * LogTape's configuration is process-global and its sink closes over *this*\n * application's `Logger`. Left in place, a second application in the same\n * process (the test runner, a dev-server restart) would find LogTape already\n * configured and keep logging into the terminated application's logger.\n */\n #loggingConfigured = false;\n\n /**\n * Whether the builder may be sealed into a `Federation` object.\n *\n * Dispatchers are registered in preload files, which AdonisJS imports *after*\n * every provider's `boot()` and `start()`. Building earlier would yield a\n * federation with no routes and 404s as the only symptom, so an early build\n * fails with an explanatory error instead.\n */\n #buildable = false;\n\n /**\n * Whether the HTTP server has started accepting connections.\n *\n * AdonisJS listens inside `start()` and runs `ready()` hooks afterwards, so a\n * request (a health probe, `fedify tunnel`) can arrive before this provider\n * is ready. Such a request is the one caller allowed to *wait* for the\n * federation, since `ready()` is on its way; any other caller (a `start()`\n * hook, a preload file, an Ace command) fails fast because nothing would\n * make it ready while they wait.\n */\n #listening = false;\n\n /**\n * Settles once `ready()` has built the federation: resolved on success,\n * rejected with the configuration error, or with `E_FEDERATION_NOT_READY`\n * when the application shuts down first or {@link READY_TIMEOUT_MS} passes.\n */\n #ready = Promise.withResolvers<void>();\n #readyTimer?: ReturnType<typeof setTimeout>;\n\n /**\n * The memoised middleware binding, kept here so that a failed `ready()` can\n * forget it.\n *\n * A request that arrives while `ready()` is running builds its middleware\n * from a federation that is buildable at that moment. Should `ready()` fail\n * afterwards, that middleware would otherwise stay cached and keep serving\n * federation traffic in an application whose `make('fedify')` fails fast and\n * whose queue never started.\n */\n #middleware?: Memoized<FedifyMiddleware<ContextData>>;\n\n protected app: ApplicationService;\n\n /**\n * Longhand rather than a parameter property, which Node's type stripping\n * does not support; the tests run these sources through it.\n */\n constructor(app: ApplicationService) {\n this.app = app;\n\n // Only requests waiting on `ready()` observe the rejection; without this\n // handler Node would report it as unhandled when nobody was.\n this.#ready.promise.catch(() => {});\n }\n\n /**\n * Registers the container bindings.\n */\n register(): void {\n /**\n * Bound as a value: the builder is a module-level singleton that\n * application code also imports directly, and both routes must reach the\n * same object.\n */\n this.app.container.bindValue(\"fedify.builder\", builder);\n\n this.app.container.bind(\n \"fedify.config\",\n memoizeSuccess(async () => {\n const provider = this.app.config.get<\n ConfigProvider<ResolvedFedifyConfig>\n >(\"fedify\");\n const config = await configProvider.resolve<ResolvedFedifyConfig>(\n this.app,\n provider,\n );\n\n if (!config) {\n throw new E_INVALID_FEDIFY_CONFIG();\n }\n\n return config;\n }),\n );\n\n const buildFederation = memoizeSuccess(async () => {\n const config = await this.app.container.make(\"fedify.config\");\n const federationBuilder = await this.app.container.make(\"fedify.builder\");\n\n return federationBuilder.build(config);\n });\n\n this.app.container.bind(\"fedify\", () => {\n if (!this.#buildable) {\n throw new E_FEDERATION_NOT_READY();\n }\n\n return buildFederation();\n });\n\n /**\n * The middleware, built with the federation and configuration in scope, as\n * `@adonisjs/cors` and `@adonisjs/session` do. `server.use()` resolves\n * the class through the container on every request, which lands here.\n *\n * A plain `bind()` plus {@link memoizeSuccess} rather than `singleton()`\n * (see the helper), so every request shares one instance.\n *\n * A request that arrives after the server listens but before `ready()` has\n * built the federation waits for it (see {@link #listening}); any other\n * caller gets `E_FEDERATION_NOT_READY` at once.\n */\n this.#middleware = memoizeSuccess(async () => {\n if (!this.#buildable) {\n if (!this.#listening) throw new E_FEDERATION_NOT_READY();\n await this.#ready.promise;\n }\n\n const federation = await this.app.container.make(\"fedify\");\n const config = await this.app.container.make(\"fedify.config\");\n\n return new FedifyMiddleware<ContextData>(\n federation,\n config.contextDataFactory,\n { ignoreRoutePrefixes: config.ignoreRoutePrefixes },\n );\n });\n\n this.app.container.bind(FedifyMiddleware, this.#middleware);\n }\n\n /**\n * Installs `ctx.federation`, bridges Fedify's logs into the AdonisJS logger,\n * and subscribes to the HTTP server's ready event.\n */\n async boot(): Promise<void> {\n // `ctx.federation` as a Macroable getter on the `HttpContext` prototype.\n // The middleware also installs it on each context's class (covering\n // another `@adonisjs/core` copy); this covers requests the middleware\n // never saw, which then get `E_FEDIFY_CONTEXT_UNAVAILABLE` rather than\n // `undefined`.\n installFederationGetter();\n\n const config = await this.app.container.make(\"fedify.config\");\n\n if (config.logging !== false) {\n const logger = await this.app.container.make(\"logger\");\n this.#loggingConfigured = await configureFedifyLogging({\n logger,\n categories: config.logging === true\n ? undefined\n : config.logging.categories,\n });\n }\n\n // The core `app_provider` binds the emitter; a bare application in a test\n // may lack it, and then has no HTTP server to hear from either.\n if (this.app.container.hasBinding(\"emitter\")) {\n const emitter = await this.app.container.make(\"emitter\");\n emitter.on(\"http:server_ready\", () => this.#serverStarted());\n }\n }\n\n /**\n * Runs after the preload files have registered every dispatcher.\n */\n async ready(): Promise<void> {\n this.#buildable = true;\n\n // Build eagerly so a broken federation surfaces at startup rather than on\n // the first federation request. Requests waiting in the middleware\n // binding get the same outcome.\n let federation: Federation<ContextData>;\n let worker: { contextData: ContextData; logger: Logger } | undefined;\n try {\n federation = await this.app.container.make(\"fedify\");\n const config = await this.app.container.make(\"fedify.config\");\n\n // Only the web process drains the queue: Ace commands, the REPL and the\n // test runner also reach `ready()`, where a worker would outlive the\n // command and compete for the queue. The context data is built before\n // the waiting requests are released, so a throwing callback fails\n // startup like a broken federation instead of rejecting `ready()` with\n // requests already being served.\n if (\n typeof config.queueContextData === \"function\" &&\n this.app.getEnvironment() === \"web\"\n ) {\n worker = {\n contextData: await config.queueContextData(),\n logger: await this.app.container.make(\"logger\"),\n };\n }\n } catch (error: unknown) {\n // Back to failing fast: a surviving process (an Ace command, the REPL)\n // must not have later `make('fedify')` calls re-run the configuration,\n // nor keep the middleware a request built while this hook was still\n // running (see {@link #middleware}).\n this.#buildable = false;\n this.#middleware?.forget();\n this.#settle(error);\n throw error;\n }\n this.#settle();\n\n if (worker === undefined) return;\n\n // `shutdown()` can have run while this hook was awaiting above: it settles\n // and aborts, but finds no queue promise to wait for. Starting a worker on\n // the aborted signal now would leave one running that nothing awaits.\n if (this.#queueController.signal.aborted) return;\n\n // `startQueue` settles only when the queue stops, so it must not be\n // awaited here. The promise is kept for `shutdown()`, `catch` included,\n // so awaiting it there never rejects.\n const { contextData, logger } = worker;\n this.#queue = federation\n .startQueue(contextData, { signal: this.#queueController.signal })\n .catch((error: unknown) => {\n if (this.#queueController.signal.aborted) return;\n logger.error(\n { err: error },\n \"The Fedify task queue stopped unexpectedly\",\n );\n });\n }\n\n /**\n * Stops the queue worker and waits for it, releases any request still\n * waiting on `ready()`, and takes the LogTape configuration down.\n *\n * Aborting only *asks* the listeners to stop; `startQueue()` settles once\n * they have, possibly one in-flight delivery later. Returning that promise\n * keeps the rest of the shutdown sequence (closing the database, most\n * importantly) behind it.\n *\n * LogTape is reset after the queue stops, so an outgoing delivery can still\n * log, and only if this provider configured it (see\n * {@link #loggingConfigured}); resetting the application's own\n * configuration would silence its logs.\n *\n * The return type stays a promise even with no queue to wait for: AdonisJS\n * warns when a provider hook returns a non-thenable.\n */\n shutdown(): Promise<void> {\n this.#settle(new E_FEDERATION_NOT_READY());\n this.#queueController.abort();\n\n const stopped = this.#queue ?? Promise.resolve();\n if (!this.#loggingConfigured) return stopped;\n\n this.#loggingConfigured = false;\n // A failing reset must not fail the shutdown queued behind it. The queue\n // promise never rejects, so only the reset is guarded.\n return stopped.then(() => resetLogging().catch(() => {}));\n }\n\n /**\n * Called when the HTTP server starts listening and requests can arrive.\n */\n #serverStarted(): void {\n this.#listening = true;\n if (this.#buildable) return;\n\n this.#readyTimer = setTimeout(\n () => this.#settle(new E_FEDERATION_NOT_READY()),\n READY_TIMEOUT_MS,\n );\n // A pending timer must not keep the process alive.\n this.#readyTimer.unref();\n }\n\n /**\n * Settles the `ready()` promise once; later calls are no-ops.\n */\n #settle(error?: unknown): void {\n clearTimeout(this.#readyTimer);\n if (error === undefined) this.#ready.resolve();\n else this.#ready.reject(error);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
+ const require_src_builder = require('../src/builder.cjs');
3
+
4
+ //#region services/builder.ts
5
+ const federation = require_src_builder.builder;
6
+
7
+ //#endregion
8
+ exports.default = federation;
9
+ //# sourceMappingURL=builder.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.cjs","names":["builder"],"sources":["../../services/builder.ts"],"sourcesContent":["/**\n * Service module exposing the Fedify {@link FederationBuilder}.\n *\n * This is the module application code imports to register dispatchers and inbox\n * listeners:\n *\n * ```ts\n * // app/federation/actors.ts\n * import federation from '@fedify/adonisjs/services/builder'\n *\n * federation.setActorDispatcher('/actors/{identifier}', async (ctx, identifier) => {\n * // ...\n * })\n * ```\n *\n * Registration must happen while the application boots — the generated\n * `start/federation.ts` preload file is the intended place — because the\n * builder is sealed into a `Federation` object once booting completes.\n *\n * @module\n */\nimport type { FederationBuilder } from \"@fedify/fedify\";\n\nimport { builder } from \"../src/builder.ts\";\nimport type { ContextData } from \"../src/types.ts\";\n\nconst federation: FederationBuilder<ContextData> = builder;\n\nexport default federation;\n"],"mappings":";;;;AA0BA,MAAM,aAA6CA"}
@@ -0,0 +1,7 @@
1
+ import { ContextData } from "../src/types.cjs";
2
+ import { FederationBuilder } from "@fedify/fedify";
3
+
4
+ //#region services/builder.d.ts
5
+ declare const federation: FederationBuilder<ContextData>;
6
+ export = federation;
7
+ //# sourceMappingURL=builder.d.cts.map
@@ -0,0 +1,8 @@
1
+ import { ContextData } from "../src/types.js";
2
+ import { FederationBuilder } from "@fedify/fedify";
3
+
4
+ //#region services/builder.d.ts
5
+ declare const federation: FederationBuilder<ContextData>;
6
+ //#endregion
7
+ export { federation as default };
8
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1,8 @@
1
+ import { builder } from "../src/builder.js";
2
+
3
+ //#region services/builder.ts
4
+ const federation = builder;
5
+
6
+ //#endregion
7
+ export { federation as default };
8
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.js","names":[],"sources":["../../services/builder.ts"],"sourcesContent":["/**\n * Service module exposing the Fedify {@link FederationBuilder}.\n *\n * This is the module application code imports to register dispatchers and inbox\n * listeners:\n *\n * ```ts\n * // app/federation/actors.ts\n * import federation from '@fedify/adonisjs/services/builder'\n *\n * federation.setActorDispatcher('/actors/{identifier}', async (ctx, identifier) => {\n * // ...\n * })\n * ```\n *\n * Registration must happen while the application boots — the generated\n * `start/federation.ts` preload file is the intended place — because the\n * builder is sealed into a `Federation` object once booting completes.\n *\n * @module\n */\nimport type { FederationBuilder } from \"@fedify/fedify\";\n\nimport { builder } from \"../src/builder.ts\";\nimport type { ContextData } from \"../src/types.ts\";\n\nconst federation: FederationBuilder<ContextData> = builder;\n\nexport default federation;\n"],"mappings":";;;AA0BA,MAAM,aAA6C"}
@@ -0,0 +1,55 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let _fedify_fedify = require("@fedify/fedify");
3
+
4
+ //#region src/builder.ts
5
+ /**
6
+ * The application-wide {@link FederationBuilder}.
7
+ *
8
+ * Fedify's builder pattern separates *registering* dispatchers from *building*
9
+ * the `Federation` object. That separation is what makes an AdonisJS
10
+ * integration possible at all: dispatcher registration happens while the
11
+ * application boots (in preload files, which may import Lucid models and other
12
+ * container services), while the `Federation` itself can only be built once
13
+ * every registration is in — and once `config/fedify.ts` has been resolved.
14
+ *
15
+ * The builder is a process-wide singleton for the same reason AdonisJS's own
16
+ * `router` and `server` service modules are: application code has to be able to
17
+ * reach it from a plain `import` at module scope, without an `await`.
18
+ *
19
+ * Note that this module deliberately imports nothing from AdonisJS. The
20
+ * original prototype of this package bound the builder into the IoC container
21
+ * as an import side effect, which meant the container binding existed only if
22
+ * some other module happened to import this one first. Binding is the service
23
+ * provider's job instead; see `providers/fedify_provider.ts`.
24
+ *
25
+ * On the dual build: this is the one module with state, so the classic
26
+ * dual-package hazard applies — a process that loaded both the ESM and the
27
+ * CommonJS copy would otherwise hold two builders, and dispatchers registered
28
+ * on one would be silently missing from the `Federation` built from the other.
29
+ * The singleton therefore lives in a `globalThis` slot under a registered
30
+ * symbol, which every copy of this module resolves to the same object.
31
+ *
32
+ * @module
33
+ */
34
+ /**
35
+ * The slot the singleton lives in, keyed by major version.
36
+ *
37
+ * Every copy of *this* package resolves the same key, which is the point — the
38
+ * ESM and CommonJS builds have to agree. A different major version of
39
+ * `@fedify/adonisjs` hoisted into the same process gets a slot of its own
40
+ * rather than inheriting dispatchers registered against another major's
41
+ * `FederationBuilder`.
42
+ */
43
+ const SLOT = Symbol.for("@fedify/adonisjs@2/builder");
44
+ const registry = globalThis;
45
+ /**
46
+ * The builder that dispatchers and inbox listeners are registered on.
47
+ *
48
+ * Prefer importing it through the `@fedify/adonisjs/services/builder` service
49
+ * module, which is the path the generated stubs and the documentation use.
50
+ */
51
+ const builder = registry[SLOT] ??= (0, _fedify_fedify.createFederationBuilder)();
52
+
53
+ //#endregion
54
+ exports.builder = builder;
55
+ //# sourceMappingURL=builder.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.cjs","names":[],"sources":["../../src/builder.ts"],"sourcesContent":["/**\n * The application-wide {@link FederationBuilder}.\n *\n * Fedify's builder pattern separates *registering* dispatchers from *building*\n * the `Federation` object. That separation is what makes an AdonisJS\n * integration possible at all: dispatcher registration happens while the\n * application boots (in preload files, which may import Lucid models and other\n * container services), while the `Federation` itself can only be built once\n * every registration is in — and once `config/fedify.ts` has been resolved.\n *\n * The builder is a process-wide singleton for the same reason AdonisJS's own\n * `router` and `server` service modules are: application code has to be able to\n * reach it from a plain `import` at module scope, without an `await`.\n *\n * Note that this module deliberately imports nothing from AdonisJS. The\n * original prototype of this package bound the builder into the IoC container\n * as an import side effect, which meant the container binding existed only if\n * some other module happened to import this one first. Binding is the service\n * provider's job instead; see `providers/fedify_provider.ts`.\n *\n * On the dual build: this is the one module with state, so the classic\n * dual-package hazard applies — a process that loaded both the ESM and the\n * CommonJS copy would otherwise hold two builders, and dispatchers registered\n * on one would be silently missing from the `Federation` built from the other.\n * The singleton therefore lives in a `globalThis` slot under a registered\n * symbol, which every copy of this module resolves to the same object.\n *\n * @module\n */\nimport {\n createFederationBuilder,\n type FederationBuilder,\n} from \"@fedify/fedify\";\n\nimport type { ContextData } from \"./types.ts\";\n\n/**\n * The slot the singleton lives in, keyed by major version.\n *\n * Every copy of *this* package resolves the same key, which is the point — the\n * ESM and CommonJS builds have to agree. A different major version of\n * `@fedify/adonisjs` hoisted into the same process gets a slot of its own\n * rather than inheriting dispatchers registered against another major's\n * `FederationBuilder`.\n */\nconst SLOT: unique symbol = Symbol.for(\"@fedify/adonisjs@2/builder\");\n\nconst registry = globalThis as { [SLOT]?: FederationBuilder<ContextData> };\n\n/**\n * The builder that dispatchers and inbox listeners are registered on.\n *\n * Prefer importing it through the `@fedify/adonisjs/services/builder` service\n * module, which is the path the generated stubs and the documentation use.\n */\nexport const builder: FederationBuilder<ContextData> = registry[SLOT] ??=\n createFederationBuilder<ContextData>();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,MAAM,OAAsB,OAAO,IAAI,4BAA4B;AAEnE,MAAM,WAAW;;;;;;;AAQjB,MAAa,UAA0C,SAAS,sDACzB"}
@@ -0,0 +1,14 @@
1
+ import { ContextData } from "./types.cjs";
2
+ import { FederationBuilder } from "@fedify/fedify";
3
+
4
+ //#region src/builder.d.ts
5
+ /**
6
+ * The builder that dispatchers and inbox listeners are registered on.
7
+ *
8
+ * Prefer importing it through the `@fedify/adonisjs/services/builder` service
9
+ * module, which is the path the generated stubs and the documentation use.
10
+ */
11
+ declare const builder: FederationBuilder<ContextData>;
12
+ //#endregion
13
+ export { builder };
14
+ //# sourceMappingURL=builder.d.cts.map
@@ -0,0 +1,14 @@
1
+ import { ContextData } from "./types.js";
2
+ import { FederationBuilder } from "@fedify/fedify";
3
+
4
+ //#region src/builder.d.ts
5
+ /**
6
+ * The builder that dispatchers and inbox listeners are registered on.
7
+ *
8
+ * Prefer importing it through the `@fedify/adonisjs/services/builder` service
9
+ * module, which is the path the generated stubs and the documentation use.
10
+ */
11
+ declare const builder: FederationBuilder<ContextData>;
12
+ //#endregion
13
+ export { builder };
14
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1,54 @@
1
+ import { createFederationBuilder } from "@fedify/fedify";
2
+
3
+ //#region src/builder.ts
4
+ /**
5
+ * The application-wide {@link FederationBuilder}.
6
+ *
7
+ * Fedify's builder pattern separates *registering* dispatchers from *building*
8
+ * the `Federation` object. That separation is what makes an AdonisJS
9
+ * integration possible at all: dispatcher registration happens while the
10
+ * application boots (in preload files, which may import Lucid models and other
11
+ * container services), while the `Federation` itself can only be built once
12
+ * every registration is in — and once `config/fedify.ts` has been resolved.
13
+ *
14
+ * The builder is a process-wide singleton for the same reason AdonisJS's own
15
+ * `router` and `server` service modules are: application code has to be able to
16
+ * reach it from a plain `import` at module scope, without an `await`.
17
+ *
18
+ * Note that this module deliberately imports nothing from AdonisJS. The
19
+ * original prototype of this package bound the builder into the IoC container
20
+ * as an import side effect, which meant the container binding existed only if
21
+ * some other module happened to import this one first. Binding is the service
22
+ * provider's job instead; see `providers/fedify_provider.ts`.
23
+ *
24
+ * On the dual build: this is the one module with state, so the classic
25
+ * dual-package hazard applies — a process that loaded both the ESM and the
26
+ * CommonJS copy would otherwise hold two builders, and dispatchers registered
27
+ * on one would be silently missing from the `Federation` built from the other.
28
+ * The singleton therefore lives in a `globalThis` slot under a registered
29
+ * symbol, which every copy of this module resolves to the same object.
30
+ *
31
+ * @module
32
+ */
33
+ /**
34
+ * The slot the singleton lives in, keyed by major version.
35
+ *
36
+ * Every copy of *this* package resolves the same key, which is the point — the
37
+ * ESM and CommonJS builds have to agree. A different major version of
38
+ * `@fedify/adonisjs` hoisted into the same process gets a slot of its own
39
+ * rather than inheriting dispatchers registered against another major's
40
+ * `FederationBuilder`.
41
+ */
42
+ const SLOT = Symbol.for("@fedify/adonisjs@2/builder");
43
+ const registry = globalThis;
44
+ /**
45
+ * The builder that dispatchers and inbox listeners are registered on.
46
+ *
47
+ * Prefer importing it through the `@fedify/adonisjs/services/builder` service
48
+ * module, which is the path the generated stubs and the documentation use.
49
+ */
50
+ const builder = registry[SLOT] ??= createFederationBuilder();
51
+
52
+ //#endregion
53
+ export { builder };
54
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.js","names":[],"sources":["../../src/builder.ts"],"sourcesContent":["/**\n * The application-wide {@link FederationBuilder}.\n *\n * Fedify's builder pattern separates *registering* dispatchers from *building*\n * the `Federation` object. That separation is what makes an AdonisJS\n * integration possible at all: dispatcher registration happens while the\n * application boots (in preload files, which may import Lucid models and other\n * container services), while the `Federation` itself can only be built once\n * every registration is in — and once `config/fedify.ts` has been resolved.\n *\n * The builder is a process-wide singleton for the same reason AdonisJS's own\n * `router` and `server` service modules are: application code has to be able to\n * reach it from a plain `import` at module scope, without an `await`.\n *\n * Note that this module deliberately imports nothing from AdonisJS. The\n * original prototype of this package bound the builder into the IoC container\n * as an import side effect, which meant the container binding existed only if\n * some other module happened to import this one first. Binding is the service\n * provider's job instead; see `providers/fedify_provider.ts`.\n *\n * On the dual build: this is the one module with state, so the classic\n * dual-package hazard applies — a process that loaded both the ESM and the\n * CommonJS copy would otherwise hold two builders, and dispatchers registered\n * on one would be silently missing from the `Federation` built from the other.\n * The singleton therefore lives in a `globalThis` slot under a registered\n * symbol, which every copy of this module resolves to the same object.\n *\n * @module\n */\nimport {\n createFederationBuilder,\n type FederationBuilder,\n} from \"@fedify/fedify\";\n\nimport type { ContextData } from \"./types.ts\";\n\n/**\n * The slot the singleton lives in, keyed by major version.\n *\n * Every copy of *this* package resolves the same key, which is the point — the\n * ESM and CommonJS builds have to agree. A different major version of\n * `@fedify/adonisjs` hoisted into the same process gets a slot of its own\n * rather than inheriting dispatchers registered against another major's\n * `FederationBuilder`.\n */\nconst SLOT: unique symbol = Symbol.for(\"@fedify/adonisjs@2/builder\");\n\nconst registry = globalThis as { [SLOT]?: FederationBuilder<ContextData> };\n\n/**\n * The builder that dispatchers and inbox listeners are registered on.\n *\n * Prefer importing it through the `@fedify/adonisjs/services/builder` service\n * module, which is the path the generated stubs and the documentation use.\n */\nexport const builder: FederationBuilder<ContextData> = registry[SLOT] ??=\n createFederationBuilder<ContextData>();\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CA,MAAM,OAAsB,OAAO,IAAI,4BAA4B;AAEnE,MAAM,WAAW;;;;;;;AAQjB,MAAa,UAA0C,SAAS,UAC9D,wBAAqC"}
@@ -0,0 +1,114 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ let _adonisjs_core = require("@adonisjs/core");
3
+ let _adonisjs_core_exceptions = require("@adonisjs/core/exceptions");
4
+
5
+ //#region src/define_config.ts
6
+ /**
7
+ * The `defineConfig` helper used by `config/fedify.ts`.
8
+ *
9
+ * @module
10
+ */
11
+ /**
12
+ * Whether the `queue` option carries a queue at all.
13
+ *
14
+ * `FederationOptions.queue` takes either one `MessageQueue` for every role or
15
+ * an object of per-role queues, and Fedify reads an object whose members are
16
+ * all `undefined` as no queue. Testing the option's presence would therefore
17
+ * misfire in both directions: `queue: { outbox: app.inProduction ? q :
18
+ * undefined }` looks configured in development, and `queue: {}` looks like a
19
+ * queue that needs draining.
20
+ */
21
+ function hasQueue(queue) {
22
+ if (queue == null) return false;
23
+ if ("enqueue" in queue && "listen" in queue) return true;
24
+ return Object.values(queue).some((role) => role != null);
25
+ }
26
+ /**
27
+ * Defines the Fedify configuration of an AdonisJS application.
28
+ *
29
+ * The return value is an AdonisJS *config provider* rather than a plain object,
30
+ * which lets the resolution step reach the application instance — needed to
31
+ * detect production mode and to log through the application's logger — while
32
+ * `config/fedify.ts` itself stays a simple, synchronous, statically analysable
33
+ * module.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * // config/fedify.ts
38
+ * import { defineConfig } from '@fedify/adonisjs'
39
+ * import { SqliteKvStore } from '@fedify/sqlite'
40
+ * import env from '#start/env'
41
+ *
42
+ * export default defineConfig({
43
+ * origin: env.get('PUBLIC_URL'),
44
+ * kv: new SqliteKvStore(database),
45
+ * })
46
+ * ```
47
+ */
48
+ function defineConfig(config) {
49
+ return _adonisjs_core.configProvider.create(async (app) => {
50
+ if (!config.origin) {
51
+ throw new _adonisjs_core_exceptions.InvalidArgumentsException("Missing \"origin\" in config/fedify.ts. It must be the canonical public URL of this server, " + "usually read from the PUBLIC_URL environment variable");
52
+ }
53
+ let kv = config.kv;
54
+ if (kv === undefined) {
55
+ const { MemoryKvStore } = await import("@fedify/fedify");
56
+ kv = new MemoryKvStore();
57
+ if (app.inProduction) {
58
+ const logger = await app.container.make("logger");
59
+ logger.warn("Fedify is using an in-memory key-value store in production. Inbox idempotency, " + "the document cache and outbox state will be lost on restart and cannot be shared " + "between processes. Configure \"kv\" in config/fedify.ts with a persistent store such " + "as @fedify/sqlite, @fedify/postgres or @fedify/redis");
60
+ }
61
+ }
62
+ const { contextDataFactory, ignoreRoutePrefixes, logging, queueContextData, ...federationOptions } = config;
63
+ /**
64
+ * A configured queue forces the "who drains it" decision to be explicit.
65
+ *
66
+ * Fedify's own default starts the queue lazily on the first enqueued task,
67
+ * capturing that request's context data and reusing it for every background
68
+ * job thereafter. For this integration the captured value would be a single
69
+ * stale `HttpContext`, whose request has long since finished. Rather than
70
+ * pick a wrong default, refuse to guess.
71
+ */
72
+ if (hasQueue(federationOptions.queue) && queueContextData === undefined) {
73
+ throw new _adonisjs_core_exceptions.InvalidArgumentsException("config/fedify.ts configures a \"queue\" but not \"queueContextData\". Set " + "\"queueContextData\" to a callback to have this process drain the queue, or to " + "\"false\" when a separate worker process owns it");
74
+ }
75
+ /**
76
+ * The mirror image: a drainer for a queue that does not exist. Nothing
77
+ * fails on its own — `startQueue()` resolves over no queues and
78
+ * `sendActivity()` delivers inline without retry, so failed deliveries are
79
+ * silently lost.
80
+ *
81
+ * Only a warning, because a task registered with
82
+ * `defineTask(name, { queue })` from a preload file brings its own queue,
83
+ * which this configuration cannot see. Refusing would break a valid
84
+ * application, and dropping the callback would hand that queue back to
85
+ * Fedify's lazy start. Only a *callback* is reported: `false` stays valid
86
+ * without a queue, so `queue: app.inProduction ? … : undefined` still
87
+ * boots in development and test.
88
+ */
89
+ if (!hasQueue(federationOptions.queue) && typeof queueContextData === "function") {
90
+ const logger = await app.container.make("logger");
91
+ logger.warn("config/fedify.ts sets \"queueContextData\" but no \"queue\". Unless a task registers its own " + "queue through defineTask(), there is nothing to drain and activity delivery stays " + "inline, with failed deliveries never retried. Configure \"queue\" with a message queue " + "such as @fedify/sqlite, @fedify/postgres, @fedify/redis or @fedify/amqp, or remove " + "\"queueContextData\"");
92
+ }
93
+ return {
94
+ ...federationOptions,
95
+ kv,
96
+ manuallyStartQueue: queueContextData !== undefined ? true : config.manuallyStartQueue,
97
+ /**
98
+ * The cast is sound because `ContextDataFactoryOption` makes
99
+ * `contextDataFactory` a *required* key the moment an application
100
+ * augments `FedifyTypes`. This branch is therefore only reachable while
101
+ * `ContextData` still includes `HttpContext`, which is exactly what it
102
+ * returns.
103
+ */
104
+ contextDataFactory: contextDataFactory ?? ((ctx) => ctx),
105
+ ignoreRoutePrefixes: ignoreRoutePrefixes ?? [],
106
+ logging: logging ?? true,
107
+ queueContextData
108
+ };
109
+ });
110
+ }
111
+
112
+ //#endregion
113
+ exports.defineConfig = defineConfig;
114
+ //# sourceMappingURL=define_config.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define_config.cjs","names":["configProvider","InvalidArgumentsException"],"sources":["../../src/define_config.ts"],"sourcesContent":["/**\n * The `defineConfig` helper used by `config/fedify.ts`.\n *\n * @module\n */\nimport { configProvider } from \"@adonisjs/core\";\nimport { InvalidArgumentsException } from \"@adonisjs/core/exceptions\";\nimport type { ConfigProvider } from \"@adonisjs/core/types\";\nimport type { FederationOptions } from \"@fedify/fedify\";\n\nimport type {\n ContextData,\n FedifyConfig,\n ResolvedFedifyConfig,\n} from \"./types.ts\";\n\n/**\n * Whether the `queue` option carries a queue at all.\n *\n * `FederationOptions.queue` takes either one `MessageQueue` for every role or\n * an object of per-role queues, and Fedify reads an object whose members are\n * all `undefined` as no queue. Testing the option's presence would therefore\n * misfire in both directions: `queue: { outbox: app.inProduction ? q :\n * undefined }` looks configured in development, and `queue: {}` looks like a\n * queue that needs draining.\n */\nfunction hasQueue(queue: FederationOptions<unknown>[\"queue\"]): boolean {\n if (queue == null) return false;\n if (\"enqueue\" in queue && \"listen\" in queue) return true;\n return Object.values(queue).some((role) => role != null);\n}\n\n/**\n * Defines the Fedify configuration of an AdonisJS application.\n *\n * The return value is an AdonisJS *config provider* rather than a plain object,\n * which lets the resolution step reach the application instance — needed to\n * detect production mode and to log through the application's logger — while\n * `config/fedify.ts` itself stays a simple, synchronous, statically analysable\n * module.\n *\n * @example\n * ```ts\n * // config/fedify.ts\n * import { defineConfig } from '@fedify/adonisjs'\n * import { SqliteKvStore } from '@fedify/sqlite'\n * import env from '#start/env'\n *\n * export default defineConfig({\n * origin: env.get('PUBLIC_URL'),\n * kv: new SqliteKvStore(database),\n * })\n * ```\n */\nexport function defineConfig(\n config: FedifyConfig,\n): ConfigProvider<ResolvedFedifyConfig> {\n return configProvider.create<ResolvedFedifyConfig>(async (app) => {\n if (!config.origin) {\n throw new InvalidArgumentsException(\n 'Missing \"origin\" in config/fedify.ts. It must be the canonical public URL of this server, ' +\n \"usually read from the PUBLIC_URL environment variable\",\n );\n }\n\n let kv = config.kv;\n if (kv === undefined) {\n const { MemoryKvStore } = await import(\"@fedify/fedify\");\n kv = new MemoryKvStore();\n\n if (app.inProduction) {\n // Not fatal — an application may genuinely run a single process and\n // accept the trade-off — but silently losing inbox idempotency and the\n // document cache on every restart is worth shouting about.\n const logger = await app.container.make(\"logger\");\n logger.warn(\n \"Fedify is using an in-memory key-value store in production. Inbox idempotency, \" +\n \"the document cache and outbox state will be lost on restart and cannot be shared \" +\n 'between processes. Configure \"kv\" in config/fedify.ts with a persistent store such ' +\n \"as @fedify/sqlite, @fedify/postgres or @fedify/redis\",\n );\n }\n }\n\n const {\n contextDataFactory,\n ignoreRoutePrefixes,\n logging,\n queueContextData,\n ...federationOptions\n } = config;\n\n /**\n * A configured queue forces the \"who drains it\" decision to be explicit.\n *\n * Fedify's own default starts the queue lazily on the first enqueued task,\n * capturing that request's context data and reusing it for every background\n * job thereafter. For this integration the captured value would be a single\n * stale `HttpContext`, whose request has long since finished. Rather than\n * pick a wrong default, refuse to guess.\n */\n if (hasQueue(federationOptions.queue) && queueContextData === undefined) {\n throw new InvalidArgumentsException(\n 'config/fedify.ts configures a \"queue\" but not \"queueContextData\". Set ' +\n '\"queueContextData\" to a callback to have this process drain the queue, or to ' +\n '\"false\" when a separate worker process owns it',\n );\n }\n\n /**\n * The mirror image: a drainer for a queue that does not exist. Nothing\n * fails on its own — `startQueue()` resolves over no queues and\n * `sendActivity()` delivers inline without retry, so failed deliveries are\n * silently lost.\n *\n * Only a warning, because a task registered with\n * `defineTask(name, { queue })` from a preload file brings its own queue,\n * which this configuration cannot see. Refusing would break a valid\n * application, and dropping the callback would hand that queue back to\n * Fedify's lazy start. Only a *callback* is reported: `false` stays valid\n * without a queue, so `queue: app.inProduction ? … : undefined` still\n * boots in development and test.\n */\n if (\n !hasQueue(federationOptions.queue) &&\n typeof queueContextData === \"function\"\n ) {\n const logger = await app.container.make(\"logger\");\n logger.warn(\n 'config/fedify.ts sets \"queueContextData\" but no \"queue\". Unless a task registers its own ' +\n \"queue through defineTask(), there is nothing to drain and activity delivery stays \" +\n 'inline, with failed deliveries never retried. Configure \"queue\" with a message queue ' +\n \"such as @fedify/sqlite, @fedify/postgres, @fedify/redis or @fedify/amqp, or remove \" +\n '\"queueContextData\"',\n );\n }\n\n return {\n ...federationOptions,\n kv,\n\n // The queue is always started deliberately — by this package's service\n // provider, or by a separate worker — never lazily by Fedify.\n manuallyStartQueue: queueContextData !== undefined\n ? true\n : config.manuallyStartQueue,\n\n /**\n * The cast is sound because `ContextDataFactoryOption` makes\n * `contextDataFactory` a *required* key the moment an application\n * augments `FedifyTypes`. This branch is therefore only reachable while\n * `ContextData` still includes `HttpContext`, which is exactly what it\n * returns.\n */\n contextDataFactory: contextDataFactory ?? ((ctx) => ctx as ContextData),\n ignoreRoutePrefixes: ignoreRoutePrefixes ?? [],\n logging: logging ?? true,\n queueContextData,\n };\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA,SAAS,SAAS,OAAqD;CACrE,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,aAAa,SAAS,YAAY,OAAO,OAAO;CACpD,OAAO,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,SAAS,QAAQ,IAAI;AACzD;;;;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,aACd,QACsC;CACtC,OAAOA,8BAAe,OAA6B,OAAO,QAAQ;EAChE,IAAI,CAAC,OAAO,QAAQ;GAClB,MAAM,IAAIC,oDACR,iGACE,uDACJ;EACF;EAEA,IAAI,KAAK,OAAO;EAChB,IAAI,OAAO,WAAW;GACpB,MAAM,EAAE,kBAAkB,MAAM,OAAO;GACvC,KAAK,IAAI,cAAc;GAEvB,IAAI,IAAI,cAAc;IAIpB,MAAM,SAAS,MAAM,IAAI,UAAU,KAAK,QAAQ;IAChD,OAAO,KACL,oFACE,sFACA,0FACA,sDACJ;GACF;EACF;EAEA,MAAM,EACJ,oBACA,qBACA,SACA,kBACA,GAAG,sBACD;;;;;;;;;;EAWJ,IAAI,SAAS,kBAAkB,KAAK,KAAK,qBAAqB,WAAW;GACvE,MAAM,IAAIA,oDACR,+EACE,oFACA,kDACJ;EACF;;;;;;;;;;;;;;;EAgBA,IACE,CAAC,SAAS,kBAAkB,KAAK,KACjC,OAAO,qBAAqB,YAC5B;GACA,MAAM,SAAS,MAAM,IAAI,UAAU,KAAK,QAAQ;GAChD,OAAO,KACL,kGACE,uFACA,4FACA,wFACA,sBACJ;EACF;EAEA,OAAO;GACL,GAAG;GACH;GAIA,oBAAoB,qBAAqB,YACrC,OACA,OAAO;;;;;;;;GASX,oBAAoB,wBAAwB,QAAQ;GACpD,qBAAqB,uBAAuB,CAAC;GAC7C,SAAS,WAAW;GACpB;EACF;CACF,CAAC;AACH"}