@classytic/arc 2.16.0 → 2.17.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 (140) hide show
  1. package/README.md +22 -2
  2. package/dist/{BaseController-DlCCTIxJ.mjs → BaseController-Dmeh4Y_y.mjs} +5 -47
  3. package/dist/{QueryCache-SvmT_9ti.d.mts → QueryCache-8YmAQ-zW.d.mts} +1 -1
  4. package/dist/{ResourceRegistry-f48hFk3m.mjs → ResourceRegistry-CzSzVK1m.mjs} +3 -0
  5. package/dist/audit/index.d.mts +1 -1
  6. package/dist/audit/index.mjs +2 -1
  7. package/dist/auth/index.d.mts +4 -4
  8. package/dist/auth/index.mjs +2 -2
  9. package/dist/auth/redis-session.d.mts +1 -1
  10. package/dist/auth/redis-session.mjs +28 -2
  11. package/dist/{buildHandler-BZX6zzDM.mjs → buildHandler-C_SkBNF3.mjs} +6 -4
  12. package/dist/cache/index.d.mts +3 -3
  13. package/dist/cli/commands/describe.d.mts +1 -1
  14. package/dist/cli/commands/describe.mjs +1 -1
  15. package/dist/cli/commands/docs.mjs +2 -2
  16. package/dist/cli/commands/generate.d.mts +1 -1
  17. package/dist/cli/commands/generate.mjs +1 -1
  18. package/dist/cli/commands/init.d.mts +1 -1
  19. package/dist/cli/commands/init.mjs +1 -1
  20. package/dist/cli/commands/introspect.mjs +1 -1
  21. package/dist/cli/index.d.mts +2 -2
  22. package/dist/cli/index.mjs +2 -2
  23. package/dist/core/index.d.mts +2 -2
  24. package/dist/core/index.mjs +3 -3
  25. package/dist/{core-DBJ_j6rX.mjs → core-DYJvu7aI.mjs} +174 -31
  26. package/dist/{createActionRouter-DUpN3Dd1.mjs → createActionRouter-BHUMb1-y.mjs} +6 -3
  27. package/dist/{createAggregationRouter-Dq-TUCuY.mjs → createAggregationRouter-BYVvhfc5.mjs} +3 -3
  28. package/dist/{createApp-DNccuhyI.mjs → createApp-CzCEEsEm.mjs} +9 -9
  29. package/dist/{defineEvent-DRwY0fYm.mjs → defineEvent-NPKUJRt8.mjs} +1 -1
  30. package/dist/docs/index.d.mts +2 -2
  31. package/dist/docs/index.mjs +2 -2
  32. package/dist/{elevation-Dci0AYLT.mjs → elevation-6mfTLBC-.mjs} +1 -1
  33. package/dist/{errorHandler-DpoXQHZ9.mjs → errorHandler-wQejMBT3.mjs} +45 -4
  34. package/dist/{eventPlugin-C2cGqtRO.mjs → eventPlugin-CnJCUyM0.mjs} +1 -1
  35. package/dist/{eventPlugin-CtHC_av1.d.mts → eventPlugin-DjEx_J3j.d.mts} +1 -1
  36. package/dist/events/index.d.mts +3 -3
  37. package/dist/events/index.mjs +5 -5
  38. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  39. package/dist/events/transports/redis-stream-entry.mjs +34 -1
  40. package/dist/events/transports/redis.d.mts +1 -1
  41. package/dist/events/transports/redis.mjs +17 -1
  42. package/dist/factory/index.d.mts +2 -2
  43. package/dist/factory/index.mjs +4 -3
  44. package/dist/fastify-augmentation-7LIR0nDv.d.mts +42 -0
  45. package/dist/{fields-Anj0xdih.d.mts → fields-DqZLp5d6.d.mts} +1 -1
  46. package/dist/hooks/index.d.mts +1 -1
  47. package/dist/idempotency/index.d.mts +2 -2
  48. package/dist/idempotency/index.mjs +21 -5
  49. package/dist/idempotency/redis.d.mts +1 -1
  50. package/dist/{index-3oIimXQn.d.mts → index-3L-adFJO.d.mts} +3 -3
  51. package/dist/{index-CkW0flkU.d.mts → index-C1JSBan-.d.mts} +256 -8
  52. package/dist/{index-B-ulKx5P.d.mts → index-CruZir1o.d.mts} +36 -16
  53. package/dist/index.d.mts +5 -5
  54. package/dist/index.mjs +5 -5
  55. package/dist/integrations/event-gateway.d.mts +2 -2
  56. package/dist/integrations/event-gateway.mjs +1 -1
  57. package/dist/integrations/index.d.mts +2 -2
  58. package/dist/integrations/jobs.d.mts +36 -1
  59. package/dist/integrations/jobs.mjs +82 -9
  60. package/dist/integrations/mcp/index.d.mts +67 -5
  61. package/dist/integrations/mcp/index.mjs +93 -2
  62. package/dist/integrations/mcp/testing.d.mts +1 -1
  63. package/dist/integrations/mcp/testing.mjs +1 -1
  64. package/dist/integrations/websocket-redis.d.mts +1 -1
  65. package/dist/integrations/websocket.d.mts +1 -1
  66. package/dist/integrations/websocket.mjs +2 -1
  67. package/dist/{loadResourcesFromEntry-BLMEI2Xa.mjs → loadResourcesFromEntry-DCjYMgfC.mjs} +1 -1
  68. package/dist/{mcpPlugin-7vGV51ED.mjs → mcpPlugin-hZE00H74.mjs} +114 -11
  69. package/dist/middleware/index.d.mts +1 -1
  70. package/dist/middleware/index.mjs +1 -1
  71. package/dist/{multipartBody-BOvVSVCD.mjs → multipartBody-VjVWmZl8.mjs} +2 -1
  72. package/dist/{openapi-34T9yNwd.mjs → openapi-DKDs9NfD.mjs} +37 -10
  73. package/dist/permissions/index.d.mts +2 -2
  74. package/dist/permissions/index.mjs +2 -2
  75. package/dist/{permissions-CTxMrreC.mjs → permissions-B1Qy55fg.mjs} +169 -169
  76. package/dist/pipeline/index.d.mts +1 -1
  77. package/dist/plugins/index.d.mts +5 -59
  78. package/dist/plugins/index.mjs +7 -7
  79. package/dist/plugins/tracing-entry.d.mts +1 -1
  80. package/dist/plugins/tracing-entry.mjs +1 -1
  81. package/dist/preloadResources-BOXzPhKU.mjs +45 -0
  82. package/dist/presets/filesUpload.d.mts +4 -4
  83. package/dist/presets/filesUpload.mjs +2 -2
  84. package/dist/presets/index.d.mts +1 -1
  85. package/dist/presets/index.mjs +1 -1
  86. package/dist/presets/multiTenant.d.mts +1 -1
  87. package/dist/presets/search.d.mts +2 -2
  88. package/dist/presets/search.mjs +1 -1
  89. package/dist/{presets-C9BE6WaZ.mjs → presets-BYbPobbF.mjs} +5 -3
  90. package/dist/{queryCachePlugin-Biqzfbi5.d.mts → queryCachePlugin-Do4P_WBC.d.mts} +2 -2
  91. package/dist/{redis-Cyzrz6SX.d.mts → redis-cpqVb85w.d.mts} +1 -1
  92. package/dist/{redis-stream-DT-YjzrB.d.mts → redis-stream-BuiAS3aE.d.mts} +1 -1
  93. package/dist/registry/index.d.mts +30 -8
  94. package/dist/registry/index.mjs +2 -2
  95. package/dist/{registry-BBE23CDj.mjs → registry-BrlHkrRS.mjs} +14 -8
  96. package/dist/{routerShared-CZV5aabX.mjs → routerShared-nCyJGOKX.mjs} +164 -17
  97. package/dist/scope/index.d.mts +2 -2
  98. package/dist/scope/index.mjs +2 -2
  99. package/dist/testing/index.d.mts +2 -25
  100. package/dist/testing/index.mjs +2 -45
  101. package/dist/testing/storageContract.d.mts +1 -1
  102. package/dist/types/index.d.mts +4 -4
  103. package/dist/types/storage.d.mts +1 -1
  104. package/dist/{types-D-fYtKjb.d.mts → types-BRmS8TNa.d.mts} +34 -11
  105. package/dist/{types-BsJMEQ4D.d.mts → types-CoMjNmkF.d.mts} +12 -1
  106. package/dist/utils/index.d.mts +83 -5
  107. package/dist/utils/index.mjs +2 -2
  108. package/dist/{utils-DC5ycPfr.mjs → utils-CvD3glpM.mjs} +168 -3
  109. package/dist/{validate-By96rH0r.mjs → validate-DFIP2VrM.mjs} +65 -3
  110. package/dist/{versioning-ZwX9tmbS.d.mts → versioning-5H4930tj.d.mts} +1 -1
  111. package/package.json +3 -3
  112. package/skills/arc/SKILL.md +43 -1
  113. package/skills/arc/references/api-reference.md +2 -0
  114. package/skills/arc/references/mcp.md +30 -1
  115. /package/dist/{EventTransport-C-2oAHtw.d.mts → EventTransport-B1GFiYUy.d.mts} +0 -0
  116. /package/dist/{EventTransport-Hxvv5QQz.mjs → EventTransport-ClEDfiMS.mjs} +0 -0
  117. /package/dist/{caching-TeHE8G-v.mjs → caching-8MfrrUYU.mjs} +0 -0
  118. /package/dist/{elevation-0YBpa663.d.mts → elevation-DvwtjDwu.d.mts} +0 -0
  119. /package/dist/{errorHandler-mHuyWzZE.d.mts → errorHandler-DuqTqeqn.d.mts} +0 -0
  120. /package/dist/{errors-C1lX_jlm.d.mts → errors-4ZloYFvm.d.mts} +0 -0
  121. /package/dist/{externalPaths-DFg-2KTp.d.mts → externalPaths-ClykOQqa.d.mts} +0 -0
  122. /package/dist/{generate-CYac-OLv.mjs → generate-CcM24hoq.mjs} +0 -0
  123. /package/dist/{generate-BWFwgcCM.d.mts → generate-cRnsafMS.d.mts} +0 -0
  124. /package/dist/{init-Dv71MsJr.d.mts → init-D-vrTZ1G.d.mts} +0 -0
  125. /package/dist/{init-HDvoO9L5.mjs → init-DYzfniuX.mjs} +0 -0
  126. /package/dist/{interface-CH0OQudo.d.mts → interface-BVkZsvEe.d.mts} +0 -0
  127. /package/dist/{interface-NwJ_qPlY.d.mts → interface-DoE6UoC2.d.mts} +0 -0
  128. /package/dist/{loadResources-ChQEj8ih.mjs → loadResources-fol6JuvW.mjs} +0 -0
  129. /package/dist/{metrics-TuOmguhi.mjs → metrics-B-PU4-Yu.mjs} +0 -0
  130. /package/dist/{replyHelpers-C-gD32oF.mjs → replyHelpers-boS5Rx26.mjs} +0 -0
  131. /package/dist/{schemaIR-Ctc89DSn.mjs → schemaIR-DAOvBWgT.mjs} +0 -0
  132. /package/dist/{sessionManager-BqFegc0W.d.mts → sessionManager-DlmaHUAl.d.mts} +0 -0
  133. /package/dist/{sse-BY6sTy4P.mjs → sse-jh5OqFjR.mjs} +0 -0
  134. /package/dist/{storage-D2KZJAmn.d.mts → storage-HcAgP5Ni.d.mts} +0 -0
  135. /package/dist/{store-helpers-B0sunfZZ.mjs → store-helpers-5JgaxieH.mjs} +0 -0
  136. /package/dist/{tracing-Dm8n7Cnn.d.mts → tracing-bRqqUxFO.d.mts} +0 -0
  137. /package/dist/{types-DVfpSfx2.d.mts → types-CfapW2t8.d.mts} +0 -0
  138. /package/dist/{types-D57iXYb8.mjs → types-DV9WDfeg.mjs} +0 -0
  139. /package/dist/{versioning-B6mimogM.mjs → versioning-CuIwUQZ2.mjs} +0 -0
  140. /package/dist/{websocket-BkjeGZRn.d.mts → websocket-BJ0w-3Dt.d.mts} +0 -0
