@classytic/arc 2.11.3 → 2.13.1

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 (185) hide show
  1. package/README.md +27 -18
  2. package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
  3. package/dist/EventTransport-CT_52aWU.d.mts +34 -0
  4. package/dist/EventTransport-DLWoUMHy.mjs +103 -0
  5. package/dist/{QueryCache-DOBNHBE0.d.mts → QueryCache-D41bfdBB.d.mts} +1 -1
  6. package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
  7. package/dist/audit/index.d.mts +2 -2
  8. package/dist/audit/index.mjs +1 -1
  9. package/dist/auth/audit.d.mts +199 -0
  10. package/dist/auth/audit.mjs +288 -0
  11. package/dist/auth/index.d.mts +5 -5
  12. package/dist/auth/index.mjs +117 -191
  13. package/dist/auth/redis-session.d.mts +1 -1
  14. package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
  15. package/dist/buildHandler-olo-gt94.mjs +610 -0
  16. package/dist/cache/index.d.mts +3 -3
  17. package/dist/cache/index.mjs +3 -3
  18. package/dist/cli/commands/describe.d.mts +89 -13
  19. package/dist/cli/commands/describe.mjs +56 -2
  20. package/dist/cli/commands/docs.mjs +2 -2
  21. package/dist/cli/commands/generate.mjs +147 -48
  22. package/dist/cli/commands/init.d.mts +13 -0
  23. package/dist/cli/commands/init.mjs +237 -112
  24. package/dist/cli/commands/introspect.mjs +8 -1
  25. package/dist/context/index.mjs +1 -1
  26. package/dist/core/index.d.mts +3 -3
  27. package/dist/core/index.mjs +5 -5
  28. package/dist/core-D72ia0EH.mjs +1399 -0
  29. package/dist/{createActionRouter-u3ql2EDo.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
  30. package/dist/createAggregationRouter-CyecOxnO.mjs +114 -0
  31. package/dist/{createApp-BFxtdKy6.mjs → createApp-XX2-N0Yd.mjs} +31 -27
  32. package/dist/defineEvent-D5h7EvAx.mjs +188 -0
  33. package/dist/docs/index.d.mts +2 -2
  34. package/dist/docs/index.mjs +2 -2
  35. package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
  36. package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
  37. package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
  38. package/dist/errors-j4aJm1Wg.mjs +184 -0
  39. package/dist/{eventPlugin-KrFIQ097.mjs → eventPlugin-CaKTYkYM.mjs} +35 -137
  40. package/dist/{eventPlugin-CUNjYYRY.d.mts → eventPlugin-qXpqTebY.d.mts} +57 -7
  41. package/dist/events/index.d.mts +164 -5
  42. package/dist/events/index.mjs +133 -209
  43. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  44. package/dist/events/transports/redis-stream-entry.mjs +204 -31
  45. package/dist/events/transports/redis.d.mts +1 -1
  46. package/dist/factory/index.d.mts +2 -2
  47. package/dist/factory/index.mjs +2 -2
  48. package/dist/{fields-C8Y0XLAu.d.mts → fields-COhcH3fk.d.mts} +23 -2
  49. package/dist/hooks/index.d.mts +1 -1
  50. package/dist/hooks/index.mjs +1 -1
  51. package/dist/idempotency/index.d.mts +3 -3
  52. package/dist/idempotency/index.mjs +1 -20
  53. package/dist/idempotency/redis.d.mts +1 -1
  54. package/dist/idempotency/redis.mjs +1 -1
  55. package/dist/{index-BYCqHCVu.d.mts → index-BTqLEvhu.d.mts} +164 -4
  56. package/dist/{index-6u4_Gg6G.d.mts → index-BtW7qYwa.d.mts} +661 -281
  57. package/dist/{index-BdXnTPRj.d.mts → index-Ds61mrJE.d.mts} +50 -4
  58. package/dist/{index-DdQ3O9Pg.d.mts → index-Dz5IKsrE.d.mts} +360 -219
  59. package/dist/index.d.mts +6 -7
  60. package/dist/index.mjs +9 -10
  61. package/dist/integrations/event-gateway.d.mts +2 -2
  62. package/dist/integrations/event-gateway.mjs +1 -1
  63. package/dist/integrations/index.d.mts +2 -2
  64. package/dist/integrations/mcp/index.d.mts +2 -2
  65. package/dist/integrations/mcp/index.mjs +1 -1
  66. package/dist/integrations/mcp/testing.d.mts +1 -1
  67. package/dist/integrations/mcp/testing.mjs +1 -1
  68. package/dist/integrations/streamline.d.mts +60 -11
  69. package/dist/integrations/streamline.mjs +75 -85
  70. package/dist/integrations/websocket-redis.d.mts +1 -1
  71. package/dist/integrations/websocket.d.mts +1 -1
  72. package/dist/integrations/websocket.mjs +2 -8
  73. package/dist/middleware/index.d.mts +1 -1
  74. package/dist/middleware/index.mjs +2 -2
  75. package/dist/migrations/index.d.mts +23 -3
  76. package/dist/migrations/index.mjs +0 -7
  77. package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
  78. package/dist/{openapi-BGUn7Ki1.mjs → openapi-CiOMVW1p.mjs} +143 -13
  79. package/dist/org/index.d.mts +2 -2
  80. package/dist/org/index.mjs +1 -1
  81. package/dist/permissions/index.d.mts +3 -3
  82. package/dist/permissions/index.mjs +3 -3
  83. package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
  84. package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
  85. package/dist/pipeline/index.d.mts +1 -1
  86. package/dist/pipeline/index.mjs +1 -1
  87. package/dist/plugins/index.d.mts +18 -33
  88. package/dist/plugins/index.mjs +33 -13
  89. package/dist/plugins/response-cache.mjs +1 -1
  90. package/dist/plugins/tracing-entry.d.mts +1 -1
  91. package/dist/plugins/tracing-entry.mjs +1 -1
  92. package/dist/presets/filesUpload.d.mts +5 -5
  93. package/dist/presets/filesUpload.mjs +6 -9
  94. package/dist/presets/index.d.mts +1 -1
  95. package/dist/presets/index.mjs +1 -1
  96. package/dist/presets/multiTenant.d.mts +1 -1
  97. package/dist/presets/multiTenant.mjs +2 -2
  98. package/dist/presets/search.d.mts +2 -2
  99. package/dist/presets/search.mjs +6 -8
  100. package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
  101. package/dist/{queryCachePlugin-BUXBSm4F.d.mts → queryCachePlugin-CqMdLI2-.d.mts} +2 -2
  102. package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
  103. package/dist/{redis-Cm1gnRDf.d.mts → redis-DiMkdHEl.d.mts} +1 -1
  104. package/dist/redis-stream-D6HzR1Z_.d.mts +232 -0
  105. package/dist/registry/index.d.mts +1 -1
  106. package/dist/registry/index.mjs +2 -2
  107. package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
  108. package/dist/{resourceToTools-ByZpgjeH.mjs → resourceToTools-C5coh64w.mjs} +224 -71
  109. package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
  110. package/dist/{schemaIR-BlG9bY7v.mjs → schemaIR-7Vl611Qs.mjs} +1 -1
  111. package/dist/schemas/index.d.mts +100 -30
  112. package/dist/schemas/index.mjs +86 -29
  113. package/dist/scim/index.d.mts +264 -0
  114. package/dist/scim/index.mjs +963 -0
  115. package/dist/scope/index.d.mts +3 -3
  116. package/dist/scope/index.mjs +4 -4
  117. package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
  118. package/dist/{store-helpers-BhrzxvyQ.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
  119. package/dist/testing/index.d.mts +2 -8
  120. package/dist/testing/index.mjs +16 -24
  121. package/dist/testing/storageContract.d.mts +1 -1
  122. package/dist/types/index.d.mts +4 -4
  123. package/dist/types/storage.d.mts +1 -1
  124. package/dist/{types-BH7dEGvU.d.mts → types-BvqwCCSx.d.mts} +77 -29
  125. package/dist/{types-tgR4Pt8F.d.mts → types-CTYvcwHe.d.mts} +195 -1
  126. package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
  127. package/dist/{types-9beEMe25.d.mts → types-DQHFc8PM.d.mts} +1 -1
  128. package/dist/utils/index.d.mts +2 -2
  129. package/dist/utils/index.mjs +5 -5
  130. package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
  131. package/dist/{versioning-M9lNLhO8.d.mts → versioning-DTTvc80y.d.mts} +1 -1
  132. package/package.json +24 -34
  133. package/skills/arc/SKILL.md +521 -785
  134. package/skills/arc/references/agent-auth.md +238 -0
  135. package/skills/arc/references/api-reference.md +187 -0
  136. package/skills/arc/references/auth.md +354 -7
  137. package/skills/arc/references/enterprise-auth.md +94 -0
  138. package/skills/arc/references/events.md +8 -6
  139. package/skills/arc/references/mcp.md +2 -2
  140. package/skills/arc/references/multi-tenancy.md +11 -2
  141. package/skills/arc/references/production.md +10 -9
  142. package/skills/arc/references/scim.md +247 -0
  143. package/skills/arc/references/testing.md +1 -1
  144. package/skills/arc-code-review/SKILL.md +141 -0
  145. package/skills/arc-code-review/references/anti-patterns.md +911 -0
  146. package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
  147. package/skills/arc-code-review/references/migration-recipes.md +700 -0
  148. package/skills/arc-code-review/references/mongokit-migration.md +386 -0
  149. package/skills/arc-code-review/references/scaffolding.md +230 -0
  150. package/skills/arc-code-review/references/severity.md +127 -0
  151. package/dist/EventTransport-CfVEGaEl.d.mts +0 -293
  152. package/dist/adapters/index.d.mts +0 -3
  153. package/dist/adapters/index.mjs +0 -2
  154. package/dist/adapters-D0tT2Tyo.mjs +0 -949
  155. package/dist/auth/mongoose.d.mts +0 -191
  156. package/dist/auth/mongoose.mjs +0 -73
  157. package/dist/core-DnUsRpuX.mjs +0 -1049
  158. package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
  159. package/dist/errorHandler-Co3lnVmJ.d.mts +0 -114
  160. package/dist/errors-D5c-5BJL.mjs +0 -232
  161. package/dist/index-BbMrcvGp.d.mts +0 -362
  162. package/dist/redis-stream-CM8TXTix.d.mts +0 -110
  163. /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
  164. /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
  165. /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
  166. /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
  167. /package/dist/{elevation-s5ykdNHr.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
  168. /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BD5nw6St.d.mts} +0 -0
  169. /package/dist/{interface-CkkWm5uR.d.mts → interface-DfLGcus7.d.mts} +0 -0
  170. /package/dist/{interface-Da0r7Lna.d.mts → interface-beEtJyWM.d.mts} +0 -0
  171. /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
  172. /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
  173. /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
  174. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  175. /package/dist/{pluralize-BneOJkpi.mjs → pluralize-DQgqgifU.mjs} +0 -0
  176. /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
  177. /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
  178. /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
  179. /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-C4Le_UB3.d.mts} +0 -0
  180. /package/dist/{storage-BwGQXUpd.d.mts → storage-Dfzt4VTl.d.mts} +0 -0
  181. /package/dist/{tracing-DokiEsuz.d.mts → tracing-QJVprktp.d.mts} +0 -0
  182. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
  183. /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
  184. /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
  185. /package/dist/{websocket-CyJ1VIFI.d.mts → websocket-ChC2rqe1.d.mts} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { a as buildAuthMiddlewareForPermissions, c as buildPreHandlerChain, f as resolveRouterPluginMw, l as buildRateLimitConfig, n as buildActionPipelineHandler, p as selectPluginMw, r as buildArcDecorator, t as buildActionPermissionMw, u as resolvePipelineSteps, y as sendControllerResponse } from "./routerShared-BqLRb5l7.mjs";
3
- import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-BlG9bY7v.mjs";
2
+ import { f as createError } from "./errors-j4aJm1Wg.mjs";
3
+ import { a as buildAuthMiddlewareForPermissions, c as buildPreHandlerChain, f as resolveRouterPluginMw, l as buildRateLimitConfig, n as buildActionPipelineHandler, p as selectPluginMw, r as buildArcDecorator, t as buildActionPermissionMw, u as resolvePipelineSteps, y as sendControllerResponse } from "./routerShared-D6_fEGHh.mjs";
4
+ import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-7Vl611Qs.mjs";
4
5
  //#region src/core/createActionRouter.ts
5
6
  var createActionRouter_exports = /* @__PURE__ */ __exportAll({
6
7
  buildActionBodySchema: () => buildActionBodySchema,
@@ -69,23 +70,13 @@ function createActionRouter(fastify, config) {
69
70
  const { action, ...data } = req.body;
70
71
  const { id } = req.params;
71
72
  const handler = wrappedHandlers.get(action);
72
- if (!handler) return sendControllerResponse(reply, {
73
- success: false,
74
- status: 400,
75
- error: `Invalid action '${action}'. Valid actions: ${actionEnum.join(", ")}`,
76
- meta: { validActions: actionEnum }
77
- }, req);
73
+ if (!handler) throw createError(400, `Invalid action '${action}'. Valid actions: ${actionEnum.join(", ")}`, { validActions: actionEnum });
78
74
  try {
79
75
  return sendControllerResponse(reply, await handler(id, data, req), req);
80
76
  } catch (error) {
81
77
  if (onError) {
82
78
  const { statusCode, error: errorMsg, code } = onError(error, action, id);
83
- return sendControllerResponse(reply, {
84
- success: false,
85
- status: statusCode,
86
- error: errorMsg,
87
- ...code ? { meta: { code } } : {}
88
- }, req);
79
+ throw createError(statusCode, errorMsg, code ? { code } : void 0);
89
80
  }
90
81
  const err = error;
91
82
  const statusCode = err.statusCode || err.status || 500;
@@ -95,12 +86,8 @@ function createActionRouter(fastify, config) {
95
86
  action,
96
87
  id
97
88
  }, "Action handler error");
98
- return sendControllerResponse(reply, {
99
- success: false,
100
- status: statusCode,
101
- error: err.message || `Failed to execute '${action}' action`,
102
- meta: { code: errorCode }
103
- }, req);
89
+ if (error?.name === "ArcError" || error instanceof Error === false) throw error;
90
+ throw createError(statusCode, err.message || `Failed to execute '${action}' action`, { code: errorCode });
104
91
  }
105
92
  }
106
93
  });
