@classytic/arc 2.16.0 → 2.16.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 (117) hide show
  1. package/dist/{BaseController-DlCCTIxJ.mjs → BaseController-Vk8I7C0x.mjs} +1 -1
  2. package/dist/{QueryCache-SvmT_9ti.d.mts → QueryCache-8YmAQ-zW.d.mts} +1 -1
  3. package/dist/{ResourceRegistry-f48hFk3m.mjs → ResourceRegistry-CzSzVK1m.mjs} +3 -0
  4. package/dist/audit/index.d.mts +1 -1
  5. package/dist/auth/index.d.mts +4 -4
  6. package/dist/auth/redis-session.d.mts +1 -1
  7. package/dist/{buildHandler-BZX6zzDM.mjs → buildHandler-C_SkBNF3.mjs} +6 -4
  8. package/dist/cache/index.d.mts +3 -3
  9. package/dist/cli/commands/describe.d.mts +1 -1
  10. package/dist/cli/commands/describe.mjs +1 -1
  11. package/dist/cli/commands/docs.mjs +2 -2
  12. package/dist/cli/commands/generate.d.mts +1 -1
  13. package/dist/cli/commands/generate.mjs +1 -1
  14. package/dist/cli/commands/init.d.mts +1 -1
  15. package/dist/cli/commands/init.mjs +1 -1
  16. package/dist/cli/commands/introspect.mjs +1 -1
  17. package/dist/cli/index.d.mts +2 -2
  18. package/dist/cli/index.mjs +2 -2
  19. package/dist/core/index.d.mts +2 -2
  20. package/dist/core/index.mjs +3 -3
  21. package/dist/{core-DBJ_j6rX.mjs → core-wKeGXn38.mjs} +5 -5
  22. package/dist/{createActionRouter-DUpN3Dd1.mjs → createActionRouter-eiOFtnC3.mjs} +2 -2
  23. package/dist/{createAggregationRouter-Dq-TUCuY.mjs → createAggregationRouter-C4unhBnV.mjs} +3 -3
  24. package/dist/{createApp-DNccuhyI.mjs → createApp-DdPBtmzp.mjs} +8 -8
  25. package/dist/{defineEvent-DRwY0fYm.mjs → defineEvent-NPKUJRt8.mjs} +1 -1
  26. package/dist/docs/index.d.mts +2 -2
  27. package/dist/docs/index.mjs +1 -1
  28. package/dist/{eventPlugin-C2cGqtRO.mjs → eventPlugin-CnJCUyM0.mjs} +1 -1
  29. package/dist/{eventPlugin-CtHC_av1.d.mts → eventPlugin-DjEx_J3j.d.mts} +1 -1
  30. package/dist/events/index.d.mts +3 -3
  31. package/dist/events/index.mjs +5 -5
  32. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  33. package/dist/events/transports/redis.d.mts +1 -1
  34. package/dist/factory/index.d.mts +2 -2
  35. package/dist/factory/index.mjs +4 -3
  36. package/dist/fastify-augmentation-CJm0vh-l.d.mts +42 -0
  37. package/dist/{fields-Anj0xdih.d.mts → fields-DqZLp5d6.d.mts} +1 -1
  38. package/dist/hooks/index.d.mts +1 -1
  39. package/dist/idempotency/index.d.mts +2 -2
  40. package/dist/idempotency/index.mjs +1 -1
  41. package/dist/idempotency/redis.d.mts +1 -1
  42. package/dist/{index-3oIimXQn.d.mts → index-3L-adFJO.d.mts} +3 -3
  43. package/dist/{index-B-ulKx5P.d.mts → index-CC_pgZjY.d.mts} +36 -16
  44. package/dist/{index-CkW0flkU.d.mts → index-DBDgy6Hw.d.mts} +79 -7
  45. package/dist/index.d.mts +5 -5
  46. package/dist/index.mjs +4 -4
  47. package/dist/integrations/event-gateway.d.mts +2 -2
  48. package/dist/integrations/event-gateway.mjs +1 -1
  49. package/dist/integrations/index.d.mts +2 -2
  50. package/dist/integrations/mcp/index.d.mts +4 -4
  51. package/dist/integrations/mcp/index.mjs +1 -1
  52. package/dist/integrations/mcp/testing.d.mts +1 -1
  53. package/dist/integrations/mcp/testing.mjs +1 -1
  54. package/dist/integrations/websocket-redis.d.mts +1 -1
  55. package/dist/integrations/websocket.d.mts +1 -1
  56. package/dist/{loadResourcesFromEntry-BLMEI2Xa.mjs → loadResourcesFromEntry-DCjYMgfC.mjs} +1 -1
  57. package/dist/{mcpPlugin-7vGV51ED.mjs → mcpPlugin-Dbgz9_Rj.mjs} +5 -5
  58. package/dist/middleware/index.d.mts +1 -1
  59. package/dist/{openapi-34T9yNwd.mjs → openapi-BRnd3pT7.mjs} +36 -9
  60. package/dist/permissions/index.d.mts +2 -2
  61. package/dist/pipeline/index.d.mts +1 -1
  62. package/dist/plugins/index.d.mts +5 -59
  63. package/dist/plugins/index.mjs +7 -7
  64. package/dist/plugins/tracing-entry.d.mts +1 -1
  65. package/dist/plugins/tracing-entry.mjs +1 -1
  66. package/dist/preloadResources-BOXzPhKU.mjs +45 -0
  67. package/dist/presets/filesUpload.d.mts +4 -4
  68. package/dist/presets/index.d.mts +1 -1
  69. package/dist/presets/multiTenant.d.mts +1 -1
  70. package/dist/presets/search.d.mts +2 -2
  71. package/dist/{queryCachePlugin-Biqzfbi5.d.mts → queryCachePlugin-Do4P_WBC.d.mts} +2 -2
  72. package/dist/{redis-Cyzrz6SX.d.mts → redis-cpqVb85w.d.mts} +1 -1
  73. package/dist/{redis-stream-DT-YjzrB.d.mts → redis-stream-BuiAS3aE.d.mts} +1 -1
  74. package/dist/registry/index.d.mts +30 -8
  75. package/dist/registry/index.mjs +2 -2
  76. package/dist/{registry-BBE23CDj.mjs → registry-BrlHkrRS.mjs} +14 -8
  77. package/dist/{routerShared-CZV5aabX.mjs → routerShared-ClV4cCaa.mjs} +37 -13
  78. package/dist/scope/index.d.mts +2 -2
  79. package/dist/testing/index.d.mts +2 -25
  80. package/dist/testing/index.mjs +2 -45
  81. package/dist/testing/storageContract.d.mts +1 -1
  82. package/dist/types/index.d.mts +4 -4
  83. package/dist/types/storage.d.mts +1 -1
  84. package/dist/{types-BsJMEQ4D.d.mts → types-BD0BCdf6.d.mts} +1 -1
  85. package/dist/{types-D-fYtKjb.d.mts → types-Dw--SyTq.d.mts} +34 -11
  86. package/dist/utils/index.d.mts +5 -4
  87. package/dist/utils/index.mjs +1 -1
  88. package/dist/{utils-DC5ycPfr.mjs → utils-DPXZnZUx.mjs} +69 -1
  89. package/dist/{validate-By96rH0r.mjs → validate-DFIP2VrM.mjs} +65 -3
  90. package/dist/{versioning-ZwX9tmbS.d.mts → versioning-5H4930tj.d.mts} +1 -1
  91. package/package.json +3 -3
  92. /package/dist/{EventTransport-C-2oAHtw.d.mts → EventTransport-B1GFiYUy.d.mts} +0 -0
  93. /package/dist/{EventTransport-Hxvv5QQz.mjs → EventTransport-ClEDfiMS.mjs} +0 -0
  94. /package/dist/{caching-TeHE8G-v.mjs → caching-8MfrrUYU.mjs} +0 -0
  95. /package/dist/{elevation-0YBpa663.d.mts → elevation-DvwtjDwu.d.mts} +0 -0
  96. /package/dist/{errorHandler-DpoXQHZ9.mjs → errorHandler-DNPzqaTT.mjs} +0 -0
  97. /package/dist/{errorHandler-mHuyWzZE.d.mts → errorHandler-DuqTqeqn.d.mts} +0 -0
  98. /package/dist/{errors-C1lX_jlm.d.mts → errors-4ZloYFvm.d.mts} +0 -0
  99. /package/dist/{externalPaths-DFg-2KTp.d.mts → externalPaths-ClykOQqa.d.mts} +0 -0
  100. /package/dist/{generate-CYac-OLv.mjs → generate-CcM24hoq.mjs} +0 -0
  101. /package/dist/{generate-BWFwgcCM.d.mts → generate-cRnsafMS.d.mts} +0 -0
  102. /package/dist/{init-Dv71MsJr.d.mts → init-D-vrTZ1G.d.mts} +0 -0
  103. /package/dist/{init-HDvoO9L5.mjs → init-DYzfniuX.mjs} +0 -0
  104. /package/dist/{interface-CH0OQudo.d.mts → interface-BVkZsvEe.d.mts} +0 -0
  105. /package/dist/{interface-NwJ_qPlY.d.mts → interface-DoE6UoC2.d.mts} +0 -0
  106. /package/dist/{loadResources-ChQEj8ih.mjs → loadResources-fol6JuvW.mjs} +0 -0
  107. /package/dist/{metrics-TuOmguhi.mjs → metrics-B-PU4-Yu.mjs} +0 -0
  108. /package/dist/{replyHelpers-C-gD32oF.mjs → replyHelpers-boS5Rx26.mjs} +0 -0
  109. /package/dist/{schemaIR-Ctc89DSn.mjs → schemaIR-DAOvBWgT.mjs} +0 -0
  110. /package/dist/{sessionManager-BqFegc0W.d.mts → sessionManager-DlmaHUAl.d.mts} +0 -0
  111. /package/dist/{sse-BY6sTy4P.mjs → sse-jh5OqFjR.mjs} +0 -0
  112. /package/dist/{storage-D2KZJAmn.d.mts → storage-HcAgP5Ni.d.mts} +0 -0
  113. /package/dist/{store-helpers-B0sunfZZ.mjs → store-helpers-5JgaxieH.mjs} +0 -0
  114. /package/dist/{tracing-Dm8n7Cnn.d.mts → tracing-bRqqUxFO.d.mts} +0 -0
  115. /package/dist/{types-DVfpSfx2.d.mts → types-CfapW2t8.d.mts} +0 -0
  116. /package/dist/{versioning-B6mimogM.mjs → versioning-CuIwUQZ2.mjs} +0 -0
  117. /package/dist/{websocket-BkjeGZRn.d.mts → websocket-BJ0w-3Dt.d.mts} +0 -0
