@nextlyhq/adapter-drizzle 0.0.2-alpha.65 → 0.0.2-alpha.66

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.
@@ -1,5 +1,5 @@
1
1
  import { AnyRelations, SQL } from 'drizzle-orm';
2
- import { T as TransactionContext, S as SelectOptions, W as WhereClause, U as UpdateOptions, D as DeleteOptions, e as UpsertOptions, f as TransactionOptions, g as DatabaseCapabilities, P as PoolStats, C as CountOptions, I as InsertOptions, a as Migration, d as MigrationResult } from './migration-R06fsTrz.js';
2
+ import { T as TransactionContext, S as SelectOptions, W as WhereClause, D as DeleteOptions, U as UpsertOptions, e as TransactionOptions, f as DatabaseCapabilities, P as PoolStats, C as CountOptions, I as InsertOptions, g as UpdateOptions, a as Migration, d as MigrationResult } from './migration-CgoxgWOL.js';
3
3
  import { S as SupportedDialect, a as SqlParam, T as TableResolver } from './core-CVO7WYDj.js';
4
4
  import { T as TableDefinition, C as CreateTableOptions, D as DropTableOptions, A as AlterTableOperation, a as AlterTableOptions } from './schema-BIQ0YQZ_.js';
5
5
  import { D as DatabaseErrorKind, a as DatabaseError } from './error-BrdknH2s.js';
@@ -21,15 +21,20 @@ import { D as DatabaseErrorKind, a as DatabaseError } from './error-BrdknH2s.js'
21
21
  interface TransactionCrudDelegator {
22
22
  select<T = unknown>(table: string, options?: SelectOptions, executor?: unknown): Promise<T[]>;
23
23
  selectOne<T = unknown>(table: string, options?: SelectOptions, executor?: unknown): Promise<T | null>;
24
- update<T = unknown>(table: string, data: Record<string, unknown>, where: WhereClause, options?: UpdateOptions, executor?: unknown): Promise<T[]>;
25
24
  updateCount(table: string, data: Record<string, unknown>, where: WhereClause, executor?: unknown): Promise<number>;
26
25
  delete(table: string, where: WhereClause, options?: DeleteOptions, executor?: unknown): Promise<number>;
27
26
  upsert<T = unknown>(table: string, data: Record<string, unknown>, options: UpsertOptions, executor?: unknown): Promise<T>;
28
27
  }
29
28
  /**
30
29
  * Transaction context CRUD methods provided by the forwarder.
30
+ *
31
+ * `update` is not among them. The pooled `update` goes through the Drizzle
32
+ * query builder, which writes the columns the runtime model declares; a
33
+ * transaction's update must reach the columns the physical table has, and
34
+ * each adapter binds the base class's `transactionUpdate` beside its
35
+ * transactional `insert`.
31
36
  */
32
- type TransactionCrudForwarders = Pick<TransactionContext, "select" | "selectOne" | "update" | "updateCount" | "delete" | "upsert" | "getDrizzle">;
37
+ type TransactionCrudForwarders = Pick<TransactionContext, "select" | "selectOne" | "updateCount" | "delete" | "upsert" | "getDrizzle">;
33
38
 
34
39
  /**
35
40
  * Base database adapter abstract class.
@@ -806,6 +811,87 @@ declare abstract class DrizzleAdapter {
806
811
  * @protected
807
812
  */
808
813
  protected createTransactionForwarders(txDb: () => unknown): TransactionCrudForwarders;
