@classytic/arc 2.31.0 → 2.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/dist/{BaseController-De7GqHck.mjs → BaseController-Dk34dxhI.mjs} +16 -13
  2. package/dist/{EventTransport-9mlTgqzE.mjs → EventTransport-BV10D0oG.mjs} +1 -1
  3. package/dist/{HookSystem-BcmPyq5I.mjs → HookSystem-vimw5D7Z.mjs} +2 -2
  4. package/dist/{ResourceRegistry-qq0mn2s9.mjs → ResourceRegistry-BVr7PgA0.mjs} +17 -15
  5. package/dist/{actionPermissions-BE5NwaQk.mjs → actionPermissions-Dti7ebyv.mjs} +1 -1
  6. package/dist/audit/index.mjs +2 -2
  7. package/dist/auth/audit.mjs +1 -1
  8. package/dist/auth/index.mjs +11 -10
  9. package/dist/auth/redis-session.mjs +1 -1
  10. package/dist/{authorizationDecision-ChvUQGG1.mjs → authorizationDecision-__T3EJDA.mjs} +2 -2
  11. package/dist/{buildHandler-CDrCJzmB.mjs → buildHandler-Dn7rzUJo.mjs} +6 -5
  12. package/dist/cache/index.mjs +4 -4
  13. package/dist/{caching-BFHA6Scf.mjs → caching-DFJqHyFs.mjs} +2 -2
  14. package/dist/cleanup/index.d.mts +36 -14
  15. package/dist/cleanup/index.mjs +45 -29
  16. package/dist/cli/commands/describe.mjs +4 -6
  17. package/dist/cli/commands/docs.mjs +3 -3
  18. package/dist/cli/commands/doctor.mjs +1 -1
  19. package/dist/cli/commands/generate.mjs +2 -2
  20. package/dist/cli/commands/init.mjs +2 -2
  21. package/dist/cli/commands/introspect.mjs +2 -2
  22. package/dist/cli/index.mjs +3 -3
  23. package/dist/{constants-D3Dv4LAV.mjs → constants-J99LGTJI.mjs} +1 -1
  24. package/dist/context/index.mjs +3 -3
  25. package/dist/core/index.d.mts +1 -1
  26. package/dist/core/index.mjs +7 -7
  27. package/dist/{core-BGpIxINN.mjs → core-BwwmNSp7.mjs} +3 -3
  28. package/dist/{createActionRouter-CDXEdasl.mjs → createActionRouter-DkJZWo0m.mjs} +14 -11
  29. package/dist/{createAggregationRouter-KrQDzx-k.mjs → createAggregationRouter-B1exOVD8.mjs} +10 -8
  30. package/dist/{createApp-Df89BEni.mjs → createApp-Bv0m-Q6_.mjs} +182 -61
  31. package/dist/{defineAggregation-DbPPS8RI.mjs → defineAggregation-gX4No1gt.mjs} +1 -1
  32. package/dist/{defineResource-Bv6EtN1x.mjs → defineResource-XFPPUu2c.mjs} +41 -32
  33. package/dist/discovery/index.mjs +1 -1
  34. package/dist/docs/index.mjs +3 -3
  35. package/dist/{elevation-D2l1mh4g.mjs → elevation-DNXXxgjl.mjs} +4 -4
  36. package/dist/encryption/index.mjs +5 -4
  37. package/dist/{errorHandler-C5NeD6bT.mjs → errorHandler-B2AZUJUj.mjs} +3 -3
  38. package/dist/{errors-Cj42AntO.mjs → errors-Bh1KK5ps.mjs} +1 -1
  39. package/dist/{eventPlugin-CkffEihm.mjs → eventPlugin-Dk_TyXeE.mjs} +8 -7
  40. package/dist/events/index.d.mts +108 -41
  41. package/dist/events/index.mjs +166 -142
  42. package/dist/events/transports/redis-stream-entry.mjs +14 -10
  43. package/dist/events/transports/redis.mjs +1 -1
  44. package/dist/factory/index.d.mts +5 -5
  45. package/dist/factory/index.mjs +131 -8
  46. package/dist/fastify-augmentation-BppG5bMT.d.mts +179 -0
  47. package/dist/{fields-CB5Cuzk9.mjs → fields-DEi65pGj.mjs} +6 -11
  48. package/dist/{generate-EcA6ui4i.mjs → generate-CRSG0jTv.mjs} +2 -2
  49. package/dist/{headers-3zvE0rM3.mjs → headers-BXjQYbeC.mjs} +2 -2
  50. package/dist/{health-DjZYjM7H.mjs → health-5XnTa2uo.mjs} +1 -1
  51. package/dist/hooks/index.mjs +2 -2
  52. package/dist/idempotency/index.mjs +8 -6
  53. package/dist/idempotency/redis.mjs +1 -1
  54. package/dist/{index-Bc2ox8_U.d.mts → index-DXlHwvuE.d.mts} +1 -1
  55. package/dist/{index-CFThn1K9.d.mts → index-ZWhLTp_s.d.mts} +70 -4
  56. package/dist/{index-CEJNZFtX.d.mts → index-zq6VrjaB.d.mts} +70 -1
  57. package/dist/index.d.mts +3 -3
  58. package/dist/index.mjs +11 -11
  59. package/dist/{init-BEiNZBsJ.mjs → init-BbfP8aky.mjs} +7 -5
  60. package/dist/integrations/event-gateway.mjs +2 -2
  61. package/dist/integrations/index.mjs +1 -1
  62. package/dist/integrations/jobs/index.mjs +3 -3
  63. package/dist/integrations/mcp/index.mjs +2 -2
  64. package/dist/integrations/mcp/testing.mjs +2 -2
  65. package/dist/integrations/streamline.mjs +3 -3
  66. package/dist/integrations/webhooks.mjs +1 -1
  67. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  68. package/dist/integrations/websocket-redis.mjs +1 -1
  69. package/dist/integrations/websocket.mjs +38 -35
  70. package/dist/{interface-DYzM0q-u.mjs → interface-BP4IJCBO.mjs} +1 -1
  71. package/dist/{jsonBody-BYbLDi5D.mjs → jsonBody-DDSR2HFl.mjs} +2 -2
  72. package/dist/{keys-C0_pa1tY.mjs → keys-B-HBTGXl.mjs} +1 -1
  73. package/dist/{loadResources-yawi9tAE.mjs → loadResources-DisEAmkm.mjs} +4 -3
  74. package/dist/{loadResourcesFromEntry-BAjYZonx.mjs → loadResourcesFromEntry-D1MJp13f.mjs} +2 -2
  75. package/dist/logger/index.mjs +1 -1
  76. package/dist/{mcpPlugin-B_es0hiF.mjs → mcpPlugin-CpfVpKHE.mjs} +32 -31
  77. package/dist/{memory-DR6wpa9F.mjs → memory-DsuWnj4T.mjs} +5 -5
  78. package/dist/{metrics-CUdw_wLB.mjs → metrics-DdQL8syd.mjs} +2 -2
  79. package/dist/middleware/index.mjs +3 -3
  80. package/dist/migrations/index.mjs +3 -3
  81. package/dist/{module-0pFJA6YW.mjs → module-Br81am6m.mjs} +1 -1
  82. package/dist/{multipartBody-wOgj_fnj.mjs → multipartBody-BnkBLN7v.mjs} +3 -3
  83. package/dist/{openapi-B2L4js1v.mjs → openapi-DIRzvxC0.mjs} +6 -6
  84. package/dist/outbox-admin/index.d.mts +85 -0
  85. package/dist/outbox-admin/index.mjs +115 -0
  86. package/dist/permissions/index.d.mts +2 -2
  87. package/dist/permissions/index.mjs +6 -6
  88. package/dist/{permissions-L--QDAir.mjs → permissions-Dl_m0NCe.mjs} +101 -8
  89. package/dist/{pipe-DRDNsI2T.mjs → pipe-ELHCP3zj.mjs} +3 -5
  90. package/dist/pipeline/index.mjs +2 -2
  91. package/dist/{pipeline-UPQFatit.mjs → pipeline-CPA-jx0M.mjs} +17 -15
  92. package/dist/plugins/index.d.mts +2 -2
  93. package/dist/plugins/index.mjs +16 -16
  94. package/dist/plugins/response-cache.mjs +2 -2
  95. package/dist/plugins/tracing-entry.mjs +2 -2
  96. package/dist/{pluralize-O0FZgESl.mjs → pluralize-C2ZkKbsn.mjs} +1 -1
  97. package/dist/{preloadResources-Dz6BjzM6.mjs → preloadResources-CDs87fTw.mjs} +1 -1
  98. package/dist/{preloadResources-CyoJMSiO.d.mts → preloadResources-T1hmlTEZ.d.mts} +2 -2
  99. package/dist/presets/filesUpload.mjs +6 -6
  100. package/dist/presets/index.mjs +2 -2
  101. package/dist/presets/multiTenant.mjs +3 -3
  102. package/dist/presets/search.mjs +2 -2
  103. package/dist/{presets-C4-g4_4P.mjs → presets-Dntd_pUO.mjs} +6 -5
  104. package/dist/{projection-BHw6xlOQ.mjs → projection-CjHhM5W1.mjs} +2 -2
  105. package/dist/{queryCachePlugin-BZr8v46E.mjs → queryCachePlugin-Dhps-Ldt.mjs} +6 -6
  106. package/dist/{rateLimit-CMPY5Qms.mjs → rateLimit-C7mmAGs3.mjs} +2 -2
  107. package/dist/{rateLimitKey-Dn5JNmkj.mjs → rateLimitKey-CccEFBfk.mjs} +1 -1
  108. package/dist/{realtime-C5NoxWFY.mjs → realtime-DVvVZZFz.mjs} +10 -10
  109. package/dist/{registerRoute-CBN84fc7.mjs → registerRoute-B_QrjYZS.mjs} +10 -8
  110. package/dist/registry/index.d.mts +1 -1
  111. package/dist/registry/index.mjs +3 -3
  112. package/dist/{registry-4213U4zb.mjs → registry-DqUmzA9W.mjs} +2 -2
  113. package/dist/{renewingLease-Dll8mGgc.mjs → renewingLease-BpP9wwj7.mjs} +1 -1
  114. package/dist/{replyHelpers-DIiZKGUt.mjs → replyHelpers-DS-P0b32.mjs} +2 -2
  115. package/dist/{repositoryFilter-f9eAfq_l.mjs → repositoryFilter-DotlxG3L.mjs} +1 -1
  116. package/dist/{requestContext-2TxXOJ7T.mjs → requestContext-B6vQF-sy.mjs} +1 -1
  117. package/dist/{rolldown-runtime-CUsjNX9R.mjs → rolldown-runtime-BhflRrBs.mjs} +1 -1
  118. package/dist/{schedules-DdtlICTf.mjs → schedules-C2Q3uv2d.mjs} +3 -3
  119. package/dist/{schemaConverter-D--wdJM-.mjs → schemaConverter-Crjvczi_.mjs} +1 -1
  120. package/dist/{schemaIR-Bm2enbnh.mjs → schemaIR-CnyKK_1j.mjs} +2 -2
  121. package/dist/schemas/index.mjs +1 -1
  122. package/dist/scim/index.mjs +2 -2
  123. package/dist/scope/index.mjs +5 -5
  124. package/dist/{scope-DHx5Fous.mjs → scope-afz_Ieek.mjs} +4 -4
  125. package/dist/{simpleEqualityMatcher-DjQJ7FKA.mjs → simpleEqualityMatcher-C4KiMWUa.mjs} +1 -1
  126. package/dist/{sse-CUTo9y0X.mjs → sse-B9P4Y-UI.mjs} +7 -6
  127. package/dist/{sseStream-B_5XcVVd.mjs → sseStream-B3zb_d1h.mjs} +2 -2
  128. package/dist/{store-helpers-D1MP_gRG.mjs → store-helpers-DmEMHlPQ.mjs} +1 -1
  129. package/dist/{streaming-PSZwi8JV.mjs → streaming-DPZdQmDz.mjs} +1 -1
  130. package/dist/{subscribe-helpers-C0Tb2aUp.mjs → subscribe-helpers-sp5xTDVA.mjs} +2 -2
  131. package/dist/sync/changelog.mjs +1 -1
  132. package/dist/testing/cleanupStoreContract.mjs +10 -6
  133. package/dist/testing/index.d.mts +31 -4
  134. package/dist/testing/index.mjs +97 -39
  135. package/dist/testing/outboxStoreContract.d.mts +11 -2
  136. package/dist/testing/outboxStoreContract.mjs +110 -21
  137. package/dist/testing/storageContract.mjs +11 -6
  138. package/dist/{transactionContext-dAfBVUrk.mjs → transactionContext-BvLKvOIL.mjs} +1 -1
  139. package/dist/{typeGuards-B0ryPo3I.mjs → typeGuards-B_5-hKjJ.mjs} +1 -1
  140. package/dist/types/index.mjs +1 -1
  141. package/dist/types/storage.mjs +1 -1
  142. package/dist/{types-CBgvkp8O.d.mts → types-CT5vu6M0.d.mts} +87 -11
  143. package/dist/{types-Cys0ko9Z.mjs → types-DXYX7dIR.mjs} +4 -4
  144. package/dist/usage/index.mjs +0 -0
  145. package/dist/{userHelpers-ChYJ6CdK.mjs → userHelpers-BNsOXzOd.mjs} +1 -1
  146. package/dist/utils/index.mjs +10 -10
  147. package/dist/{utils-vL8BrDE2.mjs → utils-BtOha_Sp.mjs} +3 -3
  148. package/dist/{validate-_KbifpHF.mjs → validate-PMCPR9El.mjs} +2 -2
  149. package/dist/{versioning-CHDTor3P.mjs → versioning-CKv2jz_U.mjs} +4 -4
  150. package/package.json +23 -13
  151. package/dist/fastify-augmentation-zh0_AYHa.d.mts +0 -85
