uql-orm 0.54.0 → 0.55.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 (131) hide show
  1. package/README.md +2 -2
  2. package/dist/browser/uql-browser.min.js.map +1 -1
  3. package/dist/bunSql/bunSql.util.d.ts +3 -14
  4. package/dist/bunSql/bunSql.util.js +33 -56
  5. package/dist/bunSql/bunSqlQuerier.d.ts +3 -6
  6. package/dist/bunSql/bunSqlQuerier.js +7 -13
  7. package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -5
  8. package/dist/bunSql/bunSqlQuerierPool.js +25 -10
  9. package/dist/cockroachdb/crdbQuerierPool.d.ts +1 -3
  10. package/dist/cockroachdb/crdbQuerierPool.js +0 -4
  11. package/dist/cockroachdb/index.d.ts +0 -1
  12. package/dist/cockroachdb/index.js +0 -1
  13. package/dist/d1/d1SqliteDialect.d.ts +5 -0
  14. package/dist/d1/d1SqliteDialect.js +7 -0
  15. package/dist/dialect/abstractSqlDialect.d.ts +9 -9
  16. package/dist/dialect/abstractSqlDialect.js +38 -51
  17. package/dist/dialect/hydrateColumn.js +2 -2
  18. package/dist/dialect/mergeSqlDialect.d.ts +2 -2
  19. package/dist/dialect/mergeSqlDialect.js +0 -4
  20. package/dist/dialect/pgLikeSqlDialect.d.ts +5 -0
  21. package/dist/dialect/pgLikeSqlDialect.js +12 -2
  22. package/dist/entity/index.d.ts +1 -1
  23. package/dist/entity/index.js +1 -1
  24. package/dist/entity/metadata/definition.d.ts +3 -1
  25. package/dist/entity/metadata/definition.js +8 -0
  26. package/dist/libsql/index.d.ts +0 -1
  27. package/dist/libsql/index.js +0 -1
  28. package/dist/libsql/libsqlQuerierPool.d.ts +3 -5
  29. package/dist/libsql/libsqlQuerierPool.js +2 -5
  30. package/dist/maria/mariadbQuerier.d.ts +0 -3
  31. package/dist/maria/mariadbQuerier.js +6 -7
  32. package/dist/maria/mariadbQuerierPool.js +4 -7
  33. package/dist/migrate/ddl/index.d.ts +3 -3
  34. package/dist/migrate/ddl/index.js +3 -3
  35. package/dist/mongo/index.d.ts +0 -1
  36. package/dist/mongo/index.js +0 -1
  37. package/dist/mongo/mongoDialect.d.ts +0 -1
  38. package/dist/mongo/mongoDialect.js +3 -14
  39. package/dist/mongo/mongodbQuerier.js +6 -5
  40. package/dist/mongo/mongodbQuerierPool.d.ts +2 -2
  41. package/dist/mongo/mongodbQuerierPool.js +2 -2
  42. package/dist/mssql/mssqlDialect.d.ts +3 -5
  43. package/dist/mssql/mssqlDialect.js +9 -8
  44. package/dist/mssql/mssqlQuerier.d.ts +13 -6
  45. package/dist/mssql/mssqlQuerier.js +30 -75
  46. package/dist/mssql/mssqlQuerierPool.js +2 -0
  47. package/dist/mssql/mssqlWireTypes.d.ts +3 -4
  48. package/dist/mssql/mssqlWireTypes.js +5 -8
  49. package/dist/mysql/index.d.ts +0 -1
  50. package/dist/mysql/index.js +0 -1
  51. package/dist/mysql/mysql2Querier.d.ts +1 -4
  52. package/dist/mysql/mysql2Querier.js +0 -3
  53. package/dist/mysql/mysql2QuerierPool.d.ts +2 -2
  54. package/dist/mysql/mysql2QuerierPool.js +5 -3
  55. package/dist/neon/index.d.ts +0 -2
  56. package/dist/neon/index.js +0 -2
  57. package/dist/neon/neonQuerierPool.d.ts +2 -4
  58. package/dist/neon/neonQuerierPool.js +2 -6
  59. package/dist/pglite/index.d.ts +0 -1
  60. package/dist/pglite/index.js +0 -1
  61. package/dist/pglite/pgliteQuerier.d.ts +3 -3
  62. package/dist/pglite/pgliteQuerier.js +1 -1
  63. package/dist/pglite/pgliteQuerierPool.d.ts +7 -2
  64. package/dist/pglite/pgliteQuerierPool.js +16 -6
  65. package/dist/postgres/abstractPgQuerierPool.d.ts +8 -12
  66. package/dist/postgres/abstractPgQuerierPool.js +8 -6
  67. package/dist/postgres/index.d.ts +0 -1
  68. package/dist/postgres/index.js +0 -1
  69. package/dist/postgres/pgNumericTypes.d.ts +5 -5
  70. package/dist/postgres/pgNumericTypes.js +11 -7
  71. package/dist/postgres/pgQuerier.d.ts +22 -4
  72. package/dist/postgres/pgQuerier.js +29 -2
  73. package/dist/postgres/pgQuerierPool.d.ts +2 -4
  74. package/dist/postgres/pgQuerierPool.js +2 -6
  75. package/dist/postgres/postgresDialect.d.ts +5 -5
  76. package/dist/postgres/postgresDialect.js +5 -5
  77. package/dist/postgres/postgresWireDriverCapabilities.d.ts +2 -2
  78. package/dist/postgres/postgresWireDriverCapabilities.js +2 -2
  79. package/dist/querier/abstractPoolQuerier.d.ts +1 -1
  80. package/dist/querier/abstractPoolQuerier.js +1 -1
  81. package/dist/querier/abstractSqlQuerier.d.ts +11 -4
  82. package/dist/querier/abstractSqlQuerier.js +28 -16
  83. package/dist/sqlite/hranaQuerier.d.ts +6 -8
  84. package/dist/sqlite/hranaQuerier.js +13 -30
  85. package/dist/sqlite/hranaQuerierPool.d.ts +3 -4
  86. package/dist/sqlite/hranaQuerierPool.js +2 -1
  87. package/dist/sqlite/localSqliteQuerierPool.d.ts +3 -3
  88. package/dist/sqlite/localSqliteQuerierPool.js +4 -2
  89. package/dist/sqlite/nodeSqliteAdapter.d.ts +0 -1
  90. package/dist/sqlite/nodeSqliteQuerierPool.js +0 -2
  91. package/dist/sqlite/sqlitePragmas.d.ts +12 -0
  92. package/dist/sqlite/sqlitePragmas.js +15 -0
  93. package/dist/sqlite/sqliteQuerierPool.d.ts +0 -5
  94. package/dist/sqlite/sqliteQuerierPool.js +2 -13
  95. package/dist/turso/index.d.ts +0 -1
  96. package/dist/turso/index.js +0 -1
  97. package/dist/turso/tursoLocalQuerier.d.ts +0 -1
  98. package/dist/turso/tursoLocalQuerierPool.js +2 -2
  99. package/dist/turso/tursoQuerierPool.d.ts +1 -3
  100. package/dist/turso/tursoQuerierPool.js +0 -4
  101. package/dist/type/dialect.d.ts +1 -1
  102. package/dist/util/dialect.util.d.ts +1 -1
  103. package/dist/util/dialect.util.js +1 -1
  104. package/dist/util/logger.d.ts +7 -8
  105. package/dist/util/logger.js +18 -11
  106. package/dist/util/raw.js +3 -3
  107. package/dist/util/sqlLiteral.js +3 -8
  108. package/dist/util/string.util.js +2 -6
  109. package/dist/util/wideNumber.d.ts +14 -0
  110. package/dist/util/wideNumber.js +24 -0
  111. package/package.json +1 -1
  112. package/dist/cockroachdb/crdbQuerier.d.ts +0 -8
  113. package/dist/cockroachdb/crdbQuerier.js +0 -6
  114. package/dist/libsql/libsqlQuerier.d.ts +0 -10
  115. package/dist/libsql/libsqlQuerier.js +0 -10
  116. package/dist/mongo/mongodbNativeDialect.d.ts +0 -9
  117. package/dist/mongo/mongodbNativeDialect.js +0 -9
  118. package/dist/mysql/mysql2Dialect.d.ts +0 -9
  119. package/dist/mysql/mysql2Dialect.js +0 -9
  120. package/dist/neon/neonDialect.d.ts +0 -10
  121. package/dist/neon/neonDialect.js +0 -10
  122. package/dist/neon/neonQuerier.d.ts +0 -5
  123. package/dist/neon/neonQuerier.js +0 -3
  124. package/dist/pglite/pgliteDialect.d.ts +0 -14
  125. package/dist/pglite/pgliteDialect.js +0 -14
  126. package/dist/postgres/abstractPgQuerier.d.ts +0 -24
  127. package/dist/postgres/abstractPgQuerier.js +0 -32
  128. package/dist/postgres/pgDialect.d.ts +0 -10
  129. package/dist/postgres/pgDialect.js +0 -10
  130. package/dist/turso/tursoQuerier.d.ts +0 -10
  131. package/dist/turso/tursoQuerier.js +0 -10
