uql-orm 0.54.0 → 0.56.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 (162) hide show
  1. package/README.md +2 -2
  2. package/dist/browser/uql-browser.min.js +2 -2
  3. package/dist/browser/uql-browser.min.js.map +4 -4
  4. package/dist/bunSql/bunSql.util.d.ts +3 -14
  5. package/dist/bunSql/bunSql.util.js +33 -56
  6. package/dist/bunSql/bunSqlQuerier.d.ts +3 -6
  7. package/dist/bunSql/bunSqlQuerier.js +7 -13
  8. package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -5
  9. package/dist/bunSql/bunSqlQuerierPool.js +25 -10
  10. package/dist/cockroachdb/crdbQuerierPool.d.ts +1 -3
  11. package/dist/cockroachdb/crdbQuerierPool.js +0 -4
  12. package/dist/cockroachdb/index.d.ts +0 -1
  13. package/dist/cockroachdb/index.js +0 -1
  14. package/dist/d1/d1SqliteDialect.d.ts +5 -0
  15. package/dist/d1/d1SqliteDialect.js +7 -0
  16. package/dist/dialect/abstractSqlDialect.d.ts +9 -9
  17. package/dist/dialect/abstractSqlDialect.js +38 -51
  18. package/dist/dialect/hydrateColumn.js +2 -2
  19. package/dist/dialect/mergeSqlDialect.d.ts +2 -2
  20. package/dist/dialect/mergeSqlDialect.js +0 -4
  21. package/dist/dialect/mysqlLikeSqlDialect.js +2 -3
  22. package/dist/dialect/pgLikeSqlDialect.d.ts +5 -0
  23. package/dist/dialect/pgLikeSqlDialect.js +14 -4
  24. package/dist/dialect/queryJoins.js +2 -4
  25. package/dist/entity/index.d.ts +1 -1
  26. package/dist/entity/index.js +1 -1
  27. package/dist/entity/metadata/definition.d.ts +5 -1
  28. package/dist/entity/metadata/definition.js +32 -26
  29. package/dist/libsql/index.d.ts +0 -1
  30. package/dist/libsql/index.js +0 -1
  31. package/dist/libsql/libsqlQuerierPool.d.ts +3 -5
  32. package/dist/libsql/libsqlQuerierPool.js +2 -5
  33. package/dist/maria/mariadbQuerier.d.ts +0 -3
  34. package/dist/maria/mariadbQuerier.js +6 -7
  35. package/dist/maria/mariadbQuerierPool.js +4 -7
  36. package/dist/migrate/builder/splitSqlStatements.js +2 -2
  37. package/dist/migrate/cli.js +2 -9
  38. package/dist/migrate/codegen/fieldOptionsSource.js +1 -1
  39. package/dist/migrate/ddl/index.d.ts +3 -3
  40. package/dist/migrate/ddl/index.js +3 -3
  41. package/dist/migrate/ddl/pgIndexDdl.d.ts +5 -0
  42. package/dist/migrate/ddl/pgIndexDdl.js +9 -0
  43. package/dist/migrate/drift/driftDetector.js +21 -8
  44. package/dist/migrate/generator/mongoSchemaGenerator.js +1 -1
  45. package/dist/migrate/introspection/baseSqlIntrospector.d.ts +1 -1
  46. package/dist/migrate/introspection/baseSqlIntrospector.js +65 -76
  47. package/dist/migrate/introspection/mssqlIntrospector.js +2 -1
  48. package/dist/migrate/introspection/mysqlIntrospector.js +5 -8
  49. package/dist/migrate/introspection/sqliteIntrospector.js +2 -5
  50. package/dist/migrate/migrator.d.ts +5 -2
  51. package/dist/migrate/migrator.js +9 -14
  52. package/dist/migrate/schemaGenerator.js +2 -6
  53. package/dist/mongo/index.d.ts +0 -1
  54. package/dist/mongo/index.js +0 -1
  55. package/dist/mongo/mongoDialect.d.ts +1 -2
  56. package/dist/mongo/mongoDialect.js +7 -21
  57. package/dist/mongo/mongodbQuerier.js +12 -11
  58. package/dist/mongo/mongodbQuerierPool.d.ts +2 -2
  59. package/dist/mongo/mongodbQuerierPool.js +2 -2
  60. package/dist/mssql/mssqlDialect.d.ts +3 -5
  61. package/dist/mssql/mssqlDialect.js +9 -8
  62. package/dist/mssql/mssqlQuerier.d.ts +13 -6
  63. package/dist/mssql/mssqlQuerier.js +30 -75
  64. package/dist/mssql/mssqlQuerierPool.js +2 -0
  65. package/dist/mssql/mssqlWireTypes.d.ts +3 -4
  66. package/dist/mssql/mssqlWireTypes.js +5 -8
  67. package/dist/mysql/index.d.ts +0 -1
  68. package/dist/mysql/index.js +0 -1
  69. package/dist/mysql/mysql2Querier.d.ts +1 -4
  70. package/dist/mysql/mysql2Querier.js +0 -3
  71. package/dist/mysql/mysql2QuerierPool.d.ts +2 -2
  72. package/dist/mysql/mysql2QuerierPool.js +5 -3
  73. package/dist/neon/index.d.ts +0 -2
  74. package/dist/neon/index.js +0 -2
  75. package/dist/neon/neonQuerierPool.d.ts +2 -4
  76. package/dist/neon/neonQuerierPool.js +2 -6
  77. package/dist/pglite/index.d.ts +0 -1
  78. package/dist/pglite/index.js +0 -1
  79. package/dist/pglite/pgliteQuerier.d.ts +3 -3
  80. package/dist/pglite/pgliteQuerier.js +1 -1
  81. package/dist/pglite/pgliteQuerierPool.d.ts +7 -2
  82. package/dist/pglite/pgliteQuerierPool.js +16 -6
  83. package/dist/postgres/abstractPgQuerierPool.d.ts +8 -12
  84. package/dist/postgres/abstractPgQuerierPool.js +8 -6
  85. package/dist/postgres/index.d.ts +0 -1
  86. package/dist/postgres/index.js +0 -1
  87. package/dist/postgres/pgNumericTypes.d.ts +5 -5
  88. package/dist/postgres/pgNumericTypes.js +11 -7
  89. package/dist/postgres/pgQuerier.d.ts +22 -4
  90. package/dist/postgres/pgQuerier.js +29 -2
  91. package/dist/postgres/pgQuerierPool.d.ts +2 -4
  92. package/dist/postgres/pgQuerierPool.js +2 -6
  93. package/dist/postgres/postgresDialect.d.ts +5 -5
  94. package/dist/postgres/postgresDialect.js +5 -5
  95. package/dist/postgres/postgresWireDriverCapabilities.d.ts +2 -2
  96. package/dist/postgres/postgresWireDriverCapabilities.js +2 -2
  97. package/dist/querier/abstractPoolQuerier.d.ts +1 -1
  98. package/dist/querier/abstractPoolQuerier.js +1 -1
  99. package/dist/querier/abstractQuerier.js +17 -22
  100. package/dist/querier/abstractSqlQuerier.d.ts +21 -12
  101. package/dist/querier/abstractSqlQuerier.js +61 -43
  102. package/dist/schema/canonicalType.js +0 -2
  103. package/dist/schema/dependencyGraph.js +2 -4
  104. package/dist/schema/indexDifferences.js +2 -2
  105. package/dist/schema/schemaASTBuilder.js +3 -10
  106. package/dist/schema/schemaASTDiffer.d.ts +10 -2
  107. package/dist/schema/schemaASTDiffer.js +12 -11
  108. package/dist/schema/types.d.ts +1 -1
  109. package/dist/sqlite/hranaQuerier.d.ts +6 -8
  110. package/dist/sqlite/hranaQuerier.js +13 -30
  111. package/dist/sqlite/hranaQuerierPool.d.ts +3 -4
  112. package/dist/sqlite/hranaQuerierPool.js +2 -1
  113. package/dist/sqlite/localSqliteQuerierPool.d.ts +3 -3
  114. package/dist/sqlite/localSqliteQuerierPool.js +4 -2
  115. package/dist/sqlite/nodeSqliteAdapter.d.ts +0 -1
  116. package/dist/sqlite/nodeSqliteQuerierPool.js +0 -2
  117. package/dist/sqlite/sqliteDialect.js +2 -1
  118. package/dist/sqlite/sqlitePragmas.d.ts +12 -0
  119. package/dist/sqlite/sqlitePragmas.js +15 -0
  120. package/dist/sqlite/sqliteQuerierPool.d.ts +0 -5
  121. package/dist/sqlite/sqliteQuerierPool.js +2 -13
  122. package/dist/turso/index.d.ts +0 -1
  123. package/dist/turso/index.js +0 -1
  124. package/dist/turso/tursoLocalQuerier.d.ts +0 -1
  125. package/dist/turso/tursoLocalQuerierPool.js +2 -2
  126. package/dist/turso/tursoQuerierPool.d.ts +1 -3
  127. package/dist/turso/tursoQuerierPool.js +0 -4
  128. package/dist/type/dialect.d.ts +1 -1
  129. package/dist/util/dialect.util.d.ts +8 -2
  130. package/dist/util/dialect.util.js +20 -1
  131. package/dist/util/logger.d.ts +17 -9
  132. package/dist/util/logger.js +36 -11
  133. package/dist/util/object.util.d.ts +2 -0
  134. package/dist/util/object.util.js +4 -0
  135. package/dist/util/raw.js +3 -3
  136. package/dist/util/relationQuery.util.d.ts +3 -3
  137. package/dist/util/relationQuery.util.js +8 -2
  138. package/dist/util/sqlLiteral.js +3 -8
  139. package/dist/util/string.util.js +2 -6
  140. package/dist/util/wideNumber.d.ts +14 -0
  141. package/dist/util/wideNumber.js +24 -0
  142. package/package.json +1 -1
  143. package/dist/cockroachdb/crdbQuerier.d.ts +0 -8
  144. package/dist/cockroachdb/crdbQuerier.js +0 -6
  145. package/dist/libsql/libsqlQuerier.d.ts +0 -10
  146. package/dist/libsql/libsqlQuerier.js +0 -10
  147. package/dist/mongo/mongodbNativeDialect.d.ts +0 -9
  148. package/dist/mongo/mongodbNativeDialect.js +0 -9
  149. package/dist/mysql/mysql2Dialect.d.ts +0 -9
  150. package/dist/mysql/mysql2Dialect.js +0 -9
  151. package/dist/neon/neonDialect.d.ts +0 -10
  152. package/dist/neon/neonDialect.js +0 -10
  153. package/dist/neon/neonQuerier.d.ts +0 -5
  154. package/dist/neon/neonQuerier.js +0 -3
  155. package/dist/pglite/pgliteDialect.d.ts +0 -14
  156. package/dist/pglite/pgliteDialect.js +0 -14
  157. package/dist/postgres/abstractPgQuerier.d.ts +0 -24
  158. package/dist/postgres/abstractPgQuerier.js +0 -32
  159. package/dist/postgres/pgDialect.d.ts +0 -10
  160. package/dist/postgres/pgDialect.js +0 -10
  161. package/dist/turso/tursoQuerier.d.ts +0 -10
  162. package/dist/turso/tursoQuerier.js +0 -10
