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
package/dist/query/builder.js
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 { postgresDialect } from '../dialect.js';
|
|
14
14
|
import { NotFoundError, TimeoutError, UnsupportedFeatureError, ValidationError, wrapPgError } from '../errors.js';
|
|
@@ -20,7 +20,7 @@ import { defaultProjectionFields, includeKeysForBatching, loadRelationsBatched,
|
|
|
20
20
|
import { expandCompoundUniqueWhere } from './compound-unique.js';
|
|
21
21
|
import { isJsonPathOrderBy, isOrderBySpec, isRelationPickOrderBy, isVectorOrderBy, isWhereOperator, orderByEntries, sortedEntries, } from './filters.js';
|
|
22
22
|
import * as relationsMod from './relations.js';
|
|
23
|
-
import { LRUCache, ownLookup, parseDbDate, sqlToPreparedName } from './utils.js';
|
|
23
|
+
import { LRUCache, ownLookup, parseDbDate, sqlToPreparedName, unknownFieldMessage, } from './utils.js';
|
|
24
24
|
import { shouldWarnOnce, WARN_NS } from './warn-registry.js';
|
|
25
25
|
import * as whereMod from './where.js';
|
|
26
26
|
import * as writesMod from './writes.js';
|
|
@@ -239,7 +239,7 @@ function isEmptyOrderBy(orderBy) {
|
|
|
239
239
|
}
|
|
240
240
|
return orderBy === undefined || orderBy === null;
|
|
241
241
|
}
|
|
242
|
-
// biome-ignore lint/complexity/noBannedTypes: {} means "no relations known"
|
|
242
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no relations known", intentional for untyped table access
|
|
243
243
|
export class QueryInterface {
|
|
244
244
|
pool;
|
|
245
245
|
table;
|
|
@@ -263,6 +263,7 @@ export class QueryInterface {
|
|
|
263
263
|
middlewares;
|
|
264
264
|
defaultLimit;
|
|
265
265
|
warnOnUnlimited;
|
|
266
|
+
scopedConnect;
|
|
266
267
|
utcTimestamps;
|
|
267
268
|
preparedStatementsEnabled;
|
|
268
269
|
/**
|
|
@@ -310,7 +311,7 @@ export class QueryInterface {
|
|
|
310
311
|
globalFilters;
|
|
311
312
|
/**
|
|
312
313
|
* Tracks tables that have already triggered an unlimited-query warning so
|
|
313
|
-
* the user is not spammed once per row. Per-instance state
|
|
314
|
+
* the user is not spammed once per row. Per-instance state, each
|
|
314
315
|
* QueryInterface is bound to a single table, so this set will only ever
|
|
315
316
|
* contain at most one entry, but using a Set keeps the API consistent with
|
|
316
317
|
* the audit's "Set<string>" guidance and leaves room for future
|
|
@@ -325,7 +326,7 @@ export class QueryInterface {
|
|
|
325
326
|
columnArrayTypeMap;
|
|
326
327
|
/**
|
|
327
328
|
* Columns whose type lives in a DIFFERENT schema than the introspected one
|
|
328
|
-
* (ColumnMetadata.pgTypeSchema is recorded only in that case)
|
|
329
|
+
* (ColumnMetadata.pgTypeSchema is recorded only in that case), such columns
|
|
329
330
|
* must never receive this schema's `::"enum"` cast (see enumTypeForColumn).
|
|
330
331
|
*/
|
|
331
332
|
crossSchemaTypeColumns;
|
|
@@ -339,7 +340,7 @@ export class QueryInterface {
|
|
|
339
340
|
camelDateFieldCache = new Map();
|
|
340
341
|
/** True when this QI runs inside an active transaction (set via _txScoped option). */
|
|
341
342
|
txScoped;
|
|
342
|
-
/** Original options reference
|
|
343
|
+
/** Original options reference, forwarded to child QIs in nested writes. */
|
|
343
344
|
options;
|
|
344
345
|
/** Set by executeWithMiddleware so queryWithTimeout can include it in events. */
|
|
345
346
|
currentAction = 'raw';
|
|
@@ -355,7 +356,7 @@ export class QueryInterface {
|
|
|
355
356
|
/**
|
|
356
357
|
* The active query's `skipGlobalFilters` opt-out, set at the top of each
|
|
357
358
|
* `build*` method and read deep in the (synchronous) SQL-build + param-collect
|
|
358
|
-
* tree
|
|
359
|
+
* tree, so relation subqueries, relation filters, `_count`, and relation
|
|
359
360
|
* `orderBy` all see it without threading it through dozens of signatures.
|
|
360
361
|
* Only load-bearing when {@link globalFilters} is configured; build+collect are
|
|
361
362
|
* synchronous per call, so this transient is never observed across an await.
|
|
@@ -399,8 +400,8 @@ export class QueryInterface {
|
|
|
399
400
|
// `warnOnUnlimited: false`, per table with `warnOnUnlimited: { users:
|
|
400
401
|
// false }` (unlisted tables keep the default), or per call via
|
|
401
402
|
// `findMany({ warnOnUnlimited: false })`.
|
|
402
|
-
// Per-table maps accept BOTH key forms
|
|
403
|
-
// (`user_profiles`) and the camelCase accessor (`userProfiles`)
|
|
403
|
+
// Per-table maps accept BOTH key forms, the snake_case table name
|
|
404
|
+
// (`user_profiles`) and the camelCase accessor (`userProfiles`), since
|
|
404
405
|
// users naturally key by the accessor they type everywhere else. The
|
|
405
406
|
// snake_case entry wins when both are present.
|
|
406
407
|
const warnOpt = options?.warnOnUnlimited;
|
|
@@ -408,6 +409,7 @@ export class QueryInterface {
|
|
|
408
409
|
typeof warnOpt === 'object' && warnOpt !== null
|
|
409
410
|
? (warnOpt[table] ?? warnOpt[snakeToCamel(table)]) !== false
|
|
410
411
|
: warnOpt !== false;
|
|
412
|
+
this.scopedConnect = options?.scopedConnect === true;
|
|
411
413
|
this.utcTimestamps = options?.utcTimestamps !== false;
|
|
412
414
|
this.preparedStatementsEnabled = options?.preparedStatements ?? true;
|
|
413
415
|
// SQL template cache capacity. `sqlCacheSize: 0` disables caching entirely
|
|
@@ -518,7 +520,7 @@ export class QueryInterface {
|
|
|
518
520
|
* SQLite use ` LIMIT <ph>` and/or ` OFFSET <ph>`. SQL Server has no `LIMIT`, so
|
|
519
521
|
* its dialect implements {@link Dialect.buildLimitOffset} to emit
|
|
520
522
|
* `[ORDER BY (SELECT NULL)] OFFSET <off> ROWS [FETCH NEXT <lim> ROWS ONLY]`.
|
|
521
|
-
* Param-push order (limit before offset) is owned by the caller and unchanged
|
|
523
|
+
* Param-push order (limit before offset) is owned by the caller and unchanged -
|
|
522
524
|
* this only varies the SQL text, so PG output stays byte-identical.
|
|
523
525
|
*/
|
|
524
526
|
buildPagination(limitPh, offsetPh, hasOrderBy) {
|
|
@@ -948,7 +950,7 @@ export class QueryInterface {
|
|
|
948
950
|
* join for a to-one relation.
|
|
949
951
|
*
|
|
950
952
|
* Resolution order:
|
|
951
|
-
* 1. an explicit `autoToOneJoinMaxRows
|
|
953
|
+
* 1. an explicit `autoToOneJoinMaxRows`, an instruction, used verbatim
|
|
952
954
|
* (no clamping: the caller has measured their own workload);
|
|
953
955
|
* 2. the configured `autoRoundTripMs` divided by
|
|
954
956
|
* {@link AUTO_JOIN_PENALTY_MS_PER_ROW}, clamped to
|
|
@@ -962,7 +964,7 @@ export class QueryInterface {
|
|
|
962
964
|
* put, so any single constant is wrong for someone by more than the margin it
|
|
963
965
|
* is trying to save. Placing the switch AT the break-even is also what removes
|
|
964
966
|
* the old cliff: two plans that cost the same at the boundary make the regret
|
|
965
|
-
* there ~1.0x, rising only as the true row count moves away from it
|
|
967
|
+
* there ~1.0x, rising only as the true row count moves away from it, where
|
|
966
968
|
* the previous fixed 1000 put its WORST case (1.44x measured) immediately
|
|
967
969
|
* below its own switch point.
|
|
968
970
|
*
|
|
@@ -1126,7 +1128,7 @@ export class QueryInterface {
|
|
|
1126
1128
|
* Build the {@link RelationLoadContext} the batched loader needs, closing over
|
|
1127
1129
|
* this interface's pool/dialect/executor. Child readers are constructed on the
|
|
1128
1130
|
* SAME pool (so they join an active transaction) with `defaultLimit` cleared
|
|
1129
|
-
* and unlimited-warnings silenced
|
|
1131
|
+
* and unlimited-warnings silenced, a relation load must fetch every matching
|
|
1130
1132
|
* child, and the per-relation `limit` is applied client-side by the loader.
|
|
1131
1133
|
*/
|
|
1132
1134
|
batchedContext(timeout, skip, includePii) {
|
|
@@ -1358,7 +1360,7 @@ export class QueryInterface {
|
|
|
1358
1360
|
async queryWithTimeout(sql, params, timeout, preparedName) {
|
|
1359
1361
|
const start = performance.now();
|
|
1360
1362
|
const action = this.currentAction;
|
|
1361
|
-
// Build the query argument
|
|
1363
|
+
// Build the query argument, use object form with `name` for prepared
|
|
1362
1364
|
// statements, or the plain (text, values) form otherwise.
|
|
1363
1365
|
const usePrepared = preparedName && this.preparedStatementsEnabled;
|
|
1364
1366
|
const exec = usePrepared
|
|
@@ -1401,7 +1403,7 @@ export class QueryInterface {
|
|
|
1401
1403
|
*
|
|
1402
1404
|
* - `'returning'` / `'output'`: the statement returns its own affected rows
|
|
1403
1405
|
* (`RETURNING *` / `OUTPUT INSERTED.*`). Byte-identical to the historical
|
|
1404
|
-
* single `queryWithTimeout` + `transform(result)` path
|
|
1406
|
+
* single `queryWithTimeout` + `transform(result)` path, the PostgreSQL
|
|
1405
1407
|
* route is unchanged.
|
|
1406
1408
|
* - `'reselect'`: the engine cannot return rows from a write, so the build
|
|
1407
1409
|
* method attached a {@link DeferredQuery.reselect} plan that runs the
|
|
@@ -1455,7 +1457,7 @@ export class QueryInterface {
|
|
|
1455
1457
|
*
|
|
1456
1458
|
* Middleware can inspect and log query parameters, measure timing, and
|
|
1457
1459
|
* transform the result returned by `next()`. Note: query SQL is generated
|
|
1458
|
-
* BEFORE middleware runs
|
|
1460
|
+
* BEFORE middleware runs, `params.args` is a read-only snapshot, and
|
|
1459
1461
|
* mutating it does NOT change the executed SQL. Cross-cutting filters
|
|
1460
1462
|
* (e.g. soft deletes) belong in the query itself: pass an explicit
|
|
1461
1463
|
* `where: { deletedAt: null }` or wrap the table accessor in a small helper.
|
|
@@ -1473,7 +1475,7 @@ export class QueryInterface {
|
|
|
1473
1475
|
const mw = this.middlewares[index++];
|
|
1474
1476
|
return mw(p, next);
|
|
1475
1477
|
}
|
|
1476
|
-
// End of chain
|
|
1478
|
+
// End of chain, execute the actual query
|
|
1477
1479
|
return executor();
|
|
1478
1480
|
};
|
|
1479
1481
|
return next(params);
|
|
@@ -1525,7 +1527,7 @@ export class QueryInterface {
|
|
|
1525
1527
|
stripFields([entity], proj.strip);
|
|
1526
1528
|
return entity;
|
|
1527
1529
|
}
|
|
1528
|
-
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause"
|
|
1530
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause", matches TypedWithClause default
|
|
1529
1531
|
buildFindUnique(args) {
|
|
1530
1532
|
this.currentSkip = args.skipGlobalFilters;
|
|
1531
1533
|
// Prisma compound-unique selector expansion (before global-filter merge and
|
|
@@ -1559,7 +1561,7 @@ export class QueryInterface {
|
|
|
1559
1561
|
const ck = `fu:${whereFingerprint}|c=${colKey}|w=${withFp}|pii=${includePii ? 1 : 0}${this.globalFilterCacheSegment()}`;
|
|
1560
1562
|
const params = [];
|
|
1561
1563
|
// Check if all where values are simple (plain equality, no operators/null/OR).
|
|
1562
|
-
// Keys are sorted to match fingerprintWhere
|
|
1564
|
+
// Keys are sorted to match fingerprintWhere, insertion order here would let
|
|
1563
1565
|
// permuted where literals share a cache entry with misaligned params.
|
|
1564
1566
|
const whereKeys = Object.keys(whereObj)
|
|
1565
1567
|
.filter((k) => whereObj[k] !== undefined)
|
|
@@ -1663,7 +1665,7 @@ export class QueryInterface {
|
|
|
1663
1665
|
if (args?.with) {
|
|
1664
1666
|
const depth = this.measureWithDepth(args.with);
|
|
1665
1667
|
if (depth > 5 && shouldWarnOnce(WARN_NS.deepWith, this.table)) {
|
|
1666
|
-
console.warn(`[turbine] Deep with clause (depth ${depth}) on "${this.tableMeta.name}"
|
|
1668
|
+
console.warn(`[turbine] Deep with clause (depth ${depth}) on "${this.tableMeta.name}", ` +
|
|
1667
1669
|
'consider splitting into separate queries for better performance.');
|
|
1668
1670
|
}
|
|
1669
1671
|
}
|
|
@@ -1763,12 +1765,53 @@ export class QueryInterface {
|
|
|
1763
1765
|
const hasExplicitLimit = args?.limit !== undefined || args?.take !== undefined || args?.cursor !== undefined;
|
|
1764
1766
|
if (hasExplicitLimit)
|
|
1765
1767
|
return;
|
|
1768
|
+
if (this.whereMatchesAtMostOneRow(args?.where))
|
|
1769
|
+
return;
|
|
1766
1770
|
if (this.warnedTables.has(this.table))
|
|
1767
1771
|
return;
|
|
1768
1772
|
this.warnedTables.add(this.table);
|
|
1769
1773
|
console.warn(`[turbine] warning: findMany on "${this.table}" has no limit: this will fetch every row. ` +
|
|
1770
1774
|
'Pass `limit`, or silence with `warnOnUnlimited: false` (per call, per table, or in config).');
|
|
1771
1775
|
}
|
|
1776
|
+
/**
|
|
1777
|
+
* Whether `where` can match at most one row, because it pins every column of
|
|
1778
|
+
* the primary key or of some unique column set to a literal value.
|
|
1779
|
+
*
|
|
1780
|
+
* The unlimited-read warning is about accidentally fetching a whole table,
|
|
1781
|
+
* so firing it on `findMany({ where: { id: 1 } })` is noise: that query is
|
|
1782
|
+
* bounded by a uniqueness constraint just as firmly as by a `limit`, and a
|
|
1783
|
+
* warning that cries wolf on correct code trains people to disable it.
|
|
1784
|
+
*
|
|
1785
|
+
* Deliberately conservative. Only DIRECT equality on a literal counts: an
|
|
1786
|
+
* operator object (`{ id: { in: [...] } }`, `{ id: { gt: 1 } }`) can match
|
|
1787
|
+
* many rows, and any `OR` / `NOT` / relation filter can widen the result, so
|
|
1788
|
+
* anything that is not a plain scalar equality leaves the warning in place.
|
|
1789
|
+
* A compound-unique SELECTOR (`{ orgId_userId: {...} }`) is expanded first,
|
|
1790
|
+
* so both spellings are recognized.
|
|
1791
|
+
*/
|
|
1792
|
+
whereMatchesAtMostOneRow(where) {
|
|
1793
|
+
if (where === null || typeof where !== 'object' || Array.isArray(where))
|
|
1794
|
+
return false;
|
|
1795
|
+
const expanded = expandCompoundUniqueWhere(this.tableMeta, where);
|
|
1796
|
+
const pinned = new Set();
|
|
1797
|
+
for (const [field, value] of Object.entries(expanded)) {
|
|
1798
|
+
if (value === undefined)
|
|
1799
|
+
continue;
|
|
1800
|
+
// A plain scalar (or a Date) is an equality. `null` is NOT: `col IS NULL`
|
|
1801
|
+
// is not a uniqueness match, since SQL uniqueness permits many nulls.
|
|
1802
|
+
const isScalarEquality = value !== null && (typeof value !== 'object' || value instanceof Date) && typeof value !== 'function';
|
|
1803
|
+
if (!isScalarEquality)
|
|
1804
|
+
return false;
|
|
1805
|
+
const column = ownLookup(this.tableMeta.columnMap, field);
|
|
1806
|
+
if (!column)
|
|
1807
|
+
return false;
|
|
1808
|
+
pinned.add(column);
|
|
1809
|
+
}
|
|
1810
|
+
if (pinned.size === 0)
|
|
1811
|
+
return false;
|
|
1812
|
+
const covers = (columns) => !!columns && columns.length > 0 && columns.every((c) => pinned.has(c));
|
|
1813
|
+
return covers(this.tableMeta.primaryKey) || (this.tableMeta.uniqueColumns ?? []).some(covers);
|
|
1814
|
+
}
|
|
1772
1815
|
/**
|
|
1773
1816
|
* Recursively measure the maximum depth of a `with` clause tree.
|
|
1774
1817
|
* Used by the dev-only deep-with warning guard.
|
|
@@ -1783,7 +1826,7 @@ export class QueryInterface {
|
|
|
1783
1826
|
}
|
|
1784
1827
|
return maxDepth;
|
|
1785
1828
|
}
|
|
1786
|
-
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause"
|
|
1829
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause", matches TypedWithClause default
|
|
1787
1830
|
buildFindMany(args) {
|
|
1788
1831
|
this.currentSkip = args?.skipGlobalFilters;
|
|
1789
1832
|
// Stable relation order (opt-in): fill PK-asc orderBy into unordered to-many
|
|
@@ -1815,7 +1858,7 @@ export class QueryInterface {
|
|
|
1815
1858
|
// path re-orders in an outer wrapper (`... AS "<table>_distinct" ORDER BY
|
|
1816
1859
|
// <userOrder>`) where a correlated relation subquery (pick-row, `_count`,
|
|
1817
1860
|
// to-one relation ordering) would reference the parent table name out of
|
|
1818
|
-
// scope
|
|
1861
|
+
// scope, a guaranteed "missing FROM-clause entry" crash on Postgres.
|
|
1819
1862
|
// Checked BEFORE the SQL cache so build and warm-cache paths throw
|
|
1820
1863
|
// identically (same rule as the vector guard inside the distinct branch).
|
|
1821
1864
|
if (args?.distinct && args.distinct.length > 0 && args.orderBy) {
|
|
@@ -1874,7 +1917,7 @@ export class QueryInterface {
|
|
|
1874
1917
|
// own cache-key segment: a cached no-PII statement must never serve an
|
|
1875
1918
|
// `includePii` call, nor vice versa.
|
|
1876
1919
|
// `relationLoadStrategy: 'flatten'` compiles eligible to-one relations to
|
|
1877
|
-
// LEFT JOINs instead of correlated subqueries
|
|
1920
|
+
// LEFT JOINs instead of correlated subqueries, a completely different
|
|
1878
1921
|
// statement for the same `with` shape, which `withFp` (strategy-blind)
|
|
1879
1922
|
// does not distinguish. So the plan gets its own cache-key segment, exactly
|
|
1880
1923
|
// like `pii=`: a join-planned template must never serve a flatten-planned
|
|
@@ -1925,7 +1968,7 @@ export class QueryInterface {
|
|
|
1925
1968
|
// where → cursor order (the collect path mirrors this exactly).
|
|
1926
1969
|
let tail = freshWhereSql;
|
|
1927
1970
|
if (args?.cursor) {
|
|
1928
|
-
// Sorted (canonical) order
|
|
1971
|
+
// Sorted (canonical) order, MUST match cursorFp and the cache-hit collect below.
|
|
1929
1972
|
const cursorEntries = sortedEntries(args.cursor).filter(([, v]) => v !== undefined);
|
|
1930
1973
|
if (cursorEntries.length > 0) {
|
|
1931
1974
|
// Resolve the seek direction per cursor field from the flattened
|
|
@@ -1975,7 +2018,7 @@ export class QueryInterface {
|
|
|
1975
2018
|
: '';
|
|
1976
2019
|
sql = `SELECT ${distinctPrefix}${selectClause} FROM ${qt}${relationJoins.join('')}${lateralJoins.join('')}${tail}${orderBySql}`;
|
|
1977
2020
|
}
|
|
1978
|
-
// Pagination
|
|
2021
|
+
// Pagination, push params in the same order the collect path mirrors
|
|
1979
2022
|
// (limit before offset); the SQL TEXT shape is dialect-owned via
|
|
1980
2023
|
// buildPagination (PG: ` LIMIT $n`/` OFFSET $n`; SQL Server: OFFSET/FETCH).
|
|
1981
2024
|
let limitPh;
|
|
@@ -1999,7 +2042,7 @@ export class QueryInterface {
|
|
|
1999
2042
|
if (args?.with) {
|
|
2000
2043
|
this.collectWithParams(args.with, params, undefined, flattenPlan);
|
|
2001
2044
|
}
|
|
2002
|
-
// 3. Cursor params
|
|
2045
|
+
// 3. Cursor params, sorted (canonical) order, matching cursorFp and the build path.
|
|
2003
2046
|
if (args?.cursor) {
|
|
2004
2047
|
const cursorEntries = sortedEntries(args.cursor).filter(([, v]) => v !== undefined);
|
|
2005
2048
|
for (const [, v] of cursorEntries) {
|
|
@@ -2007,11 +2050,11 @@ export class QueryInterface {
|
|
|
2007
2050
|
}
|
|
2008
2051
|
}
|
|
2009
2052
|
// 4. ORDER BY params (vector KNN ordering binds a `$n::vector` query vector).
|
|
2010
|
-
// Mirrors buildOrderBy's push order
|
|
2053
|
+
// Mirrors buildOrderBy's push order, between cursor and LIMIT.
|
|
2011
2054
|
if (args?.orderBy) {
|
|
2012
2055
|
this.collectOrderByParams(args.orderBy, params);
|
|
2013
2056
|
}
|
|
2014
|
-
// 5. LIMIT param
|
|
2057
|
+
// 5. LIMIT param, skipped when the dialect inlines pagination (build path
|
|
2015
2058
|
// mirrors via paginationRef → no placeholder, no param).
|
|
2016
2059
|
if (effectiveLimit !== undefined && !this.dialect.inlineLimitOffset) {
|
|
2017
2060
|
// Validate here too: on a cache HIT the build path never runs, and a
|
|
@@ -2019,7 +2062,7 @@ export class QueryInterface {
|
|
|
2019
2062
|
// i.e. no limit at all).
|
|
2020
2063
|
params.push(this.paginationValue(effectiveLimit, 'limit'));
|
|
2021
2064
|
}
|
|
2022
|
-
// 6. OFFSET param
|
|
2065
|
+
// 6. OFFSET param, same inline gate as LIMIT above.
|
|
2023
2066
|
if (args?.offset !== undefined && !this.dialect.inlineLimitOffset) {
|
|
2024
2067
|
params.push(this.paginationValue(args.offset, 'skip/offset'));
|
|
2025
2068
|
}
|
|
@@ -2035,7 +2078,7 @@ export class QueryInterface {
|
|
|
2035
2078
|
};
|
|
2036
2079
|
}
|
|
2037
2080
|
// -------------------------------------------------------------------------
|
|
2038
|
-
// findManyStream
|
|
2081
|
+
// findManyStream, async iterable using PostgreSQL cursors
|
|
2039
2082
|
// -------------------------------------------------------------------------
|
|
2040
2083
|
/**
|
|
2041
2084
|
* Stream rows from a findMany query using PostgreSQL cursors.
|
|
@@ -2074,8 +2117,8 @@ export class QueryInterface {
|
|
|
2074
2117
|
const hasRelations = !!args?.with;
|
|
2075
2118
|
// Build the positional-aware relation parser once for the whole stream.
|
|
2076
2119
|
// Same flatten plan buildFindMany compiles below. The plan is a pure
|
|
2077
|
-
// function of the schema, the `with` shape and `includePii
|
|
2078
|
-
// `limit
|
|
2120
|
+
// function of the schema, the `with` shape and `includePii`, never of
|
|
2121
|
+
// `limit`, so the batch-size override the speculative fetch applies cannot
|
|
2079
2122
|
// change it, and the stream's parser matches the emitted SQL.
|
|
2080
2123
|
const streamFlattenPlan = hasRelations ? this.planFlatten(args, args?.includePii === true) : null;
|
|
2081
2124
|
const parseWith = hasRelations
|
|
@@ -2089,7 +2132,7 @@ export class QueryInterface {
|
|
|
2089
2132
|
this.currentAction = 'findManyStream';
|
|
2090
2133
|
const speculativeResult = await this.queryWithTimeout(speculativeDeferred.sql, speculativeDeferred.params, args?.timeout);
|
|
2091
2134
|
if (speculativeResult.rows.length <= batchSize) {
|
|
2092
|
-
// Small drain
|
|
2135
|
+
// Small drain, yield all rows and return, no cursor needed
|
|
2093
2136
|
for (const row of speculativeResult.rows) {
|
|
2094
2137
|
yield (parseWith ? parseWith(row) : this.parseRow(row, this.table));
|
|
2095
2138
|
}
|
|
@@ -2129,7 +2172,7 @@ export class QueryInterface {
|
|
|
2129
2172
|
}
|
|
2130
2173
|
}
|
|
2131
2174
|
// -------------------------------------------------------------------------
|
|
2132
|
-
// findFirst
|
|
2175
|
+
// findFirst, like findMany but returns a single row or null
|
|
2133
2176
|
// -------------------------------------------------------------------------
|
|
2134
2177
|
async findFirst(args) {
|
|
2135
2178
|
return this.executeWithMiddleware('findFirst', (args ?? {}), async () => {
|
|
@@ -2154,7 +2197,7 @@ export class QueryInterface {
|
|
|
2154
2197
|
return deferred.transform(result);
|
|
2155
2198
|
});
|
|
2156
2199
|
}
|
|
2157
|
-
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause"
|
|
2200
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause", matches TypedWithClause default
|
|
2158
2201
|
buildFindFirst(args) {
|
|
2159
2202
|
// Reuse findMany's SQL builder but force LIMIT 1
|
|
2160
2203
|
const findManyArgs = { ...args, limit: 1 };
|
|
@@ -2170,7 +2213,7 @@ export class QueryInterface {
|
|
|
2170
2213
|
};
|
|
2171
2214
|
}
|
|
2172
2215
|
// -------------------------------------------------------------------------
|
|
2173
|
-
// findFirstOrThrow
|
|
2216
|
+
// findFirstOrThrow, like findFirst but throws if no record found
|
|
2174
2217
|
// -------------------------------------------------------------------------
|
|
2175
2218
|
async findFirstOrThrow(args) {
|
|
2176
2219
|
return this.executeWithMiddleware('findFirstOrThrow', (args ?? {}), async () => {
|
|
@@ -2179,7 +2222,7 @@ export class QueryInterface {
|
|
|
2179
2222
|
return deferred.transform(result);
|
|
2180
2223
|
});
|
|
2181
2224
|
}
|
|
2182
|
-
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause"
|
|
2225
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause", matches TypedWithClause default
|
|
2183
2226
|
buildFindFirstOrThrow(args) {
|
|
2184
2227
|
const inner = this.buildFindFirst(args);
|
|
2185
2228
|
return {
|
|
@@ -2200,7 +2243,7 @@ export class QueryInterface {
|
|
|
2200
2243
|
};
|
|
2201
2244
|
}
|
|
2202
2245
|
// -------------------------------------------------------------------------
|
|
2203
|
-
// findUniqueOrThrow
|
|
2246
|
+
// findUniqueOrThrow, like findUnique but throws if no record found
|
|
2204
2247
|
// -------------------------------------------------------------------------
|
|
2205
2248
|
async findUniqueOrThrow(args) {
|
|
2206
2249
|
return this.executeWithMiddleware('findUniqueOrThrow', args, async () => {
|
|
@@ -2209,7 +2252,7 @@ export class QueryInterface {
|
|
|
2209
2252
|
return deferred.transform(result);
|
|
2210
2253
|
});
|
|
2211
2254
|
}
|
|
2212
|
-
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause"
|
|
2255
|
+
// biome-ignore lint/complexity/noBannedTypes: {} means "no with clause", matches TypedWithClause default
|
|
2213
2256
|
buildFindUniqueOrThrow(args) {
|
|
2214
2257
|
const inner = this.buildFindUnique(args);
|
|
2215
2258
|
return {
|
|
@@ -2242,7 +2285,7 @@ export class QueryInterface {
|
|
|
2242
2285
|
});
|
|
2243
2286
|
}
|
|
2244
2287
|
// -------------------------------------------------------------------------
|
|
2245
|
-
// createMany
|
|
2288
|
+
// createMany, uses UNNEST for performance
|
|
2246
2289
|
// -------------------------------------------------------------------------
|
|
2247
2290
|
async createMany(args) {
|
|
2248
2291
|
return this.executeWithMiddleware('createMany', args, async () => {
|
|
@@ -2308,7 +2351,7 @@ export class QueryInterface {
|
|
|
2308
2351
|
// implicit transaction.
|
|
2309
2352
|
this.pool);
|
|
2310
2353
|
// biome-ignore lint/suspicious/noExplicitAny: TransactionClient satisfies NestedWriteContext['tx'] at runtime
|
|
2311
|
-
const ctx = { schema: this.schema, tx: tx };
|
|
2354
|
+
const ctx = { schema: this.schema, tx: tx, scopedConnect: this.scopedConnect };
|
|
2312
2355
|
const result = await fn(ctx);
|
|
2313
2356
|
await client.query(this.dialect.commitStatement());
|
|
2314
2357
|
return result;
|
|
@@ -2356,7 +2399,7 @@ export class QueryInterface {
|
|
|
2356
2399
|
});
|
|
2357
2400
|
}
|
|
2358
2401
|
// -------------------------------------------------------------------------
|
|
2359
|
-
// upsert
|
|
2402
|
+
// upsert, INSERT ... ON CONFLICT ... DO UPDATE
|
|
2360
2403
|
// -------------------------------------------------------------------------
|
|
2361
2404
|
async upsert(args) {
|
|
2362
2405
|
return this.executeWithMiddleware('upsert', args, async () => {
|
|
@@ -2365,7 +2408,7 @@ export class QueryInterface {
|
|
|
2365
2408
|
});
|
|
2366
2409
|
}
|
|
2367
2410
|
// -------------------------------------------------------------------------
|
|
2368
|
-
// updateMany
|
|
2411
|
+
// updateMany, UPDATE ... WHERE ... returning count
|
|
2369
2412
|
// -------------------------------------------------------------------------
|
|
2370
2413
|
async updateMany(args) {
|
|
2371
2414
|
return this.executeWithMiddleware('updateMany', args, async () => {
|
|
@@ -2375,7 +2418,7 @@ export class QueryInterface {
|
|
|
2375
2418
|
});
|
|
2376
2419
|
}
|
|
2377
2420
|
// -------------------------------------------------------------------------
|
|
2378
|
-
// deleteMany
|
|
2421
|
+
// deleteMany, DELETE ... WHERE ... returning count
|
|
2379
2422
|
// -------------------------------------------------------------------------
|
|
2380
2423
|
async deleteMany(args) {
|
|
2381
2424
|
return this.executeWithMiddleware('deleteMany', args, async () => {
|
|
@@ -2448,7 +2491,7 @@ export class QueryInterface {
|
|
|
2448
2491
|
return aggMod.buildAggregate(this.ctx, args);
|
|
2449
2492
|
}
|
|
2450
2493
|
// -------------------------------------------------------------------------
|
|
2451
|
-
// aggregate
|
|
2494
|
+
// aggregate, standalone aggregation without groupBy
|
|
2452
2495
|
// -------------------------------------------------------------------------
|
|
2453
2496
|
async aggregate(args) {
|
|
2454
2497
|
return this.executeWithMiddleware('aggregate', args, async () => {
|
|
@@ -2584,7 +2627,7 @@ export class QueryInterface {
|
|
|
2584
2627
|
// Fall back to camelToSnake ONLY if that snake_cased name also exists as a
|
|
2585
2628
|
// real column on the table. This preserves the convenience of writing
|
|
2586
2629
|
// `userId` when the schema exposes `user_id` under an unusual field name,
|
|
2587
|
-
// but rejects arbitrary strings
|
|
2630
|
+
// but rejects arbitrary strings, closing the defense-in-depth gap for
|
|
2588
2631
|
// SQL injection and catching typos like `where: { emial: 'x' }` with a
|
|
2589
2632
|
// clear error instead of a cryptic Postgres "column does not exist".
|
|
2590
2633
|
const snake = camelToSnake(field);
|
|
@@ -2594,15 +2637,14 @@ export class QueryInterface {
|
|
|
2594
2637
|
if (this.tableMeta.allColumns?.includes(snake)) {
|
|
2595
2638
|
return snake;
|
|
2596
2639
|
}
|
|
2597
|
-
throw new ValidationError(
|
|
2598
|
-
`Known fields: ${Object.keys(this.tableMeta.columnMap).join(', ') || '(none)'}.`);
|
|
2640
|
+
throw new ValidationError(unknownFieldMessage(this.table, field, this.tableMeta));
|
|
2599
2641
|
}
|
|
2600
2642
|
/** Convert camelCase field name to a double-quoted SQL identifier */
|
|
2601
2643
|
toSqlColumn(field) {
|
|
2602
2644
|
return this.q(this.toColumn(field));
|
|
2603
2645
|
}
|
|
2604
2646
|
// =========================================================================
|
|
2605
|
-
// Fingerprinting
|
|
2647
|
+
// Fingerprinting, value-invariant shape keys for SQL cache lookup
|
|
2606
2648
|
// =========================================================================
|
|
2607
2649
|
// ---------------------------------------------------------------------------
|
|
2608
2650
|
// WHERE-clause compilation (extracted to where.ts).
|
|
@@ -2699,7 +2741,7 @@ export class QueryInterface {
|
|
|
2699
2741
|
}
|
|
2700
2742
|
}
|
|
2701
2743
|
// -------------------------------------------------------------------------
|
|
2702
|
-
// Global filters (soft-delete / multi-tenancy
|
|
2744
|
+
// Global filters (soft-delete / multi-tenancy, WS-G)
|
|
2703
2745
|
//
|
|
2704
2746
|
// A configured global filter for a table is AND-merged into the compiled WHERE
|
|
2705
2747
|
// of every query on that table (via {@link mergeGlobalFilter}, so the merge is
|
|
@@ -2707,12 +2749,12 @@ export class QueryInterface {
|
|
|
2707
2749
|
// subquery targeting it (rendered at build time against the subquery's alias/
|
|
2708
2750
|
// table by the `*GlobalFilterAlias`/`*GlobalFilterExists` helpers, with the
|
|
2709
2751
|
// shape folded into the SQL-cache key via {@link globalFilterCacheSegment}).
|
|
2710
|
-
// Function filters are evaluated per resolve
|
|
2752
|
+
// Function filters are evaluated per resolve, at query-build time, enabling
|
|
2711
2753
|
// per-request tenancy via a closure. They must return a STABLE shape (same
|
|
2712
2754
|
// keys/operators); only values may vary between calls.
|
|
2713
2755
|
// -------------------------------------------------------------------------
|
|
2714
2756
|
// -------------------------------------------------------------------------
|
|
2715
|
-
// Scoped (non-top-level) WHERE compilation
|
|
2757
|
+
// Scoped (non-top-level) WHERE compilation, relation EXISTS sub-wheres and
|
|
2716
2758
|
// relation `with`-clause `where`s. All three consumers below drive the SAME
|
|
2717
2759
|
// canonical `walkWhere` the top level uses (bound to the SCOPE's target
|
|
2718
2760
|
// table), so their key order + combinator structure + relation detection can
|
|
@@ -2728,12 +2770,12 @@ export class QueryInterface {
|
|
|
2728
2770
|
/**
|
|
2729
2771
|
* Build ORDER BY clause from an object.
|
|
2730
2772
|
*
|
|
2731
|
-
* Each value is either a plain direction (`'asc'`/`'desc'`) or
|
|
2732
|
-
* columns
|
|
2773
|
+
* Each value is either a plain direction (`'asc'`/`'desc'`) or, for pgvector
|
|
2774
|
+
* columns, a `{ distance: { to, metric, direction? } }` KNN ordering object.
|
|
2733
2775
|
* Vector ordering binds the query vector as a `$n::vector` param, so a `params`
|
|
2734
2776
|
* array MUST be supplied when a vector ordering may be present (top-level
|
|
2735
2777
|
* findMany path). When `params` is omitted (groupBy / relation path) a vector
|
|
2736
|
-
* ordering throws
|
|
2778
|
+
* ordering throws, KNN ordering is only supported at the top level.
|
|
2737
2779
|
*/
|
|
2738
2780
|
// -------------------------------------------------------------------------
|
|
2739
2781
|
// pgvector helpers (similarity search)
|
|
@@ -2766,7 +2808,7 @@ export class QueryInterface {
|
|
|
2766
2808
|
// columns (`date[]`, `timestamp[]`, `timestamptz[]`), for which the
|
|
2767
2809
|
// driver already hands back a `Date[]`. Coercing it ran
|
|
2768
2810
|
// `new Date(String(theArray))` and replaced the whole array with a
|
|
2769
|
-
// single Invalid Date
|
|
2811
|
+
// single Invalid Date, the column was unreadable on every strategy.
|
|
2770
2812
|
// The join strategy's string arrays are handled upstream instead, by
|
|
2771
2813
|
// the JSON-wire decode in relations.ts.
|
|
2772
2814
|
if ((dateCols.has(col) || camelDateFields.has(field)) &&
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Prisma-style compound-unique `where` selectors.
|
|
3
3
|
*
|
|
4
4
|
* Prisma lets a `findUnique`-family `where` address a multi-column unique
|
|
5
5
|
* constraint through a single synthetic key holding the member columns:
|
|
Binary file
|
package/dist/query/deferred.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* turbine-orm
|
|
2
|
+
* turbine-orm, Deferred query + QueryInterface option types
|
|
3
3
|
*
|
|
4
4
|
* Split from builder.ts so the class file focuses on SQL assembly / execution.
|
|
5
5
|
*/
|
|
@@ -27,7 +27,7 @@ export interface DeferredQuery<T> {
|
|
|
27
27
|
preparedName?: string;
|
|
28
28
|
/**
|
|
29
29
|
* Execution plan for dialects whose {@link Dialect.resultStrategy} is
|
|
30
|
-
* `'reselect'` (no RETURNING
|
|
30
|
+
* `'reselect'` (no RETURNING, e.g. MySQL). Owns the statement ordering: it
|
|
31
31
|
* runs the write and the follow-up row-fetching SELECT(s) via `exec`, and
|
|
32
32
|
* resolves the result whose rows {@link DeferredQuery.transform} consumes.
|
|
33
33
|
* Absent for `'returning'`/`'output'` dialects (the statement returns its own
|
|
@@ -35,7 +35,7 @@ export interface DeferredQuery<T> {
|
|
|
35
35
|
*/
|
|
36
36
|
reselect?: (exec: ReselectExecutor) => Promise<pg.QueryResult>;
|
|
37
37
|
}
|
|
38
|
-
/** Middleware function type
|
|
38
|
+
/** Middleware function type, imported from client to avoid circular deps */
|
|
39
39
|
export type MiddlewareFn = (params: {
|
|
40
40
|
model: string;
|
|
41
41
|
action: string;
|
|
@@ -75,13 +75,19 @@ export interface QueryInterfaceOptions {
|
|
|
75
75
|
* queries are surfaced loudly during development. Pass `false` to silence
|
|
76
76
|
* the warning entirely (e.g. for CLI tooling that intentionally streams
|
|
77
77
|
* full tables), or a per-table map (`{ userProfiles: false }`) to silence
|
|
78
|
-
* only the tables that intentionally read full sets
|
|
78
|
+
* only the tables that intentionally read full sets, unlisted tables keep
|
|
79
79
|
* the default. Map keys accept BOTH the camelCase accessor name
|
|
80
80
|
* (`userProfiles`) and the snake_case table name (`user_profiles`); the
|
|
81
81
|
* snake_case entry wins if both are present. Individual calls can also
|
|
82
82
|
* override via `findMany({ warnOnUnlimited: false })`.
|
|
83
83
|
*/
|
|
84
84
|
warnOnUnlimited?: boolean | Record<string, boolean>;
|
|
85
|
+
/**
|
|
86
|
+
* Refuse a nested `connect` / `connectOrCreate` that would re-parent a
|
|
87
|
+
* to-many child already owned by a different parent. Off by default; see
|
|
88
|
+
* {@link import('../nested-write.js').NestedWriteContext.scopedConnect}.
|
|
89
|
+
*/
|
|
90
|
+
scopedConnect?: boolean;
|
|
85
91
|
/**
|
|
86
92
|
* Enable prepared statements. When true, queries are submitted with a
|
|
87
93
|
* `{ name, text, values }` object to the pg driver, which caches the
|
|
@@ -196,7 +202,7 @@ export interface QueryInterfaceOptions {
|
|
|
196
202
|
autoRoundTripMs?: number;
|
|
197
203
|
/**
|
|
198
204
|
* How nested-relation subqueries encode each row's JSON: `'object'` (default,
|
|
199
|
-
* `json_build_object`) or `'positional'` (`json_build_array`, key-less
|
|
205
|
+
* `json_build_object`) or `'positional'` (`json_build_array`, key-less, see
|
|
200
206
|
* {@link Dialect.buildJsonArray}). Positional is Postgres-only in v1; a
|
|
201
207
|
* `with` clause on any other dialect throws `UnsupportedFeatureError` (E017).
|
|
202
208
|
*/
|
|
@@ -208,7 +214,7 @@ export interface QueryInterfaceOptions {
|
|
|
208
214
|
* {@link GlobalFilters}.
|
|
209
215
|
*/
|
|
210
216
|
globalFilters?: GlobalFilters;
|
|
211
|
-
/** @internal Set by TransactionClient
|
|
217
|
+
/** @internal Set by TransactionClient, signals that this QI runs inside an active transaction. */
|
|
212
218
|
_txScoped?: boolean;
|
|
213
219
|
/** @internal Callback from TurbineClient for query event emission. */
|
|
214
220
|
_onQuery?: (event: QueryEvent) => void;
|
package/dist/query/deferred.js
CHANGED