uql-orm 0.25.1 → 0.26.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 (55) hide show
  1. package/README.md +9 -8
  2. package/dist/browser/uql-browser.min.js.map +1 -1
  3. package/dist/cockroachdb/cockroachDialect.js +1 -1
  4. package/dist/dialect/indexSqlDialect.d.ts +3 -2
  5. package/dist/dialect/indexSqlDialect.js +10 -8
  6. package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -2
  7. package/dist/dialect/mysqlLikeSqlDialect.js +0 -7
  8. package/dist/dialect/pgLikeSqlDialect.js +1 -0
  9. package/dist/maria/mariaDialect.js +1 -1
  10. package/dist/migrate/builder/migrationBuilder.js +1 -1
  11. package/dist/migrate/builder/tableBuilder.js +2 -2
  12. package/dist/migrate/cli.js +4 -6
  13. package/dist/migrate/codegen/entityCodeGenerator.d.ts +5 -0
  14. package/dist/migrate/codegen/entityCodeGenerator.js +22 -25
  15. package/dist/migrate/codegen/fieldOptionsSource.d.ts +1 -1
  16. package/dist/migrate/codegen/fieldOptionsSource.js +6 -1
  17. package/dist/migrate/codegen/indexDecoratorSource.d.ts +14 -0
  18. package/dist/migrate/codegen/indexDecoratorSource.js +105 -0
  19. package/dist/migrate/drift/driftDetector.d.ts +5 -50
  20. package/dist/migrate/drift/driftDetector.js +215 -224
  21. package/dist/migrate/drift/index.d.ts +1 -1
  22. package/dist/migrate/drift/index.js +1 -1
  23. package/dist/migrate/generator/definitionToNode.d.ts +9 -0
  24. package/dist/migrate/generator/definitionToNode.js +79 -0
  25. package/dist/migrate/generator/indexNodeToSchema.js +4 -2
  26. package/dist/migrate/generator/mongoSchemaGenerator.js +3 -3
  27. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +3 -0
  28. package/dist/migrate/introspection/baseSqlIntrospector.js +13 -5
  29. package/dist/migrate/introspection/mongoIntrospector.d.ts +3 -0
  30. package/dist/migrate/introspection/mongoIntrospector.js +5 -10
  31. package/dist/migrate/introspection/mysqlIntrospector.js +1 -1
  32. package/dist/migrate/introspection/postgresIntrospector.d.ts +57 -5
  33. package/dist/migrate/introspection/postgresIntrospector.js +93 -10
  34. package/dist/migrate/introspection/sqliteIntrospector.js +1 -1
  35. package/dist/migrate/migrator.js +3 -2
  36. package/dist/migrate/schemaGenerator.d.ts +26 -3
  37. package/dist/migrate/schemaGenerator.js +53 -92
  38. package/dist/schema/index.d.ts +3 -3
  39. package/dist/schema/index.js +2 -2
  40. package/dist/schema/indexColumns.d.ts +10 -0
  41. package/dist/schema/indexColumns.js +11 -0
  42. package/dist/schema/indexDifferences.d.ts +22 -0
  43. package/dist/schema/indexDifferences.js +49 -0
  44. package/dist/schema/schemaAST.js +2 -8
  45. package/dist/schema/schemaASTBuilder.d.ts +6 -59
  46. package/dist/schema/schemaASTBuilder.js +208 -236
  47. package/dist/schema/schemaASTDiffer.d.ts +9 -56
  48. package/dist/schema/schemaASTDiffer.js +229 -393
  49. package/dist/schema/types.d.ts +5 -12
  50. package/dist/schema/types.js +15 -0
  51. package/dist/type/dialect.d.ts +7 -2
  52. package/dist/type/dialect.js +1 -0
  53. package/dist/type/migration.d.ts +14 -1
  54. package/dist/util/string.util.js +6 -1
  55. package/package.json +1 -1
