@justscale/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (784) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -0
  3. package/bin/just.js +34 -0
  4. package/dist/app.d.ts +56 -0
  5. package/dist/app.d.ts.map +1 -0
  6. package/dist/app.js +182 -0
  7. package/dist/app.js.map +1 -0
  8. package/dist/builder/build-context.d.ts +8 -0
  9. package/dist/builder/build-context.d.ts.map +1 -0
  10. package/dist/builder/build-context.js +4 -0
  11. package/dist/builder/build-context.js.map +1 -0
  12. package/dist/builder/builder.d.ts +60 -0
  13. package/dist/builder/builder.d.ts.map +1 -0
  14. package/dist/builder/builder.js +72 -0
  15. package/dist/builder/builder.js.map +1 -0
  16. package/dist/builder/create-builder.d.ts +36 -0
  17. package/dist/builder/create-builder.d.ts.map +1 -0
  18. package/dist/builder/create-builder.js +66 -0
  19. package/dist/builder/create-builder.js.map +1 -0
  20. package/dist/builder/execute.d.ts +37 -0
  21. package/dist/builder/execute.d.ts.map +1 -0
  22. package/dist/builder/execute.js +62 -0
  23. package/dist/builder/execute.js.map +1 -0
  24. package/dist/builder/feature-builder.d.ts +147 -0
  25. package/dist/builder/feature-builder.d.ts.map +1 -0
  26. package/dist/builder/feature-builder.js +138 -0
  27. package/dist/builder/feature-builder.js.map +1 -0
  28. package/dist/builder/index.d.ts +35 -0
  29. package/dist/builder/index.d.ts.map +1 -0
  30. package/dist/builder/index.js +37 -0
  31. package/dist/builder/index.js.map +1 -0
  32. package/dist/builder/plugin.d.ts +90 -0
  33. package/dist/builder/plugin.d.ts.map +1 -0
  34. package/dist/builder/plugin.js +101 -0
  35. package/dist/builder/plugin.js.map +1 -0
  36. package/dist/builder/plugins/query.d.ts +31 -0
  37. package/dist/builder/plugins/query.d.ts.map +1 -0
  38. package/dist/builder/plugins/query.js +42 -0
  39. package/dist/builder/plugins/query.js.map +1 -0
  40. package/dist/builder/plugins/validation.d.ts +12 -0
  41. package/dist/builder/plugins/validation.d.ts.map +1 -0
  42. package/dist/builder/plugins/validation.js +12 -0
  43. package/dist/builder/plugins/validation.js.map +1 -0
  44. package/dist/builder/sort.d.ts +27 -0
  45. package/dist/builder/sort.d.ts.map +1 -0
  46. package/dist/builder/sort.js +210 -0
  47. package/dist/builder/sort.js.map +1 -0
  48. package/dist/builder/stop.d.ts +24 -0
  49. package/dist/builder/stop.d.ts.map +1 -0
  50. package/dist/builder/stop.js +27 -0
  51. package/dist/builder/stop.js.map +1 -0
  52. package/dist/builder/test/permits-type-spike.d.ts +8 -0
  53. package/dist/builder/test/permits-type-spike.d.ts.map +1 -0
  54. package/dist/builder/test/permits-type-spike.js +117 -0
  55. package/dist/builder/test/permits-type-spike.js.map +1 -0
  56. package/dist/builder/types.d.ts +678 -0
  57. package/dist/builder/types.d.ts.map +1 -0
  58. package/dist/builder/types.js +98 -0
  59. package/dist/builder/types.js.map +1 -0
  60. package/dist/builder/validation.d.ts +101 -0
  61. package/dist/builder/validation.d.ts.map +1 -0
  62. package/dist/builder/validation.js +335 -0
  63. package/dist/builder/validation.js.map +1 -0
  64. package/dist/cli/adapter.d.ts +23 -0
  65. package/dist/cli/adapter.d.ts.map +1 -0
  66. package/dist/cli/adapter.js +26 -0
  67. package/dist/cli/adapter.js.map +1 -0
  68. package/dist/cli/args.d.ts +150 -0
  69. package/dist/cli/args.d.ts.map +1 -0
  70. package/dist/cli/args.js +172 -0
  71. package/dist/cli/args.js.map +1 -0
  72. package/dist/cli/assemble.d.ts +20 -0
  73. package/dist/cli/assemble.d.ts.map +1 -0
  74. package/dist/cli/assemble.js +55 -0
  75. package/dist/cli/assemble.js.map +1 -0
  76. package/dist/cli/bin/main.d.ts +26 -0
  77. package/dist/cli/bin/main.d.ts.map +1 -0
  78. package/dist/cli/bin/main.js +475 -0
  79. package/dist/cli/bin/main.js.map +1 -0
  80. package/dist/cli/build/migrations-plugin.d.ts +21 -0
  81. package/dist/cli/build/migrations-plugin.d.ts.map +1 -0
  82. package/dist/cli/build/migrations-plugin.js +41 -0
  83. package/dist/cli/build/migrations-plugin.js.map +1 -0
  84. package/dist/cli/build/process-plugin.d.ts +29 -0
  85. package/dist/cli/build/process-plugin.d.ts.map +1 -0
  86. package/dist/cli/build/process-plugin.js +66 -0
  87. package/dist/cli/build/process-plugin.js.map +1 -0
  88. package/dist/cli/builder/create-cli-builder.d.ts +42 -0
  89. package/dist/cli/builder/create-cli-builder.d.ts.map +1 -0
  90. package/dist/cli/builder/create-cli-builder.js +104 -0
  91. package/dist/cli/builder/create-cli-builder.js.map +1 -0
  92. package/dist/cli/builder/index.d.ts +8 -0
  93. package/dist/cli/builder/index.d.ts.map +1 -0
  94. package/dist/cli/builder/index.js +7 -0
  95. package/dist/cli/builder/index.js.map +1 -0
  96. package/dist/cli/builder/types.d.ts +113 -0
  97. package/dist/cli/builder/types.d.ts.map +1 -0
  98. package/dist/cli/builder/types.js +7 -0
  99. package/dist/cli/builder/types.js.map +1 -0
  100. package/dist/cli/cluster.d.ts +8 -0
  101. package/dist/cli/cluster.d.ts.map +1 -0
  102. package/dist/cli/cluster.js +145 -0
  103. package/dist/cli/cluster.js.map +1 -0
  104. package/dist/cli/current-app.d.ts +36 -0
  105. package/dist/cli/current-app.d.ts.map +1 -0
  106. package/dist/cli/current-app.js +21 -0
  107. package/dist/cli/current-app.js.map +1 -0
  108. package/dist/cli/define-app.d.ts +35 -0
  109. package/dist/cli/define-app.d.ts.map +1 -0
  110. package/dist/cli/define-app.js +79 -0
  111. package/dist/cli/define-app.js.map +1 -0
  112. package/dist/cli/define-main.d.ts +33 -0
  113. package/dist/cli/define-main.d.ts.map +1 -0
  114. package/dist/cli/define-main.js +67 -0
  115. package/dist/cli/define-main.js.map +1 -0
  116. package/dist/cli/define-project.d.ts +93 -0
  117. package/dist/cli/define-project.d.ts.map +1 -0
  118. package/dist/cli/define-project.js +85 -0
  119. package/dist/cli/define-project.js.map +1 -0
  120. package/dist/cli/dev-server.d.ts +20 -0
  121. package/dist/cli/dev-server.d.ts.map +1 -0
  122. package/dist/cli/dev-server.js +131 -0
  123. package/dist/cli/dev-server.js.map +1 -0
  124. package/dist/cli/discovery.d.ts +29 -0
  125. package/dist/cli/discovery.d.ts.map +1 -0
  126. package/dist/cli/discovery.js +142 -0
  127. package/dist/cli/discovery.js.map +1 -0
  128. package/dist/cli/factory.d.ts +43 -0
  129. package/dist/cli/factory.d.ts.map +1 -0
  130. package/dist/cli/factory.js +52 -0
  131. package/dist/cli/factory.js.map +1 -0
  132. package/dist/cli/generators/ai.d.ts +3 -0
  133. package/dist/cli/generators/ai.d.ts.map +1 -0
  134. package/dist/cli/generators/ai.js +65 -0
  135. package/dist/cli/generators/ai.js.map +1 -0
  136. package/dist/cli/generators/ci.d.ts +5 -0
  137. package/dist/cli/generators/ci.d.ts.map +1 -0
  138. package/dist/cli/generators/ci.js +102 -0
  139. package/dist/cli/generators/ci.js.map +1 -0
  140. package/dist/cli/generators/detect.d.ts +15 -0
  141. package/dist/cli/generators/detect.d.ts.map +1 -0
  142. package/dist/cli/generators/detect.js +75 -0
  143. package/dist/cli/generators/detect.js.map +1 -0
  144. package/dist/cli/generators/ide.d.ts +3 -0
  145. package/dist/cli/generators/ide.d.ts.map +1 -0
  146. package/dist/cli/generators/ide.js +179 -0
  147. package/dist/cli/generators/ide.js.map +1 -0
  148. package/dist/cli/generators/index.d.ts +5 -0
  149. package/dist/cli/generators/index.d.ts.map +1 -0
  150. package/dist/cli/generators/index.js +5 -0
  151. package/dist/cli/generators/index.js.map +1 -0
  152. package/dist/cli/index.d.ts +81 -0
  153. package/dist/cli/index.d.ts.map +1 -0
  154. package/dist/cli/index.js +88 -0
  155. package/dist/cli/index.js.map +1 -0
  156. package/dist/cli/io.d.ts +131 -0
  157. package/dist/cli/io.d.ts.map +1 -0
  158. package/dist/cli/io.js +373 -0
  159. package/dist/cli/io.js.map +1 -0
  160. package/dist/cli/mcp/server.d.ts +23 -0
  161. package/dist/cli/mcp/server.d.ts.map +1 -0
  162. package/dist/cli/mcp/server.js +148 -0
  163. package/dist/cli/mcp/server.js.map +1 -0
  164. package/dist/cli/parser.d.ts +106 -0
  165. package/dist/cli/parser.d.ts.map +1 -0
  166. package/dist/cli/parser.js +520 -0
  167. package/dist/cli/parser.js.map +1 -0
  168. package/dist/cli/runner.d.ts +75 -0
  169. package/dist/cli/runner.d.ts.map +1 -0
  170. package/dist/cli/runner.js +422 -0
  171. package/dist/cli/runner.js.map +1 -0
  172. package/dist/cli/service.d.ts +61 -0
  173. package/dist/cli/service.d.ts.map +1 -0
  174. package/dist/cli/service.js +95 -0
  175. package/dist/cli/service.js.map +1 -0
  176. package/dist/cli/types.d.ts +47 -0
  177. package/dist/cli/types.d.ts.map +1 -0
  178. package/dist/cli/types.js +20 -0
  179. package/dist/cli/types.js.map +1 -0
  180. package/dist/cli/wizard.d.ts +11 -0
  181. package/dist/cli/wizard.d.ts.map +1 -0
  182. package/dist/cli/wizard.js +2 -0
  183. package/dist/cli/wizard.js.map +1 -0
  184. package/dist/cli/workspace-controller.d.ts +36 -0
  185. package/dist/cli/workspace-controller.d.ts.map +1 -0
  186. package/dist/cli/workspace-controller.js +896 -0
  187. package/dist/cli/workspace-controller.js.map +1 -0
  188. package/dist/cluster/client.d.ts +101 -0
  189. package/dist/cluster/client.d.ts.map +1 -0
  190. package/dist/cluster/client.js +256 -0
  191. package/dist/cluster/client.js.map +1 -0
  192. package/dist/cluster/cluster.d.ts +82 -0
  193. package/dist/cluster/cluster.d.ts.map +1 -0
  194. package/dist/cluster/cluster.js +27 -0
  195. package/dist/cluster/cluster.js.map +1 -0
  196. package/dist/cluster/coordinator/cluster-node.model.d.ts +14 -0
  197. package/dist/cluster/coordinator/cluster-node.model.d.ts.map +1 -0
  198. package/dist/cluster/coordinator/cluster-node.model.js +15 -0
  199. package/dist/cluster/coordinator/cluster-node.model.js.map +1 -0
  200. package/dist/cluster/coordinator/cluster-signals.d.ts +45 -0
  201. package/dist/cluster/coordinator/cluster-signals.d.ts.map +1 -0
  202. package/dist/cluster/coordinator/cluster-signals.js +24 -0
  203. package/dist/cluster/coordinator/cluster-signals.js.map +1 -0
  204. package/dist/cluster/coordinator/coordinator.process.d.ts +21 -0
  205. package/dist/cluster/coordinator/coordinator.process.d.ts.map +1 -0
  206. package/dist/cluster/coordinator/coordinator.process.js +221 -0
  207. package/dist/cluster/coordinator/coordinator.process.js.map +1 -0
  208. package/dist/cluster/coordinator/index.d.ts +13 -0
  209. package/dist/cluster/coordinator/index.d.ts.map +1 -0
  210. package/dist/cluster/coordinator/index.js +13 -0
  211. package/dist/cluster/coordinator/index.js.map +1 -0
  212. package/dist/cluster/coordinator/node-lifecycle.d.ts +31 -0
  213. package/dist/cluster/coordinator/node-lifecycle.d.ts.map +1 -0
  214. package/dist/cluster/coordinator/node-lifecycle.js +178 -0
  215. package/dist/cluster/coordinator/node-lifecycle.js.map +1 -0
  216. package/dist/cluster/index.d.ts +45 -0
  217. package/dist/cluster/index.d.ts.map +1 -0
  218. package/dist/cluster/index.js +57 -0
  219. package/dist/cluster/index.js.map +1 -0
  220. package/dist/cluster/protocol.d.ts +204 -0
  221. package/dist/cluster/protocol.d.ts.map +1 -0
  222. package/dist/cluster/protocol.js +274 -0
  223. package/dist/cluster/protocol.js.map +1 -0
  224. package/dist/cluster/scheduled-task/builder.d.ts +24 -0
  225. package/dist/cluster/scheduled-task/builder.d.ts.map +1 -0
  226. package/dist/cluster/scheduled-task/builder.js +63 -0
  227. package/dist/cluster/scheduled-task/builder.js.map +1 -0
  228. package/dist/cluster/scheduled-task/factory.d.ts +76 -0
  229. package/dist/cluster/scheduled-task/factory.d.ts.map +1 -0
  230. package/dist/cluster/scheduled-task/factory.js +64 -0
  231. package/dist/cluster/scheduled-task/factory.js.map +1 -0
  232. package/dist/cluster/scheduled-task/index.d.ts +43 -0
  233. package/dist/cluster/scheduled-task/index.d.ts.map +1 -0
  234. package/dist/cluster/scheduled-task/index.js +45 -0
  235. package/dist/cluster/scheduled-task/index.js.map +1 -0
  236. package/dist/cluster/scheduled-task/transport.d.ts +12 -0
  237. package/dist/cluster/scheduled-task/transport.d.ts.map +1 -0
  238. package/dist/cluster/scheduled-task/transport.js +146 -0
  239. package/dist/cluster/scheduled-task/transport.js.map +1 -0
  240. package/dist/cluster/scheduled-task/types.d.ts +89 -0
  241. package/dist/cluster/scheduled-task/types.d.ts.map +1 -0
  242. package/dist/cluster/scheduled-task/types.js +7 -0
  243. package/dist/cluster/scheduled-task/types.js.map +1 -0
  244. package/dist/cluster/server.d.ts +87 -0
  245. package/dist/cluster/server.d.ts.map +1 -0
  246. package/dist/cluster/server.js +290 -0
  247. package/dist/cluster/server.js.map +1 -0
  248. package/dist/cluster/transport.d.ts +86 -0
  249. package/dist/cluster/transport.d.ts.map +1 -0
  250. package/dist/cluster/transport.js +138 -0
  251. package/dist/cluster/transport.js.map +1 -0
  252. package/dist/core/container-hooks.d.ts +22 -0
  253. package/dist/core/container-hooks.d.ts.map +1 -0
  254. package/dist/core/container-hooks.js +29 -0
  255. package/dist/core/container-hooks.js.map +1 -0
  256. package/dist/core/container-reflection.d.ts +71 -0
  257. package/dist/core/container-reflection.d.ts.map +1 -0
  258. package/dist/core/container-reflection.js +60 -0
  259. package/dist/core/container-reflection.js.map +1 -0
  260. package/dist/core/context.d.ts +146 -0
  261. package/dist/core/context.d.ts.map +1 -0
  262. package/dist/core/context.js +155 -0
  263. package/dist/core/context.js.map +1 -0
  264. package/dist/core/contribution.d.ts +152 -0
  265. package/dist/core/contribution.d.ts.map +1 -0
  266. package/dist/core/contribution.js +213 -0
  267. package/dist/core/contribution.js.map +1 -0
  268. package/dist/core/controller.contextual.d.ts +193 -0
  269. package/dist/core/controller.contextual.d.ts.map +1 -0
  270. package/dist/core/controller.contextual.js +459 -0
  271. package/dist/core/controller.contextual.js.map +1 -0
  272. package/dist/core/controller.d.ts +510 -0
  273. package/dist/core/controller.d.ts.map +1 -0
  274. package/dist/core/controller.js +411 -0
  275. package/dist/core/controller.js.map +1 -0
  276. package/dist/core/controller.procedure.d.ts +147 -0
  277. package/dist/core/controller.procedure.d.ts.map +1 -0
  278. package/dist/core/controller.procedure.js +115 -0
  279. package/dist/core/controller.procedure.js.map +1 -0
  280. package/dist/core/disposable.d.ts +126 -0
  281. package/dist/core/disposable.d.ts.map +1 -0
  282. package/dist/core/disposable.js +179 -0
  283. package/dist/core/disposable.js.map +1 -0
  284. package/dist/core/hmr.d.ts +83 -0
  285. package/dist/core/hmr.d.ts.map +1 -0
  286. package/dist/core/hmr.js +211 -0
  287. package/dist/core/hmr.js.map +1 -0
  288. package/dist/core/index.d.ts +17 -0
  289. package/dist/core/index.d.ts.map +1 -0
  290. package/dist/core/index.js +25 -0
  291. package/dist/core/index.js.map +1 -0
  292. package/dist/core/internal/routes.d.ts +26 -0
  293. package/dist/core/internal/routes.d.ts.map +1 -0
  294. package/dist/core/internal/routes.js +48 -0
  295. package/dist/core/internal/routes.js.map +1 -0
  296. package/dist/core/lifecycle-impl.d.ts +45 -0
  297. package/dist/core/lifecycle-impl.d.ts.map +1 -0
  298. package/dist/core/lifecycle-impl.js +102 -0
  299. package/dist/core/lifecycle-impl.js.map +1 -0
  300. package/dist/core/lifecycle.d.ts +86 -0
  301. package/dist/core/lifecycle.d.ts.map +1 -0
  302. package/dist/core/lifecycle.js +38 -0
  303. package/dist/core/lifecycle.js.map +1 -0
  304. package/dist/core/logger.d.ts +282 -0
  305. package/dist/core/logger.d.ts.map +1 -0
  306. package/dist/core/logger.js +368 -0
  307. package/dist/core/logger.js.map +1 -0
  308. package/dist/core/middleware.d.ts +108 -0
  309. package/dist/core/middleware.d.ts.map +1 -0
  310. package/dist/core/middleware.js +60 -0
  311. package/dist/core/middleware.js.map +1 -0
  312. package/dist/core/openapi-methods.d.ts +61 -0
  313. package/dist/core/openapi-methods.d.ts.map +1 -0
  314. package/dist/core/openapi-methods.js +53 -0
  315. package/dist/core/openapi-methods.js.map +1 -0
  316. package/dist/core/plugin.d.ts +209 -0
  317. package/dist/core/plugin.d.ts.map +1 -0
  318. package/dist/core/plugin.js +36 -0
  319. package/dist/core/plugin.js.map +1 -0
  320. package/dist/core/scope-bridge.d.ts +19 -0
  321. package/dist/core/scope-bridge.d.ts.map +1 -0
  322. package/dist/core/scope-bridge.js +34 -0
  323. package/dist/core/scope-bridge.js.map +1 -0
  324. package/dist/core/service.d.ts +429 -0
  325. package/dist/core/service.d.ts.map +1 -0
  326. package/dist/core/service.js +875 -0
  327. package/dist/core/service.js.map +1 -0
  328. package/dist/features/channel/backend.d.ts +98 -0
  329. package/dist/features/channel/backend.d.ts.map +1 -0
  330. package/dist/features/channel/backend.js +75 -0
  331. package/dist/features/channel/backend.js.map +1 -0
  332. package/dist/features/channel/channel.d.ts +18 -0
  333. package/dist/features/channel/channel.d.ts.map +1 -0
  334. package/dist/features/channel/channel.js +219 -0
  335. package/dist/features/channel/channel.js.map +1 -0
  336. package/dist/features/channel/channels.d.ts +87 -0
  337. package/dist/features/channel/channels.d.ts.map +1 -0
  338. package/dist/features/channel/channels.js +252 -0
  339. package/dist/features/channel/channels.js.map +1 -0
  340. package/dist/features/channel/feature.d.ts +40 -0
  341. package/dist/features/channel/feature.d.ts.map +1 -0
  342. package/dist/features/channel/feature.js +44 -0
  343. package/dist/features/channel/feature.js.map +1 -0
  344. package/dist/features/channel/index.d.ts +41 -0
  345. package/dist/features/channel/index.d.ts.map +1 -0
  346. package/dist/features/channel/index.js +41 -0
  347. package/dist/features/channel/index.js.map +1 -0
  348. package/dist/features/channel/types.d.ts +165 -0
  349. package/dist/features/channel/types.d.ts.map +1 -0
  350. package/dist/features/channel/types.js +10 -0
  351. package/dist/features/channel/types.js.map +1 -0
  352. package/dist/features/config/cli/config-controller.d.ts +77 -0
  353. package/dist/features/config/cli/config-controller.d.ts.map +1 -0
  354. package/dist/features/config/cli/config-controller.js +209 -0
  355. package/dist/features/config/cli/config-controller.js.map +1 -0
  356. package/dist/features/config/cli/index.d.ts +9 -0
  357. package/dist/features/config/cli/index.d.ts.map +1 -0
  358. package/dist/features/config/cli/index.js +9 -0
  359. package/dist/features/config/cli/index.js.map +1 -0
  360. package/dist/features/config/cli/profile-controller.d.ts +87 -0
  361. package/dist/features/config/cli/profile-controller.d.ts.map +1 -0
  362. package/dist/features/config/cli/profile-controller.js +223 -0
  363. package/dist/features/config/cli/profile-controller.js.map +1 -0
  364. package/dist/features/config/cli/utils.d.ts +14 -0
  365. package/dist/features/config/cli/utils.d.ts.map +1 -0
  366. package/dist/features/config/cli/utils.js +29 -0
  367. package/dist/features/config/cli/utils.js.map +1 -0
  368. package/dist/features/config/config-of.d.ts +36 -0
  369. package/dist/features/config/config-of.d.ts.map +1 -0
  370. package/dist/features/config/config-of.js +36 -0
  371. package/dist/features/config/config-of.js.map +1 -0
  372. package/dist/features/config/config-service.d.ts +54 -0
  373. package/dist/features/config/config-service.d.ts.map +1 -0
  374. package/dist/features/config/config-service.js +184 -0
  375. package/dist/features/config/config-service.js.map +1 -0
  376. package/dist/features/config/create-config.d.ts +21 -0
  377. package/dist/features/config/create-config.d.ts.map +1 -0
  378. package/dist/features/config/create-config.js +16 -0
  379. package/dist/features/config/create-config.js.map +1 -0
  380. package/dist/features/config/define-config-partial.d.ts +13 -0
  381. package/dist/features/config/define-config-partial.d.ts.map +1 -0
  382. package/dist/features/config/define-config-partial.js +19 -0
  383. package/dist/features/config/define-config-partial.js.map +1 -0
  384. package/dist/features/config/env-service.d.ts +54 -0
  385. package/dist/features/config/env-service.d.ts.map +1 -0
  386. package/dist/features/config/env-service.js +115 -0
  387. package/dist/features/config/env-service.js.map +1 -0
  388. package/dist/features/config/file-watcher.d.ts +13 -0
  389. package/dist/features/config/file-watcher.d.ts.map +1 -0
  390. package/dist/features/config/file-watcher.js +98 -0
  391. package/dist/features/config/file-watcher.js.map +1 -0
  392. package/dist/features/config/index.d.ts +21 -0
  393. package/dist/features/config/index.d.ts.map +1 -0
  394. package/dist/features/config/index.js +24 -0
  395. package/dist/features/config/index.js.map +1 -0
  396. package/dist/features/config/profile-service.d.ts +59 -0
  397. package/dist/features/config/profile-service.d.ts.map +1 -0
  398. package/dist/features/config/profile-service.js +114 -0
  399. package/dist/features/config/profile-service.js.map +1 -0
  400. package/dist/features/config/types.d.ts +38 -0
  401. package/dist/features/config/types.d.ts.map +1 -0
  402. package/dist/features/config/types.js +17 -0
  403. package/dist/features/config/types.js.map +1 -0
  404. package/dist/features/contract/contract.d.ts +264 -0
  405. package/dist/features/contract/contract.d.ts.map +1 -0
  406. package/dist/features/contract/contract.js +183 -0
  407. package/dist/features/contract/contract.js.map +1 -0
  408. package/dist/features/contract/index.d.ts +2 -0
  409. package/dist/features/contract/index.d.ts.map +1 -0
  410. package/dist/features/contract/index.js +2 -0
  411. package/dist/features/contract/index.js.map +1 -0
  412. package/dist/features/env/contribute.d.ts +70 -0
  413. package/dist/features/env/contribute.d.ts.map +1 -0
  414. package/dist/features/env/contribute.js +195 -0
  415. package/dist/features/env/contribute.js.map +1 -0
  416. package/dist/features/environment/create-environment.d.ts +58 -0
  417. package/dist/features/environment/create-environment.d.ts.map +1 -0
  418. package/dist/features/environment/create-environment.js +22 -0
  419. package/dist/features/environment/create-environment.js.map +1 -0
  420. package/dist/features/environment/index.d.ts +12 -0
  421. package/dist/features/environment/index.d.ts.map +1 -0
  422. package/dist/features/environment/index.js +10 -0
  423. package/dist/features/environment/index.js.map +1 -0
  424. package/dist/features/environment/load.d.ts +59 -0
  425. package/dist/features/environment/load.d.ts.map +1 -0
  426. package/dist/features/environment/load.js +117 -0
  427. package/dist/features/environment/load.js.map +1 -0
  428. package/dist/features/environment/types.d.ts +165 -0
  429. package/dist/features/environment/types.d.ts.map +1 -0
  430. package/dist/features/environment/types.js +18 -0
  431. package/dist/features/environment/types.js.map +1 -0
  432. package/dist/features/feature-flags/create-feature-flag-provider.d.ts +21 -0
  433. package/dist/features/feature-flags/create-feature-flag-provider.d.ts.map +1 -0
  434. package/dist/features/feature-flags/create-feature-flag-provider.js +16 -0
  435. package/dist/features/feature-flags/create-feature-flag-provider.js.map +1 -0
  436. package/dist/features/feature-flags/define-feature-flag-partial.d.ts +20 -0
  437. package/dist/features/feature-flags/define-feature-flag-partial.d.ts.map +1 -0
  438. package/dist/features/feature-flags/define-feature-flag-partial.js +26 -0
  439. package/dist/features/feature-flags/define-feature-flag-partial.js.map +1 -0
  440. package/dist/features/feature-flags/feature-flag-of.d.ts +16 -0
  441. package/dist/features/feature-flags/feature-flag-of.d.ts.map +1 -0
  442. package/dist/features/feature-flags/feature-flag-of.js +16 -0
  443. package/dist/features/feature-flags/feature-flag-of.js.map +1 -0
  444. package/dist/features/feature-flags/feature-flag-service.d.ts +22 -0
  445. package/dist/features/feature-flags/feature-flag-service.d.ts.map +1 -0
  446. package/dist/features/feature-flags/feature-flag-service.js +112 -0
  447. package/dist/features/feature-flags/feature-flag-service.js.map +1 -0
  448. package/dist/features/feature-flags/index.d.ts +15 -0
  449. package/dist/features/feature-flags/index.d.ts.map +1 -0
  450. package/dist/features/feature-flags/index.js +12 -0
  451. package/dist/features/feature-flags/index.js.map +1 -0
  452. package/dist/features/feature-flags/types.d.ts +30 -0
  453. package/dist/features/feature-flags/types.d.ts.map +1 -0
  454. package/dist/features/feature-flags/types.js +8 -0
  455. package/dist/features/feature-flags/types.js.map +1 -0
  456. package/dist/features/index.d.ts +6 -0
  457. package/dist/features/index.d.ts.map +1 -0
  458. package/dist/features/index.js +7 -0
  459. package/dist/features/index.js.map +1 -0
  460. package/dist/features/lock/index.d.ts +4 -0
  461. package/dist/features/lock/index.d.ts.map +1 -0
  462. package/dist/features/lock/index.js +4 -0
  463. package/dist/features/lock/index.js.map +1 -0
  464. package/dist/features/lock/lock-service.d.ts +74 -0
  465. package/dist/features/lock/lock-service.d.ts.map +1 -0
  466. package/dist/features/lock/lock-service.js +210 -0
  467. package/dist/features/lock/lock-service.js.map +1 -0
  468. package/dist/features/lock/memory.d.ts +60 -0
  469. package/dist/features/lock/memory.d.ts.map +1 -0
  470. package/dist/features/lock/memory.js +194 -0
  471. package/dist/features/lock/memory.js.map +1 -0
  472. package/dist/features/lock/types.d.ts +151 -0
  473. package/dist/features/lock/types.d.ts.map +1 -0
  474. package/dist/features/lock/types.js +17 -0
  475. package/dist/features/lock/types.js.map +1 -0
  476. package/dist/features/memory/index.d.ts +18 -0
  477. package/dist/features/memory/index.d.ts.map +1 -0
  478. package/dist/features/memory/index.js +18 -0
  479. package/dist/features/memory/index.js.map +1 -0
  480. package/dist/features/memory/lock-feature.d.ts +21 -0
  481. package/dist/features/memory/lock-feature.d.ts.map +1 -0
  482. package/dist/features/memory/lock-feature.js +24 -0
  483. package/dist/features/memory/lock-feature.js.map +1 -0
  484. package/dist/features/secrets/create-secret-provider.d.ts +31 -0
  485. package/dist/features/secrets/create-secret-provider.d.ts.map +1 -0
  486. package/dist/features/secrets/create-secret-provider.js +26 -0
  487. package/dist/features/secrets/create-secret-provider.js.map +1 -0
  488. package/dist/features/secrets/define-secret-partial.d.ts +16 -0
  489. package/dist/features/secrets/define-secret-partial.d.ts.map +1 -0
  490. package/dist/features/secrets/define-secret-partial.js +26 -0
  491. package/dist/features/secrets/define-secret-partial.js.map +1 -0
  492. package/dist/features/secrets/index.d.ts +17 -0
  493. package/dist/features/secrets/index.d.ts.map +1 -0
  494. package/dist/features/secrets/index.js +14 -0
  495. package/dist/features/secrets/index.js.map +1 -0
  496. package/dist/features/secrets/secret-of.d.ts +19 -0
  497. package/dist/features/secrets/secret-of.d.ts.map +1 -0
  498. package/dist/features/secrets/secret-of.js +21 -0
  499. package/dist/features/secrets/secret-of.js.map +1 -0
  500. package/dist/features/secrets/secret-service.d.ts +21 -0
  501. package/dist/features/secrets/secret-service.d.ts.map +1 -0
  502. package/dist/features/secrets/secret-service.js +28 -0
  503. package/dist/features/secrets/secret-service.js.map +1 -0
  504. package/dist/features/secrets/types.d.ts +30 -0
  505. package/dist/features/secrets/types.d.ts.map +1 -0
  506. package/dist/features/secrets/types.js +8 -0
  507. package/dist/features/secrets/types.js.map +1 -0
  508. package/dist/features/vault/env-var-vault.d.ts +24 -0
  509. package/dist/features/vault/env-var-vault.d.ts.map +1 -0
  510. package/dist/features/vault/env-var-vault.js +43 -0
  511. package/dist/features/vault/env-var-vault.js.map +1 -0
  512. package/dist/features/vault/hardcoded-vault.d.ts +34 -0
  513. package/dist/features/vault/hardcoded-vault.d.ts.map +1 -0
  514. package/dist/features/vault/hardcoded-vault.js +46 -0
  515. package/dist/features/vault/hardcoded-vault.js.map +1 -0
  516. package/dist/features/vault/hashicorp-vault.d.ts +32 -0
  517. package/dist/features/vault/hashicorp-vault.d.ts.map +1 -0
  518. package/dist/features/vault/hashicorp-vault.js +69 -0
  519. package/dist/features/vault/hashicorp-vault.js.map +1 -0
  520. package/dist/features/vault/index.d.ts +13 -0
  521. package/dist/features/vault/index.d.ts.map +1 -0
  522. package/dist/features/vault/index.js +12 -0
  523. package/dist/features/vault/index.js.map +1 -0
  524. package/dist/features/vault/kubernetes-vault.d.ts +27 -0
  525. package/dist/features/vault/kubernetes-vault.d.ts.map +1 -0
  526. package/dist/features/vault/kubernetes-vault.js +51 -0
  527. package/dist/features/vault/kubernetes-vault.js.map +1 -0
  528. package/dist/features/vault/types.d.ts +41 -0
  529. package/dist/features/vault/types.d.ts.map +1 -0
  530. package/dist/features/vault/types.js +21 -0
  531. package/dist/features/vault/types.js.map +1 -0
  532. package/dist/index.d.ts +78 -0
  533. package/dist/index.d.ts.map +1 -0
  534. package/dist/index.js +48 -0
  535. package/dist/index.js.map +1 -0
  536. package/dist/justscale.d.ts +63 -0
  537. package/dist/justscale.d.ts.map +1 -0
  538. package/dist/justscale.js +501 -0
  539. package/dist/justscale.js.map +1 -0
  540. package/dist/kernel/adapter.d.ts +9 -0
  541. package/dist/kernel/adapter.d.ts.map +1 -0
  542. package/dist/kernel/adapter.js +2 -0
  543. package/dist/kernel/adapter.js.map +1 -0
  544. package/dist/kernel/kernel.d.ts +15 -0
  545. package/dist/kernel/kernel.d.ts.map +1 -0
  546. package/dist/kernel/kernel.js +134 -0
  547. package/dist/kernel/kernel.js.map +1 -0
  548. package/dist/models/access.d.ts +26 -0
  549. package/dist/models/access.d.ts.map +1 -0
  550. package/dist/models/access.js +126 -0
  551. package/dist/models/access.js.map +1 -0
  552. package/dist/models/apply-types-config.d.ts +52 -0
  553. package/dist/models/apply-types-config.d.ts.map +1 -0
  554. package/dist/models/apply-types-config.js +47 -0
  555. package/dist/models/apply-types-config.js.map +1 -0
  556. package/dist/models/define-model.d.ts +249 -0
  557. package/dist/models/define-model.d.ts.map +1 -0
  558. package/dist/models/define-model.js +388 -0
  559. package/dist/models/define-model.js.map +1 -0
  560. package/dist/models/field.d.ts +309 -0
  561. package/dist/models/field.d.ts.map +1 -0
  562. package/dist/models/field.js +312 -0
  563. package/dist/models/field.js.map +1 -0
  564. package/dist/models/in-memory/condition-evaluator.d.ts +53 -0
  565. package/dist/models/in-memory/condition-evaluator.d.ts.map +1 -0
  566. package/dist/models/in-memory/condition-evaluator.js +593 -0
  567. package/dist/models/in-memory/condition-evaluator.js.map +1 -0
  568. package/dist/models/in-memory/in-memory-model.d.ts +89 -0
  569. package/dist/models/in-memory/in-memory-model.d.ts.map +1 -0
  570. package/dist/models/in-memory/in-memory-model.js +101 -0
  571. package/dist/models/in-memory/in-memory-model.js.map +1 -0
  572. package/dist/models/in-memory/in-memory-repository.d.ts +208 -0
  573. package/dist/models/in-memory/in-memory-repository.d.ts.map +1 -0
  574. package/dist/models/in-memory/in-memory-repository.js +618 -0
  575. package/dist/models/in-memory/in-memory-repository.js.map +1 -0
  576. package/dist/models/in-memory/in-memory-scheduled-task.repository.d.ts +92 -0
  577. package/dist/models/in-memory/in-memory-scheduled-task.repository.d.ts.map +1 -0
  578. package/dist/models/in-memory/in-memory-scheduled-task.repository.js +395 -0
  579. package/dist/models/in-memory/in-memory-scheduled-task.repository.js.map +1 -0
  580. package/dist/models/in-memory/index.d.ts +35 -0
  581. package/dist/models/in-memory/index.d.ts.map +1 -0
  582. package/dist/models/in-memory/index.js +36 -0
  583. package/dist/models/in-memory/index.js.map +1 -0
  584. package/dist/models/index.d.ts +52 -0
  585. package/dist/models/index.d.ts.map +1 -0
  586. package/dist/models/index.js +86 -0
  587. package/dist/models/index.js.map +1 -0
  588. package/dist/models/model-name-registry.d.ts +16 -0
  589. package/dist/models/model-name-registry.d.ts.map +1 -0
  590. package/dist/models/model-name-registry.js +19 -0
  591. package/dist/models/model-name-registry.js.map +1 -0
  592. package/dist/models/model.d.ts +15 -0
  593. package/dist/models/model.d.ts.map +1 -0
  594. package/dist/models/model.js +114 -0
  595. package/dist/models/model.js.map +1 -0
  596. package/dist/models/model.repository.d.ts +318 -0
  597. package/dist/models/model.repository.d.ts.map +1 -0
  598. package/dist/models/model.repository.js +146 -0
  599. package/dist/models/model.repository.js.map +1 -0
  600. package/dist/models/observable.d.ts +15 -0
  601. package/dist/models/observable.d.ts.map +1 -0
  602. package/dist/models/observable.js +64 -0
  603. package/dist/models/observable.js.map +1 -0
  604. package/dist/models/proxy.d.ts +5 -0
  605. package/dist/models/proxy.d.ts.map +1 -0
  606. package/dist/models/proxy.js +407 -0
  607. package/dist/models/proxy.js.map +1 -0
  608. package/dist/models/query.d.ts +574 -0
  609. package/dist/models/query.d.ts.map +1 -0
  610. package/dist/models/query.js +701 -0
  611. package/dist/models/query.js.map +1 -0
  612. package/dist/models/reference/reference.d.ts +229 -0
  613. package/dist/models/reference/reference.d.ts.map +1 -0
  614. package/dist/models/reference/reference.js +331 -0
  615. package/dist/models/reference/reference.js.map +1 -0
  616. package/dist/models/reference/transient-ref.d.ts +123 -0
  617. package/dist/models/reference/transient-ref.d.ts.map +1 -0
  618. package/dist/models/reference/transient-ref.js +152 -0
  619. package/dist/models/reference/transient-ref.js.map +1 -0
  620. package/dist/models/repository.d.ts +53 -0
  621. package/dist/models/repository.d.ts.map +1 -0
  622. package/dist/models/repository.js +37 -0
  623. package/dist/models/repository.js.map +1 -0
  624. package/dist/models/scheduled-task/index.d.ts +13 -0
  625. package/dist/models/scheduled-task/index.d.ts.map +1 -0
  626. package/dist/models/scheduled-task/index.js +12 -0
  627. package/dist/models/scheduled-task/index.js.map +1 -0
  628. package/dist/models/scheduled-task/scheduled-task.d.ts +73 -0
  629. package/dist/models/scheduled-task/scheduled-task.d.ts.map +1 -0
  630. package/dist/models/scheduled-task/scheduled-task.js +95 -0
  631. package/dist/models/scheduled-task/scheduled-task.js.map +1 -0
  632. package/dist/models/scheduled-task/scheduled-task.repository.d.ts +150 -0
  633. package/dist/models/scheduled-task/scheduled-task.repository.d.ts.map +1 -0
  634. package/dist/models/scheduled-task/scheduled-task.repository.js +40 -0
  635. package/dist/models/scheduled-task/scheduled-task.repository.js.map +1 -0
  636. package/dist/models/stream.d.ts +139 -0
  637. package/dist/models/stream.d.ts.map +1 -0
  638. package/dist/models/stream.js +153 -0
  639. package/dist/models/stream.js.map +1 -0
  640. package/dist/models/symbols.d.ts +73 -0
  641. package/dist/models/symbols.d.ts.map +1 -0
  642. package/dist/models/symbols.js +97 -0
  643. package/dist/models/symbols.js.map +1 -0
  644. package/dist/models/types.d.ts +291 -0
  645. package/dist/models/types.d.ts.map +1 -0
  646. package/dist/models/types.js +50 -0
  647. package/dist/models/types.js.map +1 -0
  648. package/dist/models/watch.d.ts +27 -0
  649. package/dist/models/watch.d.ts.map +1 -0
  650. package/dist/models/watch.js +124 -0
  651. package/dist/models/watch.js.map +1 -0
  652. package/dist/models/zod-ref.d.ts +46 -0
  653. package/dist/models/zod-ref.d.ts.map +1 -0
  654. package/dist/models/zod-ref.js +31 -0
  655. package/dist/models/zod-ref.js.map +1 -0
  656. package/dist/process/builtin-serializers.d.ts +19 -0
  657. package/dist/process/builtin-serializers.d.ts.map +1 -0
  658. package/dist/process/builtin-serializers.js +213 -0
  659. package/dist/process/builtin-serializers.js.map +1 -0
  660. package/dist/process/cluster-plugin.d.ts +129 -0
  661. package/dist/process/cluster-plugin.d.ts.map +1 -0
  662. package/dist/process/cluster-plugin.js +175 -0
  663. package/dist/process/cluster-plugin.js.map +1 -0
  664. package/dist/process/createProcess.d.ts +67 -0
  665. package/dist/process/createProcess.d.ts.map +1 -0
  666. package/dist/process/createProcess.js +111 -0
  667. package/dist/process/createProcess.js.map +1 -0
  668. package/dist/process/define-signals.d.ts +113 -0
  669. package/dist/process/define-signals.d.ts.map +1 -0
  670. package/dist/process/define-signals.js +222 -0
  671. package/dist/process/define-signals.js.map +1 -0
  672. package/dist/process/delay-controller.d.ts +35 -0
  673. package/dist/process/delay-controller.d.ts.map +1 -0
  674. package/dist/process/delay-controller.js +55 -0
  675. package/dist/process/delay-controller.js.map +1 -0
  676. package/dist/process/index.d.ts +38 -0
  677. package/dist/process/index.d.ts.map +1 -0
  678. package/dist/process/index.js +47 -0
  679. package/dist/process/index.js.map +1 -0
  680. package/dist/process/primitives.d.ts +393 -0
  681. package/dist/process/primitives.d.ts.map +1 -0
  682. package/dist/process/primitives.js +325 -0
  683. package/dist/process/primitives.js.map +1 -0
  684. package/dist/process/serialization.d.ts +58 -0
  685. package/dist/process/serialization.d.ts.map +1 -0
  686. package/dist/process/serialization.js +220 -0
  687. package/dist/process/serialization.js.map +1 -0
  688. package/dist/process/stream-utils.d.ts +123 -0
  689. package/dist/process/stream-utils.d.ts.map +1 -0
  690. package/dist/process/stream-utils.js +247 -0
  691. package/dist/process/stream-utils.js.map +1 -0
  692. package/dist/process/testing/clock.d.ts +115 -0
  693. package/dist/process/testing/clock.d.ts.map +1 -0
  694. package/dist/process/testing/clock.js +166 -0
  695. package/dist/process/testing/clock.js.map +1 -0
  696. package/dist/process/testing/index.d.ts +9 -0
  697. package/dist/process/testing/index.d.ts.map +1 -0
  698. package/dist/process/testing/index.js +9 -0
  699. package/dist/process/testing/index.js.map +1 -0
  700. package/dist/process/testing.d.ts +50 -0
  701. package/dist/process/testing.d.ts.map +1 -0
  702. package/dist/process/testing.js +59 -0
  703. package/dist/process/testing.js.map +1 -0
  704. package/dist/process/types.d.ts +540 -0
  705. package/dist/process/types.d.ts.map +1 -0
  706. package/dist/process/types.js +21 -0
  707. package/dist/process/types.js.map +1 -0
  708. package/dist/queue/index.d.ts +2 -0
  709. package/dist/queue/index.d.ts.map +1 -0
  710. package/dist/queue/index.js +2 -0
  711. package/dist/queue/index.js.map +1 -0
  712. package/dist/queue/queue.d.ts +34 -0
  713. package/dist/queue/queue.d.ts.map +1 -0
  714. package/dist/queue/queue.js +108 -0
  715. package/dist/queue/queue.js.map +1 -0
  716. package/dist/runtime/process/compiled.d.ts +56 -0
  717. package/dist/runtime/process/compiled.d.ts.map +1 -0
  718. package/dist/runtime/process/compiled.js +221 -0
  719. package/dist/runtime/process/compiled.js.map +1 -0
  720. package/dist/runtime/process/executor.d.ts +279 -0
  721. package/dist/runtime/process/executor.d.ts.map +1 -0
  722. package/dist/runtime/process/executor.js +1941 -0
  723. package/dist/runtime/process/executor.js.map +1 -0
  724. package/dist/runtime/process/factory.d.ts +72 -0
  725. package/dist/runtime/process/factory.d.ts.map +1 -0
  726. package/dist/runtime/process/factory.js +78 -0
  727. package/dist/runtime/process/factory.js.map +1 -0
  728. package/dist/runtime/process/freeze.d.ts +5 -0
  729. package/dist/runtime/process/freeze.d.ts.map +1 -0
  730. package/dist/runtime/process/freeze.js +94 -0
  731. package/dist/runtime/process/freeze.js.map +1 -0
  732. package/dist/runtime/process/scheduled-task-timer.d.ts +52 -0
  733. package/dist/runtime/process/scheduled-task-timer.d.ts.map +1 -0
  734. package/dist/runtime/process/scheduled-task-timer.js +104 -0
  735. package/dist/runtime/process/scheduled-task-timer.js.map +1 -0
  736. package/dist/runtime/process/signal-bus.d.ts +186 -0
  737. package/dist/runtime/process/signal-bus.d.ts.map +1 -0
  738. package/dist/runtime/process/signal-bus.js +256 -0
  739. package/dist/runtime/process/signal-bus.js.map +1 -0
  740. package/dist/runtime/process/state-serializer.d.ts +30 -0
  741. package/dist/runtime/process/state-serializer.d.ts.map +1 -0
  742. package/dist/runtime/process/state-serializer.js +244 -0
  743. package/dist/runtime/process/state-serializer.js.map +1 -0
  744. package/dist/runtime/process/storage.d.ts +96 -0
  745. package/dist/runtime/process/storage.d.ts.map +1 -0
  746. package/dist/runtime/process/storage.js +165 -0
  747. package/dist/runtime/process/storage.js.map +1 -0
  748. package/dist/runtime/process/timer-scheduler.d.ts +115 -0
  749. package/dist/runtime/process/timer-scheduler.d.ts.map +1 -0
  750. package/dist/runtime/process/timer-scheduler.js +192 -0
  751. package/dist/runtime/process/timer-scheduler.js.map +1 -0
  752. package/dist/runtime/process/trace.d.ts +17 -0
  753. package/dist/runtime/process/trace.d.ts.map +1 -0
  754. package/dist/runtime/process/trace.js +26 -0
  755. package/dist/runtime/process/trace.js.map +1 -0
  756. package/dist/runtime/protobuf/encoding/index.d.ts +26 -0
  757. package/dist/runtime/protobuf/encoding/index.d.ts.map +1 -0
  758. package/dist/runtime/protobuf/encoding/index.js +30 -0
  759. package/dist/runtime/protobuf/encoding/index.js.map +1 -0
  760. package/dist/runtime/protobuf/encoding/reader.d.ts +182 -0
  761. package/dist/runtime/protobuf/encoding/reader.d.ts.map +1 -0
  762. package/dist/runtime/protobuf/encoding/reader.js +353 -0
  763. package/dist/runtime/protobuf/encoding/reader.js.map +1 -0
  764. package/dist/runtime/protobuf/encoding/varint.d.ts +67 -0
  765. package/dist/runtime/protobuf/encoding/varint.d.ts.map +1 -0
  766. package/dist/runtime/protobuf/encoding/varint.js +117 -0
  767. package/dist/runtime/protobuf/encoding/varint.js.map +1 -0
  768. package/dist/runtime/protobuf/encoding/wire-types.d.ts +62 -0
  769. package/dist/runtime/protobuf/encoding/wire-types.d.ts.map +1 -0
  770. package/dist/runtime/protobuf/encoding/wire-types.js +103 -0
  771. package/dist/runtime/protobuf/encoding/wire-types.js.map +1 -0
  772. package/dist/runtime/protobuf/encoding/writer.d.ts +147 -0
  773. package/dist/runtime/protobuf/encoding/writer.d.ts.map +1 -0
  774. package/dist/runtime/protobuf/encoding/writer.js +214 -0
  775. package/dist/runtime/protobuf/encoding/writer.js.map +1 -0
  776. package/dist/runtime/protobuf/index.d.ts +3 -0
  777. package/dist/runtime/protobuf/index.d.ts.map +1 -0
  778. package/dist/runtime/protobuf/index.js +3 -0
  779. package/dist/runtime/protobuf/index.js.map +1 -0
  780. package/dist/runtime/protobuf/serialized.d.ts +48 -0
  781. package/dist/runtime/protobuf/serialized.d.ts.map +1 -0
  782. package/dist/runtime/protobuf/serialized.js +517 -0
  783. package/dist/runtime/protobuf/serialized.js.map +1 -0
  784. package/package.json +209 -0