814
+ /**
815
+ * A transaction context's `update`, for each adapter to bind.
816
+ *
817
+ * Not forwarded to the pooled `update` the way `select` and `delete` are:
818
+ * that one goes through the Drizzle query builder, which writes the columns
819
+ * the runtime MODEL declares, and a transaction's update must reach the
820
+ * columns the physical TABLE has — the localization transition window
821
+ * writes a column the model has already moved to a companion table that
822
+ * does not exist yet. The INSERT half of every transaction context already
823
+ * builds its own statement for that reason; `update-statement.ts` is the
824
+ * UPDATE half, spelled once for the three adapters, and this runs it: on
825
+ * the transaction executor, classified with the operation and table named
826
+ * as the pooled `update` classifies its failures, and read back through
827
+ * `select` on the same executor when the caller asked for rows.
828
+ *
829
+ * That read is by the rows' IDENTITY where the dialect can report it: on
830
+ * PostgreSQL and SQLite the statement carries `RETURNING <primary key>`, and
831
+ * the read-back asks for exactly those rows — so a row another transaction
832
+ * adds under the same predicate meanwhile is not among them, and an update
833
+ * whose own write falsifies its predicate still reads its rows back. MySQL
834
+ * has no RETURNING, so there the read re-runs the predicate, as the pooled
835
+ * `update` always has on that dialect; a table with no primary key in its
836
+ * model reads back the same way.
837
+ *
838
+ * @param txDb - thunk returning the transaction-bound Drizzle instance
839
+ * @param run - how this dialect runs a statement on that instance, and the
840
+ * rows it returns when the statement carries RETURNING: better-sqlite3
841
+ * answers synchronously, the pooled drivers do not
842
+ * @param bindUnmodeled - how a value binds when the model declares no
843
+ * column for it; a declared column binds through its own encoder
844
+ * @returns the context's `update` method
845
+ */
846
+ protected transactionUpdate(txDb: () => unknown, run: (statement: SQL, returnsRows: boolean) => Promise<Record<string, unknown>[] | undefined> | Record<string, unknown>[] | undefined, bindUnmodeled: (value: unknown) => unknown): TransactionContext["update"];
847
+ /**
848
+ * The columns that identify a row of this table, under both spellings: the
849
+ * SQL name RETURNING reports and the Drizzle property name the query
850
+ * builder's WHERE is addressed by. Columns flagged primary first; failing
851
+ * that, the table-level primary key the dialect's table config carries
852
+ * (`primaryKey({ columns })` leaves each column's flag false), which each
853
+ * adapter reads through its own dialect. Empty when the model declares
854
+ * neither, and the read-back falls back to the caller's predicate.
855
+ */
856
+ protected identityColumns(tableObj: Record<string, unknown>): Array<{
857
+ jsName: string;
858
+ sqlName: string;
859
+ }>;
860
+ /**
861
+ * How a value binds to a column the model does not declare, on a driver
862
+ * that binds scalars, dates and binary natively: a structured value goes
863
+ * as JSON text, since what node-postgres and mysql2 make of a bare object
864
+ * or array is not JSON. For the adapters to hand `transactionUpdate`.
865
+ */
866
+ protected bindUnmodeledStructuredAsJson(value: unknown): unknown;
867
+ /**
868
+ * The columns of a table-level `primaryKey({ columns })`, as the dialect's
869
+ * table config reports them. The base class cannot read that config without
870
+ * naming a dialect, so each adapter answers for its own; a dialect that does
871
+ * not answer reports none.
872
+ */
873
+ protected compositePrimaryKey(_tableObj: Record<string, unknown>): object[];
874
+ /** The registered table object, or the error every CRUD method throws without one. */
875
+ private resolvedTableObject;
876
+ /**
877
+ * The statement `transactionUpdate` runs.
878
+ *
879
+ * @throws when nothing would be written — every key `undefined`, or none at
880
+ * all. The query builder refused that too ("No values to set"), and a
881
+ * patch that names nothing is a caller's mistake rather than a write of
882
+ * nothing.
883
+ */
884
+ private buildTransactionUpdate;
885
+ /**
886
+ * Whether a transaction's `update` reads the rows it changed back: the
887
+ * caller named columns, or `*`. An empty list, like no option at all, asks
888
+ * for nothing and `update` answers `[]`, as it always has. The rows come
889
+ * from a read of the update's own WHERE on its own transaction, so they are
890
+ * the model's view of the row, decoded as every read is decoded — which is
891
+ * what the query-builder update returned for them, and why the named list
892
+ * selects nothing narrower.
893
+ */
894
+ private updateReturnsRows;
809
895
  /**
810
896
  * Classify an error into a DatabaseError.
811
897
  *
@@ -1,5 +1,5 @@
1
1
  import { AnyRelations, SQL } from 'drizzle-orm';
2
- import { T as TransactionContext, S as SelectOptions, W as WhereClause, U as UpdateOptions, D as DeleteOptions, e as UpsertOptions, f as TransactionOptions, g as DatabaseCapabilities, P as PoolStats, C as CountOptions, I as InsertOptions, a as Migration, d as MigrationResult } from './migration-B6AmjCQ1.cjs';
2
+ import { T as TransactionContext, S as SelectOptions, W as WhereClause, D as DeleteOptions, U as UpsertOptions, e as TransactionOptions, f as DatabaseCapabilities, P as PoolStats, C as CountOptions, I as InsertOptions, g as UpdateOptions, a as Migration, d as MigrationResult } from './migration-Ce0hkgr_.cjs';
3
3
  import { S as SupportedDialect, a as SqlParam, T as TableResolver } from './core-CVO7WYDj.cjs';
4
4
  import { T as TableDefinition, C as CreateTableOptions, D as DropTableOptions, A as AlterTableOperation, a as AlterTableOptions } from './schema-BDn8WfSL.cjs';
5
5
  import { D as DatabaseErrorKind, a as DatabaseError } from './error-BrdknH2s.cjs';
@@ -21,15 +21,20 @@ import { D as DatabaseErrorKind, a as DatabaseError } from './error-BrdknH2s.cjs
21
21
  interface TransactionCrudDelegator {
22
22
  select<T = unknown>(table: string, options?: SelectOptions, executor?: unknown): Promise<T[]>;
23
23
  selectOne<T = unknown>(table: string, options?: SelectOptions, executor?: unknown): Promise<T | null>;
24
- update<T = unknown>(table: string, data: Record<string, unknown>, where: WhereClause, options?: UpdateOptions, executor?: unknown): Promise<T[]>;
25
24
  updateCount(table: string, data: Record<string, unknown>, where: WhereClause, executor?: unknown): Promise<number>;
26
25
  delete(table: string, where: WhereClause, options?: DeleteOptions, executor?: unknown): Promise<number>;
27
26
  upsert<T = unknown>(table: string, data: Record<string, unknown>, options: UpsertOptions, executor?: unknown): Promise<T>;
28
27
  }
29
28
  /**
30
29
  * Transaction context CRUD methods provided by the forwarder.
30
+ *
31
+ * `update` is not among them. The pooled `update` goes through the Drizzle
32
+ * query builder, which writes the columns the runtime model declares; a
33
+ * transaction's update must reach the columns the physical table has, and
34
+ * each adapter binds the base class's `transactionUpdate` beside its
35
+ * transactional `insert`.
31
36
  */
