@classytic/arc 2.41.0 → 2.43.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 (230) hide show
  1. package/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
  2. package/dist/{EventTransport-n7qO5Cz3.mjs → EventTransport-tzA7CAB4.mjs} +22 -17
  3. package/dist/{HookSystem-CxvZBo77.mjs → HookSystem-B6UaKRbF.mjs} +2 -2
  4. package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
  5. package/dist/{ResourceRegistry-Cu7uTJp9.mjs → ResourceRegistry-CuCARu2B.mjs} +44 -13
  6. package/dist/actionPermissions-BotX1YOa.mjs +776 -0
  7. package/dist/audit/index.d.mts +43 -5
  8. package/dist/audit/index.mjs +48 -26
  9. package/dist/auth/audit.mjs +1 -1
  10. package/dist/auth/index.d.mts +43 -13
  11. package/dist/auth/index.mjs +130 -155
  12. package/dist/auth/redis-session.mjs +1 -1
  13. package/dist/{authorizationDecision-2YY_Kaj1.mjs → authorizationDecision-DQYrkl5f.mjs} +7 -102
  14. package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
  15. package/dist/boundedMap-BULzfXYU.mjs +57 -0
  16. package/dist/{buildHandler-CUhjGHGZ.mjs → buildHandler-CRQ8EcX5.mjs} +4 -4
  17. package/dist/cache/index.d.mts +22 -34
  18. package/dist/cache/index.mjs +4 -4
  19. package/dist/{caching-DsWDjIxx.mjs → caching-Cc2M1DwO.mjs} +3 -3
  20. package/dist/claimed-B10RTfkM.mjs +8 -0
  21. package/dist/cleanup/index.d.mts +6 -10
  22. package/dist/cleanup/index.mjs +27 -73
  23. package/dist/cli/commands/describe.d.mts +4 -2
  24. package/dist/cli/commands/describe.mjs +7 -4
  25. package/dist/cli/commands/docs.mjs +3 -3
  26. package/dist/cli/commands/doctor.mjs +1 -1
  27. package/dist/cli/commands/generate.mjs +2 -2
  28. package/dist/cli/commands/init.mjs +2 -2
  29. package/dist/cli/commands/introspect.mjs +2 -2
  30. package/dist/cli/index.mjs +3 -3
  31. package/dist/{constants-BxLgNFF7.mjs → constants-Cu7Mx7_U.mjs} +4 -2
  32. package/dist/context/index.d.mts +2 -5
  33. package/dist/context/index.mjs +18 -38
  34. package/dist/core/index.d.mts +3 -3
  35. package/dist/core/index.mjs +8 -8
  36. package/dist/{core-iNGqYerO.mjs → core-Cb0LjccA.mjs} +5 -4
  37. package/dist/{createActionRouter-BBlbPs3k.mjs → createActionRouter-xjqonMRO.mjs} +87 -39
  38. package/dist/{createAggregationRouter-Dv5SuK2x.mjs → createAggregationRouter-zRYWw_5D.mjs} +11 -8
  39. package/dist/{preloadResources-Bbp4QJ8V.mjs → createApp-D-953ZAq.mjs} +299 -390
  40. package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-C9cxS8zt.d.mts} +1 -1
  41. package/dist/{defineAggregation-UyeurGkp.mjs → defineAggregation-Bm8RjMbq.mjs} +1 -1
  42. package/dist/{defineResource-DzGT-2LP.mjs → defineResource-DIC7JZGU.mjs} +118 -1024
  43. package/dist/discovery/index.mjs +1 -1
  44. package/dist/docs/index.d.mts +10 -4
  45. package/dist/docs/index.mjs +4 -4
  46. package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
  47. package/dist/{elevation-BF-vhGxY.mjs → elevation-DG8SrLN-.mjs} +41 -12
  48. package/dist/encryption/index.mjs +3 -3
  49. package/dist/{errorHandler-CIMkCOWm.mjs → errorHandler-BnfDviL1.mjs} +26 -5
  50. package/dist/{errors-C_YhELhe.mjs → errors-CtrywMHP.mjs} +58 -23
  51. package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
  52. package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
  53. package/dist/{eventPlugin-CmXaPWLv.mjs → eventPlugin-Q6BhzHzs.mjs} +82 -21
  54. package/dist/{subscribe-helpers-AVYPgqTl.mjs → eventTypes-Cdhm6ZlV.mjs} +151 -17
  55. package/dist/events/index.d.mts +33 -8
  56. package/dist/events/index.mjs +49 -53
  57. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  58. package/dist/events/transports/redis-stream-entry.mjs +139 -157
  59. package/dist/events/transports/redis.d.mts +1 -1
  60. package/dist/events/transports/redis.mjs +2 -2
  61. package/dist/factory/index.d.mts +32 -17
  62. package/dist/factory/index.mjs +213 -32
  63. package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DhUJ-mvo.d.mts} +11 -25
  64. package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
  65. package/dist/{fields-BTyPLHK0.mjs → fields-ToPYpMqS.mjs} +1 -1
  66. package/dist/filter-merge-IpGywvym.mjs +99 -0
  67. package/dist/{generate-D_YmiCNf.mjs → generate-DRTkLlIl.mjs} +2 -2
  68. package/dist/{headers-CNECDE6q.mjs → headers-BbkIF6vA.mjs} +2 -2
  69. package/dist/health-D-Ft6JM6.mjs +22 -0
  70. package/dist/{health-CxQzbJmr.mjs → health-DqbsG-_w.mjs} +115 -55
  71. package/dist/hooks/index.d.mts +2 -2
  72. package/dist/hooks/index.mjs +2 -2
  73. package/dist/idempotency/index.d.mts +31 -7
  74. package/dist/idempotency/index.mjs +155 -98
  75. package/dist/idempotency/redis.d.mts +1 -1
  76. package/dist/idempotency/redis.mjs +48 -11
  77. package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
  78. package/dist/{preloadResources-CjS2OfpU.d.mts → index-C8zVGYsk.d.mts} +37 -38
  79. package/dist/{index-D4u76eQW.d.mts → index-CRoPtISf.d.mts} +23 -73
  80. package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
  81. package/dist/index-JdbZAIsm.d.mts +510 -0
  82. package/dist/{index-DHbyPo5j.d.mts → index-mqaqZNpe.d.mts} +253 -254
  83. package/dist/index.d.mts +8 -8
  84. package/dist/index.mjs +11 -11
  85. package/dist/{init-Y2MlSN9n.mjs → init-B_oUYDXI.mjs} +1 -1
  86. package/dist/integrations/event-gateway.d.mts +8 -3
  87. package/dist/integrations/event-gateway.mjs +4 -4
  88. package/dist/integrations/index.d.mts +3 -3
  89. package/dist/integrations/index.mjs +1 -1
  90. package/dist/integrations/jobs/index.d.mts +1 -1
  91. package/dist/integrations/jobs/index.mjs +18 -5
  92. package/dist/integrations/mcp/index.d.mts +72 -59
  93. package/dist/integrations/mcp/index.mjs +19 -4
  94. package/dist/integrations/mcp/testing.d.mts +2 -2
  95. package/dist/integrations/mcp/testing.mjs +2 -2
  96. package/dist/integrations/streamline.d.mts +78 -136
  97. package/dist/integrations/streamline.mjs +116 -54
  98. package/dist/integrations/webhooks.d.mts +60 -120
  99. package/dist/integrations/webhooks.mjs +36 -80
  100. package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
  101. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  102. package/dist/integrations/websocket-redis.d.mts +1 -1
  103. package/dist/integrations/websocket-redis.mjs +1 -1
  104. package/dist/integrations/websocket.d.mts +3 -3
  105. package/dist/integrations/websocket.mjs +2 -2
  106. package/dist/{interface-DEWNOMza.d.mts → interface-C0395yUN.d.mts} +13 -1
  107. package/dist/{jsonBody-Cq9KYl2Z.mjs → jsonBody-B4YVGcql.mjs} +2 -2
  108. package/dist/{keys-Cmv4cBRa.mjs → keys-B2KtgMLJ.mjs} +7 -18
  109. package/dist/{loadResources-CblF7xt2.mjs → loadResources-Dn4lalw2.mjs} +3 -3
  110. package/dist/{loadResourcesFromEntry-99fIMC3p.mjs → loadResourcesFromEntry-3sLYbuse.mjs} +2 -2
  111. package/dist/logger/index.d.mts +1 -8
  112. package/dist/logger/index.mjs +3 -153
  113. package/dist/logger-DXjfIqjV.mjs +160 -0
  114. package/dist/{mcpPlugin-xF3Gw-HW.mjs → mcpPlugin-C08HalN5.mjs} +433 -170
  115. package/dist/{memory-CpDZ9NPw.mjs → memory-9Cp7611C.mjs} +36 -34
  116. package/dist/{metrics-BaKnq8ag.mjs → metrics-CWI11eRu.mjs} +39 -72
  117. package/dist/middleware/index.d.mts +2 -2
  118. package/dist/middleware/index.mjs +3 -3
  119. package/dist/migrations/index.mjs +2 -2
  120. package/dist/{module-BkQEesNQ.mjs → module-BSvOQC7E.mjs} +8 -8
  121. package/dist/multiTenant-cY0FkDiA.d.mts +104 -0
  122. package/dist/{multipartBody-C92aJ8am.mjs → multipartBody-CXM2a-Dy.mjs} +46 -7
  123. package/dist/{openapi-C3LFRzPq.mjs → openapi-DT_G4VhY.mjs} +48 -10
  124. package/dist/outbox-admin/index.d.mts +3 -3
  125. package/dist/outbox-admin/index.mjs +6 -18
  126. package/dist/{outbox-module-dsCyLxXH.mjs → outbox-module-Bjgzl6tg.mjs} +203 -33
  127. package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
  128. package/dist/permissions/index.d.mts +5 -4
  129. package/dist/permissions/index.mjs +8 -6
  130. package/dist/{permissions-DiHXy8Ck.mjs → permissions-Cm0uYHvd.mjs} +149 -33
  131. package/dist/{pipe-CBVmUJ-j.mjs → pipe-BqeGT6UO.mjs} +2 -2
  132. package/dist/pipeline/index.d.mts +2 -2
  133. package/dist/pipeline/index.mjs +2 -2
  134. package/dist/plan-FVR6LP59.mjs +565 -0
  135. package/dist/{BaseController-DYJRZ0EJ.mjs → plugin-BccET1tL.mjs} +1256 -344
  136. package/dist/plugins/index.d.mts +14 -11
  137. package/dist/plugins/index.mjs +44 -22
  138. package/dist/plugins/response-cache.mjs +8 -14
  139. package/dist/plugins/tracing-entry.mjs +2 -2
  140. package/dist/{pluralize-2_U6QZQg.mjs → pluralize-CVIOcgMF.mjs} +1 -1
  141. package/dist/presets/filesUpload.d.mts +2 -2
  142. package/dist/presets/filesUpload.mjs +5 -5
  143. package/dist/presets/index.d.mts +26 -31
  144. package/dist/presets/index.mjs +4 -4
  145. package/dist/presets/multiTenant.d.mts +2 -107
  146. package/dist/presets/multiTenant.mjs +22 -162
  147. package/dist/presets/search.d.mts +2 -2
  148. package/dist/presets/search.mjs +2 -2
  149. package/dist/{presets-BdlugYTc.mjs → presets-Cpvj6fGO.mjs} +99 -107
  150. package/dist/{projection-Uvgp-EEj.mjs → projection-Ce4aQhQk.mjs} +2 -2
  151. package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
  152. package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
  153. package/dist/{queryCachePlugin-B5F09bYr.mjs → queryCachePlugin-CFWNH8aT.mjs} +12 -14
  154. package/dist/{rateLimitKey-CK-EU2L8.mjs → rateLimitKey-C4O-kPm0.mjs} +1 -1
  155. package/dist/{realtime-FnnD0n3Z.mjs → realtime-DmPE9S54.mjs} +25 -21
  156. package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
  157. package/dist/{redis-Bzi1y3F9.d.mts → redis-vLvtcGy_.d.mts} +13 -24
  158. package/dist/redisShapes-CQksY67q.d.mts +55 -0
  159. package/dist/registry/index.d.mts +9 -4
  160. package/dist/registry/index.mjs +4 -4
  161. package/dist/{registry-D3y8VEJ1.mjs → registry-Bcz9G1bh.mjs} +10 -8
  162. package/dist/{renewingLease-B3VYh5aU.mjs → renewingLease-qODa7KJQ.mjs} +1 -1
  163. package/dist/{replyHelpers-DPHsdw-5.mjs → replyHelpers-CG_VtVgS.mjs} +2 -2
  164. package/dist/{requestContext-cSVKnYPk.mjs → requestContext-Dsu9P1bV.mjs} +1 -1
  165. package/dist/requirement-CbiLdQ1n.d.mts +22 -0
  166. package/dist/requirement-KDdoRcOP.mjs +35 -0
  167. package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
  168. package/dist/resourceRateLimit-dSjVr1pk.mjs +180 -0
  169. package/dist/{rolldown-runtime-DjDuyglE.mjs → rolldown-runtime-D1w9Y1VJ.mjs} +1 -1
  170. package/dist/routeValidator-D36SW8qt.mjs +70 -0
  171. package/dist/{runtimeCapabilities-DSA_dH1_.mjs → runtimeCapabilities-0tccjRlj.mjs} +1 -1
  172. package/dist/{schedules-BktJb3Y3.mjs → schedules-C-OpmHBq.mjs} +46 -37
  173. package/dist/{schemaConverter-BLCSmNbr.mjs → schemaConverter-BORek3O-.mjs} +2 -2
  174. package/dist/{schemaIR-K16Bb5BX.mjs → schemaIR-C7Vbp71I.mjs} +2 -2
  175. package/dist/schemas/index.d.mts +10 -0
  176. package/dist/schemas/index.mjs +8 -3
  177. package/dist/scim/index.mjs +2 -2
  178. package/dist/scope/index.d.mts +2 -2
  179. package/dist/scope/index.mjs +6 -6
  180. package/dist/{scope-jSxuBTFM.mjs → scope-DQthe88b.mjs} +4 -4
  181. package/dist/shutdown-MqA81z_P.mjs +59 -0
  182. package/dist/{simpleEqualityMatcher-BiOnyflP.mjs → simpleEqualityMatcher-auW8QjXN.mjs} +1 -1
  183. package/dist/{sse-CH7Slxc1.mjs → sse-CYzQBVFe.mjs} +11 -15
  184. package/dist/{sseStream-CN-Lh1Tu.mjs → sseStream-B5_Afiqn.mjs} +2 -2
  185. package/dist/{store-helpers-9DX9-1j_.mjs → store-helpers-BOzk-CuX.mjs} +1 -1
  186. package/dist/{streaming-dvppnxsr.mjs → streaming-DjTQhZrN.mjs} +1 -1
  187. package/dist/subscriberIdentity-CW39uA8G.mjs +28 -0
  188. package/dist/tenancy--ed9eH_z.mjs +43 -0
  189. package/dist/tenantPreparation-BKon6Yox.mjs +67 -0
  190. package/dist/testing/cleanupStoreContract.mjs +1 -1
  191. package/dist/testing/index.d.mts +193 -175
  192. package/dist/testing/index.mjs +286 -212
  193. package/dist/testing/outboxStoreContract.mjs +137 -2
  194. package/dist/testing/requiredPorts.d.mts +17 -0
  195. package/dist/testing/requiredPorts.mjs +65 -0
  196. package/dist/testing/storageContract.mjs +1 -1
  197. package/dist/timing-qczZ0YC-.mjs +46 -0
  198. package/dist/{transactionContext-DCWMI6hV.mjs → transactionContext-DjFkrNG6.mjs} +1 -1
  199. package/dist/{typeGuards-D7tHFdH6.mjs → typeGuards-CySF12G-.mjs} +1 -1
  200. package/dist/types/index.d.mts +4 -4
  201. package/dist/types/index.mjs +1 -1
  202. package/dist/types/storage.mjs +1 -1
  203. package/dist/{types-CWQn9-IC.d.mts → types-B4Aq4Sdj.d.mts} +22 -9
  204. package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
  205. package/dist/{types-CDy3-zw1.mjs → types-Bk9NOgOC.mjs} +4 -4
  206. package/dist/usage/index.d.mts +2 -3
  207. package/dist/usage/index.mjs +5 -5
  208. package/dist/{userHelpers-DDMOZ_Pm.mjs → userHelpers-aklawRaK.mjs} +1 -1
  209. package/dist/utils/index.d.mts +84 -223
  210. package/dist/utils/index.mjs +13 -12
  211. package/dist/{utils-B-zj_b__.mjs → utils-ec1oTo1n.mjs} +315 -404
  212. package/dist/{validate-R0G_JrnK.mjs → validate-9yzHC1e2.mjs} +6 -14
  213. package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
  214. package/dist/{versioning-DuVWcu-F.mjs → versioning-fhhWHV74.mjs} +4 -4
  215. package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
  216. package/dist/{workScope-BhsWde4k.mjs → workScope-zpPyge4t.mjs} +2 -2
  217. package/package.json +578 -574
  218. package/skills/arc/SKILL.md +13 -4
  219. package/skills/arc/references/auth.md +8 -5
  220. package/skills/arc/references/integrations.md +1 -1
  221. package/skills/arc/references/mcp.md +8 -6
  222. package/skills/arc/references/multi-tenancy.md +19 -14
  223. package/skills/arc/references/production.md +27 -10
  224. package/dist/actionPermissions-DPwPWPOV.mjs +0 -350
  225. package/dist/index-CFfLKMH4.d.mts +0 -290
  226. package/dist/interface-BIpgzbZl.mjs +0 -17
  227. package/dist/redis-stream-BPKscsTB.d.mts +0 -260
  228. package/dist/registerRoute-DRsSHPSM.mjs +0 -364
  229. package/dist/sync/changelog.d.mts +0 -3
  230. package/dist/sync/changelog.mjs +0 -3