@@ -21,6 +21,6 @@ export async function introspectSchema(introspector) {
21
21
  // SchemaAST class
22
22
  export { SchemaAST } from './schemaAST.js';
23
23
  // Builder
24
- export { SchemaASTBuilder } from './schemaASTBuilder.js';
24
+ export { buildSchemaAST } from './schemaASTBuilder.js';
25
25
  // Differ
26
- export { diffSchemas, SchemaASTDiffer } from './schemaASTDiffer.js';
26
+ export { diffSchemas } from './schemaASTDiffer.js';
@@ -0,0 +1,10 @@
1
+ import type { ColumnNode, IndexNode } from './types.js';
2
+ /**
3
+ * The table columns an index resolves to, in order.
4
+ *
5
+ * Derived rather than stored: an index is defined by its entries, and a second field repeating them
6
+ * as columns is a second thing to keep in step. It went out of step - one introspector rebuilt the
7
+ * entries from the columns it had just built from the entries, and every fixture had to write both.
8
+ * An expression entry resolves to no column at all, which is why the two were never the same list.
9
+ */
10
+ export declare function indexColumns(index: IndexNode): ColumnNode[];
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The table columns an index resolves to, in order.
3
+ *
4
+ * Derived rather than stored: an index is defined by its entries, and a second field repeating them
5
+ * as columns is a second thing to keep in step. It went out of step - one introspector rebuilt the
6
+ * entries from the columns it had just built from the entries, and every fixture had to write both.
7
+ * An expression entry resolves to no column at all, which is why the two were never the same list.
8
+ */
9
+ export function indexColumns(index) {
10
+ return index.entries.flatMap((entry) => (entry.expression ? [] : (index.table.columns.get(entry.column) ?? [])));
11
+ }
@@ -0,0 +1,22 @@
1
+ import type { IndexNode } from './types.js';
2
+ /**
3
+ * What an introspector can report about an index, and so all that diffing may compare. Anything it
4
+ * cannot report is skipped: the entity declares it, the database never mentions it, and no migration
5
+ * could ever make the two agree.
6
+ *
7
+ * Deliberately separate from the dialect's `IndexFeature`, which says what an engine can *emit* and
8
+ * rejects outright. The two look alike and are not: Postgres emits an expression index and reads one
9
+ * back, MySQL emits one it cannot describe afterwards.
10
+ */
11
+ export type IndexFacet = 'order' | 'nulls' | 'opsClass' | 'accessMethod' | 'include';
12
+ /**
13
+ * Everything an index differs by, named, or nothing when the two match.
14
+ *
15
+ * Only what both sides can state *structurally* is compared. SQL text is not: a database reprints an
16
+ * expression and a predicate from its parse tree, so `status IN ('a','b')` reads back as
17
+ * `status = ANY (ARRAY['a'::text, 'b'::text])`, `LIKE` as `~~`, and a date literal with its time zone
18
+ * spelled out. Folding that back needs a SQL parser, and every near-miss reports drift that no
19
+ * migration can settle. So a partial index's predicate is never compared, and an index over an
20
+ * expression has its entries left alone while the rest of it still compares.
21
+ */
22
+ export declare function describeIndexDifferences(source: IndexNode, target: IndexNode, facets: ReadonlySet<IndexFacet>): string[];
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Everything an index differs by, named, or nothing when the two match.
3
+ *
4
+ * Only what both sides can state *structurally* is compared. SQL text is not: a database reprints an
5
+ * expression and a predicate from its parse tree, so `status IN ('a','b')` reads back as
6
+ * `status = ANY (ARRAY['a'::text, 'b'::text])`, `LIKE` as `~~`, and a date literal with its time zone
7
+ * spelled out. Folding that back needs a SQL parser, and every near-miss reports drift that no
8
+ * migration can settle. So a partial index's predicate is never compared, and an index over an
9
+ * expression has its entries left alone while the rest of it still compares.
10
+ */
11
+ export function describeIndexDifferences(source, target, facets) {
12
+ const differences = [];
13
+ const comparableEntries = ![...source.entries, ...target.entries].some((entry) => entry.expression);
14
+ if (comparableEntries) {
15
+ const [sourceColumns, targetColumns] = [source.entries, target.entries].map((entries) => entries.map((entry) => entrySignature(entry, facets)).join(', '));
16
+ if (sourceColumns !== targetColumns) {
17
+ differences.push(`columns: (${targetColumns}) → (${sourceColumns})`);
18
+ }
19
+ }
20
+ if ((source.unique ?? false) !== (target.unique ?? false)) {
21
+ differences.push(`unique: ${target.unique ?? false} → ${source.unique ?? false}`);
22
+ }
23
+ if (facets.has('accessMethod') && (source.type ?? 'btree') !== (target.type ?? 'btree')) {
24
+ differences.push(`type: ${target.type ?? 'btree'} → ${source.type ?? 'btree'}`);
25
+ }
26
+ if (facets.has('include')) {
27
+ // Order carries no meaning in an `INCLUDE` list, so it is compared as a set.
28
+ const [sourceInclude, targetInclude] = [source.include ?? [], target.include ?? []].map((columns) => [...columns].sort().join(', '));
29
+ if (sourceInclude !== targetInclude) {
30
+ differences.push(`include: (${targetInclude}) → (${sourceInclude})`);
31
+ }
32
+ }
33
+ return differences;
34
+ }
35
+ function entrySignature(entry, facets) {
36
+ const parts = [entry.column];
37
+ if (facets.has('order')) {
38
+ parts.push(entry.order ?? 'asc');
39
+ }
40
+ if (facets.has('nulls')) {
41
+ // Postgres states this on every entry, so an entity that omits it has asked for Postgres's own
42
+ // default: nulls sort opposite to the direction.
43
+ parts.push(`nulls ${entry.nulls ?? ((entry.order ?? 'asc') === 'desc' ? 'first' : 'last')}`);
44
+ }
45
+ if (facets.has('opsClass') && entry.opsClass) {
46
+ parts.push(entry.opsClass);
47
+ }
48
+ return parts.join(' ');
49
+ }
@@ -400,13 +400,7 @@ export class SchemaAST {
400
400
  const table = clone.tables.get(idx.table.name);
401
401
  if (!table)
402
402
  continue;
403
- const columns = idx.columns.map((c) => table.columns.get(c.name)).filter((c) => c !== undefined);
404
- const clonedIdx = {
405
- ...idx,
406
- table,
407
- columns,
408
- };
409
- clone.addIndex(clonedIdx);
403
+ clone.addIndex({ ...idx, table });
410
404
  }
411
405
  return clone;
412
406
  }
