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
@@ -601,7 +601,7 @@ allowFullTableScan) {
601
601
  return;
602
602
  if (allowFullTableScan === true)
603
603
  return;
604
- throw new errors_js_1.ValidationError(`[turbine] ${operation} on "${qi.table}" refused: the \`where\` clause is empty. ` +
604
+ throw new errors_js_1.ValidationError(`${operation} on "${qi.table}" refused: the \`where\` clause is empty. ` +
605
605
  "Pass `allowFullTableScan: UNSAFE` to opt in (import { UNSAFE } from 'turbine-orm'), " +
606
606
  'or check that your filter values are defined.');
607
607
  }
@@ -693,13 +693,13 @@ function buildScalarClause(qi, key, value, params, andClauses) {
693
693
  // A JSON-only operator on a non-JSON column was almost certainly a typo
694
694
  // or schema mismatch. `contains`/`equals` are shared with WhereOperator
695
695
  // (LIKE / equality), so only shape-unique keys reach here.
696
- throw new errors_js_1.ValidationError(`[turbine] Column "${rawColumn}" on table "${qi.table}" is not a JSON column ` +
696
+ throw new errors_js_1.ValidationError(`Column "${rawColumn}" on table "${qi.table}" is not a JSON column ` +
697
697
  `(actual type: ${getColumnPgType(qi, rawColumn)}); cannot apply JSON operator '${cls.jsonKey}'.`);
698
698
  case 'array':
699
699
  andClauses.push(...buildArrayFilterClauses(qi, column, value, params, cls.colType));
700
700
  return;
701
701
  case 'arrayThrow':
702
- throw new errors_js_1.ValidationError(`[turbine] Column "${rawColumn}" on table "${qi.table}" is not an array column ` +
702
+ throw new errors_js_1.ValidationError(`Column "${rawColumn}" on table "${qi.table}" is not an array column ` +
703
703
  `(actual type: ${getColumnPgType(qi, rawColumn)}); cannot apply array operator '${cls.arrayKey}'.`);
704
704
  case 'textsearch':
705
705
  andClauses.push(buildTextSearchClause(qi, column, value, params));
@@ -757,7 +757,7 @@ function relationWhereScope(qi, targetTable, meta) {
757
757
  qualifier: `${qi.q(targetTable)}.`,
758
758
  relationParent: targetTable,
759
759
  host: scopedWhereHost(qi, meta),
760
- unknownColumn: (field) => new errors_js_1.ValidationError(`[turbine] Unknown field "${field}" in relation filter for table "${targetTable}". ` +
760
+ unknownColumn: (field) => new errors_js_1.ValidationError(`Unknown field "${field}" in relation filter for table "${targetTable}". ` +
761
761
  `Known fields: ${Object.keys(meta.columnMap).join(', ') || '(none)'}.`),
762
762
  };
763
763
  }
@@ -769,7 +769,7 @@ function aliasWhereScope(qi, targetTable, meta, alias) {
769
769
  qualifier: `${alias}.`,
770
770
  relationParent: alias,
771
771
  host: scopedWhereHost(qi, meta),
772
- unknownColumn: (field) => new errors_js_1.ValidationError(`[turbine] Unknown column "${field}" in where for table "${targetTable}"`),
772
+ unknownColumn: (field) => new errors_js_1.ValidationError(`Unknown column "${field}" in where for table "${targetTable}"`),
773
773
  };
774
774
  }
775
775
  /**
@@ -844,7 +844,7 @@ function buildScopedScalarClause(qi, scope, field, value, params, clauses) {
844
844
  }
845
845
  const jsonKey = (0, filters_js_1.findJsonUniqueKey)(value);
846
846
  if (jsonKey) {
847
- throw new errors_js_1.ValidationError(`[turbine] Column "${col}" on table "${scope.table}" is not a JSON column ` +
847
+ throw new errors_js_1.ValidationError(`Column "${col}" on table "${scope.table}" is not a JSON column ` +
848
848
  `(actual type: ${colType}); cannot apply JSON operator '${jsonKey}'.`);
849
849
  }
850
850
  }
@@ -856,7 +856,7 @@ function buildScopedScalarClause(qi, scope, field, value, params, clauses) {
856
856
  }
857
857
  const arrayKey = (0, filters_js_1.findArrayUniqueKey)(value);
858
858
  if (arrayKey) {
859
- throw new errors_js_1.ValidationError(`[turbine] Column "${col}" on table "${scope.table}" is not an array column ` +
859
+ throw new errors_js_1.ValidationError(`Column "${col}" on table "${scope.table}" is not an array column ` +
860
860
  `(actual type: ${colType}); cannot apply array operator '${arrayKey}'.`);
861
861
  }
862
862
  }
@@ -997,23 +997,23 @@ depth = 0) {
997
997
  // nested relation filters inside the branch keep their qualification. The
998
998
  // fragment binds no params, so collectRelationFilterParams needs no mirror.
999
999
  if (!relDef.through) {
1000
- throw new errors_js_1.ValidationError(`[turbine] manyToMany relation "${relDef.name}" is missing a \`through\` junction descriptor.`);
1000
+ throw new errors_js_1.ValidationError(`manyToMany relation "${relDef.name}" is missing a \`through\` junction descriptor.`);
1001
1001
  }
1002
1002
  const qJunction = qi.q(relDef.through.table);
1003
1003
  const targetKeys = (0, schema_js_1.normalizeKeyColumns)(relDef.through.targetKey);
1004
1004
  const targetPk = targetMeta.primaryKey;
1005
1005
  if (targetPk.length === 0) {
1006
- throw new errors_js_1.ValidationError(`[turbine] manyToMany relation "${relDef.name}" targets table "${targetTable}" which has no primary key; ` +
1006
+ throw new errors_js_1.ValidationError(`manyToMany relation "${relDef.name}" targets table "${targetTable}" which has no primary key; ` +
1007
1007
  `cannot correlate the relation filter through the junction.`);
1008
1008
  }
1009
1009
  if (targetKeys.length !== targetPk.length) {
1010
- throw new errors_js_1.ValidationError(`[turbine] manyToMany relation "${relDef.name}": through.targetKey has ${targetKeys.length} column(s) ` +
1010
+ throw new errors_js_1.ValidationError(`manyToMany relation "${relDef.name}": through.targetKey has ${targetKeys.length} column(s) ` +
1011
1011
  `but target "${targetTable}" primary key has ${targetPk.length}. Composite keys must pair positionally.`);
1012
1012
  }
1013
1013
  const sourceKeys = (0, schema_js_1.normalizeKeyColumns)(relDef.through.sourceKey);
1014
1014
  const refKeys = (0, schema_js_1.normalizeKeyColumns)(relDef.referenceKey);
1015
1015
  if (sourceKeys.length !== refKeys.length) {
1016
- throw new errors_js_1.ValidationError(`[turbine] manyToMany relation "${relDef.name}": through.sourceKey has ${sourceKeys.length} column(s) ` +
1016
+ throw new errors_js_1.ValidationError(`manyToMany relation "${relDef.name}": through.sourceKey has ${sourceKeys.length} column(s) ` +
1017
1017
  `but referenceKey has ${refKeys.length}. Composite keys must pair positionally.`);
1018
1018
  }
1019
1019
  const targetLink = targetKeys
@@ -1202,9 +1202,9 @@ function assertBindableEqualityValue(qi, rawColumn, value, columnPgType, table)
1202
1202
  return;
1203
1203
  const badKeys = Object.keys(value);
1204
1204
  throw new errors_js_1.ValidationError(badKeys.length === 0
1205
- ? `[turbine] Empty filter object on "${rawColumn}" for table "${table}". ` +
1205
+ ? `Empty filter object on "${rawColumn}" for table "${table}". ` +
1206
1206
  `Provide a value or an operator like { gt: 1 }.`
1207
- : `[turbine] Unknown operator${badKeys.length > 1 ? 's' : ''} ` +
1207
+ : `Unknown operator${badKeys.length > 1 ? 's' : ''} ` +
1208
1208
  `${badKeys.map((k) => `"${k}"`).join(', ')} on "${rawColumn}" for table "${table}". ` +
1209
1209
  `Supported operators: ${[...utils_js_1.OPERATOR_KEYS].join(', ')}.`);
1210
1210
  }
@@ -1247,13 +1247,13 @@ function fingerprintAliasWhere(qi, where, targetTable) {
1247
1247
  */
1248
1248
  function resolveColumnRef(_qi, ref, ctx, mode) {
1249
1249
  if (mode === 'insensitive') {
1250
- throw new errors_js_1.ValidationError(`[turbine] mode: 'insensitive' cannot be combined with a column reference ({ col: "${ref.col}" }). ` +
1250
+ throw new errors_js_1.ValidationError(`mode: 'insensitive' cannot be combined with a column reference ({ col: "${ref.col}" }). ` +
1251
1251
  `Case-insensitive column-to-column comparison is not supported: use client.sql\`...\` ` +
1252
1252
  `for lower(a) = lower(b).`);
1253
1253
  }
1254
1254
  const col = (0, utils_js_1.resolveColumnName)(ctx.meta, ref.col);
1255
1255
  if (col === undefined) {
1256
- throw new errors_js_1.ValidationError(`[turbine] Unknown field "${ref.col}" referenced by { col } in where on table "${ctx.table}". ` +
1256
+ throw new errors_js_1.ValidationError(`Unknown field "${ref.col}" referenced by { col } in where on table "${ctx.table}". ` +
1257
1257
  `Known fields: ${Object.keys(ctx.meta.columnMap).join(', ') || '(none)'}.`);
1258
1258
  }
1259
1259
  return col;
@@ -1265,7 +1265,7 @@ function resolveColumnRef(_qi, ref, ctx, mode) {
1265
1265
  */
1266
1266
  function columnRefSql(qi, ref, ctx, mode) {
1267
1267
  if (!ctx) {
1268
- throw new errors_js_1.ValidationError(`[turbine] Column reference { col: "${ref.col}" } is not supported in this filter context.`);
1268
+ throw new errors_js_1.ValidationError(`Column reference { col: "${ref.col}" } is not supported in this filter context.`);
1269
1269
  }
1270
1270
  return `${ctx.prefix}${qi.q(resolveColumnRef(qi, ref, ctx, mode))}`;
1271
1271
  }
@@ -1434,7 +1434,7 @@ function vectorOperator(qi, field, rawColumn, metric) {
1434
1434
  }
1435
1435
  const colType = getColumnPgType(qi, rawColumn);
1436
1436
  if (colType !== 'vector') {
1437
- throw new errors_js_1.ValidationError(`[turbine] Column "${field}" on table "${qi.table}" is not a vector column ` +
1437
+ throw new errors_js_1.ValidationError(`Column "${field}" on table "${qi.table}" is not a vector column ` +
1438
1438
  `(actual type: ${colType}); cannot apply a vector distance operation.`);
1439
1439
  }
1440
1440
  // ownLookup, not a bare index: an inherited Object.prototype member
@@ -1442,7 +1442,7 @@ function vectorOperator(qi, field, rawColumn, metric) {
1442
1442
  // and be spliced into the ORDER BY / WHERE clause as its source text.
1443
1443
  const op = (0, utils_js_1.ownLookup)(filters_js_1.VECTOR_METRIC_OPERATORS, metric);
1444
1444
  if (!op) {
1445
- throw new errors_js_1.ValidationError(`[turbine] Unknown vector metric "${metric}" for column "${field}". ` +
1445
+ throw new errors_js_1.ValidationError(`Unknown vector metric "${metric}" for column "${field}". ` +
1446
1446
  `Valid metrics: ${Object.keys(filters_js_1.VECTOR_METRIC_OPERATORS).join(', ')}.`);
1447
1447
  }
1448
1448
  return op;
@@ -1459,11 +1459,11 @@ function pushVectorParam(qi, field, _rawColumn, to, params) {
1459
1459
  throw new errors_js_1.UnsupportedFeatureError('pgvector distance operations', qi.dialect.name, 'Vector search requires PostgreSQL with the pgvector extension.');
1460
1460
  }
1461
1461
  if (!Array.isArray(to) || to.length === 0) {
1462
- throw new errors_js_1.ValidationError(`[turbine] Vector distance on "${field}" requires a non-empty array of numbers for "to".`);
1462
+ throw new errors_js_1.ValidationError(`Vector distance on "${field}" requires a non-empty array of numbers for "to".`);
1463
1463
  }
1464
1464
  for (const el of to) {
1465
1465
  if (typeof el !== 'number' || !Number.isFinite(el)) {
1466
- throw new errors_js_1.ValidationError(`[turbine] Vector "to" for column "${field}" must contain only finite numbers; ` + `got ${JSON.stringify(el)}.`);
1466
+ throw new errors_js_1.ValidationError(`Vector "to" for column "${field}" must contain only finite numbers; ` + `got ${JSON.stringify(el)}.`);
1467
1467
  }
1468
1468
  }
1469
1469
  // Bind as a pgvector text literal '[1,2,3]'. Elements are already validated
@@ -1569,14 +1569,14 @@ function assertJsonFilterKeys(filter, column) {
1569
1569
  if (filters_js_1.JSON_FILTER_KEYS.has(key))
1570
1570
  continue;
1571
1571
  const suggestion = JSON_PRISMA_SPELLINGS[key];
1572
- throw new errors_js_1.ValidationError(`[turbine] Unknown JSON filter operator "${key}" on ${column}.` +
1572
+ throw new errors_js_1.ValidationError(`Unknown JSON filter operator "${key}" on ${column}.` +
1573
1573
  (suggestion ? ` Did you mean \`${suggestion}\`?` : '') +
1574
1574
  ` Supported operators: ${[...filters_js_1.JSON_FILTER_KEYS].sort().join(', ')}.`);
1575
1575
  }
1576
1576
  // `mode` and `path` are modifiers, not comparisons: a filter made only of
1577
1577
  // those compares nothing, which is exactly the silent no-op shape above.
1578
1578
  if (present.length > 0 && present.every((k) => k === 'path' || k === 'mode')) {
1579
- throw new errors_js_1.ValidationError(`[turbine] JSON filter on ${column} selects a \`path\` but has no comparison. ` +
1579
+ throw new errors_js_1.ValidationError(`JSON filter on ${column} selects a \`path\` but has no comparison. ` +
1580
1580
  `Add one of: ${[...filters_js_1.JSON_FILTER_KEYS]
1581
1581
  .filter((k) => k !== 'path' && k !== 'mode')
1582
1582
  .sort()
@@ -1608,14 +1608,13 @@ function jsonStringEntries(filter, column) {
1608
1608
  if (value === undefined)
1609
1609
  continue;
1610
1610
  if (filter.path === undefined) {
1611
- throw new errors_js_1.ValidationError(`[turbine] JSON operator '${op}' on ${column} requires a \`path\` ` +
1611
+ throw new errors_js_1.ValidationError(`JSON operator '${op}' on ${column} requires a \`path\` ` +
1612
1612
  `(e.g. { path: ['meta', 'title'], ${op}: ${valueForMessage(value)} }).`);
1613
1613
  }
1614
1614
  if (typeof value !== 'string') {
1615
1615
  // `typeof` rather than the value: it says everything the reader needs
1616
1616
  // (they passed a number where a string belongs) and carries no data.
1617
- throw new errors_js_1.ValidationError(`[turbine] JSON operator '${op}' on ${column} requires a string, got ${typeof value} ` +
1618
- `(${valueForMessage(value)}).`);
1617
+ throw new errors_js_1.ValidationError(`JSON operator '${op}' on ${column} requires a string, got ${typeof value} ` + `(${valueForMessage(value)}).`);
1619
1618
  }
1620
1619
  entries.push({ op, pattern, value });
1621
1620
  }
@@ -1628,15 +1627,15 @@ function jsonRangeEntries(_qi, filter, column) {
1628
1627
  if (value === undefined)
1629
1628
  continue;
1630
1629
  if (filter.path === undefined) {
1631
- throw new errors_js_1.ValidationError(`[turbine] JSON range operator '${op}' on ${column} requires a \`path\` ` +
1630
+ throw new errors_js_1.ValidationError(`JSON range operator '${op}' on ${column} requires a \`path\` ` +
1632
1631
  `(e.g. { path: ['meta', 'score'], ${op}: ${valueForMessage(value)} }).`);
1633
1632
  }
1634
1633
  if (typeof value !== 'number' && typeof value !== 'string') {
1635
- throw new errors_js_1.ValidationError(`[turbine] JSON range operator '${op}' on ${column} requires a number or string, ` +
1634
+ throw new errors_js_1.ValidationError(`JSON range operator '${op}' on ${column} requires a number or string, ` +
1636
1635
  `got ${typeof value} (${valueForMessage(value)}).`);
1637
1636
  }
1638
1637
  if (typeof value === 'number' && !Number.isFinite(value)) {
1639
- throw new errors_js_1.ValidationError(`[turbine] JSON range operator '${op}' on ${column} requires a finite number.`);
1638
+ throw new errors_js_1.ValidationError(`JSON range operator '${op}' on ${column} requires a finite number.`);
1640
1639
  }
1641
1640
  entries.push({ sqlOp, value });
1642
1641
  }
@@ -1821,13 +1820,13 @@ function vectorThresholdEntries(filter, field) {
1821
1820
  if (threshold === undefined)
1822
1821
  continue;
1823
1822
  if (typeof threshold !== 'number' || !Number.isFinite(threshold)) {
1824
- throw new errors_js_1.ValidationError(`[turbine] Vector distance threshold "${cmp}" on "${field}" must be a finite number; ` +
1823
+ throw new errors_js_1.ValidationError(`Vector distance threshold "${cmp}" on "${field}" must be a finite number; ` +
1825
1824
  `got ${typeof threshold} (${valueForMessage(threshold)}).`);
1826
1825
  }
1827
1826
  entries.push({ sqlOp, threshold });
1828
1827
  }
1829
1828
  if (entries.length === 0) {
1830
- throw new errors_js_1.ValidationError(`[turbine] Vector distance filter on "${field}" requires at least one comparison (lt / lte / gt / gte).`);
1829
+ throw new errors_js_1.ValidationError(`Vector distance filter on "${field}" requires at least one comparison (lt / lte / gt / gte).`);
1831
1830
  }
1832
1831
  return entries;
1833
1832
  }
@@ -1839,7 +1838,7 @@ function buildTextSearchClause(qi, column, filter, params) {
1839
1838
  requireFullTextSearch(qi);
1840
1839
  const config = filter.config ?? 'english';
1841
1840
  if (!(0, filters_js_1.validateTextSearchConfig)(config)) {
1842
- throw new errors_js_1.ValidationError(`[turbine] Invalid text search config "${config}": only alphanumeric characters and underscores are allowed.`);
1841
+ throw new errors_js_1.ValidationError(`Invalid text search config "${config}": only alphanumeric characters and underscores are allowed.`);
1843
1842
  }
1844
1843
  params.push(filter.search);
1845
1844
  return `to_tsvector('${config}', ${column}) @@ to_tsquery('${config}', ${qi.p(params.length)})`;
@@ -195,7 +195,7 @@ function buildCreate(qi, args) {
195
195
  throw new errors_js_1.NotFoundError({
196
196
  table: qi.table,
197
197
  operation: 'create',
198
- message: `[turbine] create on "${qi.table}" returned no row from RETURNING *; this should never happen.`,
198
+ message: `create on "${qi.table}" returned no row from RETURNING *; this should never happen.`,
199
199
  });
200
200
  }
201
201
  return parseWriteRow(qi, row);
@@ -307,7 +307,7 @@ function assertUniformCreateManyRows(qi, rows, firstKeys) {
307
307
  parts.push(`does not supply ${quoteList(missing)}`);
308
308
  if (unexpected.length > 0)
309
309
  parts.push(`supplies ${quoteList(unexpected)}, which the first row does not`);
310
- throw new errors_js_1.ValidationError(`[turbine] createMany on "${qi.table}": row ${i} ${parts.join(' and ')}. ` +
310
+ throw new errors_js_1.ValidationError(`createMany on "${qi.table}": row ${i} ${parts.join(' and ')}. ` +
311
311
  'Every row must supply the same fields: createMany builds ONE statement whose column list comes from the ' +
312
312
  "first row, so a field a later row omits would be written as NULL over that column's default, and a field " +
313
313
  'only a later row names would be dropped. Supply the field explicitly on every row (a field set to ' +
@@ -638,7 +638,7 @@ function buildUpsert(qi, args) {
638
638
  table: qi.table,
639
639
  where: args.where,
640
640
  operation: 'upsert',
641
- message: `[turbine] upsert on "${qi.table}" returned no row from RETURNING *; this should never happen.`,
641
+ message: `upsert on "${qi.table}" returned no row from RETURNING *; this should never happen.`,
642
642
  });
643
643
  }
644
644
  return parseWriteRow(qi, row);
@@ -880,7 +880,7 @@ function parseWriteRow(qi, row) {
880
880
  */
881
881
  function assertWritable(qi, operation) {
882
882
  if (qi.tableMeta.isView) {
883
- throw new errors_js_1.ValidationError(`[turbine] Cannot ${operation} "${qi.table}": it is a view (read-only). ` +
883
+ throw new errors_js_1.ValidationError(`Cannot ${operation} "${qi.table}": it is a view (read-only). ` +
884
884
  'Views support reads (findMany/findFirst/…) but not writes.');
885
885
  }
886
886
  }
@@ -897,7 +897,7 @@ function assertNoGeneratedColumns(qi, data, operation) {
897
897
  continue;
898
898
  const col = qi.tableMeta.columns.find((c) => c.field === key || c.name === key || c.name === (0, schema_js_1.camelToSnake)(key));
899
899
  if (col?.isGeneratedStored) {
900
- throw new errors_js_1.ValidationError(`[turbine] Cannot ${operation} "${qi.table}": column "${key}" is a GENERATED ALWAYS AS (…) STORED ` +
900
+ throw new errors_js_1.ValidationError(`Cannot ${operation} "${qi.table}": column "${key}" is a GENERATED ALWAYS AS (…) STORED ` +
901
901
  'column whose value the database computes, remove it from your data.');
902
902
  }
903
903
  }
@@ -941,7 +941,7 @@ function buildSetClause(qi, key, value, params) {
941
941
  }
942
942
  // Arithmetic operators: must be finite numbers
943
943
  if (typeof opValue !== 'number' || !Number.isFinite(opValue)) {
944
- throw new errors_js_1.ValidationError(`[turbine] update operator "${op}" on "${qi.table}.${key}" requires a finite number, got ${typeof opValue}`);
944
+ throw new errors_js_1.ValidationError(`update operator "${op}" on "${qi.table}.${key}" requires a finite number, got ${typeof opValue}`);
945
945
  }
946
946
  if (op === 'increment') {
947
947
  params.push(opValue);
@@ -995,7 +995,7 @@ function assertBindableSetValue(qi, key, value) {
995
995
  const column = qi.toColumn(key);
996
996
  if (whereMod.isJsonColumnType(qi, whereMod.getColumnPgType(qi, column)))
997
997
  return;
998
- throw new errors_js_1.ValidationError(`[turbine] Unknown update operator "${keys[0]}" on "${qi.table}.${key}". ` +
998
+ throw new errors_js_1.ValidationError(`Unknown update operator "${keys[0]}" on "${qi.table}.${key}". ` +
999
999
  `Supported: ${[...filters_js_1.UPDATE_OPERATOR_KEYS].join(', ')}. ` +
1000
1000
  'A plain object is only written as a value on a json/jsonb column.');
1001
1001
  }
@@ -52,13 +52,13 @@ const MAX_CHANNEL_LEN = 63;
52
52
  */
53
53
  function validateChannel(channel) {
54
54
  if (typeof channel !== 'string' || channel.length === 0) {
55
- throw new errors_js_1.ValidationError('[turbine] $listen/$notify channel must be a non-empty string');
55
+ throw new errors_js_1.ValidationError('$listen/$notify channel must be a non-empty string');
56
56
  }
57
57
  if (channel.length > MAX_CHANNEL_LEN) {
58
- throw new errors_js_1.ValidationError(`[turbine] $listen/$notify channel "${channel}" exceeds the ${MAX_CHANNEL_LEN}-character Postgres identifier limit`);
58
+ throw new errors_js_1.ValidationError(`$listen/$notify channel "${channel}" exceeds the ${MAX_CHANNEL_LEN}-character Postgres identifier limit`);
59
59
  }
60
60
  if (!CHANNEL_REGEX.test(channel)) {
61
- throw new errors_js_1.ValidationError(`[turbine] Invalid $listen/$notify channel "${channel}", must match /^[A-Za-z_][A-Za-z0-9_]*$/ ` +
61
+ throw new errors_js_1.ValidationError(`Invalid $listen/$notify channel "${channel}", must match /^[A-Za-z_][A-Za-z0-9_]*$/ ` +
62
62
  '(letters, digits, underscores; cannot start with a digit)');
63
63
  }
64
64
  }
@@ -86,7 +86,7 @@ async function createSubscription(pool, channel, quotedChannel, handler, onClose
86
86
  // give the connection straight back.
87
87
  if (typeof client.on !== 'function') {
88
88
  client.release?.();
89
- throw new errors_js_1.ConnectionError('[turbine] $listen requires a persistent connection that can push notifications. ' +
89
+ throw new errors_js_1.ConnectionError('$listen requires a persistent connection that can push notifications. ' +
90
90
  'The configured pool returned a client with no event support (stateless HTTP drivers ' +
91
91
  'like Neon HTTP / Vercel Postgres cannot LISTEN). Use a TCP pg.Pool for LISTEN/NOTIFY.');
92
92
  }
@@ -133,7 +133,7 @@ function mapIndexes(tableDef, declared) {
133
133
  // index instead of emitting malformed PowQL later.
134
134
  for (const seg of idx.path) {
135
135
  if (typeof seg === 'number' && (!Number.isInteger(seg) || seg < 0)) {
136
- throw new errors_js_1.ValidationError(`[turbine] Doc-field index "${name}" on "${tableDef.name}": array-index path segment ${seg} must be a ` +
136
+ throw new errors_js_1.ValidationError(`Doc-field index "${name}" on "${tableDef.name}": array-index path segment ${seg} must be a ` +
137
137
  'non-negative integer (a JSON array index). Use a string for an object key.');
138
138
  }
139
139
  }
@@ -90,7 +90,7 @@ function resolveDdlType(config, dialect, columnName) {
90
90
  const dims = config.vectorDimensions;
91
91
  // pgvector caps a `vector` column at 16000 dimensions.
92
92
  if (typeof dims !== 'number' || !Number.isInteger(dims) || dims < 1 || dims > 16000) {
93
- throw new errors_js_1.ValidationError(`[turbine] Column "${columnName}": vector dimensions must be an integer between 1 and 16000, got ${String(dims)}.`);
93
+ throw new errors_js_1.ValidationError(`Column "${columnName}": vector dimensions must be an integer between 1 and 16000, got ${String(dims)}.`);
94
94
  }
95
95
  base = `vector(${dims})`;
96
96
  }
@@ -1545,7 +1545,7 @@ async function pinSearchPath(client, pgSchema) {
1545
1545
  pgSchema,
1546
1546
  ]);
1547
1547
  if (!present.rows[0]?.present) {
1548
- throw new errors_js_1.ValidationError(`[turbine] Schema "${pgSchema}" does not exist in this database. ` +
1548
+ throw new errors_js_1.ValidationError(`Schema "${pgSchema}" does not exist in this database. ` +
1549
1549
  `Postgres accepts a missing namespace in search_path without complaint, so pushing anyway would ` +
1550
1550
  `create every table in whichever schema resolves next (usually public) and report success. ` +
1551
1551
  `Create the schema first (CREATE SCHEMA "${pgSchema}") or correct the configured schema name.`);
@@ -1561,7 +1561,7 @@ async function pinSearchPath(client, pgSchema) {
1561
1561
  }
1562
1562
  /** Format the destructive-push refusal message (mirrors the migrate gate copy). */
1563
1563
  function formatDestructivePushError(hits) {
1564
- const lines = ['[turbine] Refusing to apply schema changes containing DESTRUCTIVE statements:', ''];
1564
+ const lines = ['Refusing to apply schema changes containing DESTRUCTIVE statements:', ''];
1565
1565
  for (const h of hits) {
1566
1566
  lines.push(` - [${h.kind}] ${h.target}: ${destructive_js_1.DESTRUCTIVE_KIND_LABEL[h.kind]}`);
1567
1567
  }
package/dist/cjs/seed.js CHANGED
@@ -132,7 +132,7 @@ function markSeedRan() {
132
132
  async function runSeed(fn) {
133
133
  const connectionString = process.env.DATABASE_URL;
134
134
  if (!connectionString) {
135
- throw new errors_js_1.ConnectionError('[turbine] DATABASE_URL is required to run this seed.');
135
+ throw new errors_js_1.ConnectionError('DATABASE_URL is required to run this seed.');
136
136
  }
137
137
  const db = new client_js_1.TurbineClient({ connectionString }, emptySchema);
138
138
  try {
@@ -115,11 +115,11 @@ function loadDatabaseSync() {
115
115
  ctor = req('node:sqlite').DatabaseSync;
116
116
  }
117
117
  catch (err) {
118
- throw new errors_js_1.ConnectionError("[turbine] turbine-orm/sqlite requires Node's built-in 'node:sqlite' module (Node >= 22.5). " +
118
+ throw new errors_js_1.ConnectionError("turbine-orm/sqlite requires Node's built-in 'node:sqlite' module (Node >= 22.5). " +
119
119
  `Upgrade Node to >= 22.5, or pass an already-open better-sqlite3-compatible handle. (${err.message})`);
120
120
  }
121
121
  if (typeof ctor !== 'function') {
122
- throw new errors_js_1.ConnectionError("[turbine] 'node:sqlite' loaded but did not export a DatabaseSync constructor, this Node build may lack SQLite support.");
122
+ throw new errors_js_1.ConnectionError("'node:sqlite' loaded but did not export a DatabaseSync constructor, this Node build may lack SQLite support.");
123
123
  }
124
124
  cachedDatabaseSync = ctor;
125
125
  return cachedDatabaseSync;
@@ -370,17 +370,17 @@ function augmentSqliteError(err) {
370
370
  function sqliteLogicError(message, cause) {
371
371
  const missingTable = /^no such table:\s*(\S+)/i.exec(message);
372
372
  if (missingTable) {
373
- return new errors_js_1.ValidationError(`[turbine] SQLite has no table "${missingTable[1]}": ${message}. ` +
373
+ return new errors_js_1.ValidationError(`SQLite has no table "${missingTable[1]}": ${message}. ` +
374
374
  'Create it first (run your migrations, or execute the CREATE TABLE statements for this schema); ' +
375
375
  'an in-memory database starts empty on every connection.', { cause });
376
376
  }
377
377
  const missingColumn = /^no such column:\s*(\S+)/i.exec(message);
378
378
  if (missingColumn) {
379
- return new errors_js_1.ValidationError(`[turbine] SQLite has no column "${missingColumn[1]}": ${message}. ` +
379
+ return new errors_js_1.ValidationError(`SQLite has no column "${missingColumn[1]}": ${message}. ` +
380
380
  'Check the spelling against the table as it exists in this database, ' +
381
381
  'and re-run `turbine generate` if the schema has changed.', { cause });
382
382
  }
383
- return new errors_js_1.ValidationError(`[turbine] SQLite rejected the statement: ${message}`, { cause });
383
+ return new errors_js_1.ValidationError(`SQLite rejected the statement: ${message}`, { cause });
384
384
  }
385
385
  function normalizeQueryArgs(arg, values) {
386
386
  if (typeof arg === 'string')
@@ -995,7 +995,7 @@ async function introspectSqlite(options) {
995
995
  db = new DatabaseSync(options.connectionString);
996
996
  }
997
997
  catch (err) {
998
- throw new errors_js_1.ConnectionError(`[turbine] Failed to open SQLite database "${options.connectionString}": ${err.message}`);
998
+ throw new errors_js_1.ConnectionError(`Failed to open SQLite database "${options.connectionString}": ${err.message}`);
999
999
  }
1000
1000
  try {
1001
1001
  return introspectSqliteDatabase(db, { include: options.include, exclude: options.exclude });
@@ -1011,7 +1011,7 @@ function openSqliteDatabase(target, options) {
1011
1011
  db = new DatabaseSync(target);
1012
1012
  }
1013
1013
  catch (err) {
1014
- throw new errors_js_1.ConnectionError(`[turbine] Failed to open SQLite database "${target}": ${err.message}`);
1014
+ throw new errors_js_1.ConnectionError(`Failed to open SQLite database "${target}": ${err.message}`);
1015
1015
  }
1016
1016
  db.exec(`PRAGMA busy_timeout = ${Number(options.busyTimeoutMs ?? 5000)}`);
1017
1017
  if (options.foreignKeys !== false)
@@ -70,7 +70,7 @@ function buildTypedSql(strings, values, dialect = dialect_js_1.postgresDialect)
70
70
  // silently desync placeholders from params (pg would reject it, but fail
71
71
  // loudly here instead).
72
72
  if (strings.length !== values.length + 1) {
73
- throw new errors_js_1.ValidationError(`[turbine] sql template segment/value count mismatch: ${strings.length} segments, ${values.length} values.`);
73
+ throw new errors_js_1.ValidationError(`sql template segment/value count mismatch: ${strings.length} segments, ${values.length} values.`);
74
74
  }
75
75
  let sql = '';
76
76
  for (let i = 0; i < strings.length; i++) {
package/dist/cli/index.js CHANGED
@@ -2587,7 +2587,7 @@ async function cmdMigrateDeploy(args, config) {
2587
2587
  error('Deploy blocked by migration drift');
2588
2588
  errorLine();
2589
2589
  for (const line of formatChecksumMismatchError(plan.mismatches).split('\n')) {
2590
- errorLine(`${line.replace('[turbine] ', '')}`);
2590
+ errorLine(line);
2591
2591
  }
2592
2592
  errorLine();
2593
2593
  process.exit(1);
@@ -2661,7 +2661,7 @@ async function confirmDestructive(report) {
2661
2661
  error('DESTRUCTIVE MIGRATION DETECTED');
2662
2662
  newline();
2663
2663
  for (const line of report.split('\n'))
2664
- console.log(` ${line.includes('[turbine]') ? line.replace('[turbine] ', '') : line}`);
2664
+ console.log(` ${line}`);
2665
2665
  newline();
2666
2666
  if (!process.stdin.isTTY) {
2667
2667
  console.log(` ${dim('Non-interactive shell: rerun with')} ${cyan('--allow-destructive')} ${dim('to proceed.')}`);
@@ -55,7 +55,7 @@ export function assertNoEmbeddedTransactions(files, section) {
55
55
  if (offenders.length === 0)
56
56
  return;
57
57
  const lines = [
58
- `[turbine] Refusing to run migrations that manage transactions themselves (${section.toUpperCase()} section):`,
58
+ `Refusing to run migrations that manage transactions themselves (${section.toUpperCase()} section):`,
59
59
  '',
60
60
  ];
61
61
  for (const o of offenders) {
@@ -307,7 +307,7 @@ export function parseMigrationContent(content, source) {
307
307
  }
308
308
  if (!sawUpMarker) {
309
309
  throw new MigrationError([
310
- `[turbine] Migration file has no \`-- UP\` section marker${source ? `: ${source}` : '.'}`,
310
+ `Migration file has no \`-- UP\` section marker${source ? `: ${source}` : '.'}`,
311
311
  '',
312
312
  'A migration must contain a line reading `-- UP` (a `-- DOWN` line is optional).',
313
313
  'Without it the whole file is a header comment: nothing would run, and the',
@@ -586,7 +586,7 @@ export function createMigration(migrationsDir, name, autoContent, options) {
586
586
  const recipe = MIGRATION_RECIPES[options.recipe];
587
587
  if (!recipe) {
588
588
  const known = Object.keys(MIGRATION_RECIPES).join(', ') || '(none)';
589
- throw new MigrationError(`[turbine] Unknown migration recipe "${options.recipe}". Available recipes: ${known}`);
589
+ throw new MigrationError(`Unknown migration recipe "${options.recipe}". Available recipes: ${known}`);
590
590
  }
591
591
  // A recipe builds the BODY, below `-- UP`, where a newline is not merely a
592
592
  // comment break but directly executable, so it gets the safe name too.
@@ -818,7 +818,7 @@ export function formatChecksumMismatchError(mismatches) {
818
818
  const modified = mismatches.filter((m) => m.type === 'modified');
819
819
  const missing = mismatches.filter((m) => m.type === 'missing');
820
820
  const lines = [
821
- '[turbine] Migration drift detected, refusing to apply pending migrations.',
821
+ 'Migration drift detected, refusing to apply pending migrations.',
822
822
  '',
823
823
  'Applied migrations should be immutable. The following files no longer match their applied state:',
824
824
  '',
@@ -935,7 +935,7 @@ export async function migrateUp(connectionString, migrationsDir, options) {
935
935
  const adapter = options?.adapter;
936
936
  const lock = await acquireMigrationLock(client, lockId, adapter, () => openLockConnection(connectionString));
937
937
  if (!lock.acquired) {
938
- throw new MigrationError('[turbine] Could not acquire migration lock, another migration is already running');
938
+ throw new MigrationError('Could not acquire migration lock, another migration is already running');
939
939
  }
940
940
  try {
941
941
  await ensureTrackingTable(client, dialect);
@@ -979,7 +979,7 @@ export async function migrateUp(connectionString, migrationsDir, options) {
979
979
  // pass `allowDestructive: true`. Safe-by-default is the whole point, a
980
980
  // DROP TABLE should never run just because a file exists.
981
981
  if (!options?.allowDestructive && destructive.length > 0) {
982
- const lines = ['[turbine] Refusing to apply migrations containing DESTRUCTIVE statements:', ''];
982
+ const lines = ['Refusing to apply migrations containing DESTRUCTIVE statements:', ''];
983
983
  for (const o of destructive) {
984
984
  lines.push(` ${o.file}`);
985
985
  for (const h of o.hits) {
@@ -1159,7 +1159,7 @@ export async function migrateDown(connectionString, migrationsDir, options) {
1159
1159
  const adapter = options?.adapter;
1160
1160
  const lock = await acquireMigrationLock(client, lockId, adapter, () => openLockConnection(connectionString));
1161
1161
  if (!lock.acquired) {
1162
- throw new MigrationError('[turbine] Could not acquire migration lock, another migration is already running');
1162
+ throw new MigrationError('Could not acquire migration lock, another migration is already running');
1163
1163
  }
1164
1164
  try {
1165
1165
  await ensureTrackingTable(client, dialect);
@@ -1191,7 +1191,7 @@ export async function migrateDown(connectionString, migrationsDir, options) {
1191
1191
  offenders.push({ file: file.filename, hits });
1192
1192
  }
1193
1193
  if (offenders.length > 0) {
1194
- const lines = ['[turbine] Refusing to roll back migrations whose DOWN sections are DESTRUCTIVE:', ''];
1194
+ const lines = ['Refusing to roll back migrations whose DOWN sections are DESTRUCTIVE:', ''];
1195
1195
  for (const o of offenders) {
1196
1196
  lines.push(` ${o.file}`);
1197
1197
  for (const h of o.hits) {
@@ -391,6 +391,9 @@ function constantTimeEqual(a, b) {
391
391
  function unknownTableMessage(name, ctx) {
392
392
  const available = Object.keys(ctx.metadata.tables);
393
393
  const list = available.length ? available.join(', ') : '(none)';
394
+ // turbine-prefix-ok: every caller passes this straight to sendJson as an HTTP
395
+ // body, never to a TurbineError, so nothing prepends a `[TURBINE_EXXX]` tag
396
+ // and the prefix is the browser's only signal of who produced the message.
394
397
  return `[turbine] Unknown table "${name}" in schema "${ctx.options.schema}". Available: ${list}`;
395
398
  }
396
399
  // ---------------------------------------------------------------------------
@@ -816,17 +819,17 @@ function assertNoPiiPredicates(args, tableName, metadata, showPii) {
816
819
  metadata,
817
820
  hiddenReason: (table, column) => (isRedactedColumn(table, column, showPii) ? 'is PII-tagged and redacted' : null),
818
821
  refuseColumn: (table, column, reason) => {
819
- throw new ValidationError(`[turbine] Column "${column}" on "${table.name}" ${reason}, so it cannot be used ` +
822
+ throw new ValidationError(`Column "${column}" on "${table.name}" ${reason}, so it cannot be used ` +
820
823
  `in a where, orderBy, cursor, or distinct: filtering, sorting, paging, or de-duplicating on a hidden ` +
821
824
  `value reveals it. Restart Studio with --show-pii to query it.`);
822
825
  },
823
826
  refuseDepth: (maxDepth) => {
824
- throw new ValidationError(`[turbine] Query is nested more than ${maxDepth} levels deep, which is past the point where ` +
827
+ throw new ValidationError(`Query is nested more than ${maxDepth} levels deep, which is past the point where ` +
825
828
  `Studio can prove it does not filter or sort on a PII-tagged and redacted column, so it is refused. ` +
826
829
  `Flatten the query, or restart Studio with --show-pii.`);
827
830
  },
828
831
  refuseShape: (table, key) => {
829
- throw new ValidationError(`[turbine] Studio does not recognize "${key}" in a query on "${table.name}", so it cannot prove the ` +
832
+ throw new ValidationError(`Studio does not recognize "${key}" in a query on "${table.name}", so it cannot prove the ` +
830
833
  `query does not filter or sort on a PII-tagged and redacted column, and refuses it rather than ` +
831
834
  `guessing. Remove it, or restart Studio with --show-pii.`);
832
835
  },