@classytic/arc 2.41.1 → 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-DSqD_Xsg.mjs → EventTransport-tzA7CAB4.mjs} +22 -17
  3. package/dist/{HookSystem-CMF94SEA.mjs → HookSystem-B6UaKRbF.mjs} +2 -2
  4. package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
  5. package/dist/{ResourceRegistry-MGpXoxHZ.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-DawKGoXb.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-CvxKOc0m.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-DbCBxlgw.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-D5Ubn1fe.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-5lazhva9.mjs → core-Cb0LjccA.mjs} +5 -4
  37. package/dist/{createActionRouter-DNqoaK81.mjs → createActionRouter-xjqonMRO.mjs} +87 -39
  38. package/dist/{createAggregationRouter-BcbQJf10.mjs → createAggregationRouter-zRYWw_5D.mjs} +11 -8
  39. package/dist/{preloadResources-f3boenVZ.mjs → createApp-D-953ZAq.mjs} +299 -390
  40. package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-C9cxS8zt.d.mts} +1 -1
  41. package/dist/{defineAggregation-D_eJjm5A.mjs → defineAggregation-Bm8RjMbq.mjs} +1 -1
  42. package/dist/{defineResource-DxkMTjRn.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-CkvG0LG2.mjs → elevation-DG8SrLN-.mjs} +41 -12
  48. package/dist/encryption/index.mjs +3 -3
  49. package/dist/{errorHandler-D1GHKGlV.mjs → errorHandler-BnfDviL1.mjs} +26 -5
  50. package/dist/{errors-BH7YU_9O.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-zZY-unhr.mjs → eventPlugin-Q6BhzHzs.mjs} +82 -21
  54. package/dist/{subscribe-helpers-DatDkcuG.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-BHrkoF5y.mjs → fields-ToPYpMqS.mjs} +1 -1
  66. package/dist/filter-merge-IpGywvym.mjs +99 -0
  67. package/dist/{generate-BLRf2m3y.mjs → generate-DRTkLlIl.mjs} +2 -2
  68. package/dist/{headers-BIXWKrzQ.mjs → headers-BbkIF6vA.mjs} +2 -2
  69. package/dist/health-D-Ft6JM6.mjs +22 -0
  70. package/dist/{health-ZRHeDHXH.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-CpLkjU18.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-ELR9lfeU.mjs → jsonBody-B4YVGcql.mjs} +2 -2
  108. package/dist/{keys-cpZCtcIE.mjs → keys-B2KtgMLJ.mjs} +7 -18
  109. package/dist/{loadResources-BPfWiI5s.mjs → loadResources-Dn4lalw2.mjs} +3 -3
  110. package/dist/{loadResourcesFromEntry-ixtfL3J2.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-BX5PujI0.mjs → mcpPlugin-C08HalN5.mjs} +433 -170
  115. package/dist/{memory-Iotz0cCx.mjs → memory-9Cp7611C.mjs} +36 -34
  116. package/dist/{metrics-NMsmpRmY.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-DpLJG2_N.mjs → module-BSvOQC7E.mjs} +8 -8
  121. package/dist/multiTenant-cY0FkDiA.d.mts +104 -0
  122. package/dist/{multipartBody-DtRWM6_U.mjs → multipartBody-CXM2a-Dy.mjs} +46 -7
  123. package/dist/{openapi-CmFf3yq_.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-D2z5WkDY.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-D8TBH44E.mjs → permissions-Cm0uYHvd.mjs} +149 -33
  131. package/dist/{pipe-FMWNheSO.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-CYEwjyjN.mjs → plugin-BccET1tL.mjs} +1256 -345
  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-CXWZIzTv.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-ZJSPgmT2.mjs → presets-Cpvj6fGO.mjs} +99 -107
  150. package/dist/{projection-BzIgzoKZ.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-DZHPOm9p.mjs → queryCachePlugin-CFWNH8aT.mjs} +12 -14
  154. package/dist/{rateLimitKey-BvOvcBDi.mjs → rateLimitKey-C4O-kPm0.mjs} +1 -1
  155. package/dist/{realtime-8tIQWZz4.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-DNkgGQcK.mjs → registry-Bcz9G1bh.mjs} +10 -8
  162. package/dist/{renewingLease-yF8BdW4b.mjs → renewingLease-qODa7KJQ.mjs} +1 -1
  163. package/dist/{replyHelpers-CNdvMfM4.mjs → replyHelpers-CG_VtVgS.mjs} +2 -2
  164. package/dist/{requestContext-cDkAUr-P.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-eysyzFAF.mjs → rolldown-runtime-D1w9Y1VJ.mjs} +1 -1
  170. package/dist/routeValidator-D36SW8qt.mjs +70 -0
  171. package/dist/{runtimeCapabilities-CkhS6jn6.mjs → runtimeCapabilities-0tccjRlj.mjs} +1 -1
  172. package/dist/{schedules-j6uXXAeJ.mjs → schedules-C-OpmHBq.mjs} +46 -37
  173. package/dist/{schemaConverter-BVA7cFRG.mjs → schemaConverter-BORek3O-.mjs} +2 -2
  174. package/dist/{schemaIR-CTvPDoPB.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-BUi1xV1I.mjs → scope-DQthe88b.mjs} +4 -4
  181. package/dist/shutdown-MqA81z_P.mjs +59 -0
  182. package/dist/{simpleEqualityMatcher-C9bzq2gO.mjs → simpleEqualityMatcher-auW8QjXN.mjs} +1 -1
  183. package/dist/{sse-BeqVKQKu.mjs → sse-CYzQBVFe.mjs} +11 -15
  184. package/dist/{sseStream-Ppzo-WUH.mjs → sseStream-B5_Afiqn.mjs} +2 -2
  185. package/dist/{store-helpers-dW71XVWL.mjs → store-helpers-BOzk-CuX.mjs} +1 -1
  186. package/dist/{streaming-IcRFjWQU.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-CPbdlnVw.mjs → transactionContext-DjFkrNG6.mjs} +1 -1
  199. package/dist/{typeGuards-BLjPgVBm.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-CgenB1gv.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-ChRvkuSZ.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-CxcctrAh.mjs → utils-ec1oTo1n.mjs} +315 -404
  212. package/dist/{validate-DrYWnOzI.mjs → validate-9yzHC1e2.mjs} +6 -14
  213. package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
  214. package/dist/{versioning-DKkT4hNJ.mjs → versioning-fhhWHV74.mjs} +4 -4
  215. package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
  216. package/dist/{workScope-CxKqoIFn.mjs → workScope-zpPyge4t.mjs} +2 -2
  217. package/package.json +16 -12
  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-BoPGQ5-4.mjs +0 -350
  225. package/dist/index-CFfLKMH4.d.mts +0 -290
  226. package/dist/interface-YzIbF7Ii.mjs +0 -17
  227. package/dist/redis-stream-BPKscsTB.d.mts +0 -260
  228. package/dist/registerRoute-Bsnk6NiZ.mjs +0 -364
  229. package/dist/sync/changelog.d.mts +0 -3
  230. package/dist/sync/changelog.mjs +0 -3
