@classytic/arc 2.7.7 → 2.8.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 (119) hide show
  1. package/README.md +11 -2
  2. package/dist/{BaseController-CpMfCXdn.mjs → BaseController-DAGGc5Xn.mjs} +76 -25
  3. package/dist/{EventTransport-C4VheKeC.d.mts → EventTransport-CLXJUzyT.d.mts} +37 -1
  4. package/dist/{ResourceRegistry-DsHiG9cL.mjs → ResourceRegistry-Dtcojmu8.mjs} +14 -2
  5. package/dist/adapters/index.d.mts +2 -2
  6. package/dist/adapters/index.mjs +1 -1
  7. package/dist/{adapters-BxGgSHjj.mjs → adapters-BBqAVvPK.mjs} +11 -0
  8. package/dist/audit/index.d.mts +1 -1
  9. package/dist/audit/index.mjs +1 -1
  10. package/dist/audit/mongodb.d.mts +1 -1
  11. package/dist/audit/mongodb.mjs +1 -1
  12. package/dist/auth/index.d.mts +4 -4
  13. package/dist/auth/index.mjs +3 -3
  14. package/dist/auth/redis-session.d.mts +1 -1
  15. package/dist/{betterAuthOpenApi-EkPaMWNM.mjs → betterAuthOpenApi-C5lDyRH2.mjs} +1 -1
  16. package/dist/cache/index.d.mts +2 -2
  17. package/dist/cache/index.mjs +1 -1
  18. package/dist/cli/commands/docs.mjs +2 -2
  19. package/dist/cli/commands/generate.mjs +1 -1
  20. package/dist/cli/commands/introspect.mjs +1 -1
  21. package/dist/core/index.d.mts +2 -2
  22. package/dist/core/index.mjs +4 -3
  23. package/dist/core-CrLDuqoT.mjs +34 -0
  24. package/dist/{core-B_zEeA2b.mjs → createActionRouter-Df1BuawX.mjs} +88 -52
  25. package/dist/{createApp-D7e77m8C.mjs → createApp-p2OThysU.mjs} +10 -10
  26. package/dist/{defineResource-BW2dMCu9.mjs → defineResource-CqeUltrW.mjs} +91 -8
  27. package/dist/docs/index.d.mts +2 -2
  28. package/dist/docs/index.mjs +1 -1
  29. package/dist/dynamic/index.d.mts +2 -2
  30. package/dist/dynamic/index.mjs +1 -1
  31. package/dist/{elevation-By_p2lnn.mjs → elevation-BBGFjzIP.mjs} +1 -1
  32. package/dist/{errorHandler-CH8wk1eD.mjs → errorHandler-Cw34h_om.mjs} +1 -1
  33. package/dist/{errorHandler-pCpEtNd7.d.mts → errorHandler-DJ7OAB2V.d.mts} +1 -1
  34. package/dist/{eventPlugin-CdvUoUna.d.mts → eventPlugin-Cdjwo0Gv.d.mts} +1 -1
  35. package/dist/{eventPlugin-B6U_nCFU.mjs → eventPlugin-XijlQmlL.mjs} +19 -1
  36. package/dist/events/index.d.mts +399 -28
  37. package/dist/events/index.mjs +345 -29
  38. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  39. package/dist/events/transports/redis.d.mts +1 -1
  40. package/dist/factory/index.d.mts +1 -1
  41. package/dist/factory/index.mjs +1 -1
  42. package/dist/hooks/index.d.mts +1 -1
  43. package/dist/hooks/index.mjs +1 -1
  44. package/dist/idempotency/index.d.mts +3 -3
  45. package/dist/idempotency/mongodb.d.mts +1 -1
  46. package/dist/idempotency/redis.d.mts +1 -1
  47. package/dist/{index-C9eYNjGR.d.mts → index-0zj73o2U.d.mts} +1 -1
  48. package/dist/{index-B0extFr4.d.mts → index-CBru2y5Y.d.mts} +3 -3
  49. package/dist/{index-BjShrzoj.d.mts → index-DadoLP51.d.mts} +48 -16
  50. package/dist/index.d.mts +8 -8
  51. package/dist/index.mjs +8 -8
  52. package/dist/integrations/event-gateway.d.mts +1 -1
  53. package/dist/integrations/event-gateway.mjs +1 -1
  54. package/dist/integrations/index.d.mts +1 -1
  55. package/dist/integrations/mcp/index.d.mts +2 -2
  56. package/dist/integrations/mcp/index.mjs +1 -1
  57. package/dist/integrations/mcp/testing.d.mts +1 -1
  58. package/dist/integrations/mcp/testing.mjs +1 -1
  59. package/dist/{interface-B91alUzq.d.mts → interface-CS6d7HiB.d.mts} +693 -110
  60. package/dist/{mongodb-Cgu9F1Nd.d.mts → mongodb-B1eVtFhw.d.mts} +1 -1
  61. package/dist/{mongodb-B7zupyck.d.mts → mongodb-NShVZDMr.d.mts} +1 -1
  62. package/dist/{openapi-D7Z7VODz.mjs → openapi-q6rNKfZy.mjs} +49 -2
  63. package/dist/org/index.d.mts +2 -2
  64. package/dist/permissions/index.d.mts +3 -3
  65. package/dist/plugins/index.d.mts +4 -4
  66. package/dist/plugins/index.mjs +9 -9
  67. package/dist/plugins/tracing-entry.d.mts +1 -1
  68. package/dist/plugins/tracing-entry.mjs +1 -1
  69. package/dist/policies/index.d.mts +1 -1
  70. package/dist/presets/index.d.mts +3 -3
  71. package/dist/presets/multiTenant.d.mts +1 -1
  72. package/dist/{queryCachePlugin-Ckl71mkc.d.mts → queryCachePlugin-BCFVXnxK.d.mts} +1 -1
  73. package/dist/{redis-3TQxm2VZ.d.mts → redis-Bunu3qWg.d.mts} +1 -1
  74. package/dist/{redis-stream-Dag5LFa9.d.mts → redis-stream-BgrYzpeq.d.mts} +1 -1
  75. package/dist/registry/index.d.mts +1 -1
  76. package/dist/registry/index.mjs +2 -2
  77. package/dist/{resourceToTools-BJkoQoUP.mjs → resourceToTools-DNNWnZtx.mjs} +194 -64
  78. package/dist/rpc/index.d.mts +1 -1
  79. package/dist/rpc/index.mjs +1 -1
  80. package/dist/scope/index.d.mts +2 -2
  81. package/dist/scope/index.mjs +1 -1
  82. package/dist/{sse-6W0hjVS_.mjs → sse-CD5Hghpu.mjs} +1 -1
  83. package/dist/testing/index.d.mts +2 -2
  84. package/dist/testing/index.mjs +1 -1
  85. package/dist/types/index.d.mts +5 -5
  86. package/dist/{types-CKB47kiu.d.mts → types-BlOuKTPw.d.mts} +9 -9
  87. package/dist/{types-B4BNthET.d.mts → types-BoaZHr-2.d.mts} +1 -1
  88. package/dist/{types-C5g2oRC7.d.mts → types-D3b7hA00.d.mts} +1 -1
  89. package/dist/utils/index.d.mts +4 -16
  90. package/dist/utils/index.mjs +5 -5
  91. package/dist/{utils-B-l6410F.mjs → utils-7sJ8X83I.mjs} +1 -13
  92. package/package.json +6 -5
  93. package/skills/arc/SKILL.md +23 -4
  94. package/skills/arc/references/integrations.md +1 -1
  95. package/skills/arc/references/mcp.md +2 -0
  96. /package/dist/{HookSystem-BNYKnrXF.mjs → HookSystem-BjFu7zf1.mjs} +0 -0
  97. /package/dist/{caching-5DtLwIqb.mjs → caching-CHH-iHs3.mjs} +0 -0
  98. /package/dist/{circuitBreaker-BBPDt-J_.d.mts → circuitBreaker-BGVoB1hD.d.mts} +0 -0
  99. /package/dist/{circuitBreaker-l18oRgL5.mjs → circuitBreaker-cmi5XDv5.mjs} +0 -0
  100. /package/dist/{elevation-D7WK0RXq.d.mts → elevation-UJO3-NvX.d.mts} +0 -0
  101. /package/dist/{errors-Cg58SLNi.mjs → errors-BF2bIOIS.mjs} +0 -0
  102. /package/dist/{errors-BS6lZvWy.d.mts → errors-BI8kEKsO.d.mts} +0 -0
  103. /package/dist/{externalPaths-iba7jD3d.d.mts → externalPaths-BQ8QijNH.d.mts} +0 -0
  104. /package/dist/{fields-D4nMDqnK.d.mts → fields-DoeDgh2b.d.mts} +0 -0
  105. /package/dist/{interface-CSbZdv_3.d.mts → interface-CkkWm5uR.d.mts} +0 -0
  106. /package/dist/{interface-CG7oRZjX.d.mts → interface-bpoLKKqx.d.mts} +0 -0
  107. /package/dist/{logger-DLg8-Ueg.mjs → logger-CDjpjySd.mjs} +0 -0
  108. /package/dist/{metrics-Qnvwc-LQ.mjs → metrics-DuhiSEZI.mjs} +0 -0
  109. /package/dist/{mongodb-B7X7P1P8.mjs → mongodb-5Ff3w8jy.mjs} +0 -0
  110. /package/dist/{pluralize-Dckfq6US.mjs → pluralize-BneOJkpi.mjs} +0 -0
  111. /package/dist/{queryCachePlugin-CwTpR04-.mjs → queryCachePlugin-D0iIVhW_.mjs} +0 -0
  112. /package/dist/{registry-B3lRFBWo.mjs → registry-B0Wl7uVV.mjs} +0 -0
  113. /package/dist/{replyHelpers-uDUIYh7u.mjs → replyHelpers-CXtJDAZ0.mjs} +0 -0
  114. /package/dist/{requestContext-xHIKedG6.mjs → requestContext-DYvHl113.mjs} +0 -0
  115. /package/dist/{schemaConverter-Y5EejTnJ.mjs → schemaConverter-OxfCshus.mjs} +0 -0
  116. /package/dist/{sessionManager-CEo9jwPI.d.mts → sessionManager-BkzVU8h2.d.mts} +0 -0
  117. /package/dist/{tracing-DEqdGkr-.d.mts → tracing-xqXzWeaf.d.mts} +0 -0
  118. /package/dist/{types--D3vvfdt.d.mts → types-CN6JvmYz.d.mts} +0 -0
  119. /package/dist/{versioning-CdBbFefk.mjs → versioning-CPU_5Xfs.mjs} +0 -0
