tempest-db-js 0.7.0 → 0.9.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 +9 -0
- package/dist/bin.cjs +1921 -12
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +31 -2
- package/dist/bin.js.map +1 -1
- package/dist/chunk-HXK6WIBP.js +5897 -0
- package/dist/chunk-HXK6WIBP.js.map +1 -0
- package/dist/{chunk-SI4CLSF7.js → chunk-ZBIHRVUS.js} +255 -11
- package/dist/chunk-ZBIHRVUS.js.map +1 -0
- package/dist/index.cjs +3156 -280
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3058 -549
- package/dist/index.d.ts +3058 -549
- package/dist/index.js +1 -1
- package/dist/migrations/index.cjs +1654 -11
- package/dist/migrations/index.cjs.map +1 -1
- package/dist/migrations/index.d.cts +30 -1
- package/dist/migrations/index.d.ts +30 -1
- package/dist/migrations/index.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-4AWUP7BM.js +0 -3076
- package/dist/chunk-4AWUP7BM.js.map +0 -1
- package/dist/chunk-SI4CLSF7.js.map +0 -1
package/dist/chunk-4AWUP7BM.js
DELETED
|
@@ -1,3076 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
|
-
|
|
3
|
-
// src/conditions.ts
|
|
4
|
-
var CONDITION = /* @__PURE__ */ Symbol.for("tempest-db-js.condition");
|
|
5
|
-
function isCondition(value) {
|
|
6
|
-
return typeof value === "object" && value !== null && value[CONDITION] === true;
|
|
7
|
-
}
|
|
8
|
-
function toCondNode(input) {
|
|
9
|
-
return isCondition(input) ? input.node : { kind: "fields", fields: input };
|
|
10
|
-
}
|
|
11
|
-
function wrap(node) {
|
|
12
|
-
return { [CONDITION]: true, node };
|
|
13
|
-
}
|
|
14
|
-
function toExprNode(operand) {
|
|
15
|
-
return isExpression(operand) ? operand.node : { kind: "value", value: operand };
|
|
16
|
-
}
|
|
17
|
-
function assertValueOperands(op, operands) {
|
|
18
|
-
if (operands.some(isExpression)) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
`The "${op}" operator binds its operands, so it takes values, not expressions.`
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
function isExpression(value) {
|
|
25
|
-
return value instanceof Expression;
|
|
26
|
-
}
|
|
27
|
-
var Expression = class {
|
|
28
|
-
constructor(node) {
|
|
29
|
-
this.node = node;
|
|
30
|
-
}
|
|
31
|
-
node;
|
|
32
|
-
/** Compare this expression against another expression or a bound value. */
|
|
33
|
-
compare(op, operand) {
|
|
34
|
-
return wrap({ kind: "compare", left: this.node, op, right: toExprNode(operand) });
|
|
35
|
-
}
|
|
36
|
-
/** `=` (or `IS NULL` for a null value). */
|
|
37
|
-
eq(operand) {
|
|
38
|
-
return this.compare("eq", operand);
|
|
39
|
-
}
|
|
40
|
-
/** `<>` (or `IS NOT NULL` for a null value). */
|
|
41
|
-
ne(operand) {
|
|
42
|
-
return this.compare("ne", operand);
|
|
43
|
-
}
|
|
44
|
-
/** `>`. */
|
|
45
|
-
gt(operand) {
|
|
46
|
-
return this.compare("gt", operand);
|
|
47
|
-
}
|
|
48
|
-
/** `>=`. */
|
|
49
|
-
gte(operand) {
|
|
50
|
-
return this.compare("gte", operand);
|
|
51
|
-
}
|
|
52
|
-
/** `<`. */
|
|
53
|
-
lt(operand) {
|
|
54
|
-
return this.compare("lt", operand);
|
|
55
|
-
}
|
|
56
|
-
/** `<=`. */
|
|
57
|
-
lte(operand) {
|
|
58
|
-
return this.compare("lte", operand);
|
|
59
|
-
}
|
|
60
|
-
/** `LIKE` — `%` and `_` in the operand are wildcards. */
|
|
61
|
-
like(pattern) {
|
|
62
|
-
return this.compare("like", pattern);
|
|
63
|
-
}
|
|
64
|
-
/** `ILIKE` — case-insensitive **pattern** matching, wildcards included. */
|
|
65
|
-
ilike(pattern) {
|
|
66
|
-
return this.compare("ilike", pattern);
|
|
67
|
-
}
|
|
68
|
-
/** Case-insensitive equality (`lower(a) = lower(b)`), with no wildcards. */
|
|
69
|
-
ieq(operand) {
|
|
70
|
-
return this.compare("ieq", operand);
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* `IN (...)` over a list of values.
|
|
74
|
-
*
|
|
75
|
-
* @param values The values to test against.
|
|
76
|
-
* @returns The condition.
|
|
77
|
-
* @throws Error When an entry is an {@link Expression} — a list operand is
|
|
78
|
-
* bound, so an expression there would be serialized as a parameter instead of
|
|
79
|
-
* rendered as SQL.
|
|
80
|
-
*/
|
|
81
|
-
in(values) {
|
|
82
|
-
assertValueOperands("in", values);
|
|
83
|
-
return this.compare("in", values);
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* `NOT IN (...)` over a list of values.
|
|
87
|
-
*
|
|
88
|
-
* @param values The values to exclude.
|
|
89
|
-
* @returns The condition.
|
|
90
|
-
* @throws Error When an entry is an {@link Expression} (see {@link Expression.in}).
|
|
91
|
-
*/
|
|
92
|
-
notIn(values) {
|
|
93
|
-
assertValueOperands("notIn", values);
|
|
94
|
-
return this.compare("notIn", values);
|
|
95
|
-
}
|
|
96
|
-
/**
|
|
97
|
-
* `BETWEEN lo AND hi` (inclusive).
|
|
98
|
-
*
|
|
99
|
-
* @param lo The lower bound.
|
|
100
|
-
* @param hi The upper bound.
|
|
101
|
-
* @returns The condition.
|
|
102
|
-
* @throws Error When a bound is an {@link Expression} (see {@link Expression.in}).
|
|
103
|
-
*/
|
|
104
|
-
between(lo, hi) {
|
|
105
|
-
assertValueOperands("between", [lo, hi]);
|
|
106
|
-
return this.compare("between", [lo, hi]);
|
|
107
|
-
}
|
|
108
|
-
/** `IS NULL` (true) / `IS NOT NULL` (false). */
|
|
109
|
-
isNull(value = true) {
|
|
110
|
-
return this.compare("isNull", value);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
function col(name) {
|
|
114
|
-
return new Expression({ kind: "column", name });
|
|
115
|
-
}
|
|
116
|
-
function val(value) {
|
|
117
|
-
return new Expression({ kind: "value", value });
|
|
118
|
-
}
|
|
119
|
-
var FUNCTION_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
120
|
-
function toArg(arg) {
|
|
121
|
-
return typeof arg === "string" ? { kind: "column", name: arg } : arg.node;
|
|
122
|
-
}
|
|
123
|
-
function call(name, ...args) {
|
|
124
|
-
if (!FUNCTION_NAME.test(name)) {
|
|
125
|
-
throw new Error(
|
|
126
|
-
`fn.call() takes a plain SQL function name; got ${JSON.stringify(name)}.`
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
return new Expression({ kind: "fn", name, args: args.map(toArg) });
|
|
130
|
-
}
|
|
131
|
-
var fn = {
|
|
132
|
-
/** `lower(x)`. */
|
|
133
|
-
lower: (arg) => call("lower", arg),
|
|
134
|
-
/** `upper(x)`. */
|
|
135
|
-
upper: (arg) => call("upper", arg),
|
|
136
|
-
/** `trim(x)`. */
|
|
137
|
-
trim: (arg) => call("trim", arg),
|
|
138
|
-
/** `length(x)`. */
|
|
139
|
-
length: (arg) => call("length", arg),
|
|
140
|
-
/** `abs(x)`. */
|
|
141
|
-
abs: (arg) => call("abs", arg),
|
|
142
|
-
/** `coalesce(a, b, ...)`. */
|
|
143
|
-
coalesce: (...args) => call("coalesce", ...args),
|
|
144
|
-
/**
|
|
145
|
-
* Any other SQL function, by name. Portability is the caller's problem —
|
|
146
|
-
* `date_trunc` is PostgreSQL, `strftime` is SQLite.
|
|
147
|
-
*/
|
|
148
|
-
call
|
|
149
|
-
};
|
|
150
|
-
function and(...inputs) {
|
|
151
|
-
return wrap({
|
|
152
|
-
kind: "and",
|
|
153
|
-
parts: inputs.map((i) => toCondNode(i))
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
function or(...inputs) {
|
|
157
|
-
return wrap({
|
|
158
|
-
kind: "or",
|
|
159
|
-
parts: inputs.map((i) => toCondNode(i))
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
function not(input) {
|
|
163
|
-
return wrap({ kind: "not", part: toCondNode(input) });
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// src/query.ts
|
|
167
|
-
function isSubquery(value) {
|
|
168
|
-
return typeof value === "object" && value !== null && value.node?.kind === "select";
|
|
169
|
-
}
|
|
170
|
-
var OPERATORS = [
|
|
171
|
-
"eq",
|
|
172
|
-
"ne",
|
|
173
|
-
"gt",
|
|
174
|
-
"gte",
|
|
175
|
-
"lt",
|
|
176
|
-
"lte",
|
|
177
|
-
"like",
|
|
178
|
-
"ilike",
|
|
179
|
-
"ieq",
|
|
180
|
-
"in",
|
|
181
|
-
"notIn",
|
|
182
|
-
"between",
|
|
183
|
-
"isNull",
|
|
184
|
-
"contains",
|
|
185
|
-
"containedBy",
|
|
186
|
-
"overlaps"
|
|
187
|
-
];
|
|
188
|
-
var Agg = class {
|
|
189
|
-
constructor(fn2, column2) {
|
|
190
|
-
this.fn = fn2;
|
|
191
|
-
this.column = column2;
|
|
192
|
-
}
|
|
193
|
-
fn;
|
|
194
|
-
column;
|
|
195
|
-
};
|
|
196
|
-
function count() {
|
|
197
|
-
return new Agg("count", "*");
|
|
198
|
-
}
|
|
199
|
-
function sum(column2) {
|
|
200
|
-
return new Agg("sum", column2);
|
|
201
|
-
}
|
|
202
|
-
function avg(column2) {
|
|
203
|
-
return new Agg("avg", column2);
|
|
204
|
-
}
|
|
205
|
-
function min(column2) {
|
|
206
|
-
return new Agg("min", column2);
|
|
207
|
-
}
|
|
208
|
-
function max(column2) {
|
|
209
|
-
return new Agg("max", column2);
|
|
210
|
-
}
|
|
211
|
-
var SelectBuilder = class _SelectBuilder {
|
|
212
|
-
constructor(node, source) {
|
|
213
|
-
this.node = node;
|
|
214
|
-
this.source = source;
|
|
215
|
-
}
|
|
216
|
-
node;
|
|
217
|
-
source;
|
|
218
|
-
with(patch) {
|
|
219
|
-
return new _SelectBuilder(
|
|
220
|
-
{ ...this.node, ...patch },
|
|
221
|
-
this.source
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
/** Add a WHERE filter: the object form (keys typed) or an `and`/`or`/`not`. */
|
|
225
|
-
where(input) {
|
|
226
|
-
return this.with({ where: toCondNode(input) });
|
|
227
|
-
}
|
|
228
|
-
/**
|
|
229
|
-
* Filter by the result of the aggregation (`HAVING`).
|
|
230
|
-
*
|
|
231
|
-
* Only available on a grouped builder — `.having()` before `.aggregate()` is a
|
|
232
|
-
* compile error, not invalid SQL at runtime. Keys are the aggregate aliases you
|
|
233
|
-
* named plus the grouped columns; `WHERE` still filters rows *before* grouping,
|
|
234
|
-
* which is a different question.
|
|
235
|
-
*
|
|
236
|
-
* @param input The condition, keyed by alias or grouped column.
|
|
237
|
-
* @returns A builder carrying the `HAVING` clause.
|
|
238
|
-
*
|
|
239
|
-
* @example
|
|
240
|
-
* ```ts
|
|
241
|
-
* select(Outbound)
|
|
242
|
-
* .where({ status: "queued" })
|
|
243
|
-
* .aggregate(["consumer"], { n: count() })
|
|
244
|
-
* .having({ n: { gt: 100 } });
|
|
245
|
-
* // ... GROUP BY "consumer" HAVING COUNT(*) > $2
|
|
246
|
-
* ```
|
|
247
|
-
*/
|
|
248
|
-
having(input) {
|
|
249
|
-
return new _SelectBuilder(
|
|
250
|
-
{ ...this.node, having: toCondNode(input) },
|
|
251
|
-
this.source
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
/** Emit `SELECT DISTINCT` — drop duplicate rows. */
|
|
255
|
-
distinct() {
|
|
256
|
-
return this.with({ distinct: true });
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* Group by columns and compute aggregates. The result row is the grouped
|
|
260
|
-
* columns (typed from the model) plus one field per aggregate alias.
|
|
261
|
-
*
|
|
262
|
-
* @param groupBy The columns to group by (checked against the model). Pass `[]`
|
|
263
|
-
* for a whole-table aggregate.
|
|
264
|
-
* @param spec A map of result alias → aggregate expression ({@link count},
|
|
265
|
-
* {@link sum}, {@link avg}, {@link min}, {@link max}).
|
|
266
|
-
* @returns A builder whose row is `Pick<Full, K> & { [alias]: aggResult }`.
|
|
267
|
-
*
|
|
268
|
-
* @example
|
|
269
|
-
* ```ts
|
|
270
|
-
* select(Order).aggregate(["status"], { n: count(), total: sum("amount") });
|
|
271
|
-
* // rows: { status: string; n: number; total: number | null }[]
|
|
272
|
-
* ```
|
|
273
|
-
*/
|
|
274
|
-
aggregate(groupBy, spec) {
|
|
275
|
-
const aggregates = Object.entries(spec).map(([alias, agg]) => ({
|
|
276
|
-
fn: agg.fn,
|
|
277
|
-
column: agg.column,
|
|
278
|
-
alias
|
|
279
|
-
}));
|
|
280
|
-
return new _SelectBuilder(
|
|
281
|
-
{ ...this.node, aggregates, groupBy },
|
|
282
|
-
this.source
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* Order by a column of the model, or — on a grouped query — by an aggregate
|
|
287
|
-
* alias. Unlike `HAVING`, every dialect accepts the output alias in `ORDER BY`,
|
|
288
|
-
* so the alias is emitted as written.
|
|
289
|
-
*
|
|
290
|
-
* @param column A model column, or a projected alias.
|
|
291
|
-
* @param direction `"asc"` (default) or `"desc"`.
|
|
292
|
-
* @returns A builder carrying the ordering term.
|
|
293
|
-
*/
|
|
294
|
-
orderBy(column2, direction = "asc") {
|
|
295
|
-
return this.with({
|
|
296
|
-
orderBy: [...this.node.orderBy, { column: column2, direction }]
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
/** Limit the number of rows. */
|
|
300
|
-
limit(n) {
|
|
301
|
-
return this.with({ limit: n });
|
|
302
|
-
}
|
|
303
|
-
/** Skip the first `n` rows. */
|
|
304
|
-
offset(n) {
|
|
305
|
-
return this.with({ offset: n });
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Narrow this SELECT to a single column and mark it as a subquery, so it can be
|
|
309
|
-
* the operand of `in` / `notIn`.
|
|
310
|
-
*
|
|
311
|
-
* The whole query — `where`, `orderBy`, `limit`, and a locking clause — is
|
|
312
|
-
* embedded in the outer statement, which is what collapses the claim-a-batch
|
|
313
|
-
* pattern into one round trip instead of selecting ids and sending them back.
|
|
314
|
-
*
|
|
315
|
-
* @param column The single column to project (checked against the model).
|
|
316
|
-
* @returns A subquery carrying that column's type.
|
|
317
|
-
*
|
|
318
|
-
* @example
|
|
319
|
-
* ```ts
|
|
320
|
-
* update(Outbound)
|
|
321
|
-
* .set({ status: "sending", attempts: sql.raw("attempts + 1") })
|
|
322
|
-
* .where({
|
|
323
|
-
* id: {
|
|
324
|
-
* in: select(Outbound)
|
|
325
|
-
* .where({ status: "queued" })
|
|
326
|
-
* .orderBy("nextAttemptAt")
|
|
327
|
-
* .limit(10)
|
|
328
|
-
* .forUpdate({ skipLocked: true })
|
|
329
|
-
* .asSubquery("id"),
|
|
330
|
-
* },
|
|
331
|
-
* })
|
|
332
|
-
* .returning();
|
|
333
|
-
* ```
|
|
334
|
-
*/
|
|
335
|
-
asSubquery(column2) {
|
|
336
|
-
return { node: { ...this.node, columns: [column2] } };
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Lock the selected rows for update (`SELECT ... FOR UPDATE`), à la
|
|
340
|
-
* SQLAlchemy's `with_for_update()`.
|
|
341
|
-
*
|
|
342
|
-
* `{ skipLocked: true }` is the job-queue claim: competing workers each take a
|
|
343
|
-
* disjoint batch instead of blocking on — or worse, double-processing — the
|
|
344
|
-
* same rows.
|
|
345
|
-
*
|
|
346
|
-
* PostgreSQL and MySQL 8.0+ only. SQLite has no row-level locking, and its
|
|
347
|
-
* dialect throws rather than emitting a `SELECT` that silently locks nothing —
|
|
348
|
-
* a lock that does not exist only fails under production concurrency.
|
|
349
|
-
*
|
|
350
|
-
* @param options `skipLocked` / `noWait` wait behavior, and `of` to restrict
|
|
351
|
-
* the lock to specific tables.
|
|
352
|
-
* @returns A builder carrying the locking clause.
|
|
353
|
-
* @throws Error When both `skipLocked` and `noWait` are set.
|
|
354
|
-
*
|
|
355
|
-
* @example
|
|
356
|
-
* ```ts
|
|
357
|
-
* const batch = await session.execute(
|
|
358
|
-
* select(Outbound)
|
|
359
|
-
* .where({ status: "queued" })
|
|
360
|
-
* .orderBy("nextAttemptAt")
|
|
361
|
-
* .limit(10)
|
|
362
|
-
* .forUpdate({ skipLocked: true }),
|
|
363
|
-
* ).all();
|
|
364
|
-
* ```
|
|
365
|
-
*/
|
|
366
|
-
forUpdate(options) {
|
|
367
|
-
return this.with({ lock: buildLock("update", options) });
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* Take a shared read lock on the selected rows (`SELECT ... FOR SHARE`), the
|
|
371
|
-
* weaker counterpart of {@link SelectBuilder.forUpdate}.
|
|
372
|
-
*
|
|
373
|
-
* @param options `skipLocked` / `noWait` wait behavior, and `of` tables.
|
|
374
|
-
* @returns A builder carrying the locking clause.
|
|
375
|
-
* @throws Error When both `skipLocked` and `noWait` are set.
|
|
376
|
-
*/
|
|
377
|
-
forShare(options) {
|
|
378
|
-
return this.with({ lock: buildLock("share", options) });
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
function buildLock(strength, options) {
|
|
382
|
-
if (options?.skipLocked && options?.noWait) {
|
|
383
|
-
throw new Error("forUpdate/forShare accept skipLocked or noWait, not both.");
|
|
384
|
-
}
|
|
385
|
-
const wait = options?.skipLocked ? "skipLocked" : options?.noWait ? "noWait" : "block";
|
|
386
|
-
return { strength, wait, of: options?.of ?? [] };
|
|
387
|
-
}
|
|
388
|
-
function select(model, columns) {
|
|
389
|
-
return new SelectBuilder(
|
|
390
|
-
{
|
|
391
|
-
kind: "select",
|
|
392
|
-
table: model.tablename,
|
|
393
|
-
columns: columns ?? "*",
|
|
394
|
-
distinct: false,
|
|
395
|
-
aggregates: [],
|
|
396
|
-
groupBy: [],
|
|
397
|
-
where: void 0,
|
|
398
|
-
orderBy: [],
|
|
399
|
-
limit: void 0,
|
|
400
|
-
offset: void 0,
|
|
401
|
-
names: columnNamesOf(model) ?? void 0
|
|
402
|
-
},
|
|
403
|
-
model
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// src/serialize.ts
|
|
408
|
-
var ValidationError = class extends Error {
|
|
409
|
-
constructor(table, issues) {
|
|
410
|
-
super(`Validation failed for ${table}:
|
|
411
|
-
- ${issues.join("\n - ")}`);
|
|
412
|
-
this.table = table;
|
|
413
|
-
this.issues = issues;
|
|
414
|
-
this.name = "ValidationError";
|
|
415
|
-
}
|
|
416
|
-
table;
|
|
417
|
-
issues;
|
|
418
|
-
};
|
|
419
|
-
function toBase64(bytes) {
|
|
420
|
-
if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
|
|
421
|
-
let binary = "";
|
|
422
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
423
|
-
return btoa(binary);
|
|
424
|
-
}
|
|
425
|
-
function fromBase64(value) {
|
|
426
|
-
if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64"));
|
|
427
|
-
const binary = atob(value);
|
|
428
|
-
const bytes = new Uint8Array(binary.length);
|
|
429
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
430
|
-
return bytes;
|
|
431
|
-
}
|
|
432
|
-
function encodeValue(column2, value) {
|
|
433
|
-
if (value === null || value === void 0) return null;
|
|
434
|
-
switch (column2.type.kind) {
|
|
435
|
-
case "bigint":
|
|
436
|
-
return typeof value === "bigint" ? value.toString() : value;
|
|
437
|
-
case "date":
|
|
438
|
-
case "datetime":
|
|
439
|
-
case "timestamp":
|
|
440
|
-
return value instanceof Date ? value.toISOString() : value;
|
|
441
|
-
case "blob":
|
|
442
|
-
return value instanceof Uint8Array ? toBase64(value) : value;
|
|
443
|
-
default:
|
|
444
|
-
return value;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
function decodeValue(column2, value) {
|
|
448
|
-
if (value === null || value === void 0) return null;
|
|
449
|
-
switch (column2.type.kind) {
|
|
450
|
-
case "bigint":
|
|
451
|
-
return typeof value === "bigint" ? value : BigInt(value);
|
|
452
|
-
case "date":
|
|
453
|
-
case "datetime":
|
|
454
|
-
case "timestamp":
|
|
455
|
-
return value instanceof Date ? value : new Date(value);
|
|
456
|
-
case "blob":
|
|
457
|
-
return value instanceof Uint8Array ? value : fromBase64(value);
|
|
458
|
-
case "json":
|
|
459
|
-
return typeof value === "string" ? JSON.parse(value) : value;
|
|
460
|
-
case "array": {
|
|
461
|
-
const element = column2.type.meta.element;
|
|
462
|
-
const items = typeof value === "string" ? JSON.parse(value) : value;
|
|
463
|
-
if (!Array.isArray(items)) return items;
|
|
464
|
-
if (!element) return items;
|
|
465
|
-
return items.map((item) => decodeValue({ type: element }, item));
|
|
466
|
-
}
|
|
467
|
-
case "numeric":
|
|
468
|
-
return typeof value === "string" ? value : String(value);
|
|
469
|
-
case "boolean":
|
|
470
|
-
return typeof value === "boolean" ? value : value === 1 || value === "true";
|
|
471
|
-
case "smallint":
|
|
472
|
-
case "integer":
|
|
473
|
-
case "real":
|
|
474
|
-
case "double":
|
|
475
|
-
return typeof value === "number" ? value : Number(value);
|
|
476
|
-
default:
|
|
477
|
-
return value;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
function toDict(model, row) {
|
|
481
|
-
const columns = columnsOf(model);
|
|
482
|
-
const out = {};
|
|
483
|
-
for (const name of Object.keys(columns)) {
|
|
484
|
-
out[name] = row[name] ?? null;
|
|
485
|
-
}
|
|
486
|
-
return out;
|
|
487
|
-
}
|
|
488
|
-
function toJSON(model, row) {
|
|
489
|
-
const columns = columnsOf(model);
|
|
490
|
-
const out = {};
|
|
491
|
-
for (const [name, col2] of Object.entries(columns)) {
|
|
492
|
-
out[name] = encodeValue(col2, row[name] ?? null);
|
|
493
|
-
}
|
|
494
|
-
return out;
|
|
495
|
-
}
|
|
496
|
-
function stringify(model, row) {
|
|
497
|
-
return JSON.stringify(toJSON(model, row));
|
|
498
|
-
}
|
|
499
|
-
function fromDict(model, data) {
|
|
500
|
-
const columns = columnsOf(model);
|
|
501
|
-
const out = {};
|
|
502
|
-
const issues = [];
|
|
503
|
-
for (const [name, col2] of Object.entries(columns)) {
|
|
504
|
-
const present = name in data && data[name] !== void 0 && data[name] !== null;
|
|
505
|
-
if (!present) {
|
|
506
|
-
const required = col2.flags.notNull && !col2.flags.hasDefault;
|
|
507
|
-
if (required) {
|
|
508
|
-
issues.push(`missing required column "${name}"`);
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
out[name] = null;
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
try {
|
|
515
|
-
out[name] = decodeValue(col2, data[name]);
|
|
516
|
-
} catch (error) {
|
|
517
|
-
issues.push(`column "${name}": ${error.message}`);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
if (issues.length > 0) {
|
|
521
|
-
throw new ValidationError(model.tablename, issues);
|
|
522
|
-
}
|
|
523
|
-
return out;
|
|
524
|
-
}
|
|
525
|
-
function parse(model, json) {
|
|
526
|
-
return fromDict(model, JSON.parse(json));
|
|
527
|
-
}
|
|
528
|
-
function decoderFor(type) {
|
|
529
|
-
switch (type.kind) {
|
|
530
|
-
case "bigint":
|
|
531
|
-
return (v) => v == null ? null : typeof v === "bigint" ? v : BigInt(v);
|
|
532
|
-
case "date":
|
|
533
|
-
case "datetime":
|
|
534
|
-
case "timestamp":
|
|
535
|
-
return (v) => v == null ? null : v instanceof Date ? v : new Date(v);
|
|
536
|
-
case "blob":
|
|
537
|
-
return (v) => v == null ? null : v instanceof Uint8Array ? v : fromBase64(v);
|
|
538
|
-
case "json":
|
|
539
|
-
return (v) => v == null ? null : typeof v === "string" ? JSON.parse(v) : v;
|
|
540
|
-
case "array": {
|
|
541
|
-
const element = type.meta.element;
|
|
542
|
-
const inner = element ? decoderFor(element) : null;
|
|
543
|
-
if (!inner) return null;
|
|
544
|
-
return (v) => v == null ? null : Array.isArray(v) ? v.map(inner) : v;
|
|
545
|
-
}
|
|
546
|
-
case "numeric":
|
|
547
|
-
return (v) => v == null ? null : typeof v === "string" ? v : String(v);
|
|
548
|
-
case "boolean":
|
|
549
|
-
return (v) => v == null ? null : typeof v === "boolean" ? v : v === 1 || v === "true";
|
|
550
|
-
case "smallint":
|
|
551
|
-
case "integer":
|
|
552
|
-
case "real":
|
|
553
|
-
case "double":
|
|
554
|
-
return (v) => v == null ? null : typeof v === "number" ? v : Number(v);
|
|
555
|
-
default:
|
|
556
|
-
return null;
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
var mapperCache = /* @__PURE__ */ new WeakMap();
|
|
560
|
-
function mapperFor(model) {
|
|
561
|
-
const cached = mapperCache.get(model);
|
|
562
|
-
if (cached) return cached;
|
|
563
|
-
const props = columnPropsOf(model);
|
|
564
|
-
const names = props ? Object.fromEntries(Object.entries(props).map(([db, prop]) => [prop, db])) : null;
|
|
565
|
-
const decoders = /* @__PURE__ */ new Map();
|
|
566
|
-
for (const [prop, col2] of Object.entries(columnsOf(model))) {
|
|
567
|
-
const decoder = decoderFor(col2.type);
|
|
568
|
-
if (decoder) decoders.set(names?.[prop] ?? prop, decoder);
|
|
569
|
-
}
|
|
570
|
-
const mapper = { props, decoders };
|
|
571
|
-
mapperCache.set(model, mapper);
|
|
572
|
-
return mapper;
|
|
573
|
-
}
|
|
574
|
-
function coerceRow(model, raw) {
|
|
575
|
-
const { props, decoders } = mapperFor(model);
|
|
576
|
-
const out = {};
|
|
577
|
-
for (const name of Object.keys(raw)) {
|
|
578
|
-
const decode2 = decoders.get(name);
|
|
579
|
-
out[props?.[name] ?? name] = decode2 ? decode2(raw[name]) : raw[name];
|
|
580
|
-
}
|
|
581
|
-
return out;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
// src/mutations.ts
|
|
585
|
-
var STRUCTURED_KINDS = /* @__PURE__ */ new Set(["json", "array", "blob"]);
|
|
586
|
-
function isBindableScalar(value) {
|
|
587
|
-
if (value === null || value === void 0) return true;
|
|
588
|
-
const type = typeof value;
|
|
589
|
-
if (type === "string" || type === "number" || type === "bigint" || type === "boolean") {
|
|
590
|
-
return true;
|
|
591
|
-
}
|
|
592
|
-
return value instanceof Date || value instanceof Uint8Array;
|
|
593
|
-
}
|
|
594
|
-
function assertWritableValues(model, values, clause) {
|
|
595
|
-
const columns = columnsOf(model);
|
|
596
|
-
const issues = [];
|
|
597
|
-
for (const [key, value] of Object.entries(values)) {
|
|
598
|
-
const col2 = columns[key];
|
|
599
|
-
if (!col2) {
|
|
600
|
-
issues.push(`${clause}: "${key}" is not a column of ${model.tablename}`);
|
|
601
|
-
continue;
|
|
602
|
-
}
|
|
603
|
-
if (isSqlExpression(value) || isBindableScalar(value)) continue;
|
|
604
|
-
if (typeof value === "object" && STRUCTURED_KINDS.has(col2.type.kind)) continue;
|
|
605
|
-
issues.push(
|
|
606
|
-
`${clause}: "${key}" got ${describeValue(value)}, which cannot be bound to a ${col2.type.kind} column \u2014 use sql.raw()/sql.expr\`...\` for a SQL expression`
|
|
607
|
-
);
|
|
608
|
-
}
|
|
609
|
-
if (issues.length > 0) throw new ValidationError(model.tablename, issues);
|
|
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
|
-
}
|
|
626
|
-
function describeValue(value) {
|
|
627
|
-
if (typeof value === "function") return "a function";
|
|
628
|
-
if (Array.isArray(value)) return "an array";
|
|
629
|
-
if (typeof value === "symbol") return "a symbol";
|
|
630
|
-
return "an object";
|
|
631
|
-
}
|
|
632
|
-
var InsertBuilder = class _InsertBuilder {
|
|
633
|
-
constructor(node, source) {
|
|
634
|
-
this.node = node;
|
|
635
|
-
this.source = source;
|
|
636
|
-
}
|
|
637
|
-
node;
|
|
638
|
-
source;
|
|
639
|
-
with(patch) {
|
|
640
|
-
return new _InsertBuilder({ ...this.node, ...patch }, this.source);
|
|
641
|
-
}
|
|
642
|
-
/**
|
|
643
|
-
* Provide one row or many rows to insert, typed by the insert shape.
|
|
644
|
-
*
|
|
645
|
-
* @param rows One row, or an array of rows.
|
|
646
|
-
* @returns A builder carrying the rows.
|
|
647
|
-
* @throws ValidationError When a value is not a column value the dialect can
|
|
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.
|
|
650
|
-
*/
|
|
651
|
-
values(rows) {
|
|
652
|
-
const list = Array.isArray(rows) ? rows : [rows];
|
|
653
|
-
for (const row of list) assertWritableValues(this.source, row, "values");
|
|
654
|
-
assertConsistentRows(this.source, list);
|
|
655
|
-
return this.with({ values: list });
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* On a unique/PK conflict on `target`, do nothing (skip the row).
|
|
659
|
-
*
|
|
660
|
-
* @param target The conflicting column(s) — a unique or primary key.
|
|
661
|
-
* @param options Pass `where` to name the predicate of a **partial** unique
|
|
662
|
-
* index, which PostgreSQL requires in order to match it as a conflict target.
|
|
663
|
-
* @returns A builder carrying the conflict clause.
|
|
664
|
-
*
|
|
665
|
-
* @example
|
|
666
|
-
* ```ts
|
|
667
|
-
* insert(Outbound)
|
|
668
|
-
* .values(data)
|
|
669
|
-
* .onConflictDoNothing(["consumer", "idempotencyKey"], {
|
|
670
|
-
* where: { idempotencyKey: { isNull: false } },
|
|
671
|
-
* })
|
|
672
|
-
* .returning();
|
|
673
|
-
* ```
|
|
674
|
-
*/
|
|
675
|
-
onConflictDoNothing(target, options) {
|
|
676
|
-
return this.with({
|
|
677
|
-
onConflict: {
|
|
678
|
-
target,
|
|
679
|
-
update: "nothing",
|
|
680
|
-
targetWhere: options?.where ? toCondNode(options.where) : void 0
|
|
681
|
-
}
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
/**
|
|
685
|
-
* On a unique/PK conflict on `target`, overwrite the given columns (upsert).
|
|
686
|
-
*
|
|
687
|
-
* @param target The conflicting column(s) — a unique or primary key.
|
|
688
|
-
* @param set The columns to update with new values.
|
|
689
|
-
* @param options `indexWhere` names the predicate of a partial unique index
|
|
690
|
-
* (the conflict target); `updateWhere` further restricts which conflicting
|
|
691
|
-
* rows are rewritten.
|
|
692
|
-
* @returns A builder carrying the conflict clause.
|
|
693
|
-
* @throws ValidationError When a `set` value cannot be bound.
|
|
694
|
-
*/
|
|
695
|
-
onConflictDoUpdate(target, set, options) {
|
|
696
|
-
assertWritableValues(this.source, set, "set");
|
|
697
|
-
return this.with({
|
|
698
|
-
onConflict: {
|
|
699
|
-
target,
|
|
700
|
-
update: set,
|
|
701
|
-
targetWhere: options?.indexWhere ? toCondNode(options.indexWhere) : void 0,
|
|
702
|
-
updateWhere: options?.updateWhere ? toCondNode(options.updateWhere) : void 0
|
|
703
|
-
}
|
|
704
|
-
});
|
|
705
|
-
}
|
|
706
|
-
returning(columns) {
|
|
707
|
-
return this.with({ returning: columns ?? "*" });
|
|
708
|
-
}
|
|
709
|
-
};
|
|
710
|
-
function insert(model) {
|
|
711
|
-
return new InsertBuilder(
|
|
712
|
-
{
|
|
713
|
-
kind: "insert",
|
|
714
|
-
table: model.tablename,
|
|
715
|
-
values: [],
|
|
716
|
-
returning: null,
|
|
717
|
-
names: columnNamesOf(model) ?? void 0
|
|
718
|
-
},
|
|
719
|
-
model
|
|
720
|
-
);
|
|
721
|
-
}
|
|
722
|
-
var UpdateBuilder = class _UpdateBuilder {
|
|
723
|
-
constructor(node, source) {
|
|
724
|
-
this.node = node;
|
|
725
|
-
this.source = source;
|
|
726
|
-
}
|
|
727
|
-
node;
|
|
728
|
-
source;
|
|
729
|
-
with(patch) {
|
|
730
|
-
return new _UpdateBuilder({ ...this.node, ...patch }, this.source);
|
|
731
|
-
}
|
|
732
|
-
/**
|
|
733
|
-
* The columns to write. Partial — only the given columns change.
|
|
734
|
-
*
|
|
735
|
-
* A value is bound as a parameter unless it is a {@link sql} expression, which
|
|
736
|
-
* is rendered inline instead — that is how a counter is written without a
|
|
737
|
-
* read-modify-write race.
|
|
738
|
-
*
|
|
739
|
-
* @param values The column → value map.
|
|
740
|
-
* @returns A builder carrying the assignments.
|
|
741
|
-
* @throws ValidationError When a value is not a column value the dialect can
|
|
742
|
-
* bind (a bare object, an array on a scalar column, a function).
|
|
743
|
-
*
|
|
744
|
-
* @example
|
|
745
|
-
* ```ts
|
|
746
|
-
* update(Outbound)
|
|
747
|
-
* .set({ attempts: sql.raw("attempts + 1"), updatedAt: sql.now() })
|
|
748
|
-
* .where({ id });
|
|
749
|
-
* ```
|
|
750
|
-
*/
|
|
751
|
-
set(values) {
|
|
752
|
-
assertWritableValues(this.source, values, "set");
|
|
753
|
-
return this.with({ set: values });
|
|
754
|
-
}
|
|
755
|
-
/** Restrict the rows to update. Marks the builder safe to execute. */
|
|
756
|
-
where(input) {
|
|
757
|
-
return this.with({
|
|
758
|
-
where: toCondNode(input),
|
|
759
|
-
guarded: true
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
/** Explicit opt-in to update EVERY row. Use deliberately. */
|
|
763
|
-
unguarded() {
|
|
764
|
-
return this.with({ guarded: true });
|
|
765
|
-
}
|
|
766
|
-
returning(columns) {
|
|
767
|
-
return this.with({ returning: columns ?? "*" });
|
|
768
|
-
}
|
|
769
|
-
};
|
|
770
|
-
function update(model) {
|
|
771
|
-
return new UpdateBuilder(
|
|
772
|
-
{
|
|
773
|
-
kind: "update",
|
|
774
|
-
table: model.tablename,
|
|
775
|
-
set: {},
|
|
776
|
-
where: void 0,
|
|
777
|
-
guarded: false,
|
|
778
|
-
returning: null,
|
|
779
|
-
names: columnNamesOf(model) ?? void 0
|
|
780
|
-
},
|
|
781
|
-
model
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
var DeleteBuilder = class _DeleteBuilder {
|
|
785
|
-
constructor(node, source) {
|
|
786
|
-
this.node = node;
|
|
787
|
-
this.source = source;
|
|
788
|
-
}
|
|
789
|
-
node;
|
|
790
|
-
source;
|
|
791
|
-
with(patch) {
|
|
792
|
-
return new _DeleteBuilder({ ...this.node, ...patch }, this.source);
|
|
793
|
-
}
|
|
794
|
-
/** Restrict the rows to delete. Marks the builder safe to execute. */
|
|
795
|
-
where(input) {
|
|
796
|
-
return this.with({
|
|
797
|
-
where: toCondNode(input),
|
|
798
|
-
guarded: true
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
/** Explicit opt-in to delete EVERY row. Use deliberately. */
|
|
802
|
-
unguarded() {
|
|
803
|
-
return this.with({ guarded: true });
|
|
804
|
-
}
|
|
805
|
-
returning(columns) {
|
|
806
|
-
return this.with({ returning: columns ?? "*" });
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
function del(model) {
|
|
810
|
-
return new DeleteBuilder(
|
|
811
|
-
{
|
|
812
|
-
kind: "delete",
|
|
813
|
-
table: model.tablename,
|
|
814
|
-
where: void 0,
|
|
815
|
-
guarded: false,
|
|
816
|
-
returning: null,
|
|
817
|
-
names: columnNamesOf(model) ?? void 0
|
|
818
|
-
},
|
|
819
|
-
model
|
|
820
|
-
);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// src/url.ts
|
|
824
|
-
var InvalidDatabaseUrl = class extends Error {
|
|
825
|
-
constructor(url, reason) {
|
|
826
|
-
super(`Invalid database URL ${JSON.stringify(url)}: ${reason}`);
|
|
827
|
-
this.name = "InvalidDatabaseUrl";
|
|
828
|
-
}
|
|
829
|
-
};
|
|
830
|
-
var DIALECT_ALIASES = {
|
|
831
|
-
sqlite: "sqlite",
|
|
832
|
-
sqlite3: "sqlite",
|
|
833
|
-
postgresql: "postgresql",
|
|
834
|
-
postgres: "postgresql",
|
|
835
|
-
pg: "postgresql",
|
|
836
|
-
mysql: "mysql",
|
|
837
|
-
mariadb: "mysql"
|
|
838
|
-
};
|
|
839
|
-
function splitScheme(scheme) {
|
|
840
|
-
const plus = scheme.indexOf("+");
|
|
841
|
-
if (plus === -1) return { base: scheme.toLowerCase(), driver: null };
|
|
842
|
-
return {
|
|
843
|
-
base: scheme.slice(0, plus).toLowerCase(),
|
|
844
|
-
driver: scheme.slice(plus + 1) || null
|
|
845
|
-
};
|
|
846
|
-
}
|
|
847
|
-
function decode(value) {
|
|
848
|
-
try {
|
|
849
|
-
return decodeURIComponent(value);
|
|
850
|
-
} catch {
|
|
851
|
-
return value;
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
function parseSqlite(raw, driver, rest) {
|
|
855
|
-
let database;
|
|
856
|
-
if (rest.endsWith(":memory:")) {
|
|
857
|
-
database = ":memory:";
|
|
858
|
-
} else if (rest.startsWith("///")) {
|
|
859
|
-
database = rest.slice(3) || ":memory:";
|
|
860
|
-
} else if (rest.startsWith("//")) {
|
|
861
|
-
database = rest.slice(2) || ":memory:";
|
|
862
|
-
} else {
|
|
863
|
-
database = rest || ":memory:";
|
|
864
|
-
}
|
|
865
|
-
return {
|
|
866
|
-
dialect: "sqlite",
|
|
867
|
-
driver,
|
|
868
|
-
host: null,
|
|
869
|
-
port: null,
|
|
870
|
-
user: null,
|
|
871
|
-
password: null,
|
|
872
|
-
database: decode(database),
|
|
873
|
-
options: {},
|
|
874
|
-
raw
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
function parseNetworkUrl(raw, driver, rest, dialect) {
|
|
878
|
-
let parsed;
|
|
879
|
-
try {
|
|
880
|
-
parsed = new URL(`${dialect}:${rest}`);
|
|
881
|
-
} catch {
|
|
882
|
-
throw new InvalidDatabaseUrl(raw, "could not parse host/credentials");
|
|
883
|
-
}
|
|
884
|
-
const database = decode(parsed.pathname.replace(/^\//, "")) || null;
|
|
885
|
-
const options = {};
|
|
886
|
-
for (const [key, value] of parsed.searchParams) options[key] = value;
|
|
887
|
-
return {
|
|
888
|
-
dialect,
|
|
889
|
-
driver,
|
|
890
|
-
host: parsed.hostname || null,
|
|
891
|
-
port: parsed.port ? Number(parsed.port) : null,
|
|
892
|
-
user: parsed.username ? decode(parsed.username) : null,
|
|
893
|
-
password: parsed.password ? decode(parsed.password) : null,
|
|
894
|
-
database,
|
|
895
|
-
options,
|
|
896
|
-
raw
|
|
897
|
-
};
|
|
898
|
-
}
|
|
899
|
-
function parseDatabaseUrl(url) {
|
|
900
|
-
const schemeEnd = url.indexOf(":");
|
|
901
|
-
if (schemeEnd === -1) {
|
|
902
|
-
throw new InvalidDatabaseUrl(
|
|
903
|
-
url,
|
|
904
|
-
"missing scheme (expected e.g. sqlite:// or postgresql://)"
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
const { base, driver } = splitScheme(url.slice(0, schemeEnd));
|
|
908
|
-
const dialect = DIALECT_ALIASES[base];
|
|
909
|
-
if (!dialect) {
|
|
910
|
-
throw new InvalidDatabaseUrl(url, `unknown dialect ${JSON.stringify(base)}`);
|
|
911
|
-
}
|
|
912
|
-
const rest = url.slice(schemeEnd + 1);
|
|
913
|
-
if (dialect === "sqlite") return parseSqlite(url, driver, rest);
|
|
914
|
-
return parseNetworkUrl(url, driver, rest, dialect);
|
|
915
|
-
}
|
|
916
|
-
function detectDialect(url) {
|
|
917
|
-
return parseDatabaseUrl(url).dialect;
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
// src/expressions.ts
|
|
921
|
-
function renderPortableToken(token, dialect) {
|
|
922
|
-
switch (token) {
|
|
923
|
-
case "now":
|
|
924
|
-
return dialect === "postgresql" ? "now()" : "CURRENT_TIMESTAMP";
|
|
925
|
-
case "current_date":
|
|
926
|
-
return "CURRENT_DATE";
|
|
927
|
-
case "current_time":
|
|
928
|
-
return "CURRENT_TIME";
|
|
929
|
-
case "uuidv4":
|
|
930
|
-
if (dialect === "postgresql") return "gen_random_uuid()";
|
|
931
|
-
if (dialect === "mysql") return "(UUID())";
|
|
932
|
-
return "(lower(hex(randomblob(16))))";
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// src/dialect.ts
|
|
937
|
-
var OPERATOR_SET = new Set(OPERATORS);
|
|
938
|
-
function isOperatorObject(value) {
|
|
939
|
-
if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || value instanceof Uint8Array) {
|
|
940
|
-
return false;
|
|
941
|
-
}
|
|
942
|
-
const keys = Object.keys(value);
|
|
943
|
-
return keys.length > 0 && keys.every((k) => OPERATOR_SET.has(k));
|
|
944
|
-
}
|
|
945
|
-
var Params = class {
|
|
946
|
-
constructor(placeholder) {
|
|
947
|
-
this.placeholder = placeholder;
|
|
948
|
-
}
|
|
949
|
-
placeholder;
|
|
950
|
-
values = [];
|
|
951
|
-
bind(value) {
|
|
952
|
-
this.values.push(value);
|
|
953
|
-
return this.placeholder(this.values.length);
|
|
954
|
-
}
|
|
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
|
-
}
|
|
968
|
-
function insertHasExpression(node) {
|
|
969
|
-
for (const row of node.values) {
|
|
970
|
-
for (const value of Object.values(row)) {
|
|
971
|
-
if (isSqlExpression(value)) return true;
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
const update2 = node.onConflict?.update;
|
|
975
|
-
if (update2 && update2 !== "nothing") {
|
|
976
|
-
for (const value of Object.values(update2)) {
|
|
977
|
-
if (isSqlExpression(value)) return true;
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
return false;
|
|
981
|
-
}
|
|
982
|
-
var BaseDialect = class _BaseDialect {
|
|
983
|
-
/**
|
|
984
|
-
* INSERT SQL templates keyed by structure (dialect|table|columns|rowCount|
|
|
985
|
-
* returning). Shared across dialect instances — the key namespaces by dialect
|
|
986
|
-
* name, and the placeholder text is dialect-specific but structure-determined.
|
|
987
|
-
*/
|
|
988
|
-
static insertTemplates = /* @__PURE__ */ new Map();
|
|
989
|
-
/** Quoted-identifier cache (see {@link quoteId}). Shared across dialects. */
|
|
990
|
-
static quotedIds = /* @__PURE__ */ new Map();
|
|
991
|
-
/**
|
|
992
|
-
* Validate a subquery operand before it is rendered, for dialects that restrict
|
|
993
|
-
* what an `IN (SELECT ...)` may contain. The default accepts everything.
|
|
994
|
-
*
|
|
995
|
-
* @param _node The subquery's AST.
|
|
996
|
-
* @throws Error When the dialect cannot execute this subquery.
|
|
997
|
-
*/
|
|
998
|
-
checkSubquery(_node) {
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* The SQL operator for an array containment/overlap test.
|
|
1002
|
-
*
|
|
1003
|
-
* Only PostgreSQL has native arrays; the other dialects throw rather than
|
|
1004
|
-
* emitting an operator that means something else there.
|
|
1005
|
-
*
|
|
1006
|
-
* @param op The array operator name.
|
|
1007
|
-
* @returns The SQL operator text.
|
|
1008
|
-
* @throws Error On a dialect without native array support.
|
|
1009
|
-
*/
|
|
1010
|
-
arrayOperator(op) {
|
|
1011
|
-
throw new Error(
|
|
1012
|
-
`The "${op}" operator needs native array support, which ${this.name} does not have.`
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
/**
|
|
1016
|
-
* Quote an identifier (column/table) for the active dialect.
|
|
1017
|
-
*
|
|
1018
|
-
* Memoized: identifiers form a small, stable set (column/table names), but this
|
|
1019
|
-
* runs for every identifier on every compile. Caching the quoted form removes a
|
|
1020
|
-
* regex-replace + string allocation from the hot path. The standard double-quote
|
|
1021
|
-
* form is identical across both dialects, so one shared cache is correct.
|
|
1022
|
-
*/
|
|
1023
|
-
quoteId(name) {
|
|
1024
|
-
const cached = _BaseDialect.quotedIds.get(name);
|
|
1025
|
-
if (cached !== void 0) return cached;
|
|
1026
|
-
const quoted = `"${name.replace(/"/g, '""')}"`;
|
|
1027
|
-
_BaseDialect.quotedIds.set(name, quoted);
|
|
1028
|
-
return quoted;
|
|
1029
|
-
}
|
|
1030
|
-
/** Compile any node to `{ sql, params }`. */
|
|
1031
|
-
compile(node) {
|
|
1032
|
-
const params = new Params((i) => this.placeholder(i));
|
|
1033
|
-
let sql2;
|
|
1034
|
-
switch (node.kind) {
|
|
1035
|
-
case "select":
|
|
1036
|
-
sql2 = this.compileSelect(node, params);
|
|
1037
|
-
break;
|
|
1038
|
-
case "insert":
|
|
1039
|
-
sql2 = this.compileInsert(node, params);
|
|
1040
|
-
break;
|
|
1041
|
-
case "update":
|
|
1042
|
-
sql2 = this.compileUpdate(node, params);
|
|
1043
|
-
break;
|
|
1044
|
-
case "delete":
|
|
1045
|
-
sql2 = this.compileDelete(node, params);
|
|
1046
|
-
break;
|
|
1047
|
-
case "join_select":
|
|
1048
|
-
sql2 = this.compileJoin(node, params);
|
|
1049
|
-
break;
|
|
1050
|
-
}
|
|
1051
|
-
return { sql: sql2, params: params.values };
|
|
1052
|
-
}
|
|
1053
|
-
/**
|
|
1054
|
-
* Render a qualified `alias.column` ref as `"alias"."column"`, translating the
|
|
1055
|
-
* property name to the real column name for that alias's model.
|
|
1056
|
-
*
|
|
1057
|
-
* @param ref The `alias.property` reference (a bare name is left unqualified).
|
|
1058
|
-
* @param names The node's per-alias name maps, if any source renames columns.
|
|
1059
|
-
* @returns The quoted, qualified identifier.
|
|
1060
|
-
*/
|
|
1061
|
-
qualify(ref, names) {
|
|
1062
|
-
const dot = ref.indexOf(".");
|
|
1063
|
-
if (dot === -1) return this.quoteId(ref);
|
|
1064
|
-
const alias = ref.slice(0, dot);
|
|
1065
|
-
const prop = ref.slice(dot + 1);
|
|
1066
|
-
return `${this.quoteId(alias)}.${this.columnId(prop, names?.[alias])}`;
|
|
1067
|
-
}
|
|
1068
|
-
/**
|
|
1069
|
-
* Quote a column identifier, translating the model property name to the real
|
|
1070
|
-
* database column name first.
|
|
1071
|
-
*
|
|
1072
|
-
* `names` is `undefined` for a model that renames nothing — the overwhelmingly
|
|
1073
|
-
* common case — so this stays a single lookup plus the memoized quote.
|
|
1074
|
-
*
|
|
1075
|
-
* @param prop The model property name as written in the builder.
|
|
1076
|
-
* @param names The node's property → column map, if any.
|
|
1077
|
-
* @returns The quoted database identifier.
|
|
1078
|
-
*/
|
|
1079
|
-
columnId(prop, names) {
|
|
1080
|
-
return this.quoteId(names?.[prop] ?? prop);
|
|
1081
|
-
}
|
|
1082
|
-
/**
|
|
1083
|
-
* Render a {@link SqlExpression} inline, binding the parameters it carries.
|
|
1084
|
-
*
|
|
1085
|
-
* This is what keeps `set({ attempts: sql.raw("attempts + 1") })` an expression
|
|
1086
|
-
* instead of a bound object: the fragment goes into the statement text, and
|
|
1087
|
-
* only a `sql.expr` template's interpolations become parameters.
|
|
1088
|
-
*
|
|
1089
|
-
* @param expr The branded expression.
|
|
1090
|
-
* @param params The parameter collector for the statement being compiled.
|
|
1091
|
-
* @returns The SQL text of the expression.
|
|
1092
|
-
*/
|
|
1093
|
-
renderExpression(expr, params) {
|
|
1094
|
-
const token = expr.expression;
|
|
1095
|
-
if (typeof token === "string") return renderPortableToken(token, this.name);
|
|
1096
|
-
if ("raw" in token) return token.raw;
|
|
1097
|
-
const parts = token.parts;
|
|
1098
|
-
let sql2 = parts[0] ?? "";
|
|
1099
|
-
for (let i = 1; i < parts.length; i++) {
|
|
1100
|
-
sql2 += `${params.bind(expr.params[i - 1])}${parts[i]}`;
|
|
1101
|
-
}
|
|
1102
|
-
return sql2;
|
|
1103
|
-
}
|
|
1104
|
-
/** Render one write value: a SQL expression inline, anything else as a parameter. */
|
|
1105
|
-
renderValue(value, params) {
|
|
1106
|
-
return isSqlExpression(value) ? this.renderExpression(value, params) : params.bind(value);
|
|
1107
|
-
}
|
|
1108
|
-
/**
|
|
1109
|
-
* Render a row-level locking clause (`FOR UPDATE ...`).
|
|
1110
|
-
*
|
|
1111
|
-
* Standard on PostgreSQL and MySQL 8.0+; SQLite overrides it to throw.
|
|
1112
|
-
*
|
|
1113
|
-
* @param lock The locking clause from the node.
|
|
1114
|
-
* @returns The SQL text, leading space included.
|
|
1115
|
-
*/
|
|
1116
|
-
renderLock(lock) {
|
|
1117
|
-
const strength = lock.strength === "update" ? "FOR UPDATE" : "FOR SHARE";
|
|
1118
|
-
const of = lock.of.length > 0 ? ` OF ${lock.of.map((t) => this.quoteId(t)).join(", ")}` : "";
|
|
1119
|
-
const wait = lock.wait === "skipLocked" ? " SKIP LOCKED" : lock.wait === "noWait" ? " NOWAIT" : "";
|
|
1120
|
-
return ` ${strength}${of}${wait}`;
|
|
1121
|
-
}
|
|
1122
|
-
// ---- statements -------------------------------------------------------
|
|
1123
|
-
/**
|
|
1124
|
-
* Compile a SELECT.
|
|
1125
|
-
*
|
|
1126
|
-
* Two alias rules differ between clauses and are handled here: PostgreSQL does
|
|
1127
|
-
* NOT accept a `SELECT` alias in `HAVING`, so an aggregate key is re-emitted as
|
|
1128
|
-
* its expression (`COUNT(*) > $1`), a form every dialect accepts; `ORDER BY`,
|
|
1129
|
-
* by contrast, accepts the output alias everywhere, so it is emitted as
|
|
1130
|
-
* written.
|
|
1131
|
-
*
|
|
1132
|
-
* @param node The select AST.
|
|
1133
|
-
* @param params The parameter collector.
|
|
1134
|
-
* @returns The SQL text.
|
|
1135
|
-
*/
|
|
1136
|
-
compileSelect(node, params) {
|
|
1137
|
-
const names = node.names;
|
|
1138
|
-
let cols;
|
|
1139
|
-
if (node.aggregates.length > 0) {
|
|
1140
|
-
const groupSel = node.groupBy.map((c) => this.columnId(c, names));
|
|
1141
|
-
const aggSel = node.aggregates.map((a) => {
|
|
1142
|
-
const inner = a.column === "*" ? "*" : this.columnId(a.column, names);
|
|
1143
|
-
return `${a.fn.toUpperCase()}(${inner}) AS ${this.quoteId(a.alias)}`;
|
|
1144
|
-
});
|
|
1145
|
-
cols = [...groupSel, ...aggSel].join(", ");
|
|
1146
|
-
} else {
|
|
1147
|
-
cols = node.columns === "*" ? "*" : node.columns.map((c) => this.columnId(c, names)).join(", ");
|
|
1148
|
-
}
|
|
1149
|
-
let sql2 = `SELECT ${node.distinct ? "DISTINCT " : ""}${cols} FROM ${this.quoteId(node.table)}`;
|
|
1150
|
-
const where = this.compileCondition(
|
|
1151
|
-
node.where,
|
|
1152
|
-
params,
|
|
1153
|
-
(k) => this.columnId(k, names)
|
|
1154
|
-
);
|
|
1155
|
-
if (where) sql2 += ` WHERE ${where}`;
|
|
1156
|
-
if (node.groupBy.length > 0) {
|
|
1157
|
-
sql2 += ` GROUP BY ${node.groupBy.map((c) => this.columnId(c, names)).join(", ")}`;
|
|
1158
|
-
}
|
|
1159
|
-
const aggByAlias = new Map(node.aggregates.map((a) => [a.alias, a]));
|
|
1160
|
-
if (node.having) {
|
|
1161
|
-
const having = this.compileCondition(node.having, params, (key) => {
|
|
1162
|
-
const agg = aggByAlias.get(key);
|
|
1163
|
-
if (!agg) return this.columnId(key, names);
|
|
1164
|
-
const inner = agg.column === "*" ? "*" : this.columnId(agg.column, names);
|
|
1165
|
-
return `${agg.fn.toUpperCase()}(${inner})`;
|
|
1166
|
-
});
|
|
1167
|
-
if (having) sql2 += ` HAVING ${having}`;
|
|
1168
|
-
}
|
|
1169
|
-
if (node.orderBy.length > 0) {
|
|
1170
|
-
const terms = node.orderBy.map((t) => {
|
|
1171
|
-
const id = aggByAlias.has(t.column) ? this.quoteId(t.column) : this.columnId(t.column, names);
|
|
1172
|
-
return `${id} ${t.direction === "desc" ? "DESC" : "ASC"}`;
|
|
1173
|
-
}).join(", ");
|
|
1174
|
-
sql2 += ` ORDER BY ${terms}`;
|
|
1175
|
-
}
|
|
1176
|
-
if (node.limit !== void 0) sql2 += ` LIMIT ${params.bind(node.limit)}`;
|
|
1177
|
-
if (node.offset !== void 0) sql2 += ` OFFSET ${params.bind(node.offset)}`;
|
|
1178
|
-
if (node.lock) {
|
|
1179
|
-
if (node.distinct || node.groupBy.length > 0 || node.aggregates.length > 0) {
|
|
1180
|
-
throw new Error(
|
|
1181
|
-
"FOR UPDATE / FOR SHARE cannot be combined with DISTINCT or an aggregate query \u2014 lock the underlying rows in a separate SELECT."
|
|
1182
|
-
);
|
|
1183
|
-
}
|
|
1184
|
-
sql2 += this.renderLock(node.lock);
|
|
1185
|
-
}
|
|
1186
|
-
return sql2;
|
|
1187
|
-
}
|
|
1188
|
-
/**
|
|
1189
|
-
* Compile an INSERT.
|
|
1190
|
-
*
|
|
1191
|
-
* Takes the cached fast path only when the statement text is a pure function of
|
|
1192
|
-
* its structure. A SQL expression among the values, or a conflict predicate,
|
|
1193
|
-
* makes the text depend on the values themselves — those compile uncached, in
|
|
1194
|
-
* SQL order, so placeholder positions stay correct.
|
|
1195
|
-
*/
|
|
1196
|
-
compileInsert(node, params) {
|
|
1197
|
-
const columns = insertColumns(node.values);
|
|
1198
|
-
const conflict = node.onConflict;
|
|
1199
|
-
const cacheable = conflict?.targetWhere === void 0 && conflict?.updateWhere === void 0 && !insertHasExpression(node);
|
|
1200
|
-
if (!cacheable) return this.compileInsertDirect(node, columns, params);
|
|
1201
|
-
for (const row of node.values) {
|
|
1202
|
-
for (const c of columns) params.bind(row[c] ?? null);
|
|
1203
|
-
}
|
|
1204
|
-
const conflictCols = conflict && conflict.update !== "nothing" ? Object.keys(conflict.update) : [];
|
|
1205
|
-
for (const c of conflictCols) {
|
|
1206
|
-
params.bind((conflict?.update)[c]);
|
|
1207
|
-
}
|
|
1208
|
-
return this.insertTemplate(node, columns, conflictCols, params);
|
|
1209
|
-
}
|
|
1210
|
-
/**
|
|
1211
|
-
* Compile an INSERT without the template cache, rendering clauses in statement
|
|
1212
|
-
* order so every parameter is bound at the position it appears.
|
|
1213
|
-
*
|
|
1214
|
-
* @param node The insert node.
|
|
1215
|
-
* @param columns The column keys shared by every row.
|
|
1216
|
-
* @param params The parameter collector.
|
|
1217
|
-
* @returns The SQL text.
|
|
1218
|
-
*/
|
|
1219
|
-
compileInsertDirect(node, columns, params) {
|
|
1220
|
-
const names = node.names;
|
|
1221
|
-
const colSql = columns.map((c) => this.columnId(c, names)).join(", ");
|
|
1222
|
-
const rowsSql = node.values.map((row) => {
|
|
1223
|
-
const cells = columns.map(
|
|
1224
|
-
(c) => this.renderValue(row[c] ?? null, params)
|
|
1225
|
-
);
|
|
1226
|
-
return `(${cells.join(", ")})`;
|
|
1227
|
-
}).join(", ");
|
|
1228
|
-
let sql2 = `INSERT INTO ${this.quoteId(node.table)} (${colSql}) VALUES ${rowsSql}`;
|
|
1229
|
-
if (node.onConflict) {
|
|
1230
|
-
const update2 = node.onConflict.update;
|
|
1231
|
-
const conflictCols = update2 === "nothing" ? [] : Object.keys(update2);
|
|
1232
|
-
let cursor = 0;
|
|
1233
|
-
sql2 += this.renderConflict(
|
|
1234
|
-
node.onConflict,
|
|
1235
|
-
conflictCols,
|
|
1236
|
-
() => {
|
|
1237
|
-
const key = conflictCols[cursor++];
|
|
1238
|
-
return this.renderValue(update2[key], params);
|
|
1239
|
-
},
|
|
1240
|
-
names,
|
|
1241
|
-
params
|
|
1242
|
-
);
|
|
1243
|
-
}
|
|
1244
|
-
sql2 += this.compileReturning(node.returning, names);
|
|
1245
|
-
return sql2;
|
|
1246
|
-
}
|
|
1247
|
-
/**
|
|
1248
|
-
* The INSERT SQL template for a given structure, cached across calls.
|
|
1249
|
-
*
|
|
1250
|
-
* The text depends only on (dialect, table, columns, row count, returning,
|
|
1251
|
-
* conflict shape) — never on the bound values — and placeholder positions are
|
|
1252
|
-
* deterministic from the counts (a fresh statement always starts binding at 1).
|
|
1253
|
-
* So a per-row insert loop compiles the string once and reuses it every row.
|
|
1254
|
-
*/
|
|
1255
|
-
insertTemplate(node, columns, conflictCols, params) {
|
|
1256
|
-
const returningKey = node.returning === null ? "" : node.returning === "*" ? "*" : node.returning.join(",");
|
|
1257
|
-
const conflictKey = node.onConflict ? `${node.onConflict.target.join(",")}>${node.onConflict.update === "nothing" ? "nothing" : conflictCols.join(",")}` : "";
|
|
1258
|
-
const key = `${this.name}|${node.table}|${columns.join(",")}|${node.values.length}|${returningKey}|${conflictKey}`;
|
|
1259
|
-
const cached = _BaseDialect.insertTemplates.get(key);
|
|
1260
|
-
if (cached !== void 0) return cached;
|
|
1261
|
-
const names = node.names;
|
|
1262
|
-
const colSql = columns.map((c) => this.columnId(c, names)).join(", ");
|
|
1263
|
-
let position = 0;
|
|
1264
|
-
const rowsSql = node.values.map(() => `(${columns.map(() => this.placeholder(++position)).join(", ")})`).join(", ");
|
|
1265
|
-
let sql2 = `INSERT INTO ${this.quoteId(node.table)} (${colSql}) VALUES ${rowsSql}`;
|
|
1266
|
-
if (node.onConflict) {
|
|
1267
|
-
sql2 += this.renderConflict(
|
|
1268
|
-
node.onConflict,
|
|
1269
|
-
conflictCols,
|
|
1270
|
-
() => this.placeholder(++position),
|
|
1271
|
-
names,
|
|
1272
|
-
params
|
|
1273
|
-
);
|
|
1274
|
-
}
|
|
1275
|
-
sql2 += this.compileReturning(node.returning, names);
|
|
1276
|
-
_BaseDialect.insertTemplates.set(key, sql2);
|
|
1277
|
-
return sql2;
|
|
1278
|
-
}
|
|
1279
|
-
/**
|
|
1280
|
-
* Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
|
|
1281
|
-
* `ON CONFLICT (...) [WHERE predicate] DO NOTHING | DO UPDATE SET ... [WHERE ...]`;
|
|
1282
|
-
* MySQL overrides this.
|
|
1283
|
-
*
|
|
1284
|
-
* The index predicate is rendered before the `DO UPDATE` assignments because
|
|
1285
|
-
* that is where it sits in the statement, so its parameters bind first.
|
|
1286
|
-
*
|
|
1287
|
-
* @param onConflict The conflict clause from the node.
|
|
1288
|
-
* @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
|
|
1289
|
-
* @param nextValue Yields the SQL for the next `DO UPDATE` assignment value.
|
|
1290
|
-
* @param names The node's property → column map, if any.
|
|
1291
|
-
* @param params The parameter collector, for the predicates.
|
|
1292
|
-
* @returns The SQL text, leading space included.
|
|
1293
|
-
*/
|
|
1294
|
-
renderConflict(onConflict, conflictCols, nextValue, names, params) {
|
|
1295
|
-
const idFor = (key) => this.columnId(key, names);
|
|
1296
|
-
const target = onConflict.target.map(idFor).join(", ");
|
|
1297
|
-
const indexWhere = this.compileCondition(onConflict.targetWhere, params, idFor);
|
|
1298
|
-
const targetSql = indexWhere ? `(${target}) WHERE ${indexWhere}` : `(${target})`;
|
|
1299
|
-
if (onConflict.update === "nothing") return ` ON CONFLICT ${targetSql} DO NOTHING`;
|
|
1300
|
-
const assignments = conflictCols.map((c) => `${idFor(c)} = ${nextValue()}`).join(", ");
|
|
1301
|
-
let sql2 = ` ON CONFLICT ${targetSql} DO UPDATE SET ${assignments}`;
|
|
1302
|
-
const updateWhere = this.compileCondition(onConflict.updateWhere, params, idFor);
|
|
1303
|
-
if (updateWhere) sql2 += ` WHERE ${updateWhere}`;
|
|
1304
|
-
return sql2;
|
|
1305
|
-
}
|
|
1306
|
-
compileUpdate(node, params) {
|
|
1307
|
-
const names = node.names;
|
|
1308
|
-
const sets = Object.entries(node.set).map(
|
|
1309
|
-
([col2, value]) => `${this.columnId(col2, names)} = ${this.renderValue(value, params)}`
|
|
1310
|
-
).join(", ");
|
|
1311
|
-
let sql2 = `UPDATE ${this.quoteId(node.table)} SET ${sets}`;
|
|
1312
|
-
const where = this.compileCondition(
|
|
1313
|
-
node.where,
|
|
1314
|
-
params,
|
|
1315
|
-
(k) => this.columnId(k, names)
|
|
1316
|
-
);
|
|
1317
|
-
if (where) sql2 += ` WHERE ${where}`;
|
|
1318
|
-
sql2 += this.compileReturning(node.returning, names);
|
|
1319
|
-
return sql2;
|
|
1320
|
-
}
|
|
1321
|
-
compileDelete(node, params) {
|
|
1322
|
-
const names = node.names;
|
|
1323
|
-
let sql2 = `DELETE FROM ${this.quoteId(node.table)}`;
|
|
1324
|
-
const where = this.compileCondition(
|
|
1325
|
-
node.where,
|
|
1326
|
-
params,
|
|
1327
|
-
(k) => this.columnId(k, names)
|
|
1328
|
-
);
|
|
1329
|
-
if (where) sql2 += ` WHERE ${where}`;
|
|
1330
|
-
sql2 += this.compileReturning(node.returning, names);
|
|
1331
|
-
return sql2;
|
|
1332
|
-
}
|
|
1333
|
-
compileJoin(node, params) {
|
|
1334
|
-
const names = node.names;
|
|
1335
|
-
const cols = node.selections.map((s) => {
|
|
1336
|
-
const ref = `${s.alias}.${s.column}`;
|
|
1337
|
-
return `${this.qualify(ref, names)} AS ${this.quoteId(ref)}`;
|
|
1338
|
-
}).join(", ");
|
|
1339
|
-
let sql2 = `SELECT ${cols} FROM ${this.quoteId(node.base.table)} AS ${this.quoteId(node.base.alias)}`;
|
|
1340
|
-
for (const j of node.joins) {
|
|
1341
|
-
const kw = j.kind === "left" ? "LEFT JOIN" : "INNER JOIN";
|
|
1342
|
-
const on = j.on.map(([l, r]) => `${this.qualify(l, names)} = ${this.qualify(r, names)}`).join(" AND ");
|
|
1343
|
-
sql2 += ` ${kw} ${this.quoteId(j.table)} AS ${this.quoteId(j.alias)} ON ${on}`;
|
|
1344
|
-
}
|
|
1345
|
-
const where = this.compileCondition(
|
|
1346
|
-
node.where,
|
|
1347
|
-
params,
|
|
1348
|
-
(k) => this.qualify(k, names)
|
|
1349
|
-
);
|
|
1350
|
-
if (where) sql2 += ` WHERE ${where}`;
|
|
1351
|
-
if (node.orderBy.length > 0) {
|
|
1352
|
-
const terms = node.orderBy.map(
|
|
1353
|
-
(t) => `${this.qualify(t.ref, names)} ${t.direction === "desc" ? "DESC" : "ASC"}`
|
|
1354
|
-
).join(", ");
|
|
1355
|
-
sql2 += ` ORDER BY ${terms}`;
|
|
1356
|
-
}
|
|
1357
|
-
if (node.limit !== void 0) sql2 += ` LIMIT ${params.bind(node.limit)}`;
|
|
1358
|
-
if (node.offset !== void 0) sql2 += ` OFFSET ${params.bind(node.offset)}`;
|
|
1359
|
-
return sql2;
|
|
1360
|
-
}
|
|
1361
|
-
// ---- clauses ----------------------------------------------------------
|
|
1362
|
-
compileReturning(returning, names) {
|
|
1363
|
-
if (returning === null) return "";
|
|
1364
|
-
if (returning === "*") return " RETURNING *";
|
|
1365
|
-
return ` RETURNING ${returning.map((c) => this.columnId(c, names)).join(", ")}`;
|
|
1366
|
-
}
|
|
1367
|
-
/**
|
|
1368
|
-
* Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
|
|
1369
|
-
* key to a quoted identifier — `quoteId` for single-table, `qualify` for joins —
|
|
1370
|
-
* so select/update/delete/join all share this one compiler.
|
|
1371
|
-
*/
|
|
1372
|
-
compileCondition(node, params, idFor) {
|
|
1373
|
-
if (!node) return "";
|
|
1374
|
-
switch (node.kind) {
|
|
1375
|
-
case "fields": {
|
|
1376
|
-
const conditions = [];
|
|
1377
|
-
for (const [key, value] of Object.entries(node.fields)) {
|
|
1378
|
-
const id = idFor(key);
|
|
1379
|
-
if (isOperatorObject(value)) {
|
|
1380
|
-
for (const [op, operand] of Object.entries(value)) {
|
|
1381
|
-
conditions.push(this.compileOperator(id, op, operand, params));
|
|
1382
|
-
}
|
|
1383
|
-
} else {
|
|
1384
|
-
conditions.push(
|
|
1385
|
-
value === null ? `${id} IS NULL` : `${id} = ${params.bind(value)}`
|
|
1386
|
-
);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
return conditions.join(" AND ");
|
|
1390
|
-
}
|
|
1391
|
-
case "and":
|
|
1392
|
-
case "or": {
|
|
1393
|
-
const parts = node.parts.map((p) => this.compileCondition(p, params, idFor)).filter((s) => s.length > 0);
|
|
1394
|
-
if (parts.length === 0) return "";
|
|
1395
|
-
const sep = node.kind === "and" ? " AND " : " OR ";
|
|
1396
|
-
return parts.map((p) => `(${p})`).join(sep);
|
|
1397
|
-
}
|
|
1398
|
-
case "not": {
|
|
1399
|
-
const inner = this.compileCondition(node.part, params, idFor);
|
|
1400
|
-
return inner ? `NOT (${inner})` : "";
|
|
1401
|
-
}
|
|
1402
|
-
case "compare": {
|
|
1403
|
-
const left = this.renderExpr(node.left, params, idFor);
|
|
1404
|
-
if (node.right.kind === "value") {
|
|
1405
|
-
return this.compileOperator(left, node.op, node.right.value, params);
|
|
1406
|
-
}
|
|
1407
|
-
return this.compileExprOperator(
|
|
1408
|
-
left,
|
|
1409
|
-
node.op,
|
|
1410
|
-
this.renderExpr(node.right, params, idFor)
|
|
1411
|
-
);
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
/**
|
|
1416
|
-
* Render one side of a comparison.
|
|
1417
|
-
*
|
|
1418
|
-
* A column reference goes through `idFor`, so an explicit `.name()` mapping and
|
|
1419
|
-
* join qualification apply here exactly as they do in the object form of
|
|
1420
|
-
* `where` — `col()` is not a way around them. Only a `value` node binds.
|
|
1421
|
-
*
|
|
1422
|
-
* @param node The expression AST.
|
|
1423
|
-
* @param params The parameter collector.
|
|
1424
|
-
* @param idFor The identifier resolver for the enclosing statement.
|
|
1425
|
-
* @returns The SQL text of the expression.
|
|
1426
|
-
*/
|
|
1427
|
-
renderExpr(node, params, idFor) {
|
|
1428
|
-
switch (node.kind) {
|
|
1429
|
-
case "column":
|
|
1430
|
-
return idFor(node.name);
|
|
1431
|
-
case "value":
|
|
1432
|
-
return params.bind(node.value);
|
|
1433
|
-
case "fn": {
|
|
1434
|
-
const args = node.args.map((a) => this.renderExpr(a, params, idFor)).join(", ");
|
|
1435
|
-
return `${node.name}(${args})`;
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
/**
|
|
1440
|
-
* Compile a comparison whose right-hand side is another expression rather than
|
|
1441
|
-
* a bound value (`total > paid`, `lower(a) = lower(b)`).
|
|
1442
|
-
*
|
|
1443
|
-
* The list and null operators are excluded: `IN`, `BETWEEN` and `IS NULL` take
|
|
1444
|
-
* a value operand, and accepting an expression there would silently compile to
|
|
1445
|
-
* something else.
|
|
1446
|
-
*
|
|
1447
|
-
* @param left The rendered left-hand side.
|
|
1448
|
-
* @param op The operator name.
|
|
1449
|
-
* @param right The rendered right-hand side.
|
|
1450
|
-
* @returns The SQL text of the predicate.
|
|
1451
|
-
* @throws Error When the operator needs a value operand.
|
|
1452
|
-
*/
|
|
1453
|
-
compileExprOperator(left, op, right) {
|
|
1454
|
-
switch (op) {
|
|
1455
|
-
case "eq":
|
|
1456
|
-
return `${left} = ${right}`;
|
|
1457
|
-
case "ne":
|
|
1458
|
-
return `${left} <> ${right}`;
|
|
1459
|
-
case "gt":
|
|
1460
|
-
return `${left} > ${right}`;
|
|
1461
|
-
case "gte":
|
|
1462
|
-
return `${left} >= ${right}`;
|
|
1463
|
-
case "lt":
|
|
1464
|
-
return `${left} < ${right}`;
|
|
1465
|
-
case "lte":
|
|
1466
|
-
return `${left} <= ${right}`;
|
|
1467
|
-
case "like":
|
|
1468
|
-
return `${left} LIKE ${right}`;
|
|
1469
|
-
case "ilike":
|
|
1470
|
-
return this.ilike(left, right);
|
|
1471
|
-
case "ieq":
|
|
1472
|
-
return `lower(${left}) = lower(${right})`;
|
|
1473
|
-
case "contains":
|
|
1474
|
-
case "containedBy":
|
|
1475
|
-
case "overlaps":
|
|
1476
|
-
return `${left} ${this.arrayOperator(op)} ${right}`;
|
|
1477
|
-
default:
|
|
1478
|
-
throw new Error(`The "${op}" operator takes a value operand, not an expression.`);
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
compileOperator(id, op, operand, params) {
|
|
1482
|
-
switch (op) {
|
|
1483
|
-
case "eq":
|
|
1484
|
-
return operand === null ? `${id} IS NULL` : `${id} = ${params.bind(operand)}`;
|
|
1485
|
-
case "ne":
|
|
1486
|
-
return operand === null ? `${id} IS NOT NULL` : `${id} <> ${params.bind(operand)}`;
|
|
1487
|
-
case "gt":
|
|
1488
|
-
return `${id} > ${params.bind(operand)}`;
|
|
1489
|
-
case "gte":
|
|
1490
|
-
return `${id} >= ${params.bind(operand)}`;
|
|
1491
|
-
case "lt":
|
|
1492
|
-
return `${id} < ${params.bind(operand)}`;
|
|
1493
|
-
case "lte":
|
|
1494
|
-
return `${id} <= ${params.bind(operand)}`;
|
|
1495
|
-
case "like":
|
|
1496
|
-
return `${id} LIKE ${params.bind(operand)}`;
|
|
1497
|
-
case "ilike":
|
|
1498
|
-
return this.ilike(id, params.bind(operand));
|
|
1499
|
-
case "ieq":
|
|
1500
|
-
return operand === null ? `${id} IS NULL` : `lower(${id}) = lower(${params.bind(operand)})`;
|
|
1501
|
-
case "contains":
|
|
1502
|
-
return `${id} ${this.arrayOperator("contains")} ${params.bind(operand)}`;
|
|
1503
|
-
case "containedBy":
|
|
1504
|
-
return `${id} ${this.arrayOperator("containedBy")} ${params.bind(operand)}`;
|
|
1505
|
-
case "overlaps":
|
|
1506
|
-
return `${id} ${this.arrayOperator("overlaps")} ${params.bind(operand)}`;
|
|
1507
|
-
case "in":
|
|
1508
|
-
return this.compileIn(id, operand, params, false);
|
|
1509
|
-
case "notIn":
|
|
1510
|
-
return this.compileIn(id, operand, params, true);
|
|
1511
|
-
case "between": {
|
|
1512
|
-
const [lo, hi] = operand;
|
|
1513
|
-
return `${id} BETWEEN ${params.bind(lo)} AND ${params.bind(hi)}`;
|
|
1514
|
-
}
|
|
1515
|
-
case "isNull":
|
|
1516
|
-
return operand ? `${id} IS NULL` : `${id} IS NOT NULL`;
|
|
1517
|
-
default:
|
|
1518
|
-
throw new Error(`Unknown operator ${JSON.stringify(op)}`);
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
/**
|
|
1522
|
-
* Compile `IN` / `NOT IN`, whose operand is either a value list or a
|
|
1523
|
-
* single-column subquery.
|
|
1524
|
-
*
|
|
1525
|
-
* The subquery is rendered at the position it appears in the outer statement
|
|
1526
|
-
* and shares the same parameter collector, so its own placeholders land in the
|
|
1527
|
-
* right order — and it keeps its own `names` map, since the inner model may use
|
|
1528
|
-
* a different naming convention than the outer one.
|
|
1529
|
-
*
|
|
1530
|
-
* @param id The quoted column identifier being tested.
|
|
1531
|
-
* @param operand A list of values, or a {@link Subquery}.
|
|
1532
|
-
* @param params The parameter collector for the statement being compiled.
|
|
1533
|
-
* @param negate True for `NOT IN`.
|
|
1534
|
-
* @returns The SQL text of the predicate.
|
|
1535
|
-
*/
|
|
1536
|
-
compileIn(id, operand, params, negate) {
|
|
1537
|
-
const keyword = negate ? "NOT IN" : "IN";
|
|
1538
|
-
if (isSubquery(operand)) {
|
|
1539
|
-
this.checkSubquery(operand.node);
|
|
1540
|
-
return `${id} ${keyword} (${this.compileSelect(operand.node, params)})`;
|
|
1541
|
-
}
|
|
1542
|
-
const values = operand;
|
|
1543
|
-
if (values.length === 0) {
|
|
1544
|
-
return negate ? "1 = 1" : "1 = 0";
|
|
1545
|
-
}
|
|
1546
|
-
const list = values.map((v) => params.bind(v)).join(", ");
|
|
1547
|
-
return `${id} ${keyword} (${list})`;
|
|
1548
|
-
}
|
|
1549
|
-
};
|
|
1550
|
-
var SqliteDialect = class extends BaseDialect {
|
|
1551
|
-
name = "sqlite";
|
|
1552
|
-
placeholder() {
|
|
1553
|
-
return "?";
|
|
1554
|
-
}
|
|
1555
|
-
ilike(column2, param) {
|
|
1556
|
-
return `${column2} LIKE ${param}`;
|
|
1557
|
-
}
|
|
1558
|
-
/**
|
|
1559
|
-
* SQLite has no row-level locking, so a lock request is an error rather than a
|
|
1560
|
-
* silently unlocked `SELECT` — a lock that does not exist only shows up as
|
|
1561
|
-
* duplicated work under production concurrency.
|
|
1562
|
-
*/
|
|
1563
|
-
renderLock() {
|
|
1564
|
-
throw new Error(
|
|
1565
|
-
"SQLite has no row-level locking \u2014 FOR UPDATE / FOR SHARE is unsupported. Serialize the claim inside a transaction instead."
|
|
1566
|
-
);
|
|
1567
|
-
}
|
|
1568
|
-
};
|
|
1569
|
-
var PostgresDialect = class extends BaseDialect {
|
|
1570
|
-
name = "postgresql";
|
|
1571
|
-
placeholder(index) {
|
|
1572
|
-
return `$${index}`;
|
|
1573
|
-
}
|
|
1574
|
-
ilike(column2, param) {
|
|
1575
|
-
return `${column2} ILIKE ${param}`;
|
|
1576
|
-
}
|
|
1577
|
-
arrayOperator(op) {
|
|
1578
|
-
if (op === "contains") return "@>";
|
|
1579
|
-
if (op === "containedBy") return "<@";
|
|
1580
|
-
return "&&";
|
|
1581
|
-
}
|
|
1582
|
-
};
|
|
1583
|
-
var mysqlQuotedIds = /* @__PURE__ */ new Map();
|
|
1584
|
-
var MysqlDialect = class extends BaseDialect {
|
|
1585
|
-
name = "mysql";
|
|
1586
|
-
placeholder() {
|
|
1587
|
-
return "?";
|
|
1588
|
-
}
|
|
1589
|
-
ilike(column2, param) {
|
|
1590
|
-
return `${column2} LIKE ${param}`;
|
|
1591
|
-
}
|
|
1592
|
-
quoteId(name) {
|
|
1593
|
-
const cached = mysqlQuotedIds.get(name);
|
|
1594
|
-
if (cached !== void 0) return cached;
|
|
1595
|
-
const quoted = `\`${name.replace(/`/g, "``")}\``;
|
|
1596
|
-
mysqlQuotedIds.set(name, quoted);
|
|
1597
|
-
return quoted;
|
|
1598
|
-
}
|
|
1599
|
-
/**
|
|
1600
|
-
* MySQL rejects `LIMIT` inside an `IN` subquery with
|
|
1601
|
-
* `ER_NOT_SUPPORTED_YET: This version of MySQL doesn't yet support
|
|
1602
|
-
* 'LIMIT & IN/ALL/ANY/SOME subquery'`. Failing at compile time names the fix
|
|
1603
|
-
* instead of surfacing that error from the driver at runtime.
|
|
1604
|
-
*/
|
|
1605
|
-
checkSubquery(node) {
|
|
1606
|
-
if (node.limit !== void 0 || node.offset !== void 0) {
|
|
1607
|
-
throw new Error(
|
|
1608
|
-
"MySQL does not support LIMIT/OFFSET inside an IN subquery. Select the ids first and pass them as a list, or wrap the subquery in a derived table."
|
|
1609
|
-
);
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
renderConflict(onConflict, conflictCols, nextValue, names) {
|
|
1613
|
-
if (onConflict.targetWhere || onConflict.updateWhere) {
|
|
1614
|
-
throw new Error(
|
|
1615
|
-
"MySQL's ON DUPLICATE KEY UPDATE has no conflict-target predicate \u2014 a partial unique index is PostgreSQL/SQLite only."
|
|
1616
|
-
);
|
|
1617
|
-
}
|
|
1618
|
-
if (onConflict.update === "nothing") {
|
|
1619
|
-
const col2 = this.columnId(onConflict.target[0] ?? "id", names);
|
|
1620
|
-
return ` ON DUPLICATE KEY UPDATE ${col2} = ${col2}`;
|
|
1621
|
-
}
|
|
1622
|
-
const assignments = conflictCols.map((c) => `${this.columnId(c, names)} = ${nextValue()}`).join(", ");
|
|
1623
|
-
return ` ON DUPLICATE KEY UPDATE ${assignments}`;
|
|
1624
|
-
}
|
|
1625
|
-
/**
|
|
1626
|
-
* MySQL has no `RETURNING`, so it cannot be compiled into a statement.
|
|
1627
|
-
*
|
|
1628
|
-
* `session.execute()` still honors `.returning()` on a **single-row INSERT** by
|
|
1629
|
-
* running the insert and reading the row back by key on the same connection —
|
|
1630
|
-
* that is execution, not compilation, so it never reaches here. Compiling a
|
|
1631
|
-
* node with `returning` directly is an error, rather than SQL that silently
|
|
1632
|
-
* returns nothing.
|
|
1633
|
-
*/
|
|
1634
|
-
compileReturning(returning) {
|
|
1635
|
-
if (returning === null) return "";
|
|
1636
|
-
throw new Error(
|
|
1637
|
-
"RETURNING cannot be compiled for MySQL. session.execute() reads a single-row INSERT back by key (LAST_INSERT_ID()); UPDATE/DELETE have no equivalent \u2014 run a SELECT yourself."
|
|
1638
|
-
);
|
|
1639
|
-
}
|
|
1640
|
-
};
|
|
1641
|
-
function getDialect(name) {
|
|
1642
|
-
switch (name) {
|
|
1643
|
-
case "sqlite":
|
|
1644
|
-
return new SqliteDialect();
|
|
1645
|
-
case "postgresql":
|
|
1646
|
-
return new PostgresDialect();
|
|
1647
|
-
case "mysql":
|
|
1648
|
-
return new MysqlDialect();
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
// src/join.ts
|
|
1653
|
-
function selectionsFor(alias, model) {
|
|
1654
|
-
return Object.keys(columnsOf(model)).map((column2) => ({ alias, column: column2 }));
|
|
1655
|
-
}
|
|
1656
|
-
function withAliasNames(current, alias, model) {
|
|
1657
|
-
const names = columnNamesOf(model);
|
|
1658
|
-
if (!names) return current;
|
|
1659
|
-
return { ...current ?? {}, [alias]: names };
|
|
1660
|
-
}
|
|
1661
|
-
var JoinBuilder = class _JoinBuilder {
|
|
1662
|
-
constructor(node, sources) {
|
|
1663
|
-
this.node = node;
|
|
1664
|
-
this.sources = sources;
|
|
1665
|
-
}
|
|
1666
|
-
node;
|
|
1667
|
-
sources;
|
|
1668
|
-
add(clause, model) {
|
|
1669
|
-
return new _JoinBuilder(
|
|
1670
|
-
{
|
|
1671
|
-
...this.node,
|
|
1672
|
-
joins: [...this.node.joins, clause],
|
|
1673
|
-
selections: [...this.node.selections, ...selectionsFor(clause.alias, model)],
|
|
1674
|
-
names: withAliasNames(this.node.names, clause.alias, model)
|
|
1675
|
-
},
|
|
1676
|
-
{ ...this.sources, [clause.alias]: model }
|
|
1677
|
-
);
|
|
1678
|
-
}
|
|
1679
|
-
clause(kind, model, alias, on) {
|
|
1680
|
-
return {
|
|
1681
|
-
kind,
|
|
1682
|
-
table: model.tablename,
|
|
1683
|
-
alias,
|
|
1684
|
-
on: Object.entries(on)
|
|
1685
|
-
};
|
|
1686
|
-
}
|
|
1687
|
-
/** Inner join another model under `alias`. */
|
|
1688
|
-
innerJoin(model, alias, on) {
|
|
1689
|
-
return this.add(
|
|
1690
|
-
this.clause("inner", model, alias, on),
|
|
1691
|
-
model
|
|
1692
|
-
);
|
|
1693
|
-
}
|
|
1694
|
-
/** Left (outer) join another model under `alias` — its side becomes nullable. */
|
|
1695
|
-
leftJoin(model, alias, on) {
|
|
1696
|
-
return this.add(
|
|
1697
|
-
this.clause("left", model, alias, on),
|
|
1698
|
-
model
|
|
1699
|
-
);
|
|
1700
|
-
}
|
|
1701
|
-
/** Filter by `alias.column` references (object form) or an `and`/`or`/`not`. */
|
|
1702
|
-
where(input) {
|
|
1703
|
-
return new _JoinBuilder(
|
|
1704
|
-
{ ...this.node, where: toCondNode(input) },
|
|
1705
|
-
this.sources
|
|
1706
|
-
);
|
|
1707
|
-
}
|
|
1708
|
-
/** Order by an `alias.column` reference. */
|
|
1709
|
-
orderBy(ref, direction = "asc") {
|
|
1710
|
-
return new _JoinBuilder(
|
|
1711
|
-
{
|
|
1712
|
-
...this.node,
|
|
1713
|
-
orderBy: [...this.node.orderBy, { ref, direction }]
|
|
1714
|
-
},
|
|
1715
|
-
this.sources
|
|
1716
|
-
);
|
|
1717
|
-
}
|
|
1718
|
-
limit(n) {
|
|
1719
|
-
return new _JoinBuilder({ ...this.node, limit: n }, this.sources);
|
|
1720
|
-
}
|
|
1721
|
-
offset(n) {
|
|
1722
|
-
return new _JoinBuilder({ ...this.node, offset: n }, this.sources);
|
|
1723
|
-
}
|
|
1724
|
-
};
|
|
1725
|
-
function join(model, alias) {
|
|
1726
|
-
return new JoinBuilder(
|
|
1727
|
-
{
|
|
1728
|
-
kind: "join_select",
|
|
1729
|
-
base: { table: model.tablename, alias },
|
|
1730
|
-
joins: [],
|
|
1731
|
-
selections: selectionsFor(alias, model),
|
|
1732
|
-
where: void 0,
|
|
1733
|
-
orderBy: [],
|
|
1734
|
-
limit: void 0,
|
|
1735
|
-
offset: void 0,
|
|
1736
|
-
names: withAliasNames(void 0, alias, model)
|
|
1737
|
-
},
|
|
1738
|
-
{ [alias]: model }
|
|
1739
|
-
);
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
// src/repository.ts
|
|
1743
|
-
var RecordNotFound = class extends Error {
|
|
1744
|
-
constructor(table, id) {
|
|
1745
|
-
super(`${table} not found for id ${JSON.stringify(id)}`);
|
|
1746
|
-
this.name = "RecordNotFound";
|
|
1747
|
-
}
|
|
1748
|
-
};
|
|
1749
|
-
function primaryKeyOf(model) {
|
|
1750
|
-
for (const [name, col2] of Object.entries(columnsOf(model))) {
|
|
1751
|
-
if (col2.flags.primaryKey) return name;
|
|
1752
|
-
}
|
|
1753
|
-
throw new Error(`${model.tablename} has no primary key`);
|
|
1754
|
-
}
|
|
1755
|
-
var BaseRepository = class {
|
|
1756
|
-
constructor(model, session) {
|
|
1757
|
-
this.model = model;
|
|
1758
|
-
this.session = session;
|
|
1759
|
-
this.pk = primaryKeyOf(model);
|
|
1760
|
-
}
|
|
1761
|
-
model;
|
|
1762
|
-
session;
|
|
1763
|
-
pk;
|
|
1764
|
-
/** All rows matching `filters` (or everything). Empty list when none match. */
|
|
1765
|
-
async list(filters) {
|
|
1766
|
-
const query = filters ? select(this.model).where(filters) : select(this.model);
|
|
1767
|
-
return this.session.execute(query).all();
|
|
1768
|
-
}
|
|
1769
|
-
/** The first row matching `filters`, or `null`. */
|
|
1770
|
-
async first(filters) {
|
|
1771
|
-
const query = filters ? select(this.model).where(filters) : select(this.model);
|
|
1772
|
-
return this.session.execute(query).first();
|
|
1773
|
-
}
|
|
1774
|
-
/** A single row by primary key, or `null`. */
|
|
1775
|
-
async getByIdOrNull(id) {
|
|
1776
|
-
return this.session.execute(select(this.model).where({ [this.pk]: id })).first();
|
|
1777
|
-
}
|
|
1778
|
-
/** A single row by primary key; throws `RecordNotFound` when absent. */
|
|
1779
|
-
async getById(id) {
|
|
1780
|
-
const row = await this.getByIdOrNull(id);
|
|
1781
|
-
if (row === null) throw new RecordNotFound(this.model.tablename, id);
|
|
1782
|
-
return row;
|
|
1783
|
-
}
|
|
1784
|
-
/** Whether any row matches `filters`. */
|
|
1785
|
-
async exists(filters) {
|
|
1786
|
-
return await this.first(filters) !== null;
|
|
1787
|
-
}
|
|
1788
|
-
/** How many rows match `filters` (or the whole table). */
|
|
1789
|
-
async count(filters) {
|
|
1790
|
-
const query = filters ? select(this.model, [this.pk]).where(filters) : select(this.model, [this.pk]);
|
|
1791
|
-
return (await this.session.execute(query).all()).length;
|
|
1792
|
-
}
|
|
1793
|
-
/** Insert one row, returning the created row. */
|
|
1794
|
-
async create(data) {
|
|
1795
|
-
return this.session.execute(insert(this.model).values(data).returning()).one();
|
|
1796
|
-
}
|
|
1797
|
-
/** Insert many rows, returning the created rows. */
|
|
1798
|
-
async createMany(data) {
|
|
1799
|
-
if (data.length === 0) return [];
|
|
1800
|
-
return this.session.execute(insert(this.model).values(data).returning()).all();
|
|
1801
|
-
}
|
|
1802
|
-
/** Update rows matching `filters`; returns the number of rows affected. */
|
|
1803
|
-
async update(filters, set) {
|
|
1804
|
-
return this.session.execute(update(this.model).set(set).where(filters)).rowsAffected();
|
|
1805
|
-
}
|
|
1806
|
-
/** Delete rows matching `filters`; returns the number of rows affected. */
|
|
1807
|
-
async delete(filters) {
|
|
1808
|
-
return this.session.execute(del(this.model).where(filters)).rowsAffected();
|
|
1809
|
-
}
|
|
1810
|
-
/**
|
|
1811
|
-
* A page of rows plus metadata. `total` counts all matching rows.
|
|
1812
|
-
*
|
|
1813
|
-
* @param filter Page, size, ordering and filters.
|
|
1814
|
-
* @returns The page and pagination metadata.
|
|
1815
|
-
*/
|
|
1816
|
-
async paginate(filter = {}) {
|
|
1817
|
-
const page = Math.max(1, filter.page ?? 1);
|
|
1818
|
-
const pageSize = Math.max(1, filter.pageSize ?? 20);
|
|
1819
|
-
const where = filter.filters;
|
|
1820
|
-
let query = where ? select(this.model).where(where) : select(this.model);
|
|
1821
|
-
if (filter.orderBy) {
|
|
1822
|
-
query = query.orderBy(filter.orderBy, filter.ascending === false ? "desc" : "asc");
|
|
1823
|
-
}
|
|
1824
|
-
query = query.limit(pageSize).offset((page - 1) * pageSize);
|
|
1825
|
-
const items = await this.session.execute(query).all();
|
|
1826
|
-
const total = await this.count(where);
|
|
1827
|
-
return {
|
|
1828
|
-
items,
|
|
1829
|
-
total,
|
|
1830
|
-
page,
|
|
1831
|
-
pageSize,
|
|
1832
|
-
pages: Math.max(1, Math.ceil(total / pageSize))
|
|
1833
|
-
};
|
|
1834
|
-
}
|
|
1835
|
-
};
|
|
1836
|
-
|
|
1837
|
-
// src/active-record.ts
|
|
1838
|
-
function primaryKeyOf2(model) {
|
|
1839
|
-
for (const [name, col2] of Object.entries(columnsOf(model))) {
|
|
1840
|
-
if (col2.flags.primaryKey) return name;
|
|
1841
|
-
}
|
|
1842
|
-
throw new Error(`${model.tablename} has no primary key`);
|
|
1843
|
-
}
|
|
1844
|
-
var ActiveRecord = class {
|
|
1845
|
-
constructor(model, session, data) {
|
|
1846
|
-
this.model = model;
|
|
1847
|
-
this.session = session;
|
|
1848
|
-
this.data = data;
|
|
1849
|
-
this.pk = primaryKeyOf2(model);
|
|
1850
|
-
}
|
|
1851
|
-
model;
|
|
1852
|
-
session;
|
|
1853
|
-
data;
|
|
1854
|
-
pk;
|
|
1855
|
-
/** The primary-key value of the wrapped row. */
|
|
1856
|
-
pkValue() {
|
|
1857
|
-
return this.data[this.pk];
|
|
1858
|
-
}
|
|
1859
|
-
pkFilter() {
|
|
1860
|
-
return { [this.pk]: this.pkValue() };
|
|
1861
|
-
}
|
|
1862
|
-
/**
|
|
1863
|
-
* Persist the current `data` — insert if new, otherwise overwrite the existing
|
|
1864
|
-
* row (upsert on the primary key). Refreshes `data` from the returned row.
|
|
1865
|
-
*
|
|
1866
|
-
* @returns This wrapper, for chaining.
|
|
1867
|
-
*/
|
|
1868
|
-
async save() {
|
|
1869
|
-
const cols = columnsOf(this.model);
|
|
1870
|
-
const rowData = this.data;
|
|
1871
|
-
const setPatch = {};
|
|
1872
|
-
for (const c of Object.keys(rowData)) {
|
|
1873
|
-
if (c !== this.pk && c in cols) setPatch[c] = rowData[c];
|
|
1874
|
-
}
|
|
1875
|
-
const saved = await this.session.execute(
|
|
1876
|
-
insert(this.model).values(this.data).onConflictDoUpdate(
|
|
1877
|
-
[this.pk],
|
|
1878
|
-
setPatch
|
|
1879
|
-
).returning()
|
|
1880
|
-
).one();
|
|
1881
|
-
this.data = saved;
|
|
1882
|
-
return this;
|
|
1883
|
-
}
|
|
1884
|
-
/**
|
|
1885
|
-
* Update the given columns for this row and merge them into `data`.
|
|
1886
|
-
*
|
|
1887
|
-
* @param patch The columns to change.
|
|
1888
|
-
* @returns This wrapper, for chaining.
|
|
1889
|
-
*/
|
|
1890
|
-
async update(patch) {
|
|
1891
|
-
await this.session.execute(update(this.model).set(patch).where(this.pkFilter()));
|
|
1892
|
-
this.data = { ...this.data, ...patch };
|
|
1893
|
-
return this;
|
|
1894
|
-
}
|
|
1895
|
-
/**
|
|
1896
|
-
* Delete this row.
|
|
1897
|
-
*
|
|
1898
|
-
* @returns The number of rows affected (0 or 1).
|
|
1899
|
-
*/
|
|
1900
|
-
async delete() {
|
|
1901
|
-
return this.session.execute(del(this.model).where(this.pkFilter())).rowsAffected();
|
|
1902
|
-
}
|
|
1903
|
-
/**
|
|
1904
|
-
* Re-fetch this row by primary key and refresh `data`.
|
|
1905
|
-
*
|
|
1906
|
-
* @returns This wrapper, for chaining.
|
|
1907
|
-
* @throws When the row no longer exists.
|
|
1908
|
-
*/
|
|
1909
|
-
async reload() {
|
|
1910
|
-
const fresh = await this.session.execute(select(this.model).where(this.pkFilter())).first();
|
|
1911
|
-
if (fresh === null) {
|
|
1912
|
-
throw new Error(
|
|
1913
|
-
`${this.model.tablename} row ${JSON.stringify(this.pkValue())} not found on reload`
|
|
1914
|
-
);
|
|
1915
|
-
}
|
|
1916
|
-
this.data = fresh;
|
|
1917
|
-
return this;
|
|
1918
|
-
}
|
|
1919
|
-
};
|
|
1920
|
-
function activeRecord(model, session) {
|
|
1921
|
-
const pk = primaryKeyOf2(model);
|
|
1922
|
-
return {
|
|
1923
|
-
wrap: (row) => new ActiveRecord(model, session, row),
|
|
1924
|
-
create: (data) => new ActiveRecord(model, session, data),
|
|
1925
|
-
async get(id) {
|
|
1926
|
-
const row = await session.execute(select(model).where({ [pk]: id })).first();
|
|
1927
|
-
return row === null ? null : new ActiveRecord(model, session, row);
|
|
1928
|
-
}
|
|
1929
|
-
};
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
// src/relations.ts
|
|
1933
|
-
function hasMany(target, keys) {
|
|
1934
|
-
return {
|
|
1935
|
-
kind: "hasMany",
|
|
1936
|
-
target,
|
|
1937
|
-
localKey: keys.localKey,
|
|
1938
|
-
foreignKey: keys.foreignKey
|
|
1939
|
-
};
|
|
1940
|
-
}
|
|
1941
|
-
function belongsTo(target, keys) {
|
|
1942
|
-
return {
|
|
1943
|
-
kind: "belongsTo",
|
|
1944
|
-
target,
|
|
1945
|
-
localKey: keys.localKey,
|
|
1946
|
-
foreignKey: keys.foreignKey
|
|
1947
|
-
};
|
|
1948
|
-
}
|
|
1949
|
-
async function loadRelations(session, rows, spec) {
|
|
1950
|
-
const out = rows.map((r) => ({ ...r }));
|
|
1951
|
-
for (const [name, rel] of Object.entries(spec)) {
|
|
1952
|
-
const target = rel.target();
|
|
1953
|
-
const localValues = [...new Set(rows.map((r) => r[rel.localKey]))];
|
|
1954
|
-
const related = localValues.length > 0 ? await session.execute(
|
|
1955
|
-
select(target).where({
|
|
1956
|
-
[rel.foreignKey]: { in: localValues }
|
|
1957
|
-
})
|
|
1958
|
-
).all() : [];
|
|
1959
|
-
if (rel.kind === "hasMany") {
|
|
1960
|
-
const grouped = /* @__PURE__ */ new Map();
|
|
1961
|
-
for (const row of related) {
|
|
1962
|
-
const key = row[rel.foreignKey];
|
|
1963
|
-
const list = grouped.get(key) ?? [];
|
|
1964
|
-
list.push(row);
|
|
1965
|
-
grouped.set(key, list);
|
|
1966
|
-
}
|
|
1967
|
-
out.forEach((r, i) => {
|
|
1968
|
-
r[name] = grouped.get(rows[i]?.[rel.localKey]) ?? [];
|
|
1969
|
-
});
|
|
1970
|
-
} else {
|
|
1971
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
1972
|
-
for (const row of related) {
|
|
1973
|
-
byKey.set(row[rel.foreignKey], row);
|
|
1974
|
-
}
|
|
1975
|
-
out.forEach((r, i) => {
|
|
1976
|
-
r[name] = byKey.get(rows[i]?.[rel.localKey]) ?? null;
|
|
1977
|
-
});
|
|
1978
|
-
}
|
|
1979
|
-
}
|
|
1980
|
-
return out;
|
|
1981
|
-
}
|
|
1982
|
-
var nodeRequire = createRequire(import.meta.url);
|
|
1983
|
-
function encodeSqliteParam(value) {
|
|
1984
|
-
if (value === void 0 || value === null) return null;
|
|
1985
|
-
if (typeof value === "boolean") return value ? 1 : 0;
|
|
1986
|
-
if (value instanceof Date) return value.toISOString();
|
|
1987
|
-
if (value instanceof Uint8Array) return value;
|
|
1988
|
-
if (typeof value === "object") return JSON.stringify(value);
|
|
1989
|
-
return value;
|
|
1990
|
-
}
|
|
1991
|
-
var NodeSqliteDriver = class _NodeSqliteDriver {
|
|
1992
|
-
// biome-ignore lint/suspicious/noExplicitAny: node:sqlite DatabaseSync has no shipped types here.
|
|
1993
|
-
db;
|
|
1994
|
-
/**
|
|
1995
|
-
* Prepared-statement cache keyed by SQL text. tempest-db-js always
|
|
1996
|
-
* parameterizes, so a query shape maps to one stable SQL string — reusing the
|
|
1997
|
-
* compiled statement avoids re-`prepare()` on every call (the dominant cost of
|
|
1998
|
-
* per-row inserts and point lookups).
|
|
1999
|
-
*/
|
|
2000
|
-
// biome-ignore lint/suspicious/noExplicitAny: node:sqlite StatementSync has no shipped types here.
|
|
2001
|
-
statements = /* @__PURE__ */ new Map();
|
|
2002
|
-
// biome-ignore lint/suspicious/noExplicitAny: accept an already-open DatabaseSync handle.
|
|
2003
|
-
constructor(database) {
|
|
2004
|
-
this.db = database;
|
|
2005
|
-
}
|
|
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) {
|
|
2014
|
-
const { DatabaseSync } = nodeRequire("node:sqlite");
|
|
2015
|
-
return new _NodeSqliteDriver(
|
|
2016
|
-
options ? new DatabaseSync(path, { ...options }) : new DatabaseSync(path)
|
|
2017
|
-
);
|
|
2018
|
-
}
|
|
2019
|
-
/** Return the cached prepared statement for `sql`, preparing it on first use. */
|
|
2020
|
-
// biome-ignore lint/suspicious/noExplicitAny: statement type is unavailable here.
|
|
2021
|
-
prepare(sql2) {
|
|
2022
|
-
const cached = this.statements.get(sql2);
|
|
2023
|
-
if (cached) return cached;
|
|
2024
|
-
const stmt = this.db.prepare(sql2);
|
|
2025
|
-
this.statements.set(sql2, stmt);
|
|
2026
|
-
return stmt;
|
|
2027
|
-
}
|
|
2028
|
-
execute(sql2, params) {
|
|
2029
|
-
const stmt = this.prepare(sql2);
|
|
2030
|
-
const bound = params.map(encodeSqliteParam);
|
|
2031
|
-
if (returnsRows(sql2)) {
|
|
2032
|
-
return { rows: stmt.all(...bound), changes: 0 };
|
|
2033
|
-
}
|
|
2034
|
-
const info = stmt.run(...bound);
|
|
2035
|
-
return { rows: [], changes: Number(info.changes ?? 0) };
|
|
2036
|
-
}
|
|
2037
|
-
*iterate(sql2, params) {
|
|
2038
|
-
const stmt = this.prepare(sql2);
|
|
2039
|
-
const bound = params.map(encodeSqliteParam);
|
|
2040
|
-
yield* stmt.iterate(...bound);
|
|
2041
|
-
}
|
|
2042
|
-
close() {
|
|
2043
|
-
this.statements.clear();
|
|
2044
|
-
this.db.close();
|
|
2045
|
-
}
|
|
2046
|
-
};
|
|
2047
|
-
function returnsRows(sql2) {
|
|
2048
|
-
return /^\s*(select|pragma)/i.test(sql2) || /\breturning\b/i.test(sql2);
|
|
2049
|
-
}
|
|
2050
|
-
function splitJoinRow(node, sources, raw) {
|
|
2051
|
-
const leftAliases = new Set(
|
|
2052
|
-
node.joins.filter((j) => j.kind === "left").map((j) => j.alias)
|
|
2053
|
-
);
|
|
2054
|
-
const out = {};
|
|
2055
|
-
for (const [alias, model] of Object.entries(sources)) {
|
|
2056
|
-
const sub = {};
|
|
2057
|
-
const names = columnNamesOf(model);
|
|
2058
|
-
let allNull = true;
|
|
2059
|
-
for (const colName of Object.keys(columnsOf(model))) {
|
|
2060
|
-
const value = raw[`${alias}.${colName}`];
|
|
2061
|
-
if (value !== null && value !== void 0) allNull = false;
|
|
2062
|
-
sub[names?.[colName] ?? colName] = value;
|
|
2063
|
-
}
|
|
2064
|
-
out[alias] = leftAliases.has(alias) && allNull ? null : coerceRow(model, sub);
|
|
2065
|
-
}
|
|
2066
|
-
return out;
|
|
2067
|
-
}
|
|
2068
|
-
function coerceOne(builder, raw) {
|
|
2069
|
-
const node = builder.node;
|
|
2070
|
-
if (node.kind === "join_select") {
|
|
2071
|
-
const b2 = builder;
|
|
2072
|
-
return splitJoinRow(b2.node, b2.sources, raw);
|
|
2073
|
-
}
|
|
2074
|
-
const b = builder;
|
|
2075
|
-
return coerceRow(b.source, raw);
|
|
2076
|
-
}
|
|
2077
|
-
function mapRows(builder, raw) {
|
|
2078
|
-
return raw.map((r) => coerceOne(builder, r));
|
|
2079
|
-
}
|
|
2080
|
-
var NoResultError = class extends Error {
|
|
2081
|
-
constructor(message) {
|
|
2082
|
-
super(message);
|
|
2083
|
-
this.name = "NoResultError";
|
|
2084
|
-
}
|
|
2085
|
-
};
|
|
2086
|
-
function previewParam(value) {
|
|
2087
|
-
if (value === null || value === void 0) return "null";
|
|
2088
|
-
if (value instanceof Uint8Array) return `<${value.length} bytes>`;
|
|
2089
|
-
const text = typeof value === "string" ? value : String(value);
|
|
2090
|
-
return text.length > 64 ? `${text.slice(0, 61)}...` : text;
|
|
2091
|
-
}
|
|
2092
|
-
var QueryExecutionError = class extends Error {
|
|
2093
|
-
constructor(cause, sql2, params) {
|
|
2094
|
-
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
2095
|
-
super(
|
|
2096
|
-
`Query failed: ${reason}
|
|
2097
|
-
SQL: ${sql2}
|
|
2098
|
-
params: [${params.map(previewParam).join(", ")}]`
|
|
2099
|
-
);
|
|
2100
|
-
this.cause = cause;
|
|
2101
|
-
this.sql = sql2;
|
|
2102
|
-
this.params = params;
|
|
2103
|
-
this.name = "QueryExecutionError";
|
|
2104
|
-
}
|
|
2105
|
-
cause;
|
|
2106
|
-
sql;
|
|
2107
|
-
params;
|
|
2108
|
-
};
|
|
2109
|
-
function emitLog(logger, sql2, params) {
|
|
2110
|
-
if (!logger) return;
|
|
2111
|
-
try {
|
|
2112
|
-
logger({ sql: sql2, params });
|
|
2113
|
-
} catch {
|
|
2114
|
-
}
|
|
2115
|
-
}
|
|
2116
|
-
function firstScalar(row) {
|
|
2117
|
-
if (!row) return null;
|
|
2118
|
-
const keys = Object.keys(row);
|
|
2119
|
-
return keys.length > 0 ? row[keys[0]] : null;
|
|
2120
|
-
}
|
|
2121
|
-
var SyncResult = class {
|
|
2122
|
-
constructor(rows, changes) {
|
|
2123
|
-
this.rows = rows;
|
|
2124
|
-
this.changes = changes;
|
|
2125
|
-
}
|
|
2126
|
-
rows;
|
|
2127
|
-
changes;
|
|
2128
|
-
all() {
|
|
2129
|
-
return this.rows;
|
|
2130
|
-
}
|
|
2131
|
-
first() {
|
|
2132
|
-
return this.rows[0] ?? null;
|
|
2133
|
-
}
|
|
2134
|
-
one() {
|
|
2135
|
-
if (this.rows.length !== 1) {
|
|
2136
|
-
throw new NoResultError(`expected exactly one row, got ${this.rows.length}`);
|
|
2137
|
-
}
|
|
2138
|
-
return this.rows[0];
|
|
2139
|
-
}
|
|
2140
|
-
oneOrNull() {
|
|
2141
|
-
if (this.rows.length > 1) {
|
|
2142
|
-
throw new NoResultError(`expected at most one row, got ${this.rows.length}`);
|
|
2143
|
-
}
|
|
2144
|
-
return this.rows[0] ?? null;
|
|
2145
|
-
}
|
|
2146
|
-
scalar() {
|
|
2147
|
-
return firstScalar(this.rows[0]);
|
|
2148
|
-
}
|
|
2149
|
-
scalars() {
|
|
2150
|
-
return this.rows.map((r) => firstScalar(r));
|
|
2151
|
-
}
|
|
2152
|
-
rowsAffected() {
|
|
2153
|
-
return this.changes;
|
|
2154
|
-
}
|
|
2155
|
-
};
|
|
2156
|
-
var AsyncResult = class {
|
|
2157
|
-
constructor(inner) {
|
|
2158
|
-
this.inner = inner;
|
|
2159
|
-
}
|
|
2160
|
-
inner;
|
|
2161
|
-
async all() {
|
|
2162
|
-
return (await this.inner).all();
|
|
2163
|
-
}
|
|
2164
|
-
async first() {
|
|
2165
|
-
return (await this.inner).first();
|
|
2166
|
-
}
|
|
2167
|
-
async one() {
|
|
2168
|
-
return (await this.inner).one();
|
|
2169
|
-
}
|
|
2170
|
-
async oneOrNull() {
|
|
2171
|
-
return (await this.inner).oneOrNull();
|
|
2172
|
-
}
|
|
2173
|
-
async scalar() {
|
|
2174
|
-
return (await this.inner).scalar();
|
|
2175
|
-
}
|
|
2176
|
-
async scalars() {
|
|
2177
|
-
return (await this.inner).scalars();
|
|
2178
|
-
}
|
|
2179
|
-
async rowsAffected() {
|
|
2180
|
-
return (await this.inner).rowsAffected();
|
|
2181
|
-
}
|
|
2182
|
-
};
|
|
2183
|
-
var savepointCounter = 0;
|
|
2184
|
-
function needsInsertReadBack(dialect, node) {
|
|
2185
|
-
return dialect.name === "mysql" && node.kind === "insert" && node.returning !== null;
|
|
2186
|
-
}
|
|
2187
|
-
function singlePrimaryKey(model) {
|
|
2188
|
-
const keys = Object.entries(columnsOf(model)).filter(([, column2]) => column2.flags.primaryKey).map(([name]) => name);
|
|
2189
|
-
if (keys.length !== 1) {
|
|
2190
|
-
throw new Error(
|
|
2191
|
-
`${model.tablename} needs exactly one primary key to read an insert back on a dialect without RETURNING; found ${keys.length}.`
|
|
2192
|
-
);
|
|
2193
|
-
}
|
|
2194
|
-
return keys[0];
|
|
2195
|
-
}
|
|
2196
|
-
function assertRawParams(params) {
|
|
2197
|
-
if (!Array.isArray(params)) {
|
|
2198
|
-
throw new TypeError(
|
|
2199
|
-
"session.raw(sql, params) takes an array of bound parameters \u2014 never interpolate values into the SQL string."
|
|
2200
|
-
);
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
var SyncSession = class {
|
|
2204
|
-
constructor(driver, dialect, logger) {
|
|
2205
|
-
this.driver = driver;
|
|
2206
|
-
this.dialect = dialect;
|
|
2207
|
-
this.logger = logger;
|
|
2208
|
-
}
|
|
2209
|
-
driver;
|
|
2210
|
-
dialect;
|
|
2211
|
-
logger;
|
|
2212
|
-
/** Log, run, and error-wrap one raw statement. */
|
|
2213
|
-
exec(sql2, params) {
|
|
2214
|
-
emitLog(this.logger, sql2, params);
|
|
2215
|
-
try {
|
|
2216
|
-
return this.driver.execute(sql2, params);
|
|
2217
|
-
} catch (error) {
|
|
2218
|
-
throw new QueryExecutionError(error, sql2, params);
|
|
2219
|
-
}
|
|
2220
|
-
}
|
|
2221
|
-
/**
|
|
2222
|
-
* Run a raw, parameterized SQL statement (synchronous) — the runtime counterpart of the
|
|
2223
|
-
* migrations' `Op.execute`.
|
|
2224
|
-
*
|
|
2225
|
-
* A query builder never covers all of SQL, and without an escape hatch a single
|
|
2226
|
-
* unsupported query forces a whole second database stack alongside this one. Use
|
|
2227
|
-
* it for what the builder cannot yet express, and keep everything else typed.
|
|
2228
|
-
*
|
|
2229
|
-
* The statement goes through the same path as a compiled one: it is logged via
|
|
2230
|
-
* `onQuery`, wrapped in {@link QueryExecutionError} on failure, and runs on the
|
|
2231
|
-
* reserved connection inside `transaction()`.
|
|
2232
|
-
*
|
|
2233
|
-
* @param sql The statement text. Placeholders only (`$1` / `?` per dialect) —
|
|
2234
|
-
* never interpolate a value into this string.
|
|
2235
|
-
* @param params The bound parameters, in placeholder order.
|
|
2236
|
-
* @param options Pass `as` to coerce the returned rows with a model's column
|
|
2237
|
-
* types (and its column-name mapping).
|
|
2238
|
-
* @returns The result view over the returned rows.
|
|
2239
|
-
* @throws Error When `params` is not an array — the guard against calling this
|
|
2240
|
-
* with an interpolated string and no parameters by mistake.
|
|
2241
|
-
*
|
|
2242
|
-
* @example
|
|
2243
|
-
* ```ts
|
|
2244
|
-
* const claimed = await session.raw<OutboundRow>(
|
|
2245
|
-
* `UPDATE outbound_messages SET status = 'sending'
|
|
2246
|
-
* WHERE id = ANY($1) RETURNING *`,
|
|
2247
|
-
* [ids],
|
|
2248
|
-
* { as: Outbound },
|
|
2249
|
-
* ).all();
|
|
2250
|
-
* ```
|
|
2251
|
-
*/
|
|
2252
|
-
raw(sql2, params = [], options) {
|
|
2253
|
-
assertRawParams(params);
|
|
2254
|
-
const result = this.exec(sql2, params);
|
|
2255
|
-
const model = options?.as;
|
|
2256
|
-
const rows = model ? result.rows.map((row) => coerceRow(model, row)) : result.rows;
|
|
2257
|
-
return new SyncResult(rows, result.changes);
|
|
2258
|
-
}
|
|
2259
|
-
/** Compile, run, and coerce a builder into a result. */
|
|
2260
|
-
execute(builder) {
|
|
2261
|
-
const node = builder.node;
|
|
2262
|
-
const { sql: sql2, params } = this.dialect.compile(node);
|
|
2263
|
-
const result = this.exec(sql2, params);
|
|
2264
|
-
const rows = mapRows(builder, result.rows);
|
|
2265
|
-
return new SyncResult(rows, result.changes);
|
|
2266
|
-
}
|
|
2267
|
-
/** Run `fn` inside a transaction: commit on success, rollback on throw. */
|
|
2268
|
-
transaction(fn2) {
|
|
2269
|
-
this.exec("BEGIN", []);
|
|
2270
|
-
try {
|
|
2271
|
-
const out = fn2(this);
|
|
2272
|
-
this.exec("COMMIT", []);
|
|
2273
|
-
return out;
|
|
2274
|
-
} catch (error) {
|
|
2275
|
-
this.exec("ROLLBACK", []);
|
|
2276
|
-
throw error;
|
|
2277
|
-
}
|
|
2278
|
-
}
|
|
2279
|
-
/** Run `fn` inside a SAVEPOINT (nested transaction). */
|
|
2280
|
-
beginNested(fn2) {
|
|
2281
|
-
savepointCounter += 1;
|
|
2282
|
-
const name = `qsp_${savepointCounter}`;
|
|
2283
|
-
this.exec(`SAVEPOINT ${name}`, []);
|
|
2284
|
-
try {
|
|
2285
|
-
const out = fn2(this);
|
|
2286
|
-
this.exec(`RELEASE ${name}`, []);
|
|
2287
|
-
return out;
|
|
2288
|
-
} catch (error) {
|
|
2289
|
-
this.exec(`ROLLBACK TO ${name}`, []);
|
|
2290
|
-
throw error;
|
|
2291
|
-
}
|
|
2292
|
-
}
|
|
2293
|
-
/**
|
|
2294
|
-
* Lazily iterate result rows without materializing them all. Falls back to a
|
|
2295
|
-
* buffered fetch when the driver has no native iteration.
|
|
2296
|
-
*/
|
|
2297
|
-
*stream(builder) {
|
|
2298
|
-
const node = builder.node;
|
|
2299
|
-
const { sql: sql2, params } = this.dialect.compile(node);
|
|
2300
|
-
emitLog(this.logger, sql2, params);
|
|
2301
|
-
if (this.driver.iterate) {
|
|
2302
|
-
try {
|
|
2303
|
-
for (const raw of this.driver.iterate(sql2, params)) {
|
|
2304
|
-
yield coerceOne(builder, raw);
|
|
2305
|
-
}
|
|
2306
|
-
} catch (error) {
|
|
2307
|
-
throw new QueryExecutionError(error, sql2, params);
|
|
2308
|
-
}
|
|
2309
|
-
return;
|
|
2310
|
-
}
|
|
2311
|
-
for (const raw of this.exec(sql2, params).rows) {
|
|
2312
|
-
yield coerceOne(builder, raw);
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
close() {
|
|
2316
|
-
this.driver.close();
|
|
2317
|
-
}
|
|
2318
|
-
/** `using session = ...` closes the driver when the scope exits. */
|
|
2319
|
-
[Symbol.dispose]() {
|
|
2320
|
-
this.close();
|
|
2321
|
-
}
|
|
2322
|
-
};
|
|
2323
|
-
var AsyncSession = class _AsyncSession {
|
|
2324
|
-
constructor(driver, dialect, logger) {
|
|
2325
|
-
this.driver = driver;
|
|
2326
|
-
this.dialect = dialect;
|
|
2327
|
-
this.logger = logger;
|
|
2328
|
-
}
|
|
2329
|
-
driver;
|
|
2330
|
-
dialect;
|
|
2331
|
-
logger;
|
|
2332
|
-
/** Log, run, and error-wrap one raw statement. */
|
|
2333
|
-
async exec(sql2, params) {
|
|
2334
|
-
emitLog(this.logger, sql2, params);
|
|
2335
|
-
try {
|
|
2336
|
-
return await this.driver.execute(sql2, params);
|
|
2337
|
-
} catch (error) {
|
|
2338
|
-
throw new QueryExecutionError(error, sql2, params);
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2341
|
-
/**
|
|
2342
|
-
* Run a raw, parameterized SQL statement — the runtime counterpart of the
|
|
2343
|
-
* migrations' `Op.execute`.
|
|
2344
|
-
*
|
|
2345
|
-
* A query builder never covers all of SQL, and without an escape hatch a single
|
|
2346
|
-
* unsupported query forces a whole second database stack alongside this one. Use
|
|
2347
|
-
* it for what the builder cannot yet express, and keep everything else typed.
|
|
2348
|
-
*
|
|
2349
|
-
* The statement goes through the same path as a compiled one: it is logged via
|
|
2350
|
-
* `onQuery`, wrapped in {@link QueryExecutionError} on failure, and runs on the
|
|
2351
|
-
* reserved connection inside `transaction()`.
|
|
2352
|
-
*
|
|
2353
|
-
* @param sql The statement text. Placeholders only (`$1` / `?` per dialect) —
|
|
2354
|
-
* never interpolate a value into this string.
|
|
2355
|
-
* @param params The bound parameters, in placeholder order.
|
|
2356
|
-
* @param options Pass `as` to coerce the returned rows with a model's column
|
|
2357
|
-
* types (and its column-name mapping).
|
|
2358
|
-
* @returns The result view over the returned rows.
|
|
2359
|
-
* @throws Error When `params` is not an array — the guard against calling this
|
|
2360
|
-
* with an interpolated string and no parameters by mistake.
|
|
2361
|
-
*
|
|
2362
|
-
* @example
|
|
2363
|
-
* ```ts
|
|
2364
|
-
* const claimed = await session.raw<OutboundRow>(
|
|
2365
|
-
* `UPDATE outbound_messages SET status = 'sending'
|
|
2366
|
-
* WHERE id = ANY($1) RETURNING *`,
|
|
2367
|
-
* [ids],
|
|
2368
|
-
* { as: Outbound },
|
|
2369
|
-
* ).all();
|
|
2370
|
-
* ```
|
|
2371
|
-
*/
|
|
2372
|
-
raw(sql2, params = [], options) {
|
|
2373
|
-
assertRawParams(params);
|
|
2374
|
-
const model = options?.as;
|
|
2375
|
-
const inner = this.exec(sql2, params).then((result) => {
|
|
2376
|
-
const rows = model ? result.rows.map((row) => coerceRow(model, row)) : result.rows;
|
|
2377
|
-
return new SyncResult(rows, result.changes);
|
|
2378
|
-
});
|
|
2379
|
-
return new AsyncResult(inner);
|
|
2380
|
-
}
|
|
2381
|
-
execute(builder) {
|
|
2382
|
-
const node = builder.node;
|
|
2383
|
-
if (needsInsertReadBack(this.dialect, node)) {
|
|
2384
|
-
return new AsyncResult(
|
|
2385
|
-
this.insertAndReadBack(builder, node)
|
|
2386
|
-
);
|
|
2387
|
-
}
|
|
2388
|
-
const { sql: sql2, params } = this.dialect.compile(node);
|
|
2389
|
-
const inner = this.exec(sql2, params).then((result) => {
|
|
2390
|
-
const rows = mapRows(builder, result.rows);
|
|
2391
|
-
return new SyncResult(rows, result.changes);
|
|
2392
|
-
});
|
|
2393
|
-
return new AsyncResult(inner);
|
|
2394
|
-
}
|
|
2395
|
-
/**
|
|
2396
|
-
* Honor `.returning()` on a dialect without `RETURNING`, by inserting and then
|
|
2397
|
-
* reading the row back by key.
|
|
2398
|
-
*
|
|
2399
|
-
* Both statements must run on **one** connection, because `LAST_INSERT_ID()` is
|
|
2400
|
-
* per-connection: outside a transaction the pooled driver is reserved for the
|
|
2401
|
-
* pair; inside one, the session already holds a pinned connection (a reserved
|
|
2402
|
-
* driver exposes no `reserve`), so it runs there directly.
|
|
2403
|
-
*
|
|
2404
|
-
* @param builder The insert builder, for its source model.
|
|
2405
|
-
* @param node The insert AST, whose `returning` drives the read-back.
|
|
2406
|
-
* @returns The result view over the read-back row.
|
|
2407
|
-
* @throws Error When the insert writes more than one row — `LAST_INSERT_ID()`
|
|
2408
|
-
* identifies only the first, and the rest are consecutive only under some
|
|
2409
|
-
* auto-increment lock modes.
|
|
2410
|
-
*/
|
|
2411
|
-
async insertAndReadBack(builder, node) {
|
|
2412
|
-
if (node.values.length !== 1) {
|
|
2413
|
-
throw new Error(
|
|
2414
|
-
`${this.dialect.name} has no RETURNING, and reading back a multi-row insert is not reliable \u2014 insert one row at a time, or drop .returning().`
|
|
2415
|
-
);
|
|
2416
|
-
}
|
|
2417
|
-
const model = builder.source;
|
|
2418
|
-
const pk = singlePrimaryKey(model);
|
|
2419
|
-
const supplied = node.values[0][pk];
|
|
2420
|
-
const readBack = select(model).where(
|
|
2421
|
-
supplied === void 0 || supplied === null ? col(pk).eq(fn.call("LAST_INSERT_ID")) : { [pk]: supplied }
|
|
2422
|
-
);
|
|
2423
|
-
const insertSql = this.dialect.compile({ ...node, returning: null });
|
|
2424
|
-
const selectSql = this.dialect.compile(
|
|
2425
|
-
node.returning === "*" || node.returning === null ? readBack.node : { ...readBack.node, columns: node.returning }
|
|
2426
|
-
);
|
|
2427
|
-
const run = async (driver) => {
|
|
2428
|
-
const scoped = new _AsyncSession(driver, this.dialect, this.logger);
|
|
2429
|
-
const written = await scoped.exec(insertSql.sql, insertSql.params);
|
|
2430
|
-
const read = await scoped.exec(selectSql.sql, selectSql.params);
|
|
2431
|
-
const rows = read.rows.map((row) => coerceRow(model, row));
|
|
2432
|
-
return new SyncResult(rows, written.changes);
|
|
2433
|
-
};
|
|
2434
|
-
if (!this.driver.reserve) return run(this.driver);
|
|
2435
|
-
const reserved = await this.driver.reserve();
|
|
2436
|
-
try {
|
|
2437
|
-
return await run(reserved);
|
|
2438
|
-
} finally {
|
|
2439
|
-
await reserved.release();
|
|
2440
|
-
}
|
|
2441
|
-
}
|
|
2442
|
-
/** Lazily iterate result rows. Uses driver streaming when available. */
|
|
2443
|
-
async *stream(builder) {
|
|
2444
|
-
const node = builder.node;
|
|
2445
|
-
const { sql: sql2, params } = this.dialect.compile(node);
|
|
2446
|
-
emitLog(this.logger, sql2, params);
|
|
2447
|
-
if (this.driver.iterate) {
|
|
2448
|
-
try {
|
|
2449
|
-
for await (const raw of this.driver.iterate(sql2, params)) {
|
|
2450
|
-
yield coerceOne(builder, raw);
|
|
2451
|
-
}
|
|
2452
|
-
} catch (error) {
|
|
2453
|
-
throw new QueryExecutionError(error, sql2, params);
|
|
2454
|
-
}
|
|
2455
|
-
return;
|
|
2456
|
-
}
|
|
2457
|
-
const result = await this.exec(sql2, params);
|
|
2458
|
-
for (const raw of result.rows) {
|
|
2459
|
-
yield coerceOne(builder, raw);
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
|
-
async transaction(fn2) {
|
|
2463
|
-
if (this.driver.reserve) {
|
|
2464
|
-
const reserved = await this.driver.reserve();
|
|
2465
|
-
const scoped = new _AsyncSession(reserved, this.dialect, this.logger);
|
|
2466
|
-
try {
|
|
2467
|
-
await scoped.exec("BEGIN", []);
|
|
2468
|
-
const out = await fn2(scoped);
|
|
2469
|
-
await scoped.exec("COMMIT", []);
|
|
2470
|
-
return out;
|
|
2471
|
-
} catch (error) {
|
|
2472
|
-
await scoped.exec("ROLLBACK", []);
|
|
2473
|
-
throw error;
|
|
2474
|
-
} finally {
|
|
2475
|
-
await reserved.release();
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
await this.exec("BEGIN", []);
|
|
2479
|
-
try {
|
|
2480
|
-
const out = await fn2(this);
|
|
2481
|
-
await this.exec("COMMIT", []);
|
|
2482
|
-
return out;
|
|
2483
|
-
} catch (error) {
|
|
2484
|
-
await this.exec("ROLLBACK", []);
|
|
2485
|
-
throw error;
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
async close() {
|
|
2489
|
-
await this.driver.close();
|
|
2490
|
-
}
|
|
2491
|
-
/** `await using session = ...` closes the driver when the scope exits. */
|
|
2492
|
-
async [Symbol.asyncDispose]() {
|
|
2493
|
-
await this.close();
|
|
2494
|
-
}
|
|
2495
|
-
};
|
|
2496
|
-
function emitNotice(logger, notice) {
|
|
2497
|
-
if (!logger) return;
|
|
2498
|
-
try {
|
|
2499
|
-
logger(notice);
|
|
2500
|
-
} catch {
|
|
2501
|
-
}
|
|
2502
|
-
}
|
|
2503
|
-
var SyncEngine = class {
|
|
2504
|
-
constructor(driver, logger) {
|
|
2505
|
-
this.driver = driver;
|
|
2506
|
-
this.logger = logger;
|
|
2507
|
-
}
|
|
2508
|
-
driver;
|
|
2509
|
-
logger;
|
|
2510
|
-
dialect = "sqlite";
|
|
2511
|
-
session() {
|
|
2512
|
-
return new SyncSession(this.driver, getDialect("sqlite"), this.logger);
|
|
2513
|
-
}
|
|
2514
|
-
transaction(fn2) {
|
|
2515
|
-
return this.session().transaction(fn2);
|
|
2516
|
-
}
|
|
2517
|
-
close() {
|
|
2518
|
-
this.driver.close();
|
|
2519
|
-
}
|
|
2520
|
-
/** `using engine = createSyncEngine(...)` closes the pool when the scope exits. */
|
|
2521
|
-
[Symbol.dispose]() {
|
|
2522
|
-
this.close();
|
|
2523
|
-
}
|
|
2524
|
-
};
|
|
2525
|
-
var AsyncEngine = class {
|
|
2526
|
-
constructor(driver, dialect, logger) {
|
|
2527
|
-
this.driver = driver;
|
|
2528
|
-
this.dialect = dialect;
|
|
2529
|
-
this.logger = logger;
|
|
2530
|
-
}
|
|
2531
|
-
driver;
|
|
2532
|
-
dialect;
|
|
2533
|
-
logger;
|
|
2534
|
-
session() {
|
|
2535
|
-
return new AsyncSession(this.driver, getDialect(this.dialect), this.logger);
|
|
2536
|
-
}
|
|
2537
|
-
transaction(fn2) {
|
|
2538
|
-
return this.session().transaction(fn2);
|
|
2539
|
-
}
|
|
2540
|
-
async close() {
|
|
2541
|
-
await this.driver.close();
|
|
2542
|
-
}
|
|
2543
|
-
/** `await using engine = createEngine(...)` closes the pool when the scope exits. */
|
|
2544
|
-
async [Symbol.asyncDispose]() {
|
|
2545
|
-
await this.close();
|
|
2546
|
-
}
|
|
2547
|
-
};
|
|
2548
|
-
function toAsyncDriver(driver) {
|
|
2549
|
-
return {
|
|
2550
|
-
async execute(sql2, params) {
|
|
2551
|
-
return await driver.execute(sql2, params);
|
|
2552
|
-
},
|
|
2553
|
-
async close() {
|
|
2554
|
-
await driver.close();
|
|
2555
|
-
}
|
|
2556
|
-
};
|
|
2557
|
-
}
|
|
2558
|
-
function asAsync(driver) {
|
|
2559
|
-
const syncIterate = driver.iterate?.bind(driver);
|
|
2560
|
-
return {
|
|
2561
|
-
execute: (sql2, params) => Promise.resolve(driver.execute(sql2, params)),
|
|
2562
|
-
close: () => Promise.resolve(driver.close()),
|
|
2563
|
-
...syncIterate ? {
|
|
2564
|
-
iterate: async function* (sql2, params) {
|
|
2565
|
-
yield* syncIterate(sql2, params);
|
|
2566
|
-
}
|
|
2567
|
-
} : {}
|
|
2568
|
-
};
|
|
2569
|
-
}
|
|
2570
|
-
function openSqliteDriver(path, options) {
|
|
2571
|
-
return NodeSqliteDriver.open(path, options?.driverOptions);
|
|
2572
|
-
}
|
|
2573
|
-
function createSyncEngine(url, options) {
|
|
2574
|
-
const parsed = parseDatabaseUrl(url);
|
|
2575
|
-
if (parsed.dialect !== "sqlite") {
|
|
2576
|
-
throw new Error(
|
|
2577
|
-
`createSyncEngine supports only SQLite; ${parsed.dialect} is async-only \u2014 use createEngine.`
|
|
2578
|
-
);
|
|
2579
|
-
}
|
|
2580
|
-
return new SyncEngine(
|
|
2581
|
-
openSqliteDriver(parsed.database ?? ":memory:", options),
|
|
2582
|
-
options?.onQuery
|
|
2583
|
-
);
|
|
2584
|
-
}
|
|
2585
|
-
function createEngine(url, options) {
|
|
2586
|
-
const parsed = parseDatabaseUrl(url);
|
|
2587
|
-
if (parsed.dialect === "sqlite") {
|
|
2588
|
-
return new AsyncEngine(
|
|
2589
|
-
asAsync(openSqliteDriver(parsed.database ?? ":memory:", options)),
|
|
2590
|
-
"sqlite",
|
|
2591
|
-
options?.onQuery
|
|
2592
|
-
);
|
|
2593
|
-
}
|
|
2594
|
-
if (parsed.dialect === "mysql") {
|
|
2595
|
-
return new AsyncEngine(
|
|
2596
|
-
createMysqlDriver(parsed.raw, options),
|
|
2597
|
-
"mysql",
|
|
2598
|
-
options?.onQuery
|
|
2599
|
-
);
|
|
2600
|
-
}
|
|
2601
|
-
return new AsyncEngine(
|
|
2602
|
-
createPostgresDriver(parsed.raw, options),
|
|
2603
|
-
"postgresql",
|
|
2604
|
-
options?.onQuery
|
|
2605
|
-
);
|
|
2606
|
-
}
|
|
2607
|
-
function encodeMysqlParam(value) {
|
|
2608
|
-
if (value === void 0 || value === null) return null;
|
|
2609
|
-
if (typeof value === "boolean") return value ? 1 : 0;
|
|
2610
|
-
if (value instanceof Uint8Array) return value;
|
|
2611
|
-
if (value instanceof Date) return value;
|
|
2612
|
-
if (typeof value === "object") return JSON.stringify(value);
|
|
2613
|
-
return value;
|
|
2614
|
-
}
|
|
2615
|
-
function toMysqlResult(rows) {
|
|
2616
|
-
if (Array.isArray(rows)) {
|
|
2617
|
-
return { rows, changes: 0 };
|
|
2618
|
-
}
|
|
2619
|
-
const header = rows;
|
|
2620
|
-
return { rows: [], changes: header.affectedRows ?? 0 };
|
|
2621
|
-
}
|
|
2622
|
-
function createMysqlDriver(url, options) {
|
|
2623
|
-
const pool = options?.pool;
|
|
2624
|
-
let poolHandle;
|
|
2625
|
-
const ensure = async () => {
|
|
2626
|
-
if (poolHandle) return;
|
|
2627
|
-
const moduleName = "mysql2/promise";
|
|
2628
|
-
const mod = await import(
|
|
2629
|
-
/* @vite-ignore */
|
|
2630
|
-
moduleName
|
|
2631
|
-
);
|
|
2632
|
-
const opts = { uri: url };
|
|
2633
|
-
if (pool?.size !== void 0) opts.connectionLimit = pool.size;
|
|
2634
|
-
if (pool?.idleTimeoutMs !== void 0) opts.idleTimeout = pool.idleTimeoutMs;
|
|
2635
|
-
if (pool?.connectTimeoutMs !== void 0) opts.connectTimeout = pool.connectTimeoutMs;
|
|
2636
|
-
Object.assign(opts, options?.driverOptions ?? {});
|
|
2637
|
-
poolHandle = mod.createPool(opts);
|
|
2638
|
-
};
|
|
2639
|
-
const runOn = async (queryable, sql2, params) => {
|
|
2640
|
-
const [rows] = await queryable.query(sql2, params.map(encodeMysqlParam));
|
|
2641
|
-
return toMysqlResult(rows);
|
|
2642
|
-
};
|
|
2643
|
-
return {
|
|
2644
|
-
async execute(sql2, params) {
|
|
2645
|
-
await ensure();
|
|
2646
|
-
return runOn(poolHandle, sql2, params);
|
|
2647
|
-
},
|
|
2648
|
-
async reserve() {
|
|
2649
|
-
await ensure();
|
|
2650
|
-
const conn = await poolHandle.getConnection();
|
|
2651
|
-
return {
|
|
2652
|
-
execute: (sql2, params) => runOn(conn, sql2, params),
|
|
2653
|
-
async release() {
|
|
2654
|
-
conn.release();
|
|
2655
|
-
},
|
|
2656
|
-
async close() {
|
|
2657
|
-
conn.release();
|
|
2658
|
-
}
|
|
2659
|
-
};
|
|
2660
|
-
},
|
|
2661
|
-
async close() {
|
|
2662
|
-
if (poolHandle) await poolHandle.end();
|
|
2663
|
-
}
|
|
2664
|
-
};
|
|
2665
|
-
}
|
|
2666
|
-
function toPostgresResult(rows) {
|
|
2667
|
-
const arr = rows;
|
|
2668
|
-
return { rows: Array.from(arr), changes: arr.count ?? arr.length };
|
|
2669
|
-
}
|
|
2670
|
-
function createPostgresDriver(url, options) {
|
|
2671
|
-
const pool = options?.pool;
|
|
2672
|
-
let client;
|
|
2673
|
-
const ensure = async () => {
|
|
2674
|
-
if (client) return;
|
|
2675
|
-
const moduleName = "postgres";
|
|
2676
|
-
const mod = await import(
|
|
2677
|
-
/* @vite-ignore */
|
|
2678
|
-
moduleName
|
|
2679
|
-
);
|
|
2680
|
-
const opts = {};
|
|
2681
|
-
if (pool?.size !== void 0) opts.max = pool.size;
|
|
2682
|
-
if (pool?.idleTimeoutMs !== void 0)
|
|
2683
|
-
opts.idle_timeout = Math.ceil(pool.idleTimeoutMs / 1e3);
|
|
2684
|
-
if (pool?.connectTimeoutMs !== void 0) {
|
|
2685
|
-
opts.connect_timeout = Math.ceil(pool.connectTimeoutMs / 1e3);
|
|
2686
|
-
}
|
|
2687
|
-
opts.onnotice = (notice) => emitNotice(options?.onNotice, notice);
|
|
2688
|
-
Object.assign(opts, options?.driverOptions ?? {});
|
|
2689
|
-
client = (mod.default ?? mod)(url, opts);
|
|
2690
|
-
};
|
|
2691
|
-
return {
|
|
2692
|
-
async execute(sql2, params) {
|
|
2693
|
-
await ensure();
|
|
2694
|
-
return toPostgresResult(await client.unsafe(sql2, params));
|
|
2695
|
-
},
|
|
2696
|
-
async reserve() {
|
|
2697
|
-
await ensure();
|
|
2698
|
-
const conn = await client.reserve();
|
|
2699
|
-
return {
|
|
2700
|
-
async execute(sql2, params) {
|
|
2701
|
-
return toPostgresResult(await conn.unsafe(sql2, params));
|
|
2702
|
-
},
|
|
2703
|
-
async release() {
|
|
2704
|
-
conn.release();
|
|
2705
|
-
},
|
|
2706
|
-
async close() {
|
|
2707
|
-
conn.release();
|
|
2708
|
-
}
|
|
2709
|
-
};
|
|
2710
|
-
},
|
|
2711
|
-
async close() {
|
|
2712
|
-
if (client) await client.end();
|
|
2713
|
-
}
|
|
2714
|
-
};
|
|
2715
|
-
}
|
|
2716
|
-
|
|
2717
|
-
// src/index.ts
|
|
2718
|
-
var DEFAULT_FLAGS = {
|
|
2719
|
-
primaryKey: false,
|
|
2720
|
-
notNull: false,
|
|
2721
|
-
hasDefault: false,
|
|
2722
|
-
unique: false
|
|
2723
|
-
};
|
|
2724
|
-
var EXPRESSION = /* @__PURE__ */ Symbol.for("tempest-db-js.expression");
|
|
2725
|
-
function expression(token, params = []) {
|
|
2726
|
-
return { [EXPRESSION]: true, kind: "expression", expression: token, params };
|
|
2727
|
-
}
|
|
2728
|
-
function isSqlExpression(value) {
|
|
2729
|
-
return typeof value === "object" && value !== null && value[EXPRESSION] === true;
|
|
2730
|
-
}
|
|
2731
|
-
var sql = {
|
|
2732
|
-
/** Current timestamp at insert (`CURRENT_TIMESTAMP` / `now()`). */
|
|
2733
|
-
now: () => expression("now"),
|
|
2734
|
-
/** Current date. */
|
|
2735
|
-
currentDate: () => expression("current_date"),
|
|
2736
|
-
/** Current time. */
|
|
2737
|
-
currentTime: () => expression("current_time"),
|
|
2738
|
-
/** A freshly generated UUID v4 (`gen_random_uuid()` / portable fallback). */
|
|
2739
|
-
uuidv4: () => expression("uuidv4"),
|
|
2740
|
-
/**
|
|
2741
|
-
* Escape hatch: a verbatim SQL expression rendered as-is, with no parameters.
|
|
2742
|
-
*
|
|
2743
|
-
* The fragment is interpolated into the statement untouched, so it must never
|
|
2744
|
-
* carry user input — use {@link sql.expr} when a value has to be bound.
|
|
2745
|
-
*
|
|
2746
|
-
* @param fragment The SQL text (e.g. `"attempts + 1"`).
|
|
2747
|
-
* @returns The expression, usable as a default and as a write value.
|
|
2748
|
-
*/
|
|
2749
|
-
raw: (fragment) => expression({ raw: fragment }),
|
|
2750
|
-
/**
|
|
2751
|
-
* A parameterized SQL expression, written as a tagged template. Static text is
|
|
2752
|
-
* SQL; every `${...}` interpolation becomes a bound parameter, so the fragment
|
|
2753
|
-
* is injection-safe by construction.
|
|
2754
|
-
*
|
|
2755
|
-
* Cannot be used as a column default — a `DEFAULT` clause has nowhere to bind
|
|
2756
|
-
* parameters; use {@link sql.raw} there.
|
|
2757
|
-
*
|
|
2758
|
-
* @param parts The static SQL segments supplied by the template tag.
|
|
2759
|
-
* @param values The interpolated values, bound in order.
|
|
2760
|
-
* @returns The expression, usable as a write value.
|
|
2761
|
-
*
|
|
2762
|
-
* @example
|
|
2763
|
-
* ```ts
|
|
2764
|
-
* update(Account).set({ balance: sql.expr`balance - ${amount}` }).where({ id });
|
|
2765
|
-
* // UPDATE "accounts" SET "balance" = balance - $1 WHERE "id" = $2
|
|
2766
|
-
* ```
|
|
2767
|
-
*/
|
|
2768
|
-
expr: (parts, ...values) => expression({ parts: Array.from(parts) }, values)
|
|
2769
|
-
};
|
|
2770
|
-
function isDefaultValue(value) {
|
|
2771
|
-
return typeof value === "object" && value !== null && "kind" in value && (value.kind === "literal" || value.kind === "expression");
|
|
2772
|
-
}
|
|
2773
|
-
function bindsParameters(value) {
|
|
2774
|
-
return value.kind === "expression" && typeof value.expression === "object" && "parts" in value.expression;
|
|
2775
|
-
}
|
|
2776
|
-
function parseReference(ref, options) {
|
|
2777
|
-
const dot = ref.lastIndexOf(".");
|
|
2778
|
-
if (dot <= 0 || dot === ref.length - 1) {
|
|
2779
|
-
throw new Error(`Invalid foreign key reference "${ref}"; expected "table.column".`);
|
|
2780
|
-
}
|
|
2781
|
-
return {
|
|
2782
|
-
table: ref.slice(0, dot),
|
|
2783
|
-
column: ref.slice(dot + 1),
|
|
2784
|
-
onDelete: options?.onDelete,
|
|
2785
|
-
onUpdate: options?.onUpdate
|
|
2786
|
-
};
|
|
2787
|
-
}
|
|
2788
|
-
var Column = class _Column {
|
|
2789
|
-
constructor(type, flags, defaultValue = null, onUpdateValue = null, reference = null, dbName = null) {
|
|
2790
|
-
this.type = type;
|
|
2791
|
-
this.flags = flags;
|
|
2792
|
-
this.defaultValue = defaultValue;
|
|
2793
|
-
this.onUpdateValue = onUpdateValue;
|
|
2794
|
-
this.reference = reference;
|
|
2795
|
-
this.dbName = dbName;
|
|
2796
|
-
}
|
|
2797
|
-
type;
|
|
2798
|
-
flags;
|
|
2799
|
-
defaultValue;
|
|
2800
|
-
onUpdateValue;
|
|
2801
|
-
reference;
|
|
2802
|
-
dbName;
|
|
2803
|
-
/** Clone this column with one facet replaced, carrying every other over. */
|
|
2804
|
-
derive(patch) {
|
|
2805
|
-
return new _Column(
|
|
2806
|
-
this.type,
|
|
2807
|
-
patch.flags ?? this.flags,
|
|
2808
|
-
patch.defaultValue !== void 0 ? patch.defaultValue : this.defaultValue,
|
|
2809
|
-
patch.onUpdateValue !== void 0 ? patch.onUpdateValue : this.onUpdateValue,
|
|
2810
|
-
patch.reference !== void 0 ? patch.reference : this.reference,
|
|
2811
|
-
patch.dbName !== void 0 ? patch.dbName : this.dbName
|
|
2812
|
-
);
|
|
2813
|
-
}
|
|
2814
|
-
primaryKey() {
|
|
2815
|
-
return this.derive({ flags: { ...this.flags, primaryKey: true, hasDefault: true } });
|
|
2816
|
-
}
|
|
2817
|
-
notNull() {
|
|
2818
|
-
return this.derive({ flags: { ...this.flags, notNull: true } });
|
|
2819
|
-
}
|
|
2820
|
-
/**
|
|
2821
|
-
* Add a `UNIQUE` constraint to the column (mirrors SQLAlchemy's
|
|
2822
|
-
* `mapped_column(unique=True)`). DDL-only — does not change the inferred type.
|
|
2823
|
-
*/
|
|
2824
|
-
unique() {
|
|
2825
|
-
return this.derive({ flags: { ...this.flags, unique: true } });
|
|
2826
|
-
}
|
|
2827
|
-
/**
|
|
2828
|
-
* Map this property to a differently-named database column, à la SQLAlchemy's
|
|
2829
|
-
* `mapped_column("consumer_name")` (Django's `db_column`, Prisma's `@map`).
|
|
2830
|
-
*
|
|
2831
|
-
* The override applies everywhere the name reaches SQL — select, insert,
|
|
2832
|
-
* update, delete, where, order by, group by, returning, conflict targets, the
|
|
2833
|
-
* migration IR and the drift check — while the TypeScript row keeps the
|
|
2834
|
-
* property name. Use it to keep a `snake_case` schema behind a `camelCase`
|
|
2835
|
-
* model; {@link Model.naming} does the same for a whole table at once.
|
|
2836
|
-
*
|
|
2837
|
-
* @param dbName The real column name in the database.
|
|
2838
|
-
* @returns A new column bound to that name.
|
|
2839
|
-
* @throws Error When `dbName` is empty.
|
|
2840
|
-
*
|
|
2841
|
-
* @example
|
|
2842
|
-
* ```ts
|
|
2843
|
-
* class ApiKey extends Model {
|
|
2844
|
-
* static tablename = "api_keys";
|
|
2845
|
-
* consumerName = column.text().name("consumer_name").notNull();
|
|
2846
|
-
* }
|
|
2847
|
-
* ```
|
|
2848
|
-
*/
|
|
2849
|
-
name(dbName) {
|
|
2850
|
-
if (dbName.length === 0) {
|
|
2851
|
-
throw new Error("column.name() requires a non-empty database column name.");
|
|
2852
|
-
}
|
|
2853
|
-
return this.derive({ dbName });
|
|
2854
|
-
}
|
|
2855
|
-
/**
|
|
2856
|
-
* Declare a foreign-key reference to another table's column, à la SQLAlchemy's
|
|
2857
|
-
* `mapped_column(ForeignKey("table.column", ondelete=...))`. DDL-only — does
|
|
2858
|
-
* not change the inferred type.
|
|
2859
|
-
*
|
|
2860
|
-
* @param ref The target as `"table.column"` (e.g. `"users.id"`).
|
|
2861
|
-
* @param options Optional `onDelete` / `onUpdate` referential actions.
|
|
2862
|
-
* @returns A new column carrying the reference.
|
|
2863
|
-
* @throws Error When `ref` is not a valid `"table.column"` string.
|
|
2864
|
-
*/
|
|
2865
|
-
references(ref, options) {
|
|
2866
|
-
return this.derive({ reference: parseReference(ref, options) });
|
|
2867
|
-
}
|
|
2868
|
-
/**
|
|
2869
|
-
* Set the insert-time default: a constant value of type `T`, or a portable
|
|
2870
|
-
* server-side expression from {@link sql} (e.g. `sql.now()`, `sql.uuidv4()`).
|
|
2871
|
-
*
|
|
2872
|
-
* @param value The literal default, or a {@link sql} expression.
|
|
2873
|
-
* @returns A new column carrying the default.
|
|
2874
|
-
* @throws Error When given a `sql.expr` fragment — a `DEFAULT` clause has
|
|
2875
|
-
* nowhere to bind parameters; use `sql.raw()` for a verbatim expression.
|
|
2876
|
-
*/
|
|
2877
|
-
default(value) {
|
|
2878
|
-
const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
|
|
2879
|
-
if (bindsParameters(resolved)) {
|
|
2880
|
-
throw new Error(
|
|
2881
|
-
"sql.expr`...` binds parameters and cannot be a column default \u2014 use sql.raw() for a verbatim DEFAULT expression."
|
|
2882
|
-
);
|
|
2883
|
-
}
|
|
2884
|
-
return this.derive({
|
|
2885
|
-
flags: { ...this.flags, hasDefault: true },
|
|
2886
|
-
defaultValue: resolved
|
|
2887
|
-
});
|
|
2888
|
-
}
|
|
2889
|
-
/**
|
|
2890
|
-
* Re-apply a value whenever the row is updated (e.g. an `updated_at` column
|
|
2891
|
-
* with `sql.now()`). Mirrors SQLAlchemy's `onupdate`.
|
|
2892
|
-
*
|
|
2893
|
-
* @param value The literal value, or a {@link sql} expression.
|
|
2894
|
-
* @returns A new column carrying the on-update value.
|
|
2895
|
-
* @throws Error When given a `sql.expr` fragment (see {@link Column.default}).
|
|
2896
|
-
*/
|
|
2897
|
-
onUpdate(value) {
|
|
2898
|
-
const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
|
|
2899
|
-
if (bindsParameters(resolved)) {
|
|
2900
|
-
throw new Error(
|
|
2901
|
-
"sql.expr`...` binds parameters and cannot be an onUpdate default \u2014 use sql.raw() for a verbatim expression."
|
|
2902
|
-
);
|
|
2903
|
-
}
|
|
2904
|
-
return this.derive({ onUpdateValue: resolved });
|
|
2905
|
-
}
|
|
2906
|
-
};
|
|
2907
|
-
function makeColumn(kind, meta = {}) {
|
|
2908
|
-
return new Column({ kind, meta }, DEFAULT_FLAGS);
|
|
2909
|
-
}
|
|
2910
|
-
var column = {
|
|
2911
|
-
/** `SMALLINT` → `number`. */
|
|
2912
|
-
smallInteger: () => makeColumn("smallint"),
|
|
2913
|
-
/** `INTEGER` → `number`. */
|
|
2914
|
-
integer: () => makeColumn("integer"),
|
|
2915
|
-
/** `BIGINT` → `bigint` (64-bit precision preserved). */
|
|
2916
|
-
bigInteger: () => makeColumn("bigint"),
|
|
2917
|
-
/** `NUMERIC(precision, scale)` → `string` (exact decimal, no float loss). */
|
|
2918
|
-
numeric: (precision, scale) => makeColumn("numeric", { precision, scale }),
|
|
2919
|
-
/** Alias of {@link column.numeric}. */
|
|
2920
|
-
decimal: (precision, scale) => makeColumn("numeric", { precision, scale }),
|
|
2921
|
-
/** `REAL` → `number`. */
|
|
2922
|
-
real: () => makeColumn("real"),
|
|
2923
|
-
/** `DOUBLE PRECISION` → `number`. */
|
|
2924
|
-
double: () => makeColumn("double"),
|
|
2925
|
-
/** `VARCHAR(length)` → `string`. Distinct from {@link column.text}. */
|
|
2926
|
-
varchar: (length) => makeColumn("varchar", { length }),
|
|
2927
|
-
/** Alias of {@link column.varchar} (SQLAlchemy's `String`). */
|
|
2928
|
-
string: (length) => makeColumn("varchar", { length }),
|
|
2929
|
-
/** `CHAR(length)` → `string` (fixed-width). */
|
|
2930
|
-
char: (length) => makeColumn("char", { length }),
|
|
2931
|
-
/** `TEXT` → `string` (unbounded). Distinct from {@link column.varchar}. */
|
|
2932
|
-
text: () => makeColumn("text"),
|
|
2933
|
-
/** `BOOLEAN` → `boolean`. */
|
|
2934
|
-
boolean: () => makeColumn("boolean"),
|
|
2935
|
-
/** `DATE` → `Date`. */
|
|
2936
|
-
date: () => makeColumn("date"),
|
|
2937
|
-
/** `TIME` → `string`. Pass `{ timezone: true }` for `WITH TIME ZONE`. */
|
|
2938
|
-
time: (options) => makeColumn("time", { withTimezone: options?.timezone }),
|
|
2939
|
-
/**
|
|
2940
|
-
* `DATETIME`/`TIMESTAMP` → `Date` (SQLAlchemy's generic `DateTime`). Pass
|
|
2941
|
-
* `{ timezone: true }` for `WITH TIME ZONE`. Pair with `.default(sql.now())`
|
|
2942
|
-
* and `.onUpdate(sql.now())` for managed `created_at`/`updated_at` columns.
|
|
2943
|
-
*/
|
|
2944
|
-
datetime: (options) => makeColumn("datetime", { withTimezone: options?.timezone }),
|
|
2945
|
-
/** `TIMESTAMP` → `Date` (SQL-specific). Pass `{ timezone: true }`. */
|
|
2946
|
-
timestamp: (options) => makeColumn("timestamp", { withTimezone: options?.timezone }),
|
|
2947
|
-
/** `BLOB`/`BYTEA` → `Uint8Array`. */
|
|
2948
|
-
blob: () => makeColumn("blob"),
|
|
2949
|
-
/** `JSON` → the given parsed value type `T` (defaults to `unknown`). */
|
|
2950
|
-
json: () => makeColumn("json"),
|
|
2951
|
-
/** `JSONB` (PostgreSQL) → the given parsed value type `T`. */
|
|
2952
|
-
jsonb: () => makeColumn("json", { jsonb: true }),
|
|
2953
|
-
/** `UUID` → `string`. */
|
|
2954
|
-
uuid: () => makeColumn("uuid"),
|
|
2955
|
-
/** `ENUM(...values)` → a string-literal union of the given values. */
|
|
2956
|
-
enum: (...values) => makeColumn("enum", { values }),
|
|
2957
|
-
/**
|
|
2958
|
-
* A PostgreSQL array column (`text[]`, `integer[]`) → `T[]`.
|
|
2959
|
-
*
|
|
2960
|
-
* PostgreSQL only: SQLite and MySQL have no native array type, and rendering
|
|
2961
|
-
* one as JSON there would give the same model different semantics per dialect
|
|
2962
|
-
* (`@>` and `&&` work on one and not the other), so the DDL renderer throws
|
|
2963
|
-
* for those dialects instead of falling back silently.
|
|
2964
|
-
*
|
|
2965
|
-
* @param element The element column (its type, not its flags, is what is used).
|
|
2966
|
-
* @returns A column whose inferred type is an array of the element's type.
|
|
2967
|
-
*
|
|
2968
|
-
* @example
|
|
2969
|
-
* ```ts
|
|
2970
|
-
* class ApiKey extends Model {
|
|
2971
|
-
* static tablename = "api_keys";
|
|
2972
|
-
* scopes = column.array(column.text()).notNull().default(["send"]);
|
|
2973
|
-
* }
|
|
2974
|
-
* ```
|
|
2975
|
-
*/
|
|
2976
|
-
array: (element) => makeColumn("array", { element: element.type })
|
|
2977
|
-
};
|
|
2978
|
-
function unique(...columns) {
|
|
2979
|
-
if (columns.length === 0) {
|
|
2980
|
-
throw new Error("unique() requires at least one column.");
|
|
2981
|
-
}
|
|
2982
|
-
return { kind: "unique", columns };
|
|
2983
|
-
}
|
|
2984
|
-
function foreignKey(columns, refTable, refColumns, options) {
|
|
2985
|
-
if (columns.length === 0 || columns.length !== refColumns.length) {
|
|
2986
|
-
throw new Error(
|
|
2987
|
-
"foreignKey() requires matching, non-empty local and referenced column lists."
|
|
2988
|
-
);
|
|
2989
|
-
}
|
|
2990
|
-
return {
|
|
2991
|
-
kind: "foreignKey",
|
|
2992
|
-
name: options?.name,
|
|
2993
|
-
columns,
|
|
2994
|
-
refTable,
|
|
2995
|
-
refColumns,
|
|
2996
|
-
onDelete: options?.onDelete,
|
|
2997
|
-
onUpdate: options?.onUpdate
|
|
2998
|
-
};
|
|
2999
|
-
}
|
|
3000
|
-
function toSnakeCase(name) {
|
|
3001
|
-
return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
3002
|
-
}
|
|
3003
|
-
var Model = class {
|
|
3004
|
-
static tablename;
|
|
3005
|
-
/**
|
|
3006
|
-
* Optional table-level constraints (composite unique / foreign keys), returned
|
|
3007
|
-
* by a thunk so forward references resolve lazily. Mirrors SQLAlchemy's
|
|
3008
|
-
* `__table_args__`.
|
|
3009
|
-
*/
|
|
3010
|
-
static tableArgs;
|
|
3011
|
-
/**
|
|
3012
|
-
* How to derive column names from property names (default `"preserve"`). Set
|
|
3013
|
-
* `"snake_case"` to keep a `snake_case` schema behind a `camelCase` model
|
|
3014
|
-
* without annotating every column; {@link Column.name} overrides it per column.
|
|
3015
|
-
*/
|
|
3016
|
-
static naming;
|
|
3017
|
-
};
|
|
3018
|
-
var columnsCache = /* @__PURE__ */ new WeakMap();
|
|
3019
|
-
function columnsOf(model) {
|
|
3020
|
-
const cached = columnsCache.get(model);
|
|
3021
|
-
if (cached) return cached;
|
|
3022
|
-
const instance = new model();
|
|
3023
|
-
const out = {};
|
|
3024
|
-
for (const [key, value] of Object.entries(instance)) {
|
|
3025
|
-
if (value instanceof Column) {
|
|
3026
|
-
out[key] = value;
|
|
3027
|
-
}
|
|
3028
|
-
}
|
|
3029
|
-
columnsCache.set(model, out);
|
|
3030
|
-
return out;
|
|
3031
|
-
}
|
|
3032
|
-
var nameMapCache = /* @__PURE__ */ new WeakMap();
|
|
3033
|
-
var propMapCache = /* @__PURE__ */ new WeakMap();
|
|
3034
|
-
function columnNamesOf(model) {
|
|
3035
|
-
const cached = nameMapCache.get(model);
|
|
3036
|
-
if (cached !== void 0) return cached;
|
|
3037
|
-
const strategy = model.naming ?? "preserve";
|
|
3038
|
-
const map = {};
|
|
3039
|
-
const seen = /* @__PURE__ */ new Map();
|
|
3040
|
-
let renamed = false;
|
|
3041
|
-
for (const [prop, col2] of Object.entries(columnsOf(model))) {
|
|
3042
|
-
const dbName = col2.dbName ?? (strategy === "snake_case" ? toSnakeCase(prop) : prop);
|
|
3043
|
-
const collision = seen.get(dbName);
|
|
3044
|
-
if (collision !== void 0) {
|
|
3045
|
-
throw new Error(
|
|
3046
|
-
`${model.tablename}: properties "${collision}" and "${prop}" both map to column "${dbName}".`
|
|
3047
|
-
);
|
|
3048
|
-
}
|
|
3049
|
-
seen.set(dbName, prop);
|
|
3050
|
-
map[prop] = dbName;
|
|
3051
|
-
if (dbName !== prop) renamed = true;
|
|
3052
|
-
}
|
|
3053
|
-
const result = renamed ? map : null;
|
|
3054
|
-
nameMapCache.set(model, result);
|
|
3055
|
-
return result;
|
|
3056
|
-
}
|
|
3057
|
-
function columnPropsOf(model) {
|
|
3058
|
-
const cached = propMapCache.get(model);
|
|
3059
|
-
if (cached !== void 0) return cached;
|
|
3060
|
-
const forward = columnNamesOf(model);
|
|
3061
|
-
let result = null;
|
|
3062
|
-
if (forward) {
|
|
3063
|
-
const inverse = {};
|
|
3064
|
-
for (const [prop, dbName] of Object.entries(forward)) inverse[dbName] = prop;
|
|
3065
|
-
result = inverse;
|
|
3066
|
-
}
|
|
3067
|
-
propMapCache.set(model, result);
|
|
3068
|
-
return result;
|
|
3069
|
-
}
|
|
3070
|
-
function dbColumn(names, prop) {
|
|
3071
|
-
return names?.[prop] ?? prop;
|
|
3072
|
-
}
|
|
3073
|
-
|
|
3074
|
-
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 };
|
|
3075
|
-
//# sourceMappingURL=chunk-4AWUP7BM.js.map
|
|
3076
|
-
//# sourceMappingURL=chunk-4AWUP7BM.js.map
|