@graphrefly/ts 0.0.1 → 0.0.3

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 (333) hide show
  1. package/README.md +197 -117
  2. package/dist/actions-types-CH-XCU9O.d.cts +657 -0
  3. package/dist/actions-types-w2iPQTkv.d.ts +657 -0
  4. package/dist/adapters/index.cjs +5157 -2951
  5. package/dist/adapters/index.cjs.map +1 -1
  6. package/dist/adapters/index.d.cts +207 -70
  7. package/dist/adapters/index.d.ts +207 -70
  8. package/dist/adapters/index.js +3738 -1688
  9. package/dist/adapters/index.js.map +1 -1
  10. package/dist/adapters/nestjs/microservices.cjs +841 -0
  11. package/dist/adapters/nestjs/microservices.cjs.map +1 -0
  12. package/dist/adapters/nestjs/microservices.d.cts +45 -0
  13. package/dist/adapters/nestjs/microservices.d.ts +45 -0
  14. package/dist/adapters/nestjs/microservices.js +206 -0
  15. package/dist/adapters/nestjs/microservices.js.map +1 -0
  16. package/dist/adapters/nestjs/native.cjs +1391 -0
  17. package/dist/adapters/nestjs/native.cjs.map +1 -0
  18. package/dist/adapters/nestjs/native.d.cts +111 -0
  19. package/dist/adapters/nestjs/native.d.ts +111 -0
  20. package/dist/adapters/nestjs/native.js +577 -0
  21. package/dist/adapters/nestjs/native.js.map +1 -0
  22. package/dist/adapters/nestjs/websockets.cjs +924 -0
  23. package/dist/adapters/nestjs/websockets.cjs.map +1 -0
  24. package/dist/adapters/nestjs/websockets.d.cts +47 -0
  25. package/dist/adapters/nestjs/websockets.d.ts +47 -0
  26. package/dist/adapters/nestjs/websockets.js +286 -0
  27. package/dist/adapters/nestjs/websockets.js.map +1 -0
  28. package/dist/adapters/nestjs.cjs +1085 -0
  29. package/dist/adapters/nestjs.cjs.map +1 -0
  30. package/dist/adapters/nestjs.d.cts +385 -0
  31. package/dist/adapters/nestjs.d.ts +385 -0
  32. package/dist/adapters/nestjs.js +112 -0
  33. package/dist/adapters/nestjs.js.map +1 -0
  34. package/dist/adapters/observe-storage.d.cts +3 -3
  35. package/dist/adapters/observe-storage.d.ts +3 -3
  36. package/dist/adapters/observe-storage.js +4 -4
  37. package/dist/adapters/react.cjs.map +1 -1
  38. package/dist/adapters/react.d.cts +2 -2
  39. package/dist/adapters/react.d.ts +2 -2
  40. package/dist/adapters/react.js +1 -1
  41. package/dist/adapters/solid.cjs.map +1 -1
  42. package/dist/adapters/solid.d.cts +2 -2
  43. package/dist/adapters/solid.d.ts +2 -2
  44. package/dist/adapters/solid.js +1 -1
  45. package/dist/adapters/svelte.cjs.map +1 -1
  46. package/dist/adapters/svelte.d.cts +2 -2
  47. package/dist/adapters/svelte.d.ts +2 -2
  48. package/dist/adapters/svelte.js +1 -1
  49. package/dist/adapters/vue.cjs.map +1 -1
  50. package/dist/adapters/vue.d.cts +2 -2
  51. package/dist/adapters/vue.d.ts +2 -2
  52. package/dist/adapters/vue.js +1 -1
  53. package/dist/{agent-runtime-BesGDZcB.d.ts → agent-runtime-types-core-CKwrBDyi.d.ts} +171 -320
  54. package/dist/{agent-runtime-DZTJ3C-p.d.cts → agent-runtime-types-core-bfl4m0vM.d.cts} +171 -320
  55. package/dist/agent-runtime-types-tool-BLPV3azj.d.cts +695 -0
  56. package/dist/agent-runtime-types-tool-hwqvVqNt.d.ts +695 -0
  57. package/dist/agentic-memory-retention-CLwI76JK.d.ts +14 -0
  58. package/dist/agentic-memory-retention-Cz8GR6ie.d.cts +14 -0
  59. package/dist/{agentic-memory-DfuVZ-lY.d.cts → agentic-memory-types-JDr7MPhh.d.cts} +13 -57
  60. package/dist/{agentic-memory-BmRCvh0d.d.ts → agentic-memory-types-khYJgzKL.d.ts} +13 -57
  61. package/dist/{capability-admission-B0BzqAm5.d.cts → capability-admission-B8RNFyaZ.d.cts} +3 -3
  62. package/dist/{capability-admission-g0mn9hpq.d.ts → capability-admission-Dkw1Xz3r.d.ts} +3 -3
  63. package/dist/chunk-2KUAG2GZ.js +9693 -0
  64. package/dist/chunk-2KUAG2GZ.js.map +1 -0
  65. package/dist/{chunk-CZNV3VXM.js → chunk-3J7UPZL5.js} +11 -11
  66. package/dist/{chunk-HKK2JD7Z.js → chunk-3LYI6ADX.js} +12 -125
  67. package/dist/chunk-3LYI6ADX.js.map +1 -0
  68. package/dist/{chunk-YXMZ2N6J.js → chunk-4M37FH2I.js} +1071 -1025
  69. package/dist/chunk-4M37FH2I.js.map +1 -0
  70. package/dist/{chunk-YDSIVHRR.js → chunk-4NWSNI4K.js} +1598 -1584
  71. package/dist/chunk-4NWSNI4K.js.map +1 -0
  72. package/dist/{chunk-Z2ANQ6JW.js → chunk-4Q77MMJY.js} +2 -2
  73. package/dist/{chunk-SS2A464I.js → chunk-54WPEODM.js} +3 -3
  74. package/dist/chunk-54WPEODM.js.map +1 -0
  75. package/dist/chunk-6E76H4OU.js +132 -0
  76. package/dist/chunk-6E76H4OU.js.map +1 -0
  77. package/dist/chunk-7GOTIIQ7.js +819 -0
  78. package/dist/chunk-7GOTIIQ7.js.map +1 -0
  79. package/dist/{chunk-V7CIJE4B.js → chunk-DKFWDTVY.js} +92 -9
  80. package/dist/chunk-DKFWDTVY.js.map +1 -0
  81. package/dist/{chunk-FBUU43PM.js → chunk-E4QCH32M.js} +448 -446
  82. package/dist/chunk-E4QCH32M.js.map +1 -0
  83. package/dist/{chunk-XKXWK2KP.js → chunk-GXU6WFEJ.js} +1157 -1021
  84. package/dist/chunk-GXU6WFEJ.js.map +1 -0
  85. package/dist/{chunk-K3FXTC3I.js → chunk-HJACH5PU.js} +3 -3
  86. package/dist/{chunk-GTPCOEOZ.js → chunk-IDMLXLLG.js} +3 -3
  87. package/dist/{chunk-DKP56CSW.js → chunk-JKAYLDQV.js} +2 -2
  88. package/dist/{chunk-4VLAQHZI.js → chunk-K7FB3JTD.js} +17 -1
  89. package/dist/chunk-K7FB3JTD.js.map +1 -0
  90. package/dist/{chunk-43BKLFZU.js → chunk-KFURM6FM.js} +10 -10
  91. package/dist/{chunk-H75VXTGT.js → chunk-L6LDXRWE.js} +3 -3
  92. package/dist/{chunk-H75VXTGT.js.map → chunk-L6LDXRWE.js.map} +1 -1
  93. package/dist/chunk-MCOFHMUU.js +4131 -0
  94. package/dist/chunk-MCOFHMUU.js.map +1 -0
  95. package/dist/chunk-MQEQMS7B.js +1009 -0
  96. package/dist/chunk-MQEQMS7B.js.map +1 -0
  97. package/dist/chunk-MTELVIYR.js +1421 -0
  98. package/dist/chunk-MTELVIYR.js.map +1 -0
  99. package/dist/{chunk-3JR3EDB2.js → chunk-NNDIFUKX.js} +121 -115
  100. package/dist/chunk-NNDIFUKX.js.map +1 -0
  101. package/dist/{chunk-2747WN4Y.js → chunk-PUSRRALM.js} +3 -3
  102. package/dist/{chunk-QFY4AFY4.js → chunk-PVC7WDKI.js} +58 -52
  103. package/dist/chunk-PVC7WDKI.js.map +1 -0
  104. package/dist/{chunk-VTHLCFN4.js → chunk-RAHNUYA5.js} +2 -2
  105. package/dist/chunk-TQPCAIQI.js +612 -0
  106. package/dist/chunk-TQPCAIQI.js.map +1 -0
  107. package/dist/{chunk-IXZBUWSP.js → chunk-UEMN5URK.js} +2 -2
  108. package/dist/chunk-WOS7RJYZ.js +1320 -0
  109. package/dist/chunk-WOS7RJYZ.js.map +1 -0
  110. package/dist/{chunk-6PHB5CJS.js → chunk-ZLFRB6FI.js} +2 -2
  111. package/dist/composition/index.cjs +1228 -1076
  112. package/dist/composition/index.cjs.map +1 -1
  113. package/dist/composition/index.d.cts +3 -3
  114. package/dist/composition/index.d.ts +3 -3
  115. package/dist/composition/index.js +4 -4
  116. package/dist/core/index.cjs +1198 -1046
  117. package/dist/core/index.cjs.map +1 -1
  118. package/dist/core/index.d.cts +23 -2
  119. package/dist/core/index.d.ts +23 -2
  120. package/dist/core/index.js +2 -2
  121. package/dist/cqrs/index.d.cts +3 -3
  122. package/dist/cqrs/index.d.ts +3 -3
  123. package/dist/cqrs/messaging.d.cts +4 -4
  124. package/dist/cqrs/messaging.d.ts +4 -4
  125. package/dist/cqrs/work-queue.d.cts +5 -5
  126. package/dist/cqrs/work-queue.d.ts +5 -5
  127. package/dist/data-structures/index.cjs +1226 -1074
  128. package/dist/data-structures/index.cjs.map +1 -1
  129. package/dist/data-structures/index.d.cts +8 -7
  130. package/dist/data-structures/index.d.ts +8 -7
  131. package/dist/data-structures/index.js +6 -5
  132. package/dist/{describe-DYMw_l1C.d.cts → describe-BumQ-3_0.d.cts} +1 -1
  133. package/dist/{describe-BpyJuWkw.d.ts → describe-cYr_Z5zm.d.ts} +1 -1
  134. package/dist/executors/tool-provider-adapters.cjs +3091 -0
  135. package/dist/executors/tool-provider-adapters.cjs.map +1 -0
  136. package/dist/executors/tool-provider-adapters.d.cts +174 -0
  137. package/dist/executors/tool-provider-adapters.d.ts +174 -0
  138. package/dist/executors/tool-provider-adapters.js +1336 -0
  139. package/dist/executors/tool-provider-adapters.js.map +1 -0
  140. package/dist/executors/tool-provider-runtime.cjs +5178 -0
  141. package/dist/executors/tool-provider-runtime.cjs.map +1 -0
  142. package/dist/executors/tool-provider-runtime.d.cts +17 -0
  143. package/dist/executors/tool-provider-runtime.d.ts +17 -0
  144. package/dist/executors/tool-provider-runtime.js +15 -0
  145. package/dist/executors/tool-provider-runtime.js.map +1 -0
  146. package/dist/executors/tool-provider.cjs +6392 -0
  147. package/dist/executors/tool-provider.cjs.map +1 -0
  148. package/dist/executors/tool-provider.d.cts +60 -0
  149. package/dist/executors/tool-provider.d.ts +60 -0
  150. package/dist/executors/tool-provider.js +89 -0
  151. package/dist/executors/tool-provider.js.map +1 -0
  152. package/dist/executors/work-queue.d.cts +6 -6
  153. package/dist/executors/work-queue.d.ts +6 -6
  154. package/dist/graph/index.cjs +1499 -1341
  155. package/dist/graph/index.cjs.map +1 -1
  156. package/dist/graph/index.d.cts +9 -8
  157. package/dist/graph/index.d.ts +9 -8
  158. package/dist/graph/index.js +23 -22
  159. package/dist/{graph-DCEWh4T2.d.cts → graph-C1C-wb0L.d.cts} +266 -137
  160. package/dist/{graph-D-1-SQeC.d.ts → graph-CS0b4V9B.d.ts} +266 -137
  161. package/dist/{index-72VZC0-E.d.cts → index-DLrNopda.d.cts} +151 -10
  162. package/dist/{index-Bmxeu0nK.d.ts → index-DzJni3_6.d.ts} +151 -10
  163. package/dist/index.cjs +3397 -3140
  164. package/dist/index.cjs.map +1 -1
  165. package/dist/index.d.cts +12 -9
  166. package/dist/index.d.ts +12 -9
  167. package/dist/index.js +110 -109
  168. package/dist/inspection/boundary.d.cts +4 -4
  169. package/dist/inspection/boundary.d.ts +4 -4
  170. package/dist/measurements-BWAnochv.d.ts +294 -0
  171. package/dist/measurements-B_gJfD8h.d.cts +294 -0
  172. package/dist/messaging/index.cjs +57 -51
  173. package/dist/messaging/index.cjs.map +1 -1
  174. package/dist/messaging/index.d.cts +11 -276
  175. package/dist/messaging/index.d.ts +11 -276
  176. package/dist/messaging/index.js +1 -1
  177. package/dist/operators/index.cjs +1228 -1076
  178. package/dist/operators/index.cjs.map +1 -1
  179. package/dist/operators/index.d.cts +5 -5
  180. package/dist/operators/index.d.ts +5 -5
  181. package/dist/operators/index.js +5 -5
  182. package/dist/orchestration/index.cjs +8844 -6071
  183. package/dist/orchestration/index.cjs.map +1 -1
  184. package/dist/orchestration/index.d.cts +171 -224
  185. package/dist/orchestration/index.d.ts +171 -224
  186. package/dist/orchestration/index.js +42 -991
  187. package/dist/orchestration/index.js.map +1 -1
  188. package/dist/orchestration/messaging.d.cts +5 -8
  189. package/dist/orchestration/messaging.d.ts +5 -8
  190. package/dist/orchestration/work-queue.cjs +1243 -5
  191. package/dist/orchestration/work-queue.cjs.map +1 -1
  192. package/dist/orchestration/work-queue.d.cts +121 -10
  193. package/dist/orchestration/work-queue.d.ts +121 -10
  194. package/dist/orchestration/work-queue.js +1067 -4
  195. package/dist/orchestration/work-queue.js.map +1 -1
  196. package/dist/patterns/event-flow.cjs +6 -20
  197. package/dist/patterns/event-flow.cjs.map +1 -1
  198. package/dist/patterns/event-flow.d.cts +4 -4
  199. package/dist/patterns/event-flow.d.ts +4 -4
  200. package/dist/patterns/event-flow.js +2 -2
  201. package/dist/patterns/index.cjs +453 -465
  202. package/dist/patterns/index.cjs.map +1 -1
  203. package/dist/patterns/index.d.cts +6 -5
  204. package/dist/patterns/index.d.ts +6 -5
  205. package/dist/patterns/index.js +3 -3
  206. package/dist/process-types-BB7oRhX3.d.cts +200 -0
  207. package/dist/process-types-QNhyoYJ8.d.ts +200 -0
  208. package/dist/{reactive-collection-DNV0-gFi.d.cts → reactive-collection-BYlWvtnp.d.cts} +4 -34
  209. package/dist/{reactive-collection-BAmfrGjd.d.ts → reactive-collection-efOEdGFN.d.ts} +4 -34
  210. package/dist/{reactive-collection-storage-BNEldDyi.d.cts → reactive-collection-storage-BjoLsoiC.d.cts} +5 -5
  211. package/dist/{reactive-collection-storage-YZsAQsWK.d.ts → reactive-collection-storage-DwW6UuK_.d.ts} +5 -5
  212. package/dist/{reactive-map-DRD1LJ2H.d.cts → reactive-map-BHacbIqz.d.cts} +7 -2
  213. package/dist/{reactive-map-EsDiCmJk.d.ts → reactive-map-DN4Jyqnf.d.ts} +7 -2
  214. package/dist/render/index.d.cts +2 -2
  215. package/dist/render/index.d.ts +2 -2
  216. package/dist/scheduled-readiness-BmqXxnzX.d.ts +91 -0
  217. package/dist/scheduled-readiness-CYSw7KDE.d.cts +91 -0
  218. package/dist/semantic-memory-graph-B_LwbM-E.d.cts +30 -0
  219. package/dist/semantic-memory-graph-rsFNNhwn.d.ts +30 -0
  220. package/dist/{semantic-memory-graph-DKuNzuWw.d.cts → semantic-memory-graph-types-Y73bbwVI.d.cts} +2 -28
  221. package/dist/{semantic-memory-graph-uwbwJdhe.d.ts → semantic-memory-graph-types-safmPkuX.d.ts} +2 -28
  222. package/dist/solutions/index.cjs +6353 -6169
  223. package/dist/solutions/index.cjs.map +1 -1
  224. package/dist/solutions/index.d.cts +12 -10
  225. package/dist/solutions/index.d.ts +12 -10
  226. package/dist/solutions/index.js +5 -5
  227. package/dist/solutions/reactive-layout/browser/index.cjs +116 -108
  228. package/dist/solutions/reactive-layout/browser/index.cjs.map +1 -1
  229. package/dist/solutions/reactive-layout/browser/index.d.cts +4 -4
  230. package/dist/solutions/reactive-layout/browser/index.d.ts +4 -4
  231. package/dist/solutions/reactive-layout/browser/index.js +3 -3
  232. package/dist/solutions/reactive-layout/index.cjs +3578 -3350
  233. package/dist/solutions/reactive-layout/index.cjs.map +1 -1
  234. package/dist/solutions/reactive-layout/index.d.cts +32 -576
  235. package/dist/solutions/reactive-layout/index.d.ts +32 -576
  236. package/dist/solutions/reactive-layout/index.js +65 -5
  237. package/dist/solutions/reactive-layout/node-canvas/index.cjs +127 -117
  238. package/dist/solutions/reactive-layout/node-canvas/index.cjs.map +1 -1
  239. package/dist/solutions/reactive-layout/node-canvas/index.d.cts +4 -4
  240. package/dist/solutions/reactive-layout/node-canvas/index.d.ts +4 -4
  241. package/dist/solutions/reactive-layout/node-canvas/index.js +3 -3
  242. package/dist/solutions/reactive-layout/react-native/index.cjs +127 -117
  243. package/dist/solutions/reactive-layout/react-native/index.cjs.map +1 -1
  244. package/dist/solutions/reactive-layout/react-native/index.d.cts +4 -4
  245. package/dist/solutions/reactive-layout/react-native/index.d.ts +4 -4
  246. package/dist/solutions/reactive-layout/react-native/index.js +3 -3
  247. package/dist/solutions/reactive-layout/skia/index.cjs +127 -117
  248. package/dist/solutions/reactive-layout/skia/index.cjs.map +1 -1
  249. package/dist/solutions/reactive-layout/skia/index.d.cts +4 -4
  250. package/dist/solutions/reactive-layout/skia/index.d.ts +4 -4
  251. package/dist/solutions/reactive-layout/skia/index.js +3 -3
  252. package/dist/solutions/work-item/actions.cjs +1609 -1428
  253. package/dist/solutions/work-item/actions.cjs.map +1 -1
  254. package/dist/solutions/work-item/actions.d.cts +23 -184
  255. package/dist/solutions/work-item/actions.d.ts +23 -184
  256. package/dist/solutions/work-item/actions.js +1558 -934
  257. package/dist/solutions/work-item/actions.js.map +1 -1
  258. package/dist/solutions/work-item/scheduling.cjs +9181 -1838
  259. package/dist/solutions/work-item/scheduling.cjs.map +1 -1
  260. package/dist/solutions/work-item/scheduling.d.cts +1763 -328
  261. package/dist/solutions/work-item/scheduling.d.ts +1763 -328
  262. package/dist/solutions/work-item/scheduling.js +117 -3
  263. package/dist/solutions/work-item/work-queue.d.cts +7 -7
  264. package/dist/solutions/work-item/work-queue.d.ts +7 -7
  265. package/dist/sources/browser.cjs +1318 -1083
  266. package/dist/sources/browser.cjs.map +1 -1
  267. package/dist/sources/browser.d.cts +22 -6
  268. package/dist/sources/browser.d.ts +22 -6
  269. package/dist/sources/browser.js +4 -4
  270. package/dist/sources/browser.js.map +1 -1
  271. package/dist/sources/index.cjs +1318 -1083
  272. package/dist/sources/index.cjs.map +1 -1
  273. package/dist/sources/index.d.cts +4 -4
  274. package/dist/sources/index.d.ts +4 -4
  275. package/dist/sources/index.js +4 -4
  276. package/dist/sources/node.d.cts +3 -3
  277. package/dist/sources/node.d.ts +3 -3
  278. package/dist/storage/browser.cjs +16 -0
  279. package/dist/storage/browser.cjs.map +1 -1
  280. package/dist/storage/browser.js +3 -3
  281. package/dist/storage/index.cjs +16 -0
  282. package/dist/storage/index.cjs.map +1 -1
  283. package/dist/storage/index.d.cts +5 -4
  284. package/dist/storage/index.d.ts +5 -4
  285. package/dist/storage/index.js +6 -6
  286. package/dist/storage/node.cjs +16 -0
  287. package/dist/storage/node.cjs.map +1 -1
  288. package/dist/storage/node.js +3 -3
  289. package/dist/{store-C-joM4gh.d.ts → store-6_CHF6Ge.d.ts} +1 -1
  290. package/dist/{store-CNJwmT-h.d.cts → store-BljX8RkQ.d.cts} +1 -1
  291. package/dist/types-BAw2QVVE.d.cts +274 -0
  292. package/dist/types-BUbbbh4f.d.cts +34 -0
  293. package/dist/types-CMnzoL54.d.cts +457 -0
  294. package/dist/{types-CcPIn7sy.d.cts → types-CTN3XoMl.d.cts} +57 -108
  295. package/dist/types-CV_ohy5T.d.cts +357 -0
  296. package/dist/{types-BsJvElYZ.d.ts → types-DChxk1vK.d.ts} +57 -108
  297. package/dist/types-DRu7SnLV.d.ts +274 -0
  298. package/dist/types-DeID_FRl.d.ts +34 -0
  299. package/dist/types-IhKH0hGW.d.ts +357 -0
  300. package/dist/types-ePlD2uyT.d.ts +457 -0
  301. package/dist/{work-item-runtime-qaVgsL0T.d.cts → work-item-runtime-types-BTxLskN5.d.cts} +3 -93
  302. package/dist/{work-item-runtime-C3M79vE9.d.ts → work-item-runtime-types-DAsZbb2y.d.ts} +3 -93
  303. package/dist/work-queue/index.cjs +957 -953
  304. package/dist/work-queue/index.cjs.map +1 -1
  305. package/dist/work-queue/index.d.cts +9 -357
  306. package/dist/work-queue/index.d.ts +9 -357
  307. package/dist/work-queue/index.js +957 -953
  308. package/dist/work-queue/index.js.map +1 -1
  309. package/package.json +611 -513
  310. package/dist/chunk-3JR3EDB2.js.map +0 -1
  311. package/dist/chunk-4VLAQHZI.js.map +0 -1
  312. package/dist/chunk-6XAIOL3X.js +0 -2407
  313. package/dist/chunk-6XAIOL3X.js.map +0 -1
  314. package/dist/chunk-FBUU43PM.js.map +0 -1
  315. package/dist/chunk-HKK2JD7Z.js.map +0 -1
  316. package/dist/chunk-PC3BTTBE.js +0 -3217
  317. package/dist/chunk-PC3BTTBE.js.map +0 -1
  318. package/dist/chunk-QFY4AFY4.js.map +0 -1
  319. package/dist/chunk-SS2A464I.js.map +0 -1
  320. package/dist/chunk-V7CIJE4B.js.map +0 -1
  321. package/dist/chunk-XKXWK2KP.js.map +0 -1
  322. package/dist/chunk-YDSIVHRR.js.map +0 -1
  323. package/dist/chunk-YXMZ2N6J.js.map +0 -1
  324. /package/dist/{chunk-CZNV3VXM.js.map → chunk-3J7UPZL5.js.map} +0 -0
  325. /package/dist/{chunk-Z2ANQ6JW.js.map → chunk-4Q77MMJY.js.map} +0 -0
  326. /package/dist/{chunk-K3FXTC3I.js.map → chunk-HJACH5PU.js.map} +0 -0
  327. /package/dist/{chunk-GTPCOEOZ.js.map → chunk-IDMLXLLG.js.map} +0 -0
  328. /package/dist/{chunk-DKP56CSW.js.map → chunk-JKAYLDQV.js.map} +0 -0
  329. /package/dist/{chunk-43BKLFZU.js.map → chunk-KFURM6FM.js.map} +0 -0
  330. /package/dist/{chunk-2747WN4Y.js.map → chunk-PUSRRALM.js.map} +0 -0
  331. /package/dist/{chunk-VTHLCFN4.js.map → chunk-RAHNUYA5.js.map} +0 -0
  332. /package/dist/{chunk-IXZBUWSP.js.map → chunk-UEMN5URK.js.map} +0 -0
  333. /package/dist/{chunk-6PHB5CJS.js.map → chunk-ZLFRB6FI.js.map} +0 -0