@@ -0,0 +1,114 @@
1
+ import { f as createError, l as UnauthorizedError, r as ForbiddenError } from "./errors-j4aJm1Wg.mjs";
2
+ import { c as buildPreHandlerChain, f as resolveRouterPluginMw, i as buildAuthMiddleware, l as buildRateLimitConfig, p as selectPluginMw, r as buildArcDecorator } from "./routerShared-D6_fEGHh.mjs";
3
+ import { r as validateAggregations, t as buildAggregationHandler } from "./buildHandler-olo-gt94.mjs";
4
+ //#region src/core/aggregation/createAggregationRouter.ts
5
+ /**
6
+ * Register one Fastify route per aggregation. No-op when the map is
7
+ * empty — same convention `createActionRouter` follows.
8
+ */
9
+ function createAggregationRouter(fastify, config) {
10
+ const { tag, resourceName, aggregations, fields: fieldPermissions, schemaOptions, permissions: resourcePermissions, routeGuards = [], repository, buildOptions } = config;
11
+ if (!aggregations || Object.keys(aggregations).length === 0) return;
12
+ const normalized = validateAggregations(resourceName, aggregations, schemaOptions);
13
+ const arcDecorator = buildArcDecorator({
14
+ resourceName,
15
+ schemaOptions,
16
+ permissions: resourcePermissions,
17
+ hooks: fastify.arc?.hooks,
18
+ events: fastify.events,
19
+ fields: fieldPermissions
20
+ });
21
+ for (const aggregation of normalized) registerOne(fastify, aggregation, {
22
+ tag,
23
+ arcDecorator,
24
+ routeGuards,
25
+ repository,
26
+ buildOptions
27
+ });
28
+ fastify.log?.debug?.({
29
+ aggregations: normalized.map((a) => a.name),
30
+ resourceName
31
+ }, `[createAggregationRouter] registered ${normalized.length} aggregation route(s)`);
32
+ }
33
+ function registerOne(fastify, normalized, ctx) {
34
+ const { tag, arcDecorator, routeGuards, repository, buildOptions } = ctx;
35
+ const { name } = normalized;
36
+ const config = normalized.base;
37
+ const authMw = buildAuthMiddleware(fastify, config.permissions);
38
+ const permissionFn = config.permissions;
39
+ const permissionMw = async (req, _reply) => {
40
+ const raw = await permissionFn(buildPermissionContextLite(req, normalized.name));
41
+ if (!normalizePermissionGranted(raw)) {
42
+ const status = req.user ? 403 : 401;
43
+ const reason = normalizePermissionReason(raw);
44
+ if (status === 401) throw new UnauthorizedError(reason ?? "Authentication required to access this aggregation.");
45
+ throw new ForbiddenError(reason ?? "You do not have permission to access this aggregation.");
46
+ }
47
+ };
48
+ const preHandler = buildPreHandlerChain({
49
+ arcDecorator,
50
+ authMw,
51
+ permissionMw,
52
+ pluginMw: selectPluginMw("GET", resolveRouterPluginMw(fastify, false)),
53
+ routeGuards
54
+ });
55
+ const rateLimitConfig = buildRateLimitConfig(config.rateLimit ? {
56
+ max: config.rateLimit.max,
57
+ timeWindow: `${config.rateLimit.windowMs}ms`
58
+ } : void 0);
59
+ const handler = buildAggregationHandler(normalized, {
60
+ repo: repository,
61
+ buildOptions
62
+ });
63
+ const routeSchema = {
64
+ tags: tag ? [tag] : void 0,
65
+ summary: config.summary ?? `Aggregation: ${name}`,
66
+ description: config.description ?? "Portable aggregation generated by arc. Filters from query string compose with the declaration's base filter + tenant scope."
67
+ };
68
+ fastify.route({
69
+ method: "GET",
70
+ url: `/aggregations/${name}`,
71
+ schema: routeSchema,
72
+ preHandler: preHandler.length > 0 ? preHandler : void 0,
73
+ ...rateLimitConfig ? { config: rateLimitConfig } : {},
74
+ handler: async (req, reply) => {
75
+ try {
76
+ return await handler(req, reply);
77
+ } catch (err) {
78
+ const message = err instanceof Error ? err.message : String(err);
79
+ req.log.error({
80
+ err,
81
+ aggregation: name
82
+ }, "Aggregation handler error");
83
+ throw createError(500, `Aggregation "${name}" failed: ${message}`);
84
+ }
85
+ }
86
+ });
87
+ }
88
+ /**
89
+ * Minimal `PermissionContext` for aggregation routes. Aggregations are
90
+ * read-shape so the action is `'list'` and `data` / `resourceId` stay
91
+ * undefined unless the URL includes them (none do today — `:name` is
92
+ * the only path param).
93
+ */
94
+ function buildPermissionContextLite(req, aggregationName) {
95
+ const reqWithExtras = req;
96
+ return {
97
+ user: reqWithExtras.user ?? null,
98
+ request: req,
99
+ resource: reqWithExtras.arc?.resource ?? "aggregation",
100
+ action: `aggregation:${aggregationName}`
101
+ };
102
+ }
103
+ /** PermissionCheck returns `boolean | PermissionResult`. Pull `granted`. */
104
+ function normalizePermissionGranted(raw) {
105
+ if (typeof raw === "boolean") return raw;
106
+ return raw.granted;
107
+ }
108
+ /** Pull `reason` when the check returned a structured `PermissionResult`. */
109
+ function normalizePermissionReason(raw) {
110
+ if (typeof raw === "boolean") return void 0;
111
+ return raw.reason;
112
+ }
113
+ //#endregion
114
+ export { createAggregationRouter };
@@ -1,5 +1,6 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { n as PUBLIC_SCOPE } from "./types-AOD8fxIw.mjs";
2
+ import { arcLog } from "./logger/index.mjs";
3
+ import { n as PUBLIC_SCOPE } from "./types-C_s5moIu.mjs";
3
4
  import Fastify from "fastify";
