uql-codemod 0.36.0 → 0.37.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.
package/README.md CHANGED
@@ -27,11 +27,11 @@ Only `--flag=value` is read, and anything unrecognised is an error. It exits `0`
27
27
  - `type` on a `@Field`/`@Id` that has none, from the property's declared type; not beside `references`, whose column comes from the key it points at.
28
28
  - `entity: () => X` on a bare relation decorator, and `Relation<T>` to `T`, its import going with the last use.
29
29
  - `virtual` to `computed` (on `@Field` and in `defineEntity`'s `fields`), a check's `expression` and a filter's `condition` to `where` (`@Entity`/`defineEntity`, `@Filter`, `defineFilter`), `raw('sql')` to the tagged template, and a `raw` alias argument onto `.as()`.
30
- - Members named by string to key-map callbacks: `mappedBy: 'author'` to `(post) => post.author`, and `@Index`, `include`, `references` and `@Entity`/`defineEntity`'s `indexes` and `hooks` likewise: `@Index((post) => [post.title])`.
31
- - An index expression to the callback an index takes, as an entry or a `column`: ``raw`lower("email")` `` to ``() => raw`lower("email")` ``, and a partial-index `where` string to `raw`, importing it; on entities and in the migration builder.
30
+ - Members named by string to callbacks: `mappedBy: 'author'` to `(post) => post.author`, and `@Index`, `include`, `references` and `@Entity`/`defineEntity`'s `indexes` and `hooks` likewise: `@Index((post) => [post.title])`.
31
+ - A partial-index `where` string to `raw`, importing it, on entities and in the migration builder.
32
32
  - `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.
33
33
  - An aggregate's `$agg` to `$select`, and each field it or `$text`'s `$fields` names to a key: `{ $sum: { amount: true } }`.
34
- - Renamed exports, at the import and every use: `QueryWhereMap` to `QueryWhere`, `RelationKeyMap` to `KeyMap`, `FilterCondition` to `FilterWhere`, and the removed driver classes to the one each extended: `PgDialect`/`NeonDialect`/`PgliteDialect` to `PostgresDialect`, `CrdbQuerier`/`NeonQuerier` to `PgQuerier`, `LibsqlQuerier`/`TursoQuerier` to `HranaQuerier`, `MySql2Dialect` to `MySqlDialect`, `MongodbNativeDialect` to `MongoDialect`, moving the import to the entry that exports it.
34
+ - Renamed exports, at the import and every use: `QueryWhereMap` to `QueryWhere`, `RelationKeyMap` to `KeyMap`, `FilterCondition` to `FilterWhere`, `SqlMigrationModuleOptions` to `MigrationModuleOptions` and `buildSqlQuerierMigrationModule` to `buildMigrationModule`, 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
35
  - The `[idKey]?: '...'` brand, and its import, on a key not called `id`, `_id` or `uuid` and on every composite.
36
36
  - `import 'reflect-metadata'`, and `experimentalDecorators`/`emitDecoratorMetadata` in `tsconfig.json`, comments and formatting kept.
37
37
 
@@ -3,12 +3,6 @@ import { type Edit } from './edits.js';
3
3
  /** `raw\`...\`` or `raw(...)`. */
4
4
  type RawSql = ts.TaggedTemplateExpression | ts.CallExpression;
5
5
  export declare function isRaw(node: ts.Node): node is RawSql;
6
- /**
7
- * `() => ` before each expression a column list indexes, which an index takes as a callback now. One that
8
- * interpolates takes the list callback's parameter as its refs: `(user) => [raw\`lower(${user.email})\`]`
9
- * spliced in the key `'email'`, where the ref it reads now renders the column.
10
- */
11
- export declare function expressionEntryEdits(list: ts.Expression | undefined): readonly Edit[];
12
6
  /** The expressions of a column list, written as one or returned by its callback: each entry or `column` that is `raw`. */
13
7
  export declare function columnExpressions(list: ts.Expression | undefined): readonly RawSql[];
14
8
  /** SQL text as a `raw` tagged template, escaping what would end or interpolate it. */
package/dist/entitySql.js CHANGED
@@ -9,16 +9,6 @@ export function isRaw(node) {
9
9
  : undefined;
10
10
  return callee !== undefined && ts.isIdentifier(callee) && callee.text === 'raw';
11
11
  }
12
- /**
13
- * `() => ` before each expression a column list indexes, which an index takes as a callback now. One that
14
- * interpolates takes the list callback's parameter as its refs: `(user) => [raw\`lower(${user.email})\`]`
15
- * spliced in the key `'email'`, where the ref it reads now renders the column.
16
- */
17
- export function expressionEntryEdits(list) {
18
- const param = list && ts.isArrowFunction(list) ? list.parameters[0]?.name : undefined;
19
- const refs = param && ts.isIdentifier(param) ? param.text : '';
20
- return columnExpressions(list).map((sql) => inserted(sql, `(${interpolates(sql) ? refs : ''}) => `));
21
- }
22
12
  /** The expressions of a column list, written as one or returned by its callback: each entry or `column` that is `raw`. */
23
13
  export function columnExpressions(list) {
24
14
  const body = list && ts.isArrowFunction(list) ? list.body : list;
@@ -28,9 +18,6 @@ export function columnExpressions(list) {
28
18
  return sql && isRaw(sql) ? [sql] : [];
29
19
  });
30
20
  }
