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