32
- type TransactionCrudForwarders = Pick<TransactionContext, "select" | "selectOne" | "update" | "updateCount" | "delete" | "upsert" | "getDrizzle">;
37
+ type TransactionCrudForwarders = Pick<TransactionContext, "select" | "selectOne" | "updateCount" | "delete" | "upsert" | "getDrizzle">;
33
38
 
34
39
  /**
35
40
  * Base database adapter abstract class.
@@ -806,6 +811,87 @@ declare abstract class DrizzleAdapter {
806
811
  * @protected
807
812
  */
808
813
  protected createTransactionForwarders(txDb: () => unknown): TransactionCrudForwarders;
814
+ /**
815
+ * A transaction context's `update`, for each adapter to bind.
816
+ *
817
+ * Not forwarded to the pooled `update` the way `select` and `delete` are:
818
+ * that one goes through the Drizzle query builder, which writes the columns
819
+ * the runtime MODEL declares, and a transaction's update must reach the
820
+ * columns the physical TABLE has — the localization transition window
821
+ * writes a column the model has already moved to a companion table that
822
+ * does not exist yet. The INSERT half of every transaction context already
823
+ * builds its own statement for that reason; `update-statement.ts` is the
824
+ * UPDATE half, spelled once for the three adapters, and this runs it: on
825
+ * the transaction executor, classified with the operation and table named
826
+ * as the pooled `update` classifies its failures, and read back through
827
+ * `select` on the same executor when the caller asked for rows.
828
+ *
829
+ * That read is by the rows' IDENTITY where the dialect can report it: on
830
+ * PostgreSQL and SQLite the statement carries `RETURNING <primary key>`, and
831
+ * the read-back asks for exactly those rows — so a row another transaction
832
+ * adds under the same predicate meanwhile is not among them, and an update
833
+ * whose own write falsifies its predicate still reads its rows back. MySQL
834
+ * has no RETURNING, so there the read re-runs the predicate, as the pooled
835
+ * `update` always has on that dialect; a table with no primary key in its
836
+ * model reads back the same way.
837
+ *
838
+ * @param txDb - thunk returning the transaction-bound Drizzle instance
839
+ * @param run - how this dialect runs a statement on that instance, and the
840
+ * rows it returns when the statement carries RETURNING: better-sqlite3
841
+ * answers synchronously, the pooled drivers do not
842
+ * @param bindUnmodeled - how a value binds when the model declares no
843
+ * column for it; a declared column binds through its own encoder
844
+ * @returns the context's `update` method
845
+ */
846
+ protected transactionUpdate(txDb: () => unknown, run: (statement: SQL, returnsRows: boolean) => Promise<Record<string, unknown>[] | undefined> | Record<string, unknown>[] | undefined, bindUnmodeled: (value: unknown) => unknown): TransactionContext["update"];
847
+ /**
848
+ * The columns that identify a row of this table, under both spellings: the
849
+ * SQL name RETURNING reports and the Drizzle property name the query
850
+ * builder's WHERE is addressed by. Columns flagged primary first; failing
851
+ * that, the table-level primary key the dialect's table config carries
852
+ * (`primaryKey({ columns })` leaves each column's flag false), which each
853
+ * adapter reads through its own dialect. Empty when the model declares
854
+ * neither, and the read-back falls back to the caller's predicate.
855
+ */
856
+ protected identityColumns(tableObj: Record<string, unknown>): Array<{
857
+ jsName: string;
858
+ sqlName: string;
859
+ }>;
860
+ /**
861
+ * How a value binds to a column the model does not declare, on a driver
862
+ * that binds scalars, dates and binary natively: a structured value goes
863
+ * as JSON text, since what node-postgres and mysql2 make of a bare object
864
+ * or array is not JSON. For the adapters to hand `transactionUpdate`.
865
+ */
866
+ protected bindUnmodeledStructuredAsJson(value: unknown): unknown;
867
+ /**
868
+ * The columns of a table-level `primaryKey({ columns })`, as the dialect's
869
+ * table config reports them. The base class cannot read that config without
870
+ * naming a dialect, so each adapter answers for its own; a dialect that does
871
+ * not answer reports none.
872
+ */
873
+ protected compositePrimaryKey(_tableObj: Record<string, unknown>): object[];
874
+ /** The registered table object, or the error every CRUD method throws without one. */
875
+ private resolvedTableObject;
876
+ /**
877
+ * The statement `transactionUpdate` runs.
878
+ *
879
+ * @throws when nothing would be written — every key `undefined`, or none at
880
+ * all. The query builder refused that too ("No values to set"), and a
881
+ * patch that names nothing is a caller's mistake rather than a write of
882
+ * nothing.
883
+ */
884
+ private buildTransactionUpdate;
885
+ /**
886
+ * Whether a transaction's `update` reads the rows it changed back: the
887
+ * caller named columns, or `*`. An empty list, like no option at all, asks
888
+ * for nothing and `update` answers `[]`, as it always has. The rows come
889
+ * from a read of the update's own WHERE on its own transaction, so they are
890
+ * the model's view of the row, decoded as every read is decoded — which is
891
+ * what the query-builder update returned for them, and why the named list
892
+ * selects nothing narrower.
893
+ */
894
+ private updateReturnsRows;
809
895
  /**
810
896
  * Classify an error into a DatabaseError.
811
897
  *
package/dist/index.cjs CHANGED
@@ -3,6 +3,20 @@
3
3
  var drizzleOrm = require('drizzle-orm');
4
4
 
5
5
  // src/adapter.ts
6
+
7
+ // src/column-kinds.ts
8
+ function isJsonColumn(column) {
9
+ return column.dataType === "json" || column.columnType === "PgJsonb" || column.columnType === "PgJson" || column.columnType === "MySqlJson" || column.columnType === "SQLiteTextJson";
10
+ }
11
+ function isStructuredValue(value) {
12
+ if (value === null || typeof value !== "object") return false;
13
+ if (value instanceof Date) return false;
14
+ if (Buffer.isBuffer(value) || ArrayBuffer.isView(value)) return false;
15
+ return true;
16
+ }
17
+ function bindStructuredAsJson(value) {
18
+ return isStructuredValue(value) ? JSON.stringify(value) : value;
19
+ }
6
20
  function buildDrizzleOrderBy(columns, orderBy) {
7
21
  if (!orderBy?.length) return [];
8
22
  return orderBy.flatMap((spec) => {
@@ -122,9 +136,6 @@ function createTransactionForwarders(delegator, txDb) {
122
136
  selectOne: async (table, options) => {
123
137
  return delegator.selectOne(table, options, txDb());
124
138
  },
125
- update: async (table, data, where, options) => {
126
- return delegator.update(table, data, where, options, txDb());
127
- },
128
139
  updateCount: async (table, data, where) => {
129
140
  return delegator.updateCount(table, data, where, txDb());
130
141
  },
@@ -155,6 +166,74 @@ function createDatabaseError(options) {
155
166
  if (options.cause !== void 0) error.cause = options.cause;
156
167
  return error;
157
168
  }
169
+ function isBindableColumn(value) {
170
+ return typeof value === "object" && value !== null && typeof value.name === "string" && typeof value.mapToDriverValue === "function";
171
+ }
172
+ function valueForJsonColumn(value) {
173
+ if (typeof value !== "string") return value;
174
+ try {
175
+ return JSON.parse(value);
176
+ } catch {
177
+ return value;
178
+ }
179
+ }
180
+ function columnsOf(tableObj) {
181
+ const byName = /* @__PURE__ */ new Map();
182
+ const declared = [];
183
+ for (const [jsName, column] of Object.entries(
184
+ drizzleOrm.getColumns(tableObj)
185
+ )) {
186
+ if (!isBindableColumn(column)) continue;
187
+ byName.set(column.name, column);
188
+ byName.set(jsName, column);
189
+ declared.push(column);
190
+ }
191
+ return { byName, declared };
192
+ }
193
+ function boundValue(value, column, bindUnmodeled) {
194
+ if (drizzleOrm.is(value, drizzleOrm.SQL) || drizzleOrm.is(value, drizzleOrm.Column)) return drizzleOrm.sql`${value}`;
195
+ if (column) {
196
+ return drizzleOrm.sql`${drizzleOrm.sql.param(
197
+ isJsonColumn(column) ? valueForJsonColumn(value) : value,
198
+ column
199
+ )}`;
200
+ }
201
+ return drizzleOrm.sql`${drizzleOrm.sql.param(bindUnmodeled(value))}`;
202
+ }
203
+ function buildUpdateStatement(input) {
204
+ const { byName, declared } = columnsOf(input.tableObj);
205
+ const assignments = /* @__PURE__ */ new Map();
206
+ for (const [key, value] of Object.entries(input.data)) {
207
+ const column = byName.get(key);
208
+ const name = column?.name ?? key;
209
+ if (value === void 0) {
210
+ assignments.delete(name);
211
+ continue;
212
+ }
213
+ assignments.set(
214
+ name,
215
+ drizzleOrm.sql`${drizzleOrm.sql.identifier(name)} = ${boundValue(value, column, input.bindUnmodeled)}`
216
+ );
217
+ }
218
+ if (assignments.size === 0) return null;
219
+ for (const column of declared) {
220
+ if (assignments.has(column.name) || column.onUpdateFn === void 0) {
221
+ continue;
222
+ }
223
+ const value = column.onUpdateFn();
224
+ assignments.set(
225
+ column.name,
226
+ drizzleOrm.sql`${drizzleOrm.sql.identifier(column.name)} = ${boundValue(value, column, input.bindUnmodeled)}`
227
+ );
228
+ }
229
+ const statement = drizzleOrm.sql`UPDATE ${input.tableObj} SET ${drizzleOrm.sql.join([...assignments.values()], drizzleOrm.sql`, `)}`;
230
+ const condition = buildDrizzleWhere(input.tableObj, input.where);
231
+ if (condition) statement.append(drizzleOrm.sql` WHERE ${condition}`);
232
+ if (input.returning && input.returning.length > 0) {
233
+ statement.append(drizzleOrm.sql` RETURNING ${drizzleOrm.sql.join(input.returning, drizzleOrm.sql`, `)}`);
234
+ }
235
+ return statement;
236
+ }
158
237
 
