@classytic/arc 2.31.0 → 2.33.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/README.md +1 -1
- package/bin/arc.js +58 -17
- package/dist/{BaseController-De7GqHck.mjs → BaseController-CaQC5EXX.mjs} +298 -35
- package/dist/{EventTransport-2uVV34WU.d.mts → EventTransport-BEVP50ka.d.mts} +16 -0
- package/dist/{EventTransport-9mlTgqzE.mjs → EventTransport-tq4CEB-q.mjs} +39 -2
- package/dist/{HookSystem-BcmPyq5I.mjs → HookSystem-DqXkMXkB.mjs} +2 -2
- package/dist/{ResourceRegistry-qq0mn2s9.mjs → ResourceRegistry-DoALUlOO.mjs} +18 -15
- package/dist/{actionPermissions-BE5NwaQk.mjs → actionPermissions-CUeCmpUd.mjs} +1 -1
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +2 -2
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +44 -11
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-ChvUQGG1.mjs → authorizationDecision-B3mP_QTy.mjs} +2 -2
- package/dist/{types-B7XeLEgK.d.mts → base-Csjc2oF-.d.mts} +74 -74
- package/dist/{buildHandler-CDrCJzmB.mjs → buildHandler-DLE9xX-V.mjs} +6 -5
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-BFHA6Scf.mjs → caching-BcGmvnHh.mjs} +2 -2
- package/dist/cleanup/index.d.mts +52 -18
- package/dist/cleanup/index.mjs +281 -32
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +4 -6
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -2
- package/dist/cli/commands/init.mjs +2 -2
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-D3Dv4LAV.mjs → constants-BxCoxmiN.mjs} +1 -1
- package/dist/context/index.mjs +3 -3
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +7 -7
- package/dist/{core-BGpIxINN.mjs → core-dxhcHhUD.mjs} +3 -3
- package/dist/{createActionRouter-CDXEdasl.mjs → createActionRouter-DVpoA0do.mjs} +14 -11
- package/dist/{createAggregationRouter-KrQDzx-k.mjs → createAggregationRouter-B_B48JZ-.mjs} +10 -8
- package/dist/{createApp-Df89BEni.mjs → createApp-CLGegqiG.mjs} +287 -62
- package/dist/{defineAggregation-DbPPS8RI.mjs → defineAggregation-Nv-DwfOf.mjs} +1 -1
- package/dist/{defineResource-Bv6EtN1x.mjs → defineResource-DR9vsUYy.mjs} +318 -35
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +3 -3
- package/dist/{elevation-D2l1mh4g.mjs → elevation-B0tlY8y1.mjs} +4 -4
- package/dist/encryption/index.mjs +5 -4
- package/dist/{errorHandler-C5NeD6bT.mjs → errorHandler-C7xBqHsI.mjs} +42 -5
- package/dist/{errorHandler-CpYUJmPA.d.mts → errorHandler-DI5AN2b5.d.mts} +14 -3
- package/dist/{errors-Cj42AntO.mjs → errors-Cd_yy7aW.mjs} +1 -1
- package/dist/{eventPlugin-GF5nnBIq.d.mts → eventPlugin-DSNEZWfV.d.mts} +29 -1
- package/dist/{eventPlugin-CkffEihm.mjs → eventPlugin-Dar8qRaj.mjs} +68 -11
- package/dist/events/index.d.mts +18 -297
- package/dist/events/index.mjs +7 -758
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +14 -10
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +1 -1
- package/dist/factory/index.d.mts +20 -5
- package/dist/factory/index.mjs +131 -8
- package/dist/fastify-augmentation-DVsTAy6X.d.mts +179 -0
- package/dist/{fields-CB5Cuzk9.mjs → fields-CojeUkS4.mjs} +6 -11
- package/dist/{generate-EcA6ui4i.mjs → generate-DiB0nHPR.mjs} +2 -2
- package/dist/{headers-3zvE0rM3.mjs → headers-CveNO9gR.mjs} +2 -2
- package/dist/{health-DjZYjM7H.mjs → health-CasG2A28.mjs} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.d.mts +45 -1
- package/dist/idempotency/index.mjs +95 -11
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-CEJNZFtX.d.mts → index-B-DvUzsr.d.mts} +82 -3
- package/dist/{index-CpzxqePp.d.mts → index-BcR2bFZQ.d.mts} +707 -373
- package/dist/{index-Bc2ox8_U.d.mts → index-Brin0ODU.d.mts} +4 -4
- package/dist/{index-CFThn1K9.d.mts → index-C5NnMEKF.d.mts} +70 -4
- package/dist/{index-C-481Zf2.d.mts → index-ugr1tQlW.d.mts} +17 -1
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +11 -11
- package/dist/{init-BEiNZBsJ.mjs → init-mdJ-v4Xp.mjs} +24 -16
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +2 -2
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +100 -98
- package/dist/integrations/mcp/index.d.mts +12 -3
- package/dist/integrations/mcp/index.mjs +2 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.mjs +3 -3
- package/dist/integrations/webhooks.d.mts +124 -1
- package/dist/integrations/webhooks.mjs +180 -2
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.mjs +38 -35
- package/dist/{interface-DYzM0q-u.mjs → interface-DznJrm9R.mjs} +1 -1
- package/dist/{jsonBody-BYbLDi5D.mjs → jsonBody-CfGH-GxO.mjs} +2 -2
- package/dist/{keys-C0_pa1tY.mjs → keys-WeKvY1dE.mjs} +1 -1
- package/dist/{loadResources-yawi9tAE.mjs → loadResources-Bj-aSsXi.mjs} +4 -3
- package/dist/{loadResourcesFromEntry-BAjYZonx.mjs → loadResourcesFromEntry-DwaxNcE2.mjs} +2 -2
- package/dist/logger/index.mjs +1 -1
- package/dist/{mcpPlugin-B_es0hiF.mjs → mcpPlugin-aJg5TqHi.mjs} +76 -36
- package/dist/{memory-DR6wpa9F.mjs → memory-qYSB7rMQ.mjs} +5 -5
- package/dist/{metrics-CUdw_wLB.mjs → metrics-D7uchLjN.mjs} +2 -2
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +3 -3
- package/dist/{module-0pFJA6YW.mjs → module-CwKsgZXp.mjs} +1 -1
- package/dist/{multipartBody-wOgj_fnj.mjs → multipartBody-4w-S3-PU.mjs} +3 -3
- package/dist/{openapi-B2L4js1v.mjs → openapi-BN5cQaO2.mjs} +6 -6
- package/dist/outbox-admin/index.d.mts +85 -0
- package/dist/outbox-admin/index.mjs +115 -0
- package/dist/outbox-module-CXB79bvt.mjs +839 -0
- package/dist/outbox-module-D-VFq37T.d.mts +368 -0
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +6 -6
- package/dist/{permissions-L--QDAir.mjs → permissions-BAfwgBxc.mjs} +120 -8
- package/dist/{pipe-DRDNsI2T.mjs → pipe-BzzoEhHj.mjs} +3 -5
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +2 -2
- package/dist/{pipeline-UPQFatit.mjs → pipeline-q69DZzgG.mjs} +17 -15
- package/dist/plugins/index.d.mts +6 -6
- package/dist/plugins/index.mjs +16 -16
- package/dist/plugins/response-cache.mjs +2 -2
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-O0FZgESl.mjs → pluralize-B7xf8qZF.mjs} +1 -1
- package/dist/{preloadResources-Dz6BjzM6.mjs → preloadResources-BkyqeTDz.mjs} +1 -1
- package/dist/{preloadResources-CyoJMSiO.d.mts → preloadResources-g07ldtzh.d.mts} +30 -7
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +6 -6
- package/dist/presets/index.d.mts +2 -2
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +3 -3
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-C4-g4_4P.mjs → presets-D-h0uWtP.mjs} +6 -5
- package/dist/projection-BKM0Ui-s.mjs +42 -0
- package/dist/{queryCachePlugin-BZr8v46E.mjs → queryCachePlugin-DhsZ0ZIc.mjs} +50 -8
- package/dist/{rateLimit-CMPY5Qms.mjs → rateLimit-D4-ASwsI.mjs} +2 -2
- package/dist/{rateLimitKey-Dn5JNmkj.mjs → rateLimitKey-XngqVNEV.mjs} +1 -1
- package/dist/{realtime-C5NoxWFY.mjs → realtime-CTkv3O59.mjs} +10 -10
- package/dist/{redis-stream-qN6A-RFv.d.mts → redis-stream-mwLWlfHG.d.mts} +1 -1
- package/dist/{registerRoute-CBN84fc7.mjs → registerRoute-DNnd3vkz.mjs} +10 -8
- package/dist/registry/index.d.mts +3 -3
- package/dist/registry/index.mjs +3 -3
- package/dist/{registry-4213U4zb.mjs → registry-CLxCR5LF.mjs} +2 -2
- package/dist/{renewingLease-Dll8mGgc.mjs → renewingLease-BnWtbA2U.mjs} +1 -1
- package/dist/{replyHelpers-DIiZKGUt.mjs → replyHelpers-BSBVCwL3.mjs} +2 -2
- package/dist/{repositoryFilter-f9eAfq_l.mjs → repositoryFilter-DKny7PDn.mjs} +1 -1
- package/dist/{requestContext-2TxXOJ7T.mjs → requestContext-uYE35JxI.mjs} +1 -1
- package/dist/{rolldown-runtime-CUsjNX9R.mjs → rolldown-runtime-DNg-KNCk.mjs} +1 -1
- package/dist/runtimeCapabilities-Dm88DaTW.mjs +49 -0
- package/dist/{schedules-DdtlICTf.mjs → schedules-CAcZgia0.mjs} +3 -3
- package/dist/{schemaConverter-D--wdJM-.mjs → schemaConverter-XH1RZRyu.mjs} +1 -1
- package/dist/{schemaIR-Bm2enbnh.mjs → schemaIR-CDvclAew.mjs} +2 -2
- package/dist/schemas/index.mjs +1 -1
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.mjs +5 -5
- package/dist/{scope-DHx5Fous.mjs → scope-BaWcqBvr.mjs} +4 -4
- package/dist/{simpleEqualityMatcher-DjQJ7FKA.mjs → simpleEqualityMatcher-Se8VrsaP.mjs} +1 -1
- package/dist/{sse-CUTo9y0X.mjs → sse-Cf0jUHsh.mjs} +7 -6
- package/dist/{sseStream-B_5XcVVd.mjs → sseStream-Bvpi-UXQ.mjs} +2 -2
- package/dist/{store-helpers-D1MP_gRG.mjs → store-helpers-B88p99Z2.mjs} +1 -1
- package/dist/{streaming-PSZwi8JV.mjs → streaming-DsT1B5zJ.mjs} +1 -1
- package/dist/{subscribe-helpers-C0Tb2aUp.mjs → subscribe-helpers-CLIOHsOK.mjs} +2 -2
- package/dist/sync/changelog.mjs +1 -1
- package/dist/testing/cleanupStoreContract.d.mts +1 -1
- package/dist/testing/cleanupStoreContract.mjs +12 -6
- package/dist/testing/index.d.mts +52 -13
- package/dist/testing/index.mjs +110 -40
- package/dist/testing/outboxStoreContract.d.mts +11 -2
- package/dist/testing/outboxStoreContract.mjs +110 -21
- package/dist/testing/storageContract.mjs +11 -6
- package/dist/{transactionContext-dAfBVUrk.mjs → transactionContext-BYtWnwAS.mjs} +1 -1
- package/dist/{typeGuards-B0ryPo3I.mjs → typeGuards-jjKJDN13.mjs} +1 -1
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CBgvkp8O.d.mts → types-BcTumAz_.d.mts} +128 -18
- package/dist/{types-CwcUQ610.d.mts → types-BfC83KsP.d.mts} +15 -1
- package/dist/{types-Cys0ko9Z.mjs → types-CoqIRrkY.mjs} +4 -4
- package/dist/{types-C4QE8qnm.d.mts → types-DVLddu2q.d.mts} +144 -1
- package/dist/usage/index.d.mts +1 -1
- package/dist/usage/index.mjs +0 -0
- package/dist/{userHelpers-ChYJ6CdK.mjs → userHelpers-CWF5pbZD.mjs} +1 -1
- package/dist/utils/index.d.mts +57 -5
- package/dist/utils/index.mjs +12 -11
- package/dist/{utils-vL8BrDE2.mjs → utils-h1V_sT-t.mjs} +38 -7
- package/dist/{validate-_KbifpHF.mjs → validate-CdQlz52F.mjs} +2 -2
- package/dist/{versioning-D_rhHfZm.d.mts → versioning-DEs-_hFi.d.mts} +1 -1
- package/dist/{versioning-CHDTor3P.mjs → versioning-rZbyLVD-.mjs} +4 -4
- package/package.json +31 -20
- package/skills/arc/SKILL.md +96 -4
- package/skills/arc/references/api-reference.md +19 -0
- package/skills/arc/references/production.md +2 -2
- package/dist/fastify-augmentation-zh0_AYHa.d.mts +0 -85
- package/dist/projection-BHw6xlOQ.mjs +0 -18
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-DNg-KNCk.mjs";
|
|
3
3
|
import { arcLog, configureArcLogger, createPinoWriter } from "./logger/index.mjs";
|
|
4
|
-
import { n as PUBLIC_SCOPE } from "./types-
|
|
5
|
-
import { t as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import { n as PUBLIC_SCOPE } from "./types-CoqIRrkY.mjs";
|
|
5
|
+
import { t as auditRuntimeCapabilities } from "./runtimeCapabilities-Dm88DaTW.mjs";
|
|
6
|
+
import { t as createTenantKeyGenerator } from "./rateLimitKey-XngqVNEV.mjs";
|
|
7
|
+
import { i as createRequestIdGenerator, r as mergeHealthChecks } from "./health-CasG2A28.mjs";
|
|
8
|
+
import { t as parseJsonBody } from "./jsonBody-CfGH-GxO.mjs";
|
|
9
|
+
import { r as wrapWithBoundary } from "./subscribe-helpers-CLIOHsOK.mjs";
|
|
10
|
+
import { isProductionEnv } from "@classytic/primitives/environment";
|
|
9
11
|
import Fastify from "fastify";
|
|
10
12
|
import qs from "qs";
|
|
11
13
|
//#region src/factory/security/pluginLoader.ts
|
|
@@ -183,12 +185,15 @@ async function resolveContribution(contribution, fastify) {
|
|
|
183
185
|
* them. The original error is preserved via `{ cause }`.
|
|
184
186
|
*/
|
|
185
187
|
async function resolveModuleArm(module, arm, contribution, fastify) {
|
|
188
|
+
let resolved;
|
|
186
189
|
try {
|
|
187
|
-
|
|
190
|
+
resolved = await resolveContribution(contribution, fastify);
|
|
188
191
|
} catch (err) {
|
|
189
192
|
const msg = err instanceof Error ? err.message : String(err);
|
|
190
193
|
throw new Error(`[arc] module "${module.name}" ${arm} factory threw: ${msg}`, { cause: err });
|
|
191
194
|
}
|
|
195
|
+
if (!Array.isArray(resolved)) throw new TypeError(`[arc] module "${module.name}" ${arm} factory must return an array — received ${resolved === null ? "null" : typeof resolved}.`);
|
|
196
|
+
return resolved;
|
|
192
197
|
}
|
|
193
198
|
//#endregion
|
|
194
199
|
//#region src/factory/module/contributions.ts
|
|
@@ -227,11 +232,12 @@ async function collectModuleWorkflows(fastify, modules) {
|
|
|
227
232
|
* modules. The caller passes the returned definitions to the canonical
|
|
228
233
|
* schedules plugin; factories must therefore be resolved only once.
|
|
229
234
|
*/
|
|
230
|
-
async function collectModuleScheduledJobs(fastify, modules) {
|
|
235
|
+
async function collectModuleScheduledJobs(fastify, modules, countsByModule) {
|
|
231
236
|
const owner = /* @__PURE__ */ new Map();
|
|
232
237
|
const out = [];
|
|
233
238
|
for (const m of modules) {
|
|
234
239
|
const jobs = await resolveModuleArm(m, "scheduledJobs", m.scheduledJobs, fastify);
|
|
240
|
+
countsByModule?.set(m.name, jobs.length);
|
|
235
241
|
for (const job of jobs) {
|
|
236
242
|
const prior = owner.get(job.name);
|
|
237
243
|
if (prior !== void 0) throw new Error(`[arc] duplicate scheduled-job name "${job.name}" — declared by module "${prior}" and module "${m.name}". Scheduled-job names must be unique across the module graph.`);
|
|
@@ -242,6 +248,69 @@ async function collectModuleScheduledJobs(fastify, modules) {
|
|
|
242
248
|
return out;
|
|
243
249
|
}
|
|
244
250
|
//#endregion
|
|
251
|
+
//#region src/factory/module/descriptor.ts
|
|
252
|
+
/**
|
|
253
|
+
* Build a descriptor. Called by `registerResources` once per module, after
|
|
254
|
+
* resources are resolved and `owns` is effective.
|
|
255
|
+
*
|
|
256
|
+
* Frozen at every level: a descriptor is a snapshot of a completed decision,
|
|
257
|
+
* and a consumer mutating it would silently disagree with the running app.
|
|
258
|
+
*/
|
|
259
|
+
function describeResolvedModule(input) {
|
|
260
|
+
return Object.freeze({
|
|
261
|
+
name: input.name,
|
|
262
|
+
dependsOn: Object.freeze([...input.dependsOn ?? []]),
|
|
263
|
+
resources: Object.freeze(input.resources.map((r) => Object.freeze({
|
|
264
|
+
...r.name !== void 0 ? { name: r.name } : {},
|
|
265
|
+
...r.prefix !== void 0 ? { prefix: r.prefix } : {},
|
|
266
|
+
skipGlobalPrefix: r.skipGlobalPrefix === true
|
|
267
|
+
}))),
|
|
268
|
+
owns: Object.freeze([...input.owns]),
|
|
269
|
+
lifecycle: Object.freeze({ ...input.lifecycle })
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region src/factory/module/disposers.ts
|
|
274
|
+
function createDisposerRegistry(fastify) {
|
|
275
|
+
const stacks = /* @__PURE__ */ new Map();
|
|
276
|
+
const drained = /* @__PURE__ */ new Set();
|
|
277
|
+
const contexts = /* @__PURE__ */ new Map();
|
|
278
|
+
return {
|
|
279
|
+
contextFor(moduleName) {
|
|
280
|
+
const existing = contexts.get(moduleName);
|
|
281
|
+
if (existing) return existing;
|
|
282
|
+
const ctx = { defer(disposer) {
|
|
283
|
+
if (typeof disposer !== "function") throw new TypeError(`[arc] module "${moduleName}" called defer() with a ${typeof disposer} — expected a function.`);
|
|
284
|
+
if (drained.has(moduleName)) {
|
|
285
|
+
(async () => {
|
|
286
|
+
try {
|
|
287
|
+
await disposer();
|
|
288
|
+
} catch (err) {
|
|
289
|
+
fastify.log?.error({
|
|
290
|
+
err,
|
|
291
|
+
module: moduleName
|
|
292
|
+
}, `[arc] module "${moduleName}" deferred a disposer after teardown had already run; it was invoked immediately and threw.`);
|
|
293
|
+
}
|
|
294
|
+
})();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const stack = stacks.get(moduleName);
|
|
298
|
+
if (stack) stack.push(disposer);
|
|
299
|
+
else stacks.set(moduleName, [disposer]);
|
|
300
|
+
} };
|
|
301
|
+
contexts.set(moduleName, ctx);
|
|
302
|
+
return ctx;
|
|
303
|
+
},
|
|
304
|
+
take(moduleName) {
|
|
305
|
+
drained.add(moduleName);
|
|
306
|
+
const stack = stacks.get(moduleName);
|
|
307
|
+
if (!stack?.length) return [];
|
|
308
|
+
stacks.delete(moduleName);
|
|
309
|
+
return stack.reverse();
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
//#endregion
|
|
245
314
|
//#region src/factory/module/lifecycle.ts
|
|
246
315
|
/**
|
|
247
316
|
* Subscribe every module's `eventHandlers` in dependency order (pass the
|
|
@@ -253,12 +322,13 @@ async function collectModuleScheduledJobs(fastify, modules) {
|
|
|
253
322
|
* Resolves factory contributions (after bootstraps) so a handler can close over
|
|
254
323
|
* booted engines rather than a global getter.
|
|
255
324
|
*/
|
|
256
|
-
async function subscribeModuleEventHandlers(fastify, modules) {
|
|
325
|
+
async function subscribeModuleEventHandlers(fastify, modules, countsByModule) {
|
|
257
326
|
const unsubscribes = [];
|
|
258
327
|
const owner = /* @__PURE__ */ new Map();
|
|
259
328
|
try {
|
|
260
329
|
for (const m of modules) {
|
|
261
330
|
const handlers = await resolveModuleArm(m, "eventHandlers", m.eventHandlers, fastify);
|
|
331
|
+
countsByModule?.set(m.name, handlers.length);
|
|
262
332
|
if (handlers.length === 0) continue;
|
|
263
333
|
const bus = fastify.events;
|
|
264
334
|
if (!bus) throw new Error(`[arc] module "${m.name}" declares eventHandlers but the event subsystem is unavailable (fastify.events). Enable arcPlugins.events.`);
|
|
@@ -460,7 +530,7 @@ function getModuleState(fastify, name) {
|
|
|
460
530
|
}
|
|
461
531
|
//#endregion
|
|
462
532
|
//#region src/factory/module/teardown.ts
|
|
463
|
-
function createModuleTeardown(fastify, modules) {
|
|
533
|
+
function createModuleTeardown(fastify, modules, disposers) {
|
|
464
534
|
const initializedNames = /* @__PURE__ */ new Set();
|
|
465
535
|
const consumed = /* @__PURE__ */ new Set();
|
|
466
536
|
async function closeInitialized() {
|
|
@@ -469,18 +539,30 @@ function createModuleTeardown(fastify, modules) {
|
|
|
469
539
|
const m = modules[index];
|
|
470
540
|
if (!m || !initializedNames.has(m.name) || consumed.has(m.name)) continue;
|
|
471
541
|
consumed.add(m.name);
|
|
472
|
-
|
|
542
|
+
const stack = disposers?.take(m.name) ?? [];
|
|
543
|
+
if (!m.onClose && stack.length === 0) {
|
|
473
544
|
setModuleState(fastify, m.name, "closed");
|
|
474
545
|
continue;
|
|
475
546
|
}
|
|
476
547
|
setModuleState(fastify, m.name, "closing");
|
|
477
|
-
|
|
548
|
+
let teardownFailed = false;
|
|
549
|
+
if (m.onClose) try {
|
|
478
550
|
await m.onClose(fastify);
|
|
479
|
-
setModuleState(fastify, m.name, "closed");
|
|
480
551
|
} catch (err) {
|
|
481
|
-
|
|
552
|
+
teardownFailed = true;
|
|
482
553
|
errors.push(new Error(`[arc] module "${m.name}" onClose() threw during teardown: ${err instanceof Error ? err.message : String(err)}`, { cause: err }));
|
|
483
554
|
}
|
|
555
|
+
for (let d = 0; d < stack.length; d++) {
|
|
556
|
+
const dispose = stack[d];
|
|
557
|
+
if (!dispose) continue;
|
|
558
|
+
try {
|
|
559
|
+
await dispose();
|
|
560
|
+
} catch (err) {
|
|
561
|
+
teardownFailed = true;
|
|
562
|
+
errors.push(new Error(`[arc] module "${m.name}" deferred disposer #${stack.length - d} threw during teardown: ${err instanceof Error ? err.message : String(err)}`, { cause: err }));
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
setModuleState(fastify, m.name, teardownFailed ? "failed" : "closed");
|
|
484
566
|
}
|
|
485
567
|
return errors;
|
|
486
568
|
}
|
|
@@ -554,8 +636,8 @@ const productionPreset = {
|
|
|
554
636
|
exposeStatusRoute: true,
|
|
555
637
|
maxEventLoopDelay: 3e3,
|
|
556
638
|
maxEventLoopUtilization: .98,
|
|
557
|
-
maxHeapUsedBytes:
|
|
558
|
-
maxRssBytes:
|
|
639
|
+
maxHeapUsedBytes: 1073741824,
|
|
640
|
+
maxRssBytes: 1073741824
|
|
559
641
|
}
|
|
560
642
|
};
|
|
561
643
|
/**
|
|
@@ -625,7 +707,7 @@ const testingPreset = {
|
|
|
625
707
|
arcPlugins: { gracefulShutdown: false },
|
|
626
708
|
sensible: true,
|
|
627
709
|
multipart: { limits: {
|
|
628
|
-
fileSize:
|
|
710
|
+
fileSize: 1048576,
|
|
629
711
|
files: 5
|
|
630
712
|
} }
|
|
631
713
|
};
|
|
@@ -719,9 +801,20 @@ async function registerArcCore(fastify, config, trackPlugin) {
|
|
|
719
801
|
await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
|
|
720
802
|
trackPlugin("arc-core");
|
|
721
803
|
if (config.arcPlugins?.events !== false) {
|
|
722
|
-
const { default: eventPlugin } = await import("./eventPlugin-
|
|
804
|
+
const { default: eventPlugin } = await import("./eventPlugin-Dar8qRaj.mjs").then((n) => n.n);
|
|
723
805
|
const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
|
|
724
806
|
await fastify.register(eventPlugin, {
|
|
807
|
+
/**
|
|
808
|
+
* An EXPLICIT `runtime: 'memory'` is the host's single-instance
|
|
809
|
+
* declaration (its JSDoc says exactly that), so it flows down as the
|
|
810
|
+
* event plugin's `singleProcess` flag — one topology axis, declared
|
|
811
|
+
* once, no second knob to keep in sync. The comparison is against the
|
|
812
|
+
* literal, not the resolved default: `runtime` UNSET also means memory
|
|
813
|
+
* stores, but an unstated topology must keep the forgotten-default
|
|
814
|
+
* warn. `eventOpts` spreads after, so a direct
|
|
815
|
+
* `arcPlugins.events.singleProcess` still wins either way.
|
|
816
|
+
*/
|
|
817
|
+
...config.runtime === "memory" ? { singleProcess: true } : {},
|
|
725
818
|
...eventOpts,
|
|
726
819
|
transport: config.stores?.events
|
|
727
820
|
});
|
|
@@ -767,43 +860,47 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules, moduleH
|
|
|
767
860
|
trackPlugin("arc-graceful-shutdown");
|
|
768
861
|
}
|
|
769
862
|
if (config.arcPlugins?.caching) {
|
|
770
|
-
const { default: cachingPlugin } = await import("./caching-
|
|
863
|
+
const { default: cachingPlugin } = await import("./caching-BcGmvnHh.mjs").then((n) => n.r);
|
|
771
864
|
const opts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
|
|
772
865
|
await fastify.register(cachingPlugin, opts);
|
|
773
866
|
trackPlugin("arc-caching", opts);
|
|
774
867
|
}
|
|
775
868
|
if (config.arcPlugins?.queryCache) {
|
|
776
|
-
const { queryCachePlugin } = await import("./queryCachePlugin-
|
|
869
|
+
const { queryCachePlugin } = await import("./queryCachePlugin-DhsZ0ZIc.mjs").then((n) => n.n);
|
|
777
870
|
const opts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
|
|
778
|
-
const store = config.stores?.queryCache ?? new (await (import("./memory-
|
|
871
|
+
const store = config.stores?.queryCache ?? new (await (import("./memory-qYSB7rMQ.mjs").then((n) => n.n))).MemoryCacheStore();
|
|
779
872
|
await fastify.register(queryCachePlugin, {
|
|
780
873
|
store,
|
|
781
874
|
...opts
|
|
782
875
|
});
|
|
783
876
|
trackPlugin("arc-query-cache", opts);
|
|
784
877
|
}
|
|
785
|
-
if (config.arcPlugins?.sse)
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
878
|
+
if (config.arcPlugins?.sse) {
|
|
879
|
+
if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
|
|
880
|
+
else {
|
|
881
|
+
const { default: ssePlugin } = await import("./sse-Cf0jUHsh.mjs").then((n) => n.r);
|
|
882
|
+
const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
|
|
883
|
+
await fastify.register(ssePlugin, opts);
|
|
884
|
+
trackPlugin("arc-sse", opts);
|
|
885
|
+
}
|
|
791
886
|
}
|
|
792
|
-
if (config.arcPlugins?.realtime)
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
887
|
+
if (config.arcPlugins?.realtime) {
|
|
888
|
+
if (config.arcPlugins?.events === false) fastify.log.warn("Realtime plugin requires events plugin (arcPlugins.events). Realtime disabled.");
|
|
889
|
+
else {
|
|
890
|
+
const { default: realtimePlugin } = await import("./realtime-CTkv3O59.mjs").then((n) => n.r);
|
|
891
|
+
const opts = config.arcPlugins.realtime === true ? {} : config.arcPlugins.realtime;
|
|
892
|
+
await fastify.register(realtimePlugin, opts);
|
|
893
|
+
trackPlugin("arc-realtime", opts);
|
|
894
|
+
}
|
|
798
895
|
}
|
|
799
896
|
if (config.arcPlugins?.metrics) {
|
|
800
|
-
const { default: metricsPlugin } = await import("./metrics-
|
|
897
|
+
const { default: metricsPlugin } = await import("./metrics-D7uchLjN.mjs").then((n) => n.r);
|
|
801
898
|
const opts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
|
|
802
899
|
await fastify.register(metricsPlugin, opts);
|
|
803
900
|
trackPlugin("arc-metrics", opts);
|
|
804
901
|
}
|
|
805
902
|
if (config.arcPlugins?.versioning) {
|
|
806
|
-
const { default: versioningPlugin } = await import("./versioning-
|
|
903
|
+
const { default: versioningPlugin } = await import("./versioning-rZbyLVD-.mjs").then((n) => n.r);
|
|
807
904
|
await fastify.register(versioningPlugin, config.arcPlugins.versioning);
|
|
808
905
|
trackPlugin("arc-versioning", config.arcPlugins.versioning);
|
|
809
906
|
}
|
|
@@ -811,6 +908,12 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules, moduleH
|
|
|
811
908
|
//#endregion
|
|
812
909
|
//#region src/factory/registerAuth.ts
|
|
813
910
|
/**
|
|
911
|
+
* Authentication registration for createApp.
|
|
912
|
+
*
|
|
913
|
+
* Extracted from createApp step 9: scope decoration, auth strategy,
|
|
914
|
+
* elevation plugin, and error handler.
|
|
915
|
+
*/
|
|
916
|
+
/**
|
|
814
917
|
* Decorate request.scope with PUBLIC_SCOPE default.
|
|
815
918
|
* Every request starts as public; auth hooks upgrade it.
|
|
816
919
|
*/
|
|
@@ -849,10 +952,12 @@ async function registerAuth(fastify, config, trackPlugin) {
|
|
|
849
952
|
fastify.decorate("authenticate", async (request, reply) => {
|
|
850
953
|
await authenticate(request, reply);
|
|
851
954
|
});
|
|
852
|
-
if (!fastify.hasDecorator("optionalAuthenticate"))
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
955
|
+
if (!fastify.hasDecorator("optionalAuthenticate")) {
|
|
956
|
+
if (optionalAuthenticate) fastify.decorate("optionalAuthenticate", async (request, reply) => {
|
|
957
|
+
await optionalAuthenticate(request, reply);
|
|
958
|
+
});
|
|
959
|
+
else fastify.decorate("optionalAuthenticate", createOptionalAuthenticate(authenticate));
|
|
960
|
+
}
|
|
856
961
|
trackPlugin("auth-authenticator");
|
|
857
962
|
fastify.log.debug("Custom authenticator enabled");
|
|
858
963
|
break;
|
|
@@ -872,7 +977,7 @@ async function registerAuth(fastify, config, trackPlugin) {
|
|
|
872
977
|
*/
|
|
873
978
|
async function registerElevation(fastify, config, trackPlugin) {
|
|
874
979
|
if (!config.elevation) return;
|
|
875
|
-
const { elevationPlugin } = await import("./elevation-
|
|
980
|
+
const { elevationPlugin } = await import("./elevation-B0tlY8y1.mjs").then((n) => n.r);
|
|
876
981
|
await fastify.register(elevationPlugin, config.elevation);
|
|
877
982
|
trackPlugin("arc-elevation", config.elevation);
|
|
878
983
|
fastify.log.debug("Elevation plugin enabled");
|
|
@@ -887,8 +992,51 @@ async function registerElevation(fastify, config, trackPlugin) {
|
|
|
887
992
|
*/
|
|
888
993
|
async function registerErrorHandler(fastify, config, trackPlugin, moduleErrorMappers) {
|
|
889
994
|
if (config.errorHandler === false) return;
|
|
890
|
-
const { errorHandlerPlugin } = await import("./errorHandler-
|
|
891
|
-
|
|
995
|
+
const { errorHandlerPlugin } = await import("./errorHandler-C7xBqHsI.mjs").then((n) => n.r);
|
|
996
|
+
/**
|
|
997
|
+
* DISCLOSURE DEFAULTS MERGE — they used to be REPLACED, and that was two bugs.
|
|
998
|
+
*
|
|
999
|
+
* This was a ternary: an object from the host was used verbatim, otherwise
|
|
1000
|
+
* `{ includeStack: config.preset !== "production" }`. Both branches leaked.
|
|
1001
|
+
*
|
|
1002
|
+
* 1. A host that passed ANY key — even just `errorMappers` — lost the preset-derived
|
|
1003
|
+
* `includeStack` and fell through to `errorHandlerPlugin`'s own default, which is
|
|
1004
|
+
* `!(process.env.NODE_ENV === "production")`. That comparison knows only the long
|
|
1005
|
+
* spelling, so under `NODE_ENV=prod` it yields `true` and stack traces ship to clients in
|
|
1006
|
+
* production. Nobody writing `errorHandler: { errorMappers: [...] }` could reasonably
|
|
1007
|
+
* expect it to change stack-trace exposure.
|
|
1008
|
+
* 2. The derived branch never set `exposeInternalMessages` AT ALL, so that switch fell
|
|
1009
|
+
* through to the same raw read for EVERY arc app however it was configured. A truthy value
|
|
1010
|
+
* is what skips `wire.message = "Internal Server Error"` for a 500, putting the raw thrown
|
|
1011
|
+
* message — driver text, query fragments — on the wire.
|
|
1012
|
+
*
|
|
1013
|
+
* `preset` is the right source: it is the deployment's EXPLICIT declaration, and a specific
|
|
1014
|
+
* setting must never be overridden by an ambient default. Host keys still win over `derived`,
|
|
1015
|
+
* so an operator can widen deliberately; what they can no longer do is widen by accident.
|
|
1016
|
+
*
|
|
1017
|
+
* 3. And `preset` is OPTIONAL, so its ABSENCE must not read as "not production" either.
|
|
1018
|
+
* `config.preset !== "production"` is true for an unset preset, and plenty of hosts set
|
|
1019
|
+
* only `NODE_ENV` — which turns "the operator said nothing" into "the operator said
|
|
1020
|
+
* development", the widest possible reading of silence, on the two switches where being
|
|
1021
|
+
* wrong ships stack traces and raw driver text.
|
|
1022
|
+
*
|
|
1023
|
+
* So: an explicit `preset` wins; its absence falls back to the ENVIRONMENT rather than to a
|
|
1024
|
+
* guess; and that fallback goes through `isProductionEnv` from the shared classifier, never a raw
|
|
1025
|
+
* `process.env.NODE_ENV === "production"`. The raw form recognises only the long spelling, so a
|
|
1026
|
+
* preset-less host on `NODE_ENV=prod` read as non-production and re-opened both switches — the
|
|
1027
|
+
* same defect class this fallback exists to close, one layer in. Verified against a real 500 in
|
|
1028
|
+
* every state, including a Mongo connection string reaching the response body before the fix
|
|
1029
|
+
* (`tests/factory/error-disclosure-defaults.test.ts`).
|
|
1030
|
+
*/
|
|
1031
|
+
const productionEnv = config.preset !== void 0 ? config.preset === "production" : isProductionEnv(process.env.NODE_ENV);
|
|
1032
|
+
const derived = {
|
|
1033
|
+
includeStack: !productionEnv,
|
|
1034
|
+
exposeInternalMessages: !productionEnv
|
|
1035
|
+
};
|
|
1036
|
+
let errorOpts = typeof config.errorHandler === "object" ? {
|
|
1037
|
+
...derived,
|
|
1038
|
+
...config.errorHandler
|
|
1039
|
+
} : derived;
|
|
892
1040
|
if (moduleErrorMappers && moduleErrorMappers.length > 0) errorOpts = {
|
|
893
1041
|
...errorOpts,
|
|
894
1042
|
errorMappers: [...errorOpts.errorMappers ?? [], ...moduleErrorMappers]
|
|
@@ -997,7 +1145,8 @@ async function registerResources(fastify, config, preResolvedModules) {
|
|
|
997
1145
|
}
|
|
998
1146
|
const modules = preResolvedModules ?? orderModules(await Promise.all((config.modules ?? []).map((m) => resolveModule(m))));
|
|
999
1147
|
initModuleStates(fastify, modules.map((m) => m.name));
|
|
1000
|
-
const
|
|
1148
|
+
const disposers = createDisposerRegistry(fastify);
|
|
1149
|
+
const teardown = createModuleTeardown(fastify, modules, disposers);
|
|
1001
1150
|
let eventUnsubscribes = [];
|
|
1002
1151
|
try {
|
|
1003
1152
|
if (config.plugins) {
|
|
@@ -1008,7 +1157,11 @@ async function registerResources(fastify, config, preResolvedModules) {
|
|
|
1008
1157
|
if (!m.plugins) continue;
|
|
1009
1158
|
teardown.markInitialized(m);
|
|
1010
1159
|
try {
|
|
1011
|
-
await m.plugins(fastify);
|
|
1160
|
+
const returned = await m.plugins(fastify, disposers.contextFor(m.name));
|
|
1161
|
+
if (typeof returned === "function") {
|
|
1162
|
+
assertNotAReturnedPlugin(returned, m.name, fastify);
|
|
1163
|
+
disposers.contextFor(m.name).defer(returned);
|
|
1164
|
+
}
|
|
1012
1165
|
} catch (err) {
|
|
1013
1166
|
setModuleState(fastify, m.name, "failed");
|
|
1014
1167
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1025,7 +1178,7 @@ async function registerResources(fastify, config, preResolvedModules) {
|
|
|
1025
1178
|
teardown.markInitialized(m);
|
|
1026
1179
|
setModuleState(fastify, m.name, "bootstrapping");
|
|
1027
1180
|
try {
|
|
1028
|
-
exported = await m.bootstrap(fastify);
|
|
1181
|
+
exported = await m.bootstrap(fastify, disposers.contextFor(m.name));
|
|
1029
1182
|
} catch (err) {
|
|
1030
1183
|
setModuleState(fastify, m.name, "failed");
|
|
1031
1184
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1048,6 +1201,7 @@ async function registerResources(fastify, config, preResolvedModules) {
|
|
|
1048
1201
|
if (isResourcesFactory(config.resources)) {
|
|
1049
1202
|
try {
|
|
1050
1203
|
resolvedResources = await config.resources(fastify);
|
|
1204
|
+
if (!Array.isArray(resolvedResources)) throw new TypeError(`resources factory must return an array — received ${resolvedResources === null ? "null" : typeof resolvedResources}`);
|
|
1051
1205
|
} catch (err) {
|
|
1052
1206
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1053
1207
|
fastify.log.error(`Resources factory threw during boot: ${msg}`);
|
|
@@ -1062,7 +1216,7 @@ async function registerResources(fastify, config, preResolvedModules) {
|
|
|
1062
1216
|
let discoveryPath;
|
|
1063
1217
|
let discoveryYieldedZero = false;
|
|
1064
1218
|
if (resolvedResources === void 0 && config.resourceDir) {
|
|
1065
|
-
const { loadResources } = await import("./loadResources-
|
|
1219
|
+
const { loadResources } = await import("./loadResources-Bj-aSsXi.mjs").then((n) => n.n);
|
|
1066
1220
|
const { resolve, dirname } = await import("node:path");
|
|
1067
1221
|
const { fileURLToPath } = await import("node:url");
|
|
1068
1222
|
const rawDir = config.resourceDir;
|
|
@@ -1077,6 +1231,7 @@ async function registerResources(fastify, config, preResolvedModules) {
|
|
|
1077
1231
|
resolvedResources = discovered;
|
|
1078
1232
|
}
|
|
1079
1233
|
const moduleResourceNames = /* @__PURE__ */ new Map();
|
|
1234
|
+
const perModuleResources = /* @__PURE__ */ new Map();
|
|
1080
1235
|
const moduleResources = [];
|
|
1081
1236
|
for (const m of modules) {
|
|
1082
1237
|
if (!m.resources) continue;
|
|
@@ -1087,24 +1242,37 @@ async function registerResources(fastify, config, preResolvedModules) {
|
|
|
1087
1242
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1088
1243
|
throw new Error(`[arc] module "${m.name}" resources factory threw: ${msg}. Check the module's bootstrap ran and its adapters received booted engines.`, { cause: err });
|
|
1089
1244
|
}
|
|
1245
|
+
if (!Array.isArray(rs)) throw new TypeError(`[arc] module "${m.name}" resources factory must return an array — received ${rs === null ? "null" : typeof rs}.`);
|
|
1090
1246
|
const names = /* @__PURE__ */ new Set();
|
|
1091
|
-
for (const r of rs)
|
|
1247
|
+
for (const r of rs) {
|
|
1248
|
+
if (r.name == null) continue;
|
|
1249
|
+
if (names.has(r.name)) throw new Error(`[arc] module "${m.name}" provides two resources named "${r.name}". Resource names must be unique within a module — arc registers one route group per name, so the second would be dropped.`);
|
|
1250
|
+
names.add(r.name);
|
|
1251
|
+
}
|
|
1092
1252
|
moduleResourceNames.set(m.name, names);
|
|
1253
|
+
perModuleResources.set(m.name, rs);
|
|
1093
1254
|
moduleResources.push(...rs);
|
|
1094
1255
|
}
|
|
1256
|
+
const effectiveOwns = /* @__PURE__ */ new Map();
|
|
1095
1257
|
for (const m of modules) {
|
|
1096
|
-
if (!m.owns?.length) continue;
|
|
1097
1258
|
const supplied = moduleResourceNames.get(m.name);
|
|
1259
|
+
if (m.owns === "provided") {
|
|
1260
|
+
if (!supplied?.size) throw new Error(`[arc] module "${m.name}" declares \`owns: "provided"\` but supplies no named resources. Either give it resources to own, or drop the \`owns\` entry — an empty claim is almost always a resources factory that returned early.`);
|
|
1261
|
+
effectiveOwns.set(m.name, [...supplied]);
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
if (!m.owns?.length) continue;
|
|
1098
1265
|
const missing = m.owns.filter((name) => !supplied?.has(name));
|
|
1099
1266
|
if (missing.length > 0) {
|
|
1100
1267
|
const provided = supplied?.size ? [...supplied].join(", ") : "(none)";
|
|
1101
|
-
throw new Error(`[arc] module "${m.name}" declares owns: [${missing.map((n) => `"${n}"`).join(", ")}] but its own \`resources\` do not supply ${missing.length === 1 ? "that name" : "those names"}. \`owns\` DROPS the app-level resource of that name, so an unmet claim removes the route entirely and nothing serves it. Provide it in this module's \`resources\`,
|
|
1268
|
+
throw new Error(`[arc] module "${m.name}" declares owns: [${missing.map((n) => `"${n}"`).join(", ")}] but its own \`resources\` do not supply ${missing.length === 1 ? "that name" : "those names"}. \`owns\` DROPS the app-level resource of that name, so an unmet claim removes the route entirely and nothing serves it. Provide it in this module's \`resources\`, drop the \`owns\` entry, or use \`owns: "provided"\` to derive it.
|
|
1102
1269
|
Resources supplied by "${m.name}": ${provided}.`);
|
|
1103
1270
|
}
|
|
1271
|
+
effectiveOwns.set(m.name, m.owns);
|
|
1104
1272
|
}
|
|
1105
1273
|
if (modules.length && resolvedResources && resolvedResources.length > 0) {
|
|
1106
1274
|
const owned = /* @__PURE__ */ new Set();
|
|
1107
|
-
for (const m of modules) for (const name of m.
|
|
1275
|
+
for (const m of modules) for (const name of effectiveOwns.get(m.name) ?? []) owned.add(name);
|
|
1108
1276
|
if (owned.size > 0) {
|
|
1109
1277
|
const before = resolvedResources.length;
|
|
1110
1278
|
resolvedResources = resolvedResources.filter((r) => !(r.name != null && owned.has(r.name)));
|
|
@@ -1128,10 +1296,12 @@ Resources supplied by "${m.name}": ${provided}.`);
|
|
|
1128
1296
|
for (const resource of resolvedResources) if (resource.skipGlobalPrefix) root.push(resource);
|
|
1129
1297
|
else prefixed.push(resource);
|
|
1130
1298
|
for (const resource of root) await registerOne(fastify, resource, config.mountRoutes);
|
|
1131
|
-
if (prefixed.length)
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1299
|
+
if (prefixed.length) {
|
|
1300
|
+
if (config.resourcePrefix) await fastify.register(async (scoped) => {
|
|
1301
|
+
for (const resource of prefixed) await registerOne(scoped, resource, config.mountRoutes);
|
|
1302
|
+
}, { prefix: config.resourcePrefix });
|
|
1303
|
+
else for (const resource of prefixed) await registerOne(fastify, resource, config.mountRoutes);
|
|
1304
|
+
}
|
|
1135
1305
|
const names = resolvedResources.map((r) => r.name ?? "?").join(", ");
|
|
1136
1306
|
const prefix = config.resourcePrefix ? ` (prefix: ${config.resourcePrefix})` : "";
|
|
1137
1307
|
fastify.log.info(`${resolvedResources.length} resource(s) registered${prefix}: ${names}`);
|
|
@@ -1144,19 +1314,35 @@ Resources supplied by "${m.name}": ${provided}.`);
|
|
|
1144
1314
|
for (const m of modules) {
|
|
1145
1315
|
if (!m.afterResources) continue;
|
|
1146
1316
|
try {
|
|
1147
|
-
await m.afterResources(fastify);
|
|
1317
|
+
const returned = await m.afterResources(fastify, disposers.contextFor(m.name));
|
|
1318
|
+
if (typeof returned === "function") disposers.contextFor(m.name).defer(returned);
|
|
1148
1319
|
} catch (err) {
|
|
1149
1320
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1150
1321
|
throw new Error(`[arc] module "${m.name}" afterResources() threw: ${msg}`, { cause: err });
|
|
1151
1322
|
}
|
|
1152
1323
|
}
|
|
1153
|
-
const
|
|
1324
|
+
const scheduleCounts = /* @__PURE__ */ new Map();
|
|
1325
|
+
const collectedJobs = await collectModuleScheduledJobs(fastify, modules, scheduleCounts);
|
|
1326
|
+
/**
|
|
1327
|
+
* ROLE gate (CreateAppOptions.role). Arms a role does not run are SKIPPED
|
|
1328
|
+
* and logged by name, never fatal: a module legitimately declares arms
|
|
1329
|
+
* that a different deployment of the same codebase executes. 'relay' runs
|
|
1330
|
+
* only kind:'relay' arms (the outbox relays); 'api'/'worker' run none;
|
|
1331
|
+
* 'scheduler'/'all' run everything.
|
|
1332
|
+
*/
|
|
1333
|
+
const role = config.role ?? "all";
|
|
1334
|
+
const scheduledJobs = role === "scheduler" || role === "all" ? collectedJobs : role === "relay" ? collectedJobs.filter((j) => j.kind === "relay") : [];
|
|
1335
|
+
const skipped = collectedJobs.filter((j) => !scheduledJobs.includes(j));
|
|
1336
|
+
if (skipped.length > 0) fastify.log.info({
|
|
1337
|
+
role,
|
|
1338
|
+
skipped: skipped.map((j) => j.name)
|
|
1339
|
+
}, `[arc] role '${role}': ${skipped.length} schedule arm(s) not mounted here — a deployment with the owning role runs them`);
|
|
1154
1340
|
if (scheduledJobs.length > 0) {
|
|
1155
1341
|
if (config.arcPlugins?.schedules === false) throw new Error("[arc] modules declare scheduledJobs but arcPlugins.schedules is false. Enable the scheduler or remove the declarations.");
|
|
1156
1342
|
if (fastify.hasDecorator("getScheduleStats")) throw new Error("[arc] modules declare scheduledJobs but schedulesPlugin was already registered manually. Configure their runner through arcPlugins.schedules so Arc can compose one schedule table.");
|
|
1157
1343
|
const frozenJobs = Object.freeze(scheduledJobs.map((job) => Object.freeze({ ...job })));
|
|
1158
1344
|
const scheduleOptions = typeof config.arcPlugins?.schedules === "object" ? config.arcPlugins.schedules : {};
|
|
1159
|
-
const { default: schedulesPlugin } = await import("./schedules-
|
|
1345
|
+
const { default: schedulesPlugin } = await import("./schedules-CAcZgia0.mjs").then((n) => n.r);
|
|
1160
1346
|
await fastify.register(schedulesPlugin, {
|
|
1161
1347
|
...scheduleOptions,
|
|
1162
1348
|
schedules: frozenJobs
|
|
@@ -1170,7 +1356,21 @@ Resources supplied by "${m.name}": ${provided}.`);
|
|
|
1170
1356
|
});
|
|
1171
1357
|
}
|
|
1172
1358
|
}
|
|
1173
|
-
|
|
1359
|
+
const subscriptionCounts = /* @__PURE__ */ new Map();
|
|
1360
|
+
eventUnsubscribes = await subscribeModuleEventHandlers(fastify, modules, subscriptionCounts);
|
|
1361
|
+
if (fastify.arc) fastify.arc.moduleDescriptors = Object.freeze(modules.map((m) => describeResolvedModule({
|
|
1362
|
+
name: m.name,
|
|
1363
|
+
dependsOn: m.dependsOn,
|
|
1364
|
+
resources: perModuleResources.get(m.name) ?? [],
|
|
1365
|
+
owns: effectiveOwns.get(m.name) ?? [],
|
|
1366
|
+
lifecycle: {
|
|
1367
|
+
hasClose: typeof m.onClose === "function",
|
|
1368
|
+
subscriptions: subscriptionCounts.get(m.name) ?? 0,
|
|
1369
|
+
scheduledJobs: scheduleCounts.get(m.name) ?? 0,
|
|
1370
|
+
healthChecks: m.healthChecks?.length ?? 0,
|
|
1371
|
+
exports: typeof m.bootstrap === "function"
|
|
1372
|
+
}
|
|
1373
|
+
})));
|
|
1174
1374
|
if (config.afterResources) {
|
|
1175
1375
|
await config.afterResources(fastify);
|
|
1176
1376
|
fastify.log.debug("afterResources hook executed");
|
|
@@ -1198,6 +1398,22 @@ Resources supplied by "${m.name}": ${provided}.`);
|
|
|
1198
1398
|
if (closeErrors.length > 0) throw closeErrors[0];
|
|
1199
1399
|
});
|
|
1200
1400
|
}
|
|
1401
|
+
/**
|
|
1402
|
+
* `plugins()` is CALLED by arc, never handed to `fastify.register()` — so a
|
|
1403
|
+
* module author who returns a Fastify plugin expecting arc to register it
|
|
1404
|
+
* gets silence: nothing is registered, and the "disposer" shorthand then
|
|
1405
|
+
* invokes their plugin at shutdown with no arguments.
|
|
1406
|
+
*
|
|
1407
|
+
* The `fastify-plugin` case is unambiguous (its marker symbols are only ever
|
|
1408
|
+
* set by `fp()`), so it throws. A bare `(fastify, opts)` function is a strong
|
|
1409
|
+
* but not certain signal — arc's disposer contract calls it with ZERO
|
|
1410
|
+
* arguments, so a 2-parameter disposer is meaningless either way — and warns.
|
|
1411
|
+
*/
|
|
1412
|
+
function assertNotAReturnedPlugin(returned, moduleName, fastify) {
|
|
1413
|
+
const fn = returned;
|
|
1414
|
+
if (fn[Symbol.for("skip-override")] !== void 0 || fn[Symbol.for("plugin-meta")] !== void 0 || fn[Symbol.for("fastify.display-name")] !== void 0) throw new Error(`[arc] module "${moduleName}" returned a fastify-plugin from plugins(). That slot is a SETUP function arc calls directly — it is never passed to fastify.register(), so the returned value is read as a disposer and your plugin would be invoked at shutdown instead of registered at boot. Register it yourself: \`plugins: async (fastify) => { await fastify.register(myPlugin) }\`.`);
|
|
1415
|
+
if (returned.length >= 2) fastify.log?.warn?.(`[arc] module "${moduleName}" returned a ${returned.length}-argument function from plugins(). Arc calls disposers with NO arguments, so this looks like a Fastify plugin that was meant to be registered (\`await fastify.register(fn)\`) rather than a teardown callback. If it really is a disposer, declare it with no parameters.`);
|
|
1416
|
+
}
|
|
1201
1417
|
//#endregion
|
|
1202
1418
|
//#region src/factory/security/cors.ts
|
|
1203
1419
|
/**
|
|
@@ -1399,7 +1615,7 @@ async function registerUtilityPlugins(fastify, config) {
|
|
|
1399
1615
|
throwFileSizeLimit: true,
|
|
1400
1616
|
...hostMultipart,
|
|
1401
1617
|
limits: {
|
|
1402
|
-
fileSize:
|
|
1618
|
+
fileSize: 10485760,
|
|
1403
1619
|
files: 10,
|
|
1404
1620
|
fields: 100,
|
|
1405
1621
|
parts: 120,
|
|
@@ -1589,6 +1805,14 @@ Missing:\n${lines}\n\nFix: pass durable adapters under \`stores\`, or use runtim
|
|
|
1589
1805
|
* instance is closed before the error propagates — see the wrapper below.
|
|
1590
1806
|
*/
|
|
1591
1807
|
async function createApp(options) {
|
|
1808
|
+
/**
|
|
1809
|
+
* ROLE contradiction check — before anything constructs. A relay/scheduler
|
|
1810
|
+
* process declaring HTTP resources is a wiring error, not a preference:
|
|
1811
|
+
* those roles exist to NOT serve traffic, and mounting routes anyway would
|
|
1812
|
+
* hide the misdeployment behind a working endpoint.
|
|
1813
|
+
*/
|
|
1814
|
+
const declaredRole = options.role ?? "all";
|
|
1815
|
+
if ((declaredRole === "relay" || declaredRole === "scheduler") && (options.resources !== void 0 || options.resourceDir !== void 0)) throw new Error(`createApp: role '${declaredRole}' declares HTTP resources — a ${declaredRole} process must not serve routes. Remove \`resources\`/\`resourceDir\`, or change the role.`);
|
|
1592
1816
|
let partial;
|
|
1593
1817
|
try {
|
|
1594
1818
|
return await buildApp(options, (instance) => {
|
|
@@ -1683,7 +1907,7 @@ async function buildApp(options, onInstance) {
|
|
|
1683
1907
|
await registerErrorHandler(fastify, config, trackPlugin, moduleErrorMappers);
|
|
1684
1908
|
await registerResources(fastify, config, resolvedModules);
|
|
1685
1909
|
if (config.replyHelpers) {
|
|
1686
|
-
const { replyHelpersPlugin } = await import("./replyHelpers-
|
|
1910
|
+
const { replyHelpersPlugin } = await import("./replyHelpers-BSBVCwL3.mjs").then((n) => n.n);
|
|
1687
1911
|
await fastify.register(replyHelpersPlugin);
|
|
1688
1912
|
}
|
|
1689
1913
|
if (config.serializeBigInt) {
|
|
@@ -1700,6 +1924,7 @@ async function buildApp(options, onInstance) {
|
|
|
1700
1924
|
});
|
|
1701
1925
|
}
|
|
1702
1926
|
const authMode = config.auth === false ? "none" : config.auth ? config.auth.type : "none";
|
|
1927
|
+
auditRuntimeCapabilities(fastify, config.runtime ?? "memory");
|
|
1703
1928
|
fastify.log.info({
|
|
1704
1929
|
preset: config.preset ?? "custom",
|
|
1705
1930
|
runtime: config.runtime ?? "memory",
|
|
@@ -1734,4 +1959,4 @@ const ArcFactory = {
|
|
|
1734
1959
|
}
|
|
1735
1960
|
};
|
|
1736
1961
|
//#endregion
|
|
1737
|
-
export {
|
|
1962
|
+
export { collectModuleScheduledJobs as _, developmentPreset as a, resolveModule as b, productionPreset as c, getModuleState as d, hasModule as f, collectModuleHealthChecks as g, describeResolvedModule as h, createApp_exports as i, testingPreset as l, subscribeModuleEventHandlers as m, DEFAULT_LOGGER_REDACT_PATHS as n, edgePreset as o, orderModules as p, createApp as r, getPreset as s, ArcFactory as t, workerPreset as u, collectModuleWorkflows as v, resolveContribution as y };
|