uql-codemod 0.39.1 → 0.40.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
@@ -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
 
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) {
@@ -284,7 +285,8 @@ function foreignKeyColumn(property, column, relation, checker) {
284
285
  }
285
286
  const indent = ' '.repeat(property.getSourceFile().getLineAndCharacterOfPosition(property.getStart()).character);
286
287
  const type = keyTypeSource(key, property, checker);
287
- return inserted(property, `@Field({ references: () => ${target} }) ${column}?: ${type};\n${indent}`);
288
+ // `| null`, as on every column the codemod touches: a foreign key holds one like any other.
289
+ return inserted(property, `@Field({ references: () => ${target} }) ${column}?: ${type} | null;\n${indent}`);
288
290
  }
289
291
  /** The key's type as written where that is in scope, the same file, or as the checker spells it out. */
290
292
  function keyTypeSource(key, at, checker) {
@@ -376,6 +378,7 @@ function rewriteEntityOptions(options, owner, ctx) {
376
378
  }
377
379
  for (const field of objectProperties(fields)) {
378
380
  renameSqlOption(optionsOf(field.initializer), 'virtual', 'computed', field, owner, ctx);
381
+ admitNullOnDefined(field, owner, ctx);
379
382
  }
380
383
  for (const filter of objectProperties(propertyValue(options, 'filters'))) {
381
384
  renameOption(optionsOf(filter.initializer), 'condition', 'where', filter, ctx);
@@ -668,6 +671,9 @@ function rewriteProperty(node, ctx) {
668
671
  }
669
672
  const options = decoratorOptions(decorator);
670
673
  if (FIELD_DECORATORS.has(name)) {
674
+ if (name === 'Field') {
675
+ admitNull(options, node, ctx);
676
+ }
671
677
  addFieldType(decorator, node, ctx);
672
678
  // Options it cannot read may still hold `virtual`, so they are reported where they mention it.
673
679
  if (options.kind === 'opaque' && /\bvirtual\b/.test(decorator.getText())) {
@@ -691,6 +697,64 @@ function rewriteProperty(node, ctx) {
691
697
  }
692
698
  unwrapRelationAlias(node, ctx);
693
699
  }
700
+ /**
701
+ * A column holds `null` unless `nullable: false` says otherwise, and a read hydrates one, so the property
702
+ * says so too. A key is NOT NULL on every engine, and a relation aggregate is typed by the aggregate,
703
+ * which declares its own `null` or none, so neither is touched.
704
+ */
705
+ function admitNull(options, node, ctx) {
706
+ // Options it cannot read may state `nullable` themselves, and appending to the property would then
707
+ // contradict the column. `addFieldType` already reports the same options, so this adds no second note.
708
+ if (options.kind === 'opaque') {
709
+ return;
710
+ }
711
+ if (declaresNotNull(options) ||
712
+ findProperty(options, 'isId') ||
713
+ (findProperty(options, 'computed') && !findProperty(options, 'type'))) {
714
+ return;
715
+ }
716
+ if (!node.type) {
717
+ ctx.notes.push(`${ctx.describe(node)}: declare '${propertyKey(node.name)}' as a type that admits 'null', which the column holds`);
718
+ return;
719
+ }
720
+ if (admitsNull(node.type)) {
721
+ return;
722
+ }
723
+ ctx.edits.push(appended(node.type, ' | null'));
724
+ }
725
+ /**
726
+ * The same null rule where `defineEntity` names the field: the property is on the class the call takes,
727
+ * so the declaration is found there rather than under a decorator.
728
+ */
729
+ function admitNullOnDefined(field, owner, ctx) {
730
+ if (!ts.isPropertyAssignment(field)) {
731
+ return;
732
+ }
733
+ const key = propertyKey(field.name);
734
+ const declaration = key
735
+ ? instanceTypeOf(owner.entity, ctx.checker)
736
+ .getProperty(key)
737
+ ?.declarations?.find((it) => ts.isPropertyDeclaration(it))
738
+ : undefined;
739
+ if (declaration?.type) {
740
+ admitNull(optionsOf(field.initializer), declaration, ctx);
741
+ }
742
+ }
743
+ /** Whether the options say the column refuses `null`; `nullable: true` is the default said out loud. */
744
+ function declaresNotNull(options) {
745
+ const nullable = findProperty(options, 'nullable');
746
+ return !!nullable && ts.isPropertyAssignment(nullable) && nullable.initializer.kind === ts.SyntaxKind.FalseKeyword;
747
+ }
748
+ /**
749
+ * Whether a written type already admits `null`, wherever it sits in the union. In a type position `null`
750
+ * parses as a literal type rather than a keyword, which is why the kind is read off `literal`.
751
+ */
752
+ function admitsNull(type) {
753
+ if (ts.isLiteralTypeNode(type)) {
754
+ return type.literal.kind === ts.SyntaxKind.NullKeyword;
755
+ }
756
+ return ts.isUnionTypeNode(type) && type.types.some(admitsNull);
757
+ }
694
758
  /** Names a decorator that no longer exists, wherever it appears. */
695
759
  function reportRemovedDecorators(node, ctx) {
696
760
  for (const decorator of decoratorsOf(node)) {
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.1",
5
+ "version": "0.40.0",
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",