uql-codemod 0.39.0 → 0.39.2
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/README.md +2 -2
- package/dist/transform.js +59 -43
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ Only `--flag=value` is read, and anything unrecognised is an error. It exits `0`
|
|
|
32
32
|
- A partial-index `where` string to `raw`, importing it, on entities and in the migration builder.
|
|
33
33
|
- `col('cost')` to the ref it names, ``(product) => raw`${product.cost}` `` in a `computed` and `refs(Item).cost` in a statement, and `$lock: { wait: 'skip' }` to `{ $wait: 'skip' }`, or to `true` for `{}` and a `'block'` wait.
|
|
34
34
|
- An aggregate's `$agg` to `$select`, and each field it or `$text`'s `$fields` names to a key: `{ $sum: { amount: true } }`.
|
|
35
|
-
- Renamed exports, at the import and every use: `QueryWhereMap` to `QueryWhere`, `RelationKeyMap` to `KeyMap`, `FilterCondition` to `FilterWhere`, `SqlMigrationModuleOptions` to `MigrationModuleOptions`, `buildSqlQuerierMigrationModule` to `buildMigrationModule`, `QueryDialect` to `SqlQueryDialect`, `EngineFeatures` to `DialectFeatures`, `KnownMigratorDialect` to `DialectName
|
|
35
|
+
- Renamed exports, at the import and every use: `QueryWhereMap` to `QueryWhere`, `RelationKeyMap` to `KeyMap`, `FilterCondition` to `FilterWhere`, `SqlMigrationModuleOptions` to `MigrationModuleOptions`, `buildSqlQuerierMigrationModule` to `buildMigrationModule`, `QueryDialect` to `SqlQueryDialect`, `EngineFeatures` to `DialectFeatures`, `KnownMigratorDialect` to `DialectName`, `D1Preparer`/`D1Database` to `D1Queryable`, and the removed driver classes to the one each extended: `PgDialect`/`NeonDialect`/`PgliteDialect` to `PostgresDialect`, `CrdbQuerier`/`NeonQuerier` to `PgQuerier`, `LibsqlQuerier`/`TursoQuerier` to `HranaQuerier`, `TursoLocalQuerier` to `SqliteQuerier` and `TursoDatabase` to `SqliteDatabase`, `MySql2Dialect` to `MySqlDialect`, `MongodbNativeDialect` to `MongoDialect`, moving the import to the entry that exports it.
|
|
36
36
|
- The `[idKey]?: '...'` brand, and its import, on a key not called `id`, `_id` or `uuid` and on every composite.
|
|
37
37
|
- `import 'reflect-metadata'`, and `experimentalDecorators`/`emitDecoratorMetadata` in `tsconfig.json`, comments and formatting kept.
|
|
38
38
|
|
|
@@ -45,7 +45,7 @@ Reported with what to do, never guessed:
|
|
|
45
45
|
- An export removed with no one-to-one replacement (`setQuerierPool`, `getQuerier`, `augmentWhere`, `AbstractPgQuerier`, ...).
|
|
46
46
|
- A value it cannot read: options passed as a variable or a spread, a list that is not a literal, a key under a computed name.
|
|
47
47
|
- A to-one onto a composite key: declare a column per key and pair each with it in `references`.
|
|
48
|
-
- A to-one
|
|
48
|
+
- A to-one whose `<relation>Id` is not a declared column: an undecorated property, a `defineEntity` whose `fields` leave it out, or a `defineRelation` whose entity has no such member.
|
|
49
49
|
- A partial-index `where` string that interpolates, since `raw` would bind what the template spliced in.
|
|
50
50
|
- A `col()` whose entity it cannot tell: a raw built apart from its query, one under a relation filter, or a column no field maps.
|
|
51
51
|
- SQL in a definition that names a column by hand, as a note: a rename reaches a callback's refs, ``(user) => raw`lower(${user.email})` ``, and never the text.
|
package/dist/transform.js
CHANGED
|
@@ -81,7 +81,8 @@ const RENAMED_EXPORTS = new Map([
|
|
|
81
81
|
['QueryDialect', { to: 'SqlQueryDialect' }],
|
|
82
82
|
['EngineFeatures', { to: 'DialectFeatures' }],
|
|
83
83
|
['KnownMigratorDialect', { to: 'DialectName', from: 'uql-orm' }],
|
|
84
|
-
['D1Preparer', { to: '
|
|
84
|
+
['D1Preparer', { to: 'D1Queryable' }],
|
|
85
|
+
['D1Database', { to: 'D1Queryable', from: 'uql-orm/d1' }],
|
|
85
86
|
]);
|
|
86
87
|
/** A node's decorators, or none where it is a kind that cannot carry them. */
|
|
87
88
|
function decoratorsOf(node) {
|
|
@@ -157,8 +158,12 @@ function noteHandNamedColumns(sql, owner, ctx) {
|
|
|
157
158
|
}
|
|
158
159
|
/** The names of the members of the entity `node` declares or names. */
|
|
159
160
|
function memberNames(node, checker) {
|
|
161
|
+
return propertyNames(instanceTypeOf(node, checker));
|
|
162
|
+
}
|
|
163
|
+
/** The instance type of the entity `node` declares or names. */
|
|
164
|
+
function instanceTypeOf(node, checker) {
|
|
160
165
|
const type = checker.getTypeAtLocation(node);
|
|
161
|
-
return
|
|
166
|
+
return type.getConstructSignatures()[0]?.getReturnType() ?? type;
|
|
162
167
|
}
|
|
163
168
|
function propertyNames(type) {
|
|
164
169
|
return new Set(type.getProperties().map(({ name }) => name));
|
|
@@ -228,11 +233,11 @@ function addRelationEntity(decorator, node, ctx) {
|
|
|
228
233
|
});
|
|
229
234
|
}
|
|
230
235
|
/**
|
|
231
|
-
* Names the foreign key a to-one
|
|
232
|
-
*
|
|
233
|
-
*
|
|
236
|
+
* Names the foreign key a to-one used to find by name, `references: (post) => post.authorId` beside `author`.
|
|
237
|
+
* `site` says where the entity declares its columns: the decorated property, ahead of which a missing column
|
|
238
|
+
* is declared, or `defineEntity`'s `fields`. A column it cannot see declared is reported instead.
|
|
234
239
|
*/
|
|
235
|
-
function addForeignKeyReference(relation, key, owner, ctx,
|
|
240
|
+
function addForeignKeyReference(relation, key, owner, ctx, site) {
|
|
236
241
|
const options = { kind: 'literal', node: relation };
|
|
237
242
|
const cardinality = propertyValue(relation, 'cardinality');
|
|
238
243
|
const toOne = !cardinality || (ts.isStringLiteralLike(cardinality) && TO_ONE_CARDINALITIES.has(cardinality.text));
|
|
@@ -242,49 +247,58 @@ function addForeignKeyReference(relation, key, owner, ctx, property) {
|
|
|
242
247
|
return;
|
|
243
248
|
}
|
|
244
249
|
const column = `${key}Id`;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
ctx.edits.push(declared);
|
|
250
|
+
const declaration = columnDeclaration(column, relation, owner, ctx.checker, site);
|
|
251
|
+
if (typeof declaration === 'string') {
|
|
252
|
+
ctx.unresolved.push(`${ctx.describe(site && ts.isPropertyDeclaration(site) ? site : relation)}: ${declaration}`);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (declaration) {
|
|
256
|
+
ctx.edits.push(declaration);
|
|
257
|
+
ctx.imports.set('Field', 'the foreign key column');
|
|
255
258
|
}
|
|
256
259
|
ctx.edits.push(appended(last, `, references: (${owner.param}) => ${memberAccess(owner.param, column)}`));
|
|
257
260
|
}
|
|
261
|
+
const undeclaredColumn = (column) => `declare the foreign key column '${column}' and name it in 'references'`;
|
|
262
|
+
/** Nothing where `column` is declared, the declaration to write where a decorated class lacks it, or why neither. */
|
|
263
|
+
function columnDeclaration(column, relation, owner, checker, site) {
|
|
264
|
+
const member = instanceTypeOf(owner.entity, checker).getProperty(column);
|
|
265
|
+
if (site && ts.isPropertyDeclaration(site)) {
|
|
266
|
+
if (!member) {
|
|
267
|
+
return foreignKeyColumn(site, column, relation, checker);
|
|
268
|
+
}
|
|
269
|
+
return member.declarations?.some(isFieldProperty) ? undefined : undeclaredColumn(column);
|
|
270
|
+
}
|
|
271
|
+
const declared = site ? objectProperties(site).some(({ name }) => propertyKey(name) === column) : member;
|
|
272
|
+
return declared ? undefined : undeclaredColumn(column);
|
|
273
|
+
}
|
|
258
274
|
/** `@Field({ references: () => Target }) <column>?: <key type>;` ahead of the relation, or why it cannot be written. */
|
|
259
|
-
function foreignKeyColumn(property, column, relation,
|
|
260
|
-
const { checker } = ctx;
|
|
275
|
+
function foreignKeyColumn(property, column, relation, checker) {
|
|
261
276
|
const targetType = checker.getNonNullableType(checker.getTypeAtLocation(property));
|
|
262
277
|
const target = entityGetterTarget(relation) ?? relationTargetFor(targetType, checker);
|
|
263
278
|
const keys = targetType.getProperties().filter((member) => member.declarations?.some(isIdProperty));
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
return undefined;
|
|
279
|
+
if (keys.length > 1) {
|
|
280
|
+
return `'${target}' has a composite key: declare a column per key and pair each in 'references'`;
|
|
281
|
+
}
|
|
282
|
+
const [key] = keys;
|
|
283
|
+
if (!target || !key) {
|
|
284
|
+
return undeclaredColumn(column);
|
|
271
285
|
}
|
|
272
|
-
const
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
return inserted(property, `@Field({ references: () => ${target} }) ${column}?: ${keyTypeSource(soleKey, property, ctx)};\n${indent}`);
|
|
286
|
+
const indent = ' '.repeat(property.getSourceFile().getLineAndCharacterOfPosition(property.getStart()).character);
|
|
287
|
+
const type = keyTypeSource(key, property, checker);
|
|
288
|
+
return inserted(property, `@Field({ references: () => ${target} }) ${column}?: ${type};\n${indent}`);
|
|
276
289
|
}
|
|
277
290
|
/** The key's type as written where that is in scope, the same file, or as the checker spells it out. */
|
|
278
|
-
function keyTypeSource(key, at,
|
|
291
|
+
function keyTypeSource(key, at, checker) {
|
|
279
292
|
const written = key.declarations?.find(isIdProperty)?.type;
|
|
280
293
|
if (written && written.getSourceFile() === at.getSourceFile()) {
|
|
281
294
|
return written.getText();
|
|
282
295
|
}
|
|
283
|
-
return
|
|
284
|
-
}
|
|
285
|
-
function isIdProperty(node) {
|
|
286
|
-
return ts.isPropertyDeclaration(node) && decoratorsOf(node).some((decorator) => decoratorName(decorator) === 'Id');
|
|
296
|
+
return checker.typeToString(checker.getNonNullableType(checker.getTypeOfSymbolAtLocation(key, at)), at);
|
|
287
297
|
}
|
|
298
|
+
/** Whether a declaration is a property carrying one of `names` as a decorator. */
|
|
299
|
+
const decoratedWith = (names) => (node) => ts.isPropertyDeclaration(node) && decoratorsOf(node).some((decorator) => names.has(decoratorName(decorator) ?? ''));
|
|
300
|
+
const isIdProperty = decoratedWith(new Set(['Id']));
|
|
301
|
+
const isFieldProperty = decoratedWith(FIELD_DECORATORS);
|
|
288
302
|
/**
|
|
289
303
|
* Writes the edits `rewrite` reads off `value`, which still names members as strings, or reports it
|
|
290
304
|
* where it could not be read: what it holds is only known at runtime. A callback or an object literal
|
|
@@ -352,6 +366,7 @@ function rewriteDefineCall(call, ctx) {
|
|
|
352
366
|
}
|
|
353
367
|
}
|
|
354
368
|
function rewriteEntityOptions(options, owner, ctx) {
|
|
369
|
+
const fields = propertyValue(options, 'fields');
|
|
355
370
|
for (const index of arrayElements(propertyValue(options, 'indexes'))) {
|
|
356
371
|
if (ts.isObjectLiteralExpression(index)) {
|
|
357
372
|
rewriteIndexOptions(index, owner, ctx);
|
|
@@ -360,7 +375,7 @@ function rewriteEntityOptions(options, owner, ctx) {
|
|
|
360
375
|
for (const check of arrayElements(propertyValue(options, 'checks'))) {
|
|
361
376
|
renameSqlOption(optionsOf(check), 'expression', 'where', check, owner, ctx);
|
|
362
377
|
}
|
|
363
|
-
for (const field of objectProperties(
|
|
378
|
+
for (const field of objectProperties(fields)) {
|
|
364
379
|
renameSqlOption(optionsOf(field.initializer), 'virtual', 'computed', field, owner, ctx);
|
|
365
380
|
}
|
|
366
381
|
for (const filter of objectProperties(propertyValue(options, 'filters'))) {
|
|
@@ -369,10 +384,11 @@ function rewriteEntityOptions(options, owner, ctx) {
|
|
|
369
384
|
for (const hook of objectProperties(propertyValue(options, 'hooks'))) {
|
|
370
385
|
rewriteKeyList(hook.initializer, owner.param, hook, 'a hook list', ctx);
|
|
371
386
|
}
|
|
387
|
+
const declared = fields && ts.isObjectLiteralExpression(fields) ? fields : undefined;
|
|
372
388
|
for (const relation of objectProperties(propertyValue(options, 'relations'))) {
|
|
373
389
|
if (ts.isObjectLiteralExpression(relation.initializer)) {
|
|
374
390
|
rewriteRelationOptions(relation.initializer, owner.param, ctx);
|
|
375
|
-
addForeignKeyReference(relation.initializer, propertyKey(relation.name), owner, ctx);
|
|
391
|
+
addForeignKeyReference(relation.initializer, propertyKey(relation.name), owner, ctx, declared);
|
|
376
392
|
}
|
|
377
393
|
}
|
|
378
394
|
}
|
|
@@ -605,7 +621,7 @@ function isIdKeyBrand(member) {
|
|
|
605
621
|
* One written makes `idKey` a value the file has to import, which is recorded for {@link addImports}.
|
|
606
622
|
*/
|
|
607
623
|
function brandIdKey(node, ctx) {
|
|
608
|
-
const ids = node.members.filter(
|
|
624
|
+
const ids = node.members.filter(isIdProperty);
|
|
609
625
|
if (!ids.length || node.members.some(isIdKeyBrand)) {
|
|
610
626
|
return;
|
|
611
627
|
}
|
|
@@ -666,9 +682,9 @@ function rewriteProperty(node, ctx) {
|
|
|
666
682
|
if (RELATION_DECORATORS.has(name) && options.kind === 'literal') {
|
|
667
683
|
const target = relationTargetFor(ctx.checker.getTypeAtLocation(node), ctx.checker);
|
|
668
684
|
rewriteRelationOptions(options.node, owner.param, ctx, paramFor(target));
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
685
|
+
if (TO_ONE_DECORATORS.has(name)) {
|
|
686
|
+
addForeignKeyReference(options.node, propertyKey(node.name), owner, ctx, node);
|
|
687
|
+
}
|
|
672
688
|
}
|
|
673
689
|
}
|
|
674
690
|
if (decorators.length) {
|
|
@@ -969,9 +985,9 @@ export function transformFile(source, checker) {
|
|
|
969
985
|
}
|
|
970
986
|
if (ts.isCallExpression(node)) {
|
|
971
987
|
rewriteDefineCall(node, ctx);
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
988
|
+
if (importsUql) {
|
|
989
|
+
rewriteBuilderCall(node, ctx);
|
|
990
|
+
}
|
|
975
991
|
}
|
|
976
992
|
if (ts.isPropertyAssignment(node)) {
|
|
977
993
|
rewriteStatementKeys(node, ctx);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "uql-codemod",
|
|
3
3
|
"description": "Upgrade uql-orm across versions that introduce breaking changes",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.39.
|
|
5
|
+
"version": "0.39.2",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=24"
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"repository": {
|
|
33
33
|
"type": "git",
|
|
34
34
|
"url": "git+https://github.com/rogerpadilla/uql.git",
|
|
35
|
-
"directory": "packages/
|
|
35
|
+
"directory": "packages/codemod"
|
|
36
36
|
},
|
|
37
37
|
"keywords": [
|
|
38
38
|
"uql",
|