tempest-db-js 0.2.0 → 0.4.0

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.
@@ -285,7 +285,9 @@ var DIALECT_ALIASES = {
285
285
  sqlite3: "sqlite",
286
286
  postgresql: "postgresql",
287
287
  postgres: "postgresql",
288
- pg: "postgresql"
288
+ pg: "postgresql",
289
+ mysql: "mysql",
290
+ mariadb: "mysql"
289
291
  };
290
292
  function splitScheme(scheme) {
291
293
  const plus = scheme.indexOf("+");
@@ -325,10 +327,10 @@ function parseSqlite(raw, driver, rest) {
325
327
  raw
326
328
  };
327
329
  }
328
- function parsePostgres(raw, driver, rest) {
330
+ function parseNetworkUrl(raw, driver, rest, dialect) {
329
331
  let parsed;
330
332
  try {
331
- parsed = new URL(`postgresql:${rest}`);
333
+ parsed = new URL(`${dialect}:${rest}`);
332
334
  } catch {
333
335
  throw new InvalidDatabaseUrl(raw, "could not parse host/credentials");
334
336
  }
@@ -336,7 +338,7 @@ function parsePostgres(raw, driver, rest) {
336
338
  const options = {};
337
339
  for (const [key, value] of parsed.searchParams) options[key] = value;
338
340
  return {
339
- dialect: "postgresql",
341
+ dialect,
340
342
  driver,
341
343
  host: parsed.hostname || null,
342
344
  port: parsed.port ? Number(parsed.port) : null,
@@ -361,7 +363,8 @@ function parseDatabaseUrl(url) {
361
363
  throw new InvalidDatabaseUrl(url, `unknown dialect ${JSON.stringify(base)}`);
362
364
  }
363
365
  const rest = url.slice(schemeEnd + 1);
364
- return dialect === "sqlite" ? parseSqlite(url, driver, rest) : parsePostgres(url, driver, rest);
366
+ if (dialect === "sqlite") return parseSqlite(url, driver, rest);
367
+ return parseNetworkUrl(url, driver, rest, dialect);
365
368
  }
366
369
  function detectDialect(url) {
367
370
  return parseDatabaseUrl(url).dialect;
@@ -660,18 +663,30 @@ var BaseDialect = class _BaseDialect {
660
663
  const rowsSql = node.values.map(() => `(${columns.map(() => this.placeholder(++position)).join(", ")})`).join(", ");
661
664
  let sql2 = `INSERT INTO ${this.quoteId(node.table)} (${colSql}) VALUES ${rowsSql}`;
662
665
  if (node.onConflict) {
663
- const target = node.onConflict.target.map((c) => this.quoteId(c)).join(", ");
664
- if (node.onConflict.update === "nothing") {
665
- sql2 += ` ON CONFLICT (${target}) DO NOTHING`;
666
- } else {
667
- const assignments = conflictCols.map((c) => `${this.quoteId(c)} = ${this.placeholder(++position)}`).join(", ");
668
- sql2 += ` ON CONFLICT (${target}) DO UPDATE SET ${assignments}`;
669
- }
666
+ sql2 += this.renderConflict(
667
+ node.onConflict,
668
+ conflictCols,
669
+ () => this.placeholder(++position)
670
+ );
670
671
  }
671
672
  sql2 += this.compileReturning(node.returning);
672
673
  _BaseDialect.insertTemplates.set(key, sql2);
673
674
  return sql2;
674
675
  }
676
+ /**
677
+ * Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
678
+ * `ON CONFLICT (...) DO NOTHING | DO UPDATE SET ...`; MySQL overrides this.
679
+ *
680
+ * @param onConflict The conflict clause from the node.
681
+ * @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
682
+ * @param nextPlaceholder Yields the next positional placeholder (advances the count).
683
+ */
684
+ renderConflict(onConflict, conflictCols, nextPlaceholder) {
685
+ const target = onConflict.target.map((c) => this.quoteId(c)).join(", ");
686
+ if (onConflict.update === "nothing") return ` ON CONFLICT (${target}) DO NOTHING`;
687
+ const assignments = conflictCols.map((c) => `${this.quoteId(c)} = ${nextPlaceholder()}`).join(", ");
688
+ return ` ON CONFLICT (${target}) DO UPDATE SET ${assignments}`;
689
+ }
675
690
  compileUpdate(node, params) {
676
691
  const sets = Object.entries(node.set).map(([col, value]) => `${this.quoteId(col)} = ${params.bind(value)}`).join(", ");
677
692
  let sql2 = `UPDATE ${this.quoteId(node.table)} SET ${sets}`;
@@ -809,8 +824,46 @@ var PostgresDialect = class extends BaseDialect {
809
824
  return `${column2} ILIKE ${param}`;
810
825
  }
811
826
  };
827
+ var mysqlQuotedIds = /* @__PURE__ */ new Map();
828
+ var MysqlDialect = class extends BaseDialect {
829
+ name = "mysql";
830
+ placeholder() {
831
+ return "?";
832
+ }
833
+ ilike(column2, param) {
834
+ return `${column2} LIKE ${param}`;
835
+ }
836
+ quoteId(name) {
837
+ const cached = mysqlQuotedIds.get(name);
838
+ if (cached !== void 0) return cached;
839
+ const quoted = `\`${name.replace(/`/g, "``")}\``;
840
+ mysqlQuotedIds.set(name, quoted);
841
+ return quoted;
842
+ }
843
+ renderConflict(onConflict, conflictCols, nextPlaceholder) {
844
+ if (onConflict.update === "nothing") {
845
+ const col = this.quoteId(onConflict.target[0] ?? "id");
846
+ return ` ON DUPLICATE KEY UPDATE ${col} = ${col}`;
847
+ }
848
+ const assignments = conflictCols.map((c) => `${this.quoteId(c)} = ${nextPlaceholder()}`).join(", ");
849
+ return ` ON DUPLICATE KEY UPDATE ${assignments}`;
850
+ }
851
+ compileReturning(returning) {
852
+ if (returning === null) return "";
853
+ throw new Error(
854
+ "RETURNING is not supported on MySQL \u2014 insert, then SELECT by key (e.g. LAST_INSERT_ID())."
855
+ );
856
+ }
857
+ };
812
858
  function getDialect(name) {
813
- return name === "sqlite" ? new SqliteDialect() : new PostgresDialect();
859
+ switch (name) {
860
+ case "sqlite":
861
+ return new SqliteDialect();
862
+ case "postgresql":
863
+ return new PostgresDialect();
864
+ case "mysql":
865
+ return new MysqlDialect();
866
+ }
814
867
  }
815
868
 
816
869
  // src/join.ts
@@ -1573,12 +1626,76 @@ function createEngine(url, options) {
1573
1626
  options?.onQuery
1574
1627
  );
1575
1628
  }
1629
+ if (parsed.dialect === "mysql") {
1630
+ return new AsyncEngine(
1631
+ createMysqlDriver(parsed.raw, options?.pool),
1632
+ "mysql",
1633
+ options?.onQuery
1634
+ );
1635
+ }
1576
1636
  return new AsyncEngine(
1577
1637
  createPostgresDriver(parsed.raw, options?.pool),
1578
1638
  "postgresql",
1579
1639
  options?.onQuery
1580
1640
  );
1581
1641
  }