@@ -1,11 +1,14 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-DjDuyglE.mjs";
3
- import { arcLog } from "./logger/index.mjs";
4
- import { d as createDomainError } from "./errors-C_YhELhe.mjs";
5
- import { n as requestContext } from "./requestContext-cSVKnYPk.mjs";
6
- import { n as MemoryEventTransport, r as createEvent, t as ARC_EVENT_TRANSPORT } from "./EventTransport-n7qO5Cz3.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-D1w9Y1VJ.mjs";
3
+ import { t as arcLog } from "./logger-DXjfIqjV.mjs";
4
+ import { d as createDomainError } from "./errors-CtrywMHP.mjs";
5
+ import { n as requestContext } from "./requestContext-Dsu9P1bV.mjs";
6
+ import { i as runAdmittedWork, n as isAdmittedWork, r as registerShutdownParticipant } from "./shutdown-MqA81z_P.mjs";
7
+ import { n as MemoryEventTransport, r as createEvent, t as ARC_EVENT_TRANSPORT } from "./EventTransport-tzA7CAB4.mjs";
8
+ import { n as preserveSubscriberIdentity } from "./subscriberIdentity-CW39uA8G.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.0 | MIT | https://classytic.com */
2
- import { r as createEvent } from "./EventTransport-n7qO5Cz3.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { r as createEvent } from "./EventTransport-tzA7CAB4.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-n7qO5Cz3.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
@@ -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
+ declare const EVENT_INBOX_OPERATION = "event-inbox";
79
+ 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,9 +128,9 @@ 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
  */
