@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.
- package/LICENSE +20 -0
- package/README.md +420 -0
- package/dist/configure.cjs +108 -0
- package/dist/configure.cjs.map +1 -0
- package/dist/configure.d.cts +14 -0
- package/dist/configure.d.ts +14 -0
- package/dist/configure.js +107 -0
- package/dist/configure.js.map +1 -0
- package/dist/index.cjs +19 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/package.cjs +121 -0
- package/dist/package.cjs.map +1 -0
- package/dist/package.js +115 -0
- package/dist/package.js.map +1 -0
- package/dist/providers/fedify_provider.cjs +270 -0
- package/dist/providers/fedify_provider.cjs.map +1 -0
- package/dist/providers/fedify_provider.d.cts +54 -0
- package/dist/providers/fedify_provider.d.ts +55 -0
- package/dist/providers/fedify_provider.js +269 -0
- package/dist/providers/fedify_provider.js.map +1 -0
- package/dist/services/builder.cjs +9 -0
- package/dist/services/builder.cjs.map +1 -0
- package/dist/services/builder.d.cts +7 -0
- package/dist/services/builder.d.ts +8 -0
- package/dist/services/builder.js +8 -0
- package/dist/services/builder.js.map +1 -0
- package/dist/src/builder.cjs +55 -0
- package/dist/src/builder.cjs.map +1 -0
- package/dist/src/builder.d.cts +14 -0
- package/dist/src/builder.d.ts +14 -0
- package/dist/src/builder.js +54 -0
- package/dist/src/builder.js.map +1 -0
- package/dist/src/define_config.cjs +114 -0
- package/dist/src/define_config.cjs.map +1 -0
- package/dist/src/define_config.d.cts +30 -0
- package/dist/src/define_config.d.ts +30 -0
- package/dist/src/define_config.js +113 -0
- package/dist/src/define_config.js.map +1 -0
- package/dist/src/errors.cjs +77 -0
- package/dist/src/errors.cjs.map +1 -0
- package/dist/src/errors.d.cts +56 -0
- package/dist/src/errors.d.ts +56 -0
- package/dist/src/errors.js +73 -0
- package/dist/src/errors.js.map +1 -0
- package/dist/src/fedify_middleware.cjs +507 -0
- package/dist/src/fedify_middleware.cjs.map +1 -0
- package/dist/src/fedify_middleware.d.cts +85 -0
- package/dist/src/fedify_middleware.d.ts +85 -0
- package/dist/src/fedify_middleware.js +505 -0
- package/dist/src/fedify_middleware.js.map +1 -0
- package/dist/src/http_context.cjs +79 -0
- package/dist/src/http_context.cjs.map +1 -0
- package/dist/src/http_context.d.cts +31 -0
- package/dist/src/http_context.d.ts +31 -0
- package/dist/src/http_context.js +76 -0
- package/dist/src/http_context.js.map +1 -0
- package/dist/src/logging.cjs +106 -0
- package/dist/src/logging.cjs.map +1 -0
- package/dist/src/logging.d.cts +45 -0
- package/dist/src/logging.d.ts +45 -0
- package/dist/src/logging.js +106 -0
- package/dist/src/logging.js.map +1 -0
- package/dist/src/types.cjs +1 -0
- package/dist/src/types.d.cts +269 -0
- package/dist/src/types.d.ts +269 -0
- package/dist/src/types.js +1 -0
- package/dist/src/unusable_authority.cjs +79 -0
- package/dist/src/unusable_authority.cjs.map +1 -0
- package/dist/src/unusable_authority.js +76 -0
- package/dist/src/unusable_authority.js.map +1 -0
- package/dist/stubs/config/fedify.stub +105 -0
- package/dist/stubs/federation/main.stub +64 -0
- package/dist/stubs/main.cjs +16 -0
- package/dist/stubs/main.cjs.map +1 -0
- package/dist/stubs/main.d.cts +11 -0
- package/dist/stubs/main.d.ts +11 -0
- package/dist/stubs/main.js +15 -0
- package/dist/stubs/main.js.map +1 -0
- package/dist/stubs/start/federation.stub +20 -0
- 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,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 @@
|
|
|
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"}
|