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/index.d.ts
CHANGED
|
@@ -1,28 +1,386 @@
|
|
|
1
|
+
/** One joined table. */
|
|
2
|
+
interface JoinClause {
|
|
3
|
+
readonly kind: "inner" | "left";
|
|
4
|
+
readonly table: string;
|
|
5
|
+
readonly alias: string;
|
|
6
|
+
/** Equality pairs of qualified columns: `["user.id", "order.userId"]`. */
|
|
7
|
+
readonly on: readonly (readonly [string, string])[];
|
|
8
|
+
}
|
|
9
|
+
/** A selected column, qualified by source alias. */
|
|
10
|
+
interface JoinSelection {
|
|
11
|
+
readonly alias: string;
|
|
12
|
+
readonly column: string;
|
|
13
|
+
}
|
|
14
|
+
/** Collapse a union of object types into their intersection. */
|
|
15
|
+
type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
|
|
16
|
+
/**
|
|
17
|
+
* `where` filter for a join. Keys are `alias.column` refs; each value accepts a
|
|
18
|
+
* bare value (shorthand for `eq`) or an operator object restricted to the
|
|
19
|
+
* operators valid for that column's type — exactly like the single-table
|
|
20
|
+
* {@link WhereInput}, but qualified per source. `like` on a numeric join column,
|
|
21
|
+
* or `gt` on a string one, is a compile error.
|
|
22
|
+
*/
|
|
23
|
+
type JoinWhereInput<S extends Sources> = Partial<UnionToIntersection<{
|
|
24
|
+
[A in keyof S]: {
|
|
25
|
+
[C in keyof NonNullable<S[A]> & string as `${A & string}.${C}`]: NonNullable<S[A]>[C] | OperatorsFor<NonNullable<NonNullable<S[A]>[C]>>;
|
|
26
|
+
};
|
|
27
|
+
}[keyof S]>>;
|
|
28
|
+
/** Serializable AST for a multi-table SELECT. */
|
|
29
|
+
interface JoinNode {
|
|
30
|
+
readonly kind: "join_select";
|
|
31
|
+
readonly base: {
|
|
32
|
+
readonly table: string;
|
|
33
|
+
readonly alias: string;
|
|
34
|
+
};
|
|
35
|
+
readonly joins: readonly JoinClause[];
|
|
36
|
+
readonly selections: readonly JoinSelection[];
|
|
37
|
+
readonly where: CondNode | undefined;
|
|
38
|
+
readonly orderBy: readonly {
|
|
39
|
+
readonly ref: string;
|
|
40
|
+
readonly direction: SortDirection;
|
|
41
|
+
}[];
|
|
42
|
+
readonly limit: number | undefined;
|
|
43
|
+
readonly offset: number | undefined;
|
|
44
|
+
/** Per-alias property → column maps, for the sources that rename columns. */
|
|
45
|
+
readonly names?: Readonly<Record<string, NameMap>> | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Project only this source, with **bare** column names instead of the
|
|
48
|
+
* `alias.column` labels a composite row needs.
|
|
49
|
+
*
|
|
50
|
+
* Set by {@link JoinBuilder.pick}. It is what lets a join stand where a plain
|
|
51
|
+
* `SELECT` of one table would — the recursive branch of a `WITH RECURSIVE`, for
|
|
52
|
+
* instance, whose columns have to line up with the CTE's.
|
|
53
|
+
*/
|
|
54
|
+
readonly pick?: string | undefined;
|
|
55
|
+
/** `WITH` entries this statement carries, in order. */
|
|
56
|
+
readonly with?: readonly CteNode[] | undefined;
|
|
57
|
+
}
|
|
58
|
+
/** A map of source alias → its (possibly nullable) row type. */
|
|
59
|
+
type Sources = Record<string, object | null>;
|
|
60
|
+
/** Every `alias.column` reference across the current sources. */
|
|
61
|
+
type ColRef<S extends Sources> = {
|
|
62
|
+
[A in keyof S]: `${A & string}.${keyof NonNullable<S[A]> & string}`;
|
|
63
|
+
}[keyof S];
|
|
64
|
+
/** Valid `alias.column` references for a newly joined model. */
|
|
65
|
+
type RightRef<A extends string, C extends ModelClass> = `${A}.${keyof InferModel<C> & string}`;
|
|
66
|
+
/** The `on` condition: map existing-source refs to new-table refs (equality). */
|
|
67
|
+
type JoinOn<S extends Sources, A extends string, C extends ModelClass> = Partial<Record<ColRef<S>, RightRef<A, C>>>;
|
|
68
|
+
/**
|
|
69
|
+
* Immutable, chainable multi-table SELECT builder.
|
|
70
|
+
*
|
|
71
|
+
* @typeParam S - the accumulated sources (alias → row type; nullable for left joins).
|
|
72
|
+
*/
|
|
73
|
+
declare class JoinBuilder<S extends Sources> {
|
|
74
|
+
readonly node: JoinNode;
|
|
75
|
+
/** Source models keyed by alias, for SQL expansion and row coercion. */
|
|
76
|
+
readonly sources: Readonly<Record<string, ModelClass>>;
|
|
77
|
+
/** Phantom: the composite result row type. */
|
|
78
|
+
readonly __row: {
|
|
79
|
+
[A in keyof S]: S[A];
|
|
80
|
+
};
|
|
81
|
+
constructor(node: JoinNode,
|
|
82
|
+
/** Source models keyed by alias, for SQL expansion and row coercion. */
|
|
83
|
+
sources: Readonly<Record<string, ModelClass>>);
|
|
84
|
+
private add;
|
|
85
|
+
private clause;
|
|
86
|
+
/** Inner join another model under `alias`. */
|
|
87
|
+
innerJoin<C extends ModelClass, A extends string>(model: C, alias: A, on: JoinOn<S, A, C>): JoinBuilder<S & {
|
|
88
|
+
[K in A]: InferModel<C>;
|
|
89
|
+
}>;
|
|
90
|
+
/** Left (outer) join another model under `alias` — its side becomes nullable. */
|
|
91
|
+
leftJoin<C extends ModelClass, A extends string>(model: C, alias: A, on: JoinOn<S, A, C>): JoinBuilder<S & {
|
|
92
|
+
[K in A]: InferModel<C> | null;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* Project **one** source, flat, instead of the composite row.
|
|
96
|
+
*
|
|
97
|
+
* The join still happens — it just stops being what comes back. Needed wherever
|
|
98
|
+
* the result has to match a single table's shape: the recursive branch of a
|
|
99
|
+
* CTE, an `INSERT ... SELECT`, a `UNION` branch.
|
|
100
|
+
*
|
|
101
|
+
* @param alias The source to project.
|
|
102
|
+
* @returns A builder whose rows are that source's rows.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* join(Category, "c").innerJoin(subtree, "s", { "c.parentId": "s.id" }).pick("c");
|
|
107
|
+
* // SELECT "c"."id" AS "id", "c"."name" AS "name" ... — not "c.id"
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
pick<A extends keyof S & string>(alias: A): JoinBuilder<S> & {
|
|
111
|
+
readonly __row: NonNullable<S[A]>;
|
|
112
|
+
};
|
|
113
|
+
/** Filter by `alias.column` references (object form) or an `and`/`or`/`not`. */
|
|
114
|
+
where(input: JoinWhereInput<S> | Condition): JoinBuilder<S>;
|
|
115
|
+
/** Order by an `alias.column` reference. */
|
|
116
|
+
orderBy(ref: ColRef<S>, direction?: SortDirection): JoinBuilder<S>;
|
|
117
|
+
limit(n: number): JoinBuilder<S>;
|
|
118
|
+
offset(n: number): JoinBuilder<S>;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Start a multi-table query from a base model under `alias`.
|
|
122
|
+
*
|
|
123
|
+
* @param model The base model.
|
|
124
|
+
* @param alias The alias to key the base table's rows under in the result.
|
|
125
|
+
* @returns A `JoinBuilder` with the base source registered.
|
|
126
|
+
*/
|
|
127
|
+
declare function join<C extends ModelClass, A extends string>(model: C, alias: A): JoinBuilder<{
|
|
128
|
+
[K in A]: InferModel<C>;
|
|
129
|
+
}>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* tempest-db-js — set operations (`UNION`, `INTERSECT`, `EXCEPT`).
|
|
133
|
+
*
|
|
134
|
+
* Combining two queries in any way other than a join meant dropping to
|
|
135
|
+
* `session.raw`, losing the row type, the coercion and the composition. A set
|
|
136
|
+
* operation is also the one place a typed builder can catch a mistake the
|
|
137
|
+
* database only reports at runtime: branches whose projections do not line up.
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
/** Which set operation combines the branches. */
|
|
141
|
+
type SetOperator = "union" | "unionAll" | "intersect" | "except";
|
|
142
|
+
/** Serializable AST for a set operation. */
|
|
143
|
+
interface SetNode {
|
|
144
|
+
readonly kind: "set_op";
|
|
145
|
+
readonly op: SetOperator;
|
|
146
|
+
/** The combined SELECTs, in order. A branch may be a join projecting one source. */
|
|
147
|
+
readonly branches: readonly (SelectNode | JoinNode)[];
|
|
148
|
+
/** Ordering applied to the **result**, not to a branch. */
|
|
149
|
+
readonly orderBy: readonly OrderTerm[];
|
|
150
|
+
readonly limit: number | undefined;
|
|
151
|
+
readonly offset: number | undefined;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* A combined query, executable like a `SelectBuilder`.
|
|
155
|
+
*
|
|
156
|
+
* `orderBy` / `limit` / `offset` here apply to the **combined** result, which is
|
|
157
|
+
* what SQL does: a branch that needs its own limit keeps it, and the dialect
|
|
158
|
+
* parenthesizes that branch.
|
|
159
|
+
*
|
|
160
|
+
* @typeParam Row - the row type every branch projects.
|
|
161
|
+
*/
|
|
162
|
+
declare class SetBuilder<Row> {
|
|
163
|
+
readonly node: SetNode;
|
|
164
|
+
/** The first branch's model, used to coerce the returned rows. */
|
|
165
|
+
readonly source: ModelClass;
|
|
166
|
+
/** Phantom: the result element type, read only by the type system. */
|
|
167
|
+
readonly __row: Row;
|
|
168
|
+
constructor(node: SetNode,
|
|
169
|
+
/** The first branch's model, used to coerce the returned rows. */
|
|
170
|
+
source: ModelClass);
|
|
171
|
+
private with;
|
|
172
|
+
/**
|
|
173
|
+
* Order the combined result.
|
|
174
|
+
*
|
|
175
|
+
* @param column A column of the projected row.
|
|
176
|
+
* @param direction Sort direction (default ascending).
|
|
177
|
+
* @returns A builder carrying the ordering.
|
|
178
|
+
*/
|
|
179
|
+
orderBy(column: keyof Row & string, direction?: SortDirection): SetBuilder<Row>;
|
|
180
|
+
/** Limit the combined result. */
|
|
181
|
+
limit(count: number): SetBuilder<Row>;
|
|
182
|
+
/** Skip rows of the combined result. */
|
|
183
|
+
offset(count: number): SetBuilder<Row>;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* What a set operation combines: anything that compiles to a SELECT and yields
|
|
187
|
+
* `Row` — a `select()`, or a join narrowed to one source with `.pick()`.
|
|
188
|
+
*/
|
|
189
|
+
interface Branch<Row> {
|
|
190
|
+
/** The branch's AST. */
|
|
191
|
+
readonly node: SelectNode | JoinNode;
|
|
192
|
+
/** Phantom: the row type this branch yields. */
|
|
193
|
+
readonly __row: Row;
|
|
194
|
+
/** The model rows are coerced through, when the branch has a single source. */
|
|
195
|
+
readonly source?: ModelClass;
|
|
196
|
+
/** Source models by alias, for a join branch. */
|
|
197
|
+
readonly sources?: Readonly<Record<string, ModelClass>>;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* `UNION` — every row of either branch, **duplicates removed**.
|
|
201
|
+
*
|
|
202
|
+
* Every branch must project the same shape; that is checked at compile time here,
|
|
203
|
+
* where the database would only report it at runtime.
|
|
204
|
+
*
|
|
205
|
+
* @param branches The queries to combine.
|
|
206
|
+
* @returns The combined builder.
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* ```ts
|
|
210
|
+
* union(
|
|
211
|
+
* select(Post, ["id", "createdAt"]).where({ authorId: me }),
|
|
212
|
+
* select(Comment, ["id", "createdAt"]).where({ authorId: me }),
|
|
213
|
+
* ).orderBy("createdAt", "desc").limit(50);
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
declare function union<Row>(...branches: Branch<Row>[]): SetBuilder<Row>;
|
|
217
|
+
/**
|
|
218
|
+
* `UNION ALL` — every row of either branch, duplicates **kept**.
|
|
219
|
+
*
|
|
220
|
+
* Cheaper than `UNION`, which has to sort or hash to deduplicate. Prefer it
|
|
221
|
+
* whenever the branches cannot overlap.
|
|
222
|
+
*
|
|
223
|
+
* @param branches The queries to combine.
|
|
224
|
+
* @returns The combined builder.
|
|
225
|
+
*/
|
|
226
|
+
declare function unionAll<Row>(...branches: Branch<Row>[]): SetBuilder<Row>;
|
|
227
|
+
/**
|
|
228
|
+
* `INTERSECT` — only the rows present in every branch.
|
|
229
|
+
*
|
|
230
|
+
* @param branches The queries to combine.
|
|
231
|
+
* @returns The combined builder.
|
|
232
|
+
*/
|
|
233
|
+
declare function intersect<Row>(...branches: Branch<Row>[]): SetBuilder<Row>;
|
|
234
|
+
/**
|
|
235
|
+
* `EXCEPT` — the rows of the first branch that are not in the others.
|
|
236
|
+
*
|
|
237
|
+
* @param branches The queries to combine.
|
|
238
|
+
* @returns The combined builder.
|
|
239
|
+
*/
|
|
240
|
+
declare function except<Row>(...branches: Branch<Row>[]): SetBuilder<Row>;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* tempest-db-js — common table expressions (`WITH`).
|
|
244
|
+
*
|
|
245
|
+
* A CTE names a query so the rest of the statement can select from it, and —
|
|
246
|
+
* when it is recursive — so it can select from **itself**. That second form is
|
|
247
|
+
* the only way to walk a tree (a category hierarchy, a reply chain, a dependency
|
|
248
|
+
* graph) in one statement instead of one query per level.
|
|
249
|
+
*
|
|
250
|
+
* The alias is a real model class with the CTE's name as its table, so everything
|
|
251
|
+
* that already takes a model — `select`, `join`, `where` — takes a CTE with no
|
|
252
|
+
* special casing anywhere else.
|
|
253
|
+
*/
|
|
254
|
+
|
|
255
|
+
/** A query that can be the body of a CTE. */
|
|
256
|
+
type CteBody<Row> = SelectBuilder<any, Row, any> | SetBuilder<Row>;
|
|
257
|
+
/** One `WITH` entry, as the AST carries it. */
|
|
258
|
+
interface CteNode {
|
|
259
|
+
/** The name the rest of the statement refers to. */
|
|
260
|
+
readonly name: string;
|
|
261
|
+
/** True for `WITH RECURSIVE`. */
|
|
262
|
+
readonly recursive: boolean;
|
|
263
|
+
/** The body query. */
|
|
264
|
+
readonly body: SelectNode | SetNode;
|
|
265
|
+
/**
|
|
266
|
+
* `MATERIALIZED` / `NOT MATERIALIZED`, or `null` to let the planner decide.
|
|
267
|
+
* PostgreSQL 12+ only.
|
|
268
|
+
*/
|
|
269
|
+
readonly materialized: boolean | null;
|
|
270
|
+
}
|
|
271
|
+
/** Options for {@link cte} and {@link cteRecursive}. */
|
|
272
|
+
interface CteOptions {
|
|
273
|
+
/**
|
|
274
|
+
* Force (or forbid) materialization of the CTE.
|
|
275
|
+
*
|
|
276
|
+
* PostgreSQL 12+ inlines a CTE used once, which is usually what you want;
|
|
277
|
+
* `true` pins the old behavior when the body is expensive and used twice.
|
|
278
|
+
* Ignored where the dialect has no such syntax.
|
|
279
|
+
*/
|
|
280
|
+
readonly materialized?: boolean;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* A named query, usable anywhere a model is.
|
|
284
|
+
*
|
|
285
|
+
* @typeParam C - the model class whose rows the CTE yields.
|
|
286
|
+
*/
|
|
287
|
+
declare class Cte<C extends ModelClass> {
|
|
288
|
+
/** The alias model: the same columns, under the CTE's name. */
|
|
289
|
+
readonly model: C;
|
|
290
|
+
/** The `WITH` entry this attaches to a statement. */
|
|
291
|
+
readonly node: CteNode;
|
|
292
|
+
constructor(
|
|
293
|
+
/** The alias model: the same columns, under the CTE's name. */
|
|
294
|
+
model: C,
|
|
295
|
+
/** The `WITH` entry this attaches to a statement. */
|
|
296
|
+
node: CteNode);
|
|
297
|
+
/**
|
|
298
|
+
* Select from the CTE, with the `WITH` clause attached.
|
|
299
|
+
*
|
|
300
|
+
* @param columns Optional projection.
|
|
301
|
+
* @returns A builder over the CTE.
|
|
302
|
+
*/
|
|
303
|
+
select(): SelectBuilder<InferModel<C>, InferModel<C>>;
|
|
304
|
+
select<K extends keyof InferModel<C> & string>(columns: readonly K[]): SelectBuilder<InferModel<C>, Pick<InferModel<C>, K>>;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Attach a `WITH` entry to a builder that reads from the CTE.
|
|
308
|
+
*
|
|
309
|
+
* Use it when the outer query is not a plain `select(cte.model)` — a join, for
|
|
310
|
+
* instance, whose builder was assembled elsewhere.
|
|
311
|
+
*
|
|
312
|
+
* @param builder The outer query.
|
|
313
|
+
* @param node The `WITH` entry.
|
|
314
|
+
* @returns The same builder shape, carrying the clause.
|
|
315
|
+
*/
|
|
316
|
+
declare function attach<B extends {
|
|
317
|
+
node: object;
|
|
318
|
+
}>(builder: B, node: CteNode): B;
|
|
319
|
+
/**
|
|
320
|
+
* A named query the rest of the statement can select from.
|
|
321
|
+
*
|
|
322
|
+
* @param name The CTE's name.
|
|
323
|
+
* @param model The model whose row shape the body yields.
|
|
324
|
+
* @param body The query to name.
|
|
325
|
+
* @param options Materialization hint.
|
|
326
|
+
* @returns The CTE, whose `.model` is usable anywhere a model is.
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* ```ts
|
|
330
|
+
* const recent = cte("recent", Order, select(Order).where({ createdAt: { gte: since } }));
|
|
331
|
+
* const rows = await session.execute(recent.select().where({ status: "open" })).all();
|
|
332
|
+
* // WITH "recent" AS (SELECT * FROM "orders" WHERE ...) SELECT * FROM "recent" WHERE ...
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
declare function cte<C extends ModelClass>(name: string, model: C, body: CteBody<InferModel<C>>, options?: CteOptions): Cte<C>;
|
|
1
336
|
/**
|
|
2
|
-
*
|
|
337
|
+
* A CTE whose body refers to itself — the way to walk a tree in one statement.
|
|
3
338
|
*
|
|
4
|
-
* The builder
|
|
5
|
-
*
|
|
6
|
-
* - `select(User)` infers the full row type,
|
|
7
|
-
* - `select(User, ["id", "name"])` infers a `Pick` projection,
|
|
8
|
-
* - `.where(...)` / `.orderBy(...)` reject keys that are not columns,
|
|
9
|
-
* all at compile time.
|
|
339
|
+
* The builder receives the CTE's own alias model, so the recursive branch can
|
|
340
|
+
* join against it.
|
|
10
341
|
*
|
|
11
|
-
*
|
|
342
|
+
* @param name The CTE's name.
|
|
343
|
+
* @param model The model whose row shape the body yields.
|
|
344
|
+
* @param build Receives the self-reference and returns the body (a `union`/
|
|
345
|
+
* `unionAll` of the seed and the step).
|
|
346
|
+
* @param options Materialization hint.
|
|
347
|
+
* @returns The CTE.
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```ts
|
|
351
|
+
* const subtree = cteRecursive("subtree", Category, (self) =>
|
|
352
|
+
* unionAll(
|
|
353
|
+
* select(Category).where({ id: rootId }),
|
|
354
|
+
* join(Category, "c").innerJoin(self, "s", { "c.parentId": "s.id" }).select("c"),
|
|
355
|
+
* ),
|
|
356
|
+
* );
|
|
357
|
+
* ```
|
|
12
358
|
*/
|
|
359
|
+
declare function cteRecursive<C extends ModelClass>(name: string, model: C, build: (self: C) => CteBody<InferModel<C>>, options?: CteOptions): Cte<C>;
|
|
13
360
|
|
|
361
|
+
/** The value type an expression produces, for `compute`'s row type. */
|
|
362
|
+
type ExpressionValue<E> = E extends Expression<infer T> ? T : unknown;
|
|
363
|
+
/** Flatten an intersection into one object literal, for readable inference. */
|
|
364
|
+
type Simplify$1<T> = {
|
|
365
|
+
[K in keyof T]: T[K];
|
|
366
|
+
} & {};
|
|
14
367
|
/** Sort direction for ORDER BY. */
|
|
15
368
|
type SortDirection = "asc" | "desc";
|
|
16
369
|
/** One ORDER BY term. */
|
|
17
370
|
interface OrderTerm {
|
|
18
|
-
|
|
371
|
+
/** The column to sort by, or an expression (a full-text rank, for instance). */
|
|
372
|
+
readonly column: string | ExprNode;
|
|
19
373
|
readonly direction: SortDirection;
|
|
20
374
|
}
|
|
21
375
|
/** One aggregate expression in a grouped SELECT (`COUNT(*) AS "n"`). */
|
|
22
376
|
interface AggregateTerm {
|
|
23
377
|
readonly fn: "count" | "sum" | "avg" | "min" | "max";
|
|
24
|
-
/**
|
|
25
|
-
|
|
378
|
+
/**
|
|
379
|
+
* What is aggregated: a column name, `"*"` (only valid for `count`), or an
|
|
380
|
+
* expression — which is how a conditional aggregate
|
|
381
|
+
* (`SUM(CASE WHEN ... END)`) is expressed.
|
|
382
|
+
*/
|
|
383
|
+
readonly column: string | "*" | ExprNode;
|
|
26
384
|
/** The result alias. */
|
|
27
385
|
readonly alias: string;
|
|
28
386
|
}
|
|
@@ -52,6 +410,8 @@ interface LockOptions {
|
|
|
52
410
|
interface SelectNode {
|
|
53
411
|
readonly kind: "select";
|
|
54
412
|
readonly table: string;
|
|
413
|
+
/** The name the table is read under (`FROM "users" AS "sub"`), when aliased. */
|
|
414
|
+
readonly alias?: string | undefined;
|
|
55
415
|
/** Projected columns, or "*" for the whole row. */
|
|
56
416
|
readonly columns: readonly string[] | "*";
|
|
57
417
|
/** Emit `SELECT DISTINCT` when true. */
|
|
@@ -64,6 +424,12 @@ interface SelectNode {
|
|
|
64
424
|
/** `HAVING` condition, keyed by aggregate alias or grouped column. */
|
|
65
425
|
readonly having?: CondNode | undefined;
|
|
66
426
|
readonly orderBy: readonly OrderTerm[];
|
|
427
|
+
/** Per-column codecs for custom types, by property name, when the model has any. */
|
|
428
|
+
readonly codecs?: Readonly<Record<string, ColumnCodec>> | undefined;
|
|
429
|
+
/** `WITH` entries this statement carries, in order. */
|
|
430
|
+
readonly with?: readonly CteNode[] | undefined;
|
|
431
|
+
/** Extra projected expressions, by result alias (window functions, CASE, …). */
|
|
432
|
+
readonly computed?: Readonly<Record<string, ExprNode>> | undefined;
|
|
67
433
|
readonly limit: number | undefined;
|
|
68
434
|
readonly offset: number | undefined;
|
|
69
435
|
/** Row-level locking clause, or `undefined` for none. */
|
|
@@ -84,14 +450,70 @@ interface Subquery<T> {
|
|
|
84
450
|
/** The AST the outer statement embeds. */
|
|
85
451
|
readonly node: SelectNode;
|
|
86
452
|
}
|
|
453
|
+
/**
|
|
454
|
+
* `EXISTS (subquery)` — true when the subquery returns at least one row.
|
|
455
|
+
*
|
|
456
|
+
* The right shape for "is there any…" questions: the database can stop at the
|
|
457
|
+
* first match, which an `IN` over a materialized list cannot. Correlate it by
|
|
458
|
+
* referencing an outer column with a qualified `col("users.id")`.
|
|
459
|
+
*
|
|
460
|
+
* @param subquery The inner SELECT, as a builder or a `.asSubquery()` result.
|
|
461
|
+
* @returns A condition, composable with `and`/`or`/`not`.
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* select(User).where(
|
|
466
|
+
* exists(select(Order).where({ userId: col("users.id"), status: "open" })),
|
|
467
|
+
* );
|
|
468
|
+
* // WHERE EXISTS (SELECT * FROM "orders" WHERE "userId" = "users"."id" AND ...)
|
|
469
|
+
* ```
|
|
470
|
+
*/
|
|
471
|
+
declare function exists(subquery: SubqueryLike): Condition;
|
|
472
|
+
/**
|
|
473
|
+
* `NOT EXISTS (subquery)` — true when the subquery returns no row.
|
|
474
|
+
*
|
|
475
|
+
* @param subquery The inner SELECT.
|
|
476
|
+
* @returns The condition.
|
|
477
|
+
*/
|
|
478
|
+
declare function notExists(subquery: SubqueryLike): Condition;
|
|
479
|
+
/**
|
|
480
|
+
* A scalar subquery — a `SELECT` of one column used where a value goes.
|
|
481
|
+
*
|
|
482
|
+
* Takes the result of `.asSubquery(column)` rather than a bare builder, because
|
|
483
|
+
* that is what pins the projection to exactly **one** column: a scalar subquery
|
|
484
|
+
* returning two columns is a runtime error in every database, and this makes it a
|
|
485
|
+
* compile error instead.
|
|
486
|
+
*
|
|
487
|
+
* @param subquery A single-column subquery.
|
|
488
|
+
* @returns An expression usable in `where`, `orderBy` or an aggregate.
|
|
489
|
+
*
|
|
490
|
+
* @example
|
|
491
|
+
* ```ts
|
|
492
|
+
* select(User).where(
|
|
493
|
+
* scalar(select(Order, ["total"]).where({ userId: col("users.id") })
|
|
494
|
+
* .orderBy("createdAt", "desc").limit(1).asSubquery("total")).gt(100),
|
|
495
|
+
* );
|
|
496
|
+
* ```
|
|
497
|
+
*/
|
|
498
|
+
declare function scalar<T>(subquery: Subquery<T>): Expression;
|
|
499
|
+
/** What the subquery helpers accept: a builder, or a narrowed subquery. */
|
|
500
|
+
type SubqueryLike = SelectBuilder<any, any, any> | Subquery<unknown>;
|
|
87
501
|
/** Runtime guard: is this `in`/`notIn` operand a subquery rather than a list? */
|
|
88
502
|
declare function isSubquery(value: unknown): value is Subquery<unknown>;
|
|
503
|
+
/**
|
|
504
|
+
* A comparison operand: a value of the column's type, or another expression.
|
|
505
|
+
*
|
|
506
|
+
* Accepting an {@link Expression} is what makes a correlated subquery readable —
|
|
507
|
+
* `where({ userId: col("users.id") })` compares two columns instead of binding
|
|
508
|
+
* the string `"users.id"` as a parameter.
|
|
509
|
+
*/
|
|
510
|
+
type Operand<T> = T | Expression;
|
|
89
511
|
/** Operators valid on every column type. */
|
|
90
512
|
interface BaseOperators<T> {
|
|
91
513
|
/** Equal to. */
|
|
92
|
-
eq?: T
|
|
514
|
+
eq?: Operand<T>;
|
|
93
515
|
/** Not equal to. */
|
|
94
|
-
ne?: T
|
|
516
|
+
ne?: Operand<T>;
|
|
95
517
|
/**
|
|
96
518
|
* One of the given values (`IN`) — a list, or a single-column
|
|
97
519
|
* {@link Subquery} built with `.asSubquery(column)`.
|
|
@@ -105,13 +527,13 @@ interface BaseOperators<T> {
|
|
|
105
527
|
/** Extra operators for ordered types (numbers, bigint, dates). */
|
|
106
528
|
interface OrderedOperators<T> extends BaseOperators<T> {
|
|
107
529
|
/** Greater than. */
|
|
108
|
-
gt?: T
|
|
530
|
+
gt?: Operand<T>;
|
|
109
531
|
/** Greater than or equal. */
|
|
110
|
-
gte?: T
|
|
532
|
+
gte?: Operand<T>;
|
|
111
533
|
/** Less than. */
|
|
112
|
-
lt?: T
|
|
534
|
+
lt?: Operand<T>;
|
|
113
535
|
/** Less than or equal. */
|
|
114
|
-
lte?: T
|
|
536
|
+
lte?: Operand<T>;
|
|
115
537
|
/** Inclusive range `BETWEEN lo AND hi`. */
|
|
116
538
|
between?: readonly [T, T];
|
|
117
539
|
}
|
|
@@ -132,7 +554,16 @@ interface StringOperators<T> extends BaseOperators<T> {
|
|
|
132
554
|
* wildcards. The safe operator for a case-insensitive lookup (login, email),
|
|
133
555
|
* and the one that matches a `lower(col)` functional index.
|
|
134
556
|
*/
|
|
135
|
-
ieq?: T
|
|
557
|
+
ieq?: Operand<T>;
|
|
558
|
+
/**
|
|
559
|
+
* Case-insensitive **substring** match of a literal — the safe operator for a
|
|
560
|
+
* search box.
|
|
561
|
+
*
|
|
562
|
+
* The operand is text the user typed, not a pattern: `%` and `_` in it are
|
|
563
|
+
* escaped, so searching for `100%` matches `100%` instead of every row. It
|
|
564
|
+
* compiles to `ILIKE '%…%' ESCAPE '\\'` (`LIKE` where there is no `ILIKE`).
|
|
565
|
+
*/
|
|
566
|
+
iContains?: string;
|
|
136
567
|
}
|
|
137
568
|
/** Extra operators for array columns (PostgreSQL). */
|
|
138
569
|
interface ArrayOperators<T> extends BaseOperators<T> {
|
|
@@ -159,29 +590,29 @@ type OperatorsFor<T> = [T] extends [readonly unknown[]] ? ArrayOperators<T> : [T
|
|
|
159
590
|
* `string`, is a compile error.
|
|
160
591
|
*/
|
|
161
592
|
type WhereInput<Row = Record<string, unknown>> = {
|
|
162
|
-
[K in keyof Row]?: Row[K] | OperatorsFor<NonNullable<Row[K]>>;
|
|
593
|
+
[K in keyof Row]?: Row[K] | Expression | OperatorsFor<NonNullable<Row[K]>>;
|
|
163
594
|
};
|
|
164
595
|
/** The full set of operator keys, for the dialect compiler to recognize. */
|
|
165
|
-
declare const OPERATORS: readonly ["eq", "ne", "gt", "gte", "lt", "lte", "like", "ilike", "ieq", "in", "notIn", "between", "isNull", "contains", "containedBy", "overlaps"];
|
|
596
|
+
declare const OPERATORS: readonly ["eq", "ne", "gt", "gte", "lt", "lte", "like", "ilike", "ieq", "iContains", "in", "notIn", "between", "isNull", "contains", "containedBy", "overlaps"];
|
|
166
597
|
/** One supported operator name. */
|
|
167
598
|
type Operator = (typeof OPERATORS)[number];
|
|
168
599
|
/** An aggregate expression carrying its result type `T` as a phantom. */
|
|
169
600
|
declare class Agg<T> {
|
|
170
601
|
readonly fn: AggregateTerm["fn"];
|
|
171
|
-
readonly column: string | "*";
|
|
602
|
+
readonly column: string | "*" | ExprNode;
|
|
172
603
|
readonly __t: T;
|
|
173
|
-
constructor(fn: AggregateTerm["fn"], column: string | "*");
|
|
604
|
+
constructor(fn: AggregateTerm["fn"], column: string | "*" | ExprNode);
|
|
174
605
|
}
|
|
175
606
|
/** `COUNT(*)` — the number of rows in the group (never null). */
|
|
176
607
|
declare function count(): Agg<number>;
|
|
177
608
|
/** `SUM(column)` — null when the group has no non-null values. */
|
|
178
|
-
declare function sum(column: string): Agg<number | null>;
|
|
609
|
+
declare function sum(column: string | Expression): Agg<number | null>;
|
|
179
610
|
/** `AVG(column)` — null when the group has no non-null values. */
|
|
180
|
-
declare function avg(column: string): Agg<number | null>;
|
|
611
|
+
declare function avg(column: string | Expression): Agg<number | null>;
|
|
181
612
|
/** `MIN(column)` — numeric columns; null on an empty group. */
|
|
182
|
-
declare function min(column: string): Agg<number | null>;
|
|
613
|
+
declare function min(column: string | Expression): Agg<number | null>;
|
|
183
614
|
/** `MAX(column)` — numeric columns; null on an empty group. */
|
|
184
|
-
declare function max(column: string): Agg<number | null>;
|
|
615
|
+
declare function max(column: string | Expression): Agg<number | null>;
|
|
185
616
|
/** Extract the phantom result type of an aggregate expression. */
|
|
186
617
|
type AggResult<A> = A extends Agg<infer T> ? T : never;
|
|
187
618
|
/** Flatten an intersection into a single object literal. */
|
|
@@ -259,7 +690,26 @@ declare class SelectBuilder<Full, Proj = Full, Grouped extends boolean = false>
|
|
|
259
690
|
* @param direction `"asc"` (default) or `"desc"`.
|
|
260
691
|
* @returns A builder carrying the ordering term.
|
|
261
692
|
*/
|
|
262
|
-
|
|
693
|
+
/**
|
|
694
|
+
* Project extra expressions alongside the columns, by alias.
|
|
695
|
+
*
|
|
696
|
+
* This is where a window function lands: unlike `aggregate()`, it does **not**
|
|
697
|
+
* group — every row stays, with the computed value attached.
|
|
698
|
+
*
|
|
699
|
+
* @param map Alias → expression.
|
|
700
|
+
* @returns A builder whose row type carries the aliases.
|
|
701
|
+
*
|
|
702
|
+
* @example
|
|
703
|
+
* ```ts
|
|
704
|
+
* select(Sale).compute({
|
|
705
|
+
* rank: over(rowNumber(), { partitionBy: ["region"], orderBy: [["total", "desc"]] }),
|
|
706
|
+
* });
|
|
707
|
+
* ```
|
|
708
|
+
*/
|
|
709
|
+
compute<M extends Record<string, Expression<unknown>>>(map: M): SelectBuilder<Full, Simplify$1<Proj & {
|
|
710
|
+
[K in keyof M]: ExpressionValue<M[K]>;
|
|
711
|
+
}>, Grouped>;
|
|
712
|
+
orderBy(column: (keyof Full & string) | (keyof Proj & string) | Expression, direction?: SortDirection): SelectBuilder<Full, Proj, Grouped>;
|
|
263
713
|
/** Limit the number of rows. */
|
|
264
714
|
limit(n: number): SelectBuilder<Full, Proj, Grouped>;
|
|
265
715
|
/** Skip the first `n` rows. */
|
|
@@ -369,7 +819,49 @@ type ExprNode = {
|
|
|
369
819
|
readonly kind: "fn";
|
|
370
820
|
readonly name: string;
|
|
371
821
|
readonly args: readonly ExprNode[];
|
|
822
|
+
} | {
|
|
823
|
+
readonly kind: "case";
|
|
824
|
+
readonly branches: readonly {
|
|
825
|
+
readonly when: CondNode;
|
|
826
|
+
readonly result: ExprNode;
|
|
827
|
+
}[];
|
|
828
|
+
readonly fallback: ExprNode | null;
|
|
829
|
+
} | {
|
|
830
|
+
readonly kind: "cast";
|
|
831
|
+
readonly operand: ExprNode;
|
|
832
|
+
readonly to: CastType;
|
|
833
|
+
} | {
|
|
834
|
+
readonly kind: "scalar";
|
|
835
|
+
readonly select: unknown;
|
|
836
|
+
} | {
|
|
837
|
+
readonly kind: "star";
|
|
838
|
+
} | {
|
|
839
|
+
readonly kind: "window";
|
|
840
|
+
/** The function being windowed (`row_number()`, `sum(total)`, …). */
|
|
841
|
+
readonly fn: ExprNode;
|
|
842
|
+
/** `PARTITION BY` columns, by property name. */
|
|
843
|
+
readonly partitionBy: readonly string[];
|
|
844
|
+
/** `ORDER BY` inside the window. */
|
|
845
|
+
readonly orderBy: readonly {
|
|
846
|
+
readonly column: string;
|
|
847
|
+
readonly direction: "asc" | "desc";
|
|
848
|
+
}[];
|
|
849
|
+
/** An explicit frame clause (`ROWS BETWEEN …`), or `null` for the default. */
|
|
850
|
+
readonly frame: string | null;
|
|
851
|
+
} | {
|
|
852
|
+
readonly kind: "rank";
|
|
853
|
+
readonly columns: readonly string[];
|
|
854
|
+
readonly term: string;
|
|
855
|
+
readonly language: string;
|
|
372
856
|
};
|
|
857
|
+
/**
|
|
858
|
+
* A target type for {@link cast}.
|
|
859
|
+
*
|
|
860
|
+
* Kept as a portable vocabulary rather than raw SQL: each dialect maps it to the
|
|
861
|
+
* name it actually accepts (`integer` is `INTEGER` on PostgreSQL and `SIGNED` on
|
|
862
|
+
* MySQL), so the same model does not need a different cast per database.
|
|
863
|
+
*/
|
|
864
|
+
type CastType = "integer" | "bigint" | "real" | "numeric" | "text" | "boolean" | "date" | "datetime" | "timestamp" | "uuid" | "json" | "jsonb" | "blob";
|
|
373
865
|
/** Logical condition nodes. */
|
|
374
866
|
type CondNode = CondFields | {
|
|
375
867
|
readonly kind: "and";
|
|
@@ -385,6 +877,22 @@ type CondNode = CondFields | {
|
|
|
385
877
|
readonly left: ExprNode;
|
|
386
878
|
readonly op: Operator;
|
|
387
879
|
readonly right: ExprNode;
|
|
880
|
+
} | {
|
|
881
|
+
readonly kind: "exists";
|
|
882
|
+
/** The subquery's SELECT node. */
|
|
883
|
+
readonly select: unknown;
|
|
884
|
+
/** True for `NOT EXISTS`. */
|
|
885
|
+
readonly negate: boolean;
|
|
886
|
+
} | {
|
|
887
|
+
readonly kind: "fullText";
|
|
888
|
+
readonly columns: readonly string[];
|
|
889
|
+
readonly term: string;
|
|
890
|
+
readonly language: string;
|
|
891
|
+
/**
|
|
892
|
+
* The substring condition to compile where there is no text-search engine.
|
|
893
|
+
* Built once, here, so the dialects do not each reimplement the fallback.
|
|
894
|
+
*/
|
|
895
|
+
readonly fallback: CondNode;
|
|
388
896
|
};
|
|
389
897
|
declare const CONDITION: unique symbol;
|
|
390
898
|
/** A composed condition produced by `and`/`or`/`not`. */
|
|
@@ -415,9 +923,11 @@ declare function isExpression(value: unknown): value is Expression;
|
|
|
415
923
|
* expressible at all. An operand that is not an `Expression` is bound as a
|
|
416
924
|
* parameter, so `.eq(probe)` stays safe by default.
|
|
417
925
|
*/
|
|
418
|
-
declare class Expression {
|
|
926
|
+
declare class Expression<T = unknown> {
|
|
419
927
|
/** The expression AST the dialect renders. */
|
|
420
928
|
readonly node: ExprNode;
|
|
929
|
+
/** Phantom: the value type this expression produces, read only by the types. */
|
|
930
|
+
readonly __t?: T;
|
|
421
931
|
constructor(
|
|
422
932
|
/** The expression AST the dialect renders. */
|
|
423
933
|
node: ExprNode);
|
|
@@ -497,18 +1007,57 @@ declare function col<Row = Record<string, unknown>>(name: keyof Row & string): E
|
|
|
497
1007
|
*/
|
|
498
1008
|
declare function val(value: unknown): Expression;
|
|
499
1009
|
/**
|
|
500
|
-
*
|
|
1010
|
+
* A `CASE WHEN ... THEN ... ELSE ... END` expression.
|
|
501
1011
|
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
* arguments are always rendered through the expression compiler.
|
|
1012
|
+
* The branch conditions are the same `where` language used everywhere else — the
|
|
1013
|
+
* object form or a `Condition` — so no second grammar shows up just for `CASE`.
|
|
1014
|
+
* The branch results are expressions, and a bare value there is **bound**, not
|
|
1015
|
+
* interpolated.
|
|
507
1016
|
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
* @
|
|
1017
|
+
* The classic use is a conditional aggregate: summing only the rows that match,
|
|
1018
|
+
* in one pass over the table instead of one query per bucket.
|
|
1019
|
+
*
|
|
1020
|
+
* @param branches `[condition, result]` pairs, evaluated in order.
|
|
1021
|
+
* @param fallback The `ELSE` result. Omitted, a row matching no branch is `NULL`.
|
|
1022
|
+
* @returns An expression usable anywhere an expression is.
|
|
1023
|
+
* @throws Error When no branch is given — `CASE END` is not valid SQL.
|
|
1024
|
+
*
|
|
1025
|
+
* @example
|
|
1026
|
+
* ```ts
|
|
1027
|
+
* select(Order).aggregate(["customer"], {
|
|
1028
|
+
* paid: sum(caseWhen([[{ status: "paid" }, col("total")]], val(0))),
|
|
1029
|
+
* });
|
|
1030
|
+
* // SUM(CASE WHEN "status" = $1 THEN "total" ELSE $2 END) AS "paid"
|
|
1031
|
+
* ```
|
|
1032
|
+
*/
|
|
1033
|
+
declare function caseWhen<Row = Record<string, unknown>>(branches: readonly (readonly [WhereArg<Row>, unknown])[], fallback?: unknown): Expression;
|
|
1034
|
+
/**
|
|
1035
|
+
* A `CAST(x AS type)` expression.
|
|
1036
|
+
*
|
|
1037
|
+
* @param operand The expression to convert; a bare string is a column name.
|
|
1038
|
+
* @param to The target type, from the portable {@link CastType} vocabulary.
|
|
1039
|
+
* @returns An expression for the cast.
|
|
1040
|
+
*
|
|
1041
|
+
* @example
|
|
1042
|
+
* ```ts
|
|
1043
|
+
* select(Event).where(cast("externalId", "integer").eq(42));
|
|
1044
|
+
* // WHERE CAST("externalId" AS INTEGER) = $1
|
|
1045
|
+
* ```
|
|
1046
|
+
*/
|
|
1047
|
+
declare function cast(operand: Expression | string, to: CastType): Expression;
|
|
1048
|
+
/**
|
|
1049
|
+
* Build a call to a SQL function.
|
|
1050
|
+
*
|
|
1051
|
+
* A bare string argument is a **column name** — that is the useful default here
|
|
1052
|
+
* (`fn.lower("username")`), and it is why a literal has to be wrapped in
|
|
1053
|
+
* {@link val}. The function name is interpolated into the statement, so it is
|
|
1054
|
+
* validated as a plain identifier and must never come from user input; the
|
|
1055
|
+
* arguments are always rendered through the expression compiler.
|
|
1056
|
+
*
|
|
1057
|
+
* @param name The SQL function name.
|
|
1058
|
+
* @param args Column names or expressions.
|
|
1059
|
+
* @returns An expression for the call.
|
|
1060
|
+
* @throws Error When `name` is not a plain SQL identifier.
|
|
512
1061
|
*/
|
|
513
1062
|
declare function call(name: string, ...args: (Expression | string)[]): Expression;
|
|
514
1063
|
/**
|
|
@@ -550,6 +1099,90 @@ declare function or<Row = Record<string, unknown>>(...inputs: WhereArg<NoInfer<R
|
|
|
550
1099
|
/** Negate a condition with `NOT`. */
|
|
551
1100
|
declare function not<Row = Record<string, unknown>>(input: WhereArg<NoInfer<Row>>): Condition;
|
|
552
1101
|
|
|
1102
|
+
/**
|
|
1103
|
+
* tempest-db-js — window functions.
|
|
1104
|
+
*
|
|
1105
|
+
* The questions a `GROUP BY` cannot answer without a second query: the position
|
|
1106
|
+
* of a row inside its group, a running total, the difference to the previous row.
|
|
1107
|
+
* Without them, "top 3 per region" becomes one query per region — the N+1 of
|
|
1108
|
+
* reporting.
|
|
1109
|
+
*/
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* A function that is only legal **inside** a window.
|
|
1113
|
+
*
|
|
1114
|
+
* `row_number()` and `lag()` are errors without an `OVER` clause — SQLite says
|
|
1115
|
+
* "misuse of window function", PostgreSQL "window function ... requires an OVER
|
|
1116
|
+
* clause". Making them their own type means {@link over} is the only place they
|
|
1117
|
+
* can go, so the mistake does not compile instead of failing at runtime.
|
|
1118
|
+
*/
|
|
1119
|
+
interface WindowFn<T = unknown> {
|
|
1120
|
+
/** The call node this wraps. */
|
|
1121
|
+
readonly call: ExprNode;
|
|
1122
|
+
/** Phantom: the value the function produces. */
|
|
1123
|
+
readonly __t?: T;
|
|
1124
|
+
}
|
|
1125
|
+
/** How a window is framed around the current row. */
|
|
1126
|
+
interface WindowSpec {
|
|
1127
|
+
/** Restart the window for each distinct value of these columns. */
|
|
1128
|
+
readonly partitionBy?: readonly string[];
|
|
1129
|
+
/** Order inside the window — what `rank` and a running total are computed over. */
|
|
1130
|
+
readonly orderBy?: readonly (string | readonly [string, "asc" | "desc"])[];
|
|
1131
|
+
/**
|
|
1132
|
+
* An explicit frame (`ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`).
|
|
1133
|
+
*
|
|
1134
|
+
* Omitted, the database's default applies — and the default is `RANGE`, which
|
|
1135
|
+
* for a running total lumps **every peer** of the current row together. When
|
|
1136
|
+
* rows share an `orderBy` value and that matters, say `ROWS` explicitly.
|
|
1137
|
+
*/
|
|
1138
|
+
readonly frame?: string;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Apply a function `OVER` a window.
|
|
1142
|
+
*
|
|
1143
|
+
* @param fn The window function ({@link rowNumber}, {@link lag}, …) or an
|
|
1144
|
+
* aggregate (`sum("total")`) used as one.
|
|
1145
|
+
* @param spec Partitioning, ordering and framing.
|
|
1146
|
+
* @returns An expression usable in `compute`, `where` and `orderBy`.
|
|
1147
|
+
*
|
|
1148
|
+
* @example
|
|
1149
|
+
* ```ts
|
|
1150
|
+
* select(Sale).compute({
|
|
1151
|
+
* rank: over(rowNumber(), { partitionBy: ["region"], orderBy: [["total", "desc"]] }),
|
|
1152
|
+
* running: over(sum("total"), { partitionBy: ["region"], orderBy: ["date"] }),
|
|
1153
|
+
* });
|
|
1154
|
+
* ```
|
|
1155
|
+
*/
|
|
1156
|
+
declare function over<T = unknown>(fn: WindowFn<T> | Expression<T> | Agg<T>, spec?: WindowSpec): Expression<T>;
|
|
1157
|
+
/** `row_number()` — 1, 2, 3 … within the window, with no ties. */
|
|
1158
|
+
declare function rowNumber(): WindowFn<number>;
|
|
1159
|
+
/** `rank()` — ties share a position, and the next one skips (1, 1, 3). */
|
|
1160
|
+
declare function rank(): WindowFn<number>;
|
|
1161
|
+
/** `dense_rank()` — ties share a position, and the next one does not skip (1, 1, 2). */
|
|
1162
|
+
declare function denseRank(): WindowFn<number>;
|
|
1163
|
+
/** `percent_rank()` — the rank as a fraction between 0 and 1. */
|
|
1164
|
+
declare function percentRank(): WindowFn<number>;
|
|
1165
|
+
/**
|
|
1166
|
+
* `lag(column, offset)` — the value from a row **behind** the current one.
|
|
1167
|
+
*
|
|
1168
|
+
* @param column The column to read.
|
|
1169
|
+
* @param offset How many rows back (default 1).
|
|
1170
|
+
* @returns The expression.
|
|
1171
|
+
*/
|
|
1172
|
+
declare function lag<T = unknown>(column: string, offset?: number): WindowFn<T | null>;
|
|
1173
|
+
/**
|
|
1174
|
+
* `lead(column, offset)` — the value from a row **ahead** of the current one.
|
|
1175
|
+
*
|
|
1176
|
+
* @param column The column to read.
|
|
1177
|
+
* @param offset How many rows forward (default 1).
|
|
1178
|
+
* @returns The expression.
|
|
1179
|
+
*/
|
|
1180
|
+
declare function lead<T = unknown>(column: string, offset?: number): WindowFn<T | null>;
|
|
1181
|
+
/** `first_value(column)` — the first value in the window. */
|
|
1182
|
+
declare function firstValue<T = unknown>(column: string): WindowFn<T | null>;
|
|
1183
|
+
/** `last_value(column)` — the last value in the window (mind the default frame). */
|
|
1184
|
+
declare function lastValue<T = unknown>(column: string): WindowFn<T | null>;
|
|
1185
|
+
|
|
553
1186
|
/**
|
|
554
1187
|
* tempest-db-js — Phase 2: typed INSERT / UPDATE / DELETE builders.
|
|
555
1188
|
*
|
|
@@ -571,11 +1204,11 @@ type Returning = readonly string[] | "*" | null;
|
|
|
571
1204
|
* optional.
|
|
572
1205
|
*/
|
|
573
1206
|
type WriteValues<Row> = {
|
|
574
|
-
[K in keyof Row]: Row[K] | SqlExpression;
|
|
1207
|
+
[K in keyof Row]: Row[K] | SqlExpression | Expression;
|
|
575
1208
|
};
|
|
576
1209
|
/** A partial write shape — the `SET` clause of an UPDATE or a `DO UPDATE`. */
|
|
577
1210
|
type WritePatch<Row> = {
|
|
578
|
-
[K in keyof Row]?: Row[K] | SqlExpression;
|
|
1211
|
+
[K in keyof Row]?: Row[K] | SqlExpression | Expression;
|
|
579
1212
|
};
|
|
580
1213
|
/**
|
|
581
1214
|
* Conflict-resolution clause for an INSERT (`ON CONFLICT`). `target` is the
|
|
@@ -611,8 +1244,19 @@ interface OnConflictUpdateOptions<Full> {
|
|
|
611
1244
|
/** Serializable AST for an INSERT. */
|
|
612
1245
|
interface InsertNode {
|
|
613
1246
|
readonly kind: "insert";
|
|
1247
|
+
/** Per-column codecs for custom types, by property name. */
|
|
1248
|
+
readonly codecs?: Readonly<Record<string, ColumnCodec>> | undefined;
|
|
614
1249
|
readonly table: string;
|
|
615
1250
|
readonly values: readonly Record<string, unknown>[];
|
|
1251
|
+
/**
|
|
1252
|
+
* A `SELECT` feeding the insert, with the target columns it fills.
|
|
1253
|
+
*
|
|
1254
|
+
* Set by {@link InsertBuilder.fromSelect}; mutually exclusive with `values`.
|
|
1255
|
+
*/
|
|
1256
|
+
readonly fromSelect?: {
|
|
1257
|
+
readonly columns: readonly string[];
|
|
1258
|
+
readonly select: unknown;
|
|
1259
|
+
} | undefined;
|
|
616
1260
|
readonly returning: Returning;
|
|
617
1261
|
/** Conflict handling (`ON CONFLICT ...`), or `undefined` for none. */
|
|
618
1262
|
readonly onConflict?: OnConflict;
|
|
@@ -676,6 +1320,31 @@ declare class InsertBuilder<Full, Ins, Ret = number> {
|
|
|
676
1320
|
* @throws ValidationError When a `set` value cannot be bound.
|
|
677
1321
|
*/
|
|
678
1322
|
onConflictDoUpdate(target: readonly (keyof Full & string)[], set: WritePatch<Full>, options?: OnConflictUpdateOptions<Full>): InsertBuilder<Full, Ins, Ret>;
|
|
1323
|
+
/**
|
|
1324
|
+
* Fill the table from another query — `INSERT INTO t (a, b) SELECT …`.
|
|
1325
|
+
*
|
|
1326
|
+
* The rows never leave the database, which is the point: archiving or copying a
|
|
1327
|
+
* million rows should not become a million round trips through this process.
|
|
1328
|
+
*
|
|
1329
|
+
* @param columns The target columns, in the order the query projects them.
|
|
1330
|
+
* @param query The query producing the rows.
|
|
1331
|
+
* @returns A builder ready to execute.
|
|
1332
|
+
* @throws Error When no target column is given.
|
|
1333
|
+
*
|
|
1334
|
+
* @example
|
|
1335
|
+
* ```ts
|
|
1336
|
+
* insert(ArchivedOrder).fromSelect(
|
|
1337
|
+
* ["id", "total"],
|
|
1338
|
+
* select(Order, ["id", "total"]).where({ createdAt: { lt: cutoff } }),
|
|
1339
|
+
* );
|
|
1340
|
+
* ```
|
|
1341
|
+
*/
|
|
1342
|
+
fromSelect<K extends keyof Ins & string>(columns: readonly K[], query: {
|
|
1343
|
+
readonly node: unknown;
|
|
1344
|
+
readonly __row: {
|
|
1345
|
+
[P in K]: unknown;
|
|
1346
|
+
};
|
|
1347
|
+
}): InsertBuilder<Full, Ins, Ret>;
|
|
679
1348
|
/** Return the full inserted row(s). */
|
|
680
1349
|
returning(): InsertBuilder<Full, Ins, Full>;
|
|
681
1350
|
/** Return only the given columns of the inserted row(s). */
|
|
@@ -686,7 +1355,14 @@ declare function insert<C extends ModelClass>(model: C): InsertBuilder<InferMode
|
|
|
686
1355
|
/** Serializable AST for an UPDATE. */
|
|
687
1356
|
interface UpdateNode {
|
|
688
1357
|
readonly kind: "update";
|
|
1358
|
+
/** Per-column codecs for custom types, by property name. */
|
|
1359
|
+
readonly codecs?: Readonly<Record<string, ColumnCodec>> | undefined;
|
|
689
1360
|
readonly table: string;
|
|
1361
|
+
/** Extra source tables (`UPDATE ... FROM other`), by alias. */
|
|
1362
|
+
readonly from?: readonly {
|
|
1363
|
+
readonly table: string;
|
|
1364
|
+
readonly alias: string;
|
|
1365
|
+
}[];
|
|
690
1366
|
readonly set: Record<string, unknown>;
|
|
691
1367
|
readonly where: CondNode | undefined;
|
|
692
1368
|
/** True once a where-clause or explicit opt-in makes the write safe. */
|
|
@@ -732,6 +1408,21 @@ declare class UpdateBuilder<Full, Guarded extends boolean, Ret = number> {
|
|
|
732
1408
|
* ```
|
|
733
1409
|
*/
|
|
734
1410
|
set(values: WritePatch<Full>): UpdateBuilder<Full, Guarded, Ret>;
|
|
1411
|
+
/**
|
|
1412
|
+
* Read from another table while updating — `UPDATE t SET … FROM other WHERE …`.
|
|
1413
|
+
*
|
|
1414
|
+
* The join condition goes in `where`, where SQL wants it:
|
|
1415
|
+
* `.where({ customerId: col("c.id") })`.
|
|
1416
|
+
*
|
|
1417
|
+
* PostgreSQL and SQLite (3.33+) only. MySQL spells this as a multi-table
|
|
1418
|
+
* `UPDATE a JOIN b`, which is out of this project's active scope, so it throws
|
|
1419
|
+
* there rather than emitting something the server rejects.
|
|
1420
|
+
*
|
|
1421
|
+
* @param model The extra source.
|
|
1422
|
+
* @param alias The name to reference it by.
|
|
1423
|
+
* @returns A builder carrying the extra source.
|
|
1424
|
+
*/
|
|
1425
|
+
from<C extends ModelClass>(model: C, alias: string): UpdateBuilder<Full, Guarded, Ret>;
|
|
735
1426
|
/** Restrict the rows to update. Marks the builder safe to execute. */
|
|
736
1427
|
where(input: WhereInput<Full> | Condition): UpdateBuilder<Full, true, Ret>;
|
|
737
1428
|
/** Explicit opt-in to update EVERY row. Use deliberately. */
|
|
@@ -746,7 +1437,14 @@ declare function update<C extends ModelClass>(model: C): UpdateBuilder<InferMode
|
|
|
746
1437
|
/** Serializable AST for a DELETE. */
|
|
747
1438
|
interface DeleteNode {
|
|
748
1439
|
readonly kind: "delete";
|
|
1440
|
+
/** Per-column codecs for custom types, by property name. */
|
|
1441
|
+
readonly codecs?: Readonly<Record<string, ColumnCodec>> | undefined;
|
|
749
1442
|
readonly table: string;
|
|
1443
|
+
/** Extra source tables (`DELETE ... USING other`), by alias. */
|
|
1444
|
+
readonly using?: readonly {
|
|
1445
|
+
readonly table: string;
|
|
1446
|
+
readonly alias: string;
|
|
1447
|
+
}[];
|
|
750
1448
|
readonly where: CondNode | undefined;
|
|
751
1449
|
readonly guarded: boolean;
|
|
752
1450
|
readonly returning: Returning;
|
|
@@ -770,6 +1468,18 @@ declare class DeleteBuilder<Full, Guarded extends boolean, Ret = number> {
|
|
|
770
1468
|
/** The source model, used to coerce returned rows on execution. */
|
|
771
1469
|
source: ModelClass);
|
|
772
1470
|
private with;
|
|
1471
|
+
/**
|
|
1472
|
+
* Delete by matching another table — `DELETE FROM t USING other WHERE …`.
|
|
1473
|
+
*
|
|
1474
|
+
* PostgreSQL only. SQLite and MySQL have no `USING` here; there the portable
|
|
1475
|
+
* form is `where({ id: { in: select(Other, ["id"]).where(...).asSubquery("id") } })`,
|
|
1476
|
+
* and this throws rather than pretending.
|
|
1477
|
+
*
|
|
1478
|
+
* @param model The extra source.
|
|
1479
|
+
* @param alias The name to reference it by.
|
|
1480
|
+
* @returns A builder carrying the extra source.
|
|
1481
|
+
*/
|
|
1482
|
+
using<C extends ModelClass>(model: C, alias: string): DeleteBuilder<Full, Guarded, Ret>;
|
|
773
1483
|
/** Restrict the rows to delete. Marks the builder safe to execute. */
|
|
774
1484
|
where(input: WhereInput<Full> | Condition): DeleteBuilder<Full, true, Ret>;
|
|
775
1485
|
/** Explicit opt-in to delete EVERY row. Use deliberately. */
|
|
@@ -855,6 +1565,10 @@ declare class ValidationError extends Error {
|
|
|
855
1565
|
readonly issues: readonly string[];
|
|
856
1566
|
constructor(table: string, issues: readonly string[]);
|
|
857
1567
|
}
|
|
1568
|
+
/** Encode one native row value to its JSON-safe form, by column kind. */
|
|
1569
|
+
declare function encodeValue(column: Column<unknown>, value: unknown): unknown;
|
|
1570
|
+
/** Decode one dict value to its native row form, by column kind. */
|
|
1571
|
+
declare function decodeValue(column: Column<unknown>, value: unknown): unknown;
|
|
858
1572
|
/**
|
|
859
1573
|
* Convert a row to a plain dict of native values, restricted to known columns.
|
|
860
1574
|
* Strips any non-column properties; keeps `Date`/`bigint`/`Uint8Array` as-is.
|
|
@@ -893,400 +1607,166 @@ declare function fromDict<C extends ModelClass>(model: C, data: Record<string, u
|
|
|
893
1607
|
declare function parse<C extends ModelClass>(model: C, json: string): InferModel<C>;
|
|
894
1608
|
|
|
895
1609
|
/**
|
|
896
|
-
* tempest-db-js —
|
|
1610
|
+
* tempest-db-js — capture the query plans of everything a block runs.
|
|
897
1611
|
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
901
|
-
*
|
|
902
|
-
* Columns are aliased in SQL (`"user"."id" AS "user.id"`) so a flat driver row is
|
|
903
|
-
* split back into one nested object per source, each coerced by its model.
|
|
1612
|
+
* A development tool for "why is this endpoint slow?", where the honest answer
|
|
1613
|
+
* usually needs the database's own opinion rather than a wall-clock number.
|
|
1614
|
+
* Wrapping the code beats copying SQL out of a log by hand, because the
|
|
1615
|
+
* parameters that go into the plan are the ones the code actually used.
|
|
904
1616
|
*/
|
|
905
1617
|
|
|
906
|
-
/** One
|
|
907
|
-
interface
|
|
908
|
-
|
|
909
|
-
readonly
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
/** A
|
|
915
|
-
|
|
916
|
-
readonly alias: string;
|
|
917
|
-
readonly column: string;
|
|
1618
|
+
/** One statement's plan. */
|
|
1619
|
+
interface QueryPlan {
|
|
1620
|
+
/** The statement that was explained. */
|
|
1621
|
+
readonly sql: string;
|
|
1622
|
+
/** The parameters it ran with. */
|
|
1623
|
+
readonly params: readonly unknown[];
|
|
1624
|
+
/** The database's plan, in its own shape (JSON on PostgreSQL, rows on SQLite). */
|
|
1625
|
+
readonly plan: unknown;
|
|
1626
|
+
/** A one-line, human-readable digest of the plan. */
|
|
1627
|
+
summary(): string;
|
|
918
1628
|
}
|
|
919
|
-
/**
|
|
920
|
-
|
|
921
|
-
/**
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
* {@link WhereInput}, but qualified per source. `like` on a numeric join column,
|
|
926
|
-
* or `gt` on a string one, is a compile error.
|
|
927
|
-
*/
|
|
928
|
-
type JoinWhereInput<S extends Sources> = Partial<UnionToIntersection<{
|
|
929
|
-
[A in keyof S]: {
|
|
930
|
-
[C in keyof NonNullable<S[A]> & string as `${A & string}.${C}`]: NonNullable<S[A]>[C] | OperatorsFor<NonNullable<NonNullable<S[A]>[C]>>;
|
|
931
|
-
};
|
|
932
|
-
}[keyof S]>>;
|
|
933
|
-
/** Serializable AST for a multi-table SELECT. */
|
|
934
|
-
interface JoinNode {
|
|
935
|
-
readonly kind: "join_select";
|
|
936
|
-
readonly base: {
|
|
937
|
-
readonly table: string;
|
|
938
|
-
readonly alias: string;
|
|
939
|
-
};
|
|
940
|
-
readonly joins: readonly JoinClause[];
|
|
941
|
-
readonly selections: readonly JoinSelection[];
|
|
942
|
-
readonly where: CondNode | undefined;
|
|
943
|
-
readonly orderBy: readonly {
|
|
944
|
-
readonly ref: string;
|
|
945
|
-
readonly direction: SortDirection;
|
|
946
|
-
}[];
|
|
947
|
-
readonly limit: number | undefined;
|
|
948
|
-
readonly offset: number | undefined;
|
|
949
|
-
/** Per-alias property → column maps, for the sources that rename columns. */
|
|
950
|
-
readonly names?: Readonly<Record<string, NameMap>> | undefined;
|
|
1629
|
+
/** Everything a block ran, with a plan each. */
|
|
1630
|
+
interface ExplainReport {
|
|
1631
|
+
/** One entry per statement, in execution order. */
|
|
1632
|
+
readonly plans: readonly QueryPlan[];
|
|
1633
|
+
/** A multi-line digest of every plan, for a test failure message or a log. */
|
|
1634
|
+
summary(): string;
|
|
951
1635
|
}
|
|
952
|
-
/**
|
|
953
|
-
|
|
954
|
-
/**
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
* Immutable, chainable multi-table SELECT builder.
|
|
964
|
-
*
|
|
965
|
-
* @typeParam S - the accumulated sources (alias → row type; nullable for left joins).
|
|
966
|
-
*/
|
|
967
|
-
declare class JoinBuilder<S extends Sources> {
|
|
968
|
-
readonly node: JoinNode;
|
|
969
|
-
/** Source models keyed by alias, for SQL expansion and row coercion. */
|
|
970
|
-
readonly sources: Readonly<Record<string, ModelClass>>;
|
|
971
|
-
/** Phantom: the composite result row type. */
|
|
972
|
-
readonly __row: {
|
|
973
|
-
[A in keyof S]: S[A];
|
|
974
|
-
};
|
|
975
|
-
constructor(node: JoinNode,
|
|
976
|
-
/** Source models keyed by alias, for SQL expansion and row coercion. */
|
|
977
|
-
sources: Readonly<Record<string, ModelClass>>);
|
|
978
|
-
private add;
|
|
979
|
-
private clause;
|
|
980
|
-
/** Inner join another model under `alias`. */
|
|
981
|
-
innerJoin<C extends ModelClass, A extends string>(model: C, alias: A, on: JoinOn<S, A, C>): JoinBuilder<S & {
|
|
982
|
-
[K in A]: InferModel<C>;
|
|
983
|
-
}>;
|
|
984
|
-
/** Left (outer) join another model under `alias` — its side becomes nullable. */
|
|
985
|
-
leftJoin<C extends ModelClass, A extends string>(model: C, alias: A, on: JoinOn<S, A, C>): JoinBuilder<S & {
|
|
986
|
-
[K in A]: InferModel<C> | null;
|
|
987
|
-
}>;
|
|
988
|
-
/** Filter by `alias.column` references (object form) or an `and`/`or`/`not`. */
|
|
989
|
-
where(input: JoinWhereInput<S> | Condition): JoinBuilder<S>;
|
|
990
|
-
/** Order by an `alias.column` reference. */
|
|
991
|
-
orderBy(ref: ColRef<S>, direction?: SortDirection): JoinBuilder<S>;
|
|
992
|
-
limit(n: number): JoinBuilder<S>;
|
|
993
|
-
offset(n: number): JoinBuilder<S>;
|
|
1636
|
+
/** Options for {@link explainQueries}. */
|
|
1637
|
+
interface ExplainOptions {
|
|
1638
|
+
/**
|
|
1639
|
+
* Run `EXPLAIN ANALYZE`, which **executes** the statement to measure it.
|
|
1640
|
+
*
|
|
1641
|
+
* Refused for anything that is not a `SELECT`: analyzing an `UPDATE` would
|
|
1642
|
+
* apply it a second time.
|
|
1643
|
+
*/
|
|
1644
|
+
readonly analyze?: boolean;
|
|
1645
|
+
/** Explain only the statements matching this predicate. */
|
|
1646
|
+
readonly filter?: (sql: string) => boolean;
|
|
994
1647
|
}
|
|
1648
|
+
/** True for a statement that only reads. */
|
|
1649
|
+
declare function isReadOnlyStatement(sql: string): boolean;
|
|
995
1650
|
/**
|
|
996
|
-
*
|
|
1651
|
+
* Build the digest of a PostgreSQL JSON plan or a SQLite plan row set.
|
|
997
1652
|
*
|
|
998
|
-
* @param
|
|
999
|
-
* @
|
|
1000
|
-
* @returns A `JoinBuilder` with the base source registered.
|
|
1653
|
+
* @param plan The raw plan.
|
|
1654
|
+
* @returns One line describing it.
|
|
1001
1655
|
*/
|
|
1002
|
-
declare function
|
|
1003
|
-
[K in A]: InferModel<C>;
|
|
1004
|
-
}>;
|
|
1656
|
+
declare function summarizePlan(plan: unknown): string;
|
|
1005
1657
|
|
|
1006
1658
|
/**
|
|
1007
|
-
* tempest-db-js —
|
|
1659
|
+
* tempest-db-js — an opt-in unit of work with an identity map.
|
|
1008
1660
|
*
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
* never string interpolation, so it is injection-safe by construction.
|
|
1661
|
+
* The default stays what it has always been: a row is a plain object, and a write
|
|
1662
|
+
* happens when you ask for it. This adds the other model, for the code that wants
|
|
1663
|
+
* it — load a row once, mutate it, and let one `flush()` work out the statements.
|
|
1013
1664
|
*
|
|
1014
|
-
*
|
|
1665
|
+
* Two things it buys, both of which the plain path cannot:
|
|
1666
|
+
*
|
|
1667
|
+
* - **Identity.** Loading the same row twice returns the **same object**, so two
|
|
1668
|
+
* references cannot drift apart in memory while both believe they are the row.
|
|
1669
|
+
* - **Batching.** Ten mutations become the statements the changes actually
|
|
1670
|
+
* require, in one transaction, instead of ten round trips.
|
|
1015
1671
|
*/
|
|
1016
1672
|
|
|
1017
|
-
/**
|
|
1018
|
-
|
|
1019
|
-
readonly sql: string;
|
|
1020
|
-
readonly params: readonly unknown[];
|
|
1021
|
-
}
|
|
1022
|
-
/** Any compilable AST node. */
|
|
1023
|
-
type QueryNode = SelectNode | InsertNode | UpdateNode | DeleteNode | JoinNode;
|
|
1673
|
+
/** Phantom brand distinguishing a tracked row from a plain one. */
|
|
1674
|
+
declare const TRACKED: unique symbol;
|
|
1024
1675
|
/**
|
|
1025
|
-
*
|
|
1026
|
-
*
|
|
1676
|
+
* A row the unit of work is watching.
|
|
1677
|
+
*
|
|
1678
|
+
* The brand is what makes "tracked" visible in the type: a function taking a
|
|
1679
|
+
* `Tracked<Row>` cannot be handed a plain object that nothing will ever flush.
|
|
1027
1680
|
*/
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1681
|
+
type Tracked<Row> = Row & {
|
|
1682
|
+
readonly [TRACKED]?: true;
|
|
1683
|
+
};
|
|
1684
|
+
/** What a flush did. */
|
|
1685
|
+
interface FlushResult {
|
|
1686
|
+
/** Rows inserted. */
|
|
1687
|
+
readonly inserted: number;
|
|
1688
|
+
/** Rows updated. */
|
|
1689
|
+
readonly updated: number;
|
|
1690
|
+
/** Rows deleted. */
|
|
1691
|
+
readonly deleted: number;
|
|
1033
1692
|
}
|
|
1034
1693
|
/**
|
|
1035
|
-
*
|
|
1036
|
-
*
|
|
1694
|
+
* An identity map plus a change log, flushed as one transaction.
|
|
1695
|
+
*
|
|
1696
|
+
* Scope it explicitly — one per request, one per job — and flush it before it
|
|
1697
|
+
* goes away. Nothing here is global.
|
|
1037
1698
|
*/
|
|
1038
|
-
declare
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
*/
|
|
1045
|
-
private static readonly insertTemplates;
|
|
1046
|
-
/** Quoted-identifier cache (see {@link quoteId}). Shared across dialects. */
|
|
1047
|
-
private static readonly quotedIds;
|
|
1048
|
-
/** Render the n-th (1-based) placeholder. */
|
|
1049
|
-
protected abstract placeholder(index: number): string;
|
|
1050
|
-
/** Render a case-insensitive LIKE for the active dialect. */
|
|
1051
|
-
protected abstract ilike(column: string, param: string): string;
|
|
1699
|
+
declare class UnitOfWork {
|
|
1700
|
+
private readonly session;
|
|
1701
|
+
private readonly entries;
|
|
1702
|
+
constructor(session: AsyncSession);
|
|
1703
|
+
/** How many rows are being tracked. */
|
|
1704
|
+
get size(): number;
|
|
1052
1705
|
/**
|
|
1053
|
-
*
|
|
1054
|
-
* what an `IN (SELECT ...)` may contain. The default accepts everything.
|
|
1706
|
+
* Load a row by primary key, or return the one already loaded.
|
|
1055
1707
|
*
|
|
1056
|
-
*
|
|
1057
|
-
*
|
|
1708
|
+
* The second call for the same key does **not** hit the database and returns
|
|
1709
|
+
* the **same object** as the first.
|
|
1710
|
+
*
|
|
1711
|
+
* @param model The model class.
|
|
1712
|
+
* @param key The primary key — a value, or an object for a composite key.
|
|
1713
|
+
* @returns The tracked row, or `null` when there is none.
|
|
1058
1714
|
*/
|
|
1059
|
-
|
|
1715
|
+
get<C extends ModelClass>(model: C, key: unknown): Promise<Tracked<InferModel<C>> | null>;
|
|
1060
1716
|
/**
|
|
1061
|
-
*
|
|
1717
|
+
* Track an existing row that was loaded elsewhere.
|
|
1062
1718
|
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
* @param op The array operator name.
|
|
1067
|
-
* @returns The SQL operator text.
|
|
1068
|
-
* @throws Error On a dialect without native array support.
|
|
1719
|
+
* @param model The model class.
|
|
1720
|
+
* @param row The row, as loaded.
|
|
1721
|
+
* @returns The tracked object — the same one, if this row is already known.
|
|
1069
1722
|
*/
|
|
1070
|
-
|
|
1723
|
+
track<C extends ModelClass>(model: C, row: InferModel<C>): Tracked<InferModel<C>>;
|
|
1071
1724
|
/**
|
|
1072
|
-
*
|
|
1725
|
+
* Schedule an insert.
|
|
1073
1726
|
*
|
|
1074
|
-
*
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1727
|
+
* @param model The model class.
|
|
1728
|
+
* @param data The row to insert; it must carry the primary key, since the
|
|
1729
|
+
* identity map is keyed by it and a database-generated id is not known yet.
|
|
1730
|
+
* @returns The tracked row.
|
|
1731
|
+
* @throws Error When the key is incomplete.
|
|
1078
1732
|
*/
|
|
1079
|
-
|
|
1080
|
-
/** Compile any node to `{ sql, params }`. */
|
|
1081
|
-
compile(node: QueryNode): CompiledQuery;
|
|
1733
|
+
add<C extends ModelClass>(model: C, data: InferInsert<C>): Tracked<InferModel<C>>;
|
|
1082
1734
|
/**
|
|
1083
|
-
*
|
|
1084
|
-
* property name to the real column name for that alias's model.
|
|
1735
|
+
* Schedule a delete.
|
|
1085
1736
|
*
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1737
|
+
* A row added and then removed before the flush simply disappears — no
|
|
1738
|
+
* statement is emitted for it.
|
|
1739
|
+
*
|
|
1740
|
+
* @param model The model class.
|
|
1741
|
+
* @param row The row to delete.
|
|
1089
1742
|
*/
|
|
1090
|
-
|
|
1743
|
+
remove<C extends ModelClass>(model: C, row: InferModel<C>): void;
|
|
1091
1744
|
/**
|
|
1092
|
-
*
|
|
1093
|
-
* database column name first.
|
|
1745
|
+
* Write every pending change, in one transaction.
|
|
1094
1746
|
*
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1747
|
+
* Order is inserts, then updates, then deletes — the order that keeps a
|
|
1748
|
+
* foreign key satisfied when a new parent and its children are flushed
|
|
1749
|
+
* together. It is **not** a topological sort: a graph that needs one should be
|
|
1750
|
+
* flushed in stages.
|
|
1097
1751
|
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1752
|
+
* A statement that fails takes the whole flush with it, since it all runs in
|
|
1753
|
+
* one transaction. The tracked state is left untouched in that case, so the
|
|
1754
|
+
* caller can fix and flush again.
|
|
1755
|
+
*
|
|
1756
|
+
* @returns How many rows were inserted, updated and deleted.
|
|
1101
1757
|
*/
|
|
1102
|
-
|
|
1758
|
+
flush(): Promise<FlushResult>;
|
|
1759
|
+
/** Forget everything tracked, without writing. */
|
|
1760
|
+
clear(): void;
|
|
1103
1761
|
/**
|
|
1104
|
-
*
|
|
1762
|
+
* The identity-map key for a row or a primary key.
|
|
1105
1763
|
*
|
|
1106
|
-
*
|
|
1107
|
-
*
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1110
|
-
* @param expr The branded expression.
|
|
1111
|
-
* @param params The parameter collector for the statement being compiled.
|
|
1112
|
-
* @returns The SQL text of the expression.
|
|
1113
|
-
*/
|
|
1114
|
-
protected renderExpression(expr: SqlExpression, params: Params): string;
|
|
1115
|
-
/** Render one write value: a SQL expression inline, anything else as a parameter. */
|
|
1116
|
-
protected renderValue(value: unknown, params: Params): string;
|
|
1117
|
-
/**
|
|
1118
|
-
* Render a row-level locking clause (`FOR UPDATE ...`).
|
|
1119
|
-
*
|
|
1120
|
-
* Standard on PostgreSQL and MySQL 8.0+; SQLite overrides it to throw.
|
|
1121
|
-
*
|
|
1122
|
-
* @param lock The locking clause from the node.
|
|
1123
|
-
* @returns The SQL text, leading space included.
|
|
1124
|
-
*/
|
|
1125
|
-
protected renderLock(lock: LockClause): string;
|
|
1126
|
-
/**
|
|
1127
|
-
* Compile a SELECT.
|
|
1128
|
-
*
|
|
1129
|
-
* Two alias rules differ between clauses and are handled here: PostgreSQL does
|
|
1130
|
-
* NOT accept a `SELECT` alias in `HAVING`, so an aggregate key is re-emitted as
|
|
1131
|
-
* its expression (`COUNT(*) > $1`), a form every dialect accepts; `ORDER BY`,
|
|
1132
|
-
* by contrast, accepts the output alias everywhere, so it is emitted as
|
|
1133
|
-
* written.
|
|
1134
|
-
*
|
|
1135
|
-
* @param node The select AST.
|
|
1136
|
-
* @param params The parameter collector.
|
|
1137
|
-
* @returns The SQL text.
|
|
1138
|
-
*/
|
|
1139
|
-
private compileSelect;
|
|
1140
|
-
/**
|
|
1141
|
-
* Compile an INSERT.
|
|
1142
|
-
*
|
|
1143
|
-
* Takes the cached fast path only when the statement text is a pure function of
|
|
1144
|
-
* its structure. A SQL expression among the values, or a conflict predicate,
|
|
1145
|
-
* makes the text depend on the values themselves — those compile uncached, in
|
|
1146
|
-
* SQL order, so placeholder positions stay correct.
|
|
1147
|
-
*/
|
|
1148
|
-
private compileInsert;
|
|
1149
|
-
/**
|
|
1150
|
-
* Compile an INSERT without the template cache, rendering clauses in statement
|
|
1151
|
-
* order so every parameter is bound at the position it appears.
|
|
1152
|
-
*
|
|
1153
|
-
* @param node The insert node.
|
|
1154
|
-
* @param columns The column keys shared by every row.
|
|
1155
|
-
* @param params The parameter collector.
|
|
1156
|
-
* @returns The SQL text.
|
|
1157
|
-
*/
|
|
1158
|
-
private compileInsertDirect;
|
|
1159
|
-
/**
|
|
1160
|
-
* The INSERT SQL template for a given structure, cached across calls.
|
|
1161
|
-
*
|
|
1162
|
-
* The text depends only on (dialect, table, columns, row count, returning,
|
|
1163
|
-
* conflict shape) — never on the bound values — and placeholder positions are
|
|
1164
|
-
* deterministic from the counts (a fresh statement always starts binding at 1).
|
|
1165
|
-
* So a per-row insert loop compiles the string once and reuses it every row.
|
|
1166
|
-
*/
|
|
1167
|
-
private insertTemplate;
|
|
1168
|
-
/**
|
|
1169
|
-
* Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
|
|
1170
|
-
* `ON CONFLICT (...) [WHERE predicate] DO NOTHING | DO UPDATE SET ... [WHERE ...]`;
|
|
1171
|
-
* MySQL overrides this.
|
|
1172
|
-
*
|
|
1173
|
-
* The index predicate is rendered before the `DO UPDATE` assignments because
|
|
1174
|
-
* that is where it sits in the statement, so its parameters bind first.
|
|
1175
|
-
*
|
|
1176
|
-
* @param onConflict The conflict clause from the node.
|
|
1177
|
-
* @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
|
|
1178
|
-
* @param nextValue Yields the SQL for the next `DO UPDATE` assignment value.
|
|
1179
|
-
* @param names The node's property → column map, if any.
|
|
1180
|
-
* @param params The parameter collector, for the predicates.
|
|
1181
|
-
* @returns The SQL text, leading space included.
|
|
1182
|
-
*/
|
|
1183
|
-
protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextValue: () => string, names: NameMap | undefined, params: Params): string;
|
|
1184
|
-
private compileUpdate;
|
|
1185
|
-
private compileDelete;
|
|
1186
|
-
private compileJoin;
|
|
1187
|
-
protected compileReturning(returning: readonly string[] | "*" | null, names?: NameMap | undefined): string;
|
|
1188
|
-
/**
|
|
1189
|
-
* Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
|
|
1190
|
-
* key to a quoted identifier — `quoteId` for single-table, `qualify` for joins —
|
|
1191
|
-
* so select/update/delete/join all share this one compiler.
|
|
1192
|
-
*/
|
|
1193
|
-
private compileCondition;
|
|
1194
|
-
/**
|
|
1195
|
-
* Render one side of a comparison.
|
|
1196
|
-
*
|
|
1197
|
-
* A column reference goes through `idFor`, so an explicit `.name()` mapping and
|
|
1198
|
-
* join qualification apply here exactly as they do in the object form of
|
|
1199
|
-
* `where` — `col()` is not a way around them. Only a `value` node binds.
|
|
1200
|
-
*
|
|
1201
|
-
* @param node The expression AST.
|
|
1202
|
-
* @param params The parameter collector.
|
|
1203
|
-
* @param idFor The identifier resolver for the enclosing statement.
|
|
1204
|
-
* @returns The SQL text of the expression.
|
|
1205
|
-
*/
|
|
1206
|
-
private renderExpr;
|
|
1207
|
-
/**
|
|
1208
|
-
* Compile a comparison whose right-hand side is another expression rather than
|
|
1209
|
-
* a bound value (`total > paid`, `lower(a) = lower(b)`).
|
|
1210
|
-
*
|
|
1211
|
-
* The list and null operators are excluded: `IN`, `BETWEEN` and `IS NULL` take
|
|
1212
|
-
* a value operand, and accepting an expression there would silently compile to
|
|
1213
|
-
* something else.
|
|
1214
|
-
*
|
|
1215
|
-
* @param left The rendered left-hand side.
|
|
1216
|
-
* @param op The operator name.
|
|
1217
|
-
* @param right The rendered right-hand side.
|
|
1218
|
-
* @returns The SQL text of the predicate.
|
|
1219
|
-
* @throws Error When the operator needs a value operand.
|
|
1220
|
-
*/
|
|
1221
|
-
private compileExprOperator;
|
|
1222
|
-
private compileOperator;
|
|
1223
|
-
/**
|
|
1224
|
-
* Compile `IN` / `NOT IN`, whose operand is either a value list or a
|
|
1225
|
-
* single-column subquery.
|
|
1226
|
-
*
|
|
1227
|
-
* The subquery is rendered at the position it appears in the outer statement
|
|
1228
|
-
* and shares the same parameter collector, so its own placeholders land in the
|
|
1229
|
-
* right order — and it keeps its own `names` map, since the inner model may use
|
|
1230
|
-
* a different naming convention than the outer one.
|
|
1231
|
-
*
|
|
1232
|
-
* @param id The quoted column identifier being tested.
|
|
1233
|
-
* @param operand A list of values, or a {@link Subquery}.
|
|
1234
|
-
* @param params The parameter collector for the statement being compiled.
|
|
1235
|
-
* @param negate True for `NOT IN`.
|
|
1236
|
-
* @returns The SQL text of the predicate.
|
|
1237
|
-
*/
|
|
1238
|
-
private compileIn;
|
|
1239
|
-
}
|
|
1240
|
-
/** SQLite dialect: `?` placeholders; `ILIKE` falls back to `LIKE` (ASCII-insensitive). */
|
|
1241
|
-
declare class SqliteDialect extends BaseDialect {
|
|
1242
|
-
readonly name: "sqlite";
|
|
1243
|
-
protected placeholder(): string;
|
|
1244
|
-
protected ilike(column: string, param: string): string;
|
|
1245
|
-
/**
|
|
1246
|
-
* SQLite has no row-level locking, so a lock request is an error rather than a
|
|
1247
|
-
* silently unlocked `SELECT` — a lock that does not exist only shows up as
|
|
1248
|
-
* duplicated work under production concurrency.
|
|
1249
|
-
*/
|
|
1250
|
-
protected renderLock(): string;
|
|
1251
|
-
}
|
|
1252
|
-
/** PostgreSQL dialect: `$1` placeholders; native `ILIKE`; native array operators. */
|
|
1253
|
-
declare class PostgresDialect extends BaseDialect {
|
|
1254
|
-
readonly name: "postgresql";
|
|
1255
|
-
protected placeholder(index: number): string;
|
|
1256
|
-
protected ilike(column: string, param: string): string;
|
|
1257
|
-
protected arrayOperator(op: "contains" | "containedBy" | "overlaps"): string;
|
|
1258
|
-
}
|
|
1259
|
-
/**
|
|
1260
|
-
* MySQL dialect: `?` placeholders, backtick identifiers, `ON DUPLICATE KEY
|
|
1261
|
-
* UPDATE` for upsert, and case-insensitive `LIKE` (default collation). MySQL has
|
|
1262
|
-
* no `RETURNING`, so requesting it throws.
|
|
1263
|
-
*/
|
|
1264
|
-
declare class MysqlDialect extends BaseDialect {
|
|
1265
|
-
readonly name: "mysql";
|
|
1266
|
-
protected placeholder(): string;
|
|
1267
|
-
protected ilike(column: string, param: string): string;
|
|
1268
|
-
protected quoteId(name: string): string;
|
|
1269
|
-
/**
|
|
1270
|
-
* MySQL rejects `LIMIT` inside an `IN` subquery with
|
|
1271
|
-
* `ER_NOT_SUPPORTED_YET: This version of MySQL doesn't yet support
|
|
1272
|
-
* 'LIMIT & IN/ALL/ANY/SOME subquery'`. Failing at compile time names the fix
|
|
1273
|
-
* instead of surfacing that error from the driver at runtime.
|
|
1274
|
-
*/
|
|
1275
|
-
protected checkSubquery(node: SelectNode): void;
|
|
1276
|
-
protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextValue: () => string, names: NameMap | undefined): string;
|
|
1277
|
-
/**
|
|
1278
|
-
* MySQL has no `RETURNING`, so it cannot be compiled into a statement.
|
|
1279
|
-
*
|
|
1280
|
-
* `session.execute()` still honors `.returning()` on a **single-row INSERT** by
|
|
1281
|
-
* running the insert and reading the row back by key on the same connection —
|
|
1282
|
-
* that is execution, not compilation, so it never reaches here. Compiling a
|
|
1283
|
-
* node with `returning` directly is an error, rather than SQL that silently
|
|
1284
|
-
* returns nothing.
|
|
1764
|
+
* @param model The model class.
|
|
1765
|
+
* @param key The row, or the key.
|
|
1766
|
+
* @returns A stable string key.
|
|
1285
1767
|
*/
|
|
1286
|
-
|
|
1768
|
+
private identity;
|
|
1287
1769
|
}
|
|
1288
|
-
/** Get a dialect instance by name. */
|
|
1289
|
-
declare function getDialect(name: Dialect): BaseDialect;
|
|
1290
1770
|
|
|
1291
1771
|
/** The outcome of running one statement. */
|
|
1292
1772
|
interface DriverResult {
|
|
@@ -1346,17 +1826,51 @@ declare class NodeSqliteDriver implements SyncDriver {
|
|
|
1346
1826
|
iterate(sql: string, params: readonly unknown[]): IterableIterator<Record<string, unknown>>;
|
|
1347
1827
|
close(): void;
|
|
1348
1828
|
}
|
|
1829
|
+
/**
|
|
1830
|
+
* SQLite driver backed by the `better-sqlite3` peer dependency.
|
|
1831
|
+
*
|
|
1832
|
+
* Selected with `{ driver: "better-sqlite3" }` or the URL suffix
|
|
1833
|
+
* `sqlite+better-sqlite3://…`; the built-in `node:sqlite` stays the default, so
|
|
1834
|
+
* nothing has to be installed to use SQLite. Pick this one when the service
|
|
1835
|
+
* already runs on better-sqlite3, or needs what it exposes and `node:sqlite`
|
|
1836
|
+
* does not — `pragma()`, loadable extensions, its own WAL helpers.
|
|
1837
|
+
*
|
|
1838
|
+
* Row shape matches {@link NodeSqliteDriver}: plain objects, BLOBs as `Buffer`
|
|
1839
|
+
* (a `Uint8Array` subclass), which is what `coerceRow` already expects.
|
|
1840
|
+
*/
|
|
1841
|
+
declare class BetterSqliteDriver implements SyncDriver {
|
|
1842
|
+
private readonly db;
|
|
1843
|
+
/** Prepared-statement cache keyed by SQL text — see {@link NodeSqliteDriver}. */
|
|
1844
|
+
private readonly statements;
|
|
1845
|
+
constructor(database: any);
|
|
1846
|
+
/**
|
|
1847
|
+
* Open a `better-sqlite3` database at the given path (or `":memory:"`).
|
|
1848
|
+
*
|
|
1849
|
+
* @param path The database file, or `":memory:"`.
|
|
1850
|
+
* @param options Passed straight to `new Database()` (`readonly`, `timeout`, …).
|
|
1851
|
+
* @returns A driver over the open handle.
|
|
1852
|
+
* @throws If `better-sqlite3` is not installed — it is an optional peer
|
|
1853
|
+
* dependency, so the error names the package to install.
|
|
1854
|
+
*/
|
|
1855
|
+
static open(path: string, options?: Readonly<Record<string, unknown>>): BetterSqliteDriver;
|
|
1856
|
+
/** Return the cached prepared statement for `sql`, preparing it on first use. */
|
|
1857
|
+
private prepare;
|
|
1858
|
+
execute(sql: string, params: readonly unknown[]): DriverResult;
|
|
1859
|
+
iterate(sql: string, params: readonly unknown[]): IterableIterator<Record<string, unknown>>;
|
|
1860
|
+
close(): void;
|
|
1861
|
+
}
|
|
1349
1862
|
type AnySelect = SelectBuilder<any, any, any>;
|
|
1350
1863
|
type AnyInsert = InsertBuilder<any, any, any>;
|
|
1351
1864
|
type GuardedUpdate = UpdateBuilder<any, true, any>;
|
|
1352
1865
|
type GuardedDelete = DeleteBuilder<any, true, any>;
|
|
1353
1866
|
type AnyJoin = JoinBuilder<any>;
|
|
1867
|
+
type AnySet = SetBuilder<any>;
|
|
1354
1868
|
/**
|
|
1355
1869
|
* A builder that is safe to execute. UPDATE/DELETE are accepted only once
|
|
1356
1870
|
* guarded (after `.where()` or `.unguarded()`) — an unguarded full-table write
|
|
1357
1871
|
* is a compile error at the execution boundary.
|
|
1358
1872
|
*/
|
|
1359
|
-
type Executable = AnySelect | AnyInsert | GuardedUpdate | GuardedDelete | AnyJoin;
|
|
1873
|
+
type Executable = AnySelect | AnyInsert | GuardedUpdate | GuardedDelete | AnyJoin | AnySet;
|
|
1360
1874
|
/** The element type a builder yields on execution. */
|
|
1361
1875
|
type RowOf<B> = B extends {
|
|
1362
1876
|
readonly __row: infer R;
|
|
@@ -1393,6 +1907,40 @@ type QueryLogger = (event: {
|
|
|
1393
1907
|
readonly sql: string;
|
|
1394
1908
|
readonly params: readonly unknown[];
|
|
1395
1909
|
}) => void;
|
|
1910
|
+
/**
|
|
1911
|
+
* What a statement did, reported **after** it ran.
|
|
1912
|
+
*
|
|
1913
|
+
* `onQuery` fires before execution, so it cannot time anything; this is the other
|
|
1914
|
+
* half. It fires on the failure path too, with `error` set — a slow statement that
|
|
1915
|
+
* then fails is exactly the one worth seeing.
|
|
1916
|
+
*/
|
|
1917
|
+
interface QueryEndEvent {
|
|
1918
|
+
/** The statement text. */
|
|
1919
|
+
readonly sql: string;
|
|
1920
|
+
/** The bound parameters, in placeholder order. */
|
|
1921
|
+
readonly params: readonly unknown[];
|
|
1922
|
+
/** Wall-clock time the driver took, in milliseconds. */
|
|
1923
|
+
readonly durationMs: number;
|
|
1924
|
+
/** Rows returned (a SELECT or `RETURNING`), or rows affected by a write. */
|
|
1925
|
+
readonly rowCount: number;
|
|
1926
|
+
/** The driver's error, when the statement failed. */
|
|
1927
|
+
readonly error?: unknown;
|
|
1928
|
+
}
|
|
1929
|
+
/**
|
|
1930
|
+
* Called after every statement, with its duration.
|
|
1931
|
+
*
|
|
1932
|
+
* Errors thrown by the hook are ignored, like {@link QueryLogger}.
|
|
1933
|
+
*/
|
|
1934
|
+
type QueryEndLogger = (event: QueryEndEvent) => void;
|
|
1935
|
+
/** The per-statement hooks a session carries. */
|
|
1936
|
+
interface QueryHooks {
|
|
1937
|
+
/** Called before a statement runs. */
|
|
1938
|
+
readonly onQuery?: QueryLogger | undefined;
|
|
1939
|
+
/** Called after a statement runs, with its duration. */
|
|
1940
|
+
readonly onQueryEnd?: QueryEndLogger | undefined;
|
|
1941
|
+
/** When set, `onQueryEnd` fires only for statements at least this slow (ms). */
|
|
1942
|
+
readonly slowQueryMs?: number | undefined;
|
|
1943
|
+
}
|
|
1396
1944
|
/** Synchronous result view over already-fetched rows. */
|
|
1397
1945
|
declare class SyncResult<Row> {
|
|
1398
1946
|
private readonly rows;
|
|
@@ -1418,16 +1966,41 @@ declare class AsyncResult<Row> {
|
|
|
1418
1966
|
scalars(): Promise<unknown[]>;
|
|
1419
1967
|
rowsAffected(): Promise<number>;
|
|
1420
1968
|
}
|
|
1969
|
+
/**
|
|
1970
|
+
* A transaction isolation level, in the SQL standard's names.
|
|
1971
|
+
*
|
|
1972
|
+
* The database's default is what you get without asking: `read committed` on
|
|
1973
|
+
* PostgreSQL and MySQL's `repeatable read`. SQLite has only `serializable`.
|
|
1974
|
+
*/
|
|
1975
|
+
type IsolationLevel = "read uncommitted" | "read committed" | "repeatable read" | "serializable";
|
|
1976
|
+
/** Characteristics for one transaction block. */
|
|
1977
|
+
interface TransactionOptions {
|
|
1978
|
+
/**
|
|
1979
|
+
* The isolation level for this block.
|
|
1980
|
+
*
|
|
1981
|
+
* Raising it is how the queue and outbox patterns get their invariants: under
|
|
1982
|
+
* `read committed` two workers can both pass the same check before either
|
|
1983
|
+
* commits. Asking for a level a dialect does not implement throws.
|
|
1984
|
+
*/
|
|
1985
|
+
readonly isolation?: IsolationLevel;
|
|
1986
|
+
/**
|
|
1987
|
+
* Open the block read-only, so the database itself rejects a write in it.
|
|
1988
|
+
* PostgreSQL and MySQL only; SQLite throws.
|
|
1989
|
+
*/
|
|
1990
|
+
readonly readOnly?: boolean;
|
|
1991
|
+
}
|
|
1421
1992
|
/** A synchronous unit of work (SQLite). */
|
|
1422
1993
|
declare class SyncSession {
|
|
1423
1994
|
private readonly driver;
|
|
1424
1995
|
private readonly dialect;
|
|
1425
|
-
/** Optional per-statement
|
|
1426
|
-
private readonly
|
|
1996
|
+
/** Optional per-statement hooks (query tracing and timing). */
|
|
1997
|
+
private readonly hooks?;
|
|
1427
1998
|
constructor(driver: SyncDriver, dialect: BaseDialect,
|
|
1428
|
-
/** Optional per-statement
|
|
1429
|
-
|
|
1430
|
-
/**
|
|
1999
|
+
/** Optional per-statement hooks (query tracing and timing). */
|
|
2000
|
+
hooks?: QueryHooks | undefined);
|
|
2001
|
+
/** Open `transaction()` blocks on this session; only the outermost commits. */
|
|
2002
|
+
private depth;
|
|
2003
|
+
/** Log, run, time, and error-wrap one raw statement. */
|
|
1431
2004
|
private exec;
|
|
1432
2005
|
/**
|
|
1433
2006
|
* Run a raw, parameterized SQL statement (synchronous) — the runtime counterpart of the
|
|
@@ -1466,7 +2039,29 @@ declare class SyncSession {
|
|
|
1466
2039
|
/** Compile, run, and coerce a builder into a result. */
|
|
1467
2040
|
execute<B extends Executable>(builder: B): SyncResult<RowOf<B>>;
|
|
1468
2041
|
/** Run `fn` inside a transaction: commit on success, rollback on throw. */
|
|
1469
|
-
|
|
2042
|
+
/**
|
|
2043
|
+
* How many `transaction()` blocks are open on this session.
|
|
2044
|
+
*
|
|
2045
|
+
* The counter is what makes a service that orchestrates two repositories work:
|
|
2046
|
+
* both hold the same session, so an inner block **joins** the outer one instead
|
|
2047
|
+
* of emitting a second `BEGIN`, and only the outermost exit commits.
|
|
2048
|
+
*/
|
|
2049
|
+
get transactionDepth(): number;
|
|
2050
|
+
/** Whether a `transaction()` block is currently open on this session. */
|
|
2051
|
+
get inTransaction(): boolean;
|
|
2052
|
+
/**
|
|
2053
|
+
* Run `fn` inside a transaction, committing on a clean exit and rolling back on
|
|
2054
|
+
* a throw.
|
|
2055
|
+
*
|
|
2056
|
+
* **Re-entrant:** a nested call joins the block already open on this session —
|
|
2057
|
+
* one `BEGIN`, one `COMMIT`, and an inner failure rolls the whole thing back.
|
|
2058
|
+
* To recover from an inner failure without discarding the outer work, use
|
|
2059
|
+
* {@link beginNested}, which is a real savepoint.
|
|
2060
|
+
*
|
|
2061
|
+
* @param fn The body; receives the session to work through.
|
|
2062
|
+
* @returns Whatever `fn` returned.
|
|
2063
|
+
*/
|
|
2064
|
+
transaction<T>(fn: (tx: SyncSession) => T, options?: TransactionOptions): T;
|
|
1470
2065
|
/** Run `fn` inside a SAVEPOINT (nested transaction). */
|
|
1471
2066
|
beginNested<T>(fn: (sp: SyncSession) => T): T;
|
|
1472
2067
|
/**
|
|
@@ -1482,12 +2077,14 @@ declare class SyncSession {
|
|
|
1482
2077
|
declare class AsyncSession {
|
|
1483
2078
|
private readonly driver;
|
|
1484
2079
|
private readonly dialect;
|
|
1485
|
-
/** Optional per-statement
|
|
1486
|
-
private readonly
|
|
2080
|
+
/** Optional per-statement hooks (query tracing and timing). */
|
|
2081
|
+
private readonly hooks?;
|
|
1487
2082
|
constructor(driver: AsyncDriver, dialect: BaseDialect,
|
|
1488
|
-
/** Optional per-statement
|
|
1489
|
-
|
|
1490
|
-
/**
|
|
2083
|
+
/** Optional per-statement hooks (query tracing and timing). */
|
|
2084
|
+
hooks?: QueryHooks | undefined);
|
|
2085
|
+
/** Open `transaction()` blocks on this session; only the outermost commits. */
|
|
2086
|
+
private depth;
|
|
2087
|
+
/** Log, run, time, and error-wrap one raw statement. */
|
|
1491
2088
|
private exec;
|
|
1492
2089
|
/**
|
|
1493
2090
|
* Run a raw, parameterized SQL statement — the runtime counterpart of the
|
|
@@ -1543,7 +2140,61 @@ declare class AsyncSession {
|
|
|
1543
2140
|
private insertAndReadBack;
|
|
1544
2141
|
/** Lazily iterate result rows. Uses driver streaming when available. */
|
|
1545
2142
|
stream<B extends Executable>(builder: B): AsyncIterableIterator<RowOf<B>>;
|
|
1546
|
-
|
|
2143
|
+
/**
|
|
2144
|
+
* How many `transaction()` blocks are open on this session.
|
|
2145
|
+
*
|
|
2146
|
+
* The counter is what makes a service that orchestrates two repositories work:
|
|
2147
|
+
* both hold the same session, so an inner block **joins** the outer one instead
|
|
2148
|
+
* of emitting a second `BEGIN`, and only the outermost exit commits.
|
|
2149
|
+
*/
|
|
2150
|
+
get transactionDepth(): number;
|
|
2151
|
+
/** Whether a `transaction()` block is currently open on this session. */
|
|
2152
|
+
get inTransaction(): boolean;
|
|
2153
|
+
/**
|
|
2154
|
+
* Run `fn` inside a transaction, committing on a clean exit and rolling back on
|
|
2155
|
+
* a throw.
|
|
2156
|
+
*
|
|
2157
|
+
* **Re-entrant:** a nested call joins the block already open on this session,
|
|
2158
|
+
* so a service orchestrating several repositories bound to the same session
|
|
2159
|
+
* gets one `BEGIN` and one `COMMIT`, not two of each. An inner failure rolls the
|
|
2160
|
+
* whole block back; use {@link beginNested} for a savepoint that can be
|
|
2161
|
+
* recovered from.
|
|
2162
|
+
*
|
|
2163
|
+
* Pooled drivers (PostgreSQL) pin one connection for the block: `BEGIN`/`COMMIT`
|
|
2164
|
+
* and every statement between them have to run on the same connection, or
|
|
2165
|
+
* postgres.js rejects the raw transaction. Single-connection drivers (SQLite)
|
|
2166
|
+
* skip the reservation.
|
|
2167
|
+
*
|
|
2168
|
+
* @param fn The body; receives the session to work through (the pinned one, on a
|
|
2169
|
+
* pooled driver).
|
|
2170
|
+
* @returns Whatever `fn` returned.
|
|
2171
|
+
*/
|
|
2172
|
+
transaction<T>(fn: (tx: AsyncSession) => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
2173
|
+
/**
|
|
2174
|
+
* Run `fn` inside a `SAVEPOINT` (a nested transaction that can be rolled back on
|
|
2175
|
+
* its own).
|
|
2176
|
+
*
|
|
2177
|
+
* This is the difference from a nested {@link transaction}: a savepoint that
|
|
2178
|
+
* fails discards **only** its own work, so the enclosing block can catch the
|
|
2179
|
+
* error and carry on. A nested `transaction()` joins the outer block, and its
|
|
2180
|
+
* failure takes the whole block down.
|
|
2181
|
+
*
|
|
2182
|
+
* Must run inside an open transaction — PostgreSQL rejects a savepoint outside a
|
|
2183
|
+
* transaction block.
|
|
2184
|
+
*
|
|
2185
|
+
* @param fn The body; receives the same session.
|
|
2186
|
+
* @returns Whatever `fn` returned.
|
|
2187
|
+
*/
|
|
2188
|
+
beginNested<T>(fn: (sp: AsyncSession) => Promise<T>): Promise<T>;
|
|
2189
|
+
/**
|
|
2190
|
+
* Open an opt-in {@link UnitOfWork} over this session.
|
|
2191
|
+
*
|
|
2192
|
+
* The default stays a plain object written when you ask; this is the other
|
|
2193
|
+
* model, for code that prefers to mutate and flush once.
|
|
2194
|
+
*
|
|
2195
|
+
* @returns A new, empty unit of work.
|
|
2196
|
+
*/
|
|
2197
|
+
unitOfWork(): UnitOfWork;
|
|
1547
2198
|
close(): Promise<void>;
|
|
1548
2199
|
/** `await using session = ...` closes the driver when the scope exits. */
|
|
1549
2200
|
[Symbol.asyncDispose](): Promise<void>;
|
|
@@ -1556,178 +2207,1854 @@ interface PoolOptions {
|
|
|
1556
2207
|
readonly idleTimeoutMs?: number;
|
|
1557
2208
|
/** Give up acquiring a connection after this long (ms). */
|
|
1558
2209
|
readonly connectTimeoutMs?: number;
|
|
2210
|
+
/**
|
|
2211
|
+
* Validate a connection before pinning it for a transaction.
|
|
2212
|
+
*
|
|
2213
|
+
* A pooled connection can die without the pool noticing — a failover, a
|
|
2214
|
+
* pgbouncer restart, a firewall dropping an idle socket. The damage lands on
|
|
2215
|
+
* whoever picks it up next, and it lands worst on a transaction: `BEGIN`
|
|
2216
|
+
* succeeds, a statement mid-block fails, and the block dies halfway.
|
|
2217
|
+
*
|
|
2218
|
+
* With this on, `transaction()` runs `SELECT 1` on the reserved connection
|
|
2219
|
+
* first and reserves another one if that fails. It costs a round trip per
|
|
2220
|
+
* transaction, which is why it is opt-in.
|
|
2221
|
+
*
|
|
2222
|
+
* PostgreSQL only — MySQL throws, and SQLite has no pool.
|
|
2223
|
+
*/
|
|
2224
|
+
readonly prePing?: boolean;
|
|
2225
|
+
/**
|
|
2226
|
+
* Close and reopen a connection older than this (ms), regardless of activity.
|
|
2227
|
+
*
|
|
2228
|
+
* The blunt companion to {@link prePing}: it bounds how long a connection can
|
|
2229
|
+
* have been alive, which is what keeps a slow leak (a server-side timeout, a
|
|
2230
|
+
* load balancer's idle cap) from becoming a mystery error hours later.
|
|
2231
|
+
*
|
|
2232
|
+
* PostgreSQL only — MySQL throws, and SQLite has no pool.
|
|
2233
|
+
*/
|
|
2234
|
+
readonly recycleMs?: number;
|
|
2235
|
+
}
|
|
2236
|
+
/**
|
|
2237
|
+
* A server-side notice (a PostgreSQL `NOTICE`). The shape is the driver's own —
|
|
2238
|
+
* passed through untouched rather than normalized, since what is useful in it
|
|
2239
|
+
* differs per database.
|
|
2240
|
+
*/
|
|
2241
|
+
type NoticeLogger = (notice: Record<string, unknown>) => void;
|
|
2242
|
+
/**
|
|
2243
|
+
* SQLite journal modes accepted by `PRAGMA journal_mode`.
|
|
2244
|
+
*
|
|
2245
|
+
* `"wal"` is the one worth reaching for on a server: readers stop blocking the
|
|
2246
|
+
* writer. It needs a real file — an in-memory database refuses it and stays on
|
|
2247
|
+
* `"memory"`, which this layer reports as an error rather than a silent no-op.
|
|
2248
|
+
*/
|
|
2249
|
+
type SqliteJournalMode = "delete" | "truncate" | "persist" | "memory" | "wal" | "off";
|
|
2250
|
+
/** Durability levels accepted by `PRAGMA synchronous`. */
|
|
2251
|
+
type SqliteSynchronous = "off" | "normal" | "full" | "extra";
|
|
2252
|
+
/**
|
|
2253
|
+
* Per-connection SQLite settings, applied right after the handle opens.
|
|
2254
|
+
*
|
|
2255
|
+
* Pragmas are **per connection**, not per database file, so they belong to the
|
|
2256
|
+
* engine rather than to a migration. Only `foreignKeys` has a default that
|
|
2257
|
+
* changes behavior; every other field is emitted only when given, so an existing
|
|
2258
|
+
* database keeps whatever it was configured with.
|
|
2259
|
+
*/
|
|
2260
|
+
interface SqliteOptions {
|
|
2261
|
+
/**
|
|
2262
|
+
* Enforce `FOREIGN KEY` constraints. **Defaults to `true`.**
|
|
2263
|
+
*
|
|
2264
|
+
* SQLite ships with enforcement `OFF`, per connection, so a declared foreign
|
|
2265
|
+
* key is decorative until someone turns it on: an orphan `INSERT` is accepted
|
|
2266
|
+
* and `ON DELETE CASCADE` never fires. tempest-db-js turns it on, which makes
|
|
2267
|
+
* the same model behave the same on all three databases.
|
|
2268
|
+
*
|
|
2269
|
+
* Set it to `false` only for the case it exists for — loading a dump whose
|
|
2270
|
+
* insert order does not respect the graph.
|
|
2271
|
+
*/
|
|
2272
|
+
readonly foreignKeys?: boolean;
|
|
2273
|
+
/** `PRAGMA journal_mode`. Omitted: the database keeps its current mode. */
|
|
2274
|
+
readonly journalMode?: SqliteJournalMode;
|
|
2275
|
+
/** `PRAGMA busy_timeout`, in milliseconds. How long a writer waits on a lock. */
|
|
2276
|
+
readonly busyTimeoutMs?: number;
|
|
2277
|
+
/** `PRAGMA synchronous`. Durability vs write throughput. */
|
|
2278
|
+
readonly synchronous?: SqliteSynchronous;
|
|
2279
|
+
}
|
|
2280
|
+
/** Options shared by both engine flavors. */
|
|
2281
|
+
interface EngineOptions {
|
|
2282
|
+
/**
|
|
2283
|
+
* Override the driver detected from the URL.
|
|
2284
|
+
*
|
|
2285
|
+
* SQLite ships two: `"node:sqlite"` (the built-in, default — nothing to
|
|
2286
|
+
* install) and `"better-sqlite3"` (the optional peer dependency). PostgreSQL
|
|
2287
|
+
* runs on `"postgres"` (postgres.js) and MySQL on `"mysql2"`; naming those is
|
|
2288
|
+
* a no-op today, kept so the option means the same thing everywhere.
|
|
2289
|
+
*
|
|
2290
|
+
* A name this dialect does not have throws — passing `{ driver: "sqlite3" }`
|
|
2291
|
+
* is a decision that would otherwise be silently ignored.
|
|
2292
|
+
*
|
|
2293
|
+
* The `+suffix` in the URL (`sqlite+better-sqlite3:///app.db`) selects the same
|
|
2294
|
+
* way, with one difference: a suffix naming a driver from another ecosystem
|
|
2295
|
+
* (`sqlite+aiosqlite`, `postgresql+asyncpg`) is ignored rather than rejected,
|
|
2296
|
+
* so a URL copied from a Python service still connects.
|
|
2297
|
+
*/
|
|
2298
|
+
readonly driver?: string;
|
|
2299
|
+
/** Connection-pool tuning (PostgreSQL only). */
|
|
2300
|
+
readonly pool?: PoolOptions;
|
|
2301
|
+
/**
|
|
2302
|
+
* Called for every statement a session runs — SQL + bound params. Use for
|
|
2303
|
+
* query logging/tracing. Thrown errors are swallowed so it never breaks a query.
|
|
2304
|
+
*/
|
|
2305
|
+
readonly onQuery?: QueryLogger;
|
|
2306
|
+
/**
|
|
2307
|
+
* Called for every server-side notice (`CREATE TABLE IF NOT EXISTS` on an
|
|
2308
|
+
* existing table, `DROP ... IF EXISTS` on a missing one, and so on).
|
|
2309
|
+
*
|
|
2310
|
+
* **Without this, notices are silenced.** postgres.js defaults to printing them
|
|
2311
|
+
* with `console.log`, which drops a nine-line object into the host service's
|
|
2312
|
+
* stdout in the middle of its structured log — on every boot, since a migration
|
|
2313
|
+
* runner is usually the first thing to run. Writing to the host's stdout is the
|
|
2314
|
+
* application's decision, not a library's, so the default is to say nothing and
|
|
2315
|
+
* let you route them:
|
|
2316
|
+
*
|
|
2317
|
+
* ```ts
|
|
2318
|
+
* createEngine(url, { onNotice: (n) => logger.debug({ pg: n }, "postgres notice") });
|
|
2319
|
+
* ```
|
|
2320
|
+
*
|
|
2321
|
+
* Thrown errors are swallowed, like `onQuery`.
|
|
2322
|
+
*/
|
|
2323
|
+
readonly onNotice?: NoticeLogger;
|
|
2324
|
+
/**
|
|
2325
|
+
* Options passed straight to the underlying driver, applied **last** so they
|
|
2326
|
+
* win over everything this layer derives (`pool`, `onNotice`).
|
|
2327
|
+
*
|
|
2328
|
+
* The escape hatch for what the typed surface does not model and is not going
|
|
2329
|
+
* to — postgres.js `connection`/`types`/`transform`/`ssl`, mysql2's own
|
|
2330
|
+
* settings, `node:sqlite`'s `readOnly` — so a gap need not become a feature
|
|
2331
|
+
* request.
|
|
2332
|
+
*/
|
|
2333
|
+
readonly driverOptions?: Readonly<Record<string, unknown>>;
|
|
2334
|
+
/**
|
|
2335
|
+
* Per-connection SQLite pragmas (`foreign_keys`, `journal_mode`, …), applied
|
|
2336
|
+
* as soon as the handle opens. Passing it to a PostgreSQL or MySQL engine
|
|
2337
|
+
* throws — the settings have no meaning there, and silently ignoring them is
|
|
2338
|
+
* how a durability choice gets lost.
|
|
2339
|
+
*/
|
|
2340
|
+
readonly sqlite?: SqliteOptions;
|
|
2341
|
+
/**
|
|
2342
|
+
* Called **after** every statement, with how long the driver took.
|
|
2343
|
+
*
|
|
2344
|
+
* `onQuery` fires before execution, so it cannot time anything; this is the
|
|
2345
|
+
* other half, and it fires on the failure path too (with `error` set). Use it
|
|
2346
|
+
* for latency metrics, tracing spans, and finding the query dragging p99.
|
|
2347
|
+
*
|
|
2348
|
+
* Errors thrown by the hook are swallowed, like `onQuery`.
|
|
2349
|
+
*/
|
|
2350
|
+
readonly onQueryEnd?: QueryEndLogger;
|
|
2351
|
+
/**
|
|
2352
|
+
* Only report statements at least this slow (milliseconds) to `onQueryEnd`.
|
|
2353
|
+
*
|
|
2354
|
+
* The cheapest slow-query log there is: set a threshold, log what crosses it.
|
|
2355
|
+
* Without it every statement is reported.
|
|
2356
|
+
*/
|
|
2357
|
+
readonly slowQueryMs?: number;
|
|
2358
|
+
}
|
|
2359
|
+
/** A synchronous engine (SQLite only). */
|
|
2360
|
+
declare class SyncEngine {
|
|
2361
|
+
private readonly driver;
|
|
2362
|
+
private readonly hooks?;
|
|
2363
|
+
readonly dialect: Dialect;
|
|
2364
|
+
constructor(driver: SyncDriver, hooks?: QueryHooks | undefined);
|
|
2365
|
+
session(): SyncSession;
|
|
2366
|
+
transaction<T>(fn: (tx: SyncSession) => T, options?: TransactionOptions): T;
|
|
2367
|
+
close(): void;
|
|
2368
|
+
/** `using engine = createSyncEngine(...)` closes the pool when the scope exits. */
|
|
2369
|
+
[Symbol.dispose](): void;
|
|
2370
|
+
}
|
|
2371
|
+
/** An asynchronous engine. */
|
|
2372
|
+
declare class AsyncEngine {
|
|
2373
|
+
private readonly driver;
|
|
2374
|
+
readonly dialect: Dialect;
|
|
2375
|
+
private readonly hooks?;
|
|
2376
|
+
constructor(driver: AsyncDriver, dialect: Dialect, hooks?: QueryHooks | undefined);
|
|
2377
|
+
session(): AsyncSession;
|
|
2378
|
+
transaction<T>(fn: (tx: AsyncSession) => Promise<T>, options?: TransactionOptions): Promise<T>;
|
|
2379
|
+
/**
|
|
2380
|
+
* Run `fn` and return the query plan of every statement it ran.
|
|
2381
|
+
*
|
|
2382
|
+
* The block gets its own session over a **recording** driver, so the plans are
|
|
2383
|
+
* built from the statements and the parameters the code really used — not from
|
|
2384
|
+
* SQL copied out of a log by hand. A development tool: it runs the block once
|
|
2385
|
+
* and then one `EXPLAIN` per statement, so keep it out of the hot path.
|
|
2386
|
+
*
|
|
2387
|
+
* @param fn The code to observe; receives the recording session.
|
|
2388
|
+
* @param options `analyze: true` to measure (refused for writes), `filter` to
|
|
2389
|
+
* explain only some statements.
|
|
2390
|
+
* @returns The report, one plan per statement in execution order.
|
|
2391
|
+
* @throws Error When `analyze` is requested for a statement that writes, or on
|
|
2392
|
+
* a dialect that has no `EXPLAIN ANALYZE`.
|
|
2393
|
+
*
|
|
2394
|
+
* @example
|
|
2395
|
+
* ```ts
|
|
2396
|
+
* const report = await engine.explain(async (session) => {
|
|
2397
|
+
* await new BaseRepository(Order, session).paginate({ page: 3 });
|
|
2398
|
+
* });
|
|
2399
|
+
* console.log(report.summary());
|
|
2400
|
+
* ```
|
|
2401
|
+
*/
|
|
2402
|
+
explain(fn: (session: AsyncSession) => Promise<unknown>, options?: ExplainOptions): Promise<ExplainReport>;
|
|
2403
|
+
close(): Promise<void>;
|
|
2404
|
+
/** `await using engine = createEngine(...)` closes the pool when the scope exits. */
|
|
2405
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
2406
|
+
}
|
|
2407
|
+
/**
|
|
2408
|
+
* Adapt a sync **or** async driver to the async interface.
|
|
2409
|
+
*
|
|
2410
|
+
* `await` normalizes both: a sync driver returns a plain value, an async one a
|
|
2411
|
+
* promise, and awaiting either yields the result. That is what lets the migration
|
|
2412
|
+
* CLI take one code path instead of branching on a difference it cannot detect
|
|
2413
|
+
* from the object's shape.
|
|
2414
|
+
*
|
|
2415
|
+
* @param driver Either driver flavor.
|
|
2416
|
+
* @returns An async driver delegating to it.
|
|
2417
|
+
*/
|
|
2418
|
+
declare function toAsyncDriver(driver: SyncDriver | AsyncDriver): AsyncDriver;
|
|
2419
|
+
/**
|
|
2420
|
+
* Create a **synchronous** engine from a database URL. SQLite only — PostgreSQL
|
|
2421
|
+
* has no sane synchronous driver in Node, so a Postgres URL throws, pointing at
|
|
2422
|
+
* the async `createEngine`.
|
|
2423
|
+
*
|
|
2424
|
+
* @param url A SQLite URL, e.g. `"sqlite:///app.db"` or `"sqlite://:memory:"`.
|
|
2425
|
+
* @param options Engine options.
|
|
2426
|
+
* @returns A `SyncEngine`.
|
|
2427
|
+
*/
|
|
2428
|
+
declare function createSyncEngine(url: string, options?: EngineOptions): SyncEngine;
|
|
2429
|
+
/**
|
|
2430
|
+
* Create an **asynchronous** engine from a database URL (the default). Works for
|
|
2431
|
+
* both SQLite (sync driver wrapped as async) and PostgreSQL (postgres.js,
|
|
2432
|
+
* lazy-loaded).
|
|
2433
|
+
*
|
|
2434
|
+
* @param url A database URL, e.g. `"postgresql://app@localhost/app"` or
|
|
2435
|
+
* `"sqlite:///app.db"`.
|
|
2436
|
+
* @param options Engine options.
|
|
2437
|
+
* @returns An `AsyncEngine`.
|
|
2438
|
+
*/
|
|
2439
|
+
declare function createEngine(url: string, options?: EngineOptions): AsyncEngine;
|
|
2440
|
+
|
|
2441
|
+
/**
|
|
2442
|
+
* tempest-db-js — Phase 4a: dialect SQL compilation.
|
|
2443
|
+
*
|
|
2444
|
+
* Turns the dialect-neutral AST (`SelectNode`, `InsertNode`, `UpdateNode`,
|
|
2445
|
+
* `DeleteNode` from Phases 1-2) into `{ sql, params }`. This is the ONLY place
|
|
2446
|
+
* SQL is produced — always parameterized (`?` for SQLite, `$1` for PostgreSQL),
|
|
2447
|
+
* never string interpolation, so it is injection-safe by construction.
|
|
2448
|
+
*
|
|
2449
|
+
* It does NOT touch a database — execution is Phase 4b (`session.execute`).
|
|
2450
|
+
*/
|
|
2451
|
+
|
|
2452
|
+
/** A compiled, parameterized statement ready to hand to a driver. */
|
|
2453
|
+
interface CompiledQuery {
|
|
2454
|
+
readonly sql: string;
|
|
2455
|
+
readonly params: readonly unknown[];
|
|
2456
|
+
}
|
|
2457
|
+
/** Any compilable AST node. */
|
|
2458
|
+
type QueryNode = SelectNode | InsertNode | UpdateNode | DeleteNode | JoinNode | SetNode;
|
|
2459
|
+
/**
|
|
2460
|
+
* Collects bound parameters and renders placeholders in dialect style. Exposed
|
|
2461
|
+
* because dialect subclasses receive it when overriding clause rendering.
|
|
2462
|
+
*/
|
|
2463
|
+
declare class Params {
|
|
2464
|
+
private readonly placeholder;
|
|
2465
|
+
readonly values: unknown[];
|
|
2466
|
+
constructor(placeholder: (index: number) => string);
|
|
2467
|
+
bind(value: unknown): string;
|
|
2468
|
+
}
|
|
2469
|
+
/**
|
|
2470
|
+
* Base SQL compiler shared by every dialect. Subclasses customize only what
|
|
2471
|
+
* actually differs between databases (placeholder syntax, `ILIKE` support).
|
|
2472
|
+
*/
|
|
2473
|
+
declare abstract class BaseDialect {
|
|
2474
|
+
abstract readonly name: Dialect;
|
|
2475
|
+
/**
|
|
2476
|
+
* INSERT SQL templates keyed by structure (dialect|table|columns|rowCount|
|
|
2477
|
+
* returning). Shared across dialect instances — the key namespaces by dialect
|
|
2478
|
+
* name, and the placeholder text is dialect-specific but structure-determined.
|
|
2479
|
+
*/
|
|
2480
|
+
private static readonly insertTemplates;
|
|
2481
|
+
/** Quoted-identifier cache (see {@link quoteId}). Shared across dialects. */
|
|
2482
|
+
private static readonly quotedIds;
|
|
2483
|
+
/** Render the n-th (1-based) placeholder. */
|
|
2484
|
+
protected abstract placeholder(index: number): string;
|
|
2485
|
+
/** Render a case-insensitive LIKE for the active dialect. */
|
|
2486
|
+
protected abstract ilike(column: string, param: string): string;
|
|
2487
|
+
/**
|
|
2488
|
+
* Render a case-insensitive LIKE whose pattern carries escaped wildcards.
|
|
2489
|
+
*
|
|
2490
|
+
* The `ESCAPE` clause is not decoration: PostgreSQL treats `\` as the escape
|
|
2491
|
+
* character by default, **SQLite has none at all** until one is declared, so
|
|
2492
|
+
* without this the escaping done on our side would be meaningless there.
|
|
2493
|
+
*
|
|
2494
|
+
* @param column The rendered column.
|
|
2495
|
+
* @param param The bound pattern.
|
|
2496
|
+
* @returns The rendered comparison.
|
|
2497
|
+
*/
|
|
2498
|
+
protected ilikeEscaped(column: string, param: string): string;
|
|
2499
|
+
/**
|
|
2500
|
+
* Validate a subquery operand before it is rendered, for dialects that restrict
|
|
2501
|
+
* what an `IN (SELECT ...)` may contain. The default accepts everything.
|
|
2502
|
+
*
|
|
2503
|
+
* @param _node The subquery's AST.
|
|
2504
|
+
* @throws Error When the dialect cannot execute this subquery.
|
|
2505
|
+
*/
|
|
2506
|
+
protected checkSubquery(_node: SelectNode): void;
|
|
2507
|
+
/**
|
|
2508
|
+
* The SQL operator for an array containment/overlap test.
|
|
2509
|
+
*
|
|
2510
|
+
* Only PostgreSQL has native arrays; the other dialects throw rather than
|
|
2511
|
+
* emitting an operator that means something else there.
|
|
2512
|
+
*
|
|
2513
|
+
* @param op The array operator name.
|
|
2514
|
+
* @returns The SQL operator text.
|
|
2515
|
+
* @throws Error On a dialect without native array support.
|
|
2516
|
+
*/
|
|
2517
|
+
protected arrayOperator(op: "contains" | "containedBy" | "overlaps"): string;
|
|
2518
|
+
/**
|
|
2519
|
+
* Quote an identifier (column/table) for the active dialect.
|
|
2520
|
+
*
|
|
2521
|
+
* Memoized: identifiers form a small, stable set (column/table names), but this
|
|
2522
|
+
* runs for every identifier on every compile. Caching the quoted form removes a
|
|
2523
|
+
* regex-replace + string allocation from the hot path. The standard double-quote
|
|
2524
|
+
* form is identical across both dialects, so one shared cache is correct.
|
|
2525
|
+
*/
|
|
2526
|
+
protected quoteId(name: string): string;
|
|
2527
|
+
/** Compile any node to `{ sql, params }`. */
|
|
2528
|
+
compile(node: QueryNode): CompiledQuery;
|
|
2529
|
+
/**
|
|
2530
|
+
* Render a condition as schema SQL, with values inlined.
|
|
2531
|
+
*
|
|
2532
|
+
* Same compiler as a `WHERE`, different parameter strategy — so a `CHECK` and
|
|
2533
|
+
* the query language cannot drift apart in what they mean.
|
|
2534
|
+
*
|
|
2535
|
+
* @param node The condition.
|
|
2536
|
+
* @param params A {@link LiteralParams}.
|
|
2537
|
+
* @returns The rendered predicate.
|
|
2538
|
+
*/
|
|
2539
|
+
renderConditionLiteral(node: CondNode, params: Params): string;
|
|
2540
|
+
/**
|
|
2541
|
+
* Render the `WITH` clause of a statement.
|
|
2542
|
+
*
|
|
2543
|
+
* `RECURSIVE` is a property of the **clause**, not of an entry: one recursive
|
|
2544
|
+
* entry makes the whole `WITH` recursive, which is what the SQL standard says
|
|
2545
|
+
* and what PostgreSQL and SQLite both implement.
|
|
2546
|
+
*
|
|
2547
|
+
* @param entries The `WITH` entries, if any.
|
|
2548
|
+
* @param params The parameter collector.
|
|
2549
|
+
* @returns The clause with a trailing space, or an empty string.
|
|
2550
|
+
*/
|
|
2551
|
+
protected compileWith(entries: readonly CteNode[] | undefined, params: Params): string;
|
|
2552
|
+
/**
|
|
2553
|
+
* The `EXPLAIN` prefix for this dialect.
|
|
2554
|
+
*
|
|
2555
|
+
* @param analyze Whether to measure by actually running the statement.
|
|
2556
|
+
* @returns The prefix to put in front of the statement.
|
|
2557
|
+
* @throws Error When the dialect cannot do what was asked.
|
|
2558
|
+
*/
|
|
2559
|
+
explainPrefix(analyze: boolean): string;
|
|
2560
|
+
/**
|
|
2561
|
+
* The SQL keyword for a set operation.
|
|
2562
|
+
*
|
|
2563
|
+
* @param op The operator.
|
|
2564
|
+
* @returns The keyword.
|
|
2565
|
+
* @throws Error On a dialect that does not implement it.
|
|
2566
|
+
*/
|
|
2567
|
+
protected setOperator(op: SetOperator): string;
|
|
2568
|
+
/**
|
|
2569
|
+
* Compile a set operation.
|
|
2570
|
+
*
|
|
2571
|
+
* A branch carrying its own `ORDER BY`/`LIMIT` is parenthesized: without the
|
|
2572
|
+
* parentheses those clauses bind to the **combined** result, which is a
|
|
2573
|
+
* different query and a classic source of silently wrong output.
|
|
2574
|
+
*
|
|
2575
|
+
* @param node The set-operation node.
|
|
2576
|
+
* @param params The parameter collector.
|
|
2577
|
+
* @returns The rendered statement.
|
|
2578
|
+
*/
|
|
2579
|
+
protected compileSetOp(node: SetNode, params: Params): string;
|
|
2580
|
+
/**
|
|
2581
|
+
* Render a qualified `alias.column` ref as `"alias"."column"`, translating the
|
|
2582
|
+
* property name to the real column name for that alias's model.
|
|
2583
|
+
*
|
|
2584
|
+
* @param ref The `alias.property` reference (a bare name is left unqualified).
|
|
2585
|
+
* @param names The node's per-alias name maps, if any source renames columns.
|
|
2586
|
+
* @returns The quoted, qualified identifier.
|
|
2587
|
+
*/
|
|
2588
|
+
private qualify;
|
|
2589
|
+
/**
|
|
2590
|
+
* Quote a column identifier, translating the model property name to the real
|
|
2591
|
+
* database column name first.
|
|
2592
|
+
*
|
|
2593
|
+
* `names` is `undefined` for a model that renames nothing — the overwhelmingly
|
|
2594
|
+
* common case — so this stays a single lookup plus the memoized quote.
|
|
2595
|
+
*
|
|
2596
|
+
* @param prop The model property name as written in the builder.
|
|
2597
|
+
* @param names The node's property → column map, if any.
|
|
2598
|
+
* @returns The quoted database identifier.
|
|
2599
|
+
*/
|
|
2600
|
+
protected columnId(prop: string, names: NameMap | undefined): string;
|
|
2601
|
+
/**
|
|
2602
|
+
* Render a {@link SqlExpression} inline, binding the parameters it carries.
|
|
2603
|
+
*
|
|
2604
|
+
* This is what keeps `set({ attempts: sql.raw("attempts + 1") })` an expression
|
|
2605
|
+
* instead of a bound object: the fragment goes into the statement text, and
|
|
2606
|
+
* only a `sql.expr` template's interpolations become parameters.
|
|
2607
|
+
*
|
|
2608
|
+
* @param expr The branded expression.
|
|
2609
|
+
* @param params The parameter collector for the statement being compiled.
|
|
2610
|
+
* @returns The SQL text of the expression.
|
|
2611
|
+
*/
|
|
2612
|
+
protected renderExpression(expr: SqlExpression, params: Params): string;
|
|
2613
|
+
/** Render one write value: a SQL expression inline, anything else as a parameter. */
|
|
2614
|
+
protected renderValue(value: unknown, params: Params): string;
|
|
2615
|
+
/**
|
|
2616
|
+
* The statements that open a transaction with the requested characteristics.
|
|
2617
|
+
*
|
|
2618
|
+
* Returned as a list because the dialects disagree on shape: PostgreSQL takes
|
|
2619
|
+
* everything on the `BEGIN` itself, MySQL needs a separate `SET TRANSACTION`
|
|
2620
|
+
* before it, and SQLite has no syntax at all.
|
|
2621
|
+
*
|
|
2622
|
+
* @param options The requested isolation level and read-only flag.
|
|
2623
|
+
* @returns The statements to run, in order.
|
|
2624
|
+
* @throws Error When the dialect cannot honor what was asked.
|
|
2625
|
+
*/
|
|
2626
|
+
beginStatements(options?: TransactionOptions): string[];
|
|
2627
|
+
/**
|
|
2628
|
+
* Render a row-level locking clause (`FOR UPDATE ...`).
|
|
2629
|
+
*
|
|
2630
|
+
* Standard on PostgreSQL and MySQL 8.0+; SQLite overrides it to throw.
|
|
2631
|
+
*
|
|
2632
|
+
* @param lock The locking clause from the node.
|
|
2633
|
+
* @returns The SQL text, leading space included.
|
|
2634
|
+
*/
|
|
2635
|
+
protected renderLock(lock: LockClause): string;
|
|
2636
|
+
/**
|
|
2637
|
+
* Compile a SELECT.
|
|
2638
|
+
*
|
|
2639
|
+
* Two alias rules differ between clauses and are handled here: PostgreSQL does
|
|
2640
|
+
* NOT accept a `SELECT` alias in `HAVING`, so an aggregate key is re-emitted as
|
|
2641
|
+
* its expression (`COUNT(*) > $1`), a form every dialect accepts; `ORDER BY`,
|
|
2642
|
+
* by contrast, accepts the output alias everywhere, so it is emitted as
|
|
2643
|
+
* written.
|
|
2644
|
+
*
|
|
2645
|
+
* @param node The select AST.
|
|
2646
|
+
* @param params The parameter collector.
|
|
2647
|
+
* @returns The SQL text.
|
|
2648
|
+
*/
|
|
2649
|
+
private compileSelect;
|
|
2650
|
+
/**
|
|
2651
|
+
* Compile an INSERT.
|
|
2652
|
+
*
|
|
2653
|
+
* Takes the cached fast path only when the statement text is a pure function of
|
|
2654
|
+
* its structure. A SQL expression among the values, or a conflict predicate,
|
|
2655
|
+
* makes the text depend on the values themselves — those compile uncached, in
|
|
2656
|
+
* SQL order, so placeholder positions stay correct.
|
|
2657
|
+
*/
|
|
2658
|
+
private compileInsert;
|
|
2659
|
+
/**
|
|
2660
|
+
* Compile an INSERT without the template cache, rendering clauses in statement
|
|
2661
|
+
* order so every parameter is bound at the position it appears.
|
|
2662
|
+
*
|
|
2663
|
+
* @param node The insert node.
|
|
2664
|
+
* @param columns The column keys shared by every row.
|
|
2665
|
+
* @param params The parameter collector.
|
|
2666
|
+
* @returns The SQL text.
|
|
2667
|
+
*/
|
|
2668
|
+
private compileInsertDirect;
|
|
2669
|
+
/**
|
|
2670
|
+
* The INSERT SQL template for a given structure, cached across calls.
|
|
2671
|
+
*
|
|
2672
|
+
* The text depends only on (dialect, table, columns, row count, returning,
|
|
2673
|
+
* conflict shape) — never on the bound values — and placeholder positions are
|
|
2674
|
+
* deterministic from the counts (a fresh statement always starts binding at 1).
|
|
2675
|
+
* So a per-row insert loop compiles the string once and reuses it every row.
|
|
2676
|
+
*/
|
|
2677
|
+
private insertTemplate;
|
|
2678
|
+
/**
|
|
2679
|
+
* Render the conflict-handling clause. Standard SQL (SQLite/PostgreSQL) uses
|
|
2680
|
+
* `ON CONFLICT (...) [WHERE predicate] DO NOTHING | DO UPDATE SET ... [WHERE ...]`;
|
|
2681
|
+
* MySQL overrides this.
|
|
2682
|
+
*
|
|
2683
|
+
* The index predicate is rendered before the `DO UPDATE` assignments because
|
|
2684
|
+
* that is where it sits in the statement, so its parameters bind first.
|
|
2685
|
+
*
|
|
2686
|
+
* @param onConflict The conflict clause from the node.
|
|
2687
|
+
* @param conflictCols The columns to overwrite on `DO UPDATE` (empty for nothing).
|
|
2688
|
+
* @param nextValue Yields the SQL for the next `DO UPDATE` assignment value.
|
|
2689
|
+
* @param names The node's property → column map, if any.
|
|
2690
|
+
* @param params The parameter collector, for the predicates.
|
|
2691
|
+
* @returns The SQL text, leading space included.
|
|
2692
|
+
*/
|
|
2693
|
+
protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextValue: () => string, names: NameMap | undefined, params: Params): string;
|
|
2694
|
+
private compileUpdate;
|
|
2695
|
+
private compileDelete;
|
|
2696
|
+
protected compileJoin(node: JoinNode, params: Params): string;
|
|
2697
|
+
/**
|
|
2698
|
+
* Render the extra sources of an `UPDATE ... FROM` / `DELETE ... USING`.
|
|
2699
|
+
*
|
|
2700
|
+
* The dialects that do not have the clause override this and throw: emitting it
|
|
2701
|
+
* anyway would produce a statement the server rejects, and quietly dropping it
|
|
2702
|
+
* would change which rows are written.
|
|
2703
|
+
*
|
|
2704
|
+
* @param keyword `FROM` or `USING`.
|
|
2705
|
+
* @param sources The extra tables, if any.
|
|
2706
|
+
* @returns The clause with a leading space, or an empty string.
|
|
2707
|
+
*/
|
|
2708
|
+
protected compileExtraSources(keyword: "FROM" | "USING", sources: readonly {
|
|
2709
|
+
readonly table: string;
|
|
2710
|
+
readonly alias: string;
|
|
2711
|
+
}[] | undefined): string;
|
|
2712
|
+
protected compileReturning(returning: readonly string[] | "*" | null, names?: NameMap | undefined): string;
|
|
2713
|
+
/**
|
|
2714
|
+
* Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
|
|
2715
|
+
* key to a quoted identifier — `quoteId` for single-table, `qualify` for joins —
|
|
2716
|
+
* so select/update/delete/join all share this one compiler.
|
|
2717
|
+
*/
|
|
2718
|
+
protected compileCondition(node: CondNode | undefined, params: Params, idFor: (key: string) => string, encode?: (key: string, value: unknown) => unknown): string;
|
|
2719
|
+
/**
|
|
2720
|
+
* Compile a full-text condition.
|
|
2721
|
+
*
|
|
2722
|
+
* PostgreSQL gets the real thing (`@@ websearch_to_tsquery`); the dialects with
|
|
2723
|
+
* no text-search engine override this and compile the node's prebuilt substring
|
|
2724
|
+
* fallback instead, so the query still returns the right rows.
|
|
2725
|
+
*
|
|
2726
|
+
* @param node The full-text condition node.
|
|
2727
|
+
* @param params The parameter collector.
|
|
2728
|
+
* @param idFor Column-name resolver.
|
|
2729
|
+
* @returns The rendered condition.
|
|
2730
|
+
*/
|
|
2731
|
+
protected compileFullText(node: Extract<CondNode, {
|
|
2732
|
+
kind: "fullText";
|
|
2733
|
+
}>, params: Params, idFor: (key: string) => string): string;
|
|
2734
|
+
/**
|
|
2735
|
+
* Render one side of a comparison.
|
|
2736
|
+
*
|
|
2737
|
+
* A column reference goes through `idFor`, so an explicit `.name()` mapping and
|
|
2738
|
+
* join qualification apply here exactly as they do in the object form of
|
|
2739
|
+
* `where` — `col()` is not a way around them. Only a `value` node binds.
|
|
2740
|
+
*
|
|
2741
|
+
* @param node The expression AST.
|
|
2742
|
+
* @param params The parameter collector.
|
|
2743
|
+
* @param idFor The identifier resolver for the enclosing statement.
|
|
2744
|
+
* @returns The SQL text of the expression.
|
|
2745
|
+
*/
|
|
2746
|
+
/**
|
|
2747
|
+
* Render what an aggregate is applied to: `*`, a column, or an expression.
|
|
2748
|
+
*
|
|
2749
|
+
* An expression operand is what makes a conditional aggregate
|
|
2750
|
+
* (`SUM(CASE WHEN ... END)`) expressible — one pass over the table instead of a
|
|
2751
|
+
* query per bucket.
|
|
2752
|
+
*
|
|
2753
|
+
* @param agg The aggregate term.
|
|
2754
|
+
* @param params The parameter collector.
|
|
2755
|
+
* @param names The node's column-name map.
|
|
2756
|
+
* @returns The rendered operand.
|
|
2757
|
+
*/
|
|
2758
|
+
private aggregateOperand;
|
|
2759
|
+
private renderExpr;
|
|
2760
|
+
/**
|
|
2761
|
+
* Render a full-text relevance score.
|
|
2762
|
+
*
|
|
2763
|
+
* PostgreSQL has `ts_rank`; the others have nothing equivalent, and they
|
|
2764
|
+
* override this to a constant so that ordering by it is a no-op rather than a
|
|
2765
|
+
* compile error — the fallback keeps returning the right rows, only unranked.
|
|
2766
|
+
*
|
|
2767
|
+
* @param columns The columns making up the document.
|
|
2768
|
+
* @param term The search term.
|
|
2769
|
+
* @param language The text-search configuration.
|
|
2770
|
+
* @param params The parameter collector.
|
|
2771
|
+
* @param idFor Column-name resolver.
|
|
2772
|
+
* @returns The rendered score expression.
|
|
2773
|
+
*/
|
|
2774
|
+
protected renderRank(columns: readonly string[], term: string, language: string, params: Params, idFor: (key: string) => string): string;
|
|
2775
|
+
/**
|
|
2776
|
+
* Build the `to_tsvector(...)` document out of the searched columns.
|
|
2777
|
+
*
|
|
2778
|
+
* `coalesce(col, '')` matters: in SQL a `NULL` anywhere in a concatenation makes
|
|
2779
|
+
* the whole document `NULL`, so one empty column would silently exclude the row.
|
|
2780
|
+
*
|
|
2781
|
+
* @param columns The columns making up the document.
|
|
2782
|
+
* @param config The already-bound placeholder for the text-search config.
|
|
2783
|
+
* @param idFor Column-name resolver.
|
|
2784
|
+
* @returns The rendered `to_tsvector(...)` call.
|
|
2785
|
+
*/
|
|
2786
|
+
protected tsVector(columns: readonly string[], config: string, idFor: (key: string) => string): string;
|
|
2787
|
+
/**
|
|
2788
|
+
* The SQL type name this dialect accepts in a `CAST`.
|
|
2789
|
+
*
|
|
2790
|
+
* The base mapping is the standard one PostgreSQL takes; SQLite and MySQL
|
|
2791
|
+
* override it, because the names genuinely differ (MySQL's `CAST(x AS SIGNED)`
|
|
2792
|
+
* is not `INTEGER`, and SQLite only has five storage classes to aim at).
|
|
2793
|
+
*
|
|
2794
|
+
* @param to The portable cast target.
|
|
2795
|
+
* @returns The dialect's own type name.
|
|
2796
|
+
*/
|
|
2797
|
+
protected castTypeName(to: CastType): string;
|
|
2798
|
+
/**
|
|
2799
|
+
* Compile a comparison whose right-hand side is another expression rather than
|
|
2800
|
+
* a bound value (`total > paid`, `lower(a) = lower(b)`).
|
|
2801
|
+
*
|
|
2802
|
+
* The list and null operators are excluded: `IN`, `BETWEEN` and `IS NULL` take
|
|
2803
|
+
* a value operand, and accepting an expression there would silently compile to
|
|
2804
|
+
* something else.
|
|
2805
|
+
*
|
|
2806
|
+
* @param left The rendered left-hand side.
|
|
2807
|
+
* @param op The operator name.
|
|
2808
|
+
* @param right The rendered right-hand side.
|
|
2809
|
+
* @returns The SQL text of the predicate.
|
|
2810
|
+
* @throws Error When the operator needs a value operand.
|
|
2811
|
+
*/
|
|
2812
|
+
private compileExprOperator;
|
|
2813
|
+
private compileOperator;
|
|
2814
|
+
/**
|
|
2815
|
+
* Compile `IN` / `NOT IN`, whose operand is either a value list or a
|
|
2816
|
+
* single-column subquery.
|
|
2817
|
+
*
|
|
2818
|
+
* The subquery is rendered at the position it appears in the outer statement
|
|
2819
|
+
* and shares the same parameter collector, so its own placeholders land in the
|
|
2820
|
+
* right order — and it keeps its own `names` map, since the inner model may use
|
|
2821
|
+
* a different naming convention than the outer one.
|
|
2822
|
+
*
|
|
2823
|
+
* @param id The quoted column identifier being tested.
|
|
2824
|
+
* @param operand A list of values, or a {@link Subquery}.
|
|
2825
|
+
* @param params The parameter collector for the statement being compiled.
|
|
2826
|
+
* @param negate True for `NOT IN`.
|
|
2827
|
+
* @returns The SQL text of the predicate.
|
|
2828
|
+
*/
|
|
2829
|
+
private compileIn;
|
|
2830
|
+
}
|
|
2831
|
+
/** SQLite dialect: `?` placeholders; `ILIKE` falls back to `LIKE` (ASCII-insensitive). */
|
|
2832
|
+
declare class SqliteDialect extends BaseDialect {
|
|
2833
|
+
readonly name: "sqlite";
|
|
2834
|
+
/**
|
|
2835
|
+
* SQLite explains with `EXPLAIN QUERY PLAN`, and has no `ANALYZE` — the plain
|
|
2836
|
+
* `EXPLAIN` there dumps bytecode, which answers a different question.
|
|
2837
|
+
*/
|
|
2838
|
+
explainPrefix(analyze: boolean): string;
|
|
2839
|
+
/**
|
|
2840
|
+
* SQLite has `UPDATE ... FROM` (3.33+) but no `DELETE ... USING`.
|
|
2841
|
+
*
|
|
2842
|
+
* The portable shape there is a subquery — `where({ id: { in: … } })` — so this
|
|
2843
|
+
* throws and says so, rather than emitting a clause SQLite does not parse.
|
|
2844
|
+
*/
|
|
2845
|
+
protected compileExtraSources(keyword: "FROM" | "USING", sources: readonly {
|
|
2846
|
+
readonly table: string;
|
|
2847
|
+
readonly alias: string;
|
|
2848
|
+
}[] | undefined): string;
|
|
2849
|
+
/**
|
|
2850
|
+
* SQLite has no text-search engine, so the prebuilt substring fallback is
|
|
2851
|
+
* compiled instead. The rows are right; the ranking is what is missing.
|
|
2852
|
+
*/
|
|
2853
|
+
protected compileFullText(node: Extract<CondNode, {
|
|
2854
|
+
kind: "fullText";
|
|
2855
|
+
}>, params: Params, idFor: (key: string) => string): string;
|
|
2856
|
+
/** No text-search engine means no score: a constant, so ordering by it is inert. */
|
|
2857
|
+
protected renderRank(): string;
|
|
2858
|
+
/**
|
|
2859
|
+
* SQLite has five storage classes, so most targets collapse onto `TEXT` or
|
|
2860
|
+
* `INTEGER`. Naming a type it does not know would not fail — SQLite applies the
|
|
2861
|
+
* closest affinity — but it would make the cast mean something different here
|
|
2862
|
+
* than on the other databases, which is what this mapping avoids.
|
|
2863
|
+
*/
|
|
2864
|
+
protected castTypeName(to: CastType): string;
|
|
2865
|
+
protected placeholder(): string;
|
|
2866
|
+
protected ilike(column: string, param: string): string;
|
|
2867
|
+
/**
|
|
2868
|
+
* SQLite runs one writer at a time, so its only isolation level **is**
|
|
2869
|
+
* serializable — there is no syntax to ask for another, and no weaker level to
|
|
2870
|
+
* fall back to. Asking for one is an error rather than a silent no-op, since a
|
|
2871
|
+
* caller who wrote `repeatable read` was reasoning about a guarantee.
|
|
2872
|
+
*
|
|
2873
|
+
* `readOnly` likewise has no per-transaction form here (`PRAGMA query_only` is
|
|
2874
|
+
* per connection), so it is refused instead of quietly ignored.
|
|
2875
|
+
*/
|
|
2876
|
+
beginStatements(options?: TransactionOptions): string[];
|
|
2877
|
+
/**
|
|
2878
|
+
* SQLite has no row-level locking, so a lock request is an error rather than a
|
|
2879
|
+
* silently unlocked `SELECT` — a lock that does not exist only shows up as
|
|
2880
|
+
* duplicated work under production concurrency.
|
|
2881
|
+
*/
|
|
2882
|
+
protected renderLock(): string;
|
|
2883
|
+
}
|
|
2884
|
+
/** PostgreSQL dialect: `$1` placeholders; native `ILIKE`; native array operators. */
|
|
2885
|
+
declare class PostgresDialect extends BaseDialect {
|
|
2886
|
+
readonly name: "postgresql";
|
|
2887
|
+
protected placeholder(index: number): string;
|
|
2888
|
+
protected ilike(column: string, param: string): string;
|
|
2889
|
+
protected arrayOperator(op: "contains" | "containedBy" | "overlaps"): string;
|
|
2890
|
+
}
|
|
2891
|
+
/**
|
|
2892
|
+
* MySQL dialect: `?` placeholders, backtick identifiers, `ON DUPLICATE KEY
|
|
2893
|
+
* UPDATE` for upsert, and case-insensitive `LIKE` (default collation). MySQL has
|
|
2894
|
+
* no `RETURNING`, so requesting it throws.
|
|
2895
|
+
*/
|
|
2896
|
+
declare class MysqlDialect extends BaseDialect {
|
|
2897
|
+
readonly name: "mysql";
|
|
2898
|
+
protected placeholder(): string;
|
|
2899
|
+
protected ilike(column: string, param: string): string;
|
|
2900
|
+
/** MySQL's `EXPLAIN FORMAT=JSON` spells the option differently. */
|
|
2901
|
+
explainPrefix(analyze: boolean): string;
|
|
2902
|
+
/**
|
|
2903
|
+
* MySQL only gained `INTERSECT`/`EXCEPT` in 8.0.31, and this project does not
|
|
2904
|
+
* invest in MySQL beyond what already works — so they are refused here rather
|
|
2905
|
+
* than emitted against a server that may reject them.
|
|
2906
|
+
*/
|
|
2907
|
+
protected setOperator(op: SetOperator): string;
|
|
2908
|
+
/**
|
|
2909
|
+
* MySQL writes multi-table updates as `UPDATE a JOIN b`, and has no
|
|
2910
|
+
* `DELETE ... USING` in this shape. Both are out of the project's active scope,
|
|
2911
|
+
* so they are refused rather than emitted against a server that rejects them.
|
|
2912
|
+
*/
|
|
2913
|
+
protected compileExtraSources(keyword: "FROM" | "USING", sources: readonly {
|
|
2914
|
+
readonly table: string;
|
|
2915
|
+
readonly alias: string;
|
|
2916
|
+
}[] | undefined): string;
|
|
2917
|
+
/**
|
|
2918
|
+
* MySQL's full-text search needs a `FULLTEXT` index and different syntax, and it
|
|
2919
|
+
* is outside this project's active scope — the substring fallback is compiled,
|
|
2920
|
+
* like on SQLite.
|
|
2921
|
+
*/
|
|
2922
|
+
protected compileFullText(node: Extract<CondNode, {
|
|
2923
|
+
kind: "fullText";
|
|
2924
|
+
}>, params: Params, idFor: (key: string) => string): string;
|
|
2925
|
+
/** No `ts_rank` equivalent in scope: a constant, so ordering by it is inert. */
|
|
2926
|
+
protected renderRank(): string;
|
|
2927
|
+
/**
|
|
2928
|
+
* MySQL sets the level with a statement **before** the transaction opens, and
|
|
2929
|
+
* spells the read-only flag on `START TRANSACTION` rather than on `BEGIN`.
|
|
2930
|
+
*/
|
|
2931
|
+
beginStatements(options?: TransactionOptions): string[];
|
|
2932
|
+
/**
|
|
2933
|
+
* MySQL's `CAST` takes its own vocabulary — `SIGNED`, not `INTEGER`; `CHAR`,
|
|
2934
|
+
* not `TEXT` — and rejects the standard names outright.
|
|
2935
|
+
*/
|
|
2936
|
+
protected castTypeName(to: CastType): string;
|
|
2937
|
+
protected quoteId(name: string): string;
|
|
2938
|
+
/**
|
|
2939
|
+
* MySQL rejects `LIMIT` inside an `IN` subquery with
|
|
2940
|
+
* `ER_NOT_SUPPORTED_YET: This version of MySQL doesn't yet support
|
|
2941
|
+
* 'LIMIT & IN/ALL/ANY/SOME subquery'`. Failing at compile time names the fix
|
|
2942
|
+
* instead of surfacing that error from the driver at runtime.
|
|
2943
|
+
*/
|
|
2944
|
+
protected checkSubquery(node: SelectNode): void;
|
|
2945
|
+
protected renderConflict(onConflict: NonNullable<InsertNode["onConflict"]>, conflictCols: readonly string[], nextValue: () => string, names: NameMap | undefined): string;
|
|
2946
|
+
/**
|
|
2947
|
+
* MySQL has no `RETURNING`, so it cannot be compiled into a statement.
|
|
2948
|
+
*
|
|
2949
|
+
* `session.execute()` still honors `.returning()` on a **single-row INSERT** by
|
|
2950
|
+
* running the insert and reading the row back by key on the same connection —
|
|
2951
|
+
* that is execution, not compilation, so it never reaches here. Compiling a
|
|
2952
|
+
* node with `returning` directly is an error, rather than SQL that silently
|
|
2953
|
+
* returns nothing.
|
|
2954
|
+
*/
|
|
2955
|
+
protected compileReturning(returning: readonly string[] | "*" | null): string;
|
|
2956
|
+
}
|
|
2957
|
+
/** Get a dialect instance by name. */
|
|
2958
|
+
declare function getDialect(name: Dialect): BaseDialect;
|
|
2959
|
+
|
|
2960
|
+
/**
|
|
2961
|
+
* tempest-db-js — opt-in model mixins.
|
|
2962
|
+
*
|
|
2963
|
+
* The same four or five columns show up on every real table: when the row was
|
|
2964
|
+
* created and last touched, whether it was soft-deleted, who changed it. Writing
|
|
2965
|
+
* them out per model is repetition that drifts — one table gets `updated_at`
|
|
2966
|
+
* without `onUpdate`, another spells it `updatedOn`.
|
|
2967
|
+
*
|
|
2968
|
+
* These mixins are functions that take a base class and return a subclass
|
|
2969
|
+
* carrying the extra columns, mirroring `tempest-fastapi-sdk`'s
|
|
2970
|
+
* `SoftDeleteMixin` / `AuditMixin`. They compose:
|
|
2971
|
+
*
|
|
2972
|
+
* ```ts
|
|
2973
|
+
* class Order extends withSoftDelete(withTimestamps(Model)) {
|
|
2974
|
+
* static override tablename = "orders";
|
|
2975
|
+
* id = column.integer().primaryKey();
|
|
2976
|
+
* }
|
|
2977
|
+
* ```
|
|
2978
|
+
*
|
|
2979
|
+
* The columns are real columns: they appear in `InferModel`, in the migration IR
|
|
2980
|
+
* and in the generated DDL, exactly as if you had typed them.
|
|
2981
|
+
*/
|
|
2982
|
+
|
|
2983
|
+
/** Any class usable as a mixin base — `Model` itself, or another mixin's result. */
|
|
2984
|
+
type ModelBase = abstract new (...args: any[]) => Model;
|
|
2985
|
+
/**
|
|
2986
|
+
* Add `createdAt` / `updatedAt` to a model.
|
|
2987
|
+
*
|
|
2988
|
+
* Both default to the database's own clock (`sql.now()`), and `updatedAt` carries
|
|
2989
|
+
* `onUpdate(sql.now())`, so an `UPDATE` refreshes it without the call site
|
|
2990
|
+
* remembering to. Both are `NOT NULL` with a default, so neither shows up as
|
|
2991
|
+
* required in `InferInsert`.
|
|
2992
|
+
*
|
|
2993
|
+
* @param Base The class to extend (`Model`, or another mixin's result).
|
|
2994
|
+
* @returns A subclass carrying the two timestamp columns.
|
|
2995
|
+
*/
|
|
2996
|
+
declare function withTimestamps<TBase extends ModelBase>(Base: TBase): (abstract new (...args: any[]) => {
|
|
2997
|
+
/** When the row was inserted. */
|
|
2998
|
+
createdAt: Column<Date, ColumnFlags & {
|
|
2999
|
+
notNull: true;
|
|
3000
|
+
} & {
|
|
3001
|
+
hasDefault: true;
|
|
3002
|
+
}>;
|
|
3003
|
+
/** When the row was last updated — refreshed by every `UPDATE`. */
|
|
3004
|
+
updatedAt: Column<Date, ColumnFlags & {
|
|
3005
|
+
notNull: true;
|
|
3006
|
+
} & {
|
|
3007
|
+
hasDefault: true;
|
|
3008
|
+
}>;
|
|
3009
|
+
}) & TBase;
|
|
3010
|
+
/**
|
|
3011
|
+
* Add `deletedAt` to a model, for non-destructive deletes.
|
|
3012
|
+
*
|
|
3013
|
+
* A row is alive while `deletedAt IS NULL`. **Filtering is the caller's job** —
|
|
3014
|
+
* the mixin only declares the column, so it composes with whatever query strategy
|
|
3015
|
+
* a table needs (a partial index, a repository default, a view). Use
|
|
3016
|
+
* {@link notDeleted} to spell the predicate.
|
|
3017
|
+
*
|
|
3018
|
+
* @param Base The class to extend.
|
|
3019
|
+
* @returns A subclass carrying the `deletedAt` column.
|
|
3020
|
+
*/
|
|
3021
|
+
declare function withSoftDelete<TBase extends ModelBase>(Base: TBase): (abstract new (...args: any[]) => {
|
|
3022
|
+
/** When the row was soft-deleted, or `null` while it is alive. */
|
|
3023
|
+
deletedAt: Column<Date, ColumnFlags>;
|
|
3024
|
+
}) & TBase;
|
|
3025
|
+
/**
|
|
3026
|
+
* The `where` fragment matching rows that are **not** soft-deleted.
|
|
3027
|
+
*
|
|
3028
|
+
* @typeParam Row - the row type being filtered.
|
|
3029
|
+
* @returns `{ deletedAt: { isNull: true } }`, typed for the row.
|
|
3030
|
+
*/
|
|
3031
|
+
declare function notDeleted<Row>(): WhereInput<Row>;
|
|
3032
|
+
/**
|
|
3033
|
+
* The `where` fragment matching **only** soft-deleted rows.
|
|
3034
|
+
*
|
|
3035
|
+
* @typeParam Row - the row type being filtered.
|
|
3036
|
+
* @returns `{ deletedAt: { isNull: false } }`, typed for the row.
|
|
3037
|
+
*/
|
|
3038
|
+
declare function onlyDeleted<Row>(): WhereInput<Row>;
|
|
3039
|
+
/**
|
|
3040
|
+
* Add `createdBy` / `updatedBy` to a model — *who* touched the row.
|
|
3041
|
+
*
|
|
3042
|
+
* The actor column defaults to `column.uuid()`, matching the SDK's user id. A
|
|
3043
|
+
* service whose users are keyed by something else passes a factory:
|
|
3044
|
+
* `withAudit(Model, () => column.integer())`. It must be a factory, not a column:
|
|
3045
|
+
* the two properties need two independent column instances.
|
|
3046
|
+
*
|
|
3047
|
+
* Both columns are nullable — a row created by a background job has no actor, and
|
|
3048
|
+
* forcing a sentinel value there is worse than a `NULL`.
|
|
3049
|
+
*
|
|
3050
|
+
* @param Base The class to extend.
|
|
3051
|
+
* @param actor Factory producing the actor column (default: `column.uuid()`).
|
|
3052
|
+
* @returns A subclass carrying the two actor columns.
|
|
3053
|
+
*/
|
|
3054
|
+
declare function withAudit<TBase extends ModelBase, A = string>(Base: TBase, actor?: () => Column<A, ColumnFlags>): (abstract new (...args: any[]) => {
|
|
3055
|
+
/** Who created the row, or `null` when nobody was attributed. */
|
|
3056
|
+
createdBy: Column<A, ColumnFlags>;
|
|
3057
|
+
/** Who last updated the row, or `null`. */
|
|
3058
|
+
updatedBy: Column<A, ColumnFlags>;
|
|
3059
|
+
}) & TBase;
|
|
3060
|
+
|
|
3061
|
+
/**
|
|
3062
|
+
* tempest-db-js — read a constraint violation back out of the driver's error.
|
|
3063
|
+
*
|
|
3064
|
+
* A database says *why* it refused a write, and says it in prose whose shape is
|
|
3065
|
+
* the driver's, not the application's. Answering `409 {"code":"EMAIL_TAKEN"}`
|
|
3066
|
+
* instead of a generic conflict otherwise means every service writing its own
|
|
3067
|
+
* regular expression against whichever dialect the author had running.
|
|
3068
|
+
*
|
|
3069
|
+
* The two dialects in scope say the same five things two different ways:
|
|
3070
|
+
* PostgreSQL names the constraint and lists the columns in a `DETAIL:` line,
|
|
3071
|
+
* while SQLite spells the table and columns into the message and says nothing
|
|
3072
|
+
* about the constraint's name.
|
|
3073
|
+
*/
|
|
3074
|
+
|
|
3075
|
+
/** What kind of constraint refused the write. */
|
|
3076
|
+
type IntegrityViolation = "unique" | "foreignKey" | "notNull" | "check" | "exclusion";
|
|
3077
|
+
/** A constraint violation, read out of the driver's error. */
|
|
3078
|
+
interface IntegrityFailure {
|
|
3079
|
+
/** Which kind of constraint refused the write. */
|
|
3080
|
+
readonly violation: IntegrityViolation;
|
|
3081
|
+
/** The constraint's name, when the database reported one (PostgreSQL does). */
|
|
3082
|
+
readonly constraint: string | null;
|
|
3083
|
+
/** The table, when the database reported one. */
|
|
3084
|
+
readonly table: string | null;
|
|
3085
|
+
/**
|
|
3086
|
+
* The columns the constraint covers.
|
|
3087
|
+
*
|
|
3088
|
+
* Empty when the database did not say — a SQLite `FOREIGN KEY constraint
|
|
3089
|
+
* failed` carries no names at all.
|
|
3090
|
+
*/
|
|
3091
|
+
readonly columns: readonly string[];
|
|
3092
|
+
/** The driver's own message, kept verbatim for logging. */
|
|
3093
|
+
readonly detail: string;
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* Read a driver error back into the constraint that refused the write.
|
|
3097
|
+
*
|
|
3098
|
+
* @param error Anything thrown by a write — a `QueryExecutionError` from this
|
|
3099
|
+
* package, or the driver's own error; the cause chain is followed either way.
|
|
3100
|
+
* @param model Optional. When given, database column names are translated back to
|
|
3101
|
+
* the model's property names, so a `snake_case` schema reports `idempotencyKey`
|
|
3102
|
+
* rather than `idempotency_key`.
|
|
3103
|
+
* @returns The violation, or `null` when the error is not an integrity violation
|
|
3104
|
+
* (or came from MySQL, which is out of scope — see the recipe).
|
|
3105
|
+
*
|
|
3106
|
+
* @example
|
|
3107
|
+
* ```ts
|
|
3108
|
+
* try {
|
|
3109
|
+
* await users.create({ email });
|
|
3110
|
+
* } catch (err) {
|
|
3111
|
+
* const failure = parseIntegrityError(err, User);
|
|
3112
|
+
* if (failure?.violation === "unique" && failure.columns.includes("email")) {
|
|
3113
|
+
* throw new EmailTaken();
|
|
3114
|
+
* }
|
|
3115
|
+
* throw err;
|
|
3116
|
+
* }
|
|
3117
|
+
* ```
|
|
3118
|
+
*/
|
|
3119
|
+
declare function parseIntegrityError(error: unknown, model?: ModelClass): IntegrityFailure | null;
|
|
3120
|
+
|
|
3121
|
+
/**
|
|
3122
|
+
* tempest-db-js — text search that survives what users actually type.
|
|
3123
|
+
*
|
|
3124
|
+
* Two layers, because they answer different questions:
|
|
3125
|
+
*
|
|
3126
|
+
* - {@link contains} — tokenized, **escaped** substring matching. Identical on
|
|
3127
|
+
* every dialect, needs no extension, no index and no migration. This is what
|
|
3128
|
+
* "find the row whose name contains what the user typed" should use.
|
|
3129
|
+
* - {@link fullText} / {@link fullTextRank} — PostgreSQL's `to_tsvector` /
|
|
3130
|
+
* `websearch_to_tsquery` / `ts_rank`: stemming, stop words and a relevance
|
|
3131
|
+
* score. On any other database they fall back to the first layer, which is the
|
|
3132
|
+
* honest degradation — the query still returns the right rows, just without
|
|
3133
|
+
* stemming.
|
|
3134
|
+
*/
|
|
3135
|
+
|
|
3136
|
+
/**
|
|
3137
|
+
* A PostgreSQL text-search configuration (`regconfig`), such as `"english"` or
|
|
3138
|
+
* `"portuguese"`. `"simple"` disables stemming and stop-word removal.
|
|
3139
|
+
*/
|
|
3140
|
+
type TextSearchLanguage = string;
|
|
3141
|
+
/** Options shared by the full-text helpers. */
|
|
3142
|
+
interface TextSearchOptions {
|
|
3143
|
+
/** The PostgreSQL text-search configuration. Defaults to `"english"`. */
|
|
3144
|
+
readonly language?: TextSearchLanguage;
|
|
3145
|
+
}
|
|
3146
|
+
/** Options for {@link contains}. */
|
|
3147
|
+
interface ContainsOptions {
|
|
3148
|
+
/**
|
|
3149
|
+
* Whether a row must match **every** token (default) or any one of them.
|
|
3150
|
+
*
|
|
3151
|
+
* `"all"` is what a search box wants: typing more words narrows the result.
|
|
3152
|
+
*/
|
|
3153
|
+
readonly match?: "all" | "any";
|
|
3154
|
+
}
|
|
3155
|
+
/**
|
|
3156
|
+
* Escape the `LIKE` wildcards in a literal, for use with `like` / `ilike`.
|
|
3157
|
+
*
|
|
3158
|
+
* `%` and `_` are wildcards, and `\` escapes them — so a user searching for
|
|
3159
|
+
* `100%` matches every row unless the literal is escaped first. The pattern this
|
|
3160
|
+
* produces is meant to be used with an `ESCAPE '\'` clause, which is what the
|
|
3161
|
+
* {@link ContainsOptions} operator emits; PostgreSQL assumes that escape by
|
|
3162
|
+
* default, SQLite has none until it is given one.
|
|
3163
|
+
*
|
|
3164
|
+
* @param value The literal the user typed.
|
|
3165
|
+
* @returns The same text with `\`, `%` and `_` escaped.
|
|
3166
|
+
*/
|
|
3167
|
+
declare function escapeLike(value: string): string;
|
|
3168
|
+
/**
|
|
3169
|
+
* Split a search term into the tokens a row has to match.
|
|
3170
|
+
*
|
|
3171
|
+
* @param term What the user typed.
|
|
3172
|
+
* @returns The non-empty whitespace-separated tokens.
|
|
3173
|
+
*/
|
|
3174
|
+
declare function tokenize(term: string): string[];
|
|
3175
|
+
/**
|
|
3176
|
+
* Case-insensitive substring search across one or more columns.
|
|
3177
|
+
*
|
|
3178
|
+
* The term is tokenized, and each token is matched — **escaped** — against every
|
|
3179
|
+
* listed column; a row matches when every token appears in at least one of them.
|
|
3180
|
+
* Behaves the same on every dialect, with no index or extension needed.
|
|
3181
|
+
*
|
|
3182
|
+
* @param columns The columns to search, by property name.
|
|
3183
|
+
* @param term What the user typed.
|
|
3184
|
+
* @param options `match: "any"` to require a single token instead of all of them.
|
|
3185
|
+
* @returns A condition for `where`, matching nothing when the term is blank.
|
|
3186
|
+
* @throws Error When no column is given.
|
|
3187
|
+
*
|
|
3188
|
+
* @example
|
|
3189
|
+
* ```ts
|
|
3190
|
+
* select(User).where(contains(["name", "email"], "100% ana"));
|
|
3191
|
+
* // (name ILIKE '%100\%%' ESCAPE '\' OR email ILIKE ...) AND (name ILIKE '%ana%' ...)
|
|
3192
|
+
* ```
|
|
3193
|
+
*/
|
|
3194
|
+
declare function contains<Row = Record<string, unknown>>(columns: readonly string[], term: string, options?: ContainsOptions): Condition;
|
|
3195
|
+
/**
|
|
3196
|
+
* PostgreSQL full-text search over one or more columns, with a portable fallback.
|
|
3197
|
+
*
|
|
3198
|
+
* On PostgreSQL this compiles to `to_tsvector(config, col || ' ' || col) @@
|
|
3199
|
+
* websearch_to_tsquery(config, term)`, so the search stems (`buying` matches
|
|
3200
|
+
* `buy`), drops stop words, and understands the quoting and `-exclusion` syntax
|
|
3201
|
+
* users already know from web search boxes.
|
|
3202
|
+
*
|
|
3203
|
+
* On any other database it compiles to {@link contains} instead — the same rows a
|
|
3204
|
+
* substring search would find, without stemming. That is a documented
|
|
3205
|
+
* degradation, not a silent one: a dev SQLite database keeps working, and the
|
|
3206
|
+
* difference is in ranking quality, not correctness.
|
|
3207
|
+
*
|
|
3208
|
+
* @param columns The columns to search, by property name.
|
|
3209
|
+
* @param term What the user typed.
|
|
3210
|
+
* @param options The text-search configuration.
|
|
3211
|
+
* @returns A condition for `where`.
|
|
3212
|
+
* @throws Error When no column is given.
|
|
3213
|
+
*/
|
|
3214
|
+
declare function fullText<Row = Record<string, unknown>>(columns: readonly string[], term: string, options?: TextSearchOptions): Condition;
|
|
3215
|
+
/**
|
|
3216
|
+
* The relevance score of a full-text match, for `orderBy`.
|
|
3217
|
+
*
|
|
3218
|
+
* On PostgreSQL this is `ts_rank(...)`, the number that makes "best match first"
|
|
3219
|
+
* mean something. Elsewhere it renders as a constant, so ordering by it is a
|
|
3220
|
+
* no-op and the query falls back to whatever other ordering is given — ranking is
|
|
3221
|
+
* the part that genuinely does not exist without a text-search engine.
|
|
3222
|
+
*
|
|
3223
|
+
* @param columns The same columns passed to {@link fullText}.
|
|
3224
|
+
* @param term The same term.
|
|
3225
|
+
* @param options The same configuration.
|
|
3226
|
+
* @returns An expression usable in `orderBy`.
|
|
3227
|
+
* @throws Error When no column is given.
|
|
3228
|
+
*
|
|
3229
|
+
* @example
|
|
3230
|
+
* ```ts
|
|
3231
|
+
* select(Post)
|
|
3232
|
+
* .where(fullText(["title", "body"], term, { language: "portuguese" }))
|
|
3233
|
+
* .orderBy(fullTextRank(["title", "body"], term, { language: "portuguese" }), "desc");
|
|
3234
|
+
* ```
|
|
3235
|
+
*/
|
|
3236
|
+
declare function fullTextRank(columns: readonly string[], term: string, options?: TextSearchOptions): Expression;
|
|
3237
|
+
|
|
3238
|
+
/**
|
|
3239
|
+
* tempest-db-js — lifecycle signals around the repository's write path.
|
|
3240
|
+
*
|
|
3241
|
+
* Reacting to persistence — busting a cache, enqueuing an outbox event, syncing a
|
|
3242
|
+
* search index, writing an audit row — otherwise means wrapping every call site
|
|
3243
|
+
* by hand, and the one call site somebody forgets is the bug.
|
|
3244
|
+
*
|
|
3245
|
+
* Handlers run **inside** whatever transaction is open, because they are handed
|
|
3246
|
+
* the same session the write used: a handler that writes commits with the write,
|
|
3247
|
+
* or rolls back with it.
|
|
3248
|
+
*/
|
|
3249
|
+
|
|
3250
|
+
/** The points a repository write is observable at. */
|
|
3251
|
+
type RepositorySignal = "preSave" | "postSave" | "preDelete" | "postDelete";
|
|
3252
|
+
/** What a handler receives. */
|
|
3253
|
+
interface SignalPayload<C extends ModelClass> {
|
|
3254
|
+
/**
|
|
3255
|
+
* The row the signal is about.
|
|
3256
|
+
*
|
|
3257
|
+
* `preSave` on an insert carries what was passed to `create`, so a
|
|
3258
|
+
* database-generated id is not there yet — `postSave` carries the stored row.
|
|
3259
|
+
* `preDelete` carries the row as it was just before it was deleted.
|
|
3260
|
+
*/
|
|
3261
|
+
readonly row: InferModel<C>;
|
|
3262
|
+
/** The model being written. */
|
|
3263
|
+
readonly model: C;
|
|
3264
|
+
/** The session the write is running on — the same transaction, if one is open. */
|
|
3265
|
+
readonly session: AsyncSession;
|
|
3266
|
+
/** True when the write that fired this was an insert rather than an update. */
|
|
3267
|
+
readonly isInsert: boolean;
|
|
3268
|
+
}
|
|
3269
|
+
/** A signal handler; may be async, and is awaited. */
|
|
3270
|
+
type SignalHandler<C extends ModelClass> = (payload: SignalPayload<C>) => void | Promise<void>;
|
|
3271
|
+
/**
|
|
3272
|
+
* Register a handler for one signal on one model.
|
|
3273
|
+
*
|
|
3274
|
+
* @param model The model to observe.
|
|
3275
|
+
* @param signal Which point to observe.
|
|
3276
|
+
* @param handler The handler; awaited, and free to use the payload's session.
|
|
3277
|
+
* @returns A function that unregisters this handler.
|
|
3278
|
+
*
|
|
3279
|
+
* @example
|
|
3280
|
+
* ```ts
|
|
3281
|
+
* const off = onSignal(User, "postSave", async ({ row }) => {
|
|
3282
|
+
* await cache.del(`user:${row.id}`);
|
|
3283
|
+
* });
|
|
3284
|
+
* ```
|
|
3285
|
+
*/
|
|
3286
|
+
declare function onSignal<C extends ModelClass>(model: C, signal: RepositorySignal, handler: SignalHandler<C>): () => void;
|
|
3287
|
+
/**
|
|
3288
|
+
* Whether anything is listening to a signal on a model.
|
|
3289
|
+
*
|
|
3290
|
+
* The repository asks before doing work only a handler would need — reading the
|
|
3291
|
+
* rows a filter-based `update`/`delete` is about to touch costs a `SELECT`, and
|
|
3292
|
+
* nobody should pay it when no handler exists.
|
|
3293
|
+
*
|
|
3294
|
+
* @param model The model.
|
|
3295
|
+
* @param signal The signal.
|
|
3296
|
+
* @returns True when at least one handler is registered.
|
|
3297
|
+
*/
|
|
3298
|
+
declare function hasHandlers(model: ModelClass, signal: RepositorySignal): boolean;
|
|
3299
|
+
/**
|
|
3300
|
+
* Fire a signal, awaiting every handler in registration order.
|
|
3301
|
+
*
|
|
3302
|
+
* A handler that throws **propagates**: on `preSave`/`preDelete` that vetoes the
|
|
3303
|
+
* write, which is the point — a veto that could be swallowed would be a
|
|
3304
|
+
* suggestion.
|
|
3305
|
+
*
|
|
3306
|
+
* @param signal The signal to fire.
|
|
3307
|
+
* @param payload What to hand the handlers.
|
|
3308
|
+
*/
|
|
3309
|
+
declare function emitSignal<C extends ModelClass>(signal: RepositorySignal, payload: SignalPayload<C>): Promise<void>;
|
|
3310
|
+
/**
|
|
3311
|
+
* Drop registered handlers — for tests, which otherwise leak them across cases.
|
|
3312
|
+
*
|
|
3313
|
+
* @param model Only this model's handlers; omitted, every model's.
|
|
3314
|
+
*/
|
|
3315
|
+
declare function clearSignals(model?: ModelClass): void;
|
|
3316
|
+
|
|
3317
|
+
/**
|
|
3318
|
+
* tempest-db-js — Phase 7: typed repository + pagination.
|
|
3319
|
+
*
|
|
3320
|
+
* `BaseRepository<Model>` mirrors the `tempest-fastapi-sdk` repository: a thin,
|
|
3321
|
+
* fully-typed CRUD + pagination layer over a model and an async session. The
|
|
3322
|
+
* 404-convention is honored — `getById` throws when absent, collection methods
|
|
3323
|
+
* return `[]` (never a "not found" error for an empty list).
|
|
3324
|
+
*/
|
|
3325
|
+
|
|
3326
|
+
/** Pagination request — 1-indexed page. */
|
|
3327
|
+
interface PaginationFilter<Row> {
|
|
3328
|
+
readonly page?: number;
|
|
3329
|
+
readonly pageSize?: number;
|
|
3330
|
+
readonly orderBy?: keyof Row & string;
|
|
3331
|
+
readonly ascending?: boolean;
|
|
3332
|
+
readonly filters?: WhereInput<Row>;
|
|
3333
|
+
}
|
|
3334
|
+
/** A page of results plus metadata (mirrors `BasePaginationSchema`). */
|
|
3335
|
+
interface PaginationResult<Row> {
|
|
3336
|
+
readonly items: Row[];
|
|
3337
|
+
readonly total: number;
|
|
3338
|
+
readonly page: number;
|
|
3339
|
+
readonly pageSize: number;
|
|
3340
|
+
readonly pages: number;
|
|
3341
|
+
}
|
|
3342
|
+
/**
|
|
3343
|
+
* Cursor-pagination request.
|
|
3344
|
+
*
|
|
3345
|
+
* Cursor paging trades random access for two things offset paging cannot give on a
|
|
3346
|
+
* large table: no `COUNT(*)`, and a page boundary that does not shift when rows are
|
|
3347
|
+
* inserted while the user is reading.
|
|
3348
|
+
*
|
|
3349
|
+
* @typeParam Row - the row type being paginated.
|
|
3350
|
+
*/
|
|
3351
|
+
interface CursorPaginationFilter<Row> {
|
|
3352
|
+
/** Opaque cursor from the previous page. Absent/`null` asks for the first page. */
|
|
3353
|
+
readonly cursor?: string | null;
|
|
3354
|
+
/** Maximum rows to return (default 20). */
|
|
3355
|
+
readonly limit?: number;
|
|
3356
|
+
/** Column to sort by. Defaults to the first primary-key column. */
|
|
3357
|
+
readonly orderBy?: keyof Row & string;
|
|
3358
|
+
/** Sort ascending. Defaults to `false`, so the newest rows come first. */
|
|
3359
|
+
readonly ascending?: boolean;
|
|
3360
|
+
/** Domain filters, applied to every page. */
|
|
3361
|
+
readonly filters?: WhereInput<Row>;
|
|
3362
|
+
}
|
|
3363
|
+
/** One cursor-paginated page. */
|
|
3364
|
+
interface CursorPage<Row> {
|
|
3365
|
+
/** The rows of this page. */
|
|
3366
|
+
readonly items: Row[];
|
|
3367
|
+
/** Cursor for the next page, or `null` when this was the last one. */
|
|
3368
|
+
readonly nextCursor: string | null;
|
|
3369
|
+
}
|
|
3370
|
+
/** Raised when a cursor is not one this repository produced. */
|
|
3371
|
+
declare class InvalidCursor extends Error {
|
|
3372
|
+
constructor(reason: string);
|
|
3373
|
+
}
|
|
3374
|
+
/** Request for {@link BaseRepository.changesSince}. */
|
|
3375
|
+
interface ChangesSinceFilter<Row> {
|
|
3376
|
+
/**
|
|
3377
|
+
* The client's high-water mark. Rows changed **strictly after** it are
|
|
3378
|
+
* returned; `null` (or absent) asks for everything, which is the first sync.
|
|
3379
|
+
*/
|
|
3380
|
+
readonly since?: Date | null;
|
|
3381
|
+
/** Cursor from the previous page of this same pull. */
|
|
3382
|
+
readonly cursor?: string | null;
|
|
3383
|
+
/** Maximum rows per page (default 50). */
|
|
3384
|
+
readonly limit?: number;
|
|
3385
|
+
/** Domain filters, applied to every page. */
|
|
3386
|
+
readonly filters?: WhereInput<Row>;
|
|
3387
|
+
}
|
|
3388
|
+
/** One page of changes, plus the watermark to persist for the next pull. */
|
|
3389
|
+
interface ChangesPage<Row> extends CursorPage<Row> {
|
|
3390
|
+
/**
|
|
3391
|
+
* The server's clock, read **before** the query ran.
|
|
3392
|
+
*
|
|
3393
|
+
* This is what a client persists as its next `since` — not the newest
|
|
3394
|
+
* `updatedAt` it saw. A row committed while this page was being built carries
|
|
3395
|
+
* a later timestamp, so it surfaces on the following pull instead of falling
|
|
3396
|
+
* into the gap between the two.
|
|
3397
|
+
*/
|
|
3398
|
+
readonly serverTime: Date;
|
|
3399
|
+
}
|
|
3400
|
+
/** Options for {@link BaseRepository.bulkUpsert}. */
|
|
3401
|
+
interface BulkUpsertOptions<Row> {
|
|
3402
|
+
/** The columns forming the conflict target (a unique constraint or index). */
|
|
3403
|
+
readonly conflictColumns: readonly (keyof Row & string)[];
|
|
3404
|
+
/**
|
|
3405
|
+
* Which columns to overwrite on conflict. Omitted, every column present in the
|
|
3406
|
+
* incoming rows except the conflict target is written.
|
|
3407
|
+
*/
|
|
3408
|
+
readonly update?: readonly (keyof Row & string)[];
|
|
3409
|
+
}
|
|
3410
|
+
/** Raised by single-record lookups (`getById`) when nothing matches (404). */
|
|
3411
|
+
declare class RecordNotFound extends Error {
|
|
3412
|
+
constructor(table: string, key: unknown);
|
|
3413
|
+
}
|
|
3414
|
+
/**
|
|
3415
|
+
* A fully-typed CRUD + pagination repository over a model and an async session.
|
|
3416
|
+
*
|
|
3417
|
+
* @typeParam C - the model class.
|
|
3418
|
+
*/
|
|
3419
|
+
declare class BaseRepository<C extends ModelClass> {
|
|
3420
|
+
protected readonly model: C;
|
|
3421
|
+
protected readonly session: AsyncSession;
|
|
3422
|
+
private readonly pks;
|
|
3423
|
+
constructor(model: C, session: AsyncSession);
|
|
3424
|
+
/**
|
|
3425
|
+
* Narrow every read this repository performs.
|
|
3426
|
+
*
|
|
3427
|
+
* The extension point a scoped repository overrides: returning a filter that
|
|
3428
|
+
* always carries the scope's predicate is what makes it impossible for one
|
|
3429
|
+
* query site to forget it. The base implementation adds nothing.
|
|
3430
|
+
*
|
|
3431
|
+
* @param filters The caller's filters.
|
|
3432
|
+
* @returns The filters actually sent to the database.
|
|
3433
|
+
*/
|
|
3434
|
+
protected scopeFilters(filters?: WhereInput<InferModel<C>>): WhereInput<InferModel<C>> | undefined;
|
|
3435
|
+
/**
|
|
3436
|
+
* Stamp every row this repository writes.
|
|
3437
|
+
*
|
|
3438
|
+
* The write-side counterpart of {@link scopeFilters}. The base implementation
|
|
3439
|
+
* writes the row unchanged.
|
|
3440
|
+
*
|
|
3441
|
+
* @param data The row being written.
|
|
3442
|
+
* @returns The row actually written.
|
|
3443
|
+
*/
|
|
3444
|
+
protected scopeWrite<T extends Record<string, unknown>>(data: T): T;
|
|
3445
|
+
/** All rows matching `filters` (or everything). Empty list when none match. */
|
|
3446
|
+
list(input?: WhereInput<InferModel<C>>): Promise<InferModel<C>[]>;
|
|
3447
|
+
/** The first row matching `filters`, or `null`. */
|
|
3448
|
+
first(input?: WhereInput<InferModel<C>>): Promise<InferModel<C> | null>;
|
|
3449
|
+
/**
|
|
3450
|
+
* A single row by primary key, or `null`.
|
|
3451
|
+
*
|
|
3452
|
+
* @param id The key — a bare value for a single-column key, an object
|
|
3453
|
+
* (`{ orderId, lineNumber }`) for a composite one.
|
|
3454
|
+
* @returns The row, or `null` when nothing matches.
|
|
3455
|
+
* @throws Error When a scalar is given for a composite key, or the key is
|
|
3456
|
+
* incomplete.
|
|
3457
|
+
*/
|
|
3458
|
+
getByIdOrNull(id: unknown): Promise<InferModel<C> | null>;
|
|
3459
|
+
/**
|
|
3460
|
+
* A single row by primary key; throws `RecordNotFound` when absent.
|
|
3461
|
+
*
|
|
3462
|
+
* @param id The key — see {@link getByIdOrNull}.
|
|
3463
|
+
* @returns The row.
|
|
3464
|
+
* @throws RecordNotFound When no row carries that key.
|
|
3465
|
+
*/
|
|
3466
|
+
getById(id: unknown): Promise<InferModel<C>>;
|
|
3467
|
+
/** Whether any row matches `filters`. */
|
|
3468
|
+
exists(filters: WhereInput<InferModel<C>>): Promise<boolean>;
|
|
3469
|
+
/** How many rows match `filters` (or the whole table). */
|
|
3470
|
+
count(input?: WhereInput<InferModel<C>>): Promise<number>;
|
|
3471
|
+
/**
|
|
3472
|
+
* Insert one row, returning the created row.
|
|
3473
|
+
*
|
|
3474
|
+
* Fires `preSave` (which can veto by throwing) and then `postSave`.
|
|
3475
|
+
*
|
|
3476
|
+
* @param data The row to insert.
|
|
3477
|
+
* @returns The stored row.
|
|
3478
|
+
*/
|
|
3479
|
+
create(data: InferInsert<C>): Promise<InferModel<C>>;
|
|
3480
|
+
/**
|
|
3481
|
+
* Insert many rows, returning the created rows.
|
|
3482
|
+
*
|
|
3483
|
+
* Signals fire per row, so a handler sees one row at a time whether it was
|
|
3484
|
+
* inserted alone or in a batch.
|
|
3485
|
+
*
|
|
3486
|
+
* @param data The rows to insert.
|
|
3487
|
+
* @returns The stored rows.
|
|
3488
|
+
*/
|
|
3489
|
+
createMany(data: readonly InferInsert<C>[]): Promise<InferModel<C>[]>;
|
|
3490
|
+
/**
|
|
3491
|
+
* Update rows matching `filters`; returns the number of rows affected.
|
|
3492
|
+
*
|
|
3493
|
+
* With a `preSave`/`postSave` handler registered, the matching rows are read
|
|
3494
|
+
* first so the handler can see them — that extra `SELECT` is skipped entirely
|
|
3495
|
+
* when nothing is listening.
|
|
3496
|
+
*
|
|
3497
|
+
* @param filters Which rows to update.
|
|
3498
|
+
* @param set The columns to change.
|
|
3499
|
+
* @returns The number of rows affected.
|
|
3500
|
+
*/
|
|
3501
|
+
update(input: WhereInput<InferModel<C>>, set: Partial<InferModel<C>>): Promise<number>;
|
|
3502
|
+
/**
|
|
3503
|
+
* Delete rows matching `filters`; returns the number of rows affected.
|
|
3504
|
+
*
|
|
3505
|
+
* `preDelete` and `postDelete` receive the row as it was **before** the delete —
|
|
3506
|
+
* the only chance to see it. Reading it costs a `SELECT`, which is skipped when
|
|
3507
|
+
* no handler is registered.
|
|
3508
|
+
*
|
|
3509
|
+
* @param filters Which rows to delete.
|
|
3510
|
+
* @returns The number of rows affected.
|
|
3511
|
+
*/
|
|
3512
|
+
delete(input: WhereInput<InferModel<C>>): Promise<number>;
|
|
3513
|
+
/**
|
|
3514
|
+
* Fire one signal for one row on this repository's model and session.
|
|
3515
|
+
*
|
|
3516
|
+
* @param signal Which signal.
|
|
3517
|
+
* @param row The row it is about.
|
|
3518
|
+
* @param isInsert Whether the write was an insert.
|
|
3519
|
+
*/
|
|
3520
|
+
private fire;
|
|
3521
|
+
/**
|
|
3522
|
+
* A page of rows plus metadata. `total` counts all matching rows.
|
|
3523
|
+
*
|
|
3524
|
+
* @param filter Page, size, ordering and filters.
|
|
3525
|
+
* @returns The page and pagination metadata.
|
|
3526
|
+
*/
|
|
3527
|
+
paginate(filter?: PaginationFilter<InferModel<C>>): Promise<PaginationResult<InferModel<C>>>;
|
|
3528
|
+
/**
|
|
3529
|
+
* A cursor-paginated page: the rows after `cursor`, plus the cursor for the
|
|
3530
|
+
* next page.
|
|
3531
|
+
*
|
|
3532
|
+
* No `COUNT(*)` runs, and the page boundary is stable under concurrent inserts —
|
|
3533
|
+
* the two reasons to reach for this instead of {@link paginate} on a large table.
|
|
3534
|
+
* The trade-off is losing random access: there is no "page 7".
|
|
3535
|
+
*
|
|
3536
|
+
* The primary key is always appended as a tie-break, so rows sharing an
|
|
3537
|
+
* `orderBy` value cannot be skipped or repeated across pages.
|
|
3538
|
+
*
|
|
3539
|
+
* @param filter Cursor, page size, ordering and filters.
|
|
3540
|
+
* @returns The page and the next cursor (`null` on the last page).
|
|
3541
|
+
* @throws InvalidCursor When the cursor is malformed or was built for a
|
|
3542
|
+
* different ordering.
|
|
3543
|
+
*/
|
|
3544
|
+
cursorPaginate(filter?: CursorPaginationFilter<InferModel<C>>): Promise<CursorPage<InferModel<C>>>;
|
|
3545
|
+
/**
|
|
3546
|
+
* Whether any **other** row matches `filters`.
|
|
3547
|
+
*
|
|
3548
|
+
* The uniqueness check for an update: "is this e-mail taken by somebody else?"
|
|
3549
|
+
* A plain `exists` would find the row being edited and report a false conflict.
|
|
3550
|
+
*
|
|
3551
|
+
* @param filters What to look for.
|
|
3552
|
+
* @param key The primary key to exclude — the row being updated.
|
|
3553
|
+
* @returns True when a different row matches.
|
|
3554
|
+
*/
|
|
3555
|
+
existsExcluding(filters: WhereInput<InferModel<C>>, key: unknown): Promise<boolean>;
|
|
3556
|
+
/**
|
|
3557
|
+
* The rows that changed since a high-water mark — the delta-sync read.
|
|
3558
|
+
*
|
|
3559
|
+
* Rows come back oldest change first, tie-broken by primary key, so a client
|
|
3560
|
+
* can advance its watermark monotonically and resume mid-stream with the
|
|
3561
|
+
* cursor. The filter is **strict** (`updatedAt > since`).
|
|
3562
|
+
*
|
|
3563
|
+
* Soft-deleted rows are included on purpose: they are the tombstones that tell
|
|
3564
|
+
* the client to delete its local copy. Filtering them out would strand deleted
|
|
3565
|
+
* rows on the device forever.
|
|
3566
|
+
*
|
|
3567
|
+
* @param filter The watermark, cursor, page size and domain filters.
|
|
3568
|
+
* @returns The page, plus the `serverTime` to persist as the next watermark.
|
|
3569
|
+
* @throws Error When the model has no `updatedAt` column (see `withTimestamps`).
|
|
3570
|
+
*/
|
|
3571
|
+
changesSince(filter?: ChangesSinceFilter<InferModel<C>>): Promise<ChangesPage<InferModel<C>>>;
|
|
3572
|
+
/**
|
|
3573
|
+
* Insert many rows, overwriting the ones that conflict — one statement.
|
|
3574
|
+
*
|
|
3575
|
+
* @param rows The rows to write.
|
|
3576
|
+
* @param options The conflict target, and optionally which columns to overwrite.
|
|
3577
|
+
* @returns The stored rows.
|
|
3578
|
+
* @throws Error When no conflict column is given.
|
|
3579
|
+
*/
|
|
3580
|
+
bulkUpsert(rows: readonly InferInsert<C>[], options: BulkUpsertOptions<InferModel<C>>): Promise<InferModel<C>[]>;
|
|
3581
|
+
/**
|
|
3582
|
+
* Mark a row deleted without removing it (`deletedAt = now()`).
|
|
3583
|
+
*
|
|
3584
|
+
* @param key The primary key.
|
|
3585
|
+
* @returns The updated row.
|
|
3586
|
+
* @throws Error When the model has no `deletedAt` column (see `withSoftDelete`).
|
|
3587
|
+
* @throws RecordNotFound When no row carries that key.
|
|
3588
|
+
*/
|
|
3589
|
+
softDelete(key: unknown): Promise<InferModel<C>>;
|
|
3590
|
+
/**
|
|
3591
|
+
* Bring a soft-deleted row back (`deletedAt = null`).
|
|
3592
|
+
*
|
|
3593
|
+
* @param key The primary key.
|
|
3594
|
+
* @returns The updated row.
|
|
3595
|
+
* @throws Error When the model has no `deletedAt` column.
|
|
3596
|
+
* @throws RecordNotFound When no row carries that key.
|
|
3597
|
+
*/
|
|
3598
|
+
restore(key: unknown): Promise<InferModel<C>>;
|
|
3599
|
+
/**
|
|
3600
|
+
* Delete many rows by primary key, in one statement.
|
|
3601
|
+
*
|
|
3602
|
+
* @param keys The primary keys.
|
|
3603
|
+
* @returns The number of rows actually deleted (keys that matched nothing are
|
|
3604
|
+
* not an error — deleting what is already gone is the desired end state).
|
|
3605
|
+
* @throws Error When the model has a composite primary key: `IN` over a tuple is
|
|
3606
|
+
* not portable, and the caller should loop or build the condition explicitly.
|
|
3607
|
+
*/
|
|
3608
|
+
deleteBatch(keys: readonly unknown[]): Promise<number>;
|
|
3609
|
+
/**
|
|
3610
|
+
* Fail loudly when a method needs a column the model does not declare.
|
|
3611
|
+
*
|
|
3612
|
+
* @param column The property name required.
|
|
3613
|
+
* @param method The method asking, for the message.
|
|
3614
|
+
* @throws Error When the column is absent.
|
|
3615
|
+
*/
|
|
3616
|
+
private requireColumn;
|
|
3617
|
+
}
|
|
3618
|
+
|
|
3619
|
+
/** Where an outbox row is in its lifecycle. */
|
|
3620
|
+
type OutboxStatus = "pending" | "sending" | "sent" | "failed";
|
|
3621
|
+
/**
|
|
3622
|
+
* Build the base class for a service's outbox table.
|
|
3623
|
+
*
|
|
3624
|
+
* @param tablename The table to store events in.
|
|
3625
|
+
* @returns A model base carrying the outbox columns; extend it to add your own.
|
|
3626
|
+
*
|
|
3627
|
+
* @example
|
|
3628
|
+
* ```ts
|
|
3629
|
+
* class OutboxEvent extends outboxModel("outbox") {}
|
|
3630
|
+
* ```
|
|
3631
|
+
*/
|
|
3632
|
+
declare function outboxModel(name: string): (abstract new () => {
|
|
3633
|
+
/** Monotonic id; also the claim order, so events publish in the order written. */
|
|
3634
|
+
id: Column<bigint, ColumnFlags & {
|
|
3635
|
+
primaryKey: true;
|
|
3636
|
+
hasDefault: true;
|
|
3637
|
+
}>;
|
|
3638
|
+
/** The routing key the relay publishes under. */
|
|
3639
|
+
topic: Column<string, ColumnFlags & {
|
|
3640
|
+
notNull: true;
|
|
3641
|
+
}>;
|
|
3642
|
+
/** The event body. */
|
|
3643
|
+
payload: Column<Record<string, unknown>, ColumnFlags & {
|
|
3644
|
+
notNull: true;
|
|
3645
|
+
}>;
|
|
3646
|
+
/** Lifecycle state. */
|
|
3647
|
+
status: Column<"pending" | "sending" | "sent" | "failed", ColumnFlags & {
|
|
3648
|
+
notNull: true;
|
|
3649
|
+
} & {
|
|
3650
|
+
hasDefault: true;
|
|
3651
|
+
}>;
|
|
3652
|
+
/** How many publish attempts have been made. */
|
|
3653
|
+
attempts: Column<number, ColumnFlags & {
|
|
3654
|
+
notNull: true;
|
|
3655
|
+
} & {
|
|
3656
|
+
hasDefault: true;
|
|
3657
|
+
}>;
|
|
3658
|
+
/** Epoch milliseconds before which the row must not be claimed (backoff). */
|
|
3659
|
+
availableAt: Column<bigint, ColumnFlags & {
|
|
3660
|
+
notNull: true;
|
|
3661
|
+
} & {
|
|
3662
|
+
hasDefault: true;
|
|
3663
|
+
}>;
|
|
3664
|
+
/** When the row was written. */
|
|
3665
|
+
createdAt: Column<Date, ColumnFlags & {
|
|
3666
|
+
notNull: true;
|
|
3667
|
+
} & {
|
|
3668
|
+
hasDefault: true;
|
|
3669
|
+
}>;
|
|
3670
|
+
/** When the relay confirmed the publish. */
|
|
3671
|
+
sentAt: Column<Date, ColumnFlags>;
|
|
3672
|
+
/** The last failure's message, kept for triage. */
|
|
3673
|
+
lastError: Column<string, ColumnFlags>;
|
|
3674
|
+
}) & {
|
|
3675
|
+
tablename: string;
|
|
3676
|
+
tableArgs?: () => readonly TableConstraint[];
|
|
3677
|
+
naming?: NamingStrategy;
|
|
3678
|
+
};
|
|
3679
|
+
/** An event to write to the outbox. */
|
|
3680
|
+
interface OutboxEventInput {
|
|
3681
|
+
/** The routing key. */
|
|
3682
|
+
readonly topic: string;
|
|
3683
|
+
/** The event body. */
|
|
3684
|
+
readonly payload: Record<string, unknown>;
|
|
3685
|
+
/** Delay the first attempt by this many milliseconds. */
|
|
3686
|
+
readonly delayMs?: number;
|
|
3687
|
+
}
|
|
3688
|
+
/** Options for {@link OutboxRepository.claim}. */
|
|
3689
|
+
interface ClaimOptions {
|
|
3690
|
+
/** Only claim events on these topics. */
|
|
3691
|
+
readonly topics?: readonly string[];
|
|
3692
|
+
/** Treat the clock as this instant (tests). */
|
|
3693
|
+
readonly now?: number;
|
|
3694
|
+
}
|
|
3695
|
+
/** Options for {@link OutboxRepository.markFailed}. */
|
|
3696
|
+
interface FailOptions {
|
|
3697
|
+
/** Wait this long before the row can be claimed again. */
|
|
3698
|
+
readonly retryInMs?: number;
|
|
3699
|
+
/** Give up permanently instead of scheduling a retry. */
|
|
3700
|
+
readonly permanent?: boolean;
|
|
3701
|
+
}
|
|
3702
|
+
/**
|
|
3703
|
+
* The relay's half of the outbox: claim, confirm, and fail with backoff.
|
|
3704
|
+
*
|
|
3705
|
+
* @typeParam C - the outbox model class.
|
|
3706
|
+
*/
|
|
3707
|
+
declare class OutboxRepository<C extends ModelClass> extends BaseRepository<C> {
|
|
3708
|
+
/**
|
|
3709
|
+
* Write events to the outbox.
|
|
3710
|
+
*
|
|
3711
|
+
* Call it inside the same `transaction()` as the business write — that is the
|
|
3712
|
+
* whole point, and it is why this does not open a transaction of its own.
|
|
3713
|
+
*
|
|
3714
|
+
* @param events One event, or many.
|
|
3715
|
+
* @returns The stored rows.
|
|
3716
|
+
*/
|
|
3717
|
+
publish(events: OutboxEventInput | readonly OutboxEventInput[]): Promise<InferModel<C>[]>;
|
|
3718
|
+
/**
|
|
3719
|
+
* Claim a batch of due events for this relay, in one statement.
|
|
3720
|
+
*
|
|
3721
|
+
* Uses `FOR UPDATE SKIP LOCKED` over a subquery, so two relays running at once
|
|
3722
|
+
* take **disjoint** batches instead of fighting over the same rows. SQLite has
|
|
3723
|
+
* no row locking and throws — a single-process relay there can claim with
|
|
3724
|
+
* `pending()` plus an update.
|
|
3725
|
+
*
|
|
3726
|
+
* @param limit How many events to take.
|
|
3727
|
+
* @param options Topic filter, and a clock override for tests.
|
|
3728
|
+
* @returns The claimed rows, oldest first.
|
|
3729
|
+
*/
|
|
3730
|
+
claim(limit: number, options?: ClaimOptions): Promise<InferModel<C>[]>;
|
|
3731
|
+
/**
|
|
3732
|
+
* The events that are due, without claiming them.
|
|
3733
|
+
*
|
|
3734
|
+
* @param limit How many to read.
|
|
3735
|
+
* @param options Topic filter and clock override.
|
|
3736
|
+
* @returns The due rows, oldest first.
|
|
3737
|
+
*/
|
|
3738
|
+
pending(limit: number, options?: ClaimOptions): Promise<InferModel<C>[]>;
|
|
3739
|
+
/**
|
|
3740
|
+
* Confirm that events were published.
|
|
3741
|
+
*
|
|
3742
|
+
* @param ids The claimed ids.
|
|
3743
|
+
* @returns How many rows were marked sent.
|
|
3744
|
+
*/
|
|
3745
|
+
markSent(ids: readonly bigint[]): Promise<number>;
|
|
3746
|
+
/**
|
|
3747
|
+
* Record a failed publish, scheduling a retry unless it is permanent.
|
|
3748
|
+
*
|
|
3749
|
+
* The attempt counter was already incremented by {@link claim}, so a row that
|
|
3750
|
+
* keeps failing carries its own history — which is what a dead-letter policy
|
|
3751
|
+
* reads.
|
|
3752
|
+
*
|
|
3753
|
+
* @param id The event's id.
|
|
3754
|
+
* @param error The failure, for triage.
|
|
3755
|
+
* @param options Backoff delay, or `permanent` to stop retrying.
|
|
3756
|
+
* @returns How many rows were updated (0 or 1).
|
|
3757
|
+
*/
|
|
3758
|
+
markFailed(id: bigint, error: unknown, options?: FailOptions): Promise<number>;
|
|
3759
|
+
/**
|
|
3760
|
+
* The SELECT of events that may be claimed now.
|
|
3761
|
+
*
|
|
3762
|
+
* @param now The current epoch milliseconds.
|
|
3763
|
+
* @param topics Optional topic filter.
|
|
3764
|
+
* @returns The builder, unordered.
|
|
3765
|
+
*/
|
|
3766
|
+
private dueQuery;
|
|
3767
|
+
}
|
|
3768
|
+
|
|
3769
|
+
/**
|
|
3770
|
+
* tempest-db-js — a repository scoped to one tenant.
|
|
3771
|
+
*
|
|
3772
|
+
* In a shared-schema multi-tenant database every tenant's rows live in the same
|
|
3773
|
+
* table, told apart by a column. The danger is plain: forget one
|
|
3774
|
+
* `WHERE tenantId = ?` and tenant A reads — or deletes — tenant B's data. The
|
|
3775
|
+
* footgun is not the missing predicate, it is that **every query site** has to
|
|
3776
|
+
* remember it.
|
|
3777
|
+
*
|
|
3778
|
+
* This binds the tenant once, at construction, and injects the predicate into
|
|
3779
|
+
* every read and every write the repository performs.
|
|
3780
|
+
*/
|
|
3781
|
+
|
|
3782
|
+
/** How a repository is bound to one tenant. */
|
|
3783
|
+
interface TenantScope {
|
|
3784
|
+
/** The column carrying the tenant, by property name. */
|
|
3785
|
+
readonly column: string;
|
|
3786
|
+
/** The tenant's identifier. */
|
|
3787
|
+
readonly id: unknown;
|
|
1559
3788
|
}
|
|
1560
3789
|
/**
|
|
1561
|
-
* A
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
3790
|
+
* A repository whose every read and write is confined to one tenant.
|
|
3791
|
+
*
|
|
3792
|
+
* @typeParam C - the model class.
|
|
1564
3793
|
*/
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
interface EngineOptions {
|
|
1568
|
-
/** Override the driver detected from the URL (e.g. `"better-sqlite3"`). */
|
|
1569
|
-
readonly driver?: string;
|
|
1570
|
-
/** Connection-pool tuning (PostgreSQL only). */
|
|
1571
|
-
readonly pool?: PoolOptions;
|
|
3794
|
+
declare class TenantScopedRepository<C extends ModelClass> extends BaseRepository<C> {
|
|
3795
|
+
private readonly scope;
|
|
1572
3796
|
/**
|
|
1573
|
-
*
|
|
1574
|
-
*
|
|
3797
|
+
* Bind a repository to one tenant.
|
|
3798
|
+
*
|
|
3799
|
+
* @param model The model class.
|
|
3800
|
+
* @param session The session to run on.
|
|
3801
|
+
* @param scope The tenant column and id.
|
|
3802
|
+
* @throws Error When the model has no such column — a scope that silently
|
|
3803
|
+
* matches nothing is worse than no scope at all.
|
|
1575
3804
|
*/
|
|
1576
|
-
|
|
3805
|
+
constructor(model: C, session: AsyncSession, scope: TenantScope);
|
|
3806
|
+
/** The tenant this repository is bound to. */
|
|
3807
|
+
get tenantId(): unknown;
|
|
1577
3808
|
/**
|
|
1578
|
-
*
|
|
1579
|
-
* existing table, `DROP ... IF EXISTS` on a missing one, and so on).
|
|
3809
|
+
* Merge the tenant predicate into every read.
|
|
1580
3810
|
*
|
|
1581
|
-
*
|
|
1582
|
-
*
|
|
1583
|
-
*
|
|
1584
|
-
* runner is usually the first thing to run. Writing to the host's stdout is the
|
|
1585
|
-
* application's decision, not a library's, so the default is to say nothing and
|
|
1586
|
-
* let you route them:
|
|
3811
|
+
* The caller's filters are **added to**, never replaced: passing another
|
|
3812
|
+
* tenant's id produces a contradiction that matches nothing, which is the safe
|
|
3813
|
+
* outcome.
|
|
1587
3814
|
*
|
|
1588
|
-
*
|
|
1589
|
-
*
|
|
1590
|
-
|
|
3815
|
+
* @param filters The caller's filters.
|
|
3816
|
+
* @returns The filters plus the tenant predicate.
|
|
3817
|
+
*/
|
|
3818
|
+
protected scopeFilters(filters?: WhereInput<InferModel<C>>): WhereInput<InferModel<C>> | undefined;
|
|
3819
|
+
/**
|
|
3820
|
+
* Stamp the tenant onto every row written.
|
|
1591
3821
|
*
|
|
1592
|
-
*
|
|
3822
|
+
* A row that arrives carrying a **different** tenant is refused rather than
|
|
3823
|
+
* overwritten: silently rewriting it would turn a caller's bug into data that
|
|
3824
|
+
* looks deliberate.
|
|
3825
|
+
*
|
|
3826
|
+
* @param data The row being written.
|
|
3827
|
+
* @returns The row with the tenant column set.
|
|
3828
|
+
* @throws Error When the row names another tenant.
|
|
1593
3829
|
*/
|
|
1594
|
-
|
|
3830
|
+
protected scopeWrite<T extends Record<string, unknown>>(data: T): T;
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
/** What happened to the row. */
|
|
3834
|
+
type AuditAction = "insert" | "update" | "delete";
|
|
3835
|
+
/** A before/after pair per changed column. */
|
|
3836
|
+
type AuditDiff = Record<string, readonly [unknown, unknown]>;
|
|
3837
|
+
/**
|
|
3838
|
+
* Build the base class for a service's audit-log table.
|
|
3839
|
+
*
|
|
3840
|
+
* @param name The table to store entries in.
|
|
3841
|
+
* @returns A model base carrying the audit columns; extend it to add your own.
|
|
3842
|
+
*
|
|
3843
|
+
* @example
|
|
3844
|
+
* ```ts
|
|
3845
|
+
* class AuditLog extends auditLogModel("audit_log") {}
|
|
3846
|
+
* ```
|
|
3847
|
+
*/
|
|
3848
|
+
declare function auditLogModel(name: string): (abstract new () => {
|
|
3849
|
+
/** Monotonic id — also the order the changes happened in. */
|
|
3850
|
+
id: Column<bigint, ColumnFlags & {
|
|
3851
|
+
primaryKey: true;
|
|
3852
|
+
hasDefault: true;
|
|
3853
|
+
}>;
|
|
3854
|
+
/** The audited table. */
|
|
3855
|
+
tableName: Column<string, ColumnFlags & {
|
|
3856
|
+
notNull: true;
|
|
3857
|
+
}>;
|
|
3858
|
+
/** The audited row's primary key, as an object. */
|
|
3859
|
+
rowKey: Column<Record<string, unknown>, ColumnFlags & {
|
|
3860
|
+
notNull: true;
|
|
3861
|
+
}>;
|
|
3862
|
+
/** What happened. */
|
|
3863
|
+
action: Column<"insert" | "update" | "delete", ColumnFlags & {
|
|
3864
|
+
notNull: true;
|
|
3865
|
+
}>;
|
|
3866
|
+
/** Who did it, when the caller could say. */
|
|
3867
|
+
actor: Column<string, ColumnFlags>;
|
|
3868
|
+
/** The changed columns as `[before, after]`; the whole row on insert/delete. */
|
|
3869
|
+
changes: Column<AuditDiff, ColumnFlags & {
|
|
3870
|
+
notNull: true;
|
|
3871
|
+
}>;
|
|
3872
|
+
/** When the entry was written. */
|
|
3873
|
+
at: Column<Date, ColumnFlags & {
|
|
3874
|
+
notNull: true;
|
|
3875
|
+
} & {
|
|
3876
|
+
hasDefault: true;
|
|
3877
|
+
}>;
|
|
3878
|
+
}) & {
|
|
3879
|
+
tablename: string;
|
|
3880
|
+
tableArgs?: () => readonly TableConstraint[];
|
|
3881
|
+
naming?: NamingStrategy;
|
|
3882
|
+
};
|
|
3883
|
+
/** How auditing is wired for one model. */
|
|
3884
|
+
interface AuditOptions<L extends ModelClass> {
|
|
3885
|
+
/** The audit-log model to write entries into. */
|
|
3886
|
+
readonly log: L;
|
|
1595
3887
|
/**
|
|
1596
|
-
*
|
|
1597
|
-
* win over everything this layer derives (`pool`, `onNotice`).
|
|
3888
|
+
* Who is making the change.
|
|
1598
3889
|
*
|
|
1599
|
-
*
|
|
1600
|
-
*
|
|
1601
|
-
* settings, `node:sqlite`'s `readOnly` — so a gap need not become a feature
|
|
1602
|
-
* request.
|
|
3890
|
+
* Called at write time, so it can read whatever request-scoped context the
|
|
3891
|
+
* service keeps. Return `null` for a change with no human behind it.
|
|
1603
3892
|
*/
|
|
1604
|
-
readonly
|
|
3893
|
+
readonly actor?: () => string | null;
|
|
3894
|
+
/** Columns never worth recording (a password hash, a big blob). */
|
|
3895
|
+
readonly exclude?: readonly string[];
|
|
1605
3896
|
}
|
|
1606
|
-
/**
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
3897
|
+
/**
|
|
3898
|
+
* The columns of a row, encoded for storage in the log's JSON column.
|
|
3899
|
+
*
|
|
3900
|
+
* @param model The audited model.
|
|
3901
|
+
* @param row The row.
|
|
3902
|
+
* @param exclude Columns to leave out.
|
|
3903
|
+
* @returns The row as JSON-safe values.
|
|
3904
|
+
*/
|
|
3905
|
+
declare function snapshot(model: ModelClass, row: Record<string, unknown>, exclude?: readonly string[]): Record<string, unknown>;
|
|
3906
|
+
/**
|
|
3907
|
+
* The columns that differ between two snapshots.
|
|
3908
|
+
*
|
|
3909
|
+
* Compared **after** encoding, so a `Date` and its stored string do not read as a
|
|
3910
|
+
* change; a column missing from one side counts as a change to `null`.
|
|
3911
|
+
*
|
|
3912
|
+
* @param before The earlier snapshot.
|
|
3913
|
+
* @param after The later snapshot.
|
|
3914
|
+
* @returns One `[before, after]` pair per changed column.
|
|
3915
|
+
*/
|
|
3916
|
+
declare function diffSnapshots(before: Record<string, unknown>, after: Record<string, unknown>): AuditDiff;
|
|
3917
|
+
/**
|
|
3918
|
+
* Record every create, update and delete of a model into an audit log.
|
|
3919
|
+
*
|
|
3920
|
+
* Wired through the repository signals, so entries are written on the same
|
|
3921
|
+
* session — and therefore inside the same transaction — as the change. A change
|
|
3922
|
+
* that rolls back takes its audit entry with it.
|
|
3923
|
+
*
|
|
3924
|
+
* @param model The model to audit.
|
|
3925
|
+
* @param options The log model, the actor resolver and any excluded columns.
|
|
3926
|
+
* @returns A function that turns auditing off again.
|
|
3927
|
+
*
|
|
3928
|
+
* @example
|
|
3929
|
+
* ```ts
|
|
3930
|
+
* class AuditLog extends auditLogModel("audit_log") {}
|
|
3931
|
+
* enableAudit(Order, { log: AuditLog, actor: () => currentUser()?.id ?? null });
|
|
3932
|
+
* ```
|
|
3933
|
+
*/
|
|
3934
|
+
declare function enableAudit<C extends ModelClass, L extends ModelClass>(model: C, options: AuditOptions<L>): () => void;
|
|
3935
|
+
/** The row type of an audit-log model, for the reader's convenience. */
|
|
3936
|
+
type AuditEntry<L extends ModelClass> = InferModel<L>;
|
|
3937
|
+
|
|
3938
|
+
/**
|
|
3939
|
+
* tempest-db-js — backup and restore, driven from the CLI.
|
|
3940
|
+
*
|
|
3941
|
+
* The step every runbook asks for before a migration, wrapped so it is the same
|
|
3942
|
+
* command against both databases in scope. It shells out to the canonical tooling
|
|
3943
|
+
* rather than reimplementing a dump format: `pg_dump`/`pg_restore`/`psql` on
|
|
3944
|
+
* PostgreSQL, and SQLite's own `VACUUM INTO` — which, unlike copying the file, is
|
|
3945
|
+
* consistent while the database is being written to.
|
|
3946
|
+
*/
|
|
3947
|
+
/** Raised when the tool a dialect needs is not on the PATH. */
|
|
3948
|
+
declare class BackupToolMissing extends Error {
|
|
3949
|
+
constructor(tool: string);
|
|
1617
3950
|
}
|
|
1618
|
-
/**
|
|
1619
|
-
declare class
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
3951
|
+
/** Raised for a dialect that has no backup path here. */
|
|
3952
|
+
declare class UnsupportedBackupBackend extends Error {
|
|
3953
|
+
constructor(dialect: string);
|
|
3954
|
+
}
|
|
3955
|
+
/** Options shared by backup and restore. */
|
|
3956
|
+
interface BackupOptions {
|
|
3957
|
+
/** Overwrite the target if it already exists (restore only). */
|
|
3958
|
+
readonly force?: boolean;
|
|
3959
|
+
/** Extra arguments appended to the underlying tool's command line. */
|
|
3960
|
+
readonly extraArgs?: readonly string[];
|
|
3961
|
+
}
|
|
3962
|
+
/** What a backup or restore did. */
|
|
3963
|
+
interface BackupResult {
|
|
3964
|
+
/** The file written (backup) or read (restore). */
|
|
3965
|
+
readonly file: string;
|
|
3966
|
+
/** The dialect it was taken from. */
|
|
3967
|
+
readonly dialect: string;
|
|
3968
|
+
/** The tool used, or `"VACUUM INTO"` / `"copy"` for SQLite. */
|
|
3969
|
+
readonly via: string;
|
|
1629
3970
|
}
|
|
1630
3971
|
/**
|
|
1631
|
-
*
|
|
3972
|
+
* The dump format implied by a file's extension.
|
|
1632
3973
|
*
|
|
1633
|
-
* `
|
|
1634
|
-
*
|
|
1635
|
-
*
|
|
1636
|
-
* from the object's shape.
|
|
3974
|
+
* `.dump` is `pg_dump`'s custom format, which `pg_restore` reads selectively and
|
|
3975
|
+
* in parallel; `.sql` is plain text, restored by piping it through `psql`. The
|
|
3976
|
+
* extension decides so the two commands cannot disagree about the file.
|
|
1637
3977
|
*
|
|
1638
|
-
* @param
|
|
1639
|
-
* @returns
|
|
3978
|
+
* @param file The backup file's path.
|
|
3979
|
+
* @returns Which format it is.
|
|
1640
3980
|
*/
|
|
1641
|
-
declare function
|
|
3981
|
+
declare function backupFormat(file: string): "custom" | "plain";
|
|
1642
3982
|
/**
|
|
1643
|
-
*
|
|
1644
|
-
* has no sane synchronous driver in Node, so a Postgres URL throws, pointing at
|
|
1645
|
-
* the async `createEngine`.
|
|
3983
|
+
* The connection URL with the driver suffix removed.
|
|
1646
3984
|
*
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1649
|
-
*
|
|
3985
|
+
* `postgresql+asyncpg://…` is a URL a Python service wrote; `pg_dump` does not
|
|
3986
|
+
* know that scheme.
|
|
3987
|
+
*
|
|
3988
|
+
* @param url The URL as configured.
|
|
3989
|
+
* @returns The URL a client tool accepts.
|
|
1650
3990
|
*/
|
|
1651
|
-
declare function
|
|
3991
|
+
declare function toolUrl(url: string): string;
|
|
1652
3992
|
/**
|
|
1653
|
-
*
|
|
1654
|
-
* both SQLite (sync driver wrapped as async) and PostgreSQL (postgres.js,
|
|
1655
|
-
* lazy-loaded).
|
|
3993
|
+
* Write a backup of the database to a file.
|
|
1656
3994
|
*
|
|
1657
|
-
* @param url
|
|
1658
|
-
*
|
|
1659
|
-
*
|
|
1660
|
-
* @
|
|
3995
|
+
* @param url The database URL.
|
|
3996
|
+
* @param file Where to write. On PostgreSQL the extension picks the format
|
|
3997
|
+
* (`.sql` plain, anything else custom).
|
|
3998
|
+
* @param options Extra arguments for the underlying tool.
|
|
3999
|
+
* @returns What was done.
|
|
4000
|
+
* @throws BackupToolMissing When PostgreSQL's client tools are absent.
|
|
4001
|
+
* @throws UnsupportedBackupBackend On a dialect without a backup path.
|
|
1661
4002
|
*/
|
|
1662
|
-
declare function
|
|
4003
|
+
declare function backupDatabase(url: string, file: string, options?: BackupOptions): Promise<BackupResult>;
|
|
4004
|
+
/**
|
|
4005
|
+
* Restore a database from a backup file.
|
|
4006
|
+
*
|
|
4007
|
+
* @param url The database URL to restore **into**.
|
|
4008
|
+
* @param file The backup file.
|
|
4009
|
+
* @param options `force` to overwrite an existing SQLite file; extra tool args.
|
|
4010
|
+
* @returns What was done.
|
|
4011
|
+
* @throws Error When restoring SQLite over an existing file without `force`.
|
|
4012
|
+
* @throws BackupToolMissing When PostgreSQL's client tools are absent.
|
|
4013
|
+
* @throws UnsupportedBackupBackend On a dialect without a restore path.
|
|
4014
|
+
*/
|
|
4015
|
+
declare function restoreDatabase(url: string, file: string, options?: BackupOptions): Promise<BackupResult>;
|
|
1663
4016
|
|
|
1664
4017
|
/**
|
|
1665
|
-
* tempest-db-js —
|
|
4018
|
+
* tempest-db-js — table aliases outside the join builder.
|
|
1666
4019
|
*
|
|
1667
|
-
* `
|
|
1668
|
-
*
|
|
1669
|
-
*
|
|
1670
|
-
*
|
|
4020
|
+
* `join(Model, "a")` has always taken an alias, so a self-join works there. A
|
|
4021
|
+
* plain `select()` had no way to name its table, which is what a correlated
|
|
4022
|
+
* subquery over the **same** table needs: without an alias, the inner and outer
|
|
4023
|
+
* `users` are the same name, and the correlation cannot be written at all.
|
|
1671
4024
|
*/
|
|
1672
4025
|
|
|
1673
|
-
/** Pagination request — 1-indexed page. */
|
|
1674
|
-
interface PaginationFilter<Row> {
|
|
1675
|
-
readonly page?: number;
|
|
1676
|
-
readonly pageSize?: number;
|
|
1677
|
-
readonly orderBy?: keyof Row & string;
|
|
1678
|
-
readonly ascending?: boolean;
|
|
1679
|
-
readonly filters?: WhereInput<Row>;
|
|
1680
|
-
}
|
|
1681
|
-
/** A page of results plus metadata (mirrors `BasePaginationSchema`). */
|
|
1682
|
-
interface PaginationResult<Row> {
|
|
1683
|
-
readonly items: Row[];
|
|
1684
|
-
readonly total: number;
|
|
1685
|
-
readonly page: number;
|
|
1686
|
-
readonly pageSize: number;
|
|
1687
|
-
readonly pages: number;
|
|
1688
|
-
}
|
|
1689
|
-
/** Raised by single-record lookups (`getById`) when nothing matches (404). */
|
|
1690
|
-
declare class RecordNotFound extends Error {
|
|
1691
|
-
constructor(table: string, id: unknown);
|
|
1692
|
-
}
|
|
1693
4026
|
/**
|
|
1694
|
-
*
|
|
4027
|
+
* The same model, reading from an alias.
|
|
1695
4028
|
*
|
|
1696
|
-
*
|
|
4029
|
+
* The result is a real model class — same columns, same naming strategy, same
|
|
4030
|
+
* codecs — whose `tablename` is the alias. Everything that already takes a model
|
|
4031
|
+
* takes this: `select`, `join`, `col("alias.column")`, row coercion.
|
|
4032
|
+
*
|
|
4033
|
+
* Table constraints are **not** carried over: an alias is a way to read a table,
|
|
4034
|
+
* not a second declaration of it, and reflecting it into a migration would try to
|
|
4035
|
+
* create a table named after the alias.
|
|
4036
|
+
*
|
|
4037
|
+
* @param model The model to alias.
|
|
4038
|
+
* @param alias The name to read it under.
|
|
4039
|
+
* @returns A model class bound to the alias.
|
|
4040
|
+
*
|
|
4041
|
+
* @example
|
|
4042
|
+
* ```ts
|
|
4043
|
+
* const sub = aliased(User, "sub");
|
|
4044
|
+
* select(User).where(
|
|
4045
|
+
* exists(select(sub).where({ managerId: col("users.id") })),
|
|
4046
|
+
* );
|
|
4047
|
+
* // WHERE EXISTS (SELECT * FROM "users" AS "sub" WHERE "managerId" = "users"."id")
|
|
4048
|
+
* ```
|
|
1697
4049
|
*/
|
|
1698
|
-
declare
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
first(filters?: WhereInput<InferModel<C>>): Promise<InferModel<C> | null>;
|
|
1707
|
-
/** A single row by primary key, or `null`. */
|
|
1708
|
-
getByIdOrNull(id: unknown): Promise<InferModel<C> | null>;
|
|
1709
|
-
/** A single row by primary key; throws `RecordNotFound` when absent. */
|
|
1710
|
-
getById(id: unknown): Promise<InferModel<C>>;
|
|
1711
|
-
/** Whether any row matches `filters`. */
|
|
1712
|
-
exists(filters: WhereInput<InferModel<C>>): Promise<boolean>;
|
|
1713
|
-
/** How many rows match `filters` (or the whole table). */
|
|
1714
|
-
count(filters?: WhereInput<InferModel<C>>): Promise<number>;
|
|
1715
|
-
/** Insert one row, returning the created row. */
|
|
1716
|
-
create(data: InferInsert<C>): Promise<InferModel<C>>;
|
|
1717
|
-
/** Insert many rows, returning the created rows. */
|
|
1718
|
-
createMany(data: readonly InferInsert<C>[]): Promise<InferModel<C>[]>;
|
|
1719
|
-
/** Update rows matching `filters`; returns the number of rows affected. */
|
|
1720
|
-
update(filters: WhereInput<InferModel<C>>, set: Partial<InferModel<C>>): Promise<number>;
|
|
1721
|
-
/** Delete rows matching `filters`; returns the number of rows affected. */
|
|
1722
|
-
delete(filters: WhereInput<InferModel<C>>): Promise<number>;
|
|
1723
|
-
/**
|
|
1724
|
-
* A page of rows plus metadata. `total` counts all matching rows.
|
|
1725
|
-
*
|
|
1726
|
-
* @param filter Page, size, ordering and filters.
|
|
1727
|
-
* @returns The page and pagination metadata.
|
|
1728
|
-
*/
|
|
1729
|
-
paginate(filter?: PaginationFilter<InferModel<C>>): Promise<PaginationResult<InferModel<C>>>;
|
|
1730
|
-
}
|
|
4050
|
+
declare function aliased<C extends ModelClass>(model: C, alias: string): C;
|
|
4051
|
+
/**
|
|
4052
|
+
* The model an alias was made from, or `null` when it is not an alias.
|
|
4053
|
+
*
|
|
4054
|
+
* @param model Any model class.
|
|
4055
|
+
* @returns The underlying model, or `null`.
|
|
4056
|
+
*/
|
|
4057
|
+
declare function aliasOf(model: ModelClass): ModelClass | null;
|
|
1731
4058
|
|
|
1732
4059
|
/**
|
|
1733
4060
|
* tempest-db-js — opt-in active-record layer.
|
|
@@ -1752,11 +4079,16 @@ declare class ActiveRecord<C extends ModelClass> {
|
|
|
1752
4079
|
private readonly session;
|
|
1753
4080
|
/** The current field values (a plain, typed row). */
|
|
1754
4081
|
data: InferModel<C>;
|
|
1755
|
-
private readonly
|
|
4082
|
+
private readonly pks;
|
|
1756
4083
|
constructor(model: C, session: AsyncSession,
|
|
1757
4084
|
/** The current field values (a plain, typed row). */
|
|
1758
4085
|
data: InferModel<C>);
|
|
1759
|
-
/**
|
|
4086
|
+
/**
|
|
4087
|
+
* The primary-key values of the wrapped row, as an object.
|
|
4088
|
+
*
|
|
4089
|
+
* Composite keys are the reason this is an object and not a value: identifying
|
|
4090
|
+
* the row by one column of a two-column key hits the wrong row.
|
|
4091
|
+
*/
|
|
1760
4092
|
private pkValue;
|
|
1761
4093
|
private pkFilter;
|
|
1762
4094
|
/**
|
|
@@ -1878,6 +4210,7 @@ declare function loadRelations<Row extends Record<string, unknown>, Spec extends
|
|
|
1878
4210
|
*
|
|
1879
4211
|
* This is a SPIKE, not the final API. It validates the type machinery only.
|
|
1880
4212
|
*/
|
|
4213
|
+
|
|
1881
4214
|
/** Phantom marker carrying the static TS type a column maps to. */
|
|
1882
4215
|
declare const TYPE: unique symbol;
|
|
1883
4216
|
/** Column flags that influence the inferred row/insert shape. */
|
|
@@ -1931,6 +4264,10 @@ type PortableExpression = "now" | "current_date" | "current_time" | "uuidv4" | {
|
|
|
1931
4264
|
readonly raw: string;
|
|
1932
4265
|
} | {
|
|
1933
4266
|
readonly parts: readonly string[];
|
|
4267
|
+
}
|
|
4268
|
+
/** The incoming value of a column, inside an upsert's `DO UPDATE` clause. */
|
|
4269
|
+
| {
|
|
4270
|
+
readonly excluded: string;
|
|
1934
4271
|
};
|
|
1935
4272
|
/**
|
|
1936
4273
|
* A column default. Either a constant literal value or a server-side expression
|
|
@@ -1966,6 +4303,18 @@ interface SqlExpression {
|
|
|
1966
4303
|
/** Parameters bound into the fragment's gaps, in order (empty for a token). */
|
|
1967
4304
|
readonly params: readonly unknown[];
|
|
1968
4305
|
}
|
|
4306
|
+
/**
|
|
4307
|
+
* Turn a stored column default (or `onUpdate` value) into something the write
|
|
4308
|
+
* path can render.
|
|
4309
|
+
*
|
|
4310
|
+
* A default is kept as a {@link DefaultValue} — the shape the migration IR wants
|
|
4311
|
+
* — while `set()` and `values()` want either a bindable value or a branded
|
|
4312
|
+
* {@link SqlExpression}. This is the one conversion between the two.
|
|
4313
|
+
*
|
|
4314
|
+
* @param value The stored default.
|
|
4315
|
+
* @returns A literal to bind, or a branded expression to render inline.
|
|
4316
|
+
*/
|
|
4317
|
+
declare function defaultAsWriteValue(value: DefaultValue): unknown;
|
|
1969
4318
|
/**
|
|
1970
4319
|
* Runtime guard: is this value a branded {@link SqlExpression}?
|
|
1971
4320
|
*
|
|
@@ -1988,6 +4337,18 @@ declare const sql: {
|
|
|
1988
4337
|
readonly currentTime: () => SqlExpression;
|
|
1989
4338
|
/** A freshly generated UUID v4 (`gen_random_uuid()` / portable fallback). */
|
|
1990
4339
|
readonly uuidv4: () => SqlExpression;
|
|
4340
|
+
/**
|
|
4341
|
+
* The **incoming** value of a column, valid only inside an upsert's
|
|
4342
|
+
* `onConflictDoUpdate` patch.
|
|
4343
|
+
*
|
|
4344
|
+
* A multi-row upsert cannot spell the new value as a literal — each row has its
|
|
4345
|
+
* own — so the assignment has to name the row being inserted:
|
|
4346
|
+
* `EXCLUDED."total"` on PostgreSQL and SQLite, `VALUES(total)` on MySQL.
|
|
4347
|
+
*
|
|
4348
|
+
* @param column The column's property name.
|
|
4349
|
+
* @returns The expression, for use as a write value.
|
|
4350
|
+
*/
|
|
4351
|
+
readonly excluded: (column: string) => SqlExpression;
|
|
1991
4352
|
/**
|
|
1992
4353
|
* Escape hatch: a verbatim SQL expression rendered as-is, with no parameters.
|
|
1993
4354
|
*
|
|
@@ -2055,6 +4416,8 @@ declare class Column<T, F extends ColumnFlags = ColumnFlags> {
|
|
|
2055
4416
|
readonly reference: ForeignKeyRef | null;
|
|
2056
4417
|
/** An explicit database column name overriding the property name, or `null`. */
|
|
2057
4418
|
readonly dbName: string | null;
|
|
4419
|
+
/** Conversion to and from the stored representation, or `null` for none. */
|
|
4420
|
+
readonly codec: ColumnCodec | null;
|
|
2058
4421
|
/** Phantom: never read at runtime, only inspected by the type system. */
|
|
2059
4422
|
readonly [TYPE]: T;
|
|
2060
4423
|
constructor(type: ColumnType, flags: F,
|
|
@@ -2065,7 +4428,9 @@ declare class Column<T, F extends ColumnFlags = ColumnFlags> {
|
|
|
2065
4428
|
/** The foreign-key reference this column points to, or `null` for none. */
|
|
2066
4429
|
reference?: ForeignKeyRef | null,
|
|
2067
4430
|
/** An explicit database column name overriding the property name, or `null`. */
|
|
2068
|
-
dbName?: string | null
|
|
4431
|
+
dbName?: string | null,
|
|
4432
|
+
/** Conversion to and from the stored representation, or `null` for none. */
|
|
4433
|
+
codec?: ColumnCodec | null);
|
|
2069
4434
|
/** Clone this column with one facet replaced, carrying every other over. */
|
|
2070
4435
|
private derive;
|
|
2071
4436
|
primaryKey(): Column<T, F & {
|
|
@@ -2138,6 +4503,60 @@ declare class Column<T, F extends ColumnFlags = ColumnFlags> {
|
|
|
2138
4503
|
*/
|
|
2139
4504
|
onUpdate(value: T | DefaultValue): Column<T, F>;
|
|
2140
4505
|
}
|
|
4506
|
+
/**
|
|
4507
|
+
* Conversion between a domain value and what the database stores.
|
|
4508
|
+
*
|
|
4509
|
+
* The pair is applied on **both** paths — writes and reads — plus on a `where`
|
|
4510
|
+
* operand, so a custom-typed column cannot be compared against the wrong shape by
|
|
4511
|
+
* accident.
|
|
4512
|
+
*/
|
|
4513
|
+
interface ColumnCodec {
|
|
4514
|
+
/** Domain value → stored value. */
|
|
4515
|
+
readonly toDb: (value: unknown) => unknown;
|
|
4516
|
+
/** Stored value → domain value. */
|
|
4517
|
+
readonly fromDb: (value: unknown) => unknown;
|
|
4518
|
+
}
|
|
4519
|
+
/**
|
|
4520
|
+
* Declare a column type of your own, over one this package already has.
|
|
4521
|
+
*
|
|
4522
|
+
* Money as integer cents, a `Temporal.Instant`, a branded id, a value object —
|
|
4523
|
+
* the conversion belongs to the column, not to every call site that forgets it.
|
|
4524
|
+
* The DDL and the migration IR keep using the **base** type, so a custom type is
|
|
4525
|
+
* invisible to the schema and cannot cause drift.
|
|
4526
|
+
*
|
|
4527
|
+
* @param spec `base` is a factory for the underlying column; `toDb`/`fromDb`
|
|
4528
|
+
* convert. `base` is a factory, not a column, because each declaration needs
|
|
4529
|
+
* its own instance.
|
|
4530
|
+
* @returns A factory producing columns of the custom type.
|
|
4531
|
+
*
|
|
4532
|
+
* @example
|
|
4533
|
+
* ```ts
|
|
4534
|
+
* const money = customType<Money, bigint>({
|
|
4535
|
+
* base: () => column.bigInteger(),
|
|
4536
|
+
* toDb: (m) => m.cents,
|
|
4537
|
+
* fromDb: (cents) => Money.fromCents(cents),
|
|
4538
|
+
* });
|
|
4539
|
+
*
|
|
4540
|
+
* class Order extends Model {
|
|
4541
|
+
* total = money().notNull();
|
|
4542
|
+
* }
|
|
4543
|
+
* ```
|
|
4544
|
+
*/
|
|
4545
|
+
declare function customType<Domain, Stored>(spec: {
|
|
4546
|
+
base: () => Column<Stored, ColumnFlags>;
|
|
4547
|
+
toDb: (value: Domain) => Stored;
|
|
4548
|
+
fromDb: (value: Stored) => Domain;
|
|
4549
|
+
}): () => Column<Domain, ColumnFlags>;
|
|
4550
|
+
/**
|
|
4551
|
+
* The codecs a model declares, by property name.
|
|
4552
|
+
*
|
|
4553
|
+
* `null` when the model has no custom type, which is the common case — the write
|
|
4554
|
+
* and read paths skip the whole conversion step on that answer.
|
|
4555
|
+
*
|
|
4556
|
+
* @param model The model class.
|
|
4557
|
+
* @returns The codec map, or `null`.
|
|
4558
|
+
*/
|
|
4559
|
+
declare function codecsOf(model: ModelClass): Record<string, ColumnCodec> | null;
|
|
2141
4560
|
/**
|
|
2142
4561
|
* Column factory mirroring SQLAlchemy's typed column constructors. Each entry
|
|
2143
4562
|
* pairs a distinct SQL type with the TypeScript type it maps to.
|
|
@@ -2233,6 +4652,21 @@ type TableConstraint = {
|
|
|
2233
4652
|
readonly kind: "unique";
|
|
2234
4653
|
readonly name?: string | undefined;
|
|
2235
4654
|
readonly columns: readonly string[];
|
|
4655
|
+
} | {
|
|
4656
|
+
readonly kind: "check";
|
|
4657
|
+
readonly name?: string | undefined;
|
|
4658
|
+
/** The invariant, in the same condition language `where` uses. */
|
|
4659
|
+
readonly expression: CondNode;
|
|
4660
|
+
/** Columns the expression mentions, for the generated name. */
|
|
4661
|
+
readonly columns: readonly string[];
|
|
4662
|
+
} | {
|
|
4663
|
+
readonly kind: "index";
|
|
4664
|
+
readonly name?: string | undefined;
|
|
4665
|
+
readonly columns: readonly string[];
|
|
4666
|
+
/** A unique index rather than a plain one. */
|
|
4667
|
+
readonly unique?: boolean | undefined;
|
|
4668
|
+
/** The predicate of a **partial** index, or `undefined` for a full one. */
|
|
4669
|
+
readonly where?: CondNode | undefined;
|
|
2236
4670
|
} | {
|
|
2237
4671
|
readonly kind: "foreignKey";
|
|
2238
4672
|
readonly name?: string | undefined;
|
|
@@ -2262,6 +4696,55 @@ declare function unique(...columns: string[]): TableConstraint;
|
|
|
2262
4696
|
* @returns A foreign-key {@link TableConstraint}.
|
|
2263
4697
|
* @throws Error When the column arrays are empty or mismatched in length.
|
|
2264
4698
|
*/
|
|
4699
|
+
/**
|
|
4700
|
+
* Declare a `CHECK` constraint — an invariant the **database** enforces.
|
|
4701
|
+
*
|
|
4702
|
+
* The expression uses the same condition language as `where`, not a raw string:
|
|
4703
|
+
* a string would have to be compared textually to decide whether the schema
|
|
4704
|
+
* drifted, and two spellings of the same rule would read as a change.
|
|
4705
|
+
*
|
|
4706
|
+
* @param expression The invariant, e.g. `col("total").gte(0)`.
|
|
4707
|
+
* @param options `name` to pin the constraint's name; `columns` to name the
|
|
4708
|
+
* generated one after something other than the expression's own columns.
|
|
4709
|
+
* @returns A check {@link TableConstraint}.
|
|
4710
|
+
*
|
|
4711
|
+
* @example
|
|
4712
|
+
* ```ts
|
|
4713
|
+
* class Order extends Model {
|
|
4714
|
+
* static override tableArgs = () => [check(col("total").gte(0))];
|
|
4715
|
+
* }
|
|
4716
|
+
* ```
|
|
4717
|
+
*/
|
|
4718
|
+
declare function check(expression: Condition | Record<string, unknown>, options?: {
|
|
4719
|
+
name?: string;
|
|
4720
|
+
columns?: readonly string[];
|
|
4721
|
+
}): TableConstraint;
|
|
4722
|
+
/**
|
|
4723
|
+
* Declare an index.
|
|
4724
|
+
*
|
|
4725
|
+
* An index the model does not declare is invisible to migrations: it never gets
|
|
4726
|
+
* created, and a table rebuild on SQLite drops it. Declaring it puts it in the
|
|
4727
|
+
* same place as the columns it covers.
|
|
4728
|
+
*
|
|
4729
|
+
* @param columns The indexed columns, in order.
|
|
4730
|
+
* @param options `unique` for a unique index, `where` for a **partial** one, and
|
|
4731
|
+
* `name` to pin the name.
|
|
4732
|
+
* @returns An index {@link TableConstraint}.
|
|
4733
|
+
* @throws Error When no column is given.
|
|
4734
|
+
*
|
|
4735
|
+
* @example
|
|
4736
|
+
* ```ts
|
|
4737
|
+
* static override tableArgs = () => [
|
|
4738
|
+
* index(["customerId", "createdAt"]),
|
|
4739
|
+
* index(["email"], { unique: true, where: { deletedAt: { isNull: true } } }),
|
|
4740
|
+
* ];
|
|
4741
|
+
* ```
|
|
4742
|
+
*/
|
|
4743
|
+
declare function index(columns: readonly string[], options?: {
|
|
4744
|
+
name?: string;
|
|
4745
|
+
unique?: boolean;
|
|
4746
|
+
where?: Condition | Record<string, unknown>;
|
|
4747
|
+
}): TableConstraint;
|
|
2265
4748
|
declare function foreignKey(columns: string[], refTable: string, refColumns: string[], options?: {
|
|
2266
4749
|
name?: string;
|
|
2267
4750
|
onDelete?: FkAction;
|
|
@@ -2333,6 +4816,32 @@ declare function columnNamesOf(model: ModelClass): NameMap | null;
|
|
|
2333
4816
|
declare function columnPropsOf(model: ModelClass): NameMap | null;
|
|
2334
4817
|
/** Resolve one property name to its database column name. */
|
|
2335
4818
|
declare function dbColumn(names: NameMap | null | undefined, prop: string): string;
|
|
4819
|
+
/**
|
|
4820
|
+
* Every primary-key column of a model, in declaration order.
|
|
4821
|
+
*
|
|
4822
|
+
* A composite key is a list of more than one name — which is why this returns an
|
|
4823
|
+
* array and not a single name. Reading only the first entry is how a repository
|
|
4824
|
+
* ends up filtering half a key and touching the wrong row.
|
|
4825
|
+
*
|
|
4826
|
+
* @param model The model class.
|
|
4827
|
+
* @returns The primary-key column names (property names, not database names).
|
|
4828
|
+
* @throws Error When the model declares no primary key.
|
|
4829
|
+
*/
|
|
4830
|
+
declare function primaryKeysOf(model: ModelClass): string[];
|
|
4831
|
+
/**
|
|
4832
|
+
* Turn a primary-key argument into the filter that identifies exactly one row.
|
|
4833
|
+
*
|
|
4834
|
+
* A single-column key takes the bare value (or an object carrying it); a
|
|
4835
|
+
* composite key **requires** the object, because a scalar cannot say which of the
|
|
4836
|
+
* key columns it is. Passing a scalar for a composite key throws instead of
|
|
4837
|
+
* silently matching on one column.
|
|
4838
|
+
*
|
|
4839
|
+
* @param model The model class.
|
|
4840
|
+
* @param id The key: a scalar, or an object with every key column.
|
|
4841
|
+
* @returns A filter object covering the whole key.
|
|
4842
|
+
* @throws Error When the key is incomplete, or a scalar was given for a composite key.
|
|
4843
|
+
*/
|
|
4844
|
+
declare function primaryKeyFilter(model: ModelClass, id: unknown): Record<string, unknown>;
|
|
2336
4845
|
/** Pull the static type out of a Column. */
|
|
2337
4846
|
type ColType<C> = C extends Column<infer T, infer _F> ? T : never;
|
|
2338
4847
|
/** Keys of the model instance whose values are Columns. */
|
|
@@ -2396,4 +4905,4 @@ type InferInsert<C extends ModelClass> = Simplify<{
|
|
|
2396
4905
|
[K in Exclude<ColumnKeys<InstanceType<C>>, OptionalInsertKeys<InstanceType<C>>>]: ColValue<InstanceType<C>[K]>;
|
|
2397
4906
|
}>;
|
|
2398
4907
|
|
|
2399
|
-
export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, type BelongsTo, type ColRef, type ColType, Column, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type ExprNode, Expression, type FkAction, type ForeignKeyOptions, type ForeignKeyRef, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, InvalidDatabaseUrl, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, type LockClause, type LockOptions, Model, type ModelClass, MysqlDialect, type NameMap, type NamingStrategy, NoResultError, NodeSqliteDriver, type NoticeLogger, OPERATORS, type OnConflict, type OnConflictOptions, type OnConflictUpdateOptions, type Operator, type OperatorsFor, type OrderTerm, type PaginationFilter, type PaginationResult, Params, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, QueryExecutionError, type QueryLogger, type QueryNode, RecordNotFound, type Relation, type RelationValue, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, type SortDirection, type Sources, type SqlExpression, SqliteDialect, type Subquery, type SyncDriver, SyncEngine, SyncResult, SyncSession, type TableConstraint, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WithRelations, type WritePatch, type WriteValues, 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, select, sql, stringify, sum, toAsyncDriver, toCondNode, toDict, toJSON, toSnakeCase, unique, update, val };
|
|
4908
|
+
export { ActiveRecord, type ActiveRecordManager, Agg, type AggregateTerm, type AsyncDriver, AsyncEngine, AsyncResult, AsyncSession, type AuditAction, type AuditDiff, type AuditEntry, type AuditOptions, type BackupOptions, type BackupResult, BackupToolMissing, BaseDialect, BaseRepository, type BelongsTo, BetterSqliteDriver, type BulkUpsertOptions, type CastType, type ChangesPage, type ChangesSinceFilter, type ClaimOptions, type ColRef, type ColType, Column, type ColumnCodec, type ColumnFlags, type ColumnType, type ColumnTypeKind, type ColumnTypeMeta, type CompiledQuery, type CondNode, type Condition, type ContainsOptions, Cte, type CteBody, type CteNode, type CteOptions, type CursorPage, type CursorPaginationFilter, type DefaultValue, DeleteBuilder, type DeleteNode, type Dialect, type DriverResult, type EngineOptions, type Executable, type ExplainOptions, type ExplainReport, type ExprNode, Expression, type FailOptions, type FkAction, type FlushResult, type ForeignKeyOptions, type ForeignKeyRef, type HasMany, type InferInsert, type InferModel, InsertBuilder, type InsertNode, type IntegrityFailure, type IntegrityViolation, InvalidCursor, InvalidDatabaseUrl, type IsolationLevel, JoinBuilder, type JoinClause, type JoinNode, type JoinOn, type JoinSelection, type JoinWhereInput, type LockClause, type LockOptions, Model, type ModelBase, type ModelClass, MysqlDialect, type NameMap, type NamingStrategy, NoResultError, NodeSqliteDriver, type NoticeLogger, OPERATORS, type OnConflict, type OnConflictOptions, type OnConflictUpdateOptions, type Operator, type OperatorsFor, type OrderTerm, type OutboxEventInput, OutboxRepository, type OutboxStatus, type PaginationFilter, type PaginationResult, Params, type ParsedDatabaseUrl, type PoolOptions, type PortableExpression, PostgresDialect, type QueryEndEvent, type QueryEndLogger, QueryExecutionError, type QueryHooks, type QueryLogger, type QueryNode, type QueryPlan, RecordNotFound, type Relation, type RelationValue, type RepositorySignal, type ReservedAsyncDriver, type Returning, type RowOf, SelectBuilder, type SelectNode, SetBuilder, type SetNode, type SetOperator, type SignalHandler, type SignalPayload, type SortDirection, type Sources, type SqlExpression, SqliteDialect, type SqliteJournalMode, type SqliteOptions, type SqliteSynchronous, type Subquery, type SubqueryLike, type SyncDriver, SyncEngine, SyncResult, SyncSession, type TableConstraint, type TenantScope, TenantScopedRepository, type TextSearchLanguage, type TextSearchOptions, type Tracked, type TransactionOptions, UnitOfWork, UnsupportedBackupBackend, UpdateBuilder, type UpdateNode, ValidationError, type WhereArg, type WhereInput, type WindowFn, type WindowSpec, type WithRelations, type WritePatch, type WriteValues, activeRecord, aliasOf, aliased, and, attach as attachCte, auditLogModel, avg, backupDatabase, backupFormat, belongsTo, caseWhen, cast, check, clearSignals, codecsOf, col, column, columnNamesOf, columnPropsOf, columnsOf, contains, count, createEngine, createSyncEngine, cte, cteRecursive, customType, dbColumn, decodeValue as decodeColumnValue, defaultAsWriteValue, del, denseRank, detectDialect, diffSnapshots, emitSignal, enableAudit, encodeValue as encodeColumnValue, escapeLike, except, exists, firstValue, fn, foreignKey, fromDict, fullText, fullTextRank, getDialect, hasHandlers, hasMany, index, insert, intersect, isCondition, isExpression, isReadOnlyStatement, isSqlExpression, isSubquery, join, lag, lastValue, lead, loadRelations, max, min, not, notDeleted, notExists, onSignal, onlyDeleted, or, outboxModel, over, parse, parseDatabaseUrl, parseIntegrityError, percentRank, primaryKeyFilter, primaryKeysOf, rank, restoreDatabase, rowNumber, scalar, select, snapshot, sql, stringify, sum, summarizePlan, toAsyncDriver, toCondNode, toDict, toJSON, toSnakeCase, tokenize, toolUrl, union, unionAll, unique, update, val, withAudit, withSoftDelete, withTimestamps };
|