@vibeorm/runtime 2.0.0 → 2.0.2
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/README.md +2 -0
- package/dist/adapter-kit/index.d.ts +20 -0
- package/dist/adapter-kit/index.d.ts.map +1 -0
- package/dist/adapter-kit/index.js +247 -0
- package/dist/adapter-kit/index.js.map +12 -0
- package/dist/adapter-kit/savepoint-gate.d.ts +86 -0
- package/dist/adapter-kit/savepoint-gate.d.ts.map +1 -0
- package/dist/adapter-kit/savepoints.d.ts +22 -0
- package/dist/adapter-kit/savepoints.d.ts.map +1 -0
- package/dist/adapter-kit/transaction-budget.d.ts +143 -0
- package/dist/adapter-kit/transaction-budget.d.ts.map +1 -0
- package/dist/codecs.d.ts +12 -0
- package/dist/codecs.d.ts.map +1 -1
- package/dist/index.js +528 -334
- package/dist/index.js.map +8 -7
- package/dist/lateral-projection.d.ts +25 -0
- package/dist/lateral-projection.d.ts.map +1 -0
- package/dist/query-builder.d.ts +6 -27
- package/dist/query-builder.d.ts.map +1 -1
- package/dist/relation-loader.d.ts +2 -2
- package/dist/relation-loader.d.ts.map +1 -1
- package/dist/render-cache.d.ts +16 -1
- package/dist/render-cache.d.ts.map +1 -1
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -304,7 +304,7 @@ function getFieldMeta(params) {
|
|
|
304
304
|
return found;
|
|
305
305
|
}
|
|
306
306
|
// packages/runtime/src/codecs.ts
|
|
307
|
-
import { parsePgArrayLiteral, toPgArrayLiteral } from "@vibeorm/sql";
|
|
307
|
+
import { JSON_TRANSPORT, projectionOutputName, parsePgArrayLiteral, toPgArrayLiteral } from "@vibeorm/sql";
|
|
308
308
|
import { VibeError as VibeError3, isEnumType as isEnumType2 } from "@vibeorm/schema";
|
|
309
309
|
import { DIALECT_CODECS, decodeIsNoop } from "@vibeorm/sql";
|
|
310
310
|
import {
|
|
@@ -445,16 +445,16 @@ function buildDecodePlan(params) {
|
|
|
445
445
|
const { dialect, model, wire } = params;
|
|
446
446
|
const byColumn = new Map;
|
|
447
447
|
const inPlace = [];
|
|
448
|
-
|
|
448
|
+
const renamedColumns = [];
|
|
449
449
|
for (const field of model.fields) {
|
|
450
450
|
const decode = fieldDecoder({ dialect, field, ...wire === undefined ? {} : { wire } });
|
|
451
451
|
byColumn.set(field.columnName, { name: field.name, decode });
|
|
452
452
|
if (field.columnName !== field.name)
|
|
453
|
-
|
|
453
|
+
renamedColumns.push(field.columnName);
|
|
454
454
|
if (decode !== null)
|
|
455
455
|
inPlace.push({ column: field.columnName, decode });
|
|
456
456
|
}
|
|
457
|
-
return { byColumn, inPlace:
|
|
457
|
+
return { byColumn, renamedColumns, decoders: inPlace, inPlace: renamedColumns.length > 0 ? null : inPlace };
|
|
458
458
|
}
|
|
459
459
|
function viewWire(wire) {
|
|
460
460
|
return wire?.json === "parsed" ? { json: "parsed" } : undefined;
|
|
@@ -488,6 +488,58 @@ function decodeRowWithPlan(plan, row) {
|
|
|
488
488
|
}
|
|
489
489
|
return out;
|
|
490
490
|
}
|
|
491
|
+
var projectionBindings = new WeakMap;
|
|
492
|
+
function materializeProjection(params) {
|
|
493
|
+
const { plan, rows } = params;
|
|
494
|
+
let binding = projectionBindings.get(plan);
|
|
495
|
+
for (let index = 0;index < rows.length; index++) {
|
|
496
|
+
const row = rows[index];
|
|
497
|
+
if (plan.renamedColumns.length > 0 && !plan.renamedColumns.some((column) => Object.hasOwn(row, column))) {
|
|
498
|
+
for (const entry of plan.decoders) {
|
|
499
|
+
const value = row[entry.column];
|
|
500
|
+
if (value !== null && value !== undefined)
|
|
501
|
+
row[entry.column] = entry.decode(value);
|
|
502
|
+
}
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
const keys = Object.keys(row);
|
|
506
|
+
let matches = binding !== undefined && keys.length === binding.keys.length;
|
|
507
|
+
if (matches)
|
|
508
|
+
for (let i = 0;i < keys.length; i++) {
|
|
509
|
+
if (keys[i] !== binding?.keys[i]) {
|
|
510
|
+
matches = false;
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (binding === undefined || !matches) {
|
|
515
|
+
const slots = keys.map((column) => {
|
|
516
|
+
const entry = plan.byColumn.get(column);
|
|
517
|
+
return { column, name: entry?.name ?? column, decode: entry?.decode ?? null };
|
|
518
|
+
});
|
|
519
|
+
const decoders = [];
|
|
520
|
+
for (const slot of slots)
|
|
521
|
+
if (slot.decode !== null)
|
|
522
|
+
decoders.push({ column: slot.column, decode: slot.decode });
|
|
523
|
+
binding = { keys, slots, decoders, renamed: slots.some((slot) => slot.column !== slot.name) };
|
|
524
|
+
projectionBindings.set(plan, binding);
|
|
525
|
+
}
|
|
526
|
+
if (binding.renamed) {
|
|
527
|
+
const out = {};
|
|
528
|
+
for (const slot of binding.slots) {
|
|
529
|
+
const value = row[slot.column];
|
|
530
|
+
out[slot.name] = value === null || value === undefined || slot.decode === null ? value : slot.decode(value);
|
|
531
|
+
}
|
|
532
|
+
rows[index] = out;
|
|
533
|
+
} else {
|
|
534
|
+
for (const slot of binding.decoders) {
|
|
535
|
+
const value = row[slot.column];
|
|
536
|
+
if (value !== null && value !== undefined)
|
|
537
|
+
row[slot.column] = slot.decode(value);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return rows;
|
|
542
|
+
}
|
|
491
543
|
function decodeRow(params) {
|
|
492
544
|
const { dialect, model, row, wire } = params;
|
|
493
545
|
return decodeRowWithPlan(getDecodePlan({ dialect, model, wire }), row);
|
|
@@ -500,11 +552,10 @@ function decodeRows(params) {
|
|
|
500
552
|
function materializeRows(params) {
|
|
501
553
|
const { dialect, model, rows, wire } = params;
|
|
502
554
|
const plan = getDecodePlan({ dialect, model, wire });
|
|
503
|
-
if (plan.inPlace ===
|
|
504
|
-
for (let index = 0;index < rows.length; index++) {
|
|
505
|
-
rows[index] = decodeRowWithPlan(plan, rows[index]);
|
|
506
|
-
}
|
|
555
|
+
if (rows.length === 0 || plan.inPlace?.length === 0)
|
|
507
556
|
return rows;
|
|
557
|
+
if (plan.inPlace === null || Object.keys(rows[0]).length < plan.byColumn.size) {
|
|
558
|
+
return materializeProjection({ plan, rows });
|
|
508
559
|
}
|
|
509
560
|
const decoders = plan.inPlace;
|
|
510
561
|
if (decoders.length === 0)
|
|
@@ -526,39 +577,58 @@ function getJsonDecodePlan(model) {
|
|
|
526
577
|
return plan;
|
|
527
578
|
const byColumn = new Map;
|
|
528
579
|
const inPlace = [];
|
|
529
|
-
|
|
580
|
+
const renamedColumns = [];
|
|
530
581
|
for (const field of model.fields) {
|
|
531
582
|
const decode = field.scalarType === "Json" ? null : field.scalarType === "Bytes" && !field.isList ? decodeBase64Bytes : fieldDecoder({ dialect: "postgres", field });
|
|
532
583
|
byColumn.set(field.columnName, { name: field.name, decode });
|
|
533
584
|
if (field.columnName !== field.name)
|
|
534
|
-
|
|
585
|
+
renamedColumns.push(field.columnName);
|
|
535
586
|
if (decode !== null)
|
|
536
587
|
inPlace.push({ column: field.columnName, decode });
|
|
537
588
|
}
|
|
538
|
-
plan = { byColumn, inPlace:
|
|
589
|
+
plan = { byColumn, renamedColumns, decoders: inPlace, inPlace: renamedColumns.length > 0 ? null : inPlace };
|
|
539
590
|
jsonDecodePlanCache.set(model, plan);
|
|
540
591
|
return plan;
|
|
541
592
|
}
|
|
542
|
-
function
|
|
543
|
-
const { model,
|
|
593
|
+
function positionalMaterializer(params) {
|
|
594
|
+
const { model, columns } = params;
|
|
544
595
|
const plan = getJsonDecodePlan(model);
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
596
|
+
const slots = columns.map((column) => {
|
|
597
|
+
const output = projectionOutputName({ column });
|
|
598
|
+
const entry = plan.byColumn.get(output);
|
|
599
|
+
if (entry === undefined)
|
|
600
|
+
throw new VibeError3({ code: "VIBE_INTERNAL", message: "positional projection has no schema field", meta: { model: model.name, column: output } });
|
|
601
|
+
return entry;
|
|
602
|
+
});
|
|
603
|
+
const width = slots.length;
|
|
604
|
+
const chunkSize = JSON_TRANSPORT.chunkSize;
|
|
605
|
+
const chunked = width > chunkSize;
|
|
606
|
+
const count = Math.ceil(width / chunkSize);
|
|
607
|
+
const invalid = () => {
|
|
608
|
+
throw new VibeError3({ code: "VIBE_INTERNAL", message: "positional relation payload does not match its SQL projection", meta: { model: model.name, expectedColumns: width } });
|
|
609
|
+
};
|
|
610
|
+
if (width === 0 || new Set(slots.map((slot) => slot.name)).size !== width)
|
|
611
|
+
return invalid();
|
|
612
|
+
return (value) => {
|
|
613
|
+
if (!Array.isArray(value) || value.length !== (chunked ? count : width))
|
|
614
|
+
return invalid();
|
|
615
|
+
if (chunked) {
|
|
616
|
+
for (let index = 0;index < count; index++) {
|
|
617
|
+
const chunk = value[index];
|
|
618
|
+
if (!Array.isArray(chunk) || chunk.length !== Math.min(chunkSize, width - index * chunkSize))
|
|
619
|
+
return invalid();
|
|
620
|
+
}
|
|
548
621
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const value = row[entry.column];
|
|
557
|
-
if (value !== null && value !== undefined)
|
|
558
|
-
row[entry.column] = entry.decode(value);
|
|
622
|
+
const out = {};
|
|
623
|
+
for (let index = 0;index < width; index++) {
|
|
624
|
+
const slot = slots[index];
|
|
625
|
+
const cell = chunked ? value[Math.floor(index / chunkSize)][index % chunkSize] : value[index];
|
|
626
|
+
if (cell === undefined)
|
|
627
|
+
return invalid();
|
|
628
|
+
out[slot.name] = cell === null || slot.decode === null ? cell : slot.decode(cell);
|
|
559
629
|
}
|
|
560
|
-
|
|
561
|
-
|
|
630
|
+
return out;
|
|
631
|
+
};
|
|
562
632
|
}
|
|
563
633
|
function decodeAggregateValue(params) {
|
|
564
634
|
const { dialect, key, field, value } = params;
|
|
@@ -576,8 +646,73 @@ function decodeAggregateValue(params) {
|
|
|
576
646
|
return String(value);
|
|
577
647
|
return field === null ? value : decodeValue({ dialect, field, value });
|
|
578
648
|
}
|
|
649
|
+
// packages/runtime/src/lateral-projection.ts
|
|
650
|
+
import { VibeError as VibeError4, unsupportedCapability } from "@vibeorm/schema";
|
|
651
|
+
function isPlainObject(value) {
|
|
652
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
653
|
+
}
|
|
654
|
+
var LATERAL_CHILD_ALIAS = "__rel";
|
|
655
|
+
var projections = new WeakMap;
|
|
656
|
+
function lateralProjection(params) {
|
|
657
|
+
const { node } = params;
|
|
658
|
+
const cached = projections.get(node);
|
|
659
|
+
if (cached !== undefined)
|
|
660
|
+
return cached;
|
|
661
|
+
const model = node.link.target;
|
|
662
|
+
const hiddenOrder = lateralHiddenOrderFields({ node });
|
|
663
|
+
const names = node.args.projected === null ? model.fields.map((field) => field.name) : [...node.args.projected, ...hiddenOrder];
|
|
664
|
+
const columns = names.map((name) => {
|
|
665
|
+
const field = model.fieldByName.get(name);
|
|
666
|
+
if (field === undefined)
|
|
667
|
+
throw new VibeError4({ code: "VIBE_INTERNAL", message: "lateral projection references an unknown field", meta: { model: model.name, field: name } });
|
|
668
|
+
return Object.freeze(applyLateralTransform({ model, column: { table: LATERAL_CHILD_ALIAS, name: field.columnName } }));
|
|
669
|
+
});
|
|
670
|
+
const descriptor = Object.freeze({ columns: Object.freeze(columns), hiddenOrder: Object.freeze(hiddenOrder) });
|
|
671
|
+
projections.set(node, descriptor);
|
|
672
|
+
return descriptor;
|
|
673
|
+
}
|
|
674
|
+
function lateralHiddenOrderFields(params) {
|
|
675
|
+
const { node } = params;
|
|
676
|
+
const projected = node.args.projected;
|
|
677
|
+
if (projected === null || node.args.orderBy === undefined)
|
|
678
|
+
return [];
|
|
679
|
+
const target = node.link.target;
|
|
680
|
+
const hidden = [];
|
|
681
|
+
const items = Array.isArray(node.args.orderBy) ? node.args.orderBy : [node.args.orderBy];
|
|
682
|
+
for (const item of items) {
|
|
683
|
+
if (!isPlainObject(item))
|
|
684
|
+
continue;
|
|
685
|
+
for (const [name, value] of Object.entries(item)) {
|
|
686
|
+
if (value === undefined)
|
|
687
|
+
continue;
|
|
688
|
+
if (!target.fieldByName.has(name))
|
|
689
|
+
continue;
|
|
690
|
+
if (!projected.includes(name) && !hidden.includes(name))
|
|
691
|
+
hidden.push(name);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return hidden;
|
|
695
|
+
}
|
|
696
|
+
function applyLateralTransform(params) {
|
|
697
|
+
const { model, column } = params;
|
|
698
|
+
const field = model.fieldByColumn.get(column.name);
|
|
699
|
+
if (field === undefined)
|
|
700
|
+
return column;
|
|
701
|
+
const scalar = field.scalarType;
|
|
702
|
+
if (scalar !== "BigInt" && scalar !== "Decimal" && scalar !== "Bytes")
|
|
703
|
+
return column;
|
|
704
|
+
if (field.isList) {
|
|
705
|
+
throw unsupportedCapability({
|
|
706
|
+
dialect: "postgres",
|
|
707
|
+
feature: `join-strategy relation loading of ${model.name}.${field.name} (${scalar}[])`,
|
|
708
|
+
hint: 'list columns of BigInt/Decimal/Bytes have no faithful JSON wire form \u2014 use the portable "query" relation strategy'
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
return { ...column, transform: scalar === "Bytes" ? "encodeBase64" : "castText" };
|
|
712
|
+
}
|
|
713
|
+
|
|
579
714
|
// packages/runtime/src/query-builder.ts
|
|
580
|
-
import { VibeError as
|
|
715
|
+
import { VibeError as VibeError8, unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
|
|
581
716
|
import {
|
|
582
717
|
SQL_DEFAULT_CELL,
|
|
583
718
|
containsPattern,
|
|
@@ -590,9 +725,9 @@ import {
|
|
|
590
725
|
} from "@vibeorm/sql";
|
|
591
726
|
|
|
592
727
|
// packages/runtime/src/bulk-upsert.ts
|
|
593
|
-
import { VibeError as
|
|
728
|
+
import { VibeError as VibeError5, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
594
729
|
function validationError(params) {
|
|
595
|
-
return new
|
|
730
|
+
return new VibeError5({
|
|
596
731
|
code: "VIBE_VALIDATION",
|
|
597
732
|
message: params.message,
|
|
598
733
|
...params.meta === undefined ? {} : { meta: params.meta }
|
|
@@ -680,7 +815,7 @@ function assertConflictTargetIsEnforceable(params) {
|
|
|
680
815
|
const groups = uniqueGroupSignatures({ model });
|
|
681
816
|
if (groups.size <= 1)
|
|
682
817
|
return;
|
|
683
|
-
throw
|
|
818
|
+
throw unsupportedCapability2({
|
|
684
819
|
dialect: dialect.name,
|
|
685
820
|
feature: `an enforced conflict target on ${model.clientName}.upsertMany() (${model.name} declares ${groups.size} unique groups)`,
|
|
686
821
|
hint: "mysql's ON DUPLICATE KEY UPDATE fires on ANY unique index, so another one can decide the conflict \u2014 pass allowAnyUniqueConflict: true to accept that, or gate the call per dialect"
|
|
@@ -803,7 +938,7 @@ function assertNoDuplicateSecondaryUniques(params) {
|
|
|
803
938
|
const key = conflictKeyOf({ row, columns });
|
|
804
939
|
const first = firstIndexByKey.get(key);
|
|
805
940
|
if (first !== undefined) {
|
|
806
|
-
throw
|
|
941
|
+
throw unsupportedCapability2({
|
|
807
942
|
dialect: dialect.name,
|
|
808
943
|
feature: `${model.clientName}.upsertMany() with rows ${first} and ${index} carrying the same ${fields.map((field) => field.name).join(" + ")}`,
|
|
809
944
|
hint: `ON DUPLICATE KEY UPDATE has no conflict target: it resolves through whichever unique index an incoming row matches, so row ${index} would UPDATE row ${first} through unique (${columns.join(", ")}) and one of the two rows you submitted would silently not exist. Deduplicate the feed on those columns as well as on the conflict target, or submit the colliding rows in separate calls. allowAnyUniqueConflict widens which STORED row a single incoming row may replace; it does not authorise two submitted rows collapsing into one`
|
|
@@ -885,7 +1020,7 @@ function assertConflictKeysAreComparable(params) {
|
|
|
885
1020
|
if (target.kind === "constraint") {
|
|
886
1021
|
if (engineComparesEveryPair)
|
|
887
1022
|
return;
|
|
888
|
-
throw
|
|
1023
|
+
throw unsupportedCapability2({
|
|
889
1024
|
dialect: dialect.name,
|
|
890
1025
|
feature: `a ${statementCount > 1 ? "chunked " : ""}${model.clientName}.upsertMany() batch arbitrated by constraint "${target.constraint}"`,
|
|
891
1026
|
hint: statementCount > 1 ? `a named constraint hides which columns decide the conflict, so only the engine can tell whether two incoming rows address the same stored row \u2014 and it only compares rows inside ONE statement, while this batch of ${model.name} rows needs ${statementCount}. Submit fewer rows per call, or name the unique group's fields instead of the constraint` : "a named constraint hides which columns decide the conflict, so this dialect would silently keep the last of two rows that address the same stored row"
|
|
@@ -903,7 +1038,7 @@ function assertConflictKeysAreComparable(params) {
|
|
|
903
1038
|
notes.push(`@collation("${field.collation}")`);
|
|
904
1039
|
return notes.length === 0 ? `${field.name} (column "${field.columnName}")` : `${field.name} (column "${field.columnName}", ${notes.join(", ")})`;
|
|
905
1040
|
}).join(", ");
|
|
906
|
-
throw
|
|
1041
|
+
throw unsupportedCapability2({
|
|
907
1042
|
dialect: dialect.name,
|
|
908
1043
|
feature: `${model.clientName}.upsertMany() on conflict target ${described}`,
|
|
909
1044
|
hint: statementCount > 1 ? `only the database can tell whether two of these keys address the same stored row \u2014 its column type, collation, scale or precision decides \u2014 and it only compares rows inside ONE statement, while this batch of ${model.name} rows needs ${statementCount}. Submit fewer rows per call, or ${remedyFor({ dialect, fields: opaque })}` : `only the database can tell whether two of these keys address the same stored row \u2014 its column type, collation, scale or precision decides \u2014 and this dialect answers a doubled key by silently keeping the last row instead of refusing it. Deduplicate the feed against the same rule the column uses, or ${remedyFor({ dialect, fields: opaque })}`
|
|
@@ -911,7 +1046,7 @@ function assertConflictKeysAreComparable(params) {
|
|
|
911
1046
|
}
|
|
912
1047
|
|
|
913
1048
|
// packages/runtime/src/keyset.ts
|
|
914
|
-
import { VibeError as
|
|
1049
|
+
import { VibeError as VibeError6 } from "@vibeorm/schema";
|
|
915
1050
|
var KEYSET_TOKEN_VERSION = 1;
|
|
916
1051
|
var KEYSET_TOKEN_PREFIX = "vk1.";
|
|
917
1052
|
var KEYSET_MAX_PAGE_SIZE = 1e4;
|
|
@@ -921,10 +1056,10 @@ var DATETIME_NEEDS_PRECISION_ASSERTION = Object.freeze({
|
|
|
921
1056
|
sqlite: false
|
|
922
1057
|
});
|
|
923
1058
|
function keysetValidation(params) {
|
|
924
|
-
return new
|
|
1059
|
+
return new VibeError6({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
925
1060
|
}
|
|
926
1061
|
function keysetUnsupported(params) {
|
|
927
|
-
return new
|
|
1062
|
+
return new VibeError6({
|
|
928
1063
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
929
1064
|
message: params.message,
|
|
930
1065
|
meta: params.meta
|
|
@@ -1307,11 +1442,11 @@ function decodeKeysetToken(params) {
|
|
|
1307
1442
|
}
|
|
1308
1443
|
|
|
1309
1444
|
// packages/runtime/src/relation-plan.ts
|
|
1310
|
-
import { VibeError as
|
|
1445
|
+
import { VibeError as VibeError7, implicitJoinTable } from "@vibeorm/schema";
|
|
1311
1446
|
function validationError2(params) {
|
|
1312
|
-
return new
|
|
1447
|
+
return new VibeError7({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
1313
1448
|
}
|
|
1314
|
-
function
|
|
1449
|
+
function isPlainObject2(value) {
|
|
1315
1450
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
1316
1451
|
}
|
|
1317
1452
|
function relationKeyOf(relation) {
|
|
@@ -1334,7 +1469,7 @@ function keyFields(params) {
|
|
|
1334
1469
|
return names.map((name) => {
|
|
1335
1470
|
const field = model.fieldByName.get(name);
|
|
1336
1471
|
if (field === undefined) {
|
|
1337
|
-
throw new
|
|
1472
|
+
throw new VibeError7({
|
|
1338
1473
|
code: "VIBE_SCHEMA_INVALID",
|
|
1339
1474
|
message: `relation "${relation.name}" on ${relation.model} names unknown ${what} field "${name}"`,
|
|
1340
1475
|
meta: { model: relation.model, relation: relation.name }
|
|
@@ -1346,7 +1481,7 @@ function keyFields(params) {
|
|
|
1346
1481
|
function singleKeyPair(params) {
|
|
1347
1482
|
const { link, what } = params;
|
|
1348
1483
|
if (link.parentKeys.length !== 1 || link.childKeys.length !== 1) {
|
|
1349
|
-
throw new
|
|
1484
|
+
throw new VibeError7({
|
|
1350
1485
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
1351
1486
|
message: `${what} on relation "${link.relation.name}" of ${link.relation.model} needs a single-column relation key \u2014 this relation links ${link.parentKeys.length} columns; use an explicit junction model`,
|
|
1352
1487
|
meta: { model: link.relation.model, relation: link.relation.name, arity: link.parentKeys.length }
|
|
@@ -1385,7 +1520,7 @@ function resolveRelationLinkUncached(params) {
|
|
|
1385
1520
|
const target = getModelMeta({ meta, model: relation.targetModel });
|
|
1386
1521
|
if (relation.type === "manyToMany") {
|
|
1387
1522
|
if (relation.model === relation.targetModel) {
|
|
1388
|
-
throw new
|
|
1523
|
+
throw new VibeError7({
|
|
1389
1524
|
code: "VIBE_SCHEMA_INVALID",
|
|
1390
1525
|
message: `relation "${relation.name}" on ${relation.model} is an implicit self many-to-many: both sides own the ` + `same join-table column, so this field and its inverse would read the same rows. Model each direction ` + `with an explicit junction model.`,
|
|
1391
1526
|
meta: { model: relation.model, relation: relation.name, targetModel: relation.targetModel }
|
|
@@ -1427,7 +1562,7 @@ function resolveRelationLinkUncached(params) {
|
|
|
1427
1562
|
}
|
|
1428
1563
|
const reverse = findReverseRelation({ relation, target });
|
|
1429
1564
|
if (reverse === null || reverse.fields === undefined || reverse.fields.length === 0) {
|
|
1430
|
-
throw new
|
|
1565
|
+
throw new VibeError7({
|
|
1431
1566
|
code: "VIBE_SCHEMA_INVALID",
|
|
1432
1567
|
message: `relation "${relation.name}" on ${relation.model} has no foreign-key counterpart on ${relation.targetModel}`,
|
|
1433
1568
|
meta: { model: relation.model, relation: relation.name, target: relation.targetModel }
|
|
@@ -1452,7 +1587,7 @@ function pairedKeys(params) {
|
|
|
1452
1587
|
const parentKeys = keyFields({ model, names: parentNames, relation, what: "fields" });
|
|
1453
1588
|
const childKeys = keyFields({ model: target, names: childNames, relation, what: "references" });
|
|
1454
1589
|
if (parentKeys.length !== childKeys.length) {
|
|
1455
|
-
throw new
|
|
1590
|
+
throw new VibeError7({
|
|
1456
1591
|
code: "VIBE_SCHEMA_INVALID",
|
|
1457
1592
|
message: `relation "${relation.name}" on ${relation.model} links ${parentKeys.length} field(s) to ${childKeys.length} reference(s) \u2014 a foreign key must pair every column`,
|
|
1458
1593
|
meta: { model: relation.model, relation: relation.name }
|
|
@@ -1508,7 +1643,7 @@ function compileChildSelect(params) {
|
|
|
1508
1643
|
}
|
|
1509
1644
|
function compileChildOmit(params) {
|
|
1510
1645
|
const { model, omit, relation } = params;
|
|
1511
|
-
if (!
|
|
1646
|
+
if (!isPlainObject2(omit)) {
|
|
1512
1647
|
throw validationError2({
|
|
1513
1648
|
message: `omit on relation "${relation}" must be an object`,
|
|
1514
1649
|
meta: { model: model.name, relation }
|
|
@@ -1586,7 +1721,7 @@ function resolveCounts(params) {
|
|
|
1586
1721
|
if (spec === true) {
|
|
1587
1722
|
return model.relations.filter((relation) => relation.isList).map((relation) => resolveCountNode({ meta, model, name: relation.name, where: undefined }));
|
|
1588
1723
|
}
|
|
1589
|
-
if (!
|
|
1724
|
+
if (!isPlainObject2(spec) || !isPlainObject2(spec.select)) {
|
|
1590
1725
|
throw validationError2({
|
|
1591
1726
|
message: `_count on model "${model.name}" must be true or { select: { relation: true } }`,
|
|
1592
1727
|
meta: { model: model.name }
|
|
@@ -1600,7 +1735,7 @@ function resolveCounts(params) {
|
|
|
1600
1735
|
nodes.push(resolveCountNode({ meta, model, name, where: undefined }));
|
|
1601
1736
|
continue;
|
|
1602
1737
|
}
|
|
1603
|
-
if (!
|
|
1738
|
+
if (!isPlainObject2(value)) {
|
|
1604
1739
|
throw validationError2({
|
|
1605
1740
|
message: `_count.select.${name} on model "${model.name}" must be true, false, or { where: \u2026 }`,
|
|
1606
1741
|
meta: { model: model.name, relation: name }
|
|
@@ -1617,7 +1752,7 @@ function resolveCounts(params) {
|
|
|
1617
1752
|
}
|
|
1618
1753
|
}
|
|
1619
1754
|
const where = value.where;
|
|
1620
|
-
if (where !== undefined && !
|
|
1755
|
+
if (where !== undefined && !isPlainObject2(where)) {
|
|
1621
1756
|
throw validationError2({
|
|
1622
1757
|
message: `_count.select.${name}.where on model "${model.name}" must be an object`,
|
|
1623
1758
|
meta: { model: model.name, relation: name }
|
|
@@ -1657,7 +1792,7 @@ function resolveNode(params) {
|
|
|
1657
1792
|
});
|
|
1658
1793
|
}
|
|
1659
1794
|
const where = raw.where === undefined ? undefined : (() => {
|
|
1660
|
-
if (!
|
|
1795
|
+
if (!isPlainObject2(raw.where)) {
|
|
1661
1796
|
throw validationError2({
|
|
1662
1797
|
message: `where on relation "${relation.name}" must be an object`,
|
|
1663
1798
|
meta: { model: model.name, relation: relation.name }
|
|
@@ -1720,8 +1855,8 @@ function resolveNode(params) {
|
|
|
1720
1855
|
}
|
|
1721
1856
|
function resolveRelationSelection(params) {
|
|
1722
1857
|
const { meta, model, select, include } = params;
|
|
1723
|
-
const selectSpec =
|
|
1724
|
-
const includeSpec =
|
|
1858
|
+
const selectSpec = isPlainObject2(select) ? select : null;
|
|
1859
|
+
const includeSpec = isPlainObject2(include) ? include : null;
|
|
1725
1860
|
if (includeSpec !== null) {
|
|
1726
1861
|
for (const [key, value] of Object.entries(includeSpec)) {
|
|
1727
1862
|
if (value === undefined || value === false)
|
|
@@ -1735,7 +1870,7 @@ function resolveRelationSelection(params) {
|
|
|
1735
1870
|
meta: { model: model.name, relation: key }
|
|
1736
1871
|
});
|
|
1737
1872
|
}
|
|
1738
|
-
if (value !== true && !
|
|
1873
|
+
if (value !== true && !isPlainObject2(value)) {
|
|
1739
1874
|
throw validationError2({
|
|
1740
1875
|
message: `include.${key} on model "${model.name}" must be true or an object`,
|
|
1741
1876
|
meta: { model: model.name, relation: key }
|
|
@@ -1747,14 +1882,14 @@ function resolveRelationSelection(params) {
|
|
|
1747
1882
|
if (selectSpec !== null) {
|
|
1748
1883
|
for (const relation of model.relations) {
|
|
1749
1884
|
const value = selectSpec[relation.name];
|
|
1750
|
-
if (value === true ||
|
|
1885
|
+
if (value === true || isPlainObject2(value))
|
|
1751
1886
|
specs.set(relation.name, value);
|
|
1752
1887
|
}
|
|
1753
1888
|
}
|
|
1754
1889
|
if (includeSpec !== null) {
|
|
1755
1890
|
for (const relation of model.relations) {
|
|
1756
1891
|
const value = includeSpec[relation.name];
|
|
1757
|
-
if (value === true ||
|
|
1892
|
+
if (value === true || isPlainObject2(value))
|
|
1758
1893
|
specs.set(relation.name, value);
|
|
1759
1894
|
}
|
|
1760
1895
|
}
|
|
@@ -1802,9 +1937,9 @@ var AGG_FN_BY_KEY = {
|
|
|
1802
1937
|
_max: "max"
|
|
1803
1938
|
};
|
|
1804
1939
|
function validationError3(params) {
|
|
1805
|
-
return new
|
|
1940
|
+
return new VibeError8({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
1806
1941
|
}
|
|
1807
|
-
function
|
|
1942
|
+
function isPlainObject3(value) {
|
|
1808
1943
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
1809
1944
|
}
|
|
1810
1945
|
function isDirectValue(value) {
|
|
@@ -1855,7 +1990,7 @@ function compileLock(params) {
|
|
|
1855
1990
|
function compileConflictTarget(params) {
|
|
1856
1991
|
const { dialect, meta, model, method, target, where } = params;
|
|
1857
1992
|
if ((target !== undefined || where !== undefined) && dialect.doNothingForm === "insertIgnore") {
|
|
1858
|
-
throw
|
|
1993
|
+
throw unsupportedCapability3({
|
|
1859
1994
|
dialect: dialect.name,
|
|
1860
1995
|
feature: `a conflict target on ${model.clientName}.${method}()`,
|
|
1861
1996
|
hint: "mysql's INSERT IGNORE / ON DUPLICATE KEY fire on ANY duplicate key \u2014 no target can be named; gate the call per dialect"
|
|
@@ -1871,9 +2006,9 @@ function compileConflictTarget(params) {
|
|
|
1871
2006
|
});
|
|
1872
2007
|
}
|
|
1873
2008
|
out.columns = target.map((name) => getFieldMeta({ model, field: String(name), context: "onConflict target" }).columnName);
|
|
1874
|
-
} else if (
|
|
2009
|
+
} else if (isPlainObject3(target) && typeof target.constraint === "string" && target.constraint.length > 0 && Object.keys(target).length === 1) {
|
|
1875
2010
|
if (!dialect.capabilities.conflictTargetConstraint) {
|
|
1876
|
-
throw
|
|
2011
|
+
throw unsupportedCapability3({
|
|
1877
2012
|
dialect: dialect.name,
|
|
1878
2013
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
1879
2014
|
hint: "only postgres supports ON CONSTRAINT targets \u2014 name the indexed columns instead"
|
|
@@ -1895,7 +2030,7 @@ function compileConflictTarget(params) {
|
|
|
1895
2030
|
});
|
|
1896
2031
|
}
|
|
1897
2032
|
if (!dialect.capabilities.partialIndexes) {
|
|
1898
|
-
throw
|
|
2033
|
+
throw unsupportedCapability3({
|
|
1899
2034
|
dialect: dialect.name,
|
|
1900
2035
|
feature: "a partial-index conflict target",
|
|
1901
2036
|
hint: "this dialect has no partial indexes to target"
|
|
@@ -1939,7 +2074,7 @@ function compileDbNowFilter(params) {
|
|
|
1939
2074
|
}
|
|
1940
2075
|
function requireObject(params) {
|
|
1941
2076
|
const { value, what, model } = params;
|
|
1942
|
-
if (!
|
|
2077
|
+
if (!isPlainObject3(value)) {
|
|
1943
2078
|
throw validationError3({
|
|
1944
2079
|
message: `${what} must be an object on model "${model.name}"`,
|
|
1945
2080
|
meta: { model: model.name, argument: what }
|
|
@@ -1981,7 +2116,7 @@ function assertListFilterable(params) {
|
|
|
1981
2116
|
const { dialect, model, field, operator } = params;
|
|
1982
2117
|
if (dialect.capabilities.scalarArrays)
|
|
1983
2118
|
return;
|
|
1984
|
-
throw
|
|
2119
|
+
throw unsupportedCapability3({
|
|
1985
2120
|
dialect: dialect.name,
|
|
1986
2121
|
feature: `scalar list filter "${operator}" on ${model.name}.${field.name}`,
|
|
1987
2122
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -1991,7 +2126,7 @@ function assertListWritable(params) {
|
|
|
1991
2126
|
const { dialect, model, field } = params;
|
|
1992
2127
|
if (!field.isList || dialect.capabilities.scalarArrays)
|
|
1993
2128
|
return;
|
|
1994
|
-
throw
|
|
2129
|
+
throw unsupportedCapability3({
|
|
1995
2130
|
dialect: dialect.name,
|
|
1996
2131
|
feature: `writing the scalar list ${model.name}.${field.name}`,
|
|
1997
2132
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -2108,7 +2243,7 @@ function compileJsonFieldFilter(params) {
|
|
|
2108
2243
|
const requireOp = (op) => {
|
|
2109
2244
|
if (dialect.supportedJsonOps.includes(op))
|
|
2110
2245
|
return;
|
|
2111
|
-
throw
|
|
2246
|
+
throw unsupportedCapability3({
|
|
2112
2247
|
dialect: dialect.name,
|
|
2113
2248
|
feature: `json filter "${op}" on ${model.name}.${field.name}`,
|
|
2114
2249
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -2268,7 +2403,7 @@ function compileOperatorFilter(params) {
|
|
|
2268
2403
|
});
|
|
2269
2404
|
break;
|
|
2270
2405
|
}
|
|
2271
|
-
if (
|
|
2406
|
+
if (isPlainObject3(operand)) {
|
|
2272
2407
|
conditions.push({
|
|
2273
2408
|
kind: "not",
|
|
2274
2409
|
operand: compileOperatorFilter({ ctx, model, field, filter: operand, qualify })
|
|
@@ -2385,7 +2520,7 @@ function compileFieldFilter(params) {
|
|
|
2385
2520
|
assertListFilterable({ dialect, model, field, operator: "equals" });
|
|
2386
2521
|
return { kind: "cmp", op: "eq", column, value: encodeValue({ dialect: dialect.name, field, value }) };
|
|
2387
2522
|
}
|
|
2388
|
-
if (
|
|
2523
|
+
if (isPlainObject3(value)) {
|
|
2389
2524
|
return compileOperatorFilter({ ctx, model, field, filter: value, qualify });
|
|
2390
2525
|
}
|
|
2391
2526
|
throw validationError3({
|
|
@@ -2471,7 +2606,7 @@ function buildRelationExists(params) {
|
|
|
2471
2606
|
}
|
|
2472
2607
|
function requirePredicateObject(params) {
|
|
2473
2608
|
const { model, relation, operator, operand } = params;
|
|
2474
|
-
if (!
|
|
2609
|
+
if (!isPlainObject3(operand)) {
|
|
2475
2610
|
throw validationError3({
|
|
2476
2611
|
message: `"${operator}" on relation ${model.name}.${relation.name} expects a where object`,
|
|
2477
2612
|
meta: { model: model.name, relation: relation.name, operator }
|
|
@@ -2484,7 +2619,7 @@ function compileRelationFilter(params) {
|
|
|
2484
2619
|
const link = resolveRelationLink({ meta: ctx.meta, model, relation });
|
|
2485
2620
|
const outer = qualify ?? model.tableName;
|
|
2486
2621
|
if (relation.isList) {
|
|
2487
|
-
if (!
|
|
2622
|
+
if (!isPlainObject3(value)) {
|
|
2488
2623
|
throw validationError3({
|
|
2489
2624
|
message: `filter on relation ${model.name}.${relation.name} must be { some | every | none: \u2026 }`,
|
|
2490
2625
|
meta: { model: model.name, relation: relation.name }
|
|
@@ -2546,7 +2681,7 @@ function compileRelationFilter(params) {
|
|
|
2546
2681
|
if (value === null) {
|
|
2547
2682
|
isArm = null;
|
|
2548
2683
|
hasIs = true;
|
|
2549
|
-
} else if (
|
|
2684
|
+
} else if (isPlainObject3(value) && (("is" in value) || ("isNot" in value))) {
|
|
2550
2685
|
for (const key of Object.keys(value)) {
|
|
2551
2686
|
if (value[key] === undefined)
|
|
2552
2687
|
continue;
|
|
@@ -2563,7 +2698,7 @@ function compileRelationFilter(params) {
|
|
|
2563
2698
|
});
|
|
2564
2699
|
}
|
|
2565
2700
|
}
|
|
2566
|
-
} else if (
|
|
2701
|
+
} else if (isPlainObject3(value)) {
|
|
2567
2702
|
isArm = value;
|
|
2568
2703
|
hasIs = true;
|
|
2569
2704
|
} else {
|
|
@@ -2659,7 +2794,7 @@ function compileOrderTerm(params) {
|
|
|
2659
2794
|
const column = dialect !== undefined ? compareColumnRef({ dialect, field, ...qualify === undefined ? {} : { table: qualify } }) : columnRef({ field, ...qualify === undefined ? {} : { table: qualify } });
|
|
2660
2795
|
if (spec === "asc" || spec === "desc")
|
|
2661
2796
|
return { column, direction: spec };
|
|
2662
|
-
if (
|
|
2797
|
+
if (isPlainObject3(spec)) {
|
|
2663
2798
|
const { sort, nulls } = spec;
|
|
2664
2799
|
if (sort !== "asc" && sort !== "desc") {
|
|
2665
2800
|
throw validationError3({
|
|
@@ -2831,7 +2966,7 @@ function resolveComputedSelection(params) {
|
|
|
2831
2966
|
const specs = model.computed;
|
|
2832
2967
|
if (specs === null || aggregating)
|
|
2833
2968
|
return null;
|
|
2834
|
-
if (
|
|
2969
|
+
if (isPlainObject3(args.select)) {
|
|
2835
2970
|
const names = [];
|
|
2836
2971
|
for (const [name, value] of Object.entries(args.select)) {
|
|
2837
2972
|
if (!specs.has(name) || value === undefined || value === false)
|
|
@@ -2846,7 +2981,7 @@ function resolveComputedSelection(params) {
|
|
|
2846
2981
|
}
|
|
2847
2982
|
return names.length === 0 ? null : names;
|
|
2848
2983
|
}
|
|
2849
|
-
if (
|
|
2984
|
+
if (isPlainObject3(args.omit)) {
|
|
2850
2985
|
const names = [];
|
|
2851
2986
|
for (const name of specs.keys()) {
|
|
2852
2987
|
const value = args.omit[name];
|
|
@@ -3154,7 +3289,7 @@ function compileUpdateSet(params) {
|
|
|
3154
3289
|
meta: { model: model.name, field: field.name }
|
|
3155
3290
|
});
|
|
3156
3291
|
}
|
|
3157
|
-
if (
|
|
3292
|
+
if (isPlainObject3(value) && field.scalarType !== "Json") {
|
|
3158
3293
|
const entries = Object.entries(value).filter(([, operand2]) => operand2 !== undefined);
|
|
3159
3294
|
if (entries.length !== 1) {
|
|
3160
3295
|
throw validationError3({
|
|
@@ -3316,7 +3451,7 @@ function parseCursor(params) {
|
|
|
3316
3451
|
function keysetFieldOfTerm(params) {
|
|
3317
3452
|
const { dialect, model, term } = params;
|
|
3318
3453
|
if (!("column" in term)) {
|
|
3319
|
-
throw
|
|
3454
|
+
throw unsupportedCapability3({
|
|
3320
3455
|
dialect: dialect.name,
|
|
3321
3456
|
feature: `keyset pagination over a relation or aggregate orderBy on model "${model.name}"`,
|
|
3322
3457
|
hint: "order by scalar columns plus a unique tie-breaker; a correlated subquery value is not on the row, so no position can describe it"
|
|
@@ -3324,7 +3459,7 @@ function keysetFieldOfTerm(params) {
|
|
|
3324
3459
|
}
|
|
3325
3460
|
const field = model.fieldByColumn.get(term.column.name);
|
|
3326
3461
|
if (field === undefined) {
|
|
3327
|
-
throw
|
|
3462
|
+
throw unsupportedCapability3({
|
|
3328
3463
|
dialect: dialect.name,
|
|
3329
3464
|
feature: `keyset pagination over a computed order key on model "${model.name}"`,
|
|
3330
3465
|
hint: "order by scalar columns plus a unique tie-breaker"
|
|
@@ -3470,7 +3605,7 @@ function orderByScalarFields(params) {
|
|
|
3470
3605
|
visit(entry);
|
|
3471
3606
|
return;
|
|
3472
3607
|
}
|
|
3473
|
-
if (!
|
|
3608
|
+
if (!isPlainObject3(value))
|
|
3474
3609
|
return;
|
|
3475
3610
|
for (const [key, spec] of Object.entries(value)) {
|
|
3476
3611
|
if (spec === undefined)
|
|
@@ -3667,7 +3802,10 @@ function buildFindShape(params) {
|
|
|
3667
3802
|
const absTake = take === undefined ? undefined : Math.abs(take);
|
|
3668
3803
|
const limit = firstLimit === undefined ? absTake : absTake === undefined ? firstLimit : Math.min(firstLimit, absTake);
|
|
3669
3804
|
const offset = skip;
|
|
3670
|
-
const lateralJoins = useLateral ?
|
|
3805
|
+
const lateralJoins = useLateral ? [
|
|
3806
|
+
...selection.relations.map((node, index) => buildLateralJoinClause({ dialect, meta, model, node, index })),
|
|
3807
|
+
...selection.counts.map((node, index) => buildLateralCount({ dialect, meta, model, node, index }))
|
|
3808
|
+
] : [];
|
|
3671
3809
|
const projected = projectionColumns({
|
|
3672
3810
|
model,
|
|
3673
3811
|
selectedFields,
|
|
@@ -3686,6 +3824,15 @@ function buildFindShape(params) {
|
|
|
3686
3824
|
}
|
|
3687
3825
|
return out;
|
|
3688
3826
|
};
|
|
3827
|
+
const withKeyColumns = (inner, keys) => {
|
|
3828
|
+
const out = [...inner];
|
|
3829
|
+
for (const field of keys) {
|
|
3830
|
+
if (out.some((column) => column.name === field.columnName))
|
|
3831
|
+
continue;
|
|
3832
|
+
out.push(columnRef({ field, ...qualify === undefined ? {} : { table: qualify } }));
|
|
3833
|
+
}
|
|
3834
|
+
return out;
|
|
3835
|
+
};
|
|
3689
3836
|
if (emulateDistinct) {
|
|
3690
3837
|
const arranged = orderTerms;
|
|
3691
3838
|
const windowOrder = arranged.slice(distinctLeadCount);
|
|
@@ -3738,6 +3885,30 @@ function buildFindShape(params) {
|
|
|
3738
3885
|
};
|
|
3739
3886
|
return { statement: statement2, reverseRows: backward, resortBy };
|
|
3740
3887
|
}
|
|
3888
|
+
const pageFirst = lateralJoins.length > 0 && (limit !== undefined || offset !== undefined) && orderTerms.every((term) => ("column" in term));
|
|
3889
|
+
if (pageFirst && selection !== undefined) {
|
|
3890
|
+
const correlationKeys = [...selection.relations, ...selection.counts].flatMap((node) => node.link.parentKeys);
|
|
3891
|
+
const innerColumns = withOuterOrderKeys(withKeyColumns(projected, correlationKeys), orderTerms);
|
|
3892
|
+
const inner = {
|
|
3893
|
+
kind: "select",
|
|
3894
|
+
table: { name: model.tableName },
|
|
3895
|
+
columns: innerColumns,
|
|
3896
|
+
...where === undefined ? {} : { where },
|
|
3897
|
+
...distinctOn === undefined ? {} : { distinctOn },
|
|
3898
|
+
...orderTerms.length === 0 ? {} : { orderBy: orderTerms },
|
|
3899
|
+
...limit === undefined ? {} : { limit },
|
|
3900
|
+
...offset === undefined ? {} : { offset }
|
|
3901
|
+
};
|
|
3902
|
+
const statement2 = {
|
|
3903
|
+
kind: "select",
|
|
3904
|
+
table: { name: model.tableName },
|
|
3905
|
+
columns,
|
|
3906
|
+
fromSelect: inner,
|
|
3907
|
+
lateralJoins,
|
|
3908
|
+
...orderTerms.length === 0 ? {} : { orderBy: orderTerms }
|
|
3909
|
+
};
|
|
3910
|
+
return { statement: statement2, reverseRows: backward, resortBy };
|
|
3911
|
+
}
|
|
3741
3912
|
const statement = {
|
|
3742
3913
|
kind: "select",
|
|
3743
3914
|
table: { name: model.tableName },
|
|
@@ -3751,73 +3922,66 @@ function buildFindShape(params) {
|
|
|
3751
3922
|
};
|
|
3752
3923
|
return { statement, reverseRows: backward, resortBy };
|
|
3753
3924
|
}
|
|
3754
|
-
var LATERAL_CHILD_ALIAS = "__rel";
|
|
3755
3925
|
var DISTINCT_TABLE_ALIAS = "__dist";
|
|
3756
3926
|
var DISTINCT_ROW_NUMBER_ALIAS = "__rn";
|
|
3757
3927
|
function lateralColumnAlias(params) {
|
|
3758
3928
|
return `__rel_${params.relation}`;
|
|
3759
3929
|
}
|
|
3760
|
-
function
|
|
3761
|
-
|
|
3762
|
-
const projected = node.args.projected;
|
|
3763
|
-
if (projected === null || node.args.orderBy === undefined)
|
|
3764
|
-
return [];
|
|
3765
|
-
const target = node.link.target;
|
|
3766
|
-
const hidden = [];
|
|
3767
|
-
const items = Array.isArray(node.args.orderBy) ? node.args.orderBy : [node.args.orderBy];
|
|
3768
|
-
for (const item of items) {
|
|
3769
|
-
if (!isPlainObject2(item))
|
|
3770
|
-
continue;
|
|
3771
|
-
for (const [name, value] of Object.entries(item)) {
|
|
3772
|
-
if (value === undefined)
|
|
3773
|
-
continue;
|
|
3774
|
-
if (!target.fieldByName.has(name))
|
|
3775
|
-
continue;
|
|
3776
|
-
if (!projected.includes(name) && !hidden.includes(name))
|
|
3777
|
-
hidden.push(name);
|
|
3778
|
-
}
|
|
3779
|
-
}
|
|
3780
|
-
return hidden;
|
|
3930
|
+
function lateralCountAlias(params) {
|
|
3931
|
+
return `__vibe_count_${params.index}`;
|
|
3781
3932
|
}
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
}
|
|
3933
|
+
function buildLateralCount(params) {
|
|
3934
|
+
const { dialect, meta, model, node, index } = params;
|
|
3935
|
+
const { link } = node;
|
|
3936
|
+
const childAlias = "__count_child";
|
|
3937
|
+
const joinAlias = "__count_join";
|
|
3938
|
+
const columnAlias = lateralCountAlias({ index });
|
|
3939
|
+
let select;
|
|
3940
|
+
const filter = node.where === undefined ? undefined : compileWhere({
|
|
3941
|
+
dialect,
|
|
3942
|
+
meta,
|
|
3943
|
+
model: link.target,
|
|
3944
|
+
where: node.where,
|
|
3945
|
+
qualify: childAlias
|
|
3946
|
+
});
|
|
3947
|
+
if (link.kind === "manyToMany") {
|
|
3948
|
+
const join = link.join;
|
|
3949
|
+
const pair = singleKeyPair({ link, what: "implicit many-to-many relation count" });
|
|
3950
|
+
const correlation = {
|
|
3951
|
+
kind: "cmpCol",
|
|
3952
|
+
op: "eq",
|
|
3953
|
+
left: { table: joinAlias, name: join.parentColumn },
|
|
3954
|
+
right: { table: model.tableName, name: pair.parent.columnName }
|
|
3955
|
+
};
|
|
3956
|
+
select = {
|
|
3957
|
+
kind: "select",
|
|
3958
|
+
table: filter === undefined ? { name: join.tableName, alias: joinAlias } : { name: link.target.tableName, alias: childAlias },
|
|
3959
|
+
columns: [],
|
|
3960
|
+
selectExpr: { kind: "countAll", alias: columnAlias },
|
|
3961
|
+
where: filter === undefined ? correlation : { kind: "and", operands: [correlation, filter] },
|
|
3962
|
+
...filter === undefined ? {} : { innerJoins: [{
|
|
3963
|
+
table: { name: join.tableName, alias: joinAlias },
|
|
3964
|
+
on: [{ left: { table: joinAlias, name: join.childColumn }, right: { table: childAlias, name: pair.child.columnName } }]
|
|
3965
|
+
}] }
|
|
3966
|
+
};
|
|
3967
|
+
} else {
|
|
3968
|
+
const correlation = correlateLink({ link, childTable: childAlias, outerTable: model.tableName });
|
|
3969
|
+
select = {
|
|
3970
|
+
kind: "select",
|
|
3971
|
+
table: { name: link.target.tableName, alias: childAlias },
|
|
3972
|
+
columns: [],
|
|
3973
|
+
selectExpr: { kind: "countAll", alias: columnAlias },
|
|
3974
|
+
where: filter === undefined ? correlation : { kind: "and", operands: [correlation, filter] }
|
|
3975
|
+
};
|
|
3805
3976
|
}
|
|
3806
|
-
return {
|
|
3977
|
+
return { alias: `__count_${index}`, columnAlias, agg: "count", select };
|
|
3807
3978
|
}
|
|
3808
3979
|
function buildLateralJoinClause(params) {
|
|
3809
3980
|
const { dialect, meta, model, node, index } = params;
|
|
3810
3981
|
const link = node.link;
|
|
3811
3982
|
const target = link.target;
|
|
3812
3983
|
const aliasSeq = { n: 0 };
|
|
3813
|
-
const
|
|
3814
|
-
const hiddenOrder = lateralHiddenOrderFields({ node });
|
|
3815
|
-
const selectedWithOrder = projected === null || hiddenOrder.length === 0 ? projected : [...projected, ...hiddenOrder];
|
|
3816
|
-
const columns = projectionColumns({
|
|
3817
|
-
model: target,
|
|
3818
|
-
selectedFields: selectedWithOrder,
|
|
3819
|
-
table: LATERAL_CHILD_ALIAS
|
|
3820
|
-
}).map((column) => applyLateralTransform({ model: target, column }));
|
|
3984
|
+
const { columns } = lateralProjection({ node });
|
|
3821
3985
|
let correlate;
|
|
3822
3986
|
if (link.kind === "manyToMany") {
|
|
3823
3987
|
const join = link.join;
|
|
@@ -3876,6 +4040,7 @@ function buildLateralJoinClause(params) {
|
|
|
3876
4040
|
alias: `__lat_${index}`,
|
|
3877
4041
|
columnAlias: lateralColumnAlias({ relation: node.name }),
|
|
3878
4042
|
agg: isList ? "jsonArray" : "jsonObject",
|
|
4043
|
+
rowFormat: "positional",
|
|
3879
4044
|
select: {
|
|
3880
4045
|
kind: "select",
|
|
3881
4046
|
table: { name: target.tableName, alias: LATERAL_CHILD_ALIAS },
|
|
@@ -4037,7 +4202,7 @@ function compileAggregatePredicate(params) {
|
|
|
4037
4202
|
const aggColumn = key === "_count" ? { name: field.columnName } : compareColumnRef({ dialect, field });
|
|
4038
4203
|
agg = { fn: AGG_FN_BY_KEY[key], column: aggColumn };
|
|
4039
4204
|
}
|
|
4040
|
-
const spec =
|
|
4205
|
+
const spec = isPlainObject3(filter) ? filter : { equals: filter };
|
|
4041
4206
|
const out = [];
|
|
4042
4207
|
for (const [operator, operand] of Object.entries(spec)) {
|
|
4043
4208
|
if (operand === undefined)
|
|
@@ -4104,7 +4269,7 @@ function compileHaving(params) {
|
|
|
4104
4269
|
meta: { model: model.name, field: field.name }
|
|
4105
4270
|
});
|
|
4106
4271
|
};
|
|
4107
|
-
if (
|
|
4272
|
+
if (isPlainObject3(value)) {
|
|
4108
4273
|
const plain = {};
|
|
4109
4274
|
for (const [operator, operand] of Object.entries(value)) {
|
|
4110
4275
|
if (operand === undefined)
|
|
@@ -4191,7 +4356,7 @@ function compileGroupByOrder(params) {
|
|
|
4191
4356
|
function overlayRefetchWhere(params) {
|
|
4192
4357
|
const { model, where, data } = params;
|
|
4193
4358
|
const out = { ...where };
|
|
4194
|
-
if (!
|
|
4359
|
+
if (!isPlainObject3(data))
|
|
4195
4360
|
return out;
|
|
4196
4361
|
for (const key of Object.keys(out)) {
|
|
4197
4362
|
if (!model.fieldByName.has(key))
|
|
@@ -4203,7 +4368,7 @@ function overlayRefetchWhere(params) {
|
|
|
4203
4368
|
out[key] = value;
|
|
4204
4369
|
continue;
|
|
4205
4370
|
}
|
|
4206
|
-
if (
|
|
4371
|
+
if (isPlainObject3(value) && (isDirectValue(value.set) || value.set === null)) {
|
|
4207
4372
|
out[key] = value.set;
|
|
4208
4373
|
}
|
|
4209
4374
|
}
|
|
@@ -4234,7 +4399,7 @@ function resolveStrategy(params) {
|
|
|
4234
4399
|
const strategy = explicit ?? defaultRelationStrategy ?? "query";
|
|
4235
4400
|
if (strategy === "join" && !dialect.capabilities.lateralJoin) {
|
|
4236
4401
|
if (explicit !== undefined || hasRelations) {
|
|
4237
|
-
throw
|
|
4402
|
+
throw unsupportedCapability3({
|
|
4238
4403
|
dialect: dialect.name,
|
|
4239
4404
|
feature: 'relationStrategy "join"',
|
|
4240
4405
|
hint: 'lateral joins are postgres-only \u2014 use the portable "query" strategy on this dialect'
|
|
@@ -4271,7 +4436,7 @@ function buildQuery(params) {
|
|
|
4271
4436
|
const aggregating = method === "aggregate" || method === "groupBy" || method === "count";
|
|
4272
4437
|
const relationCapable = RELATION_CAPABLE_METHODS.includes(method);
|
|
4273
4438
|
const relations = relationCapable ? resolveRelationSelection({ meta, model, select: args.select, include: args.include }) : null;
|
|
4274
|
-
if (!relationCapable && !aggregating &&
|
|
4439
|
+
if (!relationCapable && !aggregating && isPlainObject3(args.select)) {
|
|
4275
4440
|
for (const key of Object.keys(args.select)) {
|
|
4276
4441
|
if (args.select[key] === undefined || args.select[key] === false)
|
|
4277
4442
|
continue;
|
|
@@ -4641,7 +4806,7 @@ function buildQuery(params) {
|
|
|
4641
4806
|
case "createMany":
|
|
4642
4807
|
case "createManyAndReturn": {
|
|
4643
4808
|
if (method === "createManyAndReturn" && !dialect.capabilities.createManyAndReturn) {
|
|
4644
|
-
throw
|
|
4809
|
+
throw unsupportedCapability3({
|
|
4645
4810
|
dialect: dialect.name,
|
|
4646
4811
|
feature: "createManyAndReturn",
|
|
4647
4812
|
hint: "this dialect cannot RETURNING multiple inserted rows \u2014 use createMany then findMany"
|
|
@@ -4773,7 +4938,7 @@ function buildQuery(params) {
|
|
|
4773
4938
|
case "updateManyAndReturn": {
|
|
4774
4939
|
const isUnique = method === "update";
|
|
4775
4940
|
if (method === "updateManyAndReturn" && !dialect.capabilities.returning) {
|
|
4776
|
-
throw
|
|
4941
|
+
throw unsupportedCapability3({
|
|
4777
4942
|
dialect: dialect.name,
|
|
4778
4943
|
feature: "updateManyAndReturn",
|
|
4779
4944
|
hint: "this dialect has no RETURNING \u2014 use updateMany then findMany, accepting that the read is a separate, unprotected statement"
|
|
@@ -4818,7 +4983,7 @@ function buildQuery(params) {
|
|
|
4818
4983
|
});
|
|
4819
4984
|
}
|
|
4820
4985
|
if (!dialect.capabilities.conflictTargetConstraint) {
|
|
4821
|
-
throw
|
|
4986
|
+
throw unsupportedCapability3({
|
|
4822
4987
|
dialect: dialect.name,
|
|
4823
4988
|
feature: "a named-constraint conflict target on upsert",
|
|
4824
4989
|
hint: "only postgres supports ON CONFLICT ON CONSTRAINT \u2014 rely on the where-derived column target instead"
|
|
@@ -4836,7 +5001,7 @@ function buildQuery(params) {
|
|
|
4836
5001
|
const targetFields = conflictTargetGroup({ model, where }).fields;
|
|
4837
5002
|
for (const field of targetFields) {
|
|
4838
5003
|
if (!(field.columnName in row.values)) {
|
|
4839
|
-
throw new
|
|
5004
|
+
throw new VibeError8({
|
|
4840
5005
|
code: "VIBE_INTERNAL",
|
|
4841
5006
|
message: `upsert on ${model.name}: create data does not bind conflict-target column "${field.columnName}" \u2014 this shape must run the find-then-write fallback flow`,
|
|
4842
5007
|
meta: { model: model.name, column: field.columnName }
|
|
@@ -4847,7 +5012,7 @@ function buildQuery(params) {
|
|
|
4847
5012
|
for (const [key, value] of Object.entries(where)) {
|
|
4848
5013
|
if (value === undefined || nativeTargetNames.has(key))
|
|
4849
5014
|
continue;
|
|
4850
|
-
throw new
|
|
5015
|
+
throw new VibeError8({
|
|
4851
5016
|
code: "VIBE_INTERNAL",
|
|
4852
5017
|
message: `upsert on ${model.name}: where field "${key}" is not part of the conflict target \u2014 guarded upserts must run the find-then-write fallback flow`,
|
|
4853
5018
|
meta: { model: model.name, field: key }
|
|
@@ -4900,7 +5065,7 @@ function buildQuery(params) {
|
|
|
4900
5065
|
import { getSqlDialect, renderStatement } from "@vibeorm/sql";
|
|
4901
5066
|
|
|
4902
5067
|
// packages/runtime/src/relation-key.ts
|
|
4903
|
-
import { VibeError as
|
|
5068
|
+
import { VibeError as VibeError9 } from "@vibeorm/schema";
|
|
4904
5069
|
function relationValueKey(params) {
|
|
4905
5070
|
const { value } = params;
|
|
4906
5071
|
if (value === null || value === undefined)
|
|
@@ -4933,7 +5098,7 @@ function relationTupleKey(params) {
|
|
|
4933
5098
|
if (value === null || value === undefined)
|
|
4934
5099
|
return null;
|
|
4935
5100
|
if (typeof value === "object" && !(value instanceof Date) && !(value instanceof Uint8Array)) {
|
|
4936
|
-
throw new
|
|
5101
|
+
throw new VibeError9({
|
|
4937
5102
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
4938
5103
|
message: "a relation key of this type cannot be compared faithfully in memory \u2014 relation links support String, Int, BigInt, Boolean, DateTime and Bytes keys",
|
|
4939
5104
|
meta: { received: Object.prototype.toString.call(value) }
|
|
@@ -4959,7 +5124,7 @@ function assertWriteScope(params) {
|
|
|
4959
5124
|
}
|
|
4960
5125
|
|
|
4961
5126
|
// packages/runtime/src/nested-writes.ts
|
|
4962
|
-
import { VibeError as
|
|
5127
|
+
import { VibeError as VibeError10 } from "@vibeorm/schema";
|
|
4963
5128
|
function parentTupleOf(params) {
|
|
4964
5129
|
const { link, row } = params;
|
|
4965
5130
|
return link.parentKeys.map((field) => row[field.name]);
|
|
@@ -5006,12 +5171,12 @@ function detachAssignment(params) {
|
|
|
5006
5171
|
return out;
|
|
5007
5172
|
}
|
|
5008
5173
|
function validationError4(params) {
|
|
5009
|
-
return new
|
|
5174
|
+
return new VibeError10({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
5010
5175
|
}
|
|
5011
5176
|
function notFound(params) {
|
|
5012
|
-
return new
|
|
5177
|
+
return new VibeError10({ code: "VIBE_NOT_FOUND", message: params.message, meta: params.meta });
|
|
5013
5178
|
}
|
|
5014
|
-
function
|
|
5179
|
+
function isPlainObject4(value) {
|
|
5015
5180
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
5016
5181
|
}
|
|
5017
5182
|
function isDirectValue2(value) {
|
|
@@ -5019,7 +5184,7 @@ function isDirectValue2(value) {
|
|
|
5019
5184
|
}
|
|
5020
5185
|
function hasNestedWrites(params) {
|
|
5021
5186
|
const { model, data } = params;
|
|
5022
|
-
if (!
|
|
5187
|
+
if (!isPlainObject4(data))
|
|
5023
5188
|
return false;
|
|
5024
5189
|
for (const [key, value] of Object.entries(data)) {
|
|
5025
5190
|
if (value === undefined)
|
|
@@ -5059,7 +5224,7 @@ function partitionData(params) {
|
|
|
5059
5224
|
scalars[key] = value;
|
|
5060
5225
|
continue;
|
|
5061
5226
|
}
|
|
5062
|
-
if (!
|
|
5227
|
+
if (!isPlainObject4(value)) {
|
|
5063
5228
|
throw validationError4({
|
|
5064
5229
|
message: `relation ${model.name}.${key} in data must be an object of nested-write operations`,
|
|
5065
5230
|
meta: { model: model.name, relation: key }
|
|
@@ -5101,7 +5266,7 @@ function toItems(params) {
|
|
|
5101
5266
|
});
|
|
5102
5267
|
}
|
|
5103
5268
|
return list.map((item) => {
|
|
5104
|
-
if (!
|
|
5269
|
+
if (!isPlainObject4(item)) {
|
|
5105
5270
|
throw validationError4({
|
|
5106
5271
|
message: `nested "${verb}" on ${op.link.relation.model}.${op.name} expects object items`,
|
|
5107
5272
|
meta: { relation: op.name, verb }
|
|
@@ -5113,7 +5278,7 @@ function toItems(params) {
|
|
|
5113
5278
|
function requireShape(params) {
|
|
5114
5279
|
const { op, verb, item, keys } = params;
|
|
5115
5280
|
for (const key of keys) {
|
|
5116
|
-
if (!
|
|
5281
|
+
if (!isPlainObject4(item[key])) {
|
|
5117
5282
|
throw validationError4({
|
|
5118
5283
|
message: `nested "${verb}" on ${op.link.relation.model}.${op.name} needs an object "${key}"`,
|
|
5119
5284
|
meta: { relation: op.name, verb, missing: key }
|
|
@@ -5368,7 +5533,7 @@ function createNeedsTransaction(params) {
|
|
|
5368
5533
|
if (op.link.kind !== "toOneOwner")
|
|
5369
5534
|
return true;
|
|
5370
5535
|
const { verb, operand } = singleVerb({ op });
|
|
5371
|
-
if (verb !== "connect" || !
|
|
5536
|
+
if (verb !== "connect" || !isPlainObject4(operand))
|
|
5372
5537
|
return true;
|
|
5373
5538
|
const entries = Object.entries(operand).filter(([, value]) => value !== undefined);
|
|
5374
5539
|
const named = new Map(entries);
|
|
@@ -5385,7 +5550,7 @@ async function createWithNested(params) {
|
|
|
5385
5550
|
}
|
|
5386
5551
|
function updateDataOf(params) {
|
|
5387
5552
|
const { op, operand } = params;
|
|
5388
|
-
if ("data" in operand &&
|
|
5553
|
+
if ("data" in operand && isPlainObject4(operand.data))
|
|
5389
5554
|
return operand.data;
|
|
5390
5555
|
if ("data" in operand) {
|
|
5391
5556
|
throw validationError4({
|
|
@@ -5476,7 +5641,7 @@ async function resolveOwnerUpdateVerb(params) {
|
|
|
5476
5641
|
return;
|
|
5477
5642
|
}
|
|
5478
5643
|
case "update": {
|
|
5479
|
-
if (!
|
|
5644
|
+
if (!isPlainObject4(operand)) {
|
|
5480
5645
|
throw validationError4({
|
|
5481
5646
|
message: `nested "update" on ${link.relation.model}.${op.name} expects an object`,
|
|
5482
5647
|
meta: { relation: op.name }
|
|
@@ -5499,7 +5664,7 @@ async function resolveOwnerUpdateVerb(params) {
|
|
|
5499
5664
|
return;
|
|
5500
5665
|
}
|
|
5501
5666
|
default: {
|
|
5502
|
-
if (!
|
|
5667
|
+
if (!isPlainObject4(operand)) {
|
|
5503
5668
|
throw validationError4({
|
|
5504
5669
|
message: `nested "upsert" on ${link.relation.model}.${op.name} expects an object`,
|
|
5505
5670
|
meta: { relation: op.name }
|
|
@@ -5640,7 +5805,7 @@ async function applyChildUpdateOps(params) {
|
|
|
5640
5805
|
}
|
|
5641
5806
|
case "update": {
|
|
5642
5807
|
if (link.kind === "toOneInverse") {
|
|
5643
|
-
if (!
|
|
5808
|
+
if (!isPlainObject4(operand)) {
|
|
5644
5809
|
throw validationError4({
|
|
5645
5810
|
message: `nested "update" on ${link.relation.model}.${op.name} expects an object`,
|
|
5646
5811
|
meta: { relation: op.name }
|
|
@@ -5703,7 +5868,7 @@ async function applyChildUpdateOps(params) {
|
|
|
5703
5868
|
const items = toItems({ op, verb, operand });
|
|
5704
5869
|
for (const item of items) {
|
|
5705
5870
|
const data = item.data;
|
|
5706
|
-
if (!
|
|
5871
|
+
if (!isPlainObject4(data)) {
|
|
5707
5872
|
throw validationError4({
|
|
5708
5873
|
message: `nested "updateMany" on ${link.relation.model}.${op.name} needs an object "data"`,
|
|
5709
5874
|
meta: { relation: op.name }
|
|
@@ -5774,7 +5939,7 @@ async function applyChildUpdateOps(params) {
|
|
|
5774
5939
|
}
|
|
5775
5940
|
default: {
|
|
5776
5941
|
if (link.kind === "toOneInverse") {
|
|
5777
|
-
if (!
|
|
5942
|
+
if (!isPlainObject4(operand)) {
|
|
5778
5943
|
throw validationError4({
|
|
5779
5944
|
message: `nested "upsert" on ${link.relation.model}.${op.name} expects an object`,
|
|
5780
5945
|
meta: { relation: op.name }
|
|
@@ -6113,7 +6278,7 @@ function previewOperation(params) {
|
|
|
6113
6278
|
};
|
|
6114
6279
|
}
|
|
6115
6280
|
// packages/runtime/src/diagnostics/plan.ts
|
|
6116
|
-
import { VibeError as
|
|
6281
|
+
import { VibeError as VibeError11, unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
|
|
6117
6282
|
import { NATIVE_RLS, getSqlDialect as getSqlDialect2, renderPostgresExplain, renderPostgresWorkload, renderStatement as renderStatement2 } from "@vibeorm/sql";
|
|
6118
6283
|
var PLAN_DEFAULTS = Object.freeze({ maxNodes: 200, maxConditionLength: 2000 });
|
|
6119
6284
|
var CONDITION_KEYS = [
|
|
@@ -6132,7 +6297,7 @@ var CONDITION_KEYS = [
|
|
|
6132
6297
|
"Output"
|
|
6133
6298
|
];
|
|
6134
6299
|
function planError(params) {
|
|
6135
|
-
return new
|
|
6300
|
+
return new VibeError11({ code: "VIBE_ADAPTER", message: params.message, meta: {} });
|
|
6136
6301
|
}
|
|
6137
6302
|
function asRecord(value) {
|
|
6138
6303
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
@@ -6165,14 +6330,14 @@ function parsePostgresPlan(params) {
|
|
|
6165
6330
|
const maxNodes = params.maxNodes ?? PLAN_DEFAULTS.maxNodes;
|
|
6166
6331
|
const includeLiterals = params.includeLiterals === true;
|
|
6167
6332
|
if (!Number.isInteger(maxNodes) || maxNodes < 1) {
|
|
6168
|
-
throw new
|
|
6333
|
+
throw new VibeError11({ code: "VIBE_CONFIG", message: `maxNodes must be a positive integer, got ${String(maxNodes)}`, meta: { maxNodes } });
|
|
6169
6334
|
}
|
|
6170
6335
|
let parsed = payload;
|
|
6171
6336
|
if (typeof payload === "string") {
|
|
6172
6337
|
try {
|
|
6173
6338
|
parsed = JSON.parse(payload);
|
|
6174
6339
|
} catch {
|
|
6175
|
-
throw new
|
|
6340
|
+
throw new VibeError11({
|
|
6176
6341
|
code: "VIBE_ADAPTER",
|
|
6177
6342
|
message: "EXPLAIN returned malformed JSON",
|
|
6178
6343
|
meta: { reason: "malformed-plan-json", mode }
|
|
@@ -6239,7 +6404,7 @@ function parsePostgresPlan(params) {
|
|
|
6239
6404
|
}
|
|
6240
6405
|
var WRITE_LEADERS = ["INSERT", "UPDATE", "DELETE", "MERGE", "TRUNCATE", "CREATE", "DROP", "ALTER", "GRANT", "REVOKE", "COPY", "CALL", "DO"];
|
|
6241
6406
|
function refuse(params) {
|
|
6242
|
-
return new
|
|
6407
|
+
return new VibeError11({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta ?? {} });
|
|
6243
6408
|
}
|
|
6244
6409
|
function assertExecutableRead(params) {
|
|
6245
6410
|
const normalized = normalizeSql({ text: params.text });
|
|
@@ -6278,7 +6443,7 @@ async function readPlanContext(params) {
|
|
|
6278
6443
|
}
|
|
6279
6444
|
function requirePostgres(params) {
|
|
6280
6445
|
if (params.dialect !== "postgres") {
|
|
6281
|
-
throw
|
|
6446
|
+
throw unsupportedCapability4({
|
|
6282
6447
|
dialect: params.dialect,
|
|
6283
6448
|
feature: "query plan inspection",
|
|
6284
6449
|
hint: "plan inspection is PostgreSQL-shaped; operation tracing (telemetry) works on every dialect"
|
|
@@ -6330,7 +6495,7 @@ async function explainSql(params) {
|
|
|
6330
6495
|
const context = await readPlanContext({ executor, dialect, rlsContextKeys: contextKeys });
|
|
6331
6496
|
if (!context.representative) {
|
|
6332
6497
|
if (params.requireRepresentativeContext === true) {
|
|
6333
|
-
throw new
|
|
6498
|
+
throw new VibeError11({
|
|
6334
6499
|
code: "VIBE_RLS_CONTEXT",
|
|
6335
6500
|
message: `refusing to report a plan taken under a non-representative identity: ${context.reason ?? "role bypasses row policies"}`,
|
|
6336
6501
|
meta: { currentUser: context.currentUser, bypassRls: context.bypassRls, superuser: context.superuser }
|
|
@@ -6412,7 +6577,7 @@ async function explainOperation(params) {
|
|
|
6412
6577
|
});
|
|
6413
6578
|
}
|
|
6414
6579
|
// packages/runtime/src/diagnostics/workload.ts
|
|
6415
|
-
import { unsupportedCapability as
|
|
6580
|
+
import { unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
|
|
6416
6581
|
import { getSqlDialect as getSqlDialect3, renderPostgresWorkload as renderPostgresWorkload2 } from "@vibeorm/sql";
|
|
6417
6582
|
var STATEMENTS_INFO_MIN_VERSION = 140000;
|
|
6418
6583
|
var STATEMENTS_MIN_VERSION = 130000;
|
|
@@ -6445,7 +6610,7 @@ async function readAvailability(params) {
|
|
|
6445
6610
|
async function readWorkloadStatistics(params) {
|
|
6446
6611
|
const { executor } = params;
|
|
6447
6612
|
if (params.dialect !== "postgres") {
|
|
6448
|
-
throw
|
|
6613
|
+
throw unsupportedCapability5({
|
|
6449
6614
|
dialect: params.dialect,
|
|
6450
6615
|
feature: "workload statistics (pg_stat_statements)",
|
|
6451
6616
|
hint: "server workload statistics are PostgreSQL-only; operation telemetry works on every dialect"
|
|
@@ -6523,7 +6688,7 @@ async function readWorkloadStatistics(params) {
|
|
|
6523
6688
|
};
|
|
6524
6689
|
}
|
|
6525
6690
|
// packages/runtime/src/diagnostics/insight.ts
|
|
6526
|
-
import { VibeError as
|
|
6691
|
+
import { VibeError as VibeError12 } from "@vibeorm/schema";
|
|
6527
6692
|
var DIAGNOSTIC_THRESHOLDS = Object.freeze({
|
|
6528
6693
|
repeatThreshold: 5,
|
|
6529
6694
|
minRepeatThreshold: 2,
|
|
@@ -6541,7 +6706,7 @@ function bounded(params) {
|
|
|
6541
6706
|
if (value === undefined)
|
|
6542
6707
|
return fallback;
|
|
6543
6708
|
if (!Number.isInteger(value) || value < min || value > max) {
|
|
6544
|
-
throw new
|
|
6709
|
+
throw new VibeError12({
|
|
6545
6710
|
code: "VIBE_CONFIG",
|
|
6546
6711
|
message: `${name} must be an integer between ${min} and ${max}, got ${String(value)}`,
|
|
6547
6712
|
meta: { [name]: value }
|
|
@@ -6764,11 +6929,11 @@ function recommendFromPlan(params) {
|
|
|
6764
6929
|
return findings.slice(0, maxFindings);
|
|
6765
6930
|
}
|
|
6766
6931
|
// packages/runtime/src/relation-loader.ts
|
|
6767
|
-
import { VibeError as
|
|
6932
|
+
import { VibeError as VibeError14 } from "@vibeorm/schema";
|
|
6768
6933
|
import { renderStatement as renderStatement3 } from "@vibeorm/sql";
|
|
6769
6934
|
|
|
6770
6935
|
// packages/runtime/src/computed.ts
|
|
6771
|
-
import { VibeError as
|
|
6936
|
+
import { VibeError as VibeError13 } from "@vibeorm/schema";
|
|
6772
6937
|
function sameNeeds(a, b) {
|
|
6773
6938
|
if (a.length !== b.length)
|
|
6774
6939
|
return false;
|
|
@@ -6804,7 +6969,7 @@ function assertComputedManifest(params) {
|
|
|
6804
6969
|
problems.push(`"${key}" is configured but missing from the generated client`);
|
|
6805
6970
|
}
|
|
6806
6971
|
if (problems.length > 0) {
|
|
6807
|
-
throw new
|
|
6972
|
+
throw new VibeError13({
|
|
6808
6973
|
code: "VIBE_STALE_CLIENT",
|
|
6809
6974
|
message: `computed-field config changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
|
|
6810
6975
|
meta: { problems }
|
|
@@ -6936,7 +7101,7 @@ function chunkKeys(params) {
|
|
|
6936
7101
|
const rendered = renderStatement3({ dialect: ctx.dialect, statement: build(keys.slice(0, 1)) });
|
|
6937
7102
|
fixed = rendered.values.length - (expands ? arity : 1);
|
|
6938
7103
|
if (fixed + arity > budget) {
|
|
6939
|
-
throw new
|
|
7104
|
+
throw new VibeError14({
|
|
6940
7105
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
6941
7106
|
message: `relation "${relation}" needs ${fixed + arity} bound parameters for its own predicate, more than ${ctx.dialect.name}'s ${budget}-parameter statement budget \u2014 narrow the nested filter`,
|
|
6942
7107
|
meta: { relation, dialect: ctx.dialect.name, required: fixed + arity, budget }
|
|
@@ -7066,7 +7231,7 @@ async function loadManyToMany(params) {
|
|
|
7066
7231
|
const link = node.link;
|
|
7067
7232
|
const join = link.join;
|
|
7068
7233
|
if (join === null) {
|
|
7069
|
-
throw new
|
|
7234
|
+
throw new VibeError14({
|
|
7070
7235
|
code: "VIBE_INTERNAL",
|
|
7071
7236
|
message: `manyToMany relation "${node.name}" resolved without join-table wiring`
|
|
7072
7237
|
});
|
|
@@ -7327,14 +7492,15 @@ async function attachRelations(params) {
|
|
|
7327
7492
|
await loadCounts({ ctx, model, rows, counts: selection.counts });
|
|
7328
7493
|
}
|
|
7329
7494
|
async function attachLateralRelations(params) {
|
|
7330
|
-
const { dialect, meta,
|
|
7495
|
+
const { dialect, meta, rows, selection, execute, wire } = params;
|
|
7331
7496
|
if (rows.length === 0)
|
|
7332
7497
|
return;
|
|
7333
7498
|
const ctx = { dialect, meta, execute, wire };
|
|
7334
7499
|
for (const node of selection.relations) {
|
|
7335
7500
|
const column = lateralColumnAlias({ relation: node.name });
|
|
7336
7501
|
const isList = node.link.relation.isList;
|
|
7337
|
-
const hiddenOrder =
|
|
7502
|
+
const { columns, hiddenOrder } = lateralProjection({ node });
|
|
7503
|
+
const decodePosition = positionalMaterializer({ model: node.link.target, columns });
|
|
7338
7504
|
const kept = [];
|
|
7339
7505
|
for (const row of rows) {
|
|
7340
7506
|
const raw = row[column];
|
|
@@ -7344,7 +7510,7 @@ async function attachLateralRelations(params) {
|
|
|
7344
7510
|
try {
|
|
7345
7511
|
parsed = JSON.parse(raw);
|
|
7346
7512
|
} catch (cause) {
|
|
7347
|
-
throw new
|
|
7513
|
+
throw new VibeError14({
|
|
7348
7514
|
code: "VIBE_INTERNAL",
|
|
7349
7515
|
message: `relation column "${column}" returned text that is not valid JSON`,
|
|
7350
7516
|
meta: { relation: node.name },
|
|
@@ -7353,8 +7519,10 @@ async function attachLateralRelations(params) {
|
|
|
7353
7519
|
}
|
|
7354
7520
|
}
|
|
7355
7521
|
if (isList) {
|
|
7356
|
-
|
|
7357
|
-
|
|
7522
|
+
if (!Array.isArray(parsed)) {
|
|
7523
|
+
throw new VibeError14({ code: "VIBE_INTERNAL", message: "to-many relation payload must be an array", meta: { relation: node.name } });
|
|
7524
|
+
}
|
|
7525
|
+
const children = parsed.map(decodePosition);
|
|
7358
7526
|
for (const child of children) {
|
|
7359
7527
|
for (const name of hiddenOrder)
|
|
7360
7528
|
delete child[name];
|
|
@@ -7362,14 +7530,11 @@ async function attachLateralRelations(params) {
|
|
|
7362
7530
|
row[node.name] = children;
|
|
7363
7531
|
pushAll(kept, children);
|
|
7364
7532
|
} else {
|
|
7365
|
-
if (parsed === null
|
|
7533
|
+
if (parsed === null) {
|
|
7366
7534
|
row[node.name] = null;
|
|
7367
7535
|
continue;
|
|
7368
7536
|
}
|
|
7369
|
-
const child =
|
|
7370
|
-
model: node.link.target,
|
|
7371
|
-
rows: [parsed]
|
|
7372
|
-
})[0];
|
|
7537
|
+
const child = decodePosition(parsed);
|
|
7373
7538
|
for (const name of hiddenOrder)
|
|
7374
7539
|
delete child[name];
|
|
7375
7540
|
row[node.name] = child;
|
|
@@ -7378,10 +7543,25 @@ async function attachLateralRelations(params) {
|
|
|
7378
7543
|
}
|
|
7379
7544
|
await finishNode({ ctx, node, kept });
|
|
7380
7545
|
}
|
|
7381
|
-
|
|
7546
|
+
if (selection.counts.length === 0)
|
|
7547
|
+
return;
|
|
7548
|
+
for (const row of rows) {
|
|
7549
|
+
const counts = {};
|
|
7550
|
+
for (let index = 0;index < selection.counts.length; index++) {
|
|
7551
|
+
const column = lateralCountAlias({ index });
|
|
7552
|
+
const raw = row[column];
|
|
7553
|
+
const count = typeof raw === "number" || typeof raw === "bigint" || typeof raw === "string" && /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
7554
|
+
if (!Number.isSafeInteger(count) || count < 0) {
|
|
7555
|
+
throw new VibeError14({ code: "VIBE_INTERNAL", message: "relation count payload must be a non-negative safe integer", meta: { relation: selection.counts[index]?.name } });
|
|
7556
|
+
}
|
|
7557
|
+
counts[selection.counts[index].name] = count;
|
|
7558
|
+
delete row[column];
|
|
7559
|
+
}
|
|
7560
|
+
row._count = counts;
|
|
7561
|
+
}
|
|
7382
7562
|
}
|
|
7383
7563
|
// packages/runtime/src/extensions.ts
|
|
7384
|
-
import { VibeError as
|
|
7564
|
+
import { VibeError as VibeError15, extensionManifestEntry } from "@vibeorm/schema";
|
|
7385
7565
|
function assertExtensionManifest(params) {
|
|
7386
7566
|
const { manifest } = params;
|
|
7387
7567
|
const instances = params.instances ?? [];
|
|
@@ -7404,7 +7584,7 @@ function assertExtensionManifest(params) {
|
|
|
7404
7584
|
problems.push(`"${name}" is configured but missing from the generated client`);
|
|
7405
7585
|
}
|
|
7406
7586
|
if (problems.length > 0) {
|
|
7407
|
-
throw new
|
|
7587
|
+
throw new VibeError15({
|
|
7408
7588
|
code: "VIBE_STALE_CLIENT",
|
|
7409
7589
|
message: `extension list changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
|
|
7410
7590
|
meta: { manifest, problems }
|
|
@@ -7419,7 +7599,7 @@ function buildExtensionOperatorMap(params) {
|
|
|
7419
7599
|
for (const operator of extension.sql?.whereOperators ?? []) {
|
|
7420
7600
|
const existing = owner.get(operator.name);
|
|
7421
7601
|
if (existing !== undefined) {
|
|
7422
|
-
throw new
|
|
7602
|
+
throw new VibeError15({
|
|
7423
7603
|
code: "VIBE_CONFIG",
|
|
7424
7604
|
message: `where operator "${operator.name}" is claimed by both "${existing}" and "${extension.name}"`,
|
|
7425
7605
|
meta: { operator: operator.name }
|
|
@@ -7435,7 +7615,7 @@ function buildExtensionOperatorMap(params) {
|
|
|
7435
7615
|
options: extension.options
|
|
7436
7616
|
});
|
|
7437
7617
|
if ("error" in compiled) {
|
|
7438
|
-
throw new
|
|
7618
|
+
throw new VibeError15({
|
|
7439
7619
|
code: "VIBE_VALIDATION",
|
|
7440
7620
|
message: `"${operator.name}" on ${model}.${field}: ${compiled.error}`,
|
|
7441
7621
|
meta: { model, field, operator: operator.name, extension: extension.name }
|
|
@@ -7467,9 +7647,9 @@ function buildConnectHooks(params) {
|
|
|
7467
7647
|
return hooks;
|
|
7468
7648
|
}
|
|
7469
7649
|
// packages/runtime/src/views.ts
|
|
7470
|
-
import { VibeError as
|
|
7650
|
+
import { VibeError as VibeError16 } from "@vibeorm/schema";
|
|
7471
7651
|
function validationError5(params) {
|
|
7472
|
-
return new
|
|
7652
|
+
return new VibeError16({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
7473
7653
|
}
|
|
7474
7654
|
|
|
7475
7655
|
class ViewResult {
|
|
@@ -7518,12 +7698,12 @@ class ViewResult {
|
|
|
7518
7698
|
return out;
|
|
7519
7699
|
}
|
|
7520
7700
|
}
|
|
7521
|
-
function
|
|
7701
|
+
function isPlainObject5(value) {
|
|
7522
7702
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7523
7703
|
}
|
|
7524
7704
|
function resolveDefinition(params) {
|
|
7525
7705
|
const { meta, name, definition } = params;
|
|
7526
|
-
if (!
|
|
7706
|
+
if (!isPlainObject5(definition)) {
|
|
7527
7707
|
throw validationError5({
|
|
7528
7708
|
message: `view "${name}": definition must be an object of { modelKey: { field: true } }`,
|
|
7529
7709
|
meta: { view: name }
|
|
@@ -7540,7 +7720,7 @@ function resolveDefinition(params) {
|
|
|
7540
7720
|
meta: { view: name, model: key }
|
|
7541
7721
|
});
|
|
7542
7722
|
}
|
|
7543
|
-
if (!
|
|
7723
|
+
if (!isPlainObject5(fieldSpec)) {
|
|
7544
7724
|
throw validationError5({
|
|
7545
7725
|
message: `view "${name}": definition.${key} must be an object of { field: true }`,
|
|
7546
7726
|
meta: { view: name, model: model.name }
|
|
@@ -7595,7 +7775,7 @@ function revealSet(params) {
|
|
|
7595
7775
|
const revealed = new Set;
|
|
7596
7776
|
if (reveal === undefined)
|
|
7597
7777
|
return revealed;
|
|
7598
|
-
if (!
|
|
7778
|
+
if (!isPlainObject5(reveal)) {
|
|
7599
7779
|
throw validationError5({
|
|
7600
7780
|
message: `view "${name}": reveal must be an object of { field: true }`,
|
|
7601
7781
|
meta: { view: name, model: mask.model.name }
|
|
@@ -7633,13 +7813,13 @@ function mergeLevel(params) {
|
|
|
7633
7813
|
const revealed = revealSet({ name, mask, reveal: args.reveal });
|
|
7634
7814
|
let scalars;
|
|
7635
7815
|
const userSelect = args.select;
|
|
7636
|
-
if (userSelect !== undefined && !
|
|
7816
|
+
if (userSelect !== undefined && !isPlainObject5(userSelect)) {
|
|
7637
7817
|
throw validationError5({
|
|
7638
7818
|
message: `view "${name}": select must be an object`,
|
|
7639
7819
|
meta: { view: name, model: model.name }
|
|
7640
7820
|
});
|
|
7641
7821
|
}
|
|
7642
|
-
if (
|
|
7822
|
+
if (isPlainObject5(userSelect)) {
|
|
7643
7823
|
scalars = new Set;
|
|
7644
7824
|
for (const [key, flag] of Object.entries(userSelect)) {
|
|
7645
7825
|
if (flag === false || flag === undefined)
|
|
@@ -7657,7 +7837,7 @@ function mergeLevel(params) {
|
|
|
7657
7837
|
const collectRelations = (source, argName) => {
|
|
7658
7838
|
if (source === undefined)
|
|
7659
7839
|
return;
|
|
7660
|
-
if (!
|
|
7840
|
+
if (!isPlainObject5(source)) {
|
|
7661
7841
|
throw validationError5({
|
|
7662
7842
|
message: `view "${name}": ${argName} must be an object`,
|
|
7663
7843
|
meta: { view: name, model: model.name }
|
|
@@ -7705,7 +7885,7 @@ function mergeLevel(params) {
|
|
|
7705
7885
|
relationPlans.set(relationName, child2.plan);
|
|
7706
7886
|
continue;
|
|
7707
7887
|
}
|
|
7708
|
-
if (!
|
|
7888
|
+
if (!isPlainObject5(spec)) {
|
|
7709
7889
|
throw validationError5({
|
|
7710
7890
|
message: `view "${name}": include.${relationName} must be true or an object`,
|
|
7711
7891
|
meta: { view: name, model: model.name, relation: relationName }
|
|
@@ -7743,7 +7923,7 @@ function wrapRows(params) {
|
|
|
7743
7923
|
const value = record[relationName];
|
|
7744
7924
|
if (Array.isArray(value)) {
|
|
7745
7925
|
record[relationName] = wrapRows({ rows: value, plan: childPlan });
|
|
7746
|
-
} else if (
|
|
7926
|
+
} else if (isPlainObject5(value)) {
|
|
7747
7927
|
record[relationName] = wrapRows({ rows: [value], plan: childPlan })[0];
|
|
7748
7928
|
}
|
|
7749
7929
|
}
|
|
@@ -7768,7 +7948,7 @@ function createViewDelegate(params) {
|
|
|
7768
7948
|
const { meta, name, definition, mask, delegate } = params;
|
|
7769
7949
|
const merge = (args) => {
|
|
7770
7950
|
const raw = args ?? {};
|
|
7771
|
-
if (!
|
|
7951
|
+
if (!isPlainObject5(raw)) {
|
|
7772
7952
|
throw validationError5({
|
|
7773
7953
|
message: `view "${name}": query args must be an object`,
|
|
7774
7954
|
meta: { view: name, model: mask.model.name }
|
|
@@ -7823,7 +8003,7 @@ function createView(params) {
|
|
|
7823
8003
|
for (const [clientName, mask] of resolved) {
|
|
7824
8004
|
const delegate = models[clientName];
|
|
7825
8005
|
if (delegate === undefined) {
|
|
7826
|
-
throw new
|
|
8006
|
+
throw new VibeError16({
|
|
7827
8007
|
code: "VIBE_INTERNAL",
|
|
7828
8008
|
message: `view "${name}": no delegate for model "${mask.model.name}"`,
|
|
7829
8009
|
meta: { view: name, model: mask.model.name }
|
|
@@ -7839,9 +8019,9 @@ function attachDefineView(params) {
|
|
|
7839
8019
|
Object.defineProperty(client, "$defineView", { value, enumerable: true });
|
|
7840
8020
|
}
|
|
7841
8021
|
// packages/runtime/src/validators.ts
|
|
7842
|
-
import { VibeError as
|
|
8022
|
+
import { VibeError as VibeError17 } from "@vibeorm/schema";
|
|
7843
8023
|
function validationFailed(params) {
|
|
7844
|
-
return new
|
|
8024
|
+
return new VibeError17({
|
|
7845
8025
|
code: "VIBE_VALIDATION",
|
|
7846
8026
|
message: `validation failed for ${params.model}.${params.field}: ${params.issues[0]?.message ?? "invalid value"}`,
|
|
7847
8027
|
meta: { model: params.model, field: params.field, issues: params.issues }
|
|
@@ -7850,7 +8030,7 @@ function validationFailed(params) {
|
|
|
7850
8030
|
function validateValue(params) {
|
|
7851
8031
|
const result = params.schema["~standard"].validate(params.value);
|
|
7852
8032
|
if (result instanceof Promise) {
|
|
7853
|
-
throw new
|
|
8033
|
+
throw new VibeError17({
|
|
7854
8034
|
code: "VIBE_VALIDATION",
|
|
7855
8035
|
message: `async validator on ${params.model}.${params.field} \u2014 write validators must be synchronous (validate before calling the client for async rules)`,
|
|
7856
8036
|
meta: { model: params.model, field: params.field }
|
|
@@ -7862,12 +8042,12 @@ function validateValue(params) {
|
|
|
7862
8042
|
return result.value;
|
|
7863
8043
|
}
|
|
7864
8044
|
var ATOMIC_OPERATORS = ["set", "increment", "decrement", "multiply", "divide", "push"];
|
|
7865
|
-
function
|
|
8045
|
+
function isPlainObject6(value) {
|
|
7866
8046
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7867
8047
|
}
|
|
7868
8048
|
function operatorOf(params) {
|
|
7869
8049
|
const { model, field, value } = params;
|
|
7870
|
-
if (!
|
|
8050
|
+
if (!isPlainObject6(value))
|
|
7871
8051
|
return;
|
|
7872
8052
|
const meta = model.fieldByName.get(field);
|
|
7873
8053
|
if (meta === undefined || meta.scalarType === "Json")
|
|
@@ -7911,10 +8091,10 @@ function validateWriteData(params) {
|
|
|
7911
8091
|
}
|
|
7912
8092
|
function validateNestedVerbs(params) {
|
|
7913
8093
|
const { meta, target, verbs, validators } = params;
|
|
7914
|
-
const one = (row) =>
|
|
8094
|
+
const one = (row) => isPlainObject6(row) ? validateWriteData({ meta, model: target, data: row, validators }) : row;
|
|
7915
8095
|
const many = (value) => Array.isArray(value) ? value.map(one) : one(value);
|
|
7916
8096
|
const body = (value, arms) => {
|
|
7917
|
-
if (!
|
|
8097
|
+
if (!isPlainObject6(value))
|
|
7918
8098
|
return value;
|
|
7919
8099
|
if (!arms.some((arm) => value[arm] !== undefined))
|
|
7920
8100
|
return one(value);
|
|
@@ -7930,11 +8110,11 @@ function validateNestedVerbs(params) {
|
|
|
7930
8110
|
if (verbs.create !== undefined)
|
|
7931
8111
|
next.create = many(verbs.create);
|
|
7932
8112
|
if (verbs.createMany !== undefined) {
|
|
7933
|
-
next.createMany =
|
|
8113
|
+
next.createMany = isPlainObject6(verbs.createMany) ? { ...verbs.createMany, data: many(verbs.createMany.data) } : many(verbs.createMany);
|
|
7934
8114
|
}
|
|
7935
8115
|
if (verbs.connectOrCreate !== undefined) {
|
|
7936
8116
|
const entries = Array.isArray(verbs.connectOrCreate) ? verbs.connectOrCreate : [verbs.connectOrCreate];
|
|
7937
|
-
const mapped = entries.map((entry) =>
|
|
8117
|
+
const mapped = entries.map((entry) => isPlainObject6(entry) ? { ...entry, create: many(entry.create) } : entry);
|
|
7938
8118
|
next.connectOrCreate = Array.isArray(verbs.connectOrCreate) ? mapped : mapped[0];
|
|
7939
8119
|
}
|
|
7940
8120
|
if (verbs.update !== undefined)
|
|
@@ -7973,14 +8153,14 @@ function deferPolicyOperation(params) {
|
|
|
7973
8153
|
}
|
|
7974
8154
|
|
|
7975
8155
|
// packages/runtime/src/policy.ts
|
|
7976
|
-
import { VibeError as
|
|
8156
|
+
import { VibeError as VibeError18 } from "@vibeorm/schema";
|
|
7977
8157
|
function policyError(message, meta) {
|
|
7978
|
-
return new
|
|
8158
|
+
return new VibeError18({ code: "VIBE_VALIDATION", message, meta: meta ?? {} });
|
|
7979
8159
|
}
|
|
7980
8160
|
function policiedModels(meta) {
|
|
7981
8161
|
return meta.models.filter((model) => model.policy !== undefined);
|
|
7982
8162
|
}
|
|
7983
|
-
function
|
|
8163
|
+
function isPlainObject7(value) {
|
|
7984
8164
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7985
8165
|
}
|
|
7986
8166
|
function deferred(build) {
|
|
@@ -8016,7 +8196,7 @@ function uniqueToFilter(params) {
|
|
|
8016
8196
|
}
|
|
8017
8197
|
function scopeWhere(params) {
|
|
8018
8198
|
const { meta, model, where, context } = params;
|
|
8019
|
-
if (!
|
|
8199
|
+
if (!isPlainObject7(where))
|
|
8020
8200
|
return where;
|
|
8021
8201
|
const out = {};
|
|
8022
8202
|
const extra = [];
|
|
@@ -8036,10 +8216,10 @@ function scopeWhere(params) {
|
|
|
8036
8216
|
}
|
|
8037
8217
|
const scoped = target.policy !== undefined;
|
|
8038
8218
|
const pred = () => predicateFor({ model: target, context });
|
|
8039
|
-
const walkChild = (child) =>
|
|
8219
|
+
const walkChild = (child) => isPlainObject7(child) ? scopeWhere({ meta, model: target, where: child, context }) : child;
|
|
8040
8220
|
const parts = [];
|
|
8041
8221
|
if (relation.isList) {
|
|
8042
|
-
if (!
|
|
8222
|
+
if (!isPlainObject7(value)) {
|
|
8043
8223
|
out[key] = value;
|
|
8044
8224
|
continue;
|
|
8045
8225
|
}
|
|
@@ -8060,7 +8240,7 @@ function scopeWhere(params) {
|
|
|
8060
8240
|
const arms = [];
|
|
8061
8241
|
if (value === null) {
|
|
8062
8242
|
arms.push({ arm: "is", operand: null });
|
|
8063
|
-
} else if (
|
|
8243
|
+
} else if (isPlainObject7(value) && (("is" in value) || ("isNot" in value))) {
|
|
8064
8244
|
let mixed = false;
|
|
8065
8245
|
for (const [op, operand] of Object.entries(value)) {
|
|
8066
8246
|
if (operand === undefined)
|
|
@@ -8074,7 +8254,7 @@ function scopeWhere(params) {
|
|
|
8074
8254
|
out[key] = value;
|
|
8075
8255
|
continue;
|
|
8076
8256
|
}
|
|
8077
|
-
} else if (
|
|
8257
|
+
} else if (isPlainObject7(value)) {
|
|
8078
8258
|
arms.push({ arm: "is", operand: value });
|
|
8079
8259
|
} else {
|
|
8080
8260
|
out[key] = value;
|
|
@@ -8112,7 +8292,7 @@ function scopeWhere(params) {
|
|
|
8112
8292
|
function assertPolicyOrder(params) {
|
|
8113
8293
|
const { meta, model, orderBy } = params;
|
|
8114
8294
|
for (const term of Array.isArray(orderBy) ? orderBy : [orderBy]) {
|
|
8115
|
-
if (!
|
|
8295
|
+
if (!isPlainObject7(term))
|
|
8116
8296
|
continue;
|
|
8117
8297
|
for (const [name, spec] of Object.entries(term)) {
|
|
8118
8298
|
if (spec === undefined)
|
|
@@ -8134,18 +8314,18 @@ function scopeSelection(params) {
|
|
|
8134
8314
|
assertPolicyOrder({ meta, model, orderBy: args.orderBy });
|
|
8135
8315
|
for (const key of ["include", "select"]) {
|
|
8136
8316
|
const block = args[key];
|
|
8137
|
-
if (!
|
|
8317
|
+
if (!isPlainObject7(block))
|
|
8138
8318
|
continue;
|
|
8139
8319
|
const next = { ...block };
|
|
8140
8320
|
for (const [name, spec] of Object.entries(block)) {
|
|
8141
8321
|
if (spec === undefined || spec === false)
|
|
8142
8322
|
continue;
|
|
8143
8323
|
if (name === "_count") {
|
|
8144
|
-
const names = spec === true ? model.relations.filter((relation2) => relation2.isList).map((relation2) => relation2.name) :
|
|
8324
|
+
const names = spec === true ? model.relations.filter((relation2) => relation2.isList).map((relation2) => relation2.name) : isPlainObject7(spec) && isPlainObject7(spec.select) ? Object.keys(spec.select).filter((key2) => {
|
|
8145
8325
|
const entry = spec.select[key2];
|
|
8146
|
-
return entry === true ||
|
|
8326
|
+
return entry === true || isPlainObject7(entry);
|
|
8147
8327
|
}) : [];
|
|
8148
|
-
const countSelect =
|
|
8328
|
+
const countSelect = isPlainObject7(spec) && isPlainObject7(spec.select) ? { ...spec.select } : undefined;
|
|
8149
8329
|
for (const counted of names) {
|
|
8150
8330
|
const relation2 = model.relationByName.get(counted);
|
|
8151
8331
|
const target2 = relation2 === undefined ? undefined : meta.modelByName.get(relation2.targetModel);
|
|
@@ -8153,7 +8333,7 @@ function scopeSelection(params) {
|
|
|
8153
8333
|
throw policyError(`_count of ${model.name}.${counted} cannot enforce @@policy because relation counts take no filter \u2014 use a scoped child count query instead`, { model: model.name, relation: counted });
|
|
8154
8334
|
}
|
|
8155
8335
|
const entry = countSelect?.[counted];
|
|
8156
|
-
if (target2 !== undefined && countSelect !== undefined &&
|
|
8336
|
+
if (target2 !== undefined && countSelect !== undefined && isPlainObject7(entry) && entry.where !== undefined) {
|
|
8157
8337
|
countSelect[counted] = { ...entry, where: scopeWhere({ meta, model: target2, where: entry.where, context }) };
|
|
8158
8338
|
}
|
|
8159
8339
|
}
|
|
@@ -8195,7 +8375,7 @@ function forceWriteData(params) {
|
|
|
8195
8375
|
if (model.policy !== undefined) {
|
|
8196
8376
|
const value = predicateFor({ model, context })[model.policy.field];
|
|
8197
8377
|
const given = data[model.policy.field];
|
|
8198
|
-
const explicit =
|
|
8378
|
+
const explicit = isPlainObject7(given) && "set" in given ? given.set : given;
|
|
8199
8379
|
if (explicit !== undefined && explicit !== value) {
|
|
8200
8380
|
throw policyError(`${phase} on ${model.name}: data.${model.policy.field} conflicts with the bound policy context`, { model: model.name, field: model.policy.field, phase });
|
|
8201
8381
|
}
|
|
@@ -8213,7 +8393,7 @@ function forceWriteData(params) {
|
|
|
8213
8393
|
}
|
|
8214
8394
|
for (const [name, spec] of Object.entries(data)) {
|
|
8215
8395
|
const relation = model.relationByName.get(name);
|
|
8216
|
-
if (relation === undefined || !
|
|
8396
|
+
if (relation === undefined || !isPlainObject7(spec))
|
|
8217
8397
|
continue;
|
|
8218
8398
|
const target = meta.modelByName.get(relation.targetModel);
|
|
8219
8399
|
if (target === undefined)
|
|
@@ -8245,10 +8425,10 @@ function forceWriteData(params) {
|
|
|
8245
8425
|
}
|
|
8246
8426
|
function forceNestedVerbs(params) {
|
|
8247
8427
|
const { meta, target, verbs, context } = params;
|
|
8248
|
-
const walk2 = (row, phase) =>
|
|
8428
|
+
const walk2 = (row, phase) => isPlainObject7(row) ? forceWriteData({ meta, model: target, data: row, context, phase }) : row;
|
|
8249
8429
|
const each = (value, phase) => Array.isArray(value) ? value.map((row) => walk2(row, phase)) : walk2(value, phase);
|
|
8250
8430
|
const body = (value, keys) => {
|
|
8251
|
-
if (!
|
|
8431
|
+
if (!isPlainObject7(value))
|
|
8252
8432
|
return value;
|
|
8253
8433
|
const hasArm = keys.some((key) => value[key] !== undefined);
|
|
8254
8434
|
if (!hasArm)
|
|
@@ -8264,12 +8444,12 @@ function forceNestedVerbs(params) {
|
|
|
8264
8444
|
const out = { ...verbs };
|
|
8265
8445
|
if (verbs.create !== undefined)
|
|
8266
8446
|
out.create = each(verbs.create, "create");
|
|
8267
|
-
if (verbs.createMany !== undefined &&
|
|
8447
|
+
if (verbs.createMany !== undefined && isPlainObject7(verbs.createMany)) {
|
|
8268
8448
|
out.createMany = { ...verbs.createMany, data: each(verbs.createMany.data, "create") };
|
|
8269
8449
|
}
|
|
8270
8450
|
if (verbs.connectOrCreate !== undefined) {
|
|
8271
8451
|
const entries = Array.isArray(verbs.connectOrCreate) ? verbs.connectOrCreate : [verbs.connectOrCreate];
|
|
8272
|
-
const mapped = entries.map((entry) =>
|
|
8452
|
+
const mapped = entries.map((entry) => isPlainObject7(entry) ? { ...entry, create: each(entry.create, "create") } : entry);
|
|
8273
8453
|
out.connectOrCreate = Array.isArray(verbs.connectOrCreate) ? mapped : mapped[0];
|
|
8274
8454
|
}
|
|
8275
8455
|
if (verbs.update !== undefined) {
|
|
@@ -8319,7 +8499,7 @@ function scopedDelegate(params) {
|
|
|
8319
8499
|
const pkWhere = pk.length === 1 ? { [pk[0]]: target[pk[0]] } : { [pkClientKey()]: Object.fromEntries(pk.map((name) => [name, target[name]])) };
|
|
8320
8500
|
return write(raw, pkWhere);
|
|
8321
8501
|
};
|
|
8322
|
-
const notFound2 = (method) => new
|
|
8502
|
+
const notFound2 = (method) => new VibeError18({ code: "VIBE_NOT_FOUND", message: `${method} on ${model.name}: no row matches the unique where inside the bound policy context`, meta: { model: model.name, method } });
|
|
8323
8503
|
return {
|
|
8324
8504
|
findMany: (args) => base.findMany(scopeFind(args)),
|
|
8325
8505
|
findFirst: (args) => base.findFirst(scopeFind(args)),
|
|
@@ -8353,7 +8533,7 @@ function scopedDelegate(params) {
|
|
|
8353
8533
|
})
|
|
8354
8534
|
})),
|
|
8355
8535
|
upsertMany: () => {
|
|
8356
|
-
throw new
|
|
8536
|
+
throw new VibeError18({
|
|
8357
8537
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
8358
8538
|
message: "upsertMany is not available on a policy-bound client: its conflict target is not policy-checked yet",
|
|
8359
8539
|
meta: { model: model.name }
|
|
@@ -8362,7 +8542,7 @@ function scopedDelegate(params) {
|
|
|
8362
8542
|
updateManyAndReturn: (args) => deferred(() => {
|
|
8363
8543
|
const run = base.updateManyAndReturn;
|
|
8364
8544
|
if (run === undefined) {
|
|
8365
|
-
throw new
|
|
8545
|
+
throw new VibeError18({
|
|
8366
8546
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
8367
8547
|
message: "updateManyAndReturn is not available on this delegate",
|
|
8368
8548
|
meta: { model: model.name }
|
|
@@ -8433,7 +8613,7 @@ function globalDelegate(params) {
|
|
|
8433
8613
|
updateManyAndReturn: (args) => deferred(() => {
|
|
8434
8614
|
const run = base.updateManyAndReturn;
|
|
8435
8615
|
if (run === undefined) {
|
|
8436
|
-
throw new
|
|
8616
|
+
throw new VibeError18({
|
|
8437
8617
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
8438
8618
|
message: "updateManyAndReturn is not available on this delegate"
|
|
8439
8619
|
});
|
|
@@ -8444,7 +8624,7 @@ function globalDelegate(params) {
|
|
|
8444
8624
|
upsertMany: (args) => deferred(() => {
|
|
8445
8625
|
if (params.policyBound === false)
|
|
8446
8626
|
return base.upsertMany(args);
|
|
8447
|
-
throw new
|
|
8627
|
+
throw new VibeError18({
|
|
8448
8628
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
8449
8629
|
message: "upsertMany is not available on a policy-bound client: its conflict target is not policy-checked yet"
|
|
8450
8630
|
});
|
|
@@ -8491,7 +8671,7 @@ function attachPolicy(params) {
|
|
|
8491
8671
|
const guardValue = (value, key) => {
|
|
8492
8672
|
if (typeof value === "function")
|
|
8493
8673
|
return guard(key);
|
|
8494
|
-
if (!
|
|
8674
|
+
if (!isPlainObject7(value))
|
|
8495
8675
|
return value;
|
|
8496
8676
|
const namespace = {};
|
|
8497
8677
|
for (const inner of Object.keys(value)) {
|
|
@@ -8599,11 +8779,11 @@ function attachPolicy(params) {
|
|
|
8599
8779
|
});
|
|
8600
8780
|
}
|
|
8601
8781
|
// packages/runtime/src/scoped.ts
|
|
8602
|
-
import { ScopedQueryError, VibeError as
|
|
8782
|
+
import { ScopedQueryError, VibeError as VibeError19 } from "@vibeorm/schema";
|
|
8603
8783
|
function refusal(params) {
|
|
8604
8784
|
return new ScopedQueryError(params);
|
|
8605
8785
|
}
|
|
8606
|
-
function
|
|
8786
|
+
function isPlainObject8(value) {
|
|
8607
8787
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
8608
8788
|
}
|
|
8609
8789
|
var SCOPEABLE_COLUMN_TYPES = ["String", "Int", "BigInt"];
|
|
@@ -8649,7 +8829,7 @@ function resolveScopedModels(params) {
|
|
|
8649
8829
|
resolved.set(model.name, { kind: "none" });
|
|
8650
8830
|
continue;
|
|
8651
8831
|
}
|
|
8652
|
-
if (!
|
|
8832
|
+
if (!isPlainObject8(entry)) {
|
|
8653
8833
|
throw refusal({
|
|
8654
8834
|
model: model.name,
|
|
8655
8835
|
path: "models",
|
|
@@ -8797,7 +8977,7 @@ function mergeWhere2(params) {
|
|
|
8797
8977
|
}
|
|
8798
8978
|
function injectUniqueWhere(params) {
|
|
8799
8979
|
const { where, pred } = params;
|
|
8800
|
-
if (!
|
|
8980
|
+
if (!isPlainObject8(where))
|
|
8801
8981
|
return { AND: [pred] };
|
|
8802
8982
|
const existing = where.AND === undefined ? [] : Array.isArray(where.AND) ? where.AND : [where.AND];
|
|
8803
8983
|
return { ...where, AND: [...existing, pred] };
|
|
@@ -8808,7 +8988,7 @@ function flattenUniqueWhere(params) {
|
|
|
8808
8988
|
return where;
|
|
8809
8989
|
const out = {};
|
|
8810
8990
|
for (const [key, value] of Object.entries(where)) {
|
|
8811
|
-
if (
|
|
8991
|
+
if (isPlainObject8(value) && model.compoundUniques.has(key))
|
|
8812
8992
|
Object.assign(out, value);
|
|
8813
8993
|
else
|
|
8814
8994
|
out[key] = value;
|
|
@@ -8828,7 +9008,7 @@ function walkWhere(params) {
|
|
|
8828
9008
|
const out = {};
|
|
8829
9009
|
const extra = [];
|
|
8830
9010
|
const walkGroup = (value, key) => {
|
|
8831
|
-
const walkItem = (item) =>
|
|
9011
|
+
const walkItem = (item) => isPlainObject8(item) ? walkWhere({ ctx, model, where: item, path: `${path}.${key}` }) : item;
|
|
8832
9012
|
return Array.isArray(value) ? value.map(walkItem) : walkItem(value);
|
|
8833
9013
|
};
|
|
8834
9014
|
for (const [key, value] of Object.entries(where)) {
|
|
@@ -8848,10 +9028,10 @@ function walkWhere(params) {
|
|
|
8848
9028
|
const scoped = targetEntry.kind !== "none";
|
|
8849
9029
|
const childPath = `${path}.${key}`;
|
|
8850
9030
|
const pred = () => requirePred({ ctx, model: target, path: childPath });
|
|
8851
|
-
const walkChild = (child, op) =>
|
|
9031
|
+
const walkChild = (child, op) => isPlainObject8(child) ? walkWhere({ ctx, model: target, where: child, path: `${childPath}.${op}` }) : child;
|
|
8852
9032
|
const parts = [];
|
|
8853
9033
|
if (relation.isList) {
|
|
8854
|
-
if (!
|
|
9034
|
+
if (!isPlainObject8(value)) {
|
|
8855
9035
|
out[key] = value;
|
|
8856
9036
|
continue;
|
|
8857
9037
|
}
|
|
@@ -8872,7 +9052,7 @@ function walkWhere(params) {
|
|
|
8872
9052
|
const arms = [];
|
|
8873
9053
|
if (value === null) {
|
|
8874
9054
|
arms.push({ arm: "is", operand: null });
|
|
8875
|
-
} else if (
|
|
9055
|
+
} else if (isPlainObject8(value) && (("is" in value) || ("isNot" in value))) {
|
|
8876
9056
|
let mixed = false;
|
|
8877
9057
|
for (const [op, operand] of Object.entries(value)) {
|
|
8878
9058
|
if (operand === undefined)
|
|
@@ -8886,7 +9066,7 @@ function walkWhere(params) {
|
|
|
8886
9066
|
out[key] = value;
|
|
8887
9067
|
continue;
|
|
8888
9068
|
}
|
|
8889
|
-
} else if (
|
|
9069
|
+
} else if (isPlainObject8(value)) {
|
|
8890
9070
|
arms.push({ arm: "is", operand: value });
|
|
8891
9071
|
} else {
|
|
8892
9072
|
out[key] = value;
|
|
@@ -8941,7 +9121,7 @@ function walkOrderTerm(params) {
|
|
|
8941
9121
|
message: `$scoped: orderBy through ${model.name}.${key} reaches scoped model ${target.name} \u2014 ordering by a foreign row's values would leak; order by own columns instead`
|
|
8942
9122
|
});
|
|
8943
9123
|
}
|
|
8944
|
-
if (
|
|
9124
|
+
if (isPlainObject8(spec)) {
|
|
8945
9125
|
walkOrderTerm({ ctx, model: target, term: spec, path: `${path}.${key}` });
|
|
8946
9126
|
}
|
|
8947
9127
|
}
|
|
@@ -8950,7 +9130,7 @@ function walkOrderBy(params) {
|
|
|
8950
9130
|
const { ctx, model, orderBy, path } = params;
|
|
8951
9131
|
const terms = Array.isArray(orderBy) ? orderBy : [orderBy];
|
|
8952
9132
|
for (const term of terms) {
|
|
8953
|
-
if (
|
|
9133
|
+
if (isPlainObject8(term))
|
|
8954
9134
|
walkOrderTerm({ ctx, model, term, path });
|
|
8955
9135
|
}
|
|
8956
9136
|
}
|
|
@@ -8977,7 +9157,7 @@ function walkCountSpec(params) {
|
|
|
8977
9157
|
}
|
|
8978
9158
|
return spec;
|
|
8979
9159
|
}
|
|
8980
|
-
if (!
|
|
9160
|
+
if (!isPlainObject8(spec) || !isPlainObject8(spec.select))
|
|
8981
9161
|
return spec;
|
|
8982
9162
|
const select = { ...spec.select };
|
|
8983
9163
|
for (const [name, flag] of Object.entries(select)) {
|
|
@@ -8987,7 +9167,7 @@ function walkCountSpec(params) {
|
|
|
8987
9167
|
if (relation === undefined)
|
|
8988
9168
|
continue;
|
|
8989
9169
|
guard(relation);
|
|
8990
|
-
if (
|
|
9170
|
+
if (isPlainObject8(flag) && isPlainObject8(flag.where)) {
|
|
8991
9171
|
const target = ctx.meta.modelByName.get(relation.targetModel);
|
|
8992
9172
|
select[name] = { ...flag, where: walkWhere({ ctx, model: target, where: flag.where, path: `${path}.${name}.where` }) };
|
|
8993
9173
|
}
|
|
@@ -8998,7 +9178,7 @@ function walkSelection(params) {
|
|
|
8998
9178
|
const { ctx, model, args, path } = params;
|
|
8999
9179
|
for (const blockKey of ["include", "select"]) {
|
|
9000
9180
|
const block = args[blockKey];
|
|
9001
|
-
if (!
|
|
9181
|
+
if (!isPlainObject8(block))
|
|
9002
9182
|
continue;
|
|
9003
9183
|
const next = { ...block };
|
|
9004
9184
|
for (const [name, spec] of Object.entries(block)) {
|
|
@@ -9018,10 +9198,10 @@ function walkSelection(params) {
|
|
|
9018
9198
|
const childPath = `${blockPath}.${name}`;
|
|
9019
9199
|
if (spec === true && !scoped)
|
|
9020
9200
|
continue;
|
|
9021
|
-
const child = spec === true ? {} :
|
|
9022
|
-
if (!
|
|
9201
|
+
const child = spec === true ? {} : isPlainObject8(spec) ? { ...spec } : spec;
|
|
9202
|
+
if (!isPlainObject8(child))
|
|
9023
9203
|
continue;
|
|
9024
|
-
const walkedWhere =
|
|
9204
|
+
const walkedWhere = isPlainObject8(child.where) ? walkWhere({ ctx, model: target, where: child.where, path: `${childPath}.where` }) : child.where;
|
|
9025
9205
|
if (scoped) {
|
|
9026
9206
|
child.where = mergeWhere2({ where: walkedWhere, pred: requirePred({ ctx, model: target, path: childPath }) });
|
|
9027
9207
|
} else if (walkedWhere !== undefined) {
|
|
@@ -9068,7 +9248,7 @@ function protectScopedData(params) {
|
|
|
9068
9248
|
}
|
|
9069
9249
|
for (const [name, verbs] of Object.entries(out)) {
|
|
9070
9250
|
const relation = model.relationByName.get(name);
|
|
9071
|
-
if (relation === undefined || !
|
|
9251
|
+
if (relation === undefined || !isPlainObject8(verbs))
|
|
9072
9252
|
continue;
|
|
9073
9253
|
const target = ctx.meta.modelByName.get(relation.targetModel);
|
|
9074
9254
|
const targetEntry = entryOf(ctx, target);
|
|
@@ -9086,7 +9266,7 @@ function mapItems(value, fn) {
|
|
|
9086
9266
|
}
|
|
9087
9267
|
function walkCreateData(params) {
|
|
9088
9268
|
const { ctx, model, data, path } = params;
|
|
9089
|
-
if (!
|
|
9269
|
+
if (!isPlainObject8(data))
|
|
9090
9270
|
return data;
|
|
9091
9271
|
const entry = entryOf(ctx, model);
|
|
9092
9272
|
const out = { ...data };
|
|
@@ -9126,7 +9306,7 @@ function walkCreateData(params) {
|
|
|
9126
9306
|
}
|
|
9127
9307
|
for (const [key, value] of Object.entries(out)) {
|
|
9128
9308
|
const relation = model.relationByName.get(key);
|
|
9129
|
-
if (relation === undefined || !
|
|
9309
|
+
if (relation === undefined || !isPlainObject8(value))
|
|
9130
9310
|
continue;
|
|
9131
9311
|
out[key] = walkNestedVerbs({ ctx, model, relation, verbs: value, path: `${path}.${key}`, phase: "create" });
|
|
9132
9312
|
}
|
|
@@ -9134,14 +9314,14 @@ function walkCreateData(params) {
|
|
|
9134
9314
|
}
|
|
9135
9315
|
function walkUpdateData(params) {
|
|
9136
9316
|
const { ctx, model, data, path } = params;
|
|
9137
|
-
if (!
|
|
9317
|
+
if (!isPlainObject8(data))
|
|
9138
9318
|
return data;
|
|
9139
9319
|
const entry = entryOf(ctx, model);
|
|
9140
9320
|
const out = { ...data };
|
|
9141
9321
|
if (entry.kind === "column") {
|
|
9142
9322
|
const given = out[entry.field.name];
|
|
9143
9323
|
if (given !== undefined) {
|
|
9144
|
-
const setValue =
|
|
9324
|
+
const setValue = isPlainObject8(given) && Object.keys(given).length === 1 ? given.set : given;
|
|
9145
9325
|
if (setValue !== ctx.scope) {
|
|
9146
9326
|
throw refusal({
|
|
9147
9327
|
model: model.name,
|
|
@@ -9166,7 +9346,7 @@ function walkUpdateData(params) {
|
|
|
9166
9346
|
}
|
|
9167
9347
|
for (const [key, value] of Object.entries(out)) {
|
|
9168
9348
|
const relation = model.relationByName.get(key);
|
|
9169
|
-
if (relation === undefined || !
|
|
9349
|
+
if (relation === undefined || !isPlainObject8(value))
|
|
9170
9350
|
continue;
|
|
9171
9351
|
out[key] = walkNestedVerbs({ ctx, model, relation, verbs: value, path: `${path}.${key}`, phase: "update" });
|
|
9172
9352
|
}
|
|
@@ -9192,7 +9372,7 @@ function walkNestedVerbs(params) {
|
|
|
9192
9372
|
message: `$scoped: nested "${verb}" on ${model.name}.${relation.name} (${target.name}) ${why}`
|
|
9193
9373
|
});
|
|
9194
9374
|
};
|
|
9195
|
-
const walkTargetWhere = (where, verbPath) =>
|
|
9375
|
+
const walkTargetWhere = (where, verbPath) => isPlainObject8(where) ? walkWhere({ ctx, model: target, where, path: verbPath }) : where;
|
|
9196
9376
|
const uniqueWhere = (where, verbPath) => {
|
|
9197
9377
|
const walked = walkTargetWhere(where, verbPath);
|
|
9198
9378
|
return scoped ? injectUniqueWhere({ where: walked, pred: pred(verbPath) }) : walked;
|
|
@@ -9217,10 +9397,10 @@ function walkNestedVerbs(params) {
|
|
|
9217
9397
|
}
|
|
9218
9398
|
switch (verb) {
|
|
9219
9399
|
case "connect":
|
|
9220
|
-
out[verb] = mapItems(operand, (item) =>
|
|
9400
|
+
out[verb] = mapItems(operand, (item) => isPlainObject8(item) ? uniqueWhere(item, verbPath) : item);
|
|
9221
9401
|
break;
|
|
9222
9402
|
case "connectOrCreate":
|
|
9223
|
-
out[verb] = mapItems(operand, (item) =>
|
|
9403
|
+
out[verb] = mapItems(operand, (item) => isPlainObject8(item) ? { ...item, where: uniqueWhere(item.where, verbPath), create: walkChildCreate(item.create, verbPath) } : item);
|
|
9224
9404
|
break;
|
|
9225
9405
|
case "set": {
|
|
9226
9406
|
if (phase === "create")
|
|
@@ -9232,7 +9412,7 @@ function walkNestedVerbs(params) {
|
|
|
9232
9412
|
if (!implicitSafe)
|
|
9233
9413
|
refuse2(verb, "first detaches EVERY current child, which cannot be tenant-filtered on this edge; disconnect explicit rows instead");
|
|
9234
9414
|
}
|
|
9235
|
-
out[verb] = mapItems(operand ?? [], (item) =>
|
|
9415
|
+
out[verb] = mapItems(operand ?? [], (item) => isPlainObject8(item) ? uniqueWhere(item, verbPath) : item);
|
|
9236
9416
|
break;
|
|
9237
9417
|
}
|
|
9238
9418
|
case "disconnect": {
|
|
@@ -9249,7 +9429,7 @@ function walkNestedVerbs(params) {
|
|
|
9249
9429
|
}
|
|
9250
9430
|
break;
|
|
9251
9431
|
}
|
|
9252
|
-
out[verb] = mapItems(operand, (item) =>
|
|
9432
|
+
out[verb] = mapItems(operand, (item) => isPlainObject8(item) ? uniqueWhere(item, verbPath) : item);
|
|
9253
9433
|
break;
|
|
9254
9434
|
}
|
|
9255
9435
|
case "delete": {
|
|
@@ -9259,7 +9439,7 @@ function walkNestedVerbs(params) {
|
|
|
9259
9439
|
}
|
|
9260
9440
|
break;
|
|
9261
9441
|
}
|
|
9262
|
-
out[verb] = mapItems(operand, (item) =>
|
|
9442
|
+
out[verb] = mapItems(operand, (item) => isPlainObject8(item) ? uniqueWhere(item, verbPath) : item);
|
|
9263
9443
|
break;
|
|
9264
9444
|
}
|
|
9265
9445
|
case "update": {
|
|
@@ -9267,14 +9447,14 @@ function walkNestedVerbs(params) {
|
|
|
9267
9447
|
if (scoped && !implicitSafe) {
|
|
9268
9448
|
refuse2(verb, "targets whatever row is currently connected \u2014 not tenant-verifiable on this edge; update it through its own delegate");
|
|
9269
9449
|
}
|
|
9270
|
-
out[verb] =
|
|
9450
|
+
out[verb] = isPlainObject8(operand) ? "data" in operand && isPlainObject8(operand.data) ? { ...operand, data: walkChildUpdate(operand.data, verbPath) } : walkChildUpdate(operand, verbPath) : operand;
|
|
9271
9451
|
break;
|
|
9272
9452
|
}
|
|
9273
|
-
out[verb] = mapItems(operand, (item) =>
|
|
9453
|
+
out[verb] = mapItems(operand, (item) => isPlainObject8(item) ? { ...item, where: uniqueWhere(item.where, verbPath), data: walkChildUpdate(item.data, verbPath) } : item);
|
|
9274
9454
|
break;
|
|
9275
9455
|
}
|
|
9276
9456
|
case "updateMany":
|
|
9277
|
-
out[verb] = mapItems(operand, (item) =>
|
|
9457
|
+
out[verb] = mapItems(operand, (item) => isPlainObject8(item) ? { ...item, where: filterWhere(item.where, verbPath), data: walkChildUpdate(item.data, verbPath) } : item);
|
|
9278
9458
|
break;
|
|
9279
9459
|
case "deleteMany":
|
|
9280
9460
|
out[verb] = mapItems(operand, (item) => filterWhere(item, verbPath));
|
|
@@ -9284,14 +9464,14 @@ function walkNestedVerbs(params) {
|
|
|
9284
9464
|
if (scoped && !implicitSafe) {
|
|
9285
9465
|
refuse2(verb, "updates whatever row is currently connected \u2014 not tenant-verifiable on this edge");
|
|
9286
9466
|
}
|
|
9287
|
-
out[verb] =
|
|
9467
|
+
out[verb] = isPlainObject8(operand) ? {
|
|
9288
9468
|
...operand,
|
|
9289
9469
|
create: walkChildCreate(operand.create, verbPath),
|
|
9290
9470
|
update: walkChildUpdate(operand.update, verbPath)
|
|
9291
9471
|
} : operand;
|
|
9292
9472
|
break;
|
|
9293
9473
|
}
|
|
9294
|
-
out[verb] = mapItems(operand, (item) =>
|
|
9474
|
+
out[verb] = mapItems(operand, (item) => isPlainObject8(item) ? {
|
|
9295
9475
|
...item,
|
|
9296
9476
|
where: uniqueWhere(item.where, verbPath),
|
|
9297
9477
|
create: walkChildCreate(item.create, verbPath),
|
|
@@ -9333,7 +9513,7 @@ function rewriteScopedCall(params) {
|
|
|
9333
9513
|
const root = `${model.clientName}.${method}`;
|
|
9334
9514
|
const args = { ...params.args ?? {} };
|
|
9335
9515
|
const ownPred = () => requirePred({ ctx, model, path: root });
|
|
9336
|
-
const walkOwnWhere = (where, path) =>
|
|
9516
|
+
const walkOwnWhere = (where, path) => isPlainObject8(where) ? walkWhere({ ctx, model, where, path }) : where;
|
|
9337
9517
|
if (FIND_LIKE.includes(method)) {
|
|
9338
9518
|
const walked = walkOwnWhere(args.where, `${root}.where`);
|
|
9339
9519
|
if (scoped)
|
|
@@ -9355,8 +9535,8 @@ function rewriteScopedCall(params) {
|
|
|
9355
9535
|
args.where = walked2;
|
|
9356
9536
|
return { model: model.name, method, args };
|
|
9357
9537
|
}
|
|
9358
|
-
const flat =
|
|
9359
|
-
const walked =
|
|
9538
|
+
const flat = isPlainObject8(args.where) ? flattenUniqueWhere({ model, where: args.where }) : args.where;
|
|
9539
|
+
const walked = isPlainObject8(flat) ? walkWhere({ ctx, model, where: flat, path: `${root}.where` }) : flat;
|
|
9360
9540
|
args.where = mergeWhere2({ where: walked, pred: ownPred() });
|
|
9361
9541
|
return {
|
|
9362
9542
|
model: model.name,
|
|
@@ -9483,14 +9663,14 @@ function scopeExtensionProps2(params) {
|
|
|
9483
9663
|
out[key] = (args) => {
|
|
9484
9664
|
try {
|
|
9485
9665
|
const path = `${model.clientName}.${key}`;
|
|
9486
|
-
const walked =
|
|
9666
|
+
const walked = isPlainObject8(args?.where) ? walkWhere({ ctx, model, where: args.where, path: `${path}.where` }) : args?.where;
|
|
9487
9667
|
const where = entry.kind === "none" ? walked : mergeWhere2({ where: walked, pred: requirePred({ ctx, model, path }) });
|
|
9488
9668
|
return prop({ ...args ?? {}, ...where === undefined ? {} : { where } });
|
|
9489
9669
|
} catch (error) {
|
|
9490
9670
|
return Promise.reject(error);
|
|
9491
9671
|
}
|
|
9492
9672
|
};
|
|
9493
|
-
} else if (
|
|
9673
|
+
} else if (isPlainObject8(prop)) {
|
|
9494
9674
|
out[key] = scopeExtensionProps2({
|
|
9495
9675
|
ctx,
|
|
9496
9676
|
model,
|
|
@@ -9552,8 +9732,8 @@ function attachScoped(params) {
|
|
|
9552
9732
|
const { client, meta } = params;
|
|
9553
9733
|
Object.defineProperty(client, "$scoped", {
|
|
9554
9734
|
value: (config) => {
|
|
9555
|
-
if (!
|
|
9556
|
-
throw new
|
|
9735
|
+
if (!isPlainObject8(config) || !isPlainObject8(config.models)) {
|
|
9736
|
+
throw new VibeError19({
|
|
9557
9737
|
code: "VIBE_VALIDATION",
|
|
9558
9738
|
message: "$scoped takes { scope: string | number | bigint | null, models: { <Model>: \u2026 } }"
|
|
9559
9739
|
});
|
|
@@ -9569,7 +9749,7 @@ function attachScoped(params) {
|
|
|
9569
9749
|
}
|
|
9570
9750
|
const scope = config.scope;
|
|
9571
9751
|
if (scope !== null && typeof scope !== "string" && typeof scope !== "number" && typeof scope !== "bigint") {
|
|
9572
|
-
throw new
|
|
9752
|
+
throw new VibeError19({
|
|
9573
9753
|
code: "VIBE_VALIDATION",
|
|
9574
9754
|
message: `$scoped: scope must be a string, number, bigint or null \u2014 got ${typeof scope}`
|
|
9575
9755
|
});
|
|
@@ -9585,7 +9765,7 @@ function attachScoped(params) {
|
|
|
9585
9765
|
});
|
|
9586
9766
|
}
|
|
9587
9767
|
// packages/runtime/src/rls-readiness.ts
|
|
9588
|
-
import { VibeError as
|
|
9768
|
+
import { VibeError as VibeError20, assertValidSchemaIR, unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
|
|
9589
9769
|
import { NATIVE_RLS as NATIVE_RLS2, getSqlDialect as getSqlDialect4, renderRlsPolicy, renderRlsRole, NATIVE_RLS_PROBE_POLICY, renderRlsReadinessQuery, renderRlsProbeTable } from "@vibeorm/sql";
|
|
9590
9770
|
import { RLS_READINESS_SQL, NATIVE_RLS_PROBE_POLICY as NATIVE_RLS_PROBE_POLICY2 } from "@vibeorm/sql";
|
|
9591
9771
|
function textOf(params) {
|
|
@@ -9636,10 +9816,10 @@ function unprotectedTablesOf(params) {
|
|
|
9636
9816
|
function requireNativeRls(params) {
|
|
9637
9817
|
const dialect = getSqlDialect4({ dialect: params.adapter.dialect });
|
|
9638
9818
|
if (!dialect.capabilities.nativeRowLevelSecurity) {
|
|
9639
|
-
throw
|
|
9819
|
+
throw unsupportedCapability6({ dialect: dialect.name, feature: "nativeRowLevelSecurity" });
|
|
9640
9820
|
}
|
|
9641
9821
|
if (params.schema.rls === undefined) {
|
|
9642
|
-
throw new
|
|
9822
|
+
throw new VibeError20({
|
|
9643
9823
|
code: "VIBE_CONFIG",
|
|
9644
9824
|
message: "Native row-level security readiness requires a schema that declares native RLS"
|
|
9645
9825
|
});
|
|
@@ -10051,7 +10231,7 @@ async function inspectNativeRls(params) {
|
|
|
10051
10231
|
function requireEmbeddedRole(params) {
|
|
10052
10232
|
const { role } = params;
|
|
10053
10233
|
if (typeof role !== "string" || role.length === 0 || role.includes("\x00")) {
|
|
10054
|
-
throw new
|
|
10234
|
+
throw new VibeError20({
|
|
10055
10235
|
code: "VIBE_CONFIG",
|
|
10056
10236
|
message: "PGlite native row-level security requires a pre-existing restricted runtime role (nativeRls.runtimeRole) \u2014 its default embedded session is privileged and bypasses every policy",
|
|
10057
10237
|
meta: { provider: "pglite" }
|
|
@@ -10063,7 +10243,7 @@ function assertNativeRlsReady(params) {
|
|
|
10063
10243
|
const { report } = params;
|
|
10064
10244
|
if (report.ready)
|
|
10065
10245
|
return;
|
|
10066
|
-
throw new
|
|
10246
|
+
throw new VibeError20({
|
|
10067
10247
|
code: "VIBE_CONFIG",
|
|
10068
10248
|
message: `Native row-level security is not ready on this database: ${report.problems.map((problem) => `${problem.name} (${problem.status})`).join(", ")}`,
|
|
10069
10249
|
meta: {
|
|
@@ -10088,7 +10268,7 @@ function formatNativeRlsReport(params) {
|
|
|
10088
10268
|
return lines;
|
|
10089
10269
|
}
|
|
10090
10270
|
// packages/runtime/src/client.ts
|
|
10091
|
-
import { RlsContextError as RlsContextError2, VibeError as
|
|
10271
|
+
import { RlsContextError as RlsContextError2, VibeError as VibeError24, assertValidSchemaIR as assertValidSchemaIR2, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
10092
10272
|
import { ParamCollector, getSqlDialect as getSqlDialect5, renderRlsContext, renderRlsPolicy as renderRlsPolicy2, renderRlsRole as renderRlsRole2 } from "@vibeorm/sql";
|
|
10093
10273
|
|
|
10094
10274
|
// packages/runtime/src/render-cache.ts
|
|
@@ -10105,12 +10285,25 @@ var MAX_ENTRIES = 512;
|
|
|
10105
10285
|
|
|
10106
10286
|
class StatementRenderCache {
|
|
10107
10287
|
entries = new Map;
|
|
10288
|
+
get size() {
|
|
10289
|
+
return this.entries.size;
|
|
10290
|
+
}
|
|
10108
10291
|
get(key) {
|
|
10109
|
-
|
|
10292
|
+
const slot = this.entries.get(key);
|
|
10293
|
+
if (slot === undefined)
|
|
10294
|
+
return;
|
|
10295
|
+
this.entries.delete(key);
|
|
10296
|
+
this.entries.set(key, slot);
|
|
10297
|
+
return slot;
|
|
10110
10298
|
}
|
|
10111
10299
|
set(key, slot) {
|
|
10112
|
-
if (this.entries.
|
|
10113
|
-
this.entries.
|
|
10300
|
+
if (this.entries.has(key)) {
|
|
10301
|
+
this.entries.delete(key);
|
|
10302
|
+
} else if (this.entries.size >= MAX_ENTRIES) {
|
|
10303
|
+
const oldest = this.entries.keys().next();
|
|
10304
|
+
if (!oldest.done)
|
|
10305
|
+
this.entries.delete(oldest.value);
|
|
10306
|
+
}
|
|
10114
10307
|
this.entries.set(key, slot);
|
|
10115
10308
|
}
|
|
10116
10309
|
}
|
|
@@ -10384,7 +10577,8 @@ function walkSelect(walk2, dialect, statement) {
|
|
|
10384
10577
|
}
|
|
10385
10578
|
if (statement.lateralJoins !== undefined && statement.lateralJoins.length > 0) {
|
|
10386
10579
|
for (const join of statement.lateralJoins) {
|
|
10387
|
-
walk2.key += join.agg === "jsonArray" ? "Ja(" : "Jo(";
|
|
10580
|
+
walk2.key += join.agg === "count" ? "Jc(" : join.agg === "jsonArray" ? "Ja(" : "Jo(";
|
|
10581
|
+
walk2.key += join.rowFormat === "positional" ? "pos;" : "obj;";
|
|
10388
10582
|
if (!walkSelect(walk2, dialect, join.select))
|
|
10389
10583
|
return false;
|
|
10390
10584
|
addIdent(walk2, join.columnAlias);
|
|
@@ -10579,17 +10773,17 @@ function renderStatementCached(params) {
|
|
|
10579
10773
|
}
|
|
10580
10774
|
|
|
10581
10775
|
// packages/runtime/src/strict-args.ts
|
|
10582
|
-
import { VibeError as
|
|
10776
|
+
import { VibeError as VibeError21 } from "@vibeorm/schema";
|
|
10583
10777
|
var SKIP = Symbol.for("vibeorm.skip");
|
|
10584
10778
|
function explicitUndefined(params) {
|
|
10585
10779
|
const { model, method, path } = params;
|
|
10586
|
-
return new
|
|
10780
|
+
return new VibeError21({
|
|
10587
10781
|
code: "VIBE_VALIDATION",
|
|
10588
10782
|
message: `${model}.${method}(): explicit undefined at ${path} \u2014 an undefined value is refused ` + `because it would silently drop the property (widening a filter or losing a write). ` + `Omit the property, or pass SKIP to omit it deliberately; ` + `VibeClient({ strictArguments: false }) restores the legacy behaviour.`,
|
|
10589
10783
|
meta: { model, method, path }
|
|
10590
10784
|
});
|
|
10591
10785
|
}
|
|
10592
|
-
function
|
|
10786
|
+
function isPlainObject9(value) {
|
|
10593
10787
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
10594
10788
|
return false;
|
|
10595
10789
|
const proto = Object.getPrototypeOf(value);
|
|
@@ -10621,7 +10815,7 @@ function walkNode(params) {
|
|
|
10621
10815
|
const filter = params.filter === true;
|
|
10622
10816
|
if (Array.isArray(value))
|
|
10623
10817
|
return walkArray({ ctx, model, value, path, filter });
|
|
10624
|
-
if (!
|
|
10818
|
+
if (!isPlainObject9(value))
|
|
10625
10819
|
return value;
|
|
10626
10820
|
let out = null;
|
|
10627
10821
|
for (const key of Object.keys(value)) {
|
|
@@ -10637,7 +10831,7 @@ function walkNode(params) {
|
|
|
10637
10831
|
}
|
|
10638
10832
|
const target = descend({ ctx, model, key });
|
|
10639
10833
|
if (target === "json") {
|
|
10640
|
-
if (filter &&
|
|
10834
|
+
if (filter && isPlainObject9(child)) {
|
|
10641
10835
|
let filtered = null;
|
|
10642
10836
|
for (const operator of Object.keys(child)) {
|
|
10643
10837
|
if (child[operator] === undefined) {
|
|
@@ -10760,10 +10954,10 @@ function bindRlsContext(params) {
|
|
|
10760
10954
|
}
|
|
10761
10955
|
|
|
10762
10956
|
// packages/runtime/src/telemetry/recorder.ts
|
|
10763
|
-
import { VibeError as
|
|
10957
|
+
import { VibeError as VibeError23 } from "@vibeorm/schema";
|
|
10764
10958
|
|
|
10765
10959
|
// packages/runtime/src/telemetry/config.ts
|
|
10766
|
-
import { VibeError as
|
|
10960
|
+
import { VibeError as VibeError22 } from "@vibeorm/schema";
|
|
10767
10961
|
var TELEMETRY_DEFAULTS = Object.freeze({
|
|
10768
10962
|
captureSql: false,
|
|
10769
10963
|
captureParameterValues: false,
|
|
@@ -10778,7 +10972,7 @@ var TELEMETRY_DEFAULTS = Object.freeze({
|
|
|
10778
10972
|
latencyBucketsMs: Object.freeze([1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000])
|
|
10779
10973
|
});
|
|
10780
10974
|
function configError(params) {
|
|
10781
|
-
return new
|
|
10975
|
+
return new VibeError22({
|
|
10782
10976
|
code: "VIBE_CONFIG",
|
|
10783
10977
|
message: `telemetry: ${params.message}`,
|
|
10784
10978
|
...params.meta === undefined ? {} : { meta: params.meta }
|
|
@@ -10866,7 +11060,7 @@ function deriveStage(params) {
|
|
|
10866
11060
|
return "decode";
|
|
10867
11061
|
}
|
|
10868
11062
|
function errorCodeOf(error) {
|
|
10869
|
-
return error instanceof
|
|
11063
|
+
return error instanceof VibeError23 ? error.code : undefined;
|
|
10870
11064
|
}
|
|
10871
11065
|
function newHistogram(bounds) {
|
|
10872
11066
|
return { counts: new Array(bounds.length + 1).fill(0), sum: 0, count: 0 };
|
|
@@ -11240,7 +11434,7 @@ async function instrumentStatement(params) {
|
|
|
11240
11434
|
// packages/runtime/src/client.ts
|
|
11241
11435
|
function notFound2(params) {
|
|
11242
11436
|
const { model, method } = params;
|
|
11243
|
-
return new
|
|
11437
|
+
return new VibeError24({
|
|
11244
11438
|
code: "VIBE_NOT_FOUND",
|
|
11245
11439
|
message: `no ${model} record matched the where clause of ${method}()`,
|
|
11246
11440
|
meta: { model, method }
|
|
@@ -11248,7 +11442,7 @@ function notFound2(params) {
|
|
|
11248
11442
|
}
|
|
11249
11443
|
function viewIsReadOnly(params) {
|
|
11250
11444
|
const { model, method } = params;
|
|
11251
|
-
return new
|
|
11445
|
+
return new VibeError24({
|
|
11252
11446
|
code: "VIBE_VALIDATION",
|
|
11253
11447
|
message: `${model} is a view \u2014 ${method}() is not available on views (they are read-only); write to the underlying table instead`,
|
|
11254
11448
|
meta: { model, method }
|
|
@@ -11354,7 +11548,7 @@ async function engineTransaction(params) {
|
|
|
11354
11548
|
const support = engine.adapter.budgets?.transactionDeadline;
|
|
11355
11549
|
const requested = options.deadline.enforcement ?? "between-statements";
|
|
11356
11550
|
if (support === undefined || support === "unsupported" || requested === "cancel-running-statements" && support !== "cancel-running-statements") {
|
|
11357
|
-
throw new
|
|
11551
|
+
throw new VibeError24({
|
|
11358
11552
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
11359
11553
|
message: "the adapter does not declare support for the requested transaction deadline",
|
|
11360
11554
|
meta: { provider: engine.adapter.provider, feature: "transactionDeadline", requested, supported: support ?? "undeclared" }
|
|
@@ -11521,7 +11715,7 @@ class QueryPromise extends Promise {
|
|
|
11521
11715
|
}
|
|
11522
11716
|
claim() {
|
|
11523
11717
|
if (this.started) {
|
|
11524
|
-
throw new
|
|
11718
|
+
throw new VibeError24({
|
|
11525
11719
|
code: "VIBE_VALIDATION",
|
|
11526
11720
|
message: "$transaction([...]) needs operations that have not dispatched \u2014 pass delegate calls in the same tick they are created (this one was awaited or created earlier and already executed outside the transaction)"
|
|
11527
11721
|
});
|
|
@@ -11622,7 +11816,7 @@ async function runAffected(params) {
|
|
|
11622
11816
|
function plan(params) {
|
|
11623
11817
|
const { engine, model, method, args } = params;
|
|
11624
11818
|
if (args !== undefined && args["lock"] !== undefined && engine.inTransaction !== true) {
|
|
11625
|
-
throw new
|
|
11819
|
+
throw new VibeError24({
|
|
11626
11820
|
code: "VIBE_VALIDATION",
|
|
11627
11821
|
message: `lock on ${model}.${method}() requires a transaction \u2014 call it on the client $transaction hands its callback (outside one, the lock would release at statement end)`,
|
|
11628
11822
|
meta: { model, method }
|
|
@@ -11677,7 +11871,7 @@ async function insertedKeyWhere(params) {
|
|
|
11677
11871
|
const keyField = source.model.primaryKey[0];
|
|
11678
11872
|
const lastInsertIdSql = engine.dialect.lastInsertIdSql;
|
|
11679
11873
|
if (lastInsertIdSql === null || source.model.primaryKey.length !== 1 || keyField === undefined) {
|
|
11680
|
-
throw
|
|
11874
|
+
throw unsupportedCapability7({
|
|
11681
11875
|
dialect: engine.dialect.name,
|
|
11682
11876
|
feature: `${source.method} on ${source.model.name} without RETURNING`,
|
|
11683
11877
|
hint: "this model's key is assigned by the database and cannot be read back on this dialect"
|
|
@@ -11695,7 +11889,7 @@ async function insertedKeyWhere(params) {
|
|
|
11695
11889
|
});
|
|
11696
11890
|
const id = result.rows[0]?.id;
|
|
11697
11891
|
if (id === undefined || id === null) {
|
|
11698
|
-
throw new
|
|
11892
|
+
throw new VibeError24({
|
|
11699
11893
|
code: "VIBE_INTERNAL",
|
|
11700
11894
|
message: `could not read back the generated key for ${source.model.name}`,
|
|
11701
11895
|
meta: { model: source.model.name }
|
|
@@ -12012,7 +12206,7 @@ function createDelegate(params) {
|
|
|
12012
12206
|
const skipping = args.onConflict !== undefined;
|
|
12013
12207
|
if (hasNestedWrites({ model: modelMeta, data: args.data })) {
|
|
12014
12208
|
if (skipping) {
|
|
12015
|
-
throw new
|
|
12209
|
+
throw new VibeError24({
|
|
12016
12210
|
code: "VIBE_VALIDATION",
|
|
12017
12211
|
message: `onConflict cannot combine with nested writes on ${modelMeta.name}.create() \u2014 insert the parent row alone, then connect relations`,
|
|
12018
12212
|
meta: { model: modelMeta.name }
|
|
@@ -12113,7 +12307,7 @@ function createDelegate(params) {
|
|
|
12113
12307
|
const upsert = async (target, args) => {
|
|
12114
12308
|
const modelMeta = getModelMeta({ meta: target.meta, model });
|
|
12115
12309
|
if (hasNestedWrites({ model: modelMeta, data: args.create }) || hasNestedWrites({ model: modelMeta, data: args.update })) {
|
|
12116
|
-
throw new
|
|
12310
|
+
throw new VibeError24({
|
|
12117
12311
|
code: "VIBE_VALIDATION",
|
|
12118
12312
|
message: `nested writes are not supported inside upsert arms on ${modelMeta.name} \u2014 use create()/update() with nested data`,
|
|
12119
12313
|
meta: { model: modelMeta.name }
|
|
@@ -12121,7 +12315,7 @@ function createDelegate(params) {
|
|
|
12121
12315
|
}
|
|
12122
12316
|
if (!upsertUsesNativeStatement({ model: modelMeta, args })) {
|
|
12123
12317
|
if (args.onConflict !== undefined) {
|
|
12124
|
-
throw new
|
|
12318
|
+
throw new VibeError24({
|
|
12125
12319
|
code: "VIBE_VALIDATION",
|
|
12126
12320
|
message: `onConflict on ${modelMeta.name}.upsert() needs the native single-statement path \u2014 this shape (a guard field beyond the conflict target, or create data not binding the where's key) runs the find-then-write fallback, which cannot honor an explicit conflict target`,
|
|
12127
12321
|
meta: { model: modelMeta.name }
|
|
@@ -12363,7 +12557,7 @@ function buildClient(params) {
|
|
|
12363
12557
|
}
|
|
12364
12558
|
for (const operation of operations) {
|
|
12365
12559
|
if (!(operation instanceof QueryPromise)) {
|
|
12366
|
-
claimError = new
|
|
12560
|
+
claimError = new VibeError24({
|
|
12367
12561
|
code: "VIBE_VALIDATION",
|
|
12368
12562
|
message: "$transaction([...]) takes query promises from model delegate calls \u2014 a foreign promise is already running and cannot join the transaction"
|
|
12369
12563
|
});
|
|
@@ -12428,7 +12622,7 @@ function buildClient(params) {
|
|
|
12428
12622
|
$models: models,
|
|
12429
12623
|
$on: (event, listener) => {
|
|
12430
12624
|
if (event !== "query") {
|
|
12431
|
-
throw new
|
|
12625
|
+
throw new VibeError24({
|
|
12432
12626
|
code: "VIBE_VALIDATION",
|
|
12433
12627
|
message: `unknown client event "${String(event)}" \u2014 supported: "query"`,
|
|
12434
12628
|
meta: { event }
|
|
@@ -12494,7 +12688,7 @@ function buildClient(params) {
|
|
|
12494
12688
|
attachPolicy({ client, meta: engine.meta, ...wrapDelegate === undefined ? {} : { wrapDelegate } });
|
|
12495
12689
|
} else {
|
|
12496
12690
|
const refuseLegacy = () => {
|
|
12497
|
-
throw new
|
|
12691
|
+
throw new VibeError24({ code: "VIBE_CONFIG", message: "Native row-level security cannot be combined with application policy or tenant scoping" });
|
|
12498
12692
|
};
|
|
12499
12693
|
Object.defineProperties(client, {
|
|
12500
12694
|
$withContext: { enumerable: true, value: (context) => {
|
|
@@ -12556,7 +12750,7 @@ function makeMountContext(params) {
|
|
|
12556
12750
|
compileWhere: ({ where, model: modelName, qualify }) => {
|
|
12557
12751
|
const target = modelName === undefined ? model : getModelMeta({ meta: engine.meta, model: modelName });
|
|
12558
12752
|
if (target === null) {
|
|
12559
|
-
throw new
|
|
12753
|
+
throw new VibeError24({
|
|
12560
12754
|
code: "VIBE_INTERNAL",
|
|
12561
12755
|
message: `extension "${extension}" compiled a where with no model in scope`,
|
|
12562
12756
|
meta: { extension }
|
|
@@ -12585,24 +12779,24 @@ function createMountSurface(params) {
|
|
|
12585
12779
|
for (const key of path) {
|
|
12586
12780
|
receiver = current;
|
|
12587
12781
|
if (current === null || typeof current !== "object" && typeof current !== "function")
|
|
12588
|
-
throw new
|
|
12782
|
+
throw new VibeError24({ code: "VIBE_CONFIG", message: "Extension mount changed its method shape" });
|
|
12589
12783
|
current = Reflect.get(current, key);
|
|
12590
12784
|
}
|
|
12591
12785
|
if (typeof current !== "function")
|
|
12592
|
-
throw new
|
|
12786
|
+
throw new VibeError24({ code: "VIBE_CONFIG", message: "Extension mount changed its method shape" });
|
|
12593
12787
|
return Reflect.apply(current, receiver, args);
|
|
12594
12788
|
} });
|
|
12595
12789
|
}
|
|
12596
12790
|
if (value === null || typeof value !== "object")
|
|
12597
12791
|
return value;
|
|
12598
12792
|
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
12599
|
-
throw new
|
|
12793
|
+
throw new VibeError24({ code: "VIBE_CONFIG", message: "Native RLS extension mounts must expose plain objects and methods" });
|
|
12600
12794
|
}
|
|
12601
12795
|
const result = {};
|
|
12602
12796
|
for (const key of Reflect.ownKeys(value)) {
|
|
12603
12797
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
12604
12798
|
if (!("value" in descriptor))
|
|
12605
|
-
throw new
|
|
12799
|
+
throw new VibeError24({ code: "VIBE_CONFIG", message: "Native RLS extension mount accessors are unsupported" });
|
|
12606
12800
|
Object.defineProperty(result, key, { value: wrap(descriptor.value, [...path, key]), enumerable: descriptor.enumerable });
|
|
12607
12801
|
}
|
|
12608
12802
|
return Object.freeze(result);
|
|
@@ -12619,7 +12813,7 @@ function attachExtensionMounts(params) {
|
|
|
12619
12813
|
const model = getModelMeta({ meta: engine.meta, model: mount.model ?? "" });
|
|
12620
12814
|
const delegate = models[model.name];
|
|
12621
12815
|
if (delegate === undefined || mount.property in delegate) {
|
|
12622
|
-
throw new
|
|
12816
|
+
throw new VibeError24({
|
|
12623
12817
|
code: "VIBE_CONFIG",
|
|
12624
12818
|
message: `extension "${mount.extension}" cannot mount "${mount.property}" on ${model.name} \u2014 the name is taken`,
|
|
12625
12819
|
meta: { extension: mount.extension, model: model.name, property: mount.property }
|
|
@@ -12630,7 +12824,7 @@ function attachExtensionMounts(params) {
|
|
|
12630
12824
|
continue;
|
|
12631
12825
|
}
|
|
12632
12826
|
if (!mount.property.startsWith("$") || mount.property in client) {
|
|
12633
|
-
throw new
|
|
12827
|
+
throw new VibeError24({
|
|
12634
12828
|
code: "VIBE_CONFIG",
|
|
12635
12829
|
message: `extension "${mount.extension}" cannot mount client property "${mount.property}" \u2014 root mounts are $-prefixed and must not collide`,
|
|
12636
12830
|
meta: { extension: mount.extension, property: mount.property }
|
|
@@ -12647,7 +12841,7 @@ function createClient(params) {
|
|
|
12647
12841
|
if (schema.rls !== undefined) {
|
|
12648
12842
|
const dialect = getSqlDialect5({ dialect: dialectName });
|
|
12649
12843
|
if (!dialect.capabilities.nativeRowLevelSecurity)
|
|
12650
|
-
throw
|
|
12844
|
+
throw unsupportedCapability7({ dialect: dialectName, feature: "nativeRowLevelSecurity" });
|
|
12651
12845
|
assertValidSchemaIR2({ schema });
|
|
12652
12846
|
for (const model of schema.models) {
|
|
12653
12847
|
if (model.rls !== "unprotected")
|
|
@@ -12656,9 +12850,9 @@ function createClient(params) {
|
|
|
12656
12850
|
const role = options?.nativeRls?.runtimeRole;
|
|
12657
12851
|
if (adapter.provider === "pglite") {
|
|
12658
12852
|
if (typeof role !== "string" || role.length === 0 || role.includes("\x00"))
|
|
12659
|
-
throw new
|
|
12853
|
+
throw new VibeError24({ code: "VIBE_CONFIG", message: "PGlite native row-level security requires a pre-existing restricted runtimeRole; embedded binding is not a boundary against the host process" });
|
|
12660
12854
|
} else if (role !== undefined) {
|
|
12661
|
-
throw new
|
|
12855
|
+
throw new VibeError24({ code: "VIBE_CONFIG", message: "Server native row-level security must use the runtime login role, not a runtimeRole switch" });
|
|
12662
12856
|
}
|
|
12663
12857
|
}
|
|
12664
12858
|
if (extensions?.manifest !== undefined) {
|
|
@@ -12668,7 +12862,7 @@ function createClient(params) {
|
|
|
12668
12862
|
assertComputedManifest({ manifest: computed.manifest, specs: computed.specs });
|
|
12669
12863
|
}
|
|
12670
12864
|
if (computed === undefined && options !== undefined && options["computed"] !== undefined) {
|
|
12671
|
-
throw new
|
|
12865
|
+
throw new VibeError24({
|
|
12672
12866
|
code: "VIBE_CONFIG",
|
|
12673
12867
|
message: "this client was generated without computed fields \u2014 declare them in vibeorm.config.ts and re-run vibeorm generate",
|
|
12674
12868
|
meta: {}
|
|
@@ -12699,9 +12893,9 @@ function createClient(params) {
|
|
|
12699
12893
|
});
|
|
12700
12894
|
}
|
|
12701
12895
|
// packages/runtime/src/keyset-iterator.ts
|
|
12702
|
-
import { VibeError as
|
|
12896
|
+
import { VibeError as VibeError25 } from "@vibeorm/schema";
|
|
12703
12897
|
function validation(params) {
|
|
12704
|
-
return new
|
|
12898
|
+
return new VibeError25({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
12705
12899
|
}
|
|
12706
12900
|
function projectionFor(params) {
|
|
12707
12901
|
const { plan: plan2, select, omit } = params;
|
|
@@ -12778,7 +12972,7 @@ function iterateKeyset(options) {
|
|
|
12778
12972
|
});
|
|
12779
12973
|
if (advanced === false) {
|
|
12780
12974
|
stop();
|
|
12781
|
-
throw new
|
|
12975
|
+
throw new VibeError25({
|
|
12782
12976
|
code: "VIBE_DATA_INVALID",
|
|
12783
12977
|
message: `keyset traversal of "${options.model}" is not advancing: the engine returned a row that does not sort after the previous position. ` + "The ordering is not total, or a cursor value lost precision \u2014 a DateTime column finer than milliseconds asserted as millisecond-exact does this.",
|
|
12784
12978
|
meta: { model: options.model, order: plan2.signature, reason: "keyset-position-not-advancing" }
|
|
@@ -12851,7 +13045,7 @@ function iterateKeyset(options) {
|
|
|
12851
13045
|
return iterator;
|
|
12852
13046
|
}
|
|
12853
13047
|
// packages/runtime/src/telemetry/collector.ts
|
|
12854
|
-
import { VibeError as
|
|
13048
|
+
import { VibeError as VibeError26 } from "@vibeorm/schema";
|
|
12855
13049
|
function createTelemetryCollector(params) {
|
|
12856
13050
|
const throwOn = new Set(params?.throwOn ?? []);
|
|
12857
13051
|
const operations = [];
|
|
@@ -12861,7 +13055,7 @@ function createTelemetryCollector(params) {
|
|
|
12861
13055
|
let shutdowns = 0;
|
|
12862
13056
|
const hostile = (phase) => {
|
|
12863
13057
|
if (throwOn.has(phase)) {
|
|
12864
|
-
throw new
|
|
13058
|
+
throw new VibeError26({
|
|
12865
13059
|
code: "VIBE_INTERNAL",
|
|
12866
13060
|
message: `telemetry collector: deliberate ${phase} failure`,
|
|
12867
13061
|
meta: { reason: "telemetry-collector-deliberate-failure", phase }
|
|
@@ -12917,7 +13111,7 @@ function createTelemetryCollector(params) {
|
|
|
12917
13111
|
operationFor: (lookup) => {
|
|
12918
13112
|
const matches = operations.filter((event) => event.method === lookup.method && (lookup.model === undefined || event.model === lookup.model));
|
|
12919
13113
|
if (matches.length > 1) {
|
|
12920
|
-
throw new
|
|
13114
|
+
throw new VibeError26({
|
|
12921
13115
|
code: "VIBE_VALIDATION",
|
|
12922
13116
|
message: `telemetry collector: ${matches.length} operations match ${lookup.model ?? "*"}.${lookup.method}`,
|
|
12923
13117
|
meta: {
|
|
@@ -13021,4 +13215,4 @@ export {
|
|
|
13021
13215
|
COUNT_ALIAS
|
|
13022
13216
|
};
|
|
13023
13217
|
|
|
13024
|
-
//# debugId=
|
|
13218
|
+
//# debugId=ADD0DD01D02385B864756E2164756E21
|