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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- export { D as DrizzleAdapter, T as TransactionCrudDelegator, a as TransactionCrudForwarders } from './adapter-C6ENLDau.cjs';
1
+ export { D as DrizzleAdapter, T as TransactionCrudDelegator, a as TransactionCrudForwarders } from './adapter-Cs3Jl9Fb.cjs';
2
2
  export { T as TableResolver } from './core-CVO7WYDj.cjs';
3
3
  import 'drizzle-orm';
4
- import './migration-B6AmjCQ1.cjs';
4
+ import './migration-Ce0hkgr_.cjs';
5
5
  import './schema-BDn8WfSL.cjs';
6
6
  import './error-BrdknH2s.cjs';
7
7
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { D as DrizzleAdapter, T as TransactionCrudDelegator, a as TransactionCrudForwarders } from './adapter-D4GM1VAZ.js';
1
+ export { D as DrizzleAdapter, T as TransactionCrudDelegator, a as TransactionCrudForwarders } from './adapter-CHlx_Taa.js';
2
2
  export { T as TableResolver } from './core-CVO7WYDj.js';
3
3
  import 'drizzle-orm';
4
- import './migration-R06fsTrz.js';
4
+ import './migration-CgoxgWOL.js';
5
5
  import './schema-BIQ0YQZ_.js';
6
6
  import './error-BrdknH2s.js';
7
7
 
package/dist/index.mjs CHANGED
@@ -1,6 +1,20 @@
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 { getColumns, count, sql, desc, asc, and, or, not, is, SQL, Column, notBetween, between, isNotNull, isNull, notInArray, inArray, ilike, like, lte, gte, lt, gt, ne, eq } from 'drizzle-orm';
2
2
 
3
3
  // src/adapter.ts
