@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.
- package/dist/{BaseController-B-uXzf6I.mjs → BaseController-Dy6EQHjM.mjs} +7 -7
- package/dist/{QueryCache-Dsyv2mLS.d.mts → QueryCache-TRfXau3W.d.mts} +1 -1
- package/dist/{ResourceRegistry-CWmOggxS.mjs → ResourceRegistry-5fKZwhK_.mjs} +1 -1
- package/dist/{pipeline-D4sqiokR.mjs → actionPermissions-D7NGwcAR.mjs} +29 -5
- package/dist/audit/index.d.mts +1 -1
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +262 -6
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/betterAuthOpenApi-CV1lxpRX.mjs +2 -0
- package/dist/{buildHandler-Cvrc2p7r.mjs → buildHandler-B0qvAguf.mjs} +1 -1
- package/dist/cache/index.d.mts +3 -3
- package/dist/cache/index.mjs +3 -3
- package/dist/{caching-C0DdKiYB.mjs → caching-_NJeDd1_.mjs} +1 -1
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +2 -2
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.d.mts +2 -2
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.d.mts +2 -2
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -4
- package/dist/{core-C88uIwd3.mjs → core-Ba-6Sue6.mjs} +67 -63
- package/dist/{createActionRouter-DGCCp-kP.mjs → createActionRouter-B_WzBFyf.mjs} +10 -10
- package/dist/{createAggregationRouter-Ca77b8iK.mjs → createAggregationRouter-B_G_nIjm.mjs} +4 -4
- package/dist/{createApp-J1iPXY9p.mjs → createApp--703hQE1.mjs} +14 -14
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-BzhyVWfn.mjs → elevation-COpYKt2h.mjs} +2 -2
- package/dist/encryption/index.d.mts +222 -0
- package/dist/encryption/index.mjs +270 -0
- package/dist/{errorHandler-Bua97SEt.mjs → errorHandler-Dxf6P_kV.mjs} +2 -2
- package/dist/{eventPlugin-Bw4bUDEO.mjs → eventPlugin-DJG2D7sy.mjs} +106 -5
- package/dist/{eventPlugin-C1W1CRbf.d.mts → eventPlugin-DbBFd3x_.d.mts} +1 -1
- package/dist/events/index.d.mts +3 -3
- package/dist/events/index.mjs +189 -5
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +3 -3
- package/dist/{fastify-augmentation-BdCl_eNo.d.mts → fastify-augmentation-D-5_yQJi.d.mts} +2 -2
- package/dist/{fields-CKLrhyOb.d.mts → fields-C7ahdGYf.d.mts} +1 -1
- package/dist/{generate-BaxDdmJ8.mjs → generate-Brans3fW.mjs} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +2 -2
- package/dist/idempotency/index.mjs +1 -1
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-Cvo1u9n6.d.mts → index-CP6A1Ffj.d.mts} +9 -9
- package/dist/{index-vcWUGkwf.d.mts → index-CRKiiP5o.d.mts} +3 -3
- package/dist/{index-QYma8mdC.d.mts → index-CUTG2Cuo.d.mts} +88 -7
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +7 -7
- package/dist/{init-DSsdRfk9.mjs → init-Bqpt1Hxf.mjs} +274 -130
- package/dist/integrations/event-gateway.d.mts +2 -2
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/mcp/index.d.mts +4 -4
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.mjs +7 -3
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +3 -3
- package/dist/{loadResources-CwXt9MCh.mjs → loadResources-B8UmpITt.mjs} +1 -1
- package/dist/{loadResourcesFromEntry-XHNeUWpu.mjs → loadResourcesFromEntry-BTlFEx05.mjs} +1 -1
- package/dist/{mcpPlugin-CzKdt3B8.mjs → mcpPlugin-C088Nd-G.mjs} +10 -11
- package/dist/{memory-TdTHCS9-.mjs → memory-CLXA2VeJ.mjs} +1 -1
- package/dist/{metrics-Da2EUPi9.mjs → metrics-fdxI5mn_.mjs} +1 -1
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +2 -2
- package/dist/{openapi-ButnRyOM.mjs → openapi-DZ-5a5Op.mjs} +5 -5
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-3uzwkbQt.mjs → permissions-BPB1xqGf.mjs} +4 -4
- package/dist/{pipe-9Tl-OAus.mjs → pipe-BOLmGIxa.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +6 -7
- package/dist/plugins/index.mjs +12 -12
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +4 -4
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-CF79RHEo.mjs → presets-BtyFwTO4.mjs} +2 -2
- package/dist/{queryCachePlugin-CnAeFqju.mjs → queryCachePlugin-emHb0CuX.mjs} +4 -4
- package/dist/{queryCachePlugin-Bd2tYgh5.d.mts → queryCachePlugin-xzFyKAjT.d.mts} +2 -2
- package/dist/{redis-3hqH_Qk_.d.mts → redis-C3W0qbz8.d.mts} +1 -1
- package/dist/{redis-stream-BtEUtjxZ.d.mts → redis-stream-DsP1vqZB.d.mts} +1 -1
- package/dist/{rateLimit-yKvxWAky.mjs → registerRoute-BdV8DDwU.mjs} +127 -2
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{registry-Br15xdGM.mjs → registry-Bm9LjWKs.mjs} +1 -1
- package/dist/{replyHelpers-DXHVxrrj.mjs → replyHelpers-67tRawQZ.mjs} +1 -1
- package/dist/{schemaIR-CE6USIRi.mjs → schemaIR-D1MJwqmp.mjs} +1 -1
- package/dist/schemas/index.d.mts +64 -65
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +3 -3
- package/dist/{sse-BrY1W6U8.mjs → sse-CfUjwjDk.mjs} +7 -4
- package/dist/streaming-BQcW6_Kq.mjs +162 -0
- package/dist/testing/index.d.mts +7 -7
- package/dist/testing/index.mjs +3 -3
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +5 -5
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-WVlAVp8T.d.mts → types-9_wtNPsI.d.mts} +10 -10
- package/dist/{types-BWBcb88c.d.mts → types-DpvSF5Hh.d.mts} +1 -1
- package/dist/{types-Bi0r0vjG.mjs → types-bHblZ9YN.mjs} +1 -1
- package/dist/utils/index.d.mts +42 -4
- package/dist/utils/index.mjs +6 -5
- package/dist/{utils-xAQeDyU_.mjs → utils-C6QQ9-hd.mjs} +1415 -1524
- package/dist/{validate-0__MuUsK.mjs → validate-CRcaWzp2.mjs} +1 -1
- package/dist/{versioning-BSEDGXKC.d.mts → versioning-D_eBCfNr.d.mts} +8 -1
- package/dist/{versioning-C0K8KQLx.mjs → versioning-Dk-71iIz.mjs} +1 -1
- package/dist/{websocket-BFBT5rH-.d.mts → websocket-MlYlrYq0.d.mts} +1 -1
- package/package.json +45 -30
- package/dist/EventTransport-7LPXLEUX.mjs +0 -103
- package/dist/actionPermissions-CX9QIMTd.mjs +0 -24
- package/dist/betterAuthOpenApi-DAAhMZff.mjs +0 -251
- package/dist/defineEvent-DC981g-o.mjs +0 -188
- package/dist/registerRoute-BEYn0ylk.mjs +0 -108
- /package/dist/{EventTransport-CaaXM5Y1.d.mts → EventTransport-IEn7Tn-Q.d.mts} +0 -0
- /package/dist/{HookSystem-BfCpWbwM.mjs → HookSystem-C1vB_Gc9.mjs} +0 -0
- /package/dist/{constants-TrJVIJl0.mjs → constants-gaEeMlz-.mjs} +0 -0
- /package/dist/{elevation-DW90z5bT.d.mts → elevation-xGUBq6p_.d.mts} +0 -0
- /package/dist/{errorHandler-_Lp2k9M3.d.mts → errorHandler-BtUUDPEa.d.mts} +0 -0
- /package/dist/{errors-DZOemeGa.d.mts → errors-DOHkjQlD.d.mts} +0 -0
- /package/dist/{errors-j4aJm1Wg.mjs → errors-DkO8Nk6-.mjs} +0 -0
- /package/dist/{externalPaths-DLle0R07.d.mts → externalPaths-BE6wFpYv.d.mts} +0 -0
- /package/dist/{generate-Dmk8cfvX.d.mts → generate-BnsL9GBm.d.mts} +0 -0
- /package/dist/{init-BS4Daqnv.d.mts → init-BPEu8cs-.d.mts} +0 -0
- /package/dist/{interface-DL7LlWS5.d.mts → interface-C1_WLTMK.d.mts} +0 -0
- /package/dist/{interface-CsrGR8LF.d.mts → interface-Uk8ONJv7.d.mts} +0 -0
- /package/dist/{keys-B3yyBEyH.mjs → keys-Cd316u_y.mjs} +0 -0
- /package/dist/{multipartBody-709YcXi1.mjs → multipartBody-CHEQSZ6G.mjs} +0 -0
- /package/dist/{pluralize-BxdUl2P4.mjs → pluralize-DccFV9R7.mjs} +0 -0
- /package/dist/{preloadResources-B-j-iqo-.mjs → preloadResources-CHw3_oA_.mjs} +0 -0
- /package/dist/{pushref-registry-Dqclzjy7.d.mts → pushref-registry-DnCqL3aa.d.mts} +0 -0
- /package/dist/{requestContext-DDLS3NVU.mjs → requestContext-BZKOr13H.mjs} +0 -0
- /package/dist/{chunk-BpYLSNr0.mjs → rolldown-runtime-8H4AJuhK.mjs} +0 -0
- /package/dist/{schemaConverter-DatAaVNj.mjs → schemaConverter-U0MnO9vd.mjs} +0 -0
- /package/dist/{sessionManager-CyHpYXoB.d.mts → sessionManager-BXG-HGwG.d.mts} +0 -0
- /package/dist/{storage-C5q8fuEt.d.mts → storage-Co_PQpeQ.d.mts} +0 -0
- /package/dist/{store-helpers-DjSYI2uP.mjs → store-helpers-DY6xq4sM.mjs} +0 -0
- /package/dist/{tracing-Bnb1-YVd.d.mts → tracing-D8jlGXP7.d.mts} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-C9KXUly6.mjs} +0 -0
- /package/dist/{types-BJwJN2Pg.d.mts → types-LNebCoJx.d.mts} +0 -0
- /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 "./
|
|
2
|
-
import { m as statusToArcCode, p as isArcError } from "./errors-
|
|
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 "./
|
|
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-
|
|
4
|
-
import { n as requestContext } from "./requestContext-
|
|
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-
|
|
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
|
package/dist/events/index.d.mts
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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
|
package/dist/events/index.mjs
CHANGED
|
@@ -1,9 +1,193 @@
|
|
|
1
|
-
import { t as
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
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-
|
|
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-
|
|
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 {
|
package/dist/factory/index.d.mts
CHANGED
|
@@ -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-
|
|
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
|
package/dist/factory/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-
|
|
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
|
|
3
|
-
import { t as loadResources } from "../loadResources-
|
|
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 {
|
|
2
|
-
import { t as ExternalOpenApiPaths } from "./externalPaths-
|
|
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
|