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.
- package/README.md +3 -3
- package/dist/browser/querier/httpQuerier.d.ts +2 -2
- package/dist/browser/querier/httpQuerier.js +2 -1
- package/dist/browser/type/clientQuerier.d.ts +2 -2
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +9 -8
- package/dist/bunSql/bunSql.util.js +2 -1
- package/dist/cockroachdb/crdbQuerierPool.js +2 -2
- package/dist/dialect/abstractSqlDialect.d.ts +16 -6
- package/dist/dialect/abstractSqlDialect.js +110 -41
- package/dist/dialect/hydrateColumn.js +2 -12
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +2 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +5 -0
- package/dist/dialect/operators.d.ts +7 -1
- package/dist/dialect/operators.js +13 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +1 -0
- package/dist/dialect/pgLikeSqlDialect.js +13 -4
- package/dist/entity/metadata/definition.d.ts +1 -2
- package/dist/entity/metadata/definition.js +37 -39
- package/dist/http/handler.js +5 -4
- package/dist/http/query.d.ts +1 -1
- package/dist/http/query.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/maria/mariadbQuerierPool.js +4 -2
- package/dist/migrate/acquireQuerierForMigrations.js +2 -1
- package/dist/migrate/assertCliConfig.js +7 -6
- package/dist/migrate/bin.js +0 -0
- package/dist/migrate/builder/expressions.d.ts +2 -0
- package/dist/migrate/builder/expressions.js +20 -10
- package/dist/migrate/builder/tableBuilder.js +1 -1
- package/dist/migrate/cli-config.js +5 -4
- package/dist/migrate/ddl/indexDdl.js +4 -3
- package/dist/migrate/ddl/mysqlIndexDdl.js +5 -4
- package/dist/migrate/ddl/pgIndexDdl.js +2 -1
- package/dist/migrate/ddl/sqliteIndexDdl.js +2 -1
- package/dist/migrate/ddl/tableDdl.js +2 -1
- package/dist/migrate/generator/mongoCommand.js +2 -1
- package/dist/migrate/generator/mongoSchemaGenerator.d.ts +1 -1
- package/dist/migrate/generator/mongoSchemaGenerator.js +7 -6
- package/dist/migrate/indexPredicate.js +2 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +2 -1
- package/dist/migrate/introspection/mongoIntrospector.d.ts +1 -1
- package/dist/migrate/introspection/mongoIntrospector.js +3 -2
- package/dist/migrate/introspection/mssqlIntrospector.js +13 -1
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +2 -0
- package/dist/migrate/introspection/mysqlIntrospector.js +6 -2
- package/dist/migrate/introspection/postgresIntrospector.js +1 -1
- package/dist/migrate/migrationTarget.js +2 -1
- package/dist/migrate/migrator.js +2 -1
- package/dist/migrate/schemaGenerator.js +5 -4
- package/dist/migrate/storage/databaseStorage.js +1 -1
- package/dist/migrate/triggerSql.d.ts +1 -1
- package/dist/migrate/triggerSql.js +77 -61
- package/dist/mongo/mongoDialect.d.ts +1 -3
- package/dist/mongo/mongoDialect.js +9 -14
- package/dist/mongo/mongodbQuerier.js +7 -10
- package/dist/mssql/mssqlQuerier.d.ts +2 -0
- package/dist/mssql/mssqlQuerier.js +8 -5
- package/dist/mysql/mysql2QuerierPool.d.ts +1 -0
- package/dist/mysql/mysql2QuerierPool.js +20 -2
- package/dist/neon/neonQuerierPool.js +2 -2
- package/dist/pglite/pgliteQuerierPool.js +10 -4
- package/dist/postgres/pgQuerierPool.js +2 -2
- package/dist/postgres/{pgNumericTypes.d.ts → pgWireTypes.d.ts} +4 -3
- package/dist/postgres/{pgNumericTypes.js → pgWireTypes.js} +7 -3
- package/dist/querier/abstractQuerier.d.ts +9 -4
- package/dist/querier/abstractQuerier.js +26 -19
- package/dist/querier/abstractQuerierPool.d.ts +3 -3
- package/dist/querier/abstractSqlQuerier.d.ts +2 -2
- package/dist/querier/abstractSqlQuerier.js +1 -1
- package/dist/querier/abstractSqlQuerierPool.d.ts +2 -2
- package/dist/querier/queryError.d.ts +2 -2
- package/dist/schema/canonicalType.d.ts +3 -0
- package/dist/schema/canonicalType.js +31 -9
- package/dist/schema/schemaASTBuilder.js +2 -1
- package/dist/schema/schemaASTDiffer.js +4 -2
- package/dist/sqlite/sqliteDialect.d.ts +1 -3
- package/dist/sqlite/sqliteDialect.js +3 -6
- package/dist/type/dialect.d.ts +23 -1
- package/dist/type/entity.d.ts +16 -12
- package/dist/type/logger.d.ts +2 -2
- package/dist/type/querier.d.ts +3 -3
- package/dist/type/query.d.ts +3 -13
- package/dist/type/queryAggregate.d.ts +4 -10
- package/dist/type/queryRaw.d.ts +17 -3
- package/dist/type/queryRaw.js +2 -1
- package/dist/type/queryWhere.d.ts +7 -7
- package/dist/type/universalQuerier.d.ts +3 -3
- package/dist/type/vector.d.ts +2 -1
- package/dist/type/vector.js +2 -1
- package/dist/util/date.d.ts +11 -0
- package/dist/util/date.js +19 -0
- package/dist/util/dialect.util.d.ts +13 -5
- package/dist/util/dialect.util.js +28 -20
- package/dist/util/field.util.d.ts +4 -4
- package/dist/util/field.util.js +10 -2
- package/dist/util/fieldOption.util.d.ts +5 -3
- package/dist/util/fieldOption.util.js +6 -5
- package/dist/util/hook.util.d.ts +1 -1
- package/dist/util/hook.util.js +8 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/index.js +1 -0
- package/dist/util/logger.d.ts +3 -3
- package/dist/util/object.util.js +3 -2
- package/dist/util/raw.d.ts +6 -7
- package/dist/util/raw.js +10 -12
- package/dist/util/sqlLiteral.d.ts +8 -1
- package/dist/util/sqlLiteral.js +14 -9
- package/dist/util/triggerWrite.d.ts +15 -0
- package/dist/util/triggerWrite.js +20 -0
- package/package.json +1 -1
- package/skills/uql-orm/SKILL.md +4 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ObjectId } from 'mongodb';
|
|
2
2
|
import { AbstractDialect } from '../dialect/abstractDialect.js';
|
|
3
3
|
import { AGGREGATE_VALUE_ALIAS, REL_NESTED_KEY, REL_TEMP_PREFIX, SUM_COUNT_ALIAS, nullsSortField, sortAggregateField, TEXT_SCORE_ALIAS, } from '../dialect/aliases.js';
|
|
4
|
-
import { GROUP_OPS, groupClauses, isGroupOp } from '../dialect/operators.js';
|
|
4
|
+
import { betweenBounds, GROUP_OPS, groupClauses, isGroupOp, whereOperators } from '../dialect/operators.js';
|
|
5
5
|
import { aggregateColumnField, groupPathField, resolveGroupJoins, relationSortTerms, resolveQueryJoins, resolveSortableJoin, } from '../dialect/queryJoins.js';
|
|
6
6
|
import { assertSoleId, fieldOf, getMeta, relationOf, soleIdOf } from '../entity/index.js';
|
|
7
7
|
import { COUNT_RESULT_KEY } from '../type/query.js';
|
|
@@ -334,12 +334,10 @@ export class MongoDialect extends AbstractDialect {
|
|
|
334
334
|
}
|
|
335
335
|
throw new UqlUsageError(`path ${key} does not exist in ${entityName(meta)}`);
|
|
336
336
|
}
|
|
337
|
-
/**
|
|
338
|
-
|
|
339
|
-
*/
|
|
340
|
-
transformOperators(ops) {
|
|
337
|
+
/** Transform UQL operators to MongoDB operators, refusing as `refusal` a key that is none. */
|
|
338
|
+
transformOperators(ops, refusal = 'unknown operator') {
|
|
341
339
|
const result = {};
|
|
342
|
-
for (const [op, val] of
|
|
340
|
+
for (const [op, val] of whereOperators(ops, refusal)) {
|
|
343
341
|
// `$elemMatch`'s value is itself a condition, so the operators inside it need the same
|
|
344
342
|
// mapping - passing it through raw sends UQL-only operators (`$startsWith`, `$between`, ...)
|
|
345
343
|
// straight to the server, which rejects them as unknown.
|
|
@@ -349,7 +347,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
349
347
|
}
|
|
350
348
|
// `$not` wraps a condition too, so a uql-only operator inside it (`$isNull`, `$startsWith`) is mapped.
|
|
351
349
|
if (op === '$not' && isOperatorObject(val)) {
|
|
352
|
-
result[op] = this.transformOperators(val);
|
|
350
|
+
result[op] = this.transformOperators(val, refusal);
|
|
353
351
|
continue;
|
|
354
352
|
}
|
|
355
353
|
// An object or an array is matched by what it holds, as the SQL engines read it, where native `$all`
|
|
@@ -374,7 +372,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
374
372
|
// Structural transforms
|
|
375
373
|
switch (op) {
|
|
376
374
|
case '$between': {
|
|
377
|
-
const [min, max] = val;
|
|
375
|
+
const [min, max] = betweenBounds(val);
|
|
378
376
|
result['$gte'] = min;
|
|
379
377
|
result['$lte'] = max;
|
|
380
378
|
break;
|
|
@@ -390,9 +388,6 @@ export class MongoDialect extends AbstractDialect {
|
|
|
390
388
|
// reads: converting a distance would mean guessing the metric, so this refuses.
|
|
391
389
|
throw new UqlUsageError('$near is not supported on MongoDB: Atlas scores by index-defined similarity, not distance. ' +
|
|
392
390
|
"Project the score with $sort's $project and filter on it instead.");
|
|
393
|
-
default:
|
|
394
|
-
result[op] = val;
|
|
395
|
-
break;
|
|
396
391
|
}
|
|
397
392
|
}
|
|
398
393
|
return result;
|
|
@@ -1072,7 +1067,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1072
1067
|
if (columnFamily(field.type) === 'string') {
|
|
1073
1068
|
return;
|
|
1074
1069
|
}
|
|
1075
|
-
throw new
|
|
1070
|
+
throw new UqlUsageError(`'${entityName(meta)}.${meta.ids[0]}' is declared '${declaredTypeName(field.type)}' and left to the ` +
|
|
1076
1071
|
'database, which MongoDB cannot do: the only key it generates is an ObjectId, read back as a string. ' +
|
|
1077
1072
|
"Declare the key as a string, or give it an 'onInsert' generator.");
|
|
1078
1073
|
}
|
|
@@ -1282,7 +1277,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
1282
1277
|
// on identical input. Keeping only numbers and objects dropped a string or boolean without a
|
|
1283
1278
|
// word, handing back every group instead of the filtered ones.
|
|
1284
1279
|
if (isOperatorMap(condition)) {
|
|
1285
|
-
filter[alias] = this.transformOperators(condition);
|
|
1280
|
+
filter[alias] = this.transformOperators(condition, 'unsupported HAVING operator');
|
|
1286
1281
|
}
|
|
1287
1282
|
else {
|
|
1288
1283
|
filter[alias] = Array.isArray(condition) ? { $in: condition } : condition;
|
|
@@ -1368,6 +1363,6 @@ function sortNulls(value) {
|
|
|
1368
1363
|
function assertReadable(meta, key) {
|
|
1369
1364
|
const field = meta.fields[key];
|
|
1370
1365
|
if (field?.computed && !aggregateOf(field)) {
|
|
1371
|
-
throw new
|
|
1366
|
+
throw new UqlUsageError(`cannot read '${meta.entity.name}.${key}' on MongoDB: a 'computed' field writing SQL is not something a document engine evaluates`);
|
|
1372
1367
|
}
|
|
1373
1368
|
}
|
|
@@ -2,7 +2,7 @@ import { AGGREGATE_VALUE_ALIAS } from '../dialect/aliases.js';
|
|
|
2
2
|
import { hasRequiredJoin } from '../dialect/queryJoins.js';
|
|
3
3
|
import { fieldOf, getMeta, namesKey, soleIdOf } from '../entity/index.js';
|
|
4
4
|
import { AbstractQuerier, enrichError } from '../querier/index.js';
|
|
5
|
-
import { clone, getKeys, getSoftDeleteValue, hasKeys, hasTriggers, populatesRelations, textSortOf, throwNoPendingTransaction, throwPendingTransaction, vectorCandidates, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
5
|
+
import { clone, getKeys, getSoftDeleteValue, hasKeys, hasTriggers, populatesRelations, textSortOf, throwNoPendingTransaction, throwPendingTransaction, vectorCandidates, withoutSoftDeleteFilter, whereEach, } from '../util/index.js';
|
|
6
6
|
import { UqlUsageError } from '../util/uqlError.js';
|
|
7
7
|
/**
|
|
8
8
|
* `$limit: 0` asks for no rows, the way it does on every SQL dialect - but MongoDB reads `limit(0)`
|
|
@@ -13,12 +13,13 @@ function asksForNoRows(q) {
|
|
|
13
13
|
return q.$limit === 0;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
* MongoDB
|
|
17
|
-
* silently. Refused instead, as a query naming
|
|
16
|
+
* MongoDB runs no trigger within a write (Atlas Database Triggers fire after the commit), so a write to
|
|
17
|
+
* an entity declaring one - a stamp included - would skip it silently. Refused instead, as a query naming
|
|
18
|
+
* SQL is. The why, in `architecture/triggers.md`.
|
|
18
19
|
*/
|
|
19
20
|
function refuseTriggers(entity) {
|
|
20
21
|
if (hasTriggers(getMeta(entity))) {
|
|
21
|
-
throw new UqlUsageError(`'${entity.name}' declares triggers, which MongoDB
|
|
22
|
+
throw new UqlUsageError(`'${entity.name}' declares triggers, which MongoDB cannot run within a write: a write here would skip them. ` +
|
|
22
23
|
'Keep the entity on a SQL engine, or drop its triggers and stamps.');
|
|
23
24
|
}
|
|
24
25
|
}
|
|
@@ -223,11 +224,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
223
224
|
return update;
|
|
224
225
|
}
|
|
225
226
|
buildConflictFilter(entity, conflictPaths, item) {
|
|
226
|
-
|
|
227
|
-
acc[key] = item[key];
|
|
228
|
-
return acc;
|
|
229
|
-
}, {});
|
|
230
|
-
return this.dialect.where(entity, where);
|
|
227
|
+
return this.dialect.where(entity, whereEach(getKeys(conflictPaths), (key) => item[key]));
|
|
231
228
|
}
|
|
232
229
|
async internalUpsertOne(entity, conflictPaths, payload) {
|
|
233
230
|
refuseTriggers(entity);
|
|
@@ -314,7 +311,7 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
314
311
|
/** Every read and write goes through here, which makes it where a released querier is caught. */
|
|
315
312
|
collection(entity) {
|
|
316
313
|
if (this.released) {
|
|
317
|
-
throw new
|
|
314
|
+
throw new UqlUsageError('querier already released');
|
|
318
315
|
}
|
|
319
316
|
const { name } = getMeta(entity);
|
|
320
317
|
return this.db.collection(name);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DateTime2 } from 'mssql';
|
|
1
2
|
import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
|
|
2
3
|
import type { QueryUpdateResult, RawRow, TransactionOptions } from '../type/index.js';
|
|
3
4
|
/** What `tedious` hands back for one statement, whichever shape it took. */
|
|
@@ -17,6 +18,7 @@ type MsSqlRowStream = AsyncIterable<unknown> & {
|
|
|
17
18
|
/** The part of an `mssql` `Request` a querier drives. */
|
|
18
19
|
type MsSqlRequest = {
|
|
19
20
|
input(name: string, value: unknown): unknown;
|
|
21
|
+
input(name: string, type: typeof DateTime2, value: unknown): unknown;
|
|
20
22
|
query(command: string): Promise<MsSqlResult>;
|
|
21
23
|
toReadableStream(): MsSqlRowStream;
|
|
22
24
|
cancel(): unknown;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ISOLATION_LEVEL } from 'mssql';
|
|
1
|
+
import { DateTime2, ISOLATION_LEVEL } from 'mssql';
|
|
2
2
|
import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
|
|
3
3
|
import { decodeWireTypes } from './mssqlWireTypes.js';
|
|
4
4
|
/**
|
|
@@ -9,13 +9,16 @@ import { decodeWireTypes } from './mssqlWireTypes.js';
|
|
|
9
9
|
export class MsSqlQuerier extends AbstractPoolQuerier {
|
|
10
10
|
#transaction;
|
|
11
11
|
/**
|
|
12
|
-
* Values bind by name, `@p1` upward, matching {@link MsSqlDialect.placeholder}. `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Values bind by name, `@p1` upward, matching {@link MsSqlDialect.placeholder}. `mssql` infers a type
|
|
13
|
+
* from the JS value, right for a `Uint8Array` and harmlessly wrong for a bare `null` (`NVarChar`), but
|
|
14
|
+
* a `Date` it binds as the legacy `DATETIME`, whose 1/300 s steps no `DATETIME2` column compares equal to.
|
|
15
15
|
*/
|
|
16
16
|
#request(values) {
|
|
17
17
|
const request = this.#transaction ? this.#transaction.request() : this.getConn().request();
|
|
18
|
-
values?.forEach((value, index) =>
|
|
18
|
+
values?.forEach((value, index) => {
|
|
19
|
+
const name = `p${index + 1}`;
|
|
20
|
+
return value instanceof Date ? request.input(name, DateTime2, value) : request.input(name, value);
|
|
21
|
+
});
|
|
19
22
|
return request;
|
|
20
23
|
}
|
|
21
24
|
async internalAll(query, values) {
|
|
@@ -4,6 +4,7 @@ import type { ExtraOptions } from '../type/index.js';
|
|
|
4
4
|
import { MySql2Querier } from './mysql2Querier.js';
|
|
5
5
|
import { MySqlDialect } from './mysqlDialect.js';
|
|
6
6
|
export declare class MySql2QuerierPool extends AbstractSqlQuerierPool<MySql2Querier, MySqlDialect> {
|
|
7
|
+
#private;
|
|
7
8
|
readonly pool: Pool;
|
|
8
9
|
constructor(opts: PoolOptions, extra?: ExtraOptions);
|
|
9
10
|
getQuerier(): Promise<MySql2Querier>;
|
|
@@ -5,14 +5,32 @@ import { MySql2Querier } from './mysql2Querier.js';
|
|
|
5
5
|
import { MySqlDialect } from './mysqlDialect.js';
|
|
6
6
|
export class MySql2QuerierPool extends AbstractSqlQuerierPool {
|
|
7
7
|
pool;
|
|
8
|
+
#utcSessions = new WeakSet();
|
|
8
9
|
constructor(opts, extra) {
|
|
9
10
|
super(new MySqlDialect(dialectOptionsFrom(extra)), extra);
|
|
10
11
|
// A BIGINT past 2^53 as its exact text rather than a rounded number, the rule every driver here
|
|
11
12
|
// decodes by (`decodeWideNumber`); within that range it stays a number, and DECIMAL is untouched.
|
|
12
|
-
|
|
13
|
+
// A date reads as the UTC it holds, whichever zone the process runs in.
|
|
14
|
+
this.pool = createPool({ supportBigNumbers: true, timezone: 'Z', ...opts });
|
|
13
15
|
}
|
|
14
16
|
async getQuerier() {
|
|
15
|
-
return new MySql2Querier(() => this
|
|
17
|
+
return new MySql2Querier(() => this.#connection(), this.dialect, this.extra);
|
|
18
|
+
}
|
|
19
|
+
/** A connection whose session is UTC too, so `NOW()` agrees with a bound date: set once per connection. */
|
|
20
|
+
async #connection() {
|
|
21
|
+
const connection = await this.pool.getConnection();
|
|
22
|
+
if (this.#utcSessions.has(connection.connection)) {
|
|
23
|
+
return connection;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
await connection.query("SET time_zone = '+00:00'");
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
connection.release();
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
this.#utcSessions.add(connection.connection);
|
|
33
|
+
return connection;
|
|
16
34
|
}
|
|
17
35
|
async end() {
|
|
18
36
|
await this.pool.end();
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Pool, types } from '@neondatabase/serverless';
|
|
2
2
|
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
3
3
|
import { AbstractPgQuerierPool } from '../postgres/abstractPgQuerierPool.js';
|
|
4
|
-
import {
|
|
4
|
+
import { wireTypes } from '../postgres/pgWireTypes.js';
|
|
5
5
|
import { PostgresDialect } from '../postgres/postgresDialect.js';
|
|
6
6
|
export class NeonQuerierPool extends AbstractPgQuerierPool {
|
|
7
7
|
constructor(opts, extra) {
|
|
8
8
|
// Neon's own `types`, not `pg`'s: this entry has to load on an edge runtime where `pg` is absent.
|
|
9
|
-
super(new PostgresDialect(dialectOptionsFrom(extra)), new Pool({ types:
|
|
9
|
+
super(new PostgresDialect(dialectOptionsFrom(extra)), new Pool({ types: wireTypes(types), ...opts }), extra);
|
|
10
10
|
}
|
|
11
11
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
2
2
|
import { PostgresDialect } from '../postgres/postgresDialect.js';
|
|
3
3
|
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
4
|
+
import { decodeDate } from '../util/date.js';
|
|
4
5
|
import { decodeWideNumber } from '../util/wideNumber.js';
|
|
5
6
|
import { PgliteQuerier } from './pgliteQuerier.js';
|
|
6
7
|
/**
|
|
@@ -18,12 +19,17 @@ export class PgliteQuerierPool extends AbstractSharedHandleQuerierPool {
|
|
|
18
19
|
}
|
|
19
20
|
async openDb() {
|
|
20
21
|
const { PGlite, types } = await import('@electric-sql/pglite');
|
|
21
|
-
// INT8 by the one wide-integer rule, where PGlite's own answers a `bigint` past 2^53
|
|
22
|
-
//
|
|
23
|
-
// real driver, so no cast is needed
|
|
22
|
+
// INT8 by the one wide-integer rule, where PGlite's own answers a `bigint` past 2^53, and a zoneless
|
|
23
|
+
// TIMESTAMP or a DATE as UTC, as every pool reads one; a caller's own `parsers` still win. The declared
|
|
24
|
+
// return type is what checks {@link PgliteDatabase} against the real driver, so no cast is needed below it.
|
|
24
25
|
return PGlite.create(this.dataDir, {
|
|
25
26
|
...this.opts,
|
|
26
|
-
parsers: {
|
|
27
|
+
parsers: {
|
|
28
|
+
[types.INT8]: decodeWideNumber,
|
|
29
|
+
[types.TIMESTAMP]: decodeDate,
|
|
30
|
+
[types.DATE]: decodeDate,
|
|
31
|
+
...this.opts?.parsers,
|
|
32
|
+
},
|
|
27
33
|
});
|
|
28
34
|
}
|
|
29
35
|
buildQuerier(db) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Pool, types } from 'pg';
|
|
2
2
|
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
3
3
|
import { AbstractPgQuerierPool } from './abstractPgQuerierPool.js';
|
|
4
|
-
import {
|
|
4
|
+
import { wireTypes } from './pgWireTypes.js';
|
|
5
5
|
import { PostgresDialect } from './postgresDialect.js';
|
|
6
6
|
export class PgQuerierPool extends AbstractPgQuerierPool {
|
|
7
7
|
constructor(opts, extra) {
|
|
8
8
|
// keepAlive reduces (but can't eliminate) idle connections being silently
|
|
9
9
|
// dropped by NATs/firewalls on long-lived remote connections.
|
|
10
|
-
super(new PostgresDialect(dialectOptionsFrom(extra)), new Pool({ keepAlive: true, types:
|
|
10
|
+
super(new PostgresDialect(dialectOptionsFrom(extra)), new Pool({ keepAlive: true, types: wireTypes(types), ...opts }), extra);
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -11,8 +11,9 @@ type PgTypes = {
|
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
13
|
* Decodes `INT8` by `decodeWideNumber` and `FLOAT8` as the float64 it is, since `type: Number` maps to
|
|
14
|
-
* BIGINT
|
|
15
|
-
*
|
|
14
|
+
* BIGINT, and a zoneless `TIMESTAMP` or a `DATE` as UTC, where `pg` reads both in the process's zone. At
|
|
15
|
+
* the wire, which every result crosses; `NUMERIC` is left to hydration, which knows the field. Per pool,
|
|
16
|
+
* never a global parser, and a caller's own `types` win.
|
|
16
17
|
*/
|
|
17
|
-
export declare function
|
|
18
|
+
export declare function wireTypes(types: PgTypes): CustomTypesConfig;
|
|
18
19
|
export {};
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import { decodeDate } from '../util/date.js';
|
|
1
2
|
import { decodeWideNumber } from '../util/wideNumber.js';
|
|
2
3
|
/**
|
|
3
4
|
* Decodes `INT8` by `decodeWideNumber` and `FLOAT8` as the float64 it is, since `type: Number` maps to
|
|
4
|
-
* BIGINT
|
|
5
|
-
*
|
|
5
|
+
* BIGINT, and a zoneless `TIMESTAMP` or a `DATE` as UTC, where `pg` reads both in the process's zone. At
|
|
6
|
+
* the wire, which every result crosses; `NUMERIC` is left to hydration, which knows the field. Per pool,
|
|
7
|
+
* never a global parser, and a caller's own `types` win.
|
|
6
8
|
*/
|
|
7
|
-
export function
|
|
9
|
+
export function wireTypes(types) {
|
|
8
10
|
// Text only: in binary mode an INT8 arrives as an 8-byte Buffer, and `Number(buffer)` is `NaN`.
|
|
9
11
|
const decoders = new Map([
|
|
10
12
|
[types.builtins['INT8'], decodeWideNumber],
|
|
11
13
|
[types.builtins['FLOAT8'], Number],
|
|
14
|
+
[types.builtins['TIMESTAMP'], decodeDate],
|
|
15
|
+
[types.builtins['DATE'], decodeDate],
|
|
12
16
|
]);
|
|
13
17
|
return {
|
|
14
18
|
getTypeParser: (oid, format) => (format === 'text' && decoders.get(oid)) || types.getTypeParser(oid, format),
|
|
@@ -87,7 +87,7 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
87
87
|
* The `onInsert` values are filled here, before the write, so the after hooks and the ids read the
|
|
88
88
|
* same rows the statement wrote.
|
|
89
89
|
*/
|
|
90
|
-
insertMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
90
|
+
insertMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
91
91
|
/** Writes `rows`, and onto each one the key the database generated for it, where it can tell. */
|
|
92
92
|
protected abstract internalInsertMany<E extends object>(entity: Type<E>, rows: EntityData<E>[]): Promise<void>;
|
|
93
93
|
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdateWrite<E>, opts?: QueryOptions): Promise<number>;
|
|
@@ -118,9 +118,14 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
118
118
|
protected abstract internalUpdateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
119
119
|
restoreOneById<E extends object>(entity: Type<E>, id: EntityId<E>): Promise<number>;
|
|
120
120
|
restoreMany<E extends object>(entity: Type<E>, q: QuerySearch<E>): Promise<number>;
|
|
121
|
+
/**
|
|
122
|
+
* An update the library writes of its own, hooked as a caller's is but carrying no version: a restore's
|
|
123
|
+
* cleared stamp, or a row pointed at the relation just inserted for it.
|
|
124
|
+
*/
|
|
125
|
+
private unversionedUpdate;
|
|
121
126
|
/** Fires `beforeUpsert`/`afterUpsert`: which branch a row takes is the database's to decide, so neither the insert's nor the update's pair fits. */
|
|
122
127
|
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>): Promise<QueryUpsertOneResult<E>>;
|
|
123
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
128
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: readonly EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
124
129
|
protected abstract internalUpsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
|
|
125
130
|
protected abstract internalUpsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
|
|
126
131
|
deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;
|
|
@@ -137,7 +142,7 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
137
142
|
* upserts on that key, so a stale id is written rather than silently missed, and an unnamed one
|
|
138
143
|
* inserts. A composite is always named. The hooks follow the statement: a named row fires the upsert pair.
|
|
139
144
|
*/
|
|
140
|
-
saveMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
145
|
+
saveMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
141
146
|
/** Writes each inserted row's relations, one set of statements per relation whatever the number of rows. */
|
|
142
147
|
protected insertRelations<E extends object>(entity: Type<E>, rows: E[]): Promise<void>;
|
|
143
148
|
/** `EntityId` because a settled composite row is an object, which {@link childrenOf} reads each foreign key column out of. */
|
|
@@ -182,7 +187,7 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
182
187
|
/** Runs `task` after everything already queued, one at a time. Not re-entrant: never nest `serialize` calls. */
|
|
183
188
|
protected serialize<T>(task: () => Promise<T>): Promise<T>;
|
|
184
189
|
/** Runs `task`, logs `query` with its duration, and tags a failure with it: a method, since a decorator would lose the generics. */
|
|
185
|
-
protected timed<T>(query: string, values: unknown[] | undefined, task: () => Promise<T>): Promise<T>;
|
|
190
|
+
protected timed<T>(query: string, values: readonly unknown[] | undefined, task: () => Promise<T>): Promise<T>;
|
|
186
191
|
abstract beginTransaction(opts?: TransactionOptions): Promise<void>;
|
|
187
192
|
/** Strict: this is the check that catches a forgotten `beginTransaction`. */
|
|
188
193
|
abstract commitTransaction(): Promise<void>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { assertSoleId, getMeta, idOf, namesKey, relationOf } from '../entity/index.js';
|
|
2
|
+
import { namesRows } from '../dialect/operators.js';
|
|
2
3
|
import { parseQueryLock } from '../type/index.js';
|
|
3
|
-
import { cascadesOnDelete, childrenOf, clone, entityName, fillOnFields, filterFieldKeys, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, idOnlyQuery,
|
|
4
|
+
import { cascadesOnDelete, childrenOf, clone, entityName, fillOnFields, filterFieldKeys, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, idOnlyQuery, keySet, isPagedQuery, isScalarId, LoggerWrapper, parentJoins, queryLoggerFor, parseRelationAtKey, parseRelationQueryValue, rowKey, runHooks, someKey, targetKeyColumns, whereAnyOf, whereEach, whereIds, whereWith, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
4
5
|
import { UqlOptimisticLockError, UqlUsageError } from '../util/uqlError.js';
|
|
5
6
|
import { enrichError } from './queryError.js';
|
|
6
7
|
/**
|
|
@@ -35,7 +36,7 @@ function soleParentColumn(relOpts) {
|
|
|
35
36
|
* every table look narrowed, which is the case this exists to catch.
|
|
36
37
|
*/
|
|
37
38
|
function assertNamesRows(entity, method, q, opts) {
|
|
38
|
-
if (opts?.unfiltered ||
|
|
39
|
+
if (opts?.unfiltered || namesRows(q?.$where) || q?.$limit !== undefined) {
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
42
|
throw new UqlUsageError(`'${method}' over '${entity.name}' names no rows, so it would address every one: pass '{ unfiltered: true }' to mean it`);
|
|
@@ -53,7 +54,7 @@ function lockVersion(meta, key, q, row) {
|
|
|
53
54
|
const next = typeof expected === 'bigint' ? expected + 1n : expected + 1;
|
|
54
55
|
// Spread, as every other added predicate here is: one flat `AND`, and a caller already filtering on
|
|
55
56
|
// the version contradicts itself into matching nothing, which is what they asked for.
|
|
56
|
-
return { expected, next, q: { ...q, $where:
|
|
57
|
+
return { expected, next, q: { ...q, $where: whereWith(key, expected, q.$where) } };
|
|
57
58
|
}
|
|
58
59
|
/**
|
|
59
60
|
* Refuses a write that cannot carry the lock, rather than writing over whatever the row holds now.
|
|
@@ -72,8 +73,8 @@ function assertUnversioned(meta, what) {
|
|
|
72
73
|
* an `UPDATE` - reads the ids and writes them separately, putting the race back in the gap between.
|
|
73
74
|
*/
|
|
74
75
|
function assertLockableUpdate(meta, q, settles) {
|
|
75
|
-
const where = q.$where;
|
|
76
|
-
const namesOneRow = meta.ids.every((key) => where
|
|
76
|
+
const where = { ...q.$where };
|
|
77
|
+
const namesOneRow = meta.ids.every((key) => where[key] !== undefined && isScalarId(where[key]));
|
|
77
78
|
if (!namesOneRow || settles) {
|
|
78
79
|
throw new UqlUsageError(`cannot update '${entityName(meta)}' this way: a versioned row is matched and written in one statement, so it is named by its ${meta.ids.map((id) => `'${id}'`).join(', ')}, takes no '$sort', '$limit' or '$skip', writes no relation, and filters by none`);
|
|
79
80
|
}
|
|
@@ -290,9 +291,7 @@ export class AbstractQuerier {
|
|
|
290
291
|
*/
|
|
291
292
|
async throwStaleVersion(entity, key, q, expected, opts) {
|
|
292
293
|
const meta = getMeta(entity);
|
|
293
|
-
const
|
|
294
|
-
const byId = Object.fromEntries(meta.ids.map((id) => [id, where[id]]));
|
|
295
|
-
const row = await this.findOne(entity, { $select: { [key]: true }, $where: byId }, opts);
|
|
294
|
+
const row = await this.findOne(entity, { $select: keySet([key]), $where: whereIds(meta, idOf(meta, { ...q.$where })) }, opts);
|
|
296
295
|
const actual = row?.[key];
|
|
297
296
|
const message = actual === undefined
|
|
298
297
|
? `no row of '${entityName(meta)}' has that id any more: it is gone`
|
|
@@ -329,10 +328,17 @@ export class AbstractQuerier {
|
|
|
329
328
|
if (!meta.softDelete) {
|
|
330
329
|
throw new UqlUsageError(`'${entity.name}' has not enabled 'softDelete'`);
|
|
331
330
|
}
|
|
332
|
-
const $where =
|
|
331
|
+
const $where = whereWith(meta.softDelete, { $ne: null }, q.$where);
|
|
333
332
|
// No version: a restore only undoes the stamp a delete left, which takes none either, and two of
|
|
334
333
|
// them racing agree on the result anyway. A lock is for content, and a restore writes none.
|
|
335
|
-
return this.
|
|
334
|
+
return this.unversionedUpdate(entity, { ...q, $where }, { [meta.softDelete]: null }, { filters: { softDelete: false } });
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* An update the library writes of its own, hooked as a caller's is but carrying no version: a restore's
|
|
338
|
+
* cleared stamp, or a row pointed at the relation just inserted for it.
|
|
339
|
+
*/
|
|
340
|
+
unversionedUpdate(entity, q, payload, opts) {
|
|
341
|
+
return this.hooked(entity, 'Update', [payload], ([row]) => this.updateRows(entity, q, row, opts, undefined));
|
|
336
342
|
}
|
|
337
343
|
/** Fires `beforeUpsert`/`afterUpsert`: which branch a row takes is the database's to decide, so neither the insert's nor the update's pair fits. */
|
|
338
344
|
async upsertOne(entity, conflictPaths, payload) {
|
|
@@ -426,7 +432,7 @@ export class AbstractQuerier {
|
|
|
426
432
|
}
|
|
427
433
|
}
|
|
428
434
|
if (toUpsert.length) {
|
|
429
|
-
const conflictPaths =
|
|
435
|
+
const conflictPaths = keySet(meta.ids);
|
|
430
436
|
const { ids: upserted } = await this.upsertMany(entity, conflictPaths, toUpsert.map((index) => payload[index]));
|
|
431
437
|
for (let position = 0; position < toUpsert.length; position++) {
|
|
432
438
|
ids[toUpsert[position]] = upserted[position];
|
|
@@ -492,7 +498,7 @@ export class AbstractQuerier {
|
|
|
492
498
|
const savedIds = await this.saveMany(relEntity, children.map(({ row }) => row));
|
|
493
499
|
// A link needs the target's id, which a MySQL batch mixing supplied and generated keys cannot report.
|
|
494
500
|
if (savedIds.includes(undefined)) {
|
|
495
|
-
throw new
|
|
501
|
+
throw new UqlUsageError(`'${relEntity.name}' rows saved through '${holder.name}' reported no id, so they cannot be linked. ` +
|
|
496
502
|
'Insert them with their own ids, or save the relation in its own statement.');
|
|
497
503
|
}
|
|
498
504
|
const [targetColumn] = targetKeyColumns(relOpts, 1);
|
|
@@ -506,7 +512,8 @@ export class AbstractQuerier {
|
|
|
506
512
|
const pointing = writes.filter(({ value }) => value);
|
|
507
513
|
const referenceIds = await this.insertMany(relEntity, pointing.map(({ value }) => value));
|
|
508
514
|
for (const [index, { id }] of pointing.entries()) {
|
|
509
|
-
|
|
515
|
+
assertIdValue(entity, id);
|
|
516
|
+
await this.unversionedUpdate(entity, { $where: whereIds(getMeta(entity), id) }, { [localColumn]: referenceIds[index] });
|
|
510
517
|
}
|
|
511
518
|
}
|
|
512
519
|
/**
|
|
@@ -573,8 +580,8 @@ export class AbstractQuerier {
|
|
|
573
580
|
const [idKey] = meta.ids;
|
|
574
581
|
const keys = getKeys(conflictPaths);
|
|
575
582
|
const q = {
|
|
576
|
-
$select:
|
|
577
|
-
$where:
|
|
583
|
+
$select: keySet([idKey, ...keys]),
|
|
584
|
+
$where: whereAnyOf(rows.map((row) => whereEach(keys, (key) => row[key]))),
|
|
578
585
|
};
|
|
579
586
|
const found = await this.internalFindMany(entity, q, { filters: withoutSoftDeleteFilter(undefined) });
|
|
580
587
|
const byConflict = new Map();
|
|
@@ -590,12 +597,12 @@ export class AbstractQuerier {
|
|
|
590
597
|
* the write filled in - a generated key, an `onInsert` value - without it landing on the caller's.
|
|
591
598
|
*/
|
|
592
599
|
async hooked(entity, event, payloads, write) {
|
|
593
|
-
// The one place a
|
|
594
|
-
//
|
|
595
|
-
//
|
|
600
|
+
// The one place a write becomes the row the rest of the library handles. They are the same object:
|
|
601
|
+
// a write is the entity's data without the keys the database fills, which TypeScript cannot relate
|
|
602
|
+
// across an entity it has not resolved.
|
|
596
603
|
const asRows = payloads;
|
|
597
604
|
await this.emitHook(entity, `before${event}`, asRows);
|
|
598
|
-
const rows = clone(
|
|
605
|
+
const rows = asRows.map((row) => clone(row));
|
|
599
606
|
const result = await write(rows);
|
|
600
607
|
await this.emitHook(entity, `after${event}`, rows);
|
|
601
608
|
return result;
|
|
@@ -38,13 +38,13 @@ export declare abstract class AbstractQuerierPool<Q extends Querier, D extends A
|
|
|
38
38
|
aggregate<E extends object, const G extends QueryGroupMap<E>, const A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
|
|
39
39
|
estimatedCount<E extends object>(entity: Type<E>): Promise<number>;
|
|
40
40
|
insertOne<E extends object>(entity: Type<E>, payload: EntityWrite<E>): Promise<WrittenId<E> | undefined>;
|
|
41
|
-
insertMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
41
|
+
insertMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
42
42
|
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdateWrite<E>, opts?: QueryOptions): Promise<number>;
|
|
43
43
|
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdateWrite<E>, opts?: QueryOptions): Promise<number>;
|
|
44
44
|
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>): Promise<QueryUpsertOneResult<E>>;
|
|
45
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
45
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: readonly EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
46
46
|
saveOne<E extends object>(entity: Type<E>, payload: EntityWrite<E>): Promise<WrittenId<E> | undefined>;
|
|
47
|
-
saveMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
47
|
+
saveMany<E extends object>(entity: Type<E>, payload: readonly EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
48
48
|
deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;
|
|
49
49
|
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
50
50
|
restoreOneById<E extends object>(entity: Type<E>, id: EntityId<E>): Promise<number>;
|
|
@@ -32,8 +32,8 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
|
|
|
32
32
|
* caught for every SQL backend.
|
|
33
33
|
*/
|
|
34
34
|
protected lazyConnect(): Promise<void>;
|
|
35
|
-
all<T>(query: string, values?: unknown[]): Promise<T[]>;
|
|
36
|
-
run(query: string, values?: unknown[]): Promise<QueryUpdateResult>;
|
|
35
|
+
all<T>(query: string, values?: readonly unknown[]): Promise<T[]>;
|
|
36
|
+
run(query: string, values?: readonly unknown[]): Promise<QueryUpdateResult>;
|
|
37
37
|
/** The rows of a statement the dialect builds. */
|
|
38
38
|
private query;
|
|
39
39
|
/** Runs a statement the dialect builds. */
|
|
@@ -91,7 +91,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
91
91
|
*/
|
|
92
92
|
async lazyConnect() {
|
|
93
93
|
if (this.released) {
|
|
94
|
-
throw new
|
|
94
|
+
throw new UqlUsageError('querier already released');
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
async all(query, values) {
|
|
@@ -6,6 +6,6 @@ import { AbstractQuerierPool } from './abstractQuerierPool.js';
|
|
|
6
6
|
* the connection-per-call semantics.
|
|
7
7
|
*/
|
|
8
8
|
export declare abstract class AbstractSqlQuerierPool<Q extends SqlQuerier, D extends AbstractSqlDialect> extends AbstractQuerierPool<Q, D> implements SqlQuerierPool<Q, D> {
|
|
9
|
-
all<T>(query: string, values?: unknown[]): Promise<T[]>;
|
|
10
|
-
run(query: string, values?: unknown[]): Promise<QueryUpdateResult>;
|
|
9
|
+
all<T>(query: string, values?: readonly unknown[]): Promise<T[]>;
|
|
10
|
+
run(query: string, values?: readonly unknown[]): Promise<QueryUpdateResult>;
|
|
11
11
|
}
|
|
@@ -6,7 +6,7 @@ import { type QueryErrorKind } from '../util/uqlError.js';
|
|
|
6
6
|
*/
|
|
7
7
|
export interface QueryError extends Error {
|
|
8
8
|
query?: string;
|
|
9
|
-
values?: unknown[];
|
|
9
|
+
values?: readonly unknown[];
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Names what `err` ran into on any engine, or `undefined` for anything else. Pure: the error is only
|
|
@@ -18,4 +18,4 @@ export declare function queryErrorKind(err: unknown): QueryErrorKind | undefined
|
|
|
18
18
|
* control flow it is. `values` are attached only when `logger?.willLogValues()`: they already surface
|
|
19
19
|
* in the logs then, so this opens no new leak.
|
|
20
20
|
*/
|
|
21
|
-
export declare function enrichError(err: unknown, logger: LoggerWrapper | undefined, query: string, values?: unknown[]): unknown;
|
|
21
|
+
export declare function enrichError(err: unknown, logger: LoggerWrapper | undefined, query: string, values?: readonly unknown[]): unknown;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AbstractDialect } from '../dialect/abstractDialect.js';
|
|
2
2
|
import type { VectorCast } from '../dialect/vectorCast.js';
|
|
3
3
|
import type { ColumnType, EntityGetter, FieldMeta, FieldOptions } from '../type/entity.js';
|
|
4
|
+
import { type DialectName } from '../type/index.js';
|
|
4
5
|
import type { CanonicalType, TypeCategory } from './types.js';
|
|
5
6
|
/** Whether a category is one of the vector types, narrowing it to the cast pgvector names use. */
|
|
6
7
|
export declare function isVectorCategory(category: TypeCategory | undefined): category is VectorCast;
|
|
@@ -29,6 +30,8 @@ export declare function canonicalToSql(type: CanonicalType, dialect: AbstractDia
|
|
|
29
30
|
* Convert a canonical type to a TypeScript type string.
|
|
30
31
|
*/
|
|
31
32
|
export declare function canonicalToTypeScript(type: CanonicalType): string;
|
|
33
|
+
/** The fractional-second digits an engine's timestamp holds when its type states none; `undefined` where it counts none. */
|
|
34
|
+
export declare function defaultTimestampPrecision(dialectName: DialectName): number | undefined;
|
|
32
35
|
/**
|
|
33
36
|
* A type as `dialect` stores it, rendered and read back: several types share one storage type, and only
|
|
34
37
|
* the engine settles an unstated bound. Migrations and drift both compare through it.
|