@kronor/dtv 5.0.0 → 5.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 (44) hide show
  1. package/dist/assets/{index-BCuRRUo9.js → index-D2_2rYMS.js} +62 -62
  2. package/dist/cli/commands/init.js +106 -0
  3. package/dist/cli/commands/init.js.map +1 -0
  4. package/dist/cli/commands/typegen.js +26 -0
  5. package/dist/cli/commands/typegen.js.map +1 -0
  6. package/dist/cli/config/loadConfig.js +101 -0
  7. package/dist/cli/config/loadConfig.js.map +1 -0
  8. package/dist/cli/config/types.js +2 -0
  9. package/dist/cli/config/types.js.map +1 -0
  10. package/dist/cli/dtv.js +4 -504
  11. package/dist/cli/dtv.js.map +1 -1
  12. package/dist/cli/typegen/runTypegen.js +638 -0
  13. package/dist/cli/typegen/runTypegen.js.map +1 -0
  14. package/dist/cli/typegen/schemaToTs.js +5 -1
  15. package/dist/cli/typegen/schemaToTs.js.map +1 -1
  16. package/dist/index.es.js +903 -895
  17. package/dist/index.html +1 -1
  18. package/dist/types/cli/commands/init.d.ts +3 -0
  19. package/dist/types/cli/commands/init.d.ts.map +1 -0
  20. package/dist/types/cli/commands/typegen.d.ts +3 -0
  21. package/dist/types/cli/commands/typegen.d.ts.map +1 -0
  22. package/dist/types/cli/config/loadConfig.d.ts +3 -0
  23. package/dist/types/cli/config/loadConfig.d.ts.map +1 -0
  24. package/dist/types/cli/config/types.d.ts +34 -0
  25. package/dist/types/cli/config/types.d.ts.map +1 -0
  26. package/dist/types/cli/typegen/runTypegen.d.ts +9 -0
  27. package/dist/types/cli/typegen/runTypegen.d.ts.map +1 -0
  28. package/dist/types/cli/typegen/schemaToTs.d.ts.map +1 -1
  29. package/dist/types/dsl/filterExpr.d.ts +130 -46
  30. package/dist/types/dsl/filterExpr.d.ts.map +1 -1
  31. package/dist/types/dsl/filters.d.ts +72 -2
  32. package/dist/types/dsl/filters.d.ts.map +1 -1
  33. package/dist/types/framework/data.d.ts +1 -1
  34. package/dist/types/framework/data.d.ts.map +1 -1
  35. package/dist/types/framework/view-parser.d.ts +2 -0
  36. package/dist/types/framework/view-parser.d.ts.map +1 -1
  37. package/dist/types/framework/view.d.ts +5 -0
  38. package/dist/types/framework/view.d.ts.map +1 -1
  39. package/dist/types/lib/index.d.ts +1 -0
  40. package/dist/types/lib/index.d.ts.map +1 -1
  41. package/dist/types/lib/renderTableView.d.ts +4 -1
  42. package/dist/types/lib/renderTableView.d.ts.map +1 -1
  43. package/docs/typegen.md +4 -0
  44. package/package.json +1 -1
