uql-orm 0.63.0 → 0.64.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 (34) hide show
  1. package/dist/migrate/builder/expressions.js +1 -15
  2. package/dist/migrate/builder/migrationBuilder.d.ts +6 -28
  3. package/dist/migrate/builder/migrationBuilder.js +9 -83
  4. package/dist/migrate/builder/types.d.ts +14 -24
  5. package/dist/migrate/cli.d.ts +1 -6
  6. package/dist/migrate/cli.js +3 -10
  7. package/dist/migrate/codegen/migrationFile.d.ts +9 -1
  8. package/dist/migrate/codegen/migrationFile.js +2 -1
  9. package/dist/migrate/ddl/indexDdl.d.ts +4 -2
  10. package/dist/migrate/ddl/indexDdl.js +16 -15
  11. package/dist/migrate/generator/mongoCommand.d.ts +9 -9
  12. package/dist/migrate/generator/mongoCommand.js +1 -1
  13. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +21 -26
  14. package/dist/migrate/generator/mongoSchemaGenerator.js +103 -80
  15. package/dist/migrate/index.d.ts +1 -1
  16. package/dist/migrate/index.js +1 -1
  17. package/dist/migrate/indexPredicate.d.ts +8 -0
  18. package/dist/migrate/indexPredicate.js +52 -0
  19. package/dist/migrate/migrationTarget.d.ts +23 -0
  20. package/dist/migrate/migrationTarget.js +48 -0
  21. package/dist/migrate/migrator.d.ts +17 -45
  22. package/dist/migrate/migrator.js +80 -179
  23. package/dist/migrate/schemaGenerator.d.ts +12 -13
  24. package/dist/migrate/schemaGenerator.js +43 -9
  25. package/dist/mongo/mongoDialect.d.ts +6 -1
  26. package/dist/schema/schemaASTBuilder.d.ts +2 -0
  27. package/dist/schema/schemaASTBuilder.js +9 -22
  28. package/dist/type/migration.d.ts +11 -40
  29. package/dist/util/ddlExpression.util.d.ts +3 -1
  30. package/dist/util/ddlExpression.util.js +14 -0
  31. package/dist/util/sqlLiteral.js +5 -3
  32. package/package.json +1 -1
  33. package/dist/migrate/schemaGeneratorAsync.d.ts +0 -7
  34. package/dist/migrate/schemaGeneratorAsync.js +0 -12
@@ -6,7 +6,7 @@
6
6
  * - Database introspection results (TableSchema[])
7
7
  */
8
8
  import { getMeta, soleIdOf } from '../entity/metadata/definition.js';
9
- import { indexNameParts, renderIndexColumn } from '../util/ddlExpression.util.js';
9
+ import { declaredIndexes, indexNameParts, renderIndexColumn } from '../util/ddlExpression.util.js';
10
10
  import { isInlinedExpression } from '../util/field.util.js';
11
11
  import { isSoleIdField } from '../util/field.util.js';
12
12
  import { isAutoIncrement } from '../util/field.util.js';
@@ -23,6 +23,7 @@ import { DEFAULT_FOREIGN_KEY_ACTION, } from './types.js';
23
23
  */
24
24
  export function buildSchemaAST(entities, options = {}) {
25
25
  const { namingStrategy } = options;
26
+ const compileDdl = options.compileDdl ?? refuseDdl;
26
27
  const ctx = {
27
28
  ast: new SchemaAST(),
28
29
  resolveTableName: options.resolveTableName ??
@@ -30,7 +31,8 @@ export function buildSchemaAST(entities, options = {}) {
30
31
  resolveSchema: options.resolveSchema ?? ((m) => m.schema),
31
32
  resolveColumnName: options.resolveColumnName ?? ((k, f) => namingStrategy?.columnName(f.name ?? k) ?? f.name ?? k),
32
33
  defaultForeignKeyAction: options.defaultForeignKeyAction ?? DEFAULT_FOREIGN_KEY_ACTION,
33
- compileDdl: options.compileDdl ?? refuseDdl,
34
+ compileDdl,
35
+ compileIndexPredicate: options.compileIndexPredicate ?? compileDdl,
34
36
  };
35
37
  for (const pass of [addTableFromEntity, addRelationshipsFromEntity, addIndexesFromEntity]) {
36
38
  for (const entity of entities) {
@@ -180,23 +182,7 @@ function addIndexesFromEntity(ctx, meta) {
180
182
  const table = tableOf(ctx, meta);
181
183
  if (!table)
182
184
  return;
183
- for (const key of Object.keys(meta.fields)) {
184
- const field = meta.fields[key];
185
- if (!field?.index)
186
- continue;
187
- const column = table.columns.get(ctx.resolveColumnName(key, field));
188
- if (!column)
189
- continue;
190
- ctx.ast.addIndex({
191
- name: typeof field.index === 'string' ? field.index : derivedIndexName(table.name, [column.name]),
192
- table,
193
- entries: [{ column: column.name }],
194
- unique: field.unique ?? false,
195
- source: 'entity',
196
- syncStatus: 'entity_only',
197
- });
198
- }
199
- for (const idxMeta of meta.indexes ?? []) {
185
+ for (const idxMeta of declaredIndexes(meta)) {
200
186
  addCompositeIndex(ctx, table, meta, idxMeta);
201
187
  }
202
188
  addForeignKeyIndexes(ctx, meta, table);
@@ -235,7 +221,7 @@ function resolveIncludeColumn(ctx, meta, column) {
235
221
  return field ? ctx.resolveColumnName(column, field) : column;
236
222
  }
237
223
  /**
238
- * One `@Index`. Its entries keep the authored form (expression, prefix length, order) with
224
+ * One index the entity declares. Its entries keep the authored form (expression, prefix length, order) with
239
225
  * names resolved, so the generator renders exactly what was declared; `columns` is the resolvable
240
226
  * subset, which is what diffing and introspection compare.
241
227
  */
@@ -251,14 +237,15 @@ function addCompositeIndex(ctx, table, meta, idxMeta) {
251
237
  });
252
238
  if (!resolved.length)
253
239
  return;
240
+ const name = idxMeta.name ?? derivedIndexName(table.name, indexNameParts(resolved));
254
241
  ctx.ast.addIndex({
255
- name: idxMeta.name ?? derivedIndexName(table.name, indexNameParts(resolved)),
242
+ name,
256
243
  table,
257
244
  entries: resolved.map((entry) => renderIndexColumn(entry, (sql) => ctx.compileDdl(sql, meta.entity))),
258
245
  include: idxMeta.include?.map((column) => resolveIncludeColumn(ctx, meta, column)),
259
246
  unique: idxMeta.unique ?? false,
260
247
  type: idxMeta.type,
261
- where: idxMeta.where && ctx.compileDdl(idxMeta.where, meta.entity),
248
+ where: idxMeta.where && ctx.compileIndexPredicate(idxMeta.where, meta.entity, name),
262
249
  distance: idxMeta.distance,
263
250
  m: idxMeta.m,
264
251
  efConstruction: idxMeta.efConstruction,
@@ -1,8 +1,8 @@
1
1
  import type { VectorCast } from '../dialect/vectorCast.js';
2
- import type { FullColumnDefinition, IndexDefinition, TableDefinition } from '../migrate/builder/types.js';
2
+ import type { AnyMigrationOperation } from '../migrate/builder/types.js';
3
3
  import type { IndexFacet } from '../schema/indexDifferences.js';
4
4
  import type { SchemaAST } from '../schema/schemaAST.js';
5
- import type { ColumnNode, ForeignKeyAction, IndexNode, IndexType, TableNode } from '../schema/types.js';
5
+ import type { ColumnNode, ForeignKeyAction, IndexType, TableNode } from '../schema/types.js';
6
6
  import type { EntityMeta, EntityWhereMeta, FieldOptions, IndexColumnSchema, LoggingOptions, Querier, SqlQuerier, Type, VectorIndexOptions } from './index.js';
7
7
  /**
8
8
  * Defines a migration using a simple object literal. `Q` is `MongoQuerier` for a MongoDB migration.
@@ -148,7 +148,7 @@ export interface IndexSchema extends VectorIndexOptions {
148
148
  readonly unique: boolean;
149
149
  /** Index type (btree, hnsw, ivfflat, etc.) */
150
150
  readonly type?: IndexType;
151
- /** Partial index condition (WHERE clause) */
151
+ /** Partial index predicate as its engine writes it: SQL, or on MongoDB the JSON of its filter document. */
152
152
  readonly where?: string;
153
153
  /** Non-key columns stored in the index (Postgres-wire `INCLUDE`). */
154
154
  readonly include?: readonly string[];
@@ -292,14 +292,20 @@ export interface SchemaGenerator {
292
292
  */
293
293
  generateDropIndex(tableName: string, indexName: string): string;
294
294
  /**
295
- * Get the SQL type for a field based on its options
295
+ * The statements one migration builder operation runs as, one string each. An operation the engine
296
+ * has no form for throws: MongoDB has no columns, constraints or SQL.
296
297
  */
297
- getSqlType(fieldOptions: FieldOptions): string;
298
+ generateOperation(operation: AnyMigrationOperation): string[];
298
299
  /**
299
300
  * The text of SQL an entity declares - a check, a stored computed column, an index expression or
300
301
  * predicate - rendered for this engine, which is what building an entity's schema needs from it.
301
302
  */
302
303
  compileDdl(sql: EntityWhereMeta<object>, entity: Type<object>): string;
304
+ /**
305
+ * A partial index's `$where` as this engine writes it into {@link IndexSchema.where}, refused where its
306
+ * index takes less of a predicate than a query does: SQL Server's filter has no `OR`.
307
+ */
308
+ compileIndexPredicate(where: EntityWhereMeta<object>, entity: Type<object>, indexName: string): string;
303
309
  /**
304
310
  * Compare an entity with a database table node and return the differences.
305
311
  *
@@ -333,41 +339,6 @@ export interface SchemaGenerator {
333
339
  * Resolve column name using field options and naming strategy
334
340
  */
335
341
  resolveColumnName(key: string, field: FieldOptions): string;
336
- /** DDL from a `TableNode`, one string per `querier.run`. */
337
- generateCreateTableFromNode(table: TableNode, options?: {
338
- ifNotExists?: boolean;
339
- }): string[];
340
- /** Generate CREATE INDEX statement from an IndexNode */
341
- generateCreateIndexFromNode(index: IndexNode, options?: {
342
- ifNotExists?: boolean;
343
- }): string;
344
- /** DDL from a `TableDefinition`, one string per `querier.run`. */
345
- generateCreateTableFromDefinition(table: TableDefinition, options?: {
346
- ifNotExists?: boolean;
347
- }): string[];
348
- /** Generate RENAME TABLE statement */
349
- generateRenameTableSql(oldName: string, newName: string): string;
350
- }
351
- /**
352
- * The column and constraint DDL a migration builder emits, which only a SQL engine has. Split from
353
- * {@link SchemaGenerator} because MongoDB used to satisfy these six by returning `''`: a document store
354
- * has no `ADD COLUMN`, and an empty statement silently did nothing rather than saying so.
355
- */
356
- export interface SqlDdlGenerator extends SchemaGenerator {
357
- /** Generate ADD COLUMN statement */
358
- generateAddColumnSql(tableName: string, column: FullColumnDefinition): string;
359
- /** Generate ALTER COLUMN statement */
360
- generateAlterColumnSql(tableName: string, columnName: string, column: FullColumnDefinition): string;
361
- /** Generate DROP COLUMN statement */
362
- generateDropColumnSql(tableName: string, columnName: string): string;
363
- /** Generate RENAME COLUMN statement */
364
- generateRenameColumnSql(tableName: string, oldName: string, newName: string): string;
365
- /** Generate ADD FOREIGN KEY statement */
366
- generateAddForeignKeySql(tableName: string, foreignKey: ForeignKeySchema): string;
367
- /** Generate DROP FOREIGN KEY statement */
368
- generateDropForeignKeySql(tableName: string, constraintName: string): string;
369
- /** CREATE INDEX from a builder's {@link IndexDefinition}, its SQL rendered for this engine. */
370
- generateCreateIndexFromDefinition(tableName: string, index: IndexDefinition): string;
371
342
  }
372
343
  /**
373
344
  * Interface for introspecting the current database schema
@@ -1,9 +1,11 @@
1
- import { type EntityIndexColumn, type IndexColumnInput, type IndexColumnSchema, QueryRaw } from '../type/index.js';
1
+ import { type EntityIndexColumn, type EntityIndexMeta, type EntityMeta, type IndexColumnInput, type IndexColumnSchema, QueryRaw } from '../type/index.js';
2
2
  /**
3
3
  * Reduces an authored index entry to the form metadata keeps, so a column, an expression and an options
4
4
  * object reach the schema as one: a column read off the refs as its key, any other `raw` as it is.
5
5
  */
6
6
  export declare function normalizeIndexColumn(entry: IndexColumnInput): EntityIndexColumn;
7
+ /** Every index an entity declares: each `@Field({ index })` as the one-column `@Index` it is, then its `@Index`es. */
8
+ export declare function declaredIndexes<E>(meta: EntityMeta<E>): EntityIndexMeta<E>[];
7
9
  /** An index entry as the schema holds it, its expression rendered to text by `render`. */
8
10
  export declare function renderIndexColumn(entry: EntityIndexColumn, render: (sql: QueryRaw) => string): IndexColumnSchema;
9
11
  /** What an unnamed index's name is built from: each entry's column, or `expr<n>` for an expression, which has none. */
@@ -1,4 +1,5 @@
1
1
  import { ColumnRef, QueryRaw, } from '../type/index.js';
2
+ import { definedEntries } from './object.util.js';
2
3
  /**
3
4
  * Reduces an authored index entry to the form metadata keeps, so a column, an expression and an options
4
5
  * object reach the schema as one: a column read off the refs as its key, any other `raw` as it is.
@@ -7,6 +8,19 @@ export function normalizeIndexColumn(entry) {
7
8
  const { column, ...modifiers } = typeof entry === 'string' || entry instanceof QueryRaw ? { column: entry } : entry;
8
9
  return { ...modifiers, column: column instanceof ColumnRef ? column.key : column };
9
10
  }
11
+ /** Every index an entity declares: each `@Field({ index })` as the one-column `@Index` it is, then its `@Index`es. */
12
+ export function declaredIndexes(meta) {
13
+ const fieldIndexes = definedEntries(meta.fields).flatMap(([key, field]) => field.index
14
+ ? [
15
+ {
16
+ columns: [{ column: key }],
17
+ name: typeof field.index === 'string' ? field.index : undefined,
18
+ unique: field.unique,
19
+ },
20
+ ]
21
+ : []);
22
+ return [...fieldIndexes, ...(meta.indexes ?? [])];
23
+ }
10
24
  /** An index entry as the schema holds it, its expression rendered to text by `render`. */
11
25
  export function renderIndexColumn(entry, render) {
12
26
  const { column } = entry;
@@ -32,7 +32,6 @@ const MYSQL_ESCAPES = {
32
32
  '\\': '\\\\',
33
33
  };
34
34
  const mysqlStringLiteral = (val) => `'${val.replace(MYSQL_SPECIALS, (char) => MYSQL_ESCAPES[char])}'`;
35
- const pad = (value, len) => String(value).padStart(len, '0');
36
35
  const HEX_BYTES = Array.from({ length: 256 }, (_, byte) => byte.toString(16).padStart(2, '0'));
37
36
  /** Native hex encoder where available (~130x faster on 4 KB); lookup table for browsers. */
38
37
  function bytesToHexLiteral(bytes) {
@@ -49,8 +48,11 @@ function bytesToHexLiteral(bytes) {
49
48
  * measured 1.1-1.5x slower. Rejects unsupported types rather than stringifying them into SQL.
50
49
  */
51
50
  function createEscaper(escapeString) {
52
- /** `YYYY-MM-DD HH:mm:ss.mmm` in local time, wrapped as a quoted literal. */
53
- const dateLiteral = (date) => escapeString(`${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1, 2)}-${pad(date.getDate(), 2)} ${pad(date.getHours(), 2)}:${pad(date.getMinutes(), 2)}:${pad(date.getSeconds(), 2)}.${pad(date.getMilliseconds(), 3)}`);
51
+ /**
52
+ * `YYYY-MM-DD HH:mm:ss.SSS` in UTC, so the SQL is the same whichever machine wrote it. Not `toISOString`
53
+ * as it is, whose `T` and `Z` MySQL rejects outright ("Invalid default value").
54
+ */
55
+ const dateLiteral = (date) => escapeString(date.toISOString().replace('T', ' ').replace('Z', ''));
54
56
  const sqlList = (arr) => {
55
57
  let sql = '';
56
58
  for (let i = 0; i < arr.length; i++) {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "homepage": "https://uql-orm.dev",
4
4
  "description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
5
5
  "license": "MIT",
6
- "version": "0.63.0",
6
+ "version": "0.64.0",
7
7
  "type": "module",
8
8
  "engines": {
9
9
  "node": ">=24"
@@ -1,7 +0,0 @@
1
- import type { ForeignKeyAction } from '../schema/types.js';
2
- import type { MigratorDialect, SchemaGenerator } from '../type/index.js';
3
- /**
4
- * Async factory for schema generators. Use this for MongoDB so the optional peer
5
- * `mongodb` is only loaded when this path runs. SQL dialects delegate to {@link createSchemaGenerator}.
6
- */
7
- export declare function createSchemaGeneratorAsync(dialect: MigratorDialect, defaultForeignKeyAction?: ForeignKeyAction): Promise<SchemaGenerator | undefined>;
@@ -1,12 +0,0 @@
1
- import { createSchemaGenerator } from './schemaGenerator.js';
2
- /**
3
- * Async factory for schema generators. Use this for MongoDB so the optional peer
4
- * `mongodb` is only loaded when this path runs. SQL dialects delegate to {@link createSchemaGenerator}.
5
- */
6
- export async function createSchemaGeneratorAsync(dialect, defaultForeignKeyAction) {
7
- if (dialect.dialectName === 'mongodb') {
8
- const { MongoSchemaGenerator } = await import('./generator/mongoSchemaGenerator.js');
9
- return new MongoSchemaGenerator(dialect.namingStrategy, defaultForeignKeyAction);
10
- }
11
- return createSchemaGenerator(dialect, defaultForeignKeyAction);
12
- }