@@ -1,6 +1,11 @@
1
+ import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
1
2
  import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-D6GPMsvh.mjs";
2
- import { n as defineResource } from "./defineResource-BW2dMCu9.mjs";
3
+ import { a as toJsonSchema } from "./schemaConverter-OxfCshus.mjs";
3
4
  //#region src/core/createActionRouter.ts
5
+ var createActionRouter_exports = /* @__PURE__ */ __exportAll({
6
+ buildActionBodySchema: () => buildActionBodySchema,
7
+ createActionRouter: () => createActionRouter
8
+ });
4
9
  /**
5
10
  * Create action-based state transition endpoint
6
11
  *
@@ -17,19 +22,7 @@ function createActionRouter(fastify, config) {
17
22
  fastify.log.warn("[createActionRouter] No actions defined, skipping route creation");
18
23
  return;
19
24
  }
20
- const bodyProperties = { action: {
21
- type: "string",
22
- enum: actionEnum,
23
- description: `Action to perform: ${actionEnum.join(" | ")}`
24
- } };
25
- Object.entries(actionSchemas).forEach(([actionName, schema]) => {
26
- if (schema && typeof schema === "object") Object.entries(schema).forEach(([propName, propSchema]) => {
27
- bodyProperties[propName] = {
28
- ...propSchema,
29
- description: `${propSchema.description || ""} (for ${actionName} action)`.trim()
30
- };
31
- });
32
- });
25
+ const bodySchema = buildActionBodySchema(actionEnum, actionSchemas);
33
26
  const routeSchema = {
34
27
  tags: tag ? [tag] : void 0,
35
28
  summary: `Perform action (${actionEnum.join("/")})`,
@@ -42,11 +35,7 @@ function createActionRouter(fastify, config) {
42
35
  } },
43
36
  required: ["id"]
44
37
  },
45
- body: {
46
- type: "object",
47
- properties: bodyProperties,
48
- required: ["action"]
49
- }
38
+ body: bodySchema
50
39
  };
51
40
  const preHandler = [];
52
41
  const hasPublicActions = Object.entries(actionPermissions).some(([, p]) => p?._isPublic) || globalAuth && globalAuth?._isPublic;
@@ -165,6 +154,85 @@ function createActionRouter(fastify, config) {
165
154
  }, "[createActionRouter] Registered action endpoint: POST /:id/action");
166
155
  }
167
156
  /**
157
+ * Build a discriminated body schema for the unified action endpoint.
158
+ *
159
+ * Produces a schema of the form:
160
+ * ```json
161
+ * {
162
+ * "type": "object",
163
+ * "required": ["action"],
164
+ * "oneOf": [
165
+ * { "properties": { "action": { "const": "dispatch" }, "carrier": {...} }, "required": ["action", "carrier"] },
166
+ * { "properties": { "action": { "const": "approve" } }, "required": ["action"] }
167
+ * ]
168
+ * }
169
+ * ```
170
+ *
171
+ * AJV validates this natively, so an action call missing required fields is
172
+ * rejected with HTTP 400 before the handler ever runs.
173
+ *
174
+ * Exported so OpenAPI generation and MCP tool generation can reuse the same
175
+ * schema shape (single source of truth).
176
+ */
177
+ function buildActionBodySchema(actionEnum, actionSchemas = {}) {
178
+ const branches = [];
179
+ for (const actionName of actionEnum) {
180
+ const raw = actionSchemas[actionName];
181
+ const { properties, required } = normalizeActionSchema(raw);
182
+ const branchProperties = {
183
+ action: {
184
+ type: "string",
185
+ const: actionName
186
+ },
187
+ ...properties
188
+ };
189
+ const branchRequired = ["action", ...required.filter((r) => r !== "action")];
190
+ branches.push({
191
+ type: "object",
192
+ properties: branchProperties,
193
+ required: branchRequired
194
+ });
195
+ }
196
+ return {
197
+ type: "object",
198
+ required: ["action"],
199
+ oneOf: branches
200
+ };
201
+ }
202
+ /**
203
+ * Normalize the accepted schema shapes into `{ properties, required }`.
204
+ *
205
+ * Handles:
206
+ * 1. Full JSON Schema object (has `type: 'object'` + `properties`)
207
+ * 2. Zod v4 schema (has `_zod` marker) — converted via `toJsonSchema`
208
+ * 3. Legacy field map (`{ fieldName: { type: 'string' } }`) — every field required
209
+ * unless its schema has `nullable: true` or sentinel `required: false`
210
+ */
211
+ function normalizeActionSchema(raw) {
212
+ if (!raw || typeof raw !== "object") return {
213
+ properties: {},
214
+ required: []
215
+ };
216
+ const converted = toJsonSchema(raw);
217
+ if (converted && typeof converted === "object" && (converted.type === "object" || "properties" in converted)) return {
218
+ properties: converted.properties ?? {},
219
+ required: Array.isArray(converted.required) ? converted.required : []
220
+ };
221
+ const properties = {};
222
+ const required = [];
223
+ for (const [fieldName, fieldSchema] of Object.entries(raw)) {
224
+ if (fieldName === "type" || fieldName === "properties" || fieldName === "required") continue;
225
+ if (!fieldSchema || typeof fieldSchema !== "object") continue;
226
+ const fs = fieldSchema;
227
+ properties[fieldName] = fs;
228
+ if (fs.required !== false) required.push(fieldName);
229
+ }
230
+ return {
231
+ properties,
232
+ required
233
+ };
234
+ }
235
+ /**
168
236
  * Build description with action details
169
237
  * Uses _roles metadata from PermissionCheck functions for OpenAPI docs
170
238
  */