4
+
5
+ // src/column-kinds.ts
6
+ function isJsonColumn(column) {
7
+ return column.dataType === "json" || column.columnType === "PgJsonb" || column.columnType === "PgJson" || column.columnType === "MySqlJson" || column.columnType === "SQLiteTextJson";
8
+ }
9
+ function isStructuredValue(value) {
10
+ if (value === null || typeof value !== "object") return false;
11
+ if (value instanceof Date) return false;
12
+ if (Buffer.isBuffer(value) || ArrayBuffer.isView(value)) return false;
13
+ return true;
14
+ }
15
+ function bindStructuredAsJson(value) {
16
+ return isStructuredValue(value) ? JSON.stringify(value) : value;
17
+ }
4
18
  function buildDrizzleOrderBy(columns, orderBy) {
5
19
  if (!orderBy?.length) return [];
6
20
  return orderBy.flatMap((spec) => {
@@ -120,9 +134,6 @@ function createTransactionForwarders(delegator, txDb) {
120
134
  selectOne: async (table, options) => {
121
135
  return delegator.selectOne(table, options, txDb());
122
136
  },
123
- update: async (table, data, where, options) => {
124
- return delegator.update(table, data, where, options, txDb());
125
- },
126
137
  updateCount: async (table, data, where) => {
127
138
  return delegator.updateCount(table, data, where, txDb());
128
139
  },
@@ -153,6 +164,74 @@ function createDatabaseError(options) {
153
164
  if (options.cause !== void 0) error.cause = options.cause;
154
165
  return error;
155
166
  }
167
+ function isBindableColumn(value) {
168
+ return typeof value === "object" && value !== null && typeof value.name === "string" && typeof value.mapToDriverValue === "function";
169
+ }
170
+ function valueForJsonColumn(value) {
171
+ if (typeof value !== "string") return value;
172
+ try {
173
+ return JSON.parse(value);
174
+ } catch {
175
+ return value;
176
+ }
177
+ }
178
+ function columnsOf(tableObj) {
179
+ const byName = /* @__PURE__ */ new Map();
180
+ const declared = [];
181
+ for (const [jsName, column] of Object.entries(
182
+ getColumns(tableObj)
183
+ )) {
184
+ if (!isBindableColumn(column)) continue;
185
+ byName.set(column.name, column);
186
+ byName.set(jsName, column);
187
+ declared.push(column);
188
+ }
189
+ return { byName, declared };
190
+ }
191
+ function boundValue(value, column, bindUnmodeled) {
192
+ if (is(value, SQL) || is(value, Column)) return sql`${value}`;
193
+ if (column) {
194
+ return sql`${sql.param(
195
+ isJsonColumn(column) ? valueForJsonColumn(value) : value,
196
+ column
197
+ )}`;
198
+ }
199
+ return sql`${sql.param(bindUnmodeled(value))}`;
200
+ }
201
+ function buildUpdateStatement(input) {
202
+ const { byName, declared } = columnsOf(input.tableObj);
203
+ const assignments = /* @__PURE__ */ new Map();
204
+ for (const [key, value] of Object.entries(input.data)) {
205
+ const column = byName.get(key);
206
+ const name = column?.name ?? key;
207
+ if (value === void 0) {
208
+ assignments.delete(name);
209
+ continue;
210
+ }
211
+ assignments.set(
212
+ name,
213
+ sql`${sql.identifier(name)} = ${boundValue(value, column, input.bindUnmodeled)}`
214
+ );
215
+ }
216
+ if (assignments.size === 0) return null;
217
+ for (const column of declared) {
218
+ if (assignments.has(column.name) || column.onUpdateFn === void 0) {
219
+ continue;
220
+ }
221
+ const value = column.onUpdateFn();
222
+ assignments.set(
223
+ column.name,
224
+ sql`${sql.identifier(column.name)} = ${boundValue(value, column, input.bindUnmodeled)}`
225
+ );
226
+ }
227
+ const statement = sql`UPDATE ${input.tableObj} SET ${sql.join([...assignments.values()], sql`, `)}`;
228
+ const condition = buildDrizzleWhere(input.tableObj, input.where);
229
+ if (condition) statement.append(sql` WHERE ${condition}`);
230
+ if (input.returning && input.returning.length > 0) {
231
+ statement.append(sql` RETURNING ${sql.join(input.returning, sql`, `)}`);
232
+ }
233
+ return statement;
234
+ }
156
235
 
157
236
  // src/adapter.ts
158
237
  var DATE_DATA_TYPE = /(?:^|\s)date$/;
@@ -284,9 +363,7 @@ var DrizzleAdapter = class {
284
363
  continue;
285
364
  const sqlName = colDef.name;
286
365
  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") {
366
+ if (isJsonColumn(colDef)) {
290
367
  jsonColumns.add(jsName);
291
368
  }
292
369
  }
@@ -1456,11 +1533,11 @@ var DrizzleAdapter = class {
1456
1533
  */
1457
1534
  async tableExists(tableName, schema) {
1458
1535
  try {
1459
- let sql3;
1536
+ let sql5;
1460
1537
  const params = [];
1461
1538
  switch (this.dialect) {
1462
1539
  case "postgresql":
1463
- sql3 = `
1540
+ sql5 = `
1464
1541
  SELECT EXISTS (
1465
1542
  SELECT FROM information_schema.tables
1466
1543
  WHERE table_schema = COALESCE($1, (
@@ -1474,7 +1551,7 @@ var DrizzleAdapter = class {
1474
1551
  params.push(schema ?? null, tableName);
1475
1552
  break;
1476
1553
  case "mysql":
1477
- sql3 = `
1554
+ sql5 = `
1478
1555
  SELECT COUNT(*) as count
1479
1556
  FROM information_schema.tables
1480
1557
  WHERE table_schema = DATABASE()
@@ -1483,7 +1560,7 @@ var DrizzleAdapter = class {
1483
1560
  params.push(tableName);
1484
1561
  break;
1485
1562
  case "sqlite":
1486
- sql3 = `
1563
+ sql5 = `
1487
1564
  SELECT COUNT(*) as count
1488
1565
  FROM sqlite_master
1489
1566
  WHERE type = 'table'
@@ -1499,7 +1576,7 @@ var DrizzleAdapter = class {
1499
1576
  );
1500
1577
  }
1501
1578
  const results = await this.executeQuery(
1502
- sql3,
1579
+ sql5,
1503
1580
  params
1504
1581
  );
1505
1582
  if (results.length === 0) {
@@ -1531,11 +1608,11 @@ var DrizzleAdapter = class {
1531
1608
  */
1532
1609
  async listTables(schema) {
1533
1610
  try {
1534
- let sql3;
1611
+ let sql5;
1535
1612
  const params = [];
1536
1613
  switch (this.dialect) {
1537
1614
  case "postgresql":
1538
- sql3 = `
1615
+ sql5 = `
1539
1616
  SELECT table_name
1540
1617
  FROM information_schema.tables
1541
1618
  WHERE table_schema = $1
@@ -1545,7 +1622,7 @@ var DrizzleAdapter = class {
1545
1622
  params.push(schema ?? "public");
1546
1623
  break;
1547
1624
  case "mysql":
1548
- sql3 = `
1625
+ sql5 = `
1549
1626
  SELECT table_name AS table_name
1550
1627
  FROM information_schema.tables
1551
1628
  WHERE table_schema = DATABASE()
@@ -1554,7 +1631,7 @@ var DrizzleAdapter = class {
1554
1631
  `;
1555
1632
  break;
1556
1633
  case "sqlite":
1557
- sql3 = `
1634
+ sql5 = `
1558
1635
  SELECT name as table_name
1559
1636
  FROM sqlite_master
1560
1637
  WHERE type = 'table'
@@ -1570,7 +1647,7 @@ var DrizzleAdapter = class {
1570
1647
  );
1571
1648
  }
1572
1649
  const results = await this.executeQuery(
1573
- sql3,
1650
+ sql5,
1574
1651
  params
1575
1652
  );
1576
1653
  return results.map((row) => row.table_name);
@@ -1636,6 +1713,163 @@ var DrizzleAdapter = class {
1636
1713
  createTransactionForwarders(txDb) {
1637
1714
  return createTransactionForwarders(this, txDb);
1638
1715
  }
1716
+ /**
1717
+ * A transaction context's `update`, for each adapter to bind.
1718
+ *
1719
+ * Not forwarded to the pooled `update` the way `select` and `delete` are:
1720
+ * that one goes through the Drizzle query builder, which writes the columns
1721
+ * the runtime MODEL declares, and a transaction's update must reach the
1722
+ * columns the physical TABLE has — the localization transition window
1723
+ * writes a column the model has already moved to a companion table that
1724
+ * does not exist yet. The INSERT half of every transaction context already
1725
+ * builds its own statement for that reason; `update-statement.ts` is the
1726
+ * UPDATE half, spelled once for the three adapters, and this runs it: on
1727
+ * the transaction executor, classified with the operation and table named
1728
+ * as the pooled `update` classifies its failures, and read back through
1729
+ * `select` on the same executor when the caller asked for rows.
1730
+ *
1731
+ * That read is by the rows' IDENTITY where the dialect can report it: on
1732
+ * PostgreSQL and SQLite the statement carries `RETURNING <primary key>`, and
1733
+ * the read-back asks for exactly those rows — so a row another transaction
1734
+ * adds under the same predicate meanwhile is not among them, and an update
1735
+ * whose own write falsifies its predicate still reads its rows back. MySQL
1736
+ * has no RETURNING, so there the read re-runs the predicate, as the pooled
1737
+ * `update` always has on that dialect; a table with no primary key in its
1738
+ * model reads back the same way.
1739
+ *
1740
+ * @param txDb - thunk returning the transaction-bound Drizzle instance
1741
+ * @param run - how this dialect runs a statement on that instance, and the
1742
+ * rows it returns when the statement carries RETURNING: better-sqlite3
1743
+ * answers synchronously, the pooled drivers do not
1744
+ * @param bindUnmodeled - how a value binds when the model declares no
1745
+ * column for it; a declared column binds through its own encoder
1746
+ * @returns the context's `update` method
1747
+ */
1748
+ transactionUpdate(txDb, run, bindUnmodeled) {
1749
+ return async (table, data, where, options) => {
1750
+ const wantsRows = this.updateReturnsRows(options?.returning);
1751
+ const tableObj = this.resolvedTableObject(table);
1752
+ const identity = wantsRows && this.getCapabilities().supportsReturning ? this.identityColumns(tableObj) : [];
1753
+ let reported;
1754
+ try {
1755
+ reported = await run(
1756
+ this.buildTransactionUpdate(tableObj, table, data, where, {
1757
+ bindUnmodeled,
1758
+ returning: identity.map((key) => sql`${sql.identifier(key.sqlName)}`)
1759
+ }),
1760
+ identity.length > 0
1761
+ );
1762
+ } catch (error) {
1763
+ throw this.handleQueryError(error, "update", table);
1764
+ }
1765
+ if (!wantsRows) return [];
1766
+ if (identity.length === 0 || !Array.isArray(reported)) {
1767
+ return this.select(table, { where }, txDb());
1768
+ }
1769
+ const rows = [];
1770
+ for (let at = 0; at < reported.length; at += IDENTITY_READ_BACK_CHUNK) {
1771
+ rows.push(
1772
+ ...await this.select(
1773
+ table,
1774
+ {
1775
+ where: rowsByIdentity(
1776
+ identity,
1777
+ reported.slice(at, at + IDENTITY_READ_BACK_CHUNK)
1778
+ )
1779
+ },
1780
+ txDb()
1781
+ )
1782
+ );
1783
+ }
1784
+ return rows;
1785
+ };
1786
+ }
1787
+ /**
1788
+ * The columns that identify a row of this table, under both spellings: the
1789
+ * SQL name RETURNING reports and the Drizzle property name the query
1790
+ * builder's WHERE is addressed by. Columns flagged primary first; failing
1791
+ * that, the table-level primary key the dialect's table config carries
1792
+ * (`primaryKey({ columns })` leaves each column's flag false), which each
1793
+ * adapter reads through its own dialect. Empty when the model declares
1794
+ * neither, and the read-back falls back to the caller's predicate.
1795
+ */
1796
+ identityColumns(tableObj) {
1797
+ const columns = Object.entries(getColumns(tableObj));
1798
+ const flagged = columns.filter(([, c]) => c.primary === true);
1799
+ const members = flagged.length > 0 ? flagged : columns.filter(
1800
+ ([, c]) => this.compositePrimaryKey(tableObj).includes(c)
1801
+ );
1802
+ return members.flatMap(
1803
+ ([jsName, c]) => typeof c.name === "string" ? [{ jsName, sqlName: c.name }] : []
1804
+ );
1805
+ }
1806
+ /**
1807
+ * How a value binds to a column the model does not declare, on a driver
1808
+ * that binds scalars, dates and binary natively: a structured value goes
1809
+ * as JSON text, since what node-postgres and mysql2 make of a bare object
1810
+ * or array is not JSON. For the adapters to hand `transactionUpdate`.
1811
+ */
1812
+ bindUnmodeledStructuredAsJson(value) {
1813
+ return bindStructuredAsJson(value);
1814
+ }
1815
+ /**
1816
+ * The columns of a table-level `primaryKey({ columns })`, as the dialect's
1817
+ * table config reports them. The base class cannot read that config without
1818
+ * naming a dialect, so each adapter answers for its own; a dialect that does
1819
+ * not answer reports none.
1820
+ */
1821
+ compositePrimaryKey(_tableObj) {
1822
+ return [];
1823
+ }
1824
+ /** The registered table object, or the error every CRUD method throws without one. */
1825
+ resolvedTableObject(table) {
1826
+ const tableObj = this.getTableObject(table);
1827
+ if (!tableObj || typeof tableObj !== "object") {
1828
+ throw this.createDatabaseError(
1829
+ "query",
1830
+ `Table "${table}" not found in schema registry. Ensure setTableResolver() has been called during boot.`,
1831
+ void 0
1832
+ );
1833
+ }
1834
+ return tableObj;
1835
+ }
1836
+ /**
1837
+ * The statement `transactionUpdate` runs.
1838
+ *
1839
+ * @throws when nothing would be written — every key `undefined`, or none at
1840
+ * all. The query builder refused that too ("No values to set"), and a
1841
+ * patch that names nothing is a caller's mistake rather than a write of
1842
+ * nothing.
1843
+ */
1844
+ buildTransactionUpdate(tableObj, table, data, where, binding) {
1845
+ const statement = buildUpdateStatement({
1846
+ tableObj,
1847
+ data,
1848
+ where,
1849
+ bindUnmodeled: binding.bindUnmodeled,
1850
+ returning: binding.returning
1851
+ });
1852
+ if (!statement) {
1853
+ throw this.createDatabaseError(
1854
+ "query",
1855
+ `No values to set: the update of "${table}" names no column with a defined value.`,
1856
+ void 0
1857
+ );
1858
+ }
1859
+ return statement;
1860
+ }
1861
+ /**
1862
+ * Whether a transaction's `update` reads the rows it changed back: the
1863
+ * caller named columns, or `*`. An empty list, like no option at all, asks
1864
+ * for nothing and `update` answers `[]`, as it always has. The rows come
1865
+ * from a read of the update's own WHERE on its own transaction, so they are
1866
+ * the model's view of the row, decoded as every read is decoded — which is
1867
+ * what the query-builder update returned for them, and why the named list
1868
+ * selects nothing narrower.
1869
+ */
1870
+ updateReturnsRows(returning) {
1871
+ return returning !== void 0 && !(Array.isArray(returning) && returning.length === 0);
1872
+ }
1639
1873
  /**
1640
1874
  * Classify an error into a DatabaseError.
1641
1875
  *
@@ -1648,14 +1882,14 @@ var DrizzleAdapter = class {
1648
1882
  *
1649
1883
  * @protected
1650
1884
  */
1651
- classifyError(error, sql3) {
1885
+ classifyError(error, sql5) {
1652
1886
  if (isDatabaseError(error)) {
1653
1887
  return error;
1654
1888
  }
1655
1889
  const errorMessage = error instanceof Error ? error.message : String(error);
1656
1890
  return this.createDatabaseError(
1657
1891
  "query",
1658
- sql3 ? `Query failed: ${errorMessage}` : errorMessage,
1892
+ sql5 ? `Query failed: ${errorMessage}` : errorMessage,
1659
1893
  error instanceof Error ? error : void 0
1660
1894
  );
1661
1895
  }
@@ -1676,8 +1910,9 @@ var DrizzleAdapter = class {
1676
1910
  */
1677
1911
  handleQueryError(error, operation, table) {
1678
1912
  const dbError = this.classifyError(error);
1679
- if (!dbError.message.includes(operation)) {
1680
- dbError.message = `${operation} operation failed on table '${table}': ${dbError.message}`;
1913
+ const context = `${operation} operation failed on table '${table}'`;
1914
+ if (!dbError.message.includes(context)) {
1915
+ dbError.message = `${context}: ${dbError.message}`;
1681
1916
  }
1682
1917
  if (!dbError.table) {
1683
1918
  dbError.table = table;
@@ -1685,6 +1920,34 @@ var DrizzleAdapter = class {
1685
1920
  return dbError;
1686
1921
  }
1687
1922
  };
1923
+ var IDENTITY_READ_BACK_CHUNK = 200;
1924
+ function rowsByIdentity(identity, rows) {
1925
+ if (identity.length === 1) {
1926
+ const [key] = identity;
1927
+ return {
1928
+ and: [
1929
+ {
1930
+ column: key.jsName,
1931
+ op: "IN",
1932
+ value: rows.map((row) => row[key.sqlName])
1933
+ }
1934
+ ]
1935
+ };
1936
+ }
1937
+ return {
1938
+ or: rows.map(
1939
+ (row) => ({
1940
+ and: identity.map(
1941
+ (key) => ({
1942
+ column: key.jsName,
1943
+ op: "=",
1944
+ value: row[key.sqlName]
1945
+ })
1946
+ )
1947
+ })
1948
+ )
1949
+ };
1950
+ }
1688
1951
  var ALLOWED_DEFAULT_SQL_EXPRESSIONS = /* @__PURE__ */ new Set([
1689
1952
  "current_timestamp",
1690
1953
  "now()",