tempest-db-js 0.1.0 → 0.2.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/README.md +70 -2
- package/dist/bin.cjs +1132 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/bin.d.cts +27 -0
- package/dist/bin.d.ts +27 -0
- package/dist/bin.js +111 -0
- package/dist/bin.js.map +1 -0
- package/dist/{chunk-F36ZSQAN.js → chunk-AGDD7K3F.js} +461 -47
- package/dist/chunk-AGDD7K3F.js.map +1 -0
- package/dist/chunk-QMW4NKMH.js +1060 -0
- package/dist/chunk-QMW4NKMH.js.map +1 -0
- package/dist/index.cjs +467 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +293 -10
- package/dist/index.d.ts +293 -10
- package/dist/index.js +1 -1
- package/dist/migrations/index.cjs +143 -24
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +75 -1
- package/dist/migrations/index.d.ts +75 -1
- package/dist/migrations/index.js +2 -923
- package/dist/migrations/index.js.map +1 -1
- package/package.json +13 -5
- package/dist/chunk-F36ZSQAN.js.map +0 -1
|
@@ -42,6 +42,29 @@ var OPERATORS = [
|
|
|
42
42
|
"between",
|
|
43
43
|
"isNull"
|
|
44
44
|
];
|
|
45
|
+
var Agg = class {
|
|
46
|
+
constructor(fn, column2) {
|
|
47
|
+
this.fn = fn;
|
|
48
|
+
this.column = column2;
|
|
49
|
+
}
|
|
50
|
+
fn;
|
|
51
|
+
column;
|
|
52
|
+
};
|
|
53
|
+
function count() {
|
|
54
|
+
return new Agg("count", "*");
|
|
55
|
+
}
|
|
56
|
+
function sum(column2) {
|
|
57
|
+
return new Agg("sum", column2);
|
|
58
|
+
}
|
|
59
|
+
function avg(column2) {
|
|
60
|
+
return new Agg("avg", column2);
|
|
61
|
+
}
|
|
62
|
+
function min(column2) {
|
|
63
|
+
return new Agg("min", column2);
|
|
64
|
+
}
|
|
65
|
+
function max(column2) {
|
|
66
|
+
return new Agg("max", column2);
|
|
67
|
+
}
|
|
45
68
|
var SelectBuilder = class _SelectBuilder {
|
|
46
69
|
constructor(node, source) {
|
|
47
70
|
this.node = node;
|
|
@@ -56,6 +79,37 @@ var SelectBuilder = class _SelectBuilder {
|
|
|
56
79
|
where(input) {
|
|
57
80
|
return this.with({ where: toCondNode(input) });
|
|
58
81
|
}
|
|
82
|
+
/** Emit `SELECT DISTINCT` — drop duplicate rows. */
|
|
83
|
+
distinct() {
|
|
84
|
+
return this.with({ distinct: true });
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Group by columns and compute aggregates. The result row is the grouped
|
|
88
|
+
* columns (typed from the model) plus one field per aggregate alias.
|
|
89
|
+
*
|
|
90
|
+
* @param groupBy The columns to group by (checked against the model). Pass `[]`
|
|
91
|
+
* for a whole-table aggregate.
|
|
92
|
+
* @param spec A map of result alias → aggregate expression ({@link count},
|
|
93
|
+
* {@link sum}, {@link avg}, {@link min}, {@link max}).
|
|
94
|
+
* @returns A builder whose row is `Pick<Full, K> & { [alias]: aggResult }`.
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* select(Order).aggregate(["status"], { n: count(), total: sum("amount") });
|
|
99
|
+
* // rows: { status: string; n: number; total: number | null }[]
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
aggregate(groupBy, spec) {
|
|
103
|
+
const aggregates = Object.entries(spec).map(([alias, agg]) => ({
|
|
104
|
+
fn: agg.fn,
|
|
105
|
+
column: agg.column,
|
|
106
|
+
alias
|
|
107
|
+
}));
|
|
108
|
+
return new _SelectBuilder(
|
|
109
|
+
{ ...this.node, aggregates, groupBy },
|
|
110
|
+
this.source
|
|
111
|
+
);
|
|
112
|
+
}
|
|
59
113
|
/** Order by a column of `Full`. */
|
|
60
114
|
orderBy(column2, direction = "asc") {
|
|
61
115
|
return this.with({
|
|
@@ -77,6 +131,9 @@ function select(model, columns) {
|
|
|
77
131
|
kind: "select",
|
|
78
132
|
table: model.tablename,
|
|
79
133
|
columns: columns ?? "*",
|
|
134
|
+
distinct: false,
|
|
135
|
+
aggregates: [],
|
|
136
|
+
groupBy: [],
|
|
80
137
|
where: void 0,
|
|
81
138
|
orderBy: [],
|
|
82
139
|
limit: void 0,
|
|
@@ -102,6 +159,25 @@ var InsertBuilder = class _InsertBuilder {
|
|
|
102
159
|
const list = Array.isArray(rows) ? rows : [rows];
|
|
103
160
|
return this.with({ values: list });
|
|
104
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* On a unique/PK conflict on `target`, do nothing (skip the row).
|
|
164
|
+
*
|
|
165
|
+
* @param target The conflicting column(s) — a unique or primary key.
|
|
166
|
+
*/
|
|
167
|
+
onConflictDoNothing(target) {
|
|
168
|
+
return this.with({ onConflict: { target, update: "nothing" } });
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* On a unique/PK conflict on `target`, overwrite the given columns (upsert).
|
|
172
|
+
*
|
|
173
|
+
* @param target The conflicting column(s) — a unique or primary key.
|
|
174
|
+
* @param set The columns to update with new values.
|
|
175
|
+
*/
|
|
176
|
+
onConflictDoUpdate(target, set) {
|
|
177
|
+
return this.with({
|
|
178
|
+
onConflict: { target, update: set }
|
|
179
|
+
});
|
|
180
|
+
}
|
|
105
181
|
returning(columns) {
|
|
106
182
|
return this.with({ returning: columns ?? "*" });
|
|
107
183
|
}
|
|
@@ -405,12 +481,49 @@ function fromDict(model, data) {
|
|
|
405
481
|
function parse(model, json) {
|
|
406
482
|
return fromDict(model, JSON.parse(json));
|
|
407
483
|
}
|
|
484
|
+
function decoderForKind(kind) {
|
|
485
|
+
switch (kind) {
|
|
486
|
+
case "bigint":
|
|
487
|
+
return (v) => v == null ? null : typeof v === "bigint" ? v : BigInt(v);
|
|
488
|
+
case "date":
|
|
489
|
+
case "datetime":
|
|
490
|
+
case "timestamp":
|
|
491
|
+
return (v) => v == null ? null : v instanceof Date ? v : new Date(v);
|
|
492
|
+
case "blob":
|
|
493
|
+
return (v) => v == null ? null : v instanceof Uint8Array ? v : fromBase64(v);
|
|
494
|
+
case "json":
|
|
495
|
+
return (v) => v == null ? null : typeof v === "string" ? JSON.parse(v) : v;
|
|
496
|
+
case "numeric":
|
|
497
|
+
return (v) => v == null ? null : typeof v === "string" ? v : String(v);
|
|
498
|
+
case "boolean":
|
|
499
|
+
return (v) => v == null ? null : typeof v === "boolean" ? v : v === 1 || v === "true";
|
|
500
|
+
case "smallint":
|
|
501
|
+
case "integer":
|
|
502
|
+
case "real":
|
|
503
|
+
case "double":
|
|
504
|
+
return (v) => v == null ? null : typeof v === "number" ? v : Number(v);
|
|
505
|
+
default:
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
var decoderCache = /* @__PURE__ */ new WeakMap();
|
|
510
|
+
function decodersFor(model) {
|
|
511
|
+
const cached = decoderCache.get(model);
|
|
512
|
+
if (cached) return cached;
|
|
513
|
+
const map = /* @__PURE__ */ new Map();
|
|
514
|
+
for (const [name, col] of Object.entries(columnsOf(model))) {
|
|
515
|
+
const decoder = decoderForKind(col.type.kind);
|
|
516
|
+
if (decoder) map.set(name, decoder);
|
|
517
|
+
}
|
|
518
|
+
decoderCache.set(model, map);
|
|
519
|
+
return map;
|
|
520
|
+
}
|
|
408
521
|
function coerceRow(model, raw) {
|
|
409
|
-
const
|
|
522
|
+
const decoders = decodersFor(model);
|
|
410
523
|
const out = {};
|
|
411
|
-
for (const
|
|
412
|
-
const
|
|
413
|
-
out[name] =
|
|
524
|
+
for (const name of Object.keys(raw)) {
|
|
525
|
+
const decode2 = decoders.get(name);
|
|
526
|
+
out[name] = decode2 ? decode2(raw[name]) : raw[name];
|
|
414
527
|
}
|
|
415
528
|
return out;
|
|
416
529
|
}
|
|
@@ -435,10 +548,29 @@ var Params = class {
|
|
|
435
548
|
return this.placeholder(this.values.length);
|
|
436
549
|
}
|
|
437
550
|
};
|
|
438
|
-
var BaseDialect = class {
|
|
439
|
-
/**
|
|
551
|
+
var BaseDialect = class _BaseDialect {
|
|
552
|
+
/**
|
|
553
|
+
* INSERT SQL templates keyed by structure (dialect|table|columns|rowCount|
|
|
554
|
+
* returning). Shared across dialect instances — the key namespaces by dialect
|
|
555
|
+
* name, and the placeholder text is dialect-specific but structure-determined.
|
|
556
|
+
*/
|
|
557
|
+
static insertTemplates = /* @__PURE__ */ new Map();
|
|
558
|
+
/** Quoted-identifier cache (see {@link quoteId}). Shared across dialects. */
|
|
559
|
+
static quotedIds = /* @__PURE__ */ new Map();
|
|
560
|
+
/**
|
|
561
|
+
* Quote an identifier (column/table) for the active dialect.
|
|
562
|
+
*
|
|
563
|
+
* Memoized: identifiers form a small, stable set (column/table names), but this
|
|
564
|
+
* runs for every identifier on every compile. Caching the quoted form removes a
|
|
565
|
+
* regex-replace + string allocation from the hot path. The standard double-quote
|
|
566
|
+
* form is identical across both dialects, so one shared cache is correct.
|
|
567
|
+
*/
|
|
440
568
|
quoteId(name) {
|
|
441
|
-
|
|
569
|
+
const cached = _BaseDialect.quotedIds.get(name);
|
|
570
|
+
if (cached !== void 0) return cached;
|
|
571
|
+
const quoted = `"${name.replace(/"/g, '""')}"`;
|
|
572
|
+
_BaseDialect.quotedIds.set(name, quoted);
|
|
573
|
+
return quoted;
|
|
442
574
|
}
|
|
443
575
|
/** Compile any node to `{ sql, params }`. */
|
|
444
576
|
compile(node) {
|
|
@@ -471,10 +603,23 @@ var BaseDialect = class {
|
|
|
471
603
|
}
|
|
472
604
|
// ---- statements -------------------------------------------------------
|
|
473
605
|
compileSelect(node, params) {
|
|
474
|
-
|
|
475
|
-
|
|
606
|
+
let cols;
|
|
607
|
+
if (node.aggregates.length > 0) {
|
|
608
|
+
const groupSel = node.groupBy.map((c) => this.quoteId(c));
|
|
609
|
+
const aggSel = node.aggregates.map((a) => {
|
|
610
|
+
const inner = a.column === "*" ? "*" : this.quoteId(a.column);
|
|
611
|
+
return `${a.fn.toUpperCase()}(${inner}) AS ${this.quoteId(a.alias)}`;
|
|
612
|
+
});
|
|
613
|
+
cols = [...groupSel, ...aggSel].join(", ");
|
|
614
|
+
} else {
|
|
615
|
+
cols = node.columns === "*" ? "*" : node.columns.map((c) => this.quoteId(c)).join(", ");
|
|
616
|
+
}
|
|
617
|
+
let sql2 = `SELECT ${node.distinct ? "DISTINCT " : ""}${cols} FROM ${this.quoteId(node.table)}`;
|
|
476
618
|
const where = this.compileCondition(node.where, params, (k) => this.quoteId(k));
|
|
477
619
|
if (where) sql2 += ` WHERE ${where}`;
|
|
620
|
+
if (node.groupBy.length > 0) {
|
|
621
|
+
sql2 += ` GROUP BY ${node.groupBy.map((c) => this.quoteId(c)).join(", ")}`;
|
|
622
|
+
}
|
|
478
623
|
if (node.orderBy.length > 0) {
|
|
479
624
|
const terms = node.orderBy.map(
|
|
480
625
|
(t) => `${this.quoteId(t.column)} ${t.direction === "desc" ? "DESC" : "ASC"}`
|
|
@@ -487,12 +632,44 @@ var BaseDialect = class {
|
|
|
487
632
|
}
|
|
488
633
|
compileInsert(node, params) {
|
|
489
634
|
const columns = node.values.length > 0 ? Object.keys(node.values[0]) : [];
|
|
635
|
+
for (const row of node.values) {
|
|
636
|
+
for (const c of columns) params.bind(row[c] ?? null);
|
|
637
|
+
}
|
|
638
|
+
const conflictCols = node.onConflict && node.onConflict.update !== "nothing" ? Object.keys(node.onConflict.update) : [];
|
|
639
|
+
for (const c of conflictCols) {
|
|
640
|
+
params.bind((node.onConflict?.update)[c]);
|
|
641
|
+
}
|
|
642
|
+
return this.insertTemplate(node, columns, conflictCols);
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* The INSERT SQL template for a given structure, cached across calls.
|
|
646
|
+
*
|
|
647
|
+
* The text depends only on (dialect, table, columns, row count, returning,
|
|
648
|
+
* conflict shape) — never on the bound values — and placeholder positions are
|
|
649
|
+
* deterministic from the counts (a fresh statement always starts binding at 1).
|
|
650
|
+
* So a per-row insert loop compiles the string once and reuses it every row.
|
|
651
|
+
*/
|
|
652
|
+
insertTemplate(node, columns, conflictCols) {
|
|
653
|
+
const returningKey = node.returning === null ? "" : node.returning === "*" ? "*" : node.returning.join(",");
|
|
654
|
+
const conflictKey = node.onConflict ? `${node.onConflict.target.join(",")}>${node.onConflict.update === "nothing" ? "nothing" : conflictCols.join(",")}` : "";
|
|
655
|
+
const key = `${this.name}|${node.table}|${columns.join(",")}|${node.values.length}|${returningKey}|${conflictKey}`;
|
|
656
|
+
const cached = _BaseDialect.insertTemplates.get(key);
|
|
657
|
+
if (cached !== void 0) return cached;
|
|
490
658
|
const colSql = columns.map((c) => this.quoteId(c)).join(", ");
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
).join(", ");
|
|
659
|
+
let position = 0;
|
|
660
|
+
const rowsSql = node.values.map(() => `(${columns.map(() => this.placeholder(++position)).join(", ")})`).join(", ");
|
|
494
661
|
let sql2 = `INSERT INTO ${this.quoteId(node.table)} (${colSql}) VALUES ${rowsSql}`;
|
|
662
|
+
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
|
+
}
|
|
670
|
+
}
|
|
495
671
|
sql2 += this.compileReturning(node.returning);
|
|
672
|
+
_BaseDialect.insertTemplates.set(key, sql2);
|
|
496
673
|
return sql2;
|
|
497
674
|
}
|
|
498
675
|
compileUpdate(node, params) {
|
|
@@ -814,6 +991,101 @@ var BaseRepository = class {
|
|
|
814
991
|
}
|
|
815
992
|
};
|
|
816
993
|
|
|
994
|
+
// src/active-record.ts
|
|
995
|
+
function primaryKeyOf2(model) {
|
|
996
|
+
for (const [name, col] of Object.entries(columnsOf(model))) {
|
|
997
|
+
if (col.flags.primaryKey) return name;
|
|
998
|
+
}
|
|
999
|
+
throw new Error(`${model.tablename} has no primary key`);
|
|
1000
|
+
}
|
|
1001
|
+
var ActiveRecord = class {
|
|
1002
|
+
constructor(model, session, data) {
|
|
1003
|
+
this.model = model;
|
|
1004
|
+
this.session = session;
|
|
1005
|
+
this.data = data;
|
|
1006
|
+
this.pk = primaryKeyOf2(model);
|
|
1007
|
+
}
|
|
1008
|
+
model;
|
|
1009
|
+
session;
|
|
1010
|
+
data;
|
|
1011
|
+
pk;
|
|
1012
|
+
/** The primary-key value of the wrapped row. */
|
|
1013
|
+
pkValue() {
|
|
1014
|
+
return this.data[this.pk];
|
|
1015
|
+
}
|
|
1016
|
+
pkFilter() {
|
|
1017
|
+
return { [this.pk]: this.pkValue() };
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Persist the current `data` — insert if new, otherwise overwrite the existing
|
|
1021
|
+
* row (upsert on the primary key). Refreshes `data` from the returned row.
|
|
1022
|
+
*
|
|
1023
|
+
* @returns This wrapper, for chaining.
|
|
1024
|
+
*/
|
|
1025
|
+
async save() {
|
|
1026
|
+
const cols = columnsOf(this.model);
|
|
1027
|
+
const rowData = this.data;
|
|
1028
|
+
const setPatch = {};
|
|
1029
|
+
for (const c of Object.keys(rowData)) {
|
|
1030
|
+
if (c !== this.pk && c in cols) setPatch[c] = rowData[c];
|
|
1031
|
+
}
|
|
1032
|
+
const saved = await this.session.execute(
|
|
1033
|
+
insert(this.model).values(this.data).onConflictDoUpdate(
|
|
1034
|
+
[this.pk],
|
|
1035
|
+
setPatch
|
|
1036
|
+
).returning()
|
|
1037
|
+
).one();
|
|
1038
|
+
this.data = saved;
|
|
1039
|
+
return this;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Update the given columns for this row and merge them into `data`.
|
|
1043
|
+
*
|
|
1044
|
+
* @param patch The columns to change.
|
|
1045
|
+
* @returns This wrapper, for chaining.
|
|
1046
|
+
*/
|
|
1047
|
+
async update(patch) {
|
|
1048
|
+
await this.session.execute(update(this.model).set(patch).where(this.pkFilter()));
|
|
1049
|
+
this.data = { ...this.data, ...patch };
|
|
1050
|
+
return this;
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Delete this row.
|
|
1054
|
+
*
|
|
1055
|
+
* @returns The number of rows affected (0 or 1).
|
|
1056
|
+
*/
|
|
1057
|
+
async delete() {
|
|
1058
|
+
return this.session.execute(del(this.model).where(this.pkFilter())).rowsAffected();
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Re-fetch this row by primary key and refresh `data`.
|
|
1062
|
+
*
|
|
1063
|
+
* @returns This wrapper, for chaining.
|
|
1064
|
+
* @throws When the row no longer exists.
|
|
1065
|
+
*/
|
|
1066
|
+
async reload() {
|
|
1067
|
+
const fresh = await this.session.execute(select(this.model).where(this.pkFilter())).first();
|
|
1068
|
+
if (fresh === null) {
|
|
1069
|
+
throw new Error(
|
|
1070
|
+
`${this.model.tablename} row ${JSON.stringify(this.pkValue())} not found on reload`
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
this.data = fresh;
|
|
1074
|
+
return this;
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
function activeRecord(model, session) {
|
|
1078
|
+
const pk = primaryKeyOf2(model);
|
|
1079
|
+
return {
|
|
1080
|
+
wrap: (row) => new ActiveRecord(model, session, row),
|
|
1081
|
+
create: (data) => new ActiveRecord(model, session, data),
|
|
1082
|
+
async get(id) {
|
|
1083
|
+
const row = await session.execute(select(model).where({ [pk]: id })).first();
|
|
1084
|
+
return row === null ? null : new ActiveRecord(model, session, row);
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
817
1089
|
// src/relations.ts
|
|
818
1090
|
function hasMany(target, keys) {
|
|
819
1091
|
return {
|
|
@@ -876,6 +1148,14 @@ function encodeSqliteParam(value) {
|
|
|
876
1148
|
var NodeSqliteDriver = class _NodeSqliteDriver {
|
|
877
1149
|
// biome-ignore lint/suspicious/noExplicitAny: node:sqlite DatabaseSync has no shipped types here.
|
|
878
1150
|
db;
|
|
1151
|
+
/**
|
|
1152
|
+
* Prepared-statement cache keyed by SQL text. tempest-db-js always
|
|
1153
|
+
* parameterizes, so a query shape maps to one stable SQL string — reusing the
|
|
1154
|
+
* compiled statement avoids re-`prepare()` on every call (the dominant cost of
|
|
1155
|
+
* per-row inserts and point lookups).
|
|
1156
|
+
*/
|
|
1157
|
+
// biome-ignore lint/suspicious/noExplicitAny: node:sqlite StatementSync has no shipped types here.
|
|
1158
|
+
statements = /* @__PURE__ */ new Map();
|
|
879
1159
|
// biome-ignore lint/suspicious/noExplicitAny: accept an already-open DatabaseSync handle.
|
|
880
1160
|
constructor(database) {
|
|
881
1161
|
this.db = database;
|
|
@@ -885,8 +1165,17 @@ var NodeSqliteDriver = class _NodeSqliteDriver {
|
|
|
885
1165
|
const { DatabaseSync } = nodeRequire("node:sqlite");
|
|
886
1166
|
return new _NodeSqliteDriver(new DatabaseSync(path));
|
|
887
1167
|
}
|
|
888
|
-
|
|
1168
|
+
/** Return the cached prepared statement for `sql`, preparing it on first use. */
|
|
1169
|
+
// biome-ignore lint/suspicious/noExplicitAny: statement type is unavailable here.
|
|
1170
|
+
prepare(sql2) {
|
|
1171
|
+
const cached = this.statements.get(sql2);
|
|
1172
|
+
if (cached) return cached;
|
|
889
1173
|
const stmt = this.db.prepare(sql2);
|
|
1174
|
+
this.statements.set(sql2, stmt);
|
|
1175
|
+
return stmt;
|
|
1176
|
+
}
|
|
1177
|
+
execute(sql2, params) {
|
|
1178
|
+
const stmt = this.prepare(sql2);
|
|
890
1179
|
const bound = params.map(encodeSqliteParam);
|
|
891
1180
|
if (returnsRows(sql2)) {
|
|
892
1181
|
return { rows: stmt.all(...bound), changes: 0 };
|
|
@@ -895,11 +1184,12 @@ var NodeSqliteDriver = class _NodeSqliteDriver {
|
|
|
895
1184
|
return { rows: [], changes: Number(info.changes ?? 0) };
|
|
896
1185
|
}
|
|
897
1186
|
*iterate(sql2, params) {
|
|
898
|
-
const stmt = this.
|
|
1187
|
+
const stmt = this.prepare(sql2);
|
|
899
1188
|
const bound = params.map(encodeSqliteParam);
|
|
900
1189
|
yield* stmt.iterate(...bound);
|
|
901
1190
|
}
|
|
902
1191
|
close() {
|
|
1192
|
+
this.statements.clear();
|
|
903
1193
|
this.db.close();
|
|
904
1194
|
}
|
|
905
1195
|
};
|
|
@@ -941,6 +1231,36 @@ var NoResultError = class extends Error {
|
|
|
941
1231
|
this.name = "NoResultError";
|
|
942
1232
|
}
|
|
943
1233
|
};
|
|
1234
|
+
function previewParam(value) {
|
|
1235
|
+
if (value === null || value === void 0) return "null";
|
|
1236
|
+
if (value instanceof Uint8Array) return `<${value.length} bytes>`;
|
|
1237
|
+
const text = typeof value === "string" ? value : String(value);
|
|
1238
|
+
return text.length > 64 ? `${text.slice(0, 61)}...` : text;
|
|
1239
|
+
}
|
|
1240
|
+
var QueryExecutionError = class extends Error {
|
|
1241
|
+
constructor(cause, sql2, params) {
|
|
1242
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
1243
|
+
super(
|
|
1244
|
+
`Query failed: ${reason}
|
|
1245
|
+
SQL: ${sql2}
|
|
1246
|
+
params: [${params.map(previewParam).join(", ")}]`
|
|
1247
|
+
);
|
|
1248
|
+
this.cause = cause;
|
|
1249
|
+
this.sql = sql2;
|
|
1250
|
+
this.params = params;
|
|
1251
|
+
this.name = "QueryExecutionError";
|
|
1252
|
+
}
|
|
1253
|
+
cause;
|
|
1254
|
+
sql;
|
|
1255
|
+
params;
|
|
1256
|
+
};
|
|
1257
|
+
function emitLog(logger, sql2, params) {
|
|
1258
|
+
if (!logger) return;
|
|
1259
|
+
try {
|
|
1260
|
+
logger({ sql: sql2, params });
|
|
1261
|
+
} catch {
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
944
1264
|
function firstScalar(row) {
|
|
945
1265
|
if (!row) return null;
|
|
946
1266
|
const keys = Object.keys(row);
|
|
@@ -1010,29 +1330,40 @@ var AsyncResult = class {
|
|
|
1010
1330
|
};
|
|
1011
1331
|
var savepointCounter = 0;
|
|
1012
1332
|
var SyncSession = class {
|
|
1013
|
-
constructor(driver, dialect) {
|
|
1333
|
+
constructor(driver, dialect, logger) {
|
|
1014
1334
|
this.driver = driver;
|
|
1015
1335
|
this.dialect = dialect;
|
|
1336
|
+
this.logger = logger;
|
|
1016
1337
|
}
|
|
1017
1338
|
driver;
|
|
1018
1339
|
dialect;
|
|
1340
|
+
logger;
|
|
1341
|
+
/** Log, run, and error-wrap one raw statement. */
|
|
1342
|
+
exec(sql2, params) {
|
|
1343
|
+
emitLog(this.logger, sql2, params);
|
|
1344
|
+
try {
|
|
1345
|
+
return this.driver.execute(sql2, params);
|
|
1346
|
+
} catch (error) {
|
|
1347
|
+
throw new QueryExecutionError(error, sql2, params);
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1019
1350
|
/** Compile, run, and coerce a builder into a result. */
|
|
1020
1351
|
execute(builder) {
|
|
1021
1352
|
const node = builder.node;
|
|
1022
1353
|
const { sql: sql2, params } = this.dialect.compile(node);
|
|
1023
|
-
const result = this.
|
|
1354
|
+
const result = this.exec(sql2, params);
|
|
1024
1355
|
const rows = mapRows(builder, result.rows);
|
|
1025
1356
|
return new SyncResult(rows, result.changes);
|
|
1026
1357
|
}
|
|
1027
1358
|
/** Run `fn` inside a transaction: commit on success, rollback on throw. */
|
|
1028
1359
|
transaction(fn) {
|
|
1029
|
-
this.
|
|
1360
|
+
this.exec("BEGIN", []);
|
|
1030
1361
|
try {
|
|
1031
1362
|
const out = fn(this);
|
|
1032
|
-
this.
|
|
1363
|
+
this.exec("COMMIT", []);
|
|
1033
1364
|
return out;
|
|
1034
1365
|
} catch (error) {
|
|
1035
|
-
this.
|
|
1366
|
+
this.exec("ROLLBACK", []);
|
|
1036
1367
|
throw error;
|
|
1037
1368
|
}
|
|
1038
1369
|
}
|
|
@@ -1040,13 +1371,13 @@ var SyncSession = class {
|
|
|
1040
1371
|
beginNested(fn) {
|
|
1041
1372
|
savepointCounter += 1;
|
|
1042
1373
|
const name = `qsp_${savepointCounter}`;
|
|
1043
|
-
this.
|
|
1374
|
+
this.exec(`SAVEPOINT ${name}`, []);
|
|
1044
1375
|
try {
|
|
1045
1376
|
const out = fn(this);
|
|
1046
|
-
this.
|
|
1377
|
+
this.exec(`RELEASE ${name}`, []);
|
|
1047
1378
|
return out;
|
|
1048
1379
|
} catch (error) {
|
|
1049
|
-
this.
|
|
1380
|
+
this.exec(`ROLLBACK TO ${name}`, []);
|
|
1050
1381
|
throw error;
|
|
1051
1382
|
}
|
|
1052
1383
|
}
|
|
@@ -1057,31 +1388,51 @@ var SyncSession = class {
|
|
|
1057
1388
|
*stream(builder) {
|
|
1058
1389
|
const node = builder.node;
|
|
1059
1390
|
const { sql: sql2, params } = this.dialect.compile(node);
|
|
1391
|
+
emitLog(this.logger, sql2, params);
|
|
1060
1392
|
if (this.driver.iterate) {
|
|
1061
|
-
|
|
1062
|
-
|
|
1393
|
+
try {
|
|
1394
|
+
for (const raw of this.driver.iterate(sql2, params)) {
|
|
1395
|
+
yield coerceOne(builder, raw);
|
|
1396
|
+
}
|
|
1397
|
+
} catch (error) {
|
|
1398
|
+
throw new QueryExecutionError(error, sql2, params);
|
|
1063
1399
|
}
|
|
1064
1400
|
return;
|
|
1065
1401
|
}
|
|
1066
|
-
for (const raw of this.
|
|
1402
|
+
for (const raw of this.exec(sql2, params).rows) {
|
|
1067
1403
|
yield coerceOne(builder, raw);
|
|
1068
1404
|
}
|
|
1069
1405
|
}
|
|
1070
1406
|
close() {
|
|
1071
1407
|
this.driver.close();
|
|
1072
1408
|
}
|
|
1409
|
+
/** `using session = ...` closes the driver when the scope exits. */
|
|
1410
|
+
[Symbol.dispose]() {
|
|
1411
|
+
this.close();
|
|
1412
|
+
}
|
|
1073
1413
|
};
|
|
1074
|
-
var AsyncSession = class {
|
|
1075
|
-
constructor(driver, dialect) {
|
|
1414
|
+
var AsyncSession = class _AsyncSession {
|
|
1415
|
+
constructor(driver, dialect, logger) {
|
|
1076
1416
|
this.driver = driver;
|
|
1077
1417
|
this.dialect = dialect;
|
|
1418
|
+
this.logger = logger;
|
|
1078
1419
|
}
|
|
1079
1420
|
driver;
|
|
1080
1421
|
dialect;
|
|
1422
|
+
logger;
|
|
1423
|
+
/** Log, run, and error-wrap one raw statement. */
|
|
1424
|
+
async exec(sql2, params) {
|
|
1425
|
+
emitLog(this.logger, sql2, params);
|
|
1426
|
+
try {
|
|
1427
|
+
return await this.driver.execute(sql2, params);
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
throw new QueryExecutionError(error, sql2, params);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1081
1432
|
execute(builder) {
|
|
1082
1433
|
const node = builder.node;
|
|
1083
1434
|
const { sql: sql2, params } = this.dialect.compile(node);
|
|
1084
|
-
const inner = this.
|
|
1435
|
+
const inner = this.exec(sql2, params).then((result) => {
|
|
1085
1436
|
const rows = mapRows(builder, result.rows);
|
|
1086
1437
|
return new SyncResult(rows, result.changes);
|
|
1087
1438
|
});
|
|
@@ -1091,40 +1442,66 @@ var AsyncSession = class {
|
|
|
1091
1442
|
async *stream(builder) {
|
|
1092
1443
|
const node = builder.node;
|
|
1093
1444
|
const { sql: sql2, params } = this.dialect.compile(node);
|
|
1445
|
+
emitLog(this.logger, sql2, params);
|
|
1094
1446
|
if (this.driver.iterate) {
|
|
1095
|
-
|
|
1096
|
-
|
|
1447
|
+
try {
|
|
1448
|
+
for await (const raw of this.driver.iterate(sql2, params)) {
|
|
1449
|
+
yield coerceOne(builder, raw);
|
|
1450
|
+
}
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
throw new QueryExecutionError(error, sql2, params);
|
|
1097
1453
|
}
|
|
1098
1454
|
return;
|
|
1099
1455
|
}
|
|
1100
|
-
const result = await this.
|
|
1456
|
+
const result = await this.exec(sql2, params);
|
|
1101
1457
|
for (const raw of result.rows) {
|
|
1102
1458
|
yield coerceOne(builder, raw);
|
|
1103
1459
|
}
|
|
1104
1460
|
}
|
|
1105
1461
|
async transaction(fn) {
|
|
1106
|
-
|
|
1462
|
+
if (this.driver.reserve) {
|
|
1463
|
+
const reserved = await this.driver.reserve();
|
|
1464
|
+
const scoped = new _AsyncSession(reserved, this.dialect, this.logger);
|
|
1465
|
+
try {
|
|
1466
|
+
await scoped.exec("BEGIN", []);
|
|
1467
|
+
const out = await fn(scoped);
|
|
1468
|
+
await scoped.exec("COMMIT", []);
|
|
1469
|
+
return out;
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
await scoped.exec("ROLLBACK", []);
|
|
1472
|
+
throw error;
|
|
1473
|
+
} finally {
|
|
1474
|
+
await reserved.release();
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
await this.exec("BEGIN", []);
|
|
1107
1478
|
try {
|
|
1108
1479
|
const out = await fn(this);
|
|
1109
|
-
await this.
|
|
1480
|
+
await this.exec("COMMIT", []);
|
|
1110
1481
|
return out;
|
|
1111
1482
|
} catch (error) {
|
|
1112
|
-
await this.
|
|
1483
|
+
await this.exec("ROLLBACK", []);
|
|
1113
1484
|
throw error;
|
|
1114
1485
|
}
|
|
1115
1486
|
}
|
|
1116
1487
|
async close() {
|
|
1117
1488
|
await this.driver.close();
|
|
1118
1489
|
}
|
|
1490
|
+
/** `await using session = ...` closes the driver when the scope exits. */
|
|
1491
|
+
async [Symbol.asyncDispose]() {
|
|
1492
|
+
await this.close();
|
|
1493
|
+
}
|
|
1119
1494
|
};
|
|
1120
1495
|
var SyncEngine = class {
|
|
1121
|
-
constructor(driver) {
|
|
1496
|
+
constructor(driver, logger) {
|
|
1122
1497
|
this.driver = driver;
|
|
1498
|
+
this.logger = logger;
|
|
1123
1499
|
}
|
|
1124
1500
|
driver;
|
|
1501
|
+
logger;
|
|
1125
1502
|
dialect = "sqlite";
|
|
1126
1503
|
session() {
|
|
1127
|
-
return new SyncSession(this.driver, getDialect("sqlite"));
|
|
1504
|
+
return new SyncSession(this.driver, getDialect("sqlite"), this.logger);
|
|
1128
1505
|
}
|
|
1129
1506
|
transaction(fn) {
|
|
1130
1507
|
return this.session().transaction(fn);
|
|
@@ -1132,16 +1509,22 @@ var SyncEngine = class {
|
|
|
1132
1509
|
close() {
|
|
1133
1510
|
this.driver.close();
|
|
1134
1511
|
}
|
|
1512
|
+
/** `using engine = createSyncEngine(...)` closes the pool when the scope exits. */
|
|
1513
|
+
[Symbol.dispose]() {
|
|
1514
|
+
this.close();
|
|
1515
|
+
}
|
|
1135
1516
|
};
|
|
1136
1517
|
var AsyncEngine = class {
|
|
1137
|
-
constructor(driver, dialect) {
|
|
1518
|
+
constructor(driver, dialect, logger) {
|
|
1138
1519
|
this.driver = driver;
|
|
1139
1520
|
this.dialect = dialect;
|
|
1521
|
+
this.logger = logger;
|
|
1140
1522
|
}
|
|
1141
1523
|
driver;
|
|
1142
1524
|
dialect;
|
|
1525
|
+
logger;
|
|
1143
1526
|
session() {
|
|
1144
|
-
return new AsyncSession(this.driver, getDialect(this.dialect));
|
|
1527
|
+
return new AsyncSession(this.driver, getDialect(this.dialect), this.logger);
|
|
1145
1528
|
}
|
|
1146
1529
|
transaction(fn) {
|
|
1147
1530
|
return this.session().transaction(fn);
|
|
@@ -1149,6 +1532,10 @@ var AsyncEngine = class {
|
|
|
1149
1532
|
async close() {
|
|
1150
1533
|
await this.driver.close();
|
|
1151
1534
|
}
|
|
1535
|
+
/** `await using engine = createEngine(...)` closes the pool when the scope exits. */
|
|
1536
|
+
async [Symbol.asyncDispose]() {
|
|
1537
|
+
await this.close();
|
|
1538
|
+
}
|
|
1152
1539
|
};
|
|
1153
1540
|
function asAsync(driver) {
|
|
1154
1541
|
const syncIterate = driver.iterate?.bind(driver);
|
|
@@ -1172,17 +1559,29 @@ function createSyncEngine(url, options) {
|
|
|
1172
1559
|
`createSyncEngine supports only SQLite; ${parsed.dialect} is async-only \u2014 use createEngine.`
|
|
1173
1560
|
);
|
|
1174
1561
|
}
|
|
1175
|
-
return new SyncEngine(
|
|
1562
|
+
return new SyncEngine(
|
|
1563
|
+
openSqliteDriver(parsed.database ?? ":memory:"),
|
|
1564
|
+
options?.onQuery
|
|
1565
|
+
);
|
|
1176
1566
|
}
|
|
1177
1567
|
function createEngine(url, options) {
|
|
1178
1568
|
const parsed = parseDatabaseUrl(url);
|
|
1179
1569
|
if (parsed.dialect === "sqlite") {
|
|
1180
1570
|
return new AsyncEngine(
|
|
1181
1571
|
asAsync(openSqliteDriver(parsed.database ?? ":memory:")),
|
|
1182
|
-
"sqlite"
|
|
1572
|
+
"sqlite",
|
|
1573
|
+
options?.onQuery
|
|
1183
1574
|
);
|
|
1184
1575
|
}
|
|
1185
|
-
return new AsyncEngine(
|
|
1576
|
+
return new AsyncEngine(
|
|
1577
|
+
createPostgresDriver(parsed.raw, options?.pool),
|
|
1578
|
+
"postgresql",
|
|
1579
|
+
options?.onQuery
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
function toPostgresResult(rows) {
|
|
1583
|
+
const arr = rows;
|
|
1584
|
+
return { rows: Array.from(arr), changes: arr.count ?? arr.length };
|
|
1186
1585
|
}
|
|
1187
1586
|
function createPostgresDriver(url, pool) {
|
|
1188
1587
|
let client;
|
|
@@ -1205,10 +1604,21 @@ function createPostgresDriver(url, pool) {
|
|
|
1205
1604
|
return {
|
|
1206
1605
|
async execute(sql2, params) {
|
|
1207
1606
|
await ensure();
|
|
1208
|
-
|
|
1607
|
+
return toPostgresResult(await client.unsafe(sql2, params));
|
|
1608
|
+
},
|
|
1609
|
+
async reserve() {
|
|
1610
|
+
await ensure();
|
|
1611
|
+
const conn = await client.reserve();
|
|
1209
1612
|
return {
|
|
1210
|
-
|
|
1211
|
-
|
|
1613
|
+
async execute(sql2, params) {
|
|
1614
|
+
return toPostgresResult(await conn.unsafe(sql2, params));
|
|
1615
|
+
},
|
|
1616
|
+
async release() {
|
|
1617
|
+
conn.release();
|
|
1618
|
+
},
|
|
1619
|
+
async close() {
|
|
1620
|
+
conn.release();
|
|
1621
|
+
}
|
|
1212
1622
|
};
|
|
1213
1623
|
},
|
|
1214
1624
|
async close() {
|
|
@@ -1344,7 +1754,10 @@ var column = {
|
|
|
1344
1754
|
var Model = class {
|
|
1345
1755
|
static tablename;
|
|
1346
1756
|
};
|
|
1757
|
+
var columnsCache = /* @__PURE__ */ new WeakMap();
|
|
1347
1758
|
function columnsOf(model) {
|
|
1759
|
+
const cached = columnsCache.get(model);
|
|
1760
|
+
if (cached) return cached;
|
|
1348
1761
|
const instance = new model();
|
|
1349
1762
|
const out = {};
|
|
1350
1763
|
for (const [key, value] of Object.entries(instance)) {
|
|
@@ -1352,9 +1765,10 @@ function columnsOf(model) {
|
|
|
1352
1765
|
out[key] = value;
|
|
1353
1766
|
}
|
|
1354
1767
|
}
|
|
1768
|
+
columnsCache.set(model, out);
|
|
1355
1769
|
return out;
|
|
1356
1770
|
}
|
|
1357
1771
|
|
|
1358
|
-
export { AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, NoResultError, NodeSqliteDriver, OPERATORS, PostgresDialect, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, and, belongsTo, column, columnsOf, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, not, or, parse, parseDatabaseUrl, select, sql, stringify, toCondNode, toDict, toJSON, update };
|
|
1359
|
-
//# sourceMappingURL=chunk-
|
|
1360
|
-
//# sourceMappingURL=chunk-
|
|
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
|