@classytic/arc 2.8.4 → 2.9.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 (130) hide show
  1. package/README.md +116 -5
  2. package/dist/{BaseController-DAGGc5Xn.mjs → BaseController-Vu2yc56T.mjs} +188 -102
  3. package/dist/EventTransport-CqZ8FyM_.d.mts +293 -0
  4. package/dist/adapters/index.d.mts +2 -2
  5. package/dist/audit/index.d.mts +100 -11
  6. package/dist/audit/index.mjs +71 -18
  7. package/dist/auth/index.d.mts +15 -7
  8. package/dist/auth/index.mjs +13 -6
  9. package/dist/{betterAuthOpenApi-C5lDyRH2.mjs → betterAuthOpenApi--rdY15Ld.mjs} +1 -1
  10. package/dist/cache/index.d.mts +71 -1
  11. package/dist/cache/index.mjs +96 -3
  12. package/dist/cli/commands/docs.mjs +1 -1
  13. package/dist/cli/commands/generate.mjs +1 -1
  14. package/dist/core/index.d.mts +3 -3
  15. package/dist/core/index.mjs +4 -5
  16. package/dist/{core-DKSwNSXf.mjs → core-DNncu0xF.mjs} +1 -1
  17. package/dist/{createActionRouter-Df1BuawX.mjs → createActionRouter-DH1YFL9m.mjs} +3 -3
  18. package/dist/{createApp-BOYjBgdI.mjs → createApp-CBJUJKGP.mjs} +6 -5
  19. package/dist/{defineResource-Bb_Bdhtw.mjs → defineResource-C__jkwvs.mjs} +22 -57
  20. package/dist/docs/index.d.mts +1 -1
  21. package/dist/docs/index.mjs +1 -1
  22. package/dist/dynamic/index.d.mts +2 -2
  23. package/dist/dynamic/index.mjs +3 -3
  24. package/dist/{elevation-BBGFjzIP.mjs → elevation-DxQ6ACbt.mjs} +20 -6
  25. package/dist/{errorHandler-mzqk4cGl.mjs → errorHandler-CZDW4EXS.mjs} +59 -7
  26. package/dist/{errorHandler-CdZDavNH.d.mts → errorHandler-DixGcttC.d.mts} +37 -2
  27. package/dist/{eventPlugin-CVxlE6De.d.mts → eventPlugin-BxvaCIZF.d.mts} +14 -2
  28. package/dist/{eventPlugin-D91S2YF4.mjs → eventPlugin-Dl7MoVWH.mjs} +83 -5
  29. package/dist/events/index.d.mts +147 -36
  30. package/dist/events/index.mjs +338 -101
  31. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  32. package/dist/events/transports/redis.d.mts +1 -1
  33. package/dist/factory/index.d.mts +1 -1
  34. package/dist/factory/index.mjs +1 -1
  35. package/dist/{fields-DC4So2M2.d.mts → fields-BC7zcmI9.d.mts} +15 -3
  36. package/dist/{fields-ipsbIRPK.mjs → fields-CU6FlaDV.mjs} +18 -5
  37. package/dist/filesUpload-q8oHt--L.mjs +377 -0
  38. package/dist/hooks/index.d.mts +1 -1
  39. package/dist/idempotency/index.d.mts +28 -4
  40. package/dist/idempotency/index.mjs +111 -2
  41. package/dist/idempotency/redis.d.mts +2 -2
  42. package/dist/idempotency/redis.mjs +134 -13
  43. package/dist/{index-CSkeivBx.d.mts → index-C-xjcA6F.d.mts} +2 -2
  44. package/dist/{index-CpTSDqmD.d.mts → index-Cibkchnx.d.mts} +5 -136
  45. package/dist/{index-BgmMdpm8.d.mts → index-CtGKT0lf.d.mts} +1 -1
  46. package/dist/index.d.mts +8 -8
  47. package/dist/index.mjs +8 -8
  48. package/dist/integrations/event-gateway.d.mts +1 -1
  49. package/dist/integrations/index.d.mts +1 -1
  50. package/dist/integrations/jobs.d.mts +25 -3
  51. package/dist/integrations/jobs.mjs +63 -4
  52. package/dist/integrations/mcp/index.d.mts +26 -8
  53. package/dist/integrations/mcp/index.mjs +96 -17
  54. package/dist/integrations/mcp/testing.d.mts +1 -1
  55. package/dist/integrations/mcp/testing.mjs +1 -1
  56. package/dist/integrations/webhooks.d.mts +5 -0
  57. package/dist/integrations/webhooks.mjs +6 -0
  58. package/dist/{interface-BVuMfeVv.d.mts → interface-YrWsmKqE.d.mts} +324 -194
  59. package/dist/{openapi-CYCuekCn.mjs → openapi-CXuTG1M9.mjs} +3 -3
  60. package/dist/org/index.d.mts +2 -2
  61. package/dist/permissions/index.d.mts +3 -3
  62. package/dist/permissions/index.mjs +3 -3
  63. package/dist/{permissions-CH4cNwJi.mjs → permissions-oNZawnkR.mjs} +1 -1
  64. package/dist/plugins/index.d.mts +6 -6
  65. package/dist/plugins/index.mjs +4 -4
  66. package/dist/plugins/response-cache.mjs +1 -1
  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 +26 -33
  70. package/dist/presets/filesUpload.d.mts +71 -0
  71. package/dist/presets/filesUpload.mjs +2 -0
  72. package/dist/presets/index.d.mts +4 -2
  73. package/dist/presets/index.mjs +4 -2
  74. package/dist/presets/multiTenant.d.mts +1 -1
  75. package/dist/presets/multiTenant.mjs +1 -1
  76. package/dist/presets/search.d.mts +91 -0
  77. package/dist/presets/search.mjs +150 -0
  78. package/dist/{presets-C2xgzW6x.mjs → presets-hM4WhNWY.mjs} +1 -1
  79. package/dist/{queryCachePlugin-D0iIVhW_.mjs → queryCachePlugin-DbUVroUG.mjs} +2 -2
  80. package/dist/redis-MXLp1oOf.d.mts +115 -0
  81. package/dist/{redis-stream-D54N5oXs.d.mts → redis-stream-Bz-4q96t.d.mts} +1 -1
  82. package/dist/registry/index.d.mts +1 -1
  83. package/dist/{resourceToTools-O_HwWXFa.mjs → resourceToTools-C3cWymnW.mjs} +65 -48
  84. package/dist/rpc/index.mjs +1 -1
  85. package/dist/{schemaConverter-OxfCshus.mjs → schemaConverter-BxFDdtXu.mjs} +25 -9
  86. package/dist/scope/index.d.mts +2 -2
  87. package/dist/scope/index.mjs +1 -1
  88. package/dist/storage-BwGQXUpd.d.mts +146 -0
  89. package/dist/store-helpers-DFiZl5TL.mjs +57 -0
  90. package/dist/testing/index.d.mts +7 -15
  91. package/dist/testing/index.mjs +23 -76
  92. package/dist/testing/storageContract.d.mts +26 -0
  93. package/dist/testing/storageContract.mjs +216 -0
  94. package/dist/types/index.d.mts +5 -5
  95. package/dist/types/storage.d.mts +2 -0
  96. package/dist/types/storage.mjs +1 -0
  97. package/dist/{types-CcG4avic.d.mts → types-CoSzA-s-.d.mts} +1 -1
  98. package/dist/{types-Bg2X42_m.d.mts → types-CunEX4UX.d.mts} +7 -5
  99. package/dist/{types-CVC4HOKi.d.mts → types-DZi1aYhm.d.mts} +1 -1
  100. package/dist/utils/index.d.mts +26 -8
  101. package/dist/utils/index.mjs +6 -6
  102. package/dist/{utils-yYT3HDXt.mjs → utils-B7FuRr9w.mjs} +1 -1
  103. package/package.json +23 -11
  104. package/skills/arc/SKILL.md +92 -14
  105. package/skills/arc/references/auth.md +94 -0
  106. package/skills/arc/references/events.md +229 -12
  107. package/skills/arc/references/mcp.md +4 -17
  108. package/skills/arc/references/multi-tenancy.md +43 -0
  109. package/skills/arc/references/production.md +34 -19
  110. package/dist/EventTransport-CinyO7zQ.d.mts +0 -135
  111. package/dist/audit/mongodb.d.mts +0 -2
  112. package/dist/audit/mongodb.mjs +0 -2
  113. package/dist/idempotency/mongodb.d.mts +0 -2
  114. package/dist/idempotency/mongodb.mjs +0 -123
  115. package/dist/mongodb-B5O6xaW1.mjs +0 -90
  116. package/dist/mongodb-B8U2xaLj.d.mts +0 -127
  117. package/dist/mongodb-X7LbEjTN.d.mts +0 -80
  118. package/dist/redis-z3sFr1UP.d.mts +0 -49
  119. /package/dist/{applyPermissionResult-D6GPMsvh.mjs → applyPermissionResult-bqGpo9ML.mjs} +0 -0
  120. /package/dist/{circuitBreaker-cmi5XDv5.mjs → circuitBreaker-l18oRgL5.mjs} +0 -0
  121. /package/dist/{elevation-s5ykdNHr.d.mts → elevation-B6S5csVA.d.mts} +0 -0
  122. /package/dist/{errors-Bmn3eZT6.d.mts → errors-BI8kEKsO.d.mts} +0 -0
  123. /package/dist/{errors-BF2bIOIS.mjs → errors-CqWnSqM-.mjs} +0 -0
  124. /package/dist/{memory-Cp7_cAko.mjs → memory-BFAYkf8H.mjs} +0 -0
  125. /package/dist/{pluralize-A0tWEl1K.mjs → pluralize-CWP6MB39.mjs} +0 -0
  126. /package/dist/{queryParser-CgCtsjti.mjs → queryParser-Cs-6SHQK.mjs} +0 -0
  127. /package/dist/{requestContext-DYvHl113.mjs → requestContext-DYtmNpm5.mjs} +0 -0
  128. /package/dist/{tracing-DxjKk7eW.d.mts → tracing-xqXzWeaf.d.mts} +0 -0
  129. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-Cj5Rgvlg.mjs} +0 -0
  130. /package/dist/{types-C72d3NDn.d.mts → types-BD85MlEK.d.mts} +0 -0
