@classytic/arc 2.18.5 → 2.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/{actionPermissions-h7DYDNMg.mjs → actionPermissions-D7NGwcAR.mjs} +1 -0
  2. package/dist/auth/index.d.mts +1 -1
  3. package/dist/auth/index.mjs +10 -1
  4. package/dist/cli/commands/describe.d.mts +1 -1
  5. package/dist/cli/commands/docs.mjs +1 -1
  6. package/dist/core/index.d.mts +2 -2
  7. package/dist/core/index.mjs +2 -2
  8. package/dist/{core-ZnQBa5rh.mjs → core-Ba-6Sue6.mjs} +16 -11
  9. package/dist/{createActionRouter-CagItpxI.mjs → createActionRouter-B_WzBFyf.mjs} +6 -5
  10. package/dist/{createAggregationRouter-DVKroXhC.mjs → createAggregationRouter-B_G_nIjm.mjs} +1 -1
  11. package/dist/docs/index.d.mts +1 -1
  12. package/dist/docs/index.mjs +1 -1
  13. package/dist/encryption/index.d.mts +222 -0
  14. package/dist/encryption/index.mjs +270 -0
  15. package/dist/factory/index.d.mts +1 -1
  16. package/dist/{fastify-augmentation-Cxb0ROIw.d.mts → fastify-augmentation-D-5_yQJi.d.mts} +1 -1
  17. package/dist/hooks/index.d.mts +1 -1
  18. package/dist/{index-Dh0_RSTd.d.mts → index-CP6A1Ffj.d.mts} +7 -7
  19. package/dist/{index-CHNQIhcb.d.mts → index-CUTG2Cuo.d.mts} +83 -1
  20. package/dist/index.d.mts +2 -2
  21. package/dist/index.mjs +3 -3
  22. package/dist/integrations/index.d.mts +1 -1
  23. package/dist/integrations/mcp/index.d.mts +2 -2
  24. package/dist/integrations/mcp/index.mjs +1 -1
  25. package/dist/integrations/mcp/testing.d.mts +1 -1
  26. package/dist/integrations/mcp/testing.mjs +1 -1
  27. package/dist/{mcpPlugin-D1wuRD2c.mjs → mcpPlugin-C088Nd-G.mjs} +1 -1
  28. package/dist/middleware/index.d.mts +1 -1
  29. package/dist/{openapi-xVJibapr.mjs → openapi-DZ-5a5Op.mjs} +2 -2
  30. package/dist/pipeline/index.d.mts +1 -1
  31. package/dist/plugins/index.d.mts +2 -2
  32. package/dist/plugins/tracing-entry.mjs +1 -1
  33. package/dist/presets/filesUpload.d.mts +1 -1
  34. package/dist/presets/index.d.mts +1 -1
  35. package/dist/presets/multiTenant.d.mts +1 -1
  36. package/dist/presets/search.d.mts +1 -1
  37. package/dist/{registerRoute-CPmdkZGu.mjs → registerRoute-BdV8DDwU.mjs} +19 -1
  38. package/dist/registry/index.d.mts +1 -1
  39. package/dist/testing/index.d.mts +2 -2
  40. package/dist/types/index.d.mts +2 -2
  41. package/dist/{types-CmkknLzI.d.mts → types-9_wtNPsI.d.mts} +1 -1
  42. package/dist/{types-D60PFwfG.d.mts → types-DpvSF5Hh.d.mts} +1 -1
  43. package/dist/utils/index.d.mts +1 -1
  44. package/package.json +10 -1
