@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 { t as claimedAttempts } from "../claimed-DYvQoZLv.mjs";
|
|
3
|
+
import { OutboxConflictError, OutboxOwnershipError } from "@classytic/primitives/outbox";
|
|
3
4
|
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
4
5
|
//#region src/testing/outboxStoreContract.ts
|
|
5
6
|
let seq = 0;
|
|
@@ -81,6 +82,26 @@ function runOutboxStoreContract(name, setup) {
|
|
|
81
82
|
expect((await store.getPending(10)).length).toBe(0);
|
|
82
83
|
});
|
|
83
84
|
});
|
|
85
|
+
describe("save — one row per event id", () => {
|
|
86
|
+
it("a second save of the same id and payload is a no-op", async () => {
|
|
87
|
+
const e = event();
|
|
88
|
+
await store.save(e);
|
|
89
|
+
await store.save({
|
|
90
|
+
...e,
|
|
91
|
+
meta: { ...e.meta }
|
|
92
|
+
});
|
|
93
|
+
expect((await store.getPending(10)).map(idOf)).toEqual([idOf(e)]);
|
|
94
|
+
});
|
|
95
|
+
it("THROWS OutboxConflictError when the same id arrives with a different payload", async () => {
|
|
96
|
+
const e = event();
|
|
97
|
+
await store.save(e);
|
|
98
|
+
await expect(store.save({
|
|
99
|
+
...e,
|
|
100
|
+
payload: { n: -1 }
|
|
101
|
+
})).rejects.toBeInstanceOf(OutboxConflictError);
|
|
102
|
+
expect((await store.getPending(10)).length).toBe(1);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
84
105
|
describe("getPending", () => {
|
|
85
106
|
it("returns events FIFO (oldest first)", async () => {
|
|
86
107
|
const saved = await seed(3);
|
|
@@ -164,6 +185,120 @@ function runOutboxStoreContract(name, setup) {
|
|
|
164
185
|
expect(claimed.map(idOf)).toEqual(saved.slice(0, 2).map(idOf));
|
|
165
186
|
});
|
|
166
187
|
});
|
|
188
|
+
/**
|
|
189
|
+
* The fenced claim had NO coverage here, though it is the path the relay
|
|
190
|
+
* prefers and the one fencing exists for. Two guarantees, both silent when
|
|
191
|
+
* broken:
|
|
192
|
+
* - a re-claim after lease expiry mints a STRICTLY GREATER token, so the
|
|
193
|
+
* crashed-and-resumed ex-holder cannot acknowledge an event it no longer
|
|
194
|
+
* owns;
|
|
195
|
+
* - `attempts`, when a store reports it, is the DURABLE count — it must
|
|
196
|
+
* rise across claims by different consumers, because a count that each
|
|
197
|
+
* relay keeps for itself is the thing it exists to replace.
|
|
198
|
+
*/
|
|
199
|
+
describe("claimPendingFenced — fencing tokens + durable attempts", () => {
|
|
200
|
+
it("mints a STRICTLY GREATER token when a lease is taken over", async ({ skip }) => {
|
|
201
|
+
if (!store.claimPendingFenced) return skip();
|
|
202
|
+
await seed(1);
|
|
203
|
+
const [first] = await store.claimPendingFenced({
|
|
204
|
+
consumerId: "dead",
|
|
205
|
+
limit: 1,
|
|
206
|
+
leaseMs: 0
|
|
207
|
+
});
|
|
208
|
+
const [second] = await store.claimPendingFenced({
|
|
209
|
+
consumerId: "c2",
|
|
210
|
+
limit: 1
|
|
211
|
+
});
|
|
212
|
+
if (!first || !second) return skip();
|
|
213
|
+
expect(second.fencingToken).toBeGreaterThan(first.fencingToken);
|
|
214
|
+
});
|
|
215
|
+
it("REJECTS an acknowledge carrying the superseded token", async ({ skip }) => {
|
|
216
|
+
if (!store.claimPendingFenced) return skip();
|
|
217
|
+
await seed(1);
|
|
218
|
+
const [stale] = await store.claimPendingFenced({
|
|
219
|
+
consumerId: "dead",
|
|
220
|
+
limit: 1,
|
|
221
|
+
leaseMs: 0
|
|
222
|
+
});
|
|
223
|
+
const [current] = await store.claimPendingFenced({
|
|
224
|
+
consumerId: "c2",
|
|
225
|
+
limit: 1
|
|
226
|
+
});
|
|
227
|
+
if (!stale || !current) return skip();
|
|
228
|
+
await expect(store.acknowledge(idOf(stale.event), { fencingToken: stale.fencingToken })).rejects.toBeInstanceOf(OutboxOwnershipError);
|
|
229
|
+
});
|
|
230
|
+
it("reports attempts = 1 on the first claim, when it reports attempts at all", async ({ skip }) => {
|
|
231
|
+
if (!store.claimPendingFenced) return skip();
|
|
232
|
+
await seed(1);
|
|
233
|
+
const [claimed] = await store.claimPendingFenced({
|
|
234
|
+
consumerId: "c1",
|
|
235
|
+
limit: 1
|
|
236
|
+
});
|
|
237
|
+
const attempts = claimed ? claimedAttempts(claimed) : void 0;
|
|
238
|
+
if (attempts === void 0) return skip();
|
|
239
|
+
expect(attempts).toBe(1);
|
|
240
|
+
});
|
|
241
|
+
it("attempts is DURABLE — it rises across claims by DIFFERENT consumers", async ({ skip }) => {
|
|
242
|
+
if (!store.claimPendingFenced || !store.fail) return skip();
|
|
243
|
+
await seed(1);
|
|
244
|
+
const seen = [];
|
|
245
|
+
for (const consumerId of [
|
|
246
|
+
"relay-a",
|
|
247
|
+
"relay-b",
|
|
248
|
+
"relay-c"
|
|
249
|
+
]) {
|
|
250
|
+
const [claimed] = await store.claimPendingFenced({
|
|
251
|
+
consumerId,
|
|
252
|
+
limit: 1
|
|
253
|
+
});
|
|
254
|
+
if (!claimed) return skip();
|
|
255
|
+
const attempts = claimedAttempts(claimed);
|
|
256
|
+
if (attempts === void 0) return skip();
|
|
257
|
+
seen.push(attempts);
|
|
258
|
+
await store.fail(idOf(claimed.event), { message: "boom" }, {
|
|
259
|
+
consumerId,
|
|
260
|
+
fencingToken: claimed.fencingToken
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
expect(seen).toEqual([
|
|
264
|
+
1,
|
|
265
|
+
2,
|
|
266
|
+
3
|
|
267
|
+
]);
|
|
268
|
+
});
|
|
269
|
+
it("requeue RESETS attempts — a re-driven event starts over", async ({ skip }) => {
|
|
270
|
+
if (!store.claimPendingFenced || !store.fail || !store.requeue) return skip();
|
|
271
|
+
await seed(1);
|
|
272
|
+
const [claimed] = await store.claimPendingFenced({
|
|
273
|
+
consumerId: "c1",
|
|
274
|
+
limit: 1
|
|
275
|
+
});
|
|
276
|
+
if (!claimed || claimedAttempts(claimed) === void 0) return skip();
|
|
277
|
+
await store.fail(idOf(claimed.event), { message: "fatal" }, {
|
|
278
|
+
consumerId: "c1",
|
|
279
|
+
fencingToken: claimed.fencingToken,
|
|
280
|
+
deadLetter: true
|
|
281
|
+
});
|
|
282
|
+
expect(await store.requeue(idOf(claimed.event))).toBe(true);
|
|
283
|
+
const [again] = await store.claimPendingFenced({
|
|
284
|
+
consumerId: "c2",
|
|
285
|
+
limit: 1
|
|
286
|
+
});
|
|
287
|
+
expect(again ? claimedAttempts(again) : void 0).toBe(1);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
describe("acknowledgeMany — one write per flush", () => {
|
|
291
|
+
it("marks every listed event delivered and ignores unknown ids", async ({ skip }) => {
|
|
292
|
+
if (!store.acknowledgeMany) return skip();
|
|
293
|
+
const events = await seed(3);
|
|
294
|
+
await store.acknowledgeMany([
|
|
295
|
+
idOf(events[0]),
|
|
296
|
+
idOf(events[2]),
|
|
297
|
+
"nope"
|
|
298
|
+
]);
|
|
299
|
+
expect((await store.getPending(10)).map(idOf)).toEqual([idOf(events[1])]);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
167
302
|
describe("acknowledge", () => {
|
|
168
303
|
it("removes the event from pending", async () => {
|
|
169
304
|
const [e] = await seed(1);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import { r as ArcModuleInput } from "../types-BJ-2XqVF.mjs";
|
|
3
|
+
//#region src/testing/requiredPorts.d.ts
|
|
4
|
+
export interface RequiredPortsOptions {
|
|
5
|
+
/** Modules composed alongside — the peers the module `dependsOn` or `requires.modules`. */
|
|
6
|
+
readonly with?: readonly ArcModuleInput[];
|
|
7
|
+
/** Ports supplied in every case — the peers' own required ports. Never one under test. Values are never read. */
|
|
8
|
+
readonly ports?: Readonly<Record<string, unknown>>;
|
|
9
|
+
}
|
|
10
|
+
export interface RequiredPortsResult {
|
|
11
|
+
readonly module: string;
|
|
12
|
+
/** The required ports, in declared order. */
|
|
13
|
+
readonly ports: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function assertRequiresPorts(module: ArcModuleInput, ports: readonly string[], options?: RequiredPortsOptions): Promise<RequiredPortsResult>;
|
|
16
|
+
//#endregion
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { c as declareModules, d as resolveModule, i as planModules, r as compositionProblems } from "../plan-CJGMKoui.mjs";
|
|
3
|
+
//#region src/testing/requiredPorts.ts
|
|
4
|
+
/**
|
|
5
|
+
* Assert a module declares EXACTLY `ports` as required, and that composing it without any one of
|
|
6
|
+
* them fails naming the module and that port — alone, and all together in one error. `[]` asserts
|
|
7
|
+
* it declares none and plans with nothing supplied. `with` composes peers it depends on; `ports`
|
|
8
|
+
* supplies what they need. Pass `ports` as a literal, never the module's own list: dropping a port
|
|
9
|
+
* from the module must fail here.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* it("fails loud without each port", () =>
|
|
13
|
+
* assertRequiresPorts(createOrderModule(deps), ["connection", "outbox"], { with: [catalog] }));
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
async function assertRequiresPorts(module, ports, options = {}) {
|
|
17
|
+
const peers = options.with ?? [];
|
|
18
|
+
const base = options.ports ?? {};
|
|
19
|
+
const fail = (msg) => {
|
|
20
|
+
throw new Error(`[arc/testing] assertRequiresPorts: ${msg}`);
|
|
21
|
+
};
|
|
22
|
+
const target = await resolveModule(module);
|
|
23
|
+
const name = target.name;
|
|
24
|
+
const { active } = await declareModules([...peers, module]);
|
|
25
|
+
if (!active.some((m) => m.name === name)) fail(`module "${name}" is not active in this composition`);
|
|
26
|
+
const declared = target.requires?.ports ?? [];
|
|
27
|
+
if (!sameList(declared, ports)) fail(`module "${name}" declares required ports [${declared.join(", ")}], expected [${ports.join(", ")}]`);
|
|
28
|
+
const shadowed = ports.filter((p) => Object.hasOwn(base, p));
|
|
29
|
+
if (shadowed.length > 0) fail(`options.ports supplies a port under test: ${shadowed.join(", ")}`);
|
|
30
|
+
const supplying = (names) => {
|
|
31
|
+
const out = { ...base };
|
|
32
|
+
for (const p of names) out[p] = Object.freeze({ port: p });
|
|
33
|
+
return out;
|
|
34
|
+
};
|
|
35
|
+
const missingFor = (supplied) => {
|
|
36
|
+
const problems = compositionProblems(active, supplied);
|
|
37
|
+
const foreign = problems.filter((p) => p.module !== name || p.code !== "missing-port");
|
|
38
|
+
if (foreign.length > 0) fail(`the composition has problems besides the module's ports — fix \`with\`/\`ports\`:\n${foreign.map((p) => ` - ${p.message}`).join("\n")}`);
|
|
39
|
+
return problems.map((p) => p.target);
|
|
40
|
+
};
|
|
41
|
+
const unplanned = await planModules([...peers, module], { ports: supplying(ports) }).catch((err) => err);
|
|
42
|
+
if (unplanned instanceof Error) fail(`module "${name}" does not plan with every port supplied: ${unplanned.message}`);
|
|
43
|
+
if (ports.length === 0) return {
|
|
44
|
+
module: name,
|
|
45
|
+
ports
|
|
46
|
+
};
|
|
47
|
+
const allMissing = missingFor(supplying([]));
|
|
48
|
+
if (!sameList(allMissing, ports)) fail(`with no port supplied, module "${name}" reported [${allMissing.join(", ")}], expected [${ports.join(", ")}] in ONE error`);
|
|
49
|
+
const refused = await planModules([...peers, module], { ports: supplying([]) }).then(() => null, (err) => err instanceof Error ? err.message : String(err));
|
|
50
|
+
if (refused === null) fail(`module "${name}" planned with none of its required ports supplied`);
|
|
51
|
+
for (const port of ports) {
|
|
52
|
+
const missing = missingFor(supplying(ports.filter((p) => p !== port)));
|
|
53
|
+
if (!sameList(missing, [port])) fail(`without "${port}", module "${name}" reported [${missing.join(", ")}], expected exactly ["${port}"]`);
|
|
54
|
+
if (!refused?.includes(`module "${name}" requires port "${port}"`)) fail(`the composition error does not name module "${name}" and port "${port}": ${refused}`);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
module: name,
|
|
58
|
+
ports
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function sameList(a, b) {
|
|
62
|
+
return a.length === b.length && a.every((x, i) => x === b[i]);
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { assertRequiresPorts };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
|
|
2
2
|
import { t as Storage } from "../storage-Cx9Sz3Ou.mjs";
|
|
3
3
|
//#region src/testing/storageContract.d.ts
|
|
4
|
-
interface StorageContractSetupResult {
|
|
4
|
+
export interface StorageContractSetupResult {
|
|
5
5
|
storage: Storage;
|
|
6
6
|
teardown: () => Promise<void>;
|
|
7
7
|
}
|
|
8
|
-
type StorageContractSetup = () => Promise<StorageContractSetupResult>;
|
|
8
|
+
export type StorageContractSetup = () => Promise<StorageContractSetupResult>;
|
|
9
9
|
/**
|
|
10
10
|
* Register the storage contract suite under the caller's name.
|
|
11
11
|
*
|
|
@@ -21,6 +21,5 @@ type StorageContractSetup = () => Promise<StorageContractSetupResult>;
|
|
|
21
21
|
* 9. Both `kind: "stream"` and `kind: "buffer"` read results deliver correct bytes
|
|
22
22
|
* 10. Ranged reads (if adapter supports them) slice correctly
|
|
23
23
|
*/
|
|
24
|
-
declare function runStorageContract(name: string, setup: StorageContractSetup): void;
|
|
25
|
-
//#endregion
|
|
26
|
-
export { StorageContractSetup, StorageContractSetupResult, runStorageContract };
|
|
24
|
+
export declare function runStorageContract(name: string, setup: StorageContractSetup): void;
|
|
25
|
+
//#endregion
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
//#region src/utils/operationIdentity.ts
|
|
4
|
+
/** Security context included in idempotency identities across transports. */
|
|
5
|
+
function operationIdentity(operation, scope, fallbackPrincipal, namespace) {
|
|
6
|
+
const principal = scope?.kind === "service" ? scope.clientId : scope?.kind === "public" ? fallbackPrincipal : scope?.userId ?? fallbackPrincipal;
|
|
7
|
+
const org = scope && "organizationId" in scope ? scope.organizationId : void 0;
|
|
8
|
+
const team = scope && "teamId" in scope ? scope.teamId : void 0;
|
|
9
|
+
const context = scope && "context" in scope ? Object.entries(scope.context ?? {}).sort(([a], [b]) => a.localeCompare(b)) : [];
|
|
10
|
+
const identity = JSON.stringify([
|
|
11
|
+
namespace ?? "",
|
|
12
|
+
operation,
|
|
13
|
+
scope?.kind ?? "public",
|
|
14
|
+
principal ?? "anon",
|
|
15
|
+
org ?? null,
|
|
16
|
+
team ?? null,
|
|
17
|
+
context
|
|
18
|
+
]);
|
|
19
|
+
return createHash("sha256").update(identity).digest("hex");
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/idempotency/stores/interface.ts
|
|
23
|
+
/**
|
|
24
|
+
* Helper to create a result object
|
|
25
|
+
*/
|
|
26
|
+
function createIdempotencyResult(statusCode, body, headers, ttlMs, requestFingerprint) {
|
|
27
|
+
const now = /* @__PURE__ */ new Date();
|
|
28
|
+
return {
|
|
29
|
+
...requestFingerprint ? { requestFingerprint } : {},
|
|
30
|
+
statusCode,
|
|
31
|
+
headers,
|
|
32
|
+
body,
|
|
33
|
+
createdAt: now,
|
|
34
|
+
expiresAt: new Date(now.getTime() + ttlMs)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function resolveIdempotencyTiming(ttlMs, lockTimeoutMs) {
|
|
38
|
+
const timing = {
|
|
39
|
+
ttlMs: ttlMs ?? 864e5,
|
|
40
|
+
lockTimeoutMs: lockTimeoutMs ?? 3e4
|
|
41
|
+
};
|
|
42
|
+
for (const [name, value] of Object.entries(timing)) if (!Number.isFinite(value) || value <= 0) throw new Error(`[arc idempotency] ${name} must be a positive finite duration in milliseconds.`);
|
|
43
|
+
return timing;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { createIdempotencyResult as n, operationIdentity as r, resolveIdempotencyTiming as t };
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
import { i as RequestScope } from "../types-Ddl9OjmE.mjs";
|
|
3
|
-
import { a as UserLike, c as DataPolicy, d as UserBase, i as ObjectId, l as PermissionCheck, n as ArcRequest, o as UserOrganization, r as JWTPayload, s as AuthorizationDecision, t as AnyRecord, u as PermissionContext } from "../base-
|
|
4
|
-
import { $ as
|
|
5
|
-
import { n as ElevationOptions, t as ElevationEvent } from "../elevation-
|
|
6
|
-
export type { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, AuthorizationDecision, BaseControllerOptions,
|
|
3
|
+
import { a as UserLike, c as DataPolicy, d as UserBase, i as ObjectId, l as PermissionCheck, n as ArcRequest, o as UserOrganization, r as JWTPayload, s as AuthorizationDecision, t as AnyRecord, u as PermissionContext } from "../base-CZyixEQR.mjs";
|
|
4
|
+
import { $ as ResourceHookContext, A as RequestWithExtras, At as AuthHelpers, B as OnTenantDeleteConfig, Bn as IController, C as RequestIdOptions, Ct as ActionEntry, D as FastifyWithAuth, Dt as RouteMcpConfig, E as FastifyRequestExtras, En as BaseControllerOptions, Et as RouteDefinition, Fn as WriteContext, Ft as TokenPair, G as CrudRouteKey, H as PresetFunction, Hn as IRequestContext, I as CrudController, In as WriteVerbKey, J as MiddlewareConfig, K as CrudSchemas, L as ResourceConfig, Ln as ControllerHandler, Mt as Authenticator, Nn as MutationWriteContext, Nt as AuthenticatorContext, O as FastifyWithDecorators, Ot as RouteMethod, Pn as ResourceWrites, Pt as JwtContext, Q as FieldRule, R as ResourceIdempotencyConfig, Rn as ControllerLike, S as IntrospectionPluginOptions, St as ActionDefinition, T as EventsDecorator, Tt as ActionsMap, U as PresetHook, Un as RawRouteHandler, V as ResolvedTenantPurge, Vn as IControllerResponse, W as PresetResult, Wn as RouteHandler, X as RouteSchemaOptions, Y as OpenApiSchemas, Z as ArcFieldRule, _ as ServiceContext, a as IntrospectionData, b as HealthCheck, c as ResourceMetadata, d as LookupOption, et as ResourceHooks, f as OwnershipCheck, g as RequestContext, h as QueryParserInterface, i as PaginationResult, jt as AuthPluginOptions, k as MiddlewareHandler, kt as RateLimitConfig, l as ArcInternalMetadata, m as PopulateOption, n as InferDocType, nt as EventDefinition, o as RegistryEntry, p as ParsedQuery, q as LookupPolicy, r as InferResourceDoc, s as RegistryStats, t as InferAdapterDoc, tt as ResourceExtensions, u as ControllerQueryOptions, v as CrudRouterOptions, w as ArcDecorator, wt as ActionHandlerFn, x as HealthOptions, xt as ResourceCacheConfig, y as GracefulShutdownOptions, z as ResourcePermissions, zn as FastifyHandler } from "../index-WH7xgvxS.mjs";
|
|
5
|
+
import { n as ElevationOptions, t as ElevationEvent } from "../elevation-BW2J3tNj.mjs";
|
|
6
|
+
export type { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, AuthorizationDecision, BaseControllerOptions, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, DataPolicy, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, LookupPolicy, MiddlewareConfig, MiddlewareHandler, MutationWriteContext, ObjectId, OnTenantDeleteConfig, OpenApiSchemas, OwnershipCheck, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryParserInterface, RateLimitConfig, RawRouteHandler, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResolvedTenantPurge, ResourceCacheConfig, ResourceConfig, ResourceExtensions, ResourceHookContext, ResourceHooks, ResourceIdempotencyConfig, ResourceMetadata, ResourcePermissions, ResourceWrites, RouteDefinition, RouteHandler, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, UserBase, UserLike, UserOrganization, WriteContext, WriteVerbKey };
|
package/dist/types/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
2
|
export {};
|
package/dist/types/storage.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
|
|
2
2
|
export {};
|