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
@@ -214,11 +214,11 @@ function keyOf(value) {
214
214
  function validateOps(relationName, ops, isUpdate) {
215
215
  for (const opName of Object.keys(ops)) {
216
216
  if (!CREATE_ONLY_OPS.has(opName) && !UPDATE_ONLY_OPS.has(opName)) {
217
- throw new ValidationError(`[turbine] Unknown nested write operation "${opName}" on relation "${relationName}". ` +
217
+ throw new ValidationError(`Unknown nested write operation "${opName}" on relation "${relationName}". ` +
218
218
  `Valid operations: create, connect, connectOrCreate${isUpdate ? ', disconnect, set, delete, update, upsert' : ''}.`);
219
219
  }
220
220
  if (!isUpdate && UPDATE_ONLY_OPS.has(opName)) {
221
- throw new ValidationError(`[turbine] Operation "${opName}" on relation "${relationName}" is only valid inside update(), not create().`);
221
+ throw new ValidationError(`Operation "${opName}" on relation "${relationName}" is only valid inside update(), not create().`);
222
222
  }
223
223
  }
224
224
  }
@@ -237,7 +237,7 @@ function pkWhere(tableMeta, row) {
237
237
  // be one this engine just read, so a missing member is an internal fault,
238
238
  // and the only safe response is to refuse rather than to run.
239
239
  if (row[field] === undefined) {
240
- throw new ValidationError(`[turbine] Cannot address a row of "${tableMeta.name}" by primary key: "${field}" is missing from the ` +
240
+ throw new ValidationError(`Cannot address a row of "${tableMeta.name}" by primary key: "${field}" is missing from the ` +
241
241
  'row this nested write is operating on, so the generated predicate would match more rows than intended. ' +
242
242
  'This is a bug in turbine, please report it.');
243
243
  }
@@ -339,7 +339,7 @@ function assertTargetSelectsSomething(target, op, relName, rel) {
339
339
  if (target === true) {
340
340
  if (toOne)
341
341
  return;
342
- throw new ValidationError(`[turbine] Nested ${op} on to-many relation "${relName}" needs a "where" selector: ` +
342
+ throw new ValidationError(`Nested ${op} on to-many relation "${relName}" needs a "where" selector: ` +
343
343
  `"${op}: true" would ${op} every related "${rel.to}" row.`);
344
344
  }
345
345
  if (target && typeof target === 'object' && !Array.isArray(target)) {
@@ -347,7 +347,7 @@ function assertTargetSelectsSomething(target, op, relName, rel) {
347
347
  if (bound)
348
348
  return;
349
349
  }
350
- throw new ValidationError(`[turbine] Nested ${op} on relation "${relName}" requires a selector with at least one defined value. ` +
350
+ throw new ValidationError(`Nested ${op} on relation "${relName}" requires a selector with at least one defined value. ` +
351
351
  `An empty or all-undefined "where" would ${op} every related "${rel.to}" row of this parent.`);
352
352
  }
353
353
  /**
@@ -386,7 +386,7 @@ function junctionAccessorHint(junction) {
386
386
  */
387
387
  function manyToManyOpUnsupported(op, relName, rel) {
388
388
  const junction = rel.through?.table ?? 'junction';
389
- return new ValidationError(`[turbine] Nested "${op}" is not supported on the many-to-many relation "${relName}" ` +
389
+ return new ValidationError(`Nested "${op}" is not supported on the many-to-many relation "${relName}" ` +
390
390
  `(via the "${junction}" junction table). The supported many-to-many nested operations are ` +
391
391
  `connect, disconnect and set. To ${op} a "${rel.to}" row itself, write it directly on the ` +
392
392
  `"${rel.to}" table inside the same $transaction and link it with a nested connect; to write ` +
@@ -424,24 +424,24 @@ function junctionPairIsUnique(junctionMeta, sourceCol, targetCol) {
424
424
  function junctionPlan(ctx, rel, relName, parentRow) {
425
425
  const through = rel.through;
426
426
  if (!through) {
427
- throw new ValidationError(`[turbine] manyToMany relation "${relName}" is missing a \`through\` junction descriptor.`);
427
+ throw new ValidationError(`manyToMany relation "${relName}" is missing a \`through\` junction descriptor.`);
428
428
  }
429
429
  const junctionMeta = ctx.schema.tables[through.table];
430
430
  if (!junctionMeta) {
431
- throw new ValidationError(`[turbine] Nested write on the many-to-many relation "${relName}": junction table ` +
431
+ throw new ValidationError(`Nested write on the many-to-many relation "${relName}": junction table ` +
432
432
  `"${through.table}" is not present in the schema metadata, so its rows cannot be written. ` +
433
433
  `Regenerate the schema (turbine generate) so the junction table is included.`);
434
434
  }
435
435
  const targetMeta = ctx.schema.tables[rel.to];
436
436
  if (!targetMeta) {
437
- throw new ValidationError(`[turbine] Nested write on the many-to-many relation "${relName}": unknown target table "${rel.to}".`);
437
+ throw new ValidationError(`Nested write on the many-to-many relation "${relName}": unknown target table "${rel.to}".`);
438
438
  }
439
439
  const sourceKeys = normalizeKeyColumns(through.sourceKey);
440
440
  const targetKeys = normalizeKeyColumns(through.targetKey);
441
441
  const refKeys = normalizeKeyColumns(rel.referenceKey);
442
442
  const targetPk = targetMeta.primaryKey;
443
443
  if (sourceKeys.length !== 1 || targetKeys.length !== 1 || refKeys.length !== 1 || targetPk.length !== 1) {
444
- throw new ValidationError(`[turbine] Nested writes on the many-to-many relation "${relName}" (via "${through.table}") support ` +
444
+ throw new ValidationError(`Nested writes on the many-to-many relation "${relName}" (via "${through.table}") support ` +
445
445
  `single-column junction keys only; this relation links on composite keys ` +
446
446
  `(source ${sourceKeys.length}, target ${targetKeys.length}, target primary key ${targetPk.length} column(s)). ` +
447
447
  `Write the junction rows directly inside the same $transaction: ${junctionAccessorHint(through.table)}.`);
@@ -454,7 +454,7 @@ function junctionPlan(ctx, rel, relName, parentRow) {
454
454
  // key at all. Introspection can never produce this (it requires two distinct
455
455
  // FK columns), but a hand-declared `defineSchema` manyToMany can.
456
456
  if (sourceKeys[0] === targetKeys[0]) {
457
- throw new ValidationError(`[turbine] Nested write on the many-to-many relation "${relName}" cannot run: the junction ` +
457
+ throw new ValidationError(`Nested write on the many-to-many relation "${relName}" cannot run: the junction ` +
458
458
  `"${through.table}" names the same column "${sourceKeys[0]}" as BOTH its sourceKey and its ` +
459
459
  `targetKey, so a link row cannot hold the parent key and the target key at once. Fix the ` +
460
460
  `relation's \`through\` descriptor to name the two distinct junction columns.`);
@@ -462,7 +462,7 @@ function junctionPlan(ctx, rel, relName, parentRow) {
462
462
  const parentField = ctx.schema.tables[rel.from]?.reverseColumnMap[refKeys[0]] ?? refKeys[0];
463
463
  const parentValue = parentRow[parentField];
464
464
  if (parentValue === null || parentValue === undefined) {
465
- throw new ValidationError(`[turbine] Nested write on the many-to-many relation "${relName}" cannot run: the parent's reference ` +
465
+ throw new ValidationError(`Nested write on the many-to-many relation "${relName}" cannot run: the parent's reference ` +
466
466
  `key "${parentField}" is ${parentValue === null ? 'null' : 'missing from the loaded row'}, so no ` +
467
467
  `junction row can be correlated to this parent.`);
468
468
  }
@@ -477,7 +477,7 @@ function junctionPlan(ctx, rel, relName, parentRow) {
477
477
  }
478
478
  /** The E003 for a target selector that matched no row. */
479
479
  function noTargetRow(op, relName, rel, target) {
480
- return new ValidationError(`[turbine] Nested ${op} on the many-to-many relation "${relName}": no "${rel.to}" row found ` +
480
+ return new ValidationError(`Nested ${op} on the many-to-many relation "${relName}": no "${rel.to}" row found ` +
481
481
  `matching ${describeTargetForMessage(target)}.`);
482
482
  }
483
483
  /**
@@ -536,7 +536,7 @@ async function resolveJunctionTargets(ctx, rel, relName, plan, op, items) {
536
536
  throw noTargetRow(op, relName, rel, target);
537
537
  const value = row[plan.targetKeyField];
538
538
  if (value === null || value === undefined) {
539
- throw new ValidationError(`[turbine] Nested ${op} on the many-to-many relation "${relName}": the "${rel.to}" row matching ` +
539
+ throw new ValidationError(`Nested ${op} on the many-to-many relation "${relName}": the "${rel.to}" row matching ` +
540
540
  `${describeTargetForMessage(target)} has no "${plan.targetKeyField}" value to link.`);
541
541
  }
542
542
  const key = keyOf(value);
@@ -735,7 +735,7 @@ function notRelatedToParent(op, relName, rel, target) {
735
735
  table: rel.to,
736
736
  where: target,
737
737
  operation: `nested ${op}`,
738
- message: `[turbine] Nested ${op} on relation "${relName}": no "${rel.to}" record matching ` +
738
+ message: `Nested ${op} on relation "${relName}": no "${rel.to}" record matching ` +
739
739
  `${describeTargetForMessage(target)} is related to this parent. Either it does not exist, ` +
740
740
  `or it belongs to a different parent (a nested ${op} can only touch this parent's rows).`,
741
741
  });
@@ -769,14 +769,14 @@ export async function executeNestedCreate(ctx, tableName, data, depth = 0, path
769
769
  }
770
770
  const tableMeta = ctx.schema.tables[tableName];
771
771
  if (!tableMeta) {
772
- throw new ValidationError(`[turbine] Unknown table "${tableName}".`);
772
+ throw new ValidationError(`Unknown table "${tableName}".`);
773
773
  }
774
774
  const { scalars, relations } = extractRelationFields(data, tableMeta);
775
775
  // Validate all relation operations
776
776
  for (const [relName, ops] of Object.entries(relations)) {
777
777
  const rel = tableMeta.relations[relName];
778
778
  if (!rel) {
779
- throw new RelationError(`[turbine] Unknown relation "${relName}" on table "${tableName}". ` +
779
+ throw new RelationError(`Unknown relation "${relName}" on table "${tableName}". ` +
780
780
  `Available relations: ${Object.keys(tableMeta.relations).join(', ') || '(none)'}.`);
781
781
  }
782
782
  validateOps(relName, ops, false);
@@ -848,14 +848,14 @@ export async function executeNestedUpdate(ctx, tableName, where, data, depth = 0
848
848
  }
849
849
  const tableMeta = ctx.schema.tables[tableName];
850
850
  if (!tableMeta) {
851
- throw new ValidationError(`[turbine] Unknown table "${tableName}".`);
851
+ throw new ValidationError(`Unknown table "${tableName}".`);
852
852
  }
853
853
  const { scalars, relations } = extractRelationFields(data, tableMeta);
854
854
  // Validate all relation operations
855
855
  for (const [relName, ops] of Object.entries(relations)) {
856
856
  const rel = tableMeta.relations[relName];
857
857
  if (!rel) {
858
- throw new RelationError(`[turbine] Unknown relation "${relName}" on table "${tableName}". ` +
858
+ throw new RelationError(`Unknown relation "${relName}" on table "${tableName}". ` +
859
859
  `Available relations: ${Object.keys(tableMeta.relations).join(', ') || '(none)'}.`);
860
860
  }
861
861
  validateOps(relName, ops, true);
@@ -868,7 +868,7 @@ export async function executeNestedUpdate(ctx, tableName, where, data, depth = 0
868
868
  else {
869
869
  parentRow = (await ctx.tx.table(tableName).findUnique({ where }));
870
870
  if (!parentRow) {
871
- throw new ValidationError(`[turbine] update: no ${tableName} row found matching ${describeTargetForMessage(where)}.`);
871
+ throw new ValidationError(`update: no ${tableName} row found matching ${describeTargetForMessage(where)}.`);
872
872
  }
873
873
  }
874
874
  // Process each relation
@@ -916,7 +916,7 @@ export async function executeNestedUpdate(ctx, tableName, where, data, depth = 0
916
916
  return col?.nullable ?? false;
917
917
  });
918
918
  if (!nullable) {
919
- throw new ValidationError(`[turbine] Cannot disconnect "${relName}": foreign key column(s) ${fks.join(', ')} are NOT NULL. Use delete instead.`);
919
+ throw new ValidationError(`Cannot disconnect "${relName}": foreign key column(s) ${fks.join(', ')} are NOT NULL. Use delete instead.`);
920
920
  }
921
921
  const updateData = {};
922
922
  for (const fk of fks) {
@@ -1025,7 +1025,7 @@ async function resolveBelongsToForCreate(ctx, rel, ops, parentTable, depth, path
1025
1025
  const target = items[0];
1026
1026
  relatedRow = (await ctx.tx.table(rel.to).findUnique({ where: target }));
1027
1027
  if (!relatedRow) {
1028
- throw new ValidationError(`[turbine] connect on "${relName}": no ${rel.to} row found matching ${describeTargetForMessage(target)}.`);
1028
+ throw new ValidationError(`connect on "${relName}": no ${rel.to} row found matching ${describeTargetForMessage(target)}.`);
1029
1029
  }
1030
1030
  }
1031
1031
  }
@@ -1080,7 +1080,7 @@ async function processBelongsToCreate(ctx, rel, ops, parentRow, parentTable, dep
1080
1080
  const target = items[0];
1081
1081
  const existing = await ctx.tx.table(rel.to).findUnique({ where: target });
1082
1082
  if (!existing) {
1083
- throw new ValidationError(`[turbine] connect on "${relName}": no ${rel.to} row found matching ${describeTargetForMessage(target)}.`);
1083
+ throw new ValidationError(`connect on "${relName}": no ${rel.to} row found matching ${describeTargetForMessage(target)}.`);
1084
1084
  }
1085
1085
  const updateData = {};
1086
1086
  const relatedTable = ctx.schema.tables[rel.to];
@@ -1130,7 +1130,7 @@ function assertConnectInScope(ctx, rel, child, parentRow, target) {
1130
1130
  continue;
1131
1131
  if (current === key(parentRow[refField]))
1132
1132
  continue;
1133
- throw new ValidationError(`[turbine] connect refused: ${rel.to} row ${describeTargetForMessage(target)} is already owned by ` +
1133
+ throw new ValidationError(`connect refused: ${rel.to} row ${describeTargetForMessage(target)} is already owned by ` +
1134
1134
  `another "${rel.from}" (its ${fks[i]} is ${current}). \`scopedConnect\` only allows connecting a row ` +
1135
1135
  `that is unowned or already owned by this parent; re-parenting must be an explicit update.`);
1136
1136
  }
@@ -1145,7 +1145,7 @@ async function batchConnect(ctx, rel, items, parentRow) {
1145
1145
  for (const target of items) {
1146
1146
  const existing = await ctx.tx.table(rel.to).findUnique({ where: target });
1147
1147
  if (!existing) {
1148
- throw new ValidationError(`[turbine] connect: no ${rel.to} row found matching ${describeTargetForMessage(target)}.`);
1148
+ throw new ValidationError(`connect: no ${rel.to} row found matching ${describeTargetForMessage(target)}.`);
1149
1149
  }
1150
1150
  assertConnectInScope(ctx, rel, existing, parentRow, target);
1151
1151
  }
@@ -1197,7 +1197,7 @@ async function processDisconnect(ctx, rel, disconnectArg, relName, parentRow) {
1197
1197
  return col?.nullable ?? false;
1198
1198
  });
1199
1199
  if (!nullable) {
1200
- throw new ValidationError(`[turbine] Cannot disconnect "${relName}": foreign key column(s) ${fks.join(', ')} on "${rel.to}" are NOT NULL. Use delete instead.`);
1200
+ throw new ValidationError(`Cannot disconnect "${relName}": foreign key column(s) ${fks.join(', ')} on "${rel.to}" are NOT NULL. Use delete instead.`);
1201
1201
  }
1202
1202
  const items = toArray(disconnectArg);
1203
1203
  if (items.length === 0)
@@ -1240,7 +1240,7 @@ async function processSet(ctx, rel, setItems, parentRow) {
1240
1240
  const refField = ctx.schema.tables[rel.from]?.reverseColumnMap[refs[i]] ?? refs[i];
1241
1241
  const value = parentRow[refField];
1242
1242
  if (value === null || value === undefined) {
1243
- throw new ValidationError(`[turbine] Nested set on relation "${rel.name}" cannot run: the parent's reference key ` +
1243
+ throw new ValidationError(`Nested set on relation "${rel.name}" cannot run: the parent's reference key ` +
1244
1244
  `"${refField}" is ${value === null ? 'null' : 'missing from the loaded row'}, so no child rows ` +
1245
1245
  `can be correlated to this parent.`);
1246
1246
  }
@@ -1278,7 +1278,7 @@ async function processNestedUpdate(ctx, rel, updateArg, relName, parentRow) {
1278
1278
  const correlation = parentCorrelationWhere(ctx, rel, parentRow);
1279
1279
  for (const item of items) {
1280
1280
  if (!item.where || !item.data) {
1281
- throw new ValidationError(`[turbine] Nested update on "${rel.name}" requires both "where" and "data" fields.`);
1281
+ throw new ValidationError(`Nested update on "${rel.name}" requires both "where" and "data" fields.`);
1282
1282
  }
1283
1283
  assertTargetSelectsSomething(item.where, 'update', relName, rel);
1284
1284
  if (!correlation)
@@ -1302,7 +1302,7 @@ async function processNestedUpsert(ctx, rel, upsertArg, parentRow, relName) {
1302
1302
  const correlation = parentCorrelationWhere(ctx, rel, parentRow);
1303
1303
  for (const item of items) {
1304
1304
  if (!item.where || !item.create || !item.update) {
1305
- throw new ValidationError(`[turbine] Nested upsert on "${rel.name}" requires "where", "create", and "update" fields.`);
1305
+ throw new ValidationError(`Nested upsert on "${rel.name}" requires "where", "create", and "update" fields.`);
1306
1306
  }
1307
1307
  assertTargetSelectsSomething(item.where, 'upsert', relName, rel);
1308
1308
  const scoped = correlation ? scopeWhereToParent(item.where, correlation) : null;
@@ -1324,7 +1324,7 @@ async function processNestedUpsert(ctx, rel, upsertArg, parentRow, relName) {
1324
1324
  async function processBelongsToUpdate(ctx, rel, updateArg, parentRow, parentTable) {
1325
1325
  const item = updateArg;
1326
1326
  if (!item.data) {
1327
- throw new ValidationError(`[turbine] Nested update on belongsTo "${rel.name}" requires a "data" field.`);
1327
+ throw new ValidationError(`Nested update on belongsTo "${rel.name}" requires a "data" field.`);
1328
1328
  }
1329
1329
  // The related row is the one this parent's FK points at. Route through the
1330
1330
  // shared correlation helper (like every sibling operation) so a NULL parent
@@ -1346,7 +1346,7 @@ async function processBelongsToUpdate(ctx, rel, updateArg, parentRow, parentTabl
1346
1346
  async function processBelongsToUpsert(ctx, rel, upsertArg, parentRow, parentTable) {
1347
1347
  const item = upsertArg;
1348
1348
  if (!item.where || !item.create || !item.update) {
1349
- throw new ValidationError(`[turbine] Nested upsert on belongsTo "${rel.name}" requires "where", "create", and "update" fields.`);
1349
+ throw new ValidationError(`Nested upsert on belongsTo "${rel.name}" requires "where", "create", and "update" fields.`);
1350
1350
  }
1351
1351
  // Scope the lookup to the row this parent actually points at. Without the
1352
1352
  // correlation, a `where` naming any other row would update a record with no
package/dist/observe.js CHANGED
@@ -154,7 +154,7 @@ export class ObserveEngine {
154
154
  stopped = false;
155
155
  constructor(config) {
156
156
  if (!config.sink && !config.connectionString) {
157
- throw new ValidationError('[turbine] ObserveEngine: neither `connectionString` nor `sink` was provided, so there is nowhere to ' +
157
+ throw new ValidationError('ObserveEngine: neither `connectionString` nor `sink` was provided, so there is nowhere to ' +
158
158
  'flush metrics. Pass `connectionString` (a separate metrics database URL, often TURBINE_OBSERVE_URL) ' +
159
159
  'or a custom `sink` implementing ObserveSink.');
160
160
  }
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import Result from 'pg/lib/result';
20
20
  import { prepareValue } from 'pg/lib/utils';
21
- import { PipelineError, wrapPgError } from './errors.js';
21
+ import { PipelineError, TimeoutError, wrapPgError } from './errors.js';
22
22
  // ---------------------------------------------------------------------------
23
23
  // Event names we intercept
24
24
  // ---------------------------------------------------------------------------
@@ -311,7 +311,11 @@ export async function runPipelined(client, queries, options = {}) {
311
311
  // -----------------------------------------------------------------------
312
312
  if (timeout && timeout > 0) {
313
313
  timeoutHandle = setTimeout(() => {
314
- pipelineError = new Error(`[turbine] Pipeline timed out after ${timeout}ms`);
314
+ // TimeoutError (E002), matching $transaction({ timeout }). A bare Error
315
+ // here meant `pipeline({ timeout })` rejected with no .code, no
316
+ // .docsUrl, and failing `instanceof TurbineError`, so the two timeout
317
+ // paths in the same client disagreed about their own contract.
318
+ pipelineError = new TimeoutError(timeout, 'Pipeline');
315
319
  if (connection.stream.destroy) {
316
320
  connection.stream.destroy(pipelineError);
317
321
  }
package/dist/pipeline.js CHANGED
@@ -19,7 +19,7 @@
19
19
  * Hyperdrive), mock pools in tests, and any pool that doesn't expose pg internals.
20
20
  */
21
21
  import { postgresDialect } from './dialect.js';
22
- import { PipelineError, wrapPgError } from './errors.js';
22
+ import { PipelineError, TurbineError, wrapPgError } from './errors.js';
23
23
  import { runPipelined, supportsExtendedPipeline } from './pipeline-submittable.js';
24
24
  /**
25
25
  * Execute queries sequentially on an already-acquired connection.
@@ -218,6 +218,14 @@ export async function executePipeline(pool, queries, options) {
218
218
  // keywords rather than Postgres's.
219
219
  return await runSequential(client, queries, poolDialect(pool), options);
220
220
  }
221
+ catch (err) {
222
+ // Already-typed Turbine errors pass through untouched; anything else is a
223
+ // driver error that must not escape with a SQLSTATE sitting in the same
224
+ // `.code` slot Turbine puts TURBINE_E0NN in.
225
+ if (err instanceof TurbineError)
226
+ throw err;
227
+ throw wrapPgError(err);
228
+ }
221
229
  finally {
222
230
  client.release();
223
231
  }
@@ -62,7 +62,7 @@
62
62
  * wired to a networked `exec`, and `cli/config.ts` teaching the generate
63
63
  * funnel to construct a PowDB client instead of a `pg` client for `powdb://`.
64
64
  */
65
- import { type PowdbCapabilities } from './powdb.js';
65
+ import { type PowdbCapabilities } from './powdb-shared.js';
66
66
  import type { SchemaMetadata } from './schema.js';
67
67
  /** A minimal rows-returning executor over a PowDB connection (embedded or networked). */
68
68
  export type PowdbExec = (powql: string) => Promise<{
@@ -64,7 +64,7 @@
64
64
  */
65
65
  import { ValidationError } from './errors.js';
66
66
  import { applyTableFilters } from './introspect.js';
67
- import { quotePowqlIdent, requireCapability } from './powdb.js';
67
+ import { quotePowqlIdent, requireCapability } from './powdb-shared.js';
68
68
  import { singularize, snakeToCamel } from './schema.js';
69
69
  /** Coerce a wire cell to string (legacy wire cells are strings; native cells may be typed). */
70
70
  function asString(v) {
@@ -123,7 +123,7 @@ export async function introspectPowdbDatabase(exec, options = {}) {
123
123
  // every table filters out and the schema comes back silently empty. Refuse
124
124
  // that instead of losing data: real rows must carry a `name`.
125
125
  if (schemaRows.length > 0 && candidateTables.length === 0) {
126
- throw new ValidationError(`[turbine] PowDB introspection: the \`schema\` statement returned ${schemaRows.length} row(s) but none carried a ` +
126
+ throw new ValidationError(`PowDB introspection: the \`schema\` statement returned ${schemaRows.length} row(s) but none carried a ` +
127
127
  '`name` cell. The `exec` you supplied likely returns POSITIONAL rows (string[][]) rather than records keyed by ' +
128
128
  'column name; zip `columns` with each row (see introspectPowdbDatabase docs).');
129
129
  }