@classytic/arc 2.41.1 → 2.44.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-DSqD_Xsg.mjs → EventTransport-DZWDsaPa.mjs} +22 -17
- package/dist/{HookSystem-CMF94SEA.mjs → HookSystem-yjEgn7Cm.mjs} +2 -2
- package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
- package/dist/{ResourceRegistry-MGpXoxHZ.mjs → ResourceRegistry-DJxHUpE4.mjs} +44 -13
- package/dist/acquire-BozJ-9yt.mjs +35 -0
- package/dist/actionPermissions-5N04zBL4.mjs +772 -0
- package/dist/audit/index.d.mts +46 -8
- package/dist/audit/index.mjs +48 -26
- package/dist/auth/audit.d.mts +8 -8
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +46 -16
- package/dist/auth/index.mjs +134 -156
- package/dist/auth/redis-session.d.mts +4 -5
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-DawKGoXb.mjs → authorizationDecision-wX3SA9w_.mjs} +7 -102
- package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
- package/dist/boundedMap-DWiKW7lP.mjs +57 -0
- package/dist/{buildHandler-CvxKOc0m.mjs → buildHandler-Bswu_vNb.mjs} +4 -4
- package/dist/cache/index.d.mts +31 -43
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-DbCBxlgw.mjs → caching-Drp68DIS.mjs} +3 -3
- package/dist/claimed-DYvQoZLv.mjs +8 -0
- package/dist/cleanup/index.d.mts +18 -22
- package/dist/cleanup/index.mjs +27 -73
- package/dist/cli/commands/describe.d.mts +7 -5
- package/dist/cli/commands/describe.mjs +7 -4
- package/dist/cli/commands/docs.d.mts +2 -3
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.d.mts +2 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -3
- package/dist/cli/commands/init.mjs +2 -3
- package/dist/cli/commands/introspect.d.mts +2 -3
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-D5Ubn1fe.mjs → constants-B2QHb6u6.mjs} +4 -2
- package/dist/context/index.d.mts +11 -14
- 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-5lazhva9.mjs → core-COcqo0Cp.mjs} +15 -5
- package/dist/{createActionRouter-DNqoaK81.mjs → createActionRouter-zG13rpPJ.mjs} +97 -39
- package/dist/{createAggregationRouter-BcbQJf10.mjs → createAggregationRouter-CWRz96Vr.mjs} +11 -8
- package/dist/{preloadResources-f3boenVZ.mjs → createApp-BGLYtErt.mjs} +305 -394
- package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-DSztofa9.d.mts} +1 -1
- package/dist/{defineAggregation-D_eJjm5A.mjs → defineAggregation--3xguLkk.mjs} +1 -1
- package/dist/{defineResource-DxkMTjRn.mjs → defineResource-gUp9ug66.mjs} +118 -1024
- package/dist/discovery/index.d.mts +6 -7
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +11 -5
- package/dist/docs/index.mjs +4 -4
- package/dist/{elevation-CkvG0LG2.mjs → elevation-BG4gnMsJ.mjs} +41 -12
- package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
- package/dist/encryption/index.d.mts +5 -5
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-D1GHKGlV.mjs → errorHandler-COHw3N1b.mjs} +26 -5
- package/dist/{errors-BH7YU_9O.mjs → errors-CW4anJBy.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-zZY-unhr.mjs → eventPlugin-BweHAVws.mjs} +82 -21
- package/dist/{subscribe-helpers-DatDkcuG.mjs → eventTypes-N4-omQFR.mjs} +151 -17
- package/dist/events/index.d.mts +43 -18
- 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 +5 -6
- package/dist/events/transports/redis.mjs +2 -2
- package/dist/factory/index.d.mts +42 -27
- package/dist/factory/index.mjs +213 -32
- package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DRlCIWE9.d.mts} +11 -25
- package/dist/{fields-BHrkoF5y.mjs → fields-A5crhsJb.mjs} +1 -1
- package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
- package/dist/filter-merge-DGM-GQ1J.mjs +99 -0
- package/dist/{generate-BLRf2m3y.mjs → generate-BXKo2Gc8.mjs} +2 -2
- package/dist/{headers-BIXWKrzQ.mjs → headers-DKZQKL-Z.mjs} +2 -2
- package/dist/health-BYAwA6YS.mjs +22 -0
- package/dist/{health-ZRHeDHXH.mjs → health-CrqUp3sz.mjs} +115 -55
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.mjs +2 -3
- package/dist/idempotency/index.d.mts +36 -10
- package/dist/idempotency/index.mjs +230 -111
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +77 -12
- package/dist/{index-D4u76eQW.d.mts → index-Beg3aNnK.d.mts} +23 -101
- package/dist/{preloadResources-CjS2OfpU.d.mts → index-BkV55WlV.d.mts} +37 -38
- package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
- package/dist/index-CUJvoyw3.d.mts +510 -0
- package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
- package/dist/{index-DHbyPo5j.d.mts → index-WH7xgvxS.d.mts} +304 -254
- package/dist/index.d.mts +9 -9
- package/dist/index.mjs +11 -11
- package/dist/{init-CpLkjU18.mjs → init-BPzK5Lnr.mjs} +1 -1
- package/dist/integrations/event-gateway.d.mts +11 -7
- 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 +102 -89
- package/dist/integrations/mcp/index.mjs +19 -4
- package/dist/integrations/mcp/testing.d.mts +8 -9
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.d.mts +85 -144
- package/dist/integrations/streamline.mjs +131 -55
- package/dist/integrations/webhooks.d.mts +83 -143
- package/dist/integrations/webhooks.mjs +36 -80
- package/dist/integrations/websocket-pushref-redis.d.mts +5 -6
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +5 -6
- 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-JJzwH4Ro.d.mts} +34 -2
- package/dist/{jsonBody-ELR9lfeU.mjs → jsonBody-YIq-vGLv.mjs} +2 -2
- package/dist/{keys-cpZCtcIE.mjs → keys-CpoiZ8hU.mjs} +7 -18
- package/dist/{loadResources-BPfWiI5s.mjs → loadResources-6jO52-RR.mjs} +3 -3
- package/dist/{loadResourcesFromEntry-ixtfL3J2.mjs → loadResourcesFromEntry-MP8xEppu.mjs} +2 -2
- package/dist/logger/index.d.mts +9 -17
- package/dist/logger/index.mjs +2 -153
- package/dist/logger-D4V1zGLI.mjs +160 -0
- package/dist/{mcpPlugin-BX5PujI0.mjs → mcpPlugin-srPc7ItN.mjs} +446 -170
- package/dist/{memory-Iotz0cCx.mjs → memory-CTa5pOCW.mjs} +36 -34
- package/dist/{metrics-NMsmpRmY.mjs → metrics-DsZrvnpB.mjs} +39 -72
- package/dist/middleware/index.d.mts +5 -5
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.d.mts +13 -13
- package/dist/migrations/index.mjs +2 -2
- package/dist/{module-DpLJG2_N.mjs → module--L0iBIZz.mjs} +8 -8
- package/dist/mongodb-memory-server-CHhYSq5y.mjs +35539 -0
- package/dist/mongoose-CYJCLyi-.mjs +67451 -0
- package/dist/multiTenant-CAtYlW6H.d.mts +104 -0
- package/dist/{multipartBody-DtRWM6_U.mjs → multipartBody-Cc7eZ9cq.mjs} +46 -7
- package/dist/node-D8UZv_8c.mjs +41236 -0
- package/dist/{openapi-CmFf3yq_.mjs → openapi-CMegfsm8.mjs} +48 -10
- package/dist/outbox-admin/index.d.mts +5 -5
- package/dist/outbox-admin/index.mjs +6 -18
- package/dist/{outbox-module-D2z5WkDY.mjs → outbox-module-Ryk270jd.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-D8TBH44E.mjs → permissions-C5ijIz5Q.mjs} +149 -33
- package/dist/{pipe-FMWNheSO.mjs → pipe-D81zMtWs.mjs} +2 -2
- package/dist/pipeline/index.d.mts +7 -7
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plan-CJGMKoui.mjs +565 -0
- package/dist/{BaseController-CYEwjyjN.mjs → plugin-DALd9dcF.mjs} +1269 -346
- package/dist/plugins/index.d.mts +17 -14
- package/dist/plugins/index.mjs +44 -22
- package/dist/plugins/response-cache.d.mts +5 -6
- package/dist/plugins/response-cache.mjs +8 -14
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-CXWZIzTv.mjs → pluralize-COUNYo3d.mjs} +1 -1
- package/dist/presets/filesUpload.d.mts +8 -8
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +35 -40
- 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 +9 -10
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-ZJSPgmT2.mjs → presets-CONf2Qem.mjs} +99 -107
- package/dist/{projection-BzIgzoKZ.mjs → projection-wFoLqdss.mjs} +2 -2
- package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
- package/dist/{queryCachePlugin-DZHPOm9p.mjs → queryCachePlugin-BATECpyh.mjs} +12 -14
- package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
- package/dist/{rateLimitKey-BvOvcBDi.mjs → rateLimitKey-DVEZ_UpV.mjs} +1 -1
- package/dist/{realtime-8tIQWZz4.mjs → realtime-xyubMKSB.mjs} +25 -21
- package/dist/{redis-Bzi1y3F9.d.mts → redis-BDgv9Glw.d.mts} +17 -24
- package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
- package/dist/redisShapes-CQksY67q.d.mts +55 -0
- package/dist/registry/index.d.mts +15 -10
- package/dist/registry/index.mjs +4 -4
- package/dist/{registry-DNkgGQcK.mjs → registry-Bdzg-hdm.mjs} +13 -10
- package/dist/{renewingLease-yF8BdW4b.mjs → renewingLease-BFJxGPuh.mjs} +1 -1
- package/dist/{replyHelpers-CNdvMfM4.mjs → replyHelpers-Bnc6lJyy.mjs} +2 -2
- package/dist/{requestContext-cDkAUr-P.mjs → requestContext-C10UeFe5.mjs} +1 -1
- package/dist/requirement-Bu50QALH.mjs +35 -0
- package/dist/requirement-CbiLdQ1n.d.mts +22 -0
- package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
- package/dist/resourceRateLimit-urWPJxrt.mjs +180 -0
- package/dist/rolldown-runtime-MYrLNGnq.mjs +45 -0
- package/dist/routeValidator-DHtpzrzk.mjs +70 -0
- package/dist/{runtimeCapabilities-CkhS6jn6.mjs → runtimeCapabilities-DIMKMV2G.mjs} +1 -1
- package/dist/{schedules-j6uXXAeJ.mjs → schedules-CKTs5ys8.mjs} +46 -37
- package/dist/{schemaConverter-BVA7cFRG.mjs → schemaConverter-BVjkQ5DD.mjs} +2 -2
- package/dist/{schemaIR-CTvPDoPB.mjs → schemaIR-BA0Hb21Y.mjs} +2 -2
- package/dist/schemas/index.d.mts +20 -10
- package/dist/schemas/index.mjs +8 -3
- package/dist/scim/index.d.mts +13 -13
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.d.mts +4 -4
- package/dist/scope/index.mjs +6 -6
- package/dist/{scope-BUi1xV1I.mjs → scope-DINaj7Ax.mjs} +4 -4
- package/dist/shutdown-LpLMH8T1.mjs +59 -0
- package/dist/{simpleEqualityMatcher-C9bzq2gO.mjs → simpleEqualityMatcher-DbwIM1f1.mjs} +1 -1
- package/dist/{sse-BeqVKQKu.mjs → sse-BzJFPX_1.mjs} +11 -15
- package/dist/{sseStream-Ppzo-WUH.mjs → sseStream-DQChzGTs.mjs} +2 -2
- package/dist/{store-helpers-dW71XVWL.mjs → store-helpers-Coj9RhOZ.mjs} +1 -1
- package/dist/{streaming-IcRFjWQU.mjs → streaming-JzB5CAga.mjs} +1 -1
- package/dist/subscriberIdentity-DeyppIif.mjs +28 -0
- package/dist/tenancy-DxV8OuX2.mjs +43 -0
- package/dist/tenantPreparation-CBvvAcAj.mjs +67 -0
- package/dist/testing/cleanupStoreContract.d.mts +4 -5
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +215 -197
- package/dist/testing/index.mjs +289 -214
- package/dist/testing/outboxStoreContract.d.mts +4 -5
- package/dist/testing/outboxStoreContract.mjs +137 -2
- package/dist/testing/requiredPorts.d.mts +16 -0
- package/dist/testing/requiredPorts.mjs +65 -0
- package/dist/testing/storageContract.d.mts +4 -5
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/timing-CntavTlR.mjs +46 -0
- package/dist/{transactionContext-CPbdlnVw.mjs → transactionContext-orvnGu4F.mjs} +1 -1
- package/dist/{typeGuards-BLjPgVBm.mjs → typeGuards-kEaP_WmO.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-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
- package/dist/{types-CWQn9-IC.d.mts → types-D3Ixsh00.d.mts} +22 -9
- package/dist/{types-CgenB1gv.mjs → types-fvO6Ovzx.mjs} +4 -4
- package/dist/usage/index.d.mts +5 -6
- package/dist/usage/index.mjs +5 -5
- package/dist/{userHelpers-ChRvkuSZ.mjs → userHelpers-C76zD5pa.mjs} +1 -1
- package/dist/utils/index.d.mts +125 -264
- package/dist/utils/index.mjs +13 -12
- package/dist/{utils-CxcctrAh.mjs → utils-BpkVZ8rB.mjs} +315 -404
- package/dist/{validate-DrYWnOzI.mjs → validate-CWp-lsrm.mjs} +6 -14
- package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
- package/dist/{versioning-DKkT4hNJ.mjs → versioning-CHhL9P3o.mjs} +4 -4
- package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
- package/dist/{workScope-CxKqoIFn.mjs → workScope-n9a7d_lZ.mjs} +2 -2
- package/package.json +57 -59
- 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-BoPGQ5-4.mjs +0 -350
- package/dist/index-CFfLKMH4.d.mts +0 -290
- package/dist/interface-YzIbF7Ii.mjs +0 -17
- package/dist/redis-stream-BPKscsTB.d.mts +0 -260
- package/dist/registerRoute-Bsnk6NiZ.mjs +0 -364
- package/dist/rolldown-runtime-eysyzFAF.mjs +0 -15
- package/dist/sync/changelog.d.mts +0 -3
- package/dist/sync/changelog.mjs +0 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import {
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { r as __exportAll } from "./rolldown-runtime-MYrLNGnq.mjs";
|
|
3
|
+
import { _ as pathTouchesBlocked } from "./plugin-DALd9dcF.mjs";
|
|
3
4
|
//#region src/core/aggregation/normalize.ts
|
|
4
5
|
/**
|
|
5
6
|
* Framework cap on URL-driven aggregation limits when the host opts
|
|
@@ -180,11 +181,7 @@ function buildCacheOptions(config) {
|
|
|
180
181
|
function buildExecutionHints(config) {
|
|
181
182
|
const hints = {};
|
|
182
183
|
if (typeof config.timeout === "number" && config.timeout > 0) hints.maxTimeMs = config.timeout;
|
|
183
|
-
if (config.indexHint && config.indexHint.leadingKeys.length > 0) {
|
|
184
|
-
const hintObj = {};
|
|
185
|
-
for (const key of config.indexHint.leadingKeys) hintObj[key] = 1;
|
|
186
|
-
hints.indexHint = hintObj;
|
|
187
|
-
}
|
|
184
|
+
if (config.indexHint && config.indexHint.leadingKeys.length > 0) hints.indexHint = { leadingKeys: [...config.indexHint.leadingKeys] };
|
|
188
185
|
return Object.keys(hints).length > 0 ? hints : void 0;
|
|
189
186
|
}
|
|
190
187
|
//#endregion
|
|
@@ -445,13 +442,8 @@ function validateFieldReferences(input) {
|
|
|
445
442
|
function assertFieldAllowed(context, ref, input) {
|
|
446
443
|
const { resourceName, aggregationName, lookupAliases, blockedFields } = input;
|
|
447
444
|
const dot = ref.indexOf(".");
|
|
448
|
-
if (dot > 0)
|
|
449
|
-
|
|
450
|
-
if (lookupAliases.has(alias)) return;
|
|
451
|
-
if (blockedFields.has(alias)) throw new ArcAggregationConfigError(`Resource "${resourceName}" aggregation "${aggregationName}" references field "${ref}" in ${context} whose root "${alias}" is blocked from aggregation (\`hidden: true\` or \`aggregable: false\` in schemaOptions.fieldRules). Aggregating hidden fields would leak cardinality information.`);
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
if (blockedFields.has(ref)) throw new ArcAggregationConfigError(`Resource "${resourceName}" aggregation "${aggregationName}" references field "${ref}" in ${context}, but the field is blocked from aggregation (\`hidden: true\` or \`aggregable: false\` in schemaOptions.fieldRules). Aggregating hidden fields would leak cardinality information.`);
|
|
445
|
+
if (dot > 0 && lookupAliases.has(ref.slice(0, dot))) return;
|
|
446
|
+
if (pathTouchesBlocked(ref, blockedFields, { descendants: true })) throw new ArcAggregationConfigError(`Resource "${resourceName}" aggregation "${aggregationName}" references field "${ref}" in ${context}, but the field is blocked from aggregation (\`hidden: true\` or \`aggregable: false\` in schemaOptions.fieldRules). Aggregating hidden fields would leak cardinality information.`);
|
|
455
447
|
}
|
|
456
448
|
//#endregion
|
|
457
449
|
export { compileAggRequest as i, validateAggregations as n, validate_exports as r, adapterSupportsAggregate as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { n as DomainEvent } from "./EventTransport-
|
|
2
|
+
import { n as DomainEvent } from "./EventTransport-CiicKz2Z.mjs";
|
|
3
3
|
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
4
4
|
//#region src/plugins/gracefulShutdown.d.ts
|
|
5
5
|
interface GracefulShutdownOptions {
|
|
@@ -46,7 +46,6 @@ interface ShutdownState {
|
|
|
46
46
|
/** When draining began. */
|
|
47
47
|
readonly since?: Date;
|
|
48
48
|
}
|
|
49
|
-
declare const gracefulShutdownPlugin: FastifyPluginAsync<GracefulShutdownOptions>;
|
|
50
49
|
declare module "fastify" {
|
|
51
50
|
interface FastifyInstance {
|
|
52
51
|
/** Trigger graceful shutdown manually */
|
|
@@ -82,7 +81,6 @@ interface CachingOptions {
|
|
|
82
81
|
/** Custom cache rules per path prefix */
|
|
83
82
|
rules?: CachingRule[];
|
|
84
83
|
}
|
|
85
|
-
declare const cachingPlugin: FastifyPluginAsync<CachingOptions>;
|
|
86
84
|
declare const _default$4: FastifyPluginAsync<CachingOptions>;
|
|
87
85
|
//#endregion
|
|
88
86
|
//#region src/plugins/sse.d.ts
|
|
@@ -95,7 +93,22 @@ interface SSEOptions {
|
|
|
95
93
|
patterns?: string[];
|
|
96
94
|
/** Heartbeat interval in ms (default: 30000) */
|
|
97
95
|
heartbeat?: number;
|
|
98
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* Deliver an event carrying `meta.organizationId` only to callers in that
|
|
98
|
+
* organization (default: **true** since 2.41.1).
|
|
99
|
+
*
|
|
100
|
+
* It defaulted to `false`, and with `patterns: ['*']` that meant a signed-in
|
|
101
|
+
* member of one tenant received every other tenant's domain events — CRUD
|
|
102
|
+
* events carry the full document as `data`. In a framework whose other
|
|
103
|
+
* defaults fail closed, the stream was the one door left open.
|
|
104
|
+
*
|
|
105
|
+
* Flipping it costs single-tenant apps nothing: events with no
|
|
106
|
+
* `organizationId` are delivered to every caller either way. An elevated
|
|
107
|
+
* platform admin with NO active organization still sees every tenant — the
|
|
108
|
+
* same rule `checkOrgScope` applies to elevated reads. Set `false` only for a
|
|
109
|
+
* stream that is deliberately cross-tenant (an internal ops console), and
|
|
110
|
+
* put `requireAuth` plus a `filter` in front of it.
|
|
111
|
+
*/
|
|
99
112
|
orgScoped?: boolean;
|
|
100
113
|
/** Custom event filter function */
|
|
101
114
|
filter?: (event: DomainEvent<unknown>, request: FastifyRequest) => boolean;
|
|
@@ -107,7 +120,6 @@ interface SSEOptions {
|
|
|
107
120
|
*/
|
|
108
121
|
tokenQueryParam?: string | null;
|
|
109
122
|
}
|
|
110
|
-
declare const ssePlugin: FastifyPluginAsync<SSEOptions>;
|
|
111
123
|
declare const _default$3: FastifyPluginAsync<SSEOptions>;
|
|
112
124
|
//#endregion
|
|
113
125
|
//#region src/plugins/realtime.d.ts
|
|
@@ -148,13 +160,21 @@ interface RealtimeOptions {
|
|
|
148
160
|
*/
|
|
149
161
|
maxConnectionMs?: number;
|
|
150
162
|
}
|
|
151
|
-
declare const realtimePlugin: FastifyPluginAsync<RealtimeOptions>;
|
|
152
163
|
declare const _default$2: FastifyPluginAsync<RealtimeOptions>;
|
|
153
164
|
//#endregion
|
|
165
|
+
//#region src/utils/metricSeries.d.ts
|
|
166
|
+
interface MetricSample {
|
|
167
|
+
labels: Record<string, string>;
|
|
168
|
+
value: number;
|
|
169
|
+
suffix?: "_bucket" | "_sum" | "_count";
|
|
170
|
+
}
|
|
171
|
+
//#endregion
|
|
154
172
|
//#region src/plugins/metrics.d.ts
|
|
155
173
|
interface MetricsOptions {
|
|
156
174
|
/** Endpoint path (default: '/_metrics') */
|
|
157
175
|
path?: string;
|
|
176
|
+
/** Maximum label sets per metric, plus one overflow series (default: 1000). */
|
|
177
|
+
maxSeries?: number;
|
|
158
178
|
/** Prefix for all metric names (default: 'arc') */
|
|
159
179
|
prefix?: string;
|
|
160
180
|
/** Called after metrics are collected (for OTLP push, etc.) */
|
|
@@ -164,10 +184,7 @@ interface MetricEntry {
|
|
|
164
184
|
name: string;
|
|
165
185
|
type: "counter" | "histogram" | "gauge";
|
|
166
186
|
help: string;
|
|
167
|
-
values:
|
|
168
|
-
labels: Record<string, string>;
|
|
169
|
-
value: number;
|
|
170
|
-
}>;
|
|
187
|
+
values: MetricSample[];
|
|
171
188
|
}
|
|
172
189
|
interface MetricsCollector {
|
|
173
190
|
/** Get all metrics as structured data */
|
|
@@ -192,7 +209,6 @@ declare module "fastify" {
|
|
|
192
209
|
metrics: MetricsCollector;
|
|
193
210
|
}
|
|
194
211
|
}
|
|
195
|
-
declare const metricsPlugin: FastifyPluginAsync<MetricsOptions>;
|
|
196
212
|
declare const _default$1: FastifyPluginAsync<MetricsOptions>;
|
|
197
213
|
//#endregion
|
|
198
214
|
//#region src/plugins/versioning.d.ts
|
|
@@ -225,7 +241,6 @@ declare module "fastify" {
|
|
|
225
241
|
apiVersion: string;
|
|
226
242
|
}
|
|
227
243
|
}
|
|
228
|
-
declare const versioningPlugin: FastifyPluginAsync<VersioningOptions>;
|
|
229
244
|
declare const _default: FastifyPluginAsync<VersioningOptions>;
|
|
230
245
|
//#endregion
|
|
231
|
-
export {
|
|
246
|
+
export { _default$5 as _, MetricsOptions as a, RealtimeOptions as c, _default$3 as d, CachingOptions as f, ShutdownState as g, GracefulShutdownOptions as h, MetricsCollector as i, _default$2 as l, _default$4 as m, _default as n, _default$1 as o, CachingRule as p, MetricEntry as r, ChangeOperation as s, VersioningOptions as t, SSEOptions as u };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import {
|
|
3
|
-
import { d as createDomainError } from "./errors-
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { r as __exportAll } from "./rolldown-runtime-MYrLNGnq.mjs";
|
|
3
|
+
import { d as createDomainError } from "./errors-CW4anJBy.mjs";
|
|
4
4
|
import fp from "fastify-plugin";
|
|
5
5
|
//#region src/plugins/versioning.ts
|
|
6
6
|
var versioning_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -40,4 +40,4 @@ var versioning_default = fp(versioningPlugin, {
|
|
|
40
40
|
fastify: "5.x"
|
|
41
41
|
});
|
|
42
42
|
//#endregion
|
|
43
|
-
export {
|
|
43
|
+
export { versioning_exports as n, versioning_default as t };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { a as PushRefRegistry, l as EnvelopeWriter, s as PushRefStore } from "./pushref-registry-
|
|
2
|
+
import { a as PushRefRegistry, l as EnvelopeWriter, s as PushRefStore } from "./pushref-registry-UxCkoA4C.mjs";
|
|
3
3
|
import { FastifyBaseLogger, FastifyPluginAsync } from "fastify";
|
|
4
4
|
//#region src/integrations/websocket/adapter.d.ts
|
|
5
5
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { n as requestContext } from "./requestContext-
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { n as requestContext } from "./requestContext-C10UeFe5.mjs";
|
|
3
3
|
//#region src/context/workScope.ts
|
|
4
4
|
/** Enter a scope for `fn`, or run it inside the active one. */
|
|
5
5
|
function runWorkScope(seed, fn) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.44.0",
|
|
4
4
|
"description": "Resource-oriented backend framework for Fastify - clean, minimal, powerful, tree-shakable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -208,10 +208,6 @@
|
|
|
208
208
|
"types": "./dist/auth/audit.d.mts",
|
|
209
209
|
"default": "./dist/auth/audit.mjs"
|
|
210
210
|
},
|
|
211
|
-
"./sync": {
|
|
212
|
-
"types": "./dist/sync/changelog.d.mts",
|
|
213
|
-
"default": "./dist/sync/changelog.mjs"
|
|
214
|
-
},
|
|
215
211
|
"./usage": {
|
|
216
212
|
"types": "./dist/usage/index.d.mts",
|
|
217
213
|
"default": "./dist/usage/index.mjs"
|
|
@@ -227,6 +223,10 @@
|
|
|
227
223
|
"./testing/outbox": {
|
|
228
224
|
"types": "./dist/testing/outboxStoreContract.d.mts",
|
|
229
225
|
"default": "./dist/testing/outboxStoreContract.mjs"
|
|
226
|
+
},
|
|
227
|
+
"./testing/ports": {
|
|
228
|
+
"types": "./dist/testing/requiredPorts.d.mts",
|
|
229
|
+
"default": "./dist/testing/requiredPorts.mjs"
|
|
230
230
|
}
|
|
231
231
|
},
|
|
232
232
|
"main": "./dist/index.mjs",
|
|
@@ -292,9 +292,9 @@
|
|
|
292
292
|
"./mcp/testing": "stable",
|
|
293
293
|
"./scim": "stable",
|
|
294
294
|
"./auth/audit": "stable",
|
|
295
|
-
"./sync": "experimental",
|
|
296
295
|
"./usage": "experimental",
|
|
297
296
|
"./testing/outbox": "stable",
|
|
297
|
+
"./testing/ports": "stable",
|
|
298
298
|
"./outbox-admin": "experimental"
|
|
299
299
|
}
|
|
300
300
|
},
|
|
@@ -308,7 +308,7 @@
|
|
|
308
308
|
"scripts": {
|
|
309
309
|
"build": "tsdown",
|
|
310
310
|
"dev": "tsdown --watch",
|
|
311
|
-
"typecheck": "npm run typecheck:src && npm run typecheck:types && npm run check:type-lane",
|
|
311
|
+
"typecheck": "npm run typecheck:src && npm run typecheck:types && npm run typecheck:types-strict && npm run check:type-lane",
|
|
312
312
|
"typecheck:src": "tsc --noEmit",
|
|
313
313
|
"check:type-lane": "node scripts/check-type-lane.mjs",
|
|
314
314
|
"lint": "biome check src/",
|
|
@@ -322,6 +322,7 @@
|
|
|
322
322
|
"test:watch": "vitest",
|
|
323
323
|
"test:ui": "vitest --ui",
|
|
324
324
|
"typecheck:types": "tsc -p tsconfig.types.json",
|
|
325
|
+
"typecheck:types-strict": "tsc -p tsconfig.types-strict.json",
|
|
325
326
|
"api:surface": "node scripts/check-api-surface.mjs --update",
|
|
326
327
|
"check:api-surface": "node scripts/check-api-surface.mjs",
|
|
327
328
|
"check:package": "publint && attw --pack . --profile esm-only",
|
|
@@ -337,6 +338,8 @@
|
|
|
337
338
|
"smoke": "node scripts/smoke-test.mjs",
|
|
338
339
|
"smoke:consumer": "node scripts/smoke-test.mjs --consumer",
|
|
339
340
|
"smoke:mcp": "node scripts/mcp-e2e.mjs",
|
|
341
|
+
"bench:memory": "node --expose-gc scripts/bench-memory.mjs",
|
|
342
|
+
"bench:http": "node --expose-gc scripts/bench-http.mjs",
|
|
340
343
|
"push": "classytic-push",
|
|
341
344
|
"release:tag": "node -e \"require('child_process').execSync('npm run push -- v'+require('./package.json').version,{stdio:'inherit'})\"",
|
|
342
345
|
"release": "npm run push -- main && npm run release:tag && npm publish",
|
|
@@ -353,8 +356,8 @@
|
|
|
353
356
|
"node": ">=22"
|
|
354
357
|
},
|
|
355
358
|
"peerDependencies": {
|
|
356
|
-
"@classytic/primitives": ">=0.
|
|
357
|
-
"@classytic/repo-core": ">=0.
|
|
359
|
+
"@classytic/primitives": ">=0.31.0",
|
|
360
|
+
"@classytic/repo-core": ">=0.31.0",
|
|
358
361
|
"@classytic/streamline": ">=2.8.0",
|
|
359
362
|
"@fastify/cors": ">=11.0.0",
|
|
360
363
|
"@fastify/helmet": ">=13.0.0",
|
|
@@ -476,65 +479,66 @@
|
|
|
476
479
|
}
|
|
477
480
|
},
|
|
478
481
|
"dependencies": {
|
|
482
|
+
"@fastify/ajv-compiler": "^4.0.0",
|
|
479
483
|
"fastify-plugin": "^6.0.0",
|
|
480
484
|
"qs": "^6.16.0",
|
|
481
485
|
"secure-json-parse": "^4.1.0"
|
|
482
486
|
},
|
|
483
487
|
"devDependencies": {
|
|
484
488
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
485
|
-
"@better-auth/drizzle-adapter": "^1.
|
|
486
|
-
"@better-auth/mongo-adapter": "^1.
|
|
487
|
-
"@biomejs/biome": "^2.5.
|
|
488
|
-
"@classytic/dev-tools": "^0.2.
|
|
489
|
-
"@classytic/gitkit": ">=0.
|
|
490
|
-
"@classytic/mongokit": "^3.
|
|
489
|
+
"@better-auth/drizzle-adapter": "^1.7.7",
|
|
490
|
+
"@better-auth/mongo-adapter": "^1.7.7",
|
|
491
|
+
"@biomejs/biome": "^2.5.15",
|
|
492
|
+
"@classytic/dev-tools": "^0.2.1",
|
|
493
|
+
"@classytic/gitkit": ">=0.3.0",
|
|
494
|
+
"@classytic/mongokit": "^3.41.0",
|
|
491
495
|
"@classytic/pgkit": "^0.1.0",
|
|
492
|
-
"@classytic/primitives": "^0.
|
|
493
|
-
"@classytic/repo-core": "^0.
|
|
494
|
-
"@classytic/sqlitekit": "^0.
|
|
495
|
-
"@classytic/streamline": "^2.
|
|
496
|
-
"@electric-sql/pglite": "^0.5.
|
|
497
|
-
"@fastify/cors": "^11.
|
|
498
|
-
"@fastify/helmet": "^13.0
|
|
499
|
-
"@fastify/jwt": "^10.
|
|
500
|
-
"@fastify/multipart": "^10.
|
|
501
|
-
"@fastify/rate-limit": "^11.
|
|
502
|
-
"@fastify/sensible": "^6.0.
|
|
503
|
-
"@fastify/static": "^10.1.
|
|
504
|
-
"@fastify/type-provider-typebox": "^6.
|
|
505
|
-
"@fastify/under-pressure": "^9.
|
|
506
|
-
"@fastify/websocket": "^11.
|
|
507
|
-
"@modelcontextprotocol/client": "^2.
|
|
508
|
-
"@modelcontextprotocol/node": "^2.
|
|
509
|
-
"@modelcontextprotocol/server": "^2.
|
|
496
|
+
"@classytic/primitives": "^0.33.0",
|
|
497
|
+
"@classytic/repo-core": "^0.31.1",
|
|
498
|
+
"@classytic/sqlitekit": "^0.10.0",
|
|
499
|
+
"@classytic/streamline": "^2.11.0",
|
|
500
|
+
"@electric-sql/pglite": "^0.5.9",
|
|
501
|
+
"@fastify/cors": "^11.3.1",
|
|
502
|
+
"@fastify/helmet": "^13.2.0",
|
|
503
|
+
"@fastify/jwt": "^10.2.4",
|
|
504
|
+
"@fastify/multipart": "^10.1.3",
|
|
505
|
+
"@fastify/rate-limit": "^11.2.1",
|
|
506
|
+
"@fastify/sensible": "^6.0.7",
|
|
507
|
+
"@fastify/static": "^10.1.6",
|
|
508
|
+
"@fastify/type-provider-typebox": "^6.3.0",
|
|
509
|
+
"@fastify/under-pressure": "^9.2.1",
|
|
510
|
+
"@fastify/websocket": "^11.3.4",
|
|
511
|
+
"@modelcontextprotocol/client": "^2.3.1",
|
|
512
|
+
"@modelcontextprotocol/node": "^2.1.1",
|
|
513
|
+
"@modelcontextprotocol/server": "^2.3.1",
|
|
510
514
|
"@opentelemetry/api": "^1.9.1",
|
|
511
515
|
"@types/node": "^22.19.21",
|
|
512
516
|
"@types/qs": "^6.15.1",
|
|
513
|
-
"@vitest/coverage-v8": "^5.0.
|
|
517
|
+
"@vitest/coverage-v8": "^5.0.4",
|
|
514
518
|
"ajv": "^8.20.0",
|
|
515
|
-
"better-auth": "^1.7.
|
|
519
|
+
"better-auth": "^1.7.7",
|
|
516
520
|
"better-sqlite3": "^13.0.3",
|
|
517
|
-
"bullmq": "^6.
|
|
518
|
-
"dotenv": "^
|
|
519
|
-
"drizzle-orm": "^0.45.
|
|
520
|
-
"fast-check": "^4.
|
|
521
|
-
"fastify": "^5.12.
|
|
522
|
-
"fastify-raw-body": "^6.0.
|
|
523
|
-
"h3-js": "^4.
|
|
521
|
+
"bullmq": "^6.3.15",
|
|
522
|
+
"dotenv": "^18.0.7",
|
|
523
|
+
"drizzle-orm": "^0.45.4",
|
|
524
|
+
"fast-check": "^4.10.2",
|
|
525
|
+
"fastify": "^5.12.7",
|
|
526
|
+
"fastify-raw-body": "^6.0.2",
|
|
527
|
+
"h3-js": "^4.5.0",
|
|
524
528
|
"ioredis": "^6.0.0",
|
|
525
|
-
"jose": "^6.2.
|
|
529
|
+
"jose": "^6.2.12",
|
|
526
530
|
"jsonwebtoken": "^9.0.3",
|
|
527
|
-
"knip": "^6.
|
|
528
|
-
"mongodb": "^7.
|
|
529
|
-
"mongodb-memory-server": "^11.
|
|
530
|
-
"mongoose": "^9.
|
|
531
|
-
"publint": "^0.3.
|
|
532
|
-
"tsdown": "^0.
|
|
533
|
-
"typebox": "^1.3.
|
|
531
|
+
"knip": "^6.41.0",
|
|
532
|
+
"mongodb": "^7.7.0",
|
|
533
|
+
"mongodb-memory-server": "^11.3.0",
|
|
534
|
+
"mongoose": "^9.11.2",
|
|
535
|
+
"publint": "^0.3.25",
|
|
536
|
+
"tsdown": "^0.23.1",
|
|
537
|
+
"typebox": "^1.3.37",
|
|
534
538
|
"typescript": "^7.0.2",
|
|
535
|
-
"vitest": "^5.0.
|
|
536
|
-
"ws": "^8.
|
|
537
|
-
"zod": "^4.
|
|
539
|
+
"vitest": "^5.0.4",
|
|
540
|
+
"ws": "^8.22.0",
|
|
541
|
+
"zod": "^4.6.7"
|
|
538
542
|
},
|
|
539
543
|
"config": {
|
|
540
544
|
"mongodbMemoryServer": {
|
|
@@ -564,11 +568,5 @@
|
|
|
564
568
|
"repository": {
|
|
565
569
|
"type": "git",
|
|
566
570
|
"url": "git+https://github.com/classytic/arc.git"
|
|
567
|
-
},
|
|
568
|
-
"overrides": {
|
|
569
|
-
"better-sqlite3": "$better-sqlite3",
|
|
570
|
-
"better-auth": {
|
|
571
|
-
"vitest": "$vitest"
|
|
572
|
-
}
|
|
573
571
|
}
|
|
574
572
|
}
|
package/skills/arc/SKILL.md
CHANGED
|
@@ -77,7 +77,7 @@ createApp({ role: process.env.ARC_ROLE as 'api' | 'worker' | 'relay' | 'schedule
|
|
|
77
77
|
|
|
78
78
|
`api` and `worker` mount NO module schedule arms; `relay` mounts only `kind: 'relay'` arms (the outbox relay, durable webhook delivery); `scheduler` mounts all of them. Declaring `relay`/`scheduler` together with `resources`/`resourceDir` is BOOT-FATAL — a relay process must not serve routes. Default `all` is the pre-2.33 behaviour, unchanged. `createWorker()` forwards `role`; without one it still runs everything non-HTTP.
|
|
79
79
|
|
|
80
|
-
`beforeBoot()` (2.21) runs before Fastify exists and before module thunks / resource files import — connect the DB there instead of dynamic-import ordering hacks.
|
|
80
|
+
`beforeBoot()` (2.21) runs before Fastify exists and before module thunks / resource files import — connect the DB there instead of dynamic-import ordering hacks. Return a disposer (`() => mongoose.disconnect()`) and arc runs it on close and on a failed boot. A bridge declared as `{ kind: 'bridge', name, dependsOn, load }` is never imported when inactive.
|
|
81
81
|
|
|
82
82
|
For async-booted engines, pass `resources` as a factory so it runs after `bootstrap[]`:
|
|
83
83
|
|
|
@@ -210,7 +210,14 @@ presets: ['softDelete', { name: 'multiTenant', tenantField: 'organizationId' }]
|
|
|
210
210
|
|
|
211
211
|
## Permissions
|
|
212
212
|
|
|
213
|
-
A `PermissionCheck` returns `boolean |
|
|
213
|
+
A `PermissionCheck` returns `boolean | AuthorizationDecision` — build one with `allow({ policy?, scope? })` / `deny(reason?)`. `policy` is ANDed into the repo query (row-level ABAC); `scope` stamps attributes downstream. The pre-2.30 `{ granted, filters }` shape THROWS — never emit it.
|
|
214
|
+
|
|
215
|
+
**Public but row-filtered** — use `allowPublic({ policy })`, not a hand-written `allow({ policy })`: a hand-written check lacks the `_isPublic` flag, so anonymous callers get 401.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
list: allowPublic({ policy: { status: 'published' } }), // strangers: published only
|
|
219
|
+
list: allowPublic({ policy: (ctx) => isElevated(scopeOf(ctx)) ? {} : { status: 'published' } }), // {} = unrestricted
|
|
220
|
+
```
|
|
214
221
|
|
|
215
222
|
```typescript
|
|
216
223
|
import {
|
|
@@ -233,9 +240,11 @@ permissions: {
|
|
|
233
240
|
**Custom check:**
|
|
234
241
|
|
|
235
242
|
```typescript
|
|
243
|
+
import { allow, deny, type PermissionCheck } from '@classytic/arc/permissions';
|
|
244
|
+
|
|
236
245
|
const requirePro = (): PermissionCheck => async (ctx) => {
|
|
237
|
-
if (!ctx.user) return
|
|
238
|
-
return
|
|
246
|
+
if (!ctx.user) return deny('Auth required');
|
|
247
|
+
return ctx.user.plan === 'pro' ? allow() : deny('Pro plan required');
|
|
239
248
|
};
|
|
240
249
|
```
|
|
241
250
|
|
|
@@ -497,6 +497,7 @@ import {
|
|
|
497
497
|
| Function | Description |
|
|
498
498
|
|----------|-------------|
|
|
499
499
|
| `allowPublic()` | No authentication |
|
|
500
|
+
| `allowPublic({ policy })` | No authentication, rows narrowed by a filter (or a per-caller resolver; `{}` = unrestricted) |
|
|
500
501
|
| `requireAuth()` | Any authenticated user |
|
|
501
502
|
| `requireRoles(['admin'])` | At least one role matches |
|
|
502
503
|
| `requireOwnership('userId')` | Resource owner only (elevated bypasses) |
|
|
@@ -510,9 +511,11 @@ import {
|
|
|
510
511
|
**Custom permission:**
|
|
511
512
|
|
|
512
513
|
```typescript
|
|
514
|
+
import { allow, deny, type PermissionCheck } from '@classytic/arc/permissions';
|
|
515
|
+
|
|
513
516
|
const requirePro = (): PermissionCheck => async (ctx) => {
|
|
514
|
-
if (!ctx.user) return
|
|
515
|
-
return
|
|
517
|
+
if (!ctx.user) return deny('Auth required');
|
|
518
|
+
return ctx.user.plan === 'pro' ? allow() : deny('Pro plan required');
|
|
516
519
|
};
|
|
517
520
|
```
|
|
518
521
|
|
|
@@ -646,12 +649,12 @@ Gate routes on service identity using the scope helpers + a permission builder:
|
|
|
646
649
|
|
|
647
650
|
```typescript
|
|
648
651
|
import { isService, getClientId, getServiceScopes } from '@classytic/arc/scope';
|
|
649
|
-
import type
|
|
652
|
+
import { allow, deny, type PermissionCheck } from '@classytic/arc/permissions';
|
|
650
653
|
|
|
651
654
|
const requireScope = (scope: string): PermissionCheck => (ctx) => {
|
|
652
|
-
if (!isService(ctx.request.scope)) return
|
|
655
|
+
if (!isService(ctx.request.scope)) return deny('service only');
|
|
653
656
|
const has = getServiceScopes(ctx.request.scope).includes(scope);
|
|
654
|
-
return has ?
|
|
657
|
+
return has ? allow() : deny(`missing scope: ${scope}`);
|
|
655
658
|
};
|
|
656
659
|
|
|
657
660
|
defineResource({
|
|
@@ -238,7 +238,7 @@ Single configuration point for both SSE and WebSocket with shared auth, org-scop
|
|
|
238
238
|
```typescript
|
|
239
239
|
await fastify.register(eventGatewayPlugin, {
|
|
240
240
|
auth: true, // Fail-closed for both SSE and WebSocket
|
|
241
|
-
orgScoped: true
|
|
241
|
+
// orgScoped: omitted → SSE's default (true). Set false only for a cross-tenant ops stream.
|
|
242
242
|
roomPolicy: (client, room) => {
|
|
243
243
|
return ['product', 'order', 'invoice'].includes(room);
|
|
244
244
|
},
|
|
@@ -101,18 +101,20 @@ await app.register(mcpPlugin, {
|
|
|
101
101
|
|
|
102
102
|
### Permission Filters (v2.4.2)
|
|
103
103
|
|
|
104
|
-
Resource permissions
|
|
104
|
+
Resource permissions that return a row `policy` are automatically enforced in MCP tools — same as REST:
|
|
105
105
|
|
|
106
106
|
```typescript
|
|
107
|
+
import { allow, deny } from '@classytic/arc/permissions';
|
|
108
|
+
|
|
107
109
|
defineResource({
|
|
108
110
|
name: 'task',
|
|
109
111
|
permissions: {
|
|
110
|
-
list: (ctx) =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
list: (ctx) =>
|
|
113
|
+
ctx.user
|
|
114
|
+
? allow({ policy: { orgId: ctx.user.orgId, branchId: ctx.user.branchId } })
|
|
115
|
+
: deny('Auth required'),
|
|
114
116
|
create: (ctx) => !!ctx.user, // boolean works too
|
|
115
|
-
delete: (
|
|
117
|
+
delete: () => deny('Read-only'),
|
|
116
118
|
},
|
|
117
119
|
});
|
|
118
120
|
|
|
@@ -38,15 +38,21 @@ Every request goes through this ladder — if any step is missed, tenant isolati
|
|
|
38
38
|
|
|
39
39
|
## Where `tenantField` is read
|
|
40
40
|
|
|
41
|
-
Every resource defines `tenantField` (default: `'organizationId'`, or `false` to disable).
|
|
41
|
+
Every resource defines `tenantField` (default: `'organizationId'`, or `false` to disable). **One function decides tenancy — `resolveTenancy()` in `src/core/tenancy.ts`** — and every layer switches on its result:
|
|
42
42
|
|
|
43
|
-
|
|
|
44
|
-
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
43
|
+
| Result | Meaning |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `{ mode: 'scoped', field, orgId }` | filter by `field = orgId` (a member, a service key, or an elevated admin who SELECTED an org) |
|
|
46
|
+
| `unscoped: 'platform'` | elevated admin with NO org — sees and writes across tenants |
|
|
47
|
+
| `unscoped: 'cross-tenant'` | the route declared `multiTenantPreset({ crossTenant: [...] })` — reads only; the row policy still applies |
|
|
48
|
+
| `unscoped: 'no-org'` | anonymous, or signed in with no org selected — reads get no tenant filter (the preset refuses earlier where one is required); **writes are refused** |
|
|
49
|
+
| `unscoped: 'untenanted-resource'` | `tenantField: false` |
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
Consumers: `QueryResolver` (list), `AccessControl.buildIdFilter` (get/update/delete by id) and `checkOrgScope` (post-fetch), `buildTenantRepoOptions` (what the kit is told, `bypassTenant`), the bulk mixin (`bulkCreate` stamping + `buildBulkFilter`), and — via `eventAudienceOf` / `isEventVisible` — the `sse` and `realtime` streams.
|
|
52
|
+
|
|
53
|
+
**Never re-derive the org from the scope in a new layer.** Each layer used to, with its own condition, and they drifted: `crossTenant` was taught to three of four read layers (`get` silently ignored it for signed-in members), bulk writes skipped the org for ANY elevated admin (an admin acting as org-9 deleted across every tenant), and `realtime` withheld the platform view that reads grant. The union is exhaustive, so a new mode is a compile error in every consumer that has not handled it.
|
|
54
|
+
|
|
55
|
+
The cache key (`deriveCacheScope`) deliberately does NOT use it: it answers "whose cache partition", which on a cross-tenant read is still the caller's org — dropping it could let one org's cached page answer another's.
|
|
50
56
|
|
|
51
57
|
## Filter composition
|
|
52
58
|
|
|
@@ -101,19 +107,18 @@ Return the scope directly from your permission check. This is the cleanest integ
|
|
|
101
107
|
|
|
102
108
|
```ts
|
|
103
109
|
// src/permissions/apiKey.ts
|
|
104
|
-
import type
|
|
110
|
+
import { allow, deny, type PermissionCheck } from '@classytic/arc/permissions';
|
|
105
111
|
import { ClientModel } from '../models/Client.js';
|
|
106
112
|
|
|
107
113
|
export function requireApiKey(): PermissionCheck {
|
|
108
114
|
return async ({ request }) => {
|
|
109
115
|
const apiKey = request.headers['x-api-key'] as string | undefined;
|
|
110
|
-
if (!apiKey) return
|
|
116
|
+
if (!apiKey) return deny('Missing API key');
|
|
111
117
|
|
|
112
118
|
const client = await ClientModel.findOne({ apiKey });
|
|
113
|
-
if (!client) return
|
|
119
|
+
if (!client) return deny('Invalid API key');
|
|
114
120
|
|
|
115
|
-
return {
|
|
116
|
-
granted: true,
|
|
121
|
+
return allow({
|
|
117
122
|
scope: {
|
|
118
123
|
kind: 'service',
|
|
119
124
|
clientId: String(client._id),
|
|
@@ -121,8 +126,8 @@ export function requireApiKey(): PermissionCheck {
|
|
|
121
126
|
scopes: client.allowedScopes, // optional OAuth-style scope strings
|
|
122
127
|
},
|
|
123
128
|
// Optional additional row-level narrowing
|
|
124
|
-
|
|
125
|
-
};
|
|
129
|
+
...(client.projectId ? { policy: { projectId: client.projectId } } : {}),
|
|
130
|
+
});
|
|
126
131
|
};
|
|
127
132
|
}
|
|
128
133
|
```
|