@classytic/arc 2.11.4 → 2.14.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 (167) hide show
  1. package/README.md +16 -12
  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/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
  6. package/dist/audit/index.d.mts +2 -2
  7. package/dist/audit/index.mjs +1 -1
  8. package/dist/auth/audit.d.mts +199 -0
  9. package/dist/auth/audit.mjs +288 -0
  10. package/dist/auth/index.d.mts +3 -3
  11. package/dist/auth/index.mjs +117 -191
  12. package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
  13. package/dist/buildHandler-olo-gt94.mjs +610 -0
  14. package/dist/cache/index.mjs +3 -3
  15. package/dist/cli/commands/describe.d.mts +89 -13
  16. package/dist/cli/commands/describe.mjs +56 -2
  17. package/dist/cli/commands/docs.mjs +2 -2
  18. package/dist/cli/commands/generate.mjs +147 -48
  19. package/dist/cli/commands/init.d.mts +13 -0
  20. package/dist/cli/commands/init.mjs +130 -87
  21. package/dist/cli/commands/introspect.mjs +8 -1
  22. package/dist/context/index.mjs +1 -1
  23. package/dist/core/index.d.mts +3 -3
  24. package/dist/core/index.mjs +5 -5
  25. package/dist/core-DECn6zaU.mjs +1399 -0
  26. package/dist/{createActionRouter-CIKOcNA7.mjs → createActionRouter-CBxLLbn3.mjs} +7 -20
  27. package/dist/createAggregationRouter-CRIBv4sC.mjs +114 -0
  28. package/dist/{createApp-C9bRrqlX.mjs → createApp-XX2-N0Yd.mjs} +28 -22
  29. package/dist/{defineEvent-D1Ky9M1D.mjs → defineEvent-D5h7EvAx.mjs} +1 -1
  30. package/dist/docs/index.d.mts +24 -11
  31. package/dist/docs/index.mjs +2 -2
  32. package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
  33. package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
  34. package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
  35. package/dist/errors-j4aJm1Wg.mjs +184 -0
  36. package/dist/{eventPlugin-Cts2-Tfj.mjs → eventPlugin-CaKTYkYM.mjs} +28 -4
  37. package/dist/{eventPlugin-DDJoNEPL.d.mts → eventPlugin-qXpqTebY.d.mts} +24 -1
  38. package/dist/events/index.d.mts +6 -6
  39. package/dist/events/index.mjs +11 -35
  40. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  41. package/dist/events/transports/redis.d.mts +1 -1
  42. package/dist/factory/index.d.mts +2 -2
  43. package/dist/factory/index.mjs +2 -2
  44. package/dist/{fields-BRjxOAFp.d.mts → fields-COhcH3fk.d.mts} +23 -2
  45. package/dist/hooks/index.d.mts +1 -1
  46. package/dist/hooks/index.mjs +1 -1
  47. package/dist/idempotency/index.d.mts +1 -1
  48. package/dist/idempotency/index.mjs +1 -20
  49. package/dist/idempotency/redis.mjs +1 -1
  50. package/dist/{index-rHjXmJar.d.mts → index-BTqLEvhu.d.mts} +163 -3
  51. package/dist/{index-CXXRbnf8.d.mts → index-BtW7qYwa.d.mts} +660 -326
  52. package/dist/{index-m8mOOlFW.d.mts → index-Ds61mrJE.d.mts} +50 -4
  53. package/dist/{index-D9t1KNaB.d.mts → index-Dz5IKsrE.d.mts} +360 -219
  54. package/dist/index.d.mts +6 -7
  55. package/dist/index.mjs +9 -10
  56. package/dist/integrations/event-gateway.d.mts +1 -1
  57. package/dist/integrations/event-gateway.mjs +1 -1
  58. package/dist/integrations/index.d.mts +1 -1
  59. package/dist/integrations/mcp/index.d.mts +2 -2
  60. package/dist/integrations/mcp/index.mjs +1 -1
  61. package/dist/integrations/mcp/testing.d.mts +1 -1
  62. package/dist/integrations/mcp/testing.mjs +1 -1
  63. package/dist/integrations/streamline.d.mts +60 -11
  64. package/dist/integrations/streamline.mjs +75 -85
  65. package/dist/integrations/websocket.mjs +2 -8
  66. package/dist/middleware/index.d.mts +1 -1
  67. package/dist/middleware/index.mjs +2 -2
  68. package/dist/migrations/index.d.mts +23 -3
  69. package/dist/migrations/index.mjs +0 -7
  70. package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
  71. package/dist/openapi-noXno2CV.mjs +968 -0
  72. package/dist/org/index.d.mts +2 -2
  73. package/dist/org/index.mjs +1 -1
  74. package/dist/permissions/index.d.mts +3 -3
  75. package/dist/permissions/index.mjs +3 -3
  76. package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
  77. package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
  78. package/dist/pipeline/index.d.mts +1 -1
  79. package/dist/pipeline/index.mjs +1 -1
  80. package/dist/plugins/index.d.mts +16 -31
  81. package/dist/plugins/index.mjs +33 -13
  82. package/dist/plugins/response-cache.mjs +1 -1
  83. package/dist/plugins/tracing-entry.mjs +1 -1
  84. package/dist/presets/filesUpload.d.mts +4 -4
  85. package/dist/presets/filesUpload.mjs +6 -9
  86. package/dist/presets/index.d.mts +1 -1
  87. package/dist/presets/index.mjs +1 -1
  88. package/dist/presets/multiTenant.d.mts +1 -1
  89. package/dist/presets/multiTenant.mjs +2 -2
  90. package/dist/presets/search.d.mts +2 -2
  91. package/dist/presets/search.mjs +6 -8
  92. package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
  93. package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
  94. package/dist/{redis-stream-xTGxB2bm.d.mts → redis-stream-D6HzR1Z_.d.mts} +1 -1
  95. package/dist/registry/index.d.mts +1 -1
  96. package/dist/registry/index.mjs +2 -2
  97. package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
  98. package/dist/{resourceToTools-CxNmI6xF.mjs → resourceToTools-DLL32us3.mjs} +224 -71
  99. package/dist/{routerShared-BqLRb5l7.mjs → routerShared-DrOa-26E.mjs} +41 -36
  100. package/dist/{schemaIR-Dy2p4MxS.mjs → schemaIR-lYhC2gE5.mjs} +1 -1
  101. package/dist/schemas/index.d.mts +100 -30
  102. package/dist/schemas/index.mjs +86 -29
  103. package/dist/scim/index.d.mts +264 -0
  104. package/dist/scim/index.mjs +963 -0
  105. package/dist/scope/index.d.mts +3 -3
  106. package/dist/scope/index.mjs +4 -4
  107. package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
  108. package/dist/{store-helpers-Cp4uKC1U.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
  109. package/dist/testing/index.d.mts +2 -8
  110. package/dist/testing/index.mjs +16 -24
  111. package/dist/types/index.d.mts +4 -4
  112. package/dist/{types-D7KpfiL1.d.mts → types-BvqwCCSx.d.mts} +73 -25
  113. package/dist/{types-DDyTPc6y.d.mts → types-CTYvcwHe.d.mts} +195 -1
  114. package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
  115. package/dist/{types-BQ9TJQNy.d.mts → types-DQHFc8PM.d.mts} +1 -1
  116. package/dist/utils/index.d.mts +2 -2
  117. package/dist/utils/index.mjs +5 -5
  118. package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
  119. package/dist/{versioning-DsglKfM_.d.mts → versioning-DTTvc80y.d.mts} +1 -1
  120. package/package.json +24 -34
  121. package/skills/arc/SKILL.md +147 -51
  122. package/skills/arc/references/agent-auth.md +238 -0
  123. package/skills/arc/references/api-reference.md +187 -0
  124. package/skills/arc/references/auth.md +354 -7
  125. package/skills/arc/references/enterprise-auth.md +94 -0
  126. package/skills/arc/references/events.md +8 -6
  127. package/skills/arc/references/mcp.md +2 -2
  128. package/skills/arc/references/multi-tenancy.md +11 -2
  129. package/skills/arc/references/production.md +10 -9
  130. package/skills/arc/references/scim.md +247 -0
  131. package/skills/arc/references/testing.md +1 -1
  132. package/skills/arc-code-review/SKILL.md +141 -0
  133. package/skills/arc-code-review/references/anti-patterns.md +911 -0
  134. package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
  135. package/skills/arc-code-review/references/migration-recipes.md +700 -0
  136. package/skills/arc-code-review/references/mongokit-migration.md +386 -0
  137. package/skills/arc-code-review/references/scaffolding.md +230 -0
  138. package/skills/arc-code-review/references/severity.md +127 -0
  139. package/dist/EventTransport-BFQjw9pB.mjs +0 -133
  140. package/dist/EventTransport-CYNUXdCJ.d.mts +0 -293
  141. package/dist/adapters/index.d.mts +0 -3
  142. package/dist/adapters/index.mjs +0 -2
  143. package/dist/adapters-DUUiiimH.mjs +0 -964
  144. package/dist/auth/mongoose.d.mts +0 -191
  145. package/dist/auth/mongoose.mjs +0 -73
  146. package/dist/core-CbcQRIch.mjs +0 -1054
  147. package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
  148. package/dist/errorHandler-DEWmGWPz.d.mts +0 -114
  149. package/dist/errors-D5c-5BJL.mjs +0 -232
  150. package/dist/index-Rg8axYPz.d.mts +0 -370
  151. package/dist/openapi-D7G1V7ex.mjs +0 -557
  152. /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
  153. /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
  154. /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
  155. /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
  156. /package/dist/{elevation-BQQXZ_VR.d.mts → elevation-BXOWoGCF.d.mts} +0 -0
  157. /package/dist/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
  158. /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
  159. /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
  160. /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
  161. /package/dist/{pluralize-CWP6MB39.mjs → pluralize-DQgqgifU.mjs} +0 -0
  162. /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
  163. /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
  164. /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.mjs} +0 -0
  165. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
  166. /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
  167. /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +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-Dy2p4MxS.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-DrOa-26E.mjs";
