@classytic/arc 2.18.4 → 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 (160) hide show
  1. package/dist/{BaseController-B-uXzf6I.mjs → BaseController-Dy6EQHjM.mjs} +7 -7
  2. package/dist/{QueryCache-Dsyv2mLS.d.mts → QueryCache-TRfXau3W.d.mts} +1 -1
  3. package/dist/{ResourceRegistry-CWmOggxS.mjs → ResourceRegistry-5fKZwhK_.mjs} +1 -1
  4. package/dist/{pipeline-D4sqiokR.mjs → actionPermissions-D7NGwcAR.mjs} +29 -5
  5. package/dist/audit/index.d.mts +1 -1
  6. package/dist/auth/index.d.mts +4 -4
  7. package/dist/auth/index.mjs +262 -6
  8. package/dist/auth/redis-session.d.mts +1 -1
  9. package/dist/betterAuthOpenApi-CV1lxpRX.mjs +2 -0
  10. package/dist/{buildHandler-Cvrc2p7r.mjs → buildHandler-B0qvAguf.mjs} +1 -1
  11. package/dist/cache/index.d.mts +3 -3
  12. package/dist/cache/index.mjs +3 -3
  13. package/dist/{caching-C0DdKiYB.mjs → caching-_NJeDd1_.mjs} +1 -1
  14. package/dist/cli/commands/describe.d.mts +1 -1
  15. package/dist/cli/commands/describe.mjs +2 -2
  16. package/dist/cli/commands/docs.mjs +2 -2
  17. package/dist/cli/commands/generate.d.mts +2 -2
  18. package/dist/cli/commands/generate.mjs +1 -1
  19. package/dist/cli/commands/init.d.mts +2 -2
  20. package/dist/cli/commands/init.mjs +1 -1
  21. package/dist/cli/commands/introspect.mjs +1 -1
  22. package/dist/cli/index.d.mts +2 -2
  23. package/dist/cli/index.mjs +2 -2
  24. package/dist/context/index.mjs +1 -1
  25. package/dist/core/index.d.mts +3 -3
  26. package/dist/core/index.mjs +4 -4
  27. package/dist/{core-C88uIwd3.mjs → core-Ba-6Sue6.mjs} +67 -63
  28. package/dist/{createActionRouter-DGCCp-kP.mjs → createActionRouter-B_WzBFyf.mjs} +10 -10
  29. package/dist/{createAggregationRouter-Ca77b8iK.mjs → createAggregationRouter-B_G_nIjm.mjs} +4 -4
  30. package/dist/{createApp-J1iPXY9p.mjs → createApp--703hQE1.mjs} +14 -14
  31. package/dist/docs/index.d.mts +2 -2
  32. package/dist/docs/index.mjs +2 -2
  33. package/dist/{elevation-BzhyVWfn.mjs → elevation-COpYKt2h.mjs} +2 -2
  34. package/dist/encryption/index.d.mts +222 -0
  35. package/dist/encryption/index.mjs +270 -0
  36. package/dist/{errorHandler-Bua97SEt.mjs → errorHandler-Dxf6P_kV.mjs} +2 -2
  37. package/dist/{eventPlugin-Bw4bUDEO.mjs → eventPlugin-DJG2D7sy.mjs} +106 -5
  38. package/dist/{eventPlugin-C1W1CRbf.d.mts → eventPlugin-DbBFd3x_.d.mts} +1 -1
  39. package/dist/events/index.d.mts +3 -3
  40. package/dist/events/index.mjs +189 -5
  41. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  42. package/dist/events/transports/redis.d.mts +1 -1
  43. package/dist/factory/index.d.mts +1 -1
  44. package/dist/factory/index.mjs +3 -3
  45. package/dist/{fastify-augmentation-BdCl_eNo.d.mts → fastify-augmentation-D-5_yQJi.d.mts} +2 -2
  46. package/dist/{fields-CKLrhyOb.d.mts → fields-C7ahdGYf.d.mts} +1 -1
  47. package/dist/{generate-BaxDdmJ8.mjs → generate-Brans3fW.mjs} +1 -1
  48. package/dist/hooks/index.d.mts +1 -1
  49. package/dist/hooks/index.mjs +1 -1
  50. package/dist/idempotency/index.d.mts +2 -2
  51. package/dist/idempotency/index.mjs +1 -1
  52. package/dist/idempotency/redis.d.mts +1 -1
  53. package/dist/{index-Cvo1u9n6.d.mts → index-CP6A1Ffj.d.mts} +9 -9
  54. package/dist/{index-vcWUGkwf.d.mts → index-CRKiiP5o.d.mts} +3 -3
  55. package/dist/{index-QYma8mdC.d.mts → index-CUTG2Cuo.d.mts} +88 -7
  56. package/dist/index.d.mts +5 -5
  57. package/dist/index.mjs +7 -7
  58. package/dist/{init-DSsdRfk9.mjs → init-Bqpt1Hxf.mjs} +274 -130
  59. package/dist/integrations/event-gateway.d.mts +2 -2
  60. package/dist/integrations/event-gateway.mjs +1 -1
  61. package/dist/integrations/index.d.mts +3 -3
  62. package/dist/integrations/mcp/index.d.mts +4 -4
  63. package/dist/integrations/mcp/index.mjs +1 -1
  64. package/dist/integrations/mcp/testing.d.mts +1 -1
  65. package/dist/integrations/mcp/testing.mjs +1 -1
  66. package/dist/integrations/streamline.mjs +7 -3
  67. package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
  68. package/dist/integrations/websocket-redis.d.mts +1 -1
  69. package/dist/integrations/websocket.d.mts +3 -3
  70. package/dist/{loadResources-CwXt9MCh.mjs → loadResources-B8UmpITt.mjs} +1 -1
  71. package/dist/{loadResourcesFromEntry-XHNeUWpu.mjs → loadResourcesFromEntry-BTlFEx05.mjs} +1 -1
  72. package/dist/{mcpPlugin-CzKdt3B8.mjs → mcpPlugin-C088Nd-G.mjs} +10 -11
  73. package/dist/{memory-TdTHCS9-.mjs → memory-CLXA2VeJ.mjs} +1 -1
  74. package/dist/{metrics-Da2EUPi9.mjs → metrics-fdxI5mn_.mjs} +1 -1
  75. package/dist/middleware/index.d.mts +1 -1
  76. package/dist/middleware/index.mjs +2 -2
  77. package/dist/{openapi-ButnRyOM.mjs → openapi-DZ-5a5Op.mjs} +5 -5
  78. package/dist/permissions/index.d.mts +3 -3
  79. package/dist/permissions/index.mjs +2 -2
  80. package/dist/{permissions-3uzwkbQt.mjs → permissions-BPB1xqGf.mjs} +4 -4
  81. package/dist/{pipe-9Tl-OAus.mjs → pipe-BOLmGIxa.mjs} +1 -1
  82. package/dist/pipeline/index.d.mts +1 -1
  83. package/dist/pipeline/index.mjs +1 -1
  84. package/dist/plugins/index.d.mts +6 -7
  85. package/dist/plugins/index.mjs +12 -12
  86. package/dist/plugins/response-cache.mjs +1 -1
  87. package/dist/plugins/tracing-entry.d.mts +1 -1
  88. package/dist/plugins/tracing-entry.mjs +1 -1
  89. package/dist/presets/filesUpload.d.mts +4 -4
  90. package/dist/presets/filesUpload.mjs +4 -4
  91. package/dist/presets/index.d.mts +1 -1
  92. package/dist/presets/index.mjs +1 -1
  93. package/dist/presets/multiTenant.d.mts +1 -1
  94. package/dist/presets/multiTenant.mjs +2 -2
  95. package/dist/presets/search.d.mts +2 -2
  96. package/dist/presets/search.mjs +1 -1
  97. package/dist/{presets-CF79RHEo.mjs → presets-BtyFwTO4.mjs} +2 -2
  98. package/dist/{queryCachePlugin-CnAeFqju.mjs → queryCachePlugin-emHb0CuX.mjs} +4 -4
  99. package/dist/{queryCachePlugin-Bd2tYgh5.d.mts → queryCachePlugin-xzFyKAjT.d.mts} +2 -2
  100. package/dist/{redis-3hqH_Qk_.d.mts → redis-C3W0qbz8.d.mts} +1 -1
  101. package/dist/{redis-stream-BtEUtjxZ.d.mts → redis-stream-DsP1vqZB.d.mts} +1 -1
  102. package/dist/{rateLimit-yKvxWAky.mjs → registerRoute-BdV8DDwU.mjs} +127 -2
  103. package/dist/registry/index.d.mts +1 -1
  104. package/dist/registry/index.mjs +2 -2
  105. package/dist/{registry-Br15xdGM.mjs → registry-Bm9LjWKs.mjs} +1 -1
  106. package/dist/{replyHelpers-DXHVxrrj.mjs → replyHelpers-67tRawQZ.mjs} +1 -1
  107. package/dist/{schemaIR-CE6USIRi.mjs → schemaIR-D1MJwqmp.mjs} +1 -1
  108. package/dist/schemas/index.d.mts +64 -65
  109. package/dist/scope/index.d.mts +2 -2
  110. package/dist/scope/index.mjs +3 -3
  111. package/dist/{sse-BrY1W6U8.mjs → sse-CfUjwjDk.mjs} +7 -4
  112. package/dist/streaming-BQcW6_Kq.mjs +162 -0
  113. package/dist/testing/index.d.mts +7 -7
  114. package/dist/testing/index.mjs +3 -3
  115. package/dist/testing/storageContract.d.mts +1 -1
  116. package/dist/types/index.d.mts +5 -5
  117. package/dist/types/storage.d.mts +1 -1
  118. package/dist/{types-WVlAVp8T.d.mts → types-9_wtNPsI.d.mts} +10 -10
  119. package/dist/{types-BWBcb88c.d.mts → types-DpvSF5Hh.d.mts} +1 -1
  120. package/dist/{types-Bi0r0vjG.mjs → types-bHblZ9YN.mjs} +1 -1
  121. package/dist/utils/index.d.mts +42 -4
  122. package/dist/utils/index.mjs +6 -5
  123. package/dist/{utils-xAQeDyU_.mjs → utils-C6QQ9-hd.mjs} +1415 -1524
  124. package/dist/{validate-0__MuUsK.mjs → validate-CRcaWzp2.mjs} +1 -1
  125. package/dist/{versioning-BSEDGXKC.d.mts → versioning-D_eBCfNr.d.mts} +8 -1
  126. package/dist/{versioning-C0K8KQLx.mjs → versioning-Dk-71iIz.mjs} +1 -1
  127. package/dist/{websocket-BFBT5rH-.d.mts → websocket-MlYlrYq0.d.mts} +1 -1
  128. package/package.json +45 -30
  129. package/dist/EventTransport-7LPXLEUX.mjs +0 -103
  130. package/dist/actionPermissions-CX9QIMTd.mjs +0 -24
  131. package/dist/betterAuthOpenApi-DAAhMZff.mjs +0 -251
  132. package/dist/defineEvent-DC981g-o.mjs +0 -188
  133. package/dist/registerRoute-BEYn0ylk.mjs +0 -108
  134. /package/dist/{EventTransport-CaaXM5Y1.d.mts → EventTransport-IEn7Tn-Q.d.mts} +0 -0
  135. /package/dist/{HookSystem-BfCpWbwM.mjs → HookSystem-C1vB_Gc9.mjs} +0 -0
  136. /package/dist/{constants-TrJVIJl0.mjs → constants-gaEeMlz-.mjs} +0 -0
  137. /package/dist/{elevation-DW90z5bT.d.mts → elevation-xGUBq6p_.d.mts} +0 -0
  138. /package/dist/{errorHandler-_Lp2k9M3.d.mts → errorHandler-BtUUDPEa.d.mts} +0 -0
  139. /package/dist/{errors-DZOemeGa.d.mts → errors-DOHkjQlD.d.mts} +0 -0
  140. /package/dist/{errors-j4aJm1Wg.mjs → errors-DkO8Nk6-.mjs} +0 -0
  141. /package/dist/{externalPaths-DLle0R07.d.mts → externalPaths-BE6wFpYv.d.mts} +0 -0
  142. /package/dist/{generate-Dmk8cfvX.d.mts → generate-BnsL9GBm.d.mts} +0 -0
  143. /package/dist/{init-BS4Daqnv.d.mts → init-BPEu8cs-.d.mts} +0 -0
  144. /package/dist/{interface-DL7LlWS5.d.mts → interface-C1_WLTMK.d.mts} +0 -0
  145. /package/dist/{interface-CsrGR8LF.d.mts → interface-Uk8ONJv7.d.mts} +0 -0
  146. /package/dist/{keys-B3yyBEyH.mjs → keys-Cd316u_y.mjs} +0 -0
  147. /package/dist/{multipartBody-709YcXi1.mjs → multipartBody-CHEQSZ6G.mjs} +0 -0
  148. /package/dist/{pluralize-BxdUl2P4.mjs → pluralize-DccFV9R7.mjs} +0 -0
  149. /package/dist/{preloadResources-B-j-iqo-.mjs → preloadResources-CHw3_oA_.mjs} +0 -0
  150. /package/dist/{pushref-registry-Dqclzjy7.d.mts → pushref-registry-DnCqL3aa.d.mts} +0 -0
  151. /package/dist/{requestContext-DDLS3NVU.mjs → requestContext-BZKOr13H.mjs} +0 -0
  152. /package/dist/{chunk-BpYLSNr0.mjs → rolldown-runtime-8H4AJuhK.mjs} +0 -0
  153. /package/dist/{schemaConverter-DatAaVNj.mjs → schemaConverter-U0MnO9vd.mjs} +0 -0
  154. /package/dist/{sessionManager-CyHpYXoB.d.mts → sessionManager-BXG-HGwG.d.mts} +0 -0
  155. /package/dist/{storage-C5q8fuEt.d.mts → storage-Co_PQpeQ.d.mts} +0 -0
  156. /package/dist/{store-helpers-DjSYI2uP.mjs → store-helpers-DY6xq4sM.mjs} +0 -0
  157. /package/dist/{tracing-Bnb1-YVd.d.mts → tracing-D8jlGXP7.d.mts} +0 -0
  158. /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-C9KXUly6.mjs} +0 -0
  159. /package/dist/{types-BJwJN2Pg.d.mts → types-LNebCoJx.d.mts} +0 -0
  160. /package/dist/{types-D57iXYb8.mjs → types-XTApa8b6.mjs} +0 -0
