@classytic/arc 2.23.0 → 2.28.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 +2 -0
- package/dist/{BaseController-BQ8KNvWC.mjs → BaseController-B65zbz9m.mjs} +94 -23
- package/dist/QueryCache-DOMT_iq7.d.mts +72 -0
- package/dist/actionPermissions-CAphcmUG.mjs +24 -0
- package/dist/{applyPermissionResult-Tw4iXtlf.mjs → applyPermissionResult-BnyVE9eD.mjs} +10 -0
- package/dist/audit/index.d.mts +32 -3
- package/dist/audit/index.mjs +7 -4
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +263 -259
- package/dist/{buildHandler-D_2ULWxZ.mjs → buildHandler-Dtwd8fdz.mjs} +1 -1
- package/dist/cache/index.d.mts +41 -3
- package/dist/cache/index.mjs +33 -4
- package/dist/cleanup/index.d.mts +313 -0
- package/dist/cleanup/index.mjs +1267 -0
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/generate.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/index.mjs +2 -2
- package/dist/context/index.mjs +2 -64
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -4
- package/dist/core-BGa8TXJR.mjs +221 -0
- package/dist/{createActionRouter-BuOv-m4Y.mjs → createActionRouter-C-N7z2EL.mjs} +3 -2
- package/dist/{createAggregationRouter-CgJv_BU_.mjs → createAggregationRouter-wzwFa_dW.mjs} +3 -3
- package/dist/{createApp-CjOeWJ9_.mjs → createApp-Du5G54TM.mjs} +187 -96
- package/dist/{core-DQ6rDTyH.mjs → defineResource-2zvVVpxg.mjs} +297 -230
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-COpYKt2h.mjs → elevation-CIz1GjGm.mjs} +4 -3
- package/dist/{eventPlugin-A6EbthXR.d.mts → eventPlugin-Bg3cP2_u.d.mts} +38 -3
- package/dist/{eventPlugin-DJG2D7sy.mjs → eventPlugin-DrQ_FesK.mjs} +22 -4
- package/dist/events/index.d.mts +83 -314
- package/dist/events/index.mjs +183 -95
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +24 -2
- package/dist/factory/index.d.mts +5 -5
- package/dist/factory/index.mjs +30 -8
- package/dist/{fastify-augmentation-g_f9omSc.d.mts → fastify-augmentation-OzYGIiU1.d.mts} +26 -2
- package/dist/{generate-usRSdQmP.mjs → generate-D0wZupBg.mjs} +1 -1
- package/dist/headers-BvlQSDMB.mjs +45 -0
- package/dist/health-CB_m4S32.mjs +377 -0
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.mjs +27 -3
- package/dist/{index-wMoWBLkn.d.mts → index-BMnbzvtO.d.mts} +3 -3
- package/dist/{integrations/jobs.d.mts → index-CG4yqMDB.d.mts} +82 -2
- package/dist/index-CZY4EGga.d.mts +107 -0
- package/dist/{index-CC5ruDGh.d.mts → index-CjVg-Epg.d.mts} +37 -5
- package/dist/{index-BBsvQt7U.d.mts → index-DPRH0Cme.d.mts} +36 -106
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +7 -6
- package/dist/{init-CjO4p_q1.mjs → init-tlRSt667.mjs} +206 -208
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/jobs/index.d.mts +2 -0
- package/dist/integrations/{jobs.mjs → jobs/index.mjs} +110 -67
- 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 +1 -1
- package/dist/integrations/webhooks.d.mts +37 -30
- package/dist/integrations/webhooks.mjs +51 -62
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/integrations/websocket.mjs +41 -14
- package/dist/{interface-C1_WLTMK.d.mts → interface-BGZCzOGL.d.mts} +24 -31
- package/dist/{mcpPlugin-BtzIL_kM.mjs → mcpPlugin-Cs6nRGCt.mjs} +12 -10
- package/dist/{memory-CLXA2VeJ.mjs → memory-CmnzXE5Z.mjs} +29 -0
- package/dist/middleware/index.d.mts +17 -1
- package/dist/middleware/index.mjs +1 -1
- package/dist/migrations/index.d.mts +94 -44
- package/dist/migrations/index.mjs +196 -44
- package/dist/module-_-M7C5ES.mjs +29 -0
- package/dist/{multipartBody-CHEQSZ6G.mjs → multipartBody-D0eBvkeq.mjs} +17 -0
- package/dist/{openapi-jSDx-VDv.mjs → openapi-BLV4OMXb.mjs} +3 -3
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-tbxFHxjZ.mjs → permissions-BrtiBKGs.mjs} +229 -229
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/{actionPermissions-lszoJle1.mjs → pipeline-BGRnV-To.mjs} +4 -27
- package/dist/plugins/index.d.mts +6 -77
- package/dist/plugins/index.mjs +11 -95
- package/dist/plugins/response-cache.mjs +1 -0
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/{index-Ba10fdKp.d.mts → preloadResources-CM4uUaFv.d.mts} +51 -50
- package/dist/preloadResources-DSY5HGpS.mjs +45 -0
- package/dist/presets/filesUpload.d.mts +3 -3
- package/dist/presets/filesUpload.mjs +3 -3
- package/dist/presets/index.d.mts +2 -1
- 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 +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-BOe4Ugkl.mjs → presets-DwFDVKP0.mjs} +2 -2
- package/dist/{queryCachePlugin-DzqLN904.mjs → queryCachePlugin-DSGuSVLW.mjs} +39 -8
- package/dist/{queryCachePlugin-CfYWq-Ak.d.mts → queryCachePlugin-br4otiqF.d.mts} +2 -2
- package/dist/{registerRoute-CWO6mdYe.mjs → rateLimit-DrED-C4v.mjs} +2 -109
- package/dist/{realtime-I8OrfnMR.mjs → realtime-DEuDu9io.mjs} +3 -3
- package/dist/{redis-stream-iVR2l7aS.d.mts → redis-stream-uIaauNpZ.d.mts} +22 -0
- package/dist/registerRoute-DTp18Ah_.mjs +108 -0
- package/dist/registry/index.d.mts +2 -2
- package/dist/registry/index.mjs +1 -1
- package/dist/{registry-vcOCtPij.mjs → registry-9km_w1aP.mjs} +15 -0
- package/dist/renewingLease-3cuQH7h8.mjs +44 -0
- package/dist/{requestContext-BZKOr13H.mjs → requestContext-MrlJwECf.mjs} +2 -2
- package/dist/schedules-CjOd2MG9.mjs +113 -0
- package/dist/schemas/index.d.mts +67 -67
- package/dist/schemas/index.mjs +2 -3
- package/dist/scim/index.mjs +1 -2
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +4 -4
- package/dist/{scope-LONQ3mfZ.mjs → scope-DfaPntpo.mjs} +4 -5
- package/dist/{sse-0u4P4-OI.mjs → sse-BQ1w0dfl.mjs} +1 -1
- package/dist/testing/cleanupStoreContract.d.mts +10 -0
- package/dist/testing/cleanupStoreContract.mjs +375 -0
- package/dist/testing/index.d.mts +5 -3
- package/dist/testing/index.mjs +4 -2
- package/dist/testing/outboxStoreContract.d.mts +13 -0
- package/dist/testing/outboxStoreContract.mjs +247 -0
- package/dist/transactionContext-PNdoyLLm.mjs +64 -0
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-bHblZ9YN.mjs → types-CSyCxl2V.mjs} +38 -7
- package/dist/types-C_ZZRcD6.d.mts +450 -0
- package/dist/{module-B4u10iOJ.d.mts → types-Cu6vVJmh.d.mts} +196 -60
- package/dist/{types-DH0GupI7.d.mts → types-DjC-KUCz.d.mts} +118 -20
- package/dist/{types-Csxga7d3.d.mts → types-Dmw-6Qeo.d.mts} +96 -4
- package/dist/{types-5OuqeTDX.d.mts → types-QFGc3Cjv.d.mts} +1 -1
- package/dist/usage/index.d.mts +3 -52
- package/dist/usage/index.mjs +0 -0
- package/dist/userHelpers-Cg_y3fBz.mjs +29 -0
- package/dist/utils/index.d.mts +58 -85
- package/dist/utils/index.mjs +4 -2
- package/dist/{utils-CZyeRXTh.mjs → utils-taUVD4p3.mjs} +47 -337
- package/dist/{validate-C2mGgSXg.mjs → validate-Bk44F2yT.mjs} +1 -1
- package/dist/{versioning-D6PvCwpq.d.mts → versioning-DXgY5BUm.d.mts} +11 -29
- package/dist/{versioning-Dk-71iIz.mjs → versioning-gKWyryT3.mjs} +8 -1
- package/dist/{websocket-uuWqM6UY.d.mts → websocket-_Pd0CgR0.d.mts} +15 -1
- package/package.json +541 -513
- package/skills/arc/references/api-reference.md +3 -1
- package/skills/arc/references/integrations.md +20 -11
- package/dist/QueryCache-Z1B38koh.d.mts +0 -40
- package/dist/betterAuthOpenApi-CV1lxpRX.mjs +0 -2
- package/dist/health-ScyjIVo5.mjs +0 -202
- package/dist/requestId-5BhTVzBv.mjs +0 -113
- package/dist/types-XTApa8b6.mjs +0 -24
package/dist/plugins/index.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { p as MUTATION_OPERATIONS } from "../constants-DNre0wRg.mjs";
|
|
2
|
-
import { c as getOrgId } from "../types-
|
|
3
|
-
import { n as requestContext } from "../requestContext-
|
|
2
|
+
import { c as getOrgId } from "../types-CSyCxl2V.mjs";
|
|
3
|
+
import { n as requestContext } from "../requestContext-MrlJwECf.mjs";
|
|
4
4
|
import { t as hasEvents } from "../typeGuards-C9KXUly6.mjs";
|
|
5
5
|
import { t as HookSystem } from "../HookSystem-C1vB_Gc9.mjs";
|
|
6
6
|
import { t as ResourceRegistry } from "../ResourceRegistry-y9Hnypus.mjs";
|
|
7
|
-
import { i as
|
|
8
|
-
import { n as health_default, t as healthPlugin } from "../health-ScyjIVo5.mjs";
|
|
7
|
+
import { a as requestIdPlugin, i as createRequestIdGenerator, n as health_default, o as requestId_default, s as sanitizeRequestId, t as healthPlugin } from "../health-CB_m4S32.mjs";
|
|
9
8
|
import { n as caching_default, t as cachingPlugin } from "../caching-_NJeDd1_.mjs";
|
|
10
9
|
import { n as errorHandlerPlugin, t as defaultIsDuplicateKeyError } from "../errorHandler-Bq2OeEQL.mjs";
|
|
11
10
|
import { n as metrics_default, t as metricsPlugin } from "../metrics-fdxI5mn_.mjs";
|
|
12
|
-
import { n as realtime_default, t as realtimePlugin } from "../realtime-
|
|
11
|
+
import { n as realtime_default, t as realtimePlugin } from "../realtime-DEuDu9io.mjs";
|
|
13
12
|
import { t as replyHelpersPlugin } from "../replyHelpers-67tRawQZ.mjs";
|
|
14
|
-
import { n as
|
|
15
|
-
import { n as
|
|
13
|
+
import { n as schedules_default, t as schedulesPlugin } from "../schedules-CjOd2MG9.mjs";
|
|
14
|
+
import { n as sse_default, t as ssePlugin } from "../sse-BQ1w0dfl.mjs";
|
|
15
|
+
import { n as versioning_default, t as versioningPlugin } from "../versioning-gKWyryT3.mjs";
|
|
16
16
|
import fp from "fastify-plugin";
|
|
17
17
|
//#region src/core/arcCorePlugin.ts
|
|
18
18
|
const arcCorePlugin = async (fastify, opts = {}) => {
|
|
@@ -24,7 +24,10 @@ const arcCorePlugin = async (fastify, opts = {}) => {
|
|
|
24
24
|
registry: actualRegistry,
|
|
25
25
|
emitEvents,
|
|
26
26
|
externalOpenApiPaths: [],
|
|
27
|
-
plugins: /* @__PURE__ */ new Map()
|
|
27
|
+
plugins: /* @__PURE__ */ new Map(),
|
|
28
|
+
healthChecks: [],
|
|
29
|
+
moduleDefinitions: [],
|
|
30
|
+
scheduledJobs: []
|
|
28
31
|
});
|
|
29
32
|
for (const field of [
|
|
30
33
|
"_policyFilters",
|
|
@@ -201,91 +204,4 @@ var gracefulShutdown_default = fp(gracefulShutdownPlugin, {
|
|
|
201
204
|
fastify: "5.x"
|
|
202
205
|
});
|
|
203
206
|
//#endregion
|
|
204
|
-
//#region src/plugins/schedules.ts
|
|
205
|
-
const MIN_SAFE_INTERVAL_MS = 1e3;
|
|
206
|
-
const MAX_DEFAULT_LEASE_MS = 3e5;
|
|
207
|
-
const schedulesPlugin = async (fastify, opts) => {
|
|
208
|
-
const { schedules, lock, enabled = true } = opts;
|
|
209
|
-
const holderId = opts.holderId ?? `arc-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
210
|
-
const stats = /* @__PURE__ */ new Map();
|
|
211
|
-
fastify.decorate("getScheduleStats", () => [...stats.values()].map((s) => ({ ...s })));
|
|
212
|
-
if (!enabled) {
|
|
213
|
-
fastify.log.debug("schedulesPlugin disabled — registered as no-op");
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
const seen = /* @__PURE__ */ new Set();
|
|
217
|
-
for (const s of schedules) {
|
|
218
|
-
if (!s.name) throw new TypeError("[arc-schedules] every schedule needs a name");
|
|
219
|
-
if (seen.has(s.name)) throw new TypeError(`[arc-schedules] duplicate schedule name "${s.name}"`);
|
|
220
|
-
seen.add(s.name);
|
|
221
|
-
if (!Number.isFinite(s.every) || s.every <= 0) throw new TypeError(`[arc-schedules] "${s.name}": \`every\` must be a positive number of ms`);
|
|
222
|
-
if (s.every < MIN_SAFE_INTERVAL_MS) fastify.log.warn(`[arc-schedules] "${s.name}" runs every ${s.every}ms — sub-second schedules are almost always a bug in production (DB pressure, lock churn). Intended for tests only.`);
|
|
223
|
-
}
|
|
224
|
-
const timers = /* @__PURE__ */ new Map();
|
|
225
|
-
const inFlight = /* @__PURE__ */ new Map();
|
|
226
|
-
let closing = false;
|
|
227
|
-
async function tick(s) {
|
|
228
|
-
const stat = stats.get(s.name);
|
|
229
|
-
if (!stat) return;
|
|
230
|
-
try {
|
|
231
|
-
if (lock) {
|
|
232
|
-
const leaseMs = s.leaseMs ?? Math.min(Math.floor(s.every * .9), MAX_DEFAULT_LEASE_MS);
|
|
233
|
-
if (!await lock.tryAcquire(`arc:schedule:${s.name}`, holderId, leaseMs)) {
|
|
234
|
-
stat.skippedByLock++;
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
stat.runs++;
|
|
239
|
-
stat.lastRunAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
240
|
-
await s.handler(fastify);
|
|
241
|
-
stat.lastError = null;
|
|
242
|
-
} catch (err) {
|
|
243
|
-
stat.failures++;
|
|
244
|
-
stat.lastError = err instanceof Error ? err.message : String(err);
|
|
245
|
-
fastify.log.error({
|
|
246
|
-
err,
|
|
247
|
-
schedule: s.name
|
|
248
|
-
}, "[arc-schedules] schedule run failed");
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
function scheduleNext(s, delay) {
|
|
252
|
-
if (closing) return;
|
|
253
|
-
const timer = setTimeout(() => {
|
|
254
|
-
const run = tick(s).finally(() => {
|
|
255
|
-
inFlight.delete(s.name);
|
|
256
|
-
scheduleNext(s, s.every);
|
|
257
|
-
});
|
|
258
|
-
inFlight.set(s.name, run);
|
|
259
|
-
}, delay);
|
|
260
|
-
timer.unref?.();
|
|
261
|
-
timers.set(s.name, timer);
|
|
262
|
-
}
|
|
263
|
-
fastify.addHook("onReady", async () => {
|
|
264
|
-
for (const s of schedules) {
|
|
265
|
-
if (s.enabled === false) continue;
|
|
266
|
-
stats.set(s.name, {
|
|
267
|
-
name: s.name,
|
|
268
|
-
runs: 0,
|
|
269
|
-
failures: 0,
|
|
270
|
-
skippedByLock: 0,
|
|
271
|
-
lastRunAt: null,
|
|
272
|
-
lastError: null
|
|
273
|
-
});
|
|
274
|
-
const jitter = !s.runOnStart && s.jitterMs ? Math.floor(Math.random() * s.jitterMs) : 0;
|
|
275
|
-
scheduleNext(s, s.runOnStart ? 0 : s.every + jitter);
|
|
276
|
-
}
|
|
277
|
-
if (stats.size > 0) fastify.log.debug(`[arc-schedules] ${stats.size} schedule(s) armed${lock ? ` (leader-safe, holder ${holderId})` : ""}`);
|
|
278
|
-
});
|
|
279
|
-
fastify.addHook("onClose", async () => {
|
|
280
|
-
closing = true;
|
|
281
|
-
for (const timer of timers.values()) clearTimeout(timer);
|
|
282
|
-
timers.clear();
|
|
283
|
-
await Promise.allSettled([...inFlight.values()]);
|
|
284
|
-
});
|
|
285
|
-
};
|
|
286
|
-
var schedules_default = fp(schedulesPlugin, {
|
|
287
|
-
name: "arc-schedules",
|
|
288
|
-
fastify: "5.x"
|
|
289
|
-
});
|
|
290
|
-
//#endregion
|
|
291
207
|
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, 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 };
|
|
@@ -92,6 +92,7 @@ const responseCachePluginImpl = async (fastify, opts = {}) => {
|
|
|
92
92
|
"PATCH",
|
|
93
93
|
"DELETE"
|
|
94
94
|
], xCacheHeader = true, statsPath = null, keyFn } = opts;
|
|
95
|
+
if (maxEntries > 5e3) fastify.log.warn(`[arc] responseCache maxEntries=${maxEntries}: prefix invalidation scans all entries synchronously on every mutating request — expect event-loop pauses at this size. This cache is an instance-local micro-cache; for large shared caching use the queryCache with a Redis store instead.`);
|
|
95
96
|
const cache = new LRUCache(maxEntries);
|
|
96
97
|
const invalidateMethods = new Set(invalidateOn.map((m) => m.toUpperCase()));
|
|
97
98
|
if (!fastify.hasRequestDecorator("__arcCacheTTL")) fastify.decorateRequest("__arcCacheTTL", void 0);
|
|
@@ -58,7 +58,7 @@ try {
|
|
|
58
58
|
function createTracerProvider(options) {
|
|
59
59
|
if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
|
|
60
60
|
const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
|
|
61
|
-
const resolvedVersion = serviceVersion ?? "2.
|
|
61
|
+
const resolvedVersion = serviceVersion ?? "2.28.0";
|
|
62
62
|
const exporter = new OTLPTraceExporter({ url: exporterUrl });
|
|
63
63
|
const provider = new NodeTracerProvider({ resource: { attributes: {
|
|
64
64
|
"service.name": serviceName,
|
|
@@ -1,38 +1,16 @@
|
|
|
1
|
-
import { Wt as Authenticator } from "./index-
|
|
2
|
-
import { r as CacheStore } from "./interface-
|
|
1
|
+
import { Wt as Authenticator } from "./index-DPRH0Cme.mjs";
|
|
2
|
+
import { r as CacheStore } from "./interface-BGZCzOGL.mjs";
|
|
3
3
|
import { n as ElevationOptions } from "./elevation-C9s5hFP_.mjs";
|
|
4
4
|
import { a as EventTransport } from "./EventTransport-QmY7A258.mjs";
|
|
5
5
|
import { t as ExternalOpenApiPaths } from "./externalPaths-BE6wFpYv.mjs";
|
|
6
|
-
import { r as QueryCachePluginOptions } from "./queryCachePlugin-
|
|
6
|
+
import { r as QueryCachePluginOptions } from "./queryCachePlugin-br4otiqF.mjs";
|
|
7
7
|
import { t as ErrorHandlerOptions } from "./errorHandler-C6yv4ZTW.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
8
|
+
import { f as SchedulesPluginOptions, g as HealthOptions, n as ArcModuleInput, s as ResourceLike } from "./types-Cu6vVJmh.mjs";
|
|
9
|
+
import "./index-CZY4EGga.mjs";
|
|
10
|
+
import { t as EventPluginOptions } from "./eventPlugin-Bg3cP2_u.mjs";
|
|
11
|
+
import { g as CachingOptions, o as MetricsOptions, p as SSEOptions, t as VersioningOptions, u as RealtimeOptions } from "./versioning-DXgY5BUm.mjs";
|
|
11
12
|
import { r as IdempotencyStore } from "./interface-Uk8ONJv7.mjs";
|
|
12
13
|
import { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest, FastifyServerOptions } from "fastify";
|
|
13
|
-
//#region src/testing/preloadResources.d.ts
|
|
14
|
-
/** Eager glob result: `{ '/path/to/file.ts': resourceModule }` */
|
|
15
|
-
type EagerGlobResult = Record<string, unknown>;
|
|
16
|
-
/** Lazy glob result: `{ '/path/to/file.ts': () => Promise<unknown> }` */
|
|
17
|
-
type LazyGlobResult = Record<string, () => Promise<unknown>>;
|
|
18
|
-
/**
|
|
19
|
-
* Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
|
|
20
|
-
*
|
|
21
|
-
* Accepts either:
|
|
22
|
-
* - `{ import: 'default' }` form: values are the resource directly
|
|
23
|
-
* - default form: values are the full module — picks first export with `toPlugin()`
|
|
24
|
-
*
|
|
25
|
-
* Throws if any module doesn't yield a valid `ResourceLike`.
|
|
26
|
-
*/
|
|
27
|
-
declare function preloadResources(globResult: EagerGlobResult): ResourceLike[];
|
|
28
|
-
/**
|
|
29
|
-
* Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
|
|
30
|
-
*
|
|
31
|
-
* Use this when resources depend on prior bootstrap (e.g., engine init) and
|
|
32
|
-
* cannot be evaluated at import time of the preload file.
|
|
33
|
-
*/
|
|
34
|
-
declare function preloadResourcesAsync(globResult: LazyGlobResult): Promise<ResourceLike[]>;
|
|
35
|
-
//#endregion
|
|
36
14
|
//#region src/factory/types/auth.d.ts
|
|
37
15
|
/**
|
|
38
16
|
* Arc's built-in JWT auth
|
|
@@ -238,6 +216,8 @@ interface UnderPressureOptions {
|
|
|
238
216
|
exposeStatusRoute?: boolean;
|
|
239
217
|
/** Event loop lag threshold in ms — requests rejected above this (default: 1000) */
|
|
240
218
|
maxEventLoopDelay?: number;
|
|
219
|
+
/** Event loop utilization threshold (0–1) — requests rejected above this */
|
|
220
|
+
maxEventLoopUtilization?: number;
|
|
241
221
|
/** V8 heap usage threshold in bytes — requests rejected above this */
|
|
242
222
|
maxHeapUsedBytes?: number;
|
|
243
223
|
/** RSS memory threshold in bytes — requests rejected above this */
|
|
@@ -445,6 +425,12 @@ interface CreateAppOptions {
|
|
|
445
425
|
* - `'10.0.0.0/8'` / `['10.0.0.0/8', '172.16.0.0/12']` — trust only these
|
|
446
426
|
* CIDR ranges
|
|
447
427
|
*
|
|
428
|
+
* Default: `false` everywhere, including `preset: 'production'` (2.24
|
|
429
|
+
* flip — fail-closed; the preset previously trusted every proxy). Apps
|
|
430
|
+
* behind a proxy/LB must set this explicitly or `request.ip` is the
|
|
431
|
+
* proxy's address; a boot warning fires when the production preset's
|
|
432
|
+
* default is inherited without an explicit choice.
|
|
433
|
+
*
|
|
448
434
|
* @example
|
|
449
435
|
* ```ts
|
|
450
436
|
* createApp({ preset: 'production', trustProxy: 1 }); // one LB hop
|
|
@@ -661,6 +647,18 @@ interface CreateAppOptions {
|
|
|
661
647
|
* ```
|
|
662
648
|
*/
|
|
663
649
|
events?: Omit<EventPluginOptions, "transport"> | boolean;
|
|
650
|
+
/**
|
|
651
|
+
* Runtime options for module-contributed `scheduledJobs`. Module schedules
|
|
652
|
+
* automatically use Arc's existing schedules plugin. Pass lock/holder
|
|
653
|
+
* options for multi-replica safety, `true` for single-instance defaults,
|
|
654
|
+
* or `false` to reject any module that declares schedules.
|
|
655
|
+
*
|
|
656
|
+
* `enabled: false` registers the scheduler as a NO-OP — the host's runtime
|
|
657
|
+
* kill switch (e.g. an ops instance that must run zero background work
|
|
658
|
+
* against a shared DB). Unlike `schedules: false`, it does NOT reject
|
|
659
|
+
* modules that declare schedules; they are collected but never armed.
|
|
660
|
+
*/
|
|
661
|
+
schedules?: Omit<SchedulesPluginOptions, "schedules"> | boolean;
|
|
664
662
|
/**
|
|
665
663
|
* Caching headers (ETag + Cache-Control). Default: false (opt-in).
|
|
666
664
|
* Set to true for defaults, or pass CachingOptions for fine control.
|
|
@@ -698,26 +696,6 @@ interface CreateAppOptions {
|
|
|
698
696
|
*/
|
|
699
697
|
versioning?: VersioningOptions;
|
|
700
698
|
};
|
|
701
|
-
/**
|
|
702
|
-
* Type provider for schema inference.
|
|
703
|
-
*
|
|
704
|
-
* When set to `'typebox'`, enables TypeBox type provider for
|
|
705
|
-
* automatic TypeScript inference from route schemas.
|
|
706
|
-
*
|
|
707
|
-
* Requires `@sinclair/typebox` and `@fastify/type-provider-typebox` installed.
|
|
708
|
-
*
|
|
709
|
-
* @example
|
|
710
|
-
* ```typescript
|
|
711
|
-
* import { Type } from '@classytic/arc/schemas';
|
|
712
|
-
*
|
|
713
|
-
* const app = await createApp({
|
|
714
|
-
* typeProvider: 'typebox',
|
|
715
|
-
* });
|
|
716
|
-
*
|
|
717
|
-
* // Now route schemas built with Type.* give full TS inference
|
|
718
|
-
* ```
|
|
719
|
-
*/
|
|
720
|
-
typeProvider?: "typebox";
|
|
721
699
|
/**
|
|
722
700
|
* Error handler plugin. Normalizes AJV, Mongoose, and ArcError responses
|
|
723
701
|
* into a consistent JSON envelope. Enabled by default.
|
|
@@ -1092,4 +1070,27 @@ interface CreateAppOptions {
|
|
|
1092
1070
|
onClose?: (fastify: FastifyInstance) => void | Promise<void>;
|
|
1093
1071
|
}
|
|
1094
1072
|
//#endregion
|
|
1095
|
-
|
|
1073
|
+
//#region src/factory/preloadResources.d.ts
|
|
1074
|
+
/** Eager glob result: `{ '/path/to/file.ts': resourceModule }` */
|
|
1075
|
+
type EagerGlobResult = Record<string, unknown>;
|
|
1076
|
+
/** Lazy glob result: `{ '/path/to/file.ts': () => Promise<unknown> }` */
|
|
1077
|
+
type LazyGlobResult = Record<string, () => Promise<unknown>>;
|
|
1078
|
+
/**
|
|
1079
|
+
* Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
|
|
1080
|
+
*
|
|
1081
|
+
* Accepts either:
|
|
1082
|
+
* - `{ import: 'default' }` form: values are the resource directly
|
|
1083
|
+
* - default form: values are the full module — picks first export with `toPlugin()`
|
|
1084
|
+
*
|
|
1085
|
+
* Throws if any module doesn't yield a valid `ResourceLike`.
|
|
1086
|
+
*/
|
|
1087
|
+
declare function preloadResources(globResult: EagerGlobResult): ResourceLike[];
|
|
1088
|
+
/**
|
|
1089
|
+
* Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
|
|
1090
|
+
*
|
|
1091
|
+
* Use this when resources depend on prior bootstrap (e.g., engine init) and
|
|
1092
|
+
* cannot be evaluated at import time of the preload file.
|
|
1093
|
+
*/
|
|
1094
|
+
declare function preloadResourcesAsync(globResult: LazyGlobResult): Promise<ResourceLike[]>;
|
|
1095
|
+
//#endregion
|
|
1096
|
+
export { RawBodyOptions as a, BetterAuthOption as c, JwtAuthOption as d, MultipartOptions as i, CustomAuthenticatorOption as l, preloadResourcesAsync as n, UnderPressureOptions as o, CreateAppOptions as r, AuthOption as s, preloadResources as t, CustomPluginAuthOption as u };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
//#region src/factory/preloadResources.ts
|
|
2
|
+
/**
|
|
3
|
+
* Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
|
|
4
|
+
*
|
|
5
|
+
* Accepts either:
|
|
6
|
+
* - `{ import: 'default' }` form: values are the resource directly
|
|
7
|
+
* - default form: values are the full module — picks first export with `toPlugin()`
|
|
8
|
+
*
|
|
9
|
+
* Throws if any module doesn't yield a valid `ResourceLike`.
|
|
10
|
+
*/
|
|
11
|
+
function preloadResources(globResult) {
|
|
12
|
+
const resources = [];
|
|
13
|
+
for (const [path, value] of Object.entries(globResult)) {
|
|
14
|
+
const resource = pickResource(value);
|
|
15
|
+
if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
|
|
16
|
+
resources.push(resource);
|
|
17
|
+
}
|
|
18
|
+
return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
|
|
22
|
+
*
|
|
23
|
+
* Use this when resources depend on prior bootstrap (e.g., engine init) and
|
|
24
|
+
* cannot be evaluated at import time of the preload file.
|
|
25
|
+
*/
|
|
26
|
+
async function preloadResourcesAsync(globResult) {
|
|
27
|
+
return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
|
|
28
|
+
const resource = pickResource(await loader());
|
|
29
|
+
if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
|
|
30
|
+
return resource;
|
|
31
|
+
}))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
32
|
+
}
|
|
33
|
+
function pickResource(value) {
|
|
34
|
+
if (!value || typeof value !== "object") return void 0;
|
|
35
|
+
if (typeof value.toPlugin === "function") return value;
|
|
36
|
+
const mod = value;
|
|
37
|
+
const candidates = [
|
|
38
|
+
mod.default,
|
|
39
|
+
mod.resource,
|
|
40
|
+
...Object.values(mod)
|
|
41
|
+
];
|
|
42
|
+
for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
45
|
+
export { preloadResourcesAsync as n, preloadResources as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Y as PresetResult } from "../index-
|
|
2
|
-
import { i as RequestScope } from "../types-
|
|
3
|
-
import { t as PermissionCheck } from "../types-
|
|
1
|
+
import { Y as PresetResult } from "../index-DPRH0Cme.mjs";
|
|
2
|
+
import { i as RequestScope } from "../types-DjC-KUCz.mjs";
|
|
3
|
+
import { t as PermissionCheck } from "../types-Dmw-6Qeo.mjs";
|
|
4
4
|
import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-DLUZ04kK.mjs";
|
|
5
5
|
//#region src/presets/filesUpload.d.ts
|
|
6
6
|
interface FilesUploadPresetRoutes {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { i as NotFoundError, u as ValidationError } from "../errors-DkO8Nk6-.mjs";
|
|
2
|
-
import { c as getOrgId, g as getUserId } from "../types-
|
|
3
|
-
import {
|
|
4
|
-
import { t as multipartBody } from "../multipartBody-
|
|
2
|
+
import { c as getOrgId, g as getUserId } from "../types-CSyCxl2V.mjs";
|
|
3
|
+
import { A as requireAuth, E as allowPublic } from "../permissions-BrtiBKGs.mjs";
|
|
4
|
+
import { t as multipartBody } from "../multipartBody-D0eBvkeq.mjs";
|
|
5
5
|
//#region src/presets/filesUpload.ts
|
|
6
6
|
const DEFAULT_FIELD_NAME = "file";
|
|
7
7
|
const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
|
package/dist/presets/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Rt as IControllerResponse, U as ResourceConfig, Y as PresetResult, d as PaginationResult, zt as IRequestContext } from "../index-DPRH0Cme.mjs";
|
|
2
|
+
import { c as AnyRecord } from "../types-Dmw-6Qeo.mjs";
|
|
2
3
|
import { FilesUploadPresetOptions, FilesUploadPresetPermissions, FilesUploadPresetRoutes, filesUploadPreset } from "./filesUpload.mjs";
|
|
3
4
|
import { MultiTenantOptions, TenantFieldSpec, multiTenantPreset } from "./multiTenant.mjs";
|
|
4
5
|
import { SearchHandler, SearchPresetOptions, SearchRouteConfig, searchPreset } from "./search.mjs";
|
package/dist/presets/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { multiTenantPreset } from "./multiTenant.mjs";
|
|
2
|
-
import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-
|
|
2
|
+
import { a as registerPreset, c as auditedPreset, d as ownedByUserPreset, i as getPreset, l as softDeletePreset, n as flexibleMultiTenantPreset, o as treePreset, r as getAvailablePresets, s as bulkPreset, t as applyPresets, u as slugLookupPreset } from "../presets-DwFDVKP0.mjs";
|
|
3
3
|
import { filesUploadPreset } from "./filesUpload.mjs";
|
|
4
4
|
import { searchPreset } from "./search.mjs";
|
|
5
5
|
export { applyPresets, auditedPreset, bulkPreset, filesUploadPreset, flexibleMultiTenantPreset, getAvailablePresets, getPreset, multiTenantPreset, ownedByUserPreset, registerPreset, searchPreset, slugLookupPreset, softDeletePreset, treePreset };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { X as CrudRouteKey, Y as PresetResult } from "../index-
|
|
1
|
+
import { X as CrudRouteKey, Y as PresetResult } from "../index-DPRH0Cme.mjs";
|
|
2
2
|
//#region src/presets/multiTenant.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* One tenant dimension for multi-field filtering. Discriminated by source:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../constants-DNre0wRg.mjs";
|
|
2
|
-
import { b as isElevated, c as getOrgId, d as getScopeContext, m as getTeamId, u as getRequestScope, v as hasOrgAccess } from "../types-
|
|
2
|
+
import { b as isElevated, c as getOrgId, d as getScopeContext, m as getTeamId, u as getRequestScope, v as hasOrgAccess } from "../types-CSyCxl2V.mjs";
|
|
3
3
|
//#region src/presets/multiTenant.ts
|
|
4
4
|
/**
|
|
5
5
|
* Resolve a single TenantFieldSpec against the current scope.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { At as RouteDefinition, Pt as ControllerHandler, Y as PresetResult, jt as RouteMcpConfig } from "../index-
|
|
2
|
-
import { t as PermissionCheck } from "../types-
|
|
1
|
+
import { At as RouteDefinition, Pt as ControllerHandler, Y as PresetResult, jt as RouteMcpConfig } from "../index-DPRH0Cme.mjs";
|
|
2
|
+
import { t as PermissionCheck } from "../types-Dmw-6Qeo.mjs";
|
|
3
3
|
//#region src/presets/search.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Search Preset — backend-agnostic search / vector / embed routes
|
package/dist/presets/search.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { b as isElevated, n as PUBLIC_SCOPE } from "./types-
|
|
1
|
+
import { b as isElevated, n as PUBLIC_SCOPE } from "./types-CSyCxl2V.mjs";
|
|
2
|
+
import { A as requireAuth, E as allowPublic, M as requireRoles } from "./permissions-BrtiBKGs.mjs";
|
|
2
3
|
import { multiTenantPreset } from "./presets/multiTenant.mjs";
|
|
3
|
-
import { C as allowPublic, D as requireAuth, k as requireRoles } from "./permissions-tbxFHxjZ.mjs";
|
|
4
4
|
//#region src/presets/ownedByUser.ts
|
|
5
5
|
/**
|
|
6
6
|
* Create ownership check middleware.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
2
|
-
import { t as MemoryCacheStore } from "./memory-CLXA2VeJ.mjs";
|
|
3
2
|
import { t as hasEvents } from "./typeGuards-C9KXUly6.mjs";
|
|
4
3
|
import { i as versionKey, r as tagVersionKey } from "./keys-Cd316u_y.mjs";
|
|
4
|
+
import { t as MemoryCacheStore } from "./memory-CmnzXE5Z.mjs";
|
|
5
5
|
import fp from "fastify-plugin";
|
|
6
6
|
//#region src/cache/QueryCache.ts
|
|
7
7
|
var QueryCache = class {
|
|
@@ -33,7 +33,9 @@ var QueryCache = class {
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
async set(key, data, config) {
|
|
36
|
-
const
|
|
36
|
+
const baseStaleTimeSec = config.staleTime ?? 0;
|
|
37
|
+
const jitter = config.jitter ?? 0;
|
|
38
|
+
const staleTimeSec = jitter > 0 ? Math.max(0, baseStaleTimeSec * (1 + (Math.random() * 2 - 1) * jitter)) : baseStaleTimeSec;
|
|
37
39
|
const totalTtlSec = staleTimeSec + (config.gcTime ?? 60);
|
|
38
40
|
const now = Date.now();
|
|
39
41
|
const envelope = {
|
|
@@ -54,9 +56,7 @@ var QueryCache = class {
|
|
|
54
56
|
}
|
|
55
57
|
/** Bump resource version — orphans all cached queries for this resource */
|
|
56
58
|
async bumpResourceVersion(resource) {
|
|
57
|
-
|
|
58
|
-
const newVersion = Date.now();
|
|
59
|
-
await this.store.set(key, newVersion, 1440 * 60);
|
|
59
|
+
await this.bumpVersion(versionKey(resource));
|
|
60
60
|
}
|
|
61
61
|
/** Get current version for a tag */
|
|
62
62
|
async getTagVersion(tag) {
|
|
@@ -64,11 +64,42 @@ var QueryCache = class {
|
|
|
64
64
|
}
|
|
65
65
|
/** Bump tag version — orphans all cached queries tagged with this tag */
|
|
66
66
|
async bumpTagVersion(tag) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
await this.bumpVersion(tagVersionKey(tag));
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Version-bump semantics:
|
|
71
|
+
*
|
|
72
|
+
* - `store.increment` present (Redis `INCR`) — atomic and strictly
|
|
73
|
+
* monotonic across replicas; concurrent bumps can never collide or
|
|
74
|
+
* go backwards.
|
|
75
|
+
* - fallback — read-modify-write with `max(now, current + 1)`: two
|
|
76
|
+
* bumps in the same millisecond and clock regressions still advance
|
|
77
|
+
* the version. Concurrent bumps from DIFFERENT replicas can lose one
|
|
78
|
+
* write, which is why distributed hosts should use an incrementing
|
|
79
|
+
* store (`runtime: 'distributed'` already requires a shared one).
|
|
80
|
+
*
|
|
81
|
+
* Version keys must outlive every derived cache entry — that invariant
|
|
82
|
+
* is what makes version invalidation sound (a version key expiring back
|
|
83
|
+
* to 0 would make entries written under the pre-reset version
|
|
84
|
+
* addressable again). The 1-year TTL applies with the repo-core
|
|
85
|
+
* TTL-ON-CREATE semantics, so a version key expires one year after its
|
|
86
|
+
* FIRST bump — safe, because a revived collision would need an entry to
|
|
87
|
+
* outlive that reset and entry TTLs are minutes-scale (staleTime +
|
|
88
|
+
* gcTime). The read-modify-write fallback uses plain `set`, which
|
|
89
|
+
* refreshes the TTL per bump — strictly longer-lived, equally safe.
|
|
90
|
+
*/
|
|
91
|
+
async bumpVersion(key) {
|
|
92
|
+
if (typeof this.store.increment === "function") {
|
|
93
|
+
await this.store.increment(key, 1, VERSION_TTL_SECONDS);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const current = await this.store.get(key);
|
|
97
|
+
const next = Math.max(Date.now(), (current ?? 0) + 1);
|
|
98
|
+
await this.store.set(key, next, VERSION_TTL_SECONDS);
|
|
70
99
|
}
|
|
71
100
|
};
|
|
101
|
+
/** See `bumpVersion` — effectively non-expiring, refreshed on every bump. */
|
|
102
|
+
const VERSION_TTL_SECONDS = 365 * 24 * 60 * 60;
|
|
72
103
|
//#endregion
|
|
73
104
|
//#region src/cache/queryCachePlugin.ts
|
|
74
105
|
var queryCachePlugin_exports = /* @__PURE__ */ __exportAll({ queryCachePlugin: () => queryCachePlugin });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { r as CacheStore } from "./interface-
|
|
2
|
-
import { i as QueryCache } from "./QueryCache-
|
|
1
|
+
import { r as CacheStore } from "./interface-BGZCzOGL.mjs";
|
|
2
|
+
import { i as QueryCache } from "./QueryCache-DOMT_iq7.mjs";
|
|
3
3
|
import { FastifyPluginAsync } from "fastify";
|
|
4
4
|
//#region src/cache/queryCachePlugin.d.ts
|
|
5
5
|
interface QueryCachePluginOptions {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as requestContext } from "./requestContext-BZKOr13H.mjs";
|
|
3
|
-
import { n as evaluateAndApplyPermission } from "./applyPermissionResult-Tw4iXtlf.mjs";
|
|
1
|
+
import { n as requestContext } from "./requestContext-MrlJwECf.mjs";
|
|
4
2
|
//#region src/core/middlewares/arcDecorator.ts
|
|
5
3
|
/**
|
|
6
4
|
* Build the `arcDecorator` preHandler for a resource.
|
|
@@ -179,76 +177,6 @@ function describeValue(v) {
|
|
|
179
177
|
return `${typeof v} (${JSON.stringify(v).slice(0, 80)})`;
|
|
180
178
|
}
|
|
181
179
|
//#endregion
|
|
182
|
-
//#region src/core/middlewares/permissions.ts
|
|
183
|
-
/**
|
|
184
|
-
* Build a PermissionContext from a Fastify request. Extracted so the CRUD
|
|
185
|
-
* permission middleware and the dynamic action-permission check use the same
|
|
186
|
-
* field layout — divergence here silently broke policy filters for actions.
|
|
187
|
-
*/
|
|
188
|
-
function buildPermissionContext(req, opts) {
|
|
189
|
-
const reqWithExtras = req;
|
|
190
|
-
const params = req.params;
|
|
191
|
-
return {
|
|
192
|
-
user: reqWithExtras.user ?? null,
|
|
193
|
-
request: req,
|
|
194
|
-
resource: opts.resource,
|
|
195
|
-
action: opts.action,
|
|
196
|
-
resourceId: opts.resourceId ?? params?.id,
|
|
197
|
-
params,
|
|
198
|
-
data: opts.data ?? req.body
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Static per-route CRUD permission gate. The permission and action are known
|
|
203
|
-
* at route-registration time, so the gate is a plain preHandler.
|
|
204
|
-
*
|
|
205
|
-
* Actions use the dynamic counterpart `buildActionPermissionMw` — their
|
|
206
|
-
* permission is resolved from `body.action` at request time.
|
|
207
|
-
*/
|
|
208
|
-
function buildCrudPermissionMw(permissionCheck, resourceName, action) {
|
|
209
|
-
if (!permissionCheck) return null;
|
|
210
|
-
return async (req, reply) => {
|
|
211
|
-
await evaluateAndApplyPermission(permissionCheck, buildPermissionContext(req, {
|
|
212
|
-
resource: resourceName,
|
|
213
|
-
action
|
|
214
|
-
}), req, reply);
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Dynamic per-action permission gate for the action router.
|
|
219
|
-
*
|
|
220
|
-
* Resolves the permission from `body.action` at request time and runs
|
|
221
|
-
* `evaluateAndApplyPermission` from the canonical `permissionMw` slot — so
|
|
222
|
-
* `_policyFilters` and `request.scope` are installed BEFORE `pluginMw`
|
|
223
|
-
* (idempotency) and `routeGuards` run. Previously this check lived inside
|
|
224
|
-
* the main action handler, which meant idempotency recorded unauthorized
|
|
225
|
-
* requests and route guards saw unfiltered scope — the very divergence
|
|
226
|
-
* routerShared exists to prevent.
|
|
227
|
-
*
|
|
228
|
-
* Also acts as a defensive fallback for invalid action names — the
|
|
229
|
-
* `oneOf` body schema normally rejects these at AJV validation, but
|
|
230
|
-
* hosts that disable schema validation still get a 400 here.
|
|
231
|
-
*/
|
|
232
|
-
function buildActionPermissionMw(actionEnum, actionPermissions, globalAuth, resourceName) {
|
|
233
|
-
const enumSet = new Set(actionEnum);
|
|
234
|
-
const validActions = [...actionEnum];
|
|
235
|
-
return async (req, reply) => {
|
|
236
|
-
const body = req.body ?? {};
|
|
237
|
-
const action = body.action;
|
|
238
|
-
if (!action || !enumSet.has(action)) throw createError(400, `Invalid action '${action ?? ""}'. Valid actions: ${validActions.join(", ")}`, { validActions });
|
|
239
|
-
const permissionCheck = actionPermissions[action] ?? globalAuth;
|
|
240
|
-
if (!permissionCheck) return;
|
|
241
|
-
const { action: _discard, ...data } = body;
|
|
242
|
-
const params = req.params;
|
|
243
|
-
await evaluateAndApplyPermission(permissionCheck, buildPermissionContext(req, {
|
|
244
|
-
resource: resourceName,
|
|
245
|
-
action,
|
|
246
|
-
resourceId: params?.id,
|
|
247
|
-
data
|
|
248
|
-
}), req, reply, { defaultDenialMessage: (user) => user ? `Permission denied for '${action}'` : "Authentication required" });
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
//#endregion
|
|
252
180
|
//#region src/core/middlewares/pluginMw.ts
|
|
253
181
|
/**
|
|
254
182
|
* Pick the request-lifecycle plugin middleware for an HTTP method:
|
|
@@ -314,39 +242,4 @@ function buildRateLimitConfig(rateLimit) {
|
|
|
314
242
|
} };
|
|
315
243
|
}
|
|
316
244
|
//#endregion
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Register a route with friendly handling of `FST_ERR_DUPLICATED_ROUTE`.
|
|
320
|
-
*
|
|
321
|
-
* Boot-time `validateRouteCrudCollisions()` catches custom-route → auto-CRUD
|
|
322
|
-
* overlaps before Fastify ever runs, but a few cases still slip through to
|
|
323
|
-
* Fastify itself: two presets contributing the same `routes` entry, hosts
|
|
324
|
-
* mounting two resources at the same prefix, custom routes that share a
|
|
325
|
-
* URL with another plugin. Fastify's default message ("Method 'GET'
|
|
326
|
-
* already declared for route '/'") gives no hint at the resource-shaped
|
|
327
|
-
* fix — `disabledRoutes: ['list']` — so wrap it with one.
|
|
328
|
-
*/
|
|
329
|
-
function tryRegisterRoute(fastify, opts, context) {
|
|
330
|
-
try {
|
|
331
|
-
fastify.route(opts);
|
|
332
|
-
} catch (err) {
|
|
333
|
-
const fastifyErr = err;
|
|
334
|
-
if (fastifyErr?.code !== "FST_ERR_DUPLICATED_ROUTE") throw err;
|
|
335
|
-
const method = opts.method;
|
|
336
|
-
const url = opts.url;
|
|
337
|
-
const opHint = context.op ? ` (op: ${context.op})` : "";
|
|
338
|
-
const fixHint = context.op && [
|
|
339
|
-
"list",
|
|
340
|
-
"get",
|
|
341
|
-
"create",
|
|
342
|
-
"update",
|
|
343
|
-
"delete"
|
|
344
|
-
].includes(context.op) ? ` Suppress the auto-CRUD route with \`disabledRoutes: ['${context.op}']\`.` : " If a custom route in `routes:` collides with auto-CRUD, add `disabledRoutes: ['list' | 'get' | 'create' | 'update' | 'delete']` to `defineResource()` to suppress the matching auto-CRUD route.";
|
|
345
|
-
const enhanced = /* @__PURE__ */ new Error(`[arc] Duplicate route ${method} ${url} on resource "${context.resourceName}"${opHint}.${fixHint} Original: ${fastifyErr.message ?? "FST_ERR_DUPLICATED_ROUTE"}`);
|
|
346
|
-
enhanced.code = "FST_ERR_DUPLICATED_ROUTE";
|
|
347
|
-
enhanced.cause = err;
|
|
348
|
-
throw enhanced;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
//#endregion
|
|
352
|
-
export { selectPluginMw as a, buildRouteHooks as c, buildAuthMiddleware as d, buildAuthMiddlewareForPermissions as f, resolveRouterPluginMw as i, resolveRoutePreHandlers as l, buildRateLimitConfig as n, buildActionPermissionMw as o, buildArcDecorator as p, buildRouteConfig as r, buildCrudPermissionMw as s, tryRegisterRoute as t, routeHookOptions as u };
|
|
245
|
+
export { buildRouteHooks as a, buildAuthMiddleware as c, selectPluginMw as i, buildAuthMiddlewareForPermissions as l, buildRouteConfig as n, resolveRoutePreHandlers as o, resolveRouterPluginMw as r, routeHookOptions as s, buildRateLimitConfig as t, buildArcDecorator as u };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
2
2
|
import { arcLog } from "./logger/index.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { n as getUserRoles } from "./userHelpers-Cg_y3fBz.mjs";
|
|
4
|
+
import { c as getOrgId } from "./types-CSyCxl2V.mjs";
|
|
4
5
|
import { t as simpleEqualityMatcher } from "./simpleEqualityMatcher-Cg05HPsn.mjs";
|
|
5
|
-
import { i as applyFieldReadPermissions, n as evaluateAndApplyPermission } from "./applyPermissionResult-
|
|
6
|
-
import { t as getUserRoles } from "./types-XTApa8b6.mjs";
|
|
6
|
+
import { i as applyFieldReadPermissions, n as evaluateAndApplyPermission } from "./applyPermissionResult-BnyVE9eD.mjs";
|
|
7
7
|
import { a as promoteStreamTokenToHeader } from "./streaming-BQcW6_Kq.mjs";
|
|
8
8
|
import { t as openSseStream } from "./sseStream-BQ7Qaynp.mjs";
|
|
9
9
|
import fp from "fastify-plugin";
|