@@ -1,4 +1,4 @@
1
- import { getMeta, soleIdOf } from '../entity/index.js';
1
+ import { fieldOf, getMeta, soleIdOf } from '../entity/index.js';
2
2
  import { parseQueryLock, QueryRaw, RAW_ALIAS, RAW_VALUE, VECTOR_QUERY_KEYS, } from '../type/index.js';
3
3
  import { isInlinedExpression } from '../util/field.util.js';
4
4
  import { asSelectMap, assertNonNegativeInteger, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getSoftDeleteValue, hasKeys, columnFamily, isJsonUpdateOp, isOperatorMap, isOperatorObject, isOperatorOnlyObject, isVectorSearch, normalizeScalarFieldSelection, parentJoins, targetKeyColumns, parseGroupMap, parseRelationSize, parseSortByCount, populatesRelations, queryChildrenOf, raw, someValue, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
@@ -10,6 +10,13 @@ import { SqlQueryContext } from './queryContext.js';
10
10
  import { NO_JOINS, resolveQueryJoins, resolveSortableJoin, } from './queryJoins.js';
11
11
  import { resolveVectorCast } from './vectorCast.js';
12
12
  import { VectorSqlDialect } from './vectorSqlDialect.js';
13
+ /** An `$in`/`$nin` operand, which the types require to be an array but `/http` hands over untyped. */
14
+ function inOperands(op, value) {
15
+ if (!Array.isArray(value)) {
16
+ throw TypeError(`${op} expects an array, got ${value === null ? 'null' : typeof value}`);
17
+ }
18
+ return value;
19
+ }
13
20
  export class AbstractSqlDialect extends VectorSqlDialect {
14
21
  /**
15
22
  * Whether {@link autoIncrementSuffix} states `PRIMARY KEY` itself, so the table must not state it again.
@@ -99,6 +106,10 @@ export class AbstractSqlDialect extends VectorSqlDialect {
99
106
  build(fragmentCtx);
100
107
  return fragmentCtx.sql;
101
108
  }
109
+ /** A `raw()` operand, rendered in place: bound, it would reach the driver as the object itself. */
110
+ rawFragment(ctx, value) {
111
+ return this.buildFragment(ctx, (fragmentCtx) => this.getRawValue(fragmentCtx, { value }));
112
+ }
102
113
  /**
103
114
  * Each operand rendered into its own fragment, keeping only those that emitted SQL.
104
115
  *
@@ -117,20 +128,13 @@ export class AbstractSqlDialect extends VectorSqlDialect {
117
128
  return this.placeholder(values.length);
118
129
  }
119
130
  /**
120
- * Normalizes a parameter value for the database driver.
121
- * Handles bigint, boolean, and serializes plain objects/arrays to JSON strings.
122
- * Date values are preserved so SQL drivers can apply native date/time binding.
123
- * Postgres overrides to pass objects through to its native JSONB driver.
131
+ * A parameter value as this engine's driver takes it: a boolean as the integer an engine with no
132
+ * boolean type stores, and everything else as it is - a `bigint` included, which every driver here
133
+ * binds exactly, where a number would round it past 2^53. A driver that refuses one (D1) overrides.
124
134
  */
125
135
  normalizeValue(value) {
126
- if (value == null || value instanceof Date || value instanceof Uint8Array || value instanceof QueryRaw) {
127
- return value;
128
- }
129
- if (typeof value === 'bigint') {
130
- return Number(value);
131
- }
132
- if (typeof value === 'boolean') {
133
- return this.booleanLiteral === 'native' ? value : value ? 1 : 0;
136
+ if (typeof value === 'boolean' && this.booleanLiteral !== 'native') {
137
+ return value ? 1 : 0;
134
138
  }
135
139
  return value;
136
140
  }
@@ -198,9 +202,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
198
202
  });
199
203
  }
