graphddb 0.6.0 → 0.7.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.
@@ -0,0 +1,982 @@
1
+ // src/transform/prepared-transform.ts
2
+ import ts from "typescript";
3
+ var HOIST_SLOT_PREFIX = "__gddbPrepared";
4
+ var SLOT_NAME_RE = /^__gddbPrepared\d+$/;
5
+ function collectModuleBindings(sf) {
6
+ const out = /* @__PURE__ */ new Map();
7
+ for (const stmt of sf.statements) {
8
+ if (ts.isImportDeclaration(stmt) && stmt.importClause) {
9
+ const clause = stmt.importClause;
10
+ if (clause.name) {
11
+ out.set(clause.name.text, {
12
+ kind: "import",
13
+ importedName: void 0,
14
+ namespace: false
15
+ });
16
+ }
17
+ const bindings = clause.namedBindings;
18
+ if (bindings) {
19
+ if (ts.isNamespaceImport(bindings)) {
20
+ out.set(bindings.name.text, {
21
+ kind: "import",
22
+ importedName: void 0,
23
+ namespace: true
24
+ });
25
+ } else {
26
+ for (const spec of bindings.elements) {
27
+ out.set(spec.name.text, {
28
+ kind: "import",
29
+ importedName: (spec.propertyName ?? spec.name).text,
30
+ namespace: false
31
+ });
32
+ }
33
+ }
34
+ }
35
+ } else if (ts.isVariableStatement(stmt)) {
36
+ const isConst = (stmt.declarationList.flags & ts.NodeFlags.Const) !== 0;
37
+ for (const decl of stmt.declarationList.declarations) {
38
+ if (ts.isIdentifier(decl.name)) {
39
+ out.set(
40
+ decl.name.text,
41
+ isConst ? { kind: "const", initializer: decl.initializer } : { kind: "mutable" }
42
+ );
43
+ } else {
44
+ for (const name of bindingNames(decl.name)) {
45
+ out.set(
46
+ name,
47
+ isConst ? { kind: "const", initializer: void 0 } : { kind: "mutable" }
48
+ );
49
+ }
50
+ }
51
+ }
52
+ } else if (ts.isClassDeclaration(stmt) && stmt.name) {
53
+ out.set(stmt.name.text, { kind: "class", node: stmt });
54
+ } else if (ts.isFunctionDeclaration(stmt) && stmt.name) {
55
+ out.set(stmt.name.text, { kind: "function", node: stmt });
56
+ } else if (ts.isEnumDeclaration(stmt)) {
57
+ out.set(stmt.name.text, { kind: "enum" });
58
+ }
59
+ }
60
+ return out;
61
+ }
62
+ function bindingNames(name) {
63
+ if (ts.isIdentifier(name)) return [name.text];
64
+ const out = [];
65
+ for (const el of name.elements) {
66
+ if (ts.isBindingElement(el)) out.push(...bindingNames(el.name));
67
+ }
68
+ return out;
69
+ }
70
+ function isDDBModelRef(expr, bindings, seen = /* @__PURE__ */ new Set()) {
71
+ const e = unwrapExpr(expr);
72
+ if (ts.isIdentifier(e)) {
73
+ const name = e.text;
74
+ if (seen.has(name)) return false;
75
+ seen.add(name);
76
+ const b = bindings.get(name);
77
+ if (!b) return false;
78
+ if (b.kind === "import") return b.importedName === "DDBModel";
79
+ if (b.kind === "const" && b.initializer)
80
+ return isDDBModelRef(b.initializer, bindings, seen);
81
+ if (b.kind === "class") {
82
+ const heritage = b.node.heritageClauses?.find(
83
+ (h) => h.token === ts.SyntaxKind.ExtendsKeyword
84
+ );
85
+ const base = heritage?.types[0]?.expression;
86
+ return base !== void 0 && isDDBModelRef(base, bindings, seen);
87
+ }
88
+ return false;
89
+ }
90
+ if (ts.isPropertyAccessExpression(e) && e.name.text === "DDBModel") {
91
+ const root = unwrapExpr(e.expression);
92
+ if (ts.isIdentifier(root)) {
93
+ const b = bindings.get(root.text);
94
+ return b?.kind === "import" && b.namespace;
95
+ }
96
+ }
97
+ return false;
98
+ }
99
+ function unwrapExpr(e) {
100
+ let cur = e;
101
+ for (; ; ) {
102
+ if (ts.isParenthesizedExpression(cur)) cur = cur.expression;
103
+ else if (ts.isAsExpression(cur)) cur = cur.expression;
104
+ else if (ts.isSatisfiesExpression(cur)) cur = cur.expression;
105
+ else if (ts.isNonNullExpression(cur)) cur = cur.expression;
106
+ else if (ts.isTypeAssertionExpression(cur)) cur = cur.expression;
107
+ else return cur;
108
+ }
109
+ }
110
+ function isFunctionLikeScope(n) {
111
+ return ts.isFunctionDeclaration(n) || ts.isFunctionExpression(n) || ts.isArrowFunction(n) || ts.isMethodDeclaration(n) || ts.isConstructorDeclaration(n) || ts.isGetAccessorDeclaration(n) || ts.isSetAccessorDeclaration(n);
112
+ }
113
+ function scopeDeclares(scope, name) {
114
+ if (isFunctionLikeScope(scope)) {
115
+ const fn = scope;
116
+ for (const p of fn.parameters) {
117
+ if (bindingNames(p.name).includes(name)) return true;
118
+ }
119
+ if (fn.body && ts.isBlock(fn.body) && blockDeclares(fn.body, name)) return true;
120
+ if ((ts.isFunctionExpression(scope) || ts.isFunctionDeclaration(scope)) && scope.name?.text === name) {
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+ if (ts.isBlock(scope)) return blockDeclares(scope, name);
126
+ if (ts.isCatchClause(scope)) {
127
+ return scope.variableDeclaration !== void 0 && bindingNames(scope.variableDeclaration.name).includes(name);
128
+ }
129
+ if (ts.isForStatement(scope) || ts.isForOfStatement(scope) || ts.isForInStatement(scope)) {
130
+ const init = scope.initializer;
131
+ if (init && ts.isVariableDeclarationList(init)) {
132
+ for (const d of init.declarations) {
133
+ if (bindingNames(d.name).includes(name)) return true;
134
+ }
135
+ }
136
+ return false;
137
+ }
138
+ return false;
139
+ }
140
+ function blockDeclares(block, name) {
141
+ for (const stmt of block.statements) {
142
+ if (ts.isVariableStatement(stmt)) {
143
+ for (const d of stmt.declarationList.declarations) {
144
+ if (bindingNames(d.name).includes(name)) return true;
145
+ }
146
+ } else if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && stmt.name?.text === name) {
147
+ return true;
148
+ }
149
+ }
150
+ return false;
151
+ }
152
+ function resolveIdentifier(id, bodyFn, bindings) {
153
+ const name = id.text;
154
+ let node = id;
155
+ while (node.parent) {
156
+ const parent = node.parent;
157
+ if (isFunctionLikeScope(parent) || ts.isBlock(parent) || ts.isCatchClause(parent) || ts.isForStatement(parent) || ts.isForOfStatement(parent) || ts.isForInStatement(parent)) {
158
+ if (scopeDeclares(parent, name)) {
159
+ if (bodyFn && isInside(parent, bodyFn)) return { at: "body-local" };
160
+ if (isFunctionLikeScope(parent) || hasFunctionAncestorBelowModule(parent)) {
161
+ return { at: "enclosing-function" };
162
+ }
163
+ return { at: "module", binding: { kind: "const", initializer: void 0 } };
164
+ }
165
+ }
166
+ node = parent;
167
+ }
168
+ const b = bindings.get(name);
169
+ if (b) return { at: "module", binding: b };
170
+ return { at: "unresolved" };
171
+ }
172
+ function isInside(node, ancestor) {
173
+ let cur = node;
174
+ while (cur) {
175
+ if (cur === ancestor) return true;
176
+ cur = cur.parent;
177
+ }
178
+ return false;
179
+ }
180
+ function hasFunctionAncestorBelowModule(node) {
181
+ let cur = node.parent;
182
+ while (cur && !ts.isSourceFile(cur)) {
183
+ if (isFunctionLikeScope(cur)) return true;
184
+ cur = cur.parent;
185
+ }
186
+ return false;
187
+ }
188
+ function report(ctx, node, code, message, category = "error") {
189
+ const { line, character } = ctx.sf.getLineAndCharacterOfPosition(node.getStart(ctx.sf));
190
+ ctx.diagnostics.push({
191
+ code,
192
+ category,
193
+ message,
194
+ file: ctx.fileName,
195
+ line: line + 1,
196
+ column: character + 1
197
+ });
198
+ }
199
+ function lintPreparedBody(ctx, fn) {
200
+ const before = countErrors(ctx.diagnostics);
201
+ if (fn.parameters.length > 1) {
202
+ report(
203
+ ctx,
204
+ fn.parameters[1],
205
+ "impure-expression",
206
+ "A prepared body takes exactly one parameter (the `$` placeholder)."
207
+ );
208
+ return countErrors(ctx.diagnostics) - before;
209
+ }
210
+ const paramNames = /* @__PURE__ */ new Set();
211
+ const destructured = /* @__PURE__ */ new Set();
212
+ const param = fn.parameters[0];
213
+ if (param) {
214
+ if (ts.isIdentifier(param.name)) {
215
+ paramNames.add(param.name.text);
216
+ } else if (ts.isObjectBindingPattern(param.name)) {
217
+ for (const el of param.name.elements) {
218
+ if (!ts.isBindingElement(el) || !ts.isIdentifier(el.name) || el.initializer !== void 0 || el.dotDotDotToken !== void 0 || el.propertyName !== void 0 && !ts.isIdentifier(el.propertyName)) {
219
+ report(
220
+ ctx,
221
+ el,
222
+ "impure-expression",
223
+ "Only a plain, single-level destructuring of the `$` placeholder is supported (no nesting, defaults, or rest)."
224
+ );
225
+ } else {
226
+ destructured.add(el.name.text);
227
+ }
228
+ }
229
+ } else {
230
+ report(
231
+ ctx,
232
+ param,
233
+ "impure-expression",
234
+ "The `$` parameter must be a plain identifier or a single-level object destructuring."
235
+ );
236
+ }
237
+ }
238
+ const env = { bodyFn: fn, paramNames, destructuredParamNames: destructured };
239
+ const body = fn.body;
240
+ if (body !== void 0) {
241
+ if (ts.isBlock(body)) lintBodyBlock(ctx, body, env);
242
+ else checkExpr(ctx, body, env);
243
+ }
244
+ return countErrors(ctx.diagnostics) - before;
245
+ }
246
+ function countErrors(diags) {
247
+ return diags.reduce((n, d) => n + (d.category === "error" ? 1 : 0), 0);
248
+ }
249
+ function lintBodyBlock(ctx, block, env) {
250
+ for (const stmt of block.statements) {
251
+ if (ts.isReturnStatement(stmt)) {
252
+ if (stmt.expression) checkExpr(ctx, stmt.expression, env);
253
+ continue;
254
+ }
255
+ if (ts.isVariableStatement(stmt) && (stmt.declarationList.flags & ts.NodeFlags.Const) !== 0) {
256
+ for (const d of stmt.declarationList.declarations) {
257
+ if (d.initializer) checkExpr(ctx, d.initializer, env);
258
+ }
259
+ continue;
260
+ }
261
+ report(
262
+ ctx,
263
+ stmt,
264
+ "impure-expression",
265
+ "A prepared body block may contain only `const` declarations and `return` statements \u2014 any other statement is per-call control flow, which breaks compile-time hoisting."
266
+ );
267
+ }
268
+ }
269
+ function checkExpr(ctx, node, env) {
270
+ const e = unwrapExpr(node);
271
+ if (ts.isStringLiteral(e) || ts.isNumericLiteral(e) || ts.isBigIntLiteral(e) || ts.isNoSubstitutionTemplateLiteral(e) || e.kind === ts.SyntaxKind.TrueKeyword || e.kind === ts.SyntaxKind.FalseKeyword || e.kind === ts.SyntaxKind.NullKeyword) {
272
+ return;
273
+ }
274
+ if (ts.isPrefixUnaryExpression(e) && (e.operator === ts.SyntaxKind.MinusToken || e.operator === ts.SyntaxKind.PlusToken) && ts.isNumericLiteral(e.operand)) {
275
+ return;
276
+ }
277
+ if (ts.isObjectLiteralExpression(e)) {
278
+ for (const prop of e.properties) {
279
+ if (ts.isPropertyAssignment(prop)) {
280
+ if (ts.isComputedPropertyName(prop.name)) {
281
+ report(
282
+ ctx,
283
+ prop.name,
284
+ "impure-expression",
285
+ "A computed property name is per-call control flow \u2014 a prepared template needs statically known keys."
286
+ );
287
+ }
288
+ checkExpr(ctx, prop.initializer, env);
289
+ } else if (ts.isShorthandPropertyAssignment(prop)) {
290
+ checkIdentifierRef(ctx, prop.name, env);
291
+ } else if (ts.isSpreadAssignment(prop)) {
292
+ checkExpr(ctx, prop.expression, env);
293
+ } else {
294
+ report(
295
+ ctx,
296
+ prop,
297
+ "impure-expression",
298
+ "Only plain property assignments are supported in a prepared template."
299
+ );
300
+ }
301
+ }
302
+ return;
303
+ }
304
+ if (ts.isArrayLiteralExpression(e)) {
305
+ for (const el of e.elements) {
306
+ if (ts.isSpreadElement(el)) checkExpr(ctx, el.expression, env);
307
+ else checkExpr(ctx, el, env);
308
+ }
309
+ return;
310
+ }
311
+ if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) {
312
+ if (e.parameters.length !== 0) {
313
+ report(
314
+ ctx,
315
+ e,
316
+ "impure-expression",
317
+ "A function inside a prepared template is only valid as a zero-parameter model ref (`() => Model`)."
318
+ );
319
+ return;
320
+ }
321
+ const refExpr = thunkResultExpression(e);
322
+ if (refExpr === void 0) {
323
+ report(
324
+ ctx,
325
+ e,
326
+ "impure-expression",
327
+ "A model-ref thunk must be a single expression (or single `return`) referencing a module-static model."
328
+ );
329
+ return;
330
+ }
331
+ checkStaticRefChain(ctx, refExpr, env);
332
+ return;
333
+ }
334
+ if (ts.isIdentifier(e)) {
335
+ checkIdentifierRef(ctx, e, env);
336
+ return;
337
+ }
338
+ if (ts.isPropertyAccessExpression(e)) {
339
+ checkPropertyChain(ctx, e, env);
340
+ return;
341
+ }
342
+ if (ts.isCallExpression(e) || ts.isNewExpression(e)) {
343
+ report(
344
+ ctx,
345
+ e,
346
+ "call-in-body",
347
+ "A call inside a prepared body is an indirect runtime-capture channel (the lint cannot see through it, and even a global like `Date.now()` is a fresh per-call value). Inline the result as a static literal, bind it via `$`, or extract a module-scope `const` template."
348
+ );
349
+ return;
350
+ }
351
+ if (e.kind === ts.SyntaxKind.ThisKeyword) {
352
+ report(
353
+ ctx,
354
+ e,
355
+ "runtime-capture",
356
+ "`this` inside a prepared body captures the per-call receiver. Bind the value via `$` instead."
357
+ );
358
+ return;
359
+ }
360
+ if (ts.isTemplateExpression(e)) {
361
+ report(
362
+ ctx,
363
+ e,
364
+ "impure-expression",
365
+ "A template literal with substitutions embeds a runtime value into the template. Bind the value via `$` (the runtime binds whole fields, never string fragments)."
366
+ );
367
+ return;
368
+ }
369
+ report(
370
+ ctx,
371
+ e,
372
+ "impure-expression",
373
+ `Unsupported expression (${ts.SyntaxKind[e.kind]}) in a prepared body \u2014 only object/array literals, static literals, \`$.<name>\` refs, module-static references, and \`() => Model\` thunks are declarative.`
374
+ );
375
+ }
376
+ function thunkResultExpression(fn) {
377
+ if (ts.isArrowFunction(fn) && !ts.isBlock(fn.body)) return fn.body;
378
+ const body = fn.body;
379
+ if (body && ts.isBlock(body) && body.statements.length === 1) {
380
+ const only = body.statements[0];
381
+ if (ts.isReturnStatement(only) && only.expression) return only.expression;
382
+ }
383
+ return void 0;
384
+ }
385
+ function checkStaticRefChain(ctx, expr, env) {
386
+ const e = unwrapExpr(expr);
387
+ if (ts.isIdentifier(e)) {
388
+ checkIdentifierRef(
389
+ ctx,
390
+ e,
391
+ env,
392
+ /* refOnly */
393
+ true
394
+ );
395
+ return;
396
+ }
397
+ if (ts.isPropertyAccessExpression(e)) {
398
+ checkPropertyChain(
399
+ ctx,
400
+ e,
401
+ env,
402
+ /* refOnly */
403
+ true
404
+ );
405
+ return;
406
+ }
407
+ report(
408
+ ctx,
409
+ e,
410
+ "impure-expression",
411
+ "A model ref must be a plain reference to a module-static model (`() => Model` / `() => ns.Model`)."
412
+ );
413
+ }
414
+ function checkIdentifierRef(ctx, id, env, refOnly = false) {
415
+ const name = id.text;
416
+ if (name === "undefined") {
417
+ report(
418
+ ctx,
419
+ id,
420
+ "non-static-reference",
421
+ "`undefined` is not a bindable template value \u2014 omit the field or bind it via `$`."
422
+ );
423
+ return;
424
+ }
425
+ if (env.paramNames.has(name)) {
426
+ report(
427
+ ctx,
428
+ id,
429
+ "impure-expression",
430
+ "The `$` placeholder itself is not a value \u2014 bind a specific param via `$.<name>`."
431
+ );
432
+ return;
433
+ }
434
+ if (env.destructuredParamNames.has(name)) return;
435
+ const res = resolveIdentifier(id, env.bodyFn, ctx.bindings);
436
+ switch (res.at) {
437
+ case "body-local":
438
+ return;
439
+ case "enclosing-function":
440
+ report(
441
+ ctx,
442
+ id,
443
+ "runtime-capture",
444
+ `'${name}' is declared in an enclosing function \u2014 a per-call runtime value captured into the prepared body. Bind it via \`$\` (a per-call param) or move it to a module-scope \`const\`.`
445
+ );
446
+ return;
447
+ case "unresolved":
448
+ report(
449
+ ctx,
450
+ id,
451
+ "non-static-reference",
452
+ `'${name}' does not resolve to \`$\` or a module-static binding in this file. Globals are per-call runtime state; a prepared body may reference only \`$\` and module-static values.`
453
+ );
454
+ return;
455
+ case "module":
456
+ checkModuleBindingUse(ctx, id, name, res.binding, refOnly);
457
+ return;
458
+ }
459
+ }
460
+ function checkModuleBindingUse(ctx, at, name, binding, refOnly) {
461
+ switch (binding.kind) {
462
+ case "mutable":
463
+ report(
464
+ ctx,
465
+ at,
466
+ "mutable-module-state",
467
+ `'${name}' is module-scope \`let\`/\`var\` \u2014 mutable module state. A hoisted body would freeze its value while an unhoisted one would not; results must not depend on placement. Make it a \`const\` or bind it via \`$\`.`
468
+ );
469
+ return;
470
+ case "import":
471
+ case "class":
472
+ case "enum":
473
+ return;
474
+ // module-static by construction (see the documented limits).
475
+ case "function":
476
+ if (binding.node.parameters.length === 0) {
477
+ const result = binding.node.body ? functionSingleReturn(binding.node) : void 0;
478
+ if (result !== void 0) {
479
+ checkStaticRefChain(
480
+ ctx,
481
+ result,
482
+ // Module context: no `$`, no body scope.
483
+ { bodyFn: void 0, paramNames: /* @__PURE__ */ new Set(), destructuredParamNames: /* @__PURE__ */ new Set() }
484
+ );
485
+ return;
486
+ }
487
+ }
488
+ if (!refOnly) return;
489
+ report(
490
+ ctx,
491
+ at,
492
+ "impure-expression",
493
+ `'${name}' is not a zero-parameter thunk returning a module-static reference \u2014 a model ref must be statically resolvable.`
494
+ );
495
+ return;
496
+ case "const": {
497
+ if (binding.initializer === void 0) return;
498
+ if (ctx.visitedConsts.has(name)) return;
499
+ ctx.visitedConsts.add(name);
500
+ checkExpr(ctx, binding.initializer, {
501
+ bodyFn: void 0,
502
+ paramNames: /* @__PURE__ */ new Set(),
503
+ destructuredParamNames: /* @__PURE__ */ new Set()
504
+ });
505
+ return;
506
+ }
507
+ }
508
+ }
509
+ function functionSingleReturn(fn) {
510
+ const body = fn.body;
511
+ if (body && body.statements.length === 1) {
512
+ const only = body.statements[0];
513
+ if (ts.isReturnStatement(only) && only.expression) return only.expression;
514
+ }
515
+ return void 0;
516
+ }
517
+ function checkPropertyChain(ctx, e, env, refOnly = false) {
518
+ let levels = 0;
519
+ let cur = e;
520
+ while (ts.isPropertyAccessExpression(cur)) {
521
+ levels += 1;
522
+ cur = unwrapExpr(cur.expression);
523
+ }
524
+ if (ts.isIdentifier(cur)) {
525
+ const rootName = cur.text;
526
+ if (env.paramNames.has(rootName)) {
527
+ if (levels > 1) {
528
+ report(
529
+ ctx,
530
+ e,
531
+ "param-deep-access",
532
+ "Only a direct `$.<name>` read is supported \u2014 nested access on a param is a per-call transform the runtime rejects too."
533
+ );
534
+ }
535
+ return;
536
+ }
537
+ if (env.destructuredParamNames.has(rootName)) {
538
+ report(
539
+ ctx,
540
+ e,
541
+ "param-deep-access",
542
+ "A destructured `$` param is already the bound value \u2014 nested access on it is a per-call transform."
543
+ );
544
+ return;
545
+ }
546
+ checkIdentifierRef(ctx, cur, env, refOnly);
547
+ return;
548
+ }
549
+ if (cur.kind === ts.SyntaxKind.ThisKeyword) {
550
+ report(
551
+ ctx,
552
+ e,
553
+ "runtime-capture",
554
+ "`this` inside a prepared body captures the per-call receiver. Bind the value via `$` instead."
555
+ );
556
+ return;
557
+ }
558
+ report(
559
+ ctx,
560
+ e,
561
+ "impure-expression",
562
+ "A property chain in a prepared body must be rooted at `$` or a module-static reference."
563
+ );
564
+ }
565
+ function isPerCallSite(call) {
566
+ let cur = call.parent;
567
+ while (cur && !ts.isSourceFile(cur)) {
568
+ if (isFunctionLikeScope(cur)) return true;
569
+ if (ts.isPropertyDeclaration(cur) && !cur.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword)) {
570
+ return true;
571
+ }
572
+ cur = cur.parent;
573
+ }
574
+ return false;
575
+ }
576
+ function isAlreadyHoisted(call) {
577
+ const parent = call.parent;
578
+ return parent !== void 0 && ts.isBinaryExpression(parent) && parent.operatorToken.kind === ts.SyntaxKind.QuestionQuestionEqualsToken && ts.isIdentifier(parent.left) && SLOT_NAME_RE.test(parent.left.text) && parent.right === call;
579
+ }
580
+ function transformPreparedSource(fileName, sourceText, options = {}) {
581
+ const mode = options.mode ?? "transform";
582
+ const scriptKind = fileName.endsWith(".tsx") ? ts.ScriptKind.TSX : fileName.endsWith(".jsx") ? ts.ScriptKind.JSX : ts.ScriptKind.TS;
583
+ const sf = ts.createSourceFile(
584
+ fileName,
585
+ sourceText,
586
+ ts.ScriptTarget.Latest,
587
+ /* setParentNodes */
588
+ true,
589
+ scriptKind
590
+ );
591
+ const bindings = collectModuleBindings(sf);
592
+ const ctx = {
593
+ sf,
594
+ fileName,
595
+ bindings,
596
+ diagnostics: [],
597
+ visitedConsts: /* @__PURE__ */ new Set()
598
+ };
599
+ const calls = [];
600
+ const visit = (node) => {
601
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "prepare" && isDDBModelRef(node.expression.expression, bindings)) {
602
+ let bodyFn;
603
+ let indirect = false;
604
+ const arg = node.arguments.length === 1 ? unwrapExpr(node.arguments[0]) : void 0;
605
+ if (arg && (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg))) {
606
+ bodyFn = arg;
607
+ } else if (arg && ts.isIdentifier(arg)) {
608
+ const res = resolveIdentifier(arg, void 0, bindings);
609
+ if (res.at === "module") {
610
+ const b = res.binding;
611
+ if (b.kind === "const" && b.initializer) {
612
+ const init = unwrapExpr(b.initializer);
613
+ if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
614
+ bodyFn = init;
615
+ indirect = true;
616
+ }
617
+ } else if (b.kind === "function") {
618
+ bodyFn = b.node;
619
+ indirect = true;
620
+ }
621
+ }
622
+ }
623
+ calls.push({ call: node, bodyFn, bodyIsIndirect: indirect });
624
+ }
625
+ ts.forEachChild(node, visit);
626
+ };
627
+ visit(sf);
628
+ const edits = [];
629
+ const slotNames = [];
630
+ let hoistedCount = 0;
631
+ let moduleScopeCount = 0;
632
+ let alreadyHoistedCount = 0;
633
+ let slotIndex = 0;
634
+ for (const m of sourceText.matchAll(/__gddbPrepared(\d+)\b/g)) {
635
+ slotIndex = Math.max(slotIndex, Number(m[1]));
636
+ }
637
+ for (const { call, bodyFn } of calls) {
638
+ if (bodyFn === void 0) {
639
+ report(
640
+ ctx,
641
+ call,
642
+ "unverifiable-body",
643
+ "The `prepare` argument is not a statically visible function (an inline arrow/function or a module-scope `const` body). The no-runtime-capture lint cannot verify it \u2014 pass the body inline or as a module-scope const."
644
+ );
645
+ continue;
646
+ }
647
+ const bodyErrors = lintPreparedBody(ctx, bodyFn);
648
+ if (bodyErrors > 0) continue;
649
+ if (isAlreadyHoisted(call)) {
650
+ alreadyHoistedCount += 1;
651
+ continue;
652
+ }
653
+ if (!isPerCallSite(call)) {
654
+ moduleScopeCount += 1;
655
+ continue;
656
+ }
657
+ if (mode === "lint") {
658
+ report(
659
+ ctx,
660
+ call,
661
+ "hoistable",
662
+ "Inline `DDBModel.prepare` call site \u2014 `graphddb transform prepared --write` normalizes it to a module-scope prepared slot (zero per-op overhead). Untransformed it falls back to the runtime structural-memoization path.",
663
+ "note"
664
+ );
665
+ continue;
666
+ }
667
+ slotIndex += 1;
668
+ const slot = `${HOIST_SLOT_PREFIX}${slotIndex}`;
669
+ slotNames.push(slot);
670
+ edits.push({ pos: call.getStart(sf), insert: `(${slot} ??= ` });
671
+ edits.push({ pos: call.getEnd(), insert: `)` });
672
+ hoistedCount += 1;
673
+ }
674
+ const errorCount = countErrors(ctx.diagnostics);
675
+ if (errorCount > 0) {
676
+ return {
677
+ fileName,
678
+ text: sourceText,
679
+ changed: false,
680
+ hoistedCount: 0,
681
+ moduleScopeCount,
682
+ alreadyHoistedCount,
683
+ diagnostics: ctx.diagnostics,
684
+ errorCount
685
+ };
686
+ }
687
+ let text = sourceText;
688
+ if (hoistedCount > 0) {
689
+ let declPos = 0;
690
+ for (const stmt of sf.statements) {
691
+ if (ts.isImportDeclaration(stmt)) declPos = stmt.getEnd();
692
+ }
693
+ const nl = sourceText.includes("\r\n") ? "\r\n" : "\n";
694
+ const header = `${nl}${nl}// graphddb transform prepared (#206): module-scope lazy slots \u2014 each${nl}// prepared statement compiles once per module and every later call is a${nl}// plain \`.execute\` (zero per-op planning / hashing).` + slotNames.map((s) => `${nl}let ${s};`).join("");
695
+ edits.push({ pos: declPos, insert: declPos === 0 ? header.trimStart() + nl : header });
696
+ edits.sort((a, b) => b.pos - a.pos);
697
+ for (const e of edits) {
698
+ text = text.slice(0, e.pos) + e.insert + text.slice(e.pos);
699
+ }
700
+ }
701
+ return {
702
+ fileName,
703
+ text,
704
+ changed: text !== sourceText,
705
+ hoistedCount,
706
+ moduleScopeCount,
707
+ alreadyHoistedCount,
708
+ diagnostics: ctx.diagnostics,
709
+ errorCount: 0
710
+ };
711
+ }
712
+ function lintPreparedSource(fileName, sourceText) {
713
+ return transformPreparedSource(fileName, sourceText, { mode: "lint" }).diagnostics;
714
+ }
715
+ var AOT_LOADER_LOCAL = "__gddbLoadPreparedPlan";
716
+ var AOT_PLANS_LOCAL = "__gddbPreparedPlans";
717
+ var AOT_SHIM_EXPORT = "__gddbAotBodies";
718
+ function referencedIdentifierNames(node, out) {
719
+ const visit = (n) => {
720
+ if (ts.isIdentifier(n)) {
721
+ const parent = n.parent;
722
+ if (parent !== void 0) {
723
+ if (ts.isPropertyAccessExpression(parent) && parent.name === n) return;
724
+ if (ts.isQualifiedName(parent) && parent.right === n) return;
725
+ if ((ts.isClassDeclaration(parent) || ts.isFunctionDeclaration(parent) || ts.isEnumDeclaration(parent) || ts.isVariableDeclaration(parent) || ts.isParameter(parent) || ts.isMethodDeclaration(parent) || ts.isPropertyDeclaration(parent) || ts.isGetAccessorDeclaration(parent) || ts.isSetAccessorDeclaration(parent) || ts.isBindingElement(parent)) && parent.name === n) {
726
+ return;
727
+ }
728
+ if (ts.isPropertyAssignment(parent) && parent.name === n) return;
729
+ if (ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent)) return;
730
+ }
731
+ out.add(n.text);
732
+ return;
733
+ }
734
+ ts.forEachChild(n, visit);
735
+ };
736
+ visit(node);
737
+ }
738
+ function collectShimDeps(roots, bindings) {
739
+ const referenced = /* @__PURE__ */ new Set();
740
+ for (const root of roots) referencedIdentifierNames(root, referenced);
741
+ const deps = /* @__PURE__ */ new Set();
742
+ const queue = [...referenced];
743
+ while (queue.length > 0) {
744
+ const name = queue.pop();
745
+ if (deps.has(name)) continue;
746
+ const binding = bindings.get(name);
747
+ if (binding === void 0) continue;
748
+ deps.add(name);
749
+ const nested = /* @__PURE__ */ new Set();
750
+ if (binding.kind === "const" && binding.initializer !== void 0) {
751
+ referencedIdentifierNames(binding.initializer, nested);
752
+ } else if (binding.kind === "class" || binding.kind === "function") {
753
+ referencedIdentifierNames(binding.node, nested);
754
+ }
755
+ for (const n of nested) {
756
+ if (!deps.has(n)) queue.push(n);
757
+ }
758
+ }
759
+ return deps;
760
+ }
761
+ function statementBoundNames(stmt) {
762
+ const names = [];
763
+ if (ts.isImportDeclaration(stmt) && stmt.importClause) {
764
+ const clause = stmt.importClause;
765
+ if (clause.name) names.push(clause.name.text);
766
+ const b = clause.namedBindings;
767
+ if (b) {
768
+ if (ts.isNamespaceImport(b)) names.push(b.name.text);
769
+ else for (const spec of b.elements) names.push(spec.name.text);
770
+ }
771
+ } else if (ts.isVariableStatement(stmt)) {
772
+ for (const d of stmt.declarationList.declarations) names.push(...bindingNames(d.name));
773
+ } else if ((ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt) || ts.isEnumDeclaration(stmt)) && stmt.name !== void 0) {
774
+ names.push(stmt.name.text);
775
+ }
776
+ return names;
777
+ }
778
+ function transformPreparedSourceAot(fileName, sourceText, options) {
779
+ const scriptKind = fileName.endsWith(".tsx") ? ts.ScriptKind.TSX : fileName.endsWith(".jsx") ? ts.ScriptKind.JSX : ts.ScriptKind.TS;
780
+ const sf = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true, scriptKind);
781
+ const bindings = collectModuleBindings(sf);
782
+ const ctx = { sf, fileName, bindings, diagnostics: [], visitedConsts: /* @__PURE__ */ new Set() };
783
+ const detected = [];
784
+ const visit = (node) => {
785
+ if (ts.isCallExpression(node)) {
786
+ if (ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "prepare" && isDDBModelRef(node.expression.expression, bindings)) {
787
+ const arg = node.arguments.length >= 1 ? node.arguments[0] : void 0;
788
+ const resolved = arg !== void 0 ? resolveBodyFn(arg, bindings) : void 0;
789
+ if (arg !== void 0) {
790
+ detected.push({ call: node, form: "prepare", bodyArg: arg, bodyFn: resolved });
791
+ } else {
792
+ report(ctx, node, "unverifiable-body", "A `prepare` call needs a body argument.");
793
+ }
794
+ } else if (ts.isIdentifier(node.expression) && node.expression.text === AOT_LOADER_LOCAL) {
795
+ if (node.arguments.length !== 3 || !ts.isStringLiteral(node.arguments[1])) {
796
+ report(
797
+ ctx,
798
+ node,
799
+ "unverifiable-body",
800
+ `A '${AOT_LOADER_LOCAL}' call must have the generated shape (document, '<plan id>', body). Regenerate with --aot --write.`
801
+ );
802
+ } else {
803
+ const arg = node.arguments[2];
804
+ detected.push({
805
+ call: node,
806
+ form: "aot",
807
+ bodyArg: arg,
808
+ bodyFn: resolveBodyFn(arg, bindings),
809
+ idLiteral: node.arguments[1]
810
+ });
811
+ }
812
+ }
813
+ }
814
+ ts.forEachChild(node, visit);
815
+ };
816
+ visit(sf);
817
+ const edits = [];
818
+ const slotNames = [];
819
+ const sites = [];
820
+ const verifiedBodies = [];
821
+ let slotIndex = 0;
822
+ for (const m of sourceText.matchAll(/__gddbPrepared(\d+)\b/g)) {
823
+ slotIndex = Math.max(slotIndex, Number(m[1]));
824
+ }
825
+ let needsHeaderImports = false;
826
+ let index = 0;
827
+ for (const site of detected) {
828
+ index += 1;
829
+ const planId = `${options.planIdPrefix}#${index}`;
830
+ if (site.bodyFn === void 0) {
831
+ report(
832
+ ctx,
833
+ site.call,
834
+ "unverifiable-body",
835
+ "The prepared body is not a statically visible function (an inline arrow/function or a module-scope `const` body) \u2014 it cannot be verified or compiled at build time."
836
+ );
837
+ continue;
838
+ }
839
+ const bodyErrors = lintPreparedBody(ctx, site.bodyFn);
840
+ if (bodyErrors > 0) continue;
841
+ sites.push({ index, planId, form: site.form });
842
+ verifiedBodies.push({ index, bodyArg: site.bodyArg, bodyFn: site.bodyFn });
843
+ if (options.mode === "check") {
844
+ if (site.form === "prepare") {
845
+ report(
846
+ ctx,
847
+ site.call,
848
+ "aot-pending",
849
+ `Call site not yet rewritten to the static prepared plan '${planId}' \u2014 run \`graphddb transform prepared --aot <artifact> --write\`.`,
850
+ "note"
851
+ );
852
+ }
853
+ continue;
854
+ }
855
+ if (site.form === "prepare") {
856
+ needsHeaderImports = true;
857
+ edits.push({
858
+ pos: site.call.getStart(sf),
859
+ end: site.bodyArg.getStart(sf),
860
+ insert: `${AOT_LOADER_LOCAL}(${AOT_PLANS_LOCAL}, ${JSON.stringify(planId)}, `
861
+ });
862
+ if (isPerCallSite(site.call) && !isAlreadyHoisted(site.call)) {
863
+ slotIndex += 1;
864
+ const slot = `${HOIST_SLOT_PREFIX}${slotIndex}`;
865
+ slotNames.push(slot);
866
+ edits.push({ pos: site.call.getStart(sf), end: site.call.getStart(sf), insert: `(${slot} ??= ` });
867
+ edits.push({ pos: site.call.getEnd(), end: site.call.getEnd(), insert: `)` });
868
+ }
869
+ } else if (site.idLiteral !== void 0 && site.idLiteral.text !== planId) {
870
+ edits.push({
871
+ pos: site.idLiteral.getStart(sf),
872
+ end: site.idLiteral.getEnd(),
873
+ insert: JSON.stringify(planId)
874
+ });
875
+ }
876
+ }
877
+ const errorCount = countErrors(ctx.diagnostics);
878
+ if (errorCount > 0) {
879
+ return {
880
+ fileName,
881
+ text: sourceText,
882
+ changed: false,
883
+ sites: [],
884
+ diagnostics: ctx.diagnostics,
885
+ errorCount
886
+ };
887
+ }
888
+ let shimSource;
889
+ if (verifiedBodies.length > 0) {
890
+ const deps = collectShimDeps(
891
+ verifiedBodies.flatMap((b) => [b.bodyFn, b.bodyArg]),
892
+ bindings
893
+ );
894
+ const parts = [
895
+ "// Generated by `graphddb transform prepared --aot` \u2014 build-time evaluation",
896
+ "// shim (auto-deleted). Carries ONLY the module-static declarations the",
897
+ "// prepared bodies reference; top-level side effects are NOT copied."
898
+ ];
899
+ for (const stmt of sf.statements) {
900
+ const bound = statementBoundNames(stmt);
901
+ if (bound.length === 0) continue;
902
+ const isDecl = ts.isImportDeclaration(stmt) || ts.isVariableStatement(stmt) || ts.isClassDeclaration(stmt) || ts.isFunctionDeclaration(stmt) || ts.isEnumDeclaration(stmt);
903
+ if (!isDecl) continue;
904
+ if (bound.some((n) => deps.has(n))) {
905
+ parts.push(sourceText.slice(stmt.getStart(sf), stmt.getEnd()));
906
+ }
907
+ }
908
+ for (const b of verifiedBodies) {
909
+ parts.push(
910
+ `const __gddbAotBody_${b.index} = (${sourceText.slice(
911
+ b.bodyArg.getStart(sf),
912
+ b.bodyArg.getEnd()
913
+ )});`
914
+ );
915
+ }
916
+ parts.push(
917
+ `export const ${AOT_SHIM_EXPORT} = {` + verifiedBodies.map((b) => ` ${b.index}: __gddbAotBody_${b.index},`).join("") + " };"
918
+ );
919
+ shimSource = parts.join("\n") + "\n";
920
+ }
921
+ let text = sourceText;
922
+ if (options.mode === "transform" && (edits.length > 0 || needsHeaderImports)) {
923
+ let header = "";
924
+ const nl = sourceText.includes("\r\n") ? "\r\n" : "\n";
925
+ if (needsHeaderImports && !bindings.has(AOT_LOADER_LOCAL)) {
926
+ header += `${nl}${nl}// graphddb transform prepared --aot (#208): static prepared plans \u2014 each${nl}// call site loads its build-time-compiled plan; the body stays in source as${nl}// the plan's compile source (drift regeneration) and typed fallback.${nl}import { loadPreparedPlan as ${AOT_LOADER_LOCAL} } from 'graphddb';${nl}import ${AOT_PLANS_LOCAL} from '${options.artifactImport}';`;
927
+ }
928
+ if (slotNames.length > 0) {
929
+ header += `${nl}${nl}// graphddb transform prepared (#206): module-scope lazy slots.` + slotNames.map((s) => `${nl}let ${s};`).join("");
930
+ }
931
+ if (header !== "") {
932
+ let declPos = 0;
933
+ for (const stmt of sf.statements) {
934
+ if (ts.isImportDeclaration(stmt)) declPos = stmt.getEnd();
935
+ }
936
+ edits.push({
937
+ pos: declPos,
938
+ end: declPos,
939
+ insert: declPos === 0 ? header.trimStart() + nl : header
940
+ });
941
+ }
942
+ edits.sort((a, b) => b.pos - a.pos || b.end - a.end);
943
+ for (const e of edits) {
944
+ text = text.slice(0, e.pos) + e.insert + text.slice(e.end);
945
+ }
946
+ }
947
+ return {
948
+ fileName,
949
+ text,
950
+ changed: text !== sourceText,
951
+ sites,
952
+ ...shimSource !== void 0 ? { shimSource } : {},
953
+ diagnostics: ctx.diagnostics,
954
+ errorCount: 0
955
+ };
956
+ }
957
+ function resolveBodyFn(arg, bindings) {
958
+ const e = unwrapExpr(arg);
959
+ if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) return e;
960
+ if (ts.isIdentifier(e)) {
961
+ const res = resolveIdentifier(e, void 0, bindings);
962
+ if (res.at === "module") {
963
+ const b = res.binding;
964
+ if (b.kind === "const" && b.initializer !== void 0) {
965
+ const init = unwrapExpr(b.initializer);
966
+ if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) return init;
967
+ } else if (b.kind === "function") {
968
+ return b.node;
969
+ }
970
+ }
971
+ }
972
+ return void 0;
973
+ }
974
+ export {
975
+ AOT_LOADER_LOCAL,
976
+ AOT_PLANS_LOCAL,
977
+ AOT_SHIM_EXPORT,
978
+ HOIST_SLOT_PREFIX,
979
+ lintPreparedSource,
980
+ transformPreparedSource,
981
+ transformPreparedSourceAot
982
+ };