@@ -60,772 +60,244 @@ function attachMessageBusDeferredCommandSink(graph, bus, commands) {
60
60
  return unsubscribe;
61
61
  }
62
62
 
63
- // src/work-queue/index.ts
64
- function workQueue(graph, opts) {
65
- assertNonEmpty(opts.queueId, "workQueue.queueId");
66
- assertNonEmpty(opts.topic, "workQueue.topic");
67
- assertNonEmpty(opts.subscriptionId, "workQueue.subscriptionId");
68
- const name = opts.name ?? `workQueue/${opts.queueId}`;
69
- const now = opts.now ?? Date.now;
70
- const leaseDurationMs = opts.leaseDurationMs ?? 3e4;
71
- const retry = opts.retry ?? { maxAttempts: 3, delayMs: 0 };
72
- const admission = opts.bus.subscription({
73
- topic: opts.topic,
74
- subscriptionId: opts.subscriptionId,
75
- from: opts.from ?? "earliest",
76
- name: `${name}/admission`
77
- });
78
- const commands = graph.node(
79
- [],
80
- (ctx) => {
81
- for (const command of depBatch(ctx, 0) ?? []) ctx.down([["DATA", command]]);
82
- },
83
- {
84
- name: `${name}/commands`,
85
- factory: "workQueueCommands",
86
- completeWhenDepsComplete: false,
87
- errorWhenDepsError: false
88
- }
89
- );
90
- const state = {
91
- recordSeq: 0,
92
- leaseSeq: 0,
93
- works: /* @__PURE__ */ new Map(),
94
- sourceSeqs: /* @__PURE__ */ new Set(),
95
- commandIds: /* @__PURE__ */ new Set(),
96
- idempotencyKeys: /* @__PURE__ */ new Set(),
97
- records: [],
98
- deadLetters: []
99
- };
100
- const admissionKick = graph.node([], null, {
101
- name: `${name}/admissionKick`,
102
- factory: "workQueueAdmissionKick",
103
- initial: "poll",
104
- completeWhenDepsComplete: false,
105
- errorWhenDepsError: false
106
- });
107
- const admissionPages = graph.node(
108
- [admission.available, admissionKick, opts.bus.messages, opts.bus.status],
109
- (ctx) => {
110
- for (const page of depBatch(ctx, 0) ?? []) {
111
- ctx.down([["DATA", page]]);
112
- }
113
- let shouldPull = (depBatch(ctx, 1)?.length ?? 0) > 0;
114
- for (const message of depBatch(ctx, 2) ?? []) {
115
- if (message.topic === opts.topic) shouldPull = true;
116
- }
117
- for (const status2 of depBatch(ctx, 3) ?? []) {
118
- if (shouldPollAdmission(status2, opts.topic, opts.subscriptionId)) {
119
- shouldPull = true;
120
- }
121
- }
122
- if (shouldPull) {
123
- ctx.upNext([["PULL", { pullId: admission.availablePullId, params: { limit: 100 } }]], 0);
124
- }
125
- },
126
- {
127
- name: `${name}/admissionPages`,
128
- factory: "workQueueAdmissionPages",
129
- partial: true,
130
- completeWhenDepsComplete: false,
131
- errorWhenDepsError: false
132
- }
133
- );
134
- const runtime = graph.node(
135
- [commands, admissionPages],
136
- (ctx) => {
137
- for (const page of depBatch(ctx, 1) ?? []) {
138
- for (const msg of page.messages) {
139
- for (const event of admitMessage(opts, state, msg, now())) ctx.down([["DATA", event]]);
140
- }
141
- }
142
- for (const command of depBatch(ctx, 0) ?? []) {
143
- const commandTime = commandNowMs(command, now());
144
- for (const event of reduceQueueCommand(
145
- opts,
146
- state,
147
- command,
148
- commandTime,
149
- leaseDurationMs,
150
- retry
151
- )) {
152
- ctx.down([["DATA", event]]);
153
- }
154
- }
155
- },
156
- {
157
- name: `${name}/runtime`,
158
- factory: "workQueueRuntime",
159
- partial: true,
160
- completeWhenDepsComplete: false,
161
- errorWhenDepsError: false
162
- }
163
- );
164
- const records = graph.node(
165
- [runtime],
166
- (ctx) => {
167
- for (const event of depBatch(ctx, 0) ?? []) {
168
- const typed = event;
169
- if (typed.kind === "record") ctx.down([["DATA", typed.record]]);
170
- }
171
- },
172
- {
173
- name: `${name}/records`,
174
- factory: "workQueueRecords",
175
- completeWhenDepsComplete: false,
176
- errorWhenDepsError: false
177
- }
178
- );
179
- const status = graph.node(
180
- [runtime],
181
- (ctx) => {
182
- for (const event of depBatch(ctx, 0) ?? []) {
183
- const typed = event;
184
- if (typed.kind === "status") ctx.down([["DATA", typed.status]]);
185
- }
186
- },
187
- {
188
- name: `${name}/status`,
189
- factory: "workQueueStatus",
190
- completeWhenDepsComplete: false,
191
- errorWhenDepsError: false
192
- }
193
- );
194
- const issues = graph.node(
195
- [runtime],
196
- (ctx) => {
197
- for (const event of depBatch(ctx, 0) ?? []) {
198
- const typed = event;
199
- if (typed.kind === "issue") ctx.down([["DATA", typed.issue]]);
200
- }
201
- },
202
- {
203
- name: `${name}/issues`,
204
- factory: "workQueueIssues",
205
- completeWhenDepsComplete: false,
206
- errorWhenDepsError: false
207
- }
208
- );
209
- const admissionAckCommands = graph.node(
210
- [records],
211
- (ctx) => {
212
- for (const record of depBatch(ctx, 0) ?? []) {
213
- const command = admissionAckCommand(record);
214
- if (command !== void 0) ctx.down([["DATA", command]]);
215
- }
216
- },
217
- {
218
- name: `${name}/admissionAckCommands`,
219
- factory: "workQueueAdmissionAckCommands",
220
- completeWhenDepsComplete: false,
221
- errorWhenDepsError: false
222
- }
223
- );
224
- attachMessageBusDeferredCommandSink(graph, opts.bus, admissionAckCommands);
225
- graph.retain(runtime, { reason: `${name}.workQueue.runtime` });
226
- const queue = {
227
- commands,
228
- records,
229
- status,
230
- issues,
231
- submit(payload, submitOpts = {}) {
232
- const command = opts.bus.publish(
233
- opts.topic,
234
- submitPayload(payload, {
235
- ...submitOpts.workId === void 0 ? {} : { workId: submitOpts.workId },
236
- ...submitOpts.priority === void 0 ? {} : { priority: submitOpts.priority },
237
- ...submitOpts.tags === void 0 ? {} : { tags: submitOpts.tags },
238
- ...submitOpts.requirements === void 0 ? {} : { requirements: submitOpts.requirements },
239
- ...submitOpts.notBeforeMs === void 0 ? {} : { notBeforeMs: submitOpts.notBeforeMs },
240
- ...submitOpts.deadlineMs === void 0 ? {} : { deadlineMs: submitOpts.deadlineMs }
241
- }),
242
- {
243
- commandId: submitOpts.commandId ?? nextCommandId(opts.queueId, "submit"),
244
- idempotencyKey: submitOpts.idempotencyKey
245
- }
246
- );
247
- return command;
248
- },
249
- claim(claimOpts) {
250
- return publishQueueCommand(commands, {
251
- ...claimOpts,
252
- kind: "claim",
253
- commandId: claimOpts.commandId ?? nextCommandId(opts.queueId, "claim")
254
- });
255
- },
256
- renewLease(renewOpts) {
257
- return publishQueueCommand(commands, {
258
- ...renewOpts,
259
- kind: "renew-lease",
260
- commandId: renewOpts.commandId ?? nextCommandId(opts.queueId, "renew")
261
- });
262
- },
263
- release(releaseOpts) {
264
- return publishQueueCommand(commands, {
265
- ...releaseOpts,
266
- kind: "release",
267
- commandId: releaseOpts.commandId ?? nextCommandId(opts.queueId, "release")
268
- });
269
- },
270
- complete(completeOpts) {
271
- return publishQueueCommand(commands, {
272
- ...completeOpts,
273
- kind: "complete",
274
- commandId: completeOpts.commandId ?? nextCommandId(opts.queueId, "complete")
275
- });
276
- },
277
- fail(failOpts) {
278
- return publishQueueCommand(commands, {
279
- ...failOpts,
280
- kind: "fail",
281
- commandId: failOpts.commandId ?? nextCommandId(opts.queueId, "fail")
282
- });
283
- },
284
- cancel(cancelOpts) {
285
- return publishQueueCommand(commands, {
286
- ...cancelOpts,
287
- kind: "cancel",
288
- commandId: cancelOpts.commandId ?? nextCommandId(opts.queueId, "cancel")
289
- });
290
- },
291
- schedule(scheduleOpts) {
292
- return publishQueueCommand(commands, {
293
- ...scheduleOpts,
294
- kind: "schedule",
295
- commandId: scheduleOpts.commandId ?? nextCommandId(opts.queueId, "schedule")
296
- });
297
- },
298
- expireLeases(expireOpts = {}) {
299
- return publishQueueCommand(commands, {
300
- ...expireOpts,
301
- kind: "expire-leases",
302
- commandId: expireOpts.commandId ?? nextCommandId(opts.queueId, "expire")
303
- });
304
- },
305
- available(projectionOpts = {}) {
306
- const availablePullId = /* @__PURE__ */ Symbol(`${name}/available`);
307
- const available = graph.node(
308
- [records],
309
- (ctx) => {
310
- const params = pullParams(ctx.pull);
311
- ctx.down([["DATA", availablePage(state, params)]]);
312
- },
313
- {
314
- name: projectionOpts.name ?? `${name}/available`,
315
- factory: "workQueueAvailable",
316
- pullId: availablePullId,
317
- partial: true,
318
- completeWhenDepsComplete: false,
319
- errorWhenDepsError: false
320
- }
321
- );
322
- return { available, availablePullId, status, issues };
323
- },
324
- work(workId, projectionOpts = {}) {
325
- const snapshotPullId = /* @__PURE__ */ Symbol(`${name}/${workId}/snapshot`);
326
- const snapshot = graph.node(
327
- [records],
328
- (ctx) => {
329
- ctx.down([["DATA", workSnapshot(state, workId)]]);
330
- },
331
- {
332
- name: projectionOpts.name ?? `${name}/${workId}`,
333
- factory: "workQueueWorkSnapshot",
334
- pullId: snapshotPullId,
335
- partial: true,
336
- completeWhenDepsComplete: false,
337
- errorWhenDepsError: false
338
- }
339
- );
340
- return { snapshot, snapshotPullId, status, issues };
341
- },
342
- deadLetter(projectionOpts = {}) {
343
- const snapshotPullId = /* @__PURE__ */ Symbol(`${name}/deadLetter`);
344
- const snapshot = graph.node(
345
- [records],
346
- (ctx) => {
347
- const params = pullParams(ctx.pull);
348
- ctx.down([["DATA", deadLetterPage(state, params)]]);
349
- },
350
- {
351
- name: projectionOpts.name ?? `${name}/deadLetter`,
352
- factory: "workQueueDeadLetter",
353
- pullId: snapshotPullId,
354
- partial: true,
355
- completeWhenDepsComplete: false,
356
- errorWhenDepsError: false
357
- }
358
- );
359
- return { snapshot, snapshotPullId, status, issues };
360
- }
361
- };
362
- return queue;
63
+ // src/work-queue/utils.ts
64
+ function appendRecord(state, queueId, record) {
65
+ const full = { ...record, queueId, recordSeq: ++state.recordSeq };
66
+ state.records.push(full);
67
+ return full;
363
68
  }
364
- function shouldPollAdmission(status, topic, subscriptionId) {
365
- if (status.topic !== topic) return false;
366
- if (status.kind === "message-published" || status.kind === "retention-trimmed") return true;
367
- return status.subscriptionId === subscriptionId && (status.kind === "subscription-acked" || status.kind === "subscription-sought");
69
+ function recordEvent(record) {
70
+ return { kind: "record", record };
368
71
  }
369
- function admissionAckCommand(record) {
370
- const messageBus = admissionMessageBus(record);
371
- if (messageBus === void 0) return void 0;
72
+ function statusEvent(queueId, kind, timestampMs, fields = {}) {
372
73
  return {
373
- kind: "ack",
374
- topic: messageBus.topic,
375
- subscriptionId: messageBus.subscriptionId,
376
- seq: messageBus.seq,
377
- commandId: `${record.queueId}:admission-ack:${messageBus.topic}:${messageBus.subscriptionId}:${messageBus.seq}`
378
- };
379
- }
380
- function admissionMessageBus(record) {
381
- return "messageBus" in record ? record.messageBus : void 0;
382
- }
383
- function admitMessage(opts, state, message, nowMs) {
384
- const source = `${message.topic}:${message.seq}`;
385
- if (state.sourceSeqs.has(source)) return [];
386
- state.sourceSeqs.add(source);
387
- const submitted = decodeSubmittedPayload(message.payload);
388
- const payload = submitted.payload;
389
- const payloadRecord = payloadAsRecord(submitted.meta);
390
- const workId = typeof payloadRecord.workId === "string" ? payloadRecord.workId : `${opts.queueId}:${message.topic}:${message.seq}`;
391
- if (state.works.has(workId)) {
392
- const record2 = appendRecord(state, opts.queueId, {
393
- kind: "admission-deduped",
394
- workId,
395
- messageBus: { topic: message.topic, seq: message.seq, subscriptionId: opts.subscriptionId },
396
- reason: "duplicate-work",
397
- existingWorkId: workId,
398
- recordedAtMs: nowMs
399
- });
400
- return [
401
- recordEvent(record2),
402
- statusEvent(opts.queueId, "admission-rejected", nowMs, {
403
- workId,
404
- recordSeq: record2.recordSeq,
405
- issueCode: "duplicate-work"
406
- })
407
- ];
408
- }
409
- const work = {
410
- workId,
411
- payload,
412
- state: typeof payloadRecord.notBeforeMs === "number" && payloadRecord.notBeforeMs > nowMs ? "scheduled" : "ready",
413
- admissionSeq: message.seq,
414
- priority: numberOrUndefined(payloadRecord.priority),
415
- tags: stringArrayOrUndefined(payloadRecord.tags),
416
- requirements: stringArrayOrUndefined(payloadRecord.requirements),
417
- notBeforeMs: numberOrUndefined(payloadRecord.notBeforeMs),
418
- deadlineMs: numberOrUndefined(payloadRecord.deadlineMs),
419
- attempt: 0
74
+ kind: "status",
75
+ status: {
76
+ kind,
77
+ queueId,
78
+ timestampMs,
79
+ ...fields.workId === void 0 ? {} : { workId: fields.workId },
80
+ ...fields.commandId === void 0 ? {} : { commandId: fields.commandId },
81
+ ...fields.recordSeq === void 0 ? {} : { recordSeq: fields.recordSeq },
82
+ ...fields.asOfRecordSeq === void 0 ? {} : { asOfRecordSeq: fields.asOfRecordSeq },
83
+ ...fields.issueCode === void 0 ? {} : { issueCode: fields.issueCode },
84
+ ...fields.details === void 0 ? {} : { details: fields.details }
85
+ }
420
86
  };
421
- state.works.set(workId, work);
422
- const record = appendRecord(state, opts.queueId, {
423
- kind: "work-admitted",
424
- workId,
425
- payload,
426
- messageBus: { topic: message.topic, seq: message.seq, subscriptionId: opts.subscriptionId },
427
- priority: work.priority,
428
- tags: work.tags,
429
- requirements: work.requirements,
430
- notBeforeMs: work.notBeforeMs,
431
- deadlineMs: work.deadlineMs,
432
- recordedAtMs: nowMs
433
- });
87
+ }
88
+ function issueEvent(queueId, code, message, timestampMs, details) {
89
+ return {
90
+ kind: "issue",
91
+ issue: {
92
+ kind: "issue",
93
+ code,
94
+ message,
95
+ severity: "error",
96
+ source: "workQueue",
97
+ details,
98
+ metadata: { queueId, timestampMs }
99
+ }
100
+ };
101
+ }
102
+ function rejectQueueCommand(queueId, command, code, message, nowMs, commandId = commandIdFrom(command)) {
434
103
  return [
435
- recordEvent(record),
436
- statusEvent(opts.queueId, "admission-accepted", nowMs, { workId, recordSeq: record.recordSeq })
104
+ issueEvent(queueId, code, message, nowMs, command),
105
+ statusEvent(queueId, "command-rejected", nowMs, {
106
+ ...commandId === void 0 ? {} : { commandId },
107
+ issueCode: code
108
+ })
437
109
  ];
438
110
  }
439
- function reduceQueueCommand(opts, state, command, nowMs, leaseDurationMs, retry) {
440
- const invalid = validateQueueCommand(opts.queueId, command);
441
- if (invalid !== void 0)
442
- return rejectQueueCommand(opts.queueId, command, invalid.code, invalid.message, nowMs);
443
- if (state.commandIds.has(command.commandId)) {
444
- return rejectQueueCommand(
445
- opts.queueId,
446
- command,
447
- "duplicate-command",
448
- "duplicate commandId",
449
- nowMs
450
- );
451
- }
452
- if (command.idempotencyKey !== void 0 && state.idempotencyKeys.has(command.idempotencyKey)) {
453
- return rejectQueueCommand(
454
- opts.queueId,
455
- command,
456
- "duplicate-command",
457
- "duplicate idempotencyKey",
458
- nowMs
459
- );
460
- }
461
- state.commandIds.add(command.commandId);
462
- if (command.idempotencyKey !== void 0) state.idempotencyKeys.add(command.idempotencyKey);
111
+ function validateQueueCommand(queueId, command) {
112
+ if (!isObjectRecord(command))
113
+ return { code: "malformed-command", message: "command must be an object" };
114
+ if (typeof command.kind !== "string")
115
+ return { code: "malformed-command", message: "command kind is required" };
116
+ if (!isWorkQueueCommandKind(command.kind))
117
+ return { code: "malformed-command", message: `unknown command kind '${command.kind}'` };
118
+ if (typeof command.commandId !== "string" || command.commandId.length === 0)
119
+ return { code: "malformed-command", message: "commandId must be a non-empty string" };
120
+ if (command.queueId !== void 0 && command.queueId !== queueId)
121
+ return { code: "queue-mismatch", message: "command queueId does not match this queue" };
463
122
  switch (command.kind) {
123
+ case "submit":
124
+ return command.payload === void 0 ? { code: "malformed-command", message: "submit payload is required" } : void 0;
464
125
  case "claim":
465
- return claimWork(opts.queueId, state, command, nowMs, leaseDurationMs);
126
+ if (typeof command.workerId !== "string" || command.workerId.length === 0)
127
+ return { code: "malformed-command", message: "claim workerId is required" };
128
+ if (command.limit !== void 0 && !isPositiveInteger(command.limit))
129
+ return { code: "malformed-command", message: "claim limit must be a positive integer" };
130
+ if (command.requestedWorkIds !== void 0 && (!Array.isArray(command.requestedWorkIds) || !command.requestedWorkIds.every((id) => typeof id === "string" && id.length > 0))) {
131
+ return { code: "malformed-command", message: "requestedWorkIds must be strings" };
132
+ }
133
+ if (command.leaseDurationMs !== void 0 && !isPositiveInteger(command.leaseDurationMs))
134
+ return {
135
+ code: "malformed-command",
136
+ message: "leaseDurationMs must be a positive integer"
137
+ };
138
+ return void 0;
466
139
  case "renew-lease":
467
- return renewLease(opts.queueId, state, command, nowMs, leaseDurationMs);
140
+ if (command.leaseDurationMs !== void 0 && !isPositiveInteger(command.leaseDurationMs))
141
+ return {
142
+ code: "malformed-command",
143
+ message: "leaseDurationMs must be a positive integer"
144
+ };
145
+ if (command.leaseExpiresAtMs !== void 0 && !isFiniteNumber(command.leaseExpiresAtMs))
146
+ return { code: "malformed-command", message: "leaseExpiresAtMs must be finite" };
147
+ return validateLeaseCommand(command);
468
148
  case "release":
469
- return releaseWork(opts.queueId, state, command, nowMs);
470
149
  case "complete":
471
- return completeWork(opts.queueId, state, command, nowMs);
472
150
  case "fail":
473
- return failWork(opts.queueId, state, command, nowMs, retry);
151
+ return validateLeaseCommand(command);
474
152
  case "cancel":
475
- return cancelWork(opts.queueId, state, command, nowMs);
476
- case "schedule":
477
- return scheduleWork(opts.queueId, state, command, nowMs);
153
+ return requiredString(command.workId, "workId");
154
+ case "schedule": {
155
+ const workIdError = requiredString(command.workId, "workId");
156
+ if (workIdError !== void 0) return workIdError;
157
+ if (!isFiniteNumber(command.notBeforeMs))
158
+ return { code: "malformed-command", message: "notBeforeMs must be finite" };
159
+ if (command.deadlineMs !== void 0 && !isFiniteNumber(command.deadlineMs))
160
+ return { code: "malformed-command", message: "deadlineMs must be finite" };
161
+ return void 0;
162
+ }
478
163
  case "expire-leases":
479
- return expireLeases(opts.queueId, state, command, nowMs);
480
- case "submit":
481
- return [
482
- statusEvent(opts.queueId, "command-accepted", nowMs, {
483
- commandId: command.commandId,
484
- details: { submitUsesMessageBus: true }
485
- })
486
- ];
164
+ if (command.limit !== void 0 && !isPositiveInteger(command.limit))
165
+ return {
166
+ code: "malformed-command",
167
+ message: "expire-leases limit must be a positive integer"
168
+ };
169
+ if (command.workIds !== void 0 && (!Array.isArray(command.workIds) || !command.workIds.every((id) => typeof id === "string" && id.length > 0))) {
170
+ return { code: "malformed-command", message: "workIds must be strings" };
171
+ }
172
+ return void 0;
487
173
  }
488
174
  }
489
- function claimWork(queueId, state, command, nowMs, defaultLeaseDurationMs) {
490
- const limit = positiveLimit(command.limit ?? command.requestedWorkIds?.length ?? 1);
491
- const requested = new Set(command.requestedWorkIds ?? []);
492
- const events = [];
493
- for (const work of [...state.works.values()].filter((entry) => requested.size === 0 ? true : requested.has(entry.workId)).filter((entry) => isExpiredLease(entry, nowMs))) {
494
- events.push(...materializeLeaseExpired(queueId, state, work, command.commandId, nowMs));
495
- }
496
- const candidates = [...state.works.values()].filter((work) => requested.size === 0 ? true : requested.has(work.workId)).filter((work) => isReady(work, nowMs)).sort((a, b) => a.admissionSeq - b.admissionSeq).slice(0, limit);
497
- if (candidates.length === 0)
498
- return [
499
- ...events,
500
- ...requested.size === 0 ? rejectQueueCommand(queueId, command, "not-ready", "no ready work", nowMs) : claimMissEvents(queueId, state, command, requested, /* @__PURE__ */ new Set(), nowMs)
501
- ];
502
- const claimed = /* @__PURE__ */ new Set();
503
- for (const work of candidates) {
504
- claimed.add(work.workId);
505
- work.state = "leased";
506
- work.attempt += 1;
507
- work.leaseId = `${work.workId}:lease:${++state.leaseSeq}`;
508
- work.workerId = command.workerId;
509
- work.leaseExpiresAtMs = nowMs + (command.leaseDurationMs ?? defaultLeaseDurationMs);
510
- const record = appendRecord(state, queueId, {
511
- kind: "work-claimed",
512
- workId: work.workId,
513
- commandId: command.commandId,
514
- leaseId: work.leaseId,
515
- attempt: work.attempt,
516
- workerId: command.workerId,
517
- claimedAtMs: nowMs,
518
- leaseExpiresAtMs: work.leaseExpiresAtMs,
519
- recordedAtMs: nowMs
520
- });
521
- events.push(
522
- recordEvent(record),
523
- statusEvent(queueId, "command-accepted", nowMs, {
524
- workId: work.workId,
525
- commandId: command.commandId,
526
- recordSeq: record.recordSeq
527
- })
528
- );
529
- }
530
- if (requested.size > 0) {
531
- events.push(...claimMissEvents(queueId, state, command, requested, claimed, nowMs));
175
+ function validateLeaseCommand(command) {
176
+ for (const key of ["workId", "leaseId", "workerId"]) {
177
+ const err = requiredString(command[key], key);
178
+ if (err !== void 0) return err;
532
179
  }
533
- return events;
180
+ if (!isPositiveInteger(command.attempt))
181
+ return { code: "malformed-command", message: "attempt must be a positive integer" };
182
+ return void 0;
534
183
  }
535
- function claimMissEvents(queueId, state, command, requested, claimed, nowMs) {
536
- const events = [];
537
- for (const workId of requested) {
538
- if (claimed.has(workId)) continue;
539
- const work = state.works.get(workId);
540
- if (work === void 0) {
541
- events.push(
542
- ...rejectQueueCommand(
543
- queueId,
544
- command,
545
- "unknown-work",
546
- `unknown work '${workId}'`,
547
- nowMs
548
- )
549
- );
550
- continue;
551
- }
552
- if (isReady(work, nowMs)) continue;
553
- const code = isTerminal(work.state) ? "terminal-work" : work.state === "leased" ? "already-leased" : "not-ready";
554
- events.push(
555
- ...rejectQueueCommand(
556
- queueId,
557
- command,
558
- code,
559
- `requested work '${workId}' is not claimable`,
560
- nowMs
561
- )
562
- );
184
+ function requiredString(value, name) {
185
+ return typeof value === "string" && value.length > 0 ? void 0 : { code: "malformed-command", message: `${name} must be a non-empty string` };
186
+ }
187
+ function commandIdFrom(command) {
188
+ return isObjectRecord(command) && typeof command.commandId === "string" ? command.commandId : void 0;
189
+ }
190
+ function isWorkQueueCommandKind(kind) {
191
+ return kind === "submit" || kind === "claim" || kind === "renew-lease" || kind === "release" || kind === "complete" || kind === "fail" || kind === "cancel" || kind === "schedule" || kind === "expire-leases";
192
+ }
193
+ function isObjectRecord(value) {
194
+ return typeof value === "object" && value !== null && !Array.isArray(value);
195
+ }
196
+ function isPositiveInteger(value) {
197
+ return Number.isInteger(value) && value > 0;
198
+ }
199
+ function isFiniteNumber(value) {
200
+ return typeof value === "number" && Number.isFinite(value);
201
+ }
202
+ function publishQueueCommand(node, command) {
203
+ node.down([["DATA", command]]);
204
+ return command;
205
+ }
206
+ var commandSeq = 0;
207
+ function nextCommandId(queueId, kind) {
208
+ commandSeq += 1;
209
+ return `${queueId}:${kind}:${commandSeq}`;
210
+ }
211
+ function isReady(work, nowMs) {
212
+ if (work.state === "scheduled")
213
+ return work.notBeforeMs !== void 0 && work.notBeforeMs <= nowMs;
214
+ if (work.state === "retry-wait") return work.retryAtMs !== void 0 && work.retryAtMs <= nowMs;
215
+ return work.state === "ready";
216
+ }
217
+ function isReadyForProjection(work, nowMs) {
218
+ if (work.state === "scheduled" || work.state === "retry-wait") {
219
+ return nowMs === void 0 ? false : isReady(work, nowMs);
563
220
  }
564
- return events;
221
+ return isReady(work, 0);
222
+ }
223
+ function isTerminal(state) {
224
+ return state === "completed" || state === "canceled" || state === "dead-lettered";
225
+ }
226
+ function payloadAsRecord(value) {
227
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
228
+ }
229
+ function numberOrUndefined(value) {
230
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
565
231
  }
566
- function renewLease(queueId, state, command, nowMs, defaultLeaseDurationMs) {
567
- const checked = currentLease(queueId, state, command, nowMs);
568
- if ("issue" in checked) return [checked.issue];
569
- if ("events" in checked) return checked.events;
570
- const work = checked.work;
571
- const previous = work.leaseExpiresAtMs;
572
- work.leaseExpiresAtMs = command.leaseExpiresAtMs ?? nowMs + (command.leaseDurationMs ?? defaultLeaseDurationMs);
573
- const record = appendRecord(state, queueId, {
574
- kind: "lease-renewed",
575
- workId: work.workId,
576
- commandId: command.commandId,
577
- leaseId: command.leaseId,
578
- attempt: command.attempt,
579
- workerId: command.workerId,
580
- previousLeaseExpiresAtMs: previous,
581
- leaseExpiresAtMs: work.leaseExpiresAtMs,
582
- renewedAtMs: nowMs,
583
- recordedAtMs: nowMs
584
- });
585
- return [
586
- recordEvent(record),
587
- statusEvent(queueId, "command-accepted", nowMs, {
588
- workId: work.workId,
589
- commandId: command.commandId,
590
- recordSeq: record.recordSeq
591
- })
592
- ];
232
+ function stringArrayOrUndefined(value) {
233
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
593
234
  }
594
- function releaseWork(queueId, state, command, nowMs) {
595
- const checked = currentLease(queueId, state, command, nowMs);
596
- if ("issue" in checked) return [checked.issue];
597
- if ("events" in checked) return checked.events;
598
- const work = checked.work;
599
- work.state = "ready";
600
- work.leaseId = void 0;
601
- work.workerId = void 0;
602
- work.leaseExpiresAtMs = void 0;
603
- const record = appendRecord(state, queueId, {
604
- kind: "work-released",
605
- workId: work.workId,
606
- commandId: command.commandId,
607
- leaseId: command.leaseId,
608
- attempt: command.attempt,
609
- workerId: command.workerId,
610
- releasedAtMs: nowMs,
611
- reason: command.reason,
612
- recordedAtMs: nowMs
613
- });
614
- return [
615
- recordEvent(record),
616
- statusEvent(queueId, "command-accepted", nowMs, {
617
- workId: work.workId,
618
- commandId: command.commandId,
619
- recordSeq: record.recordSeq
620
- })
621
- ];
235
+ function pullParams(pull) {
236
+ return typeof pull?.params === "object" && pull.params !== null && !Array.isArray(pull.params) ? pull.params : {};
622
237
  }
623
- function completeWork(queueId, state, command, nowMs) {
624
- const checked = currentLease(queueId, state, command, nowMs);
625
- if ("issue" in checked) return [checked.issue];
626
- if ("events" in checked) return checked.events;
627
- const work = checked.work;
628
- work.state = "completed";
629
- clearLease(work);
630
- work.terminalRecordSeq = state.recordSeq + 2;
631
- const attempt = appendRecord(state, queueId, {
632
- kind: "attempt-completed",
633
- workId: work.workId,
634
- commandId: command.commandId,
635
- leaseId: command.leaseId,
636
- attempt: command.attempt,
637
- workerId: command.workerId,
638
- result: command.result,
639
- recordedAtMs: nowMs
640
- });
641
- const done = appendRecord(state, queueId, {
642
- kind: "work-completed",
643
- workId: work.workId,
644
- commandId: command.commandId,
645
- leaseId: command.leaseId,
646
- attempt: command.attempt,
647
- workerId: command.workerId,
648
- result: command.result,
649
- recordedAtMs: nowMs
650
- });
651
- return [
652
- recordEvent(attempt),
653
- recordEvent(done),
654
- statusEvent(queueId, "command-accepted", nowMs, {
655
- workId: work.workId,
656
- commandId: command.commandId,
657
- recordSeq: done.recordSeq
658
- })
659
- ];
238
+ var SUBMIT_ENVELOPE = "__graphreflyWorkQueueSubmit";
239
+ function submitPayload(payload, meta) {
240
+ if (Object.keys(meta).length === 0) return payload;
241
+ return { [SUBMIT_ENVELOPE]: true, payload, ...meta };
660
242
  }
661
- function failWork(queueId, state, command, nowMs, retry) {
662
- const checked = currentLease(queueId, state, command, nowMs);
663
- if ("issue" in checked) return [checked.issue];
664
- if ("events" in checked) return checked.events;
665
- const work = checked.work;
666
- const failed = appendRecord(state, queueId, {
667
- kind: "attempt-failed",
668
- workId: work.workId,
669
- commandId: command.commandId,
670
- leaseId: command.leaseId,
671
- attempt: command.attempt,
672
- workerId: command.workerId,
673
- error: command.error,
674
- retryable: command.retryable,
675
- recordedAtMs: nowMs
676
- });
677
- const maxAttempts = retry.maxAttempts ?? 3;
678
- if (command.retryable === false || work.attempt >= maxAttempts) {
679
- work.state = "dead-lettered";
680
- clearLease(work);
681
- work.retryAtMs = void 0;
682
- const dead = appendRecord(state, queueId, {
683
- kind: "work-dead-lettered",
684
- workId: work.workId,
685
- commandId: command.commandId,
686
- reason: command.retryable === false ? "non-retryable" : "attempts-exhausted",
687
- exhaustedAttempts: work.attempt,
688
- recordedAtMs: nowMs
689
- });
690
- state.deadLetters.push(dead);
691
- return [
692
- recordEvent(failed),
693
- recordEvent(dead),
694
- statusEvent(queueId, "command-accepted", nowMs, {
695
- workId: work.workId,
696
- commandId: command.commandId,
697
- recordSeq: dead.recordSeq
698
- })
699
- ];
243
+ function decodeSubmittedPayload(payload) {
244
+ if (isObjectRecord(payload) && payload[SUBMIT_ENVELOPE] === true && Object.hasOwn(payload, "payload")) {
245
+ const { payload: inner, [SUBMIT_ENVELOPE]: _tag, ...meta } = payload;
246
+ return { payload: inner, meta };
700
247
  }
701
- const delayMs = retry.delayMs ?? 0;
702
- work.state = delayMs > 0 ? "retry-wait" : "ready";
703
- work.retryAtMs = nowMs + delayMs;
704
- clearLease(work);
705
- const retryRecord = appendRecord(state, queueId, {
706
- kind: "retry-scheduled",
707
- workId: work.workId,
708
- commandId: command.commandId,
709
- retryAtMs: work.retryAtMs,
710
- delayMs,
711
- reason: "retry-policy",
712
- recordedAtMs: nowMs
713
- });
714
- return [
715
- recordEvent(failed),
716
- recordEvent(retryRecord),
717
- statusEvent(queueId, "command-accepted", nowMs, {
718
- workId: work.workId,
719
- commandId: command.commandId,
720
- recordSeq: retryRecord.recordSeq
721
- })
722
- ];
248
+ return { payload, meta: payloadAsRecord(payload) };
723
249
  }
724
- function cancelWork(queueId, state, command, nowMs) {
725
- const work = state.works.get(command.workId);
726
- if (work === void 0)
727
- return [issueEvent(queueId, "unknown-work", "unknown work", nowMs, command)];
728
- if (isTerminal(work.state))
729
- return [issueEvent(queueId, "terminal-work", "work is terminal", nowMs, command)];
730
- work.state = "canceled";
731
- const canceledLeaseId = work.leaseId;
732
- const attempt = work.attempt || void 0;
733
- const record = appendRecord(state, queueId, {
734
- kind: "work-canceled",
735
- workId: work.workId,
736
- commandId: command.commandId,
737
- reason: command.reason,
738
- canceledAtMs: nowMs,
739
- canceledLeaseId,
740
- attempt,
741
- recordedAtMs: nowMs
742
- });
743
- clearLease(work);
744
- return [
745
- recordEvent(record),
746
- statusEvent(queueId, "command-accepted", nowMs, {
747
- workId: work.workId,
748
- commandId: command.commandId,
749
- recordSeq: record.recordSeq
750
- })
751
- ];
250
+ function commandNowMs(command, fallback) {
251
+ return typeof command.nowMs === "number" && Number.isFinite(command.nowMs) ? command.nowMs : fallback;
752
252
  }
753
- function scheduleWork(queueId, state, command, nowMs) {
754
- const work = state.works.get(command.workId);
755
- if (work === void 0)
756
- return [issueEvent(queueId, "unknown-work", "unknown work", nowMs, command)];
757
- if (isTerminal(work.state))
758
- return [issueEvent(queueId, "terminal-work", "work is terminal", nowMs, command)];
759
- work.state = "scheduled";
760
- work.notBeforeMs = command.notBeforeMs;
761
- work.deadlineMs = command.deadlineMs;
253
+ function positiveLimit(limit) {
254
+ if (!Number.isInteger(limit) || limit < 1)
255
+ throw new Error("workQueue: limit must be a positive integer");
256
+ return limit;
257
+ }
258
+ function assertNonEmpty(value, owner) {
259
+ if (typeof value !== "string" || value.length === 0)
260
+ throw new Error(`${owner} must be a non-empty string`);
261
+ }
262
+ function materializeLeaseExpired(queueId, state, work, commandId, nowMs, rejection) {
762
263
  const record = appendRecord(state, queueId, {
763
- kind: "work-scheduled",
264
+ kind: "lease-expired",
764
265
  workId: work.workId,
765
- commandId: command.commandId,
766
- scheduleId: command.scheduleId,
767
- scheduleAtMs: command.scheduleAtMs,
768
- runAtMs: command.runAtMs,
769
- notBeforeMs: command.notBeforeMs,
770
- deadlineMs: command.deadlineMs,
771
- reason: command.reason,
266
+ ...commandId === void 0 ? {} : { commandId },
267
+ leaseId: work.leaseId,
268
+ attempt: work.attempt,
269
+ workerId: work.workerId,
270
+ leaseExpiresAtMs: work.leaseExpiresAtMs,
271
+ expiredAtMs: nowMs,
772
272
  recordedAtMs: nowMs
773
273
  });
774
- return [
775
- recordEvent(record),
776
- statusEvent(queueId, "command-accepted", nowMs, {
777
- workId: work.workId,
778
- commandId: command.commandId,
779
- recordSeq: record.recordSeq
780
- })
781
- ];
782
- }
783
- function expireLeases(queueId, state, command, nowMs) {
784
- const workIds = new Set(command.workIds ?? []);
785
- const limit = positiveLimit(command.limit ?? Number.MAX_SAFE_INTEGER);
786
- const expired = [...state.works.values()].filter(
787
- (work) => work.state === "leased" && work.leaseExpiresAtMs !== void 0 && work.leaseExpiresAtMs <= nowMs
788
- ).filter((work) => workIds.size === 0 || workIds.has(work.workId)).slice(0, limit);
789
- if (expired.length === 0)
790
- return [statusEvent(queueId, "maintenance-noop", nowMs, { commandId: command.commandId })];
791
- const events = [];
792
- for (const work of expired) {
793
- events.push(...materializeLeaseExpired(queueId, state, work, command.commandId, nowMs));
274
+ work.state = "ready";
275
+ clearLease(work);
276
+ const events = [recordEvent(record)];
277
+ if (rejection !== void 0) {
278
+ events.push(
279
+ ...rejectQueueCommand(
280
+ queueId,
281
+ rejection.command,
282
+ rejection.code,
283
+ rejection.message,
284
+ nowMs,
285
+ commandId
286
+ )
287
+ );
794
288
  }
795
- events.push(
796
- statusEvent(queueId, "maintenance-applied", nowMs, {
797
- commandId: command.commandId,
798
- details: { expired: expired.length }
799
- })
800
- );
801
289
  return events;
802
290
  }
803
- function currentLease(queueId, state, command, nowMs) {
804
- const work = state.works.get(command.workId);
805
- if (work === void 0)
806
- return { issue: issueEvent(queueId, "unknown-work", "unknown work", nowMs, command) };
807
- if (isTerminal(work.state))
808
- return { issue: issueEvent(queueId, "terminal-work", "work is terminal", nowMs, command) };
809
- if (work.state !== "leased")
810
- return {
811
- issue: issueEvent(queueId, "lease-not-current", "work is not leased", nowMs, command)
812
- };
813
- if (work.leaseId !== command.leaseId)
814
- return { issue: issueEvent(queueId, "stale-lease", "lease is not current", nowMs, command) };
815
- if (work.attempt !== command.attempt)
816
- return { issue: issueEvent(queueId, "attempt-mismatch", "attempt mismatch", nowMs, command) };
817
- if (work.workerId !== command.workerId)
818
- return { issue: issueEvent(queueId, "worker-mismatch", "worker mismatch", nowMs, command) };
819
- if (isExpiredLease(work, nowMs))
820
- return {
821
- events: materializeLeaseExpired(queueId, state, work, command.commandId, nowMs, {
822
- command,
823
- code: "lease-expired",
824
- message: "lease expired"
825
- })
826
- };
827
- return { work };
291
+ function isExpiredLease(work, nowMs) {
292
+ return work.state === "leased" && work.leaseExpiresAtMs !== void 0 && work.leaseExpiresAtMs <= nowMs;
293
+ }
294
+ function clearLease(work) {
295
+ work.leaseId = void 0;
296
+ work.workerId = void 0;
297
+ work.leaseExpiresAtMs = void 0;
828
298
  }
299
+
300
+ // src/work-queue/projections.ts
829
301
  function availablePage(state, params) {
830
302
  const limit = positiveLimit(params.limit ?? 100);
831
303
  const orderByWorkId = params.afterWorkId !== void 0 && params.afterAdmissionSeq === void 0;
@@ -869,261 +341,793 @@ function workSnapshot(state, workId) {
869
341
  leaseExpiresAtMs: work.leaseExpiresAtMs
870
342
  }
871
343
  },
872
- records: state.records.filter((record) => record.workId === workId),
873
- asOfRecordSeq: state.recordSeq
344
+ records: state.records.filter((record) => record.workId === workId),
345
+ asOfRecordSeq: state.recordSeq
346
+ };
347
+ }
348
+ function deadLetterPage(state, params) {
349
+ const limit = positiveLimit(params.limit ?? 100);
350
+ const entries = state.deadLetters.filter((record) => {
351
+ if (params.afterDeadLetterSeq !== void 0 && record.recordSeq <= params.afterDeadLetterSeq)
352
+ return false;
353
+ if (params.afterWorkId !== void 0 && (record.workId ?? "") <= params.afterWorkId)
354
+ return false;
355
+ return true;
356
+ });
357
+ const page = entries.slice(0, limit);
358
+ return {
359
+ entries: page,
360
+ ...entries.length > limit && page.length > 0 ? { nextAfterDeadLetterSeq: page[page.length - 1]?.recordSeq } : {},
361
+ hasMore: entries.length > limit,
362
+ asOfRecordSeq: state.recordSeq
363
+ };
364
+ }
365
+
366
+ // src/work-queue/index.ts
367
+ function workQueue(graph, opts) {
368
+ assertNonEmpty(opts.queueId, "workQueue.queueId");
369
+ assertNonEmpty(opts.topic, "workQueue.topic");
370
+ assertNonEmpty(opts.subscriptionId, "workQueue.subscriptionId");
371
+ const name = opts.name ?? `workQueue/${opts.queueId}`;
372
+ const now = opts.now ?? Date.now;
373
+ const leaseDurationMs = opts.leaseDurationMs ?? 3e4;
374
+ const retry = opts.retry ?? { maxAttempts: 3, delayMs: 0 };
375
+ const admission = opts.bus.subscription({
376
+ topic: opts.topic,
377
+ subscriptionId: opts.subscriptionId,
378
+ from: opts.from ?? "earliest",
379
+ name: `${name}/admission`
380
+ });
381
+ const commands = graph.node(
382
+ [],
383
+ (ctx) => {
384
+ for (const command of depBatch(ctx, 0) ?? []) ctx.down([["DATA", command]]);
385
+ },
386
+ {
387
+ name: `${name}/commands`,
388
+ factory: "workQueueCommands",
389
+ completeWhenDepsComplete: false,
390
+ errorWhenDepsError: false
391
+ }
392
+ );
393
+ const state = {
394
+ recordSeq: 0,
395
+ leaseSeq: 0,
396
+ works: /* @__PURE__ */ new Map(),
397
+ sourceSeqs: /* @__PURE__ */ new Set(),
398
+ commandIds: /* @__PURE__ */ new Set(),
399
+ idempotencyKeys: /* @__PURE__ */ new Set(),
400
+ records: [],
401
+ deadLetters: []
402
+ };
403
+ const admissionKick = graph.node([], null, {
404
+ name: `${name}/admissionKick`,
405
+ factory: "workQueueAdmissionKick",
406
+ initial: "poll",
407
+ completeWhenDepsComplete: false,
408
+ errorWhenDepsError: false
409
+ });
410
+ const admissionPages = graph.node(
411
+ [admission.available, admissionKick, opts.bus.messages, opts.bus.status],
412
+ (ctx) => {
413
+ for (const page of depBatch(ctx, 0) ?? []) {
414
+ ctx.down([["DATA", page]]);
415
+ }
416
+ let shouldPull = (depBatch(ctx, 1)?.length ?? 0) > 0;
417
+ for (const message of depBatch(ctx, 2) ?? []) {
418
+ if (message.topic === opts.topic) shouldPull = true;
419
+ }
420
+ for (const status2 of depBatch(ctx, 3) ?? []) {
421
+ if (shouldPollAdmission(status2, opts.topic, opts.subscriptionId)) {
422
+ shouldPull = true;
423
+ }
424
+ }
425
+ if (shouldPull) {
426
+ ctx.upNext([["PULL", { pullId: admission.availablePullId, params: { limit: 100 } }]], 0);
427
+ }
428
+ },
429
+ {
430
+ name: `${name}/admissionPages`,
431
+ factory: "workQueueAdmissionPages",
432
+ partial: true,
433
+ completeWhenDepsComplete: false,
434
+ errorWhenDepsError: false
435
+ }
436
+ );
437
+ const runtime = graph.node(
438
+ [commands, admissionPages],
439
+ (ctx) => {
440
+ for (const page of depBatch(ctx, 1) ?? []) {
441
+ for (const msg of page.messages) {
442
+ for (const event of admitMessage(opts, state, msg, now())) ctx.down([["DATA", event]]);
443
+ }
444
+ }
445
+ for (const command of depBatch(ctx, 0) ?? []) {
446
+ const commandTime = commandNowMs(command, now());
447
+ for (const event of reduceQueueCommand(
448
+ opts,
449
+ state,
450
+ command,
451
+ commandTime,
452
+ leaseDurationMs,
453
+ retry
454
+ )) {
455
+ ctx.down([["DATA", event]]);
456
+ }
457
+ }
458
+ },
459
+ {
460
+ name: `${name}/runtime`,
461
+ factory: "workQueueRuntime",
462
+ partial: true,
463
+ completeWhenDepsComplete: false,
464
+ errorWhenDepsError: false
465
+ }
466
+ );
467
+ const records = graph.node(
468
+ [runtime],
469
+ (ctx) => {
470
+ for (const event of depBatch(ctx, 0) ?? []) {
471
+ const typed = event;
472
+ if (typed.kind === "record") ctx.down([["DATA", typed.record]]);
473
+ }
474
+ },
475
+ {
476
+ name: `${name}/records`,
477
+ factory: "workQueueRecords",
478
+ completeWhenDepsComplete: false,
479
+ errorWhenDepsError: false
480
+ }
481
+ );
482
+ const status = graph.node(
483
+ [runtime],
484
+ (ctx) => {
485
+ for (const event of depBatch(ctx, 0) ?? []) {
486
+ const typed = event;
487
+ if (typed.kind === "status") ctx.down([["DATA", typed.status]]);
488
+ }
489
+ },
490
+ {
491
+ name: `${name}/status`,
492
+ factory: "workQueueStatus",
493
+ completeWhenDepsComplete: false,
494
+ errorWhenDepsError: false
495
+ }
496
+ );
497
+ const issues = graph.node(
498
+ [runtime],
499
+ (ctx) => {
500
+ for (const event of depBatch(ctx, 0) ?? []) {
501
+ const typed = event;
502
+ if (typed.kind === "issue") ctx.down([["DATA", typed.issue]]);
503
+ }
504
+ },
505
+ {
506
+ name: `${name}/issues`,
507
+ factory: "workQueueIssues",
508
+ completeWhenDepsComplete: false,
509
+ errorWhenDepsError: false
510
+ }
511
+ );
512
+ const admissionAckCommands = graph.node(
513
+ [records],
514
+ (ctx) => {
515
+ for (const record of depBatch(ctx, 0) ?? []) {
516
+ const command = admissionAckCommand(record);
517
+ if (command !== void 0) ctx.down([["DATA", command]]);
518
+ }
519
+ },
520
+ {
521
+ name: `${name}/admissionAckCommands`,
522
+ factory: "workQueueAdmissionAckCommands",
523
+ completeWhenDepsComplete: false,
524
+ errorWhenDepsError: false
525
+ }
526
+ );
527
+ attachMessageBusDeferredCommandSink(graph, opts.bus, admissionAckCommands);
528
+ graph.retain(runtime, { reason: `${name}.workQueue.runtime` });
529
+ const queue = {
530
+ commands,
531
+ records,
532
+ status,
533
+ issues,
534
+ submit(payload, submitOpts = {}) {
535
+ const command = opts.bus.publish(
536
+ opts.topic,
537
+ submitPayload(payload, {
538
+ ...submitOpts.workId === void 0 ? {} : { workId: submitOpts.workId },
539
+ ...submitOpts.priority === void 0 ? {} : { priority: submitOpts.priority },
540
+ ...submitOpts.tags === void 0 ? {} : { tags: submitOpts.tags },
541
+ ...submitOpts.requirements === void 0 ? {} : { requirements: submitOpts.requirements },
542
+ ...submitOpts.notBeforeMs === void 0 ? {} : { notBeforeMs: submitOpts.notBeforeMs },
543
+ ...submitOpts.deadlineMs === void 0 ? {} : { deadlineMs: submitOpts.deadlineMs }
544
+ }),
545
+ {
546
+ commandId: submitOpts.commandId ?? nextCommandId(opts.queueId, "submit"),
547
+ idempotencyKey: submitOpts.idempotencyKey
548
+ }
549
+ );
550
+ return command;
551
+ },
552
+ claim(claimOpts) {
553
+ return publishQueueCommand(commands, {
554
+ ...claimOpts,
555
+ kind: "claim",
556
+ commandId: claimOpts.commandId ?? nextCommandId(opts.queueId, "claim")
557
+ });
558
+ },
559
+ renewLease(renewOpts) {
560
+ return publishQueueCommand(commands, {
561
+ ...renewOpts,
562
+ kind: "renew-lease",
563
+ commandId: renewOpts.commandId ?? nextCommandId(opts.queueId, "renew")
564
+ });
565
+ },
566
+ release(releaseOpts) {
567
+ return publishQueueCommand(commands, {
568
+ ...releaseOpts,
569
+ kind: "release",
570
+ commandId: releaseOpts.commandId ?? nextCommandId(opts.queueId, "release")
571
+ });
572
+ },
573
+ complete(completeOpts) {
574
+ return publishQueueCommand(commands, {
575
+ ...completeOpts,
576
+ kind: "complete",
577
+ commandId: completeOpts.commandId ?? nextCommandId(opts.queueId, "complete")
578
+ });
579
+ },
580
+ fail(failOpts) {
581
+ return publishQueueCommand(commands, {
582
+ ...failOpts,
583
+ kind: "fail",
584
+ commandId: failOpts.commandId ?? nextCommandId(opts.queueId, "fail")
585
+ });
586
+ },
587
+ cancel(cancelOpts) {
588
+ return publishQueueCommand(commands, {
589
+ ...cancelOpts,
590
+ kind: "cancel",
591
+ commandId: cancelOpts.commandId ?? nextCommandId(opts.queueId, "cancel")
592
+ });
593
+ },
594
+ schedule(scheduleOpts) {
595
+ return publishQueueCommand(commands, {
596
+ ...scheduleOpts,
597
+ kind: "schedule",
598
+ commandId: scheduleOpts.commandId ?? nextCommandId(opts.queueId, "schedule")
599
+ });
600
+ },
601
+ expireLeases(expireOpts = {}) {
602
+ return publishQueueCommand(commands, {
603
+ ...expireOpts,
604
+ kind: "expire-leases",
605
+ commandId: expireOpts.commandId ?? nextCommandId(opts.queueId, "expire")
606
+ });
607
+ },
608
+ available(projectionOpts = {}) {
609
+ const availablePullId = /* @__PURE__ */ Symbol(`${name}/available`);
610
+ const available = graph.node(
611
+ [records],
612
+ (ctx) => {
613
+ const params = pullParams(ctx.pull);
614
+ ctx.down([["DATA", availablePage(state, params)]]);
615
+ },
616
+ {
617
+ name: projectionOpts.name ?? `${name}/available`,
618
+ factory: "workQueueAvailable",
619
+ pullId: availablePullId,
620
+ partial: true,
621
+ completeWhenDepsComplete: false,
622
+ errorWhenDepsError: false
623
+ }
624
+ );
625
+ return { available, availablePullId, status, issues };
626
+ },
627
+ work(workId, projectionOpts = {}) {
628
+ const snapshotPullId = /* @__PURE__ */ Symbol(`${name}/${workId}/snapshot`);
629
+ const snapshot = graph.node(
630
+ [records],
631
+ (ctx) => {
632
+ ctx.down([["DATA", workSnapshot(state, workId)]]);
633
+ },
634
+ {
635
+ name: projectionOpts.name ?? `${name}/${workId}`,
636
+ factory: "workQueueWorkSnapshot",
637
+ pullId: snapshotPullId,
638
+ partial: true,
639
+ completeWhenDepsComplete: false,
640
+ errorWhenDepsError: false
641
+ }
642
+ );
643
+ return { snapshot, snapshotPullId, status, issues };
644
+ },
645
+ deadLetter(projectionOpts = {}) {
646
+ const snapshotPullId = /* @__PURE__ */ Symbol(`${name}/deadLetter`);
647
+ const snapshot = graph.node(
648
+ [records],
649
+ (ctx) => {
650
+ const params = pullParams(ctx.pull);
651
+ ctx.down([["DATA", deadLetterPage(state, params)]]);
652
+ },
653
+ {
654
+ name: projectionOpts.name ?? `${name}/deadLetter`,
655
+ factory: "workQueueDeadLetter",
656
+ pullId: snapshotPullId,
657
+ partial: true,
658
+ completeWhenDepsComplete: false,
659
+ errorWhenDepsError: false
660
+ }
661
+ );
662
+ return { snapshot, snapshotPullId, status, issues };
663
+ }
874
664
  };
665
+ return queue;
875
666
  }
876
- function materializeLeaseExpired(queueId, state, work, commandId, nowMs, rejection) {
877
- const record = appendRecord(state, queueId, {
878
- kind: "lease-expired",
879
- workId: work.workId,
880
- ...commandId === void 0 ? {} : { commandId },
881
- leaseId: work.leaseId,
882
- attempt: work.attempt,
883
- workerId: work.workerId,
884
- leaseExpiresAtMs: work.leaseExpiresAtMs,
885
- expiredAtMs: nowMs,
886
- recordedAtMs: nowMs
887
- });
888
- work.state = "ready";
889
- clearLease(work);
890
- const events = [recordEvent(record)];
891
- if (rejection !== void 0) {
892
- events.push(
893
- ...rejectQueueCommand(
894
- queueId,
895
- rejection.command,
896
- rejection.code,
897
- rejection.message,
898
- nowMs,
899
- commandId
900
- )
901
- );
902
- }
903
- return events;
904
- }
905
- function isExpiredLease(work, nowMs) {
906
- return work.state === "leased" && work.leaseExpiresAtMs !== void 0 && work.leaseExpiresAtMs <= nowMs;
907
- }
908
- function clearLease(work) {
909
- work.leaseId = void 0;
910
- work.workerId = void 0;
911
- work.leaseExpiresAtMs = void 0;
667
+ function shouldPollAdmission(status, topic, subscriptionId) {
668
+ if (status.topic !== topic) return false;
669
+ if (status.kind === "message-published" || status.kind === "retention-trimmed") return true;
670
+ return status.subscriptionId === subscriptionId && (status.kind === "subscription-acked" || status.kind === "subscription-sought");
912
671
  }
913
- function deadLetterPage(state, params) {
914
- const limit = positiveLimit(params.limit ?? 100);
915
- const entries = state.deadLetters.filter((record) => {
916
- if (params.afterDeadLetterSeq !== void 0 && record.recordSeq <= params.afterDeadLetterSeq)
917
- return false;
918
- if (params.afterWorkId !== void 0 && (record.workId ?? "") <= params.afterWorkId)
919
- return false;
920
- return true;
921
- });
922
- const page = entries.slice(0, limit);
672
+ function admissionAckCommand(record) {
673
+ const messageBus = admissionMessageBus(record);
674
+ if (messageBus === void 0) return void 0;
923
675
  return {
924
- entries: page,
925
- ...entries.length > limit && page.length > 0 ? { nextAfterDeadLetterSeq: page[page.length - 1]?.recordSeq } : {},
926
- hasMore: entries.length > limit,
927
- asOfRecordSeq: state.recordSeq
676
+ kind: "ack",
677
+ topic: messageBus.topic,
678
+ subscriptionId: messageBus.subscriptionId,
679
+ seq: messageBus.seq,
680
+ commandId: `${record.queueId}:admission-ack:${messageBus.topic}:${messageBus.subscriptionId}:${messageBus.seq}`
928
681
  };
929
682
  }
930
- function appendRecord(state, queueId, record) {
931
- const full = { ...record, queueId, recordSeq: ++state.recordSeq };
932
- state.records.push(full);
933
- return full;
934
- }
935
- function recordEvent(record) {
936
- return { kind: "record", record };
937
- }
938
- function statusEvent(queueId, kind, timestampMs, fields = {}) {
939
- return {
940
- kind: "status",
941
- status: {
942
- kind,
943
- queueId,
944
- timestampMs,
945
- ...fields.workId === void 0 ? {} : { workId: fields.workId },
946
- ...fields.commandId === void 0 ? {} : { commandId: fields.commandId },
947
- ...fields.recordSeq === void 0 ? {} : { recordSeq: fields.recordSeq },
948
- ...fields.asOfRecordSeq === void 0 ? {} : { asOfRecordSeq: fields.asOfRecordSeq },
949
- ...fields.issueCode === void 0 ? {} : { issueCode: fields.issueCode },
950
- ...fields.details === void 0 ? {} : { details: fields.details }
951
- }
952
- };
683
+ function admissionMessageBus(record) {
684
+ return "messageBus" in record ? record.messageBus : void 0;
953
685
  }
954
- function issueEvent(queueId, code, message, timestampMs, details) {
955
- return {
956
- kind: "issue",
957
- issue: {
958
- kind: "issue",
959
- code,
960
- message,
961
- severity: "error",
962
- source: "workQueue",
963
- details,
964
- metadata: { queueId, timestampMs }
965
- }
686
+ function admitMessage(opts, state, message, nowMs) {
687
+ const source = `${message.topic}:${message.seq}`;
688
+ if (state.sourceSeqs.has(source)) return [];
689
+ state.sourceSeqs.add(source);
690
+ const submitted = decodeSubmittedPayload(message.payload);
691
+ const payload = submitted.payload;
692
+ const payloadRecord = payloadAsRecord(submitted.meta);
693
+ const workId = typeof payloadRecord.workId === "string" ? payloadRecord.workId : `${opts.queueId}:${message.topic}:${message.seq}`;
694
+ if (state.works.has(workId)) {
695
+ const record2 = appendRecord(state, opts.queueId, {
696
+ kind: "admission-deduped",
697
+ workId,
698
+ messageBus: { topic: message.topic, seq: message.seq, subscriptionId: opts.subscriptionId },
699
+ reason: "duplicate-work",
700
+ existingWorkId: workId,
701
+ recordedAtMs: nowMs
702
+ });
703
+ return [
704
+ recordEvent(record2),
705
+ statusEvent(opts.queueId, "admission-rejected", nowMs, {
706
+ workId,
707
+ recordSeq: record2.recordSeq,
708
+ issueCode: "duplicate-work"
709
+ })
710
+ ];
711
+ }
712
+ const work = {
713
+ workId,
714
+ payload,
715
+ state: typeof payloadRecord.notBeforeMs === "number" && payloadRecord.notBeforeMs > nowMs ? "scheduled" : "ready",
716
+ admissionSeq: message.seq,
717
+ priority: numberOrUndefined(payloadRecord.priority),
718
+ tags: stringArrayOrUndefined(payloadRecord.tags),
719
+ requirements: stringArrayOrUndefined(payloadRecord.requirements),
720
+ notBeforeMs: numberOrUndefined(payloadRecord.notBeforeMs),
721
+ deadlineMs: numberOrUndefined(payloadRecord.deadlineMs),
722
+ attempt: 0
966
723
  };
967
- }
968
- function rejectQueueCommand(queueId, command, code, message, nowMs, commandId = commandIdFrom(command)) {
724
+ state.works.set(workId, work);
725
+ const record = appendRecord(state, opts.queueId, {
726
+ kind: "work-admitted",
727
+ workId,
728
+ payload,
729
+ messageBus: { topic: message.topic, seq: message.seq, subscriptionId: opts.subscriptionId },
730
+ priority: work.priority,
731
+ tags: work.tags,
732
+ requirements: work.requirements,
733
+ notBeforeMs: work.notBeforeMs,
734
+ deadlineMs: work.deadlineMs,
735
+ recordedAtMs: nowMs
736
+ });
969
737
  return [
970
- issueEvent(queueId, code, message, nowMs, command),
971
- statusEvent(queueId, "command-rejected", nowMs, {
972
- ...commandId === void 0 ? {} : { commandId },
973
- issueCode: code
974
- })
738
+ recordEvent(record),
739
+ statusEvent(opts.queueId, "admission-accepted", nowMs, { workId, recordSeq: record.recordSeq })
975
740
  ];
976
741
  }
977
- function validateQueueCommand(queueId, command) {
978
- if (!isObjectRecord(command))
979
- return { code: "malformed-command", message: "command must be an object" };
980
- if (typeof command.kind !== "string")
981
- return { code: "malformed-command", message: "command kind is required" };
982
- if (!isWorkQueueCommandKind(command.kind))
983
- return { code: "malformed-command", message: `unknown command kind '${command.kind}'` };
984
- if (typeof command.commandId !== "string" || command.commandId.length === 0)
985
- return { code: "malformed-command", message: "commandId must be a non-empty string" };
986
- if (command.queueId !== void 0 && command.queueId !== queueId)
987
- return { code: "queue-mismatch", message: "command queueId does not match this queue" };
742
+ function reduceQueueCommand(opts, state, command, nowMs, leaseDurationMs, retry) {
743
+ const invalid = validateQueueCommand(opts.queueId, command);
744
+ if (invalid !== void 0)
745
+ return rejectQueueCommand(opts.queueId, command, invalid.code, invalid.message, nowMs);
746
+ if (state.commandIds.has(command.commandId)) {
747
+ return rejectQueueCommand(
748
+ opts.queueId,
749
+ command,
750
+ "duplicate-command",
751
+ "duplicate commandId",
752
+ nowMs
753
+ );
754
+ }
755
+ if (command.idempotencyKey !== void 0 && state.idempotencyKeys.has(command.idempotencyKey)) {
756
+ return rejectQueueCommand(
757
+ opts.queueId,
758
+ command,
759
+ "duplicate-command",
760
+ "duplicate idempotencyKey",
761
+ nowMs
762
+ );
763
+ }
764
+ state.commandIds.add(command.commandId);
765
+ if (command.idempotencyKey !== void 0) state.idempotencyKeys.add(command.idempotencyKey);
988
766
  switch (command.kind) {
989
- case "submit":
990
- return command.payload === void 0 ? { code: "malformed-command", message: "submit payload is required" } : void 0;
991
767
  case "claim":
992
- if (typeof command.workerId !== "string" || command.workerId.length === 0)
993
- return { code: "malformed-command", message: "claim workerId is required" };
994
- if (command.limit !== void 0 && !isPositiveInteger(command.limit))
995
- return { code: "malformed-command", message: "claim limit must be a positive integer" };
996
- if (command.requestedWorkIds !== void 0 && (!Array.isArray(command.requestedWorkIds) || !command.requestedWorkIds.every((id) => typeof id === "string" && id.length > 0))) {
997
- return { code: "malformed-command", message: "requestedWorkIds must be strings" };
998
- }
999
- if (command.leaseDurationMs !== void 0 && !isPositiveInteger(command.leaseDurationMs))
1000
- return {
1001
- code: "malformed-command",
1002
- message: "leaseDurationMs must be a positive integer"
1003
- };
1004
- return void 0;
768
+ return claimWork(opts.queueId, state, command, nowMs, leaseDurationMs);
1005
769
  case "renew-lease":
1006
- if (command.leaseDurationMs !== void 0 && !isPositiveInteger(command.leaseDurationMs))
1007
- return {
1008
- code: "malformed-command",
1009
- message: "leaseDurationMs must be a positive integer"
1010
- };
1011
- if (command.leaseExpiresAtMs !== void 0 && !isFiniteNumber(command.leaseExpiresAtMs))
1012
- return { code: "malformed-command", message: "leaseExpiresAtMs must be finite" };
1013
- return validateLeaseCommand(command);
770
+ return renewLease(opts.queueId, state, command, nowMs, leaseDurationMs);
1014
771
  case "release":
772
+ return releaseWork(opts.queueId, state, command, nowMs);
1015
773
  case "complete":
774
+ return completeWork(opts.queueId, state, command, nowMs);
1016
775
  case "fail":
1017
- return validateLeaseCommand(command);
776
+ return failWork(opts.queueId, state, command, nowMs, retry);
1018
777
  case "cancel":
1019
- return requiredString(command.workId, "workId");
1020
- case "schedule": {
1021
- const workIdError = requiredString(command.workId, "workId");
1022
- if (workIdError !== void 0) return workIdError;
1023
- if (!isFiniteNumber(command.notBeforeMs))
1024
- return { code: "malformed-command", message: "notBeforeMs must be finite" };
1025
- if (command.deadlineMs !== void 0 && !isFiniteNumber(command.deadlineMs))
1026
- return { code: "malformed-command", message: "deadlineMs must be finite" };
1027
- return void 0;
1028
- }
778
+ return cancelWork(opts.queueId, state, command, nowMs);
779
+ case "schedule":
780
+ return scheduleWork(opts.queueId, state, command, nowMs);
1029
781
  case "expire-leases":
1030
- if (command.limit !== void 0 && !isPositiveInteger(command.limit))
1031
- return {
1032
- code: "malformed-command",
1033
- message: "expire-leases limit must be a positive integer"
1034
- };
1035
- if (command.workIds !== void 0 && (!Array.isArray(command.workIds) || !command.workIds.every((id) => typeof id === "string" && id.length > 0))) {
1036
- return { code: "malformed-command", message: "workIds must be strings" };
1037
- }
1038
- return void 0;
782
+ return expireLeases(opts.queueId, state, command, nowMs);
783
+ case "submit":
784
+ return [
785
+ statusEvent(opts.queueId, "command-accepted", nowMs, {
786
+ commandId: command.commandId,
787
+ details: { submitUsesMessageBus: true }
788
+ })
789
+ ];
1039
790
  }
1040
791
  }
1041
- function validateLeaseCommand(command) {
1042
- for (const key of ["workId", "leaseId", "workerId"]) {
1043
- const err = requiredString(command[key], key);
1044
- if (err !== void 0) return err;
792
+ function claimWork(queueId, state, command, nowMs, defaultLeaseDurationMs) {
793
+ const limit = positiveLimit(command.limit ?? command.requestedWorkIds?.length ?? 1);
794
+ const requested = new Set(command.requestedWorkIds ?? []);
795
+ const events = [];
796
+ for (const work of [...state.works.values()].filter((entry) => requested.size === 0 ? true : requested.has(entry.workId)).filter((entry) => isExpiredLease(entry, nowMs))) {
797
+ events.push(...materializeLeaseExpired(queueId, state, work, command.commandId, nowMs));
1045
798
  }
1046
- if (!isPositiveInteger(command.attempt))
1047
- return { code: "malformed-command", message: "attempt must be a positive integer" };
1048
- return void 0;
1049
- }
1050
- function requiredString(value, name) {
1051
- return typeof value === "string" && value.length > 0 ? void 0 : { code: "malformed-command", message: `${name} must be a non-empty string` };
1052
- }
1053
- function commandIdFrom(command) {
1054
- return isObjectRecord(command) && typeof command.commandId === "string" ? command.commandId : void 0;
1055
- }
1056
- function isWorkQueueCommandKind(kind) {
1057
- return kind === "submit" || kind === "claim" || kind === "renew-lease" || kind === "release" || kind === "complete" || kind === "fail" || kind === "cancel" || kind === "schedule" || kind === "expire-leases";
1058
- }
1059
- function isObjectRecord(value) {
1060
- return typeof value === "object" && value !== null && !Array.isArray(value);
1061
- }
1062
- function isPositiveInteger(value) {
1063
- return Number.isInteger(value) && value > 0;
1064
- }
1065
- function isFiniteNumber(value) {
1066
- return typeof value === "number" && Number.isFinite(value);
1067
- }
1068
- function publishQueueCommand(node, command) {
1069
- node.down([["DATA", command]]);
1070
- return command;
1071
- }
1072
- var commandSeq = 0;
1073
- function nextCommandId(queueId, kind) {
1074
- commandSeq += 1;
1075
- return `${queueId}:${kind}:${commandSeq}`;
1076
- }
1077
- function isReady(work, nowMs) {
1078
- if (work.state === "scheduled")
1079
- return work.notBeforeMs !== void 0 && work.notBeforeMs <= nowMs;
1080
- if (work.state === "retry-wait") return work.retryAtMs !== void 0 && work.retryAtMs <= nowMs;
1081
- return work.state === "ready";
799
+ const candidates = [...state.works.values()].filter((work) => requested.size === 0 ? true : requested.has(work.workId)).filter((work) => isReady(work, nowMs)).sort((a, b) => a.admissionSeq - b.admissionSeq).slice(0, limit);
800
+ if (candidates.length === 0)
801
+ return [
802
+ ...events,
803
+ ...requested.size === 0 ? rejectQueueCommand(queueId, command, "not-ready", "no ready work", nowMs) : claimMissEvents(queueId, state, command, requested, /* @__PURE__ */ new Set(), nowMs)
804
+ ];
805
+ const claimed = /* @__PURE__ */ new Set();
806
+ for (const work of candidates) {
807
+ claimed.add(work.workId);
808
+ work.state = "leased";
809
+ work.attempt += 1;
810
+ work.leaseId = `${work.workId}:lease:${++state.leaseSeq}`;
811
+ work.workerId = command.workerId;
812
+ work.leaseExpiresAtMs = nowMs + (command.leaseDurationMs ?? defaultLeaseDurationMs);
813
+ const record = appendRecord(state, queueId, {
814
+ kind: "work-claimed",
815
+ workId: work.workId,
816
+ commandId: command.commandId,
817
+ leaseId: work.leaseId,
818
+ attempt: work.attempt,
819
+ workerId: command.workerId,
820
+ claimedAtMs: nowMs,
821
+ leaseExpiresAtMs: work.leaseExpiresAtMs,
822
+ recordedAtMs: nowMs
823
+ });
824
+ events.push(
825
+ recordEvent(record),
826
+ statusEvent(queueId, "command-accepted", nowMs, {
827
+ workId: work.workId,
828
+ commandId: command.commandId,
829
+ recordSeq: record.recordSeq
830
+ })
831
+ );
832
+ }
833
+ if (requested.size > 0) {
834
+ events.push(...claimMissEvents(queueId, state, command, requested, claimed, nowMs));
835
+ }
836
+ return events;
1082
837
  }
1083
- function isReadyForProjection(work, nowMs) {
1084
- if (work.state === "scheduled" || work.state === "retry-wait") {
1085
- return nowMs === void 0 ? false : isReady(work, nowMs);
838
+ function claimMissEvents(queueId, state, command, requested, claimed, nowMs) {
839
+ const events = [];
840
+ for (const workId of requested) {
841
+ if (claimed.has(workId)) continue;
842
+ const work = state.works.get(workId);
843
+ if (work === void 0) {
844
+ events.push(
845
+ ...rejectQueueCommand(
846
+ queueId,
847
+ command,
848
+ "unknown-work",
849
+ `unknown work '${workId}'`,
850
+ nowMs
851
+ )
852
+ );
853
+ continue;
854
+ }
855
+ if (isReady(work, nowMs)) continue;
856
+ const code = isTerminal(work.state) ? "terminal-work" : work.state === "leased" ? "already-leased" : "not-ready";
857
+ events.push(
858
+ ...rejectQueueCommand(
859
+ queueId,
860
+ command,
861
+ code,
862
+ `requested work '${workId}' is not claimable`,
863
+ nowMs
864
+ )
865
+ );
1086
866
  }
1087
- return isReady(work, 0);
867
+ return events;
1088
868
  }
1089
- function isTerminal(state) {
1090
- return state === "completed" || state === "canceled" || state === "dead-lettered";
869
+ function renewLease(queueId, state, command, nowMs, defaultLeaseDurationMs) {
870
+ const checked = currentLease(queueId, state, command, nowMs);
871
+ if ("issue" in checked) return [checked.issue];
872
+ if ("events" in checked) return checked.events;
873
+ const work = checked.work;
874
+ const previous = work.leaseExpiresAtMs;
875
+ work.leaseExpiresAtMs = command.leaseExpiresAtMs ?? nowMs + (command.leaseDurationMs ?? defaultLeaseDurationMs);
876
+ const record = appendRecord(state, queueId, {
877
+ kind: "lease-renewed",
878
+ workId: work.workId,
879
+ commandId: command.commandId,
880
+ leaseId: command.leaseId,
881
+ attempt: command.attempt,
882
+ workerId: command.workerId,
883
+ previousLeaseExpiresAtMs: previous,
884
+ leaseExpiresAtMs: work.leaseExpiresAtMs,
885
+ renewedAtMs: nowMs,
886
+ recordedAtMs: nowMs
887
+ });
888
+ return [
889
+ recordEvent(record),
890
+ statusEvent(queueId, "command-accepted", nowMs, {
891
+ workId: work.workId,
892
+ commandId: command.commandId,
893
+ recordSeq: record.recordSeq
894
+ })
895
+ ];
1091
896
  }
1092
- function payloadAsRecord(value) {
1093
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
897
+ function releaseWork(queueId, state, command, nowMs) {
898
+ const checked = currentLease(queueId, state, command, nowMs);
899
+ if ("issue" in checked) return [checked.issue];
900
+ if ("events" in checked) return checked.events;
901
+ const work = checked.work;
902
+ work.state = "ready";
903
+ work.leaseId = void 0;
904
+ work.workerId = void 0;
905
+ work.leaseExpiresAtMs = void 0;
906
+ const record = appendRecord(state, queueId, {
907
+ kind: "work-released",
908
+ workId: work.workId,
909
+ commandId: command.commandId,
910
+ leaseId: command.leaseId,
911
+ attempt: command.attempt,
912
+ workerId: command.workerId,
913
+ releasedAtMs: nowMs,
914
+ reason: command.reason,
915
+ recordedAtMs: nowMs
916
+ });
917
+ return [
918
+ recordEvent(record),
919
+ statusEvent(queueId, "command-accepted", nowMs, {
920
+ workId: work.workId,
921
+ commandId: command.commandId,
922
+ recordSeq: record.recordSeq
923
+ })
924
+ ];
1094
925
  }
1095
- function numberOrUndefined(value) {
1096
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
926
+ function completeWork(queueId, state, command, nowMs) {
927
+ const checked = currentLease(queueId, state, command, nowMs);
928
+ if ("issue" in checked) return [checked.issue];
929
+ if ("events" in checked) return checked.events;
930
+ const work = checked.work;
931
+ work.state = "completed";
932
+ clearLease(work);
933
+ work.terminalRecordSeq = state.recordSeq + 2;
934
+ const attempt = appendRecord(state, queueId, {
935
+ kind: "attempt-completed",
936
+ workId: work.workId,
937
+ commandId: command.commandId,
938
+ leaseId: command.leaseId,
939
+ attempt: command.attempt,
940
+ workerId: command.workerId,
941
+ result: command.result,
942
+ recordedAtMs: nowMs
943
+ });
944
+ const done = appendRecord(state, queueId, {
945
+ kind: "work-completed",
946
+ workId: work.workId,
947
+ commandId: command.commandId,
948
+ leaseId: command.leaseId,
949
+ attempt: command.attempt,
950
+ workerId: command.workerId,
951
+ result: command.result,
952
+ recordedAtMs: nowMs
953
+ });
954
+ return [
955
+ recordEvent(attempt),
956
+ recordEvent(done),
957
+ statusEvent(queueId, "command-accepted", nowMs, {
958
+ workId: work.workId,
959
+ commandId: command.commandId,
960
+ recordSeq: done.recordSeq
961
+ })
962
+ ];
1097
963
  }
1098
- function stringArrayOrUndefined(value) {
1099
- return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
964
+ function failWork(queueId, state, command, nowMs, retry) {
965
+ const checked = currentLease(queueId, state, command, nowMs);
966
+ if ("issue" in checked) return [checked.issue];
967
+ if ("events" in checked) return checked.events;
968
+ const work = checked.work;
969
+ const failed = appendRecord(state, queueId, {
970
+ kind: "attempt-failed",
971
+ workId: work.workId,
972
+ commandId: command.commandId,
973
+ leaseId: command.leaseId,
974
+ attempt: command.attempt,
975
+ workerId: command.workerId,
976
+ error: command.error,
977
+ retryable: command.retryable,
978
+ recordedAtMs: nowMs
979
+ });
980
+ const maxAttempts = retry.maxAttempts ?? 3;
981
+ if (command.retryable === false || work.attempt >= maxAttempts) {
982
+ work.state = "dead-lettered";
983
+ clearLease(work);
984
+ work.retryAtMs = void 0;
985
+ const dead = appendRecord(state, queueId, {
986
+ kind: "work-dead-lettered",
987
+ workId: work.workId,
988
+ commandId: command.commandId,
989
+ reason: command.retryable === false ? "non-retryable" : "attempts-exhausted",
990
+ exhaustedAttempts: work.attempt,
991
+ recordedAtMs: nowMs
992
+ });
993
+ state.deadLetters.push(dead);
994
+ return [
995
+ recordEvent(failed),
996
+ recordEvent(dead),
997
+ statusEvent(queueId, "command-accepted", nowMs, {
998
+ workId: work.workId,
999
+ commandId: command.commandId,
1000
+ recordSeq: dead.recordSeq
1001
+ })
1002
+ ];
1003
+ }
1004
+ const delayMs = retry.delayMs ?? 0;
1005
+ work.state = delayMs > 0 ? "retry-wait" : "ready";
1006
+ work.retryAtMs = nowMs + delayMs;
1007
+ clearLease(work);
1008
+ const retryRecord = appendRecord(state, queueId, {
1009
+ kind: "retry-scheduled",
1010
+ workId: work.workId,
1011
+ commandId: command.commandId,
1012
+ retryAtMs: work.retryAtMs,
1013
+ delayMs,
1014
+ reason: "retry-policy",
1015
+ recordedAtMs: nowMs
1016
+ });
1017
+ return [
1018
+ recordEvent(failed),
1019
+ recordEvent(retryRecord),
1020
+ statusEvent(queueId, "command-accepted", nowMs, {
1021
+ workId: work.workId,
1022
+ commandId: command.commandId,
1023
+ recordSeq: retryRecord.recordSeq
1024
+ })
1025
+ ];
1100
1026
  }
1101
- function pullParams(pull) {
1102
- return typeof pull?.params === "object" && pull.params !== null && !Array.isArray(pull.params) ? pull.params : {};
1027
+ function cancelWork(queueId, state, command, nowMs) {
1028
+ const work = state.works.get(command.workId);
1029
+ if (work === void 0)
1030
+ return [issueEvent(queueId, "unknown-work", "unknown work", nowMs, command)];
1031
+ if (isTerminal(work.state))
1032
+ return [issueEvent(queueId, "terminal-work", "work is terminal", nowMs, command)];
1033
+ work.state = "canceled";
1034
+ const canceledLeaseId = work.leaseId;
1035
+ const attempt = work.attempt || void 0;
1036
+ const record = appendRecord(state, queueId, {
1037
+ kind: "work-canceled",
1038
+ workId: work.workId,
1039
+ commandId: command.commandId,
1040
+ reason: command.reason,
1041
+ canceledAtMs: nowMs,
1042
+ canceledLeaseId,
1043
+ attempt,
1044
+ recordedAtMs: nowMs
1045
+ });
1046
+ clearLease(work);
1047
+ return [
1048
+ recordEvent(record),
1049
+ statusEvent(queueId, "command-accepted", nowMs, {
1050
+ workId: work.workId,
1051
+ commandId: command.commandId,
1052
+ recordSeq: record.recordSeq
1053
+ })
1054
+ ];
1103
1055
  }
1104
- var SUBMIT_ENVELOPE = "__graphreflyWorkQueueSubmit";
1105
- function submitPayload(payload, meta) {
1106
- if (Object.keys(meta).length === 0) return payload;
1107
- return { [SUBMIT_ENVELOPE]: true, payload, ...meta };
1056
+ function scheduleWork(queueId, state, command, nowMs) {
1057
+ const work = state.works.get(command.workId);
1058
+ if (work === void 0)
1059
+ return [issueEvent(queueId, "unknown-work", "unknown work", nowMs, command)];
1060
+ if (isTerminal(work.state))
1061
+ return [issueEvent(queueId, "terminal-work", "work is terminal", nowMs, command)];
1062
+ work.state = "scheduled";
1063
+ work.notBeforeMs = command.notBeforeMs;
1064
+ work.deadlineMs = command.deadlineMs;
1065
+ const record = appendRecord(state, queueId, {
1066
+ kind: "work-scheduled",
1067
+ workId: work.workId,
1068
+ commandId: command.commandId,
1069
+ scheduleId: command.scheduleId,
1070
+ scheduleAtMs: command.scheduleAtMs,
1071
+ runAtMs: command.runAtMs,
1072
+ notBeforeMs: command.notBeforeMs,
1073
+ deadlineMs: command.deadlineMs,
1074
+ reason: command.reason,
1075
+ recordedAtMs: nowMs
1076
+ });
1077
+ return [
1078
+ recordEvent(record),
1079
+ statusEvent(queueId, "command-accepted", nowMs, {
1080
+ workId: work.workId,
1081
+ commandId: command.commandId,
1082
+ recordSeq: record.recordSeq
1083
+ })
1084
+ ];
1108
1085
  }
1109
- function decodeSubmittedPayload(payload) {
1110
- if (isObjectRecord(payload) && payload[SUBMIT_ENVELOPE] === true && Object.hasOwn(payload, "payload")) {
1111
- const { payload: inner, [SUBMIT_ENVELOPE]: _tag, ...meta } = payload;
1112
- return { payload: inner, meta };
1086
+ function expireLeases(queueId, state, command, nowMs) {
1087
+ const workIds = new Set(command.workIds ?? []);
1088
+ const limit = positiveLimit(command.limit ?? Number.MAX_SAFE_INTEGER);
1089
+ const expired = [...state.works.values()].filter(
1090
+ (work) => work.state === "leased" && work.leaseExpiresAtMs !== void 0 && work.leaseExpiresAtMs <= nowMs
1091
+ ).filter((work) => workIds.size === 0 || workIds.has(work.workId)).slice(0, limit);
1092
+ if (expired.length === 0)
1093
+ return [statusEvent(queueId, "maintenance-noop", nowMs, { commandId: command.commandId })];
1094
+ const events = [];
1095
+ for (const work of expired) {
1096
+ events.push(...materializeLeaseExpired(queueId, state, work, command.commandId, nowMs));
1113
1097
  }
1114
- return { payload, meta: payloadAsRecord(payload) };
1115
- }
1116
- function commandNowMs(command, fallback) {
1117
- return typeof command.nowMs === "number" && Number.isFinite(command.nowMs) ? command.nowMs : fallback;
1118
- }
1119
- function positiveLimit(limit) {
1120
- if (!Number.isInteger(limit) || limit < 1)
1121
- throw new Error("workQueue: limit must be a positive integer");
1122
- return limit;
1098
+ events.push(
1099
+ statusEvent(queueId, "maintenance-applied", nowMs, {
1100
+ commandId: command.commandId,
1101
+ details: { expired: expired.length }
1102
+ })
1103
+ );
1104
+ return events;
1123
1105
  }
1124
- function assertNonEmpty(value, owner) {
1125
- if (typeof value !== "string" || value.length === 0)
1126
- throw new Error(`${owner} must be a non-empty string`);
1106
+ function currentLease(queueId, state, command, nowMs) {
1107
+ const work = state.works.get(command.workId);
1108
+ if (work === void 0)
1109
+ return { issue: issueEvent(queueId, "unknown-work", "unknown work", nowMs, command) };
1110
+ if (isTerminal(work.state))
1111
+ return { issue: issueEvent(queueId, "terminal-work", "work is terminal", nowMs, command) };
1112
+ if (work.state !== "leased")
1113
+ return {
1114
+ issue: issueEvent(queueId, "lease-not-current", "work is not leased", nowMs, command)
1115
+ };
1116
+ if (work.leaseId !== command.leaseId)
1117
+ return { issue: issueEvent(queueId, "stale-lease", "lease is not current", nowMs, command) };
1118
+ if (work.attempt !== command.attempt)
1119
+ return { issue: issueEvent(queueId, "attempt-mismatch", "attempt mismatch", nowMs, command) };
1120
+ if (work.workerId !== command.workerId)
1121
+ return { issue: issueEvent(queueId, "worker-mismatch", "worker mismatch", nowMs, command) };
1122
+ if (isExpiredLease(work, nowMs))
1123
+ return {
1124
+ events: materializeLeaseExpired(queueId, state, work, command.commandId, nowMs, {
1125
+ command,
1126
+ code: "lease-expired",
1127
+ message: "lease expired"
1128
+ })
1129
+ };
1130
+ return { work };
1127
1131
  }
1128
1132
  // Annotate the CommonJS export names for ESM import in node:
1129
1133
  0 && (module.exports = {