@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,22 +1,22 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-D3Dv4LAV.mjs";
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
+ import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-J99LGTJI.mjs";
3
3
  import { arcLog } from "./logger/index.mjs";
4
- import { r as ForbiddenError } from "./errors-Cj42AntO.mjs";
5
- import { n as getUserRoles } from "./userHelpers-ChYJ6CdK.mjs";
6
- import { b as isElevated, n as PUBLIC_SCOPE, x as isMember } from "./types-Cys0ko9Z.mjs";
7
- import { d as DEFAULT_FIELD_WRITE_DENIAL_POLICY, t as BaseController } from "./BaseController-De7GqHck.mjs";
8
- import { a as buildRouteHooks, c as buildAuthMiddleware, i as selectPluginMw, n as buildRouteConfig, o as resolveRoutePreHandlers, r as resolveRouterPluginMw, s as routeHookOptions, t as buildRateLimitConfig, u as buildArcDecorator } from "./rateLimit-CMPY5Qms.mjs";
9
- import { i as resolveEffectiveRoles, n as applyFieldWritePermissions } from "./fields-CB5Cuzk9.mjs";
10
- import { r as buildCrudPermissionMw, t as tryRegisterRoute } from "./registerRoute-CBN84fc7.mjs";
11
- import { c as getDefaultCrudSchemas } from "./utils-vL8BrDE2.mjs";
12
- import { n as convertRouteSchema, r as ensureSchemaConverter, t as convertOpenApiSchemas } from "./schemaConverter-D--wdJM-.mjs";
13
- import { i as pipeUIMessageStreamToReply, r as isReadableStream } from "./streaming-PSZwi8JV.mjs";
14
- import { t as hasEvents } from "./typeGuards-B0ryPo3I.mjs";
15
- import { a as createFastifyHandler, i as createCrudHandlers, n as buildPipelineHandler, o as createRequestContext, r as resolvePipelineSteps } from "./pipeline-UPQFatit.mjs";
16
- import { I as requireAuth } from "./permissions-L--QDAir.mjs";
17
- import { r as getAvailablePresets, t as applyPresets } from "./presets-C4-g4_4P.mjs";
18
- import { t as pluralize } from "./pluralize-O0FZgESl.mjs";
19
- import { t as resolveActionPermission } from "./actionPermissions-BE5NwaQk.mjs";
4
+ import { r as ForbiddenError } from "./errors-Bh1KK5ps.mjs";
5
+ import { n as getUserRoles } from "./userHelpers-BNsOXzOd.mjs";
6
+ import { b as isElevated, n as PUBLIC_SCOPE, x as isMember } from "./types-DXYX7dIR.mjs";
7
+ import { d as DEFAULT_FIELD_WRITE_DENIAL_POLICY, t as BaseController } from "./BaseController-Dk34dxhI.mjs";
8
+ import { a as buildRouteHooks, c as buildAuthMiddleware, i as selectPluginMw, n as buildRouteConfig, o as resolveRoutePreHandlers, r as resolveRouterPluginMw, s as routeHookOptions, t as buildRateLimitConfig, u as buildArcDecorator } from "./rateLimit-C7mmAGs3.mjs";
9
+ import { i as resolveEffectiveRoles, n as applyFieldWritePermissions } from "./fields-DEi65pGj.mjs";
10
+ import { r as buildCrudPermissionMw, t as tryRegisterRoute } from "./registerRoute-B_QrjYZS.mjs";
11
+ import { c as getDefaultCrudSchemas } from "./utils-BtOha_Sp.mjs";
12
+ import { n as convertRouteSchema, r as ensureSchemaConverter, t as convertOpenApiSchemas } from "./schemaConverter-Crjvczi_.mjs";
13
+ import { i as pipeUIMessageStreamToReply, r as isReadableStream } from "./streaming-DPZdQmDz.mjs";
14
+ import { t as hasEvents } from "./typeGuards-B_5-hKjJ.mjs";
15
+ import { a as createFastifyHandler, i as createCrudHandlers, n as buildPipelineHandler, o as createRequestContext, r as resolvePipelineSteps } from "./pipeline-CPA-jx0M.mjs";
16
+ import { B as requireAuth } from "./permissions-Dl_m0NCe.mjs";
17
+ import { r as getAvailablePresets, t as applyPresets } from "./presets-Dntd_pUO.mjs";
18
+ import { t as pluralize } from "./pluralize-C2ZkKbsn.mjs";
19
+ import { t as resolveActionPermission } from "./actionPermissions-Dti7ebyv.mjs";
20
20
  //#region src/core/middlewares/fieldWrite.ts
