turbine-orm 0.75.0 → 0.76.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 +48 -7
- package/dist/cjs/cli/compile-query.d.ts +22 -2
- package/dist/cjs/cli/compile-query.js +49 -5
- package/dist/cjs/cli/config.d.ts +2 -0
- package/dist/cjs/cli/config.js +1 -1
- package/dist/cjs/cli/destructive.js +78 -43
- package/dist/cjs/cli/index.d.ts +95 -1
- package/dist/cjs/cli/index.js +609 -145
- package/dist/cjs/cli/mcp.js +30 -1
- package/dist/cjs/cli/pii-predicate-guard.d.ts +25 -0
- package/dist/cjs/cli/pii-predicate-guard.js +72 -12
- package/dist/cjs/cli/rate-limit.js +38 -1
- package/dist/cjs/cli/studio.js +26 -5
- package/dist/cjs/cli/ui.d.ts +33 -0
- package/dist/cjs/cli/ui.js +53 -7
- package/dist/cjs/client.d.ts +13 -1
- package/dist/cjs/client.js +1 -1
- package/dist/cjs/errors.d.ts +12 -1
- package/dist/cjs/errors.js +11 -2
- package/dist/cjs/generate.d.ts +26 -0
- package/dist/cjs/generate.js +174 -27
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/introspect.d.ts +17 -0
- package/dist/cjs/introspect.js +100 -1
- package/dist/cjs/mssql.d.ts +18 -0
- package/dist/cjs/mssql.js +20 -1
- package/dist/cjs/pipeline.js +44 -6
- package/dist/cjs/powql.js +51 -17
- package/dist/cjs/query/batched-loader.js +3 -3
- package/dist/cjs/query/builder.js +1 -1
- package/dist/cjs/query/relations.d.ts +5 -0
- package/dist/cjs/query/relations.js +141 -69
- package/dist/cjs/query/utils.d.ts +13 -0
- package/dist/cjs/query/utils.js +16 -0
- package/dist/cjs/serverless.d.ts +1 -1
- package/dist/cjs/serverless.js +1 -1
- package/dist/cjs/sqlite.d.ts +33 -1
- package/dist/cjs/sqlite.js +84 -3
- package/dist/cli/compile-query.d.ts +22 -2
- package/dist/cli/compile-query.js +50 -6
- package/dist/cli/config.d.ts +2 -0
- package/dist/cli/config.js +1 -1
- package/dist/cli/destructive.js +78 -43
- package/dist/cli/index.d.ts +95 -1
- package/dist/cli/index.js +604 -147
- package/dist/cli/mcp.js +30 -1
- package/dist/cli/pii-predicate-guard.d.ts +25 -0
- package/dist/cli/pii-predicate-guard.js +73 -13
- package/dist/cli/rate-limit.js +38 -1
- package/dist/cli/studio.js +27 -6
- package/dist/cli/ui.d.ts +33 -0
- package/dist/cli/ui.js +51 -7
- package/dist/client.d.ts +13 -1
- package/dist/client.js +1 -1
- package/dist/errors.d.ts +12 -1
- package/dist/errors.js +11 -2
- package/dist/generate.d.ts +26 -0
- package/dist/generate.js +172 -27
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/introspect.d.ts +17 -0
- package/dist/introspect.js +98 -1
- package/dist/mssql.d.ts +18 -0
- package/dist/mssql.js +20 -1
- package/dist/pipeline.js +44 -6
- package/dist/powql.js +53 -19
- package/dist/query/batched-loader.js +4 -4
- package/dist/query/builder.js +2 -2
- package/dist/query/relations.d.ts +5 -0
- package/dist/query/relations.js +141 -70
- package/dist/query/utils.d.ts +13 -0
- package/dist/query/utils.js +15 -0
- package/dist/serverless.d.ts +1 -1
- package/dist/serverless.js +1 -1
- package/dist/sqlite.d.ts +33 -1
- package/dist/sqlite.js +85 -4
- package/package.json +2 -2
package/dist/pipeline.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* Sequential fallback covers HTTP-based drivers (Neon HTTP, Vercel Postgres, Cloudflare
|
|
19
19
|
* Hyperdrive), mock pools in tests, and any pool that doesn't expose pg internals.
|
|
20
20
|
*/
|
|
21
|
+
import { postgresDialect } from './dialect.js';
|
|
21
22
|
import { PipelineError, wrapPgError } from './errors.js';
|
|
22
23
|
import { runPipelined, supportsExtendedPipeline } from './pipeline-submittable.js';
|
|
23
24
|
/**
|
|
@@ -28,7 +29,7 @@ import { runPipelined, supportsExtendedPipeline } from './pipeline-submittable.j
|
|
|
28
29
|
* The caller is responsible for acquiring the client and releasing it after
|
|
29
30
|
* this function completes (in the finally block).
|
|
30
31
|
*/
|
|
31
|
-
async function runSequential(client, queries, options = {}) {
|
|
32
|
+
async function runSequential(client, queries, dialect, options = {}) {
|
|
32
33
|
const { transactional = true } = options;
|
|
33
34
|
if (!transactional)
|
|
34
35
|
return runIndependent(client, queries);
|
|
@@ -40,7 +41,7 @@ async function runSequential(client, queries, options = {}) {
|
|
|
40
41
|
// `TURBINE_E004` nor a retryable flag for the one failure that is most
|
|
41
42
|
// worth retrying.
|
|
42
43
|
try {
|
|
43
|
-
await client.query(
|
|
44
|
+
await client.query(dialect.beginStatement());
|
|
44
45
|
}
|
|
45
46
|
catch (err) {
|
|
46
47
|
throw wrapPgError(err);
|
|
@@ -57,7 +58,7 @@ async function runSequential(client, queries, options = {}) {
|
|
|
57
58
|
results.push(q.transform(raw));
|
|
58
59
|
}
|
|
59
60
|
try {
|
|
60
|
-
await client.query(
|
|
61
|
+
await client.query(dialect.commitStatement());
|
|
61
62
|
}
|
|
62
63
|
catch (err) {
|
|
63
64
|
throw wrapPgError(err);
|
|
@@ -66,7 +67,7 @@ async function runSequential(client, queries, options = {}) {
|
|
|
66
67
|
}
|
|
67
68
|
catch (err) {
|
|
68
69
|
try {
|
|
69
|
-
await client.query(
|
|
70
|
+
await client.query(dialect.rollbackStatement());
|
|
70
71
|
}
|
|
71
72
|
catch {
|
|
72
73
|
// Best-effort rollback
|
|
@@ -74,6 +75,40 @@ async function runSequential(client, queries, options = {}) {
|
|
|
74
75
|
throw err;
|
|
75
76
|
}
|
|
76
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* The dialect whose transaction keywords this batch must use.
|
|
80
|
+
*
|
|
81
|
+
* `executePipeline` is handed a POOL and nothing else, so the pool is the only
|
|
82
|
+
* place the engine can be read from; the engine pool shims are the objects that
|
|
83
|
+
* know their own dialect, and one of them (`MssqlPool`) publishes it for exactly
|
|
84
|
+
* this. Anything else, a real `pg.Pool`, a serverless HTTP pool, a test mock,
|
|
85
|
+
* keeps PostgreSQL, which is what every one of them already spoke.
|
|
86
|
+
*
|
|
87
|
+
* This existed as three hard-coded strings, `BEGIN` / `COMMIT` / `ROLLBACK`,
|
|
88
|
+
* which is the one engine-specific decision in this file and the one it was not
|
|
89
|
+
* making. On SQL Server a bare `BEGIN` opens a statement BLOCK, not a
|
|
90
|
+
* transaction: `MssqlTxClient` matches the dialect's `BEGIN TRANSACTION` and
|
|
91
|
+
* nothing else, so a bare `BEGIN` missed that branch, reached the server as a
|
|
92
|
+
* block opener with no `END`, and was rejected; the `COMMIT` and `ROLLBACK`
|
|
93
|
+
* that followed then found no open transaction and silently did nothing. A
|
|
94
|
+
* pipeline that documents itself as atomic was neither atomic nor rolled back.
|
|
95
|
+
*
|
|
96
|
+
* Duck-typed rather than `instanceof`, deliberately: importing an engine module
|
|
97
|
+
* here would pull an optional peer's whole module graph into the Postgres path.
|
|
98
|
+
* The three methods tested are exactly the three called below, so a partial
|
|
99
|
+
* object can never be accepted and then fail at the call site.
|
|
100
|
+
*/
|
|
101
|
+
function poolDialect(pool) {
|
|
102
|
+
const candidate = pool.dialect;
|
|
103
|
+
if (candidate !== null &&
|
|
104
|
+
typeof candidate === 'object' &&
|
|
105
|
+
typeof candidate.beginStatement === 'function' &&
|
|
106
|
+
typeof candidate.commitStatement === 'function' &&
|
|
107
|
+
typeof candidate.rollbackStatement === 'function') {
|
|
108
|
+
return candidate;
|
|
109
|
+
}
|
|
110
|
+
return postgresDialect;
|
|
111
|
+
}
|
|
77
112
|
/**
|
|
78
113
|
* Sequential fallback for `{ transactional: false }`: each query is
|
|
79
114
|
* INDEPENDENT, which is what that option promises and what the real pipeline
|
|
@@ -177,8 +212,11 @@ export async function executePipeline(pool, queries, options) {
|
|
|
177
212
|
const results = await runPipelined(client, queries, pipelineOptions);
|
|
178
213
|
return results;
|
|
179
214
|
}
|
|
180
|
-
// Sequential fallback, reuses the same client
|
|
181
|
-
|
|
215
|
+
// Sequential fallback, reuses the same client. This is the path every
|
|
216
|
+
// non-Postgres engine takes (none of their clients expose pg's wire
|
|
217
|
+
// internals), so it is the one that has to speak the engine's transaction
|
|
218
|
+
// keywords rather than Postgres's.
|
|
219
|
+
return await runSequential(client, queries, poolDialect(pool), options);
|
|
182
220
|
}
|
|
183
221
|
finally {
|
|
184
222
|
client.release();
|
package/dist/powql.js
CHANGED
|
@@ -40,14 +40,14 @@ import { executeNestedCreate, executeNestedUpdate, hasRelationFields, } from './
|
|
|
40
40
|
import { ALL_POWDB_CAPABILITIES, coerceNativeValue, isJsonColumn, isPowdbDatetimeColumn, isStaleFramePowdbError, PowdbFloatParam, PowdbJsonParam, powqlColumnType, quotePowqlDotted, quotePowqlIdent, requireCapability, rowToEntity, } from './powdb.js';
|
|
41
41
|
import { assertAggregatePiiOptIn } from './query/aggregates.js';
|
|
42
42
|
import { assertWhereIdentifiesOneRow, expandCompoundUniqueWhere } from './query/compound-unique.js';
|
|
43
|
-
import { isJsonFilter, isRelationPickOrderBy, orderByEntries } from './query/filters.js';
|
|
43
|
+
import { ARRAY_OPERATOR_KEYS, isJsonFilter, isRelationPickOrderBy, orderByEntries } from './query/filters.js';
|
|
44
44
|
import { warnUnknownQueryOptions } from './query/option-surface.js';
|
|
45
45
|
import { applyStableRelationOrderTo, normalizeWithClause } from './query/relation-names.js';
|
|
46
46
|
// The privilege sentinel and its resolver: `includePii` / `allowFullTableScan`
|
|
47
47
|
// are unlocked ONLY by the UNSAFE symbol, on this engine exactly as on the SQL
|
|
48
48
|
// engines, so a spread request body cannot turn either on here either.
|
|
49
49
|
import { assertDirectionToken, resolveSkipGlobalFilters, resolveUnsafeFlag, UNSAFE } from './query/types.js';
|
|
50
|
-
import { escapeLike, normalizePagination, ownLookup, relationInProjectionMessage, resolveColumnName, resolveRelationDef, selectNamesNothingMessage, selectOmitExclusiveMessage, } from './query/utils.js';
|
|
50
|
+
import { availableClause, escapeLike, normalizePagination, ownLookup, relationInProjectionMessage, resolveColumnName, resolveRelationDef, selectNamesNothingMessage, selectOmitExclusiveMessage, } from './query/utils.js';
|
|
51
51
|
import { assertJsonFilterKeys, jsonStringEntries, resolveGlobalFilterFrom } from './query/where.js';
|
|
52
52
|
import { normalizeKeyColumns, snakeToCamel, } from './schema.js';
|
|
53
53
|
/**
|
|
@@ -149,8 +149,17 @@ function rejectUnsupportedFilter(value, field) {
|
|
|
149
149
|
if ('path' in value || 'hasKey' in value) {
|
|
150
150
|
throw new UnsupportedFeatureError('JSON path/key filters', 'PowDB', `field "${field}"`);
|
|
151
151
|
}
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
// Enumerated from the SHARED {@link ARRAY_OPERATOR_KEYS} rather than
|
|
153
|
+
// re-listed here, because the hand-written copy had already drifted from it:
|
|
154
|
+
// `has` was missing, so `{ tags: { has: 'x' } }` was not recognised as an
|
|
155
|
+
// array filter at all, fell through to the bare-object equality branch, and
|
|
156
|
+
// bound the whole `{ has: 'x' }` OBJECT as a scalar parameter. A silent wrong
|
|
157
|
+
// answer where its three siblings raise E017. Two lists is how that happened;
|
|
158
|
+
// reading the one the SQL side reads is what stops it happening again.
|
|
159
|
+
for (const key of ARRAY_OPERATOR_KEYS) {
|
|
160
|
+
if (key in value) {
|
|
161
|
+
throw new UnsupportedFeatureError('array filters', 'PowDB', `field "${field}"`);
|
|
162
|
+
}
|
|
154
163
|
}
|
|
155
164
|
if ('search' in value) {
|
|
156
165
|
throw new UnsupportedFeatureError('full-text search filters', 'PowDB', `field "${field}"`);
|
|
@@ -180,7 +189,7 @@ export class PowqlInterface {
|
|
|
180
189
|
this.options = options;
|
|
181
190
|
const meta = schema.tables[table];
|
|
182
191
|
if (!meta) {
|
|
183
|
-
throw new ValidationError(`[turbine] Unknown table "${table}".
|
|
192
|
+
throw new ValidationError(`[turbine] Unknown table "${table}". ${availableClause(Object.keys(schema.tables), 'The schema has no tables.')}`);
|
|
184
193
|
}
|
|
185
194
|
this.meta = meta;
|
|
186
195
|
this.defaultLimit = options.defaultLimit;
|
|
@@ -1153,8 +1162,18 @@ export class PowqlInterface {
|
|
|
1153
1162
|
stripWritePii(entity) {
|
|
1154
1163
|
if (!entity)
|
|
1155
1164
|
return entity;
|
|
1165
|
+
// Same PRIMARY-KEY exemption as the SQL engines' `piiColumns` / `piiFields`
|
|
1166
|
+
// (query/writes.ts), and for the same measured reason: the returned row has
|
|
1167
|
+
// to stay ADDRESSABLE. Without it a PII-tagged key column came back deleted,
|
|
1168
|
+
// so the caller's follow-up `update`/`delete` built its where from a partial
|
|
1169
|
+
// key, the empty-where guard did not fire (the other member was present),
|
|
1170
|
+
// and the write hit every row sharing the remaining member. The policy is
|
|
1171
|
+
// "tag sensitive data, not keys": a PII PK is documented out of scope for
|
|
1172
|
+
// stripping. This docstring already cross-referenced the SQL rule; only the
|
|
1173
|
+
// exemption itself was missing.
|
|
1174
|
+
const pk = new Set(this.meta.primaryKey);
|
|
1156
1175
|
for (const col of this.meta.columns) {
|
|
1157
|
-
if (col.pii)
|
|
1176
|
+
if (col.pii && !pk.has(col.name))
|
|
1158
1177
|
delete entity[col.field];
|
|
1159
1178
|
}
|
|
1160
1179
|
return entity;
|
|
@@ -1422,18 +1441,40 @@ export class PowqlInterface {
|
|
|
1422
1441
|
if (args.cursor) {
|
|
1423
1442
|
throw new UnsupportedFeatureError('cursor pagination', 'PowDB', 'use limit/offset instead');
|
|
1424
1443
|
}
|
|
1444
|
+
// `distinct` names COLUMNS: Postgres compiles it to `SELECT DISTINCT ON
|
|
1445
|
+
// (col)`, one row per distinct value of those columns. PowQL's `distinct`
|
|
1446
|
+
// keyword is ROW-WIDE (`SELECT DISTINCT *`) and takes no column list, and
|
|
1447
|
+
// the language has no window functions to rebuild per-column distinct with,
|
|
1448
|
+
// so there is nothing faithful to emit. Accepting the option and emitting
|
|
1449
|
+
// the row-wide keyword returned a DIFFERENT row set under the same
|
|
1450
|
+
// argument, with no error and no warning, which is the one outcome the
|
|
1451
|
+
// dialect seam exists to prevent.
|
|
1452
|
+
//
|
|
1453
|
+
// Refused with the same E017 the other non-Postgres engines already raise
|
|
1454
|
+
// (query/builder.ts's `dialect.name !== 'postgresql'` gate) and in the same
|
|
1455
|
+
// words, so `distinct` now means one thing across every engine and matches
|
|
1456
|
+
// what the README documents. Refused BEFORE the column names are resolved,
|
|
1457
|
+
// matching that gate's position, so `distinct: ['nope']` reports the
|
|
1458
|
+
// unsupported feature rather than the typo on every engine alike.
|
|
1459
|
+
if (args.distinct?.length) {
|
|
1460
|
+
throw new UnsupportedFeatureError('DISTINCT ON (findMany distinct)', 'PowDB', "findMany({ distinct }) requires PostgreSQL: PowQL's `distinct` is row-wide, not per-column. " +
|
|
1461
|
+
'Group in application code, or use groupBy({ by }) for one row per combination.');
|
|
1462
|
+
}
|
|
1425
1463
|
const resolvedWhere = await this.resolveRelationFilters(args.where, args.timeout);
|
|
1426
1464
|
const { cols, forcedPk } = this.projectionPlan(args.select, args.omit, resolveUnsafeFlag(args.includePii, 'includePii'));
|
|
1427
1465
|
// Partition the `with` clause: nested-projection blocks vs loader residue.
|
|
1428
|
-
// A
|
|
1429
|
-
//
|
|
1430
|
-
//
|
|
1431
|
-
//
|
|
1466
|
+
// A relation whose field name collides with a projected parent column stays
|
|
1467
|
+
// on the loaders (its block key would duplicate the column's). A parent
|
|
1468
|
+
// `distinct` used to be a third exclusion here (distinct over a row
|
|
1469
|
+
// containing a JSON array is not a defined comparison); the refusal above
|
|
1470
|
+
// now settles that one statement earlier, so the branch cannot be reached
|
|
1471
|
+
// with a `distinct` present and testing for it again would be dead code
|
|
1472
|
+
// implying the option is still accepted.
|
|
1432
1473
|
const withClause = args.with;
|
|
1433
1474
|
const nestedPlans = [];
|
|
1434
1475
|
const linkPlans = [];
|
|
1435
1476
|
let residualWith = withClause;
|
|
1436
|
-
if (withClause &&
|
|
1477
|
+
if (withClause && this.nestedProjectionsPreferred(args)) {
|
|
1437
1478
|
const residue = {};
|
|
1438
1479
|
for (const [relName, opt] of Object.entries(withClause)) {
|
|
1439
1480
|
if (!opt)
|
|
@@ -1464,13 +1505,6 @@ export class PowqlInterface {
|
|
|
1464
1505
|
const alias = nest ? 't0' : undefined;
|
|
1465
1506
|
let where = this.buildWhere(resolvedWhere, params, alias);
|
|
1466
1507
|
where = this.applyGlobalFilter(where, params, args.skipGlobalFilters, alias);
|
|
1467
|
-
// PowQL's `distinct` is row-wide, so these names never reach the emitted
|
|
1468
|
-
// statement. They are still caller-supplied names, and a name resolves or
|
|
1469
|
-
// throws: reading the array for its LENGTH alone let `distinct: ['nope']`
|
|
1470
|
-
// succeed here while every SQL engine refuses it. Validation only.
|
|
1471
|
-
for (const key of args.distinct ?? [])
|
|
1472
|
-
this.column(key);
|
|
1473
|
-
const distinct = args.distinct?.length ? ' distinct' : '';
|
|
1474
1508
|
const filter = where ? ` filter ${where}` : '';
|
|
1475
1509
|
const order = this.buildOrder(args.orderBy, params, alias);
|
|
1476
1510
|
const limit = this.effectiveLimit(args);
|
|
@@ -1498,7 +1532,7 @@ export class PowqlInterface {
|
|
|
1498
1532
|
else {
|
|
1499
1533
|
projection = this.projection(cols);
|
|
1500
1534
|
}
|
|
1501
|
-
const powql = `${this.qt}${nest ? ' as t0' : ''}${
|
|
1535
|
+
const powql = `${this.qt}${nest ? ' as t0' : ''}${filter}${order}${limitClause}${offsetClause} ${projection}`;
|
|
1502
1536
|
return { powql, resolvedWhere, nestedPlans, linkPlans, residualWith, forcedPk };
|
|
1503
1537
|
}
|
|
1504
1538
|
/** Build + run the findMany select; returns raw rows, the serving wire, the resolved where, and the `with` partition. */
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
import { CircularRelationError, RelationError, UnsupportedFeatureError, ValidationError } from '../errors.js';
|
|
57
57
|
import { normalizeKeyColumns } from '../schema.js';
|
|
58
58
|
import { dedupeOrderEntries, isOrderBySpec, isRelationPickOrderBy, orderByEntries, sortedEntries } from './filters.js';
|
|
59
|
-
import { markInternalCombinator, ownLookup, resolveColumnName, selectNamesNothingMessage, selectOmitExclusiveMessage, sqlToPreparedName, } from './utils.js';
|
|
59
|
+
import { availableClause, markInternalCombinator, ownLookup, resolveColumnName, selectNamesNothingMessage, selectOmitExclusiveMessage, sqlToPreparedName, } from './utils.js';
|
|
60
60
|
/**
|
|
61
61
|
* Max parent keys per follow-up query. On Postgres the whole key set travels as
|
|
62
62
|
* ONE array parameter (`= ANY($1)`), so this is not a bind-parameter limit, it
|
|
@@ -419,7 +419,7 @@ export function resolveCountRelations(parentMeta, countSpec) {
|
|
|
419
419
|
const rel = ownLookup(parentMeta.relations, relName);
|
|
420
420
|
if (!rel) {
|
|
421
421
|
throw new RelationError(`[turbine] Unknown relation "${relName}" in _count on table "${parentMeta.name}". ` +
|
|
422
|
-
|
|
422
|
+
availableClause(Object.keys(parentMeta.relations), 'It has no relations.'));
|
|
423
423
|
}
|
|
424
424
|
if (!isToMany(rel)) {
|
|
425
425
|
throw new ValidationError(`[turbine] _count is only supported for to-many relations; "${relName}" on ` +
|
|
@@ -495,7 +495,7 @@ export async function loadRelationsBatched(ctx, parents, withClause, timeout, de
|
|
|
495
495
|
// E005 for this exact shape (relations.ts), and under 'auto' the two
|
|
496
496
|
// must refuse identically or the error CODE depends on table size.
|
|
497
497
|
throw new RelationError(`[turbine] Unknown relation "${relName}" on table "${ctx.parentMeta.name}". ` +
|
|
498
|
-
|
|
498
|
+
availableClause(Object.keys(ctx.parentMeta.relations), 'It has no relations.'));
|
|
499
499
|
}
|
|
500
500
|
resolved.push({ relName, rel, options: spec === true ? {} : spec });
|
|
501
501
|
}
|
|
@@ -523,7 +523,7 @@ export async function loadRelationsBatched(ctx, parents, withClause, timeout, de
|
|
|
523
523
|
// not a line in a correctness fix. Until then both strategies say no.
|
|
524
524
|
if (hasCount && depth > 0) {
|
|
525
525
|
throw new RelationError(`[turbine] Unknown relation "_count" on table "${ctx.parentMeta.name}". ` +
|
|
526
|
-
|
|
526
|
+
`${availableClause(Object.keys(ctx.parentMeta.relations), 'It has no relations.')} ` +
|
|
527
527
|
'(`_count` is supported on the top-level `with` only, on every relationLoadStrategy.)');
|
|
528
528
|
}
|
|
529
529
|
// Fix key order BEFORE anything is awaited: the loads below all write their
|
package/dist/query/builder.js
CHANGED
|
@@ -23,7 +23,7 @@ import { warnUnknownQueryOptions } from './option-surface.js';
|
|
|
23
23
|
import { applyStableRelationOrderTo, normalizeWithClause } from './relation-names.js';
|
|
24
24
|
import * as relationsMod from './relations.js';
|
|
25
25
|
import { resolveSkipGlobalFilters, resolveUnsafeFlag, UNSAFE } from './types.js';
|
|
26
|
-
import { isTemporalInfinity, LRUCache, normalizePagination, ownLookup, parseDbDate, resolveColumnName, resolveRelation, resolveRelationDef, sqlToPreparedName, unknownFieldMessage, warnRedundantSortTerm, } from './utils.js';
|
|
26
|
+
import { availableClause, isTemporalInfinity, LRUCache, normalizePagination, ownLookup, parseDbDate, resolveColumnName, resolveRelation, resolveRelationDef, sqlToPreparedName, unknownFieldMessage, warnRedundantSortTerm, } from './utils.js';
|
|
27
27
|
import { shouldWarnOnce, WARN_NS } from './warn-registry.js';
|
|
28
28
|
import * as whereMod from './where.js';
|
|
29
29
|
import * as writesMod from './writes.js';
|
|
@@ -669,7 +669,7 @@ export class QueryInterface {
|
|
|
669
669
|
this.schema = schema;
|
|
670
670
|
const meta = schema.tables[table];
|
|
671
671
|
if (!meta) {
|
|
672
|
-
throw new ValidationError(`[turbine] Unknown table "${table}".
|
|
672
|
+
throw new ValidationError(`[turbine] Unknown table "${table}". ${availableClause(Object.keys(schema.tables), 'The schema has no tables.')}`);
|
|
673
673
|
}
|
|
674
674
|
this.tableMeta = meta;
|
|
675
675
|
this.middlewares = middlewares ?? [];
|
|
@@ -71,6 +71,11 @@ export declare function resolveProjection(qi: BuilderCtx, table: string, meta: T
|
|
|
71
71
|
* the existing call sites in builder.ts.
|
|
72
72
|
*/
|
|
73
73
|
export declare function resolveColumns(qi: BuilderCtx, select?: Record<string, boolean>, omit?: Record<string, boolean>, includePii?: boolean): string[] | null;
|
|
74
|
+
/**
|
|
75
|
+
* Read a relation spec as options. See {@link EMPTY_WITH_OPTIONS} for why this
|
|
76
|
+
* is a shared authority rather than a guard repeated per walker.
|
|
77
|
+
*/
|
|
78
|
+
export declare function relationOptions(spec: true | WithOptions): WithOptions;
|
|
74
79
|
/**
|
|
75
80
|
* Produce a fingerprint for a `with` clause tree. Recursion mirrors
|
|
76
81
|
* buildSelectWithRelations / buildRelationSubquery.
|