@graphrefly/ts 0.0.0 → 0.0.1

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 (307) hide show
  1. package/README.md +135 -0
  2. package/dist/adapters/index.cjs +105 -66
  3. package/dist/adapters/index.cjs.map +1 -1
  4. package/dist/adapters/index.d.cts +11 -136
  5. package/dist/adapters/index.d.ts +11 -136
  6. package/dist/adapters/index.js +27 -225
  7. package/dist/adapters/index.js.map +1 -1
  8. package/dist/adapters/observe-storage.d.cts +4 -4
  9. package/dist/adapters/observe-storage.d.ts +4 -4
  10. package/dist/adapters/observe-storage.js +1 -0
  11. package/dist/adapters/observe-storage.js.map +1 -1
  12. package/dist/adapters/react.cjs +166 -0
  13. package/dist/adapters/react.cjs.map +1 -0
  14. package/dist/adapters/react.d.cts +31 -0
  15. package/dist/adapters/react.d.ts +31 -0
  16. package/dist/adapters/react.js +51 -0
  17. package/dist/adapters/react.js.map +1 -0
  18. package/dist/adapters/solid.cjs +157 -0
  19. package/dist/adapters/solid.cjs.map +1 -0
  20. package/dist/adapters/solid.d.cts +26 -0
  21. package/dist/adapters/solid.d.ts +26 -0
  22. package/dist/adapters/solid.js +48 -0
  23. package/dist/adapters/solid.js.map +1 -0
  24. package/dist/adapters/svelte.cjs +148 -0
  25. package/dist/adapters/svelte.cjs.map +1 -0
  26. package/dist/adapters/svelte.d.cts +31 -0
  27. package/dist/adapters/svelte.d.ts +31 -0
  28. package/dist/adapters/svelte.js +46 -0
  29. package/dist/adapters/svelte.js.map +1 -0
  30. package/dist/adapters/vue.cjs +155 -0
  31. package/dist/adapters/vue.cjs.map +1 -0
  32. package/dist/adapters/vue.d.cts +26 -0
  33. package/dist/adapters/vue.d.ts +26 -0
  34. package/dist/adapters/vue.js +46 -0
  35. package/dist/adapters/vue.js.map +1 -0
  36. package/dist/agent-runtime-BesGDZcB.d.ts +555 -0
  37. package/dist/agent-runtime-DZTJ3C-p.d.cts +555 -0
  38. package/dist/agentic-memory-BmRCvh0d.d.ts +517 -0
  39. package/dist/agentic-memory-DfuVZ-lY.d.cts +517 -0
  40. package/dist/capability-admission-B0BzqAm5.d.cts +102 -0
  41. package/dist/capability-admission-g0mn9hpq.d.ts +102 -0
  42. package/dist/{chunk-JR4IKCR2.js → chunk-2747WN4Y.js} +5 -5
  43. package/dist/{chunk-2ILL7HQI.js → chunk-3JR3EDB2.js} +17 -17
  44. package/dist/{chunk-2ILL7HQI.js.map → chunk-3JR3EDB2.js.map} +1 -1
  45. package/dist/{chunk-GDL26N2T.js → chunk-3KN7NJDR.js} +2 -2
  46. package/dist/{chunk-DJC4CHLQ.js → chunk-43BKLFZU.js} +9 -9
  47. package/dist/chunk-6XAIOL3X.js +2407 -0
  48. package/dist/chunk-6XAIOL3X.js.map +1 -0
  49. package/dist/{chunk-SCRFRCGR.js → chunk-BSQGE5PS.js} +1 -1
  50. package/dist/{chunk-Y3ZLRGHC.js → chunk-CZNV3VXM.js} +16 -16
  51. package/dist/chunk-DGUM43GV.js +11 -0
  52. package/dist/chunk-DGUM43GV.js.map +1 -0
  53. package/dist/{chunk-JIMUDQN3.js → chunk-FBUU43PM.js} +2 -2
  54. package/dist/chunk-H75VXTGT.js +219 -0
  55. package/dist/chunk-H75VXTGT.js.map +1 -0
  56. package/dist/{chunk-RIIOUTGM.js → chunk-HKK2JD7Z.js} +12 -9
  57. package/dist/chunk-HKK2JD7Z.js.map +1 -0
  58. package/dist/{chunk-D6BG7U6A.js → chunk-IXZBUWSP.js} +3 -3
  59. package/dist/chunk-PC3BTTBE.js +3217 -0
  60. package/dist/chunk-PC3BTTBE.js.map +1 -0
  61. package/dist/chunk-QFY4AFY4.js +879 -0
  62. package/dist/chunk-QFY4AFY4.js.map +1 -0
  63. package/dist/{chunk-DK554R7P.js → chunk-S6HXABGL.js} +2 -1
  64. package/dist/chunk-S6HXABGL.js.map +1 -0
  65. package/dist/{chunk-PKVBHGBM.js → chunk-SS2A464I.js} +7 -7
  66. package/dist/chunk-UT3MHJCW.js +49 -0
  67. package/dist/chunk-UT3MHJCW.js.map +1 -0
  68. package/dist/{chunk-G4M3NHRJ.js → chunk-V7CIJE4B.js} +3 -3
  69. package/dist/{chunk-TEW47IVR.js → chunk-VPEFSDME.js} +2 -2
  70. package/dist/chunk-VTHLCFN4.js +368 -0
  71. package/dist/chunk-VTHLCFN4.js.map +1 -0
  72. package/dist/{chunk-SWTLEQL6.js → chunk-WQIA3GFB.js} +2 -2
  73. package/dist/chunk-WQIA3GFB.js.map +1 -0
  74. package/dist/{chunk-I3IHOHCJ.js → chunk-XKXWK2KP.js} +108 -64
  75. package/dist/chunk-XKXWK2KP.js.map +1 -0
  76. package/dist/{chunk-COHO25CZ.js → chunk-YDSIVHRR.js} +553 -7
  77. package/dist/chunk-YDSIVHRR.js.map +1 -0
  78. package/dist/chunk-YXMZ2N6J.js +1884 -0
  79. package/dist/chunk-YXMZ2N6J.js.map +1 -0
  80. package/dist/composition/index.cjs +102 -57
  81. package/dist/composition/index.cjs.map +1 -1
  82. package/dist/composition/index.d.cts +4 -4
  83. package/dist/composition/index.d.ts +4 -4
  84. package/dist/composition/index.js +6 -5
  85. package/dist/core/index.cjs +102 -57
  86. package/dist/core/index.cjs.map +1 -1
  87. package/dist/core/index.d.cts +2 -2
  88. package/dist/core/index.d.ts +2 -2
  89. package/dist/core/index.js +5 -4
  90. package/dist/cqrs/index.cjs.map +1 -1
  91. package/dist/cqrs/index.d.cts +4 -4
  92. package/dist/cqrs/index.d.ts +4 -4
  93. package/dist/cqrs/index.js +3 -2
  94. package/dist/cqrs/index.js.map +1 -1
  95. package/dist/cqrs/messaging.cjs +312 -0
  96. package/dist/cqrs/messaging.cjs.map +1 -0
  97. package/dist/cqrs/messaging.d.cts +57 -0
  98. package/dist/cqrs/messaging.d.ts +57 -0
  99. package/dist/cqrs/messaging.js +274 -0
  100. package/dist/cqrs/messaging.js.map +1 -0
  101. package/dist/cqrs/work-queue.cjs +329 -0
  102. package/dist/cqrs/work-queue.cjs.map +1 -0
  103. package/dist/cqrs/work-queue.d.cts +90 -0
  104. package/dist/cqrs/work-queue.d.ts +90 -0
  105. package/dist/cqrs/work-queue.js +292 -0
  106. package/dist/cqrs/work-queue.js.map +1 -0
  107. package/dist/data/index.cjs +19 -0
  108. package/dist/data/index.cjs.map +1 -0
  109. package/dist/data/index.d.cts +36 -0
  110. package/dist/data/index.d.ts +36 -0
  111. package/dist/data/index.js +1 -0
  112. package/dist/data/index.js.map +1 -0
  113. package/dist/data-structures/index.cjs +105 -57
  114. package/dist/data-structures/index.cjs.map +1 -1
  115. package/dist/data-structures/index.d.cts +8 -8
  116. package/dist/data-structures/index.d.ts +8 -8
  117. package/dist/data-structures/index.js +7 -6
  118. package/dist/{describe-BZb7AS2y.d.ts → describe-BpyJuWkw.d.ts} +1 -1
  119. package/dist/{describe-Ciu8SUnN.d.cts → describe-DYMw_l1C.d.cts} +1 -1
  120. package/dist/executors/work-queue.cjs +342 -0
  121. package/dist/executors/work-queue.cjs.map +1 -0
  122. package/dist/executors/work-queue.d.cts +90 -0
  123. package/dist/executors/work-queue.d.ts +90 -0
  124. package/dist/executors/work-queue.js +306 -0
  125. package/dist/executors/work-queue.js.map +1 -0
  126. package/dist/graph/index.cjs +112 -58
  127. package/dist/graph/index.cjs.map +1 -1
  128. package/dist/graph/index.d.cts +9 -9
  129. package/dist/graph/index.d.ts +9 -9
  130. package/dist/graph/index.js +18 -17
  131. package/dist/{graph-DfdF9zy4.d.ts → graph-D-1-SQeC.d.ts} +3 -3
  132. package/dist/{graph-s23RbbFO.d.cts → graph-DCEWh4T2.d.cts} +3 -3
  133. package/dist/{index-CAEmLCYF.d.cts → index-72VZC0-E.d.cts} +2 -2
  134. package/dist/{index-SnzZu3x7.d.ts → index-Bmxeu0nK.d.ts} +2 -2
  135. package/dist/index.cjs +112 -58
  136. package/dist/index.cjs.map +1 -1
  137. package/dist/index.d.cts +11 -10
  138. package/dist/index.d.ts +11 -10
  139. package/dist/index.js +118 -113
  140. package/dist/inspection/boundary.cjs +98 -0
  141. package/dist/inspection/boundary.cjs.map +1 -0
  142. package/dist/inspection/boundary.d.cts +61 -0
  143. package/dist/inspection/boundary.d.ts +61 -0
  144. package/dist/inspection/boundary.js +75 -0
  145. package/dist/inspection/boundary.js.map +1 -0
  146. package/dist/{messages-BZqrGGm1.d.ts → messages-CAzUPPJM.d.cts} +10 -5
  147. package/dist/{messages-BZqrGGm1.d.cts → messages-CAzUPPJM.d.ts} +10 -5
  148. package/dist/messaging/index.cjs +744 -613
  149. package/dist/messaging/index.cjs.map +1 -1
  150. package/dist/messaging/index.d.cts +216 -160
  151. package/dist/messaging/index.d.ts +216 -160
  152. package/dist/messaging/index.js +19 -749
  153. package/dist/messaging/index.js.map +1 -1
  154. package/dist/operators/index.cjs +102 -57
  155. package/dist/operators/index.cjs.map +1 -1
  156. package/dist/operators/index.d.cts +6 -6
  157. package/dist/operators/index.d.ts +6 -6
  158. package/dist/operators/index.js +8 -7
  159. package/dist/orchestration/index.cjs +3333 -59
  160. package/dist/orchestration/index.cjs.map +1 -1
  161. package/dist/orchestration/index.d.cts +7 -4
  162. package/dist/orchestration/index.d.ts +7 -4
  163. package/dist/orchestration/index.js +49 -8
  164. package/dist/orchestration/index.js.map +1 -1
  165. package/dist/orchestration/messaging.cjs +314 -0
  166. package/dist/orchestration/messaging.cjs.map +1 -0
  167. package/dist/orchestration/messaging.d.cts +59 -0
  168. package/dist/orchestration/messaging.d.ts +59 -0
  169. package/dist/orchestration/messaging.js +276 -0
  170. package/dist/orchestration/messaging.js.map +1 -0
  171. package/dist/orchestration/work-queue.cjs +262 -0
  172. package/dist/orchestration/work-queue.cjs.map +1 -0
  173. package/dist/orchestration/work-queue.d.cts +85 -0
  174. package/dist/orchestration/work-queue.d.ts +85 -0
  175. package/dist/orchestration/work-queue.js +226 -0
  176. package/dist/orchestration/work-queue.js.map +1 -0
  177. package/dist/patterns/event-flow.cjs +447 -0
  178. package/dist/patterns/event-flow.cjs.map +1 -0
  179. package/dist/patterns/event-flow.d.cts +115 -0
  180. package/dist/patterns/event-flow.d.ts +115 -0
  181. package/dist/patterns/event-flow.js +14 -0
  182. package/dist/patterns/event-flow.js.map +1 -0
  183. package/dist/patterns/index.cjs +423 -18
  184. package/dist/patterns/index.cjs.map +1 -1
  185. package/dist/patterns/index.d.cts +8 -5
  186. package/dist/patterns/index.d.ts +8 -5
  187. package/dist/patterns/index.js +13 -4
  188. package/dist/{reactive-collection-Cb1II1Fi.d.ts → reactive-collection-BAmfrGjd.d.ts} +9 -9
  189. package/dist/{reactive-collection-BxBqtIOE.d.cts → reactive-collection-DNV0-gFi.d.cts} +9 -9
  190. package/dist/{reactive-collection-storage-D-KmjE0J.d.cts → reactive-collection-storage-BNEldDyi.d.cts} +5 -5
  191. package/dist/{reactive-collection-storage-BNiEc-Gd.d.ts → reactive-collection-storage-YZsAQsWK.d.ts} +5 -5
  192. package/dist/{reactive-map-D-g3dv8I.d.cts → reactive-map-DRD1LJ2H.d.cts} +6 -6
  193. package/dist/{reactive-map-BcB4KsQR.d.ts → reactive-map-EsDiCmJk.d.ts} +6 -6
  194. package/dist/render/index.d.cts +3 -3
  195. package/dist/render/index.d.ts +3 -3
  196. package/dist/render/index.js +1 -0
  197. package/dist/{semantic-memory-graph-BYoIlP35.d.cts → semantic-memory-graph-DKuNzuWw.d.cts} +2 -2
  198. package/dist/{semantic-memory-graph-CxyXLms3.d.ts → semantic-memory-graph-uwbwJdhe.d.ts} +2 -2
  199. package/dist/solutions/index.cjs +4478 -2
  200. package/dist/solutions/index.cjs.map +1 -1
  201. package/dist/solutions/index.d.cts +13 -519
  202. package/dist/solutions/index.d.ts +13 -519
  203. package/dist/solutions/index.js +90 -6
  204. package/dist/solutions/reactive-layout/browser/index.cjs +449 -0
  205. package/dist/solutions/reactive-layout/browser/index.cjs.map +1 -0
  206. package/dist/solutions/reactive-layout/browser/index.d.cts +44 -0
  207. package/dist/solutions/reactive-layout/browser/index.d.ts +44 -0
  208. package/dist/solutions/reactive-layout/browser/index.js +69 -0
  209. package/dist/solutions/reactive-layout/browser/index.js.map +1 -0
  210. package/dist/solutions/reactive-layout/index.cjs +4266 -0
  211. package/dist/solutions/reactive-layout/index.cjs.map +1 -0
  212. package/dist/solutions/reactive-layout/index.d.cts +581 -0
  213. package/dist/solutions/reactive-layout/index.d.ts +581 -0
  214. package/dist/solutions/reactive-layout/index.js +86 -0
  215. package/dist/solutions/reactive-layout/index.js.map +1 -0
  216. package/dist/solutions/reactive-layout/node-canvas/index.cjs +545 -0
  217. package/dist/solutions/reactive-layout/node-canvas/index.cjs.map +1 -0
  218. package/dist/solutions/reactive-layout/node-canvas/index.d.cts +54 -0
  219. package/dist/solutions/reactive-layout/node-canvas/index.d.ts +54 -0
  220. package/dist/solutions/reactive-layout/node-canvas/index.js +131 -0
  221. package/dist/solutions/reactive-layout/node-canvas/index.js.map +1 -0
  222. package/dist/solutions/reactive-layout/react-native/index.cjs +494 -0
  223. package/dist/solutions/reactive-layout/react-native/index.cjs.map +1 -0
  224. package/dist/solutions/reactive-layout/react-native/index.d.cts +51 -0
  225. package/dist/solutions/reactive-layout/react-native/index.d.ts +51 -0
  226. package/dist/solutions/reactive-layout/react-native/index.js +77 -0
  227. package/dist/solutions/reactive-layout/react-native/index.js.map +1 -0
  228. package/dist/solutions/reactive-layout/skia/index.cjs +547 -0
  229. package/dist/solutions/reactive-layout/skia/index.cjs.map +1 -0
  230. package/dist/solutions/reactive-layout/skia/index.d.cts +64 -0
  231. package/dist/solutions/reactive-layout/skia/index.d.ts +64 -0
  232. package/dist/solutions/reactive-layout/skia/index.js +70 -0
  233. package/dist/solutions/reactive-layout/skia/index.js.map +1 -0
  234. package/dist/solutions/work-item/actions.cjs +2164 -0
  235. package/dist/solutions/work-item/actions.cjs.map +1 -0
  236. package/dist/solutions/work-item/actions.d.cts +201 -0
  237. package/dist/solutions/work-item/actions.d.ts +201 -0
  238. package/dist/solutions/work-item/actions.js +1454 -0
  239. package/dist/solutions/work-item/actions.js.map +1 -0
  240. package/dist/solutions/work-item/scheduling.cjs +2454 -0
  241. package/dist/solutions/work-item/scheduling.cjs.map +1 -0
  242. package/dist/solutions/work-item/scheduling.d.cts +433 -0
  243. package/dist/solutions/work-item/scheduling.d.ts +433 -0
  244. package/dist/solutions/work-item/scheduling.js +32 -0
  245. package/dist/solutions/work-item/scheduling.js.map +1 -0
  246. package/dist/solutions/work-item/work-queue.cjs +306 -0
  247. package/dist/solutions/work-item/work-queue.cjs.map +1 -0
  248. package/dist/solutions/work-item/work-queue.d.cts +71 -0
  249. package/dist/solutions/work-item/work-queue.d.ts +71 -0
  250. package/dist/solutions/work-item/work-queue.js +270 -0
  251. package/dist/solutions/work-item/work-queue.js.map +1 -0
  252. package/dist/sources/browser.cjs +169 -57
  253. package/dist/sources/browser.cjs.map +1 -1
  254. package/dist/sources/browser.d.cts +33 -6
  255. package/dist/sources/browser.d.ts +33 -6
  256. package/dist/sources/browser.js +73 -6
  257. package/dist/sources/browser.js.map +1 -1
  258. package/dist/sources/index.cjs +102 -57
  259. package/dist/sources/index.cjs.map +1 -1
  260. package/dist/sources/index.d.cts +5 -5
  261. package/dist/sources/index.d.ts +5 -5
  262. package/dist/sources/index.js +6 -5
  263. package/dist/sources/node.cjs.map +1 -1
  264. package/dist/sources/node.d.cts +4 -4
  265. package/dist/sources/node.d.ts +4 -4
  266. package/dist/sources/node.js +2 -1
  267. package/dist/sources/node.js.map +1 -1
  268. package/dist/storage/browser.js +1 -0
  269. package/dist/storage/browser.js.map +1 -1
  270. package/dist/storage/index.d.cts +5 -5
  271. package/dist/storage/index.d.ts +5 -5
  272. package/dist/storage/index.js +5 -4
  273. package/dist/storage/node.js +1 -0
  274. package/dist/storage/node.js.map +1 -1
  275. package/dist/store-C-joM4gh.d.ts +123 -0
  276. package/dist/store-CNJwmT-h.d.cts +123 -0
  277. package/dist/testing/index.cjs +1 -0
  278. package/dist/testing/index.cjs.map +1 -1
  279. package/dist/testing/index.d.cts +1 -1
  280. package/dist/testing/index.d.ts +1 -1
  281. package/dist/testing/index.js +3 -2
  282. package/dist/{types-h12mNYbq.d.ts → types-BsJvElYZ.d.ts} +31 -31
  283. package/dist/{types-bNMSWKqF.d.cts → types-CcPIn7sy.d.cts} +31 -31
  284. package/dist/work-item-runtime-C3M79vE9.d.ts +228 -0
  285. package/dist/work-item-runtime-qaVgsL0T.d.cts +228 -0
  286. package/dist/work-queue/index.cjs +1132 -0
  287. package/dist/work-queue/index.cjs.map +1 -0
  288. package/dist/work-queue/index.d.cts +369 -0
  289. package/dist/work-queue/index.d.ts +369 -0
  290. package/dist/work-queue/index.js +1078 -0
  291. package/dist/work-queue/index.js.map +1 -0
  292. package/package.json +266 -2
  293. package/dist/chunk-COHO25CZ.js.map +0 -1
  294. package/dist/chunk-DK554R7P.js.map +0 -1
  295. package/dist/chunk-I3IHOHCJ.js.map +0 -1
  296. package/dist/chunk-RIIOUTGM.js.map +0 -1
  297. package/dist/chunk-SWTLEQL6.js.map +0 -1
  298. /package/dist/{chunk-JR4IKCR2.js.map → chunk-2747WN4Y.js.map} +0 -0
  299. /package/dist/{chunk-GDL26N2T.js.map → chunk-3KN7NJDR.js.map} +0 -0
  300. /package/dist/{chunk-DJC4CHLQ.js.map → chunk-43BKLFZU.js.map} +0 -0
  301. /package/dist/{chunk-SCRFRCGR.js.map → chunk-BSQGE5PS.js.map} +0 -0
  302. /package/dist/{chunk-Y3ZLRGHC.js.map → chunk-CZNV3VXM.js.map} +0 -0
  303. /package/dist/{chunk-JIMUDQN3.js.map → chunk-FBUU43PM.js.map} +0 -0
  304. /package/dist/{chunk-D6BG7U6A.js.map → chunk-IXZBUWSP.js.map} +0 -0
  305. /package/dist/{chunk-PKVBHGBM.js.map → chunk-SS2A464I.js.map} +0 -0
  306. /package/dist/{chunk-G4M3NHRJ.js.map → chunk-V7CIJE4B.js.map} +0 -0
  307. /package/dist/{chunk-TEW47IVR.js.map → chunk-VPEFSDME.js.map} +0 -0