@@ -1,25 +1,17 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { r as workSeedFromEvent, t as runWorkScope } from "../../workScope-CxKqoIFn.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { n as evictToSize } from "../../boundedMap-BULzfXYU.mjs";
3
+ import { r as workSeedFromEvent, t as runWorkScope } from "../../workScope-zpPyge4t.mjs";
4
+ import { t as getSubscriberId } from "../../subscriberIdentity-CW39uA8G.mjs";
3
5
  import sjson from "secure-json-parse";
4
- import { matchEventPattern } from "@classytic/primitives/events";
6
+ import { createEventRouter } from "@classytic/primitives/events";
5
7
  //#region src/events/transports/redis-stream.ts
6
8
  /**
7
- * Redis Stream Event Transport — Durable Event Delivery
9
+ * Redis Stream Event Transport — durable, at-least-once delivery via
10
+ * `XADD`/`XREADGROUP` consumer groups (one consumer per group gets each event).
8
11
  *
9
- * Uses Redis Streams (`XADD`/`XREADGROUP`) for persistent, at-least-once event
10
- * delivery across multiple service instances. Unlike Pub/Sub, events are stored
11
- * in Redis and survive crashes/restarts.
12
- *
13
- * **Delivery guarantee:** at-least-once. Failed messages are left unacked and
14
- * reclaimed after `claimTimeoutMs`, which can result in duplicate handler
15
- * execution. Consumers must be idempotent (e.g. use `event.meta.id` as a
16
- * deduplication key) to achieve effectively-once processing.
17
- *
18
- * Key features:
19
- * - Consumer groups: each event delivered to exactly one consumer per group
20
- * - Crash recovery: pending entries are auto-claimed after `claimTimeoutMs`
21
- * - Dead letter stream: events exceeding `maxRetries` are moved to a DLQ
22
- * - Backpressure: configurable block time and batch size
12
+ * Failed messages stay unacked and are reclaimed after `claimTimeoutMs`, so
13
+ * handlers may run more than once — they must be idempotent (dedupe on
14
+ * `event.meta.id`). Entries exceeding `maxRetries` move to a dead-letter stream.
23
15
  *
24
16
  * @example
25
17
  * ```typescript
@@ -36,13 +28,9 @@ import { matchEventPattern } from "@classytic/primitives/events";
36
28
  * ```
37
29
  */
38
30
  /**
39
- * Hard cap on the failure-context map. Entries are deleted on local ack and
40
- * on DLQ write, but a message that fails HERE and is later acked or
41
- * dead-lettered by a DIFFERENT consumer in the group never gets cleared
42
- * locally — without a cap the map grows for the life of the process in
43
- * multi-consumer deployments. Eviction is oldest-first (Map insertion
44
- * order); an evicted entry only degrades the eventual DLQ envelope to the
45
- * existing cross-consumer fallback message, never loses the event itself.
31
+ * Cap on the failure-context map: a message that fails here but is resolved by
32
+ * ANOTHER consumer is never cleared locally. Eviction is oldest-first and only
33
+ * degrades the DLQ envelope's error detail, never loses the event.
46
34
  */
47
35
  const MAX_FAILURE_CONTEXT_ENTRIES = 1024;
