@reactra/babel-plugin 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/ast/index.d.ts +2 -0
  4. package/dist/ast/index.d.ts.map +1 -0
  5. package/dist/ast/index.js +3 -0
  6. package/dist/ast/index.js.map +1 -0
  7. package/dist/ast/nodes.d.ts +437 -0
  8. package/dist/ast/nodes.d.ts.map +1 -0
  9. package/dist/ast/nodes.js +35 -0
  10. package/dist/ast/nodes.js.map +1 -0
  11. package/dist/behaviours/index.d.ts +18 -0
  12. package/dist/behaviours/index.d.ts.map +1 -0
  13. package/dist/behaviours/index.js +36 -0
  14. package/dist/behaviours/index.js.map +1 -0
  15. package/dist/behaviours/plugin.d.ts +22 -0
  16. package/dist/behaviours/plugin.d.ts.map +1 -0
  17. package/dist/behaviours/plugin.js +70 -0
  18. package/dist/behaviours/plugin.js.map +1 -0
  19. package/dist/behaviours/replayable.d.ts +10 -0
  20. package/dist/behaviours/replayable.d.ts.map +1 -0
  21. package/dist/behaviours/replayable.js +86 -0
  22. package/dist/behaviours/replayable.js.map +1 -0
  23. package/dist/behaviours/types.d.ts +77 -0
  24. package/dist/behaviours/types.d.ts.map +1 -0
  25. package/dist/behaviours/types.js +10 -0
  26. package/dist/behaviours/types.js.map +1 -0
  27. package/dist/behaviours/undoable.d.ts +10 -0
  28. package/dist/behaviours/undoable.d.ts.map +1 -0
  29. package/dist/behaviours/undoable.js +62 -0
  30. package/dist/behaviours/undoable.js.map +1 -0
  31. package/dist/compile.d.ts +69 -0
  32. package/dist/compile.d.ts.map +1 -0
  33. package/dist/compile.js +75 -0
  34. package/dist/compile.js.map +1 -0
  35. package/dist/conventions/index.d.ts +110 -0
  36. package/dist/conventions/index.d.ts.map +1 -0
  37. package/dist/conventions/index.js +193 -0
  38. package/dist/conventions/index.js.map +1 -0
  39. package/dist/index.d.ts +48 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +77 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/passes/index.d.ts +5 -0
  44. package/dist/passes/index.d.ts.map +1 -0
  45. package/dist/passes/index.js +6 -0
  46. package/dist/passes/index.js.map +1 -0
  47. package/dist/passes/pass-1-parse.d.ts +3 -0
  48. package/dist/passes/pass-1-parse.d.ts.map +1 -0
  49. package/dist/passes/pass-1-parse.js +21 -0
  50. package/dist/passes/pass-1-parse.js.map +1 -0
  51. package/dist/passes/pass-2-extract.d.ts +4 -0
  52. package/dist/passes/pass-2-extract.d.ts.map +1 -0
  53. package/dist/passes/pass-2-extract.js +762 -0
  54. package/dist/passes/pass-2-extract.js.map +1 -0
  55. package/dist/passes/pass-3-readset.d.ts +11 -0
  56. package/dist/passes/pass-3-readset.d.ts.map +1 -0
  57. package/dist/passes/pass-3-readset.js +338 -0
  58. package/dist/passes/pass-3-readset.js.map +1 -0
  59. package/dist/passes/pass-9-codegen.d.ts +27 -0
  60. package/dist/passes/pass-9-codegen.d.ts.map +1 -0
  61. package/dist/passes/pass-9-codegen.js +2755 -0
  62. package/dist/passes/pass-9-codegen.js.map +1 -0
  63. package/dist/preprocess/helpers.d.ts +71 -0
  64. package/dist/preprocess/helpers.d.ts.map +1 -0
  65. package/dist/preprocess/helpers.js +342 -0
  66. package/dist/preprocess/helpers.js.map +1 -0
  67. package/dist/preprocess/index.d.ts +6 -0
  68. package/dist/preprocess/index.d.ts.map +1 -0
  69. package/dist/preprocess/index.js +11 -0
  70. package/dist/preprocess/index.js.map +1 -0
  71. package/dist/preprocess/keywords.d.ts +28 -0
  72. package/dist/preprocess/keywords.d.ts.map +1 -0
  73. package/dist/preprocess/keywords.js +99 -0
  74. package/dist/preprocess/keywords.js.map +1 -0
  75. package/dist/preprocess/lexer.d.ts +8 -0
  76. package/dist/preprocess/lexer.d.ts.map +1 -0
  77. package/dist/preprocess/lexer.js +143 -0
  78. package/dist/preprocess/lexer.js.map +1 -0
  79. package/dist/preprocess/preprocess.d.ts +3 -0
  80. package/dist/preprocess/preprocess.d.ts.map +1 -0
  81. package/dist/preprocess/preprocess.js +568 -0
  82. package/dist/preprocess/preprocess.js.map +1 -0
  83. package/dist/preprocess/rewriters.d.ts +35 -0
  84. package/dist/preprocess/rewriters.d.ts.map +1 -0
  85. package/dist/preprocess/rewriters.js +1391 -0
  86. package/dist/preprocess/rewriters.js.map +1 -0
  87. package/dist/preprocess/source-map.d.ts +70 -0
  88. package/dist/preprocess/source-map.d.ts.map +1 -0
  89. package/dist/preprocess/source-map.js +253 -0
  90. package/dist/preprocess/source-map.js.map +1 -0
  91. package/dist/preprocess/types.d.ts +57 -0
  92. package/dist/preprocess/types.d.ts.map +1 -0
  93. package/dist/preprocess/types.js +7 -0
  94. package/dist/preprocess/types.js.map +1 -0
  95. package/dist/sidecar.d.ts +137 -0
  96. package/dist/sidecar.d.ts.map +1 -0
  97. package/dist/sidecar.js +172 -0
  98. package/dist/sidecar.js.map +1 -0
  99. package/package.json +42 -0
@@ -0,0 +1,762 @@
1
+ // Pass 2 — extract Reactra metadata from the parsed Babel AST.
2
+ //
3
+ // Walks the AST, finds the top-level `__reactra_{component|service|store}__`
4
+ // wrappers, then walks each one's factory body finding the per-marker calls
5
+ // (state, derived, action, view, ...). Produces a FileGraph that later
6
+ // passes consume.
7
+ //
8
+ // Owner spec: reactra-compiler-spec.md §4 Pass 2, Pass 2a.
9
+ import _traverse from "@babel/traverse";
10
+ import * as t from "@babel/types";
11
+ // §B1: marker vocabulary + await arg order come from the shared conventions home
12
+ // (CONTAINER_MARKERS is re-exported there from ast/nodes.ts — one home).
13
+ import { AWAIT_ARG_ORDER, CONTAINER_MARKERS } from "../conventions/index.js";
14
+ // @babel/traverse is a CJS module that exports its default via .default
15
+ // when imported from ESM. The dual cast keeps both interop paths happy.
16
+ // CJS/ESM interop: @babel/traverse exports `module.exports = traverse` (CJS).
17
+ // Under moduleResolution:NodeNext the default import resolves to the module
18
+ // namespace (not callable). The runtime `.default` unwrap + any-cast recovers it.
19
+ // The dev typecheck (moduleResolution:Bundler) validates call-site types.
20
+ const traverse = (_traverse.default ?? _traverse);
21
+ /** Slice the original source between two AST positions. */
22
+ const sliceSource = (source, node) => {
23
+ if (node.start == null || node.end == null)
24
+ return "";
25
+ return source.slice(node.start, node.end);
26
+ };
27
+ /** Returns the string literal value of the first argument, or undefined. */
28
+ const firstStringArg = (call) => {
29
+ const a = call.arguments[0];
30
+ if (a && t.isStringLiteral(a))
31
+ return a.value;
32
+ return undefined;
33
+ };
34
+ /** Returns the arrow function at the given argument index, or undefined. */
35
+ const arrowArg = (call, idx) => {
36
+ const a = call.arguments[idx];
37
+ if (a && t.isArrowFunctionExpression(a))
38
+ return a;
39
+ return undefined;
40
+ };
41
+ /** Extract the expression body of a thunk arrow `() => (expr)`. */
42
+ const thunkBody = (arrow) => {
43
+ const body = arrow.body;
44
+ if (!t.isBlockStatement(body))
45
+ return body;
46
+ // Single-return form `() => { return expr }`
47
+ if (body.body.length === 1) {
48
+ const stmt = body.body[0];
49
+ if (t.isReturnStatement(stmt) && stmt.argument)
50
+ return stmt.argument;
51
+ }
52
+ return undefined;
53
+ };
54
+ export const extractGraph = (source, preprocessed, ast) => {
55
+ // AST positions are relative to `preprocessed` (what Babel parsed).
56
+ // Use that for source-slicing; `source` is kept for source-map work later.
57
+ const sliceFrom = preprocessed;
58
+ const containers = [];
59
+ // Top-level `import …` statements — emitted verbatim by Pass 9 above
60
+ // its own auto-injected imports so user modules (api, utils, peer
61
+ // components imported into a JSX view) stay in scope. Looking at
62
+ // `ast.program.body` directly avoids walking the whole tree.
63
+ const userImports = [];
64
+ // §1.1 — top-level `interface`/`type` declarations are pure type-level, so we
65
+ // preserve them verbatim (Pass 9 re-emits after imports) and a DSL file can
66
+ // declare its own props types in-file. `export`ed variants included.
67
+ const userTypes = [];
68
+ const isTypeDecl = (n) => t.isTSInterfaceDeclaration(n) || t.isTSTypeAliasDeclaration(n);
69
+ for (const stmt of ast.program.body) {
70
+ if (t.isImportDeclaration(stmt)) {
71
+ userImports.push(sliceSource(sliceFrom, stmt));
72
+ }
73
+ else if (isTypeDecl(stmt)) {
74
+ userTypes.push(sliceSource(sliceFrom, stmt));
75
+ }
76
+ else if (t.isExportNamedDeclaration(stmt) && stmt.declaration && isTypeDecl(stmt.declaration)) {
77
+ userTypes.push(sliceSource(sliceFrom, stmt));
78
+ }
79
+ }
80
+ traverse(ast, {
81
+ VariableDeclaration(path) {
82
+ // Top-level declarations only — direct children of Program.
83
+ if (!t.isProgram(path.parent) && !t.isExportNamedDeclaration(path.parent))
84
+ return;
85
+ for (const declarator of path.node.declarations) {
86
+ const init = declarator.init;
87
+ if (!t.isCallExpression(init))
88
+ continue;
89
+ const callee = init.callee;
90
+ if (!t.isIdentifier(callee))
91
+ continue;
92
+ const kind = CONTAINER_MARKERS[callee.name];
93
+ if (!kind)
94
+ continue;
95
+ const name = firstStringArg(init);
96
+ if (!name)
97
+ continue;
98
+ // The factory arrow is the LAST argument (subtree stores have
99
+ // `(name, "/path", factory)`; keyed have `(name, (key) => {})`).
100
+ const factory = init.arguments[init.arguments.length - 1];
101
+ if (!t.isArrowFunctionExpression(factory))
102
+ continue;
103
+ // subtree path: 2nd arg is a string for `__reactra_route_store_subtree__`
104
+ let subtreePath;
105
+ if (callee.name === "__reactra_route_store_subtree__") {
106
+ const second = init.arguments[1];
107
+ if (t.isStringLiteral(second))
108
+ subtreePath = second.value;
109
+ }
110
+ // keyed sig — emitted by the preprocessor as the factory's first param
111
+ let keySig;
112
+ if (callee.name === "__reactra_route_store_keyed__" && factory.params.length === 1) {
113
+ keySig = sliceSource(sliceFrom, factory.params[0]);
114
+ }
115
+ // Wave 3 §2b — service modifier (`scoped` / `server`). The
116
+ // preprocessor differentiates by emitting separate marker
117
+ // function names; here we map them back to the field on
118
+ // ContainerNode for Pass 9 to consume.
119
+ let serviceModifier;
120
+ if (callee.name === "__reactra_service_scoped__")
121
+ serviceModifier = "scoped";
122
+ else if (callee.name === "__reactra_service_server__")
123
+ serviceModifier = "server";
124
+ const exported = t.isExportNamedDeclaration(path.parent);
125
+ const container = {
126
+ kind,
127
+ name,
128
+ exported,
129
+ call: init,
130
+ factory,
131
+ states: [],
132
+ deriveds: [],
133
+ actions: [],
134
+ commands: [],
135
+ resources: [],
136
+ refs: [],
137
+ effects: [],
138
+ mounts: [],
139
+ cleanups: [],
140
+ injects: [],
141
+ provides: [],
142
+ params: [],
143
+ queries: [],
144
+ storeUses: [],
145
+ inputs: [],
146
+ awaitBlocks: [],
147
+ subtreePath,
148
+ keySig,
149
+ serviceModifier,
150
+ };
151
+ // §1.1 — component props parameter. The preprocessor emitted it as the
152
+ // factory arrow's first param; record the verbatim text (Pass 9 re-emits
153
+ // it) and the binding names (Pass 3 adds them to the reactive read-set).
154
+ if (kind === "component" && factory.params.length > 0) {
155
+ const p = factory.params[0];
156
+ container.propsParam = sliceSource(sliceFrom, p);
157
+ const names = [];
158
+ collectPatternNames(p, names);
159
+ container.propNames = names;
160
+ }
161
+ extractContainerBody(sliceFrom, factory, container);
162
+ // Day 23 / `#7b`: walk the captured view (if any) and surface
163
+ // every `await(r) { ... } pending { ... } error(e) { ... }`
164
+ // block into `container.awaitBlocks`. Runtime path is still
165
+ // pass-9-codegen's inline Suspense+ErrorBoundary rewrite; this
166
+ // list exists so sidecar consumers (devtools / LSP / future
167
+ // passes) can enumerate await sites without re-parsing the
168
+ // view AST.
169
+ extractAwaitBlocks(sliceFrom, container);
170
+ containers.push(container);
171
+ }
172
+ },
173
+ });
174
+ return { source, preprocessed, containers, userImports, userTypes };
175
+ };
176
+ /**
177
+ * Collect every binding name introduced by a component's props parameter
178
+ * (Component DSL §1.1) into `out`. A bag identifier `(props)` contributes
179
+ * `props`; a destructuring pattern `({ a, b = d, ...rest })` contributes each
180
+ * leaf binding (`a`, `b`, `rest`). These are the names Pass 3 treats as reactive
181
+ * reads — for the bag form, `props` then absorbs `props.x` member reads.
182
+ */
183
+ const collectPatternNames = (node, out) => {
184
+ if (t.isIdentifier(node))
185
+ out.push(node.name);
186
+ else if (t.isAssignmentPattern(node))
187
+ collectPatternNames(node.left, out);
188
+ else if (t.isRestElement(node))
189
+ collectPatternNames(node.argument, out);
190
+ else if (t.isObjectPattern(node)) {
191
+ for (const prop of node.properties) {
192
+ if (t.isObjectProperty(prop))
193
+ collectPatternNames(prop.value, out);
194
+ else
195
+ collectPatternNames(prop, out); // RestElement
196
+ }
197
+ }
198
+ else if (t.isArrayPattern(node)) {
199
+ for (const el of node.elements)
200
+ if (el)
201
+ collectPatternNames(el, out);
202
+ }
203
+ };
204
+ /** Walk the factory body of a container, classifying each top-level marker call. */
205
+ const extractContainerBody = (source, factory, container) => {
206
+ const body = factory.body;
207
+ if (!t.isBlockStatement(body))
208
+ return;
209
+ for (const stmt of body.body) {
210
+ if (!t.isExpressionStatement(stmt))
211
+ continue;
212
+ const expr = stmt.expression;
213
+ if (!t.isCallExpression(expr))
214
+ continue;
215
+ classifyBodyMarker(source, expr, container);
216
+ }
217
+ };
218
+ /** Dispatch a body-level call to the right node-builder. */
219
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
220
+ const classifyBodyMarker = (source, call, c) => {
221
+ const callee = call.callee;
222
+ if (!t.isIdentifier(callee))
223
+ return;
224
+ switch (callee.name) {
225
+ case "__reactra_state__":
226
+ c.states.push(buildState(source, call));
227
+ return;
228
+ case "__reactra_derived__":
229
+ c.deriveds.push(buildDerived(source, call));
230
+ return;
231
+ case "__reactra_action__":
232
+ c.actions.push(buildAction(call, false));
233
+ return;
234
+ case "__reactra_action_async__":
235
+ c.actions.push(buildAction(call, true));
236
+ return;
237
+ case "__reactra_command__":
238
+ c.commands.push(buildCommand(call, "block"));
239
+ return;
240
+ case "__reactra_command_arrow__":
241
+ c.commands.push(buildCommand(call, "arrow"));
242
+ return;
243
+ case "__reactra_resource__":
244
+ c.resources.push(buildResource(source, call));
245
+ return;
246
+ case "__reactra_ref__":
247
+ c.refs.push(buildRef(source, call));
248
+ return;
249
+ case "__reactra_mount__":
250
+ c.mounts.push(buildMount(call));
251
+ return;
252
+ case "__reactra_cleanup__":
253
+ c.cleanups.push(buildCleanup(call));
254
+ return;
255
+ case "__reactra_effect__":
256
+ case "__reactra_effect_on__":
257
+ c.effects.push(buildEffect(call, callee.name === "__reactra_effect_on__"));
258
+ return;
259
+ case "__reactra_view__":
260
+ c.view = buildView(call);
261
+ return;
262
+ case "__reactra_uses__":
263
+ // One `uses` clause per container (Component DSL §9). A second marker
264
+ // means the source carried BOTH a header clause (lowered first by
265
+ // Pass 1.1) and a body line — error rather than silently overwrite.
266
+ // Code-less: no `R` code is minted for this structural band.
267
+ if (c.uses) {
268
+ throw new Error(`[reactra:compile] BHV002: ${c.kind} "${c.name}" declares more than one \`uses\` clause ` +
269
+ `(header form + body line). Declare ONE — either in the header ` +
270
+ `(\`component ${c.name} uses ${c.uses.names.join(", ")} {\`) or as a body line. ` +
271
+ `(Component DSL §9; Behaviour Plugin spec §8.)`);
272
+ }
273
+ c.uses = buildUses(call);
274
+ return;
275
+ case "__reactra_inject__":
276
+ c.injects.push(buildInject(call));
277
+ return;
278
+ case "__reactra_provide__":
279
+ c.provides.push(buildProvide(source, call));
280
+ return;
281
+ case "__reactra_param__":
282
+ c.params.push(buildParam(call));
283
+ return;
284
+ case "__reactra_query__":
285
+ c.queries.push(buildQuery(source, call));
286
+ return;
287
+ case "__reactra_meta__":
288
+ c.meta = buildMeta(call);
289
+ return;
290
+ case "__reactra_prefetch__":
291
+ c.prefetch = buildPrefetch(call);
292
+ return;
293
+ case "__reactra_transition__":
294
+ c.transition = buildTransition(call);
295
+ return;
296
+ case "__reactra_inject_store__":
297
+ c.storeUses.push(buildStoreUseBare(call));
298
+ return;
299
+ case "__reactra_inject_store_args__":
300
+ c.storeUses.push(buildStoreUseArgs(call));
301
+ return;
302
+ case "__reactra_inject_store_keyed__":
303
+ c.storeUses.push(buildStoreUseKeyed(call));
304
+ return;
305
+ case "__reactra_error_boundary__":
306
+ c.errorBoundary = buildErrorBoundary(call);
307
+ return;
308
+ case "__reactra_suspense__":
309
+ c.suspense = buildSuspense(call);
310
+ return;
311
+ case "__reactra_input__":
312
+ c.inputs.push(buildInput(source, call));
313
+ return;
314
+ case "__reactra_preserved__":
315
+ // v2: each `preserved state X = init` emits one __reactra_preserved__("X") call
316
+ // with a single field; multiple calls accumulate on the same node. v1 emitted
317
+ // a single call with all fields as string args — both forms are handled by
318
+ // merging into the existing `preserved` node's field list.
319
+ if (c.preserved) {
320
+ const extra = buildPreserved(call);
321
+ for (const f of extra.fields) {
322
+ if (!c.preserved.fields.includes(f))
323
+ c.preserved.fields.push(f);
324
+ }
325
+ }
326
+ else {
327
+ c.preserved = buildPreserved(call);
328
+ }
329
+ return;
330
+ default:
331
+ return;
332
+ }
333
+ };
334
+ // ---------- per-marker builders ----------
335
+ const buildState = (source, call) => {
336
+ const name = firstStringArg(call) ?? "";
337
+ const init = (call.arguments[1] ?? t.identifier("undefined"));
338
+ return {
339
+ kind: "state",
340
+ name,
341
+ initializer: init,
342
+ initializerText: sliceSource(source, init),
343
+ call,
344
+ };
345
+ };
346
+ const buildDerived = (source, call) => {
347
+ const name = firstStringArg(call) ?? "";
348
+ const thunk = arrowArg(call, 1);
349
+ const expr = thunkBody(thunk);
350
+ return {
351
+ kind: "derived",
352
+ name,
353
+ thunk,
354
+ exprText: expr ? sliceSource(source, expr) : "",
355
+ deps: [], // populated by Pass 3 (read-set analysis)
356
+ call,
357
+ };
358
+ };
359
+ const buildAction = (call, isAsync) => ({
360
+ kind: "action",
361
+ name: firstStringArg(call) ?? "",
362
+ isAsync,
363
+ arrow: arrowArg(call, 1),
364
+ deps: [], // populated by Pass 3 (read-set analysis)
365
+ call,
366
+ });
367
+ /** The value of an object-literal property by key, or undefined. */
368
+ const objProp = (obj, key) => {
369
+ for (const p of obj.properties) {
370
+ if (t.isObjectProperty(p) && t.isIdentifier(p.key, { name: key }))
371
+ return p.value;
372
+ }
373
+ return undefined;
374
+ };
375
+ /**
376
+ * Resource names from an `invalidate [a, b]` array. Each entry names a resource —
377
+ * a bare identifier (`posts`) or a string literal (`"posts"`, the useMutation
378
+ * muscle-memory form); both mean the same cache key. Any OTHER element kind
379
+ * (member expression, call, spread) is NOT a resource name — warn loudly rather
380
+ * than silently dropping it, so a malformed `invalidate` can't quietly skip a
381
+ * refetch (a cache-coherence footgun). Holes (`[a, ,]`) are ignored silently.
382
+ */
383
+ const invalidateNames = (commandName, arr) => {
384
+ const names = [];
385
+ for (const e of arr.elements) {
386
+ if (e == null)
387
+ continue; // an array hole (trailing/extra comma) — harmless
388
+ if (t.isIdentifier(e))
389
+ names.push(e.name);
390
+ else if (t.isStringLiteral(e))
391
+ names.push(e.value);
392
+ else {
393
+ console.warn(`[reactra:compile] command "${commandName}" — \`invalidate\` accepts bare resource ` +
394
+ `names (e.g. \`invalidate [posts]\` or \`invalidate ["posts"]\`); ignoring an ` +
395
+ `unsupported entry of kind "${e.type}".`);
396
+ }
397
+ }
398
+ return names;
399
+ };
400
+ const buildCommand = (call, form) => {
401
+ // Arrow form may carry a 3rd arg — the clause options object
402
+ // `{ optimistic: () => {…}, invalidate: [a, b], rollback: (e) => {…} }`.
403
+ const opts = form === "arrow" && t.isObjectExpression(call.arguments[2]) ? call.arguments[2] : undefined;
404
+ const name = firstStringArg(call) ?? "";
405
+ const optimistic = opts && objProp(opts, "optimistic");
406
+ const invalidate = opts && objProp(opts, "invalidate");
407
+ const rollback = opts && objProp(opts, "rollback");
408
+ return {
409
+ kind: "command",
410
+ name,
411
+ form,
412
+ arrow: arrowArg(call, 1),
413
+ ...(optimistic && t.isArrowFunctionExpression(optimistic) ? { optimistic } : {}),
414
+ ...(invalidate && t.isArrayExpression(invalidate) ? { invalidate: invalidateNames(name, invalidate) } : {}),
415
+ ...(rollback && t.isArrowFunctionExpression(rollback) ? { rollback } : {}),
416
+ call,
417
+ };
418
+ };
419
+ const buildResource = (source, call) => {
420
+ // Stage 3 — the 4th arg, when present, is an ObjectExpression literal the
421
+ // preprocessor emits for modifier opt-ins (`cache`, `swr`, `retry`). We read
422
+ // both the flags AND the parameterized values: `cache: { ttlMs }` (hard-expiry)
423
+ // vs `cache: { staleWhileRevalidate, ttlMs }` (swr) vs `cache: true` (bare); and
424
+ // the `retry` count. Values are sliced as source so an expression (`retry: MAX`)
425
+ // survives. Absent → all default (Pass 9 fills `ttlMs: 60_000` / `retry: 3`).
426
+ const optsArg = call.arguments[3];
427
+ let optCache;
428
+ let optSwr;
429
+ let optRetry;
430
+ let cacheTtl;
431
+ let retryCount;
432
+ let select;
433
+ if (optsArg && t.isObjectExpression(optsArg)) {
434
+ const cacheVal = objProp(optsArg, "cache");
435
+ const retryVal = objProp(optsArg, "retry");
436
+ if (cacheVal && t.isObjectExpression(cacheVal)) {
437
+ const isSwr = objProp(cacheVal, "staleWhileRevalidate") !== undefined;
438
+ if (isSwr)
439
+ optSwr = true;
440
+ else
441
+ optCache = true;
442
+ const ttl = objProp(cacheVal, "ttlMs");
443
+ if (ttl)
444
+ cacheTtl = sliceSource(source, ttl);
445
+ }
446
+ else if (cacheVal) {
447
+ optCache = true; // `cache: true` (bare, no TTL)
448
+ }
449
+ if (retryVal) {
450
+ optRetry = true;
451
+ retryCount = sliceSource(source, retryVal);
452
+ }
453
+ const selectVal = objProp(optsArg, "select");
454
+ if (selectVal && t.isArrowFunctionExpression(selectVal)) {
455
+ select = sliceSource(source, selectVal);
456
+ }
457
+ }
458
+ return {
459
+ kind: "resource",
460
+ name: firstStringArg(call) ?? "",
461
+ depsThunk: arrowArg(call, 1),
462
+ fetcher: arrowArg(call, 2),
463
+ optCache,
464
+ optSwr,
465
+ optRetry,
466
+ ...(cacheTtl !== undefined ? { cacheTtl } : {}),
467
+ ...(retryCount !== undefined ? { retryCount } : {}),
468
+ ...(select !== undefined ? { select } : {}),
469
+ call,
470
+ };
471
+ };
472
+ const buildRef = (source, call) => {
473
+ const name = firstStringArg(call) ?? "";
474
+ const init = (call.arguments[1] ?? t.nullLiteral());
475
+ return {
476
+ kind: "ref",
477
+ name,
478
+ initializer: init,
479
+ initializerText: sliceSource(source, init),
480
+ call,
481
+ };
482
+ };
483
+ const buildMount = (call) => ({
484
+ kind: "mount",
485
+ body: arrowArg(call, 0),
486
+ call,
487
+ });
488
+ const buildCleanup = (call) => ({
489
+ kind: "cleanup",
490
+ body: arrowArg(call, 0),
491
+ call,
492
+ });
493
+ const buildEffect = (call, hasWatch) => ({
494
+ kind: "effect",
495
+ watch: hasWatch ? arrowArg(call, 0) : undefined,
496
+ body: hasWatch ? arrowArg(call, 1) : arrowArg(call, 0),
497
+ call,
498
+ });
499
+ const buildView = (call) => ({
500
+ kind: "view",
501
+ body: arrowArg(call, 0),
502
+ call,
503
+ });
504
+ const buildUses = (call) => {
505
+ const names = [];
506
+ for (const arg of call.arguments) {
507
+ if (t.isStringLiteral(arg))
508
+ names.push(arg.value);
509
+ }
510
+ return { kind: "uses", names, call };
511
+ };
512
+ /** Read a string-valued property `key` off an ObjectExpression argument. */
513
+ const objStringProp = (arg, key) => {
514
+ if (!arg || !t.isObjectExpression(arg))
515
+ return undefined;
516
+ for (const prop of arg.properties) {
517
+ if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key) || prop.key.name !== key)
518
+ continue;
519
+ if (t.isStringLiteral(prop.value))
520
+ return prop.value.value;
521
+ }
522
+ return undefined;
523
+ };
524
+ /**
525
+ * Read a `{ fields: ["a", { source: "b", local: "localB" }] }` argument into a
526
+ * `StoreField[]` (undefined if absent). A string element is a bare field; an
527
+ * object element is a `{ source, local, as? }` rename (Store v4.7 §2.5).
528
+ */
529
+ const readFieldsArg = (arg) => {
530
+ if (!arg || !t.isObjectExpression(arg))
531
+ return undefined;
532
+ for (const prop of arg.properties) {
533
+ if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key) || prop.key.name !== "fields")
534
+ continue;
535
+ if (!t.isArrayExpression(prop.value))
536
+ return undefined;
537
+ const out = [];
538
+ for (const e of prop.value.elements) {
539
+ if (e == null)
540
+ continue;
541
+ if (t.isStringLiteral(e)) {
542
+ out.push(e.value);
543
+ }
544
+ else if (t.isObjectExpression(e)) {
545
+ const source = objStringProp(e, "source");
546
+ const local = objStringProp(e, "local");
547
+ if (source && local)
548
+ out.push({ source, local, as: objBoolProp(e, "as") });
549
+ }
550
+ }
551
+ return out.length > 0 ? out : undefined;
552
+ }
553
+ return undefined;
554
+ };
555
+ /** Read a boolean object property (`{ as: true }`) — true only when literally `true`. */
556
+ const objBoolProp = (obj, key) => {
557
+ if (!obj || !t.isObjectExpression(obj))
558
+ return false;
559
+ for (const prop of obj.properties) {
560
+ if (t.isObjectProperty(prop) && t.isIdentifier(prop.key) && prop.key.name === key) {
561
+ return t.isBooleanLiteral(prop.value) && prop.value.value === true;
562
+ }
563
+ }
564
+ return false;
565
+ };
566
+ const buildInject = (call) => {
567
+ const opts = call.arguments[1];
568
+ const serviceKind = objStringProp(opts, "kind") === "service" ? "service" : undefined;
569
+ return {
570
+ kind: "inject",
571
+ name: firstStringArg(call) ?? "",
572
+ serviceKind,
573
+ source: objStringProp(opts, "source"),
574
+ alias: objStringProp(opts, "alias"),
575
+ call,
576
+ };
577
+ };
578
+ const buildProvide = (source, call) => {
579
+ const name = firstStringArg(call) ?? "";
580
+ const value = (call.arguments[1] ?? t.nullLiteral());
581
+ return {
582
+ kind: "provide",
583
+ name,
584
+ value,
585
+ valueText: sliceSource(source, value),
586
+ call,
587
+ };
588
+ };
589
+ const buildParam = (call) => ({
590
+ kind: "param",
591
+ name: firstStringArg(call) ?? "",
592
+ call,
593
+ });
594
+ const buildQuery = (source, call) => {
595
+ const name = firstStringArg(call) ?? "";
596
+ // `__reactra_query__("name", "<type>"[, default[, parser]])` — arg[1] is the
597
+ // declared type string (preprocessor always emits it; "string" when unannotated),
598
+ // arg[2] the optional default, arg[3] the optional custom-from parser identifier.
599
+ // When `from parseFoo` is present without a default, arg[2] is the literal
600
+ // `undefined` (a placeholder so the parser stays at arg[3]).
601
+ const typeArg = call.arguments[1];
602
+ const tsType = t.isStringLiteral(typeArg) ? typeArg.value : "string";
603
+ const def = call.arguments[2];
604
+ const isUndefinedPlaceholder = def != null && t.isIdentifier(def) && def.name === "undefined";
605
+ const parserArg = call.arguments[3];
606
+ const fromParser = parserArg != null && t.isIdentifier(parserArg) ? parserArg.name : undefined;
607
+ return {
608
+ kind: "query",
609
+ name,
610
+ tsType,
611
+ defaultExpr: isUndefinedPlaceholder ? undefined : def,
612
+ defaultText: def && !isUndefinedPlaceholder ? sliceSource(source, def) : undefined,
613
+ fromParser,
614
+ call,
615
+ };
616
+ };
617
+ const buildMeta = (call) => ({
618
+ kind: "meta",
619
+ object: call.arguments[0],
620
+ call,
621
+ });
622
+ const buildPrefetch = (call) => {
623
+ const trigger = firstStringArg(call) ?? "none";
624
+ return {
625
+ kind: "prefetch",
626
+ trigger: trigger,
627
+ call,
628
+ };
629
+ };
630
+ const buildTransition = (call) => {
631
+ const name = firstStringArg(call) ?? "";
632
+ const second = call.arguments[1];
633
+ const durationMs = second && t.isNumericLiteral(second) ? second.value : undefined;
634
+ return { kind: "transition", name, durationMs, call };
635
+ };
636
+ const buildStoreUseBare = (call) => ({
637
+ kind: "store-use",
638
+ storeName: firstStringArg(call) ?? "",
639
+ classification: "bare",
640
+ // __reactra_inject_store__("X" [, { fields: [...], typeOverride: "T", alias: "Y" }])
641
+ fields: readFieldsArg(call.arguments[1]),
642
+ typeOverride: objStringProp(call.arguments[1], "typeOverride"),
643
+ alias: objStringProp(call.arguments[1], "alias"),
644
+ call,
645
+ });
646
+ const buildStoreUseArgs = (call) => ({
647
+ kind: "store-use",
648
+ storeName: firstStringArg(call) ?? "",
649
+ classification: "argumented",
650
+ // __reactra_inject_store_args__("X", { inputs } [, { fields: [...], typeOverride: "T" }])
651
+ args: call.arguments[1],
652
+ fields: readFieldsArg(call.arguments[2]),
653
+ typeOverride: objStringProp(call.arguments[2], "typeOverride"),
654
+ call,
655
+ });
656
+ const buildStoreUseKeyed = (call) => ({
657
+ kind: "store-use",
658
+ storeName: firstStringArg(call) ?? "",
659
+ classification: "keyed",
660
+ // __reactra_inject_store_keyed__("X", key, { inputs } [, { fields: [...], typeOverride: "T" }])
661
+ key: call.arguments[1],
662
+ args: call.arguments[2],
663
+ fields: readFieldsArg(call.arguments[3]),
664
+ typeOverride: objStringProp(call.arguments[3], "typeOverride"),
665
+ call,
666
+ });
667
+ const buildErrorBoundary = (call) => {
668
+ const body = arrowArg(call, 0);
669
+ const paramNode = body.params[0];
670
+ const errorParam = paramNode && t.isIdentifier(paramNode) ? paramNode.name : "e";
671
+ return { kind: "error-boundary", errorParam, body, call };
672
+ };
673
+ const buildSuspense = (call) => ({
674
+ kind: "suspense",
675
+ body: arrowArg(call, 0),
676
+ call,
677
+ });
678
+ const buildInput = (source, call) => {
679
+ const name = firstStringArg(call) ?? "";
680
+ let requirement = "required";
681
+ let defaultExpr;
682
+ let defaultText;
683
+ const opts = call.arguments[1];
684
+ if (opts && t.isObjectExpression(opts)) {
685
+ for (const prop of opts.properties) {
686
+ if (!t.isObjectProperty(prop) || !t.isIdentifier(prop.key))
687
+ continue;
688
+ if (prop.key.name === "optional" && t.isBooleanLiteral(prop.value) && prop.value.value) {
689
+ requirement = "optional";
690
+ }
691
+ else if (prop.key.name === "default") {
692
+ requirement = "default";
693
+ defaultExpr = prop.value;
694
+ defaultText = sliceSource(source, prop.value);
695
+ }
696
+ }
697
+ }
698
+ // arg[1].tsType — the declared type string (Run 2 / D3), present only when the
699
+ // source annotated the input. Shadow-emitter only; Pass-9 ignores it.
700
+ const tsType = objStringProp(opts, "tsType");
701
+ return { kind: "input", name, requirement, defaultExpr, defaultText, tsType, call };
702
+ };
703
+ const buildPreserved = (call) => {
704
+ const fields = [];
705
+ for (const arg of call.arguments) {
706
+ if (t.isStringLiteral(arg))
707
+ fields.push(arg.value);
708
+ }
709
+ return { kind: "preserved", fields, call };
710
+ };
711
+ /**
712
+ * Day 23 / `#7b`: walk the component's view body for `__reactra_await__(...)`
713
+ * CallExpressions and append one `AwaitBlockNode` per match to
714
+ * `container.awaitBlocks`. Mirrors `scanAwaits` in pass-9-codegen but
715
+ * keeps the extracted data on the FileGraph so the sidecar builder
716
+ * (and future devtools/LSP consumers) can read it without
717
+ * re-traversing the view AST.
718
+ *
719
+ * `resourceName` is the text of the first arrow's body — typically a
720
+ * bare identifier (e.g. `"customer"`) but tolerates richer expressions
721
+ * (e.g. `"customer.data"`). `hasPending` / `hasError` mirror whether
722
+ * the optional 3rd / 4th arrow arguments are present (the preprocessor
723
+ * emits arrows for both unconditionally if the source declares them).
724
+ */
725
+ const extractAwaitBlocks = (source, container) => {
726
+ if (!container.view)
727
+ return;
728
+ const viewBody = container.view.body.body;
729
+ // Wrap the view body's expression in a synthetic file so we can
730
+ // traverse it the same way pass-9-codegen does. (Direct traversal
731
+ // of a bare Expression isn't supported by @babel/traverse.)
732
+ traverse(t.file(t.program([t.expressionStatement(viewBody)])), {
733
+ CallExpression(path) {
734
+ const callee = path.node.callee;
735
+ if (!t.isIdentifier(callee) || callee.name !== "__reactra_await__")
736
+ return;
737
+ // §B1 MAJOR-2: index via the shared AWAIT_ARG_ORDER (this extract-side
738
+ // reader feeds `awaitBlocks` to devtools/LSP/sidecar — it must agree with
739
+ // the Pass-9 + shadow await wiring, not carry its own magic positions).
740
+ const args = path.node.arguments;
741
+ const resourceArrow = args[AWAIT_ARG_ORDER.resource];
742
+ const pendingArrow = args[AWAIT_ARG_ORDER.pending];
743
+ const errorArrow = args[AWAIT_ARG_ORDER.error];
744
+ if (!t.isArrowFunctionExpression(resourceArrow))
745
+ return;
746
+ // Resource expression text — the body of the first arrow.
747
+ // Slice from the original source via the node's start/end
748
+ // (sliceSource tolerates missing positions by returning "").
749
+ const resourceName = sliceSource(source, resourceArrow.body).trim();
750
+ if (resourceName.length === 0)
751
+ return;
752
+ const block = {
753
+ kind: "await-block",
754
+ resourceName,
755
+ hasPending: t.isArrowFunctionExpression(pendingArrow),
756
+ hasError: t.isArrowFunctionExpression(errorArrow),
757
+ };
758
+ container.awaitBlocks.push(block);
759
+ },
760
+ });
761
+ };
762
+ //# sourceMappingURL=pass-2-extract.js.map