turbine-orm 0.40.1 → 0.41.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 +22 -4
- package/dist/cjs/cli/config.js +3 -0
- package/dist/cjs/cli/index.js +179 -0
- package/dist/cjs/cli/prisma-report.js +216 -0
- package/dist/cjs/cli/prisma-resolve.js +335 -0
- package/dist/cjs/cli/prisma-schema.js +484 -0
- package/dist/cjs/client.js +1 -0
- package/dist/cjs/generate.js +279 -22
- package/dist/cjs/index.js +3 -2
- package/dist/cjs/introspect.js +203 -26
- package/dist/cjs/mssql.js +9 -10
- package/dist/cjs/mysql.js +3 -9
- package/dist/cjs/powdb-introspect.js +5 -10
- package/dist/cjs/powql.js +13 -0
- package/dist/cjs/prisma-compat.js +1147 -0
- package/dist/cjs/query/aggregates.js +67 -7
- package/dist/cjs/query/builder.js +388 -17
- package/dist/cjs/query/compound-unique.js +0 -0
- package/dist/cjs/query/relations.js +7 -5
- package/dist/cjs/query/warn-registry.js +98 -0
- package/dist/cjs/query/writes.js +13 -5
- package/dist/cjs/schema.js +47 -0
- package/dist/cjs/sqlite.js +4 -9
- package/dist/cli/config.d.ts +26 -0
- package/dist/cli/config.js +3 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.js +180 -1
- package/dist/cli/prisma-report.d.ts +19 -0
- package/dist/cli/prisma-report.js +211 -0
- package/dist/cli/prisma-resolve.d.ts +87 -0
- package/dist/cli/prisma-resolve.js +330 -0
- package/dist/cli/prisma-schema.d.ts +116 -0
- package/dist/cli/prisma-schema.js +479 -0
- package/dist/cli/ui.d.ts +1 -1
- package/dist/client.d.ts +18 -2
- package/dist/client.js +1 -0
- package/dist/generate.d.ts +80 -1
- package/dist/generate.js +277 -25
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/introspect.d.ts +92 -2
- package/dist/introspect.js +198 -26
- package/dist/mssql.js +10 -11
- package/dist/mysql.js +4 -10
- package/dist/powdb-introspect.js +5 -10
- package/dist/powql.js +13 -0
- package/dist/prisma-compat.d.ts +281 -0
- package/dist/prisma-compat.js +1143 -0
- package/dist/query/aggregates.js +67 -7
- package/dist/query/builder.d.ts +77 -4
- package/dist/query/builder.js +390 -19
- package/dist/query/compound-unique.d.ts +49 -0
- package/dist/query/compound-unique.js +0 -0
- package/dist/query/deferred.d.ts +18 -0
- package/dist/query/relations.js +7 -5
- package/dist/query/types.d.ts +70 -9
- package/dist/query/warn-registry.d.ts +57 -0
- package/dist/query/warn-registry.js +92 -0
- package/dist/query/writes.js +13 -5
- package/dist/schema.d.ts +75 -0
- package/dist/schema.js +46 -0
- package/dist/sqlite.js +5 -10
- package/package.json +6 -1
package/dist/query/aggregates.js
CHANGED
|
@@ -92,15 +92,37 @@ export function buildGroupBy(qi, args) {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
// _count
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
// - `true` / omitted → scalar `_count` column (COUNT(*)), result `_count: number`.
|
|
96
|
+
// - record form → one column per selection: `_all` → COUNT(*) AS "_count__all"
|
|
97
|
+
// (double underscore, collision-proof against a real column named `all`),
|
|
98
|
+
// each field → COUNT(col) AS "_count_<col>", result `_count: { _all, field }`.
|
|
99
|
+
const countArg = args._count;
|
|
100
|
+
const countIsRecord = countArg !== true && countArg !== undefined && typeof countArg === 'object';
|
|
101
|
+
const scalarCount = countArg === true || countArg === undefined;
|
|
102
|
+
if (scalarCount) {
|
|
103
|
+
// default: always include the scalar count
|
|
98
104
|
selectExprs.push(`${qi.castAgg('COUNT(*)', 'int')} AS _count`);
|
|
99
105
|
}
|
|
106
|
+
else if (countIsRecord) {
|
|
107
|
+
for (const [field, enabled] of Object.entries(countArg)) {
|
|
108
|
+
if (!enabled)
|
|
109
|
+
continue;
|
|
110
|
+
if (field === '_all') {
|
|
111
|
+
selectExprs.push(`${qi.castAgg('COUNT(*)', 'int')} AS ${qi.q('_count__all')}`);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const col = qi.toColumn(field);
|
|
115
|
+
selectExprs.push(`${qi.castAgg(`COUNT(${qi.q(col)})`, 'int')} AS ${qi.q(`_count_${col}`)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
100
119
|
// ORDER BY aggregate expressions, keyed `${aggKey}:${field}` (plus a bare
|
|
101
120
|
// `_count`). Populated alongside the SELECT list below so `orderBy` can only
|
|
102
121
|
// reference an aggregate that is actually requested. `COUNT(*)` (uncast) is
|
|
103
122
|
// the ordering expression (the SELECT cast is only for the returned value).
|
|
123
|
+
// COUNT(*) is orderable whenever it is selected: scalar `_count`, OR the
|
|
124
|
+
// record form containing `_all`.
|
|
125
|
+
const countSelected = scalarCount || (countIsRecord && countArg._all === true);
|
|
104
126
|
const aggOrderExprs = new Map();
|
|
105
127
|
if (countSelected)
|
|
106
128
|
aggOrderExprs.set('_count', 'COUNT(*)');
|
|
@@ -192,12 +214,34 @@ export function buildGroupBy(qi, args) {
|
|
|
192
214
|
restructured[reader.resultKey] = reader.raw ? row[reader.rowKey] : parsed[reader.resultKey];
|
|
193
215
|
}
|
|
194
216
|
// _count
|
|
217
|
+
// scalar form → the plain `_count` (or driver-lowercased `count`) column.
|
|
218
|
+
// record form → assemble `{ _all, field, ... }` from the `_count__all`
|
|
219
|
+
// and `_count_<col>` columns. `_count__all` MUST be matched before the
|
|
220
|
+
// generic `_count_` prefix (its slice(7) would map through snakeToCamel).
|
|
195
221
|
if ('_count' in row) {
|
|
196
222
|
restructured._count = row._count;
|
|
197
223
|
}
|
|
198
224
|
else if ('count' in row) {
|
|
199
225
|
restructured._count = row.count;
|
|
200
226
|
}
|
|
227
|
+
else {
|
|
228
|
+
const countObj = {};
|
|
229
|
+
let hasCount = false;
|
|
230
|
+
for (const [rawKey, rawValue] of Object.entries(row)) {
|
|
231
|
+
if (rawKey === '_count__all') {
|
|
232
|
+
countObj._all = rawValue;
|
|
233
|
+
hasCount = true;
|
|
234
|
+
}
|
|
235
|
+
else if (rawKey.startsWith('_count_')) {
|
|
236
|
+
const col = rawKey.slice(7);
|
|
237
|
+
const field = qi.tableMeta.reverseColumnMap[col] ?? snakeToCamel(col);
|
|
238
|
+
countObj[field] = rawValue;
|
|
239
|
+
hasCount = true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (hasCount)
|
|
243
|
+
restructured._count = countObj;
|
|
244
|
+
}
|
|
201
245
|
// Collect aggregates into nested objects
|
|
202
246
|
const sumObj = {};
|
|
203
247
|
const avgObj = {};
|
|
@@ -520,6 +564,9 @@ export function buildAggregate(qi, args) {
|
|
|
520
564
|
}
|
|
521
565
|
if (args._count && typeof args._count === 'object') {
|
|
522
566
|
for (const key of Object.keys(args._count)) {
|
|
567
|
+
// `_all` is the reserved COUNT(*) selector, not a column.
|
|
568
|
+
if (key === '_all')
|
|
569
|
+
continue;
|
|
523
570
|
if (!(key in meta.columnMap)) {
|
|
524
571
|
throw new ValidationError(`Unknown column "${key}" in aggregate for table "${qi.table}"`);
|
|
525
572
|
}
|
|
@@ -527,13 +574,20 @@ export function buildAggregate(qi, args) {
|
|
|
527
574
|
}
|
|
528
575
|
}
|
|
529
576
|
const selectExprs = [];
|
|
530
|
-
// _count
|
|
577
|
+
// _count. `true` → scalar COUNT(*). Record form: reserved `_all` → COUNT(*) AS
|
|
578
|
+
// "_count__all" (double underscore, collision-proof against a real column named
|
|
579
|
+
// `all`); each field → COUNT(col) AS "_count_<col>".
|
|
531
580
|
if (args._count === true) {
|
|
532
581
|
selectExprs.push(`${qi.castAgg('COUNT(*)', 'int')} AS _count`);
|
|
533
582
|
}
|
|
534
583
|
else if (args._count && typeof args._count === 'object') {
|
|
535
584
|
for (const [field, enabled] of Object.entries(args._count)) {
|
|
536
|
-
if (enabled)
|
|
585
|
+
if (!enabled)
|
|
586
|
+
continue;
|
|
587
|
+
if (field === '_all') {
|
|
588
|
+
selectExprs.push(`${qi.castAgg('COUNT(*)', 'int')} AS ${qi.q('_count__all')}`);
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
537
591
|
const col = qi.toColumn(field);
|
|
538
592
|
selectExprs.push(`${qi.castAgg(`COUNT(${qi.q(col)})`, 'int')} AS ${qi.q(`_count_${col}`)}`);
|
|
539
593
|
}
|
|
@@ -590,11 +644,17 @@ export function buildAggregate(qi, args) {
|
|
|
590
644
|
aggResult._count = row._count;
|
|
591
645
|
}
|
|
592
646
|
else {
|
|
593
|
-
// Check for per-column counts
|
|
647
|
+
// Check for per-column counts. `_count__all` MUST be matched before the
|
|
648
|
+
// generic `_count_` prefix (its slice(7) is `_all`, which snakeToCamel
|
|
649
|
+
// would mangle to `All`).
|
|
594
650
|
const countObj = {};
|
|
595
651
|
let hasCountFields = false;
|
|
596
652
|
for (const [key, val] of Object.entries(row)) {
|
|
597
|
-
if (key
|
|
653
|
+
if (key === '_count__all') {
|
|
654
|
+
countObj._all = val;
|
|
655
|
+
hasCountFields = true;
|
|
656
|
+
}
|
|
657
|
+
else if (key.startsWith('_count_')) {
|
|
598
658
|
const col = key.slice(7);
|
|
599
659
|
const field = qi.tableMeta.reverseColumnMap[col] ?? snakeToCamel(col);
|
|
600
660
|
countObj[field] = val;
|
package/dist/query/builder.d.ts
CHANGED
|
@@ -47,8 +47,16 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
47
47
|
*/
|
|
48
48
|
private sqlCacheEnabled;
|
|
49
49
|
private readonly dialect;
|
|
50
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Client-level default relation-loading strategy. When nothing is configured
|
|
52
|
+
* this is `'auto'` (the implicit default): per-relation, keep the single-
|
|
53
|
+
* statement join unless the introspected metadata proves a probe is unindexed,
|
|
54
|
+
* in which case that relation falls back to the batched loader. An explicit
|
|
55
|
+
* `'join'`/`'batched'` (client or query level) always wins.
|
|
56
|
+
*/
|
|
51
57
|
private readonly relationLoadStrategy;
|
|
58
|
+
/** Client-level default for {@link applyStableRelationOrder} (off unless configured). */
|
|
59
|
+
private readonly stableRelationOrder;
|
|
52
60
|
/** Nested-relation JSON encoding: 'object' (default) or 'positional'. */
|
|
53
61
|
private readonly jsonEncoding;
|
|
54
62
|
/**
|
|
@@ -79,8 +87,6 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
79
87
|
* must never receive this schema's `::"enum"` cast (see enumTypeForColumn).
|
|
80
88
|
*/
|
|
81
89
|
private readonly crossSchemaTypeColumns;
|
|
82
|
-
/** Tracks tables that have already triggered a deep-with warning (one-time) */
|
|
83
|
-
private readonly deepWithWarned;
|
|
84
90
|
/**
|
|
85
91
|
* Per-table memo of date columns keyed by their camelCase FIELD name.
|
|
86
92
|
* `meta.dateColumns` is keyed by raw snake_case column name, which matches
|
|
@@ -95,6 +101,15 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
95
101
|
private readonly options?;
|
|
96
102
|
/** Set by executeWithMiddleware so queryWithTimeout can include it in events. */
|
|
97
103
|
private currentAction;
|
|
104
|
+
/**
|
|
105
|
+
* Tags the query events of an in-flight `relationLoadStrategy: 'auto'` query
|
|
106
|
+
* that engaged the batched fallback (`'auto-batched'`), so observability sees
|
|
107
|
+
* which queries the auto default re-planned. Same transient-instance-state
|
|
108
|
+
* caveat as {@link currentAction}: set for the whole auto-split operation and
|
|
109
|
+
* cleared afterward; a concurrent unrelated query on the same accessor during
|
|
110
|
+
* that window could read it (a best-effort diagnostic tag, not load-bearing).
|
|
111
|
+
*/
|
|
112
|
+
private currentStrategyTag;
|
|
98
113
|
/**
|
|
99
114
|
* The active query's `skipGlobalFilters` opt-out, set at the top of each
|
|
100
115
|
* `build*` method and read deep in the (synchronous) SQL-build + param-collect
|
|
@@ -181,10 +196,68 @@ export declare class QueryInterface<T extends object, R extends object = {}> {
|
|
|
181
196
|
private inParam;
|
|
182
197
|
/**
|
|
183
198
|
* Resolve the effective relation-loading strategy for a query: the per-query
|
|
184
|
-
* arg wins, then the client-level default, then `'
|
|
199
|
+
* arg wins, then the client-level default, then `'auto'`. Only meaningful when
|
|
185
200
|
* a `with` clause is present; the callers gate on that.
|
|
186
201
|
*/
|
|
187
202
|
private resolveLoadStrategy;
|
|
203
|
+
/**
|
|
204
|
+
* The effective {@link QueryInterfaceOptions.stableRelationOrder} for a query:
|
|
205
|
+
* the per-query arg wins, then the client-level default (off).
|
|
206
|
+
*/
|
|
207
|
+
private resolveStableOrder;
|
|
208
|
+
/**
|
|
209
|
+
* Fill a PK-ascending `orderBy` into every to-many `with` relation that has no
|
|
210
|
+
* explicit one, recursing into nested `with`. Returns a CLONED clause (user
|
|
211
|
+
* args are never mutated); when nothing needs filling it returns the input
|
|
212
|
+
* object unchanged, so the byte-identical fast path stays free. Only called
|
|
213
|
+
* when {@link resolveStableOrder} is true; runs BEFORE `withFingerprint`, so
|
|
214
|
+
* the two orderings get distinct SQL-cache entries automatically. To-one
|
|
215
|
+
* relations are single rows (no array to order) and PK-less targets have
|
|
216
|
+
* nothing stable to order by, so both are left untouched.
|
|
217
|
+
*/
|
|
218
|
+
private applyStableRelationOrder;
|
|
219
|
+
/**
|
|
220
|
+
* Whether a relation can be served by the batched loader, i.e. all its
|
|
221
|
+
* correlation keys are single-column (the loader throws E017 on composite
|
|
222
|
+
* keys). Composite-key relations therefore always stay on the join plan under
|
|
223
|
+
* `'auto'` (and keep the existing unindexed-probe dev warning).
|
|
224
|
+
*/
|
|
225
|
+
private relationBatchEligible;
|
|
226
|
+
/**
|
|
227
|
+
* The verdict for one relation SUBTREE under `'auto'`: is any probe in the
|
|
228
|
+
* subtree unindexed, is EVERY relation in the subtree batched-eligible, and
|
|
229
|
+
* the first unindexed probe found (for the dev note). Subtree-atomic: a whole
|
|
230
|
+
* top-level relation falls back only when its entire subtree is eligible,
|
|
231
|
+
* mirroring the batched loader recursing the same tree.
|
|
232
|
+
*/
|
|
233
|
+
private autoSubtreeVerdict;
|
|
234
|
+
/** The `_count` verdict under `'auto'`: any counted probe unindexed + all single-key. */
|
|
235
|
+
private autoCountVerdict;
|
|
236
|
+
/**
|
|
237
|
+
* Partition a top-level `with` clause under `'auto'`: each relation whose
|
|
238
|
+
* subtree has a PROVEN unindexed probe AND is fully batched-eligible routes to
|
|
239
|
+
* `batchedWith`; everything else (indexed, composite-key, unknown) stays in
|
|
240
|
+
* `joinWith` (byte-identical join). The reserved `_count` key partitions the
|
|
241
|
+
* same way. Also returns the engaged relations for the dev note.
|
|
242
|
+
*/
|
|
243
|
+
private partitionWithForAuto;
|
|
244
|
+
/**
|
|
245
|
+
* Plan the `'auto'` split for a query's `with` clause: normalize stable order,
|
|
246
|
+
* partition, and return the split ONLY when at least one relation falls back
|
|
247
|
+
* to batched. Returns `null` (→ run the plain join path, byte-identical, same
|
|
248
|
+
* cache keys) when there is no DB-backed index metadata or nothing qualifies.
|
|
249
|
+
*/
|
|
250
|
+
private planAuto;
|
|
251
|
+
/** Dev-only once-per-relation note that `'auto'` engaged the batched fallback. */
|
|
252
|
+
private emitAutoNotes;
|
|
253
|
+
/**
|
|
254
|
+
* Execute a findMany/findUnique `'auto'` split: run the base query with the
|
|
255
|
+
* residual `joinWith` (plus any parent stitch keys the batched subset needs),
|
|
256
|
+
* then load `batchedWith` via the batched loader and stitch. `single` returns
|
|
257
|
+
* the first entity (findUnique) instead of the array. Output is identical in
|
|
258
|
+
* shape to the pure join plan.
|
|
259
|
+
*/
|
|
260
|
+
private runAutoSplit;
|
|
188
261
|
/**
|
|
189
262
|
* Build the {@link RelationLoadContext} the batched loader needs, closing over
|
|
190
263
|
* this interface's pool/dialect/executor. Child readers are constructed on the
|