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.
package/dist/index.cjs CHANGED
@@ -611,6 +611,21 @@ function assertWritableValues(model, values, clause) {
611
611
  }
612
612
  if (issues.length > 0) throw new ValidationError(model.tablename, issues);
613
613
  }
614
+ function assertConsistentRows(model, rows) {
615
+ if (rows.length < 2) return;
616
+ const union = /* @__PURE__ */ new Set();
617
+ for (const row of rows) for (const key of Object.keys(row)) union.add(key);
618
+ const inconsistent = [...union].filter((key) => rows.some((row) => !(key in row)));
619
+ if (inconsistent.length === 0) return;
620
+ const columns = columnsOf(model);
621
+ const defaulted = inconsistent.filter((key) => columns[key]?.flags.hasDefault);
622
+ if (defaulted.length === 0) return;
623
+ const named = defaulted.map((c) => `"${c}"`).join(", ");
624
+ const verb = defaulted.length === 1 ? "has" : "have";
625
+ throw new ValidationError(model.tablename, [
626
+ `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.`
627
+ ]);
628
+ }
614
629
  function describeValue(value) {
615
630
  if (typeof value === "function") return "a function";
616
631
  if (Array.isArray(value)) return "an array";
@@ -633,11 +648,13 @@ var InsertBuilder = class _InsertBuilder {
633
648
  * @param rows One row, or an array of rows.
634
649
  * @returns A builder carrying the rows.
635
650
  * @throws ValidationError When a value is not a column value the dialect can
636
- * bind (see the `sql` helpers for writing an expression instead).
651
+ * bind (see the `sql` helpers for writing an expression instead), or when the
652
+ * rows of a multi-row insert disagree about a column that has a default.
637
653
  */
638
654
  values(rows) {
639
655
  const list = Array.isArray(rows) ? rows : [rows];
640
656
  for (const row of list) assertWritableValues(this.source, row, "values");
657
+ assertConsistentRows(this.source, list);
641
658
  return this.with({ values: list });
642
659
  }
643
660
  /**
@@ -939,6 +956,18 @@ var Params = class {
939
956
  return this.placeholder(this.values.length);
940
957
  }
941
958
  };
959
+ function insertColumns(rows) {
960
+ const columns = [];
961
+ const seen = /* @__PURE__ */ new Set();
962
+ for (const row of rows) {
963
+ for (const key of Object.keys(row)) {
964
+ if (seen.has(key)) continue;
965
+ seen.add(key);
966
+ columns.push(key);
967
+ }
968
+ }
969
+ return columns;
970
+ }
942
971
  function insertHasExpression(node) {
943
972
  for (const row of node.values) {
944
973
  for (const value of Object.values(row)) {
@@ -1168,7 +1197,7 @@ var BaseDialect = class _BaseDialect {
1168
1197
  * SQL order, so placeholder positions stay correct.
1169
1198
  */
1170
1199
  compileInsert(node, params) {
1171
- const columns = node.values.length > 0 ? Object.keys(node.values[0]) : [];
1200
+ const columns = insertColumns(node.values);
1172
1201
  const conflict = node.onConflict;
1173
1202
  const cacheable = conflict?.targetWhere === void 0 && conflict?.updateWhere === void 0 && !insertHasExpression(node);
1174
1203
  if (!cacheable) return this.compileInsertDirect(node, columns, params);
@@ -1977,10 +2006,18 @@ var NodeSqliteDriver = class _NodeSqliteDriver {
1977
2006
  constructor(database) {
1978
2007
  this.db = database;
1979
2008
  }
1980
- /** Open a `node:sqlite` database at the given path (or `:memory:`). */
1981
- static open(path) {
2009
+ /**
2010
+ * Open a `node:sqlite` database at the given path (or `:memory:`).
2011
+ *
2012
+ * @param path The database file, or `":memory:"`.
2013
+ * @param options Passed straight to `DatabaseSync` (`readOnly`, `timeout`, …).
2014
+ * @returns A driver over the open handle.
2015
+ */
2016
+ static open(path, options) {
1982
2017
  const { DatabaseSync } = nodeRequire("node:sqlite");
1983
- return new _NodeSqliteDriver(new DatabaseSync(path));
2018
+ return new _NodeSqliteDriver(
2019
+ options ? new DatabaseSync(path, { ...options }) : new DatabaseSync(path)
2020
+ );
1984
2021
  }
1985
2022
  /** Return the cached prepared statement for `sql`, preparing it on first use. */
1986
2023
  // biome-ignore lint/suspicious/noExplicitAny: statement type is unavailable here.
@@ -2010,6 +2047,68 @@ var NodeSqliteDriver = class _NodeSqliteDriver {
2010
2047
  this.db.close();
2011
2048
  }
2012
2049
  };
2050
+ var BetterSqliteDriver = class _BetterSqliteDriver {
2051
+ // biome-ignore lint/suspicious/noExplicitAny: the peer dep's types are optional here.
2052
+ db;
2053
+ /** Prepared-statement cache keyed by SQL text — see {@link NodeSqliteDriver}. */
2054
+ // biome-ignore lint/suspicious/noExplicitAny: see above.
2055
+ statements = /* @__PURE__ */ new Map();
2056
+ // biome-ignore lint/suspicious/noExplicitAny: accept an already-open Database handle.
2057
+ constructor(database) {
2058
+ this.db = database;
2059
+ }
2060
+ /**
2061
+ * Open a `better-sqlite3` database at the given path (or `":memory:"`).
2062
+ *
2063
+ * @param path The database file, or `":memory:"`.
2064
+ * @param options Passed straight to `new Database()` (`readonly`, `timeout`, …).
2065
+ * @returns A driver over the open handle.
2066
+ * @throws If `better-sqlite3` is not installed — it is an optional peer
2067
+ * dependency, so the error names the package to install.
2068
+ */
2069
+ static open(path, options) {
2070
+ let Database;
2071
+ try {
2072
+ const mod = nodeRequire("better-sqlite3");
2073
+ Database = mod.default ?? mod;
2074
+ } catch (cause) {
2075
+ throw new Error(
2076
+ 'The "better-sqlite3" driver requires the better-sqlite3 package: npm install better-sqlite3',
2077
+ { cause }
2078
+ );
2079
+ }
2080
+ return new _BetterSqliteDriver(
2081
+ options ? new Database(path, { ...options }) : new Database(path)
2082
+ );
2083
+ }
2084
+ /** Return the cached prepared statement for `sql`, preparing it on first use. */
2085
+ // biome-ignore lint/suspicious/noExplicitAny: statement type is optional here.
2086
+ prepare(sql2) {
2087
+ const cached = this.statements.get(sql2);
2088
+ if (cached) return cached;
2089
+ const stmt = this.db.prepare(sql2);
2090
+ this.statements.set(sql2, stmt);
2091
+ return stmt;
2092
+ }
2093
+ execute(sql2, params) {
2094
+ const stmt = this.prepare(sql2);
2095
+ const bound = params.map(encodeSqliteParam);
2096
+ if (stmt.reader) {
2097
+ return { rows: stmt.all(...bound), changes: 0 };
2098
+ }
2099
+ const info = stmt.run(...bound);
2100
+ return { rows: [], changes: Number(info.changes ?? 0) };
2101
+ }
2102
+ *iterate(sql2, params) {
2103
+ const stmt = this.prepare(sql2);
2104
+ const bound = params.map(encodeSqliteParam);
2105
+ yield* stmt.iterate(...bound);
2106
+ }
2107
+ close() {
2108
+ this.statements.clear();
2109
+ this.db.close();
2110
+ }
2111
+ };
2013
2112
  function returnsRows(sql2) {
2014
2113
  return /^\s*(select|pragma)/i.test(sql2) || /\breturning\b/i.test(sql2);
2015
2114
  }
@@ -2459,6 +2558,13 @@ var AsyncSession = class _AsyncSession {
2459
2558
  await this.close();
2460
2559
  }
2461
2560
  };
2561
+ function emitNotice(logger, notice) {
2562
+ if (!logger) return;
2563
+ try {
2564
+ logger(notice);
2565
+ } catch {
2566
+ }
2567
+ }
2462
2568
  var SyncEngine = class {
2463
2569
  constructor(driver, logger) {
2464
2570
  this.driver = driver;
@@ -2526,8 +2632,43 @@ function asAsync(driver) {
2526
2632
  } : {}
2527
2633
  };
2528
2634
  }
2529
- function openSqliteDriver(path, _options) {
2530
- return NodeSqliteDriver.open(path);
2635
+ var SQLITE_DRIVER_ALIASES = {
2636
+ "node:sqlite": "node:sqlite",
2637
+ "node-sqlite": "node:sqlite",
2638
+ node: "node:sqlite",
2639
+ "better-sqlite3": "better-sqlite3",
2640
+ better_sqlite3: "better-sqlite3",
2641
+ bettersqlite3: "better-sqlite3"
2642
+ };
2643
+ var SERVER_DRIVER_ALIASES = {
2644
+ postgresql: ["postgres", "postgres.js", "postgresjs", "pg"],
2645
+ mysql: ["mysql", "mysql2"]
2646
+ };
2647
+ function checkServerDriver(dialect, driver) {
2648
+ if (!driver) return;
2649
+ const accepted = SERVER_DRIVER_ALIASES[dialect] ?? [];
2650
+ if (accepted.includes(driver.toLowerCase())) return;
2651
+ throw new Error(
2652
+ `Unknown ${dialect} driver ${JSON.stringify(driver)}; tempest-db-js runs ${dialect} on ${JSON.stringify(accepted[0])}.`
2653
+ );
2654
+ }
2655
+ function resolveSqliteDriver(parsed, options) {
2656
+ const explicit = options?.driver;
2657
+ if (explicit) {
2658
+ const resolved = SQLITE_DRIVER_ALIASES[explicit.toLowerCase()];
2659
+ if (!resolved) {
2660
+ throw new Error(
2661
+ `Unknown SQLite driver ${JSON.stringify(explicit)}; supported: "node:sqlite" (built-in, default) and "better-sqlite3".`
2662
+ );
2663
+ }
2664
+ return resolved;
2665
+ }
2666
+ const fromUrl = parsed.driver ? SQLITE_DRIVER_ALIASES[parsed.driver.toLowerCase()] : void 0;
2667
+ return fromUrl ?? "node:sqlite";
2668
+ }
2669
+ function openSqliteDriver(parsed, options) {
2670
+ const path = parsed.database ?? ":memory:";
2671
+ return resolveSqliteDriver(parsed, options) === "better-sqlite3" ? BetterSqliteDriver.open(path, options?.driverOptions) : NodeSqliteDriver.open(path, options?.driverOptions);
2531
2672
  }
2532
2673
  function createSyncEngine(url, options) {
2533
2674
  const parsed = parseDatabaseUrl(url);
@@ -2536,29 +2677,28 @@ function createSyncEngine(url, options) {
2536
2677
  `createSyncEngine supports only SQLite; ${parsed.dialect} is async-only \u2014 use createEngine.`
2537
2678
  );
2538
2679
  }
2539
- return new SyncEngine(
2540
- openSqliteDriver(parsed.database ?? ":memory:"),
2541
- options?.onQuery
2542
- );
2680
+ return new SyncEngine(openSqliteDriver(parsed, options), options?.onQuery);
2543
2681
  }
2544
2682
  function createEngine(url, options) {
2545
2683
  const parsed = parseDatabaseUrl(url);
2546
2684
  if (parsed.dialect === "sqlite") {
2547
2685
  return new AsyncEngine(
2548
- asAsync(openSqliteDriver(parsed.database ?? ":memory:")),
2686
+ asAsync(openSqliteDriver(parsed, options)),
2549
2687
  "sqlite",
2550
2688
  options?.onQuery
2551
2689
  );
2552
2690
  }
2553
2691
  if (parsed.dialect === "mysql") {
2692
+ checkServerDriver("mysql", options?.driver);
2554
2693
  return new AsyncEngine(
2555
- createMysqlDriver(parsed.raw, options?.pool),
2694
+ createMysqlDriver(parsed.raw, options),
2556
2695
  "mysql",
2557
2696
  options?.onQuery
2558
2697
  );
2559
2698
  }
2699
+ checkServerDriver("postgresql", options?.driver);
2560
2700
  return new AsyncEngine(
2561
- createPostgresDriver(parsed.raw, options?.pool),
2701
+ createPostgresDriver(parsed.raw, options),
2562
2702
  "postgresql",
2563
2703
  options?.onQuery
2564
2704
  );
@@ -2578,7 +2718,8 @@ function toMysqlResult(rows) {
2578
2718
  const header = rows;
2579
2719
  return { rows: [], changes: header.affectedRows ?? 0 };
2580
2720
  }
2581
- function createMysqlDriver(url, pool) {
2721
+ function createMysqlDriver(url, options) {
2722
+ const pool = options?.pool;
2582
2723
  let poolHandle;
2583
2724
  const ensure = async () => {
2584
2725
  if (poolHandle) return;
@@ -2591,6 +2732,7 @@ function createMysqlDriver(url, pool) {
2591
2732
  if (pool?.size !== void 0) opts.connectionLimit = pool.size;
2592
2733
  if (pool?.idleTimeoutMs !== void 0) opts.idleTimeout = pool.idleTimeoutMs;
2593
2734
  if (pool?.connectTimeoutMs !== void 0) opts.connectTimeout = pool.connectTimeoutMs;
2735
+ Object.assign(opts, options?.driverOptions ?? {});
2594
2736
  poolHandle = mod.createPool(opts);
2595
2737
  };
2596
2738
  const runOn = async (queryable, sql2, params) => {
@@ -2624,7 +2766,8 @@ function toPostgresResult(rows) {
2624
2766
  const arr = rows;
2625
2767
  return { rows: Array.from(arr), changes: arr.count ?? arr.length };
2626
2768
  }
2627
- function createPostgresDriver(url, pool) {
2769
+ function createPostgresDriver(url, options) {
2770
+ const pool = options?.pool;
2628
2771
  let client;
2629
2772
  const ensure = async () => {
2630
2773
  if (client) return;
@@ -2640,6 +2783,8 @@ function createPostgresDriver(url, pool) {
2640
2783
  if (pool?.connectTimeoutMs !== void 0) {
2641
2784
  opts.connect_timeout = Math.ceil(pool.connectTimeoutMs / 1e3);
2642
2785
  }
2786
+ opts.onnotice = (notice) => emitNotice(options?.onNotice, notice);
2787
+ Object.assign(opts, options?.driverOptions ?? {});
2643
2788
  client = (mod.default ?? mod)(url, opts);
2644
2789
  };
2645
2790
  return {
@@ -3032,6 +3177,7 @@ exports.AsyncResult = AsyncResult;
3032
3177
  exports.AsyncSession = AsyncSession;
3033
3178
  exports.BaseDialect = BaseDialect;
3034
3179
  exports.BaseRepository = BaseRepository;
3180
+ exports.BetterSqliteDriver = BetterSqliteDriver;
3035
3181
  exports.Column = Column;
3036
3182
  exports.DeleteBuilder = DeleteBuilder;
3037
3183
  exports.Expression = Expression;