@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
@@ -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,5 +1,5 @@
1
- import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { m as statusToArcCode, p as isArcError } from "./errors-j4aJm1Wg.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
2
+ import { m as statusToArcCode, p as isArcError } from "./errors-DkO8Nk6-.mjs";
3
3
  import { isHttpError, toErrorContract } from "@classytic/repo-core/errors";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/plugins/errorHandler.ts
@@ -1,9 +1,110 @@
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 { d as createDomainError } from "./errors-j4aJm1Wg.mjs";
4
- import { n as requestContext } from "./requestContext-DDLS3NVU.mjs";
5
- import { n as createEvent, t as MemoryEventTransport } from "./EventTransport-7LPXLEUX.mjs";
3
+ import { d as createDomainError } from "./errors-DkO8Nk6-.mjs";
4
+ import { n as requestContext } from "./requestContext-BZKOr13H.mjs";
6
5
  import fp from "fastify-plugin";
6
+ import { createEvent, matchEventPattern } from "@classytic/primitives/events";
7
+ //#region src/events/EventTransport.ts
8
+ /**
9
+ * Internal arc events module — concrete in-memory transport.
10
+ *
11
+ * **Public type contracts moved out (arc 2.12).** `EventMeta`, `DomainEvent`,
12
+ * `EventHandler`, `EventLogger`, `EventTransport`, `DeadLetteredEvent`,
13
+ * `PublishManyResult`, plus the `createEvent` / `createChildEvent` /
14
+ * `matchEventPattern` helpers, are now owned by `@classytic/primitives/events`.
15
+ * Two reasons:
16
+ *
17
+ * 1. The shapes ARE the cross-package contract. arc, arc-next, mongokit's
18
+ * audit-log plugin, future kits and downstream services all share them.
19
+ * Owning them in `primitives` (pure types, zero runtime, zero deps)
20
+ * eliminates the "manually mirror in two places" problem the previous
21
+ * `primitives/src/events.ts` header explicitly called out.
22
+ * 2. Inverting the dependency direction lets future packages (sqlitekit
23
+ * audit, billing services) consume the contract without depending on
24
+ * arc's HTTP-coupled stack.
25
+ *
26
+ * Hosts MUST import the types from primitives directly:
27
+ *
28
+ * ```typescript
29
+ * import type {
30
+ * EventMeta, DomainEvent, EventHandler, EventTransport,
31
+ * DeadLetteredEvent, PublishManyResult,
32
+ * } from '@classytic/primitives/events';
33
+ * import { createEvent, createChildEvent } from '@classytic/primitives/events';
34
+ * ```
35
+ *
36
+ * arc's `events/index.ts` barrel does NOT re-export these — by design, so
37
+ * that consumer code is forced into the canonical import path and the org's
38
+ * "no re-exports" rule holds at every public surface.
39
+ *
40
+ * What stays here:
41
+ * - `MemoryEventTransport` — the in-memory `EventTransport` implementation
42
+ * used as arc's default transport. Its handler-set state is process-local;
43
+ * wrong layer for primitives.
44
+ * - `MemoryEventTransportOptions` — its options shape.
45
+ *
46
+ * Inside arc, files that previously imported types from `./EventTransport.js`
47
+ * keep working: this file re-exports them from primitives for arc's own
48
+ * call sites. That re-export is an internal refactor convenience — the
49
+ * public `events/index.ts` barrel stays clean.
50
+ */
51
+ /**
52
+ * In-memory event transport (default).
53
+ *
54
+ * Events are delivered synchronously within the process. Not suitable for
55
+ * multi-instance deployments — pair with `RedisEventTransport` /
56
+ * `RedisStreamTransport` (subpath imports) for those.
57
+ */
58
+ var MemoryEventTransport = class {
59
+ name = "memory";
60
+ handlers = /* @__PURE__ */ new Map();
61
+ logger;
62
+ constructor(options) {
63
+ this.logger = options?.logger ?? console;
64
+ }
65
+ async publish(event) {
66
+ const allHandlers = /* @__PURE__ */ new Set();
67
+ for (const [pattern, handlers] of this.handlers) if (matchEventPattern(pattern, event.type)) for (const h of handlers) allHandlers.add(h);
68
+ for (const handler of allHandlers) try {
69
+ await handler(event);
70
+ } catch (err) {
71
+ this.logger.error(`[EventTransport] Handler error for ${event.type}:`, err);
72
+ }
73
+ }
74
+ /**
75
+ * Reference `publishMany` implementation — delegates to `publish()` in order.
76
+ *
77
+ * Production transports (Kafka, Redis pipeline, SQS batch) should override
78
+ * this with a single batched network call. Memory transport has nothing to
79
+ * batch, so we just loop — the loop still returns a proper result map so
80
+ * `EventOutbox.relay` can exercise the batched code path in tests.
81
+ */
82
+ async publishMany(events) {
83
+ const results = /* @__PURE__ */ new Map();
84
+ for (const event of events) try {
85
+ await this.publish(event);
86
+ results.set(event.meta.id, null);
87
+ } catch (err) {
88
+ results.set(event.meta.id, err instanceof Error ? err : new Error(String(err)));
89
+ }
90
+ return results;
91
+ }
92
+ async subscribe(pattern, handler) {
93
+ if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
94
+ this.handlers.get(pattern)?.add(handler);
95
+ return () => {
96
+ const set = this.handlers.get(pattern);
97
+ if (set) {
98
+ set.delete(handler);
99
+ if (set.size === 0) this.handlers.delete(pattern);
100
+ }
101
+ };
102
+ }
103
+ async close() {
104
+ this.handlers.clear();
105
+ }
106
+ };
107
+ //#endregion
7
108
  //#region src/events/retry.ts