@@ -1,58 +1,18 @@
1
- import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-TrJVIJl0.mjs";
1
+ import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-gaEeMlz-.mjs";
2
2
  import { arcLog } from "./logger/index.mjs";
3
- import { r as ForbiddenError } from "./errors-j4aJm1Wg.mjs";
4
- import { b as isElevated, n as PUBLIC_SCOPE, x as isMember } from "./types-Bi0r0vjG.mjs";
5
- import { N as assertValidConfig, f as getDefaultCrudSchemas, i as pipeUIMessageStreamToReply, r as isReadableStream } from "./utils-xAQeDyU_.mjs";
6
- import { d as DEFAULT_FIELD_WRITE_DENIAL_POLICY, t as BaseController } from "./BaseController-B-uXzf6I.mjs";
7
- import { a as resolveRoutePreHandlers, c as buildArcDecorator, i as buildPreHandlerChain, n as resolveRouterPluginMw, o as buildAuthMiddleware, r as selectPluginMw, t as buildRateLimitConfig } from "./rateLimit-yKvxWAky.mjs";
8
- import { F as applyFieldWritePermissions, L as resolveEffectiveRoles } from "./permissions-3uzwkbQt.mjs";
9
- import { t as getUserRoles } from "./types-D57iXYb8.mjs";
10
- import { r as buildCrudPermissionMw, t as tryRegisterRoute } from "./registerRoute-BEYn0ylk.mjs";
11
- import { t as applyPresets } from "./presets-CF79RHEo.mjs";
12
- import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-DatAaVNj.mjs";
13
- import { t as hasEvents } from "./typeGuards-CcFZXgU7.mjs";
14
- import { a as createFastifyHandler, i as createCrudHandlers, n as buildPipelineHandler, o as createRequestContext, r as resolvePipelineSteps, u as buildRequestScopeProjection } from "./pipeline-D4sqiokR.mjs";
15
- import { t as pluralize } from "./pluralize-BxdUl2P4.mjs";
16
- import { t as resolveActionPermission } from "./actionPermissions-CX9QIMTd.mjs";
17
- //#region src/core/middlewares/fieldWrite.ts
18
- /**
19
- * Build a preHandler that enforces field-write permissions on the request body.
20
- *
21
- * Auto-CRUD's create/update routes get this for free via `BodySanitizer.sanitize()`
22
- * inside `BaseController`. Custom routes (`config.routes`, presets, action endpoints)
23
- * never went through that path — so a host that declared
24
- * `fields: { role: fields.writableBy(['admin']) }` and a custom
25
- * `POST /users/promote` happily accepted `{ role: 'admin' }` from any caller.
26
- *
27
- * This preHandler closes that gap. It applies ONLY the `applyFieldWritePermissions`
28
- * step — no `SYSTEM_FIELDS` strip, no `fieldRules` strip — because custom-route
29
- * bodies don't necessarily mirror the resource's adapter shape, and we'd
30
- * generate surprising 403s by enforcing rules tied to that shape.
31
- *
32
- * Returns `null` when the resource has no field permissions configured — the
33
- * caller should then skip wiring it into the preHandler chain.
34
- */
35
- function buildFieldWritePreHandler(fieldPermissions, policy = DEFAULT_FIELD_WRITE_DENIAL_POLICY) {
36
- if (!fieldPermissions || Object.keys(fieldPermissions).length === 0) return null;
37
- return async (request, _reply) => {
38
- const body = request.body;
39
- if (!body || typeof body !== "object" || Array.isArray(body)) return;
40
- const scope = request.scope ?? PUBLIC_SCOPE;
41
- if (isElevated(scope)) return;
42
- const { body: filtered, deniedFields } = applyFieldWritePermissions(body, fieldPermissions, resolveEffectiveRoles(getUserRoles(request.user), isMember(scope) ? scope.orgRoles : []));
43
- if (deniedFields.length > 0 && policy === "reject") throw new ForbiddenError(`Not permitted to write field${deniedFields.length === 1 ? "" : "s"}: ${deniedFields.join(", ")}`);
44
- request.body = filtered;
45
- };
46
- }
47
- /**
48
- * True for HTTP methods that carry a request body and should run the
49
- * field-write preHandler. GET/DELETE/HEAD/OPTIONS skip enforcement.
50
- */
51
- function methodCarriesBody(method) {
52
- const m = method.toUpperCase();
53
- return m === "POST" || m === "PUT" || m === "PATCH";
54
- }
55
- //#endregion
3
+ import { r as ForbiddenError } from "./errors-DkO8Nk6-.mjs";
4
+ import { b as isElevated, n as PUBLIC_SCOPE, x as isMember } from "./types-bHblZ9YN.mjs";
5
+ import { p as assertValidConfig, x as getDefaultCrudSchemas } from "./utils-C6QQ9-hd.mjs";
6
+ import { d as DEFAULT_FIELD_WRITE_DENIAL_POLICY, t as BaseController } from "./BaseController-Dy6EQHjM.mjs";
7
+ import { F as applyFieldWritePermissions, L as resolveEffectiveRoles } from "./permissions-BPB1xqGf.mjs";
8
+ import { t as getUserRoles } from "./types-XTApa8b6.mjs";
9
+ import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-U0MnO9vd.mjs";
10
+ import { i as pipeUIMessageStreamToReply, r as isReadableStream } from "./streaming-BQcW6_Kq.mjs";
11
+ import { a as createCrudHandlers, d as buildRequestScopeProjection, i as resolvePipelineSteps, o as createFastifyHandler, r as buildPipelineHandler, s as createRequestContext, t as resolveActionPermission } from "./actionPermissions-D7NGwcAR.mjs";
12
+ import { a as selectPluginMw, c as buildPreHandlerChain, f as buildArcDecorator, i as resolveRouterPluginMw, l as resolveRoutePreHandlers, n as buildRateLimitConfig, r as buildRouteConfig, s as buildCrudPermissionMw, t as tryRegisterRoute, u as buildAuthMiddleware } from "./registerRoute-BdV8DDwU.mjs";
13
+ import { t as applyPresets } from "./presets-BtyFwTO4.mjs";
14
+ import { t as hasEvents } from "./typeGuards-C9KXUly6.mjs";
15
+ import { t as pluralize } from "./pluralize-DccFV9R7.mjs";
56
16
  //#region src/core/aggregation/defineAggregation.ts
