turbine-orm 0.50.0 → 0.51.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 +66 -66
- package/dist/adapters/cockroachdb.d.ts +5 -5
- package/dist/adapters/cockroachdb.js +10 -10
- package/dist/adapters/index.d.ts +5 -5
- package/dist/adapters/index.js +7 -7
- package/dist/adapters/yugabytedb.d.ts +7 -7
- package/dist/adapters/yugabytedb.js +10 -10
- package/dist/cjs/adapters/cockroachdb.d.ts +5 -5
- package/dist/cjs/adapters/cockroachdb.js +10 -10
- package/dist/cjs/adapters/index.d.ts +5 -5
- package/dist/cjs/adapters/index.js +7 -7
- package/dist/cjs/adapters/yugabytedb.d.ts +7 -7
- package/dist/cjs/adapters/yugabytedb.js +10 -10
- package/dist/cjs/cli/config.d.ts +13 -2
- package/dist/cjs/cli/config.js +3 -2
- package/dist/cjs/cli/destructive.d.ts +1 -1
- package/dist/cjs/cli/destructive.js +1 -1
- package/dist/cjs/cli/index.d.ts +10 -10
- package/dist/cjs/cli/index.js +49 -45
- package/dist/cjs/cli/loader.d.ts +7 -7
- package/dist/cjs/cli/loader.js +9 -9
- package/dist/cjs/cli/mcp.js +4 -4
- package/dist/cjs/cli/migrate.d.ts +5 -5
- package/dist/cjs/cli/migrate.js +11 -11
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/ui.d.ts +2 -2
- package/dist/cjs/cli/ui.js +2 -2
- package/dist/cjs/client.d.ts +49 -38
- package/dist/cjs/client.js +57 -56
- package/dist/cjs/dialect.d.ts +62 -18
- package/dist/cjs/dialect.js +40 -2
- package/dist/cjs/errors.d.ts +5 -5
- package/dist/cjs/errors.js +11 -11
- package/dist/cjs/generate.d.ts +6 -6
- package/dist/cjs/generate.js +31 -29
- package/dist/cjs/index-advisor.d.ts +5 -5
- package/dist/cjs/index-advisor.js +0 -0
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +7 -7
- package/dist/cjs/introspect.d.ts +35 -9
- package/dist/cjs/introspect.js +83 -32
- package/dist/cjs/mssql.d.ts +11 -11
- package/dist/cjs/mssql.js +64 -29
- package/dist/cjs/mysql.d.ts +8 -8
- package/dist/cjs/mysql.js +61 -23
- package/dist/cjs/nested-write.d.ts +21 -2
- package/dist/cjs/nested-write.js +51 -14
- package/dist/cjs/optional-peer-import.cjs +7 -7
- package/dist/cjs/optional-peer-import.d.cts +7 -7
- package/dist/cjs/pipeline-submittable.d.ts +2 -2
- package/dist/cjs/pipeline-submittable.js +6 -6
- package/dist/cjs/pipeline.d.ts +1 -1
- package/dist/cjs/pipeline.js +4 -4
- package/dist/cjs/powdb-introspect.d.ts +1 -1
- package/dist/cjs/powdb-introspect.js +1 -1
- package/dist/cjs/powdb.d.ts +28 -28
- package/dist/cjs/powdb.js +66 -66
- package/dist/cjs/powql.d.ts +27 -27
- package/dist/cjs/powql.js +73 -52
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +5 -5
- package/dist/cjs/query/batched-loader.d.ts +11 -11
- package/dist/cjs/query/batched-loader.js +24 -24
- package/dist/cjs/query/builder.d.ts +39 -21
- package/dist/cjs/query/builder.js +99 -57
- package/dist/cjs/query/compound-unique.d.ts +1 -1
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/deferred.d.ts +12 -6
- package/dist/cjs/query/deferred.js +1 -1
- package/dist/cjs/query/filters.d.ts +31 -11
- package/dist/cjs/query/filters.js +67 -14
- package/dist/cjs/query/index.d.ts +1 -1
- package/dist/cjs/query/index.js +1 -1
- package/dist/cjs/query/relations.d.ts +9 -9
- package/dist/cjs/query/relations.js +164 -57
- package/dist/cjs/query/types.d.ts +86 -35
- package/dist/cjs/query/types.js +1 -1
- package/dist/cjs/query/utils.d.ts +27 -10
- package/dist/cjs/query/utils.js +86 -14
- package/dist/cjs/query/where.d.ts +47 -28
- package/dist/cjs/query/where.js +130 -31
- package/dist/cjs/query/writes.d.ts +24 -5
- package/dist/cjs/query/writes.js +102 -13
- package/dist/cjs/realtime.d.ts +7 -7
- package/dist/cjs/realtime.js +9 -9
- package/dist/cjs/schema-builder.d.ts +18 -7
- package/dist/cjs/schema-builder.js +17 -10
- package/dist/cjs/schema-metadata.d.ts +3 -3
- package/dist/cjs/schema-metadata.js +9 -9
- package/dist/cjs/schema-sql.d.ts +9 -9
- package/dist/cjs/schema-sql.js +20 -20
- package/dist/cjs/schema.d.ts +19 -9
- package/dist/cjs/schema.js +6 -6
- package/dist/cjs/serverless.d.ts +15 -15
- package/dist/cjs/serverless.js +16 -16
- package/dist/cjs/sqlite.d.ts +8 -8
- package/dist/cjs/sqlite.js +53 -22
- package/dist/cjs/typed-sql.d.ts +4 -4
- package/dist/cjs/typed-sql.js +5 -5
- package/dist/cli/config.d.ts +13 -2
- package/dist/cli/config.js +3 -2
- package/dist/cli/destructive.d.ts +1 -1
- package/dist/cli/destructive.js +1 -1
- package/dist/cli/index.d.ts +10 -10
- package/dist/cli/index.js +49 -45
- package/dist/cli/loader.d.ts +7 -7
- package/dist/cli/loader.js +9 -9
- package/dist/cli/mcp.js +4 -4
- package/dist/cli/migrate.d.ts +5 -5
- package/dist/cli/migrate.js +11 -11
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/ui.d.ts +2 -2
- package/dist/cli/ui.js +2 -2
- package/dist/client.d.ts +49 -38
- package/dist/client.js +57 -56
- package/dist/dialect.d.ts +62 -18
- package/dist/dialect.js +40 -2
- package/dist/errors.d.ts +5 -5
- package/dist/errors.js +11 -11
- package/dist/generate.d.ts +6 -6
- package/dist/generate.js +31 -29
- package/dist/index-advisor.d.ts +5 -5
- package/dist/index-advisor.js +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -7
- package/dist/introspect.d.ts +35 -9
- package/dist/introspect.js +82 -32
- package/dist/mssql.d.ts +11 -11
- package/dist/mssql.js +64 -29
- package/dist/mysql.d.ts +8 -8
- package/dist/mysql.js +61 -23
- package/dist/nested-write.d.ts +21 -2
- package/dist/nested-write.js +51 -14
- package/dist/optional-peer-import.cjs +7 -7
- package/dist/optional-peer-import.d.cts +7 -7
- package/dist/pipeline-submittable.d.ts +2 -2
- package/dist/pipeline-submittable.js +6 -6
- package/dist/pipeline.d.ts +1 -1
- package/dist/pipeline.js +4 -4
- package/dist/powdb-introspect.d.ts +1 -1
- package/dist/powdb-introspect.js +1 -1
- package/dist/powdb.d.ts +28 -28
- package/dist/powdb.js +66 -66
- package/dist/powql.d.ts +27 -27
- package/dist/powql.js +73 -52
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +5 -5
- package/dist/query/batched-loader.d.ts +11 -11
- package/dist/query/batched-loader.js +24 -24
- package/dist/query/builder.d.ts +39 -21
- package/dist/query/builder.js +100 -58
- package/dist/query/compound-unique.d.ts +1 -1
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +12 -6
- package/dist/query/deferred.js +1 -1
- package/dist/query/filters.d.ts +31 -11
- package/dist/query/filters.js +66 -13
- package/dist/query/index.d.ts +1 -1
- package/dist/query/index.js +1 -1
- package/dist/query/relations.d.ts +9 -9
- package/dist/query/relations.js +165 -58
- package/dist/query/types.d.ts +86 -35
- package/dist/query/types.js +1 -1
- package/dist/query/utils.d.ts +27 -10
- package/dist/query/utils.js +84 -14
- package/dist/query/where.d.ts +47 -28
- package/dist/query/where.js +129 -32
- package/dist/query/writes.d.ts +24 -5
- package/dist/query/writes.js +101 -13
- package/dist/realtime.d.ts +7 -7
- package/dist/realtime.js +9 -9
- package/dist/schema-builder.d.ts +18 -7
- package/dist/schema-builder.js +17 -10
- package/dist/schema-metadata.d.ts +3 -3
- package/dist/schema-metadata.js +9 -9
- package/dist/schema-sql.d.ts +9 -9
- package/dist/schema-sql.js +20 -20
- package/dist/schema.d.ts +19 -9
- package/dist/schema.js +6 -6
- package/dist/serverless.d.ts +15 -15
- package/dist/serverless.js +16 -16
- package/dist/sqlite.d.ts +8 -8
- package/dist/sqlite.js +53 -22
- package/dist/typed-sql.d.ts +4 -4
- package/dist/typed-sql.js +5 -5
- package/package.json +2 -2
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Batched relation loader (the `relationLoadStrategy: 'batched'` path)
|
|
3
3
|
*
|
|
4
4
|
* ## Why this exists
|
|
5
5
|
*
|
|
6
6
|
* Turbine's default `with`-clause strategy resolves nested relations in ONE SQL
|
|
7
|
-
* statement using correlated `json_agg(json_build_object(...))` subqueries
|
|
7
|
+
* statement using correlated `json_agg(json_build_object(...))` subqueries, one
|
|
8
8
|
* probe per parent row (see `buildRelationSubquery` in builder.ts). That is the
|
|
9
9
|
* right default: a single round-trip, and when the child FK columns are indexed
|
|
10
10
|
* each probe is an index seek. But it degrades in two situations:
|
|
11
11
|
*
|
|
12
|
-
* 1. **Missing FK index
|
|
12
|
+
* 1. **Missing FK index**, a correlated probe per parent row becomes
|
|
13
13
|
* N-parents × full-table-scan. A batched-loader ORM pays that missing index
|
|
14
14
|
* only ONCE (a single `WHERE fk = ANY($1)` seq-scan), which is why schemas
|
|
15
15
|
* migrated from those ORMs often lack the index the json_agg path needs.
|
|
16
|
-
* 2. **Huge unpaginated result sets
|
|
16
|
+
* 2. **Huge unpaginated result sets**, the JSON wire format
|
|
17
17
|
* (`json_build_object` per row, re-serialized inside `json_agg`) is heavy to
|
|
18
18
|
* encode/decode compared with flat rows.
|
|
19
19
|
*
|
|
@@ -29,19 +29,19 @@
|
|
|
29
29
|
* - **Same executor / connection path.** Every follow-up query runs through the
|
|
30
30
|
* caller's own executor ({@link RelationLoadContext.exec}) and child query
|
|
31
31
|
* interfaces built on the caller's pool. Inside a `$transaction` that pool is
|
|
32
|
-
* the pinned-connection `txPool`, so batched loads join the transaction
|
|
32
|
+
* the pinned-connection `txPool`, so batched loads join the transaction, no
|
|
33
33
|
* separate pool checkout per query.
|
|
34
34
|
* - **Identical output shape.** The stitched result is byte-for-byte the same
|
|
35
35
|
* shape the join strategy produces: relation arrays for hasMany/manyToMany
|
|
36
36
|
* (`[]` when empty), single-or-null for hasOne/belongsTo, with the same
|
|
37
|
-
* camelCase keys and Date coercion
|
|
37
|
+
* camelCase keys and Date coercion, because the child rows are parsed by the
|
|
38
38
|
* very same `parseRow`/`buildFindMany` machinery via a child QueryInterface.
|
|
39
39
|
* - **Stitch keys never leak.** To stitch, the follow-up query must select the
|
|
40
40
|
* FK/PK it joins on even when the caller's `select`/`omit` excluded it; the
|
|
41
41
|
* loader adds those columns for the query and strips them from the returned
|
|
42
42
|
* entities afterwards ({@link includeKeysForBatching}).
|
|
43
43
|
*
|
|
44
|
-
* PowDB (powql.ts) has its own batched loaders for the same reasons
|
|
44
|
+
* PowDB (powql.ts) has its own batched loaders for the same reasons, this is the
|
|
45
45
|
* clean Postgres/SQL implementation, deliberately NOT shared with PowQL.
|
|
46
46
|
*
|
|
47
47
|
* @module
|
|
@@ -52,14 +52,14 @@ import { isRelationPickOrderBy } from './filters.js';
|
|
|
52
52
|
import { ownLookup } from './utils.js';
|
|
53
53
|
/**
|
|
54
54
|
* Max parent keys per follow-up query. On Postgres the whole key set travels as
|
|
55
|
-
* ONE array parameter (`= ANY($1)`), so this is not a bind-parameter limit
|
|
55
|
+
* ONE array parameter (`= ANY($1)`), so this is not a bind-parameter limit, it
|
|
56
56
|
* only bounds planner/memory cost per statement. Keep it large: every extra
|
|
57
57
|
* chunk is an extra network round-trip, and round-trips are exactly what the
|
|
58
58
|
* batched strategy exists to minimize (a 9-chunk load was measured 2× slower
|
|
59
59
|
* than a single-statement one over a WAN link).
|
|
60
60
|
*/
|
|
61
61
|
const MAX_RELATION_KEYS = 32_000;
|
|
62
|
-
/** Nesting cap
|
|
62
|
+
/** Nesting cap, parity with the join strategy's depth-10 guard. */
|
|
63
63
|
const MAX_DEPTH = 10;
|
|
64
64
|
/**
|
|
65
65
|
* The default projection of `meta` expressed in FIELD names: which fields the
|
|
@@ -91,7 +91,7 @@ export function defaultProjectionFields(meta, includePii) {
|
|
|
91
91
|
*
|
|
92
92
|
* Used both for the base query (parent keys) and each follow-up query (child
|
|
93
93
|
* keys) so a caller's `select: { title: true }` on a relation still stitches even
|
|
94
|
-
* though the FK was not requested
|
|
94
|
+
* though the FK was not requested, and the FK never appears in the output.
|
|
95
95
|
*/
|
|
96
96
|
export function includeKeysForBatching(select, omit, fields,
|
|
97
97
|
/**
|
|
@@ -113,7 +113,7 @@ defaultProjection) {
|
|
|
113
113
|
for (const f of unique) {
|
|
114
114
|
if (!next[f]) {
|
|
115
115
|
next[f] = true;
|
|
116
|
-
strip.push(f); // not requested by the caller
|
|
116
|
+
strip.push(f); // not requested by the caller, added only to stitch
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
return { select: next, omit, strip };
|
|
@@ -173,7 +173,7 @@ export function neededParentKeyFields(parentMeta, withClause) {
|
|
|
173
173
|
}
|
|
174
174
|
const rel = ownLookup(parentMeta.relations, relName);
|
|
175
175
|
if (!rel)
|
|
176
|
-
continue; // unknown relation
|
|
176
|
+
continue; // unknown relation, the join path throws; let the loader surface it
|
|
177
177
|
for (const col of localKeyColumns(rel)) {
|
|
178
178
|
fields.add(parentMeta.reverseColumnMap[col] ?? col);
|
|
179
179
|
}
|
|
@@ -222,18 +222,18 @@ export function resolveCountRelations(parentMeta, countSpec) {
|
|
|
222
222
|
}
|
|
223
223
|
return out;
|
|
224
224
|
}
|
|
225
|
-
/** Stringified stitch key
|
|
225
|
+
/** Stringified stitch key, robust to number/uuid/bigint type drift across a join. */
|
|
226
226
|
function keyOf(value) {
|
|
227
227
|
return String(value);
|
|
228
228
|
}
|
|
229
229
|
/**
|
|
230
|
-
* Reject pick-row relation ordering anywhere inside a `with` tree's orderBy
|
|
230
|
+
* Reject pick-row relation ordering anywhere inside a `with` tree's orderBy -
|
|
231
231
|
* strategy parity with the join path, which throws this exact E003 at SQL
|
|
232
232
|
* build time (`pickOrderNestedError` in builder.ts). Without this guard the
|
|
233
233
|
* loaders would forward `options.orderBy` as the child reader's TOP-LEVEL
|
|
234
|
-
* findMany orderBy, where the pick shape compiles fine
|
|
234
|
+
* findMany orderBy, where the pick shape compiles fine, so the same query
|
|
235
235
|
* would execute on 'batched' but throw on 'join'. Walks the whole tree up
|
|
236
|
-
* front so acceptance never depends on which levels have rows
|
|
236
|
+
* front so acceptance never depends on which levels have rows, the batched
|
|
237
237
|
* runners in builder.ts call this BEFORE the base query (a zero-row base
|
|
238
238
|
* result must still reject, exactly like the join strategy's build-time throw).
|
|
239
239
|
*/
|
|
@@ -269,13 +269,13 @@ export async function loadRelationsBatched(ctx, parents, withClause, timeout, de
|
|
|
269
269
|
if (parents.length === 0)
|
|
270
270
|
return;
|
|
271
271
|
// Sibling relations are independent (each writes only its own parent[relName]
|
|
272
|
-
// and reads only parent keys), so load them concurrently
|
|
272
|
+
// and reads only parent keys), so load them concurrently, on a pool that's
|
|
273
273
|
// real parallelism, inside a transaction pg queues them on the one connection.
|
|
274
274
|
const loads = [];
|
|
275
275
|
for (const [relName, spec] of Object.entries(withClause)) {
|
|
276
276
|
if (!spec)
|
|
277
277
|
continue;
|
|
278
|
-
// Reserved `_count` key
|
|
278
|
+
// Reserved `_count` key, one grouped COUNT(*) follow-up per counted relation.
|
|
279
279
|
if (relName === '_count') {
|
|
280
280
|
loads.push(loadCounts(ctx, parents, spec));
|
|
281
281
|
continue;
|
|
@@ -300,7 +300,7 @@ async function loadToOneOrMany(ctx, parents, rel, relName, options, timeout, dep
|
|
|
300
300
|
const fk = normalizeKeyColumns(rel.foreignKey);
|
|
301
301
|
const rk = normalizeKeyColumns(rel.referenceKey);
|
|
302
302
|
if (fk.length > 1 || rk.length > 1) {
|
|
303
|
-
throw new UnsupportedFeatureError('composite-key batched relation loading', 'relationLoadStrategy: "batched"', `relation "${relName}"
|
|
303
|
+
throw new UnsupportedFeatureError('composite-key batched relation loading', 'relationLoadStrategy: "batched"', `relation "${relName}", use the default 'join' strategy for composite-key relations`);
|
|
304
304
|
}
|
|
305
305
|
const targetMeta = requireTable(ctx.schema, rel.to, relName);
|
|
306
306
|
// Local key lives on the parent; the correlating key lives on the child.
|
|
@@ -375,7 +375,7 @@ async function loadManyToMany(ctx, parents, rel, relName, options, timeout, dept
|
|
|
375
375
|
const sourceRef = normalizeKeyColumns(rel.referenceKey);
|
|
376
376
|
const targetMeta = requireTable(ctx.schema, rel.to, relName);
|
|
377
377
|
if (sourceJ.length > 1 || targetJ.length > 1 || sourceRef.length > 1 || targetMeta.primaryKey.length !== 1) {
|
|
378
|
-
throw new UnsupportedFeatureError('composite-key batched manyToMany loading', 'relationLoadStrategy: "batched"', `relation "${relName}"
|
|
378
|
+
throw new UnsupportedFeatureError('composite-key batched manyToMany loading', 'relationLoadStrategy: "batched"', `relation "${relName}", use the default 'join' strategy for composite-key m2m relations`);
|
|
379
379
|
}
|
|
380
380
|
const sourceJCol = sourceJ[0];
|
|
381
381
|
const targetJCol = targetJ[0];
|
|
@@ -474,7 +474,7 @@ async function loadManyToMany(ctx, parents, rel, relName, options, timeout, dept
|
|
|
474
474
|
* Load correlated `_count` values for the counted relations. One grouped
|
|
475
475
|
* follow-up per relation (`SELECT key, COUNT(*) … WHERE key = ANY($1) GROUP BY
|
|
476
476
|
* key`), attached onto each parent's `_count` object (0 when a parent has no
|
|
477
|
-
* matching rows)
|
|
477
|
+
* matching rows), byte-identical to the join strategy's `_count` output.
|
|
478
478
|
*/
|
|
479
479
|
async function loadCounts(ctx, parents, countSpec) {
|
|
480
480
|
const rels = resolveCountRelations(ctx.parentMeta, countSpec);
|
|
@@ -499,7 +499,7 @@ async function loadOneCount(ctx, parents, rel) {
|
|
|
499
499
|
const sourceRef = normalizeKeyColumns(rel.referenceKey);
|
|
500
500
|
const sourceJ = normalizeKeyColumns(through.sourceKey);
|
|
501
501
|
if (sourceRef.length > 1 || sourceJ.length > 1) {
|
|
502
|
-
throw new UnsupportedFeatureError('composite-key batched _count', 'relationLoadStrategy: "batched"', `relation "${rel.name}"
|
|
502
|
+
throw new UnsupportedFeatureError('composite-key batched _count', 'relationLoadStrategy: "batched"', `relation "${rel.name}", use the default 'join' strategy for composite-key m2m _count`);
|
|
503
503
|
}
|
|
504
504
|
parentKeyCol = sourceRef[0];
|
|
505
505
|
childTable = through.table;
|
|
@@ -510,7 +510,7 @@ async function loadOneCount(ctx, parents, rel) {
|
|
|
510
510
|
const fk = normalizeKeyColumns(rel.foreignKey);
|
|
511
511
|
const rk = normalizeKeyColumns(rel.referenceKey);
|
|
512
512
|
if (fk.length > 1 || rk.length > 1) {
|
|
513
|
-
throw new UnsupportedFeatureError('composite-key batched _count', 'relationLoadStrategy: "batched"', `relation "${rel.name}"
|
|
513
|
+
throw new UnsupportedFeatureError('composite-key batched _count', 'relationLoadStrategy: "batched"', `relation "${rel.name}", use the default 'join' strategy for composite-key _count`);
|
|
514
514
|
}
|
|
515
515
|
parentKeyCol = rk[0];
|
|
516
516
|
childTable = rel.to;
|
|
@@ -529,7 +529,7 @@ async function loadOneCount(ctx, parents, rel) {
|
|
|
529
529
|
// two strategies return identical counts under a filter. hasMany filters
|
|
530
530
|
// the counted table directly; m2m counts junction rows but restricts them
|
|
531
531
|
// to junction rows whose TARGET survives the target table's filter via
|
|
532
|
-
// EXISTS
|
|
532
|
+
// EXISTS, mirroring buildRelationCountExpr's EXISTS-on-target (which also
|
|
533
533
|
// skips the filter when the junction targetKey arity doesn't match the
|
|
534
534
|
// target PK). Rendered after the $1 key array.
|
|
535
535
|
let gf = null;
|
package/dist/query/builder.d.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Query builder
|
|
3
3
|
*
|
|
4
4
|
* Each table accessor (db.users, db.posts, etc.) returns a QueryInterface<T>
|
|
5
5
|
* that builds parameterized SQL and executes it through the connection pool.
|
|
6
6
|
*
|
|
7
7
|
* Nested relations use json_build_object + json_agg subqueries for single-query
|
|
8
|
-
* resolution
|
|
8
|
+
* resolution, a PostgreSQL-native approach that eliminates N+1 query patterns.
|
|
9
9
|
*
|
|
10
10
|
* Schema-driven: all column names, types, and relations come from introspected
|
|
11
|
-
* metadata
|
|
11
|
+
* metadata, nothing is hardcoded.
|
|
12
12
|
*/
|
|
13
13
|
import type pg from 'pg';
|
|
14
14
|
import type { SchemaMetadata } from '../schema.js';
|
|
@@ -103,6 +103,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
103
103
|
private readonly middlewares;
|
|
104
104
|
private readonly defaultLimit?;
|
|
105
105
|
private readonly warnOnUnlimited;
|
|
106
|
+
private readonly scopedConnect;
|
|
106
107
|
private readonly utcTimestamps;
|
|
107
108
|
private readonly preparedStatementsEnabled;
|
|
108
109
|
/**
|
|
@@ -150,7 +151,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
150
151
|
private readonly globalFilters?;
|
|
151
152
|
/**
|
|
152
153
|
* Tracks tables that have already triggered an unlimited-query warning so
|
|
153
|
-
* the user is not spammed once per row. Per-instance state
|
|
154
|
+
* the user is not spammed once per row. Per-instance state, each
|
|
154
155
|
* QueryInterface is bound to a single table, so this set will only ever
|
|
155
156
|
* contain at most one entry, but using a Set keeps the API consistent with
|
|
156
157
|
* the audit's "Set<string>" guidance and leaves room for future
|
|
@@ -165,7 +166,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
165
166
|
private readonly columnArrayTypeMap;
|
|
166
167
|
/**
|
|
167
168
|
* Columns whose type lives in a DIFFERENT schema than the introspected one
|
|
168
|
-
* (ColumnMetadata.pgTypeSchema is recorded only in that case)
|
|
169
|
+
* (ColumnMetadata.pgTypeSchema is recorded only in that case), such columns
|
|
169
170
|
* must never receive this schema's `::"enum"` cast (see enumTypeForColumn).
|
|
170
171
|
*/
|
|
171
172
|
private readonly crossSchemaTypeColumns;
|
|
@@ -179,7 +180,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
179
180
|
private readonly camelDateFieldCache;
|
|
180
181
|
/** True when this QI runs inside an active transaction (set via _txScoped option). */
|
|
181
182
|
private readonly txScoped;
|
|
182
|
-
/** Original options reference
|
|
183
|
+
/** Original options reference, forwarded to child QIs in nested writes. */
|
|
183
184
|
private readonly options?;
|
|
184
185
|
/** Set by executeWithMiddleware so queryWithTimeout can include it in events. */
|
|
185
186
|
private currentAction;
|
|
@@ -195,7 +196,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
195
196
|
/**
|
|
196
197
|
* The active query's `skipGlobalFilters` opt-out, set at the top of each
|
|
197
198
|
* `build*` method and read deep in the (synchronous) SQL-build + param-collect
|
|
198
|
-
* tree
|
|
199
|
+
* tree, so relation subqueries, relation filters, `_count`, and relation
|
|
199
200
|
* `orderBy` all see it without threading it through dozens of signatures.
|
|
200
201
|
* Only load-bearing when {@link globalFilters} is configured; build+collect are
|
|
201
202
|
* synchronous per call, so this transient is never observed across an await.
|
|
@@ -240,7 +241,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
240
241
|
* SQLite use ` LIMIT <ph>` and/or ` OFFSET <ph>`. SQL Server has no `LIMIT`, so
|
|
241
242
|
* its dialect implements {@link Dialect.buildLimitOffset} to emit
|
|
242
243
|
* `[ORDER BY (SELECT NULL)] OFFSET <off> ROWS [FETCH NEXT <lim> ROWS ONLY]`.
|
|
243
|
-
* Param-push order (limit before offset) is owned by the caller and unchanged
|
|
244
|
+
* Param-push order (limit before offset) is owned by the caller and unchanged -
|
|
244
245
|
* this only varies the SQL text, so PG output stays byte-identical.
|
|
245
246
|
*/
|
|
246
247
|
private buildPagination;
|
|
@@ -431,7 +432,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
431
432
|
* join for a to-one relation.
|
|
432
433
|
*
|
|
433
434
|
* Resolution order:
|
|
434
|
-
* 1. an explicit `autoToOneJoinMaxRows
|
|
435
|
+
* 1. an explicit `autoToOneJoinMaxRows`, an instruction, used verbatim
|
|
435
436
|
* (no clamping: the caller has measured their own workload);
|
|
436
437
|
* 2. the configured `autoRoundTripMs` divided by
|
|
437
438
|
* {@link AUTO_JOIN_PENALTY_MS_PER_ROW}, clamped to
|
|
@@ -445,7 +446,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
445
446
|
* put, so any single constant is wrong for someone by more than the margin it
|
|
446
447
|
* is trying to save. Placing the switch AT the break-even is also what removes
|
|
447
448
|
* the old cliff: two plans that cost the same at the boundary make the regret
|
|
448
|
-
* there ~1.0x, rising only as the true row count moves away from it
|
|
449
|
+
* there ~1.0x, rising only as the true row count moves away from it, where
|
|
449
450
|
* the previous fixed 1000 put its WORST case (1.44x measured) immediately
|
|
450
451
|
* below its own switch point.
|
|
451
452
|
*
|
|
@@ -492,7 +493,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
492
493
|
* Build the {@link RelationLoadContext} the batched loader needs, closing over
|
|
493
494
|
* this interface's pool/dialect/executor. Child readers are constructed on the
|
|
494
495
|
* SAME pool (so they join an active transaction) with `defaultLimit` cleared
|
|
495
|
-
* and unlimited-warnings silenced
|
|
496
|
+
* and unlimited-warnings silenced, a relation load must fetch every matching
|
|
496
497
|
* child, and the per-relation `limit` is applied client-side by the loader.
|
|
497
498
|
*/
|
|
498
499
|
private batchedContext;
|
|
@@ -582,7 +583,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
582
583
|
*
|
|
583
584
|
* - `'returning'` / `'output'`: the statement returns its own affected rows
|
|
584
585
|
* (`RETURNING *` / `OUTPUT INSERTED.*`). Byte-identical to the historical
|
|
585
|
-
* single `queryWithTimeout` + `transform(result)` path
|
|
586
|
+
* single `queryWithTimeout` + `transform(result)` path, the PostgreSQL
|
|
586
587
|
* route is unchanged.
|
|
587
588
|
* - `'reselect'`: the engine cannot return rows from a write, so the build
|
|
588
589
|
* method attached a {@link DeferredQuery.reselect} plan that runs the
|
|
@@ -612,7 +613,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
612
613
|
*
|
|
613
614
|
* Middleware can inspect and log query parameters, measure timing, and
|
|
614
615
|
* transform the result returned by `next()`. Note: query SQL is generated
|
|
615
|
-
* BEFORE middleware runs
|
|
616
|
+
* BEFORE middleware runs, `params.args` is a read-only snapshot, and
|
|
616
617
|
* mutating it does NOT change the executed SQL. Cross-cutting filters
|
|
617
618
|
* (e.g. soft deletes) belong in the query itself: pass an explicit
|
|
618
619
|
* `where: { deletedAt: null }` or wrap the table accessor in a small helper.
|
|
@@ -626,7 +627,7 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
626
627
|
* strategy's shape for the one row.
|
|
627
628
|
*/
|
|
628
629
|
private runFindUniqueBatched;
|
|
629
|
-
buildFindUnique<W extends TypedWithClause<R> = {}
|
|
630
|
+
buildFindUnique<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: FindUniqueArgs<T, R, W, S, O>): DeferredQuery<QueryResult<T, R, W, S, O> | null>;
|
|
630
631
|
findMany<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyArgs<T, R, W, S, O>): Promise<QueryResult<T, R, W, S, O>[]>;
|
|
631
632
|
/**
|
|
632
633
|
* Return the engine's query plan for a {@link findMany}-shaped query as plain
|
|
@@ -672,12 +673,29 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
672
673
|
* disabled in config).
|
|
673
674
|
*/
|
|
674
675
|
private maybeWarnUnlimited;
|
|
676
|
+
/**
|
|
677
|
+
* Whether `where` can match at most one row, because it pins every column of
|
|
678
|
+
* the primary key or of some unique column set to a literal value.
|
|
679
|
+
*
|
|
680
|
+
* The unlimited-read warning is about accidentally fetching a whole table,
|
|
681
|
+
* so firing it on `findMany({ where: { id: 1 } })` is noise: that query is
|
|
682
|
+
* bounded by a uniqueness constraint just as firmly as by a `limit`, and a
|
|
683
|
+
* warning that cries wolf on correct code trains people to disable it.
|
|
684
|
+
*
|
|
685
|
+
* Deliberately conservative. Only DIRECT equality on a literal counts: an
|
|
686
|
+
* operator object (`{ id: { in: [...] } }`, `{ id: { gt: 1 } }`) can match
|
|
687
|
+
* many rows, and any `OR` / `NOT` / relation filter can widen the result, so
|
|
688
|
+
* anything that is not a plain scalar equality leaves the warning in place.
|
|
689
|
+
* A compound-unique SELECTOR (`{ orgId_userId: {...} }`) is expanded first,
|
|
690
|
+
* so both spellings are recognized.
|
|
691
|
+
*/
|
|
692
|
+
private whereMatchesAtMostOneRow;
|
|
675
693
|
/**
|
|
676
694
|
* Recursively measure the maximum depth of a `with` clause tree.
|
|
677
695
|
* Used by the dev-only deep-with warning guard.
|
|
678
696
|
*/
|
|
679
697
|
private measureWithDepth;
|
|
680
|
-
buildFindMany<W extends TypedWithClause<R> = {}
|
|
698
|
+
buildFindMany<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyArgs<T, R, W, S, O>): DeferredQuery<QueryResult<T, R, W, S, O>[]>;
|
|
681
699
|
/**
|
|
682
700
|
* Stream rows from a findMany query using PostgreSQL cursors.
|
|
683
701
|
* Returns an AsyncIterable that yields individual rows, fetching in batches internally.
|
|
@@ -712,11 +730,11 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
712
730
|
*/
|
|
713
731
|
findManyStream<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyStreamArgs<T, R, W, S, O>): AsyncGenerator<QueryResult<T, R, W, S, O>, void, undefined>;
|
|
714
732
|
findFirst<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyArgs<T, R, W, S, O>): Promise<QueryResult<T, R, W, S, O> | null>;
|
|
715
|
-
buildFindFirst<W extends TypedWithClause<R> = {}
|
|
733
|
+
buildFindFirst<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyArgs<T, R, W, S, O>): DeferredQuery<QueryResult<T, R, W, S, O> | null>;
|
|
716
734
|
findFirstOrThrow<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyArgs<T, R, W, S, O>): Promise<QueryResult<T, R, W, S, O>>;
|
|
717
|
-
buildFindFirstOrThrow<W extends TypedWithClause<R> = {}
|
|
735
|
+
buildFindFirstOrThrow<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args?: FindManyArgs<T, R, W, S, O>): DeferredQuery<QueryResult<T, R, W, S, O>>;
|
|
718
736
|
findUniqueOrThrow<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: FindUniqueArgs<T, R, W, S, O>): Promise<QueryResult<T, R, W, S, O>>;
|
|
719
|
-
buildFindUniqueOrThrow<W extends TypedWithClause<R> = {}
|
|
737
|
+
buildFindUniqueOrThrow<W extends TypedWithClause<R> = {}, S extends Record<string, boolean> | undefined = undefined, O extends Record<string, boolean> | undefined = undefined>(args: FindUniqueArgs<T, R, W, S, O>): DeferredQuery<QueryResult<T, R, W, S, O>>;
|
|
720
738
|
create(args: CreateArgs<T, R>): Promise<T>;
|
|
721
739
|
createMany(args: CreateManyArgs<T>): Promise<T[]>;
|
|
722
740
|
update(args: UpdateArgs<T, R>): Promise<T>;
|
|
@@ -831,12 +849,12 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
831
849
|
/**
|
|
832
850
|
* Build ORDER BY clause from an object.
|
|
833
851
|
*
|
|
834
|
-
* Each value is either a plain direction (`'asc'`/`'desc'`) or
|
|
835
|
-
* columns
|
|
852
|
+
* Each value is either a plain direction (`'asc'`/`'desc'`) or, for pgvector
|
|
853
|
+
* columns, a `{ distance: { to, metric, direction? } }` KNN ordering object.
|
|
836
854
|
* Vector ordering binds the query vector as a `$n::vector` param, so a `params`
|
|
837
855
|
* array MUST be supplied when a vector ordering may be present (top-level
|
|
838
856
|
* findMany path). When `params` is omitted (groupBy / relation path) a vector
|
|
839
|
-
* ordering throws
|
|
857
|
+
* ordering throws, KNN ordering is only supported at the top level.
|
|
840
858
|
*/
|
|
841
859
|
/** Parse a flat row: convert snake_case to camelCase + Date coercion */
|
|
842
860
|
/**
|