turbine-orm 0.18.0 → 0.19.1
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 +8 -8
- package/dist/adapters/index.d.ts +3 -2
- package/dist/cjs/cli/index.js +26 -4
- package/dist/cjs/cli/loader.js +62 -7
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.js +54 -76
- package/dist/cjs/client.js +8 -0
- package/dist/cjs/query/builder.js +261 -51
- package/dist/cli/index.js +28 -6
- package/dist/cli/loader.d.ts +22 -5
- package/dist/cli/loader.js +61 -7
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +9 -4
- package/dist/cli/studio.js +54 -76
- package/dist/client.js +8 -0
- package/dist/index.d.ts +1 -1
- package/dist/query/builder.d.ts +35 -0
- package/dist/query/builder.js +261 -51
- package/dist/query/index.d.ts +1 -1
- package/package.json +3 -3
- package/dist/cjs/query.js +0 -2711
- package/dist/query.d.ts +0 -878
- package/dist/query.js +0 -2705
package/dist/query.d.ts
DELETED
|
@@ -1,878 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* turbine-orm — Query builder
|
|
3
|
-
*
|
|
4
|
-
* Each table accessor (db.users, db.posts, etc.) returns a QueryInterface<T>
|
|
5
|
-
* that builds parameterized SQL and executes it through the connection pool.
|
|
6
|
-
*
|
|
7
|
-
* Nested relations use json_build_object + json_agg subqueries for single-query
|
|
8
|
-
* resolution — a PostgreSQL-native approach that eliminates N+1 query patterns.
|
|
9
|
-
*
|
|
10
|
-
* Schema-driven: all column names, types, and relations come from introspected
|
|
11
|
-
* metadata — nothing is hardcoded.
|
|
12
|
-
*/
|
|
13
|
-
import type pg from 'pg';
|
|
14
|
-
import type { SchemaMetadata } from './schema.js';
|
|
15
|
-
/**
|
|
16
|
-
* Quote a SQL identifier (table name, column name) using Postgres double-quote
|
|
17
|
-
* rules: wrap in double quotes, escape internal double quotes by doubling them.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* quoteIdent('users') → '"users"'
|
|
21
|
-
* quoteIdent('my"table') → '"my""table"'
|
|
22
|
-
* quoteIdent('user name') → '"user name"'
|
|
23
|
-
*/
|
|
24
|
-
export declare function quoteIdent(name: string): string;
|
|
25
|
-
export type OrderDirection = 'asc' | 'desc';
|
|
26
|
-
/** Operator object for advanced where filtering */
|
|
27
|
-
export interface WhereOperator<V = unknown> {
|
|
28
|
-
gt?: V;
|
|
29
|
-
gte?: V;
|
|
30
|
-
lt?: V;
|
|
31
|
-
lte?: V;
|
|
32
|
-
not?: V | null;
|
|
33
|
-
in?: V[];
|
|
34
|
-
notIn?: V[];
|
|
35
|
-
contains?: string;
|
|
36
|
-
startsWith?: string;
|
|
37
|
-
endsWith?: string;
|
|
38
|
-
/** Set to 'insensitive' to use ILIKE instead of LIKE for string comparisons */
|
|
39
|
-
mode?: 'default' | 'insensitive';
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* A where value can be:
|
|
43
|
-
* - A plain value (equality: column = $N)
|
|
44
|
-
* - null (column IS NULL)
|
|
45
|
-
* - An operator object ({ gt: 5, lte: 10 })
|
|
46
|
-
* - A JSONB filter object ({ contains, equals, path, hasKey })
|
|
47
|
-
* - An array filter object ({ has, hasEvery, hasSome, isEmpty })
|
|
48
|
-
*/
|
|
49
|
-
export type WhereValue<V = unknown> = V | WhereOperator<V> | JsonFilter | ArrayFilter | null;
|
|
50
|
-
/**
|
|
51
|
-
* Where clause type: each field can be a plain value, null, or operator object.
|
|
52
|
-
* Special keys: OR for disjunctive conditions.
|
|
53
|
-
* Relation names can be used with some/every/none sub-filters.
|
|
54
|
-
*/
|
|
55
|
-
export type WhereClause<T> = {
|
|
56
|
-
[K in keyof T]?: WhereValue<T[K]>;
|
|
57
|
-
} & {
|
|
58
|
-
OR?: WhereClause<T>[];
|
|
59
|
-
AND?: WhereClause<T>[];
|
|
60
|
-
NOT?: WhereClause<T>;
|
|
61
|
-
/** Relation filters — keyed by relation name, value is { some, every, none } */
|
|
62
|
-
[relationName: string]: unknown;
|
|
63
|
-
};
|
|
64
|
-
/**
|
|
65
|
-
* Unparameterized with clause — accepts any relation name.
|
|
66
|
-
* Used internally by the query builder at runtime.
|
|
67
|
-
*/
|
|
68
|
-
export interface WithClause {
|
|
69
|
-
[relation: string]: true | WithOptions;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Relation-aware with clause. When R (the relations map) is provided,
|
|
73
|
-
* only keys from R are autocompleted. Used in public method signatures
|
|
74
|
-
* so the compiler can narrow the return type.
|
|
75
|
-
*
|
|
76
|
-
* For typed maps, each relation accepts either `true` (default include) or a
|
|
77
|
-
* {@link WithOptions} object whose nested `with` is keyed against the relation
|
|
78
|
-
* target's own relations interface — this is what enables deep
|
|
79
|
-
* `WithResult` inference.
|
|
80
|
-
*/
|
|
81
|
-
export type TypedWithClause<R extends object = {}> = [keyof R] extends [never] ? WithClause : {
|
|
82
|
-
[K in keyof R]?: true | WithOptions<RelationRelations<R[K]> & object>;
|
|
83
|
-
};
|
|
84
|
-
/**
|
|
85
|
-
* Options for an included relation.
|
|
86
|
-
*
|
|
87
|
-
* Generic over `NestedR` — the relations interface of the *target* entity —
|
|
88
|
-
* so the nested `with` clause is autocompleted with the correct relation keys
|
|
89
|
-
* and so {@link WithResult} can recursively infer the return type. Defaults to
|
|
90
|
-
* `{}` (no relation suggestions) for callers that use the unparameterized
|
|
91
|
-
* {@link WithClause}.
|
|
92
|
-
*/
|
|
93
|
-
export interface WithOptions<NestedR extends object = {}> {
|
|
94
|
-
with?: TypedWithClause<NestedR>;
|
|
95
|
-
where?: Record<string, unknown>;
|
|
96
|
-
orderBy?: Record<string, OrderDirection>;
|
|
97
|
-
limit?: number;
|
|
98
|
-
/** Only include these fields from the relation */
|
|
99
|
-
select?: Record<string, boolean>;
|
|
100
|
-
/** Exclude these fields from the relation */
|
|
101
|
-
omit?: Record<string, boolean>;
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* A relation descriptor used by generated `*Relations` interfaces to make deep
|
|
105
|
-
* `with` clause inference work. It bundles three pieces of information that
|
|
106
|
-
* `WithResult` needs to recurse through nested relations:
|
|
107
|
-
*
|
|
108
|
-
* - `__target` — the target entity type (e.g. `Post`)
|
|
109
|
-
* - `__cardinality`— `'many'` for hasMany, `'one'` for belongsTo / hasOne
|
|
110
|
-
* - `__relations` — the target entity's relations interface (for further recursion)
|
|
111
|
-
*
|
|
112
|
-
* **Generator contract (Track 3):** the code generator emits `*Relations`
|
|
113
|
-
* interfaces in the following shape so that `WithResult` can walk arbitrary
|
|
114
|
-
* nesting depth:
|
|
115
|
-
*
|
|
116
|
-
* ```ts
|
|
117
|
-
* export interface UserRelations {
|
|
118
|
-
* posts: RelationDescriptor<Post, 'many', PostRelations>;
|
|
119
|
-
* profile: RelationDescriptor<Profile, 'one', ProfileRelations>;
|
|
120
|
-
* }
|
|
121
|
-
* ```
|
|
122
|
-
*
|
|
123
|
-
* The brand fields are phantom — they exist only for type inference and have
|
|
124
|
-
* no runtime representation. The runtime always sees the parsed entity values
|
|
125
|
-
* (arrays for hasMany, single object or null for belongsTo / hasOne) — see the
|
|
126
|
-
* cardinality projection inside {@link WithResult}.
|
|
127
|
-
*
|
|
128
|
-
* **Backward compatibility:** legacy generated code emitted bare types
|
|
129
|
-
* (`posts: Post[]`, `profile: Profile | null`). `WithResult` still accepts that
|
|
130
|
-
* shape via a fallback branch — it just cannot recurse into nested `with` for
|
|
131
|
-
* those relations until the generator is updated.
|
|
132
|
-
*
|
|
133
|
-
* @typeParam Target - The entity type the relation points at.
|
|
134
|
-
* @typeParam Cardinality - `'many'` (array) or `'one'` (single object | null).
|
|
135
|
-
* @typeParam Relations - The target entity's own `*Relations` interface, or
|
|
136
|
-
* `{}` if the target has no relations of its own.
|
|
137
|
-
*/
|
|
138
|
-
export interface RelationDescriptor<Target, Cardinality extends 'one' | 'many', Relations extends object = {}> {
|
|
139
|
-
readonly __target?: Target;
|
|
140
|
-
readonly __cardinality?: Cardinality;
|
|
141
|
-
readonly __relations?: Relations;
|
|
142
|
-
}
|
|
143
|
-
/** Extract the target entity from a relation descriptor or bare relation type. */
|
|
144
|
-
type RelationTarget<Rel> = Rel extends RelationDescriptor<infer Target, infer _C, infer _R> ? Target : Rel extends Array<infer Item> ? Item : Rel extends infer One | null ? One : Rel;
|
|
145
|
-
/** Extract the target's relations map from a relation descriptor (or `{}` for bare types). */
|
|
146
|
-
type RelationRelations<Rel> = Rel extends RelationDescriptor<infer _T, infer _C, infer R> ? R : {};
|
|
147
|
-
/** Project the target type into its runtime shape (array for many, single for one). */
|
|
148
|
-
type ApplyCardinality<Rel, Resolved> = Rel extends RelationDescriptor<infer _T, infer Cardinality, infer _R> ? Cardinality extends 'many' ? Resolved[] : Resolved | null : Rel extends Array<infer _Item> ? Resolved[] : Resolved | null;
|
|
149
|
-
/**
|
|
150
|
-
* Compute the result type when relations are included via `with`.
|
|
151
|
-
*
|
|
152
|
-
* Recursively walks the `with` clause, looking up each relation in `R` and:
|
|
153
|
-
*
|
|
154
|
-
* 1. If the relation is included with `true` (or no nested `with`), the
|
|
155
|
-
* relation's bare resolved type is grafted onto `T` (e.g. `posts: Post[]`).
|
|
156
|
-
* 2. If the relation is included with a nested `with: {...}`, the recursion
|
|
157
|
-
* looks up the target entity's relations interface (via the
|
|
158
|
-
* {@link RelationDescriptor} brand fields the generator emits) and
|
|
159
|
-
* recursively applies `WithResult` to the nested target. Cardinality is
|
|
160
|
-
* re-applied at each level so a hasMany relation stays an array even after
|
|
161
|
-
* deep nesting.
|
|
162
|
-
*
|
|
163
|
-
* **When `R` is `{}` (the default):** the recursion short-circuits and the
|
|
164
|
-
* function returns plain `T` — preserving the existing untyped escape hatch
|
|
165
|
-
* for callers that have not generated typed clients.
|
|
166
|
-
*
|
|
167
|
-
* **When `R` does not contain the requested relations:** the unknown keys are
|
|
168
|
-
* ignored (the runtime will throw a `RelationError`, but the type system stays
|
|
169
|
-
* permissive so the legacy `WithClause` index signature still typechecks).
|
|
170
|
-
*
|
|
171
|
-
* @typeParam T - Base entity type (e.g. `User`).
|
|
172
|
-
* @typeParam R - Relations map for `T` (e.g.
|
|
173
|
-
* `{ posts: RelationDescriptor<Post, 'many', PostRelations>; ... }`).
|
|
174
|
-
* Legacy bare shapes (`{ posts: Post[]; profile: Profile | null }`)
|
|
175
|
-
* are also accepted, but cannot recurse beyond one level.
|
|
176
|
-
* @typeParam W - The `with` clause the user passed (e.g. `{ posts: true }` or
|
|
177
|
-
* `{ posts: { with: { comments: true } } }`).
|
|
178
|
-
*/
|
|
179
|
-
export type WithResult<T, R extends object, W> = [keyof R] extends [never] ? T : W extends object ? [keyof W & keyof R] extends [never] ? T : T & {
|
|
180
|
-
[K in keyof W & keyof R]: W[K] extends true ? ApplyCardinality<R[K], RelationTarget<R[K]>> : W[K] extends {
|
|
181
|
-
with?: infer NestedW;
|
|
182
|
-
} ? NestedW extends object ? ApplyCardinality<R[K], WithResult<RelationTarget<R[K]>, RelationRelations<R[K]> & object, NestedW>> : ApplyCardinality<R[K], RelationTarget<R[K]>> : ApplyCardinality<R[K], RelationTarget<R[K]>>;
|
|
183
|
-
} : T;
|
|
184
|
-
export interface FindUniqueArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>> {
|
|
185
|
-
where: WhereClause<T>;
|
|
186
|
-
select?: Record<string, boolean>;
|
|
187
|
-
omit?: Record<string, boolean>;
|
|
188
|
-
with?: W;
|
|
189
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
190
|
-
timeout?: number;
|
|
191
|
-
}
|
|
192
|
-
export interface FindManyArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>> {
|
|
193
|
-
where?: WhereClause<T>;
|
|
194
|
-
select?: Record<string, boolean>;
|
|
195
|
-
omit?: Record<string, boolean>;
|
|
196
|
-
orderBy?: Record<string, OrderDirection>;
|
|
197
|
-
limit?: number;
|
|
198
|
-
offset?: number;
|
|
199
|
-
with?: W;
|
|
200
|
-
/** Cursor-based pagination: start after this row */
|
|
201
|
-
cursor?: Partial<T>;
|
|
202
|
-
/** Number of records to take (used with cursor) */
|
|
203
|
-
take?: number;
|
|
204
|
-
/** De-duplicate results by specified fields */
|
|
205
|
-
distinct?: (keyof T & string)[];
|
|
206
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
207
|
-
timeout?: number;
|
|
208
|
-
}
|
|
209
|
-
export interface FindManyStreamArgs<T, R extends object = {}, W extends TypedWithClause<R> = TypedWithClause<R>> extends FindManyArgs<T, R, W> {
|
|
210
|
-
/**
|
|
211
|
-
* Number of rows to fetch per internal FETCH batch (default: 1000).
|
|
212
|
-
*
|
|
213
|
-
* Trade-off: larger batches reduce network round-trips (important for
|
|
214
|
-
* high-latency connections like Neon) but increase per-batch memory.
|
|
215
|
-
* At 1000 rows x ~500 bytes/row the default is ~500 KB per batch.
|
|
216
|
-
*
|
|
217
|
-
* When the total result set fits within one batch, the stream avoids
|
|
218
|
-
* cursor overhead entirely (no BEGIN / DECLARE / CLOSE / COMMIT) by
|
|
219
|
-
* using a speculative `SELECT ... LIMIT batchSize+1` first.
|
|
220
|
-
*/
|
|
221
|
-
batchSize?: number;
|
|
222
|
-
}
|
|
223
|
-
export interface CreateArgs<T> {
|
|
224
|
-
data: Partial<T>;
|
|
225
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
226
|
-
timeout?: number;
|
|
227
|
-
}
|
|
228
|
-
export interface CreateManyArgs<T> {
|
|
229
|
-
data: Partial<T>[];
|
|
230
|
-
/** When true, adds ON CONFLICT DO NOTHING to skip duplicate rows */
|
|
231
|
-
skipDuplicates?: boolean;
|
|
232
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
233
|
-
timeout?: number;
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
* Atomic update operators for a field.
|
|
237
|
-
*
|
|
238
|
-
* `set` works on any type; `increment`, `decrement`, `multiply`, and `divide`
|
|
239
|
-
* are only valid on numeric fields. They generate SQL like
|
|
240
|
-
* `col = col + $n` (and the corresponding `-`, `*`, `/` variants) instead of
|
|
241
|
-
* plain absolute assignments, so they are safe against concurrent writers —
|
|
242
|
-
* the database performs the math atomically.
|
|
243
|
-
*
|
|
244
|
-
* @example
|
|
245
|
-
* db.posts.update({ where: { id: 5 }, data: { viewCount: { increment: 1 } } });
|
|
246
|
-
*/
|
|
247
|
-
export type UpdateOperatorInput<V> = {
|
|
248
|
-
set: V;
|
|
249
|
-
} | (V extends number ? {
|
|
250
|
-
increment: number;
|
|
251
|
-
} : never) | (V extends number ? {
|
|
252
|
-
decrement: number;
|
|
253
|
-
} : never) | (V extends number ? {
|
|
254
|
-
multiply: number;
|
|
255
|
-
} : never) | (V extends number ? {
|
|
256
|
-
divide: number;
|
|
257
|
-
} : never);
|
|
258
|
-
/**
|
|
259
|
-
* Update data — each field can be a plain value or an atomic operator object.
|
|
260
|
-
* Back-compatible with `Partial<T>`: plain values still typecheck unchanged.
|
|
261
|
-
*/
|
|
262
|
-
export type UpdateInput<T> = {
|
|
263
|
-
[K in keyof T]?: T[K] | UpdateOperatorInput<T[K]>;
|
|
264
|
-
};
|
|
265
|
-
export interface UpdateArgs<T> {
|
|
266
|
-
where: WhereClause<T>;
|
|
267
|
-
data: UpdateInput<T>;
|
|
268
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
269
|
-
timeout?: number;
|
|
270
|
-
/**
|
|
271
|
-
* Opt in to running this mutation when `where` resolves to an empty
|
|
272
|
-
* predicate (e.g. `{}` or `{ id: undefined }`). Default `false` — an
|
|
273
|
-
* empty predicate throws `ValidationError` to catch the common case of
|
|
274
|
-
* a filter value accidentally being `undefined`. Set this to `true` only
|
|
275
|
-
* when an unconditional mutation is the intended behaviour.
|
|
276
|
-
*/
|
|
277
|
-
allowFullTableScan?: boolean;
|
|
278
|
-
}
|
|
279
|
-
export interface UpdateManyArgs<T> {
|
|
280
|
-
where: WhereClause<T>;
|
|
281
|
-
data: UpdateInput<T>;
|
|
282
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
283
|
-
timeout?: number;
|
|
284
|
-
/** See {@link UpdateArgs.allowFullTableScan}. */
|
|
285
|
-
allowFullTableScan?: boolean;
|
|
286
|
-
}
|
|
287
|
-
export interface DeleteArgs<T> {
|
|
288
|
-
where: WhereClause<T>;
|
|
289
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
290
|
-
timeout?: number;
|
|
291
|
-
/** See {@link UpdateArgs.allowFullTableScan}. */
|
|
292
|
-
allowFullTableScan?: boolean;
|
|
293
|
-
}
|
|
294
|
-
export interface DeleteManyArgs<T> {
|
|
295
|
-
where: WhereClause<T>;
|
|
296
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
297
|
-
timeout?: number;
|
|
298
|
-
/** See {@link UpdateArgs.allowFullTableScan}. */
|
|
299
|
-
allowFullTableScan?: boolean;
|
|
300
|
-
}
|
|
301
|
-
export interface UpsertArgs<T> {
|
|
302
|
-
where: WhereClause<T>;
|
|
303
|
-
create: Partial<T>;
|
|
304
|
-
update: Partial<T>;
|
|
305
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
306
|
-
timeout?: number;
|
|
307
|
-
}
|
|
308
|
-
export interface CountArgs<T> {
|
|
309
|
-
where?: WhereClause<T>;
|
|
310
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
311
|
-
timeout?: number;
|
|
312
|
-
}
|
|
313
|
-
export interface GroupByArgs<T> {
|
|
314
|
-
by: (keyof T & string)[];
|
|
315
|
-
where?: WhereClause<T>;
|
|
316
|
-
/** Include count of each group */
|
|
317
|
-
_count?: true;
|
|
318
|
-
/** Sum of numeric fields in each group */
|
|
319
|
-
_sum?: Partial<Record<keyof T & string, boolean>>;
|
|
320
|
-
/** Average of numeric fields in each group */
|
|
321
|
-
_avg?: Partial<Record<keyof T & string, boolean>>;
|
|
322
|
-
/** Minimum value of fields in each group */
|
|
323
|
-
_min?: Partial<Record<keyof T & string, boolean>>;
|
|
324
|
-
/** Maximum value of fields in each group */
|
|
325
|
-
_max?: Partial<Record<keyof T & string, boolean>>;
|
|
326
|
-
/** Order groups */
|
|
327
|
-
orderBy?: Record<string, OrderDirection>;
|
|
328
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
329
|
-
timeout?: number;
|
|
330
|
-
}
|
|
331
|
-
/** Arguments for the standalone aggregate method */
|
|
332
|
-
export interface AggregateArgs<T> {
|
|
333
|
-
where?: WhereClause<T>;
|
|
334
|
-
/** Count all rows matching the filter */
|
|
335
|
-
_count?: true | Partial<Record<keyof T & string, boolean>>;
|
|
336
|
-
/** Sum of numeric fields */
|
|
337
|
-
_sum?: Partial<Record<keyof T & string, boolean>>;
|
|
338
|
-
/** Average of numeric fields */
|
|
339
|
-
_avg?: Partial<Record<keyof T & string, boolean>>;
|
|
340
|
-
/** Minimum value of fields */
|
|
341
|
-
_min?: Partial<Record<keyof T & string, boolean>>;
|
|
342
|
-
/** Maximum value of fields */
|
|
343
|
-
_max?: Partial<Record<keyof T & string, boolean>>;
|
|
344
|
-
/** Query timeout in milliseconds. Rejects with an error if exceeded. */
|
|
345
|
-
timeout?: number;
|
|
346
|
-
}
|
|
347
|
-
/** Result type for aggregate queries */
|
|
348
|
-
export interface AggregateResult<T> {
|
|
349
|
-
_count?: number | Record<string, number>;
|
|
350
|
-
_sum?: Partial<Record<keyof T & string, number | null>>;
|
|
351
|
-
_avg?: Partial<Record<keyof T & string, number | null>>;
|
|
352
|
-
_min?: Partial<Record<keyof T & string, unknown>>;
|
|
353
|
-
_max?: Partial<Record<keyof T & string, unknown>>;
|
|
354
|
-
}
|
|
355
|
-
/** Relation filter operators for where clauses */
|
|
356
|
-
export interface RelationFilter {
|
|
357
|
-
some?: Record<string, unknown>;
|
|
358
|
-
every?: Record<string, unknown>;
|
|
359
|
-
none?: Record<string, unknown>;
|
|
360
|
-
}
|
|
361
|
-
/** JSONB query operators for where clauses */
|
|
362
|
-
export interface JsonFilter {
|
|
363
|
-
/** Access nested path via #>> operator */
|
|
364
|
-
path?: string[];
|
|
365
|
-
/** Exact match: column @> value::jsonb (containment) */
|
|
366
|
-
equals?: unknown;
|
|
367
|
-
/** Containment check: column @> value::jsonb */
|
|
368
|
-
contains?: unknown;
|
|
369
|
-
/** Key existence check: column ? key */
|
|
370
|
-
hasKey?: string;
|
|
371
|
-
}
|
|
372
|
-
/** Array query operators for where clauses */
|
|
373
|
-
export interface ArrayFilter {
|
|
374
|
-
/** Check if array contains a single value: value = ANY(column) */
|
|
375
|
-
has?: unknown;
|
|
376
|
-
/** Check if array contains ALL values: column @> ARRAY[...] */
|
|
377
|
-
hasEvery?: unknown[];
|
|
378
|
-
/** Check if array has ANY of the values: column && ARRAY[...] */
|
|
379
|
-
hasSome?: unknown[];
|
|
380
|
-
/** Check if array is empty: array_length(column, 1) IS NULL */
|
|
381
|
-
isEmpty?: boolean;
|
|
382
|
-
}
|
|
383
|
-
/** Cached SQL template paired with its prepared-statement name. */
|
|
384
|
-
export interface SqlCacheEntry {
|
|
385
|
-
sql: string;
|
|
386
|
-
name: string;
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* FNV-1a 64-bit hash returning 16 lowercase hex chars.
|
|
390
|
-
* Single-loop string iteration. Uses BigInt for 64-bit math.
|
|
391
|
-
*
|
|
392
|
-
* @internal Exported for testing only.
|
|
393
|
-
*/
|
|
394
|
-
export declare function fnv1a64Hex(s: string): string;
|
|
395
|
-
/**
|
|
396
|
-
* Derive a prepared-statement name from a SQL string.
|
|
397
|
-
* Format: `t_<16hex>` — always 18 chars, well under NAMEDATALEN (63).
|
|
398
|
-
*
|
|
399
|
-
* @internal Exported for testing only.
|
|
400
|
-
*/
|
|
401
|
-
export declare function sqlToPreparedName(sql: string): string;
|
|
402
|
-
export interface DeferredQuery<T> {
|
|
403
|
-
/** SQL text with $1, $2 placeholders */
|
|
404
|
-
sql: string;
|
|
405
|
-
/** Bound parameter values */
|
|
406
|
-
params: unknown[];
|
|
407
|
-
/** How to transform the raw pg.QueryResult into the final value */
|
|
408
|
-
transform: (result: pg.QueryResult) => T;
|
|
409
|
-
/** Tag for debugging / logging */
|
|
410
|
-
tag: string;
|
|
411
|
-
/** Prepared statement name (t_<16hex>). Set when SQL cache is enabled. */
|
|
412
|
-
preparedName?: string;
|
|
413
|
-
}
|
|
414
|
-
/** Middleware function type — imported from client to avoid circular deps */
|
|
415
|
-
type MiddlewareFn = (params: {
|
|
416
|
-
model: string;
|
|
417
|
-
action: string;
|
|
418
|
-
args: Record<string, unknown>;
|
|
419
|
-
}, next: (params: {
|
|
420
|
-
model: string;
|
|
421
|
-
action: string;
|
|
422
|
-
args: Record<string, unknown>;
|
|
423
|
-
}) => Promise<unknown>) => Promise<unknown>;
|
|
424
|
-
/** Options passed from TurbineClient to QueryInterface */
|
|
425
|
-
export interface QueryInterfaceOptions {
|
|
426
|
-
/** Default LIMIT applied to findMany() when no limit is specified */
|
|
427
|
-
defaultLimit?: number;
|
|
428
|
-
/**
|
|
429
|
-
* Log a one-time warning when {@link QueryInterface.findMany} is called
|
|
430
|
-
* without a `limit`. Defaults to `true` so that accidental unbounded
|
|
431
|
-
* queries are surfaced loudly during development. Pass `false` to silence
|
|
432
|
-
* the warning entirely (e.g. for CLI tooling that intentionally streams
|
|
433
|
-
* full tables).
|
|
434
|
-
*/
|
|
435
|
-
warnOnUnlimited?: boolean;
|
|
436
|
-
/**
|
|
437
|
-
* Enable prepared statements. When true, queries are submitted with a
|
|
438
|
-
* `{ name, text, values }` object to the pg driver, which caches the
|
|
439
|
-
* parse+plan on the server per connection.
|
|
440
|
-
*
|
|
441
|
-
* Default: `true` for Turbine-owned pools, `false` for external pools
|
|
442
|
-
* (serverless drivers may not support named statements).
|
|
443
|
-
*/
|
|
444
|
-
preparedStatements?: boolean;
|
|
445
|
-
/**
|
|
446
|
-
* Enable the SQL template cache. When true, repeated queries with the
|
|
447
|
-
* same shape (same keys, operators, relations — different values) reuse
|
|
448
|
-
* cached SQL text instead of rebuilding from scratch.
|
|
449
|
-
*
|
|
450
|
-
* Default: `true`. Set to `false` as a nuclear kill switch.
|
|
451
|
-
*/
|
|
452
|
-
sqlCache?: boolean;
|
|
453
|
-
}
|
|
454
|
-
export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
455
|
-
private readonly pool;
|
|
456
|
-
private readonly table;
|
|
457
|
-
private readonly schema;
|
|
458
|
-
private readonly tableMeta;
|
|
459
|
-
/** SQL template cache: cacheKey → SqlCacheEntry (sql + prepared statement name) */
|
|
460
|
-
private readonly sqlTemplateCache;
|
|
461
|
-
private readonly middlewares;
|
|
462
|
-
private readonly defaultLimit?;
|
|
463
|
-
private readonly warnOnUnlimited;
|
|
464
|
-
private readonly preparedStatementsEnabled;
|
|
465
|
-
private readonly sqlCacheEnabled;
|
|
466
|
-
/**
|
|
467
|
-
* Tracks tables that have already triggered an unlimited-query warning so
|
|
468
|
-
* the user is not spammed once per row. Per-instance state — each
|
|
469
|
-
* QueryInterface is bound to a single table, so this set will only ever
|
|
470
|
-
* contain at most one entry, but using a Set keeps the API consistent with
|
|
471
|
-
* the audit's "Set<string>" guidance and leaves room for future
|
|
472
|
-
* cross-table sharing.
|
|
473
|
-
*/
|
|
474
|
-
private readonly warnedTables;
|
|
475
|
-
/** Cache hit/miss counters for diagnostics */
|
|
476
|
-
private cacheHits;
|
|
477
|
-
private cacheMisses;
|
|
478
|
-
/** Pre-computed column type lookups (avoids linear scans per query) */
|
|
479
|
-
private readonly columnPgTypeMap;
|
|
480
|
-
private readonly columnArrayTypeMap;
|
|
481
|
-
constructor(pool: pg.Pool, table: string, schema: SchemaMetadata, middlewares?: MiddlewareFn[], options?: QueryInterfaceOptions);
|
|
482
|
-
/**
|
|
483
|
-
* Return cache hit/miss statistics for this QueryInterface instance.
|
|
484
|
-
* Useful for monitoring and benchmarking.
|
|
485
|
-
*/
|
|
486
|
-
cacheStats(): {
|
|
487
|
-
hits: number;
|
|
488
|
-
misses: number;
|
|
489
|
-
hitRate: number;
|
|
490
|
-
size: number;
|
|
491
|
-
};
|
|
492
|
-
/**
|
|
493
|
-
* Look up or build a SQL template in the cache.
|
|
494
|
-
* On miss, calls `build()` to generate the SQL, stores the entry, and returns it.
|
|
495
|
-
* On hit, increments counters and returns the cached entry.
|
|
496
|
-
*
|
|
497
|
-
* When `sqlCache` is disabled, always calls `build()` without caching.
|
|
498
|
-
*/
|
|
499
|
-
private acquireSql;
|
|
500
|
-
/**
|
|
501
|
-
* Reset the per-instance unlimited-query warning dedupe set.
|
|
502
|
-
* Exposed for tests so a single test process can verify the warning fires
|
|
503
|
-
* exactly once per table without bleeding state between assertions.
|
|
504
|
-
*/
|
|
505
|
-
resetUnlimitedWarnings(): void;
|
|
506
|
-
/**
|
|
507
|
-
* Execute a pool.query with an optional timeout.
|
|
508
|
-
* If timeout is set, races the query against a timer and rejects on expiry.
|
|
509
|
-
* pg driver errors are translated to typed Turbine errors via wrapPgError.
|
|
510
|
-
*/
|
|
511
|
-
private queryWithTimeout;
|
|
512
|
-
/**
|
|
513
|
-
* Execute a query through the middleware chain.
|
|
514
|
-
* If no middlewares are registered, executes directly.
|
|
515
|
-
*
|
|
516
|
-
* Middleware can inspect and log query parameters, modify results after execution,
|
|
517
|
-
* and measure timing. Note: query SQL is generated before middleware runs, so
|
|
518
|
-
* modifying params.args in middleware will NOT affect the executed SQL.
|
|
519
|
-
* To intercept queries before SQL generation, use the raw() method instead.
|
|
520
|
-
*/
|
|
521
|
-
private executeWithMiddleware;
|
|
522
|
-
findUnique<W extends TypedWithClause<R> = {}>(args: FindUniqueArgs<T, R, W>): Promise<WithResult<T, R, W> | null>;
|
|
523
|
-
buildFindUnique<W extends TypedWithClause<R> = {}>(args: FindUniqueArgs<T, R, W>): DeferredQuery<T | null>;
|
|
524
|
-
findMany<W extends TypedWithClause<R> = {}>(args?: FindManyArgs<T, R, W>): Promise<WithResult<T, R, W>[]>;
|
|
525
|
-
/**
|
|
526
|
-
* Emit a one-time `console.warn` when {@link findMany} is called without an
|
|
527
|
-
* explicit `limit`/`take` and `warnOnUnlimited` has not been disabled.
|
|
528
|
-
*
|
|
529
|
-
* Deduped per QueryInterface instance via {@link warnedTables} so a busy
|
|
530
|
-
* loop calling `db.users.findMany()` thousands of times only logs once.
|
|
531
|
-
* Suppressed when `defaultLimit` is configured (the caller has already
|
|
532
|
-
* opted in to a bounded query) and when the user passed an explicit
|
|
533
|
-
* `limit`, `take`, or `cursor`.
|
|
534
|
-
*/
|
|
535
|
-
private maybeWarnUnlimited;
|
|
536
|
-
buildFindMany<W extends TypedWithClause<R> = {}>(args?: FindManyArgs<T, R, W>): DeferredQuery<T[]>;
|
|
537
|
-
/**
|
|
538
|
-
* Stream rows from a findMany query using PostgreSQL cursors.
|
|
539
|
-
* Returns an AsyncIterable that yields individual rows, fetching in batches internally.
|
|
540
|
-
*
|
|
541
|
-
* **Speculative fast-path:** Before opening a cursor, issues a single
|
|
542
|
-
* `SELECT ... LIMIT batchSize+1`. If the result fits within `batchSize`,
|
|
543
|
-
* all rows are yielded immediately with zero cursor overhead (no BEGIN /
|
|
544
|
-
* DECLARE / CLOSE / COMMIT). Only when the result overflows does the
|
|
545
|
-
* method fall back to the full cursor path.
|
|
546
|
-
*
|
|
547
|
-
* **Cursor path:** Uses DECLARE CURSOR within a dedicated transaction on a
|
|
548
|
-
* single pooled connection. The cursor is automatically closed and the
|
|
549
|
-
* connection released when iteration completes or is terminated early
|
|
550
|
-
* (e.g. `break` from `for await`).
|
|
551
|
-
*
|
|
552
|
-
* **Snapshot semantics note:** The speculative fast-path runs outside a
|
|
553
|
-
* transaction. If the result overflows and the cursor path is opened, the
|
|
554
|
-
* cursor runs in its own transaction — spanning two separate snapshots.
|
|
555
|
-
* For strict single-snapshot semantics, wrap the call in `$transaction`.
|
|
556
|
-
*
|
|
557
|
-
* @example
|
|
558
|
-
* ```ts
|
|
559
|
-
* for await (const user of db.users.findManyStream({ where: { orgId: 1 }, batchSize: 500 })) {
|
|
560
|
-
* process.stdout.write(`${user.email}\n`);
|
|
561
|
-
* }
|
|
562
|
-
* ```
|
|
563
|
-
*/
|
|
564
|
-
findManyStream<W extends TypedWithClause<R> = {}>(args?: FindManyStreamArgs<T, R, W>): AsyncGenerator<WithResult<T, R, W>, void, undefined>;
|
|
565
|
-
findFirst<W extends TypedWithClause<R> = {}>(args?: FindManyArgs<T, R, W>): Promise<WithResult<T, R, W> | null>;
|
|
566
|
-
buildFindFirst<W extends TypedWithClause<R> = {}>(args?: FindManyArgs<T, R, W>): DeferredQuery<T | null>;
|
|
567
|
-
findFirstOrThrow<W extends TypedWithClause<R> = {}>(args?: FindManyArgs<T, R, W>): Promise<WithResult<T, R, W>>;
|
|
568
|
-
buildFindFirstOrThrow<W extends TypedWithClause<R> = {}>(args?: FindManyArgs<T, R, W>): DeferredQuery<T>;
|
|
569
|
-
findUniqueOrThrow<W extends TypedWithClause<R> = {}>(args: FindUniqueArgs<T, R, W>): Promise<WithResult<T, R, W>>;
|
|
570
|
-
buildFindUniqueOrThrow<W extends TypedWithClause<R> = {}>(args: FindUniqueArgs<T, R, W>): DeferredQuery<T>;
|
|
571
|
-
create(args: CreateArgs<T>): Promise<T>;
|
|
572
|
-
buildCreate(args: CreateArgs<T>): DeferredQuery<T>;
|
|
573
|
-
createMany(args: CreateManyArgs<T>): Promise<T[]>;
|
|
574
|
-
buildCreateMany(args: CreateManyArgs<T>): DeferredQuery<T[]>;
|
|
575
|
-
update(args: UpdateArgs<T>): Promise<T>;
|
|
576
|
-
buildUpdate(args: UpdateArgs<T>): DeferredQuery<T>;
|
|
577
|
-
delete(args: DeleteArgs<T>): Promise<T>;
|
|
578
|
-
buildDelete(args: DeleteArgs<T>): DeferredQuery<T>;
|
|
579
|
-
upsert(args: UpsertArgs<T>): Promise<T>;
|
|
580
|
-
buildUpsert(args: UpsertArgs<T>): DeferredQuery<T>;
|
|
581
|
-
updateMany(args: UpdateManyArgs<T>): Promise<{
|
|
582
|
-
count: number;
|
|
583
|
-
}>;
|
|
584
|
-
buildUpdateMany(args: UpdateManyArgs<T>): DeferredQuery<{
|
|
585
|
-
count: number;
|
|
586
|
-
}>;
|
|
587
|
-
deleteMany(args: DeleteManyArgs<T>): Promise<{
|
|
588
|
-
count: number;
|
|
589
|
-
}>;
|
|
590
|
-
buildDeleteMany(args: DeleteManyArgs<T>): DeferredQuery<{
|
|
591
|
-
count: number;
|
|
592
|
-
}>;
|
|
593
|
-
count(args?: CountArgs<T>): Promise<number>;
|
|
594
|
-
buildCount(args?: CountArgs<T>): DeferredQuery<number>;
|
|
595
|
-
groupBy(args: GroupByArgs<T>): Promise<Record<string, unknown>[]>;
|
|
596
|
-
buildGroupBy(args: GroupByArgs<T>): DeferredQuery<Record<string, unknown>[]>;
|
|
597
|
-
aggregate(args: AggregateArgs<T>): Promise<AggregateResult<T>>;
|
|
598
|
-
buildAggregate(args: AggregateArgs<T>): DeferredQuery<AggregateResult<T>>;
|
|
599
|
-
/**
|
|
600
|
-
* Resolve select/omit options into a list of snake_case column names.
|
|
601
|
-
* Returns null if neither is provided (meaning all columns).
|
|
602
|
-
*/
|
|
603
|
-
private resolveColumns;
|
|
604
|
-
/** Convert camelCase field name to snake_case column name (unquoted, for non-SQL uses) */
|
|
605
|
-
private toColumn;
|
|
606
|
-
/** Convert camelCase field name to a double-quoted SQL identifier */
|
|
607
|
-
private toSqlColumn;
|
|
608
|
-
/**
|
|
609
|
-
* Build a single SET clause entry for update/updateMany.
|
|
610
|
-
*
|
|
611
|
-
* Supports plain values and atomic operator objects ({ set, increment,
|
|
612
|
-
* decrement, multiply, divide }). An operator object is detected ONLY when
|
|
613
|
-
* it has EXACTLY one key that is one of the 5 operator keys — this avoids
|
|
614
|
-
* misinterpreting JSON column values like `{ set: 'x' }` as operators
|
|
615
|
-
* (real operator objects always have exactly one key, and a plain JSON
|
|
616
|
-
* payload that happens to have a single `set` key is extremely unusual).
|
|
617
|
-
* Multi-key objects are always treated as plain (JSON) values.
|
|
618
|
-
*
|
|
619
|
-
* Returns the SQL fragment (e.g., `"view_count" = "view_count" + $3`) and
|
|
620
|
-
* pushes any required params onto the shared params array so that WHERE
|
|
621
|
-
* clause numbering continues correctly afterward.
|
|
622
|
-
*/
|
|
623
|
-
private buildSetClause;
|
|
624
|
-
/**
|
|
625
|
-
* Produce a value-invariant fingerprint of a where clause.
|
|
626
|
-
* Same keys + same operator shapes + same combinator structure => same string.
|
|
627
|
-
* Different values (e.g. id=1 vs id=999) => identical fingerprint.
|
|
628
|
-
*
|
|
629
|
-
* @internal Exposed as package-private for testing via class access.
|
|
630
|
-
*/
|
|
631
|
-
fingerprintWhere(where: Record<string, unknown>): string;
|
|
632
|
-
/**
|
|
633
|
-
* Fingerprint a relation filter sub-where for some/every/none.
|
|
634
|
-
*/
|
|
635
|
-
private fingerprintRelFilter;
|
|
636
|
-
/**
|
|
637
|
-
* Walk a where clause and push ONLY values into `params`, in the EXACT same
|
|
638
|
-
* order that `buildWhereClause` pushes them. Used on cache hit to fill params
|
|
639
|
-
* without rebuilding SQL.
|
|
640
|
-
*
|
|
641
|
-
* @internal Exposed as package-private for testing.
|
|
642
|
-
*/
|
|
643
|
-
collectWhereParams(where: Record<string, unknown>, params: unknown[]): void;
|
|
644
|
-
/** Collect params from a relation filter sub-where. Mirrors buildSubWhereForRelation. */
|
|
645
|
-
private collectRelFilterParams;
|
|
646
|
-
/** Collect params from operator clauses. Mirrors buildOperatorClauses. */
|
|
647
|
-
private collectOperatorParams;
|
|
648
|
-
/** Collect params from JSON filter. Mirrors buildJsonFilterClauses. */
|
|
649
|
-
private collectJsonFilterParams;
|
|
650
|
-
/** Collect params from array filter. Mirrors buildArrayFilterClauses. */
|
|
651
|
-
private collectArrayFilterParams;
|
|
652
|
-
/**
|
|
653
|
-
* Produce a fingerprint for a `with` clause tree. Recursion mirrors
|
|
654
|
-
* buildSelectWithRelations / buildRelationSubquery.
|
|
655
|
-
*
|
|
656
|
-
* @internal Exposed as package-private for testing.
|
|
657
|
-
*/
|
|
658
|
-
withFingerprint(withClause: WithClause | undefined, table?: string, depth?: number): string;
|
|
659
|
-
/**
|
|
660
|
-
* Collect params from a `with` clause tree. Mirrors buildSelectWithRelations +
|
|
661
|
-
* buildRelationSubquery param-push order.
|
|
662
|
-
*/
|
|
663
|
-
private collectWithParams;
|
|
664
|
-
/**
|
|
665
|
-
* Collect params from a single relation subquery. Mirrors buildRelationSubquery.
|
|
666
|
-
*/
|
|
667
|
-
private collectRelationSubqueryParams;
|
|
668
|
-
/**
|
|
669
|
-
* Fingerprint SET clauses for update/updateMany.
|
|
670
|
-
* Captures key names + operator types (set/increment/etc) but not values.
|
|
671
|
-
*/
|
|
672
|
-
private fingerprintSet;
|
|
673
|
-
/**
|
|
674
|
-
* Collect SET params for update/updateMany. Mirrors buildSetClause param order.
|
|
675
|
-
*/
|
|
676
|
-
private collectSetParams;
|
|
677
|
-
/** Build WHERE clause from a where object (supports operators, NULL, OR) */
|
|
678
|
-
private buildWhere;
|
|
679
|
-
/**
|
|
680
|
-
* Refuse mutations with an empty predicate unless explicitly opted in.
|
|
681
|
-
*
|
|
682
|
-
* An empty `where` (e.g. `{}` or `{ id: undefined }`) resolves to a
|
|
683
|
-
* mutation with no filter — a common footgun when a caller's filter
|
|
684
|
-
* value accidentally resolves to `undefined`. This guard throws
|
|
685
|
-
* `ValidationError` in that case unless `allowFullTableScan: true`.
|
|
686
|
-
*/
|
|
687
|
-
private assertMutationHasPredicate;
|
|
688
|
-
/**
|
|
689
|
-
* Build the inner WHERE expression (without the WHERE keyword).
|
|
690
|
-
* Returns null if no conditions exist.
|
|
691
|
-
* Supports: equality, operators, NULL, OR, AND, NOT, relation filters (some/every/none).
|
|
692
|
-
*/
|
|
693
|
-
private buildWhereClause;
|
|
694
|
-
/**
|
|
695
|
-
* Build relation filter SQL: WHERE EXISTS / NOT EXISTS subquery
|
|
696
|
-
* Supports: some (EXISTS), every (NOT EXISTS ... NOT), none (NOT EXISTS)
|
|
697
|
-
*/
|
|
698
|
-
private buildRelationFilter;
|
|
699
|
-
/**
|
|
700
|
-
* Build WHERE clause conditions for a relation filter subquery.
|
|
701
|
-
* Uses the target table's column mapping to resolve field names.
|
|
702
|
-
*/
|
|
703
|
-
private buildSubWhereForRelation;
|
|
704
|
-
/**
|
|
705
|
-
* Build SQL clauses for a single operator object on a column.
|
|
706
|
-
* Each operator key becomes its own clause, all ANDed together.
|
|
707
|
-
*/
|
|
708
|
-
private buildOperatorClauses;
|
|
709
|
-
/** Build ORDER BY clause from an object */
|
|
710
|
-
private buildOrderBy;
|
|
711
|
-
/** Parse a flat row: convert snake_case to camelCase + Date coercion */
|
|
712
|
-
private parseRow;
|
|
713
|
-
/** Parse a row that may contain JSON nested relation columns */
|
|
714
|
-
private parseNestedRow;
|
|
715
|
-
/**
|
|
716
|
-
* Build a SELECT clause that includes both base columns and nested relation subqueries.
|
|
717
|
-
*
|
|
718
|
-
* For each relation specified in the `with` clause, this method generates a correlated
|
|
719
|
-
* subquery using PostgreSQL's `json_agg(json_build_object(...))` pattern. The result
|
|
720
|
-
* is a single SQL SELECT clause that resolves the full object tree in one query --
|
|
721
|
-
* no N+1 problem.
|
|
722
|
-
*
|
|
723
|
-
* **How it works:**
|
|
724
|
-
* 1. Resolves the base columns for the root table (all columns, or a subset via `columnsList`).
|
|
725
|
-
* 2. Iterates over each key in the `with` clause, looking up the relation definition.
|
|
726
|
-
* 3. For each relation, delegates to {@link buildRelationSubquery} to generate a
|
|
727
|
-
* correlated subquery that returns JSON (array for hasMany, object for belongsTo/hasOne).
|
|
728
|
-
* 4. Each subquery is aliased as the relation name in the final SELECT.
|
|
729
|
-
*
|
|
730
|
-
* **aliasCounter:** A shared `{ n: number }` object is passed through all nesting levels.
|
|
731
|
-
* Each call to `buildRelationSubquery` increments it to produce unique table aliases
|
|
732
|
-
* (`t0`, `t1`, `t2`, ...) across arbitrarily deep relation trees, preventing alias
|
|
733
|
-
* collisions in the generated SQL.
|
|
734
|
-
*
|
|
735
|
-
* **Example output:**
|
|
736
|
-
* ```sql
|
|
737
|
-
* "users"."id", "users"."name", "users"."email",
|
|
738
|
-
* (SELECT COALESCE(json_agg(json_build_object('id', t0."id", 'title', t0."title")), '[]'::json)
|
|
739
|
-
* FROM "posts" t0 WHERE t0."user_id" = "users"."id") AS "posts"
|
|
740
|
-
* ```
|
|
741
|
-
*
|
|
742
|
-
* @param table - The root table name (e.g. `"users"`).
|
|
743
|
-
* @param withClause - An object mapping relation names to their include specs
|
|
744
|
-
* (`true` for default inclusion, or `WithOptions` for select/omit/where/orderBy/limit).
|
|
745
|
-
* @param params - Shared parameter array for parameterized values (`$1`, `$2`, ...).
|
|
746
|
-
* Nested where/limit values are pushed here to prevent SQL injection.
|
|
747
|
-
* @param columnsList - Optional subset of columns to include in the SELECT. When `null`
|
|
748
|
-
* or omitted, all columns from the table's schema metadata are used.
|
|
749
|
-
* @param depth - Current nesting depth, passed through to {@link buildRelationSubquery}
|
|
750
|
-
* for circular-relation detection. Defaults to `0` at the top level.
|
|
751
|
-
* @param path - Breadcrumb trail of relation names traversed so far, used in error
|
|
752
|
-
* messages when circular or too-deep nesting is detected.
|
|
753
|
-
* @returns A complete SELECT clause string (without the `SELECT` keyword) containing
|
|
754
|
-
* base columns and relation subqueries.
|
|
755
|
-
*/
|
|
756
|
-
private buildSelectWithRelations;
|
|
757
|
-
/**
|
|
758
|
-
* Generate a correlated subquery that returns JSON for a single relation.
|
|
759
|
-
*
|
|
760
|
-
* This is the core of Turbine's single-query nested relation strategy. For a given
|
|
761
|
-
* relation (e.g. `posts` on a `users` query), it produces a self-contained SQL subquery
|
|
762
|
-
* that PostgreSQL evaluates per parent row, returning either a JSON array (hasMany) or
|
|
763
|
-
* a single JSON object (belongsTo / hasOne).
|
|
764
|
-
*
|
|
765
|
-
* ### Algorithm overview
|
|
766
|
-
*
|
|
767
|
-
* 1. **Alias generation:** Allocates a unique alias (`t0`, `t1`, ...) from the shared
|
|
768
|
-
* `aliasCounter` so that deeply nested subqueries never collide.
|
|
769
|
-
*
|
|
770
|
-
* 2. **Column resolution:** Honors `select` / `omit` options to control which columns
|
|
771
|
-
* appear in the output JSON.
|
|
772
|
-
*
|
|
773
|
-
* 3. **`json_build_object`:** Builds a JSON object for each row by mapping camelCase
|
|
774
|
-
* field names to their column values:
|
|
775
|
-
* ```sql
|
|
776
|
-
* json_build_object('id', t0."id", 'title', t0."title", 'createdAt', t0."created_at")
|
|
777
|
-
* ```
|
|
778
|
-
*
|
|
779
|
-
* 4. **`json_agg` wrapping (hasMany):** For one-to-many relations, wraps the
|
|
780
|
-
* `json_build_object` call in `json_agg(...)` to aggregate all matching child rows
|
|
781
|
-
* into a JSON array. Uses `COALESCE(..., '[]'::json)` so the result is never NULL.
|
|
782
|
-
* For belongsTo / hasOne, no aggregation is used -- just the single JSON object
|
|
783
|
-
* with `LIMIT 1`.
|
|
784
|
-
*
|
|
785
|
-
* 5. **Correlation (WHERE clause):** Links the subquery to the parent row:
|
|
786
|
-
* - **hasMany:** `alias.foreignKey = parentRef.referenceKey`
|
|
787
|
-
* (e.g. `t0."user_id" = "users"."id"` -- child FK points to parent PK)
|
|
788
|
-
* - **belongsTo / hasOne:** `alias.referenceKey = parentRef.foreignKey`
|
|
789
|
-
* (e.g. `t0."id" = "posts"."author_id"` -- parent FK points to child PK)
|
|
790
|
-
*
|
|
791
|
-
* 6. **Recursion:** If the spec includes a nested `with` clause, this method calls
|
|
792
|
-
* itself recursively for each nested relation, passing the current alias as
|
|
793
|
-
* `parentRef`. The nested subquery appears as an additional key in the
|
|
794
|
-
* `json_build_object` call, wrapped in `COALESCE(..., '[]'::json)`.
|
|
795
|
-
* Depth is incremented and capped at 10 to guard against circular relations.
|
|
796
|
-
*
|
|
797
|
-
* 7. **LIMIT / ORDER BY wrapping:** For hasMany relations with `limit` or `orderBy`,
|
|
798
|
-
* the query is restructured into a two-level form:
|
|
799
|
-
* ```sql
|
|
800
|
-
* SELECT COALESCE(json_agg(json_build_object(...)), '[]'::json)
|
|
801
|
-
* FROM (
|
|
802
|
-
* SELECT t0.* FROM "posts" t0
|
|
803
|
-
* WHERE t0."user_id" = "users"."id"
|
|
804
|
-
* ORDER BY t0."created_at" DESC
|
|
805
|
-
* LIMIT $1
|
|
806
|
-
* ) t0i
|
|
807
|
-
* ```
|
|
808
|
-
* This ensures LIMIT and ORDER BY apply to the raw rows *before* `json_agg`
|
|
809
|
-
* aggregation. Without the inner subquery, LIMIT would be meaningless because
|
|
810
|
-
* `json_agg` produces a single aggregated row.
|
|
811
|
-
*
|
|
812
|
-
* 8. **Parameter threading:** All user-supplied values (where filters, limit) are
|
|
813
|
-
* pushed to the shared `params` array with `$N` placeholders. No string
|
|
814
|
-
* interpolation of user data ever occurs -- all identifiers go through
|
|
815
|
-
* `quoteIdent()` and all values are parameterized.
|
|
816
|
-
*
|
|
817
|
-
* ### Example output (hasMany with nested relation)
|
|
818
|
-
* ```sql
|
|
819
|
-
* SELECT COALESCE(json_agg(json_build_object(
|
|
820
|
-
* 'id', t0."id",
|
|
821
|
-
* 'title', t0."title",
|
|
822
|
-
* 'comments', COALESCE((
|
|
823
|
-
* SELECT COALESCE(json_agg(json_build_object('id', t1."id", 'body', t1."body")), '[]'::json)
|
|
824
|
-
* FROM "comments" t1 WHERE t1."post_id" = t0."id"
|
|
825
|
-
* ), '[]'::json)
|
|
826
|
-
* )), '[]'::json) FROM "posts" t0 WHERE t0."user_id" = "users"."id"
|
|
827
|
-
* ```
|
|
828
|
-
*
|
|
829
|
-
* @param relDef - The relation definition from schema metadata (contains `to`, `type`,
|
|
830
|
-
* `foreignKey`, `referenceKey`).
|
|
831
|
-
* @param spec - Either `true` (include with defaults) or a `WithOptions` object that
|
|
832
|
-
* can specify `select`, `omit`, `where`, `orderBy`, `limit`, and nested `with`.
|
|
833
|
-
* @param params - Shared parameter array. User-supplied values are pushed here and
|
|
834
|
-
* referenced as `$1`, `$2`, etc. in the generated SQL.
|
|
835
|
-
* @param parentRef - The alias (e.g. `"t0"`) or table name (e.g. `"users"`) of the
|
|
836
|
-
* parent query. Used to build the correlated WHERE clause that ties
|
|
837
|
-
* child rows to their parent row.
|
|
838
|
-
* @param aliasCounter - Shared mutable counter (`{ n: number }`) for generating unique
|
|
839
|
-
* table aliases (`t0`, `t1`, `t2`, ...) across all nesting levels.
|
|
840
|
-
* Each call increments `n` by 1.
|
|
841
|
-
* @param depth - Current nesting depth (starts at `0`). Incremented on each recursive
|
|
842
|
-
* call. If it reaches 10, a {@link CircularRelationError} is thrown.
|
|
843
|
-
* @param path - Breadcrumb trail of relation/table names traversed so far
|
|
844
|
-
* (e.g. `["users", "posts", "comments"]`). Used in the error message
|
|
845
|
-
* when circular or too-deep nesting is detected.
|
|
846
|
-
* @returns A complete SQL subquery string (without surrounding parentheses) that
|
|
847
|
-
* evaluates to a JSON array (hasMany) or a JSON object (belongsTo/hasOne).
|
|
848
|
-
*/
|
|
849
|
-
private buildRelationSubquery;
|
|
850
|
-
/**
|
|
851
|
-
* Get the Postgres type for a column (e.g. 'jsonb', 'text', '_int4').
|
|
852
|
-
* Used to detect JSONB/array columns for specialized operators.
|
|
853
|
-
* Uses pre-computed Map for O(1) lookup instead of linear scan.
|
|
854
|
-
*/
|
|
855
|
-
private getColumnPgType;
|
|
856
|
-
/**
|
|
857
|
-
* Get the Postgres base element type for an array column.
|
|
858
|
-
* E.g. '_text' → 'text', '_int4' → 'integer'
|
|
859
|
-
*/
|
|
860
|
-
private getArrayElementType;
|
|
861
|
-
/**
|
|
862
|
-
* Build SQL clauses for JSONB filter operators on a column.
|
|
863
|
-
* Supports: path, equals, contains, hasKey.
|
|
864
|
-
*/
|
|
865
|
-
private buildJsonFilterClauses;
|
|
866
|
-
/**
|
|
867
|
-
* Build SQL clauses for Array filter operators on a column.
|
|
868
|
-
* Supports: has, hasEvery, hasSome, isEmpty.
|
|
869
|
-
*/
|
|
870
|
-
private buildArrayFilterClauses;
|
|
871
|
-
/**
|
|
872
|
-
* Get the Postgres array type for a column (used by UNNEST in createMany).
|
|
873
|
-
* Uses pre-computed Map for O(1) lookup instead of linear scan.
|
|
874
|
-
*/
|
|
875
|
-
private getColumnArrayType;
|
|
876
|
-
}
|
|
877
|
-
export {};
|
|
878
|
-
//# sourceMappingURL=query.d.ts.map
|