@justscale/typescript 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +128 -0
  3. package/dist/api.d.ts +144 -0
  4. package/dist/api.d.ts.map +1 -0
  5. package/dist/api.js +380 -0
  6. package/dist/api.js.map +1 -0
  7. package/dist/compiler/analyzer.d.ts +247 -0
  8. package/dist/compiler/analyzer.d.ts.map +1 -0
  9. package/dist/compiler/analyzer.js +3201 -0
  10. package/dist/compiler/analyzer.js.map +1 -0
  11. package/dist/compiler/cli.d.ts +12 -0
  12. package/dist/compiler/cli.d.ts.map +1 -0
  13. package/dist/compiler/cli.js +209 -0
  14. package/dist/compiler/cli.js.map +1 -0
  15. package/dist/compiler/compile.d.ts +26 -0
  16. package/dist/compiler/compile.d.ts.map +1 -0
  17. package/dist/compiler/compile.js +121 -0
  18. package/dist/compiler/compile.js.map +1 -0
  19. package/dist/compiler/errors.d.ts +336 -0
  20. package/dist/compiler/errors.d.ts.map +1 -0
  21. package/dist/compiler/errors.js +466 -0
  22. package/dist/compiler/errors.js.map +1 -0
  23. package/dist/compiler/exports-prepass.d.ts +31 -0
  24. package/dist/compiler/exports-prepass.d.ts.map +1 -0
  25. package/dist/compiler/exports-prepass.js +249 -0
  26. package/dist/compiler/exports-prepass.js.map +1 -0
  27. package/dist/compiler/hmr-change-detector.d.ts +47 -0
  28. package/dist/compiler/hmr-change-detector.d.ts.map +1 -0
  29. package/dist/compiler/hmr-change-detector.js +395 -0
  30. package/dist/compiler/hmr-change-detector.js.map +1 -0
  31. package/dist/compiler/hmr-transformer.d.ts +54 -0
  32. package/dist/compiler/hmr-transformer.d.ts.map +1 -0
  33. package/dist/compiler/hmr-transformer.js +535 -0
  34. package/dist/compiler/hmr-transformer.js.map +1 -0
  35. package/dist/compiler/index.d.ts +19 -0
  36. package/dist/compiler/index.d.ts.map +1 -0
  37. package/dist/compiler/index.js +16 -0
  38. package/dist/compiler/index.js.map +1 -0
  39. package/dist/compiler/primitive-detector.d.ts +70 -0
  40. package/dist/compiler/primitive-detector.d.ts.map +1 -0
  41. package/dist/compiler/primitive-detector.js +338 -0
  42. package/dist/compiler/primitive-detector.js.map +1 -0
  43. package/dist/compiler/ptsc.d.ts +40 -0
  44. package/dist/compiler/ptsc.d.ts.map +1 -0
  45. package/dist/compiler/ptsc.js +462 -0
  46. package/dist/compiler/ptsc.js.map +1 -0
  47. package/dist/compiler/rewriter.d.ts +96 -0
  48. package/dist/compiler/rewriter.d.ts.map +1 -0
  49. package/dist/compiler/rewriter.js +418 -0
  50. package/dist/compiler/rewriter.js.map +1 -0
  51. package/dist/compiler/step-hash.d.ts +43 -0
  52. package/dist/compiler/step-hash.d.ts.map +1 -0
  53. package/dist/compiler/step-hash.js +83 -0
  54. package/dist/compiler/step-hash.js.map +1 -0
  55. package/dist/compiler/switch-codegen.d.ts +84 -0
  56. package/dist/compiler/switch-codegen.d.ts.map +1 -0
  57. package/dist/compiler/switch-codegen.js +1540 -0
  58. package/dist/compiler/switch-codegen.js.map +1 -0
  59. package/dist/compiler/transformer.d.ts +29 -0
  60. package/dist/compiler/transformer.d.ts.map +1 -0
  61. package/dist/compiler/transformer.js +216 -0
  62. package/dist/compiler/transformer.js.map +1 -0
  63. package/dist/config/index.d.ts +122 -0
  64. package/dist/config/index.d.ts.map +1 -0
  65. package/dist/config/index.js +215 -0
  66. package/dist/config/index.js.map +1 -0
  67. package/dist/di-errors/formatter.d.ts +126 -0
  68. package/dist/di-errors/formatter.d.ts.map +1 -0
  69. package/dist/di-errors/formatter.js +384 -0
  70. package/dist/di-errors/formatter.js.map +1 -0
  71. package/dist/di-errors/index.d.ts +5 -0
  72. package/dist/di-errors/index.d.ts.map +1 -0
  73. package/dist/di-errors/index.js +13 -0
  74. package/dist/di-errors/index.js.map +1 -0
  75. package/dist/editor.d.ts +11 -0
  76. package/dist/editor.d.ts.map +1 -0
  77. package/dist/editor.js +2 -0
  78. package/dist/editor.js.map +1 -0
  79. package/dist/index.d.ts +35 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +40 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/language-service/index.d.ts +52 -0
  84. package/dist/language-service/index.d.ts.map +1 -0
  85. package/dist/language-service/index.js +366 -0
  86. package/dist/language-service/index.js.map +1 -0
  87. package/dist/language-service/process-quick-fixes.d.ts +20 -0
  88. package/dist/language-service/process-quick-fixes.d.ts.map +1 -0
  89. package/dist/language-service/process-quick-fixes.js +114 -0
  90. package/dist/language-service/process-quick-fixes.js.map +1 -0
  91. package/dist/language-service/quick-fix-discovery.d.ts +39 -0
  92. package/dist/language-service/quick-fix-discovery.d.ts.map +1 -0
  93. package/dist/language-service/quick-fix-discovery.js +124 -0
  94. package/dist/language-service/quick-fix-discovery.js.map +1 -0
  95. package/dist/loader/incremental.d.ts +50 -0
  96. package/dist/loader/incremental.d.ts.map +1 -0
  97. package/dist/loader/incremental.js +151 -0
  98. package/dist/loader/incremental.js.map +1 -0
  99. package/dist/loader/index.d.ts +25 -0
  100. package/dist/loader/index.d.ts.map +1 -0
  101. package/dist/loader/index.js +24 -0
  102. package/dist/loader/index.js.map +1 -0
  103. package/dist/loader/loader.d.ts +52 -0
  104. package/dist/loader/loader.d.ts.map +1 -0
  105. package/dist/loader/loader.js +248 -0
  106. package/dist/loader/loader.js.map +1 -0
  107. package/dist/loader/register.d.ts +14 -0
  108. package/dist/loader/register.d.ts.map +1 -0
  109. package/dist/loader/register.js +20 -0
  110. package/dist/loader/register.js.map +1 -0
  111. package/dist/plugins/index.d.ts +13 -0
  112. package/dist/plugins/index.d.ts.map +1 -0
  113. package/dist/plugins/index.js +13 -0
  114. package/dist/plugins/index.js.map +1 -0
  115. package/dist/plugins/index.public.d.ts +13 -0
  116. package/dist/plugins/index.public.d.ts.map +1 -0
  117. package/dist/plugins/index.public.js +13 -0
  118. package/dist/plugins/index.public.js.map +1 -0
  119. package/dist/plugins/types.d.ts +83 -0
  120. package/dist/plugins/types.d.ts.map +1 -0
  121. package/dist/plugins/types.js +24 -0
  122. package/dist/plugins/types.js.map +1 -0
  123. package/dist/server/index.d.ts +33 -0
  124. package/dist/server/index.d.ts.map +1 -0
  125. package/dist/server/index.js +42 -0
  126. package/dist/server/index.js.map +1 -0
  127. package/dist/server/tsserver.d.ts +28 -0
  128. package/dist/server/tsserver.d.ts.map +1 -0
  129. package/dist/server/tsserver.js +126 -0
  130. package/dist/server/tsserver.js.map +1 -0
  131. package/lib/lib.d.ts +20 -0
  132. package/lib/lib.decorators.d.ts +382 -0
  133. package/lib/lib.decorators.legacy.d.ts +20 -0
  134. package/lib/lib.dom.asynciterable.d.ts +18 -0
  135. package/lib/lib.dom.d.ts +45125 -0
  136. package/lib/lib.dom.iterable.d.ts +18 -0
  137. package/lib/lib.es2015.collection.d.ts +150 -0
  138. package/lib/lib.es2015.core.d.ts +595 -0
  139. package/lib/lib.es2015.d.ts +26 -0
  140. package/lib/lib.es2015.generator.d.ts +75 -0
  141. package/lib/lib.es2015.iterable.d.ts +603 -0
  142. package/lib/lib.es2015.promise.d.ts +79 -0
  143. package/lib/lib.es2015.proxy.d.ts +126 -0
  144. package/lib/lib.es2015.reflect.d.ts +142 -0
  145. package/lib/lib.es2015.symbol.d.ts +44 -0
  146. package/lib/lib.es2015.symbol.wellknown.d.ts +324 -0
  147. package/lib/lib.es2016.array.include.d.ts +114 -0
  148. package/lib/lib.es2016.d.ts +19 -0
  149. package/lib/lib.es2016.full.d.ts +21 -0
  150. package/lib/lib.es2016.intl.d.ts +29 -0
  151. package/lib/lib.es2017.arraybuffer.d.ts +19 -0
  152. package/lib/lib.es2017.d.ts +24 -0
  153. package/lib/lib.es2017.date.d.ts +29 -0
  154. package/lib/lib.es2017.full.d.ts +21 -0
  155. package/lib/lib.es2017.intl.d.ts +42 -0
  156. package/lib/lib.es2017.object.d.ts +47 -0
  157. package/lib/lib.es2017.sharedmemory.d.ts +133 -0
  158. package/lib/lib.es2017.string.d.ts +43 -0
  159. package/lib/lib.es2017.typedarrays.d.ts +51 -0
  160. package/lib/lib.es2018.asyncgenerator.d.ts +75 -0
  161. package/lib/lib.es2018.asynciterable.d.ts +51 -0
  162. package/lib/lib.es2018.d.ts +22 -0
  163. package/lib/lib.es2018.full.d.ts +22 -0
  164. package/lib/lib.es2018.intl.d.ts +81 -0
  165. package/lib/lib.es2018.promise.d.ts +28 -0
  166. package/lib/lib.es2018.regexp.d.ts +35 -0
  167. package/lib/lib.es2019.array.d.ts +77 -0
  168. package/lib/lib.es2019.d.ts +22 -0
  169. package/lib/lib.es2019.full.d.ts +22 -0
  170. package/lib/lib.es2019.intl.d.ts +21 -0
  171. package/lib/lib.es2019.object.d.ts +31 -0
  172. package/lib/lib.es2019.string.d.ts +35 -0
  173. package/lib/lib.es2019.symbol.d.ts +22 -0
  174. package/lib/lib.es2020.bigint.d.ts +763 -0
  175. package/lib/lib.es2020.d.ts +25 -0
  176. package/lib/lib.es2020.date.d.ts +40 -0
  177. package/lib/lib.es2020.full.d.ts +22 -0
  178. package/lib/lib.es2020.intl.d.ts +472 -0
  179. package/lib/lib.es2020.number.d.ts +26 -0
  180. package/lib/lib.es2020.promise.d.ts +45 -0
  181. package/lib/lib.es2020.sharedmemory.d.ts +97 -0
  182. package/lib/lib.es2020.string.d.ts +42 -0
  183. package/lib/lib.es2020.symbol.wellknown.d.ts +39 -0
  184. package/lib/lib.es2021.d.ts +21 -0
  185. package/lib/lib.es2021.full.d.ts +22 -0
  186. package/lib/lib.es2021.intl.d.ts +164 -0
  187. package/lib/lib.es2021.promise.d.ts +46 -0
  188. package/lib/lib.es2021.string.d.ts +31 -0
  189. package/lib/lib.es2021.weakref.d.ts +76 -0
  190. package/lib/lib.es2022.array.d.ts +119 -0
  191. package/lib/lib.es2022.d.ts +23 -0
  192. package/lib/lib.es2022.error.d.ts +73 -0
  193. package/lib/lib.es2022.full.d.ts +22 -0
  194. package/lib/lib.es2022.intl.d.ts +143 -0
  195. package/lib/lib.es2022.object.d.ts +24 -0
  196. package/lib/lib.es2022.regexp.d.ts +37 -0
  197. package/lib/lib.es2022.string.d.ts +23 -0
  198. package/lib/lib.es2023.array.d.ts +922 -0
  199. package/lib/lib.es2023.collection.d.ts +19 -0
  200. package/lib/lib.es2023.d.ts +20 -0
  201. package/lib/lib.es2023.full.d.ts +22 -0
  202. package/lib/lib.es2023.intl.d.ts +62 -0
  203. package/lib/lib.es2024.arraybuffer.d.ts +63 -0
  204. package/lib/lib.es2024.collection.d.ts +27 -0
  205. package/lib/lib.es2024.d.ts +24 -0
  206. package/lib/lib.es2024.full.d.ts +22 -0
  207. package/lib/lib.es2024.object.d.ts +27 -0
  208. package/lib/lib.es2024.promise.d.ts +33 -0
  209. package/lib/lib.es2024.regexp.d.ts +23 -0
  210. package/lib/lib.es2024.sharedmemory.d.ts +66 -0
  211. package/lib/lib.es2024.string.d.ts +27 -0
  212. package/lib/lib.es2025.collection.d.ts +94 -0
  213. package/lib/lib.es2025.d.ts +23 -0
  214. package/lib/lib.es2025.float16.d.ts +443 -0
  215. package/lib/lib.es2025.full.d.ts +22 -0
  216. package/lib/lib.es2025.intl.d.ts +200 -0
  217. package/lib/lib.es2025.iterator.d.ts +146 -0
  218. package/lib/lib.es2025.promise.d.ts +32 -0
  219. package/lib/lib.es2025.regexp.d.ts +30 -0
  220. package/lib/lib.es5.d.ts +4599 -0
  221. package/lib/lib.es6.d.ts +21 -0
  222. package/lib/lib.esnext.array.d.ts +33 -0
  223. package/lib/lib.esnext.collection.d.ts +47 -0
  224. package/lib/lib.esnext.d.ts +27 -0
  225. package/lib/lib.esnext.date.d.ts +21 -0
  226. package/lib/lib.esnext.decorators.d.ts +26 -0
  227. package/lib/lib.esnext.disposable.d.ts +191 -0
  228. package/lib/lib.esnext.error.d.ts +22 -0
  229. package/lib/lib.esnext.full.d.ts +22 -0
  230. package/lib/lib.esnext.intl.d.ts +107 -0
  231. package/lib/lib.esnext.sharedmemory.d.ts +23 -0
  232. package/lib/lib.esnext.temporal.d.ts +485 -0
  233. package/lib/lib.esnext.typedarrays.d.ts +90 -0
  234. package/lib/lib.scripthost.d.ts +320 -0
  235. package/lib/lib.webworker.asynciterable.d.ts +18 -0
  236. package/lib/lib.webworker.d.ts +15606 -0
  237. package/lib/lib.webworker.importscripts.d.ts +21 -0
  238. package/lib/lib.webworker.iterable.d.ts +18 -0
  239. package/lib/logger.js +144 -0
  240. package/lib/package.json +7 -0
  241. package/lib/tsserver.js +57 -0
  242. package/lib/tsserverlibrary.js +171 -0
  243. package/lib/typesMap.json +497 -0
  244. package/lib/typescript.js +373 -0
  245. package/package.json +115 -0
