uql-orm 0.81.0 → 0.83.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 (113) hide show
  1. package/README.md +3 -3
  2. package/dist/browser/querier/httpQuerier.d.ts +2 -2
  3. package/dist/browser/querier/httpQuerier.js +2 -1
  4. package/dist/browser/type/clientQuerier.d.ts +2 -2
  5. package/dist/browser/uql-browser.min.js +2 -2
  6. package/dist/browser/uql-browser.min.js.map +9 -8
  7. package/dist/bunSql/bunSql.util.js +2 -1
  8. package/dist/cockroachdb/crdbQuerierPool.js +2 -2
  9. package/dist/dialect/abstractSqlDialect.d.ts +16 -6
  10. package/dist/dialect/abstractSqlDialect.js +110 -41
  11. package/dist/dialect/hydrateColumn.js +2 -12
  12. package/dist/dialect/mysqlLikeSqlDialect.d.ts +2 -0
  13. package/dist/dialect/mysqlLikeSqlDialect.js +5 -0
  14. package/dist/dialect/operators.d.ts +7 -1
  15. package/dist/dialect/operators.js +13 -1
  16. package/dist/dialect/pgLikeSqlDialect.d.ts +1 -0
  17. package/dist/dialect/pgLikeSqlDialect.js +13 -4
  18. package/dist/entity/metadata/definition.d.ts +1 -2
  19. package/dist/entity/metadata/definition.js +37 -39
  20. package/dist/http/handler.js +5 -4
  21. package/dist/http/query.d.ts +1 -1
  22. package/dist/http/query.js +2 -2
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +1 -0
  25. package/dist/maria/mariadbQuerierPool.js +4 -2
  26. package/dist/migrate/acquireQuerierForMigrations.js +2 -1
  27. package/dist/migrate/assertCliConfig.js +7 -6
  28. package/dist/migrate/bin.js +0 -0
  29. package/dist/migrate/builder/expressions.d.ts +2 -0
  30. package/dist/migrate/builder/expressions.js +20 -10
  31. package/dist/migrate/builder/tableBuilder.js +1 -1
  32. package/dist/migrate/cli-config.js +5 -4
  33. package/dist/migrate/ddl/indexDdl.js +4 -3
  34. package/dist/migrate/ddl/mysqlIndexDdl.js +5 -4
  35. package/dist/migrate/ddl/pgIndexDdl.js +2 -1
  36. package/dist/migrate/ddl/sqliteIndexDdl.js +2 -1
  37. package/dist/migrate/ddl/tableDdl.js +2 -1
  38. package/dist/migrate/generator/mongoCommand.js +2 -1
  39. package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
  40. package/dist/migrate/generator/mongoSchemaGenerator.js +7 -6
  41. package/dist/migrate/indexPredicate.js +2 -1
  42. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +2 -1
  43. package/dist/migrate/introspection/mongoIntrospector.d.ts +1 -1
  44. package/dist/migrate/introspection/mongoIntrospector.js +3 -2
  45. package/dist/migrate/introspection/mssqlIntrospector.js +13 -1
  46. package/dist/migrate/introspection/mysqlIntrospector.d.ts +2 -0
  47. package/dist/migrate/introspection/mysqlIntrospector.js +6 -2
  48. package/dist/migrate/introspection/postgresIntrospector.js +1 -1
  49. package/dist/migrate/migrationTarget.js +2 -1
  50. package/dist/migrate/migrator.js +2 -1
  51. package/dist/migrate/schemaGenerator.js +5 -4
  52. package/dist/migrate/storage/databaseStorage.js +1 -1
  53. package/dist/migrate/triggerSql.d.ts +1 -1
  54. package/dist/migrate/triggerSql.js +77 -61
  55. package/dist/mongo/mongoDialect.d.ts +1 -3
  56. package/dist/mongo/mongoDialect.js +9 -14
  57. package/dist/mongo/mongodbQuerier.js +7 -10
  58. package/dist/mssql/mssqlQuerier.d.ts +2 -0
  59. package/dist/mssql/mssqlQuerier.js +8 -5
  60. package/dist/mysql/mysql2QuerierPool.d.ts +1 -0
  61. package/dist/mysql/mysql2QuerierPool.js +20 -2
  62. package/dist/neon/neonQuerierPool.js +2 -2
  63. package/dist/pglite/pgliteQuerierPool.js +10 -4
  64. package/dist/postgres/pgQuerierPool.js +2 -2
  65. package/dist/postgres/{pgNumericTypes.d.ts → pgWireTypes.d.ts} +4 -3
  66. package/dist/postgres/{pgNumericTypes.js → pgWireTypes.js} +7 -3
  67. package/dist/querier/abstractQuerier.d.ts +9 -4
  68. package/dist/querier/abstractQuerier.js +26 -19
  69. package/dist/querier/abstractQuerierPool.d.ts +3 -3
  70. package/dist/querier/abstractSqlQuerier.d.ts +2 -2
  71. package/dist/querier/abstractSqlQuerier.js +1 -1
  72. package/dist/querier/abstractSqlQuerierPool.d.ts +2 -2
  73. package/dist/querier/queryError.d.ts +2 -2
  74. package/dist/schema/canonicalType.d.ts +3 -0
  75. package/dist/schema/canonicalType.js +31 -9
  76. package/dist/schema/schemaASTBuilder.js +2 -1
  77. package/dist/schema/schemaASTDiffer.js +4 -2
  78. package/dist/sqlite/sqliteDialect.d.ts +1 -3
  79. package/dist/sqlite/sqliteDialect.js +3 -6
  80. package/dist/type/dialect.d.ts +23 -1
  81. package/dist/type/entity.d.ts +16 -12
  82. package/dist/type/logger.d.ts +2 -2
  83. package/dist/type/querier.d.ts +3 -3
  84. package/dist/type/query.d.ts +3 -13
  85. package/dist/type/queryAggregate.d.ts +4 -10
  86. package/dist/type/queryRaw.d.ts +17 -3
  87. package/dist/type/queryRaw.js +2 -1
  88. package/dist/type/queryWhere.d.ts +7 -7
  89. package/dist/type/universalQuerier.d.ts +3 -3
  90. package/dist/type/vector.d.ts +2 -1
  91. package/dist/type/vector.js +2 -1
  92. package/dist/util/date.d.ts +11 -0
  93. package/dist/util/date.js +19 -0
  94. package/dist/util/dialect.util.d.ts +13 -5
  95. package/dist/util/dialect.util.js +28 -20
  96. package/dist/util/field.util.d.ts +4 -4
  97. package/dist/util/field.util.js +10 -2
  98. package/dist/util/fieldOption.util.d.ts +5 -3
  99. package/dist/util/fieldOption.util.js +6 -5
  100. package/dist/util/hook.util.d.ts +1 -1
  101. package/dist/util/hook.util.js +8 -1
  102. package/dist/util/index.d.ts +1 -0
  103. package/dist/util/index.js +1 -0
  104. package/dist/util/logger.d.ts +3 -3
  105. package/dist/util/object.util.js +3 -2
  106. package/dist/util/raw.d.ts +6 -7
  107. package/dist/util/raw.js +10 -12
  108. package/dist/util/sqlLiteral.d.ts +8 -1
  109. package/dist/util/sqlLiteral.js +14 -9
  110. package/dist/util/triggerWrite.d.ts +15 -0
  111. package/dist/util/triggerWrite.js +20 -0
  112. package/package.json +1 -1
  113. package/skills/uql-orm/SKILL.md +4 -4
