@graphrefly/ts 0.0.1 → 0.0.2

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