48
36
  var RedisStreamTransport = class {
@@ -58,36 +46,34 @@ var RedisStreamTransport = class {
58
46
  claimTimeoutMs;
59
47
  deadLetterStream;
60
48
  maxLen;
49
+ groupStartId;
61
50
  maxPayloadBytes;
62
51
  logger;
63
- /** Tracks the lifecycle policy — set in constructor, read in close(). */
64
52
  externalLifecycle;
65
53
  closeTimeoutMs;
66
- handlers = /* @__PURE__ */ new Map();
54
+ router = createEventRouter();
67
55
  running = false;
68
56
  pollPromise = null;
69
57
  /**
70
- * Monotonic counter bumped every time the poll loop should stop —
71
- * `unsubscribe` (last handler removed) and `close()` increment it. Each
72
- * `pollLoop` instance captures its generation at start and exits when
73
- * `this.generation` no longer matches. Prevents the
74
- * subscribe → unsubscribe → fast-resubscribe race where the old loop
75
- * would still be in `XREADGROUP BLOCK` while a new loop started, leading
76
- * to two concurrent poll loops on the same consumer name.
58
+ * Bumped whenever the poll loop should stop (last unsubscribe, `close()`).
59
+ * Each loop exits once its captured generation is stale, so a fast
60
+ * unsubscribe → resubscribe never leaves two loops on one consumer name.
77
61
  */
78
62
  generation = 0;
79
63
  groupCreated = false;
80
64
  /**
81
- * Last-seen failure context per message id, populated when an in-process
82
- * handler throws in {@link processEntry}. Consumed (and cleared) by
83
- * {@link moveToDlq} so the dead-letter envelope carries the actual error
84
- * message instead of opaque "reclaimed without context". Entries are
85
- * deleted on ack and on DLQ write; messages resolved by ANOTHER consumer
86
- * never drain locally, so the map is additionally hard-capped at
87
- * {@link MAX_FAILURE_CONTEXT_ENTRIES} with oldest-first eviction.
65
+ * Last handler failure per message id, so the DLQ envelope carries the real
66
+ * error. Cleared on ack / DLQ write; capped at {@link MAX_FAILURE_CONTEXT_ENTRIES}.
88
67
  */
89
68
  failureContext = /* @__PURE__ */ new Map();
90
- /** One-shot guard so the "client lacks xrange" warning fires once per process. */
69
+ /** Set by `hold()`: subscriptions register, consumption waits for `start()`. */
70
+ held = false;
71
+ /** Handler → a key stable across replicas: pattern, name, and order among same-named handlers. */
72
+ handlerKeys = /* @__PURE__ */ new Map();
73
+ handlerKeyOwners = /* @__PURE__ */ new Map();
74
+ handlerPatterns = /* @__PURE__ */ new Map();
75
+ handlerProgressTtlMs;
76
+ tracksProgress;
91
77
  xrangeWarningEmitted = false;
92
78
  constructor(redis, options = {}) {
93
79
  this.redis = redis;
@@ -100,15 +86,25 @@ var RedisStreamTransport = class {
100
86
  this.maxRetries = options.maxRetries ?? 5;
101
87
  this.claimTimeoutMs = options.claimTimeoutMs ?? 3e4;
102
88
  this.deadLetterStream = options.deadLetterStream ?? "arc:events:dlq";
103
- this.maxLen = options.maxLen ?? 1e4;
89
+ this.maxLen = options.maxLen ?? 0;
90
+ this.groupStartId = options.groupStart === "beginning" ? "0" : "$";
104
91
  this.maxPayloadBytes = options.maxPayloadBytes ?? 1e6;
105
92
  this.externalLifecycle = options.externalLifecycle ?? false;
106
93
  this.closeTimeoutMs = options.closeTimeoutMs ?? 1e3;
107
94
  this.logger = options.logger ?? console;
95
+ this.handlerProgressTtlMs = options.handlerProgressTtlMs ?? 6048e5;
96
+ this.tracksProgress = [
97
+ "hset",
98
+ "hgetall",
99
+ "del",
100
+ "pexpire"
101
+ ].every((m) => typeof redis[m] === "function");
102
+ if (!this.tracksProgress) this.logger.warn("[RedisStreamTransport] redis client lacks hset/hgetall/del/pexpire — a retried message re-runs handlers that already succeeded. Use ioredis or add them to the wrapper.");
108
103
  }
109
104
  async publish(event) {
110
105
  const serialized = JSON.stringify(event);
111
- if (serialized.length > this.maxPayloadBytes) throw new Error(`[RedisStreamTransport] Event payload (${serialized.length} bytes) exceeds limit (${this.maxPayloadBytes}). Consider breaking into smaller events or increasing maxPayloadBytes.`);
106
+ const payloadBytes = Buffer.byteLength(serialized, "utf8");
107
+ if (payloadBytes > this.maxPayloadBytes) throw new Error(`[RedisStreamTransport] Event payload (${payloadBytes} bytes) exceeds limit (${this.maxPayloadBytes}). Consider breaking into smaller events or increasing maxPayloadBytes.`);
112
108
  const args = [
113
109
  this.stream,
114
110
  ...this.maxLen > 0 ? [
@@ -125,66 +121,77 @@ var RedisStreamTransport = class {
125
121
  await this.redis.xadd(...args);
126
122
  }
127
123
  async subscribe(pattern, handler) {
128
- if (!this.handlers.has(pattern)) this.handlers.set(pattern, /* @__PURE__ */ new Set());
129
- this.handlers.get(pattern)?.add(handler);
130
- if (!this.running) {
131
- await this.ensureGroup();
132
- this.running = true;
133
- const myGen = ++this.generation;
134
- this.pollPromise = this.pollLoop(myGen).catch((err) => {
135
- this.logger.error("[RedisStreamTransport] Poll loop crashed:", err);
136
- if (this.generation === myGen) this.running = false;
137
- });
124
+ if (!this.handlerKeys.has(handler)) {
125
+ const id = getSubscriberId(handler);
126
+ const key = id === void 0 ? `${pattern}#${handler.name || "anonymous"}` : `subscriber:${id}`;
127
+ const owner = this.handlerKeyOwners.get(key);
128
+ if (owner && owner !== handler) throw new Error(`[RedisStreamTransport] Ambiguous subscriber identity '${key}' — wrap each handler withSubscriberId using a stable unique id.`);
129
+ this.handlerKeyOwners.set(key, handler);
130
+ this.handlerKeys.set(handler, key);
138
131
  }
132
+ const patterns = this.handlerPatterns.get(handler) ?? /* @__PURE__ */ new Set();
133
+ patterns.add(pattern);
134
+ this.handlerPatterns.set(handler, patterns);
135
+ this.router.add(pattern, handler);
136
+ if (!this.held) await this.startPolling();
139
137
  return () => {
140
- const set = this.handlers.get(pattern);
141
- if (set) {
142
- set.delete(handler);
143
- if (set.size === 0) this.handlers.delete(pattern);
138
+ this.router.remove(pattern, handler);
139
+ patterns.delete(pattern);
140
+ if (patterns.size === 0) {
141
+ const key = this.handlerKeys.get(handler);
142
+ if (key && this.handlerKeyOwners.get(key) === handler) this.handlerKeyOwners.delete(key);
143
+ this.handlerKeys.delete(handler);
144
+ this.handlerPatterns.delete(handler);
144
145
  }
145
- if (this.handlers.size === 0 && this.running) {
146
+ if (this.router.size === 0 && this.running) {
146
147
  this.running = false;
147
148
  this.generation++;
148
149
  }
149
150
  };
150
151
  }
151
152
  /**
152
- * Stop polling and release transport state.
153
- *
154
- * **Two close contracts** — pick the one that matches your deployment:
155
- *
156
- * 1. **Default (`externalLifecycle: false`) — strict bounded close.**
157
- * `close()` waits up to `closeTimeoutMs` for the in-flight
158
- * `XREADGROUP BLOCK` to drain. On timeout it calls `redis.disconnect()`
159
- * (or `quit()` if the client lacks `disconnect`) to break the BLOCK
160
- * immediately, then awaits the loop's exit. After `close()` returns
161
- * the transport is fully closed and the connection is released.
162
- *
163
- * 2. **`externalLifecycle: true` — bounded RETURN, background drain.**
164
- * Arc must NOT touch a connection it doesn't own. `close()` returns
165
- * within `closeTimeoutMs`, but the poll loop is left to drain on its
166
- * own when its outstanding `XREADGROUP BLOCK` returns (up to
167
- * `blockTimeMs`). Arc silently absorbs the loop's eventual completion
168
- * so the host doesn't see unhandled rejections / log spam against a
169
- * transport it considers closed. The host's own `redis.quit()` /
170
- * process exit is what ultimately tears the connection down.
153
+ * Register subscriptions without consuming until `start()`. arc holds consumption for the whole
154
+ * boot, so an entry for a module whose handler installs later is not acked with no handler.
155
+ */
156
+ hold() {
157
+ this.held = true;
158
+ }
159
+ /** Begin consuming — every handler registered while held sees the stream from here. */
160
+ async start() {
161
+ this.held = false;
162
+ if (this.router.size > 0) await this.startPolling();
163
+ }
164
+ async startPolling() {
165
+ if (this.running) return;
166
+ await this.ensureGroup();
167
+ this.running = true;
168
+ const myGen = ++this.generation;
169
+ this.pollPromise = this.pollLoop(myGen).catch((err) => {
170
+ this.logger.error("[RedisStreamTransport] Poll loop crashed:", err);
171
+ if (this.generation === myGen) this.running = false;
172
+ });
173
+ }
174
+ /**
175
+ * Stop polling and release transport state. Waits up to `closeTimeoutMs`
176
+ * for the in-flight `XREADGROUP BLOCK`, then:
171
177
  *
172
- * Practical implication: under `externalLifecycle: true`, set
173
- * `blockTimeMs` low (e.g. 500ms) so the background drain window is
174
- * short. The transport is "closed enough" to stop dispatching to
175
- * handlers (handlers map is cleared and generation is bumped) but is
176
- * not "fully closed" in the connection-lifecycle sense until the host
177
- * closes the underlying client.
178
+ * - default: force-disconnects the client to break the BLOCK and awaits the
179
+ * loop — the transport is fully closed on return.
180
+ * - `externalLifecycle: true`: returns without touching the host's client;
181
+ * the loop drains in the background (up to `blockTimeMs` — keep it low)
182
+ * with its outcome silenced. Handlers are already cleared, so nothing
183
+ * dispatches meanwhile.
178
184
  *
179
- * In both modes the generation counter is bumped, so a follow-up
180
- * `subscribe()` spawns a fresh poll loop with a new generation — the
181
- * stale loop exits on its next iteration and never overlaps the new one.
185
+ * A later `subscribe()` starts a fresh loop that never overlaps the stale one.
182
186
  */
183
187
  async close() {
184
188
  this.running = false;
185
189
  this.generation++;
186
- this.handlers.clear();
190
+ this.router.clear();
187
191
  this.failureContext.clear();
192
+ this.handlerKeys.clear();
193
+ this.handlerKeyOwners.clear();
194
+ this.handlerPatterns.clear();
188
195
  if (this.pollPromise !== null) {
189
196
  if (await Promise.race([this.pollPromise.then(() => "drained"), this.sleep(this.closeTimeoutMs).then(() => "timeout")]) === "timeout") {
190
197
  if (!this.externalLifecycle) {
@@ -202,7 +209,7 @@ var RedisStreamTransport = class {
202
209
  async ensureGroup() {
203
210
  if (this.groupCreated) return;
204
211
  try {
205
- await this.redis.xgroup("CREATE", this.stream, this.group, "$", "MKSTREAM");
212
+ await this.redis.xgroup("CREATE", this.stream, this.group, this.groupStartId, "MKSTREAM");
206
213
  } catch (err) {
207
214
  if (err instanceof Error && !err.message.includes("BUSYGROUP")) throw err;
208
215
  }
@@ -227,13 +234,8 @@ var RedisStreamTransport = class {
227
234
  if (!result) return;
228
235
  for (const [, entries] of result) await this.processEntries(entries);
229
236
  }
230
- /**
231
- * Process a batch's entries with bounded concurrency
232
- * (`processingConcurrency`, default 1 = strict order). Worker-pool over a
233
- * shared index — at most N entries in flight, each entry's own handlers
234
- * still sequential inside `processEntry`.
235
- */
236
- async processEntries(entries) {
237
+ /** Process a batch with at most `processingConcurrency` entries in flight. */
238
+ async processEntries(entries, retry = false) {
237
239
  const live = [];
238
240
  const ghostIds = [];
239
241
  for (const [messageId, fields] of entries) if (fields) live.push([messageId, fields]);
@@ -244,14 +246,14 @@ var RedisStreamTransport = class {
244
246
  this.logger.error(`[RedisStreamTransport] Failed to ack ${ghostIds.length} trimmed entr(ies):`, err);
245
247
  }
246
248
  if (this.processingConcurrency <= 1) {
247
- for (const [messageId, fields] of live) await this.processEntry(messageId, fields);
249
+ for (const [messageId, fields] of live) await this.processEntry(messageId, fields, retry);
248
250
  return;
249
251
  }
250
252
  let next = 0;
251
253
  const workers = Array.from({ length: Math.min(this.processingConcurrency, live.length) }, async () => {
252
254
  while (next < live.length) {
253
255
  const entry = live[next++];
254
- if (entry) await this.processEntry(entry[0], entry[1]);
256
+ if (entry) await this.processEntry(entry[0], entry[1], retry);
255
257
  }
256
258
  });
257
259
  await Promise.all(workers);
@@ -272,13 +274,13 @@ var RedisStreamTransport = class {
272
274
  if (overRetryIds.length > 0) await this.moveToDlq(overRetryIds);
273
275
  if (staleIds.length > 0) {
274
276
  const claimed = await this.redis.xclaim(this.stream, this.group, this.consumer, this.claimTimeoutMs, ...staleIds);
275
- await this.processEntries(claimed);
277
+ await this.processEntries(claimed, true);
276
278
  }
277
279
  } catch (err) {
278
280
  this.logger.error("[RedisStreamTransport] claimPending error:", err);
279
281
  }
280
282
  }
281
- async processEntry(messageId, fields) {
283
+ async processEntry(messageId, fields, retry = false) {
282
284
  const event = parseStreamFields(fields);
283
285
  if (!event) {
284
286
  this.logger.warn(`[RedisStreamTransport] Malformed entry ${messageId} — missing type/data or invalid JSON, acking and skipping`);
@@ -286,24 +288,37 @@ var RedisStreamTransport = class {
286
288
  return;
287
289
  }
288
290
  const matchingHandlers = this.getMatchingHandlers(event.type);
291
+ const progressKey = `${this.stream}:handled:${this.group}:${messageId}`;
292
+ const alreadyDone = retry && this.tracksProgress ? new Set(Object.keys(await this.redis.hgetall?.(progressKey) ?? {})) : /* @__PURE__ */ new Set();
293
+ const completed = [];
289
294
  let allSucceeded = true;
290
295
  let lastError;
291
296
  let lastHandlerName;
292
297
  await runWorkScope(workSeedFromEvent(event), async () => {
293
- for (const handler of matchingHandlers) try {
294
- await handler(event);
295
- } catch (err) {
296
- allSucceeded = false;
297
- lastError = err instanceof Error ? err : new Error(String(err));
298
- lastHandlerName = handler.name || lastHandlerName;
299
- this.logger.error(`[RedisStreamTransport] Handler error for ${event.type}:`, err);
298
+ for (const handler of matchingHandlers) {
299
+ const key = this.handlerKeys.get(handler) ?? handler.name;
300
+ if (alreadyDone.has(key)) continue;
301
+ try {
302
+ await handler(event);
303
+ completed.push(key);
304
+ } catch (err) {
305
+ allSucceeded = false;
306
+ lastError = err instanceof Error ? err : new Error(String(err));
307
+ lastHandlerName = handler.name || lastHandlerName;
308
+ this.logger.error(`[RedisStreamTransport] Handler error for ${event.type}:`, err);
309
+ }
300
310
  }
301
311
  });
302
312
  if (allSucceeded) {
303
313
  await this.redis.xack(this.stream, this.group, messageId);
314
+ if (alreadyDone.size > 0) await this.redis.del?.(progressKey);
304
315
  this.failureContext.delete(messageId);
305
316
  return;
306
317
  }
318
+ if (this.tracksProgress && completed.length > 0) {
319
+ await this.redis.hset?.(progressKey, ...completed.flatMap((k) => [k, "1"]));
320
+ await this.redis.pexpire?.(progressKey, this.handlerProgressTtlMs);
321
+ }
307
322
  const now = /* @__PURE__ */ new Date();
308
323
  const prior = this.failureContext.get(messageId);
309
324
  this.failureContext.delete(messageId);
@@ -314,21 +329,15 @@ var RedisStreamTransport = class {
314
329
  attempts: (prior?.attempts ?? 0) + 1,
315
330
  handlerName: lastHandlerName ?? prior?.handlerName
316
331
  });
317
- if (this.failureContext.size > MAX_FAILURE_CONTEXT_ENTRIES) {
318
- const oldest = this.failureContext.keys().next().value;
319
- if (oldest !== void 0) this.failureContext.delete(oldest);
320
- }
332
+ evictToSize(this.failureContext, MAX_FAILURE_CONTEXT_ENTRIES);
321
333
  }
322
334
  /**
323
- * The canonical matcher from primitives — the SAME glob rules the memory
324
- * transport speaks. A private matcher here once accepted `prefix.*` but not
325
- * `prefix:*`, so an `order:*` subscription that worked in-process received
326
- * nothing after a move to the stream, with no error.
335
+ * Uses the primitives router — the same glob rules (and dedup) as the memory
336
+ * transport. Don't swap in a private matcher: subscriptions must behave
337
+ * identically in-process and on the stream.
327
338
  */
328
339
  getMatchingHandlers(eventType) {
329
- const matched = [];
330
- for (const [pattern, handlers] of this.handlers) if (matchEventPattern(pattern, eventType)) for (const h of handlers) matched.push(h);
331
- return matched;
340
+ return this.router.route(eventType);
332
341
  }
333
342
  async moveToDlq(ids) {
334
343
  if (this.deadLetterStream === false) {
@@ -354,17 +363,10 @@ var RedisStreamTransport = class {
354
363
  }
355
364
  }
356
365
  /**
357
- * Reconstruct a `DeadLetteredEvent` for a message id. Reads the original
358
- * entry via `xrange` (when the client supports it) and merges in any
359
- * in-process failure context. Returns `null` only when BOTH sources are
360
- * missing — callers ack-and-drop rather than re-queuing a ghost.
361
- *
362
- * Graceful degradation paths:
363
- * - Client lacks `xrange` (older custom wrappers) → log once, build the
364
- * envelope from `failureContext` alone. Payload is absent but the
365
- * error reason + attempt accounting still survive.
366
- * - `xrange` throws (network blip, ACL) → same fallback.
367
- * - Source entry trimmed before DLQ write → same fallback.
366
+ * Build a `DeadLetteredEvent` from the original entry (via `xrange`) plus
367
+ * any local failure context. If the entry is unreadable (no `xrange`, error,
368
+ * or trimmed) the envelope keeps just the error and attempts. Returns `null`
369
+ * only when both are missing.
368
370
  */
369
371
  async buildDlqEnvelope(id) {
370
372
  const ctx = this.failureContext.get(id);
@@ -404,24 +406,14 @@ var RedisStreamTransport = class {
404
406
  return new Promise((resolve) => setTimeout(resolve, ms));
405
407
  }
406
408
  };
407
- /** Poll-error backoff tuning — see {@link fullJitterBackoffMs}. */
408
409
  const POLL_ERROR_BACKOFF_BASE_MS = 1e3;
409
410
  const POLL_ERROR_BACKOFF_MAX_MS = 3e4;
410
411
  const POLL_ERROR_BACKOFF_FLOOR_MS = 100;
411
412
  /**
412
- * Exponential backoff with full jitter for poll-error retries.
413
- *
414
- * The delay is drawn uniformly from the capped exponential window
415
- * `[floor, min(max, base * 2^(attempt-1))]` — the AWS "full jitter"
416
- * strategy. Full jitter decorrelates a fleet of consumers that all observed
417
- * the same Redis blip; the small floor keeps a near-zero draw from
418
- * hot-spinning the loop while Redis is hard-down.
419
- *
420
- * Sibling of `exponentialBackoff()` in `src/events/outbox.ts`, which is a
421
- * PUBLIC store-author helper with additive jitter and `Date` (retryAt)
422
- * semantics. Deliberately not shared: the two have different jitter models
423
- * and audiences, and coupling them would hang this internal tuning off the
424
- * public API surface.
413
+ * Poll-error delay drawn uniformly from `[floor, min(max, base * 2^(attempt-1))]`
414
+ * ("full jitter"). The floor stops a near-zero draw from hot-spinning while
415
+ * Redis is down. Intentionally separate from the public `exponentialBackoff()`
416
+ * (different jitter model and audience).
425
417
  *
426
418
  * @param attempt 1-indexed consecutive poll-error count (reset on success)
427
419
  * @param random injectable for deterministic tests; defaults to `Math.random`
@@ -431,11 +423,7 @@ function fullJitterBackoffMs(attempt, random = Math.random) {
431
423
  const capped = Math.min(POLL_ERROR_BACKOFF_MAX_MS, exp);
432
424
  return POLL_ERROR_BACKOFF_FLOOR_MS + random() * (capped - POLL_ERROR_BACKOFF_FLOOR_MS);
433
425
  }
434
- /**
435
- * Convert a thrown value into the `DeadLetteredEvent.error` shape — message
436
- * always present, optional `code` (string only) and `stack`. Centralised so
437
- * the failure-context tracker and the DLQ envelope writer agree.
438
- */
426
+ /** Convert a thrown value into the `DeadLetteredEvent.error` shape. */
439
427
  function toErrorRecord(err) {
440
428
  const e = err instanceof Error ? err : new Error(String(err));
441
429
  const code = e.code;
@@ -446,15 +434,9 @@ function toErrorRecord(err) {
446
434
  };
447
435
  }
448
436
  /**
449
- * Parse a Redis Stream entry's flat `[key, value, key, value, ...]` field
450
- * array into a typed `DomainEvent`, or `null` when the entry is malformed
451
- * (missing `type` / `data`, unparseable JSON, or missing required event
452
- * structure).
453
- *
454
- * Pure on purpose — used by both `processEntry` (the live consumer path)
455
- * and `buildDlqEnvelope` (the dead-letter writer). Keeping the parse logic
456
- * in one place avoids the silent drift class that produced the original
457
- * "DLQ has no payload" bug.
437
+ * Parse a stream entry's flat `[key, value, ...]` fields into a `DomainEvent`,
438
+ * or `null` when malformed. Shared by the consumer and the DLQ writer so the
439
+ * two can't drift.
458
440
  */
459
441
  function parseStreamFields(fields) {
460
442
  let eventType;
@@ -1,5 +1,5 @@
1
1
 
2
- import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "../../EventTransport-CbUKDzZZ.mjs";
2
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "../../EventTransport-CiicKz2Z.mjs";
3
3
  //#region src/events/transports/redis.d.ts
4
4
  interface RedisLike {
5
5
  publish(channel: string, message: string): Promise<number>;
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { r as workSeedFromEvent, t as runWorkScope } from "../../workScope-CxKqoIFn.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { r as workSeedFromEvent, t as runWorkScope } from "../../workScope-zpPyge4t.mjs";
3
3
  import sjson from "secure-json-parse";
4
4
  //#region src/events/transports/redis.ts
5
5
  /**
@@ -1,23 +1,15 @@
1
1
 
2
- import { _ as HealthCheck, a as ModuleContribution, c as LoadResourcesOptions, d as loadResources, i as EventHandlerDefinition, l as ResourceLike, n as ArcModuleInput, o as ModuleDisposer, r as ArcModuleRegistry, s as ModuleSetupContext, t as ArcModule, u as ResourceModule } from "../types-DfVDvkdP.mjs";
3
- import { _ as collectModuleScheduledJobs, a as lazyModuleExports, c as getModuleState, d as resolveModule, f as orderModules, g as collectModuleHealthChecks, h as extendModule, i as hasModuleExports, l as hasModule, m as ModuleExtension, n as getModuleExports, o as lazyRequiredModuleExports, p as subscribeModuleEventHandlers, r as getOptionalModuleExports, s as ModuleState, t as defineModule, u as resolveContribution, v as collectModuleWorkflows } from "../index-CFfLKMH4.mjs";
4
- import { c as ResolvedResourceDescriptor, l as describeResolvedModule, o as ResolvedModuleDescriptor, s as ResolvedModuleLifecycle } from "../fastify-augmentation-Cq2nHVaW.mjs";
5
- import { a as RawBodyOptions, c as BetterAuthOption, d as JwtAuthOption, i as MultipartOptions, l as CustomAuthenticatorOption, n as preloadResourcesAsync, o as UnderPressureOptions, r as CreateAppOptions, s as AuthOption, t as preloadResources, u as CustomPluginAuthOption } from "../preloadResources-CjS2OfpU.mjs";
2
+ import { _ as ResourceLike, a as ArcPortRegistry, c as LazyBridge, d as ModulePorts, f as ModuleRequirements, g as LoadResourcesOptions, h as ModuleSetupContext, i as ArcModuleRegistry, l as ModuleContribution, m as ModuleDisposer, n as ArcModule, o as ArcPorts, p as PortName, r as ArcModuleInput, s as EventHandlerDefinition, t as ArcCompositionConfig, u as ModuleName, v as ResourceModule, w as HealthCheck, y as loadResources } from "../types-BJ-2XqVF.mjs";
3
+ import { $ as verifyModuleGraph, A as AsyncApiCatalogEntry, B as ModuleGraphMode, C as planModules, D as ModuleExtension, E as subscribeModuleEventHandlers, F as DurabilityFacts, G as activateBridges, H as ModuleGraphOptions, I as HandlerDurabilityFact, J as crudEventsOf, K as analyzeModuleGraph, L as ModuleGraph, M as toAsyncApi, N as CrudSurface, O as extendModule, P as DerivedPublishes, Q as verifyEventDurability, R as ModuleGraphAnalysis, S as compositionProblems, T as orderModules, U as ModuleKind, V as ModuleGraphNode, W as SkippedBridge, X as eventTopology, Y as dependentsOf, Z as renderModuleGraph, _ as ClosureNode, a as lazyModuleExports, b as PlanModulesOptions, c as getModuleResources, d as resolveContribution, f as resolveModule, g as withRelayNudge, h as createRelayNudge, i as hasModuleExports, j as AsyncApiInfo, k as collectModuleWorkflows, l as getModuleState, m as RelayOption, n as getModuleExports, o as lazyRequiredModuleExports, p as RelayNudge, q as buildModuleGraph, r as getOptionalModuleExports, s as ModuleState, t as defineModule, u as hasModule, v as CompositionProblem, w as resolveModuleClosure, x as assertComposable, y as ModulePlan, z as ModuleGraphIssue } from "../index-JdbZAIsm.mjs";
4
+ import { a as ResolvedModuleDescriptor, o as ResolvedModuleLifecycle, s as ResolvedResourceDescriptor } from "../fastify-augmentation-DhUJ-mvo.mjs";
5
+ import { a as UnderPressureOptions, c as CustomAuthenticatorOption, i as RawBodyOptions, l as CustomPluginAuthOption, n as CreateAppOptions, o as AuthOption, r as MultipartOptions, s as BetterAuthOption, t as BootDisposer, u as JwtAuthOption } from "../index-C8zVGYsk.mjs";
6
6
  import { FastifyInstance, FastifyServerOptions } from "fastify";
7
7
  //#region src/factory/createApp.d.ts
8
8
  /**
9
- * Default redact paths layered into Fastify's pino logger when the host
10
- * doesn't supply a `logger.redact` of their own. Covers the common token /
11
- * cookie / password leak surfaces — `Authorization` and `Cookie` headers
12
- * (and their case-variants because Node lower-cases incoming headers but
13
- * outgoing logs may carry either), API-key headers, and any nested
14
- * `password` / `token` / `secret` / `apiKey` fields anywhere in the log
15
- * tree.
16
- *
17
- * Hosts that need NO redaction (test fixtures, security audits) opt out
18
- * with `logger: { redact: [] }`. Hosts that need MORE redaction supply
19
- * their own `redact` and arc steps aside — this default never overrides
20
- * an explicit setting.
9
+ * Default pino redact paths (auth/cookie/API-key headers, nested
10
+ * password/token/secret fields), applied only when the host supplies no
11
+ * `logger.redact`. Opt out with `logger: { redact: [] }`; an explicit
12
+ * `redact` always wins.
21
13
  */
22
14
  declare const DEFAULT_LOGGER_REDACT_PATHS: readonly ["req.headers.authorization", 'req.headers["x-api-key"]', 'req.headers["x-internal-api-key"]', 'req.headers["x-device-token"]', "req.headers.cookie", 'req.headers["set-cookie"]', 'res.headers["set-cookie"]', "*.password", "*.passwordHash", "*.token", "*.accessToken", "*.refreshToken", "*.secret", "*.apiKey"];
23
15
  /**
@@ -111,6 +103,29 @@ interface FetchHandlerOptions {
111
103
  */
112
104
  declare function toFetchHandler(app: FastifyInstance, options?: FetchHandlerOptions): (request: Request) => Promise<Response>;
113
105
  //#endregion
106
+ //#region src/factory/preloadResources.d.ts
107
+ /** Eager glob result: `{ '/path/to/file.ts': resourceModule }` */
108
+ type EagerGlobResult = Record<string, unknown>;
109
+ /** Lazy glob result: `{ '/path/to/file.ts': () => Promise<unknown> }` */
110
+ type LazyGlobResult = Record<string, () => Promise<unknown>>;
111
+ /**
112
+ * Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
113
+ *
114
+ * Accepts either:
115
+ * - `{ import: 'default' }` form: values are the resource directly
116
+ * - default form: values are the full module — picks first export with `toPlugin()`
117
+ *
118
+ * Throws if any module doesn't yield a valid `ResourceLike`.
119
+ */
120
+ declare function preloadResources(globResult: EagerGlobResult): ResourceLike[];
121
+ /**
122
+ * Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
123
+ *
124
+ * Use this when resources depend on prior bootstrap (e.g., engine init) and
125
+ * cannot be evaluated at import time of the preload file.
126
+ */
127
+ declare function preloadResourcesAsync(globResult: LazyGlobResult): Promise<ResourceLike[]>;
128
+ //#endregion
114
129
  //#region src/factory/presets.d.ts
115
130
  /**
116
131
  * Production preset - strict security, performance optimized
@@ -152,4 +167,4 @@ declare const testingPreset: Partial<CreateAppOptions>;
152
167
  declare const edgePreset: Partial<CreateAppOptions>;
153
168
  declare function getPreset(name: "production" | "development" | "testing" | "edge" | "worker"): Partial<CreateAppOptions>;
154
169
  //#endregion
155
- export { ArcFactory, type ArcModule, type ArcModuleInput, type ArcModuleRegistry, type ArcWorker, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CreateWorkerOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, DEFAULT_LOGGER_REDACT_PATHS, type EventHandlerDefinition, type FetchHandlerOptions, type JwtAuthOption, type LoadResourcesOptions, type ModuleContribution, type ModuleDisposer, type ModuleExtension, type ModuleSetupContext, type ModuleState, type MultipartOptions, type RawBodyOptions, type ResolvedModuleDescriptor, type ResolvedModuleLifecycle, type ResolvedResourceDescriptor, type ResourceLike, type ResourceModule, type UnderPressureOptions, type WorkerHealthOptions, collectModuleHealthChecks, collectModuleScheduledJobs, collectModuleWorkflows, createApp, createWorker, defineModule, describeResolvedModule, developmentPreset, edgePreset, extendModule, getModuleExports, getModuleState, getOptionalModuleExports, getPreset, hasModule, hasModuleExports, lazyModuleExports, lazyRequiredModuleExports, loadResources, orderModules, preloadResources, preloadResourcesAsync, productionPreset, resolveContribution, resolveModule, subscribeModuleEventHandlers, testingPreset, toFetchHandler };
170
+ export { type ArcCompositionConfig, ArcFactory, type ArcModule, type ArcModuleInput, type ArcModuleRegistry, type ArcPortRegistry, type ArcPorts, type ArcWorker, type AsyncApiCatalogEntry, type AsyncApiInfo, type AuthOption, type BetterAuthOption, type BootDisposer, type ClosureNode, type CompositionProblem, type CreateAppOptions, type CreateWorkerOptions, type CrudSurface, type CustomAuthenticatorOption, type CustomPluginAuthOption, DEFAULT_LOGGER_REDACT_PATHS, type DerivedPublishes, type DurabilityFacts, type EagerGlobResult, type EventHandlerDefinition, type FetchHandlerOptions, type HandlerDurabilityFact, type JwtAuthOption, type LazyBridge, type LazyGlobResult, type LoadResourcesOptions, type ModuleContribution, type ModuleDisposer, type ModuleExtension, type ModuleGraph, type ModuleGraphAnalysis, type ModuleGraphIssue, type ModuleGraphMode, type ModuleGraphNode, type ModuleGraphOptions, type ModuleKind, type ModuleName, type ModulePlan, type ModulePorts, type ModuleRequirements, type ModuleSetupContext, type ModuleState, type MultipartOptions, type PlanModulesOptions, type PortName, type RawBodyOptions, type RelayNudge, type RelayOption, type ResolvedModuleDescriptor, type ResolvedModuleLifecycle, type ResolvedResourceDescriptor, type ResourceLike, type ResourceModule, type SkippedBridge, type UnderPressureOptions, type WorkerHealthOptions, activateBridges, analyzeModuleGraph, assertComposable, buildModuleGraph, collectModuleWorkflows, compositionProblems, createApp, createRelayNudge, createWorker, crudEventsOf, defineModule, dependentsOf, developmentPreset, edgePreset, eventTopology, extendModule, getModuleExports, getModuleResources, getModuleState, getOptionalModuleExports, getPreset, hasModule, hasModuleExports, lazyModuleExports, lazyRequiredModuleExports, loadResources, orderModules, planModules, preloadResources, preloadResourcesAsync, productionPreset, renderModuleGraph, resolveContribution, resolveModule, resolveModuleClosure, subscribeModuleEventHandlers, testingPreset, toAsyncApi, toFetchHandler, verifyEventDurability, verifyModuleGraph, withRelayNudge };