@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,11 +1,14 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-eysyzFAF.mjs";
3
- import { arcLog } from "./logger/index.mjs";
4
- import { d as createDomainError } from "./errors-BH7YU_9O.mjs";
5
- import { n as requestContext } from "./requestContext-cDkAUr-P.mjs";
6
- import { n as MemoryEventTransport, r as createEvent, t as ARC_EVENT_TRANSPORT } from "./EventTransport-DSqD_Xsg.mjs";
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 { d as createDomainError } from "./errors-CW4anJBy.mjs";
5
+ import { n as requestContext } from "./requestContext-C10UeFe5.mjs";
6
+ import { i as runAdmittedWork, n as isAdmittedWork, r as registerShutdownParticipant } from "./shutdown-LpLMH8T1.mjs";
7
+ import { n as MemoryEventTransport, r as createEvent, t as ARC_EVENT_TRANSPORT } from "./EventTransport-DZWDsaPa.mjs";
8
+ import { n as preserveSubscriberIdentity } from "./subscriberIdentity-DeyppIif.mjs";
7
9
  import { isProductionEnv } from "@classytic/primitives/environment";
8
10
  import fp from "fastify-plugin";
11
+ import { NonDurablePublishError } from "@classytic/primitives/durable-events";
9
12
  //#region src/events/retry.ts
10
13
  /**
11
14
  * Wrap an event handler with retry logic and dead letter support.
@@ -21,7 +24,7 @@ import fp from "fastify-plugin";
21
24
  function withRetry(handler, options = {}) {
22
25
  const { maxRetries = 3, backoffMs = 1e3, maxBackoffMs = 3e4, jitter = .1, transport, deadLetter, onDead, dlqFailureMode = "rethrow", name, logger = console } = options;
23
26
  const label = name ?? handler.name ?? "anonymous";
24
- return async (event) => {
27
+ return preserveSubscriberIdentity(handler, async (event) => {
25
28
  const errors = [];
26
29
  let firstFailedAt;
27
30
  let lastFailedAt;
@@ -76,8 +79,9 @@ function withRetry(handler, options = {}) {
76
79
  } catch (dlqErr) {
77
80
  logger.error("[Arc Events] onDead observability callback failed:", dlqErr);
78
81
  }
82
+ if (!transport?.deadLetter && !deadLetter && dlqFailureMode === "rethrow") throw new Error(`[Arc Events] Handler '${label}' exhausted retries for ${event.type} and no dead-letter sink is wired — rethrowing so the delivery stays owed (the outbox relay retries and dead-letters it) instead of acknowledging a lost event. Set dlqFailureMode: 'log-and-drop' on a handler whose loss is acceptable.`, { cause: errors[errors.length - 1] });
79
83
  if (dlqPersistError && dlqFailureMode === "rethrow") throw new Error(`[Arc Events] Handler '${label}' exhausted retries for ${event.type} AND dead-letter persistence failed — rethrowing so the transport redelivers instead of acknowledging a lost event (set dlqFailureMode: 'log-and-drop' to accept loss).`, { cause: dlqPersistError });
80
- };
84
+ });
81
85
  }
82
86
  /**
83
87
  * Create a dead letter publisher that sends failed events to a `$deadLetter` channel.
@@ -134,8 +138,23 @@ function sleep(ms) {
134
138
  */
135
139
  var eventPlugin_exports = /* @__PURE__ */ __exportAll({
136
140
  default: () => eventPlugin_default,
141
+ emitObserved: () => emitObserved,
137
142
  eventPlugin: () => eventPlugin
138
143
  });
144
+ const OBSERVERS = Symbol.for("arc.events.observers");
145
+ /** Notify `fastify.events.observe` listeners. Internal: the plugin and the outbox relay call it. */
146
+ function emitObserved(events, event) {
147
+ const listeners = events?.[OBSERVERS];
148
+ if (!listeners) return;
149
+ for (const listener of listeners) try {
150
+ listener(event);
151
+ } catch {}
152
+ }
153
+ /** A consumer that can register subscriptions now and start consuming later (`RedisStreamTransport`). */
154
+ function isHoldable(transport) {
155
+ const t = transport;
156
+ return typeof t.hold === "function" && typeof t.start === "function";
157
+ }
139
158
  const eventPlugin = async (fastify, opts = {}) => {
140
159
  /**
141
160
  * Lifecycle ownership: arc closes ONLY the transport it built.
@@ -148,6 +167,18 @@ const eventPlugin = async (fastify, opts = {}) => {
148
167
  */
149
168
  const ownsTransport = opts.transport === void 0;
150
169
  const { transport = new MemoryEventTransport(opts.memoryTransport), singleProcess = false, logEvents = false, failOpen = true, retry: retryOpts, deadLetterQueue: dlqOpts, wal, onPublish, onPublishError, registry, validateMode: rawValidateMode, warnOnDuplicate: rawWarnOnDuplicate } = opts;
170
+ /** A host telemetry callback — isolated like an observer, so it can never change delivery. */
171
+ const notify = (hook, run, eventType) => {
172
+ try {
173
+ run();
174
+ } catch (error) {
175
+ fastify.log?.warn?.({
176
+ hook,
177
+ eventType,
178
+ error
179
+ }, "[Arc Events] publish telemetry callback threw — ignored");
180
+ }
181
+ };
151
182
  const validateMode = rawValidateMode ?? (registry ? "warn" : "off");
152
183
  const warnOnDuplicate = rawWarnOnDuplicate ?? !isProductionEnv(process.env.NODE_ENV);
153
184
  const DUP_WINDOW_MS = 5e3;
@@ -159,7 +190,27 @@ const eventPlugin = async (fastify, opts = {}) => {
159
190
  recentPublishes.delete(key);
160
191
  }
161
192
  };