@@ -1,11 +1,11 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-CUsjNX9R.mjs";
3
- import "./constants-D3Dv4LAV.mjs";
4
- import { f as createError } from "./errors-Cj42AntO.mjs";
5
- import { a as buildRouteHooks, i as selectPluginMw, l as buildAuthMiddlewareForPermissions, n as buildRouteConfig, r as resolveRouterPluginMw, s as routeHookOptions, t as buildRateLimitConfig, u as buildArcDecorator } from "./rateLimit-CMPY5Qms.mjs";
6
- import { n as buildActionPermissionMw, t as tryRegisterRoute } from "./registerRoute-CBN84fc7.mjs";
7
- import { l as sendControllerResponse, r as resolvePipelineSteps, t as buildActionPipelineHandler } from "./pipeline-UPQFatit.mjs";
8
- import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-Bm2enbnh.mjs";
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-BhflRrBs.mjs";
3
+ import "./constants-J99LGTJI.mjs";
4
+ import { f as createError } from "./errors-Bh1KK5ps.mjs";
5
+ import { a as buildRouteHooks, i as selectPluginMw, l as buildAuthMiddlewareForPermissions, n as buildRouteConfig, r as resolveRouterPluginMw, s as routeHookOptions, t as buildRateLimitConfig, u as buildArcDecorator } from "./rateLimit-C7mmAGs3.mjs";
6
+ import { n as buildActionPermissionMw, t as tryRegisterRoute } from "./registerRoute-B_QrjYZS.mjs";
7
+ import { l as sendControllerResponse, r as resolvePipelineSteps, t as buildActionPipelineHandler } from "./pipeline-CPA-jx0M.mjs";
8
+ import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-CnyKK_1j.mjs";
9
9
  //#region src/core/createActionRouter.ts
