@nextlyhq/adapter-drizzle 0.0.2-alpha.5 → 0.0.2-alpha.50

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,21 @@ function createDatabaseError(options) {
106
106
  }
107
107
 
108
108
  // src/adapter.ts
109
+ function affectedRowCount(result) {
110
+ if (Array.isArray(result)) {
111
+ const header = result[0];
112
+ if (header && typeof header.affectedRows === "number") {
113
+ return header.affectedRows;
114
+ }
115
+ return result.length;
116
+ }
117
+ const record = result;
118
+ const rowCount = record?.rowCount;
119
+ if (typeof rowCount === "number") return rowCount;
120
+ const changes = record?.changes;
121
+ if (typeof changes === "number") return changes;
122
+ return 0;
123
+ }
109
124
  var DrizzleAdapter = class {
110
125
  // ============================================================
111
126
  // Drizzle Query API Support
@@ -134,6 +149,45 @@ var DrizzleAdapter = class {
134
149
  getTableObject(tableName) {
135
150
  return this.tableResolver?.getTable(tableName) ?? null;
136
151
  }
152
+ /**
153
+ * Run a Drizzle-built statement and return its rows.
154
+ *
155
+ * @remarks
156
+ * The CRUD methods resolve their table through the schema registry and reject
157
+ * any name it does not declare, which leaves no way to read from a table the
158
+ * ORM does not know — one mid-rename, above all — except by assembling SQL and
159
+ * quoting identifiers by hand. This takes Drizzle's `sql` template instead, so
160
+ * the dialect in use decides the quoting and the parameter binding.
161
+ *
162
+ * Concrete rather than abstract so existing adapters keep working unchanged.
163
+ * The three drivers disagree about both the call and the result: node-postgres
164
+ * returns `{ rows }`, mysql2 a `[rows, fields]` tuple, and better-sqlite3 has
165
+ * no `execute` at all and answers `all`. Keeping that here rather than at each
166
+ * call site is the point — a caller reasoning about it would be reasoning
167
+ * about a driver it cannot see.
168
+ *
169
+ * @param statement - Drizzle `sql` template to run
170
+ * @returns Rows the statement produced
171
+ */
172
+ async queryStatement(statement) {
173
+ if (this.getCapabilities().dialect === "sqlite") {
174
+ const db2 = this.getDrizzle();
175
+ return await db2.all(statement);
176
+ }
177
+ const db = this.getDrizzle();
178
+ const result = await db.execute(statement);
179
+ if (Array.isArray(result)) {
180
+ return Array.isArray(result[0]) ? result[0] : result;
181
+ }
182
+ if (typeof result === "object" && result !== null && Array.isArray(result.rows)) {
183
+ return result.rows;
184
+ }
185
+ throw this.createDatabaseError(
186
+ "query",
187
+ "Drizzle statement returned a result shape this adapter does not recognise; refusing to report it as an empty result.",
188
+ void 0
189
+ );
190
+ }
137
191
  /**
138
192
  * Map data keys from SQL column names (snake_case) to Drizzle JS property names (camelCase).
139
193
  * Drizzle schemas define columns as e.g. `createdAt: timestamp("created_at")` — the JS
@@ -173,6 +227,107 @@ var DrizzleAdapter = class {
173
227
  }
174
228
  return mapped;
175
229
  }
230
+ /**
231
+ * Map data keys from Drizzle JS property names to SQL column names for the
232
+ * raw-SQL transaction insert path. The transaction context builds INSERT
233
+ * statements from Object.keys(data) used directly as column identifiers, so a
234
+ * table whose Drizzle property names differ from its SQL column names
235
+ * (camelCase core tables like nextly_versions) needs its keys translated
236
+ * first. For tables whose property names already equal their column names
237
+ * (the dynamic dc_/single_/comp_ tables) every lookup is identity, so
238
+ * existing callers are unaffected.
239
+ */
240
+ mapKeysToSqlColumns(tableObj, data) {
241
+ if (!tableObj || typeof tableObj !== "object") return data;
242
+ const jsToSql = /* @__PURE__ */ new Map();
243
+ for (const [jsName, colDef] of Object.entries(
244
+ tableObj
245
+ )) {
246
+ if (colDef && typeof colDef === "object" && "name" in colDef && typeof colDef.name === "string") {
247
+ jsToSql.set(jsName, colDef.name);
248
+ }
249
+ }
250
+ if (jsToSql.size === 0) return data;
251
+ const out = {};
252
+ for (const [key, value] of Object.entries(data)) {
253
+ out[jsToSql.get(key) ?? key] = value;
254
+ }
255
+ return out;
256
+ }
257
+ /**
258
+ * Map a list of column identifiers (Drizzle property names) to their SQL
259
+ * column names, for the raw-SQL transaction insert paths that build a
260
+ * RETURNING clause from `options.returning`. Same identity behavior as
261
+ * `mapKeysToSqlColumns`: names that are already SQL columns (the dynamic
262
+ * dc_/single_/comp_ tables) pass through unchanged.
263
+ */
264
+ mapColumnNamesToSql(tableObj, names) {
265
+ if (!tableObj || typeof tableObj !== "object") return names;
266
+ const jsToSql = /* @__PURE__ */ new Map();
267
+ for (const [jsName, colDef] of Object.entries(
268
+ tableObj
269
+ )) {
270
+ if (colDef && typeof colDef === "object" && "name" in colDef && typeof colDef.name === "string") {
271
+ jsToSql.set(jsName, colDef.name);
272
+ }
273
+ }
274
+ if (jsToSql.size === 0) return names;
275
+ return names.map((n) => jsToSql.get(n) ?? n);
276
+ }
277
+ /**
278
+ * Remap a raw-SQL result row's KEYS from SQL column names to Drizzle property
279
+ * names, so the raw-SQL transaction insert paths return the same key casing
280
+ * as the non-transactional (Drizzle) insert. Keys only - values are left
281
+ * untouched, so this does not change how JSON/date columns are decoded. For
282
+ * tables whose property names already equal their SQL columns (the dynamic
283
+ * dc_/single_/comp_ tables) every lookup is identity, so existing callers see
284
+ * no change.
285
+ */
286
+ mapRowKeysToJs(tableObj, row) {
287
+ if (!tableObj || typeof tableObj !== "object" || !row || typeof row !== "object") {
288
+ return row;
289
+ }
290
+ const sqlToJs = /* @__PURE__ */ new Map();
291
+ for (const [jsName, colDef] of Object.entries(
292
+ tableObj
293
+ )) {
294
+ if (colDef && typeof colDef === "object" && "name" in colDef && typeof colDef.name === "string") {
295
+ sqlToJs.set(colDef.name, jsName);
296
+ }
297
+ }
298
+ if (sqlToJs.size === 0) return row;
299
+ const out = {};
300
+ for (const [key, value] of Object.entries(row)) {
301
+ out[sqlToJs.get(key) ?? key] = value;
302
+ }
303
+ return out;
304
+ }
305
+ /**
306
+ * Build a Drizzle column projection object (`{ propertyName: column }`) from a
307
+ * requested column list, used by `select` (columns) and `insert` (returning).
308
+ * A requested name resolves against either the Drizzle property name
309
+ * (camelCase) or the SQL column name (snake_case); the projection is keyed by
310
+ * the property name so the row shape matches a full select. Returns undefined
311
+ * for `"*"` or when nothing resolves, so callers fall back to all columns.
312
+ */
313
+ buildColumnProjection(tableObj, names) {
314
+ if (names == null || names === "*" || !tableObj || typeof tableObj !== "object") {
315
+ return void 0;
316
+ }
317
+ const cols = getColumns(tableObj);
318
+ const byAnyName = {};
319
+ for (const [jsName, col] of Object.entries(cols)) {
320
+ byAnyName[jsName] = { jsName, col };
321
+ const sqlName = col?.name;
322
+ if (typeof sqlName === "string") byAnyName[sqlName] = { jsName, col };
323
+ }
324
+ const projection = {};
325
+ for (const name of names) {
326
+ const hit = byAnyName[name];
327
+ if (hit) projection[hit.jsName] = hit.col;
328
+ }
329
+ return Object.keys(projection).length ? projection : void 0;
330
+ }
176
331
  // ============================================================
177
332
  // Connection Status (Default implementations, can override)
178
333
  // ============================================================
@@ -326,12 +481,13 @@ var DrizzleAdapter = class {
326
481
  * });
327
482
  * ```
328
483
  */
329
- async select(table, options) {
484
+ async select(table, options, executor) {
330
485
  const tableObj = this.getTableObject(table);
331
486
  if (tableObj) {
332
487
  try {
333
- const db = this.getDrizzle();
334
- let query = db.select().from(tableObj);
488
+ const db = executor ?? this.getDrizzle();
489
+ const projection = options?.columns?.length ? this.buildColumnProjection(tableObj, options.columns) : void 0;
490
+ let query = projection ? db.select(projection).from(tableObj) : db.select().from(tableObj);
335
491
  if (options?.where) {
336
492
  const whereCondition = buildDrizzleWhere(
337
493
  tableObj,
@@ -342,7 +498,7 @@ var DrizzleAdapter = class {
342
498
  }
343
499
  }
344
500
  if (options?.orderBy?.length) {
345
- const columns = getTableColumns(tableObj);
501
+ const columns = getColumns(tableObj);
346
502
  const orderClauses = options.orderBy.map((o) => {
347
503
  const col = columns[o.column];
348
504
  if (!col) return void 0;
@@ -358,6 +514,16 @@ var DrizzleAdapter = class {
358
514
  if (options?.offset !== void 0) {
359
515
  query = query.offset(options.offset);
360
516
  }
517
+ if (options?.forUpdate && !executor) {
518
+ throw this.createDatabaseError(
519
+ "query",
520
+ "forUpdate requires a transaction executor: a lock request on the pooled connection takes no durable lock and cannot prevent a concurrent write.",
521
+ void 0
522
+ );
523
+ }
524
+ if (options?.forUpdate && this.dialect !== "sqlite") {
525
+ query = query.for("update");
526
+ }
361
527
  return await query;
362
528
  } catch (error) {
363
529
  throw this.handleQueryError(error, "select", table);
@@ -389,8 +555,12 @@ var DrizzleAdapter = class {
389
555
  * });
390
556
  * ```
391
557
  */
392
- async selectOne(table, options) {
393
- const results = await this.select(table, { ...options, limit: 1 });
558
+ async selectOne(table, options, executor) {
559
+ const results = await this.select(
560
+ table,
561
+ { ...options, limit: 1 },
562
+ executor
563
+ );
394
564
  return results.length > 0 ? results[0] : null;
395
565
  }
396
566
  /**
@@ -422,19 +592,22 @@ var DrizzleAdapter = class {
422
592
  const mappedData = this.mapDataToColumnNames(tableObj, data);
423
593
  const db = this.getDrizzle();
424
594
  const caps = this.getCapabilities();
425
- if (caps.supportsReturning && options?.returning) {
426
- const result2 = await db.insert(tableObj).values(mappedData).returning();
595
+ const returning = options?.returning;
596
+ const wantsReturning = returning != null && !(Array.isArray(returning) && returning.length === 0);
597
+ if (caps.supportsReturning && wantsReturning) {
598
+ const projection = this.buildColumnProjection(tableObj, returning);
599
+ const insertQuery = db.insert(tableObj).values(mappedData);
600
+ const result2 = await (projection ? insertQuery.returning(projection) : insertQuery.returning());
427
601
  return Array.isArray(result2) ? result2[0] : result2;
428
602
  }
429
603
  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
- }
604
+ if (!caps.supportsReturning && wantsReturning && data.id !== void 0) {
605
+ return await this.selectOne(table, {
606
+ columns: returning === "*" ? void 0 : returning,
607
+ where: {
608
+ and: [{ column: "id", op: "=", value: data.id }]
609
+ }
610
+ });
438
611
  }
439
612
  return Array.isArray(result) ? result[0] : result;
440
613
  } catch (error) {
@@ -504,11 +677,11 @@ var DrizzleAdapter = class {
504
677
  * );
505
678
  * ```
506
679
  */
507
- async update(table, data, where, options) {
680
+ async update(table, data, where, options, executor) {
508
681
  const tableObj = this.getTableObject(table);
509
682
  if (tableObj) {
510
683
  try {
511
- const db = this.getDrizzle();
684
+ const db = executor ?? this.getDrizzle();
512
685
  const caps = this.getCapabilities();
513
686
  const mappedData = this.mapDataToColumnNames(tableObj, data);
514
687
  let query = db.update(tableObj).set(mappedData);
@@ -521,7 +694,7 @@ var DrizzleAdapter = class {
521
694
  }
522
695
  await query;
523
696
  if (!caps.supportsReturning && options?.returning) {
524
- return await this.select(table, { where });
697
+ return await this.select(table, { where }, executor);
525
698
  }
526
699
  return [];
527
700
  } catch (error) {
@@ -556,18 +729,18 @@ var DrizzleAdapter = class {
556
729
  * console.log(`Deleted ${count} users`);
557
730
  * ```
558
731
  */
559
- async delete(table, where, _options) {
732
+ async delete(table, where, _options, executor) {
560
733
  const tableObj = this.getTableObject(table);
561
734
  if (tableObj) {
562
735
  try {
563
- const db = this.getDrizzle();
736
+ const db = executor ?? this.getDrizzle();
564
737
  let query = db.delete(tableObj);
565
738
  const whereCondition = buildDrizzleWhere(tableObj, where);
566
739
  if (whereCondition) {
567
740
  query = query.where(whereCondition);
568
741
  }
569
742
  const result = await query;
570
- return Array.isArray(result) ? result.length : result?.rowCount ?? result?.changes ?? 0;
743
+ return affectedRowCount(result);
571
744
  } catch (error) {
572
745
  throw this.handleQueryError(error, "delete", table);
573
746
  }
@@ -605,13 +778,13 @@ var DrizzleAdapter = class {
605
778
  * });
606
779
  * ```
607
780
  */
608
- async upsert(table, data, options) {
781
+ async upsert(table, data, options, executor) {
609
782
  const tableObj = this.getTableObject(table);
610
783
  if (tableObj) {
611
784
  try {
612
- const db = this.getDrizzle();
785
+ const db = executor ?? this.getDrizzle();
613
786
  const caps = this.getCapabilities();
614
- const columns = getTableColumns(tableObj);
787
+ const columns = getColumns(tableObj);
615
788
  const conflictTarget = options.conflictColumns.map((col) => columns[col]).filter(Boolean);
616
789
  const conflictSet = new Set(options.conflictColumns);
617
790
  const updateData = {};
@@ -636,17 +809,21 @@ var DrizzleAdapter = class {
636
809
  }
637
810
  await query;
638
811
  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
- });
812
+ return await this.selectOne(
813
+ table,
814
+ {
815
+ where: {
816
+ and: [
817
+ {
818
+ column: options.conflictColumns[0],
819
+ op: "=",
820
+ value: data[options.conflictColumns[0]]
821
+ }
822
+ ]
823
+ }
824
+ },
825
+ executor
826
+ );
650
827
  }
651
828
  return data;
652
829
  } catch (error) {
@@ -972,7 +1149,7 @@ var DrizzleAdapter = class {
972
1149
  break;
973
1150
  case "mysql":
974
1151
  sql = `
975
- SELECT table_name
1152
+ SELECT table_name AS table_name
976
1153
  FROM information_schema.tables
977
1154
  WHERE table_schema = DATABASE()
978
1155
  AND table_type = 'BASE TABLE'