193
+ const observers = /* @__PURE__ */ new Set();
194
+ const activeHandlers = /* @__PURE__ */ new Set();
195
+ let stopping = false;
196
+ registerShutdownParticipant(fastify, {
197
+ name: "events.handlers",
198
+ kind: "consumer",
199
+ stop: () => {
200
+ stopping = true;
201
+ },
202
+ drain: async () => {
203
+ while (activeHandlers.size) await Promise.allSettled([...activeHandlers]);
204
+ }
205
+ });
162
206
  fastify.decorate("events", {
207
+ [OBSERVERS]: observers,
208
+ observe: (listener) => {
209
+ observers.add(listener);
210
+ return () => {
211
+ observers.delete(listener);
212
+ };
213
+ },
163
214
  publish: async (type, payload, meta) => {
164
215
  if (!type || typeof type !== "string") throw new Error("[Arc Events] Event type must be a non-empty string");
165
216
  if (type.startsWith("$") && type !== "$deadLetter") throw new Error(`[Arc Events] Event type '${type}' uses reserved '$' prefix`);
@@ -170,6 +221,7 @@ const eventPlugin = async (fastify, opts = {}) => {
170
221
  ...meta
171
222
  };
172
223
  const event = createEvent(type, payload, enrichedMeta);
224
+ if (registry?.get(type, event.meta.schemaVersion)?.durability === "durable") throw new NonDurablePublishError(type, "fastify.events writes no outbox row — save it in the unit of work that made it true (dispatchDomainEvent, or outbox.save in the session)");
173
225
  if (warnOnDuplicate && event.meta.correlationId) {
174
226
  const now = Date.now();
175
227
  evictExpiredPublishes(now);
@@ -200,16 +252,18 @@ const eventPlugin = async (fastify, opts = {}) => {
200
252
  if (wal && !isInternalEvent) await wal.save(event);
201
253
  await transport.publish(event);
202
254
  if (wal?.acknowledge && !isInternalEvent) await wal.acknowledge(event.meta.id);
203
- onPublish?.(event);
204
255
  } catch (error) {
205
256
  fastify.log?.error?.({
206
257
  transport: transport.name,
207
258
  eventType: type,
208
259
  error
209
260
  }, "[Arc Events] Failed to publish event");
210
- onPublishError?.(event, error);
261
+ notify("onPublishError", () => onPublishError?.(event, error), type);
211
262
  if (!failOpen) throw error;
263
+ return;
212
264
  }
265
+ notify("onPublish", () => onPublish?.(event), type);
266
+ emitObserved(fastify.events, event);
213
267
  },
214
268
  subscribe: async (pattern, handler) => {
215
269
  let wrappedHandler = handler;
@@ -222,17 +276,18 @@ const eventPlugin = async (fastify, opts = {}) => {
222
276
  pattern,
223
277
  retry: !!retryOpts
224
278
  }, "Subscribing to events");
225
- if (typeof transport.subscribe !== "function") {
226
- const error = /* @__PURE__ */ new Error(`[Arc Events] transport '${transport.name}' is publish-only (no subscribe) — cannot register a handler for '${pattern}'. Use MemoryEventTransport / RedisEventTransport, or a transport that implements subscribe().`);
227
- fastify.log?.error?.({
228
- transport: transport.name,
229
- pattern
230
- }, error.message);
231
- if (!failOpen) throw error;
232
- return () => {};
233
- }
279
+ if (typeof transport.subscribe !== "function") throw new Error(`[Arc Events] transport '${transport.name}' is publish-only (no subscribe) — cannot register a handler for '${pattern}'. Use MemoryEventTransport / RedisEventTransport, or a transport that implements subscribe().`);
234
280
  try {
235
- return await transport.subscribe(pattern, wrappedHandler);
281
+ const dispatch = wrappedHandler;
282
+ const tracked = (event) => {
283
+ if (stopping && !isAdmittedWork()) return Promise.reject(/* @__PURE__ */ new Error("[Arc Events] consumer is shutting down"));
284
+ const pending = runAdmittedWork(() => dispatch(event));
285
+ activeHandlers.add(pending);
286
+ return pending.finally(() => {
287
+ activeHandlers.delete(pending);
288
+ });
289
+ };
290
+ return await transport.subscribe(pattern, preserveSubscriberIdentity(handler, tracked));
236
291
  } catch (error) {
237
292
  fastify.log?.error?.({
238
293
  transport: transport.name,
@@ -270,6 +325,12 @@ const eventPlugin = async (fastify, opts = {}) => {
270
325
  * if a host's graph ends up with two arc copies.
271
326
  */
272
327
  fastify.decorate(ARC_EVENT_TRANSPORT, transport);
328
+ if (isHoldable(transport)) {
329
+ transport.hold();
330
+ fastify.addHook("onReady", async () => {
331
+ await transport.start();
332
+ });
333
+ }
273
334
  fastify.addHook("onClose", async () => {
274
335
  if (!ownsTransport) return;
275
336
  try {
@@ -295,4 +356,4 @@ var eventPlugin_default = fp(eventPlugin, {
295
356
  fastify: "5.x"
296
357
  });
297
358
  //#endregion
298
- export { withRetry as i, eventPlugin_exports as n, createDeadLetterPublisher as r, eventPlugin as t };
359
+ export { withRetry as a, createDeadLetterPublisher as i, eventPlugin as n, eventPlugin_exports as r, emitObserved as t };
@@ -1,5 +1,7 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { r as createEvent } from "./EventTransport-DSqD_Xsg.mjs";
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
+ import { r as createEvent } from "./EventTransport-DZWDsaPa.mjs";
3
+ import { createRequire } from "node:module";
4
+ import { durableEventMeta } from "@classytic/primitives/durable-events";
3
5
  //#region src/events/defineEvent.ts
4
6
  /**
5
7
  * Typed event declarations, a registry for introspection, and `.create()` to
@@ -24,25 +26,30 @@ import { r as createEvent } from "./EventTransport-DSqD_Xsg.mjs";
24
26
  * await fastify.events.publish(event.type, event.payload, event.meta);
25
27
  * ```
26
28
  */
27
- /**
28
- * Define a typed event with optional schema validation.
29
- *
30
- * @example
31
- * const OrderCreated = defineEvent({
32
- * name: 'order.created',
33
- * schema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'] },
34
- * });
35
- *
36
- * const event = OrderCreated.create({ orderId: '123' });
37
- */
38
29
  function defineEvent(input) {
39
- const { name, schema, version = 1, description } = input;
30
+ const { name, payload, version = 1, description } = input;
31
+ const durable = input.durability === "durable" ? input : void 0;
32
+ if (durable && typeof durable.occurrence !== "function") throw new Error(`[arc] durable event "${name}" must declare \`occurrence(payload)\`.`);
33
+ if (payload && input.schema) throw new Error(`[arc] event "${name}" declares both \`payload\` and \`schema\` — declare the payload once.`);
40
34
  return {
41
35
  name,
42
- schema,
36
+ schema: payload ? payloadToJsonSchema(name, payload) : input.schema,
37
+ ...payload ? { payloadSchema: payload } : {},
43
38
  version,
44
39
  description,
40
+ ...input.durability !== void 0 ? { durability: input.durability } : {},
41
+ ...durable ? { occurrence: (p) => durable.occurrence(p) } : {},
42
+ ...durable?.produces ? { produces: durable.produces } : {},
45
43
  create(payload, meta) {
44
+ if (durable) {
45
+ const fixed = durableEventMeta(name, version, durable.occurrence(payload));
46
+ if (meta?.id !== void 0 && meta.id !== fixed.id) throw new Error(`[arc] durable event "${name}": meta.id "${meta.id}" contradicts its occurrence id "${fixed.id}".`);
47
+ return createEvent(name, payload, {
48
+ schemaVersion: version,
49
+ ...meta,
50
+ ...fixed
51
+ });
52
+ }
46
53
  return createEvent(name, payload, {
47
54
  schemaVersion: version,
48
55
  ...meta
@@ -51,6 +58,24 @@ function defineEvent(input) {
51
58
  };
52
59
  }
53
60
  /**
61
+ * The payload's JSON Schema, via zod's own converter — loaded only here, so an app that never
62
+ * declares a zod payload never loads zod. A payload JSON cannot carry fails AT DEFINITION.
63
+ */
64
+ function payloadToJsonSchema(name, payload) {
65
+ let zod;
66
+ try {
67
+ zod = createRequire(import.meta.url)("zod");
68
+ } catch {
69
+ throw new Error(`[arc] event "${name}" declares a zod \`payload\` but zod is not installed.`);
70
+ }
71
+ if (typeof zod.toJSONSchema !== "function") throw new Error(`[arc] event "${name}": \`payload\` needs zod v4 (\`z.toJSONSchema\`).`);
72
+ try {
73
+ return zod.toJSONSchema(payload);
74
+ } catch (err) {
75
+ throw new Error(`[arc] event "${name}": its payload cannot travel as JSON — ${err.message}. An event crosses a transport as JSON: send dates as ISO strings (\`z.string()\`).`);
76
+ }
77
+ }
78
+ /**
54
79
  * Create an event registry for cataloging and validating events.
55
80
  *
56
81
  * The registry is opt-in — unregistered events pass validation.
@@ -87,7 +112,8 @@ function createEventRegistry(options) {
87
112
  name: def.name,
88
113
  version: def.version,
89
114
  description: def.description,
90
- schema: def.schema
115
+ schema: def.schema,
116
+ ...def.durability !== void 0 ? { durability: def.durability } : {}
91
117
  }));
92
118
  },
93
119
  validate(name, payload, version) {
@@ -100,6 +126,13 @@ function createEventRegistry(options) {
100
126
  }
101
127
  }
102
128
  if (!target) return { valid: true };
129
+ if (target.payloadSchema) {
130
+ const parsed = target.payloadSchema.safeParse(payload);
131
+ return parsed.success ? { valid: true } : {
132
+ valid: false,
133
+ errors: (parsed.error?.issues ?? []).map((i) => `${i.path.map(String).join(".") || "(payload)"}: ${i.message}`)
134
+ };
135
+ }
103
136
  if (!target.schema) return { valid: true };
104
137
  return customValidator ? customValidator(target.schema, payload) : validatePayload(payload, target.schema);
105
138
  }
@@ -258,6 +291,29 @@ async function subscribeWithSchema(fastify, definition, handler, options) {
258
291
  return fastify.events.subscribe(definition.name, wrapWithSchema(definition, handler, options));
259
292
  }
260
293
  /**
294
+ * Wrap a handler so anything it throws names it: `[<name>] <message>`, the original as `cause`
295
+ * and its string `code` kept. A transport's aggregate error — and so an outbox row's `lastError`
296
+ * — then says WHICH subscriber failed. A message already carrying the label is left as is.
297
+ */
298
+ function wrapWithAttribution(handler, name) {
299
+ const label = `[${name}]`;
300
+ return async (event) => {
301
+ try {
302
+ await handler(event);
303
+ } catch (err) {
304
+ const message = err instanceof Error ? err.message : String(err);
305
+ if (message.startsWith(label)) throw err;
306
+ const attributed = new Error(`${label} ${message}`, { cause: err });
307
+ if (err instanceof Error) {
308
+ attributed.name = err.name;
309
+ const code = Reflect.get(err, "code");
310
+ if (typeof code === "string") Reflect.set(attributed, "code", code);
311
+ }
312
+ throw attributed;
313
+ }
314
+ };
315
+ }
316
+ /**
261
317
  * Pure handler wrapper — returns a new `EventHandler` that catches handler
262
318
  * exceptions and routes them to `onError` (or logs and swallows). For
263
319
  * projection / cache-invalidation / fire-and-forget handlers where retry
@@ -309,4 +365,82 @@ async function subscribeWithBoundary(fastify, pattern, handler, options) {
309
365
  return fastify.events.subscribe(pattern, wrapWithBoundary(handler, options));
310
366
  }
311
367
  //#endregion
312
- export { createEventRegistry as a, wrapWithSchema as i, subscribeWithSchema as n, defineEvent as o, wrapWithBoundary as r, subscribeWithBoundary as t };
368
+ //#region src/events/eventTypes.ts
369
+ /**
370
+ * Event Type Constants and Helpers
371
+ *
372
+ * Provides well-typed event names for CRUD operations and lifecycle events.
373
+ * Use these instead of hand-typing event strings to prevent typos.
374
+ *
375
+ * @example
376
+ * ```typescript
377
+ * import { crudEventType, ARC_LIFECYCLE_EVENTS } from '@classytic/arc/events';
378
+ *
379
+ * // Subscribe to product creation events
380
+ * events.subscribe(crudEventType('product', 'created'), handler);
381
+ *
382
+ * // Subscribe to Arc lifecycle events
383
+ * events.subscribe(ARC_LIFECYCLE_EVENTS.READY, handler);
384
+ * ```
385
+ */
386
+ /** Suffixes for auto-emitted CRUD events */
387
+ const CRUD_EVENT_SUFFIXES = Object.freeze([
388
+ "created",
389
+ "updated",
390
+ "deleted"
391
+ ]);
392
+ /**
393
+ * Build a CRUD event type string.
394
+ *
395
+ * @example
396
+ * ```typescript
397
+ * crudEventType('product', 'created') // 'product.created'
398
+ * crudEventType('order', 'deleted') // 'order.deleted'
399
+ * ```
400
+ */
401
+ function crudEventType(resource, suffix) {
402
+ return `${resource}.${suffix}`;
403
+ }
404
+ /** Arc framework lifecycle events — emitted automatically by the framework */
405
+ const ARC_LIFECYCLE_EVENTS = Object.freeze({
406
+ /** Emitted when a resource plugin is registered */
407
+ RESOURCE_REGISTERED: "arc.resource.registered",
408
+ /** Emitted when Arc is fully ready (all resources registered, onReady fired) */
409
+ READY: "arc.ready"
410
+ });
411
+ /** Cache-specific event types for observability and external triggers */
412
+ const CACHE_EVENTS = Object.freeze({
413
+ /** Emitted when a resource's cache version is bumped */
414
+ VERSION_BUMPED: "arc.cache.version.bumped",
415
+ /** Emitted when a tag version is bumped */
416
+ TAG_VERSION_BUMPED: "arc.cache.tag.bumped"
417
+ });
418
+ /**
419
+ * The payload arc publishes for every auto-CRUD event (`<resource>.created|updated|deleted`).
420
+ * `data` is the resource document as the route returned it.
421
+ */
422
+ const CRUD_EVENT_SCHEMA = Object.freeze({
423
+ type: "object",
424
+ properties: {
425
+ resource: { type: "string" },
426
+ operation: {
427
+ type: "string",
428
+ enum: [
429
+ "create",
430
+ "update",
431
+ "delete"
432
+ ]
433
+ },
434
+ data: {},
435
+ userId: {},
436
+ organizationId: { type: "string" },
437
+ timestamp: { type: "string" }
438
+ },
439
+ required: [
440
+ "resource",
441
+ "operation",
442
+ "timestamp"
443
+ ]
444
+ });
445
+ //#endregion
446
+ export { crudEventType as a, wrapWithAttribution as c, createEventRegistry as d, defineEvent as f, CRUD_EVENT_SUFFIXES as i, wrapWithBoundary as l, CACHE_EVENTS as n, subscribeWithBoundary as o, CRUD_EVENT_SCHEMA as r, subscribeWithSchema as s, ARC_LIFECYCLE_EVENTS as t, wrapWithSchema as u };
@@ -1,9 +1,10 @@
1
1
 
2
- import { i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions } from "../EventTransport-CbUKDzZZ.mjs";
3
- import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-B52abJ8W.mjs";
4
- import { a as EventOutbox, c as OutboxRelayErrorKind, i as MemoryOutboxStore, l as RelayResult, n as OutboxModuleOptions, o as EventOutboxOptions, r as createOutboxModule, s as OutboxRelayErrorHandler, t as OutboxModuleExports } from "../outbox-module-BHdUnuuK.mjs";
2
+ import { i as EventLogger, n as DomainEvent, o as MemoryEventTransport, r as EventHandler, s as MemoryEventTransportOptions } from "../EventTransport-CiicKz2Z.mjs";
3
+ import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-BtmA4-3P.mjs";
4
+ import { a as MemoryOutboxStore, c as OutboxRelayErrorHandler, d as DEFAULT_RELAY_LAG_UNHEALTHY_MS, f as OutboxHealth, i as createOutboxModule, l as OutboxRelayErrorKind, m as readOutboxHealth, n as OutboxModuleExports, o as EventOutbox, p as OutboxHealthStore, r as OutboxModuleOptions, s as EventOutboxOptions, t as OutboxDrainResult, u as RelayResult } from "../outbox-module-gZCHoE4H.mjs";
5
5
  import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
6
- import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-BPKscsTB.mjs";
6
+ import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-BMk-bpQS.mjs";
7
+ import { IdempotencyClaimStore } from "@classytic/repo-core/idempotency";
7
8
  import { OutboxStore } from "@classytic/primitives/outbox";
8
9
  import { RepositoryLike } from "@classytic/repo-core/adapter";
9
10
  //#region src/events/eventTypes.d.ts
@@ -25,7 +26,7 @@ import { RepositoryLike } from "@classytic/repo-core/adapter";
25
26
  * ```
26
27
  */
27
28
  /** Suffixes for auto-emitted CRUD events */
28
- declare const CRUD_EVENT_SUFFIXES: readonly ["created", "updated", "deleted"];
29
+ export declare const CRUD_EVENT_SUFFIXES: readonly ["created", "updated", "deleted"];
29
30
  /** Type for CRUD event suffixes */
30
31
  type CrudEventSuffix = (typeof CRUD_EVENT_SUFFIXES)[number];
31
32
  /**
@@ -37,9 +38,9 @@ type CrudEventSuffix = (typeof CRUD_EVENT_SUFFIXES)[number];
37
38
  * crudEventType('order', 'deleted') // 'order.deleted'
38
39
  * ```
39
40
  */
40
- declare function crudEventType(resource: string, suffix: CrudEventSuffix): string;
41
+ export declare function crudEventType(resource: string, suffix: CrudEventSuffix): string;
41
42
  /** Arc framework lifecycle events — emitted automatically by the framework */
42
- declare const ARC_LIFECYCLE_EVENTS: Readonly<{
43
+ export declare const ARC_LIFECYCLE_EVENTS: Readonly<{
43
44
  /** Emitted when a resource plugin is registered */
44
45
  readonly RESOURCE_REGISTERED: "arc.resource.registered";
45
46
  /** Emitted when Arc is fully ready (all resources registered, onReady fired) */
@@ -48,7 +49,7 @@ declare const ARC_LIFECYCLE_EVENTS: Readonly<{
48
49
  /** Type for Arc lifecycle event names */
49
50
  type ArcLifecycleEvent = (typeof ARC_LIFECYCLE_EVENTS)[keyof typeof ARC_LIFECYCLE_EVENTS];
50
51
  /** Cache-specific event types for observability and external triggers */
51
- declare const CACHE_EVENTS: Readonly<{
52
+ export declare const CACHE_EVENTS: Readonly<{
52
53
  /** Emitted when a resource's cache version is bumped */
53
54
  readonly VERSION_BUMPED: "arc.cache.version.bumped";
54
55
  /** Emitted when a tag version is bumped */
@@ -57,6 +58,26 @@ declare const CACHE_EVENTS: Readonly<{
57
58
  /** Type for cache event names */
58
59
  type CacheEvent = (typeof CACHE_EVENTS)[keyof typeof CACHE_EVENTS];
59
60
  //#endregion
61
+ //#region src/events/inbox.d.ts
62
+ interface InboxOptions {
63
+ /** The claims store — ONE per deployment, shared with command idempotency. */
64
+ readonly store: IdempotencyClaimStore;
65
+ /** Must exceed the handler's worst-case run time. Default: the store's lease default. */
66
+ readonly leaseMs?: number;
67
+ /** Commit or roll back ALL work, including claim completion; external effects need their own key. */
68
+ readonly transaction?: <T>(work: (session: unknown) => Promise<T>) => Promise<T>;
69
+ }
70
+ interface InboxHandlerContext {
71
+ /** Use this session on every database write when a transaction is configured. */
72
+ readonly session?: unknown;
73
+ /** Stable effect deduplication key for external calls or nontransactional handlers. */
74
+ readonly eventId: string;
75
+ }
76
+ type InboxHandler<T> = (event: DomainEvent<T>, context: InboxHandlerContext) => Promise<void>;
77
+ /** The claim operation every inbox row is filed under. */
78
+ export declare const EVENT_INBOX_OPERATION = "event-inbox";
79
+ export declare function withInbox<T = unknown>(name: string, handler: InboxHandler<T>, options: InboxOptions): EventHandler<T>;
80
+ //#endregion
60
81
  //#region src/events/outbox/backoff.d.ts
61
82
  /**
62
83
  * Exponential backoff for outbox failure policies — pure date math shared
@@ -107,13 +128,13 @@ interface ExponentialBackoffOptions {
107
128
  * @example Tuning for a faster transport
108
129
  * ```typescript
109
130
  * exponentialBackoff({ attempt: 3, baseMs: 250, maxMs: 10_000, jitter: 0.3 });
110
- * // attempt=1 → ~250ms ±30%
111
- * // attempt=2 → ~500ms ±30%
112
- * // attempt=3 → ~1000ms ±30%
131
+ * // attempt=1 → 250–325ms (+0–30%: jitter only ever DELAYS, never schedules early)
132
+ * // attempt=2 → 500–650ms
133
+ * // attempt=3 → 1000–1300ms
113
134
  * // attempt=10 → capped at 10_000ms
114
135
  * ```
115
136
  */
116
- declare function exponentialBackoff(options: ExponentialBackoffOptions): Date;
137
+ export declare function exponentialBackoff(options: ExponentialBackoffOptions): Date;
117
138
  //#endregion
118
139
  //#region src/events/repository-outbox-adapter.d.ts
119
140
  interface RepositoryOutboxStoreOptions {
@@ -143,7 +164,7 @@ interface RepositoryOutboxStoreOptions {
143
164
  * quietly does nothing and reports success.
144
165
  */
145
166
  type RepositoryOutboxStore = OutboxStore & Required<Pick<OutboxStore, "fail" | "getDeadLettered" | "purge" | "requeue" | "countByStatus" | "oldestPendingAgeMs">>;
146
- declare function repositoryAsOutboxStore(repository: RepositoryLike, options?: RepositoryOutboxStoreOptions): RepositoryOutboxStore;
167
+ export declare function repositoryAsOutboxStore(repository: RepositoryLike, options?: RepositoryOutboxStoreOptions): RepositoryOutboxStore;
147
168
  //#endregion
148
169
  //#region src/events/subscribe-helpers.d.ts
149
170
  /**
@@ -223,7 +244,7 @@ interface WrapWithSchemaOptions<_T> {
223
244
  * );
224
245
  * ```
225
246
  */
226
- declare function wrapWithSchema<T>(definition: EventDefinitionOutput<T>, handler: EventHandler<T>, options?: WrapWithSchemaOptions<T>): EventHandler<unknown>;
247
+ export declare function wrapWithSchema<T>(definition: EventDefinitionOutput<T>, handler: EventHandler<T>, options?: WrapWithSchemaOptions<T>): EventHandler<unknown>;
227
248
  /**
228
249
  * Convenience: validate + subscribe in one call. Equivalent to
229
250
  * `fastify.events.subscribe(definition.name, wrapWithSchema(definition, handler, options))`.
@@ -245,7 +266,7 @@ declare function wrapWithSchema<T>(definition: EventDefinitionOutput<T>, handler
245
266
  * );
246
267
  * ```
247
268
  */
248
- declare function subscribeWithSchema<T>(fastify: FastifyEventBus, definition: EventDefinitionOutput<T>, handler: EventHandler<T>, options?: WrapWithSchemaOptions<T>): Promise<() => void>;
269
+ export declare function subscribeWithSchema<T>(fastify: FastifyEventBus, definition: EventDefinitionOutput<T>, handler: EventHandler<T>, options?: WrapWithSchemaOptions<T>): Promise<() => void>;
249
270
  interface WrapWithBoundaryOptions {
250
271
  /**
251
272
  * Called when the handler throws. Default behaviour: log the error with
@@ -285,14 +306,14 @@ interface WrapWithBoundaryOptions {
285
306
  * );
286
307
  * ```
287
308
  */
288
- declare function wrapWithBoundary(handler: EventHandler, options?: WrapWithBoundaryOptions): EventHandler;
309
+ export declare function wrapWithBoundary(handler: EventHandler, options?: WrapWithBoundaryOptions): EventHandler;
289
310
  /**
290
311
  * Convenience: subscribe + error-boundary in one call. Equivalent to
291
312
  * `fastify.events.subscribe(pattern, wrapWithBoundary(handler, options))`.
292
313
  *
293
314
  * Returns the unsubscribe function from the underlying transport.
294
315
  */
295
- declare function subscribeWithBoundary(fastify: FastifyEventBus, pattern: string, handler: EventHandler, options?: WrapWithBoundaryOptions): Promise<() => void>;
316
+ export declare function subscribeWithBoundary(fastify: FastifyEventBus, pattern: string, handler: EventHandler, options?: WrapWithBoundaryOptions): Promise<() => void>;
296
317
  /**
297
318
  * Structural type — accepts anything with the `events.subscribe` method,
298
319
  * including `FastifyInstance` (declaration-merged in `eventPlugin.ts`) and
@@ -304,4 +325,8 @@ interface FastifyEventBus {
304
325
  };
305
326
  }
306
327
  //#endregion
307
- export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, type EventDefinitionInput, type EventDefinitionOutput, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, type ExponentialBackoffOptions, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxModuleExports, type OutboxModuleOptions, type OutboxRelayErrorHandler, type OutboxRelayErrorKind, type PayloadOf, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RelayResult, type RepositoryOutboxStore, type RepositoryOutboxStoreOptions, type RetryOptions, type ValidationResult, type WrapWithBoundaryOptions, type WrapWithSchemaOptions, createDeadLetterPublisher, createEventRegistry, createOutboxModule, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };
328
+ //#region src/events/subscriberIdentity.d.ts
329
+ /** Assign a deployment-stable identity for durable per-handler progress; never use an index. */
330
+ export declare function withSubscriberId<T>(id: string, handler: EventHandler<T>): EventHandler<T>;
331
+ //#endregion
332
+ export { type ArcLifecycleEvent, type CacheEvent, type CrudEventSuffix, type CustomValidator, DEFAULT_RELAY_LAG_UNHEALTHY_MS, type EventDefinitionInput, type EventDefinitionOutput, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, type ExponentialBackoffOptions, type FastifyEventBus, type InboxHandler, type InboxHandlerContext, type InboxOptions, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxDrainResult, type OutboxHealth, type OutboxHealthStore, type OutboxModuleExports, type OutboxModuleOptions, type OutboxRelayErrorHandler, type OutboxRelayErrorKind, type PayloadOf, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RelayResult, type RepositoryOutboxStore, type RepositoryOutboxStoreOptions, type RetryOptions, type ValidationResult, type WrapWithBoundaryOptions, type WrapWithSchemaOptions, createDeadLetterPublisher, createEventRegistry, createOutboxModule, defineEvent, eventPlugin, readOutboxHealth, withRetry };
@@ -1,58 +1,54 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { n as MemoryEventTransport } from "../EventTransport-DSqD_Xsg.mjs";
3
- import { a as createEventRegistry, i as wrapWithSchema, n as subscribeWithSchema, o as defineEvent, r as wrapWithBoundary, t as subscribeWithBoundary } from "../subscribe-helpers-DatDkcuG.mjs";
4
- import { i as withRetry, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-zZY-unhr.mjs";
5
- import { i as exponentialBackoff, n as EventOutbox, r as repositoryAsOutboxStore, t as createOutboxModule } from "../outbox-module-D2z5WkDY.mjs";
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
+ import { n as MemoryEventTransport } from "../EventTransport-DZWDsaPa.mjs";
3
+ import { a as crudEventType, d as createEventRegistry, f as defineEvent, i as CRUD_EVENT_SUFFIXES, l as wrapWithBoundary, n as CACHE_EVENTS, o as subscribeWithBoundary, s as subscribeWithSchema, t as ARC_LIFECYCLE_EVENTS, u as wrapWithSchema } from "../eventTypes-N4-omQFR.mjs";
4
+ import { r as withSubscriberId } from "../subscriberIdentity-DeyppIif.mjs";
5
+ import { a as withRetry, i as createDeadLetterPublisher, n as eventPlugin } from "../eventPlugin-BweHAVws.mjs";
6
+ import { r as readOutboxHealth, t as DEFAULT_RELAY_LAG_UNHEALTHY_MS } from "../health-BYAwA6YS.mjs";
7
+ import { i as exponentialBackoff, n as EventOutbox, r as repositoryAsOutboxStore, t as createOutboxModule } from "../outbox-module-Ryk270jd.mjs";
8
+ import { fingerprintRequest, runIdempotent } from "@classytic/repo-core/idempotency";
6
9
  import { MemoryOutboxStore } from "@classytic/primitives/memory-outbox";
7
- //#region src/events/eventTypes.ts
10
+ //#region src/events/inbox.ts
8
11
  /**
9
- * Event Type Constants and Helpers
10
- *
11
- * Provides well-typed event names for CRUD operations and lifecycle events.
12
- * Use these instead of hand-typing event strings to prevent typos.
13
- *
14
- * @example
15
- * ```typescript
16
- * import { crudEventType, ARC_LIFECYCLE_EVENTS } from '@classytic/arc/events';
17
- *
18
- * // Subscribe to product creation events
19
- * events.subscribe(crudEventType('product', 'created'), handler);
20
- *
21
- * // Subscribe to Arc lifecycle events
22
- * events.subscribe(ARC_LIFECYCLE_EVENTS.READY, handler);
23
- * ```
12
+ * Per-handler completion deduplication. Without a shared transaction, a crash between an
13
+ * effect and completion can repeat the effect: handlers must dedupe effects by event id.
14
+ * With `transaction`, the handler's DB effect and claim completion share one session; the
15
+ * host must provide a transaction-aware claim store on the SAME database connection.
24
16
  */
25
- /** Suffixes for auto-emitted CRUD events */
26
- const CRUD_EVENT_SUFFIXES = Object.freeze([
27
- "created",
28
- "updated",
29
- "deleted"
30
- ]);
31
- /**
32
- * Build a CRUD event type string.
33
- *
34
- * @example
35
- * ```typescript
36
- * crudEventType('product', 'created') // 'product.created'
37
- * crudEventType('order', 'deleted') // 'order.deleted'
38
- * ```
39
- */
40
- function crudEventType(resource, suffix) {
41
- return `${resource}.${suffix}`;
17
+ /** The claim operation every inbox row is filed under. */
18
+ const EVENT_INBOX_OPERATION = "event-inbox";
19
+ function withInbox(name, handler, options) {
20
+ if (typeof name !== "string" || name.length === 0) throw new Error("[Arc Events] withInbox: a handler name is required — it is the inbox key.");
21
+ return withSubscriberId(name, async (event) => {
22
+ const execute = async (session) => {
23
+ const run = await runIdempotent({
24
+ store: options.store,
25
+ identity: {
26
+ operation: EVENT_INBOX_OPERATION,
27
+ key: `${name}:${event.meta.id}`
28
+ },
29
+ requestFingerprint: fingerprintRequest({
30
+ type: event.type,
31
+ payload: event.payload
32
+ }),
33
+ ...options.leaseMs !== void 0 ? { leaseMs: options.leaseMs } : {},
34
+ ...session !== void 0 ? { storeOptions: { session } } : {},
35
+ execute: async () => {
36
+ await handler(event, {
37
+ session,
38
+ eventId: event.meta.id
39
+ });
40
+ return true;
41
+ }
42
+ });
43
+ if (run.outcome === "in_flight") throw new Error(`[Arc Events] Handler '${name}' for ${event.type} ${event.meta.id} is in flight elsewhere — retry later.`);
44
+ if (run.outcome === "exhausted") throw new Error(`[Arc Events] Handler '${name}' for ${event.type} ${event.meta.id} exhausted its takeovers: ${run.lastError ?? "unknown"}.`);
45
+ };
46
+ if (options.transaction) await options.transaction(async (session) => {
47
+ if (session === void 0 || session === null) throw new Error("[Arc Events] Inbox transaction must provide a database session.");
48
+ await execute(session);
49
+ });
50
+ else await execute();
51
+ });
42
52
  }
43
- /** Arc framework lifecycle events — emitted automatically by the framework */
44
- const ARC_LIFECYCLE_EVENTS = Object.freeze({
45
- /** Emitted when a resource plugin is registered */
46
- RESOURCE_REGISTERED: "arc.resource.registered",
47
- /** Emitted when Arc is fully ready (all resources registered, onReady fired) */
48
- READY: "arc.ready"
49
- });
50
- /** Cache-specific event types for observability and external triggers */
51
- const CACHE_EVENTS = Object.freeze({
52
- /** Emitted when a resource's cache version is bumped */
53
- VERSION_BUMPED: "arc.cache.version.bumped",
54
- /** Emitted when a tag version is bumped */
55
- TAG_VERSION_BUMPED: "arc.cache.tag.bumped"
56
- });
57
53
  //#endregion
58
- export { ARC_LIFECYCLE_EVENTS, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, EventOutbox, MemoryEventTransport, MemoryOutboxStore, createDeadLetterPublisher, createEventRegistry, createOutboxModule, crudEventType, defineEvent, eventPlugin, exponentialBackoff, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withRetry, wrapWithBoundary, wrapWithSchema };
54
+ export { ARC_LIFECYCLE_EVENTS, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, DEFAULT_RELAY_LAG_UNHEALTHY_MS, EVENT_INBOX_OPERATION, EventOutbox, MemoryEventTransport, MemoryOutboxStore, createDeadLetterPublisher, createEventRegistry, createOutboxModule, crudEventType, defineEvent, eventPlugin, exponentialBackoff, readOutboxHealth, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withInbox, withRetry, withSubscriberId, wrapWithBoundary, wrapWithSchema };