4
+ import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-lYhC2gE5.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-DrOa-26E.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
@@ -201,7 +202,7 @@ async function registerArcCore(fastify, config, trackPlugin) {
201
202
  await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
202
203
  trackPlugin("arc-core");
203
204
  if (config.arcPlugins?.events !== false) {
204
- const { default: eventPlugin } = await import("./eventPlugin-Cts2-Tfj.mjs").then((n) => n.n);
205
+ const { default: eventPlugin } = await import("./eventPlugin-CaKTYkYM.mjs").then((n) => n.n);
205
206
  const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
206
207
  await fastify.register(eventPlugin, {
207
208
  ...eventOpts,
@@ -237,15 +238,15 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
237
238
  trackPlugin("arc-graceful-shutdown");
238
239
  }
239
240
  if (config.arcPlugins?.caching) {
240
- 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);
241
242
  const opts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
242
243
  await fastify.register(cachingPlugin, opts);
243
244
  trackPlugin("arc-caching", opts);
244
245
  }
245
246
  if (config.arcPlugins?.queryCache) {
246
- const { queryCachePlugin } = await import("./queryCachePlugin-Bq6bO6vc.mjs").then((n) => n.n);
247
+ const { queryCachePlugin } = await import("./queryCachePlugin-m1XsgAIJ.mjs").then((n) => n.n);
247
248
  const opts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
248
- 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();
249
250
  await fastify.register(queryCachePlugin, {
250
251
  store,
251
252
  ...opts
@@ -254,19 +255,19 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
254
255
  }
255
256
  if (config.arcPlugins?.sse) if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
256
257
  else {
257
- 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);
258
259
  const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
259
260
  await fastify.register(ssePlugin, opts);
260
261
  trackPlugin("arc-sse", opts);
261
262
  }
262
263
  if (config.arcPlugins?.metrics) {
263
- 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);
264
265
  const opts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
265
266
  await fastify.register(metricsPlugin, opts);
266
267
  trackPlugin("arc-metrics", opts);
267
268
  }
268
269
  if (config.arcPlugins?.versioning) {
269
- 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);
270
271
  await fastify.register(versioningPlugin, config.arcPlugins.versioning);
271
272
  trackPlugin("arc-versioning", config.arcPlugins.versioning);
272
273
  }
@@ -335,7 +336,7 @@ async function registerAuth(fastify, config, trackPlugin) {
335
336
  */
336
337
  async function registerElevation(fastify, config, trackPlugin) {
337
338
  if (!config.elevation) return;
338
- const { elevationPlugin } = await import("./elevation-DOFoxoDs.mjs").then((n) => n.r);
339
+ const { elevationPlugin } = await import("./elevation-DgoeTyfX.mjs").then((n) => n.r);
339
340
  await fastify.register(elevationPlugin, config.elevation);
340
341
  trackPlugin("arc-elevation", config.elevation);
341
342
  fastify.log.debug("Elevation plugin enabled");
@@ -345,7 +346,7 @@ async function registerElevation(fastify, config, trackPlugin) {
345
346
  */
346
347
  async function registerErrorHandler(fastify, config, trackPlugin) {
347
348
  if (config.errorHandler === false) return;
348
- const { errorHandlerPlugin } = await import("./errorHandler-BQm8ZxTK.mjs").then((n) => n.r);
349
+ const { errorHandlerPlugin } = await import("./errorHandler-Bk-AGhkU.mjs").then((n) => n.r);
349
350
  const errorOpts = typeof config.errorHandler === "object" ? config.errorHandler : { includeStack: config.preset !== "production" };
350
351
  await fastify.register(errorHandlerPlugin, errorOpts);
351
352
  trackPlugin("arc-error-handler", errorOpts);
@@ -399,7 +400,7 @@ async function registerOne(parent, resource) {
399
400
  }
400
401
  /**
401
402
  * Execute the full resource lifecycle:
402
- * 1. plugins() — infra (DB, docs, webhooks)
403
+ * 1. plugins() — infra (DB, data, webhooks)
403
404
  * 2. bootstrap[] — domain init (singletons, event handlers)
404
405
  * 3. resources factory (if any) — resolved AFTER bootstrap, so engine-backed
405
406
  * adapters can `await ensureEngine()` and pass
@@ -445,7 +446,7 @@ async function registerResources(fastify, config) {
445
446
  let discoveryPath;
446
447
  let discoveryYieldedZero = false;
447
448
  if (resolvedResources === void 0 && config.resourceDir) {
448
- const { loadResources } = await import("./loadResources-CPpkyKfM.mjs").then((n) => n.n);
449
+ const { loadResources } = await import("./loadResources-DBMQg_Aj.mjs").then((n) => n.n);
449
450
  const { resolve, dirname } = await import("node:path");
450
451
  const { fileURLToPath } = await import("node:url");
451
452
  const rawDir = config.resourceDir;
@@ -745,7 +746,7 @@ function validateDistributedRuntime(options) {
745
746
  * 4. Arc core (fastify.arc, events)
746
747
  * 5. Arc plugins (requestId, health, caching, SSE, metrics, versioning)
747
748
  * 6. Auth (scope decoration, auth strategy, elevation, error handler)
748
- * 7. plugins() — user infra (DB, docs, webhooks)
749
+ * 7. plugins() — user infra (DB, data, webhooks)
749
750
  * 8. bootstrap[] — domain init (singletons, event handlers)
750
751
  * 9. resources[] — auto-discovered routes (prefix + skipGlobalPrefix)
751
752
  * 10. afterResources() — post-registration wiring
@@ -810,17 +811,22 @@ async function createApp(options) {
810
811
  await registerErrorHandler(fastify, config, trackPlugin);
811
812
  await registerResources(fastify, config);
812
813
  if (config.replyHelpers) {
813
- const { replyHelpersPlugin } = await import("./replyHelpers-ByllIXXV.mjs").then((n) => n.n);
814
+ const { replyHelpersPlugin } = await import("./replyHelpers-CK-FNO8E.mjs").then((n) => n.n);
814
815
  await fastify.register(replyHelpersPlugin);
815
816
  }
816
- if (config.serializeBigInt) fastify.addHook("preSerialization", async (_request, _reply, payload) => {
817
- if (payload === null || payload === void 0) return payload;
818
- try {
819
- return JSON.parse(JSON.stringify(payload, (_key, value) => typeof value === "bigint" ? Number(value) : value));
820
- } catch {
821
- return payload;
822
- }
823
- });
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
+ }
824
830
  const authMode = config.auth === false ? "none" : config.auth ? config.auth.type : "none";
825
831
  fastify.log.info({
826
832
  preset: config.preset ?? "custom",
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { r as createEvent } from "./EventTransport-BFQjw9pB.mjs";
2
+ import { n as createEvent } from "./EventTransport-DLWoUMHy.mjs";
3
3
  //#region src/events/defineEvent.ts
4
4
  /**
5
5
  * defineEvent — Typed Event Definitions with Optional Schema Validation
@@ -1,8 +1,14 @@
1
- import { p as RegistryEntry } from "../index-CXXRbnf8.mjs";
1
+ import { p as RegistryEntry } from "../index-BtW7qYwa.mjs";
2
2
  import { t as ExternalOpenApiPaths } from "../externalPaths-BD5nw6St.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
5
- //#region src/docs/openapi.d.ts
5
+ //#region src/docs/openapi/types.d.ts
6
+ /**
7
+ * OpenAPI 3.0 type primitives used by arc's spec emitter.
8
+ *
9
+ * Internal to `src/docs/openapi/*` — public exports are surfaced via
10
+ * `src/docs/index.ts` (which re-exports `OpenApiSpec` only).
11
+ */
6
12
  interface OpenApiOptions {
7
13
  /** API title */
8
14
  title?: string;
@@ -23,6 +29,13 @@ interface OpenApiOptions {
23
29
  /** Custom OpenAPI extensions */
24
30
  extensions?: Record<string, unknown>;
25
31
  }
32
+ interface OpenApiBuildOptions {
33
+ title?: string;
34
+ version?: string;
35
+ description?: string;
36
+ serverUrl?: string;
37
+ apiPrefix?: string;
38
+ }
26
39
  interface OpenApiSpec {
27
40
  openapi: string;
28
41
  info: {
@@ -45,13 +58,6 @@ interface OpenApiSpec {
45
58
  }>;
46
59
  security?: Array<Record<string, string[]>>;
47
60
  }
48
- interface OpenApiBuildOptions {
49
- title?: string;
50
- version?: string;
51
- description?: string;
52
- serverUrl?: string;
53
- apiPrefix?: string;
54
- }
55
61
  interface PathItem {
56
62
  get?: Operation;
57
63
  post?: Operation;
@@ -101,7 +107,7 @@ interface Response {
101
107
  }>;
102
108
  }
103
109
  interface SchemaObject {
104
- type?: string;
110
+ type?: string | string[];
105
111
  format?: string;
106
112
  properties?: Record<string, SchemaObject>;
107
113
  items?: SchemaObject;
@@ -110,12 +116,17 @@ interface SchemaObject {
110
116
  description?: string;
111
117
  example?: unknown;
112
118
  additionalProperties?: boolean | SchemaObject;
113
- enum?: string[];
119
+ enum?: (string | number | boolean | null)[];
114
120
  minimum?: number;
115
121
  maximum?: number;
116
122
  minLength?: number;
117
123
  maxLength?: number;
118
124
  pattern?: string;
125
+ oneOf?: SchemaObject[];
126
+ anyOf?: SchemaObject[];
127
+ allOf?: SchemaObject[];
128
+ default?: unknown;
129
+ nullable?: boolean;
119
130
  }
120
131
  interface SecurityScheme {
121
132
  type: string;
@@ -124,6 +135,8 @@ interface SecurityScheme {
124
135
  in?: string;
125
136
  name?: string;
126
137
  }
138
+ //#endregion
139
+ //#region src/docs/openapi/index.d.ts
127
140
  declare const openApiPlugin: FastifyPluginAsync<OpenApiOptions>;
128
141
  /**
129
142
  * Build OpenAPI spec from registry resources.
@@ -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-D7G1V7ex.mjs";
1
+ import { t as getUserRoles } from "../types-D57iXYb8.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-noXno2CV.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({
@@ -0,0 +1,174 @@
1
+ import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
+ import { m as statusToArcCode, p as isArcError } from "./errors-j4aJm1Wg.mjs";
3
+ import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
4
+ import fp from "fastify-plugin";
5
+ //#region src/plugins/errorHandler.ts
6
+ var errorHandler_exports = /* @__PURE__ */ __exportAll({
7
+ defaultIsDuplicateKeyError: () => defaultIsDuplicateKeyError,
8
+ errorHandlerPlugin: () => errorHandlerPlugin
9
+ });
10
+ /**
11
+ * Default duplicate-key detector. Strict driver-code matching only — never
12
+ * message strings (false positives mask real WriteConflict / NotWritable
13
+ * errors as 409s). Long-tail drivers compose: see jsdoc on
14
+ * {@link ErrorHandlerOptions.isDuplicateKeyError}.
15
+ */
16
+ function defaultIsDuplicateKeyError(err) {
17
+ if (!err || typeof err !== "object") return false;
18
+ const e = err;
19
+ if (e.code === 11e3 || e.codeName === "DuplicateKey") return true;
20
+ if (e.code === "P2002") return true;
21
+ if (e.code === "23505") return true;
22
+ if (e.code === "ER_DUP_ENTRY" || e.errno === 1062) return true;
23
+ if (e.code === "SQLITE_CONSTRAINT_UNIQUE" || e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") return true;
24
+ return false;
25
+ }
26
+ function extractDuplicateFields(err) {
27
+ if (!err || typeof err !== "object") return null;
28
+ const e = err;
29
+ if (e.keyValue && typeof e.keyValue === "object") return Object.keys(e.keyValue);
30
+ if (e.meta?.target) {
31
+ if (Array.isArray(e.meta.target)) return e.meta.target.map(String);
32
+ if (typeof e.meta.target === "string") return [e.meta.target];
33
+ }
34
+ if (typeof e.constraint === "string") return [e.constraint];
35
+ return null;
36
+ }
37
+ /** Map Fastify schema-validation errors → canonical `ErrorDetail[]`. */
38
+ function fastifyValidationDetails(error) {
39
+ return (error.validation ?? []).map((v) => {
40
+ const missingProperty = v.params?.missingProperty;
41
+ const path = v.instancePath?.replace(/^\//, "") || (typeof missingProperty === "string" ? missingProperty : void 0);
42
+ return {
43
+ ...path ? { path } : {},
44
+ code: v.keyword ?? "invalid",
45
+ message: v.message || "Invalid value"
46
+ };
47
+ });
48
+ }
49
+ /** Map Mongoose `ValidationError.errors` → canonical `ErrorDetail[]`. */
50
+ function mongooseValidationDetails(errors) {
51
+ return Object.entries(errors).map(([field, e]) => ({
52
+ path: e.path || field,
53
+ code: "validation_error",
54
+ message: e.message
55
+ }));
56
+ }
57
+ async function errorHandlerPluginFn(fastify, options = {}) {
58
+ const isProduction = process.env.NODE_ENV === "production";
59
+ const { includeStack = !isProduction, onError, errorMap = {}, errorMappers = [], isDuplicateKeyError = defaultIsDuplicateKeyError } = options;
60
+ fastify.setErrorHandler(async (error, request, reply) => {
61
+ if (onError) try {
62
+ await onError(error, request);
63
+ } catch (callbackError) {
64
+ request.log.error({ err: callbackError }, "Error in onError callback");
65
+ }
66
+ const correlationId = request.id;
67
+ const contract = classify(error, {
68
+ errorMappers,
69
+ errorMap,
70
+ isDuplicateKeyError
71
+ });
72
+ const meta = { ...contract.meta ?? {} };
73
+ if (includeStack && error.stack) meta.stack = error.stack;
74
+ const wire = {
75
+ ...contract,
76
+ ...correlationId ? { correlationId } : {},
77
+ ...Object.keys(meta).length > 0 ? { meta } : {}
78
+ };
79
+ const status = wire.status ?? 500;
80
+ if (status >= 500) request.log.error({
81
+ err: error,
82
+ status
83
+ }, "Server error");
84
+ else if (status >= 400) request.log.warn({
85
+ err: error,
86
+ status
87
+ }, "Client error");
88
+ if (isArcError(error) && error.cause) request.log.error({ cause: error.cause }, "Error cause chain");
89
+ return reply.status(status).send(wire);
90
+ });
91
+ }
92
+ /**
93
+ * Single-pass error → `ErrorContract` classifier. Dispatch order is fixed:
94
+ *
95
+ * 1. Class-based mappers (`instanceof`) — user-registered domain errors
96
+ * 2. `ArcError` / any `HttpError`-shaped throw — flows through `toErrorContract`
97
+ * 3. Fastify schema-validation errors — `error.validation` array
98
+ * 4. Fastify-style errors with a numeric `statusCode`
99
+ * 5. Name-keyed `errorMap` entries
100
+ * 6. Mongoose `ValidationError` / `CastError`
101
+ * 7. Driver-specific duplicate-key classifier
102
+ * 8. Fallback: `arc.internal_error` 500
103
+ */
104
+ function classify(error, ctx) {
105
+ for (const mapper of ctx.errorMappers) if (error instanceof mapper.type) {
106
+ const mapped = mapper.toResponse(error);
107
+ return {
108
+ code: mapped.code ?? statusToArcCode(mapped.status),
109
+ message: mapped.message ?? error.message,
110
+ status: mapped.status,
111
+ ...mapped.details ? { details: mapped.details } : {},
112
+ ...mapped.meta ? { meta: mapped.meta } : {}
113
+ };
114
+ }
115
+ if (isArcError(error) || isHttpError(error)) return toErrorContract(error);
116
+ const fastifyErr = error;
117
+ if (Array.isArray(fastifyErr.validation)) return {
118
+ code: "arc.validation_error",
119
+ message: "Validation failed",
120
+ status: 400,
121
+ details: fastifyValidationDetails(fastifyErr)
122
+ };
123
+ if (typeof fastifyErr.statusCode === "number") return {
124
+ code: statusToArcCode(fastifyErr.statusCode),
125
+ message: error.message || "Error",
126
+ status: fastifyErr.statusCode
127
+ };
128
+ if (error.name && ctx.errorMap[error.name]) {
129
+ const m = ctx.errorMap[error.name];
130
+ return {
131
+ code: m.code,
132
+ message: m.message ?? error.message,
133
+ status: m.statusCode
134
+ };
135
+ }
136
+ if (error.name === "ValidationError" && "errors" in error) {
137
+ const errs = error.errors;
138
+ return {
139
+ code: "arc.validation_error",
140
+ message: error.message || "Validation failed",
141
+ status: 400,
142
+ details: mongooseValidationDetails(errs)
143
+ };
144
+ }
145
+ if (error.name === "CastError") return {
146
+ code: "arc.invalid_id",
147
+ message: "Invalid identifier format",
148
+ status: 400
149
+ };
150
+ if (ctx.isDuplicateKeyError(error)) {
151
+ const fields = extractDuplicateFields(error);
152
+ return {
153
+ code: "arc.conflict",
154
+ message: "Resource already exists",
155
+ status: 409,
156
+ ...fields && fields.length > 0 ? { details: fields.map((f) => ({
157
+ path: f,
158
+ code: "duplicate_key",
159
+ message: `Duplicate value for "${f}"`
160
+ })) } : {}
161
+ };
162
+ }
163
+ return {
164
+ code: "arc.internal_error",
165
+ message: error.message || "Internal Server Error",
166
+ status: 500
167
+ };
168
+ }
169
+ const errorHandlerPlugin = fp(errorHandlerPluginFn, {
170
+ name: "arc-error-handler",
171
+ fastify: "5.x"
172
+ });
173
+ //#endregion
174
+ export { errorHandlerPlugin as n, errorHandler_exports as r, defaultIsDuplicateKeyError as t };
@@ -0,0 +1,45 @@
1
+ import { ErrorDetail } from "@classytic/repo-core/errors";
2
+ import { FastifyInstance, FastifyRequest } from "fastify";
3
+
4
+ //#region src/plugins/errorHandler.d.ts
5
+ /**
6
+ * Class-based error mapper — `instanceof` check converts a thrown class
7
+ * to a partial `ErrorContract`. Highest-priority dispatch in the handler.
8
+ */
9
+ interface ErrorMapper<T extends Error = Error> {
10
+ type: abstract new (...args: any[]) => T;
11
+ toResponse: (error: T) => {
12
+ status: number;
13
+ code?: string;
14
+ message?: string;
15
+ details?: ReadonlyArray<ErrorDetail>;
16
+ meta?: Record<string, unknown>;
17
+ };
18
+ }
19
+ interface ErrorHandlerOptions {
20
+ /** Include `meta.stack` on the wire (defaults to `NODE_ENV !== 'production'`). */
21
+ includeStack?: boolean;
22
+ /** Custom callback fired for every error — log to Sentry / Datadog / etc. */
23
+ onError?: (error: Error, request: FastifyRequest) => void | Promise<void>;
24
+ /** Map by `error.name` string. Lower priority than `errorMappers`. */
25
+ errorMap?: Record<string, {
26
+ statusCode: number;
27
+ code: string;
28
+ message?: string;
29
+ }>;
30
+ /** Map by `instanceof`. Highest priority — checked first. */
31
+ errorMappers?: ErrorMapper[];
32
+ /** Driver-aware duplicate-key classifier. Override to add long-tail drivers. */
33
+ isDuplicateKeyError?: (err: unknown) => boolean;
34
+ }
35
+ /**
36
+ * Default duplicate-key detector. Strict driver-code matching only — never
37
+ * message strings (false positives mask real WriteConflict / NotWritable
38
+ * errors as 409s). Long-tail drivers compose: see jsdoc on
39
+ * {@link ErrorHandlerOptions.isDuplicateKeyError}.
40
+ */
41
+ declare function defaultIsDuplicateKeyError(err: unknown): boolean;
42
+ declare function errorHandlerPluginFn(fastify: FastifyInstance, options?: ErrorHandlerOptions): Promise<void>;
43
+ declare const errorHandlerPlugin: typeof errorHandlerPluginFn;
44
+ //#endregion
45
+ export { errorHandlerPlugin as i, ErrorMapper as n, defaultIsDuplicateKeyError as r, ErrorHandlerOptions as t };