@@ -28,11 +28,11 @@ __export(messaging_exports, {
28
28
  SPAWNS_TOPIC: () => SPAWNS_TOPIC,
29
29
  STANDARD_TOPICS: () => STANDARD_TOPICS,
30
30
  TODOS_TOPIC: () => TODOS_TOPIC,
31
- dynamicHub: () => dynamicHub,
32
- fromHubTopic: () => fromHubTopic,
31
+ eventMessage: () => eventMessage,
32
+ eventMessageIssue: () => eventMessageIssue,
33
33
  fromTopic: () => fromTopic,
34
+ isEventMessage: () => isEventMessage,
34
35
  messageBus: () => messageBus,
35
- toHubTopic: () => toHubTopic,
36
36
  toTopic: () => toTopic
37
37
  });
38
38
  module.exports = __toCommonJS(messaging_exports);
@@ -51,15 +51,78 @@ function depBatch(ctx, depIndex) {
51
51
  return flattened.length === 0 ? [] : flattened;
52
52
  }
53
53
 
54
- // src/graph/graph.ts
55
- var lifecycleRegistrars = /* @__PURE__ */ new WeakMap();
56
- function releaseGraphNodes(graph, nodes, opts = {}) {
57
- const registrar = lifecycleRegistrars.get(graph);
58
- if (registrar === void 0) throw new Error("graph: unknown lifecycle registrar");
59
- registrar.releaseNodes(nodes, opts);
54
+ // src/messaging/internal.ts
55
+ var busStates = /* @__PURE__ */ new WeakMap();
56
+ function registerMessageBusState(bus, state) {
57
+ busStates.set(bus, state);
58
+ }
59
+ function getMessageBusState(bus) {
60
+ return busStates.get(bus);
61
+ }
62
+ function attachMessageBusCommandSource(graph, bus, commands) {
63
+ const state = getMessageBusState(bus);
64
+ if (state === void 0) throw new Error("messageBus: unknown implementation");
65
+ if (state.graph !== graph) throw new Error("messageBus: command source graph must match");
66
+ state.commandSources.push(commands);
67
+ const busCommands = bus.commands;
68
+ if (busCommands === void 0) throw new Error("messageBus: missing commands node");
69
+ busCommands.replaceDeps([...state.commandSources], state.commandBody);
70
+ let released = false;
71
+ return () => {
72
+ if (released) return;
73
+ released = true;
74
+ const index = state.commandSources.indexOf(commands);
75
+ if (index >= 0) state.commandSources.splice(index, 1);
76
+ busCommands.replaceDeps([...state.commandSources], state.commandBody);
77
+ };
60
78
  }
61
79
 
62
80
  // src/messaging/index.ts
81
+ function eventMessage(type, payload, opts) {
82
+ assertNonEmpty(type, "eventMessage.type");
83
+ assertNonEmpty(opts.id, "eventMessage.id");
84
+ return {
85
+ id: opts.id,
86
+ type,
87
+ payload,
88
+ ...opts.key === void 0 ? {} : { key: opts.key },
89
+ ...opts.subjectId === void 0 ? {} : { subjectId: opts.subjectId },
90
+ ...opts.correlationId === void 0 ? {} : { correlationId: opts.correlationId },
91
+ ...opts.causationId === void 0 ? {} : { causationId: opts.causationId },
92
+ ...opts.occurredAtMs === void 0 ? {} : { occurredAtMs: opts.occurredAtMs },
93
+ ...opts.actor === void 0 ? {} : { actor: opts.actor },
94
+ ...opts.evidenceRefs === void 0 ? {} : { evidenceRefs: opts.evidenceRefs },
95
+ ...opts.schema === void 0 ? {} : { schema: opts.schema },
96
+ ...opts.metadata === void 0 ? {} : { metadata: opts.metadata }
97
+ };
98
+ }
99
+ function isEventMessage(value) {
100
+ return eventMessageIssue(value) === void 0;
101
+ }
102
+ function eventMessageIssue(value) {
103
+ if (!isObjectRecord(value)) {
104
+ return eventIssue("event message must be an object");
105
+ }
106
+ if (typeof value.id !== "string" || value.id.length === 0) {
107
+ return eventIssue("event message id must be a non-empty string");
108
+ }
109
+ if (typeof value.type !== "string" || value.type.length === 0) {
110
+ return eventIssue("event message type must be a non-empty string");
111
+ }
112
+ if (!("payload" in value)) {
113
+ return eventIssue("event message payload field is required");
114
+ }
115
+ return void 0;
116
+ }
117
+ function eventIssue(message) {
118
+ return {
119
+ kind: "issue",
120
+ code: "malformed-event-message",
121
+ message,
122
+ severity: "error",
123
+ source: "messageBus.eventMessage"
124
+ };
125
+ }
63
126
  var PROMPTS_TOPIC = "prompts";
64
127
  var RESPONSES_TOPIC = "responses";
65
128
  var INJECTIONS_TOPIC = "injections";
@@ -76,727 +139,795 @@ var STANDARD_TOPICS = Object.freeze([
76
139
  CONTEXT_TOPIC,
77
140
  TODOS_TOPIC
78
141
  ]);
79
- var busStates = /* @__PURE__ */ new WeakMap();
80
- var dynamicHubStates = /* @__PURE__ */ new WeakMap();
81
- var DEFAULT_DYNAMIC_HUB_MAX_TOPICS = 1024;
82
- var DEFAULT_DYNAMIC_HUB_MAX_TOPIC_LENGTH = 256;
83
- function messageBus(graph, opts) {
84
- const unique = [...new Set(opts.topics)];
85
- if (unique.length !== opts.topics.length) throw new Error("messageBus: duplicate topic");
86
- if (unique.length === 0) throw new Error("messageBus: at least one topic is required");
142
+ function messageBus(graph, opts = {}) {
87
143
  const name = opts.name ?? "messageBus";
88
- const state = {
89
- graph,
90
- name,
91
- now: opts.now ?? Date.now,
92
- topics: Object.freeze([...unique]),
93
- records: /* @__PURE__ */ new Map(),
94
- seq: 0
95
- };
96
- for (const topic of unique) {
97
- const producers = [];
98
- const body = (ctx) => {
99
- for (let i = 0; i < producers.length; i++) {
100
- for (const produced of depBatch(ctx, i) ?? []) {
101
- if (produced.kind === "publish") {
102
- ctx.down([
103
- [
104
- "DATA",
105
- produced.envelope
106
- ]
107
- ]);
108
- }
109
- }
110
- }
111
- };
112
- const node = graph.node([], body, {
113
- name: `${name}/${topic}`,
114
- factory: "messageTopic",
115
- meta: { topic },
116
- completeWhenDepsComplete: false,
117
- errorWhenDepsError: false
118
- });
119
- state.records.set(topic, { node, producers, body });
120
- }
121
- const bus = {
122
- topics: state.topics,
123
- topic(topic) {
124
- return requireTopic(state, topic);
125
- },
126
- publish(topic, payload, publishOpts = {}) {
127
- const node = requireTopic(state, topic);
128
- const envelope = {
129
- topic,
130
- seq: ++state.seq,
131
- payload,
132
- timestampMs: state.now(),
133
- ...publishOpts.key === void 0 ? {} : { key: publishOpts.key }
134
- };
135
- node.down([["DATA", envelope]]);
136
- return envelope;
137
- },
138
- has(topic) {
139
- return state.records.has(topic);
140
- }
141
- };
142
- busStates.set(bus, state);
143
- return bus;
144
- }
145
- function dynamicHub(graph, opts = {}) {
146
- const name = opts.name ?? "dynamicHub";
147
- const unknownTopic = normalizeUnknownTopicPolicy(opts.unknownTopic);
148
- const maxTopics = normalizeHubLimit(
149
- opts.maxTopics,
150
- DEFAULT_DYNAMIC_HUB_MAX_TOPICS,
151
- "dynamicHub: maxTopics"
152
- );
153
- const maxTopicLength = normalizeHubLimit(
154
- opts.maxTopicLength,
155
- DEFAULT_DYNAMIC_HUB_MAX_TOPIC_LENGTH,
156
- "dynamicHub: maxTopicLength"
157
- );
158
- const now = opts.now ?? Date.now;
159
- const initialTopics = uniqueTopics(opts.topics ?? [], maxTopicLength);
160
- if (initialTopics.length > maxTopics) {
161
- throw new Error("dynamicHub: topics exceed maxTopics");
162
- }
144
+ const initialTopics = uniqueTopics(opts.topics ?? []);
163
145
  const commandSources = [];
164
146
  const commandBody = (ctx) => {
165
147
  for (let i = 0; i < commandSources.length; i++) {
166
- for (const command2 of depBatch(ctx, i) ?? []) ctx.down([["DATA", command2]]);
148
+ for (const command of depBatch(ctx, i) ?? []) ctx.down([["DATA", command]]);
167
149
  }
168
150
  };
169
- const command = graph.node([], commandBody, {
170
- name: `${name}/command`,
171
- factory: "dynamicHubCommand",
151
+ const commands = graph.node([], commandBody, {
152
+ name: `${name}/commands`,
153
+ factory: "messageBusCommands",
172
154
  completeWhenDepsComplete: false,
173
155
  errorWhenDepsError: false
174
156
  });
175
- const events = graph.node(
176
- [command],
157
+ const state = {
158
+ graph,
159
+ name,
160
+ now: opts.now ?? Date.now,
161
+ topicPolicy: opts.topicPolicy ?? "strict",
162
+ retention: opts.retention ?? {},
163
+ dedupe: opts.dedupe ?? { commandId: "status" },
164
+ topics: new Map(initialTopics.map((topic) => [topic, makeTopicState()])),
165
+ subscriptions: /* @__PURE__ */ new Map(),
166
+ seenCommandIds: /* @__PURE__ */ new Set(),
167
+ seenIdempotencyKeys: /* @__PURE__ */ new Set(),
168
+ deadLetters: [],
169
+ deadLetterSeq: 0,
170
+ commandSources,
171
+ commandBody
172
+ };
173
+ const runtime = graph.node(
174
+ [commands],
177
175
  (ctx) => {
178
- const runtime = ctx.state.get() ?? {
179
- topics: [...initialTopics],
180
- open: true,
181
- seq: 0,
182
- cursor: 0
183
- };
184
- ctx.state.persist(true);
185
- for (const commandFact of depBatch(ctx, 0) ?? []) {
186
- const parsed = parseDynamicHubCommand(commandFact);
187
- if (typeof parsed === "string") {
188
- ctx.down([["DATA", hubError(runtime, commandFact, parsed, timestampOrFallback(now))]]);
189
- continue;
176
+ for (const command of depBatch(ctx, 0) ?? []) {
177
+ for (const event of reduceMessageBusCommand(state, command)) {
178
+ ctx.down([["DATA", event]]);
190
179
  }
191
- const reduced = reduceDynamicHubCommand(
192
- runtime,
193
- parsed,
194
- unknownTopic,
195
- maxTopics,
196
- maxTopicLength,
197
- now
198
- );
199
- for (const event of reduced) ctx.down([["DATA", event]]);
200
180
  }
201
- ctx.state.set(runtime);
202
181
  },
203
182
  {
204
- name: `${name}/events`,
205
- factory: "dynamicHubEvents",
206
- meta: { unknownTopic },
183
+ name: `${name}/runtime`,
184
+ factory: "messageBusRuntime",
207
185
  completeWhenDepsComplete: false,
208
186
  errorWhenDepsError: false
209
187
  }
210
188
  );
211
- const status = graph.node(
212
- [events],
189
+ graph.retain(runtime, { reason: `${name}.messageBus.runtime` });
190
+ const messages = graph.node(
191
+ [runtime],
213
192
  (ctx) => {
214
193
  for (const event of depBatch(ctx, 0) ?? []) {
215
194
  const typed = event;
216
- ctx.down([["DATA", typed.status]]);
195
+ if (typed.kind === "message") ctx.down([["DATA", typed.message]]);
217
196
  }
218
197
  },
219
198
  {
220
- name: `${name}/status`,
221
- factory: "dynamicHubStatus",
199
+ name: `${name}/messages`,
200
+ factory: "messageBusMessages",
222
201
  completeWhenDepsComplete: false,
223
202
  errorWhenDepsError: false
224
203
  }
225
204
  );
226
- const errors = graph.node(
227
- [events],
205
+ const status = graph.node(
206
+ [runtime],
228
207
  (ctx) => {
229
208
  for (const event of depBatch(ctx, 0) ?? []) {
230
209
  const typed = event;
231
- if (typed.kind !== "error") continue;
232
- ctx.down([
233
- [
234
- "DATA",
235
- {
236
- ...typed.topic === void 0 ? {} : { topic: typed.topic },
237
- error: typed.error,
238
- command: typed.command,
239
- meta: typed.meta
240
- }
241
- ]
242
- ]);
210
+ if (typed.kind === "status") ctx.down([["DATA", typed.status]]);
243
211
  }
244
212
  },
245
213
  {
246
- name: `${name}/errors`,
247
- factory: "dynamicHubErrors",
214
+ name: `${name}/status`,
215
+ factory: "messageBusStatus",
248
216
  completeWhenDepsComplete: false,
249
217
  errorWhenDepsError: false
250
218
  }
251
219
  );
252
- const deadLetter = opts.deadLetter === true || unknownTopic === "dead-letter" ? graph.node(
253
- [events],
220
+ const issues = graph.node(
221
+ [runtime],
254
222
  (ctx) => {
255
223
  for (const event of depBatch(ctx, 0) ?? []) {
256
224
  const typed = event;
257
- if (typed.kind !== "dead-letter") continue;
258
- ctx.down([
259
- [
260
- "DATA",
261
- {
262
- ...typed.topic === void 0 ? {} : { topic: typed.topic },
263
- reason: typed.reason,
264
- command: typed.command,
265
- meta: typed.meta
266
- }
267
- ]
268
- ]);
225
+ if (typed.kind === "issue") ctx.down([["DATA", typed.issue]]);
269
226
  }
270
227
  },
271
228
  {
272
- name: `${name}/deadLetter`,
273
- factory: "dynamicHubDeadLetter",
229
+ name: `${name}/issues`,
230
+ factory: "messageBusIssues",
274
231
  completeWhenDepsComplete: false,
275
232
  errorWhenDepsError: false
276
233
  }
277
- ) : void 0;
278
- const releaseEventsRetain = graph.retain(events, { reason: `${name}.dynamicHub.events` });
279
- const hub = {
280
- command,
281
- events,
234
+ );
235
+ const bus = {
236
+ commands,
237
+ messages,
282
238
  status,
283
- errors,
284
- ...deadLetter === void 0 ? {} : { deadLetter },
285
- create(topic, commandOpts = {}) {
286
- return publishHubCommand(command, {
287
- kind: "create",
239
+ issues,
240
+ ensureTopic(topic, commandOpts = {}) {
241
+ return publishCommand(commands, {
242
+ kind: "ensure-topic",
288
243
  topic,
289
- ...commandOpts.key === void 0 ? {} : { key: commandOpts.key },
290
- ...commandOpts.payload === void 0 ? {} : { payload: commandOpts.payload }
244
+ ...commandOpts.commandId === void 0 ? {} : { commandId: commandOpts.commandId }
291
245
  });
292
246
  },
293
- delete(topic, commandOpts = {}) {
294
- return publishHubCommand(command, {
295
- kind: "delete",
247
+ closeTopic(topic, commandOpts = {}) {
248
+ return publishCommand(commands, {
249
+ kind: "close-topic",
296
250
  topic,
297
- ...commandOpts.key === void 0 ? {} : { key: commandOpts.key },
298
- ...commandOpts.payload === void 0 ? {} : { payload: commandOpts.payload }
251
+ ...commandOpts.commandId === void 0 ? {} : { commandId: commandOpts.commandId }
299
252
  });
300
253
  },
301
- publish(topic, payload, commandOpts = {}) {
302
- return publishHubCommand(command, {
254
+ publish(topic, payload, publishOpts = {}) {
255
+ return publishCommand(commands, {
303
256
  kind: "publish",
304
257
  topic,
305
258
  payload,
306
- ...commandOpts.key === void 0 ? {} : { key: commandOpts.key }
259
+ ...publishOpts.key === void 0 ? {} : { key: publishOpts.key },
260
+ ...publishOpts.commandId === void 0 ? {} : { commandId: publishOpts.commandId },
261
+ ...publishOpts.idempotencyKey === void 0 ? {} : { idempotencyKey: publishOpts.idempotencyKey }
307
262
  });
308
263
  },
309
- subscribeTopic(topic, commandOpts = {}) {
310
- return publishHubCommand(command, {
311
- kind: "subscribe",
312
- topic,
313
- ...commandOpts.key === void 0 ? {} : { key: commandOpts.key },
314
- ...commandOpts.payload === void 0 ? {} : { payload: commandOpts.payload }
315
- });
264
+ topic(topic, projectionOpts = {}) {
265
+ assertTopicKey(topic, "messageBus.topic");
266
+ const snapshotPullId = /* @__PURE__ */ Symbol(`${name}/${topic}/topicSnapshot`);
267
+ const snapshot = graph.node(
268
+ [messages],
269
+ (ctx) => {
270
+ const params = pullParams(ctx.pull);
271
+ ctx.down([["DATA", topicPage(state, topic, params)]]);
272
+ },
273
+ {
274
+ name: projectionOpts.name ?? `${name}/${topic}/topic`,
275
+ factory: "messageBusTopicProjection",
276
+ meta: { topic },
277
+ pullId: snapshotPullId,
278
+ partial: true,
279
+ completeWhenDepsComplete: false,
280
+ errorWhenDepsError: false
281
+ }
282
+ );
283
+ return { snapshot, snapshotPullId, status, issues };
284
+ },
285
+ catalog(projectionOpts = {}) {
286
+ const snapshotPullId = /* @__PURE__ */ Symbol(`${name}/catalogSnapshot`);
287
+ const snapshot = graph.node(
288
+ [runtime],
289
+ (ctx) => {
290
+ const params = pullParams(ctx.pull);
291
+ ctx.down([["DATA", catalogPage(state, params)]]);
292
+ },
293
+ {
294
+ name: projectionOpts.name ?? `${name}/catalog`,
295
+ factory: "messageBusCatalog",
296
+ pullId: snapshotPullId,
297
+ partial: true,
298
+ completeWhenDepsComplete: false,
299
+ errorWhenDepsError: false
300
+ }
301
+ );
302
+ return { snapshot, snapshotPullId, status, issues };
303
+ },
304
+ deadLetter(projectionOpts = {}) {
305
+ const snapshotPullId = /* @__PURE__ */ Symbol(`${name}/deadLetterSnapshot`);
306
+ const snapshot = graph.node(
307
+ [runtime],
308
+ (ctx) => {
309
+ const params = pullParams(ctx.pull);
310
+ ctx.down([["DATA", deadLetterPage(state, params)]]);
311
+ },
312
+ {
313
+ name: projectionOpts.name ?? `${name}/deadLetter`,
314
+ factory: "messageBusDeadLetter",
315
+ pullId: snapshotPullId,
316
+ partial: true,
317
+ completeWhenDepsComplete: false,
318
+ errorWhenDepsError: false
319
+ }
320
+ );
321
+ return { snapshot, snapshotPullId, status, issues };
316
322
  },
317
- close(commandOpts = {}) {
318
- return publishHubCommand(command, {
319
- kind: "close",
320
- ...commandOpts.key === void 0 ? {} : { key: commandOpts.key },
321
- ...commandOpts.payload === void 0 ? {} : { payload: commandOpts.payload }
323
+ subscription(subscriptionOpts) {
324
+ assertTopicKey(subscriptionOpts.topic, "messageBus.subscription");
325
+ assertNonEmpty(subscriptionOpts.subscriptionId, "messageBus.subscriptionId");
326
+ const sub = ensureSubscription(state, {
327
+ topic: subscriptionOpts.topic,
328
+ subscriptionId: subscriptionOpts.subscriptionId,
329
+ from: subscriptionOpts.from ?? "earliest"
322
330
  });
331
+ const availablePullId = /* @__PURE__ */ Symbol(
332
+ `${name}/${subscriptionOpts.topic}/${subscriptionOpts.subscriptionId}/available`
333
+ );
334
+ const projectionName = subscriptionOpts.name ?? `${name}/${subscriptionOpts.topic}/${subscriptionOpts.subscriptionId}`;
335
+ const available = graph.node(
336
+ [messages, status],
337
+ (ctx) => {
338
+ const params = pullParams(ctx.pull);
339
+ ctx.down([["DATA", availablePage(state, sub, params)]]);
340
+ },
341
+ {
342
+ name: `${projectionName}/available`,
343
+ factory: "messageBusSubscriptionAvailable",
344
+ meta: { topic: subscriptionOpts.topic, subscriptionId: subscriptionOpts.subscriptionId },
345
+ pullId: availablePullId,
346
+ partial: true,
347
+ completeWhenDepsComplete: false,
348
+ errorWhenDepsError: false
349
+ }
350
+ );
351
+ const cursor = graph.node(
352
+ [status],
353
+ (ctx) => {
354
+ for (const fact of depBatch(ctx, 0) ?? []) {
355
+ const typed = fact;
356
+ const subscriptionMoved = typed.topic === sub.topic && typed.subscriptionId === sub.subscriptionId && (typed.kind === "subscription-acked" || typed.kind === "subscription-sought" || typed.kind === "subscription-closed");
357
+ const retentionMoved = typed.topic === sub.topic && typed.kind === "retention-trimmed";
358
+ if (subscriptionMoved || retentionMoved) {
359
+ ctx.down([["DATA", cursorSnapshot(state, sub)]]);
360
+ }
361
+ }
362
+ },
363
+ {
364
+ name: `${projectionName}/cursor`,
365
+ factory: "messageBusSubscriptionCursor",
366
+ completeWhenDepsComplete: false,
367
+ errorWhenDepsError: false
368
+ }
369
+ );
370
+ return {
371
+ available,
372
+ availablePullId,
373
+ cursor,
374
+ status,
375
+ issues,
376
+ ack(seq, commandOpts = {}) {
377
+ return publishCommand(commands, {
378
+ kind: "ack",
379
+ topic: sub.topic,
380
+ subscriptionId: sub.subscriptionId,
381
+ seq,
382
+ ...commandOpts.commandId === void 0 ? {} : { commandId: commandOpts.commandId }
383
+ });
384
+ },
385
+ seek(nextSeq, commandOpts = {}) {
386
+ return publishCommand(commands, {
387
+ kind: "seek",
388
+ topic: sub.topic,
389
+ subscriptionId: sub.subscriptionId,
390
+ nextSeq,
391
+ ...commandOpts.commandId === void 0 ? {} : { commandId: commandOpts.commandId }
392
+ });
393
+ },
394
+ close(commandOpts = {}) {
395
+ return publishCommand(commands, {
396
+ kind: "close-subscription",
397
+ topic: sub.topic,
398
+ subscriptionId: sub.subscriptionId,
399
+ ...commandOpts.commandId === void 0 ? {} : { commandId: commandOpts.commandId }
400
+ });
401
+ }
402
+ };
323
403
  }
324
404
  };
325
- dynamicHubStates.set(hub, {
326
- graph,
327
- name,
328
- maxTopicLength,
329
- commandSources,
330
- commandBody,
331
- releaseEventsRetain
332
- });
333
- return hub;
405
+ registerMessageBusState(bus, state);
406
+ return bus;
407
+ }
408
+ function fromTopic(bus, topic) {
409
+ return bus.topic(topic).snapshot;
334
410
  }
335
- function fromHubTopic(hub, topic, opts = {}) {
336
- const state = requireDynamicHubState(hub);
337
- assertTopicKey(topic, "fromHubTopic", state.maxTopicLength);
338
- return state.graph.node(
339
- [hub.events],
411
+ function toTopic(graph, source, bus, topic, opts = {}) {
412
+ const state = getMessageBusState(bus);
413
+ if (state === void 0) throw new Error("toTopic: unknown message bus implementation");
414
+ if (state.graph !== graph) throw new Error("toTopic: bus and source graph must match");
415
+ assertTopicKey(topic, "toTopic");
416
+ const commands = graph.node(
417
+ [source],
340
418
  (ctx) => {
341
- for (const event of depBatch(ctx, 0) ?? []) {
342
- const typed = event;
343
- if (typed.kind !== "message" || typed.topic !== topic) continue;
419
+ for (const value of depBatch(ctx, 0) ?? []) {
420
+ const key = opts.keyOf?.(value);
421
+ const commandId = opts.commandIdOf?.(value);
344
422
  ctx.down([
345
423
  [
346
424
  "DATA",
347
425
  {
426
+ kind: "publish",
348
427
  topic,
349
- seq: typed.meta.seq,
350
- payload: typed.payload,
351
- timestampMs: typed.meta.timestampMs,
352
- ...typed.key === void 0 ? {} : { key: typed.key }
428
+ payload: value,
429
+ ...key === void 0 ? {} : { key },
430
+ ...commandId === void 0 ? {} : { commandId }
353
431
  }
354
432
  ]
355
433
  ]);
356
434
  }
357
435
  },
358
436
  {
359
- name: opts.name ?? `${state.name}/${topic}/fromHubTopic`,
360
- factory: "fromHubTopic",
437
+ name: opts.name ?? `${state.name}/${topic}/toTopic`,
438
+ factory: "toTopic",
361
439
  meta: { topic },
362
440
  completeWhenDepsComplete: false,
363
441
  errorWhenDepsError: false
364
442
  }
365
443
  );
444
+ const releaseSource = attachMessageBusCommandSource(
445
+ graph,
446
+ bus,
447
+ commands
448
+ );
449
+ let released = false;
450
+ return {
451
+ commands,
452
+ release() {
453
+ if (released) return;
454
+ released = true;
455
+ releaseSource();
456
+ }
457
+ };
366
458
  }
367
- function toHubTopic(graph, source, hub, topic, opts = {}) {
368
- const state = requireDynamicHubState(hub);
369
- assertTopicKey(topic, "toHubTopic", state.maxTopicLength);
370
- if (state.graph !== graph) throw new Error("toHubTopic: hub and source graph must match");
371
- if (isReachableUpstream(source, hub.command)) {
372
- throw new Error("toHubTopic: source already depends on hub command path");
459
+ function reduceMessageBusCommand(state, command) {
460
+ const malformed = validateCommand(command);
461
+ if (malformed !== void 0) return rejectCommand(state, command, malformed);
462
+ if (command.commandId !== void 0) {
463
+ if (state.seenCommandIds.has(command.commandId)) {
464
+ return duplicateCommandEvents(state, command, "duplicate commandId");
465
+ }
466
+ state.seenCommandIds.add(command.commandId);
373
467
  }
374
- const commands = graph.node(
375
- [source],
376
- (ctx) => {
377
- for (const value of depBatch(ctx, 0) ?? []) {
378
- try {
379
- const key = opts.keyOf?.(value);
380
- ctx.down([
381
- [
382
- "DATA",
383
- {
384
- kind: "publish",
385
- topic,
386
- payload: value,
387
- ...key === void 0 ? {} : { key }
388
- }
389
- ]
390
- ]);
391
- } catch (error) {
392
- ctx.down([
393
- [
394
- "DATA",
395
- {
396
- kind: "invalid",
397
- topic,
398
- error: error instanceof Error ? error.message : String(error)
399
- }
400
- ]
401
- ]);
402
- }
468
+ if (command.kind === "publish" && command.idempotencyKey !== void 0 && state.seenIdempotencyKeys.has(idempotencyKey(command.topic, command.idempotencyKey))) {
469
+ return duplicateCommandEvents(state, command, "duplicate idempotencyKey");
470
+ }
471
+ switch (command.kind) {
472
+ case "ensure-topic":
473
+ return ensureTopic(state, command.topic, command.commandId);
474
+ case "close-topic": {
475
+ const topic = state.topics.get(command.topic);
476
+ if (topic === void 0) {
477
+ return issueEvents(state, command, "unknown-topic", `unknown topic '${command.topic}'`);
403
478
  }
404
- },
405
- {
406
- name: opts.name ?? `${state.name}/${topic}/toHubTopic`,
407
- factory: "toHubTopic",
408
- meta: { topic },
409
- completeWhenDepsComplete: false,
410
- errorWhenDepsError: false
479
+ topic.closed = true;
480
+ return [
481
+ {
482
+ kind: "status",
483
+ status: statusFact(state, "topic-closed", {
484
+ topic: command.topic,
485
+ commandId: command.commandId
486
+ })
487
+ }
488
+ ];
411
489
  }
412
- );
413
- const commandSource = commands;
414
- state.commandSources.push(commandSource);
415
- try {
416
- hub.command.replaceDeps([...state.commandSources], state.commandBody);
417
- } catch (error) {
418
- state.commandSources.splice(state.commandSources.indexOf(commandSource), 1);
419
- releaseGraphNodes(graph, [commands], {
420
- reason: "toHubTopic failed command wiring"
421
- });
422
- throw error;
490
+ case "topic-policy":
491
+ state.topicPolicy = command.topicPolicy;
492
+ return [
493
+ {
494
+ kind: "status",
495
+ status: statusFact(state, "projection-ready", {
496
+ commandId: command.commandId,
497
+ details: { topicPolicy: command.topicPolicy }
498
+ })
499
+ }
500
+ ];
501
+ case "publish":
502
+ return publishMessage(state, command);
503
+ case "ack":
504
+ return ackSubscription(state, command);
505
+ case "seek":
506
+ return seekSubscription(state, command);
507
+ case "close-subscription":
508
+ return closeSubscription(state, command);
423
509
  }
424
- return { commands };
425
510
  }
426
- function fromTopic(bus, topic) {
427
- return bus.topic(topic);
511
+ function publishMessage(state, command) {
512
+ let topic = state.topics.get(command.topic);
513
+ const events = [];
514
+ if (topic === void 0) {
515
+ if (state.topicPolicy !== "create-as-fact") {
516
+ return issueEvents(state, command, "unknown-topic", `unknown topic '${command.topic}'`);
517
+ }
518
+ events.push(...ensureTopic(state, command.topic, command.commandId));
519
+ topic = state.topics.get(command.topic);
520
+ }
521
+ if (topic === void 0)
522
+ return issueEvents(state, command, "unknown-topic", `unknown topic '${command.topic}'`);
523
+ if (topic.closed)
524
+ return issueEvents(state, command, "closed-topic", `closed topic '${command.topic}'`);
525
+ const message = {
526
+ topic: command.topic,
527
+ seq: topic.nextSeq++,
528
+ payload: command.payload,
529
+ timestampMs: state.now(),
530
+ ...command.key === void 0 ? {} : { key: command.key },
531
+ ...command.idempotencyKey === void 0 ? {} : { idempotencyKey: command.idempotencyKey },
532
+ ...command.commandId === void 0 ? {} : { commandId: command.commandId }
533
+ };
534
+ if (command.idempotencyKey !== void 0) {
535
+ state.seenIdempotencyKeys.add(idempotencyKey(command.topic, command.idempotencyKey));
536
+ }
537
+ topic.messages.push(message);
538
+ events.push({ kind: "message", message });
539
+ events.push({
540
+ kind: "status",
541
+ status: statusFact(state, "message-published", {
542
+ topic: command.topic,
543
+ seq: message.seq,
544
+ commandId: command.commandId
545
+ })
546
+ });
547
+ events.push(...trimRetention(state, command.topic, topic));
548
+ return events;
428
549
  }
429
- function toTopic(graph, source, bus, topic, opts = {}) {
430
- const state = busStates.get(bus);
431
- if (state === void 0) throw new Error("toTopic: unknown message bus implementation");
432
- if (state.graph !== graph) throw new Error("toTopic: bus and source graph must match");
433
- const record = requireTopicRecord(state, topic);
434
- const name = opts.name ?? `toTopic/${topic}`;
435
- const producer = graph.node(
436
- [source],
437
- (ctx) => {
438
- for (const value of depBatch(ctx, 0) ?? []) {
439
- try {
440
- const key = opts.keyOf?.(value);
441
- const envelope = {
442
- topic,
443
- seq: ++state.seq,
444
- payload: value,
445
- timestampMs: state.now(),
446
- ...key === void 0 ? {} : { key }
447
- };
448
- ctx.down([
449
- [
450
- "DATA",
451
- {
452
- kind: "publish",
453
- envelope
454
- }
455
- ]
456
- ]);
457
- } catch (error) {
458
- ctx.down([["DATA", { kind: "error", topic, error }]]);
459
- }
460
- }
461
- },
550
+ function ensureTopic(state, topic, commandId) {
551
+ assertTopicKey(topic, "messageBus");
552
+ if (!state.topics.has(topic)) state.topics.set(topic, makeTopicState());
553
+ return [
462
554
  {
463
- name,
464
- factory: "toTopic",
465
- meta: { topic },
466
- completeWhenDepsComplete: false,
467
- errorWhenDepsError: false
555
+ kind: "status",
556
+ status: statusFact(state, "topic-created", { topic, commandId })
468
557
  }
469
- );
470
- record.producers.push(producer);
471
- record.node.replaceDeps([...record.producers], record.body);
472
- const events = graph.node(
473
- [producer, source],
474
- (ctx) => {
475
- for (const produced of depBatch(ctx, 0) ?? []) {
476
- const typed = produced;
477
- if (typed.kind === "publish") {
478
- ctx.down([
479
- [
480
- "DATA",
481
- {
482
- kind: "publish",
483
- topic,
484
- seq: typed.envelope.seq,
485
- payload: typed.envelope.payload
486
- }
487
- ]
488
- ]);
489
- } else {
490
- ctx.down([
491
- ["DATA", { kind: "error", topic, error: typed.error }]
492
- ]);
493
- }
494
- }
495
- const terminal = ctx.terminal[0];
496
- if (terminal !== false && terminal !== void 0 && terminal !== true) {
497
- ctx.down([
498
- ["DATA", { kind: "error", topic, error: terminal }]
499
- ]);
500
- }
501
- const sourceTerminal = ctx.terminal[1];
502
- if (sourceTerminal === true) {
503
- ctx.down([["DATA", { kind: "complete", topic }]]);
504
- } else if (sourceTerminal !== false && sourceTerminal !== void 0) {
505
- ctx.down([
506
- ["DATA", { kind: "error", topic, error: sourceTerminal }]
507
- ]);
508
- }
509
- },
558
+ ];
559
+ }
560
+ function trimRetention(state, topicName, topic) {
561
+ const max = state.retention.maxMessages;
562
+ if (max === void 0 || topic.messages.length <= max) return [];
563
+ if (!Number.isInteger(max) || max < 1) {
564
+ return issueEvents(
565
+ state,
566
+ { kind: "publish", topic: topicName, payload: void 0 },
567
+ "policy-rejected",
568
+ "retention.maxMessages must be a positive integer"
569
+ );
570
+ }
571
+ const trimCount = topic.messages.length - max;
572
+ topic.messages.splice(0, trimCount);
573
+ topic.headSeq = topic.messages[0]?.seq ?? topic.nextSeq;
574
+ const events = [
510
575
  {
511
- name: `${name}/events`,
512
- factory: "toTopicEvents",
513
- meta: { topic },
514
- completeWhenDepsComplete: false,
515
- errorWhenDepsError: false,
516
- terminalAsRealInput: true
576
+ kind: "status",
577
+ status: statusFact(state, "retention-trimmed", {
578
+ topic: topicName,
579
+ headSeq: topic.headSeq,
580
+ details: { trimCount }
581
+ })
517
582
  }
518
- );
519
- return { events };
520
- }
521
- function requireTopic(state, topic) {
522
- return requireTopicRecord(state, topic).node;
523
- }
524
- function requireTopicRecord(state, topic) {
525
- const record = state.records.get(topic);
526
- if (record === void 0) throw new Error(`messageBus: unknown topic '${topic}'`);
527
- return record;
528
- }
529
- function uniqueTopics(topics, maxTopicLength) {
530
- const unique = [...new Set(topics)];
531
- if (unique.length !== topics.length) throw new Error("dynamicHub: duplicate topic");
532
- for (const topic of unique) assertTopicKey(topic, "dynamicHub", maxTopicLength);
533
- return Object.freeze(unique.sort());
534
- }
535
- function normalizeUnknownTopicPolicy(policy) {
536
- if (policy === void 0) return "error";
537
- if (policy === "drop" || policy === "error" || policy === "dead-letter" || policy === "create-as-fact") {
538
- return policy;
539
- }
540
- throw new Error("dynamicHub: unknownTopic policy is not recognized");
541
- }
542
- function normalizeHubLimit(value, fallback, owner) {
543
- if (value === void 0) return fallback;
544
- if (!Number.isInteger(value) || value < 1) {
545
- throw new Error(`${owner} must be a positive integer`);
583
+ ];
584
+ for (const sub of state.subscriptions.values()) {
585
+ if (sub.closed || sub.topic !== topicName || sub.nextSeq >= topic.headSeq) continue;
586
+ sub.retentionGap = true;
587
+ events.push(
588
+ ...issueEvents(
589
+ state,
590
+ {
591
+ kind: "seek",
592
+ topic: topicName,
593
+ subscriptionId: sub.subscriptionId,
594
+ nextSeq: sub.nextSeq
595
+ },
596
+ "retention-gap",
597
+ `subscription '${sub.subscriptionId}' is before retained headSeq`
598
+ )
599
+ );
546
600
  }
547
- return value;
601
+ return events;
602
+ }
603
+ function ackSubscription(state, command) {
604
+ const topic = state.topics.get(command.topic);
605
+ if (topic === void 0)
606
+ return issueEvents(state, command, "unknown-topic", `unknown topic '${command.topic}'`);
607
+ const sub = getSubscription(state, command.topic, command.subscriptionId);
608
+ if (sub === void 0)
609
+ return issueEvents(
610
+ state,
611
+ command,
612
+ "unknown-subscription",
613
+ `unknown subscription '${command.subscriptionId}'`
614
+ );
615
+ if (sub.closed)
616
+ return issueEvents(state, command, "subscription-closed", "subscription is closed");
617
+ if (sub.retentionGap)
618
+ return issueEvents(state, command, "retention-gap", "subscription must seek before ack");
619
+ if (command.seq < sub.nextSeq)
620
+ return issueEvents(state, command, "source-cursor-stale", "ack is behind subscription cursor");
621
+ if (command.seq >= topic.nextSeq)
622
+ return issueEvents(state, command, "cursor-out-of-range", "ack is beyond topic tail");
623
+ sub.nextSeq = Math.max(sub.nextSeq, command.seq + 1);
624
+ return [
625
+ {
626
+ kind: "status",
627
+ status: statusFact(state, "subscription-acked", {
628
+ topic: command.topic,
629
+ subscriptionId: command.subscriptionId,
630
+ nextSeq: sub.nextSeq,
631
+ commandId: command.commandId
632
+ })
633
+ }
634
+ ];
635
+ }
636
+ function seekSubscription(state, command) {
637
+ const topic = state.topics.get(command.topic);
638
+ if (topic === void 0)
639
+ return issueEvents(state, command, "unknown-topic", `unknown topic '${command.topic}'`);
640
+ const sub = getSubscription(state, command.topic, command.subscriptionId);
641
+ if (sub === void 0)
642
+ return issueEvents(
643
+ state,
644
+ command,
645
+ "unknown-subscription",
646
+ `unknown subscription '${command.subscriptionId}'`
647
+ );
648
+ if (sub.closed)
649
+ return issueEvents(state, command, "subscription-closed", "subscription is closed");
650
+ if (command.nextSeq < topic.headSeq)
651
+ return issueEvents(state, command, "retention-gap", "seek is before retained headSeq");
652
+ if (command.nextSeq > topic.nextSeq)
653
+ return issueEvents(state, command, "cursor-out-of-range", "seek is beyond topic tail");
654
+ sub.nextSeq = command.nextSeq;
655
+ sub.retentionGap = false;
656
+ return [
657
+ {
658
+ kind: "status",
659
+ status: statusFact(state, "subscription-sought", {
660
+ topic: command.topic,
661
+ subscriptionId: command.subscriptionId,
662
+ nextSeq: sub.nextSeq,
663
+ commandId: command.commandId
664
+ })
665
+ }
666
+ ];
667
+ }
668
+ function closeSubscription(state, command) {
669
+ if (!state.topics.has(command.topic))
670
+ return issueEvents(state, command, "unknown-topic", `unknown topic '${command.topic}'`);
671
+ const sub = getSubscription(state, command.topic, command.subscriptionId);
672
+ if (sub === void 0)
673
+ return issueEvents(
674
+ state,
675
+ command,
676
+ "unknown-subscription",
677
+ `unknown subscription '${command.subscriptionId}'`
678
+ );
679
+ sub.closed = true;
680
+ return [
681
+ {
682
+ kind: "status",
683
+ status: statusFact(state, "subscription-closed", {
684
+ topic: command.topic,
685
+ subscriptionId: command.subscriptionId,
686
+ nextSeq: sub.nextSeq,
687
+ commandId: command.commandId
688
+ })
689
+ }
690
+ ];
691
+ }
692
+ function issueEvents(state, command, code, message) {
693
+ const topic = commandTopic(command);
694
+ const commandId = commandIdOf(command);
695
+ const issue = {
696
+ kind: "issue",
697
+ code,
698
+ message,
699
+ severity: "error",
700
+ source: "messageBus",
701
+ details: command,
702
+ metadata: topic === void 0 ? void 0 : { topic }
703
+ };
704
+ const entry = {
705
+ entrySeq: ++state.deadLetterSeq,
706
+ ...topic === void 0 ? {} : { topic },
707
+ ...isObjectRecord(command) ? { command } : {},
708
+ issue,
709
+ timestampMs: state.now()
710
+ };
711
+ state.deadLetters.push(entry);
712
+ return [
713
+ { kind: "issue", issue },
714
+ { kind: "dead-letter", entry },
715
+ {
716
+ kind: "status",
717
+ status: statusFact(state, "command-rejected", {
718
+ topic,
719
+ commandId,
720
+ issueCode: code
721
+ })
722
+ }
723
+ ];
548
724
  }
549
- function publishHubCommand(commandNode, command) {
550
- commandNode.down([["DATA", command]]);
551
- return command;
725
+ function rejectCommand(state, command, reason) {
726
+ return issueEvents(state, command, "malformed-command", reason);
552
727
  }
553
- function parseDynamicHubCommand(value) {
554
- if (!isObjectRecord(value)) return "dynamicHub: command fact must be an object";
555
- const kind = value.kind;
556
- if (kind !== "create" && kind !== "delete" && kind !== "publish" && kind !== "subscribe" && kind !== "close") {
557
- return "dynamicHub: command kind is not recognized";
558
- }
559
- if (kind === "close") {
560
- return {
561
- kind,
562
- ...typeof value.key === "string" ? { key: value.key } : {},
563
- ...value.payload === void 0 ? {} : { payload: value.payload }
564
- };
565
- }
566
- const topic = value.topic;
567
- if (typeof topic !== "string" || topic.length === 0) {
568
- return "dynamicHub: command topic must be a non-empty string";
569
- }
570
- if (kind === "publish") {
571
- if (value.payload === void 0) return "dynamicHub: publish command payload is required";
572
- return {
573
- kind,
574
- topic,
575
- payload: value.payload,
576
- ...typeof value.key === "string" ? { key: value.key } : {}
577
- };
728
+ function duplicateCommandEvents(state, command, message) {
729
+ const status = statusFact(state, "duplicate-command", {
730
+ topic: "topic" in command ? command.topic : void 0,
731
+ commandId: command.commandId
732
+ });
733
+ if (state.dedupe.commandId === "issue") {
734
+ return [
735
+ { kind: "status", status },
736
+ ...issueEvents(state, command, "duplicate-command", message)
737
+ ];
578
738
  }
739
+ return [{ kind: "status", status }];
740
+ }
741
+ function statusFact(state, kind, fields) {
579
742
  return {
580
743
  kind,
581
- topic,
582
- ...typeof value.key === "string" ? { key: value.key } : {},
583
- ...value.payload === void 0 ? {} : { payload: value.payload }
744
+ timestampMs: state.now(),
745
+ ...fields.topic === void 0 ? {} : { topic: fields.topic },
746
+ ...fields.seq === void 0 ? {} : { seq: fields.seq },
747
+ ...fields.headSeq === void 0 ? {} : { headSeq: fields.headSeq },
748
+ ...fields.subscriptionId === void 0 ? {} : { subscriptionId: fields.subscriptionId },
749
+ ...fields.nextSeq === void 0 ? {} : { nextSeq: fields.nextSeq },
750
+ ...fields.commandId === void 0 ? {} : { commandId: fields.commandId },
751
+ ...fields.issueCode === void 0 ? {} : { issueCode: fields.issueCode },
752
+ ...fields.details === void 0 ? {} : { details: fields.details }
584
753
  };
585
754
  }
586
- function isObjectRecord(value) {
587
- return typeof value === "object" && value !== null && !Array.isArray(value);
588
- }
589
- function readHubTimestamp(now) {
590
- try {
591
- const timestampMs = now();
592
- return Number.isFinite(timestampMs) ? timestampMs : "dynamicHub: now() must return a finite number";
593
- } catch (error) {
594
- return `dynamicHub: now() threw: ${error instanceof Error ? error.message : String(error)}`;
755
+ function validateCommand(command) {
756
+ if (!isObjectRecord(command)) return "command must be an object";
757
+ if (typeof command.kind !== "string") return "command kind is required";
758
+ if (!isMessageBusCommandKind(command.kind)) return `unknown command kind '${command.kind}'`;
759
+ if ("topic" in command) {
760
+ if (typeof command.topic !== "string") return "messageBus: topic must be a non-empty string";
761
+ const topicError = validateTopicKey(command.topic, "messageBus");
762
+ if (topicError !== void 0) return topicError;
595
763
  }
596
- }
597
- function timestampOrFallback(now) {
598
- const timestampMs = readHubTimestamp(now);
599
- return typeof timestampMs === "number" ? timestampMs : Date.now();
600
- }
601
- function reduceDynamicHubCommand(runtime, command, unknownTopic, maxTopics, maxTopicLength, now) {
602
- const topic = "topic" in command ? command.topic : void 0;
603
- if (topic !== void 0) {
604
- const topicError = validateTopicKey(topic, "dynamicHub", maxTopicLength);
605
- if (topicError !== void 0) {
606
- return [hubError(runtime, command, topicError, timestampOrFallback(now))];
607
- }
764
+ if ("subscriptionId" in command && typeof command.subscriptionId !== "string") {
765
+ return "subscriptionId must be a string";
608
766
  }
609
- if (!runtime.open && command.kind !== "close") {
610
- return [hubError(runtime, command, "dynamicHub: hub is closed", timestampOrFallback(now))];
767
+ if (command.kind === "publish" && command.payload === void 0)
768
+ return "publish payload is required";
769
+ if (command.kind === "ack" && !isPositiveSeq(command.seq))
770
+ return "ack seq must be a positive integer";
771
+ if (command.kind === "seek" && !isPositiveSeq(command.nextSeq)) {
772
+ return "seek nextSeq must be a positive integer";
611
773
  }
612
- switch (command.kind) {
613
- case "create": {
614
- const capacityError = canAddHubTopic(runtime, command.topic, maxTopics);
615
- if (capacityError !== void 0) {
616
- return [hubError(runtime, command, capacityError, timestampOrFallback(now))];
617
- }
618
- const timestampMs = readHubTimestamp(now);
619
- if (typeof timestampMs === "string")
620
- return [hubError(runtime, command, timestampMs, Date.now())];
621
- addHubTopic(runtime, command.topic);
622
- return [hubEvent(runtime, timestampMs, command)];
623
- }
624
- case "delete": {
625
- if (!hasHubTopic(runtime, command.topic)) {
626
- return unknownHubTopic(runtime, command, unknownTopic, now);
627
- }
628
- const timestampMs = readHubTimestamp(now);
629
- if (typeof timestampMs === "string")
630
- return [hubError(runtime, command, timestampMs, Date.now())];
631
- deleteHubTopic(runtime, command.topic);
632
- return [hubEvent(runtime, timestampMs, command)];
633
- }
634
- case "publish": {
635
- if (!hasHubTopic(runtime, command.topic)) {
636
- if (unknownTopic === "create-as-fact") {
637
- const capacityError = canAddHubTopic(runtime, command.topic, maxTopics);
638
- if (capacityError !== void 0) {
639
- return [hubError(runtime, command, capacityError, timestampOrFallback(now))];
640
- }
641
- const createTimestampMs = readHubTimestamp(now);
642
- if (typeof createTimestampMs === "string") {
643
- return [hubError(runtime, command, createTimestampMs, Date.now())];
644
- }
645
- const messageTimestampMs = readHubTimestamp(now);
646
- if (typeof messageTimestampMs === "string") {
647
- return [hubError(runtime, command, messageTimestampMs, Date.now())];
648
- }
649
- addHubTopic(runtime, command.topic);
650
- return [
651
- hubEvent(runtime, createTimestampMs, { kind: "create", topic: command.topic }),
652
- hubEvent(runtime, messageTimestampMs, {
653
- kind: "message",
654
- topic: command.topic,
655
- payload: command.payload,
656
- ...command.key === void 0 ? {} : { key: command.key }
657
- })
658
- ];
659
- }
660
- return unknownHubTopic(runtime, command, unknownTopic, now);
661
- }
662
- const timestampMs = readHubTimestamp(now);
663
- if (typeof timestampMs === "string")
664
- return [hubError(runtime, command, timestampMs, Date.now())];
665
- return [
666
- hubEvent(runtime, timestampMs, {
667
- kind: "message",
668
- topic: command.topic,
669
- payload: command.payload,
670
- ...command.key === void 0 ? {} : { key: command.key }
671
- })
672
- ];
673
- }
674
- case "subscribe": {
675
- if (!hasHubTopic(runtime, command.topic)) {
676
- if (unknownTopic === "create-as-fact") {
677
- const capacityError = canAddHubTopic(runtime, command.topic, maxTopics);
678
- if (capacityError !== void 0) {
679
- return [hubError(runtime, command, capacityError, timestampOrFallback(now))];
680
- }
681
- const createTimestampMs = readHubTimestamp(now);
682
- if (typeof createTimestampMs === "string") {
683
- return [hubError(runtime, command, createTimestampMs, Date.now())];
684
- }
685
- const subscribeTimestampMs = readHubTimestamp(now);
686
- if (typeof subscribeTimestampMs === "string") {
687
- return [hubError(runtime, command, subscribeTimestampMs, Date.now())];
688
- }
689
- addHubTopic(runtime, command.topic);
690
- return [
691
- hubEvent(runtime, createTimestampMs, { kind: "create", topic: command.topic }),
692
- hubEvent(runtime, subscribeTimestampMs, command)
693
- ];
694
- }
695
- return unknownHubTopic(runtime, command, unknownTopic, now);
696
- }
697
- const timestampMs = readHubTimestamp(now);
698
- if (typeof timestampMs === "string")
699
- return [hubError(runtime, command, timestampMs, Date.now())];
700
- return [hubEvent(runtime, timestampMs, command)];
701
- }
702
- case "close": {
703
- const timestampMs = readHubTimestamp(now);
704
- if (typeof timestampMs === "string")
705
- return [hubError(runtime, command, timestampMs, Date.now())];
706
- runtime.open = false;
707
- return [hubEvent(runtime, timestampMs, command)];
708
- }
774
+ if (command.kind === "topic-policy" && command.topicPolicy !== "strict" && command.topicPolicy !== "create-as-fact") {
775
+ return "topicPolicy is not recognized";
709
776
  }
777
+ return void 0;
778
+ }
779
+ function isMessageBusCommandKind(kind) {
780
+ return kind === "ensure-topic" || kind === "close-topic" || kind === "publish" || kind === "topic-policy" || kind === "ack" || kind === "seek" || kind === "close-subscription";
710
781
  }
711
- function hubEvent(runtime, timestampMs, event) {
712
- const meta = nextHubMetadata(runtime, timestampMs);
782
+ function catalogPage(state, params) {
783
+ const limit = positiveLimit(params.limit);
784
+ const topics = [...state.topics.entries()].filter(
785
+ ([topic, value]) => (params.includeClosed ? true : !value.closed) && (params.afterTopic === void 0 || topic > params.afterTopic)
786
+ ).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
787
+ const page = topics.slice(0, limit);
788
+ const hasMore = topics.length > limit;
713
789
  return {
714
- ...event,
715
- meta,
716
- status: snapshotHubStatus(runtime, meta, event.kind)
790
+ topics: page.map(([topic, value]) => ({
791
+ topic,
792
+ closed: value.closed,
793
+ headSeq: value.headSeq,
794
+ nextSeq: value.nextSeq,
795
+ messageCount: value.messages.length
796
+ })),
797
+ ...hasMore && page.length > 0 ? { nextAfterTopic: page[page.length - 1]?.[0] } : {},
798
+ hasMore
717
799
  };
718
800
  }
719
- function hubError(runtime, command, error, timestampMs) {
720
- return hubEvent(runtime, timestampMs, {
721
- kind: "error",
722
- ...isObjectRecord(command) && typeof command.topic === "string" ? { topic: command.topic } : {},
723
- error,
724
- command
725
- });
801
+ function topicPage(state, topicName, params) {
802
+ const topic = state.topics.get(topicName);
803
+ const start = params.afterSeq === void 0 ? topic?.headSeq ?? 1 : params.afterSeq + 1;
804
+ const all = (topic?.messages ?? []).filter((message) => message.seq >= start);
805
+ const limit = positiveLimit(params.limit);
806
+ const messages = all.slice(0, limit);
807
+ const hasMore = all.length > limit;
808
+ return {
809
+ topic: topicName,
810
+ messages,
811
+ fromSeq: start,
812
+ ...messages.length > 0 ? { throughSeq: messages[messages.length - 1]?.seq } : {},
813
+ ...hasMore && messages.length > 0 ? { nextAfterSeq: messages[messages.length - 1]?.seq } : {},
814
+ hasMore
815
+ };
726
816
  }
727
- function unknownHubTopic(runtime, command, policy, now) {
728
- const topic = "topic" in command ? command.topic : void 0;
729
- const reason = `dynamicHub: unknown topic '${topic ?? ""}'`;
730
- if (policy === "drop") return [];
731
- if (policy === "dead-letter") {
732
- return [
733
- hubEvent(runtime, timestampOrFallback(now), {
734
- kind: "dead-letter",
735
- ...topic === void 0 ? {} : { topic },
736
- reason,
737
- command
738
- })
739
- ];
740
- }
741
- return [hubError(runtime, command, reason, timestampOrFallback(now))];
817
+ function availablePage(state, sub, params) {
818
+ const cursor = cursorSnapshot(state, sub);
819
+ const start = params.afterSeq === void 0 ? sub.nextSeq : params.afterSeq + 1;
820
+ const topic = state.topics.get(sub.topic);
821
+ const all = sub.retentionGap ? [] : (topic?.messages ?? []).filter((message) => message.seq >= start);
822
+ const limit = positiveLimit(params.limit);
823
+ const messages = all.slice(0, limit);
824
+ const hasMore = all.length > limit;
825
+ return {
826
+ topic: sub.topic,
827
+ subscriptionId: sub.subscriptionId,
828
+ cursor,
829
+ messages,
830
+ fromSeq: start,
831
+ ...messages.length > 0 ? { throughSeq: messages[messages.length - 1]?.seq } : {},
832
+ ...hasMore && messages.length > 0 ? { nextAfterSeq: messages[messages.length - 1]?.seq } : {},
833
+ hasMore
834
+ };
742
835
  }
743
- function nextHubMetadata(runtime, timestampMs) {
744
- runtime.seq += 1;
745
- runtime.cursor = runtime.seq;
746
- return { seq: runtime.seq, cursor: runtime.cursor, timestampMs };
836
+ function deadLetterPage(state, params) {
837
+ const limit = positiveLimit(params.limit);
838
+ const entries = state.deadLetters.filter((entry) => {
839
+ if (params.afterEntrySeq !== void 0 && entry.entrySeq <= params.afterEntrySeq) return false;
840
+ if (params.topic !== void 0 && entry.topic !== params.topic) return false;
841
+ if (params.code !== void 0 && entry.issue.code !== params.code) return false;
842
+ return true;
843
+ });
844
+ const page = entries.slice(0, limit);
845
+ const hasMore = entries.length > limit;
846
+ return {
847
+ entries: page,
848
+ ...hasMore && page.length > 0 ? { nextAfterEntrySeq: page[page.length - 1]?.entrySeq } : {},
849
+ hasMore
850
+ };
747
851
  }
748
- function snapshotHubStatus(runtime, meta, kind) {
852
+ function cursorSnapshot(state, sub) {
853
+ const topic = state.topics.get(sub.topic);
749
854
  return {
750
- open: runtime.open,
751
- topics: Object.freeze([...runtime.topics].sort()),
752
- seq: meta.seq,
753
- cursor: meta.cursor,
754
- lastEventKind: kind
855
+ topic: sub.topic,
856
+ subscriptionId: sub.subscriptionId,
857
+ nextSeq: sub.nextSeq,
858
+ closed: sub.closed,
859
+ retentionGap: sub.retentionGap,
860
+ headSeq: topic?.headSeq ?? 1
861
+ };
862
+ }
863
+ function ensureSubscription(state, opts) {
864
+ const key = `${opts.topic}\0${opts.subscriptionId}`;
865
+ const existing = state.subscriptions.get(key);
866
+ if (existing !== void 0) return existing;
867
+ const topic = state.topics.get(opts.topic);
868
+ const nextSeq = opts.from === "earliest" ? topic?.headSeq ?? 1 : opts.from === "latest" ? topic?.nextSeq ?? 1 : opts.from;
869
+ const sub = {
870
+ topic: opts.topic,
871
+ subscriptionId: opts.subscriptionId,
872
+ nextSeq,
873
+ closed: false,
874
+ retentionGap: topic !== void 0 && nextSeq < topic.headSeq
755
875
  };
876
+ state.subscriptions.set(key, sub);
877
+ return sub;
878
+ }
879
+ function getSubscription(state, topic, subscriptionId) {
880
+ return state.subscriptions.get(`${topic}\0${subscriptionId}`);
881
+ }
882
+ function pullParams(pull) {
883
+ return isObjectRecord(pull?.params) ? pull.params : {};
884
+ }
885
+ function publishCommand(commandNode, command) {
886
+ commandNode.down([["DATA", command]]);
887
+ return command;
888
+ }
889
+ function makeTopicState() {
890
+ return { closed: false, headSeq: 1, nextSeq: 1, messages: [] };
891
+ }
892
+ function uniqueTopics(topics) {
893
+ const unique = [...new Set(topics)];
894
+ if (unique.length !== topics.length) throw new Error("messageBus: duplicate topic");
895
+ for (const topic of unique) assertTopicKey(topic, "messageBus");
896
+ return Object.freeze(unique.sort());
756
897
  }
757
- function assertTopicKey(topic, owner, maxTopicLength) {
758
- const error = validateTopicKey(topic, owner, maxTopicLength);
898
+ function assertTopicKey(topic, owner) {
899
+ const error = validateTopicKey(topic, owner);
759
900
  if (error !== void 0) throw new Error(error);
760
901
  }
761
- function validateTopicKey(topic, owner, maxTopicLength) {
762
- if (typeof topic !== "string" || topic.length === 0) {
902
+ function validateTopicKey(topic, owner) {
903
+ if (typeof topic !== "string" || topic.length === 0)
763
904
  return `${owner}: topic must be a non-empty string`;
764
- }
765
- if (topic.length > maxTopicLength) {
766
- return `${owner}: topic exceeds maxTopicLength`;
767
- }
768
905
  return void 0;
769
906
  }
770
- function hasHubTopic(runtime, topic) {
771
- return runtime.topics.includes(topic);
772
- }
773
- function addHubTopic(runtime, topic) {
774
- if (!hasHubTopic(runtime, topic)) runtime.topics = [...runtime.topics, topic];
775
- }
776
- function deleteHubTopic(runtime, topic) {
777
- runtime.topics = runtime.topics.filter((existing) => existing !== topic);
778
- }
779
- function canAddHubTopic(runtime, topic, maxTopics) {
780
- if (hasHubTopic(runtime, topic)) return void 0;
781
- return runtime.topics.length >= maxTopics ? "dynamicHub: topic count exceeds maxTopics" : void 0;
782
- }
783
- function isReachableUpstream(from, target) {
784
- const seen = /* @__PURE__ */ new Set();
785
- const stack = [from];
786
- while (stack.length > 0) {
787
- const node = stack.pop();
788
- if (node === void 0) continue;
789
- if (node === target) return true;
790
- if (seen.has(node)) continue;
791
- seen.add(node);
792
- for (const dep of node.deps) stack.push(dep);
793
- }
794
- return false;
907
+ function assertNonEmpty(value, owner) {
908
+ if (typeof value !== "string" || value.length === 0)
909
+ throw new Error(`${owner}: must be a non-empty string`);
910
+ }
911
+ function positiveLimit(limit) {
912
+ if (limit === void 0) return 100;
913
+ if (!Number.isInteger(limit) || limit < 1)
914
+ throw new Error("messageBus: limit must be a positive integer");
915
+ return limit;
916
+ }
917
+ function isPositiveSeq(value) {
918
+ return Number.isInteger(value) && value >= 1;
919
+ }
920
+ function isObjectRecord(value) {
921
+ return typeof value === "object" && value !== null && !Array.isArray(value);
922
+ }
923
+ function commandTopic(command) {
924
+ return isObjectRecord(command) && typeof command.topic === "string" ? command.topic : void 0;
925
+ }
926
+ function commandIdOf(command) {
927
+ return isObjectRecord(command) && typeof command.commandId === "string" ? command.commandId : void 0;
795
928
  }
796
- function requireDynamicHubState(hub) {
797
- const state = dynamicHubStates.get(hub);
798
- if (state === void 0) throw new Error("dynamicHub: unknown hub implementation");
799
- return state;
929
+ function idempotencyKey(topic, key) {
930
+ return `${topic}\0${key}`;
800
931
  }
801
932
  // Annotate the CommonJS export names for ESM import in node:
802
933
  0 && (module.exports = {
@@ -808,11 +939,11 @@ function requireDynamicHubState(hub) {
808
939
  SPAWNS_TOPIC,
809
940
  STANDARD_TOPICS,
810
941
  TODOS_TOPIC,
811
- dynamicHub,
812
- fromHubTopic,
942
+ eventMessage,
943
+ eventMessageIssue,
813
944
  fromTopic,
945
+ isEventMessage,
814
946
  messageBus,
815
- toHubTopic,
816
947
  toTopic
817
948
  });
818
949
  //# sourceMappingURL=index.cjs.map