@@ -141,5 +141,75 @@ declare class RedisCacheStore<TValue = unknown> implements CacheStore<TValue> {
141
141
  private scanAndDelete;
142
142
  private withPrefix;
143
143
  }
144
+ /**
145
+ * Minimal ioredis shape we depend on. We don't import ioredis itself so the
146
+ * cache subpath stays peer-dep-free.
147
+ */
148
+ interface IoredisLike {
149
+ get(key: string): Promise<string | null>;
150
+ set(...args: unknown[]): Promise<string | null>;
151
+ del(...keys: string[]): Promise<number>;
152
+ scan(cursor: string | number, ...args: (string | number)[]): Promise<[string, string[]]>;
153
+ pipeline?(): {
154
+ del(key: string): unknown;
155
+ exec(): Promise<unknown>;
156
+ };
157
+ }
158
+ /**
159
+ * Wrap an ioredis instance as a `RedisCacheClient`.
160
+ *
161
+ * Why: arc's `RedisCacheClient` uses node-redis-v4 object-options style
162
+ * (`set(key, val, { PX })`), but ioredis expects positional flags
163
+ * (`set(key, val, 'PX', ms)`). Without this adapter every ioredis user
164
+ * reinvents the bridge.
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * import Redis from 'ioredis';
169
+ * import { RedisCacheStore, ioredisAsCacheClient } from '@classytic/arc/cache';
170
+ *
171
+ * const redis = new Redis(process.env.REDIS_URL);
172
+ * const store = new RedisCacheStore({
173
+ * client: ioredisAsCacheClient(redis),
174
+ * prefix: 'arc:cache:',
175
+ * });
176
+ * ```
177
+ */
178
+ declare function ioredisAsCacheClient(client: IoredisLike): RedisCacheClient;
179
+ /**
180
+ * Minimal `@upstash/redis` REST SDK shape we depend on.
181
+ *
182
+ * `@upstash/redis` is HTTP-based and works on edge runtimes (Cloudflare
183
+ * Workers, Vercel Edge, Deno Deploy) where TCP connections — and thus
184
+ * ioredis — are unavailable.
185
+ */
186
+ interface UpstashRedisLike {
187
+ get(key: string): Promise<string | null | unknown>;
188
+ set(key: string, value: unknown, opts?: Record<string, unknown>): Promise<unknown>;
189
+ del(...keys: string[]): Promise<number>;
190
+ scan(cursor: number | string, opts?: {
191
+ match?: string;
192
+ count?: number;
193
+ }): Promise<[number, string[]] | [string, string[]]>;
194
+ }
195
+ /**
196
+ * Wrap an `@upstash/redis` REST client as a `RedisCacheClient`.
197
+ *
198
+ * Enables running arc's cache layer on edge runtimes without ioredis.
199
+ * Requires `@upstash/redis` as an optional peer dependency.
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * import { Redis } from '@upstash/redis';
204
+ * import { RedisCacheStore, upstashAsCacheClient } from '@classytic/arc/cache';
205
+ *
206
+ * const redis = Redis.fromEnv();
207
+ * const store = new RedisCacheStore({
208
+ * client: upstashAsCacheClient(redis),
209
+ * prefix: 'arc:cache:',
210
+ * });
211
+ * ```
212
+ */
213
+ declare function upstashAsCacheClient(client: UpstashRedisLike): RedisCacheClient;
144
214
  //#endregion