10
10
  var createActionRouter_exports = /* @__PURE__ */ __exportAll({
11
11
  buildActionBodySchema: () => buildActionBodySchema,
@@ -58,7 +58,8 @@ function createActionRouter(fastify, config) {
58
58
  const { urlPath, actionSubset, hasId } = mountOpts;
59
59
  if (actionSubset.length === 0) return;
60
60
  const subsetSet = new Set(actionSubset);
61
- const authMw = buildAuthMiddlewareForPermissions(fastify, actionSubset.map((name) => actionPermissions[name] ?? globalAuth));
61
+ const perActionPermissions = actionSubset.map((name) => actionPermissions[name] ?? globalAuth);
62
+ const authMw = buildAuthMiddlewareForPermissions(fastify, perActionPermissions);
62
63
  const subsetPermissions = {};
63
64
  for (const name of actionSubset) {
64
65
  const perm = actionPermissions[name];
@@ -90,7 +91,8 @@ function createActionRouter(fastify, config) {
90
91
  if (!submitted || !subsetSet.has(submitted)) {
91
92
  const wrongMount = submitted && allActionNames.includes(submitted) ? submitted : void 0;
92
93
  const otherMount = hasId ? "/action" : "/:id/action";
93
- throw createError(400, wrongMount ? `Action '${wrongMount}' on '${resourceName}' is mounted at 'POST ${otherMount}', not 'POST ${urlPath}'` : submitted ? `Unknown action '${submitted}' on '${resourceName}'. Valid at 'POST ${urlPath}': ${actionSubset.join(", ")}` : `Missing 'action' field on '${resourceName}'. Valid at 'POST ${urlPath}': ${actionSubset.join(", ")}`, {
94
+ const message = wrongMount ? `Action '${wrongMount}' on '${resourceName}' is mounted at 'POST ${otherMount}', not 'POST ${urlPath}'` : submitted ? `Unknown action '${submitted}' on '${resourceName}'. Valid at 'POST ${urlPath}': ${actionSubset.join(", ")}` : `Missing 'action' field on '${resourceName}'. Valid at 'POST ${urlPath}': ${actionSubset.join(", ")}`;
95
+ throw createError(400, message, {
94
96
  code: "arc.invalid_action",
95
97
  validActions: actionSubset,
96
98
  mount: urlPath,
@@ -141,7 +143,8 @@ function createActionRouter(fastify, config) {
141
143
  const handler = wrappedHandlers.get(action);
142
144
  if (!handler) throw createError(400, `Invalid action '${action}'. Valid actions: ${actionSubset.join(", ")}`, { validActions: actionSubset });
143
145
  try {
144
- sendControllerResponse(reply, await handler(id, data, req), req);
146
+ const response = await handler(id, data, req);
147
+ sendControllerResponse(reply, response, req);
145
148
  return reply;
146
149
  } catch (error) {
147
150
  if (onError) {
@@ -1,10 +1,10 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { n as evaluateAndApplyPermission } from "./authorizationDecision-ChvUQGG1.mjs";
3
- import { f as createError } from "./errors-Cj42AntO.mjs";
4
- import { u as getRequestScope } from "./types-Cys0ko9Z.mjs";
5
- import { a as buildRouteHooks, c as buildAuthMiddleware, i as selectPluginMw, r as resolveRouterPluginMw, s as routeHookOptions, t as buildRateLimitConfig, u as buildArcDecorator } from "./rateLimit-CMPY5Qms.mjs";
6
- import { n as validateAggregations } from "./validate-_KbifpHF.mjs";
7
- import { t as buildAggregationHandler } from "./buildHandler-CDrCJzmB.mjs";
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
+ import { n as evaluateAndApplyPermission } from "./authorizationDecision-__T3EJDA.mjs";
3
+ import { f as createError } from "./errors-Bh1KK5ps.mjs";
4
+ import { u as getRequestScope } from "./types-DXYX7dIR.mjs";
5
+ import { a as buildRouteHooks, c as buildAuthMiddleware, i as selectPluginMw, r as resolveRouterPluginMw, s as routeHookOptions, t as buildRateLimitConfig, u as buildArcDecorator } from "./rateLimit-C7mmAGs3.mjs";
6
+ import { n as validateAggregations } from "./validate-PMCPR9El.mjs";
7
+ import { t as buildAggregationHandler } from "./buildHandler-Dn7rzUJo.mjs";
8
8
  //#region src/core/aggregation/createAggregationRouter.ts
9
9
  /**
10
10
  * Register one Fastify route per aggregation. No-op when the map is
@@ -45,11 +45,13 @@ function registerOne(fastify, normalized, ctx) {
45
45
  const ctx = buildPermissionContextLite(req, normalized.name);
46
46
  await evaluateAndApplyPermission(permissionFn, ctx, req, reply, { defaultDenialMessage: (user) => user ? "You do not have permission to access this aggregation." : "Authentication required to access this aggregation." });
47
47
  };
48
+ const pluginMwAll = resolveRouterPluginMw(fastify, false);
49
+ const pluginMw = selectPluginMw("GET", pluginMwAll);
48
50
  const hooks = buildRouteHooks({
49
51
  arcDecorator,
50
52
  authMw,
51
53
  permissionMw,
52
- pluginMw: selectPluginMw("GET", resolveRouterPluginMw(fastify, false)),
54
+ pluginMw,
53
55
  routeGuards,
54
56
  customMws: middlewares
55
57
  });
@@ -1,11 +1,11 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-CUsjNX9R.mjs";
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-BhflRrBs.mjs";
3
3
  import { arcLog, configureArcLogger, createPinoWriter } from "./logger/index.mjs";
4
- import { n as PUBLIC_SCOPE } from "./types-Cys0ko9Z.mjs";
5
- import { t as createTenantKeyGenerator } from "./rateLimitKey-Dn5JNmkj.mjs";
6
- import { i as createRequestIdGenerator, r as mergeHealthChecks } from "./health-DjZYjM7H.mjs";
7
- import { t as parseJsonBody } from "./jsonBody-BYbLDi5D.mjs";
8
- import { r as wrapWithBoundary } from "./subscribe-helpers-C0Tb2aUp.mjs";
4
+ import { n as PUBLIC_SCOPE } from "./types-DXYX7dIR.mjs";
5
+ import { t as createTenantKeyGenerator } from "./rateLimitKey-CccEFBfk.mjs";
6
+ import { i as createRequestIdGenerator, r as mergeHealthChecks } from "./health-5XnTa2uo.mjs";
7
+ import { t as parseJsonBody } from "./jsonBody-DDSR2HFl.mjs";
8
+ import { r as wrapWithBoundary } from "./subscribe-helpers-sp5xTDVA.mjs";
9
9
  import Fastify from "fastify";
10
10
  import qs from "qs";
11
11
  //#region src/factory/security/pluginLoader.ts
@@ -183,12 +183,15 @@ async function resolveContribution(contribution, fastify) {
183
183
  * them. The original error is preserved via `{ cause }`.
184
184
  */
185
185
  async function resolveModuleArm(module, arm, contribution, fastify) {
186
+ let resolved;
186
187
  try {
187
- return await resolveContribution(contribution, fastify);
188
+ resolved = await resolveContribution(contribution, fastify);
188
189
  } catch (err) {
189
190
  const msg = err instanceof Error ? err.message : String(err);
190
191
  throw new Error(`[arc] module "${module.name}" ${arm} factory threw: ${msg}`, { cause: err });
191
192
  }
193
+ if (!Array.isArray(resolved)) throw new TypeError(`[arc] module "${module.name}" ${arm} factory must return an array — received ${resolved === null ? "null" : typeof resolved}.`);
194
+ return resolved;
192
195
  }
193
196
  //#endregion
194
197
  //#region src/factory/module/contributions.ts
@@ -227,11 +230,12 @@ async function collectModuleWorkflows(fastify, modules) {
227
230
  * modules. The caller passes the returned definitions to the canonical
228
231
  * schedules plugin; factories must therefore be resolved only once.
229
232
  */
230
- async function collectModuleScheduledJobs(fastify, modules) {
233
+ async function collectModuleScheduledJobs(fastify, modules, countsByModule) {
231
234
  const owner = /* @__PURE__ */ new Map();
232
235
  const out = [];
233
236
  for (const m of modules) {
234
237
  const jobs = await resolveModuleArm(m, "scheduledJobs", m.scheduledJobs, fastify);
238
+ countsByModule?.set(m.name, jobs.length);
235
239
  for (const job of jobs) {
236
240
  const prior = owner.get(job.name);
237
241
  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 +246,69 @@ async function collectModuleScheduledJobs(fastify, modules) {
242
246
  return out;
243
247
  }
244
248
  //#endregion
249
+ //#region src/factory/module/descriptor.ts
250
+ /**
251
+ * Build a descriptor. Called by `registerResources` once per module, after
252
+ * resources are resolved and `owns` is effective.
253
+ *
254
+ * Frozen at every level: a descriptor is a snapshot of a completed decision,
255
+ * and a consumer mutating it would silently disagree with the running app.
256
+ */
257
+ function describeResolvedModule(input) {
258
+ return Object.freeze({
259
+ name: input.name,
260
+ dependsOn: Object.freeze([...input.dependsOn ?? []]),
261
+ resources: Object.freeze(input.resources.map((r) => Object.freeze({
262
+ ...r.name !== void 0 ? { name: r.name } : {},
263
+ ...r.prefix !== void 0 ? { prefix: r.prefix } : {},
264
+ skipGlobalPrefix: r.skipGlobalPrefix === true
265
+ }))),
266
+ owns: Object.freeze([...input.owns]),
267
+ lifecycle: Object.freeze({ ...input.lifecycle })
268
+ });
269
+ }
270
+ //#endregion
271
+ //#region src/factory/module/disposers.ts
272
+ function createDisposerRegistry(fastify) {
273
+ const stacks = /* @__PURE__ */ new Map();
274
+ const drained = /* @__PURE__ */ new Set();
275
+ const contexts = /* @__PURE__ */ new Map();
276
+ return {
277
+ contextFor(moduleName) {
278
+ const existing = contexts.get(moduleName);
279
+ if (existing) return existing;
280
+ const ctx = { defer(disposer) {
281
+ if (typeof disposer !== "function") throw new TypeError(`[arc] module "${moduleName}" called defer() with a ${typeof disposer} — expected a function.`);
282
+ if (drained.has(moduleName)) {
283
+ (async () => {
284
+ try {
285
+ await disposer();
286
+ } catch (err) {
287
+ fastify.log?.error({
288
+ err,
289
+ module: moduleName
290
+ }, `[arc] module "${moduleName}" deferred a disposer after teardown had already run; it was invoked immediately and threw.`);
291
+ }
292
+ })();
293
+ return;
294
+ }
295
+ const stack = stacks.get(moduleName);
296
+ if (stack) stack.push(disposer);
297
+ else stacks.set(moduleName, [disposer]);
298
+ } };
299
+ contexts.set(moduleName, ctx);
300
+ return ctx;
301
+ },
302
+ take(moduleName) {
303
+ drained.add(moduleName);
304
+ const stack = stacks.get(moduleName);
305
+ if (!stack?.length) return [];
306
+ stacks.delete(moduleName);
307
+ return stack.reverse();
308
+ }
309
+ };
310
+ }
311
+ //#endregion
245
312
  //#region src/factory/module/lifecycle.ts
246
313
  /**
247
314
  * Subscribe every module's `eventHandlers` in dependency order (pass the
@@ -253,12 +320,13 @@ async function collectModuleScheduledJobs(fastify, modules) {
253
320
  * Resolves factory contributions (after bootstraps) so a handler can close over
254
321
  * booted engines rather than a global getter.
255
322
  */
256
- async function subscribeModuleEventHandlers(fastify, modules) {
323
+ async function subscribeModuleEventHandlers(fastify, modules, countsByModule) {
257
324
  const unsubscribes = [];
258
325
  const owner = /* @__PURE__ */ new Map();
259
326
  try {
260
327
  for (const m of modules) {
261
328
  const handlers = await resolveModuleArm(m, "eventHandlers", m.eventHandlers, fastify);
329
+ countsByModule?.set(m.name, handlers.length);
262
330
  if (handlers.length === 0) continue;
263
331
  const bus = fastify.events;
264
332
  if (!bus) throw new Error(`[arc] module "${m.name}" declares eventHandlers but the event subsystem is unavailable (fastify.events). Enable arcPlugins.events.`);
@@ -460,7 +528,7 @@ function getModuleState(fastify, name) {
460
528
  }
461
529
  //#endregion
462
530
  //#region src/factory/module/teardown.ts
463
- function createModuleTeardown(fastify, modules) {
531
+ function createModuleTeardown(fastify, modules, disposers) {
464
532
  const initializedNames = /* @__PURE__ */ new Set();
465
533
  const consumed = /* @__PURE__ */ new Set();
466
534
  async function closeInitialized() {
@@ -469,18 +537,30 @@ function createModuleTeardown(fastify, modules) {
469
537
  const m = modules[index];
470
538
  if (!m || !initializedNames.has(m.name) || consumed.has(m.name)) continue;
471
539
  consumed.add(m.name);
472
- if (!m.onClose) {
540
+ const stack = disposers?.take(m.name) ?? [];
541
+ if (!m.onClose && stack.length === 0) {
473
542
  setModuleState(fastify, m.name, "closed");
474
543
  continue;
475
544
  }
476
545
  setModuleState(fastify, m.name, "closing");
477
- try {
546
+ let teardownFailed = false;
547
+ if (m.onClose) try {
478
548
  await m.onClose(fastify);
479
- setModuleState(fastify, m.name, "closed");
480
549
  } catch (err) {
481
- setModuleState(fastify, m.name, "failed");
550
+ teardownFailed = true;
482
551
  errors.push(new Error(`[arc] module "${m.name}" onClose() threw during teardown: ${err instanceof Error ? err.message : String(err)}`, { cause: err }));
483
552
  }
553
+ for (let d = 0; d < stack.length; d++) {
554
+ const dispose = stack[d];
555
+ if (!dispose) continue;
556
+ try {
557
+ await dispose();
558
+ } catch (err) {
559
+ teardownFailed = true;
560
+ 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 }));
561
+ }
562
+ }
563
+ setModuleState(fastify, m.name, teardownFailed ? "failed" : "closed");
484
564
  }
485
565
  return errors;
486
566
  }
@@ -554,8 +634,8 @@ const productionPreset = {
554
634
  exposeStatusRoute: true,
555
635
  maxEventLoopDelay: 3e3,
556
636
  maxEventLoopUtilization: .98,
557
- maxHeapUsedBytes: 1024 * 1024 * 1024,
558
- maxRssBytes: 1024 * 1024 * 1024
637
+ maxHeapUsedBytes: 1073741824,
638
+ maxRssBytes: 1073741824
559
639
  }
560
640
  };
561
641
  /**
@@ -625,7 +705,7 @@ const testingPreset = {
625
705
  arcPlugins: { gracefulShutdown: false },
626
706
  sensible: true,
627
707
  multipart: { limits: {
628
- fileSize: 1024 * 1024,
708
+ fileSize: 1048576,
629
709
  files: 5
630
710
  } }
631
711
  };
@@ -719,7 +799,7 @@ async function registerArcCore(fastify, config, trackPlugin) {
719
799
  await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
720
800
  trackPlugin("arc-core");
721
801
  if (config.arcPlugins?.events !== false) {
722
- const { default: eventPlugin } = await import("./eventPlugin-CkffEihm.mjs").then((n) => n.n);
802
+ const { default: eventPlugin } = await import("./eventPlugin-Dk_TyXeE.mjs").then((n) => n.n);
723
803
  const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
724
804
  await fastify.register(eventPlugin, {
725
805
  ...eventOpts,
@@ -767,43 +847,47 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules, moduleH
767
847
  trackPlugin("arc-graceful-shutdown");
768
848
  }
769
849
  if (config.arcPlugins?.caching) {
770
- const { default: cachingPlugin } = await import("./caching-BFHA6Scf.mjs").then((n) => n.r);
850
+ const { default: cachingPlugin } = await import("./caching-DFJqHyFs.mjs").then((n) => n.r);
771
851
  const opts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
772
852
  await fastify.register(cachingPlugin, opts);
773
853
  trackPlugin("arc-caching", opts);
774
854
  }
775
855
  if (config.arcPlugins?.queryCache) {
776
- const { queryCachePlugin } = await import("./queryCachePlugin-BZr8v46E.mjs").then((n) => n.n);
856
+ const { queryCachePlugin } = await import("./queryCachePlugin-Dhps-Ldt.mjs").then((n) => n.n);
777
857
  const opts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
778
- const store = config.stores?.queryCache ?? new (await (import("./memory-DR6wpa9F.mjs").then((n) => n.n))).MemoryCacheStore();
858
+ const store = config.stores?.queryCache ?? new (await (import("./memory-DsuWnj4T.mjs").then((n) => n.n))).MemoryCacheStore();
779
859
  await fastify.register(queryCachePlugin, {
780
860
  store,
781
861
  ...opts
782
862
  });
783
863
  trackPlugin("arc-query-cache", opts);
784
864
  }
785
- if (config.arcPlugins?.sse) if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
786
- else {
787
- const { default: ssePlugin } = await import("./sse-CUTo9y0X.mjs").then((n) => n.r);
788
- const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
789
- await fastify.register(ssePlugin, opts);
790
- trackPlugin("arc-sse", opts);
865
+ if (config.arcPlugins?.sse) {
866
+ if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
867
+ else {
868
+ const { default: ssePlugin } = await import("./sse-B9P4Y-UI.mjs").then((n) => n.r);
869
+ const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
870
+ await fastify.register(ssePlugin, opts);
871
+ trackPlugin("arc-sse", opts);
872
+ }
791
873
  }
792
- if (config.arcPlugins?.realtime) if (config.arcPlugins?.events === false) fastify.log.warn("Realtime plugin requires events plugin (arcPlugins.events). Realtime disabled.");
793
- else {
794
- const { default: realtimePlugin } = await import("./realtime-C5NoxWFY.mjs").then((n) => n.r);
795
- const opts = config.arcPlugins.realtime === true ? {} : config.arcPlugins.realtime;
796
- await fastify.register(realtimePlugin, opts);
797
- trackPlugin("arc-realtime", opts);
874
+ if (config.arcPlugins?.realtime) {
875
+ if (config.arcPlugins?.events === false) fastify.log.warn("Realtime plugin requires events plugin (arcPlugins.events). Realtime disabled.");
876
+ else {
877
+ const { default: realtimePlugin } = await import("./realtime-DVvVZZFz.mjs").then((n) => n.r);
878
+ const opts = config.arcPlugins.realtime === true ? {} : config.arcPlugins.realtime;
879
+ await fastify.register(realtimePlugin, opts);
880
+ trackPlugin("arc-realtime", opts);
881
+ }
798
882
  }
799
883
  if (config.arcPlugins?.metrics) {
800
- const { default: metricsPlugin } = await import("./metrics-CUdw_wLB.mjs").then((n) => n.r);
884
+ const { default: metricsPlugin } = await import("./metrics-DdQL8syd.mjs").then((n) => n.r);
801
885
  const opts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
802
886
  await fastify.register(metricsPlugin, opts);
803
887
  trackPlugin("arc-metrics", opts);
804
888
  }
805
889
  if (config.arcPlugins?.versioning) {
806
- const { default: versioningPlugin } = await import("./versioning-CHDTor3P.mjs").then((n) => n.r);
890
+ const { default: versioningPlugin } = await import("./versioning-CKv2jz_U.mjs").then((n) => n.r);
807
891
  await fastify.register(versioningPlugin, config.arcPlugins.versioning);
808
892
  trackPlugin("arc-versioning", config.arcPlugins.versioning);
809
893
  }
@@ -849,10 +933,12 @@ async function registerAuth(fastify, config, trackPlugin) {
849
933
  fastify.decorate("authenticate", async (request, reply) => {
850
934
  await authenticate(request, reply);
851
935
  });
852
- if (!fastify.hasDecorator("optionalAuthenticate")) if (optionalAuthenticate) fastify.decorate("optionalAuthenticate", async (request, reply) => {
853
- await optionalAuthenticate(request, reply);
854
- });
855
- else fastify.decorate("optionalAuthenticate", createOptionalAuthenticate(authenticate));
936
+ if (!fastify.hasDecorator("optionalAuthenticate")) {
937
+ if (optionalAuthenticate) fastify.decorate("optionalAuthenticate", async (request, reply) => {
938
+ await optionalAuthenticate(request, reply);
939
+ });
940
+ else fastify.decorate("optionalAuthenticate", createOptionalAuthenticate(authenticate));
941
+ }
856
942
  trackPlugin("auth-authenticator");
857
943
  fastify.log.debug("Custom authenticator enabled");
858
944
  break;
@@ -872,7 +958,7 @@ async function registerAuth(fastify, config, trackPlugin) {
872
958
  */
873
959
  async function registerElevation(fastify, config, trackPlugin) {
874
960
  if (!config.elevation) return;
875
- const { elevationPlugin } = await import("./elevation-D2l1mh4g.mjs").then((n) => n.r);
961
+ const { elevationPlugin } = await import("./elevation-DNXXxgjl.mjs").then((n) => n.r);
876
962
  await fastify.register(elevationPlugin, config.elevation);
877
963
  trackPlugin("arc-elevation", config.elevation);
878
964
  fastify.log.debug("Elevation plugin enabled");
@@ -887,7 +973,7 @@ async function registerElevation(fastify, config, trackPlugin) {
887
973
  */
888
974
  async function registerErrorHandler(fastify, config, trackPlugin, moduleErrorMappers) {
889
975
  if (config.errorHandler === false) return;
890
- const { errorHandlerPlugin } = await import("./errorHandler-C5NeD6bT.mjs").then((n) => n.r);
976
+ const { errorHandlerPlugin } = await import("./errorHandler-B2AZUJUj.mjs").then((n) => n.r);
891
977
  let errorOpts = typeof config.errorHandler === "object" ? config.errorHandler : { includeStack: config.preset !== "production" };
892
978
  if (moduleErrorMappers && moduleErrorMappers.length > 0) errorOpts = {
893
979
  ...errorOpts,
@@ -997,7 +1083,8 @@ async function registerResources(fastify, config, preResolvedModules) {
997
1083
  }
998
1084
  const modules = preResolvedModules ?? orderModules(await Promise.all((config.modules ?? []).map((m) => resolveModule(m))));
999
1085
  initModuleStates(fastify, modules.map((m) => m.name));
1000
- const teardown = createModuleTeardown(fastify, modules);
1086
+ const disposers = createDisposerRegistry(fastify);
1087
+ const teardown = createModuleTeardown(fastify, modules, disposers);
1001
1088
  let eventUnsubscribes = [];
1002
1089
  try {
1003
1090
  if (config.plugins) {
@@ -1008,7 +1095,8 @@ async function registerResources(fastify, config, preResolvedModules) {
1008
1095
  if (!m.plugins) continue;
1009
1096
  teardown.markInitialized(m);
1010
1097
  try {
1011
- await m.plugins(fastify);
1098
+ const returned = await m.plugins(fastify, disposers.contextFor(m.name));
1099
+ if (typeof returned === "function") disposers.contextFor(m.name).defer(returned);
1012
1100
  } catch (err) {
1013
1101
  setModuleState(fastify, m.name, "failed");
1014
1102
  const msg = err instanceof Error ? err.message : String(err);
@@ -1025,7 +1113,7 @@ async function registerResources(fastify, config, preResolvedModules) {
1025
1113
  teardown.markInitialized(m);
1026
1114
  setModuleState(fastify, m.name, "bootstrapping");
1027
1115
  try {
1028
- exported = await m.bootstrap(fastify);
1116
+ exported = await m.bootstrap(fastify, disposers.contextFor(m.name));
1029
1117
  } catch (err) {
1030
1118
  setModuleState(fastify, m.name, "failed");
1031
1119
  const msg = err instanceof Error ? err.message : String(err);
@@ -1048,6 +1136,7 @@ async function registerResources(fastify, config, preResolvedModules) {
1048
1136
  if (isResourcesFactory(config.resources)) {
1049
1137
  try {
1050
1138
  resolvedResources = await config.resources(fastify);
1139
+ if (!Array.isArray(resolvedResources)) throw new TypeError(`resources factory must return an array — received ${resolvedResources === null ? "null" : typeof resolvedResources}`);
1051
1140
  } catch (err) {
1052
1141
  const msg = err instanceof Error ? err.message : String(err);
1053
1142
  fastify.log.error(`Resources factory threw during boot: ${msg}`);
@@ -1062,7 +1151,7 @@ async function registerResources(fastify, config, preResolvedModules) {
1062
1151
  let discoveryPath;
1063
1152
  let discoveryYieldedZero = false;
1064
1153
  if (resolvedResources === void 0 && config.resourceDir) {
1065
- const { loadResources } = await import("./loadResources-yawi9tAE.mjs").then((n) => n.n);
1154
+ const { loadResources } = await import("./loadResources-DisEAmkm.mjs").then((n) => n.n);
1066
1155
  const { resolve, dirname } = await import("node:path");
1067
1156
  const { fileURLToPath } = await import("node:url");
1068
1157
  const rawDir = config.resourceDir;
@@ -1077,6 +1166,7 @@ async function registerResources(fastify, config, preResolvedModules) {
1077
1166
  resolvedResources = discovered;
1078
1167
  }
1079
1168
  const moduleResourceNames = /* @__PURE__ */ new Map();
1169
+ const perModuleResources = /* @__PURE__ */ new Map();
1080
1170
  const moduleResources = [];
1081
1171
  for (const m of modules) {
1082
1172
  if (!m.resources) continue;
@@ -1087,24 +1177,37 @@ async function registerResources(fastify, config, preResolvedModules) {
1087
1177
  const msg = err instanceof Error ? err.message : String(err);
1088
1178
  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
1179
  }
1180
+ if (!Array.isArray(rs)) throw new TypeError(`[arc] module "${m.name}" resources factory must return an array — received ${rs === null ? "null" : typeof rs}.`);
1090
1181
  const names = /* @__PURE__ */ new Set();
1091
- for (const r of rs) if (r.name != null) names.add(r.name);
1182
+ for (const r of rs) {
1183
+ if (r.name == null) continue;
1184
+ 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.`);
1185
+ names.add(r.name);
1186
+ }
1092
1187
  moduleResourceNames.set(m.name, names);
1188
+ perModuleResources.set(m.name, rs);
1093
1189
  moduleResources.push(...rs);
1094
1190
  }
1191
+ const effectiveOwns = /* @__PURE__ */ new Map();
1095
1192
  for (const m of modules) {
1096
- if (!m.owns?.length) continue;
1097
1193
  const supplied = moduleResourceNames.get(m.name);
1194
+ if (m.owns === "provided") {
1195
+ 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.`);
1196
+ effectiveOwns.set(m.name, [...supplied]);
1197
+ continue;
1198
+ }
1199
+ if (!m.owns?.length) continue;
1098
1200
  const missing = m.owns.filter((name) => !supplied?.has(name));
1099
1201
  if (missing.length > 0) {
1100
1202
  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\`, or drop the \`owns\` entry.
1203
+ 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
1204
  Resources supplied by "${m.name}": ${provided}.`);
1103
1205
  }
1206
+ effectiveOwns.set(m.name, m.owns);
1104
1207
  }
1105
1208
  if (modules.length && resolvedResources && resolvedResources.length > 0) {
1106
1209
  const owned = /* @__PURE__ */ new Set();
1107
- for (const m of modules) for (const name of m.owns ?? []) owned.add(name);
1210
+ for (const m of modules) for (const name of effectiveOwns.get(m.name) ?? []) owned.add(name);
1108
1211
  if (owned.size > 0) {
1109
1212
  const before = resolvedResources.length;
1110
1213
  resolvedResources = resolvedResources.filter((r) => !(r.name != null && owned.has(r.name)));
@@ -1128,10 +1231,12 @@ Resources supplied by "${m.name}": ${provided}.`);
1128
1231
  for (const resource of resolvedResources) if (resource.skipGlobalPrefix) root.push(resource);
1129
1232
  else prefixed.push(resource);
1130
1233
  for (const resource of root) await registerOne(fastify, resource, config.mountRoutes);
1131
- if (prefixed.length) if (config.resourcePrefix) await fastify.register(async (scoped) => {
1132
- for (const resource of prefixed) await registerOne(scoped, resource, config.mountRoutes);
1133
- }, { prefix: config.resourcePrefix });
1134
- else for (const resource of prefixed) await registerOne(fastify, resource, config.mountRoutes);
1234
+ if (prefixed.length) {
1235
+ if (config.resourcePrefix) await fastify.register(async (scoped) => {
1236
+ for (const resource of prefixed) await registerOne(scoped, resource, config.mountRoutes);
1237
+ }, { prefix: config.resourcePrefix });
1238
+ else for (const resource of prefixed) await registerOne(fastify, resource, config.mountRoutes);
1239
+ }
1135
1240
  const names = resolvedResources.map((r) => r.name ?? "?").join(", ");
1136
1241
  const prefix = config.resourcePrefix ? ` (prefix: ${config.resourcePrefix})` : "";
1137
1242
  fastify.log.info(`${resolvedResources.length} resource(s) registered${prefix}: ${names}`);
@@ -1144,19 +1249,21 @@ Resources supplied by "${m.name}": ${provided}.`);
1144
1249
  for (const m of modules) {
1145
1250
  if (!m.afterResources) continue;
1146
1251
  try {
1147
- await m.afterResources(fastify);
1252
+ const returned = await m.afterResources(fastify, disposers.contextFor(m.name));
1253
+ if (typeof returned === "function") disposers.contextFor(m.name).defer(returned);
1148
1254
  } catch (err) {
1149
1255
  const msg = err instanceof Error ? err.message : String(err);
1150
1256
  throw new Error(`[arc] module "${m.name}" afterResources() threw: ${msg}`, { cause: err });
1151
1257
  }
1152
1258
  }
1153
- const scheduledJobs = await collectModuleScheduledJobs(fastify, modules);
1259
+ const scheduleCounts = /* @__PURE__ */ new Map();
1260
+ const scheduledJobs = await collectModuleScheduledJobs(fastify, modules, scheduleCounts);
1154
1261
  if (scheduledJobs.length > 0) {
1155
1262
  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
1263
  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
1264
  const frozenJobs = Object.freeze(scheduledJobs.map((job) => Object.freeze({ ...job })));
1158
1265
  const scheduleOptions = typeof config.arcPlugins?.schedules === "object" ? config.arcPlugins.schedules : {};
1159
- const { default: schedulesPlugin } = await import("./schedules-DdtlICTf.mjs").then((n) => n.r);
1266
+ const { default: schedulesPlugin } = await import("./schedules-C2Q3uv2d.mjs").then((n) => n.r);
1160
1267
  await fastify.register(schedulesPlugin, {
1161
1268
  ...scheduleOptions,
1162
1269
  schedules: frozenJobs
@@ -1170,7 +1277,21 @@ Resources supplied by "${m.name}": ${provided}.`);
1170
1277
  });
1171
1278
  }
1172
1279
  }
1173
- eventUnsubscribes = await subscribeModuleEventHandlers(fastify, modules);
1280
+ const subscriptionCounts = /* @__PURE__ */ new Map();
1281
+ eventUnsubscribes = await subscribeModuleEventHandlers(fastify, modules, subscriptionCounts);
1282
+ if (fastify.arc) fastify.arc.moduleDescriptors = Object.freeze(modules.map((m) => describeResolvedModule({
1283
+ name: m.name,
1284
+ dependsOn: m.dependsOn,
1285
+ resources: perModuleResources.get(m.name) ?? [],
1286
+ owns: effectiveOwns.get(m.name) ?? [],
1287
+ lifecycle: {
1288
+ hasClose: typeof m.onClose === "function",
1289
+ subscriptions: subscriptionCounts.get(m.name) ?? 0,
1290
+ scheduledJobs: scheduleCounts.get(m.name) ?? 0,
1291
+ healthChecks: m.healthChecks?.length ?? 0,
1292
+ exports: typeof m.bootstrap === "function"
1293
+ }
1294
+ })));
1174
1295
  if (config.afterResources) {
1175
1296
  await config.afterResources(fastify);
1176
1297
  fastify.log.debug("afterResources hook executed");
@@ -1399,7 +1520,7 @@ async function registerUtilityPlugins(fastify, config) {
1399
1520
  throwFileSizeLimit: true,
1400
1521
  ...hostMultipart,
1401
1522
  limits: {
1402
- fileSize: 10 * 1024 * 1024,
1523
+ fileSize: 10485760,
1403
1524
  files: 10,
1404
1525
  fields: 100,
1405
1526
  parts: 120,
@@ -1683,7 +1804,7 @@ async function buildApp(options, onInstance) {
1683
1804
  await registerErrorHandler(fastify, config, trackPlugin, moduleErrorMappers);
1684
1805
  await registerResources(fastify, config, resolvedModules);
1685
1806
  if (config.replyHelpers) {
1686
- const { replyHelpersPlugin } = await import("./replyHelpers-DIiZKGUt.mjs").then((n) => n.n);
1807
+ const { replyHelpersPlugin } = await import("./replyHelpers-DS-P0b32.mjs").then((n) => n.n);
1687
1808
  await fastify.register(replyHelpersPlugin);
1688
1809
  }
1689
1810
  if (config.serializeBigInt) {
@@ -1734,4 +1855,4 @@ const ArcFactory = {
1734
1855
  }
1735
1856
  };
1736
1857
  //#endregion
1737
- export { resolveContribution as _, edgePreset as a, testingPreset as c, hasModule as d, orderModules as f, collectModuleWorkflows as g, collectModuleScheduledJobs as h, developmentPreset as i, workerPreset as l, collectModuleHealthChecks as m, createApp as n, getPreset as o, subscribeModuleEventHandlers as p, createApp_exports as r, productionPreset as s, ArcFactory as t, getModuleState as u, resolveModule as v };
1858
+ export { collectModuleWorkflows as _, edgePreset as a, testingPreset as c, hasModule as d, orderModules as f, collectModuleScheduledJobs as g, collectModuleHealthChecks as h, developmentPreset as i, workerPreset as l, describeResolvedModule as m, createApp as n, getPreset as o, subscribeModuleEventHandlers as p, createApp_exports as r, productionPreset as s, ArcFactory as t, getModuleState as u, resolveContribution as v, resolveModule as y };
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
2
  //#region src/core/aggregation/defineAggregation.ts
3
3
  /**
4
4
  * Declare a single resource aggregation. Exported configs flow into