tempest-db-js 0.6.0 → 0.8.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.
@@ -608,6 +608,21 @@ function assertWritableValues(model, values, clause) {
608
608
  }
609
609
  if (issues.length > 0) throw new ValidationError(model.tablename, issues);
610
610
  }
611
+ function assertConsistentRows(model, rows) {
612
+ if (rows.length < 2) return;
613
+ const union = /* @__PURE__ */ new Set();
614
+ for (const row of rows) for (const key of Object.keys(row)) union.add(key);
615
+ const inconsistent = [...union].filter((key) => rows.some((row) => !(key in row)));
616
+ if (inconsistent.length === 0) return;
617
+ const columns = columnsOf(model);
618
+ const defaulted = inconsistent.filter((key) => columns[key]?.flags.hasDefault);
619
+ if (defaulted.length === 0) return;
620
+ const named = defaulted.map((c) => `"${c}"`).join(", ");
621
+ const verb = defaulted.length === 1 ? "has" : "have";
622
+ throw new ValidationError(model.tablename, [
623
+ `values: ${named} ${verb} a default but is missing from some rows of this multi-row insert \u2014 every row shares one column list, so the omitting rows would be written as NULL instead of taking the default. Give the column in every row, or insert the rows separately.`
624
+ ]);
625
+ }
611
626
  function describeValue(value) {
612
627
  if (typeof value === "function") return "a function";
613
628
  if (Array.isArray(value)) return "an array";
@@ -630,11 +645,13 @@ var InsertBuilder = class _InsertBuilder {
630
645
  * @param rows One row, or an array of rows.
631
646
  * @returns A builder carrying the rows.
632
647
  * @throws ValidationError When a value is not a column value the dialect can
633
- * bind (see the `sql` helpers for writing an expression instead).
648
+ * bind (see the `sql` helpers for writing an expression instead), or when the
649
+ * rows of a multi-row insert disagree about a column that has a default.
634
650
  */
635
651
  values(rows) {
636
652
  const list = Array.isArray(rows) ? rows : [rows];
637
653
  for (const row of list) assertWritableValues(this.source, row, "values");
654
+ assertConsistentRows(this.source, list);
638
655
  return this.with({ values: list });
639
656
  }
640
657
  /**
@@ -936,6 +953,18 @@ var Params = class {
936
953
  return this.placeholder(this.values.length);
937
954
  }
938
955
  };
956
+ function insertColumns(rows) {
957
+ const columns = [];
958
+ const seen = /* @__PURE__ */ new Set();
959
+ for (const row of rows) {
960
+ for (const key of Object.keys(row)) {
961
+ if (seen.has(key)) continue;
962
+ seen.add(key);
963
+ columns.push(key);
964
+ }
965
+ }
966
+ return columns;
967
+ }
939
968
  function insertHasExpression(node) {
940
969
  for (const row of node.values) {
941
970
  for (const value of Object.values(row)) {
@@ -1165,7 +1194,7 @@ var BaseDialect = class _BaseDialect {
1165
1194
  * SQL order, so placeholder positions stay correct.
1166
1195
  */
1167
1196
  compileInsert(node, params) {
1168
- const columns = node.values.length > 0 ? Object.keys(node.values[0]) : [];
1197
+ const columns = insertColumns(node.values);
1169
1198
  const conflict = node.onConflict;
1170
1199
  const cacheable = conflict?.targetWhere === void 0 && conflict?.updateWhere === void 0 && !insertHasExpression(node);
1171
1200
  if (!cacheable) return this.compileInsertDirect(node, columns, params);
@@ -1974,10 +2003,18 @@ var NodeSqliteDriver = class _NodeSqliteDriver {
1974
2003
  constructor(database) {
1975
2004
  this.db = database;
1976
2005
  }
1977
- /** Open a `node:sqlite` database at the given path (or `:memory:`). */
1978
- static open(path) {
2006
+ /**
2007
+ * Open a `node:sqlite` database at the given path (or `:memory:`).
2008
+ *
2009
+ * @param path The database file, or `":memory:"`.
2010
+ * @param options Passed straight to `DatabaseSync` (`readOnly`, `timeout`, …).
2011
+ * @returns A driver over the open handle.
2012
+ */
2013
+ static open(path, options) {
1979
2014
  const { DatabaseSync } = nodeRequire("node:sqlite");
1980
- return new _NodeSqliteDriver(new DatabaseSync(path));
2015
+ return new _NodeSqliteDriver(
2016
+ options ? new DatabaseSync(path, { ...options }) : new DatabaseSync(path)
2017
+ );
1981
2018
  }
1982
2019
  /** Return the cached prepared statement for `sql`, preparing it on first use. */
1983
2020
  // biome-ignore lint/suspicious/noExplicitAny: statement type is unavailable here.
@@ -2007,6 +2044,68 @@ var NodeSqliteDriver = class _NodeSqliteDriver {
2007
2044
  this.db.close();
2008
2045
  }
2009
2046
  };
2047
+ var BetterSqliteDriver = class _BetterSqliteDriver {
2048
+ // biome-ignore lint/suspicious/noExplicitAny: the peer dep's types are optional here.
2049
+ db;
2050
+ /** Prepared-statement cache keyed by SQL text — see {@link NodeSqliteDriver}. */
2051
+ // biome-ignore lint/suspicious/noExplicitAny: see above.
2052
+ statements = /* @__PURE__ */ new Map();
2053
+ // biome-ignore lint/suspicious/noExplicitAny: accept an already-open Database handle.
2054
+ constructor(database) {
2055
+ this.db = database;
2056
+ }
2057
+ /**
2058
+ * Open a `better-sqlite3` database at the given path (or `":memory:"`).
2059
+ *
2060
+ * @param path The database file, or `":memory:"`.
2061
+ * @param options Passed straight to `new Database()` (`readonly`, `timeout`, …).
2062
+ * @returns A driver over the open handle.
2063
+ * @throws If `better-sqlite3` is not installed — it is an optional peer
2064
+ * dependency, so the error names the package to install.
2065
+ */
2066
+ static open(path, options) {
2067
+ let Database;
2068
+ try {
2069
+ const mod = nodeRequire("better-sqlite3");
2070
+ Database = mod.default ?? mod;
2071
+ } catch (cause) {
2072
+ throw new Error(
2073
+ 'The "better-sqlite3" driver requires the better-sqlite3 package: npm install better-sqlite3',
2074
+ { cause }
2075
+ );
2076
+ }
2077
+ return new _BetterSqliteDriver(
2078
+ options ? new Database(path, { ...options }) : new Database(path)
2079
+ );
2080
+ }
2081
+ /** Return the cached prepared statement for `sql`, preparing it on first use. */
2082
+ // biome-ignore lint/suspicious/noExplicitAny: statement type is optional here.
2083
+ prepare(sql2) {
2084
+ const cached = this.statements.get(sql2);
2085
+ if (cached) return cached;
2086
+ const stmt = this.db.prepare(sql2);
2087
+ this.statements.set(sql2, stmt);
2088
+ return stmt;
2089
+ }
2090
+ execute(sql2, params) {
2091
+ const stmt = this.prepare(sql2);
2092
+ const bound = params.map(encodeSqliteParam);
2093
+ if (stmt.reader) {
2094
+ return { rows: stmt.all(...bound), changes: 0 };
2095
+ }
2096
+ const info = stmt.run(...bound);
2097
+ return { rows: [], changes: Number(info.changes ?? 0) };
2098
+ }
2099
+ *iterate(sql2, params) {
2100
+ const stmt = this.prepare(sql2);
2101
+ const bound = params.map(encodeSqliteParam);
2102
+ yield* stmt.iterate(...bound);
2103
+ }
2104
+ close() {
2105
+ this.statements.clear();
2106
+ this.db.close();
2107
+ }
2108
+ };
2010
2109
  function returnsRows(sql2) {
2011
2110
  return /^\s*(select|pragma)/i.test(sql2) || /\breturning\b/i.test(sql2);
2012
2111
  }
@@ -2456,6 +2555,13 @@ var AsyncSession = class _AsyncSession {
2456
2555
  await this.close();
2457
2556
  }
2458
2557
  };
2558
+ function emitNotice(logger, notice) {
2559
+ if (!logger) return;
2560
+ try {
2561
+ logger(notice);
2562
+ } catch {
2563
+ }
2564
+ }
2459
2565
  var SyncEngine = class {
2460
2566
  constructor(driver, logger) {
2461
2567
  this.driver = driver;
@@ -2523,8 +2629,43 @@ function asAsync(driver) {
2523
2629
  } : {}
2524
2630
  };
2525
2631
  }
2526
- function openSqliteDriver(path, _options) {
2527
- return NodeSqliteDriver.open(path);
2632
+ var SQLITE_DRIVER_ALIASES = {
2633
+ "node:sqlite": "node:sqlite",
2634
+ "node-sqlite": "node:sqlite",
2635
+ node: "node:sqlite",
2636
+ "better-sqlite3": "better-sqlite3",
2637
+ better_sqlite3: "better-sqlite3",
2638
+ bettersqlite3: "better-sqlite3"
2639
+ };
2640
+ var SERVER_DRIVER_ALIASES = {
2641
+ postgresql: ["postgres", "postgres.js", "postgresjs", "pg"],
2642
+ mysql: ["mysql", "mysql2"]
2643
+ };
2644
+ function checkServerDriver(dialect, driver) {
2645
+ if (!driver) return;
2646
+ const accepted = SERVER_DRIVER_ALIASES[dialect] ?? [];
2647
+ if (accepted.includes(driver.toLowerCase())) return;
2648
+ throw new Error(
2649
+ `Unknown ${dialect} driver ${JSON.stringify(driver)}; tempest-db-js runs ${dialect} on ${JSON.stringify(accepted[0])}.`
2650
+ );
2651
+ }
2652
+ function resolveSqliteDriver(parsed, options) {
2653
+ const explicit = options?.driver;
2654
+ if (explicit) {
2655
+ const resolved = SQLITE_DRIVER_ALIASES[explicit.toLowerCase()];
2656
+ if (!resolved) {
2657
+ throw new Error(
2658
+ `Unknown SQLite driver ${JSON.stringify(explicit)}; supported: "node:sqlite" (built-in, default) and "better-sqlite3".`
2659
+ );
2660
+ }
2661
+ return resolved;
2662
+ }
2663
+ const fromUrl = parsed.driver ? SQLITE_DRIVER_ALIASES[parsed.driver.toLowerCase()] : void 0;
2664
+ return fromUrl ?? "node:sqlite";
2665
+ }
2666
+ function openSqliteDriver(parsed, options) {
2667
+ const path = parsed.database ?? ":memory:";
2668
+ return resolveSqliteDriver(parsed, options) === "better-sqlite3" ? BetterSqliteDriver.open(path, options?.driverOptions) : NodeSqliteDriver.open(path, options?.driverOptions);
2528
2669
  }
2529
2670
  function createSyncEngine(url, options) {
2530
2671
  const parsed = parseDatabaseUrl(url);
@@ -2533,29 +2674,28 @@ function createSyncEngine(url, options) {
2533
2674
  `createSyncEngine supports only SQLite; ${parsed.dialect} is async-only \u2014 use createEngine.`
2534
2675
  );
2535
2676
  }
2536
- return new SyncEngine(
2537
- openSqliteDriver(parsed.database ?? ":memory:"),
2538
- options?.onQuery
2539
- );
2677
+ return new SyncEngine(openSqliteDriver(parsed, options), options?.onQuery);
2540
2678
  }
2541
2679
  function createEngine(url, options) {
2542
2680
  const parsed = parseDatabaseUrl(url);
2543
2681
  if (parsed.dialect === "sqlite") {
2544
2682
  return new AsyncEngine(
2545
- asAsync(openSqliteDriver(parsed.database ?? ":memory:")),
2683
+ asAsync(openSqliteDriver(parsed, options)),
2546
2684
  "sqlite",
2547
2685
  options?.onQuery
2548
2686
  );
2549
2687
  }
2550
2688
  if (parsed.dialect === "mysql") {
2689
+ checkServerDriver("mysql", options?.driver);
2551
2690
  return new AsyncEngine(
2552
- createMysqlDriver(parsed.raw, options?.pool),
2691
+ createMysqlDriver(parsed.raw, options),
2553
2692
  "mysql",
2554
2693
  options?.onQuery
2555
2694
  );
2556
2695
  }
2696
+ checkServerDriver("postgresql", options?.driver);
2557
2697
  return new AsyncEngine(
2558
- createPostgresDriver(parsed.raw, options?.pool),
2698
+ createPostgresDriver(parsed.raw, options),
2559
2699
  "postgresql",
2560
2700
  options?.onQuery
2561
2701
  );
@@ -2575,7 +2715,8 @@ function toMysqlResult(rows) {
2575
2715
  const header = rows;
2576
2716
  return { rows: [], changes: header.affectedRows ?? 0 };
2577
2717
  }
2578
- function createMysqlDriver(url, pool) {
2718
+ function createMysqlDriver(url, options) {
2719
+ const pool = options?.pool;
2579
2720
  let poolHandle;
2580
2721
  const ensure = async () => {
2581
2722
  if (poolHandle) return;
@@ -2588,6 +2729,7 @@ function createMysqlDriver(url, pool) {
2588
2729
  if (pool?.size !== void 0) opts.connectionLimit = pool.size;
2589
2730
  if (pool?.idleTimeoutMs !== void 0) opts.idleTimeout = pool.idleTimeoutMs;
2590
2731
  if (pool?.connectTimeoutMs !== void 0) opts.connectTimeout = pool.connectTimeoutMs;
2732
+ Object.assign(opts, options?.driverOptions ?? {});
2591
2733
  poolHandle = mod.createPool(opts);
2592
2734
  };
2593
2735
  const runOn = async (queryable, sql2, params) => {
@@ -2621,7 +2763,8 @@ function toPostgresResult(rows) {
2621
2763
  const arr = rows;
2622
2764
  return { rows: Array.from(arr), changes: arr.count ?? arr.length };
2623
2765
  }
2624
- function createPostgresDriver(url, pool) {
2766
+ function createPostgresDriver(url, options) {
2767
+ const pool = options?.pool;
2625
2768
  let client;
2626
2769
  const ensure = async () => {
2627
2770
  if (client) return;
@@ -2637,6 +2780,8 @@ function createPostgresDriver(url, pool) {
2637
2780
  if (pool?.connectTimeoutMs !== void 0) {
2638
2781
  opts.connect_timeout = Math.ceil(pool.connectTimeoutMs / 1e3);
2639
2782
  }
2783
+ opts.onnotice = (notice) => emitNotice(options?.onNotice, notice);
2784
+ Object.assign(opts, options?.driverOptions ?? {});
2640
2785
  client = (mod.default ?? mod)(url, opts);
2641
2786
  };
2642
2787
  return {
@@ -3022,6 +3167,6 @@ function dbColumn(names, prop) {
3022
3167
  return names?.[prop] ?? prop;
3023
3168
  }
3024
3169
 
3025
- export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, Expression, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, Params, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, col, column, columnNamesOf, columnPropsOf, columnsOf, count, createEngine, createSyncEngine, dbColumn, del, detectDialect, fn, foreignKey, fromDict, getDialect, hasMany, insert, isCondition, isExpression, isSqlExpression, isSubquery, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, renderPortableToken, select, sql, stringify, sum, toAsyncDriver, toCondNode, toDict, toJSON, toSnakeCase, unique, update, val };
3026
- //# sourceMappingURL=chunk-G7O5DCCC.js.map
3027
- //# sourceMappingURL=chunk-G7O5DCCC.js.map
3170
+ export { ActiveRecord, Agg, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, BetterSqliteDriver, Column, DeleteBuilder, Expression, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, MysqlDialect, NoResultError, NodeSqliteDriver, OPERATORS, Params, PostgresDialect, QueryExecutionError, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, activeRecord, and, avg, belongsTo, col, column, columnNamesOf, columnPropsOf, columnsOf, count, createEngine, createSyncEngine, dbColumn, del, detectDialect, fn, foreignKey, fromDict, getDialect, hasMany, insert, isCondition, isExpression, isSqlExpression, isSubquery, join, loadRelations, max, min, not, or, parse, parseDatabaseUrl, renderPortableToken, select, sql, stringify, sum, toAsyncDriver, toCondNode, toDict, toJSON, toSnakeCase, unique, update, val };
3171
+ //# sourceMappingURL=chunk-NH6K5LTX.js.map
3172
+ //# sourceMappingURL=chunk-NH6K5LTX.js.map