@@ -120,7 +120,8 @@ const MYSQL_SCALAR_MAP = {
120
120
  boolean: 'TINYINT(1)',
121
121
  date: 'DATE',
122
122
  time: 'TIME',
123
- timestamp: 'DATETIME',
123
+ // The milliseconds a `Date` holds, which a bare `DATETIME` rounds away.
124
+ timestamp: 'DATETIME(3)',
124
125
  json: 'JSON',
125
126
  uuid: 'CHAR(36)',
126
127
  blob: 'BLOB',
@@ -193,25 +194,28 @@ const ENGINE_TYPES = {
193
194
  postgres: {
194
195
  scalars: { ...PG_SCALAR_MAP, vector: 'VECTOR', halfvec: 'HALFVEC', sparsevec: 'SPARSEVEC' },
195
196
  sizes: PG_SIZES,
197
+ timestampPrecision: 6,
196
198
  },
197
199
  // CockroachDB's VECTOR is native, no extension needed.
198
- cockroachdb: { scalars: withVectorType(PG_SCALAR_MAP, 'VECTOR'), sizes: PG_SIZES },
200
+ cockroachdb: { scalars: withVectorType(PG_SCALAR_MAP, 'VECTOR'), sizes: PG_SIZES, timestampPrecision: 6 },
199
201
  // MySQL does have a `VECTOR` type (26.7), but no distance function outside HeatWave and no vector
200
202
  // index, so JSON keeps the column queryable with the JSON operators and needs no conversion.
201
203
  mysql: {
202
204
  scalars: withVectorType(MYSQL_SCALAR_MAP, 'JSON'),
203
205
  sizes: MYSQL_SIZES,
204
206
  decimal: { precision: 10, scale: 2 },
207
+ timestampPrecision: 0,
205
208
  },
206
209
  mariadb: {
207
210
  scalars: withVectorType(MYSQL_SCALAR_MAP, 'VECTOR'),
208
211
  sizes: MYSQL_SIZES,
209
212
  decimal: { precision: 10, scale: 2 },
213
+ timestampPrecision: 0,
210
214
  },
211
215
  // SQLite uses affinity, so no size variants. `F32_BLOB` is libSQL's vector type; elsewhere just a name of BLOB affinity.
212
216
  sqlite: { scalars: withVectorType(SQLITE_SCALAR_MAP, 'F32_BLOB') },
213
217
  // 2025 and up; below that the server refuses the type rather than storing it as text.
214
- mssql: { scalars: withVectorType(MSSQL_SCALAR_MAP, 'VECTOR'), sizes: MSSQL_SIZES },
218
+ mssql: { scalars: withVectorType(MSSQL_SCALAR_MAP, 'VECTOR'), sizes: MSSQL_SIZES, timestampPrecision: 7 },
215
219
  mongodb: { scalars: withVectorType(MONGO_SCALAR_MAP, 'array') },
216
220
  };
217
221
  /**
@@ -241,8 +245,11 @@ export function sqlToCanonical(sqlType) {
241
245
  const normalized = sqlType.toLowerCase().trim();
242
246
  const unsigned = normalized.includes('unsigned');
243
247
  const withoutUnsigned = normalized.replace(/\s*unsigned\s*/i, ' ').trim();
244
- // Extract base type and parameters: "VARCHAR(255)" -> ["varchar", "255"]
245
- const match = withoutUnsigned.match(/^([a-z][a-z0-9_ ]*?)(?:\(([^)]+)\))?$/);
248
+ // Extract base type and parameters: "VARCHAR(255)" -> ["varchar", "255"], and Postgres's
249
+ // "timestamp(3) with time zone", whose parameter sits inside the name, as "timestamp with time zone(3)".
250
+ const match = withoutUnsigned
251
+ .replace(/^(\w+)\((\d+)\)\s+(.+)$/, '$1 $3($2)')
252
+ .match(/^([a-z][a-z0-9_ ]*?)(?:\(([^)]+)\))?$/);
246
253
  const base = match ? SQL_TO_CANONICAL[match[1]] : undefined;