@@ -1,7 +1,7 @@
1
1
  import { COUNT_ALIAS, TOTAL_ALIAS } from '../dialect/aliases.js';
2
2
  import { decodeColumn } from '../dialect/hydrateColumn.js';
3
3
  import { getMeta, idOf, namesKey } from '../entity/index.js';
4
- import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, insertShapeOf, getRelationRequestSummary, hasKeys, idOnlyQuery, isAutoIncrement, isPagedQuery, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
4
+ import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, insertShapeOf, getRelationRequestSummary, idOnlyQuery, isAutoIncrement, isPagedQuery, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
5
5
  import { AbstractQuerier } from './abstractQuerier.js';
6
6
  import { enrichError } from './queryError.js';
7
7
  /**
@@ -223,8 +223,11 @@ export class AbstractSqlQuerier extends AbstractQuerier {
223
223
  return this.all(ctx.sql, ctx.values);
224
224
  }
225
225
  async hydrateRows(entity, q, rows) {
226
- const founds = unflatObjects(rows).map((row) => this.hydrateFields(entity, row));
227
- await this.fillToManyRelations(entity, founds, q.$populate);
226
+ const founds = unflatObjects(rows);
227
+ this.hydrateAll(entity, founds);
228
+ if (q.$populate) {
229
+ await this.fillToManyRelations(entity, founds, q.$populate);
230
+ }
228
231
  return founds;
229
232
  }
230
233
  async *internalFindManyStream(entity, q, opts) {
@@ -241,18 +244,22 @@ export class AbstractSqlQuerier extends AbstractQuerier {
241
244
  // The one path that does not go through `all`/`run`, so it connects on its own: streaming first on
242
245
  // a freshly acquired querier used to reach `getConn()` with nothing acquired.
243
246
  await this.lazyConnect();
247
+ // No `normalizeValues` here, unlike `all`/`run`: those also take raw SQL, while every value a
248
+ // context holds was normalized as it was bound.
244
249
  const ctx = this.dialect.createContext();
245
250
  this.dialect.find(ctx, entity, q, opts);
246
- const normalizedParams = this.dialect.normalizeValues(ctx.values);
251
+ const fields = this.dialect.hydratableFields(entity);
247
252
  let attrsPaths;
248
253
  try {
249
- for await (const row of this.internalStream(ctx.sql, normalizedParams)) {
254
+ for await (const row of this.internalStream(ctx.sql, ctx.values)) {
250
255
  attrsPaths ??= obtainAttrsPaths(row);
251
- yield this.hydrateFields(entity, unflatObject(row, attrsPaths));
256
+ const found = unflatObject(row, attrsPaths);
257
+ this.hydrateFields(meta, fields, found);
258
+ yield found;
252
259
  }
253
260
  }
254
261
  catch (err) {
255
- throw enrichError(err, this.logger, ctx.sql, normalizedParams);
262
+ throw enrichError(err, this.logger, ctx.sql, ctx.values);
256
263
  }
257
264
  }
258
265
  /**
@@ -261,37 +268,38 @@ export class AbstractSqlQuerier extends AbstractQuerier {
261
268
  * Drivers with native cursor/streaming APIs (SQLite, Pg) should override this.
262
269
  */