@@ -1,7 +1,7 @@
1
- import { rt as PresetResult } from "../index-CkW0flkU.mjs";
2
- import { i as RequestScope } from "../types-DVfpSfx2.mjs";
3
- import { c as PermissionCheck } from "../fields-Anj0xdih.mjs";
4
- import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-D2KZJAmn.mjs";
1
+ import { rt as PresetResult } from "../index-DBDgy6Hw.mjs";
2
+ import { i as RequestScope } from "../types-CfapW2t8.mjs";
3
+ import { c as PermissionCheck } from "../fields-DqZLp5d6.mjs";
4
+ import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-HcAgP5Ni.mjs";
5
5
 
6
6
  //#region src/presets/filesUpload.d.ts
7
7
  interface FilesUploadPresetRoutes {
@@ -1,4 +1,4 @@
1
- import { Kt as AnyRecord, bt as IRequestContext, d as PaginationResult, rt as PresetResult, st as ResourceConfig, yt as IControllerResponse } from "../index-CkW0flkU.mjs";
1
+ import { Kt as AnyRecord, bt as IRequestContext, d as PaginationResult, rt as PresetResult, st as ResourceConfig, yt as IControllerResponse } from "../index-DBDgy6Hw.mjs";
2
2
  import { FilesUploadPresetOptions, FilesUploadPresetPermissions, FilesUploadPresetRoutes, filesUploadPreset } from "./filesUpload.mjs";
3
3
  import { MultiTenantOptions, TenantFieldSpec, multiTenantPreset } from "./multiTenant.mjs";
4
4
  import { SearchHandler, SearchPresetOptions, SearchRouteConfig, searchPreset } from "./search.mjs";
@@ -1,4 +1,4 @@
1
- import { J as CrudRouteKey, rt as PresetResult } from "../index-CkW0flkU.mjs";
1
+ import { J as CrudRouteKey, rt as PresetResult } from "../index-DBDgy6Hw.mjs";
2
2
 
3
3
  //#region src/presets/multiTenant.d.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
- import { dt as RouteDefinition, ft as RouteMcpConfig, ht as ControllerHandler, rt as PresetResult } from "../index-CkW0flkU.mjs";
2
- import { c as PermissionCheck } from "../fields-Anj0xdih.mjs";
1
+ import { dt as RouteDefinition, ft as RouteMcpConfig, ht as ControllerHandler, rt as PresetResult } from "../index-DBDgy6Hw.mjs";
2
+ import { c as PermissionCheck } from "../fields-DqZLp5d6.mjs";
3
3
 
4
4
  //#region src/presets/search.d.ts
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { r as CacheStore } from "./interface-CH0OQudo.mjs";
2
- import { i as QueryCache } from "./QueryCache-SvmT_9ti.mjs";
1
+ import { r as CacheStore } from "./interface-BVkZsvEe.mjs";
2
+ import { i as QueryCache } from "./QueryCache-8YmAQ-zW.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
5
5
  //#region src/cache/queryCachePlugin.d.ts
@@ -1,4 +1,4 @@
1
- import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-NwJ_qPlY.mjs";
1
+ import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-DoE6UoC2.mjs";
2
2
 
3
3
  //#region src/idempotency/stores/redis.d.ts
4
4
  interface RedisClient {
@@ -1,4 +1,4 @@
1
- import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-C-2oAHtw.mjs";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-B1GFiYUy.mjs";
2
2
 
3
3
  //#region src/events/transports/redis-stream.d.ts
4
4
  interface RedisStreamLike {
@@ -1,4 +1,4 @@
1
- import { J as CrudRouteKey, R as RegisterOptions, V as ResourceDefinition, at as ResolvedTenantPurge, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-CkW0flkU.mjs";
1
+ import { J as CrudRouteKey, R as RegisterOptions, V as ResourceDefinition, at as ResolvedTenantPurge, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-DBDgy6Hw.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
3
  import { TenantPurgeProgress, TenantPurgeStrategy } from "@classytic/repo-core/repository";
4
4
 
@@ -20,7 +20,14 @@ interface AssertNoTenantDataOptions {
20
20
  interface TenantDataLeak {
21
21
  readonly resource: string;
22
22
  readonly tenantField: string;
23
- readonly strategy: TenantPurgeStrategy["type"];
23
+ /**
24
+ * Full `TenantPurgeStrategy` discriminated union — not just `.type`.
25
+ * `anonymize` carries the field map; `custom` carries the handler
26
+ * descriptor; auditors get the actionable signal without re-resolving
27
+ * the resource definition. Narrow on `.strategy.type` for typed
28
+ * access.
29
+ */
30
+ readonly strategy: TenantPurgeStrategy;
24
31
  readonly expected: number;
25
32
  readonly actual: number;
26
33
  readonly reason?: string;
@@ -64,7 +71,14 @@ declare function assertNoTenantData(registry: ResourceRegistry, options: AssertN
64
71
  interface PurgeResourceOutcome {
65
72
  readonly resource: string;
66
73
  readonly tenantField: string;
67
- readonly strategy: TenantPurgeStrategy["type"];
74
+ /**
75
+ * Full `TenantPurgeStrategy` discriminated union. Audit consumers
76
+ * narrow on `.strategy.type` for typed access — `skip` carries the
77
+ * mandatory `reason`, `anonymize` carries the field map, `custom`
78
+ * carries the handler descriptor. Pre-2.17 this was the `.type` tag
79
+ * only, which dropped the audit-critical `reason` field.
80
+ */
81
+ readonly strategy: TenantPurgeStrategy;
68
82
  readonly processed: number;
69
83
  readonly ok: boolean;
70
84
  readonly path: "purgeByField" | "legacy-deleteMany" | "skipped" | "unsupported";
@@ -92,11 +106,13 @@ interface CascadeResourceReport {
92
106
  */
93
107
  readonly deletedCount: number;
94
108
  /**
95
- * Strategy that actually ran for this resource — `'hard'` / `'soft'` /
96
- * `'anonymize'` / `'skip'`. Audit consumers branch on this to answer
97
- * "did data physically leave the system?".
109
+ * Full `TenantPurgeStrategy` that ran for this resource. Branch on
110
+ * `.strategy.type` (`'hard'` / `'soft'` / `'anonymize'` / `'skip'` /
111
+ * `'custom'`) to answer "did data physically leave the system?". The
112
+ * `skip` variant carries the mandatory `reason`; `anonymize` carries
113
+ * the field map; `custom` carries the handler descriptor.
98
114
  */
99
- readonly strategy?: TenantPurgeStrategy["type"];
115
+ readonly strategy?: TenantPurgeStrategy;
100
116
  /**
101
117
  * Where the strategy came from — `'declared'` (host wrote
102
118
  * `onTenantDelete`) or `'disabled'` (filtered out before reaching
@@ -221,11 +237,17 @@ declare function getCascadingResources(registry: ResourceRegistry): readonly str
221
237
  * cascading resource. Use for audit dashboards that answer "what
222
238
  * happens to this resource on org-delete?" without grepping the
223
239
  * source.
240
+ *
241
+ * **Returns the full `TenantPurgeStrategy` discriminated union**, not
242
+ * just its `type` tag. The `skip` variant carries a required `reason`
243
+ * field — auditors need it to sign off on every "we deliberately don't
244
+ * purge this" entry. Anonymize carries the field map. Custom carries
245
+ * the handler reference. Narrow on `.strategy.type` for typed access.
224
246
  */
225
247
  declare function getCascadingResourcesWithMetadata(registry: ResourceRegistry): readonly {
226
248
  name: string;
227
249
  tenantField: string;
228
- strategy: TenantPurgeStrategy["type"];
250
+ strategy: TenantPurgeStrategy;
229
251
  source: ResolvedTenantPurge["source"];
230
252
  priority: number;
231
253
  }[];
@@ -1,3 +1,3 @@
1
- import { a as cascadeDeleteForOrganization, c as assertNoTenantData, i as introspectionPlugin_default, n as buildResourceManifestFromRegistry, o as getCascadingResources, r as introspectionPlugin, s as getCascadingResourcesWithMetadata, t as buildResourceManifest } from "../registry-BBE23CDj.mjs";
2
- import { t as ResourceRegistry } from "../ResourceRegistry-f48hFk3m.mjs";
1
+ import { a as cascadeDeleteForOrganization, c as assertNoTenantData, i as introspectionPlugin_default, n as buildResourceManifestFromRegistry, o as getCascadingResources, r as introspectionPlugin, s as getCascadingResourcesWithMetadata, t as buildResourceManifest } from "../registry-BrlHkrRS.mjs";
2
+ import { t as ResourceRegistry } from "../ResourceRegistry-CzSzVK1m.mjs";
3
3
  export { ResourceRegistry, assertNoTenantData, buildResourceManifest, buildResourceManifestFromRegistry, cascadeDeleteForOrganization, getCascadingResources, getCascadingResourcesWithMetadata, introspectionPlugin_default as introspectionPlugin, introspectionPlugin as introspectionPluginFn };
@@ -59,7 +59,7 @@ async function assertNoTenantData(registry, options) {
59
59
  if (actual !== expected) leaks.push({
60
60
  resource: r.name,
61
61
  tenantField,
62
- strategy: strategy.type,
62
+ strategy,
63
63
  expected,
64
64
  actual
65
65
  });
@@ -88,7 +88,7 @@ async function purgeResource(resourceName, tenantField, organizationId, resolved
88
88
  if (strategy.type === "skip") return {
89
89
  resource: resourceName,
90
90
  tenantField,
91
- strategy: "skip",
91
+ strategy,
92
92
  processed: 0,
93
93
  ok: true,
94
94
  path: "skipped",
@@ -103,7 +103,7 @@ async function purgeResource(resourceName, tenantField, organizationId, resolved
103
103
  return {
104
104
  resource: resourceName,
105
105
  tenantField,
106
- strategy: result.strategy,
106
+ strategy,
107
107
  processed: result.processed,
108
108
  ok: result.ok,
109
109
  path: "purgeByField",
@@ -114,7 +114,7 @@ async function purgeResource(resourceName, tenantField, organizationId, resolved
114
114
  if (strategy.type !== "hard") return {
115
115
  resource: resourceName,
116
116
  tenantField,
117
- strategy: strategy.type,
117
+ strategy,
118
118
  processed: 0,
119
119
  ok: false,
120
120
  path: "unsupported",
@@ -127,7 +127,7 @@ async function purgeResource(resourceName, tenantField, organizationId, resolved
127
127
  if (!op) return {
128
128
  resource: resourceName,
129
129
  tenantField,
130
- strategy: "hard",
130
+ strategy,
131
131
  processed: 0,
132
132
  ok: false,
133
133
  path: "unsupported",
@@ -142,7 +142,7 @@ async function purgeResource(resourceName, tenantField, organizationId, resolved
142
142
  return {
143
143
  resource: resourceName,
144
144
  tenantField,
145
- strategy: "hard",
145
+ strategy,
146
146
  processed: typeof result === "number" ? result : typeof result?.deletedCount === "number" ? result.deletedCount : typeof result?.count === "number" ? result.count : -1,
147
147
  ok: true,
148
148
  path: "legacy-deleteMany",
@@ -152,7 +152,7 @@ async function purgeResource(resourceName, tenantField, organizationId, resolved
152
152
  return {
153
153
  resource: resourceName,
154
154
  tenantField,
155
- strategy: "hard",
155
+ strategy,
156
156
  processed: 0,
157
157
  ok: false,
158
158
  path: "legacy-deleteMany",
@@ -178,6 +178,12 @@ function getCascadingResources(registry) {
178
178
  * cascading resource. Use for audit dashboards that answer "what
179
179
  * happens to this resource on org-delete?" without grepping the
180
180
  * source.
181
+ *
182
+ * **Returns the full `TenantPurgeStrategy` discriminated union**, not
183
+ * just its `type` tag. The `skip` variant carries a required `reason`
184
+ * field — auditors need it to sign off on every "we deliberately don't
185
+ * purge this" entry. Anonymize carries the field map. Custom carries
186
+ * the handler reference. Narrow on `.strategy.type` for typed access.
181
187
  */
182
188
  function getCascadingResourcesWithMetadata(registry) {
183
189
  return registry.getAll().filter((r) => isResourceCascading(r)).map((r) => {
@@ -191,7 +197,7 @@ function getCascadingResourcesWithMetadata(registry) {
191
197
  return {
192
198
  name: r.name,
193
199
  tenantField,
194
- strategy: r0.strategy.type,
200
+ strategy: r0.strategy,
195
201
  source: r0.source,
196
202
  priority: r0.priority
197
203
  };
@@ -55,9 +55,13 @@ function applyFieldMask(data, fieldMask) {
55
55
  return data;
56
56
  }
57
57
  /**
58
- * Create IRequestContext from Fastify request
59
- *
60
- * Extracts framework-agnostic context from Fastify-specific request object
58
+ * Project `FastifyRequest` into the curated `IRequestContext` shape that
59
+ * arc's controllers receive. This is a developer-facing facade, not a
60
+ * portability boundary — arc is Fastify-only. The projection lifts the
61
+ * fields tenant-scoped controllers reach for (`organizationId`,
62
+ * `teamId`, `scope`, `context`, server accessor) onto a flat, typed
63
+ * surface so handler code doesn't have to dig through Fastify's
64
+ * decorator surface or `req.metadata._scope` internals.
61
65
  */
62
66
  function createRequestContext(req) {
63
67
  const reqWithExtras = req;
@@ -145,12 +149,33 @@ function computeFieldCapabilities(fieldPerms, effectiveRoles) {
145
149
  return caps;
146
150
  }
147
151
  /**
148
- * Send IControllerResponse via Fastify reply
152
+ * Send a controller's `IControllerResponse` through Fastify's reply.
153
+ *
154
+ * The `IControllerResponse<T>` envelope is arc's internal pipeline
155
+ * contract — it's how custom `status`, `meta`, `headers`, and field
156
+ * permissions thread from a handler through interceptors to the wire
157
+ * without each step having to touch `reply` directly. Not a portability
158
+ * abstraction (arc is Fastify-only); a pipeline-internal one.
149
159
  *
150
- * Converts framework-agnostic response to Fastify response
151
- * Applies field masking if specified in request
160
+ * **Auto-envelope of bare returns.** Custom route handlers are
161
+ * expected to return the envelope shape so this function can apply
162
+ * field permissions, attach `fieldCaps`, and flatten paginated lists.
163
+ * A handler that returns raw `T` (array, plain object, primitive) used
164
+ * to silently produce a broken response — `response.data` was
165
+ * `undefined`, the body sent was `undefined`, and any declared
166
+ * `fields:` permission map was bypassed (the leak shape mentora
167
+ * flagged). 2.17 detects bare returns and wraps them into
168
+ * `{ data: response, status: 200 }` before the field-permission
169
+ * pipeline runs, so `fields:` applies to ALL custom routes whether the
170
+ * handler envelopes its return value or not. The envelope shape is
171
+ * still the canonical contract — auto-wrapping is a safety net, not
172
+ * an invitation to drop the envelope.
152
173
  */
153
174
  function sendControllerResponse(reply, response, request) {
175
+ if (response === null || typeof response !== "object" || !Object.hasOwn(response, "data")) response = {
176
+ data: response,
177
+ status: 200
178
+ };
154
179
  const reqWithExtras = request;
155
180
  const fieldMaskConfig = reqWithExtras?.fieldMask;
156
181
  const arcMeta = reqWithExtras?.arc;
@@ -207,18 +232,17 @@ function sendControllerResponse(reply, response, request) {
207
232
  reply.code(status).send(filteredData);
208
233
  }
209
234
  /**
210
- * Create Fastify route handler from IController method
211
- *
212
- * Wraps framework-agnostic controller method in Fastify-specific handler
235
+ * Bridge an `IController` method (`(ctx: IRequestContext) => Promise<IControllerResponse<T>>`)
236
+ * into a Fastify route handler. Translates between arc's curated request
237
+ * context surface and Fastify's request/reply, and routes the envelope
238
+ * through `sendControllerResponse` so the pipeline applies field
239
+ * permissions, status, and meta uniformly. Internal plumbing, not a
240
+ * portability layer — arc is Fastify-only.
213
241
  *
214
242
  * @example
215
243
  * ```typescript
216
244
  * const controller = new BaseController(repository);
217
- *
218
- * // Create Fastify handler
219
245
  * const listHandler = createFastifyHandler(controller.list.bind(controller));
220
- *
221
- * // Register route
222
246
  * fastify.get('/products', listHandler);
223
247
  * ```
224
248
  */
@@ -1,5 +1,5 @@
1
- import { C as isMember, D as requireOrgId, E as requireClientId, O as requireTeamId, S as isElevated, T as isService, _ as getTenantFromRequest, a as getAncestorOrgIds, b as hasOrgAccess, c as getMandate, d as getOrgRoles, f as getRequestScope, g as getTeamId, h as getServiceScopes, i as RequestScope, k as requireUserId, l as getOrgContext, m as getScopeContextMap, n as Mandate, o as getClientId, p as getScopeContext, r as PUBLIC_SCOPE, s as getDPoPJkt, t as AUTHENTICATED_SCOPE, u as getOrgId, v as getUserId, w as isOrgInScope, x as isAuthenticated, y as getUserRoles } from "../types-DVfpSfx2.mjs";
2
- import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-0YBpa663.mjs";
1
+ import { C as isMember, D as requireOrgId, E as requireClientId, O as requireTeamId, S as isElevated, T as isService, _ as getTenantFromRequest, a as getAncestorOrgIds, b as hasOrgAccess, c as getMandate, d as getOrgRoles, f as getRequestScope, g as getTeamId, h as getServiceScopes, i as RequestScope, k as requireUserId, l as getOrgContext, m as getScopeContextMap, n as Mandate, o as getClientId, p as getScopeContext, r as PUBLIC_SCOPE, s as getDPoPJkt, t as AUTHENTICATED_SCOPE, u as getOrgId, v as getUserId, w as isOrgInScope, x as isAuthenticated, y as getUserRoles } from "../types-CfapW2t8.mjs";
2
+ import { i as elevationPlugin, n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-DvwtjDwu.mjs";
3
3
  import { FastifyReply, FastifyRequest } from "fastify";
4
4
 
5
5
  //#region src/scope/rateLimitKey.d.ts
@@ -1,5 +1,5 @@
1
- import { Kt as AnyRecord, V as ResourceDefinition } from "../index-CkW0flkU.mjs";
2
- import { d as ResourceLike, r as CreateAppOptions } from "../types-D-fYtKjb.mjs";
1
+ import { Kt as AnyRecord, V as ResourceDefinition } from "../index-DBDgy6Hw.mjs";
2
+ import { d as preloadResourcesAsync, r as CreateAppOptions, u as preloadResources } from "../types-Dw--SyTq.mjs";
3
3
  import { StorageContractSetup, StorageContractSetupResult, runStorageContract } from "./storageContract.mjs";
4
4
  import { FastifyInstance, FastifyServerOptions } from "fastify";
5
5
  import { Mock } from "vitest";
@@ -464,29 +464,6 @@ declare function createTestTimer(): {
464
464
  reset: () => void;
465
465
  };
466
466
  //#endregion
467
- //#region src/testing/preloadResources.d.ts
468
- /** Eager glob result: `{ '/path/to/file.ts': resourceModule }` */
469
- type EagerGlobResult = Record<string, unknown>;
470
- /** Lazy glob result: `{ '/path/to/file.ts': () => Promise<unknown> }` */
471
- type LazyGlobResult = Record<string, () => Promise<unknown>>;
472
- /**
473
- * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
474
- *
475
- * Accepts either:
476
- * - `{ import: 'default' }` form: values are the resource directly
477
- * - default form: values are the full module — picks first export with `toPlugin()`
478
- *
479
- * Throws if any module doesn't yield a valid `ResourceLike`.
480
- */
481
- declare function preloadResources(globResult: EagerGlobResult): ResourceLike[];
482
- /**
483
- * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
484
- *
485
- * Use this when resources depend on prior bootstrap (e.g., engine init) and
486
- * cannot be evaluated at import time of the preload file.
487
- */
488
- declare function preloadResourcesAsync(globResult: LazyGlobResult): Promise<ResourceLike[]>;
489
- //#endregion
490
467
  //#region src/testing/testApp.d.ts
491
468
  type DbMode = "in-memory" | {
492
469
  uri: string;
@@ -1,4 +1,5 @@
1
1
  import { t as CRUD_OPERATIONS } from "../constants-TrJVIJl0.mjs";
2
+ import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-BOXzPhKU.mjs";
2
3
  import { runStorageContract } from "./storageContract.mjs";
3
4
  import Fastify from "fastify";
4
5
  import { afterAll, describe, expect, it, vi } from "vitest";
@@ -970,50 +971,6 @@ function createTestTimer() {
970
971
  };
971
972
  }
972
973
  //#endregion
973
- //#region src/testing/preloadResources.ts
974
- /**
975
- * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
976
- *
977
- * Accepts either:
978
- * - `{ import: 'default' }` form: values are the resource directly
979
- * - default form: values are the full module — picks first export with `toPlugin()`
980
- *
981
- * Throws if any module doesn't yield a valid `ResourceLike`.
982
- */
983
- function preloadResources(globResult) {
984
- const resources = [];
985
- for (const [path, value] of Object.entries(globResult)) {
986
- const resource = pickResource(value);
987
- if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
988
- resources.push(resource);
989
- }
990
- return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
991
- }
992
- /**
993
- * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
994
- *
995
- * Use this when resources depend on prior bootstrap (e.g., engine init) and
996
- * cannot be evaluated at import time of the preload file.
997
- */
998
- async function preloadResourcesAsync(globResult) {
999
- return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
1000
- const resource = pickResource(await loader());
1001
- if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
1002
- return resource;
1003
- }))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
1004
- }
1005
- function pickResource(value) {
1006
- if (!value || typeof value !== "object") return void 0;
1007
- if (typeof value.toPlugin === "function") return value;
1008
- const mod = value;
1009
- const candidates = [
1010
- mod.default,
1011
- mod.resource,
1012
- ...Object.values(mod)
1013
- ];
1014
- for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
1015
- }
1016
- //#endregion
1017
974
  //#region src/testing/testApp.ts
1018
975
  /**
1019
976
  * createTestApp — test app factory for arc
@@ -1079,7 +1036,7 @@ function pickDefaultAuth(authMode, callerAuth) {
1079
1036
  };
1080
1037
  }
1081
1038
  async function createTestApp(options = {}) {
1082
- const { createApp } = await import("../createApp-DNccuhyI.mjs").then((n) => n.r);
1039
+ const { createApp } = await import("../createApp-DdPBtmzp.mjs").then((n) => n.r);
1083
1040
  const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
1084
1041
  let dbHandle;
1085
1042
  let dbUri;
@@ -1,4 +1,4 @@
1
- import { t as Storage } from "../storage-D2KZJAmn.mjs";
1
+ import { t as Storage } from "../storage-HcAgP5Ni.mjs";
2
2
 
3
3
  //#region src/testing/storageContract.d.ts
4
4
  interface StorageContractSetupResult {
@@ -1,5 +1,5 @@
1
- import { $ as OnTenantDeleteConfig, A as RequestIdOptions, Bt as AuthHelpers, C as RequestContext, D as HealthCheck, E as GracefulShutdownOptions, F as FastifyWithDecorators, G as ActionsMap, Gt as TokenPair, H as ActionDefinition, Ht as Authenticator, I as MiddlewareHandler, J as CrudRouteKey, Jt as ArcRequest, K as ArcFieldRule, Kt as AnyRecord, L as RequestWithExtras, M as EventsDecorator, N as FastifyRequestExtras, O as HealthOptions, P as FastifyWithAuth, Q as MiddlewareConfig, Qt as UserOrganization, S as QueryParserInterface, T as CrudRouterOptions, U as ActionEntry, Ut as AuthenticatorContext, Vt as AuthPluginOptions, W as ActionHandlerFn, Wt as JwtContext, X as EventDefinition, Xt as ObjectId, Y as CrudSchemas, Yt as JWTPayload, Z as FieldRule, Zt as UserLike, _ as ControllerQueryOptions, _t as FastifyHandler, a as InferAdapterDoc, at as ResolvedTenantPurge, b as ParsedQuery, bt as IRequestContext, c as TypedController, cn as BaseControllerOptions, ct as ResourceHookContext, d as PaginationResult, dt as RouteDefinition, et as OpenApiSchemas, f as IntrospectionData, ft as RouteMcpConfig, g as ArcInternalMetadata, gt as ControllerLike, h as ResourceMetadata, ht as ControllerHandler, i as ValidationResult, it as RateLimitConfig, j as ArcDecorator, k as IntrospectionPluginOptions, l as TypedRepository, lt as ResourceHooks, m as RegistryStats, mt as RouteSchemaOptions, n as ConfigError, nt as PresetHook, o as InferDocType, ot as ResourceCacheConfig, p as RegistryEntry, pt as RouteMethod, q as CrudController, qt as ApiResponse, r as ValidateOptions, rt as PresetResult, s as InferResourceDoc, st as ResourceConfig, t as RouteHandlerMethod, tt as PresetFunction, u as TypedResourceConfig, ut as ResourcePermissions, v as LookupOption, vt as IController, w as ServiceContext, x as PopulateOption, xt as RouteHandler, y as OwnershipCheck, yt as IControllerResponse } from "../index-CkW0flkU.mjs";
2
- import { i as RequestScope } from "../types-DVfpSfx2.mjs";
3
- import { c as PermissionCheck, d as UserBase, l as PermissionContext, u as PermissionResult } from "../fields-Anj0xdih.mjs";
4
- import { n as ElevationOptions, t as ElevationEvent } from "../elevation-0YBpa663.mjs";
1
+ import { $ as OnTenantDeleteConfig, A as RequestIdOptions, Bt as AuthHelpers, C as RequestContext, D as HealthCheck, E as GracefulShutdownOptions, F as FastifyWithDecorators, G as ActionsMap, Gt as TokenPair, H as ActionDefinition, Ht as Authenticator, I as MiddlewareHandler, J as CrudRouteKey, Jt as ArcRequest, K as ArcFieldRule, Kt as AnyRecord, L as RequestWithExtras, M as EventsDecorator, N as FastifyRequestExtras, O as HealthOptions, P as FastifyWithAuth, Q as MiddlewareConfig, Qt as UserOrganization, S as QueryParserInterface, T as CrudRouterOptions, U as ActionEntry, Ut as AuthenticatorContext, Vt as AuthPluginOptions, W as ActionHandlerFn, Wt as JwtContext, X as EventDefinition, Xt as ObjectId, Y as CrudSchemas, Yt as JWTPayload, Z as FieldRule, Zt as UserLike, _ as ControllerQueryOptions, _t as FastifyHandler, a as InferAdapterDoc, at as ResolvedTenantPurge, b as ParsedQuery, bt as IRequestContext, c as TypedController, cn as BaseControllerOptions, ct as ResourceHookContext, d as PaginationResult, dt as RouteDefinition, et as OpenApiSchemas, f as IntrospectionData, ft as RouteMcpConfig, g as ArcInternalMetadata, gt as ControllerLike, h as ResourceMetadata, ht as ControllerHandler, i as ValidationResult, it as RateLimitConfig, j as ArcDecorator, k as IntrospectionPluginOptions, l as TypedRepository, lt as ResourceHooks, m as RegistryStats, mt as RouteSchemaOptions, n as ConfigError, nt as PresetHook, o as InferDocType, ot as ResourceCacheConfig, p as RegistryEntry, pt as RouteMethod, q as CrudController, qt as ApiResponse, r as ValidateOptions, rt as PresetResult, s as InferResourceDoc, st as ResourceConfig, t as RouteHandlerMethod, tt as PresetFunction, u as TypedResourceConfig, ut as ResourcePermissions, v as LookupOption, vt as IController, w as ServiceContext, x as PopulateOption, xt as RouteHandler, y as OwnershipCheck, yt as IControllerResponse } from "../index-DBDgy6Hw.mjs";
2
+ import { i as RequestScope } from "../types-CfapW2t8.mjs";
3
+ import { c as PermissionCheck, d as UserBase, l as PermissionContext, u as PermissionResult } from "../fields-DqZLp5d6.mjs";
4
+ import { n as ElevationOptions, t as ElevationEvent } from "../elevation-DvwtjDwu.mjs";
5
5
  export { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ApiResponse, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OnTenantDeleteConfig, OpenApiSchemas, OwnershipCheck, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext, PermissionResult, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryParserInterface, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResolvedTenantPurge, ResourceCacheConfig, ResourceConfig, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult };
@@ -1,2 +1,2 @@
1
- import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-D2KZJAmn.mjs";
1
+ import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-HcAgP5Ni.mjs";
2
2
  export { Storage, StorageContext, StorageFile, StorageReadRange, StorageReadResult, StorageUploadInput };
@@ -1,4 +1,4 @@
1
- import { V as ResourceDefinition } from "./index-CkW0flkU.mjs";
1
+ import { V as ResourceDefinition } from "./index-DBDgy6Hw.mjs";
2
2
  import { z } from "zod";
3
3
 
4
4
  //#region src/integrations/mcp/types.d.ts
@@ -1,13 +1,13 @@
1
- import { r as CacheStore } from "./interface-CH0OQudo.mjs";
2
- import { Ht as Authenticator } from "./index-CkW0flkU.mjs";
3
- import { n as ElevationOptions } from "./elevation-0YBpa663.mjs";
4
- import { a as EventTransport } from "./EventTransport-C-2oAHtw.mjs";
5
- import { t as ExternalOpenApiPaths } from "./externalPaths-DFg-2KTp.mjs";
6
- import { r as QueryCachePluginOptions } from "./queryCachePlugin-Biqzfbi5.mjs";
7
- import { t as EventPluginOptions } from "./eventPlugin-CtHC_av1.mjs";
8
- import { _ as HealthOptions, f as CachingOptions, l as SSEOptions, o as MetricsOptions, t as VersioningOptions } from "./versioning-ZwX9tmbS.mjs";
9
- import { t as ErrorHandlerOptions } from "./errorHandler-mHuyWzZE.mjs";
10
- import { r as IdempotencyStore } from "./interface-NwJ_qPlY.mjs";
1
+ import { r as CacheStore } from "./interface-BVkZsvEe.mjs";
2
+ import { Ht as Authenticator } from "./index-DBDgy6Hw.mjs";
3
+ import { n as ElevationOptions } from "./elevation-DvwtjDwu.mjs";
4
+ import { a as EventTransport } from "./EventTransport-B1GFiYUy.mjs";
5
+ import { t as ExternalOpenApiPaths } from "./externalPaths-ClykOQqa.mjs";
6
+ import { r as QueryCachePluginOptions } from "./queryCachePlugin-Do4P_WBC.mjs";
7
+ import { t as EventPluginOptions } from "./eventPlugin-DjEx_J3j.mjs";
8
+ import { _ as HealthOptions, f as CachingOptions, l as SSEOptions, o as MetricsOptions, t as VersioningOptions } from "./versioning-5H4930tj.mjs";
9
+ import { t as ErrorHandlerOptions } from "./errorHandler-DuqTqeqn.mjs";
10
+ import { r as IdempotencyStore } from "./interface-DoE6UoC2.mjs";
11
11
  import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify";
12
12
 
13
13
  //#region src/factory/loadResources.d.ts
@@ -203,6 +203,29 @@ interface LoadResourcesOptions<TContext = unknown> {
203
203
  */
204
204
  declare function loadResources<TContext = unknown>(dir: string, options?: LoadResourcesOptions<TContext>): Promise<ResourceLike[]>;
205
205
  //#endregion
206
+ //#region src/testing/preloadResources.d.ts
207
+ /** Eager glob result: `{ '/path/to/file.ts': resourceModule }` */
208
+ type EagerGlobResult = Record<string, unknown>;
209
+ /** Lazy glob result: `{ '/path/to/file.ts': () => Promise<unknown> }` */
210
+ type LazyGlobResult = Record<string, () => Promise<unknown>>;
211
+ /**
212
+ * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
213
+ *
214
+ * Accepts either:
215
+ * - `{ import: 'default' }` form: values are the resource directly
216
+ * - default form: values are the full module — picks first export with `toPlugin()`
217
+ *
218
+ * Throws if any module doesn't yield a valid `ResourceLike`.
219
+ */
220
+ declare function preloadResources(globResult: EagerGlobResult): ResourceLike[];
221
+ /**
222
+ * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
223
+ *
224
+ * Use this when resources depend on prior bootstrap (e.g., engine init) and
225
+ * cannot be evaluated at import time of the preload file.
226
+ */
227
+ declare function preloadResourcesAsync(globResult: LazyGlobResult): Promise<ResourceLike[]>;
228
+ //#endregion
206
229
  //#region src/factory/types.d.ts
207
230
  type CorsOptions = Record<string, unknown> & {
208
231
  origin?: unknown;
@@ -1041,4 +1064,4 @@ interface RawBodyOptions {
1041
1064
  runFirst?: boolean;
1042
1065
  }
1043
1066
  //#endregion
1044
- export { CustomPluginAuthOption as a, RawBodyOptions as c, ResourceLike as d, ResourceModule as f, CustomAuthenticatorOption as i, UnderPressureOptions as l, BetterAuthOption as n, JwtAuthOption as o, loadResources as p, CreateAppOptions as r, MultipartOptions as s, AuthOption as t, LoadResourcesOptions as u };
1067
+ export { CustomPluginAuthOption as a, RawBodyOptions as c, preloadResourcesAsync as d, LoadResourcesOptions as f, loadResources as h, CustomAuthenticatorOption as i, UnderPressureOptions as l, ResourceModule as m, BetterAuthOption as n, JwtAuthOption as o, ResourceLike as p, CreateAppOptions as r, MultipartOptions as s, AuthOption as t, preloadResources as u };
@@ -1,6 +1,6 @@
1
- import { Kt as AnyRecord, S as QueryParserInterface, Zt as UserLike, b as ParsedQuery, et as OpenApiSchemas, st as ResourceConfig } from "../index-CkW0flkU.mjs";
2
- import { n as ErrorMapper } from "../errorHandler-mHuyWzZE.mjs";
3
- import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createDomainError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorOptions, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-C1lX_jlm.mjs";
1
+ import { Kt as AnyRecord, S as QueryParserInterface, Zt as UserLike, b as ParsedQuery, et as OpenApiSchemas, st as ResourceConfig } from "../index-DBDgy6Hw.mjs";
2
+ import { n as ErrorMapper } from "../errorHandler-DuqTqeqn.mjs";
3
+ import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createDomainError, i as ForbiddenError, l as ServiceUnavailableError, m as isArcError, n as ConflictError, o as OrgAccessDeniedError, p as createError, r as ErrorOptions, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-4ZloYFvm.mjs";
4
4
  import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
5
5
  import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
6
6
 
@@ -1144,7 +1144,8 @@ declare function simpleEqualityMatcher(item: unknown, filters: Record<string, un
1144
1144
  * State Machine Utility
1145
1145
  *
1146
1146
  * Pure utility for validating state transitions in workflow systems.
1147
- * Zero dependencies, framework-agnostic.
1147
+ * Zero runtime dependencies — usable from any layer of arc (controllers,
1148
+ * hooks, pipeline steps, tests).
1148
1149
  *
1149
1150
  * @example
1150
1151
  * const orderState = createStateMachine('Order', {
@@ -1,5 +1,5 @@
1
1
  import { a as OrgAccessDeniedError, c as ServiceUnavailableError, d as createDomainError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-j4aJm1Wg.mjs";
2
- import { A as assertValidConfig, C as withCompensation, D as CircuitState, E as CircuitBreakerRegistry, M as validateResourceConfig, N as simpleEqualityMatcher, O as createCircuitBreaker, S as defineCompensation, T as CircuitBreakerError, _ as ArcQueryParser, a as bareListResponse, b as defineGuard, c as errorDetailSchema, d as keysetListResponse, f as listResponse, g as responses, h as queryParams, i as aggregateListResponse, j as formatValidationErrors, k as createCircuitBreakerRegistry, l as getDefaultCrudSchemas, m as paginationSchema, n as createStateMachine, o as deleteResponse, p as offsetListResponse, r as scheduleBackground, s as errorContractSchema, t as getUserId, u as getListQueryParams, v as createQueryParser, w as CircuitBreaker, x as defineErrorMapper, y as handleRaw } from "../utils-DC5ycPfr.mjs";
2
+ import { A as assertValidConfig, C as withCompensation, D as CircuitState, E as CircuitBreakerRegistry, M as validateResourceConfig, N as simpleEqualityMatcher, O as createCircuitBreaker, S as defineCompensation, T as CircuitBreakerError, _ as ArcQueryParser, a as bareListResponse, b as defineGuard, c as errorDetailSchema, d as keysetListResponse, f as listResponse, g as responses, h as queryParams, i as aggregateListResponse, j as formatValidationErrors, k as createCircuitBreakerRegistry, l as getDefaultCrudSchemas, m as paginationSchema, n as createStateMachine, o as deleteResponse, p as offsetListResponse, r as scheduleBackground, s as errorContractSchema, t as getUserId, u as getListQueryParams, v as createQueryParser, w as CircuitBreaker, x as defineErrorMapper, y as handleRaw } from "../utils-DPXZnZUx.mjs";
3
3
  import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-De34B1ZG.mjs";
4
4
  import { t as hasEvents } from "../typeGuards-BzkXkvVv.mjs";
5
5
  export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UnauthorizedError, ValidationError, aggregateListResponse, assertValidConfig, bareListResponse, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createDomainError, createError, createQueryParser, createStateMachine, defineCompensation, defineErrorMapper, defineGuard, deleteResponse, errorContractSchema, errorDetailSchema, formatValidationErrors, getDefaultCrudSchemas, getListQueryParams, getUserId, handleRaw, hasEvents, isArcError, isJsonSchema, isZodSchema, keysetListResponse, listResponse, offsetListResponse, paginationSchema, queryParams, responses, scheduleBackground, simpleEqualityMatcher, toJsonSchema, validateResourceConfig, withCompensation };
@@ -142,7 +142,10 @@ function validateResourceConfig(config, options = {}) {
142
142
  suggestion: `Change to "${config.prefix.slice(0, -1)}"`
143
143
  });
144
144
  }
145
- if (config.routes) validateRoutes(config.routes, errors);
145
+ if (config.routes) {
146
+ validateRoutes(config.routes, errors);
147
+ validateRouteCrudCollisions(config, errors);
148
+ }
146
149
  return {
147
150
  valid: errors.length === 0,
148
151
  errors,
@@ -210,6 +213,71 @@ function validatePresetOptions(preset, warnings) {
210
213
  suggestion: validOptions.length > 0 ? `Valid options: ${validOptions.join(", ")}` : `Preset "${preset.name}" has no configurable options`
211
214
  });
212
215
  }
216
+ /**
217
+ * Detect collisions between user-declared `routes:` entries and the
218
+ * auto-CRUD route table that `createCrudRouter` will register.
219
+ *
220
+ * Without this check, a custom `POST /` on the same prefix as auto-CRUD's
221
+ * `create` boots fine through `defineResource()` and only blows up at
222
+ * `app.register()` time with Fastify's opaque `FST_ERR_DUPLICATED_ROUTE`.
223
+ * That error doesn't mention `disabledRoutes` and doesn't distinguish
224
+ * "you collided with auto-CRUD" from "you collided with yourself" — so
225
+ * the consumer has to grep arc's source to discover the fix.
226
+ *
227
+ * We surface the fix in the error message, naming the conflicting op
228
+ * and the literal line to add.
229
+ *
230
+ * Honored config:
231
+ * - `disableDefaultRoutes: true` → no auto-CRUD, no collisions possible
232
+ * - `disabledRoutes: ['create']` → that op's path is free for custom use
233
+ * - `updateMethod: 'PUT' | 'both'` → update slot covers PUT/PATCH accordingly
234
+ */
235
+ function validateRouteCrudCollisions(config, errors) {
236
+ if (config.disableDefaultRoutes) return;
237
+ if (!config.routes || config.routes.length === 0) return;
238
+ const updateMethod = config.updateMethod ?? "PATCH";
239
+ const updateMethods = updateMethod === "both" ? ["PUT", "PATCH"] : [updateMethod];
240
+ const disabled = new Set(config.disabledRoutes ?? []);
241
+ const crudTable = [
242
+ {
243
+ op: "list",
244
+ method: "GET",
245
+ path: "/"
246
+ },
247
+ {
248
+ op: "get",
249
+ method: "GET",
250
+ path: "/:id"
251
+ },
252
+ {
253
+ op: "create",
254
+ method: "POST",
255
+ path: "/"
256
+ },
257
+ ...updateMethods.map((m) => ({
258
+ op: "update",
259
+ method: m,
260
+ path: "/:id"
261
+ })),
262
+ {
263
+ op: "delete",
264
+ method: "DELETE",
265
+ path: "/:id"
266
+ }
267
+ ];
268
+ for (const [i, route] of config.routes.entries()) {
269
+ if (!route.method || !route.path) continue;
270
+ for (const crud of crudTable) {
271
+ if (disabled.has(crud.op)) continue;
272
+ if (route.method !== crud.method || route.path !== crud.path) continue;
273
+ errors.push({
274
+ field: `routes[${i}]`,
275
+ message: `Route ${route.method} ${route.path} collides with auto-CRUD "${crud.op}"`,
276
+ suggestion: `Add \`disabledRoutes: ['${crud.op}']\` to suppress the auto-CRUD route, or move your custom handler to a different path. Set \`disableDefaultRoutes: true\` to disable all auto-CRUD on this resource.`
277
+ });
278
+ }
279
+ }
280
+ }
213
281
  function validateRoutes(routes, errors) {
214
282
  const validMethods = [
215
283
  "GET",