8
109
  /**
9
110
  * Wrap an event handler with retry logic and dead letter support.
@@ -219,4 +320,4 @@ var eventPlugin_default = fp(eventPlugin, {
219
320
  fastify: "5.x"
220
321
  });
221
322
  //#endregion
222
- export { withRetry as i, eventPlugin_exports as n, createDeadLetterPublisher as r, eventPlugin as t };
323
+ export { MemoryEventTransport as a, withRetry as i, eventPlugin_exports as n, createEvent as o, createDeadLetterPublisher as r, eventPlugin as t };
@@ -1,4 +1,4 @@
1
- import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-CaaXM5Y1.mjs";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-IEn7Tn-Q.mjs";
2
2
  import { FastifyPluginAsync } from "fastify";
3
3
 
4
4
  //#region src/events/defineEvent.d.ts
@@ -1,7 +1,7 @@
1
- import { a as EventTransport, i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions, t as DeadLetteredEvent } from "../EventTransport-CaaXM5Y1.mjs";
2
- import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-C1W1CRbf.mjs";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions, t as DeadLetteredEvent } from "../EventTransport-IEn7Tn-Q.mjs";
2
+ import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-DbBFd3x_.mjs";
3
3
  import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
4
- import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-BtEUtjxZ.mjs";
4
+ import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-DsP1vqZB.mjs";
5
5
  import { RepositoryLike } from "@classytic/repo-core/adapter";
6
6
 
7
7
  //#region src/events/eventTypes.d.ts
@@ -1,9 +1,193 @@
1
- import { t as MemoryEventTransport } from "../EventTransport-7LPXLEUX.mjs";
2
- import { n as defineEvent, t as createEventRegistry } from "../defineEvent-DC981g-o.mjs";
3
- import { i as withRetry, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-Bw4bUDEO.mjs";
4
- import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-DjSYI2uP.mjs";
1
+ import { t as __exportAll } from "../rolldown-runtime-8H4AJuhK.mjs";
2
+ import { a as MemoryEventTransport, i as withRetry, o as createEvent, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-DJG2D7sy.mjs";
3
+ import { n as createSafeGetOne, t as createIsDuplicateKeyError } from "../store-helpers-DY6xq4sM.mjs";
5
4
  import { and, anyOf, eq, lte, ne, or } from "@classytic/repo-core/filter";
6
5
  import { update } from "@classytic/repo-core/update";
6
+ //#region src/events/defineEvent.ts
7
+ /**
8
+ * defineEvent — Typed Event Definitions with Optional Schema Validation
9
+ *
10
+ * Provides:
11
+ * 1. defineEvent() — declare an event with name, schema, version, description
12
+ * 2. EventRegistry — catalog of all known events + payload validation
13
+ * 3. .create() helper — build DomainEvent with auto-generated metadata
14
+ *
15
+ * The built-in validator checks: object type, required fields, and top-level
16
+ * property types. It does NOT recurse into nested objects, validate arrays,
17
+ * enums, patterns, formats, or $ref. This is intentional — it's a lightweight
18
+ * guard, not a full JSON Schema engine.
19
+ *
20
+ * For full validation, pass a custom `validate` function to `createEventRegistry()`:
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * import Ajv from 'ajv';
25
+ * const ajv = new Ajv();
26
+ *
27
+ * const registry = createEventRegistry({
28
+ * validate: (schema, payload) => {
29
+ * const valid = ajv.validate(schema, payload);
30
+ * return valid
31
+ * ? { valid: true }
32
+ * : { valid: false, errors: ajv.errorsText().split(', ') };
33
+ * },
34
+ * });
35
+ * ```
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * import { defineEvent, createEventRegistry } from '@classytic/arc/events';
40
+ *
41
+ * const OrderCreated = defineEvent({
42
+ * name: 'order.created',
43
+ * version: 1,
44
+ * schema: {
45
+ * type: 'object',
46
+ * properties: {
47
+ * orderId: { type: 'string' },
48
+ * total: { type: 'number' },
49
+ * },
50
+ * required: ['orderId', 'total'],
51
+ * },
52
+ * });
53
+ *
54
+ * // Type-safe event creation
55
+ * const event = OrderCreated.create({ orderId: 'o-1', total: 100 });
56
+ * await fastify.events.publish(event.type, event.payload, event.meta);
57
+ *
58
+ * // Registry for introspection + validation
59
+ * const registry = createEventRegistry();
60
+ * registry.register(OrderCreated);
61
+ * const result = registry.validate('order.created', payload);
62
+ * ```
63
+ */
64
+ var defineEvent_exports = /* @__PURE__ */ __exportAll({
65
+ createEventRegistry: () => createEventRegistry,
66
+ defineEvent: () => defineEvent
67
+ });
68
+ /**
69
+ * Define a typed event with optional schema validation.
70
+ *
71
+ * @example
72
+ * const OrderCreated = defineEvent({
73
+ * name: 'order.created',
74
+ * schema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] },
75
+ * });
76
+ *
77
+ * const event = OrderCreated.create({ orderId: '123' });
78
+ */
79
+ function defineEvent(input) {
80
+ const { name, schema, version = 1, description } = input;
81
+ return {
82
+ name,
83
+ schema,
84
+ version,
85
+ description,
86
+ create(payload, meta) {
87
+ return createEvent(name, payload, {
88
+ schemaVersion: version,
89
+ ...meta
90
+ });
91
+ }
92
+ };
93
+ }
94
+ /**
95
+ * Create an event registry for cataloging and validating events.
96
+ *
97
+ * The registry is opt-in — unregistered events pass validation.
98
+ * This allows gradual adoption without breaking existing code.
99
+ *
100
+ * @param options.validate - Custom validator replacing the built-in minimal validator.
101
+ * The built-in validator only checks top-level object structure (type, required, property types).
102
+ * For nested objects, arrays, enums, patterns, or $ref, provide AJV or similar.
103
+ */
104
+ function createEventRegistry(options) {
105
+ const customValidator = options?.validate;
106
+ const definitions = /* @__PURE__ */ new Map();
107
+ function registryKey(name, version) {
108
+ return `${name}:v${version}`;
109
+ }
110
+ return {
111
+ register(definition) {
112
+ const key = registryKey(definition.name, definition.version);
113
+ if (definitions.has(key)) throw new Error(`Event '${definition.name}' v${definition.version} is already registered. Use a different version number for schema evolution.`);
114
+ definitions.set(key, definition);
115
+ },
116
+ get(name, version) {
117
+ if (version !== void 0) return definitions.get(registryKey(name, version));
118
+ let latest;
119
+ let latestVersion = -1;
120
+ for (const def of definitions.values()) if (def.name === name && def.version > latestVersion) {
121
+ latest = def;
122
+ latestVersion = def.version;
123
+ }
124
+ return latest;
125
+ },
126
+ catalog() {
127
+ return Array.from(definitions.values()).map((def) => ({
128
+ name: def.name,
129
+ version: def.version,
130
+ description: def.description,
131
+ schema: def.schema
132
+ }));
133
+ },
134
+ validate(name, payload, version) {
135
+ let target = version !== void 0 ? definitions.get(registryKey(name, version)) : void 0;
136
+ if (!target && version === void 0) {
137
+ let latestVersion = -1;
138
+ for (const candidate of definitions.values()) if (candidate.name === name && candidate.version > latestVersion) {
139
+ target = candidate;
140
+ latestVersion = candidate.version;
141
+ }
142
+ }
143
+ if (!target) return { valid: true };
144
+ if (!target.schema) return { valid: true };
145
+ return customValidator ? customValidator(target.schema, payload) : validatePayload(payload, target.schema);
146
+ }
147
+ };
148
+ }
149
+ /**
150
+ * Built-in minimal validator — lightweight guard, NOT a full JSON Schema engine.
151
+ *
152
+ * Checks:
153
+ * - payload is an object (not null, not array)
154
+ * - required fields are present
155
+ * - top-level property types match (string, number, boolean, array, object)
156
+ *
157
+ * Does NOT check:
158
+ * - nested object properties
159
+ * - array item types
160
+ * - enum, pattern, format, minLength, minimum, $ref
161
+ *
162
+ * For full validation, pass a custom `validate` function to `createEventRegistry()`.
163
+ */
164
+ function validatePayload(payload, schema) {
165
+ const errors = [];
166
+ if (schema.type === "object") {
167
+ if (payload === null || payload === void 0 || typeof payload !== "object" || Array.isArray(payload)) return {
168
+ valid: false,
169
+ errors: ["Payload must be an object"]
170
+ };
171
+ const record = payload;
172
+ if (schema.required) {
173
+ for (const field of schema.required) if (!(field in record) || record[field] === void 0) errors.push(`Missing required field: '${field}'`);
174
+ }
175
+ if (schema.properties) {
176
+ for (const [key, propSchema] of Object.entries(schema.properties)) if (key in record && record[key] !== void 0 && record[key] !== null) {
177
+ const expectedType = propSchema.type;
178
+ if (expectedType) {
179
+ const actualType = Array.isArray(record[key]) ? "array" : typeof record[key];
180
+ if (expectedType !== actualType) errors.push(`Field '${key}': expected ${expectedType}, got ${actualType}`);
181
+ }
182
+ }
183
+ }
184
+ }
185
+ return errors.length === 0 ? { valid: true } : {
186
+ valid: false,
187
+ errors
188
+ };
189
+ }
190
+ //#endregion
7
191
  //#region src/events/eventTypes.ts
