@nextlyhq/adapter-drizzle 0.0.2-alpha.66 → 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-Cs3Jl9Fb.d.cts → adapter-bUAaPzjV.d.cts} +219 -2
  2. package/dist/{adapter-CHlx_Taa.d.ts → adapter-iJ5hbUvL.d.ts} +219 -2
  3. package/dist/index.cjs +299 -28
  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 +300 -29
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/{migration-CgoxgWOL.d.ts → migration-BqHebSwM.d.ts} +40 -3
  10. package/dist/{migration-Ce0hkgr_.d.cts → migration-Bu0WLdX4.d.cts} +40 -3
  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.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ var async_hooks = require('async_hooks');
3
4
  var drizzleOrm = require('drizzle-orm');
4
5
 
5
6
  // src/adapter.ts
@@ -28,6 +29,14 @@ function buildDrizzleOrderBy(columns, orderBy) {
28
29
  return [spec.nulls === "last" ? drizzleOrm.asc(isNull2) : drizzleOrm.desc(isNull2), direction];
29
30
  });
30
31
  }
32
+ function containsPattern(value) {
33
+ return `%${value.replace(/[!%_]/g, (character) => `!${character}`)}%`;
34
+ }
35
+ function caseInsensitiveContains(column, value) {
36
+ return drizzleOrm.sql`lower(${column}) like lower(${containsPattern(value)}) escape '!'`;
37
+ }
38
+
39
+ // src/drizzle-where.ts
31
40
  function buildDrizzleWhere(table, where) {
32
41
  const columns = drizzleOrm.getColumns(table);
33
42
  return processWhereClause(columns, where);
@@ -80,9 +89,6 @@ function isWhereCondition(item) {
80
89
  function namesABranch(where) {
81
90
  return Boolean(where.and?.length || where.or?.length || where.not);
82
91
  }
83
- function containsPattern(value) {
84
- return `%${value.replace(/[!%_]/g, (character) => `!${character}`)}%`;
85
- }
86
92
  function buildCondition(columns, cond) {
87
93
  const column = columns[cond.column];
88
94
  if (!column) {
@@ -107,7 +113,7 @@ function buildCondition(columns, cond) {
107
113
  case "LIKE":
108
114
  return drizzleOrm.like(col, cond.value);
109
115
  case "ILIKE":
110
- return drizzleOrm.ilike(col, cond.value);
116
+ return drizzleOrm.sql`lower(${col}) like lower(${cond.value})`;
111
117
  case "IN":
112
118
  return drizzleOrm.inArray(col, cond.value);
113
119
  case "NOT IN":
@@ -122,11 +128,45 @@ function buildCondition(columns, cond) {
122
128
  return drizzleOrm.notBetween(col, cond.value, cond.valueTo);
123
129
  case "CONTAINS":
124
130
  return drizzleOrm.sql`${col} like ${containsPattern(String(cond.value))} escape '!'`;
131
+ case "ICONTAINS":
132
+ return caseInsensitiveContains(col, String(cond.value));
125
133
  default:
126
134
  throw new Error(`Unsupported operator: ${cond.op}`);
127
135
  }
128
136
  }
129
137
 
138
+ // src/pinned-transaction.ts
139
+ function pinTransactions(db, checkout) {
140
+ const pinned = async (work, config) => {
141
+ const connection = await checkout();
142
+ let dirty = false;
143
+ let workFailure;
144
+ try {
145
+ return await connection.drizzle.transaction(async (tx) => {
146
+ try {
147
+ return await work(tx);
148
+ } catch (error) {
149
+ workFailure = { error };
150
+ throw error;
151
+ }
152
+ }, config);
153
+ } catch (error) {
154
+ await connection.rollback().catch(() => {
155
+ dirty = true;
156
+ });
157
+ throw workFailure ? workFailure.error : error;
158
+ } finally {
159
+ connection.release(dirty);
160
+ }
161
+ };
162
+ Object.defineProperty(db, "transaction", {
163
+ value: pinned,
164
+ configurable: true,
165
+ writable: true
166
+ });
167
+ return db;
168
+ }
169
+
130
170
  // src/transaction-forwarders.ts
131
171
  function createTransactionForwarders(delegator, txDb) {
132
172
  return {
@@ -148,6 +188,20 @@ function createTransactionForwarders(delegator, txDb) {
148
188
  getDrizzle: () => txDb()
149
189
  };
150
190
  }
191
+ function transactionDrizzleHandles(build) {
192
+ let bareInstance;
193
+ const bare = () => bareInstance ??= build();
194
+ const relational = /* @__PURE__ */ new WeakMap();
195
+ const withRelations = (relations) => {
196
+ let cached = relational.get(relations);
197
+ if (cached === void 0) {
198
+ cached = build(relations);
199
+ relational.set(relations, cached);
200
+ }
201
+ return cached;
202
+ };
203
+ return { bare, withRelations };
204
+ }
151
205
 
152
206
  // src/types/error.ts
153
207
  function isDatabaseError(error) {
@@ -282,6 +336,155 @@ function affectedRowCount(result) {
282
336
  return 0;
283
337
  }
284
338
  var DrizzleAdapter = class {
339
+ /**
340
+ * Whether a `transaction()` call made from the current async context would
341
+ * join a transaction already open on this adapter, as a savepoint, rather
342
+ * than open one of its own.
343
+ *
344
+ * A caller about to do something no rollback can undo — removing a stored
345
+ * file, say — asks this first: work that joins an enclosing transaction is
346
+ * undone with it, while the effect it performs after its own savepoint
347
+ * releases is not.
348
+ *
349
+ * False here. Concrete rather than abstract so existing adapters keep
350
+ * working unchanged, and false is right for a pooled adapter: a call made
351
+ * inside another transaction's work opens its own transaction on its own
352
+ * connection, which commits independently.
353
+ *
354
+ * @returns True when the call would nest in an open transaction
355
+ */
356
+ inTransaction() {
357
+ return false;
358
+ }
359
+ /**
360
+ * The transaction each async flow is inside, kept per flow rather than per
361
+ * connection: on SQLite every request shares the one connection, and
362
+ * another request's open transaction says nothing about this one.
363
+ */
364
+ flowTransactions = new async_hooks.AsyncLocalStorage();
365
+ /**
366
+ * Whether the calling async flow is inside a transaction on this adapter
367
+ * that has not finished: its own, or one it was started from inside.
368
+ *
369
+ * A write that must outlive any rollback — a count of a failed attempt, the
370
+ * spend of a single-use secret — asks this before running in a transaction
371
+ * of its own. Made inside an open transaction on SQLite, it joins that
372
+ * transaction as a savepoint and is undone with it. On PostgreSQL and MySQL
373
+ * it would commit on a connection of its own, so refusing it there too
374
+ * keeps code that passes on one dialect from failing on another. Unlike
375
+ * {@link inTransaction}, which asks whether a call would nest and is always
376
+ * false on a pooled adapter, this answers alike on every dialect, and
377
+ * another flow's open transaction never answers true.
378
+ *
379
+ * Covers transactions opened through {@link transaction} and through the
380
+ * Drizzle handle's own `transaction` on the dialects whose handle takes
381
+ * async work. A dialect that keeps scopes of its own judges from those
382
+ * instead; an adapter that does neither answers false.
383
+ *
384
+ * @experimental
385
+ * @returns True while a transaction the calling flow is inside is open
386
+ */
387
+ transactionOpenInFlow() {
388
+ for (let at = this.flowTransactions.getStore(); at !== void 0; at = at.enclosing) {
389
+ if (at.open) return true;
390
+ }
391
+ return false;
392
+ }
393
+ /**
394
+ * Run a whole transaction, from its BEGIN until its COMMIT or ROLLBACK has
395
+ * run, as the calling flow's open transaction, for
396
+ * {@link transactionOpenInFlow}. A dialect's `transaction` runs through
397
+ * this unless it judges the flow from scopes of its own.
398
+ *
399
+ * The transaction counts as open for its work and for anything the work
400
+ * starts, awaited or not, until it has finished. Something the work starts
401
+ * that outlives it then sees it closed, or the one enclosing it still open.
402
+ *
403
+ * @param run - The transaction, BEGIN to COMMIT or ROLLBACK
404
+ * @returns What `run` resolves to
405
+ */
406
+ runInFlowTransaction(run) {
407
+ const scope = {
408
+ open: true,
409
+ enclosing: this.flowTransactions.getStore()
410
+ };
411
+ return this.flowTransactions.run(scope, async () => {
412
+ try {
413
+ return await run();
414
+ } finally {
415
+ scope.open = false;
416
+ }
417
+ });
418
+ }
419
+ /**
420
+ * Run each of a pool-bound Drizzle handle's transactions on a connection
421
+ * `checkout` hands it, and return that connection as the adapter's own
422
+ * `transaction()` does: closed rather than pooled when no ROLLBACK can be
423
+ * confirmed on it, and never leaked by a failed BEGIN (`pinTransactions`).
424
+ *
425
+ * For PostgreSQL's and MySQL's handles. Apply it before
426
+ * {@link trackFlowTransactions}, which wraps whatever `transaction` the
427
+ * handle has at that point.
428
+ *
429
+ * @param db - A Drizzle handle on this adapter's pool
430
+ * @param checkout - Checks out a connection for one transaction
431
+ * @returns The same handle, its `transaction` replaced
432
+ */
433
+ pinDrizzleTransactions(db, checkout) {
434
+ return pinTransactions(db, checkout);
435
+ }
436
+ /**
437
+ * Make a Drizzle handle's own `transaction` count for
438
+ * {@link transactionOpenInFlow}, as the adapter's does.
439
+ *
440
+ * Core's services run their transactions through the handle on PostgreSQL
441
+ * and MySQL; without this, a call made inside one would be refused on
442
+ * SQLite, where they go through the adapter, and let through on the
443
+ * others. Not for SQLite's handle: its `transaction` takes only
444
+ * synchronous work, which no async flow can be inside.
445
+ *
446
+ * @param db - A Drizzle handle this adapter created
447
+ * @returns The same handle, its `transaction` tracked
448
+ */
449
+ trackFlowTransactions(db) {
450
+ const transactor = db;
451
+ const untracked = transactor.transaction.bind(transactor);
452
+ const tracked = (work, config) => this.runInFlowTransaction(() => untracked(work, config));
453
+ Object.defineProperty(db, "transaction", {
454
+ value: tracked,
455
+ configurable: true,
456
+ writable: true
457
+ });
458
+ return db;
459
+ }
460
+ /**
461
+ * Run `effect` once the write made from the current async context has
462
+ * committed: an event, a hook, a cache flush, anything outside the database
463
+ * that must not happen for a change that is rolled back.
464
+ *
465
+ * A caller invokes this after its own transaction has resolved. Where that
466
+ * transaction was nested in an enclosing one, as a savepoint, the change is
467
+ * not durable yet, and an adapter that nests (see {@link inTransaction})
468
+ * holds `effect` until the outermost transaction commits, and drops it when
469
+ * that transaction, or the savepoint it was registered in, rolls back. A
470
+ * held effect runs after the commit, before the outermost `transaction()`
471
+ * call resolves, in registration order; one that then throws or rejects is
472
+ * handed to `onDeferredFailure` rather than failing a transaction that has
473
+ * committed.
474
+ *
475
+ * Here `effect` runs now and the returned promise settles with it, its
476
+ * failure included. Concrete rather than abstract, as `inTransaction` is,
477
+ * and right for a pooled adapter: each transaction has its own connection
478
+ * and has committed by the time its caller continues.
479
+ *
480
+ * @experimental
481
+ * @param effect - What to run once the change is durable
482
+ * @param onDeferredFailure - Receives the error of a held effect that fails
483
+ * @returns Settles once `effect` has run, or once it has been held
484
+ */
485
+ async afterCommit(effect, _onDeferredFailure) {
486
+ await effect();
487
+ }
285
488
  // ============================================================
286
489
  // Drizzle Query API Support
287
490
  // ============================================================
@@ -1229,7 +1432,8 @@ var DrizzleAdapter = class {
1229
1432
  * @remarks
1230
1433
  * Default implementation uses dialect-specific ON CONFLICT syntax.
1231
1434
  * PostgreSQL/SQLite: ON CONFLICT ... DO UPDATE
1232
- * MySQL: ON DUPLICATE KEY UPDATE
1435
+ * MySQL: the MySQL adapter overrides this, resolving the conflict by the
1436
+ * conflict columns (see its `upsert`)
1233
1437
  *
1234
1438
  * @param table - Table name
1235
1439
  * @param data - Record data
@@ -1266,9 +1470,12 @@ var DrizzleAdapter = class {
1266
1470
  updateData[key] = data[key];
1267
1471
  }
1268
1472
  }
1473
+ const keepExisting = Object.keys(updateData).length === 0;
1474
+ this.assertUpsertTarget(table, data, options, keepExisting);
1269
1475
  let query;
1270
1476
  if (caps.supportsOnConflict) {
1271
- query = db.insert(tableObj).values(data).onConflictDoUpdate({
1477
+ const inserted = db.insert(tableObj).values(data);
1478
+ query = keepExisting ? inserted.onConflictDoNothing({ target: conflictTarget }) : inserted.onConflictDoUpdate({
1272
1479
  target: conflictTarget,
1273
1480
  set: updateData
1274
1481
  });
@@ -1277,21 +1484,24 @@ var DrizzleAdapter = class {
1277
1484
  }
1278
1485
  if (caps.supportsReturning) {
1279
1486
  const result = await query.returning();
1280
- return Array.isArray(result) ? result[0] : result;
1487
+ const row = Array.isArray(result) ? result[0] : result;
1488
+ if (row !== void 0) return row;
1489
+ } else {
1490
+ await query;
1281
1491
  }
1282
- await query;
1283
- if (options.conflictColumns.length && data[options.conflictColumns[0]] !== void 0) {
1492
+ const conflictValues = options.conflictColumns.map(
1493
+ (column) => [column, data[column]]
1494
+ );
1495
+ if (conflictValues.length > 0 && conflictValues.every(([, value]) => value !== void 0)) {
1284
1496
  return await this.selectOne(
1285
1497
  table,
1286
1498
  {
1287
1499
  where: {
1288
- and: [
1289
- {
1290
- column: options.conflictColumns[0],
1291
- op: "=",
1292
- value: data[options.conflictColumns[0]]
1293
- }
1294
- ]
1500
+ and: conflictValues.map(([column, value]) => ({
1501
+ column,
1502
+ op: "=",
1503
+ value
1504
+ }))
1295
1505
  }
1296
1506
  },
1297
1507
  executor
@@ -1535,11 +1745,11 @@ var DrizzleAdapter = class {
1535
1745
  */
1536
1746
  async tableExists(tableName, schema) {
1537
1747
  try {
1538
- let sql5;
1748
+ let sql6;
1539
1749
  const params = [];
1540
1750
  switch (this.dialect) {
1541
1751
  case "postgresql":
1542
- sql5 = `
1752
+ sql6 = `
1543
1753
  SELECT EXISTS (
1544
1754
  SELECT FROM information_schema.tables
1545
1755
  WHERE table_schema = COALESCE($1, (
@@ -1553,7 +1763,7 @@ var DrizzleAdapter = class {
1553
1763
  params.push(schema ?? null, tableName);
1554
1764
  break;
1555
1765
  case "mysql":
1556
- sql5 = `
1766
+ sql6 = `
1557
1767
  SELECT COUNT(*) as count
1558
1768
  FROM information_schema.tables
1559
1769
  WHERE table_schema = DATABASE()
@@ -1562,7 +1772,7 @@ var DrizzleAdapter = class {
1562
1772
  params.push(tableName);
1563
1773
  break;
1564
1774
  case "sqlite":
1565
- sql5 = `
1775
+ sql6 = `
1566
1776
  SELECT COUNT(*) as count
1567
1777
  FROM sqlite_master
1568
1778
  WHERE type = 'table'
@@ -1578,7 +1788,7 @@ var DrizzleAdapter = class {
1578
1788
  );
1579
1789
  }
1580
1790
  const results = await this.executeQuery(
1581
- sql5,
1791
+ sql6,
1582
1792
  params
1583
1793
  );
1584
1794
  if (results.length === 0) {
@@ -1610,11 +1820,11 @@ var DrizzleAdapter = class {
1610
1820
  */
1611
1821
  async listTables(schema) {
1612
1822
  try {
1613
- let sql5;
1823
+ let sql6;
1614
1824
  const params = [];
1615
1825
  switch (this.dialect) {
1616
1826
  case "postgresql":
1617
- sql5 = `
1827
+ sql6 = `
1618
1828
  SELECT table_name
1619
1829
  FROM information_schema.tables
1620
1830
  WHERE table_schema = $1
@@ -1624,7 +1834,7 @@ var DrizzleAdapter = class {
1624
1834
  params.push(schema ?? "public");
1625
1835
  break;
1626
1836
  case "mysql":
1627
- sql5 = `
1837
+ sql6 = `
1628
1838
  SELECT table_name AS table_name
1629
1839
  FROM information_schema.tables
1630
1840
  WHERE table_schema = DATABASE()
@@ -1633,7 +1843,7 @@ var DrizzleAdapter = class {
1633
1843
  `;
1634
1844
  break;
1635
1845
  case "sqlite":
1636
- sql5 = `
1846
+ sql6 = `
1637
1847
  SELECT name as table_name
1638
1848
  FROM sqlite_master
1639
1849
  WHERE type = 'table'
@@ -1649,7 +1859,7 @@ var DrizzleAdapter = class {
1649
1859
  );
1650
1860
  }
1651
1861
  const results = await this.executeQuery(
1652
- sql5,
1862
+ sql6,
1653
1863
  params
1654
1864
  );
1655
1865
  return results.map((row) => row.table_name);
@@ -1715,6 +1925,16 @@ var DrizzleAdapter = class {
1715
1925
  createTransactionForwarders(txDb) {
1716
1926
  return createTransactionForwarders(this, txDb);
1717
1927
  }
1928
+ /**
1929
+ * The Drizzle handles one transaction hands out; see
1930
+ * `transactionDrizzleHandles`. `build` makes an instance on the
1931
+ * transaction's own connection, with the relations config when given one.
1932
+ *
1933
+ * @protected
1934
+ */
1935
+ transactionDrizzleHandles(build) {
1936
+ return transactionDrizzleHandles(build);
1937
+ }
1718
1938
  /**
1719
1939
  * A transaction context's `update`, for each adapter to bind.
1720
1940
  *
@@ -1814,6 +2034,29 @@ var DrizzleAdapter = class {
1814
2034
  bindUnmodeledStructuredAsJson(value) {
1815
2035
  return bindStructuredAsJson(value);
1816
2036
  }
2037
+ /**
2038
+ * Refuse an upsert target every dialect would not answer alike: a conflict
2039
+ * column named twice, which SQLite rejects as no constraint's target and
2040
+ * MySQL would accept; and, when nothing is to be updated, a conflict column
2041
+ * left for the database to fill, since the row that stands is then read
2042
+ * back by a value the caller never gave. Shared with the dialects that
2043
+ * override `upsert`, so the refusals are one rule.
2044
+ */
2045
+ assertUpsertTarget(table, data, options, keepExisting) {
2046
+ const columns = options.conflictColumns;
2047
+ if (new Set(columns).size !== columns.length) {
2048
+ throw createDatabaseError({
2049
+ kind: "query",
2050
+ message: `upsert on ${table} names a conflict column more than once (${columns.join(", ")}).`
2051
+ });
2052
+ }
2053
+ if (keepExisting && columns.some((column) => data[column] === void 0)) {
2054
+ throw createDatabaseError({
2055
+ kind: "query",
2056
+ message: `upsert on ${table} needs a value for every conflict column (${columns.join(", ")}).`
2057
+ });
2058
+ }
2059
+ }
1817
2060
  /**
1818
2061
  * The columns of a table-level `primaryKey({ columns })`, as the dialect's
1819
2062
  * table config reports them. The base class cannot read that config without
@@ -1872,6 +2115,34 @@ var DrizzleAdapter = class {
1872
2115
  updateReturnsRows(returning) {
1873
2116
  return returning !== void 0 && !(Array.isArray(returning) && returning.length === 0);
1874
2117
  }
2118
+ /**
2119
+ * The driver's own error inside `error`: the first in its `cause` chain that
2120
+ * `isDriverError` recognises, or `error` itself when none does.
2121
+ *
2122
+ * A query that runs through Drizzle fails with Drizzle's `DrizzleQueryError`,
2123
+ * whose message is the SQL statement and which carries no driver code; the
2124
+ * driver's error, the only object that knows what actually went wrong, is
2125
+ * its `cause`. A dialect's {@link classifyError} classifies from what this
2126
+ * returns, so a failure reads the same whether or not Drizzle wrapped it.
2127
+ *
2128
+ * The walk is bounded, because an error chain can be made cyclic and a
2129
+ * classifier that can hang is worse than one that gives up. Falling back to
2130
+ * `error` keeps an error that carries no code anywhere classified as before.
2131
+ *
2132
+ * @param error - The error as it was caught
2133
+ * @param isDriverError - Whether a link in the chain is the driver's error
2134
+ * @returns The driver's error, or `error` when no link is
2135
+ */
2136
+ driverErrorIn(error, isDriverError) {
2137
+ let cursor = error;
2138
+ for (let depth = 0; depth < 10; depth++) {
2139
+ if (cursor === null || typeof cursor !== "object") break;
2140
+ const candidate = cursor;
2141
+ if (isDriverError(candidate)) return candidate;
2142
+ cursor = candidate.cause;
2143
+ }
2144
+ return error ?? {};
2145
+ }
1875
2146
  /**
1876
2147
  * Classify an error into a DatabaseError.
1877
2148
  *
@@ -1884,14 +2155,14 @@ var DrizzleAdapter = class {
1884
2155
  *
1885
2156
  * @protected
1886
2157
  */
1887
- classifyError(error, sql5) {
2158
+ classifyError(error, sql6) {
1888
2159
  if (isDatabaseError(error)) {
1889
2160
  return error;
1890
2161
  }
1891
2162
  const errorMessage = error instanceof Error ? error.message : String(error);
1892
2163
  return this.createDatabaseError(
1893
2164
  "query",
1894
- sql5 ? `Query failed: ${errorMessage}` : errorMessage,
2165
+ sql6 ? `Query failed: ${errorMessage}` : errorMessage,
1895
2166
  error instanceof Error ? error : void 0
1896
2167
  );
1897
2168
  }