@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.
Files changed (248) hide show
  1. package/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
  2. package/dist/{EventTransport-DSqD_Xsg.mjs → EventTransport-DZWDsaPa.mjs} +22 -17
  3. package/dist/{HookSystem-CMF94SEA.mjs → HookSystem-yjEgn7Cm.mjs} +2 -2
  4. package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
  5. package/dist/{ResourceRegistry-MGpXoxHZ.mjs → ResourceRegistry-DJxHUpE4.mjs} +44 -13
  6. package/dist/acquire-BozJ-9yt.mjs +35 -0
  7. package/dist/actionPermissions-5N04zBL4.mjs +772 -0
  8. package/dist/audit/index.d.mts +46 -8
  9. package/dist/audit/index.mjs +48 -26
  10. package/dist/auth/audit.d.mts +8 -8
  11. package/dist/auth/audit.mjs +1 -1
  12. package/dist/auth/index.d.mts +46 -16
  13. package/dist/auth/index.mjs +134 -156
  14. package/dist/auth/redis-session.d.mts +4 -5
  15. package/dist/auth/redis-session.mjs +1 -1
  16. package/dist/{authorizationDecision-DawKGoXb.mjs → authorizationDecision-wX3SA9w_.mjs} +7 -102
  17. package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
  18. package/dist/boundedMap-DWiKW7lP.mjs +57 -0
  19. package/dist/{buildHandler-CvxKOc0m.mjs → buildHandler-Bswu_vNb.mjs} +4 -4
  20. package/dist/cache/index.d.mts +31 -43
  21. package/dist/cache/index.mjs +4 -4
  22. package/dist/{caching-DbCBxlgw.mjs → caching-Drp68DIS.mjs} +3 -3
  23. package/dist/claimed-DYvQoZLv.mjs +8 -0
  24. package/dist/cleanup/index.d.mts +18 -22
  25. package/dist/cleanup/index.mjs +27 -73
  26. package/dist/cli/commands/describe.d.mts +7 -5
  27. package/dist/cli/commands/describe.mjs +7 -4
  28. package/dist/cli/commands/docs.d.mts +2 -3
  29. package/dist/cli/commands/docs.mjs +3 -3
  30. package/dist/cli/commands/doctor.d.mts +2 -3
  31. package/dist/cli/commands/doctor.mjs +1 -1
  32. package/dist/cli/commands/generate.mjs +2 -3
  33. package/dist/cli/commands/init.mjs +2 -3
  34. package/dist/cli/commands/introspect.d.mts +2 -3
  35. package/dist/cli/commands/introspect.mjs +2 -2
  36. package/dist/cli/index.mjs +3 -3
  37. package/dist/{constants-D5Ubn1fe.mjs → constants-B2QHb6u6.mjs} +4 -2
  38. package/dist/context/index.d.mts +11 -14
  39. package/dist/context/index.mjs +18 -38
  40. package/dist/core/index.d.mts +3 -3
  41. package/dist/core/index.mjs +8 -8
  42. package/dist/{core-5lazhva9.mjs → core-COcqo0Cp.mjs} +15 -5
  43. package/dist/{createActionRouter-DNqoaK81.mjs → createActionRouter-zG13rpPJ.mjs} +97 -39
  44. package/dist/{createAggregationRouter-BcbQJf10.mjs → createAggregationRouter-CWRz96Vr.mjs} +11 -8
  45. package/dist/{preloadResources-f3boenVZ.mjs → createApp-BGLYtErt.mjs} +305 -394
  46. package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-DSztofa9.d.mts} +1 -1
  47. package/dist/{defineAggregation-D_eJjm5A.mjs → defineAggregation--3xguLkk.mjs} +1 -1
  48. package/dist/{defineResource-DxkMTjRn.mjs → defineResource-gUp9ug66.mjs} +118 -1024
  49. package/dist/discovery/index.d.mts +6 -7
  50. package/dist/discovery/index.mjs +1 -1
  51. package/dist/docs/index.d.mts +11 -5
  52. package/dist/docs/index.mjs +4 -4
  53. package/dist/{elevation-CkvG0LG2.mjs → elevation-BG4gnMsJ.mjs} +41 -12
  54. package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
  55. package/dist/encryption/index.d.mts +5 -5
  56. package/dist/encryption/index.mjs +3 -3
  57. package/dist/{errorHandler-D1GHKGlV.mjs → errorHandler-COHw3N1b.mjs} +26 -5
  58. package/dist/{errors-BH7YU_9O.mjs → errors-CW4anJBy.mjs} +58 -23
  59. package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
  60. package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
  61. package/dist/{eventPlugin-zZY-unhr.mjs → eventPlugin-BweHAVws.mjs} +82 -21
  62. package/dist/{subscribe-helpers-DatDkcuG.mjs → eventTypes-N4-omQFR.mjs} +151 -17
  63. package/dist/events/index.d.mts +43 -18
  64. package/dist/events/index.mjs +49 -53
  65. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  66. package/dist/events/transports/redis-stream-entry.mjs +139 -157
  67. package/dist/events/transports/redis.d.mts +5 -6
  68. package/dist/events/transports/redis.mjs +2 -2
  69. package/dist/factory/index.d.mts +42 -27
  70. package/dist/factory/index.mjs +213 -32
  71. package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DRlCIWE9.d.mts} +11 -25
  72. package/dist/{fields-BHrkoF5y.mjs → fields-A5crhsJb.mjs} +1 -1
  73. package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
  74. package/dist/filter-merge-DGM-GQ1J.mjs +99 -0
  75. package/dist/{generate-BLRf2m3y.mjs → generate-BXKo2Gc8.mjs} +2 -2
  76. package/dist/{headers-BIXWKrzQ.mjs → headers-DKZQKL-Z.mjs} +2 -2
  77. package/dist/health-BYAwA6YS.mjs +22 -0
  78. package/dist/{health-ZRHeDHXH.mjs → health-CrqUp3sz.mjs} +115 -55
  79. package/dist/hooks/index.d.mts +2 -2
  80. package/dist/hooks/index.mjs +2 -3
  81. package/dist/idempotency/index.d.mts +36 -10
  82. package/dist/idempotency/index.mjs +230 -111
  83. package/dist/idempotency/redis.d.mts +1 -1
  84. package/dist/idempotency/redis.mjs +77 -12
  85. package/dist/{index-D4u76eQW.d.mts → index-Beg3aNnK.d.mts} +23 -101
  86. package/dist/{preloadResources-CjS2OfpU.d.mts → index-BkV55WlV.d.mts} +37 -38
  87. package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
  88. package/dist/index-CUJvoyw3.d.mts +510 -0
  89. package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
  90. package/dist/{index-DHbyPo5j.d.mts → index-WH7xgvxS.d.mts} +304 -254
  91. package/dist/index.d.mts +9 -9
  92. package/dist/index.mjs +11 -11
  93. package/dist/{init-CpLkjU18.mjs → init-BPzK5Lnr.mjs} +1 -1
  94. package/dist/integrations/event-gateway.d.mts +11 -7
  95. package/dist/integrations/event-gateway.mjs +4 -4
  96. package/dist/integrations/index.d.mts +3 -3
  97. package/dist/integrations/index.mjs +1 -1
  98. package/dist/integrations/jobs/index.d.mts +1 -1
  99. package/dist/integrations/jobs/index.mjs +18 -5
  100. package/dist/integrations/mcp/index.d.mts +102 -89
  101. package/dist/integrations/mcp/index.mjs +19 -4
  102. package/dist/integrations/mcp/testing.d.mts +8 -9
  103. package/dist/integrations/mcp/testing.mjs +2 -2
  104. package/dist/integrations/streamline.d.mts +85 -144
  105. package/dist/integrations/streamline.mjs +131 -55
  106. package/dist/integrations/webhooks.d.mts +83 -143
  107. package/dist/integrations/webhooks.mjs +36 -80
  108. package/dist/integrations/websocket-pushref-redis.d.mts +5 -6
  109. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  110. package/dist/integrations/websocket-redis.d.mts +5 -6
  111. package/dist/integrations/websocket-redis.mjs +1 -1
  112. package/dist/integrations/websocket.d.mts +3 -3
  113. package/dist/integrations/websocket.mjs +2 -2
  114. package/dist/{interface-DEWNOMza.d.mts → interface-JJzwH4Ro.d.mts} +34 -2
  115. package/dist/{jsonBody-ELR9lfeU.mjs → jsonBody-YIq-vGLv.mjs} +2 -2
  116. package/dist/{keys-cpZCtcIE.mjs → keys-CpoiZ8hU.mjs} +7 -18
  117. package/dist/{loadResources-BPfWiI5s.mjs → loadResources-6jO52-RR.mjs} +3 -3
  118. package/dist/{loadResourcesFromEntry-ixtfL3J2.mjs → loadResourcesFromEntry-MP8xEppu.mjs} +2 -2
  119. package/dist/logger/index.d.mts +9 -17
  120. package/dist/logger/index.mjs +2 -153
  121. package/dist/logger-D4V1zGLI.mjs +160 -0
  122. package/dist/{mcpPlugin-BX5PujI0.mjs → mcpPlugin-srPc7ItN.mjs} +446 -170
  123. package/dist/{memory-Iotz0cCx.mjs → memory-CTa5pOCW.mjs} +36 -34
  124. package/dist/{metrics-NMsmpRmY.mjs → metrics-DsZrvnpB.mjs} +39 -72
  125. package/dist/middleware/index.d.mts +5 -5
  126. package/dist/middleware/index.mjs +3 -3
  127. package/dist/migrations/index.d.mts +13 -13
  128. package/dist/migrations/index.mjs +2 -2
  129. package/dist/{module-DpLJG2_N.mjs → module--L0iBIZz.mjs} +8 -8
  130. package/dist/mongodb-memory-server-CHhYSq5y.mjs +35539 -0
  131. package/dist/mongoose-CYJCLyi-.mjs +67451 -0
  132. package/dist/multiTenant-CAtYlW6H.d.mts +104 -0
  133. package/dist/{multipartBody-DtRWM6_U.mjs → multipartBody-Cc7eZ9cq.mjs} +46 -7
  134. package/dist/node-D8UZv_8c.mjs +41236 -0
  135. package/dist/{openapi-CmFf3yq_.mjs → openapi-CMegfsm8.mjs} +48 -10
  136. package/dist/outbox-admin/index.d.mts +5 -5
  137. package/dist/outbox-admin/index.mjs +6 -18
  138. package/dist/{outbox-module-D2z5WkDY.mjs → outbox-module-Ryk270jd.mjs} +203 -33
  139. package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
  140. package/dist/permissions/index.d.mts +5 -4
  141. package/dist/permissions/index.mjs +8 -6
  142. package/dist/{permissions-D8TBH44E.mjs → permissions-C5ijIz5Q.mjs} +149 -33
  143. package/dist/{pipe-FMWNheSO.mjs → pipe-D81zMtWs.mjs} +2 -2
  144. package/dist/pipeline/index.d.mts +7 -7
  145. package/dist/pipeline/index.mjs +2 -2
  146. package/dist/plan-CJGMKoui.mjs +565 -0
  147. package/dist/{BaseController-CYEwjyjN.mjs → plugin-DALd9dcF.mjs} +1269 -346
  148. package/dist/plugins/index.d.mts +17 -14
  149. package/dist/plugins/index.mjs +44 -22
  150. package/dist/plugins/response-cache.d.mts +5 -6
  151. package/dist/plugins/response-cache.mjs +8 -14
  152. package/dist/plugins/tracing-entry.mjs +2 -2
  153. package/dist/{pluralize-CXWZIzTv.mjs → pluralize-COUNYo3d.mjs} +1 -1
  154. package/dist/presets/filesUpload.d.mts +8 -8
  155. package/dist/presets/filesUpload.mjs +5 -5
  156. package/dist/presets/index.d.mts +35 -40
  157. package/dist/presets/index.mjs +4 -4
  158. package/dist/presets/multiTenant.d.mts +2 -107
  159. package/dist/presets/multiTenant.mjs +22 -162
  160. package/dist/presets/search.d.mts +9 -10
  161. package/dist/presets/search.mjs +2 -2
  162. package/dist/{presets-ZJSPgmT2.mjs → presets-CONf2Qem.mjs} +99 -107
  163. package/dist/{projection-BzIgzoKZ.mjs → projection-wFoLqdss.mjs} +2 -2
  164. package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
  165. package/dist/{queryCachePlugin-DZHPOm9p.mjs → queryCachePlugin-BATECpyh.mjs} +12 -14
  166. package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
  167. package/dist/{rateLimitKey-BvOvcBDi.mjs → rateLimitKey-DVEZ_UpV.mjs} +1 -1
  168. package/dist/{realtime-8tIQWZz4.mjs → realtime-xyubMKSB.mjs} +25 -21
  169. package/dist/{redis-Bzi1y3F9.d.mts → redis-BDgv9Glw.d.mts} +17 -24
  170. package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
  171. package/dist/redisShapes-CQksY67q.d.mts +55 -0
  172. package/dist/registry/index.d.mts +15 -10
  173. package/dist/registry/index.mjs +4 -4
  174. package/dist/{registry-DNkgGQcK.mjs → registry-Bdzg-hdm.mjs} +13 -10
  175. package/dist/{renewingLease-yF8BdW4b.mjs → renewingLease-BFJxGPuh.mjs} +1 -1
  176. package/dist/{replyHelpers-CNdvMfM4.mjs → replyHelpers-Bnc6lJyy.mjs} +2 -2
  177. package/dist/{requestContext-cDkAUr-P.mjs → requestContext-C10UeFe5.mjs} +1 -1
  178. package/dist/requirement-Bu50QALH.mjs +35 -0
  179. package/dist/requirement-CbiLdQ1n.d.mts +22 -0
  180. package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
  181. package/dist/resourceRateLimit-urWPJxrt.mjs +180 -0
  182. package/dist/rolldown-runtime-MYrLNGnq.mjs +45 -0
  183. package/dist/routeValidator-DHtpzrzk.mjs +70 -0
  184. package/dist/{runtimeCapabilities-CkhS6jn6.mjs → runtimeCapabilities-DIMKMV2G.mjs} +1 -1
  185. package/dist/{schedules-j6uXXAeJ.mjs → schedules-CKTs5ys8.mjs} +46 -37
  186. package/dist/{schemaConverter-BVA7cFRG.mjs → schemaConverter-BVjkQ5DD.mjs} +2 -2
  187. package/dist/{schemaIR-CTvPDoPB.mjs → schemaIR-BA0Hb21Y.mjs} +2 -2
  188. package/dist/schemas/index.d.mts +20 -10
  189. package/dist/schemas/index.mjs +8 -3
  190. package/dist/scim/index.d.mts +13 -13
  191. package/dist/scim/index.mjs +2 -2
  192. package/dist/scope/index.d.mts +4 -4
  193. package/dist/scope/index.mjs +6 -6
  194. package/dist/{scope-BUi1xV1I.mjs → scope-DINaj7Ax.mjs} +4 -4
  195. package/dist/shutdown-LpLMH8T1.mjs +59 -0
  196. package/dist/{simpleEqualityMatcher-C9bzq2gO.mjs → simpleEqualityMatcher-DbwIM1f1.mjs} +1 -1
  197. package/dist/{sse-BeqVKQKu.mjs → sse-BzJFPX_1.mjs} +11 -15
  198. package/dist/{sseStream-Ppzo-WUH.mjs → sseStream-DQChzGTs.mjs} +2 -2
  199. package/dist/{store-helpers-dW71XVWL.mjs → store-helpers-Coj9RhOZ.mjs} +1 -1
  200. package/dist/{streaming-IcRFjWQU.mjs → streaming-JzB5CAga.mjs} +1 -1
  201. package/dist/subscriberIdentity-DeyppIif.mjs +28 -0
  202. package/dist/tenancy-DxV8OuX2.mjs +43 -0
  203. package/dist/tenantPreparation-CBvvAcAj.mjs +67 -0
  204. package/dist/testing/cleanupStoreContract.d.mts +4 -5
  205. package/dist/testing/cleanupStoreContract.mjs +1 -1
  206. package/dist/testing/index.d.mts +215 -197
  207. package/dist/testing/index.mjs +289 -214
  208. package/dist/testing/outboxStoreContract.d.mts +4 -5
  209. package/dist/testing/outboxStoreContract.mjs +137 -2
  210. package/dist/testing/requiredPorts.d.mts +16 -0
  211. package/dist/testing/requiredPorts.mjs +65 -0
  212. package/dist/testing/storageContract.d.mts +4 -5
  213. package/dist/testing/storageContract.mjs +1 -1
  214. package/dist/timing-CntavTlR.mjs +46 -0
  215. package/dist/{transactionContext-CPbdlnVw.mjs → transactionContext-orvnGu4F.mjs} +1 -1
  216. package/dist/{typeGuards-BLjPgVBm.mjs → typeGuards-kEaP_WmO.mjs} +1 -1
  217. package/dist/types/index.d.mts +4 -4
  218. package/dist/types/index.mjs +1 -1
  219. package/dist/types/storage.mjs +1 -1
  220. package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
  221. package/dist/{types-CWQn9-IC.d.mts → types-D3Ixsh00.d.mts} +22 -9
  222. package/dist/{types-CgenB1gv.mjs → types-fvO6Ovzx.mjs} +4 -4
  223. package/dist/usage/index.d.mts +5 -6
  224. package/dist/usage/index.mjs +5 -5
  225. package/dist/{userHelpers-ChRvkuSZ.mjs → userHelpers-C76zD5pa.mjs} +1 -1
  226. package/dist/utils/index.d.mts +125 -264
  227. package/dist/utils/index.mjs +13 -12
  228. package/dist/{utils-CxcctrAh.mjs → utils-BpkVZ8rB.mjs} +315 -404
  229. package/dist/{validate-DrYWnOzI.mjs → validate-CWp-lsrm.mjs} +6 -14
  230. package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
  231. package/dist/{versioning-DKkT4hNJ.mjs → versioning-CHhL9P3o.mjs} +4 -4
  232. package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
  233. package/dist/{workScope-CxKqoIFn.mjs → workScope-n9a7d_lZ.mjs} +2 -2
  234. package/package.json +57 -59
  235. package/skills/arc/SKILL.md +13 -4
  236. package/skills/arc/references/auth.md +8 -5
  237. package/skills/arc/references/integrations.md +1 -1
  238. package/skills/arc/references/mcp.md +8 -6
  239. package/skills/arc/references/multi-tenancy.md +19 -14
  240. package/skills/arc/references/production.md +27 -10
  241. package/dist/actionPermissions-BoPGQ5-4.mjs +0 -350
  242. package/dist/index-CFfLKMH4.d.mts +0 -290
  243. package/dist/interface-YzIbF7Ii.mjs +0 -17
  244. package/dist/redis-stream-BPKscsTB.d.mts +0 -260
  245. package/dist/registerRoute-Bsnk6NiZ.mjs +0 -364
  246. package/dist/rolldown-runtime-eysyzFAF.mjs +0 -15
  247. package/dist/sync/changelog.d.mts +0 -3
  248. package/dist/sync/changelog.mjs +0 -3
