uql-orm 0.50.0 → 0.52.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 (70) hide show
  1. package/README.md +1 -1
  2. package/dist/browser/uql-browser.min.js +2 -2
  3. package/dist/browser/uql-browser.min.js.map +2 -2
  4. package/dist/bunSql/bunSql.util.d.ts +33 -10
  5. package/dist/bunSql/bunSql.util.js +57 -42
  6. package/dist/bunSql/bunSqlQuerier.d.ts +13 -8
  7. package/dist/bunSql/bunSqlQuerier.js +17 -8
  8. package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -2
  9. package/dist/bunSql/bunSqlQuerierPool.js +38 -25
  10. package/dist/bunSql/index.d.ts +1 -3
  11. package/dist/bunSql/index.js +0 -3
  12. package/dist/dialect/abstractSqlDialect.d.ts +61 -7
  13. package/dist/dialect/abstractSqlDialect.js +88 -27
  14. package/dist/dialect/aliases.d.ts +2 -0
  15. package/dist/dialect/aliases.js +2 -0
  16. package/dist/dialect/jsonSql.d.ts +2 -2
  17. package/dist/dialect/jsonSql.js +2 -2
  18. package/dist/dialect/mergeSqlDialect.d.ts +45 -0
  19. package/dist/dialect/mergeSqlDialect.js +89 -0
  20. package/dist/dialect/mysqlLikeSqlDialect.js +4 -1
  21. package/dist/dialect/pgLikeSqlDialect.d.ts +3 -3
  22. package/dist/dialect/pgLikeSqlDialect.js +15 -12
  23. package/dist/migrate/builder/expressions.js +5 -0
  24. package/dist/migrate/introspection/index.d.ts +2 -0
  25. package/dist/migrate/introspection/index.js +2 -0
  26. package/dist/migrate/introspection/mssqlIntrospector.d.ts +63 -0
  27. package/dist/migrate/introspection/mssqlIntrospector.js +198 -0
  28. package/dist/migrate/introspection/postgresIntrospector.js +3 -3
  29. package/dist/migrate/introspection/registry.d.ts +3 -0
  30. package/dist/migrate/introspection/registry.js +28 -0
  31. package/dist/migrate/migrator.js +2 -21
  32. package/dist/mongo/mongoDialect.js +4 -1
  33. package/dist/mongo/mongodbQuerier.d.ts +2 -2
  34. package/dist/mongo/mongodbQuerier.js +8 -6
  35. package/dist/mssql/index.d.ts +3 -0
  36. package/dist/mssql/index.js +3 -0
  37. package/dist/mssql/mssqlDialect.d.ts +144 -0
  38. package/dist/mssql/mssqlDialect.js +328 -0
  39. package/dist/mssql/mssqlQuerier.d.ts +23 -0
  40. package/dist/mssql/mssqlQuerier.js +137 -0
  41. package/dist/mssql/mssqlQuerierPool.d.ts +17 -0
  42. package/dist/mssql/mssqlQuerierPool.js +32 -0
  43. package/dist/mssql/mssqlWireTypes.d.ts +23 -0
  44. package/dist/mssql/mssqlWireTypes.js +44 -0
  45. package/dist/pglite/pgliteQuerier.d.ts +4 -2
  46. package/dist/pglite/pgliteQuerier.js +7 -2
  47. package/dist/postgres/pgCursorStream.d.ts +20 -0
  48. package/dist/postgres/pgCursorStream.js +49 -0
  49. package/dist/postgres/pgDialect.d.ts +1 -1
  50. package/dist/postgres/pgDialect.js +1 -1
  51. package/dist/postgres/postgresWireDriverCapabilities.d.ts +12 -10
  52. package/dist/postgres/postgresWireDriverCapabilities.js +12 -10
  53. package/dist/querier/abstractQuerier.d.ts +3 -7
  54. package/dist/querier/abstractQuerier.js +22 -2
  55. package/dist/querier/abstractQuerierPool.d.ts +3 -3
  56. package/dist/schema/canonicalType.js +96 -113
  57. package/dist/sqlite/sqliteDialect.d.ts +7 -7
  58. package/dist/sqlite/sqliteDialect.js +21 -18
  59. package/dist/type/dialect.d.ts +31 -4
  60. package/dist/type/migratorDialect.d.ts +1 -1
  61. package/dist/type/migratorDialect.js +1 -0
  62. package/dist/type/query.d.ts +29 -6
  63. package/dist/type/universalQuerier.d.ts +3 -3
  64. package/package.json +13 -3
  65. package/dist/bunSql/bunSqlCockroachDialect.d.ts +0 -12
  66. package/dist/bunSql/bunSqlCockroachDialect.js +0 -15
  67. package/dist/bunSql/bunSqlPostgresDialect.d.ts +0 -11
  68. package/dist/bunSql/bunSqlPostgresDialect.js +0 -14
  69. package/dist/bunSql/bunSqliteDialect.d.ts +0 -6
  70. package/dist/bunSql/bunSqliteDialect.js +0 -6