1642
+ function encodeMysqlParam(value) {
1643
+ if (value === void 0 || value === null) return null;
1644
+ if (typeof value === "boolean") return value ? 1 : 0;
1645
+ if (value instanceof Uint8Array) return value;
1646
+ if (value instanceof Date) return value;
1647
+ if (typeof value === "object") return JSON.stringify(value);
1648
+ return value;
1649
+ }
1650
+ function toMysqlResult(rows) {
1651
+ if (Array.isArray(rows)) {
1652
+ return { rows, changes: 0 };
1653
+ }
1654
+ const header = rows;
1655
+ return { rows: [], changes: header.affectedRows ?? 0 };
1656
+ }
1657
+ function createMysqlDriver(url, pool) {
1658
+ let poolHandle;
1659
+ const ensure = async () => {
1660
+ if (poolHandle) return;
1661
+ const moduleName = "mysql2/promise";
1662
+ const mod = await import(
1663
+ /* @vite-ignore */
1664
+ moduleName
1665
+ );
1666
+ const opts = { uri: url };
1667
+ if (pool?.size !== void 0) opts.connectionLimit = pool.size;
1668
+ if (pool?.idleTimeoutMs !== void 0) opts.idleTimeout = pool.idleTimeoutMs;
1669
+ if (pool?.connectTimeoutMs !== void 0) opts.connectTimeout = pool.connectTimeoutMs;
1670
+ poolHandle = mod.createPool(opts);
1671
+ };
1672
+ const runOn = async (queryable, sql2, params) => {
1673
+ const [rows] = await queryable.query(sql2, params.map(encodeMysqlParam));
1674
+ return toMysqlResult(rows);
1675
+ };
1676
+ return {
1677
+ async execute(sql2, params) {
1678
+ await ensure();
1679
+ return runOn(poolHandle, sql2, params);
1680
+ },
1681
+ async reserve() {
1682
+ await ensure();
1683
+ const conn = await poolHandle.getConnection();
1684
+ return {
1685
+ execute: (sql2, params) => runOn(conn, sql2, params),
1686
+ async release() {
1687
+ conn.release();
1688
+ },
1689
+ async close() {
1690
+ conn.release();
1691
+ }
1692
+ };
1693
+ },
1694
+ async close() {
1695
+ if (poolHandle) await poolHandle.end();
1696
+ }
1697
+ };
1698
+ }
1582
1699
  function toPostgresResult(rows) {
1583
1700
  const arr = rows;
1584
1701
  return { rows: Array.from(arr), changes: arr.count ?? arr.length };
@@ -1631,7 +1748,8 @@ function createPostgresDriver(url, pool) {
1631
1748
  var DEFAULT_FLAGS = {
1632
1749
  primaryKey: false,
1633
1750
  notNull: false,
1634
- hasDefault: false
1751
+ hasDefault: false,
1752
+ unique: false
1635
1753
  };
1636
1754
  var sql = {
1637
1755
  /** Current timestamp at insert (`CURRENT_TIMESTAMP` / `now()`). */
@@ -1651,23 +1769,38 @@ var sql = {
1651
1769
  function isDefaultValue(value) {
1652
1770
  return typeof value === "object" && value !== null && "kind" in value && (value.kind === "literal" || value.kind === "expression");
1653
1771
  }
1772
+ function parseReference(ref, options) {
1773
+ const dot = ref.lastIndexOf(".");
1774
+ if (dot <= 0 || dot === ref.length - 1) {
1775
+ throw new Error(`Invalid foreign key reference "${ref}"; expected "table.column".`);
1776
+ }
1777
+ return {
1778
+ table: ref.slice(0, dot),
1779
+ column: ref.slice(dot + 1),
1780
+ onDelete: options?.onDelete,
1781
+ onUpdate: options?.onUpdate
1782
+ };
1783
+ }
1654
1784
  var Column = class _Column {
1655
- constructor(type, flags, defaultValue = null, onUpdateValue = null) {
1785
+ constructor(type, flags, defaultValue = null, onUpdateValue = null, reference = null) {
1656
1786
  this.type = type;
1657
1787
  this.flags = flags;
1658
1788
  this.defaultValue = defaultValue;
1659
1789
  this.onUpdateValue = onUpdateValue;
1790
+ this.reference = reference;
1660
1791
  }
1661
1792
  type;
1662
1793
  flags;
1663
1794
  defaultValue;
1664
1795
  onUpdateValue;
1796
+ reference;
1665
1797
  primaryKey() {
1666
1798
  return new _Column(
1667
1799
  this.type,
1668
1800
  { ...this.flags, primaryKey: true, hasDefault: true },
1669
1801
  this.defaultValue,
1670
- this.onUpdateValue
1802
+ this.onUpdateValue,
1803
+ this.reference
1671
1804
  );
1672
1805
  }
1673
1806
  notNull() {
@@ -1675,7 +1808,40 @@ var Column = class _Column {
1675
1808
  this.type,
1676
1809
  { ...this.flags, notNull: true },
1677
1810
  this.defaultValue,
1678
- this.onUpdateValue
1811
+ this.onUpdateValue,
1812
+ this.reference
1813
+ );
1814
+ }
1815
+ /**
1816
+ * Add a `UNIQUE` constraint to the column (mirrors SQLAlchemy's
1817
+ * `mapped_column(unique=True)`). DDL-only — does not change the inferred type.
1818
+ */
1819
+ unique() {
1820
+ return new _Column(
1821
+ this.type,
1822
+ { ...this.flags, unique: true },
1823
+ this.defaultValue,
1824
+ this.onUpdateValue,
1825
+ this.reference
1826
+ );
1827
+ }
1828
+ /**
1829
+ * Declare a foreign-key reference to another table's column, à la SQLAlchemy's
1830
+ * `mapped_column(ForeignKey("table.column", ondelete=...))`. DDL-only — does
1831
+ * not change the inferred type.
1832
+ *
1833
+ * @param ref The target as `"table.column"` (e.g. `"users.id"`).
1834
+ * @param options Optional `onDelete` / `onUpdate` referential actions.
1835
+ * @returns A new column carrying the reference.
1836
+ * @throws Error When `ref` is not a valid `"table.column"` string.
1837
+ */
1838
+ references(ref, options) {
1839
+ return new _Column(
1840
+ this.type,
1841
+ this.flags,
1842
+ this.defaultValue,
1843
+ this.onUpdateValue,
1844
+ parseReference(ref, options)
1679
1845
  );
1680
1846
  }
1681
1847
  /**
@@ -1688,7 +1854,8 @@ var Column = class _Column {
1688
1854
  this.type,
1689
1855
  { ...this.flags, hasDefault: true },
1690
1856
  resolved,
1691
- this.onUpdateValue
1857
+ this.onUpdateValue,
1858
+ this.reference
1692
1859
  );
1693
1860
  }
1694
1861
  /**
@@ -1697,7 +1864,7 @@ var Column = class _Column {
1697
1864
  */
1698
1865
  onUpdate(value) {
1699
1866
  const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
1700
- return new _Column(this.type, this.flags, this.defaultValue, resolved);
1867
+ return new _Column(this.type, this.flags, this.defaultValue, resolved, this.reference);
1701
1868
  }
1702
1869
  };
1703
1870
  function makeColumn(kind, meta = {}) {
@@ -1751,8 +1918,36 @@ var column = {
1751
1918
  /** `ENUM(...values)` → a string-literal union of the given values. */
1752
1919
  enum: (...values) => makeColumn("enum", { values })
1753
1920
  };
1921
+ function unique(...columns) {
1922
+ if (columns.length === 0) {
1923
+ throw new Error("unique() requires at least one column.");
1924
+ }
1925
+ return { kind: "unique", columns };
1926
+ }
1927
+ function foreignKey(columns, refTable, refColumns, options) {
1928
+ if (columns.length === 0 || columns.length !== refColumns.length) {
1929
+ throw new Error(
1930
+ "foreignKey() requires matching, non-empty local and referenced column lists."
1931
+ );
1932
+ }
1933
+ return {
1934
+ kind: "foreignKey",
1935
+ name: options?.name,
1936
+ columns,
1937
+ refTable,
1938
+ refColumns,
1939
+ onDelete: options?.onDelete,
1940
+ onUpdate: options?.onUpdate
1941
+ };
1942
+ }
1754
1943
  var Model = class {
1755
1944
  static tablename;
1945
+ /**
1946
+ * Optional table-level constraints (composite unique / foreign keys), returned
1947
+ * by a thunk so forward references resolve lazily. Mirrors SQLAlchemy's
1948
+ * `__table_args__`.
1949
+ */
1950
+ static tableArgs;
1756
1951
  };
1757
1952
  var columnsCache = /* @__PURE__ */ new WeakMap();
1758
1953
  function columnsOf(model) {
@@ -1769,6 +1964,6 @@ function columnsOf(model) {
1769
1964
  return out;
1770
1965
  }
1771
1966
 
1772
- export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, NoResultError, NodeSqliteDriver, OPERATORS, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, update };
1773
- //# sourceMappingURL=chunk-AGDD7K3F.js.map
1774
- //# sourceMappingURL=chunk-AGDD7K3F.js.map
1967
+ export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, column, columnsOf, count, createEngine, createSyncEngine, del, detectDialect, foreignKey, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, select, sql, stringify, sum, toCondNode, toDict, toJSON, unique, update };
1968
+ //# sourceMappingURL=chunk-JR4MLFQN.js.map
1969
+ //# sourceMappingURL=chunk-JR4MLFQN.js.map