@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,10 +1,24 @@
|
|
|
1
1
|
|
|
2
|
-
import { a as EventTransport, n as DomainEvent, t as DeadLetteredEvent } from "./EventTransport-
|
|
3
|
-
import {
|
|
2
|
+
import { a as EventTransport, n as DomainEvent, t as DeadLetteredEvent } from "./EventTransport-CiicKz2Z.mjs";
|
|
3
|
+
import { n as ArcModule } from "./types-BJ-2XqVF.mjs";
|
|
4
4
|
import { FastifyInstance } from "fastify";
|
|
5
5
|
import { MemoryOutboxStore } from "@classytic/primitives/memory-outbox";
|
|
6
6
|
import { OutboxFailurePolicy, OutboxStore, OutboxWriteOptions } from "@classytic/primitives/outbox";
|
|
7
7
|
import { RepositoryLike } from "@classytic/repo-core/adapter";
|
|
8
|
+
//#region src/events/outbox/health.d.ts
|
|
9
|
+
/** Pending age past which the relay counts as stalled. */
|
|
10
|
+
declare const DEFAULT_RELAY_LAG_UNHEALTHY_MS: number;
|
|
11
|
+
interface OutboxHealth {
|
|
12
|
+
pending: number;
|
|
13
|
+
deadLetter: number;
|
|
14
|
+
/** Age of the oldest pending event, `null` when nothing is pending. */
|
|
15
|
+
relayLagMs: number | null;
|
|
16
|
+
healthy: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** A store that can report its own depth — both reads are optional on the contract. */
|
|
19
|
+
type OutboxHealthStore = OutboxStore & Required<Pick<OutboxStore, "countByStatus" | "oldestPendingAgeMs">>;
|
|
20
|
+
declare function readOutboxHealth(store: OutboxHealthStore, relayLagUnhealthyMs?: number): Promise<OutboxHealth>;
|
|
21
|
+
//#endregion
|
|
8
22
|
//#region src/events/outbox/relay.d.ts
|
|
9
23
|
/** Reason codes passed to {@link EventOutboxOptions.onError}. */
|
|
10
24
|
type OutboxRelayErrorKind = "publish_failed" | "acknowledge_failed" | "fail_failed" | "ownership_mismatch" | "malformed_event";
|
|
@@ -94,6 +108,8 @@ interface EventOutboxOptions {
|
|
|
94
108
|
* logging and metrics. Must not throw — exceptions are swallowed.
|
|
95
109
|
*/
|
|
96
110
|
readonly onError?: OutboxRelayErrorHandler;
|
|
111
|
+
/** Called with each event the transport accepted — observation only, never affects the batch. */
|
|
112
|
+
readonly onPublished?: (event: DomainEvent) => void;
|
|
97
113
|
/**
|
|
98
114
|
* Enable {@link EventTransport.publishMany} when the transport implements it.
|
|
99
115
|
* Default: `true`. Set to `false` to force per-event `publish()` — useful
|
|
@@ -166,6 +182,7 @@ declare class EventOutbox {
|
|
|
166
182
|
private readonly _consumerId;
|
|
167
183
|
private readonly _leaseMs;
|
|
168
184
|
private readonly _onError?;
|
|
185
|
+
private readonly _onPublished?;
|
|
169
186
|
private readonly _usePublishMany;
|
|
170
187
|
private readonly _sessionProvider?;
|
|
171
188
|
private readonly _traceContextProvider?;
|
|
@@ -175,9 +192,11 @@ declare class EventOutbox {
|
|
|
175
192
|
* process; resets on restart. Populated as failures occur and cleared on
|
|
176
193
|
* successful ack or dead-letter transition; additionally hard-capped at
|
|
177
194
|
* {@link MAX_TRACKED_ATTEMPTS} (oldest-write evicted first) so events
|
|
178
|
-
* resolved by another relay worker can't accumulate forever.
|
|
179
|
-
*
|
|
180
|
-
*
|
|
195
|
+
* resolved by another relay worker can't accumulate forever.
|
|
196
|
+
*
|
|
197
|
+
* A FALLBACK only: when the store reports `attempts` on the fenced claim
|
|
198
|
+
* (`repositoryAsOutboxStore` and primitives' `MemoryOutboxStore` do), the
|
|
199
|
+
* durable count wins — see the failure path in `relayBatch`.
|
|
181
200
|
*/
|
|
182
201
|
private readonly _attempts;
|
|
183
202
|
constructor(opts: EventOutboxOptions);
|
|
@@ -365,6 +384,46 @@ interface OutboxModuleOptions {
|
|
|
365
384
|
* the events the previous process failed to publish.
|
|
366
385
|
*/
|
|
367
386
|
runOnStart?: boolean;
|
|
387
|
+
/**
|
|
388
|
+
* The relay's readiness check (`/_health/ready`, name `<name>.relay`). NON-critical:
|
|
389
|
+
* a stalled relay is a cluster problem, and pulling this instance from rotation would
|
|
390
|
+
* not drain it. Unhealthy when anything is dead-lettered or the oldest pending event
|
|
391
|
+
* is older than `relayLagUnhealthyMs` (default 5 min), and when the store cannot
|
|
392
|
+
* report either. `false` omits the check.
|
|
393
|
+
*
|
|
394
|
+
* `probeCacheMs` (default 5000) is how long the check reuses its last reading.
|
|
395
|
+
* The reading costs two status counts and an oldest-pending lookup, and a
|
|
396
|
+
* readiness probe runs on EVERY replica every few seconds — uncached, 50 pods
|
|
397
|
+
* probed every 5 s is ~30 outbox queries a second, and a status count is at
|
|
398
|
+
* its most expensive during a backlog, the moment the database has least to
|
|
399
|
+
* spare. Concurrent probes also share one in-flight reading. Keep it at or
|
|
400
|
+
* below the probe interval; `0` reads on every probe. The `health()` export is
|
|
401
|
+
* never cached — an operator asking wants the current answer.
|
|
402
|
+
*
|
|
403
|
+
* `alertEveryMs` (default 5 min, `0` disables) schedules `<name>.health`, which logs
|
|
404
|
+
* while unhealthy: `marker: 'OUTBOX_RELAY_LAG'` at error, `marker: 'OUTBOX_DEAD_LETTER'`
|
|
405
|
+
* at warn. A readiness check is PULLED — nobody is paged by a degraded row in a probe
|
|
406
|
+
* body, so the stall is also pushed into the log where alerting reads it. It is a
|
|
407
|
+
* `kind: 'relay'` arm, so it runs wherever the relay runs (including a `role: 'relay'`
|
|
408
|
+
* process). A store that cannot report counts is warned about once, not every tick.
|
|
409
|
+
*/
|
|
410
|
+
health?: {
|
|
411
|
+
relayLagUnhealthyMs?: number;
|
|
412
|
+
probeCacheMs?: number;
|
|
413
|
+
alertEveryMs?: number;
|
|
414
|
+
} | false;
|
|
415
|
+
}
|
|
416
|
+
/** Totals across the passes of one {@link OutboxModuleExports.drain}. */
|
|
417
|
+
interface OutboxDrainResult {
|
|
418
|
+
passes: number;
|
|
419
|
+
relayed: number;
|
|
420
|
+
publishFailed: number;
|
|
421
|
+
deadLettered: number;
|
|
422
|
+
/**
|
|
423
|
+
* `true` once a pass found nothing VISIBLE to claim. Rows backing off after a failed
|
|
424
|
+
* publish are invisible until their retry time, so read `publishFailed` too.
|
|
425
|
+
*/
|
|
426
|
+
drained: boolean;
|
|
368
427
|
}
|
|
369
428
|
/** What the module exports at `fastify.arc.modules.<name>`. */
|
|
370
429
|
interface OutboxModuleExports {
|
|
@@ -385,6 +444,20 @@ interface OutboxModuleExports {
|
|
|
385
444
|
* The pass runs scope-free, so each event opens its own (see `runWorkScope`).
|
|
386
445
|
*/
|
|
387
446
|
requestDrain: () => void;
|
|
447
|
+
/**
|
|
448
|
+
* Relay passes until nothing visible is pending, and resolve with the totals —
|
|
449
|
+
* including events published BY the handlers it ran. For tests that assert what
|
|
450
|
+
* an event eventually did, and for a shutdown that should empty the queue.
|
|
451
|
+
* Bounded by `maxPasses` (default 100). Runs outside the caller's request scope,
|
|
452
|
+
* like {@link requestDrain}. Waits for this module's own in-flight passes (a nudge,
|
|
453
|
+
* a scheduled tick), whose leased rows no other pass can see; rows leased by
|
|
454
|
+
* ANOTHER process are not awaited.
|
|
455
|
+
*/
|
|
456
|
+
drain: (options?: {
|
|
457
|
+
maxPasses?: number;
|
|
458
|
+
}) => Promise<OutboxDrainResult>;
|
|
459
|
+
/** The same reading as the readiness check. Throws when the store cannot report. */
|
|
460
|
+
health: () => Promise<OutboxHealth>;
|
|
388
461
|
}
|
|
389
462
|
/**
|
|
390
463
|
* Compose the outbox as a module.
|
|
@@ -415,4 +488,4 @@ interface OutboxModuleExports {
|
|
|
415
488
|
*/
|
|
416
489
|
declare function createOutboxModule(options: OutboxModuleOptions): ArcModule<OutboxModuleExports>;
|
|
417
490
|
//#endregion
|
|
418
|
-
export {
|
|
491
|
+
export { MemoryOutboxStore as a, OutboxRelayErrorHandler as c, DEFAULT_RELAY_LAG_UNHEALTHY_MS as d, OutboxHealth as f, createOutboxModule as i, OutboxRelayErrorKind as l, readOutboxHealth as m, OutboxModuleExports as n, EventOutbox as o, OutboxHealthStore as p, OutboxModuleOptions as r, EventOutboxOptions as s, OutboxDrainResult as t, RelayResult as u };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { c as DataPolicy, d as UserBase, l as PermissionCheck, m as normalizeRoles, p as getUserRoles, s as AuthorizationDecision, u as PermissionContext } from "../base-
|
|
3
|
-
import { a as
|
|
4
|
-
import { $ as
|
|
5
|
-
|
|
2
|
+
import { c as DataPolicy, d as UserBase, l as PermissionCheck, m as normalizeRoles, p as getUserRoles, s as AuthorizationDecision, u as PermissionContext } from "../base-CZyixEQR.mjs";
|
|
3
|
+
import { a as applyFieldReadPermissions, c as resolveEffectiveRoles, i as FieldWritePermissionResult, n as FieldPermissionMap, o as applyFieldWritePermissions, r as FieldPermissionType, s as fields, t as FieldPermission } from "../fields-BBSpf8pS.mjs";
|
|
4
|
+
import { $ as deny, A as GrantResolution, B as explainAccess, C as requireScopeContext, D as createRoleHierarchy, E as RoleHierarchy, F as AccessExplanation, G as createDynamicPermissionMatrix, H as DynamicPermissionMatrix, I as PermissionRequirement, J as PublicPolicy, K as createOrgPermissions, L as collectPublicSurface, M as modeSatisfies, N as requireGrant, O as GRANT_MODES, P as conjoinPolicyFilters, Q as anyOf, R as describePermission, S as requireOrgRole, T as requireTeamMembership, U as DynamicPermissionMatrixConfig, V as ConnectEventsOptions, W as PermissionEventBus, X as allow, Y as allOf, Z as allowPublic, _ as publicRead, _t as requireMandate, a as PermissionMatrixRegistry, at as requireRoles, b as requireOrgInScope, c as introspectModule, ct as scopeOf, d as adminOnly, dt as evaluatePermissionDecision, et as denyAll, f as authenticated, ft as normalizeToDecision, g as presets_d_exports, gt as requireDPoP, h as permissionMatrix, ht as requireAgentScope, i as PermissionMatrixOptions, it as requirePlatformRole, j as RequireGrantOptions, k as GrantMode, l as introspectRegistry, lt as PermissionCheckReturn, m as ownerWithAdminBypass, mt as RequireMandateOptions, n as PermissionEntryType, nt as requireAuth, o as buildPermissionMatrix, ot as roles, p as fullPublic, pt as RequireAgentScopeOptions, q as AllowPublicOptions, r as PermissionMatrix, rt as requireOwnership, s as introspectCheck, st as when, t as PermissionEntry, tt as not, u as PermissionOverrides, ut as applyAuthorizationDecision, v as publicReadAdminWrite, w as requireServiceScope, x as requireOrgMembership, y as readOnly, z as describePermissionMap } from "../index-Bxriq3so.mjs";
|
|
5
|
+
import { a as createMemoryResourceRateLimiter, i as ResourceRateLimiter, n as ResourceRateLimitDecision, o as resolveResourceRateLimiter, r as ResourceRateLimitPolicy, t as ResourceRateLimitContext } from "../resourceRateLimit-Wf6d6FL9.mjs";
|
|
6
|
+
export { type AccessExplanation, type AllowPublicOptions, type AuthorizationDecision, type ConnectEventsOptions, type DataPolicy, type DynamicPermissionMatrix, type DynamicPermissionMatrixConfig, type FieldPermission, type FieldPermissionMap, type FieldPermissionType, type FieldWritePermissionResult, GRANT_MODES, type GrantMode, type GrantResolution, type PermissionCheck, type PermissionCheckReturn, type PermissionContext, type PermissionEntry, type PermissionEntryType, type PermissionEventBus, type PermissionMatrix, type PermissionMatrixOptions, type PermissionMatrixRegistry, type PermissionOverrides, type PermissionRequirement, type PublicPolicy, type RequireAgentScopeOptions, type RequireGrantOptions, type RequireMandateOptions, type ResourceRateLimitContext, type ResourceRateLimitDecision, type ResourceRateLimitPolicy, type ResourceRateLimiter, type RoleHierarchy, type UserBase, adminOnly, allOf, allow, allowPublic, anyOf, applyAuthorizationDecision, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, buildPermissionMatrix, collectPublicSurface, conjoinPolicyFilters, createDynamicPermissionMatrix, createMemoryResourceRateLimiter, createOrgPermissions, createRoleHierarchy, deny, denyAll, describePermission, describePermissionMap, evaluatePermissionDecision, explainAccess, fields, fullPublic, getUserRoles, introspectCheck, introspectModule, introspectRegistry, modeSatisfies, normalizeRoles, normalizeToDecision, not, ownerWithAdminBypass, permissionMatrix, presets_d_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireGrant, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requirePlatformRole, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, resolveResourceRateLimiter, roles, scopeOf, when };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import {
|
|
3
|
-
import { n as getUserRoles, r as normalizeRoles } from "../userHelpers-
|
|
4
|
-
import { a as resolveEffectiveRoles, i as fields, n as applyFieldWritePermissions, t as applyFieldReadPermissions } from "../fields-
|
|
5
|
-
import {
|
|
6
|
-
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as conjoinPolicyFilters } from "../filter-merge-DGM-GQ1J.mjs";
|
|
3
|
+
import { n as getUserRoles, r as normalizeRoles } from "../userHelpers-C76zD5pa.mjs";
|
|
4
|
+
import { a as resolveEffectiveRoles, i as fields, n as applyFieldWritePermissions, t as applyFieldReadPermissions } from "../fields-A5crhsJb.mjs";
|
|
5
|
+
import { i as normalizeToDecision, r as evaluatePermissionDecision, t as applyAuthorizationDecision } from "../authorizationDecision-wX3SA9w_.mjs";
|
|
6
|
+
import { i as resolveResourceRateLimiter, t as createMemoryResourceRateLimiter } from "../resourceRateLimit-urWPJxrt.mjs";
|
|
7
|
+
import { A as requireAgentScope, B as requireAuth, C as createOrgPermissions, D as requireScopeContext, E as requireOrgRole, F as allowPublic, G as when, H as requirePlatformRole, I as anyOf, K as scopeOf, L as deny, M as requireMandate, N as allOf, O as requireServiceScope, P as allow, R as denyAll, S as createDynamicPermissionMatrix, T as requireOrgMembership, U as requireRoles, V as requireOwnership, W as roles, _ as requireGrant, a as adminOnly, b as describePermissionMap, c as ownerWithAdminBypass, d as publicRead, f as publicReadAdminWrite, g as modeSatisfies, h as GRANT_MODES, i as introspectRegistry, j as requireDPoP, k as requireTeamMembership, l as permissionMatrix, m as createRoleHierarchy, n as introspectCheck, o as authenticated, p as readOnly, r as introspectModule, s as fullPublic, t as buildPermissionMatrix, u as presets_exports, v as collectPublicSurface, w as requireOrgInScope, x as explainAccess, y as describePermission, z as not } from "../permissions-C5ijIz5Q.mjs";
|
|
8
|
+
export { GRANT_MODES, adminOnly, allOf, allow, allowPublic, anyOf, applyAuthorizationDecision, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, buildPermissionMatrix, collectPublicSurface, conjoinPolicyFilters, createDynamicPermissionMatrix, createMemoryResourceRateLimiter, createOrgPermissions, createRoleHierarchy, deny, denyAll, describePermission, describePermissionMap, evaluatePermissionDecision, explainAccess, fields, fullPublic, getUserRoles, introspectCheck, introspectModule, introspectRegistry, modeSatisfies, normalizeRoles, normalizeToDecision, not, ownerWithAdminBypass, permissionMatrix, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAgentScope, requireAuth, requireDPoP, requireGrant, requireMandate, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requirePlatformRole, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, resolveEffectiveRoles, resolveResourceRateLimiter, roles, scopeOf, when };
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import {
|
|
3
|
-
import { arcLog } from "./logger
|
|
4
|
-
import {
|
|
5
|
-
import { n as getUserRoles } from "./userHelpers-
|
|
6
|
-
import { C as isService, S as isOrgInScope, a as getDPoPJkt, b as isElevated, d as getScopeContext, f as getScopeContextMap, g as getUserId, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, u as getRequestScope, v as hasOrgAccess, x as isMember } from "./types-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { r as __exportAll } from "./rolldown-runtime-MYrLNGnq.mjs";
|
|
3
|
+
import { t as arcLog } from "./logger-D4V1zGLI.mjs";
|
|
4
|
+
import { t as conjoinPolicyFilters } from "./filter-merge-DGM-GQ1J.mjs";
|
|
5
|
+
import { n as getUserRoles } from "./userHelpers-C76zD5pa.mjs";
|
|
6
|
+
import { C as isService, S as isOrgInScope, a as getDPoPJkt, b as isElevated, d as getScopeContext, f as getScopeContextMap, g as getUserId, m as getTeamId, n as PUBLIC_SCOPE, o as getMandate, p as getServiceScopes, u as getRequestScope, v as hasOrgAccess, x as isMember } from "./types-fvO6Ovzx.mjs";
|
|
7
|
+
import { i as normalizeToDecision } from "./authorizationDecision-wX3SA9w_.mjs";
|
|
8
|
+
import { n as declareRuntimeCapability } from "./runtimeCapabilities-DIMKMV2G.mjs";
|
|
9
|
+
import { t as MemoryCacheStore } from "./memory-CTa5pOCW.mjs";
|
|
9
10
|
import { randomUUID } from "node:crypto";
|
|
10
11
|
//#region src/permissions/context.ts
|
|
11
12
|
/**
|
|
@@ -76,20 +77,78 @@ function deny(reason) {
|
|
|
76
77
|
function normalizeVariadicOrArray(args) {
|
|
77
78
|
return Array.isArray(args[0]) ? args[0] : args;
|
|
78
79
|
}
|
|
80
|
+
/** A non-null, non-array object — the only shape a row policy can take. */
|
|
81
|
+
function isPolicyObject(value) {
|
|
82
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
83
|
+
}
|
|
84
|
+
/** Own keys, symbol-keyed included (a branded adapter-native filter is symbol-keyed). */
|
|
85
|
+
function hasPolicyKeys(policy) {
|
|
86
|
+
return Object.keys(policy).length > 0 || Object.getOwnPropertySymbols(policy).length > 0;
|
|
87
|
+
}
|
|
79
88
|
/**
|
|
80
|
-
* Allow public access (no authentication required).
|
|
89
|
+
* Allow public access (no authentication required) — optionally row-filtered.
|
|
90
|
+
*
|
|
91
|
+
* Without options, anyone passes and sees everything the query asks for.
|
|
92
|
+
*
|
|
93
|
+
* With `policy`, anyone still passes, but only the rows the policy admits are
|
|
94
|
+
* readable (or writable). This is the declarative form of a check that returns
|
|
95
|
+
* `allow({ policy })`, and it exists because the hand-written form has a trap:
|
|
96
|
+
* a check function is treated as PROTECTED unless it carries `_isPublic`, so a
|
|
97
|
+
* custom "public, but only published rows" check that forgets to set the flag
|
|
98
|
+
* answers every anonymous visitor with a 401 — the public page silently
|
|
99
|
+
* becomes a login wall. `allowPublic({ policy })` sets it for you.
|
|
100
|
+
*
|
|
101
|
+
* Fail-closed on both ends:
|
|
102
|
+
* - a STATIC policy must be a non-empty object — `{}` filters nothing and is
|
|
103
|
+
* almost certainly a mistake, so it throws at definition time (use
|
|
104
|
+
* `allowPublic()` for an unrestricted gate);
|
|
105
|
+
* - a RESOLVER that returns anything but an object throws per request, which
|
|
106
|
+
* arc answers as a 403 for everyone. A forgotten branch that falls off the
|
|
107
|
+
* end of the function must not quietly mean "no filter". Return `{}` when a
|
|
108
|
+
* caller genuinely should be unrestricted.
|
|
109
|
+
*
|
|
110
|
+
* Composing: inside `anyOf`, the FIRST granting branch wins, and a public
|
|
111
|
+
* branch grants everyone — so `anyOf(allowPublic({ policy }), requireRoles("admin"))`
|
|
112
|
+
* filters admins too. Put the privileged branch first, or use the resolver
|
|
113
|
+
* form and return `{}` for staff.
|
|
81
114
|
*
|
|
82
115
|
* @example
|
|
83
116
|
* ```typescript
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
117
|
+
* // Strangers see only published reviews, whatever filter they send.
|
|
118
|
+
* list: allowPublic({ policy: { status: { $in: ["published", "flagged"] } } }),
|
|
119
|
+
*
|
|
120
|
+
* // Scope-aware: the author also sees their own pending review; staff see all.
|
|
121
|
+
* list: allowPublic({
|
|
122
|
+
* policy: (ctx) => {
|
|
123
|
+
* const scope = scopeOf(ctx);
|
|
124
|
+
* if (isElevated(scope)) return {};
|
|
125
|
+
* const visible = { status: { $in: ["published", "flagged"] } };
|
|
126
|
+
* const userId = getUserId(scope);
|
|
127
|
+
* return userId ? { $or: [visible, { authorId: userId }] } : visible;
|
|
128
|
+
* },
|
|
129
|
+
* }),
|
|
88
130
|
* ```
|
|
89
131
|
*/
|
|
90
|
-
function allowPublic() {
|
|
91
|
-
const
|
|
132
|
+
function allowPublic(options) {
|
|
133
|
+
const policy = options?.policy;
|
|
134
|
+
if (policy === void 0) {
|
|
135
|
+
const check = () => true;
|
|
136
|
+
check._isPublic = true;
|
|
137
|
+
return check;
|
|
138
|
+
}
|
|
139
|
+
let check;
|
|
140
|
+
if (typeof policy === "function") check = async (ctx) => {
|
|
141
|
+
const resolved = await policy(ctx);
|
|
142
|
+
if (!isPolicyObject(resolved)) throw new Error(`[arc] allowPublic({ policy }) resolver returned ${resolved === null ? "null" : Array.isArray(resolved) ? "an array" : typeof resolved} for '${ctx.resource}:${ctx.action}'. Return a filter object, or {} to admit this caller unrestricted. Returning nothing is refused rather than read as 'no filter'.`);
|
|
143
|
+
return hasPolicyKeys(resolved) ? allow({ policy: resolved }) : allow();
|
|
144
|
+
};
|
|
145
|
+
else {
|
|
146
|
+
if (!isPolicyObject(policy)) throw new Error(`[arc] allowPublic({ policy }) expects a filter object or a function returning one, got ${policy === null ? "null" : Array.isArray(policy) ? "an array" : typeof policy}.`);
|
|
147
|
+
if (!hasPolicyKeys(policy)) throw new Error("[arc] allowPublic({ policy: {} }) filters nothing. Use allowPublic() for an unrestricted public gate, or give the policy a constraint.");
|
|
148
|
+
check = () => allow({ policy });
|
|
149
|
+
}
|
|
92
150
|
check._isPublic = true;
|
|
151
|
+
check._rowPolicy = true;
|
|
93
152
|
return check;
|
|
94
153
|
}
|
|
95
154
|
/**
|
|
@@ -262,8 +321,11 @@ function allOf(...checks) {
|
|
|
262
321
|
};
|
|
263
322
|
const roleBranches = checks.filter((c) => (c._roles?.length ?? 0) > 0 || (c._orgRoles?.length ?? 0) > 0);
|
|
264
323
|
if (checks.some((c) => c._isDenied)) check._isDenied = true;
|
|
265
|
-
else if (checks.
|
|
266
|
-
else if (
|
|
324
|
+
else if (checks.some((c) => c._deniedWhen)) check._deniedWhen = () => checks.some((c) => c._deniedWhen?.() === true);
|
|
325
|
+
if (check._isDenied) {} else if (checks.length > 0 && checks.every((c) => c._isPublic)) {
|
|
326
|
+
check._isPublic = true;
|
|
327
|
+
if (checks.some((c) => c._rowPolicy)) check._rowPolicy = true;
|
|
328
|
+
} else if (roleBranches.length === 1) {
|
|
267
329
|
const [rb] = roleBranches;
|
|
268
330
|
if (rb?._roles?.length) check._roles = [...rb._roles];
|
|
269
331
|
if (rb?._orgRoles?.length) check._orgRoles = [...rb._orgRoles];
|
|
@@ -274,7 +336,7 @@ function allOf(...checks) {
|
|
|
274
336
|
else scopeContext[dim] = val;
|
|
275
337
|
if (Object.keys(scopeContext).length > 0) check._scopeContext = scopeContext;
|
|
276
338
|
const hasMetaSurface = (c) => Boolean(c._isPublic || c._roles?.length || c._orgRoles?.length || c._scopeContext && Object.keys(c._scopeContext).length > 0);
|
|
277
|
-
const opaqueBranch = checks.some((c) => !hasMetaSurface(c) && !c._requiresAuth);
|
|
339
|
+
const opaqueBranch = checks.some((c) => !hasMetaSurface(c) && !c._requiresAuth && !c._deniedWhen);
|
|
278
340
|
const childConditional = checks.some((c) => c._conditional);
|
|
279
341
|
if (opaqueBranch || scopeConflict || childConditional) check._conditional = true;
|
|
280
342
|
return check;
|
|
@@ -302,9 +364,13 @@ function anyOf(...checks) {
|
|
|
302
364
|
}
|
|
303
365
|
return deny(reasons.join("; "));
|
|
304
366
|
};
|
|
367
|
+
const closed = (c) => Boolean(c._isDenied || c._deniedWhen?.());
|
|
305
368
|
if (checks.length > 0 && checks.every((c) => c._isDenied)) check._isDenied = true;
|
|
306
|
-
else if (checks.
|
|
307
|
-
else if (checks.
|
|
369
|
+
else if (checks.length > 0 && checks.every((c) => c._isDenied || c._deniedWhen)) check._deniedWhen = () => checks.every(closed);
|
|
370
|
+
else if (checks.some((c) => c._isPublic)) {
|
|
371
|
+
check._isPublic = true;
|
|
372
|
+
if (checks.find((c) => c._isPublic)?._rowPolicy) check._rowPolicy = true;
|
|
373
|
+
} else if (checks.every((c) => (c._roles?.length ?? 0) > 0 || (c._orgRoles?.length ?? 0) > 0)) {
|
|
308
374
|
const roles = /* @__PURE__ */ new Set();
|
|
309
375
|
const orgRoles = /* @__PURE__ */ new Set();
|
|
310
376
|
for (const c of checks) {
|
|
@@ -314,9 +380,27 @@ function anyOf(...checks) {
|
|
|
314
380
|
if (roles.size > 0) check._roles = [...roles];
|
|
315
381
|
if (orgRoles.size > 0) check._orgRoles = [...orgRoles];
|
|
316
382
|
if (checks.some((c) => c._conditional)) check._conditional = true;
|
|
383
|
+
const scopes = checks.map((c) => c._scopeContext ?? {});
|
|
384
|
+
const key = (s) => JSON.stringify(Object.entries(s).sort(([a], [b]) => a.localeCompare(b)));
|
|
385
|
+
const first = scopes[0] ?? {};
|
|
386
|
+
if (scopes.every((s) => key(s) === key(first))) {
|
|
387
|
+
if (Object.keys(first).length > 0) check._scopeContext = { ...first };
|
|
388
|
+
} else check._conditional = true;
|
|
389
|
+
} else if (checks.some(isMembershipGate) && checks.every((c) => isMembershipGate(c) || isPlainRoleGate(c))) {
|
|
390
|
+
check._orgPermission = "membership";
|
|
391
|
+
const roles = /* @__PURE__ */ new Set();
|
|
392
|
+
const orgRoles = /* @__PURE__ */ new Set();
|
|
393
|
+
for (const c of checks) {
|
|
394
|
+
for (const r of c._roles ?? []) roles.add(r);
|
|
395
|
+
for (const r of c._orgRoles ?? []) orgRoles.add(r);
|
|
396
|
+
}
|
|
397
|
+
if (roles.size > 0) check._roles = [...roles];
|
|
398
|
+
if (orgRoles.size > 0) check._orgRoles = [...orgRoles];
|
|
317
399
|
}
|
|
318
400
|
return check;
|
|
319
401
|
}
|
|
402
|
+
const isMembershipGate = (c) => c._orgPermission === "membership" && !c._conditional && !c._scopeContext;
|
|
403
|
+
const isPlainRoleGate = (c) => ((c._roles?.length ?? 0) > 0 || (c._orgRoles?.length ?? 0) > 0) && !c._conditional && !c._scopeContext;
|
|
320
404
|
/**
|
|
321
405
|
* Invert a permission check. Grants when the wrapped check denies, denies
|
|
322
406
|
* when the wrapped check grants. Useful for "block if X" patterns —
|
|
@@ -1085,25 +1169,33 @@ function createDynamicPermissionMatrix(config) {
|
|
|
1085
1169
|
* fully captured statically carries `conditional: true` (see `_conditional`).
|
|
1086
1170
|
*/
|
|
1087
1171
|
function describePermission(check) {
|
|
1088
|
-
if (check._isDenied) return { kind: "denied" };
|
|
1089
|
-
if (check._isPublic) return
|
|
1172
|
+
if (check._isDenied || check._deniedWhen?.() === true) return { kind: "denied" };
|
|
1173
|
+
if (check._isPublic) return check._rowPolicy ? {
|
|
1174
|
+
kind: "public",
|
|
1175
|
+
rowFiltered: true
|
|
1176
|
+
} : { kind: "public" };
|
|
1090
1177
|
const roles = [...check._roles ?? [], ...check._orgRoles ?? []];
|
|
1091
1178
|
const scope = check._scopeContext;
|
|
1092
|
-
const conditional = check._conditional
|
|
1179
|
+
const conditional = check._conditional === true;
|
|
1180
|
+
const flag = conditional ? { conditional: true } : {};
|
|
1093
1181
|
if (scope && Object.keys(scope).length > 0) return roles.length > 0 ? {
|
|
1094
1182
|
kind: "scoped",
|
|
1095
1183
|
scope,
|
|
1096
1184
|
roles,
|
|
1097
|
-
|
|
1185
|
+
...flag
|
|
1098
1186
|
} : {
|
|
1099
1187
|
kind: "scoped",
|
|
1100
1188
|
scope,
|
|
1101
|
-
|
|
1189
|
+
...flag
|
|
1102
1190
|
};
|
|
1191
|
+
if (check._orgPermission === "membership" && !conditional) return roles.length > 0 ? {
|
|
1192
|
+
kind: "member",
|
|
1193
|
+
roles
|
|
1194
|
+
} : { kind: "member" };
|
|
1103
1195
|
if (roles.length > 0) return {
|
|
1104
1196
|
kind: "roles",
|
|
1105
1197
|
roles,
|
|
1106
|
-
|
|
1198
|
+
...flag
|
|
1107
1199
|
};
|
|
1108
1200
|
return { kind: "authenticated" };
|
|
1109
1201
|
}
|
|
@@ -1131,7 +1223,7 @@ function explainAccess(check, principal) {
|
|
|
1131
1223
|
};
|
|
1132
1224
|
if (requirement.kind === "public") return {
|
|
1133
1225
|
decision: "allow",
|
|
1134
|
-
reason: "public — no authentication required",
|
|
1226
|
+
reason: requirement.rowFiltered ? "public — no authentication required; rows narrowed by a policy" : "public — no authentication required",
|
|
1135
1227
|
requirement
|
|
1136
1228
|
};
|
|
1137
1229
|
if (requirement.kind === "authenticated") return {
|
|
@@ -1140,6 +1232,29 @@ function explainAccess(check, principal) {
|
|
|
1140
1232
|
requirement
|
|
1141
1233
|
};
|
|
1142
1234
|
const held = /* @__PURE__ */ new Set([...principal?.roles ?? [], ...principal?.orgRoles ?? []]);
|
|
1235
|
+
if (requirement.kind === "member") {
|
|
1236
|
+
const reason = `requires membership of the active organization${requirement.roles?.length ? ` or one of: ${requirement.roles.join(", ")}` : ""}`;
|
|
1237
|
+
const byRole = requirement.roles?.filter((r) => held.has(r)) ?? [];
|
|
1238
|
+
if (byRole.length > 0) return {
|
|
1239
|
+
decision: "allow",
|
|
1240
|
+
reason: `granted via role(s): ${byRole.join(", ")}`,
|
|
1241
|
+
requirement
|
|
1242
|
+
};
|
|
1243
|
+
if (principal?.member === void 0) return {
|
|
1244
|
+
decision: "conditional",
|
|
1245
|
+
reason,
|
|
1246
|
+
requirement
|
|
1247
|
+
};
|
|
1248
|
+
return principal.member ? {
|
|
1249
|
+
decision: "allow",
|
|
1250
|
+
reason: "granted as a member of the active organization",
|
|
1251
|
+
requirement
|
|
1252
|
+
} : {
|
|
1253
|
+
decision: "deny",
|
|
1254
|
+
reason,
|
|
1255
|
+
requirement
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1143
1258
|
if (requirement.kind === "scoped") {
|
|
1144
1259
|
const scopeReason = describeScope(requirement.scope);
|
|
1145
1260
|
const rolesReason = requirement.roles?.length ? `role(s) ${requirement.roles.join(", ")} + ` : "";
|
|
@@ -1600,12 +1715,9 @@ function readOnly(overrides) {
|
|
|
1600
1715
|
*
|
|
1601
1716
|
* ## Known gap
|
|
1602
1717
|
*
|
|
1603
|
-
* `
|
|
1604
|
-
*
|
|
1605
|
-
*
|
|
1606
|
-
* server decides" rather than "any logged-in user", and fall back to an
|
|
1607
|
-
* explicit gate for those. Fixing it means teaching the combinators to
|
|
1608
|
-
* aggregate meta — an arc change, tracked here because this is where it shows.
|
|
1718
|
+
* `authenticated` means "the server decides" — a custom or request-time check
|
|
1719
|
+
* (ownership, service scope) — never "any logged-in user". Membership and
|
|
1720
|
+
* role/membership disjunctions are `member`; only opaque branches remain here.
|
|
1609
1721
|
*/
|
|
1610
1722
|
/** Re-shape arc's typed requirement into the wire entry. */
|
|
1611
1723
|
function introspectCheck(check) {
|
|
@@ -1623,6 +1735,10 @@ function introspectCheck(check) {
|
|
|
1623
1735
|
roles: req.roles ? [...req.roles] : [],
|
|
1624
1736
|
scope: { ...req.scope }
|
|
1625
1737
|
};
|
|
1738
|
+
if (req.kind === "member") return {
|
|
1739
|
+
type: "member",
|
|
1740
|
+
roles: req.roles ? [...req.roles] : []
|
|
1741
|
+
};
|
|
1626
1742
|
if (req.kind === "denied") return {
|
|
1627
1743
|
type: "denied",
|
|
1628
1744
|
roles: []
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { r as ForbiddenError } from "./errors-
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { r as ForbiddenError } from "./errors-CW4anJBy.mjs";
|
|
3
3
|
//#region src/pipeline/pipe.ts
|
|
4
4
|
/**
|
|
5
5
|
* Compose pipeline steps into an ordered array.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
2
|
+
import { Vn as IControllerResponse, _t as PipelineConfig, bt as Transform, gt as OperationFilter, ht as NextFunction, mt as Interceptor, pt as Guard, vt as PipelineContext, yt as PipelineStep } from "../index-WH7xgvxS.mjs";
|
|
3
3
|
//#region src/pipeline/guard.d.ts
|
|
4
4
|
interface GuardOptions {
|
|
5
5
|
operations?: OperationFilter;
|
|
@@ -11,7 +11,7 @@ interface GuardOptions {
|
|
|
11
11
|
* @param name - Guard name (for debugging/introspection)
|
|
12
12
|
* @param handlerOrOptions - Handler function or options object
|
|
13
13
|
*/
|
|
14
|
-
declare function guard(name: string, handlerOrOptions: ((ctx: PipelineContext) => boolean | Promise<boolean>) | GuardOptions): Guard;
|
|
14
|
+
export declare function guard(name: string, handlerOrOptions: ((ctx: PipelineContext) => boolean | Promise<boolean>) | GuardOptions): Guard;
|
|
15
15
|
//#endregion
|
|
16
16
|
//#region src/pipeline/intercept.d.ts
|
|
17
17
|
interface InterceptOptions {
|
|
@@ -24,14 +24,14 @@ interface InterceptOptions {
|
|
|
24
24
|
* @param name - Interceptor name (for debugging/introspection)
|
|
25
25
|
* @param handlerOrOptions - Handler function or options object
|
|
26
26
|
*/
|
|
27
|
-
declare function intercept(name: string, handlerOrOptions: ((ctx: PipelineContext, next: NextFunction) => Promise<IControllerResponse<unknown>>) | InterceptOptions): Interceptor;
|
|
27
|
+
export declare function intercept(name: string, handlerOrOptions: ((ctx: PipelineContext, next: NextFunction) => Promise<IControllerResponse<unknown>>) | InterceptOptions): Interceptor;
|
|
28
28
|
//#endregion
|
|
29
29
|
//#region src/pipeline/pipe.d.ts
|
|
30
30
|
/**
|
|
31
31
|
* Compose pipeline steps into an ordered array.
|
|
32
32
|
* Accepts guards, transforms, and interceptors in any order.
|
|
33
33
|
*/
|
|
34
|
-
declare function pipe(...steps: PipelineStep[]): PipelineStep[];
|
|
34
|
+
export declare function pipe(...steps: PipelineStep[]): PipelineStep[];
|
|
35
35
|
/**
|
|
36
36
|
* Execute a pipeline against a request context.
|
|
37
37
|
*
|
|
@@ -44,7 +44,7 @@ declare function pipe(...steps: PipelineStep[]): PipelineStep[];
|
|
|
44
44
|
* @param operation - The CRUD operation name
|
|
45
45
|
* @returns The controller response (possibly modified by interceptors)
|
|
46
46
|
*/
|
|
47
|
-
declare function executePipeline(steps: PipelineStep[], ctx: PipelineContext, handler: (ctx: PipelineContext) => Promise<IControllerResponse<unknown>>, operation: string): Promise<IControllerResponse<unknown>>;
|
|
47
|
+
export declare function executePipeline(steps: PipelineStep[], ctx: PipelineContext, handler: (ctx: PipelineContext) => Promise<IControllerResponse<unknown>>, operation: string): Promise<IControllerResponse<unknown>>;
|
|
48
48
|
//#endregion
|
|
49
49
|
//#region src/pipeline/transform.d.ts
|
|
50
50
|
interface TransformOptions {
|
|
@@ -57,6 +57,6 @@ interface TransformOptions {
|
|
|
57
57
|
* @param name - Transform name (for debugging/introspection)
|
|
58
58
|
* @param handlerOrOptions - Handler function or options object
|
|
59
59
|
*/
|
|
60
|
-
declare function transform(name: string, handlerOrOptions: ((ctx: PipelineContext) => PipelineContext | undefined | Promise<PipelineContext | undefined>) | TransformOptions): Transform;
|
|
60
|
+
export declare function transform(name: string, handlerOrOptions: ((ctx: PipelineContext) => PipelineContext | undefined | Promise<PipelineContext | undefined>) | TransformOptions): Transform;
|
|
61
61
|
//#endregion
|
|
62
|
-
export {
|
|
62
|
+
export type { Guard, GuardOptions, InterceptOptions, Interceptor, NextFunction, OperationFilter, PipelineConfig, PipelineContext, PipelineStep, Transform, TransformOptions };
|
package/dist/pipeline/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { n as pipe, t as executePipeline } from "../pipe-
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { n as pipe, t as executePipeline } from "../pipe-D81zMtWs.mjs";
|
|
3
3
|
//#region src/pipeline/guard.ts
|
|
4
4
|
/**
|
|
5
5
|
* Create a named guard.
|