@nextlyhq/adapter-drizzle 0.0.2-alpha.6 → 0.0.2-alpha.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
- import { getTableColumns, desc, asc, and, or, not, like, notBetween, between, isNotNull, isNull, notInArray, inArray, ilike, lte, gte, lt, gt, ne, eq } from 'drizzle-orm';
1
+ import { getColumns, desc, asc, and, or, not, like, notBetween, between, isNotNull, isNull, notInArray, inArray, ilike, lte, gte, lt, gt, ne, eq } from 'drizzle-orm';
2
2
 
3
3
  // src/adapter.ts
4
4
  function buildDrizzleWhere(table, where) {
5
- const columns = getTableColumns(table);
5
+ const columns = getColumns(table);
6
6
  return processWhereClause(columns, where);
7
7
  }
8
8
  function processWhereClause(columns, where) {
@@ -106,6 +106,51 @@ function createDatabaseError(options) {
106
106
  }
107
107
 
108
108
  // src/adapter.ts
109
+ var DATE_DATA_TYPE = /(?:^|\s)date$/;
110
+ var WALL_CLOCK_ALIAS_STEM = "__nx_wc";
111
+ var WALL_CLOCK_PATTERN = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?$/;
112
+ function parseWallClockAsUtc(text) {
113
+ const match = WALL_CLOCK_PATTERN.exec(text.trim());
114
+ if (!match) return void 0;
115
+ const [, year, month, day, hour, minute, second, fraction = ""] = match;
116
+ const millis = Number(`${fraction}000`.slice(0, 3));
117
+ const parsed = new Date(
118
+ Date.UTC(
119
+ Number(year),
120
+ Number(month) - 1,
121
+ Number(day),
122
+ Number(hour),
123
+ Number(minute),
124
+ Number(second),
125
+ millis
126
+ )
127
+ );
128
+ if (Number(year) < 100) parsed.setUTCFullYear(Number(year));
129
+ return parsed;
130
+ }
131
+ function isDateColumn(colDef) {
132
+ if (!colDef || typeof colDef !== "object") return false;
133
+ const candidate = colDef;
134
+ return typeof candidate.mapFromDriverValue === "function" && typeof candidate.mapToDriverValue === "function" && DATE_DATA_TYPE.test(String(candidate.dataType));
135
+ }
136
+ function carriesTimeZone(colDef) {
137
+ return colDef.withTimezone === true;
138
+ }
139
+ function affectedRowCount(result) {
140
+ if (Array.isArray(result)) {
141
+ const header = result[0];
142
+ if (header && typeof header.affectedRows === "number") {
143
+ return header.affectedRows;
144
+ }
145
+ return result.length;
146
+ }
147
+ const record = result;
148
+ const rowCount = record?.rowCount;
149
+ if (typeof rowCount === "number") return rowCount;
150
+ const changes = record?.changes;
151
+ if (typeof changes === "number") return changes;
152
+ return 0;
153
+ }
109
154
  var DrizzleAdapter = class {
110
155
  // ============================================================
111
156
  // Drizzle Query API Support
@@ -134,6 +179,45 @@ var DrizzleAdapter = class {
134
179
  getTableObject(tableName) {
135
180
  return this.tableResolver?.getTable(tableName) ?? null;
136
181
  }
182
+ /**
183
+ * Run a Drizzle-built statement and return its rows.
184
+ *
185
+ * @remarks
186
+ * The CRUD methods resolve their table through the schema registry and reject
187
+ * any name it does not declare, which leaves no way to read from a table the
188
+ * ORM does not know — one mid-rename, above all — except by assembling SQL and
189
+ * quoting identifiers by hand. This takes Drizzle's `sql` template instead, so
190
+ * the dialect in use decides the quoting and the parameter binding.
191
+ *
192
+ * Concrete rather than abstract so existing adapters keep working unchanged.
193
+ * The three drivers disagree about both the call and the result: node-postgres
194
+ * returns `{ rows }`, mysql2 a `[rows, fields]` tuple, and better-sqlite3 has
195
+ * no `execute` at all and answers `all`. Keeping that here rather than at each
196
+ * call site is the point — a caller reasoning about it would be reasoning
197
+ * about a driver it cannot see.
198
+ *
199
+ * @param statement - Drizzle `sql` template to run
200
+ * @returns Rows the statement produced
201
+ */
202
+ async queryStatement(statement) {
203
+ if (this.getCapabilities().dialect === "sqlite") {
204
+ const db2 = this.getDrizzle();
205
+ return await db2.all(statement);
206
+ }
207
+ const db = this.getDrizzle();
208
+ const result = await db.execute(statement);
209
+ if (Array.isArray(result)) {
210
+ return Array.isArray(result[0]) ? result[0] : result;
211
+ }
212
+ if (typeof result === "object" && result !== null && Array.isArray(result.rows)) {
213
+ return result.rows;
214
+ }
215
+ throw this.createDatabaseError(
216
+ "query",
217
+ "Drizzle statement returned a result shape this adapter does not recognise; refusing to report it as an empty result.",
218
+ void 0
219
+ );
220
+ }
137
221
  /**
138
222
  * Map data keys from SQL column names (snake_case) to Drizzle JS property names (camelCase).
139
223
  * Drizzle schemas define columns as e.g. `createdAt: timestamp("created_at")` — the JS
@@ -173,6 +257,281 @@ var DrizzleAdapter = class {
173
257
  }
174
258
  return mapped;
175
259
  }
260
+ /**
261
+ * Map data keys from Drizzle JS property names to SQL column names for the
262
+ * raw-SQL transaction insert path. The transaction context builds INSERT
263
+ * statements from Object.keys(data) used directly as column identifiers, so a
264
+ * table whose Drizzle property names differ from its SQL column names
265
+ * (camelCase core tables like nextly_versions) needs its keys translated
266
+ * first. For tables whose property names already equal their column names
267
+ * (the dynamic dc_/single_/comp_ tables) every lookup is identity, so
268
+ * existing callers are unaffected.
269
+ */
270
+ mapKeysToSqlColumns(tableObj, data) {
271
+ if (!tableObj || typeof tableObj !== "object") return data;
272
+ const jsToSql = /* @__PURE__ */ new Map();
273
+ for (const [jsName, colDef] of Object.entries(
274
+ tableObj
275
+ )) {
276
+ if (colDef && typeof colDef === "object" && "name" in colDef && typeof colDef.name === "string") {
277
+ jsToSql.set(jsName, colDef.name);
278
+ }
279
+ }
280
+ if (jsToSql.size === 0) return data;
281
+ const out = {};
282
+ for (const [key, value] of Object.entries(data)) {
283
+ out[jsToSql.get(key) ?? key] = value;
284
+ }
285
+ return out;
286
+ }
287
+ /**
288
+ * Map a list of column identifiers (Drizzle property names) to their SQL
289
+ * column names, for the raw-SQL transaction insert paths that build a
290
+ * RETURNING clause from `options.returning`. Same identity behavior as
291
+ * `mapKeysToSqlColumns`: names that are already SQL columns (the dynamic
292
+ * dc_/single_/comp_ tables) pass through unchanged.
293
+ */
294
+ mapColumnNamesToSql(tableObj, names) {
295
+ if (!tableObj || typeof tableObj !== "object") return names;
296
+ const jsToSql = /* @__PURE__ */ new Map();
297
+ for (const [jsName, colDef] of Object.entries(
298
+ tableObj
299
+ )) {
300
+ if (colDef && typeof colDef === "object" && "name" in colDef && typeof colDef.name === "string") {
301
+ jsToSql.set(jsName, colDef.name);
302
+ }
303
+ }
304
+ if (jsToSql.size === 0) return names;
305
+ return names.map((n) => jsToSql.get(n) ?? n);
306
+ }
307
+ /**
308
+ * Remap a raw-SQL result row's KEYS from SQL column names to Drizzle property
309
+ * names, so the raw-SQL transaction insert paths return the same key casing
310
+ * as the non-transactional (Drizzle) insert. Keys only - values are left
311
+ * untouched, so this does not change how JSON/date columns are decoded. For
312
+ * tables whose property names already equal their SQL columns (the dynamic
313
+ * dc_/single_/comp_ tables) every lookup is identity, so existing callers see
314
+ * no change.
315
+ */
316
+ mapRowKeysToJs(tableObj, row) {
317
+ if (!tableObj || typeof tableObj !== "object" || !row || typeof row !== "object") {
318
+ return row;
319
+ }
320
+ const sqlToJs = /* @__PURE__ */ new Map();
321
+ for (const [jsName, colDef] of Object.entries(
322
+ tableObj
323
+ )) {
324
+ if (colDef && typeof colDef === "object" && "name" in colDef && typeof colDef.name === "string") {
325
+ sqlToJs.set(colDef.name, jsName);
326
+ }
327
+ }
328
+ if (sqlToJs.size === 0) return row;
329
+ const out = {};
330
+ for (const [key, value] of Object.entries(row)) {
331
+ out[sqlToJs.get(key) ?? key] = value;
332
+ }
333
+ return out;
334
+ }
335
+ /**
336
+ * Decode a raw-SQL result row's DATE column values the way a Drizzle query
337
+ * would, so a write answers with the same representation a read of the same
338
+ * column gives.
339
+ *
340
+ * A Drizzle query path runs every value through its column definition; a
341
+ * raw-SQL path does not, so a dialect that stores a timestamp as a number
342
+ * answers a write with that number while every read answers a `Date`. Only
343
+ * date columns are touched, and a value that is already a `Date` is left
344
+ * alone, so a driver that decodes on its own is unaffected.
345
+ *
346
+ * Runs after {@link mapRowKeysToJs}: keys are Drizzle property names by then,
347
+ * which is what the table definition is keyed by.
348
+ */
349
+ mapDateValuesFromDriver(tableObj, row) {
350
+ if (!tableObj || typeof tableObj !== "object" || !row || typeof row !== "object") {
351
+ return row;
352
+ }
353
+ let decoded;
354
+ for (const [jsName, colDef] of Object.entries(
355
+ tableObj
356
+ )) {
357
+ if (!isDateColumn(colDef)) continue;
358
+ const value = row[jsName];
359
+ if (value == null || value instanceof Date) continue;
360
+ decoded ??= { ...row };
361
+ decoded[jsName] = colDef.mapFromDriverValue(value);
362
+ }
363
+ return decoded ?? row;
364
+ }
365
+ /**
366
+ * Encode a row's DATE values the way a Drizzle query would before binding
367
+ * them, so what lands in the column does not depend on the server's timezone.
368
+ *
369
+ * A column declared without a time zone stores a wall clock and records
370
+ * nothing about which zone it belongs to, so both ends have to agree. Drizzle
371
+ * writes UTC and reads UTC. A driver handed a `Date` writes the LOCAL wall
372
+ * clock instead, and the same row then reads back shifted by the offset --
373
+ * on a UTC server the two agree and nothing is visibly wrong, which is why
374
+ * this survives CI.
375
+ *
376
+ * Only date columns are touched. A value that is not a `Date` is left as it
377
+ * is, so a caller that already encoded one is not encoded twice.
378
+ *
379
+ * Keys may be spelled either way at this point, so both the Drizzle property
380
+ * name and the SQL column name resolve.
381
+ */
382
+ mapDateValuesToDriver(tableObj, data) {
383
+ if (!tableObj || typeof tableObj !== "object" || !data) return data;
384
+ const dateColumns = /* @__PURE__ */ new Map();
385
+ for (const [jsName, colDef] of Object.entries(
386
+ tableObj
387
+ )) {
388
+ if (!isDateColumn(colDef)) continue;
389
+ dateColumns.set(jsName, colDef);
390
+ const sqlName = colDef.name;
391
+ if (typeof sqlName === "string") dateColumns.set(sqlName, colDef);
392
+ }
393
+ if (dateColumns.size === 0) return data;
394
+ let encoded;
395
+ for (const [key, value] of Object.entries(data)) {
396
+ if (!(value instanceof Date)) continue;
397
+ const column = dateColumns.get(key);
398
+ if (!column) continue;
399
+ encoded ??= { ...data };
400
+ encoded[key] = column.mapToDriverValue(value);
401
+ }
402
+ return encoded ?? data;
403
+ }
404
+ /**
405
+ * Bring a row about to be written through a raw-SQL statement into the shape
406
+ * a Drizzle query would have bound: SQL column names for keys, encoded values
407
+ * for date columns.
408
+ *
409
+ * The mirror of {@link mapRowFromRawSql}. The raw-SQL write paths exist
410
+ * because a dialect needs SQL a Drizzle query cannot express, not because
411
+ * their callers want different values in the table.
412
+ */
413
+ mapRowToRawSql(tableObj, data) {
414
+ return this.mapDateValuesToDriver(
415
+ tableObj,
416
+ this.mapKeysToSqlColumns(tableObj, data)
417
+ );
418
+ }
419
+ /**
420
+ * Bring a raw-SQL result row into the shape a Drizzle query would have
421
+ * produced: Drizzle property names for keys, decoded values for date columns.
422
+ *
423
+ * The raw-SQL write paths exist because a dialect needs SQL a Drizzle query
424
+ * cannot express, not because their callers want a different row shape, so
425
+ * every one of them ends here.
426
+ */
427
+ mapRowFromRawSql(tableObj, row, aliases = []) {
428
+ const mapped = this.mapDateValuesFromDriver(
429
+ tableObj,
430
+ this.mapRowKeysToJs(tableObj, row)
431
+ );
432
+ return this.applyWallClockAliases(mapped, aliases);
433
+ }
434
+ /**
435
+ * The date columns a statement is about to return, and the alias each one's
436
+ * wall clock is spelled out under.
437
+ *
438
+ * A driver turns a timestamp into a `Date` using ITS zone before any of this
439
+ * code runs, and that conversion is lossy: a wall clock inside a
440
+ * daylight-saving gap is a local time that does not exist, so the driver
441
+ * normalizes it and the original is gone. Asking the database for the wall
442
+ * clock as text alongside the row is the only way to read it exactly.
443
+ *
444
+ * `"*"` covers every date column; a projection covers only the ones it asked
445
+ * for, so a caller still gets back what it requested and nothing more.
446
+ */
447
+ dateWallClockAliases(tableObj, returning) {
448
+ if (!tableObj || typeof tableObj !== "object" || returning === void 0) {
449
+ return [];
450
+ }
451
+ const requested = returning === "*" ? void 0 : new Set(returning.map(String));
452
+ const taken = /* @__PURE__ */ new Set();
453
+ for (const [jsName, colDef] of Object.entries(
454
+ tableObj
455
+ )) {
456
+ taken.add(jsName);
457
+ const columnName = colDef.name;
458
+ if (typeof columnName === "string") taken.add(columnName);
459
+ }
460
+ let next = 0;
461
+ const freeAlias = () => {
462
+ let candidate = `${WALL_CLOCK_ALIAS_STEM}${next++}`;
463
+ while (taken.has(candidate)) {
464
+ candidate = `${WALL_CLOCK_ALIAS_STEM}${next++}`;
465
+ }
466
+ taken.add(candidate);
467
+ return candidate;
468
+ };
469
+ const aliases = [];
470
+ for (const [jsName, colDef] of Object.entries(
471
+ tableObj
472
+ )) {
473
+ if (!isDateColumn(colDef)) continue;
474
+ if (carriesTimeZone(colDef)) continue;
475
+ const sqlName = colDef.name;
476
+ if (typeof sqlName !== "string") continue;
477
+ if (requested && !requested.has(sqlName) && !requested.has(jsName)) {
478
+ continue;
479
+ }
480
+ aliases.push({ sqlName, alias: freeAlias(), jsName });
481
+ }
482
+ return aliases;
483
+ }
484
+ /**
485
+ * Replace each date in a row with the wall clock the database spelled out,
486
+ * read as UTC, and drop the aliases that carried it.
487
+ *
488
+ * The statement writes a UTC wall clock, so reading one back as UTC is what
489
+ * makes a write and a later read agree. Whatever the column actually stored
490
+ * is what arrives -- a column keeping only whole seconds reports whole
491
+ * seconds -- because this is the database's own text, not a value
492
+ * reconstructed from the one that was bound.
493
+ */
494
+ applyWallClockAliases(row, aliases) {
495
+ if (!row || typeof row !== "object" || aliases.length === 0) return row;
496
+ const record = row;
497
+ let resolved;
498
+ for (const { alias, jsName } of aliases) {
499
+ if (!(alias in record)) continue;
500
+ resolved ??= { ...record };
501
+ const text = resolved[alias];
502
+ delete resolved[alias];
503
+ if (typeof text !== "string") continue;
504
+ const parsed = parseWallClockAsUtc(text);
505
+ if (parsed !== void 0) resolved[jsName] = parsed;
506
+ }
507
+ return resolved ?? row;
508
+ }
509
+ /**
510
+ * Build a Drizzle column projection object (`{ propertyName: column }`) from a
511
+ * requested column list, used by `select` (columns) and `insert` (returning).
512
+ * A requested name resolves against either the Drizzle property name
513
+ * (camelCase) or the SQL column name (snake_case); the projection is keyed by
514
+ * the property name so the row shape matches a full select. Returns undefined
515
+ * for `"*"` or when nothing resolves, so callers fall back to all columns.
516
+ */
517
+ buildColumnProjection(tableObj, names) {
518
+ if (names == null || names === "*" || !tableObj || typeof tableObj !== "object") {
519
+ return void 0;
520
+ }
521
+ const cols = getColumns(tableObj);
522
+ const byAnyName = {};
523
+ for (const [jsName, col] of Object.entries(cols)) {
524
+ byAnyName[jsName] = { jsName, col };
525
+ const sqlName = col?.name;
526
+ if (typeof sqlName === "string") byAnyName[sqlName] = { jsName, col };
527
+ }
528
+ const projection = {};
529
+ for (const name of names) {
530
+ const hit = byAnyName[name];
531
+ if (hit) projection[hit.jsName] = hit.col;
532
+ }
533
+ return Object.keys(projection).length ? projection : void 0;
534
+ }
176
535
  // ============================================================
177
536
  // Connection Status (Default implementations, can override)
178
537
  // ============================================================
@@ -326,12 +685,13 @@ var DrizzleAdapter = class {
326
685
  * });
327
686
  * ```
328
687
  */
329
- async select(table, options) {
688
+ async select(table, options, executor) {
330
689
  const tableObj = this.getTableObject(table);
331
690
  if (tableObj) {
332
691
  try {
333
- const db = this.getDrizzle();
334
- let query = db.select().from(tableObj);
692
+ const db = executor ?? this.getDrizzle();
693
+ const projection = options?.columns?.length ? this.buildColumnProjection(tableObj, options.columns) : void 0;
694
+ let query = projection ? db.select(projection).from(tableObj) : db.select().from(tableObj);
335
695
  if (options?.where) {
336
696
  const whereCondition = buildDrizzleWhere(
337
697
  tableObj,
@@ -342,7 +702,7 @@ var DrizzleAdapter = class {
342
702
  }
343
703
  }
344
704
  if (options?.orderBy?.length) {
345
- const columns = getTableColumns(tableObj);
705
+ const columns = getColumns(tableObj);
346
706
  const orderClauses = options.orderBy.map((o) => {
347
707
  const col = columns[o.column];
348
708
  if (!col) return void 0;
@@ -358,6 +718,16 @@ var DrizzleAdapter = class {
358
718
  if (options?.offset !== void 0) {
359
719
  query = query.offset(options.offset);
360
720
  }
721
+ if (options?.forUpdate && !executor) {
722
+ throw this.createDatabaseError(
723
+ "query",
724
+ "forUpdate requires a transaction executor: a lock request on the pooled connection takes no durable lock and cannot prevent a concurrent write.",
725
+ void 0
726
+ );
727
+ }
728
+ if (options?.forUpdate && this.dialect !== "sqlite") {
729
+ query = query.for("update");
730
+ }
361
731
  return await query;
362
732
  } catch (error) {
363
733
  throw this.handleQueryError(error, "select", table);
@@ -389,8 +759,12 @@ var DrizzleAdapter = class {
389
759
  * });
390
760
  * ```
391
761
  */
392
- async selectOne(table, options) {
393
- const results = await this.select(table, { ...options, limit: 1 });
762
+ async selectOne(table, options, executor) {
763
+ const results = await this.select(
764
+ table,
765
+ { ...options, limit: 1 },
766
+ executor
767
+ );
394
768
  return results.length > 0 ? results[0] : null;
395
769
  }
396
770
  /**
@@ -422,19 +796,22 @@ var DrizzleAdapter = class {
422
796
  const mappedData = this.mapDataToColumnNames(tableObj, data);
423
797
  const db = this.getDrizzle();
424
798
  const caps = this.getCapabilities();
425
- if (caps.supportsReturning && options?.returning) {
426
- const result2 = await db.insert(tableObj).values(mappedData).returning();
799
+ const returning = options?.returning;
800
+ const wantsReturning = returning != null && !(Array.isArray(returning) && returning.length === 0);
801
+ if (caps.supportsReturning && wantsReturning) {
802
+ const projection = this.buildColumnProjection(tableObj, returning);
803
+ const insertQuery = db.insert(tableObj).values(mappedData);
804
+ const result2 = await (projection ? insertQuery.returning(projection) : insertQuery.returning());
427
805
  return Array.isArray(result2) ? result2[0] : result2;
428
806
  }
429
807
  const result = await db.insert(tableObj).values(mappedData);
430
- if (!caps.supportsReturning && options?.returning) {
431
- if (data.id !== void 0) {
432
- return await this.selectOne(table, {
433
- where: {
434
- and: [{ column: "id", op: "=", value: data.id }]
435
- }
436
- });
437
- }
808
+ if (!caps.supportsReturning && wantsReturning && data.id !== void 0) {
809
+ return await this.selectOne(table, {
810
+ columns: returning === "*" ? void 0 : returning,
811
+ where: {
812
+ and: [{ column: "id", op: "=", value: data.id }]
813
+ }
814
+ });
438
815
  }
439
816
  return Array.isArray(result) ? result[0] : result;
440
817
  } catch (error) {
@@ -504,11 +881,11 @@ var DrizzleAdapter = class {
504
881
  * );
505
882
  * ```
506
883
  */
507
- async update(table, data, where, options) {
884
+ async update(table, data, where, options, executor) {
508
885
  const tableObj = this.getTableObject(table);
509
886
  if (tableObj) {
510
887
  try {
511
- const db = this.getDrizzle();
888
+ const db = executor ?? this.getDrizzle();
512
889
  const caps = this.getCapabilities();
513
890
  const mappedData = this.mapDataToColumnNames(tableObj, data);
514
891
  let query = db.update(tableObj).set(mappedData);
@@ -521,7 +898,7 @@ var DrizzleAdapter = class {
521
898
  }
522
899
  await query;
523
900
  if (!caps.supportsReturning && options?.returning) {
524
- return await this.select(table, { where });
901
+ return await this.select(table, { where }, executor);
525
902
  }
526
903
  return [];
527
904
  } catch (error) {
@@ -534,6 +911,42 @@ var DrizzleAdapter = class {
534
911
  void 0
535
912
  );
536
913
  }
914
+ /**
915
+ * Update records in a table and report how many rows the statement affected.
916
+ *
917
+ * The count is the whole return value, mirroring `delete`. `update` cannot answer this: without
918
+ * `returning` it discards the driver's count, and WITH `returning` on a dialect that lacks
919
+ * RETURNING it re-SELECTs using the same WHERE — so a conditional update that just changed a
920
+ * column named in that WHERE reads back zero rows and a write that landed reports as unmatched.
921
+ * Reading the driver's own count has no second query to disagree with the first.
922
+ *
923
+ * 🔴 MySQL reports CHANGED rows, not matched rows: an UPDATE that matches a row but writes values
924
+ * identical to what it holds counts zero. A caller using this as a compare-and-set must therefore
925
+ * include a column the write always moves — a version bump, a timestamp with enough resolution —
926
+ * so that matched implies changed. Postgres (`rowCount`) and SQLite (`changes`) count matched
927
+ * rows and do not need the precaution, which is exactly why it cannot be dropped: the dialect
928
+ * where the distinction exists is the one with no RETURNING to fall back on.
929
+ */
930
+ async updateCount(table, data, where, executor) {
931
+ const tableObj = this.getTableObject(table);
932
+ if (tableObj) {
933
+ try {
934
+ const db = executor ?? this.getDrizzle();
935
+ const mappedData = this.mapDataToColumnNames(tableObj, data);
936
+ const statement = db.update(tableObj).set(mappedData);
937
+ const whereCondition = buildDrizzleWhere(tableObj, where);
938
+ const result = await (whereCondition ? statement.where(whereCondition) : statement);
939
+ return affectedRowCount(result);
940
+ } catch (error) {
941
+ throw this.handleQueryError(error, "update", table);
942
+ }
943
+ }
944
+ throw this.createDatabaseError(
945
+ "query",
946
+ `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`,
947
+ void 0
948
+ );
949
+ }
537
950
  /**
538
951
  * Delete records from a table.
539
952
  *
@@ -556,18 +969,18 @@ var DrizzleAdapter = class {
556
969
  * console.log(`Deleted ${count} users`);
557
970
  * ```
558
971
  */
559
- async delete(table, where, _options) {
972
+ async delete(table, where, _options, executor) {
560
973
  const tableObj = this.getTableObject(table);
561
974
  if (tableObj) {
562
975
  try {
563
- const db = this.getDrizzle();
976
+ const db = executor ?? this.getDrizzle();
564
977
  let query = db.delete(tableObj);
565
978
  const whereCondition = buildDrizzleWhere(tableObj, where);
566
979
  if (whereCondition) {
567
980
  query = query.where(whereCondition);
568
981
  }
569
982
  const result = await query;
570
- return Array.isArray(result) ? result.length : result?.rowCount ?? result?.changes ?? 0;
983
+ return affectedRowCount(result);
571
984
  } catch (error) {
572
985
  throw this.handleQueryError(error, "delete", table);
573
986
  }
@@ -605,13 +1018,13 @@ var DrizzleAdapter = class {
605
1018
  * });
606
1019
  * ```
607
1020
  */
608
- async upsert(table, data, options) {
1021
+ async upsert(table, data, options, executor) {
609
1022
  const tableObj = this.getTableObject(table);
610
1023
  if (tableObj) {
611
1024
  try {
612
- const db = this.getDrizzle();
1025
+ const db = executor ?? this.getDrizzle();
613
1026
  const caps = this.getCapabilities();
614
- const columns = getTableColumns(tableObj);
1027
+ const columns = getColumns(tableObj);
615
1028
  const conflictTarget = options.conflictColumns.map((col) => columns[col]).filter(Boolean);
616
1029
  const conflictSet = new Set(options.conflictColumns);
617
1030
  const updateData = {};
@@ -636,17 +1049,21 @@ var DrizzleAdapter = class {
636
1049
  }
637
1050
  await query;
638
1051
  if (options.conflictColumns.length && data[options.conflictColumns[0]] !== void 0) {
639
- return await this.selectOne(table, {
640
- where: {
641
- and: [
642
- {
643
- column: options.conflictColumns[0],
644
- op: "=",
645
- value: data[options.conflictColumns[0]]
646
- }
647
- ]
648
- }
649
- });
1052
+ return await this.selectOne(
1053
+ table,
1054
+ {
1055
+ where: {
1056
+ and: [
1057
+ {
1058
+ column: options.conflictColumns[0],
1059
+ op: "=",
1060
+ value: data[options.conflictColumns[0]]
1061
+ }
1062
+ ]
1063
+ }
1064
+ },
1065
+ executor
1066
+ );
650
1067
  }
651
1068
  return data;
652
1069
  } catch (error) {
@@ -893,11 +1310,15 @@ var DrizzleAdapter = class {
893
1310
  sql = `
894
1311
  SELECT EXISTS (
895
1312
  SELECT FROM information_schema.tables
896
- WHERE table_schema = $1
1313
+ WHERE table_schema = COALESCE($1, (
1314
+ SELECT n.nspname FROM pg_class c
1315
+ JOIN pg_namespace n ON n.oid = c.relnamespace
1316
+ WHERE c.oid = to_regclass($2)
1317
+ ))
897
1318
  AND table_name = $2
898
1319
  ) as exists
899
1320
  `;
900
- params.push(schema ?? "public", tableName);
1321
+ params.push(schema ?? null, tableName);
901
1322
  break;
902
1323
  case "mysql":
903
1324
  sql = `
@@ -972,7 +1393,7 @@ var DrizzleAdapter = class {
972
1393
  break;
973
1394
  case "mysql":
974
1395
  sql = `
975
- SELECT table_name
1396
+ SELECT table_name AS table_name
976
1397
  FROM information_schema.tables
977
1398
  WHERE table_schema = DATABASE()
978
1399
  AND table_type = 'BASE TABLE'