57
17
  /**
58
18
  * Declare a single resource aggregation. Exported configs flow into
@@ -99,13 +59,52 @@ function defineAggregation(config) {
99
59
  return config;
100
60
  }
101
61
  //#endregion
62
+ //#region src/core/middlewares/fieldWrite.ts
63
+ /**
64
+ * Build a preHandler that enforces field-write permissions on the request body.
65
+ *
66
+ * Auto-CRUD's create/update routes get this for free via `BodySanitizer.sanitize()`
67
+ * inside `BaseController`. Custom routes (`config.routes`, presets, action endpoints)
68
+ * never went through that path — so a host that declared
69
+ * `fields: { role: fields.writableBy(['admin']) }` and a custom
70
+ * `POST /users/promote` happily accepted `{ role: 'admin' }` from any caller.
71
+ *
72
+ * This preHandler closes that gap. It applies ONLY the `applyFieldWritePermissions`
73
+ * step — no `SYSTEM_FIELDS` strip, no `fieldRules` strip — because custom-route
74
+ * bodies don't necessarily mirror the resource's adapter shape, and we'd
75
+ * generate surprising 403s by enforcing rules tied to that shape.
76
+ *
77
+ * Returns `null` when the resource has no field permissions configured — the
78
+ * caller should then skip wiring it into the preHandler chain.
79
+ */
80
+ function buildFieldWritePreHandler(fieldPermissions, policy = DEFAULT_FIELD_WRITE_DENIAL_POLICY) {
81
+ if (!fieldPermissions || Object.keys(fieldPermissions).length === 0) return null;
82
+ return async (request, _reply) => {
83
+ const body = request.body;
84
+ if (!body || typeof body !== "object" || Array.isArray(body)) return;
85
+ const scope = request.scope ?? PUBLIC_SCOPE;
86
+ if (isElevated(scope)) return;
87
+ const { body: filtered, deniedFields } = applyFieldWritePermissions(body, fieldPermissions, resolveEffectiveRoles(getUserRoles(request.user), isMember(scope) ? scope.orgRoles : []));
88
+ if (deniedFields.length > 0 && policy === "reject") throw new ForbiddenError(`Not permitted to write field${deniedFields.length === 1 ? "" : "s"}: ${deniedFields.join(", ")}`);
89
+ request.body = filtered;
90
+ };
91
+ }
92
+ /**
93
+ * True for HTTP methods that carry a request body and should run the
94
+ * field-write preHandler. GET/DELETE/HEAD/OPTIONS skip enforcement.
95
+ */
96
+ function methodCarriesBody(method) {
97
+ const m = method.toUpperCase();
98
+ return m === "POST" || m === "PUT" || m === "PATCH";
99
+ }
100
+ //#endregion
102
101
  //#region src/core/createCrudRouter.ts
