@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
@@ -0,0 +1,219 @@
1
+
2
+ import { a as EventTransport, i as EventLogger, n as DomainEvent, r as EventHandler } from "./EventTransport-CiicKz2Z.mjs";
3
+ //#region src/events/transports/redis-stream.d.ts
4
+ interface RedisStreamLike {
5
+ xadd(key: string, id: string, ...fieldValues: string[]): Promise<string | null>;
6
+ /**
7
+ * Per-handler progress for a partially failed message — which handlers already completed, so
8
+ * a retry (on any replica) runs only the rest. Optional for custom wrappers; without them a
9
+ * retry re-runs every handler and the transport warns once.
10
+ */
11
+ hset?(key: string, ...fieldValues: string[]): Promise<unknown>;
12
+ hgetall?(key: string): Promise<Record<string, string>>;
13
+ del?(...keys: string[]): Promise<unknown>;
14
+ pexpire?(key: string, ms: number): Promise<unknown>;
15
+ xreadgroup(command: "GROUP", group: string, consumer: string, ...args: unknown[]): Promise<Array<[string, Array<[string, string[] | null]>]> | null>;
16
+ xack(key: string, group: string, ...ids: unknown[]): Promise<number>;
17
+ xgroup(command: string, key: string, group: string, ...args: unknown[]): Promise<unknown>;
18
+ xpending(key: string, group: string, ...args: unknown[]): Promise<unknown[]>;
19
+ xclaim(key: string, group: string, consumer: string, minIdleTime: number, ...ids: unknown[]): Promise<unknown[]>;
20
+ xlen(key: string): Promise<number>;
21
+ /**
22
+ * Lets the DLQ writer copy the original payload into the envelope, making
23
+ * dead-lettered events replayable. Without it the envelope keeps the error
24
+ * and attempt count but `event.payload` is `null` (warned once per process).
25
+ */
26
+ xrange?(key: string, start: string, end: string): Promise<Array<[string, string[]]>>;
27
+ /** Graceful close — flushes queued commands then closes. ioredis-style. */
28
+ quit(): Promise<unknown>;
29
+ /**
30
+ * Force-disconnect, abandoning a live `XREADGROUP BLOCK`. When missing,
31
+ * `close()` falls back to `quit()` and may wait up to `blockTimeMs`.
32
+ */
33
+ disconnect?(): void;
34
+ }
35
+ interface RedisStreamTransportOptions {
36
+ /**
37
+ * Redis stream key name.
38
+ * @default 'arc:events'
39
+ */
40
+ stream?: string;
41
+ /**
42
+ * How long a partially failed message's per-handler progress is kept, in ms. Must outlast the
43
+ * retry window. @default 7 days
44
+ */
45
+ handlerProgressTtlMs?: number;
46
+ /**
47
+ * Consumer group name. Each group receives every event independently.
48
+ * Multiple instances of the same service should share a group name.
49
+ * @default 'default'
50
+ */
51
+ group?: string;
52
+ /**
53
+ * Consumer name within the group. Must be unique per instance.
54
+ * @default 'consumer-<random>'
55
+ */
56
+ consumer?: string;
57
+ /**
58
+ * Block time in ms when waiting for new events.
59
+ * @default 5000
60
+ */
61
+ blockTimeMs?: number;
62
+ /**
63
+ * Max events to read per batch.
64
+ * @default 10
65
+ */
66
+ batchSize?: number;
67
+ /**
68
+ * Max entries of a batch processed concurrently. `1` keeps strict stream
69
+ * order; higher values let entries complete OUT OF ORDER, so raise it only
70
+ * for order-independent handlers. Handlers for a single event always run
71
+ * sequentially.
72
+ * @default 1
73
+ */
74
+ processingConcurrency?: number;
75
+ /**
76
+ * Max delivery attempts before moving to dead letter stream.
77
+ * @default 5
78
+ */
79
+ maxRetries?: number;
80
+ /**
81
+ * Idle time in ms before another consumer may claim a pending entry
82
+ * (crash recovery).
83
+ * @default 30000
84
+ */
85
+ claimTimeoutMs?: number;
86
+ /**
87
+ * Dead letter stream name. Failed events are moved here after maxRetries.
88
+ * Set to `false` to disable DLQ (failed events are acked and dropped).
89
+ * @default 'arc:events:dlq'
90
+ */
91
+ deadLetterStream?: string | false;
92
+ /**
93
+ * Max stream length (approximate, XADD MAXLEN ~). Trimming can drop entries still PENDING for a
94
+ * slow consumer, so it is opt-in: set it only with lag / oldest-pending monitoring in place.
95
+ * @default 0 (no trimming)
96
+ */
97
+ maxLen?: number;
98
+ /**
99
+ * Where a NEW consumer group starts reading. `'latest'` (`$`) skips entries published before the
100
+ * group existed; `'beginning'` (`0`) replays the whole stream — including every side effect.
101
+ * @default 'latest'
102
+ */
103
+ groupStart?: "latest" | "beginning";
104
+ /**
105
+ * Max serialized event size in bytes; `publish()` rejects larger events.
106
+ * @default 1_000_000 (1 MB)
107
+ */
108
+ maxPayloadBytes?: number;
109
+ /**
110
+ * If `true`, `close()` never quits/disconnects the client — for a Redis
111
+ * connection shared with other transports or stores.
112
+ * @default false
113
+ */
114
+ externalLifecycle?: boolean;
115
+ /**
116
+ * Max time `close()` waits for the in-flight `XREADGROUP BLOCK` to drain.
117
+ * On timeout the client is force-disconnected, or — under
118
+ * `externalLifecycle` — the loop drains in the background (see `close()`).
119
+ * @default 1000
120
+ */
121
+ closeTimeoutMs?: number;
122
+ /**
123
+ * Logger for error messages (default: console).
124
+ * Pass `fastify.log` to integrate with your application logger.
125
+ */
126
+ logger?: EventLogger;
127
+ }
128
+ declare class RedisStreamTransport implements EventTransport {
129
+ readonly name = "redis-stream";
130
+ private redis;
131
+ private stream;
132
+ private group;
133
+ private consumer;
134
+ private blockTimeMs;
135
+ private batchSize;
136
+ private processingConcurrency;
137
+ private maxRetries;
138
+ private claimTimeoutMs;
139
+ private deadLetterStream;
140
+ private maxLen;
141
+ private groupStartId;
142
+ private maxPayloadBytes;
143
+ private logger;
144
+ private externalLifecycle;
145
+ private closeTimeoutMs;
146
+ private router;
147
+ private running;
148
+ private pollPromise;
149
+ /**
150
+ * Bumped whenever the poll loop should stop (last unsubscribe, `close()`).
151
+ * Each loop exits once its captured generation is stale, so a fast
152
+ * unsubscribe → resubscribe never leaves two loops on one consumer name.
153
+ */
154
+ private generation;
155
+ private groupCreated;
156
+ /**
157
+ * Last handler failure per message id, so the DLQ envelope carries the real
158
+ * error. Cleared on ack / DLQ write; capped at {@link MAX_FAILURE_CONTEXT_ENTRIES}.
159
+ */
160
+ private failureContext;
161
+ /** Set by `hold()`: subscriptions register, consumption waits for `start()`. */
162
+ private held;
163
+ /** Handler → a key stable across replicas: pattern, name, and order among same-named handlers. */
164
+ private handlerKeys;
165
+ private handlerKeyOwners;
166
+ private handlerPatterns;
167
+ private handlerProgressTtlMs;
168
+ private readonly tracksProgress;
169
+ private xrangeWarningEmitted;
170
+ constructor(redis: RedisStreamLike, options?: RedisStreamTransportOptions);
171
+ publish(event: DomainEvent): Promise<void>;
172
+ subscribe(pattern: string, handler: EventHandler): Promise<() => void>;
173
+ /**
174
+ * Register subscriptions without consuming until `start()`. arc holds consumption for the whole
175
+ * boot, so an entry for a module whose handler installs later is not acked with no handler.
176
+ */
177
+ hold(): void;
178
+ /** Begin consuming — every handler registered while held sees the stream from here. */
179
+ start(): Promise<void>;
180
+ private startPolling;
181
+ /**
182
+ * Stop polling and release transport state. Waits up to `closeTimeoutMs`
183
+ * for the in-flight `XREADGROUP BLOCK`, then:
184
+ *
185
+ * - default: force-disconnects the client to break the BLOCK and awaits the
186
+ * loop — the transport is fully closed on return.
187
+ * - `externalLifecycle: true`: returns without touching the host's client;
188
+ * the loop drains in the background (up to `blockTimeMs` — keep it low)
189
+ * with its outcome silenced. Handlers are already cleared, so nothing
190
+ * dispatches meanwhile.
191
+ *
192
+ * A later `subscribe()` starts a fresh loop that never overlaps the stale one.
193
+ */
194
+ close(): Promise<void>;
195
+ private ensureGroup;
196
+ private pollLoop;
197
+ private readNewMessages;
198
+ /** Process a batch with at most `processingConcurrency` entries in flight. */
199
+ private processEntries;
200
+ private claimPending;
201
+ private processEntry;
202
+ /**
203
+ * Uses the primitives router — the same glob rules (and dedup) as the memory
204
+ * transport. Don't swap in a private matcher: subscriptions must behave
205
+ * identically in-process and on the stream.
206
+ */
207
+ private getMatchingHandlers;
208
+ private moveToDlq;
209
+ /**
210
+ * Build a `DeadLetteredEvent` from the original entry (via `xrange`) plus
211
+ * any local failure context. If the entry is unreadable (no `xrange`, error,
212
+ * or trimmed) the envelope keeps just the error and attempts. Returns `null`
213
+ * only when both are missing.
214
+ */
215
+ private buildDlqEnvelope;
216
+ private sleep;
217
+ }
218
+ //#endregion
219
+ export { RedisStreamTransport as n, RedisStreamTransportOptions as r, RedisStreamLike as t };
@@ -1,5 +1,6 @@
1
1
 