159
238
  // src/adapter.ts
160
239
  var DATE_DATA_TYPE = /(?:^|\s)date$/;
@@ -286,9 +365,7 @@ var DrizzleAdapter = class {
286
365
  continue;
287
366
  const sqlName = colDef.name;
288
367
  sqlToJs.set(sqlName, jsName);
289
- const dataType = colDef.dataType;
290
- const columnType = colDef.columnType;
291
- if (dataType === "json" || columnType === "PgJsonb" || columnType === "PgJson" || columnType === "MySqlJson" || columnType === "SQLiteTextJson") {
368
+ if (isJsonColumn(colDef)) {
292
369
  jsonColumns.add(jsName);
293
370
  }
294
371
  }
@@ -1458,11 +1535,11 @@ var DrizzleAdapter = class {
1458
1535
  */
1459
1536
  async tableExists(tableName, schema) {
1460
1537
  try {
1461
- let sql3;
1538
+ let sql5;
1462
1539
  const params = [];
1463
1540
  switch (this.dialect) {
1464
1541
  case "postgresql":
1465
- sql3 = `
1542
+ sql5 = `
1466
1543
  SELECT EXISTS (
1467
1544
  SELECT FROM information_schema.tables
1468
1545
  WHERE table_schema = COALESCE($1, (
@@ -1476,7 +1553,7 @@ var DrizzleAdapter = class {
1476
1553
  params.push(schema ?? null, tableName);
1477
1554
  break;
1478
1555
  case "mysql":
1479
- sql3 = `
1556
+ sql5 = `
1480
1557
  SELECT COUNT(*) as count
1481
1558
  FROM information_schema.tables
1482
1559
  WHERE table_schema = DATABASE()
@@ -1485,7 +1562,7 @@ var DrizzleAdapter = class {
1485
1562
  params.push(tableName);
1486
1563
  break;
1487
1564
  case "sqlite":
1488
- sql3 = `
1565
+ sql5 = `
1489
1566
  SELECT COUNT(*) as count
1490
1567
  FROM sqlite_master
1491
1568
  WHERE type = 'table'
@@ -1501,7 +1578,7 @@ var DrizzleAdapter = class {
1501
1578
  );
1502
1579
  }
1503
1580
  const results = await this.executeQuery(
1504
- sql3,
1581
+ sql5,
1505
1582
  params
1506
1583
  );
1507
1584
  if (results.length === 0) {
@@ -1533,11 +1610,11 @@ var DrizzleAdapter = class {
1533
1610
  */
1534
1611
  async listTables(schema) {
1535
1612
  try {
1536
- let sql3;
1613
+ let sql5;
1537
1614
  const params = [];
1538
1615
  switch (this.dialect) {
1539
1616
  case "postgresql":
1540
- sql3 = `
1617
+ sql5 = `
1541
1618
  SELECT table_name
1542
1619
  FROM information_schema.tables
1543
1620
  WHERE table_schema = $1
@@ -1547,7 +1624,7 @@ var DrizzleAdapter = class {
1547
1624
  params.push(schema ?? "public");
1548
1625
  break;
1549
1626
  case "mysql":
1550
- sql3 = `
1627
+ sql5 = `
1551
1628
  SELECT table_name AS table_name
1552
1629
  FROM information_schema.tables
1553
1630
  WHERE table_schema = DATABASE()
@@ -1556,7 +1633,7 @@ var DrizzleAdapter = class {
1556
1633
  `;
1557
1634
  break;
1558
1635
  case "sqlite":
1559
- sql3 = `
1636
+ sql5 = `
1560
1637
  SELECT name as table_name
1561
1638
  FROM sqlite_master
1562
1639
  WHERE type = 'table'
@@ -1572,7 +1649,7 @@ var DrizzleAdapter = class {
1572
1649
  );
1573
1650
  }
1574
1651
  const results = await this.executeQuery(
1575
- sql3,
1652
+ sql5,
1576
1653
  params
1577
1654
  );
1578
1655
  return results.map((row) => row.table_name);
@@ -1638,6 +1715,163 @@ var DrizzleAdapter = class {
1638
1715
  createTransactionForwarders(txDb) {
1639
1716
  return createTransactionForwarders(this, txDb);
1640
1717
  }
1718
+ /**
1719
+ * A transaction context's `update`, for each adapter to bind.
1720
+ *
1721
+ * Not forwarded to the pooled `update` the way `select` and `delete` are:
1722
+ * that one goes through the Drizzle query builder, which writes the columns
1723
+ * the runtime MODEL declares, and a transaction's update must reach the
1724
+ * columns the physical TABLE has — the localization transition window
1725
+ * writes a column the model has already moved to a companion table that
1726
+ * does not exist yet. The INSERT half of every transaction context already
1727
+ * builds its own statement for that reason; `update-statement.ts` is the
1728
+ * UPDATE half, spelled once for the three adapters, and this runs it: on
1729
+ * the transaction executor, classified with the operation and table named
1730
+ * as the pooled `update` classifies its failures, and read back through
1731
+ * `select` on the same executor when the caller asked for rows.
1732
+ *
1733
+ * That read is by the rows' IDENTITY where the dialect can report it: on
1734
+ * PostgreSQL and SQLite the statement carries `RETURNING <primary key>`, and
1735
+ * the read-back asks for exactly those rows — so a row another transaction
1736
+ * adds under the same predicate meanwhile is not among them, and an update
1737
+ * whose own write falsifies its predicate still reads its rows back. MySQL
1738
+ * has no RETURNING, so there the read re-runs the predicate, as the pooled
1739
+ * `update` always has on that dialect; a table with no primary key in its
1740
+ * model reads back the same way.
1741
+ *
1742
+ * @param txDb - thunk returning the transaction-bound Drizzle instance
1743
+ * @param run - how this dialect runs a statement on that instance, and the
1744
+ * rows it returns when the statement carries RETURNING: better-sqlite3
1745
+ * answers synchronously, the pooled drivers do not
1746
+ * @param bindUnmodeled - how a value binds when the model declares no
1747
+ * column for it; a declared column binds through its own encoder
1748
+ * @returns the context's `update` method
1749
+ */
1750
+ transactionUpdate(txDb, run, bindUnmodeled) {
1751
+ return async (table, data, where, options) => {
1752
+ const wantsRows = this.updateReturnsRows(options?.returning);
1753
+ const tableObj = this.resolvedTableObject(table);
1754
+ const identity = wantsRows && this.getCapabilities().supportsReturning ? this.identityColumns(tableObj) : [];
1755
+ let reported;
1756
+ try {
1757
+ reported = await run(
1758
+ this.buildTransactionUpdate(tableObj, table, data, where, {
1759
+ bindUnmodeled,
1760
+ returning: identity.map((key) => drizzleOrm.sql`${drizzleOrm.sql.identifier(key.sqlName)}`)
1761
+ }),
1762
+ identity.length > 0
1763
+ );
1764
+ } catch (error) {
1765
+ throw this.handleQueryError(error, "update", table);
1766
+ }
1767
+ if (!wantsRows) return [];
1768
+ if (identity.length === 0 || !Array.isArray(reported)) {
1769
+ return this.select(table, { where }, txDb());
1770
+ }
1771
+ const rows = [];
1772
+ for (let at = 0; at < reported.length; at += IDENTITY_READ_BACK_CHUNK) {
1773
+ rows.push(
1774
+ ...await this.select(
1775
+ table,
1776
+ {
1777
+ where: rowsByIdentity(
1778
+ identity,
1779
+ reported.slice(at, at + IDENTITY_READ_BACK_CHUNK)
1780
+ )
1781
+ },
1782
+ txDb()
1783
+ )
1784
+ );
1785
+ }
1786
+ return rows;
1787
+ };
1788
+ }
1789
+ /**
1790
+ * The columns that identify a row of this table, under both spellings: the
1791
+ * SQL name RETURNING reports and the Drizzle property name the query
1792
+ * builder's WHERE is addressed by. Columns flagged primary first; failing
1793
+ * that, the table-level primary key the dialect's table config carries
1794
+ * (`primaryKey({ columns })` leaves each column's flag false), which each
1795
+ * adapter reads through its own dialect. Empty when the model declares
1796
+ * neither, and the read-back falls back to the caller's predicate.
1797
+ */
1798
+ identityColumns(tableObj) {
1799
+ const columns = Object.entries(drizzleOrm.getColumns(tableObj));
1800
+ const flagged = columns.filter(([, c]) => c.primary === true);
1801
+ const members = flagged.length > 0 ? flagged : columns.filter(
1802
+ ([, c]) => this.compositePrimaryKey(tableObj).includes(c)
1803
+ );
1804
+ return members.flatMap(
1805
+ ([jsName, c]) => typeof c.name === "string" ? [{ jsName, sqlName: c.name }] : []
1806
+ );
1807
+ }
1808
+ /**
1809
+ * How a value binds to a column the model does not declare, on a driver
1810
+ * that binds scalars, dates and binary natively: a structured value goes
1811
+ * as JSON text, since what node-postgres and mysql2 make of a bare object
1812
+ * or array is not JSON. For the adapters to hand `transactionUpdate`.
1813
+ */
1814
+ bindUnmodeledStructuredAsJson(value) {
1815
+ return bindStructuredAsJson(value);
1816
+ }
1817
+ /**
1818
+ * The columns of a table-level `primaryKey({ columns })`, as the dialect's
1819
+ * table config reports them. The base class cannot read that config without
1820
+ * naming a dialect, so each adapter answers for its own; a dialect that does
1821
+ * not answer reports none.
1822
+ */
1823
+ compositePrimaryKey(_tableObj) {
1824
+ return [];
1825
+ }
1826
+ /** The registered table object, or the error every CRUD method throws without one. */
1827
+ resolvedTableObject(table) {
1828
+ const tableObj = this.getTableObject(table);
1829
+ if (!tableObj || typeof tableObj !== "object") {
1830
+ throw this.createDatabaseError(
1831
+ "query",
1832
+ `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`,
1833
+ void 0
1834
+ );
1835
+ }
1836
+ return tableObj;
1837
+ }
1838
+ /**
1839
+ * The statement `transactionUpdate` runs.
1840
+ *
1841
+ * @throws when nothing would be written — every key `undefined`, or none at
1842
+ * all. The query builder refused that too ("No values to set"), and a
1843
+ * patch that names nothing is a caller's mistake rather than a write of
1844
+ * nothing.
1845
+ */
1846
+ buildTransactionUpdate(tableObj, table, data, where, binding) {
1847
+ const statement = buildUpdateStatement({
1848
+ tableObj,
1849
+ data,
1850
+ where,
1851
+ bindUnmodeled: binding.bindUnmodeled,
1852
+ returning: binding.returning
1853
+ });
1854
+ if (!statement) {
1855
+ throw this.createDatabaseError(
1856
+ "query",
1857
+ `No values to set: the update of "${table}" names no column with a defined value.`,
1858
+ void 0
1859
+ );
1860
+ }
1861
+ return statement;
1862
+ }
1863
+ /**
1864
+ * Whether a transaction's `update` reads the rows it changed back: the
1865
+ * caller named columns, or `*`. An empty list, like no option at all, asks
1866
+ * for nothing and `update` answers `[]`, as it always has. The rows come
1867
+ * from a read of the update's own WHERE on its own transaction, so they are
1868
+ * the model's view of the row, decoded as every read is decoded — which is
1869
+ * what the query-builder update returned for them, and why the named list
1870
+ * selects nothing narrower.
1871
+ */
1872
+ updateReturnsRows(returning) {
1873
+ return returning !== void 0 && !(Array.isArray(returning) && returning.length === 0);
1874
+ }
1641
1875
  /**
1642
1876
  * Classify an error into a DatabaseError.
1643
1877
  *
@@ -1650,14 +1884,14 @@ var DrizzleAdapter = class {
1650
1884
  *
1651
1885
  * @protected
1652
1886
  */
1653
- classifyError(error, sql3) {
1887
+ classifyError(error, sql5) {
1654
1888
  if (isDatabaseError(error)) {
1655
1889
  return error;
1656
1890
  }
1657
1891
  const errorMessage = error instanceof Error ? error.message : String(error);
1658
1892
  return this.createDatabaseError(
1659
1893
  "query",
1660
- sql3 ? `Query failed: ${errorMessage}` : errorMessage,
1894
+ sql5 ? `Query failed: ${errorMessage}` : errorMessage,
1661
1895
  error instanceof Error ? error : void 0
1662
1896
  );
1663
1897
  }
@@ -1678,8 +1912,9 @@ var DrizzleAdapter = class {
1678
1912
  */
1679
1913
  handleQueryError(error, operation, table) {
1680
1914
  const dbError = this.classifyError(error);
1681
- if (!dbError.message.includes(operation)) {
1682
- dbError.message = `${operation} operation failed on table '${table}': ${dbError.message}`;
1915
+ const context = `${operation} operation failed on table '${table}'`;
1916
+ if (!dbError.message.includes(context)) {
1917
+ dbError.message = `${context}: ${dbError.message}`;
1683
1918
  }
1684
1919
  if (!dbError.table) {
1685
1920
  dbError.table = table;
@@ -1687,6 +1922,34 @@ var DrizzleAdapter = class {
1687
1922
  return dbError;
1688
1923
  }
1689
1924
  };
1925
+ var IDENTITY_READ_BACK_CHUNK = 200;
1926
+ function rowsByIdentity(identity, rows) {
1927
+ if (identity.length === 1) {
1928
+ const [key] = identity;
1929
+ return {
1930
+ and: [
1931
+ {
1932
+ column: key.jsName,
1933
+ op: "IN",
1934
+ value: rows.map((row) => row[key.sqlName])
1935
+ }
1936
+ ]
1937
+ };
1938
+ }
1939
+ return {
1940
+ or: rows.map(
1941
+ (row) => ({
1942
+ and: identity.map(
1943
+ (key) => ({
1944
+ column: key.jsName,
1945
+ op: "=",
1946
+ value: row[key.sqlName]
1947
+ })
1948
+ )
1949
+ })
1950
+ )
1951
+ };
1952
+ }
1690
1953
  var ALLOWED_DEFAULT_SQL_EXPRESSIONS = /* @__PURE__ */ new Set([
1691
1954
  "current_timestamp",
1692
1955
  "now()",