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

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 (37) hide show
  1. package/dist/{adapter-C6ENLDau.d.cts → adapter-bUAaPzjV.d.cts} +307 -4
  2. package/dist/{adapter-D4GM1VAZ.d.ts → adapter-iJ5hbUvL.d.ts} +307 -4
  3. package/dist/index.cjs +570 -36
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +2 -2
  6. package/dist/index.d.ts +2 -2
  7. package/dist/index.mjs +571 -37
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/{migration-R06fsTrz.d.ts → migration-BqHebSwM.d.ts} +65 -6
  10. package/dist/{migration-B6AmjCQ1.d.cts → migration-Bu0WLdX4.d.cts} +65 -6
  11. package/dist/migrations.d.cts +3 -3
  12. package/dist/migrations.d.ts +3 -3
  13. package/dist/schema-name.cjs +20 -0
  14. package/dist/schema-name.cjs.map +1 -0
  15. package/dist/schema-name.d.cts +24 -0
  16. package/dist/schema-name.d.ts +24 -0
  17. package/dist/schema-name.mjs +17 -0
  18. package/dist/schema-name.mjs.map +1 -0
  19. package/dist/text-match.cjs +16 -0
  20. package/dist/text-match.cjs.map +1 -0
  21. package/dist/text-match.d.cts +44 -0
  22. package/dist/text-match.d.ts +44 -0
  23. package/dist/text-match.mjs +13 -0
  24. package/dist/text-match.mjs.map +1 -0
  25. package/dist/types/index.cjs +7 -0
  26. package/dist/types/index.cjs.map +1 -1
  27. package/dist/types/index.d.cts +16 -2
  28. package/dist/types/index.d.ts +16 -2
  29. package/dist/types/index.mjs +7 -0
  30. package/dist/types/index.mjs.map +1 -1
  31. package/dist/version-check.cjs +10 -1
  32. package/dist/version-check.cjs.map +1 -1
  33. package/dist/version-check.d.cts +18 -2
  34. package/dist/version-check.d.ts +18 -2
  35. package/dist/version-check.mjs +10 -2
  36. package/dist/version-check.mjs.map +1 -1
  37. package/package.json +14 -4
package/dist/index.mjs CHANGED
@@ -1,6 +1,21 @@
1
- import { getColumns, count, desc, asc, sql, and, or, not, notBetween, between, isNotNull, isNull, notInArray, inArray, ilike, like, lte, gte, lt, gt, ne, eq } from 'drizzle-orm';
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ import { getColumns, count, sql, desc, asc, and, or, not, is, SQL, Column, notBetween, between, isNotNull, isNull, notInArray, inArray, like, lte, gte, lt, gt, ne, eq } from 'drizzle-orm';
2
3
 
3
4
  // src/adapter.ts