4
5
  import qs from "qs";
5
6
  //#region src/factory/presets.ts
@@ -117,10 +118,7 @@ const developmentPreset = {
117
118
  ]
118
119
  },
119
120
  rateLimit: false,
120
- underPressure: {
121
- exposeStatusRoute: true,
122
- maxEventLoopDelay: 5e3
123
- }
121
+ underPressure: false
124
122
  };
125
123
  /**
126
124
  * Testing preset - minimal setup, fast startup
@@ -204,7 +202,7 @@ async function registerArcCore(fastify, config, trackPlugin) {
204
202
  await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
205
203
  trackPlugin("arc-core");
206
204
  if (config.arcPlugins?.events !== false) {
207
- const { default: eventPlugin } = await import("./eventPlugin-KrFIQ097.mjs").then((n) => n.n);
205
+ const { default: eventPlugin } = await import("./eventPlugin-CaKTYkYM.mjs").then((n) => n.n);
208
206
  const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
209
207
  await fastify.register(eventPlugin, {
210
208
  ...eventOpts,
@@ -240,15 +238,15 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
240
238
  trackPlugin("arc-graceful-shutdown");
241
239
  }
242
240
  if (config.arcPlugins?.caching) {
243
- const { default: cachingPlugin } = await import("./caching-CheW3m-S.mjs").then((n) => n.r);
241
+ const { default: cachingPlugin } = await import("./caching-SM8gghN6.mjs").then((n) => n.r);
244
242
  const opts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
245
243
  await fastify.register(cachingPlugin, opts);
246
244
  trackPlugin("arc-caching", opts);
247
245
  }
248
246
  if (config.arcPlugins?.queryCache) {
249
- const { queryCachePlugin } = await import("./queryCachePlugin-Bq6bO6vc.mjs").then((n) => n.n);
247
+ const { queryCachePlugin } = await import("./queryCachePlugin-m1XsgAIJ.mjs").then((n) => n.n);
250
248
  const opts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
251
- const store = config.stores?.queryCache ?? new (await (import("./memory-DikHSvWa.mjs").then((n) => n.n))).MemoryCacheStore();
249
+ const store = config.stores?.queryCache ?? new (await (import("./memory-UBydS5ku.mjs").then((n) => n.n))).MemoryCacheStore();
252
250
  await fastify.register(queryCachePlugin, {
253
251
  store,
254
252
  ...opts
@@ -257,19 +255,19 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
257
255
  }
258
256
  if (config.arcPlugins?.sse) if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
259
257
  else {
260
- const { default: ssePlugin } = await import("./sse-V7aXc3bW.mjs").then((n) => n.r);
258
+ const { default: ssePlugin } = await import("./sse-Bz-5ZeTt.mjs").then((n) => n.r);
261
259
  const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
262
260
  await fastify.register(ssePlugin, opts);
263
261
  trackPlugin("arc-sse", opts);
264
262
  }
265
263
  if (config.arcPlugins?.metrics) {
266
- const { default: metricsPlugin } = await import("./metrics-Csh4nsvv.mjs").then((n) => n.r);
264
+ const { default: metricsPlugin } = await import("./metrics-Qnvwc-LQ.mjs").then((n) => n.r);
267
265
  const opts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
268
266
  await fastify.register(metricsPlugin, opts);
269
267
  trackPlugin("arc-metrics", opts);
270
268
  }
271
269
  if (config.arcPlugins?.versioning) {
272
- const { default: versioningPlugin } = await import("./versioning-CGPjkqAg.mjs").then((n) => n.r);
270
+ const { default: versioningPlugin } = await import("./versioning-BUrT5aP4.mjs").then((n) => n.r);
273
271
  await fastify.register(versioningPlugin, config.arcPlugins.versioning);
274
272
  trackPlugin("arc-versioning", config.arcPlugins.versioning);
275
273
  }
@@ -338,7 +336,7 @@ async function registerAuth(fastify, config, trackPlugin) {
338
336
  */