145
- export { type CacheEnvelope, type CacheLogger, type CacheResult, type CacheSetOptions, type CacheStats, type CacheStatus, type CacheStore, type CrossResourceRule, MemoryCacheStore, type MemoryCacheStoreOptions, QueryCache, type QueryCacheConfig, type QueryCacheDefaults, type QueryCachePluginOptions, type RedisCacheClient, RedisCacheStore, type RedisCacheStoreOptions, type RedisPipeline, buildQueryKey, hashParams, queryCachePlugin, tagVersionKey, versionKey };
215
+ export { type CacheEnvelope, type CacheLogger, type CacheResult, type CacheSetOptions, type CacheStats, type CacheStatus, type CacheStore, type CrossResourceRule, type IoredisLike, MemoryCacheStore, type MemoryCacheStoreOptions, QueryCache, type QueryCacheConfig, type QueryCacheDefaults, type QueryCachePluginOptions, type RedisCacheClient, RedisCacheStore, type RedisCacheStoreOptions, type RedisPipeline, type UpstashRedisLike, buildQueryKey, hashParams, ioredisAsCacheClient, queryCachePlugin, tagVersionKey, upstashAsCacheClient, versionKey };
@@ -1,6 +1,6 @@
1
1
  import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-qcD-TVJl.mjs";
2
- import { t as MemoryCacheStore } from "../memory-Cp7_cAko.mjs";
3
- import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-D0iIVhW_.mjs";
2
+ import { t as MemoryCacheStore } from "../memory-BFAYkf8H.mjs";
3
+ import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-DbUVroUG.mjs";
4
4
  //#region src/cache/redis.ts
5
5
  /**
6
6
  * Redis-backed cache store.
@@ -85,5 +85,98 @@ var RedisCacheStore = class {
85
85
  return `${this.prefix}${key}`;
86
86
  }
87
87
  };
88
+ /**
89
+ * Wrap an ioredis instance as a `RedisCacheClient`.
90
+ *
91
+ * Why: arc's `RedisCacheClient` uses node-redis-v4 object-options style
92
+ * (`set(key, val, { PX })`), but ioredis expects positional flags
93
+ * (`set(key, val, 'PX', ms)`). Without this adapter every ioredis user
94
+ * reinvents the bridge.
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * import Redis from 'ioredis';
99
+ * import { RedisCacheStore, ioredisAsCacheClient } from '@classytic/arc/cache';
100
+ *
101
+ * const redis = new Redis(process.env.REDIS_URL);
102
+ * const store = new RedisCacheStore({
103
+ * client: ioredisAsCacheClient(redis),
104
+ * prefix: 'arc:cache:',
105
+ * });
106
+ * ```
107
+ */
108
+ function ioredisAsCacheClient(client) {
109
+ return {
110
+ async get(key) {
111
+ return client.get(key);
112
+ },
113
+ async set(key, value, options) {
114
+ if (options?.PX) return client.set(key, value, "PX", options.PX, ...options.NX ? ["NX"] : []);
115
+ if (options?.EX) return client.set(key, value, "EX", options.EX, ...options.NX ? ["NX"] : []);
116
+ if (options?.NX) return client.set(key, value, "NX");
117
+ return client.set(key, value);
118
+ },
119
+ async del(key) {
120
+ if (Array.isArray(key)) return client.del(...key);
121
+ return client.del(key);
122
+ },
123
+ async scan(cursor, ...args) {
124
+ const [next, keys] = await client.scan(cursor, ...args);
125
+ return [next, keys];
126
+ },
127
+ pipeline: client.pipeline ? () => client.pipeline() : void 0
128
+ };
129
+ }
130
+ /**
131
+ * Wrap an `@upstash/redis` REST client as a `RedisCacheClient`.
132
+ *
133
+ * Enables running arc's cache layer on edge runtimes without ioredis.
134
+ * Requires `@upstash/redis` as an optional peer dependency.
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * import { Redis } from '@upstash/redis';
139
+ * import { RedisCacheStore, upstashAsCacheClient } from '@classytic/arc/cache';
140
+ *
141
+ * const redis = Redis.fromEnv();
142
+ * const store = new RedisCacheStore({
143
+ * client: upstashAsCacheClient(redis),
144
+ * prefix: 'arc:cache:',
145
+ * });
146
+ * ```
147
+ */
148
+ function upstashAsCacheClient(client) {
149
+ return {
150
+ async get(key) {
151
+ const raw = await client.get(key);
152
+ if (raw == null) return null;
153
+ return typeof raw === "string" ? raw : JSON.stringify(raw);
154
+ },
155
+ async set(key, value, options) {
156
+ const opts = {};
157
+ if (options?.PX) opts.px = options.PX;
158
+ if (options?.EX) opts.ex = options.EX;
159
+ if (options?.NX) opts.nx = true;
160
+ if (options?.XX) opts.xx = true;
161
+ const res = await client.set(key, value, opts);
162
+ return res == null ? null : String(res);
163
+ },
164
+ async del(key) {
165
+ if (Array.isArray(key)) return client.del(...key);
166
+ return client.del(key);
167
+ },
168
+ async scan(cursor, ...args) {
169
+ const opts = {};
170
+ for (let i = 0; i < args.length; i += 2) {
171
+ const flag = String(args[i]).toLowerCase();
172
+ const val = args[i + 1];
173
+ if (flag === "match" && typeof val === "string") opts.match = val;
174
+ if (flag === "count") opts.count = Number(val);
175
+ }
176
+ const [next, keys] = await client.scan(cursor, opts);
177
+ return [next, keys];
178
+ }
179
+ };
180
+ }
88
181
  //#endregion