@@ -0,0 +1,1540 @@
1
+ /**
2
+ * @justscale/core/process - Switch-Based Code Generator
3
+ *
4
+ * Generates VM-style switch-based execution code from process analysis.
5
+ *
6
+ * The generated code follows this pattern:
7
+ * ```
8
+ * async execute(ctx) {
9
+ * const { state, services } = ctx
10
+ * let step = state.step | 0
11
+ * const __r = [0, undefined] // [DONE/SUSPEND, payload]
12
+ *
13
+ * main_loop: while (true) {
14
+ * switch (step) {
15
+ * case 0: { ... }
16
+ * case 1: { ... }
17
+ * }
18
+ * }
19
+ * return __r
20
+ * }
21
+ * ```
22
+ */
23
+ import ts from 'typescript';
24
+ import { rewriteStatement, rewriteExpression, extractParamVars, extractParamAliases, extractServiceVars, extractDeclaredVars, cloneExpression, } from './rewriter.js';
25
+ import { computeStepHash } from './step-hash.js';
26
+ /**
27
+ * Get the using variables that need rehydration for a step executing the
28
+ * opcode range `[start, end)`.
29
+ *
30
+ * On resume, any `using` var whose initializer runs in this step (via a REHYDRATE
31
+ * opcode) needs to be re-run. Three passes:
32
+ *
33
+ * 1. Vars whose REHYDRATE opcode lives in `[start, end)` - they're local to this step.
34
+ * 2. Vars referenced in BLOCK opcodes within `[start, end)` whose `usedInBlocks` overlaps.
35
+ * 3. Transitive deps: if var A's initializer references using-var B, B must
36
+ * also be in the prelude. We resolve transitivity here so the caller can
37
+ * topologically sort them in the emitter.
38
+ *
39
+ * Scanning is bounded to `[start, end)` to avoid leaking vars from sibling race
40
+ * branches or later steps - replaying branch A must never run branch B's
41
+ * `await rooms.lock(...)` just because B's opcodes live after A's in the flat
42
+ * opcode list.
43
+ */
44
+ function getRehydrateDepsForStep(analysis, start, end) {
45
+ const { opcodes, rehydrationBlocks } = analysis;
46
+ const usingVarNames = Object.keys(rehydrationBlocks);
47
+ if (usingVarNames.length === 0)
48
+ return undefined;
49
+ const usingVarSet = new Set(usingVarNames);
50
+ const needed = new Set();
51
+ // REHYDRATE opcodes within [start, end): vars initialized here must be re-run on resume.
52
+ for (let i = start; i < end; i++) {
53
+ const op = opcodes[i];
54
+ if (op.op === 'REHYDRATE') {
55
+ needed.add(op.var);
56
+ }
57
+ }
58
+ // BLOCK opcodes within [start, end): vars referenced (but declared earlier) also need rehydration.
59
+ const usedBlockIds = new Set();
60
+ for (let i = start; i < end; i++) {
61
+ const op = opcodes[i];
62
+ if (op.op === 'BLOCK' && op.blockId !== undefined) {
63
+ usedBlockIds.add(op.blockId);
64
+ }
65
+ }
66
+ for (const varName of usingVarNames) {
67
+ const varInfo = analysis.variables.get(varName);
68
+ if (varInfo && varInfo.usedInBlocks.some(blockId => usedBlockIds.has(blockId))) {
69
+ needed.add(varName);
70
+ }
71
+ }
72
+ // Transitive deps: if A's initializer references using-var B, B must also be rehydrated.
73
+ // Per-opcode REHYDRATE expressions take priority over the global rehydrationBlocks map.
74
+ const opcodeExprByVar = new Map();
75
+ for (let i = start; i < end; i++) {
76
+ const op = opcodes[i];
77
+ if (op.op === 'REHYDRATE') {
78
+ opcodeExprByVar.set(op.var, op.expression);
79
+ }
80
+ }
81
+ let changed = true;
82
+ while (changed) {
83
+ changed = false;
84
+ for (const varName of Array.from(needed)) {
85
+ // Get the initializer expression for this var
86
+ const expr = opcodeExprByVar.get(varName) ?? rehydrationBlocks[varName]?.expression;
87
+ if (!expr)
88
+ continue;
89
+ // Walk the expression to find referenced using-vars
90
+ const visit = (node) => {
91
+ if (ts.isIdentifier(node) && usingVarSet.has(node.text) && !needed.has(node.text)) {
92
+ needed.add(node.text);
93
+ changed = true;
94
+ }
95
+ ts.forEachChild(node, visit);
96
+ };
97
+ visit(expr);
98
+ }
99
+ }
100
+ return needed.size > 0 ? Array.from(needed) : undefined;
101
+ }
102
+ /**
103
+ * Build steps from opcodes.
104
+ * Steps are created for:
105
+ * 1. Entry point (step 0)
106
+ * 2. After each suspension point (WAIT, RACE_SUSPEND)
107
+ * 3. Jump targets (after JUMP_IF, LABEL targets)
108
+ * 4. Race branch handlers
109
+ */
110
+ export function buildSteps(analysis) {
111
+ const { opcodes, opcodeSourceNodes, raceBranchSourceNodes } = analysis;
112
+ const steps = [];
113
+ const stepMap = {};
114
+ const sourceMap = {};
115
+ // First pass: identify step boundaries
116
+ const stepBoundaries = new Set();
117
+ stepBoundaries.add(0); // Entry point
118
+ for (let i = 0; i < opcodes.length; i++) {
119
+ const op = opcodes[i];
120
+ // After WAIT: next opcode is a resume point
121
+ if (op.op === 'WAIT' && i + 1 < opcodes.length) {
122
+ stepBoundaries.add(i + 1);
123
+ }
124
+ // After RACE_SUSPEND: each branch jumpTarget is a resume point
125
+ if (op.op === 'RACE_SUSPEND') {
126
+ // Find the preceding RACE_START
127
+ for (let j = i - 1; j >= 0; j--) {
128
+ const prevOp = opcodes[j];
129
+ if (prevOp.op === 'RACE_START') {
130
+ for (const branch of prevOp.branches) {
131
+ stepBoundaries.add(branch.jumpTarget);
132
+ }
133
+ break;
134
+ }
135
+ }
136
+ }
137
+ // JUMP targets
138
+ if (op.op === 'JUMP') {
139
+ stepBoundaries.add(op.target);
140
+ }
141
+ // JUMP_IF targets (both true and false paths)
142
+ if (op.op === 'JUMP_IF') {
143
+ stepBoundaries.add(op.target);
144
+ if (i + 1 < opcodes.length) {
145
+ stepBoundaries.add(i + 1); // Fall-through path
146
+ }
147
+ }
148
+ // LABEL is a jump target
149
+ if (op.op === 'LABEL') {
150
+ stepBoundaries.add(i);
151
+ }
152
+ // ITER_NEXT doneTarget is a jump target
153
+ if (op.op === 'ITER_NEXT') {
154
+ stepBoundaries.add(op.doneTarget);
155
+ }
156
+ // After PARALLEL_WAIT: next opcode (PARALLEL_COLLECT) is a resume point
157
+ if (op.op === 'PARALLEL_WAIT' && i + 1 < opcodes.length) {
158
+ stepBoundaries.add(i + 1);
159
+ }
160
+ // After SCOPE_WAIT or SCOPE_HANDLER: next opcode is a resume point
161
+ if ((op.op === 'SCOPE_WAIT' || op.op === 'SCOPE_HANDLER') && i + 1 < opcodes.length) {
162
+ stepBoundaries.add(i + 1);
163
+ }
164
+ // SUBPROCESS_SPAWN: next opcode is a resume point (subprocess may suspend)
165
+ if (op.op === 'SUBPROCESS_SPAWN' && i + 1 < opcodes.length) {
166
+ stepBoundaries.add(i + 1);
167
+ }
168
+ }
169
+ // Sort boundaries
170
+ const sortedBoundaries = Array.from(stepBoundaries).sort((a, b) => a - b);
171
+ // Second pass: create steps
172
+ for (let i = 0; i < sortedBoundaries.length; i++) {
173
+ const start = sortedBoundaries[i];
174
+ const end = i + 1 < sortedBoundaries.length ? sortedBoundaries[i + 1] : opcodes.length;
175
+ // Skip empty ranges
176
+ if (start >= opcodes.length)
177
+ continue;
178
+ // Determine step type and branch info
179
+ let type = 'block';
180
+ let branchInfo;
181
+ if (start === 0) {
182
+ type = 'entry';
183
+ }
184
+ else {
185
+ // Check if this is a resume after WAIT or PARALLEL_WAIT
186
+ const prevOp = start > 0 ? opcodes[start - 1] : null;
187
+ if (prevOp?.op === 'WAIT' || prevOp?.op === 'PARALLEL_WAIT') {
188
+ type = 'resume';
189
+ }
190
+ // Check if this is a race branch target
191
+ for (let j = 0; j < start; j++) {
192
+ const op = opcodes[j];
193
+ if (op.op === 'RACE_START') {
194
+ for (const branch of op.branches) {
195
+ if (branch.jumpTarget === start) {
196
+ type = 'branch';
197
+ branchInfo = { raceOpcodeIndex: j, branchId: branch.id };
198
+ break;
199
+ }
200
+ }
201
+ }
202
+ }
203
+ }
204
+ // Get rehydrate deps for resume points (both WAIT resume and race branches).
205
+ // Only consider blocks inside THIS step's opcode range - pulling deps from
206
+ // later steps or sibling race branches leaks cross-branch `using` vars.
207
+ let rehydrateDeps;
208
+ if (start > 0 && (type === 'resume' || type === 'branch')) {
209
+ rehydrateDeps = getRehydrateDepsForStep(analysis, start, end);
210
+ }
211
+ // Compute source range
212
+ let sourceRange;
213
+ // For branch steps, use the race branch source node (the case clause)
214
+ if (type === 'branch' && branchInfo) {
215
+ const branchNodes = raceBranchSourceNodes.get(branchInfo.raceOpcodeIndex);
216
+ const branchNode = branchNodes?.get(branchInfo.branchId);
217
+ if (branchNode) {
218
+ const sf = branchNode.getSourceFile();
219
+ if (sf) {
220
+ const startLine = sf.getLineAndCharacterOfPosition(branchNode.getStart()).line + 1;
221
+ const endLine = sf.getLineAndCharacterOfPosition(branchNode.getEnd()).line + 1;
222
+ sourceRange = [startLine, endLine];
223
+ }
224
+ }
225
+ }
226
+ // For other steps or as fallback, use opcodes in the range
227
+ if (!sourceRange) {
228
+ for (let j = start; j < end; j++) {
229
+ const sourceNode = opcodeSourceNodes[j];
230
+ if (sourceNode) {
231
+ const sf = sourceNode.getSourceFile();
232
+ if (sf) {
233
+ const startLine = sf.getLineAndCharacterOfPosition(sourceNode.getStart()).line + 1;
234
+ const endLine = sf.getLineAndCharacterOfPosition(sourceNode.getEnd()).line + 1;
235
+ if (!sourceRange) {
236
+ sourceRange = [startLine, endLine];
237
+ }
238
+ else {
239
+ sourceRange[0] = Math.min(sourceRange[0], startLine);
240
+ sourceRange[1] = Math.max(sourceRange[1], endLine);
241
+ }
242
+ }
243
+ }
244
+ }
245
+ }
246
+ // Compute hash
247
+ const hashInput = {
248
+ type,
249
+ opcodeRange: { start, end },
250
+ index: steps.length,
251
+ };
252
+ const hash = computeStepHash(hashInput, analysis, start);
253
+ const step = {
254
+ index: steps.length,
255
+ hash,
256
+ type,
257
+ sourceRange,
258
+ opcodeRange: { start, end },
259
+ rehydrateDeps,
260
+ branchInfo,
261
+ };
262
+ // Determine next step
263
+ if (end < opcodes.length && !stepBoundaries.has(end)) {
264
+ // Find the step that contains the next opcode
265
+ const nextBoundary = sortedBoundaries.find(b => b > end);
266
+ if (nextBoundary !== undefined) {
267
+ // Will be patched after all steps are created
268
+ }
269
+ }
270
+ stepMap[hash] = step.index;
271
+ if (sourceRange) {
272
+ sourceMap[step.index] = sourceRange;
273
+ }
274
+ steps.push(step);
275
+ }
276
+ // Third pass: patch nextStep references
277
+ // First, compute each race branch's opcode range so we can determine
278
+ // which steps belong to which branch body.
279
+ // Map: raceOpcodeIndex -> array of { branchId, startOpcode, endOpcode }
280
+ const raceBranchRanges = new Map();
281
+ for (let i = 0; i < opcodes.length; i++) {
282
+ const op = opcodes[i];
283
+ if (op.op === 'RACE_START') {
284
+ const ranges = [];
285
+ for (let b = 0; b < op.branches.length; b++) {
286
+ const branch = op.branches[b];
287
+ const start = branch.jumpTarget;
288
+ // End is the next branch's start, or the end of all opcodes
289
+ const end = b + 1 < op.branches.length
290
+ ? op.branches[b + 1].jumpTarget
291
+ : opcodes.length;
292
+ ranges.push({ branchId: branch.id, start, end });
293
+ }
294
+ raceBranchRanges.set(i, ranges);
295
+ }
296
+ }
297
+ // Find the continuation step for each race (first step after all branch bodies)
298
+ const raceContinuationSteps = new Map();
299
+ for (const [raceIdx, ranges] of raceBranchRanges) {
300
+ const lastRange = ranges[ranges.length - 1];
301
+ // Find the first step whose opcodes start at or after the last branch's end
302
+ const continuationStep = steps.find(s => s.opcodeRange.start >= lastRange.end);
303
+ if (continuationStep) {
304
+ raceContinuationSteps.set(raceIdx, continuationStep.index);
305
+ }
306
+ }
307
+ for (let i = 0; i < steps.length; i++) {
308
+ const step = steps[i];
309
+ const endOpcodeIdx = step.opcodeRange.end - 1;
310
+ if (endOpcodeIdx >= 0 && endOpcodeIdx < opcodes.length) {
311
+ const lastOp = opcodes[endOpcodeIdx];
312
+ // If last opcode is JUMP, set nextStep to the target step
313
+ if (lastOp.op === 'JUMP') {
314
+ const targetStep = steps.find(s => s.opcodeRange.start === lastOp.target);
315
+ if (targetStep) {
316
+ step.nextStep = targetStep.index;
317
+ }
318
+ }
319
+ // If last opcode is WAIT, RACE_SUSPEND, or PARALLEL_WAIT, no nextStep (handled by resume handler)
320
+ else if (lastOp.op === 'WAIT' || lastOp.op === 'RACE_SUSPEND' || lastOp.op === 'PARALLEL_WAIT' || lastOp.op === 'SCOPE_WAIT' || lastOp.op === 'SCOPE_HANDLER') {
321
+ // No nextStep - will be set by resume handler
322
+ }
323
+ // If last opcode is RETURN, no nextStep
324
+ else if (lastOp.op === 'RETURN') {
325
+ // No nextStep
326
+ }
327
+ // Race branch entry: nextStep depends on whether the branch has
328
+ // intermediate steps (e.g. for-of loops creating additional steps).
329
+ // If the next step is still within this branch's body, flow to it.
330
+ // Otherwise, jump to the continuation step after all race branches.
331
+ else if (step.type === 'branch' && step.branchInfo) {
332
+ if (i + 1 < steps.length) {
333
+ const nextStepCandidate = steps[i + 1];
334
+ // Check if next step is another branch entry of the same race
335
+ const isNextBranchEntry = nextStepCandidate.type === 'branch' &&
336
+ nextStepCandidate.branchInfo?.raceOpcodeIndex === step.branchInfo.raceOpcodeIndex;
337
+ if (isNextBranchEntry) {
338
+ // This branch is a single step; jump to continuation after all branches
339
+ let continuation = raceContinuationSteps.get(step.branchInfo.raceOpcodeIndex);
340
+ if (continuation === undefined) {
341
+ // No continuation found by opcode range (happens when last branch extends
342
+ // to end of opcodes, e.g. race inside while(true)). Find the first non-branch
343
+ // step after all branches of this race.
344
+ for (let j = i + 1; j < steps.length; j++) {
345
+ const candidate = steps[j];
346
+ if (candidate.type !== 'branch' || candidate.branchInfo?.raceOpcodeIndex !== step.branchInfo.raceOpcodeIndex) {
347
+ continuation = candidate.index;
348
+ raceContinuationSteps.set(step.branchInfo.raceOpcodeIndex, continuation);
349
+ break;
350
+ }
351
+ }
352
+ }
353
+ if (continuation !== undefined) {
354
+ step.nextStep = continuation;
355
+ }
356
+ }
357
+ else {
358
+ // Branch has more steps; flow to the next one
359
+ step.nextStep = nextStepCandidate.index;
360
+ }
361
+ }
362
+ else {
363
+ // Last step overall - use continuation if available
364
+ const continuation = raceContinuationSteps.get(step.branchInfo.raceOpcodeIndex);
365
+ if (continuation !== undefined) {
366
+ step.nextStep = continuation;
367
+ }
368
+ }
369
+ }
370
+ // Otherwise, nextStep is the following step - but check if we're at
371
+ // the end of a race branch body, in which case we need to jump to
372
+ // the continuation step (past all branches) instead of falling into
373
+ // the next branch's entry.
374
+ else if (i + 1 < steps.length) {
375
+ const nextCandidate = steps[i + 1];
376
+ // Check if next step is a branch entry (of any race) while current
377
+ // step is within that race's branch body
378
+ if (nextCandidate.type === 'branch' && nextCandidate.branchInfo) {
379
+ const ranges = raceBranchRanges.get(nextCandidate.branchInfo.raceOpcodeIndex);
380
+ const isInSameRace = ranges?.some(r => step.opcodeRange.start >= r.start && step.opcodeRange.start < r.end);
381
+ if (isInSameRace) {
382
+ // Current step is the last step of a branch body - jump to continuation
383
+ const continuation = raceContinuationSteps.get(nextCandidate.branchInfo.raceOpcodeIndex);
384
+ if (continuation !== undefined) {
385
+ step.nextStep = continuation;
386
+ }
387
+ // else: no continuation (race is at end of handler) - no nextStep
388
+ }
389
+ else {
390
+ step.nextStep = nextCandidate.index;
391
+ }
392
+ }
393
+ else {
394
+ step.nextStep = nextCandidate.index;
395
+ }
396
+ }
397
+ }
398
+ }
399
+ return { steps, stepMap, sourceMap };
400
+ }
401
+ /**
402
+ * Generate the compiled switch-based process.
403
+ */
404
+ export function generateSwitchProcess(factory, input) {
405
+ const { id, path, version, injectNode, handler, analysis, originalNode } = input;
406
+ // Build rewriter context
407
+ // Subprocess varNames are compile-time constructs - the compiler emits
408
+ // SUBPROCESS_SPAWN opcodes for calls to them, so they must never be
409
+ // rewritten to state.vars.* or treated as persisted locals.
410
+ const subprocessVarNames = new Set(analysis.subprocesses.map(s => s.varName));
411
+ const rewriterCtx = {
412
+ paramVars: extractParamVars(handler),
413
+ paramAliases: extractParamAliases(handler),
414
+ serviceVars: extractServiceVars(handler),
415
+ localVars: new Set(Array.from(analysis.variables.entries())
416
+ .filter(([name, info]) => !info.isUsing && !subprocessVarNames.has(name))
417
+ .map(([name]) => name)),
418
+ usingVars: new Set(Array.from(analysis.variables.entries())
419
+ .filter(([_, info]) => info.isUsing)
420
+ .map(([name]) => name)),
421
+ raceVars: analysis.raceVars,
422
+ };
423
+ // Build steps from opcodes
424
+ const { steps, stepMap, sourceMap } = buildSteps(analysis);
425
+ // Generate the execute function
426
+ const executeFunction = generateExecuteFunction(factory, steps, analysis, rewriterCtx, handler);
427
+ // Build property assignments for the __createProcess call
428
+ const processProperties = [
429
+ factory.createPropertyAssignment('id', factory.createStringLiteral(id)),
430
+ factory.createPropertyAssignment('path', factory.createStringLiteral(path)),
431
+ factory.createPropertyAssignment('version', factory.createStringLiteral(version)),
432
+ factory.createPropertyAssignment('inject', injectNode ?? factory.createObjectLiteralExpression([])),
433
+ factory.createPropertyAssignment('stepMap', generateStepMapObject(factory, stepMap)),
434
+ factory.createPropertyAssignment('sourceMap', generateSourceMapObject(factory, sourceMap)),
435
+ factory.createPropertyAssignment('signals', generateSignalsObject(factory, analysis.signals)),
436
+ factory.createPropertyAssignment('execute', executeFunction),
437
+ ];
438
+ // Emit types config when provided (for runtime ref wrapping of params)
439
+ if (input.typesNode) {
440
+ processProperties.push(factory.createPropertyAssignment('types', input.typesNode));
441
+ }
442
+ // Emit exports metadata when handler uses `using exports = { ... }`
443
+ if (analysis.exports) {
444
+ processProperties.push(factory.createPropertyAssignment('exports', generateExportsMetadata(factory, analysis.exports)));
445
+ }
446
+ // Emit subprocess definitions
447
+ if (analysis.subprocesses.length > 0) {
448
+ processProperties.push(factory.createPropertyAssignment('subprocesses', factory.createArrayLiteralExpression(analysis.subprocesses.map(sub => generateSubProcessDefinition(factory, sub, rewriterCtx.serviceVars)), true)));
449
+ }
450
+ // Build the __createProcess({ ... }) call
451
+ const callExpr = factory.createCallExpression(factory.createIdentifier('__createProcess'), undefined, [factory.createObjectLiteralExpression(processProperties, true)]);
452
+ // If exports exist, wrap with a type assertion so TExports flows through to the declaration
453
+ if (analysis.exports && input.typeChecker) {
454
+ const exportsTypeNode = buildExportsTypeNode(factory, analysis.exports, input.typeChecker);
455
+ if (exportsTypeNode) {
456
+ // Emit: __createProcess({...}) as __WithExports<typeof __createProcess({...}), TExports>
457
+ // We use a helper type to avoid repeating all generic args.
458
+ // __withExports is: <T, E>(def: T) => T & { data: E }
459
+ // Add a phantom property to carry TExports through to __createProcess.
460
+ // __createProcess infers TExports from compiled.__exportsType.
461
+ processProperties.push(factory.createPropertyAssignment('__exportsType', factory.createAsExpression(factory.createVoidExpression(factory.createNumericLiteral(0)), exportsTypeNode)));
462
+ }
463
+ }
464
+ if (originalNode) {
465
+ return ts.setTextRange(callExpr, originalNode);
466
+ }
467
+ return callExpr;
468
+ }
469
+ /**
470
+ * Generate the stepMap object: { 'entry_abc': 0, ... }
471
+ */
472
+ function generateStepMapObject(factory, stepMap) {
473
+ return factory.createObjectLiteralExpression(Object.entries(stepMap).map(([hash, index]) => factory.createPropertyAssignment(factory.createStringLiteral(hash), factory.createNumericLiteral(index))), true);
474
+ }
475
+ /**
476
+ * Generate the sourceMap object: { 0: [5, 10], ... }
477
+ */
478
+ function generateSourceMapObject(factory, sourceMap) {
479
+ return factory.createObjectLiteralExpression(Object.entries(sourceMap).map(([index, range]) => factory.createPropertyAssignment(factory.createNumericLiteral(parseInt(index)), factory.createArrayLiteralExpression([
480
+ factory.createNumericLiteral(range[0]),
481
+ factory.createNumericLiteral(range[1]),
482
+ ]))), true);
483
+ }
484
+ /**
485
+ * Generate the signals object.
486
+ */
487
+ function generateSignalsObject(factory, signals) {
488
+ return factory.createObjectLiteralExpression(Object.entries(signals).map(([name, info]) => factory.createPropertyAssignment(factory.createStringLiteral(name), factory.createObjectLiteralExpression([
489
+ factory.createPropertyAssignment('identity', factory.createArrayLiteralExpression(info.identity.map(i => factory.createStringLiteral(i)))),
490
+ factory.createPropertyAssignment('payloadType', factory.createStringLiteral(info.payloadType)),
491
+ ]))), true);
492
+ }
493
+ /**
494
+ * Generate a compiled subprocess definition object.
495
+ * Each subprocess gets its own execute function, step map, and signal definitions.
496
+ */
497
+ function generateSubProcessDefinition(factory, sub, parentServiceVars = new Set()) {
498
+ const subAnalysis = sub.analysis;
499
+ // Build subprocess rewriter context.
500
+ // The subprocess handler closes over the parent's injected services, so
501
+ // identifiers like `signals` must be rewritten to `services.signals` just
502
+ // as they are in the parent execute function. Inherit parent serviceVars.
503
+ const subRewriterCtx = {
504
+ paramVars: new Set(sub.handlerParams),
505
+ paramAliases: new Map(),
506
+ serviceVars: parentServiceVars,
507
+ localVars: new Set(Array.from(subAnalysis.variables.entries())
508
+ .filter(([_, info]) => !info.isUsing)
509
+ .map(([name]) => name)),
510
+ usingVars: new Set(Array.from(subAnalysis.variables.entries())
511
+ .filter(([_, info]) => info.isUsing)
512
+ .map(([name]) => name)),
513
+ raceVars: subAnalysis.raceVars,
514
+ };
515
+ // Build steps from subprocess opcodes
516
+ const { steps, stepMap } = buildSteps(subAnalysis);
517
+ // Generate the subprocess execute function
518
+ const executeFunction = generateExecuteFunction(factory, steps, subAnalysis, subRewriterCtx, sub.handlerNode);
519
+ const properties = [
520
+ factory.createPropertyAssignment('name', factory.createStringLiteral(sub.name)),
521
+ factory.createPropertyAssignment('path', factory.createStringLiteral(sub.path)),
522
+ factory.createPropertyAssignment('params', factory.createArrayLiteralExpression(sub.handlerParams.map(p => factory.createStringLiteral(p)))),
523
+ factory.createPropertyAssignment('stepMap', generateStepMapObject(factory, stepMap)),
524
+ factory.createPropertyAssignment('signals', generateSignalsObject(factory, subAnalysis.signals)),
525
+ factory.createPropertyAssignment('execute', executeFunction),
526
+ ];
527
+ if (subAnalysis.exports) {
528
+ properties.push(factory.createPropertyAssignment('exports', generateExportsMetadata(factory, subAnalysis.exports)));
529
+ }
530
+ return factory.createObjectLiteralExpression(properties, true);
531
+ }
532
+ /**
533
+ * Build a TypeNode for the exports type using the type checker.
534
+ * Extracts the type from the `using exports = { ... }` declaration and converts
535
+ * it to a TypeNode that can be emitted as a type argument on __createProcess.
536
+ */
537
+ function buildExportsTypeNode(factory, exports, typeChecker) {
538
+ const declNode = exports.declarationNode;
539
+ if (!ts.isVariableDeclaration(declNode))
540
+ return undefined;
541
+ const type = typeChecker.getTypeAtLocation(declNode);
542
+ const typeNode = typeChecker.typeToTypeNode(type, declNode, ts.NodeBuilderFlags.NoTruncation | ts.NodeBuilderFlags.WriteArrayAsGenericType);
543
+ return typeNode;
544
+ }
545
+ /**
546
+ * Generate exports metadata: { fields: ['count', 'name'], methods: { getCount() { ... } } }
547
+ * Methods are emitted as-is from the source AST so they can be reattached on resume.
548
+ */
549
+ function generateExportsMetadata(factory, exports) {
550
+ const fieldsArray = factory.createArrayLiteralExpression(exports.fields.map(f => factory.createStringLiteral(f.name)));
551
+ const methodProperties = [];
552
+ for (const method of exports.methods) {
553
+ if (ts.isMethodDeclaration(method.node)) {
554
+ // method declaration: getCount() { ... } -> getCount: function() { ... }
555
+ const funcExpr = factory.createFunctionExpression(method.node.modifiers?.filter(m => m.kind === ts.SyntaxKind.AsyncKeyword), undefined, undefined, method.node.typeParameters, method.node.parameters, method.node.type, method.node.body ?? factory.createBlock([]));
556
+ methodProperties.push(factory.createPropertyAssignment(factory.createStringLiteral(method.name), funcExpr));
557
+ }
558
+ else if (ts.isPropertyAssignment(method.node)) {
559
+ // arrow/function expression: getFirst: () => 'x' or compute: function() { ... }
560
+ methodProperties.push(factory.createPropertyAssignment(factory.createStringLiteral(method.name), method.node.initializer));
561
+ }
562
+ }
563
+ return factory.createObjectLiteralExpression([
564
+ factory.createPropertyAssignment('fields', fieldsArray),
565
+ factory.createPropertyAssignment('methods', factory.createObjectLiteralExpression(methodProperties, true)),
566
+ ], true);
567
+ }
568
+ /**
569
+ * Generate the execute function.
570
+ */
571
+ function generateExecuteFunction(factory, steps, analysis, rewriterCtx, handler) {
572
+ // Get the handler's closing brace for source mapping the function exit
573
+ const handlerBody = handler.body;
574
+ const closingBracePos = handlerBody && ts.isBlock(handlerBody) ? handlerBody.end : handler.end;
575
+ const statements = [];
576
+ // Source range for the original handler, used to map generated boilerplate
577
+ const handlerRange = { pos: handler.pos, end: handler.end };
578
+ // const { state, services } = ctx
579
+ const ctxDestructure = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
580
+ factory.createVariableDeclaration(factory.createObjectBindingPattern([
581
+ factory.createBindingElement(undefined, undefined, 'state'),
582
+ factory.createBindingElement(undefined, undefined, 'services'),
583
+ ]), undefined, undefined, factory.createIdentifier('ctx')),
584
+ ], ts.NodeFlags.Const));
585
+ ts.setSourceMapRange(ctxDestructure, handlerRange);
586
+ statements.push(ctxDestructure);
587
+ // Declare local variables for using vars
588
+ const usingVars = Array.from(rewriterCtx.usingVars);
589
+ if (usingVars.length > 0) {
590
+ statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList(usingVars.map(v => factory.createVariableDeclaration(v, undefined, undefined, undefined)), ts.NodeFlags.Let)));
591
+ }
592
+ // let step = state.step | 0
593
+ const stepDecl = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
594
+ factory.createVariableDeclaration('step', undefined, undefined, factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'step'), ts.SyntaxKind.BarToken, factory.createNumericLiteral(0))),
595
+ ], ts.NodeFlags.Let));
596
+ ts.setSourceMapRange(stepDecl, handlerRange);
597
+ statements.push(stepDecl);
598
+ // const __r: [number, unknown] = [0, undefined]
599
+ const rDecl = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
600
+ factory.createVariableDeclaration('__r', undefined, factory.createTupleTypeNode([
601
+ factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
602
+ factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
603
+ ]), factory.createArrayLiteralExpression([
604
+ factory.createNumericLiteral(0),
605
+ factory.createIdentifier('undefined'),
606
+ ])),
607
+ ], ts.NodeFlags.Const));
608
+ ts.setSourceMapRange(rDecl, handlerRange);
609
+ statements.push(rDecl);
610
+ // let __blockResult: unknown
611
+ const blockResultDecl = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
612
+ factory.createVariableDeclaration('__blockResult', undefined, factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword), undefined),
613
+ ], ts.NodeFlags.Let));
614
+ ts.setSourceMapRange(blockResultDecl, handlerRange);
615
+ statements.push(blockResultDecl);
616
+ // let __raceResult: unknown
617
+ const raceResultDecl = factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
618
+ factory.createVariableDeclaration('__raceResult', undefined, factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword), undefined),
619
+ ], ts.NodeFlags.Let));
620
+ ts.setSourceMapRange(raceResultDecl, handlerRange);
621
+ statements.push(raceResultDecl);
622
+ // const __dispose: (Disposable | undefined)[] = [undefined, ...]
623
+ if (usingVars.length > 0) {
624
+ statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
625
+ factory.createVariableDeclaration('__dispose', undefined, undefined, factory.createArrayLiteralExpression(usingVars.map(() => factory.createIdentifier('undefined')))),
626
+ ], ts.NodeFlags.Const)));
627
+ // let __dispose_i = 0
628
+ statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
629
+ factory.createVariableDeclaration('__dispose_i', undefined, undefined, factory.createNumericLiteral(0)),
630
+ ], ts.NodeFlags.Let)));
631
+ }
632
+ // Generate the main_loop: while (true) { switch (step) { ... } }
633
+ const switchCases = steps.map(step => generateSwitchCase(factory, step, steps, analysis, rewriterCtx));
634
+ // Add default case
635
+ switchCases.push(factory.createDefaultClause([
636
+ factory.createThrowStatement(factory.createNewExpression(factory.createIdentifier('Error'), undefined, [
637
+ factory.createTemplateExpression(factory.createTemplateHead('Invalid step: '), [
638
+ factory.createTemplateSpan(factory.createIdentifier('step'), factory.createTemplateTail('')),
639
+ ]),
640
+ ])),
641
+ ]));
642
+ const switchStatement = factory.createSwitchStatement(factory.createIdentifier('step'), factory.createCaseBlock(switchCases));
643
+ const whileStatement = factory.createWhileStatement(factory.createTrue(), factory.createBlock([switchStatement], true));
644
+ // Add label: main_loop
645
+ const labeledWhile = factory.createLabeledStatement('main_loop', whileStatement);
646
+ statements.push(labeledWhile);
647
+ // Cleanup: dispose using vars
648
+ if (usingVars.length > 0) {
649
+ // while (__dispose_i > 0) { __dispose[--__dispose_i]?.[Symbol.dispose]?.() }
650
+ statements.push(factory.createWhileStatement(factory.createBinaryExpression(factory.createIdentifier('__dispose_i'), ts.SyntaxKind.GreaterThanToken, factory.createNumericLiteral(0)), factory.createBlock([
651
+ factory.createExpressionStatement(factory.createCallChain(factory.createElementAccessChain(factory.createElementAccessChain(factory.createIdentifier('__dispose'), factory.createToken(ts.SyntaxKind.QuestionDotToken), factory.createPrefixUnaryExpression(ts.SyntaxKind.MinusMinusToken, factory.createIdentifier('__dispose_i'))), factory.createToken(ts.SyntaxKind.QuestionDotToken), factory.createPropertyAccessExpression(factory.createIdentifier('Symbol'), 'dispose')), factory.createToken(ts.SyntaxKind.QuestionDotToken), undefined, [])),
652
+ ])));
653
+ }
654
+ // Suspend handling: persist locals to state
655
+ // if (__r[0] === 1) { state.step = step; ... }
656
+ // Map to closing brace - this is function exit logic
657
+ const stateStepAssign = factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'step'), ts.SyntaxKind.EqualsToken, factory.createIdentifier('step')));
658
+ const suspendIfStmt = factory.createIfStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.ExclamationEqualsEqualsToken, factory.createNumericLiteral(0) // not DONE - save step on SUSPEND or SUBPROCESS
659
+ ), factory.createBlock([
660
+ stateStepAssign,
661
+ // Locals are already persisted: block rewriting transforms `const x = ...`
662
+ // to `state.vars.x = ...`, so all locals survive suspension points.
663
+ ]));
664
+ const closingRange = { pos: closingBracePos - 1, end: closingBracePos };
665
+ ts.setSourceMapRange(stateStepAssign, closingRange);
666
+ ts.setTextRange(suspendIfStmt, closingRange);
667
+ ts.setSourceMapRange(suspendIfStmt, closingRange);
668
+ statements.push(suspendIfStmt);
669
+ // return __r - map to closing brace for clean function exit in debugger
670
+ const returnStmt = factory.createReturnStatement(factory.createIdentifier('__r'));
671
+ ts.setTextRange(returnStmt, closingRange);
672
+ ts.setSourceMapRange(returnStmt, closingRange);
673
+ statements.push(returnStmt);
674
+ // Create the async arrow function
675
+ const arrowFn = factory.createArrowFunction([factory.createModifier(ts.SyntaxKind.AsyncKeyword)], undefined, [factory.createParameterDeclaration(undefined, undefined, 'ctx')], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBlock(statements, true));
676
+ // Map generated nodes back to original handler for source maps
677
+ ts.setSourceMapRange(arrowFn, handlerRange);
678
+ if (handlerBody && ts.isBlock(handlerBody)) {
679
+ const bodyRange = { pos: handlerBody.pos, end: handlerBody.end };
680
+ ts.setSourceMapRange(switchStatement, bodyRange);
681
+ ts.setSourceMapRange(whileStatement, bodyRange);
682
+ ts.setSourceMapRange(labeledWhile, bodyRange);
683
+ }
684
+ return arrowFn;
685
+ }
686
+ /**
687
+ * Emit a single rehydrate-prelude entry: varName = expr; __dispose[__dispose_i++] = varName.
688
+ * Uses the expression from the REHYDRATE opcode so that sibling branches with the same
689
+ * variable name each use their own initializer.
690
+ */
691
+ function emitRehydrateEntry(factory, varName, expression, rewriterCtx, out) {
692
+ const rewrittenExpr = rewriteExpression(factory, expression, rewriterCtx);
693
+ out.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier(varName), ts.SyntaxKind.EqualsToken, rewrittenExpr)));
694
+ out.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__dispose'), factory.createPostfixUnaryExpression(factory.createIdentifier('__dispose_i'), ts.SyntaxKind.PlusPlusToken)), ts.SyntaxKind.EqualsToken, factory.createIdentifier(varName))));
695
+ }
696
+ /**
697
+ * Topologically sort rehydrate deps so that a var whose initializer references
698
+ * another using-var is emitted after that dep.
699
+ *
700
+ * The expression carried on each REHYDRATE opcode is the authoritative source;
701
+ * deps extracted from it tell us what other using-vars must be available first.
702
+ */
703
+ function topoSortRehydrateDeps(varNames, rehydrateOpcodeExprs, usingVars) {
704
+ // Build adjacency: varName -> set of using-vars it depends on
705
+ const deps = new Map();
706
+ for (const v of varNames) {
707
+ deps.set(v, new Set());
708
+ const expr = rehydrateOpcodeExprs.get(v);
709
+ if (expr) {
710
+ const visit = (node) => {
711
+ if (ts.isIdentifier(node) && usingVars.has(node.text) && node.text !== v) {
712
+ deps.get(v).add(node.text);
713
+ }
714
+ ts.forEachChild(node, visit);
715
+ };
716
+ visit(expr);
717
+ }
718
+ }
719
+ // Kahn's algorithm
720
+ const inDegree = new Map();
721
+ for (const v of varNames)
722
+ inDegree.set(v, 0);
723
+ for (const [, ds] of deps) {
724
+ for (const d of ds) {
725
+ if (inDegree.has(d)) {
726
+ // d is a dep of something; increment in-degree of the dependent
727
+ }
728
+ }
729
+ }
730
+ // Actually: inDegree[v] = number of vars in varNames that v depends on
731
+ for (const v of varNames) {
732
+ let count = 0;
733
+ for (const d of deps.get(v)) {
734
+ if (inDegree.has(d))
735
+ count++;
736
+ }
737
+ inDegree.set(v, count);
738
+ }
739
+ const queue = varNames.filter(v => inDegree.get(v) === 0);
740
+ const result = [];
741
+ while (queue.length > 0) {
742
+ const v = queue.shift();
743
+ result.push(v);
744
+ // Vars that depend on v can now have their in-degree reduced
745
+ for (const other of varNames) {
746
+ if (deps.get(other)?.has(v)) {
747
+ const newDeg = inDegree.get(other) - 1;
748
+ inDegree.set(other, newDeg);
749
+ if (newDeg === 0)
750
+ queue.push(other);
751
+ }
752
+ }
753
+ }
754
+ // If cycle (shouldn't happen in valid code), append remaining
755
+ for (const v of varNames) {
756
+ if (!result.includes(v))
757
+ result.push(v);
758
+ }
759
+ return result;
760
+ }
761
+ /**
762
+ * Generate a switch case for a step.
763
+ */
764
+ function generateSwitchCase(factory, step, steps, analysis, rewriterCtx) {
765
+ const { opcodes, rehydrationBlocks } = analysis;
766
+ const caseStatements = [];
767
+ // Add comment with step hash
768
+ // This is done via synthetic comments on the first statement
769
+ // For branch steps, the first opcode is always STORE __raceResult fromRace.
770
+ // That STORE must run before the rehydrate prelude so that initializers that
771
+ // reference the race result (e.g. `using lk = await svc.lock(r.id)`) can
772
+ // resolve `r` correctly.
773
+ //
774
+ // Collect the REHYDRATE opcodes in this step's range so we can:
775
+ // - use each opcode's own expression (not the global rehydrationBlocks map)
776
+ // for the correct per-branch initializer
777
+ // - skip those REHYDRATE opcodes when iterating later (no double-acquire)
778
+ const rehydrateOpcodeExprs = new Map();
779
+ const rehydrateOpcodeIndices = new Set();
780
+ for (let i = step.opcodeRange.start; i < step.opcodeRange.end; i++) {
781
+ const op = opcodes[i];
782
+ if (op.op === 'REHYDRATE') {
783
+ rehydrateOpcodeExprs.set(op.var, op.expression);
784
+ rehydrateOpcodeIndices.add(i);
785
+ }
786
+ }
787
+ // For branch steps: emit any leading STORE opcodes FIRST, before the rehydrate prelude.
788
+ // Leading STORE opcodes are at the very beginning of the branch range (the fromRace STORE).
789
+ const leadingStoreIndices = new Set();
790
+ if (step.type === 'branch') {
791
+ for (let i = step.opcodeRange.start; i < step.opcodeRange.end; i++) {
792
+ const op = opcodes[i];
793
+ if (op.op === 'STORE' && op.fromRace) {
794
+ const stmts = generateOpcodeStatements(factory, op, step, steps, analysis, rewriterCtx, i);
795
+ const sourceNode = analysis.opcodeSourceNodes[i];
796
+ const stepSourceRng = getStepSourceRange(step, analysis);
797
+ const opcodeRange = sourceNode ? { pos: sourceNode.pos, end: sourceNode.end } : stepSourceRng;
798
+ if (opcodeRange)
799
+ stmts.forEach(s => ts.setSourceMapRange(s, opcodeRange));
800
+ caseStatements.push(...stmts);
801
+ leadingStoreIndices.add(i);
802
+ }
803
+ else {
804
+ // Stop at first non-STORE opcode
805
+ break;
806
+ }
807
+ }
808
+ }
809
+ // Handle rehydration prelude for resume steps (and branch steps after STORE).
810
+ // Resolve the expression from the REHYDRATE opcode within this step's range when
811
+ // available (per-branch expression from REHYDRATE opcode), otherwise fall back to rehydrationBlocks.
812
+ if (step.rehydrateDeps && step.rehydrateDeps.length > 0) {
813
+ // Build a map of expressions to use for each dep
814
+ const exprMap = new Map();
815
+ for (const varName of step.rehydrateDeps) {
816
+ // Prefer the expression carried on the REHYDRATE opcode in this step
817
+ const opcodeExpr = rehydrateOpcodeExprs.get(varName);
818
+ if (opcodeExpr) {
819
+ exprMap.set(varName, opcodeExpr);
820
+ }
821
+ else {
822
+ // Var was declared in a prior step; look up in global map
823
+ const rehydBlock = rehydrationBlocks[varName];
824
+ if (rehydBlock)
825
+ exprMap.set(varName, rehydBlock.expression);
826
+ }
827
+ }
828
+ // Topologically sort so deps are emitted before dependents
829
+ const depsToEmit = step.rehydrateDeps.filter(v => exprMap.has(v));
830
+ const sorted = topoSortRehydrateDeps(depsToEmit, exprMap, rewriterCtx.usingVars);
831
+ for (const varName of sorted) {
832
+ const expr = exprMap.get(varName);
833
+ emitRehydrateEntry(factory, varName, expr, rewriterCtx, caseStatements);
834
+ }
835
+ }
836
+ // Compute step-level source range for fallback when individual opcodes lack source nodes
837
+ const stepSourceRange = getStepSourceRange(step, analysis);
838
+ // For race-branch steps, thread the continuation step through as the
839
+ // target for unlabeled `break` and `continue` statements that the user
840
+ // wrote inside their `switch(true) { case signal(...): ... }` branch bodies.
841
+ // Both statements inside an if-body of a race branch need to advance `step`
842
+ // to the continuation before restarting main_loop. See transformStatement.
843
+ const stepCtx = step.type === 'branch' && step.nextStep !== undefined
844
+ ? { ...rewriterCtx, breakTarget: step.nextStep, continueTarget: step.nextStep }
845
+ : rewriterCtx;
846
+ // Process opcodes in this step's range, skipping:
847
+ // - leading STORE opcodes already emitted above (branch steps, branch-result-first ordering)
848
+ // - REHYDRATE opcodes for vars in the rehydrate prelude (no double-acquire)
849
+ for (let i = step.opcodeRange.start; i < step.opcodeRange.end; i++) {
850
+ if (leadingStoreIndices.has(i))
851
+ continue;
852
+ // Skip REHYDRATE opcodes for vars already emitted by the rehydrate prelude (no double-acquire)
853
+ if (rehydrateOpcodeIndices.has(i)) {
854
+ const op = opcodes[i];
855
+ if (op.op === 'REHYDRATE' && step.rehydrateDeps?.includes(op.var))
856
+ continue;
857
+ }
858
+ const opcode = opcodes[i];
859
+ const stmts = generateOpcodeStatements(factory, opcode, step, steps, analysis, stepCtx, i);
860
+ // Map generated statements to the opcode's original source node for source maps
861
+ const sourceNode = analysis.opcodeSourceNodes[i];
862
+ const opcodeRange = sourceNode
863
+ ? { pos: sourceNode.pos, end: sourceNode.end }
864
+ : stepSourceRange; // fallback to step-level range
865
+ if (opcodeRange) {
866
+ for (const stmt of stmts) {
867
+ ts.setSourceMapRange(stmt, opcodeRange);
868
+ }
869
+ }
870
+ caseStatements.push(...stmts);
871
+ }
872
+ // Add explicit fallthrough to nextStep if the step doesn't end with a control flow statement.
873
+ // This handles race branches that don't emit JUMP (their break is a no-op).
874
+ if (step.nextStep !== undefined) {
875
+ // Check if last statement is a control flow (break, continue, return equiv)
876
+ const lastStmt = caseStatements[caseStatements.length - 1];
877
+ const hasControlFlow = lastStmt && (ts.isBreakStatement(lastStmt) ||
878
+ ts.isContinueStatement(lastStmt));
879
+ if (!hasControlFlow) {
880
+ // step = nextStep; continue main_loop
881
+ caseStatements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(step.nextStep))));
882
+ caseStatements.push(factory.createContinueStatement(factory.createIdentifier('main_loop')));
883
+ }
884
+ }
885
+ // Create the case clause
886
+ const caseClause = factory.createCaseClause(factory.createNumericLiteral(step.index), [factory.createBlock(caseStatements, true)]);
887
+ // Map case clause to original source range for source maps
888
+ const sourceRange = getStepSourceRange(step, analysis);
889
+ if (sourceRange) {
890
+ ts.setSourceMapRange(caseClause, sourceRange);
891
+ }
892
+ return caseClause;
893
+ }
894
+ /**
895
+ * Get the original source byte range (pos/end) for a step,
896
+ * derived from the opcode source nodes in the analysis.
897
+ */
898
+ function getStepSourceRange(step, analysis) {
899
+ const { opcodeSourceNodes, raceBranchSourceNodes } = analysis;
900
+ // For branch steps, prefer the race branch source node
901
+ if (step.type === 'branch' && step.branchInfo) {
902
+ const branchNodes = raceBranchSourceNodes.get(step.branchInfo.raceOpcodeIndex);
903
+ const branchNode = branchNodes?.get(step.branchInfo.branchId);
904
+ if (branchNode) {
905
+ return { pos: branchNode.pos, end: branchNode.end };
906
+ }
907
+ }
908
+ // For other steps, compute range from opcode source nodes
909
+ let pos;
910
+ let end;
911
+ for (let j = step.opcodeRange.start; j < step.opcodeRange.end; j++) {
912
+ const sourceNode = opcodeSourceNodes[j];
913
+ if (sourceNode) {
914
+ if (pos === undefined || sourceNode.pos < pos)
915
+ pos = sourceNode.pos;
916
+ if (end === undefined || sourceNode.end > end)
917
+ end = sourceNode.end;
918
+ }
919
+ }
920
+ if (pos !== undefined && end !== undefined) {
921
+ return { pos, end };
922
+ }
923
+ return undefined;
924
+ }
925
+ /**
926
+ * Transform a statement for codegen.
927
+ * Handles if statements specially - transforming their returns to __r[1] = expr; break main_loop;
928
+ * Returns transformed statements (may be multiple for a single input).
929
+ */
930
+ function transformStatement(factory, stmt, rewriterCtx) {
931
+ // Unlabeled `break` inside an if-body of a race branch: rewrite to jump
932
+ // to the race's continuation step. Without this the compiled case ends
933
+ // with a naked `break;` that exits `switch(step)` but leaves `step`
934
+ // unchanged, re-entering the same branch body on the next iteration of
935
+ // `main_loop` - infinite loop.
936
+ //
937
+ // We only rewrite here, not inside user-written for/while/switch bodies
938
+ // (those fall through to the `rewriteStatement` path which preserves
939
+ // their `break` verbatim so it continues to target the user's own
940
+ // construct).
941
+ if (ts.isBreakStatement(stmt) &&
942
+ !stmt.label &&
943
+ rewriterCtx.breakTarget !== undefined) {
944
+ return [
945
+ ts.setTextRange(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(rewriterCtx.breakTarget))), stmt),
946
+ factory.createContinueStatement(factory.createIdentifier('main_loop')),
947
+ ];
948
+ }
949
+ // Unlabeled `continue` inside an if-body of a race branch: same class of
950
+ // bug as the `break` case above. A naked `continue` restarts `main_loop`
951
+ // without advancing `step`, causing the same branch body to run again -
952
+ // infinite loop. Rewrite to `step = continueTarget; continue main_loop;`.
953
+ if (ts.isContinueStatement(stmt) &&
954
+ !stmt.label &&
955
+ rewriterCtx.continueTarget !== undefined) {
956
+ return [
957
+ ts.setTextRange(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(rewriterCtx.continueTarget))), stmt),
958
+ factory.createContinueStatement(factory.createIdentifier('main_loop')),
959
+ ];
960
+ }
961
+ // Return statement -> __r[1] = expr; break main_loop;
962
+ if (ts.isReturnStatement(stmt)) {
963
+ const returnExpr = stmt.expression
964
+ ? rewriteExpression(factory, stmt.expression, rewriterCtx)
965
+ : factory.createIdentifier('undefined');
966
+ return [
967
+ // __r[0] = 0 (DONE)
968
+ factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(0))),
969
+ // __r[1] = returnExpr
970
+ ts.setTextRange(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, returnExpr)), stmt),
971
+ // break main_loop
972
+ factory.createBreakStatement(factory.createIdentifier('main_loop')),
973
+ ];
974
+ }
975
+ // If statement -> transform with rewritten condition and transformed body
976
+ if (ts.isIfStatement(stmt)) {
977
+ const condition = rewriteExpression(factory, stmt.expression, rewriterCtx);
978
+ // Transform then branch
979
+ const thenStatements = [];
980
+ if (ts.isBlock(stmt.thenStatement)) {
981
+ for (const s of stmt.thenStatement.statements) {
982
+ thenStatements.push(...transformStatement(factory, s, rewriterCtx));
983
+ }
984
+ }
985
+ else {
986
+ thenStatements.push(...transformStatement(factory, stmt.thenStatement, rewriterCtx));
987
+ }
988
+ // Transform else branch if exists
989
+ let elseStatement;
990
+ if (stmt.elseStatement) {
991
+ const elseStatements = [];
992
+ if (ts.isBlock(stmt.elseStatement)) {
993
+ for (const s of stmt.elseStatement.statements) {
994
+ elseStatements.push(...transformStatement(factory, s, rewriterCtx));
995
+ }
996
+ }
997
+ else if (ts.isIfStatement(stmt.elseStatement)) {
998
+ // else if - recursively transform
999
+ elseStatements.push(...transformStatement(factory, stmt.elseStatement, rewriterCtx));
1000
+ }
1001
+ else {
1002
+ elseStatements.push(...transformStatement(factory, stmt.elseStatement, rewriterCtx));
1003
+ }
1004
+ elseStatement = factory.createBlock(elseStatements, true);
1005
+ }
1006
+ return [
1007
+ ts.setTextRange(factory.createIfStatement(condition, factory.createBlock(thenStatements, true), elseStatement), stmt),
1008
+ ];
1009
+ }
1010
+ // Other statements - just rewrite
1011
+ return [rewriteStatement(factory, stmt, rewriterCtx)];
1012
+ }
1013
+ /**
1014
+ * Find the step index that contains a given opcode index.
1015
+ */
1016
+ function findStepIndexForOpcode(steps, opcodeIndex) {
1017
+ for (const step of steps) {
1018
+ if (opcodeIndex >= step.opcodeRange.start && opcodeIndex < step.opcodeRange.end) {
1019
+ return step.index;
1020
+ }
1021
+ }
1022
+ // If the opcode index is a boundary point, find the step that starts at that index
1023
+ const step = steps.find(s => s.opcodeRange.start === opcodeIndex);
1024
+ return step?.index ?? 0;
1025
+ }
1026
+ /**
1027
+ * Wrap an expression with `expr[Symbol.asyncIterator]()` to obtain an iterator from an iterable.
1028
+ */
1029
+ function wrapWithAsyncIterator(factory, expr) {
1030
+ return factory.createCallExpression(factory.createElementAccessExpression(expr, factory.createPropertyAccessExpression(factory.createIdentifier('Symbol'), 'asyncIterator')), undefined, []);
1031
+ }
1032
+ /**
1033
+ * Generate statements for a single opcode within a step.
1034
+ */
1035
+ function generateOpcodeStatements(factory, opcode, step, steps, analysis, rewriterCtx, opcodeIndex) {
1036
+ const statements = [];
1037
+ const { blocks, rehydrationBlocks } = analysis;
1038
+ switch (opcode.op) {
1039
+ case 'BLOCK': {
1040
+ const block = blocks[opcode.blockId];
1041
+ if (block && block.statements.length > 0) {
1042
+ // Extract variables declared in this block, excluding localVars which persist
1043
+ // blockLocalVars are for variables that DON'T need state.vars rewriting
1044
+ const allDeclaredVars = extractDeclaredVars(block.statements);
1045
+ const blockLocalVars = new Set(Array.from(allDeclaredVars).filter(v => !rewriterCtx.localVars.has(v)));
1046
+ const blockCtx = { ...rewriterCtx, blockLocalVars };
1047
+ // Rewrite and add each statement
1048
+ for (const stmt of block.statements) {
1049
+ // If statements - transform with special return handling
1050
+ if (ts.isIfStatement(stmt)) {
1051
+ statements.push(...transformStatement(factory, stmt, blockCtx));
1052
+ }
1053
+ // Return statements at block level - use __blockResult for later RETURN opcode
1054
+ else if (ts.isReturnStatement(stmt)) {
1055
+ const returnExpr = stmt.expression
1056
+ ? rewriteExpression(factory, stmt.expression, blockCtx)
1057
+ : factory.createIdentifier('undefined');
1058
+ statements.push(ts.setTextRange(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('__blockResult'), ts.SyntaxKind.EqualsToken, returnExpr)), stmt));
1059
+ }
1060
+ // Variable statements - transform localVar declarations to state.vars assignments
1061
+ else if (ts.isVariableStatement(stmt)) {
1062
+ for (const decl of stmt.declarationList.declarations) {
1063
+ if (ts.isIdentifier(decl.name)) {
1064
+ const varName = decl.name.text;
1065
+ // LocalVar (but not raceVar) -> state.vars.xxx = initializer
1066
+ // These need to persist across suspension points
1067
+ // Race vars stay local (the result is in __raceResult after resumption)
1068
+ if (rewriterCtx.localVars.has(varName) && !rewriterCtx.raceVars.has(varName)) {
1069
+ const initializer = decl.initializer
1070
+ ? rewriteExpression(factory, decl.initializer, blockCtx)
1071
+ : factory.createIdentifier('undefined');
1072
+ statements.push(ts.setTextRange(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), varName), ts.SyntaxKind.EqualsToken, initializer)), stmt));
1073
+ continue;
1074
+ }
1075
+ }
1076
+ // Other declarations - keep as variable statement with rewritten initializer
1077
+ // This handles usingVars, destructuring, etc.
1078
+ const newInitializer = decl.initializer
1079
+ ? rewriteExpression(factory, decl.initializer, blockCtx)
1080
+ : undefined;
1081
+ statements.push(factory.createVariableStatement(stmt.modifiers, factory.createVariableDeclarationList([factory.createVariableDeclaration(decl.name, decl.exclamationToken, decl.type, newInitializer)], stmt.declarationList.flags)));
1082
+ }
1083
+ }
1084
+ // Other statements - just rewrite
1085
+ else {
1086
+ statements.push(rewriteStatement(factory, stmt, blockCtx));
1087
+ }
1088
+ }
1089
+ }
1090
+ break;
1091
+ }
1092
+ case 'STORE': {
1093
+ // For race results: __raceResult = state.vars.__raceResult (load from state)
1094
+ // For block/signal results: state.vars.varName = __blockResult / ctx.signalPayload
1095
+ if (opcode.fromRace) {
1096
+ // Load race result from state.vars (set by executor) into local variable
1097
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('__raceResult'), ts.SyntaxKind.EqualsToken, factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), '__raceResult'))));
1098
+ break;
1099
+ }
1100
+ // state.vars.varName = __blockResult / ctx.signalPayload
1101
+ let valueExpr;
1102
+ if (opcode.fromBlock) {
1103
+ valueExpr = factory.createIdentifier('__blockResult');
1104
+ }
1105
+ else if (opcode.fromSignal) {
1106
+ valueExpr = factory.createPropertyAccessExpression(factory.createIdentifier('ctx'), 'signalPayload');
1107
+ }
1108
+ else {
1109
+ valueExpr = factory.createIdentifier('undefined');
1110
+ }
1111
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.var), ts.SyntaxKind.EqualsToken, valueExpr)));
1112
+ break;
1113
+ }
1114
+ case 'REHYDRATE': {
1115
+ // Use the expression carried on the opcode (each branch site has its own
1116
+ // expression, so two sibling branches declaring `using v = X()` and `using v = Y()`
1117
+ // will each emit their own correct initializer rather than sharing the last-write
1118
+ // from the global rehydrationBlocks map).
1119
+ const expr = opcode.expression ?? rehydrationBlocks[opcode.var]?.expression;
1120
+ if (expr) {
1121
+ const rewrittenExpr = rewriteExpression(factory, expr, rewriterCtx);
1122
+ // varName = await ... (expression may already be an await, don't double-wrap)
1123
+ const awaitedExpr = ts.isAwaitExpression(rewrittenExpr)
1124
+ ? rewrittenExpr
1125
+ : factory.createAwaitExpression(rewrittenExpr);
1126
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier(opcode.var), ts.SyntaxKind.EqualsToken, awaitedExpr)));
1127
+ // __dispose[__dispose_i++] = varName
1128
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__dispose'), factory.createPostfixUnaryExpression(factory.createIdentifier('__dispose_i'), ts.SyntaxKind.PlusPlusToken)), ts.SyntaxKind.EqualsToken, factory.createIdentifier(opcode.var))));
1129
+ }
1130
+ break;
1131
+ }
1132
+ case 'WAIT': {
1133
+ // Find the resume step for this wait
1134
+ const resumeStepIndex = step.nextStep ?? step.index + 1;
1135
+ // step = resumeStep
1136
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(resumeStepIndex))));
1137
+ // __r[0] = 1 (SUSPEND)
1138
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(1))));
1139
+ // __r[1] = { signal: '...' } or { timer: { ... } }
1140
+ let suspendConfig;
1141
+ if (opcode.signal === '__timer__') {
1142
+ // Timer suspend - use unit-based format: { timer: { seconds: 30 } }
1143
+ const timerProperties = opcode.timer
1144
+ ? [
1145
+ factory.createPropertyAssignment(opcode.timer.unit, rewriteExpression(factory, cloneExpression(factory, opcode.timer.valueExpr), rewriterCtx)),
1146
+ ]
1147
+ : [];
1148
+ suspendConfig = factory.createObjectLiteralExpression([
1149
+ factory.createPropertyAssignment('timer', factory.createObjectLiteralExpression(timerProperties)),
1150
+ ]);
1151
+ }
1152
+ else if (opcode.signalExpr) {
1153
+ // Use the signal expression and access .signalName at runtime
1154
+ suspendConfig = factory.createObjectLiteralExpression([
1155
+ factory.createPropertyAssignment('signal', factory.createPropertyAccessExpression(rewriteExpression(factory, opcode.signalExpr, rewriterCtx), 'signalName')),
1156
+ ]);
1157
+ }
1158
+ else {
1159
+ // Fallback to static signal name (legacy)
1160
+ suspendConfig = factory.createObjectLiteralExpression([
1161
+ factory.createPropertyAssignment('signal', factory.createStringLiteral(opcode.signal)),
1162
+ ]);
1163
+ }
1164
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, suspendConfig)));
1165
+ // break main_loop
1166
+ statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
1167
+ break;
1168
+ }
1169
+ case 'RACE_START': {
1170
+ // Generate race suspend config
1171
+ // Convert branch.jumpTarget (opcode index) to step index for each branch
1172
+ const raceBranches = opcode.branches.map(branch => {
1173
+ const branchStepIndex = findStepIndexForOpcode(steps, branch.jumpTarget);
1174
+ // For stream branches (stream:ModelName:*:fieldName), use the static signal name
1175
+ // For signal branches, access .signalName on the signal service call
1176
+ const isStreamBranch = branch.signal?.startsWith('stream:');
1177
+ const signalAssignment = branch.signalExpr
1178
+ ? isStreamBranch
1179
+ ? [
1180
+ // Stream: use the static signal name from analysis (with wildcard)
1181
+ factory.createPropertyAssignment('signal', factory.createStringLiteral(branch.signal)),
1182
+ ]
1183
+ : [
1184
+ // Signal: access .signalName on the rewritten signal expression
1185
+ factory.createPropertyAssignment('signal', factory.createPropertyAccessExpression(rewriteExpression(factory, branch.signalExpr, rewriterCtx), 'signalName')),
1186
+ ]
1187
+ : [];
1188
+ return factory.createObjectLiteralExpression([
1189
+ factory.createPropertyAssignment('id', factory.createStringLiteral(branch.id)),
1190
+ ...signalAssignment,
1191
+ ...(branch.timer
1192
+ ? [
1193
+ factory.createPropertyAssignment('timer', factory.createObjectLiteralExpression([
1194
+ // Use unit-based format for persistence: { seconds: X } or { minutes: X }
1195
+ // This is human-readable in the database and can be recalculated on resume
1196
+ factory.createPropertyAssignment(branch.timer.unit, // 'seconds', 'minutes', 'hours', 'days'
1197
+ // Clone and rewrite the value expression (may contain state.vars references)
1198
+ rewriteExpression(factory, cloneExpression(factory, branch.timer.valueExpr), rewriterCtx)),
1199
+ ])),
1200
+ ]
1201
+ : []),
1202
+ factory.createPropertyAssignment('resumeStep', factory.createNumericLiteral(branchStepIndex)),
1203
+ ]);
1204
+ });
1205
+ // Store for use by RACE_SUSPEND
1206
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), '__raceBranches'), ts.SyntaxKind.EqualsToken, factory.createArrayLiteralExpression(raceBranches, true))));
1207
+ break;
1208
+ }
1209
+ case 'RACE_SUSPEND': {
1210
+ // __r[0] = 1 (SUSPEND)
1211
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(1))));
1212
+ // __r[1] = { race: state.vars.__raceBranches }
1213
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression([
1214
+ factory.createPropertyAssignment('race', factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), '__raceBranches')),
1215
+ ]))));
1216
+ // break main_loop
1217
+ statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
1218
+ break;
1219
+ }
1220
+ case 'JUMP': {
1221
+ // Find the step that contains the target opcode and convert to step index
1222
+ const targetStepIndex = findStepIndexForOpcode(steps, opcode.target);
1223
+ // step = targetStepIndex
1224
+ // continue main_loop
1225
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(targetStepIndex))));
1226
+ statements.push(factory.createContinueStatement(factory.createIdentifier('main_loop')));
1227
+ break;
1228
+ }
1229
+ case 'JUMP_IF': {
1230
+ // if (condition) { step = target; continue main_loop }
1231
+ // For now, we assume __condition is in state.vars
1232
+ const condition = opcode.condition === '__condition_false'
1233
+ ? factory.createPrefixUnaryExpression(ts.SyntaxKind.ExclamationToken, factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), '__condition'))
1234
+ : factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.condition);
1235
+ // Convert opcode target index to step index
1236
+ const targetStepIndex = findStepIndexForOpcode(steps, opcode.target);
1237
+ statements.push(factory.createIfStatement(condition, factory.createBlock([
1238
+ factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(targetStepIndex))),
1239
+ factory.createContinueStatement(factory.createIdentifier('main_loop')),
1240
+ ])));
1241
+ break;
1242
+ }
1243
+ case 'LABEL': {
1244
+ // Labels don't generate code - they're just markers for jump targets
1245
+ break;
1246
+ }
1247
+ case 'LABEL_ENTER': {
1248
+ // Initialize label tracking arrays if needed
1249
+ // state.labelStack = state.labelStack ?? []
1250
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), ts.SyntaxKind.EqualsToken, factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), ts.SyntaxKind.QuestionQuestionToken, factory.createArrayLiteralExpression([])))));
1251
+ // state.labelHistory = state.labelHistory ?? []
1252
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), ts.SyntaxKind.EqualsToken, factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), ts.SyntaxKind.QuestionQuestionToken, factory.createArrayLiteralExpression([])))));
1253
+ // state.labelStack.push('labelName')
1254
+ statements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), 'push'), undefined, [factory.createStringLiteral(opcode.label)])));
1255
+ // state.label = 'labelName'
1256
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'label'), ts.SyntaxKind.EqualsToken, factory.createStringLiteral(opcode.label))));
1257
+ // state.labelHistory.push({ label: 'labelName', enteredAt: state.step })
1258
+ statements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), 'push'), undefined, [
1259
+ factory.createObjectLiteralExpression([
1260
+ factory.createPropertyAssignment('label', factory.createStringLiteral(opcode.label)),
1261
+ factory.createPropertyAssignment('enteredAt', factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'step')),
1262
+ ]),
1263
+ ])));
1264
+ // Keep history bounded: if (state.labelHistory.length > 50) state.labelHistory.shift()
1265
+ statements.push(factory.createIfStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), 'length'), ts.SyntaxKind.GreaterThanToken, factory.createNumericLiteral(50)), factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), 'shift'), undefined, []))));
1266
+ break;
1267
+ }
1268
+ case 'LABEL_EXIT': {
1269
+ // state.labelStack?.pop()
1270
+ statements.push(factory.createExpressionStatement(factory.createCallChain(factory.createPropertyAccessChain(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), factory.createToken(ts.SyntaxKind.QuestionDotToken), 'pop'), undefined, undefined, [])));
1271
+ // state.label = state.labelStack?.[state.labelStack.length - 1]
1272
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'label'), ts.SyntaxKind.EqualsToken, factory.createElementAccessChain(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), factory.createToken(ts.SyntaxKind.QuestionDotToken), factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelStack'), 'length'), ts.SyntaxKind.MinusToken, factory.createNumericLiteral(1))))));
1273
+ // Find the last entry for this label in history and set exitedAt
1274
+ // const __lastEntry = state.labelHistory?.findLast(e => e.label === 'labelName' && e.exitedAt === undefined)
1275
+ // if (__lastEntry) __lastEntry.exitedAt = state.step
1276
+ const lastEntryVarName = `__lastEntry_${opcode.label.replace(/\W/g, '_')}`;
1277
+ statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
1278
+ factory.createVariableDeclaration(lastEntryVarName, undefined, undefined, factory.createCallChain(factory.createPropertyAccessChain(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'labelHistory'), factory.createToken(ts.SyntaxKind.QuestionDotToken), 'findLast'), undefined, undefined, [
1279
+ factory.createArrowFunction(undefined, undefined, [factory.createParameterDeclaration(undefined, undefined, 'e')], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createBinaryExpression(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('e'), 'label'), ts.SyntaxKind.EqualsEqualsEqualsToken, factory.createStringLiteral(opcode.label)), ts.SyntaxKind.AmpersandAmpersandToken, factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('e'), 'exitedAt'), ts.SyntaxKind.EqualsEqualsEqualsToken, factory.createIdentifier('undefined')))),
1280
+ ])),
1281
+ ], ts.NodeFlags.Const)));
1282
+ statements.push(factory.createIfStatement(factory.createIdentifier(lastEntryVarName), factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier(lastEntryVarName), 'exitedAt'), ts.SyntaxKind.EqualsToken, factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'step')))));
1283
+ break;
1284
+ }
1285
+ case 'RETURN': {
1286
+ // __r[0] = 0 (DONE)
1287
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(0))));
1288
+ // __r[1] = result (from __blockResult or literal)
1289
+ // The return value comes from the preceding BLOCK opcode
1290
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, factory.createIdentifier('__blockResult'))));
1291
+ // break main_loop
1292
+ statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
1293
+ break;
1294
+ }
1295
+ case 'YIELD_EMIT': {
1296
+ // Emit a value without suspending: ctx.emit(rewrittenValueExpr)
1297
+ const emitValue = rewriteExpression(factory, opcode.valueExpr, rewriterCtx);
1298
+ statements.push(factory.createExpressionStatement(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('ctx'), 'emit'), undefined, [emitValue])));
1299
+ break;
1300
+ }
1301
+ case 'SCOPE_ENTER':
1302
+ case 'SCOPE_EXIT': {
1303
+ // Scope opcodes are handled implicitly by the using var cleanup
1304
+ break;
1305
+ }
1306
+ case 'SCOPE_START': {
1307
+ // Evaluate entities and store them in state vars for the executor
1308
+ // Generated: state.vars.__scope_N_entities = Array.from(rewrittenIterableExpr)
1309
+ const entitiesExpr = rewriteExpression(factory, opcode.iterableExpr, rewriterCtx);
1310
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), factory.createStringLiteral(`__scope_${opcode.scopeId}_entities`)), ts.SyntaxKind.EqualsToken, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Array'), 'from'), undefined, [entitiesExpr]))));
1311
+ // Store idExtractor if provided
1312
+ // Generated: state.vars.__scope_N_idFn = rewrittenIdExtractor
1313
+ if (opcode.idExtractor) {
1314
+ const idExpr = rewriteExpression(factory, opcode.idExtractor, rewriterCtx);
1315
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), factory.createStringLiteral(`__scope_${opcode.scopeId}_idFn`)), ts.SyntaxKind.EqualsToken, idExpr)));
1316
+ }
1317
+ // Store param alias if provided
1318
+ if (opcode.paramAlias) {
1319
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), factory.createStringLiteral(`__scope_${opcode.scopeId}_alias`)), ts.SyntaxKind.EqualsToken, factory.createStringLiteral(opcode.paramAlias))));
1320
+ }
1321
+ break;
1322
+ }
1323
+ case 'SCOPE_WAIT': {
1324
+ // Signal-first form: suspend with scope config
1325
+ const signalExpr = rewriteExpression(factory, opcode.signalExpr, rewriterCtx);
1326
+ const resumeStepIndex = step.nextStep ?? step.index + 1;
1327
+ // step = resumeStep
1328
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(resumeStepIndex))));
1329
+ // __r[0] = 1 (SUSPEND)
1330
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(1))));
1331
+ // __r[1] = { scope: { scopeId, type: 'signal', signal, resumeStep } }
1332
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression([
1333
+ factory.createPropertyAssignment('scope', factory.createObjectLiteralExpression([
1334
+ factory.createPropertyAssignment('scopeId', factory.createNumericLiteral(opcode.scopeId)),
1335
+ factory.createPropertyAssignment('type', factory.createStringLiteral('signal')),
1336
+ factory.createPropertyAssignment('signal', factory.createPropertyAccessExpression(signalExpr, 'signalName')),
1337
+ factory.createPropertyAssignment('resumeStep', factory.createNumericLiteral(resumeStepIndex)),
1338
+ ])),
1339
+ ]))));
1340
+ // break main_loop
1341
+ statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
1342
+ break;
1343
+ }
1344
+ case 'SCOPE_HANDLER': {
1345
+ // Handler form: suspend with scope config
1346
+ const resumeStepIndex = step.nextStep ?? step.index + 1;
1347
+ // step = resumeStep
1348
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(resumeStepIndex))));
1349
+ // __r[0] = 1 (SUSPEND)
1350
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(1))));
1351
+ // __r[1] = { scope: { scopeId, type: 'handler', resumeStep } }
1352
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression([
1353
+ factory.createPropertyAssignment('scope', factory.createObjectLiteralExpression([
1354
+ factory.createPropertyAssignment('scopeId', factory.createNumericLiteral(opcode.scopeId)),
1355
+ factory.createPropertyAssignment('type', factory.createStringLiteral('handler')),
1356
+ factory.createPropertyAssignment('resumeStep', factory.createNumericLiteral(resumeStepIndex)),
1357
+ ])),
1358
+ ]))));
1359
+ // break main_loop
1360
+ statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
1361
+ break;
1362
+ }
1363
+ case 'SCOPE_NEXT': {
1364
+ // Not used in current codegen - per-entity iteration handled by executor
1365
+ break;
1366
+ }
1367
+ case 'SCOPE_END': {
1368
+ // Collect scope results from state vars
1369
+ // Generated: state.vars.resultVar = state.vars.__scope_N_results
1370
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), factory.createStringLiteral(opcode.resultVar)), ts.SyntaxKind.EqualsToken, factory.createElementAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), factory.createStringLiteral(`__scope_${opcode.scopeId}_results`)))));
1371
+ break;
1372
+ }
1373
+ case 'ITER_START': {
1374
+ // Initialize iterator from iterable expression
1375
+ // const __iter_N = state.vars.__cursor_N
1376
+ // ? iterable[FromCursor](state.vars.__cursor_N)
1377
+ // : createDurableArrayIterator(iterable)
1378
+ const iterVarName = `__iter_${opcode.loopId}`;
1379
+ // First, store the iterable expression in a temp var
1380
+ const iterableVarName = `__iterable_${opcode.loopId}`;
1381
+ const rewrittenIterable = rewriteExpression(factory, opcode.iterableExpr, rewriterCtx);
1382
+ statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(iterableVarName, undefined, undefined, rewrittenIterable)], ts.NodeFlags.Const)));
1383
+ // Check if cursor exists in state.vars, if so use FromCursor, otherwise create new
1384
+ // const __iter_N = state.vars.__cursor_N !== undefined
1385
+ // ? (__iterable_N[FromCursor] ? __iterable_N[FromCursor](state.vars.__cursor_N) : createDurableArrayIterator(__iterable_N, state.vars.__cursor_N))
1386
+ // : (__iterable_N[FromCursor] ? __iterable_N : createDurableArrayIterator(__iterable_N))
1387
+ const cursorAccess = factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.cursorVar);
1388
+ // Symbol.for('justscale:FromCursor') is emitted into user code (cross-realm) - keep Symbol.for.
1389
+ const fromCursorSymbolExpr = factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Symbol'), 'for'), undefined, [factory.createStringLiteral('justscale:FromCursor')]);
1390
+ const hasFromCursor = factory.createBinaryExpression(fromCursorSymbolExpr, ts.SyntaxKind.InKeyword, factory.createIdentifier(iterableVarName));
1391
+ // If cursor exists
1392
+ const withCursor = factory.createConditionalExpression(hasFromCursor, factory.createToken(ts.SyntaxKind.QuestionToken),
1393
+ // Iterable has FromCursor - use it, then get iterator
1394
+ wrapWithAsyncIterator(factory, factory.createCallExpression(factory.createElementAccessExpression(factory.createIdentifier(iterableVarName), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Symbol'), 'for'), undefined, [factory.createStringLiteral('justscale:FromCursor')])), undefined, [cursorAccess])), factory.createToken(ts.SyntaxKind.ColonToken),
1395
+ // Array - wrap with DurableArrayIterator
1396
+ factory.createNewExpression(factory.createIdentifier('DurableArrayIterator'), undefined, [factory.createIdentifier(iterableVarName), cursorAccess]));
1397
+ // If no cursor exists
1398
+ const withoutCursor = factory.createConditionalExpression(hasFromCursor, factory.createToken(ts.SyntaxKind.QuestionToken),
1399
+ // Iterable has FromCursor - get iterator from iterable
1400
+ wrapWithAsyncIterator(factory, factory.createIdentifier(iterableVarName)), factory.createToken(ts.SyntaxKind.ColonToken),
1401
+ // Array - wrap with DurableArrayIterator
1402
+ factory.createNewExpression(factory.createIdentifier('DurableArrayIterator'), undefined, [factory.createIdentifier(iterableVarName)]));
1403
+ // Complete conditional
1404
+ const iteratorInit = factory.createConditionalExpression(factory.createBinaryExpression(cursorAccess, ts.SyntaxKind.ExclamationEqualsEqualsToken, factory.createIdentifier('undefined')), factory.createToken(ts.SyntaxKind.QuestionToken), withCursor, factory.createToken(ts.SyntaxKind.ColonToken), withoutCursor);
1405
+ statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(iterVarName, undefined, undefined, iteratorInit)], ts.NodeFlags.Const)));
1406
+ break;
1407
+ }
1408
+ case 'ITER_NEXT': {
1409
+ // Fetch next item from iterator
1410
+ // const { value, done } = await __iter_N.next()
1411
+ // if (done) { step = doneTarget; continue main_loop }
1412
+ // state.vars.itemVar = value
1413
+ const iterVarName = `__iter_${opcode.loopId}`;
1414
+ const resultVarName = `__iterResult_${opcode.loopId}`;
1415
+ // const __iterResult_N = await __iter_N.next()
1416
+ statements.push(factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
1417
+ factory.createVariableDeclaration(resultVarName, undefined, undefined, factory.createAwaitExpression(factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier(iterVarName), 'next'), undefined, []))),
1418
+ ], ts.NodeFlags.Const)));
1419
+ // if (__iterResult_N.done) { step = doneTarget; continue main_loop }
1420
+ const doneTarget = findStepIndexForOpcode(steps, opcode.doneTarget);
1421
+ statements.push(factory.createIfStatement(factory.createPropertyAccessExpression(factory.createIdentifier(resultVarName), 'done'), factory.createBlock([
1422
+ factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(doneTarget))),
1423
+ factory.createContinueStatement(factory.createIdentifier('main_loop')),
1424
+ ])));
1425
+ // state.vars.itemVar = __iterResult_N.value
1426
+ // Find the ITER_START opcode to get the itemVar name
1427
+ let itemVarName = '__item';
1428
+ for (let j = opcodeIndex - 1; j >= 0; j--) {
1429
+ const prevOp = analysis.opcodes[j];
1430
+ if (prevOp.op === 'ITER_START' && prevOp.loopId === opcode.loopId) {
1431
+ itemVarName = prevOp.itemVar;
1432
+ break;
1433
+ }
1434
+ }
1435
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), itemVarName), ts.SyntaxKind.EqualsToken, factory.createPropertyAccessExpression(factory.createIdentifier(resultVarName), 'value'))));
1436
+ break;
1437
+ }
1438
+ case 'ITER_SAVE': {
1439
+ // Save cursor position before suspension
1440
+ // state.vars.__cursor_N = __iter_N[DurableCursor]?.() ?? state.vars.__cursor_N
1441
+ const iterVarName = `__iter_${opcode.loopId}`;
1442
+ // Try to get cursor from iterator if it supports DurableCursor
1443
+ const getCursor = factory.createCallChain(factory.createElementAccessChain(factory.createIdentifier(iterVarName), factory.createToken(ts.SyntaxKind.QuestionDotToken), factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Symbol'), 'for'), undefined, [factory.createStringLiteral('justscale:DurableCursor')])), undefined, undefined, []);
1444
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.cursorVar), ts.SyntaxKind.EqualsToken, factory.createBinaryExpression(getCursor, ts.SyntaxKind.QuestionQuestionToken, factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.cursorVar)))));
1445
+ break;
1446
+ }
1447
+ case 'PARALLEL_START': {
1448
+ // Initialize parallel context in state.vars (persisted across suspension)
1449
+ // state.vars.__parallel_N = { pending: N, results: [], errors: [], isSettled: bool }
1450
+ const parallelVarName = `__parallel_${opcode.parallelId}`;
1451
+ const branchCount = opcode.branches.length;
1452
+ // Create parallel context initialization
1453
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), parallelVarName), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression([
1454
+ factory.createPropertyAssignment('parallelId', factory.createNumericLiteral(opcode.parallelId)),
1455
+ factory.createPropertyAssignment('pending', factory.createNumericLiteral(branchCount)),
1456
+ factory.createPropertyAssignment('results', factory.createArrayLiteralExpression([])),
1457
+ factory.createPropertyAssignment('errors', factory.createArrayLiteralExpression([])),
1458
+ factory.createPropertyAssignment('isSettled', opcode.isSettled ? factory.createTrue() : factory.createFalse()),
1459
+ factory.createPropertyAssignment('branches', factory.createArrayLiteralExpression(opcode.branches.map((branch) => factory.createObjectLiteralExpression([
1460
+ factory.createPropertyAssignment('id', typeof branch.id === 'number'
1461
+ ? factory.createNumericLiteral(branch.id)
1462
+ : factory.createStringLiteral(branch.id)),
1463
+ factory.createPropertyAssignment('type', factory.createStringLiteral(branch.type)),
1464
+ factory.createPropertyAssignment('expr', rewriteExpression(factory, cloneExpression(factory, branch.expr), rewriterCtx)),
1465
+ ])))),
1466
+ ], true))));
1467
+ break;
1468
+ }
1469
+ case 'PARALLEL_WAIT': {
1470
+ // Subscribe to all branches and suspend
1471
+ // This is a suspension point - we'll return SUSPEND with parallel info
1472
+ const parallelVarName = `__parallel_${opcode.parallelId}`;
1473
+ // Set state.step to the resume step (PARALLEL_COLLECT is next)
1474
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'step'), ts.SyntaxKind.EqualsToken, factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.PlusToken, factory.createNumericLiteral(1)))));
1475
+ // __r[0] = SUSPEND
1476
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), 0), ts.SyntaxKind.EqualsToken, factory.createIdentifier('SUSPEND'))));
1477
+ // __r[1] = { parallel: state.vars.__parallel_N }
1478
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), 1), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression([
1479
+ factory.createPropertyAssignment('parallel', factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), parallelVarName)),
1480
+ ]))));
1481
+ // break main_loop (use same pattern as WAIT/RACE_SUSPEND)
1482
+ statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
1483
+ break;
1484
+ }
1485
+ case 'PARALLEL_COLLECT': {
1486
+ // Collect results from parallel execution
1487
+ // The results are stored in state.vars.__parallel_N.results by the runtime
1488
+ const parallelVarName = `__parallel_${opcode.parallelId}`;
1489
+ // Helper: generates state.vars.__parallel_N
1490
+ const stateVarsParallel = () => factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), parallelVarName);
1491
+ if (opcode.isObject) {
1492
+ // Object form: convert array results to object using branch IDs as keys
1493
+ // state.vars.result = Object.fromEntries(state.vars.__parallel_N.branches.map((b, i) => [b.id, state.vars.__parallel_N.results[i]]))
1494
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.resultVar), ts.SyntaxKind.EqualsToken, factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier('Object'), 'fromEntries'), undefined, [
1495
+ factory.createCallExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(stateVarsParallel(), 'branches'), 'map'), undefined, [
1496
+ factory.createArrowFunction(undefined, undefined, [
1497
+ factory.createParameterDeclaration(undefined, undefined, 'b'),
1498
+ factory.createParameterDeclaration(undefined, undefined, 'i'),
1499
+ ], undefined, factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), factory.createArrayLiteralExpression([
1500
+ factory.createPropertyAccessExpression(factory.createIdentifier('b'), 'id'),
1501
+ factory.createElementAccessExpression(factory.createPropertyAccessExpression(stateVarsParallel(), 'results'), factory.createIdentifier('i')),
1502
+ ])),
1503
+ ]),
1504
+ ]))));
1505
+ }
1506
+ else {
1507
+ // Array form: directly assign results array
1508
+ // state.vars.result = state.vars.__parallel_N.results
1509
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createPropertyAccessExpression(factory.createPropertyAccessExpression(factory.createIdentifier('state'), 'vars'), opcode.resultVar), ts.SyntaxKind.EqualsToken, factory.createPropertyAccessExpression(stateVarsParallel(), 'results'))));
1510
+ }
1511
+ break;
1512
+ }
1513
+ case 'SUBPROCESS_SPAWN': {
1514
+ // Set step to resume point
1515
+ const resumeStepIndex = step.nextStep ?? step.index + 1;
1516
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createIdentifier('step'), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(resumeStepIndex))));
1517
+ // __r[0] = 2 (SUBPROCESS)
1518
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(0)), ts.SyntaxKind.EqualsToken, factory.createNumericLiteral(2))));
1519
+ // __r[1] = { name: '...', args: [...], storeVar: '...', awaited: true }
1520
+ const spawnProperties = [
1521
+ factory.createPropertyAssignment('name', factory.createStringLiteral(opcode.name)),
1522
+ factory.createPropertyAssignment('args', factory.createArrayLiteralExpression(opcode.argExprs.map((arg) => rewriteExpression(factory, cloneExpression(factory, arg), rewriterCtx)))),
1523
+ factory.createPropertyAssignment('awaited', opcode.awaited ? factory.createTrue() : factory.createFalse()),
1524
+ ];
1525
+ if (opcode.storeVar) {
1526
+ spawnProperties.push(factory.createPropertyAssignment('storeVar', factory.createStringLiteral(opcode.storeVar)));
1527
+ }
1528
+ statements.push(factory.createExpressionStatement(factory.createBinaryExpression(factory.createElementAccessExpression(factory.createIdentifier('__r'), factory.createNumericLiteral(1)), ts.SyntaxKind.EqualsToken, factory.createObjectLiteralExpression(spawnProperties))));
1529
+ // break main_loop
1530
+ statements.push(factory.createBreakStatement(factory.createIdentifier('main_loop')));
1531
+ break;
1532
+ }
1533
+ case 'SUBPROCESS_DECL': {
1534
+ // Declaration opcodes don't generate runtime code - they're metadata only
1535
+ break;
1536
+ }
1537
+ }
1538
+ return statements;
1539
+ }
1540
+ //# sourceMappingURL=switch-codegen.js.map