@classytic/arc 2.41.0 → 2.43.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/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
- package/dist/{EventTransport-n7qO5Cz3.mjs → EventTransport-tzA7CAB4.mjs} +22 -17
- package/dist/{HookSystem-CxvZBo77.mjs → HookSystem-B6UaKRbF.mjs} +2 -2
- package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
- package/dist/{ResourceRegistry-Cu7uTJp9.mjs → ResourceRegistry-CuCARu2B.mjs} +44 -13
- package/dist/actionPermissions-BotX1YOa.mjs +776 -0
- package/dist/audit/index.d.mts +43 -5
- package/dist/audit/index.mjs +48 -26
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +43 -13
- package/dist/auth/index.mjs +130 -155
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-2YY_Kaj1.mjs → authorizationDecision-DQYrkl5f.mjs} +7 -102
- package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
- package/dist/boundedMap-BULzfXYU.mjs +57 -0
- package/dist/{buildHandler-CUhjGHGZ.mjs → buildHandler-CRQ8EcX5.mjs} +4 -4
- package/dist/cache/index.d.mts +22 -34
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-DsWDjIxx.mjs → caching-Cc2M1DwO.mjs} +3 -3
- package/dist/claimed-B10RTfkM.mjs +8 -0
- package/dist/cleanup/index.d.mts +6 -10
- package/dist/cleanup/index.mjs +27 -73
- package/dist/cli/commands/describe.d.mts +4 -2
- package/dist/cli/commands/describe.mjs +7 -4
- 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-BxLgNFF7.mjs → constants-Cu7Mx7_U.mjs} +4 -2
- package/dist/context/index.d.mts +2 -5
- package/dist/context/index.mjs +18 -38
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +8 -8
- package/dist/{core-iNGqYerO.mjs → core-Cb0LjccA.mjs} +5 -4
- package/dist/{createActionRouter-BBlbPs3k.mjs → createActionRouter-xjqonMRO.mjs} +87 -39
- package/dist/{createAggregationRouter-Dv5SuK2x.mjs → createAggregationRouter-zRYWw_5D.mjs} +11 -8
- package/dist/{preloadResources-Bbp4QJ8V.mjs → createApp-D-953ZAq.mjs} +299 -390
- package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-C9cxS8zt.d.mts} +1 -1
- package/dist/{defineAggregation-UyeurGkp.mjs → defineAggregation-Bm8RjMbq.mjs} +1 -1
- package/dist/{defineResource-DzGT-2LP.mjs → defineResource-DIC7JZGU.mjs} +118 -1024
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +10 -4
- package/dist/docs/index.mjs +4 -4
- package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
- package/dist/{elevation-BF-vhGxY.mjs → elevation-DG8SrLN-.mjs} +41 -12
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-CIMkCOWm.mjs → errorHandler-BnfDviL1.mjs} +26 -5
- package/dist/{errors-C_YhELhe.mjs → errors-CtrywMHP.mjs} +58 -23
- package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
- package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
- package/dist/{eventPlugin-CmXaPWLv.mjs → eventPlugin-Q6BhzHzs.mjs} +82 -21
- package/dist/{subscribe-helpers-AVYPgqTl.mjs → eventTypes-Cdhm6ZlV.mjs} +151 -17
- package/dist/events/index.d.mts +33 -8
- package/dist/events/index.mjs +49 -53
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +139 -157
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +2 -2
- package/dist/factory/index.d.mts +32 -17
- package/dist/factory/index.mjs +213 -32
- package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DhUJ-mvo.d.mts} +11 -25
- package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
- package/dist/{fields-BTyPLHK0.mjs → fields-ToPYpMqS.mjs} +1 -1
- package/dist/filter-merge-IpGywvym.mjs +99 -0
- package/dist/{generate-D_YmiCNf.mjs → generate-DRTkLlIl.mjs} +2 -2
- package/dist/{headers-CNECDE6q.mjs → headers-BbkIF6vA.mjs} +2 -2
- package/dist/health-D-Ft6JM6.mjs +22 -0
- package/dist/{health-CxQzbJmr.mjs → health-DqbsG-_w.mjs} +115 -55
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.d.mts +31 -7
- package/dist/idempotency/index.mjs +155 -98
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +48 -11
- package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
- package/dist/{preloadResources-CjS2OfpU.d.mts → index-C8zVGYsk.d.mts} +37 -38
- package/dist/{index-D4u76eQW.d.mts → index-CRoPtISf.d.mts} +23 -73
- package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
- package/dist/index-JdbZAIsm.d.mts +510 -0
- package/dist/{index-DHbyPo5j.d.mts → index-mqaqZNpe.d.mts} +253 -254
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +11 -11
- package/dist/{init-Y2MlSN9n.mjs → init-B_oUYDXI.mjs} +1 -1
- package/dist/integrations/event-gateway.d.mts +8 -3
- package/dist/integrations/event-gateway.mjs +4 -4
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +18 -5
- package/dist/integrations/mcp/index.d.mts +72 -59
- package/dist/integrations/mcp/index.mjs +19 -4
- package/dist/integrations/mcp/testing.d.mts +2 -2
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.d.mts +78 -136
- package/dist/integrations/streamline.mjs +116 -54
- package/dist/integrations/webhooks.d.mts +60 -120
- package/dist/integrations/webhooks.mjs +36 -80
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.d.mts +3 -3
- package/dist/integrations/websocket.mjs +2 -2
- package/dist/{interface-DEWNOMza.d.mts → interface-C0395yUN.d.mts} +13 -1
- package/dist/{jsonBody-Cq9KYl2Z.mjs → jsonBody-B4YVGcql.mjs} +2 -2
- package/dist/{keys-Cmv4cBRa.mjs → keys-B2KtgMLJ.mjs} +7 -18
- package/dist/{loadResources-CblF7xt2.mjs → loadResources-Dn4lalw2.mjs} +3 -3
- package/dist/{loadResourcesFromEntry-99fIMC3p.mjs → loadResourcesFromEntry-3sLYbuse.mjs} +2 -2
- package/dist/logger/index.d.mts +1 -8
- package/dist/logger/index.mjs +3 -153
- package/dist/logger-DXjfIqjV.mjs +160 -0
- package/dist/{mcpPlugin-xF3Gw-HW.mjs → mcpPlugin-C08HalN5.mjs} +433 -170
- package/dist/{memory-CpDZ9NPw.mjs → memory-9Cp7611C.mjs} +36 -34
- package/dist/{metrics-BaKnq8ag.mjs → metrics-CWI11eRu.mjs} +39 -72
- package/dist/middleware/index.d.mts +2 -2
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +2 -2
- package/dist/{module-BkQEesNQ.mjs → module-BSvOQC7E.mjs} +8 -8
- package/dist/multiTenant-cY0FkDiA.d.mts +104 -0
- package/dist/{multipartBody-C92aJ8am.mjs → multipartBody-CXM2a-Dy.mjs} +46 -7
- package/dist/{openapi-C3LFRzPq.mjs → openapi-DT_G4VhY.mjs} +48 -10
- package/dist/outbox-admin/index.d.mts +3 -3
- package/dist/outbox-admin/index.mjs +6 -18
- package/dist/{outbox-module-dsCyLxXH.mjs → outbox-module-Bjgzl6tg.mjs} +203 -33
- package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
- package/dist/permissions/index.d.mts +5 -4
- package/dist/permissions/index.mjs +8 -6
- package/dist/{permissions-DiHXy8Ck.mjs → permissions-Cm0uYHvd.mjs} +149 -33
- package/dist/{pipe-CBVmUJ-j.mjs → pipe-BqeGT6UO.mjs} +2 -2
- package/dist/pipeline/index.d.mts +2 -2
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plan-FVR6LP59.mjs +565 -0
- package/dist/{BaseController-DYJRZ0EJ.mjs → plugin-BccET1tL.mjs} +1256 -344
- package/dist/plugins/index.d.mts +14 -11
- package/dist/plugins/index.mjs +44 -22
- package/dist/plugins/response-cache.mjs +8 -14
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-2_U6QZQg.mjs → pluralize-CVIOcgMF.mjs} +1 -1
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +26 -31
- package/dist/presets/index.mjs +4 -4
- package/dist/presets/multiTenant.d.mts +2 -107
- package/dist/presets/multiTenant.mjs +22 -162
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-BdlugYTc.mjs → presets-Cpvj6fGO.mjs} +99 -107
- package/dist/{projection-Uvgp-EEj.mjs → projection-Ce4aQhQk.mjs} +2 -2
- package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
- package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
- package/dist/{queryCachePlugin-B5F09bYr.mjs → queryCachePlugin-CFWNH8aT.mjs} +12 -14
- package/dist/{rateLimitKey-CK-EU2L8.mjs → rateLimitKey-C4O-kPm0.mjs} +1 -1
- package/dist/{realtime-FnnD0n3Z.mjs → realtime-DmPE9S54.mjs} +25 -21
- package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
- package/dist/{redis-Bzi1y3F9.d.mts → redis-vLvtcGy_.d.mts} +13 -24
- package/dist/redisShapes-CQksY67q.d.mts +55 -0
- package/dist/registry/index.d.mts +9 -4
- package/dist/registry/index.mjs +4 -4
- package/dist/{registry-D3y8VEJ1.mjs → registry-Bcz9G1bh.mjs} +10 -8
- package/dist/{renewingLease-B3VYh5aU.mjs → renewingLease-qODa7KJQ.mjs} +1 -1
- package/dist/{replyHelpers-DPHsdw-5.mjs → replyHelpers-CG_VtVgS.mjs} +2 -2
- package/dist/{requestContext-cSVKnYPk.mjs → requestContext-Dsu9P1bV.mjs} +1 -1
- package/dist/requirement-CbiLdQ1n.d.mts +22 -0
- package/dist/requirement-KDdoRcOP.mjs +35 -0
- package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
- package/dist/resourceRateLimit-dSjVr1pk.mjs +180 -0
- package/dist/{rolldown-runtime-DjDuyglE.mjs → rolldown-runtime-D1w9Y1VJ.mjs} +1 -1
- package/dist/routeValidator-D36SW8qt.mjs +70 -0
- package/dist/{runtimeCapabilities-DSA_dH1_.mjs → runtimeCapabilities-0tccjRlj.mjs} +1 -1
- package/dist/{schedules-BktJb3Y3.mjs → schedules-C-OpmHBq.mjs} +46 -37
- package/dist/{schemaConverter-BLCSmNbr.mjs → schemaConverter-BORek3O-.mjs} +2 -2
- package/dist/{schemaIR-K16Bb5BX.mjs → schemaIR-C7Vbp71I.mjs} +2 -2
- package/dist/schemas/index.d.mts +10 -0
- package/dist/schemas/index.mjs +8 -3
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +6 -6
- package/dist/{scope-jSxuBTFM.mjs → scope-DQthe88b.mjs} +4 -4
- package/dist/shutdown-MqA81z_P.mjs +59 -0
- package/dist/{simpleEqualityMatcher-BiOnyflP.mjs → simpleEqualityMatcher-auW8QjXN.mjs} +1 -1
- package/dist/{sse-CH7Slxc1.mjs → sse-CYzQBVFe.mjs} +11 -15
- package/dist/{sseStream-CN-Lh1Tu.mjs → sseStream-B5_Afiqn.mjs} +2 -2
- package/dist/{store-helpers-9DX9-1j_.mjs → store-helpers-BOzk-CuX.mjs} +1 -1
- package/dist/{streaming-dvppnxsr.mjs → streaming-DjTQhZrN.mjs} +1 -1
- package/dist/subscriberIdentity-CW39uA8G.mjs +28 -0
- package/dist/tenancy--ed9eH_z.mjs +43 -0
- package/dist/tenantPreparation-BKon6Yox.mjs +67 -0
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +193 -175
- package/dist/testing/index.mjs +286 -212
- package/dist/testing/outboxStoreContract.mjs +137 -2
- package/dist/testing/requiredPorts.d.mts +17 -0
- package/dist/testing/requiredPorts.mjs +65 -0
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/timing-qczZ0YC-.mjs +46 -0
- package/dist/{transactionContext-DCWMI6hV.mjs → transactionContext-DjFkrNG6.mjs} +1 -1
- package/dist/{typeGuards-D7tHFdH6.mjs → typeGuards-CySF12G-.mjs} +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CWQn9-IC.d.mts → types-B4Aq4Sdj.d.mts} +22 -9
- package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
- package/dist/{types-CDy3-zw1.mjs → types-Bk9NOgOC.mjs} +4 -4
- package/dist/usage/index.d.mts +2 -3
- package/dist/usage/index.mjs +5 -5
- package/dist/{userHelpers-DDMOZ_Pm.mjs → userHelpers-aklawRaK.mjs} +1 -1
- package/dist/utils/index.d.mts +84 -223
- package/dist/utils/index.mjs +13 -12
- package/dist/{utils-B-zj_b__.mjs → utils-ec1oTo1n.mjs} +315 -404
- package/dist/{validate-R0G_JrnK.mjs → validate-9yzHC1e2.mjs} +6 -14
- package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
- package/dist/{versioning-DuVWcu-F.mjs → versioning-fhhWHV74.mjs} +4 -4
- package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
- package/dist/{workScope-BhsWde4k.mjs → workScope-zpPyge4t.mjs} +2 -2
- package/package.json +578 -574
- package/skills/arc/SKILL.md +13 -4
- package/skills/arc/references/auth.md +8 -5
- package/skills/arc/references/integrations.md +1 -1
- package/skills/arc/references/mcp.md +8 -6
- package/skills/arc/references/multi-tenancy.md +19 -14
- package/skills/arc/references/production.md +27 -10
- package/dist/actionPermissions-DPwPWPOV.mjs +0 -350
- package/dist/index-CFfLKMH4.d.mts +0 -290
- package/dist/interface-BIpgzbZl.mjs +0 -17
- package/dist/redis-stream-BPKscsTB.d.mts +0 -260
- package/dist/registerRoute-DRsSHPSM.mjs +0 -364
- package/dist/sync/changelog.d.mts +0 -3
- package/dist/sync/changelog.mjs +0 -3
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as createTenantKeyGenerator } from "./rateLimitKey-C4O-kPm0.mjs";
|
|
3
|
+
//#region src/permissions/resourceRateLimit.ts
|
|
4
|
+
function decision(allowed, max, remaining, ttlSeconds) {
|
|
5
|
+
const reset = Math.max(1, ttlSeconds);
|
|
6
|
+
return {
|
|
7
|
+
allowed,
|
|
8
|
+
retryAfterSeconds: reset,
|
|
9
|
+
headers: {
|
|
10
|
+
"x-ratelimit-limit": max,
|
|
11
|
+
"x-ratelimit-remaining": remaining,
|
|
12
|
+
"x-ratelimit-reset": reset,
|
|
13
|
+
...!allowed ? { "retry-after": reset } : {}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Policy namespacing must reach the store key, not merely the local limiter cache. */
|
|
18
|
+
function policyIdentity(resource, policy) {
|
|
19
|
+
if (!Number.isSafeInteger(policy.max) || policy.max <= 0) throw new Error("rateLimit.max must be a positive safe integer.");
|
|
20
|
+
return JSON.stringify([
|
|
21
|
+
"arc-resource",
|
|
22
|
+
resource,
|
|
23
|
+
policy.bucket ?? "resource",
|
|
24
|
+
policy.max,
|
|
25
|
+
resourceRateLimitWindowMs(policy.timeWindow)
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
const RESOURCE_RATE_LIMITER = Symbol.for("arc.resourceRateLimiter");
|
|
29
|
+
const tenantKey = createTenantKeyGenerator();
|
|
30
|
+
function resourceRateLimitKey(resource, context) {
|
|
31
|
+
const actor = tenantKey({
|
|
32
|
+
scope: context.scope,
|
|
33
|
+
ip: context.ip ?? "anonymous"
|
|
34
|
+
});
|
|
35
|
+
const dimension = context.scope && context.scope.kind !== "public" && "organizationId" in context.scope && context.scope.organizationId ? "tenant" : context.scope?.kind ?? "public";
|
|
36
|
+
return JSON.stringify([
|
|
37
|
+
resource,
|
|
38
|
+
dimension,
|
|
39
|
+
actor
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
const RATE_LIMITING_DISABLED = Symbol.for("arc.rateLimitingDisabled");
|
|
43
|
+
/**
|
|
44
|
+
* The host said `rateLimit: false`. A resource's declared limit is then a recorded no-op, and
|
|
45
|
+
* the resources are named once at ready. Absent this mark, a declared limit with no limiter
|
|
46
|
+
* refuses to boot: nobody decided, so nothing would enforce it.
|
|
47
|
+
*/
|
|
48
|
+
function markRateLimitingDisabled(app) {
|
|
49
|
+
if (Reflect.get(app, RATE_LIMITING_DISABLED)) return;
|
|
50
|
+
const unenforced = /* @__PURE__ */ new Set();
|
|
51
|
+
Reflect.set(app, RATE_LIMITING_DISABLED, unenforced);
|
|
52
|
+
app.addHook("onReady", async () => {
|
|
53
|
+
if (unenforced.size === 0) return;
|
|
54
|
+
app.log.info({ resources: [...unenforced] }, `[arc] rate limiting is disabled for this app: declared rateLimit not enforced on ${[...unenforced].join(", ")}`);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Record a resource whose declared limit the host disabled; `false` when the host never decided. */
|
|
58
|
+
function recordUnenforcedRateLimit(app, resource) {
|
|
59
|
+
let owner = app;
|
|
60
|
+
while (owner) {
|
|
61
|
+
const unenforced = Reflect.get(owner, RATE_LIMITING_DISABLED);
|
|
62
|
+
if (unenforced instanceof Set) {
|
|
63
|
+
unenforced.add(resource);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
owner = Object.getPrototypeOf(owner);
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
/** Uses createRateLimit's store factory, preserving Redis/custom-store configuration. */
|
|
71
|
+
function resolveResourceRateLimiter(app, durability = "memory") {
|
|
72
|
+
if (!app.hasDecorator("createRateLimit")) return void 0;
|
|
73
|
+
let owner = app;
|
|
74
|
+
while (!Object.hasOwn(owner, "createRateLimit")) {
|
|
75
|
+
const parent = Object.getPrototypeOf(owner);
|
|
76
|
+
if (!parent || typeof parent !== "object" || !("createRateLimit" in parent)) break;
|
|
77
|
+
owner = parent;
|
|
78
|
+
}
|
|
79
|
+
const existing = Reflect.get(owner, RESOURCE_RATE_LIMITER);
|
|
80
|
+
if (existing) return existing;
|
|
81
|
+
const limiters = /* @__PURE__ */ new Map();
|
|
82
|
+
const limiter = {
|
|
83
|
+
durability,
|
|
84
|
+
async consume(resource, policy, context) {
|
|
85
|
+
const identity = policyIdentity(resource, policy);
|
|
86
|
+
let consume = limiters.get(identity);
|
|
87
|
+
if (!consume) {
|
|
88
|
+
consume = owner.createRateLimit({
|
|
89
|
+
max: policy.max,
|
|
90
|
+
timeWindow: resourceRateLimitWindowMs(policy.timeWindow),
|
|
91
|
+
allowList: [],
|
|
92
|
+
skipOnError: false,
|
|
93
|
+
keyGenerator: (req) => req.ip
|
|
94
|
+
});
|
|
95
|
+
limiters.set(identity, consume);
|
|
96
|
+
}
|
|
97
|
+
const req = {
|
|
98
|
+
ip: JSON.stringify([identity, resourceRateLimitKey(resource, context)]),
|
|
99
|
+
routeOptions: { config: {} }
|
|
100
|
+
};
|
|
101
|
+
const result = await consume(req);
|
|
102
|
+
return result.isAllowed ? decision(true, policy.max, policy.max, Math.ceil(resourceRateLimitWindowMs(policy.timeWindow) / 1e3)) : decision(!result.isExceeded, result.max, result.remaining, result.ttlInSeconds);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
Reflect.set(owner, RESOURCE_RATE_LIMITER, limiter);
|
|
106
|
+
return limiter;
|
|
107
|
+
}
|
|
108
|
+
/** Standalone tools may use memory; wired distributed apps use the host's shared store. */
|
|
109
|
+
function createMemoryResourceRateLimiter(capacity = 1e4) {
|
|
110
|
+
if (!Number.isSafeInteger(capacity) || capacity <= 0) throw new Error("rateLimit memory capacity must be a positive safe integer.");
|
|
111
|
+
const windows = /* @__PURE__ */ new Map();
|
|
112
|
+
return {
|
|
113
|
+
durability: "memory",
|
|
114
|
+
async consume(resource, policy, context) {
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
const windowMs = resourceRateLimitWindowMs(policy.timeWindow);
|
|
117
|
+
const key = JSON.stringify([policyIdentity(resource, policy), resourceRateLimitKey(resource, context)]);
|
|
118
|
+
let slot = windows.get(key);
|
|
119
|
+
if (!slot || slot.expires <= now) {
|
|
120
|
+
if (windows.size >= capacity) {
|
|
121
|
+
for (const [entry, window] of windows) if (window.expires <= now) windows.delete(entry);
|
|
122
|
+
if (!windows.has(key) && windows.size >= capacity) {
|
|
123
|
+
let retryMs = Number.POSITIVE_INFINITY;
|
|
124
|
+
for (const entry of windows.values()) retryMs = Math.min(retryMs, entry.expires - now);
|
|
125
|
+
return decision(false, policy.max, 0, Math.ceil(retryMs / 1e3));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
slot = {
|
|
129
|
+
expires: now + windowMs,
|
|
130
|
+
count: 0
|
|
131
|
+
};
|
|
132
|
+
windows.set(key, slot);
|
|
133
|
+
}
|
|
134
|
+
slot.count++;
|
|
135
|
+
return decision(slot.count <= policy.max, policy.max, Math.max(0, policy.max - slot.count), Math.ceil((slot.expires - now) / 1e3));
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function resourceRateLimitWindowMs(window) {
|
|
140
|
+
const match = /^\s*((?:\d+(?:\.\d+)?|\.\d+))\s*(ms|milliseconds?|msecs?|s|secs?|seconds?|m|mins?|minutes?|h|hrs?|hours?|d|days?|w|wks?|weeks?|y|yrs?|years?)?\s*$/i.exec(window);
|
|
141
|
+
const value = Number(match?.[1]) * ({
|
|
142
|
+
ms: 1,
|
|
143
|
+
millisecond: 1,
|
|
144
|
+
milliseconds: 1,
|
|
145
|
+
msec: 1,
|
|
146
|
+
msecs: 1,
|
|
147
|
+
s: 1e3,
|
|
148
|
+
sec: 1e3,
|
|
149
|
+
secs: 1e3,
|
|
150
|
+
second: 1e3,
|
|
151
|
+
seconds: 1e3,
|
|
152
|
+
m: 6e4,
|
|
153
|
+
min: 6e4,
|
|
154
|
+
mins: 6e4,
|
|
155
|
+
minute: 6e4,
|
|
156
|
+
minutes: 6e4,
|
|
157
|
+
h: 36e5,
|
|
158
|
+
hr: 36e5,
|
|
159
|
+
hrs: 36e5,
|
|
160
|
+
hour: 36e5,
|
|
161
|
+
hours: 36e5,
|
|
162
|
+
d: 864e5,
|
|
163
|
+
day: 864e5,
|
|
164
|
+
days: 864e5,
|
|
165
|
+
w: 6048e5,
|
|
166
|
+
wk: 6048e5,
|
|
167
|
+
wks: 6048e5,
|
|
168
|
+
week: 6048e5,
|
|
169
|
+
weeks: 6048e5,
|
|
170
|
+
y: 315576e5,
|
|
171
|
+
yr: 315576e5,
|
|
172
|
+
yrs: 315576e5,
|
|
173
|
+
year: 315576e5,
|
|
174
|
+
years: 315576e5
|
|
175
|
+
}[match?.[2]?.toLowerCase() ?? "ms"] ?? 0);
|
|
176
|
+
if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`rateLimit.timeWindow '${window}' must be a positive duration in milliseconds.`);
|
|
177
|
+
return value;
|
|
178
|
+
}
|
|
179
|
+
//#endregion
|
|
180
|
+
export { resourceRateLimitWindowMs as a, resolveResourceRateLimiter as i, markRateLimitingDisabled as n, recordUnenforcedRateLimit as r, createMemoryResourceRateLimiter as t };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { AjvCompiler } from "@fastify/ajv-compiler";
|
|
3
|
+
//#region src/core/routeValidator.ts
|
|
4
|
+
/**
|
|
5
|
+
* Arc's request validator — the AJV settings every route arc builds is validated with, on ANY host.
|
|
6
|
+
*
|
|
7
|
+
* Fastify's stock AJV runs `removeAdditional: true`, which turns `additionalProperties: false`
|
|
8
|
+
* into a silent STRIP: an unknown key in a strict action body or filter querystring reaches the
|
|
9
|
+
* handler as if it were never sent, and a dropped filter is a WIDER read. Arc's routes therefore
|
|
10
|
+
* carry their own `validatorCompiler` unless the instance was built by `createApp`, whose root
|
|
11
|
+
* AJV already runs these options (plus the host's `ajv.keywords`).
|
|
12
|
+
* Pinned by `tests/core/route-validator-bare-host.test.ts`.
|
|
13
|
+
*/
|
|
14
|
+
/** The AJV options `createApp` configures and arc's own routes are compiled with. */
|
|
15
|
+
function arcAjvCustomOptions(extraKeywords = []) {
|
|
16
|
+
return {
|
|
17
|
+
coerceTypes: true,
|
|
18
|
+
useDefaults: true,
|
|
19
|
+
removeAdditional: false,
|
|
20
|
+
strictTypes: false,
|
|
21
|
+
keywords: ["example", ...extraKeywords]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Mark an instance whose ROOT AJV was configured with `arcAjvCustomOptions` (createApp only). */
|
|
25
|
+
function markArcRootValidator(fastify) {
|
|
26
|
+
fastify.decorate("arcRootValidator", true);
|
|
27
|
+
}
|
|
28
|
+
/** One pool for the process: `@fastify/ajv-compiler` caches an AJV per (shared schemas, options). */
|
|
29
|
+
const buildFromPool = AjvCompiler();
|
|
30
|
+
/**
|
|
31
|
+
* The `validatorCompiler` an arc-built route registers with — `undefined` under `createApp`
|
|
32
|
+
* (the root already validates this way). Schemas added with `fastify.addSchema` stay
|
|
33
|
+
* `$ref`-able: the shared pool is read when Fastify compiles the route.
|
|
34
|
+
*/
|
|
35
|
+
function arcRouteValidatorCompiler(fastify) {
|
|
36
|
+
if (fastify.arcRootValidator === true) return void 0;
|
|
37
|
+
return ({ schema, httpPart }) => {
|
|
38
|
+
return buildFromPool(sharedSchemas(fastify), { customOptions: arcAjvCustomOptions() })({ schema: httpPart === "headers" ? lowerCaseHeaderSchema(schema) : schema });
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** The instance's `addSchema` pool, in the shape AJV accepts (a schema is an object or boolean). */
|
|
42
|
+
function sharedSchemas(fastify) {
|
|
43
|
+
const out = {};
|
|
44
|
+
for (const [id, schema] of Object.entries(fastify.getSchemas())) if (isSchemaValue(schema)) out[id] = schema;
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function isSchemaValue(value) {
|
|
48
|
+
return typeof value === "boolean" || typeof value === "object" && value !== null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Fastify lower-cases a headers schema's keys before compiling it with its OWN compiler and
|
|
52
|
+
* skips that for a custom one — so a route-level compiler must do it, or `Authorization` in a
|
|
53
|
+
* schema never matches the `authorization` header Node delivers.
|
|
54
|
+
*/
|
|
55
|
+
function lowerCaseHeaderSchema(schema) {
|
|
56
|
+
if (!isPlainObject(schema)) return schema;
|
|
57
|
+
const out = { ...schema };
|
|
58
|
+
if (Array.isArray(schema.required)) out.required = schema.required.map((h) => typeof h === "string" ? h.toLowerCase() : h);
|
|
59
|
+
if (isPlainObject(schema.properties)) {
|
|
60
|
+
const properties = {};
|
|
61
|
+
for (const [key, value] of Object.entries(schema.properties)) properties[key.toLowerCase()] = value;
|
|
62
|
+
out.properties = properties;
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
function isPlainObject(value) {
|
|
67
|
+
return typeof value === "object" && value !== null && Object.getPrototypeOf(value) === Object.prototype;
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { arcRouteValidatorCompiler as n, markArcRootValidator as r, arcAjvCustomOptions as t };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-D1w9Y1VJ.mjs";
|
|
3
|
+
import { a as runShutdown, i as runAdmittedWork, r as registerShutdownParticipant } from "./shutdown-MqA81z_P.mjs";
|
|
4
|
+
import { t as runWorkScope } from "./workScope-zpPyge4t.mjs";
|
|
5
|
+
import { t as startRenewingLease } from "./renewingLease-qODa7KJQ.mjs";
|
|
5
6
|
import fp from "fastify-plugin";
|
|
6
7
|
//#region src/plugins/schedules.ts
|
|
7
8
|
var schedules_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -61,10 +62,10 @@ const schedulesPlugin = async (fastify, opts) => {
|
|
|
61
62
|
}
|
|
62
63
|
stat.runs++;
|
|
63
64
|
stat.lastRunAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
64
|
-
await runWorkScope({
|
|
65
|
+
await runAdmittedWork(() => runWorkScope({
|
|
65
66
|
kind: "job",
|
|
66
67
|
id: s.name
|
|
67
|
-
}, () => s.handler(fastify));
|
|
68
|
+
}, () => s.handler(fastify)));
|
|
68
69
|
stat.lastError = null;
|
|
69
70
|
} catch (err) {
|
|
70
71
|
stat.failures++;
|
|
@@ -105,37 +106,45 @@ const schedulesPlugin = async (fastify, opts) => {
|
|
|
105
106
|
}
|
|
106
107
|
if (stats.size > 0) fastify.log.debug(`[arc-schedules] ${stats.size} schedule(s) armed${lock ? ` (leader-safe, holder ${holderId})` : ""}`);
|
|
107
108
|
});
|
|
109
|
+
registerShutdownParticipant(fastify, {
|
|
110
|
+
name: "schedules",
|
|
111
|
+
stop: () => {
|
|
112
|
+
closing = true;
|
|
113
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
114
|
+
timers.clear();
|
|
115
|
+
},
|
|
116
|
+
drain: async () => {
|
|
117
|
+
/**
|
|
118
|
+
* Let in-flight runs settle so shutdown never truncates a sweep mid-write —
|
|
119
|
+
* but BOUNDED, because a tick has no cancellation contract and its duration
|
|
120
|
+
* is not this plugin's to predict. An outbox relay whose subscriber retries
|
|
121
|
+
* on a backoff ladder holds one tick for minutes, and an unbounded await
|
|
122
|
+
* here contradicts the `unref` two functions up: pending scheduled work is
|
|
123
|
+
* already declared as something that must not hold the process.
|
|
124
|
+
*
|
|
125
|
+
* Waiting longer than the orchestrator's grace period buys nothing anyway —
|
|
126
|
+
* SIGKILL truncates the same write, without the log line naming what was
|
|
127
|
+
* still running.
|
|
128
|
+
*/
|
|
129
|
+
const pending = [...inFlight.entries()];
|
|
130
|
+
if (pending.length === 0) return;
|
|
131
|
+
let timeout;
|
|
132
|
+
const expired = Symbol("drain-timeout");
|
|
133
|
+
const budget = new Promise((resolve) => {
|
|
134
|
+
timeout = setTimeout(() => resolve(expired), drainTimeoutMs);
|
|
135
|
+
timeout.unref?.();
|
|
136
|
+
});
|
|
137
|
+
const settled = Promise.allSettled(pending.map(([, run]) => run)).then(() => void 0);
|
|
138
|
+
const outcome = await Promise.race([settled, budget]);
|
|
139
|
+
if (timeout) clearTimeout(timeout);
|
|
140
|
+
if (outcome === expired) fastify.log.warn({
|
|
141
|
+
schedules: pending.map(([name]) => name),
|
|
142
|
+
drainTimeoutMs
|
|
143
|
+
}, "[arc-schedules] shutdown drain budget exceeded — abandoning in-flight run(s). Durable work (outbox rows, leased sweeps) is retried on the next boot; raise `drainTimeoutMs` only if a listed job is genuinely non-resumable.");
|
|
144
|
+
}
|
|
145
|
+
});
|
|
108
146
|
fastify.addHook("onClose", async () => {
|
|
109
|
-
|
|
110
|
-
for (const timer of timers.values()) clearTimeout(timer);
|
|
111
|
-
timers.clear();
|
|
112
|
-
/**
|
|
113
|
-
* Let in-flight runs settle so shutdown never truncates a sweep mid-write —
|
|
114
|
-
* but BOUNDED, because a tick has no cancellation contract and its duration
|
|
115
|
-
* is not this plugin's to predict. An outbox relay whose subscriber retries
|
|
116
|
-
* on a backoff ladder holds one tick for minutes, and an unbounded await
|
|
117
|
-
* here contradicts the `unref` two functions up: pending scheduled work is
|
|
118
|
-
* already declared as something that must not hold the process.
|
|
119
|
-
*
|
|
120
|
-
* Waiting longer than the orchestrator's grace period buys nothing anyway —
|
|
121
|
-
* SIGKILL truncates the same write, without the log line naming what was
|
|
122
|
-
* still running.
|
|
123
|
-
*/
|
|
124
|
-
const pending = [...inFlight.entries()];
|
|
125
|
-
if (pending.length === 0) return;
|
|
126
|
-
let timeout;
|
|
127
|
-
const expired = Symbol("drain-timeout");
|
|
128
|
-
const budget = new Promise((resolve) => {
|
|
129
|
-
timeout = setTimeout(() => resolve(expired), drainTimeoutMs);
|
|
130
|
-
timeout.unref?.();
|
|
131
|
-
});
|
|
132
|
-
const settled = Promise.allSettled(pending.map(([, run]) => run)).then(() => void 0);
|
|
133
|
-
const outcome = await Promise.race([settled, budget]);
|
|
134
|
-
if (timeout) clearTimeout(timeout);
|
|
135
|
-
if (outcome === expired) fastify.log.warn({
|
|
136
|
-
schedules: pending.map(([name]) => name),
|
|
137
|
-
drainTimeoutMs
|
|
138
|
-
}, "[arc-schedules] shutdown drain budget exceeded — abandoning in-flight run(s). Durable work (outbox rows, leased sweeps) is retried on the next boot; raise `drainTimeoutMs` only if a listed job is genuinely non-resumable.");
|
|
147
|
+
await runShutdown(fastify);
|
|
139
148
|
});
|
|
140
149
|
};
|
|
141
150
|
var schedules_default = fp(schedulesPlugin, {
|
|
@@ -143,4 +152,4 @@ var schedules_default = fp(schedulesPlugin, {
|
|
|
143
152
|
fastify: "5.x"
|
|
144
153
|
});
|
|
145
154
|
//#endregion
|
|
146
|
-
export {
|
|
155
|
+
export { schedules_exports as n, schedules_default as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { arcLog } from "./logger
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as arcLog } from "./logger-DXjfIqjV.mjs";
|
|
3
3
|
//#region src/utils/schemaConverter.ts
|
|
4
4
|
/**
|
|
5
5
|
* Schema Converter — Detect-First, Convert-Only-When-Needed
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { o as toJsonSchema } from "./schemaConverter-
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { o as toJsonSchema } from "./schemaConverter-BORek3O-.mjs";
|
|
3
3
|
//#region src/core/schemaIR.ts
|
|
4
4
|
/**
|
|
5
5
|
* Schema IR — one canonical representation, two adapters.
|
package/dist/schemas/index.d.mts
CHANGED
|
@@ -26,12 +26,17 @@ declare function ArcListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TUn
|
|
|
26
26
|
pages: Type$1.TInteger;
|
|
27
27
|
hasNext: Type$1.TBoolean;
|
|
28
28
|
hasPrev: Type$1.TBoolean;
|
|
29
|
+
totalIsEstimate: Type$1.TOptional<Type$1.TBoolean>;
|
|
30
|
+
countedAt: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
|
|
29
31
|
}>, Type$1.TObject<{
|
|
30
32
|
method: Type$1.TLiteral<"keyset">;
|
|
31
33
|
data: Type$1.TArray<T>;
|
|
32
34
|
limit: Type$1.TInteger;
|
|
33
35
|
hasMore: Type$1.TBoolean;
|
|
34
36
|
next: Type$1.TUnion<[Type$1.TString, Type$1.TNull]>;
|
|
37
|
+
prev: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
|
|
38
|
+
hasPrev: Type$1.TOptional<Type$1.TBoolean>;
|
|
39
|
+
end: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
|
|
35
40
|
}>, Type$1.TObject<{
|
|
36
41
|
method: Type$1.TLiteral<"aggregate">;
|
|
37
42
|
data: Type$1.TArray<T>;
|
|
@@ -54,6 +59,8 @@ declare function ArcOffsetListResponse<T extends TSchema$1>(itemSchema: T): Type
|
|
|
54
59
|
pages: Type$1.TInteger;
|
|
55
60
|
hasNext: Type$1.TBoolean;
|
|
56
61
|
hasPrev: Type$1.TBoolean;
|
|
62
|
+
totalIsEstimate: Type$1.TOptional<Type$1.TBoolean>;
|
|
63
|
+
countedAt: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
|
|
57
64
|
}>;
|
|
58
65
|
/** Keyset variant — `{ method: 'keyset', data, limit, hasMore, next: string | null }`. */
|
|
59
66
|
declare function ArcKeysetListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
|
|
@@ -62,6 +69,9 @@ declare function ArcKeysetListResponse<T extends TSchema$1>(itemSchema: T): Type
|
|
|
62
69
|
limit: Type$1.TInteger;
|
|
63
70
|
hasMore: Type$1.TBoolean;
|
|
64
71
|
next: Type$1.TUnion<[Type$1.TString, Type$1.TNull]>;
|
|
72
|
+
prev: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
|
|
73
|
+
hasPrev: Type$1.TOptional<Type$1.TBoolean>;
|
|
74
|
+
end: Type$1.TOptional<Type$1.TUnion<[Type$1.TString, Type$1.TNull]>>;
|
|
65
75
|
}>;
|
|
66
76
|
/** Aggregate variant — `{ method: 'aggregate', ...same as offset }`. */
|
|
67
77
|
declare function ArcAggregateListResponse<T extends TSchema$1>(itemSchema: T): Type$1.TObject<{
|
package/dist/schemas/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
2
|
import { errorContractSchema, errorDetailSchema } from "@classytic/repo-core/errors";
|
|
3
3
|
import { Type, Type as Type$1 } from "typebox";
|
|
4
4
|
//#region src/schemas/index.ts
|
|
@@ -34,7 +34,9 @@ function ArcOffsetListResponse(itemSchema) {
|
|
|
34
34
|
total: Type$1.Integer(),
|
|
35
35
|
pages: Type$1.Integer(),
|
|
36
36
|
hasNext: Type$1.Boolean(),
|
|
37
|
-
hasPrev: Type$1.Boolean()
|
|
37
|
+
hasPrev: Type$1.Boolean(),
|
|
38
|
+
totalIsEstimate: Type$1.Optional(Type$1.Boolean()),
|
|
39
|
+
countedAt: Type$1.Optional(Type$1.Union([Type$1.String({ format: "date-time" }), Type$1.Null()]))
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
/** Keyset variant — `{ method: 'keyset', data, limit, hasMore, next: string | null }`. */
|
|
@@ -44,7 +46,10 @@ function ArcKeysetListResponse(itemSchema) {
|
|
|
44
46
|
data: Type$1.Array(itemSchema),
|
|
45
47
|
limit: Type$1.Integer(),
|
|
46
48
|
hasMore: Type$1.Boolean(),
|
|
47
|
-
next: Type$1.Union([Type$1.String(), Type$1.Null()])
|
|
49
|
+
next: Type$1.Union([Type$1.String(), Type$1.Null()]),
|
|
50
|
+
prev: Type$1.Optional(Type$1.Union([Type$1.String(), Type$1.Null()])),
|
|
51
|
+
hasPrev: Type$1.Optional(Type$1.Boolean()),
|
|
52
|
+
end: Type$1.Optional(Type$1.Union([Type$1.String(), Type$1.Null()]))
|
|
48
53
|
});
|
|
49
54
|
}
|
|
50
55
|
/** Aggregate variant — `{ method: 'aggregate', ...same as offset }`. */
|
package/dist/scim/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as parseJsonBody } from "../jsonBody-
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as parseJsonBody } from "../jsonBody-B4YVGcql.mjs";
|
|
3
3
|
import fp from "fastify-plugin";
|
|
4
4
|
//#region src/scim/errors.ts
|
|
5
5
|
var ScimError = class extends Error {
|
package/dist/scope/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { A as requireOrgId, C as hasOrgAccess, D as isOrgInScope, E as isMember, M as requireUserId, N as scopeFirstCtx, O as isService, S as getUserRoles, T as isElevated, _ as getScopeContextMap, a as ScopeFirstCtx, b as getTenantFromRequest, c as getAncestorOrgIds, d as getMandate, f as getOrgContext, g as getScopeContext, h as getRequestScope, i as RequestScope, j as requireTeamId, k as requireClientId, l as getClientId, m as getOrgRoles, n as Mandate, o as ScopeFirstCtxOptions, p as getOrgId, r as PUBLIC_SCOPE, s as ScopeFirstCtxRequest, t as AUTHENTICATED_SCOPE, u as getDPoPJkt, v as getServiceScopes, w as isAuthenticated, x as getUserId, y as getTeamId } from "../types-Ddl9OjmE.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { n as ElevationOptions, r as _default, t as ElevationEvent } from "../elevation-BW2J3tNj.mjs";
|
|
4
4
|
import { FastifyReply, FastifyRequest } from "fastify";
|
|
5
5
|
//#region src/scope/rateLimitKey.d.ts
|
|
6
6
|
interface RateLimitKeyContext {
|
|
@@ -30,4 +30,4 @@ interface ResolveOrgFromHeaderOptions {
|
|
|
30
30
|
*/
|
|
31
31
|
declare function resolveOrgFromHeader(options: ResolveOrgFromHeaderOptions): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
32
32
|
//#endregion
|
|
33
|
-
export { AUTHENTICATED_SCOPE, type ElevationEvent, type ElevationOptions, type Mandate, PUBLIC_SCOPE, type RateLimitKeyContext, type RequestScope, type ResolveOrgFromHeaderOptions, type ScopeFirstCtx, type ScopeFirstCtxOptions, type ScopeFirstCtxRequest, type TenantKeyGeneratorOptions, createTenantKeyGenerator, _default as elevationPlugin,
|
|
33
|
+
export { AUTHENTICATED_SCOPE, type ElevationEvent, type ElevationOptions, type Mandate, PUBLIC_SCOPE, type RateLimitKeyContext, type RequestScope, type ResolveOrgFromHeaderOptions, type ScopeFirstCtx, type ScopeFirstCtxOptions, type ScopeFirstCtxRequest, type TenantKeyGeneratorOptions, createTenantKeyGenerator, _default as elevationPlugin, getAncestorOrgIds, getClientId, getDPoPJkt, getMandate, getOrgContext, getOrgId, getOrgRoles, getRequestScope, getScopeContext, getScopeContextMap, getServiceScopes, getTeamId, getTenantFromRequest, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader, scopeFirstCtx };
|
package/dist/scope/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { C as isService, D as requireUserId, E as requireTeamId, O as scopeFirstCtx, S as isOrgInScope, T as requireOrgId, _ as getUserRoles, a as getDPoPJkt, b as isElevated, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserId, h as getTenantFromRequest, i as getClientId, l as getOrgRoles, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, r as getAncestorOrgIds, s as getOrgContext, t as AUTHENTICATED_SCOPE, u as getRequestScope, v as hasOrgAccess, w as requireClientId, x as isMember, y as isAuthenticated } from "../types-
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
5
|
-
import { t as resolveOrgFromHeader } from "../scope-
|
|
6
|
-
export { AUTHENTICATED_SCOPE, PUBLIC_SCOPE, createTenantKeyGenerator, elevation_default as elevationPlugin,
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { C as isService, D as requireUserId, E as requireTeamId, O as scopeFirstCtx, S as isOrgInScope, T as requireOrgId, _ as getUserRoles, a as getDPoPJkt, b as isElevated, c as getOrgId, d as getScopeContext, f as getScopeContextMap, g as getUserId, h as getTenantFromRequest, i as getClientId, l as getOrgRoles, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, r as getAncestorOrgIds, s as getOrgContext, t as AUTHENTICATED_SCOPE, u as getRequestScope, v as hasOrgAccess, w as requireClientId, x as isMember, y as isAuthenticated } from "../types-Bk9NOgOC.mjs";
|
|
3
|
+
import { t as createTenantKeyGenerator } from "../rateLimitKey-C4O-kPm0.mjs";
|
|
4
|
+
import { t as elevation_default } from "../elevation-DG8SrLN-.mjs";
|
|
5
|
+
import { t as resolveOrgFromHeader } from "../scope-DQthe88b.mjs";
|
|
6
|
+
export { AUTHENTICATED_SCOPE, PUBLIC_SCOPE, createTenantKeyGenerator, elevation_default as elevationPlugin, getAncestorOrgIds, getClientId, getDPoPJkt, getMandate, getOrgContext, getOrgId, getOrgRoles, getRequestScope, getScopeContext, getScopeContextMap, getServiceScopes, getTeamId, getTenantFromRequest, getUserId, getUserRoles, hasOrgAccess, isAuthenticated, isElevated, isMember, isOrgInScope, isService, requireClientId, requireOrgId, requireTeamId, requireUserId, resolveOrgFromHeader, scopeFirstCtx };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { a as OrgAccessDeniedError, l as UnauthorizedError } from "./errors-
|
|
3
|
-
import { n as requireSingleHeaderValue } from "./headers-
|
|
4
|
-
import { n as getUserRoles } from "./userHelpers-
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { a as OrgAccessDeniedError, l as UnauthorizedError } from "./errors-CtrywMHP.mjs";
|
|
3
|
+
import { n as requireSingleHeaderValue } from "./headers-BbkIF6vA.mjs";
|
|
4
|
+
import { n as getUserRoles } from "./userHelpers-aklawRaK.mjs";
|
|
5
5
|
//#region src/scope/resolveOrgFromHeader.ts
|
|
6
6
|
/**
|
|
7
7
|
* Create a preHandler hook that resolves org scope from a header.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
//#region src/utils/shutdown.ts
|
|
4
|
+
/** Stop admissions across every runtime before draining work and disposing domain engines. */
|
|
5
|
+
const SHUTDOWN = Symbol.for("arc.shutdown");
|
|
6
|
+
const admittedWork = new AsyncLocalStorage();
|
|
7
|
+
/** Descendants of admitted work may finish their causal publications during shutdown. */
|
|
8
|
+
async function runAdmittedWork(work) {
|
|
9
|
+
if (isAdmittedWork()) return await work();
|
|
10
|
+
const scope = { active: true };
|
|
11
|
+
return admittedWork.run(scope, async () => {
|
|
12
|
+
try {
|
|
13
|
+
return await work();
|
|
14
|
+
} finally {
|
|
15
|
+
scope.active = false;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function isAdmittedWork() {
|
|
20
|
+
return admittedWork.getStore()?.active === true;
|
|
21
|
+
}
|
|
22
|
+
/** Seed on the root before plugins register, so encapsulated plugins share one coordinator. */
|
|
23
|
+
function initShutdownRegistry(fastify) {
|
|
24
|
+
const holder = fastify;
|
|
25
|
+
if (holder[SHUTDOWN]) return;
|
|
26
|
+
holder[SHUTDOWN] = { participants: [] };
|
|
27
|
+
fastify.addHook("onClose", async () => {
|
|
28
|
+
const errors = await runShutdown(fastify);
|
|
29
|
+
for (const err of errors) fastify.log.error({ err }, "[arc] runtime shutdown failed");
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** A participant's stop must prevent new work; drain waits for work already admitted. */
|
|
33
|
+
function registerShutdownParticipant(fastify, participant) {
|
|
34
|
+
initShutdownRegistry(fastify);
|
|
35
|
+
const registry = fastify[SHUTDOWN];
|
|
36
|
+
if (!registry) throw new Error("[arc] shutdown coordinator is unavailable");
|
|
37
|
+
if (registry.completion) throw new Error(`[arc] cannot register ${participant.name} during shutdown`);
|
|
38
|
+
registry.participants.push(participant);
|
|
39
|
+
}
|
|
40
|
+
/** Once-guarded, best effort; callers dispose engines only after this completes. */
|
|
41
|
+
function runShutdown(fastify) {
|
|
42
|
+
const registry = fastify[SHUTDOWN];
|
|
43
|
+
if (!registry) return Promise.resolve([]);
|
|
44
|
+
registry.completion ??= Promise.resolve().then(async () => {
|
|
45
|
+
const errors = [];
|
|
46
|
+
for (const phase of ["stop", "drain"]) {
|
|
47
|
+
const participants = phase === "drain" ? [...registry.participants.filter((p) => p.kind !== "consumer"), ...registry.participants.filter((p) => p.kind === "consumer")] : registry.participants;
|
|
48
|
+
for (const participant of participants) try {
|
|
49
|
+
await participant[phase]?.();
|
|
50
|
+
} catch (cause) {
|
|
51
|
+
errors.push(new Error(`[arc] ${participant.name} shutdown ${phase} failed`, { cause }));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return errors;
|
|
55
|
+
});
|
|
56
|
+
return registry.completion;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { runShutdown as a, runAdmittedWork as i, isAdmittedWork as n, registerShutdownParticipant as r, initShutdownRegistry as t };
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as __exportAll } from "./rolldown-runtime-
|
|
3
|
-
import { arcLog } from "./logger
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as __exportAll } from "./rolldown-runtime-D1w9Y1VJ.mjs";
|
|
3
|
+
import { t as arcLog } from "./logger-DXjfIqjV.mjs";
|
|
4
|
+
import { n as PUBLIC_SCOPE } from "./types-Bk9NOgOC.mjs";
|
|
5
|
+
import { n as isEventVisible, t as eventAudienceOf } from "./tenancy--ed9eH_z.mjs";
|
|
6
|
+
import { a as promoteStreamTokenToHeader } from "./streaming-DjTQhZrN.mjs";
|
|
7
|
+
import { t as openSseStream } from "./sseStream-B5_Afiqn.mjs";
|
|
7
8
|
import fp from "fastify-plugin";
|
|
8
9
|
//#region src/plugins/sse.ts
|
|
9
10
|
var sse_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -12,7 +13,7 @@ var sse_exports = /* @__PURE__ */ __exportAll({
|
|
|
12
13
|
});
|
|
13
14
|
const log = arcLog("sse");
|
|
14
15
|
const ssePlugin = async (fastify, opts = {}) => {
|
|
15
|
-
const { path = "/events/stream", requireAuth = true, patterns = ["*"], heartbeat = 3e4, orgScoped =
|
|
16
|
+
const { path = "/events/stream", requireAuth = true, patterns = ["*"], heartbeat = 3e4, orgScoped = true, filter: customFilter, tokenQueryParam = "token" } = opts;
|
|
16
17
|
if (!fastify.hasDecorator("events")) {
|
|
17
18
|
log.warn("Events plugin (arc-events) not registered. SSE plugin will not function. Register eventPlugin before ssePlugin.");
|
|
18
19
|
return;
|
|
@@ -34,15 +35,10 @@ const ssePlugin = async (fastify, opts = {}) => {
|
|
|
34
35
|
const stream = openSseStream(request, reply, { heartbeatMs: heartbeat });
|
|
35
36
|
stream.onCleanup(() => activeConnections.delete(stream.close));
|
|
36
37
|
const scope = request.scope ?? PUBLIC_SCOPE;
|
|
37
|
-
const
|
|
38
|
-
const dropOrgEvents = orgScoped && !requestOrgId;
|
|
38
|
+
const audience = eventAudienceOf(scope);
|
|
39
39
|
for (const pattern of patterns) {
|
|
40
40
|
const unsub = await fastify.events.subscribe(pattern, async (event) => {
|
|
41
|
-
if (orgScoped)
|
|
42
|
-
const eventOrgId = event.meta?.organizationId;
|
|
43
|
-
if (dropOrgEvents && eventOrgId) return;
|
|
44
|
-
if (requestOrgId && eventOrgId && eventOrgId !== requestOrgId) return;
|
|
45
|
-
}
|
|
41
|
+
if (orgScoped && !isEventVisible(event.meta?.organizationId, audience)) return;
|
|
46
42
|
if (customFilter && !customFilter(event, request)) return;
|
|
47
43
|
const data = JSON.stringify({
|
|
48
44
|
type: event.type,
|
|
@@ -90,4 +86,4 @@ var sse_default = fp(ssePlugin, {
|
|
|
90
86
|
dependencies: ["arc-events"]
|
|
91
87
|
});
|
|
92
88
|
//#endregion
|
|
93
|
-
export {
|
|
89
|
+
export { sse_exports as n, sse_default as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { n as forwardedStreamHeaders } from "./streaming-
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { n as forwardedStreamHeaders } from "./streaming-DjTQhZrN.mjs";
|
|
3
3
|
//#region src/utils/sseStream.ts
|
|
4
4
|
/**
|
|
5
5
|
* Take over the reply socket and open an SSE stream. Call ONLY after every
|