31
- function interpolates(sql) {
32
- return ts.isTaggedTemplateExpression(sql) && ts.isTemplateExpression(sql.template);
33
- }
34
21
  /** SQL text as a `raw` tagged template, escaping what would end or interpolate it. */
35
22
  export function rawTag(sql) {
36
23
  return `raw\`${sql.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${')}\``;
package/dist/keyMaps.d.ts CHANGED
@@ -4,6 +4,8 @@ import { type Edit } from './edits.js';
4
4
  export type Edits = readonly Edit[] | undefined;
5
5
  /** A callback's parameter, named after the class it reads: `Post` -> `post`. */
6
6
  export declare function paramFor(className: string | undefined): string;
7
+ /** `text` as a single-quoted string literal, escaping what would end it. */
8
+ export declare function quoted(text: string): string;
7
9
  /** `post.title`, or `post['first-name']` for a key that is no identifier. */
8
10
  export declare function memberAccess(param: string, key: string): string;
9
11
  /** A key's name where it is spelled out, which is every form but a computed one (`{ [k]: v }`). */
package/dist/keyMaps.js CHANGED
@@ -7,9 +7,13 @@ export function paramFor(className) {
7
7
  function isIdentifierName(key) {
8
8
  return /^[\p{ID_Start}$_][\p{ID_Continue}$\u200C\u200D]*$/u.test(key);
9
9
  }
10
+ /** `text` as a single-quoted string literal, escaping what would end it. */
11
+ export function quoted(text) {
12
+ return `'${text.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
13
+ }
10
14
  /** `post.title`, or `post['first-name']` for a key that is no identifier. */
11
15
  export function memberAccess(param, key) {
12
- return isIdentifierName(key) ? `${param}.${key}` : `${param}['${key}']`;
16
+ return isIdentifierName(key) ? `${param}.${key}` : `${param}[${quoted(key)}]`;
13
17
  }
14
18
  /** A key's name where it is spelled out, which is every form but a computed one (`{ [k]: v }`). */
15
19
  export function propertyKey(name) {
@@ -26,7 +30,7 @@ export function fieldKeysEdits(value) {
26
30
  if (!names.every((name) => ts.isStringLiteralLike(name))) {
27
31
  return undefined;
28
32
  }
29
- const keys = names.map(({ text }) => `${isIdentifierName(text) ? text : `'${text}'`}: true`);
33
+ const keys = names.map(({ text }) => `${isIdentifierName(text) ? text : quoted(text)}: true`);
30
34
  return [replaced(value, `{ ${keys.join(', ')} }`)];
31
35
  }
32
36
  /** Whether `value` is already a function, the form every rewrite here produces. */
@@ -61,7 +65,7 @@ export function entityGetterTarget(relation) {
61
65
  const getter = propertyValue(relation, 'entity');
62
66
  return getter && ts.isArrowFunction(getter) && ts.isIdentifier(getter.body) ? getter.body.text : undefined;
63
67
  }
64
- /** A column-list entry, its name read off the key map: an expression, or a `column` given otherwise, has none. */
68
+ /** A column-list entry, its name read off the refs: an expression, or a `column` given otherwise, has none. */
65
69
  function keyListEntryEdits(entry, param) {
66
70
  if (ts.isStringLiteralLike(entry)) {
67
71
  return [replaced(entry, memberAccess(param, entry.text))];
package/dist/transform.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import ts from 'typescript';
2
2
  import { applyEdits, inserted, removeFromList, replaced } from './edits.js';
3
- import { columnExpressions, expressionEntryEdits, handNamedColumns, isRaw, rawTag, rawWhereEdit } from './entitySql.js';
3
+ import { columnExpressions, handNamedColumns, isRaw, rawTag, rawWhereEdit } from './entitySql.js';
4
4
  import { fieldTypeFor, isBrandedString, relationTargetFor } from './fieldType.js';
5
- import { entityGetterTarget, fieldKeysEdits, isCallback, keyListEdits, mappedByEdits, memberAccess, paramFor, propertyKey, propertyValue, referencesEdits, } from './keyMaps.js';
5
+ import { entityGetterTarget, fieldKeysEdits, isCallback, keyListEdits, mappedByEdits, memberAccess, paramFor, propertyKey, propertyValue, quoted, referencesEdits, } from './keyMaps.js';
6
6
  const FIELD_DECORATORS = new Set(['Field', 'Id']);
7
7
  const RELATION_DECORATORS = new Set(['OneToOne', 'ManyToOne', 'OneToMany', 'ManyToMany']);
8
8
  /**
@@ -31,6 +31,18 @@ const REMOVED_EXPORTS = new Map([
31
31
  ['augmentWhere', 'spread the two maps: `{ ...where, ...extra }`'],
32
32
  ['buildQueryWhereAsMap', 'a `$where` is a map already; name the key for ids: `{ id: [1, 2] }`'],
33
33
  ['AbstractPgQuerier', 'every pg-compatible pool returns `PgQuerier` (`uql-orm/postgres`), which is concrete'],
34
+ ['AbstractHranaQuerierPool', 'extend `AbstractSqlQuerierPool` and hand every querier a `HranaQuerier`'],
35
+ [
36
+ 'PreparedSqliteQuerier',
37
+ 'use `SqliteQuerier`, which takes any driver that prepares, or extend `AbstractSqliteQuerier`',
38
+ ],
39
+ ['toSqliteBindValues', 'pass the values as they are: every SQLite driver binds a `SqliteBindValue[]`'],
40
+ ['libsqlUseRemoteForMigrations', '`LibsqlQuerierPool.getMigrationQuerier()` decides it'],
41
+ ['SqlCallback', 'use `EntitySql<E>`, which holds the callback form'],
42
+ [
43
+ 'IndexColumnOptions',
44
+ "an entity's index entry is `EntityIndexColumnInput<E>`, the migration builder's `IndexColumnInput`",
45
+ ],
34
46
  ]);
35
47
  /**
36
48
  * Exports renamed and nothing else - the driver classes were empty subclasses - so the import and every
@@ -49,6 +61,10 @@ const RENAMED_EXPORTS = new Map([
49
61
  ['MongodbNativeDialect', { to: 'MongoDialect', from: 'uql-orm/mongo' }],
50
62
  ['LibsqlQuerier', { to: 'HranaQuerier', from: 'uql-orm/sqlite' }],
51
63
  ['TursoQuerier', { to: 'HranaQuerier', from: 'uql-orm/sqlite' }],
64
+ ['TursoLocalQuerier', { to: 'SqliteQuerier', from: 'uql-orm/sqlite' }],
65
+ ['TursoDatabase', { to: 'SqliteDatabase', from: 'uql-orm/sqlite' }],
66
+ ['SqlMigrationModuleOptions', { to: 'MigrationModuleOptions' }],
67
+ ['buildSqlQuerierMigrationModule', { to: 'buildMigrationModule' }],
52
68
  ]);
53
69
  /** A node's decorators, or none where it is a kind that cannot carry them. */
54
70
  function decoratorsOf(node) {
@@ -212,11 +228,11 @@ function rewriteValue(value, rewrite, node, advice, ctx) {
212
228
  }
213
229
  }
214
230
  function rewriteKeyList(value, param, node, what, ctx) {
215
- rewriteValue(value, (list) => keyListEdits(list, param), node, `write ${what} as a key-map callback`, ctx);
231
+ rewriteValue(value, (list) => keyListEdits(list, param), node, `write ${what} as a callback`, ctx);
216
232
  }
217
233
  /**
218
234
  * Rewrites a class's decorators - `@Index`, `@Filter` and `@Entity`'s options - for what they define now:
219
- * members named by string read off key maps named after the class, and SQL written as it is taken.
235
+ * members named by string read off a callback's parameter named after the class, and SQL written as it is taken.
220
236
  */
221
237
  function rewriteClassDecorators(node, ctx) {
222
238
  const owner = { param: paramFor(node.name?.text), entity: node };
@@ -288,23 +304,22 @@ function rewriteIndexOptions(index, owner, ctx) {
288
304
  }
289
305
  /**
290
306
  * An index, as `@Index(columns, options)` or a `defineIndex`/`indexes` entry holding both: its columns and
291
- * `include` read off the key map, its SQL written as {@link rewriteIndexSql} writes it, and noted.
307
+ * `include` read off the entity's refs, its `where` written as {@link rewriteIndexWhere} writes it, its SQL noted.
292
308
  */
293
309
  function rewriteIndex(columns, options, owner, node, what, ctx) {
294
310
  const literal = options && ts.isObjectLiteralExpression(options) ? options : undefined;
295
311
  rewriteKeyList(columns, owner.param, node, what, ctx);
296
312
  rewriteKeyList(literal && propertyValue(literal, 'include'), owner.param, node, "'include'", ctx);
297
- rewriteIndexSql(columns, literal, ctx);
313
+ rewriteIndexWhere(literal, ctx);
298
314
  for (const sql of [...columnExpressions(columns), literal && propertyValue(literal, 'where')]) {
299
315
  noteHandNamedColumns(sql, owner, ctx);
300
316
  }
301
317
  }
302
318
  /**
303
- * An index's SQL as an index takes it now, on an entity and in the migration builder alike: each expression
304
- * a callback, and a `where` string `raw`. A string it cannot write as `raw` is reported.
319
+ * A partial-index `where` string as `raw`, on an entity and in the migration builder alike. A string it
320
+ * cannot write as `raw` is reported.
305
321
  */
306
- function rewriteIndexSql(columns, options, ctx) {
307
- ctx.edits.push(...expressionEntryEdits(columns));
322
+ function rewriteIndexWhere(options, ctx) {
308
323
  const where = options && propertyValue(options, 'where');
309
324
  if (!where || !isStringTyped(ctx.checker.getTypeAtLocation(where))) {
310
325
  return;
@@ -322,14 +337,14 @@ function rewriteIndexSql(columns, options, ctx) {
322
337
  function isStringTyped(type) {
323
338
  return type.isUnion() ? type.types.every(isStringTyped) : Boolean(type.flags & ts.TypeFlags.StringLike);
324
339
  }
325
- /** The migration builder's index methods, each with the position of the columns it takes. */
340
+ /** The migration builder's index methods, each with the position of the options it takes. */
326
341
  const BUILDER_INDEX_METHODS = new Map([
327
- ['index', 0],
328
- ['unique', 0],
329
- ['addIndex', 0],
330
- ['createIndex', 1],
342
+ ['index', 1],
343
+ ['unique', 1],
344
+ ['addIndex', 1],
345
+ ['createIndex', 2],
331
346
  ]);
332
- /** Rewrites the SQL of the migration builder's `t.index()`, `t.unique()`, `t.addIndex()` and `m.createIndex()`. */
347
+ /** Rewrites the `where` of the migration builder's `t.index()`, `t.unique()`, `t.addIndex()` and `m.createIndex()`. */
333
348
  function rewriteBuilderCall(call, ctx) {
334
349
  const at = ts.isPropertyAccessExpression(call.expression)
335
350
  ? BUILDER_INDEX_METHODS.get(call.expression.name.text)
@@ -337,15 +352,15 @@ function rewriteBuilderCall(call, ctx) {
337
352
  if (at === undefined) {
338
353
  return;
339
354
  }
340
- const options = call.arguments[at + 1];
341
- rewriteIndexSql(call.arguments[at], options && ts.isObjectLiteralExpression(options) ? options : undefined, ctx);
355
+ const options = call.arguments[at];
356
+ rewriteIndexWhere(options && ts.isObjectLiteralExpression(options) ? options : undefined, ctx);
342
357
  }
343
358
  /**
344
359
  * `owner` is the declaring class's parameter and `target` the related one's, read off the `entity`
345
360
  * getter unless the caller knows it from the property's type.
346
361
  */
347
362
  function rewriteRelationOptions(relation, owner, ctx, target = paramFor(entityGetterTarget(relation))) {
348
- const advice = (what) => `write '${what}' as a key-map callback`;
363
+ const advice = (what) => `write '${what}' as a callback`;
349
364
  const mappedBy = (value) => mappedByEdits(value, target);
350
365
  const references = (value) => referencesEdits(value, owner, target);
351
366
  rewriteValue(propertyValue(relation, 'mappedBy'), mappedBy, relation, advice('mappedBy'), ctx);
@@ -526,7 +541,7 @@ function brandIdKey(node, ctx) {
526
541
  return;
527
542
  }
528
543
  const indent = ' '.repeat(node.getSourceFile().getLineAndCharacterOfPosition(ids[0].getStart()).character);
529
- const brand = names.map((name) => `'${name}'`).join(' | ');
544
+ const brand = names.map(quoted).join(' | ');
530
545
  const start = node.members.pos;
531
546
  ctx.edits.push({ start, end: start, text: `\n${indent}[idKey]?: ${brand};` });
532
547
  ctx.imports.set('idKey', 'the brand(s)');
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.36.0",
5
+ "version": "0.37.0",
6
6
  "type": "module",
7
7
  "engines": {
8
8
  "node": ">=24"