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 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` and `D1Preparer` to `D1Database`, 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.
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 in `defineEntity`/`defineRelation` whose entity has no foreign key column: declare it with `defineField`.
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: 'D1Database' }],
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 propertyNames(type.getConstructSignatures()[0]?.getReturnType() ?? type);
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 found by name alone, `references: (post) => post.authorId` beside `author`.
232
- * A key the entity neither declares nor inherits is declared first, on a decorated property, typed as the
233
- * target's key; elsewhere it is reported.
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, property) {
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
- if (!memberNames(owner.entity, ctx.checker).has(column)) {
246
- if (!property) {
247
- ctx.unresolved.push(`${ctx.describe(relation)}: declare the foreign key column '${column}' and name it in 'references'`);
248
- return;
249
- }
250
- const declared = foreignKeyColumn(property, column, relation, ctx);
251
- if (!declared) {
252
- return;
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, ctx) {
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
- const [soleKey] = keys;
265
- if (!target || !soleKey || keys.length > 1) {
266
- const advice = keys.length > 1
267
- ? `'${target}' has a composite key: declare a column per key and pair each in 'references'`
268
- : `declare the foreign key column '${column}' and name it in 'references'`;
269
- ctx.unresolved.push(`${ctx.describe(property)}: ${advice}`);
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 source = property.getSourceFile();
273
- const indent = ' '.repeat(source.getLineAndCharacterOfPosition(property.getStart()).character);
274
- ctx.imports.set('Field', 'the foreign key column');
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, ctx) {
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 ctx.checker.typeToString(ctx.checker.getNonNullableType(ctx.checker.getTypeOfSymbolAtLocation(key, at)), at);
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(propertyValue(options, 'fields'))) {
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((m) => ts.isPropertyDeclaration(m) && decoratorsOf(m).some((d) => decoratorName(d) === 'Id'));
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
- if (TO_ONE_DECORATORS.has(name) && options.kind === 'literal') {
671
- addForeignKeyReference(options.node, propertyKey(node.name), owner, ctx, node);
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
- if (importsUql && ts.isCallExpression(node)) {
974
- rewriteBuilderCall(node, ctx);
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.0",
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/uql-codemod"
35
+ "directory": "packages/codemod"
36
36
  },
37
37
  "keywords": [
38
38
  "uql",