uql-orm 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/README.md +2 -2
  2. package/dist/browser/uql-browser.min.js.map +2 -2
  3. package/dist/bunSql/bunSql.util.d.ts +3 -14
  4. package/dist/bunSql/bunSql.util.js +33 -56
  5. package/dist/bunSql/bunSqlQuerier.d.ts +3 -6
  6. package/dist/bunSql/bunSqlQuerier.js +7 -13
  7. package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -5
  8. package/dist/bunSql/bunSqlQuerierPool.js +25 -10
  9. package/dist/cockroachdb/cockroachDialect.d.ts +2 -5
  10. package/dist/cockroachdb/cockroachDialect.js +2 -5
  11. package/dist/cockroachdb/crdbQuerierPool.d.ts +1 -3
  12. package/dist/cockroachdb/crdbQuerierPool.js +0 -4
  13. package/dist/cockroachdb/index.d.ts +0 -1
  14. package/dist/cockroachdb/index.js +0 -1
  15. package/dist/d1/d1SqliteDialect.d.ts +5 -0
  16. package/dist/d1/d1SqliteDialect.js +7 -0
  17. package/dist/dialect/abstractSqlDialect.d.ts +15 -9
  18. package/dist/dialect/abstractSqlDialect.js +47 -54
  19. package/dist/dialect/hydrateColumn.js +2 -2
  20. package/dist/dialect/mergeSqlDialect.d.ts +2 -2
  21. package/dist/dialect/mergeSqlDialect.js +0 -4
  22. package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -3
  23. package/dist/dialect/mysqlLikeSqlDialect.js +0 -3
  24. package/dist/dialect/pgLikeSqlDialect.d.ts +5 -0
  25. package/dist/dialect/pgLikeSqlDialect.js +12 -2
  26. package/dist/entity/decorator/members.d.ts +3 -10
  27. package/dist/entity/index.d.ts +1 -1
  28. package/dist/entity/index.js +1 -1
  29. package/dist/entity/metadata/definition.d.ts +3 -1
  30. package/dist/entity/metadata/definition.js +8 -4
  31. package/dist/libsql/index.d.ts +0 -1
  32. package/dist/libsql/index.js +0 -1
  33. package/dist/libsql/libsqlQuerierPool.d.ts +3 -5
  34. package/dist/libsql/libsqlQuerierPool.js +2 -5
  35. package/dist/maria/mariadbQuerier.d.ts +0 -3
  36. package/dist/maria/mariadbQuerier.js +6 -7
  37. package/dist/maria/mariadbQuerierPool.js +4 -7
  38. package/dist/migrate/builder/migrationBuilder.js +0 -4
  39. package/dist/migrate/ddl/index.d.ts +3 -3
  40. package/dist/migrate/ddl/index.js +3 -3
  41. package/dist/migrate/migrator.d.ts +3 -7
  42. package/dist/migrate/migrator.js +3 -7
  43. package/dist/migrate/schemaGenerator.d.ts +0 -13
  44. package/dist/migrate/schemaGenerator.js +0 -13
  45. package/dist/migrate/storage/databaseStorage.d.ts +2 -2
  46. package/dist/migrate/storage/databaseStorage.js +2 -2
  47. package/dist/mongo/index.d.ts +0 -1
  48. package/dist/mongo/index.js +0 -1
  49. package/dist/mongo/mongoDialect.d.ts +0 -1
  50. package/dist/mongo/mongoDialect.js +3 -14
  51. package/dist/mongo/mongodbQuerier.d.ts +3 -5
  52. package/dist/mongo/mongodbQuerier.js +23 -23
  53. package/dist/mongo/mongodbQuerierPool.d.ts +2 -2
  54. package/dist/mongo/mongodbQuerierPool.js +2 -2
  55. package/dist/mssql/mssqlDialect.d.ts +5 -5
  56. package/dist/mssql/mssqlDialect.js +11 -8
  57. package/dist/mssql/mssqlQuerier.d.ts +13 -6
  58. package/dist/mssql/mssqlQuerier.js +30 -75
  59. package/dist/mssql/mssqlQuerierPool.js +2 -0
  60. package/dist/mssql/mssqlWireTypes.d.ts +3 -4
  61. package/dist/mssql/mssqlWireTypes.js +5 -8
  62. package/dist/mysql/index.d.ts +0 -1
  63. package/dist/mysql/index.js +0 -1
  64. package/dist/mysql/mysql2Querier.d.ts +1 -4
  65. package/dist/mysql/mysql2Querier.js +0 -3
  66. package/dist/mysql/mysql2QuerierPool.d.ts +2 -2
  67. package/dist/mysql/mysql2QuerierPool.js +5 -3
  68. package/dist/neon/index.d.ts +0 -2
  69. package/dist/neon/index.js +0 -2
  70. package/dist/neon/neonQuerierPool.d.ts +2 -4
  71. package/dist/neon/neonQuerierPool.js +2 -6
  72. package/dist/pglite/index.d.ts +0 -1
  73. package/dist/pglite/index.js +0 -1
  74. package/dist/pglite/pgliteQuerier.d.ts +3 -3
  75. package/dist/pglite/pgliteQuerier.js +1 -1
  76. package/dist/pglite/pgliteQuerierPool.d.ts +7 -2
  77. package/dist/pglite/pgliteQuerierPool.js +16 -6
  78. package/dist/postgres/abstractPgQuerierPool.d.ts +8 -12
  79. package/dist/postgres/abstractPgQuerierPool.js +8 -6
  80. package/dist/postgres/index.d.ts +0 -1
  81. package/dist/postgres/index.js +0 -1
  82. package/dist/postgres/pgNumericTypes.d.ts +5 -5
  83. package/dist/postgres/pgNumericTypes.js +11 -7
  84. package/dist/postgres/pgQuerier.d.ts +22 -4
  85. package/dist/postgres/pgQuerier.js +29 -2
  86. package/dist/postgres/pgQuerierPool.d.ts +2 -4
  87. package/dist/postgres/pgQuerierPool.js +2 -6
  88. package/dist/postgres/postgresDialect.d.ts +5 -5
  89. package/dist/postgres/postgresDialect.js +5 -5
  90. package/dist/postgres/postgresWireDriverCapabilities.d.ts +2 -2
  91. package/dist/postgres/postgresWireDriverCapabilities.js +2 -2
  92. package/dist/querier/abstractPoolQuerier.d.ts +1 -1
  93. package/dist/querier/abstractPoolQuerier.js +1 -1
  94. package/dist/querier/abstractQuerier.d.ts +17 -22
  95. package/dist/querier/abstractQuerier.js +80 -57
  96. package/dist/querier/abstractSqlQuerier.d.ts +20 -13
  97. package/dist/querier/abstractSqlQuerier.js +96 -100
  98. package/dist/schema/schemaASTBuilder.js +7 -7
  99. package/dist/sqlite/hranaQuerier.d.ts +6 -8
  100. package/dist/sqlite/hranaQuerier.js +13 -30
  101. package/dist/sqlite/hranaQuerierPool.d.ts +3 -4
  102. package/dist/sqlite/hranaQuerierPool.js +2 -1
  103. package/dist/sqlite/localSqliteQuerierPool.d.ts +3 -3
  104. package/dist/sqlite/localSqliteQuerierPool.js +4 -2
  105. package/dist/sqlite/nodeSqliteAdapter.d.ts +0 -1
  106. package/dist/sqlite/nodeSqliteQuerierPool.js +0 -2
  107. package/dist/sqlite/sqlitePragmas.d.ts +12 -0
  108. package/dist/sqlite/sqlitePragmas.js +15 -0
  109. package/dist/sqlite/sqliteQuerierPool.d.ts +0 -5
  110. package/dist/sqlite/sqliteQuerierPool.js +2 -13
  111. package/dist/turso/index.d.ts +0 -1
  112. package/dist/turso/index.js +0 -1
  113. package/dist/turso/tursoLocalQuerier.d.ts +0 -1
  114. package/dist/turso/tursoLocalQuerierPool.js +2 -2
  115. package/dist/turso/tursoQuerierPool.d.ts +1 -3
  116. package/dist/turso/tursoQuerierPool.js +0 -4
  117. package/dist/type/dialect.d.ts +1 -1
  118. package/dist/type/entity.d.ts +6 -11
  119. package/dist/type/migration.d.ts +0 -3
  120. package/dist/type/query.d.ts +12 -12
  121. package/dist/type/query.js +0 -6
  122. package/dist/type/universalQuerier.d.ts +3 -3
  123. package/dist/util/dialect.util.d.ts +4 -3
  124. package/dist/util/dialect.util.js +2 -1
  125. package/dist/util/field.util.d.ts +4 -16
  126. package/dist/util/field.util.js +6 -19
  127. package/dist/util/fieldOption.util.d.ts +1 -4
  128. package/dist/util/fieldOption.util.js +0 -2
  129. package/dist/util/logger.d.ts +10 -11
  130. package/dist/util/logger.js +21 -11
  131. package/dist/util/raw.d.ts +3 -10
  132. package/dist/util/raw.js +3 -3
  133. package/dist/util/sql.util.js +2 -2
  134. package/dist/util/sqlLiteral.js +3 -8
  135. package/dist/util/string.util.js +2 -6
  136. package/dist/util/wideNumber.d.ts +14 -0
  137. package/dist/util/wideNumber.js +24 -0
  138. package/package.json +1 -1
  139. package/dist/cockroachdb/crdbQuerier.d.ts +0 -8
  140. package/dist/cockroachdb/crdbQuerier.js +0 -6
  141. package/dist/libsql/libsqlQuerier.d.ts +0 -10
  142. package/dist/libsql/libsqlQuerier.js +0 -10
  143. package/dist/mongo/mongodbNativeDialect.d.ts +0 -9
  144. package/dist/mongo/mongodbNativeDialect.js +0 -9
  145. package/dist/mysql/mysql2Dialect.d.ts +0 -9
  146. package/dist/mysql/mysql2Dialect.js +0 -9
  147. package/dist/neon/neonDialect.d.ts +0 -10
  148. package/dist/neon/neonDialect.js +0 -10
  149. package/dist/neon/neonQuerier.d.ts +0 -5
  150. package/dist/neon/neonQuerier.js +0 -3
  151. package/dist/pglite/pgliteDialect.d.ts +0 -14
  152. package/dist/pglite/pgliteDialect.js +0 -14
  153. package/dist/postgres/abstractPgQuerier.d.ts +0 -24
  154. package/dist/postgres/abstractPgQuerier.js +0 -32
  155. package/dist/postgres/pgDialect.d.ts +0 -10
  156. package/dist/postgres/pgDialect.js +0 -10
  157. package/dist/turso/tursoQuerier.d.ts +0 -10
  158. package/dist/turso/tursoQuerier.js +0 -10