@@ -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
+ declare function withSubscriberId<T>(id: string, handler: EventHandler<T>): EventHandler<T>;
331
+ //#endregion
332
+ export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, DEFAULT_RELAY_LAG_UNHEALTHY_MS, EVENT_INBOX_OPERATION, 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, crudEventType, defineEvent, eventPlugin, exponentialBackoff, readOutboxHealth, repositoryAsOutboxStore, subscribeWithBoundary, subscribeWithSchema, withInbox, withRetry, withSubscriberId, wrapWithBoundary, wrapWithSchema };
@@ -1,58 +1,54 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { n as MemoryEventTransport } from "../EventTransport-n7qO5Cz3.mjs";
3
- import { a as createEventRegistry, i as wrapWithSchema, n as subscribeWithSchema, o as defineEvent, r as wrapWithBoundary, t as subscribeWithBoundary } from "../subscribe-helpers-AVYPgqTl.mjs";
4
- import { i as withRetry, r as createDeadLetterPublisher, t as eventPlugin } from "../eventPlugin-CmXaPWLv.mjs";
5
- import { i as exponentialBackoff, n as EventOutbox, r as repositoryAsOutboxStore, t as createOutboxModule } from "../outbox-module-dsCyLxXH.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { n as MemoryEventTransport } from "../EventTransport-tzA7CAB4.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-Cdhm6ZlV.mjs";
4
+ import { r as withSubscriberId } from "../subscriberIdentity-CW39uA8G.mjs";
5
+ import { a as withRetry, i as createDeadLetterPublisher, n as eventPlugin } from "../eventPlugin-Q6BhzHzs.mjs";
6
+ import { r as readOutboxHealth, t as DEFAULT_RELAY_LAG_UNHEALTHY_MS } from "../health-D-Ft6JM6.mjs";
7
+ import { i as exponentialBackoff, n as EventOutbox, r as repositoryAsOutboxStore, t as createOutboxModule } from "../outbox-module-Bjgzl6tg.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 };
@@ -1,3 +1,3 @@
1
1
 
2
- import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-BPKscsTB.mjs";
2
+ import { n as RedisStreamTransport, r as RedisStreamTransportOptions, t as RedisStreamLike } from "../../redis-stream-BMk-bpQS.mjs";
3
3
  export { type RedisStreamLike, RedisStreamTransport, type RedisStreamTransportOptions };