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.
Files changed (87) hide show
  1. package/dist/cjs/cli/index.js +2 -2
  2. package/dist/cjs/cli/migrate.js +8 -8
  3. package/dist/cjs/cli/studio.js +6 -3
  4. package/dist/cjs/client.js +10 -10
  5. package/dist/cjs/dialect.js +1 -1
  6. package/dist/cjs/errors.d.ts +2 -2
  7. package/dist/cjs/errors.js +21 -25
  8. package/dist/cjs/generate.js +1 -1
  9. package/dist/cjs/introspect.js +5 -5
  10. package/dist/cjs/mssql.js +12 -12
  11. package/dist/cjs/mysql.js +5 -5
  12. package/dist/cjs/nested-write.js +31 -31
  13. package/dist/cjs/observe.js +1 -1
  14. package/dist/cjs/pipeline-submittable.js +5 -1
  15. package/dist/cjs/pipeline.js +8 -0
  16. package/dist/cjs/powdb-introspect.d.ts +1 -1
  17. package/dist/cjs/powdb-introspect.js +4 -4
  18. package/dist/cjs/powdb-shared.d.ts +348 -0
  19. package/dist/cjs/powdb-shared.js +587 -0
  20. package/dist/cjs/powdb.d.ts +12 -299
  21. package/dist/cjs/powdb.js +106 -567
  22. package/dist/cjs/powql.d.ts +1 -1
  23. package/dist/cjs/powql.js +89 -89
  24. package/dist/cjs/prisma-compat.js +16 -16
  25. package/dist/cjs/query/aggregates.js +19 -20
  26. package/dist/cjs/query/batched-loader.js +8 -8
  27. package/dist/cjs/query/builder.js +8 -8
  28. package/dist/cjs/query/compound-unique.js +2 -2
  29. package/dist/cjs/query/filters.js +1 -1
  30. package/dist/cjs/query/relations.js +60 -48
  31. package/dist/cjs/query/types.js +4 -4
  32. package/dist/cjs/query/utils.js +5 -5
  33. package/dist/cjs/query/warn-registry.d.ts +7 -0
  34. package/dist/cjs/query/warn-registry.js +7 -0
  35. package/dist/cjs/query/where-compile.js +1 -1
  36. package/dist/cjs/query/where.js +30 -31
  37. package/dist/cjs/query/writes.js +7 -7
  38. package/dist/cjs/realtime.js +4 -4
  39. package/dist/cjs/schema-metadata.js +1 -1
  40. package/dist/cjs/schema-sql.js +3 -3
  41. package/dist/cjs/seed.js +1 -1
  42. package/dist/cjs/sqlite.js +7 -7
  43. package/dist/cjs/typed-sql.js +1 -1
  44. package/dist/cli/index.js +2 -2
  45. package/dist/cli/migrate.js +8 -8
  46. package/dist/cli/studio.js +6 -3
  47. package/dist/client.js +10 -10
  48. package/dist/dialect.js +1 -1
  49. package/dist/errors.d.ts +2 -2
  50. package/dist/errors.js +21 -25
  51. package/dist/generate.js +1 -1
  52. package/dist/introspect.js +5 -5
  53. package/dist/mssql.js +12 -12
  54. package/dist/mysql.js +5 -5
  55. package/dist/nested-write.js +31 -31
  56. package/dist/observe.js +1 -1
  57. package/dist/pipeline-submittable.js +6 -2
  58. package/dist/pipeline.js +9 -1
  59. package/dist/powdb-introspect.d.ts +1 -1
  60. package/dist/powdb-introspect.js +2 -2
  61. package/dist/powdb-shared.d.ts +348 -0
  62. package/dist/powdb-shared.js +570 -0
  63. package/dist/powdb.d.ts +12 -299
  64. package/dist/powdb.js +71 -534
  65. package/dist/powql.d.ts +1 -1
  66. package/dist/powql.js +43 -43
  67. package/dist/prisma-compat.js +16 -16
  68. package/dist/query/aggregates.js +19 -20
  69. package/dist/query/batched-loader.js +8 -8
  70. package/dist/query/builder.js +8 -8
  71. package/dist/query/compound-unique.js +2 -2
  72. package/dist/query/filters.js +1 -1
  73. package/dist/query/relations.js +60 -48
  74. package/dist/query/types.js +4 -4
  75. package/dist/query/utils.js +5 -5
  76. package/dist/query/warn-registry.d.ts +7 -0
  77. package/dist/query/warn-registry.js +7 -0
  78. package/dist/query/where-compile.js +1 -1
  79. package/dist/query/where.js +30 -31
  80. package/dist/query/writes.js +7 -7
  81. package/dist/realtime.js +4 -4
  82. package/dist/schema-metadata.js +1 -1
  83. package/dist/schema-sql.js +3 -3
  84. package/dist/seed.js +1 -1
  85. package/dist/sqlite.js +7 -7
  86. package/dist/typed-sql.js +1 -1
  87. package/package.json +15 -7