@@ -1,19 +1,44 @@
1
- import { p as isArcError } from "./errors-j4aJm1Wg.mjs";
2
- import { t as BaseController } from "./BaseController-DlCCTIxJ.mjs";
3
- import { L as normalizePermissionResult } from "./permissions-CTxMrreC.mjs";
1
+ import { p as isArcError, t as ArcError } from "./errors-j4aJm1Wg.mjs";
2
+ import { t as BaseController } from "./BaseController-Dmeh4Y_y.mjs";
3
+ import { L as normalizePermissionResult } from "./permissions-B1Qy55fg.mjs";
4
4
  import { t as executePipeline } from "./pipe-DiCyvyPN.mjs";
5
- import { u as resolvePipelineSteps } from "./routerShared-CZV5aabX.mjs";
6
- import { r as validateAggregations } from "./validate-By96rH0r.mjs";
7
- import { n as executeAggregation } from "./buildHandler-BZX6zzDM.mjs";
5
+ import { f as resolvePipelineSteps } from "./routerShared-nCyJGOKX.mjs";
6
+ import { r as validateAggregations } from "./validate-DFIP2VrM.mjs";
7
+ import { n as executeAggregation } from "./buildHandler-C_SkBNF3.mjs";
8
8
  import { t as pluralize } from "./pluralize-B9M8xvy-.mjs";
