turbine-orm 0.76.0 → 0.77.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/dist/cjs/cli/index.js +2 -2
- package/dist/cjs/cli/migrate.js +8 -8
- package/dist/cjs/cli/studio.js +6 -3
- package/dist/cjs/client.js +10 -10
- package/dist/cjs/dialect.js +1 -1
- package/dist/cjs/errors.d.ts +2 -2
- package/dist/cjs/errors.js +21 -25
- package/dist/cjs/generate.js +1 -1
- package/dist/cjs/introspect.js +5 -5
- package/dist/cjs/mssql.js +12 -12
- package/dist/cjs/mysql.js +5 -5
- package/dist/cjs/nested-write.js +31 -31
- package/dist/cjs/observe.js +1 -1
- package/dist/cjs/pipeline-submittable.js +5 -1
- package/dist/cjs/pipeline.js +8 -0
- package/dist/cjs/powdb-introspect.d.ts +1 -1
- package/dist/cjs/powdb-introspect.js +4 -4
- package/dist/cjs/powdb-shared.d.ts +348 -0
- package/dist/cjs/powdb-shared.js +587 -0
- package/dist/cjs/powdb.d.ts +12 -299
- package/dist/cjs/powdb.js +106 -567
- package/dist/cjs/powql.d.ts +1 -1
- package/dist/cjs/powql.js +89 -89
- package/dist/cjs/prisma-compat.js +16 -16
- package/dist/cjs/query/aggregates.js +19 -20
- package/dist/cjs/query/batched-loader.js +8 -8
- package/dist/cjs/query/builder.js +8 -8
- package/dist/cjs/query/compound-unique.js +2 -2
- package/dist/cjs/query/filters.js +1 -1
- package/dist/cjs/query/relations.js +60 -48
- package/dist/cjs/query/types.js +4 -4
- package/dist/cjs/query/utils.js +5 -5
- package/dist/cjs/query/warn-registry.d.ts +7 -0
- package/dist/cjs/query/warn-registry.js +7 -0
- package/dist/cjs/query/where-compile.js +1 -1
- package/dist/cjs/query/where.js +30 -31
- package/dist/cjs/query/writes.js +7 -7
- package/dist/cjs/realtime.js +4 -4
- package/dist/cjs/schema-metadata.js +1 -1
- package/dist/cjs/schema-sql.js +3 -3
- package/dist/cjs/seed.js +1 -1
- package/dist/cjs/sqlite.js +7 -7
- package/dist/cjs/typed-sql.js +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/migrate.js +8 -8
- package/dist/cli/studio.js +6 -3
- package/dist/client.js +10 -10
- package/dist/dialect.js +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +21 -25
- package/dist/generate.js +1 -1
- package/dist/introspect.js +5 -5
- package/dist/mssql.js +12 -12
- package/dist/mysql.js +5 -5
- package/dist/nested-write.js +31 -31
- package/dist/observe.js +1 -1
- package/dist/pipeline-submittable.js +6 -2
- package/dist/pipeline.js +9 -1
- package/dist/powdb-introspect.d.ts +1 -1
- package/dist/powdb-introspect.js +2 -2
- package/dist/powdb-shared.d.ts +348 -0
- package/dist/powdb-shared.js +570 -0
- package/dist/powdb.d.ts +12 -299
- package/dist/powdb.js +71 -534
- package/dist/powql.d.ts +1 -1
- package/dist/powql.js +43 -43
- package/dist/prisma-compat.js +16 -16
- package/dist/query/aggregates.js +19 -20
- package/dist/query/batched-loader.js +8 -8
- package/dist/query/builder.js +8 -8
- package/dist/query/compound-unique.js +2 -2
- package/dist/query/filters.js +1 -1
- package/dist/query/relations.js +60 -48
- package/dist/query/types.js +4 -4
- package/dist/query/utils.js +5 -5
- package/dist/query/warn-registry.d.ts +7 -0
- package/dist/query/warn-registry.js +7 -0
- package/dist/query/where-compile.js +1 -1
- package/dist/query/where.js +30 -31
- package/dist/query/writes.js +7 -7
- package/dist/realtime.js +4 -4
- package/dist/schema-metadata.js +1 -1
- package/dist/schema-sql.js +3 -3
- package/dist/seed.js +1 -1
- package/dist/sqlite.js +7 -7
- package/dist/typed-sql.js +1 -1
- package/package.json +15 -7
|
@@ -192,8 +192,7 @@ function resolveProjection(qi, table, meta, select, omit, includePii) {
|
|
|
192
192
|
// numeric indices and throw a cryptic `Unknown field "0"`, catch it early
|
|
193
193
|
// with an actionable message.
|
|
194
194
|
if (Array.isArray(select)) {
|
|
195
|
-
throw new errors_js_1.ValidationError(`
|
|
196
|
-
`(e.g. { id: true, name: true }), not an array.`);
|
|
195
|
+
throw new errors_js_1.ValidationError(`"select" must be an object mapping field names to true ` + `(e.g. { id: true, name: true }), not an array.`);
|
|
197
196
|
}
|
|
198
197
|
// Only include columns where value is true. An explicit `select` naming a
|
|
199
198
|
// PII column IS the opt-in: it comes back regardless of `includePii`.
|
|
@@ -235,7 +234,7 @@ function resolveProjection(qi, table, meta, select, omit, includePii) {
|
|
|
235
234
|
const hasPii = piiCols !== undefined && piiCols.size > 0;
|
|
236
235
|
if (omit) {
|
|
237
236
|
if (Array.isArray(omit)) {
|
|
238
|
-
throw new errors_js_1.ValidationError(`
|
|
237
|
+
throw new errors_js_1.ValidationError(`"omit" must be an object mapping field names to true ` + `(e.g. { createdAt: true }), not an array.`);
|
|
239
238
|
}
|
|
240
239
|
// Include all columns except those where value is true (and PII columns).
|
|
241
240
|
const omitCols = new Set(Object.entries(omit)
|
|
@@ -586,7 +585,7 @@ function buildOrderBy(qi, orderBy, params, lateralSink) {
|
|
|
586
585
|
if (meta)
|
|
587
586
|
resolveOrderByColumn(qi, qi.table, meta, key);
|
|
588
587
|
if (!params) {
|
|
589
|
-
throw new errors_js_1.ValidationError(`
|
|
588
|
+
throw new errors_js_1.ValidationError(`Vector distance ordering on "${key}" is only supported in a top-level findMany orderBy.`);
|
|
590
589
|
}
|
|
591
590
|
const rawColumn = qi.toColumn(key);
|
|
592
591
|
const operator = whereMod.vectorOperator(qi, key, rawColumn, value.distance.metric);
|
|
@@ -666,7 +665,7 @@ function nullsSuffix(qi, nulls) {
|
|
|
666
665
|
function resolveOrderByColumn(_qi, table, meta, key) {
|
|
667
666
|
const col = (0, utils_js_1.resolveColumnName)(meta, key);
|
|
668
667
|
if (col === undefined) {
|
|
669
|
-
throw new errors_js_1.ValidationError(`
|
|
668
|
+
throw new errors_js_1.ValidationError(`Unknown field "${key}" in orderBy on table "${table}". ` +
|
|
670
669
|
`Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`);
|
|
671
670
|
}
|
|
672
671
|
return col;
|
|
@@ -682,12 +681,12 @@ function validateJsonPathOrderBy(qi, table, meta, field, spec) {
|
|
|
682
681
|
const col = resolveOrderByColumn(qi, table, meta, field);
|
|
683
682
|
if (spec.path.length === 0 ||
|
|
684
683
|
spec.path.some((el) => typeof el !== 'string' && !(typeof el === 'number' && Number.isFinite(el)))) {
|
|
685
|
-
throw new errors_js_1.ValidationError(`
|
|
684
|
+
throw new errors_js_1.ValidationError(`JSON-path orderBy on "${field}" (table "${table}") requires a non-empty \`path\` array ` +
|
|
686
685
|
`of keys/indexes (e.g. { path: ['weight'], direction: 'asc' }).`);
|
|
687
686
|
}
|
|
688
687
|
const colType = whereMod.pgTypeForColumn(qi, meta, col);
|
|
689
688
|
if (!whereMod.isJsonColumnType(qi, colType)) {
|
|
690
|
-
throw new errors_js_1.ValidationError(`
|
|
689
|
+
throw new errors_js_1.ValidationError(`JSON-path orderBy on "${field}": column "${col}" on table "${table}" is not a JSON column ` +
|
|
691
690
|
`(actual type: ${colType}).`);
|
|
692
691
|
}
|
|
693
692
|
return col;
|
|
@@ -704,7 +703,7 @@ function validateJsonPathOrderBy(qi, table, meta, field, spec) {
|
|
|
704
703
|
function buildJsonPathOrderEntry(qi, table, meta, field, spec, prefix, params) {
|
|
705
704
|
const col = validateJsonPathOrderBy(qi, table, meta, field, spec);
|
|
706
705
|
if (!params) {
|
|
707
|
-
throw new errors_js_1.ValidationError(`
|
|
706
|
+
throw new errors_js_1.ValidationError(`JSON-path ordering on "${field}" is not supported in this orderBy context.`);
|
|
708
707
|
}
|
|
709
708
|
params.push(whereMod.jsonPathParam(qi, spec.path));
|
|
710
709
|
const extract = qi.dialect.buildJsonPathExtract(`${prefix}${qi.q(col)}`, qi.p(params.length));
|
|
@@ -756,14 +755,14 @@ function buildChainedToOneOrderBy(qi, head, nextRelName, nextValue, params) {
|
|
|
756
755
|
// relation object. Bounded by the same depth cap as nested `with`.
|
|
757
756
|
for (;;) {
|
|
758
757
|
if (!currentMeta)
|
|
759
|
-
throw new errors_js_1.RelationError(`
|
|
758
|
+
throw new errors_js_1.RelationError(`Unknown relation target in orderBy chain "${path.join('.')}"`);
|
|
760
759
|
const relDef = (0, utils_js_1.ownLookup)(currentMeta.relations, relName);
|
|
761
760
|
if (!relDef) {
|
|
762
|
-
throw new errors_js_1.ValidationError(`
|
|
761
|
+
throw new errors_js_1.ValidationError(`Unknown relation "${relName}" in orderBy on relation "${path.join('.')}" ` +
|
|
763
762
|
`(table "${currentMeta.name}"). Available: ${Object.keys(currentMeta.relations).join(', ') || '(none)'}.`);
|
|
764
763
|
}
|
|
765
764
|
if (relDef.type !== 'belongsTo' && relDef.type !== 'hasOne') {
|
|
766
|
-
throw new errors_js_1.ValidationError(`
|
|
765
|
+
throw new errors_js_1.ValidationError(`orderBy cannot traverse the to-many relation "${relName}" on "${currentMeta.name}" ` +
|
|
767
766
|
`(path "${path.concat(relName).join('.')}"): a to-many relation has no single value to order by. ` +
|
|
768
767
|
`Use a pick-row ordering ({ pick, by }) at the top level, or order by "_count".`);
|
|
769
768
|
}
|
|
@@ -772,7 +771,7 @@ function buildChainedToOneOrderBy(qi, head, nextRelName, nextValue, params) {
|
|
|
772
771
|
}
|
|
773
772
|
const nextMeta = qi.schema.tables[relDef.to];
|
|
774
773
|
if (!nextMeta)
|
|
775
|
-
throw new errors_js_1.RelationError(`
|
|
774
|
+
throw new errors_js_1.RelationError(`Unknown relation target "${relDef.to}" in orderBy`);
|
|
776
775
|
const nextAlias = `${head.alias}c${hop}`;
|
|
777
776
|
const on = relDef.type === 'belongsTo'
|
|
778
777
|
? qi.dialect.buildCorrelation(nextAlias, relDef.referenceKey, currentAlias, relDef.foreignKey)
|
|
@@ -790,7 +789,7 @@ function buildChainedToOneOrderBy(qi, head, nextRelName, nextValue, params) {
|
|
|
790
789
|
// Exactly one key per level, as with the single-hop form.
|
|
791
790
|
const entries = Object.entries(value);
|
|
792
791
|
if (entries.length !== 1) {
|
|
793
|
-
throw new errors_js_1.ValidationError(`
|
|
792
|
+
throw new errors_js_1.ValidationError(`orderBy on relation "${path.join('.')}" needs exactly one key per level ` +
|
|
794
793
|
`(got: ${entries.map(([k]) => k).join(', ') || '(empty)'}).`);
|
|
795
794
|
}
|
|
796
795
|
const [key, entryValue] = entries[0];
|
|
@@ -806,7 +805,7 @@ function buildChainedToOneOrderBy(qi, head, nextRelName, nextValue, params) {
|
|
|
806
805
|
// Terminal: a column on the last table in the chain.
|
|
807
806
|
const snakeCol = (0, utils_js_1.resolveColumnName)(currentMeta, key);
|
|
808
807
|
if (snakeCol === undefined) {
|
|
809
|
-
throw new errors_js_1.ValidationError(`
|
|
808
|
+
throw new errors_js_1.ValidationError(`Unknown column "${key}" in orderBy on relation "${path.join('.')}" (table "${currentMeta.name}").`);
|
|
810
809
|
}
|
|
811
810
|
(0, types_js_1.assertOrderDirection)(entryValue, `orderBy on relation path "${path.join('.')}"`);
|
|
812
811
|
const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(entryValue);
|
|
@@ -854,9 +853,9 @@ function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lateralSin
|
|
|
854
853
|
// missing relation.
|
|
855
854
|
const isColumn = (0, utils_js_1.resolveColumnName)(ownerMeta, relName) !== undefined;
|
|
856
855
|
throw new errors_js_1.RelationError(isColumn
|
|
857
|
-
? `
|
|
856
|
+
? `orderBy on "${ownerTable}.${relName}" got a relation-shaped value, but "${relName}" is a ` +
|
|
858
857
|
`column. Order a column with 'asc' / 'desc' (or { sort, nulls }); the object form is for relations.`
|
|
859
|
-
: `
|
|
858
|
+
: `Unknown relation "${relName}" in orderBy on table "${ownerTable}". ` +
|
|
860
859
|
(0, utils_js_1.availableClause)(Object.keys(ownerMeta.relations), `"${ownerTable}" has no relations.`));
|
|
861
860
|
}
|
|
862
861
|
// Pick-row ordering (`{ pick, by }`): order by a value from ONE related
|
|
@@ -872,7 +871,7 @@ function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lateralSin
|
|
|
872
871
|
if (relDef.type === 'hasMany' || relDef.type === 'manyToMany') {
|
|
873
872
|
const keys = Object.keys(value);
|
|
874
873
|
if (keys.length !== 1 || keys[0] !== '_count') {
|
|
875
|
-
throw new errors_js_1.ValidationError(`
|
|
874
|
+
throw new errors_js_1.ValidationError(`orderBy on to-many relation "${relName}" only supports "_count" ` +
|
|
876
875
|
`or a pick-row ordering ({ pick, by }) (got: ${keys.join(', ') || '(empty)'}).`);
|
|
877
876
|
}
|
|
878
877
|
(0, types_js_1.assertOrderDirection)(value._count, `orderBy { ${relName}: { _count } }`);
|
|
@@ -882,7 +881,7 @@ function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lateralSin
|
|
|
882
881
|
// To-one: each entry orders by a correlated scalar subquery on a target column.
|
|
883
882
|
const targetMeta = qi.schema.tables[relDef.to];
|
|
884
883
|
if (!targetMeta)
|
|
885
|
-
throw new errors_js_1.RelationError(`
|
|
884
|
+
throw new errors_js_1.RelationError(`Unknown relation target "${relDef.to}"`);
|
|
886
885
|
const qTarget = qi.q(relDef.to);
|
|
887
886
|
const qParent = qi.q(parentRef);
|
|
888
887
|
// belongsTo: alias.referenceKey = parent.foreignKey; hasOne: reversed.
|
|
@@ -891,7 +890,7 @@ function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lateralSin
|
|
|
891
890
|
: qi.dialect.buildCorrelation(alias, relDef.foreignKey, qParent, relDef.referenceKey);
|
|
892
891
|
const entries = Object.entries(value);
|
|
893
892
|
if (entries.length === 0) {
|
|
894
|
-
throw new errors_js_1.ValidationError(`
|
|
893
|
+
throw new errors_js_1.ValidationError(`orderBy on to-one relation "${relName}" needs at least one target column.`);
|
|
895
894
|
}
|
|
896
895
|
return entries
|
|
897
896
|
.map(([col, dirValue]) => {
|
|
@@ -911,7 +910,7 @@ function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lateralSin
|
|
|
911
910
|
? ` "${col}" is a relation on "${relDef.to}": order by one of ITS columns, e.g. ` +
|
|
912
911
|
`{ ${relName}: { ${col}: { <column>: 'asc' } } }.`
|
|
913
912
|
: '';
|
|
914
|
-
throw new errors_js_1.ValidationError(`
|
|
913
|
+
throw new errors_js_1.ValidationError(`Unknown column "${col}" in orderBy on relation "${relName}" (table "${relDef.to}").${relationHint}`);
|
|
915
914
|
}
|
|
916
915
|
(0, types_js_1.assertOrderDirection)(dirValue, `orderBy "${col}" on relation "${relName}"`);
|
|
917
916
|
const { dir, nulls } = (0, filters_js_1.normalizeOrderBy)(dirValue);
|
|
@@ -942,7 +941,7 @@ function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lateralSin
|
|
|
942
941
|
* - `by` must be a target column name or a `{ field, path }` JSON-path spec.
|
|
943
942
|
*/
|
|
944
943
|
function pickOrderNestedError(_qi, relName) {
|
|
945
|
-
return new errors_js_1.ValidationError(`
|
|
944
|
+
return new errors_js_1.ValidationError(`Pick-row ordering on relation "${relName}" is only supported in a top-level ` +
|
|
946
945
|
'findMany orderBy: nested `with` orderBy does not support it.');
|
|
947
946
|
}
|
|
948
947
|
function validatePickOrderBy(qi, relName, relDef, spec, nested) {
|
|
@@ -950,10 +949,10 @@ function validatePickOrderBy(qi, relName, relDef, spec, nested) {
|
|
|
950
949
|
throw pickOrderNestedError(qi, relName);
|
|
951
950
|
}
|
|
952
951
|
if (relDef.type === 'manyToMany') {
|
|
953
|
-
throw new errors_js_1.ValidationError(`
|
|
952
|
+
throw new errors_js_1.ValidationError(`Pick-row ordering is not supported on manyToMany relation "${relName}": hasMany relations only.`);
|
|
954
953
|
}
|
|
955
954
|
if (relDef.type !== 'hasMany') {
|
|
956
|
-
throw new errors_js_1.ValidationError(`
|
|
955
|
+
throw new errors_js_1.ValidationError(`Pick-row ordering is only for to-many (hasMany) relations; "${relName}" is ${relDef.type}. ` +
|
|
957
956
|
`Order by the target column directly instead ({ ${relName}: { <column>: 'asc' } }).`);
|
|
958
957
|
}
|
|
959
958
|
const pickOrder = spec.pick?.orderBy;
|
|
@@ -962,20 +961,20 @@ function validatePickOrderBy(qi, relName, relDef, spec, nested) {
|
|
|
962
961
|
typeof pickOrder !== 'object' ||
|
|
963
962
|
pickOrder === null ||
|
|
964
963
|
Object.keys(pickOrder).length === 0) {
|
|
965
|
-
throw new errors_js_1.ValidationError(`
|
|
964
|
+
throw new errors_js_1.ValidationError(`Pick-row ordering on relation "${relName}" requires \`pick.orderBy\` to choose ONE ` +
|
|
966
965
|
"related row deterministically (e.g. pick: { orderBy: { createdAt: 'desc' } }).");
|
|
967
966
|
}
|
|
968
967
|
const by = spec.by;
|
|
969
968
|
const validJsonBy = typeof by === 'object' && by !== null && typeof by.field === 'string' && Array.isArray(by.path);
|
|
970
969
|
if (typeof by !== 'string' && !validJsonBy) {
|
|
971
|
-
throw new errors_js_1.ValidationError(`
|
|
970
|
+
throw new errors_js_1.ValidationError(`Pick-row ordering on relation "${relName}" requires \`by\`: a target column name ` +
|
|
972
971
|
"or a JSON-path spec ({ field: 'data', path: ['title'] }).");
|
|
973
972
|
}
|
|
974
973
|
// Physical plan gate. A typo like `plan: 'latreal'` must never silently run
|
|
975
974
|
// the subquery plan (a silent plan change wearing a validation gap). Shared
|
|
976
975
|
// by build and cache-hit collect so a warmed cache throws identically.
|
|
977
976
|
if (spec.plan !== undefined && spec.plan !== 'subquery' && spec.plan !== 'lateral') {
|
|
978
|
-
throw new errors_js_1.ValidationError(`
|
|
977
|
+
throw new errors_js_1.ValidationError(`Pick-row ordering on relation "${relName}" has an invalid \`plan\`: ` +
|
|
979
978
|
`${JSON.stringify(spec.plan)}. Use 'subquery' (default) or 'lateral'.`);
|
|
980
979
|
}
|
|
981
980
|
if (spec.plan === 'lateral') {
|
|
@@ -986,7 +985,7 @@ function validatePickOrderBy(qi, relName, relDef, spec, nested) {
|
|
|
986
985
|
// parent column with that exact name would make the unqualified WHERE
|
|
987
986
|
// reference ambiguous once the join is in scope; refuse it explicitly.
|
|
988
987
|
if (qi.tableMeta.allColumns.includes('__turbine_pick')) {
|
|
989
|
-
throw new errors_js_1.ValidationError(`
|
|
988
|
+
throw new errors_js_1.ValidationError(`Pick-row ordering with plan: 'lateral' cannot be used: table "${qi.tableMeta.name}" ` +
|
|
990
989
|
'has a column named "__turbine_pick", which the lateral join output reserves.');
|
|
991
990
|
}
|
|
992
991
|
}
|
|
@@ -1010,11 +1009,11 @@ function validatePickOrderBy(qi, relName, relDef, spec, nested) {
|
|
|
1010
1009
|
*/
|
|
1011
1010
|
function buildRelationPickOrderBy(qi, relName, relDef, spec, alias, parentRef, params, lateralSink) {
|
|
1012
1011
|
if (!params) {
|
|
1013
|
-
throw new errors_js_1.ValidationError(`
|
|
1012
|
+
throw new errors_js_1.ValidationError(`Pick-row ordering on relation "${relName}" is only supported in a top-level findMany orderBy.`);
|
|
1014
1013
|
}
|
|
1015
1014
|
const targetMeta = qi.schema.tables[relDef.to];
|
|
1016
1015
|
if (!targetMeta)
|
|
1017
|
-
throw new errors_js_1.RelationError(`
|
|
1016
|
+
throw new errors_js_1.RelationError(`Unknown relation target "${relDef.to}"`);
|
|
1018
1017
|
(0, types_js_1.assertDirectionToken)(spec.direction, `pick-row orderBy on relation "${relName}"`);
|
|
1019
1018
|
const dir = spec.direction?.toLowerCase() === 'desc' ? 'DESC' : 'ASC';
|
|
1020
1019
|
const limitOne = qi.buildPagination('1', undefined, true);
|
|
@@ -1039,7 +1038,7 @@ function buildRelationPickOrderBy(qi, relName, relDef, spec, alias, parentRef, p
|
|
|
1039
1038
|
// context and hard-fails rather than silently emitting a subquery.
|
|
1040
1039
|
if (spec.plan === 'lateral') {
|
|
1041
1040
|
if (!lateralSink) {
|
|
1042
|
-
throw new errors_js_1.ValidationError(`
|
|
1041
|
+
throw new errors_js_1.ValidationError(`Pick-row ordering with plan: 'lateral' on relation "${relName}" is only supported ` +
|
|
1043
1042
|
'in a top-level findMany orderBy.');
|
|
1044
1043
|
}
|
|
1045
1044
|
const childAlias = `${alias}i`;
|
|
@@ -1106,7 +1105,7 @@ function collectRelationPickOrderParams(qi, relName, relDef, spec, params) {
|
|
|
1106
1105
|
validatePickOrderBy(qi, relName, relDef, spec, false);
|
|
1107
1106
|
const targetMeta = qi.schema.tables[relDef.to];
|
|
1108
1107
|
if (!targetMeta)
|
|
1109
|
-
throw new errors_js_1.RelationError(`
|
|
1108
|
+
throw new errors_js_1.RelationError(`Unknown relation target "${relDef.to}"`);
|
|
1110
1109
|
if (typeof spec.by === 'string') {
|
|
1111
1110
|
resolveOrderByColumn(qi, relDef.to, targetMeta, spec.by);
|
|
1112
1111
|
}
|
|
@@ -1151,7 +1150,7 @@ function buildRelationOrderClause(qi, targetTable, targetMeta, alias, orderEntri
|
|
|
1151
1150
|
const orders = orderEntries
|
|
1152
1151
|
.map(([key, dirValue]) => {
|
|
1153
1152
|
if ((0, filters_js_1.isVectorOrderBy)(dirValue)) {
|
|
1154
|
-
throw new errors_js_1.ValidationError(`
|
|
1153
|
+
throw new errors_js_1.ValidationError(`Vector distance ordering on "${key}" is only supported in a top-level findMany orderBy.`);
|
|
1155
1154
|
}
|
|
1156
1155
|
if ((0, filters_js_1.isJsonPathOrderBy)(dirValue)) {
|
|
1157
1156
|
return buildJsonPathOrderEntry(qi, targetTable, targetMeta, key, dirValue, `${alias}.`, params);
|
|
@@ -1177,7 +1176,7 @@ function buildRelationOrderClause(qi, targetTable, targetMeta, alias, orderEntri
|
|
|
1177
1176
|
function collectRelationOrderParams(qi, targetTable, targetMeta, orderEntries, params) {
|
|
1178
1177
|
for (const [key, dirValue] of orderEntries) {
|
|
1179
1178
|
if ((0, filters_js_1.isVectorOrderBy)(dirValue)) {
|
|
1180
|
-
throw new errors_js_1.ValidationError(`
|
|
1179
|
+
throw new errors_js_1.ValidationError(`Vector distance ordering on "${key}" is only supported in a top-level findMany orderBy.`);
|
|
1181
1180
|
}
|
|
1182
1181
|
if ((0, filters_js_1.isJsonPathOrderBy)(dirValue)) {
|
|
1183
1182
|
validateJsonPathOrderBy(qi, targetTable, targetMeta, key, dirValue);
|
|
@@ -1221,7 +1220,7 @@ function buildRelationCountExpr(qi, relDef, parentRef, alias, params) {
|
|
|
1221
1220
|
const count = qi.castAgg('COUNT(*)', 'int');
|
|
1222
1221
|
if (relDef.type === 'manyToMany') {
|
|
1223
1222
|
if (!relDef.through) {
|
|
1224
|
-
throw new errors_js_1.ValidationError(`
|
|
1223
|
+
throw new errors_js_1.ValidationError(`manyToMany relation "${relDef.name}" is missing its \`through\` junction.`);
|
|
1225
1224
|
}
|
|
1226
1225
|
const qJ = qi.q(relDef.through.table);
|
|
1227
1226
|
const jalias = `${alias}j`;
|
|
@@ -1405,9 +1404,22 @@ function parseNestedRow(qi, row, table, fromJson = false) {
|
|
|
1405
1404
|
parsed[relName] = jsonVal;
|
|
1406
1405
|
}
|
|
1407
1406
|
}
|
|
1408
|
-
catch {
|
|
1409
|
-
|
|
1410
|
-
|
|
1407
|
+
catch (err) {
|
|
1408
|
+
// A relation column that does not parse is a bug in the emitted SQL or
|
|
1409
|
+
// a driver-level corruption, not a recoverable condition. Handing back
|
|
1410
|
+
// the raw string satisfied the assignment and violated the generated
|
|
1411
|
+
// type: callers got a `string` where `Post[]` was promised, and found
|
|
1412
|
+
// out at the first `.map`. Throw instead, with the context needed to
|
|
1413
|
+
// find it.
|
|
1414
|
+
//
|
|
1415
|
+
// The warn is gated and deduped because this sits in parseNestedRow's
|
|
1416
|
+
// per-relation loop: one 10,000-row page was 10,000 console.warn calls.
|
|
1417
|
+
if (process.env.NODE_ENV !== 'production' &&
|
|
1418
|
+
(0, warn_registry_js_1.shouldWarnOnce)(warn_registry_js_1.WARN_NS.relationParseFailure, `${qi.table}.${relName}`)) {
|
|
1419
|
+
console.warn(`[turbine] Relation "${relName}" on "${qi.table}" returned a payload that is not valid JSON. ` +
|
|
1420
|
+
`This is a bug; please report it with the emitted SQL.`);
|
|
1421
|
+
}
|
|
1422
|
+
throw new errors_js_1.ValidationError(`Relation "${relName}" on table "${qi.table}" returned a payload that could not be parsed as JSON.`, { cause: err });
|
|
1411
1423
|
}
|
|
1412
1424
|
}
|
|
1413
1425
|
else if (Array.isArray(rawValue)) {
|
|
@@ -2238,7 +2250,7 @@ function makeFlattenParser(qi, plan) {
|
|
|
2238
2250
|
function buildSelectWithRelations(qi, table, withClause, params, columnsList, depth, path, includePii, flatten) {
|
|
2239
2251
|
const meta = qi.schema.tables[table];
|
|
2240
2252
|
if (!meta)
|
|
2241
|
-
throw new errors_js_1.ValidationError(`
|
|
2253
|
+
throw new errors_js_1.ValidationError(`Unknown table "${table}"`);
|
|
2242
2254
|
// Positional JSON encoding is Postgres-only in v1. Gate here, the single
|
|
2243
2255
|
// entry point for every `with` clause, so no engine ever emits the
|
|
2244
2256
|
// json_build_array shape its dialect can't produce (and mssql's FOR JSON
|
|
@@ -2257,7 +2269,7 @@ function buildSelectWithRelations(qi, table, withClause, params, columnsList, de
|
|
|
2257
2269
|
continue;
|
|
2258
2270
|
const relDef = (0, utils_js_1.ownLookup)(meta.relations, relName);
|
|
2259
2271
|
if (!relDef) {
|
|
2260
|
-
throw new errors_js_1.RelationError(`
|
|
2272
|
+
throw new errors_js_1.RelationError(`Unknown relation "${relName}" on table "${table}". ` +
|
|
2261
2273
|
(0, utils_js_1.availableClause)(Object.keys(meta.relations), `"${table}" has no relations.`));
|
|
2262
2274
|
}
|
|
2263
2275
|
// `relationLoadStrategy: 'flatten'`: an eligible to-one relation becomes a
|
|
@@ -2391,7 +2403,7 @@ function buildRelationSubquery(qi, relDef, spec, params, parentRef, aliasCounter
|
|
|
2391
2403
|
}
|
|
2392
2404
|
const targetMeta = qi.schema.tables[targetTable];
|
|
2393
2405
|
if (!targetMeta)
|
|
2394
|
-
throw new errors_js_1.RelationError(`
|
|
2406
|
+
throw new errors_js_1.RelationError(`Unknown relation target "${targetTable}"`);
|
|
2395
2407
|
// `true` IS `{}`; below this line there is no `true` case, which is what
|
|
2396
2408
|
// keeps this walk and collectRelationSubqueryParams reading one shape.
|
|
2397
2409
|
// The raw `spec` is still what crosses the dialect seam below, since
|
|
@@ -2496,7 +2508,7 @@ function buildRelationSubquery(qi, relDef, spec, params, parentRef, aliasCounter
|
|
|
2496
2508
|
for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(opts.with)) {
|
|
2497
2509
|
const nestedRelDef = (0, utils_js_1.ownLookup)(targetMeta.relations, nestedRelName);
|
|
2498
2510
|
if (!nestedRelDef) {
|
|
2499
|
-
throw new errors_js_1.RelationError(`
|
|
2511
|
+
throw new errors_js_1.RelationError(`Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
|
|
2500
2512
|
(0, utils_js_1.availableClause)(Object.keys(targetMeta.relations), `"${relDef.to}" has no relations.`));
|
|
2501
2513
|
}
|
|
2502
2514
|
// Recursively build nested subquery, passing THIS alias as the parent reference
|
|
@@ -2570,7 +2582,7 @@ function buildRelationSubquery(qi, relDef, spec, params, parentRef, aliasCounter
|
|
|
2570
2582
|
for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(opts.with)) {
|
|
2571
2583
|
const nestedRelDef = (0, utils_js_1.ownLookup)(targetMeta.relations, nestedRelName);
|
|
2572
2584
|
if (!nestedRelDef) {
|
|
2573
|
-
throw new errors_js_1.RelationError(`
|
|
2585
|
+
throw new errors_js_1.RelationError(`Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
|
|
2574
2586
|
(0, utils_js_1.availableClause)(Object.keys(targetMeta.relations), `"${relDef.to}" has no relations.`));
|
|
2575
2587
|
}
|
|
2576
2588
|
const nestedSub = buildRelationSubquery(qi, nestedRelDef, nestedSpec, params, innerAlias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name], includePii);
|
|
@@ -2614,7 +2626,7 @@ function buildRelationSubquery(qi, relDef, spec, params, parentRef, aliasCounter
|
|
|
2614
2626
|
*/
|
|
2615
2627
|
function buildManyToManySubquery(qi, relDef, spec, params, parentRef, aliasCounter, currentDepth, currentPath, talias, targetMeta, targetColumns, includePii) {
|
|
2616
2628
|
if (!relDef.through) {
|
|
2617
|
-
throw new errors_js_1.ValidationError(`
|
|
2629
|
+
throw new errors_js_1.ValidationError(`manyToMany relation "${relDef.name}" is missing a \`through\` junction descriptor.`);
|
|
2618
2630
|
}
|
|
2619
2631
|
// `true` IS `{}`; see {@link relationOptions}. Same reading as
|
|
2620
2632
|
// buildRelationSubquery and collectRelationSubqueryParams' m2m branch.
|
|
@@ -2630,12 +2642,12 @@ function buildManyToManySubquery(qi, relDef, spec, params, parentRef, aliasCount
|
|
|
2630
2642
|
// introspected PK means we cannot know what to JOIN on, fail loudly rather than
|
|
2631
2643
|
// silently guessing `id` and generating a wrong JOIN.
|
|
2632
2644
|
if (targetMeta.primaryKey.length === 0) {
|
|
2633
|
-
throw new errors_js_1.ValidationError(`
|
|
2645
|
+
throw new errors_js_1.ValidationError(`manyToMany relation "${relDef.name}" targets table "${targetTable}" which has no primary key; ` +
|
|
2634
2646
|
`cannot determine the join column. Define a primary key or use an explicit through descriptor.`);
|
|
2635
2647
|
}
|
|
2636
2648
|
const targetPk = targetMeta.primaryKey;
|
|
2637
2649
|
if (targetKeys.length !== targetPk.length) {
|
|
2638
|
-
throw new errors_js_1.ValidationError(`
|
|
2650
|
+
throw new errors_js_1.ValidationError(`manyToMany relation "${relDef.name}": through.targetKey has ${targetKeys.length} column(s) ` +
|
|
2639
2651
|
`but target "${targetTable}" primary key has ${targetPk.length}. Composite keys must pair positionally.`);
|
|
2640
2652
|
}
|
|
2641
2653
|
const joinOn = targetKeys.map((jcol, i) => `${jalias}.${qi.q(jcol)} = ${talias}.${qi.q(targetPk[i])}`).join(' AND ');
|
|
@@ -2643,7 +2655,7 @@ function buildManyToManySubquery(qi, relDef, spec, params, parentRef, aliasCount
|
|
|
2643
2655
|
const sourceKeys = (0, schema_js_1.normalizeKeyColumns)(relDef.through.sourceKey);
|
|
2644
2656
|
const refKeys = (0, schema_js_1.normalizeKeyColumns)(relDef.referenceKey);
|
|
2645
2657
|
if (sourceKeys.length !== refKeys.length) {
|
|
2646
|
-
throw new errors_js_1.ValidationError(`
|
|
2658
|
+
throw new errors_js_1.ValidationError(`manyToMany relation "${relDef.name}": through.sourceKey has ${sourceKeys.length} column(s) ` +
|
|
2647
2659
|
`but referenceKey has ${refKeys.length}. Composite keys must pair positionally.`);
|
|
2648
2660
|
}
|
|
2649
2661
|
let whereClause = sourceKeys
|
|
@@ -2689,7 +2701,7 @@ function buildManyToManySubquery(qi, relDef, spec, params, parentRef, aliasCount
|
|
|
2689
2701
|
for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(opts.with)) {
|
|
2690
2702
|
const nestedRelDef = (0, utils_js_1.ownLookup)(targetMeta.relations, nestedRelName);
|
|
2691
2703
|
if (!nestedRelDef) {
|
|
2692
|
-
throw new errors_js_1.RelationError(`
|
|
2704
|
+
throw new errors_js_1.RelationError(`Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
|
|
2693
2705
|
(0, utils_js_1.availableClause)(Object.keys(targetMeta.relations), `"${relDef.to}" has no relations.`));
|
|
2694
2706
|
}
|
|
2695
2707
|
const nestedSub = buildRelationSubquery(qi, nestedRelDef, nestedSpec, params, innerAlias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name], includePii);
|
|
@@ -2709,7 +2721,7 @@ function buildManyToManySubquery(qi, relDef, spec, params, parentRef, aliasCount
|
|
|
2709
2721
|
for (const [nestedRelName, nestedSpec] of (0, filters_js_1.sortedEntries)(opts.with)) {
|
|
2710
2722
|
const nestedRelDef = (0, utils_js_1.ownLookup)(targetMeta.relations, nestedRelName);
|
|
2711
2723
|
if (!nestedRelDef) {
|
|
2712
|
-
throw new errors_js_1.RelationError(`
|
|
2724
|
+
throw new errors_js_1.RelationError(`Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
|
|
2713
2725
|
(0, utils_js_1.availableClause)(Object.keys(targetMeta.relations), `"${relDef.to}" has no relations.`));
|
|
2714
2726
|
}
|
|
2715
2727
|
const nestedSub = buildRelationSubquery(qi, nestedRelDef, nestedSpec, params, talias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name], includePii);
|
package/dist/cjs/query/types.js
CHANGED
|
@@ -73,7 +73,7 @@ const errors_js_1 = require("../errors.js");
|
|
|
73
73
|
exports.UNSAFE = Symbol.for('turbine-orm.UNSAFE');
|
|
74
74
|
/** The one message shape every privilege refusal uses. */
|
|
75
75
|
function privilegeRefusal(option, received, extra = '') {
|
|
76
|
-
return new errors_js_1.ValidationError(
|
|
76
|
+
return new errors_js_1.ValidationError(`\`${option}\` must be the \`UNSAFE\` symbol, received ${describeValue(received)}. ` +
|
|
77
77
|
`${option} is a privilege option: it removes a safety boundary (a tenant filter, the PII ` +
|
|
78
78
|
'projection, or the empty-`where` guard), so it cannot be enabled by a plain value that ' +
|
|
79
79
|
'JSON.parse can produce. Import the sentinel and pass it explicitly: ' +
|
|
@@ -145,12 +145,12 @@ function resolveSkipGlobalFilters(value) {
|
|
|
145
145
|
// `[UNSAFE, ...tables]` ESCALATE to a global skip whenever `tables` happens
|
|
146
146
|
// to come back empty, which is the same accident with a much worse outcome.
|
|
147
147
|
if (tables.length === 0) {
|
|
148
|
-
throw new errors_js_1.ValidationError('
|
|
148
|
+
throw new errors_js_1.ValidationError('`skipGlobalFilters: [UNSAFE]` names no table, so it would skip nothing. ' +
|
|
149
149
|
'Pass the tables to skip (`[UNSAFE, "posts"]`), or the bare sentinel (`UNSAFE`) to skip every table.');
|
|
150
150
|
}
|
|
151
151
|
for (const t of tables) {
|
|
152
152
|
if (typeof t !== 'string') {
|
|
153
|
-
throw new errors_js_1.ValidationError('
|
|
153
|
+
throw new errors_js_1.ValidationError('`skipGlobalFilters: [UNSAFE, ...]` takes table accessor NAMES after the sentinel, ' +
|
|
154
154
|
`received ${describeValue(t)}.`);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
@@ -212,7 +212,7 @@ function assertDirectionToken(value, context) {
|
|
|
212
212
|
if (lowered === 'asc' || lowered === 'desc')
|
|
213
213
|
return;
|
|
214
214
|
}
|
|
215
|
-
throw new errors_js_1.ValidationError(`
|
|
215
|
+
throw new errors_js_1.ValidationError(`Invalid orderBy direction ${describeValue(value)} for ${context}. ` +
|
|
216
216
|
"Use 'asc' or 'desc'. An unrecognized direction used to sort ASCENDING silently, " +
|
|
217
217
|
'which returns a correct-looking page in the wrong order.');
|
|
218
218
|
}
|
package/dist/cjs/query/utils.js
CHANGED
|
@@ -1464,7 +1464,7 @@ function unknownFieldMessage(table, field, meta) {
|
|
|
1464
1464
|
const didYouMean = suggestion
|
|
1465
1465
|
? ` Did you mean "${suggestion}"${relations.includes(suggestion) ? ' (a relation)' : ''}?`
|
|
1466
1466
|
: '';
|
|
1467
|
-
return (`
|
|
1467
|
+
return (`Unknown field "${field}" on table "${table}".${didYouMean}` +
|
|
1468
1468
|
` Known columns: ${columns.join(', ') || '(none)'}.` +
|
|
1469
1469
|
(relations.length ? ` Known relations (valid in \`where\` and \`with\`): ${relations.join(', ')}.` : ''));
|
|
1470
1470
|
}
|
|
@@ -1499,7 +1499,7 @@ function availableClause(names, emptySentence) {
|
|
|
1499
1499
|
* it. Naming the fix costs one sentence and saves a search.
|
|
1500
1500
|
*/
|
|
1501
1501
|
function relationInProjectionMessage(table, field, clause) {
|
|
1502
|
-
const head = `
|
|
1502
|
+
const head = `"${field}" is a relation on table "${table}", not a column, so it cannot be named in \`${clause}\`.`;
|
|
1503
1503
|
return clause === 'select'
|
|
1504
1504
|
? `${head} Load it with \`with: { ${field}: true }\`, which is a sibling of \`select\`, not a member of it.` +
|
|
1505
1505
|
" To narrow the relation's own columns, put a `select` inside that relation's options:" +
|
|
@@ -1550,11 +1550,11 @@ function warnRedundantSortTerm(table, clause, dropped) {
|
|
|
1550
1550
|
* which is exactly the class 0.64/0.65 exist to kill.
|
|
1551
1551
|
*/
|
|
1552
1552
|
function selectNamesNothingMessage(table) {
|
|
1553
|
-
return (`
|
|
1553
|
+
return (`"select" names no fields (on table "${table}"): every value is false or it is empty. ` +
|
|
1554
1554
|
`Pass at least one field as true, or drop "select" to get the default projection.`);
|
|
1555
1555
|
}
|
|
1556
1556
|
function selectOmitExclusiveMessage(table) {
|
|
1557
|
-
return (`
|
|
1557
|
+
return (`"select" and "omit" are mutually exclusive (on table "${table}"). ` +
|
|
1558
1558
|
`A select already lists exactly the fields you want.`);
|
|
1559
1559
|
}
|
|
1560
1560
|
/**
|
|
@@ -1608,7 +1608,7 @@ function normalizePagination(args) {
|
|
|
1608
1608
|
function assertAliasAgrees(alias, aliasValue, native, nativeValue) {
|
|
1609
1609
|
if (nativeValue === undefined || nativeValue === aliasValue)
|
|
1610
1610
|
return;
|
|
1611
|
-
throw new errors_js_1.ValidationError(`
|
|
1611
|
+
throw new errors_js_1.ValidationError(`"${alias}" and "${native}" are the same option and were given different values ` +
|
|
1612
1612
|
`(${alias}: ${String(aliasValue)}, ${native}: ${String(nativeValue)}). ` +
|
|
1613
1613
|
`"${alias}" is Prisma's spelling of "${native}"; pass one of them.`);
|
|
1614
1614
|
}
|
|
@@ -127,4 +127,11 @@ export declare const WARN_NS: {
|
|
|
127
127
|
* Keyed on the requested mode.
|
|
128
128
|
*/
|
|
129
129
|
readonly planCacheModeIgnored: "planCacheModeIgnored";
|
|
130
|
+
/**
|
|
131
|
+
* A relation's JSON payload could not be parsed in `parseNestedRow`
|
|
132
|
+
* (relations.ts). Keyed on `table.relation`, so a page of 10,000 rows whose
|
|
133
|
+
* relation column is malformed says it once rather than once per row: the
|
|
134
|
+
* warn sits in the per-relation loop, which runs once per row per relation.
|
|
135
|
+
*/
|
|
136
|
+
readonly relationParseFailure: "relationParseFailure";
|
|
130
137
|
};
|
|
@@ -168,4 +168,11 @@ exports.WARN_NS = {
|
|
|
168
168
|
* Keyed on the requested mode.
|
|
169
169
|
*/
|
|
170
170
|
planCacheModeIgnored: 'planCacheModeIgnored',
|
|
171
|
+
/**
|
|
172
|
+
* A relation's JSON payload could not be parsed in `parseNestedRow`
|
|
173
|
+
* (relations.ts). Keyed on `table.relation`, so a page of 10,000 rows whose
|
|
174
|
+
* relation column is malformed says it once rather than once per row: the
|
|
175
|
+
* warn sits in the per-relation loop, which runs once per row per relation.
|
|
176
|
+
*/
|
|
177
|
+
relationParseFailure: 'relationParseFailure',
|
|
171
178
|
};
|
|
@@ -75,7 +75,7 @@ exports.MAX_WHERE_DEPTH = 32;
|
|
|
75
75
|
function assertWhereDepth(depth, clause = 'where') {
|
|
76
76
|
if (depth <= exports.MAX_WHERE_DEPTH)
|
|
77
77
|
return;
|
|
78
|
-
throw new errors_js_1.ValidationError(
|
|
78
|
+
throw new errors_js_1.ValidationError(`\`${clause}\` clause nests more than ${exports.MAX_WHERE_DEPTH} levels of AND / OR / NOT ` +
|
|
79
79
|
`(or relation filters) deep. That is far past anything a real predicate needs, and an unbounded ` +
|
|
80
80
|
`walk over caller-supplied nesting is a stack-overflow surface, so it is refused. If this is a ` +
|
|
81
81
|
`generated predicate, flatten it: a single \`AND\` / \`OR\` array of N conditions is one level, ` +
|