@@ -442,7 +436,7 @@ export class SchemaAST {
442
436
  })),
443
437
  indexes: t.indexes.map((i) => ({
444
438
  name: i.name,
445
- columns: i.columns.map((c) => c.name),
439
+ columns: i.entries.map((entry) => entry.column),
446
440
  unique: i.unique,
447
441
  })),
448
442
  })),
@@ -12,7 +12,7 @@ import type { ForeignKeyAction } from './types.js';
12
12
  /**
13
13
  * Options for building SchemaAST from entities.
14
14
  */
15
- export interface BuildFromEntitiesOptions {
15
+ export interface BuildSchemaASTOptions {
16
16
  /** Custom table name resolver */
17
17
  resolveTableName?: (entity: Type<unknown>, meta: EntityMeta<unknown>) => string;
18
18
  /** Custom column name resolver */
@@ -23,62 +23,9 @@ export interface BuildFromEntitiesOptions {
23
23
  defaultForeignKeyAction?: ForeignKeyAction;
24
24
  }
25
25
  /**
26
- * Builds SchemaAST instances from various sources.
26
+ * Build a SchemaAST from entity classes (decorated with `@Entity`, `@Field`, etc.).
27
+ *
28
+ * Three passes, because each needs the one before it to have finished for *every* entity: a relation
29
+ * resolves against a table another entity declares, and an index against the columns of its own.
27
30
  */
28
- export declare class SchemaASTBuilder {
29
- private readonly namingStrategy?;
30
- private readonly defaultForeignKeyAction;
31
- private ast;
32
- constructor(namingStrategy?: NamingStrategy | undefined, defaultForeignKeyAction?: ForeignKeyAction);
33
- /**
34
- * Reset the builder for a new schema.
35
- */
36
- reset(): this;
37
- /**
38
- * Get the built AST.
39
- */
40
- getAST(): SchemaAST;
41
- /**
42
- * Build AST from entity classes (decorated with @Entity, @Field, etc.)
43
- */
44
- fromEntities(entities: readonly Type<unknown>[], options?: BuildFromEntitiesOptions): SchemaAST;
45
- /**
46
- * Resolve the canonical type for a field, inheriting from the referenced
47
- * entity's primary key when the field is a foreign-key reference
48
- * (`@Field({ references: () => SomeEntity })`) with no explicit type of its
49
- * own.
50
- *
51
- * Without this, a field like `creatorId?: UUID` (a bare TypeScript alias for
52
- * `string`, erased at runtime) falls back to the generic string inference in
53
- * {@link fieldOptionsToCanonical} and gets typed as TEXT/VARCHAR - producing a
54
- * foreign key column whose type doesn't match the UUID primary key it
55
- * references, which Postgres (and most databases) reject outright.
56
- *
57
- * `field.typeFromReference` (set by `defineField`, see entity/metadata/definition.ts)
58
- * is what distinguishes "no type was given" from "the decorator explicitly set
59
- * a type" - including explicit constructor overrides like `type: BigInt`, which
60
- * a value-based check (e.g. `typeof field.type === 'string'`) would miss since
61
- * reflection also produces constructor values like `String`/`Number`.
62
- * `columnType` remains the unambiguous, always-respected explicit override.
63
- */
64
- private resolveColumnCanonicalType;
65
- /**
66
- * Add a table from entity metadata.
67
- */
68
- private addTableFromEntity;
69
- /**
70
- * Add relationships from entity relation decorators.
71
- */
72
- private addRelationshipsFromEntity;
73
- /**
74
- * Add indexes from field options (`@Field({ index })`) and from `@Index([...])`, which have nothing
75
- * in common beyond their target table.
76
- */
77
- private addIndexesFromEntity;
78
- /**
79
- * One `@Index([...])`. Its entries keep the authored form (expression, prefix length, order) with
80
- * names resolved, so the generator renders exactly what was declared; `columns` is the resolvable
81
- * subset, which is what diffing and introspection compare.
82
- */
83
- private addCompositeIndex;
84
- }
31
+ export declare function buildSchemaAST(entities: readonly Type<unknown>[], options?: BuildSchemaASTOptions): SchemaAST;