turbine-orm 0.55.0 → 0.57.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 +16 -1
- package/dist/cjs/cli/index.d.ts +3 -1
- package/dist/cjs/cli/index.js +341 -14
- package/dist/cjs/client.d.ts +16 -1
- package/dist/cjs/client.js +7 -39
- package/dist/cjs/dialect.d.ts +9 -0
- package/dist/cjs/index-stats.d.ts +46 -0
- package/dist/cjs/index-stats.js +42 -1
- package/dist/cjs/plan-divergence.d.ts +511 -0
- package/dist/cjs/plan-divergence.js +790 -0
- package/dist/cjs/powql.d.ts +11 -0
- package/dist/cjs/powql.js +22 -0
- package/dist/cjs/prisma-compat.d.ts +32 -1
- package/dist/cjs/prisma-compat.js +297 -41
- package/dist/cjs/query/builder.d.ts +45 -0
- package/dist/cjs/query/builder.js +90 -17
- package/dist/cjs/query/deferred.d.ts +9 -0
- package/dist/cjs/query/index.d.ts +2 -0
- package/dist/cjs/query/index.js +18 -1
- package/dist/cjs/query/option-surface.d.ts +100 -0
- package/dist/cjs/query/option-surface.js +214 -0
- package/dist/cjs/query/types.d.ts +140 -0
- package/dist/cjs/query/utils.d.ts +30 -0
- package/dist/cjs/query/utils.js +67 -3
- package/dist/cjs/query/warn-registry.d.ts +8 -0
- package/dist/cjs/query/warn-registry.js +8 -0
- package/dist/cli/index.d.ts +3 -1
- package/dist/cli/index.js +341 -14
- package/dist/client.d.ts +16 -1
- package/dist/client.js +8 -40
- package/dist/dialect.d.ts +9 -0
- package/dist/index-stats.d.ts +46 -0
- package/dist/index-stats.js +42 -1
- package/dist/plan-divergence.d.ts +511 -0
- package/dist/plan-divergence.js +783 -0
- package/dist/powql.d.ts +11 -0
- package/dist/powql.js +22 -0
- package/dist/prisma-compat.d.ts +32 -1
- package/dist/prisma-compat.js +297 -41
- package/dist/query/builder.d.ts +45 -0
- package/dist/query/builder.js +90 -17
- package/dist/query/deferred.d.ts +9 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +1 -0
- package/dist/query/option-surface.d.ts +100 -0
- package/dist/query/option-surface.js +209 -0
- package/dist/query/types.d.ts +140 -0
- package/dist/query/utils.d.ts +30 -0
- package/dist/query/utils.js +66 -3
- package/dist/query/warn-registry.d.ts +8 -0
- package/dist/query/warn-registry.js +8 -0
- package/package.json +1 -1
package/dist/query/builder.d.ts
CHANGED
|
@@ -712,6 +712,51 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
712
712
|
*/
|
|
713
713
|
resetUnlimitedWarnings(): void;
|
|
714
714
|
private emitQueryEvent;
|
|
715
|
+
/**
|
|
716
|
+
* Resolve the prepared-statement name a read should execute under, honouring
|
|
717
|
+
* the per-query {@link FindManyArgs.forceCustomPlan} opt-in.
|
|
718
|
+
*
|
|
719
|
+
* `forceCustomPlan: true` returns `undefined`, which sends the statement
|
|
720
|
+
* UNNAMED. The mechanism is NOT "PostgreSQL treats an unnamed statement as a
|
|
721
|
+
* one-shot plan that never enters the plan cache": the backend builds and
|
|
722
|
+
* saves a `CachedPlanSource` for the unnamed statement too. It works because
|
|
723
|
+
* node-postgres only skips Parse for a statement it has already parsed BY
|
|
724
|
+
* NAME (`Query.hasBeenParsed` is `this.name && connection.parsedStatements[this.name]`),
|
|
725
|
+
* so an unnamed statement is re-Parsed on every execution, each Parse
|
|
726
|
+
* replaces the unnamed cached plan source with a fresh one whose custom-plan
|
|
727
|
+
* counter is zero, and the five-execution threshold that precedes promotion
|
|
728
|
+
* is never reached. Every execution is therefore planned with the real
|
|
729
|
+
* parameter values.
|
|
730
|
+
*
|
|
731
|
+
* No GUC is set, no `SET LOCAL` is emitted, no transaction is opened, and no
|
|
732
|
+
* extra round trip is added, which is exactly why the opt-in can be per query
|
|
733
|
+
* while the client-level `planCacheMode` (a connection parameter) cannot be.
|
|
734
|
+
*
|
|
735
|
+
* The refusal is deliberately here, at the one seam every read execution
|
|
736
|
+
* passes through, rather than in each build method: the flag changes NOTHING
|
|
737
|
+
* about the SQL text, so a build-time check would have had to be repeated in
|
|
738
|
+
* every builder and could still be bypassed by a hand-executed
|
|
739
|
+
* `DeferredQuery`.
|
|
740
|
+
*
|
|
741
|
+
* Engines whose dialect does not report {@link Dialect.supportsPlanCacheMode}
|
|
742
|
+
* throw {@link UnsupportedFeatureError} (E017): the flag names a PostgreSQL
|
|
743
|
+
* plan-cache guarantee, and an engine with no such cache cannot make it.
|
|
744
|
+
* The same flag left unset (or `false`) is accepted everywhere.
|
|
745
|
+
*
|
|
746
|
+
* THE ONE COMBINATION THAT IS REFUSED RATHER THAN HONOURED. A client-level
|
|
747
|
+
* `planCacheMode: 'force_generic_plan'` DEFEATS this option, and that was
|
|
748
|
+
* MEASURED rather than reasoned about: on PostgreSQL 16.14, five executions
|
|
749
|
+
* of one unnamed statement read 19,107 buffers with that setting in force and
|
|
750
|
+
* 55 buffers with the same connection set back to `auto`, against 19,107 for
|
|
751
|
+
* the named statement. So the setting governs the unnamed statement too, and
|
|
752
|
+
* withholding the name buys nothing against it. Accepting the flag there
|
|
753
|
+
* would report a guarantee the very next execution breaks, so the
|
|
754
|
+
* contradiction throws {@link ValidationError} (E003) naming both settings.
|
|
755
|
+
* Turbine can only see the setting IT applied: a `plan_cache_mode` installed
|
|
756
|
+
* by the caller's own `SET`, by `ALTER ROLE`, or by a pooler is invisible
|
|
757
|
+
* here and is not refused.
|
|
758
|
+
*/
|
|
759
|
+
private preparedNameFor;
|
|
715
760
|
/**
|
|
716
761
|
* Execute a pool.query with an optional timeout.
|
|
717
762
|
* If timeout is set, races the query against a timer and rejects on expiry.
|
package/dist/query/builder.js
CHANGED
|
@@ -1352,11 +1352,11 @@ export class QueryInterface {
|
|
|
1352
1352
|
const deferred = single
|
|
1353
1353
|
? this.buildFindUnique(baseArgs)
|
|
1354
1354
|
: this.buildFindMany(baseArgs);
|
|
1355
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, deferred.preparedName);
|
|
1355
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
1356
1356
|
const rows = deferred.transform(result);
|
|
1357
1357
|
const entities = single ? (rows ? [rows] : []) : rows;
|
|
1358
1358
|
if (entities.length > 0) {
|
|
1359
|
-
await loadRelationsBatched(this.batchedContext(args.timeout, skip, args.includePii === true), entities, batchedWith, args.timeout);
|
|
1359
|
+
await loadRelationsBatched(this.batchedContext(args.timeout, skip, args.includePii === true, args.forceCustomPlan === true), entities, batchedWith, args.timeout);
|
|
1360
1360
|
}
|
|
1361
1361
|
stripFields(entities, proj.strip);
|
|
1362
1362
|
return single ? (entities[0] ?? null) : entities;
|
|
@@ -1372,7 +1372,7 @@ export class QueryInterface {
|
|
|
1372
1372
|
* and unlimited-warnings silenced, a relation load must fetch every matching
|
|
1373
1373
|
* child, and the per-relation `limit` is applied client-side by the loader.
|
|
1374
1374
|
*/
|
|
1375
|
-
batchedContext(timeout, skip, includePii) {
|
|
1375
|
+
batchedContext(timeout, skip, includePii, forceCustomPlan = false) {
|
|
1376
1376
|
const childOptions = {
|
|
1377
1377
|
...this.options,
|
|
1378
1378
|
defaultLimit: undefined,
|
|
@@ -1382,7 +1382,11 @@ export class QueryInterface {
|
|
|
1382
1382
|
parentMeta: this.tableMeta,
|
|
1383
1383
|
schema: this.schema,
|
|
1384
1384
|
makeChild: (table) => new QueryInterface(this.pool, table, this.schema, [], childOptions),
|
|
1385
|
-
|
|
1385
|
+
// The per-query `forceCustomPlan` opt-in covers the relation follow-ups
|
|
1386
|
+
// too: a batched load re-issues the SAME tenant-shaped predicate one
|
|
1387
|
+
// level down, so leaving those named would keep exactly the plan-cache
|
|
1388
|
+
// exposure the caller asked to be rid of.
|
|
1389
|
+
exec: (sql, params, preparedName) => this.queryWithTimeout(sql, params, timeout, this.preparedNameFor({ forceCustomPlan }, preparedName)),
|
|
1386
1390
|
quote: (name) => this.q(name),
|
|
1387
1391
|
buildInClause: (expr, paramRef, negated) => this.inClause(expr, paramRef, negated),
|
|
1388
1392
|
inClauseParam: (values) => this.inParam(values),
|
|
@@ -1434,10 +1438,10 @@ export class QueryInterface {
|
|
|
1434
1438
|
const { baseArgs, strip } = this.prepareBatchedBase(args, withClause);
|
|
1435
1439
|
// baseArgs.with is always undefined here; the cast just bridges the R generic.
|
|
1436
1440
|
const deferred = this.buildFindMany(baseArgs);
|
|
1437
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, deferred.preparedName);
|
|
1441
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
1438
1442
|
const entities = deferred.transform(result);
|
|
1439
1443
|
if (entities.length > 0) {
|
|
1440
|
-
await loadRelationsBatched(this.batchedContext(args.timeout, skip, args.includePii === true), entities, withClause, args.timeout);
|
|
1444
|
+
await loadRelationsBatched(this.batchedContext(args.timeout, skip, args.includePii === true, args.forceCustomPlan === true), entities, withClause, args.timeout);
|
|
1441
1445
|
}
|
|
1442
1446
|
stripFields(entities, strip);
|
|
1443
1447
|
return entities;
|
|
@@ -1593,6 +1597,69 @@ export class QueryInterface {
|
|
|
1593
1597
|
// Listener errors must never crash a query
|
|
1594
1598
|
}
|
|
1595
1599
|
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Resolve the prepared-statement name a read should execute under, honouring
|
|
1602
|
+
* the per-query {@link FindManyArgs.forceCustomPlan} opt-in.
|
|
1603
|
+
*
|
|
1604
|
+
* `forceCustomPlan: true` returns `undefined`, which sends the statement
|
|
1605
|
+
* UNNAMED. The mechanism is NOT "PostgreSQL treats an unnamed statement as a
|
|
1606
|
+
* one-shot plan that never enters the plan cache": the backend builds and
|
|
1607
|
+
* saves a `CachedPlanSource` for the unnamed statement too. It works because
|
|
1608
|
+
* node-postgres only skips Parse for a statement it has already parsed BY
|
|
1609
|
+
* NAME (`Query.hasBeenParsed` is `this.name && connection.parsedStatements[this.name]`),
|
|
1610
|
+
* so an unnamed statement is re-Parsed on every execution, each Parse
|
|
1611
|
+
* replaces the unnamed cached plan source with a fresh one whose custom-plan
|
|
1612
|
+
* counter is zero, and the five-execution threshold that precedes promotion
|
|
1613
|
+
* is never reached. Every execution is therefore planned with the real
|
|
1614
|
+
* parameter values.
|
|
1615
|
+
*
|
|
1616
|
+
* No GUC is set, no `SET LOCAL` is emitted, no transaction is opened, and no
|
|
1617
|
+
* extra round trip is added, which is exactly why the opt-in can be per query
|
|
1618
|
+
* while the client-level `planCacheMode` (a connection parameter) cannot be.
|
|
1619
|
+
*
|
|
1620
|
+
* The refusal is deliberately here, at the one seam every read execution
|
|
1621
|
+
* passes through, rather than in each build method: the flag changes NOTHING
|
|
1622
|
+
* about the SQL text, so a build-time check would have had to be repeated in
|
|
1623
|
+
* every builder and could still be bypassed by a hand-executed
|
|
1624
|
+
* `DeferredQuery`.
|
|
1625
|
+
*
|
|
1626
|
+
* Engines whose dialect does not report {@link Dialect.supportsPlanCacheMode}
|
|
1627
|
+
* throw {@link UnsupportedFeatureError} (E017): the flag names a PostgreSQL
|
|
1628
|
+
* plan-cache guarantee, and an engine with no such cache cannot make it.
|
|
1629
|
+
* The same flag left unset (or `false`) is accepted everywhere.
|
|
1630
|
+
*
|
|
1631
|
+
* THE ONE COMBINATION THAT IS REFUSED RATHER THAN HONOURED. A client-level
|
|
1632
|
+
* `planCacheMode: 'force_generic_plan'` DEFEATS this option, and that was
|
|
1633
|
+
* MEASURED rather than reasoned about: on PostgreSQL 16.14, five executions
|
|
1634
|
+
* of one unnamed statement read 19,107 buffers with that setting in force and
|
|
1635
|
+
* 55 buffers with the same connection set back to `auto`, against 19,107 for
|
|
1636
|
+
* the named statement. So the setting governs the unnamed statement too, and
|
|
1637
|
+
* withholding the name buys nothing against it. Accepting the flag there
|
|
1638
|
+
* would report a guarantee the very next execution breaks, so the
|
|
1639
|
+
* contradiction throws {@link ValidationError} (E003) naming both settings.
|
|
1640
|
+
* Turbine can only see the setting IT applied: a `plan_cache_mode` installed
|
|
1641
|
+
* by the caller's own `SET`, by `ALTER ROLE`, or by a pooler is invisible
|
|
1642
|
+
* here and is not refused.
|
|
1643
|
+
*/
|
|
1644
|
+
preparedNameFor(args, name) {
|
|
1645
|
+
if (args?.forceCustomPlan !== true)
|
|
1646
|
+
return name;
|
|
1647
|
+
if (this.dialect.supportsPlanCacheMode !== true) {
|
|
1648
|
+
throw new UnsupportedFeatureError('The forceCustomPlan query option', this.dialect.name, 'Forcing a per-query custom plan means keeping the statement out of the PostgreSQL plan cache, and this ' +
|
|
1649
|
+
'engine has no such cache to keep it out of. Remove the option, or set it only on PostgreSQL queries.');
|
|
1650
|
+
}
|
|
1651
|
+
if (this.options?.planCacheMode === 'force_generic_plan') {
|
|
1652
|
+
throw new ValidationError('[turbine] forceCustomPlan: true cannot be honoured on a client configured with ' +
|
|
1653
|
+
"planCacheMode: 'force_generic_plan'. That setting is a connection parameter and it governs UNNAMED " +
|
|
1654
|
+
'statements as well as named ones, so the mechanism this option uses (withholding the ' +
|
|
1655
|
+
'prepared-statement name, so the driver re-parses the statement on every execution and it is planned ' +
|
|
1656
|
+
'with its real values) is ' +
|
|
1657
|
+
'overridden by it and the query would be planned generically anyway. Leave the client on the default ' +
|
|
1658
|
+
'(`planCacheMode` unset, or `auto`) and force the custom plan per query: that is the combination that ' +
|
|
1659
|
+
'expresses "custom here, auto there".');
|
|
1660
|
+
}
|
|
1661
|
+
return undefined;
|
|
1662
|
+
}
|
|
1596
1663
|
/**
|
|
1597
1664
|
* Execute a pool.query with an optional timeout.
|
|
1598
1665
|
* If timeout is set, races the query against a timer and rejects on expiry.
|
|
@@ -1741,7 +1808,7 @@ export class QueryInterface {
|
|
|
1741
1808
|
}
|
|
1742
1809
|
}
|
|
1743
1810
|
const deferred = this.buildFindUnique(args);
|
|
1744
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, deferred.preparedName);
|
|
1811
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
1745
1812
|
return deferred.transform(result);
|
|
1746
1813
|
});
|
|
1747
1814
|
}
|
|
@@ -1762,11 +1829,11 @@ export class QueryInterface {
|
|
|
1762
1829
|
const proj = includeKeysForBatching(args.select, args.omit, needed, defaultProjectionFields(this.tableMeta, args.includePii));
|
|
1763
1830
|
const baseArgs = { ...args, with: undefined, select: proj.select, omit: proj.omit };
|
|
1764
1831
|
const deferred = this.buildFindUnique(baseArgs);
|
|
1765
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, deferred.preparedName);
|
|
1832
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
1766
1833
|
const entity = deferred.transform(result);
|
|
1767
1834
|
if (!entity)
|
|
1768
1835
|
return null;
|
|
1769
|
-
await loadRelationsBatched(this.batchedContext(args.timeout, args.skipGlobalFilters, args.includePii === true), [entity], withClause, args.timeout);
|
|
1836
|
+
await loadRelationsBatched(this.batchedContext(args.timeout, args.skipGlobalFilters, args.includePii === true, args.forceCustomPlan === true), [entity], withClause, args.timeout);
|
|
1770
1837
|
stripFields([entity], proj.strip);
|
|
1771
1838
|
return entity;
|
|
1772
1839
|
}
|
|
@@ -1937,7 +2004,7 @@ export class QueryInterface {
|
|
|
1937
2004
|
}
|
|
1938
2005
|
}
|
|
1939
2006
|
const deferred = this.buildFindMany(args);
|
|
1940
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, deferred.preparedName);
|
|
2007
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
1941
2008
|
return deferred.transform(result);
|
|
1942
2009
|
});
|
|
1943
2010
|
}
|
|
@@ -2388,7 +2455,13 @@ export class QueryInterface {
|
|
|
2388
2455
|
limit: batchSize + 1,
|
|
2389
2456
|
});
|
|
2390
2457
|
this.currentAction = 'findManyStream';
|
|
2391
|
-
|
|
2458
|
+
// Streaming is ALREADY immune to the generic-plan cliff: the speculative
|
|
2459
|
+
// fetch has never passed a prepared name, and the cursor path runs through
|
|
2460
|
+
// DECLARE, so neither statement enters the plan cache. `preparedNameFor` is
|
|
2461
|
+
// still called with no name so that `forceCustomPlan: true` is VALIDATED on
|
|
2462
|
+
// an engine that cannot honour it here either, rather than being quietly
|
|
2463
|
+
// satisfied by an accident of this code path.
|
|
2464
|
+
const speculativeResult = await this.queryWithTimeout(speculativeDeferred.sql, speculativeDeferred.params, args?.timeout, this.preparedNameFor(args, undefined));
|
|
2392
2465
|
if (speculativeResult.rows.length <= batchSize) {
|
|
2393
2466
|
// Small drain, yield all rows and return, no cursor needed
|
|
2394
2467
|
for (const row of speculativeResult.rows) {
|
|
@@ -2451,7 +2524,7 @@ export class QueryInterface {
|
|
|
2451
2524
|
}
|
|
2452
2525
|
}
|
|
2453
2526
|
const deferred = this.buildFindFirst(args);
|
|
2454
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, deferred.preparedName);
|
|
2527
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
2455
2528
|
return deferred.transform(result);
|
|
2456
2529
|
});
|
|
2457
2530
|
}
|
|
@@ -2476,7 +2549,7 @@ export class QueryInterface {
|
|
|
2476
2549
|
async findFirstOrThrow(args) {
|
|
2477
2550
|
return this.executeWithMiddleware('findFirstOrThrow', (args ?? {}), async () => {
|
|
2478
2551
|
const deferred = this.buildFindFirstOrThrow(args);
|
|
2479
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, deferred.preparedName);
|
|
2552
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
2480
2553
|
return deferred.transform(result);
|
|
2481
2554
|
});
|
|
2482
2555
|
}
|
|
@@ -2506,7 +2579,7 @@ export class QueryInterface {
|
|
|
2506
2579
|
async findUniqueOrThrow(args) {
|
|
2507
2580
|
return this.executeWithMiddleware('findUniqueOrThrow', args, async () => {
|
|
2508
2581
|
const deferred = this.buildFindUniqueOrThrow(args);
|
|
2509
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, deferred.preparedName);
|
|
2582
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
2510
2583
|
return deferred.transform(result);
|
|
2511
2584
|
});
|
|
2512
2585
|
}
|
|
@@ -2691,7 +2764,7 @@ export class QueryInterface {
|
|
|
2691
2764
|
async count(args) {
|
|
2692
2765
|
return this.executeWithMiddleware('count', (args ?? {}), async () => {
|
|
2693
2766
|
const deferred = this.buildCount(args);
|
|
2694
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, deferred.preparedName);
|
|
2767
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args?.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
2695
2768
|
return deferred.transform(result);
|
|
2696
2769
|
});
|
|
2697
2770
|
}
|
|
@@ -2735,7 +2808,7 @@ export class QueryInterface {
|
|
|
2735
2808
|
async groupBy(args) {
|
|
2736
2809
|
return this.executeWithMiddleware('groupBy', args, async () => {
|
|
2737
2810
|
const deferred = this.buildGroupBy(args);
|
|
2738
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, deferred.preparedName);
|
|
2811
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
2739
2812
|
return deferred.transform(result);
|
|
2740
2813
|
});
|
|
2741
2814
|
}
|
|
@@ -2754,7 +2827,7 @@ export class QueryInterface {
|
|
|
2754
2827
|
async aggregate(args) {
|
|
2755
2828
|
return this.executeWithMiddleware('aggregate', args, async () => {
|
|
2756
2829
|
const deferred = this.buildAggregate(args);
|
|
2757
|
-
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, deferred.preparedName);
|
|
2830
|
+
const result = await this.queryWithTimeout(deferred.sql, deferred.params, args.timeout, this.preparedNameFor(args, deferred.preparedName));
|
|
2758
2831
|
return deferred.transform(result);
|
|
2759
2832
|
});
|
|
2760
2833
|
}
|
package/dist/query/deferred.d.ts
CHANGED
|
@@ -127,6 +127,15 @@ export interface QueryInterfaceOptions {
|
|
|
127
127
|
* (serverless drivers may not support named statements).
|
|
128
128
|
*/
|
|
129
129
|
preparedStatements?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* The client-level `plan_cache_mode` this interface's connections were opened
|
|
132
|
+
* with, forwarded from `TurbineConfig.planCacheMode` purely so the per-query
|
|
133
|
+
* `forceCustomPlan` option can REFUSE the one combination it cannot honour
|
|
134
|
+
* (`'force_generic_plan'`, see `preparedNameFor` in builder.ts). Nothing else
|
|
135
|
+
* reads it, and it is never emitted into SQL: the setting itself travels as a
|
|
136
|
+
* connection parameter, set by client.ts when it opens the pool.
|
|
137
|
+
*/
|
|
138
|
+
planCacheMode?: 'auto' | 'force_custom_plan' | 'force_generic_plan';
|
|
130
139
|
/**
|
|
131
140
|
* Enable the SQL template cache. When true, repeated queries with the
|
|
132
141
|
* same shape (same keys, operators, relations, different values) reuse
|
package/dist/query/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
export type { AggregateArgs, AggregateResult, ArrayFilter, ColumnRef, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByAggregateSpec, GroupByArgs, GroupByDistinctOn, GroupByResult, HavingClause, JsonFilter, JsonPathAggregateTarget, JsonPathGroupKey, JsonPathOrderBy, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderByObject, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, RelationPickBy, RelationPickOrderBy, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithOrderByObject, WithResult, } from './types.js';
|
|
9
9
|
export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, InsertStatementInput, UpsertStatementInput, } from '../dialect.js';
|
|
10
10
|
export { postgresDialect } from '../dialect.js';
|
|
11
|
+
export type { OptionKind, OptionTable } from './option-surface.js';
|
|
12
|
+
export { AGGREGATE_OPTIONS, ALL_OPTION_TABLES, applyNativeOptions, COUNT_OPTIONS, CREATE_MANY_OPTIONS, CREATE_OPTIONS, DELETE_MANY_OPTIONS, DELETE_OPTIONS, FIND_MANY_OPTIONS, FIND_MANY_STREAM_OPTIONS, FIND_UNIQUE_OPTIONS, GROUP_BY_OPTIONS, optionKeysOfKind, UPDATE_MANY_OPTIONS, UPDATE_OPTIONS, UPSERT_OPTIONS, } from './option-surface.js';
|
|
11
13
|
export type { SqlCacheEntry } from './utils.js';
|
|
12
14
|
export { buildCorrelation, escapeLike, escSingleQuote, fnv1a64Hex, LRUCache, OPERATOR_KEYS, quoteIdent, sqlToPreparedName, } from './utils.js';
|
|
13
15
|
export type { DeferredQuery, MiddlewareFn, QueryEvent, QueryEventListener, QueryInterfaceOptions, ReselectExecutor, TemporalInfinityReading, } from './builder.js';
|
package/dist/query/index.js
CHANGED
|
@@ -6,5 +6,6 @@
|
|
|
6
6
|
* former monolithic `import { … } from './query.js'`.
|
|
7
7
|
*/
|
|
8
8
|
export { postgresDialect } from '../dialect.js';
|
|
9
|
+
export { AGGREGATE_OPTIONS, ALL_OPTION_TABLES, applyNativeOptions, COUNT_OPTIONS, CREATE_MANY_OPTIONS, CREATE_OPTIONS, DELETE_MANY_OPTIONS, DELETE_OPTIONS, FIND_MANY_OPTIONS, FIND_MANY_STREAM_OPTIONS, FIND_UNIQUE_OPTIONS, GROUP_BY_OPTIONS, optionKeysOfKind, UPDATE_MANY_OPTIONS, UPDATE_OPTIONS, UPSERT_OPTIONS, } from './option-surface.js';
|
|
9
10
|
export { buildCorrelation, escapeLike, escSingleQuote, fnv1a64Hex, LRUCache, OPERATOR_KEYS, quoteIdent, sqlToPreparedName, } from './utils.js';
|
|
10
11
|
export { AUTO_ASSUMED_ROUND_TRIP_MS, AUTO_COUNT_BATCH_MIN_PARENT_ROWS, AUTO_JOIN_PENALTY_MS_PER_ROW, AUTO_TO_ONE_JOIN_MAX_ROWS, AUTO_TO_ONE_JOIN_ROWS_MAX, AUTO_TO_ONE_JOIN_ROWS_MIN, QueryInterface, } from './builder.js';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm, the query-argument OPTION SURFACE as runtime data.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this file exists
|
|
5
|
+
*
|
|
6
|
+
* TypeScript erases interfaces, so `FindManyArgs` does not exist at runtime and
|
|
7
|
+
* any layer that has to decide, key by key, what to do with an args object has
|
|
8
|
+
* to keep its own hand-written list. `turbine-orm/prisma-compat` is exactly
|
|
9
|
+
* such a layer: it builds a FRESH turbine args object out of Prisma-shaped
|
|
10
|
+
* input and copies over the keys it recognizes. Every time core gained a
|
|
11
|
+
* query-level option, that ad-hoc allowlist silently failed to gain it, and the
|
|
12
|
+
* option was accepted by the caller's type-checker and then dropped on the
|
|
13
|
+
* floor. There was no feedback of any kind: no error, no warning, no test.
|
|
14
|
+
*
|
|
15
|
+
* These tables are the fix. Each one is a `Record<keyof SomeArgs<Row>,
|
|
16
|
+
* OptionKind>`, the same mechanism `TURBINE_CONFIG_KEYS` (client.ts) uses for
|
|
17
|
+
* the client-config surface, and it binds the compiler in BOTH directions:
|
|
18
|
+
*
|
|
19
|
+
* - add an option to an arg interface and this file stops compiling until a
|
|
20
|
+
* human classifies it ("Property 'fooMode' is missing in type ..."), so an
|
|
21
|
+
* option can no longer be stranded BY OMISSION;
|
|
22
|
+
* - list a key here that is not on the interface and it fails as an excess
|
|
23
|
+
* property, so a table can never drift into describing an option that does
|
|
24
|
+
* not exist.
|
|
25
|
+
*
|
|
26
|
+
* It deliberately does NOT make "add the option in one place" sufficient: it
|
|
27
|
+
* makes the second edit a BUILD FAILURE rather than a silent drop. That trade is
|
|
28
|
+
* intentional. A passthrough-by-default translator would satisfy the shorter
|
|
29
|
+
* wording and be actively wrong, because two of the options below carry FIELD
|
|
30
|
+
* NAMES in their values (`optimisticLock.field`, `distinctOn.columns`), which a
|
|
31
|
+
* compat layer must rename before core ever sees them. Copying those blind
|
|
32
|
+
* works on a schema whose names happen to coincide and breaks on one that
|
|
33
|
+
* renames a column, i.e. it makes the failure mode depend on the schema.
|
|
34
|
+
*
|
|
35
|
+
* ## THE ONE RULE for classifying a new option
|
|
36
|
+
*
|
|
37
|
+
* Classify a key `'native'` ONLY when its value contains no field, relation,
|
|
38
|
+
* column, or model NAME. If the value names anything in the schema, it is
|
|
39
|
+
* `'prisma'`: a name-translating consumer has to walk it by hand.
|
|
40
|
+
*
|
|
41
|
+
* @module
|
|
42
|
+
*/
|
|
43
|
+
import type { AggregateArgs, CountArgs, CreateArgs, CreateManyArgs, DeleteArgs, DeleteManyArgs, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GroupByArgs, UpdateArgs, UpdateManyArgs, UpsertArgs } from './types.js';
|
|
44
|
+
/**
|
|
45
|
+
* How a name-translating consumer (today: `turbine-orm/prisma-compat`) must
|
|
46
|
+
* handle one key of a turbine query-arg interface.
|
|
47
|
+
*
|
|
48
|
+
* - `'prisma'`, the key is a Prisma concept too, or its VALUE carries names
|
|
49
|
+
* that live in the caller's naming space. Translated by hand; NEVER copied
|
|
50
|
+
* verbatim.
|
|
51
|
+
* - `'native'`, turbine-only and its value is opaque to naming (a boolean, a
|
|
52
|
+
* number, a list of table names). Copied through untouched.
|
|
53
|
+
* - `'nativeAlias'`, the turbine SPELLING of a concept the caller's surface
|
|
54
|
+
* already has under another name (`with`/`limit`/`offset` vs
|
|
55
|
+
* `include`/`take`/`skip`). Refused, because forwarding it would collide with
|
|
56
|
+
* the translated key and would carry turbine relation names into a call
|
|
57
|
+
* written in the caller's names. The diagnostic names the right key instead.
|
|
58
|
+
* - `'internal'`, not reachable through the compat surface at all
|
|
59
|
+
* (`batchSize` belongs to a streaming method compat does not expose), so it
|
|
60
|
+
* is not part of any known set and passing it is reported as unknown.
|
|
61
|
+
*/
|
|
62
|
+
export type OptionKind = 'prisma' | 'native' | 'nativeAlias' | 'internal';
|
|
63
|
+
/**
|
|
64
|
+
* The generic parameter the tables are instantiated at. `keyof FindManyArgs<T>`
|
|
65
|
+
* is the literal union of the DECLARED key names regardless of `T`, so a
|
|
66
|
+
* neutral row type keeps the tables stable and free of entity coupling.
|
|
67
|
+
*/
|
|
68
|
+
type Row = Record<string, unknown>;
|
|
69
|
+
/** One option table: every declared key of one arg interface, classified. */
|
|
70
|
+
export type OptionTable<A> = Readonly<Record<keyof A, OptionKind>>;
|
|
71
|
+
export declare const FIND_UNIQUE_OPTIONS: OptionTable<FindUniqueArgs<Row>>;
|
|
72
|
+
export declare const FIND_MANY_OPTIONS: OptionTable<FindManyArgs<Row>>;
|
|
73
|
+
export declare const FIND_MANY_STREAM_OPTIONS: OptionTable<FindManyStreamArgs<Row>>;
|
|
74
|
+
export declare const CREATE_OPTIONS: OptionTable<CreateArgs<Row>>;
|
|
75
|
+
export declare const CREATE_MANY_OPTIONS: OptionTable<CreateManyArgs<Row>>;
|
|
76
|
+
export declare const UPDATE_OPTIONS: OptionTable<UpdateArgs<Row>>;
|
|
77
|
+
export declare const UPDATE_MANY_OPTIONS: OptionTable<UpdateManyArgs<Row>>;
|
|
78
|
+
export declare const DELETE_OPTIONS: OptionTable<DeleteArgs<Row>>;
|
|
79
|
+
export declare const DELETE_MANY_OPTIONS: OptionTable<DeleteManyArgs<Row>>;
|
|
80
|
+
export declare const UPSERT_OPTIONS: OptionTable<UpsertArgs<Row>>;
|
|
81
|
+
export declare const COUNT_OPTIONS: OptionTable<CountArgs<Row>>;
|
|
82
|
+
export declare const AGGREGATE_OPTIONS: OptionTable<AggregateArgs<Row>>;
|
|
83
|
+
export declare const GROUP_BY_OPTIONS: OptionTable<GroupByArgs<Row>>;
|
|
84
|
+
/**
|
|
85
|
+
* Every table, so a test can assert the set is complete and well-formed without
|
|
86
|
+
* naming each one (a table stubbed out during a refactor shows up here).
|
|
87
|
+
*/
|
|
88
|
+
export declare const ALL_OPTION_TABLES: Readonly<Record<string, Readonly<Record<string, OptionKind>>>>;
|
|
89
|
+
/**
|
|
90
|
+
* Copy every `'native'` key present on `src` onto `dst`.
|
|
91
|
+
*
|
|
92
|
+
* Iterates `src` (a small caller-supplied object) rather than the table, so the
|
|
93
|
+
* cost is proportional to what was actually passed. `undefined` values are
|
|
94
|
+
* skipped: `{ ...maybeOpts }` routinely materializes keys with no value, and
|
|
95
|
+
* writing `undefined` through would be indistinguishable from passing it.
|
|
96
|
+
*/
|
|
97
|
+
export declare function applyNativeOptions(table: Readonly<Record<string, OptionKind>>, src: Record<string, unknown>, dst: Record<string, unknown>): void;
|
|
98
|
+
/** The keys of `table` with the given kind, as a set. */
|
|
99
|
+
export declare function optionKeysOfKind(table: Readonly<Record<string, OptionKind>>, ...kinds: OptionKind[]): string[];
|
|
100
|
+
export {};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* turbine-orm, the query-argument OPTION SURFACE as runtime data.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this file exists
|
|
5
|
+
*
|
|
6
|
+
* TypeScript erases interfaces, so `FindManyArgs` does not exist at runtime and
|
|
7
|
+
* any layer that has to decide, key by key, what to do with an args object has
|
|
8
|
+
* to keep its own hand-written list. `turbine-orm/prisma-compat` is exactly
|
|
9
|
+
* such a layer: it builds a FRESH turbine args object out of Prisma-shaped
|
|
10
|
+
* input and copies over the keys it recognizes. Every time core gained a
|
|
11
|
+
* query-level option, that ad-hoc allowlist silently failed to gain it, and the
|
|
12
|
+
* option was accepted by the caller's type-checker and then dropped on the
|
|
13
|
+
* floor. There was no feedback of any kind: no error, no warning, no test.
|
|
14
|
+
*
|
|
15
|
+
* These tables are the fix. Each one is a `Record<keyof SomeArgs<Row>,
|
|
16
|
+
* OptionKind>`, the same mechanism `TURBINE_CONFIG_KEYS` (client.ts) uses for
|
|
17
|
+
* the client-config surface, and it binds the compiler in BOTH directions:
|
|
18
|
+
*
|
|
19
|
+
* - add an option to an arg interface and this file stops compiling until a
|
|
20
|
+
* human classifies it ("Property 'fooMode' is missing in type ..."), so an
|
|
21
|
+
* option can no longer be stranded BY OMISSION;
|
|
22
|
+
* - list a key here that is not on the interface and it fails as an excess
|
|
23
|
+
* property, so a table can never drift into describing an option that does
|
|
24
|
+
* not exist.
|
|
25
|
+
*
|
|
26
|
+
* It deliberately does NOT make "add the option in one place" sufficient: it
|
|
27
|
+
* makes the second edit a BUILD FAILURE rather than a silent drop. That trade is
|
|
28
|
+
* intentional. A passthrough-by-default translator would satisfy the shorter
|
|
29
|
+
* wording and be actively wrong, because two of the options below carry FIELD
|
|
30
|
+
* NAMES in their values (`optimisticLock.field`, `distinctOn.columns`), which a
|
|
31
|
+
* compat layer must rename before core ever sees them. Copying those blind
|
|
32
|
+
* works on a schema whose names happen to coincide and breaks on one that
|
|
33
|
+
* renames a column, i.e. it makes the failure mode depend on the schema.
|
|
34
|
+
*
|
|
35
|
+
* ## THE ONE RULE for classifying a new option
|
|
36
|
+
*
|
|
37
|
+
* Classify a key `'native'` ONLY when its value contains no field, relation,
|
|
38
|
+
* column, or model NAME. If the value names anything in the schema, it is
|
|
39
|
+
* `'prisma'`: a name-translating consumer has to walk it by hand.
|
|
40
|
+
*
|
|
41
|
+
* @module
|
|
42
|
+
*/
|
|
43
|
+
export const FIND_UNIQUE_OPTIONS = {
|
|
44
|
+
where: 'prisma',
|
|
45
|
+
select: 'prisma',
|
|
46
|
+
omit: 'prisma',
|
|
47
|
+
// Prisma spells this `include`; forwarding `with` would collide with the
|
|
48
|
+
// translated projection and carry turbine relation names into a Prisma call.
|
|
49
|
+
with: 'nativeAlias',
|
|
50
|
+
// Same key on both surfaces, DIFFERENT value domains ('query' | 'join' vs
|
|
51
|
+
// 'join' | 'batched' | 'auto' | 'flatten'), so the value needs mapping.
|
|
52
|
+
relationLoadStrategy: 'prisma',
|
|
53
|
+
timeout: 'native',
|
|
54
|
+
stableRelationOrder: 'native',
|
|
55
|
+
skipGlobalFilters: 'native',
|
|
56
|
+
includePii: 'native',
|
|
57
|
+
forceCustomPlan: 'native',
|
|
58
|
+
};
|
|
59
|
+
export const FIND_MANY_OPTIONS = {
|
|
60
|
+
where: 'prisma',
|
|
61
|
+
select: 'prisma',
|
|
62
|
+
omit: 'prisma',
|
|
63
|
+
orderBy: 'prisma',
|
|
64
|
+
cursor: 'prisma',
|
|
65
|
+
take: 'prisma',
|
|
66
|
+
distinct: 'prisma',
|
|
67
|
+
relationLoadStrategy: 'prisma',
|
|
68
|
+
with: 'nativeAlias',
|
|
69
|
+
limit: 'nativeAlias',
|
|
70
|
+
offset: 'nativeAlias',
|
|
71
|
+
timeout: 'native',
|
|
72
|
+
stableRelationOrder: 'native',
|
|
73
|
+
skipGlobalFilters: 'native',
|
|
74
|
+
warnOnUnlimited: 'native',
|
|
75
|
+
includePii: 'native',
|
|
76
|
+
forceCustomPlan: 'native',
|
|
77
|
+
};
|
|
78
|
+
export const FIND_MANY_STREAM_OPTIONS = {
|
|
79
|
+
...FIND_MANY_OPTIONS,
|
|
80
|
+
// No streaming delegate exists on the compat surface, so this is not a known
|
|
81
|
+
// key there and passing it is reported rather than quietly ignored.
|
|
82
|
+
batchSize: 'internal',
|
|
83
|
+
};
|
|
84
|
+
export const CREATE_OPTIONS = {
|
|
85
|
+
data: 'prisma',
|
|
86
|
+
timeout: 'native',
|
|
87
|
+
};
|
|
88
|
+
export const CREATE_MANY_OPTIONS = {
|
|
89
|
+
data: 'prisma',
|
|
90
|
+
skipDuplicates: 'prisma',
|
|
91
|
+
timeout: 'native',
|
|
92
|
+
};
|
|
93
|
+
export const UPDATE_OPTIONS = {
|
|
94
|
+
where: 'prisma',
|
|
95
|
+
data: 'prisma',
|
|
96
|
+
// `{ field, expected }`, and `field` is a FIELD NAME, so it has to be renamed
|
|
97
|
+
// into turbine's naming space rather than copied. See THE ONE RULE above.
|
|
98
|
+
optimisticLock: 'prisma',
|
|
99
|
+
timeout: 'native',
|
|
100
|
+
allowFullTableScan: 'native',
|
|
101
|
+
skipGlobalFilters: 'native',
|
|
102
|
+
};
|
|
103
|
+
export const UPDATE_MANY_OPTIONS = {
|
|
104
|
+
where: 'prisma',
|
|
105
|
+
data: 'prisma',
|
|
106
|
+
timeout: 'native',
|
|
107
|
+
allowFullTableScan: 'native',
|
|
108
|
+
skipGlobalFilters: 'native',
|
|
109
|
+
};
|
|
110
|
+
export const DELETE_OPTIONS = {
|
|
111
|
+
where: 'prisma',
|
|
112
|
+
timeout: 'native',
|
|
113
|
+
allowFullTableScan: 'native',
|
|
114
|
+
skipGlobalFilters: 'native',
|
|
115
|
+
};
|
|
116
|
+
export const DELETE_MANY_OPTIONS = {
|
|
117
|
+
where: 'prisma',
|
|
118
|
+
timeout: 'native',
|
|
119
|
+
allowFullTableScan: 'native',
|
|
120
|
+
skipGlobalFilters: 'native',
|
|
121
|
+
};
|
|
122
|
+
export const UPSERT_OPTIONS = {
|
|
123
|
+
where: 'prisma',
|
|
124
|
+
create: 'prisma',
|
|
125
|
+
update: 'prisma',
|
|
126
|
+
timeout: 'native',
|
|
127
|
+
skipGlobalFilters: 'native',
|
|
128
|
+
};
|
|
129
|
+
export const COUNT_OPTIONS = {
|
|
130
|
+
where: 'prisma',
|
|
131
|
+
timeout: 'native',
|
|
132
|
+
skipGlobalFilters: 'native',
|
|
133
|
+
forceCustomPlan: 'native',
|
|
134
|
+
};
|
|
135
|
+
export const AGGREGATE_OPTIONS = {
|
|
136
|
+
where: 'prisma',
|
|
137
|
+
_count: 'prisma',
|
|
138
|
+
_sum: 'prisma',
|
|
139
|
+
_avg: 'prisma',
|
|
140
|
+
_min: 'prisma',
|
|
141
|
+
_max: 'prisma',
|
|
142
|
+
timeout: 'native',
|
|
143
|
+
skipGlobalFilters: 'native',
|
|
144
|
+
includePii: 'native',
|
|
145
|
+
forceCustomPlan: 'native',
|
|
146
|
+
};
|
|
147
|
+
export const GROUP_BY_OPTIONS = {
|
|
148
|
+
by: 'prisma',
|
|
149
|
+
where: 'prisma',
|
|
150
|
+
having: 'prisma',
|
|
151
|
+
orderBy: 'prisma',
|
|
152
|
+
_count: 'prisma',
|
|
153
|
+
_sum: 'prisma',
|
|
154
|
+
_avg: 'prisma',
|
|
155
|
+
_min: 'prisma',
|
|
156
|
+
_max: 'prisma',
|
|
157
|
+
// `{ columns, orderBy }`, both in FIELD-NAME space. See THE ONE RULE.
|
|
158
|
+
distinctOn: 'prisma',
|
|
159
|
+
limit: 'nativeAlias',
|
|
160
|
+
offset: 'nativeAlias',
|
|
161
|
+
timeout: 'native',
|
|
162
|
+
skipGlobalFilters: 'native',
|
|
163
|
+
includePii: 'native',
|
|
164
|
+
forceCustomPlan: 'native',
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Every table, so a test can assert the set is complete and well-formed without
|
|
168
|
+
* naming each one (a table stubbed out during a refactor shows up here).
|
|
169
|
+
*/
|
|
170
|
+
export const ALL_OPTION_TABLES = {
|
|
171
|
+
findUnique: FIND_UNIQUE_OPTIONS,
|
|
172
|
+
findMany: FIND_MANY_OPTIONS,
|
|
173
|
+
findManyStream: FIND_MANY_STREAM_OPTIONS,
|
|
174
|
+
create: CREATE_OPTIONS,
|
|
175
|
+
createMany: CREATE_MANY_OPTIONS,
|
|
176
|
+
update: UPDATE_OPTIONS,
|
|
177
|
+
updateMany: UPDATE_MANY_OPTIONS,
|
|
178
|
+
delete: DELETE_OPTIONS,
|
|
179
|
+
deleteMany: DELETE_MANY_OPTIONS,
|
|
180
|
+
upsert: UPSERT_OPTIONS,
|
|
181
|
+
count: COUNT_OPTIONS,
|
|
182
|
+
aggregate: AGGREGATE_OPTIONS,
|
|
183
|
+
groupBy: GROUP_BY_OPTIONS,
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* Copy every `'native'` key present on `src` onto `dst`.
|
|
187
|
+
*
|
|
188
|
+
* Iterates `src` (a small caller-supplied object) rather than the table, so the
|
|
189
|
+
* cost is proportional to what was actually passed. `undefined` values are
|
|
190
|
+
* skipped: `{ ...maybeOpts }` routinely materializes keys with no value, and
|
|
191
|
+
* writing `undefined` through would be indistinguishable from passing it.
|
|
192
|
+
*/
|
|
193
|
+
export function applyNativeOptions(table, src, dst) {
|
|
194
|
+
// Total on any input: a delegate whose args are optional can be called with
|
|
195
|
+
// none, and a diagnostic-adjacent helper must not be the thing that throws.
|
|
196
|
+
if (src === null || typeof src !== 'object')
|
|
197
|
+
return;
|
|
198
|
+
for (const key of Object.keys(src)) {
|
|
199
|
+
if (table[key] !== 'native')
|
|
200
|
+
continue;
|
|
201
|
+
const value = src[key];
|
|
202
|
+
if (value !== undefined)
|
|
203
|
+
dst[key] = value;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** The keys of `table` with the given kind, as a set. */
|
|
207
|
+
export function optionKeysOfKind(table, ...kinds) {
|
|
208
|
+
return Object.keys(table).filter((k) => kinds.includes(table[k]));
|
|
209
|
+
}
|