turbine-orm 0.76.0 → 0.77.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/index.js +2 -2
- package/dist/cjs/cli/migrate.js +8 -8
- package/dist/cjs/cli/studio.js +6 -3
- package/dist/cjs/client.js +10 -10
- package/dist/cjs/dialect.js +1 -1
- package/dist/cjs/errors.d.ts +2 -2
- package/dist/cjs/errors.js +21 -25
- package/dist/cjs/generate.js +1 -1
- package/dist/cjs/introspect.js +5 -5
- package/dist/cjs/mssql.js +12 -12
- package/dist/cjs/mysql.js +5 -5
- package/dist/cjs/nested-write.js +31 -31
- package/dist/cjs/observe.js +1 -1
- package/dist/cjs/pipeline-submittable.js +5 -1
- package/dist/cjs/pipeline.js +8 -0
- package/dist/cjs/powdb-introspect.d.ts +1 -1
- package/dist/cjs/powdb-introspect.js +4 -4
- package/dist/cjs/powdb-shared.d.ts +348 -0
- package/dist/cjs/powdb-shared.js +587 -0
- package/dist/cjs/powdb.d.ts +12 -299
- package/dist/cjs/powdb.js +106 -567
- package/dist/cjs/powql.d.ts +1 -1
- package/dist/cjs/powql.js +89 -89
- package/dist/cjs/prisma-compat.js +16 -16
- package/dist/cjs/query/aggregates.js +19 -20
- package/dist/cjs/query/batched-loader.js +8 -8
- package/dist/cjs/query/builder.js +8 -8
- package/dist/cjs/query/compound-unique.js +2 -2
- package/dist/cjs/query/filters.js +1 -1
- package/dist/cjs/query/relations.js +60 -48
- package/dist/cjs/query/types.js +4 -4
- package/dist/cjs/query/utils.js +5 -5
- package/dist/cjs/query/warn-registry.d.ts +7 -0
- package/dist/cjs/query/warn-registry.js +7 -0
- package/dist/cjs/query/where-compile.js +1 -1
- package/dist/cjs/query/where.js +30 -31
- package/dist/cjs/query/writes.js +7 -7
- package/dist/cjs/realtime.js +4 -4
- package/dist/cjs/schema-metadata.js +1 -1
- package/dist/cjs/schema-sql.js +3 -3
- package/dist/cjs/seed.js +1 -1
- package/dist/cjs/sqlite.js +7 -7
- package/dist/cjs/typed-sql.js +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/migrate.js +8 -8
- package/dist/cli/studio.js +6 -3
- package/dist/client.js +10 -10
- package/dist/dialect.js +1 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +21 -25
- package/dist/generate.js +1 -1
- package/dist/introspect.js +5 -5
- package/dist/mssql.js +12 -12
- package/dist/mysql.js +5 -5
- package/dist/nested-write.js +31 -31
- package/dist/observe.js +1 -1
- package/dist/pipeline-submittable.js +6 -2
- package/dist/pipeline.js +9 -1
- package/dist/powdb-introspect.d.ts +1 -1
- package/dist/powdb-introspect.js +2 -2
- package/dist/powdb-shared.d.ts +348 -0
- package/dist/powdb-shared.js +570 -0
- package/dist/powdb.d.ts +12 -299
- package/dist/powdb.js +71 -534
- package/dist/powql.d.ts +1 -1
- package/dist/powql.js +43 -43
- package/dist/prisma-compat.js +16 -16
- package/dist/query/aggregates.js +19 -20
- package/dist/query/batched-loader.js +8 -8
- package/dist/query/builder.js +8 -8
- package/dist/query/compound-unique.js +2 -2
- package/dist/query/filters.js +1 -1
- package/dist/query/relations.js +60 -48
- package/dist/query/types.js +4 -4
- package/dist/query/utils.js +5 -5
- package/dist/query/warn-registry.d.ts +7 -0
- package/dist/query/warn-registry.js +7 -0
- package/dist/query/where-compile.js +1 -1
- package/dist/query/where.js +30 -31
- package/dist/query/writes.js +7 -7
- package/dist/realtime.js +4 -4
- package/dist/schema-metadata.js +1 -1
- package/dist/schema-sql.js +3 -3
- package/dist/seed.js +1 -1
- package/dist/sqlite.js +7 -7
- package/dist/typed-sql.js +1 -1
- package/package.json +15 -7
package/dist/nested-write.js
CHANGED
|
@@ -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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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: `
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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(`
|
|
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('
|
|
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
|
-
|
|
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<{
|
package/dist/powdb-introspect.js
CHANGED
|
@@ -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(`
|
|
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
|
}
|