339
337
  async function registerElevation(fastify, config, trackPlugin) {
340
338
  if (!config.elevation) return;
341
- const { elevationPlugin } = await import("./elevation-DOFoxoDs.mjs").then((n) => n.r);
339
+ const { elevationPlugin } = await import("./elevation-DgoeTyfX.mjs").then((n) => n.r);
342
340
  await fastify.register(elevationPlugin, config.elevation);
343
341
  trackPlugin("arc-elevation", config.elevation);
344
342
  fastify.log.debug("Elevation plugin enabled");
@@ -348,7 +346,7 @@ async function registerElevation(fastify, config, trackPlugin) {
348
346
  */
349
347
  async function registerErrorHandler(fastify, config, trackPlugin) {
350
348
  if (config.errorHandler === false) return;
351
- const { errorHandlerPlugin } = await import("./errorHandler-BQm8ZxTK.mjs").then((n) => n.r);
349
+ const { errorHandlerPlugin } = await import("./errorHandler-Bk-AGhkU.mjs").then((n) => n.r);
352
350
  const errorOpts = typeof config.errorHandler === "object" ? config.errorHandler : { includeStack: config.preset !== "production" };
353
351
  await fastify.register(errorHandlerPlugin, errorOpts);
354
352
  trackPlugin("arc-error-handler", errorOpts);
@@ -402,7 +400,7 @@ async function registerOne(parent, resource) {
402
400
  }
403
401
  /**
404
402
  * Execute the full resource lifecycle:
405
- * 1. plugins() — infra (DB, docs, webhooks)
403
+ * 1. plugins() — infra (DB, data, webhooks)
406
404
  * 2. bootstrap[] — domain init (singletons, event handlers)
407
405
  * 3. resources factory (if any) — resolved AFTER bootstrap, so engine-backed
408
406
  * adapters can `await ensureEngine()` and pass
@@ -448,7 +446,7 @@ async function registerResources(fastify, config) {
448
446
  let discoveryPath;
449
447
  let discoveryYieldedZero = false;
450
448
  if (resolvedResources === void 0 && config.resourceDir) {
451
- const { loadResources } = await import("./loadResources-CPpkyKfM.mjs").then((n) => n.n);
449
+ const { loadResources } = await import("./loadResources-DBMQg_Aj.mjs").then((n) => n.n);
452
450
  const { resolve, dirname } = await import("node:path");
453
451
  const { fileURLToPath } = await import("node:url");
454
452
  const rawDir = config.resourceDir;
@@ -605,7 +603,8 @@ async function registerSecurityPlugins(fastify, config) {
605
603
  if (config.cors !== false) {
606
604
  const cors = await loadPlugin("cors");
607
605
  const corsOptions = { ...config.cors ?? {} };
608
- if (config.preset === "production" && corsOptions && !("origin" in corsOptions)) fastify.log.warn("CORS origin is not explicitly configured in production. Set cors.origin to allowed domains, cors: { origin: '*' }, or cors: false to disable.");
606
+ const originDeclared = "origin" in corsOptions && corsOptions.origin !== void 0;
607
+ if (config.preset === "production" && !originDeclared) fastify.log.warn("CORS origin is not explicitly configured in production. Browser apps: set cors.origin to allowed domains (e.g. ['https://app.example.com']) with credentials: true. Server-to-server / API-key services: cors: { origin: '*', credentials: false } OR cors: false to disable. Tip: when wiring cors.origin from an env var, fail fast on missing (`if (!process.env.ALLOWED_ORIGINS) throw ...`) instead of letting `undefined` slip through.");
609
608
  if (corsOptions.credentials && corsOptions.origin === "*") corsOptions.origin = true;
610
609
  await fastify.register(cors, corsOptions);
611
610
  fastify.log.debug("CORS enabled");
@@ -747,7 +746,7 @@ function validateDistributedRuntime(options) {
747
746
  * 4. Arc core (fastify.arc, events)
748
747
  * 5. Arc plugins (requestId, health, caching, SSE, metrics, versioning)
749
748
  * 6. Auth (scope decoration, auth strategy, elevation, error handler)
750
- * 7. plugins() — user infra (DB, docs, webhooks)
749
+ * 7. plugins() — user infra (DB, data, webhooks)
751
750
  * 8. bootstrap[] — domain init (singletons, event handlers)
752
751
  * 9. resources[] — auto-discovered routes (prefix + skipGlobalPrefix)
753
752
  * 10. afterResources() — post-registration wiring
@@ -812,17 +811,22 @@ async function createApp(options) {
812
811
  await registerErrorHandler(fastify, config, trackPlugin);
813
812
  await registerResources(fastify, config);
814
813
  if (config.replyHelpers) {
815
- const { replyHelpersPlugin } = await import("./replyHelpers-ByllIXXV.mjs").then((n) => n.n);
814
+ const { replyHelpersPlugin } = await import("./replyHelpers-CK-FNO8E.mjs").then((n) => n.n);
816
815
  await fastify.register(replyHelpersPlugin);
817
816
  }
818
- if (config.serializeBigInt) fastify.addHook("preSerialization", async (_request, _reply, payload) => {
819
- if (payload === null || payload === void 0) return payload;
820
- try {
821
- return JSON.parse(JSON.stringify(payload, (_key, value) => typeof value === "bigint" ? Number(value) : value));
822
- } catch {
823
- return payload;
824
- }
825
- });
817
+ if (config.serializeBigInt) {
818
+ const mode = config.serializeBigInt === "string" ? "string" : "number";
819
+ if (config.serializeBigInt === true) arcLog("createApp").warn("serializeBigInt: true is a back-compat alias for 'number' (lossy above Number.MAX_SAFE_INTEGER = 9007199254740991). For IDs / money / counters / ledger values use serializeBigInt: 'string' (lossless). The boolean form will be removed in a future major.");
820
+ else if (mode === "number") arcLog("createApp").warn("serializeBigInt: 'number' loses precision above Number.MAX_SAFE_INTEGER (9007199254740991). Use 'string' instead unless you've audited that no bigint payload field can exceed the safe range.");
821
+ fastify.addHook("preSerialization", async (_request, _reply, payload) => {
822
+ if (payload === null || payload === void 0) return payload;
823
+ try {
824
+ return JSON.parse(JSON.stringify(payload, (_key, value) => typeof value === "bigint" ? mode === "string" ? value.toString() : Number(value) : value));
825
+ } catch {
826
+ return payload;
827
+ }
828
+ });
829
+ }
826
830
  const authMode = config.auth === false ? "none" : config.auth ? config.auth.type : "none";
827
831
  fastify.log.info({
828
832
  preset: config.preset ?? "custom",
@@ -0,0 +1,188 @@
1
+ import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
+ import { n as createEvent } from "./EventTransport-DLWoUMHy.mjs";
3
+ //#region src/events/defineEvent.ts
4
+ /**
5
+ * defineEvent — Typed Event Definitions with Optional Schema Validation
6
+ *
7
+ * Provides:
8
+ * 1. defineEvent() — declare an event with name, schema, version, description
9
+ * 2. EventRegistry — catalog of all known events + payload validation
10
+ * 3. .create() helper — build DomainEvent with auto-generated metadata
11
+ *
12
+ * The built-in validator checks: object type, required fields, and top-level
13
+ * property types. It does NOT recurse into nested objects, validate arrays,
14
+ * enums, patterns, formats, or $ref. This is intentional — it's a lightweight
15
+ * guard, not a full JSON Schema engine.
16
+ *
17
+ * For full validation, pass a custom `validate` function to `createEventRegistry()`:
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import Ajv from 'ajv';
22
+ * const ajv = new Ajv();
23
+ *
24
+ * const registry = createEventRegistry({
25
+ * validate: (schema, payload) => {
26
+ * const valid = ajv.validate(schema, payload);
27
+ * return valid
28
+ * ? { valid: true }
29
+ * : { valid: false, errors: ajv.errorsText().split(', ') };
30
+ * },
31
+ * });
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * import { defineEvent, createEventRegistry } from '@classytic/arc/events';
37
+ *
38
+ * const OrderCreated = defineEvent({
39
+ * name: 'order.created',
40
+ * version: 1,
41
+ * schema: {
42
+ * type: 'object',
43
+ * properties: {
44
+ * orderId: { type: 'string' },
45
+ * total: { type: 'number' },
46
+ * },
47
+ * required: ['orderId', 'total'],
48
+ * },
49
+ * });
50
+ *
51
+ * // Type-safe event creation
52
+ * const event = OrderCreated.create({ orderId: 'o-1', total: 100 });
53
+ * await fastify.events.publish(event.type, event.payload, event.meta);
54
+ *
55
+ * // Registry for introspection + validation
56
+ * const registry = createEventRegistry();
57
+ * registry.register(OrderCreated);
58
+ * const result = registry.validate('order.created', payload);
59
+ * ```
60
+ */
61
+ var defineEvent_exports = /* @__PURE__ */ __exportAll({
62
+ createEventRegistry: () => createEventRegistry,
63
+ defineEvent: () => defineEvent
64
+ });
65
+ /**
66
+ * Define a typed event with optional schema validation.
67
+ *
68
+ * @example
69
+ * const OrderCreated = defineEvent({
70
+ * name: 'order.created',
71
+ * schema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] },
72
+ * });
73
+ *
74
+ * const event = OrderCreated.create({ orderId: '123' });
75
+ */
76
+ function defineEvent(input) {
77
+ const { name, schema, version = 1, description } = input;
78
+ return {
79
+ name,
80
+ schema,
81
+ version,
82
+ description,
83
+ create(payload, meta) {
84
+ return createEvent(name, payload, {
85
+ schemaVersion: version,
86
+ ...meta
87
+ });
88
+ }
89
+ };
90
+ }
91
+ /**
92
+ * Create an event registry for cataloging and validating events.
93
+ *
94
+ * The registry is opt-in — unregistered events pass validation.
95
+ * This allows gradual adoption without breaking existing code.
96
+ *
97
+ * @param options.validate - Custom validator replacing the built-in minimal validator.
98
+ * The built-in validator only checks top-level object structure (type, required, property types).
99
+ * For nested objects, arrays, enums, patterns, or $ref, provide AJV or similar.
100
+ */
101
+ function createEventRegistry(options) {
102
+ const customValidator = options?.validate;
103
+ const definitions = /* @__PURE__ */ new Map();
104
+ function registryKey(name, version) {
105
+ return `${name}:v${version}`;
106
+ }
107
+ return {
108
+ register(definition) {
109
+ const key = registryKey(definition.name, definition.version);
110
+ if (definitions.has(key)) throw new Error(`Event '${definition.name}' v${definition.version} is already registered. Use a different version number for schema evolution.`);
111
+ definitions.set(key, definition);
112
+ },
113
+ get(name, version) {
114
+ if (version !== void 0) return definitions.get(registryKey(name, version));
115
+ let latest;
116
+ let latestVersion = -1;
117
+ for (const def of definitions.values()) if (def.name === name && def.version > latestVersion) {
118
+ latest = def;
119
+ latestVersion = def.version;
120
+ }
121
+ return latest;
122
+ },
123
+ catalog() {
124
+ return Array.from(definitions.values()).map((def) => ({
125
+ name: def.name,
126
+ version: def.version,
127
+ description: def.description,
128
+ schema: def.schema
129
+ }));
130
+ },
131
+ validate(name, payload, version) {
132
+ let target = version !== void 0 ? definitions.get(registryKey(name, version)) : void 0;
133
+ if (!target && version === void 0) {
134
+ let latestVersion = -1;
135
+ for (const candidate of definitions.values()) if (candidate.name === name && candidate.version > latestVersion) {
136
+ target = candidate;
137
+ latestVersion = candidate.version;
138
+ }
139
+ }
140
+ if (!target) return { valid: true };
141
+ if (!target.schema) return { valid: true };
142
+ return customValidator ? customValidator(target.schema, payload) : validatePayload(payload, target.schema);
143
+ }
144
+ };
145
+ }
146
+ /**
147
+ * Built-in minimal validator — lightweight guard, NOT a full JSON Schema engine.
148
+ *
149
+ * Checks:
150
+ * - payload is an object (not null, not array)
151
+ * - required fields are present
152
+ * - top-level property types match (string, number, boolean, array, object)
153
+ *
154
+ * Does NOT check:
155
+ * - nested object properties
156
+ * - array item types
157
+ * - enum, pattern, format, minLength, minimum, $ref
158
+ *
159
+ * For full validation, pass a custom `validate` function to `createEventRegistry()`.
160
+ */
161
+ function validatePayload(payload, schema) {
162
+ const errors = [];
163
+ if (schema.type === "object") {
164
+ if (payload === null || payload === void 0 || typeof payload !== "object" || Array.isArray(payload)) return {
165
+ valid: false,
166
+ errors: ["Payload must be an object"]
167
+ };
168
+ const record = payload;
169
+ if (schema.required) {
170
+ for (const field of schema.required) if (!(field in record) || record[field] === void 0) errors.push(`Missing required field: '${field}'`);
171
+ }
172
+ if (schema.properties) {
173
+ for (const [key, propSchema] of Object.entries(schema.properties)) if (key in record && record[key] !== void 0 && record[key] !== null) {
174
+ const expectedType = propSchema.type;
175
+ if (expectedType) {
176
+ const actualType = Array.isArray(record[key]) ? "array" : typeof record[key];
177
+ if (expectedType !== actualType) errors.push(`Field '${key}': expected ${expectedType}, got ${actualType}`);
178
+ }
179
+ }
180
+ }
181
+ }
182
+ return errors.length === 0 ? { valid: true } : {
183
+ valid: false,
184
+ errors
185
+ };
186
+ }
187
+ //#endregion
188
+ export { defineEvent as n, defineEvent_exports as r, createEventRegistry as t };
@@ -1,5 +1,5 @@
1
- import { p as RegistryEntry } from "../index-6u4_Gg6G.mjs";
2
- import { t as ExternalOpenApiPaths } from "../externalPaths-Bapitwvd.mjs";
1
+ import { p as RegistryEntry } from "../index-BtW7qYwa.mjs";
2
+ import { t as ExternalOpenApiPaths } from "../externalPaths-BD5nw6St.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
5
5
  //#region src/docs/openapi.d.ts
@@ -1,5 +1,5 @@
1
- import { t as getUserRoles } from "../types-DV9WDfeg.mjs";
2
- import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-BGUn7Ki1.mjs";
1
+ import { t as getUserRoles } from "../types-D57iXYb8.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-CiOMVW1p.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/docs/scalar.ts
5
5
  const scalarPlugin = async (fastify, opts = {}) => {
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
2
  import { arcLog } from "./logger/index.mjs";
3
- import { t as getUserRoles } from "./types-DV9WDfeg.mjs";
3
+ import { t as getUserRoles } from "./types-D57iXYb8.mjs";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/scope/elevation.ts
6
6
  var elevation_exports = /* @__PURE__ */ __exportAll({