@@ -0,0 +1,638 @@
1
+ import * as path from 'node:path';
2
+ import * as fs from 'node:fs/promises';
3
+ import fg from 'fast-glob';
4
+ import ts from 'typescript';
5
+ import { getIntrospectionQuery, buildClientSchema } from 'graphql';
6
+ import { loadConfig } from '../config/loadConfig.js';
7
+ import { collectReachableTypes, renderTsFromSchema, unwrapCollectionElementType } from './schemaToTs.js';
8
+ function toPascalCase(input) {
9
+ return input
10
+ .replace(/[^a-zA-Z0-9]+/g, ' ')
11
+ .trim()
12
+ .split(' ')
13
+ .filter(Boolean)
14
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
15
+ .join('');
16
+ }
17
+ function toIdentifier(pascal) {
18
+ if (/^[A-Za-z_]/.test(pascal))
19
+ return pascal;
20
+ return `_${pascal}`;
21
+ }
22
+ function singleQuoteStringLiteral(value) {
23
+ return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/\n/g, '\\n').replace(/\r/g, '\\r')}'`;
24
+ }
25
+ async function writeFileEnsuringDir(filePath, content) {
26
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
27
+ await fs.writeFile(filePath, content, 'utf8');
28
+ }
29
+ async function fetchSchema(endpoint, headers) {
30
+ const query = getIntrospectionQuery({ descriptions: true });
31
+ const res = await fetch(endpoint, {
32
+ method: 'POST',
33
+ headers: {
34
+ 'content-type': 'application/json',
35
+ ...headers
36
+ },
37
+ body: JSON.stringify({ query })
38
+ });
39
+ if (!res.ok) {
40
+ const text = await res.text().catch(() => '');
41
+ throw new Error(`Introspection request failed (${res.status} ${res.statusText}): ${text}`);
42
+ }
43
+ const json = await res.json();
44
+ if (json.errors && Array.isArray(json.errors) && json.errors.length) {
45
+ throw new Error(`Introspection returned errors: ${JSON.stringify(json.errors, null, 2)}`);
46
+ }
47
+ if (!json.data) {
48
+ throw new Error('Introspection response missing data');
49
+ }
50
+ return buildClientSchema(json.data);
51
+ }
52
+ function applyFileNamePattern(pattern, view) {
53
+ return pattern
54
+ .replace(/\{viewId\}/g, view.viewId)
55
+ .replace(/\{collectionName\}/g, view.collectionName);
56
+ }
57
+ function getColumnDefinitionsArray(argObject) {
58
+ for (const p of argObject.properties) {
59
+ if (!ts.isPropertyAssignment(p))
60
+ continue;
61
+ const name = p.name;
62
+ const key = ts.isIdentifier(name)
63
+ ? name.text
64
+ : ts.isStringLiteral(name)
65
+ ? name.text
66
+ : null;
67
+ if (key !== 'columnDefinitions')
68
+ continue;
69
+ return ts.isArrayLiteralExpression(p.initializer) ? p.initializer : null;
70
+ }
71
+ return null;
72
+ }
73
+ function hasRowTypeProp(obj) {
74
+ for (const p of obj.properties) {
75
+ if (!ts.isPropertyAssignment(p))
76
+ continue;
77
+ const name = p.name;
78
+ const key = ts.isIdentifier(name)
79
+ ? name.text
80
+ : ts.isStringLiteral(name)
81
+ ? name.text
82
+ : null;
83
+ if (key === 'rowType')
84
+ return true;
85
+ }
86
+ return false;
87
+ }
88
+ function applyTextEdits(original, edits) {
89
+ const sorted = [...edits].sort((a, b) => b.pos - a.pos);
90
+ let out = original;
91
+ for (const e of sorted) {
92
+ out = out.slice(0, e.pos) + e.insert + out.slice(e.pos);
93
+ }
94
+ return out;
95
+ }
96
+ function ensureRowTypeImport(sourceText, sourceFile, importName, importPathNoExt) {
97
+ for (const stmt of sourceFile.statements) {
98
+ if (!ts.isImportDeclaration(stmt))
99
+ continue;
100
+ if (!ts.isStringLiteral(stmt.moduleSpecifier))
101
+ continue;
102
+ if (stmt.moduleSpecifier.text !== importPathNoExt)
103
+ continue;
104
+ const nb = stmt.importClause?.namedBindings;
105
+ if (!nb || !ts.isNamedImports(nb)) {
106
+ return { updatedText: sourceText, changed: false };
107
+ }
108
+ if (nb.elements.some(e => e.name.text === importName)) {
109
+ return { updatedText: sourceText, changed: false };
110
+ }
111
+ const insertPos = nb.getEnd() - 1; // before `}`
112
+ const insert = `${nb.elements.length ? ', ' : ' '}${importName}`;
113
+ return { updatedText: applyTextEdits(sourceText, [{ pos: insertPos, insert }]), changed: true };
114
+ }
115
+ const importStmts = sourceFile.statements.filter(ts.isImportDeclaration);
116
+ const insertPos = importStmts.length
117
+ ? importStmts[importStmts.length - 1].end
118
+ : 0;
119
+ const prefix = insertPos === 0 ? '' : '\n';
120
+ const importLine = `${prefix}import { ${importName} } from ${singleQuoteStringLiteral(importPathNoExt)};\n`;
121
+ return {
122
+ updatedText: applyTextEdits(sourceText, [{ pos: insertPos, insert: importLine }]),
123
+ changed: true
124
+ };
125
+ }
126
+ function patchInlineColumnsWithRowType(args) {
127
+ const cols = getColumnDefinitionsArray(args.viewArgObject);
128
+ if (!cols)
129
+ return { updatedText: args.sourceText, changed: false, patchedCount: 0 };
130
+ const edits = [];
131
+ let patchedCount = 0;
132
+ for (const el of cols.elements) {
133
+ if (!ts.isCallExpression(el))
134
+ continue;
135
+ const expr = el.expression;
136
+ if (!ts.isPropertyAccessExpression(expr))
137
+ continue;
138
+ if (expr.name.text !== 'column')
139
+ continue;
140
+ const receiver = expr.expression;
141
+ const isDtvColumn = (() => {
142
+ if (ts.isIdentifier(receiver) && args.dslIdentifiers.has(receiver.text))
143
+ return true;
144
+ if (ts.isPropertyAccessExpression(receiver)) {
145
+ if (receiver.name.text !== 'DSL')
146
+ return false;
147
+ const maybeNs = receiver.expression;
148
+ return ts.isIdentifier(maybeNs) && args.dtvNamespaces.has(maybeNs.text);
149
+ }
150
+ return false;
151
+ })();
152
+ if (!isDtvColumn)
153
+ continue;
154
+ const firstArg = el.arguments[0];
155
+ if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
156
+ continue;
157
+ if (hasRowTypeProp(firstArg))
158
+ continue;
159
+ const firstProp = firstArg.properties[0];
160
+ const insertPos = firstProp ? firstProp.getStart(args.sourceFile, false) : firstArg.getEnd() - 1;
161
+ const between = args.sourceText.slice(firstArg.getStart(args.sourceFile, false) + 1, insertPos);
162
+ const isMultiline = between.includes('\n');
163
+ if (!isMultiline) {
164
+ edits.push({ pos: insertPos, insert: `rowType: ${args.rowTypeIdentifier}, ` });
165
+ patchedCount += 1;
166
+ continue;
167
+ }
168
+ const lineStart = args.sourceText.lastIndexOf('\n', insertPos - 1) + 1;
169
+ const before = args.sourceText.slice(lineStart, insertPos);
170
+ const indentMatch = before.match(/^[ \t]*/);
171
+ const indent = indentMatch ? indentMatch[0] : '';
172
+ edits.push({ pos: insertPos, insert: `${indent}rowType: ${args.rowTypeIdentifier},\n` });
173
+ patchedCount += 1;
174
+ }
175
+ if (edits.length === 0) {
176
+ return { updatedText: args.sourceText, changed: false, patchedCount: 0 };
177
+ }
178
+ return {
179
+ updatedText: applyTextEdits(args.sourceText, edits),
180
+ changed: true,
181
+ patchedCount
182
+ };
183
+ }
184
+ function findViewsInFile(sourceText, fileName, dtvImport, debug) {
185
+ const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true);
186
+ const unwrapParens = (expr) => {
187
+ let cur = expr;
188
+ while (ts.isParenthesizedExpression(cur)) {
189
+ cur = cur.expression;
190
+ }
191
+ return cur;
192
+ };
193
+ const getDslReceivers = () => {
194
+ const dslIdentifiers = new Set();
195
+ const dtvNamespaces = new Set();
196
+ for (const stmt of sourceFile.statements) {
197
+ if (ts.isImportDeclaration(stmt)) {
198
+ if (!ts.isStringLiteral(stmt.moduleSpecifier))
199
+ continue;
200
+ if (stmt.moduleSpecifier.text !== dtvImport)
201
+ continue;
202
+ const clause = stmt.importClause;
203
+ if (!clause)
204
+ continue;
205
+ if (clause.name) {
206
+ dtvNamespaces.add(clause.name.text);
207
+ }
208
+ const nb = clause.namedBindings;
209
+ if (!nb)
210
+ continue;
211
+ if (ts.isNamespaceImport(nb)) {
212
+ dtvNamespaces.add(nb.name.text);
213
+ continue;
214
+ }
215
+ if (ts.isNamedImports(nb)) {
216
+ for (const el of nb.elements) {
217
+ const imported = (el.propertyName ?? el.name).text;
218
+ const local = el.name.text;
219
+ if (imported === 'DSL') {
220
+ dslIdentifiers.add(local);
221
+ }
222
+ }
223
+ continue;
224
+ }
225
+ }
226
+ if (ts.isImportEqualsDeclaration(stmt)) {
227
+ const mr = stmt.moduleReference;
228
+ if (!ts.isExternalModuleReference(mr))
229
+ continue;
230
+ const expr = mr.expression;
231
+ if (!expr || !ts.isStringLiteral(expr))
232
+ continue;
233
+ if (expr.text !== dtvImport)
234
+ continue;
235
+ dtvNamespaces.add(stmt.name.text);
236
+ continue;
237
+ }
238
+ if (ts.isVariableStatement(stmt)) {
239
+ for (const decl of stmt.declarationList.declarations) {
240
+ if (!decl.initializer)
241
+ continue;
242
+ const init = unwrapParens(decl.initializer);
243
+ if (!ts.isCallExpression(init))
244
+ continue;
245
+ if (!ts.isIdentifier(init.expression) || init.expression.text !== 'require')
246
+ continue;
247
+ const arg0 = init.arguments[0];
248
+ if (!arg0 || !ts.isStringLiteral(arg0) || arg0.text !== dtvImport)
249
+ continue;
250
+ if (ts.isIdentifier(decl.name)) {
251
+ dtvNamespaces.add(decl.name.text);
252
+ continue;
253
+ }
254
+ if (ts.isObjectBindingPattern(decl.name)) {
255
+ for (const el of decl.name.elements) {
256
+ const imported = (el.propertyName ?? el.name);
257
+ if (ts.isIdentifier(imported) && imported.text === 'DSL') {
258
+ if (ts.isIdentifier(el.name)) {
259
+ dslIdentifiers.add(el.name.text);
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
267
+ return { dslIdentifiers, dtvNamespaces };
268
+ };
269
+ const receivers = getDslReceivers();
270
+ if (receivers.dslIdentifiers.size === 0 && receivers.dtvNamespaces.size === 0) {
271
+ debug?.log(`- no matching imports/requires from ${JSON.stringify(dtvImport)}`);
272
+ return [];
273
+ }
274
+ debug?.log(`- dslIdentifiers: ${[...receivers.dslIdentifiers].sort().join(', ') || '(none)'}`);
275
+ debug?.log(`- dtvNamespaces: ${[...receivers.dtvNamespaces].sort().join(', ') || '(none)'}`);
276
+ const views = [];
277
+ const tryGetStringProp = (obj, propName) => {
278
+ for (const p of obj.properties) {
279
+ if (!ts.isPropertyAssignment(p))
280
+ continue;
281
+ const name = p.name;
282
+ const key = ts.isIdentifier(name)
283
+ ? name.text
284
+ : ts.isStringLiteral(name)
285
+ ? name.text
286
+ : null;
287
+ if (key !== propName)
288
+ continue;
289
+ if (ts.isStringLiteral(p.initializer))
290
+ return p.initializer.text;
291
+ if (ts.isNoSubstitutionTemplateLiteral(p.initializer))
292
+ return p.initializer.text;
293
+ return null;
294
+ }
295
+ return null;
296
+ };
297
+ const visit = (node) => {
298
+ if (ts.isCallExpression(node)) {
299
+ const expr = node.expression;
300
+ if (ts.isPropertyAccessExpression(expr)) {
301
+ const receiver = unwrapParens(expr.expression);
302
+ const method = expr.name.text;
303
+ const isDslReceiver = (() => {
304
+ if (ts.isIdentifier(receiver) && receivers.dslIdentifiers.has(receiver.text))
305
+ return true;
306
+ if (ts.isPropertyAccessExpression(receiver)) {
307
+ const maybeNs = unwrapParens(receiver.expression);
308
+ if (receiver.name.text !== 'DSL')
309
+ return false;
310
+ return ts.isIdentifier(maybeNs) && receivers.dtvNamespaces.has(maybeNs.text);
311
+ }
312
+ return false;
313
+ })();
314
+ if (method === 'view' && isDslReceiver) {
315
+ const firstArg = node.arguments[0];
316
+ if (!firstArg) {
317
+ debug?.log('- found DSL.view(...) with no args (skipping)');
318
+ }
319
+ else if (!ts.isObjectLiteralExpression(firstArg)) {
320
+ debug?.log('- found DSL.view(<non-object-literal>) (skipping)');
321
+ }
322
+ else {
323
+ const viewId = tryGetStringProp(firstArg, 'id');
324
+ const collectionName = tryGetStringProp(firstArg, 'collectionName');
325
+ if (!viewId || !collectionName) {
326
+ debug?.log(`- found DSL.view({ ... }) but id/collectionName not string literals (id=${viewId ?? 'null'}, collectionName=${collectionName ?? 'null'})`);
327
+ }
328
+ else {
329
+ debug?.log(`- found view id=${JSON.stringify(viewId)} collectionName=${JSON.stringify(collectionName)}`);
330
+ views.push({
331
+ viewId,
332
+ collectionName,
333
+ sourceFile: fileName
334
+ });
335
+ }
336
+ }
337
+ }
338
+ }
339
+ }
340
+ ts.forEachChild(node, visit);
341
+ };
342
+ visit(sourceFile);
343
+ return views;
344
+ }
345
+ async function scanViews(config, debug) {
346
+ const dtvImport = config.scan.dtvImport ?? '@kronor/dtv';
347
+ const files = await fg(config.scan.include, {
348
+ ignore: config.scan.exclude ?? [],
349
+ absolute: true,
350
+ onlyFiles: true
351
+ });
352
+ const focusAbs = debug?.focusFile
353
+ ? (path.isAbsolute(debug.focusFile) ? debug.focusFile : path.resolve(process.cwd(), debug.focusFile))
354
+ : undefined;
355
+ if (debug?.enabled) {
356
+ console.log('[dtv typegen] scan debug');
357
+ console.log(`- dtvImport: ${dtvImport}`);
358
+ console.log(`- include: ${JSON.stringify(config.scan.include)}`);
359
+ console.log(`- exclude: ${JSON.stringify(config.scan.exclude ?? [])}`);
360
+ console.log(`- matchedFiles: ${files.length}`);
361
+ if (focusAbs) {
362
+ console.log(`- focusFile: ${focusAbs}`);
363
+ console.log(`- focusFileMatchedByGlob: ${files.map(f => path.resolve(f)).includes(path.resolve(focusAbs))}`);
364
+ }
365
+ }
366
+ const results = [];
367
+ const filesToScan = focusAbs ? files.filter(f => path.resolve(f) === path.resolve(focusAbs)) : files;
368
+ for (const f of filesToScan) {
369
+ if (!f.endsWith('.ts') && !f.endsWith('.tsx'))
370
+ continue;
371
+ const text = await fs.readFile(f, 'utf8');
372
+ const fileDebug = debug?.enabled
373
+ ? { log: (line) => console.log(`[dtv typegen] ${path.resolve(f)} ${line}`) }
374
+ : undefined;
375
+ results.push(...findViewsInFile(text, f, dtvImport, fileDebug));
376
+ }
377
+ const byId = new Map();
378
+ for (const v of results) {
379
+ const list = byId.get(v.viewId);
380
+ if (list)
381
+ list.push(v);
382
+ else
383
+ byId.set(v.viewId, [v]);
384
+ }
385
+ const duplicates = [];
386
+ for (const [viewId, views] of byId.entries()) {
387
+ if (views.length > 1)
388
+ duplicates.push({ viewId, views });
389
+ }
390
+ if (duplicates.length) {
391
+ const lines = [];
392
+ lines.push('Duplicate DTV view ids found (each view `id` must be unique):');
393
+ for (const d of duplicates.sort((a, b) => a.viewId.localeCompare(b.viewId))) {
394
+ lines.push(`- ${d.viewId}`);
395
+ for (const v of d.views) {
396
+ lines.push(` - ${path.resolve(v.sourceFile)}`);
397
+ }
398
+ }
399
+ throw new Error(lines.join('\n'));
400
+ }
401
+ return results;
402
+ }
403
+ export async function runTypegen(args) {
404
+ const config = await loadConfig(args.configPath);
405
+ const debug = (args.debugScan || args.debugScanFile)
406
+ ? { enabled: true, focusFile: args.debugScanFile }
407
+ : undefined;
408
+ const views = await scanViews(config, debug);
409
+ if (views.length === 0) {
410
+ throw new Error('No views found. Ensure Config.scan.include matches files that import DSL from your configured DTV specifier and call DSL.view({ ... }).');
411
+ }
412
+ const selectedViews = args.onlyViewId
413
+ ? views.filter(v => v.viewId === args.onlyViewId)
414
+ : views;
415
+ if (args.onlyViewId && selectedViews.length === 0) {
416
+ const sample = views.map(v => v.viewId).slice(0, 25);
417
+ throw new Error(`No view found with id ${JSON.stringify(args.onlyViewId)}. `
418
+ + `Sample discovered view ids: ${sample.join(', ')}${sample.length === 25 ? ', ...' : ''}`);
419
+ }
420
+ const schema = await fetchSchema(config.schema.endpoint, config.schema.headers ?? {});
421
+ const queryType = schema.getQueryType();
422
+ if (!queryType)
423
+ throw new Error('Schema has no Query type');
424
+ const queryFields = queryType.getFields();
425
+ const dtvImport = config.scan.dtvImport ?? '@kronor/dtv';
426
+ // Resolve view -> row type
427
+ const viewRows = selectedViews.map(v => {
428
+ const f = queryFields[v.collectionName];
429
+ if (!f) {
430
+ const sample = Object.keys(queryFields).slice(0, 25);
431
+ throw new Error(`View "${v.viewId}" references collectionName "${v.collectionName}" but it was not found on Query. `
432
+ + `Sample Query fields: ${sample.join(', ')}${sample.length === 25 ? ', ...' : ''}`);
433
+ }
434
+ const rowNamed = unwrapCollectionElementType(f.type);
435
+ return {
436
+ ...v,
437
+ rowTypeName: rowNamed.name
438
+ };
439
+ });
440
+ const outputFiles = new Set();
441
+ for (const v of viewRows) {
442
+ const root = schema.getType(v.rowTypeName);
443
+ if (!root || Array.isArray(root)) {
444
+ throw new Error(`Could not resolve row type "${v.rowTypeName}" in schema for view "${v.viewId}"`);
445
+ }
446
+ const reachable = collectReachableTypes(schema, [root]);
447
+ const viewTypeName = `${toIdentifier(toPascalCase(v.viewId))}Row`;
448
+ const rowTypeConstName = `${toIdentifier(toPascalCase(v.viewId))}RowType`;
449
+ const fileName = applyFileNamePattern(config.output.fileNamePattern, v);
450
+ if (!fileName.endsWith('.ts')) {
451
+ throw new Error(`Config.output.fileNamePattern must produce a .ts file name. Got: ${fileName}`);
452
+ }
453
+ const outFile = path.join(path.dirname(v.sourceFile), fileName);
454
+ if (outputFiles.has(outFile)) {
455
+ throw new Error(`Multiple views would write the same output file: ${outFile}`);
456
+ }
457
+ outputFiles.add(outFile);
458
+ const content = [
459
+ `import { DSL as DTV } from ${singleQuoteStringLiteral(dtvImport)};`,
460
+ '',
461
+ renderTsFromSchema(reachable, {
462
+ scalars: config.scalars,
463
+ includeGraphqlTypeComments: config.debug?.includeGraphqlTypeComments === true,
464
+ exportTypes: false
465
+ }).trimEnd(),
466
+ '',
467
+ `export type ${viewTypeName} = ${v.rowTypeName};`,
468
+ `export const ${rowTypeConstName} = DTV.rowType<${viewTypeName}>();`,
469
+ ''
470
+ ].join('\n');
471
+ await writeFileEnsuringDir(outFile, content);
472
+ // Best-effort patch view file for inline columns.
473
+ try {
474
+ const viewText = await fs.readFile(v.sourceFile, 'utf8');
475
+ const sf = ts.createSourceFile(v.sourceFile, viewText, ts.ScriptTarget.Latest, true);
476
+ const dslIdentifiers = new Set();
477
+ const dtvNamespaces = new Set();
478
+ for (const stmt of sf.statements) {
479
+ if (ts.isImportDeclaration(stmt)) {
480
+ if (!ts.isStringLiteral(stmt.moduleSpecifier))
481
+ continue;
482
+ if (stmt.moduleSpecifier.text !== dtvImport)
483
+ continue;
484
+ const clause = stmt.importClause;
485
+ if (!clause)
486
+ continue;
487
+ if (clause.name)
488
+ dtvNamespaces.add(clause.name.text);
489
+ const nb = clause.namedBindings;
490
+ if (!nb)
491
+ continue;
492
+ if (ts.isNamespaceImport(nb)) {
493
+ dtvNamespaces.add(nb.name.text);
494
+ continue;
495
+ }
496
+ if (ts.isNamedImports(nb)) {
497
+ for (const el of nb.elements) {
498
+ const imported = (el.propertyName ?? el.name).text;
499
+ const local = el.name.text;
500
+ if (imported === 'DSL')
501
+ dslIdentifiers.add(local);
502
+ }
503
+ }
504
+ }
505
+ if (ts.isImportEqualsDeclaration(stmt)) {
506
+ const mr = stmt.moduleReference;
507
+ if (!ts.isExternalModuleReference(mr))
508
+ continue;
509
+ const expr = mr.expression;
510
+ if (!expr || !ts.isStringLiteral(expr))
511
+ continue;
512
+ if (expr.text !== dtvImport)
513
+ continue;
514
+ dtvNamespaces.add(stmt.name.text);
515
+ }
516
+ if (ts.isVariableStatement(stmt)) {
517
+ for (const decl of stmt.declarationList.declarations) {
518
+ if (!decl.initializer)
519
+ continue;
520
+ if (!ts.isCallExpression(decl.initializer))
521
+ continue;
522
+ if (!ts.isIdentifier(decl.initializer.expression) || decl.initializer.expression.text !== 'require')
523
+ continue;
524
+ const arg0 = decl.initializer.arguments[0];
525
+ if (!arg0 || !ts.isStringLiteral(arg0) || arg0.text !== dtvImport)
526
+ continue;
527
+ if (ts.isIdentifier(decl.name)) {
528
+ dtvNamespaces.add(decl.name.text);
529
+ }
530
+ else if (ts.isObjectBindingPattern(decl.name)) {
531
+ for (const el of decl.name.elements) {
532
+ const imported = (el.propertyName ?? el.name);
533
+ if (ts.isIdentifier(imported) && imported.text === 'DSL') {
534
+ if (ts.isIdentifier(el.name))
535
+ dslIdentifiers.add(el.name.text);
536
+ }
537
+ }
538
+ }
539
+ }
540
+ }
541
+ }
542
+ // Find the first matching view call object for this viewId.
543
+ let viewArgObject = null;
544
+ const visit = (node) => {
545
+ if (viewArgObject)
546
+ return;
547
+ if (ts.isCallExpression(node)) {
548
+ const expr = node.expression;
549
+ if (ts.isPropertyAccessExpression(expr) && expr.name.text === 'view') {
550
+ const receiver = expr.expression;
551
+ const isDslReceiver = (() => {
552
+ if (ts.isIdentifier(receiver) && dslIdentifiers.has(receiver.text))
553
+ return true;
554
+ if (ts.isPropertyAccessExpression(receiver) && receiver.name.text === 'DSL') {
555
+ const maybeNs = receiver.expression;
556
+ return ts.isIdentifier(maybeNs) && dtvNamespaces.has(maybeNs.text);
557
+ }
558
+ return false;
559
+ })();
560
+ if (!isDslReceiver)
561
+ return;
562
+ const firstArg = node.arguments[0];
563
+ if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
564
+ return;
565
+ const idProp = firstArg.properties.find(p => ts.isPropertyAssignment(p)
566
+ && ((ts.isIdentifier(p.name) && p.name.text === 'id')
567
+ || (ts.isStringLiteral(p.name) && p.name.text === 'id')));
568
+ const idVal = idProp?.initializer;
569
+ const id = idVal && (ts.isStringLiteral(idVal) || ts.isNoSubstitutionTemplateLiteral(idVal))
570
+ ? idVal.text
571
+ : null;
572
+ if (id === v.viewId) {
573
+ viewArgObject = firstArg;
574
+ }
575
+ }
576
+ }
577
+ ts.forEachChild(node, visit);
578
+ };
579
+ visit(sf);
580
+ if (!viewArgObject) {
581
+ continue;
582
+ }
583
+ const importPathNoExt = './' + fileName.replace(/\.ts$/i, '');
584
+ const withImport = ensureRowTypeImport(viewText, sf, rowTypeConstName, importPathNoExt);
585
+ const sf2 = ts.createSourceFile(v.sourceFile, withImport.updatedText, ts.ScriptTarget.Latest, true);
586
+ // Re-find view object in updated text.
587
+ let viewArgObject2 = null;
588
+ const visit2 = (node) => {
589
+ if (viewArgObject2)
590
+ return;
591
+ if (ts.isCallExpression(node)) {
592
+ const expr = node.expression;
593
+ if (ts.isPropertyAccessExpression(expr) && expr.name.text === 'view') {
594
+ const firstArg = node.arguments[0];
595
+ if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
596
+ return;
597
+ const idProp = firstArg.properties.find(p => ts.isPropertyAssignment(p)
598
+ && ((ts.isIdentifier(p.name) && p.name.text === 'id')
599
+ || (ts.isStringLiteral(p.name) && p.name.text === 'id')));
600
+ const idVal = idProp?.initializer;
601
+ const id = idVal && (ts.isStringLiteral(idVal) || ts.isNoSubstitutionTemplateLiteral(idVal))
602
+ ? idVal.text
603
+ : null;
604
+ if (id === v.viewId) {
605
+ viewArgObject2 = firstArg;
606
+ }
607
+ }
608
+ }
609
+ ts.forEachChild(node, visit2);
610
+ };
611
+ visit2(sf2);
612
+ if (!viewArgObject2) {
613
+ continue;
614
+ }
615
+ const patched = patchInlineColumnsWithRowType({
616
+ sourceText: withImport.updatedText,
617
+ sourceFile: sf2,
618
+ viewArgObject: viewArgObject2,
619
+ rowTypeIdentifier: rowTypeConstName,
620
+ dslIdentifiers,
621
+ dtvNamespaces
622
+ });
623
+ if (withImport.changed || patched.changed) {
624
+ await fs.writeFile(v.sourceFile, patched.updatedText, 'utf8');
625
+ }
626
+ }
627
+ catch {
628
+ // Ignore patching errors; generation still succeeds.
629
+ }
630
+ }
631
+ if (args.onlyViewId) {
632
+ console.log(`Generated types for view ${JSON.stringify(args.onlyViewId)}.`);
633
+ }
634
+ else {
635
+ console.log(`Generated types for ${viewRows.length} view(s).`);
636
+ }
637
+ }
638
+ //# sourceMappingURL=runTypegen.js.map