5
+
6
+ // src/column-kinds.ts
7
+ function isJsonColumn(column) {
8
+ return column.dataType === "json" || column.columnType === "PgJsonb" || column.columnType === "PgJson" || column.columnType === "MySqlJson" || column.columnType === "SQLiteTextJson";
9
+ }
10
+ function isStructuredValue(value) {
11
+ if (value === null || typeof value !== "object") return false;
12
+ if (value instanceof Date) return false;
13
+ if (Buffer.isBuffer(value) || ArrayBuffer.isView(value)) return false;
14
+ return true;
15
+ }
16
+ function bindStructuredAsJson(value) {
17
+ return isStructuredValue(value) ? JSON.stringify(value) : value;
18
+ }
4
19
  function buildDrizzleOrderBy(columns, orderBy) {
5
20
  if (!orderBy?.length) return [];
6
21
  return orderBy.flatMap((spec) => {
@@ -12,6 +27,14 @@ function buildDrizzleOrderBy(columns, orderBy) {
12
27
  return [spec.nulls === "last" ? asc(isNull2) : desc(isNull2), direction];
13
28
  });
14
29
  }
30
+ function containsPattern(value) {
31
+ return `%${value.replace(/[!%_]/g, (character) => `!${character}`)}%`;
32
+ }
33
+ function caseInsensitiveContains(column, value) {
34
+ return sql`lower(${column}) like lower(${containsPattern(value)}) escape '!'`;
35
+ }
36
+
37
+ // src/drizzle-where.ts
15
38
  function buildDrizzleWhere(table, where) {
16
39
  const columns = getColumns(table);
17
40
  return processWhereClause(columns, where);
@@ -64,9 +87,6 @@ function isWhereCondition(item) {
64
87
  function namesABranch(where) {
65
88
  return Boolean(where.and?.length || where.or?.length || where.not);
66
89
  }
67
- function containsPattern(value) {
68
- return `%${value.replace(/[!%_]/g, (character) => `!${character}`)}%`;
69
- }
70
90
  function buildCondition(columns, cond) {
71
91
  const column = columns[cond.column];
72
92
  if (!column) {
@@ -91,7 +111,7 @@ function buildCondition(columns, cond) {
91
111
  case "LIKE":
92
112
  return like(col, cond.value);
93
113
  case "ILIKE":
94
- return ilike(col, cond.value);
114
+ return sql`lower(${col}) like lower(${cond.value})`;
95
115
  case "IN":
96
116
  return inArray(col, cond.value);
97
117
  case "NOT IN":
@@ -106,11 +126,45 @@ function buildCondition(columns, cond) {
106
126
  return notBetween(col, cond.value, cond.valueTo);
107
127
  case "CONTAINS":
108
128
  return sql`${col} like ${containsPattern(String(cond.value))} escape '!'`;
129
+ case "ICONTAINS":
130
+ return caseInsensitiveContains(col, String(cond.value));
109
131
  default:
110
132
  throw new Error(`Unsupported operator: ${cond.op}`);
111
133
  }
112
134
  }
113
135
 
136
+ // src/pinned-transaction.ts
137
+ function pinTransactions(db, checkout) {
138
+ const pinned = async (work, config) => {
139
+ const connection = await checkout();
140
+ let dirty = false;
141
+ let workFailure;
142
+ try {
143
+ return await connection.drizzle.transaction(async (tx) => {
144
+ try {
145
+ return await work(tx);
146
+ } catch (error) {
147
+ workFailure = { error };
148
+ throw error;
149
+ }
150
+ }, config);
151
+ } catch (error) {
152
+ await connection.rollback().catch(() => {
153
+ dirty = true;
154
+ });
155
+ throw workFailure ? workFailure.error : error;
156
+ } finally {
157
+ connection.release(dirty);
158
+ }
159
+ };
160
+ Object.defineProperty(db, "transaction", {
161
+ value: pinned,
162
+ configurable: true,
163
+ writable: true
164
+ });
165
+ return db;
166
+ }
167
+
114
168
  // src/transaction-forwarders.ts
115
169
  function createTransactionForwarders(delegator, txDb) {
116
170
  return {
@@ -120,9 +174,6 @@ function createTransactionForwarders(delegator, txDb) {
120
174
  selectOne: async (table, options) => {
121
175
  return delegator.selectOne(table, options, txDb());
122
176
  },
123
- update: async (table, data, where, options) => {
124
- return delegator.update(table, data, where, options, txDb());
125
- },
126
177
  updateCount: async (table, data, where) => {
127
178
  return delegator.updateCount(table, data, where, txDb());
128
179
  },
@@ -135,6 +186,20 @@ function createTransactionForwarders(delegator, txDb) {
135
186
  getDrizzle: () => txDb()
136
187
  };
137
188
  }
189
+ function transactionDrizzleHandles(build) {
190
+ let bareInstance;
191
+ const bare = () => bareInstance ??= build();
192
+ const relational = /* @__PURE__ */ new WeakMap();
193
+ const withRelations = (relations) => {
194
+ let cached = relational.get(relations);
195
+ if (cached === void 0) {
196
+ cached = build(relations);
197
+ relational.set(relations, cached);
198
+ }
199
+ return cached;
200
+ };
201
+ return { bare, withRelations };
202
+ }
138
203
 
139
204
  // src/types/error.ts
140
205
  function isDatabaseError(error) {
@@ -153,6 +218,74 @@ function createDatabaseError(options) {
153
218
  if (options.cause !== void 0) error.cause = options.cause;
154
219
  return error;
155
220
  }
221
+ function isBindableColumn(value) {
222
+ return typeof value === "object" && value !== null && typeof value.name === "string" && typeof value.mapToDriverValue === "function";
223
+ }
224
+ function valueForJsonColumn(value) {
225
+ if (typeof value !== "string") return value;
226
+ try {
227
+ return JSON.parse(value);
228
+ } catch {
229
+ return value;
230
+ }
231
+ }
232
+ function columnsOf(tableObj) {
233
+ const byName = /* @__PURE__ */ new Map();
234
+ const declared = [];
235
+ for (const [jsName, column] of Object.entries(
236
+ getColumns(tableObj)
237
+ )) {
238
+ if (!isBindableColumn(column)) continue;
239
+ byName.set(column.name, column);
240
+ byName.set(jsName, column);
241
+ declared.push(column);
242
+ }
243
+ return { byName, declared };
244
+ }
245
+ function boundValue(value, column, bindUnmodeled) {
246
+ if (is(value, SQL) || is(value, Column)) return sql`${value}`;
247
+ if (column) {
248
+ return sql`${sql.param(
249
+ isJsonColumn(column) ? valueForJsonColumn(value) : value,
250
+ column
251
+ )}`;
252
+ }
253
+ return sql`${sql.param(bindUnmodeled(value))}`;
254
+ }
255
+ function buildUpdateStatement(input) {
256
+ const { byName, declared } = columnsOf(input.tableObj);
257
+ const assignments = /* @__PURE__ */ new Map();
258
+ for (const [key, value] of Object.entries(input.data)) {
259
+ const column = byName.get(key);
260
+ const name = column?.name ?? key;
261
+ if (value === void 0) {
262
+ assignments.delete(name);
263
+ continue;
264
+ }
265
+ assignments.set(
266
+ name,
267
+ sql`${sql.identifier(name)} = ${boundValue(value, column, input.bindUnmodeled)}`
268
+ );
269
+ }
270
+ if (assignments.size === 0) return null;
271
+ for (const column of declared) {
272
+ if (assignments.has(column.name) || column.onUpdateFn === void 0) {
273
+ continue;
274
+ }
275
+ const value = column.onUpdateFn();
276
+ assignments.set(
277
+ column.name,
278
+ sql`${sql.identifier(column.name)} = ${boundValue(value, column, input.bindUnmodeled)}`
279
+ );
280
+ }
281
+ const statement = sql`UPDATE ${input.tableObj} SET ${sql.join([...assignments.values()], sql`, `)}`;
282
+ const condition = buildDrizzleWhere(input.tableObj, input.where);
283
+ if (condition) statement.append(sql` WHERE ${condition}`);
284
+ if (input.returning && input.returning.length > 0) {
285
+ statement.append(sql` RETURNING ${sql.join(input.returning, sql`, `)}`);
286
+ }
287
+ return statement;
288
+ }
156
289
 
157
290
  // src/adapter.ts
158
291
  var DATE_DATA_TYPE = /(?:^|\s)date$/;
@@ -201,6 +334,155 @@ function affectedRowCount(result) {
201
334
  return 0;
202
335
  }
203
336
  var DrizzleAdapter = class {
337
+ /**
338
+ * Whether a `transaction()` call made from the current async context would
339
+ * join a transaction already open on this adapter, as a savepoint, rather
340
+ * than open one of its own.
341
+ *
342
+ * A caller about to do something no rollback can undo — removing a stored
343
+ * file, say — asks this first: work that joins an enclosing transaction is
344
+ * undone with it, while the effect it performs after its own savepoint
345
+ * releases is not.
346
+ *
347
+ * False here. Concrete rather than abstract so existing adapters keep
348
+ * working unchanged, and false is right for a pooled adapter: a call made
349
+ * inside another transaction's work opens its own transaction on its own
350
+ * connection, which commits independently.
351
+ *
352
+ * @returns True when the call would nest in an open transaction
353
+ */
354
+ inTransaction() {
355
+ return false;
356
+ }
357
+ /**
358
+ * The transaction each async flow is inside, kept per flow rather than per
359
+ * connection: on SQLite every request shares the one connection, and
360
+ * another request's open transaction says nothing about this one.
361
+ */
362
+ flowTransactions = new AsyncLocalStorage();
363
+ /**
364
+ * Whether the calling async flow is inside a transaction on this adapter
365
+ * that has not finished: its own, or one it was started from inside.
366
+ *
367
+ * A write that must outlive any rollback — a count of a failed attempt, the
368
+ * spend of a single-use secret — asks this before running in a transaction
369
+ * of its own. Made inside an open transaction on SQLite, it joins that
370
+ * transaction as a savepoint and is undone with it. On PostgreSQL and MySQL
371
+ * it would commit on a connection of its own, so refusing it there too
372
+ * keeps code that passes on one dialect from failing on another. Unlike
373
+ * {@link inTransaction}, which asks whether a call would nest and is always
374
+ * false on a pooled adapter, this answers alike on every dialect, and
375
+ * another flow's open transaction never answers true.
376
+ *
377
+ * Covers transactions opened through {@link transaction} and through the
378
+ * Drizzle handle's own `transaction` on the dialects whose handle takes
379
+ * async work. A dialect that keeps scopes of its own judges from those
380
+ * instead; an adapter that does neither answers false.
381
+ *
382
+ * @experimental
383
+ * @returns True while a transaction the calling flow is inside is open
384
+ */
385
+ transactionOpenInFlow() {
386
+ for (let at = this.flowTransactions.getStore(); at !== void 0; at = at.enclosing) {
387
+ if (at.open) return true;
388
+ }
389
+ return false;
390
+ }
391
+ /**
392
+ * Run a whole transaction, from its BEGIN until its COMMIT or ROLLBACK has
393
+ * run, as the calling flow's open transaction, for
394
+ * {@link transactionOpenInFlow}. A dialect's `transaction` runs through
395
+ * this unless it judges the flow from scopes of its own.
396
+ *
397
+ * The transaction counts as open for its work and for anything the work
398
+ * starts, awaited or not, until it has finished. Something the work starts
399
+ * that outlives it then sees it closed, or the one enclosing it still open.
400
+ *
401
+ * @param run - The transaction, BEGIN to COMMIT or ROLLBACK
402
+ * @returns What `run` resolves to
403
+ */
404
+ runInFlowTransaction(run) {
405
+ const scope = {
406
+ open: true,
407
+ enclosing: this.flowTransactions.getStore()
408
+ };
409
+ return this.flowTransactions.run(scope, async () => {
410
+ try {
411
+ return await run();
412
+ } finally {
413
+ scope.open = false;
414
+ }
415
+ });
416
+ }
417
+ /**
418
+ * Run each of a pool-bound Drizzle handle's transactions on a connection
419
+ * `checkout` hands it, and return that connection as the adapter's own
420
+ * `transaction()` does: closed rather than pooled when no ROLLBACK can be
421
+ * confirmed on it, and never leaked by a failed BEGIN (`pinTransactions`).
422
+ *
423
+ * For PostgreSQL's and MySQL's handles. Apply it before
424
+ * {@link trackFlowTransactions}, which wraps whatever `transaction` the
425
+ * handle has at that point.
426
+ *
427
+ * @param db - A Drizzle handle on this adapter's pool
428
+ * @param checkout - Checks out a connection for one transaction
429
+ * @returns The same handle, its `transaction` replaced
430
+ */
431
+ pinDrizzleTransactions(db, checkout) {
432
+ return pinTransactions(db, checkout);
433
+ }
434
+ /**
435
+ * Make a Drizzle handle's own `transaction` count for
436
+ * {@link transactionOpenInFlow}, as the adapter's does.
437
+ *
438
+ * Core's services run their transactions through the handle on PostgreSQL
439
+ * and MySQL; without this, a call made inside one would be refused on
440
+ * SQLite, where they go through the adapter, and let through on the
441
+ * others. Not for SQLite's handle: its `transaction` takes only
442
+ * synchronous work, which no async flow can be inside.
443
+ *
444
+ * @param db - A Drizzle handle this adapter created
445
+ * @returns The same handle, its `transaction` tracked
446
+ */
447
+ trackFlowTransactions(db) {
448
+ const transactor = db;
449
+ const untracked = transactor.transaction.bind(transactor);
450
+ const tracked = (work, config) => this.runInFlowTransaction(() => untracked(work, config));
451
+ Object.defineProperty(db, "transaction", {
452
+ value: tracked,
453
+ configurable: true,
454
+ writable: true
455
+ });
456
+ return db;
457
+ }
458
+ /**
459
+ * Run `effect` once the write made from the current async context has
460
+ * committed: an event, a hook, a cache flush, anything outside the database
461
+ * that must not happen for a change that is rolled back.
462
+ *
463
+ * A caller invokes this after its own transaction has resolved. Where that
464
+ * transaction was nested in an enclosing one, as a savepoint, the change is
465
+ * not durable yet, and an adapter that nests (see {@link inTransaction})
466
+ * holds `effect` until the outermost transaction commits, and drops it when
467
+ * that transaction, or the savepoint it was registered in, rolls back. A
468
+ * held effect runs after the commit, before the outermost `transaction()`
469
+ * call resolves, in registration order; one that then throws or rejects is
470
+ * handed to `onDeferredFailure` rather than failing a transaction that has
471
+ * committed.
472
+ *
473
+ * Here `effect` runs now and the returned promise settles with it, its
474
+ * failure included. Concrete rather than abstract, as `inTransaction` is,
475
+ * and right for a pooled adapter: each transaction has its own connection
476
+ * and has committed by the time its caller continues.
477
+ *
478
+ * @experimental
479
+ * @param effect - What to run once the change is durable
480
+ * @param onDeferredFailure - Receives the error of a held effect that fails
481
+ * @returns Settles once `effect` has run, or once it has been held
482
+ */
483
+ async afterCommit(effect, _onDeferredFailure) {
484
+ await effect();
485
+ }
204
486
  // ============================================================
205
487
  // Drizzle Query API Support
206
488
  // ============================================================
@@ -284,9 +566,7 @@ var DrizzleAdapter = class {
284
566
  continue;
285
567
  const sqlName = colDef.name;
286
568
  sqlToJs.set(sqlName, jsName);
287
- const dataType = colDef.dataType;
288
- const columnType = colDef.columnType;
289
- if (dataType === "json" || columnType === "PgJsonb" || columnType === "PgJson" || columnType === "MySqlJson" || columnType === "SQLiteTextJson") {
569
+ if (isJsonColumn(colDef)) {
290
570
  jsonColumns.add(jsName);
291
571
  }
292
572
  }
@@ -1150,7 +1430,8 @@ var DrizzleAdapter = class {
1150
1430
  * @remarks
1151
1431
  * Default implementation uses dialect-specific ON CONFLICT syntax.
1152
1432
  * PostgreSQL/SQLite: ON CONFLICT ... DO UPDATE
1153
- * MySQL: ON DUPLICATE KEY UPDATE
1433
+ * MySQL: the MySQL adapter overrides this, resolving the conflict by the
1434
+ * conflict columns (see its `upsert`)
1154
1435
  *
1155
1436
  * @param table - Table name
1156
1437
  * @param data - Record data
@@ -1187,9 +1468,12 @@ var DrizzleAdapter = class {
1187
1468
  updateData[key] = data[key];
1188
1469
  }
1189
1470
  }
1471
+ const keepExisting = Object.keys(updateData).length === 0;
1472
+ this.assertUpsertTarget(table, data, options, keepExisting);
1190
1473
  let query;
1191
1474
  if (caps.supportsOnConflict) {
1192
- query = db.insert(tableObj).values(data).onConflictDoUpdate({
1475
+ const inserted = db.insert(tableObj).values(data);
1476
+ query = keepExisting ? inserted.onConflictDoNothing({ target: conflictTarget }) : inserted.onConflictDoUpdate({
1193
1477
  target: conflictTarget,
1194
1478
  set: updateData
1195
1479
  });
@@ -1198,21 +1482,24 @@ var DrizzleAdapter = class {
1198
1482
  }
1199
1483
  if (caps.supportsReturning) {
1200
1484
  const result = await query.returning();
1201
- return Array.isArray(result) ? result[0] : result;
1485
+ const row = Array.isArray(result) ? result[0] : result;
1486
+ if (row !== void 0) return row;
1487
+ } else {
1488
+ await query;
1202
1489
  }
1203
- await query;
1204
- if (options.conflictColumns.length && data[options.conflictColumns[0]] !== void 0) {
1490
+ const conflictValues = options.conflictColumns.map(
1491
+ (column) => [column, data[column]]
1492
+ );
1493
+ if (conflictValues.length > 0 && conflictValues.every(([, value]) => value !== void 0)) {
1205
1494
  return await this.selectOne(
1206
1495
  table,
1207
1496
  {
1208
1497
  where: {
1209
- and: [
1210
- {
1211
- column: options.conflictColumns[0],
1212
- op: "=",
1213
- value: data[options.conflictColumns[0]]
1214
- }
1215
- ]
1498
+ and: conflictValues.map(([column, value]) => ({
1499
+ column,
1500
+ op: "=",
1501
+ value
1502
+ }))
1216
1503
  }
1217
1504
  },
1218
1505
  executor
@@ -1456,11 +1743,11 @@ var DrizzleAdapter = class {
1456
1743
  */
1457
1744
  async tableExists(tableName, schema) {
1458
1745
  try {
1459
- let sql3;
1746
+ let sql6;
1460
1747
  const params = [];
1461
1748
  switch (this.dialect) {
1462
1749
  case "postgresql":
1463
- sql3 = `
1750
+ sql6 = `
1464
1751
  SELECT EXISTS (
1465
1752
  SELECT FROM information_schema.tables
1466
1753
  WHERE table_schema = COALESCE($1, (
@@ -1474,7 +1761,7 @@ var DrizzleAdapter = class {
1474
1761
  params.push(schema ?? null, tableName);
1475
1762
  break;
1476
1763
  case "mysql":
1477
- sql3 = `
1764
+ sql6 = `
1478
1765
  SELECT COUNT(*) as count
1479
1766
  FROM information_schema.tables
1480
1767
  WHERE table_schema = DATABASE()
@@ -1483,7 +1770,7 @@ var DrizzleAdapter = class {
1483
1770
  params.push(tableName);
1484
1771
  break;
1485
1772
  case "sqlite":
1486
- sql3 = `
1773
+ sql6 = `
1487
1774
  SELECT COUNT(*) as count
1488
1775
  FROM sqlite_master
1489
1776
  WHERE type = 'table'
@@ -1499,7 +1786,7 @@ var DrizzleAdapter = class {
1499
1786
  );
1500
1787
  }
1501
1788
  const results = await this.executeQuery(
1502
- sql3,
1789
+ sql6,
1503
1790
  params
1504
1791
  );
1505
1792
  if (results.length === 0) {
@@ -1531,11 +1818,11 @@ var DrizzleAdapter = class {
1531
1818
  */
1532
1819
  async listTables(schema) {
1533
1820
  try {
1534
- let sql3;
1821
+ let sql6;
1535
1822
  const params = [];
1536
1823
  switch (this.dialect) {
1537
1824
  case "postgresql":
1538
- sql3 = `
1825
+ sql6 = `
1539
1826
  SELECT table_name
1540
1827
  FROM information_schema.tables
1541
1828
  WHERE table_schema = $1
@@ -1545,7 +1832,7 @@ var DrizzleAdapter = class {
1545
1832
  params.push(schema ?? "public");
1546
1833
  break;
1547
1834
  case "mysql":
1548
- sql3 = `
1835
+ sql6 = `
1549
1836
  SELECT table_name AS table_name
1550
1837
  FROM information_schema.tables
1551
1838
  WHERE table_schema = DATABASE()
@@ -1554,7 +1841,7 @@ var DrizzleAdapter = class {
1554
1841
  `;
1555
1842
  break;
1556
1843
  case "sqlite":
1557
- sql3 = `
1844
+ sql6 = `
1558
1845
  SELECT name as table_name
1559
1846
  FROM sqlite_master
1560
1847
  WHERE type = 'table'
@@ -1570,7 +1857,7 @@ var DrizzleAdapter = class {
1570
1857
  );
1571
1858
  }
1572
1859
  const results = await this.executeQuery(
1573
- sql3,
1860
+ sql6,
1574
1861
  params
1575
1862
  );
1576
1863
  return results.map((row) => row.table_name);
@@ -1636,6 +1923,224 @@ var DrizzleAdapter = class {
1636
1923
  createTransactionForwarders(txDb) {
1637
1924
  return createTransactionForwarders(this, txDb);
1638
1925
  }
1926
+ /**
1927
+ * The Drizzle handles one transaction hands out; see
1928
+ * `transactionDrizzleHandles`. `build` makes an instance on the
1929
+ * transaction's own connection, with the relations config when given one.
1930
+ *
1931
+ * @protected
1932
+ */
1933
+ transactionDrizzleHandles(build) {
1934
+ return transactionDrizzleHandles(build);
1935
+ }
1936
+ /**
1937
+ * A transaction context's `update`, for each adapter to bind.
1938
+ *
1939
+ * Not forwarded to the pooled `update` the way `select` and `delete` are:
1940
+ * that one goes through the Drizzle query builder, which writes the columns
1941
+ * the runtime MODEL declares, and a transaction's update must reach the
1942
+ * columns the physical TABLE has — the localization transition window
1943
+ * writes a column the model has already moved to a companion table that
1944
+ * does not exist yet. The INSERT half of every transaction context already
1945
+ * builds its own statement for that reason; `update-statement.ts` is the
1946
+ * UPDATE half, spelled once for the three adapters, and this runs it: on
1947
+ * the transaction executor, classified with the operation and table named
1948
+ * as the pooled `update` classifies its failures, and read back through
1949
+ * `select` on the same executor when the caller asked for rows.
1950
+ *
1951
+ * That read is by the rows' IDENTITY where the dialect can report it: on
1952
+ * PostgreSQL and SQLite the statement carries `RETURNING <primary key>`, and
1953
+ * the read-back asks for exactly those rows — so a row another transaction
1954
+ * adds under the same predicate meanwhile is not among them, and an update
1955
+ * whose own write falsifies its predicate still reads its rows back. MySQL
1956
+ * has no RETURNING, so there the read re-runs the predicate, as the pooled
1957
+ * `update` always has on that dialect; a table with no primary key in its
1958
+ * model reads back the same way.
1959
+ *
1960
+ * @param txDb - thunk returning the transaction-bound Drizzle instance
1961
+ * @param run - how this dialect runs a statement on that instance, and the
1962
+ * rows it returns when the statement carries RETURNING: better-sqlite3
1963
+ * answers synchronously, the pooled drivers do not
1964
+ * @param bindUnmodeled - how a value binds when the model declares no
1965
+ * column for it; a declared column binds through its own encoder
1966
+ * @returns the context's `update` method
1967
+ */
1968
+ transactionUpdate(txDb, run, bindUnmodeled) {
1969
+ return async (table, data, where, options) => {
1970
+ const wantsRows = this.updateReturnsRows(options?.returning);
1971
+ const tableObj = this.resolvedTableObject(table);
1972
+ const identity = wantsRows && this.getCapabilities().supportsReturning ? this.identityColumns(tableObj) : [];
1973
+ let reported;
1974
+ try {
1975
+ reported = await run(
1976
+ this.buildTransactionUpdate(tableObj, table, data, where, {
1977
+ bindUnmodeled,
1978
+ returning: identity.map((key) => sql`${sql.identifier(key.sqlName)}`)
1979
+ }),
1980
+ identity.length > 0
1981
+ );
1982
+ } catch (error) {
1983
+ throw this.handleQueryError(error, "update", table);
1984
+ }
1985
+ if (!wantsRows) return [];
1986
+ if (identity.length === 0 || !Array.isArray(reported)) {
1987
+ return this.select(table, { where }, txDb());
1988
+ }
1989
+ const rows = [];
1990
+ for (let at = 0; at < reported.length; at += IDENTITY_READ_BACK_CHUNK) {
1991
+ rows.push(
1992
+ ...await this.select(
1993
+ table,
1994
+ {
1995
+ where: rowsByIdentity(
1996
+ identity,
1997
+ reported.slice(at, at + IDENTITY_READ_BACK_CHUNK)
1998
+ )
1999
+ },
2000
+ txDb()
2001
+ )
2002
+ );
2003
+ }
2004
+ return rows;
2005
+ };
2006
+ }
2007
+ /**
2008
+ * The columns that identify a row of this table, under both spellings: the
2009
+ * SQL name RETURNING reports and the Drizzle property name the query
2010
+ * builder's WHERE is addressed by. Columns flagged primary first; failing
2011
+ * that, the table-level primary key the dialect's table config carries
2012
+ * (`primaryKey({ columns })` leaves each column's flag false), which each
2013
+ * adapter reads through its own dialect. Empty when the model declares
2014
+ * neither, and the read-back falls back to the caller's predicate.
2015
+ */
2016
+ identityColumns(tableObj) {
2017
+ const columns = Object.entries(getColumns(tableObj));
2018
+ const flagged = columns.filter(([, c]) => c.primary === true);
2019
+ const members = flagged.length > 0 ? flagged : columns.filter(
2020
+ ([, c]) => this.compositePrimaryKey(tableObj).includes(c)
2021
+ );
2022
+ return members.flatMap(
2023
+ ([jsName, c]) => typeof c.name === "string" ? [{ jsName, sqlName: c.name }] : []
2024
+ );
2025
+ }
2026
+ /**
2027
+ * How a value binds to a column the model does not declare, on a driver
2028
+ * that binds scalars, dates and binary natively: a structured value goes
2029
+ * as JSON text, since what node-postgres and mysql2 make of a bare object
2030
+ * or array is not JSON. For the adapters to hand `transactionUpdate`.
2031
+ */
2032
+ bindUnmodeledStructuredAsJson(value) {
2033
+ return bindStructuredAsJson(value);
2034
+ }
2035
+ /**
2036
+ * Refuse an upsert target every dialect would not answer alike: a conflict
2037
+ * column named twice, which SQLite rejects as no constraint's target and
2038
+ * MySQL would accept; and, when nothing is to be updated, a conflict column
2039
+ * left for the database to fill, since the row that stands is then read
2040
+ * back by a value the caller never gave. Shared with the dialects that
2041
+ * override `upsert`, so the refusals are one rule.
2042
+ */
2043
+ assertUpsertTarget(table, data, options, keepExisting) {
2044
+ const columns = options.conflictColumns;
2045
+ if (new Set(columns).size !== columns.length) {
2046
+ throw createDatabaseError({
2047
+ kind: "query",
2048
+ message: `upsert on ${table} names a conflict column more than once (${columns.join(", ")}).`
2049
+ });
2050
+ }
2051
+ if (keepExisting && columns.some((column) => data[column] === void 0)) {
2052
+ throw createDatabaseError({
2053
+ kind: "query",
2054
+ message: `upsert on ${table} needs a value for every conflict column (${columns.join(", ")}).`
2055
+ });
2056
+ }
2057
+ }
2058
+ /**
2059
+ * The columns of a table-level `primaryKey({ columns })`, as the dialect's
2060
+ * table config reports them. The base class cannot read that config without
2061
+ * naming a dialect, so each adapter answers for its own; a dialect that does
2062
+ * not answer reports none.
2063
+ */
2064
+ compositePrimaryKey(_tableObj) {
2065
+ return [];
2066
+ }
2067
+ /** The registered table object, or the error every CRUD method throws without one. */
2068
+ resolvedTableObject(table) {
2069
+ const tableObj = this.getTableObject(table);
2070
+ if (!tableObj || typeof tableObj !== "object") {
2071
+ throw this.createDatabaseError(
2072
+ "query",
2073
+ `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`,
2074
+ void 0
2075
+ );
2076
+ }
2077
+ return tableObj;
2078
+ }
2079
+ /**
2080
+ * The statement `transactionUpdate` runs.
2081
+ *
2082
+ * @throws when nothing would be written — every key `undefined`, or none at
2083
+ * all. The query builder refused that too ("No values to set"), and a
2084
+ * patch that names nothing is a caller's mistake rather than a write of
2085
+ * nothing.
2086
+ */
2087
+ buildTransactionUpdate(tableObj, table, data, where, binding) {
2088
+ const statement = buildUpdateStatement({
2089
+ tableObj,
2090
+ data,
2091
+ where,
2092
+ bindUnmodeled: binding.bindUnmodeled,
2093
+ returning: binding.returning
2094
+ });
2095
+ if (!statement) {
2096
+ throw this.createDatabaseError(
2097
+ "query",
2098
+ `No values to set: the update of "${table}" names no column with a defined value.`,
2099
+ void 0
2100
+ );
2101
+ }
2102
+ return statement;
2103
+ }
2104
+ /**
2105
+ * Whether a transaction's `update` reads the rows it changed back: the
2106
+ * caller named columns, or `*`. An empty list, like no option at all, asks
2107
+ * for nothing and `update` answers `[]`, as it always has. The rows come
2108
+ * from a read of the update's own WHERE on its own transaction, so they are
2109
+ * the model's view of the row, decoded as every read is decoded — which is
2110
+ * what the query-builder update returned for them, and why the named list
2111
+ * selects nothing narrower.
2112
+ */
2113
+ updateReturnsRows(returning) {
2114
+ return returning !== void 0 && !(Array.isArray(returning) && returning.length === 0);
2115
+ }
2116
+ /**
2117
+ * The driver's own error inside `error`: the first in its `cause` chain that
2118
+ * `isDriverError` recognises, or `error` itself when none does.
2119
+ *
2120
+ * A query that runs through Drizzle fails with Drizzle's `DrizzleQueryError`,
2121
+ * whose message is the SQL statement and which carries no driver code; the
2122
+ * driver's error, the only object that knows what actually went wrong, is
2123
+ * its `cause`. A dialect's {@link classifyError} classifies from what this
2124
+ * returns, so a failure reads the same whether or not Drizzle wrapped it.
2125
+ *
2126
+ * The walk is bounded, because an error chain can be made cyclic and a
2127
+ * classifier that can hang is worse than one that gives up. Falling back to
2128
+ * `error` keeps an error that carries no code anywhere classified as before.
2129
+ *
2130
+ * @param error - The error as it was caught
2131
+ * @param isDriverError - Whether a link in the chain is the driver's error
2132
+ * @returns The driver's error, or `error` when no link is
2133
+ */
2134
+ driverErrorIn(error, isDriverError) {
2135
+ let cursor = error;
2136
+ for (let depth = 0; depth < 10; depth++) {
2137
+ if (cursor === null || typeof cursor !== "object") break;
2138
+ const candidate = cursor;
2139
+ if (isDriverError(candidate)) return candidate;
2140
+ cursor = candidate.cause;
2141
+ }
2142
+ return error ?? {};
2143
+ }
1639
2144
  /**
1640
2145
  * Classify an error into a DatabaseError.
1641
2146
  *
@@ -1648,14 +2153,14 @@ var DrizzleAdapter = class {
1648
2153
  *
1649
2154
  * @protected
1650
2155
  */
1651
- classifyError(error, sql3) {
2156
+ classifyError(error, sql6) {
1652
2157
  if (isDatabaseError(error)) {
1653
2158
  return error;
1654
2159
  }
1655
2160
  const errorMessage = error instanceof Error ? error.message : String(error);
1656
2161
  return this.createDatabaseError(
1657
2162
  "query",
1658
- sql3 ? `Query failed: ${errorMessage}` : errorMessage,
2163
+ sql6 ? `Query failed: ${errorMessage}` : errorMessage,
1659
2164
  error instanceof Error ? error : void 0
1660
2165
  );
1661
2166
  }
@@ -1676,8 +2181,9 @@ var DrizzleAdapter = class {
1676
2181
  */
1677
2182
  handleQueryError(error, operation, table) {
1678
2183
  const dbError = this.classifyError(error);
1679
- if (!dbError.message.includes(operation)) {
1680
- dbError.message = `${operation} operation failed on table '${table}': ${dbError.message}`;
2184
+ const context = `${operation} operation failed on table '${table}'`;
2185
+ if (!dbError.message.includes(context)) {
2186
+ dbError.message = `${context}: ${dbError.message}`;
1681
2187
  }
1682
2188
  if (!dbError.table) {
1683
2189
  dbError.table = table;
@@ -1685,6 +2191,34 @@ var DrizzleAdapter = class {
1685
2191
  return dbError;
1686
2192
  }
1687
2193
  };
2194
+ var IDENTITY_READ_BACK_CHUNK = 200;
2195
+ function rowsByIdentity(identity, rows) {
2196
+ if (identity.length === 1) {
2197
+ const [key] = identity;
2198
+ return {
2199
+ and: [
2200
+ {
2201
+ column: key.jsName,
2202
+ op: "IN",
2203
+ value: rows.map((row) => row[key.sqlName])
2204
+ }
2205
+ ]
2206
+ };
2207
+ }
2208
+ return {
2209
+ or: rows.map(
2210
+ (row) => ({
2211
+ and: identity.map(
2212
+ (key) => ({
2213
+ column: key.jsName,
2214
+ op: "=",
2215
+ value: row[key.sqlName]
2216
+ })
2217
+ )
2218
+ })
2219
+ )
2220
+ };
2221
+ }
1688
2222
  var ALLOWED_DEFAULT_SQL_EXPRESSIONS = /* @__PURE__ */ new Set([
1689
2223
  "current_timestamp",
1690
2224
  "now()",