@@ -0,0 +1,32 @@
1
+ import { ConnectionPool } from 'mssql';
2
+ import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
3
+ import { AbstractSqlQuerierPool } from '../querier/index.js';
4
+ import { MsSqlDialect } from './mssqlDialect.js';
5
+ import { MsSqlQuerier } from './mssqlQuerier.js';
6
+ export class MsSqlQuerierPool extends AbstractSqlQuerierPool {
7
+ pool;
8
+ #connected;
9
+ constructor(opts, extra) {
10
+ super(new MsSqlDialect(dialectOptionsFrom(extra)), extra);
11
+ this.pool = new ConnectionPool(opts);
12
+ }
13
+ /**
14
+ * `mssql` connects the pool as a whole rather than per checkout, so the promise is shared. A
15
+ * failed one is dropped rather than kept: memoized, a single transient failure would be handed to
16
+ * every later caller for the life of the pool.
17
+ */
18
+ async getQuerier() {
19
+ return new MsSqlQuerier(() => this.#connect(), this.dialect, this.extra);
20
+ }
21
+ #connect() {
22
+ this.#connected ??= this.pool.connect().catch((err) => {
23
+ this.#connected = undefined;
24
+ throw err;
25
+ });
26
+ return this.#connected;
27
+ }
28
+ async end() {
29
+ this.#connected = undefined;
30
+ await this.pool.close();
31
+ }
32
+ }
@@ -0,0 +1,23 @@
1
+ /** The column metadata `tedious` reports beside a recordset, narrowed to the one field read here. */
2
+ type ColumnTypes = Record<string, {
3
+ readonly type: unknown;
4
+ }>;
5
+ /**
6
+ * Decode `BIGINT` as a JS number, leaving every other type to the driver.
7
+ *
8
+ * uql owes this to the caller because uql picks the column: `type: Number` maps to BIGINT (see
9
+ * `schema/canonicalType.ts`), and `tedious` hands that back as a string to protect the digits past
10
+ * 2^53 - so without this a field declared `number` reads back as `'9'`, including every generated
11
+ * primary key on every entity.
12
+ *
13
+ * At the wire, for the reason `pgNumericTypes` gives: everything crosses it exactly once - entity
14
+ * reads, the ids an `OUTPUT` reports, raw SQL, counts, aggregates - where the ORM's own hydration
15
+ * only ever sees entity reads.
16
+ *
17
+ * Exact to 2^53, which covers any generated id. A value past it keeps the string the driver gave,
18
+ * because a number could no longer represent it: that is the one case where handing back the exact
19
+ * text is more useful than handing back the type that was asked for. The lighter escape hatch for a
20
+ * column that big is the declaration - `@Field({ type: String, columnType: 'bigint' })`.
21
+ */
22
+ export declare function decodeWireTypes<T>(rows: T[] | undefined, columns: ColumnTypes | undefined): T[];
23
+ export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Decode `BIGINT` as a JS number, leaving every other type to the driver.
3
+ *
4
+ * uql owes this to the caller because uql picks the column: `type: Number` maps to BIGINT (see
5
+ * `schema/canonicalType.ts`), and `tedious` hands that back as a string to protect the digits past
6
+ * 2^53 - so without this a field declared `number` reads back as `'9'`, including every generated
7
+ * primary key on every entity.
8
+ *
9
+ * At the wire, for the reason `pgNumericTypes` gives: everything crosses it exactly once - entity
10
+ * reads, the ids an `OUTPUT` reports, raw SQL, counts, aggregates - where the ORM's own hydration
11
+ * only ever sees entity reads.
12
+ *
13
+ * Exact to 2^53, which covers any generated id. A value past it keeps the string the driver gave,
14
+ * because a number could no longer represent it: that is the one case where handing back the exact
15
+ * text is more useful than handing back the type that was asked for. The lighter escape hatch for a
16
+ * column that big is the declaration - `@Field({ type: String, columnType: 'bigint' })`.
17
+ */
18
+ export function decodeWireTypes(rows, columns) {
19
+ if (!rows?.length || !columns) {
20
+ return rows ?? [];
21
+ }
22
+ const wide = Object.keys(columns).filter((name) => typeName(columns[name]?.type) === 'BigInt');
23
+ if (!wide.length) {
24
+ // The overwhelmingly common case, so an ordinary read copies nothing.
25
+ return rows;
26
+ }
27
+ return rows.map((row) => {
28
+ const decoded = { ...row };
29
+ for (const name of wide) {
30
+ const value = decoded[name];
31
+ if (typeof value === 'string') {
32
+ const asNumber = Number(value);
33
+ if (Number.isSafeInteger(asNumber)) {
34
+ decoded[name] = asNumber;
35
+ }
36
+ }
37
+ }
38
+ return decoded;
39
+ });
40
+ }
41
+ /** `tedious` reports a column's type as its factory function, whose `name` is the type's own. */
42
+ function typeName(type) {
43
+ return typeof type === 'function' ? type.name : undefined;
44
+ }
@@ -21,8 +21,8 @@ export type PgliteDatabase = {
21
21
  * Querier for PGlite, Postgres compiled to WASM and run in this process.
22
22
  *
23
23
  * @remarks Extends {@link AbstractSqlQuerier} rather than `AbstractPgQuerier`, whose `internalStream`
24
- * hands a `pg-query-stream` object to `query()`: PGlite has no cursor API, so streaming falls back to
25
- * the base class buffering the whole result. `BEGIN`/`COMMIT` are plain statements on the single
24
+ * hands a `pg-query-stream` object to `query()`, which PGlite's client has no equivalent of - so
25
+ * streaming pages the rows in SQL instead. `BEGIN`/`COMMIT` are plain statements on the single
26
26
  * connection, leaving transactions to the base class.
27
27
  */
28
28
  export declare class PgliteQuerier extends AbstractSqlQuerier {
@@ -31,6 +31,8 @@ export declare class PgliteQuerier extends AbstractSqlQuerier {
31
31
  constructor(db: PgliteDatabase, dialect: PgliteDialect, extra?: ExtraOptions | undefined);
32
32
  internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
33
33
  internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
34
+ /** Postgres compiled to WASM is still Postgres: `DECLARE`/`FETCH` streams what the client cannot. */
35
+ protected internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, any>;
34
36
  /** The handle belongs to the pool, which hands out one querier per unit of work over it. */
35
37
  internalRelease(): Promise<void>;
36
38
  }
@@ -1,10 +1,11 @@
1
+ import { streamViaCursor } from '../postgres/pgCursorStream.js';
1
2
  import { AbstractSqlQuerier } from '../querier/index.js';
2
3
  /**
3
4
  * Querier for PGlite, Postgres compiled to WASM and run in this process.
4
5
  *
5
6
  * @remarks Extends {@link AbstractSqlQuerier} rather than `AbstractPgQuerier`, whose `internalStream`
6
- * hands a `pg-query-stream` object to `query()`: PGlite has no cursor API, so streaming falls back to
7
- * the base class buffering the whole result. `BEGIN`/`COMMIT` are plain statements on the single
7
+ * hands a `pg-query-stream` object to `query()`, which PGlite's client has no equivalent of - so
8
+ * streaming pages the rows in SQL instead. `BEGIN`/`COMMIT` are plain statements on the single
8
9
  * connection, leaving transactions to the base class.
9
10
  */
10
11
  export class PgliteQuerier extends AbstractSqlQuerier {
@@ -25,6 +26,10 @@ export class PgliteQuerier extends AbstractSqlQuerier {
25
26
  // where the latter also counts a `SELECT`'s rows and is absent altogether from a DDL tag.
26
27
  return this.buildUpdateResult({ rows: res.rows, changes: res.affectedRows ?? 0 });
27
28
  }
29
+ /** Postgres compiled to WASM is still Postgres: `DECLARE`/`FETCH` streams what the client cannot. */
30
+ async *internalStream(query, values) {
31
+ yield* streamViaCursor((sql, params) => this.internalAll(sql, params), query, values, this.hasOpenTransaction);
32
+ }
28
33
  /** The handle belongs to the pool, which hands out one querier per unit of work over it. */
29
34
  async internalRelease() { }
30
35
  }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Runs one statement of the cursor protocol. `internalAll` for every caller so far: the cursor holds
3
+ * a connection's session state, so it must be the querier's own connection, and it must not go
4
+ * through `all()`, whose `serialize` is not re-entrant.
5
+ */
6
+ export type CursorExecutor<T> = (query: string, values?: unknown[]) => Promise<T[]>;
7
+ /**
8
+ * Stream a Postgres-wire result through a server-side cursor, for a driver whose client exposes none:
9
+ * `bun:sql` (no cursor API at all, [oven-sh/bun#17181](https://github.com/oven-sh/bun/issues/17181))
10
+ * and PGlite. `pg` has `pg-query-stream` and keeps using it.
11
+ *
12
+ * `DECLARE` is only legal inside a transaction, so one is opened here when the caller has none - and
13
+ * then committed, or rolled back if the stream failed. That `BEGIN` goes straight to the connection
14
+ * rather than through `beginTransaction`, so the querier's own transaction state stays untouched:
15
+ * this one is the generator's, and ends with it.
16
+ *
17
+ * The cleanup lives in `finally` because a consumer that stops early (`break`, a `throw` downstream)
18
+ * ends the generator there and nowhere else, and an abandoned cursor holds its transaction open.
19
+ */
20
+ export declare function streamViaCursor<T>(exec: CursorExecutor<T>, query: string, values?: unknown[], inTransaction?: boolean): AsyncIterable<T>;
@@ -0,0 +1,49 @@
1
+ /** The cursor a stream declares. Suffixed per call, so two streams on one connection cannot collide. */
2
+ const CURSOR_ALIAS = '_uql_cursor';
3
+ /** Rows per round trip, matching `pg-query-stream`'s own default so both paths read the same. */
4
+ const FETCH_SIZE = 100;
5
+ let cursorSeq = 0;
6
+ /**
7
+ * Stream a Postgres-wire result through a server-side cursor, for a driver whose client exposes none:
8
+ * `bun:sql` (no cursor API at all, [oven-sh/bun#17181](https://github.com/oven-sh/bun/issues/17181))
9
+ * and PGlite. `pg` has `pg-query-stream` and keeps using it.
10
+ *
11
+ * `DECLARE` is only legal inside a transaction, so one is opened here when the caller has none - and
12
+ * then committed, or rolled back if the stream failed. That `BEGIN` goes straight to the connection
13
+ * rather than through `beginTransaction`, so the querier's own transaction state stays untouched:
14
+ * this one is the generator's, and ends with it.
15
+ *
16
+ * The cleanup lives in `finally` because a consumer that stops early (`break`, a `throw` downstream)
17
+ * ends the generator there and nowhere else, and an abandoned cursor holds its transaction open.
18
+ */
19
+ export async function* streamViaCursor(exec, query, values, inTransaction = false) {
20
+ const cursor = `${CURSOR_ALIAS}_${++cursorSeq}`;
21
+ const ownsTransaction = !inTransaction;
22
+ if (ownsTransaction) {
23
+ await exec('BEGIN');
24
+ }
25
+ let failed = false;
26
+ try {
27
+ await exec(`DECLARE ${cursor} CURSOR FOR ${query}`, values);
28
+ for (;;) {
29
+ const rows = await exec(`FETCH FORWARD ${FETCH_SIZE} FROM ${cursor}`);
30
+ yield* rows;
31
+ if (rows.length < FETCH_SIZE) {
32
+ return;
33
+ }
34
+ }
35
+ }
36
+ catch (err) {
37
+ failed = true;
38
+ throw err;
39
+ }
40
+ finally {
41
+ // Best-effort once the stream has failed: an error raised here would replace the one that brought
42
+ // us here, which is the one worth reporting, and the rollback discards the cursor either way.
43
+ const end = failed ? (sql) => exec(sql).catch(() => []) : exec;
44
+ await end(`CLOSE ${cursor}`);
45
+ if (ownsTransaction) {
46
+ await end(failed ? 'ROLLBACK' : 'COMMIT');
47
+ }
48
+ }
49
+ }
@@ -4,7 +4,7 @@ import { PostgresDialect } from './postgresDialect.js';
4
4
  *
5
5
  * @remarks Uses base {@link PostgresDialect} capabilities: native JS arrays for `ANY` / `ALL`
6
6
  * (`nativeArrays: true`) and `$n::jsonb` without a text re-cast. Bun SQL Postgres needs
7
- * `BunSqlPostgresDialect` from `uql-orm/bunSql` instead (wire array literals + text json cast).
7
+ * `BunSqlQuerierPool` from `uql-orm/bunSql` instead (wire array literals + text json cast).
8
8
  */
9
9
  export declare class PgDialect extends PostgresDialect {
10
10
  }
@@ -4,7 +4,7 @@ import { PostgresDialect } from './postgresDialect.js';
4
4
  *
5
5
  * @remarks Uses base {@link PostgresDialect} capabilities: native JS arrays for `ANY` / `ALL`
6
6
  * (`nativeArrays: true`) and `$n::jsonb` without a text re-cast. Bun SQL Postgres needs
7
- * `BunSqlPostgresDialect` from `uql-orm/bunSql` instead (wire array literals + text json cast).
7
+ * `BunSqlQuerierPool` from `uql-orm/bunSql` instead (wire array literals + text json cast).
8
8
  */
9
9
  export class PgDialect extends PostgresDialect {
10
10
  }
@@ -1,19 +1,21 @@
1
1
  /**
2
- * Wire-style parameter shaping for **Bun SQL** (and similar clients) on any Postgres-wire dialect:
3
- * arrays are sent as string literals (`nativeArrays: false`) via {@link PgLikeSqlDialect}'s
4
- * `toPgArray` path.
2
+ * Driver-shaped parameter handling for **Bun SQL** (and any client like it) on a Postgres-wire
3
+ * dialect: arrays go as string literals (`nativeArrays: false`, {@link PgLikeSqlDialect}'s `toPgArray`
4
+ * path) and a JSON bind is re-cast through text (`explicitJsonCast: true`). Both are measured, on a
5
+ * live server: `bun:sql` binds neither `sql.array(...)` nor a plain JS array through `unsafe()`
6
+ * (verified again on Bun 1.4.2), and without the text re-cast a `$set`/`$push` on a JSONB column
7
+ * silently writes the wrong value or throws - on Postgres and, identically, on CockroachDB, which
8
+ * `bun:sql` reaches through its own Postgres wire implementation.
5
9
  *
6
- * `PgDialect` does **not** use this constant - it keeps base {@link PgLikeSqlDialect} defaults
7
- * (`nativeArrays: true`, `explicitJsonCast: false`), since node-`pg` doesn't need the fix.
8
- * `BunSqlPostgresDialect` and `BunSqlCockroachDialect` both spread this and set
9
- * `explicitJsonCast: true` - `bun:sql` routes CockroachDB through its own Postgres wire-protocol
10
- * implementation (see `bunSql.util.ts#normalizeBunOpts`), so it needs the identical fix: verified
11
- * directly that without it, `$set`/`$push` on a JSONB column silently produce the wrong value
12
- * or throw on a live CockroachDB instance.
10
+ * The pair is one constant because it is one driver's shape, and `BunSqlQuerierPool` hands it to
11
+ * `PostgresDialect`/`CockroachDialect` as their `driverCapabilities` rather than subclassing either:
12
+ * Bun changes how a parameter binds, never the SQL. `PgDialect` uses neither, keeping the base
13
+ * {@link PgLikeSqlDialect} defaults, since node-`pg` needs no fix.
13
14
  *
14
15
  * @remarks Optional import for custom pools. Neon uses its own serverless driver (not `bun:sql`),
15
16
  * so `NeonDialect` is a separate, unverified case - do not assume it needs this without testing.
16
17
  */
17
18
  export declare const POSTGRES_WIRE_DRIVER_CAPABILITIES: {
18
19
  readonly nativeArrays: false;
20
+ readonly explicitJsonCast: true;
19
21
  };
@@ -1,19 +1,21 @@
1
1
  /**
2
- * Wire-style parameter shaping for **Bun SQL** (and similar clients) on any Postgres-wire dialect:
3
- * arrays are sent as string literals (`nativeArrays: false`) via {@link PgLikeSqlDialect}'s
4
- * `toPgArray` path.
2
+ * Driver-shaped parameter handling for **Bun SQL** (and any client like it) on a Postgres-wire
3
+ * dialect: arrays go as string literals (`nativeArrays: false`, {@link PgLikeSqlDialect}'s `toPgArray`
4
+ * path) and a JSON bind is re-cast through text (`explicitJsonCast: true`). Both are measured, on a
5
+ * live server: `bun:sql` binds neither `sql.array(...)` nor a plain JS array through `unsafe()`
6
+ * (verified again on Bun 1.4.2), and without the text re-cast a `$set`/`$push` on a JSONB column
7
+ * silently writes the wrong value or throws - on Postgres and, identically, on CockroachDB, which
8
+ * `bun:sql` reaches through its own Postgres wire implementation.
5
9
  *
6
- * `PgDialect` does **not** use this constant - it keeps base {@link PgLikeSqlDialect} defaults
7
- * (`nativeArrays: true`, `explicitJsonCast: false`), since node-`pg` doesn't need the fix.
8
- * `BunSqlPostgresDialect` and `BunSqlCockroachDialect` both spread this and set
9
- * `explicitJsonCast: true` - `bun:sql` routes CockroachDB through its own Postgres wire-protocol
10
- * implementation (see `bunSql.util.ts#normalizeBunOpts`), so it needs the identical fix: verified
11
- * directly that without it, `$set`/`$push` on a JSONB column silently produce the wrong value
12
- * or throw on a live CockroachDB instance.
10
+ * The pair is one constant because it is one driver's shape, and `BunSqlQuerierPool` hands it to
11
+ * `PostgresDialect`/`CockroachDialect` as their `driverCapabilities` rather than subclassing either:
12
+ * Bun changes how a parameter binds, never the SQL. `PgDialect` uses neither, keeping the base
13
+ * {@link PgLikeSqlDialect} defaults, since node-`pg` needs no fix.
13
14
  *
14
15
  * @remarks Optional import for custom pools. Neon uses its own serverless driver (not `bun:sql`),
15
16
  * so `NeonDialect` is a separate, unverified case - do not assume it needs this without testing.
16
17
  */
17
18
  export const POSTGRES_WIRE_DRIVER_CAPABILITIES = {
18
19
  nativeArrays: false,
20
+ explicitJsonCast: true,
19
21
  };
@@ -1,9 +1,5 @@
1
- import type { EntityData, EntityId, ExtraOptions, FieldKey, IdValue, Querier, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryPopulate, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpdateResult, RawRow, RelationKey, TransactionOptions, Type, UpdatePayload, WrittenId } from '../type/index.js';
1
+ import type { EntityData, EntityId, ExtraOptions, FieldKey, IdValue, Querier, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryPopulate, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpdateResult, QueryUpsertOneResult, QueryUpsertManyResult, RawRow, RelationKey, TransactionOptions, Type, UpdatePayload, WrittenId } from '../type/index.js';
2
2
  import { LoggerWrapper, type ParentJoin, type ParentPartition } from '../util/index.js';
3
- /**
4
- * Base class for all database queriers.
5
- * It provides a standardized way to execute tasks serially to prevent race conditions on database connections.
6
- */
7
3
  export declare abstract class AbstractQuerier implements Querier {
8
4
  readonly extra?: ExtraOptions | undefined;
9
5
  /**
@@ -129,8 +125,8 @@ export declare abstract class AbstractQuerier implements Querier {
129
125
  * but the upsert itself is a fact known before and after, and a row written with no hook at all
130
126
  * was how an `@Id({ onInsert })` or an audit trail silently skipped this path.
131
127
  */
132
- upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
133
- upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
128
+ upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpsertOneResult<E>>;
129
+ upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpsertManyResult<E>>;
134
130
  protected abstract internalUpsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
135
131
  protected abstract internalUpsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
136
132
  deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;
@@ -43,6 +43,19 @@ function soleParentColumn(relOpts) {
43
43
  * Base class for all database queriers.
44
44
  * It provides a standardized way to execute tasks serially to prevent race conditions on database connections.
45
45
  */
46
+ /**
47
+ * The ids an upsert reports, payload-aligned so the result zips with the rows that were passed.
48
+ *
49
+ * A composite is named from the payload, as an insert's is: no column holds that key, so no
50
+ * statement reports one. A sole key takes what the statement reported when it spoke for every row,
51
+ * and otherwise falls back to the key the caller supplied - a `firstId` dialect reports nothing for
52
+ * a batch, which is not the same as those rows having no id.
53
+ */
54
+ function upsertIds(meta, payload, reported) {
55
+ return meta.ids.length === 1 && reported?.length === payload.length
56
+ ? reported
57
+ : payload.map((it) => (namesKey(meta, it) ? idOf(meta, it) : undefined));
58
+ }
46
59
  export class AbstractQuerier {
47
60
  extra;
48
61
  /**
@@ -207,10 +220,17 @@ export class AbstractQuerier {
207
220
  * was how an `@Id({ onInsert })` or an audit trail silently skipped this path.
208
221
  */
209
222
  async upsertOne(entity, conflictPaths, payload) {
210
- return this.hooked(entity, 'Upsert', [payload], () => this.internalUpsertOne(entity, conflictPaths, payload));
223
+ return this.hooked(entity, 'Upsert', [payload], async () => {
224
+ const { ids, changes, created } = await this.internalUpsertOne(entity, conflictPaths, payload);
225
+ const [id] = upsertIds(getMeta(entity), [payload], ids);
226
+ return { id, changes, created };
227
+ });
211
228
  }
212
229
  async upsertMany(entity, conflictPaths, payload) {
213
- return this.hooked(entity, 'Upsert', payload, () => this.internalUpsertMany(entity, conflictPaths, payload));
230
+ return this.hooked(entity, 'Upsert', payload, async () => {
231
+ const { ids, changes } = await this.internalUpsertMany(entity, conflictPaths, payload);
232
+ return { ids: upsertIds(getMeta(entity), payload, ids), changes };
233
+ });
214
234
  }
215
235
  async deleteOneById(entity, id, opts) {
216
236
  assertIdValue(entity, id);
@@ -1,5 +1,5 @@
1
1
  import type { AbstractDialect } from '../dialect/index.js';
2
- import type { EntityData, EntityId, ExtraOptions, FieldKey, PoolRunOptions, Querier, QuerierPool, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpdateResult, RelationKey, TransactionOptions, Type, UpdatePayload, WrittenId } from '../type/index.js';
2
+ import type { EntityData, EntityId, ExtraOptions, FieldKey, PoolRunOptions, Querier, QuerierPool, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpsertOneResult, QueryUpsertManyResult, RelationKey, TransactionOptions, Type, UpdatePayload, WrittenId } from '../type/index.js';
3
3
  /**
4
4
  * Base pool: dialect id and behavior come only from the `dialect` instance (see {@link QuerierPool}).
5
5
  */
@@ -41,8 +41,8 @@ export declare abstract class AbstractQuerierPool<Q extends Querier, D extends A
41
41
  insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
42
42
  updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
43
43
  updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
44
- upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
45
- upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
44
+ upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpsertOneResult<E>>;
45
+ upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpsertManyResult<E>>;
46
46
  saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<WrittenId<E> | undefined>;
47
47
  saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
48
48
  deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;