@@ -1,3 +1,30 @@
1
- import { AbstractPgQuerier } from './abstractPgQuerier.js';
2
- export class PgQuerier extends AbstractPgQuerier {
1
+ import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
2
+ /**
3
+ * Querier for every client with node-postgres' API: `pg` itself, for Postgres and CockroachDB, and
4
+ * Neon's serverless driver. Generic over the client alone - the dialect is whichever the pool built.
5
+ */
6
+ export class PgQuerier extends AbstractPoolQuerier {
7
+ async internalAll(query, values) {
8
+ const res = await this.getConn().query(query, values);
9
+ return res.rows;
10
+ }
11
+ async internalRun(query, values) {
12
+ const res = await this.getConn().query(query, values);
13
+ return this.buildUpdateResult({ rows: res.rows, changes: res.rowCount ?? 0 });
14
+ }
15
+ async *internalStream(query, values) {
16
+ const { default: QueryStream } = await import('pg-query-stream');
17
+ const stream = this.getConn().query(new QueryStream(query, values));
18
+ try {
19
+ for await (const row of stream) {
20
+ yield row;
21
+ }
22
+ }
23
+ finally {
24
+ stream.destroy();
25
+ }
26
+ }
27
+ async releaseConn(conn, discard) {
28
+ await conn.release(discard);
29
+ }
3
30
  }
@@ -1,10 +1,8 @@
1
1
  import { Pool, type PoolClient, type PoolConfig } from 'pg';
2
2
  import type { ExtraOptions } from '../type/index.js';
3
3
  import { AbstractPgQuerierPool } from './abstractPgQuerierPool.js';
4
- import { PgDialect } from './pgDialect.js';
5
- import { PgQuerier } from './pgQuerier.js';
6
- export declare class PgQuerierPool extends AbstractPgQuerierPool<PoolClient, PgQuerier, PgDialect> {
4
+ import { PostgresDialect } from './postgresDialect.js';
5
+ export declare class PgQuerierPool extends AbstractPgQuerierPool<PoolClient, PostgresDialect> {
7
6
  readonly pool: Pool;
8
7
  constructor(opts: PoolConfig, extra?: ExtraOptions);
9
- protected buildQuerier(connect: () => Promise<PoolClient>): PgQuerier;
10
8
  }
@@ -1,16 +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 { PgDialect } from './pgDialect.js';
5
4
  import { numericTypes } from './pgNumericTypes.js';
6
- import { PgQuerier } from './pgQuerier.js';
5
+ import { PostgresDialect } from './postgresDialect.js';
7
6
  export class PgQuerierPool extends AbstractPgQuerierPool {
8
7
  constructor(opts, extra) {
9
8
  // keepAlive reduces (but can't eliminate) idle connections being silently
10
9
  // dropped by NATs/firewalls on long-lived remote connections.
11
- super(new PgDialect(dialectOptionsFrom(extra)), new Pool({ keepAlive: true, types: numericTypes(types), ...opts }), extra);
12
- }
13
- buildQuerier(connect) {
14
- return new PgQuerier(connect, this.dialect, this.extra);
10
+ super(new PostgresDialect(dialectOptionsFrom(extra)), new Pool({ keepAlive: true, types: numericTypes(types), ...opts }), extra);
15
11
  }
16
12
  }
@@ -1,11 +1,11 @@
1
1
  import { PgLikeSqlDialect } from '../dialect/pgLikeSqlDialect.js';
2
2
  import type { QueryConflictPaths, QueryContext, SqlDialectName, Type } from '../type/index.js';
3
3
  /**
4
- * PostgreSQL dialect. For node-pg use PgDialect. Neon, Bun SQL, and Cockroach use driver-specific
5
- * subclasses. Shared Postgres-wire AST/quoting/JSONB/full-text-search/vector-search logic
6
- * (including BIGINT IDENTITY PKs) lives in {@link PgLikeSqlDialect}; this class adds what's
7
- * Postgres-only: the `vector` extension requirement, pgvector's index syntax, and `xmax`-based
8
- * upsert `created` detection.
4
+ * PostgreSQL dialect, the same class under every Postgres driver - `pg`, Neon, PGlite, `bun:sql` -
5
+ * where a driver that binds differently passes `driverCapabilities` rather than subclassing it.
6
+ * Shared Postgres-wire AST/quoting/JSONB/full-text-search/vector-search logic (including BIGINT
7
+ * IDENTITY PKs) lives in {@link PgLikeSqlDialect}; this class adds what's Postgres-only: the `vector`
8
+ * extension requirement, pgvector's index syntax, and `xmax`-based upsert `created` detection.
9
9
  */
10
10
  export declare class PostgresDialect extends PgLikeSqlDialect {
11
11
  readonly dialectName: SqlDialectName;
@@ -2,11 +2,11 @@ import { COUNT_ALIAS } from '../dialect/aliases.js';
2
2
  import { PgLikeSqlDialect } from '../dialect/pgLikeSqlDialect.js';
3
3
  import { getMeta } from '../entity/index.js';
4
4
  /**
5
- * PostgreSQL dialect. For node-pg use PgDialect. Neon, Bun SQL, and Cockroach use driver-specific
6
- * subclasses. Shared Postgres-wire AST/quoting/JSONB/full-text-search/vector-search logic
7
- * (including BIGINT IDENTITY PKs) lives in {@link PgLikeSqlDialect}; this class adds what's
8
- * Postgres-only: the `vector` extension requirement, pgvector's index syntax, and `xmax`-based
9
- * upsert `created` detection.
5
+ * PostgreSQL dialect, the same class under every Postgres driver - `pg`, Neon, PGlite, `bun:sql` -
6
+ * where a driver that binds differently passes `driverCapabilities` rather than subclassing it.
7
+ * Shared Postgres-wire AST/quoting/JSONB/full-text-search/vector-search logic (including BIGINT
8
+ * IDENTITY PKs) lives in {@link PgLikeSqlDialect}; this class adds what's Postgres-only: the `vector`
9
+ * extension requirement, pgvector's index syntax, and `xmax`-based upsert `created` detection.
10
10
  */
11
11
  export class PostgresDialect extends PgLikeSqlDialect {
12
12
  dialectName = 'postgres';
@@ -9,11 +9,11 @@
9
9
  *
10
10
  * The pair is one constant because it is one driver's shape, and `BunSqlQuerierPool` hands it to
11
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
12
+ * Bun changes how a parameter binds, never the SQL. `PgQuerierPool` uses neither, keeping the base
13
13
  * {@link PgLikeSqlDialect} defaults, since node-`pg` needs no fix.
14
14
  *
15
15
  * @remarks Optional import for custom pools. Neon uses its own serverless driver (not `bun:sql`),
16
- * so `NeonDialect` is a separate, unverified case - do not assume it needs this without testing.
16
+ * so `NeonQuerierPool` is a separate, unverified case - do not assume it needs this without testing.
17
17
  */
18
18
  export declare const POSTGRES_WIRE_DRIVER_CAPABILITIES: {
19
19
  readonly nativeArrays: false;
@@ -9,11 +9,11 @@
9
9
  *
10
10
  * The pair is one constant because it is one driver's shape, and `BunSqlQuerierPool` hands it to
11
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
12
+ * Bun changes how a parameter binds, never the SQL. `PgQuerierPool` uses neither, keeping the base
13
13
  * {@link PgLikeSqlDialect} defaults, since node-`pg` needs no fix.
14
14
  *
15
15
  * @remarks Optional import for custom pools. Neon uses its own serverless driver (not `bun:sql`),
16
- * so `NeonDialect` is a separate, unverified case - do not assume it needs this without testing.
16
+ * so `NeonQuerierPool` is a separate, unverified case - do not assume it needs this without testing.
17
17
  */
18
18
  export const POSTGRES_WIRE_DRIVER_CAPABILITIES = {
19
19
  nativeArrays: false,
@@ -6,7 +6,7 @@ export declare abstract class AbstractPoolQuerier<C> extends AbstractSqlQuerier
6
6
  readonly extra?: ExtraOptions | undefined;
7
7
  protected conn: C | undefined;
8
8
  protected getConn(): C;
9
- constructor(dialect: AbstractSqlDialect, connect: () => Promise<C>, extra?: ExtraOptions | undefined);
9
+ constructor(connect: () => Promise<C>, dialect: AbstractSqlDialect, extra?: ExtraOptions | undefined);
10
10
  protected lazyConnect(): Promise<void>;
11
11
  internalRelease(discard: boolean): Promise<void>;
12
12
  protected abstract releaseConn(conn: C, discard: boolean): Promise<void>;
@@ -8,7 +8,7 @@ export class AbstractPoolQuerier extends AbstractSqlQuerier {
8
8
  throw new TypeError('pool querier not connected');
9
9
  return this.conn;
10
10
  }
11
- constructor(dialect, connect, extra) {
11
+ constructor(connect, dialect, extra) {
12
12
  super(dialect, extra);
13
13
  this.connect = connect;
14
14
  this.extra = extra;
@@ -1,5 +1,6 @@
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';
1
+ import type { EntityData, EntityId, ExtraOptions, FieldKey, IdValue, Querier, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryPopulate, QueryProjected, QuerySearch, QueryStreamProjected, PrimaryKey, 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
+ /** Base class for all database queriers. */
3
4
  export declare abstract class AbstractQuerier implements Querier {
4
5
  readonly extra?: ExtraOptions | undefined;
5
6
  /**
@@ -109,11 +110,12 @@ export declare abstract class AbstractQuerier implements Querier {
109
110
  abstract estimatedCount<E extends object>(entity: Type<E>): Promise<number>;
110
111
  insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<WrittenId<E> | undefined>;
111
112
  /**
112
- * A composite key is named here rather than by the statement: no column holds it, so no database
113
- * reports one, but the caller wrote every column of it and the payload still carries them.
113
+ * The `onInsert` values are filled here, before the write, so the after hooks and the ids read the
114
+ * same rows the statement wrote.
114
115
  */
115
116
  insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
116
- protected abstract internalInsertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(IdValue<E> | undefined)[]>;
117
+ /** Writes `rows`, and onto each one the key the database generated for it, where it can tell. */
118
+ protected abstract internalInsertMany<E extends object>(entity: Type<E>, rows: EntityData<E>[]): Promise<void>;
117
119
  updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
118
120
  updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
119
121
  protected abstract internalUpdateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
@@ -150,20 +152,9 @@ export declare abstract class AbstractQuerier implements Querier {
150
152
  protected abstract internalDeleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
151
153
  saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<WrittenId<E> | undefined>;
152
154
  /**
153
- * Insert or update, as the name has always promised - and now as one statement per kind rather
154
- * than a guess.
155
- *
156
- * Whether a row names its key decides which statement it takes, never whether the row exists: an
157
- * id the caller invented is not proof of anything, and a stale one used to issue an `UPDATE` that
158
- * matched nothing and reported success. A named row upserts on its own key, so it is written
159
- * either way and no read can go stale between deciding and writing. An unnamed one inserts, and
160
- * the database assigns the key.
161
- *
162
- * A composite key is always supplied by the caller, so it always takes the upsert branch - which
163
- * is why nothing here special-cases one, and why this is the method that stopped refusing them.
164
- *
165
- * The hooks follow the statement: a named row fires `beforeUpsert`/`afterUpsert`, never the
166
- * update pair, because the database picks the branch as the statement runs.
155
+ * Whether a row names its key decides its statement, never whether the row exists: a named row
156
+ * upserts on that key, so a stale id is written rather than silently missed, and an unnamed one
157
+ * inserts. A composite is always named. The hooks follow the statement: a named row fires the upsert pair.
167
158
  */
168
159
  saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
169
160
  protected fillToManyRelations<E>(entity: Type<E>, payload: E[], populate?: QueryPopulate<E>): Promise<void>;
@@ -217,14 +208,18 @@ export declare abstract class AbstractQuerier implements Querier {
217
208
  /** Whether anything at all - a global listener or the entity itself - handles `event`. */
218
209
  private hasHook;
219
210
  /**
220
- * Emit a lifecycle hook event for the given entity.
221
- * Fires global listeners first, then entity-level hooks.
211
+ * The ids of `rows`, read back by the columns an upsert matched them on, for a statement that could
212
+ * not report them in payload order. A row that no read row matches, or that two do, keeps
213
+ * `undefined`: a missing id is honest where a guessed one is not.
222
214
  */
215
+ protected idsByConflict<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, rows: EntityData<E>[]): Promise<(PrimaryKey | undefined)[]>;
223
216
  /**
224
- * Runs `write` between the event's `before`/`after` pair. Every hooked write is this shape, and
225
- * each one spelled out was a place the pair could drift - `upsert` had none at all for a release.
217
+ * Runs `write` between the event's `before`/`after` pair. The before hooks get the caller's rows, so
218
+ * what they assign is written; `write` and the after hooks get a copy, which by then carries what
219
+ * the write filled in - a generated key, an `onInsert` value - without it landing on the caller's.
226
220
  */
227
221
  private hooked;
222
+ /** Fires the global listeners first, then the entity's own hooks. */
228
223
  private emitHook;
229
224
  /**
230
225
  * Runs `task` after everything already queued on this querier, one at a time.
@@ -1,5 +1,5 @@
1
1
  import { assertSoleId, getMeta, idOf, namesKey, soleIdOf } from '../entity/index.js';
2
- import { asSelectMap, childrenOf, clone, dataKeyed, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, idOnlyQuery, isScalarId, joinedColumns, keyColumns, LoggerWrapper, isBoundedPerParent, parentJoins, queryChildrenOfAll, parseRelationAtKey, parseRelationQueryValue, rowKey, runHooks, someKey, targetKeyColumns, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
2
+ import { asSelectMap, childrenOf, clone, dataKeyed, fillOnFields, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, idOnlyQuery, isScalarId, joinedColumns, keyColumns, LoggerWrapper, isBoundedPerParent, parentJoins, queryChildrenOfAll, parseRelationAtKey, parseRelationQueryValue, rowKey, runHooks, someKey, targetKeyColumns, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
3
3
  import { enrichError } from './queryError.js';
4
4
  import { fillRelationCounts, withIdForCounts } from './relationCount.js';
5
5
  /**
@@ -41,22 +41,27 @@ function soleParentColumn(relOpts) {
41
41
  return parentJoins(relOpts, 1)[0].joined;
42
42
  }
43
43
  /**
44
- * Base class for all database queriers.
45
- * It provides a standardized way to execute tasks serially to prevent race conditions on database connections.
44
+ * The id each written row is named by, in payload order. Read off the rows as written, so a key the
45
+ * database generated or the ORM filled is there, and a composite is named by every column of it.
46
46
  */
47
+ function writtenIds(meta, rows) {
48
+ return rows.map((row) => (namesKey(meta, row) ? idOf(meta, row) : undefined));
49
+ }
47
50
  /**
48
- * The ids an upsert reports, payload-aligned so the result zips with the rows that were passed.
49
- *
50
- * A composite is named from the payload, as an insert's is: no column holds that key, so no
51
- * statement reports one. A sole key takes what the statement reported when it spoke for every row,
52
- * and otherwise falls back to the key the caller supplied - a `firstId` dialect reports nothing for
53
- * a batch, which is not the same as those rows having no id.
51
+ * Writes the key an upsert reported onto each row that named none - only the key, since an `onInsert`
52
+ * value was never written to a row the upsert updated. A report aligns with the rows only when it
53
+ * speaks for every one: a `firstId` dialect reports nothing for a batch.
54
54
  */
55
- function upsertIds(meta, payload, reported) {
56
- return meta.ids.length === 1 && reported?.length === payload.length
57
- ? reported
58
- : payload.map((it) => (namesKey(meta, it) ? idOf(meta, it) : undefined));
55
+ function adoptReportedIds(meta, rows, reported) {
56
+ if (meta.ids.length !== 1 || reported?.length !== rows.length) {
57
+ return;
58
+ }
59
+ const [idKey] = meta.ids;
60
+ for (let index = 0; index < rows.length; index++) {
61
+ rows[index][idKey] ??= reported[index];
62
+ }
59
63
  }
64
+ /** Base class for all database queriers. */
60
65
  export class AbstractQuerier {
61
66
  extra;
62
67
  /**
@@ -182,15 +187,18 @@ export class AbstractQuerier {
182
187
  return id;
183
188
  }
184
189
  /**
185
- * A composite key is named here rather than by the statement: no column holds it, so no database
186
- * reports one, but the caller wrote every column of it and the payload still carries them.
190
+ * The `onInsert` values are filled here, before the write, so the after hooks and the ids read the
191
+ * same rows the statement wrote.
187
192
  */
188
193
  async insertMany(entity, payload) {
194
+ if (!payload?.length) {
195
+ return [];
196
+ }
189
197
  const meta = getMeta(entity);
190
- return this.hooked(entity, 'Insert', payload, async () => {
191
- const reported = await this.internalInsertMany(entity, payload);
192
- // Neither branch can be shown to be `WrittenId` for an unresolved `E`; `idOf` narrows likewise.
193
- return meta.ids.length === 1 ? reported : payload.map((it) => idOf(meta, it));
198
+ return this.hooked(entity, 'Insert', payload, async (rows) => {
199
+ fillOnFields(meta, rows, 'onInsert');
200
+ await this.internalInsertMany(entity, rows);
201
+ return writtenIds(meta, rows);
194
202
  });
195
203
  }
196
204
  async updateOneById(entity, id, payload, opts) {
@@ -198,7 +206,10 @@ export class AbstractQuerier {
198
206
  return this.updateMany(entity, { $where: whereIds(getMeta(entity), id) }, payload, opts);
199
207
  }
200
208
  async updateMany(entity, q, payload, opts) {
201
- return this.hooked(entity, 'Update', [payload], () => this.internalUpdateMany(entity, q, payload, opts));
209
+ return this.hooked(entity, 'Update', [payload], ([row]) => {
210
+ fillOnFields(getMeta(entity), [row], 'onUpdate');
211
+ return this.internalUpdateMany(entity, q, row, opts);
212
+ });
202
213
  }
203
214
  async restoreOneById(entity, id) {
204
215
  assertIdValue(entity, id);
@@ -221,16 +232,20 @@ export class AbstractQuerier {
221
232
  * was how an `@Id({ onInsert })` or an audit trail silently skipped this path.
222
233
  */
223
234
  async upsertOne(entity, conflictPaths, payload) {
224
- return this.hooked(entity, 'Upsert', [payload], async () => {
225
- const { ids, changes, created } = await this.internalUpsertOne(entity, conflictPaths, payload);
226
- const [id] = upsertIds(getMeta(entity), [payload], ids);
235
+ const meta = getMeta(entity);
236
+ return this.hooked(entity, 'Upsert', [payload], async (rows) => {
237
+ const { ids, changes, created } = await this.internalUpsertOne(entity, conflictPaths, rows[0]);
238
+ adoptReportedIds(meta, rows, ids);
239
+ const [id] = writtenIds(meta, rows);
227
240
  return { id, changes, created };
228
241
  });
229
242
  }
230
243
  async upsertMany(entity, conflictPaths, payload) {
231
- return this.hooked(entity, 'Upsert', payload, async () => {
232
- const { ids, changes } = await this.internalUpsertMany(entity, conflictPaths, payload);
233
- return { ids: upsertIds(getMeta(entity), payload, ids), changes };
244
+ const meta = getMeta(entity);
245
+ return this.hooked(entity, 'Upsert', payload, async (rows) => {
246
+ const { ids, changes } = await this.internalUpsertMany(entity, conflictPaths, rows);
247
+ adoptReportedIds(meta, rows, ids);
248
+ return { ids: writtenIds(meta, rows), changes };
234
249
  });
235
250
  }
236
251
  async deleteOneById(entity, id, opts) {
@@ -278,20 +293,9 @@ export class AbstractQuerier {
278
293
  return id;
279
294
  }
280
295
  /**
281
- * Insert or update, as the name has always promised - and now as one statement per kind rather
282
- * than a guess.
283
- *
284
- * Whether a row names its key decides which statement it takes, never whether the row exists: an
285
- * id the caller invented is not proof of anything, and a stale one used to issue an `UPDATE` that
286
- * matched nothing and reported success. A named row upserts on its own key, so it is written
287
- * either way and no read can go stale between deciding and writing. An unnamed one inserts, and
288
- * the database assigns the key.
289
- *
290
- * A composite key is always supplied by the caller, so it always takes the upsert branch - which
291
- * is why nothing here special-cases one, and why this is the method that stopped refusing them.
292
- *
293
- * The hooks follow the statement: a named row fires `beforeUpsert`/`afterUpsert`, never the
294
- * update pair, because the database picks the branch as the statement runs.
296
+ * Whether a row names its key decides its statement, never whether the row exists: a named row
297
+ * upserts on that key, so a stale id is written rather than silently missed, and an unnamed one
298
+ * inserts. A composite is always named. The hooks follow the statement: a named row fires the upsert pair.
295
299
  */
296
300
  async saveMany(entity, payload) {
297
301
  const meta = getMeta(entity);
@@ -320,15 +324,15 @@ export class AbstractQuerier {
320
324
  const write = async () => {
321
325
  if (toInsert.length) {
322
326
  const inserted = await this.insertMany(entity, toInsert.map((index) => payload[index]));
323
- toInsert.forEach((index, position) => {
324
- ids[index] = inserted[position];
325
- });
327
+ for (let position = 0; position < toInsert.length; position++) {
328
+ ids[toInsert[position]] = inserted[position];
329
+ }
326
330
  }
327
331
  if (toUpsert.length) {
328
332
  const conflictPaths = Object.fromEntries(meta.ids.map((key) => [key, true]));
329
- await this.upsertMany(entity, conflictPaths, toUpsert.map((index) => payload[index]));
330
- for (const index of toUpsert) {
331
- ids[index] = idOf(meta, payload[index]);
333
+ const { ids: upserted } = await this.upsertMany(entity, conflictPaths, toUpsert.map((index) => payload[index]));
334
+ for (let position = 0; position < toUpsert.length; position++) {
335
+ ids[toUpsert[position]] = upserted[position];
332
336
  }
333
337
  }
334
338
  };
@@ -519,16 +523,15 @@ export class AbstractQuerier {
519
523
  // unless this has run - and this method is `protected`, so a caller can arrive without them.
520
524
  assertSoleId(meta, 'saving a relation');
521
525
  const relEntity = relOpts.entity();
522
- const relPayload = relValue;
523
526
  switch (relOpts.cardinality) {
524
527
  case '1m':
525
528
  case 'mm':
526
- return this.saveToMany(relOpts, relEntity, ids, relPayload, isUpdate);
529
+ return this.saveToMany(relOpts, relEntity, ids, relValue, isUpdate);
527
530
  case '11':
528
- return this.saveOneToOne(relEntity, relOpts, ids, relPayload, isUpdate);
531
+ return this.saveOneToOne(relEntity, relOpts, ids, relValue, isUpdate);
529
532
  case 'm1':
530
- if (relPayload)
531
- return this.saveManyToOne(entity, relEntity, relOpts, ids, relPayload);
533
+ if (relValue)
534
+ return this.saveManyToOne(entity, relEntity, relOpts, ids, relValue);
532
535
  }
533
536
  }
534
537
  async saveToMany(relOpts, relEntity, ids, relPayload, isUpdate) {
@@ -628,19 +631,39 @@ export class AbstractQuerier {
628
631
  return (this.extra?.listeners?.some((listener) => listener[event]) || (getMeta(entity).hooks?.[event]?.length ?? 0) > 0);
629
632
  }
630
633
  /**
631
- * Emit a lifecycle hook event for the given entity.
632
- * Fires global listeners first, then entity-level hooks.
634
+ * The ids of `rows`, read back by the columns an upsert matched them on, for a statement that could
635
+ * not report them in payload order. A row that no read row matches, or that two do, keeps
636
+ * `undefined`: a missing id is honest where a guessed one is not.
633
637
  */
638
+ async idsByConflict(entity, conflictPaths, rows) {
639
+ const meta = getMeta(entity);
640
+ const [idKey] = meta.ids;
641
+ const keys = getKeys(conflictPaths);
642
+ const q = {
643
+ $select: Object.fromEntries([idKey, ...keys].map((key) => [key, true])),
644
+ $where: { $or: rows.map((row) => Object.fromEntries(keys.map((key) => [key, row[key]]))) },
645
+ };
646
+ const found = await this.internalFindMany(entity, q, { filters: withoutSoftDeleteFilter(undefined) });
647
+ const byConflict = new Map();
648
+ for (const row of found) {
649
+ const key = rowKey(row, keys);
650
+ byConflict.set(key, byConflict.has(key) ? undefined : row[idKey]);
651
+ }
652
+ return rows.map((row) => byConflict.get(rowKey(row, keys)));
653
+ }
634
654
  /**
635
- * Runs `write` between the event's `before`/`after` pair. Every hooked write is this shape, and
636
- * each one spelled out was a place the pair could drift - `upsert` had none at all for a release.
655
+ * Runs `write` between the event's `before`/`after` pair. The before hooks get the caller's rows, so
656
+ * what they assign is written; `write` and the after hooks get a copy, which by then carries what
657
+ * the write filled in - a generated key, an `onInsert` value - without it landing on the caller's.
637
658
  */
638
659
  async hooked(entity, event, payloads, write) {
639
660
  await this.emitHook(entity, `before${event}`, payloads);
640
- const result = await write();
641
- await this.emitHook(entity, `after${event}`, payloads);
661
+ const rows = clone(payloads);
662
+ const result = await write(rows);
663
+ await this.emitHook(entity, `after${event}`, rows);
642
664
  return result;
643
665
  }
666
+ /** Fires the global listeners first, then the entity's own hooks. */
644
667
  async emitHook(entity, event, payloads) {
645
668
  if (!this.hasHook(entity, event))
646
669
  return;
@@ -1,5 +1,5 @@
1
1
  import type { AbstractSqlDialect } from '../dialect/index.js';
2
- import type { EntityData, ExtraOptions, IdValue, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryGroupMap, QueryOptions, QuerySearch, QueryUpdateResult, SqlQuerier, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
2
+ import type { EntityData, ExtraOptions, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryGroupMap, QueryOptions, QuerySearch, QueryUpdateResult, SqlQuerier, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
3
3
  import { type ParentPartition } from '../util/index.js';
4
4
  import type { BuildUpdateResultPayload } from '../util/sql.util.js';
5
5
  import { AbstractQuerier } from './abstractQuerier.js';
@@ -71,6 +71,13 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
71
71
  * query, so neither can reach here.
72
72
  */
73
73
  protected internalFindManyPerParent<E extends object>(entity: Type<E>, q: Query<E>, partition: ParentPartition): Promise<E[]>;
74
+ /**
75
+ * How to count when the total cannot ride along in the read's own `COUNT(*) OVER ()` column, or
76
+ * `undefined` when it can. Two clauses rule the window out: `$distinct`, because a window counts
77
+ * before the deduplication and so overstates the page, and `$lock` on an engine that refuses the
78
+ * pair outright. Both then cost a second statement; only the counting differs.
79
+ */
80
+ private countedSeparately;
74
81
  /**
75
82
  * One statement for both: the page carries its own unpaged total in an extra column. An empty page
76
83
  * has no row to carry it, which is the one case still needing a count of its own - a `$skip` past
@@ -82,13 +89,6 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
82
89
  * answers; where it does, the total comes from a count of its own. A `$distinct` read needs one
83
90
  * too, and a deduplicating one: see {@link AbstractSqlDialect.countDistinct}.
84
91
  */
85
- /**
86
- * How to count when the total cannot ride along in the read's own `COUNT(*) OVER ()` column, or
87
- * `undefined` when it can. Two clauses rule the window out: `$distinct`, because a window counts
88
- * before the deduplication and so overstates the page, and `$lock` on an engine that refuses the
89
- * pair outright. Both then cost a second statement; only the counting differs.
90
- */
91
- private countedSeparately;
92
92
  protected internalFindManyAndCount<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<[E[], number]>;
93
93
  private selectRows;
94
94
  private hydrateRows;
@@ -119,7 +119,7 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
119
119
  protected internalCount<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: QueryOptions): Promise<number>;
120
120
  estimatedCount<E extends object>(entity: Type<E>): Promise<number>;
121
121
  protected internalAggregate<E extends object, G extends QueryGroupMap<E>, A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
122
- internalInsertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(IdValue<E> | undefined)[]>;
122
+ internalInsertMany<E extends object>(entity: Type<E>, rows: EntityData<E>[]): Promise<void>;
123
123
  internalUpdateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
124
124
  /** The ids matching `q`, in `q`'s own order and page, so a write can name the rows it settled on. */
125
125
  private settleIds;
@@ -129,14 +129,21 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
129
129
  protected internalDeleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
130
130
  get hasOpenTransaction(): boolean;
131
131
  beginTransaction(opts?: TransactionOptions): Promise<void>;
132
+ /**
133
+ * Only an end that succeeded ends the transaction. A `COMMIT` that fails can leave it open (SQLite
134
+ * answers `SQLITE_BUSY` and keeps it), so the flag has to stay set for the `catch` in
135
+ * {@link AbstractQuerier.transaction} or {@link AbstractQuerier.release} to roll it back.
136
+ */
132
137
  commitTransaction(): Promise<void>;
133
138
  rollbackTransaction(): Promise<void>;
134
139
  /**
135
- * Only a statement that succeeded ends the transaction. A `COMMIT` that fails can leave it open
136
- * (SQLite answers `SQLITE_BUSY` and keeps it), so the flag has to stay set for the `catch` in
137
- * {@link AbstractQuerier.transaction} or {@link AbstractQuerier.release} to roll it back.
140
+ * How this driver opens, commits and rolls back: the dialect's statements, unless its transactions
141
+ * are objects rather than statements - Hrana's session handle, `mssql`'s `Transaction` - in which
142
+ * case it overrides all three. The bookkeeping around them stays above, written once.
138
143
  */
139
- private endTransactionWith;
144
+ protected internalBegin(opts?: TransactionOptions): Promise<void>;
145
+ protected internalCommit(): Promise<void>;
146
+ protected internalRollback(): Promise<void>;
140
147
  /** Transaction statements skip `timed()`, so they attach their own query context to a failure. */
141
148
  private runTransactionCommand;
142
149
  }