@classytic/arc 2.41.1 → 2.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (248) hide show
  1. package/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
  2. package/dist/{EventTransport-DSqD_Xsg.mjs → EventTransport-DZWDsaPa.mjs} +22 -17
  3. package/dist/{HookSystem-CMF94SEA.mjs → HookSystem-yjEgn7Cm.mjs} +2 -2
  4. package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
  5. package/dist/{ResourceRegistry-MGpXoxHZ.mjs → ResourceRegistry-DJxHUpE4.mjs} +44 -13
  6. package/dist/acquire-BozJ-9yt.mjs +35 -0
  7. package/dist/actionPermissions-5N04zBL4.mjs +772 -0
  8. package/dist/audit/index.d.mts +46 -8
  9. package/dist/audit/index.mjs +48 -26
  10. package/dist/auth/audit.d.mts +8 -8
  11. package/dist/auth/audit.mjs +1 -1
  12. package/dist/auth/index.d.mts +46 -16
  13. package/dist/auth/index.mjs +134 -156
  14. package/dist/auth/redis-session.d.mts +4 -5
  15. package/dist/auth/redis-session.mjs +1 -1
  16. package/dist/{authorizationDecision-DawKGoXb.mjs → authorizationDecision-wX3SA9w_.mjs} +7 -102
  17. package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
  18. package/dist/boundedMap-DWiKW7lP.mjs +57 -0
  19. package/dist/{buildHandler-CvxKOc0m.mjs → buildHandler-Bswu_vNb.mjs} +4 -4
  20. package/dist/cache/index.d.mts +31 -43
  21. package/dist/cache/index.mjs +4 -4
  22. package/dist/{caching-DbCBxlgw.mjs → caching-Drp68DIS.mjs} +3 -3
  23. package/dist/claimed-DYvQoZLv.mjs +8 -0
  24. package/dist/cleanup/index.d.mts +18 -22
  25. package/dist/cleanup/index.mjs +27 -73
  26. package/dist/cli/commands/describe.d.mts +7 -5
  27. package/dist/cli/commands/describe.mjs +7 -4
  28. package/dist/cli/commands/docs.d.mts +2 -3
  29. package/dist/cli/commands/docs.mjs +3 -3
  30. package/dist/cli/commands/doctor.d.mts +2 -3
  31. package/dist/cli/commands/doctor.mjs +1 -1
  32. package/dist/cli/commands/generate.mjs +2 -3
  33. package/dist/cli/commands/init.mjs +2 -3
  34. package/dist/cli/commands/introspect.d.mts +2 -3
  35. package/dist/cli/commands/introspect.mjs +2 -2
  36. package/dist/cli/index.mjs +3 -3
  37. package/dist/{constants-D5Ubn1fe.mjs → constants-B2QHb6u6.mjs} +4 -2
  38. package/dist/context/index.d.mts +11 -14
  39. package/dist/context/index.mjs +18 -38
  40. package/dist/core/index.d.mts +3 -3
  41. package/dist/core/index.mjs +8 -8
  42. package/dist/{core-5lazhva9.mjs → core-COcqo0Cp.mjs} +15 -5
  43. package/dist/{createActionRouter-DNqoaK81.mjs → createActionRouter-zG13rpPJ.mjs} +97 -39
  44. package/dist/{createAggregationRouter-BcbQJf10.mjs → createAggregationRouter-CWRz96Vr.mjs} +11 -8
  45. package/dist/{preloadResources-f3boenVZ.mjs → createApp-BGLYtErt.mjs} +305 -394
  46. package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-DSztofa9.d.mts} +1 -1
  47. package/dist/{defineAggregation-D_eJjm5A.mjs → defineAggregation--3xguLkk.mjs} +1 -1
  48. package/dist/{defineResource-DxkMTjRn.mjs → defineResource-gUp9ug66.mjs} +118 -1024
  49. package/dist/discovery/index.d.mts +6 -7
  50. package/dist/discovery/index.mjs +1 -1
  51. package/dist/docs/index.d.mts +11 -5
  52. package/dist/docs/index.mjs +4 -4
  53. package/dist/{elevation-CkvG0LG2.mjs → elevation-BG4gnMsJ.mjs} +41 -12
  54. package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
  55. package/dist/encryption/index.d.mts +5 -5
  56. package/dist/encryption/index.mjs +3 -3
  57. package/dist/{errorHandler-D1GHKGlV.mjs → errorHandler-COHw3N1b.mjs} +26 -5
  58. package/dist/{errors-BH7YU_9O.mjs → errors-CW4anJBy.mjs} +58 -23
  59. package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
  60. package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
  61. package/dist/{eventPlugin-zZY-unhr.mjs → eventPlugin-BweHAVws.mjs} +82 -21
  62. package/dist/{subscribe-helpers-DatDkcuG.mjs → eventTypes-N4-omQFR.mjs} +151 -17
  63. package/dist/events/index.d.mts +43 -18
  64. package/dist/events/index.mjs +49 -53
  65. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  66. package/dist/events/transports/redis-stream-entry.mjs +139 -157
  67. package/dist/events/transports/redis.d.mts +5 -6
  68. package/dist/events/transports/redis.mjs +2 -2
  69. package/dist/factory/index.d.mts +42 -27
  70. package/dist/factory/index.mjs +213 -32
  71. package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DRlCIWE9.d.mts} +11 -25
  72. package/dist/{fields-BHrkoF5y.mjs → fields-A5crhsJb.mjs} +1 -1
  73. package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
  74. package/dist/filter-merge-DGM-GQ1J.mjs +99 -0
  75. package/dist/{generate-BLRf2m3y.mjs → generate-BXKo2Gc8.mjs} +2 -2
  76. package/dist/{headers-BIXWKrzQ.mjs → headers-DKZQKL-Z.mjs} +2 -2
  77. package/dist/health-BYAwA6YS.mjs +22 -0
  78. package/dist/{health-ZRHeDHXH.mjs → health-CrqUp3sz.mjs} +115 -55
  79. package/dist/hooks/index.d.mts +2 -2
  80. package/dist/hooks/index.mjs +2 -3
  81. package/dist/idempotency/index.d.mts +36 -10
  82. package/dist/idempotency/index.mjs +230 -111
  83. package/dist/idempotency/redis.d.mts +1 -1
  84. package/dist/idempotency/redis.mjs +77 -12
  85. package/dist/{index-D4u76eQW.d.mts → index-Beg3aNnK.d.mts} +23 -101
  86. package/dist/{preloadResources-CjS2OfpU.d.mts → index-BkV55WlV.d.mts} +37 -38
  87. package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
  88. package/dist/index-CUJvoyw3.d.mts +510 -0
  89. package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
  90. package/dist/{index-DHbyPo5j.d.mts → index-WH7xgvxS.d.mts} +304 -254
  91. package/dist/index.d.mts +9 -9
  92. package/dist/index.mjs +11 -11
  93. package/dist/{init-CpLkjU18.mjs → init-BPzK5Lnr.mjs} +1 -1
  94. package/dist/integrations/event-gateway.d.mts +11 -7
  95. package/dist/integrations/event-gateway.mjs +4 -4
  96. package/dist/integrations/index.d.mts +3 -3
  97. package/dist/integrations/index.mjs +1 -1
  98. package/dist/integrations/jobs/index.d.mts +1 -1
  99. package/dist/integrations/jobs/index.mjs +18 -5
  100. package/dist/integrations/mcp/index.d.mts +102 -89
  101. package/dist/integrations/mcp/index.mjs +19 -4
  102. package/dist/integrations/mcp/testing.d.mts +8 -9
  103. package/dist/integrations/mcp/testing.mjs +2 -2
  104. package/dist/integrations/streamline.d.mts +85 -144
  105. package/dist/integrations/streamline.mjs +131 -55
  106. package/dist/integrations/webhooks.d.mts +83 -143
  107. package/dist/integrations/webhooks.mjs +36 -80
  108. package/dist/integrations/websocket-pushref-redis.d.mts +5 -6
  109. package/dist/integrations/websocket-pushref-redis.mjs +1 -1
  110. package/dist/integrations/websocket-redis.d.mts +5 -6
  111. package/dist/integrations/websocket-redis.mjs +1 -1
  112. package/dist/integrations/websocket.d.mts +3 -3
  113. package/dist/integrations/websocket.mjs +2 -2
  114. package/dist/{interface-DEWNOMza.d.mts → interface-JJzwH4Ro.d.mts} +34 -2
  115. package/dist/{jsonBody-ELR9lfeU.mjs → jsonBody-YIq-vGLv.mjs} +2 -2
  116. package/dist/{keys-cpZCtcIE.mjs → keys-CpoiZ8hU.mjs} +7 -18
  117. package/dist/{loadResources-BPfWiI5s.mjs → loadResources-6jO52-RR.mjs} +3 -3
  118. package/dist/{loadResourcesFromEntry-ixtfL3J2.mjs → loadResourcesFromEntry-MP8xEppu.mjs} +2 -2
  119. package/dist/logger/index.d.mts +9 -17
  120. package/dist/logger/index.mjs +2 -153
  121. package/dist/logger-D4V1zGLI.mjs +160 -0
  122. package/dist/{mcpPlugin-BX5PujI0.mjs → mcpPlugin-srPc7ItN.mjs} +446 -170
  123. package/dist/{memory-Iotz0cCx.mjs → memory-CTa5pOCW.mjs} +36 -34
  124. package/dist/{metrics-NMsmpRmY.mjs → metrics-DsZrvnpB.mjs} +39 -72
  125. package/dist/middleware/index.d.mts +5 -5
  126. package/dist/middleware/index.mjs +3 -3
  127. package/dist/migrations/index.d.mts +13 -13
  128. package/dist/migrations/index.mjs +2 -2
  129. package/dist/{module-DpLJG2_N.mjs → module--L0iBIZz.mjs} +8 -8
  130. package/dist/mongodb-memory-server-CHhYSq5y.mjs +35539 -0
  131. package/dist/mongoose-CYJCLyi-.mjs +67451 -0
  132. package/dist/multiTenant-CAtYlW6H.d.mts +104 -0
  133. package/dist/{multipartBody-DtRWM6_U.mjs → multipartBody-Cc7eZ9cq.mjs} +46 -7
  134. package/dist/node-D8UZv_8c.mjs +41236 -0
  135. package/dist/{openapi-CmFf3yq_.mjs → openapi-CMegfsm8.mjs} +48 -10
  136. package/dist/outbox-admin/index.d.mts +5 -5
  137. package/dist/outbox-admin/index.mjs +6 -18
  138. package/dist/{outbox-module-D2z5WkDY.mjs → outbox-module-Ryk270jd.mjs} +203 -33
  139. package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
  140. package/dist/permissions/index.d.mts +5 -4
  141. package/dist/permissions/index.mjs +8 -6
  142. package/dist/{permissions-D8TBH44E.mjs → permissions-C5ijIz5Q.mjs} +149 -33
  143. package/dist/{pipe-FMWNheSO.mjs → pipe-D81zMtWs.mjs} +2 -2
  144. package/dist/pipeline/index.d.mts +7 -7
  145. package/dist/pipeline/index.mjs +2 -2
  146. package/dist/plan-CJGMKoui.mjs +565 -0
  147. package/dist/{BaseController-CYEwjyjN.mjs → plugin-DALd9dcF.mjs} +1269 -346
  148. package/dist/plugins/index.d.mts +17 -14
  149. package/dist/plugins/index.mjs +44 -22
  150. package/dist/plugins/response-cache.d.mts +5 -6
  151. package/dist/plugins/response-cache.mjs +8 -14
  152. package/dist/plugins/tracing-entry.mjs +2 -2
  153. package/dist/{pluralize-CXWZIzTv.mjs → pluralize-COUNYo3d.mjs} +1 -1
  154. package/dist/presets/filesUpload.d.mts +8 -8
  155. package/dist/presets/filesUpload.mjs +5 -5
  156. package/dist/presets/index.d.mts +35 -40
  157. package/dist/presets/index.mjs +4 -4
  158. package/dist/presets/multiTenant.d.mts +2 -107
  159. package/dist/presets/multiTenant.mjs +22 -162
  160. package/dist/presets/search.d.mts +9 -10
  161. package/dist/presets/search.mjs +2 -2
  162. package/dist/{presets-ZJSPgmT2.mjs → presets-CONf2Qem.mjs} +99 -107
  163. package/dist/{projection-BzIgzoKZ.mjs → projection-wFoLqdss.mjs} +2 -2
  164. package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
  165. package/dist/{queryCachePlugin-DZHPOm9p.mjs → queryCachePlugin-BATECpyh.mjs} +12 -14
  166. package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
  167. package/dist/{rateLimitKey-BvOvcBDi.mjs → rateLimitKey-DVEZ_UpV.mjs} +1 -1
  168. package/dist/{realtime-8tIQWZz4.mjs → realtime-xyubMKSB.mjs} +25 -21
  169. package/dist/{redis-Bzi1y3F9.d.mts → redis-BDgv9Glw.d.mts} +17 -24
  170. package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
  171. package/dist/redisShapes-CQksY67q.d.mts +55 -0
  172. package/dist/registry/index.d.mts +15 -10
  173. package/dist/registry/index.mjs +4 -4
  174. package/dist/{registry-DNkgGQcK.mjs → registry-Bdzg-hdm.mjs} +13 -10
  175. package/dist/{renewingLease-yF8BdW4b.mjs → renewingLease-BFJxGPuh.mjs} +1 -1
  176. package/dist/{replyHelpers-CNdvMfM4.mjs → replyHelpers-Bnc6lJyy.mjs} +2 -2
  177. package/dist/{requestContext-cDkAUr-P.mjs → requestContext-C10UeFe5.mjs} +1 -1
  178. package/dist/requirement-Bu50QALH.mjs +35 -0
  179. package/dist/requirement-CbiLdQ1n.d.mts +22 -0
  180. package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
  181. package/dist/resourceRateLimit-urWPJxrt.mjs +180 -0
  182. package/dist/rolldown-runtime-MYrLNGnq.mjs +45 -0
  183. package/dist/routeValidator-DHtpzrzk.mjs +70 -0
  184. package/dist/{runtimeCapabilities-CkhS6jn6.mjs → runtimeCapabilities-DIMKMV2G.mjs} +1 -1
  185. package/dist/{schedules-j6uXXAeJ.mjs → schedules-CKTs5ys8.mjs} +46 -37
  186. package/dist/{schemaConverter-BVA7cFRG.mjs → schemaConverter-BVjkQ5DD.mjs} +2 -2
  187. package/dist/{schemaIR-CTvPDoPB.mjs → schemaIR-BA0Hb21Y.mjs} +2 -2
  188. package/dist/schemas/index.d.mts +20 -10
  189. package/dist/schemas/index.mjs +8 -3
  190. package/dist/scim/index.d.mts +13 -13
  191. package/dist/scim/index.mjs +2 -2
  192. package/dist/scope/index.d.mts +4 -4
  193. package/dist/scope/index.mjs +6 -6
  194. package/dist/{scope-BUi1xV1I.mjs → scope-DINaj7Ax.mjs} +4 -4
  195. package/dist/shutdown-LpLMH8T1.mjs +59 -0
  196. package/dist/{simpleEqualityMatcher-C9bzq2gO.mjs → simpleEqualityMatcher-DbwIM1f1.mjs} +1 -1
  197. package/dist/{sse-BeqVKQKu.mjs → sse-BzJFPX_1.mjs} +11 -15
  198. package/dist/{sseStream-Ppzo-WUH.mjs → sseStream-DQChzGTs.mjs} +2 -2
  199. package/dist/{store-helpers-dW71XVWL.mjs → store-helpers-Coj9RhOZ.mjs} +1 -1
  200. package/dist/{streaming-IcRFjWQU.mjs → streaming-JzB5CAga.mjs} +1 -1
  201. package/dist/subscriberIdentity-DeyppIif.mjs +28 -0
  202. package/dist/tenancy-DxV8OuX2.mjs +43 -0
  203. package/dist/tenantPreparation-CBvvAcAj.mjs +67 -0
  204. package/dist/testing/cleanupStoreContract.d.mts +4 -5
  205. package/dist/testing/cleanupStoreContract.mjs +1 -1
  206. package/dist/testing/index.d.mts +215 -197
  207. package/dist/testing/index.mjs +289 -214
  208. package/dist/testing/outboxStoreContract.d.mts +4 -5
  209. package/dist/testing/outboxStoreContract.mjs +137 -2
  210. package/dist/testing/requiredPorts.d.mts +16 -0
  211. package/dist/testing/requiredPorts.mjs +65 -0
  212. package/dist/testing/storageContract.d.mts +4 -5
  213. package/dist/testing/storageContract.mjs +1 -1
  214. package/dist/timing-CntavTlR.mjs +46 -0
  215. package/dist/{transactionContext-CPbdlnVw.mjs → transactionContext-orvnGu4F.mjs} +1 -1
  216. package/dist/{typeGuards-BLjPgVBm.mjs → typeGuards-kEaP_WmO.mjs} +1 -1
  217. package/dist/types/index.d.mts +4 -4
  218. package/dist/types/index.mjs +1 -1
  219. package/dist/types/storage.mjs +1 -1
  220. package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
  221. package/dist/{types-CWQn9-IC.d.mts → types-D3Ixsh00.d.mts} +22 -9
  222. package/dist/{types-CgenB1gv.mjs → types-fvO6Ovzx.mjs} +4 -4
  223. package/dist/usage/index.d.mts +5 -6
  224. package/dist/usage/index.mjs +5 -5
  225. package/dist/{userHelpers-ChRvkuSZ.mjs → userHelpers-C76zD5pa.mjs} +1 -1
  226. package/dist/utils/index.d.mts +125 -264
  227. package/dist/utils/index.mjs +13 -12
  228. package/dist/{utils-CxcctrAh.mjs → utils-BpkVZ8rB.mjs} +315 -404
  229. package/dist/{validate-DrYWnOzI.mjs → validate-CWp-lsrm.mjs} +6 -14
  230. package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
  231. package/dist/{versioning-DKkT4hNJ.mjs → versioning-CHhL9P3o.mjs} +4 -4
  232. package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
  233. package/dist/{workScope-CxKqoIFn.mjs → workScope-n9a7d_lZ.mjs} +2 -2
  234. package/package.json +57 -59
  235. package/skills/arc/SKILL.md +13 -4
  236. package/skills/arc/references/auth.md +8 -5
  237. package/skills/arc/references/integrations.md +1 -1
  238. package/skills/arc/references/mcp.md +8 -6
  239. package/skills/arc/references/multi-tenancy.md +19 -14
  240. package/skills/arc/references/production.md +27 -10
  241. package/dist/actionPermissions-BoPGQ5-4.mjs +0 -350
  242. package/dist/index-CFfLKMH4.d.mts +0 -290
  243. package/dist/interface-YzIbF7Ii.mjs +0 -17
  244. package/dist/redis-stream-BPKscsTB.d.mts +0 -260
  245. package/dist/registerRoute-Bsnk6NiZ.mjs +0 -364
  246. package/dist/rolldown-runtime-eysyzFAF.mjs +0 -15
  247. package/dist/sync/changelog.d.mts +0 -3
  248. package/dist/sync/changelog.mjs +0 -3