103
102
  /**
104
103
  * Mount custom routes (from presets or user-defined `routes`) on Fastify.
105
104
  * `wrapHandler` is derived inline from `!route.raw`.
106
105
  */
107
106
  function createCustomRoutes(fastify, routes, controller, options) {
108
- const { tag, resourceName, arcDecorator, rateLimitConfig, pluginMw, pipeline, routeGuards, tenantScopeMw, fieldPermissions, onFieldWriteDenied } = options;
107
+ const { tag, resourceName, arcDecorator, rateLimitConfig, pluginMw, pipeline, routeGuards, tenantScopeMw, fieldPermissions, onFieldWriteDenied, extensions } = options;
109
108
  for (const route of routes) {
110
109
  const routeWithRefs = route;
111
110
  const hasHandler = route.handler !== void 0;
@@ -178,7 +177,7 @@ function createCustomRoutes(fastify, routes, controller, options) {
178
177
  }
179
178
  return result;
180
179
  } : handler,
181
- ...rateLimitConfig ? { config: rateLimitConfig } : {}
180
+ ...buildRouteConfig(rateLimitConfig, extensions)
182
181
  }, {
183
182
  resourceName,
184
183
  op: opName
@@ -195,7 +194,7 @@ function createCustomRoutes(fastify, routes, controller, options) {
195
194
  * @param options - Router configuration
196
195
  */
197
196
  function createCrudRouter(fastify, controller, options = {}) {
198
- const { tag = "Resource", schemas = {}, permissions = {}, middlewares = {}, routeGuards = [], routes: customRoutes = [], disableDefaultRoutes = false, disabledRoutes = [], resourceName = "unknown", schemaOptions, rateLimit, pipe: pipeline, fields: fieldPermissions, onFieldWriteDenied, updateMethod = DEFAULT_UPDATE_METHOD, idField } = options;
197
+ const { tag = "Resource", schemas = {}, permissions = {}, middlewares = {}, routeGuards = [], routes: customRoutes = [], disableDefaultRoutes = false, disabledRoutes = [], resourceName = "unknown", schemaOptions, rateLimit, pipe: pipeline, fields: fieldPermissions, onFieldWriteDenied, updateMethod = DEFAULT_UPDATE_METHOD, idField, extensions } = options;
199
198
  const rateLimitConfig = buildRateLimitConfig(rateLimit);
200
199
  const resourceHasQueryCache = fastify.hasDecorator("queryCache") && controller && typeof controller._cacheConfig !== "undefined" && controller._cacheConfig !== void 0;
201
200
  const pluginMw = resolveRouterPluginMw(fastify, Boolean(resourceHasQueryCache));
@@ -296,7 +295,7 @@ function createCrudRouter(fastify, controller, options = {}) {
296
295
  }, defaultSchemas[spec.op], schemas[spec.op]),
297
296
  preHandler: preHandler.length > 0 ? preHandler : void 0,
298
297
  handler: handlers[spec.op],
299
- ...rateLimitConfig ? { config: rateLimitConfig } : {}
298
+ ...buildRouteConfig(rateLimitConfig, extensions)
300
299
  }, {
301
300
  resourceName,
302
301
  op: spec.op
@@ -314,7 +313,8 @@ function createCrudRouter(fastify, controller, options = {}) {
314
313
  routeGuards,
315
314
  tenantScopeMw,
316
315
  fieldPermissions,
317
- onFieldWriteDenied
316
+ onFieldWriteDenied,
317
+ extensions
318
318
  });
319
319
  }
320
320
  function buildCrudHandlers(ctrl, pipeline, resourceName) {
@@ -1243,10 +1243,11 @@ function buildResourcePlugin(resource) {
1243
1243
  pipe: resource.pipe,
1244
1244
  fields: resource.fields,
1245
1245
  onFieldWriteDenied: resource.onFieldWriteDenied,
1246
- idField: resource.idField
1246
+ idField: resource.idField,
1247
+ extensions: resource.extensions
1247
1248
  });