2
- import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-DEWNOMza.mjs";
2
+ import { n as UpstashKeyValueCommands, t as IoredisKeyValueCommands } from "./redisShapes-CQksY67q.mjs";
3
+ import { n as IdempotencyResult, r as IdempotencyStore } from "./interface-C0395yUN.mjs";
3
4
  //#region src/idempotency/stores/redis.d.ts
4
5
  interface RedisClient {
5
6
  get(key: string): Promise<string | null>;
@@ -26,6 +27,7 @@ interface RedisIdempotencyStoreOptions {
26
27
  }
27
28
  declare class RedisIdempotencyStore implements IdempotencyStore {
28
29
  readonly name = "redis";
30
+ readonly supportsFencedCompletion: boolean;
29
31
  private client;
30
32
  private prefix;
31
33
  private lockPrefix;
@@ -36,6 +38,7 @@ declare class RedisIdempotencyStore implements IdempotencyStore {
36
38
  get(key: string): Promise<IdempotencyResult | undefined>;
37
39
  set(key: string, result: Omit<IdempotencyResult, "key">): Promise<void>;
38
40
  tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;
41
+ setIfLocked(key: string, requestId: string, result: Omit<IdempotencyResult, "key">): Promise<boolean>;
39
42
  unlock(key: string, requestId: string): Promise<void>;
40
43
  isLocked(key: string): Promise<boolean>;
41
44
  delete(key: string): Promise<void>;
@@ -46,21 +49,12 @@ declare class RedisIdempotencyStore implements IdempotencyStore {
46
49
  close(): Promise<void>;
47
50
  }
48
51
  /**
49
- * Minimal ioredis shape we depend on — keeps this file peer-dep-free.
50
- *
51
- * Variadic tails are `unknown[]`, not `(string | number)[]`: ioredis types
52
- * every command as overloads whose tails mix literal tokens (`'MATCH'`),
53
- * `Buffer` and an optional callback, and a narrower tail matches NONE of
54
- * them — a real `Redis` instance then fails to assign (TS2345). Pinned by
55
- * `tests/types/ioredis-assignability.test.ts`.
52
+ * Minimal ioredis shape the idempotency store depends on — the shared
53
+ * key/value commands (`utils/redisShapes.ts`, where the `unknown[]`-tail rule
54
+ * is explained) plus `exists` and lifecycle. Keeps this file peer-dep-free.
56
55
  */
57
- interface IoredisLike {
58
- get(key: string): Promise<string | null>;
59
- set(...args: unknown[]): Promise<string | null>;
60
- del(...keys: string[]): Promise<number>;
56
+ interface IoredisLike extends IoredisKeyValueCommands {
61
57
  exists(...keys: string[]): Promise<number>;
62
- scan(cursor: string | number, ...args: unknown[]): Promise<[string, string[]]>;
63
- eval?(script: string, numKeys: number, ...args: (string | number)[]): Promise<unknown>;
64
58
  quit?(): Promise<string>;
65
59
  disconnect?(): void;
66
60
  }
@@ -86,17 +80,12 @@ interface IoredisLike {
86
80
  declare function ioredisAsIdempotencyClient(client: IoredisLike): RedisClient & {
87
81
  eval?: (script: string, numkeys: number, ...args: (string | number)[]) => Promise<unknown>;
88
82
  };
89
- /** Minimal `@upstash/redis` shape we depend on (REST client, edge-safe). */
90
- interface UpstashRedisLike {
91
- get(key: string): Promise<string | null | unknown>;
92
- set(key: string, value: unknown, opts?: Record<string, unknown>): Promise<unknown>;
93
- del(...keys: string[]): Promise<number>;
83
+ /**
84
+ * Minimal `@upstash/redis` shape the idempotency store depends on (REST,
85
+ * edge-safe) — the shared key/value commands plus `exists`.
86
+ */
87
+ interface UpstashRedisLike extends UpstashKeyValueCommands {
94
88
  exists(...keys: string[]): Promise<number>;
95
- scan(cursor: number | string, opts?: {
96
- match?: string;
97
- count?: number;
98
- }): Promise<[number, string[]] | [string, string[]]>;
99
- eval?(script: string, keys: string[], args: (string | number)[]): Promise<unknown>;
100
89
  }
101
90
  /**
102
91
  * Wrap an `@upstash/redis` REST client as an idempotency `RedisClient`.
@@ -0,0 +1,55 @@
1
+
2
+ //#region src/utils/redisShapes.d.ts
3
+ /**
4
+ * The key/value command shapes arc's cache and idempotency stores share, for
5
+ * the two Redis clients hosts actually plug in: ioredis (TCP) and
6
+ * `@upstash/redis` (REST, edge-safe).
7
+ *
8
+ * Both stores used to declare their own copies of these, and they drifted the
9
+ * way copies do — the ioredis-assignability fix in 2.41 had to be applied to
10
+ * each separately. Each store's exported `IoredisLike` / `UpstashRedisLike`
11
+ * now EXTENDS the base here and adds only what that store uses beyond it, so
12
+ * the exported names are unchanged.
13
+ *
14
+ * Deliberately NOT a single shape for every Redis user in arc. Streams
15
+ * (`xadd`/`xreadgroup`), pub/sub (`subscribe`/`duplicate`), sessions (sets),
16
+ * usage (hashes) and circuit counters (`incr`) share almost no commands with
17
+ * each other, and where they overlap the signatures differ on purpose. A
18
+ * shared superset would couple unrelated features and make every host wrapper
19
+ * implement commands it never serves.
20
+ *
21
+ * ## The one rule every ioredis shape must follow
22
+ *
23
+ * Variadic tails are `unknown[]`, never `(string | number)[]`. ioredis types
24
+ * each command as a set of OVERLOADS whose tails mix literal tokens (`'MATCH'`,
25
+ * `'PX'`, `'NX'`), `Buffer`, and an optional trailing callback. A narrower
26
+ * tail matches none of them, and `new Redis(url)` then fails to assign with
27
+ * TS2345. Pinned for every arc shape by `tests/types/ioredis-assignability.test.ts`.
28
+ */
29
+ /** ioredis key/value commands shared by the cache and idempotency stores. */
30
+ interface IoredisKeyValueCommands {
31
+ get(key: string): Promise<string | null>;
32
+ set(...args: unknown[]): Promise<string | null>;
33
+ del(...keys: string[]): Promise<number>;
34
+ scan(cursor: string | number, ...args: unknown[]): Promise<[string, string[]]>;
35
+ eval?(script: string, numKeys: number, ...args: (string | number)[]): Promise<unknown>;
36
+ }
37
+ /**
38
+ * `@upstash/redis` key/value commands shared by the cache and idempotency
39
+ * stores. REST-based, so it runs on edge runtimes (Cloudflare Workers, Vercel
40
+ * Edge, Deno Deploy) where TCP — and thus ioredis — is unavailable. Note the
41
+ * different call shapes: options objects rather than positional tokens, and
42
+ * `eval(script, keys[], args[])`.
43
+ */
44
+ interface UpstashKeyValueCommands {
45
+ get(key: string): Promise<string | null | unknown>;
46
+ set(key: string, value: unknown, opts?: Record<string, unknown>): Promise<unknown>;
47
+ del(...keys: string[]): Promise<number>;
48
+ scan(cursor: number | string, opts?: {
49
+ match?: string;
50
+ count?: number;
51
+ }): Promise<[number, string[]] | [string, string[]]>;
52
+ eval?(script: string, keys: string[], args: (string | number)[]): Promise<unknown>;
53
+ }
54
+ //#endregion
55
+ export { UpstashKeyValueCommands as n, IoredisKeyValueCommands as t };
@@ -1,6 +1,6 @@
1
1
 
2
- import { K as ResolvedTenantPurge, R as RegisterOptions, V as ResourceDefinition, X as CrudRouteKey, k as IntrospectionPluginOptions, p as RegistryEntry, z as ResourceRegistry } from "../index-DHbyPo5j.mjs";
3
- import "../fastify-augmentation-Cq2nHVaW.mjs";
2
+ import { F as ResourceDefinition, G as CrudRouteKey, M as ResourceRegistry, N as RouteRow, S as IntrospectionPluginOptions, V as ResolvedTenantPurge, j as RegisterOptions, o as RegistryEntry } from "../index-mqaqZNpe.mjs";
3
+ import "../fastify-augmentation-DhUJ-mvo.mjs";
4
4
  import { TenantPurgeProgress, TenantPurgeStrategy } from "@classytic/repo-core/repository";
5
5
  import { FastifyPluginAsync } from "fastify";
6
6
  //#region src/registry/assertNoTenantData.d.ts
@@ -277,7 +277,6 @@ declare function getCascadingResourcesWithMetadata(registry: ResourceRegistry):
277
277
  declare function cascadeDeleteForOrganization(registry: ResourceRegistry, options: CascadeOptions): Promise<CascadeReport>;
278
278
  //#endregion
279
279
  //#region src/registry/introspectionPlugin.d.ts
280
- declare const introspectionPlugin: FastifyPluginAsync<IntrospectionPluginOptions>;
281
280
  declare const _default: FastifyPluginAsync<IntrospectionPluginOptions>;
282
281
  //#endregion
283
282
  //#region src/registry/manifest.d.ts
@@ -326,6 +325,12 @@ interface ResourceManifest {
326
325
  readonly customRoutes: readonly CustomRouteManifestEntry[];
327
326
  /** Tenant scoping field (when set) — surfaces for FE callers that need to send `x-organization-id`. */
328
327
  readonly tenantField?: string | false;
328
+ /**
329
+ * Optimistic-concurrency version field — present only when the resource declares
330
+ * `concurrency`. A client sends `If-Match: "<doc[versionField]>"` on update and gets
331
+ * `412 arc.precondition_failed` for a stale one (arc-next: `createCrudApi(entity, { versionField })`).
332
+ */
333
+ readonly versionField?: string;
329
334
  }
330
335
  /**
331
336
  * Build a manifest from a `ResourceDefinition` (the value returned by
@@ -345,4 +350,4 @@ declare function buildResourceManifest(resource: ResourceDefinition): ResourceMa
345
350
  */
346
351
  declare function buildResourceManifestFromRegistry(entry: RegistryEntry): ResourceManifest;
347
352
  //#endregion
348
- export { type ActionManifestEntry, type ActionMount, type AggregationManifestEntry, type AssertNoTenantDataOptions, type AssertNoTenantDataReport, type CascadeCheckpoint, type CascadeCheckpointState, type CascadeOptions, type CascadeReport, type CascadeResourceReport, type CustomRouteManifestEntry, type IntrospectionPluginOptions, type RegisterOptions, type ResourceManifest, ResourceRegistry, type TenantDataLeak, assertNoTenantData, buildResourceManifest, buildResourceManifestFromRegistry, cascadeDeleteForOrganization, getCascadingResources, getCascadingResourcesWithMetadata, _default as introspectionPlugin, introspectionPlugin as introspectionPluginFn };
353
+ export { type ActionManifestEntry, type ActionMount, type AggregationManifestEntry, type AssertNoTenantDataOptions, type AssertNoTenantDataReport, type CascadeCheckpoint, type CascadeCheckpointState, type CascadeOptions, type CascadeReport, type CascadeResourceReport, type CustomRouteManifestEntry, type IntrospectionPluginOptions, type RegisterOptions, type ResourceManifest, ResourceRegistry, type RouteRow, type TenantDataLeak, assertNoTenantData, buildResourceManifest, buildResourceManifestFromRegistry, cascadeDeleteForOrganization, getCascadingResources, getCascadingResourcesWithMetadata, _default as introspectionPlugin };
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { a as cascadeDeleteForOrganization, c as assertNoTenantData, i as introspectionPlugin_default, n as buildResourceManifestFromRegistry, o as getCascadingResources, r as introspectionPlugin, s as getCascadingResourcesWithMetadata, t as buildResourceManifest } from "../registry-D3y8VEJ1.mjs";
3
- import { t as ResourceRegistry } from "../ResourceRegistry-Cu7uTJp9.mjs";
4
- export { ResourceRegistry, assertNoTenantData, buildResourceManifest, buildResourceManifestFromRegistry, cascadeDeleteForOrganization, getCascadingResources, getCascadingResourcesWithMetadata, introspectionPlugin_default as introspectionPlugin, introspectionPlugin as introspectionPluginFn };
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { a as getCascadingResources, i as cascadeDeleteForOrganization, n as buildResourceManifestFromRegistry, o as getCascadingResourcesWithMetadata, r as introspectionPlugin_default, s as assertNoTenantData, t as buildResourceManifest } from "../registry-Bcz9G1bh.mjs";
3
+ import { t as ResourceRegistry } from "../ResourceRegistry-CuCARu2B.mjs";
4
+ export { ResourceRegistry, assertNoTenantData, buildResourceManifest, buildResourceManifestFromRegistry, cascadeDeleteForOrganization, getCascadingResources, getCascadingResourcesWithMetadata, introspectionPlugin_default as introspectionPlugin };
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { t as CRUD_OPERATIONS } from "./constants-BxLgNFF7.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { t as CRUD_OPERATIONS } from "./constants-Cu7Mx7_U.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/registry/assertNoTenantData.ts
5
5
  /**
@@ -442,8 +442,8 @@ var introspectionPlugin_default = fp(introspectionPlugin, { name: "arc-introspec
442
442
  //#region src/registry/manifest.ts
443
443
  /**
444
444
  * Resource metadata for frontend code generators — `{ name, prefix, idField,
445
- * crudOps, actions[] }`, where each action carries its `mount` and
446
- * `requiresId`.
445
+ * versionField?, crudOps, actions[] }`, where each action carries its `mount`
446
+ * and `requiresId`.
447
447
  *
448
448
  * A hand-rolled FE client needs a method per declarative action and drifts the
449
449
  * moment a new one is declared. Arc owns the resource definition, so it can
@@ -502,7 +502,8 @@ function buildResourceManifest(resource) {
502
502
  actions,
503
503
  aggregations,
504
504
  customRoutes,
505
- ...resource.tenantField !== void 0 ? { tenantField: resource.tenantField } : {}
505
+ ...resource.tenantField !== void 0 ? { tenantField: resource.tenantField } : {},
506
+ ...resource.versionField !== void 0 ? { versionField: resource.versionField } : {}
506
507
  };
507
508
  }
508
509
  /**
@@ -544,14 +545,15 @@ function buildResourceManifestFromRegistry(entry) {
544
545
  name: entry.name,
545
546
  displayName: entry.displayName ?? entry.name,
546
547
  prefix: entry.prefix,
547
- idField: "_id",
548
+ idField: entry.idField ?? "_id",
548
549
  updateMethod,
549
550
  crudOps,
550
551
  actions,
551
552
  aggregations,
552
553
  customRoutes,
553
- ...entry.tenantField !== void 0 ? { tenantField: entry.tenantField } : {}
554
+ ...entry.tenantField !== void 0 ? { tenantField: entry.tenantField } : {},
555
+ ...entry.versionField !== void 0 ? { versionField: entry.versionField } : {}
554
556
  };
555
557
  }
556
558
  //#endregion
557
- export { cascadeDeleteForOrganization as a, assertNoTenantData as c, introspectionPlugin_default as i, buildResourceManifestFromRegistry as n, getCascadingResources as o, introspectionPlugin as r, getCascadingResourcesWithMetadata as s, buildResourceManifest as t };
559
+ export { getCascadingResources as a, cascadeDeleteForOrganization as i, buildResourceManifestFromRegistry as n, getCascadingResourcesWithMetadata as o, introspectionPlugin_default as r, assertNoTenantData as s, buildResourceManifest as t };
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
2
  //#region src/lock/renewingLease.ts
3
3
  /**
4
4
  * Start renewing an ALREADY-ACQUIRED lease. The caller performed the
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-DjDuyglE.mjs";
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ import { t as __exportAll } from "./rolldown-runtime-D1w9Y1VJ.mjs";
3
3
  import { toCanonicalList } from "@classytic/repo-core/pagination";
4
4
  import fp from "fastify-plugin";
5
5
  //#region src/plugins/replyHelpers.ts
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.0 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  //#region src/context/requestContext.ts
4
4
  /**
@@ -0,0 +1,22 @@
1
+
2
+ import { FastifyRequest } from "fastify";
3
+ //#region src/idempotency/requirement.d.ts
4
+ /** The `config.idempotency` slot of a Fastify route. */
5
+ interface RouteIdempotencyConfig {
6
+ /** Every call needs a key — any method, DELETE included. */
7
+ readonly required?: boolean;
8
+ /** Action route only: a call whose `body.action` is one of these needs a key. */
9
+ readonly requiredActions?: readonly string[];
10
+ /**
11
+ * `'handler'`: the handler owns at-most-once for this route (its own durable claim). Arc still
12
+ * refuses a keyless call and exposes the key on `request.idempotencyKey`, but stores, locks,
13
+ * fingerprints and replays nothing. Needs `required: true` and an `owner`.
14
+ */
15
+ readonly dedup?: "handler";
16
+ /** Names the runner that owns deduplication under `dedup: 'handler'`. */
17
+ readonly owner?: string;
18
+ /** Action route only: action name -> owner, for actions in `requiredActions` that dedupe themselves. */
19
+ readonly handlerDedup?: Readonly<Record<string, string>>;
20
+ }
21
+ //#endregion
22
+ export { RouteIdempotencyConfig as t };
@@ -0,0 +1,35 @@
1
+ /*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
2
+ //#region src/idempotency/requirement.ts
3
+ /** What is wrong with a route's idempotency declaration, or `undefined` when it is sound. */
4
+ function idempotencyConfigProblem(config) {
5
+ const idem = config?.idempotency;
6
+ if (!idem) return void 0;
7
+ const ownerOk = typeof idem.owner === "string" && idem.owner.trim() !== "";
8
+ if (idem.dedup !== void 0 && idem.dedup !== "handler") return `dedup must be 'handler', got '${String(idem.dedup)}'`;
9
+ if (idem.dedup === "handler" && idem.required !== true) return "dedup: 'handler' needs required: true (the key is still demanded)";
10
+ if (idem.dedup === "handler" && !ownerOk) return "dedup: 'handler' needs a non-empty `owner` naming the runner that deduplicates";
11
+ if (idem.dedup === void 0 && idem.owner !== void 0) return "`owner` is only meaningful with dedup: 'handler'";
12
+ for (const [action, owner] of Object.entries(idem.handlerDedup ?? {})) if (!idem.requiredActions?.includes(action) || owner.trim() === "") return `handlerDedup '${action}' must name an action in requiredActions and a non-empty owner`;
13
+ }
14
+ /** True iff the route declares a fence for at least one call shape. */
15
+ function declaresIdempotencyFence(config) {
16
+ const idem = config?.idempotency;
17
+ return idem?.required === true || (idem?.requiredActions?.length ?? 0) > 0;
18
+ }
19
+ /** True iff THIS request's handler deduplicates itself (arc only demands and exposes the key). */
20
+ function requestDedupedByHandler(request) {
21
+ const idem = request.routeOptions.config?.idempotency;
22
+ if (idem?.required === true) return idem.dedup === "handler";
23
+ const action = request.body?.action;
24
+ return typeof action === "string" && idem?.handlerDedup?.[action] !== void 0;
25
+ }
26
+ /** True iff THIS request is fenced — a keyless call is refused, a keyed one deduplicated. */
27
+ function requestRequiresIdempotencyKey(request) {
28
+ const idem = request.routeOptions.config?.idempotency;
29
+ if (idem?.required === true) return true;
30
+ if (!idem?.requiredActions?.length) return false;
31
+ const action = request.body?.action;
32
+ return typeof action === "string" && idem.requiredActions.includes(action);
33
+ }
34
+ //#endregion
35
+ export { requestRequiresIdempotencyKey as i, idempotencyConfigProblem as n, requestDedupedByHandler as r, declaresIdempotencyFence as t };
@@ -0,0 +1,30 @@
1
+
2
+ import { i as RequestScope } from "./types-Ddl9OjmE.mjs";
3
+ import { FastifyInstance } from "fastify";
4
+ //#region src/permissions/resourceRateLimit.d.ts
5
+ interface ResourceRateLimitPolicy {
6
+ max: number;
7
+ timeWindow: string;
8
+ /** Explicit route overrides own a separate budget. */
9
+ bucket?: string;
10
+ }
11
+ interface ResourceRateLimitContext {
12
+ scope?: RequestScope;
13
+ ip?: string;
14
+ }
15
+ interface ResourceRateLimitDecision {
16
+ readonly allowed: boolean;
17
+ readonly retryAfterSeconds: number;
18
+ readonly headers: Readonly<Record<string, number>>;
19
+ }
20
+ /** One resource budget across operations and transports, backed by the host's limiter. */
21
+ interface ResourceRateLimiter {
22
+ readonly durability: "memory" | "shared";
23
+ consume(resource: string, policy: ResourceRateLimitPolicy, context: ResourceRateLimitContext): Promise<ResourceRateLimitDecision>;
24
+ }
25
+ /** Uses createRateLimit's store factory, preserving Redis/custom-store configuration. */
26
+ declare function resolveResourceRateLimiter(app: FastifyInstance, durability?: ResourceRateLimiter["durability"]): ResourceRateLimiter | undefined;
27
+ /** Standalone tools may use memory; wired distributed apps use the host's shared store. */
28
+ declare function createMemoryResourceRateLimiter(capacity?: number): ResourceRateLimiter;
29
+ //#endregion
30
+ export { createMemoryResourceRateLimiter as a, ResourceRateLimiter as i, ResourceRateLimitDecision as n, resolveResourceRateLimiter as o, ResourceRateLimitPolicy as r, ResourceRateLimitContext as t };