@@ -1,5 +1,6 @@
1
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 { i as IdempotencyStore, r as IdempotencyResult, t as IdempotencyAcquisition } from "./interface-JJzwH4Ro.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,9 @@ interface RedisIdempotencyStoreOptions {
26
27
  }
27
28
  declare class RedisIdempotencyStore implements IdempotencyStore {
28
29
  readonly name = "redis";
30
+ readonly supportsFencedCompletion: boolean;
31
+ /** The fenced completion script deletes the lock; it needs `eval`, as fencing does. */
32
+ readonly completionReleasesLock: boolean;
29
33
  private client;
30
34
  private prefix;
31
35
  private lockPrefix;
@@ -34,8 +38,11 @@ declare class RedisIdempotencyStore implements IdempotencyStore {
34
38
  private resultKey;
35
39
  private lockKey;
36
40
  get(key: string): Promise<IdempotencyResult | undefined>;
41
+ private parseResult;
37
42
  set(key: string, result: Omit<IdempotencyResult, "key">): Promise<void>;
38
43
  tryLock(key: string, requestId: string, ttlMs: number): Promise<boolean>;
44
+ acquire(key: string, requestId: string, lockTtlMs: number): Promise<IdempotencyAcquisition>;
45
+ setIfLocked(key: string, requestId: string, result: Omit<IdempotencyResult, "key">): Promise<boolean>;
39
46
  unlock(key: string, requestId: string): Promise<void>;
40
47
  isLocked(key: string): Promise<boolean>;
41
48
  delete(key: string): Promise<void>;
@@ -46,21 +53,12 @@ declare class RedisIdempotencyStore implements IdempotencyStore {
46
53
  close(): Promise<void>;
47
54
  }
48
55
  /**
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`.
56
+ * Minimal ioredis shape the idempotency store depends on — the shared
57
+ * key/value commands (`utils/redisShapes.ts`, where the `unknown[]`-tail rule
58
+ * is explained) plus `exists` and lifecycle. Keeps this file peer-dep-free.
56
59
  */
57
- interface IoredisLike {
58
- get(key: string): Promise<string | null>;
59
- set(...args: unknown[]): Promise<string | null>;
60
- del(...keys: string[]): Promise<number>;
60
+ interface IoredisLike extends IoredisKeyValueCommands {
61
61
  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
62
  quit?(): Promise<string>;
65
63
  disconnect?(): void;
66
64
  }
@@ -86,17 +84,12 @@ interface IoredisLike {
86
84
  declare function ioredisAsIdempotencyClient(client: IoredisLike): RedisClient & {
87
85
  eval?: (script: string, numkeys: number, ...args: (string | number)[]) => Promise<unknown>;
88
86
  };
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>;
87
+ /**
88
+ * Minimal `@upstash/redis` shape the idempotency store depends on (REST,
89
+ * edge-safe) — the shared key/value commands plus `exists`.
90
+ */
91
+ interface UpstashRedisLike extends UpstashKeyValueCommands {
94
92
  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
93
  }
101
94
  /**
102
95
  * Wrap an `@upstash/redis` REST client as an idempotency `RedisClient`.
@@ -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 };
@@ -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-WH7xgvxS.mjs";
3
+ import "../fastify-augmentation-DRlCIWE9.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
@@ -60,7 +60,7 @@ interface AssertNoTenantDataReport {
60
60
  * });
61
61
  * ```
62
62
  */
63
- declare function assertNoTenantData(registry: ResourceRegistry, options: AssertNoTenantDataOptions): Promise<AssertNoTenantDataReport>;
63
+ export declare function assertNoTenantData(registry: ResourceRegistry, options: AssertNoTenantDataOptions): Promise<AssertNoTenantDataReport>;
64
64
  //#endregion
65
65
  //#region src/registry/purgeResource.d.ts
66
66
  /**
@@ -232,7 +232,7 @@ interface CascadeCheckpoint {
232
232
  * A resource is cascading when its resolved strategy isn't `disabled`
233
233
  * (i.e. the host declared `onTenantDelete`).
234
234
  */
235
- declare function getCascadingResources(registry: ResourceRegistry): readonly string[];
235
+ export declare function getCascadingResources(registry: ResourceRegistry): readonly string[];
236
236
  /**
237
237
  * Rich introspection — returns the resolved strategy + source per
238
238
  * cascading resource. Use for audit dashboards that answer "what
@@ -245,7 +245,7 @@ declare function getCascadingResources(registry: ResourceRegistry): readonly str
245
245
  * purge this" entry. Anonymize carries the field map. Custom carries
246
246
  * the handler reference. Narrow on `.strategy.type` for typed access.
247
247
  */
248
- declare function getCascadingResourcesWithMetadata(registry: ResourceRegistry): readonly {
248
+ export declare function getCascadingResourcesWithMetadata(registry: ResourceRegistry): readonly {
249
249
  name: string;
250
250
  tenantField: string;
251
251
  strategy: TenantPurgeStrategy;
@@ -274,10 +274,9 @@ declare function getCascadingResourcesWithMetadata(registry: ResourceRegistry):
274
274
  * @param registry The arc resource registry (`fastify.arc.registry`).
275
275
  * @param options Org id + filters + logger + progress + signal.
276
276
  */
277
- declare function cascadeDeleteForOrganization(registry: ResourceRegistry, options: CascadeOptions): Promise<CascadeReport>;
277
+ export 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,13 +325,19 @@ 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
332
337
  * `defineResource(...)`). Use at build time when you have direct access
333
338
  * to the resource module — typically the simplest codegen path.
334
339
  */
335
- declare function buildResourceManifest(resource: ResourceDefinition): ResourceManifest;
340
+ export declare function buildResourceManifest(resource: ResourceDefinition): ResourceManifest;
336
341
  /**
337
342
  * Build a manifest from a `RegistryEntry` — use when the resource is
338
343
  * already registered (introspection endpoint, runtime audit script).
@@ -343,6 +348,6 @@ declare function buildResourceManifest(resource: ResourceDefinition): ResourceMa
343
348
  * `/_resources` endpoint) gets the same output without dragging the resource
344
349
  * module into the codegen path.
345
350
  */
346
- declare function buildResourceManifestFromRegistry(entry: RegistryEntry): ResourceManifest;
351
+ export 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, _default as introspectionPlugin };
@@ -1,4 +1,4 @@
1
- /*! @classytic/arc v2.41.1 | 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-DNkgGQcK.mjs";
3
- import { t as ResourceRegistry } from "../ResourceRegistry-MGpXoxHZ.mjs";
4
- export { ResourceRegistry, assertNoTenantData, buildResourceManifest, buildResourceManifestFromRegistry, cascadeDeleteForOrganization, getCascadingResources, getCascadingResourcesWithMetadata, introspectionPlugin_default as introspectionPlugin, introspectionPlugin as introspectionPluginFn };
1
+ /*! @classytic/arc v2.44.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-Bdzg-hdm.mjs";
3
+ import { t as ResourceRegistry } from "../ResourceRegistry-DJxHUpE4.mjs";
4
+ export { ResourceRegistry, assertNoTenantData, buildResourceManifest, buildResourceManifestFromRegistry, cascadeDeleteForOrganization, getCascadingResources, getCascadingResourcesWithMetadata, introspectionPlugin_default as introspectionPlugin };
@@ -1,5 +1,5 @@
1
- /*! @classytic/arc v2.41.1 | MIT | https://classytic.com */
2
- import { t as CRUD_OPERATIONS } from "./constants-D5Ubn1fe.mjs";
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
+ import { t as CRUD_OPERATIONS } from "./constants-B2QHb6u6.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/registry/assertNoTenantData.ts
5
5
  /**
@@ -405,7 +405,7 @@ const introspectionPlugin = async (fastify, opts = {}) => {
405
405
  const typedFastify = fastify;
406
406
  const authMiddleware = authRoles.length > 0 && typedFastify.authenticate ? [typedFastify.authenticate, typedFastify.authorize?.(...authRoles)].filter(Boolean) : [];
407
407
  const getRegistry = () => fastify.arc?.registry;
408
- await fastify.register(async (instance) => {
408
+ const introspectionRoutes = async (instance) => {
409
409
  instance.get("/", { preHandler: authMiddleware }, async (_req, _reply) => {
410
410
  return getRegistry()?.getIntrospection() ?? {
411
411
  resources: [],
@@ -434,7 +434,8 @@ const introspectionPlugin = async (fastify, opts = {}) => {
434
434
  if (!resource) return reply.code(404).send({ error: `Resource '${req.params.name}' not found` });
435
435
  return resource;
436
436
  });
437
- }, { prefix });
437
+ };
438
+ await fastify.register(introspectionRoutes, { prefix });
438
439
  fastify.log?.debug?.(`Introspection API at ${prefix}`);
439
440
  };
440
441
  var introspectionPlugin_default = fp(introspectionPlugin, { name: "arc-introspection" });
@@ -442,8 +443,8 @@ var introspectionPlugin_default = fp(introspectionPlugin, { name: "arc-introspec
442
443
  //#region src/registry/manifest.ts
443
444
  /**
444
445
  * Resource metadata for frontend code generators — `{ name, prefix, idField,
445
- * crudOps, actions[] }`, where each action carries its `mount` and
446
- * `requiresId`.
446
+ * versionField?, crudOps, actions[] }`, where each action carries its `mount`
447
+ * and `requiresId`.
447
448
  *
448
449
  * A hand-rolled FE client needs a method per declarative action and drifts the
449
450
  * moment a new one is declared. Arc owns the resource definition, so it can
@@ -502,7 +503,8 @@ function buildResourceManifest(resource) {
502
503
  actions,
503
504
  aggregations,
504
505
  customRoutes,
505
- ...resource.tenantField !== void 0 ? { tenantField: resource.tenantField } : {}
506
+ ...resource.tenantField !== void 0 ? { tenantField: resource.tenantField } : {},
507
+ ...resource.versionField !== void 0 ? { versionField: resource.versionField } : {}
506
508
  };
507
509
  }
508
510
  /**
@@ -544,14 +546,15 @@ function buildResourceManifestFromRegistry(entry) {
544
546
  name: entry.name,
545
547
  displayName: entry.displayName ?? entry.name,
546
548
  prefix: entry.prefix,
547
- idField: "_id",
549
+ idField: entry.idField ?? "_id",
548
550
  updateMethod,
549
551
  crudOps,
550
552
  actions,
551
553
  aggregations,
552
554
  customRoutes,
553
- ...entry.tenantField !== void 0 ? { tenantField: entry.tenantField } : {}
555
+ ...entry.tenantField !== void 0 ? { tenantField: entry.tenantField } : {},
556
+ ...entry.versionField !== void 0 ? { versionField: entry.versionField } : {}
554
557
  };
555
558
  }
556
559
  //#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 };
560
+ 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.1 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.44.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.1 | MIT | https://classytic.com */
2
- import { t as __exportAll } from "./rolldown-runtime-eysyzFAF.mjs";
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
+ import { r as __exportAll } from "./rolldown-runtime-MYrLNGnq.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.1 | MIT | https://classytic.com */
1
+ /*! @classytic/arc v2.44.0 | MIT | https://classytic.com */
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  //#region src/context/requestContext.ts
4
4
  /**
@@ -0,0 +1,35 @@
1
+ /*! @classytic/arc v2.44.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,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,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 };