1248
1249
  if (resource.actions && Object.keys(resource.actions).length > 0) {
1249
- const { createActionRouter } = await import("./createActionRouter-DGCCp-kP.mjs").then((n) => n.n);
1250
+ const { createActionRouter } = await import("./createActionRouter-B_WzBFyf.mjs").then((n) => n.n);
1250
1251
  createActionRouter(typedInstance, {
1251
1252
  ...normalizeActionsToRouterConfig(resource.actions, resource.actionPermissions, resource.tag, resource.permissions, resource.name, typedInstance.log),
1252
1253
  resourceName: resource.name,
@@ -1256,11 +1257,12 @@ function buildResourcePlugin(resource) {
1256
1257
  permissions: resource.permissions,
1257
1258
  routeGuards: resource.routeGuards,
1258
1259
  pipeline: resource.pipe,
1259
- rateLimit: resource.rateLimit
1260
+ rateLimit: resource.rateLimit,
1261
+ extensions: resource.extensions
1260
1262
  });
1261
1263
  }
1262
1264
  if (resource.aggregations && Object.keys(resource.aggregations).length > 0) {
1263
- const [{ createAggregationRouter }, { adapterSupportsAggregate, ArcAggregationConfigError }] = await Promise.all([import("./createAggregationRouter-Ca77b8iK.mjs"), import("./validate-0__MuUsK.mjs").then((n) => n.r)]);
1265
+ const [{ createAggregationRouter }, { adapterSupportsAggregate, ArcAggregationConfigError }] = await Promise.all([import("./createAggregationRouter-B_G_nIjm.mjs"), import("./validate-CRcaWzp2.mjs").then((n) => n.r)]);
1264
1266
  const repoForAgg = resource.controller?.repository ?? resource.repository;
1265
1267
  if (!adapterSupportsAggregate(repoForAgg)) {
1266
1268
  const undispatchable = Object.entries(resource.aggregations).filter(([, cfg]) => !cfg.materialized).map(([name]) => name);
@@ -1333,6 +1335,7 @@ var ResourceDefinition = class {
1333
1335
  */
1334
1336
  onFieldWriteDenied;
1335
1337
  cache;
1338
+ extensions;
1336
1339
  referenceData;
1337
1340
  /**
1338
1341
  * Per-resource MCP opt-out. `false` keeps the resource out of every
@@ -1430,6 +1433,7 @@ var ResourceDefinition = class {
1430
1433
  this.fields = config.fields;
1431
1434
  this.onFieldWriteDenied = config.onFieldWriteDenied;
1432
1435
  this.cache = config.cache;
1436
+ this.extensions = config.extensions ? Object.freeze({ ...config.extensions }) : void 0;
1433
1437
  this.mcp = config.mcp !== false;
1434
1438
  this.referenceData = config.referenceData === true;
1435
1439
  this.tenantField = config.tenantField;
@@ -1,10 +1,9 @@
1
- import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import "./constants-TrJVIJl0.mjs";
3
- import { f as createError } from "./errors-j4aJm1Wg.mjs";
4
- import { c as buildArcDecorator, i as buildPreHandlerChain, n as resolveRouterPluginMw, r as selectPluginMw, s as buildAuthMiddlewareForPermissions, t as buildRateLimitConfig } from "./rateLimit-yKvxWAky.mjs";
5
- import { n as buildActionPermissionMw, t as tryRegisterRoute } from "./registerRoute-BEYn0ylk.mjs";
6
- import { l as sendControllerResponse, r as resolvePipelineSteps, t as buildActionPipelineHandler } from "./pipeline-D4sqiokR.mjs";
7
- import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-CE6USIRi.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
+ import "./constants-gaEeMlz-.mjs";
3
+ import { f as createError } from "./errors-DkO8Nk6-.mjs";
4
+ import { i as resolvePipelineSteps, n as buildActionPipelineHandler, u as sendControllerResponse } from "./actionPermissions-D7NGwcAR.mjs";
5
+ import { a as selectPluginMw, c as buildPreHandlerChain, d as buildAuthMiddlewareForPermissions, f as buildArcDecorator, i as resolveRouterPluginMw, n as buildRateLimitConfig, o as buildActionPermissionMw, r as buildRouteConfig, t as tryRegisterRoute } from "./registerRoute-BdV8DDwU.mjs";
6
+ import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-D1MJwqmp.mjs";
8
7
  //#region src/core/createActionRouter.ts
9
8
  var createActionRouter_exports = /* @__PURE__ */ __exportAll({
10
9
  buildActionBodySchema: () => buildActionBodySchema,
@@ -19,7 +18,7 @@ var createActionRouter_exports = /* @__PURE__ */ __exportAll({
19
18
  * (keyed by `body.action` at request time).
20
19
  */
21
20
  function createActionRouter(fastify, config) {
22
- const { tag, resourceName = tag ?? "action", actions, actionPermissions = {}, actionSchemas = {}, globalAuth, onError, fields: fieldPermissions, schemaOptions, idField = "_id", permissions: resourcePermissions, routeGuards = [], pipeline, rateLimit, idLessActionNames = [] } = config;
21
+ const { tag, resourceName = tag ?? "action", actions, actionPermissions = {}, actionSchemas = {}, globalAuth, onError, fields: fieldPermissions, schemaOptions, idField = "_id", permissions: resourcePermissions, routeGuards = [], pipeline, rateLimit, idLessActionNames = [], extensions } = config;
23
22
  const allActionNames = Object.keys(actions);
24
23
  if (allActionNames.length === 0) {
25
24
  fastify.log.warn("[createActionRouter] No actions defined, skipping route creation");
@@ -122,7 +121,7 @@ function createActionRouter(fastify, config) {
122
121
  schema: routeSchema,
123
122
  attachValidation: true,
124
123
  preHandler: preHandler.length > 0 ? preHandler : void 0,
125
- ...rateLimitConfig ? { config: rateLimitConfig } : {},
124
+ ...buildRouteConfig(rateLimitConfig, extensions),
126
125
  handler: async (req, reply) => {
127
126
  const { action, ...data } = req.body;
128
127
  const id = hasId ? req.params.id : "";
@@ -136,7 +135,8 @@ function createActionRouter(fastify, config) {
136
135
  const handler = wrappedHandlers.get(action);
137
136
  if (!handler) throw createError(400, `Invalid action '${action}'. Valid actions: ${actionSubset.join(", ")}`, { validActions: actionSubset });
138
137
  try {
139
- return sendControllerResponse(reply, await handler(id, data, req), req);
138
+ sendControllerResponse(reply, await handler(id, data, req), req);
139
+ return reply;
140
140
  } catch (error) {
141
141
  if (onError) {
142
142
  const { statusCode, error: errorMsg, code } = onError(error, action, id);
@@ -1,7 +1,7 @@
1
- import { f as createError, l as UnauthorizedError, r as ForbiddenError } from "./errors-j4aJm1Wg.mjs";
2
- import { c as buildArcDecorator, i as buildPreHandlerChain, n as resolveRouterPluginMw, o as buildAuthMiddleware, r as selectPluginMw, t as buildRateLimitConfig } from "./rateLimit-yKvxWAky.mjs";
3
- import { n as validateAggregations } from "./validate-0__MuUsK.mjs";
4
- import { t as buildAggregationHandler } from "./buildHandler-Cvrc2p7r.mjs";
1
+ import { f as createError, l as UnauthorizedError, r as ForbiddenError } from "./errors-DkO8Nk6-.mjs";
2
+ import { a as selectPluginMw, c as buildPreHandlerChain, f as buildArcDecorator, i as resolveRouterPluginMw, n as buildRateLimitConfig, u as buildAuthMiddleware } from "./registerRoute-BdV8DDwU.mjs";
3
+ import { n as validateAggregations } from "./validate-CRcaWzp2.mjs";
4
+ import { t as buildAggregationHandler } from "./buildHandler-B0qvAguf.mjs";
5
5
  //#region src/core/aggregation/createAggregationRouter.ts
6
6
  /**
7
7
  * Register one Fastify route per aggregation. No-op when the map is
@@ -1,6 +1,6 @@
1
- import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
2
  import { arcLog } from "./logger/index.mjs";
3
- import { n as PUBLIC_SCOPE } from "./types-Bi0r0vjG.mjs";
3
+ import { n as PUBLIC_SCOPE } from "./types-bHblZ9YN.mjs";
4
4
  import Fastify from "fastify";
5
5
  import qs from "qs";
6
6
  //#region src/factory/presets.ts
@@ -74,7 +74,7 @@ const productionPreset = {
74
74
  */
75
75
  function devLoggerConfig() {
76
76
  try {
77
- if (typeof import.meta?.resolve === "function") {
77
+ if (typeof import.meta.resolve === "function") {
78
78
  import.meta.resolve("pino-pretty");
79
79
  return {
80
80
  level: "debug",
@@ -202,7 +202,7 @@ async function registerArcCore(fastify, config, trackPlugin) {
202
202
  await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
203
203
  trackPlugin("arc-core");
204
204
  if (config.arcPlugins?.events !== false) {
205
- const { default: eventPlugin } = await import("./eventPlugin-Bw4bUDEO.mjs").then((n) => n.n);
205
+ const { default: eventPlugin } = await import("./eventPlugin-DJG2D7sy.mjs").then((n) => n.n);
206
206
  const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
207
207
  await fastify.register(eventPlugin, {
208
208
  ...eventOpts,
@@ -239,15 +239,15 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
239
239
  trackPlugin("arc-graceful-shutdown");
240
240
  }
241
241
  if (config.arcPlugins?.caching) {
242
- const { default: cachingPlugin } = await import("./caching-C0DdKiYB.mjs").then((n) => n.r);
242
+ const { default: cachingPlugin } = await import("./caching-_NJeDd1_.mjs").then((n) => n.r);
243
243
  const opts = config.arcPlugins.caching === true ? {} : config.arcPlugins.caching;
244
244
  await fastify.register(cachingPlugin, opts);
245
245
  trackPlugin("arc-caching", opts);
246
246
  }
247
247
  if (config.arcPlugins?.queryCache) {
248
- const { queryCachePlugin } = await import("./queryCachePlugin-CnAeFqju.mjs").then((n) => n.n);
248
+ const { queryCachePlugin } = await import("./queryCachePlugin-emHb0CuX.mjs").then((n) => n.n);
249
249
  const opts = config.arcPlugins.queryCache === true ? {} : config.arcPlugins.queryCache;
250
- const store = config.stores?.queryCache ?? new (await (import("./memory-TdTHCS9-.mjs").then((n) => n.n))).MemoryCacheStore();
250
+ const store = config.stores?.queryCache ?? new (await (import("./memory-CLXA2VeJ.mjs").then((n) => n.n))).MemoryCacheStore();
251
251
  await fastify.register(queryCachePlugin, {
252
252
  store,
253
253
  ...opts
@@ -256,19 +256,19 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules) {
256
256
  }
257
257
  if (config.arcPlugins?.sse) if (config.arcPlugins?.events === false) fastify.log.warn("SSE plugin requires events plugin (arcPlugins.events). SSE disabled.");
258
258
  else {
259
- const { default: ssePlugin } = await import("./sse-BrY1W6U8.mjs").then((n) => n.r);
259
+ const { default: ssePlugin } = await import("./sse-CfUjwjDk.mjs").then((n) => n.r);
260
260
  const opts = config.arcPlugins.sse === true ? {} : config.arcPlugins.sse;
261
261
  await fastify.register(ssePlugin, opts);
262
262
  trackPlugin("arc-sse", opts);
263
263
  }
264
264
  if (config.arcPlugins?.metrics) {
265
- const { default: metricsPlugin } = await import("./metrics-Da2EUPi9.mjs").then((n) => n.r);
265
+ const { default: metricsPlugin } = await import("./metrics-fdxI5mn_.mjs").then((n) => n.r);
266
266
  const opts = config.arcPlugins.metrics === true ? {} : config.arcPlugins.metrics;
267
267
  await fastify.register(metricsPlugin, opts);
268
268
  trackPlugin("arc-metrics", opts);
269
269
  }
270
270
  if (config.arcPlugins?.versioning) {
271
- const { default: versioningPlugin } = await import("./versioning-C0K8KQLx.mjs").then((n) => n.r);
271
+ const { default: versioningPlugin } = await import("./versioning-Dk-71iIz.mjs").then((n) => n.r);
272
272
  await fastify.register(versioningPlugin, config.arcPlugins.versioning);
273
273
  trackPlugin("arc-versioning", config.arcPlugins.versioning);
274
274
  }
@@ -337,7 +337,7 @@ async function registerAuth(fastify, config, trackPlugin) {
337
337
  */
338
338
  async function registerElevation(fastify, config, trackPlugin) {
339
339
  if (!config.elevation) return;
340
- const { elevationPlugin } = await import("./elevation-BzhyVWfn.mjs").then((n) => n.r);
340
+ const { elevationPlugin } = await import("./elevation-COpYKt2h.mjs").then((n) => n.r);
341
341
  await fastify.register(elevationPlugin, config.elevation);
342
342
  trackPlugin("arc-elevation", config.elevation);
343
343
  fastify.log.debug("Elevation plugin enabled");
@@ -347,7 +347,7 @@ async function registerElevation(fastify, config, trackPlugin) {
347
347
  */
348
348
  async function registerErrorHandler(fastify, config, trackPlugin) {
349
349
  if (config.errorHandler === false) return;
350
- const { errorHandlerPlugin } = await import("./errorHandler-Bua97SEt.mjs").then((n) => n.r);
350
+ const { errorHandlerPlugin } = await import("./errorHandler-Dxf6P_kV.mjs").then((n) => n.r);
351
351
  const errorOpts = typeof config.errorHandler === "object" ? config.errorHandler : { includeStack: config.preset !== "production" };
352
352
  await fastify.register(errorHandlerPlugin, errorOpts);
353
353
  trackPlugin("arc-error-handler", errorOpts);
@@ -448,7 +448,7 @@ async function registerResources(fastify, config) {
448
448
  let discoveryPath;
449
449
  let discoveryYieldedZero = false;
450
450
  if (resolvedResources === void 0 && config.resourceDir) {
451
- const { loadResources } = await import("./loadResources-CwXt9MCh.mjs").then((n) => n.n);
451
+ const { loadResources } = await import("./loadResources-B8UmpITt.mjs").then((n) => n.n);
452
452
  const { resolve, dirname } = await import("node:path");
453
453
  const { fileURLToPath } = await import("node:url");
454
454
  const rawDir = config.resourceDir;
@@ -896,7 +896,7 @@ async function createApp(options) {
896
896
  await registerErrorHandler(fastify, config, trackPlugin);
897
897
  await registerResources(fastify, config);
898
898
  if (config.replyHelpers) {
899
- const { replyHelpersPlugin } = await import("./replyHelpers-DXHVxrrj.mjs").then((n) => n.n);
899
+ const { replyHelpersPlugin } = await import("./replyHelpers-67tRawQZ.mjs").then((n) => n.n);
900
900
  await fastify.register(replyHelpersPlugin);
901
901
  }
902
902
  if (config.serializeBigInt) {
@@ -1,5 +1,5 @@
1
- import { p as RegistryEntry } from "../index-QYma8mdC.mjs";
2
- import { t as ExternalOpenApiPaths } from "../externalPaths-DLle0R07.mjs";
1
+ import { p as RegistryEntry } from "../index-CUTG2Cuo.mjs";
2
+ import { t as ExternalOpenApiPaths } from "../externalPaths-BE6wFpYv.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
5
5
  //#region src/docs/openapi/types.d.ts
@@ -1,5 +1,5 @@
1
- import { t as getUserRoles } from "../types-D57iXYb8.mjs";
2
- import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-ButnRyOM.mjs";
1
+ import { t as getUserRoles } from "../types-XTApa8b6.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-DZ-5a5Op.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/docs/scalar.ts
5
5
  const scalarPlugin = async (fastify, opts = {}) => {
@@ -1,6 +1,6 @@
1
- import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
2
  import { arcLog } from "./logger/index.mjs";
3
- import { t as getUserRoles } from "./types-D57iXYb8.mjs";
3
+ import { t as getUserRoles } from "./types-XTApa8b6.mjs";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/scope/elevation.ts
6
6
  var elevation_exports = /* @__PURE__ */ __exportAll({
@@ -0,0 +1,222 @@
1
+ import { FastifyPluginAsync, FastifyRequest } from "fastify";
2
+
3
+ //#region src/encryption/types.d.ts
4
+ /** JWE key-management algorithm — the protected-header `alg`. */
5
+ type JweAlg = "RSA-OAEP-256" | "RSA-OAEP-384" | "RSA-OAEP-512" | "ECDH-ES" | "ECDH-ES+A256KW" | "A256GCMKW" | "dir";
6
+ /** JWE content-encryption algorithm — the protected-header `enc`. AEAD only. */
7
+ type JweEnc = "A128GCM" | "A192GCM" | "A256GCM";
8
+ /** Encryption strategy for a route's response. */
9
+ type EncryptionMode = "jwe" | "fields";
10
+ /**
11
+ * Opaque key material. For `jwe` mode this is a `jose` `KeyLike` /
12
+ * `CryptoKey` / `Uint8Array`; for `fields` mode a 32-byte secret
13
+ * (`Buffer` / `Uint8Array`). Kept `unknown` at the contract layer so the
14
+ * `KeyProvider` interface carries no hard `jose` type dependency — hosts
15
+ * without `jose` installed can still implement field-mode providers.
16
+ */
17
+ type KeyMaterial = unknown;
18
+ /** A key paired with its identifier — what encrypt operations consume. */
19
+ interface ActiveKey {
20
+ /** Key id, stamped into the JWE `kid` header / field envelope for rotation. */
21
+ readonly kid: string;
22
+ /** The key material (public key for JWE, 32-byte secret for fields). */
23
+ readonly key: KeyMaterial;
24
+ /** Optional per-key `alg` override (else the plugin default applies). */
25
+ readonly alg?: JweAlg;
26
+ }
27
+ /** Context handed to the key provider so resolution can be request-scoped. */
28
+ interface KeyContext {
29
+ readonly request: FastifyRequest;
30
+ }
31
+ type MaybePromise<T> = T | Promise<T>;
32
+ /**
33
+ * Pluggable key resolution. Arc owns the request-time plumbing — `kid` →
34
+ * key, algorithm allowlist enforcement; the host owns the trust store —
35
+ * which keys are valid and where they live (file / env / AWS KMS / GCP KMS
36
+ * / Vault). Encryption and decryption are deliberately separate operations
37
+ * because they often use DIFFERENT keys (JWE: a public key encrypts the
38
+ * outbound response, a private key by `kid` decrypts the inbound request —
39
+ * exactly Visa's two-key-pair model).
40
+ *
41
+ * Rotation mirrors `@fastify/secure-session`: the active key encrypts; any
42
+ * key resolvable by `kid` can decrypt, so you can roll keys without dropping
43
+ * in-flight traffic.
44
+ */
45
+ interface KeyProvider {
46
+ /** Active key for encrypting OUTBOUND responses. */
47
+ encryptionKey(ctx: KeyContext): MaybePromise<ActiveKey>;
48
+ /** Resolve a DECRYPTION key by the inbound message's `kid`. */
49
+ decryptionKey(kid: string, ctx: KeyContext): MaybePromise<KeyMaterial | undefined>;
50
+ }
51
+ /**
52
+ * Per-resource encryption directive — the typed slice plugins register on
53
+ * `ResourceExtensions`. Declared on a resource via
54
+ * `defineResource({ extensions: { encryption: { … } } })`; arc threads it
55
+ * to request time on `request.routeOptions.config.arcExtensions.encryption`.
56
+ */
57
+ interface EncryptionDirective {
58
+ /** Strategy. Defaults to the plugin-level `mode` (default `'jwe'`). */
59
+ readonly mode?: EncryptionMode;
60
+ /**
61
+ * For `mode: 'fields'` — dot-paths of fields to encrypt, resolved against
62
+ * the serialized response body (e.g. `'cardNumber'`, `'account.iban'`).
63
+ * Ignored for `mode: 'jwe'` (the whole body is encrypted).
64
+ */
65
+ readonly fields?: readonly string[];
66
+ /**
67
+ * Explicit opt-out — set `false` to disable encryption for this resource
68
+ * even when a plugin-level default (`routes`) would otherwise apply.
69
+ */
70
+ readonly enabled?: boolean;
71
+ }
72
+ /**
73
+ * Plugin-level options. `keyProvider` is the only required field; everything
74
+ * else has a secure default.
75
+ */
76
+ interface EncryptionOptions {
77
+ /**
78
+ * Master on/off. Default `true`. Set `false` to register the plugin as a
79
+ * typed no-op (config-gated environments) — same pattern as
80
+ * `idempotencyPlugin`.
81
+ */
82
+ readonly enabled?: boolean;
83
+ /** Key provider — resolves `kid` → key for encrypt/decrypt. Required. */
84
+ readonly keyProvider: KeyProvider;
85
+ /** Default mode when a route opts in without naming one. Default `'jwe'`. */
86
+ readonly mode?: EncryptionMode;
87
+ /** JWE key-management algorithm. Default `'RSA-OAEP-256'`. */
88
+ readonly alg?: JweAlg;
89
+ /** JWE content-encryption algorithm. Default `'A256GCM'`. */
90
+ readonly enc?: JweEnc;
91
+ /**
92
+ * Inbound-decryption algorithm allowlists — block algorithm-substitution
93
+ * downgrade attacks. Default `[alg]` / `[enc]`. Set explicitly only when a
94
+ * client legitimately uses more than one algorithm.
95
+ */
96
+ readonly allowedAlgs?: readonly JweAlg[];
97
+ readonly allowedEncs?: readonly JweEnc[];
98
+ /**
99
+ * Fallback opt-in for routes that don't declare
100
+ * `extensions.encryption`. Return a directive (or `true` for the default
101
+ * mode) to encrypt, `false`/`undefined` to skip. The per-resource
102
+ * `extensions.encryption` ALWAYS wins when present. Use this to scope ALE
103
+ * to sensitive prefixes without touching every resource.
104
+ *
105
+ * @example confine ALE to `/payments` and `/accounts`
106
+ * ```ts
107
+ * routes: (req) => /^\/(payments|accounts)\b/.test(req.url)
108
+ * ```
109
+ */
110
+ readonly routes?: (request: FastifyRequest) => EncryptionDirective | boolean | undefined;
111
+ /** Decrypt inbound `application/jose` request bodies. Default `true`. */
112
+ readonly decryptRequests?: boolean;
113
+ /** Media type that signals an encrypted REQUEST body. Default `'application/jose'`. */
114
+ readonly contentType?: string;
115
+ /** Content-Type set on full-body JWE RESPONSES. Default `'application/jose'`. */
116
+ readonly responseContentType?: string;
117
+ /**
118
+ * Response header advertising that the body is encrypted, so generic
119
+ * client SDKs know to decrypt. Default `'x-encrypted'`; set `false` to omit.
120
+ */
121
+ readonly flagHeader?: string | false;
122
+ }
123
+ /** Request decorations added by the plugin. */
124
+ declare module "fastify" {
125
+ interface FastifyRequest {
126
+ /** `true` when the inbound body was decrypted from a JWE by this plugin. */
127
+ requestDecrypted?: boolean;
128
+ /** `true` when the outbound body was encrypted by this plugin. */
129
+ responseEncrypted?: boolean;
130
+ }
131
+ }
132
+ //#endregion
133
+ //#region src/encryption/encryptionPlugin.d.ts
134
+ declare const encryptionPlugin: FastifyPluginAsync<EncryptionOptions>;
135
+ declare const _default: FastifyPluginAsync<EncryptionOptions>;
136
+ //#endregion
137
+ //#region src/encryption/fieldCipher.d.ts
138
+ /**
139
+ * Field-level cipher — `node:crypto` AES-256-GCM, zero external deps.
140
+ *
141
+ * Used by `mode: 'fields'` to encrypt individual field values in place while
142
+ * leaving the JSON structure intact. The envelope is an authenticated,
143
+ * versioned, `kid`-tagged token:
144
+ *
145
+ * arc.v1.<b64url(kid)>.<b64url(iv)>.<b64url(ciphertext)>.<b64url(tag)>
146
+ *
147
+ * This is NOT a hand-rolled cipher — it's the correct use of a vetted AEAD
148
+ * primitive (AES-256-GCM): a fresh random 96-bit IV per operation (never
149
+ * caller-supplied — IV reuse under GCM is catastrophic), authentication via
150
+ * the GCM tag, and a version prefix so the format can evolve. Same shape, in
151
+ * spirit, as MongoDB CSFLE's BSON-binary subtype-6 envelope. For
152
+ * cross-vendor interop or asymmetric keys use `mode: 'jwe'` (RFC 7516)
153
+ * instead.
154
+ */
155
+ /** Encrypt a UTF-8 string into the `arc.v1` field envelope. */
156
+ declare function encryptField(plaintext: string, kid: string, key: unknown): string;
157
+ /** Parsed envelope header — exposes `kid` for key resolution before decrypt. */
158
+ interface ParsedFieldEnvelope {
159
+ readonly kid: string;
160
+ readonly iv: Buffer;
161
+ readonly ciphertext: Buffer;
162
+ readonly tag: Buffer;
163
+ }
164
+ /** Returns the parsed envelope, or `null` when the token isn't `arc.v1`. */
165
+ declare function parseFieldEnvelope(token: string): ParsedFieldEnvelope | null;
166
+ /** Decrypt a parsed `arc.v1` envelope with the resolved key. Throws on tamper. */
167
+ declare function decryptField(envelope: ParsedFieldEnvelope, key: unknown): string;
168
+ //#endregion
169
+ //#region src/encryption/keyProvider.d.ts
170
+ interface StaticKeyProviderOptions {
171
+ /**
172
+ * Active key used to ENCRYPT outbound responses. For `jwe` mode this is the
173
+ * recipient's PUBLIC key; for `fields` mode a 32-byte symmetric secret.
174
+ */
175
+ readonly encryptionKey: ActiveKey;
176
+ /**
177
+ * Keys used to DECRYPT inbound requests, indexed by `kid`. For `jwe` mode
178
+ * these are PRIVATE keys; for `fields` mode the same symmetric secrets.
179
+ *
180
+ * Defaults to `{ [encryptionKey.kid]: encryptionKey.key }` — correct for
181
+ * symmetric field mode where one key both encrypts and decrypts. For
182
+ * asymmetric JWE you MUST pass the private key(s) here; the public
183
+ * `encryptionKey` cannot decrypt. Keep 2–3 entries during a rotation so
184
+ * in-flight messages signed with the previous `kid` still decrypt.
185
+ */
186
+ readonly decryptionKeys?: Readonly<Record<string, KeyMaterial>>;
187
+ }
188
+ /**
189
+ * A static, in-memory key provider with `kid`-based rotation. Keys are held
190
+ * for the process lifetime — resolve them from env / a secrets file at boot
191
+ * and pass the imported key material in (don't re-parse PEM per request).
192
+ *
193
+ * @example symmetric field mode
194
+ * ```ts
195
+ * const provider = createStaticKeyProvider({
196
+ * encryptionKey: { kid: "2026-06", key: Buffer.from(process.env.FIELD_KEY!, "base64") },
197
+ * });
198
+ * ```
199
+ *
200
+ * @example asymmetric JWE (jose key material)
201
+ * ```ts
202
+ * const provider = createStaticKeyProvider({
203
+ * encryptionKey: { kid: "client-1", key: await importSPKI(clientPubPem, "RSA-OAEP-256") },
204
+ * decryptionKeys: { "server-1": await importPKCS8(serverPrivPem, "RSA-OAEP-256") },
205
+ * });
206
+ * ```
207
+ */
208
+ declare function createStaticKeyProvider(options: StaticKeyProviderOptions): KeyProvider;
209
+ //#endregion
210
+ //#region src/encryption/index.d.ts
211
+ declare module "../types/resource.js" {
212
+ interface ResourceExtensions {
213
+ /**
214
+ * Encrypt this resource's responses — full-body JWE or specific fields.
215
+ * Threaded to request time on
216
+ * `request.routeOptions.config.arcExtensions.encryption`.
217
+ */
218
+ encryption?: EncryptionDirective;
219
+ }
220
+ }
221
+ //#endregion
222
+ export { type ActiveKey, type EncryptionDirective, type EncryptionMode, type EncryptionOptions, type JweAlg, type JweEnc, type KeyContext, type KeyMaterial, type KeyProvider, type ParsedFieldEnvelope, type StaticKeyProviderOptions, createStaticKeyProvider, decryptField, _default as default, encryptField, encryptionPlugin, parseFieldEnvelope };