@classytic/arc 2.4.3 → 2.6.2
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/README.md +57 -6
- package/dist/{BaseController-CkM5dUh_.mjs → BaseController-AbbRx3e0.mjs} +5 -2
- package/dist/{ResourceRegistry-DeCIFlix.mjs → ResourceRegistry-C6ngvOnn.mjs} +1 -0
- package/dist/adapters/index.d.mts +2 -2
- package/dist/adapters/index.mjs +1 -1
- package/dist/{adapters-DTC4Ug66.mjs → adapters-CTn28N4y.mjs} +72 -11
- package/dist/audit/index.d.mts +32 -6
- package/dist/audit/index.mjs +32 -4
- package/dist/audit/mongodb.d.mts +1 -1
- package/dist/auth/index.d.mts +1 -1
- package/dist/auth/index.mjs +2 -2
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/init.mjs +12 -9
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +2 -2
- package/dist/{createApp-CBgVaFyh.mjs → createApp-D2w0LdYJ.mjs} +431 -290
- package/dist/{defineResource-B22gcNvn.mjs → defineResource-Ckxg6HrZ.mjs} +125 -22
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/dynamic/index.d.mts +1 -1
- package/dist/dynamic/index.mjs +2 -2
- package/dist/{elevation-Ca_yveIO.d.mts → elevation-C_taLQrM.d.mts} +27 -1
- package/dist/{errorHandler-DMbGdzBG.mjs → errorHandler-r2595m8T.mjs} +1 -1
- package/dist/{errors-CPpvPHT0.d.mts → errors-CcVbl1-T.d.mts} +17 -1
- package/dist/{errors-rxhfP7Hf.mjs → errors-NoQKsbAT.mjs} +23 -1
- package/dist/{eventPlugin-iGrSEmwJ.d.mts → eventPlugin-DW45v4V5.d.mts} +30 -2
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +40 -10
- package/dist/factory/index.d.mts +44 -23
- package/dist/factory/index.mjs +152 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-BL8CaQih.d.mts → index-B4uZm82R.d.mts} +2 -2
- package/dist/{index-yhxyjqNb.d.mts → index-DrCqa3Jq.d.mts} +4 -8
- package/dist/{index-Diqcm14c.d.mts → index-NGZksqM5.d.mts} +30 -1
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +8 -7
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +4 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/{interface-DGmPxakH.d.mts → interface-CrN45qz1.d.mts} +229 -13
- package/dist/{mongodb-CUpYfxfD.d.mts → mongodb-kltrBPa1.d.mts} +10 -0
- package/dist/{mongodb-bga9AbkD.d.mts → mongodb-pMvOlR5_.d.mts} +1 -1
- package/dist/org/index.d.mts +1 -1
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-Jk5x3sxz.mjs → permissions-C8ImI8gC.mjs} +44 -2
- package/dist/plugins/index.d.mts +1 -1
- package/dist/plugins/index.mjs +4 -4
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +1 -1
- package/dist/{presets-OMPaHMTY.mjs → presets-BMfdy34e.mjs} +2 -2
- package/dist/{redis-CQ5YxMC5.d.mts → redis-D0Qc-9EW.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +1 -1
- package/dist/{resourceToTools-PMFE8HIv.mjs → resourceToTools-DH3c3e-T.mjs} +81 -7
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +2 -2
- package/dist/{sse-BkViJPlT.mjs → sse-BF7GR7IB.mjs} +1 -1
- package/dist/testing/index.d.mts +26 -3
- package/dist/testing/index.mjs +46 -2
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +23 -2
- package/dist/{types-C6TQjtdi.mjs → types-BhtYdxZU.mjs} +26 -1
- package/dist/{types-Dt0-AI6E.d.mts → types-C1Z28coa.d.mts} +195 -6
- package/dist/{types-BJmgxNbF.d.mts → types-DurlBP2N.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +1 -1
- package/package.json +6 -5
- package/skills/arc/SKILL.md +151 -4
- package/skills/arc/references/mcp.md +160 -2
- /package/dist/{interface-B4awm1RJ.d.mts → interface-gr-7qo9j.d.mts} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import { n as PUBLIC_SCOPE } from "./types-
|
|
2
|
+
import { n as PUBLIC_SCOPE } from "./types-BhtYdxZU.mjs";
|
|
3
3
|
import Fastify from "fastify";
|
|
4
4
|
import qs from "qs";
|
|
5
5
|
//#region src/factory/presets.ts
|
|
@@ -66,20 +66,36 @@ const productionPreset = {
|
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
/**
|
|
69
|
+
* Try to detect if `pino-pretty` is installed (devDep). Returns the transport
|
|
70
|
+
* config if available, or falls back to plain JSON logging. This prevents the
|
|
71
|
+
* common "pino-pretty not found" crash in production when someone uses the
|
|
72
|
+
* development preset by mistake (or via NODE_ENV-based preset selection).
|
|
73
|
+
*/
|
|
74
|
+
function devLoggerConfig() {
|
|
75
|
+
try {
|
|
76
|
+
const req = eval("require") ?? null;
|
|
77
|
+
if (req?.resolve) {
|
|
78
|
+
req.resolve("pino-pretty");
|
|
79
|
+
return {
|
|
80
|
+
level: "debug",
|
|
81
|
+
transport: {
|
|
82
|
+
target: "pino-pretty",
|
|
83
|
+
options: {
|
|
84
|
+
colorize: true,
|
|
85
|
+
translateTime: "SYS:HH:MM:ss",
|
|
86
|
+
ignore: "pid,hostname"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
return { level: "debug" };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
69
95
|
* Development preset - relaxed security, verbose logging
|
|
70
96
|
*/
|
|
71
97
|
const developmentPreset = {
|
|
72
|
-
logger:
|
|
73
|
-
level: "debug",
|
|
74
|
-
transport: {
|
|
75
|
-
target: "pino-pretty",
|
|
76
|
-
options: {
|
|
77
|
-
colorize: true,
|
|
78
|
-
translateTime: "SYS:HH:MM:ss",
|
|
79
|
-
ignore: "pid,hostname"
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
},
|
|
98
|
+
logger: devLoggerConfig(),
|
|
83
99
|
trustProxy: true,
|
|
84
100
|
helmet: { contentSecurityPolicy: false },
|
|
85
101
|
cors: {
|
|
@@ -120,6 +136,7 @@ const testingPreset = {
|
|
|
120
136
|
cors: false,
|
|
121
137
|
rateLimit: false,
|
|
122
138
|
underPressure: false,
|
|
139
|
+
arcPlugins: { gracefulShutdown: false },
|
|
123
140
|
sensible: true,
|
|
124
141
|
multipart: { limits: {
|
|
125
142
|
fileSize: 1024 * 1024,
|
|
@@ -181,49 +198,263 @@ function getPreset(name) {
|
|
|
181
198
|
}
|
|
182
199
|
}
|
|
183
200
|
//#endregion
|
|
184
|
-
//#region src/factory/
|
|
201
|
+
//#region src/factory/registerArcPlugins.ts
|
|
185
202
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* const primaryDb = await mongoose.connect(process.env.PRIMARY_DB);
|
|
213
|
-
* const analyticsDb = mongoose.createConnection(process.env.ANALYTICS_DB);
|
|
203
|
+
* Register Arc core plugin and event system.
|
|
204
|
+
* Returns loaded plugin modules for registerArcPlugins (avoids duplicate dynamic import).
|
|
205
|
+
*/
|
|
206
|
+
async function registerArcCore(fastify, config, trackPlugin) {
|
|
207
|
+
const { arcCorePlugin, requestIdPlugin, healthPlugin, gracefulShutdownPlugin } = await import("./plugins/index.mjs");
|
|
208
|
+
await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
|
|
209
|
+
trackPlugin("arc-core");
|
|
210
|
+
if (config.arcPlugins?.events !== false) {
|
|
211
|
+
const { default: eventPlugin } = await import("./eventPlugin-Ba00swHF.mjs").then((n) => n.n);
|
|
212
|
+
const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
|
|
213
|
+
await fastify.register(eventPlugin, {
|
|
214
|
+
...eventOpts,
|
|
215
|
+
transport: config.stores?.events
|
|
216
|
+
});
|
|
217
|
+
trackPlugin("arc-events", eventOpts);
|
|
218
|
+
fastify.log.debug(`Arc events plugin enabled (transport: ${fastify.events.transportName})`);
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
requestIdPlugin,
|
|
222
|
+
healthPlugin,
|
|
223
|
+
gracefulShutdownPlugin
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Register opt-in Arc plugins (requestId, health, gracefulShutdown,
|
|
228
|
+
* caching, queryCache, SSE, metrics, versioning).
|
|
214
229
|
*
|
|
215
|
-
*
|
|
216
|
-
|
|
217
|
-
|
|
230
|
+
* @param modules - Plugin modules loaded by registerArcCore (avoids re-importing)
|
|
231
|
+
*/
|
|
232
|
+
async function registerArcPlugins(fastify, config, trackPlugin, modules) {
|
|
233
|
+
const { requestIdPlugin, healthPlugin, gracefulShutdownPlugin } = modules;
|
|
234
|
+
if (config.arcPlugins?.requestId !== false) {
|
|
235
|
+
await fastify.register(requestIdPlugin);
|
|
236
|
+
trackPlugin("arc-request-id");
|
|
237
|
+
}
|
|
238
|
+
if (config.arcPlugins?.health !== false) {
|
|
239
|
+
await fastify.register(healthPlugin);
|
|
240
|
+
trackPlugin("arc-health");
|
|
241
|
+
}
|
|
242
|
+
if (config.arcPlugins?.gracefulShutdown !== false) {
|
|
243
|
+
await fastify.register(gracefulShutdownPlugin);
|
|
244
|
+
trackPlugin("arc-graceful-shutdown");
|
|
245
|
+
}
|
|
246
|
+
if (config.arcPlugins?.caching) {
|
|
247
|
+
const { default: cachingPlugin } = await import("./caching-BSXB-Xr7.mjs").then((n) => n.r);
|
|
248
|
+
const opts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
|
|
249
|
+
await fastify.register(cachingPlugin, opts);
|
|
250
|
+
trackPlugin("arc-caching", opts);
|
|
251
|
+
}
|
|
252
|
+
if (config.arcPlugins?.queryCache) {
|
|
253
|
+
const { queryCachePlugin } = await import("./queryCachePlugin-XtFplYO9.mjs").then((n) => n.n);
|
|
254
|
+
const opts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
|
|
255
|
+
const store = config.stores?.queryCache ?? new (await (import("./memory-BFAYkf8H.mjs").then((n) => n.n))).MemoryCacheStore();
|
|
256
|
+
await fastify.register(queryCachePlugin, {
|
|
257
|
+
store,
|
|
258
|
+
...opts
|
|
259
|
+
});
|
|
260
|
+
trackPlugin("arc-query-cache", opts);
|
|
261
|
+
}
|
|
262
|
+
if (config.arcPlugins?.sse) if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
|
|
263
|
+
else {
|
|
264
|
+
const { default: ssePlugin } = await import("./sse-BF7GR7IB.mjs").then((n) => n.r);
|
|
265
|
+
const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
|
|
266
|
+
await fastify.register(ssePlugin, opts);
|
|
267
|
+
trackPlugin("arc-sse", opts);
|
|
268
|
+
}
|
|
269
|
+
if (config.arcPlugins?.metrics) {
|
|
270
|
+
const { default: metricsPlugin } = await import("./metrics-Csh4nsvv.mjs").then((n) => n.r);
|
|
271
|
+
const opts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
|
|
272
|
+
await fastify.register(metricsPlugin, opts);
|
|
273
|
+
trackPlugin("arc-metrics", opts);
|
|
274
|
+
}
|
|
275
|
+
if (config.arcPlugins?.versioning) {
|
|
276
|
+
const { default: versioningPlugin } = await import("./versioning-BzfeHmhj.mjs").then((n) => n.r);
|
|
277
|
+
await fastify.register(versioningPlugin, config.arcPlugins.versioning);
|
|
278
|
+
trackPlugin("arc-versioning", config.arcPlugins.versioning);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/factory/registerAuth.ts
|
|
283
|
+
/**
|
|
284
|
+
* Decorate request.scope with PUBLIC_SCOPE default.
|
|
285
|
+
* Every request starts as public; auth hooks upgrade it.
|
|
286
|
+
*/
|
|
287
|
+
function decorateRequestScope(fastify) {
|
|
288
|
+
fastify.decorateRequest("scope", null);
|
|
289
|
+
fastify.addHook("onRequest", async (request) => {
|
|
290
|
+
if (!request.scope) request.scope = PUBLIC_SCOPE;
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Register the configured auth strategy (JWT, Better Auth, Custom, or Authenticator).
|
|
295
|
+
*/
|
|
296
|
+
async function registerAuth(fastify, config, trackPlugin) {
|
|
297
|
+
const authConfig = config.auth;
|
|
298
|
+
if (authConfig === false || !authConfig) {
|
|
299
|
+
fastify.log.debug("Authentication disabled");
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
switch (authConfig.type) {
|
|
303
|
+
case "betterAuth": {
|
|
304
|
+
const { plugin, openapi } = authConfig.betterAuth;
|
|
305
|
+
await fastify.register(plugin);
|
|
306
|
+
trackPlugin("auth-better-auth");
|
|
307
|
+
if (openapi && !fastify.arc.externalOpenApiPaths.includes(openapi)) fastify.arc.externalOpenApiPaths.push(openapi);
|
|
308
|
+
fastify.log.debug("Better Auth authentication enabled");
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
case "custom":
|
|
312
|
+
await fastify.register(authConfig.plugin);
|
|
313
|
+
trackPlugin("auth-custom");
|
|
314
|
+
fastify.log.debug("Custom authentication plugin enabled");
|
|
315
|
+
break;
|
|
316
|
+
case "authenticator": {
|
|
317
|
+
const { authenticate, optionalAuthenticate } = authConfig;
|
|
318
|
+
fastify.decorate("authenticate", async (request, reply) => {
|
|
319
|
+
await authenticate(request, reply);
|
|
320
|
+
});
|
|
321
|
+
if (!fastify.hasDecorator("optionalAuthenticate")) if (optionalAuthenticate) fastify.decorate("optionalAuthenticate", async (request, reply) => {
|
|
322
|
+
await optionalAuthenticate(request, reply);
|
|
323
|
+
});
|
|
324
|
+
else fastify.decorate("optionalAuthenticate", createOptionalAuthenticate(authenticate));
|
|
325
|
+
trackPlugin("auth-authenticator");
|
|
326
|
+
fastify.log.debug("Custom authenticator enabled");
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
case "jwt": {
|
|
330
|
+
const { authPlugin } = await import("./auth/index.mjs");
|
|
331
|
+
const { type: _, ...arcAuthOpts } = authConfig;
|
|
332
|
+
await fastify.register(authPlugin, arcAuthOpts);
|
|
333
|
+
trackPlugin("auth-jwt");
|
|
334
|
+
fastify.log.debug("Arc authentication plugin enabled");
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Register elevation plugin (opt-in, runs after auth).
|
|
341
|
+
*/
|
|
342
|
+
async function registerElevation(fastify, config, trackPlugin) {
|
|
343
|
+
if (!config.elevation) return;
|
|
344
|
+
const { elevationPlugin } = await import("./elevation-BEdACOLB.mjs").then((n) => n.r);
|
|
345
|
+
await fastify.register(elevationPlugin, config.elevation);
|
|
346
|
+
trackPlugin("arc-elevation", config.elevation);
|
|
347
|
+
fastify.log.debug("Elevation plugin enabled");
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Register error handler plugin (opt-out).
|
|
351
|
+
*/
|
|
352
|
+
async function registerErrorHandler(fastify, config, trackPlugin) {
|
|
353
|
+
if (config.errorHandler === false) return;
|
|
354
|
+
const { errorHandlerPlugin } = await import("./errorHandler-r2595m8T.mjs").then((n) => n.n);
|
|
355
|
+
const errorOpts = typeof config.errorHandler === "object" ? config.errorHandler : { includeStack: config.preset !== "production" };
|
|
356
|
+
await fastify.register(errorHandlerPlugin, errorOpts);
|
|
357
|
+
trackPlugin("arc-error-handler", errorOpts);
|
|
358
|
+
fastify.log.debug("Arc error handler enabled");
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Create an optionalAuthenticate that wraps the main authenticate function.
|
|
362
|
+
* Intercepts 401/403 responses so unauthenticated requests proceed as public.
|
|
218
363
|
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* });
|
|
364
|
+
* Uses a try/catch approach first; falls back to reply proxy only when
|
|
365
|
+
* the authenticator calls reply.code(401).send() instead of throwing.
|
|
222
366
|
*/
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
367
|
+
function createOptionalAuthenticate(authenticate) {
|
|
368
|
+
return async (request, reply) => {
|
|
369
|
+
let intercepted = false;
|
|
370
|
+
const proxyReply = new Proxy(reply, { get(target, prop) {
|
|
371
|
+
if (prop === "code") return (statusCode) => {
|
|
372
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
373
|
+
intercepted = true;
|
|
374
|
+
return new Proxy(target, { get(_t, p) {
|
|
375
|
+
if (p === "send" || p === "type" || p === "header" || p === "headers") return () => proxyReply;
|
|
376
|
+
return Reflect.get(target, p, target);
|
|
377
|
+
} });
|
|
378
|
+
}
|
|
379
|
+
return target.code(statusCode);
|
|
380
|
+
};
|
|
381
|
+
if (prop === "send" && intercepted) return () => proxyReply;
|
|
382
|
+
if (prop === "sent") return intercepted ? false : target.sent;
|
|
383
|
+
return Reflect.get(target, prop, target);
|
|
384
|
+
} });
|
|
385
|
+
try {
|
|
386
|
+
await authenticate(request, proxyReply);
|
|
387
|
+
} catch {}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/factory/registerResources.ts
|
|
392
|
+
/** Register a single resource with descriptive error on failure. */
|
|
393
|
+
async function registerOne(parent, resource) {
|
|
394
|
+
const name = resource.name ?? "unknown";
|
|
395
|
+
try {
|
|
396
|
+
await parent.register(resource.toPlugin());
|
|
397
|
+
} catch (err) {
|
|
398
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
399
|
+
parent.log.error(`Failed to register resource "${name}": ${msg}`);
|
|
400
|
+
throw new Error(`Resource "${name}" failed to register: ${msg}. Check the resource definition, adapter, and permissions.`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Execute the full resource lifecycle:
|
|
405
|
+
* 1. plugins() — infra (DB, docs, webhooks)
|
|
406
|
+
* 2. bootstrap[] — domain init (singletons, event handlers)
|
|
407
|
+
* 3. resources[] — auto-discovered routes (split by prefix)
|
|
408
|
+
* 4. afterResources() — post-registration wiring
|
|
409
|
+
* 5. onReady/onClose — lifecycle hooks
|
|
410
|
+
*/
|
|
411
|
+
async function registerResources(fastify, config) {
|
|
412
|
+
if (config.plugins) {
|
|
413
|
+
await config.plugins(fastify);
|
|
414
|
+
fastify.log.debug("Custom plugins registered");
|
|
415
|
+
}
|
|
416
|
+
if (config.bootstrap?.length) {
|
|
417
|
+
for (const init of config.bootstrap) await init(fastify);
|
|
418
|
+
fastify.log.debug(`${config.bootstrap.length} bootstrap function(s) executed`);
|
|
419
|
+
}
|
|
420
|
+
if (config.resources?.length) {
|
|
421
|
+
const seen = /* @__PURE__ */ new Set();
|
|
422
|
+
for (const resource of config.resources) if (resource.name) {
|
|
423
|
+
if (seen.has(resource.name)) fastify.log.warn(`Duplicate resource name "${resource.name}" detected. This will cause route conflicts. Check your resources array and loadResources() output.`);
|
|
424
|
+
seen.add(resource.name);
|
|
425
|
+
}
|
|
426
|
+
const prefixed = [];
|
|
427
|
+
const root = [];
|
|
428
|
+
for (const resource of config.resources) if (resource.skipGlobalPrefix) root.push(resource);
|
|
429
|
+
else prefixed.push(resource);
|
|
430
|
+
for (const resource of root) await registerOne(fastify, resource);
|
|
431
|
+
if (prefixed.length) if (config.resourcePrefix) await fastify.register(async (scoped) => {
|
|
432
|
+
for (const resource of prefixed) await registerOne(scoped, resource);
|
|
433
|
+
}, { prefix: config.resourcePrefix });
|
|
434
|
+
else for (const resource of prefixed) await registerOne(fastify, resource);
|
|
435
|
+
const names = config.resources.map((r) => r.name ?? "?").join(", ");
|
|
436
|
+
const prefix = config.resourcePrefix ? ` (prefix: ${config.resourcePrefix})` : "";
|
|
437
|
+
fastify.log.info(`${config.resources.length} resource(s) registered${prefix}: ${names}`);
|
|
438
|
+
}
|
|
439
|
+
if (config.afterResources) {
|
|
440
|
+
await config.afterResources(fastify);
|
|
441
|
+
fastify.log.debug("afterResources hook executed");
|
|
442
|
+
}
|
|
443
|
+
if (config.onReady) {
|
|
444
|
+
const onReady = config.onReady;
|
|
445
|
+
fastify.addHook("onReady", async () => {
|
|
446
|
+
await onReady(fastify);
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
if (config.onClose) {
|
|
450
|
+
const onClose = config.onClose;
|
|
451
|
+
fastify.addHook("onClose", async () => {
|
|
452
|
+
await onClose(fastify);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
//#endregion
|
|
457
|
+
//#region src/factory/registerSecurity.ts
|
|
227
458
|
const PLUGIN_REGISTRY = {
|
|
228
459
|
cors: {
|
|
229
460
|
package: "@fastify/cors",
|
|
@@ -256,6 +487,7 @@ const PLUGIN_REGISTRY = {
|
|
|
256
487
|
optional: true
|
|
257
488
|
}
|
|
258
489
|
};
|
|
490
|
+
/** Load a plugin from the registry with helpful error messages. */
|
|
259
491
|
async function loadPlugin(name, logger) {
|
|
260
492
|
const entry = PLUGIN_REGISTRY[name];
|
|
261
493
|
if (!entry) throw new Error(`Unknown plugin: ${name}`);
|
|
@@ -273,81 +505,10 @@ async function loadPlugin(name, logger) {
|
|
|
273
505
|
}
|
|
274
506
|
}
|
|
275
507
|
/**
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
* Security plugins are enabled by default (opt-out):
|
|
279
|
-
* - helmet (security headers)
|
|
280
|
-
* - cors (cross-origin requests)
|
|
281
|
-
* - rateLimit (DDoS protection)
|
|
282
|
-
* - underPressure (health monitoring)
|
|
283
|
-
*
|
|
284
|
-
* Note: Compression is not included due to known Fastify 5 issues.
|
|
285
|
-
* Use a reverse proxy (Nginx, Caddy) or CDN for compression.
|
|
286
|
-
*
|
|
287
|
-
* @param options - Application configuration
|
|
288
|
-
* @returns Configured Fastify instance
|
|
508
|
+
* Register security plugins (Helmet, CORS, Rate Limiting).
|
|
509
|
+
* All enabled by default — set to `false` to opt out.
|
|
289
510
|
*/
|
|
290
|
-
async function
|
|
291
|
-
if (options.debug !== void 0 && options.debug !== false) {
|
|
292
|
-
const { configureArcLogger } = await import("./logger-Dz3j1ItV.mjs").then((n) => n.r);
|
|
293
|
-
configureArcLogger({ debug: options.debug });
|
|
294
|
-
}
|
|
295
|
-
const authConfig = options.auth;
|
|
296
|
-
const isAuthDisabled = authConfig === false;
|
|
297
|
-
if (!isAuthDisabled && authConfig && authConfig.type === "jwt") {
|
|
298
|
-
if (!authConfig.jwt?.secret && !authConfig.authenticate) throw new Error("createApp: JWT secret required when Arc auth is enabled.\nProvide auth.jwt.secret, auth.authenticate, or set auth: false to disable.\nExample: auth: { type: 'jwt', jwt: { secret: process.env.JWT_SECRET } }");
|
|
299
|
-
}
|
|
300
|
-
const deferredWarnings = [];
|
|
301
|
-
if (options.runtime === "distributed") {
|
|
302
|
-
const MEMORY_NAMES = new Set(["memory", "memory-cache"]);
|
|
303
|
-
const missing = [];
|
|
304
|
-
const eventsTransport = options.stores?.events;
|
|
305
|
-
if (!eventsTransport || MEMORY_NAMES.has(eventsTransport.name)) missing.push("events transport");
|
|
306
|
-
if (options.arcPlugins?.caching) {
|
|
307
|
-
const cacheStore = options.stores?.cache;
|
|
308
|
-
if (!cacheStore || MEMORY_NAMES.has(cacheStore.name)) missing.push("cache store");
|
|
309
|
-
}
|
|
310
|
-
const idempotencyStore = options.stores?.idempotency;
|
|
311
|
-
if (idempotencyStore && MEMORY_NAMES.has(idempotencyStore.name)) missing.push("idempotency store (memory-backed in distributed mode)");
|
|
312
|
-
else if (!idempotencyStore) deferredWarnings.push("runtime: 'distributed' — no idempotency store configured. Write-path deduplication will be instance-local. If resources use the idempotency plugin, provide stores.idempotency with a Redis/MongoDB store.");
|
|
313
|
-
if (options.arcPlugins?.queryCache) {
|
|
314
|
-
const qcStore = options.stores?.queryCache;
|
|
315
|
-
if (!qcStore || MEMORY_NAMES.has(qcStore.name)) missing.push("queryCache store");
|
|
316
|
-
}
|
|
317
|
-
if (missing.length > 0) throw new Error(`[Arc] runtime: 'distributed' requires Redis/durable adapters.\nMissing: ${missing.join(", ")}.\nProvide Redis-backed stores or use runtime: 'memory' for development.`);
|
|
318
|
-
}
|
|
319
|
-
const config = {
|
|
320
|
-
...options.preset ? getPreset(options.preset) : {},
|
|
321
|
-
...options
|
|
322
|
-
};
|
|
323
|
-
const fastify = Fastify({
|
|
324
|
-
logger: config.logger ?? true,
|
|
325
|
-
trustProxy: config.trustProxy ?? false,
|
|
326
|
-
routerOptions: { querystringParser: (str) => qs.parse(str) },
|
|
327
|
-
ajv: { customOptions: {
|
|
328
|
-
coerceTypes: true,
|
|
329
|
-
useDefaults: true,
|
|
330
|
-
removeAdditional: false,
|
|
331
|
-
keywords: ["example", ...config.ajv?.keywords ?? []]
|
|
332
|
-
} }
|
|
333
|
-
});
|
|
334
|
-
for (const warning of deferredWarnings) fastify.log.warn(warning);
|
|
335
|
-
if (config.typeProvider === "typebox") try {
|
|
336
|
-
const { TypeBoxValidatorCompiler } = await import("@fastify/type-provider-typebox");
|
|
337
|
-
fastify.setValidatorCompiler(TypeBoxValidatorCompiler);
|
|
338
|
-
fastify.log.debug("TypeBox type provider enabled");
|
|
339
|
-
} catch {
|
|
340
|
-
fastify.log.warn("typeProvider: \"typebox\" requested but @fastify/type-provider-typebox is not installed. Install it with: npm install @sinclair/typebox @fastify/type-provider-typebox");
|
|
341
|
-
}
|
|
342
|
-
fastify.removeContentTypeParser("application/json");
|
|
343
|
-
fastify.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
|
|
344
|
-
if (!body || body.length === 0) return done(null, void 0);
|
|
345
|
-
try {
|
|
346
|
-
done(null, JSON.parse(body));
|
|
347
|
-
} catch (err) {
|
|
348
|
-
done(err);
|
|
349
|
-
}
|
|
350
|
-
});
|
|
511
|
+
async function registerSecurityPlugins(fastify, config) {
|
|
351
512
|
if (config.helmet !== false) {
|
|
352
513
|
const helmet = await loadPlugin("helmet");
|
|
353
514
|
await fastify.register(helmet, config.helmet ?? {});
|
|
@@ -369,19 +530,22 @@ async function createApp(options) {
|
|
|
369
530
|
};
|
|
370
531
|
await fastify.register(rateLimit, rateLimitOpts);
|
|
371
532
|
if (!(typeof rateLimitOpts === "object" && "store" in rateLimitOpts)) {
|
|
372
|
-
if (config.runtime === "distributed") {
|
|
373
|
-
|
|
374
|
-
throw new Error("[Arc] runtime: 'distributed' with rate limiting requires a shared store.\nProvide rateLimit: { store: new RedisStore({ ... }) } or disable rate limiting: rateLimit: false");
|
|
375
|
-
} else if (config.preset === "production") fastify.log.warn("Rate limiting is using in-memory store. In multi-instance deployments, each instance tracks limits independently. Configure a Redis store for distributed rate limiting: rateLimit: { store: new RedisStore({ ... }) }");
|
|
533
|
+
if (config.runtime === "distributed") throw new Error("[Arc] runtime: 'distributed' with rate limiting requires a shared store.\nProvide rateLimit: { store: new RedisStore({ ... }) } or disable rate limiting: rateLimit: false");
|
|
534
|
+
else if (config.preset === "production") fastify.log.warn("Rate limiting is using in-memory store. In multi-instance deployments, each instance tracks limits independently. Configure a Redis store for distributed rate limiting.");
|
|
376
535
|
}
|
|
377
536
|
fastify.log.debug("Rate limiting enabled");
|
|
378
537
|
} else fastify.log.warn("Rate limiting disabled");
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Register performance and utility plugins (Under Pressure, Sensible, Multipart, Raw Body).
|
|
541
|
+
*/
|
|
542
|
+
async function registerUtilityPlugins(fastify, config) {
|
|
379
543
|
if (config.preset === "production") fastify.log.warn("Response compression is not enabled (Fastify 5 stream issues). Use a reverse proxy (Nginx, Caddy, Cloudflare) for gzip/brotli in production.");
|
|
380
544
|
if (config.underPressure !== false) {
|
|
381
545
|
const underPressure = await loadPlugin("underPressure");
|
|
382
546
|
await fastify.register(underPressure, config.underPressure ?? { exposeStatusRoute: true });
|
|
383
547
|
fastify.log.debug("Health monitoring (under-pressure) enabled");
|
|
384
|
-
}
|
|
548
|
+
}
|
|
385
549
|
if (config.sensible !== false) {
|
|
386
550
|
const sensible = await loadPlugin("sensible");
|
|
387
551
|
await fastify.register(sensible);
|
|
@@ -414,9 +578,135 @@ async function createApp(options) {
|
|
|
414
578
|
fastify.log.debug("Raw body parsing enabled");
|
|
415
579
|
}
|
|
416
580
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
581
|
+
}
|
|
582
|
+
//#endregion
|
|
583
|
+
//#region src/factory/createApp.ts
|
|
584
|
+
/**
|
|
585
|
+
* ArcFactory - Production-ready Fastify application factory
|
|
586
|
+
*
|
|
587
|
+
* Enforces security best practices by making plugins opt-out instead of opt-in.
|
|
588
|
+
* A developer must explicitly disable security features rather than forget to enable them.
|
|
589
|
+
*
|
|
590
|
+
* Note: Arc is database-agnostic. Connect your database separately and provide
|
|
591
|
+
* adapters when defining resources. This allows multiple databases, custom
|
|
592
|
+
* connection pooling, and full control over your data layer.
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* // 1. Connect your database(s) separately
|
|
596
|
+
* import mongoose from 'mongoose';
|
|
597
|
+
* await mongoose.connect(process.env.MONGO_URI);
|
|
598
|
+
*
|
|
599
|
+
* // 2. Create Arc app with resources
|
|
600
|
+
* const app = await createApp({
|
|
601
|
+
* preset: 'production',
|
|
602
|
+
* auth: { type: 'jwt', jwt: { secret: process.env.JWT_SECRET } },
|
|
603
|
+
* cors: { origin: ['https://example.com'] },
|
|
604
|
+
* resources: [productResource, orderResource],
|
|
605
|
+
* });
|
|
606
|
+
*
|
|
607
|
+
* @example
|
|
608
|
+
* // Multiple databases example
|
|
609
|
+
* const primaryDb = await mongoose.connect(process.env.PRIMARY_DB);
|
|
610
|
+
* const analyticsDb = mongoose.createConnection(process.env.ANALYTICS_DB);
|
|
611
|
+
*
|
|
612
|
+
* const orderResource = defineResource({
|
|
613
|
+
* adapter: createMongooseAdapter({ model: OrderModel, repository: orderRepo }),
|
|
614
|
+
* });
|
|
615
|
+
*
|
|
616
|
+
* const analyticsResource = defineResource({
|
|
617
|
+
* adapter: createMongooseAdapter({ model: AnalyticsModel, repository: analyticsRepo }),
|
|
618
|
+
* });
|
|
619
|
+
*/
|
|
620
|
+
var createApp_exports = /* @__PURE__ */ __exportAll({
|
|
621
|
+
ArcFactory: () => ArcFactory,
|
|
622
|
+
createApp: () => createApp
|
|
623
|
+
});
|
|
624
|
+
const MEMORY_STORE_NAMES = new Set(["memory", "memory-cache"]);
|
|
625
|
+
function validateAuthOptions(options) {
|
|
626
|
+
const authConfig = options.auth;
|
|
627
|
+
if (authConfig === false || !authConfig) return;
|
|
628
|
+
if (authConfig.type === "jwt" && !authConfig.jwt?.secret && !authConfig.authenticate) throw new Error("createApp: JWT secret required when Arc auth is enabled.\nProvide auth.jwt.secret, auth.authenticate, or set auth: false to disable.\nExample: auth: { type: 'jwt', jwt: { secret: process.env.JWT_SECRET } }");
|
|
629
|
+
}
|
|
630
|
+
function validateDistributedRuntime(options) {
|
|
631
|
+
const deferredWarnings = [];
|
|
632
|
+
if (options.runtime !== "distributed") return deferredWarnings;
|
|
633
|
+
const missing = [];
|
|
634
|
+
const events = options.stores?.events;
|
|
635
|
+
if (!events || MEMORY_STORE_NAMES.has(events.name)) missing.push("events transport");
|
|
636
|
+
if (options.arcPlugins?.caching) {
|
|
637
|
+
const cache = options.stores?.cache;
|
|
638
|
+
if (!cache || MEMORY_STORE_NAMES.has(cache.name)) missing.push("cache store");
|
|
639
|
+
}
|
|
640
|
+
const idempotency = options.stores?.idempotency;
|
|
641
|
+
if (idempotency && MEMORY_STORE_NAMES.has(idempotency.name)) missing.push("idempotency store (memory-backed in distributed mode)");
|
|
642
|
+
else if (!idempotency) deferredWarnings.push("runtime: 'distributed' — no idempotency store configured. Write-path deduplication will be instance-local. If resources use the idempotency plugin, provide stores.idempotency with a Redis/MongoDB store.");
|
|
643
|
+
if (options.arcPlugins?.queryCache) {
|
|
644
|
+
const qc = options.stores?.queryCache;
|
|
645
|
+
if (!qc || MEMORY_STORE_NAMES.has(qc.name)) missing.push("queryCache store");
|
|
646
|
+
}
|
|
647
|
+
if (missing.length > 0) throw new Error(`[Arc] runtime: 'distributed' requires Redis/durable adapters.\nMissing: ${missing.join(", ")}.\nProvide Redis-backed stores or use runtime: 'memory' for development.`);
|
|
648
|
+
return deferredWarnings;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Create a production-ready Fastify application with Arc framework.
|
|
652
|
+
*
|
|
653
|
+
* Boot order:
|
|
654
|
+
* ```
|
|
655
|
+
* 0. Logger, validation, preset merge
|
|
656
|
+
* 1. Create Fastify instance
|
|
657
|
+
* 2. Security plugins (Helmet, CORS, Rate Limit) — opt-out
|
|
658
|
+
* 3. Utility plugins (Under Pressure, Sensible, Multipart, Raw Body)
|
|
659
|
+
* 4. Arc core (fastify.arc, events)
|
|
660
|
+
* 5. Arc plugins (requestId, health, caching, SSE, metrics, versioning)
|
|
661
|
+
* 6. Auth (scope decoration, auth strategy, elevation, error handler)
|
|
662
|
+
* 7. plugins() — user infra (DB, docs, webhooks)
|
|
663
|
+
* 8. bootstrap[] — domain init (singletons, event handlers)
|
|
664
|
+
* 9. resources[] — auto-discovered routes (prefix + skipGlobalPrefix)
|
|
665
|
+
* 10. afterResources() — post-registration wiring
|
|
666
|
+
* 11. onReady/onClose — lifecycle hooks
|
|
667
|
+
* ```
|
|
668
|
+
*/
|
|
669
|
+
async function createApp(options) {
|
|
670
|
+
if (options.debug !== void 0 && options.debug !== false) {
|
|
671
|
+
const { configureArcLogger } = await import("./logger-Dz3j1ItV.mjs").then((n) => n.r);
|
|
672
|
+
configureArcLogger({ debug: options.debug });
|
|
673
|
+
}
|
|
674
|
+
validateAuthOptions(options);
|
|
675
|
+
const deferredWarnings = validateDistributedRuntime(options);
|
|
676
|
+
const config = {
|
|
677
|
+
...options.preset ? getPreset(options.preset) : {},
|
|
678
|
+
...options
|
|
679
|
+
};
|
|
680
|
+
const fastify = Fastify({
|
|
681
|
+
logger: config.logger ?? true,
|
|
682
|
+
trustProxy: config.trustProxy ?? false,
|
|
683
|
+
routerOptions: { querystringParser: (str) => qs.parse(str) },
|
|
684
|
+
ajv: { customOptions: {
|
|
685
|
+
coerceTypes: true,
|
|
686
|
+
useDefaults: true,
|
|
687
|
+
removeAdditional: false,
|
|
688
|
+
keywords: ["example", ...config.ajv?.keywords ?? []]
|
|
689
|
+
} }
|
|
690
|
+
});
|
|
691
|
+
for (const warning of deferredWarnings) fastify.log.warn(warning);
|
|
692
|
+
if (config.typeProvider === "typebox") try {
|
|
693
|
+
const { TypeBoxValidatorCompiler } = await import("@fastify/type-provider-typebox");
|
|
694
|
+
fastify.setValidatorCompiler(TypeBoxValidatorCompiler);
|
|
695
|
+
} catch {
|
|
696
|
+
fastify.log.warn("typeProvider: \"typebox\" requested but @fastify/type-provider-typebox is not installed.");
|
|
697
|
+
}
|
|
698
|
+
const sjp = await import("secure-json-parse");
|
|
699
|
+
fastify.removeContentTypeParser("application/json");
|
|
700
|
+
fastify.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
|
|
701
|
+
if (!body || body.length === 0) return done(null, void 0);
|
|
702
|
+
try {
|
|
703
|
+
done(null, sjp.parse(body));
|
|
704
|
+
} catch (err) {
|
|
705
|
+
done(err);
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
await registerSecurityPlugins(fastify, config);
|
|
709
|
+
await registerUtilityPlugins(fastify, config);
|
|
420
710
|
const trackPlugin = (name, opts) => {
|
|
421
711
|
fastify.arc.plugins.set(name, {
|
|
422
712
|
name,
|
|
@@ -424,162 +714,13 @@ async function createApp(options) {
|
|
|
424
714
|
registeredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
425
715
|
});
|
|
426
716
|
};
|
|
427
|
-
trackPlugin(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
});
|
|
435
|
-
trackPlugin("arc-events", eventOpts);
|
|
436
|
-
fastify.log.debug(`Arc events plugin enabled (transport: ${fastify.events.transportName})`);
|
|
437
|
-
}
|
|
438
|
-
if (config.arcPlugins?.requestId !== false) {
|
|
439
|
-
await fastify.register(requestIdPlugin);
|
|
440
|
-
trackPlugin("arc-request-id");
|
|
441
|
-
fastify.log.debug("Arc requestId plugin enabled");
|
|
442
|
-
}
|
|
443
|
-
if (config.arcPlugins?.health !== false) {
|
|
444
|
-
await fastify.register(healthPlugin);
|
|
445
|
-
trackPlugin("arc-health");
|
|
446
|
-
fastify.log.debug("Arc health plugin enabled");
|
|
447
|
-
}
|
|
448
|
-
if (config.arcPlugins?.gracefulShutdown !== false) {
|
|
449
|
-
await fastify.register(gracefulShutdownPlugin);
|
|
450
|
-
trackPlugin("arc-graceful-shutdown");
|
|
451
|
-
fastify.log.debug("Arc gracefulShutdown plugin enabled");
|
|
452
|
-
}
|
|
453
|
-
if (config.arcPlugins?.caching) {
|
|
454
|
-
const { default: cachingPlugin } = await import("./caching-BSXB-Xr7.mjs").then((n) => n.r);
|
|
455
|
-
const cachingOpts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
|
|
456
|
-
await fastify.register(cachingPlugin, cachingOpts);
|
|
457
|
-
trackPlugin("arc-caching", cachingOpts);
|
|
458
|
-
fastify.log.debug("Arc caching plugin enabled");
|
|
459
|
-
}
|
|
460
|
-
if (config.arcPlugins?.queryCache) {
|
|
461
|
-
const { queryCachePlugin } = await import("./queryCachePlugin-XtFplYO9.mjs").then((n) => n.n);
|
|
462
|
-
const qcOpts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
|
|
463
|
-
const store = options.stores?.queryCache ?? new (await (import("./memory-BFAYkf8H.mjs").then((n) => n.n))).MemoryCacheStore();
|
|
464
|
-
await fastify.register(queryCachePlugin, {
|
|
465
|
-
store,
|
|
466
|
-
...qcOpts
|
|
467
|
-
});
|
|
468
|
-
trackPlugin("arc-query-cache", qcOpts);
|
|
469
|
-
fastify.log.debug("Arc queryCache plugin enabled");
|
|
470
|
-
}
|
|
471
|
-
if (config.arcPlugins?.sse) if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
|
|
472
|
-
else {
|
|
473
|
-
const { default: ssePlugin } = await import("./sse-BkViJPlT.mjs").then((n) => n.r);
|
|
474
|
-
const sseOpts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
|
|
475
|
-
await fastify.register(ssePlugin, sseOpts);
|
|
476
|
-
trackPlugin("arc-sse", sseOpts);
|
|
477
|
-
fastify.log.debug("Arc SSE plugin enabled");
|
|
478
|
-
}
|
|
479
|
-
if (config.arcPlugins?.metrics) {
|
|
480
|
-
const { default: metricsPlugin } = await import("./metrics-Csh4nsvv.mjs").then((n) => n.r);
|
|
481
|
-
const metricsOpts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
|
|
482
|
-
await fastify.register(metricsPlugin, metricsOpts);
|
|
483
|
-
trackPlugin("arc-metrics", metricsOpts);
|
|
484
|
-
fastify.log.debug("Arc metrics plugin enabled");
|
|
485
|
-
}
|
|
486
|
-
if (config.arcPlugins?.versioning) {
|
|
487
|
-
const { default: versioningPlugin } = await import("./versioning-BzfeHmhj.mjs").then((n) => n.r);
|
|
488
|
-
await fastify.register(versioningPlugin, config.arcPlugins.versioning);
|
|
489
|
-
trackPlugin("arc-versioning", config.arcPlugins.versioning);
|
|
490
|
-
fastify.log.debug("Arc versioning plugin enabled");
|
|
491
|
-
}
|
|
492
|
-
fastify.decorateRequest("scope", null);
|
|
493
|
-
fastify.addHook("onRequest", async (request) => {
|
|
494
|
-
if (!request.scope) request.scope = PUBLIC_SCOPE;
|
|
495
|
-
});
|
|
496
|
-
if (isAuthDisabled) fastify.log.debug("Authentication disabled");
|
|
497
|
-
else if (authConfig) switch (authConfig.type) {
|
|
498
|
-
case "betterAuth": {
|
|
499
|
-
const { plugin, openapi } = authConfig.betterAuth;
|
|
500
|
-
await fastify.register(plugin);
|
|
501
|
-
trackPlugin("auth-better-auth");
|
|
502
|
-
if (openapi && !fastify.arc.externalOpenApiPaths.includes(openapi)) fastify.arc.externalOpenApiPaths.push(openapi);
|
|
503
|
-
fastify.log.debug("Better Auth authentication enabled");
|
|
504
|
-
break;
|
|
505
|
-
}
|
|
506
|
-
case "custom":
|
|
507
|
-
await fastify.register(authConfig.plugin);
|
|
508
|
-
trackPlugin("auth-custom");
|
|
509
|
-
fastify.log.debug("Custom authentication plugin enabled");
|
|
510
|
-
break;
|
|
511
|
-
case "authenticator": {
|
|
512
|
-
const { authenticate, optionalAuthenticate } = authConfig;
|
|
513
|
-
fastify.decorate("authenticate", async (request, reply) => {
|
|
514
|
-
await authenticate(request, reply);
|
|
515
|
-
});
|
|
516
|
-
if (!fastify.hasDecorator("optionalAuthenticate")) if (optionalAuthenticate) fastify.decorate("optionalAuthenticate", async (request, reply) => {
|
|
517
|
-
await optionalAuthenticate(request, reply);
|
|
518
|
-
});
|
|
519
|
-
else fastify.decorate("optionalAuthenticate", async (request, reply) => {
|
|
520
|
-
let intercepted = false;
|
|
521
|
-
const proxyReply = new Proxy(reply, { get(target, prop) {
|
|
522
|
-
if (prop === "code") return (statusCode) => {
|
|
523
|
-
if (statusCode === 401 || statusCode === 403) {
|
|
524
|
-
intercepted = true;
|
|
525
|
-
return new Proxy(target, { get(_t, p) {
|
|
526
|
-
if (p === "send" || p === "type" || p === "header" || p === "headers") return () => proxyReply;
|
|
527
|
-
return Reflect.get(target, p, target);
|
|
528
|
-
} });
|
|
529
|
-
}
|
|
530
|
-
return target.code(statusCode);
|
|
531
|
-
};
|
|
532
|
-
if (prop === "send" && intercepted) return () => proxyReply;
|
|
533
|
-
if (prop === "sent") return intercepted ? false : target.sent;
|
|
534
|
-
return Reflect.get(target, prop, target);
|
|
535
|
-
} });
|
|
536
|
-
try {
|
|
537
|
-
await authenticate(request, proxyReply);
|
|
538
|
-
} catch {}
|
|
539
|
-
});
|
|
540
|
-
trackPlugin("auth-authenticator");
|
|
541
|
-
fastify.log.debug("Custom authenticator enabled");
|
|
542
|
-
break;
|
|
543
|
-
}
|
|
544
|
-
case "jwt": {
|
|
545
|
-
const { authPlugin } = await import("./auth/index.mjs");
|
|
546
|
-
const { type: _, ...arcAuthOpts } = authConfig;
|
|
547
|
-
await fastify.register(authPlugin, arcAuthOpts);
|
|
548
|
-
trackPlugin("auth-jwt");
|
|
549
|
-
fastify.log.debug("Arc authentication plugin enabled");
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
if (config.elevation) {
|
|
554
|
-
const { elevationPlugin } = await import("./elevation-BEdACOLB.mjs").then((n) => n.r);
|
|
555
|
-
await fastify.register(elevationPlugin, config.elevation);
|
|
556
|
-
trackPlugin("arc-elevation", config.elevation);
|
|
557
|
-
fastify.log.debug("Elevation plugin enabled");
|
|
558
|
-
}
|
|
559
|
-
if (config.errorHandler !== false) {
|
|
560
|
-
const { errorHandlerPlugin } = await import("./errorHandler-DMbGdzBG.mjs").then((n) => n.n);
|
|
561
|
-
const errorOpts = typeof config.errorHandler === "object" ? config.errorHandler : { includeStack: config.preset !== "production" };
|
|
562
|
-
await fastify.register(errorHandlerPlugin, errorOpts);
|
|
563
|
-
trackPlugin("arc-error-handler", errorOpts);
|
|
564
|
-
fastify.log.debug("Arc error handler enabled");
|
|
565
|
-
}
|
|
566
|
-
if (config.plugins) {
|
|
567
|
-
await config.plugins(fastify);
|
|
568
|
-
fastify.log.debug("Custom plugins registered");
|
|
569
|
-
}
|
|
570
|
-
if (config.onReady) {
|
|
571
|
-
const onReady = config.onReady;
|
|
572
|
-
fastify.addHook("onReady", async () => {
|
|
573
|
-
await onReady(fastify);
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
if (config.onClose) {
|
|
577
|
-
const onClose = config.onClose;
|
|
578
|
-
fastify.addHook("onClose", async () => {
|
|
579
|
-
await onClose(fastify);
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
const authMode = isAuthDisabled ? "none" : authConfig ? authConfig.type : "none";
|
|
717
|
+
await registerArcPlugins(fastify, config, trackPlugin, await registerArcCore(fastify, config, trackPlugin));
|
|
718
|
+
decorateRequestScope(fastify);
|
|
719
|
+
await registerAuth(fastify, config, trackPlugin);
|
|
720
|
+
await registerElevation(fastify, config, trackPlugin);
|
|
721
|
+
await registerErrorHandler(fastify, config, trackPlugin);
|
|
722
|
+
await registerResources(fastify, config);
|
|
723
|
+
const authMode = config.auth === false ? "none" : config.auth ? config.auth.type : "none";
|
|
583
724
|
fastify.log.info({
|
|
584
725
|
preset: config.preset ?? "custom",
|
|
585
726
|
runtime: config.runtime ?? "memory",
|
|
@@ -614,4 +755,4 @@ const ArcFactory = {
|
|
|
614
755
|
}
|
|
615
756
|
};
|
|
616
757
|
//#endregion
|
|
617
|
-
export {
|
|
758
|
+
export { edgePreset as a, testingPreset as c, developmentPreset as i, createApp as n, getPreset as o, createApp_exports as r, productionPreset as s, ArcFactory as t };
|