89
- export { MemoryCacheStore, QueryCache, RedisCacheStore, buildQueryKey, hashParams, queryCachePlugin, tagVersionKey, versionKey };
182
+ export { MemoryCacheStore, QueryCache, RedisCacheStore, buildQueryKey, hashParams, ioredisAsCacheClient, queryCachePlugin, tagVersionKey, upstashAsCacheClient, versionKey };
@@ -1,5 +1,5 @@
1
1
  import { t as ResourceRegistry } from "../../ResourceRegistry-Dq3_zBQP.mjs";
2
- import { t as buildOpenApiSpec } from "../../openapi-CYCuekCn.mjs";
2
+ import { t as buildOpenApiSpec } from "../../openapi-CXuTG1M9.mjs";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { mkdirSync, writeFileSync } from "node:fs";
@@ -1,4 +1,4 @@
1
- import { t as pluralize } from "../../pluralize-A0tWEl1K.mjs";
1
+ import { t as pluralize } from "../../pluralize-CWP6MB39.mjs";
2
2
  import { join } from "node:path";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  //#region src/cli/commands/generate.ts
@@ -1,3 +1,3 @@
1
- import { At as BaseController, Ft as BodySanitizerConfig, It as AccessControl, Jt as defineResource, Lt as AccessControlConfig, Mt as QueryResolver, Nt as QueryResolverConfig, Pt as BodySanitizer, jt as BaseControllerOptions, qt as ResourceDefinition } from "../interface-BVuMfeVv.mjs";
2
- import { A as MUTATION_OPERATIONS, C as HOOK_OPERATIONS, D as MAX_FILTER_DEPTH, E as HookPhase, M as RESERVED_QUERY_PARAMS, N as SYSTEM_FIELDS, O as MAX_REGEX_LENGTH, S as DEFAULT_UPDATE_METHOD, T as HookOperation, _ as DEFAULT_ID_FIELD, a as getControllerScope, b as DEFAULT_SORT, c as createCrudRouter, d as ActionRouterConfig, f as IdempotencyService, g as CrudOperation, h as CRUD_OPERATIONS, i as getControllerContext, j as MutationOperation, k as MAX_SEARCH_LENGTH, l as createPermissionMiddleware, m as createActionRouter, n as createFastifyHandler, o as sendControllerResponse, p as buildActionBodySchema, r as createRequestContext, s as defineResourceVariants, t as createCrudHandlers, u as ActionHandler, v as DEFAULT_LIMIT, w as HOOK_PHASES, x as DEFAULT_TENANT_FIELD, y as DEFAULT_MAX_LIMIT } from "../index-CpTSDqmD.mjs";
3
- export { AccessControl, AccessControlConfig, ActionHandler, ActionRouterConfig, BaseController, BaseControllerOptions, BodySanitizer, BodySanitizerConfig, CRUD_OPERATIONS, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, IdempotencyService, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, buildActionBodySchema, createActionRouter, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
1
+ import { At as BaseControllerOptions, Ft as AccessControl, It as AccessControlConfig, Kt as ResourceDefinition, Mt as QueryResolverConfig, Nt as BodySanitizer, Pt as BodySanitizerConfig, jt as QueryResolver, kt as BaseController, qt as defineResource } from "../interface-YrWsmKqE.mjs";
2
+ import { C as MAX_REGEX_LENGTH, D as RESERVED_QUERY_PARAMS, E as MutationOperation, O as SYSTEM_FIELDS, S as MAX_FILTER_DEPTH, T as MUTATION_OPERATIONS, _ as DEFAULT_UPDATE_METHOD, a as getControllerScope, b as HookOperation, c as createCrudRouter, d as CrudOperation, f as DEFAULT_ID_FIELD, g as DEFAULT_TENANT_FIELD, h as DEFAULT_SORT, i as getControllerContext, l as createPermissionMiddleware, m as DEFAULT_MAX_LIMIT, n as createFastifyHandler, o as sendControllerResponse, p as DEFAULT_LIMIT, r as createRequestContext, s as defineResourceVariants, t as createCrudHandlers, u as CRUD_OPERATIONS, v as HOOK_OPERATIONS, w as MAX_SEARCH_LENGTH, x as HookPhase, y as HOOK_PHASES } from "../index-Cibkchnx.mjs";
3
+ export { AccessControl, AccessControlConfig, BaseController, BaseControllerOptions, BodySanitizer, BodySanitizerConfig, CRUD_OPERATIONS, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
@@ -1,6 +1,5 @@
1
1
  import { a as DEFAULT_SORT, c as HOOK_OPERATIONS, d as MAX_REGEX_LENGTH, f as MAX_SEARCH_LENGTH, h as SYSTEM_FIELDS, i as DEFAULT_MAX_LIMIT, l as HOOK_PHASES, m as RESERVED_QUERY_PARAMS, n as DEFAULT_ID_FIELD, o as DEFAULT_TENANT_FIELD, p as MUTATION_OPERATIONS, r as DEFAULT_LIMIT, s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS, u as MAX_FILTER_DEPTH } from "../constants-Cxde4rpC.mjs";
2
- import { i as AccessControl, n as QueryResolver, r as BodySanitizer, t as BaseController } from "../BaseController-DAGGc5Xn.mjs";
3
- import { n as createActionRouter, t as buildActionBodySchema } from "../createActionRouter-Df1BuawX.mjs";
4
- import { c as createCrudHandlers, d as getControllerContext, f as getControllerScope, l as createFastifyHandler, n as defineResource, o as createCrudRouter, p as sendControllerResponse, s as createPermissionMiddleware, t as ResourceDefinition, u as createRequestContext } from "../defineResource-Bb_Bdhtw.mjs";
5
- import { t as defineResourceVariants } from "../core-DKSwNSXf.mjs";
6
- export { AccessControl, BaseController, BodySanitizer, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, QueryResolver, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, buildActionBodySchema, createActionRouter, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
2
+ import { i as AccessControl, n as QueryResolver, r as BodySanitizer, t as BaseController } from "../BaseController-Vu2yc56T.mjs";
3
+ import { c as createCrudHandlers, d as getControllerContext, f as getControllerScope, l as createFastifyHandler, n as defineResource, o as createCrudRouter, p as sendControllerResponse, s as createPermissionMiddleware, t as ResourceDefinition, u as createRequestContext } from "../defineResource-C__jkwvs.mjs";
4
+ import { t as defineResourceVariants } from "../core-DNncu0xF.mjs";
5
+ export { AccessControl, BaseController, BodySanitizer, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, QueryResolver, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
@@ -1,4 +1,4 @@
1
- import { n as defineResource } from "./defineResource-Bb_Bdhtw.mjs";
1
+ import { n as defineResource } from "./defineResource-C__jkwvs.mjs";
2
2
  //#region src/core/defineResourceVariants.ts
3
3
  /**
4
4
  * Define multiple resources from a shared base config and per-variant overrides.
@@ -1,6 +1,6 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-D6GPMsvh.mjs";
3
- import { a as toJsonSchema } from "./schemaConverter-OxfCshus.mjs";
2
+ import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-bqGpo9ML.mjs";
3
+ import { a as toJsonSchema } from "./schemaConverter-BxFDdtXu.mjs";
4
4
  //#region src/core/createActionRouter.ts
5
5
  var createActionRouter_exports = /* @__PURE__ */ __exportAll({
6
6
  buildActionBodySchema: () => buildActionBodySchema,
@@ -246,4 +246,4 @@ function buildActionDescription(actions, actionPermissions) {
246
246
  return lines.join("\n");
247
247
  }
248
248
  //#endregion
249
- export { createActionRouter as n, createActionRouter_exports as r, buildActionBodySchema as t };
249
+ export { createActionRouter_exports as n, 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-D91S2YF4.mjs").then((n) => n.n);
210
+ const { default: eventPlugin } = await import("./eventPlugin-Dl7MoVWH.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,
@@ -249,9 +249,9 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
249
249
  trackPlugin("arc-caching", opts);
250
250
  }
251
251
  if (config.arcPlugins?.queryCache) {
252
- const { queryCachePlugin } = await import("./queryCachePlugin-D0iIVhW_.mjs").then((n) => n.n);
252
+ const { queryCachePlugin } = await import("./queryCachePlugin-DbUVroUG.mjs").then((n) => n.n);
253
253
  const opts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
254
- const store = config.stores?.queryCache ?? new (await (import("./memory-Cp7_cAko.mjs").then((n) => n.n))).MemoryCacheStore();
254
+ const store = config.stores?.queryCache ?? new (await (import("./memory-BFAYkf8H.mjs").then((n) => n.n))).MemoryCacheStore();
255
255
  await fastify.register(queryCachePlugin, {
256
256
  store,
257
257
  ...opts
@@ -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-BBGFjzIP.mjs").then((n) => n.r);
343
+ const { elevationPlugin } = await import("./elevation-DxQ6ACbt.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-mzqk4cGl.mjs").then((n) => n.n);
353
+ const { errorHandlerPlugin } = await import("./errorHandler-CZDW4EXS.mjs").then((n) => n.r);
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);
@@ -688,6 +688,7 @@ async function createApp(options) {
688
688
  const fastify = Fastify({
689
689
  logger: config.logger ?? true,
690
690
  trustProxy: config.trustProxy ?? false,
691
+ pluginTimeout: config.pluginTimeout ?? 1e4,
691
692
  routerOptions: { querystringParser: (str) => qs.parse(str) },
692
693
  ajv: { customOptions: {
693
694
  coerceTypes: true,
@@ -1,15 +1,15 @@
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-DAGGc5Xn.mjs";
4
- import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-ipsbIRPK.mjs";
3
+ import { t as BaseController } from "./BaseController-Vu2yc56T.mjs";
4
+ import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-CU6FlaDV.mjs";
5
5
  import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
6
- import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-D6GPMsvh.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-yYT3HDXt.mjs";
10
- import { r as ForbiddenError } from "./errors-BF2bIOIS.mjs";
11
- import { t as hasEvents } from "./typeGuards-CcFZXgU7.mjs";
12
- import { r as getAvailablePresets, t as applyPresets } from "./presets-C2xgzW6x.mjs";
6
+ import { r as ForbiddenError } from "./errors-CqWnSqM-.mjs";
7
+ import { t as requestContext } from "./requestContext-DYtmNpm5.mjs";
8
+ import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-bqGpo9ML.mjs";
9
+ import { i as getDefaultCrudSchemas } from "./utils-B7FuRr9w.mjs";
10
+ import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-BxFDdtXu.mjs";
11
+ import { t as hasEvents } from "./typeGuards-Cj5Rgvlg.mjs";
12
+ import { r as getAvailablePresets, t as applyPresets } from "./presets-hM4WhNWY.mjs";
13
13
  //#region src/pipeline/pipe.ts
14
14
  /**
15
15
  * Compose pipeline steps into an ordered array.
@@ -387,24 +387,26 @@ function buildPermissionMiddleware(permissionCheck, resourceName, action) {
387
387
  };
388
388
  }
389
389
  /**
390
- * Create additional routes from preset/custom definitions
390
+ * Mount custom routes (from presets or user-defined `routes`) on Fastify.
391
+ * `wrapHandler` is derived inline from `!route.raw`.
391
392
  */
392
- function createAdditionalRoutes(fastify, routes, controller, options) {
393
+ function createCustomRoutes(fastify, routes, controller, options) {
393
394
  const { tag, resourceName, arcDecorator, rateLimitConfig, cacheMw, idempotencyMw, pipeline, routeGuards } = options;
394
395
  for (const route of routes) {
395
396
  const opName = route.operation ?? (typeof route.handler === "string" ? route.handler : `${route.method.toLowerCase()}${route.path.replace(/[/:]/g, "_")}`);
397
+ const wrapHandler = !route.raw;
396
398
  let handler;
397
399
  if (typeof route.handler === "string") {
398
400
  if (!controller) throw new Error(`Route ${route.method} ${route.path}: string handler '${route.handler}' requires a controller. Either provide a controller or use a function handler instead.`);
399
401
  const method = controller[route.handler];
400
402
  if (typeof method !== "function") throw new Error(`Handler '${route.handler}' not found on controller`);
401
403
  const boundMethod = method.bind(controller);
402
- if (route.wrapHandler) {
404
+ if (wrapHandler) {
403
405
  const steps = pipeline ? resolvePipelineSteps(pipeline, opName) : [];
404
406
  if (steps.length > 0) handler = createPipelineHandler(boundMethod, steps, opName, resourceName);
405
407
  else handler = createFastifyHandler(boundMethod);
406
408
  } else handler = boundMethod;
407
- } else if (route.wrapHandler) {
409
+ } else if (wrapHandler) {
408
410
  const steps = pipeline ? resolvePipelineSteps(pipeline, opName) : [];
409
411
  if (steps.length > 0) handler = createPipelineHandler(route.handler, steps, opName, resourceName);
410
412
  else handler = createFastifyHandler(route.handler);
@@ -480,7 +482,7 @@ function createPipelineHandler(controllerMethod, steps, operation, resourceName)
480
482
  * @param options - Router configuration
481
483
  */
482
484
  function createCrudRouter(fastify, controller, options = {}) {
483
- const { tag = "Resource", schemas = {}, permissions = {}, middlewares = {}, routeGuards = [], additionalRoutes = [], disableDefaultRoutes = false, disabledRoutes = [], resourceName = "unknown", schemaOptions, rateLimit, pipe: pipeline, fields: fieldPermissions, updateMethod = DEFAULT_UPDATE_METHOD } = options;
485
+ const { tag = "Resource", schemas = {}, permissions = {}, middlewares = {}, routeGuards = [], routes: customRoutes = [], disableDefaultRoutes = false, disabledRoutes = [], resourceName = "unknown", schemaOptions, rateLimit, pipe: pipeline, fields: fieldPermissions, updateMethod = DEFAULT_UPDATE_METHOD } = options;
484
486
  const rateLimitConfig = buildRateLimitConfig(rateLimit);
485
487
  const cacheMw = !(fastify.hasDecorator("queryCache") && controller && typeof controller._cacheConfig !== "undefined" && controller._cacheConfig !== void 0) && fastify.hasDecorator("responseCache") ? fastify.responseCache.middleware : null;
486
488
  const idempotencyMw = fastify.hasDecorator("idempotency") ? fastify.idempotency.middleware : null;
@@ -646,7 +648,7 @@ function createCrudRouter(fastify, controller, options = {}) {
646
648
  });
647
649
  }
648
650
  }
649
- if (additionalRoutes.length > 0) createAdditionalRoutes(fastify, additionalRoutes, controller, {
651
+ if (customRoutes.length > 0) createCustomRoutes(fastify, customRoutes, controller, {
650
652
  tag,
651
653
  resourceName,
652
654
  arcDecorator,
@@ -941,6 +943,7 @@ function defineResource(config) {
941
943
  idField: resolvedConfig.idField,
942
944
  matchesFilter: config.adapter?.matchesFilter,
943
945
  cache: resolvedConfig.cache,
946
+ onFieldWriteDenied: resolvedConfig.onFieldWriteDenied,
944
947
  presetFields: resolvedConfig._controllerOptions ? {
945
948
  slugField: resolvedConfig._controllerOptions.slugField,
946
949
  parentField: resolvedConfig._controllerOptions.parentField
@@ -1088,16 +1091,6 @@ var ResourceDefinition = class {
1088
1091
  schemaOptions;
1089
1092
  customSchemas;
1090
1093
  permissions;
1091
- additionalRoutes;
1092
- /**
1093
- * Original v2.8 `routes` declaration — retained for downstream consumers
1094
- * (OpenAPI, MCP, registry, CLI introspect). Preserves fields dropped during
1095
- * normalization to `additionalRoutes` (notably `mcp`, `description`,
1096
- * `annotations`). Undefined when the resource was defined with the legacy
1097
- * `additionalRoutes` shape.
1098
- *
1099
- * Added in 2.8.1 — the source-of-truth fix for "canonical resource manifest".
1100
- */
1101
1094
  routes;
1102
1095
  middlewares;
1103
1096
  routeGuards;
@@ -1130,8 +1123,7 @@ var ResourceDefinition = class {
1130
1123
  this.schemaOptions = config.schemaOptions ?? {};
1131
1124
  this.customSchemas = config.customSchemas ?? {};
1132
1125
  this.permissions = config.permissions ?? {};
1133
- this.routes = config.routes;
1134
- this.additionalRoutes = config.routes ? convertRoutesToAdditionalRoutes(config.routes) : [];
1126
+ this.routes = config.routes ?? [];
1135
1127
  this.middlewares = config.middlewares ?? {};
1136
1128
  this.routeGuards = config.routeGuards;
1137
1129
  this.disableDefaultRoutes = config.disableDefaultRoutes ?? false;
@@ -1150,7 +1142,6 @@ var ResourceDefinition = class {
1150
1142
  this.queryParser = config.queryParser;
1151
1143
  this._appliedPresets = config._appliedPresets ?? [];
1152
1144
  this._pendingHooks = config._pendingHooks ?? [];
1153
- if (config.onRegister) this._onRegister = config.onRegister;
1154
1145
  }
1155
1146
  /** Get repository from adapter (if available) */
1156
1147
  get repository() {
@@ -1166,7 +1157,7 @@ var ResourceDefinition = class {
1166
1157
  const ctrl = this.controller;
1167
1158
  for (const method of enabledCrudRoutes) if (typeof ctrl[method] !== "function") errors.push(`CRUD method '${method}' not found on controller`);
1168
1159
  }
1169
- for (const route of this.additionalRoutes) if (typeof route.handler === "string") {
1160
+ for (const route of this.routes) if (typeof route.handler === "string") {
1170
1161
  if (!this.controller) errors.push(`Route ${route.method} ${route.path}: string handler '${route.handler}' requires a controller`);
1171
1162
  else if (typeof this.controller[route.handler] !== "function") errors.push(`Route ${route.method} ${route.path}: handler '${route.handler}' not found`);
1172
1163
  }
@@ -1207,8 +1198,6 @@ var ResourceDefinition = class {
1207
1198
  });
1208
1199
  await fastify.register(async (instance) => {
1209
1200
  const typedInstance = instance;
1210
- const onRegister = self._onRegister;
1211
- if (onRegister) await onRegister(instance);
1212
1201
  let schemas = null;
1213
1202
  const openApi = self._registryMeta?.openApiSchemas;
1214
1203
  if (openApi && (!self.customSchemas || Object.keys(self.customSchemas).length === 0)) {
@@ -1281,14 +1270,13 @@ var ResourceDefinition = class {
1281
1270
  schemas = schemas ?? {};
1282
1271
  schemas.list = schemas.list ? deepMergeSchemas({ querystring: normalizedSchema }, schemas.list) : { querystring: normalizedSchema };
1283
1272
  }
1284
- const resolvedRoutes = self.additionalRoutes;
1285
1273
  createCrudRouter(typedInstance, self.controller, {
1286
1274
  tag: self.tag,
1287
1275
  schemas: schemas ?? void 0,
1288
1276
  permissions: self.permissions,
1289
1277
  middlewares: self.middlewares,
1290
1278
  routeGuards: self.routeGuards,
1291
- additionalRoutes: resolvedRoutes,
1279
+ routes: self.routes,
1292
1280
  disableDefaultRoutes: self.disableDefaultRoutes,
1293
1281
  disabledRoutes: self.disabledRoutes,
1294
1282
  resourceName: self.name,
@@ -1299,7 +1287,7 @@ var ResourceDefinition = class {
1299
1287
  fields: self.fields
1300
1288
  });
1301
1289
  if (self.actions && Object.keys(self.actions).length > 0) {
1302
- const { createActionRouter } = await import("./createActionRouter-Df1BuawX.mjs").then((n) => n.r);
1290
+ const { createActionRouter } = await import("./createActionRouter-DH1YFL9m.mjs").then((n) => n.n);
1303
1291
  createActionRouter(instance, normalizeActionsToRouterConfig(self.actions, self.actionPermissions, self.tag));
1304
1292
  }
1305
1293
  if (self.events && Object.keys(self.events).length > 0) typedInstance.log?.debug?.(`Resource '${self.name}' defined ${Object.keys(self.events).length} events`);
@@ -1366,29 +1354,6 @@ function capitalize(str) {
1366
1354
  return str.charAt(0).toUpperCase() + str.slice(1);
1367
1355
  }
1368
1356
  /**
1369
- * Convert v2.8 RouteDefinition[] to internal AdditionalRoute[] format.
1370
- * The internal format is what createCrudRouter understands.
1371
- */
1372
- function convertRoutesToAdditionalRoutes(routes) {
1373
- return routes.map((route) => ({
1374
- method: route.method,
1375
- path: route.path,
1376
- handler: route.handler,
1377
- permissions: route.permissions,
1378
- wrapHandler: !route.raw,
1379
- operation: route.operation,
1380
- summary: route.summary,
1381
- description: route.description,
1382
- tags: route.tags,
1383
- preHandler: route.preHandler,
1384
- preAuth: route.preAuth,
1385
- streamResponse: route.streamResponse,
1386
- schema: route.schema,
1387
- mcpHandler: route.mcpHandler,
1388
- mcp: route.mcp
1389
- }));
1390
- }
1391
- /**
1392
1357
  * Normalize ActionsMap into the ActionRouterConfig shape that createActionRouter expects.
1393
1358
  */
1394
1359
  function normalizeActionsToRouterConfig(actions, globalAuth, tag) {
@@ -1,4 +1,4 @@
1
- import { it as RegistryEntry } from "../interface-BVuMfeVv.mjs";
1
+ import { rt as RegistryEntry } from "../interface-YrWsmKqE.mjs";
2
2
  import { t as ExternalOpenApiPaths } from "../externalPaths-Bapitwvd.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
@@ -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-CYCuekCn.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-CXuTG1M9.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 { qt as ResourceDefinition, r as DataAdapter } from "../interface-BVuMfeVv.mjs";
2
- import { t as PermissionCheck } from "../types-CVC4HOKi.mjs";
1
+ import { Kt as ResourceDefinition, r as DataAdapter } from "../interface-YrWsmKqE.mjs";
2
+ import { t as PermissionCheck } from "../types-DZi1aYhm.mjs";
3
3
 
4
4
  //#region src/dynamic/ArcDynamicLoader.d.ts
5
5
  interface ArcArchitectureSchema {
@@ -1,6 +1,6 @@
1
- import { t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
2
- import { n as defineResource } from "../defineResource-Bb_Bdhtw.mjs";
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";
1
+ import { t as ArcQueryParser } from "../queryParser-Cs-6SHQK.mjs";
2
+ import { n as defineResource } from "../defineResource-C__jkwvs.mjs";
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-oNZawnkR.mjs";
4
4
  //#region src/dynamic/ArcDynamicLoader.ts
5
5
  const VALID_FIELD_TYPES = new Set([
6
6
  "string",
@@ -57,13 +57,27 @@ const elevationPlugin = async (fastify, opts = {}) => {
57
57
  userId,
58
58
  organizationId: orgId
59
59
  });
60
- if (onElevation) try {
61
- await onElevation({
62
- userId,
63
- organizationId: orgId || void 0,
64
- request,
65
- timestamp: /* @__PURE__ */ new Date()
60
+ const event = {
61
+ userId,
62
+ organizationId: orgId || void 0,
63
+ request,
64
+ timestamp: /* @__PURE__ */ new Date()
65
+ };
66
+ const publish = fastify.events?.publish;
67
+ if (publish) try {
68
+ await publish("arc.scope.elevated", {
69
+ userId: event.userId,
70
+ organizationId: event.organizationId,
71
+ route: request.routeOptions?.url ?? request.url,
72
+ method: request.method,
73
+ requestId: request.id,
74
+ timestamp: event.timestamp.toISOString()
66
75
  });
76
+ } catch (err) {
77
+ log.warn("Failed to publish arc.scope.elevated event", { error: err instanceof Error ? err.message : String(err) });
78
+ }
79
+ if (onElevation) try {
80
+ await onElevation(event);
67
81
  } catch {
68
82
  log.warn("onElevation callback threw — continuing request");
69
83
  }
@@ -1,11 +1,61 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { p as isArcError } from "./errors-BF2bIOIS.mjs";
2
+ import { p as isArcError } from "./errors-CqWnSqM-.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/errorHandler.ts
5
- var errorHandler_exports = /* @__PURE__ */ __exportAll({ errorHandlerPlugin: () => errorHandlerPlugin });
5
+ var errorHandler_exports = /* @__PURE__ */ __exportAll({
6
+ defaultIsDuplicateKeyError: () => defaultIsDuplicateKeyError,
7
+ errorHandlerPlugin: () => errorHandlerPlugin
8
+ });
9
+ /**
10
+ * Default duplicate-key detector covering the mainstream drivers arc sees
11
+ * most. Detection is strictly by known driver codes — never by message
12
+ * string matching — because false positives on dup-key silently mask real
13
+ * errors (WriteConflict, NotWritablePrimary, etc.) as 409s. For long-tail
14
+ * drivers (Neo4j, MSSQL, DynamoDB, custom kits), compose rather than
15
+ * replace:
16
+ *
17
+ * ```ts
18
+ * import { defaultIsDuplicateKeyError } from '@classytic/arc/plugins';
19
+ *
20
+ * errorHandler: {
21
+ * isDuplicateKeyError: (err) =>
22
+ * defaultIsDuplicateKeyError(err) || isNeo4jDupKey(err),
23
+ * }
24
+ * ```
25
+ *
26
+ * Drizzle apps get coverage transitively (Drizzle doesn't wrap driver
27
+ * errors — pg/mysql2/better-sqlite3 codes propagate as-is). Neon is
28
+ * Postgres-wire-compatible → `23505` covers `@neondatabase/serverless`.
29
+ */
30
+ function defaultIsDuplicateKeyError(err) {
31
+ if (!err || typeof err !== "object") return false;
32
+ const e = err;
33
+ if (e.code === 11e3 || e.codeName === "DuplicateKey") return true;
34
+ if (e.code === "P2002") return true;
35
+ if (e.code === "23505") return true;
36
+ if (e.code === "ER_DUP_ENTRY" || e.errno === 1062) return true;
37
+ if (e.code === "SQLITE_CONSTRAINT_UNIQUE" || e.code === "SQLITE_CONSTRAINT_PRIMARYKEY") return true;
38
+ return false;
39
+ }
40
+ /**
41
+ * Extract the duplicate-field names for the `details.duplicateFields`
42
+ * response. Only called when the caller has opted into detail exposure
43
+ * (`includeStack: true`) — shape differs per driver.
44
+ */
45
+ function extractDuplicateFields(err) {
46
+ if (!err || typeof err !== "object") return null;
47
+ const e = err;
48
+ if (e.keyValue && typeof e.keyValue === "object") return Object.keys(e.keyValue);
49
+ if (e.meta?.target) {
50
+ if (Array.isArray(e.meta.target)) return e.meta.target.map(String);
51
+ if (typeof e.meta.target === "string") return [e.meta.target];
52
+ }
53
+ if (typeof e.constraint === "string") return [e.constraint];
54
+ return null;
55
+ }
6
56
  async function errorHandlerPluginFn(fastify, options = {}) {
7
57
  const isProduction = process.env.NODE_ENV === "production";
8
- const { includeStack = !isProduction, onError, errorMap = {}, errorMappers = [] } = options;
58
+ const { includeStack = !isProduction, onError, errorMap = {}, errorMappers = [], isDuplicateKeyError = defaultIsDuplicateKeyError } = options;
9
59
  fastify.setErrorHandler(async (error, request, reply) => {
10
60
  if (onError) try {
11
61
  await onError(error, request);
@@ -75,12 +125,14 @@ async function errorHandlerPluginFn(fastify, options = {}) {
75
125
  statusCode = 400;
76
126
  response.code = "INVALID_ID";
77
127
  response.error = "Invalid identifier format";
78
- } else if (error.name === "MongoServerError" && error.code === 11e3) {
128
+ } else if (isDuplicateKeyError(error)) {
79
129
  statusCode = 409;
80
130
  response.code = "DUPLICATE_KEY";
81
131
  response.error = "Resource already exists";
82
- const keyValue = error.keyValue;
83
- if (keyValue && includeStack) response.details = { duplicateFields: Object.keys(keyValue) };
132
+ if (includeStack) {
133
+ const duplicateFields = extractDuplicateFields(error);
134
+ if (duplicateFields && duplicateFields.length > 0) response.details = { duplicateFields };
135
+ }
84
136
  }
85
137
  if (includeStack && error.stack) response.stack = error.stack;
86
138
  if (statusCode >= 500) request.log.error({
@@ -118,4 +170,4 @@ const errorHandlerPlugin = fp(errorHandlerPluginFn, {
118
170
  fastify: "5.x"
119
171
  });
120
172
  //#endregion
121
- export { errorHandler_exports as n, errorHandlerPlugin as t };
173
+ export { errorHandlerPlugin as n, errorHandler_exports as r, defaultIsDuplicateKeyError as t };