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