@@ -1,5 +1,6 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { OutboxOwnershipError } from "@classytic/primitives/outbox";
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
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
2
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
3
  //#region src/testing/storageContract.ts
4
4
  /**
@@ -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 };
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  //#region src/context/transactionContext.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
2
  //#region src/utils/typeGuards.ts
3
3
  /** Check if fastify has the events plugin registered */
4
4
  function hasEvents(instance) {
@@ -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-CxrV1LmG.mjs";
4
- import { $ as OpenApiSchemas, A as RequestIdOptions, An as WriteVerbKey, At as RouteDefinition, C as RequestContext, D as HealthCheck, Dn as MutationWriteContext, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Fn as IControllerResponse, Ft as AuthPluginOptions, G as OnTenantDeleteConfig, H as CrudController, I as MiddlewareHandler, In as IRequestContext, It as Authenticator, J as PresetHook, K as ResolvedTenantPurge, L as RequestWithExtras, Ln as RawRouteHandler, Lt as AuthenticatorContext, M as EventsDecorator, Mn as ControllerLike, Mt as RouteMethod, N as FastifyRequestExtras, Nn as FastifyHandler, Nt as RateLimitConfig, O as HealthOptions, On as ResourceWrites, Ot as ActionHandlerFn, P as FastifyWithAuth, Pn as IController, Pt as AuthHelpers, Q as MiddlewareConfig, Rn as RouteHandler, Rt as JwtContext, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, W as ResourcePermissions, X as CrudRouteKey, Y as PresetResult, Z as CrudSchemas, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jn as ControllerHandler, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kn as WriteContext, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, xn as BaseControllerOptions, y as OwnershipCheck, zt as TokenPair } from "../index-DHbyPo5j.mjs";
5
- import { n as ElevationOptions, t as ElevationEvent } from "../elevation-BXCfVcNG.mjs";
6
- export type { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, AuthorizationDecision, BaseControllerOptions, ConfigError, 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, 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, ResourceMetadata, ResourcePermissions, ResourceWrites, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult, WriteContext, WriteVerbKey };
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 };
@@ -1,2 +1,2 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
2
  export {};
@@ -1,2 +1,2 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
2
  export {};