uql-codemod 0.48.1 → 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +27 -16
  2. package/dist/builders.d.ts +18 -0
  3. package/dist/builders.js +588 -0
  4. package/dist/classOptions.d.ts +9 -0
  5. package/dist/classOptions.js +240 -0
  6. package/dist/columnMethods.d.ts +11 -0
  7. package/dist/columnMethods.js +6 -0
  8. package/dist/columnRefs.js +3 -10
  9. package/dist/context.d.ts +18 -5
  10. package/dist/context.js +22 -4
  11. package/dist/definers.d.ts +24 -0
  12. package/dist/definers.js +34 -0
  13. package/dist/edits.d.ts +43 -11
  14. package/dist/edits.js +105 -26
  15. package/dist/entitySql.d.ts +15 -16
  16. package/dist/entitySql.js +33 -31
  17. package/dist/imports.d.ts +33 -16
  18. package/dist/imports.js +213 -99
  19. package/dist/index.d.ts +3 -3
  20. package/dist/index.js +9 -12
  21. package/dist/jsonIndex.d.ts +11 -0
  22. package/dist/jsonIndex.js +78 -0
  23. package/dist/keyMaps.d.ts +3 -1
  24. package/dist/keyMaps.js +5 -1
  25. package/dist/migrationBuilders.d.ts +13 -0
  26. package/dist/migrationBuilders.js +182 -0
  27. package/dist/relationTarget.d.ts +6 -0
  28. package/dist/relationTarget.js +21 -0
  29. package/dist/schemaEntity.d.ts +12 -0
  30. package/dist/schemaEntity.js +518 -0
  31. package/dist/schemaOptions.d.ts +6 -0
  32. package/dist/schemaOptions.js +6 -0
  33. package/dist/statementSql.d.ts +2 -1
  34. package/dist/statementSql.js +10 -4
  35. package/dist/syntax.d.ts +54 -0
  36. package/dist/syntax.js +131 -1
  37. package/dist/transform.d.ts +11 -2
  38. package/dist/transform.js +174 -165
  39. package/dist/tsconfig.d.ts +3 -9
  40. package/dist/tsconfig.js +18 -22
  41. package/package.json +1 -1
  42. package/dist/fieldType.d.ts +0 -13
  43. package/dist/fieldType.js +0 -56