9
9
  import { t as resolveActionPermission } from "./actionPermissions-Bjmvn7Eb.mjs";
10
- import { i as shouldRejectAdditionalProperties, r as schemaIRToZodShape, t as normalizeSchemaIR } from "./schemaIR-Ctc89DSn.mjs";
10
+ import { i as shouldRejectAdditionalProperties, r as schemaIRToZodShape, t as normalizeSchemaIR } from "./schemaIR-DAOvBWgT.mjs";
11
11
  import { createHash, randomUUID } from "node:crypto";
12
12
  import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
13
13
  import fp from "fastify-plugin";
14
14
  import { z } from "zod";
15
15
  //#region src/integrations/mcp/createMcpServer.ts
16
16
  /**
17
+ * @classytic/arc — createMcpServer()
18
+ *
19
+ * Level 2 — Declarative MCP server factory.
20
+ * Uses `server.registerTool()` / `server.registerPrompt()` (official SDK API)
21
+ * with flat inputSchema shapes — no z.object() wrapping, no Zod version hacks.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { createMcpServer, defineTool } from '@classytic/arc/mcp';
26
+ * import { z } from 'zod';
27
+ *
28
+ * const server = await createMcpServer({
29
+ * name: 'my-api',
30
+ * version: '1.0.0',
31
+ * tools: [
32
+ * defineTool('greet', {
33
+ * description: 'Say hello',
34
+ * input: { name: z.string() },
35
+ * handler: async ({ name }) => ({ content: [{ type: 'text', text: `Hello ${name}` }] }),
36
+ * }),
37
+ * ],
38
+ * });
39
+ * ```
40
+ */
41
+ /**
17
42
  * Create a configured MCP server from declarative config.
18
43
  *
19
44
  * @param config - Server name, version, tools, prompts
@@ -25,11 +50,70 @@ async function createMcpServer(config, authRef) {
25
50
  name: config.name,
26
51
  version: config.version ?? "1.0.0"
27
52
  }, config.instructions ? { instructions: config.instructions } : void 0);
28
- if (config.tools) for (const tool of config.tools) registerTool(server, tool, authRef);
53
+ if (config.tools) {
54
+ const resolved = resolveToolCollisions(config.tools);
55
+ for (const tool of resolved) registerTool(server, tool, authRef);
56
+ }
29
57
  if (config.prompts) for (const prompt of config.prompts) registerPrompt(server, prompt);
30
58
  return server;
31
59
  }
32
60
  /**
61
+ * Detect and resolve duplicate MCP tool names across the assembled tool list.
62
+ *
63
+ * Two outcomes:
64
+ *
65
+ * 1. **Auto-namespace** — when one tool's `source` carries a `preset:<name>`
66
+ * prefix and the other doesn't, the preset tool is renamed to
67
+ * `<presetName>_<originalName>` (e.g. softDelete's `restore_post`
68
+ * becomes `softdelete_restore_post` when the user defines
69
+ * `actions.restore`). The user's tool wins the canonical name because
70
+ * it's the more-specific intent.
71
+ *
72
+ * 2. **Typed throw** — every other collision (two user actions, two
73
+ * custom routes, two presets) raises `ArcError('arc.mcp.tool_name_collision')`
74
+ * naming both sources so the host's stack trace points at the
75
+ * resource definitions, not the MCP SDK internals.
76
+ *
77
+ * Exported for the testing harness so collision behaviour can be exercised
78
+ * without a full server boot.
79
+ */
80
+ function resolveToolCollisions(tools) {
81
+ const byName = /* @__PURE__ */ new Map();
82
+ for (const t of tools) {
83
+ const list = byName.get(t.name);
84
+ if (list) list.push(t);
85
+ else byName.set(t.name, [t]);
86
+ }
87
+ const out = [];
88
+ for (const [name, list] of byName) {
89
+ const [first, second, ...rest] = list;
90
+ if (!first) continue;
91
+ if (!second) {
92
+ out.push(first);
93
+ continue;
94
+ }
95
+ if (rest.length === 0) {
96
+ const firstIsPreset = first.source?.startsWith("preset:") ?? false;
97
+ if (firstIsPreset !== (second.source?.startsWith("preset:") ?? false)) {
98
+ const preset = firstIsPreset ? first : second;
99
+ const user = firstIsPreset ? second : first;
100
+ const namespaced = `${(preset.source?.split(":")[1] ?? "preset").toLowerCase()}_${name}`;
101
+ out.push({
102
+ ...preset,
103
+ name: namespaced
104
+ });
105
+ out.push(user);
106
+ continue;
107
+ }
108
+ }
109
+ throw new ArcError(`MCP tool name '${name}' is registered by ${list.length} sources: ${list.map((t) => t.source ?? "(unknown)").join(", ")}. Rename one of the sources (e.g. via 'actions.${name}' → 'actions.${name}V2', or pass 'names: { [op]: "custom_name" }' in mcpPlugin overrides) so each tool has a unique name.`, {
110
+ code: "arc.mcp.tool_name_collision",
111
+ statusCode: 500
112
+ });
113
+ }
114
+ return out;
115
+ }
116
+ /**
33
117
  * Register a ToolDefinition using `server.registerTool()`.
34
118
  *
35
119
  * The inputSchema is passed as a flat Zod shape `{ field: z.string() }` —
@@ -223,6 +307,13 @@ function expandOperatorKeys(input) {
223
307
  }
224
308
  return out;
225
309
  }
310
+ /**
311
+ * Resolve an `McpAuthResult` (or `null` for unauthenticated calls) into a
312
+ * canonical `RequestScope`. Exported so the MCP tool helpers can build the
313
+ * same scope when constructing the permission-check context, ensuring
314
+ * `requireOrgMembership()` / `requireOrgRole()` see the same auth shape on
315
+ * MCP that they see on HTTP.
316
+ */
226
317
  function buildScope(auth) {
227
318
  if (!auth) return { kind: "public" };
228
319
  if (auth.clientId && auth.organizationId) return {
@@ -264,6 +355,7 @@ async function evaluatePermission(check, session, resource, action, input) {
264
355
  _id: session.userId,
265
356
  ...session
266
357
  } : null;
358
+ const scope = buildScope(session);
267
359
  return normalizePermissionResult(await check({
268
360
  user,
269
361
  request: {
@@ -271,7 +363,9 @@ async function evaluatePermission(check, session, resource, action, input) {
271
363
  headers: {},
272
364
  params: {},
273
365
  query: {},
274
- body: input
366
+ body: input,
367
+ scope,
368
+ metadata: { _scope: scope }
275
369
  },
276
370
  resource,
277
371
  action,
@@ -1473,7 +1567,8 @@ function resourceToTools(resource, config = {}) {
1473
1567
  allowedOperators,
1474
1568
  adapterBodies
1475
1569
  }),
1476
- handler: createCrudHandler(op, controller, resource.name, resource.permissions)
1570
+ handler: createCrudHandler(op, controller, resource.name, resource.permissions),
1571
+ source: `crud:${resource.name}:${op}`
1477
1572
  });
1478
1573
  }
1479
1574
  }
@@ -1508,11 +1603,13 @@ function resourceToTools(resource, config = {}) {
1508
1603
  for (const [key, val] of Object.entries(schemaIRToZodShape(ir))) if (!(key in inputShape)) inputShape[key] = val;
1509
1604
  }
1510
1605
  const toolName = prefix ? `${prefix}_${opName}_${resource.name}` : `${opName}_${resource.name}`;
1606
+ const routeSource = route._presetSource ? `preset:${route._presetSource}:${resource.name}:${opName}` : `route:${resource.name}:${route.method} ${route.path}`;
1511
1607
  tools.push({
1512
1608
  name: toolName,
1513
1609
  description: toolDescription,
1514
1610
  annotations: toolAnnotations,
1515
1611
  inputSchema: inputShape,
1612
+ source: routeSource,
1516
1613
  handler: mcpHandler ? createMcpHandlerPassthrough(mcpHandler) : createCustomRouteHandler({
1517
1614
  handler: resolvedRouteHandler,
1518
1615
  operation: route.operation,
@@ -1552,6 +1649,7 @@ function resourceToTools(resource, config = {}) {
1552
1649
  description: String(description),
1553
1650
  annotations,
1554
1651
  inputSchema: inputShape,
1652
+ source: `action:${resource.name}:${actionName}`,
1555
1653
  handler: createActionToolHandler(actionName, handler, actionPerms, resource.name, resource.permissions, rawSchema, idLess)
1556
1654
  });
1557
1655
  }
@@ -1799,6 +1897,11 @@ const mcpPluginImpl = async (fastify, options) => {
1799
1897
  }
1800
1898
  if (options.auth && isBetterAuth(options.auth)) await registerOAuthDiscovery(fastify, options.auth);
1801
1899
  const prefix = options.prefix ?? "/mcp";
1900
+ if (options.auth === false) {
1901
+ const toolList = allTools.map((t) => t.name).slice(0, 5).join(", ");
1902
+ const more = allTools.length > 5 ? ` (+${allTools.length - 5} more)` : "";
1903
+ fastify.log.warn(`[arc/mcp] MCP server registered at "${prefix}" with auth DISABLED. Anyone who can reach this URL has full access to ${allTools.length} tools across ${enabledResources.length} resources: ${toolList}${more}. Pass \`auth: betterAuthInstance\` or \`auth: async (headers) => ...\` to gate access. Safe only for stdio transports, local development, or explicitly-public read-only APIs.`);
1904
+ }
1802
1905
  fastify.get(`${prefix}/health`, async (_request, reply) => {
1803
1906
  reply.send({
1804
1907
  status: "ok",
@@ -2013,4 +2116,4 @@ const mcpPlugin = fp(mcpPluginImpl, {
2013
2116
  fastify: "5.x"
2014
2117
  });
2015
2118
  //#endregion
2016
- export { defaultCrudDescription as a, mcpHandlerAdapter as c, toCallToolResult as d, toCallToolSuccess as f, fieldRulesToZod as i, permissionDeniedResult as l, createMcpServer as m, mcpPlugin as n, resolveCrudDescription as o, buildRequestContext as p, resourceToTools as r, invokeController as s, filterResourcesForMcp as t, toCallToolError as u };
2119
+ export { defaultCrudDescription as a, mcpHandlerAdapter as c, toCallToolResult as d, toCallToolSuccess as f, resolveToolCollisions as h, fieldRulesToZod as i, permissionDeniedResult as l, createMcpServer as m, mcpPlugin as n, resolveCrudDescription as o, buildRequestContext as p, resourceToTools as r, invokeController as s, filterResourcesForMcp as t, toCallToolError as u };
@@ -1,4 +1,4 @@
1
- import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-CkW0flkU.mjs";
1
+ import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-C1JSBan-.mjs";
2
2
  import { RouteHandlerMethod } from "fastify";
3
3
 
4
4
  //#region src/middleware/middleware.d.ts
@@ -1,5 +1,5 @@
1
1
  import { t as CRUD_OPERATIONS } from "../constants-TrJVIJl0.mjs";
2
- import { t as multipartBody } from "../multipartBody-BOvVSVCD.mjs";
2
+ import { t as multipartBody } from "../multipartBody-VjVWmZl8.mjs";
3
3
  //#region src/middleware/middleware.ts
4
4
  /**
5
5
  * Named Middleware — Priority-based, conditional middleware execution.
@@ -1,3 +1,4 @@
1
+ import sjson from "secure-json-parse";
1
2
  //#region src/middleware/multipartBody.ts
2
3
  const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
3
4
  const DEFAULT_MAX_FILES = 5;
@@ -118,7 +119,7 @@ function tryParseValue(value) {
118
119
  if (Number.isFinite(num)) return num;
119
120
  }
120
121
  if (value.startsWith("{") && value.endsWith("}") || value.startsWith("[") && value.endsWith("]")) try {
121
- return JSON.parse(value);
122
+ return sjson.parse(value);
122
123
  } catch {}
123
124
  return value;
124
125
  }
@@ -1,8 +1,8 @@
1
- import { t as getUserRoles } from "./types-D57iXYb8.mjs";
1
+ import { t as getUserRoles } from "./types-DV9WDfeg.mjs";
2
2
  import { n as convertRouteSchema } from "./schemaConverter-De34B1ZG.mjs";
3
3
  import { t as pluralize } from "./pluralize-B9M8xvy-.mjs";
4
4
  import { t as resolveActionPermission } from "./actionPermissions-Bjmvn7Eb.mjs";
5
- import { t as buildActionBodySchema } from "./createActionRouter-DUpN3Dd1.mjs";
5
+ import { t as buildActionBodySchema } from "./createActionRouter-BHUMb1-y.mjs";
6
6
  import fp from "fastify-plugin";
7
7
  //#region src/docs/openapi/canonical-schemas.ts
8
8
  /**
@@ -647,13 +647,7 @@ function appendAggregationPaths(paths, resource, basePath, additionalSecurity) {
647
647
  if (!paths[path]) paths[path] = {};
648
648
  paths[path].get = createOperation(resource, `aggregation.${agg.name}`, agg.summary ?? `Aggregation: ${agg.name}`, {
649
649
  description: descLines.join("\n"),
650
- parameters: [{
651
- name: "querystring",
652
- in: "query",
653
- required: false,
654
- schema: querystring,
655
- description: "Filter narrowing — composes with base filter + tenant scope."
656
- }],
650
+ parameters: buildAggregationParameters(agg, querystring),
657
651
  responses: {
658
652
  "200": {
659
653
  description: "Aggregation result",
@@ -701,6 +695,37 @@ function normalizeGroupByForOpenApi(groupBy) {
701
695
  if (!groupBy) return [];
702
696
  return typeof groupBy === "string" ? [groupBy] : groupBy;
703
697
  }
698
+ /**
699
+ * Build the OpenAPI `parameters` array for an aggregation route.
700
+ *
701
+ * Always includes the filter-narrowing `querystring` slot. When the
702
+ * aggregation declares URL-driven paging (`defaultLimit`), adds an
703
+ * explicit `?limit` integer parameter with the declared default +
704
+ * maximum. Auto-generated FE clients pick the constraint up; humans
705
+ * reading the docs see the cap without reading source.
706
+ */
707
+ function buildAggregationParameters(agg, querystring) {
708
+ const params = [{
709
+ name: "querystring",
710
+ in: "query",
711
+ required: false,
712
+ schema: querystring,
713
+ description: "Filter narrowing — composes with base filter + tenant scope."
714
+ }];
715
+ if (agg.defaultLimit !== void 0) params.push({
716
+ name: "limit",
717
+ in: "query",
718
+ required: false,
719
+ schema: {
720
+ type: "integer",
721
+ minimum: 1,
722
+ default: agg.defaultLimit,
723
+ maximum: agg.maxLimit ?? 1e3
724
+ },
725
+ description: `Result row cap. Default ${agg.defaultLimit}; values above ${agg.maxLimit ?? 1e3} are capped silently. Invalid values fall back to the default.`
726
+ });
727
+ return params;
728
+ }
704
729
  function setNestedSchemaProp(target, path, leaf) {
705
730
  if (path.length === 1) {
706
731
  target[path[0]] = leaf;
@@ -850,7 +875,9 @@ function appendCustomRoutePaths(paths, resource, basePath, additionalSecurity) {
850
875
  }
851
876
  if (routeSchema?.response) {
852
877
  const responseSchemas = routeSchema.response;
853
- for (const [statusCode, schema] of Object.entries(responseSchemas)) extras.responses[statusCode] = {
878
+ const responses = extras.responses ?? {};
879
+ extras.responses = responses;
880
+ for (const [statusCode, schema] of Object.entries(responseSchemas)) responses[statusCode] = {
854
881
  description: schema.description || `Response ${statusCode}`,
855
882
  content: { "application/json": { schema } }
856
883
  };
@@ -1,3 +1,3 @@
1
- import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, f as getUserRoles, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, p as normalizeRoles, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission, u as PermissionResult } from "../fields-Anj0xdih.mjs";
2
- import { A as requireRoles, C as allOf, D as not, E as denyAll, F as RequireAgentScopeOptions, I as RequireMandateOptions, L as requireAgentScope, M as when, N as applyPermissionResult, O as requireAuth, P as normalizePermissionResult, R as requireDPoP, S as createOrgPermissions, T as anyOf, _ as ConnectEventsOptions, a as presets_d_exports, b as PermissionEventBus, c as readOnly, d as requireOrgRole, f as requireScopeContext, g as createRoleHierarchy, h as RoleHierarchy, i as ownerWithAdminBypass, j as roles, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig, z as requireMandate } from "../index-3oIimXQn.mjs";
1
+ import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, f as getUserRoles, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, p as normalizeRoles, r as FieldPermissionType, s as resolveEffectiveRoles, t as FieldPermission, u as PermissionResult } from "../fields-DqZLp5d6.mjs";
2
+ import { A as requireRoles, C as allOf, D as not, E as denyAll, F as RequireAgentScopeOptions, I as RequireMandateOptions, L as requireAgentScope, M as when, N as applyPermissionResult, O as requireAuth, P as normalizePermissionResult, R as requireDPoP, S as createOrgPermissions, T as anyOf, _ as ConnectEventsOptions, a as presets_d_exports, b as PermissionEventBus, c as readOnly, d as requireOrgRole, f as requireScopeContext, g as createRoleHierarchy, h as RoleHierarchy, i as ownerWithAdminBypass, j as roles, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig, z as requireMandate } from "../index-3L-adFJO.mjs";
3
3
  export { ConnectEventsOptions, DynamicPermissionMatrix, DynamicPermissionMatrixConfig, FieldPermission, FieldPermissionMap, FieldPermissionType, PermissionCheck, PermissionContext, PermissionEventBus, PermissionResult, RequireAgentScopeOptions, RequireMandateOptions, RoleHierarchy, UserBase, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, presets_d_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };
@@ -1,3 +1,3 @@
1
- import { A as roles, C as allowPublic, D as requireAuth, E as not, F as applyPermissionResult, L as normalizePermissionResult, M as requireAgentScope, N as requireDPoP, O as requireOwnership, P as requireMandate, S as allOf, T as denyAll, _ as requireOrgMembership, a as presets_exports, b as requireServiceScope, c as readOnly, d as applyFieldWritePermissions, f as fields, g as requireOrgInScope, h as createOrgPermissions, i as ownerWithAdminBypass, j as when, k as requireRoles, l as createRoleHierarchy, m as createDynamicPermissionMatrix, n as authenticated, o as publicRead, p as resolveEffectiveRoles, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as applyFieldReadPermissions, v as requireOrgRole, w as anyOf, x as requireTeamMembership, y as requireScopeContext } from "../permissions-CTxMrreC.mjs";
2
- import { n as normalizeRoles, t as getUserRoles } from "../types-D57iXYb8.mjs";
1
+ import { A as requireMandate, C as requireAuth, D as when, E as roles, F as applyPermissionResult, L as normalizePermissionResult, M as applyFieldWritePermissions, N as fields, O as requireAgentScope, P as resolveEffectiveRoles, S as not, T as requireRoles, _ as requireTeamMembership, a as presets_exports, b as anyOf, c as readOnly, d as createOrgPermissions, f as requireOrgInScope, g as requireServiceScope, h as requireScopeContext, i as ownerWithAdminBypass, j as applyFieldReadPermissions, k as requireDPoP, l as createRoleHierarchy, m as requireOrgRole, n as authenticated, o as publicRead, p as requireOrgMembership, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as createDynamicPermissionMatrix, v as allOf, w as requireOwnership, x as denyAll, y as allowPublic } from "../permissions-B1Qy55fg.mjs";
2
+ import { n as normalizeRoles, t as getUserRoles } from "../types-DV9WDfeg.mjs";
3
3
  export { adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, applyPermissionResult, authenticated, createDynamicPermissionMatrix, createOrgPermissions, createRoleHierarchy, denyAll, fields, fullPublic, getUserRoles, normalizePermissionResult, normalizeRoles, not, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, roles, when };