orchid-orm 1.4.17 → 1.4.20

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,512 @@
1
+ import { RakeDbAst } from 'rake-db';
2
+ import fs from 'fs/promises';
3
+ import { FileChanges } from '../fileChanges';
4
+ import { ts } from '../tsUtils';
5
+ import { toPascalCase } from '../../utils';
6
+ import {
7
+ CallExpression,
8
+ Expression,
9
+ NodeArray,
10
+ ObjectLiteralElementLike,
11
+ ObjectLiteralExpression,
12
+ PropertyAssignment,
13
+ Statement,
14
+ } from 'typescript';
15
+ import {
16
+ addCode,
17
+ Code,
18
+ codeToString,
19
+ columnDefaultArgumentToCode,
20
+ ColumnType,
21
+ foreignKeyToCode,
22
+ indexToCode,
23
+ primaryKeyToCode,
24
+ quoteObjectKey,
25
+ singleQuote,
26
+ TableData,
27
+ } from 'pqb';
28
+ import { UpdateTableFileParams } from './updateTableFile';
29
+
30
+ export const changeTable = async ({
31
+ ast,
32
+ ...params
33
+ }: UpdateTableFileParams & { ast: RakeDbAst.ChangeTable }) => {
34
+ const tablePath = params.tablePath(ast.name);
35
+ const content = await fs.readFile(tablePath, 'utf-8').catch(() => undefined);
36
+ if (!content) return;
37
+
38
+ const changes = new FileChanges(content);
39
+ const statements = ts.getStatements(content);
40
+ const className = toPascalCase(ast.name);
41
+
42
+ for (const { t, object } of iterateColumnsShapes(statements, className)) {
43
+ const context = makeChangeContext(changes, ast, content, object, t);
44
+
45
+ prependSpaces(context);
46
+ applySchemaChanges(context);
47
+ appendTrailingComma(context);
48
+ addColumns(context);
49
+ addTableData(context);
50
+ }
51
+
52
+ await fs.writeFile(tablePath, changes.apply());
53
+ };
54
+
55
+ function* iterateColumnsShapes(
56
+ statements: NodeArray<Statement>,
57
+ className: string,
58
+ ) {
59
+ for (const node of ts.class.iterate(statements)) {
60
+ if (node.name?.escapedText !== className) continue;
61
+
62
+ for (const member of node.members) {
63
+ const name = ts.prop.getName(member);
64
+ const { initializer: call } = member as unknown as {
65
+ initializer?: Expression;
66
+ };
67
+
68
+ if (name !== 'columns' || !call || !ts.is.call(call)) continue;
69
+
70
+ const { expression } = call;
71
+ if (
72
+ !ts.is.propertyAccess(expression) ||
73
+ !ts.is.this(expression.expression) ||
74
+ expression.name.escapedText !== 'setColumns'
75
+ )
76
+ continue;
77
+
78
+ const [arg] = call.arguments;
79
+ if (!ts.is.arrowFunction(arg)) continue;
80
+
81
+ const { parameters, body } = arg;
82
+ const param = parameters[0]?.name;
83
+ if (!ts.is.identifier(param) || !ts.is.parenthesizedExpression(body))
84
+ continue;
85
+
86
+ const { expression: object } = body;
87
+ if (!ts.is.objectLiteral(object)) continue;
88
+
89
+ yield { t: param.escapedText.toString(), object };
90
+ }
91
+ }
92
+ }
93
+
94
+ type ChangeContext = {
95
+ changes: FileChanges;
96
+ props: NodeArray<ObjectLiteralElementLike>;
97
+ shape: {
98
+ add: Record<string, ColumnType>;
99
+ drop: Record<string, true>;
100
+ change: Record<string, RakeDbAst.ChangeTableItem.Change>;
101
+ };
102
+ t: string;
103
+ spaces: string;
104
+ object: Expression;
105
+ drop: TableData;
106
+ add: TableData;
107
+ };
108
+
109
+ const makeChangeContext = (
110
+ changes: FileChanges,
111
+ ast: RakeDbAst.ChangeTable,
112
+ content: string,
113
+ object: ObjectLiteralExpression,
114
+ t: string,
115
+ ): ChangeContext => {
116
+ const add: ChangeContext['shape']['add'] = {};
117
+ const drop: ChangeContext['shape']['drop'] = {};
118
+ const change: ChangeContext['shape']['change'] = {};
119
+
120
+ const { properties: props } = object;
121
+ const existingColumns = getExistingColumns(props);
122
+
123
+ for (const key in ast.shape) {
124
+ const item = ast.shape[key];
125
+ if (item.type === 'add' && !existingColumns[key]) {
126
+ add[key] = item.item;
127
+ }
128
+
129
+ if (!existingColumns[key]) continue;
130
+
131
+ if (item.type === 'drop' && existingColumns[key]) {
132
+ drop[key] = true;
133
+ } else if (item.type === 'change' && existingColumns[key]) {
134
+ change[key] = item;
135
+ }
136
+ }
137
+
138
+ const spaces = ts.spaces.getAtLine(content, object.end);
139
+
140
+ const shape = { add, drop, change };
141
+ return {
142
+ changes,
143
+ props,
144
+ shape,
145
+ spaces,
146
+ t,
147
+ object,
148
+ add: ast.add,
149
+ drop: ast.drop,
150
+ };
151
+ };
152
+
153
+ const getExistingColumns = (props: NodeArray<ObjectLiteralElementLike>) => {
154
+ const existingColumns: Record<string, true> = {};
155
+ props
156
+ .map((prop) => ts.is.propertyAssignment(prop) && ts.prop.getName(prop))
157
+ .filter((name): name is string => !!name)
158
+ .forEach((name) => (existingColumns[name] = true));
159
+
160
+ for (const prop of props) {
161
+ if (!ts.is.propertyAssignment(prop)) continue;
162
+ const name = ts.prop.getName(prop);
163
+ if (name) existingColumns[name] = true;
164
+ }
165
+
166
+ return existingColumns;
167
+ };
168
+
169
+ const prependSpaces = ({
170
+ props,
171
+ shape: { add },
172
+ changes,
173
+ spaces,
174
+ }: ChangeContext) => {
175
+ if (Object.keys(add).length && props.pos === props.end) {
176
+ changes.add(props.pos, `\n${spaces}`);
177
+ }
178
+ };
179
+
180
+ const applySchemaChanges = (context: ChangeContext) => {
181
+ const {
182
+ props,
183
+ shape: { drop: dropColumns, change: changeColumns },
184
+ add,
185
+ drop,
186
+ } = context;
187
+
188
+ props.forEach((prop, i) => {
189
+ if (ts.is.spreadAssignment(prop)) {
190
+ const call = prop.expression;
191
+ if (!ts.is.call(call)) return;
192
+
193
+ const access = call.expression;
194
+ if (!ts.is.propertyAccess(access)) return;
195
+
196
+ const name = access.name.escapedText.toString();
197
+ if (name === 'primaryKey') {
198
+ if (drop.primaryKey || add.primaryKey) {
199
+ removeProp(context, prop, i);
200
+ }
201
+ } else if (name === 'index') {
202
+ dropMatchingIndexes(context, prop, i, call, drop.indexes);
203
+ } else if (name === 'foreignKey') {
204
+ dropMatchingForeignKey(context, prop, i, call, drop.foreignKeys);
205
+ }
206
+ } else if (ts.is.propertyAssignment(prop)) {
207
+ const name = ts.prop.getName(prop);
208
+ if (!name) return;
209
+
210
+ if (dropColumns[name]) {
211
+ removeProp(context, prop, i);
212
+ } else {
213
+ const changeItem = changeColumns[name];
214
+ if (changeItem) {
215
+ changeColumn(context, changeItem, prop);
216
+ }
217
+ }
218
+ }
219
+ });
220
+ };
221
+
222
+ const removeProp = (
223
+ { props, changes }: ChangeContext,
224
+ prop: ObjectLiteralElementLike,
225
+ i: number,
226
+ ) => {
227
+ const end = props[i + 1]?.pos || props.end;
228
+ changes.remove(prop.pos, end);
229
+ };
230
+
231
+ const changeColumn = (
232
+ { changes, t, spaces }: ChangeContext,
233
+ changeItem: RakeDbAst.ChangeTableItem.Change,
234
+ prop: PropertyAssignment,
235
+ ) => {
236
+ const { from, to } = changeItem;
237
+ if (from.type !== to.type && to.column) {
238
+ changes.replace(
239
+ prop.initializer.pos,
240
+ prop.end,
241
+ ` ${codeToString(to.column.toCode(t), spaces + ' ', ' ').trim()}`,
242
+ );
243
+ return;
244
+ }
245
+
246
+ const items: CallExpression[] = [];
247
+ let chain: Expression | undefined = prop.initializer;
248
+ while (ts.is.call(chain) && ts.is.propertyAccess(chain.expression)) {
249
+ items.push(chain);
250
+ chain = chain.expression.expression;
251
+ }
252
+
253
+ type Key = keyof RakeDbAst.ChangeTableItem.Change['to'];
254
+ const propsToChange: Partial<Record<Key, true>> = {};
255
+
256
+ for (const key in from) {
257
+ if (to[key as Key] !== from[key as Key]) {
258
+ propsToChange[key as Key] = true;
259
+ }
260
+ }
261
+
262
+ for (const key in to) {
263
+ if (to[key as Key] !== from[key as Key]) {
264
+ propsToChange[key as Key] = true;
265
+ }
266
+ }
267
+
268
+ const changedProps: Partial<Record<Key, true>> = {};
269
+ for (const item of items.reverse()) {
270
+ if (!ts.is.propertyAccess(item.expression)) continue;
271
+
272
+ const { name } = item.expression;
273
+ const key = name.escapedText.toString();
274
+ if (!propsToChange[key as Key]) continue;
275
+
276
+ const value = getColumnMethodArgs(to, key as Key);
277
+ if (value) {
278
+ const code = [`${key}(`];
279
+ addCode(code, value);
280
+ addCode(code, ')');
281
+ changes.replace(name.pos, item.end, codeToString(code, '', ' ').trim());
282
+ } else {
283
+ changes.remove(item.expression.expression.end, item.end);
284
+ }
285
+
286
+ changedProps[key as Key] = true;
287
+ }
288
+
289
+ let append = '';
290
+ for (const key in propsToChange) {
291
+ if (changedProps[key as Key]) continue;
292
+
293
+ const value = getColumnMethodArgs(to, key as Key);
294
+ if (value !== undefined) {
295
+ const code = [`.${key}(`];
296
+ addCode(code, value);
297
+ addCode(code, ')');
298
+ append += codeToString(code, '', ' ').trim();
299
+ }
300
+ }
301
+
302
+ if (append) {
303
+ changes.add(prop.end, append);
304
+ }
305
+ };
306
+
307
+ const appendTrailingComma = ({ props, changes }: ChangeContext) => {
308
+ if (!props.hasTrailingComma) {
309
+ const last = props[props.length - 1];
310
+ if (last) {
311
+ changes.add(last.end, ',');
312
+ }
313
+ }
314
+ };
315
+
316
+ const addColumns = ({
317
+ shape: { add },
318
+ changes,
319
+ object,
320
+ t,
321
+ spaces,
322
+ }: ChangeContext) => {
323
+ const end = object.end - 1;
324
+ for (const key in add) {
325
+ const code = codeToString(add[key].toCode(t), spaces + ' ', ' ');
326
+ changes.add(end, ` ${quoteObjectKey(key)}: ${code.trim()},\n${spaces}`);
327
+ }
328
+ };
329
+
330
+ const addTableData = ({ add, changes, object, t, spaces }: ChangeContext) => {
331
+ const end = object.end - 1;
332
+ if (add.primaryKey) {
333
+ const code = codeToString(
334
+ primaryKeyToCode(add.primaryKey, t),
335
+ spaces,
336
+ ' ',
337
+ );
338
+ changes.add(end, ` ${code.trim()}\n${spaces}`);
339
+ }
340
+ for (const item of add.indexes) {
341
+ const code = codeToString(indexToCode(item, t), spaces + ' ', ' ');
342
+ changes.add(end, ` ${code.trim()}\n${spaces}`);
343
+ }
344
+ for (const item of add.foreignKeys) {
345
+ const code = codeToString(foreignKeyToCode(item, t), spaces + ' ', ' ');
346
+ changes.add(end, ` ${code.trim()}\n${spaces}`);
347
+ }
348
+ };
349
+
350
+ const getColumnMethodArgs = (
351
+ to: RakeDbAst.ChangeTableItem.Change['to'],
352
+ key: keyof RakeDbAst.ChangeTableItem.Change['to'],
353
+ ): Code | undefined => {
354
+ const value = to[key];
355
+ if (!value) return;
356
+
357
+ if (key === 'collate' || key === 'compression') {
358
+ return singleQuote(value as string);
359
+ } else if (key === 'default') {
360
+ return columnDefaultArgumentToCode(value);
361
+ } else if (key === 'nullable' || key === 'primaryKey') {
362
+ return '';
363
+ } else {
364
+ return;
365
+ }
366
+ };
367
+
368
+ const dropMatchingIndexes = (
369
+ context: ChangeContext,
370
+ prop: ObjectLiteralElementLike,
371
+ i: number,
372
+ call: CallExpression,
373
+ items: TableData.Index[],
374
+ ) => {
375
+ if (!items.length) return;
376
+
377
+ const [columnsNode, optionsNode] = call.arguments;
378
+ const columns: Record<string, string | number>[] = [];
379
+ if (ts.is.stringLiteral(columnsNode)) {
380
+ columns.push({ column: columnsNode.text });
381
+ } else if (ts.is.arrayLiteral(columnsNode)) {
382
+ for (const node of columnsNode.elements) {
383
+ if (ts.is.stringLiteral(node)) {
384
+ columns.push({ column: node.text });
385
+ } else if (ts.is.objectLiteral(node)) {
386
+ const object = collectObjectFromCode(node);
387
+ if (!object) return;
388
+ columns.push(object);
389
+ }
390
+ }
391
+ } else {
392
+ return;
393
+ }
394
+
395
+ const options =
396
+ (ts.is.objectLiteral(optionsNode) && collectObjectFromCode(optionsNode)) ||
397
+ {};
398
+
399
+ for (const item of items) {
400
+ if (
401
+ deepCompare(columns, item.columns) &&
402
+ deepCompare(options, item.options)
403
+ ) {
404
+ removeProp(context, prop, i);
405
+ }
406
+ }
407
+ };
408
+
409
+ const dropMatchingForeignKey = (
410
+ context: ChangeContext,
411
+ prop: ObjectLiteralElementLike,
412
+ i: number,
413
+ call: CallExpression,
414
+ items: TableData.ForeignKey[],
415
+ ) => {
416
+ if (!items.length) return;
417
+
418
+ const { arguments: args } = call;
419
+
420
+ const columns = collectStringArrayFromCode(args[0]);
421
+ if (!columns) return;
422
+
423
+ const fnOrTableNode = args[1];
424
+ let fnOrTable: string;
425
+ if (ts.is.stringLiteral(fnOrTableNode)) {
426
+ fnOrTable = fnOrTableNode.text;
427
+ } else if (ts.is.arrowFunction(fnOrTableNode)) {
428
+ fnOrTable = context.changes.content
429
+ .slice(fnOrTableNode.pos, fnOrTableNode.end)
430
+ .replaceAll(/\s/g, '');
431
+ } else {
432
+ return;
433
+ }
434
+
435
+ const foreignColumns = collectStringArrayFromCode(args[2]);
436
+ if (!foreignColumns) return;
437
+
438
+ const options =
439
+ (ts.is.objectLiteral(args[3]) && collectObjectFromCode(args[3])) || {};
440
+
441
+ for (const item of items) {
442
+ const itemOptions = item.options;
443
+ delete itemOptions.dropMode;
444
+
445
+ if (
446
+ deepCompare(columns, item.columns) &&
447
+ deepCompare(fnOrTable, item.fnOrTable.toString()) &&
448
+ deepCompare(foreignColumns, item.foreignColumns) &&
449
+ deepCompare(options, itemOptions)
450
+ ) {
451
+ removeProp(context, prop, i);
452
+ }
453
+ }
454
+ };
455
+
456
+ const collectStringArrayFromCode = (node: Expression) => {
457
+ if (!ts.is.arrayLiteral(node)) return;
458
+
459
+ const result = node.elements
460
+ .filter(ts.is.stringLiteral)
461
+ .map((item) => item.text);
462
+
463
+ return result.length === node.elements.length ? result : undefined;
464
+ };
465
+
466
+ const collectObjectFromCode = (node: ObjectLiteralExpression) => {
467
+ const object: Record<string, string | number> = {};
468
+ for (const prop of node.properties) {
469
+ if (!ts.is.propertyAssignment(prop)) return;
470
+ const name = ts.prop.getName(prop);
471
+ if (!name) return;
472
+
473
+ const init = prop.initializer;
474
+ if (ts.is.stringLiteral(init)) {
475
+ object[name] = init.text;
476
+ } else if (ts.is.numericLiteral(init)) {
477
+ object[name] = parseFloat(init.text);
478
+ } else {
479
+ return;
480
+ }
481
+ }
482
+ return object;
483
+ };
484
+
485
+ const deepCompare = (a: unknown, b: unknown): boolean => {
486
+ if (typeof a !== typeof b) return false;
487
+ if (a === b) return true;
488
+ if (typeof a === 'object') {
489
+ if (a === null) return b === null;
490
+
491
+ if (Array.isArray(a)) {
492
+ if (!Array.isArray(b) || a.length !== b.length) return false;
493
+
494
+ return a.every((item, i) => deepCompare(item, b[i]));
495
+ }
496
+
497
+ for (const key in a) {
498
+ if (
499
+ !deepCompare(
500
+ (a as Record<string, unknown>)[key],
501
+ (b as Record<string, unknown>)[key],
502
+ )
503
+ )
504
+ return false;
505
+ }
506
+
507
+ for (const key in b as Record<string, unknown>) {
508
+ if (!(key in a)) return false;
509
+ }
510
+ }
511
+ return true;
512
+ };
@@ -0,0 +1,86 @@
1
+ import { updateTableFile } from './updateTableFile';
2
+ import { ast, makeTestWritten, tablePath } from '../testUtils';
3
+ import path from 'path';
4
+
5
+ jest.mock('fs/promises', () => ({
6
+ readFile: jest.fn(),
7
+ writeFile: jest.fn(),
8
+ }));
9
+
10
+ const baseTablePath = path.resolve('baseTable.ts');
11
+ const baseTableName = 'BaseTable';
12
+ const params = { baseTablePath, baseTableName, tablePath };
13
+
14
+ const testWritten = makeTestWritten(tablePath('table'));
15
+
16
+ describe('createTable', () => {
17
+ beforeEach(() => {
18
+ jest.resetAllMocks();
19
+ });
20
+
21
+ it('should add table', async () => {
22
+ await updateTableFile({
23
+ ...params,
24
+ ast: {
25
+ ...ast.addTable,
26
+ primaryKey: {
27
+ columns: ['one', 'two'],
28
+ options: { name: 'name' },
29
+ },
30
+ indexes: [
31
+ {
32
+ columns: [{ column: 'one' }, { column: 'two' }],
33
+ options: { name: 'indexName', unique: true },
34
+ },
35
+ ],
36
+ foreignKeys: [
37
+ {
38
+ columns: ['one', 'two'],
39
+ fnOrTable: 'table',
40
+ foreignColumns: ['three', 'four'],
41
+ options: { name: 'foreignKeyName' },
42
+ },
43
+ ],
44
+ },
45
+ });
46
+
47
+ testWritten(`import { BaseTable } from '../baseTable';
48
+
49
+ export class Table extends BaseTable {
50
+ table = 'table';
51
+ columns = this.setColumns((t) => ({
52
+ id: t.serial().primaryKey(),
53
+ ...t.primaryKey(['one', 'two'], { name: 'name' }),
54
+ ...t.index(['one', 'two'], {
55
+ name: 'indexName',
56
+ unique: true,
57
+ }),
58
+ ...t.foreignKey(
59
+ ['one', 'two'],
60
+ 'table',
61
+ ['three', 'four'],
62
+ {
63
+ name: 'foreignKeyName',
64
+ },
65
+ ),
66
+ }));
67
+ }`);
68
+ });
69
+
70
+ it('should add noPrimaryKey prop when noPrimaryKey is `ignore` in ast', async () => {
71
+ await updateTableFile({
72
+ ...params,
73
+ ast: { ...ast.addTable, noPrimaryKey: 'ignore' },
74
+ });
75
+
76
+ testWritten(`import { BaseTable } from '../baseTable';
77
+
78
+ export class Table extends BaseTable {
79
+ table = 'table';
80
+ noPrimaryKey = true;
81
+ columns = this.setColumns((t) => ({
82
+ id: t.serial().primaryKey(),
83
+ }));
84
+ }`);
85
+ });
86
+ });
@@ -0,0 +1,34 @@
1
+ import { RakeDbAst } from 'rake-db';
2
+ import { getImportPath } from '../utils';
3
+ import { Code, codeToString, columnsShapeToCode, singleQuote } from 'pqb';
4
+ import { toPascalCase } from '../../utils';
5
+ import fs from 'fs/promises';
6
+ import { UpdateTableFileParams } from './updateTableFile';
7
+
8
+ export const createTable = async ({
9
+ ast,
10
+ ...params
11
+ }: UpdateTableFileParams & { ast: RakeDbAst.Table }) => {
12
+ const tablePath = params.tablePath(ast.name);
13
+ const baseTablePath = getImportPath(tablePath, params.baseTablePath);
14
+
15
+ const props: Code[] = [`table = ${singleQuote(ast.name)};`];
16
+ if (ast.noPrimaryKey === 'ignore') {
17
+ props.push('noPrimaryKey = true;');
18
+ }
19
+
20
+ props.push(
21
+ 'columns = this.setColumns((t) => ({',
22
+ columnsShapeToCode(ast.shape, ast, 't'),
23
+ '}));',
24
+ );
25
+
26
+ const code: Code[] = [
27
+ `import { ${params.baseTableName} } from '${baseTablePath}';\n`,
28
+ `export class ${toPascalCase(ast.name)} extends ${params.baseTableName} {`,
29
+ props,
30
+ '}',
31
+ ];
32
+
33
+ await fs.writeFile(tablePath, codeToString(code, '', ' '));
34
+ };
@@ -0,0 +1,43 @@
1
+ import { updateTableFile } from './updateTableFile';
2
+ import { asMock, ast, makeTestWritten, tablePath } from '../testUtils';
3
+ import path from 'path';
4
+ import fs from 'fs/promises';
5
+
6
+ jest.mock('fs/promises', () => ({
7
+ readFile: jest.fn(),
8
+ writeFile: jest.fn(),
9
+ }));
10
+
11
+ const baseTablePath = path.resolve('baseTable.ts');
12
+ const baseTableName = 'BaseTable';
13
+ const params = { baseTablePath, baseTableName, tablePath };
14
+
15
+ const testWritten = makeTestWritten(tablePath('renamedTable'));
16
+
17
+ describe('renameTable', () => {
18
+ beforeEach(() => {
19
+ jest.resetAllMocks();
20
+ });
21
+
22
+ it('should only change `table` property', async () => {
23
+ asMock(fs.readFile)
24
+ .mockResolvedValue(`import { BaseTable } from '../baseTable';
25
+
26
+ export class Table extends BaseTable {
27
+ table = 'table';
28
+ columns = this.setColumns((t) => ({}));
29
+ }`);
30
+
31
+ await updateTableFile({
32
+ ...params,
33
+ ast: ast.renameTable,
34
+ });
35
+
36
+ testWritten(`import { BaseTable } from '../baseTable';
37
+
38
+ export class Table extends BaseTable {
39
+ table = 'renamedTable';
40
+ columns = this.setColumns((t) => ({}));
41
+ }`);
42
+ });
43
+ });
@@ -0,0 +1,40 @@
1
+ import { UpdateTableFileParams } from './updateTableFile';
2
+ import { RakeDbAst } from 'rake-db';
3
+ import fs from 'fs/promises';
4
+ import { FileChanges } from '../fileChanges';
5
+ import { ts } from '../tsUtils';
6
+ import { toPascalCase } from '../../utils';
7
+ import { Expression } from 'typescript';
8
+ import { singleQuote } from 'pqb';
9
+
10
+ export const renameTable = async ({
11
+ ast,
12
+ ...params
13
+ }: UpdateTableFileParams & { ast: RakeDbAst.RenameTable }) => {
14
+ const tablePath = params.tablePath(ast.from);
15
+ const content = await fs.readFile(tablePath, 'utf-8').catch(() => undefined);
16
+ if (!content) return;
17
+
18
+ const changes = new FileChanges(content);
19
+ const statements = ts.getStatements(content);
20
+ const className = toPascalCase(ast.from);
21
+
22
+ for (const node of ts.class.iterate(statements)) {
23
+ if (node.name?.escapedText !== className) continue;
24
+
25
+ for (const member of node.members) {
26
+ const name = ts.prop.getName(member);
27
+ if (name !== 'table') continue;
28
+
29
+ const { initializer: value } = member as unknown as {
30
+ initializer?: Expression;
31
+ };
32
+
33
+ if (!value) continue;
34
+
35
+ changes.replace(value.pos, value.end, ` ${singleQuote(ast.to)}`);
36
+ }
37
+ }
38
+
39
+ await fs.writeFile(params.tablePath(ast.to), changes.apply());
40
+ };