247
254
  if (!match || !base) {
248
255
  return { category: 'string', raw: sqlType };
@@ -260,7 +267,7 @@ export function sqlToCanonical(sqlType) {
260
267
  size: measured && params[0] === 'max' ? 'small' : base.size,
261
268
  withTimezone: base.withTimezone,
262
269
  length: measured ? first : undefined,
263
- precision: decimal ? first : undefined,
270
+ precision: decimal || base.category === 'timestamp' ? first : undefined,
264
271
  scale: decimal ? second : undefined,
265
272
  unsigned: unsigned || undefined,
266
273
  };
@@ -317,6 +324,9 @@ export function canonicalToSql(type, dialect) {
317
324
  if (type.category === 'timestamp' && type.withTimezone && features.supportsTimestamptz) {
318
325
  sqlType = 'TIMESTAMPTZ';
319
326
  }
327
+ if (type.category === 'timestamp' && type.precision !== undefined && engine.timestampPrecision !== undefined) {
328
+ sqlType = `${sqlType.replace(/\(\d+\)$/, '')}(${type.precision})`;
329
+ }
320
330
  return type.unsigned && features.supportsUnsigned ? `${sqlType} UNSIGNED` : sqlType;
321
331
  }
322
332
  /** See {@link DialectFeatures.stringSizing} for what each mode means. */
@@ -343,12 +353,22 @@ function formatDecimalSqlType(type, fallback, baseType) {
343
353
  export function canonicalToTypeScript(type) {
344
354
  return CANONICAL_TO_TS[type.category];
345
355
  }
356
+ /** The fractional-second digits an engine's timestamp holds when its type states none; `undefined` where it counts none. */
357
+ export function defaultTimestampPrecision(dialectName) {
358
+ return ENGINE_TYPES[dialectName].timestampPrecision;
359
+ }
346
360
  /**
347
361
  * A type as `dialect` stores it, rendered and read back: several types share one storage type, and only
348
362
  * the engine settles an unstated bound. Migrations and drift both compare through it.
349
363
  */
350
364
  export function engineType(dialect) {
351
- return (type) => sqlToCanonical(canonicalToSql(type, dialect));
365
+ const timestampPrecision = defaultTimestampPrecision(dialect.dialectName);
366
+ return (type) => {
367
+ const stored = sqlToCanonical(canonicalToSql(type, dialect));
368
+ return stored.category === 'timestamp' && stored.precision === undefined
369
+ ? { ...stored, precision: timestampPrecision }
370
+ : stored;
371
+ };
352
372
  }
353
373
  /**
354
374
  * Convert UQL FieldOptions to a canonical type.
@@ -363,14 +383,16 @@ export function fieldOptionsToCanonical(options) {
363
383
  switch (columnFamily(options.type)) {
364
384
  case 'numeric':
365
385
  // BIGINT for every `Number` without a scale, key or not: a 32-bit column is a migration waiting
366
- // to happen, and the pools decode it back to a JS number at the wire (see `pgNumericTypes`).
386
+ // to happen, and the pools decode it back to a JS number at the wire (see `pgWireTypes`).
367
387
  return isIntegerColumn(options)
368
388
  ? { category: 'integer', size: 'big' }
369
389
  : { category: 'decimal', precision: options.precision, scale: options.scale };
370
390
  case 'boolean':
371
391
  return { category: 'boolean' };
392
+ // An instant: uql reads a zoneless timestamp as UTC, but the database's own clock and every other
393
+ // client read one in the session's zone.
372
394
  case 'date':
373
- return { category: 'timestamp' };
395
+ return { category: 'timestamp', withTimezone: true, precision: options.precision };
374
396
  // `String`, and anything a reflected type left unrecognised.
375
397
  default:
376
398
  return { category: 'string', length: options.length };
@@ -4,6 +4,7 @@ import { fulltextWeights, textWeightSteps } from '../util/dialect.util.js';
4
4
  import { isAutoIncrement, isInlinedExpression, isSoleIdField } from '../util/field.util.js';
5
5
  import { definedEntries } from '../util/object.util.js';
6
6
  import { derivedForeignKeyName, derivedIndexName, qualifyName } from '../util/sql.util.js';
7
+ import { UqlUsageError } from '../util/uqlError.js';
7
8
  import { resolveColumnCanonicalType } from './canonicalType.js';
8
9
  import { createTableNode, keyOfColumns, SchemaAST } from './schemaAST.js';
9
10
  import { DEFAULT_FOREIGN_KEY_ACTION } from './types.js';
@@ -37,7 +38,7 @@ export function buildSchemaAST(entities, options = {}) {
37
38
  }
38
39
  /** The `compileDdl` of a build given no dialect, which has nothing to render an entity's SQL with. */
39
40
  function refuseDdl() {
40
- throw new TypeError('building the schema of an entity that declares SQL (a check, a stored computed column, an index expression or predicate) needs a dialect to render it: pass `compileDdl`, as `buildEntityAST` does');
41
+ throw new UqlUsageError('building the schema of an entity that declares SQL (a check, a stored computed column, an index expression or predicate) needs a dialect to render it: pass `compileDdl`, as `buildEntityAST` does');
41
42
  }
42
43
  /** The entries a vector index of `meta` covers: the members it names, and any expression. */
43
44
  function vectorIndexedEntries(meta) {
@@ -124,7 +124,9 @@ function diffColumn(tableName, source, target, opts) {
124
124
  // inconsistently (`BIGINT(20)`, SQLite's `notnull: 0` rowid), so neither is compared.
125
125
  const generatedType = source.isAutoIncrement && target.isAutoIncrement;
126
126
  const impliedNotNull = source.isPrimaryKey && target.isPrimaryKey;
127
- const typeChanged = !generatedType && !areTypesEqual(opts.normalizeType(source.type), opts.normalizeType(target.type));
127
+ const expectedType = opts.normalizeType(source.type);
128
+ const actualType = opts.normalizeType(target.type);
129
+ const typeChanged = !generatedType && !areTypesEqual(expectedType, actualType);
128
130
  if (typeChanged) {
129
131
  differences.push(`type: ${formatType(source.type)} -> ${formatType(target.type)}`);
130
132
  }
@@ -158,7 +160,7 @@ function diffColumn(tableName, source, target, opts) {
158
160
  // Only the type this diff actually reports: a column altered for its default carries no data loss,
159
161
  // and a generated key's type - never compared above - reads as unsigned against an entity that
160
162
  // cannot say so.
161
- isBreaking: (typeChanged || signednessChanged) && isBreakingTypeChange(target.type, source.type),
163
+ isBreaking: (typeChanged || signednessChanged) && isBreakingTypeChange(actualType, expectedType),
162
164
  description: differences.join(', '),
163
165
  };
164
166
  }
@@ -1,4 +1,4 @@
1
- import { AbstractSqlDialect, type DerivedRelation, type HydrateKind, type RelationRows } from '../dialect/abstractSqlDialect.js';
1
+ import { AbstractSqlDialect, type DerivedRelation, type RelationRows } from '../dialect/abstractSqlDialect.js';
2
2
  import { type JsonAccessMode, type JsonSlot } from '../dialect/jsonSql.js';
3
3
  import { type EntityMeta, type FieldOptions, type Query, type QueryContext, type QueryPager, type QueryTextSearchOptions, type QueryWhere, type SqlDialectFeatures, type VectorDistance, type VectorMetric } from '../type/index.js';
4
4
  /** What SQLite and the engines derived from it have. */
@@ -60,8 +60,6 @@ export declare class SqliteDialect extends AbstractSqlDialect {
60
60
  vector: (expr: string) => string;
61
61
  };
62
62
  private bytesAsText;
63
- /** A date reads back as SQLite stored it, a number or text, which JSON carries unchanged. */
64
- protected hydrateKind(field: FieldOptions | undefined): HydrateKind | undefined;
65
63
  /**
66
64
  * FTS5 matches the table itself, so this works only where the table *is* an FTS5 virtual table (UQL does
67
65
  * not create those; declare it outside your entities). The whole query is bound, column filter and all.
@@ -3,9 +3,10 @@ import { BYTES_PREFIX } from '../dialect/hydrateColumn.js';
3
3
  import { chainedCall, groupsPerCall, jsonSetCall, jsonPath, jsonArraySlotArgs, jsonSlotArgs, jsonRemoveCall, jsonSetTarget, } from '../dialect/jsonSql.js';
4
4
  import { QueryRaw, } from '../type/index.js';
5
5
  import { indexDistance, isVectorIndexType } from '../type/vector.js';
6
+ import { utcTimestamp } from '../util/date.js';
6
7
  import { declaredIndexName } from '../util/ddlExpression.util.js';
7
8
  import { findVectorIndex, findVectorSort, textSearchFields, vectorCandidates } from '../util/dialect.util.js';
8
- import { columnFamily, isIntegerColumn } from '../util/field.util.js';
9
+ import { isIntegerColumn } from '../util/field.util.js';
9
10
  /**
10
11
  * An FTS5 query over `columns` for what a person typed: each word a quoted string, which FTS5 reads as a
11
12
  * term to match and never as syntax, and every one required, as the other engines read plain words.
@@ -128,7 +129,7 @@ export class SqliteDialect extends AbstractSqlDialect {
128
129
  }
129
130
  normalizeValue(value) {
130
131
  if (value instanceof Date)
131
- return value.getTime();
132
+ return utcTimestamp(value);
132
133
  return super.normalizeValue(value);
133
134
  }
134
135
  /**
@@ -169,10 +170,6 @@ export class SqliteDialect extends AbstractSqlDialect {
169
170
  bytesAsText(expr) {
170
171
  return `${this.escape(BYTES_PREFIX)} || hex(${expr})`;
171
172
  }
172
- /** A date reads back as SQLite stored it, a number or text, which JSON carries unchanged. */
173
- hydrateKind(field) {
174
- return columnFamily(field?.type) === 'date' ? undefined : super.hydrateKind(field);
175
- }
176
173
  /**
177
174
  * FTS5 matches the table itself, so this works only where the table *is* an FTS5 virtual table (UQL does
178
175
  * not create those; declare it outside your entities). The whole query is bound, column filter and all.
@@ -1,6 +1,7 @@
1
- import type { EntityMeta, UpdatePayload } from './entity.js';
1
+ import type { EntityMeta, EntityPredicate, UpdatePayload } from './entity.js';
2
2
  import type { Query, QueryConflictPaths, QueryPage, QueryRenderOptions, QuerySearch, RelationQuery } from './query.js';
3
3
  import type { QueryAggMap, QueryAggregate, QueryAggregateOp, QueryGroupMap } from './queryAggregate.js';
4
+ import type { QueryRawRenderOptions } from './queryRaw.js';
4
5
  import type { QueryWhere } from './queryWhere.js';
5
6
  import type { Type } from './utility.js';
6
7
  import type { QueryVectorQuery } from './vector.js';
@@ -231,6 +232,25 @@ export interface TriggerFeatures {
231
232
  */
232
233
  readonly before: boolean;
233
234
  }
235
+ /** Where DDL's SQL sits: the row a trigger's predicate reads, as its prefix, and a set-based body's rows. */
236
+ export type DdlRenderOptions = Pick<QueryComparisonOptions, 'escapedPrefix' | 'operand'> & Pick<QueryRawRenderOptions, 'rows'>;
237
+ /**
238
+ * A write a trigger's body runs, as `insertInto`, `updateTable` and `deleteFrom` state it. Held untyped
239
+ * here, past those helpers' typing, since the dialect renders it by the entity's metadata alone.
240
+ */
241
+ export type TriggerWrite = {
242
+ readonly entity: Type<object>;
243
+ } & ({
244
+ readonly kind: 'insert';
245
+ readonly row: Readonly<Record<string, unknown>>;
246
+ } | {
247
+ readonly kind: 'update';
248
+ readonly set: Readonly<Record<string, unknown>>;
249
+ readonly where: EntityPredicate<object>;
250
+ } | {
251
+ readonly kind: 'delete';
252
+ readonly where: EntityPredicate<object>;
253
+ });
234
254
  /**
235
255
  * What a SQL statement is rendered through, as a `raw` callback and a query context see it:
236
256
  * `AbstractSqlDialect` is the one implementation.
@@ -262,6 +282,8 @@ export interface SqlQueryDialect {
262
282
  update<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryRenderOptions): void;
263
283
  /** An upsert of one record or many by their conflict paths. */
264
284
  upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
285
+ /** A write in a trigger's body; `rows` is where a set-based engine's body reads its rows from. */
286
+ triggerWrite(ctx: QueryContext, write: TriggerWrite, rows?: string): void;
265
287
  /** A delete of the records the query matches, a soft delete where the entity has one. */
266
288
  delete<E>(ctx: QueryContext, entity: Type<E>, q: QuerySearch<E>, opts?: QueryRenderOptions): void;
267
289
  /**
@@ -1,7 +1,7 @@
1
1
  import type { EnumValues, ForeignKeyAction, IndexType } from '../schema/types.js';
2
2
  import type { SqlDialectName } from './dialect.js';
3
3
  import type { FilterOptions, RelationQuery } from './query.js';
4
- import type { ColumnRef, QueryRaw, RelationAggregate } from './queryRaw.js';
4
+ import type { ColumnRef, QueryRaw, RawFor, RelationAggregate } from './queryRaw.js';
5
5
  import type { QueryWhere } from './queryWhere.js';
6
6
  import type { AtLeastOne, Except, ExactlyOne, IsEqual, IsMany, Json, Scalar, Type, Unpacked, Writable } from './utility.js';
7
7
  import type { VectorDistance, VectorIndexOptions, VectorIndexType } from './vector.js';
@@ -37,6 +37,10 @@ export type WritableKey<E> = {
37
37
  }[FieldKey<E>];
38
38
  /** A whole-record write as a caller supplies one: {@link EntityData} without the fields it cannot write. */
39
39
  export type EntityWrite<E> = EntityData<E, WritableKey<E>>;
40
+ /** A row a trigger's body writes: each writable field its value, or SQL - a row's ref most often. */
41
+ export type WriteRow<E, F extends keyof E = WritableKey<E>> = {
42
+ readonly [K in F]?: E[K] | RawFor<QueryRaw, E[K]>;
43
+ };
40
44
  /**
41
45
  * The property an entity brands with {@link versionKey} as its optimistic lock, `never` where it
42
46
  * brands none. The brand is what carries `@Field({ version: true })` to the type level, since a
@@ -114,7 +118,7 @@ export type JsonArrayFields<T> = {
114
118
  */
115
119
  export type JsonUpdateOp<T = unknown> = {
116
120
  readonly $set?: Partial<T>;
117
- readonly $unset?: unknown extends T ? string[] : (keyof T & string)[];
121
+ readonly $unset?: unknown extends T ? readonly string[] : readonly (keyof T & string)[];
118
122
  readonly $push?: JsonArrayFields<T>;
119
123
  readonly $pull?: JsonArrayFields<T>;
120
124
  };
@@ -133,7 +137,7 @@ export type FieldUpdateOp<T extends number | bigint = number | bigint> = Exactly
133
137
  /** The {@link FieldUpdateOp} a field takes: `never` on one it has no operator for, which is any but a number. */
134
138
  type FieldUpdateOpFor<V> = [NonNullable<V>] extends [number] ? FieldUpdateOp<number> : [NonNullable<V>] extends [bigint] ? FieldUpdateOp<bigint> : never;
135
139
  /** What an update takes beyond the value: `null` to clear an optional member, `raw` SQL, and update operators. */
136
- type UpdateExtra<V, Raw> = (undefined extends V ? null : never) | Raw | JsonUpdateOpFor<V> | FieldUpdateOpFor<V>;
140
+ type UpdateExtra<V, Raw> = (undefined extends V ? null : never) | RawFor<Raw, V> | JsonUpdateOpFor<V> | FieldUpdateOpFor<V>;
137
141
  /**
138
142
  * What a whole-record write persists: the fields and relations with their declared optionality, a
139
143
  * related row's alike, and no methods. Two mapped types, since asking each key costs a conditional.
@@ -284,7 +288,7 @@ export type FieldOptions<V = TsTypeOf<FieldType>, E = unknown> = {
284
288
  readonly columnType?: ColumnType | QueryRaw;
285
289
  /** A string column's length. */
286
290
  readonly length?: number;
287
- /** A decimal column's precision. */
291
+ /** A decimal column's digits, or a timestamp's fractional-second digits. */
288
292
  readonly precision?: number;
289
293
  /** A decimal column's scale. */
290
294
  readonly scale?: number;
@@ -411,6 +415,10 @@ export type RelationReference<O, E> = {
411
415
  readonly foreign: F;
412
416
  };
413
417
  }[FieldKey<E>];
418
+ /** The fields of `E` whose value is a `T`, however optional: `FieldKeyOf<E, number | bigint>` are the ones a sum adds up. */
419
+ export type FieldKeyOf<E, T> = {
420
+ readonly [K in FieldKey<E>]-?: [NonNullable<E[K]>] extends [T] ? K : never;
421
+ }[FieldKey<E>];
414
422
  /** The fields of `O` that can hold any value `V` takes. */
415
423
  type FieldKeyHolding<O, V> = {
416
424
  readonly [K in keyof O]-?: [NonNullable<V>] extends [NonNullable<O[K]>] ? K : never;
@@ -446,16 +454,12 @@ export type KeyMap<E> = {
446
454
  };
447
455
  /** The fields of `E` as {@link ColumnRef}s, for SQL that names them: `refs(User)`, or a definition's callback. */
448
456
  export type RefMap<E, F extends keyof E = FieldKey<E>> = {
449
- readonly [K in F]-?: ColumnRef<K & string>;
457
+ readonly [K in F]-?: ColumnRef<K & string, E[K]>;
450
458
  };
451
459
  /** SQL a definition writes: `raw`, or a callback reading the fields off its refs, bivariant so the registry can hold it. */
452
460
  export type EntitySql<E> = QueryRaw | {
453
461
  sql(refs: RefMap<E>): QueryRaw;
454
462
  }['sql'];
455
- /** The fields of `C` a `sum` or an `avg` can add up. */
456
- type NumericKey<C> = {
457
- readonly [K in FieldKey<C>]-?: [NonNullable<C[K]>] extends [number | bigint] ? K : never;
458
- }[FieldKey<C>];
459
463
  /** One field of `C`, read off its refs: `(item) => item.amount`. */
460
464
  type PickRef<C, K extends keyof C> = (refs: RefMap<C>) => ColumnRef<K & string>;
461
465
  /**
@@ -466,11 +470,11 @@ type PickRef<C, K extends keyof C> = (refs: RefMap<C>) => ColumnRef<K & string>;
466
470
  export type RelationRef<C> = {
467
471
  count(q?: AggregateFilter<C>): RelationAggregate<number, true>;
468
472
  count(q: AggregatePage<C>): RelationAggregate<number, false>;
469
- sum<K extends NumericKey<C>>(pick: PickRef<C, K>, q?: AggregateFilter<C>): RelationAggregate<NonNullable<C[K]>, true>;
470
- sum<K extends NumericKey<C>>(pick: PickRef<C, K>, q: AggregateTopRows<C>): RelationAggregate<NonNullable<C[K]>, false>;
473
+ sum<K extends FieldKeyOf<C, number | bigint>>(pick: PickRef<C, K>, q?: AggregateFilter<C>): RelationAggregate<NonNullable<C[K]>, true>;
474
+ sum<K extends FieldKeyOf<C, number | bigint>>(pick: PickRef<C, K>, q: AggregateTopRows<C>): RelationAggregate<NonNullable<C[K]>, false>;
471
475
  min<K extends FieldKey<C>>(pick: PickRef<C, K>, q?: AggregateRows<C>): RelationAggregate<NonNullable<C[K]> | null, false>;
472
476
  max<K extends FieldKey<C>>(pick: PickRef<C, K>, q?: AggregateRows<C>): RelationAggregate<NonNullable<C[K]> | null, false>;
473
- avg<K extends NumericKey<C>>(pick: PickRef<C, K>, q?: AggregateRows<C>): RelationAggregate<number | null, false>;
477
+ avg<K extends FieldKeyOf<C, number | bigint>>(pick: PickRef<C, K>, q?: AggregateRows<C>): RelationAggregate<number | null, false>;
474
478
  };
475
479
  /**
476
480
  * What an aggregate reads of the related rows. The predicate is an {@link EntityPredicate} rather than a
@@ -12,9 +12,9 @@ export interface Logger {
12
12
  * @param values - The parameters passed to the query.
13
13
  * @param duration - The time it took to execute the query in milliseconds.
14
14
  */
15
- logQuery?(query: string, values?: unknown[], duration?: number): void;
15
+ logQuery?(query: string, values?: readonly unknown[], duration?: number): void;
16
16
  /** Logs a query that took longer than the threshold, its values `undefined` unless `logValues` is on. */
17
- logSlowQuery?(query: string, values?: unknown[], duration?: number): void;
17
+ logSlowQuery?(query: string, values?: readonly unknown[], duration?: number): void;
18
18
  /**
19
19
  * Logs a warning.
20
20
  */
@@ -106,11 +106,11 @@ export interface SqlQuerier extends Querier {
106
106
  /**
107
107
  * Execute a raw SQL query and return results
108
108
  */
109
- all<T>(query: string, values?: unknown[]): Promise<T[]>;
109
+ all<T>(query: string, values?: readonly unknown[]): Promise<T[]>;
110
110
  /**
111
111
  * Execute a raw SQL command (INSERT, UPDATE, DELETE, DDL)
112
112
  */
113
- run(query: string, values?: unknown[]): Promise<QueryUpdateResult>;
113
+ run(query: string, values?: readonly unknown[]): Promise<QueryUpdateResult>;
114
114
  }
115
115
  /**
116
116
  * Type guard to check if a querier supports raw SQL execution
@@ -136,7 +136,7 @@ export declare function isMongoQuerier(querier: Querier): querier is MongoQuerie
136
136
  export type ListenerContext<E extends object = object> = {
137
137
  readonly entity: Type<E>;
138
138
  readonly querier: Querier;
139
- readonly payloads: E[];
139
+ readonly payloads: readonly E[];
140
140
  readonly event: HookEvent;
141
141
  };
142
142
  /**
@@ -1,4 +1,4 @@
1
- import type { FieldKey, JsonFieldPaths, RelationKey, RelationTarget, ToManyRelationKey, WrittenId } from './entity.js';
1
+ import type { FieldKey, FieldKeyOf, JsonFieldPaths, RelationKey, RelationTarget, ToManyRelationKey, WrittenId } from './entity.js';
2
2
  import type { QueryLock } from './queryLock.js';
3
3
  import type { QueryRaw } from './queryRaw.js';
4
4
  import type { QueryWhere } from './queryWhere.js';
@@ -145,17 +145,13 @@ export type QuerySortValue = QuerySortDirection | QueryVectorSearch;
145
145
  export type QuerySortByCount = {
146
146
  $count: QuerySortDirection;
147
147
  };
148
- /** The fields of `E` a vector search can rank by. */
149
- type VectorFieldKey<E> = {
150
- [P in FieldKey<E>]: NonNullable<E[P]> extends readonly number[] ? P : never;
151
- }[FieldKey<E>];
152
148
  /**
153
149
  * Ordering parents by the row of a to-many nearest a vector, per vector field: its distance is the
154
150
  * smallest of theirs. Nothing to `$project`, since no one row of the parent's answers under it. Never
155
151
  * where the target has no vector, since an empty map would admit any value at all.
156
152
  */
157
- export type QuerySortByNearest<E> = [VectorFieldKey<E>] extends [never] ? never : {
158
- [P in VectorFieldKey<E>]?: QueryVectorQuery;
153
+ export type QuerySortByNearest<E> = [FieldKeyOf<E, readonly number[]>] extends [never] ? never : {
154
+ [P in FieldKeyOf<E, readonly number[]>]?: QueryVectorQuery;
159
155
  };
160
156
  /**
161
157
  * Ordering by relevance to the `$text` at the root of `$where`, in either direction as any key sorts. The
@@ -371,12 +367,6 @@ Pick<E, Exclude<ProjectedKeys<E, S, V, X, P>, PopulatedToMany<E, P>> & keyof E>
371
367
  } : E;
372
368
  /** The to-many relations a query populated, which come back as lists rather than as optional ones. */
373
369
  type PopulatedToMany<E, P> = Extract<P, ToManyRelationKey<E>>;
374
- /**
375
- * stringified query.
376
- */
377
- export type QueryStringified = {
378
- [K in keyof Query<unknown>]?: string;
379
- };
380
370
  /** What upserting one row reports. `created` is only knowable for a single statement, so a batch has none. */
381
371
  export type QueryUpsertOneResult<E> = {
382
372
  readonly id?: WrittenId<E>;
@@ -1,4 +1,4 @@
1
- import type { FieldKey, RelationKey, RelationTarget } from './entity.js';
1
+ import type { FieldKey, FieldKeyOf, RelationKey, RelationTarget } from './entity.js';
2
2
  import type { QueryPager, QuerySelect, QuerySortDirection } from './query.js';
3
3
  import type { QueryRaw } from './queryRaw.js';
4
4
  import type { QueryWhere, QueryWhereFieldValue } from './queryWhere.js';
@@ -44,13 +44,6 @@ export declare function resolveAggregateOp(key: string): {
44
44
  export type QueryFieldRef<E, F extends keyof E = FieldKey<E>> = ExactlyOne<Required<QuerySelect<E, F, true>>>;
45
45
  /** The argument of an aggregate function: a field, or `'*'` (only meaningful for `COUNT(*)`). */
46
46
  export type QueryAggregateArg<E> = QueryFieldRef<E> | '*';
47
- /**
48
- * Fields `SUM`/`AVG` can total. Restricted to numeric columns because totalling a text or date one is
49
- * either an engine error or a coercion, and neither produces the value the signature promises.
50
- */
51
- type NumericFieldKey<E> = {
52
- readonly [K in FieldKey<E>]: [NonNullable<E[K]>] extends [number | bigint] ? K : never;
53
- }[FieldKey<E>];
54
47
  /** Every aggregate op, plain and DISTINCT-qualified. */
55
48
  type AggregateOp = QueryAggregateOp | QueryAggregateDistinctOp;
56
49
  /**
@@ -67,9 +60,10 @@ type AveragingOp = OpsOf<'$avg'>;
67
60
  type TotallingOp = SummingOp | AveragingOp;
68
61
  /**
69
62
  * Every aggregate op mapped to the argument it accepts: `$count` a field or `'*'` (`COUNT(*)`),
70
- * the totalling ops a numeric field, `$min`/`$max`/`$countDistinct` any field.
63
+ * the totalling ops a numeric field, since totalling any other is an engine error or a coercion,
64
+ * and `$min`/`$max`/`$countDistinct` any field.
71
65
  */
72
- type QueryAggregateArgMap<E> = Record<'$count', QueryAggregateArg<E>> & Record<TotallingOp, QueryFieldRef<E, NumericFieldKey<E>>> & Record<Exclude<AggregateOp, '$count' | TotallingOp>, QueryFieldRef<E>>;
66
+ type QueryAggregateArgMap<E> = Record<'$count', QueryAggregateArg<E>> & Record<TotallingOp, QueryFieldRef<E, FieldKeyOf<E, number | bigint>>> & Record<Exclude<AggregateOp, '$count' | TotallingOp>, QueryFieldRef<E>>;
73
67
  /**
74
68
  * An aggregate over one field, exactly one op per entry: `{ $sum: { amount: true } }` is `SUM("amount")`,
75
69
  * `{ $countDistinct: { id: true } }` is `COUNT(DISTINCT "id")`, and only `$count` takes `'*'`. Its own
@@ -15,6 +15,11 @@ export type QueryRawRenderOptions = {
15
15
  * computed field's own, or the one whose schema is built. Absent where a statement renders SQL.
16
16
  */
17
17
  entity?: Type<unknown>;
18
+ /**
19
+ * The `FROM` a set-based trigger's body reads its rows through, `FROM inserted` and the like, which a
20
+ * write in it names. Absent where the body reads `NEW` and `OLD` bare, and outside a trigger.
21
+ */
22
+ rows?: string;
18
23
  };
19
24
  /** {@link QueryRawRenderOptions} as the callers along the way fill them in, every one still optional. */
20
25
  export type QueryRawFnOptions = Partial<QueryRawRenderOptions>;
@@ -45,12 +50,21 @@ export declare class QueryRaw {
45
50
  }
46
51
  /**
47
52
  * A field of an entity as SQL, read off `refs(Entity)` or a definition's refs: interpolated into `raw`, it
48
- * renders as the field's column. Its `key` is how an index tells a column from an expression.
53
+ * renders as the field's column. Its `key` is how an index tells a column from an expression, and `V`,
54
+ * the field's type, is what a value slot checks it against: see {@link RawFor}.
49
55
  */
50
- export declare class ColumnRef<K extends string = string> extends QueryRaw {
56
+ export declare class ColumnRef<K extends string = string, V = unknown> extends QueryRaw {
51
57
  readonly key: K;
58
+ readonly __value?: V;
52
59
  constructor(key: K, value: QueryRawFn);
53
60
  }
61
+ /**
62
+ * SQL where a value of type `V` goes: bare SQL, whose type is its author's to know, or a ref to a column
63
+ * holding one, nullability aside. `Raw` is what the transport carries, so the wire's `never` stays one.
64
+ */
65
+ export type RawFor<Raw, V> = Raw & {
66
+ readonly __value?: V | null;
67
+ };
54
68
  /**
55
69
  * A relation aggregate as SQL, read off a `computed` field's refs: `(user) => user.resources.count()`.
56
70
  * It renders as the correlated subquery a `$count` reads, so a field holding one is read, filtered and
@@ -63,7 +77,7 @@ export declare class ColumnRef<K extends string = string> extends QueryRaw {
63
77
  export declare class RelationAggregate<V = unknown, Storable extends boolean = boolean> extends QueryRaw {
64
78
  /** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */
65
79
  readonly spec: RelationAggregateSpec;
66
- private readonly __value;
80
+ readonly __value?: V;
67
81
  private readonly __storable;
68
82
  constructor(
69
83
  /** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */
@@ -29,7 +29,8 @@ export class QueryRaw {
29
29
  }
30
30
  /**
31
31
  * A field of an entity as SQL, read off `refs(Entity)` or a definition's refs: interpolated into `raw`, it
32
- * renders as the field's column. Its `key` is how an index tells a column from an expression.
32
+ * renders as the field's column. Its `key` is how an index tells a column from an expression, and `V`,
33
+ * the field's type, is what a value slot checks it against: see {@link RawFor}.
33
34
  */
34
35
  export class ColumnRef extends QueryRaw {
35
36
  key;
@@ -1,6 +1,6 @@
1
1
  import type { FieldKey, JsonFieldPaths, JsonFieldPathValue, RelationKey, RelationTarget } from './entity.js';
2
2
  import type { QuerySelect } from './query.js';
3
- import type { QueryRaw } from './queryRaw.js';
3
+ import type { QueryRaw, RawFor } from './queryRaw.js';
4
4
  import type { AtLeastOne, ExpandScalar, IsMany, QueryComparableScalar, Scalar } from './utility.js';
5
5
  import type { QueryVectorQuery } from './vector.js';
6
6
  /**
@@ -136,7 +136,7 @@ export type QueryWhereFieldOperatorMap<T, Raw = QueryRaw> = {
136
136
  * whether a value is between two values (inclusive). Shorthand for $gte + $lte.
137
137
  * @example { age: { $between: [18, 65] } }
138
138
  */
139
- $between?: [ExpandScalar<T>, ExpandScalar<T>];
139
+ $between?: readonly [ExpandScalar<T>, ExpandScalar<T>];
140
140
  /**
141
141
  * whether a string begins with the given string (case sensitive).
142
142
  */
@@ -176,11 +176,11 @@ export type QueryWhereFieldOperatorMap<T, Raw = QueryRaw> = {
176
176
  /**
177
177
  * whether a value matches any of the given values.
178
178
  */
179
- $in?: ExpandScalar<T>[];
179
+ $in?: readonly ExpandScalar<T>[];
180
180
  /**
181
181
  * whether a value does not match any of the given values.
182
182
  */
183
- $nin?: ExpandScalar<T>[];
183
+ $nin?: readonly ExpandScalar<T>[];
184
184
  /**
185
185
  * whether a value is null.
186
186
  * @example { deletedAt: { $isNull: true } }
@@ -195,7 +195,7 @@ export type QueryWhereFieldOperatorMap<T, Raw = QueryRaw> = {
195
195
  * whether an array contains all the specified values.
196
196
  * @example { tags: { $all: ['typescript', 'orm'] } }
197
197
  */
198
- $all?: unknown extends T ? unknown[] : NonNullable<T> extends readonly (infer U)[] ? ExpandScalar<U>[] : never;
198
+ $all?: unknown extends T ? readonly unknown[] : NonNullable<T> extends readonly (infer U)[] ? readonly ExpandScalar<U>[] : never;
199
199
  /** whether an array has the given length, or one in range: `{ roles: { $size: { $gte: 2 } } }`. */
200
200
  $size?: number | QuerySizeComparisonOps;
201
201
  /**
@@ -284,9 +284,9 @@ type IsUntypedColumn<T> = [Scalar] extends [NonNullable<T>] ? true : false;
284
284
  * A field's filter value: the value, `null` where it is optional, a list as an implicit `$in` (not on
285
285
  * an array field, where it would be ambiguous), or an operator map.
286
286
  */
287
- export type QueryWhereFieldValue<T, Raw = QueryRaw> = T | (undefined extends T ? null : never) | (IsMany<T> extends true ? never : T[]) | QueryWhereFieldOperators<T, Raw> | Raw;
287
+ export type QueryWhereFieldValue<T, Raw = QueryRaw> = T | (undefined extends T ? null : never) | (IsMany<T> extends true ? never : readonly T[]) | QueryWhereFieldOperators<T, Raw> | RawFor<Raw, T>;
288
288
  /**
289
289
  * query filter array - the value every {@link QueryGroupOp} takes.
290
290
  */
291
- export type QueryWhereArray<E, Raw = QueryRaw> = (QueryWhere<E, Raw> | Raw)[];
291
+ export type QueryWhereArray<E, Raw = QueryRaw> = readonly (QueryWhere<E, Raw> | Raw)[];
292
292
  export {};
@@ -65,11 +65,11 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
65
65
  * Ids are exact everywhere but MySQL, which infers them from its header and reports `undefined` rather
66
66
  * than a guess where it cannot: a batch naming some keys, or a key that is not `AUTO_INCREMENT`.
67
67
  */
68
- insertMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
68
+ insertMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
69
69
  /** Insert or update a record by its conflict paths; resolves to its id and whether it was created. */
70
70
  upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>): Promise<QueryUpsertOneResult<E>>;
71
71
  /** Insert or update records by their conflict paths; resolves to their ids in payload order. */
72
- upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
72
+ upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: readonly EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
73
73
  /**
74
74
  * insert or update a record.
75
75
  * @param entity the entity to persist on
@@ -83,7 +83,7 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
83
83
  * @param payload the data to be persisted
84
84
  * @return the IDs
85
85
  */
86
- saveMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
86
+ saveMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
87
87
  /**
88
88
  * Restore soft-deleted records (sets the soft-delete field back to `null`). Throws if the
89
89
  * entity has no soft-delete field.
@@ -1,5 +1,6 @@
1
1
  import type { VectorCast } from '../dialect/vectorCast.js';
2
2
  import type { IndexType } from '../schema/types.js';
3
+ import { UqlUsageError } from '../util/uqlError.js';
3
4
  /**
4
5
  * A vector search's metric: `cosine` (the default), `l2`, `inner` product or `l1`. No hamming: every
5
6
  * engine's takes a bit vector, which no field type maps to.
@@ -37,7 +38,7 @@ export type VectorMetric = {
37
38
  readonly index?: string;
38
39
  };
39
40
  /** The error every dialect throws for a metric it lacks. */
40
- export declare function unsupportedVectorMetric(dialectName: string, distance: VectorDistance, indexName?: string): TypeError;
41
+ export declare function unsupportedVectorMetric(dialectName: string, distance: VectorDistance, indexName?: string): UqlUsageError;
41
42
  /**
42
43
  * Vector-specific tuning options shared by `@Index` decorator, entity metadata, and migration schema.
43
44
  */
@@ -1,9 +1,10 @@
1
+ import { UqlUsageError } from '../util/uqlError.js';
1
2
  /** The keys that describe the search rather than bound it, so `$near`'s bounds are what is left. */
2
3
  export const VECTOR_QUERY_KEYS = ['$vector', '$distance'];
3
4
  /** The error every dialect throws for a metric it lacks. */
4
5
  export function unsupportedVectorMetric(dialectName, distance, indexName) {
5
6
  const where = indexName === undefined ? '' : ` (index "${indexName}")`;
6
- return new TypeError(`${dialectName} does not support vector distance metric: ${distance}${where}`);
7
+ return new UqlUsageError(`${dialectName} does not support vector distance metric: ${distance}${where}`);
7
8
  }
8
9
  /** The metric a search or an index measures by where nothing names one: every engine with vectors has it. */
9
10
  export const DEFAULT_VECTOR_DISTANCE = 'cosine';
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `YYYY-MM-DD HH:mm:ss.SSS` in UTC, then `zone`: how a date is written, whichever machine writes it. Not
3
+ * `toISOString` as it is, whose `T` and `Z` MySQL rejects outright ("Invalid default value").
4
+ */
5
+ export declare function utcTimestamp(date: Date, zone?: string): string;
6
+ /**
7
+ * A timestamp's text as the `Date` it names, the one rule every driver and hydration read dates by: UTC
8
+ * where it names no zone, its own offset where it does, a bare day at UTC midnight, and the fraction cut
9
+ * to the milliseconds a `Date` holds. Text that is none of these, such as `infinity`, stays text.
10
+ */
11
+ export declare function decodeDate(text: string): Date | string;