@classytic/arc 2.34.0 → 2.35.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-CbkuhNI2.mjs → BaseController-C-pofd0v.mjs} +95 -14
- package/dist/{EventTransport-tq4CEB-q.mjs → EventTransport-BrPeGUil.mjs} +1 -1
- package/dist/{HookSystem-DqXkMXkB.mjs → HookSystem-LlPm3Anm.mjs} +6 -4
- package/dist/{ResourceRegistry-DoALUlOO.mjs → ResourceRegistry-yuD1Ra53.mjs} +2 -2
- package/dist/{pipeline-q69DZzgG.mjs → actionPermissions-LFuOra7W.mjs} +30 -7
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +2 -2
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +6 -6
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-CMMLWmuZ.mjs → authorizationDecision-DYXBBgtg.mjs} +2 -2
- package/dist/{base-Csjc2oF-.d.mts → base-GZU3LlzP.d.mts} +1 -1
- package/dist/{buildHandler-BrdYrG_1.mjs → buildHandler-Do9aMIvL.mjs} +4 -4
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-BcGmvnHh.mjs → caching-cN5X9VEL.mjs} +2 -2
- package/dist/cleanup/index.d.mts +2 -2
- package/dist/cleanup/index.mjs +4 -4
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +3 -3
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -2
- package/dist/cli/commands/init.mjs +2 -2
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-BxCoxmiN.mjs → constants-BhzLXCfN.mjs} +1 -1
- package/dist/context/index.d.mts +16 -1
- package/dist/context/index.mjs +64 -4
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +7 -8
- package/dist/{core-BYKa8wHu.mjs → core-DdEvmmkY.mjs} +3 -3
- package/dist/{createActionRouter-D6T0jWhS.mjs → createActionRouter-EYWgd0ly.mjs} +27 -64
- package/dist/{createAggregationRouter-QLV2PrWP.mjs → createAggregationRouter-ByOK9Iev.mjs} +7 -7
- package/dist/defineAggregation-DoIijIFq.mjs +25 -0
- package/dist/{defineResource-ZhuLonLm.mjs → defineResource-BOrGeocE.mjs} +62 -66
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +3 -3
- package/dist/{elevation-B0tlY8y1.mjs → elevation-tGIAldco.mjs} +4 -4
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-C7xBqHsI.mjs → errorHandler-BbQ1nQdk.mjs} +3 -3
- package/dist/{errors-Cd_yy7aW.mjs → errors-CLid6atr.mjs} +1 -1
- package/dist/{eventPlugin-DSNEZWfV.d.mts → eventPlugin-B47sfCJ8.d.mts} +1 -1
- package/dist/{eventPlugin-Dar8qRaj.mjs → eventPlugin-ClPn5kjn.mjs} +5 -5
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +5 -5
- package/dist/events/transports/redis-stream-entry.mjs +1 -1
- package/dist/events/transports/redis.mjs +1 -1
- package/dist/factory/index.d.mts +5 -5
- package/dist/factory/index.mjs +5 -6
- package/dist/{fastify-augmentation-DVsTAy6X.d.mts → fastify-augmentation-CMMTL3cj.d.mts} +3 -3
- package/dist/{fields-CojeUkS4.mjs → fields-UtpiJO5R.mjs} +1 -1
- package/dist/{generate-DiB0nHPR.mjs → generate-BoAVy3_b.mjs} +2 -2
- package/dist/{headers-CveNO9gR.mjs → headers-DODWf7aC.mjs} +2 -2
- package/dist/{health-CasG2A28.mjs → health-_Zs3R_Sb.mjs} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.mjs +22 -56
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-C5NnMEKF.d.mts → index-C50BUxKs.d.mts} +1 -1
- package/dist/{index-BcR2bFZQ.d.mts → index-C5K78SXo.d.mts} +33 -105
- package/dist/{index-ugr1tQlW.d.mts → index-CJCcbADG.d.mts} +1 -1
- package/dist/{index-B-DvUzsr.d.mts → index-K7auNNW0.d.mts} +2 -2
- package/dist/{index-CFAJEWCX.d.mts → index-h-mTgMqy.d.mts} +7 -30
- package/dist/index.d.mts +5 -5
- package/dist/index.mjs +11 -12
- package/dist/{init-mdJ-v4Xp.mjs → init-Ce4R0ZGK.mjs} +1 -1
- package/dist/integrations/event-gateway.mjs +2 -2
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +2 -2
- package/dist/integrations/mcp/index.d.mts +4 -4
- package/dist/integrations/mcp/index.mjs +2 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +3 -3
- package/dist/integrations/streamline.mjs +3 -3
- package/dist/integrations/webhooks.d.mts +1 -1
- package/dist/integrations/webhooks.mjs +11 -24
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.mjs +1 -1
- package/dist/{interface-DznJrm9R.mjs → interface-my7sxjw5.mjs} +1 -1
- package/dist/{jsonBody-CfGH-GxO.mjs → jsonBody-CVKA7C5m.mjs} +2 -2
- package/dist/{keys-WeKvY1dE.mjs → keys-GlU7XpmG.mjs} +1 -1
- package/dist/{loadResources-Bj-aSsXi.mjs → loadResources-DdQb0qgV.mjs} +2 -2
- package/dist/{loadResourcesFromEntry-DwaxNcE2.mjs → loadResourcesFromEntry-s6aGs6-0.mjs} +2 -2
- package/dist/logger/index.d.mts +23 -4
- package/dist/logger/index.mjs +50 -7
- package/dist/{mcpPlugin-DX3PHD7K.mjs → mcpPlugin-DkzZ7Nfx.mjs} +15 -16
- package/dist/{memory-qYSB7rMQ.mjs → memory-BwedEXau.mjs} +2 -2
- package/dist/{metrics-D7uchLjN.mjs → metrics-5LNPvL3P.mjs} +2 -2
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +7 -4
- package/dist/{module-CwKsgZXp.mjs → module-Bh5OC7ww.mjs} +1 -1
- package/dist/{multipartBody-4w-S3-PU.mjs → multipartBody-DjilSDAT.mjs} +1 -1
- package/dist/{openapi-CkV5mQ53.mjs → openapi-ItLH12_L.mjs} +6 -6
- package/dist/outbox-admin/index.d.mts +2 -2
- package/dist/outbox-admin/index.mjs +4 -4
- package/dist/{outbox-module-D1QfHqO4.mjs → outbox-module-SGIZz6oS.mjs} +6 -4
- package/dist/{outbox-module-C1Bs_FXF.d.mts → outbox-module-b1TyDGGi.d.mts} +24 -1
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +5 -5
- package/dist/{permissions-CP47jdi7.mjs → permissions-CpSBlFXZ.mjs} +6 -6
- package/dist/{pipe-BzzoEhHj.mjs → pipe-DNOuCfAs.mjs} +2 -2
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plugins/index.d.mts +92 -7
- package/dist/plugins/index.mjs +168 -17
- package/dist/plugins/response-cache.mjs +2 -2
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-B7xf8qZF.mjs → pluralize-DGAQQKL8.mjs} +1 -1
- package/dist/{preloadResources-g07ldtzh.d.mts → preloadResources--ZtY9pe9.d.mts} +54 -270
- package/dist/{createApp-zR-DEesr.mjs → preloadResources-DYMzmUk1.mjs} +112 -31
- package/dist/presets/filesUpload.d.mts +3 -3
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +2 -2
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +3 -3
- package/dist/presets/search.d.mts +9 -47
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-BAPijK_A.mjs → presets-Bi0Z6Iq8.mjs} +4 -4
- package/dist/{projection-BKM0Ui-s.mjs → projection-aevMua7Q.mjs} +2 -2
- package/dist/{queryCachePlugin-DhsZ0ZIc.mjs → queryCachePlugin-BI7Z88Cx.mjs} +5 -5
- package/dist/{rateLimitKey-XngqVNEV.mjs → rateLimitKey-pyevMUVf.mjs} +1 -1
- package/dist/{realtime-G1TC6Yzc.mjs → realtime-Cfl2bVjj.mjs} +9 -9
- package/dist/{rateLimit-D4-ASwsI.mjs → registerRoute-u1opU66a.mjs} +114 -3
- package/dist/registry/index.d.mts +2 -2
- package/dist/registry/index.mjs +3 -3
- package/dist/{registry-CLxCR5LF.mjs → registry-B4sf9Rfm.mjs} +15 -56
- package/dist/{renewingLease-BnWtbA2U.mjs → renewingLease-5ESPMxp0.mjs} +14 -2
- package/dist/{replyHelpers-BSBVCwL3.mjs → replyHelpers-2jfmMnnV.mjs} +2 -2
- package/dist/{requestContext-uYE35JxI.mjs → requestContext-DzOUGNdY.mjs} +1 -1
- package/dist/{rolldown-runtime-DNg-KNCk.mjs → rolldown-runtime-CEvvVXTC.mjs} +1 -1
- package/dist/{runtimeCapabilities-Dm88DaTW.mjs → runtimeCapabilities-BagWc3TI.mjs} +1 -1
- package/dist/{schedules-CAcZgia0.mjs → schedules-BLpZLWGF.mjs} +6 -4
- package/dist/{schemaConverter-XH1RZRyu.mjs → schemaConverter-D2_bHGil.mjs} +19 -32
- package/dist/{schemaIR-CDvclAew.mjs → schemaIR-s3-ouZmP.mjs} +2 -2
- package/dist/schemas/index.mjs +1 -1
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.d.mts +1 -1
- package/dist/scope/index.mjs +5 -5
- package/dist/{scope-BaWcqBvr.mjs → scope-DgepeCv6.mjs} +4 -4
- package/dist/simpleEqualityMatcher-BO-sFTQf.mjs +43 -0
- package/dist/{sse-Cf0jUHsh.mjs → sse-DUIP1XH6.mjs} +5 -5
- package/dist/{sseStream-Bvpi-UXQ.mjs → sseStream-ByFGyIVr.mjs} +2 -2
- package/dist/{store-helpers-B88p99Z2.mjs → store-helpers-kIJCY0wI.mjs} +1 -1
- package/dist/{streaming-DsT1B5zJ.mjs → streaming-DOpZVht6.mjs} +1 -1
- package/dist/{subscribe-helpers-CLIOHsOK.mjs → subscribe-helpers-Dh2BnzJf.mjs} +12 -46
- package/dist/sync/changelog.mjs +1 -1
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +6 -6
- package/dist/testing/index.mjs +10 -11
- package/dist/testing/outboxStoreContract.mjs +1 -1
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/{transactionContext-BYtWnwAS.mjs → transactionContext-BTZzEcRo.mjs} +1 -1
- package/dist/{typeGuards-jjKJDN13.mjs → typeGuards-B-_jqaMi.mjs} +1 -1
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-BcTumAz_.d.mts → types-DDD5RKTu.d.mts} +30 -87
- package/dist/{types-BfC83KsP.d.mts → types-DSvHH0uR.d.mts} +1 -1
- package/dist/{types-ChuShpCP.d.mts → types-Ddl9OjmE.d.mts} +18 -121
- package/dist/{types-CoqIRrkY.mjs → types-GY4kATJb.mjs} +10 -53
- package/dist/usage/index.d.mts +2 -2
- package/dist/usage/index.mjs +0 -0
- package/dist/{userHelpers-CWF5pbZD.mjs → userHelpers-AKcB4V03.mjs} +1 -1
- package/dist/utils/index.d.mts +16 -42
- package/dist/utils/index.mjs +11 -11
- package/dist/{utils-h1V_sT-t.mjs → utils-CBK9HQOI.mjs} +3 -3
- package/dist/{validate-CdQlz52F.mjs → validate-dosAIvQU.mjs} +2 -2
- package/dist/{versioning-rZbyLVD-.mjs → versioning-BbTs4pwM.mjs} +3 -3
- package/package.json +8 -8
- package/dist/actionPermissions-CUeCmpUd.mjs +0 -25
- package/dist/defineAggregation-Nv-DwfOf.mjs +0 -48
- package/dist/preloadResources-BkyqeTDz.mjs +0 -46
- package/dist/registerRoute-Ba8v0PCa.mjs +0 -113
- package/dist/repositoryFilter-BANpz6BX.mjs +0 -78
- package/dist/simpleEqualityMatcher-Se8VrsaP.mjs +0 -69
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { p as MUTATION_OPERATIONS } from "../constants-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { t as
|
|
7
|
-
import { t as
|
|
8
|
-
import {
|
|
9
|
-
import { n as
|
|
10
|
-
import { n as
|
|
11
|
-
import { n as
|
|
12
|
-
import { n as
|
|
13
|
-
import { t as
|
|
14
|
-
import {
|
|
15
|
-
import { n as
|
|
16
|
-
import { n as
|
|
1
|
+
/*! @classytic/arc v2.35.0 | MIT | https://classytic.com */
|
|
2
|
+
import { p as MUTATION_OPERATIONS } from "../constants-BhzLXCfN.mjs";
|
|
3
|
+
import { arcLog } from "../logger/index.mjs";
|
|
4
|
+
import { c as getOrgId } from "../types-GY4kATJb.mjs";
|
|
5
|
+
import { n as requestContext } from "../requestContext-DzOUGNdY.mjs";
|
|
6
|
+
import { t as hasEvents } from "../typeGuards-B-_jqaMi.mjs";
|
|
7
|
+
import { t as HookSystem } from "../HookSystem-LlPm3Anm.mjs";
|
|
8
|
+
import { t as ResourceRegistry } from "../ResourceRegistry-yuD1Ra53.mjs";
|
|
9
|
+
import { a as requestIdPlugin, i as createRequestIdGenerator, n as health_default, o as requestId_default, s as sanitizeRequestId, t as healthPlugin } from "../health-_Zs3R_Sb.mjs";
|
|
10
|
+
import { n as caching_default, t as cachingPlugin } from "../caching-cN5X9VEL.mjs";
|
|
11
|
+
import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-BbQ1nQdk.mjs";
|
|
12
|
+
import { n as metrics_default, t as metricsPlugin } from "../metrics-5LNPvL3P.mjs";
|
|
13
|
+
import { n as realtime_default, t as realtimePlugin } from "../realtime-Cfl2bVjj.mjs";
|
|
14
|
+
import { t as replyHelpersPlugin } from "../replyHelpers-2jfmMnnV.mjs";
|
|
15
|
+
import { n as schedules_default, t as schedulesPlugin } from "../schedules-BLpZLWGF.mjs";
|
|
16
|
+
import { n as sse_default, t as ssePlugin } from "../sse-DUIP1XH6.mjs";
|
|
17
|
+
import { n as versioning_default, t as versioningPlugin } from "../versioning-BbTs4pwM.mjs";
|
|
17
18
|
import fp from "fastify-plugin";
|
|
19
|
+
import { performance as performance$1 } from "node:perf_hooks";
|
|
18
20
|
//#region src/core/arcCorePlugin.ts
|
|
19
21
|
const arcCorePlugin = async (fastify, opts = {}) => {
|
|
20
22
|
const { emitEvents = true, hookSystem, registry } = opts;
|
|
@@ -205,4 +207,153 @@ var gracefulShutdown_default = fp(gracefulShutdownPlugin, {
|
|
|
205
207
|
fastify: "5.x"
|
|
206
208
|
});
|
|
207
209
|
//#endregion
|
|
208
|
-
|
|
210
|
+
//#region src/plugins/pressure.ts
|
|
211
|
+
/**
|
|
212
|
+
* Admission control — ONE place that decides "are we saturated?".
|
|
213
|
+
*
|
|
214
|
+
* Arc already sheds on event-loop lag (`@fastify/under-pressure`), but lag is
|
|
215
|
+
* LATE: by the time the loop is behind, the work causing it is already
|
|
216
|
+
* admitted. What actually saturates a backend — a drained pool, an undrained
|
|
217
|
+
* queue, an outbox falling behind — is invisible to it. Rate limiting is a
|
|
218
|
+
* different axis: it bounds request FREQUENCY, this bounds concurrent
|
|
219
|
+
* EXPENSIVE WORK.
|
|
220
|
+
*
|
|
221
|
+
* Signals are REGISTERED, not enumerated, because only ELU has a source arc
|
|
222
|
+
* can read today — no kit exposes pool stats, no queue exposes depth. Arc owns
|
|
223
|
+
* the DECISION; signals come from whoever can produce them.
|
|
224
|
+
*
|
|
225
|
+
* `read()` returns SATURATION in [0,1]. Each signal normalises itself: 90% of
|
|
226
|
+
* a pool is nearly fatal, 90% ELU is merely busy, and only the signal's author
|
|
227
|
+
* knows which.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```typescript
|
|
231
|
+
* await app.register(pressurePlugin, {
|
|
232
|
+
* signals: [{ name: 'db-pool', read: () => pool.borrowed / pool.size }],
|
|
233
|
+
* shed: true,
|
|
234
|
+
* });
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
const log = arcLog("pressure");
|
|
238
|
+
/**
|
|
239
|
+
* Event-loop utilisation as saturation — already a [0,1] ratio, and measured
|
|
240
|
+
* ACROSS the interval rather than instantaneously: one lag reading is a spike,
|
|
241
|
+
* utilisation over a second is load.
|
|
242
|
+
*/
|
|
243
|
+
function eluSignal() {
|
|
244
|
+
let previous = performance$1.eventLoopUtilization();
|
|
245
|
+
return {
|
|
246
|
+
name: "event-loop",
|
|
247
|
+
read() {
|
|
248
|
+
const current = performance$1.eventLoopUtilization();
|
|
249
|
+
const delta = performance$1.eventLoopUtilization(current, previous);
|
|
250
|
+
previous = current;
|
|
251
|
+
return delta.utilization;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const clamp = (n) => Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : 0;
|
|
256
|
+
const pressurePlugin = async (fastify, options) => {
|
|
257
|
+
const degradedAt = options.thresholds?.degraded ?? .8;
|
|
258
|
+
const saturatedAt = options.thresholds?.saturated ?? .95;
|
|
259
|
+
if (degradedAt > saturatedAt) throw new Error(`pressurePlugin: thresholds.degraded (${degradedAt}) is above thresholds.saturated (${saturatedAt}) — 'saturated' would be unreachable and the app would never shed.`);
|
|
260
|
+
const signals = [...options.eventLoopUtilization === false ? [] : [eluSignal()], ...options.signals ?? []];
|
|
261
|
+
/** Signals that threw. Logged once each — a broken reader must not spam. */
|
|
262
|
+
const broken = /* @__PURE__ */ new Set();
|
|
263
|
+
let snapshot = {
|
|
264
|
+
state: "ok",
|
|
265
|
+
worst: void 0,
|
|
266
|
+
signals: {}
|
|
267
|
+
};
|
|
268
|
+
async function sample() {
|
|
269
|
+
const values = {};
|
|
270
|
+
let worst;
|
|
271
|
+
for (const signal of signals) {
|
|
272
|
+
let value = 0;
|
|
273
|
+
try {
|
|
274
|
+
value = clamp(await signal.read());
|
|
275
|
+
broken.delete(signal.name);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
if (!broken.has(signal.name)) {
|
|
278
|
+
broken.add(signal.name);
|
|
279
|
+
log.warn("pressure signal failed to read; treating as 0", {
|
|
280
|
+
signal: signal.name,
|
|
281
|
+
error
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
values[signal.name] = value;
|
|
286
|
+
if (!worst || value > worst.value) worst = {
|
|
287
|
+
name: signal.name,
|
|
288
|
+
value
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const peak = worst?.value ?? 0;
|
|
292
|
+
const next = peak >= saturatedAt ? "saturated" : peak >= degradedAt ? "degraded" : "ok";
|
|
293
|
+
const previous = snapshot.state;
|
|
294
|
+
snapshot = {
|
|
295
|
+
state: next,
|
|
296
|
+
worst,
|
|
297
|
+
signals: values
|
|
298
|
+
};
|
|
299
|
+
if (next !== previous) {
|
|
300
|
+
log.info("pressure state changed", {
|
|
301
|
+
from: previous,
|
|
302
|
+
to: next,
|
|
303
|
+
worst
|
|
304
|
+
});
|
|
305
|
+
options.onStateChange?.(next, snapshot);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const timer = setInterval(() => {
|
|
309
|
+
sample();
|
|
310
|
+
}, options.intervalMs ?? 1e3);
|
|
311
|
+
timer.unref?.();
|
|
312
|
+
fastify.addHook("onClose", async () => clearInterval(timer));
|
|
313
|
+
fastify.decorate("pressure", {
|
|
314
|
+
state: () => snapshot.state,
|
|
315
|
+
snapshot: () => snapshot,
|
|
316
|
+
register: (signal) => {
|
|
317
|
+
signals.push(signal);
|
|
318
|
+
},
|
|
319
|
+
shouldShed: () => snapshot.state === "saturated",
|
|
320
|
+
readinessCheck: (checkOptions) => ({
|
|
321
|
+
name: checkOptions?.name ?? "pressure",
|
|
322
|
+
critical: checkOptions?.critical ?? true,
|
|
323
|
+
check: () => snapshot.state !== "saturated"
|
|
324
|
+
})
|
|
325
|
+
});
|
|
326
|
+
if (options.shed) {
|
|
327
|
+
const shedConfig = options.shed === true ? {} : options.shed;
|
|
328
|
+
const statusCode = shedConfig.statusCode ?? 503;
|
|
329
|
+
const retryAfter = shedConfig.retryAfterSeconds ?? 1;
|
|
330
|
+
const exempt = [
|
|
331
|
+
"/_health",
|
|
332
|
+
"/health",
|
|
333
|
+
...shedConfig.exclude ?? []
|
|
334
|
+
];
|
|
335
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
336
|
+
if (snapshot.state !== "saturated") return;
|
|
337
|
+
const url = request.url;
|
|
338
|
+
if (exempt.some((prefix) => url.startsWith(prefix))) return;
|
|
339
|
+
reply.header("Retry-After", String(retryAfter));
|
|
340
|
+
return reply.code(statusCode).send({
|
|
341
|
+
code: "arc.unavailable",
|
|
342
|
+
message: "Server is at capacity — retry shortly.",
|
|
343
|
+
status: statusCode
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
await sample();
|
|
348
|
+
};
|
|
349
|
+
/**
|
|
350
|
+
* `fp()`-wrapped so `fastify.pressure` is visible to the whole app rather than
|
|
351
|
+
* trapped in this plugin's encapsulation context — the named export above is
|
|
352
|
+
* the raw plugin, matching every other arc plugin entry.
|
|
353
|
+
*/
|
|
354
|
+
var pressure_default = fp(pressurePlugin, {
|
|
355
|
+
name: "arc-pressure",
|
|
356
|
+
fastify: "5.x"
|
|
357
|
+
});
|
|
358
|
+
//#endregion
|
|
359
|
+
export { arcCorePlugin_default as arcCorePlugin, arcCorePlugin as arcCorePluginFn, caching_default as cachingPlugin, cachingPlugin as cachingPluginFn, createPlugin, createRequestIdGenerator, defaultIsDuplicateKeyError, errorHandlerPlugin, errorHandlerPlugin as errorHandlerPluginFn, gracefulShutdown_default as gracefulShutdownPlugin, gracefulShutdownPlugin as gracefulShutdownPluginFn, health_default as healthPlugin, healthPlugin as healthPluginFn, metrics_default as metricsPlugin, metricsPlugin as metricsPluginFn, pressure_default as pressurePlugin, pressurePlugin as pressurePluginFn, realtime_default as realtimePlugin, realtimePlugin as realtimePluginFn, replyHelpersPlugin, requestId_default as requestIdPlugin, requestIdPlugin as requestIdPluginFn, sanitizeRequestId, schedules_default as schedulesPlugin, schedulesPlugin as schedulesPluginFn, sse_default as ssePlugin, ssePlugin as ssePluginFn, versioning_default as versioningPlugin, versioningPlugin as versioningPluginFn };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as hasEvents } from "../typeGuards-
|
|
1
|
+
/*! @classytic/arc v2.35.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as hasEvents } from "../typeGuards-B-_jqaMi.mjs";
|
|
3
3
|
import fp from "fastify-plugin";
|
|
4
4
|
//#region src/plugins/response-cache.ts
|
|
5
5
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.35.0 | MIT | https://classytic.com */
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import fp from "fastify-plugin";
|
|
4
4
|
//#region src/plugins/tracing.ts
|
|
@@ -59,7 +59,7 @@ try {
|
|
|
59
59
|
function createTracerProvider(options) {
|
|
60
60
|
if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
|
|
61
61
|
const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
|
|
62
|
-
const resolvedVersion = serviceVersion ?? "2.
|
|
62
|
+
const resolvedVersion = serviceVersion ?? "2.35.0";
|
|
63
63
|
const exporter = new OTLPTraceExporter({ url: exporterUrl });
|
|
64
64
|
const provider = new NodeTracerProvider({ resource: { attributes: {
|
|
65
65
|
"service.name": serviceName,
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
|
|
2
|
-
import { It as Authenticator } from "./index-
|
|
2
|
+
import { It as Authenticator } from "./index-C5K78SXo.mjs";
|
|
3
3
|
import { r as CacheStore } from "./interface-BOk3YnTz.mjs";
|
|
4
4
|
import { n as ElevationOptions } from "./elevation-BXCfVcNG.mjs";
|
|
5
5
|
import { a as EventTransport } from "./EventTransport-BEVP50ka.mjs";
|
|
6
6
|
import { t as ExternalOpenApiPaths } from "./externalPaths-CmGOxOtD.mjs";
|
|
7
7
|
import { r as QueryCachePluginOptions } from "./queryCachePlugin-BzqxconG.mjs";
|
|
8
8
|
import { t as ErrorHandlerOptions } from "./errorHandler-DI5AN2b5.mjs";
|
|
9
|
-
import { l as ResourceLike, m as SchedulesPluginOptions, n as ArcModuleInput, v as HealthOptions } from "./types-
|
|
10
|
-
import "./index-
|
|
11
|
-
import { t as EventPluginOptions } from "./eventPlugin-
|
|
9
|
+
import { l as ResourceLike, m as SchedulesPluginOptions, n as ArcModuleInput, v as HealthOptions } from "./types-DDD5RKTu.mjs";
|
|
10
|
+
import "./index-C50BUxKs.mjs";
|
|
11
|
+
import { t as EventPluginOptions } from "./eventPlugin-B47sfCJ8.mjs";
|
|
12
12
|
import { g as CachingOptions, o as MetricsOptions, p as SSEOptions, t as VersioningOptions, u as RealtimeOptions } from "./versioning-DEs-_hFi.mjs";
|
|
13
13
|
import { r as IdempotencyStore } from "./interface-DEWNOMza.mjs";
|
|
14
14
|
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify";
|
|
@@ -331,7 +331,7 @@ interface RawBodyOptions {
|
|
|
331
331
|
//#region src/factory/types/security.d.ts
|
|
332
332
|
/**
|
|
333
333
|
* Security-plugin option shapes — CORS / Helmet / rate-limit config for
|
|
334
|
-
* `createApp()`, including plan-aware rate limiting
|
|
334
|
+
* `createApp()`, including plan-aware rate limiting.
|
|
335
335
|
*/
|
|
336
336
|
type CorsOptions = Record<string, unknown> & {
|
|
337
337
|
origin?: unknown;
|
|
@@ -400,43 +400,14 @@ interface RateLimitPlanConfig {
|
|
|
400
400
|
//#endregion
|
|
401
401
|
//#region src/factory/types/app-options.d.ts
|
|
402
402
|
/**
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
* Configuration for creating an Arc application.
|
|
403
|
+
* Configuration for `createApp()`.
|
|
406
404
|
*
|
|
407
405
|
* @example
|
|
408
406
|
* ```typescript
|
|
409
|
-
* // Minimal setup
|
|
410
|
-
* const app = await createApp({
|
|
411
|
-
* preset: 'development',
|
|
412
|
-
* auth: {
|
|
413
|
-
* type: 'jwt',
|
|
414
|
-
* jwt: { secret: process.env.JWT_SECRET },
|
|
415
|
-
* },
|
|
416
|
-
* });
|
|
417
|
-
*
|
|
418
|
-
* // With custom authenticator
|
|
419
407
|
* const app = await createApp({
|
|
420
408
|
* preset: 'production',
|
|
421
|
-
* auth: {
|
|
422
|
-
*
|
|
423
|
-
* jwt: { secret: process.env.JWT_SECRET },
|
|
424
|
-
* authenticate: async (request, { jwt }) => {
|
|
425
|
-
* // Check API key first
|
|
426
|
-
* const apiKey = request.headers['x-api-key'];
|
|
427
|
-
* if (apiKey) {
|
|
428
|
-
* const result = await apiKeyService.verify(apiKey);
|
|
429
|
-
* if (result) return { _id: result.userId, isApiKey: true };
|
|
430
|
-
* }
|
|
431
|
-
* // Then check JWT
|
|
432
|
-
* const token = request.headers.authorization?.split(' ')[1];
|
|
433
|
-
* if (token) {
|
|
434
|
-
* const decoded = jwt.verify(token);
|
|
435
|
-
* return userRepo.findById(decoded.id);
|
|
436
|
-
* }
|
|
437
|
-
* return null;
|
|
438
|
-
* },
|
|
439
|
-
* },
|
|
409
|
+
* auth: { type: 'jwt', jwt: { secret: process.env.JWT_SECRET } },
|
|
410
|
+
* resources: [productResource],
|
|
440
411
|
* });
|
|
441
412
|
* ```
|
|
442
413
|
*/
|
|
@@ -540,13 +511,20 @@ interface CreateAppOptions {
|
|
|
540
511
|
* proxy's address; a boot warning fires when the production preset's
|
|
541
512
|
* default is inherited without an explicit choice.
|
|
542
513
|
*
|
|
514
|
+
* ⚠ **A hop COUNT is no longer accepted.** Fastify 5.12.1 removed `number`
|
|
515
|
+
* from this option and made hop-count trust fail closed, because it cannot
|
|
516
|
+
* validate the immediate peer — a direct client could spoof `X-Forwarded-*`
|
|
517
|
+
* simply by supplying enough hops. Name the proxies instead; arc warns at
|
|
518
|
+
* boot if a number reaches it from untyped (JavaScript) config.
|
|
519
|
+
*
|
|
543
520
|
* @example
|
|
544
521
|
* ```ts
|
|
545
|
-
* createApp({ preset: 'production', trustProxy:
|
|
546
|
-
* createApp({ preset: 'production', trustProxy: '10.0.0.0/8' });
|
|
522
|
+
* createApp({ preset: 'production', trustProxy: 'loopback' }); // local proxy
|
|
523
|
+
* createApp({ preset: 'production', trustProxy: '10.0.0.0/8' }); // VPC proxies
|
|
524
|
+
* createApp({ preset: 'production', trustProxy: true }); // every hop trusted
|
|
547
525
|
* ```
|
|
548
526
|
*/
|
|
549
|
-
trustProxy?: boolean | string | string[]
|
|
527
|
+
trustProxy?: boolean | string | string[];
|
|
550
528
|
/** Fastify plugin/onReady timeout in ms (default: 10_000). Raise for slow boot work (index materialisation, WAL replay, external warm-up). */
|
|
551
529
|
pluginTimeout?: number;
|
|
552
530
|
/**
|
|
@@ -559,102 +537,33 @@ interface CreateAppOptions {
|
|
|
559
537
|
*/
|
|
560
538
|
requestTimeout?: number;
|
|
561
539
|
/**
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
*
|
|
565
|
-
* ~60s still bounds idle pre-request sockets).
|
|
540
|
+
* Connection-establishment timeout in ms (Fastify `connectionTimeout`).
|
|
541
|
+
* Default `0` = unbounded; Node's ~60s `headersTimeout` still caps idle
|
|
542
|
+
* pre-request sockets.
|
|
566
543
|
*/
|
|
567
544
|
connectionTimeout?: number;
|
|
568
545
|
/**
|
|
569
|
-
* Keep-alive idle timeout in ms (
|
|
570
|
-
*
|
|
571
|
-
*
|
|
572
|
-
* sockets the server just closed → intermittent 502s.
|
|
546
|
+
* Keep-alive idle timeout in ms (default 72_000). MUST exceed the idle
|
|
547
|
+
* timeout of any LB in front (ALB default 60s), or the LB reuses sockets
|
|
548
|
+
* the server just closed — intermittent 502s.
|
|
573
549
|
*/
|
|
574
550
|
keepAliveTimeout?: number;
|
|
575
551
|
/**
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
* / JSON-RPC batch endpoints — without this, Fastify rejects oversized
|
|
580
|
-
* payloads with `FST_ERR_CTP_BODY_TOO_LARGE` (413) before any route
|
|
581
|
-
* handler runs. File uploads on `multipart` routes are governed
|
|
582
|
-
* separately by `multipart.limits.fileSize`. (2.15.1)
|
|
552
|
+
* Max JSON body in bytes. Fastify default 1 MiB; over it is a 413
|
|
553
|
+
* (`FST_ERR_CTP_BODY_TOO_LARGE`) raised BEFORE any handler runs.
|
|
554
|
+
* Multipart uploads are bounded separately by `multipart.limits.fileSize`.
|
|
583
555
|
*/
|
|
584
556
|
bodyLimit?: number;
|
|
585
557
|
/**
|
|
586
|
-
*
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
* token URLs: HMAC tracking tokens (~250 chars), JWT-in-URL, OAuth state
|
|
590
|
-
* parameters, password-reset / magic-link tokens, etc. Arc's default
|
|
591
|
-
* is **400** to match the everything-is-a-signed-token reality of
|
|
592
|
-
* production apps; hosts that need longer params can raise it further
|
|
593
|
-
* (`1024` for very long JWTs).
|
|
594
|
-
*
|
|
595
|
-
* Pass-through to Fastify's `maxParamLength` constructor option — see
|
|
596
|
-
* https://fastify.dev/docs/latest/Reference/Server/#maxparamlength.
|
|
597
|
-
*
|
|
598
|
-
* @example
|
|
599
|
-
* ```ts
|
|
600
|
-
* // App expects long HMAC tokens in the URL path
|
|
601
|
-
* createApp({ preset: 'production', maxParamLength: 1024 });
|
|
602
|
-
*
|
|
603
|
-
* // Constrain tighter than arc's default for audit reasons
|
|
604
|
-
* createApp({ preset: 'production', maxParamLength: 256 });
|
|
605
|
-
* ```
|
|
558
|
+
* Max characters in one URL path param. Arc defaults to 400; Fastify's own
|
|
559
|
+
* 100 silently 404s signed-token URLs (HMAC ~250 chars, JWT, OAuth state).
|
|
560
|
+
* Raise to ~1024 for long JWTs.
|
|
606
561
|
*/
|
|
607
562
|
maxParamLength?: number;
|
|
608
563
|
/**
|
|
609
|
-
* Auth configuration
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
-
* Each auth strategy requires a `type` discriminant field.
|
|
613
|
-
*
|
|
614
|
-
* @example
|
|
615
|
-
* ```typescript
|
|
616
|
-
* // Disable auth
|
|
617
|
-
* auth: false,
|
|
618
|
-
*
|
|
619
|
-
* // Arc JWT
|
|
620
|
-
* auth: {
|
|
621
|
-
* type: 'jwt',
|
|
622
|
-
* jwt: { secret: process.env.JWT_SECRET },
|
|
623
|
-
* },
|
|
624
|
-
*
|
|
625
|
-
* // Arc JWT + custom authenticator
|
|
626
|
-
* auth: {
|
|
627
|
-
* type: 'jwt',
|
|
628
|
-
* jwt: { secret: process.env.JWT_SECRET },
|
|
629
|
-
* authenticate: async (request, { jwt }) => {
|
|
630
|
-
* const token = request.headers.authorization?.split(' ')[1];
|
|
631
|
-
* if (!token) return null;
|
|
632
|
-
* const decoded = jwt.verify(token);
|
|
633
|
-
* return userRepo.findById(decoded.id);
|
|
634
|
-
* },
|
|
635
|
-
* },
|
|
636
|
-
*
|
|
637
|
-
* // Better Auth adapter
|
|
638
|
-
* auth: { type: 'betterAuth', betterAuth: createBetterAuthAdapter({ auth: myBetterAuth }) },
|
|
639
|
-
*
|
|
640
|
-
* // Custom auth plugin
|
|
641
|
-
* auth: {
|
|
642
|
-
* type: 'custom',
|
|
643
|
-
* plugin: async (fastify) => {
|
|
644
|
-
* fastify.decorate('authenticate', async (req, reply) => { ... });
|
|
645
|
-
* },
|
|
646
|
-
* },
|
|
647
|
-
*
|
|
648
|
-
* // Custom authenticator function
|
|
649
|
-
* auth: {
|
|
650
|
-
* type: 'authenticator',
|
|
651
|
-
* authenticate: async (request, reply) => {
|
|
652
|
-
* const session = await validateSession(request);
|
|
653
|
-
* if (!session) reply.code(401).send({ error: 'Unauthorized' });
|
|
654
|
-
* request.user = session.user;
|
|
655
|
-
* },
|
|
656
|
-
* },
|
|
657
|
-
* ```
|
|
564
|
+
* Auth configuration, or `false` to disable entirely. Each strategy is a
|
|
565
|
+
* discriminated union on `type` (`'jwt'` | `'better-auth'` | custom); see
|
|
566
|
+
* [[auth]] for the strategy matrix and `authenticate` override.
|
|
658
567
|
*/
|
|
659
568
|
auth?: AuthOption;
|
|
660
569
|
/**
|
|
@@ -886,162 +795,37 @@ interface CreateAppOptions {
|
|
|
886
795
|
*/
|
|
887
796
|
replyHelpers?: boolean;
|
|
888
797
|
/**
|
|
889
|
-
* Serialize `bigint`
|
|
890
|
-
*
|
|
891
|
-
* `JSON.stringify` throws on `bigint` by default. This option installs a
|
|
892
|
-
* `preSerialization` hook that converts them on the way out:
|
|
893
|
-
*
|
|
894
|
-
* - `false` (default) — no conversion. JSON serialization throws if a
|
|
895
|
-
* `bigint` reaches the wire. Safest when no codepath produces bigints.
|
|
896
|
-
* - `'string'` — **recommended for IDs / money / counters / ledgers.**
|
|
897
|
-
* Converts every `bigint` to a decimal string. Lossless: every digit
|
|
898
|
-
* survives the wire. Clients parse with `BigInt(value)` to reconstitute.
|
|
899
|
-
* - `'number'` — converts every `bigint` to `Number(value)`.
|
|
900
|
-
* **Lossy above 2^53 - 1 (`Number.MAX_SAFE_INTEGER` = 9007199254740991).**
|
|
901
|
-
* Use ONLY when you've audited the value range — e.g. small enums,
|
|
902
|
-
* bounded counters that physically can't exceed the safe range. For
|
|
903
|
-
* anything that could be an ID, monetary amount, or unbounded counter,
|
|
904
|
-
* `Number()` corruption silently rounds digits and there is no
|
|
905
|
-
* recovery. arc emits a one-shot startup warning when you opt into
|
|
906
|
-
* this mode.
|
|
907
|
-
* - `true` — back-compat alias for `'number'`. Will be removed in a
|
|
908
|
-
* future major. Migrate to `'string'` (lossless) or explicit
|
|
909
|
-
* `'number'` (acknowledges the precision risk).
|
|
910
|
-
*
|
|
911
|
-
* Default: `false` (opt-in — most apps don't use bigint).
|
|
798
|
+
* Serialize `bigint` in JSON responses — `JSON.stringify` throws on it.
|
|
912
799
|
*
|
|
913
|
-
*
|
|
914
|
-
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
*
|
|
918
|
-
* });
|
|
919
|
-
* // { totalSatoshis: "9007199254740999" }
|
|
920
|
-
*
|
|
921
|
-
* // Lossy — only when value range is bounded
|
|
922
|
-
* const app = await createApp({
|
|
923
|
-
* serializeBigInt: 'number',
|
|
924
|
-
* });
|
|
925
|
-
* // { totalSatoshis: 9007199254740999 } // precision lost above MAX_SAFE_INTEGER
|
|
926
|
-
* });
|
|
927
|
-
* ```
|
|
800
|
+
* - `false` (default) — no conversion; a bigint reaching the wire throws.
|
|
801
|
+
* - `'string'` — lossless decimal string. The choice for IDs, money,
|
|
802
|
+
* counters, ledgers. Clients reconstitute with `BigInt(value)`.
|
|
803
|
+
* - `'number'` — LOSSY above `Number.MAX_SAFE_INTEGER` (2^53-1). Only for
|
|
804
|
+
* an audited, bounded range.
|
|
928
805
|
*/
|
|
929
806
|
serializeBigInt?: boolean | "number" | "string";
|
|
930
807
|
/**
|
|
931
|
-
* Domain modules —
|
|
932
|
-
*
|
|
933
|
-
* Where a `resources` entry is a single route group, a module is a self-
|
|
934
|
-
* contained domain package's full contribution (engine init + resources +
|
|
935
|
-
* post-wiring) bundled as one `ArcModule`. A package exports
|
|
936
|
-
* `createXModule(deps): ArcModule`; the host lists it here instead of hand-
|
|
937
|
-
* threading the package's pieces across `bootstrap` / `resources` /
|
|
938
|
-
* `afterResources`.
|
|
939
|
-
*
|
|
940
|
-
* Modules are pure sugar over the existing lifecycle — each expands into the
|
|
941
|
-
* SAME phases, in `dependsOn` order, running BEFORE the app-level entry in
|
|
942
|
-
* every phase:
|
|
943
|
-
* ```
|
|
944
|
-
* app plugins() → modules[].plugins (infra registration)
|
|
945
|
-
* modules[].bootstrap → options.bootstrap
|
|
946
|
-
* modules[].resources → options.resources / resourceDir (see note ↓)
|
|
947
|
-
* modules[].afterResources → options.afterResources
|
|
948
|
-
* modules[].onClose (REVERSE order) → options.onClose (one hook; see below)
|
|
949
|
-
* ```
|
|
950
|
-
* RESOURCES — two orderings, both intentional: app `resources`/`resourceDir`
|
|
951
|
-
* RESOLVE first (semantics untouched), but module resources REGISTER first
|
|
952
|
-
* (prepended, so their routes mount ahead of app resources). Teardown runs
|
|
953
|
-
* module `onClose` (reverse composition order) then app `onClose` in a single
|
|
954
|
-
* close hook, so module teardown (flush outboxes, drain queues, close a
|
|
955
|
-
* module-owned connection) sees live shared infra — the app/plugin
|
|
956
|
-
* connections close last. Module resources flow through arc's normal
|
|
957
|
-
* registration (prefix, dedup, docs, audit) — they are not special-cased.
|
|
958
|
-
*
|
|
959
|
-
* Entries may be eager modules, promises, or **thunks of dynamic imports**
|
|
960
|
-
* (the `next/dynamic` idea, backend-shaped) — thunks resolve once at boot,
|
|
961
|
-
* so region/tier-gated packs are only imported when selected:
|
|
962
|
-
*
|
|
963
|
-
* @example
|
|
964
|
-
* ```ts
|
|
965
|
-
* const taxPack = region === 'BD'
|
|
966
|
-
* ? () => import('@classytic/bd-tax/module').then((m) => m.createBdTaxModule(deps))
|
|
967
|
-
* : () => import('@classytic/us-tax/module').then((m) => m.createUsTaxModule(deps));
|
|
968
|
-
*
|
|
969
|
-
* const app = await createApp({
|
|
970
|
-
* resourcePrefix: '/api/v1',
|
|
971
|
-
* modules: [accountingModule({ permissions }), taxPack],
|
|
972
|
-
* resources: [healthResource], // app-local resources still compose alongside
|
|
973
|
-
* });
|
|
974
|
-
* ```
|
|
808
|
+
* Domain modules — one entry per self-contained domain package.
|
|
975
809
|
*
|
|
976
|
-
*
|
|
977
|
-
*
|
|
810
|
+
* A `resources` entry is a route group; an `ArcModule` is a package's whole
|
|
811
|
+
* contribution (engine init + resources + post-wiring), so the host stops
|
|
812
|
+
* hand-threading pieces across `bootstrap` / `resources` / `afterResources`.
|
|
813
|
+
* Modules expand into the SAME lifecycle phases in `dependsOn` order,
|
|
814
|
+
* always before the app-level entry for that phase. See [[modules]].
|
|
978
815
|
*/
|
|
979
816
|
modules?: ReadonlyArray<ArcModuleInput>;
|
|
980
817
|
/**
|
|
981
|
-
* Resources to register
|
|
818
|
+
* Resources to register. An ARRAY is resolved at module import, so every
|
|
819
|
+
* adapter must be constructible synchronously. A FACTORY (sync or async,
|
|
820
|
+
* receiving the Fastify instance) is resolved after `bootstrap[]` and before
|
|
821
|
+
* routes are wired — the answer when an adapter depends on an engine that
|
|
822
|
+
* boots asynchronously, since `defineResource()` then runs with that engine
|
|
823
|
+
* already live. Boot order lives in CLAUDE.md; do not restate it here.
|
|
982
824
|
*
|
|
983
|
-
*
|
|
984
|
-
* Defined at module-import time, so the resource's adapter must be
|
|
985
|
-
* constructible without any async state.
|
|
986
|
-
*
|
|
987
|
-
* 2. **Factory function** (sync or async) — called AFTER `bootstrap[]`
|
|
988
|
-
* but BEFORE routes are wired. Use this when a resource's adapter
|
|
989
|
-
* depends on an engine / singleton that boots asynchronously
|
|
990
|
-
* (e.g. `await ensureCatalogEngine()` / `await createFlowEngine()`).
|
|
991
|
-
* The factory receives the Fastify instance for symmetry with
|
|
992
|
-
* `plugins` and `bootstrap`.
|
|
993
|
-
*
|
|
994
|
-
* Arc's lifecycle contract:
|
|
995
|
-
* ```
|
|
996
|
-
* 1. Arc core (security, auth, events)
|
|
997
|
-
* 2. plugins() ← infra (DB, SSE, data)
|
|
998
|
-
* 3. bootstrap[] ← domain init (engines, singletons)
|
|
999
|
-
* 4. resources resolution ← (factory form: call it here)
|
|
1000
|
-
* 5. resources registered ← plugins mounted on Fastify
|
|
1001
|
-
* 6. afterResources() ← post-registration wiring
|
|
1002
|
-
* ```
|
|
1003
|
-
*
|
|
1004
|
-
* The factory form is the canonical answer to "my repository lives in an
|
|
1005
|
-
* engine that boots asynchronously." Before this shape existed, hosts had
|
|
1006
|
-
* to write per-resource lazy-bridge adapters that awaited the engine on
|
|
1007
|
-
* every CRUD call — pure boilerplate. With a factory, `defineResource(...)`
|
|
1008
|
-
* runs with the engine already live, so `createMongooseAdapter(engine.models.X, engine.repositories.X)`
|
|
1009
|
-
* works directly.
|
|
1010
|
-
*
|
|
1011
|
-
* @example Static array (most resources)
|
|
1012
|
-
* ```ts
|
|
1013
|
-
* const app = await createApp({
|
|
1014
|
-
* resources: [productResource, orderResource, userResource],
|
|
1015
|
-
* auth: { type: 'jwt', jwt: { secret: 'xxx' } },
|
|
1016
|
-
* });
|
|
1017
|
-
* ```
|
|
1018
|
-
*
|
|
1019
|
-
* @example Factory with async-booted engine
|
|
1020
|
-
* ```ts
|
|
1021
|
-
* import { createApp, defineResource } from '@classytic/arc';
|
|
1022
|
-
* import { createMongooseAdapter } from '@classytic/mongokit/adapter';
|
|
1023
|
-
*
|
|
1024
|
-
* const app = await createApp({
|
|
1025
|
-
* bootstrap: [async () => { await ensureCatalogEngine(); }],
|
|
1026
|
-
* resources: async () => {
|
|
1027
|
-
* const cat = await ensureCatalogEngine();
|
|
1028
|
-
* return [
|
|
1029
|
-
* defineResource({
|
|
1030
|
-
* name: 'product',
|
|
1031
|
-
* adapter: createMongooseAdapter(cat.models.Product, cat.repositories.product),
|
|
1032
|
-
* // ...
|
|
1033
|
-
* }),
|
|
1034
|
-
* ];
|
|
1035
|
-
* },
|
|
1036
|
-
* });
|
|
1037
|
-
* ```
|
|
1038
|
-
*
|
|
1039
|
-
* @example Factory delegating to auto-discovery
|
|
825
|
+
* @example
|
|
1040
826
|
* ```ts
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
* resources: async () => loadResources(import.meta.url),
|
|
1044
|
-
* });
|
|
827
|
+
* resources: [productResource] // static
|
|
828
|
+
* resources: async () => [defineResource({ ... })] // engine-dependent
|
|
1045
829
|
* ```
|
|
1046
830
|
*/
|
|
1047
831
|
resources?: ReadonlyArray<ResourceLike> | ((fastify: FastifyInstance) => ReadonlyArray<ResourceLike> | Promise<ReadonlyArray<ResourceLike>>);
|