@@ -0,0 +1,182 @@
1
+ import ts from 'typescript';
2
+ import { call, flag, flagOrCall } from './columnMethods.js';
3
+ import { formatFindings, locationAt } from './context.js';
4
+ import { applyEdits, replaced } from './edits.js';
5
+ import { chainOf, identifierText, propertiesByKey } from './syntax.js';
6
+ /** The migration builder's calls a column is declared by, and the callback each hands its builder to. */
7
+ const DECLARING_CALLS = new Set(['createTable', 'alterTable', 'addColumn', 'alterColumn']);
8
+ /** The column factories of a migration's table builder. */
9
+ export const FACTORY_NAMES = [
10
+ 'id',
11
+ 'integer',
12
+ 'smallint',
13
+ 'bigint',
14
+ 'float',
15
+ 'double',
16
+ 'decimal',
17
+ 'string',
18
+ 'char',
19
+ 'text',
20
+ 'boolean',
21
+ 'date',
22
+ 'time',
23
+ 'timestamp',
24
+ 'timestamptz',
25
+ 'json',
26
+ 'jsonb',
27
+ 'uuid',
28
+ 'blob',
29
+ 'vector',
30
+ ];
31
+ const FACTORIES = new Set(FACTORY_NAMES);
32
+ /** A chained method renamed, or removed where it now says what a column already is. */
33
+ const RENAMED = {
34
+ notNullable: () => '',
35
+ nullable: ([value]) => (value === 'false' ? '' : '.nullable()'),
36
+ defaultValue: (args) => `.default(${args.join(', ')})`,
37
+ primaryKey: () => '.id()',
38
+ computed: (args) => `.as(${args.join(', ')})`,
39
+ };
40
+ /** Each column option the factories took as a method now, in the order a column chains them. */
41
+ const OPTION_METHODS = [
42
+ ['length', call('length')],
43
+ ['precision', call('precision')],
44
+ ['scale', call('scale')],
45
+ ['nullable', flag('nullable')],
46
+ ['primaryKey', flag('id')],
47
+ ['autoIncrement', flag('autoIncrement')],
48
+ ['unique', flag('unique')],
49
+ ['unsigned', flag('unsigned')],
50
+ ['defaultValue', call('default')],
51
+ ['index', flagOrCall('index')],
52
+ ['comment', call('comment')],
53
+ ];
54
+ /**
55
+ * Writes a migration's columns in an entity column's vocabulary: `table.string('email', { length: 80 })` as
56
+ * `table.string('email').length(80)`, `.defaultValue()` as `.default()`, `.primaryKey()` as `.id()`, `.computed()`
57
+ * as `.as()`, and `.notNullable()` dropped, a column being NOT NULL already. Only a chain off the builder a
58
+ * `createTable`, `alterTable`, `addColumn` or `alterColumn` callback is handed, so no other `.string()` is touched.
59
+ */
60
+ export function toMigrationBuilders(fileName, text, locate) {
61
+ if (![...DECLARING_CALLS].some((call) => text.includes(`${call}(`))) {
62
+ return { text, unresolved: [] };
63
+ }
64
+ const source = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true);
65
+ const edits = [];
66
+ const findings = [];
67
+ const visit = (node) => {
68
+ if (ts.isCallExpression(node) && isDeclaringCall(node)) {
69
+ for (const callback of node.arguments) {
70
+ const builder = ts.isArrowFunction(callback) && identifierText(callback.parameters[0]?.name);
71
+ if (builder) {
72
+ collect(callback.body, builder, edits, (at, message) => findings.push({ node: at, message }));
73
+ }
74
+ }
75
+ }
76
+ ts.forEachChild(node, visit);
77
+ };
78
+ visit(source);
79
+ return {
80
+ text: edits.length ? applyEdits(text, edits) : text,
81
+ unresolved: formatFindings(findings, locate ?? ((at) => locationAt(source, at))),
82
+ };
83
+ }
84
+ function isDeclaringCall(call) {
85
+ return ts.isPropertyAccessExpression(call.expression) && DECLARING_CALLS.has(call.expression.name.text);
86
+ }
87
+ /** Each column chain in `body` rooted at `builder`, the outermost call of each rewritten whole. */
88
+ function collect(body, builder, edits, report) {
89
+ const visit = (node) => {
90
+ if (ts.isCallExpression(node) && !isChained(node)) {
91
+ const rewritten = chainSource(node, builder, report);
92
+ if (rewritten !== undefined) {
93
+ if (rewritten !== node.getText())
94
+ edits.push(replaced(node, rewritten));
95
+ return;
96
+ }
97
+ }
98
+ ts.forEachChild(node, visit);
99
+ };
100
+ visit(body);
101
+ }
102
+ /** Whether `call` is the receiver of a further call in the chain, which the outermost one rewrites. */
103
+ function isChained(call) {
104
+ return ts.isPropertyAccessExpression(call.parent);
105
+ }
106
+ /** The chain as source in the new vocabulary, or `undefined` where it is no column chain off `builder`. */
107
+ function chainSource(outer, builder, report) {
108
+ const { root, methods } = chainOf(outer);
109
+ const [first, ...chained] = methods;
110
+ if (!first || identifierText(root) !== builder || !FACTORIES.has(first.name)) {
111
+ return undefined;
112
+ }
113
+ const start = factorySource(first.call, first.name, builder, report);
114
+ if (start === undefined) {
115
+ return outer.getText();
116
+ }
117
+ return start + chained.map(methodSource).join('');
118
+ }
119
+ /** `table.vector('embedding', { dimensions: 3, nullable: true })` as `table.vector('embedding', 3).nullable()`. */
120
+ function factorySource(root, factory, builder, report) {
121
+ const [name, options] = root.arguments;
122
+ const nameText = name?.getText() ?? '';
123
+ if (!options) {
124
+ return root.getText();
125
+ }
126
+ const props = propertiesByKey(options);
127
+ if (!props) {
128
+ report(root, `cannot write '${options.getText()}' as the column's methods: chain each option as one`);
129
+ return undefined;
130
+ }
131
+ const text = (key) => {
132
+ const value = props.get(key)?.getText();
133
+ props.delete(key);
134
+ return value;
135
+ };
136
+ const args = [nameText];
137
+ if (factory === 'vector') {
138
+ const dimensions = text('dimensions');
139
+ if (dimensions !== undefined)
140
+ args.push(dimensions);
141
+ }
142
+ const references = props.get('references');
143
+ props.delete('references');
144
+ const methods = [];
145
+ for (const [option, method] of OPTION_METHODS) {
146
+ const value = text(option);
147
+ const written = value === undefined ? '' : method(value);
148
+ if (written === undefined) {
149
+ report(root, `cannot write '${option}: ${value}' as a method: chain it, or not, by hand`);
150
+ return undefined;
151
+ }
152
+ methods.push(written);
153
+ }
154
+ if (props.size) {
155
+ report(root, `has options no column method writes: ${[...props.keys()].join(', ')}`);
156
+ return undefined;
157
+ }
158
+ const foreign = references && referencesSource(references);
159
+ if (references && foreign === undefined) {
160
+ report(root, `cannot write '${references.getText()}' as '.references(table, column)'`);
161
+ return undefined;
162
+ }
163
+ return `${builder}.${factory}(${args.join(', ')})${methods.join('')}${foreign ?? ''}`;
164
+ }
165
+ /** `{ table: 'users', column: 'id', onDelete: 'CASCADE' }` as `.references('users', 'id').onDelete('CASCADE')`. */
166
+ function referencesSource(value) {
167
+ const props = propertiesByKey(value);
168
+ const table = props?.get('table')?.getText();
169
+ if (!table) {
170
+ return undefined;
171
+ }
172
+ const [column, onDelete, onUpdate] = ['column', 'onDelete', 'onUpdate'].map((key) => props?.get(key)?.getText());
173
+ return (`.references(${[table, column].filter(Boolean).join(', ')})` +
174
+ (onDelete ? `.onDelete(${onDelete})` : '') +
175
+ (onUpdate ? `.onUpdate(${onUpdate})` : ''));
176
+ }
177
+ /** One chained call in the new vocabulary. */
178
+ function methodSource({ call, name }) {
179
+ const args = call.arguments.map((arg) => arg.getText());
180
+ const rename = RENAMED[name];
181
+ return rename ? rename(args) : `.${name}(${args.join(', ')})`;
182
+ }
@@ -0,0 +1,6 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * The entity name a relation property points at: `Company` for `Company` and `Company[]` alike, since the
4
+ * element of an array is what a to-many relation targets.
5
+ */
6
+ export declare function relationTargetFor(type: ts.Type, checker: ts.TypeChecker): string | undefined;
@@ -0,0 +1,21 @@
1
+ import ts from 'typescript';
2
+ /** Reads a union by requiring its arms to agree, `undefined` and `null` dropped first: `Company | null` is `Company`. */
3
+ function foldUnion(type, read) {
4
+ const arms = type.types.filter((arm) => !(arm.getFlags() & (ts.TypeFlags.Undefined | ts.TypeFlags.Null)));
5
+ const readings = new Set(arms.map(read));
6
+ return readings.size === 1 ? [...readings][0] : undefined;
7
+ }
8
+ /**
9
+ * The entity name a relation property points at: `Company` for `Company` and `Company[]` alike, since the
10
+ * element of an array is what a to-many relation targets.
11
+ */
12
+ export function relationTargetFor(type, checker) {
13
+ if (type.isUnion()) {
14
+ return foldUnion(type, (arm) => relationTargetFor(arm, checker));
15
+ }
16
+ const elementType = checker.getIndexTypeOfType(type, ts.IndexKind.Number);
17
+ const target = elementType ?? type;
18
+ const symbol = target.getSymbol();
19
+ const name = symbol?.getName();
20
+ return name && name !== '__type' && /^[A-Z][A-Za-z0-9_]*$/.test(name) ? name : undefined;
21
+ }
@@ -0,0 +1,12 @@
1
+ import { type Pass } from './edits.js';
2
+ export type SchemaResult = Pass & {
3
+ readonly unresolved: string[];
4
+ readonly notes: string[];
5
+ };
6
+ /**
7
+ * Declares each class `defineEntity(Class, options)` registered as the schema `defineEntity({ name, ... })`,
8
+ * folding the string-keyed definers of the same class in. Read off the text the other rewrites produced,
9
+ * whose options already name members by callback, so it only moves what is written. An entity it cannot
10
+ * declare faithfully is left as written and reported.
11
+ */
12
+ export declare function toSchemaEntities(fileName: string, text: string, locate?: (at: number) => string): SchemaResult;