@vibeorm/runtime 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter-kit/index.d.ts +6 -3
- package/dist/adapter-kit/index.d.ts.map +1 -1
- package/dist/adapter-kit/index.js +178 -19
- package/dist/adapter-kit/index.js.map +8 -5
- package/dist/adapter-kit/nested-options.d.ts +22 -0
- package/dist/adapter-kit/nested-options.d.ts.map +1 -0
- package/dist/adapter-kit/savepoint-gate.d.ts +31 -0
- package/dist/adapter-kit/savepoint-gate.d.ts.map +1 -1
- package/dist/adapter-kit/session.d.ts +9 -0
- package/dist/adapter-kit/session.d.ts.map +1 -0
- package/dist/adapter-kit/sqlite-session.d.ts +8 -0
- package/dist/adapter-kit/sqlite-session.d.ts.map +1 -0
- package/dist/adapter.d.ts +34 -27
- package/dist/adapter.d.ts.map +1 -1
- package/dist/bulk-upsert.d.ts +5 -3
- package/dist/bulk-upsert.d.ts.map +1 -1
- package/dist/client-types.d.ts +14 -0
- package/dist/client-types.d.ts.map +1 -0
- package/dist/client.d.ts +29 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/codecs.d.ts +5 -0
- package/dist/codecs.d.ts.map +1 -1
- package/dist/database-module.d.ts +27 -0
- package/dist/database-module.d.ts.map +1 -0
- package/dist/diagnostics/index.d.ts +3 -1
- package/dist/diagnostics/index.d.ts.map +1 -1
- package/dist/diagnostics/plan.d.ts +39 -1
- package/dist/diagnostics/plan.d.ts.map +1 -1
- package/dist/diagnostics/preview.d.ts +3 -1
- package/dist/diagnostics/preview.d.ts.map +1 -1
- package/dist/diagnostics/statement-diagnostics.d.ts +176 -0
- package/dist/diagnostics/statement-diagnostics.d.ts.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1362 -566
- package/dist/index.js.map +24 -21
- package/dist/module-context.d.ts +20 -0
- package/dist/module-context.d.ts.map +1 -0
- package/dist/nested-writes.d.ts.map +1 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/query-builder.d.ts +11 -0
- package/dist/query-builder.d.ts.map +1 -1
- package/dist/read-only.d.ts.map +1 -1
- package/dist/relation-loader.d.ts +12 -0
- package/dist/relation-loader.d.ts.map +1 -1
- package/dist/relation-plan.d.ts +14 -0
- package/dist/relation-plan.d.ts.map +1 -1
- package/dist/rls-readiness.d.ts.map +1 -1
- package/dist/scoped.d.ts.map +1 -1
- package/dist/sql-access.d.ts +18 -2
- package/dist/sql-access.d.ts.map +1 -1
- package/dist/telemetry/statement.d.ts +7 -0
- package/dist/telemetry/statement.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -50,22 +50,30 @@ function createSqlModel(params) {
|
|
|
50
50
|
return Object.freeze({ table: params.alias === undefined ? table : sql`${table} AS ${sql.ident({ parts: [params.alias] })}`, fields: Object.freeze(fields) });
|
|
51
51
|
}
|
|
52
52
|
// packages/runtime/src/adapter.ts
|
|
53
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
53
54
|
function toSqlExecutor(params) {
|
|
54
55
|
const { adapter } = params;
|
|
55
|
-
|
|
56
|
-
const result = await adapter.executeUnsafe(
|
|
57
|
-
text: queryParams.text,
|
|
58
|
-
values: queryParams.values
|
|
59
|
-
});
|
|
56
|
+
const execute = async (queryParams) => {
|
|
57
|
+
const result = await adapter.executeUnsafe(queryParams);
|
|
60
58
|
return result.rows;
|
|
61
59
|
};
|
|
60
|
+
const withSession = async ({ run }) => {
|
|
61
|
+
if (adapter.withSession === undefined)
|
|
62
|
+
throw new VibeError2({
|
|
63
|
+
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
64
|
+
message: "This adapter cannot reserve a migration session. Supply an adapter with session support or an explicitly owned SqlExecutor.",
|
|
65
|
+
meta: { capability: "session", provider: adapter.provider }
|
|
66
|
+
});
|
|
67
|
+
return adapter.withSession({ run });
|
|
68
|
+
};
|
|
69
|
+
return Object.assign(execute, { withSession });
|
|
62
70
|
}
|
|
63
71
|
// packages/runtime/src/db-now.ts
|
|
64
|
-
import { VibeError as
|
|
72
|
+
import { VibeError as VibeError3 } from "@vibeorm/schema";
|
|
65
73
|
function dbNow(options) {
|
|
66
74
|
const offset = options?.plusSeconds ?? 0;
|
|
67
75
|
if (typeof offset !== "number" || !Number.isFinite(offset)) {
|
|
68
|
-
throw new
|
|
76
|
+
throw new VibeError3({
|
|
69
77
|
code: "VIBE_VALIDATION",
|
|
70
78
|
message: `dbNow plusSeconds must be a finite number of seconds (fractional is fine), got ${String(offset)}`,
|
|
71
79
|
meta: { plusSeconds: offset }
|
|
@@ -84,10 +92,10 @@ function isDbNow(value) {
|
|
|
84
92
|
return candidate.__vibeDbNow === true && typeof candidate.offsetSeconds === "number" && Number.isFinite(candidate.offsetSeconds);
|
|
85
93
|
}
|
|
86
94
|
// packages/runtime/src/advisory-lock.ts
|
|
87
|
-
import { VibeError as
|
|
95
|
+
import { VibeError as VibeError4, unsupportedCapability } from "@vibeorm/schema";
|
|
88
96
|
import { ADVISORY_KEY_BOUNDS } from "@vibeorm/sql";
|
|
89
97
|
function validationError(params) {
|
|
90
|
-
return new
|
|
98
|
+
return new VibeError4({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
91
99
|
}
|
|
92
100
|
function readInt32(params) {
|
|
93
101
|
const { value, member, method } = params;
|
|
@@ -192,7 +200,7 @@ function statementFor(params) {
|
|
|
192
200
|
};
|
|
193
201
|
}
|
|
194
202
|
// packages/runtime/src/model-meta.ts
|
|
195
|
-
import { VibeError as
|
|
203
|
+
import { VibeError as VibeError5, enumStorageOf, isEnumType as isEnumType2, validateComputedConfig } from "@vibeorm/schema";
|
|
196
204
|
import { enumMembershipCodec } from "@vibeorm/sql";
|
|
197
205
|
function toClientName(params) {
|
|
198
206
|
const { name } = params;
|
|
@@ -321,7 +329,7 @@ function buildModelMeta(params) {
|
|
|
321
329
|
for (const name of model.primaryKey.fields) {
|
|
322
330
|
const field = fieldByName.get(name);
|
|
323
331
|
if (field === undefined) {
|
|
324
|
-
throw new
|
|
332
|
+
throw new VibeError5({
|
|
325
333
|
code: "VIBE_SCHEMA_INVALID",
|
|
326
334
|
message: `model "${model.name}" declares composite primary-key field "${name}", which does not exist`,
|
|
327
335
|
meta: { model: model.name, field: name }
|
|
@@ -427,7 +435,7 @@ function getModelMeta(params) {
|
|
|
427
435
|
const { meta, model } = params;
|
|
428
436
|
const found = meta.modelByName.get(model);
|
|
429
437
|
if (found === undefined) {
|
|
430
|
-
throw new
|
|
438
|
+
throw new VibeError5({
|
|
431
439
|
code: "VIBE_VALIDATION",
|
|
432
440
|
message: `unknown model "${model}"`,
|
|
433
441
|
meta: { model, known: [...new Set(meta.models.map((entry) => entry.name))] }
|
|
@@ -440,20 +448,20 @@ function getFieldMeta(params) {
|
|
|
440
448
|
const found = model.fieldByName.get(field);
|
|
441
449
|
if (found === undefined) {
|
|
442
450
|
if (model.computed !== null && model.computed.has(field)) {
|
|
443
|
-
throw new
|
|
451
|
+
throw new VibeError5({
|
|
444
452
|
code: "VIBE_VALIDATION",
|
|
445
453
|
message: `"${field}" on model "${model.name}" is a computed field \u2014 computed fields appear in results (select/omit) only, not in ${context}`,
|
|
446
454
|
meta: { model: model.name, field, context, computed: true }
|
|
447
455
|
});
|
|
448
456
|
}
|
|
449
457
|
if (model.relationByName.has(field)) {
|
|
450
|
-
throw new
|
|
458
|
+
throw new VibeError5({
|
|
451
459
|
code: "VIBE_VALIDATION",
|
|
452
460
|
message: `"${field}" on model "${model.name}" is a relation \u2014 not valid in ${context} here (relation writes use nested-write verbs, relation reads use select/include)`,
|
|
453
461
|
meta: { model: model.name, field, context }
|
|
454
462
|
});
|
|
455
463
|
}
|
|
456
|
-
throw new
|
|
464
|
+
throw new VibeError5({
|
|
457
465
|
code: "VIBE_VALIDATION",
|
|
458
466
|
message: `unknown field "${field}" on model "${model.name}" in ${context}`,
|
|
459
467
|
meta: { model: model.name, field, context }
|
|
@@ -463,7 +471,7 @@ function getFieldMeta(params) {
|
|
|
463
471
|
}
|
|
464
472
|
// packages/runtime/src/codecs.ts
|
|
465
473
|
import { JSON_TRANSPORT, projectionOutputName, parsePgArrayLiteral, toPgArrayLiteral } from "@vibeorm/sql";
|
|
466
|
-
import { VibeError as
|
|
474
|
+
import { VibeError as VibeError6, isEnumType as isEnumType3 } from "@vibeorm/schema";
|
|
467
475
|
import { DIALECT_CODECS, decodeIsNoop } from "@vibeorm/sql";
|
|
468
476
|
import {
|
|
469
477
|
DIALECT_CODECS as DIALECT_CODECS2,
|
|
@@ -512,10 +520,29 @@ function decodeValue(params) {
|
|
|
512
520
|
return codec.decode(value);
|
|
513
521
|
}
|
|
514
522
|
var DECIMAL_LITERAL = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
523
|
+
function validateAtomicOperand(params) {
|
|
524
|
+
const { field, value } = params;
|
|
525
|
+
if (field.scalarType === "BigInt") {
|
|
526
|
+
if (typeof value === "bigint" || typeof value === "number" && Number.isSafeInteger(value))
|
|
527
|
+
return value;
|
|
528
|
+
} else if (field.scalarType === "Decimal") {
|
|
529
|
+
if (typeof value === "string" && DECIMAL_LITERAL.test(value.trim()))
|
|
530
|
+
return value.trim();
|
|
531
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
532
|
+
return value;
|
|
533
|
+
} else if (typeof value === "number" && Number.isFinite(value)) {
|
|
534
|
+
return value;
|
|
535
|
+
}
|
|
536
|
+
throw new VibeError6({
|
|
537
|
+
code: "VIBE_VALIDATION",
|
|
538
|
+
message: `Invalid arithmetic operand for ${field.scalarType} field "${field.name}"`,
|
|
539
|
+
meta: { field: field.name, value }
|
|
540
|
+
});
|
|
541
|
+
}
|
|
515
542
|
function toRealNumber(params) {
|
|
516
543
|
const { field, value } = params;
|
|
517
544
|
const refuse = () => {
|
|
518
|
-
throw new
|
|
545
|
+
throw new VibeError6({
|
|
519
546
|
code: "VIBE_VALIDATION",
|
|
520
547
|
message: `"${String(value)}" is not a numeric value for the Decimal field "${field.name}"`,
|
|
521
548
|
meta: { field: field.name, value }
|
|
@@ -755,7 +782,7 @@ function positionalMaterializer(params) {
|
|
|
755
782
|
const output = projectionOutputName({ column });
|
|
756
783
|
const entry = plan.byColumn.get(output);
|
|
757
784
|
if (entry === undefined)
|
|
758
|
-
throw new
|
|
785
|
+
throw new VibeError6({ code: "VIBE_INTERNAL", message: "positional projection has no schema field", meta: { model: model.name, column: output } });
|
|
759
786
|
return entry;
|
|
760
787
|
});
|
|
761
788
|
const width = slots.length;
|
|
@@ -763,7 +790,7 @@ function positionalMaterializer(params) {
|
|
|
763
790
|
const chunked = width > chunkSize;
|
|
764
791
|
const count = Math.ceil(width / chunkSize);
|
|
765
792
|
const invalid = () => {
|
|
766
|
-
throw new
|
|
793
|
+
throw new VibeError6({ code: "VIBE_INTERNAL", message: "positional relation payload does not match its SQL projection", meta: { model: model.name, expectedColumns: width } });
|
|
767
794
|
};
|
|
768
795
|
if (width === 0 || new Set(slots.map((slot) => slot.name)).size !== width)
|
|
769
796
|
return invalid();
|
|
@@ -805,7 +832,7 @@ function decodeAggregateValue(params) {
|
|
|
805
832
|
return field === null ? value : decodeValue({ dialect, field, value });
|
|
806
833
|
}
|
|
807
834
|
// packages/runtime/src/lateral-projection.ts
|
|
808
|
-
import { VibeError as
|
|
835
|
+
import { VibeError as VibeError7, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
809
836
|
function isPlainObject(value) {
|
|
810
837
|
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
811
838
|
}
|
|
@@ -822,7 +849,7 @@ function lateralProjection(params) {
|
|
|
822
849
|
const columns = names.map((name) => {
|
|
823
850
|
const field = model.fieldByName.get(name);
|
|
824
851
|
if (field === undefined)
|
|
825
|
-
throw new
|
|
852
|
+
throw new VibeError7({ code: "VIBE_INTERNAL", message: "lateral projection references an unknown field", meta: { model: model.name, field: name } });
|
|
826
853
|
return Object.freeze(applyLateralTransform({ model, column: { table: LATERAL_CHILD_ALIAS, name: field.columnName } }));
|
|
827
854
|
});
|
|
828
855
|
const descriptor = Object.freeze({ columns: Object.freeze(columns), hiddenOrder: Object.freeze(hiddenOrder) });
|
|
@@ -871,7 +898,7 @@ function applyLateralTransform(params) {
|
|
|
871
898
|
|
|
872
899
|
// packages/runtime/src/query-builder.ts
|
|
873
900
|
import { isSqlFragment, mapConflictColumns } from "@vibeorm/sql";
|
|
874
|
-
import { VibeError as
|
|
901
|
+
import { VibeError as VibeError11, unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
|
|
875
902
|
import {
|
|
876
903
|
SQL_DEFAULT_CELL,
|
|
877
904
|
containsPattern,
|
|
@@ -885,9 +912,9 @@ import {
|
|
|
885
912
|
import { describeRowLock } from "@vibeorm/sql";
|
|
886
913
|
|
|
887
914
|
// packages/runtime/src/bulk-upsert.ts
|
|
888
|
-
import { VibeError as
|
|
915
|
+
import { VibeError as VibeError8, unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
|
|
889
916
|
function validationError2(params) {
|
|
890
|
-
return new
|
|
917
|
+
return new VibeError8({
|
|
891
918
|
code: "VIBE_VALIDATION",
|
|
892
919
|
message: params.message,
|
|
893
920
|
...params.meta === undefined ? {} : { meta: params.meta }
|
|
@@ -1069,8 +1096,13 @@ function conflictKeyOf(params) {
|
|
|
1069
1096
|
function assertNoDuplicateConflictKeys(params) {
|
|
1070
1097
|
const { model, compiled, targetColumns } = params;
|
|
1071
1098
|
const firstIndexByKey = new Map;
|
|
1099
|
+
const signature = [...targetColumns].sort((left, right) => left < right ? -1 : 1).join("\x00");
|
|
1100
|
+
const nullsNotDistinct = [...model.compoundUniques.values(), ...model.singleIndexUniques].some((group) => group.nullsNotDistinct && signatureOf({ fields: group.fields }) === signature);
|
|
1072
1101
|
for (let index = 0;index < compiled.length; index += 1) {
|
|
1073
|
-
const
|
|
1102
|
+
const row = compiled[index];
|
|
1103
|
+
if (!nullsNotDistinct && targetColumns.some((column) => row[column] === null))
|
|
1104
|
+
continue;
|
|
1105
|
+
const key = conflictKeyOf({ row, columns: targetColumns });
|
|
1074
1106
|
const first = firstIndexByKey.get(key);
|
|
1075
1107
|
if (first !== undefined) {
|
|
1076
1108
|
throw validationError2({
|
|
@@ -1206,7 +1238,7 @@ function assertConflictKeysAreComparable(params) {
|
|
|
1206
1238
|
}
|
|
1207
1239
|
|
|
1208
1240
|
// packages/runtime/src/keyset.ts
|
|
1209
|
-
import { VibeError as
|
|
1241
|
+
import { VibeError as VibeError9 } from "@vibeorm/schema";
|
|
1210
1242
|
var KEYSET_TOKEN_VERSION = 1;
|
|
1211
1243
|
var KEYSET_TOKEN_PREFIX = "vk1.";
|
|
1212
1244
|
var KEYSET_MAX_PAGE_SIZE = 1e4;
|
|
@@ -1216,10 +1248,10 @@ var DATETIME_NEEDS_PRECISION_ASSERTION = Object.freeze({
|
|
|
1216
1248
|
sqlite: false
|
|
1217
1249
|
});
|
|
1218
1250
|
function keysetValidation(params) {
|
|
1219
|
-
return new
|
|
1251
|
+
return new VibeError9({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
1220
1252
|
}
|
|
1221
1253
|
function keysetUnsupported(params) {
|
|
1222
|
-
return new
|
|
1254
|
+
return new VibeError9({
|
|
1223
1255
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
1224
1256
|
message: params.message,
|
|
1225
1257
|
meta: params.meta
|
|
@@ -1602,9 +1634,9 @@ function decodeKeysetToken(params) {
|
|
|
1602
1634
|
}
|
|
1603
1635
|
|
|
1604
1636
|
// packages/runtime/src/relation-plan.ts
|
|
1605
|
-
import { VibeError as
|
|
1637
|
+
import { VibeError as VibeError10, implicitJoinTable } from "@vibeorm/schema";
|
|
1606
1638
|
function validationError3(params) {
|
|
1607
|
-
return new
|
|
1639
|
+
return new VibeError10({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
1608
1640
|
}
|
|
1609
1641
|
function isPlainObject2(value) {
|
|
1610
1642
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
@@ -1629,7 +1661,7 @@ function keyFields(params) {
|
|
|
1629
1661
|
return names.map((name) => {
|
|
1630
1662
|
const field = model.fieldByName.get(name);
|
|
1631
1663
|
if (field === undefined) {
|
|
1632
|
-
throw new
|
|
1664
|
+
throw new VibeError10({
|
|
1633
1665
|
code: "VIBE_SCHEMA_INVALID",
|
|
1634
1666
|
message: `relation "${relation.name}" on ${relation.model} names unknown ${what} field "${name}"`,
|
|
1635
1667
|
meta: { model: relation.model, relation: relation.name }
|
|
@@ -1641,7 +1673,7 @@ function keyFields(params) {
|
|
|
1641
1673
|
function singleKeyPair(params) {
|
|
1642
1674
|
const { link, what } = params;
|
|
1643
1675
|
if (link.parentKeys.length !== 1 || link.childKeys.length !== 1) {
|
|
1644
|
-
throw new
|
|
1676
|
+
throw new VibeError10({
|
|
1645
1677
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
1646
1678
|
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`,
|
|
1647
1679
|
meta: { model: link.relation.model, relation: link.relation.name, arity: link.parentKeys.length }
|
|
@@ -1649,6 +1681,11 @@ function singleKeyPair(params) {
|
|
|
1649
1681
|
}
|
|
1650
1682
|
return { parent: link.parentKeys[0], child: link.childKeys[0] };
|
|
1651
1683
|
}
|
|
1684
|
+
function inverseRelationName(params) {
|
|
1685
|
+
const { link } = params;
|
|
1686
|
+
const reverse = findReverseRelation({ relation: link.relation, target: link.target });
|
|
1687
|
+
return reverse === null ? null : reverse.name;
|
|
1688
|
+
}
|
|
1652
1689
|
function singlePrimaryKey(params) {
|
|
1653
1690
|
const { model, relation } = params;
|
|
1654
1691
|
const pk = model.primaryKey;
|
|
@@ -1680,7 +1717,7 @@ function resolveRelationLinkUncached(params) {
|
|
|
1680
1717
|
const target = getModelMeta({ meta, model: relation.targetModel });
|
|
1681
1718
|
if (relation.type === "manyToMany") {
|
|
1682
1719
|
if (relation.model === relation.targetModel) {
|
|
1683
|
-
throw new
|
|
1720
|
+
throw new VibeError10({
|
|
1684
1721
|
code: "VIBE_SCHEMA_INVALID",
|
|
1685
1722
|
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.`,
|
|
1686
1723
|
meta: { model: relation.model, relation: relation.name, targetModel: relation.targetModel }
|
|
@@ -1722,7 +1759,7 @@ function resolveRelationLinkUncached(params) {
|
|
|
1722
1759
|
}
|
|
1723
1760
|
const reverse = findReverseRelation({ relation, target });
|
|
1724
1761
|
if (reverse === null || reverse.fields === undefined || reverse.fields.length === 0) {
|
|
1725
|
-
throw new
|
|
1762
|
+
throw new VibeError10({
|
|
1726
1763
|
code: "VIBE_SCHEMA_INVALID",
|
|
1727
1764
|
message: `relation "${relation.name}" on ${relation.model} has no foreign-key counterpart on ${relation.targetModel}`,
|
|
1728
1765
|
meta: { model: relation.model, relation: relation.name, target: relation.targetModel }
|
|
@@ -1747,7 +1784,7 @@ function pairedKeys(params) {
|
|
|
1747
1784
|
const parentKeys = keyFields({ model, names: parentNames, relation, what: "fields" });
|
|
1748
1785
|
const childKeys = keyFields({ model: target, names: childNames, relation, what: "references" });
|
|
1749
1786
|
if (parentKeys.length !== childKeys.length) {
|
|
1750
|
-
throw new
|
|
1787
|
+
throw new VibeError10({
|
|
1751
1788
|
code: "VIBE_SCHEMA_INVALID",
|
|
1752
1789
|
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`,
|
|
1753
1790
|
meta: { model: relation.model, relation: relation.name }
|
|
@@ -2097,7 +2134,7 @@ var AGG_FN_BY_KEY = {
|
|
|
2097
2134
|
_max: "max"
|
|
2098
2135
|
};
|
|
2099
2136
|
function validationError4(params) {
|
|
2100
|
-
return new
|
|
2137
|
+
return new VibeError11({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
2101
2138
|
}
|
|
2102
2139
|
function isPlainObject3(value) {
|
|
2103
2140
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
@@ -2662,6 +2699,12 @@ function compileOperatorFilter(params) {
|
|
|
2662
2699
|
});
|
|
2663
2700
|
}
|
|
2664
2701
|
for (const item of operand) {
|
|
2702
|
+
if (item === null) {
|
|
2703
|
+
throw validationError4({
|
|
2704
|
+
message: `"${operator}" on ${model.name}.${field.name} cannot contain null; use an explicit null comparison`,
|
|
2705
|
+
meta: { model: model.name, field: field.name, operator }
|
|
2706
|
+
});
|
|
2707
|
+
}
|
|
2665
2708
|
if (isDbNow(item)) {
|
|
2666
2709
|
throw validationError4({
|
|
2667
2710
|
message: `dbNow() cannot ride an "${operator}" list on ${model.name}.${field.name} \u2014 compare with lt/lte/gt/gte/equals instead`,
|
|
@@ -3294,21 +3337,27 @@ function expandCompoundWhere(params) {
|
|
|
3294
3337
|
function isAddressableMember(params) {
|
|
3295
3338
|
return isDirectValue(params.value) || params.nullsNotDistinct && params.value === null;
|
|
3296
3339
|
}
|
|
3297
|
-
function
|
|
3298
|
-
const { model, where
|
|
3340
|
+
function isUniqueWhere(params) {
|
|
3341
|
+
const { model, where } = params;
|
|
3299
3342
|
for (const field of model.uniqueFields) {
|
|
3300
3343
|
if (isDirectValue(where[field.name]))
|
|
3301
|
-
return;
|
|
3344
|
+
return true;
|
|
3302
3345
|
}
|
|
3303
3346
|
for (const single of model.singleIndexUniques) {
|
|
3304
3347
|
const field = single.fields[0];
|
|
3305
3348
|
if (isAddressableMember({ value: where[field.name], nullsNotDistinct: single.nullsNotDistinct }))
|
|
3306
|
-
return;
|
|
3349
|
+
return true;
|
|
3307
3350
|
}
|
|
3308
3351
|
for (const group of model.compoundUniques.values()) {
|
|
3309
3352
|
if (group.fields.every((field) => isAddressableMember({ value: where[field.name], nullsNotDistinct: group.nullsNotDistinct })))
|
|
3310
|
-
return;
|
|
3353
|
+
return true;
|
|
3311
3354
|
}
|
|
3355
|
+
return false;
|
|
3356
|
+
}
|
|
3357
|
+
function assertUniqueWhere(params) {
|
|
3358
|
+
const { model, where, method } = params;
|
|
3359
|
+
if (isUniqueWhere({ model, where }))
|
|
3360
|
+
return;
|
|
3312
3361
|
const candidates = [
|
|
3313
3362
|
...model.uniqueFields.map((field) => field.name),
|
|
3314
3363
|
...model.singleIndexUniques.map((single) => single.fields[0].name),
|
|
@@ -3485,21 +3534,17 @@ function orderedColumns(params) {
|
|
|
3485
3534
|
return model.fields.map((field) => field.columnName).filter((column) => (column in values));
|
|
3486
3535
|
}
|
|
3487
3536
|
function compileAtomicOperation(params) {
|
|
3488
|
-
const { model,
|
|
3537
|
+
const { model, entry, operator, operand } = params;
|
|
3538
|
+
const field = entry.field;
|
|
3489
3539
|
if (field.scalarType === null || !NUMERIC_TYPES.includes(field.scalarType)) {
|
|
3490
3540
|
throw validationError4({
|
|
3491
3541
|
message: `"${operator}" needs a numeric field, but ${model.name}.${field.name} is ${String(field.enumName ?? field.scalarType)}`,
|
|
3492
3542
|
meta: { model: model.name, field: field.name, operator }
|
|
3493
3543
|
});
|
|
3494
3544
|
}
|
|
3495
|
-
const numeric =
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
message: `"${operator}" on ${model.name}.${field.name} expects a number`,
|
|
3499
|
-
meta: { model: model.name, field: field.name, operator, operand }
|
|
3500
|
-
});
|
|
3501
|
-
}
|
|
3502
|
-
return { __op: operator, value: numeric };
|
|
3545
|
+
const numeric = validateAtomicOperand({ field, value: operand });
|
|
3546
|
+
const encoded = encodeWith(entry, numeric);
|
|
3547
|
+
return { __op: operator, value: encoded, ...field.scalarType === "Decimal" ? { numericType: "Decimal" } : {} };
|
|
3503
3548
|
}
|
|
3504
3549
|
function compileUpdateSet(params) {
|
|
3505
3550
|
const { dialect, model, data } = params;
|
|
@@ -3554,7 +3599,7 @@ function compileUpdateSet(params) {
|
|
|
3554
3599
|
meta: { model: model.name, field: field.name, operator }
|
|
3555
3600
|
});
|
|
3556
3601
|
}
|
|
3557
|
-
set[field.columnName] = compileAtomicOperation({ model,
|
|
3602
|
+
set[field.columnName] = compileAtomicOperation({ model, entry, operator: atomic, operand });
|
|
3558
3603
|
continue;
|
|
3559
3604
|
}
|
|
3560
3605
|
set[field.columnName] = encodeWith(entry, value);
|
|
@@ -5272,7 +5317,7 @@ function buildQuery(params) {
|
|
|
5272
5317
|
const targetFields = conflictTargetGroup({ model, where }).fields;
|
|
5273
5318
|
for (const field of targetFields) {
|
|
5274
5319
|
if (!(field.columnName in row.values)) {
|
|
5275
|
-
throw new
|
|
5320
|
+
throw new VibeError11({
|
|
5276
5321
|
code: "VIBE_INTERNAL",
|
|
5277
5322
|
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`,
|
|
5278
5323
|
meta: { model: model.name, column: field.columnName }
|
|
@@ -5283,7 +5328,7 @@ function buildQuery(params) {
|
|
|
5283
5328
|
for (const [key, value] of Object.entries(where)) {
|
|
5284
5329
|
if (value === undefined || nativeTargetNames.has(key))
|
|
5285
5330
|
continue;
|
|
5286
|
-
throw new
|
|
5331
|
+
throw new VibeError11({
|
|
5287
5332
|
code: "VIBE_INTERNAL",
|
|
5288
5333
|
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`,
|
|
5289
5334
|
meta: { model: model.name, field: key }
|
|
@@ -5335,8 +5380,95 @@ function buildQuery(params) {
|
|
|
5335
5380
|
// packages/runtime/src/diagnostics/preview.ts
|
|
5336
5381
|
import { getSqlDialect, renderStatement } from "@vibeorm/sql";
|
|
5337
5382
|
|
|
5383
|
+
// packages/runtime/src/extensions.ts
|
|
5384
|
+
import { VibeError as VibeError12, extensionManifestEntry } from "@vibeorm/schema";
|
|
5385
|
+
function assertExtensionManifest(params) {
|
|
5386
|
+
const { manifest } = params;
|
|
5387
|
+
const instances = params.instances ?? [];
|
|
5388
|
+
const live = new Map(instances.map((extension) => [extension.name, extensionManifestEntry({ extension })]));
|
|
5389
|
+
const problems = [];
|
|
5390
|
+
for (const baked of manifest) {
|
|
5391
|
+
const current = live.get(baked.name);
|
|
5392
|
+
if (current === undefined) {
|
|
5393
|
+
problems.push(`"${baked.name}" was baked into the client but not passed to VibeClient`);
|
|
5394
|
+
continue;
|
|
5395
|
+
}
|
|
5396
|
+
if (current.version !== baked.version) {
|
|
5397
|
+
problems.push(`"${baked.name}" version changed (generated ${baked.version}, live ${current.version})`);
|
|
5398
|
+
} else if (current.optionsHash !== baked.optionsHash) {
|
|
5399
|
+
problems.push(`"${baked.name}" options changed since generate`);
|
|
5400
|
+
}
|
|
5401
|
+
live.delete(baked.name);
|
|
5402
|
+
}
|
|
5403
|
+
for (const name of live.keys()) {
|
|
5404
|
+
problems.push(`"${name}" is configured but missing from the generated client`);
|
|
5405
|
+
}
|
|
5406
|
+
if (problems.length > 0) {
|
|
5407
|
+
throw new VibeError12({
|
|
5408
|
+
code: "VIBE_STALE_CLIENT",
|
|
5409
|
+
message: `extension list changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
|
|
5410
|
+
meta: { manifest, problems }
|
|
5411
|
+
});
|
|
5412
|
+
}
|
|
5413
|
+
}
|
|
5414
|
+
function buildExtensionOperatorMap(params) {
|
|
5415
|
+
const { instances, dialect, provider } = params;
|
|
5416
|
+
const map = new Map;
|
|
5417
|
+
const owner = new Map;
|
|
5418
|
+
for (const extension of instances) {
|
|
5419
|
+
for (const operator of extension.sql?.whereOperators ?? []) {
|
|
5420
|
+
const existing = owner.get(operator.name);
|
|
5421
|
+
if (existing !== undefined) {
|
|
5422
|
+
throw new VibeError12({
|
|
5423
|
+
code: "VIBE_CONFIG",
|
|
5424
|
+
message: `where operator "${operator.name}" is claimed by both "${existing}" and "${extension.name}"`,
|
|
5425
|
+
meta: { operator: operator.name }
|
|
5426
|
+
});
|
|
5427
|
+
}
|
|
5428
|
+
owner.set(operator.name, extension.name);
|
|
5429
|
+
map.set(operator.name, ({ column, operand, model, field }) => {
|
|
5430
|
+
const compiled = operator.compile({
|
|
5431
|
+
column,
|
|
5432
|
+
operand,
|
|
5433
|
+
dialect,
|
|
5434
|
+
provider,
|
|
5435
|
+
options: extension.options
|
|
5436
|
+
});
|
|
5437
|
+
if ("error" in compiled) {
|
|
5438
|
+
throw new VibeError12({
|
|
5439
|
+
code: "VIBE_VALIDATION",
|
|
5440
|
+
message: `"${operator.name}" on ${model}.${field}: ${compiled.error}`,
|
|
5441
|
+
meta: { model, field, operator: operator.name, extension: extension.name }
|
|
5442
|
+
});
|
|
5443
|
+
}
|
|
5444
|
+
return { kind: "fragment", fragment: compiled.fragment };
|
|
5445
|
+
});
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5448
|
+
return map.size === 0 ? null : map;
|
|
5449
|
+
}
|
|
5450
|
+
function buildConnectHooks(params) {
|
|
5451
|
+
const { instances, dialect, provider } = params;
|
|
5452
|
+
const hooks = [];
|
|
5453
|
+
for (const extension of instances) {
|
|
5454
|
+
const onConnect = extension.runtime?.onConnect;
|
|
5455
|
+
if (onConnect === undefined)
|
|
5456
|
+
continue;
|
|
5457
|
+
hooks.push(({ execute }) => onConnect({
|
|
5458
|
+
execute: (execParams) => execute({
|
|
5459
|
+
text: execParams.text,
|
|
5460
|
+
...execParams.values === undefined ? {} : { values: execParams.values }
|
|
5461
|
+
}),
|
|
5462
|
+
dialect,
|
|
5463
|
+
provider,
|
|
5464
|
+
options: extension.options
|
|
5465
|
+
}));
|
|
5466
|
+
}
|
|
5467
|
+
return hooks;
|
|
5468
|
+
}
|
|
5469
|
+
|
|
5338
5470
|
// packages/runtime/src/relation-key.ts
|
|
5339
|
-
import { VibeError as
|
|
5471
|
+
import { VibeError as VibeError13 } from "@vibeorm/schema";
|
|
5340
5472
|
function relationValueKey(params) {
|
|
5341
5473
|
const { value } = params;
|
|
5342
5474
|
if (value === null || value === undefined)
|
|
@@ -5369,7 +5501,7 @@ function relationTupleKey(params) {
|
|
|
5369
5501
|
if (value === null || value === undefined)
|
|
5370
5502
|
return null;
|
|
5371
5503
|
if (typeof value === "object" && !(value instanceof Date) && !(value instanceof Uint8Array)) {
|
|
5372
|
-
throw new
|
|
5504
|
+
throw new VibeError13({
|
|
5373
5505
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
5374
5506
|
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",
|
|
5375
5507
|
meta: { received: Object.prototype.toString.call(value) }
|
|
@@ -5395,7 +5527,7 @@ function assertWriteScope(params) {
|
|
|
5395
5527
|
}
|
|
5396
5528
|
|
|
5397
5529
|
// packages/runtime/src/nested-writes.ts
|
|
5398
|
-
import { VibeError as
|
|
5530
|
+
import { VibeError as VibeError14 } from "@vibeorm/schema";
|
|
5399
5531
|
function parentTupleOf(params) {
|
|
5400
5532
|
const { link, row } = params;
|
|
5401
5533
|
return link.parentKeys.map((field) => row[field.name]);
|
|
@@ -5442,10 +5574,10 @@ function detachAssignment(params) {
|
|
|
5442
5574
|
return out;
|
|
5443
5575
|
}
|
|
5444
5576
|
function validationError5(params) {
|
|
5445
|
-
return new
|
|
5577
|
+
return new VibeError14({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
5446
5578
|
}
|
|
5447
5579
|
function notFound(params) {
|
|
5448
|
-
return new
|
|
5580
|
+
return new VibeError14({ code: "VIBE_NOT_FOUND", message: params.message, meta: params.meta });
|
|
5449
5581
|
}
|
|
5450
5582
|
function isPlainObject4(value) {
|
|
5451
5583
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
@@ -5586,85 +5718,124 @@ function joinValues(params) {
|
|
|
5586
5718
|
const childValue = encodeValue({ dialect: dialect.name, field: pair.child, value: childKey });
|
|
5587
5719
|
return join.parentColumn === "A" ? { a: parentValue, b: childValue } : { a: childValue, b: parentValue };
|
|
5588
5720
|
}
|
|
5589
|
-
|
|
5590
|
-
const {
|
|
5721
|
+
function chunkKeyList(params) {
|
|
5722
|
+
const { dialect, keys, perKey, reserved } = params;
|
|
5723
|
+
if (keys.length === 0)
|
|
5724
|
+
return [];
|
|
5725
|
+
if (params.expanded !== true && dialect.inArrayStrategy !== "expand")
|
|
5726
|
+
return [keys];
|
|
5727
|
+
const size = Math.max(1, Math.floor((dialect.paramBudget - reserved) / perKey));
|
|
5728
|
+
const chunks = [];
|
|
5729
|
+
for (let start = 0;start < keys.length; start += size)
|
|
5730
|
+
chunks.push(keys.slice(start, start + size));
|
|
5731
|
+
return chunks;
|
|
5732
|
+
}
|
|
5733
|
+
async function insertJoinRows(params) {
|
|
5734
|
+
const { run, dialect, link, parentKey, childKeys } = params;
|
|
5735
|
+
if (childKeys.length === 0)
|
|
5736
|
+
return;
|
|
5591
5737
|
const join = link.join;
|
|
5592
|
-
const
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
kind: "insert",
|
|
5596
|
-
table: { name: join.tableName },
|
|
5597
|
-
columns: ["A", "B"],
|
|
5598
|
-
rows: [[a, b]],
|
|
5599
|
-
onConflict: { columns: ["A", "B"], doNothing: true }
|
|
5600
|
-
},
|
|
5601
|
-
model: link.relation.model,
|
|
5602
|
-
method: "nested.connect"
|
|
5738
|
+
const rows = childKeys.map((childKey) => {
|
|
5739
|
+
const { a, b } = joinValues({ dialect, link, parentKey, childKey });
|
|
5740
|
+
return [a, b];
|
|
5603
5741
|
});
|
|
5742
|
+
const perChunk = Math.max(1, Math.floor(dialect.paramBudget / 2));
|
|
5743
|
+
for (let start = 0;start < rows.length; start += perChunk) {
|
|
5744
|
+
await run.statement({
|
|
5745
|
+
statement: {
|
|
5746
|
+
kind: "insert",
|
|
5747
|
+
table: { name: join.tableName },
|
|
5748
|
+
columns: ["A", "B"],
|
|
5749
|
+
rows: rows.slice(start, start + perChunk),
|
|
5750
|
+
onConflict: { columns: ["A", "B"], doNothing: true }
|
|
5751
|
+
},
|
|
5752
|
+
model: link.relation.model,
|
|
5753
|
+
method: "nested.connect"
|
|
5754
|
+
});
|
|
5755
|
+
}
|
|
5604
5756
|
}
|
|
5605
5757
|
async function deleteJoinRows(params) {
|
|
5606
5758
|
const { run, dialect, link, parentKey, childKeys } = params;
|
|
5607
5759
|
const join = link.join;
|
|
5608
5760
|
const pair = singleKeyPair({ link, what: "implicit many-to-many writes" });
|
|
5609
5761
|
const parentValue = encodeValue({ dialect: dialect.name, field: pair.parent, value: parentKey });
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
{
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5762
|
+
const chunks = childKeys === undefined ? [undefined] : chunkKeyList({ dialect, keys: childKeys, perKey: 1, reserved: 1 });
|
|
5763
|
+
for (const chunk of chunks) {
|
|
5764
|
+
await run.statement({
|
|
5765
|
+
statement: {
|
|
5766
|
+
kind: "delete",
|
|
5767
|
+
table: { name: join.tableName },
|
|
5768
|
+
where: {
|
|
5769
|
+
kind: "and",
|
|
5770
|
+
operands: [
|
|
5771
|
+
{ kind: "cmp", op: "eq", column: { name: join.parentColumn }, value: parentValue },
|
|
5772
|
+
...chunk === undefined ? [] : [
|
|
5773
|
+
{
|
|
5774
|
+
kind: "inArray",
|
|
5775
|
+
column: { name: join.childColumn },
|
|
5776
|
+
values: chunk.map((key) => encodeValue({ dialect: dialect.name, field: pair.child, value: key }))
|
|
5777
|
+
}
|
|
5778
|
+
]
|
|
5624
5779
|
]
|
|
5625
|
-
|
|
5626
|
-
}
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
}
|
|
5780
|
+
}
|
|
5781
|
+
},
|
|
5782
|
+
model: link.relation.model,
|
|
5783
|
+
method: "nested.disconnect"
|
|
5784
|
+
});
|
|
5785
|
+
}
|
|
5631
5786
|
}
|
|
5632
|
-
async function
|
|
5633
|
-
const { run, ctx, link, parentKey,
|
|
5634
|
-
|
|
5635
|
-
const pair = singleKeyPair({ link, what: "implicit many-to-many writes" });
|
|
5636
|
-
const childValue = encodeValue({ dialect: ctx.dialect.name, field: pair.child, value: childKey });
|
|
5637
|
-
if (members.some((member) => String(member) === String(childValue)))
|
|
5787
|
+
async function assertM2MMembers(params) {
|
|
5788
|
+
const { run, ctx, link, parentKey, childKeys, target, verb, relation } = params;
|
|
5789
|
+
if (childKeys.length === 0)
|
|
5638
5790
|
return;
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5791
|
+
const members = await loadMemberKeys({ run, ctx, link, parentKey, childKeys });
|
|
5792
|
+
const pair = singleKeyPair({ link, what: "implicit many-to-many writes" });
|
|
5793
|
+
const connected = new Set(members.map((member) => relationValueKey({ value: decodeValue({ dialect: ctx.dialect.name, field: pair.child, value: member }) })));
|
|
5794
|
+
for (const childKey of childKeys) {
|
|
5795
|
+
const childValue = decodeValue({ dialect: ctx.dialect.name, field: pair.child, value: childKey });
|
|
5796
|
+
if (connected.has(relationValueKey({ value: childValue })))
|
|
5797
|
+
continue;
|
|
5798
|
+
throw notFound({
|
|
5799
|
+
message: `nested "${verb}" on ${link.relation.model}.${relation}: ${target} is not connected`,
|
|
5800
|
+
meta: { relation }
|
|
5801
|
+
});
|
|
5802
|
+
}
|
|
5643
5803
|
}
|
|
5644
|
-
async function
|
|
5645
|
-
const { run, dialect, link,
|
|
5804
|
+
async function deleteJoinRowsForChildren(params) {
|
|
5805
|
+
const { run, dialect, link, childKeys } = params;
|
|
5806
|
+
if (childKeys.length === 0)
|
|
5807
|
+
return;
|
|
5646
5808
|
const join = link.join;
|
|
5647
|
-
const
|
|
5648
|
-
|
|
5649
|
-
field: singleKeyPair({ link, what: "implicit many-to-many writes" }).child,
|
|
5650
|
-
value: childKey
|
|
5651
|
-
});
|
|
5809
|
+
const pair = singleKeyPair({ link, what: "implicit many-to-many writes" });
|
|
5810
|
+
const values = childKeys.map((childKey) => encodeValue({ dialect: dialect.name, field: pair.child, value: childKey }));
|
|
5652
5811
|
const isSelf = link.relation.model === link.relation.targetModel;
|
|
5653
|
-
|
|
5654
|
-
statement
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5812
|
+
for (const chunk of chunkKeyList({ dialect, keys: values, perKey: isSelf ? 2 : 1, reserved: 0 })) {
|
|
5813
|
+
await run.statement({
|
|
5814
|
+
statement: {
|
|
5815
|
+
kind: "delete",
|
|
5816
|
+
table: { name: join.tableName },
|
|
5817
|
+
where: isSelf ? {
|
|
5818
|
+
kind: "or",
|
|
5819
|
+
operands: [
|
|
5820
|
+
{ kind: "inArray", column: { name: "A" }, values: [...chunk] },
|
|
5821
|
+
{ kind: "inArray", column: { name: "B" }, values: [...chunk] }
|
|
5822
|
+
]
|
|
5823
|
+
} : { kind: "inArray", column: { name: join.childColumn }, values: [...chunk] }
|
|
5824
|
+
},
|
|
5825
|
+
model: link.relation.model,
|
|
5826
|
+
method: "nested.delete"
|
|
5827
|
+
});
|
|
5828
|
+
}
|
|
5829
|
+
}
|
|
5830
|
+
async function deleteChildrenByKey(params) {
|
|
5831
|
+
const { run, dialect, link, childKeys } = params;
|
|
5832
|
+
for (const chunk of chunkKeyList({ dialect, keys: childKeys, perKey: 1, reserved: 0 })) {
|
|
5833
|
+
await run.affected({
|
|
5834
|
+
model: link.target.name,
|
|
5835
|
+
method: "deleteMany",
|
|
5836
|
+
args: { where: childAssignment({ link, values: [{ in: [...chunk] }] }) }
|
|
5837
|
+
});
|
|
5838
|
+
}
|
|
5668
5839
|
}
|
|
5669
5840
|
function singleVerb(params) {
|
|
5670
5841
|
const { op } = params;
|
|
@@ -5703,6 +5874,55 @@ async function resolveOwnerCreateVerb(params) {
|
|
|
5703
5874
|
}
|
|
5704
5875
|
}
|
|
5705
5876
|
}
|
|
5877
|
+
function isFilterableUniqueWhere(params) {
|
|
5878
|
+
const { target, where } = params;
|
|
5879
|
+
const entries = Object.entries(where).filter(([, value]) => value !== undefined);
|
|
5880
|
+
if (entries.length === 0)
|
|
5881
|
+
return false;
|
|
5882
|
+
if (!entries.every(([key, value]) => target.fieldByName.has(key) && isDirectValue2(value)))
|
|
5883
|
+
return false;
|
|
5884
|
+
return isUniqueWhere({ model: target, where });
|
|
5885
|
+
}
|
|
5886
|
+
async function connectChildrenByFk(params) {
|
|
5887
|
+
const { dialect, run, op, items, fk, verb } = params;
|
|
5888
|
+
const target = op.link.target;
|
|
5889
|
+
if (items.length === 0)
|
|
5890
|
+
return;
|
|
5891
|
+
const batchable = items.length > 1 && items.every((item) => isFilterableUniqueWhere({ target, where: item }));
|
|
5892
|
+
if (!batchable) {
|
|
5893
|
+
for (const item of items) {
|
|
5894
|
+
await run.single({ model: target.name, method: "update", args: { where: item, data: fk } });
|
|
5895
|
+
}
|
|
5896
|
+
return;
|
|
5897
|
+
}
|
|
5898
|
+
let perKey = 1;
|
|
5899
|
+
for (const item of items)
|
|
5900
|
+
perKey = Math.max(perKey, Object.values(item).filter((value) => value !== undefined && value !== null).length);
|
|
5901
|
+
const reserved = Object.values(fk).filter((value) => value !== undefined).length + target.fields.filter((field) => field.isUpdatedAt && fk[field.name] === undefined).length;
|
|
5902
|
+
let affected = 0;
|
|
5903
|
+
for (const chunk of chunkKeyList({ dialect, keys: items, perKey, reserved, expanded: true })) {
|
|
5904
|
+
affected += await run.affected({
|
|
5905
|
+
model: target.name,
|
|
5906
|
+
method: "updateMany",
|
|
5907
|
+
args: { where: { OR: chunk.map((item) => ({ ...item })) }, data: fk }
|
|
5908
|
+
});
|
|
5909
|
+
}
|
|
5910
|
+
if (affected >= items.length)
|
|
5911
|
+
return;
|
|
5912
|
+
for (const item of items) {
|
|
5913
|
+
const found = await run.rows({
|
|
5914
|
+
model: target.name,
|
|
5915
|
+
method: "findUnique",
|
|
5916
|
+
args: { where: item, select: childKeySelect({ link: op.link }) }
|
|
5917
|
+
});
|
|
5918
|
+
if (found[0] !== undefined)
|
|
5919
|
+
continue;
|
|
5920
|
+
throw notFound({
|
|
5921
|
+
message: `no ${target.name} record matched the where clause of update()`,
|
|
5922
|
+
meta: { model: target.name, method: "update", relation: op.name, verb }
|
|
5923
|
+
});
|
|
5924
|
+
}
|
|
5925
|
+
}
|
|
5706
5926
|
function sameDefinedKeys(rows) {
|
|
5707
5927
|
const first = rows[0];
|
|
5708
5928
|
if (first === undefined)
|
|
@@ -5731,14 +5951,14 @@ async function applyChildCreateOps(params) {
|
|
|
5731
5951
|
continue;
|
|
5732
5952
|
}
|
|
5733
5953
|
}
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5954
|
+
if (link.kind === "manyToMany") {
|
|
5955
|
+
const childKeys = [];
|
|
5956
|
+
for (const item of items) {
|
|
5737
5957
|
if (verb === "connect") {
|
|
5738
|
-
|
|
5958
|
+
childKeys.push((await resolveTargetKeys({ run, link, where: item, what: `connect ${op.name}` }))[0]);
|
|
5739
5959
|
} else if (verb === "create") {
|
|
5740
5960
|
const child = await runCreate({ ctx, run, model: target, data: item });
|
|
5741
|
-
|
|
5961
|
+
childKeys.push(childTupleOf({ link, row: child })[0]);
|
|
5742
5962
|
} else {
|
|
5743
5963
|
requireShape({ op, verb, item, keys: ["where", "create"] });
|
|
5744
5964
|
const found = await run.rows({
|
|
@@ -5746,18 +5966,22 @@ async function applyChildCreateOps(params) {
|
|
|
5746
5966
|
method: "findUnique",
|
|
5747
5967
|
args: { where: item.where, select: childKeySelect({ link }) }
|
|
5748
5968
|
});
|
|
5749
|
-
|
|
5969
|
+
childKeys.push(found[0] !== undefined ? childTupleOf({ link, row: found[0] })[0] : childTupleOf({
|
|
5750
5970
|
link,
|
|
5751
5971
|
row: await runCreate({ ctx, run, model: target, data: item.create })
|
|
5752
|
-
})[0];
|
|
5972
|
+
})[0]);
|
|
5753
5973
|
}
|
|
5754
|
-
await insertJoinRow({ run, dialect: ctx.dialect, link, parentKey, childKey });
|
|
5755
|
-
continue;
|
|
5756
5974
|
}
|
|
5975
|
+
await insertJoinRows({ run, dialect: ctx.dialect, link, parentKey, childKeys });
|
|
5976
|
+
continue;
|
|
5977
|
+
}
|
|
5978
|
+
if (verb === "connect") {
|
|
5979
|
+
await connectChildrenByFk({ dialect: ctx.dialect, run, op, items, fk, verb });
|
|
5980
|
+
continue;
|
|
5981
|
+
}
|
|
5982
|
+
for (const item of items) {
|
|
5757
5983
|
if (verb === "create") {
|
|
5758
5984
|
await runCreate({ ctx, run, model: target, data: { ...item, ...fk } });
|
|
5759
|
-
} else if (verb === "connect") {
|
|
5760
|
-
await run.single({ model: target.name, method: "update", args: { where: item, data: fk } });
|
|
5761
5985
|
} else {
|
|
5762
5986
|
requireShape({ op, verb, item, keys: ["where", "create"] });
|
|
5763
5987
|
const found = await run.rows({
|
|
@@ -5956,28 +6180,53 @@ async function resolveOwnerUpdateVerb(params) {
|
|
|
5956
6180
|
}
|
|
5957
6181
|
}
|
|
5958
6182
|
async function loadMemberKeys(params) {
|
|
5959
|
-
const { run, ctx, link, parentKey } = params;
|
|
6183
|
+
const { run, ctx, link, parentKey, childKeys } = params;
|
|
5960
6184
|
const join = link.join;
|
|
5961
|
-
const
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
6185
|
+
const pair = singleKeyPair({ link, what: "implicit many-to-many writes" });
|
|
6186
|
+
const parentMatch = {
|
|
6187
|
+
kind: "cmp",
|
|
6188
|
+
op: "eq",
|
|
6189
|
+
column: { name: join.parentColumn },
|
|
6190
|
+
value: encodeValue({ dialect: ctx.dialect.name, field: pair.parent, value: parentKey })
|
|
6191
|
+
};
|
|
6192
|
+
const scan = async (narrow) => {
|
|
6193
|
+
const rows = await run.statement({
|
|
6194
|
+
statement: {
|
|
6195
|
+
kind: "select",
|
|
6196
|
+
table: { name: join.tableName },
|
|
6197
|
+
columns: [{ name: join.childColumn }],
|
|
6198
|
+
where: narrow === null ? parentMatch : {
|
|
6199
|
+
kind: "and",
|
|
6200
|
+
operands: [
|
|
6201
|
+
parentMatch,
|
|
6202
|
+
{ kind: "inArray", column: { name: join.childColumn }, values: [...narrow] }
|
|
6203
|
+
]
|
|
6204
|
+
}
|
|
6205
|
+
},
|
|
6206
|
+
model: link.relation.model,
|
|
6207
|
+
method: "nested.members"
|
|
6208
|
+
});
|
|
6209
|
+
return rows.map((row) => row[join.childColumn]);
|
|
6210
|
+
};
|
|
6211
|
+
if (childKeys === undefined)
|
|
6212
|
+
return await scan(null);
|
|
6213
|
+
const values = childKeys.map((childKey) => encodeValue({ dialect: ctx.dialect.name, field: pair.child, value: childKey }));
|
|
6214
|
+
const found = [];
|
|
6215
|
+
for (const chunk of chunkKeyList({ dialect: ctx.dialect, keys: values, perKey: 1, reserved: 1 })) {
|
|
6216
|
+
for (const member of await scan(chunk))
|
|
6217
|
+
found.push(member);
|
|
6218
|
+
}
|
|
6219
|
+
return found;
|
|
6220
|
+
}
|
|
6221
|
+
async function membershipFilter(params) {
|
|
6222
|
+
const { run, ctx, link, parentKey } = params;
|
|
6223
|
+
const inverse = inverseRelationName({ link });
|
|
6224
|
+
if (inverse !== null) {
|
|
6225
|
+
const pair = singleKeyPair({ link, what: "implicit many-to-many writes" });
|
|
6226
|
+
return { [inverse]: { some: { [pair.parent.name]: parentKey } } };
|
|
6227
|
+
}
|
|
6228
|
+
const members = await loadMemberKeys({ run, ctx, link, parentKey });
|
|
6229
|
+
return childAssignment({ link, values: [{ in: members }] });
|
|
5981
6230
|
}
|
|
5982
6231
|
async function applyChildUpdateOps(params) {
|
|
5983
6232
|
const { ctx, run, op, parentValues } = params;
|
|
@@ -5999,10 +6248,11 @@ async function applyChildUpdateOps(params) {
|
|
|
5999
6248
|
const items = toItems({ op, verb, operand: operand ?? [] });
|
|
6000
6249
|
if (isM2M) {
|
|
6001
6250
|
await deleteJoinRows({ run, dialect: ctx.dialect, link, parentKey });
|
|
6251
|
+
const childKeys = [];
|
|
6002
6252
|
for (const item of items) {
|
|
6003
|
-
|
|
6004
|
-
await insertJoinRow({ run, dialect: ctx.dialect, link, parentKey, childKey });
|
|
6253
|
+
childKeys.push((await resolveTargetKeys({ run, link, where: item, what: `set ${op.name}` }))[0]);
|
|
6005
6254
|
}
|
|
6255
|
+
await insertJoinRows({ run, dialect: ctx.dialect, link, parentKey, childKeys });
|
|
6006
6256
|
} else {
|
|
6007
6257
|
const detach = childDetach({ op, verb });
|
|
6008
6258
|
await run.affected({
|
|
@@ -6010,9 +6260,7 @@ async function applyChildUpdateOps(params) {
|
|
|
6010
6260
|
method: "updateMany",
|
|
6011
6261
|
args: { where: fk, data: detach }
|
|
6012
6262
|
});
|
|
6013
|
-
|
|
6014
|
-
await run.single({ model: target.name, method: "update", args: { where: item, data: fk } });
|
|
6015
|
-
}
|
|
6263
|
+
await connectChildrenByFk({ dialect: ctx.dialect, run, op, items, fk, verb });
|
|
6016
6264
|
}
|
|
6017
6265
|
break;
|
|
6018
6266
|
}
|
|
@@ -6054,23 +6302,31 @@ async function applyChildUpdateOps(params) {
|
|
|
6054
6302
|
break;
|
|
6055
6303
|
}
|
|
6056
6304
|
const items = toItems({ op, verb, operand });
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
await
|
|
6061
|
-
await deleteJoinRowsForChild({ run, dialect: ctx.dialect, link, childKey });
|
|
6062
|
-
await run.single({
|
|
6063
|
-
model: target.name,
|
|
6064
|
-
method: "delete",
|
|
6065
|
-
args: { where: childAssignment({ link, values: [childKey] }) }
|
|
6066
|
-
});
|
|
6067
|
-
} else {
|
|
6068
|
-
await run.affected({
|
|
6069
|
-
model: target.name,
|
|
6070
|
-
method: "deleteMany",
|
|
6071
|
-
args: { where: { AND: [item, fk] } }
|
|
6072
|
-
});
|
|
6305
|
+
if (isM2M) {
|
|
6306
|
+
const childKeys = [];
|
|
6307
|
+
for (const item of items) {
|
|
6308
|
+
childKeys.push((await resolveTargetKeys({ run, link, where: item, what: `delete ${op.name}` }))[0]);
|
|
6073
6309
|
}
|
|
6310
|
+
await assertM2MMembers({
|
|
6311
|
+
run,
|
|
6312
|
+
ctx,
|
|
6313
|
+
link,
|
|
6314
|
+
parentKey,
|
|
6315
|
+
childKeys,
|
|
6316
|
+
target: target.name,
|
|
6317
|
+
verb: "delete",
|
|
6318
|
+
relation: op.name
|
|
6319
|
+
});
|
|
6320
|
+
await deleteJoinRowsForChildren({ run, dialect: ctx.dialect, link, childKeys });
|
|
6321
|
+
await deleteChildrenByKey({ run, dialect: ctx.dialect, link, childKeys });
|
|
6322
|
+
break;
|
|
6323
|
+
}
|
|
6324
|
+
for (const item of items) {
|
|
6325
|
+
await run.affected({
|
|
6326
|
+
model: target.name,
|
|
6327
|
+
method: "deleteMany",
|
|
6328
|
+
args: { where: { AND: [item, fk] } }
|
|
6329
|
+
});
|
|
6074
6330
|
}
|
|
6075
6331
|
break;
|
|
6076
6332
|
}
|
|
@@ -6096,41 +6352,44 @@ async function applyChildUpdateOps(params) {
|
|
|
6096
6352
|
break;
|
|
6097
6353
|
}
|
|
6098
6354
|
const items = toItems({ op, verb, operand });
|
|
6099
|
-
for (const item of items)
|
|
6355
|
+
for (const item of items)
|
|
6100
6356
|
requireShape({ op, verb, item, keys: ["where", "data"] });
|
|
6101
|
-
|
|
6102
|
-
|
|
6357
|
+
if (isM2M) {
|
|
6358
|
+
const childKeys = [];
|
|
6359
|
+
for (const item of items) {
|
|
6360
|
+
childKeys.push((await resolveTargetKeys({
|
|
6103
6361
|
run,
|
|
6104
6362
|
link,
|
|
6105
6363
|
where: item.where,
|
|
6106
6364
|
what: `update ${op.name}`
|
|
6107
|
-
}))[0];
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
}
|
|
6365
|
+
}))[0]);
|
|
6366
|
+
}
|
|
6367
|
+
await assertM2MMembers({
|
|
6368
|
+
run,
|
|
6369
|
+
ctx,
|
|
6370
|
+
link,
|
|
6371
|
+
parentKey,
|
|
6372
|
+
childKeys,
|
|
6373
|
+
target: target.name,
|
|
6374
|
+
verb: "update",
|
|
6375
|
+
relation: op.name
|
|
6376
|
+
});
|
|
6377
|
+
for (const item of items) {
|
|
6121
6378
|
await run.single({ model: target.name, method: "update", args: { where: item.where, data: item.data } });
|
|
6122
|
-
}
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6379
|
+
}
|
|
6380
|
+
break;
|
|
6381
|
+
}
|
|
6382
|
+
for (const item of items) {
|
|
6383
|
+
const affected = await run.affected({
|
|
6384
|
+
model: target.name,
|
|
6385
|
+
method: "updateMany",
|
|
6386
|
+
args: { where: { AND: [item.where, fk] }, data: item.data }
|
|
6387
|
+
});
|
|
6388
|
+
if (affected === 0) {
|
|
6389
|
+
throw notFound({
|
|
6390
|
+
message: `nested "update" on ${link.relation.model}.${op.name}: no connected ${target.name} matched`,
|
|
6391
|
+
meta: { relation: op.name }
|
|
6127
6392
|
});
|
|
6128
|
-
if (affected === 0) {
|
|
6129
|
-
throw notFound({
|
|
6130
|
-
message: `nested "update" on ${link.relation.model}.${op.name}: no connected ${target.name} matched`,
|
|
6131
|
-
meta: { relation: op.name }
|
|
6132
|
-
});
|
|
6133
|
-
}
|
|
6134
6393
|
}
|
|
6135
6394
|
}
|
|
6136
6395
|
break;
|
|
@@ -6146,12 +6405,11 @@ async function applyChildUpdateOps(params) {
|
|
|
6146
6405
|
});
|
|
6147
6406
|
}
|
|
6148
6407
|
if (isM2M) {
|
|
6149
|
-
const members = await loadMemberKeys({ run, ctx, link, parentKey });
|
|
6150
6408
|
await run.affected({
|
|
6151
6409
|
model: target.name,
|
|
6152
6410
|
method: "updateMany",
|
|
6153
6411
|
args: {
|
|
6154
|
-
where: { AND: [item.where ?? {},
|
|
6412
|
+
where: { AND: [item.where ?? {}, await membershipFilter({ run, ctx, link, parentKey })] },
|
|
6155
6413
|
data
|
|
6156
6414
|
}
|
|
6157
6415
|
});
|
|
@@ -6169,35 +6427,17 @@ async function applyChildUpdateOps(params) {
|
|
|
6169
6427
|
const items = toItems({ op, verb, operand });
|
|
6170
6428
|
for (const item of items) {
|
|
6171
6429
|
if (isM2M) {
|
|
6172
|
-
const members = await loadMemberKeys({ run, ctx, link, parentKey });
|
|
6173
6430
|
const victims = await run.rows({
|
|
6174
6431
|
model: target.name,
|
|
6175
6432
|
method: "findMany",
|
|
6176
6433
|
args: {
|
|
6177
|
-
where: { AND: [item,
|
|
6434
|
+
where: { AND: [item, await membershipFilter({ run, ctx, link, parentKey })] },
|
|
6178
6435
|
select: childKeySelect({ link })
|
|
6179
6436
|
}
|
|
6180
6437
|
});
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
dialect: ctx.dialect,
|
|
6185
|
-
link,
|
|
6186
|
-
childKey: childTupleOf({ link, row: victim })[0]
|
|
6187
|
-
});
|
|
6188
|
-
}
|
|
6189
|
-
if (victims.length > 0) {
|
|
6190
|
-
await run.affected({
|
|
6191
|
-
model: target.name,
|
|
6192
|
-
method: "deleteMany",
|
|
6193
|
-
args: {
|
|
6194
|
-
where: childAssignment({
|
|
6195
|
-
link,
|
|
6196
|
-
values: [{ in: victims.map((victim) => childTupleOf({ link, row: victim })[0]) }]
|
|
6197
|
-
})
|
|
6198
|
-
}
|
|
6199
|
-
});
|
|
6200
|
-
}
|
|
6438
|
+
const childKeys = victims.map((victim) => childTupleOf({ link, row: victim })[0]);
|
|
6439
|
+
await deleteJoinRowsForChildren({ run, dialect: ctx.dialect, link, childKeys });
|
|
6440
|
+
await deleteChildrenByKey({ run, dialect: ctx.dialect, link, childKeys });
|
|
6201
6441
|
} else {
|
|
6202
6442
|
await run.affected({
|
|
6203
6443
|
model: target.name,
|
|
@@ -6251,12 +6491,12 @@ async function applyChildUpdateOps(params) {
|
|
|
6251
6491
|
});
|
|
6252
6492
|
if (found[0] !== undefined) {
|
|
6253
6493
|
if (isM2M) {
|
|
6254
|
-
await
|
|
6494
|
+
await assertM2MMembers({
|
|
6255
6495
|
run,
|
|
6256
6496
|
ctx,
|
|
6257
6497
|
link,
|
|
6258
6498
|
parentKey,
|
|
6259
|
-
|
|
6499
|
+
childKeys: [childTupleOf({ link, row: found[0] })[0]],
|
|
6260
6500
|
target: target.name,
|
|
6261
6501
|
verb: "upsert",
|
|
6262
6502
|
relation: op.name
|
|
@@ -6270,12 +6510,12 @@ async function applyChildUpdateOps(params) {
|
|
|
6270
6510
|
await run.single({ model: target.name, method: "update", args: { where: item.where, data: item.update } });
|
|
6271
6511
|
} else if (isM2M) {
|
|
6272
6512
|
const child = await runCreate({ ctx, run, model: target, data: item.create });
|
|
6273
|
-
await
|
|
6513
|
+
await insertJoinRows({
|
|
6274
6514
|
run,
|
|
6275
6515
|
dialect: ctx.dialect,
|
|
6276
6516
|
link,
|
|
6277
6517
|
parentKey,
|
|
6278
|
-
|
|
6518
|
+
childKeys: [childTupleOf({ link, row: child })[0]]
|
|
6279
6519
|
});
|
|
6280
6520
|
} else {
|
|
6281
6521
|
await runCreate({
|
|
@@ -6294,20 +6534,23 @@ async function applyChildUpdateOps(params) {
|
|
|
6294
6534
|
async function updateWithNested(params) {
|
|
6295
6535
|
const { ctx, model, where, data } = params;
|
|
6296
6536
|
const { scalars, ops } = partitionData({ meta: ctx.meta, model, data, phase: "update" });
|
|
6537
|
+
const ownerOps = ops.filter((op) => op.link.kind === "toOneOwner");
|
|
6538
|
+
const needsPreRead = ownerOps.length > 0 || Object.keys(scalars).length === 0;
|
|
6297
6539
|
return await ctx.transactional(async (run) => {
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6540
|
+
let pre;
|
|
6541
|
+
if (needsPreRead) {
|
|
6542
|
+
const preRows = await run.rows({ model: model.name, method: "findUnique", args: { where } });
|
|
6543
|
+
pre = preRows[0];
|
|
6544
|
+
if (pre === undefined) {
|
|
6545
|
+
throw notFound({
|
|
6546
|
+
message: `no ${model.name} record matched the where clause of update()`,
|
|
6547
|
+
meta: { model: model.name }
|
|
6548
|
+
});
|
|
6549
|
+
}
|
|
6305
6550
|
}
|
|
6306
6551
|
const fkSets = {};
|
|
6307
6552
|
const deferred = [];
|
|
6308
|
-
for (const op of
|
|
6309
|
-
if (op.link.kind !== "toOneOwner")
|
|
6310
|
-
continue;
|
|
6553
|
+
for (const op of ownerOps) {
|
|
6311
6554
|
await resolveOwnerUpdateVerb({ ctx, run, op, pre, fkSets, deferred });
|
|
6312
6555
|
}
|
|
6313
6556
|
assertWriteScope({ data, assignments: fkSets });
|
|
@@ -6467,7 +6710,9 @@ function previewOperation(params) {
|
|
|
6467
6710
|
const { schema, model, method, args, relationStrategy, clientOptions } = params;
|
|
6468
6711
|
const includeValues = params.includeParameterValues === true;
|
|
6469
6712
|
const dialect = getSqlDialect({ dialect: params.dialect });
|
|
6470
|
-
const
|
|
6713
|
+
const baseMeta = buildRuntimeMeta({ schema });
|
|
6714
|
+
const operatorMap = buildExtensionOperatorMap({ instances: params.extensions ?? [], dialect: params.dialect, provider: schema.datasource.provider });
|
|
6715
|
+
const meta = operatorMap === null ? baseMeta : { ...baseMeta, extensionOperators: operatorMap };
|
|
6471
6716
|
const modelMeta = getModelMeta({ meta, model });
|
|
6472
6717
|
const writesNested = (method === "create" || method === "update" || method === "upsert") && (hasNestedWrites({ model: modelMeta, data: args?.data }) || hasNestedWrites({ model: modelMeta, data: args?.create }) || hasNestedWrites({ model: modelMeta, data: args?.update }));
|
|
6473
6718
|
const effectiveArgs = args === undefined || !writesNested ? args : {
|
|
@@ -6549,7 +6794,7 @@ function previewOperation(params) {
|
|
|
6549
6794
|
};
|
|
6550
6795
|
}
|
|
6551
6796
|
// packages/runtime/src/diagnostics/plan.ts
|
|
6552
|
-
import { VibeError as
|
|
6797
|
+
import { VibeError as VibeError15, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
|
|
6553
6798
|
import { NATIVE_RLS, getSqlDialect as getSqlDialect2, renderPostgresExplain, renderPostgresWorkload, renderStatement as renderStatement2 } from "@vibeorm/sql";
|
|
6554
6799
|
var PLAN_DEFAULTS = Object.freeze({ maxNodes: 200, maxConditionLength: 2000 });
|
|
6555
6800
|
var CONDITION_KEYS = [
|
|
@@ -6568,7 +6813,7 @@ var CONDITION_KEYS = [
|
|
|
6568
6813
|
"Output"
|
|
6569
6814
|
];
|
|
6570
6815
|
function planError(params) {
|
|
6571
|
-
return new
|
|
6816
|
+
return new VibeError15({ code: "VIBE_ADAPTER", message: params.message, meta: {} });
|
|
6572
6817
|
}
|
|
6573
6818
|
function asRecord(value) {
|
|
6574
6819
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
@@ -6601,14 +6846,14 @@ function parsePostgresPlan(params) {
|
|
|
6601
6846
|
const maxNodes = params.maxNodes ?? PLAN_DEFAULTS.maxNodes;
|
|
6602
6847
|
const includeLiterals = params.includeLiterals === true;
|
|
6603
6848
|
if (!Number.isInteger(maxNodes) || maxNodes < 1) {
|
|
6604
|
-
throw new
|
|
6849
|
+
throw new VibeError15({ code: "VIBE_CONFIG", message: `maxNodes must be a positive integer, got ${String(maxNodes)}`, meta: { maxNodes } });
|
|
6605
6850
|
}
|
|
6606
6851
|
let parsed = payload;
|
|
6607
6852
|
if (typeof payload === "string") {
|
|
6608
6853
|
try {
|
|
6609
6854
|
parsed = JSON.parse(payload);
|
|
6610
6855
|
} catch {
|
|
6611
|
-
throw new
|
|
6856
|
+
throw new VibeError15({
|
|
6612
6857
|
code: "VIBE_ADAPTER",
|
|
6613
6858
|
message: "EXPLAIN returned malformed JSON",
|
|
6614
6859
|
meta: { reason: "malformed-plan-json", mode }
|
|
@@ -6674,22 +6919,35 @@ function parsePostgresPlan(params) {
|
|
|
6674
6919
|
};
|
|
6675
6920
|
}
|
|
6676
6921
|
var WRITE_LEADERS = ["INSERT", "UPDATE", "DELETE", "MERGE", "TRUNCATE", "CREATE", "DROP", "ALTER", "GRANT", "REVOKE", "COPY", "CALL", "DO"];
|
|
6922
|
+
var SIDE_EFFECT_CALLS = /\bpg_(?:try_)?advisory_(?:xact_)?lock(?:_shared)?\s*\(/i;
|
|
6677
6923
|
function refuse(params) {
|
|
6678
|
-
return new
|
|
6924
|
+
return new VibeError15({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta ?? {} });
|
|
6679
6925
|
}
|
|
6680
|
-
function
|
|
6926
|
+
function executableReadRefusal(params) {
|
|
6681
6927
|
const normalized = normalizeSql({ text: params.text });
|
|
6682
6928
|
const body = normalized.replace(/;\s*$/, "");
|
|
6683
6929
|
if (body.includes(";")) {
|
|
6684
|
-
|
|
6930
|
+
return refuse({ message: "EXPLAIN ANALYZE refuses multi-statement text: it would execute every statement in it", meta: {} });
|
|
6685
6931
|
}
|
|
6686
6932
|
const leader = (body.match(/^\(*\s*([A-Za-z]+)/)?.[1] ?? "").toUpperCase();
|
|
6687
6933
|
if (WRITE_LEADERS.includes(leader)) {
|
|
6688
|
-
|
|
6934
|
+
return refuse({ message: `EXPLAIN ANALYZE refuses a ${leader} statement: the executing mode would perform the write`, meta: { statement: leader } });
|
|
6689
6935
|
}
|
|
6690
6936
|
if (leader !== "SELECT" && leader !== "WITH" && leader !== "TABLE" && leader !== "VALUES") {
|
|
6691
|
-
|
|
6937
|
+
return refuse({ message: `EXPLAIN ANALYZE only accepts read statements; this one starts with "${leader || "?"}"`, meta: { statement: leader } });
|
|
6938
|
+
}
|
|
6939
|
+
if (SIDE_EFFECT_CALLS.test(body)) {
|
|
6940
|
+
return refuse({ message: "EXPLAIN ANALYZE refuses a statement that takes an advisory lock: the executing mode would take the lock on the diagnostic session", meta: { statement: leader } });
|
|
6692
6941
|
}
|
|
6942
|
+
return;
|
|
6943
|
+
}
|
|
6944
|
+
function assertExecutableRead(params) {
|
|
6945
|
+
const refusal = executableReadRefusal(params);
|
|
6946
|
+
if (refusal !== undefined)
|
|
6947
|
+
throw refusal;
|
|
6948
|
+
}
|
|
6949
|
+
function isExecutableRead(params) {
|
|
6950
|
+
return executableReadRefusal(params) === undefined;
|
|
6693
6951
|
}
|
|
6694
6952
|
async function readPlanContext(params) {
|
|
6695
6953
|
const { executor, dialect, rlsContextKeys } = params;
|
|
@@ -6753,59 +7011,20 @@ async function explainSql(params) {
|
|
|
6753
7011
|
await run(session("sessionReadOnly"));
|
|
6754
7012
|
}
|
|
6755
7013
|
try {
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
query: "setContext",
|
|
6761
|
-
key: `${NATIVE_RLS.settingPrefix}${key}`,
|
|
6762
|
-
value: value === null || value === undefined ? null : String(value)
|
|
6763
|
-
});
|
|
6764
|
-
await run(statement);
|
|
6765
|
-
}
|
|
6766
|
-
const context = await readPlanContext({ executor, dialect, rlsContextKeys: contextKeys });
|
|
6767
|
-
if (!context.representative) {
|
|
6768
|
-
if (params.requireRepresentativeContext === true) {
|
|
6769
|
-
throw new VibeError13({
|
|
6770
|
-
code: "VIBE_RLS_CONTEXT",
|
|
6771
|
-
message: `refusing to report a plan taken under a non-representative identity: ${context.reason ?? "role bypasses row policies"}`,
|
|
6772
|
-
meta: { currentUser: context.currentUser, bypassRls: context.bypassRls, superuser: context.superuser }
|
|
6773
|
-
});
|
|
6774
|
-
}
|
|
6775
|
-
warnings.push(context.reason ?? "the plan was taken under a role that bypasses row policies");
|
|
6776
|
-
}
|
|
6777
|
-
const explain = renderPostgresExplain({
|
|
6778
|
-
dialect,
|
|
6779
|
-
query: { text: sql2.text, values: [...sql2.values], returning: "none" },
|
|
6780
|
-
options: mode === "analyze" ? { analyze: true, buffers: true } : {}
|
|
6781
|
-
});
|
|
6782
|
-
const rows = await run(explain);
|
|
6783
|
-
const payload = rows[0]?.["QUERY PLAN"] ?? rows[0]?.["query plan"] ?? rows[0]?.["queryPlan"];
|
|
6784
|
-
const parsed = parsePostgresPlan({
|
|
6785
|
-
payload,
|
|
7014
|
+
const report = await explainOnSession({
|
|
7015
|
+
executor,
|
|
7016
|
+
dialect: params.dialect,
|
|
7017
|
+
sql: sql2,
|
|
6786
7018
|
mode,
|
|
7019
|
+
warnings,
|
|
7020
|
+
...rlsContext === undefined ? {} : { rlsContext },
|
|
7021
|
+
...params.requireRepresentativeContext === undefined ? {} : { requireRepresentativeContext: params.requireRepresentativeContext },
|
|
6787
7022
|
...params.maxNodes === undefined ? {} : { maxNodes: params.maxNodes },
|
|
6788
7023
|
...params.includeLiterals === undefined ? {} : { includeLiterals: params.includeLiterals }
|
|
6789
7024
|
});
|
|
6790
|
-
if (parsed.truncated)
|
|
6791
|
-
warnings.push(`the plan was truncated at ${parsed.nodeCount} nodes; raise maxNodes to see the rest`);
|
|
6792
|
-
if (mode === "estimate") {
|
|
6793
|
-
warnings.push("estimated plan: costs are planner cost units (not milliseconds) and rows are estimates (not observations); EXPLAIN still parses, plans and takes planning locks");
|
|
6794
|
-
}
|
|
6795
7025
|
if (transactional)
|
|
6796
7026
|
await run(session("sessionCommit"));
|
|
6797
|
-
return
|
|
6798
|
-
mode,
|
|
6799
|
-
root: parsed.root,
|
|
6800
|
-
nodeCount: parsed.nodeCount,
|
|
6801
|
-
truncated: parsed.truncated,
|
|
6802
|
-
estimatedTotalCostUnits: parsed.root.estimatedTotalCost,
|
|
6803
|
-
...parsed.planningTimeMs === undefined ? {} : { planningTimeMs: parsed.planningTimeMs },
|
|
6804
|
-
...parsed.executionTimeMs === undefined ? {} : { executionTimeMs: parsed.executionTimeMs },
|
|
6805
|
-
literalsRedacted: params.includeLiterals !== true,
|
|
6806
|
-
context,
|
|
6807
|
-
warnings
|
|
6808
|
-
};
|
|
7027
|
+
return report;
|
|
6809
7028
|
} catch (error) {
|
|
6810
7029
|
if (transactional) {
|
|
6811
7030
|
await run(session("sessionRollback")).catch(() => []);
|
|
@@ -6813,11 +7032,71 @@ async function explainSql(params) {
|
|
|
6813
7032
|
throw error;
|
|
6814
7033
|
}
|
|
6815
7034
|
}
|
|
7035
|
+
async function explainOnSession(params) {
|
|
7036
|
+
const { executor, sql: sql2, mode, rlsContext } = params;
|
|
7037
|
+
const dialect = requirePostgres({ dialect: params.dialect });
|
|
7038
|
+
const warnings = params.warnings ?? [];
|
|
7039
|
+
const contextKeys = rlsContext === undefined ? [] : Object.keys(rlsContext);
|
|
7040
|
+
const run = async (query) => executor({ text: query.text, values: query.values });
|
|
7041
|
+
for (const key of contextKeys) {
|
|
7042
|
+
const value = rlsContext?.[key];
|
|
7043
|
+
const statement = renderPostgresWorkload({
|
|
7044
|
+
dialect,
|
|
7045
|
+
query: "setContext",
|
|
7046
|
+
key: `${NATIVE_RLS.settingPrefix}${key}`,
|
|
7047
|
+
value: value === null || value === undefined ? null : String(value)
|
|
7048
|
+
});
|
|
7049
|
+
await run(statement);
|
|
7050
|
+
}
|
|
7051
|
+
const context = await readPlanContext({ executor, dialect, rlsContextKeys: contextKeys });
|
|
7052
|
+
if (!context.representative) {
|
|
7053
|
+
if (params.requireRepresentativeContext === true) {
|
|
7054
|
+
throw new VibeError15({
|
|
7055
|
+
code: "VIBE_RLS_CONTEXT",
|
|
7056
|
+
message: `refusing to report a plan taken under a non-representative identity: ${context.reason ?? "role bypasses row policies"}`,
|
|
7057
|
+
meta: { currentUser: context.currentUser, bypassRls: context.bypassRls, superuser: context.superuser }
|
|
7058
|
+
});
|
|
7059
|
+
}
|
|
7060
|
+
warnings.push(context.reason ?? "the plan was taken under a role that bypasses row policies");
|
|
7061
|
+
}
|
|
7062
|
+
const explain = renderPostgresExplain({
|
|
7063
|
+
dialect,
|
|
7064
|
+
query: { text: sql2.text, values: [...sql2.values], returning: "none" },
|
|
7065
|
+
options: mode === "analyze" ? { analyze: true, buffers: true } : {}
|
|
7066
|
+
});
|
|
7067
|
+
const rows = await run(explain);
|
|
7068
|
+
const payload = rows[0]?.["QUERY PLAN"] ?? rows[0]?.["query plan"] ?? rows[0]?.["queryPlan"];
|
|
7069
|
+
const parsed = parsePostgresPlan({
|
|
7070
|
+
payload,
|
|
7071
|
+
mode,
|
|
7072
|
+
...params.maxNodes === undefined ? {} : { maxNodes: params.maxNodes },
|
|
7073
|
+
...params.includeLiterals === undefined ? {} : { includeLiterals: params.includeLiterals }
|
|
7074
|
+
});
|
|
7075
|
+
if (parsed.truncated)
|
|
7076
|
+
warnings.push(`the plan was truncated at ${parsed.nodeCount} nodes; raise maxNodes to see the rest`);
|
|
7077
|
+
if (mode === "estimate") {
|
|
7078
|
+
warnings.push("estimated plan: costs are planner cost units (not milliseconds) and rows are estimates (not observations); EXPLAIN still parses, plans and takes planning locks");
|
|
7079
|
+
}
|
|
7080
|
+
return {
|
|
7081
|
+
mode,
|
|
7082
|
+
root: parsed.root,
|
|
7083
|
+
nodeCount: parsed.nodeCount,
|
|
7084
|
+
truncated: parsed.truncated,
|
|
7085
|
+
estimatedTotalCostUnits: parsed.root.estimatedTotalCost,
|
|
7086
|
+
...parsed.planningTimeMs === undefined ? {} : { planningTimeMs: parsed.planningTimeMs },
|
|
7087
|
+
...parsed.executionTimeMs === undefined ? {} : { executionTimeMs: parsed.executionTimeMs },
|
|
7088
|
+
literalsRedacted: params.includeLiterals !== true,
|
|
7089
|
+
context,
|
|
7090
|
+
warnings
|
|
7091
|
+
};
|
|
7092
|
+
}
|
|
6816
7093
|
async function explainOperation(params) {
|
|
6817
7094
|
const { schema, model, method, args } = params;
|
|
6818
7095
|
const dialect = requirePostgres({ dialect: params.dialect });
|
|
6819
7096
|
const mode = params.mode ?? "estimate";
|
|
6820
|
-
const
|
|
7097
|
+
const baseMeta = buildRuntimeMeta({ schema });
|
|
7098
|
+
const operatorMap = buildExtensionOperatorMap({ instances: params.extensions ?? [], dialect: params.dialect, provider: schema.datasource.provider });
|
|
7099
|
+
const meta = operatorMap === null ? baseMeta : { ...baseMeta, extensionOperators: operatorMap };
|
|
6821
7100
|
const plan = buildQuery({
|
|
6822
7101
|
meta,
|
|
6823
7102
|
dialect,
|
|
@@ -6847,9 +7126,258 @@ async function explainOperation(params) {
|
|
|
6847
7126
|
...params.includeLiterals === undefined ? {} : { includeLiterals: params.includeLiterals }
|
|
6848
7127
|
});
|
|
6849
7128
|
}
|
|
7129
|
+
// packages/runtime/src/diagnostics/statement-diagnostics.ts
|
|
7130
|
+
import { VibeError as VibeError16 } from "@vibeorm/schema";
|
|
7131
|
+
import { renderPostgresWorkload as renderPostgresWorkload2 } from "@vibeorm/sql";
|
|
7132
|
+
var STATEMENT_DIAGNOSTICS_DEFAULTS = Object.freeze({
|
|
7133
|
+
slowerThanMs: 0,
|
|
7134
|
+
explain: "off",
|
|
7135
|
+
explainTimeoutMs: 5000,
|
|
7136
|
+
maxOutstandingExplains: 4,
|
|
7137
|
+
analyzeWarnBelowMs: 1,
|
|
7138
|
+
envVariable: "VIBEORM_DIAGNOSTICS"
|
|
7139
|
+
});
|
|
7140
|
+
var EXPLAIN_MODES = ["off", "estimate", "analyze"];
|
|
7141
|
+
function configError(params) {
|
|
7142
|
+
return new VibeError16({ code: "VIBE_CONFIG", message: params.message, meta: params.meta ?? {} });
|
|
7143
|
+
}
|
|
7144
|
+
function isExplainMode(value) {
|
|
7145
|
+
return typeof value === "string" && EXPLAIN_MODES.includes(value);
|
|
7146
|
+
}
|
|
7147
|
+
function assertThreshold(params) {
|
|
7148
|
+
const { value, source } = params;
|
|
7149
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
7150
|
+
throw configError({
|
|
7151
|
+
message: `${source}: slowerThanMs must be a non-negative number of milliseconds, got ${String(value)}`,
|
|
7152
|
+
meta: { source, value }
|
|
7153
|
+
});
|
|
7154
|
+
}
|
|
7155
|
+
return value;
|
|
7156
|
+
}
|
|
7157
|
+
function parseDiagnosticsEnv(params) {
|
|
7158
|
+
const raw = params.value?.trim();
|
|
7159
|
+
if (raw === undefined || raw === "")
|
|
7160
|
+
return;
|
|
7161
|
+
const variable = STATEMENT_DIAGNOSTICS_DEFAULTS.envVariable;
|
|
7162
|
+
const [modePart, thresholdPart, ...rest] = raw.split(":");
|
|
7163
|
+
const mode = modePart?.trim().toLowerCase();
|
|
7164
|
+
if (rest.length > 0 || !isExplainMode(mode)) {
|
|
7165
|
+
throw configError({
|
|
7166
|
+
message: `${variable}="${raw}" is not <explain>[:<slowerThanMs>] with explain one of ${EXPLAIN_MODES.join(" | ")} \u2014 e.g. ${variable}=analyze:5`,
|
|
7167
|
+
meta: { variable, value: raw }
|
|
7168
|
+
});
|
|
7169
|
+
}
|
|
7170
|
+
if (thresholdPart === undefined)
|
|
7171
|
+
return { explain: mode, slowerThanMs: STATEMENT_DIAGNOSTICS_DEFAULTS.slowerThanMs };
|
|
7172
|
+
const threshold = thresholdPart.trim();
|
|
7173
|
+
const parsed = /^\d+(?:\.\d+)?$/.test(threshold) ? Number(threshold) : Number.NaN;
|
|
7174
|
+
if (!Number.isFinite(parsed)) {
|
|
7175
|
+
throw configError({
|
|
7176
|
+
message: `${variable}="${raw}": the threshold after ":" must be a non-negative number of milliseconds`,
|
|
7177
|
+
meta: { variable, value: raw }
|
|
7178
|
+
});
|
|
7179
|
+
}
|
|
7180
|
+
return { explain: mode, slowerThanMs: parsed };
|
|
7181
|
+
}
|
|
7182
|
+
function formatStatementDiagnostic(params) {
|
|
7183
|
+
const { report } = params;
|
|
7184
|
+
const head = `[vibeorm] ${report.model ?? "raw"}.${report.method} ${report.durationMs.toFixed(1)} ms \xB7 ${report.parameterCount} param${report.parameterCount === 1 ? "" : "s"}${report.inTransaction ? " \xB7 in transaction" : ""}`;
|
|
7185
|
+
if (report.plan !== undefined)
|
|
7186
|
+
return `${head} \xB7 ${report.plan.summary}`;
|
|
7187
|
+
if (report.explainSkipped !== undefined) {
|
|
7188
|
+
const detail = report.explainError === undefined ? "" : ` (${report.explainError})`;
|
|
7189
|
+
return `${head} \xB7 explain skipped: ${report.explainSkipped}${detail}`;
|
|
7190
|
+
}
|
|
7191
|
+
return head;
|
|
7192
|
+
}
|
|
7193
|
+
var defaultSink = (report) => {
|
|
7194
|
+
console.warn(formatStatementDiagnostic({ report }));
|
|
7195
|
+
};
|
|
7196
|
+
function resolveStatementDiagnostics(params) {
|
|
7197
|
+
const { options } = params;
|
|
7198
|
+
const fromEnv = parseDiagnosticsEnv({ value: params.env });
|
|
7199
|
+
if (options === undefined && fromEnv === undefined)
|
|
7200
|
+
return;
|
|
7201
|
+
const explain = options?.explain ?? fromEnv?.explain ?? STATEMENT_DIAGNOSTICS_DEFAULTS.explain;
|
|
7202
|
+
if (!isExplainMode(explain)) {
|
|
7203
|
+
throw configError({
|
|
7204
|
+
message: `diagnostics.explain must be one of ${EXPLAIN_MODES.join(" | ")}, got ${String(explain)}`,
|
|
7205
|
+
meta: { explain }
|
|
7206
|
+
});
|
|
7207
|
+
}
|
|
7208
|
+
const slowerThanMs = options?.slowerThanMs === undefined ? fromEnv?.slowerThanMs ?? STATEMENT_DIAGNOSTICS_DEFAULTS.slowerThanMs : assertThreshold({ value: options.slowerThanMs, source: "diagnostics" });
|
|
7209
|
+
if (options?.sink !== undefined && typeof options.sink !== "function") {
|
|
7210
|
+
throw configError({ message: "diagnostics.sink must be a function", meta: {} });
|
|
7211
|
+
}
|
|
7212
|
+
return {
|
|
7213
|
+
explain,
|
|
7214
|
+
slowerThanMs,
|
|
7215
|
+
sink: options?.sink ?? defaultSink,
|
|
7216
|
+
showLiterals: options?.showLiterals === true,
|
|
7217
|
+
fromEnvironment: fromEnv !== undefined && (options?.explain === undefined || options.slowerThanMs === undefined)
|
|
7218
|
+
};
|
|
7219
|
+
}
|
|
7220
|
+
function nodeLabel(node) {
|
|
7221
|
+
const target = node.relation ?? node.indexName;
|
|
7222
|
+
return target === undefined ? node.nodeType : `${node.nodeType} on ${target}`;
|
|
7223
|
+
}
|
|
7224
|
+
function collectLoops(params) {
|
|
7225
|
+
const out = [];
|
|
7226
|
+
const walk = (node) => {
|
|
7227
|
+
if (node.actualLoops !== undefined && node.actualLoops > 1) {
|
|
7228
|
+
out.push({ node: nodeLabel(node), loops: node.actualLoops, rows: node.actualRows ?? 0 });
|
|
7229
|
+
}
|
|
7230
|
+
for (const child of node.children)
|
|
7231
|
+
walk(child);
|
|
7232
|
+
};
|
|
7233
|
+
walk(params.root);
|
|
7234
|
+
out.sort((a, b) => b.loops - a.loops);
|
|
7235
|
+
return out;
|
|
7236
|
+
}
|
|
7237
|
+
var formatCount = (value) => value.toLocaleString("en-US");
|
|
7238
|
+
function summarizePlan(params) {
|
|
7239
|
+
const { plan, mode } = params;
|
|
7240
|
+
const root = plan.root;
|
|
7241
|
+
const loops = mode === "analyze" ? collectLoops({ root }) : [];
|
|
7242
|
+
const parts = [];
|
|
7243
|
+
if (mode === "analyze") {
|
|
7244
|
+
const executionMs = plan.executionTimeMs ?? root.actualTotalTimeMs;
|
|
7245
|
+
parts.push(executionMs === undefined ? nodeLabel(root) : `${nodeLabel(root)} ${executionMs.toFixed(1)} ms`);
|
|
7246
|
+
const hottest = loops[0];
|
|
7247
|
+
if (hottest !== undefined)
|
|
7248
|
+
parts.push(`${hottest.node} loops=${formatCount(hottest.loops)} rows=${formatCount(hottest.rows)}`);
|
|
7249
|
+
if (root.sharedBlocksHit !== undefined)
|
|
7250
|
+
parts.push(`shared hit ${formatCount(root.sharedBlocksHit)}`);
|
|
7251
|
+
if (root.sharedBlocksRead !== undefined && root.sharedBlocksRead > 0)
|
|
7252
|
+
parts.push(`shared read ${formatCount(root.sharedBlocksRead)}`);
|
|
7253
|
+
} else {
|
|
7254
|
+
parts.push(`${nodeLabel(root)} cost=${root.estimatedTotalCost} rows\u2248${formatCount(root.estimatedRows)} (estimated)`);
|
|
7255
|
+
}
|
|
7256
|
+
if (plan.truncated)
|
|
7257
|
+
parts.push(`plan truncated at ${plan.nodeCount} nodes`);
|
|
7258
|
+
return {
|
|
7259
|
+
summary: parts.join(" \xB7 "),
|
|
7260
|
+
nodes: plan,
|
|
7261
|
+
...plan.executionTimeMs === undefined ? {} : { executionMs: plan.executionTimeMs },
|
|
7262
|
+
...plan.planningTimeMs === undefined ? {} : { planningMs: plan.planningTimeMs },
|
|
7263
|
+
...root.sharedBlocksHit === undefined ? {} : { buffersShared: root.sharedBlocksHit },
|
|
7264
|
+
...loops.length === 0 ? {} : { loops }
|
|
7265
|
+
};
|
|
7266
|
+
}
|
|
7267
|
+
function errorCode(error) {
|
|
7268
|
+
return error instanceof VibeError16 ? error.code : "VIBE_INTERNAL";
|
|
7269
|
+
}
|
|
7270
|
+
function createStatementDiagnostics(params) {
|
|
7271
|
+
const { config, adapter, dialect } = params;
|
|
7272
|
+
const outstanding = new Set;
|
|
7273
|
+
const explainable = dialect.name === "postgres";
|
|
7274
|
+
const timeoutMs = STATEMENT_DIAGNOSTICS_DEFAULTS.explainTimeoutMs;
|
|
7275
|
+
const transactionOptions = adapter.budgets?.statementTimeout === "all-statements" ? { timeout: timeoutMs } : undefined;
|
|
7276
|
+
const report = (diagnostic) => {
|
|
7277
|
+
try {
|
|
7278
|
+
config.sink(diagnostic);
|
|
7279
|
+
} catch {}
|
|
7280
|
+
};
|
|
7281
|
+
const explain = async (statement, mode) => {
|
|
7282
|
+
const run = async (tx) => {
|
|
7283
|
+
if (mode === "analyze") {
|
|
7284
|
+
const readOnly = renderPostgresWorkload2({ dialect, query: "sessionReadOnly" });
|
|
7285
|
+
await tx.execute({ text: readOnly.text, values: readOnly.values });
|
|
7286
|
+
}
|
|
7287
|
+
const executor = statement.raw ? async (query) => (await tx.executeUnsafe({ text: query.text, values: query.values ?? [] })).rows : async (query) => tx.execute({
|
|
7288
|
+
text: query.text,
|
|
7289
|
+
values: (query.values ?? []).map((value) => Array.isArray(value) ? tx.formatArrayParam(value) : value)
|
|
7290
|
+
});
|
|
7291
|
+
const report2 = await explainOnSession({
|
|
7292
|
+
executor,
|
|
7293
|
+
dialect: dialect.name,
|
|
7294
|
+
sql: { text: statement.text, values: statement.values },
|
|
7295
|
+
mode,
|
|
7296
|
+
includeLiterals: config.showLiterals
|
|
7297
|
+
});
|
|
7298
|
+
return summarizePlan({
|
|
7299
|
+
plan: {
|
|
7300
|
+
root: report2.root,
|
|
7301
|
+
nodeCount: report2.nodeCount,
|
|
7302
|
+
truncated: report2.truncated,
|
|
7303
|
+
...report2.planningTimeMs === undefined ? {} : { planningTimeMs: report2.planningTimeMs },
|
|
7304
|
+
...report2.executionTimeMs === undefined ? {} : { executionTimeMs: report2.executionTimeMs }
|
|
7305
|
+
},
|
|
7306
|
+
mode
|
|
7307
|
+
});
|
|
7308
|
+
};
|
|
7309
|
+
return transactionOptions === undefined ? adapter.transaction(run) : adapter.transaction(run, transactionOptions);
|
|
7310
|
+
};
|
|
7311
|
+
const schedule = (statement, mode, base) => {
|
|
7312
|
+
const task = new Promise((resolve) => {
|
|
7313
|
+
setTimeout(() => {
|
|
7314
|
+
explain(statement, mode).then((plan) => {
|
|
7315
|
+
report({ ...base, plan });
|
|
7316
|
+
resolve();
|
|
7317
|
+
}, (error) => {
|
|
7318
|
+
report({ ...base, explainSkipped: "error", explainError: errorCode(error) });
|
|
7319
|
+
resolve();
|
|
7320
|
+
});
|
|
7321
|
+
}, 0);
|
|
7322
|
+
});
|
|
7323
|
+
outstanding.add(task);
|
|
7324
|
+
task.finally(() => outstanding.delete(task));
|
|
7325
|
+
};
|
|
7326
|
+
return {
|
|
7327
|
+
config,
|
|
7328
|
+
observe: (statement) => {
|
|
7329
|
+
if (statement.durationMs < config.slowerThanMs)
|
|
7330
|
+
return;
|
|
7331
|
+
const base = {
|
|
7332
|
+
model: statement.model,
|
|
7333
|
+
method: statement.method,
|
|
7334
|
+
text: statement.text,
|
|
7335
|
+
durationMs: statement.durationMs,
|
|
7336
|
+
parameterCount: statement.values.length,
|
|
7337
|
+
inTransaction: statement.inTransaction
|
|
7338
|
+
};
|
|
7339
|
+
if (config.explain === "off") {
|
|
7340
|
+
report(base);
|
|
7341
|
+
return;
|
|
7342
|
+
}
|
|
7343
|
+
if (!explainable) {
|
|
7344
|
+
report({ ...base, explainSkipped: "dialect" });
|
|
7345
|
+
return;
|
|
7346
|
+
}
|
|
7347
|
+
if (statement.inTransaction) {
|
|
7348
|
+
report({ ...base, explainSkipped: "in-transaction" });
|
|
7349
|
+
return;
|
|
7350
|
+
}
|
|
7351
|
+
if (!isExecutableRead({ text: statement.text })) {
|
|
7352
|
+
report({ ...base, explainSkipped: "not-a-read" });
|
|
7353
|
+
return;
|
|
7354
|
+
}
|
|
7355
|
+
if (outstanding.size >= STATEMENT_DIAGNOSTICS_DEFAULTS.maxOutstandingExplains) {
|
|
7356
|
+
report({ ...base, explainSkipped: "overload" });
|
|
7357
|
+
return;
|
|
7358
|
+
}
|
|
7359
|
+
schedule(statement, config.explain, base);
|
|
7360
|
+
},
|
|
7361
|
+
flush: async () => {
|
|
7362
|
+
if (outstanding.size === 0)
|
|
7363
|
+
return;
|
|
7364
|
+
let timer;
|
|
7365
|
+
const bound = new Promise((resolve) => {
|
|
7366
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
7367
|
+
});
|
|
7368
|
+
try {
|
|
7369
|
+
await Promise.race([Promise.allSettled([...outstanding]), bound]);
|
|
7370
|
+
} finally {
|
|
7371
|
+
if (timer !== undefined)
|
|
7372
|
+
clearTimeout(timer);
|
|
7373
|
+
}
|
|
7374
|
+
},
|
|
7375
|
+
outstanding: () => outstanding.size
|
|
7376
|
+
};
|
|
7377
|
+
}
|
|
6850
7378
|
// packages/runtime/src/diagnostics/workload.ts
|
|
6851
7379
|
import { unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
|
|
6852
|
-
import { getSqlDialect as getSqlDialect3, renderPostgresWorkload as
|
|
7380
|
+
import { getSqlDialect as getSqlDialect3, renderPostgresWorkload as renderPostgresWorkload3 } from "@vibeorm/sql";
|
|
6853
7381
|
var STATEMENTS_INFO_MIN_VERSION = 140000;
|
|
6854
7382
|
var STATEMENTS_MIN_VERSION = 130000;
|
|
6855
7383
|
var NOTES = Object.freeze({
|
|
@@ -6864,7 +7392,7 @@ function num2(value) {
|
|
|
6864
7392
|
return typeof value === "number" ? value : Number(value ?? 0);
|
|
6865
7393
|
}
|
|
6866
7394
|
async function readAvailability(params) {
|
|
6867
|
-
const query =
|
|
7395
|
+
const query = renderPostgresWorkload3({ dialect: params.dialect, query: "statementExtension" });
|
|
6868
7396
|
const rows = await params.executor({ text: query.text, values: query.values });
|
|
6869
7397
|
const row = rows[0] ?? {};
|
|
6870
7398
|
const installedVersion = row["installedVersion"] === null || row["installedVersion"] === undefined ? null : String(row["installedVersion"]);
|
|
@@ -6916,14 +7444,14 @@ async function readWorkloadStatistics(params) {
|
|
|
6916
7444
|
notes.push(NOTES.restrictedText);
|
|
6917
7445
|
let statsReset = null;
|
|
6918
7446
|
if (availability.serverVersionNum >= STATEMENTS_INFO_MIN_VERSION) {
|
|
6919
|
-
const info =
|
|
7447
|
+
const info = renderPostgresWorkload3({ dialect, query: "statementInfo", schema: availability.schema });
|
|
6920
7448
|
const infoRows = await executor({ text: info.text, values: info.values });
|
|
6921
7449
|
const value = infoRows[0]?.["statsReset"];
|
|
6922
7450
|
statsReset = value === null || value === undefined ? null : String(value);
|
|
6923
7451
|
} else {
|
|
6924
7452
|
notes.push("this server predates pg_stat_statements_info, so the collection window's reset time is unknown");
|
|
6925
7453
|
}
|
|
6926
|
-
const snapshot =
|
|
7454
|
+
const snapshot = renderPostgresWorkload3({
|
|
6927
7455
|
dialect,
|
|
6928
7456
|
query: "statementSnapshot",
|
|
6929
7457
|
schema: availability.schema,
|
|
@@ -6959,7 +7487,7 @@ async function readWorkloadStatistics(params) {
|
|
|
6959
7487
|
};
|
|
6960
7488
|
}
|
|
6961
7489
|
// packages/runtime/src/diagnostics/insight.ts
|
|
6962
|
-
import { VibeError as
|
|
7490
|
+
import { VibeError as VibeError17 } from "@vibeorm/schema";
|
|
6963
7491
|
var DIAGNOSTIC_THRESHOLDS = Object.freeze({
|
|
6964
7492
|
repeatThreshold: 5,
|
|
6965
7493
|
minRepeatThreshold: 2,
|
|
@@ -6977,7 +7505,7 @@ function bounded(params) {
|
|
|
6977
7505
|
if (value === undefined)
|
|
6978
7506
|
return fallback;
|
|
6979
7507
|
if (!Number.isInteger(value) || value < min || value > max) {
|
|
6980
|
-
throw new
|
|
7508
|
+
throw new VibeError17({
|
|
6981
7509
|
code: "VIBE_CONFIG",
|
|
6982
7510
|
message: `${name} must be an integer between ${min} and ${max}, got ${String(value)}`,
|
|
6983
7511
|
meta: { [name]: value }
|
|
@@ -7200,11 +7728,11 @@ function recommendFromPlan(params) {
|
|
|
7200
7728
|
return findings.slice(0, maxFindings);
|
|
7201
7729
|
}
|
|
7202
7730
|
// packages/runtime/src/relation-loader.ts
|
|
7203
|
-
import { VibeError as
|
|
7731
|
+
import { VibeError as VibeError19 } from "@vibeorm/schema";
|
|
7204
7732
|
import { renderStatement as renderStatement3 } from "@vibeorm/sql";
|
|
7205
7733
|
|
|
7206
7734
|
// packages/runtime/src/computed.ts
|
|
7207
|
-
import { VibeError as
|
|
7735
|
+
import { VibeError as VibeError18 } from "@vibeorm/schema";
|
|
7208
7736
|
function sameNeeds(a, b) {
|
|
7209
7737
|
if (a.length !== b.length)
|
|
7210
7738
|
return false;
|
|
@@ -7240,7 +7768,7 @@ function assertComputedManifest(params) {
|
|
|
7240
7768
|
problems.push(`"${key}" is configured but missing from the generated client`);
|
|
7241
7769
|
}
|
|
7242
7770
|
if (problems.length > 0) {
|
|
7243
|
-
throw new
|
|
7771
|
+
throw new VibeError18({
|
|
7244
7772
|
code: "VIBE_STALE_CLIENT",
|
|
7245
7773
|
message: `computed-field config changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
|
|
7246
7774
|
meta: { problems }
|
|
@@ -7263,6 +7791,87 @@ function applyComputedFields(params) {
|
|
|
7263
7791
|
}
|
|
7264
7792
|
|
|
7265
7793
|
// packages/runtime/src/relation-loader.ts
|
|
7794
|
+
function resolveRelationConcurrency(params) {
|
|
7795
|
+
const { concurrency } = params;
|
|
7796
|
+
if (concurrency === undefined)
|
|
7797
|
+
return 1;
|
|
7798
|
+
if (!Number.isInteger(concurrency) || concurrency < 1) {
|
|
7799
|
+
throw new VibeError19({
|
|
7800
|
+
code: "VIBE_CONFIG",
|
|
7801
|
+
message: `relationLoading.concurrency must be a positive integer \u2014 got ${String(concurrency)}`,
|
|
7802
|
+
meta: { concurrency }
|
|
7803
|
+
});
|
|
7804
|
+
}
|
|
7805
|
+
return concurrency;
|
|
7806
|
+
}
|
|
7807
|
+
function createStatementLimiter(params) {
|
|
7808
|
+
const { concurrency } = params;
|
|
7809
|
+
const waiting = [];
|
|
7810
|
+
let active = 0;
|
|
7811
|
+
const release = () => {
|
|
7812
|
+
const next = waiting.shift();
|
|
7813
|
+
if (next === undefined) {
|
|
7814
|
+
active -= 1;
|
|
7815
|
+
return;
|
|
7816
|
+
}
|
|
7817
|
+
next();
|
|
7818
|
+
};
|
|
7819
|
+
return {
|
|
7820
|
+
run: async (task) => {
|
|
7821
|
+
if (active >= concurrency) {
|
|
7822
|
+
await new Promise((resolve) => {
|
|
7823
|
+
waiting.push(resolve);
|
|
7824
|
+
});
|
|
7825
|
+
} else {
|
|
7826
|
+
active += 1;
|
|
7827
|
+
}
|
|
7828
|
+
try {
|
|
7829
|
+
return await task();
|
|
7830
|
+
} finally {
|
|
7831
|
+
release();
|
|
7832
|
+
}
|
|
7833
|
+
}
|
|
7834
|
+
};
|
|
7835
|
+
}
|
|
7836
|
+
async function runAllSettled(params) {
|
|
7837
|
+
const settled = await Promise.allSettled(params.tasks.map((task) => task()));
|
|
7838
|
+
for (const result of settled) {
|
|
7839
|
+
if (result.status === "rejected")
|
|
7840
|
+
throw result.reason;
|
|
7841
|
+
}
|
|
7842
|
+
}
|
|
7843
|
+
function createLoaderContext(params) {
|
|
7844
|
+
const { dialect, meta, execute, wire } = params;
|
|
7845
|
+
const concurrency = resolveRelationConcurrency({ concurrency: params.concurrency });
|
|
7846
|
+
if (concurrency === 1)
|
|
7847
|
+
return { dialect, meta, execute, wire, limiter: null };
|
|
7848
|
+
const limiter = createStatementLimiter({ concurrency });
|
|
7849
|
+
return {
|
|
7850
|
+
dialect,
|
|
7851
|
+
meta,
|
|
7852
|
+
execute: (statement) => limiter.run(() => execute(statement)),
|
|
7853
|
+
wire,
|
|
7854
|
+
limiter
|
|
7855
|
+
};
|
|
7856
|
+
}
|
|
7857
|
+
async function executeChunks(params) {
|
|
7858
|
+
const { ctx, chunks, run } = params;
|
|
7859
|
+
const raw = [];
|
|
7860
|
+
if (ctx.limiter === null || chunks.length < 2) {
|
|
7861
|
+
for (const chunk of chunks)
|
|
7862
|
+
pushAll(raw, await run(chunk));
|
|
7863
|
+
return raw;
|
|
7864
|
+
}
|
|
7865
|
+
const perChunk = chunks.map(() => []);
|
|
7866
|
+
await runAllSettled({
|
|
7867
|
+
tasks: chunks.map((chunk, index) => async () => {
|
|
7868
|
+
perChunk[index] = await run(chunk);
|
|
7869
|
+
})
|
|
7870
|
+
});
|
|
7871
|
+
for (const rows of perChunk)
|
|
7872
|
+
pushAll(raw, rows);
|
|
7873
|
+
return raw;
|
|
7874
|
+
}
|
|
7266
7875
|
var COUNT_VALUE_ALIAS = "__vibe_count";
|
|
7267
7876
|
var WINDOW_ROW_ALIAS = "__vibe_rn";
|
|
7268
7877
|
var WINDOW_TABLE_ALIAS = "__vibe_win";
|
|
@@ -7372,7 +7981,7 @@ function chunkKeys(params) {
|
|
|
7372
7981
|
const rendered = renderStatement3({ dialect: ctx.dialect, statement: build(keys.slice(0, 1)) });
|
|
7373
7982
|
fixed = rendered.values.length - (expands ? arity : 1);
|
|
7374
7983
|
if (fixed + arity > budget) {
|
|
7375
|
-
throw new
|
|
7984
|
+
throw new VibeError19({
|
|
7376
7985
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
7377
7986
|
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`,
|
|
7378
7987
|
meta: { relation, dialect: ctx.dialect.name, required: fixed + arity, budget }
|
|
@@ -7411,7 +8020,6 @@ async function loadToManyFk(params) {
|
|
|
7411
8020
|
}
|
|
7412
8021
|
}
|
|
7413
8022
|
});
|
|
7414
|
-
const raw = [];
|
|
7415
8023
|
const chunks = chunkKeys({
|
|
7416
8024
|
ctx,
|
|
7417
8025
|
keys,
|
|
@@ -7420,10 +8028,11 @@ async function loadToManyFk(params) {
|
|
|
7420
8028
|
measure: node.args.where !== undefined || bounds !== null,
|
|
7421
8029
|
arity: link.childKeys.length
|
|
7422
8030
|
});
|
|
7423
|
-
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
8031
|
+
const raw = await executeChunks({
|
|
8032
|
+
ctx,
|
|
8033
|
+
chunks,
|
|
8034
|
+
run: (chunk) => ctx.execute({ statement: build(chunk), model: model.name, method: `include.${node.name}` })
|
|
8035
|
+
});
|
|
7427
8036
|
const children = materializeRows({ dialect: ctx.dialect.name, model: link.target, rows: raw, wire: ctx.wire });
|
|
7428
8037
|
const grouped = new Map;
|
|
7429
8038
|
for (const child of children) {
|
|
@@ -7459,7 +8068,6 @@ async function loadToOne(params) {
|
|
|
7459
8068
|
node,
|
|
7460
8069
|
membership: membershipExpr({ ctx, fields: link.childKeys, encodeAs: link.childKeys, tuples: chunk })
|
|
7461
8070
|
});
|
|
7462
|
-
const raw = [];
|
|
7463
8071
|
const chunks = chunkKeys({
|
|
7464
8072
|
ctx,
|
|
7465
8073
|
keys,
|
|
@@ -7468,10 +8076,11 @@ async function loadToOne(params) {
|
|
|
7468
8076
|
measure: node.args.where !== undefined,
|
|
7469
8077
|
arity: link.childKeys.length
|
|
7470
8078
|
});
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7474
|
-
|
|
8079
|
+
const raw = await executeChunks({
|
|
8080
|
+
ctx,
|
|
8081
|
+
chunks,
|
|
8082
|
+
run: (chunk) => ctx.execute({ statement: build(chunk), model: model.name, method: `include.${node.name}` })
|
|
8083
|
+
});
|
|
7475
8084
|
const children = materializeRows({ dialect: ctx.dialect.name, model: link.target, rows: raw, wire: ctx.wire });
|
|
7476
8085
|
const byKey = new Map;
|
|
7477
8086
|
for (const child of children) {
|
|
@@ -7502,7 +8111,7 @@ async function loadManyToMany(params) {
|
|
|
7502
8111
|
const link = node.link;
|
|
7503
8112
|
const join = link.join;
|
|
7504
8113
|
if (join === null) {
|
|
7505
|
-
throw new
|
|
8114
|
+
throw new VibeError19({
|
|
7506
8115
|
code: "VIBE_INTERNAL",
|
|
7507
8116
|
message: `manyToMany relation "${node.name}" resolved without join-table wiring`
|
|
7508
8117
|
});
|
|
@@ -7575,7 +8184,6 @@ async function loadManyToMany(params) {
|
|
|
7575
8184
|
};
|
|
7576
8185
|
return windowedStatement({ inner, columns: outerColumns, bounds });
|
|
7577
8186
|
};
|
|
7578
|
-
const raw = [];
|
|
7579
8187
|
const chunks = chunkKeys({
|
|
7580
8188
|
ctx,
|
|
7581
8189
|
keys,
|
|
@@ -7584,10 +8192,11 @@ async function loadManyToMany(params) {
|
|
|
7584
8192
|
measure: node.args.where !== undefined || bounds !== null,
|
|
7585
8193
|
arity: 1
|
|
7586
8194
|
});
|
|
7587
|
-
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
8195
|
+
const raw = await executeChunks({
|
|
8196
|
+
ctx,
|
|
8197
|
+
chunks,
|
|
8198
|
+
run: (chunk) => ctx.execute({ statement: build(chunk), model: model.name, method: `include.${node.name}` })
|
|
8199
|
+
});
|
|
7591
8200
|
const pairs = materializeRows({ dialect: ctx.dialect.name, model: target, rows: raw, wire: ctx.wire });
|
|
7592
8201
|
const decodeParent = fieldDecoder({
|
|
7593
8202
|
dialect: ctx.dialect.name,
|
|
@@ -7614,16 +8223,19 @@ async function loadManyToMany(params) {
|
|
|
7614
8223
|
}
|
|
7615
8224
|
return kept;
|
|
7616
8225
|
}
|
|
7617
|
-
|
|
7618
|
-
const {
|
|
7619
|
-
if (counts.length === 0 || rows.length === 0)
|
|
7620
|
-
return;
|
|
8226
|
+
function seedCountBuckets(params) {
|
|
8227
|
+
const { rows, counts } = params;
|
|
7621
8228
|
for (const row of rows) {
|
|
7622
8229
|
const bucket = {};
|
|
7623
8230
|
for (const node of counts)
|
|
7624
8231
|
bucket[node.name] = 0;
|
|
7625
8232
|
row._count = bucket;
|
|
7626
8233
|
}
|
|
8234
|
+
}
|
|
8235
|
+
async function loadCounts(params) {
|
|
8236
|
+
const { ctx, model, rows, counts } = params;
|
|
8237
|
+
if (counts.length === 0 || rows.length === 0)
|
|
8238
|
+
return;
|
|
7627
8239
|
for (const node of counts) {
|
|
7628
8240
|
const name = node.name;
|
|
7629
8241
|
const link = node.link;
|
|
@@ -7681,7 +8293,6 @@ async function loadCounts(params) {
|
|
|
7681
8293
|
]
|
|
7682
8294
|
};
|
|
7683
8295
|
};
|
|
7684
|
-
const raw = [];
|
|
7685
8296
|
const chunks = chunkKeys({
|
|
7686
8297
|
ctx,
|
|
7687
8298
|
keys,
|
|
@@ -7690,9 +8301,11 @@ async function loadCounts(params) {
|
|
|
7690
8301
|
measure: node.where !== undefined,
|
|
7691
8302
|
arity: groupFields.length
|
|
7692
8303
|
});
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
8304
|
+
const raw = await executeChunks({
|
|
8305
|
+
ctx,
|
|
8306
|
+
chunks,
|
|
8307
|
+
run: (chunk) => ctx.execute({ statement: build(chunk), model: model.name, method: `_count.${name}` })
|
|
8308
|
+
});
|
|
7696
8309
|
const byKey = new Map;
|
|
7697
8310
|
for (const entry of raw) {
|
|
7698
8311
|
const decoded = groupFields.map((field, index) => decodeValue({
|
|
@@ -7735,15 +8348,7 @@ async function finishNode(params) {
|
|
|
7735
8348
|
applyComputedFields({ model: node.link.target, rows: kept, names: node.args.computed });
|
|
7736
8349
|
}
|
|
7737
8350
|
if (node.args.nested !== null && kept.length > 0) {
|
|
7738
|
-
await
|
|
7739
|
-
dialect: ctx.dialect,
|
|
7740
|
-
meta: ctx.meta,
|
|
7741
|
-
model: node.link.target,
|
|
7742
|
-
rows: [...kept],
|
|
7743
|
-
selection: node.args.nested,
|
|
7744
|
-
execute: ctx.execute,
|
|
7745
|
-
...ctx.wire === undefined ? {} : { wire: ctx.wire }
|
|
7746
|
-
});
|
|
8351
|
+
await attachSelection({ ctx, model: node.link.target, rows: [...kept], selection: node.args.nested });
|
|
7747
8352
|
}
|
|
7748
8353
|
if (node.args.strip.length > 0) {
|
|
7749
8354
|
for (const child of kept) {
|
|
@@ -7753,20 +8358,42 @@ async function finishNode(params) {
|
|
|
7753
8358
|
}
|
|
7754
8359
|
}
|
|
7755
8360
|
async function attachRelations(params) {
|
|
7756
|
-
const { dialect, meta, model, rows, selection, execute, wire } = params;
|
|
8361
|
+
const { dialect, meta, model, rows, selection, execute, wire, concurrency } = params;
|
|
7757
8362
|
if (rows.length === 0)
|
|
7758
8363
|
return;
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
8364
|
+
await attachSelection({
|
|
8365
|
+
ctx: createLoaderContext({ dialect, meta, execute, wire, concurrency }),
|
|
8366
|
+
model,
|
|
8367
|
+
rows,
|
|
8368
|
+
selection
|
|
8369
|
+
});
|
|
8370
|
+
}
|
|
8371
|
+
async function attachSelection(params) {
|
|
8372
|
+
const { ctx, model, rows, selection } = params;
|
|
8373
|
+
if (rows.length === 0)
|
|
8374
|
+
return;
|
|
8375
|
+
if (selection.counts.length > 0)
|
|
8376
|
+
seedCountBuckets({ rows, counts: selection.counts });
|
|
8377
|
+
if (ctx.limiter === null) {
|
|
8378
|
+
for (const node of selection.relations) {
|
|
8379
|
+
await loadNode({ ctx, model, node, rows });
|
|
8380
|
+
}
|
|
8381
|
+
await loadCounts({ ctx, model, rows, counts: selection.counts });
|
|
8382
|
+
return;
|
|
7762
8383
|
}
|
|
7763
|
-
await
|
|
8384
|
+
await runAllSettled({
|
|
8385
|
+
tasks: [
|
|
8386
|
+
...selection.relations.map((node) => () => loadNode({ ctx, model, node, rows })),
|
|
8387
|
+
...selection.counts.map((node) => () => loadCounts({ ctx, model, rows, counts: [node] }))
|
|
8388
|
+
]
|
|
8389
|
+
});
|
|
7764
8390
|
}
|
|
7765
8391
|
async function attachLateralRelations(params) {
|
|
7766
|
-
const { dialect, meta, rows, selection, execute, wire } = params;
|
|
8392
|
+
const { dialect, meta, rows, selection, execute, wire, concurrency } = params;
|
|
7767
8393
|
if (rows.length === 0)
|
|
7768
8394
|
return;
|
|
7769
|
-
const ctx = { dialect, meta, execute, wire };
|
|
8395
|
+
const ctx = createLoaderContext({ dialect, meta, execute, wire, concurrency });
|
|
8396
|
+
const parsed = [];
|
|
7770
8397
|
for (const node of selection.relations) {
|
|
7771
8398
|
const column = lateralColumnAlias({ relation: node.name });
|
|
7772
8399
|
const isList = node.link.relation.isList;
|
|
@@ -7776,12 +8403,12 @@ async function attachLateralRelations(params) {
|
|
|
7776
8403
|
for (const row of rows) {
|
|
7777
8404
|
const raw = row[column];
|
|
7778
8405
|
delete row[column];
|
|
7779
|
-
let
|
|
8406
|
+
let parsed2 = raw;
|
|
7780
8407
|
if (typeof raw === "string") {
|
|
7781
8408
|
try {
|
|
7782
|
-
|
|
8409
|
+
parsed2 = JSON.parse(raw);
|
|
7783
8410
|
} catch (cause) {
|
|
7784
|
-
throw new
|
|
8411
|
+
throw new VibeError19({
|
|
7785
8412
|
code: "VIBE_INTERNAL",
|
|
7786
8413
|
message: `relation column "${column}" returned text that is not valid JSON`,
|
|
7787
8414
|
meta: { relation: node.name },
|
|
@@ -7790,10 +8417,10 @@ async function attachLateralRelations(params) {
|
|
|
7790
8417
|
}
|
|
7791
8418
|
}
|
|
7792
8419
|
if (isList) {
|
|
7793
|
-
if (!Array.isArray(
|
|
7794
|
-
throw new
|
|
8420
|
+
if (!Array.isArray(parsed2)) {
|
|
8421
|
+
throw new VibeError19({ code: "VIBE_INTERNAL", message: "to-many relation payload must be an array", meta: { relation: node.name } });
|
|
7795
8422
|
}
|
|
7796
|
-
const children =
|
|
8423
|
+
const children = parsed2.map(decodePosition);
|
|
7797
8424
|
for (const child of children) {
|
|
7798
8425
|
for (const name of hiddenOrder)
|
|
7799
8426
|
delete child[name];
|
|
@@ -7801,18 +8428,27 @@ async function attachLateralRelations(params) {
|
|
|
7801
8428
|
row[node.name] = children;
|
|
7802
8429
|
pushAll(kept, children);
|
|
7803
8430
|
} else {
|
|
7804
|
-
if (
|
|
8431
|
+
if (parsed2 === null) {
|
|
7805
8432
|
row[node.name] = null;
|
|
7806
8433
|
continue;
|
|
7807
8434
|
}
|
|
7808
|
-
const child = decodePosition(
|
|
8435
|
+
const child = decodePosition(parsed2);
|
|
7809
8436
|
for (const name of hiddenOrder)
|
|
7810
8437
|
delete child[name];
|
|
7811
8438
|
row[node.name] = child;
|
|
7812
8439
|
kept.push(child);
|
|
7813
8440
|
}
|
|
7814
8441
|
}
|
|
7815
|
-
|
|
8442
|
+
parsed.push({ node, kept });
|
|
8443
|
+
}
|
|
8444
|
+
if (ctx.limiter === null) {
|
|
8445
|
+
for (const entry of parsed) {
|
|
8446
|
+
await finishNode({ ctx, node: entry.node, kept: entry.kept });
|
|
8447
|
+
}
|
|
8448
|
+
} else {
|
|
8449
|
+
await runAllSettled({
|
|
8450
|
+
tasks: parsed.map((entry) => () => finishNode({ ctx, node: entry.node, kept: entry.kept }))
|
|
8451
|
+
});
|
|
7816
8452
|
}
|
|
7817
8453
|
if (selection.counts.length === 0)
|
|
7818
8454
|
return;
|
|
@@ -7823,7 +8459,7 @@ async function attachLateralRelations(params) {
|
|
|
7823
8459
|
const raw = row[column];
|
|
7824
8460
|
const count = typeof raw === "number" || typeof raw === "bigint" || typeof raw === "string" && /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
7825
8461
|
if (!Number.isSafeInteger(count) || count < 0) {
|
|
7826
|
-
throw new
|
|
8462
|
+
throw new VibeError19({ code: "VIBE_INTERNAL", message: "relation count payload must be a non-negative safe integer", meta: { relation: selection.counts[index]?.name } });
|
|
7827
8463
|
}
|
|
7828
8464
|
counts[selection.counts[index].name] = count;
|
|
7829
8465
|
delete row[column];
|
|
@@ -7831,97 +8467,12 @@ async function attachLateralRelations(params) {
|
|
|
7831
8467
|
row._count = counts;
|
|
7832
8468
|
}
|
|
7833
8469
|
}
|
|
7834
|
-
// packages/runtime/src/extensions.ts
|
|
7835
|
-
import { VibeError as VibeError17, extensionManifestEntry } from "@vibeorm/schema";
|
|
7836
|
-
function assertExtensionManifest(params) {
|
|
7837
|
-
const { manifest } = params;
|
|
7838
|
-
const instances = params.instances ?? [];
|
|
7839
|
-
const live = new Map(instances.map((extension) => [extension.name, extensionManifestEntry({ extension })]));
|
|
7840
|
-
const problems = [];
|
|
7841
|
-
for (const baked of manifest) {
|
|
7842
|
-
const current = live.get(baked.name);
|
|
7843
|
-
if (current === undefined) {
|
|
7844
|
-
problems.push(`"${baked.name}" was baked into the client but not passed to VibeClient`);
|
|
7845
|
-
continue;
|
|
7846
|
-
}
|
|
7847
|
-
if (current.version !== baked.version) {
|
|
7848
|
-
problems.push(`"${baked.name}" version changed (generated ${baked.version}, live ${current.version})`);
|
|
7849
|
-
} else if (current.optionsHash !== baked.optionsHash) {
|
|
7850
|
-
problems.push(`"${baked.name}" options changed since generate`);
|
|
7851
|
-
}
|
|
7852
|
-
live.delete(baked.name);
|
|
7853
|
-
}
|
|
7854
|
-
for (const name of live.keys()) {
|
|
7855
|
-
problems.push(`"${name}" is configured but missing from the generated client`);
|
|
7856
|
-
}
|
|
7857
|
-
if (problems.length > 0) {
|
|
7858
|
-
throw new VibeError17({
|
|
7859
|
-
code: "VIBE_STALE_CLIENT",
|
|
7860
|
-
message: `extension list changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
|
|
7861
|
-
meta: { manifest, problems }
|
|
7862
|
-
});
|
|
7863
|
-
}
|
|
7864
|
-
}
|
|
7865
|
-
function buildExtensionOperatorMap(params) {
|
|
7866
|
-
const { instances, dialect, provider } = params;
|
|
7867
|
-
const map = new Map;
|
|
7868
|
-
const owner = new Map;
|
|
7869
|
-
for (const extension of instances) {
|
|
7870
|
-
for (const operator of extension.sql?.whereOperators ?? []) {
|
|
7871
|
-
const existing = owner.get(operator.name);
|
|
7872
|
-
if (existing !== undefined) {
|
|
7873
|
-
throw new VibeError17({
|
|
7874
|
-
code: "VIBE_CONFIG",
|
|
7875
|
-
message: `where operator "${operator.name}" is claimed by both "${existing}" and "${extension.name}"`,
|
|
7876
|
-
meta: { operator: operator.name }
|
|
7877
|
-
});
|
|
7878
|
-
}
|
|
7879
|
-
owner.set(operator.name, extension.name);
|
|
7880
|
-
map.set(operator.name, ({ column, operand, model, field }) => {
|
|
7881
|
-
const compiled = operator.compile({
|
|
7882
|
-
column,
|
|
7883
|
-
operand,
|
|
7884
|
-
dialect,
|
|
7885
|
-
provider,
|
|
7886
|
-
options: extension.options
|
|
7887
|
-
});
|
|
7888
|
-
if ("error" in compiled) {
|
|
7889
|
-
throw new VibeError17({
|
|
7890
|
-
code: "VIBE_VALIDATION",
|
|
7891
|
-
message: `"${operator.name}" on ${model}.${field}: ${compiled.error}`,
|
|
7892
|
-
meta: { model, field, operator: operator.name, extension: extension.name }
|
|
7893
|
-
});
|
|
7894
|
-
}
|
|
7895
|
-
return { kind: "fragment", fragment: compiled.fragment };
|
|
7896
|
-
});
|
|
7897
|
-
}
|
|
7898
|
-
}
|
|
7899
|
-
return map.size === 0 ? null : map;
|
|
7900
|
-
}
|
|
7901
|
-
function buildConnectHooks(params) {
|
|
7902
|
-
const { instances, dialect, provider } = params;
|
|
7903
|
-
const hooks = [];
|
|
7904
|
-
for (const extension of instances) {
|
|
7905
|
-
const onConnect = extension.runtime?.onConnect;
|
|
7906
|
-
if (onConnect === undefined)
|
|
7907
|
-
continue;
|
|
7908
|
-
hooks.push(({ execute }) => onConnect({
|
|
7909
|
-
execute: (execParams) => execute({
|
|
7910
|
-
text: execParams.text,
|
|
7911
|
-
...execParams.values === undefined ? {} : { values: execParams.values }
|
|
7912
|
-
}),
|
|
7913
|
-
dialect,
|
|
7914
|
-
provider,
|
|
7915
|
-
options: extension.options
|
|
7916
|
-
}));
|
|
7917
|
-
}
|
|
7918
|
-
return hooks;
|
|
7919
|
-
}
|
|
7920
8470
|
// packages/runtime/src/views.ts
|
|
7921
|
-
import { VibeError as
|
|
8471
|
+
import { VibeError as VibeError21 } from "@vibeorm/schema";
|
|
7922
8472
|
|
|
7923
8473
|
// packages/runtime/src/sql-access.ts
|
|
7924
|
-
import {
|
|
8474
|
+
import { Buffer as Buffer2 } from "buffer";
|
|
8475
|
+
import { VibeError as VibeError20 } from "@vibeorm/schema";
|
|
7925
8476
|
import { isSqlFragment as isSqlFragment2 } from "@vibeorm/sql";
|
|
7926
8477
|
function containsSqlFragment(value, seen = new Set) {
|
|
7927
8478
|
if (isSqlFragment2(value))
|
|
@@ -7929,42 +8480,92 @@ function containsSqlFragment(value, seen = new Set) {
|
|
|
7929
8480
|
if (typeof value !== "object" || value === null || seen.has(value))
|
|
7930
8481
|
return false;
|
|
7931
8482
|
seen.add(value);
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
8483
|
+
const keys = new Set;
|
|
8484
|
+
for (let object = value;object !== null && object !== Object.prototype; object = Object.getPrototypeOf(object)) {
|
|
8485
|
+
for (const key of Object.getOwnPropertyNames(object)) {
|
|
8486
|
+
if (keys.has(key) || key === "constructor")
|
|
8487
|
+
continue;
|
|
8488
|
+
keys.add(key);
|
|
8489
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
8490
|
+
if (descriptor === undefined)
|
|
8491
|
+
continue;
|
|
8492
|
+
const child = "value" in descriptor ? descriptor.value : object === value && descriptor.enumerable ? value[key] : undefined;
|
|
8493
|
+
if (containsSqlFragment(child, seen))
|
|
8494
|
+
return true;
|
|
8495
|
+
}
|
|
8496
|
+
}
|
|
8497
|
+
return false;
|
|
7935
8498
|
}
|
|
7936
8499
|
function restrictedSqlArgs(params) {
|
|
8500
|
+
const refuse2 = () => params.refusal?.() ?? new VibeError20({ code: "VIBE_VALIDATION", message: `SQL fragments are unavailable through ${params.path}; use the unrestricted client`, meta: { path: params.path } });
|
|
8501
|
+
if (containsSqlFragment(params.value))
|
|
8502
|
+
throw refuse2();
|
|
7937
8503
|
const seen = new Map;
|
|
7938
8504
|
const copy = (value) => {
|
|
7939
8505
|
if (isSqlFragment2(value))
|
|
7940
|
-
throw
|
|
8506
|
+
throw refuse2();
|
|
7941
8507
|
if (typeof value !== "object" || value === null)
|
|
7942
8508
|
return value;
|
|
7943
8509
|
if (seen.has(value))
|
|
7944
8510
|
return seen.get(value);
|
|
8511
|
+
let out;
|
|
7945
8512
|
if (Array.isArray(value)) {
|
|
7946
|
-
const
|
|
7947
|
-
|
|
8513
|
+
const array = [];
|
|
8514
|
+
out = array;
|
|
8515
|
+
seen.set(value, out);
|
|
7948
8516
|
for (const item of value)
|
|
7949
|
-
|
|
7950
|
-
|
|
8517
|
+
array.push(copy(item));
|
|
8518
|
+
} else if (value instanceof Date) {
|
|
8519
|
+
out = new Date(value.getTime());
|
|
8520
|
+
} else if (value instanceof Uint8Array) {
|
|
8521
|
+
out = Buffer2.isBuffer(value) ? Buffer2.from(value) : new Uint8Array(value);
|
|
8522
|
+
} else {
|
|
8523
|
+
const record = Object.create(null);
|
|
8524
|
+
out = record;
|
|
8525
|
+
seen.set(value, out);
|
|
8526
|
+
for (const [key, child] of Object.entries(value)) {
|
|
8527
|
+
if (key !== "toJSON" || typeof child !== "function")
|
|
8528
|
+
record[key] = copy(child);
|
|
8529
|
+
}
|
|
7951
8530
|
}
|
|
7952
|
-
if (value instanceof Date)
|
|
7953
|
-
return new Date(value.getTime());
|
|
7954
|
-
if (value instanceof Uint8Array)
|
|
7955
|
-
return new Uint8Array(value);
|
|
7956
|
-
const out = Object.create(null);
|
|
7957
8531
|
seen.set(value, out);
|
|
7958
|
-
|
|
7959
|
-
|
|
8532
|
+
const toJSON = value.toJSON;
|
|
8533
|
+
const nativeJson = value instanceof Date && toJSON === Date.prototype.toJSON || Buffer2.isBuffer(value) && toJSON === Buffer2.prototype.toJSON;
|
|
8534
|
+
if (typeof toJSON === "function" && !nativeJson) {
|
|
8535
|
+
Object.defineProperty(out, "toJSON", { enumerable: false, value: (key) => {
|
|
8536
|
+
const result = toJSON.call(value, key);
|
|
8537
|
+
return result === value ? out : restrictedSqlArgs({ ...params, value: result });
|
|
8538
|
+
} });
|
|
8539
|
+
}
|
|
7960
8540
|
return out;
|
|
7961
8541
|
};
|
|
7962
8542
|
return copy(params.value);
|
|
7963
8543
|
}
|
|
8544
|
+
var conflictSchemas = new WeakMap;
|
|
8545
|
+
function registerRestrictedSqlSchema(params) {
|
|
8546
|
+
conflictSchemas.set(params.meta, new Map(params.schema.models.map((model) => [
|
|
8547
|
+
model.name,
|
|
8548
|
+
model.indexes.flatMap((index) => index.kind === "unique" && index.where !== undefined ? [{ fields: index.fields.map((field) => field.name), where: index.where }] : [])
|
|
8549
|
+
])));
|
|
8550
|
+
}
|
|
8551
|
+
function restrictConflictPredicates(params) {
|
|
8552
|
+
for (const key of ["onConflict", "skipDuplicates"]) {
|
|
8553
|
+
const option = params.args[key];
|
|
8554
|
+
if (typeof option !== "object" || option === null)
|
|
8555
|
+
continue;
|
|
8556
|
+
const spec = option;
|
|
8557
|
+
if (spec.where === undefined)
|
|
8558
|
+
continue;
|
|
8559
|
+
const target = spec.target;
|
|
8560
|
+
const declared = conflictSchemas.get(params.meta)?.get(params.model.name)?.some((index) => typeof spec.where === "string" && index.where === spec.where && Array.isArray(target) && target.length === index.fields.length && index.fields.every((field, position) => field === target[position]));
|
|
8561
|
+
if (declared !== true)
|
|
8562
|
+
throw new VibeError20({ code: "VIBE_VALIDATION", message: `${params.path}: ${key}.where must match a declared unique index and its target fields; free-form SQL requires the unrestricted client`, meta: { model: params.model.name, path: params.path } });
|
|
8563
|
+
}
|
|
8564
|
+
}
|
|
7964
8565
|
|
|
7965
8566
|
// packages/runtime/src/views.ts
|
|
7966
8567
|
function validationError6(params) {
|
|
7967
|
-
return new
|
|
8568
|
+
return new VibeError21({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
7968
8569
|
}
|
|
7969
8570
|
|
|
7970
8571
|
class ViewResult {
|
|
@@ -8318,7 +8919,7 @@ function createView(params) {
|
|
|
8318
8919
|
for (const [clientName, mask] of resolved) {
|
|
8319
8920
|
const delegate = models[clientName];
|
|
8320
8921
|
if (delegate === undefined) {
|
|
8321
|
-
throw new
|
|
8922
|
+
throw new VibeError21({
|
|
8322
8923
|
code: "VIBE_INTERNAL",
|
|
8323
8924
|
message: `view "${name}": no delegate for model "${mask.model.name}"`,
|
|
8324
8925
|
meta: { view: name, model: mask.model.name }
|
|
@@ -8334,9 +8935,9 @@ function attachDefineView(params) {
|
|
|
8334
8935
|
Object.defineProperty(client, "$defineView", { value, enumerable: true });
|
|
8335
8936
|
}
|
|
8336
8937
|
// packages/runtime/src/validators.ts
|
|
8337
|
-
import { VibeError as
|
|
8938
|
+
import { VibeError as VibeError22 } from "@vibeorm/schema";
|
|
8338
8939
|
function validationFailed(params) {
|
|
8339
|
-
return new
|
|
8940
|
+
return new VibeError22({
|
|
8340
8941
|
code: "VIBE_VALIDATION",
|
|
8341
8942
|
message: `validation failed for ${params.model}.${params.field}: ${params.issues[0]?.message ?? "invalid value"}`,
|
|
8342
8943
|
meta: { model: params.model, field: params.field, issues: params.issues }
|
|
@@ -8345,7 +8946,7 @@ function validationFailed(params) {
|
|
|
8345
8946
|
function validateValue(params) {
|
|
8346
8947
|
const result = params.schema["~standard"].validate(params.value);
|
|
8347
8948
|
if (result instanceof Promise) {
|
|
8348
|
-
throw new
|
|
8949
|
+
throw new VibeError22({
|
|
8349
8950
|
code: "VIBE_VALIDATION",
|
|
8350
8951
|
message: `async validator on ${params.model}.${params.field} \u2014 write validators must be synchronous (validate before calling the client for async rules)`,
|
|
8351
8952
|
meta: { model: params.model, field: params.field }
|
|
@@ -8468,7 +9069,29 @@ function deferPolicyOperation(params) {
|
|
|
8468
9069
|
}
|
|
8469
9070
|
|
|
8470
9071
|
// packages/runtime/src/policy.ts
|
|
8471
|
-
import { VibeError as
|
|
9072
|
+
import { VibeError as VibeError24 } from "@vibeorm/schema";
|
|
9073
|
+
|
|
9074
|
+
// packages/runtime/src/module-context.ts
|
|
9075
|
+
import { VibeError as VibeError23 } from "@vibeorm/schema";
|
|
9076
|
+
var binders = new WeakMap;
|
|
9077
|
+
function registerModuleBinder(params) {
|
|
9078
|
+
binders.set(params.client, params.bind);
|
|
9079
|
+
}
|
|
9080
|
+
function inheritReadOnlyModuleBinder(params) {
|
|
9081
|
+
const bind = binders.get(params.from);
|
|
9082
|
+
if (bind !== undefined)
|
|
9083
|
+
binders.set(params.to, (artifact) => bind(artifact).$readOnly());
|
|
9084
|
+
}
|
|
9085
|
+
function bindModuleContext(params) {
|
|
9086
|
+
const bind = binders.get(params.db);
|
|
9087
|
+
if (bind === undefined)
|
|
9088
|
+
throw new VibeError23({
|
|
9089
|
+
code: "VIBE_CONFIG",
|
|
9090
|
+
message: `Module ${params.artifact.manifest.id} needs an unrestricted handle from the same VibeORM runtime installation; scoped and foreign handles cannot attach package models`,
|
|
9091
|
+
meta: { reason: "module_handle", moduleId: params.artifact.manifest.id, protocol: params.artifact.manifest.protocol }
|
|
9092
|
+
});
|
|
9093
|
+
return bind(params.artifact);
|
|
9094
|
+
}
|
|
8472
9095
|
|
|
8473
9096
|
// packages/runtime/src/read-only.ts
|
|
8474
9097
|
import { ReadOnlyClientError } from "@vibeorm/schema";
|
|
@@ -8555,7 +9178,7 @@ function classifiedMount(params) {
|
|
|
8555
9178
|
return typeof value === "function" ? (...args) => {
|
|
8556
9179
|
if (containsSqlFragment(args))
|
|
8557
9180
|
return Promise.reject(readOnlyRefusal({ path: "extension", reason: "raw" }));
|
|
8558
|
-
return value(...restrictedSqlArgs({ value: args, path: "$readOnly extension" }));
|
|
9181
|
+
return value(...restrictedSqlArgs({ value: args, path: "$readOnly extension", refusal: () => readOnlyRefusal({ path: "extension", reason: "raw" }) }));
|
|
8559
9182
|
} : undefined;
|
|
8560
9183
|
if (typeof value !== "object" || value === null)
|
|
8561
9184
|
return;
|
|
@@ -8567,7 +9190,7 @@ function classifiedMount(params) {
|
|
|
8567
9190
|
namespace[member] = (...args) => {
|
|
8568
9191
|
if (containsSqlFragment(args))
|
|
8569
9192
|
return Promise.reject(readOnlyRefusal({ path: member, reason: "raw" }));
|
|
8570
|
-
return fn(...restrictedSqlArgs({ value: args, path: "$readOnly extension" }));
|
|
9193
|
+
return fn(...restrictedSqlArgs({ value: args, path: "$readOnly extension", refusal: () => readOnlyRefusal({ path: member, reason: "raw" }) }));
|
|
8571
9194
|
};
|
|
8572
9195
|
}
|
|
8573
9196
|
return Object.freeze(namespace);
|
|
@@ -8582,7 +9205,7 @@ function readOnlyDelegate(params) {
|
|
|
8582
9205
|
delegate[method] = (...args) => {
|
|
8583
9206
|
if (containsSqlFragment(args))
|
|
8584
9207
|
return Promise.reject(readOnlyRefusal({ path: `${model.clientName}.${method}`, reason: "raw" }));
|
|
8585
|
-
const operation = fn(...args);
|
|
9208
|
+
const operation = fn(...restrictedSqlArgs({ value: args, path: "$readOnly", refusal: () => readOnlyRefusal({ path: `${model.clientName}.${method}`, reason: "raw" }) }));
|
|
8586
9209
|
params.operations.restrictSql?.({ value: operation });
|
|
8587
9210
|
return operation;
|
|
8588
9211
|
};
|
|
@@ -8701,6 +9324,7 @@ function readOnlyOver(params) {
|
|
|
8701
9324
|
if (existing !== undefined)
|
|
8702
9325
|
return existing;
|
|
8703
9326
|
const built = buildReadOnly(params);
|
|
9327
|
+
inheritReadOnlyModuleBinder({ from: params.client, to: built });
|
|
8704
9328
|
facades.set(params.client, built);
|
|
8705
9329
|
return built;
|
|
8706
9330
|
}
|
|
@@ -8731,7 +9355,7 @@ function inheritReadOnly(params) {
|
|
|
8731
9355
|
|
|
8732
9356
|
// packages/runtime/src/policy.ts
|
|
8733
9357
|
function policyError(message, meta) {
|
|
8734
|
-
return new
|
|
9358
|
+
return new VibeError24({ code: "VIBE_VALIDATION", message, meta: meta ?? {} });
|
|
8735
9359
|
}
|
|
8736
9360
|
function policiedModels(meta) {
|
|
8737
9361
|
return meta.models.filter((model) => model.policy !== undefined);
|
|
@@ -9056,7 +9680,11 @@ function scopedDelegate(params) {
|
|
|
9056
9680
|
const flat = uniqueToFilter({ model, where: scoped.where ?? {} });
|
|
9057
9681
|
return { ...scoped, where: mergeWhere({ where: flat, field: policy.field, value }) };
|
|
9058
9682
|
};
|
|
9059
|
-
const scopeWrite = (
|
|
9683
|
+
const scopeWrite = (input) => {
|
|
9684
|
+
const args = restrictedSqlArgs({ value: input, path: "$withPolicy" });
|
|
9685
|
+
restrictConflictPredicates({ meta, model, args, path: "$withPolicy" });
|
|
9686
|
+
return scopeSelection({ meta, model, args, context });
|
|
9687
|
+
};
|
|
9060
9688
|
const writeData = (data, phase) => forceWriteData({ meta, model, data: data ?? {}, context, phase });
|
|
9061
9689
|
const pkClientKey = () => {
|
|
9062
9690
|
for (const [key, group] of model.compoundUniques) {
|
|
@@ -9077,7 +9705,7 @@ function scopedDelegate(params) {
|
|
|
9077
9705
|
const pkWhere = pk.length === 1 ? { [pk[0]]: target[pk[0]] } : { [pkClientKey()]: Object.fromEntries(pk.map((name) => [name, target[name]])) };
|
|
9078
9706
|
return write(raw, pkWhere);
|
|
9079
9707
|
};
|
|
9080
|
-
const notFound2 = (method) => new
|
|
9708
|
+
const notFound2 = (method) => new VibeError24({ 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 } });
|
|
9081
9709
|
return {
|
|
9082
9710
|
findMany: (args) => base.findMany(scopeFind(args)),
|
|
9083
9711
|
findFirst: (args) => base.findFirst(scopeFind(args)),
|
|
@@ -9086,7 +9714,7 @@ function scopedDelegate(params) {
|
|
|
9086
9714
|
findUniqueOrThrow: (args) => base.findFirstOrThrow(scopeUnique(args)),
|
|
9087
9715
|
create: (args) => deferred(() => base.create({ ...scopeWrite(args), data: writeData(args.data, "create") })),
|
|
9088
9716
|
createMany: (args) => deferred(() => base.createMany({
|
|
9089
|
-
...args,
|
|
9717
|
+
...scopeWrite(args),
|
|
9090
9718
|
data: (args.data ?? []).map((row) => writeData(row, "create"))
|
|
9091
9719
|
})),
|
|
9092
9720
|
createManyAndReturn: (args) => deferred(() => base.createManyAndReturn({
|
|
@@ -9111,7 +9739,7 @@ function scopedDelegate(params) {
|
|
|
9111
9739
|
})
|
|
9112
9740
|
})),
|
|
9113
9741
|
upsertMany: () => {
|
|
9114
|
-
throw new
|
|
9742
|
+
throw new VibeError24({
|
|
9115
9743
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
9116
9744
|
message: "upsertMany is not available on a policy-bound client: its conflict target is not policy-checked yet",
|
|
9117
9745
|
meta: { model: model.name }
|
|
@@ -9120,7 +9748,7 @@ function scopedDelegate(params) {
|
|
|
9120
9748
|
updateManyAndReturn: (args) => deferred(() => {
|
|
9121
9749
|
const run = base.updateManyAndReturn;
|
|
9122
9750
|
if (run === undefined) {
|
|
9123
|
-
throw new
|
|
9751
|
+
throw new VibeError24({
|
|
9124
9752
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
9125
9753
|
message: "updateManyAndReturn is not available on this delegate",
|
|
9126
9754
|
meta: { model: model.name }
|
|
@@ -9169,7 +9797,10 @@ function scopedDelegate(params) {
|
|
|
9169
9797
|
function globalDelegate(params) {
|
|
9170
9798
|
const { meta, model, base, context } = params;
|
|
9171
9799
|
const readArgs = (args) => scopeArgs({ meta, model, args: args ?? {}, context });
|
|
9172
|
-
const sel = (
|
|
9800
|
+
const sel = (input) => {
|
|
9801
|
+
const args = params.policyBound === false ? input : restrictedSqlArgs({ value: input, path: "$withPolicy" });
|
|
9802
|
+
if (params.policyBound !== false)
|
|
9803
|
+
restrictConflictPredicates({ meta, model, args, path: "$withPolicy" });
|
|
9173
9804
|
const scoped = scopeSelection({ meta, model, args, context });
|
|
9174
9805
|
return scoped.where === undefined ? scoped : { ...scoped, where: scopeWhere({ meta, model, where: scoped.where, context }) };
|
|
9175
9806
|
};
|
|
@@ -9184,14 +9815,14 @@ function globalDelegate(params) {
|
|
|
9184
9815
|
aggregate: (args) => base.aggregate(readArgs(args)),
|
|
9185
9816
|
groupBy: (args) => base.groupBy(readArgs(args)),
|
|
9186
9817
|
create: (args) => deferred(() => base.create({ ...sel(args), data: payload(args.data, "create") })),
|
|
9187
|
-
createMany: (args) => deferred(() => base.createMany({ ...args, data: (args.data ?? []).map((r) => payload(r, "create")) })),
|
|
9818
|
+
createMany: (args) => deferred(() => base.createMany({ ...sel(args), data: (args.data ?? []).map((r) => payload(r, "create")) })),
|
|
9188
9819
|
createManyAndReturn: (args) => deferred(() => base.createManyAndReturn({ ...sel(args), data: (args.data ?? []).map((r) => payload(r, "create")) })),
|
|
9189
9820
|
update: (args) => deferred(() => base.update({ ...sel(args), data: payload(args.data, "update") })),
|
|
9190
9821
|
updateMany: (args) => deferred(() => base.updateMany({ ...sel(args), data: payload(args.data, "update") })),
|
|
9191
9822
|
updateManyAndReturn: (args) => deferred(() => {
|
|
9192
9823
|
const run = base.updateManyAndReturn;
|
|
9193
9824
|
if (run === undefined) {
|
|
9194
|
-
throw new
|
|
9825
|
+
throw new VibeError24({
|
|
9195
9826
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
9196
9827
|
message: "updateManyAndReturn is not available on this delegate"
|
|
9197
9828
|
});
|
|
@@ -9202,7 +9833,7 @@ function globalDelegate(params) {
|
|
|
9202
9833
|
upsertMany: (args) => deferred(() => {
|
|
9203
9834
|
if (params.policyBound === false)
|
|
9204
9835
|
return base.upsertMany(args);
|
|
9205
|
-
throw new
|
|
9836
|
+
throw new VibeError24({
|
|
9206
9837
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
9207
9838
|
message: "upsertMany is not available on a policy-bound client: its conflict target is not policy-checked yet"
|
|
9208
9839
|
});
|
|
@@ -9269,13 +9900,26 @@ function attachPolicy(params) {
|
|
|
9269
9900
|
});
|
|
9270
9901
|
}
|
|
9271
9902
|
}
|
|
9272
|
-
const buildScoped = (
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
if (context[key] === undefined || context[key] === null) {
|
|
9276
|
-
throw policyError(`$withPolicy: missing context value "${key}" (required by ${model.name})`, { context: key });
|
|
9277
|
-
}
|
|
9903
|
+
const buildScoped = (input) => {
|
|
9904
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
9905
|
+
throw policyError("$withPolicy: context must contain own scalar identity values");
|
|
9278
9906
|
}
|
|
9907
|
+
const snapshot = Object.create(null);
|
|
9908
|
+
for (const model of policied) {
|
|
9909
|
+
const policy = model.policy;
|
|
9910
|
+
const key = policy.context;
|
|
9911
|
+
const descriptor = Object.getOwnPropertyDescriptor(Object.hasOwn(snapshot, key) ? snapshot : input, key);
|
|
9912
|
+
if (descriptor === undefined || !("value" in descriptor) || descriptor.value === undefined || descriptor.value === null) {
|
|
9913
|
+
throw policyError(`$withPolicy: missing context value "${key}" (required as an own data property by ${model.name})`, { context: key });
|
|
9914
|
+
}
|
|
9915
|
+
const value = descriptor.value;
|
|
9916
|
+
const field = model.fieldByName.get(policy.field);
|
|
9917
|
+
const valid = field !== undefined && !field.isList && (field.scalarType === "String" ? typeof value === "string" : field.scalarType === "Int" ? typeof value === "number" && Number.isSafeInteger(value) : field.scalarType === "BigInt" ? typeof value === "bigint" : field.scalarType === "Float" || field.scalarType === "Decimal" ? typeof value === "number" && Number.isFinite(value) : field.enumName !== null && typeof value === "string" && meta.enums.get(field.enumName)?.includes(value) === true);
|
|
9918
|
+
if (!valid)
|
|
9919
|
+
throw policyError(`$withPolicy: context "${key}" must match the scalar type of ${model.name}.${policy.field}`, { context: key, model: model.name, field: policy.field });
|
|
9920
|
+
snapshot[key] = value;
|
|
9921
|
+
}
|
|
9922
|
+
const context = Object.freeze(snapshot);
|
|
9279
9923
|
const scoped = Object.create(client);
|
|
9280
9924
|
const scopedModels = { ...client.$models };
|
|
9281
9925
|
for (const model of meta.models) {
|
|
@@ -9360,7 +10004,7 @@ function attachPolicy(params) {
|
|
|
9360
10004
|
});
|
|
9361
10005
|
}
|
|
9362
10006
|
// packages/runtime/src/scoped.ts
|
|
9363
|
-
import { ScopedQueryError, VibeError as
|
|
10007
|
+
import { ScopedQueryError, VibeError as VibeError25 } from "@vibeorm/schema";
|
|
9364
10008
|
function refusal(params) {
|
|
9365
10009
|
return new ScopedQueryError(params);
|
|
9366
10010
|
}
|
|
@@ -10093,6 +10737,7 @@ function rewriteScopedCall(params) {
|
|
|
10093
10737
|
const scoped = entry.kind !== "none";
|
|
10094
10738
|
const root = `${model.clientName}.${method}`;
|
|
10095
10739
|
const args = restrictedSqlArgs({ value: params.args ?? {}, path: "$scoped" });
|
|
10740
|
+
restrictConflictPredicates({ meta, model, args, path: "$scoped" });
|
|
10096
10741
|
const ownPred = () => requirePred({ ctx, model, path: root });
|
|
10097
10742
|
const walkOwnWhere = (where, path) => isPlainObject8(where) ? walkWhere({ ctx, model, where, path }) : where;
|
|
10098
10743
|
if (FIND_LIKE.includes(method)) {
|
|
@@ -10315,7 +10960,7 @@ function attachScoped(params) {
|
|
|
10315
10960
|
Object.defineProperty(client, "$scoped", {
|
|
10316
10961
|
value: (config) => {
|
|
10317
10962
|
if (!isPlainObject8(config) || !isPlainObject8(config.models)) {
|
|
10318
|
-
throw new
|
|
10963
|
+
throw new VibeError25({
|
|
10319
10964
|
code: "VIBE_VALIDATION",
|
|
10320
10965
|
message: "$scoped takes { scope: string | number | bigint | null, models: { <Model>: \u2026 } }"
|
|
10321
10966
|
});
|
|
@@ -10331,7 +10976,7 @@ function attachScoped(params) {
|
|
|
10331
10976
|
}
|
|
10332
10977
|
const scope = config.scope;
|
|
10333
10978
|
if (scope !== null && typeof scope !== "string" && typeof scope !== "number" && typeof scope !== "bigint") {
|
|
10334
|
-
throw new
|
|
10979
|
+
throw new VibeError25({
|
|
10335
10980
|
code: "VIBE_VALIDATION",
|
|
10336
10981
|
message: `$scoped: scope must be a string, number, bigint or null \u2014 got ${typeof scope}`
|
|
10337
10982
|
});
|
|
@@ -10347,7 +10992,7 @@ function attachScoped(params) {
|
|
|
10347
10992
|
});
|
|
10348
10993
|
}
|
|
10349
10994
|
// packages/runtime/src/rls-readiness.ts
|
|
10350
|
-
import { VibeError as
|
|
10995
|
+
import { VibeError as VibeError26, assertValidSchemaIR, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
10351
10996
|
import { NATIVE_RLS as NATIVE_RLS2, getSqlDialect as getSqlDialect4, renderRlsPolicy, renderRlsRole, NATIVE_RLS_PROBE_POLICY, renderRlsReadinessQuery, renderRlsProbeTable } from "@vibeorm/sql";
|
|
10352
10997
|
import { RLS_READINESS_SQL, NATIVE_RLS_PROBE_POLICY as NATIVE_RLS_PROBE_POLICY2 } from "@vibeorm/sql";
|
|
10353
10998
|
function textOf(params) {
|
|
@@ -10370,6 +11015,35 @@ function intOf(params) {
|
|
|
10370
11015
|
return null;
|
|
10371
11016
|
return Number.parseInt(value, 10);
|
|
10372
11017
|
}
|
|
11018
|
+
function unreadableCatalog(params) {
|
|
11019
|
+
return new VibeError26({
|
|
11020
|
+
code: "VIBE_ADAPTER",
|
|
11021
|
+
message: `native RLS readiness could not read the "${params.key}" result of the folded catalog read: ${params.detail}. ` + "The inspection is refused rather than treated as an empty result set.",
|
|
11022
|
+
meta: { result: params.key }
|
|
11023
|
+
});
|
|
11024
|
+
}
|
|
11025
|
+
function catalogRowsOf(params) {
|
|
11026
|
+
const value = params.row[params.key];
|
|
11027
|
+
if (value === null || value === undefined)
|
|
11028
|
+
return [];
|
|
11029
|
+
let parsed = value;
|
|
11030
|
+
if (typeof value === "string") {
|
|
11031
|
+
try {
|
|
11032
|
+
parsed = JSON.parse(value);
|
|
11033
|
+
} catch (error) {
|
|
11034
|
+
throw unreadableCatalog({ key: params.key, detail: describeFailure({ error }) });
|
|
11035
|
+
}
|
|
11036
|
+
}
|
|
11037
|
+
if (!Array.isArray(parsed)) {
|
|
11038
|
+
throw unreadableCatalog({ key: params.key, detail: `expected a JSON array, got ${typeof parsed}` });
|
|
11039
|
+
}
|
|
11040
|
+
return parsed.map((entry) => {
|
|
11041
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
11042
|
+
throw unreadableCatalog({ key: params.key, detail: `expected JSON objects, got ${entry === null ? "null" : typeof entry}` });
|
|
11043
|
+
}
|
|
11044
|
+
return entry;
|
|
11045
|
+
});
|
|
11046
|
+
}
|
|
10373
11047
|
var EXPRESSION_CACHE_LIMITS = Object.freeze({ connections: 64 });
|
|
10374
11048
|
var expressionCaches = new WeakMap;
|
|
10375
11049
|
function buildCheck(params) {
|
|
@@ -10401,7 +11075,7 @@ function requireNativeRls(params) {
|
|
|
10401
11075
|
throw unsupportedCapability7({ dialect: dialect.name, feature: "nativeRowLevelSecurity" });
|
|
10402
11076
|
}
|
|
10403
11077
|
if (params.schema.rls === undefined) {
|
|
10404
|
-
throw new
|
|
11078
|
+
throw new VibeError26({
|
|
10405
11079
|
code: "VIBE_CONFIG",
|
|
10406
11080
|
message: "Native row-level security readiness requires a schema that declares native RLS"
|
|
10407
11081
|
});
|
|
@@ -10692,8 +11366,9 @@ async function inspectNativeRlsTransaction(params) {
|
|
|
10692
11366
|
const dialect = requireNativeRls({ adapter, schema });
|
|
10693
11367
|
const protectedTables = protectedTablesOf({ schema });
|
|
10694
11368
|
const unprotected = unprotectedTablesOf({ schema });
|
|
10695
|
-
const
|
|
10696
|
-
const
|
|
11369
|
+
const catalogRows = await adapter.execute(renderRlsReadinessQuery({ dialect, query: "catalog" }));
|
|
11370
|
+
const catalog = catalogRows[0] ?? {};
|
|
11371
|
+
const identityRow = catalogRowsOf({ row: catalog, key: "identity" })[0] ?? {};
|
|
10697
11372
|
const identity = {
|
|
10698
11373
|
currentRole: textOf({ row: identityRow, key: "currentUser" }),
|
|
10699
11374
|
sessionRole: textOf({ row: identityRow, key: "sessionUser" }),
|
|
@@ -10703,13 +11378,13 @@ async function inspectNativeRlsTransaction(params) {
|
|
|
10703
11378
|
createRole: flagOf({ row: identityRow, key: "createRole" }),
|
|
10704
11379
|
serverVersionNum: intOf({ row: identityRow, key: "serverVersionNum" })
|
|
10705
11380
|
};
|
|
10706
|
-
const elevatedRows =
|
|
10707
|
-
const controlRows =
|
|
10708
|
-
const tableRows =
|
|
10709
|
-
const policyRows =
|
|
10710
|
-
const viewRows =
|
|
10711
|
-
const routineRows =
|
|
10712
|
-
const triggerRows =
|
|
11381
|
+
const elevatedRows = catalogRowsOf({ row: catalog, key: "elevatedRoles" });
|
|
11382
|
+
const controlRows = catalogRowsOf({ row: catalog, key: "schemaControl" });
|
|
11383
|
+
const tableRows = catalogRowsOf({ row: catalog, key: "tables" });
|
|
11384
|
+
const policyRows = catalogRowsOf({ row: catalog, key: "policies" });
|
|
11385
|
+
const viewRows = catalogRowsOf({ row: catalog, key: "views" });
|
|
11386
|
+
const routineRows = catalogRowsOf({ row: catalog, key: "routines" });
|
|
11387
|
+
const triggerRows = catalogRowsOf({ row: catalog, key: "triggers" });
|
|
10713
11388
|
const controlRow = controlRows[0];
|
|
10714
11389
|
const control = controlRow === undefined ? undefined : {
|
|
10715
11390
|
schema: textOf({ row: controlRow, key: "schema" }),
|
|
@@ -10813,7 +11488,7 @@ async function inspectNativeRls(params) {
|
|
|
10813
11488
|
function requireEmbeddedRole(params) {
|
|
10814
11489
|
const { role } = params;
|
|
10815
11490
|
if (typeof role !== "string" || role.length === 0 || role.includes("\x00")) {
|
|
10816
|
-
throw new
|
|
11491
|
+
throw new VibeError26({
|
|
10817
11492
|
code: "VIBE_CONFIG",
|
|
10818
11493
|
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",
|
|
10819
11494
|
meta: { provider: "pglite" }
|
|
@@ -10825,7 +11500,7 @@ function assertNativeRlsReady(params) {
|
|
|
10825
11500
|
const { report } = params;
|
|
10826
11501
|
if (report.ready)
|
|
10827
11502
|
return;
|
|
10828
|
-
throw new
|
|
11503
|
+
throw new VibeError26({
|
|
10829
11504
|
code: "VIBE_CONFIG",
|
|
10830
11505
|
message: `Native row-level security is not ready on this database: ${report.problems.map((problem) => `${problem.name} (${problem.status})`).join(", ")}`,
|
|
10831
11506
|
meta: {
|
|
@@ -10849,9 +11524,13 @@ function formatNativeRlsReport(params) {
|
|
|
10849
11524
|
}
|
|
10850
11525
|
return lines;
|
|
10851
11526
|
}
|
|
11527
|
+
// packages/runtime/src/database-module.ts
|
|
11528
|
+
import { createHash } from "crypto";
|
|
11529
|
+
import { VibeError as VibeError31, assertValidSchemaIR as assertValidSchemaIR3, providerToDialect } from "@vibeorm/schema";
|
|
11530
|
+
|
|
10852
11531
|
// packages/runtime/src/client.ts
|
|
10853
11532
|
import { compileSql } from "@vibeorm/sql";
|
|
10854
|
-
import { RlsContextError as RlsContextError2, VibeError as
|
|
11533
|
+
import { RlsContextError as RlsContextError2, VibeError as VibeError30, assertValidSchemaIR as assertValidSchemaIR2, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
10855
11534
|
import { ADVISORY_LOCK_RESULT_COLUMN, ParamCollector, getSqlDialect as getSqlDialect5, renderRlsContext, renderRlsPolicy as renderRlsPolicy2, renderRlsRole as renderRlsRole2, renderStatement as renderStatement5 } from "@vibeorm/sql";
|
|
10856
11535
|
|
|
10857
11536
|
// packages/runtime/src/render-cache.ts
|
|
@@ -11362,11 +12041,11 @@ function renderStatementCached(params) {
|
|
|
11362
12041
|
}
|
|
11363
12042
|
|
|
11364
12043
|
// packages/runtime/src/strict-args.ts
|
|
11365
|
-
import { VibeError as
|
|
12044
|
+
import { VibeError as VibeError27 } from "@vibeorm/schema";
|
|
11366
12045
|
var SKIP = Symbol.for("vibeorm.skip");
|
|
11367
12046
|
function explicitUndefined(params) {
|
|
11368
12047
|
const { model, method, path } = params;
|
|
11369
|
-
return new
|
|
12048
|
+
return new VibeError27({
|
|
11370
12049
|
code: "VIBE_VALIDATION",
|
|
11371
12050
|
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.`,
|
|
11372
12051
|
meta: { model, method, path }
|
|
@@ -11543,10 +12222,10 @@ function bindRlsContext(params) {
|
|
|
11543
12222
|
}
|
|
11544
12223
|
|
|
11545
12224
|
// packages/runtime/src/telemetry/recorder.ts
|
|
11546
|
-
import { VibeError as
|
|
12225
|
+
import { VibeError as VibeError29 } from "@vibeorm/schema";
|
|
11547
12226
|
|
|
11548
12227
|
// packages/runtime/src/telemetry/config.ts
|
|
11549
|
-
import { VibeError as
|
|
12228
|
+
import { VibeError as VibeError28 } from "@vibeorm/schema";
|
|
11550
12229
|
var TELEMETRY_DEFAULTS = Object.freeze({
|
|
11551
12230
|
captureSql: false,
|
|
11552
12231
|
captureParameterValues: false,
|
|
@@ -11560,8 +12239,8 @@ var TELEMETRY_DEFAULTS = Object.freeze({
|
|
|
11560
12239
|
maxFingerprintLength: 512,
|
|
11561
12240
|
latencyBucketsMs: Object.freeze([1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000])
|
|
11562
12241
|
});
|
|
11563
|
-
function
|
|
11564
|
-
return new
|
|
12242
|
+
function configError2(params) {
|
|
12243
|
+
return new VibeError28({
|
|
11565
12244
|
code: "VIBE_CONFIG",
|
|
11566
12245
|
message: `telemetry: ${params.message}`,
|
|
11567
12246
|
...params.meta === undefined ? {} : { meta: params.meta }
|
|
@@ -11572,7 +12251,7 @@ function positiveInteger(params) {
|
|
|
11572
12251
|
if (value === undefined)
|
|
11573
12252
|
return fallback;
|
|
11574
12253
|
if (!Number.isInteger(value) || value < 1) {
|
|
11575
|
-
throw
|
|
12254
|
+
throw configError2({ message: `${name} must be a positive integer, received ${String(value)}`, meta: { option: name } });
|
|
11576
12255
|
}
|
|
11577
12256
|
return value;
|
|
11578
12257
|
}
|
|
@@ -11581,22 +12260,22 @@ function nonNegativeNumber(params) {
|
|
|
11581
12260
|
if (value === undefined)
|
|
11582
12261
|
return fallback;
|
|
11583
12262
|
if (!Number.isFinite(value) || value < 0) {
|
|
11584
|
-
throw
|
|
12263
|
+
throw configError2({ message: `${name} must be a finite number >= 0, received ${String(value)}`, meta: { option: name } });
|
|
11585
12264
|
}
|
|
11586
12265
|
return value;
|
|
11587
12266
|
}
|
|
11588
12267
|
function resolveTelemetryConfig(params) {
|
|
11589
12268
|
const { options } = params;
|
|
11590
12269
|
if (options.exporter === undefined || options.exporter === null || typeof options.exporter !== "object") {
|
|
11591
|
-
throw
|
|
12270
|
+
throw configError2({ message: "an exporter is required \u2014 telemetry has nowhere to send events without one" });
|
|
11592
12271
|
}
|
|
11593
12272
|
const sampleRate = options.sampleRate ?? TELEMETRY_DEFAULTS.sampleRate;
|
|
11594
12273
|
if (!Number.isFinite(sampleRate) || sampleRate < 0 || sampleRate > 1) {
|
|
11595
|
-
throw
|
|
12274
|
+
throw configError2({ message: `sampleRate must be between 0 and 1, received ${String(sampleRate)}`, meta: { option: "sampleRate" } });
|
|
11596
12275
|
}
|
|
11597
12276
|
const delivery = options.delivery ?? TELEMETRY_DEFAULTS.delivery;
|
|
11598
12277
|
if (delivery !== "immediate" && delivery !== "buffered") {
|
|
11599
|
-
throw
|
|
12278
|
+
throw configError2({ message: `delivery must be "immediate" or "buffered", received ${JSON.stringify(delivery)}`, meta: { option: "delivery" } });
|
|
11600
12279
|
}
|
|
11601
12280
|
return {
|
|
11602
12281
|
captureSql: options.captureSql ?? TELEMETRY_DEFAULTS.captureSql,
|
|
@@ -11649,7 +12328,7 @@ function deriveStage(params) {
|
|
|
11649
12328
|
return "decode";
|
|
11650
12329
|
}
|
|
11651
12330
|
function errorCodeOf(error) {
|
|
11652
|
-
return error instanceof
|
|
12331
|
+
return error instanceof VibeError29 ? error.code : undefined;
|
|
11653
12332
|
}
|
|
11654
12333
|
function newHistogram(bounds) {
|
|
11655
12334
|
return { counts: new Array(bounds.length + 1).fill(0), sum: 0, count: 0 };
|
|
@@ -11968,8 +12647,8 @@ function createTelemetryRecorder(params) {
|
|
|
11968
12647
|
|
|
11969
12648
|
// packages/runtime/src/telemetry/statement.ts
|
|
11970
12649
|
async function instrumentStatement(params) {
|
|
11971
|
-
const { scope, legacy, run } = params;
|
|
11972
|
-
if (scope === undefined && legacy === undefined)
|
|
12650
|
+
const { scope, legacy, diagnose, run } = params;
|
|
12651
|
+
if (scope === undefined && legacy === undefined && diagnose === undefined)
|
|
11973
12652
|
return await run();
|
|
11974
12653
|
const started = performance.now();
|
|
11975
12654
|
let value;
|
|
@@ -12017,13 +12696,22 @@ async function instrumentStatement(params) {
|
|
|
12017
12696
|
};
|
|
12018
12697
|
scope.statement(success);
|
|
12019
12698
|
}
|
|
12699
|
+
diagnose?.({
|
|
12700
|
+
model: params.model,
|
|
12701
|
+
method: params.method,
|
|
12702
|
+
text: params.text,
|
|
12703
|
+
values: params.values,
|
|
12704
|
+
durationMs,
|
|
12705
|
+
inTransaction: params.inTransaction,
|
|
12706
|
+
raw: params.kind === "raw"
|
|
12707
|
+
});
|
|
12020
12708
|
return value;
|
|
12021
12709
|
}
|
|
12022
12710
|
|
|
12023
12711
|
// packages/runtime/src/client.ts
|
|
12024
12712
|
function notFound2(params) {
|
|
12025
12713
|
const { model, method } = params;
|
|
12026
|
-
return new
|
|
12714
|
+
return new VibeError30({
|
|
12027
12715
|
code: "VIBE_NOT_FOUND",
|
|
12028
12716
|
message: `no ${model} record matched the where clause of ${method}()`,
|
|
12029
12717
|
meta: { model, method }
|
|
@@ -12031,7 +12719,7 @@ function notFound2(params) {
|
|
|
12031
12719
|
}
|
|
12032
12720
|
function transactionLifecycleRefusal(params) {
|
|
12033
12721
|
const { method } = params;
|
|
12034
|
-
return new
|
|
12722
|
+
return new VibeError30({
|
|
12035
12723
|
code: "VIBE_VALIDATION",
|
|
12036
12724
|
message: `${method}() is not available on a transaction client \u2014 the connection is owned by the open transaction; call it on the client that started the transaction`,
|
|
12037
12725
|
meta: { method, inTransaction: true }
|
|
@@ -12039,7 +12727,7 @@ function transactionLifecycleRefusal(params) {
|
|
|
12039
12727
|
}
|
|
12040
12728
|
function viewIsReadOnly(params) {
|
|
12041
12729
|
const { model, method } = params;
|
|
12042
|
-
return new
|
|
12730
|
+
return new VibeError30({
|
|
12043
12731
|
code: "VIBE_VALIDATION",
|
|
12044
12732
|
message: `${model} is a view \u2014 ${method}() is not available on views (they are read-only); write to the underlying table instead`,
|
|
12045
12733
|
meta: { model, method }
|
|
@@ -12122,6 +12810,7 @@ function assembleAggregates(params) {
|
|
|
12122
12810
|
}
|
|
12123
12811
|
return into;
|
|
12124
12812
|
}
|
|
12813
|
+
var moduleMetadata = new WeakMap;
|
|
12125
12814
|
function engineStateError(params) {
|
|
12126
12815
|
const { engine } = params;
|
|
12127
12816
|
for (let frame = engine.lifetime;frame !== undefined; frame = frame.parent) {
|
|
@@ -12145,7 +12834,7 @@ async function engineTransaction(params) {
|
|
|
12145
12834
|
const support = engine.adapter.budgets?.transactionDeadline;
|
|
12146
12835
|
const requested = options.deadline.enforcement ?? "between-statements";
|
|
12147
12836
|
if (support === undefined || support === "unsupported" || requested === "cancel-running-statements" && support !== "cancel-running-statements") {
|
|
12148
|
-
throw new
|
|
12837
|
+
throw new VibeError30({
|
|
12149
12838
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
12150
12839
|
message: "the adapter does not declare support for the requested transaction deadline",
|
|
12151
12840
|
meta: { provider: engine.adapter.provider, feature: "transactionDeadline", requested, supported: support ?? "undeclared" }
|
|
@@ -12221,6 +12910,7 @@ function runStatement(params) {
|
|
|
12221
12910
|
return instrumentStatement({
|
|
12222
12911
|
scope: engine.operation,
|
|
12223
12912
|
legacy: legacySink({ engine }),
|
|
12913
|
+
diagnose: engine.diagnostics?.observe,
|
|
12224
12914
|
kind: params.kind,
|
|
12225
12915
|
model: params.model,
|
|
12226
12916
|
method: params.method,
|
|
@@ -12319,8 +13009,6 @@ class QueryPromise extends Promise {
|
|
|
12319
13009
|
const state = engineStateError({ engine: this.#target });
|
|
12320
13010
|
if (state !== undefined)
|
|
12321
13011
|
return state;
|
|
12322
|
-
if (engine.rlsSchema === undefined && this.#target.rlsSchema === undefined)
|
|
12323
|
-
return;
|
|
12324
13012
|
if (this.#target.rootIdentity !== engine.rootIdentity || this.#target.lifetime !== engine.lifetime || !sameRecord(this.#target.context?.settings ?? {}, engine.context?.settings ?? {}))
|
|
12325
13013
|
return new RlsContextError2({ reason: "conflicting" });
|
|
12326
13014
|
return;
|
|
@@ -12335,7 +13023,7 @@ class QueryPromise extends Promise {
|
|
|
12335
13023
|
}
|
|
12336
13024
|
#claim() {
|
|
12337
13025
|
if (this.#started) {
|
|
12338
|
-
throw new
|
|
13026
|
+
throw new VibeError30({
|
|
12339
13027
|
code: "VIBE_VALIDATION",
|
|
12340
13028
|
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)"
|
|
12341
13029
|
});
|
|
@@ -12344,6 +13032,7 @@ class QueryPromise extends Promise {
|
|
|
12344
13032
|
const scope = this.#scope;
|
|
12345
13033
|
return {
|
|
12346
13034
|
start: (target) => {
|
|
13035
|
+
target = { ...target, meta: this.#target.meta, renderCache: this.#target.renderCache, options: this.#target.options };
|
|
12347
13036
|
if (this.#target.readOnlySql === true)
|
|
12348
13037
|
target = { ...target, readOnlySql: true };
|
|
12349
13038
|
if (scope !== undefined && target.operation !== undefined) {
|
|
@@ -12448,19 +13137,18 @@ async function runAffected(params) {
|
|
|
12448
13137
|
}
|
|
12449
13138
|
var LOCKABLE_METHODS = Object.freeze(["findMany", "findFirst", "findUnique"]);
|
|
12450
13139
|
function plan(params) {
|
|
12451
|
-
const { engine, model, method
|
|
12452
|
-
|
|
12453
|
-
throw readOnlyRefusal({ path: `${model}.${method}`, reason: "raw" });
|
|
13140
|
+
const { engine, model, method } = params;
|
|
13141
|
+
const args = engine.readOnlySql === true ? restrictedSqlArgs({ value: params.args, path: "$readOnly", refusal: () => readOnlyRefusal({ path: `${model}.${method}`, reason: "raw" }) }) : params.args;
|
|
12454
13142
|
if (args !== undefined && args["lock"] !== undefined) {
|
|
12455
13143
|
if (engine.inTransaction !== true) {
|
|
12456
|
-
throw new
|
|
13144
|
+
throw new VibeError30({
|
|
12457
13145
|
code: "VIBE_VALIDATION",
|
|
12458
13146
|
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)`,
|
|
12459
13147
|
meta: { model, method }
|
|
12460
13148
|
});
|
|
12461
13149
|
}
|
|
12462
13150
|
if (!LOCKABLE_METHODS.includes(method)) {
|
|
12463
|
-
throw new
|
|
13151
|
+
throw new VibeError30({
|
|
12464
13152
|
code: "VIBE_VALIDATION",
|
|
12465
13153
|
message: `lock on ${model}.${method}() is not supported \u2014 only ${LOCKABLE_METHODS.join(", ")} take a lock. ` + `Lock the rows with a find inside the transaction, then write them`,
|
|
12466
13154
|
meta: { model, method }
|
|
@@ -12487,7 +13175,7 @@ function readAcquiredVerdict(params) {
|
|
|
12487
13175
|
return true;
|
|
12488
13176
|
if (value === false || value === 0 || value === "f" || value === "false" || value === "0")
|
|
12489
13177
|
return false;
|
|
12490
|
-
throw new
|
|
13178
|
+
throw new VibeError30({
|
|
12491
13179
|
code: "VIBE_INTERNAL",
|
|
12492
13180
|
message: `internal: ${params.method}() expected a boolean "${ADVISORY_LOCK_RESULT_COLUMN}" column, got ${String(value)}`,
|
|
12493
13181
|
meta: { method: params.method, value: String(value) }
|
|
@@ -12535,6 +13223,10 @@ async function insertedKeyWhere(params) {
|
|
|
12535
13223
|
hint: "this model's key is assigned by the database and cannot be read back on this dialect"
|
|
12536
13224
|
});
|
|
12537
13225
|
}
|
|
13226
|
+
const insertId = params.insertId;
|
|
13227
|
+
if (typeof insertId === "number" && Number.isSafeInteger(insertId) || typeof insertId === "string" && /^-?\d+$/.test(insertId)) {
|
|
13228
|
+
return { [keyField.name]: decodeValue({ dialect: engine.dialect.name, field: keyField, value: insertId }) };
|
|
13229
|
+
}
|
|
12538
13230
|
const result = await runStatement({
|
|
12539
13231
|
engine,
|
|
12540
13232
|
kind: "rows",
|
|
@@ -12547,7 +13239,7 @@ async function insertedKeyWhere(params) {
|
|
|
12547
13239
|
});
|
|
12548
13240
|
const id = result.rows[0]?.id;
|
|
12549
13241
|
if (id === undefined || id === null) {
|
|
12550
|
-
throw new
|
|
13242
|
+
throw new VibeError30({
|
|
12551
13243
|
code: "VIBE_INTERNAL",
|
|
12552
13244
|
message: `could not read back the generated key for ${source.model.name}`,
|
|
12553
13245
|
meta: { model: source.model.name }
|
|
@@ -12585,7 +13277,7 @@ async function runSingleMutation(params) {
|
|
|
12585
13277
|
values: bindValues({ adapter: target.adapter, values: rendered.values })
|
|
12586
13278
|
})
|
|
12587
13279
|
});
|
|
12588
|
-
return result
|
|
13280
|
+
return result;
|
|
12589
13281
|
};
|
|
12590
13282
|
if (readBeforeWrite) {
|
|
12591
13283
|
const where = source.refetchWhere;
|
|
@@ -12596,7 +13288,7 @@ async function runSingleMutation(params) {
|
|
|
12596
13288
|
const before = await refetch({ engine: txEngine, source, where, lock: true });
|
|
12597
13289
|
if (before === null)
|
|
12598
13290
|
throw notFound2({ model: source.model.name, method: source.method });
|
|
12599
|
-
const affected = await executeWrite(txEngine);
|
|
13291
|
+
const { affectedRows: affected } = await executeWrite(txEngine);
|
|
12600
13292
|
if (affected === 0)
|
|
12601
13293
|
throw notFound2({ model: source.model.name, method: source.method });
|
|
12602
13294
|
return before;
|
|
@@ -12631,7 +13323,7 @@ async function runSingleMutation(params) {
|
|
|
12631
13323
|
if (source.method === "update") {
|
|
12632
13324
|
return await engine.adapter.transaction(async (txAdapter) => {
|
|
12633
13325
|
const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
|
|
12634
|
-
const affected2 = await executeWrite(txEngine);
|
|
13326
|
+
const { affectedRows: affected2 } = await executeWrite(txEngine);
|
|
12635
13327
|
if (affected2 === 0)
|
|
12636
13328
|
throw notFound2({ model: source.model.name, method: source.method });
|
|
12637
13329
|
const after2 = await refetch({ engine: txEngine, source, where: knownWhere });
|
|
@@ -12640,7 +13332,7 @@ async function runSingleMutation(params) {
|
|
|
12640
13332
|
return after2;
|
|
12641
13333
|
});
|
|
12642
13334
|
}
|
|
12643
|
-
const affected = await executeWrite(engine);
|
|
13335
|
+
const { affectedRows: affected } = await executeWrite(engine);
|
|
12644
13336
|
if (affected === 0 && params.allowZeroRows === true)
|
|
12645
13337
|
return null;
|
|
12646
13338
|
const after = await refetch({ engine, source, where: knownWhere });
|
|
@@ -12650,10 +13342,10 @@ async function runSingleMutation(params) {
|
|
|
12650
13342
|
}
|
|
12651
13343
|
return await engine.adapter.transaction(async (txAdapter) => {
|
|
12652
13344
|
const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
|
|
12653
|
-
const affected = await executeWrite(txEngine);
|
|
13345
|
+
const { affectedRows: affected, insertId } = await executeWrite(txEngine);
|
|
12654
13346
|
if (affected === 0 && params.allowZeroRows === true)
|
|
12655
13347
|
return null;
|
|
12656
|
-
const where = await insertedKeyWhere({ engine: txEngine, source });
|
|
13348
|
+
const where = await insertedKeyWhere({ engine: txEngine, source, insertId });
|
|
12657
13349
|
const after = await refetch({ engine: txEngine, source, where });
|
|
12658
13350
|
if (after === null)
|
|
12659
13351
|
throw notFound2({ model: source.model.name, method: source.method });
|
|
@@ -12723,10 +13415,17 @@ function relationExecutor(params) {
|
|
|
12723
13415
|
});
|
|
12724
13416
|
};
|
|
12725
13417
|
}
|
|
13418
|
+
function relationConcurrency(params) {
|
|
13419
|
+
const { engine } = params;
|
|
13420
|
+
if (engine.inTransaction === true)
|
|
13421
|
+
return 1;
|
|
13422
|
+
return engine.options.relationLoading?.concurrency ?? 1;
|
|
13423
|
+
}
|
|
12726
13424
|
async function finalizeRows(params) {
|
|
12727
13425
|
const { engine, model, shape, rows } = params;
|
|
12728
13426
|
if (shape.relations !== null && rows.length > 0) {
|
|
12729
13427
|
const execute = relationExecutor({ engine });
|
|
13428
|
+
const concurrency = relationConcurrency({ engine });
|
|
12730
13429
|
if (shape.relationStrategy === "join") {
|
|
12731
13430
|
await attachLateralRelations({
|
|
12732
13431
|
dialect: engine.dialect,
|
|
@@ -12735,7 +13434,8 @@ async function finalizeRows(params) {
|
|
|
12735
13434
|
rows,
|
|
12736
13435
|
selection: shape.relations,
|
|
12737
13436
|
execute,
|
|
12738
|
-
wire: engine.adapter.wire
|
|
13437
|
+
wire: engine.adapter.wire,
|
|
13438
|
+
concurrency
|
|
12739
13439
|
});
|
|
12740
13440
|
} else {
|
|
12741
13441
|
await attachRelations({
|
|
@@ -12745,7 +13445,8 @@ async function finalizeRows(params) {
|
|
|
12745
13445
|
rows,
|
|
12746
13446
|
selection: shape.relations,
|
|
12747
13447
|
execute,
|
|
12748
|
-
wire: engine.adapter.wire
|
|
13448
|
+
wire: engine.adapter.wire,
|
|
13449
|
+
concurrency
|
|
12749
13450
|
});
|
|
12750
13451
|
}
|
|
12751
13452
|
}
|
|
@@ -12866,7 +13567,7 @@ function createDelegate(params) {
|
|
|
12866
13567
|
const skipping = args.onConflict !== undefined;
|
|
12867
13568
|
if (hasNestedWrites({ model: modelMeta, data: args.data })) {
|
|
12868
13569
|
if (skipping) {
|
|
12869
|
-
throw new
|
|
13570
|
+
throw new VibeError30({
|
|
12870
13571
|
code: "VIBE_VALIDATION",
|
|
12871
13572
|
message: `onConflict cannot combine with nested writes on ${modelMeta.name}.create() \u2014 insert the parent row alone, then connect relations`,
|
|
12872
13573
|
meta: { model: modelMeta.name }
|
|
@@ -12967,7 +13668,7 @@ function createDelegate(params) {
|
|
|
12967
13668
|
const upsert = async (target, args) => {
|
|
12968
13669
|
const modelMeta = getModelMeta({ meta: target.meta, model });
|
|
12969
13670
|
if (hasNestedWrites({ model: modelMeta, data: args.create }) || hasNestedWrites({ model: modelMeta, data: args.update })) {
|
|
12970
|
-
throw new
|
|
13671
|
+
throw new VibeError30({
|
|
12971
13672
|
code: "VIBE_VALIDATION",
|
|
12972
13673
|
message: `nested writes are not supported inside upsert arms on ${modelMeta.name} \u2014 use create()/update() with nested data`,
|
|
12973
13674
|
meta: { model: modelMeta.name }
|
|
@@ -12975,7 +13676,7 @@ function createDelegate(params) {
|
|
|
12975
13676
|
}
|
|
12976
13677
|
if (!upsertUsesNativeStatement({ model: modelMeta, args })) {
|
|
12977
13678
|
if (args.onConflict !== undefined) {
|
|
12978
|
-
throw new
|
|
13679
|
+
throw new VibeError30({
|
|
12979
13680
|
code: "VIBE_VALIDATION",
|
|
12980
13681
|
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`,
|
|
12981
13682
|
meta: { model: modelMeta.name }
|
|
@@ -13182,7 +13883,7 @@ function buildClient(params) {
|
|
|
13182
13883
|
text: statement.text,
|
|
13183
13884
|
values: statement.values,
|
|
13184
13885
|
rowCount: (value) => method.startsWith("$query") ? value.rows.length : value.affectedRows,
|
|
13185
|
-
run: () => statement.values.length > 0 ? runBoundAffected({ adapter: target.adapter, text: statement.text, values: statement.values }) : target.adapter.executeUnsafe(statement)
|
|
13886
|
+
run: () => statement.values.length > 0 ? target.adapter.executeRawBound !== undefined ? target.adapter.executeRawBound(statement) : runBoundAffected({ adapter: target.adapter, text: statement.text, values: statement.values }) : target.adapter.executeUnsafe(statement)
|
|
13186
13887
|
});
|
|
13187
13888
|
decodeRows2?.(result.rows);
|
|
13188
13889
|
return result;
|
|
@@ -13253,7 +13954,7 @@ function buildClient(params) {
|
|
|
13253
13954
|
}
|
|
13254
13955
|
for (const operation of operations) {
|
|
13255
13956
|
if (!isQueryPromise(operation)) {
|
|
13256
|
-
claimError = new
|
|
13957
|
+
claimError = new VibeError30({
|
|
13257
13958
|
code: "VIBE_VALIDATION",
|
|
13258
13959
|
message: "$transaction([...]) takes query promises from model delegate calls \u2014 a foreign promise is already running and cannot join the transaction"
|
|
13259
13960
|
});
|
|
@@ -13318,7 +14019,7 @@ function buildClient(params) {
|
|
|
13318
14019
|
$models: models,
|
|
13319
14020
|
$on: (event, listener) => {
|
|
13320
14021
|
if (event !== "query") {
|
|
13321
|
-
throw new
|
|
14022
|
+
throw new VibeError30({
|
|
13322
14023
|
code: "VIBE_VALIDATION",
|
|
13323
14024
|
message: `unknown client event "${String(event)}" \u2014 supported: "query"`,
|
|
13324
14025
|
meta: { event }
|
|
@@ -13333,7 +14034,7 @@ function buildClient(params) {
|
|
|
13333
14034
|
$querySql: async ({ query, decoder }) => {
|
|
13334
14035
|
assertEngineActive({ engine });
|
|
13335
14036
|
if (decoder === null || typeof decoder !== "object" || typeof decoder.decode !== "function")
|
|
13336
|
-
throw new
|
|
14037
|
+
throw new VibeError30({ code: "VIBE_VALIDATION", message: "$querySql requires an explicit result decoder" });
|
|
13337
14038
|
const statement = compileSql({ dialect: engine.dialect, query });
|
|
13338
14039
|
let output = [];
|
|
13339
14040
|
await runRaw("$querySql", statement, (rows) => {
|
|
@@ -13355,6 +14056,8 @@ function buildClient(params) {
|
|
|
13355
14056
|
$tryAdvisoryLock: (options) => runAdvisoryLock({ method: "$tryAdvisoryLock", options }),
|
|
13356
14057
|
$connect: async () => {
|
|
13357
14058
|
assertEngineActive({ engine, requireContext: false });
|
|
14059
|
+
if (engine.borrowedModule === true)
|
|
14060
|
+
throw new VibeError30({ code: "VIBE_VALIDATION", message: "A package module borrows its adapter; the application owns connection lifecycle", meta: { reason: "module_borrowed_lifecycle", method: "$connect" } });
|
|
13358
14061
|
if (engine.inTransaction === true)
|
|
13359
14062
|
throw transactionLifecycleRefusal({ method: "$connect" });
|
|
13360
14063
|
if (engine.rlsSchema !== undefined) {
|
|
@@ -13375,8 +14078,11 @@ function buildClient(params) {
|
|
|
13375
14078
|
},
|
|
13376
14079
|
$disconnect: async () => {
|
|
13377
14080
|
assertEngineActive({ engine, requireContext: false });
|
|
14081
|
+
if (engine.borrowedModule === true)
|
|
14082
|
+
throw new VibeError30({ code: "VIBE_VALIDATION", message: "A package module borrows its adapter; the application owns connection lifecycle", meta: { reason: "module_borrowed_lifecycle", method: "$disconnect" } });
|
|
13378
14083
|
if (engine.inTransaction === true)
|
|
13379
14084
|
throw transactionLifecycleRefusal({ method: "$disconnect" });
|
|
14085
|
+
await engine.diagnostics?.flush();
|
|
13380
14086
|
await engine.adapter.disconnect();
|
|
13381
14087
|
await engine.telemetry?.flush();
|
|
13382
14088
|
}
|
|
@@ -13409,7 +14115,7 @@ function buildClient(params) {
|
|
|
13409
14115
|
});
|
|
13410
14116
|
} else {
|
|
13411
14117
|
const refuseLegacy = () => {
|
|
13412
|
-
throw new
|
|
14118
|
+
throw new VibeError30({ code: "VIBE_CONFIG", message: "Native row-level security cannot be combined with application policy or tenant scoping" });
|
|
13413
14119
|
};
|
|
13414
14120
|
Object.defineProperties(client, {
|
|
13415
14121
|
$withContext: { enumerable: true, value: (context) => {
|
|
@@ -13435,6 +14141,25 @@ function buildClient(params) {
|
|
|
13435
14141
|
Object.freeze(models);
|
|
13436
14142
|
Object.freeze(client);
|
|
13437
14143
|
}
|
|
14144
|
+
registerModuleBinder({ client, bind: ({ schema, manifest }) => {
|
|
14145
|
+
assertEngineActive({ engine, requireContext: false });
|
|
14146
|
+
if (engine.rlsSchema !== undefined || engine.meta.models.some((model) => model.policy !== undefined))
|
|
14147
|
+
throw new VibeError30({ code: "VIBE_CONFIG", message: `Module ${manifest.id} cannot attach models to a policy or native-RLS context`, meta: { reason: "module_restricted_context", moduleId: manifest.id } });
|
|
14148
|
+
if (manifest.dialect !== engine.dialect.name)
|
|
14149
|
+
throw new VibeError30({ code: "VIBE_UNSUPPORTED_CAPABILITY", message: `Module ${manifest.id} requires ${manifest.dialect}, received ${engine.dialect.name}`, meta: { reason: "module_dialect", moduleId: manifest.id } });
|
|
14150
|
+
const previous = engine.moduleManifests.get(manifest.id);
|
|
14151
|
+
if (previous !== undefined && (previous.schemaChecksum !== manifest.schemaChecksum || previous.revision !== manifest.revision || previous.contract !== manifest.contract))
|
|
14152
|
+
throw new VibeError30({ code: "VIBE_CONFIG", message: `Module ${manifest.id} was already bound with a different manifest; use compatible package artifacts`, meta: { reason: "module_manifest_conflict", moduleId: manifest.id, revision: manifest.revision } });
|
|
14153
|
+
let meta = moduleMetadata.get(schema);
|
|
14154
|
+
if (meta === undefined) {
|
|
14155
|
+
meta = buildRuntimeMeta({ schema });
|
|
14156
|
+
registerRestrictedSqlSchema({ meta, schema });
|
|
14157
|
+
moduleMetadata.set(schema, meta);
|
|
14158
|
+
}
|
|
14159
|
+
engine.moduleManifests.set(manifest.id, manifest);
|
|
14160
|
+
const { validate: _validate, ...options } = engine.options;
|
|
14161
|
+
return buildClient({ engine: { ...engine, meta, options, borrowedModule: true, extensionMounts: [], connectHooks: [], renderCache: new StatementRenderCache } });
|
|
14162
|
+
} });
|
|
13438
14163
|
return client;
|
|
13439
14164
|
}
|
|
13440
14165
|
function makeMountContext(params) {
|
|
@@ -13479,7 +14204,7 @@ function makeMountContext(params) {
|
|
|
13479
14204
|
compileWhere: ({ where, model: modelName, qualify }) => {
|
|
13480
14205
|
const target = modelName === undefined ? model : getModelMeta({ meta: engine.meta, model: modelName });
|
|
13481
14206
|
if (target === null) {
|
|
13482
|
-
throw new
|
|
14207
|
+
throw new VibeError30({
|
|
13483
14208
|
code: "VIBE_INTERNAL",
|
|
13484
14209
|
message: `extension "${extension}" compiled a where with no model in scope`,
|
|
13485
14210
|
meta: { extension }
|
|
@@ -13508,24 +14233,24 @@ function createMountSurface(params) {
|
|
|
13508
14233
|
for (const key of path) {
|
|
13509
14234
|
receiver = current;
|
|
13510
14235
|
if (current === null || typeof current !== "object" && typeof current !== "function")
|
|
13511
|
-
throw new
|
|
14236
|
+
throw new VibeError30({ code: "VIBE_CONFIG", message: "Extension mount changed its method shape" });
|
|
13512
14237
|
current = Reflect.get(current, key);
|
|
13513
14238
|
}
|
|
13514
14239
|
if (typeof current !== "function")
|
|
13515
|
-
throw new
|
|
14240
|
+
throw new VibeError30({ code: "VIBE_CONFIG", message: "Extension mount changed its method shape" });
|
|
13516
14241
|
return Reflect.apply(current, receiver, args);
|
|
13517
14242
|
} });
|
|
13518
14243
|
}
|
|
13519
14244
|
if (value === null || typeof value !== "object")
|
|
13520
14245
|
return value;
|
|
13521
14246
|
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
13522
|
-
throw new
|
|
14247
|
+
throw new VibeError30({ code: "VIBE_CONFIG", message: "Native RLS extension mounts must expose plain objects and methods" });
|
|
13523
14248
|
}
|
|
13524
14249
|
const result = {};
|
|
13525
14250
|
for (const key of Reflect.ownKeys(value)) {
|
|
13526
14251
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
13527
14252
|
if (!("value" in descriptor))
|
|
13528
|
-
throw new
|
|
14253
|
+
throw new VibeError30({ code: "VIBE_CONFIG", message: "Native RLS extension mount accessors are unsupported" });
|
|
13529
14254
|
Object.defineProperty(result, key, { value: wrap(descriptor.value, [...path, key]), enumerable: descriptor.enumerable });
|
|
13530
14255
|
}
|
|
13531
14256
|
return Object.freeze(result);
|
|
@@ -13542,7 +14267,7 @@ function attachExtensionMounts(params) {
|
|
|
13542
14267
|
const model = getModelMeta({ meta: engine.meta, model: mount.model ?? "" });
|
|
13543
14268
|
const delegate = models[model.name];
|
|
13544
14269
|
if (delegate === undefined || mount.property in delegate) {
|
|
13545
|
-
throw new
|
|
14270
|
+
throw new VibeError30({
|
|
13546
14271
|
code: "VIBE_CONFIG",
|
|
13547
14272
|
message: `extension "${mount.extension}" cannot mount "${mount.property}" on ${model.name} \u2014 the name is taken`,
|
|
13548
14273
|
meta: { extension: mount.extension, model: model.name, property: mount.property }
|
|
@@ -13553,7 +14278,7 @@ function attachExtensionMounts(params) {
|
|
|
13553
14278
|
continue;
|
|
13554
14279
|
}
|
|
13555
14280
|
if (!mount.property.startsWith("$") || mount.property in client) {
|
|
13556
|
-
throw new
|
|
14281
|
+
throw new VibeError30({
|
|
13557
14282
|
code: "VIBE_CONFIG",
|
|
13558
14283
|
message: `extension "${mount.extension}" cannot mount client property "${mount.property}" \u2014 root mounts are $-prefixed and must not collide`,
|
|
13559
14284
|
meta: { extension: mount.extension, property: mount.property }
|
|
@@ -13567,6 +14292,15 @@ function createClient(params) {
|
|
|
13567
14292
|
const { schema, adapter, options, extensions, computed } = params;
|
|
13568
14293
|
const dialectName = adapter.dialect;
|
|
13569
14294
|
const telemetry = options?.telemetry === undefined ? undefined : createTelemetryRecorder({ options: options.telemetry });
|
|
14295
|
+
resolveRelationConcurrency({ concurrency: options?.relationLoading?.concurrency });
|
|
14296
|
+
const diagnosticsConfig = resolveStatementDiagnostics({
|
|
14297
|
+
options: options?.diagnostics,
|
|
14298
|
+
env: process.env[STATEMENT_DIAGNOSTICS_DEFAULTS.envVariable]
|
|
14299
|
+
});
|
|
14300
|
+
const diagnostics = diagnosticsConfig === undefined ? undefined : createStatementDiagnostics({ config: diagnosticsConfig, adapter, dialect: getSqlDialect5({ dialect: dialectName }) });
|
|
14301
|
+
if (diagnosticsConfig !== undefined && diagnosticsConfig.explain === "analyze" && diagnosticsConfig.slowerThanMs < STATEMENT_DIAGNOSTICS_DEFAULTS.analyzeWarnBelowMs) {
|
|
14302
|
+
console.warn(`[vibeorm] diagnostics: explain "analyze" with slowerThanMs ${diagnosticsConfig.slowerThanMs} re-executes every read statement under EXPLAIN ANALYZE \u2014 set a threshold, and never use this against production`);
|
|
14303
|
+
}
|
|
13570
14304
|
if (schema.rls !== undefined) {
|
|
13571
14305
|
const dialect = getSqlDialect5({ dialect: dialectName });
|
|
13572
14306
|
if (!dialect.capabilities.nativeRowLevelSecurity)
|
|
@@ -13579,9 +14313,9 @@ function createClient(params) {
|
|
|
13579
14313
|
const role = options?.nativeRls?.runtimeRole;
|
|
13580
14314
|
if (adapter.provider === "pglite") {
|
|
13581
14315
|
if (typeof role !== "string" || role.length === 0 || role.includes("\x00"))
|
|
13582
|
-
throw new
|
|
14316
|
+
throw new VibeError30({ 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" });
|
|
13583
14317
|
} else if (role !== undefined) {
|
|
13584
|
-
throw new
|
|
14318
|
+
throw new VibeError30({ code: "VIBE_CONFIG", message: "Server native row-level security must use the runtime login role, not a runtimeRole switch" });
|
|
13585
14319
|
}
|
|
13586
14320
|
}
|
|
13587
14321
|
if (extensions?.manifest !== undefined) {
|
|
@@ -13591,7 +14325,7 @@ function createClient(params) {
|
|
|
13591
14325
|
assertComputedManifest({ manifest: computed.manifest, specs: computed.specs });
|
|
13592
14326
|
}
|
|
13593
14327
|
if (computed === undefined && options !== undefined && options["computed"] !== undefined) {
|
|
13594
|
-
throw new
|
|
14328
|
+
throw new VibeError30({
|
|
13595
14329
|
code: "VIBE_CONFIG",
|
|
13596
14330
|
message: "this client was generated without computed fields \u2014 declare them in vibeorm.config.ts and re-run vibeorm generate",
|
|
13597
14331
|
meta: {}
|
|
@@ -13605,9 +14339,11 @@ function createClient(params) {
|
|
|
13605
14339
|
...computed?.specs === undefined ? {} : { computed: computed.specs }
|
|
13606
14340
|
});
|
|
13607
14341
|
const meta = operatorMap === null ? baseMeta : { ...baseMeta, extensionOperators: operatorMap };
|
|
14342
|
+
registerRestrictedSqlSchema({ meta, schema });
|
|
13608
14343
|
return buildClient({
|
|
13609
14344
|
engine: {
|
|
13610
14345
|
rootIdentity: Object.freeze({}),
|
|
14346
|
+
moduleManifests: new Map,
|
|
13611
14347
|
...schema.rls === undefined ? {} : { rlsSchema: schema },
|
|
13612
14348
|
meta,
|
|
13613
14349
|
adapter,
|
|
@@ -13616,15 +14352,66 @@ function createClient(params) {
|
|
|
13616
14352
|
listeners: new Set,
|
|
13617
14353
|
renderCache: new StatementRenderCache,
|
|
13618
14354
|
...telemetry === undefined ? {} : { telemetry },
|
|
14355
|
+
...diagnostics === undefined ? {} : { diagnostics },
|
|
13619
14356
|
...extensions?.mounts === undefined ? {} : { extensionMounts: extensions.mounts },
|
|
13620
14357
|
...connectHooks.length === 0 ? {} : { connectHooks }
|
|
13621
14358
|
}
|
|
13622
14359
|
});
|
|
13623
14360
|
}
|
|
14361
|
+
|
|
14362
|
+
// packages/runtime/src/database-module.ts
|
|
14363
|
+
function canonicalValue(params) {
|
|
14364
|
+
const { value } = params;
|
|
14365
|
+
if (Array.isArray(value))
|
|
14366
|
+
return value.map((entry) => canonicalValue({ value: entry }));
|
|
14367
|
+
if (value === null || typeof value !== "object")
|
|
14368
|
+
return value;
|
|
14369
|
+
return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, entry]) => [key, canonicalValue({ value: entry })]));
|
|
14370
|
+
}
|
|
14371
|
+
function freezeData(params) {
|
|
14372
|
+
if (params.value === null || typeof params.value !== "object" || Object.isFrozen(params.value))
|
|
14373
|
+
return;
|
|
14374
|
+
for (const value of Object.values(params.value))
|
|
14375
|
+
freezeData({ value });
|
|
14376
|
+
Object.freeze(params.value);
|
|
14377
|
+
}
|
|
14378
|
+
function defineDatabaseModule(params) {
|
|
14379
|
+
for (const key of ["id", "contract", "revision"]) {
|
|
14380
|
+
if (typeof params[key] !== "string" || params[key].trim().length === 0 || params[key].includes("\x00"))
|
|
14381
|
+
throw new VibeError31({ code: "VIBE_CONFIG", message: `Database module ${key} must be a non-empty identifier`, meta: { reason: "module_manifest", key } });
|
|
14382
|
+
}
|
|
14383
|
+
assertValidSchemaIR3({ schema: params.schema });
|
|
14384
|
+
if (params.schema.rls !== undefined || params.schema.extensions.length > 0 || params.schema.models.some((model) => model.policy !== undefined))
|
|
14385
|
+
throw new VibeError31({
|
|
14386
|
+
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
14387
|
+
message: `Module ${params.id} cannot declare extensions, policy or native RLS in this module protocol`,
|
|
14388
|
+
meta: { reason: "module_schema_feature", moduleId: params.id }
|
|
14389
|
+
});
|
|
14390
|
+
const serialized = JSON.stringify(canonicalValue({ value: params.schema }));
|
|
14391
|
+
const schema = JSON.parse(serialized);
|
|
14392
|
+
freezeData({ value: schema });
|
|
14393
|
+
const manifest = Object.freeze({
|
|
14394
|
+
id: params.id,
|
|
14395
|
+
contract: params.contract,
|
|
14396
|
+
revision: params.revision,
|
|
14397
|
+
protocol: 1,
|
|
14398
|
+
dialect: providerToDialect({ provider: schema.datasource.provider }),
|
|
14399
|
+
schemaChecksum: createHash("sha256").update(serialized).digest("hex")
|
|
14400
|
+
});
|
|
14401
|
+
if (params.manifest !== undefined && Object.keys(manifest).some((key) => params.manifest[key] !== manifest[key]))
|
|
14402
|
+
throw new VibeError31({
|
|
14403
|
+
code: "VIBE_STALE_CLIENT",
|
|
14404
|
+
message: `Module ${params.id} has incompatible or modified generated metadata; regenerate with a compatible runtime`,
|
|
14405
|
+
meta: { reason: "module_artifact", moduleId: params.id, revision: params.revision, protocol: params.manifest.protocol }
|
|
14406
|
+
});
|
|
14407
|
+
const artifact = Object.freeze({ schema, manifest });
|
|
14408
|
+
const bind = ({ db }) => bindModuleContext({ db, artifact });
|
|
14409
|
+
return Object.freeze({ ...artifact, bind, create: ({ adapter }) => bind({ db: createClient({ schema, adapter }) }) });
|
|
14410
|
+
}
|
|
13624
14411
|
// packages/runtime/src/keyset-iterator.ts
|
|
13625
|
-
import { VibeError as
|
|
14412
|
+
import { VibeError as VibeError32 } from "@vibeorm/schema";
|
|
13626
14413
|
function validation(params) {
|
|
13627
|
-
return new
|
|
14414
|
+
return new VibeError32({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
13628
14415
|
}
|
|
13629
14416
|
function projectionFor(params) {
|
|
13630
14417
|
const { plan: plan2, select, omit } = params;
|
|
@@ -13701,7 +14488,7 @@ function iterateKeyset(options) {
|
|
|
13701
14488
|
});
|
|
13702
14489
|
if (advanced === false) {
|
|
13703
14490
|
stop();
|
|
13704
|
-
throw new
|
|
14491
|
+
throw new VibeError32({
|
|
13705
14492
|
code: "VIBE_DATA_INVALID",
|
|
13706
14493
|
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.",
|
|
13707
14494
|
meta: { model: options.model, order: plan2.signature, reason: "keyset-position-not-advancing" }
|
|
@@ -13774,7 +14561,7 @@ function iterateKeyset(options) {
|
|
|
13774
14561
|
return iterator;
|
|
13775
14562
|
}
|
|
13776
14563
|
// packages/runtime/src/telemetry/collector.ts
|
|
13777
|
-
import { VibeError as
|
|
14564
|
+
import { VibeError as VibeError33 } from "@vibeorm/schema";
|
|
13778
14565
|
function createTelemetryCollector(params) {
|
|
13779
14566
|
const throwOn = new Set(params?.throwOn ?? []);
|
|
13780
14567
|
const operations = [];
|
|
@@ -13784,7 +14571,7 @@ function createTelemetryCollector(params) {
|
|
|
13784
14571
|
let shutdowns = 0;
|
|
13785
14572
|
const hostile = (phase) => {
|
|
13786
14573
|
if (throwOn.has(phase)) {
|
|
13787
|
-
throw new
|
|
14574
|
+
throw new VibeError33({
|
|
13788
14575
|
code: "VIBE_INTERNAL",
|
|
13789
14576
|
message: `telemetry collector: deliberate ${phase} failure`,
|
|
13790
14577
|
meta: { reason: "telemetry-collector-deliberate-failure", phase }
|
|
@@ -13840,7 +14627,7 @@ function createTelemetryCollector(params) {
|
|
|
13840
14627
|
operationFor: (lookup) => {
|
|
13841
14628
|
const matches = operations.filter((event) => event.method === lookup.method && (lookup.model === undefined || event.model === lookup.model));
|
|
13842
14629
|
if (matches.length > 1) {
|
|
13843
|
-
throw new
|
|
14630
|
+
throw new VibeError33({
|
|
13844
14631
|
code: "VIBE_VALIDATION",
|
|
13845
14632
|
message: `telemetry collector: ${matches.length} operations match ${lookup.model ?? "*"}.${lookup.method}`,
|
|
13846
14633
|
meta: {
|
|
@@ -13860,10 +14647,12 @@ export {
|
|
|
13860
14647
|
updateWithNested,
|
|
13861
14648
|
toSqlExecutor,
|
|
13862
14649
|
toClientName,
|
|
14650
|
+
summarizePlan,
|
|
13863
14651
|
sqlProjection,
|
|
13864
14652
|
sql2 as sql,
|
|
13865
14653
|
rewriteScopedCall,
|
|
13866
14654
|
resolveTelemetryConfig,
|
|
14655
|
+
resolveStatementDiagnostics,
|
|
13867
14656
|
resolveScopedModels,
|
|
13868
14657
|
resolveResultShape,
|
|
13869
14658
|
resolveRelationSelection,
|
|
@@ -13874,12 +14663,14 @@ export {
|
|
|
13874
14663
|
queryFingerprint,
|
|
13875
14664
|
previewOperation,
|
|
13876
14665
|
parsePostgresPlan,
|
|
14666
|
+
parseDiagnosticsEnv,
|
|
13877
14667
|
parameterTypeTag,
|
|
13878
14668
|
normalizeSql,
|
|
13879
14669
|
lateralColumnAlias,
|
|
13880
14670
|
keysetRowValues,
|
|
13881
14671
|
keysetRowIsAfter,
|
|
13882
14672
|
iterateKeyset,
|
|
14673
|
+
isExecutableRead,
|
|
13883
14674
|
isDbNow,
|
|
13884
14675
|
inspectNativeRlsTransaction,
|
|
13885
14676
|
inspectNativeRls,
|
|
@@ -13893,12 +14684,15 @@ export {
|
|
|
13893
14684
|
generateNanoid,
|
|
13894
14685
|
generateDefaultValue,
|
|
13895
14686
|
generateCuid,
|
|
14687
|
+
formatStatementDiagnostic,
|
|
13896
14688
|
formatNativeRlsReport,
|
|
13897
14689
|
explainSql,
|
|
13898
14690
|
explainOperation,
|
|
14691
|
+
explainOnSession,
|
|
13899
14692
|
encodeValue,
|
|
13900
14693
|
encodeKeysetToken,
|
|
13901
14694
|
delegateMethodAccess,
|
|
14695
|
+
defineDatabaseModule,
|
|
13902
14696
|
decodeValue,
|
|
13903
14697
|
decodeRows,
|
|
13904
14698
|
decodeRow,
|
|
@@ -13911,6 +14705,7 @@ export {
|
|
|
13911
14705
|
createView,
|
|
13912
14706
|
createTelemetryRecorder,
|
|
13913
14707
|
createTelemetryCollector,
|
|
14708
|
+
createStatementDiagnostics,
|
|
13914
14709
|
createSqlModel,
|
|
13915
14710
|
createScopeRewriter,
|
|
13916
14711
|
createClient,
|
|
@@ -13942,6 +14737,7 @@ export {
|
|
|
13942
14737
|
WRITE_DELEGATE_METHODS,
|
|
13943
14738
|
ViewResult,
|
|
13944
14739
|
TELEMETRY_DEFAULTS,
|
|
14740
|
+
STATEMENT_DIAGNOSTICS_DEFAULTS,
|
|
13945
14741
|
SQLITE_CODECS,
|
|
13946
14742
|
SKIP,
|
|
13947
14743
|
READ_DELEGATE_METHODS,
|
|
@@ -13958,4 +14754,4 @@ export {
|
|
|
13958
14754
|
CLIENT_MEMBER_ACCESS
|
|
13959
14755
|
};
|
|
13960
14756
|
|
|
13961
|
-
//# debugId=
|
|
14757
|
+
//# debugId=22841846B558EACA64756E2164756E21
|