21
21
  /**
22
22
  * Build a preHandler that enforces field-write permissions on the request body.
@@ -42,7 +42,10 @@ function buildFieldWritePreHandler(fieldPermissions, policy = DEFAULT_FIELD_WRIT
42
42
  if (!body || typeof body !== "object" || Array.isArray(body)) return;
43
43
  const scope = request.scope ?? PUBLIC_SCOPE;
44
44
  if (isElevated(scope)) return;
45
- const { body: filtered, deniedFields } = applyFieldWritePermissions(body, fieldPermissions, resolveEffectiveRoles(getUserRoles(request.user), isMember(scope) ? scope.orgRoles : []));
45
+ const globalRoles = getUserRoles(request.user);
46
+ const orgRoles = isMember(scope) ? scope.orgRoles : [];
47
+ const effectiveRoles = resolveEffectiveRoles(globalRoles, orgRoles);
48
+ const { body: filtered, deniedFields } = applyFieldWritePermissions(body, fieldPermissions, effectiveRoles);
46
49
  if (deniedFields.length > 0 && policy === "reject") throw new ForbiddenError(`Not permitted to write field${deniedFields.length === 1 ? "" : "s"}: ${deniedFields.join(", ")}`);
47
50
  request.body = filtered;
48
51
  };
@@ -114,8 +117,9 @@ function createCustomRoutes(fastify, routes, controller, options) {
114
117
  const fieldWriteMw = buildFieldWritePreHandler(fieldPermissions, onFieldWriteDenied);
115
118
  if (fieldWriteMw) customPreHandlers.push(fieldWriteMw);
116
119
  }
120
+ const preAuthHandlers = route.preAuth ?? [];
117
121
  const hooks = buildRouteHooks({
118
- preAuth: route.preAuth ?? [],
122
+ preAuth: preAuthHandlers,
119
123
  arcDecorator,
120
124
  authMw: buildAuthMiddleware(fastify, route.permissions),
121
125
  permissionMw: buildCrudPermissionMw(route.permissions, resourceName, opName),
@@ -287,7 +291,8 @@ function buildCrudHandlers(ctrl, pipeline, resourceName) {
287
291
  for (const op of CRUD_OPERATIONS) {
288
292
  const steps = resolvePipelineSteps(pipeline, op);
289
293
  if (steps.length === 0) continue;
290
- wrapped[op] = buildPipelineHandler(ctrl[op].bind(ctrl), steps, op, resourceName);
294
+ const method = ctrl[op].bind(ctrl);
295
+ wrapped[op] = buildPipelineHandler(method, steps, op, resourceName);
291
296
  }
292
297
  return wrapped;
293
298
  }
@@ -1226,11 +1231,13 @@ function buildResourcePlugin(resource) {
1226
1231
  handler: hook.handler,
1227
1232
  priority: hook.priority
1228
1233
  });
1229
- if (isFirstMount && resource.cache?.invalidateOn) if (fastify.registerCacheInvalidationRule) for (const [pattern, tags] of Object.entries(resource.cache.invalidateOn)) fastify.registerCacheInvalidationRule({
1230
- pattern,
1231
- tags
1232
- });
1233
- else fastify.log?.warn?.(`[Arc] Resource '${resource.name}' declares \`cache.invalidateOn\` but \`queryCachePlugin\` is not registered. Cache-invalidation rules are silently ignored. Either register the plugin (\`await fastify.register(queryCachePlugin, ...)\`) or remove the \`cache.invalidateOn\` config to clean up the resource definition.`);
1234
+ if (isFirstMount && resource.cache?.invalidateOn) {
1235
+ if (fastify.registerCacheInvalidationRule) for (const [pattern, tags] of Object.entries(resource.cache.invalidateOn)) fastify.registerCacheInvalidationRule({
1236
+ pattern,
1237
+ tags
1238
+ });
1239
+ else fastify.log?.warn?.(`[Arc] Resource '${resource.name}' declares \`cache.invalidateOn\` but \`queryCachePlugin\` is not registered. Cache-invalidation rules are silently ignored. Either register the plugin (\`await fastify.register(queryCachePlugin, ...)\`) or remove the \`cache.invalidateOn\` config to clean up the resource definition.`);
1240
+ }
1234
1241
  if (isFirstMount && resource.audit) {
1235
1242
  if (!(fastify.hasDecorator("audit") && typeof fastify.audit?.create === "function" && fastify.audit?._noop !== true)) fastify.log?.warn?.(`[Arc] Resource '${resource.name}' declares \`audit: ${JSON.stringify(resource.audit)}\` but \`auditPlugin\` is not registered (or is registered with a noop logger). Audit events for this resource will NOT be recorded. Either register \`auditPlugin\` (\`await fastify.register(auditPlugin, { store: ... })\`) or remove the \`audit\` flag from the resource definition.`);
1236
1243
  }
@@ -1271,7 +1278,7 @@ function buildResourcePlugin(resource) {
1271
1278
  extensions: resource.extensions
1272
1279
  });
1273
1280
  if (resource.actions && Object.keys(resource.actions).length > 0) {
1274
- const { createActionRouter } = await import("./createActionRouter-CDXEdasl.mjs").then((n) => n.n);
1281
+ const { createActionRouter } = await import("./createActionRouter-DkJZWo0m.mjs").then((n) => n.n);
1275
1282
  createActionRouter(typedInstance, {
1276
1283
  ...normalizeActionsToRouterConfig(resource.actions, resource.actionPermissions, resource.tag, resource.permissions, resource.name, typedInstance.log),
1277
1284
  resourceName: resource.name,
@@ -1286,7 +1293,7 @@ function buildResourcePlugin(resource) {
1286
1293
  });
1287
1294
  }
1288
1295
  if (resource.aggregations && Object.keys(resource.aggregations).length > 0) {
1289
- const [{ createAggregationRouter }, { adapterSupportsAggregate, ArcAggregationConfigError }] = await Promise.all([import("./createAggregationRouter-KrQDzx-k.mjs"), import("./validate-_KbifpHF.mjs").then((n) => n.r)]);
1296
+ const [{ createAggregationRouter }, { adapterSupportsAggregate, ArcAggregationConfigError }] = await Promise.all([import("./createAggregationRouter-B1exOVD8.mjs"), import("./validate-PMCPR9El.mjs").then((n) => n.r)]);
1290
1297
  const repoForAgg = resource.controller?.repository ?? resource.repository;
1291
1298
  if (!adapterSupportsAggregate(repoForAgg)) {
1292
1299
  const undispatchable = Object.entries(resource.aggregations).filter(([, cfg]) => !cfg.materialized).map(([name]) => name);
@@ -1486,10 +1493,12 @@ var ResourceDefinition = class {
1486
1493
  _validateControllerMethods() {
1487
1494
  const errors = [];
1488
1495
  const enabledCrudRoutes = CRUD_OPERATIONS.filter((route) => !this.disabledRoutes.includes(route));
1489
- if (!this.disableDefaultRoutes && enabledCrudRoutes.length > 0) if (!this.controller) errors.push("Controller is required when CRUD routes are enabled");
1490
- else {
1491
- const ctrl = this.controller;
1492
- for (const method of enabledCrudRoutes) if (typeof ctrl[method] !== "function") errors.push(`CRUD method '${method}' not found on controller`);
1496
+ if (!this.disableDefaultRoutes && enabledCrudRoutes.length > 0) {
1497
+ if (!this.controller) errors.push("Controller is required when CRUD routes are enabled");
1498
+ else {
1499
+ const ctrl = this.controller;
1500
+ for (const method of enabledCrudRoutes) if (typeof ctrl[method] !== "function") errors.push(`CRUD method '${method}' not found on controller`);
1501
+ }
1493
1502
  }
1494
1503
  for (const route of this.routes) {
1495
1504
  if (typeof route.handler !== "string") continue;
@@ -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
  import { readdir } from "node:fs/promises";
3
3
  import { extname, join, resolve } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { n as getUserRoles } from "../userHelpers-ChYJ6CdK.mjs";
3
- import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-B2L4js1v.mjs";
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
+ import { n as getUserRoles } from "../userHelpers-BNsOXzOd.mjs";
3
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-DIRzvxC0.mjs";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/docs/scalar.ts
6
6
  const scalarPlugin = async (fastify, opts = {}) => {
@@ -1,8 +1,8 @@
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 } from "./logger/index.mjs";
4
- import { n as requireSingleHeaderValue } from "./headers-3zvE0rM3.mjs";
5
- import { n as getUserRoles } from "./userHelpers-ChYJ6CdK.mjs";
4
+ import { n as requireSingleHeaderValue } from "./headers-BXjQYbeC.mjs";
5
+ import { n as getUserRoles } from "./userHelpers-BNsOXzOd.mjs";
6
6
  import fp from "fastify-plugin";
7
7
  //#region src/scope/elevation.ts
8
8
  var elevation_exports = /* @__PURE__ */ __exportAll({
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { t as ArcError } from "../errors-Cj42AntO.mjs";
3
- import { t as parseJsonBody } from "../jsonBody-BYbLDi5D.mjs";
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
+ import { t as ArcError } from "../errors-Bh1KK5ps.mjs";
3
+ import { t as parseJsonBody } from "../jsonBody-DDSR2HFl.mjs";
4
4
  import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
5
5
  import fp from "fastify-plugin";
6
6
  //#region src/encryption/fieldCipher.ts
@@ -71,7 +71,8 @@ function parseFieldEnvelope(token) {
71
71
  }
72
72
  /** Decrypt a parsed `arc.v1` envelope with the resolved key. Throws on tamper. */
73
73
  function decryptField(envelope, key) {
74
- const decipher = createDecipheriv("aes-256-gcm", toKeyBuffer(key), envelope.iv);
74
+ const secret = toKeyBuffer(key);
75
+ const decipher = createDecipheriv("aes-256-gcm", secret, envelope.iv);
75
76
  decipher.setAuthTag(envelope.tag);
76
77
  return Buffer.concat([decipher.update(envelope.ciphertext), decipher.final()]).toString("utf8");
77
78
  }
@@ -1,6 +1,6 @@
1
- /*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-CUsjNX9R.mjs";
3
- import { m as statusToArcCode, p as isArcError } from "./errors-Cj42AntO.mjs";
1
+ /*! @classytic/arc v2.32.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-BhflRrBs.mjs";
3
+ import { m as statusToArcCode, p as isArcError } from "./errors-Bh1KK5ps.mjs";
4
4
  import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
5
5
  import fp from "fastify-plugin";
6
6
  //#region src/plugins/errorHandler.ts
@@ -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/utils/errors.ts
3
3
  /**
4
4
  * Base Arc Error. Implements the canonical `HttpError` contract — `status`
@@ -1,9 +1,9 @@
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 } from "./logger/index.mjs";
4
- import { d as createDomainError } from "./errors-Cj42AntO.mjs";
5
- import { n as requestContext } from "./requestContext-2TxXOJ7T.mjs";
6
- import { n as createEvent, t as MemoryEventTransport } from "./EventTransport-9mlTgqzE.mjs";
4
+ import { d as createDomainError } from "./errors-Bh1KK5ps.mjs";
5
+ import { n as requestContext } from "./requestContext-B6vQF-sy.mjs";
6
+ import { n as createEvent, t as MemoryEventTransport } from "./EventTransport-BV10D0oG.mjs";
7
7
  import fp from "fastify-plugin";
8
8
  //#region src/events/retry.ts
9
9
  /**
@@ -139,10 +139,11 @@ const eventPlugin = async (fastify, opts = {}) => {
139
139
  if (type.startsWith("$") && type !== "$deadLetter") throw new Error(`[Arc Events] Event type '${type}' uses reserved '$' prefix`);
140
140
  if (type.length > 256) throw new Error("[Arc Events] Event type exceeds 256 characters");
141
141
  const store = requestContext.get();
142
- const event = createEvent(type, payload, {
142
+ const enrichedMeta = {
143
143
  ...store?.requestId && !meta?.correlationId ? { correlationId: store.requestId } : {},
144
144
  ...meta
145
- });
145
+ };
146
+ const event = createEvent(type, payload, enrichedMeta);
146
147
  if (warnOnDuplicate && event.meta.correlationId) {
147
148
  const now = Date.now();
148
149
  evictExpiredPublishes(now);
@@ -1,9 +1,12 @@
1
1
 
2
2
  import { a as EventTransport, i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions, t as DeadLetteredEvent } from "../EventTransport-2uVV34WU.mjs";
3
+ import { t as ArcModule } from "../types-CT5vu6M0.mjs";
3
4
  import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-GF5nnBIq.mjs";
4
5
  import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
5
6
  import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-qN6A-RFv.mjs";
6
- import { OutboxAcknowledgeOptions, OutboxClaimOptions, OutboxErrorInfo, OutboxFailOptions, OutboxFailurePolicy, OutboxStore, OutboxWriteOptions } from "@classytic/primitives/outbox";
7
+ import { FastifyInstance } from "fastify";
8
+ import { MemoryOutboxStore } from "@classytic/primitives/memory-outbox";
9
+ import { OutboxFailurePolicy, OutboxStore, OutboxWriteOptions } from "@classytic/primitives/outbox";
7
10
  import { RepositoryLike } from "@classytic/repo-core/adapter";
8
11
  //#region src/events/eventTypes.d.ts
9
12
  /**
@@ -56,44 +59,6 @@ declare const CACHE_EVENTS: Readonly<{
56
59
  /** Type for cache event names */
57
60
  type CacheEvent = (typeof CACHE_EVENTS)[keyof typeof CACHE_EVENTS];
58
61
  //#endregion
59
- //#region src/events/memory-outbox.d.ts
60
- interface MemoryEntry {
61
- event: DomainEvent;
62
- status: "pending" | "delivered" | "dead_letter";
63
- attempts: number;
64
- visibleAt: number;
65
- leaseOwner: string | null;
66
- leaseExpiresAt: number;
67
- deliveredAt: number | null;
68
- firstFailedAt: number | null;
69
- lastFailedAt: number | null;
70
- lastError: OutboxErrorInfo | null;
71
- dedupeKey?: string;
72
- }
73
- declare class MemoryOutboxStore implements OutboxStore {
74
- private readonly entries;
75
- private readonly seenDedupeKeys;
76
- /**
77
- * Drop every entry (including delivered / failed / dead-lettered rows) and
78
- * the dedupe-key memory.
79
- *
80
- * For tests only — it is the in-memory analogue of truncating the table, and
81
- * exists so a suite can isolate cases against ONE store instance (the
82
- * `reset` hook of `runOutboxStoreContract`). Not part of `OutboxStore`; a
83
- * production store must never expose this.
84
- */
85
- clear(): void;
86
- save(event: DomainEvent, options?: OutboxWriteOptions): Promise<void>;
87
- getPending(limit: number): Promise<DomainEvent[]>;
88
- claimPending(options?: OutboxClaimOptions): Promise<DomainEvent[]>;
89
- acknowledge(eventId: string, options?: OutboxAcknowledgeOptions): Promise<void>;
90
- fail(eventId: string, error: OutboxErrorInfo, options?: OutboxFailOptions): Promise<void>;
91
- getDeadLettered(limit: number): Promise<DeadLetteredEvent[]>;
92
- purge(olderThanMs: number): Promise<number>;
93
- /** Test helper: inspect entry by id */
94
- _getEntry(eventId: string): Readonly<MemoryEntry> | undefined;
95
- }
96
- //#endregion
97
62
  //#region src/events/outbox/backoff.d.ts
98
63
  /**
99
64
  * Exponential backoff for outbox failure policies — pure date math shared
@@ -405,6 +370,97 @@ declare class EventOutbox {
405
370
  purge(olderThanMs?: number): Promise<number>;
406
371
  }
407
372
  //#endregion
373
+ //#region src/events/outbox-module.d.ts
374
+ interface OutboxModuleOptions {
375
+ /**
376
+ * The durable store. Supply it directly, or as a factory when it needs the booted app
377
+ * (a mongoose connection decorated by an earlier plugin, for instance).
378
+ */
379
+ store: OutboxStore | ((fastify: FastifyInstance) => OutboxStore);
380
+ /**
381
+ * Where relayed events are published. Omit to use `fastify.events` — the ordinary case; pass
382
+ * one only when the outbox must publish somewhere other than the app's own transport.
383
+ */
384
+ transport?: EventTransport | ((fastify: FastifyInstance) => EventTransport);
385
+ /** Module name, and therefore the `dependsOn` key. Default `'outbox'`. */
386
+ name?: string;
387
+ /** Relay interval in ms. Default 5000. */
388
+ relayEveryMs?: number;
389
+ /**
390
+ * Rows per relay tick. Left to the store's own default when omitted — a batch size is a
391
+ * throughput tuning decision, not something this module should invent.
392
+ */
393
+ batchSize?: number;
394
+ /** Lease duration for a claimed batch, in ms. */
395
+ leaseMs?: number;
396
+ /**
397
+ * Consumer id recorded on the lease. Default `'arc-outbox-relay'`.
398
+ *
399
+ * Give each relaying process a DISTINCT id when several run: the lease is what stops two
400
+ * relays publishing the same event, and it can only do that if they are distinguishable.
401
+ */
402
+ consumerId?: string;
403
+ /**
404
+ * Called for every non-fatal relay problem — publish failures, ownership mismatches,
405
+ * acknowledge failures.
406
+ *
407
+ * **Wire this.** Silence here is the failure mode that matters: the relay keeps ticking, rows
408
+ * keep failing, and the only symptom is revenue that never posted.
409
+ */
410
+ onError?: OutboxRelayErrorHandler;
411
+ /**
412
+ * Retry / dead-letter policy for a failed publish — `({ attempts }) => { retryAt } |
413
+ * { deadLetter: true }`.
414
+ *
415
+ * Omitting it takes `EventOutbox`'s default. Exposed because a real host had one and losing it
416
+ * on adoption would be a silent downgrade: without a policy a permanently-failing row either
417
+ * retries forever or dead-letters on the first blip, and both are wrong for money. A typical
418
+ * shape is exponential backoff up to N attempts, then dead-letter.
419
+ */
420
+ failurePolicy?: OutboxFailurePolicy;
421
+ /**
422
+ * Run one relay pass at boot. Default `true` — a process that restarts with a backlog should
423
+ * drain it immediately rather than waiting a full interval, because that backlog is precisely
424
+ * the events the previous process failed to publish.
425
+ */
426
+ runOnStart?: boolean;
427
+ }
428
+ /** What the module exports at `fastify.arc.modules.<name>`. */
429
+ interface OutboxModuleExports {
430
+ /** The store, for consumers that write events transactionally. */
431
+ store: OutboxStore;
432
+ /** The relay, for a host that wants to drain on demand (tests, admin actions). */
433
+ relay: EventOutbox;
434
+ }
435
+ /**
436
+ * Compose the outbox as a module.
437
+ *
438
+ * ```ts
439
+ * // host
440
+ * const model = createOutboxModel(connection, { visibleAtField: 'nextVisibleAt' });
441
+ * createApp({ modules: [
442
+ * createOutboxModule({
443
+ * store: repositoryAsOutboxStore(new Repository(model), { visibleAtField: 'nextVisibleAt' }),
444
+ * onError: (err, ctx) => app.log.error({ err, ...ctx }, 'outbox relay'),
445
+ * }),
446
+ * …
447
+ * ]});
448
+ *
449
+ * // consumer module
450
+ * defineModule({
451
+ * name: 'order',
452
+ * dependsOn: ['outbox'],
453
+ * bootstrap: (f) => createOrder({
454
+ * outbox: getModuleExports<OutboxModuleExports>(f, 'outbox').store,
455
+ * }),
456
+ * });
457
+ * ```
458
+ *
459
+ * `dependsOn` is doing real work there: it both orders the bootstraps and makes a missing outbox a
460
+ * boot error naming the module that needed it.
461
+ */
462
+ declare function createOutboxModule(options: OutboxModuleOptions): ArcModule<OutboxModuleExports>;
463
+ //#endregion
408
464
  //#region src/events/repository-outbox-adapter.d.ts
409
465
  interface RepositoryOutboxStoreOptions {
410
466
  /**
@@ -422,7 +478,18 @@ interface RepositoryOutboxStoreOptions {
422
478
  */
423
479
  readonly visibleAtField?: string;
424
480
  }
425
- declare function repositoryAsOutboxStore(repository: RepositoryLike, options?: RepositoryOutboxStoreOptions): OutboxStore;
481
+ /**
482
+ * What this adapter GUARANTEES, as opposed to what `OutboxStore` permits.
483
+ *
484
+ * Most of the contract is optional so a minimal or non-relational store can implement
485
+ * the floor and no more. This adapter implements all of it, and saying so in the type
486
+ * is not cosmetic: with a plain `OutboxStore` return every caller of `getDeadLettered`,
487
+ * `requeue` or `countByStatus` has to write `store.requeue?.(id)`, and the `?.` silently
488
+ * evaluates to `undefined` if the method ever disappears — an operator's replay that
489
+ * quietly does nothing and reports success.
490
+ */
491
+ type RepositoryOutboxStore = OutboxStore & Required<Pick<OutboxStore, "fail" | "getDeadLettered" | "purge" | "requeue" | "countByStatus" | "oldestPendingAgeMs">>;
492
+ declare function repositoryAsOutboxStore(repository: RepositoryLike, options?: RepositoryOutboxStoreOptions): RepositoryOutboxStore;
426
493
  //#endregion
427
494
  //#region src/events/subscribe-helpers.d.ts
428
495
  /**
@@ -583,4 +650,4 @@ interface FastifyEventBus {
583
650
  };
584
651
  }
585
652
  //#endregion
586
- export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, type EventDefinitionInput, type EventDefinitionOutput, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, type ExponentialBackoffOptions, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxRelayErrorHandler, type OutboxRelayErrorKind, type PayloadOf, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RelayResult, type RepositoryOutboxStoreOptions, type RetryOptions, type ValidationResult, type WrapWithBoundaryOptions, type WrapWithSchemaOptions, createDeadLetterPublisher, createEventRegistry, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };
653
+ export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, type EventDefinitionInput, type EventDefinitionOutput, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, type ExponentialBackoffOptions, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxModuleExports, type OutboxModuleOptions, type OutboxRelayErrorHandler, type OutboxRelayErrorKind, type PayloadOf, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RelayResult, type RepositoryOutboxStore, type RepositoryOutboxStoreOptions, type RetryOptions, type ValidationResult, type WrapWithBoundaryOptions, type WrapWithSchemaOptions, createDeadLetterPublisher, createEventRegistry, createOutboxModule, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };