uql-orm 0.63.0 → 0.65.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 (98) hide show
  1. package/dist/bunSql/bunSqlQuerier.d.ts +3 -10
  2. package/dist/bunSql/bunSqlQuerier.js +2 -16
  3. package/dist/bunSql/bunSqlQuerierPool.d.ts +0 -2
  4. package/dist/bunSql/bunSqlQuerierPool.js +14 -16
  5. package/dist/d1/d1Querier.d.ts +13 -34
  6. package/dist/d1/d1Querier.js +1 -1
  7. package/dist/d1/d1QuerierPool.d.ts +3 -3
  8. package/dist/dialect/abstractDialect.d.ts +4 -9
  9. package/dist/dialect/abstractDialect.js +4 -5
  10. package/dist/dialect/abstractSqlDialect.d.ts +2 -2
  11. package/dist/dialect/index.d.ts +0 -1
  12. package/dist/dialect/index.js +2 -3
  13. package/dist/dialect/mysqlLikeSqlDialect.js +0 -3
  14. package/dist/dialect/pgLikeSqlDialect.d.ts +9 -1
  15. package/dist/dialect/pgLikeSqlDialect.js +8 -5
  16. package/dist/dialect/queryContext.d.ts +3 -3
  17. package/dist/entity/metadata/definition.d.ts +6 -0
  18. package/dist/entity/metadata/definition.js +109 -76
  19. package/dist/migrate/builder/expressions.js +1 -15
  20. package/dist/migrate/builder/migrationBuilder.d.ts +6 -28
  21. package/dist/migrate/builder/migrationBuilder.js +9 -83
  22. package/dist/migrate/builder/types.d.ts +14 -24
  23. package/dist/migrate/cli.d.ts +2 -7
  24. package/dist/migrate/cli.js +22 -43
  25. package/dist/migrate/codegen/entityCodeGenerator.d.ts +5 -0
  26. package/dist/migrate/codegen/entityCodeGenerator.js +29 -7
  27. package/dist/migrate/codegen/indexDecoratorSource.js +1 -5
  28. package/dist/migrate/codegen/migrationFile.d.ts +9 -1
  29. package/dist/migrate/codegen/migrationFile.js +2 -1
  30. package/dist/migrate/codegen/sourceLiteral.d.ts +2 -0
  31. package/dist/migrate/codegen/sourceLiteral.js +4 -0
  32. package/dist/migrate/ddl/indexDdl.d.ts +4 -2
  33. package/dist/migrate/ddl/indexDdl.js +16 -15
  34. package/dist/migrate/generator/mongoCommand.d.ts +9 -9
  35. package/dist/migrate/generator/mongoCommand.js +1 -1
  36. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +21 -26
  37. package/dist/migrate/generator/mongoSchemaGenerator.js +103 -80
  38. package/dist/migrate/index.d.ts +2 -2
  39. package/dist/migrate/index.js +2 -2
  40. package/dist/migrate/indexPredicate.d.ts +8 -0
  41. package/dist/migrate/indexPredicate.js +52 -0
  42. package/dist/migrate/introspection/registry.d.ts +2 -2
  43. package/dist/migrate/introspection/registry.js +6 -11
  44. package/dist/migrate/migrationTarget.d.ts +23 -0
  45. package/dist/migrate/migrationTarget.js +50 -0
  46. package/dist/migrate/migrator.d.ts +18 -50
  47. package/dist/migrate/migrator.js +79 -199
  48. package/dist/migrate/schemaGenerator.d.ts +11 -14
  49. package/dist/migrate/schemaGenerator.js +42 -12
  50. package/dist/mongo/mongoDialect.d.ts +7 -2
  51. package/dist/mongo/mongoDialect.js +1 -4
  52. package/dist/mssql/mssqlDialect.js +0 -3
  53. package/dist/pglite/pgliteQuerier.d.ts +2 -6
  54. package/dist/pglite/pgliteQuerier.js +2 -9
  55. package/dist/postgres/index.d.ts +0 -1
  56. package/dist/postgres/index.js +0 -1
  57. package/dist/querier/abstractSharedHandleQuerierPool.d.ts +2 -5
  58. package/dist/querier/abstractSharedHandleQuerierPool.js +2 -5
  59. package/dist/querier/abstractSqlQuerier.d.ts +2 -3
  60. package/dist/querier/abstractSqlQuerier.js +8 -4
  61. package/dist/querier/cursorStream.d.ts +13 -0
  62. package/dist/{postgres/pgCursorStream.js → querier/cursorStream.js} +4 -13
  63. package/dist/schema/canonicalType.js +1 -1
  64. package/dist/schema/schemaASTBuilder.d.ts +2 -0
  65. package/dist/schema/schemaASTBuilder.js +43 -62
  66. package/dist/sqlite/abstractSqliteQuerier.d.ts +7 -3
  67. package/dist/sqlite/abstractSqliteQuerier.js +18 -4
  68. package/dist/sqlite/hranaQuerier.d.ts +1 -1
  69. package/dist/sqlite/localSqliteQuerierPool.d.ts +7 -0
  70. package/dist/sqlite/localSqliteQuerierPool.js +19 -0
  71. package/dist/sqlite/nodeSqliteQuerierPool.js +2 -3
  72. package/dist/sqlite/sqliteDialect.js +0 -3
  73. package/dist/sqlite/sqliteQuerier.d.ts +8 -5
  74. package/dist/sqlite/sqliteQuerier.js +4 -13
  75. package/dist/sqlite/sqliteQuerierPool.js +4 -4
  76. package/dist/turso/tursoSessionQuerier.d.ts +2 -2
  77. package/dist/turso/tursoSessionQuerier.js +16 -18
  78. package/dist/type/dialect.d.ts +25 -35
  79. package/dist/type/entity.d.ts +26 -28
  80. package/dist/type/migration.d.ts +11 -40
  81. package/dist/type/migratorDialect.d.ts +0 -9
  82. package/dist/type/migratorDialect.js +1 -16
  83. package/dist/type/querierPool.d.ts +0 -4
  84. package/dist/type/queryRaw.d.ts +2 -2
  85. package/dist/type/universalQuerier.d.ts +2 -2
  86. package/dist/util/ddlExpression.util.d.ts +3 -1
  87. package/dist/util/ddlExpression.util.js +14 -0
  88. package/dist/util/sqlLiteral.js +5 -3
  89. package/package.json +1 -3
  90. package/dist/migrate/schemaGeneratorAsync.d.ts +0 -7
  91. package/dist/migrate/schemaGeneratorAsync.js +0 -12
  92. package/dist/postgres/pgCursorStream.d.ts +0 -20
  93. package/dist/postgres/postgresWireDriverCapabilities.d.ts +0 -21
  94. package/dist/postgres/postgresWireDriverCapabilities.js +0 -21
  95. package/dist/sqlite/bunSqliteAdapter.bun.d.ts +0 -27
  96. package/dist/sqlite/bunSqliteAdapter.bun.js +0 -25
  97. package/dist/sqlite/nodeSqliteAdapter.d.ts +0 -33
  98. package/dist/sqlite/nodeSqliteAdapter.js +0 -25