@@ -0,0 +1,1941 @@
1
+ /**
2
+ * @justscale/process - Process Executor
3
+ *
4
+ * Switch-based executor for durable processes.
5
+ * Uses pluggable storage, signal bus, and timer scheduler.
6
+ */
7
+ import { serializeState, deserializeState } from './state-serializer.js';
8
+ import { encodeProcessable, decodeProcessable } from '../../process/serialization.js';
9
+ import { defineAbstract } from '../../core/index.js';
10
+ import { runInFullRequestScope, getRequestContext } from '../../core/context.js';
11
+ import { runWithLockTracking, DoubleLockError } from '../../features/lock/lock-service.js';
12
+ import { SIGNAL_BRAND, } from '../../process/types.js';
13
+ import { resolveStreamWildcard as resolveStreamWildcardUtil, } from '../../process/stream-utils.js';
14
+ import { createTracer } from './trace.js';
15
+ import { freezeExports } from './freeze.js';
16
+ const { trace } = createTracer('ProcessExecutor');
17
+ function createDeferred() {
18
+ let resolve;
19
+ let reject;
20
+ const promise = new Promise((res, rej) => {
21
+ resolve = res;
22
+ reject = rej;
23
+ });
24
+ return { promise, resolve, reject };
25
+ }
26
+ // ============================================================================
27
+ // Instance ID Generation
28
+ // ============================================================================
29
+ /**
30
+ * Resolve a param value: if it has an `identifier` property (e.g. a Reference
31
+ * or Model.ref(entity)), use that. Otherwise stringify.
32
+ */
33
+ function resolveParam(value) {
34
+ if (value != null && typeof value === 'object' && 'identifier' in value) {
35
+ return String(value.identifier);
36
+ }
37
+ return String(value);
38
+ }
39
+ import { applyTypesConfig } from '../../models/apply-types-config.js';
40
+ export function generateInstanceId(path, params) {
41
+ let paramIndex = 0;
42
+ const segments = path.split('/').filter(Boolean);
43
+ const resolved = segments.map(segment => {
44
+ if (segment.startsWith(':')) {
45
+ const value = params[paramIndex++];
46
+ if (value === undefined) {
47
+ throw new Error(`Missing parameter for ${segment} in path ${path}`);
48
+ }
49
+ return resolveParam(value);
50
+ }
51
+ return segment;
52
+ });
53
+ return resolved.join('/');
54
+ }
55
+ /**
56
+ * Detect whether an instanceId belongs to a subprocess and split it into
57
+ * `${parentInstanceId}/${subKey}` where `subKey` starts with `__sub:`.
58
+ * Returns { parentInstanceId: null, subKey: null } for non-subprocess IDs.
59
+ * A subprocess may itself have subprocesses — we split at the LAST `/__sub:`
60
+ * boundary so the direct parent is always returned.
61
+ */
62
+ export function parseChildInstanceId(instanceId) {
63
+ const marker = '/__sub:';
64
+ const idx = instanceId.lastIndexOf(marker);
65
+ if (idx === -1)
66
+ return { parentInstanceId: null, subKey: null };
67
+ return {
68
+ parentInstanceId: instanceId.slice(0, idx),
69
+ subKey: instanceId.slice(idx + 1),
70
+ };
71
+ }
72
+ /**
73
+ * Resolve a path pattern with params to a concrete path.
74
+ */
75
+ export function resolvePath(path, params) {
76
+ let paramIndex = 0;
77
+ return path.replace(/:([^/]+)/g, () => {
78
+ const value = params[paramIndex++];
79
+ if (value === undefined) {
80
+ throw new Error(`Missing parameter in path ${path}`);
81
+ }
82
+ return String(value);
83
+ });
84
+ }
85
+ /**
86
+ * Extract identity map from path params.
87
+ *
88
+ * Throws on missing params to stay consistent with generateInstanceId.
89
+ * A process that lacks a required path param must fail loudly on both
90
+ * id generation and identity extraction; silent coercion to "undefined"
91
+ * hides routing bugs.
92
+ */
93
+ export function extractIdentity(path, params) {
94
+ const identity = {};
95
+ let paramIndex = 0;
96
+ const segments = path.split('/').filter(Boolean);
97
+ for (const segment of segments) {
98
+ if (segment.startsWith(':')) {
99
+ const name = segment.slice(1);
100
+ const value = params[paramIndex++];
101
+ if (value === undefined) {
102
+ throw new Error(`Missing parameter for ${segment} in path ${path}`);
103
+ }
104
+ identity[name] = resolveParam(value);
105
+ }
106
+ }
107
+ return identity;
108
+ }
109
+ // ============================================================================
110
+ // Process Handle Implementation
111
+ // ============================================================================
112
+ class ProcessHandleImpl {
113
+ id;
114
+ path;
115
+ getState;
116
+ completionDeferred;
117
+ cancelFn;
118
+ exportsMetadata;
119
+ _data = undefined;
120
+ _dataProxy = undefined;
121
+ // Broadcast set: each entry is a pending resolve for one independent iterator.
122
+ // setExportsData fans out to ALL of them simultaneously.
123
+ _dataSubscribers = new Set();
124
+ _done = false;
125
+ _status = 'pending';
126
+ _statusSubscribers = new Set();
127
+ constructor(id, path, getState, completionDeferred, cancelFn, exportsMetadata) {
128
+ this.id = id;
129
+ this.path = path;
130
+ this.getState = getState;
131
+ this.completionDeferred = completionDeferred;
132
+ this.cancelFn = cancelFn;
133
+ this.exportsMetadata = exportsMetadata;
134
+ // When the process completes, close all data iterators and status iterators
135
+ this.completionDeferred.promise.then(() => {
136
+ this._done = true;
137
+ for (const resolve of this._dataSubscribers)
138
+ resolve(undefined);
139
+ this._dataSubscribers.clear();
140
+ // Status was already pushed by updateStatus before resolve — no extra push needed
141
+ for (const resolve of this._statusSubscribers)
142
+ resolve(undefined);
143
+ this._statusSubscribers.clear();
144
+ }).catch(() => {
145
+ this._done = true;
146
+ for (const resolve of this._dataSubscribers)
147
+ resolve(undefined);
148
+ this._dataSubscribers.clear();
149
+ for (const resolve of this._statusSubscribers)
150
+ resolve(undefined);
151
+ this._statusSubscribers.clear();
152
+ });
153
+ }
154
+ get status() {
155
+ return this._status;
156
+ }
157
+ updateStatus(status) {
158
+ this._status = status;
159
+ for (const resolve of this._statusSubscribers)
160
+ resolve(status);
161
+ this._statusSubscribers.clear();
162
+ }
163
+ get statusChanges() {
164
+ const self = this;
165
+ return {
166
+ [Symbol.asyncIterator]() {
167
+ let localDone = false;
168
+ let pendingCb = null;
169
+ return {
170
+ next() {
171
+ if (localDone || self._done) {
172
+ return Promise.resolve({ value: undefined, done: true });
173
+ }
174
+ return new Promise(resolve => {
175
+ const cb = (status) => {
176
+ pendingCb = null;
177
+ self._statusSubscribers.delete(cb);
178
+ if (status === undefined) {
179
+ localDone = true;
180
+ resolve({ value: undefined, done: true });
181
+ }
182
+ else {
183
+ resolve({ value: status, done: false });
184
+ }
185
+ };
186
+ pendingCb = cb;
187
+ self._statusSubscribers.add(cb);
188
+ });
189
+ },
190
+ return() {
191
+ localDone = true;
192
+ if (pendingCb !== null) {
193
+ self._statusSubscribers.delete(pendingCb);
194
+ pendingCb = null;
195
+ }
196
+ return Promise.resolve({ value: undefined, done: true });
197
+ },
198
+ };
199
+ },
200
+ };
201
+ }
202
+ get result() {
203
+ return undefined; // Must await for result via wait()
204
+ }
205
+ get error() {
206
+ return undefined; // Must check via wait()
207
+ }
208
+ get data() {
209
+ if (this._data === undefined)
210
+ return undefined;
211
+ if (this._dataProxy)
212
+ return this._dataProxy;
213
+ const self = this;
214
+ const base = this._data;
215
+ if (typeof base !== 'object' || base === null)
216
+ return base;
217
+ if (base[Symbol.asyncIterator])
218
+ return base;
219
+ // Create and cache a Proxy that adds Symbol.asyncIterator.
220
+ // Each call to [Symbol.asyncIterator]() creates an INDEPENDENT subscriber
221
+ // that receives every broadcast snapshot (fan-out, not fan-in).
222
+ this._dataProxy = new Proxy(base, {
223
+ get(_target, prop, _receiver) {
224
+ if (prop === Symbol.asyncIterator) {
225
+ return function () {
226
+ // Late subscribers immediately receive the current snapshot, then wait for subsequent broadcasts.
227
+ let seededInitial = false;
228
+ let localDone = false;
229
+ let cb = null;
230
+ return {
231
+ next() {
232
+ if (localDone)
233
+ return Promise.resolve({ value: undefined, done: true });
234
+ if (!seededInitial) {
235
+ seededInitial = true;
236
+ if (self._data !== undefined) {
237
+ return Promise.resolve({ value: self._data, done: false });
238
+ }
239
+ }
240
+ if (self._done)
241
+ return Promise.resolve({ value: undefined, done: true });
242
+ return new Promise(resolve => {
243
+ cb = (newData) => {
244
+ cb = null;
245
+ if (newData === undefined) {
246
+ localDone = true;
247
+ resolve({ value: undefined, done: true });
248
+ }
249
+ else {
250
+ resolve({ value: newData, done: false });
251
+ }
252
+ };
253
+ self._dataSubscribers.add(cb);
254
+ });
255
+ },
256
+ return() {
257
+ localDone = true;
258
+ if (cb !== null) {
259
+ const pendingCb = cb;
260
+ cb = null;
261
+ self._dataSubscribers.delete(pendingCb);
262
+ // Resolve the pending next() with done so the caller unblocks
263
+ pendingCb(undefined);
264
+ }
265
+ return Promise.resolve({ value: undefined, done: true });
266
+ },
267
+ };
268
+ };
269
+ }
270
+ // For non-asyncIterator access, read from the LATEST _data (not the original base)
271
+ return Reflect.get(self._data, prop, _receiver);
272
+ },
273
+ });
274
+ return this._dataProxy;
275
+ }
276
+ /** Set the exports data (called by executor after loading/updating state) */
277
+ setExportsData(data) {
278
+ this._data = data;
279
+ const subs = [...this._dataSubscribers];
280
+ this._dataSubscribers.clear();
281
+ for (const resolve of subs) {
282
+ resolve(data);
283
+ }
284
+ }
285
+ wait() {
286
+ return this.completionDeferred.promise;
287
+ }
288
+ cancel() {
289
+ return this.cancelFn(this.id);
290
+ }
291
+ /** Update cached status from storage (kept for backwards compat) */
292
+ async refresh() {
293
+ const state = await this.getState();
294
+ if (state) {
295
+ this.updateStatus(state.status);
296
+ }
297
+ }
298
+ }
299
+ function createYieldQueue() {
300
+ const consumers = [];
301
+ let completed = false;
302
+ return {
303
+ push(value) {
304
+ for (const consumer of consumers) {
305
+ consumer.callback(value);
306
+ }
307
+ },
308
+ complete() {
309
+ completed = true;
310
+ for (const consumer of consumers) {
311
+ consumer.onComplete();
312
+ }
313
+ consumers.length = 0;
314
+ },
315
+ subscribe(callback, onComplete) {
316
+ if (completed) {
317
+ onComplete();
318
+ return () => { };
319
+ }
320
+ const entry = { callback, onComplete };
321
+ consumers.push(entry);
322
+ return () => {
323
+ const idx = consumers.indexOf(entry);
324
+ if (idx !== -1)
325
+ consumers.splice(idx, 1);
326
+ };
327
+ },
328
+ };
329
+ }
330
+ // ============================================================================
331
+ // Process Continuation Implementation
332
+ // ============================================================================
333
+ class ProcessContinuationImpl {
334
+ id;
335
+ consumerId;
336
+ yieldQueue;
337
+ getYieldsFromStorage;
338
+ _status;
339
+ cursor;
340
+ completionDeferred;
341
+ persistCursor;
342
+ constructor(id, consumerId, initialCursor, initialStatus, yieldQueue, getYieldsFromStorage, completionDeferred, persistCursor) {
343
+ this.id = id;
344
+ this.consumerId = consumerId;
345
+ this.yieldQueue = yieldQueue;
346
+ this.getYieldsFromStorage = getYieldsFromStorage;
347
+ this._status = initialStatus;
348
+ this.cursor = initialCursor;
349
+ this.completionDeferred = completionDeferred;
350
+ this.persistCursor = persistCursor;
351
+ }
352
+ get status() {
353
+ return this._status;
354
+ }
355
+ get result() {
356
+ return this.completionDeferred.promise;
357
+ }
358
+ async cancel() {
359
+ await this.persistCursor(this.consumerId, this.cursor);
360
+ }
361
+ [Symbol.asyncIterator]() {
362
+ let unsubscribe = null;
363
+ let pendingResolve = null;
364
+ const buffer = [];
365
+ let done = false;
366
+ let storageHighWater = 0;
367
+ const self = this;
368
+ // Subscribe to live yield events
369
+ unsubscribe = this.yieldQueue.subscribe((value) => {
370
+ if (pendingResolve) {
371
+ const resolve = pendingResolve;
372
+ pendingResolve = null;
373
+ self.cursor++;
374
+ resolve({ value: value, done: false });
375
+ }
376
+ else {
377
+ buffer.push(value);
378
+ }
379
+ }, () => {
380
+ done = true;
381
+ if (pendingResolve) {
382
+ const resolve = pendingResolve;
383
+ pendingResolve = null;
384
+ resolve({ value: undefined, done: true });
385
+ }
386
+ });
387
+ return {
388
+ async next() {
389
+ // First: drain any yields from storage that we haven't consumed yet
390
+ const storedYields = await self.getYieldsFromStorage();
391
+ // Discard buffer items that are now also in storage (both paths receive the same values)
392
+ const newInStorage = storedYields.length - storageHighWater;
393
+ if (newInStorage > 0) {
394
+ buffer.splice(0, newInStorage);
395
+ storageHighWater = storedYields.length;
396
+ }
397
+ if (self.cursor < storedYields.length) {
398
+ const value = storedYields[self.cursor++];
399
+ return { value: value, done: false };
400
+ }
401
+ // Then: check live buffer (only items beyond storage)
402
+ if (buffer.length > 0) {
403
+ self.cursor++;
404
+ return { value: buffer.shift(), done: false };
405
+ }
406
+ // Then: check if already done
407
+ if (done) {
408
+ return { value: undefined, done: true };
409
+ }
410
+ // Wait for next live value or completion
411
+ return new Promise((resolve) => {
412
+ pendingResolve = resolve;
413
+ });
414
+ },
415
+ async return() {
416
+ unsubscribe?.();
417
+ unsubscribe = null;
418
+ await self.persistCursor(self.consumerId, self.cursor);
419
+ return { value: undefined, done: true };
420
+ },
421
+ };
422
+ }
423
+ }
424
+ /**
425
+ * Abstract ProcessExecutor for dependency injection.
426
+ *
427
+ * Inject this into services to create signals bound to the executor:
428
+ *
429
+ * @example
430
+ * ```typescript
431
+ * const OrderSignals = defineService({
432
+ * inject: { executor: AbstractProcessExecutor },
433
+ * factory: ({ executor }) => ({
434
+ * shipped: executor.createSignal<[orderId: string]>('orders.shipped', ['orderId']),
435
+ * })
436
+ * })
437
+ * ```
438
+ */
439
+ export class AbstractProcessExecutor extends defineAbstract('AbstractProcessExecutor') {
440
+ }
441
+ // ============================================================================
442
+ // Process Executor
443
+ // ============================================================================
444
+ /**
445
+ * Executes durable processes using switch-based execution model.
446
+ *
447
+ * Features:
448
+ * - VM-style switch execution with suspension/resumption
449
+ * - Signal routing via SignalBus
450
+ * - Timer scheduling via TimerScheduler
451
+ * - State persistence via ProcessStorage
452
+ */
453
+ export class ProcessExecutor extends AbstractProcessExecutor {
454
+ container;
455
+ resolve;
456
+ storage;
457
+ signalBus;
458
+ timerScheduler;
459
+ lockProvider;
460
+ // Unique identifier for this executor instance (for lock ownership)
461
+ executorId = `exec_${process.pid}_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
462
+ // In-memory completion promises (not persisted)
463
+ completions = new Map();
464
+ // Cache resolved services per process definition
465
+ resolvedServicesCache = new Map();
466
+ // Active subscriptions by instance ID (for cleanup)
467
+ subscriptions = new Map();
468
+ // Registry of process definitions by ID (for resume)
469
+ processRegistry = new Map();
470
+ // Origin request context by instance ID (for tracing)
471
+ originContexts = new Map();
472
+ // Yield queues for generator processes (live consumers)
473
+ yieldQueues = new Map();
474
+ // Live handle registry: executor pushes exports + status into active handles
475
+ handles = new Map();
476
+ // Lock options for process execution
477
+ lockOptions = {
478
+ ttl: 60_000, // 60s TTL
479
+ timeout: 30_000, // 30s wait for acquisition
480
+ key: '', // Set per-lock
481
+ heartbeat: false,
482
+ heartbeatInterval: 20_000,
483
+ };
484
+ publishExports;
485
+ constructor(options) {
486
+ super();
487
+ this.container = options.container ?? null;
488
+ this.resolve = options.resolve;
489
+ this.storage = options.storage;
490
+ this.signalBus = options.signalBus;
491
+ this.timerScheduler = options.timerScheduler;
492
+ this.lockProvider = options.lockProvider ?? null;
493
+ this.publishExports = options.publishExports ?? null;
494
+ // Listen for signal matches
495
+ // Signal bus handles re-entrancy via processingInstances tracking and queuing.
496
+ // We must await handleSignalMatch so that emit() callers can await process completion.
497
+ this.signalBus.onMatch(async (match) => {
498
+ await this.handleSignalMatch(match);
499
+ });
500
+ // Listen for timer fires
501
+ // Timers can use setImmediate since they're fire-and-forget (no caller awaits)
502
+ this.timerScheduler.onFire(fired => {
503
+ setImmediate(() => this.handleTimerFired(fired));
504
+ });
505
+ }
506
+ /**
507
+ * Register a process definition for resumption after signals.
508
+ */
509
+ register(process) {
510
+ this.processRegistry.set(process.id, process);
511
+ }
512
+ /**
513
+ * Start or resume a process.
514
+ */
515
+ async start(process, params) {
516
+ trace('start', { processId: process.id, params });
517
+ // Register process for resumption
518
+ this.register(process);
519
+ const instanceId = generateInstanceId(process.path, params);
520
+ trace('instanceId', { instanceId });
521
+ const resolvedPath = resolvePath(process.path, params);
522
+ const identity = extractIdentity(process.path, params);
523
+ // Acquire process lock before checking/creating state
524
+ const lockKey = `process:${instanceId}`;
525
+ trace('start.acquiringLock', { instanceId });
526
+ await this.acquireLock(lockKey);
527
+ trace('start.lockAcquired', { instanceId });
528
+ try {
529
+ // Check for existing process
530
+ const existing = await this.storage.load(instanceId);
531
+ trace('start.existingCheck', { instanceId, exists: !!existing, status: existing?.status });
532
+ // rt-3: if the existing instance is cancelled, discard it and start fresh
533
+ if (existing && existing.status === 'cancelled') {
534
+ trace('start.cancelledRestart', { instanceId });
535
+ await this.storage.delete(instanceId);
536
+ // Remove stale completion/handle so fresh ones are created below
537
+ this.completions.delete(instanceId);
538
+ this.handles.delete(instanceId);
539
+ }
540
+ else if (existing) {
541
+ let completion = this.completions.get(instanceId);
542
+ if (!completion) {
543
+ const newCompletion = createDeferred();
544
+ this.completions.set(instanceId, newCompletion);
545
+ completion = newCompletion;
546
+ if (existing.status === 'completed') {
547
+ newCompletion.resolve(existing.result);
548
+ }
549
+ else if (existing.status === 'failed') {
550
+ newCompletion.reject(new Error(existing.error ?? 'Process failed'));
551
+ }
552
+ }
553
+ // Release lock if process already completed/failed (no execution needed)
554
+ if (existing.status === 'completed' || existing.status === 'failed') {
555
+ await this.releaseLock(lockKey);
556
+ }
557
+ else if (existing.status === 'suspended') {
558
+ // Re-subscribe to signals for suspended process (in-memory signal bus doesn't persist)
559
+ trace('start.resubscribing', { instanceId });
560
+ await this.resubscribeSuspended(existing, identity, process.types);
561
+ trace('start.releasingLock', { instanceId });
562
+ await this.releaseLock(lockKey);
563
+ trace('start.lockReleased', { instanceId });
564
+ }
565
+ const handle = new ProcessHandleImpl(instanceId, resolvedPath, () => this.loadState(instanceId), completion, (id) => this.cancel(id), process.exports);
566
+ handle.updateStatus(existing.status);
567
+ this.handles.set(instanceId, handle);
568
+ // Populate handle with exports from stored state (deserialize first)
569
+ if (process.exports && existing.variables?.exports) {
570
+ const deserialized = deserializeState({ exports: existing.variables.exports });
571
+ if (deserialized.exports) {
572
+ handle.setExportsData(this.buildFrozenExports(deserialized.exports, process.exports));
573
+ }
574
+ }
575
+ return handle;
576
+ }
577
+ // Create new process state
578
+ const state = {
579
+ processId: process.id,
580
+ instanceId,
581
+ version: process.version,
582
+ step: 0,
583
+ persistedStep: Object.entries(process.stepMap).find(([_, v]) => v === 0)?.[0] ?? 'entry',
584
+ vars: {
585
+ __identity: identity,
586
+ __params: params,
587
+ ...identity,
588
+ },
589
+ timers: [],
590
+ createdAt: new Date(),
591
+ updatedAt: new Date(),
592
+ status: 'pending',
593
+ };
594
+ await this.saveState(state);
595
+ const completion = createDeferred();
596
+ this.completions.set(instanceId, completion);
597
+ // Capture origin request context for tracing
598
+ const originCtx = getRequestContext();
599
+ if (originCtx) {
600
+ this.originContexts.set(instanceId, {
601
+ type: originCtx.type,
602
+ name: originCtx.name,
603
+ id: originCtx.id,
604
+ });
605
+ }
606
+ const handle = new ProcessHandleImpl(instanceId, resolvedPath, () => this.loadState(instanceId), completion, (id) => this.cancel(id), process.exports);
607
+ this.handles.set(instanceId, handle);
608
+ // Start execution (lock released in suspend/complete/fail).
609
+ // saveState inside execute() pushes exports and status into the handle.
610
+ await this.execute(state, process, identity);
611
+ return handle;
612
+ }
613
+ catch (err) {
614
+ // Release lock on error
615
+ await this.releaseLock(lockKey);
616
+ throw err;
617
+ }
618
+ }
619
+ /**
620
+ * Emit a signal to waiting processes.
621
+ *
622
+ * Lock acquisition happens in handleSignalMatch() when the signal is delivered.
623
+ * This avoids deadlock since handleSignalMatch() is called synchronously from emit().
624
+ */
625
+ async emit(signal, identity, payload) {
626
+ // Encode Processable payloads so they survive JSONB round-trips
627
+ const encoded = encodeProcessable(payload);
628
+ return this.signalBus.emit(signal, identity, encoded);
629
+ }
630
+ /**
631
+ * Create a signal bound to this executor.
632
+ *
633
+ * The returned signal is callable directly and will emit through this executor,
634
+ * which handles locking to prevent dead letters.
635
+ *
636
+ * @param name - Signal name for routing (e.g., 'orders.shipped')
637
+ * @param identityParams - Names of identity parameters (e.g., ['orderId'])
638
+ *
639
+ * @example
640
+ * ```typescript
641
+ * const OrderSignals = defineService({
642
+ * inject: { executor: ProcessExecutor },
643
+ * factory: ({ executor }) => ({
644
+ * shipped: executor.createSignal<[orderId: string], { trackingNumber: string }>(
645
+ * 'orders.shipped',
646
+ * ['orderId']
647
+ * ),
648
+ * })
649
+ * })
650
+ *
651
+ * // Usage: await orderSignals.shipped('order-123', { trackingNumber: 'ABC' })
652
+ * ```
653
+ */
654
+ createSignal(name, identityParams = []) {
655
+ const executor = this;
656
+ const signal = async (...args) => {
657
+ trace('signal.invoke', { name, args: args.slice(0, identityParams.length) });
658
+ // Build identity record from args and param names
659
+ const identity = {};
660
+ for (let i = 0; i < identityParams.length && i < args.length; i++) {
661
+ identity[identityParams[i]] = String(args[i]);
662
+ }
663
+ // Payload is the last arg if there are more args than identity params
664
+ const payload = args.length > identityParams.length
665
+ ? args[identityParams.length]
666
+ : undefined;
667
+ trace('signal.emit', { name, identity, hasPayload: payload !== undefined });
668
+ await executor.emit(name, identity, payload);
669
+ trace('signal.emit.complete', { name });
670
+ };
671
+ // Add brand and metadata
672
+ Object.defineProperty(signal, SIGNAL_BRAND, { value: SIGNAL_BRAND });
673
+ Object.defineProperty(signal, 'signalName', { value: name });
674
+ Object.defineProperty(signal, '__identityParams', { value: identityParams });
675
+ // PromiseLike support - allows `await signal` in process handlers
676
+ // At compile time this is transformed; at runtime it throws if used incorrectly
677
+ Object.defineProperty(signal, 'then', {
678
+ value: () => {
679
+ throw new Error(`Cannot await signal "${name}" directly. Use signal() inside a process handler.`);
680
+ },
681
+ });
682
+ return signal;
683
+ }
684
+ // ============================================================================
685
+ // Execution Engine
686
+ // ============================================================================
687
+ /**
688
+ * Execute a switch-based process.
689
+ * Runs within a full request scope for context propagation (if container available).
690
+ */
691
+ async execute(state, process, identity) {
692
+ // Inner execution logic
693
+ const executeInner = async () => {
694
+ const services = await this.resolveServices(process);
695
+ try {
696
+ // Get or create yield queue for this instance
697
+ let yieldQueue = this.yieldQueues.get(state.instanceId);
698
+ if (!yieldQueue) {
699
+ yieldQueue = createYieldQueue();
700
+ this.yieldQueues.set(state.instanceId, yieldQueue);
701
+ }
702
+ // Initialize __yields array if not present
703
+ if (!state.vars.__yields) {
704
+ state.vars.__yields = [];
705
+ }
706
+ const yields = state.vars.__yields;
707
+ const currentYieldQueue = yieldQueue;
708
+ const ctx = {
709
+ state,
710
+ services,
711
+ signalPayload: state.vars.__signalPayload,
712
+ emit: (value) => {
713
+ yields.push(value);
714
+ currentYieldQueue.push(value);
715
+ },
716
+ };
717
+ // Reattach methods on resume — methods serialize as null in JSONB,
718
+ // so we restore them from exports.methods before executing
719
+ if (state.step > 0 && process.exports && state.vars.exports) {
720
+ const exportsObj = state.vars.exports;
721
+ for (const [name, fn] of Object.entries(process.exports.methods)) {
722
+ exportsObj[name] = fn;
723
+ }
724
+ }
725
+ // Re-apply types config on every execution (including resume).
726
+ // References don't survive serialization, so we re-wrap from the
727
+ // persisted string identity on each run.
728
+ if (process.types) {
729
+ const typed = applyTypesConfig(identity, process.types);
730
+ for (const [key, value] of Object.entries(typed)) {
731
+ state.vars[key] = value;
732
+ }
733
+ }
734
+ state.status = 'running';
735
+ // Clear recoverable-error marker optimistically — if the handler
736
+ // throws DoubleLockError again it'll be re-stamped in the catch.
737
+ state.lastError = undefined;
738
+ state.lastErrorAt = undefined;
739
+ trace('execute.beforeExecute', { instanceId: state.instanceId, step: state.step, varsKeys: Object.keys(state.vars) });
740
+ const result = await process.execute(ctx);
741
+ trace('execute.afterExecute', { instanceId: state.instanceId, resultType: result[0], resultPayload: (() => { try {
742
+ return JSON.stringify(result[1], (_k, v) => typeof v === 'bigint' ? `${v}n` : v)?.slice(0, 200);
743
+ }
744
+ catch {
745
+ return String(result[1]);
746
+ } })() });
747
+ // result is [type, payload]
748
+ if (result[0] === 1) {
749
+ // SUSPEND
750
+ await this.suspend(state, process, result[1], identity);
751
+ }
752
+ else if (result[0] === 2) {
753
+ // SUBPROCESS — spawn a subprocess and run it within parent's context
754
+ const spawnConfig = result[1];
755
+ await this.spawnSubprocess(state, process, spawnConfig, identity);
756
+ }
757
+ else {
758
+ // DONE
759
+ await this.complete(state, result[1]);
760
+ }
761
+ }
762
+ catch (error) {
763
+ trace('execute.error', { instanceId: state.instanceId, error: error instanceof Error ? error.message : String(error) });
764
+ if (error instanceof DoubleLockError) {
765
+ // Step acquired a lock it already held in this async context.
766
+ // `using lock = await repo.lock(...)` always runs BEFORE mutations,
767
+ // so no side effects occurred. Keep the process SUSPENDED at its
768
+ // prior step so a future execution (next firing, or post-deploy
769
+ // restart) can retry against updated code. Terminating the process
770
+ // with `fail()` would destroy weeks-old durable state because of a
771
+ // bug that might land in the very next PR.
772
+ error.message += ` (process ${process.id}/${state.instanceId} step ${state.step})`;
773
+ console.error(`[ProcessExecutor] ${error.message}`);
774
+ state.lastError = error.message;
775
+ state.lastErrorAt = new Date();
776
+ state.status = 'suspended';
777
+ // Re-register prior subscriptions so the next firing re-triggers
778
+ // execute. Cleanup existing tracked subs first to avoid leaks.
779
+ this.cleanupSubscriptions(state.instanceId);
780
+ const priorRace = state.vars.__raceBranches;
781
+ if (priorRace) {
782
+ await this.suspend(state, process, { race: priorRace }, identity);
783
+ }
784
+ else {
785
+ // First-run failure or non-race suspend point — just persist
786
+ // suspended state. Post-deploy re-hydration will retry.
787
+ await this.saveState(state, process);
788
+ }
789
+ return;
790
+ }
791
+ await this.fail(state, error instanceof Error ? error : new Error(String(error)));
792
+ }
793
+ };
794
+ // Wrap in lock tracking so acquire() can detect re-entrant locks within
795
+ // the same async context. Each execution (fresh + resume) gets its own
796
+ // tracking set.
797
+ const tracked = () => runWithLockTracking(executeInner);
798
+ // If container is available, run in request scope for context propagation
799
+ if (this.container) {
800
+ // Get origin context for tracing (if this process was started from a request)
801
+ const originCtx = this.originContexts.get(state.instanceId);
802
+ await runInFullRequestScope({
803
+ container: this.container,
804
+ type: 'process',
805
+ name: `process:${process.id}/${state.instanceId}`,
806
+ metadata: {
807
+ 'process.id': process.id,
808
+ 'process.instanceId': state.instanceId,
809
+ 'process.step': state.step,
810
+ // Include origin context for tracing
811
+ ...(originCtx && {
812
+ 'origin.type': originCtx.type,
813
+ 'origin.name': originCtx.name,
814
+ 'origin.id': originCtx.id,
815
+ }),
816
+ },
817
+ }, tracked);
818
+ }
819
+ else {
820
+ // No container - just run directly
821
+ await tracked();
822
+ }
823
+ }
824
+ /**
825
+ * Handle process suspension.
826
+ */
827
+ async suspend(state, process, config, identity) {
828
+ trace('suspend', { instanceId: state.instanceId, config });
829
+ state.status = 'suspended';
830
+ state.suspendedAt = new Date();
831
+ state.persistedStep = Object.entries(process.stepMap)
832
+ .find(([_, v]) => v === state.step)?.[0] ?? String(state.step);
833
+ if ('race' in config) {
834
+ // Race - subscribe to all branches
835
+ const branches = config.race.map(branch => {
836
+ let signalName = branch.signal;
837
+ let isStream = false;
838
+ // Resolve stream wildcards: stream:ModelName:*:fieldName -> stream:ModelName:entityId:fieldName
839
+ // The * placeholder is set by the compiler and resolved here using process identity
840
+ if (signalName?.startsWith('stream:')) {
841
+ isStream = true;
842
+ if (signalName.includes(':*:')) {
843
+ signalName = this.resolveStreamWildcard(signalName, identity, process.types);
844
+ }
845
+ }
846
+ return {
847
+ branchId: branch.id,
848
+ signal: signalName,
849
+ // Stream signals encode the entity ID in the signal name itself,
850
+ // so the identity map is redundant. Using it as a subscription filter
851
+ // breaks when publisher/subscriber use different key conventions
852
+ // (e.g. publisher emits { roomRef: id }, subscriber has { room: id }).
853
+ identity: signalName ? (isStream ? {} : identity) : undefined,
854
+ expiresAt: branch.timer ? this.calculateExpiry(branch.timer) : undefined,
855
+ };
856
+ });
857
+ const subscriptionId = await this.signalBus.subscribeRace(state.instanceId, branches);
858
+ this.trackSubscription(state.instanceId, subscriptionId);
859
+ // Store race branches in state for resume
860
+ state.vars.__raceBranches = config.race;
861
+ // Schedule timers
862
+ for (const branch of config.race) {
863
+ if (branch.timer) {
864
+ const expiresAt = this.calculateExpiry(branch.timer);
865
+ const timerId = await this.timerScheduler.schedule(state.instanceId, expiresAt, branch.id);
866
+ this.trackSubscription(state.instanceId, timerId);
867
+ }
868
+ }
869
+ }
870
+ else if ('signal' in config) {
871
+ const subscriptionId = await this.signalBus.subscribe(state.instanceId, config.signal, identity);
872
+ this.trackSubscription(state.instanceId, subscriptionId);
873
+ // Persist signal name so resubscribeSuspended can restore it after restart
874
+ state.vars.__suspendSignal = config.signal;
875
+ }
876
+ else if ('timer' in config) {
877
+ const expiresAt = this.calculateExpiry(config.timer);
878
+ const timerId = await this.timerScheduler.schedule(state.instanceId, expiresAt, '__timer__');
879
+ this.trackSubscription(state.instanceId, timerId);
880
+ }
881
+ else if ('parallel' in config) {
882
+ // Parallel (signal.all / signal.settled): subscribe each signal branch individually
883
+ const parallel = config.parallel;
884
+ const branchSubs = [];
885
+ for (let i = 0; i < parallel.branches.length; i++) {
886
+ const branch = parallel.branches[i];
887
+ if (branch.type === 'signal' && branch.expr) {
888
+ const signalName = branch.expr.signalName;
889
+ if (signalName) {
890
+ const subscriptionId = await this.signalBus.subscribe(state.instanceId, signalName, identity);
891
+ this.trackSubscription(state.instanceId, subscriptionId);
892
+ branchSubs.push({ branchIndex: i, subscriptionId });
893
+ }
894
+ }
895
+ else if (branch.type === 'delay') {
896
+ const timerDuration = branch.expr ?? {};
897
+ const expiresAt = this.calculateExpiry(timerDuration);
898
+ const timerId = await this.timerScheduler.schedule(state.instanceId, expiresAt, `__parallel_${parallel.parallelId}_${i}`);
899
+ this.trackSubscription(state.instanceId, timerId);
900
+ branchSubs.push({ branchIndex: i, subscriptionId: timerId });
901
+ }
902
+ }
903
+ // Store parallel tracking info in state.vars for handleSignalMatch
904
+ state.vars.__parallelBranches = branchSubs.map(bs => ({
905
+ branchIndex: bs.branchIndex,
906
+ subscriptionId: bs.subscriptionId,
907
+ parallelId: parallel.parallelId,
908
+ }));
909
+ }
910
+ else if ('scope' in config) {
911
+ // Scope suspension: parallel fan-out over entities
912
+ await this.handleScopeSuspend(state, process, config.scope, identity);
913
+ // Empty scope: handleScopeSuspend sets status='running' and advances the step.
914
+ // Re-execute instead of suspending.
915
+ if (state.status === 'running') {
916
+ await this.saveState(state, process);
917
+ await this.execute(state, process, identity);
918
+ return;
919
+ }
920
+ }
921
+ await this.saveState(state, process);
922
+ // Release lock AFTER subscriptions are registered
923
+ // This ensures signals wait for subscription before being delivered
924
+ await this.releaseLock(`process:${state.instanceId}`);
925
+ }
926
+ /**
927
+ * Re-subscribe a suspended process to its signals.
928
+ * Called when loading a suspended process from storage, since in-memory
929
+ * signal bus doesn't persist subscriptions across restarts.
930
+ */
931
+ async resubscribeSuspended(state, identity, types) {
932
+ // Check if this process has already been re-subscribed
933
+ if (this.subscriptions.has(state.instanceId)) {
934
+ return;
935
+ }
936
+ const raceBranches = state.variables.__raceBranches;
937
+ const suspendSignal = state.variables.__suspendSignal;
938
+ if (raceBranches) {
939
+ // Re-subscribe to race branches
940
+ const branches = raceBranches.map(branch => {
941
+ let signalName = branch.signal;
942
+ let isStream = false;
943
+ // Resolve stream wildcards (same as in suspend)
944
+ if (signalName?.startsWith('stream:')) {
945
+ isStream = true;
946
+ if (signalName.includes(':*:')) {
947
+ signalName = this.resolveStreamWildcard(signalName, identity, types);
948
+ }
949
+ }
950
+ return {
951
+ branchId: branch.id,
952
+ signal: signalName,
953
+ identity: signalName ? (isStream ? {} : identity) : undefined,
954
+ expiresAt: branch.timer ? this.calculateExpiry(branch.timer) : undefined,
955
+ };
956
+ });
957
+ const subscriptionId = await this.signalBus.subscribeRace(state.instanceId, branches);
958
+ this.trackSubscription(state.instanceId, subscriptionId);
959
+ // Schedule timers for any timer branches
960
+ for (const branch of raceBranches) {
961
+ if (branch.timer) {
962
+ const expiresAt = this.calculateExpiry(branch.timer);
963
+ const timerId = await this.timerScheduler.schedule(state.instanceId, expiresAt, branch.id);
964
+ this.trackSubscription(state.instanceId, timerId);
965
+ }
966
+ }
967
+ }
968
+ else if (suspendSignal) {
969
+ // Re-subscribe to plain signal suspension (await signal(x))
970
+ const subscriptionId = await this.signalBus.subscribe(state.instanceId, suspendSignal, identity);
971
+ this.trackSubscription(state.instanceId, subscriptionId);
972
+ }
973
+ }
974
+ // ============================================================================
975
+ // Signal & Timer Handling
976
+ // ============================================================================
977
+ async handleSignalMatch(match) {
978
+ trace('handleSignalMatch', { instanceId: match.instanceId, branchId: match.branchId });
979
+ // Subprocess instanceIds carry an `/__sub:...` suffix — route those to
980
+ // the child-resume path, which loads the parent row and resumes the
981
+ // nested child state in place.
982
+ if (match.instanceId.includes('/__sub:')) {
983
+ await this.resumeChild(match);
984
+ return;
985
+ }
986
+ // Check if this subscription belongs to this executor instance.
987
+ // In multi-instance scenarios, all executors receive the same NOTIFY via Postgres,
988
+ // but only the executor that registered the subscription should process it.
989
+ const trackedSubs = this.subscriptions.get(match.instanceId);
990
+ if (!trackedSubs || !trackedSubs.includes(match.subscriptionId)) {
991
+ trace('handleSignalMatch.skipped', { instanceId: match.instanceId, reason: 'subscription not owned by this executor' });
992
+ return; // This subscription was registered by a different executor instance
993
+ }
994
+ // Acquire lock before resuming process.
995
+ // By design, the lock is always available: the process is suspended (nobody holds it).
996
+ // If this fails, something is fundamentally wrong — propagate the error.
997
+ const lockKey = `process:${match.instanceId}`;
998
+ await this.acquireLock(lockKey);
999
+ trace('handleSignalMatch.lockAcquired', { instanceId: match.instanceId });
1000
+ try {
1001
+ // Note: Don't cleanup subscriptions here - do it after execute
1002
+ // This prevents race conditions when multiple signals arrive quickly
1003
+ const state = await this.loadState(match.instanceId);
1004
+ trace('handleSignalMatch.stateLoaded', { instanceId: match.instanceId, status: state?.status });
1005
+ if (!state) {
1006
+ trace('handleSignalMatch.noState', { instanceId: match.instanceId });
1007
+ await this.releaseLock(lockKey);
1008
+ return;
1009
+ }
1010
+ if (state.status !== 'suspended') {
1011
+ trace('handleSignalMatch.notSuspended', { instanceId: match.instanceId, status: state.status });
1012
+ await this.releaseLock(lockKey);
1013
+ return;
1014
+ }
1015
+ // Find the process definition
1016
+ const process = this.processRegistry.get(state.processId);
1017
+ if (!process) {
1018
+ trace('handleSignalMatch.noProcess', { processId: state.processId });
1019
+ await this.releaseLock(lockKey);
1020
+ return;
1021
+ }
1022
+ trace('handleSignalMatch.processFound', { processId: state.processId });
1023
+ // Check for parallel branch subscription
1024
+ const parallelBranches = state.vars.__parallelBranches;
1025
+ if (parallelBranches) {
1026
+ // Check if this subscription matches a parallel branch
1027
+ const parallelBranch = parallelBranches.find(pb => pb.subscriptionId === match.subscriptionId);
1028
+ if (parallelBranch) {
1029
+ // This is a parallel branch signal - update parallel state in state.vars
1030
+ const parallelVarName = `__parallel_${parallelBranch.parallelId}`;
1031
+ const parallelState = state.vars[parallelVarName];
1032
+ if (parallelState) {
1033
+ parallelState.results[parallelBranch.branchIndex] = decodeProcessable(match.payload);
1034
+ parallelState.pending--;
1035
+ trace('handleSignalMatch.parallelBranch', {
1036
+ parallelId: parallelBranch.parallelId,
1037
+ branchIndex: parallelBranch.branchIndex,
1038
+ pending: parallelState.pending,
1039
+ });
1040
+ // Remove this subscription from tracking
1041
+ this.untrackSubscription(match.instanceId, match.subscriptionId);
1042
+ if (parallelState.pending > 0) {
1043
+ // Not all branches done - save state and release lock, don't resume
1044
+ await this.saveState(state, process);
1045
+ await this.releaseLock(lockKey);
1046
+ return;
1047
+ }
1048
+ // All branches completed - clean up parallel tracking and resume
1049
+ delete state.vars.__parallelBranches;
1050
+ // Get identity from state
1051
+ const identity = state.vars.__identity;
1052
+ // Snapshot remaining subscriptions for cleanup
1053
+ const oldSubs = [...(this.subscriptions.get(match.instanceId) ?? [])];
1054
+ state.status = 'running';
1055
+ trace('handleSignalMatch.parallelComplete', { instanceId: match.instanceId, step: state.step });
1056
+ await this.execute(state, process, identity);
1057
+ trace('handleSignalMatch.executeComplete', { instanceId: match.instanceId });
1058
+ // Cleanup remaining subscriptions
1059
+ for (const subId of oldSubs) {
1060
+ this.signalBus.unsubscribe(subId);
1061
+ this.timerScheduler.cancel(subId);
1062
+ this.untrackSubscription(match.instanceId, subId);
1063
+ }
1064
+ return;
1065
+ }
1066
+ }
1067
+ }
1068
+ // Handle race vs simple signal
1069
+ if (match.branchId) {
1070
+ // Race result - find the winning branch and set step to its resumeStep
1071
+ const branches = state.vars.__raceBranches;
1072
+ trace('handleSignalMatch.raceBranches', { branchId: match.branchId, hasBranches: !!branches, branchCount: branches?.length });
1073
+ if (branches) {
1074
+ const branch = branches.find(b => b.id === match.branchId);
1075
+ if (branch) {
1076
+ state.step = branch.resumeStep;
1077
+ // Stream branches expect { value: T }, signals expect T directly
1078
+ // The branch ID for streams starts with "stream:"
1079
+ if (match.branchId.startsWith('stream:')) {
1080
+ state.vars.__raceResult = { value: decodeProcessable(match.payload) };
1081
+ }
1082
+ else {
1083
+ state.vars.__raceResult = decodeProcessable(match.payload);
1084
+ }
1085
+ delete state.vars.__raceBranches;
1086
+ trace('handleSignalMatch.branchFound', { branchId: match.branchId, resumeStep: branch.resumeStep });
1087
+ }
1088
+ else {
1089
+ trace('handleSignalMatch.branchNotFound', { branchId: match.branchId, availableBranches: branches.map(b => b.id) });
1090
+ }
1091
+ }
1092
+ }
1093
+ else {
1094
+ // Simple signal - payload goes into signalPayload for the execute context
1095
+ state.vars.__signalPayload = decodeProcessable(match.payload);
1096
+ delete state.vars.__suspendSignal;
1097
+ }
1098
+ // Get identity from state
1099
+ const identity = state.vars.__identity;
1100
+ // Snapshot all OLD subscriptions before re-executing.
1101
+ // A race registers both signal subscriptions and timers under different IDs.
1102
+ // When one branch wins, ALL losing branches must be cancelled — not just the matched one.
1103
+ const oldSubs = [...(this.subscriptions.get(match.instanceId) ?? [])];
1104
+ // Resume execution (lock released in suspend/complete/fail)
1105
+ trace('handleSignalMatch.execute', { instanceId: match.instanceId, step: state.step });
1106
+ await this.execute(state, process, identity);
1107
+ trace('handleSignalMatch.executeComplete', { instanceId: match.instanceId });
1108
+ // Cleanup ALL old subscriptions (signals + timers from the previous suspend).
1109
+ // The process has now re-suspended with NEW subscriptions (different IDs)
1110
+ // or completed (which already called cleanupSubscriptions).
1111
+ for (const subId of oldSubs) {
1112
+ this.signalBus.unsubscribe(subId);
1113
+ this.timerScheduler.cancel(subId);
1114
+ this.untrackSubscription(match.instanceId, subId);
1115
+ }
1116
+ }
1117
+ catch (err) {
1118
+ // Release lock on error
1119
+ await this.releaseLock(lockKey);
1120
+ throw err;
1121
+ }
1122
+ }
1123
+ async handleTimerFired(fired) {
1124
+ // Timer fired - treat as signal match with void payload
1125
+ const match = {
1126
+ subscriptionId: fired.timerId,
1127
+ instanceId: fired.instanceId,
1128
+ payload: undefined,
1129
+ branchId: fired.branchId,
1130
+ };
1131
+ await this.handleSignalMatch(match);
1132
+ }
1133
+ // External entry point for adapters that source timer fires outside the
1134
+ // bound timerScheduler (e.g. ScheduledTask delivery via HTTP/WS). Mirrors
1135
+ // the internal onFire path: setImmediate so we don't run the dispatch
1136
+ // synchronously inside the caller's stack, then route into the same
1137
+ // private handler. handleSignalMatch absorbs unknown subscriptions, so a
1138
+ // stale fire after subscribe-then-cancel is harmless.
1139
+ receiveTimerFire(fired) {
1140
+ setImmediate(() => {
1141
+ this.handleTimerFired(fired).catch(err => {
1142
+ trace('receiveTimerFire dispatch error', { err: String(err) });
1143
+ });
1144
+ });
1145
+ }
1146
+ // ============================================================================
1147
+ // Scope Handling (parallel fan-out)
1148
+ // ============================================================================
1149
+ /**
1150
+ * Handle scope suspension: fan-out over entities.
1151
+ *
1152
+ * For signal-first form: subscribe to the signal for each entity's identity.
1153
+ * For handler form: spawn sub-processes for each entity.
1154
+ *
1155
+ * When all entities complete, resume the parent process at resumeStep.
1156
+ */
1157
+ async handleScopeSuspend(state, process, scopeConfig, identity) {
1158
+ const { scopeId, resumeStep } = scopeConfig;
1159
+ const entities = state.vars[`__scope_${scopeId}_entities`];
1160
+ const idFn = state.vars[`__scope_${scopeId}_idFn`];
1161
+ if (!entities || entities.length === 0) {
1162
+ // No entities — skip suspension, continue to resume step
1163
+ state.step = resumeStep;
1164
+ state.status = 'running';
1165
+ state.vars[`__scope_${scopeId}_results`] = {};
1166
+ return;
1167
+ }
1168
+ // Extract entity IDs
1169
+ const entityIds = entities.map((entity, index) => {
1170
+ if (idFn)
1171
+ return idFn(entity);
1172
+ // Auto-derive ID: if entity has an 'id' property, use it
1173
+ if (typeof entity === 'object' && entity !== null && 'id' in entity) {
1174
+ return String(entity.id);
1175
+ }
1176
+ return String(index);
1177
+ });
1178
+ // TSP3008: Check for duplicate entity IDs (runtime check)
1179
+ const seen = new Set();
1180
+ for (const id of entityIds) {
1181
+ if (seen.has(id)) {
1182
+ throw new Error(`Duplicate entity ID '${id}' in scope(). Each entity must have a unique identity.`);
1183
+ }
1184
+ seen.add(id);
1185
+ }
1186
+ // TSP3006: Check item limit (runtime check)
1187
+ if (entities.length > 1000) {
1188
+ throw new Error(`scope() exceeded maximum item limit (1000). Got ${entities.length} entities.`);
1189
+ }
1190
+ // Initialize scope tracking
1191
+ state.vars[`__scope_${scopeId}_remaining`] = entities.length;
1192
+ state.vars[`__scope_${scopeId}_results`] = {};
1193
+ state.vars[`__scope_${scopeId}_entityIds`] = entityIds;
1194
+ state.vars[`__scope_${scopeId}_resumeStep`] = resumeStep;
1195
+ if (scopeConfig.type === 'signal') {
1196
+ // Signal-first form: subscribe to signal for each entity
1197
+ // Each entity gets a race-style subscription that resolves independently
1198
+ for (let i = 0; i < entityIds.length; i++) {
1199
+ const entityId = entityIds[i];
1200
+ const entityIdentity = { ...identity, __scopeEntityId: entityId };
1201
+ const subscriptionId = await this.signalBus.subscribe(state.instanceId, String(scopeConfig.signal), entityIdentity);
1202
+ this.trackSubscription(state.instanceId, subscriptionId);
1203
+ }
1204
+ // Store scope info for signal matching
1205
+ state.vars.__scopeActive = scopeId;
1206
+ }
1207
+ // Handler form would spawn sub-processes here (future: compile handler body)
1208
+ }
1209
+ /**
1210
+ * Notify that a scope entity has completed.
1211
+ * Decrements the remaining counter and resumes parent if all done.
1212
+ */
1213
+ async notifyScopeEntityComplete(instanceId, scopeId, entityId, result) {
1214
+ const state = await this.loadState(instanceId);
1215
+ if (!state)
1216
+ return;
1217
+ const results = state.vars[`__scope_${scopeId}_results`];
1218
+ results[entityId] = result;
1219
+ const remaining = state.vars[`__scope_${scopeId}_remaining`] - 1;
1220
+ state.vars[`__scope_${scopeId}_remaining`] = remaining;
1221
+ const process = this.processRegistry.get(state.processId);
1222
+ if (remaining === 0) {
1223
+ // All entities complete — results already a plain object, resume
1224
+ const resumeStep = state.vars[`__scope_${scopeId}_resumeStep`];
1225
+ state.step = resumeStep;
1226
+ state.status = 'running';
1227
+ await this.saveState(state, process);
1228
+ // Resume execution
1229
+ if (process) {
1230
+ const identity = state.vars.__identity;
1231
+ await this.execute(state, process, identity);
1232
+ }
1233
+ }
1234
+ else {
1235
+ await this.saveState(state, process);
1236
+ }
1237
+ }
1238
+ // ============================================================================
1239
+ // State Management
1240
+ // ============================================================================
1241
+ async loadState(instanceId) {
1242
+ const stored = await this.storage.load(instanceId);
1243
+ if (!stored)
1244
+ return null;
1245
+ // Convert from storage format to SwitchProcessState
1246
+ return {
1247
+ processId: stored.processId,
1248
+ instanceId: stored.instanceId,
1249
+ version: stored.version,
1250
+ step: stored.pc,
1251
+ persistedStep: stored.variables.__persistedStep ?? 'entry',
1252
+ vars: deserializeState(stored.variables),
1253
+ timers: stored.timers,
1254
+ createdAt: stored.createdAt,
1255
+ updatedAt: stored.updatedAt,
1256
+ suspendedAt: stored.suspendedAt,
1257
+ completedAt: stored.completedAt,
1258
+ status: stored.status,
1259
+ result: stored.result,
1260
+ error: stored.error,
1261
+ lastError: stored.lastError,
1262
+ lastErrorAt: stored.lastErrorAt,
1263
+ };
1264
+ }
1265
+ async saveState(state, process) {
1266
+ // Store persistedStep in vars so it survives round-trip
1267
+ state.vars.__persistedStep = state.persistedStep;
1268
+ // Convert to storage format
1269
+ const serializedVars = serializeState(state.vars);
1270
+ await this.storage.save({
1271
+ processId: state.processId,
1272
+ instanceId: state.instanceId,
1273
+ version: state.version,
1274
+ pc: state.step,
1275
+ variables: serializedVars,
1276
+ timers: state.timers,
1277
+ createdAt: state.createdAt,
1278
+ updatedAt: new Date(),
1279
+ suspendedAt: state.suspendedAt,
1280
+ completedAt: state.completedAt,
1281
+ status: state.status,
1282
+ result: state.result,
1283
+ error: state.error,
1284
+ lastError: state.lastError,
1285
+ lastErrorAt: state.lastErrorAt,
1286
+ });
1287
+ const handle = this.handles.get(state.instanceId);
1288
+ if (handle) {
1289
+ handle.updateStatus(state.status);
1290
+ }
1291
+ // Broadcast exports if the process has them.
1292
+ // Use state.vars.exports (pre-serialization) — serializedVars has Processable-encoded
1293
+ // forms (Maps → __$type tags) which aren't useful for subscribers.
1294
+ if (process?.exports && state.vars.exports) {
1295
+ const exportsData = {};
1296
+ const exportsObj = state.vars.exports;
1297
+ for (const fieldName of process.exports.fields) {
1298
+ exportsData[fieldName] = exportsObj[fieldName];
1299
+ }
1300
+ const frozenSnap = this.buildFrozenExports(exportsObj, process.exports);
1301
+ if (handle) {
1302
+ handle.setExportsData(frozenSnap);
1303
+ }
1304
+ // Also publish to external channel backend if wired
1305
+ if (this.publishExports) {
1306
+ try {
1307
+ await this.publishExports({
1308
+ instanceId: state.instanceId,
1309
+ processId: state.processId,
1310
+ exports: exportsData,
1311
+ });
1312
+ }
1313
+ catch {
1314
+ // Don't fail the process if export broadcast fails
1315
+ trace('saveState.exportsBroadcastFailed', { instanceId: state.instanceId });
1316
+ }
1317
+ }
1318
+ }
1319
+ }
1320
+ // ============================================================================
1321
+ // Helpers
1322
+ // ============================================================================
1323
+ async spawnSubprocess(state, process, spawnConfig, identity) {
1324
+ const subDef = process.subprocesses?.find(s => s.name === spawnConfig.name);
1325
+ if (!subDef) {
1326
+ throw new Error(`Subprocess '${spawnConfig.name}' not found on process '${process.id}'`);
1327
+ }
1328
+ const awaited = spawnConfig.awaited !== false;
1329
+ // Create a unique key for this subprocess instance based on args
1330
+ const subKey = `__sub:${spawnConfig.name}:${spawnConfig.args.map(String).join(':')}`;
1331
+ const childInstanceId = `${state.instanceId}/${subKey}`;
1332
+ // Blob layout under parent.vars[subKey]:
1333
+ // { vars: {...user+signal state...}, step, done, result, raceBranches?, suspendSignal? }
1334
+ // User-space child variables live under `.vars` so they never collide with
1335
+ // the blob's own metadata. Idempotent: if already-done blob exists and the
1336
+ // spawn is awaited, flow the cached result straight into storeVar.
1337
+ let blob = state.vars[subKey];
1338
+ if (blob?.done === true && awaited) {
1339
+ if (spawnConfig.storeVar) {
1340
+ state.vars[spawnConfig.storeVar] = blob.result;
1341
+ }
1342
+ await this.saveState(state, process);
1343
+ await this.execute(state, process, identity);
1344
+ return;
1345
+ }
1346
+ if (!blob) {
1347
+ blob = { vars: {}, step: 0, done: false };
1348
+ for (let i = 0; i < subDef.params.length; i++) {
1349
+ blob.vars[subDef.params[i]] = spawnConfig.args[i];
1350
+ }
1351
+ state.vars[subKey] = blob;
1352
+ }
1353
+ // Build a minimal SwitchProcessState for the child execute function.
1354
+ // The child shares the parent's services (same inject map on parent process).
1355
+ const childState = {
1356
+ processId: `${process.id}/${subKey}`,
1357
+ instanceId: childInstanceId,
1358
+ version: '0',
1359
+ step: blob.step ?? 0,
1360
+ persistedStep: String(blob.step ?? 0),
1361
+ vars: blob.vars,
1362
+ timers: [],
1363
+ createdAt: new Date(),
1364
+ updatedAt: new Date(),
1365
+ status: 'running',
1366
+ };
1367
+ const services = await this.resolveServices(process);
1368
+ const childCtx = {
1369
+ state: childState,
1370
+ services,
1371
+ emit: () => { },
1372
+ };
1373
+ // Child signal identity = parent's identity + child's path-param args.
1374
+ // Without the child-specific keys, two siblings (alice/bob) would both
1375
+ // match emits scoped to one (e.g. signals.childTick(p1, alice) would
1376
+ // also wake bob). Merging params lets the bus route per-child.
1377
+ const childIdentity = { ...identity };
1378
+ for (let i = 0; i < subDef.params.length; i++) {
1379
+ childIdentity[subDef.params[i]] = String(spawnConfig.args[i]);
1380
+ }
1381
+ const childResult = await subDef.execute(childCtx);
1382
+ await this.applyChildResult(state, process, identity, childIdentity, subKey, childState, childResult, spawnConfig, /* fromSpawn */ true);
1383
+ }
1384
+ /**
1385
+ * Apply a child execute() result — from either the initial spawn or a
1386
+ * signal-driven resume. Mutates parent.vars[subKey] for DONE/SUSPEND,
1387
+ * resumes the parent on awaited DONE, and manages the parent lock.
1388
+ *
1389
+ * `fromSpawn=true` means the parent is currently inside its own execute()
1390
+ * loop and should resume there for detached outcomes. `fromSpawn=false`
1391
+ * means resumeChild called us and the parent was otherwise idle/suspended;
1392
+ * detached outcomes just save + release.
1393
+ */
1394
+ async applyChildResult(state, process, parentIdentity, childIdentity, subKey, childState, childResult, spawnConfig, fromSpawn) {
1395
+ const awaited = spawnConfig.awaited !== false;
1396
+ const childInstanceId = childState.instanceId;
1397
+ const blob = state.vars[subKey];
1398
+ const parentLockKey = `process:${state.instanceId}`;
1399
+ if (childResult[0] === 0) {
1400
+ // DONE
1401
+ blob.done = true;
1402
+ blob.result = childResult[1];
1403
+ blob.step = childState.step;
1404
+ delete blob.raceBranches;
1405
+ delete blob.suspendSignal;
1406
+ if (awaited) {
1407
+ if (spawnConfig.storeVar) {
1408
+ state.vars[spawnConfig.storeVar] = childResult[1];
1409
+ }
1410
+ delete state.vars.__pendingChildAwait;
1411
+ state.status = 'running';
1412
+ await this.saveState(state, process);
1413
+ await this.execute(state, process, parentIdentity);
1414
+ return;
1415
+ }
1416
+ // Detached DONE — refresh the handle ref
1417
+ if (spawnConfig.storeVar) {
1418
+ state.vars[spawnConfig.storeVar] = { __subRef: true, key: subKey, name: spawnConfig.name, result: childResult[1], done: true };
1419
+ }
1420
+ await this.saveState(state, process);
1421
+ if (fromSpawn) {
1422
+ await this.execute(state, process, parentIdentity);
1423
+ }
1424
+ else {
1425
+ await this.releaseLock(parentLockKey);
1426
+ }
1427
+ return;
1428
+ }
1429
+ if (childResult[0] === 2) {
1430
+ throw new Error(`Subprocess '${spawnConfig.name}' attempted to spawn a nested subprocess; ` +
1431
+ 'nested subprocesses are not yet implemented.');
1432
+ }
1433
+ // SUSPEND — register the child's subscriptions under childInstanceId
1434
+ const childConfig = childResult[1];
1435
+ blob.step = childState.step;
1436
+ await this.subscribeChildSuspension(childInstanceId, childConfig, childIdentity, blob);
1437
+ if (awaited) {
1438
+ state.vars.__pendingChildAwait = { subKey, storeVar: spawnConfig.storeVar };
1439
+ state.status = 'suspended';
1440
+ state.suspendedAt = new Date();
1441
+ await this.saveState(state, process);
1442
+ await this.releaseLock(parentLockKey);
1443
+ return;
1444
+ }
1445
+ // Detached SUSPEND — parent continues / stays as-is
1446
+ if (spawnConfig.storeVar) {
1447
+ state.vars[spawnConfig.storeVar] = { __subRef: true, key: subKey, name: spawnConfig.name, done: false };
1448
+ }
1449
+ await this.saveState(state, process);
1450
+ if (fromSpawn) {
1451
+ await this.execute(state, process, parentIdentity);
1452
+ }
1453
+ else {
1454
+ await this.releaseLock(parentLockKey);
1455
+ }
1456
+ }
1457
+ async subscribeChildSuspension(childInstanceId, config, identity, blob) {
1458
+ if ('race' in config) {
1459
+ const branches = config.race.map(branch => {
1460
+ let signalName = branch.signal;
1461
+ let isStream = false;
1462
+ if (signalName?.startsWith('stream:')) {
1463
+ isStream = true;
1464
+ if (signalName.includes(':*:')) {
1465
+ signalName = this.resolveStreamWildcard(signalName, identity);
1466
+ }
1467
+ }
1468
+ return {
1469
+ branchId: branch.id,
1470
+ signal: signalName,
1471
+ identity: signalName ? (isStream ? {} : identity) : undefined,
1472
+ expiresAt: branch.timer ? this.calculateExpiry(branch.timer) : undefined,
1473
+ };
1474
+ });
1475
+ const subscriptionId = await this.signalBus.subscribeRace(childInstanceId, branches);
1476
+ this.trackSubscription(childInstanceId, subscriptionId);
1477
+ blob.raceBranches = config.race;
1478
+ for (const branch of config.race) {
1479
+ if (branch.timer) {
1480
+ const expiresAt = this.calculateExpiry(branch.timer);
1481
+ const timerId = await this.timerScheduler.schedule(childInstanceId, expiresAt, branch.id);
1482
+ this.trackSubscription(childInstanceId, timerId);
1483
+ }
1484
+ }
1485
+ }
1486
+ else if ('signal' in config) {
1487
+ const subscriptionId = await this.signalBus.subscribe(childInstanceId, config.signal, identity);
1488
+ this.trackSubscription(childInstanceId, subscriptionId);
1489
+ blob.suspendSignal = config.signal;
1490
+ }
1491
+ else if ('timer' in config) {
1492
+ const expiresAt = this.calculateExpiry(config.timer);
1493
+ const timerId = await this.timerScheduler.schedule(childInstanceId, expiresAt, '__timer__');
1494
+ this.trackSubscription(childInstanceId, timerId);
1495
+ }
1496
+ else {
1497
+ throw new Error('Subprocess suspended with unsupported config shape ' +
1498
+ '(scope/parallel inside subprocesses is not yet implemented).');
1499
+ }
1500
+ }
1501
+ /**
1502
+ * Resume a child process after one of its signal subscriptions fired.
1503
+ * The child's instanceId encodes its scope as `${parentInstanceId}/__sub:name:args`.
1504
+ * We load the parent row, extract the child's nested state, apply the signal
1505
+ * match, and re-enter child.execute(). On child DONE, applyChildResult
1506
+ * resumes the parent if the parent was awaiting.
1507
+ */
1508
+ async resumeChild(match) {
1509
+ const { parentInstanceId, subKey } = parseChildInstanceId(match.instanceId);
1510
+ if (!parentInstanceId || !subKey) {
1511
+ trace('resumeChild.invalidInstanceId', { instanceId: match.instanceId });
1512
+ return;
1513
+ }
1514
+ const trackedSubs = this.subscriptions.get(match.instanceId);
1515
+ if (!trackedSubs || !trackedSubs.includes(match.subscriptionId)) {
1516
+ trace('resumeChild.notOwned', { instanceId: match.instanceId });
1517
+ return;
1518
+ }
1519
+ const parentLockKey = `process:${parentInstanceId}`;
1520
+ await this.acquireLock(parentLockKey);
1521
+ try {
1522
+ const parentState = await this.loadState(parentInstanceId);
1523
+ if (!parentState) {
1524
+ trace('resumeChild.noParent', { parentInstanceId });
1525
+ await this.releaseLock(parentLockKey);
1526
+ return;
1527
+ }
1528
+ const blob = parentState.vars[subKey];
1529
+ if (!blob) {
1530
+ trace('resumeChild.noChildBlob', { parentInstanceId, subKey });
1531
+ await this.releaseLock(parentLockKey);
1532
+ return;
1533
+ }
1534
+ const process = this.processRegistry.get(parentState.processId);
1535
+ if (!process) {
1536
+ trace('resumeChild.noProcess', { processId: parentState.processId });
1537
+ await this.releaseLock(parentLockKey);
1538
+ return;
1539
+ }
1540
+ const subName = subKey.split(':')[1];
1541
+ const subDef = process.subprocesses?.find(s => s.name === subName);
1542
+ if (!subDef) {
1543
+ trace('resumeChild.noSubDef', { subName });
1544
+ await this.releaseLock(parentLockKey);
1545
+ return;
1546
+ }
1547
+ // Apply the signal match to the child's blob — mirrors handleSignalMatch
1548
+ // race/simple-signal dispatch but on the nested blob.
1549
+ if (match.branchId) {
1550
+ const branches = blob.raceBranches;
1551
+ if (branches) {
1552
+ const branch = branches.find(b => b.id === match.branchId);
1553
+ if (branch) {
1554
+ blob.step = branch.resumeStep;
1555
+ if (match.branchId.startsWith('stream:')) {
1556
+ blob.vars.__raceResult = { value: decodeProcessable(match.payload) };
1557
+ }
1558
+ else {
1559
+ blob.vars.__raceResult = decodeProcessable(match.payload);
1560
+ }
1561
+ delete blob.raceBranches;
1562
+ }
1563
+ }
1564
+ }
1565
+ else {
1566
+ blob.vars.__signalPayload = decodeProcessable(match.payload);
1567
+ delete blob.suspendSignal;
1568
+ }
1569
+ const childState = {
1570
+ processId: `${process.id}/${subKey}`,
1571
+ instanceId: match.instanceId,
1572
+ version: '0',
1573
+ step: blob.step ?? 0,
1574
+ persistedStep: String(blob.step ?? 0),
1575
+ vars: blob.vars,
1576
+ timers: [],
1577
+ createdAt: new Date(),
1578
+ updatedAt: new Date(),
1579
+ status: 'running',
1580
+ };
1581
+ const services = await this.resolveServices(process);
1582
+ const childCtx = { state: childState, services, emit: () => { } };
1583
+ const oldSubs = [...(this.subscriptions.get(match.instanceId) ?? [])];
1584
+ const childResult = await subDef.execute(childCtx);
1585
+ // Reconstruct spawnConfig from __pendingChildAwait so applyChildResult
1586
+ // knows the awaited/storeVar context. For detached, neither is set.
1587
+ const pendingAwait = parentState.vars.__pendingChildAwait;
1588
+ const spawnConfig = {
1589
+ name: subName,
1590
+ args: subDef.params.map(p => blob.vars[p]),
1591
+ storeVar: pendingAwait?.subKey === subKey ? pendingAwait.storeVar : undefined,
1592
+ awaited: pendingAwait?.subKey === subKey,
1593
+ };
1594
+ const parentIdentity = parentState.vars.__identity ?? {};
1595
+ const childIdentity = { ...parentIdentity };
1596
+ for (const p of subDef.params) {
1597
+ childIdentity[p] = String(blob.vars[p]);
1598
+ }
1599
+ await this.applyChildResult(parentState, process, parentIdentity, childIdentity, subKey, childState, childResult, spawnConfig, /* fromSpawn */ false);
1600
+ for (const subId of oldSubs) {
1601
+ this.signalBus.unsubscribe(subId);
1602
+ this.timerScheduler.cancel(subId);
1603
+ this.untrackSubscription(match.instanceId, subId);
1604
+ }
1605
+ }
1606
+ catch (err) {
1607
+ await this.releaseLock(parentLockKey);
1608
+ throw err;
1609
+ }
1610
+ }
1611
+ buildFrozenExports(exportsObj, metadata) {
1612
+ const data = {};
1613
+ for (const fieldName of metadata.fields) {
1614
+ data[fieldName] = exportsObj[fieldName];
1615
+ }
1616
+ return freezeExports(data, metadata.methods);
1617
+ }
1618
+ async resolveServices(process) {
1619
+ const cached = this.resolvedServicesCache.get(process.id);
1620
+ if (cached)
1621
+ return cached;
1622
+ const resolved = {};
1623
+ for (const [key, token] of Object.entries(process.inject)) {
1624
+ resolved[key] = await this.resolve(token);
1625
+ }
1626
+ this.resolvedServicesCache.set(process.id, resolved);
1627
+ return resolved;
1628
+ }
1629
+ calculateExpiry(timer) {
1630
+ const ms = (timer.days ?? 0) * 24 * 60 * 60 * 1000 +
1631
+ (timer.hours ?? 0) * 60 * 60 * 1000 +
1632
+ (timer.minutes ?? 0) * 60 * 1000 +
1633
+ (timer.seconds ?? 0) * 1000;
1634
+ return new Date(Date.now() + ms);
1635
+ }
1636
+ /**
1637
+ * Resolve stream signal wildcard to actual entity ID.
1638
+ *
1639
+ * Stream signals from the compiler have format: stream:ModelName:*:fieldName
1640
+ * The * needs to be resolved using the process identity at runtime.
1641
+ *
1642
+ * Uses the shared stream-utils for proper camelCase conversion that handles:
1643
+ * - Standard models: Order → orderId
1644
+ * - Acronyms: ABC → abcId, HTTPServer → httpServerId
1645
+ * - Numbers: V2Order → v2OrderId
1646
+ *
1647
+ * @example
1648
+ * Input: stream:Order:*:statusUpdates, identity: { orderId: 'abc123' }
1649
+ * Output: stream:Order:abc123:statusUpdates
1650
+ */
1651
+ resolveStreamWildcard(signalName, identity, types) {
1652
+ const { resolved, result } = resolveStreamWildcardUtil(signalName, identity, types);
1653
+ if (result.success) {
1654
+ if (result.usedFallback) {
1655
+ trace('resolveStreamWildcard.fallback', {
1656
+ from: signalName,
1657
+ to: resolved,
1658
+ usedKey: result.usedKey,
1659
+ });
1660
+ }
1661
+ else {
1662
+ trace('resolveStreamWildcard', { from: signalName, to: resolved });
1663
+ }
1664
+ }
1665
+ else {
1666
+ trace('resolveStreamWildcard.failed', { signalName, error: result.error });
1667
+ // Log warning to help users debug
1668
+ console.warn(`[Stream] Warning: ${result.error}`);
1669
+ }
1670
+ return resolved;
1671
+ }
1672
+ trackSubscription(instanceId, subscriptionId) {
1673
+ const subs = this.subscriptions.get(instanceId) ?? [];
1674
+ subs.push(subscriptionId);
1675
+ this.subscriptions.set(instanceId, subs);
1676
+ }
1677
+ untrackSubscription(instanceId, subscriptionId) {
1678
+ const subs = this.subscriptions.get(instanceId);
1679
+ if (subs) {
1680
+ const idx = subs.indexOf(subscriptionId);
1681
+ if (idx !== -1) {
1682
+ subs.splice(idx, 1);
1683
+ }
1684
+ if (subs.length === 0) {
1685
+ this.subscriptions.delete(instanceId);
1686
+ }
1687
+ }
1688
+ }
1689
+ cleanupSubscriptions(instanceId) {
1690
+ const subs = this.subscriptions.get(instanceId);
1691
+ if (subs) {
1692
+ for (const subId of subs) {
1693
+ this.signalBus.unsubscribe(subId);
1694
+ this.timerScheduler.cancel(subId);
1695
+ }
1696
+ this.subscriptions.delete(instanceId);
1697
+ }
1698
+ }
1699
+ /**
1700
+ * Cascade-cancel all live subprocesses of a parent reaching a terminal
1701
+ * state. Lexical scope dictates lifetime: children's subscriptions are
1702
+ * torn down so they can no longer receive signals and resume. Nested
1703
+ * blob stays in parent.vars for post-mortem inspection.
1704
+ */
1705
+ cleanupChildSubscriptions(state) {
1706
+ for (const key of Object.keys(state.vars)) {
1707
+ if (!key.startsWith('__sub:'))
1708
+ continue;
1709
+ const blob = state.vars[key];
1710
+ if (!blob || blob.done)
1711
+ continue;
1712
+ const childInstanceId = `${state.instanceId}/${key}`;
1713
+ this.cleanupSubscriptions(childInstanceId);
1714
+ }
1715
+ }
1716
+ /**
1717
+ * Acquire a process lock. No-op if no lock provider configured.
1718
+ * Blocks until lock is acquired - JustScale locks never fail.
1719
+ */
1720
+ async acquireLock(lockKey) {
1721
+ if (!this.lockProvider)
1722
+ return;
1723
+ await this.lockProvider.acquire(lockKey, { ...this.lockOptions, key: lockKey }, this.executorId);
1724
+ }
1725
+ /**
1726
+ * Release a process lock. No-op if no lock provider configured.
1727
+ */
1728
+ async releaseLock(lockKey) {
1729
+ trace('releaseLock', { lockKey });
1730
+ if (!this.lockProvider)
1731
+ return;
1732
+ await this.lockProvider.release(lockKey, this.executorId);
1733
+ trace('releaseLock.done', { lockKey });
1734
+ }
1735
+ async complete(state, result) {
1736
+ state.status = 'completed';
1737
+ state.result = result;
1738
+ state.completedAt = new Date();
1739
+ // Cascade: parent reached terminal state, tear down any live children.
1740
+ // Lexical scope dictates lifetime; detached mode is a v1.1 concern.
1741
+ this.cleanupChildSubscriptions(state);
1742
+ const process = this.processRegistry.get(state.processId);
1743
+ await this.saveState(state, process);
1744
+ await this.storage.complete(state.instanceId, result);
1745
+ this.cleanupSubscriptions(state.instanceId);
1746
+ this.originContexts.delete(state.instanceId);
1747
+ this.handles.delete(state.instanceId);
1748
+ // Signal yield queue completion
1749
+ const yieldQueue = this.yieldQueues.get(state.instanceId);
1750
+ if (yieldQueue) {
1751
+ yieldQueue.complete();
1752
+ this.yieldQueues.delete(state.instanceId);
1753
+ }
1754
+ // Release process lock after completion
1755
+ await this.releaseLock(`process:${state.instanceId}`);
1756
+ const completion = this.completions.get(state.instanceId);
1757
+ if (completion) {
1758
+ completion.resolve(result);
1759
+ this.completions.delete(state.instanceId);
1760
+ }
1761
+ }
1762
+ async fail(state, error) {
1763
+ state.status = 'failed';
1764
+ state.error = error.message;
1765
+ state.completedAt = new Date();
1766
+ this.cleanupChildSubscriptions(state);
1767
+ const process = this.processRegistry.get(state.processId);
1768
+ await this.saveState(state, process);
1769
+ await this.storage.fail(state.instanceId, error.message);
1770
+ this.cleanupSubscriptions(state.instanceId);
1771
+ this.originContexts.delete(state.instanceId);
1772
+ this.handles.delete(state.instanceId);
1773
+ // Signal yield queue completion (consumers see done)
1774
+ const yieldQueue = this.yieldQueues.get(state.instanceId);
1775
+ if (yieldQueue) {
1776
+ yieldQueue.complete();
1777
+ this.yieldQueues.delete(state.instanceId);
1778
+ }
1779
+ // Release process lock after failure
1780
+ await this.releaseLock(`process:${state.instanceId}`);
1781
+ const completion = this.completions.get(state.instanceId);
1782
+ if (completion) {
1783
+ completion.reject(error);
1784
+ this.completions.delete(state.instanceId);
1785
+ }
1786
+ }
1787
+ // ============================================================================
1788
+ // Query Methods
1789
+ // ============================================================================
1790
+ /**
1791
+ * Get a process state by instance ID.
1792
+ */
1793
+ async get(instanceId) {
1794
+ return this.loadState(instanceId);
1795
+ }
1796
+ async *queryByStatus(status) {
1797
+ for await (const stored of this.storage.findByStatus(status)) {
1798
+ const state = await this.loadState(stored.instanceId);
1799
+ if (state)
1800
+ yield state;
1801
+ }
1802
+ }
1803
+ async *queryByProcessId(processId) {
1804
+ for await (const stored of this.storage.findByProcessId(processId)) {
1805
+ const state = await this.loadState(stored.instanceId);
1806
+ if (state)
1807
+ yield state;
1808
+ }
1809
+ }
1810
+ // ============================================================================
1811
+ // Cancellation
1812
+ // ============================================================================
1813
+ /**
1814
+ * Cancel a process instance.
1815
+ * Only processes in 'pending' or 'suspended' status can be cancelled.
1816
+ * Cleans up subscriptions, timers, and rejects the completion promise.
1817
+ *
1818
+ * @returns true if the process was cancelled, false if it was already completed/failed
1819
+ */
1820
+ async cancel(instanceId) {
1821
+ const lockKey = `process:${instanceId}`;
1822
+ await this.acquireLock(lockKey);
1823
+ try {
1824
+ const state = await this.loadState(instanceId);
1825
+ if (!state) {
1826
+ await this.releaseLock(lockKey);
1827
+ return false;
1828
+ }
1829
+ if (state.status === 'completed' || state.status === 'failed' || state.status === 'cancelled') {
1830
+ await this.releaseLock(lockKey);
1831
+ return false;
1832
+ }
1833
+ state.status = 'cancelled';
1834
+ state.completedAt = new Date();
1835
+ this.cleanupChildSubscriptions(state);
1836
+ const process = this.processRegistry.get(state.processId);
1837
+ await this.saveState(state, process);
1838
+ this.cleanupSubscriptions(instanceId);
1839
+ this.originContexts.delete(instanceId);
1840
+ this.handles.delete(instanceId);
1841
+ // Signal yield queue completion
1842
+ const yieldQueue = this.yieldQueues.get(instanceId);
1843
+ if (yieldQueue) {
1844
+ yieldQueue.complete();
1845
+ this.yieldQueues.delete(instanceId);
1846
+ }
1847
+ await this.releaseLock(lockKey);
1848
+ // Reject completion promise
1849
+ const completion = this.completions.get(instanceId);
1850
+ if (completion) {
1851
+ completion.reject(new Error('Process cancelled'));
1852
+ this.completions.delete(instanceId);
1853
+ }
1854
+ return true;
1855
+ }
1856
+ catch (err) {
1857
+ await this.releaseLock(lockKey);
1858
+ throw err;
1859
+ }
1860
+ }
1861
+ // ============================================================================
1862
+ // Yield / Continuation
1863
+ // ============================================================================
1864
+ /**
1865
+ * Create a ProcessContinuation for a generator process.
1866
+ * Allows iterating over yielded values with a durable consumer cursor.
1867
+ *
1868
+ * @param instanceId - The process instance ID
1869
+ * @param consumerId - Optional consumer ID for reconnecting to a previous cursor position.
1870
+ * If not provided, a new consumer is created starting from the beginning.
1871
+ */
1872
+ async createContinuation(instanceId, consumerId) {
1873
+ const state = await this.loadState(instanceId);
1874
+ if (!state) {
1875
+ throw new Error(`Process instance not found: ${instanceId}`);
1876
+ }
1877
+ const resolvedConsumerId = consumerId ?? `consumer_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
1878
+ // Load persisted cursor for this consumer
1879
+ const consumers = (state.vars.__yieldConsumers ?? {});
1880
+ const initialCursor = consumers[resolvedConsumerId]?.cursor ?? 0;
1881
+ // Get or create yield queue
1882
+ let yieldQueue = this.yieldQueues.get(instanceId);
1883
+ if (!yieldQueue) {
1884
+ yieldQueue = createYieldQueue();
1885
+ this.yieldQueues.set(instanceId, yieldQueue);
1886
+ // If process already finished, signal completion on the new queue
1887
+ // so consumers don't hang after draining stored yields
1888
+ if (state.status === 'completed' || state.status === 'failed' || state.status === 'cancelled') {
1889
+ yieldQueue.complete();
1890
+ }
1891
+ }
1892
+ // Get or create completion deferred
1893
+ let completion = this.completions.get(instanceId);
1894
+ if (!completion) {
1895
+ const newCompletion = createDeferred();
1896
+ this.completions.set(instanceId, newCompletion);
1897
+ completion = newCompletion;
1898
+ if (state.status === 'completed') {
1899
+ newCompletion.resolve(state.result);
1900
+ }
1901
+ else if (state.status === 'failed') {
1902
+ newCompletion.reject(new Error(state.error ?? 'Process failed'));
1903
+ }
1904
+ }
1905
+ const storage = this.storage;
1906
+ const impl = new ProcessContinuationImpl(instanceId, resolvedConsumerId, initialCursor, state.status, yieldQueue, async () => {
1907
+ const s = await storage.load(instanceId);
1908
+ return s?.variables?.__yields ?? [];
1909
+ }, completion, async (cid, cursor) => {
1910
+ const current = await storage.load(instanceId);
1911
+ if (current) {
1912
+ const vars = current.variables;
1913
+ const yieldConsumers = (vars.__yieldConsumers ?? {});
1914
+ yieldConsumers[cid] = { cursor };
1915
+ vars.__yieldConsumers = yieldConsumers;
1916
+ await storage.save(current);
1917
+ }
1918
+ });
1919
+ return impl;
1920
+ }
1921
+ // ============================================================================
1922
+ // Lifecycle
1923
+ // ============================================================================
1924
+ /** Start the timer scheduler */
1925
+ startTimers() {
1926
+ this.timerScheduler.start();
1927
+ }
1928
+ /** Stop the timer scheduler */
1929
+ stopTimers() {
1930
+ this.timerScheduler.stop();
1931
+ }
1932
+ /** Clear caches (for testing) */
1933
+ clearCaches() {
1934
+ this.resolvedServicesCache.clear();
1935
+ this.completions.clear();
1936
+ this.subscriptions.clear();
1937
+ this.originContexts.clear();
1938
+ this.yieldQueues.clear();
1939
+ }
1940
+ }
1941
+ //# sourceMappingURL=executor.js.map