@classytic/arc 2.18.5 → 2.20.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/README.md +1 -1
- package/bin/arc.js +37 -0
- package/dist/{BaseController-Dy6EQHjM.mjs → BaseController-D-MHZ27_.mjs} +4 -4
- package/dist/{ResourceRegistry-5fKZwhK_.mjs → ResourceRegistry-7RnrEWNP.mjs} +1 -1
- package/dist/{actionPermissions-h7DYDNMg.mjs → actionPermissions-C9RMiIpc.mjs} +2 -1
- package/dist/audit/index.mjs +1 -1
- package/dist/auth/index.d.mts +1 -1
- package/dist/auth/index.mjs +17 -6
- package/dist/{buildHandler-B0qvAguf.mjs → buildHandler-D_2ULWxZ.mjs} +3 -3
- package/dist/cache/index.mjs +2 -2
- 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.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +4 -13
- package/dist/cli/index.mjs +2 -2
- package/dist/{constants-gaEeMlz-.mjs → constants-DNre0wRg.mjs} +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +4 -4
- package/dist/{core-ZnQBa5rh.mjs → core-CWhyGkiM.mjs} +136 -59
- package/dist/{createActionRouter-CagItpxI.mjs → createActionRouter-HlQso0jO.mjs} +8 -7
- package/dist/{createAggregationRouter-DVKroXhC.mjs → createAggregationRouter-5LzPzgw1.mjs} +5 -4
- package/dist/{createApp--703hQE1.mjs → createApp-D6uwzCKu.mjs} +210 -17
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +1 -1
- package/dist/encryption/index.d.mts +222 -0
- package/dist/encryption/index.mjs +270 -0
- package/dist/events/index.mjs +9 -6
- package/dist/factory/index.d.mts +3 -2
- package/dist/factory/index.mjs +2 -2
- package/dist/{fastify-augmentation-Cxb0ROIw.d.mts → fastify-augmentation-CCQJEDgh.d.mts} +12 -1
- package/dist/{generate-Brans3fW.mjs → generate-usRSdQmP.mjs} +4 -3
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +1 -1
- package/dist/{index-CRKiiP5o.d.mts → index-CdFkIMEd.d.mts} +5 -0
- package/dist/{index-Dh0_RSTd.d.mts → index-DUa8WHz3.d.mts} +7 -7
- package/dist/{index-CHNQIhcb.d.mts → index-_--GyzcO.d.mts} +111 -2
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +6 -6
- package/dist/{init-Bqpt1Hxf.mjs → init-YH_nslQR.mjs} +87 -5
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- 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.d.mts +17 -5
- package/dist/integrations/streamline.mjs +9 -5
- package/dist/{loadResourcesFromEntry-BTlFEx05.mjs → loadResourcesFromEntry-OMCvIEE7.mjs} +1 -1
- package/dist/{mcpPlugin-D1wuRD2c.mjs → mcpPlugin-CuW6L_uD.mjs} +148 -49
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/module-JYH9V0I8.d.mts +384 -0
- package/dist/{openapi-xVJibapr.mjs → openapi-Dq6RbEGK.mjs} +3 -3
- package/dist/permissions/index.d.mts +1 -1
- package/dist/permissions/index.mjs +1 -1
- package/dist/{permissions-BPB1xqGf.mjs → permissions-CgbOLJZG.mjs} +10 -2
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/plugins/index.d.mts +10 -5
- package/dist/plugins/index.mjs +3 -3
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/{pluralize-DccFV9R7.mjs → pluralize-BhrgwZb9.mjs} +3 -3
- package/dist/presets/filesUpload.d.mts +1 -1
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +16 -2
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +1 -1
- package/dist/presets/search.d.mts +1 -1
- package/dist/presets/search.mjs +3 -2
- package/dist/{presets-BtyFwTO4.mjs → presets-DWsYQdbV.mjs} +1 -1
- package/dist/{queryCachePlugin-emHb0CuX.mjs → queryCachePlugin-DzqLN904.mjs} +2 -2
- package/dist/{registerRoute-CPmdkZGu.mjs → registerRoute-DGSREwPw.mjs} +20 -2
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{registry-Bm9LjWKs.mjs → registry-vcOCtPij.mjs} +1 -1
- package/dist/{schemaIR-D1MJwqmp.mjs → schemaIR-ajetw3Fv.mjs} +1 -42
- package/dist/scim/index.mjs +2 -2
- package/dist/sync/changelog.d.mts +2 -0
- package/dist/sync/changelog.mjs +2 -0
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +2 -2
- package/dist/types/index.d.mts +2 -2
- package/dist/{types-CmkknLzI.d.mts → types-BHMKlMBU.d.mts} +53 -195
- package/dist/{types-D60PFwfG.d.mts → types-C9b2HR5W.d.mts} +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/index.mjs +1 -1
- package/dist/{utils-C6QQ9-hd.mjs → utils-ChjYQpjn.mjs} +1400 -1400
- package/dist/{validate-CRcaWzp2.mjs → validate-C2mGgSXg.mjs} +3 -3
- package/package.json +22 -8
- package/skills/arc/SKILL.md +10 -0
- package/skills/arc/references/mcp.md +2 -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 };
|
package/dist/events/index.mjs
CHANGED
|
@@ -417,12 +417,13 @@ function repositoryAsOutboxStore(repository) {
|
|
|
417
417
|
},
|
|
418
418
|
async getPending(limit) {
|
|
419
419
|
const now = /* @__PURE__ */ new Date();
|
|
420
|
-
|
|
420
|
+
const result = await r.getAll({
|
|
421
421
|
filters: claimableFilter(now),
|
|
422
422
|
sort: { createdAt: 1 },
|
|
423
423
|
page: 1,
|
|
424
424
|
limit
|
|
425
|
-
})
|
|
425
|
+
});
|
|
426
|
+
return unwrapDocs(result).map((d) => d.event).filter(isWellFormed);
|
|
426
427
|
},
|
|
427
428
|
async claimPending(options) {
|
|
428
429
|
const limit = options?.limit ?? DEFAULT_CLAIM_LIMIT;
|
|
@@ -493,12 +494,13 @@ function repositoryAsOutboxStore(repository) {
|
|
|
493
494
|
}
|
|
494
495
|
},
|
|
495
496
|
async getDeadLettered(limit) {
|
|
496
|
-
|
|
497
|
+
const result = await r.getAll({
|
|
497
498
|
filters: eq("status", "dead_letter"),
|
|
498
499
|
sort: { [idField]: 1 },
|
|
499
500
|
page: 1,
|
|
500
501
|
limit
|
|
501
|
-
})
|
|
502
|
+
});
|
|
503
|
+
return unwrapDocs(result).filter((d) => isWellFormed(d.event)).map((d) => ({
|
|
502
504
|
event: d.event,
|
|
503
505
|
error: {
|
|
504
506
|
message: d.lastError?.message ?? "unknown",
|
|
@@ -513,13 +515,14 @@ function repositoryAsOutboxStore(repository) {
|
|
|
513
515
|
const cutoff = new Date(Date.now() - olderThanMs);
|
|
514
516
|
let totalDeleted = 0;
|
|
515
517
|
for (;;) {
|
|
516
|
-
const
|
|
518
|
+
const result = await r.getAll({
|
|
517
519
|
filters: and(eq("status", "delivered"), lte("deliveredAt", cutoff)),
|
|
518
520
|
sort: { deliveredAt: 1 },
|
|
519
521
|
page: 1,
|
|
520
522
|
limit: DEFAULT_PURGE_BATCH,
|
|
521
523
|
select: idField
|
|
522
|
-
})
|
|
524
|
+
});
|
|
525
|
+
const batch = unwrapDocs(result);
|
|
523
526
|
if (batch.length === 0) break;
|
|
524
527
|
const ids = batch.map((d) => d[idField]);
|
|
525
528
|
const res = await r.deleteMany(anyOf(idField, ids));
|
package/dist/factory/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as getModuleExports, c as LoadResourcesOptions, d as loadResources, i as defineModule, l as ResourceLike, n as ArcModuleInput, o as orderModules, r as ArcModuleRegistry, s as resolveModule, t as ArcModule, u as ResourceModule } from "../module-JYH9V0I8.mjs";
|
|
2
|
+
import { a as CustomPluginAuthOption, c as RawBodyOptions, d as preloadResourcesAsync, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as preloadResources } from "../types-BHMKlMBU.mjs";
|
|
2
3
|
import { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
3
4
|
|
|
4
5
|
//#region src/factory/createApp.d.ts
|
|
@@ -95,4 +96,4 @@ declare const edgePreset: Partial<CreateAppOptions>;
|
|
|
95
96
|
*/
|
|
96
97
|
declare function getPreset(name: "production" | "development" | "testing" | "edge"): Partial<CreateAppOptions>;
|
|
97
98
|
//#endregion
|
|
98
|
-
export { ArcFactory, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, type FetchHandlerOptions, type JwtAuthOption, type LoadResourcesOptions, type MultipartOptions, type RawBodyOptions, type ResourceLike, type ResourceModule, type UnderPressureOptions, createApp, developmentPreset, edgePreset, getPreset, loadResources, preloadResources, preloadResourcesAsync, productionPreset, testingPreset, toFetchHandler };
|
|
99
|
+
export { ArcFactory, type ArcModule, type ArcModuleInput, type ArcModuleRegistry, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, type FetchHandlerOptions, type JwtAuthOption, type LoadResourcesOptions, type MultipartOptions, type RawBodyOptions, type ResourceLike, type ResourceModule, type UnderPressureOptions, createApp, defineModule, developmentPreset, edgePreset, getModuleExports, getPreset, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveModule, testingPreset, toFetchHandler };
|
package/dist/factory/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-CHw3_oA_.mjs";
|
|
2
|
-
import { a as
|
|
2
|
+
import { a as getModuleExports, c as developmentPreset, d as productionPreset, f as testingPreset, i as defineModule, l as edgePreset, n as createApp, o as orderModules, s as resolveModule, t as ArcFactory, u as getPreset } from "../createApp-D6uwzCKu.mjs";
|
|
3
3
|
import { t as loadResources } from "../loadResources-B8UmpITt.mjs";
|
|
4
4
|
//#region src/factory/edge.ts
|
|
5
5
|
/**
|
|
@@ -47,4 +47,4 @@ function toFetchHandler(app, options = {}) {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
//#endregion
|
|
50
|
-
export { ArcFactory, createApp, developmentPreset, edgePreset, getPreset, loadResources, preloadResources, preloadResourcesAsync, productionPreset, testingPreset, toFetchHandler };
|
|
50
|
+
export { ArcFactory, createApp, defineModule, developmentPreset, edgePreset, getModuleExports, getPreset, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveModule, testingPreset, toFetchHandler };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { bn as HookSystem, z as ResourceRegistry } from "./index-_--GyzcO.mjs";
|
|
2
2
|
import { t as ExternalOpenApiPaths } from "./externalPaths-BE6wFpYv.mjs";
|
|
3
|
+
import { r as ArcModuleRegistry } from "./module-JYH9V0I8.mjs";
|
|
3
4
|
import { FastifyPluginAsync } from "fastify";
|
|
4
5
|
|
|
5
6
|
//#region src/core/arcCorePlugin.d.ts
|
|
@@ -28,6 +29,16 @@ interface ArcCore {
|
|
|
28
29
|
externalOpenApiPaths: ExternalOpenApiPaths[];
|
|
29
30
|
/** Registered plugins for introspection */
|
|
30
31
|
plugins: Map<string, PluginMeta>;
|
|
32
|
+
/**
|
|
33
|
+
* Module public exports — `bootstrap()` return values keyed by module name
|
|
34
|
+
* (see `src/factory/module.ts`). Read via `getModuleExports(fastify, name)`:
|
|
35
|
+
* augment `ArcModuleRegistry` once and the name infers the export type;
|
|
36
|
+
* otherwise pass it inline. `Partial<ArcModuleRegistry>` gives augmented
|
|
37
|
+
* apps typed direct access (`fastify.arc.modules.order`), while the
|
|
38
|
+
* `Record<string, unknown>` intersection keeps un-augmented reads open.
|
|
39
|
+
* Populated lazily by `registerResources` — absent until a module exports.
|
|
40
|
+
*/
|
|
41
|
+
modules?: Partial<ArcModuleRegistry> & Record<string, unknown>;
|
|
31
42
|
}
|
|
32
43
|
declare const arcCorePlugin: FastifyPluginAsync<ArcCorePluginOptions>;
|
|
33
44
|
declare const _default: FastifyPluginAsync<ArcCorePluginOptions>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as pluralize } from "./pluralize-
|
|
1
|
+
import { t as pluralize } from "./pluralize-BhrgwZb9.mjs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
4
|
//#region src/cli/commands/generate/name-helpers.ts
|
|
@@ -601,9 +601,10 @@ async function generate(type, args) {
|
|
|
601
601
|
if (!type) throw new Error("Missing type argument\nUsage: arc generate <resource|controller|model|repository|schemas> <name>");
|
|
602
602
|
const [name, ...restArgs] = args;
|
|
603
603
|
if (!name) throw new Error("Missing name argument\nUsage: arc generate <type> <name>\nExample: arc generate resource product");
|
|
604
|
+
if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(name)) throw new Error(`Invalid name "${name}". Use kebab-case: a letter followed by letters, digits, or hyphens (e.g. 'product', 'order-line'). No slashes, dots, spaces, or path segments.`);
|
|
604
605
|
const capitalizedName = toPascalCase(name);
|
|
605
606
|
const lowerName = name.toLowerCase();
|
|
606
|
-
if ((type === "resource" || type === "r" || type === "model" || type === "m") && new Set([
|
|
607
|
+
if ((type === "resource" || type === "r" || type === "model" || type === "m") && (/* @__PURE__ */ new Set([
|
|
607
608
|
"user",
|
|
608
609
|
"session",
|
|
609
610
|
"account",
|
|
@@ -614,7 +615,7 @@ async function generate(type, args) {
|
|
|
614
615
|
"team",
|
|
615
616
|
"team-member",
|
|
616
617
|
"apikey"
|
|
617
|
-
]).has(lowerName)) {
|
|
618
|
+
])).has(lowerName)) {
|
|
618
619
|
console.warn(`\n[arc generate] "${lowerName}" is a Better Auth-owned collection.\nBetter Auth's organization/admin/bearer plugins write this collection directly,\nso generating a parallel arc model would create a duplicate registration.\n\nRecommended pattern:\n import { createBetterAuthOverlay } from '@classytic/mongokit/better-auth';\n const adapter = await createBetterAuthOverlay({\n auth, mongoose, collection: '${lowerName}',\n });\n // ...then \`defineResource({ name: '${lowerName}', adapter, ... })\` reads\n // BA's collection through arc with full pagination/filters/permissions.\n\nAborting. Re-run with a different name if you need a separate domain model.\n`);
|
|
619
620
|
return;
|
|
620
621
|
}
|
package/dist/hooks/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Cn as
|
|
1
|
+
import { Cn as afterDelete, Dn as beforeUpdate, En as beforeDelete, On as createHookSystem, Sn as afterCreate, Tn as beforeCreate, _n as HookOperation, bn as HookSystem, gn as HookHandler, hn as HookContext, kn as defineHook, mn as DefineHookOptions, vn as HookPhase, wn as afterUpdate, xn as HookSystemOptions, yn as HookRegistration } from "../index-_--GyzcO.mjs";
|
|
2
2
|
export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
|
|
@@ -387,7 +387,7 @@ const idempotencyPlugin = async (fastify, opts = {}) => {
|
|
|
387
387
|
const statusCode = reply.statusCode;
|
|
388
388
|
if (statusCode < 200 || statusCode >= 300) return payload;
|
|
389
389
|
const headersToCache = {};
|
|
390
|
-
const excludeHeaders = new Set([
|
|
390
|
+
const excludeHeaders = /* @__PURE__ */ new Set([
|
|
391
391
|
"content-length",
|
|
392
392
|
"transfer-encoding",
|
|
393
393
|
"connection",
|
|
@@ -669,6 +669,11 @@ declare function fullPublic<TDoc = Record<string, unknown>>(overrides?: Permissi
|
|
|
669
669
|
/**
|
|
670
670
|
* Read-only: list + get authenticated, write operations denied.
|
|
671
671
|
* Useful for computed/derived resources.
|
|
672
|
+
*
|
|
673
|
+
* Writes carry an explicit `denyAll` check — an omitted CRUD permission
|
|
674
|
+
* mounts the route with NO gate (public-by-omission), so leaving
|
|
675
|
+
* create/update/delete undefined here would expose unauthenticated writes.
|
|
676
|
+
* Hosts that disable write routes entirely (`disabledRoutes`) are unaffected.
|
|
672
677
|
*/
|
|
673
678
|
declare function readOnly<TDoc = Record<string, unknown>>(overrides?: PermissionOverrides<TDoc>): ResourcePermissions<TDoc>;
|
|
674
679
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as RequestContext, F as FastifyWithDecorators, H as ActionDefinition, K as ArcFieldRule,
|
|
1
|
+
import { C as RequestContext, Et as AggregationConfig, F as FastifyWithDecorators, H as ActionDefinition, K as ArcFieldRule, L as RequestWithExtras, T as CrudRouterOptions, V as ResourceDefinition, bt as IControllerResponse, ht as RouteSchemaOptions, pt as RouteMcpConfig, q as CrudController, qt as AnyRecord, st as ResourceConfig, xt as IRequestContext, yt as IController } from "./index-_--GyzcO.mjs";
|
|
2
2
|
import { i as RequestScope } from "./types-LNebCoJx.mjs";
|
|
3
3
|
import { c as PermissionCheck } from "./fields-C7ahdGYf.mjs";
|
|
4
4
|
import { FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
|
|
@@ -300,7 +300,7 @@ declare function sendControllerResponse<T>(reply: FastifyReply, response: IContr
|
|
|
300
300
|
* fastify.get('/products', listHandler);
|
|
301
301
|
* ```
|
|
302
302
|
*/
|
|
303
|
-
declare function createFastifyHandler<T>(controllerMethod: (req: IRequestContext) => Promise<IControllerResponse<T>>): (req: FastifyRequest, reply: FastifyReply) => Promise<
|
|
303
|
+
declare function createFastifyHandler<T>(controllerMethod: (req: IRequestContext) => Promise<IControllerResponse<T>>): (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
|
|
304
304
|
/**
|
|
305
305
|
* Create Fastify adapters for all CRUD methods of an IController
|
|
306
306
|
*
|
|
@@ -319,11 +319,11 @@ declare function createFastifyHandler<T>(controllerMethod: (req: IRequestContext
|
|
|
319
319
|
* ```
|
|
320
320
|
*/
|
|
321
321
|
declare function createCrudHandlers<TDoc>(controller: IController<TDoc>): {
|
|
322
|
-
list: (req: FastifyRequest, reply: FastifyReply) => Promise<
|
|
323
|
-
get: (req: FastifyRequest, reply: FastifyReply) => Promise<
|
|
324
|
-
create: (req: FastifyRequest, reply: FastifyReply) => Promise<
|
|
325
|
-
update: (req: FastifyRequest, reply: FastifyReply) => Promise<
|
|
326
|
-
delete: (req: FastifyRequest, reply: FastifyReply) => Promise<
|
|
322
|
+
list: (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
|
|
323
|
+
get: (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
|
|
324
|
+
create: (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
|
|
325
|
+
update: (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
|
|
326
|
+
delete: (req: FastifyRequest, reply: FastifyReply) => Promise<FastifyReply>;
|
|
327
327
|
};
|
|
328
328
|
//#endregion
|
|
329
329
|
//#region src/core/fieldRulePredicates.d.ts
|
|
@@ -2467,6 +2467,25 @@ interface RouteDefinition {
|
|
|
2467
2467
|
* pipeline entirely.
|
|
2468
2468
|
*/
|
|
2469
2469
|
readonly fieldWrite?: boolean;
|
|
2470
|
+
/**
|
|
2471
|
+
* Per-route rate limit. A custom route is its own Fastify route, so this
|
|
2472
|
+
* overrides the resource-level `rateLimit` for THIS endpoint only:
|
|
2473
|
+
*
|
|
2474
|
+
* - omitted → inherit the resource-level limit (or app default)
|
|
2475
|
+
* - `false` → disable rate limiting on this route
|
|
2476
|
+
* - `{ max, timeWindow }` → apply this specific limit
|
|
2477
|
+
*
|
|
2478
|
+
* Requires `@fastify/rate-limit` registered (arc's factory wires it by
|
|
2479
|
+
* default). Use for an expensive `/export` or an auth-sensitive `/reset`
|
|
2480
|
+
* that needs a tighter cap than the resource's read-heavy CRUD, or `false`
|
|
2481
|
+
* on an internal health/webhook route that must not be throttled.
|
|
2482
|
+
*
|
|
2483
|
+
* Note: this is per-route because each custom route is a distinct endpoint.
|
|
2484
|
+
* `actions` share ONE `POST /:id/action` mount by design, so a per-ACTION
|
|
2485
|
+
* limit can't be expressed here — throttle a specific action by promoting
|
|
2486
|
+
* it to a dedicated `routes:` entry, or rely on the resource-level limit.
|
|
2487
|
+
*/
|
|
2488
|
+
readonly rateLimit?: RateLimitConfig | false;
|
|
2470
2489
|
/**
|
|
2471
2490
|
* Fastify route schema. Each slot (`body`, `querystring`, `params`,
|
|
2472
2491
|
* `headers`, `response[status]`) accepts a plain JSON Schema object
|
|
@@ -2504,7 +2523,15 @@ interface RouteDefinition {
|
|
|
2504
2523
|
* ID, action-specific data, and the request.
|
|
2505
2524
|
*/
|
|
2506
2525
|
type ActionHandlerFn = (id: string, data: Record<string, unknown>, req: RequestWithExtras) => Promise<unknown>;
|
|
2507
|
-
/**
|
|
2526
|
+
/**
|
|
2527
|
+
* Full action configuration with handler, permissions, and schema.
|
|
2528
|
+
*
|
|
2529
|
+
* Rate limiting is intentionally NOT per-action: every action shares one
|
|
2530
|
+
* `POST /:id/action` (or `POST /action`) mount, so Fastify's per-route limit
|
|
2531
|
+
* can't distinguish them. Actions inherit the resource-level `rateLimit`; to
|
|
2532
|
+
* throttle a specific operation, promote it to a `routes:` entry (which
|
|
2533
|
+
* supports `RouteDefinition.rateLimit`).
|
|
2534
|
+
*/
|
|
2508
2535
|
interface ActionDefinition {
|
|
2509
2536
|
readonly handler: ActionHandlerFn;
|
|
2510
2537
|
/** Per-action permission (overrides resource-level `actionPermissions`) */
|
|
@@ -2667,6 +2694,48 @@ interface ResourcePermissions {
|
|
|
2667
2694
|
update?: PermissionCheck;
|
|
2668
2695
|
delete?: PermissionCheck;
|
|
2669
2696
|
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Plugin extension namespace — arc's typed escape hatch for attaching
|
|
2699
|
+
* declarative, per-resource config that **plugins** read at request time.
|
|
2700
|
+
*
|
|
2701
|
+
* This interface is intentionally **empty in core**. Each optional plugin
|
|
2702
|
+
* augments it via TypeScript declaration merging to register its own typed
|
|
2703
|
+
* slice — exactly the model Fastify uses for `FastifyInstance` decorators.
|
|
2704
|
+
* A host that never installs a plugin never sees its key; a host that does
|
|
2705
|
+
* gets full autocomplete + compile-time checking on the matching
|
|
2706
|
+
* `extensions` block, and a typo (`extensions: { encyrption: … }`) is a
|
|
2707
|
+
* type error rather than a silent no-op.
|
|
2708
|
+
*
|
|
2709
|
+
* **The "React for backend" composition model.** `defineResource()` is the
|
|
2710
|
+
* declarative component, `extensions` is its typed props bag, and plugins
|
|
2711
|
+
* are the providers that consume those props at the framework boundary.
|
|
2712
|
+
* Adding a cross-cutting capability becomes *additive* — a plugin ships a
|
|
2713
|
+
* subpath + a one-line `declare module` augmentation, with no new
|
|
2714
|
+
* first-class `ResourceConfig` field and no core release per feature.
|
|
2715
|
+
*
|
|
2716
|
+
* Arc threads the declared `extensions` onto every generated route's
|
|
2717
|
+
* Fastify `config` (see `createCrudRouter`), so a plugin reads its slice
|
|
2718
|
+
* at request time via `request.routeOptions.config.arcExtensions` without
|
|
2719
|
+
* re-deriving which resource a route belongs to.
|
|
2720
|
+
*
|
|
2721
|
+
* @example A plugin registers its slice (in `@classytic/arc/encryption`)
|
|
2722
|
+
* ```ts
|
|
2723
|
+
* declare module "@classytic/arc/types" {
|
|
2724
|
+
* interface ResourceExtensions {
|
|
2725
|
+
* encryption?: import("@classytic/arc/encryption").EncryptionDirective;
|
|
2726
|
+
* }
|
|
2727
|
+
* }
|
|
2728
|
+
* ```
|
|
2729
|
+
*
|
|
2730
|
+
* @example A host declares it on a resource
|
|
2731
|
+
* ```ts
|
|
2732
|
+
* defineResource({
|
|
2733
|
+
* name: "payment",
|
|
2734
|
+
* extensions: { encryption: { mode: "fields", fields: ["cardNumber", "cvv"] } },
|
|
2735
|
+
* });
|
|
2736
|
+
* ```
|
|
2737
|
+
*/
|
|
2738
|
+
interface ResourceExtensions {}
|
|
2670
2739
|
interface ResourceConfig<TDoc = AnyRecord> {
|
|
2671
2740
|
name: string;
|
|
2672
2741
|
displayName?: string;
|
|
@@ -3097,6 +3166,26 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
3097
3166
|
audit?: boolean | {
|
|
3098
3167
|
operations?: ("create" | "update" | "delete")[];
|
|
3099
3168
|
};
|
|
3169
|
+
/**
|
|
3170
|
+
* Typed plugin-extension namespace — declarative per-resource config that
|
|
3171
|
+
* arc plugins read at request time. Empty by default; each installed
|
|
3172
|
+
* plugin augments {@link ResourceExtensions} with its own typed slice via
|
|
3173
|
+
* `declare module`. See {@link ResourceExtensions} for the full rationale
|
|
3174
|
+
* and the "React for backend" composition model.
|
|
3175
|
+
*
|
|
3176
|
+
* Threaded onto every generated route's Fastify `config` as
|
|
3177
|
+
* `config.arcExtensions`, so a plugin reads its slice via
|
|
3178
|
+
* `request.routeOptions.config.arcExtensions` without a resource lookup.
|
|
3179
|
+
*
|
|
3180
|
+
* @example
|
|
3181
|
+
* ```ts
|
|
3182
|
+
* defineResource({
|
|
3183
|
+
* name: "payment",
|
|
3184
|
+
* extensions: { encryption: { mode: "fields", fields: ["cardNumber"] } },
|
|
3185
|
+
* });
|
|
3186
|
+
* ```
|
|
3187
|
+
*/
|
|
3188
|
+
extensions?: ResourceExtensions;
|
|
3100
3189
|
}
|
|
3101
3190
|
//#endregion
|
|
3102
3191
|
//#region src/core/defineResource/diagnostics.d.ts
|
|
@@ -3189,6 +3278,7 @@ declare class ResourceDefinition<TDoc = AnyRecord> {
|
|
|
3189
3278
|
*/
|
|
3190
3279
|
readonly onFieldWriteDenied?: "reject" | "strip";
|
|
3191
3280
|
readonly cache?: ResourceCacheConfig;
|
|
3281
|
+
readonly extensions?: ResourceExtensions;
|
|
3192
3282
|
readonly referenceData: boolean;
|
|
3193
3283
|
/**
|
|
3194
3284
|
* Per-resource MCP opt-out. `false` keeps the resource out of every
|
|
@@ -3544,6 +3634,18 @@ type FastifyWithDecorators = FastifyInstance & {
|
|
|
3544
3634
|
};
|
|
3545
3635
|
/** Handler signature for middleware functions. */
|
|
3546
3636
|
type MiddlewareHandler = (request: RequestWithExtras, reply: FastifyReply) => Promise<unknown>;
|
|
3637
|
+
/**
|
|
3638
|
+
* Route-config augmentation — arc stamps `defineResource({ extensions })`
|
|
3639
|
+
* onto every generated route's Fastify `config` under `arcExtensions`, so
|
|
3640
|
+
* plugins read their typed slice at request time via
|
|
3641
|
+
* `request.routeOptions.config.arcExtensions` without re-resolving which
|
|
3642
|
+
* resource a route belongs to. Type-only; see `ResourceExtensions`.
|
|
3643
|
+
*/
|
|
3644
|
+
declare module "fastify" {
|
|
3645
|
+
interface FastifyContextConfig {
|
|
3646
|
+
arcExtensions?: ResourceExtensions;
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3547
3649
|
//#endregion
|
|
3548
3650
|
//#region src/types/plugins.d.ts
|
|
3549
3651
|
interface GracefulShutdownOptions {
|
|
@@ -3619,6 +3721,13 @@ interface CrudRouterOptions {
|
|
|
3619
3721
|
* re-reading resource config. Defaults to `_id`.
|
|
3620
3722
|
*/
|
|
3621
3723
|
idField?: string;
|
|
3724
|
+
/**
|
|
3725
|
+
* Resource-level plugin extensions (`defineResource({ extensions })`).
|
|
3726
|
+
* Stamped onto every registered route's Fastify `config.arcExtensions`
|
|
3727
|
+
* so request-time plugins (encryption, …) read their typed slice without
|
|
3728
|
+
* re-resolving which resource a route belongs to. See `ResourceExtensions`.
|
|
3729
|
+
*/
|
|
3730
|
+
extensions?: ResourceExtensions;
|
|
3622
3731
|
}
|
|
3623
3732
|
//#endregion
|
|
3624
3733
|
//#region src/types/query.d.ts
|
|
@@ -4032,4 +4141,4 @@ interface ValidateOptions {
|
|
|
4032
4141
|
strict?: boolean;
|
|
4033
4142
|
}
|
|
4034
4143
|
//#endregion
|
|
4035
|
-
export { OnTenantDeleteConfig as $,
|
|
4144
|
+
export { OnTenantDeleteConfig as $, UserOrganization as $t, RequestIdOptions as A, ArcCreateResult as An, AggregationMaterializedResult as At, defineResource as B, BodySanitizerConfig as Bn, Transform as Bt, RequestContext as C, afterDelete as Cn, AggMeasureInput as Ct, HealthCheck as D, beforeUpdate as Dn, AggregationDateRangeRequirement as Dt, GracefulShutdownOptions as E, beforeDelete as En, AggregationConfig as Et, FastifyWithDecorators as F, BaseControllerOptions as Fn, NextFunction as Ft, ActionsMap as G, JwtContext as Gt, ActionDefinition as H, AuthPluginOptions as Ht, MiddlewareHandler as I, ControllerConfigurableOptions as In, OperationFilter as It, CrudRouteKey as J, ApiResponse as Jt, ArcFieldRule as K, TokenPair as Kt, RequestWithExtras as L, ControllerConstructionOptions as Ln, PipelineConfig as Lt, EventsDecorator as M, ArcGetResult as Mn, AggregationsMap as Mt, FastifyRequestExtras as N, ArcListResult as Nn, Guard as Nt, HealthOptions as O, createHookSystem as On, AggregationIndexHint as Ot, FastifyWithAuth as P, ArcUpdateResult as Pn, Interceptor as Pt, MiddlewareConfig as Q, UserLike as Qt, RegisterOptions as R, ListResult as Rn, PipelineContext as Rt, QueryParserInterface as S, afterCreate as Sn, RouteHandler as St, CrudRouterOptions as T, beforeCreate as Tn, AggregationCacheConfig as Tt, ActionEntry as U, Authenticator as Ut, ResourceDefinition as V, AuthHelpers as Vt, ActionHandlerFn as W, AuthenticatorContext as Wt, EventDefinition as X, JWTPayload as Xt, CrudSchemas as Y, ArcRequest as Yt, FieldRule$1 as Z, ObjectId as Zt, ControllerQueryOptions as _, HookOperation as _n, ControllerLike as _t, InferAdapterDoc as a, SlugExt as an, ResolvedTenantPurge as at, ParsedQuery as b, HookSystem as bn, IControllerResponse as bt, TypedController as c, BulkMixin as cn, ResourceExtensions as ct, PaginationResult as d, QueryResolverConfig as dn, ResourcePermissions as dt, BaseController as en, OpenApiSchemas as et, IntrospectionData as f, AccessControl as fn, RouteDefinition as ft, ArcInternalMetadata as g, HookHandler as gn, ControllerHandler as gt, ResourceMetadata as h, HookContext as hn, RouteSchemaOptions as ht, ValidationResult as i, TreeMixin as in, RateLimitConfig as it, ArcDecorator as j, ArcDeleteResult as jn, AggregationRateLimit as jt, IntrospectionPluginOptions as k, defineHook as kn, AggregationMaterializedContext as kt, TypedRepository as l, BaseCrudController as ln, ResourceHookContext as lt, RegistryStats as m, DefineHookOptions as mn, RouteMethod as mt, ConfigError as n, SoftDeleteMixin as nn, PresetHook as nt, InferDocType as o, SlugMixin as on, ResourceCacheConfig as ot, RegistryEntry as p, AccessControlConfig as pn, RouteMcpConfig as pt, CrudController as q, AnyRecord as qt, ValidateOptions as r, TreeExt as rn, PresetResult as rt, InferResourceDoc as s, BulkExt as sn, ResourceConfig as st, RouteHandlerMethod$1 as t, SoftDeleteExt as tn, PresetFunction as tt, TypedResourceConfig as u, QueryResolver as un, ResourceHooks as ut, LookupOption as v, HookPhase as vn, FastifyHandler as vt, ServiceContext as w, afterUpdate as wn, AggMeasureShorthand as wt, PopulateOption as x, HookSystemOptions as xn, IRequestContext as xt, OwnershipCheck as y, HookRegistration as yn, IController as yt, ResourceRegistry as z, BodySanitizer as zn, PipelineStep as zt };
|