@@ -20,10 +20,8 @@ export type PgliteDatabase = {
20
20
  /**
21
21
  * Querier for PGlite, Postgres compiled to WASM and run in this process.
22
22
  *
23
- * @remarks Extends {@link AbstractSqlQuerier} rather than `PgQuerier`, whose `internalStream`
24
- * hands a `pg-query-stream` object to `query()`, which PGlite's client has no equivalent of - so
25
- * streaming pages the rows in SQL instead. `BEGIN`/`COMMIT` are plain statements on the single
26
- * connection, leaving transactions to the base class.
23
+ * @remarks Extends {@link AbstractSqlQuerier} rather than `PgQuerier`, whose stream needs `pg-query-stream`,
24
+ * which PGlite's client cannot run. `BEGIN`/`COMMIT` are plain statements on the single connection.
27
25
  */
28
26
  export declare class PgliteQuerier extends AbstractSqlQuerier {
29
27
  readonly db: PgliteDatabase;
@@ -31,8 +29,6 @@ export declare class PgliteQuerier extends AbstractSqlQuerier {
31
29
  constructor(db: PgliteDatabase, dialect: PostgresDialect, extra?: ExtraOptions | undefined);
32
30
  internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
33
31
  internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
34
- /** Postgres compiled to WASM is still Postgres: `DECLARE`/`FETCH` streams what the client cannot. */
35
- protected internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, any>;
36
32
  /** The handle belongs to the pool, which hands out one querier per unit of work over it. */
37
33
  internalRelease(): Promise<void>;
38
34
  }
@@ -1,12 +1,9 @@
1
- import { streamViaCursor } from '../postgres/pgCursorStream.js';
2
1
  import { AbstractSqlQuerier } from '../querier/index.js';
3
2
  /**
4
3
  * Querier for PGlite, Postgres compiled to WASM and run in this process.
5
4
  *
6
- * @remarks Extends {@link AbstractSqlQuerier} rather than `PgQuerier`, whose `internalStream`
7
- * hands a `pg-query-stream` object to `query()`, which PGlite's client has no equivalent of - so
8
- * streaming pages the rows in SQL instead. `BEGIN`/`COMMIT` are plain statements on the single
9
- * connection, leaving transactions to the base class.
5
+ * @remarks Extends {@link AbstractSqlQuerier} rather than `PgQuerier`, whose stream needs `pg-query-stream`,
6
+ * which PGlite's client cannot run. `BEGIN`/`COMMIT` are plain statements on the single connection.
10
7
  */
11
8
  export class PgliteQuerier extends AbstractSqlQuerier {
12
9
  db;
@@ -26,10 +23,6 @@ export class PgliteQuerier extends AbstractSqlQuerier {
26
23
  // where the latter also counts a `SELECT`'s rows and is absent altogether from a DDL tag.
27
24
  return this.buildUpdateResult({ rows: res.rows, changes: res.affectedRows ?? 0 });
28
25
  }
29
- /** Postgres compiled to WASM is still Postgres: `DECLARE`/`FETCH` streams what the client cannot. */
30
- async *internalStream(query, values) {
31
- yield* streamViaCursor((sql, params) => this.internalAll(sql, params), query, values, this.hasOpenTransaction);
32
- }
33
26
  /** The handle belongs to the pool, which hands out one querier per unit of work over it. */
34
27
  async internalRelease() { }
35
28
  }
@@ -1,4 +1,3 @@
1
1
  export * from './pgQuerier.js';
2
2
  export * from './pgQuerierPool.js';
3
3
  export * from './postgresDialect.js';
4
- export * from './postgresWireDriverCapabilities.js';
@@ -1,4 +1,3 @@
1
1
  export * from './pgQuerier.js';
2
2
  export * from './pgQuerierPool.js';
3
3
  export * from './postgresDialect.js';
4
- export * from './postgresWireDriverCapabilities.js';
@@ -15,13 +15,10 @@ import { AbstractSqlQuerierPool } from './abstractSqlQuerierPool.js';
15
15
  * into the transaction already open - see {@link PgliteQuerierPool}, which is why that one is worth
16
16
  * saying out loud.
17
17
  *
18
- * Subclasses supply only how to open the handle and how to wrap it, the way {@link AbstractPgQuerierPool}
19
- * takes `buildQuerier` alone. The lazy open and the close were written out once per pool before, along
20
- * with three partial copies of the paragraph above.
18
+ * Subclasses supply only how to open the handle and how to wrap it.
21
19
  *
22
20
  * @remarks Deliberately not re-exported from `querier/index.ts`, which the root entry point re-exports:
23
- * only the three driver entries need this, and each imports it by path, as `postgres/abstractPgQuerier.ts`
24
- * is imported.
21
+ * only the driver entries that open a single handle need this, and each imports it by path.
25
22
  */
26
23
  export declare abstract class AbstractSharedHandleQuerierPool<DB extends {
27
24
  close(): unknown;
@@ -13,13 +13,10 @@ import { AbstractSqlQuerierPool } from './abstractSqlQuerierPool.js';
13
13
  * into the transaction already open - see {@link PgliteQuerierPool}, which is why that one is worth
14
14
  * saying out loud.
15
15
  *
16
- * Subclasses supply only how to open the handle and how to wrap it, the way {@link AbstractPgQuerierPool}
17
- * takes `buildQuerier` alone. The lazy open and the close were written out once per pool before, along
18
- * with three partial copies of the paragraph above.
16
+ * Subclasses supply only how to open the handle and how to wrap it.
19
17
  *
20
18
  * @remarks Deliberately not re-exported from `querier/index.ts`, which the root entry point re-exports:
21
- * only the three driver entries need this, and each imports it by path, as `postgres/abstractPgQuerier.ts`
22
- * is imported.
19
+ * only the driver entries that open a single handle need this, and each imports it by path.
23
20
  */
24
21
  export class AbstractSharedHandleQuerierPool extends AbstractSqlQuerierPool {
25
22
  /**
@@ -80,9 +80,8 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
80
80
  private hydrateRows;
81
81
  protected internalFindManyStream<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): AsyncGenerator<Awaited<E>, void, unknown>;
82
82
  /**
83
- * Internal streaming query - returns an async iterable of raw rows.
84
- * Default implementation falls back to `internalAll()` then yields each row.
85
- * Drivers with native cursor/streaming APIs (SQLite, Pg) should override this.
83
+ * A read's rows one at a time: paged through a server-side cursor where the engine has one, read whole
84
+ * where it has none. A driver that streams on its own overrides this.
86
85
  */
87
86
  protected internalStream<T>(query: string, values?: unknown[]): AsyncIterable<T>;
88
87
  /**
@@ -4,6 +4,7 @@ import { getMeta, idOf, namesKey } from '../entity/index.js';
4
4
  import { COUNT_RESULT_KEY } from '../type/index.js';
5
5
  import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, insertShapeOf, idOnlyQuery, isAutoIncrement, isPagedQuery, isRecord, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
6
6
  import { AbstractQuerier } from './abstractQuerier.js';
7
+ import { streamViaCursor } from './cursorStream.js';
7
8
  import { enrichError } from './queryError.js';
8
9
  /**
9
10
  * Row indexes grouped by whether the caller supplied the key, payload order kept within each group.
@@ -240,12 +241,15 @@ export class AbstractSqlQuerier extends AbstractQuerier {
240
241
  }
241
242
  }
242
243
  /**
243
- * Internal streaming query - returns an async iterable of raw rows.
244
- * Default implementation falls back to `internalAll()` then yields each row.
245
- * Drivers with native cursor/streaming APIs (SQLite, Pg) should override this.
244
+ * A read's rows one at a time: paged through a server-side cursor where the engine has one, read whole
245
+ * where it has none. A driver that streams on its own overrides this.
246
246
  */
247
247
  async *internalStream(query, values) {
248
- yield* await this.internalAll(query, values);
248
+ if (!this.dialect.features.serverSideCursors) {
249
+ yield* await this.internalAll(query, values);
250
+ return;
251
+ }
252
+ yield* streamViaCursor((sql, params) => this.internalAll(sql, params), query, values, this.hasOpenTransaction);
249
253
  }
250
254
  /**
251
255
  * Turn what a driver returned back into the types the entity declares, for every row and everything
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Runs one statement of the cursor protocol on the querier's own connection, which holds the cursor, and
3
+ * not through `all()`, whose `serialize` is not re-entrant.
4
+ */
5
+ type CursorExecutor<T> = (query: string, values?: unknown[]) => Promise<T[]>;
6
+ /**
7
+ * Streams a Postgres-wire read through a server-side cursor. `DECLARE` needs a transaction, so one is
8
+ * opened on the connection itself when the caller has none, leaving the querier's state untouched.
9
+ * Cleanup sits in `finally`, which a consumer that stops early still reaches, and is best-effort once
10
+ * the read failed, so the read's error stays the one thrown.
11
+ */
12
+ export declare function streamViaCursor<T>(exec: CursorExecutor<T>, query: string, values?: unknown[], inTransaction?: boolean): AsyncIterable<T>;
13
+ export {};
@@ -4,17 +4,10 @@ const CURSOR_ALIAS = '_uql_cursor';
4
4
  const FETCH_SIZE = 100;
5
5
  let cursorSeq = 0;
6
6
  /**
7
- * Stream a Postgres-wire result through a server-side cursor, for a driver whose client exposes none:
8
- * `bun:sql` (no cursor API at all, [oven-sh/bun#17181](https://github.com/oven-sh/bun/issues/17181))
9
- * and PGlite. `pg` has `pg-query-stream` and keeps using it.
10
- *
11
- * `DECLARE` is only legal inside a transaction, so one is opened here when the caller has none - and
12
- * then committed, or rolled back if the stream failed. That `BEGIN` goes straight to the connection
13
- * rather than through `beginTransaction`, so the querier's own transaction state stays untouched:
14
- * this one is the generator's, and ends with it.
15
- *
16
- * The cleanup lives in `finally` because a consumer that stops early (`break`, a `throw` downstream)
17
- * ends the generator there and nowhere else, and an abandoned cursor holds its transaction open.
7
+ * Streams a Postgres-wire read through a server-side cursor. `DECLARE` needs a transaction, so one is
8
+ * opened on the connection itself when the caller has none, leaving the querier's state untouched.
9
+ * Cleanup sits in `finally`, which a consumer that stops early still reaches, and is best-effort once
10
+ * the read failed, so the read's error stays the one thrown.
18
11
  */
19
12
  export async function* streamViaCursor(exec, query, values, inTransaction = false) {
20
13
  const cursor = `${CURSOR_ALIAS}_${++cursorSeq}`;
@@ -38,8 +31,6 @@ export async function* streamViaCursor(exec, query, values, inTransaction = fals
38
31
  throw err;
39
32
  }
40
33
  finally {
41
- // Best-effort once the stream has failed: an error raised here would replace the one that brought
42
- // us here, which is the one worth reporting, and the rollback discards the cursor either way.
43
34
  const end = failed ? (sql) => exec(sql).catch(() => []) : exec;
44
35
  await end(`CLOSE ${cursor}`);
45
36
  if (ownsTransaction) {
@@ -338,7 +338,7 @@ export function canonicalToSql(type, dialect) {
338
338
  }
339
339
  return type.unsigned && features.supportsUnsigned ? `${sqlType} UNSIGNED` : sqlType;
340
340
  }
341
- /** See {@link EngineFeatures.stringSizing} for what each mode means. */
341
+ /** See {@link DialectFeatures.stringSizing} for what each mode means. */
342
342
  function formatStringSqlType(type, base, sizing) {
343
343
  if (sizing === 'text') {
344
344
  return base;
@@ -29,6 +29,8 @@ export interface BuildSchemaASTOptions {
29
29
  * predicate - which only a dialect can render. `buildEntityAST` supplies it from the generator.
30
30
  */
31
31
  compileDdl?: (sql: EntityWhereMeta<object>, entity: Type<object>) => string;
32
+ /** A partial index's predicate as the engine writes it, `compileDdl` where none is given. `buildEntityAST` supplies it. */
33
+ compileIndexPredicate?: (where: EntityWhereMeta<object>, entity: Type<object>, indexName: string) => string;
32
34
  }
33
35
  /**
34
36
  * Build a SchemaAST from entity classes (decorated with `@Entity`, `@Field`, etc.).
@@ -5,8 +5,8 @@
5
5
  * - Entity metadata (decorator-based entities)
6
6
  * - Database introspection results (TableSchema[])
7
7
  */
8
- import { getMeta, soleIdOf } from '../entity/metadata/definition.js';
9
- import { indexNameParts, renderIndexColumn } from '../util/ddlExpression.util.js';
8
+ import { foreignKeysOf, getMeta, soleIdOf } from '../entity/metadata/definition.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) {
@@ -122,54 +124,48 @@ function tableOf(ctx, meta) {
122
124
  return ctx.ast.getTable(qualifyName(ctx.resolveTableName(meta), ctx.resolveSchema(meta)));
123
125
  }
124
126
  /**
125
- * Add relationships from entity relation decorators.
127
+ * Add a relationship for each foreign key the entity holds, whether a relation declares it or a bare
128
+ * `@Field({ references })` does.
126
129
  */
127
130
  function addRelationshipsFromEntity(ctx, meta) {
128
131
  const table = tableOf(ctx, meta);
129
132
  if (!table)
130
133
  return;
131
- for (const [key, relation] of definedEntries(meta.relations)) {
132
- const relatedMeta = getMeta(relation.entity());
134
+ for (const foreignKey of foreignKeysOf(meta)) {
135
+ const relatedMeta = getMeta(foreignKey.entity());
133
136
  const relatedTable = tableOf(ctx, relatedMeta);
134
137
  if (!relatedTable)
135
138
  continue;
136
- // Only the owning side gets the FK. `mappedBy` marks the inverse side of a one-to-one, whose
137
- // `references` describe how to join back (its own primary key against the owner's FK column) -
138
- // reading those as a foreign key emitted a reversed constraint (`User(id) REFERENCES
139
- // user_profile(creatorId)`), which SQLite rejects outright as a foreign key mismatch.
140
- const ownsForeignKey = relation.cardinality === 'm1' || (relation.cardinality === '11' && !relation.mappedBy);
141
- if (ownsForeignKey) {
142
- // Every pair, not just the first: a composite key is one constraint over all its columns, and
143
- // the engine requires the referenced columns to match a unique constraint as a whole.
144
- const localColumns = [];
145
- const foreignColumns = [];
146
- for (const { local: localProp, foreign: foreignProp } of relation.references) {
147
- const localField = meta.fields[localProp];
148
- const foreignField = relatedMeta.fields[foreignProp];
149
- const localColumn = localField && table.columns.get(ctx.resolveColumnName(localProp, localField));
150
- const foreignColumn = foreignField && relatedTable.columns.get(ctx.resolveColumnName(foreignProp, foreignField));
151
- if (!localColumn || !foreignColumn)
152
- break;
153
- localColumns.push(localColumn);
154
- foreignColumns.push(foreignColumn);
155
- }
156
- // A pair that cannot be resolved drops the whole constraint: half of one enforces a rule
157
- // nobody declared, over a subset of the key.
158
- if (localColumns.length !== relation.references.length)
159
- continue;
160
- ctx.ast.addRelationship({
161
- name: derivedForeignKeyName(table.name, localColumns.map((column) => column.name)),
162
- type: relation.cardinality === 'm1' ? 'ManyToOne' : 'OneToOne',
163
- from: { table, columns: localColumns },
164
- to: { table: relatedTable, columns: foreignColumns },
165
- // Falls back to the FK column's own `onDelete`, which is what makes a bare `@Field({
166
- // references, onDelete })` work with no relation declared at all.
167
- onDelete: relation.onDelete ?? meta.fields[relation.references[0].local]?.onDelete ?? ctx.defaultForeignKeyAction,
168
- onUpdate: relation.onUpdate ?? ctx.defaultForeignKeyAction,
169
- confidence: 1.0,
170
- inferredFrom: 'entity_decorator',
171
- });
139
+ // Every pair, not just the first: a composite key is one constraint over all its columns, and
140
+ // the engine requires the referenced columns to match a unique constraint as a whole.
141
+ const localColumns = [];
142
+ const foreignColumns = [];
143
+ for (const { local: localProp, foreign: foreignProp } of foreignKey.references) {
144
+ const localField = meta.fields[localProp];
145
+ const foreignField = relatedMeta.fields[foreignProp];
146
+ const localColumn = localField && table.columns.get(ctx.resolveColumnName(localProp, localField));
147
+ const foreignColumn = foreignField && relatedTable.columns.get(ctx.resolveColumnName(foreignProp, foreignField));
148
+ if (!localColumn || !foreignColumn)
149
+ break;
150
+ localColumns.push(localColumn);
151
+ foreignColumns.push(foreignColumn);
172
152
  }
153
+ // A pair that cannot be resolved drops the whole constraint: half of one enforces a rule
154
+ // nobody declared, over a subset of the key.
155
+ if (localColumns.length !== foreignKey.references.length)
156
+ continue;
157
+ ctx.ast.addRelationship({
158
+ name: derivedForeignKeyName(table.name, localColumns.map((column) => column.name)),
159
+ type: foreignKey.cardinality === 'm1' ? 'ManyToOne' : 'OneToOne',
160
+ from: { table, columns: localColumns },
161
+ to: { table: relatedTable, columns: foreignColumns },
162
+ // Falls back to the FK column's own `onDelete`, which is what makes a bare `@Field({
163
+ // references, onDelete })` work with no relation declared at all.
164
+ onDelete: foreignKey.onDelete ?? meta.fields[foreignKey.references[0].local]?.onDelete ?? ctx.defaultForeignKeyAction,
165
+ onUpdate: foreignKey.onUpdate ?? ctx.defaultForeignKeyAction,
166
+ confidence: 1.0,
167
+ inferredFrom: 'entity_decorator',
168
+ });
173
169
  }
174
170
  }
175
171
  /**
@@ -180,23 +176,7 @@ function addIndexesFromEntity(ctx, meta) {
180
176
  const table = tableOf(ctx, meta);
181
177
  if (!table)
182
178
  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 ?? []) {
179
+ for (const idxMeta of declaredIndexes(meta)) {
200
180
  addCompositeIndex(ctx, table, meta, idxMeta);
201
181
  }
202
182
  addForeignKeyIndexes(ctx, meta, table);
@@ -235,7 +215,7 @@ function resolveIncludeColumn(ctx, meta, column) {
235
215
  return field ? ctx.resolveColumnName(column, field) : column;
236
216
  }
237
217
  /**
238
- * One `@Index`. Its entries keep the authored form (expression, prefix length, order) with
218
+ * One index the entity declares. Its entries keep the authored form (expression, prefix length, order) with
239
219
  * names resolved, so the generator renders exactly what was declared; `columns` is the resolvable
240
220
  * subset, which is what diffing and introspection compare.
241
221
  */
@@ -251,14 +231,15 @@ function addCompositeIndex(ctx, table, meta, idxMeta) {
251
231
  });
252
232
  if (!resolved.length)
253
233
  return;
234
+ const name = idxMeta.name ?? derivedIndexName(table.name, indexNameParts(resolved));
254
235
  ctx.ast.addIndex({
255
- name: idxMeta.name ?? derivedIndexName(table.name, indexNameParts(resolved)),
236
+ name,
256
237
  table,
257
238
  entries: resolved.map((entry) => renderIndexColumn(entry, (sql) => ctx.compileDdl(sql, meta.entity))),
258
239
  include: idxMeta.include?.map((column) => resolveIncludeColumn(ctx, meta, column)),
259
240
  unique: idxMeta.unique ?? false,
260
241
  type: idxMeta.type,
261
- where: idxMeta.where && ctx.compileDdl(idxMeta.where, meta.entity),
242
+ where: idxMeta.where && ctx.compileIndexPredicate(idxMeta.where, meta.entity, name),
262
243
  distance: idxMeta.distance,
263
244
  m: idxMeta.m,
264
245
  efConstruction: idxMeta.efConstruction,
@@ -15,14 +15,18 @@ export type SqliteExecution = {
15
15
  readonly changes: number;
16
16
  };
17
17
  /**
18
- * Querier for every SQLite driver. Each supplies one hook, {@link execute}; what they share - the
19
- * wide-integer decode, and a RETURNING statement counted by its rows - is written once here.
18
+ * Querier for every SQLite driver. Each supplies {@link execute}, and {@link iterate} where it steps a
19
+ * statement row by row; what they share - the values bound as SQLite takes them, the wide-integer
20
+ * decode, and a RETURNING statement counted by its rows - is written once here.
20
21
  */
21
22
  export declare abstract class AbstractSqliteQuerier extends AbstractSqlQuerier {
22
23
  /** Runs one statement, answering its rows as the driver read them. */
23
- protected abstract execute(query: string, values?: unknown[]): Promise<SqliteExecution>;
24
+ protected abstract execute(query: string, values: SqliteBindValue[]): Promise<SqliteExecution>;
25
+ /** A statement's rows as the driver answers them: read whole, unless it steps them one at a time. */
26
+ protected iterate(query: string, values: SqliteBindValue[]): Promise<Iterable<RawRow> | AsyncIterable<RawRow>>;
24
27
  internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
25
28
  internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
29
+ internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, unknown>;
26
30
  /**
27
31
  * SQLite drivers hold a single shared handle rather than a connection from a pool, so releasing
28
32
  * a querier returns nothing at all. Drivers owning a closable per-querier connection override this.
@@ -1,22 +1,36 @@
1
1
  import { AbstractSqlQuerier } from '../querier/index.js';
2
2
  import { decodeBigInts } from '../util/wideNumber.js';
3
3
  /**
4
- * Querier for every SQLite driver. Each supplies one hook, {@link execute}; what they share - the
5
- * wide-integer decode, and a RETURNING statement counted by its rows - is written once here.
4
+ * Querier for every SQLite driver. Each supplies {@link execute}, and {@link iterate} where it steps a
5
+ * statement row by row; what they share - the values bound as SQLite takes them, the wide-integer
6
+ * decode, and a RETURNING statement counted by its rows - is written once here.
6
7
  */
7
8
  export class AbstractSqliteQuerier extends AbstractSqlQuerier {
9
+ /** A statement's rows as the driver answers them: read whole, unless it steps them one at a time. */
10
+ async iterate(query, values) {
11
+ return (await this.execute(query, values)).rows;
12
+ }
8
13
  async internalAll(query, values) {
9
- const { rows } = await this.execute(query, values);
14
+ const { rows } = await this.execute(query, toBindValues(values));
10
15
  return rows.map(decodeBigInts);
11
16
  }
12
17
  async internalRun(query, values) {
13
- const { rows, changes } = await this.execute(query, values);
18
+ const { rows, changes } = await this.execute(query, toBindValues(values));
14
19
  // A driver's own count is unreliable for a RETURNING statement (Hrana answers 0), so its rows answer.
15
20
  return this.buildUpdateResult({ rows: rows.map(decodeBigInts), changes: rows.length || changes });
16
21
  }
22
+ async *internalStream(query, values) {
23
+ for await (const row of await this.iterate(query, toBindValues(values))) {
24
+ yield decodeBigInts(row);
25
+ }
26
+ }
17
27
  /**
18
28
  * SQLite drivers hold a single shared handle rather than a connection from a pool, so releasing
19
29
  * a querier returns nothing at all. Drivers owning a closable per-querier connection override this.
20
30
  */
21
31
  async internalRelease() { }
22
32
  }
33
+ /** The compiler hands values over untyped, which the dialect has already normalized to what SQLite binds. */
34
+ function toBindValues(values = []) {
35
+ return values;
36
+ }
@@ -44,7 +44,7 @@ export declare class HranaQuerier extends AbstractSqliteQuerier {
44
44
  private readonly closeClientOnRelease;
45
45
  constructor(client: HranaClient, dialect: SqliteDialect, extra?: ExtraOptions | undefined, connection?: HranaQuerierConnectionOptions);
46
46
  /** Runs on the open transaction's handle when there is one. */
47
- protected execute(query: string, values?: unknown[]): Promise<{
47
+ protected execute(query: string, values: SqliteBindValue[]): Promise<{
48
48
  rows: RawRow[];
49
49
  changes: number;
50
50
  }>;
@@ -20,6 +20,13 @@ export type LocalSqliteDatabase = {
20
20
  loadExtension(path: string): void;
21
21
  close(): unknown;
22
22
  };
23
+ /**
24
+ * A `node:sqlite` or `bun:sqlite` handle as a {@link LocalSqliteDatabase}. Neither says whether a statement
25
+ * reads, so `reads` does: taken by `run()`, a RETURNING statement's rows would be lost, and its ids with them.
26
+ */
27
+ export declare function adaptSqlite<S extends Omit<SqlitePreparedStatement, 'reader'>>(db: Omit<LocalSqliteDatabase, 'prepare'> & {
28
+ prepare(sql: string): S;
29
+ }, reads: (stmt: S) => boolean): LocalSqliteDatabase;
23
30
  /**
24
31
  * Pool for a SQLite database opened in this process, whichever driver provides it. SQLite gives one
25
32
  * connection per file, so the shared-handle lifecycle is {@link AbstractSharedHandleQuerierPool}'s.
@@ -3,6 +3,25 @@ import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandle
3
3
  import { SqliteDialect } from './sqliteDialect.js';
4
4
  import { applySqlitePragmas } from './sqlitePragmas.js';
5
5
  import { SqliteQuerier } from './sqliteQuerier.js';
6
+ /**
7
+ * A `node:sqlite` or `bun:sqlite` handle as a {@link LocalSqliteDatabase}. Neither says whether a statement
8
+ * reads, so `reads` does: taken by `run()`, a RETURNING statement's rows would be lost, and its ids with them.
9
+ */
10
+ export function adaptSqlite(db, reads) {
11
+ return {
12
+ prepare: (sql) => {
13
+ const stmt = db.prepare(sql);
14
+ return {
15
+ reader: reads(stmt),
16
+ all: (...values) => stmt.all(...values),
17
+ run: (...values) => stmt.run(...values),
18
+ iterate: (...values) => stmt.iterate(...values),
19
+ };
20
+ },
21
+ loadExtension: (path) => db.loadExtension(path),
22
+ close: () => db.close(),
23
+ };
24
+ }
6
25
  /**
7
26
  * Pool for a SQLite database opened in this process, whichever driver provides it. SQLite gives one
8
27
  * connection per file, so the shared-handle lifecycle is {@link AbstractSharedHandleQuerierPool}'s.
@@ -1,5 +1,4 @@
1
- import { AbstractLocalSqliteQuerierPool, } from './localSqliteQuerierPool.js';
2
- import { adaptNodeSqlite } from './nodeSqliteAdapter.js';
1
+ import { AbstractLocalSqliteQuerierPool, adaptSqlite, } from './localSqliteQuerierPool.js';
3
2
  /**
4
3
  * Pool backed by Node's built-in `node:sqlite`, so SQLite works with **no dependency at all** rather
5
4
  * than requiring the `better-sqlite3` native build. Use {@link Sqlite3QuerierPool} instead when you
@@ -25,6 +24,6 @@ export class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool {
25
24
  // `node:sqlite` refuses `loadExtension` unless the database was opened with this on.
26
25
  ...(extensions?.length ? { allowExtension: true } : undefined),
27
26
  });
28
- return adaptNodeSqlite(nodeDb);
27
+ return adaptSqlite(nodeDb, (stmt) => stmt.columns().length > 0);
29
28
  }
30
29
  }
@@ -7,9 +7,6 @@ import { columnFamily, isIntegerColumn } from '../util/field.util.js';
7
7
  export class SqliteDialect extends AbstractSqlDialect {
8
8
  /** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
9
9
  featureDefaults = {
10
- explicitJsonCast: false,
11
- nativeArrays: false,
12
- supportsJsonb: false,
13
10
  ifNotExists: true,
14
11
  indexIfNotExists: true,
15
12
  schemas: false, // SQLite's namespaces are attached database files, not declared objects
@@ -1,9 +1,12 @@
1
1
  import type { ExtraOptions, RawRow } from '../type/index.js';
2
2
  import { AbstractSqliteQuerier, type SqliteBindValue } from './abstractSqliteQuerier.js';
3
3
  import type { SqliteDialect } from './sqliteDialect.js';
4
- /** What uql reads of a driver's `run()`: the row count. An inserted id comes back through `RETURNING`. */
4
+ /**
5
+ * What uql reads of a driver's `run()`: the row count, which `node:sqlite` answers as a `bigint` once it
6
+ * reads integers as ones. An inserted id comes back through `RETURNING`.
7
+ */
5
8
  export type SqliteRunResult = {
6
- changes: number;
9
+ changes: number | bigint;
7
10
  };
8
11
  /** A prepared statement with better-sqlite3 semantics, answering at once or with a promise. */
9
12
  export type SqlitePreparedStatement = {
@@ -15,7 +18,7 @@ export type SqlitePreparedStatement = {
15
18
  };
16
19
  /**
17
20
  * A SQLite driver that prepares statements. `better-sqlite3` and the embedded Turso engine satisfy it
18
- * as they are, `bun:sqlite` and `node:sqlite` through their adapters.
21
+ * as they are, `bun:sqlite` and `node:sqlite` through `adaptSqlite`.
19
22
  */
20
23
  export type SqliteDatabase = {
21
24
  prepare(sql: string): SqlitePreparedStatement | Promise<SqlitePreparedStatement>;
@@ -31,9 +34,9 @@ export declare class SqliteQuerier extends AbstractSqliteQuerier {
31
34
  readonly extra?: ExtraOptions | undefined;
32
35
  constructor(db: SqliteDatabase, dialect: SqliteDialect, extra?: ExtraOptions | undefined);
33
36
  /** `reader` picks the call: `run()` would discard the rows of a statement that reads, RETURNING included. */
34
- protected execute(query: string, values?: unknown[]): Promise<{
37
+ protected execute(query: string, values: SqliteBindValue[]): Promise<{
35
38
  rows: RawRow[];
36
39
  changes: number;
37
40
  }>;
38
- internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, unknown>;
41
+ protected iterate(query: string, values: SqliteBindValue[]): Promise<AsyncIterable<RawRow> | Iterable<RawRow>>;
39
42
  }
@@ -1,4 +1,3 @@
1
- import { decodeBigInts } from '../util/wideNumber.js';
2
1
  import { AbstractSqliteQuerier } from './abstractSqliteQuerier.js';
3
2
  /**
4
3
  * Querier for the SQLite drivers that prepare statements: `better-sqlite3`, `bun:sqlite`, `node:sqlite`
@@ -16,20 +15,12 @@ export class SqliteQuerier extends AbstractSqliteQuerier {
16
15
  /** `reader` picks the call: `run()` would discard the rows of a statement that reads, RETURNING included. */
17
16
  async execute(query, values) {
18
17
  const stmt = await this.db.prepare(query);
19
- const bound = toBindValues(values);
20
18
  if (stmt.reader) {
21
- return { rows: (await stmt.all(...bound)), changes: 0 };
19
+ return { rows: (await stmt.all(...values)), changes: 0 };
22
20
  }
23
- return { rows: [], changes: (await stmt.run(...bound)).changes };
21
+ return { rows: [], changes: Number((await stmt.run(...values)).changes) };
24
22
  }
25
- async *internalStream(query, values) {
26
- const stmt = await this.db.prepare(query);
27
- for await (const row of stmt.iterate(...toBindValues(values))) {
28
- yield decodeBigInts(row);
29
- }
23
+ async iterate(query, values) {
24
+ return (await this.db.prepare(query)).iterate(...values);
30
25
  }
31
26
  }
32
- /** Bound parameters reach a driver as `unknown[]` from the compiler; every driver types them narrowly. */
33
- function toBindValues(values) {
34
- return (values ?? []);
35
- }
@@ -1,4 +1,4 @@
1
- import { AbstractLocalSqliteQuerierPool, } from './localSqliteQuerierPool.js';
1
+ import { AbstractLocalSqliteQuerierPool, adaptSqlite, } from './localSqliteQuerierPool.js';
2
2
  /**
3
3
  * Pool for `better-sqlite3`, or `bun:sqlite` when running under Bun - the same file, through whichever
4
4
  * driver the runtime provides.
@@ -18,11 +18,11 @@ export class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool {
18
18
  const { extensions, ...driverOpts } = this.opts ?? {};
19
19
  if (typeof Bun !== 'undefined') {
20
20
  const { Database } = await import('bun:sqlite');
21
- const { adaptBunSqlite } = await import('./bunSqliteAdapter.bun.js');
22
21
  const bunOpts = { ...driverOpts, safeIntegers: true };
23
- return adaptBunSqlite(typeof this.filename === 'string'
22
+ const bunDb = typeof this.filename === 'string'
24
23
  ? new Database(this.filename, bunOpts)
25
- : Database.deserialize(this.filename, bunOpts));
24
+ : Database.deserialize(this.filename, bunOpts);
25
+ return adaptSqlite(bunDb, (stmt) => stmt.columnNames.length > 0);
26
26
  }
27
27
  const { default: BetterSqlite3 } = await import('better-sqlite3');
28
28
  return new BetterSqlite3(this.filename, driverOpts).defaultSafeIntegers(true);
@@ -47,11 +47,11 @@ export declare class TursoSessionQuerier extends AbstractSqliteQuerier {
47
47
  readonly session: TursoSession;
48
48
  readonly extra?: ExtraOptions | undefined;
49
49
  constructor(session: TursoSession, dialect: SqliteDialect, extra?: ExtraOptions | undefined);
50
- protected execute(query: string, values?: unknown[]): Promise<{
50
+ protected execute(query: string, values: SqliteBindValue[]): Promise<{
51
51
  rows: RawRow[];
52
52
  changes: number;
53
53
  }>;
54
54
  /** Row by row off the statement's cursor, as the server steps it, each decoded as `execute` decodes one. */
55
- internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, unknown>;
55
+ protected iterate(query: string, values: SqliteBindValue[]): Promise<AsyncGenerator<RawRow, void, unknown>>;
56
56
  internalRelease(): Promise<void>;
57
57
  }