263
270
  async *internalStream(query, values) {
264
- const rows = await this.internalAll(query, this.dialect.normalizeValues(values));
265
- yield* rows;
271
+ yield* await this.internalAll(query, values);
266
272
  }
267
273
  /**
268
- * Turn what a driver returned back into the types the entity declares, for the row and everything
269
- * populated under it. Which columns, and as what, is `hydratableFields`; the per-cell decode is
270
- * `decodeColumn`. Both live with the dialect, because a `sparsevec` is only sparse on Postgres.
271
- *
272
- * `visited` guards a populated graph that points back at itself, and makes a node two paths reach
273
- * decode once. Only a relation can lead the walk back somewhere it has been, so an entity that
274
- * declares none skips the guard rather than allocating a set per row to hold a single object -
275
- * which cost more than the decoding it guards, on a flat read.
274
+ * Turn what a driver returned back into the types the entity declares, for every row and everything
275
+ * populated under them. Which columns, and as what, is `hydratableFields`, resolved once for all the
276
+ * rows; the per-cell decode is `decodeColumn`. Both live with the dialect, because a `sparsevec` is
277
+ * only sparse on Postgres.
276
278
  */
277
- hydrateFields(entity, dto, visited) {
279
+ hydrateAll(entity, dtos, visited) {
280
+ const meta = getMeta(entity);
281
+ const fields = this.dialect.hydratableFields(entity);
282
+ for (const dto of dtos) {
283
+ this.hydrateFields(meta, fields, dto, visited);
284
+ }
285
+ }
286
+ /**
287
+ * One row of {@link hydrateAll}. `visited` guards a populated graph that points back at itself, and
288
+ * makes a node two paths reach decode once. Only a populated relation can lead the walk back, so the
289
+ * guard is created at the first one a row carries: rows that populated nothing never allocate one.
290
+ */
291
+ hydrateFields(meta, fields, dto, visited) {
278
292
  if (!dto || typeof dto !== 'object' || visited?.has(dto)) {
279
- return dto;
293
+ return;
280
294
  }
281
- const meta = getMeta(entity);
295
+ visited?.add(dto);
282
296
  const row = dto;
283
- for (const [key, kind] of this.dialect.hydratableFields(entity)) {
297
+ for (const [key, kind] of fields) {
284
298
  const value = row[key];
285
299
  if (value != null) {
286
300
  row[key] = decodeColumn(value, kind);
287
301
  }
288
302
  }
289
- // Allocated only where the walk can continue: an entity declaring no relation cannot lead back
290
- // to a node already decoded, and the loop below is a no-op for it anyway.
291
- if (hasKeys(meta.relations)) {
292
- visited ??= new WeakSet();
293
- }
294
- visited?.add(dto);
295
303
  // The value is read before the relation's target is resolved: a query that populated nothing
296
304
  // still walks every relation the entity declares, and `rel.entity()` is a call per row per
297
305
  // relation that only the populated ones need.
@@ -302,16 +310,14 @@ export class AbstractSqlQuerier extends AbstractQuerier {
302
310
  const rel = meta.relations[key];
303
311
  if (!rel)
304
312
  continue;
313
+ visited ??= new WeakSet([dto]);
305
314
  const relEntity = rel.entity();
306
315
  if (Array.isArray(value)) {
307
- for (const it of value) {
308
- this.hydrateFields(relEntity, it, visited);
309
- }
316
+ this.hydrateAll(relEntity, value, visited);
310
317
  continue;
311
318
  }
312
- this.hydrateFields(relEntity, value, visited);
319
+ this.hydrateFields(getMeta(relEntity), this.dialect.hydratableFields(relEntity), value, visited);
313
320
  }
314
- return dto;
315
321
  }
316
322
  /**
317
323
  * Runs a statement whose one row carries a {@link COUNT_ALIAS} column. `Number` because `COUNT(*)` is BIGINT and
@@ -504,35 +510,47 @@ export class AbstractSqlQuerier extends AbstractQuerier {
504
510
  throwPendingTransaction();
505
511
  }
506
512
  await this.lazyConnect();
507
- for (const sql of this.dialect.getBeginTransactionStatements(opts?.isolationLevel)) {
508
- await this.runTransactionCommand(sql);
509
- }
513
+ await this.internalBegin(opts);
510
514
  this.hasPendingTransaction = true;
511
515
  });
512
516
  }
517
+ /**
518
+ * Only an end that succeeded ends the transaction. A `COMMIT` that fails can leave it open (SQLite
519
+ * answers `SQLITE_BUSY` and keeps it), so the flag has to stay set for the `catch` in
520
+ * {@link AbstractQuerier.transaction} or {@link AbstractQuerier.release} to roll it back.
521
+ */
513
522
  async commitTransaction() {
514
523
  return this.serialize(async () => {
515
524
  if (!this.hasPendingTransaction) {
516
525
  throwNoPendingTransaction();
517
526
  }
518
- await this.endTransactionWith(this.dialect.commitTransactionCommand);
527
+ await this.internalCommit();
528
+ this.hasPendingTransaction = false;
519
529
  });
520
530
  }
521
531
  async rollbackTransaction() {
522
532
  return this.serialize(async () => {
523
533
  if (this.hasPendingTransaction) {
524
- await this.endTransactionWith(this.dialect.rollbackTransactionCommand);
534
+ await this.internalRollback();
535
+ this.hasPendingTransaction = false;
525
536
  }
526
537
  });
527
538
  }
528
539
  /**
529
- * Only a statement that succeeded ends the transaction. A `COMMIT` that fails can leave it open
530
- * (SQLite answers `SQLITE_BUSY` and keeps it), so the flag has to stay set for the `catch` in
531
- * {@link AbstractQuerier.transaction} or {@link AbstractQuerier.release} to roll it back.
540
+ * How this driver opens, commits and rolls back: the dialect's statements, unless its transactions
541
+ * are objects rather than statements - Hrana's session handle, `mssql`'s `Transaction` - in which
542
+ * case it overrides all three. The bookkeeping around them stays above, written once.
532
543
  */
533
- async endTransactionWith(command) {
534
- await this.runTransactionCommand(command);
535
- this.hasPendingTransaction = false;
544
+ async internalBegin(opts) {
545
+ for (const sql of this.dialect.getBeginTransactionStatements(opts?.isolationLevel)) {
546
+ await this.runTransactionCommand(sql);
547
+ }
548
+ }
549
+ internalCommit() {
550
+ return this.runTransactionCommand(this.dialect.commitTransactionCommand);
551
+ }
552
+ internalRollback() {
553
+ return this.runTransactionCommand(this.dialect.rollbackTransactionCommand);
536
554
  }
537
555
  /** Transaction statements skip `timed()`, so they attach their own query context to a failure. */
538
556
  async runTransactionCommand(sql) {
@@ -492,7 +492,5 @@ export function canonicalToColumnType(type) {
492
492
  return 'halfvec';
493
493
  case 'sparsevec':
494
494
  return 'sparsevec';
495
- default:
496
- return 'varchar';
497
495
  }
498
496
  }
@@ -38,11 +38,9 @@ export function findCycles(nodes, dependenciesOf) {
38
38
  const visited = new Set();
39
39
  const onPath = new Set();
40
40
  const visit = (node, path) => {
41
+ // A node on the walk was handed down in `path` too, so the cycle closes where it first appears.
41
42
  if (onPath.has(node)) {
42
- const start = path.indexOf(node);
43
- if (start !== -1) {
44
- cycles.push(path.slice(start));
45
- }
43
+ cycles.push(path.slice(path.indexOf(node)));
46
44
  return;
47
45
  }
48
46
  if (visited.has(node)) {
@@ -64,8 +64,8 @@ export function describeIndexDifferences(source, target, facets) {
64
64
  differences.push(`columns: (${targetColumns}) → (${sourceColumns})`);
65
65
  }
66
66
  }
67
- if ((source.unique ?? false) !== (target.unique ?? false)) {
68
- differences.push(`unique: ${target.unique ?? false} → ${source.unique ?? false}`);
67
+ if (source.unique !== target.unique) {
68
+ differences.push(`unique: ${target.unique} → ${source.unique}`);
69
69
  }
70
70
  if (facets.has('accessMethod') && (source.type ?? 'btree') !== (target.type ?? 'btree')) {
71
71
  differences.push(`type: ${target.type ?? 'btree'} → ${source.type ?? 'btree'}`);
@@ -10,6 +10,7 @@ import { ddlText } 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';
13
+ import { definedEntries } from '../util/object.util.js';
13
14
  import { derivedForeignKeyName, derivedIndexName, qualifyName } from '../util/sql.util.js';
14
15
  import { fieldOptionsToCanonical } from './canonicalType.js';
15
16
  import { createTableNode, SchemaAST } from './schemaAST.js';
@@ -79,11 +80,7 @@ function addTableFromEntity(ctx, meta) {
79
80
  const { columns, primaryKey } = table;
80
81
  table.checks?.push(...(meta.checks ?? []));
81
82
  // Add columns from fields
82
- const fields = meta.fields;
83
- for (const key of Object.keys(fields)) {
84
- const field = fields[key];
85
- if (!field)
86
- continue;
83
+ for (const [key, field] of definedEntries(meta.fields)) {
87
84
  // An inlined expression has no column; a stored one is a column like any other.
88
85
  if (isInlinedExpression(field))
89
86
  continue;
@@ -126,11 +123,7 @@ function addRelationshipsFromEntity(ctx, meta) {
126
123
  const table = tableOf(ctx, meta);
127
124
  if (!table)
128
125
  return;
129
- const relations = meta.relations;
130
- for (const key of Object.keys(relations)) {
131
- const relation = relations[key];
132
- if (!relation)
133
- continue;
126
+ for (const [key, relation] of definedEntries(meta.relations)) {
134
127
  const relatedMeta = getMeta(relation.entity());
135
128
  const relatedTable = tableOf(ctx, relatedMeta);
136
129
  if (!relatedTable)
@@ -10,7 +10,7 @@
10
10
  import { type IndexFacet } from './indexDifferences.js';
11
11
  import type { SchemaAST } from './schemaAST.js';
12
12
  import type { CanonicalType } from './types.js';
13
- import type { RelationshipDiff, RelationshipNode, SchemaDiffResult, TableDiff, TableNode } from './types.js';
13
+ import type { ColumnDiff, ForeignKeyAction, IndexDiff, RelationshipDiff, RelationshipNode, SchemaDiffResult, TableDiff, TableNode } from './types.js';
14
14
  /**
15
15
  * Options for schema diffing.
16
16
  */
@@ -59,7 +59,10 @@ export declare function diffSchemas(source: SchemaAST, target: SchemaAST, option
59
59
  * against the one the database reported, then projects the result into a `SchemaDiff`. One
60
60
  * comparison serves both, so drift and migrations can no longer disagree about what has changed.
61
61
  */
62
- export declare function diffTable(source: TableNode, target: TableNode, options?: DiffOptions): TableDiff | undefined;
62
+ export declare function diffTable(source: TableNode, target: TableNode, options?: DiffOptions): (TableDiff & {
63
+ readonly columnDiffs: ColumnDiff[];
64
+ readonly indexDiffs: IndexDiff[];
65
+ }) | undefined;
63
66
  /**
64
67
  * Compare two lists of relationships.
65
68
  *
@@ -70,3 +73,8 @@ export declare function diffTable(source: TableNode, target: TableNode, options?
70
73
  * pairing on names would report every hand-named one as a drop and an add.
71
74
  */
72
75
  export declare function diffRelationshipNodes(source: readonly RelationshipNode[], target: readonly RelationshipNode[], opts?: DiffOptions): RelationshipDiff[];
76
+ /** A relationship's `ON DELETE` and `ON UPDATE`, an unstated one read as the action the database applies. */
77
+ export declare function referentialActions(rel: RelationshipNode): {
78
+ readonly onDelete: ForeignKeyAction;
79
+ readonly onUpdate: ForeignKeyAction;
80
+ };
@@ -61,8 +61,8 @@ export function diffSchemas(source, target, options = {}) {
61
61
  const tablesToAlter = matched
62
62
  .map(([sourceTable, targetTable]) => diffTable(sourceTable, targetTable, opts))
63
63
  .filter((tableDiff) => tableDiff !== undefined);
64
- const columnDiffs = tablesToAlter.flatMap((tableDiff) => tableDiff.columnDiffs ?? []);
65
- const indexDiffs = tablesToAlter.flatMap((tableDiff) => tableDiff.indexDiffs ?? []);
64
+ const columnDiffs = tablesToAlter.flatMap((tableDiff) => tableDiff.columnDiffs);
65
+ const indexDiffs = tablesToAlter.flatMap((tableDiff) => tableDiff.indexDiffs);
66
66
  const primaryKeyDiffs = tablesToAlter.flatMap((tableDiff) => tableDiff.primaryKeyDiff ?? []);
67
67
  // Relationships span tables, so they are compared over the whole schema rather than per table.
68
68
  const relationshipDiffs = opts.compareRelationships
@@ -265,16 +265,17 @@ export function diffRelationshipNodes(source, target, opts = {}) {
265
265
  .filter((diff) => diff !== undefined),
266
266
  ];
267
267
  }
268
- /**
269
- * Compare two relationships.
270
- */
268
+ /** A relationship's `ON DELETE` and `ON UPDATE`, an unstated one read as the action the database applies. */
269
+ export function referentialActions(rel) {
270
+ return {
271
+ onDelete: rel.onDelete ?? DEFAULT_FOREIGN_KEY_ACTION,
272
+ onUpdate: rel.onUpdate ?? DEFAULT_FOREIGN_KEY_ACTION,
273
+ };
274
+ }
275
+ /** Two relationships over the same columns differ only in what they do when the row they point at goes. */
271
276
  function diffRelationship(source, target) {
272
- // Compare on delete/update actions (normalizing defaults)
273
- const sDelete = source.onDelete ?? DEFAULT_FOREIGN_KEY_ACTION;
274
- const tDelete = target.onDelete ?? DEFAULT_FOREIGN_KEY_ACTION;
275
- const sUpdate = source.onUpdate ?? DEFAULT_FOREIGN_KEY_ACTION;
276
- const tUpdate = target.onUpdate ?? DEFAULT_FOREIGN_KEY_ACTION;
277
- if (sDelete !== tDelete || sUpdate !== tUpdate) {
277
+ const [expected, actual] = [referentialActions(source), referentialActions(target)];
278
+ if (expected.onDelete !== actual.onDelete || expected.onUpdate !== actual.onUpdate) {
278
279
  return { ...relationEnds(source), type: 'alter', expected: source, actual: target };
279
280
  }
280
281
  return undefined;
@@ -342,7 +342,7 @@ export type DriftSeverity = 'critical' | 'warning' | 'info';
342
342
  /**
343
343
  * Type of schema drift.
344
344
  */
345
- export type DriftType = 'missing_table' | 'unexpected_table' | 'missing_column' | 'unexpected_column' | 'type_mismatch' | 'constraint_mismatch' | 'missing_index' | 'unexpected_index' | 'index_mismatch' | 'missing_relationship' | 'unexpected_relationship';
345
+ export type DriftType = 'missing_table' | 'unexpected_table' | 'missing_column' | 'unexpected_column' | 'type_mismatch' | 'constraint_mismatch' | 'missing_index' | 'unexpected_index' | 'index_mismatch' | 'missing_relationship' | 'unexpected_relationship' | 'relationship_mismatch';
346
346
  /**
347
347
  * A single schema drift issue.
348
348
  */
@@ -1,4 +1,4 @@
1
- import type { ExtraOptions, TransactionOptions } from '../type/index.js';
1
+ import type { ExtraOptions } from '../type/index.js';
2
2
  import { AbstractSqliteQuerier, type SqliteBindValue } from './abstractSqliteQuerier.js';
3
3
  import type { SqliteDialect } from './sqliteDialect.js';
4
4
  /**
@@ -46,14 +46,12 @@ export declare class HranaQuerier extends AbstractSqliteQuerier {
46
46
  constructor(client: HranaClient, dialect: SqliteDialect, extra?: ExtraOptions | undefined, connection?: HranaQuerierConnectionOptions);
47
47
  internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
48
48
  internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
49
- get hasOpenTransaction(): boolean;
50
- beginTransaction(_opts?: TransactionOptions): Promise<void>;
49
+ protected internalBegin(): Promise<void>;
51
50
  /**
52
- * Both drop the handle before the call, not after: one that outlived a failed commit or rollback left
53
- * the querier unreleasable. The optional call in the rollback is also what makes it a no-op when
54
- * there is nothing open.
51
+ * Both drop the handle before the call, not after: one that outlived a failed commit would carry
52
+ * every later statement into a transaction the server may already have ended.
55
53
  */
56
- commitTransaction(): Promise<void>;
57
- rollbackTransaction(): Promise<void>;
54
+ protected internalCommit(): Promise<void>;
55
+ protected internalRollback(): Promise<void>;
58
56
  internalRelease(): Promise<void>;
59
57
  }
@@ -1,4 +1,3 @@
1
- import { throwNoPendingTransaction, throwPendingTransaction } from '../util/index.js';
2
1
  import { AbstractSqliteQuerier } from './abstractSqliteQuerier.js';
3
2
  /**
4
3
  * Querier for SQLite databases reached through a Hrana client.
@@ -30,38 +29,22 @@ export class HranaQuerier extends AbstractSqliteQuerier {
30
29
  // the actual row count when rows were returned.
31
30
  return this.buildUpdateResult({ rows, changes: rows.length || res.rowsAffected, id: res.lastInsertRowid });
32
31
  }
33
- get hasOpenTransaction() {
34
- return !!this.tx;
35
- }
36
- async beginTransaction(_opts) {
37
- return this.serialize(async () => {
38
- if (this.tx) {
39
- throwPendingTransaction();
40
- }
41
- this.tx = await this.client.transaction('write');
42
- });
32
+ async internalBegin() {
33
+ this.tx = await this.client.transaction('write');
43
34
  }
44
35
  /**
45
- * Both drop the handle before the call, not after: one that outlived a failed commit or rollback left
46
- * the querier unreleasable. The optional call in the rollback is also what makes it a no-op when
47
- * there is nothing open.
36
+ * Both drop the handle before the call, not after: one that outlived a failed commit would carry
37
+ * every later statement into a transaction the server may already have ended.
48
38
  */
49
- async commitTransaction() {
50
- return this.serialize(async () => {
51
- const tx = this.tx;
52
- if (!tx) {
53
- throwNoPendingTransaction();
54
- }
55
- this.tx = undefined;
56
- await tx.commit();
57
- });
58
- }
59
- async rollbackTransaction() {
60
- return this.serialize(async () => {
61
- const tx = this.tx;
62
- this.tx = undefined;
63
- await tx?.rollback();
64
- });
39
+ async internalCommit() {
40
+ const tx = this.tx;
41
+ this.tx = undefined;
42
+ await tx?.commit();
43
+ }
44
+ async internalRollback() {
45
+ const tx = this.tx;
46
+ this.tx = undefined;
47
+ await tx?.rollback();
65
48
  }
66
49
  async internalRelease() {
67
50
  await super.internalRelease();
@@ -1,5 +1,5 @@
1
1
  import { AbstractSqlQuerierPool } from '../querier/index.js';
2
- import type { HranaClient, HranaQuerier, HranaQuerierConnectionOptions } from './hranaQuerier.js';
2
+ import { type HranaClient, HranaQuerier } from './hranaQuerier.js';
3
3
  import type { SqliteDialect } from './sqliteDialect.js';
4
4
  /**
5
5
  * Pool for SQLite databases reached over the Hrana wire protocol (`@libsql/client`,
@@ -10,12 +10,11 @@ import type { SqliteDialect } from './sqliteDialect.js';
10
10
  * constructor, so building a pool never throws when the optional driver peer is absent, which is what
11
11
  * lets a Workers bundle construct one at module scope.
12
12
  */
13
- export declare abstract class AbstractHranaQuerierPool<Q extends HranaQuerier, D extends SqliteDialect> extends AbstractSqlQuerierPool<Q, D> {
13
+ export declare abstract class AbstractHranaQuerierPool<D extends SqliteDialect> extends AbstractSqlQuerierPool<HranaQuerier, D> {
14
14
  private client?;
15
15
  /** False when the caller injected their own client, in which case they own its lifecycle. */
16
16
  protected readonly ownsClient: boolean;
17
17
  protected abstract openClient(): Promise<HranaClient>;
18
- protected abstract buildQuerier(client: HranaClient, connection?: HranaQuerierConnectionOptions): Q;
19
- getQuerier(): Promise<Q>;
18
+ getQuerier(): Promise<HranaQuerier>;
20
19
  end(): Promise<void>;
21
20
  }
@@ -1,4 +1,5 @@
1
1
  import { AbstractSqlQuerierPool } from '../querier/index.js';
2
+ import { HranaQuerier } from './hranaQuerier.js';
2
3
  /**
3
4
  * Pool for SQLite databases reached over the Hrana wire protocol (`@libsql/client`,
4
5
  * `@tursodatabase/serverless/compat`).
@@ -14,7 +15,7 @@ export class AbstractHranaQuerierPool extends AbstractSqlQuerierPool {
14
15
  ownsClient = true;
15
16
  async getQuerier() {
16
17
  this.client ??= await this.openClient();
17
- return this.buildQuerier(this.client);
18
+ return new HranaQuerier(this.client, this.dialect, this.extra);
18
19
  }
19
20
  async end() {
20
21
  if (this.ownsClient) {
@@ -15,13 +15,13 @@ export type LocalSqlitePoolOptions = {
15
15
  * Pool for a SQLite database opened in this process, whichever driver provides it. SQLite gives one
16
16
  * connection per file, so the shared-handle lifecycle is {@link AbstractSharedHandleQuerierPool}'s.
17
17
  *
18
- * Subclasses supply only {@link createDb}: loading the extensions on the way up is the same for
19
- * `better-sqlite3`, `bun:sqlite` and `node:sqlite`, and was written out once per pool before.
18
+ * Subclasses supply only {@link createDb}: configuring the connection on the way up - the pragmas,
19
+ * then the extensions - is the same for `better-sqlite3`, `bun:sqlite` and `node:sqlite`.
20
20
  */
21
21
  export declare abstract class AbstractLocalSqliteQuerierPool<O extends LocalSqlitePoolOptions> extends AbstractSharedHandleQuerierPool<SqliteDatabase, SqliteQuerier, SqliteDialect> {
22
22
  readonly opts?: O | undefined;
23
23
  constructor(opts?: O | undefined, extra?: ExtraOptions);
24
- /** Opens the driver's database. Extensions are loaded by the caller, not here. */
24
+ /** Opens the driver's database, and nothing more: the caller configures it. */
25
25
  protected abstract createDb(): Promise<SqliteDatabase>;
26
26
  protected openDb(): Promise<SqliteDatabase>;
27
27
  protected buildQuerier(db: SqliteDatabase): SqliteQuerier;
@@ -1,13 +1,14 @@
1
1
  import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
2
2
  import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
3
3
  import { SqliteDialect } from './sqliteDialect.js';
4
+ import { applySqlitePragmas } from './sqlitePragmas.js';
4
5
  import { SqliteQuerier } from './sqliteQuerier.js';
5
6
  /**
6
7
  * Pool for a SQLite database opened in this process, whichever driver provides it. SQLite gives one
7
8
  * connection per file, so the shared-handle lifecycle is {@link AbstractSharedHandleQuerierPool}'s.
8
9
  *
9
- * Subclasses supply only {@link createDb}: loading the extensions on the way up is the same for
10
- * `better-sqlite3`, `bun:sqlite` and `node:sqlite`, and was written out once per pool before.
10
+ * Subclasses supply only {@link createDb}: configuring the connection on the way up - the pragmas,
11
+ * then the extensions - is the same for `better-sqlite3`, `bun:sqlite` and `node:sqlite`.
11
12
  */
12
13
  export class AbstractLocalSqliteQuerierPool extends AbstractSharedHandleQuerierPool {
13
14
  opts;
@@ -17,6 +18,7 @@ export class AbstractLocalSqliteQuerierPool extends AbstractSharedHandleQuerierP
17
18
  }
18
19
  async openDb() {
19
20
  const db = await this.createDb();
21
+ await applySqlitePragmas(db);
20
22
  for (const extension of this.opts?.extensions ?? []) {
21
23
  db.loadExtension(extension);
22
24
  }
@@ -19,7 +19,6 @@ type NodeSqliteStatement = {
19
19
  */
20
20
  export type NodeSqliteDatabase = {
21
21
  prepare(sql: string): NodeSqliteStatement;
22
- exec(sql: string): void;
23
22
  loadExtension(path: string): void;
24
23
  close(): void;
25
24
  };
@@ -23,8 +23,6 @@ export class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool {
23
23
  // `node:sqlite` refuses `loadExtension` unless the database was opened with this on.
24
24
  ...(extensions?.length ? { allowExtension: true } : undefined),
25
25
  });
26
- nodeDb.exec('PRAGMA journal_mode = WAL');
27
- nodeDb.exec('PRAGMA foreign_keys = ON');
28
26
  return adaptNodeSqlite(nodeDb);
29
27
  }
30
28
  }
@@ -1,6 +1,7 @@
1
1
  import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
2
2
  import { JSON_ELEM_ALIAS_PREFIX, JSON_PULL_ALIAS } from '../dialect/aliases.js';
3
3
  import { jsonAssignCall, jsonElemExists, jsonPath, jsonRemoveCall, jsonSetTarget } from '../dialect/jsonSql.js';
4
+ import { textSearchFields } from '../util/dialect.util.js';
4
5
  export class SqliteDialect extends AbstractSqlDialect {
5
6
  /** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
6
7
  featureDefaults = {
@@ -86,7 +87,7 @@ export class SqliteDialect extends AbstractSqlDialect {
86
87
  * FTS5 virtual table (UQL does not create those; declare it outside your entities).
87
88
  */
88
89
  appendTextSearch(ctx, entity, meta, search) {
89
- const columns = search.$fields.map((key) => this.escapeId(this.resolveColumnName(key, meta.fields[key])));
90
+ const columns = textSearchFields(meta, search).map((key) => this.escapeId(this.resolveColumnName(key, meta.fields[key])));
90
91
  ctx.append(`${this.escapedTableName(meta)} MATCH {${columns.join(' ')}} : `);
91
92
  ctx.addValue(search.$value);
92
93
  }
@@ -0,0 +1,12 @@
1
+ import type { SqlitePreparedStatement } from './abstractSqliteQuerier.js';
2
+ /**
3
+ * How every local SQLite connection opens. WAL, so a reader does not wait on the writer, and
4
+ * `foreign_keys`, which SQLite ships off per connection for backward compatibility: without it the
5
+ * constraints uql's own DDL declares are decorative - a declared `onDelete: 'CASCADE'` silently does
6
+ * nothing and a dangling reference is accepted.
7
+ */
8
+ export declare const SQLITE_PRAGMAS: readonly ['journal_mode = WAL', 'foreign_keys = ON'];
9
+ /** Runs {@link SQLITE_PRAGMAS} through a driver's own statements, whether it answers now or later. */
10
+ export declare function applySqlitePragmas(db: {
11
+ prepare(sql: string): SqlitePreparedStatement | Promise<SqlitePreparedStatement>;
12
+ }): Promise<void>;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * How every local SQLite connection opens. WAL, so a reader does not wait on the writer, and
3
+ * `foreign_keys`, which SQLite ships off per connection for backward compatibility: without it the
4
+ * constraints uql's own DDL declares are decorative - a declared `onDelete: 'CASCADE'` silently does
5
+ * nothing and a dangling reference is accepted.
6
+ */
7
+ export const SQLITE_PRAGMAS = ['journal_mode = WAL', 'foreign_keys = ON'];
8
+ /** Runs {@link SQLITE_PRAGMAS} through a driver's own statements, whether it answers now or later. */
9
+ export async function applySqlitePragmas(db) {
10
+ for (const pragma of SQLITE_PRAGMAS) {
11
+ const stmt = await db.prepare(`PRAGMA ${pragma}`);
12
+ // `journal_mode` answers with a row and `foreign_keys` with none; `reader` picks the call for each.
13
+ await (stmt.reader ? stmt.all() : stmt.run());
14
+ }
15
+ }
@@ -11,10 +11,5 @@ export type Sqlite3PoolOptions = Options & LocalSqlitePoolOptions;
11
11
  export declare class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool<Sqlite3PoolOptions> {
12
12
  readonly filename: string | Buffer;
13
13
  constructor(filename?: string | Buffer, opts?: Sqlite3PoolOptions, extra?: ExtraOptions);
14
- /**
15
- * SQLite ships with foreign keys unenforced, per connection, for backward compatibility. UQL emits the
16
- * constraints in its DDL, so leaving them off means a declared `onDelete: 'CASCADE'` silently does
17
- * nothing and a dangling reference is accepted. Enabled here on every driver, as TypeORM also does.
18
- */
19
14
  protected createDb(): Promise<SqliteDatabase>;
20
15
  }
@@ -10,11 +10,6 @@ export class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool {
10
10
  super(opts, extra);
11
11
  this.filename = filename;
12
12
  }
13
- /**
14
- * SQLite ships with foreign keys unenforced, per connection, for backward compatibility. UQL emits the
15
- * constraints in its DDL, so leaving them off means a declared `onDelete: 'CASCADE'` silently does
16
- * nothing and a dangling reference is accepted. Enabled here on every driver, as TypeORM also does.
17
- */
18
13
  async createDb() {
19
14
  // `bun:sqlite` rejects option keys it does not know, and rejects an options object carrying no
20
15
  // open flags, so `extensions` is stripped out and what remains of it collapses back to nothing.
@@ -23,15 +18,9 @@ export class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool {
23
18
  if (typeof Bun !== 'undefined') {
24
19
  const { Database: BunDatabase } = await import('bun:sqlite');
25
20
  const { adaptBunSqlite } = await import('./bunSqliteAdapter.bun.js');
26
- const bunDb = new BunDatabase(this.filename, opts);
27
- bunDb.run('PRAGMA journal_mode = WAL');
28
- bunDb.run('PRAGMA foreign_keys = ON');
29
- return adaptBunSqlite(bunDb);
21
+ return adaptBunSqlite(new BunDatabase(this.filename, opts));
30
22
  }
31
23
  const { default: BetterSqlite3 } = await import('better-sqlite3');
32
- const db = new BetterSqlite3(this.filename, opts);
33
- db.pragma('journal_mode = WAL');
34
- db.pragma('foreign_keys = ON');
35
- return db;
24
+ return new BetterSqlite3(this.filename, opts);
36
25
  }
37
26
  }
@@ -1,3 +1,2 @@
1
1
  export * from './tursoDialect.js';
2
- export * from './tursoQuerier.js';
3
2
  export * from './tursoQuerierPool.js';
@@ -1,3 +1,2 @@
1
1
  export * from './tursoDialect.js';
2
- export * from './tursoQuerier.js';
3
2
  export * from './tursoQuerierPool.js';
@@ -7,7 +7,6 @@ import type { ExtraOptions } from '../type/index.js';
7
7
  */
8
8
  export type TursoDatabase = {
9
9
  prepare(sql: string): Promise<SqlitePreparedStatement>;
10
- pragma(source: string, options?: unknown): Promise<unknown[]>;
11
10
  close(): Promise<void>;
12
11
  };
13
12
  /**