@@ -178,36 +246,4 @@ function buildActionDescription(actions, actionPermissions) {
178
246
  return lines.join("\n");
179
247
  }
180
248
  //#endregion
181
- //#region src/core/defineResourceVariants.ts
182
- /**
183
- * Define multiple resources from a shared base config and per-variant overrides.
184
- *
185
- * Each variant is independently passed through `defineResource()` — the
186
- * returned `ResourceDefinition`s are real, fully-registered resources.
187
- * Register each one's plugin in your app:
188
- *
189
- * ```typescript
190
- * await app.register(articlePublic.toPlugin());
191
- * await app.register(articleAdmin.toPlugin());
192
- * ```
193
- *
194
- * @param base Shared config — adapter, queryParser, schemaOptions, hooks, etc.
195
- * Must NOT include `name` or `prefix` (those are per-variant).
196
- * @param variants Map of variant key → override. Each variant must declare
197
- * its own `name` and `prefix`. Other fields override the base.
198
- * @returns A record where each key from `variants` maps to a real
199
- * `ResourceDefinition` ready for `.toPlugin()` registration.
200
- */
201
- function defineResourceVariants(base, variants) {
202
- const out = {};
203
- for (const key of Object.keys(variants)) {
204
- const override = variants[key];
205
- out[key] = defineResource({
206
- ...base,
207
- ...override
208
- });
209
- }
210
- return out;
211
- }
212
- //#endregion
213
- export { createActionRouter as n, defineResourceVariants as t };
249
+ export { createActionRouter as n, createActionRouter_exports as r, buildActionBodySchema as t };
@@ -207,7 +207,7 @@ async function registerArcCore(fastify, config, trackPlugin) {
207
207
  await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
208
208
  trackPlugin("arc-core");
209
209
  if (config.arcPlugins?.events !== false) {
210
- const { default: eventPlugin } = await import("./eventPlugin-B6U_nCFU.mjs").then((n) => n.n);
210
+ const { default: eventPlugin } = await import("./eventPlugin-XijlQmlL.mjs").then((n) => n.n);
211
211
  const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
212
212
  await fastify.register(eventPlugin, {
213
213
  ...eventOpts,
@@ -243,13 +243,13 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
243
243
  trackPlugin("arc-graceful-shutdown");
244
244
  }
245
245
  if (config.arcPlugins?.caching) {
246
- const { default: cachingPlugin } = await import("./caching-5DtLwIqb.mjs").then((n) => n.r);
246
+ const { default: cachingPlugin } = await import("./caching-CHH-iHs3.mjs").then((n) => n.r);
247
247
  const opts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
248
248
  await fastify.register(cachingPlugin, opts);
249
249
  trackPlugin("arc-caching", opts);
250
250
  }
251
251
  if (config.arcPlugins?.queryCache) {
252
- const { queryCachePlugin } = await import("./queryCachePlugin-CwTpR04-.mjs").then((n) => n.n);
252
+ const { queryCachePlugin } = await import("./queryCachePlugin-D0iIVhW_.mjs").then((n) => n.n);
253
253
  const opts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
254
254
  const store = config.stores?.queryCache ?? new (await (import("./memory-Cp7_cAko.mjs").then((n) => n.n))).MemoryCacheStore();
255
255
  await fastify.register(queryCachePlugin, {
@@ -260,19 +260,19 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
260
260
  }
261
261
  if (config.arcPlugins?.sse) if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
262
262
  else {
263
- const { default: ssePlugin } = await import("./sse-6W0hjVS_.mjs").then((n) => n.r);
263
+ const { default: ssePlugin } = await import("./sse-CD5Hghpu.mjs").then((n) => n.r);
264
264
  const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
265
265
  await fastify.register(ssePlugin, opts);
266
266
  trackPlugin("arc-sse", opts);
267
267
  }
268
268
  if (config.arcPlugins?.metrics) {
269
- const { default: metricsPlugin } = await import("./metrics-Qnvwc-LQ.mjs").then((n) => n.r);
269
+ const { default: metricsPlugin } = await import("./metrics-DuhiSEZI.mjs").then((n) => n.r);
270
270
  const opts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
271
271
  await fastify.register(metricsPlugin, opts);
272
272
  trackPlugin("arc-metrics", opts);
273
273
  }
274
274
  if (config.arcPlugins?.versioning) {
275
- const { default: versioningPlugin } = await import("./versioning-CdBbFefk.mjs").then((n) => n.r);
275
+ const { default: versioningPlugin } = await import("./versioning-CPU_5Xfs.mjs").then((n) => n.r);
276
276
  await fastify.register(versioningPlugin, config.arcPlugins.versioning);
277
277
  trackPlugin("arc-versioning", config.arcPlugins.versioning);
278
278
  }
@@ -340,7 +340,7 @@ async function registerAuth(fastify, config, trackPlugin) {
340
340
  */
341
341
  async function registerElevation(fastify, config, trackPlugin) {
342
342
  if (!config.elevation) return;
343
- const { elevationPlugin } = await import("./elevation-By_p2lnn.mjs").then((n) => n.r);
343
+ const { elevationPlugin } = await import("./elevation-BBGFjzIP.mjs").then((n) => n.r);
344
344
  await fastify.register(elevationPlugin, config.elevation);
345
345
  trackPlugin("arc-elevation", config.elevation);
346
346
  fastify.log.debug("Elevation plugin enabled");
@@ -350,7 +350,7 @@ async function registerElevation(fastify, config, trackPlugin) {
350
350
  */
351
351
  async function registerErrorHandler(fastify, config, trackPlugin) {
352
352
  if (config.errorHandler === false) return;
353
- const { errorHandlerPlugin } = await import("./errorHandler-CH8wk1eD.mjs").then((n) => n.n);
353
+ const { errorHandlerPlugin } = await import("./errorHandler-Cw34h_om.mjs").then((n) => n.n);
354
354
  const errorOpts = typeof config.errorHandler === "object" ? config.errorHandler : { includeStack: config.preset !== "production" };
355
355
  await fastify.register(errorHandlerPlugin, errorOpts);
356
356
  trackPlugin("arc-error-handler", errorOpts);
@@ -667,7 +667,7 @@ function validateDistributedRuntime(options) {
667
667
  */
668
668
  async function createApp(options) {
669
669
  if (options.debug !== void 0 && options.debug !== false) {
670
- const { configureArcLogger } = await import("./logger-DLg8-Ueg.mjs").then((n) => n.r);
670
+ const { configureArcLogger } = await import("./logger-CDjpjySd.mjs").then((n) => n.r);
671
671
  configureArcLogger({ debug: options.debug });
672
672
  }
673
673
  validateAuthOptions(options);
@@ -720,7 +720,7 @@ async function createApp(options) {
720
720
  await registerErrorHandler(fastify, config, trackPlugin);
721
721
  await registerResources(fastify, config);
722
722
  if (config.replyHelpers) {
723
- const { replyHelpersPlugin } = await import("./replyHelpers-uDUIYh7u.mjs").then((n) => n.n);
723
+ const { replyHelpersPlugin } = await import("./replyHelpers-CXtJDAZ0.mjs").then((n) => n.n);
724
724
  await fastify.register(replyHelpersPlugin);
725
725
  }
726
726
  if (config.serializeBigInt) fastify.addHook("preSerialization", async (_request, _reply, payload) => {
@@ -1,13 +1,13 @@
1
1
  import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-Cxde4rpC.mjs";
2
2
  import { _ as isElevated, n as PUBLIC_SCOPE, v as isMember } from "./types-AOD8fxIw.mjs";
3
- import { t as BaseController } from "./BaseController-CpMfCXdn.mjs";
3
+ import { t as BaseController } from "./BaseController-DAGGc5Xn.mjs";
4
4
  import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-ipsbIRPK.mjs";
5
5
  import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
6
6
  import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-D6GPMsvh.mjs";
7
- import { t as requestContext } from "./requestContext-xHIKedG6.mjs";
8
- import { i as getDefaultCrudSchemas } from "./utils-B-l6410F.mjs";
9
- import { r as ForbiddenError } from "./errors-Cg58SLNi.mjs";
10
- import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-Y5EejTnJ.mjs";
7
+ import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-OxfCshus.mjs";
8
+ import { t as requestContext } from "./requestContext-DYvHl113.mjs";
9
+ import { i as getDefaultCrudSchemas } from "./utils-7sJ8X83I.mjs";
10
+ import { r as ForbiddenError } from "./errors-BF2bIOIS.mjs";
11
11
  import { t as hasEvents } from "./typeGuards-CcFZXgU7.mjs";
12
12
  import { r as getAvailablePresets, t as applyPresets } from "./presets-BFrGvvjL.mjs";
13
13
  //#region src/pipeline/pipe.ts
@@ -888,6 +888,25 @@ function defineResource(config) {
888
888
  if (typeof route.permissions !== "function") throw new Error(`[Arc] Resource '${config.name}' route ${route.method} ${route.path}: permissions is required and must be a PermissionCheck function.\nUse allowPublic() or requireAuth() from @classytic/arc/permissions.`);
889
889
  if (typeof route.wrapHandler !== "boolean") throw new Error(`[Arc] Resource '${config.name}' route ${route.method} ${route.path}: wrapHandler is required.\nSet true for ControllerHandler (context object) or false for FastifyHandler (req, reply).`);
890
890
  }
891
+ if (config.routes && config.additionalRoutes) throw new Error(`[Arc] Resource '${config.name}': Cannot use both 'routes' and 'additionalRoutes'.\nUse 'routes' (v2.8) — it replaces 'additionalRoutes'.`);
892
+ for (const route of config.routes ?? []) if (typeof route.permissions !== "function") throw new Error(`[Arc] Resource '${config.name}' route ${route.method} ${route.path}: permissions is required and must be a PermissionCheck function.`);
893
+ if (config.actions) {
894
+ const CRUD_OPS = new Set([
895
+ "create",
896
+ "update",
897
+ "delete",
898
+ "list",
899
+ "get"
900
+ ]);
901
+ for (const [name, entry] of Object.entries(config.actions)) {
902
+ if (CRUD_OPS.has(name)) throw new Error(`[Arc] Resource '${config.name}': action '${name}' conflicts with CRUD operation.\nUse a different name (e.g., '${name}_item', 'do_${name}').`);
903
+ if (typeof entry !== "function") {
904
+ const def = entry;
905
+ if (typeof def.handler !== "function") throw new Error(`[Arc] Resource '${config.name}': actions.${name}.handler must be a function.`);
906
+ if (def.permissions !== void 0 && typeof def.permissions !== "function") throw new Error(`[Arc] Resource '${config.name}': actions.${name}.permissions must be a PermissionCheck function.`);
907
+ }
908
+ }
909
+ }
891
910
  }
892
911
  const repository = config.adapter?.repository;
893
912
  if (config.idField === void 0 && repository) {
@@ -1068,9 +1087,21 @@ var ResourceDefinition = class {
1068
1087
  customSchemas;
1069
1088
  permissions;
1070
1089
  additionalRoutes;
1090
+ /**
1091
+ * Original v2.8 `routes` declaration — retained for downstream consumers
1092
+ * (OpenAPI, MCP, registry, CLI introspect). Preserves fields dropped during
1093
+ * normalization to `additionalRoutes` (notably `mcp`, `description`,
1094
+ * `annotations`). Undefined when the resource was defined with the legacy
1095
+ * `additionalRoutes` shape.
1096
+ *
1097
+ * Added in 2.8.1 — the source-of-truth fix for "canonical resource manifest".
1098
+ */
1099
+ routes;
1071
1100
  middlewares;
1072
1101
  disableDefaultRoutes;
1073
1102
  disabledRoutes;
1103
+ actions;
1104
+ actionPermissions;
1074
1105
  events;
1075
1106
  rateLimit;
1076
1107
  audit;
@@ -1096,10 +1127,13 @@ var ResourceDefinition = class {
1096
1127
  this.schemaOptions = config.schemaOptions ?? {};
1097
1128
  this.customSchemas = config.customSchemas ?? {};
1098
1129
  this.permissions = config.permissions ?? {};
1099
- this.additionalRoutes = config.additionalRoutes ?? [];
1130
+ this.routes = config.routes;
1131
+ this.additionalRoutes = config.routes ? convertRoutesToAdditionalRoutes(config.routes) : config.additionalRoutes ?? [];
1100
1132
  this.middlewares = config.middlewares ?? {};
1101
1133
  this.disableDefaultRoutes = config.disableDefaultRoutes ?? false;
1102
1134
  this.disabledRoutes = config.disabledRoutes ?? [];
1135
+ this.actions = config.actions;
1136
+ this.actionPermissions = config.actionPermissions;
1103
1137
  this.events = config.events ?? {};
1104
1138
  this.rateLimit = config.rateLimit;
1105
1139
  this.audit = config.audit;
@@ -1167,10 +1201,10 @@ var ResourceDefinition = class {
1167
1201
  pattern,
1168
1202
  tags
1169
1203
  });
1170
- const onRegister = self._onRegister;
1171
- if (onRegister) await onRegister(fastify);
1172
1204
  await fastify.register(async (instance) => {
1173
1205
  const typedInstance = instance;
1206
+ const onRegister = self._onRegister;
1207
+ if (onRegister) await onRegister(instance);
1174
1208
  let schemas = null;
1175
1209
  const openApi = self._registryMeta?.openApiSchemas;
1176
1210
  if (openApi && (!self.customSchemas || Object.keys(self.customSchemas).length === 0)) {
@@ -1259,6 +1293,10 @@ var ResourceDefinition = class {
1259
1293
  pipe: self.pipe,
1260
1294
  fields: self.fields
1261
1295
  });
1296
+ if (self.actions && Object.keys(self.actions).length > 0) {
1297
+ const { createActionRouter } = await import("./createActionRouter-Df1BuawX.mjs").then((n) => n.r);
1298
+ createActionRouter(instance, normalizeActionsToRouterConfig(self.actions, self.actionPermissions, self.tag));
1299
+ }
1262
1300
  if (self.events && Object.keys(self.events).length > 0) typedInstance.log?.debug?.(`Resource '${self.name}' defined ${Object.keys(self.events).length} events`);
1263
1301
  }, { prefix: self.prefix });
1264
1302
  if (hasEvents(fastify)) try {
@@ -1312,5 +1350,50 @@ function capitalize(str) {
1312
1350
  if (!str) return "";
1313
1351
  return str.charAt(0).toUpperCase() + str.slice(1);
1314
1352
  }
1353
+ /**
1354
+ * Convert v2.8 RouteDefinition[] to internal AdditionalRoute[] format.
1355
+ * The internal format is what createCrudRouter understands.
1356
+ */
1357
+ function convertRoutesToAdditionalRoutes(routes) {
1358
+ return routes.map((route) => ({
1359
+ method: route.method,
1360
+ path: route.path,
1361
+ handler: route.handler,
1362
+ permissions: route.permissions,
1363
+ wrapHandler: !route.raw,
1364
+ operation: route.operation,
1365
+ summary: route.summary,
1366
+ description: route.description,
1367
+ tags: route.tags,
1368
+ preHandler: route.preHandler,
1369
+ preAuth: route.preAuth,
1370
+ streamResponse: route.streamResponse,
1371
+ schema: route.schema,
1372
+ mcpHandler: route.mcpHandler,
1373
+ mcp: route.mcp
1374
+ }));
1375
+ }
1376
+ /**
1377
+ * Normalize ActionsMap into the ActionRouterConfig shape that createActionRouter expects.
1378
+ */
1379
+ function normalizeActionsToRouterConfig(actions, globalAuth, tag) {
1380
+ const handlers = {};
1381
+ const permissions = {};
1382
+ const schemas = {};
1383
+ for (const [name, entry] of Object.entries(actions)) if (typeof entry === "function") handlers[name] = entry;
1384
+ else {
1385
+ const def = entry;
1386
+ handlers[name] = def.handler;
1387
+ if (def.permissions) permissions[name] = def.permissions;
1388
+ if (def.schema) schemas[name] = def.schema;
1389
+ }
1390
+ return {
1391
+ tag,
1392
+ actions: handlers,
1393
+ actionPermissions: permissions,
1394
+ actionSchemas: schemas,
1395
+ globalAuth
1396
+ };
1397
+ }
1315
1398
  //#endregion
1316
1399
  export { validateResourceConfig as a, createCrudHandlers as c, getControllerContext as d, getControllerScope as f, formatValidationErrors as i, createFastifyHandler as l, pipe as m, defineResource as n, createCrudRouter as o, sendControllerResponse as p, assertValidConfig as r, createPermissionMiddleware as s, ResourceDefinition as t, createRequestContext as u };
@@ -1,5 +1,5 @@
1
- import { et as RegistryEntry } from "../interface-B91alUzq.mjs";
2
- import { t as ExternalOpenApiPaths } from "../externalPaths-iba7jD3d.mjs";
1
+ import { it as RegistryEntry } from "../interface-CS6d7HiB.mjs";
2
+ import { t as ExternalOpenApiPaths } from "../externalPaths-BQ8QijNH.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
5
5
  //#region src/docs/openapi.d.ts
@@ -1,5 +1,5 @@
1
1
  import { t as getUserRoles } from "../types-ZUu_h0jp.mjs";
2
- import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-D7Z7VODz.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-q6rNKfZy.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/docs/scalar.ts
5
5
  const scalarPlugin = async (fastify, opts = {}) => {
@@ -1,5 +1,5 @@
1
- import { Vt as ResourceDefinition, r as DataAdapter } from "../interface-B91alUzq.mjs";
2
- import { t as PermissionCheck } from "../types-B4BNthET.mjs";
1
+ import { qt as ResourceDefinition, r as DataAdapter } from "../interface-CS6d7HiB.mjs";
2
+ import { t as PermissionCheck } from "../types-BoaZHr-2.mjs";
3
3
 
4
4
  //#region src/dynamic/ArcDynamicLoader.d.ts
5
5
  interface ArcArchitectureSchema {
@@ -1,5 +1,5 @@
1
1
  import { t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
2
- import { n as defineResource } from "../defineResource-BW2dMCu9.mjs";
2
+ import { n as defineResource } from "../defineResource-CqeUltrW.mjs";
3
3
  import { C as publicRead, T as readOnly, b as fullPublic, v as adminOnly, w as publicReadAdminWrite, x as ownerWithAdminBypass, y as authenticated } from "../permissions-CH4cNwJi.mjs";
4
4
  //#region src/dynamic/ArcDynamicLoader.ts
5
5
  const VALID_FIELD_TYPES = new Set([
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
2
  import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
3
- import { t as arcLog } from "./logger-DLg8-Ueg.mjs";
3
+ import { t as arcLog } from "./logger-CDjpjySd.mjs";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/scope/elevation.ts
6
6
  var elevation_exports = /* @__PURE__ */ __exportAll({
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { p as isArcError } from "./errors-Cg58SLNi.mjs";
2
+ import { p as isArcError } from "./errors-BF2bIOIS.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/errorHandler.ts
5
5
  var errorHandler_exports = /* @__PURE__ */ __exportAll({ errorHandlerPlugin: () => errorHandlerPlugin });
@@ -1,4 +1,4 @@
1
- import { t as DomainEvent } from "./EventTransport-C4VheKeC.mjs";
1
+ import { t as DomainEvent } from "./EventTransport-CLXJUzyT.mjs";
2
2
  import { FastifyInstance, FastifyPluginAsync, FastifyRequest } from "fastify";
3
3
 
4
4
  //#region src/plugins/caching.d.ts
@@ -1,4 +1,4 @@
1
- import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "./EventTransport-C4VheKeC.mjs";
1
+ import { i as EventTransport, n as EventHandler, r as EventLogger, t as DomainEvent } from "./EventTransport-CLXJUzyT.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
3
 
4
4
  //#region src/events/defineEvent.d.ts
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { t as requestContext } from "./requestContext-xHIKedG6.mjs";
2
+ import { t as requestContext } from "./requestContext-DYvHl113.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/events/EventTransport.ts
5
5
  /**
@@ -33,6 +33,24 @@ var MemoryEventTransport = class {
33
33
  this.logger.error(`[EventTransport] Handler error for ${event.type}:`, err);
34
34
  }
35
35
  }
36
+ /**
37
+ * Reference `publishMany` implementation — delegates to `publish()` in order.
38
+ *
39
+ * Production transports (Kafka, Redis pipeline, SQS batch) should override
40
+ * this with a single batched network call. Memory transport has nothing to
41
+ * batch, so we just loop — the loop still returns a proper result map so
42
+ * `EventOutbox.relay` can exercise the batched code path in tests.
43
+ */
44
+ async publishMany(events) {
45
+ const results = /* @__PURE__ */ new Map();
46
+ for (const event of events) try {
47
+ await this.publish(event);
48
+ results.set(event.meta.id, null);
49
+ } catch (err) {
50
+ results.set(event.meta.id, err instanceof Error ? err : new Error(String(err)));
51
+ }
52
+ return results;
53
+ }
36
54
  async subscribe(pattern, handler) {
37
55
  if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
38
56
  this.handlers.get(pattern)?.add(handler);