@@ -112,8 +112,7 @@ export function resolveProjection(qi, table, meta, select, omit, includePii) {
112
112
  // numeric indices and throw a cryptic `Unknown field "0"`, catch it early
113
113
  // with an actionable message.
114
114
  if (Array.isArray(select)) {
115
- throw new ValidationError(`[turbine] "select" must be an object mapping field names to true ` +
116
- `(e.g. { id: true, name: true }), not an array.`);
115
+ throw new ValidationError(`"select" must be an object mapping field names to true ` + `(e.g. { id: true, name: true }), not an array.`);
117
116
  }
118
117
  // Only include columns where value is true. An explicit `select` naming a
119
118
  // PII column IS the opt-in: it comes back regardless of `includePii`.
@@ -155,7 +154,7 @@ export function resolveProjection(qi, table, meta, select, omit, includePii) {
155
154
  const hasPii = piiCols !== undefined && piiCols.size > 0;
156
155
  if (omit) {
157
156
  if (Array.isArray(omit)) {
158
- throw new ValidationError(`[turbine] "omit" must be an object mapping field names to true ` + `(e.g. { createdAt: true }), not an array.`);
157
+ throw new ValidationError(`"omit" must be an object mapping field names to true ` + `(e.g. { createdAt: true }), not an array.`);
159
158
  }
160
159
  // Include all columns except those where value is true (and PII columns).
161
160
  const omitCols = new Set(Object.entries(omit)
@@ -506,7 +505,7 @@ export function buildOrderBy(qi, orderBy, params, lateralSink) {
506
505
  if (meta)
507
506
  resolveOrderByColumn(qi, qi.table, meta, key);
508
507
  if (!params) {
509
- throw new ValidationError(`[turbine] Vector distance ordering on "${key}" is only supported in a top-level findMany orderBy.`);
508
+ throw new ValidationError(`Vector distance ordering on "${key}" is only supported in a top-level findMany orderBy.`);
510
509
  }
511
510
  const rawColumn = qi.toColumn(key);
512
511
  const operator = whereMod.vectorOperator(qi, key, rawColumn, value.distance.metric);
@@ -586,7 +585,7 @@ export function nullsSuffix(qi, nulls) {
586
585
  export function resolveOrderByColumn(_qi, table, meta, key) {
587
586
  const col = resolveColumnName(meta, key);
588
587
  if (col === undefined) {
589
- throw new ValidationError(`[turbine] Unknown field "${key}" in orderBy on table "${table}". ` +
588
+ throw new ValidationError(`Unknown field "${key}" in orderBy on table "${table}". ` +
590
589
  `Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`);
591
590
  }
592
591
  return col;
@@ -602,12 +601,12 @@ export function validateJsonPathOrderBy(qi, table, meta, field, spec) {
602
601
  const col = resolveOrderByColumn(qi, table, meta, field);
603
602
  if (spec.path.length === 0 ||
604
603
  spec.path.some((el) => typeof el !== 'string' && !(typeof el === 'number' && Number.isFinite(el)))) {
605
- throw new ValidationError(`[turbine] JSON-path orderBy on "${field}" (table "${table}") requires a non-empty \`path\` array ` +
604
+ throw new ValidationError(`JSON-path orderBy on "${field}" (table "${table}") requires a non-empty \`path\` array ` +
606
605
  `of keys/indexes (e.g. { path: ['weight'], direction: 'asc' }).`);
607
606
  }
608
607
  const colType = whereMod.pgTypeForColumn(qi, meta, col);
609
608
  if (!whereMod.isJsonColumnType(qi, colType)) {
610
- throw new ValidationError(`[turbine] JSON-path orderBy on "${field}": column "${col}" on table "${table}" is not a JSON column ` +
609
+ throw new ValidationError(`JSON-path orderBy on "${field}": column "${col}" on table "${table}" is not a JSON column ` +
611
610
  `(actual type: ${colType}).`);
612
611
  }
613
612
  return col;
@@ -624,7 +623,7 @@ export function validateJsonPathOrderBy(qi, table, meta, field, spec) {
624
623
  export function buildJsonPathOrderEntry(qi, table, meta, field, spec, prefix, params) {
625
624
  const col = validateJsonPathOrderBy(qi, table, meta, field, spec);
626
625
  if (!params) {
627
- throw new ValidationError(`[turbine] JSON-path ordering on "${field}" is not supported in this orderBy context.`);
626
+ throw new ValidationError(`JSON-path ordering on "${field}" is not supported in this orderBy context.`);
628
627
  }
629
628
  params.push(whereMod.jsonPathParam(qi, spec.path));
630
629
  const extract = qi.dialect.buildJsonPathExtract(`${prefix}${qi.q(col)}`, qi.p(params.length));
@@ -676,14 +675,14 @@ function buildChainedToOneOrderBy(qi, head, nextRelName, nextValue, params) {
676
675
  // relation object. Bounded by the same depth cap as nested `with`.
677
676
  for (;;) {
678
677
  if (!currentMeta)
679
- throw new RelationError(`[turbine] Unknown relation target in orderBy chain "${path.join('.')}"`);
678
+ throw new RelationError(`Unknown relation target in orderBy chain "${path.join('.')}"`);
680
679
  const relDef = ownLookup(currentMeta.relations, relName);
681
680
  if (!relDef) {
682
- throw new ValidationError(`[turbine] Unknown relation "${relName}" in orderBy on relation "${path.join('.')}" ` +
681
+ throw new ValidationError(`Unknown relation "${relName}" in orderBy on relation "${path.join('.')}" ` +
683
682
  `(table "${currentMeta.name}"). Available: ${Object.keys(currentMeta.relations).join(', ') || '(none)'}.`);
684
683
  }
685
684
  if (relDef.type !== 'belongsTo' && relDef.type !== 'hasOne') {
686
- throw new ValidationError(`[turbine] orderBy cannot traverse the to-many relation "${relName}" on "${currentMeta.name}" ` +
685
+ throw new ValidationError(`orderBy cannot traverse the to-many relation "${relName}" on "${currentMeta.name}" ` +
687
686
  `(path "${path.concat(relName).join('.')}"): a to-many relation has no single value to order by. ` +
688
687
  `Use a pick-row ordering ({ pick, by }) at the top level, or order by "_count".`);
689
688
  }
@@ -692,7 +691,7 @@ function buildChainedToOneOrderBy(qi, head, nextRelName, nextValue, params) {
692
691
  }
693
692
  const nextMeta = qi.schema.tables[relDef.to];
694
693
  if (!nextMeta)
695
- throw new RelationError(`[turbine] Unknown relation target "${relDef.to}" in orderBy`);
694
+ throw new RelationError(`Unknown relation target "${relDef.to}" in orderBy`);
696
695
  const nextAlias = `${head.alias}c${hop}`;
697
696
  const on = relDef.type === 'belongsTo'
698
697
  ? qi.dialect.buildCorrelation(nextAlias, relDef.referenceKey, currentAlias, relDef.foreignKey)
@@ -710,7 +709,7 @@ function buildChainedToOneOrderBy(qi, head, nextRelName, nextValue, params) {
710
709
  // Exactly one key per level, as with the single-hop form.
711
710
  const entries = Object.entries(value);
712
711
  if (entries.length !== 1) {
713
- throw new ValidationError(`[turbine] orderBy on relation "${path.join('.')}" needs exactly one key per level ` +
712
+ throw new ValidationError(`orderBy on relation "${path.join('.')}" needs exactly one key per level ` +
714
713
  `(got: ${entries.map(([k]) => k).join(', ') || '(empty)'}).`);
715
714
  }
716
715
  const [key, entryValue] = entries[0];
@@ -726,7 +725,7 @@ function buildChainedToOneOrderBy(qi, head, nextRelName, nextValue, params) {
726
725
  // Terminal: a column on the last table in the chain.
727
726
  const snakeCol = resolveColumnName(currentMeta, key);
728
727
  if (snakeCol === undefined) {
729
- throw new ValidationError(`[turbine] Unknown column "${key}" in orderBy on relation "${path.join('.')}" (table "${currentMeta.name}").`);
728
+ throw new ValidationError(`Unknown column "${key}" in orderBy on relation "${path.join('.')}" (table "${currentMeta.name}").`);
730
729
  }
731
730
  assertOrderDirection(entryValue, `orderBy on relation path "${path.join('.')}"`);
732
731
  const { dir, nulls } = normalizeOrderBy(entryValue);
@@ -774,9 +773,9 @@ export function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lat
774
773
  // missing relation.
775
774
  const isColumn = resolveColumnName(ownerMeta, relName) !== undefined;
776
775
  throw new RelationError(isColumn
777
- ? `[turbine] orderBy on "${ownerTable}.${relName}" got a relation-shaped value, but "${relName}" is a ` +
776
+ ? `orderBy on "${ownerTable}.${relName}" got a relation-shaped value, but "${relName}" is a ` +
778
777
  `column. Order a column with 'asc' / 'desc' (or { sort, nulls }); the object form is for relations.`
779
- : `[turbine] Unknown relation "${relName}" in orderBy on table "${ownerTable}". ` +
778
+ : `Unknown relation "${relName}" in orderBy on table "${ownerTable}". ` +
780
779
  availableClause(Object.keys(ownerMeta.relations), `"${ownerTable}" has no relations.`));
781
780
  }
782
781
  // Pick-row ordering (`{ pick, by }`): order by a value from ONE related
@@ -792,7 +791,7 @@ export function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lat
792
791
  if (relDef.type === 'hasMany' || relDef.type === 'manyToMany') {
793
792
  const keys = Object.keys(value);
794
793
  if (keys.length !== 1 || keys[0] !== '_count') {
795
- throw new ValidationError(`[turbine] orderBy on to-many relation "${relName}" only supports "_count" ` +
794
+ throw new ValidationError(`orderBy on to-many relation "${relName}" only supports "_count" ` +
796
795
  `or a pick-row ordering ({ pick, by }) (got: ${keys.join(', ') || '(empty)'}).`);
797
796
  }
798
797
  assertOrderDirection(value._count, `orderBy { ${relName}: { _count } }`);
@@ -802,7 +801,7 @@ export function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lat
802
801
  // To-one: each entry orders by a correlated scalar subquery on a target column.
803
802
  const targetMeta = qi.schema.tables[relDef.to];
804
803
  if (!targetMeta)
805
- throw new RelationError(`[turbine] Unknown relation target "${relDef.to}"`);
804
+ throw new RelationError(`Unknown relation target "${relDef.to}"`);
806
805
  const qTarget = qi.q(relDef.to);
807
806
  const qParent = qi.q(parentRef);
808
807
  // belongsTo: alias.referenceKey = parent.foreignKey; hasOne: reversed.
@@ -811,7 +810,7 @@ export function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lat
811
810
  : qi.dialect.buildCorrelation(alias, relDef.foreignKey, qParent, relDef.referenceKey);
812
811
  const entries = Object.entries(value);
813
812
  if (entries.length === 0) {
814
- throw new ValidationError(`[turbine] orderBy on to-one relation "${relName}" needs at least one target column.`);
813
+ throw new ValidationError(`orderBy on to-one relation "${relName}" needs at least one target column.`);
815
814
  }
816
815
  return entries
817
816
  .map(([col, dirValue]) => {
@@ -831,7 +830,7 @@ export function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lat
831
830
  ? ` "${col}" is a relation on "${relDef.to}": order by one of ITS columns, e.g. ` +
832
831
  `{ ${relName}: { ${col}: { <column>: 'asc' } } }.`
833
832
  : '';
834
- throw new ValidationError(`[turbine] Unknown column "${col}" in orderBy on relation "${relName}" (table "${relDef.to}").${relationHint}`);
833
+ throw new ValidationError(`Unknown column "${col}" in orderBy on relation "${relName}" (table "${relDef.to}").${relationHint}`);
835
834
  }
836
835
  assertOrderDirection(dirValue, `orderBy "${col}" on relation "${relName}"`);
837
836
  const { dir, nulls } = normalizeOrderBy(dirValue);
@@ -862,7 +861,7 @@ export function buildRelationOrderBy(qi, relName, value, alias, params, ctx, lat
862
861
  * - `by` must be a target column name or a `{ field, path }` JSON-path spec.
863
862
  */
864
863
  export function pickOrderNestedError(_qi, relName) {
865
- return new ValidationError(`[turbine] Pick-row ordering on relation "${relName}" is only supported in a top-level ` +
864
+ return new ValidationError(`Pick-row ordering on relation "${relName}" is only supported in a top-level ` +
866
865
  'findMany orderBy: nested `with` orderBy does not support it.');
867
866
  }
868
867
  export function validatePickOrderBy(qi, relName, relDef, spec, nested) {
@@ -870,10 +869,10 @@ export function validatePickOrderBy(qi, relName, relDef, spec, nested) {
870
869
  throw pickOrderNestedError(qi, relName);
871
870
  }
872
871
  if (relDef.type === 'manyToMany') {
873
- throw new ValidationError(`[turbine] Pick-row ordering is not supported on manyToMany relation "${relName}": hasMany relations only.`);
872
+ throw new ValidationError(`Pick-row ordering is not supported on manyToMany relation "${relName}": hasMany relations only.`);
874
873
  }
875
874
  if (relDef.type !== 'hasMany') {
876
- throw new ValidationError(`[turbine] Pick-row ordering is only for to-many (hasMany) relations; "${relName}" is ${relDef.type}. ` +
875
+ throw new ValidationError(`Pick-row ordering is only for to-many (hasMany) relations; "${relName}" is ${relDef.type}. ` +
877
876
  `Order by the target column directly instead ({ ${relName}: { <column>: 'asc' } }).`);
878
877
  }
879
878
  const pickOrder = spec.pick?.orderBy;
@@ -882,20 +881,20 @@ export function validatePickOrderBy(qi, relName, relDef, spec, nested) {
882
881
  typeof pickOrder !== 'object' ||
883
882
  pickOrder === null ||
884
883
  Object.keys(pickOrder).length === 0) {
885
- throw new ValidationError(`[turbine] Pick-row ordering on relation "${relName}" requires \`pick.orderBy\` to choose ONE ` +
884
+ throw new ValidationError(`Pick-row ordering on relation "${relName}" requires \`pick.orderBy\` to choose ONE ` +
886
885
  "related row deterministically (e.g. pick: { orderBy: { createdAt: 'desc' } }).");
887
886
  }
888
887
  const by = spec.by;
889
888
  const validJsonBy = typeof by === 'object' && by !== null && typeof by.field === 'string' && Array.isArray(by.path);
890
889
  if (typeof by !== 'string' && !validJsonBy) {
891
- throw new ValidationError(`[turbine] Pick-row ordering on relation "${relName}" requires \`by\`: a target column name ` +
890
+ throw new ValidationError(`Pick-row ordering on relation "${relName}" requires \`by\`: a target column name ` +
892
891
  "or a JSON-path spec ({ field: 'data', path: ['title'] }).");
893
892
  }
894
893
  // Physical plan gate. A typo like `plan: 'latreal'` must never silently run
895
894
  // the subquery plan (a silent plan change wearing a validation gap). Shared
896
895
  // by build and cache-hit collect so a warmed cache throws identically.
897
896
  if (spec.plan !== undefined && spec.plan !== 'subquery' && spec.plan !== 'lateral') {
898
- throw new ValidationError(`[turbine] Pick-row ordering on relation "${relName}" has an invalid \`plan\`: ` +
897
+ throw new ValidationError(`Pick-row ordering on relation "${relName}" has an invalid \`plan\`: ` +
899
898
  `${JSON.stringify(spec.plan)}. Use 'subquery' (default) or 'lateral'.`);
900
899
  }
901
900
  if (spec.plan === 'lateral') {
@@ -906,7 +905,7 @@ export function validatePickOrderBy(qi, relName, relDef, spec, nested) {
906
905
  // parent column with that exact name would make the unqualified WHERE
907
906
  // reference ambiguous once the join is in scope; refuse it explicitly.
908
907
  if (qi.tableMeta.allColumns.includes('__turbine_pick')) {
909
- throw new ValidationError(`[turbine] Pick-row ordering with plan: 'lateral' cannot be used: table "${qi.tableMeta.name}" ` +
908
+ throw new ValidationError(`Pick-row ordering with plan: 'lateral' cannot be used: table "${qi.tableMeta.name}" ` +
910
909
  'has a column named "__turbine_pick", which the lateral join output reserves.');
911
910
  }
912
911
  }
@@ -930,11 +929,11 @@ export function validatePickOrderBy(qi, relName, relDef, spec, nested) {
930
929
  */
931
930
  export function buildRelationPickOrderBy(qi, relName, relDef, spec, alias, parentRef, params, lateralSink) {
932
931
  if (!params) {
933
- throw new ValidationError(`[turbine] Pick-row ordering on relation "${relName}" is only supported in a top-level findMany orderBy.`);
932
+ throw new ValidationError(`Pick-row ordering on relation "${relName}" is only supported in a top-level findMany orderBy.`);
934
933
  }
935
934
  const targetMeta = qi.schema.tables[relDef.to];
936
935
  if (!targetMeta)
937
- throw new RelationError(`[turbine] Unknown relation target "${relDef.to}"`);
936
+ throw new RelationError(`Unknown relation target "${relDef.to}"`);
938
937
  assertDirectionToken(spec.direction, `pick-row orderBy on relation "${relName}"`);
939
938
  const dir = spec.direction?.toLowerCase() === 'desc' ? 'DESC' : 'ASC';
940
939
  const limitOne = qi.buildPagination('1', undefined, true);
@@ -959,7 +958,7 @@ export function buildRelationPickOrderBy(qi, relName, relDef, spec, alias, paren
959
958
  // context and hard-fails rather than silently emitting a subquery.
960
959
  if (spec.plan === 'lateral') {
961
960
  if (!lateralSink) {
962
- throw new ValidationError(`[turbine] Pick-row ordering with plan: 'lateral' on relation "${relName}" is only supported ` +
961
+ throw new ValidationError(`Pick-row ordering with plan: 'lateral' on relation "${relName}" is only supported ` +
963
962
  'in a top-level findMany orderBy.');
964
963
  }
965
964
  const childAlias = `${alias}i`;
@@ -1026,7 +1025,7 @@ export function collectRelationPickOrderParams(qi, relName, relDef, spec, params
1026
1025
  validatePickOrderBy(qi, relName, relDef, spec, false);
1027
1026
  const targetMeta = qi.schema.tables[relDef.to];
1028
1027
  if (!targetMeta)
1029
- throw new RelationError(`[turbine] Unknown relation target "${relDef.to}"`);
1028
+ throw new RelationError(`Unknown relation target "${relDef.to}"`);
1030
1029
  if (typeof spec.by === 'string') {
1031
1030
  resolveOrderByColumn(qi, relDef.to, targetMeta, spec.by);
1032
1031
  }
@@ -1071,7 +1070,7 @@ export function buildRelationOrderClause(qi, targetTable, targetMeta, alias, ord
1071
1070
  const orders = orderEntries
1072
1071
  .map(([key, dirValue]) => {
1073
1072
  if (isVectorOrderBy(dirValue)) {
1074
- throw new ValidationError(`[turbine] Vector distance ordering on "${key}" is only supported in a top-level findMany orderBy.`);
1073
+ throw new ValidationError(`Vector distance ordering on "${key}" is only supported in a top-level findMany orderBy.`);
1075
1074
  }
1076
1075
  if (isJsonPathOrderBy(dirValue)) {
1077
1076
  return buildJsonPathOrderEntry(qi, targetTable, targetMeta, key, dirValue, `${alias}.`, params);
@@ -1097,7 +1096,7 @@ export function buildRelationOrderClause(qi, targetTable, targetMeta, alias, ord
1097
1096
  export function collectRelationOrderParams(qi, targetTable, targetMeta, orderEntries, params) {
1098
1097
  for (const [key, dirValue] of orderEntries) {
1099
1098
  if (isVectorOrderBy(dirValue)) {
1100
- throw new ValidationError(`[turbine] Vector distance ordering on "${key}" is only supported in a top-level findMany orderBy.`);
1099
+ throw new ValidationError(`Vector distance ordering on "${key}" is only supported in a top-level findMany orderBy.`);
1101
1100
  }
1102
1101
  if (isJsonPathOrderBy(dirValue)) {
1103
1102
  validateJsonPathOrderBy(qi, targetTable, targetMeta, key, dirValue);
@@ -1141,7 +1140,7 @@ export function buildRelationCountExpr(qi, relDef, parentRef, alias, params) {
1141
1140
  const count = qi.castAgg('COUNT(*)', 'int');
1142
1141
  if (relDef.type === 'manyToMany') {
1143
1142
  if (!relDef.through) {
1144
- throw new ValidationError(`[turbine] manyToMany relation "${relDef.name}" is missing its \`through\` junction.`);
1143
+ throw new ValidationError(`manyToMany relation "${relDef.name}" is missing its \`through\` junction.`);
1145
1144
  }
1146
1145
  const qJ = qi.q(relDef.through.table);
1147
1146
  const jalias = `${alias}j`;
@@ -1325,9 +1324,22 @@ export function parseNestedRow(qi, row, table, fromJson = false) {
1325
1324
  parsed[relName] = jsonVal;
1326
1325
  }
1327
1326
  }
1328
- catch {
1329
- console.warn(`[turbine] Warning: Failed to parse JSON for relation "${relName}" on table "${qi.table}". Using raw value.`);
1330
- parsed[relName] = rawValue;
1327
+ catch (err) {
1328
+ // A relation column that does not parse is a bug in the emitted SQL or
1329
+ // a driver-level corruption, not a recoverable condition. Handing back
1330
+ // the raw string satisfied the assignment and violated the generated
1331
+ // type: callers got a `string` where `Post[]` was promised, and found
1332
+ // out at the first `.map`. Throw instead, with the context needed to
1333
+ // find it.
1334
+ //
1335
+ // The warn is gated and deduped because this sits in parseNestedRow's
1336
+ // per-relation loop: one 10,000-row page was 10,000 console.warn calls.
1337
+ if (process.env.NODE_ENV !== 'production' &&
1338
+ shouldWarnOnce(WARN_NS.relationParseFailure, `${qi.table}.${relName}`)) {
1339
+ console.warn(`[turbine] Relation "${relName}" on "${qi.table}" returned a payload that is not valid JSON. ` +
1340
+ `This is a bug; please report it with the emitted SQL.`);
1341
+ }
1342
+ throw new ValidationError(`Relation "${relName}" on table "${qi.table}" returned a payload that could not be parsed as JSON.`, { cause: err });
1331
1343
  }
1332
1344
  }
1333
1345
  else if (Array.isArray(rawValue)) {
@@ -2158,7 +2170,7 @@ export function makeFlattenParser(qi, plan) {
2158
2170
  export function buildSelectWithRelations(qi, table, withClause, params, columnsList, depth, path, includePii, flatten) {
2159
2171
  const meta = qi.schema.tables[table];
2160
2172
  if (!meta)
2161
- throw new ValidationError(`[turbine] Unknown table "${table}"`);
2173
+ throw new ValidationError(`Unknown table "${table}"`);
2162
2174
  // Positional JSON encoding is Postgres-only in v1. Gate here, the single
2163
2175
  // entry point for every `with` clause, so no engine ever emits the
2164
2176
  // json_build_array shape its dialect can't produce (and mssql's FOR JSON
@@ -2177,7 +2189,7 @@ export function buildSelectWithRelations(qi, table, withClause, params, columnsL
2177
2189
  continue;
2178
2190
  const relDef = ownLookup(meta.relations, relName);
2179
2191
  if (!relDef) {
2180
- throw new RelationError(`[turbine] Unknown relation "${relName}" on table "${table}". ` +
2192
+ throw new RelationError(`Unknown relation "${relName}" on table "${table}". ` +
2181
2193
  availableClause(Object.keys(meta.relations), `"${table}" has no relations.`));
2182
2194
  }
2183
2195
  // `relationLoadStrategy: 'flatten'`: an eligible to-one relation becomes a
@@ -2311,7 +2323,7 @@ export function buildRelationSubquery(qi, relDef, spec, params, parentRef, alias
2311
2323
  }
2312
2324
  const targetMeta = qi.schema.tables[targetTable];
2313
2325
  if (!targetMeta)
2314
- throw new RelationError(`[turbine] Unknown relation target "${targetTable}"`);
2326
+ throw new RelationError(`Unknown relation target "${targetTable}"`);
2315
2327
  // `true` IS `{}`; below this line there is no `true` case, which is what
2316
2328
  // keeps this walk and collectRelationSubqueryParams reading one shape.
2317
2329
  // The raw `spec` is still what crosses the dialect seam below, since
@@ -2416,7 +2428,7 @@ export function buildRelationSubquery(qi, relDef, spec, params, parentRef, alias
2416
2428
  for (const [nestedRelName, nestedSpec] of sortedEntries(opts.with)) {
2417
2429
  const nestedRelDef = ownLookup(targetMeta.relations, nestedRelName);
2418
2430
  if (!nestedRelDef) {
2419
- throw new RelationError(`[turbine] Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
2431
+ throw new RelationError(`Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
2420
2432
  availableClause(Object.keys(targetMeta.relations), `"${relDef.to}" has no relations.`));
2421
2433
  }
2422
2434
  // Recursively build nested subquery, passing THIS alias as the parent reference
@@ -2490,7 +2502,7 @@ export function buildRelationSubquery(qi, relDef, spec, params, parentRef, alias
2490
2502
  for (const [nestedRelName, nestedSpec] of sortedEntries(opts.with)) {
2491
2503
  const nestedRelDef = ownLookup(targetMeta.relations, nestedRelName);
2492
2504
  if (!nestedRelDef) {
2493
- throw new RelationError(`[turbine] Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
2505
+ throw new RelationError(`Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
2494
2506
  availableClause(Object.keys(targetMeta.relations), `"${relDef.to}" has no relations.`));
2495
2507
  }
2496
2508
  const nestedSub = buildRelationSubquery(qi, nestedRelDef, nestedSpec, params, innerAlias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name], includePii);
@@ -2534,7 +2546,7 @@ export function buildRelationSubquery(qi, relDef, spec, params, parentRef, alias
2534
2546
  */
2535
2547
  export function buildManyToManySubquery(qi, relDef, spec, params, parentRef, aliasCounter, currentDepth, currentPath, talias, targetMeta, targetColumns, includePii) {
2536
2548
  if (!relDef.through) {
2537
- throw new ValidationError(`[turbine] manyToMany relation "${relDef.name}" is missing a \`through\` junction descriptor.`);
2549
+ throw new ValidationError(`manyToMany relation "${relDef.name}" is missing a \`through\` junction descriptor.`);
2538
2550
  }
2539
2551
  // `true` IS `{}`; see {@link relationOptions}. Same reading as
2540
2552
  // buildRelationSubquery and collectRelationSubqueryParams' m2m branch.
@@ -2550,12 +2562,12 @@ export function buildManyToManySubquery(qi, relDef, spec, params, parentRef, ali
2550
2562
  // introspected PK means we cannot know what to JOIN on, fail loudly rather than
2551
2563
  // silently guessing `id` and generating a wrong JOIN.
2552
2564
  if (targetMeta.primaryKey.length === 0) {
2553
- throw new ValidationError(`[turbine] manyToMany relation "${relDef.name}" targets table "${targetTable}" which has no primary key; ` +
2565
+ throw new ValidationError(`manyToMany relation "${relDef.name}" targets table "${targetTable}" which has no primary key; ` +
2554
2566
  `cannot determine the join column. Define a primary key or use an explicit through descriptor.`);
2555
2567
  }
2556
2568
  const targetPk = targetMeta.primaryKey;
2557
2569
  if (targetKeys.length !== targetPk.length) {
2558
- throw new ValidationError(`[turbine] manyToMany relation "${relDef.name}": through.targetKey has ${targetKeys.length} column(s) ` +
2570
+ throw new ValidationError(`manyToMany relation "${relDef.name}": through.targetKey has ${targetKeys.length} column(s) ` +
2559
2571
  `but target "${targetTable}" primary key has ${targetPk.length}. Composite keys must pair positionally.`);
2560
2572
  }
2561
2573
  const joinOn = targetKeys.map((jcol, i) => `${jalias}.${qi.q(jcol)} = ${talias}.${qi.q(targetPk[i])}`).join(' AND ');
@@ -2563,7 +2575,7 @@ export function buildManyToManySubquery(qi, relDef, spec, params, parentRef, ali
2563
2575
  const sourceKeys = normalizeKeyColumns(relDef.through.sourceKey);
2564
2576
  const refKeys = normalizeKeyColumns(relDef.referenceKey);
2565
2577
  if (sourceKeys.length !== refKeys.length) {
2566
- throw new ValidationError(`[turbine] manyToMany relation "${relDef.name}": through.sourceKey has ${sourceKeys.length} column(s) ` +
2578
+ throw new ValidationError(`manyToMany relation "${relDef.name}": through.sourceKey has ${sourceKeys.length} column(s) ` +
2567
2579
  `but referenceKey has ${refKeys.length}. Composite keys must pair positionally.`);
2568
2580
  }
2569
2581
  let whereClause = sourceKeys
@@ -2609,7 +2621,7 @@ export function buildManyToManySubquery(qi, relDef, spec, params, parentRef, ali
2609
2621
  for (const [nestedRelName, nestedSpec] of sortedEntries(opts.with)) {
2610
2622
  const nestedRelDef = ownLookup(targetMeta.relations, nestedRelName);
2611
2623
  if (!nestedRelDef) {
2612
- throw new RelationError(`[turbine] Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
2624
+ throw new RelationError(`Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
2613
2625
  availableClause(Object.keys(targetMeta.relations), `"${relDef.to}" has no relations.`));
2614
2626
  }
2615
2627
  const nestedSub = buildRelationSubquery(qi, nestedRelDef, nestedSpec, params, innerAlias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name], includePii);
@@ -2629,7 +2641,7 @@ export function buildManyToManySubquery(qi, relDef, spec, params, parentRef, ali
2629
2641
  for (const [nestedRelName, nestedSpec] of sortedEntries(opts.with)) {
2630
2642
  const nestedRelDef = ownLookup(targetMeta.relations, nestedRelName);
2631
2643
  if (!nestedRelDef) {
2632
- throw new RelationError(`[turbine] Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
2644
+ throw new RelationError(`Unknown relation "${nestedRelName}" on table "${targetTable}". ` +
2633
2645
  availableClause(Object.keys(targetMeta.relations), `"${relDef.to}" has no relations.`));
2634
2646
  }
2635
2647
  const nestedSub = buildRelationSubquery(qi, nestedRelDef, nestedSpec, params, talias, aliasCounter, currentDepth + 1, [...currentPath, relDef.name], includePii);
@@ -66,7 +66,7 @@ import { ValidationError } from '../errors.js';
66
66
  export const UNSAFE = Symbol.for('turbine-orm.UNSAFE');
67
67
  /** The one message shape every privilege refusal uses. */
68
68
  function privilegeRefusal(option, received, extra = '') {
69
- return new ValidationError(`[turbine] \`${option}\` must be the \`UNSAFE\` symbol, received ${describeValue(received)}. ` +
69
+ return new ValidationError(`\`${option}\` must be the \`UNSAFE\` symbol, received ${describeValue(received)}. ` +
70
70
  `${option} is a privilege option: it removes a safety boundary (a tenant filter, the PII ` +
71
71
  'projection, or the empty-`where` guard), so it cannot be enabled by a plain value that ' +
72
72
  'JSON.parse can produce. Import the sentinel and pass it explicitly: ' +
@@ -138,12 +138,12 @@ export function resolveSkipGlobalFilters(value) {
138
138
  // `[UNSAFE, ...tables]` ESCALATE to a global skip whenever `tables` happens
139
139
  // to come back empty, which is the same accident with a much worse outcome.
140
140
  if (tables.length === 0) {
141
- throw new ValidationError('[turbine] `skipGlobalFilters: [UNSAFE]` names no table, so it would skip nothing. ' +
141
+ throw new ValidationError('`skipGlobalFilters: [UNSAFE]` names no table, so it would skip nothing. ' +
142
142
  'Pass the tables to skip (`[UNSAFE, "posts"]`), or the bare sentinel (`UNSAFE`) to skip every table.');
143
143
  }
144
144
  for (const t of tables) {
145
145
  if (typeof t !== 'string') {
146
- throw new ValidationError('[turbine] `skipGlobalFilters: [UNSAFE, ...]` takes table accessor NAMES after the sentinel, ' +
146
+ throw new ValidationError('`skipGlobalFilters: [UNSAFE, ...]` takes table accessor NAMES after the sentinel, ' +
147
147
  `received ${describeValue(t)}.`);
148
148
  }
149
149
  }
@@ -205,7 +205,7 @@ export function assertDirectionToken(value, context) {
205
205
  if (lowered === 'asc' || lowered === 'desc')
206
206
  return;
207
207
  }
208
- throw new ValidationError(`[turbine] Invalid orderBy direction ${describeValue(value)} for ${context}. ` +
208
+ throw new ValidationError(`Invalid orderBy direction ${describeValue(value)} for ${context}. ` +
209
209
  "Use 'asc' or 'desc'. An unrecognized direction used to sort ASCENDING silently, " +
210
210
  'which returns a correct-looking page in the wrong order.');
211
211
  }
@@ -1415,7 +1415,7 @@ export function unknownFieldMessage(table, field, meta) {
1415
1415
  const didYouMean = suggestion
1416
1416
  ? ` Did you mean "${suggestion}"${relations.includes(suggestion) ? ' (a relation)' : ''}?`
1417
1417
  : '';
1418
- return (`[turbine] Unknown field "${field}" on table "${table}".${didYouMean}` +
1418
+ return (`Unknown field "${field}" on table "${table}".${didYouMean}` +
1419
1419
  ` Known columns: ${columns.join(', ') || '(none)'}.` +
1420
1420
  (relations.length ? ` Known relations (valid in \`where\` and \`with\`): ${relations.join(', ')}.` : ''));
1421
1421
  }
@@ -1450,7 +1450,7 @@ export function availableClause(names, emptySentence) {
1450
1450
  * it. Naming the fix costs one sentence and saves a search.
1451
1451
  */
1452
1452
  export function relationInProjectionMessage(table, field, clause) {
1453
- const head = `[turbine] "${field}" is a relation on table "${table}", not a column, so it cannot be named in \`${clause}\`.`;
1453
+ const head = `"${field}" is a relation on table "${table}", not a column, so it cannot be named in \`${clause}\`.`;
1454
1454
  return clause === 'select'
1455
1455
  ? `${head} Load it with \`with: { ${field}: true }\`, which is a sibling of \`select\`, not a member of it.` +
1456
1456
  " To narrow the relation's own columns, put a `select` inside that relation's options:" +
@@ -1501,11 +1501,11 @@ export function warnRedundantSortTerm(table, clause, dropped) {
1501
1501
  * which is exactly the class 0.64/0.65 exist to kill.
1502
1502
  */
1503
1503
  export function selectNamesNothingMessage(table) {
1504
- return (`[turbine] "select" names no fields (on table "${table}"): every value is false or it is empty. ` +
1504
+ return (`"select" names no fields (on table "${table}"): every value is false or it is empty. ` +
1505
1505
  `Pass at least one field as true, or drop "select" to get the default projection.`);
1506
1506
  }
1507
1507
  export function selectOmitExclusiveMessage(table) {
1508
- return (`[turbine] "select" and "omit" are mutually exclusive (on table "${table}"). ` +
1508
+ return (`"select" and "omit" are mutually exclusive (on table "${table}"). ` +
1509
1509
  `A select already lists exactly the fields you want.`);
1510
1510
  }
1511
1511
  /**
@@ -1559,7 +1559,7 @@ export function normalizePagination(args) {
1559
1559
  function assertAliasAgrees(alias, aliasValue, native, nativeValue) {
1560
1560
  if (nativeValue === undefined || nativeValue === aliasValue)
1561
1561
  return;
1562
- throw new ValidationError(`[turbine] "${alias}" and "${native}" are the same option and were given different values ` +
1562
+ throw new ValidationError(`"${alias}" and "${native}" are the same option and were given different values ` +
1563
1563
  `(${alias}: ${String(aliasValue)}, ${native}: ${String(nativeValue)}). ` +
1564
1564
  `"${alias}" is Prisma's spelling of "${native}"; pass one of them.`);
1565
1565
  }
@@ -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
  };
@@ -162,4 +162,11 @@ export const WARN_NS = {
162
162
  * Keyed on the requested mode.
163
163
  */
164
164
  planCacheModeIgnored: 'planCacheModeIgnored',
165
+ /**
166
+ * A relation's JSON payload could not be parsed in `parseNestedRow`
167
+ * (relations.ts). Keyed on `table.relation`, so a page of 10,000 rows whose
168
+ * relation column is malformed says it once rather than once per row: the
169
+ * warn sits in the per-relation loop, which runs once per row per relation.
170
+ */
171
+ relationParseFailure: 'relationParseFailure',
165
172
  };
@@ -68,7 +68,7 @@ export const MAX_WHERE_DEPTH = 32;
68
68
  export function assertWhereDepth(depth, clause = 'where') {
69
69
  if (depth <= MAX_WHERE_DEPTH)
70
70
  return;
71
- throw new ValidationError(`[turbine] \`${clause}\` clause nests more than ${MAX_WHERE_DEPTH} levels of AND / OR / NOT ` +
71
+ throw new ValidationError(`\`${clause}\` clause nests more than ${MAX_WHERE_DEPTH} levels of AND / OR / NOT ` +
72
72
  `(or relation filters) deep. That is far past anything a real predicate needs, and an unbounded ` +
73
73
  `walk over caller-supplied nesting is a stack-overflow surface, so it is refused. If this is a ` +
74
74
  `generated predicate, flatten it: a single \`AND\` / \`OR\` array of N conditions is one level, ` +