@@ -0,0 +1,270 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
+ import fp from "fastify-plugin";
3
+ //#region src/encryption/fieldCipher.ts
4
+ /**
5
+ * Field-level cipher — `node:crypto` AES-256-GCM, zero external deps.
6
+ *
7
+ * Used by `mode: 'fields'` to encrypt individual field values in place while
8
+ * leaving the JSON structure intact. The envelope is an authenticated,
9
+ * versioned, `kid`-tagged token:
10
+ *
11
+ * arc.v1.<b64url(kid)>.<b64url(iv)>.<b64url(ciphertext)>.<b64url(tag)>
12
+ *
13
+ * This is NOT a hand-rolled cipher — it's the correct use of a vetted AEAD
14
+ * primitive (AES-256-GCM): a fresh random 96-bit IV per operation (never
15
+ * caller-supplied — IV reuse under GCM is catastrophic), authentication via
16
+ * the GCM tag, and a version prefix so the format can evolve. Same shape, in
17
+ * spirit, as MongoDB CSFLE's BSON-binary subtype-6 envelope. For
18
+ * cross-vendor interop or asymmetric keys use `mode: 'jwe'` (RFC 7516)
19
+ * instead.
20
+ */
21
+ const ENVELOPE_PREFIX = "arc.v1";
22
+ const IV_BYTES = 12;
23
+ const KEY_BYTES = 32;
24
+ function b64url(buf) {
25
+ return buf.toString("base64url");
26
+ }
27
+ function toKeyBuffer(key) {
28
+ if (Buffer.isBuffer(key)) {
29
+ if (key.length !== KEY_BYTES) throw new Error(`[arc/encryption] field-mode key must be ${KEY_BYTES} bytes (AES-256); got ${key.length}`);
30
+ return key;
31
+ }
32
+ if (key instanceof Uint8Array) return toKeyBuffer(Buffer.from(key));
33
+ throw new Error("[arc/encryption] field-mode key must be a 32-byte Buffer/Uint8Array. For asymmetric or cross-vendor encryption use mode: 'jwe'.");
34
+ }
35
+ /** Encrypt a UTF-8 string into the `arc.v1` field envelope. */
36
+ function encryptField(plaintext, kid, key) {
37
+ const secret = toKeyBuffer(key);
38
+ const iv = randomBytes(IV_BYTES);
39
+ const cipher = createCipheriv("aes-256-gcm", secret, iv);
40
+ const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
41
+ const tag = cipher.getAuthTag();
42
+ return [
43
+ ENVELOPE_PREFIX,
44
+ b64url(Buffer.from(kid, "utf8")),
45
+ b64url(iv),
46
+ b64url(ciphertext),
47
+ b64url(tag)
48
+ ].join(".");
49
+ }
50
+ /** Returns the parsed envelope, or `null` when the token isn't `arc.v1`. */
51
+ function parseFieldEnvelope(token) {
52
+ if (typeof token !== "string") return null;
53
+ const parts = token.split(".");
54
+ if (parts.length !== 6) return null;
55
+ const [v0, v1, kidPart, ivPart, ctPart, tagPart] = parts;
56
+ if (`${v0}.${v1}` !== ENVELOPE_PREFIX) return null;
57
+ if (kidPart === void 0 || ivPart === void 0 || ctPart === void 0 || tagPart === void 0) return null;
58
+ try {
59
+ return {
60
+ kid: Buffer.from(kidPart, "base64url").toString("utf8"),
61
+ iv: Buffer.from(ivPart, "base64url"),
62
+ ciphertext: Buffer.from(ctPart, "base64url"),
63
+ tag: Buffer.from(tagPart, "base64url")
64
+ };
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+ /** Decrypt a parsed `arc.v1` envelope with the resolved key. Throws on tamper. */
70
+ function decryptField(envelope, key) {
71
+ const decipher = createDecipheriv("aes-256-gcm", toKeyBuffer(key), envelope.iv);
72
+ decipher.setAuthTag(envelope.tag);
73
+ return Buffer.concat([decipher.update(envelope.ciphertext), decipher.final()]).toString("utf8");
74
+ }
75
+ //#endregion
76
+ //#region src/encryption/jose.ts
77
+ let josePromise;
78
+ /** Load `jose` once, caching the module. Throws an actionable error if absent. */
79
+ async function loadJose() {
80
+ if (!josePromise) josePromise = import("jose").then((m) => m, (cause) => {
81
+ josePromise = void 0;
82
+ throw new Error("[arc/encryption] mode 'jwe' requires the optional peer dependency `jose`. Install it with `npm i jose`, or use mode: 'fields' (node:crypto, no dependency).", { cause });
83
+ });
84
+ return josePromise;
85
+ }
86
+ const encoder = new TextEncoder();
87
+ const decoder = new TextDecoder();
88
+ /** Encrypt a UTF-8 string into a JWE compact serialization. */
89
+ async function jweEncrypt(plaintext, active, alg, enc) {
90
+ return new (await (loadJose())).CompactEncrypt(encoder.encode(plaintext)).setProtectedHeader({
91
+ alg: active.alg ?? alg,
92
+ enc,
93
+ kid: active.kid
94
+ }).encrypt(active.key);
95
+ }
96
+ /**
97
+ * Decrypt a JWE compact string. `resolve` maps the header `kid` to a key;
98
+ * the allowlists are enforced by `jose` so a malicious header can't downgrade
99
+ * the algorithm.
100
+ */
101
+ async function jweDecrypt(jwe, resolve, allowedAlgs, allowedEncs) {
102
+ const { plaintext } = await (await loadJose()).compactDecrypt(jwe, async (header) => {
103
+ if (!header.kid) throw new Error("[arc/encryption] inbound JWE is missing the `kid` header");
104
+ const key = await resolve(header.kid);
105
+ if (key === void 0) throw new Error(`[arc/encryption] no decryption key for kid '${header.kid}'`);
106
+ return key;
107
+ }, {
108
+ keyManagementAlgorithms: [...allowedAlgs],
109
+ contentEncryptionAlgorithms: [...allowedEncs]
110
+ });
111
+ return decoder.decode(plaintext);
112
+ }
113
+ //#endregion
114
+ //#region src/encryption/resolver.ts
115
+ function routeDirective(request) {
116
+ return (request.routeOptions?.config)?.arcExtensions?.encryption;
117
+ }
118
+ function finalize(directive, defaultMode) {
119
+ if (directive.enabled === false) return null;
120
+ return {
121
+ mode: directive.mode ?? defaultMode,
122
+ fields: directive.fields ?? []
123
+ };
124
+ }
125
+ /**
126
+ * Resolve the active directive for a request, or `null` to skip encryption.
127
+ *
128
+ * @param request incoming request (carries route config)
129
+ * @param defaultMode plugin-level default mode
130
+ * @param fallback plugin-level `routes` matcher (optional)
131
+ */
132
+ function resolveDirective(request, defaultMode, fallback) {
133
+ const declared = routeDirective(request);
134
+ if (declared) return finalize(declared, defaultMode);
135
+ if (fallback) {
136
+ const result = fallback(request);
137
+ if (result === true) return {
138
+ mode: defaultMode,
139
+ fields: []
140
+ };
141
+ if (result && typeof result === "object") return finalize(result, defaultMode);
142
+ }
143
+ return null;
144
+ }
145
+ //#endregion
146
+ //#region src/encryption/encryptionPlugin.ts
147
+ function getPath(obj, path) {
148
+ let cur = obj;
149
+ for (const seg of path.split(".")) {
150
+ if (cur === null || typeof cur !== "object") return void 0;
151
+ cur = cur[seg];
152
+ }
153
+ return cur;
154
+ }
155
+ function setPath(obj, path, value) {
156
+ const segs = path.split(".");
157
+ const last = segs[segs.length - 1];
158
+ if (last === void 0) return;
159
+ let cur = obj;
160
+ for (let i = 0; i < segs.length - 1; i++) {
161
+ const seg = segs[i];
162
+ if (seg === void 0) return;
163
+ const next = cur[seg];
164
+ if (next === null || typeof next !== "object") return;
165
+ cur = next;
166
+ }
167
+ cur[last] = value;
168
+ }
169
+ const encryptionPlugin = async (fastify, opts) => {
170
+ const { enabled = true, keyProvider, mode: defaultMode = "jwe", alg = "RSA-OAEP-256", enc = "A256GCM", routes: fallback, decryptRequests = true, contentType = "application/jose", responseContentType = "application/jose", flagHeader = "x-encrypted" } = opts;
171
+ fastify.decorateRequest("requestDecrypted", false);
172
+ fastify.decorateRequest("responseEncrypted", false);
173
+ if (enabled === false) {
174
+ fastify.log?.debug?.("[arc/encryption] disabled");
175
+ return;
176
+ }
177
+ if (!keyProvider) throw new Error("[arc/encryption] `keyProvider` is required when the plugin is enabled.");
178
+ const allowedAlgs = opts.allowedAlgs ?? [alg];
179
+ const allowedEncs = opts.allowedEncs ?? [enc];
180
+ function setFlag(reply) {
181
+ if (flagHeader) reply.header(flagHeader, "true");
182
+ }
183
+ if (decryptRequests) fastify.addContentTypeParser(contentType, { parseAs: "string" }, async (request, body) => {
184
+ const provider = keyProvider;
185
+ const plaintext = await jweDecrypt(body, (kid) => provider.decryptionKey(kid, { request }), allowedAlgs, allowedEncs);
186
+ request.requestDecrypted = true;
187
+ if (plaintext.length === 0) return void 0;
188
+ try {
189
+ return JSON.parse(plaintext);
190
+ } catch (cause) {
191
+ throw Object.assign(/* @__PURE__ */ new Error("[arc/encryption] decrypted request body is not valid JSON"), {
192
+ statusCode: 400,
193
+ cause
194
+ });
195
+ }
196
+ });
197
+ fastify.addHook("preSerialization", async (request, reply, payload) => {
198
+ const directive = resolveDirective(request, defaultMode, fallback);
199
+ if (!directive) return payload;
200
+ if (payload === null || typeof payload !== "object") return payload;
201
+ if (directive.mode === "fields") {
202
+ if (directive.fields.length === 0) return payload;
203
+ const { kid, key } = await keyProvider.encryptionKey({ request });
204
+ const obj = payload;
205
+ let touched = false;
206
+ for (const path of directive.fields) {
207
+ const value = getPath(obj, path);
208
+ if (value === void 0 || value === null) continue;
209
+ setPath(obj, path, encryptField(JSON.stringify(value), kid, key));
210
+ touched = true;
211
+ }
212
+ if (touched) {
213
+ setFlag(reply);
214
+ request.responseEncrypted = true;
215
+ }
216
+ return payload;
217
+ }
218
+ const active = await keyProvider.encryptionKey({ request });
219
+ const jwe = await jweEncrypt(JSON.stringify(payload), active, alg, enc);
220
+ reply.header("content-type", responseContentType);
221
+ setFlag(reply);
222
+ reply.serializer((p) => p);
223
+ request.responseEncrypted = true;
224
+ return jwe;
225
+ });
226
+ fastify.log?.debug?.({
227
+ mode: defaultMode,
228
+ alg,
229
+ enc
230
+ }, "[arc/encryption] enabled");
231
+ };
232
+ var encryptionPlugin_default = fp(encryptionPlugin, {
233
+ name: "arc-encryption",
234
+ fastify: "5.x"
235
+ });
236
+ //#endregion
237
+ //#region src/encryption/keyProvider.ts
238
+ /**
239
+ * A static, in-memory key provider with `kid`-based rotation. Keys are held
240
+ * for the process lifetime — resolve them from env / a secrets file at boot
241
+ * and pass the imported key material in (don't re-parse PEM per request).
242
+ *
243
+ * @example symmetric field mode
244
+ * ```ts
245
+ * const provider = createStaticKeyProvider({
246
+ * encryptionKey: { kid: "2026-06", key: Buffer.from(process.env.FIELD_KEY!, "base64") },
247
+ * });
248
+ * ```
249
+ *
250
+ * @example asymmetric JWE (jose key material)
251
+ * ```ts
252
+ * const provider = createStaticKeyProvider({
253
+ * encryptionKey: { kid: "client-1", key: await importSPKI(clientPubPem, "RSA-OAEP-256") },
254
+ * decryptionKeys: { "server-1": await importPKCS8(serverPrivPem, "RSA-OAEP-256") },
255
+ * });
256
+ * ```
257
+ */
258
+ function createStaticKeyProvider(options) {
259
+ const { encryptionKey } = options;
260
+ const decryptionKeys = {
261
+ [encryptionKey.kid]: encryptionKey.key,
262
+ ...options.decryptionKeys
263
+ };
264
+ return {
265
+ encryptionKey: () => encryptionKey,
266
+ decryptionKey: (kid) => decryptionKeys[kid]
267
+ };
268
+ }
269
+ //#endregion
270
+ export { createStaticKeyProvider, decryptField, encryptionPlugin_default as default, encryptField, encryptionPlugin, parseFieldEnvelope };
@@ -1,4 +1,4 @@
1
- import { a as CustomPluginAuthOption, c as RawBodyOptions, d as preloadResourcesAsync, f as LoadResourcesOptions, h as loadResources, i as CustomAuthenticatorOption, l as UnderPressureOptions, m as ResourceModule, n as BetterAuthOption, o as JwtAuthOption, p as ResourceLike, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as preloadResources } from "../types-CmkknLzI.mjs";
1
+ import { a as CustomPluginAuthOption, c as RawBodyOptions, d as preloadResourcesAsync, f as LoadResourcesOptions, h as loadResources, i as CustomAuthenticatorOption, l as UnderPressureOptions, m as ResourceModule, n as BetterAuthOption, o as JwtAuthOption, p as ResourceLike, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as preloadResources } from "../types-9_wtNPsI.mjs";
2
2
  import { FastifyInstance, FastifyServerOptions } from "fastify";
3
3
 
4
4
  //#region src/factory/createApp.d.ts
@@ -1,4 +1,4 @@
1
- import { yn as HookSystem, z as ResourceRegistry } from "./index-CHNQIhcb.mjs";
1
+ import { bn as HookSystem, z as ResourceRegistry } from "./index-CUTG2Cuo.mjs";
2
2
  import { t as ExternalOpenApiPaths } from "./externalPaths-BE6wFpYv.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
@@ -1,2 +1,2 @@
1
- import { Cn as afterUpdate, Dn as createHookSystem, En as beforeUpdate, On as defineHook, Sn as afterDelete, Tn as beforeDelete, _n as HookPhase, bn as HookSystemOptions, gn as HookOperation, hn as HookHandler, mn as HookContext, pn as DefineHookOptions, vn as HookRegistration, wn as beforeCreate, xn as afterCreate, yn as HookSystem } from "../index-CHNQIhcb.mjs";
1
+ import { Cn as afterDelete, Dn as beforeUpdate, En as beforeDelete, On as createHookSystem, Sn as afterCreate, Tn as beforeCreate, _n as HookOperation, bn as HookSystem, gn as HookHandler, hn as HookContext, kn as defineHook, mn as DefineHookOptions, vn as HookPhase, wn as afterUpdate, xn as HookSystemOptions, yn as HookRegistration } from "../index-CUTG2Cuo.mjs";
2
2
  export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -1,4 +1,4 @@
1
- import { C as RequestContext, F as FastifyWithDecorators, H as ActionDefinition, K as ArcFieldRule, Kt as AnyRecord, L as RequestWithExtras, T as CrudRouterOptions, Tt as AggregationConfig, V as ResourceDefinition, bt as IRequestContext, ft as RouteMcpConfig, mt as RouteSchemaOptions, q as CrudController, st as ResourceConfig, vt as IController, yt as IControllerResponse } from "./index-CHNQIhcb.mjs";
1
+ import { C as RequestContext, Et as AggregationConfig, F as FastifyWithDecorators, H as ActionDefinition, K as ArcFieldRule, L as RequestWithExtras, T as CrudRouterOptions, V as ResourceDefinition, bt as IControllerResponse, ht as RouteSchemaOptions, pt as RouteMcpConfig, q as CrudController, qt as AnyRecord, st as ResourceConfig, xt as IRequestContext, yt as IController } from "./index-CUTG2Cuo.mjs";
2
2
  import { i as RequestScope } from "./types-LNebCoJx.mjs";
3
3
  import { c as PermissionCheck } from "./fields-C7ahdGYf.mjs";
4
4
  import { FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
@@ -300,7 +300,7 @@ declare function sendControllerResponse<T>(reply: FastifyReply, response: IContr
300
300
  * fastify.get('/products', listHandler);
301
301
  * ```
302
302
  */
303
- declare function createFastifyHandler<T>(controllerMethod: (req: IRequestContext) => Promise<IControllerResponse<T>>): (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
303
+ declare function createFastifyHandler<T>(controllerMethod: (req: IRequestContext) => Promise<IControllerResponse<T>>): (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
304
304
  /**
305
305
  * Create Fastify adapters for all CRUD methods of an IController
306
306
  *
@@ -319,11 +319,11 @@ declare function createFastifyHandler<T>(controllerMethod: (req: IRequestContext
319
319
  * ```
320
320
  */
321
321
  declare function createCrudHandlers<TDoc>(controller: IController<TDoc>): {
322
- list: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
323
- get: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
324
- create: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
325
- update: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
326
- delete: (req: FastifyRequest, reply: FastifyReply) => Promise<void>;
322
+ list: (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
323
+ get: (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
324
+ create: (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
325
+ update: (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
326
+ delete: (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
327
327
  };
328
328
  //#endregion
329
329
  //#region src/core/fieldRulePredicates.d.ts
@@ -2667,6 +2667,48 @@ interface ResourcePermissions {
2667
2667
  update?: PermissionCheck;
2668
2668
  delete?: PermissionCheck;
2669
2669
  }
2670
+ /**
2671
+ * Plugin extension namespace — arc's typed escape hatch for attaching
2672
+ * declarative, per-resource config that **plugins** read at request time.
2673
+ *
2674
+ * This interface is intentionally **empty in core**. Each optional plugin
2675
+ * augments it via TypeScript declaration merging to register its own typed
2676
+ * slice — exactly the model Fastify uses for `FastifyInstance` decorators.
2677
+ * A host that never installs a plugin never sees its key; a host that does
2678
+ * gets full autocomplete + compile-time checking on the matching
2679
+ * `extensions` block, and a typo (`extensions: { encyrption: … }`) is a
2680
+ * type error rather than a silent no-op.
2681
+ *
2682
+ * **The "React for backend" composition model.** `defineResource()` is the
2683
+ * declarative component, `extensions` is its typed props bag, and plugins
2684
+ * are the providers that consume those props at the framework boundary.
2685
+ * Adding a cross-cutting capability becomes *additive* — a plugin ships a
2686
+ * subpath + a one-line `declare module` augmentation, with no new
2687
+ * first-class `ResourceConfig` field and no core release per feature.
2688
+ *
2689
+ * Arc threads the declared `extensions` onto every generated route's
2690
+ * Fastify `config` (see `createCrudRouter`), so a plugin reads its slice
2691
+ * at request time via `request.routeOptions.config.arcExtensions` without
2692
+ * re-deriving which resource a route belongs to.
2693
+ *
2694
+ * @example A plugin registers its slice (in `@classytic/arc/encryption`)
2695
+ * ```ts
2696
+ * declare module "@classytic/arc/types" {
2697
+ * interface ResourceExtensions {
2698
+ * encryption?: import("@classytic/arc/encryption").EncryptionDirective;
2699
+ * }
2700
+ * }
2701
+ * ```
2702
+ *
2703
+ * @example A host declares it on a resource
2704
+ * ```ts
2705
+ * defineResource({
2706
+ * name: "payment",
2707
+ * extensions: { encryption: { mode: "fields", fields: ["cardNumber", "cvv"] } },
2708
+ * });
2709
+ * ```
2710
+ */
2711
+ interface ResourceExtensions {}
2670
2712
  interface ResourceConfig<TDoc = AnyRecord> {
2671
2713
  name: string;
2672
2714
  displayName?: string;
@@ -3097,6 +3139,26 @@ interface ResourceConfig<TDoc = AnyRecord> {
3097
3139
  audit?: boolean | {
3098
3140
  operations?: ("create" | "update" | "delete")[];
3099
3141
  };
3142
+ /**
3143
+ * Typed plugin-extension namespace — declarative per-resource config that
3144
+ * arc plugins read at request time. Empty by default; each installed
3145
+ * plugin augments {@link ResourceExtensions} with its own typed slice via
3146
+ * `declare module`. See {@link ResourceExtensions} for the full rationale
3147
+ * and the "React for backend" composition model.
3148
+ *
3149
+ * Threaded onto every generated route's Fastify `config` as
3150
+ * `config.arcExtensions`, so a plugin reads its slice via
3151
+ * `request.routeOptions.config.arcExtensions` without a resource lookup.
3152
+ *
3153
+ * @example
3154
+ * ```ts
3155
+ * defineResource({
3156
+ * name: "payment",
3157
+ * extensions: { encryption: { mode: "fields", fields: ["cardNumber"] } },
3158
+ * });
3159
+ * ```
3160
+ */
3161
+ extensions?: ResourceExtensions;
3100
3162
  }
3101
3163
  //#endregion
3102
3164
  //#region src/core/defineResource/diagnostics.d.ts
@@ -3189,6 +3251,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
3189
3251
  */
3190
3252
  readonly onFieldWriteDenied?: "reject" | "strip";
3191
3253
  readonly cache?: ResourceCacheConfig;
3254
+ readonly extensions?: ResourceExtensions;
3192
3255
  readonly referenceData: boolean;
3193
3256
  /**
3194
3257
  * Per-resource MCP opt-out. `false` keeps the resource out of every
@@ -3544,6 +3607,18 @@ type FastifyWithDecorators = FastifyInstance & {
3544
3607
  };
3545
3608
  /** Handler signature for middleware functions. */
3546
3609
  type MiddlewareHandler = (request: RequestWithExtras, reply: FastifyReply) => Promise<unknown>;
3610
+ /**
3611
+ * Route-config augmentation — arc stamps `defineResource({ extensions })`
3612
+ * onto every generated route's Fastify `config` under `arcExtensions`, so
3613
+ * plugins read their typed slice at request time via
3614
+ * `request.routeOptions.config.arcExtensions` without re-resolving which
3615
+ * resource a route belongs to. Type-only; see `ResourceExtensions`.
3616
+ */
3617
+ declare module "fastify" {
3618
+ interface FastifyContextConfig {
3619
+ arcExtensions?: ResourceExtensions;
3620
+ }
3621
+ }
3547
3622
  //#endregion
3548
3623
  //#region src/types/plugins.d.ts
3549
3624
  interface GracefulShutdownOptions {
@@ -3619,6 +3694,13 @@ interface CrudRouterOptions {
3619
3694
  * re-reading resource config. Defaults to `_id`.
3620
3695
  */
3621
3696
  idField?: string;
3697
+ /**
3698
+ * Resource-level plugin extensions (`defineResource({ extensions })`).
3699
+ * Stamped onto every registered route's Fastify `config.arcExtensions`
3700
+ * so request-time plugins (encryption, …) read their typed slice without
3701
+ * re-resolving which resource a route belongs to. See `ResourceExtensions`.
3702
+ */
3703
+ extensions?: ResourceExtensions;
3622
3704
  }
3623
3705
  //#endregion
3624
3706
  //#region src/types/query.d.ts
@@ -4032,4 +4114,4 @@ interface ValidateOptions {
4032
4114
  strict?: boolean;
4033
4115
  }
4034
4116
  //#endregion
4035
- export { OnTenantDeleteConfig as $, BaseController as $t, RequestIdOptions as A, ArcDeleteResult as An, AggregationRateLimit as At, defineResource as B, AuthHelpers as Bt, RequestContext as C, afterUpdate as Cn, AggMeasureShorthand as Ct, HealthCheck as D, createHookSystem as Dn, AggregationIndexHint as Dt, GracefulShutdownOptions as E, beforeUpdate as En, AggregationDateRangeRequirement as Et, FastifyWithDecorators as F, ControllerConfigurableOptions as Fn, OperationFilter as Ft, ActionsMap as G, TokenPair as Gt, ActionDefinition as H, Authenticator as Ht, MiddlewareHandler as I, ControllerConstructionOptions as In, PipelineConfig as It, CrudRouteKey as J, ArcRequest as Jt, ArcFieldRule as K, AnyRecord as Kt, RequestWithExtras as L, ListResult as Ln, PipelineContext as Lt, EventsDecorator as M, ArcListResult as Mn, Guard as Mt, FastifyRequestExtras as N, ArcUpdateResult as Nn, Interceptor as Nt, HealthOptions as O, defineHook as On, AggregationMaterializedContext as Ot, FastifyWithAuth as P, BaseControllerOptions as Pn, NextFunction as Pt, MiddlewareConfig as Q, UserOrganization as Qt, RegisterOptions as R, BodySanitizer as Rn, PipelineStep as Rt, QueryParserInterface as S, afterDelete as Sn, AggMeasureInput as St, CrudRouterOptions as T, beforeDelete as Tn, AggregationConfig as Tt, ActionEntry as U, AuthenticatorContext as Ut, ResourceDefinition as V, AuthPluginOptions as Vt, ActionHandlerFn as W, JwtContext as Wt, EventDefinition as X, ObjectId as Xt, CrudSchemas as Y, JWTPayload as Yt, FieldRule$1 as Z, UserLike as Zt, ControllerQueryOptions as _, HookPhase as _n, FastifyHandler as _t, InferAdapterDoc as a, SlugMixin as an, ResolvedTenantPurge as at, ParsedQuery as b, HookSystemOptions as bn, IRequestContext as bt, TypedController as c, BaseCrudController as cn, ResourceHookContext as ct, PaginationResult as d, AccessControl as dn, RouteDefinition as dt, SoftDeleteExt as en, OpenApiSchemas as et, IntrospectionData as f, AccessControlConfig as fn, RouteMcpConfig as ft, ArcInternalMetadata as g, HookOperation as gn, ControllerLike as gt, ResourceMetadata as h, HookHandler as hn, ControllerHandler as ht, ValidationResult as i, SlugExt as in, RateLimitConfig as it, ArcDecorator as j, ArcGetResult as jn, AggregationsMap as jt, IntrospectionPluginOptions as k, ArcCreateResult as kn, AggregationMaterializedResult as kt, TypedRepository as l, QueryResolver as ln, ResourceHooks as lt, RegistryStats as m, HookContext as mn, RouteSchemaOptions as mt, ConfigError as n, TreeExt as nn, PresetHook as nt, InferDocType as o, BulkExt as on, ResourceCacheConfig as ot, RegistryEntry as p, DefineHookOptions as pn, RouteMethod as pt, CrudController as q, ApiResponse as qt, ValidateOptions as r, TreeMixin as rn, PresetResult as rt, InferResourceDoc as s, BulkMixin as sn, ResourceConfig as st, RouteHandlerMethod$1 as t, SoftDeleteMixin as tn, PresetFunction as tt, TypedResourceConfig as u, QueryResolverConfig as un, ResourcePermissions as ut, LookupOption as v, HookRegistration as vn, IController as vt, ServiceContext as w, beforeCreate as wn, AggregationCacheConfig as wt, PopulateOption as x, afterCreate as xn, RouteHandler as xt, OwnershipCheck as y, HookSystem as yn, IControllerResponse as yt, ResourceRegistry as z, BodySanitizerConfig as zn, Transform as zt };
4117
+ export { OnTenantDeleteConfig as $, UserOrganization as $t, RequestIdOptions as A, ArcCreateResult as An, AggregationMaterializedResult as At, defineResource as B, BodySanitizerConfig as Bn, Transform as Bt, RequestContext as C, afterDelete as Cn, AggMeasureInput as Ct, HealthCheck as D, beforeUpdate as Dn, AggregationDateRangeRequirement as Dt, GracefulShutdownOptions as E, beforeDelete as En, AggregationConfig as Et, FastifyWithDecorators as F, BaseControllerOptions as Fn, NextFunction as Ft, ActionsMap as G, JwtContext as Gt, ActionDefinition as H, AuthPluginOptions as Ht, MiddlewareHandler as I, ControllerConfigurableOptions as In, OperationFilter as It, CrudRouteKey as J, ApiResponse as Jt, ArcFieldRule as K, TokenPair as Kt, RequestWithExtras as L, ControllerConstructionOptions as Ln, PipelineConfig as Lt, EventsDecorator as M, ArcGetResult as Mn, AggregationsMap as Mt, FastifyRequestExtras as N, ArcListResult as Nn, Guard as Nt, HealthOptions as O, createHookSystem as On, AggregationIndexHint as Ot, FastifyWithAuth as P, ArcUpdateResult as Pn, Interceptor as Pt, MiddlewareConfig as Q, UserLike as Qt, RegisterOptions as R, ListResult as Rn, PipelineContext as Rt, QueryParserInterface as S, afterCreate as Sn, RouteHandler as St, CrudRouterOptions as T, beforeCreate as Tn, AggregationCacheConfig as Tt, ActionEntry as U, Authenticator as Ut, ResourceDefinition as V, AuthHelpers as Vt, ActionHandlerFn as W, AuthenticatorContext as Wt, EventDefinition as X, JWTPayload as Xt, CrudSchemas as Y, ArcRequest as Yt, FieldRule$1 as Z, ObjectId as Zt, ControllerQueryOptions as _, HookOperation as _n, ControllerLike as _t, InferAdapterDoc as a, SlugExt as an, ResolvedTenantPurge as at, ParsedQuery as b, HookSystem as bn, IControllerResponse as bt, TypedController as c, BulkMixin as cn, ResourceExtensions as ct, PaginationResult as d, QueryResolverConfig as dn, ResourcePermissions as dt, BaseController as en, OpenApiSchemas as et, IntrospectionData as f, AccessControl as fn, RouteDefinition as ft, ArcInternalMetadata as g, HookHandler as gn, ControllerHandler as gt, ResourceMetadata as h, HookContext as hn, RouteSchemaOptions as ht, ValidationResult as i, TreeMixin as in, RateLimitConfig as it, ArcDecorator as j, ArcDeleteResult as jn, AggregationRateLimit as jt, IntrospectionPluginOptions as k, defineHook as kn, AggregationMaterializedContext as kt, TypedRepository as l, BaseCrudController as ln, ResourceHookContext as lt, RegistryStats as m, DefineHookOptions as mn, RouteMethod as mt, ConfigError as n, SoftDeleteMixin as nn, PresetHook as nt, InferDocType as o, SlugMixin as on, ResourceCacheConfig as ot, RegistryEntry as p, AccessControlConfig as pn, RouteMcpConfig as pt, CrudController as q, AnyRecord as qt, ValidateOptions as r, TreeExt as rn, PresetResult as rt, InferResourceDoc as s, BulkExt as sn, ResourceConfig as st, RouteHandlerMethod$1 as t, SoftDeleteExt as tn, PresetFunction as tt, TypedResourceConfig as u, QueryResolver as un, ResourceHooks as ut, LookupOption as v, HookPhase as vn, FastifyHandler as vt, ServiceContext as w, afterUpdate as wn, AggMeasureShorthand as wt, PopulateOption as x, HookSystemOptions as xn, IRequestContext as xt, OwnershipCheck as y, HookRegistration as yn, IController as yt, ResourceRegistry as z, BodySanitizer as zn, PipelineStep as zt };
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { $t as BaseController, A as RequestIdOptions, An as ArcDeleteResult, At as AggregationRateLimit, B as defineResource, C as RequestContext, Ct as AggMeasureShorthand, D as HealthCheck, Dt as AggregationIndexHint, E as GracefulShutdownOptions, Et as AggregationDateRangeRequirement, F as FastifyWithDecorators, Fn as ControllerConfigurableOptions, In as ControllerConstructionOptions, J as CrudRouteKey, Jt as ArcRequest, Kt as AnyRecord, L as RequestWithExtras, Ln as ListResult, Mn as ArcListResult, N as FastifyRequestExtras, Nn as ArcUpdateResult, O as HealthOptions, Ot as AggregationMaterializedContext, P as FastifyWithAuth, Pn as BaseControllerOptions, Q as MiddlewareConfig, Qt as UserOrganization, S as QueryParserInterface, St as AggMeasureInput, T as CrudRouterOptions, Tt as AggregationConfig, V as ResourceDefinition, Vt as AuthPluginOptions, X as EventDefinition, Y as CrudSchemas, Yt as JWTPayload, Z as FieldRule, a as InferAdapterDoc, an as SlugMixin, bt as IRequestContext, c as TypedController, cn as BaseCrudController, d as PaginationResult, en as SoftDeleteExt, f as IntrospectionData, g as ArcInternalMetadata, gt as ControllerLike, h as ResourceMetadata, i as ValidationResult, in as SlugExt, it as RateLimitConfig, jn as ArcGetResult, jt as AggregationsMap, k as IntrospectionPluginOptions, kn as ArcCreateResult, kt as AggregationMaterializedResult, l as TypedRepository, m as RegistryStats, mt as RouteSchemaOptions, n as ConfigError, nn as TreeExt, o as InferDocType, on as BulkExt, p as RegistryEntry, q as CrudController, qt as ApiResponse, r as ValidateOptions, rn as TreeMixin, rt as PresetResult, s as InferResourceDoc, sn as BulkMixin, st as ResourceConfig, t as RouteHandlerMethod, tn as SoftDeleteMixin, tt as PresetFunction, u as TypedResourceConfig, vt as IController, w as ServiceContext, wt as AggregationCacheConfig, xt as RouteHandler, y as OwnershipCheck, yt as IControllerResponse } from "./index-CHNQIhcb.mjs";
1
+ import { $t as UserOrganization, A as RequestIdOptions, An as ArcCreateResult, At as AggregationMaterializedResult, B as defineResource, C as RequestContext, Ct as AggMeasureInput, D as HealthCheck, Dt as AggregationDateRangeRequirement, E as GracefulShutdownOptions, Et as AggregationConfig, F as FastifyWithDecorators, Fn as BaseControllerOptions, Ht as AuthPluginOptions, In as ControllerConfigurableOptions, J as CrudRouteKey, Jt as ApiResponse, L as RequestWithExtras, Ln as ControllerConstructionOptions, Mn as ArcGetResult, Mt as AggregationsMap, N as FastifyRequestExtras, Nn as ArcListResult, O as HealthOptions, Ot as AggregationIndexHint, P as FastifyWithAuth, Pn as ArcUpdateResult, Q as MiddlewareConfig, Rn as ListResult, S as QueryParserInterface, St as RouteHandler, T as CrudRouterOptions, Tt as AggregationCacheConfig, V as ResourceDefinition, X as EventDefinition, Xt as JWTPayload, Y as CrudSchemas, Yt as ArcRequest, Z as FieldRule, _t as ControllerLike, a as InferAdapterDoc, an as SlugExt, bt as IControllerResponse, c as TypedController, cn as BulkMixin, d as PaginationResult, en as BaseController, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, ht as RouteSchemaOptions, i as ValidationResult, in as TreeMixin, it as RateLimitConfig, jn as ArcDeleteResult, jt as AggregationRateLimit, k as IntrospectionPluginOptions, kt as AggregationMaterializedContext, l as TypedRepository, ln as BaseCrudController, m as RegistryStats, n as ConfigError, nn as SoftDeleteMixin, o as InferDocType, on as SlugMixin, p as RegistryEntry, q as CrudController, qt as AnyRecord, r as ValidateOptions, rn as TreeExt, rt as PresetResult, s as InferResourceDoc, sn as BulkExt, st as ResourceConfig, t as RouteHandlerMethod, tn as SoftDeleteExt, tt as PresetFunction, u as TypedResourceConfig, w as ServiceContext, wt as AggMeasureShorthand, xt as IRequestContext, y as OwnershipCheck, yt as IController } from "./index-CUTG2Cuo.mjs";
2
2
  import { a as applyFieldWritePermissions, c as PermissionCheck, d as UserBase, i as applyFieldReadPermissions, l as PermissionContext, n as FieldPermissionMap, o as fields, t as FieldPermission, u as PermissionResult } from "./fields-C7ahdGYf.mjs";
3
- import { A as MAX_FILTER_DEPTH, C as DEFAULT_SORT, D as HOOK_PHASES, E as HOOK_OPERATIONS, F as RESERVED_QUERY_PARAMS, I as SYSTEM_FIELDS, M as MAX_SEARCH_LENGTH, N as MUTATION_OPERATIONS, O as HookOperation, P as MutationOperation, S as DEFAULT_MAX_LIMIT, T as DEFAULT_UPDATE_METHOD, _ as defineAggregation, b as DEFAULT_ID_FIELD, f as defineResourceVariants, j as MAX_REGEX_LENGTH, k as HookPhase, m as defineAction, s as getControllerScope, v as CRUD_OPERATIONS, w as DEFAULT_TENANT_FIELD, x as DEFAULT_LIMIT, y as CrudOperation } from "./index-Dh0_RSTd.mjs";
3
+ import { A as MAX_FILTER_DEPTH, C as DEFAULT_SORT, D as HOOK_PHASES, E as HOOK_OPERATIONS, F as RESERVED_QUERY_PARAMS, I as SYSTEM_FIELDS, M as MAX_SEARCH_LENGTH, N as MUTATION_OPERATIONS, O as HookOperation, P as MutationOperation, S as DEFAULT_MAX_LIMIT, T as DEFAULT_UPDATE_METHOD, _ as defineAggregation, b as DEFAULT_ID_FIELD, f as defineResourceVariants, j as MAX_REGEX_LENGTH, k as HookPhase, m as defineAction, s as getControllerScope, v as CRUD_OPERATIONS, w as DEFAULT_TENANT_FIELD, x as DEFAULT_LIMIT, y as CrudOperation } from "./index-CP6A1Ffj.mjs";
4
4
  import { A as requireRoles, C as allOf, E as denyAll, M as when, O as requireAuth, S as createOrgPermissions, T as anyOf, a as presets_d_exports, c as readOnly, d as requireOrgRole, f as requireScopeContext, i as ownerWithAdminBypass, k as requireOwnership, l as requireOrgInScope, m as requireTeamMembership, n as authenticated, o as publicRead, p as requireServiceScope, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as requireOrgMembership, v as DynamicPermissionMatrix, w as allowPublic, x as createDynamicPermissionMatrix, y as DynamicPermissionMatrixConfig } from "./index-CRKiiP5o.mjs";
5
5
  import { a as NotFoundError, d as ValidationError, f as createDomainError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-DOHkjQlD.mjs";
6
6
 
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
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-gaEeMlz-.mjs";
2
2
  import { d as createDomainError, i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-DkO8Nk6-.mjs";
3
3
  import { a as BulkMixin, i as SlugMixin, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, t as BaseController } from "./BaseController-Dy6EQHjM.mjs";
4
- import { a as defineResource, i as defineResourceVariants, o as ResourceDefinition, s as defineAction, u as defineAggregation } from "./core-ZnQBa5rh.mjs";
4
+ import { a as defineResource, i as defineResourceVariants, o as ResourceDefinition, s as defineAction, u as defineAggregation } from "./core-Ba-6Sue6.mjs";
5
5
  import { C as requireAuth, D as when, F as applyFieldWritePermissions, I as fields, P as applyFieldReadPermissions, T as requireRoles, _ as requireTeamMembership, a as presets_exports, b as anyOf, c as readOnly, d as createOrgPermissions, f as requireOrgInScope, g as requireServiceScope, h as requireScopeContext, i as ownerWithAdminBypass, m as requireOrgRole, n as authenticated, o as publicRead, p as requireOrgMembership, r as fullPublic, s as publicReadAdminWrite, t as adminOnly, u as createDynamicPermissionMatrix, v as allOf, w as requireOwnership, x as denyAll, y as allowPublic } from "./permissions-BPB1xqGf.mjs";
6
- import { l as getControllerScope } from "./actionPermissions-h7DYDNMg.mjs";
6
+ import { l as getControllerScope } from "./actionPermissions-D7NGwcAR.mjs";
7
7
  //#region src/index.ts
8
- const version = "2.18.5";
8
+ const version = "2.19.0";
9
9
  //#endregion
10
10
  export { ArcError, BaseController, BaseCrudController, BulkMixin, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, ForbiddenError, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, NotFoundError, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugMixin, SoftDeleteMixin, TreeMixin, UnauthorizedError, ValidationError, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, createDomainError, createDynamicPermissionMatrix, createOrgPermissions, defineAction, defineAggregation, defineResource, defineResourceVariants, denyAll, fields, fullPublic, getControllerScope, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, version, when };
@@ -1,7 +1,7 @@
1
1
  import { c as WebSocketClient, l as WebSocketMessage, u as WebSocketPluginOptions } from "../websocket-MlYlrYq0.mjs";
2
2
  import { EventGatewayOptions } from "./event-gateway.mjs";
3
3
  import { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, QueueStats } from "./jobs.mjs";
4
- import { c as McpAuthResult, d as PromptDefinition, g as ToolDefinition, h as ToolContext, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, r as CreateMcpServerConfig, t as BetterAuthHandler, u as McpResourceConfig } from "../types-D60PFwfG.mjs";
4
+ import { c as McpAuthResult, d as PromptDefinition, g as ToolDefinition, h as ToolContext, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, r as CreateMcpServerConfig, t as BetterAuthHandler, u as McpResourceConfig } from "../types-DpvSF5Hh.mjs";
5
5
  import { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike } from "./streamline.mjs";
6
6
  import { WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription } from "./webhooks.mjs";
7
7
  export type { BetterAuthHandler, CallToolResult, CreateMcpServerConfig, CrudOperation, EventGatewayOptions, JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, McpAuthResult, McpPluginOptions, McpResourceConfig, PromptDefinition, QueueStats, StreamlinePluginOptions, ToolAnnotations, ToolContext, ToolDefinition, WebSocketClient, WebSocketMessage, WebSocketPluginOptions, WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, WorkflowLike, WorkflowRunLike };
@@ -1,7 +1,7 @@
1
- import { V as ResourceDefinition, bt as IRequestContext, yt as IControllerResponse } from "../../index-CHNQIhcb.mjs";
1
+ import { V as ResourceDefinition, bt as IControllerResponse, xt as IRequestContext } from "../../index-CUTG2Cuo.mjs";
2
2
  import { i as RequestScope } from "../../types-LNebCoJx.mjs";
3
3
  import { c as PermissionCheck } from "../../fields-C7ahdGYf.mjs";
4
- import { a as CrudDescriptionOverride, c as McpAuthResult, d as PromptDefinition, f as PromptResult, g as ToolDefinition, h as ToolContext, i as CrudDescriptionMeta, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, p as SessionEntry, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpResourceConfig } from "../../types-D60PFwfG.mjs";
4
+ import { a as CrudDescriptionOverride, c as McpAuthResult, d as PromptDefinition, f as PromptResult, g as ToolDefinition, h as ToolContext, i as CrudDescriptionMeta, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, p as SessionEntry, r as CreateMcpServerConfig, s as McpAuthResolver, t as BetterAuthHandler, u as McpResourceConfig } from "../../types-DpvSF5Hh.mjs";
5
5
  import { ErrorContract } from "@classytic/repo-core/errors";
6
6
  import { FastifyPluginAsync } from "fastify";
7
7
  import { z } from "zod";
@@ -1,4 +1,4 @@
1
- import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, h as resolveToolCollisions, i as fieldRulesToZod, l as permissionDeniedResult, m as createMcpServer, n as mcpPlugin, o as resolveCrudDescription, p as buildRequestContext, r as resourceToTools, s as invokeController, t as filterResourcesForMcp, u as toCallToolError } from "../../mcpPlugin-D1wuRD2c.mjs";
1
+ import { a as defaultCrudDescription, c as mcpHandlerAdapter, d as toCallToolResult, f as toCallToolSuccess, h as resolveToolCollisions, i as fieldRulesToZod, l as permissionDeniedResult, m as createMcpServer, n as mcpPlugin, o as resolveCrudDescription, p as buildRequestContext, r as resourceToTools, s as invokeController, t as filterResourcesForMcp, u as toCallToolError } from "../../mcpPlugin-C088Nd-G.mjs";
2
2
  //#region src/integrations/mcp/defineTool.ts
3
3
  /**
4
4
  * Define a type-safe MCP tool.
@@ -1,4 +1,4 @@
1
- import { c as McpAuthResult, l as McpPluginOptions } from "../../types-D60PFwfG.mjs";
1
+ import { c as McpAuthResult, l as McpPluginOptions } from "../../types-DpvSF5Hh.mjs";
2
2
 
3
3
  //#region src/integrations/mcp/testing.d.ts
4
4
  interface TestMcpClientOptions {
@@ -1,4 +1,4 @@
1
- import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-D1wuRD2c.mjs";
1
+ import { m as createMcpServer, r as resourceToTools, t as filterResourcesForMcp } from "../../mcpPlugin-C088Nd-G.mjs";
2
2
  //#region src/integrations/mcp/testing.ts
3
3
  /**
4
4
  * @classytic/arc/mcp/testing — MCP Test Utilities
@@ -1,7 +1,7 @@
1
1
  import { p as isArcError, t as ArcError } from "./errors-DkO8Nk6-.mjs";
2
2
  import { t as BaseController } from "./BaseController-Dy6EQHjM.mjs";
3
3
  import { N as normalizePermissionResult } from "./permissions-BPB1xqGf.mjs";
4
- import { i as resolvePipelineSteps, t as resolveActionPermission } from "./actionPermissions-h7DYDNMg.mjs";
4
+ import { i as resolvePipelineSteps, t as resolveActionPermission } from "./actionPermissions-D7NGwcAR.mjs";
5
5
  import { t as executePipeline } from "./pipe-BOLmGIxa.mjs";
6
6
  import { t as pluralize } from "./pluralize-DccFV9R7.mjs";
7
7
  import { i as shouldRejectAdditionalProperties, r as schemaIRToZodShape, t as normalizeSchemaIR } from "./schemaIR-D1MJwqmp.mjs";
@@ -1,4 +1,4 @@
1
- import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-CHNQIhcb.mjs";
1
+ import { I as MiddlewareHandler, L as RequestWithExtras, Q as MiddlewareConfig } from "../index-CUTG2Cuo.mjs";
2
2
  import { RouteHandlerMethod } from "fastify";
3
3
 
4
4
  //#region src/middleware/middleware.d.ts
@@ -1,8 +1,8 @@
1
1
  import { t as getUserRoles } from "./types-XTApa8b6.mjs";
2
2
  import { n as convertRouteSchema } from "./schemaConverter-U0MnO9vd.mjs";
3
- import { t as resolveActionPermission } from "./actionPermissions-h7DYDNMg.mjs";
3
+ import { t as resolveActionPermission } from "./actionPermissions-D7NGwcAR.mjs";
4
4
  import { t as pluralize } from "./pluralize-DccFV9R7.mjs";
5
- import { t as buildActionBodySchema } from "./createActionRouter-CagItpxI.mjs";
5
+ import { t as buildActionBodySchema } from "./createActionRouter-B_WzBFyf.mjs";
6
6
  import fp from "fastify-plugin";
7
7
  //#region src/docs/openapi/canonical-schemas.ts
8
8
  /**
@@ -1,4 +1,4 @@
1
- import { Ft as OperationFilter, It as PipelineConfig, Lt as PipelineContext, Mt as Guard, Nt as Interceptor, Pt as NextFunction, Rt as PipelineStep, yt as IControllerResponse, zt as Transform } from "../index-CHNQIhcb.mjs";
1
+ import { Bt as Transform, Ft as NextFunction, It as OperationFilter, Lt as PipelineConfig, Nt as Guard, Pt as Interceptor, Rt as PipelineContext, bt as IControllerResponse, zt as PipelineStep } from "../index-CUTG2Cuo.mjs";
2
2
 
3
3
  //#region src/pipeline/guard.d.ts
4
4
  interface GuardOptions {
@@ -1,5 +1,5 @@
1
- import { Kt as AnyRecord, Q as MiddlewareConfig, dt as RouteDefinition, mt as RouteSchemaOptions, nt as PresetHook } from "../index-CHNQIhcb.mjs";
2
- import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-Cxb0ROIw.mjs";
1
+ import { Q as MiddlewareConfig, ft as RouteDefinition, ht as RouteSchemaOptions, nt as PresetHook, qt as AnyRecord } from "../index-CUTG2Cuo.mjs";
2
+ import { a as arcCorePlugin, i as _default, n as ArcCorePluginOptions, r as PluginMeta, t as ArcCore } from "../fastify-augmentation-D-5_yQJi.mjs";
3
3
  import { _ as HealthOptions, a as MetricsCollector, c as metricsPlugin, d as ssePlugin, f as CachingOptions, g as HealthCheck, h as cachingPlugin, i as MetricEntry, l as SSEOptions, m as _default$1, n as _default$7, o as MetricsOptions, p as CachingRule, r as versioningPlugin, s as _default$4, t as VersioningOptions, u as _default$6, v as _default$3, y as healthPlugin } from "../versioning-D_eBCfNr.mjs";
4
4
  import { i as errorHandlerPlugin, n as ErrorMapper, r as defaultIsDuplicateKeyError, t as ErrorHandlerOptions } from "../errorHandler-BtUUDPEa.mjs";
5
5
  import { t as TracingOptions } from "../tracing-D8jlGXP7.mjs";
@@ -58,7 +58,7 @@ try {
58
58
  function createTracerProvider(options) {
59
59
  if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
60
60
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
61
- const resolvedVersion = serviceVersion ?? "2.18.5";
61
+ const resolvedVersion = serviceVersion ?? "2.19.0";
62
62
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
63
63
  const provider = new NodeTracerProvider({ resource: { attributes: {
64
64
  "service.name": serviceName,
@@ -1,4 +1,4 @@
1
- import { rt as PresetResult } from "../index-CHNQIhcb.mjs";
1
+ import { rt as PresetResult } from "../index-CUTG2Cuo.mjs";
2
2
  import { i as RequestScope } from "../types-LNebCoJx.mjs";
3
3
  import { c as PermissionCheck } from "../fields-C7ahdGYf.mjs";
4
4
  import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-Co_PQpeQ.mjs";
@@ -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-CHNQIhcb.mjs";
1
+ import { bt as IControllerResponse, d as PaginationResult, qt as AnyRecord, rt as PresetResult, st as ResourceConfig, xt as IRequestContext } from "../index-CUTG2Cuo.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-CHNQIhcb.mjs";
1
+ import { J as CrudRouteKey, rt as PresetResult } from "../index-CUTG2Cuo.mjs";
2
2
 
3
3
  //#region src/presets/multiTenant.d.ts
4
4
  /**