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