200
204
  else {
201
- const field = meta.fields[key];
202
- if (!field)
203
- return;
205
+ const field = fieldOf(meta, key);
204
206
  if (isInlinedExpression(field)) {
205
207
  // Qualified even when nothing else in this statement is: the expression is spliced in, and
206
208
  // one that opens a correlated subquery has the inner table's columns in scope, so a bare
@@ -427,7 +429,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
427
429
  if (entry instanceof QueryRaw) {
428
430
  this.getRawValue(fragmentCtx, { value: entry });
429
431
  }
430
- else if (entry) {
432
+ else {
431
433
  this.renderWhere(fragmentCtx, entity, entry, { prefix: opts.prefix, operand: childOperand, clause: false });
432
434
  }
433
435
  });
@@ -589,13 +591,8 @@ export class AbstractSqlDialect extends VectorSqlDialect {
589
591
  case '$regex':
590
592
  return this.regexCondition(operand, this.addValue(ctx.values, val));
591
593
  case '$in':
592
- case '$nin': {
593
- if (!Array.isArray(val)) {
594
- // Not covered by the types: `/http` casts client JSON straight to `Query`, so this arrives untyped.
595
- throw TypeError(`${op} expects an array, got ${val === null ? 'null' : typeof val}`);
596
- }
597
- return operand + this.formatIn(ctx, val, op === '$nin');
598
- }
594
+ case '$nin':
595
+ return operand + this.formatIn(ctx, inOperands(op, val), op === '$nin');
599
596
  case '$between': {
600
597
  const [min, max] = val;
601
598
  return `${operand} BETWEEN ${this.addValue(ctx.values, min)} AND ${this.addValue(ctx.values, max)}`;
@@ -663,7 +660,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
663
660
  }
664
661
  }
665
662
  jsonInNin(ctx, jsonField, comparand, op, value, asJson) {
666
- const values = Array.isArray(value) ? value : [];
663
+ const values = inOperands(op, value);
667
664
  const negate = op === '$nin';
668
665
  if (!asJson) {
669
666
  return `${comparand(values)}${this.formatIn(ctx, values, negate)}`;
@@ -739,7 +736,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
739
736
  */
740
737
  jsonScalarParam(ctx, value) {
741
738
  if (value instanceof QueryRaw) {
742
- return this.addValue(ctx.values, value);
739
+ return this.rawFragment(ctx, value);
743
740
  }
744
741
  ctx.pushValue(JSON.stringify(value));
745
742
  // The placeholder for the value just pushed, so a named or numbered one is spelled correctly.
@@ -917,16 +914,14 @@ export class AbstractSqlDialect extends VectorSqlDialect {
917
914
  estimatedCount(_ctx, _entity) {
918
915
  throw new TypeError(`${this.dialectName} does not support estimatedCount`);
919
916
  }
920
- /** `$group` aggregate operator → SQL function name. An allowlist, not a formatter: the op key
921
- * comes from query data, so anything outside this map must be rejected rather than passed
922
- * through as a raw SQL function name. */
923
- static AGGREGATE_FN_MAP = new Map([
924
- ['$count', 'COUNT'],
925
- ['$sum', 'SUM'],
926
- ['$avg', 'AVG'],
927
- ['$min', 'MIN'],
928
- ['$max', 'MAX'],
929
- ]);
917
+ /** `$group` aggregate operator to SQL function name, over ops `resolveAggregateOp` has already allowlisted. */
918
+ static AGGREGATE_FN = {
919
+ $count: 'COUNT',
920
+ $sum: 'SUM',
921
+ $avg: 'AVG',
922
+ $min: 'MIN',
923
+ $max: 'MAX',
924
+ };
930
925
  aggregate(ctx, entity, q, opts = {}) {
931
926
  const meta = getMeta(entity);
932
927
  const tableName = this.escapedTableName(meta);
@@ -946,10 +941,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
946
941
  selectParts.push(columnName !== entry.alias ? `${escaped} ${this.escapeId(entry.alias)}` : escaped);
947
942
  }
948
943
  else {
949
- const sqlFn = AbstractSqlDialect.AGGREGATE_FN_MAP.get(entry.op);
950
- if (!sqlFn) {
951
- throw TypeError(`unsupported aggregate operator: ${entry.op}`);
952
- }
944
+ const sqlFn = AbstractSqlDialect.AGGREGATE_FN[entry.op];
953
945
  const sqlArg = entry.fieldRef === '*' ? '*' : this.escapeId(this.columnOf(meta, entry.fieldRef));
954
946
  const expr = `${sqlFn}(${entry.distinct ? 'DISTINCT ' : ''}${sqlArg})`;
955
947
  emittedColumns[entry.alias] = expr;
@@ -1238,14 +1230,12 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1238
1230
  // Soft-delete (stamp only live rows) unless `hardDelete` is requested or the entity has no
1239
1231
  // soft-delete field (e.g. a cascade onto a non-soft-deletable child).
1240
1232
  if (!opts.hardDelete && meta.softDelete) {
1241
- const field = meta.fields[meta.softDelete];
1242
- if (field) {
1243
- const columnName = this.resolveColumnName(meta.softDelete, field);
1244
- ctx.append(`UPDATE ${tableName} SET ${this.escapeId(columnName)} = `);
1245
- this.formatPersistableValue(ctx, field, getSoftDeleteValue(field));
1246
- this.search(ctx, entity, q, opts);
1247
- return;
1248
- }
1233
+ const field = fieldOf(meta, meta.softDelete);
1234
+ const columnName = this.resolveColumnName(meta.softDelete, field);
1235
+ ctx.append(`UPDATE ${tableName} SET ${this.escapeId(columnName)} = `);
1236
+ this.formatPersistableValue(ctx, field, getSoftDeleteValue(field));
1237
+ this.search(ctx, entity, q, opts);
1238
+ return;
1249
1239
  }
1250
1240
  // Hard delete removes matching rows regardless of soft-delete state (keeps other filters, e.g. tenant).
1251
1241
  // Only rewrite the filters when there is a soft-delete filter to disable.
@@ -1764,9 +1754,6 @@ export class AbstractSqlDialect extends VectorSqlDialect {
1764
1754
  const phs = values.map((v) => this.addValue(ctx.values, v)).join(', ');
1765
1755
  return ` ${negate ? 'NOT IN' : 'IN'} (${phs})`;
1766
1756
  }
1767
- numericCast(expr) {
1768
- return expr;
1769
- }
1770
1757
  toString() {
1771
1758
  return this.dialectName;
1772
1759
  }
@@ -1,3 +1,4 @@
1
+ import { decodeWideNumber } from '../util/wideNumber.js';
1
2
  import { parseVectorLiteral } from './vectorCast.js';
2
3
  /**
3
4
  * Decode one non-null cell. Kept beside {@link HydrateKind} rather than inlined into the querier's
@@ -31,8 +32,7 @@ export function decodeColumn(value, kind) {
31
32
  return value;
32
33
  }
33
34
  if (kind === 'number') {
34
- const decoded = Number(text);
35
- return Number.isNaN(decoded) ? value : decoded;
35
+ return Number.isNaN(Number(text)) ? value : decodeWideNumber(text);
36
36
  }
37
37
  if (kind === 'json') {
38
38
  try {
@@ -38,8 +38,8 @@ export declare abstract class MergeSqlDialect extends AbstractSqlDialect {
38
38
  * holds it across both. Empty on Oracle, which does not have the hint and does not need it.
39
39
  */
40
40
  protected readonly mergeTargetHint: string;
41
- /** How the merge reports the row it wrote. Oracle has no such clause on a `MERGE` and omits it. */
42
- protected mergeReturning(expression: string): string;
41
+ /** How the merge reports the row it wrote: SQL Server's `OUTPUT`. Oracle has no such clause on a `MERGE`. */
42
+ protected abstract mergeReturning(expression: string): string;
43
43
  /** `MERGE` must be terminated on SQL Server; nothing else here cares. */
44
44
  protected readonly statementTerminator: string;
45
45
  }
@@ -80,10 +80,6 @@ export class MergeSqlDialect extends AbstractSqlDialect {
80
80
  * holds it across both. Empty on Oracle, which does not have the hint and does not need it.
81
81
  */
82
82
  mergeTargetHint = '';
83
- /** How the merge reports the row it wrote. Oracle has no such clause on a `MERGE` and omits it. */
84
- mergeReturning(expression) {
85
- return `RETURNING ${expression}`;
86
- }
87
83
  /** `MERGE` must be terminated on SQL Server; nothing else here cares. */
88
84
  statementTerminator = '';
89
85
  }
@@ -93,6 +93,11 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
93
93
  * ORDINALITY` keeps the surviving elements in their original order.
94
94
  */
95
95
  protected jsonPullKey(ctx: QueryContext, expr: string, escapedCol: string, key: string, value: unknown): string;
96
+ /**
97
+ * The plain values merge as one bound object; a `raw()` one is an SQL expression rather than JSON,
98
+ * so it merges through `JSONB_BUILD_OBJECT` and is evaluated in place - stringified with the rest,
99
+ * it would land as `{}`.
100
+ */
96
101
  protected jsonSet(ctx: QueryContext, expr: string, set: Record<string, unknown>, field?: FieldOptions): string;
97
102
  /** The only fragment that references `expr` twice - safe here because placeholders are numbered. */
98
103
  protected jsonPush(ctx: QueryContext, expr: string, push: Record<string, unknown>): string;
@@ -213,8 +213,18 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
213
213
  const kept = `SELECT JSONB_AGG(${JSON_PULL_ALIAS}.val ORDER BY ${JSON_PULL_ALIAS}.ord) FROM JSONB_ARRAY_ELEMENTS(${escapedCol}->'${escapedKey}') WITH ORDINALITY AS ${JSON_PULL_ALIAS}(val, ord) WHERE ${JSON_PULL_ALIAS}.val <> ${this.jsonVal(ctx, value)}`;
214
214
  return `JSONB_SET(${expr}, '{${escapedKey}}', COALESCE((${kept}), '[]'::jsonb), false)`;
215
215
  }
216
+ /**
217
+ * The plain values merge as one bound object; a `raw()` one is an SQL expression rather than JSON,
218
+ * so it merges through `JSONB_BUILD_OBJECT` and is evaluated in place - stringified with the rest,
219
+ * it would land as `{}`.
220
+ */
216
221
  jsonSet(ctx, expr, set, field) {
217
- return `${jsonSetTarget(expr, field, `'{}'::jsonb`)} || ${this.jsonVal(ctx, set)}`;
222
+ const entries = Object.entries(set);
223
+ const merged = this.jsonVal(ctx, Object.fromEntries(entries.filter(([, value]) => !(value instanceof QueryRaw))));
224
+ const raws = entries.flatMap(([key, value]) => value instanceof QueryRaw
225
+ ? [` || JSONB_BUILD_OBJECT('${escapeSingleQuotes(key)}', ${this.rawFragment(ctx, value)})`]
226
+ : []);
227
+ return `${jsonSetTarget(expr, field, `'{}'::jsonb`)} || ${merged}${raws.join('')}`;
218
228
  }
219
229
  /** The only fragment that references `expr` twice - safe here because placeholders are numbered. */
220
230
  jsonPush(ctx, expr, push) {
@@ -236,7 +246,7 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
236
246
  */
237
247
  jsonVal(ctx, value, type = 'jsonb') {
238
248
  if (value instanceof QueryRaw)
239
- return this.addValue(ctx.values, value);
249
+ return this.rawFragment(ctx, value);
240
250
  if (value == null)
241
251
  return `${this.addValue(ctx.values, null)}::${type}`;
242
252
  const json = JSON.stringify(value);
@@ -1,3 +1,3 @@
1
1
  export * from './decorator/entity.js';
2
2
  export * from './decorator/members.js';
3
- export { defineEntity, defineField, defineFilter, defineHook, defineId, defineIndex, defineRelation, getEntities, getMeta, removeEntity, assertSoleId, idOf, namesKey, soleIdOf, } from './metadata/definition.js';
3
+ export { defineEntity, defineField, defineFilter, defineHook, defineId, defineIndex, defineRelation, getEntities, getMeta, removeEntity, assertSoleId, fieldOf, idOf, namesKey, soleIdOf, } from './metadata/definition.js';
@@ -1,3 +1,3 @@
1
1
  export * from './decorator/entity.js';
2
2
  export * from './decorator/members.js';
3
- export { defineEntity, defineField, defineFilter, defineHook, defineId, defineIndex, defineRelation, getEntities, getMeta, removeEntity, assertSoleId, idOf, namesKey, soleIdOf, } from './metadata/definition.js';
3
+ export { defineEntity, defineField, defineFilter, defineHook, defineId, defineIndex, defineRelation, getEntities, getMeta, removeEntity, assertSoleId, fieldOf, idOf, namesKey, soleIdOf, } from './metadata/definition.js';
@@ -1,4 +1,4 @@
1
- import type { EntityData, EntityIndexInput, EntityMembers, EntityMeta, EntityOptions, FieldKey, FieldOptions, FilterOptions, HookEvent, IdKey, RelationOptions, Type, WrittenId } from '../../type/index.js';
1
+ import type { EntityData, EntityIndexInput, EntityMembers, EntityMeta, EntityOptions, FieldKey, FieldMeta, FieldOptions, FilterOptions, HookEvent, IdKey, RelationOptions, Type, WrittenId } from '../../type/index.js';
2
2
  export declare function defineField<E>(entity: Type<E>, key: string, opts?: FieldOptions): EntityMeta<E>;
3
3
  export declare function defineId<E>(entity: Type<E>, key: string, opts: FieldOptions): EntityMeta<E>;
4
4
  export declare function defineRelation<E>(entity: Type<E>, key: string, opts: RelationOptions): EntityMeta<E>;
@@ -30,6 +30,8 @@ export declare function defineEntity<E>(entity: Type<E>, opts?: EntityOptions<E>
30
30
  export declare function assertSoleId<E>(meta: EntityMeta<E>, what: string): void;
31
31
  /** The entity's one primary key, for a path that cannot express a composite. See {@link assertSoleId}. */
32
32
  export declare function soleIdOf<E>(meta: EntityMeta<E>, what: string): IdKey<E>;
33
+ /** The field `key` names, for a caller that took `key` from the metadata itself. */
34
+ export declare function fieldOf<E>(meta: EntityMeta<E>, key: string): FieldMeta;
33
35
  /**
34
36
  * Whether the caller named every column of the row's primary key, so {@link idOf} can name the row.
35
37
  *
@@ -219,6 +219,14 @@ export function soleIdOf(meta, what) {
219
219
  assertSoleId(meta, what);
220
220
  return meta.ids[0];
221
221
  }
222
+ /** The field `key` names, for a caller that took `key` from the metadata itself. */
223
+ export function fieldOf(meta, key) {
224
+ const field = meta.fields[key];
225
+ if (!field) {
226
+ throw new TypeError(`'${meta.entity.name}' has no field '${key}'`);
227
+ }
228
+ return field;
229
+ }
222
230
  /**
223
231
  * Whether the caller named every column of the row's primary key, so {@link idOf} can name the row.
224
232
  *
@@ -1,3 +1,2 @@
1
1
  export * from './libsqlDialect.js';
2
- export * from './libsqlQuerier.js';
3
2
  export * from './libsqlQuerierPool.js';
@@ -1,3 +1,2 @@
1
1
  export * from './libsqlDialect.js';
2
- export * from './libsqlQuerier.js';
3
2
  export * from './libsqlQuerierPool.js';
@@ -1,21 +1,19 @@
1
1
  import type { Config } from '@libsql/client';
2
- import type { HranaClient, HranaQuerierConnectionOptions } from '../sqlite/hranaQuerier.js';
2
+ import { type HranaClient, HranaQuerier } from '../sqlite/hranaQuerier.js';
3
3
  import { AbstractHranaQuerierPool } from '../sqlite/hranaQuerierPool.js';
4
4
  import type { ExtraOptions } from '../type/index.js';
5
5
  import { LibsqlDialect } from './libsqlDialect.js';
6
- import { LibsqlQuerier } from './libsqlQuerier.js';
7
6
  /** Embedded replica: local `file:` DB + `syncUrl` remote - DDL should run on the remote (sqld). */
8
7
  export declare function libsqlUseRemoteForMigrations(config: Pick<Config, 'url' | 'syncUrl'>): boolean;
9
- export declare class LibsqlQuerierPool extends AbstractHranaQuerierPool<LibsqlQuerier, LibsqlDialect> {
8
+ export declare class LibsqlQuerierPool extends AbstractHranaQuerierPool<LibsqlDialect> {
10
9
  private readonly conf;
11
10
  constructor(conf: Config, extra?: ExtraOptions);
12
11
  protected openClient(): Promise<HranaClient>;
13
- protected buildQuerier(client: HranaClient, connection?: HranaQuerierConnectionOptions): LibsqlQuerier;
14
12
  /**
15
13
  * For embedded replicas (`file:` + `syncUrl`), returns a querier connected to `syncUrl` so migrations hit sqld.
16
14
  * Otherwise same as `getQuerier`. The migrator calls this for `up`/`down`, `syncForce`, and `autoSync` DDL.
17
15
  */
18
- getMigrationQuerier(): Promise<LibsqlQuerier>;
16
+ getMigrationQuerier(): Promise<HranaQuerier>;
19
17
  /** Imported on use, so `uql-orm/libsql` loads without the optional `@libsql/client` peer installed. */
20
18
  private createClient;
21
19
  }
@@ -1,7 +1,7 @@
1
1
  import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
2
+ import { HranaQuerier } from '../sqlite/hranaQuerier.js';
2
3
  import { AbstractHranaQuerierPool } from '../sqlite/hranaQuerierPool.js';
3
4
  import { LibsqlDialect } from './libsqlDialect.js';
4
- import { LibsqlQuerier } from './libsqlQuerier.js';
5
5
  /** Embedded replica: local `file:` DB + `syncUrl` remote - DDL should run on the remote (sqld). */
6
6
  export function libsqlUseRemoteForMigrations(config) {
7
7
  return Boolean(config.syncUrl && config.url.startsWith('file:'));
@@ -20,9 +20,6 @@ export class LibsqlQuerierPool extends AbstractHranaQuerierPool {
20
20
  openClient() {
21
21
  return this.createClient(this.conf);
22
22
  }
23
- buildQuerier(client, connection) {
24
- return new LibsqlQuerier(client, this.dialect, this.extra, connection);
25
- }
26
23
  /**
27
24
  * For embedded replicas (`file:` + `syncUrl`), returns a querier connected to `syncUrl` so migrations hit sqld.
28
25
  * Otherwise same as `getQuerier`. The migrator calls this for `up`/`down`, `syncForce`, and `autoSync` DDL.
@@ -32,7 +29,7 @@ export class LibsqlQuerierPool extends AbstractHranaQuerierPool {
32
29
  return this.getQuerier();
33
30
  }
34
31
  const remote = await this.createClient(remoteMigrationClientConfig(this.conf));
35
- return this.buildQuerier(remote, { closeClientOnRelease: true });
32
+ return new HranaQuerier(remote, this.dialect, this.extra, { closeClientOnRelease: true });
36
33
  }
37
34
  /** Imported on use, so `uql-orm/libsql` loads without the optional `@libsql/client` peer installed. */
38
35
  async createClient(conf) {
@@ -1,9 +1,6 @@
1
1
  import type { PoolConnection } from 'mariadb';
2
2
  import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
3
- import type { ExtraOptions } from '../type/index.js';
4
- import type { MariaDialect } from './mariaDialect.js';
5
3
  export declare class MariadbQuerier extends AbstractPoolQuerier<PoolConnection> {
6
- constructor(connect: () => Promise<PoolConnection>, dialect: MariaDialect, extra?: ExtraOptions);
7
4
  internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
8
5
  internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
9
6
  internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, unknown>;
@@ -1,23 +1,22 @@
1
1
  import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
2
+ import { decodeBigInts } from '../util/wideNumber.js';
2
3
  export class MariadbQuerier extends AbstractPoolQuerier {
3
- constructor(connect, dialect, extra) {
4
- super(dialect, connect, extra);
5
- }
6
4
  async internalAll(query, values) {
7
- const res = await this.getConn().query(query, values);
8
- return res;
5
+ const rows = await this.getConn().query(query, values);
6
+ return Array.from(rows, decodeBigInts);
9
7
  }
10
8
  async internalRun(query, values) {
11
9
  const res = await this.getConn().query(query, values);
12
10
  // MariaDB may not set `affectedRows` when RETURNING is used; fall back to row count.
13
11
  const changes = res.affectedRows ?? res.length ?? 0;
14
- return this.buildUpdateResult({ rows: res.length ? res : [], changes, upsertStatus: res.affectedRows });
12
+ const rows = res.length ? Array.from(res, decodeBigInts) : [];
13
+ return this.buildUpdateResult({ rows, changes, upsertStatus: res.affectedRows });
15
14
  }
16
15
  async *internalStream(query, values) {
17
16
  const stream = this.getConn().queryStream(query, values);
18
17
  try {
19
18
  for await (const row of stream) {
20
- yield row;
19
+ yield decodeBigInts(row);
21
20
  }
22
21
  }
23
22
  finally {
@@ -8,19 +8,16 @@ export class MariadbQuerierPool extends AbstractSqlQuerierPool {
8
8
  pool;
9
9
  constructor(opts, extra) {
10
10
  super(new MariaDialect(dialectOptionsFrom(extra)), extra);
11
- // `mariadb` defaults to handing BIGINT back as a BigInt, and uql maps `type: Number` to BIGINT
12
- // (see `schema/canonicalType.ts`), so every auto-increment id reached a field declared `number`
13
- // as `9n` without this. Same trade as the pg pools: exact to 2^53, and `...opts` wins for a
14
- // caller who needs more. This belongs to the pool, not to the suites - it lived in
15
- // `mariadbQuerier.test.ts`, which meant the tests passed on behaviour the library never shipped.
16
- this.pool = createPool({ bigIntAsNumber: true, ...opts });
11
+ // BIGINT stays the driver's `bigint`, which `MariadbQuerier` decodes by the rule every driver here
12
+ // shares (`decodeWideNumber`) - not `bigIntAsNumber`, which rounds past 2^53 without a word.
13
+ this.pool = createPool(opts);
17
14
  // `mariadb`'s own `createPool` already attaches a silent no-op 'error'
18
15
  // listener (so a dropped connection can't crash the process), but its
19
16
  // `Pool` type only declares `on` for 'acquire' | 'connection' | 'enqueue'
20
17
  // | 'release' - 'error' genuinely fires at runtime (see `lib/pool.js`)
21
18
  // but isn't in the declaration, hence the cast. Re-attaching our own
22
19
  // listener here just makes the error visible instead of a silent no-op.
23
- attachPoolErrorHandler(this.pool, 'Idle MariaDB pool connection encountered an error');
20
+ attachPoolErrorHandler(this.pool, 'Idle MariaDB pool connection encountered an error', extra?.logger);
24
21
  }
25
22
  async getQuerier() {
26
23
  return new MariadbQuerier(() => this.pool.getConnection(), this.dialect, this.extra);
@@ -7,9 +7,9 @@ export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl
7
7
  export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
8
8
  export { TableDdl } from './tableDdl.js';
9
9
  /**
10
- * The index DDL a dialect gets, most specific first. `instanceof` rather than `dialectName` so a
11
- * dialect subclassed by a user keeps its family's DDL, which is what overriding gave it while this
12
- * lived on the dialect itself. Anything else gets the portable form, which is SQLite's.
10
+ * The index DDL a dialect gets, most specific first. `instanceof` rather than the `dialectName`
11
+ * {@link tableDdlFor} reads, because each family's index DDL is typed to its dialect and the narrowing
12
+ * is what hands it one. Anything else gets the portable form, which is SQLite's.
13
13
  */
14
14
  export declare function indexDdlFor(dialect: AbstractSqlDialect): IndexDdl;
15
15
  /**
@@ -14,9 +14,9 @@ export { MariaIndexDdl, MySqlIndexDdl, MysqlLikeIndexDdl } from './mysqlIndexDdl
14
14
  export { CockroachIndexDdl, PgIndexDdl } from './pgIndexDdl.js';
15
15
  export { TableDdl } from './tableDdl.js';
16
16
  /**
17
- * The index DDL a dialect gets, most specific first. `instanceof` rather than `dialectName` so a
18
- * dialect subclassed by a user keeps its family's DDL, which is what overriding gave it while this
19
- * lived on the dialect itself. Anything else gets the portable form, which is SQLite's.
17
+ * The index DDL a dialect gets, most specific first. `instanceof` rather than the `dialectName`
18
+ * {@link tableDdlFor} reads, because each family's index DDL is typed to its dialect and the narrowing
19
+ * is what hands it one. Anything else gets the portable form, which is SQLite's.
20
20
  */
21
21
  export function indexDdlFor(dialect) {
22
22
  if (dialect instanceof CockroachDialect) {
@@ -1,5 +1,4 @@
1
1
  export { MongoSchemaGenerator } from '../migrate/generator/mongoSchemaGenerator.js';
2
2
  export * from './mongoDialect.js';
3
- export * from './mongodbNativeDialect.js';
4
3
  export * from './mongodbQuerier.js';
5
4
  export * from './mongodbQuerierPool.js';
@@ -1,5 +1,4 @@
1
1
  export { MongoSchemaGenerator } from '../migrate/generator/mongoSchemaGenerator.js';
2
2
  export * from './mongoDialect.js';
3
- export * from './mongodbNativeDialect.js';
4
3
  export * from './mongodbQuerier.js';
5
4
  export * from './mongodbQuerierPool.js';
@@ -20,7 +20,6 @@ export declare class MongoDialect extends AbstractDialect {
20
20
  private static readonly ID_KEY;
21
21
  /** Atlas rejects a `$vectorSearch` asking for more candidates than this. */
22
22
  private static readonly MAX_NUM_CANDIDATES;
23
- private static readonly AGGREGATE_OP_MAP;
24
23
  /**
25
24
  * MongoDB stores the primary key as `_id`; everything else resolves as usual. Projections, sorts,
26
25
  * `$group` refs and `$where` keys all map through here, so no read path can address a property name
@@ -39,14 +39,6 @@ export class MongoDialect extends AbstractDialect {
39
39
  static ID_KEY = '_id';
40
40
  /** Atlas rejects a `$vectorSearch` asking for more candidates than this. */
41
41
  static MAX_NUM_CANDIDATES = 10_000;
42
- // Direct field aggregates → MongoDB accumulator. `$count` is handled separately (COUNT(*) vs
43
- // COUNT(field) differ), so it is not listed here.
44
- static AGGREGATE_OP_MAP = new Map([
45
- ['$sum', '$sum'],
46
- ['$avg', '$avg'],
47
- ['$min', '$min'],
48
- ['$max', '$max'],
49
- ]);
50
42
  /**
51
43
  * MongoDB stores the primary key as `_id`; everything else resolves as usual. Projections, sorts,
52
44
  * `$group` refs and `$where` keys all map through here, so no read path can address a property name
@@ -511,7 +503,7 @@ export class MongoDialect extends AbstractDialect {
511
503
  */
512
504
  aliasSort(sort) {
513
505
  const normalized = {};
514
- for (const [alias, dir] of Object.entries(sort ?? {})) {
506
+ for (const [alias, dir] of Object.entries(sort)) {
515
507
  normalized[alias] = sortDirection(dir);
516
508
  }
517
509
  return normalized;
@@ -999,11 +991,8 @@ export class MongoDialect extends AbstractDialect {
999
991
  entry.fieldRef === '*' ? { $sum: 1 } : { $sum: { $cond: [{ $ne: [ref, null] }, 1, 0] } };
1000
992
  }
1001
993
  else {
1002
- const mongoOp = MongoDialect.AGGREGATE_OP_MAP.get(entry.op);
1003
- if (!mongoOp) {
1004
- throw TypeError(`unsupported aggregate operator: ${entry.op}`);
1005
- }
1006
- groupAccumulators[entry.alias] = { [mongoOp]: ref };
994
+ // `$sum`, `$avg`, `$min` and `$max` are MongoDB accumulators of the same name.
995
+ groupAccumulators[entry.alias] = { [entry.op]: ref };
1007
996
  }
1008
997
  }
1009
998
  return { groupId, groupAccumulators, distinctReducers };
@@ -1,6 +1,6 @@
1
1
  import { COUNT_ALIAS } from '../dialect/aliases.js';
2
2
  import { hasRequiredJoin } from '../dialect/queryJoins.js';
3
- import { getMeta, idOf, namesKey, soleIdOf } from '../entity/index.js';
3
+ import { fieldOf, getMeta, idOf, namesKey, soleIdOf } from '../entity/index.js';
4
4
  import { AbstractQuerier, enrichError } from '../querier/index.js';
5
5
  import { clone, getKeys, getRelationRequestSummary, getSoftDeleteValue, hasKeys, idOnlyQuery, isPagedQuery, populatesRelations, queryChildrenOf, throwNoPendingTransaction, throwPendingTransaction, withoutSoftDeleteFilter, } from '../util/index.js';
6
6
  /**
@@ -372,9 +372,9 @@ export class MongodbQuerier extends AbstractQuerier {
372
372
  return this.timed('internalDeleteMany', undefined, async () => {
373
373
  const meta = getMeta(entity);
374
374
  // Soft-delete (stamp) unless `hardDelete` is requested or the entity has no soft-delete field.
375
- const field = !opts.hardDelete && meta.softDelete ? meta.fields[meta.softDelete] : undefined;
375
+ const softDelete = opts.hardDelete ? undefined : meta.softDelete;
376
376
  // Hard delete targets matching rows regardless of soft-delete state (keeps other filters).
377
- const findOpts = field ? opts : { ...opts, filters: withoutSoftDeleteFilter(opts.filters) };
377
+ const findOpts = softDelete ? opts : { ...opts, filters: withoutSoftDeleteFilter(opts.filters) };
378
378
  // Delete has always resolved its ids first (it stamps or removes them by `_id`), so a relation
379
379
  // condition needs nothing extra here - and passing the whole query is what makes its page apply.
380
380
  const ids = await this.settleIds(entity, qm, findOpts);
@@ -382,10 +382,11 @@ export class MongodbQuerier extends AbstractQuerier {
382
382
  return 0;
383
383
  }
384
384
  let changes;
385
- if (field) {
385
+ if (softDelete) {
386
+ const field = fieldOf(meta, softDelete);
386
387
  // Stamp the mapped column: reads filter on it, so a `@Field({ name })` mismatch here would
387
388
  // report a successful delete and leave the row visible.
388
- const softDeleteColumn = this.dialect.resolveColumnName(meta.softDelete, field);
389
+ const softDeleteColumn = this.dialect.resolveColumnName(softDelete, field);
389
390
  const updateResult = await this.execute((session) => this.collection(entity).updateMany({ _id: { $in: this.dialect.toWireId(ids) } }, { $set: { [softDeleteColumn]: getSoftDeleteValue(field) } }, {
390
391
  session,
391
392
  }));
@@ -1,9 +1,9 @@
1
1
  import { type MongoClientOptions } from 'mongodb';
2
2
  import { AbstractQuerierPool } from '../querier/index.js';
3
3
  import type { ExtraOptions } from '../type/index.js';
4
- import { MongodbNativeDialect } from './mongodbNativeDialect.js';
4
+ import { MongoDialect } from './mongoDialect.js';
5
5
  import { MongodbQuerier } from './mongodbQuerier.js';
6
- export declare class MongodbQuerierPool extends AbstractQuerierPool<MongodbQuerier, MongodbNativeDialect> {
6
+ export declare class MongodbQuerierPool extends AbstractQuerierPool<MongodbQuerier, MongoDialect> {
7
7
  private readonly client;
8
8
  constructor(uri: string, opts?: MongoClientOptions, extra?: ExtraOptions);
9
9
  getQuerier(): Promise<MongodbQuerier>;
@@ -1,12 +1,12 @@
1
1
  import { MongoClient } from 'mongodb';
2
2
  import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
3
3
  import { AbstractQuerierPool } from '../querier/index.js';
4
- import { MongodbNativeDialect } from './mongodbNativeDialect.js';
4
+ import { MongoDialect } from './mongoDialect.js';
5
5
  import { MongodbQuerier } from './mongodbQuerier.js';
6
6
  export class MongodbQuerierPool extends AbstractQuerierPool {
7
7
  client;
8
8
  constructor(uri, opts, extra) {
9
- super(new MongodbNativeDialect(dialectOptionsFrom(extra)), extra);
9
+ super(new MongoDialect(dialectOptionsFrom(extra)), extra);
10
10
  this.client = new MongoClient(uri, opts);
11
11
  }
12
12
  async getQuerier() {
@@ -18,12 +18,10 @@ export declare class MsSqlDialect extends MergeSqlDialect {
18
18
  readonly commitTransactionCommand = "COMMIT TRANSACTION";
19
19
  readonly rollbackTransactionCommand = "ROLLBACK TRANSACTION";
20
20
  /**
21
- * The level rides the `BEGIN` rather than preceding it as its own statement: a `SET TRANSACTION
22
- * ISOLATION LEVEL` sent on its own would go to whichever pooled connection served it, not the one
23
- * the transaction opens on, and would then stick to that connection for unrelated later queries.
24
- * `MsSqlQuerier` reads the level back off this command and hands it to the driver.
21
+ * T-SQL has no inline form, so the level is set before the `BEGIN`. What a driver that sends these
22
+ * as statements would run; `MsSqlQuerier` opens its transactions through the driver instead.
25
23
  */
26
- readonly isolationLevelStrategy = "inline";
24
+ readonly isolationLevelStrategy = "set-before";
27
25
  readonly dropIndexSyntax = "on-table";
28
26
  readonly booleanLiteral = "integer";
29
27
  /** [The hard server limit](https://github.com/yiisoft/yii2/issues/10371), not a driver preference. */