8
192
  /**
9
193
  * Event Type Constants and Helpers
@@ -757,7 +941,7 @@ function wrapWithSchema(definition, handler, options = {}) {
757
941
  if (validate && definition.schema) result = validate(definition.schema, event.payload);
758
942
  else if (registry) result = registry.validate(definition.name, event.payload, eventVersion);
759
943
  else if (definition.schema) {
760
- const { createEventRegistry } = await import("../defineEvent-DC981g-o.mjs").then((n) => n.r);
944
+ const { createEventRegistry } = await Promise.resolve().then(() => defineEvent_exports);
761
945
  const adhoc = createEventRegistry();
762
946
  adhoc.register(definition);
763
947
  result = adhoc.validate(definition.name, event.payload);
@@ -1,2 +1,2 @@
1
- import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-BtEUtjxZ.mjs";
1
+ import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-DsP1vqZB.mjs";
2
2
  export { type RedisStreamLike, RedisStreamTransport, type RedisStreamTransportOptions };
@@ -1,4 +1,4 @@
1
- import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "../../EventTransport-CaaXM5Y1.mjs";
1
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "../../EventTransport-IEn7Tn-Q.mjs";
2
2
 
3
3
  //#region src/events/transports/redis.d.ts
4
4
  interface RedisLike {
@@ -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-WVlAVp8T.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,6 +1,6 @@
1
- import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-B-j-iqo-.mjs";
2
- import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp-J1iPXY9p.mjs";
3
- import { t as loadResources } from "../loadResources-CwXt9MCh.mjs";
1
+ import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-CHw3_oA_.mjs";
2
+ import { a as edgePreset, c as testingPreset, i as developmentPreset, n as createApp, o as getPreset, s as productionPreset, t as ArcFactory } from "../createApp--703hQE1.mjs";
3
+ import { t as loadResources } from "../loadResources-B8UmpITt.mjs";
4
4
  //#region src/factory/edge.ts
5
5
  /**
6
6
  * Convert a Fastify app into a Web Standards fetch handler.
@@ -1,5 +1,5 @@
1
- import { yn as HookSystem, z as ResourceRegistry } from "./index-QYma8mdC.mjs";
2
- import { t as ExternalOpenApiPaths } from "./externalPaths-DLle0R07.mjs";
1
+ import { bn as HookSystem, z as ResourceRegistry } 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/core/arcCorePlugin.d.ts
@@ -1,4 +1,4 @@
1
- import { i as RequestScope } from "./types-BJwJN2Pg.mjs";
1
+ import { i as RequestScope } from "./types-LNebCoJx.mjs";
2
2
  import { FastifyRequest } from "fastify";
3
3
 
4
4
  //#region src/permissions/types.d.ts