@vibeorm/runtime 2.0.1 → 2.1.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/README.md +2 -0
- package/dist/adapter-kit/index.d.ts +20 -0
- package/dist/adapter-kit/index.d.ts.map +1 -0
- package/dist/adapter-kit/index.js +247 -0
- package/dist/adapter-kit/index.js.map +12 -0
- package/dist/adapter-kit/savepoint-gate.d.ts +86 -0
- package/dist/adapter-kit/savepoint-gate.d.ts.map +1 -0
- package/dist/adapter-kit/savepoints.d.ts +22 -0
- package/dist/adapter-kit/savepoints.d.ts.map +1 -0
- package/dist/adapter-kit/transaction-budget.d.ts +143 -0
- package/dist/adapter-kit/transaction-budget.d.ts.map +1 -0
- package/dist/advisory-lock.d.ts +68 -0
- package/dist/advisory-lock.d.ts.map +1 -0
- package/dist/client.d.ts +75 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/codecs.d.ts +3 -3
- package/dist/extensions.d.ts +7 -0
- package/dist/extensions.d.ts.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1190 -434
- package/dist/index.js.map +16 -11
- package/dist/policy.d.ts.map +1 -1
- package/dist/query-builder.d.ts.map +1 -1
- package/dist/read-only.d.ts +114 -0
- package/dist/read-only.d.ts.map +1 -0
- package/dist/render-cache.d.ts +16 -1
- package/dist/render-cache.d.ts.map +1 -1
- package/dist/scoped.d.ts.map +1 -1
- package/dist/sql-access.d.ts +8 -0
- package/dist/sql-access.d.ts.map +1 -0
- package/dist/sql.d.ts +29 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/views.d.ts +3 -3
- package/dist/views.d.ts.map +1 -1
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,54 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// packages/runtime/src/index.ts
|
|
3
|
+
import { sql as sql2, decode as decode2 } from "@vibeorm/sql";
|
|
4
|
+
|
|
5
|
+
// packages/runtime/src/sql.ts
|
|
6
|
+
import { VibeError, isEnumType } from "@vibeorm/schema";
|
|
7
|
+
import { decode, sql } from "@vibeorm/sql";
|
|
8
|
+
function sqlProjection(params) {
|
|
9
|
+
const entries = Object.entries(params.fields);
|
|
10
|
+
if (entries.length === 0)
|
|
11
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "A SQL projection needs at least one field" });
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
sql: sql.join({ items: entries.map(([name, column]) => sql`${column.sql} AS ${sql.ident({ parts: [name] })}`) }),
|
|
14
|
+
decoder: decode.object({ fields: Object.fromEntries(entries.map(([name, column]) => [name, column.decoder])) })
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
var SCALAR_DECODERS = Object.freeze({
|
|
18
|
+
String: decode.string,
|
|
19
|
+
Boolean: decode.boolean,
|
|
20
|
+
Int: decode.int,
|
|
21
|
+
Float: decode.float,
|
|
22
|
+
BigInt: decode.bigInt,
|
|
23
|
+
Decimal: decode.decimal,
|
|
24
|
+
DateTime: decode.dateTime,
|
|
25
|
+
Json: decode.json,
|
|
26
|
+
Bytes: decode.bytes
|
|
27
|
+
});
|
|
28
|
+
function createSqlModel(params) {
|
|
29
|
+
const model = params.schema.models.find((entry) => entry.name === params.model);
|
|
30
|
+
if (model === undefined)
|
|
31
|
+
throw new VibeError({ code: "VIBE_VALIDATION", message: "Unknown SQL model" });
|
|
32
|
+
const tableName = model.dbName ?? model.name;
|
|
33
|
+
const table = sql.ident({ parts: [tableName] });
|
|
34
|
+
const fields = Object.fromEntries(model.fields.filter((field) => field.isUnsupported !== true).map((field) => {
|
|
35
|
+
let decoder;
|
|
36
|
+
if (isEnumType(field.type)) {
|
|
37
|
+
const name = field.type.enum;
|
|
38
|
+
const declared = params.schema.enums.find((entry) => entry.name === name);
|
|
39
|
+
if (declared === undefined)
|
|
40
|
+
throw new VibeError({ code: "VIBE_SCHEMA_INVALID", message: "Unknown enum in SQL model" });
|
|
41
|
+
decoder = decode.enum({ values: declared.values });
|
|
42
|
+
} else
|
|
43
|
+
decoder = SCALAR_DECODERS[field.type];
|
|
44
|
+
if (field.isList)
|
|
45
|
+
decoder = decode.array({ decoder });
|
|
46
|
+
if (field.isOptional)
|
|
47
|
+
decoder = decode.nullable({ decoder });
|
|
48
|
+
return [field.name, Object.freeze({ sql: sql.ident({ parts: [params.alias ?? tableName, field.dbName ?? field.name] }), decoder })];
|
|
49
|
+
}));
|
|
50
|
+
return Object.freeze({ table: params.alias === undefined ? table : sql`${table} AS ${sql.ident({ parts: [params.alias] })}`, fields: Object.freeze(fields) });
|
|
51
|
+
}
|
|
2
52
|
// packages/runtime/src/adapter.ts
|
|
3
53
|
function toSqlExecutor(params) {
|
|
4
54
|
const { adapter } = params;
|
|
@@ -11,11 +61,11 @@ function toSqlExecutor(params) {
|
|
|
11
61
|
};
|
|
12
62
|
}
|
|
13
63
|
// packages/runtime/src/db-now.ts
|
|
14
|
-
import { VibeError } from "@vibeorm/schema";
|
|
64
|
+
import { VibeError as VibeError2 } from "@vibeorm/schema";
|
|
15
65
|
function dbNow(options) {
|
|
16
66
|
const offset = options?.plusSeconds ?? 0;
|
|
17
67
|
if (typeof offset !== "number" || !Number.isFinite(offset)) {
|
|
18
|
-
throw new
|
|
68
|
+
throw new VibeError2({
|
|
19
69
|
code: "VIBE_VALIDATION",
|
|
20
70
|
message: `dbNow plusSeconds must be a finite number of seconds (fractional is fine), got ${String(offset)}`,
|
|
21
71
|
meta: { plusSeconds: offset }
|
|
@@ -33,8 +83,116 @@ function isDbNow(value) {
|
|
|
33
83
|
const candidate = value;
|
|
34
84
|
return candidate.__vibeDbNow === true && typeof candidate.offsetSeconds === "number" && Number.isFinite(candidate.offsetSeconds);
|
|
35
85
|
}
|
|
86
|
+
// packages/runtime/src/advisory-lock.ts
|
|
87
|
+
import { VibeError as VibeError3, unsupportedCapability } from "@vibeorm/schema";
|
|
88
|
+
import { ADVISORY_KEY_BOUNDS } from "@vibeorm/sql";
|
|
89
|
+
function validationError(params) {
|
|
90
|
+
return new VibeError3({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
91
|
+
}
|
|
92
|
+
function readInt32(params) {
|
|
93
|
+
const { value, member, method } = params;
|
|
94
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < ADVISORY_KEY_BOUNDS.pair.min || value > ADVISORY_KEY_BOUNDS.pair.max) {
|
|
95
|
+
throw validationError({
|
|
96
|
+
message: `${method}({ ${member} }) must be a 32-bit integer ` + `(${ADVISORY_KEY_BOUNDS.pair.min} \u2026 ${ADVISORY_KEY_BOUNDS.pair.max}), got ${String(value)}`,
|
|
97
|
+
meta: { method, member, value: typeof value === "number" ? value : String(value) }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
function readInt64(params) {
|
|
103
|
+
const { value, method } = params;
|
|
104
|
+
if (typeof value === "bigint") {
|
|
105
|
+
if (value < ADVISORY_KEY_BOUNDS.single.min || value > ADVISORY_KEY_BOUNDS.single.max) {
|
|
106
|
+
throw validationError({
|
|
107
|
+
message: `${method}({ key }) must fit a 64-bit integer, got ${value.toString()}`,
|
|
108
|
+
meta: { method, member: "key", value: value.toString() }
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
if (typeof value === "number" && Number.isSafeInteger(value))
|
|
114
|
+
return BigInt(value);
|
|
115
|
+
throw validationError({
|
|
116
|
+
message: `${method}({ key }) must be a bigint or a safe integer, got ${String(value)} \u2014 ` + `a number past 2^53 cannot name one specific lock key, so pass a bigint (e.g. 9007199254740993n)`,
|
|
117
|
+
meta: { method, member: "key", value: String(value) }
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function compileAdvisoryLock(params) {
|
|
121
|
+
const { dialect, method, inTransaction, options } = params;
|
|
122
|
+
if (!dialect.capabilities.advisoryLocks) {
|
|
123
|
+
throw unsupportedCapability({
|
|
124
|
+
dialect: dialect.name,
|
|
125
|
+
feature: `${method}()`,
|
|
126
|
+
hint: dialect.name === "mysql" ? "mysql's GET_LOCK is SESSION-scoped \u2014 it survives a rollback, so it cannot be released with the transaction. Serialize on a real row instead (a lock table with one row per resource)" : "serialize on a real row instead (a lock table with one row per resource)"
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (!inTransaction) {
|
|
130
|
+
throw validationError({
|
|
131
|
+
message: `${method}() requires a transaction \u2014 call it on the client $transaction hands its callback. ` + `Outside one, the lock is released before the next statement runs`,
|
|
132
|
+
meta: { method }
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
136
|
+
throw validationError({
|
|
137
|
+
message: `${method}() takes { key } or { classId, objectId }, plus an optional mode, got ${String(options)}`,
|
|
138
|
+
meta: { method }
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const request = options;
|
|
142
|
+
const hasSingle = "key" in request;
|
|
143
|
+
const hasPair = "classId" in request || "objectId" in request;
|
|
144
|
+
if (hasSingle && hasPair) {
|
|
145
|
+
throw validationError({
|
|
146
|
+
message: `${method}() takes ONE key form: { key } or { classId, objectId }. They are different lock spaces, ` + `so naming both cannot be resolved`,
|
|
147
|
+
meta: { method }
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
for (const member of Object.keys(request)) {
|
|
151
|
+
if (member === "mode" || member === "key" || member === "classId" || member === "objectId")
|
|
152
|
+
continue;
|
|
153
|
+
throw validationError({
|
|
154
|
+
message: `${method}() has no "${member}" option \u2014 it takes { key } or { classId, objectId } and an optional mode`,
|
|
155
|
+
meta: { method, member }
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
const mode = readMode({ value: request.mode, method });
|
|
159
|
+
if (hasSingle) {
|
|
160
|
+
const key2 = { key: readInt64({ value: request.key, method }) };
|
|
161
|
+
return statementFor({ method, mode, key: key2 });
|
|
162
|
+
}
|
|
163
|
+
if (!("classId" in request) || !("objectId" in request)) {
|
|
164
|
+
throw validationError({
|
|
165
|
+
message: `${method}({ classId, objectId }) needs BOTH halves of the two-key form`,
|
|
166
|
+
meta: { method }
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const key = {
|
|
170
|
+
classId: readInt32({ value: request.classId, member: "classId", method }),
|
|
171
|
+
objectId: readInt32({ value: request.objectId, member: "objectId", method })
|
|
172
|
+
};
|
|
173
|
+
return statementFor({ method, mode, key });
|
|
174
|
+
}
|
|
175
|
+
function readMode(params) {
|
|
176
|
+
const { value, method } = params;
|
|
177
|
+
if (value === undefined || value === "exclusive")
|
|
178
|
+
return "exclusive";
|
|
179
|
+
if (value === "shared")
|
|
180
|
+
return "shared";
|
|
181
|
+
throw validationError({
|
|
182
|
+
message: `${method}({ mode }) must be "exclusive" or "shared", got ${String(value)}`,
|
|
183
|
+
meta: { method, mode: String(value) }
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function statementFor(params) {
|
|
187
|
+
return {
|
|
188
|
+
kind: "advisoryLock",
|
|
189
|
+
mode: params.mode,
|
|
190
|
+
wait: params.method === "$tryAdvisoryLock" ? "nowait" : "wait",
|
|
191
|
+
key: params.key
|
|
192
|
+
};
|
|
193
|
+
}
|
|
36
194
|
// packages/runtime/src/model-meta.ts
|
|
37
|
-
import { VibeError as
|
|
195
|
+
import { VibeError as VibeError4, enumStorageOf, isEnumType as isEnumType2, validateComputedConfig } from "@vibeorm/schema";
|
|
38
196
|
import { enumMembershipCodec } from "@vibeorm/sql";
|
|
39
197
|
function toClientName(params) {
|
|
40
198
|
const { name } = params;
|
|
@@ -131,7 +289,7 @@ function defaultOriginOf(params) {
|
|
|
131
289
|
}
|
|
132
290
|
function buildFieldMeta(params) {
|
|
133
291
|
const { model, field, enums } = params;
|
|
134
|
-
const enumName =
|
|
292
|
+
const enumName = isEnumType2(field.type) ? field.type.enum : null;
|
|
135
293
|
const enumDef = enumName === null ? undefined : enums.get(enumName);
|
|
136
294
|
const enumStorage = enumName === null ? null : enumStorageOf({ enumDef: enumDef ?? {} });
|
|
137
295
|
return {
|
|
@@ -163,7 +321,7 @@ function buildModelMeta(params) {
|
|
|
163
321
|
for (const name of model.primaryKey.fields) {
|
|
164
322
|
const field = fieldByName.get(name);
|
|
165
323
|
if (field === undefined) {
|
|
166
|
-
throw new
|
|
324
|
+
throw new VibeError4({
|
|
167
325
|
code: "VIBE_SCHEMA_INVALID",
|
|
168
326
|
message: `model "${model.name}" declares composite primary-key field "${name}", which does not exist`,
|
|
169
327
|
meta: { model: model.name, field: name }
|
|
@@ -269,7 +427,7 @@ function getModelMeta(params) {
|
|
|
269
427
|
const { meta, model } = params;
|
|
270
428
|
const found = meta.modelByName.get(model);
|
|
271
429
|
if (found === undefined) {
|
|
272
|
-
throw new
|
|
430
|
+
throw new VibeError4({
|
|
273
431
|
code: "VIBE_VALIDATION",
|
|
274
432
|
message: `unknown model "${model}"`,
|
|
275
433
|
meta: { model, known: [...new Set(meta.models.map((entry) => entry.name))] }
|
|
@@ -282,20 +440,20 @@ function getFieldMeta(params) {
|
|
|
282
440
|
const found = model.fieldByName.get(field);
|
|
283
441
|
if (found === undefined) {
|
|
284
442
|
if (model.computed !== null && model.computed.has(field)) {
|
|
285
|
-
throw new
|
|
443
|
+
throw new VibeError4({
|
|
286
444
|
code: "VIBE_VALIDATION",
|
|
287
445
|
message: `"${field}" on model "${model.name}" is a computed field \u2014 computed fields appear in results (select/omit) only, not in ${context}`,
|
|
288
446
|
meta: { model: model.name, field, context, computed: true }
|
|
289
447
|
});
|
|
290
448
|
}
|
|
291
449
|
if (model.relationByName.has(field)) {
|
|
292
|
-
throw new
|
|
450
|
+
throw new VibeError4({
|
|
293
451
|
code: "VIBE_VALIDATION",
|
|
294
452
|
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)`,
|
|
295
453
|
meta: { model: model.name, field, context }
|
|
296
454
|
});
|
|
297
455
|
}
|
|
298
|
-
throw new
|
|
456
|
+
throw new VibeError4({
|
|
299
457
|
code: "VIBE_VALIDATION",
|
|
300
458
|
message: `unknown field "${field}" on model "${model.name}" in ${context}`,
|
|
301
459
|
meta: { model: model.name, field, context }
|
|
@@ -305,7 +463,7 @@ function getFieldMeta(params) {
|
|
|
305
463
|
}
|
|
306
464
|
// packages/runtime/src/codecs.ts
|
|
307
465
|
import { JSON_TRANSPORT, projectionOutputName, parsePgArrayLiteral, toPgArrayLiteral } from "@vibeorm/sql";
|
|
308
|
-
import { VibeError as
|
|
466
|
+
import { VibeError as VibeError5, isEnumType as isEnumType3 } from "@vibeorm/schema";
|
|
309
467
|
import { DIALECT_CODECS, decodeIsNoop } from "@vibeorm/sql";
|
|
310
468
|
import {
|
|
311
469
|
DIALECT_CODECS as DIALECT_CODECS2,
|
|
@@ -317,7 +475,7 @@ import {
|
|
|
317
475
|
function getCodec(params) {
|
|
318
476
|
const { dialect, type } = params;
|
|
319
477
|
const table = DIALECT_CODECS[dialect];
|
|
320
|
-
return
|
|
478
|
+
return isEnumType3(type) ? table.String : table[type];
|
|
321
479
|
}
|
|
322
480
|
function fieldCodec(params) {
|
|
323
481
|
const { dialect, field } = params;
|
|
@@ -357,7 +515,7 @@ var DECIMAL_LITERAL = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
|
357
515
|
function toRealNumber(params) {
|
|
358
516
|
const { field, value } = params;
|
|
359
517
|
const refuse = () => {
|
|
360
|
-
throw new
|
|
518
|
+
throw new VibeError5({
|
|
361
519
|
code: "VIBE_VALIDATION",
|
|
362
520
|
message: `"${String(value)}" is not a numeric value for the Decimal field "${field.name}"`,
|
|
363
521
|
meta: { field: field.name, value }
|
|
@@ -426,20 +584,20 @@ function wirePlanKey(wire) {
|
|
|
426
584
|
}
|
|
427
585
|
function fieldDecoder(params) {
|
|
428
586
|
const { dialect, field, wire } = params;
|
|
429
|
-
const
|
|
587
|
+
const decode2 = fieldCodec({ dialect, field }).decode;
|
|
430
588
|
if (isPostgresEnumList({ dialect, field })) {
|
|
431
589
|
return (value) => decodeValue({ dialect, field, value });
|
|
432
590
|
}
|
|
433
|
-
if (
|
|
591
|
+
if (decode2 === IDENTITY_DECODE)
|
|
434
592
|
return null;
|
|
435
593
|
if (field.isList) {
|
|
436
594
|
if (field.scalarType === "Json" && decodeIsNoop({ scalarType: "Json", wire }))
|
|
437
595
|
return null;
|
|
438
|
-
return (value) => Array.isArray(value) ? value.map((element) => element === null || element === undefined ? element :
|
|
596
|
+
return (value) => Array.isArray(value) ? value.map((element) => element === null || element === undefined ? element : decode2(element)) : decode2(value);
|
|
439
597
|
}
|
|
440
598
|
if (field.scalarType !== null && decodeIsNoop({ scalarType: field.scalarType, wire }))
|
|
441
599
|
return null;
|
|
442
|
-
return
|
|
600
|
+
return decode2;
|
|
443
601
|
}
|
|
444
602
|
function buildDecodePlan(params) {
|
|
445
603
|
const { dialect, model, wire } = params;
|
|
@@ -447,12 +605,12 @@ function buildDecodePlan(params) {
|
|
|
447
605
|
const inPlace = [];
|
|
448
606
|
const renamedColumns = [];
|
|
449
607
|
for (const field of model.fields) {
|
|
450
|
-
const
|
|
451
|
-
byColumn.set(field.columnName, { name: field.name, decode });
|
|
608
|
+
const decode2 = fieldDecoder({ dialect, field, ...wire === undefined ? {} : { wire } });
|
|
609
|
+
byColumn.set(field.columnName, { name: field.name, decode: decode2 });
|
|
452
610
|
if (field.columnName !== field.name)
|
|
453
611
|
renamedColumns.push(field.columnName);
|
|
454
|
-
if (
|
|
455
|
-
inPlace.push({ column: field.columnName, decode });
|
|
612
|
+
if (decode2 !== null)
|
|
613
|
+
inPlace.push({ column: field.columnName, decode: decode2 });
|
|
456
614
|
}
|
|
457
615
|
return { byColumn, renamedColumns, decoders: inPlace, inPlace: renamedColumns.length > 0 ? null : inPlace };
|
|
458
616
|
}
|
|
@@ -579,12 +737,12 @@ function getJsonDecodePlan(model) {
|
|
|
579
737
|
const inPlace = [];
|
|
580
738
|
const renamedColumns = [];
|
|
581
739
|
for (const field of model.fields) {
|
|
582
|
-
const
|
|
583
|
-
byColumn.set(field.columnName, { name: field.name, decode });
|
|
740
|
+
const decode2 = field.scalarType === "Json" ? null : field.scalarType === "Bytes" && !field.isList ? decodeBase64Bytes : fieldDecoder({ dialect: "postgres", field });
|
|
741
|
+
byColumn.set(field.columnName, { name: field.name, decode: decode2 });
|
|
584
742
|
if (field.columnName !== field.name)
|
|
585
743
|
renamedColumns.push(field.columnName);
|
|
586
|
-
if (
|
|
587
|
-
inPlace.push({ column: field.columnName, decode });
|
|
744
|
+
if (decode2 !== null)
|
|
745
|
+
inPlace.push({ column: field.columnName, decode: decode2 });
|
|
588
746
|
}
|
|
589
747
|
plan = { byColumn, renamedColumns, decoders: inPlace, inPlace: renamedColumns.length > 0 ? null : inPlace };
|
|
590
748
|
jsonDecodePlanCache.set(model, plan);
|
|
@@ -597,7 +755,7 @@ function positionalMaterializer(params) {
|
|
|
597
755
|
const output = projectionOutputName({ column });
|
|
598
756
|
const entry = plan.byColumn.get(output);
|
|
599
757
|
if (entry === undefined)
|
|
600
|
-
throw new
|
|
758
|
+
throw new VibeError5({ code: "VIBE_INTERNAL", message: "positional projection has no schema field", meta: { model: model.name, column: output } });
|
|
601
759
|
return entry;
|
|
602
760
|
});
|
|
603
761
|
const width = slots.length;
|
|
@@ -605,7 +763,7 @@ function positionalMaterializer(params) {
|
|
|
605
763
|
const chunked = width > chunkSize;
|
|
606
764
|
const count = Math.ceil(width / chunkSize);
|
|
607
765
|
const invalid = () => {
|
|
608
|
-
throw new
|
|
766
|
+
throw new VibeError5({ code: "VIBE_INTERNAL", message: "positional relation payload does not match its SQL projection", meta: { model: model.name, expectedColumns: width } });
|
|
609
767
|
};
|
|
610
768
|
if (width === 0 || new Set(slots.map((slot) => slot.name)).size !== width)
|
|
611
769
|
return invalid();
|
|
@@ -647,7 +805,7 @@ function decodeAggregateValue(params) {
|
|
|
647
805
|
return field === null ? value : decodeValue({ dialect, field, value });
|
|
648
806
|
}
|
|
649
807
|
// packages/runtime/src/lateral-projection.ts
|
|
650
|
-
import { VibeError as
|
|
808
|
+
import { VibeError as VibeError6, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
651
809
|
function isPlainObject(value) {
|
|
652
810
|
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date);
|
|
653
811
|
}
|
|
@@ -664,7 +822,7 @@ function lateralProjection(params) {
|
|
|
664
822
|
const columns = names.map((name) => {
|
|
665
823
|
const field = model.fieldByName.get(name);
|
|
666
824
|
if (field === undefined)
|
|
667
|
-
throw new
|
|
825
|
+
throw new VibeError6({ code: "VIBE_INTERNAL", message: "lateral projection references an unknown field", meta: { model: model.name, field: name } });
|
|
668
826
|
return Object.freeze(applyLateralTransform({ model, column: { table: LATERAL_CHILD_ALIAS, name: field.columnName } }));
|
|
669
827
|
});
|
|
670
828
|
const descriptor = Object.freeze({ columns: Object.freeze(columns), hiddenOrder: Object.freeze(hiddenOrder) });
|
|
@@ -702,7 +860,7 @@ function applyLateralTransform(params) {
|
|
|
702
860
|
if (scalar !== "BigInt" && scalar !== "Decimal" && scalar !== "Bytes")
|
|
703
861
|
return column;
|
|
704
862
|
if (field.isList) {
|
|
705
|
-
throw
|
|
863
|
+
throw unsupportedCapability2({
|
|
706
864
|
dialect: "postgres",
|
|
707
865
|
feature: `join-strategy relation loading of ${model.name}.${field.name} (${scalar}[])`,
|
|
708
866
|
hint: 'list columns of BigInt/Decimal/Bytes have no faithful JSON wire form \u2014 use the portable "query" relation strategy'
|
|
@@ -712,7 +870,8 @@ function applyLateralTransform(params) {
|
|
|
712
870
|
}
|
|
713
871
|
|
|
714
872
|
// packages/runtime/src/query-builder.ts
|
|
715
|
-
import {
|
|
873
|
+
import { isSqlFragment, mapConflictColumns } from "@vibeorm/sql";
|
|
874
|
+
import { VibeError as VibeError10, unsupportedCapability as unsupportedCapability4 } from "@vibeorm/schema";
|
|
716
875
|
import {
|
|
717
876
|
SQL_DEFAULT_CELL,
|
|
718
877
|
containsPattern,
|
|
@@ -723,11 +882,12 @@ import {
|
|
|
723
882
|
jsonParamCell,
|
|
724
883
|
startsWithPattern
|
|
725
884
|
} from "@vibeorm/sql";
|
|
885
|
+
import { describeRowLock } from "@vibeorm/sql";
|
|
726
886
|
|
|
727
887
|
// packages/runtime/src/bulk-upsert.ts
|
|
728
|
-
import { VibeError as
|
|
729
|
-
function
|
|
730
|
-
return new
|
|
888
|
+
import { VibeError as VibeError7, unsupportedCapability as unsupportedCapability3 } from "@vibeorm/schema";
|
|
889
|
+
function validationError2(params) {
|
|
890
|
+
return new VibeError7({
|
|
731
891
|
code: "VIBE_VALIDATION",
|
|
732
892
|
message: params.message,
|
|
733
893
|
...params.meta === undefined ? {} : { meta: params.meta }
|
|
@@ -767,7 +927,7 @@ function uniqueGroupsOf(params) {
|
|
|
767
927
|
function resolveUpsertConflictTarget(params) {
|
|
768
928
|
const { model, target } = params;
|
|
769
929
|
if (target === undefined) {
|
|
770
|
-
throw
|
|
930
|
+
throw validationError2({
|
|
771
931
|
message: `upsertMany on ${model.clientName} needs an explicit conflictTarget \u2014 the unique group that decides which stored row an incoming row replaces`,
|
|
772
932
|
meta: { model: model.name }
|
|
773
933
|
});
|
|
@@ -775,7 +935,7 @@ function resolveUpsertConflictTarget(params) {
|
|
|
775
935
|
if (typeof target === "object" && target !== null && !Array.isArray(target) && typeof target.constraint === "string") {
|
|
776
936
|
const constraint = target.constraint;
|
|
777
937
|
if (constraint.length === 0 || Object.keys(target).length !== 1) {
|
|
778
|
-
throw
|
|
938
|
+
throw validationError2({
|
|
779
939
|
message: `conflictTarget on ${model.clientName}.upsertMany() must be a field-name array or { constraint: string }`,
|
|
780
940
|
meta: { model: model.name }
|
|
781
941
|
});
|
|
@@ -783,7 +943,7 @@ function resolveUpsertConflictTarget(params) {
|
|
|
783
943
|
return { kind: "constraint", constraint };
|
|
784
944
|
}
|
|
785
945
|
if (!Array.isArray(target) || target.length === 0 || target.some((name) => typeof name !== "string")) {
|
|
786
|
-
throw
|
|
946
|
+
throw validationError2({
|
|
787
947
|
message: `conflictTarget on ${model.clientName}.upsertMany() must be a non-empty field-name array or { constraint: string }`,
|
|
788
948
|
meta: { model: model.name }
|
|
789
949
|
});
|
|
@@ -792,7 +952,7 @@ function resolveUpsertConflictTarget(params) {
|
|
|
792
952
|
for (const name of target) {
|
|
793
953
|
const field = model.fieldByName.get(name);
|
|
794
954
|
if (field === undefined) {
|
|
795
|
-
throw
|
|
955
|
+
throw validationError2({
|
|
796
956
|
message: `unknown field "${name}" in conflictTarget on ${model.clientName}.upsertMany()`,
|
|
797
957
|
meta: { model: model.name, field: name }
|
|
798
958
|
});
|
|
@@ -801,7 +961,7 @@ function resolveUpsertConflictTarget(params) {
|
|
|
801
961
|
}
|
|
802
962
|
const signature = [...fields.map((field) => field.columnName)].sort((left, right) => left < right ? -1 : 1).join("\x00");
|
|
803
963
|
if (!uniqueGroupSignatures({ model }).has(signature)) {
|
|
804
|
-
throw
|
|
964
|
+
throw validationError2({
|
|
805
965
|
message: `conflictTarget [${fields.map((field) => field.name).join(", ")}] on ${model.clientName}.upsertMany() is not a declared unique group of ${model.name} \u2014 an arbiter without a unique index cannot decide which row an incoming row replaces`,
|
|
806
966
|
meta: { model: model.name, fields: fields.map((field) => field.name) }
|
|
807
967
|
});
|
|
@@ -815,7 +975,7 @@ function assertConflictTargetIsEnforceable(params) {
|
|
|
815
975
|
const groups = uniqueGroupSignatures({ model });
|
|
816
976
|
if (groups.size <= 1)
|
|
817
977
|
return;
|
|
818
|
-
throw
|
|
978
|
+
throw unsupportedCapability3({
|
|
819
979
|
dialect: dialect.name,
|
|
820
980
|
feature: `an enforced conflict target on ${model.clientName}.upsertMany() (${model.name} declares ${groups.size} unique groups)`,
|
|
821
981
|
hint: "mysql's ON DUPLICATE KEY UPDATE fires on ANY unique index, so another one can decide the conflict \u2014 pass allowAnyUniqueConflict: true to accept that, or gate the call per dialect"
|
|
@@ -824,7 +984,7 @@ function assertConflictTargetIsEnforceable(params) {
|
|
|
824
984
|
function resolveUpsertUpdateFields(params) {
|
|
825
985
|
const { model, update, targetColumns, skipUpdatedAt } = params;
|
|
826
986
|
if (!Array.isArray(update) || update.length === 0 || update.some((name) => typeof name !== "string")) {
|
|
827
|
-
throw
|
|
987
|
+
throw validationError2({
|
|
828
988
|
message: `update on ${model.clientName}.upsertMany() must be a non-empty array of field names \u2014 the columns a conflicting row takes from the incoming row`,
|
|
829
989
|
meta: { model: model.name }
|
|
830
990
|
});
|
|
@@ -834,19 +994,19 @@ function resolveUpsertUpdateFields(params) {
|
|
|
834
994
|
for (const name of update) {
|
|
835
995
|
const field = model.fieldByName.get(name);
|
|
836
996
|
if (field === undefined) {
|
|
837
|
-
throw
|
|
997
|
+
throw validationError2({
|
|
838
998
|
message: `unknown field "${name}" in update on ${model.clientName}.upsertMany()`,
|
|
839
999
|
meta: { model: model.name, field: name }
|
|
840
1000
|
});
|
|
841
1001
|
}
|
|
842
1002
|
if (targetColumns.has(field.columnName)) {
|
|
843
|
-
throw
|
|
1003
|
+
throw validationError2({
|
|
844
1004
|
message: `update on ${model.clientName}.upsertMany() cannot rewrite "${field.name}": it is part of the conflict target, the value that decides which row is being replaced`,
|
|
845
1005
|
meta: { model: model.name, field: field.name }
|
|
846
1006
|
});
|
|
847
1007
|
}
|
|
848
1008
|
if (model.policy !== undefined && model.policy.field === field.name) {
|
|
849
|
-
throw
|
|
1009
|
+
throw validationError2({
|
|
850
1010
|
message: `update on ${model.clientName}.upsertMany() cannot rewrite "${field.name}": it is ${model.name}'s tenant field (@@policy), and a batch that moves rows between tenants is never what an ingestion feed meant`,
|
|
851
1011
|
meta: { model: model.name, field: field.name }
|
|
852
1012
|
});
|
|
@@ -873,7 +1033,7 @@ function assertUpsertRowsBindColumns(params) {
|
|
|
873
1033
|
for (const column of targetColumns) {
|
|
874
1034
|
if (column in row)
|
|
875
1035
|
continue;
|
|
876
|
-
throw
|
|
1036
|
+
throw validationError2({
|
|
877
1037
|
message: `row ${index} of ${model.clientName}.upsertMany() does not set conflict-target column "${column}" \u2014 an incoming row without the arbiter's value cannot replace anything`,
|
|
878
1038
|
meta: { model: model.name, row: index, column }
|
|
879
1039
|
});
|
|
@@ -881,7 +1041,7 @@ function assertUpsertRowsBindColumns(params) {
|
|
|
881
1041
|
for (const field of updateFields) {
|
|
882
1042
|
if (field.columnName in row)
|
|
883
1043
|
continue;
|
|
884
|
-
throw
|
|
1044
|
+
throw validationError2({
|
|
885
1045
|
message: `row ${index} of ${model.clientName}.upsertMany() does not set "${field.name}", which the update list overwrites \u2014 the conflict arm would write the column default over a stored value; give every row a value or drop "${field.name}" from update`,
|
|
886
1046
|
meta: { model: model.name, row: index, field: field.name }
|
|
887
1047
|
});
|
|
@@ -913,7 +1073,7 @@ function assertNoDuplicateConflictKeys(params) {
|
|
|
913
1073
|
const key = conflictKeyOf({ row: compiled[index], columns: targetColumns });
|
|
914
1074
|
const first = firstIndexByKey.get(key);
|
|
915
1075
|
if (first !== undefined) {
|
|
916
|
-
throw
|
|
1076
|
+
throw validationError2({
|
|
917
1077
|
message: `${model.clientName}.upsertMany() got the same conflict key twice \u2014 rows ${first} and ${index} both address (${targetColumns.join(", ")}); deduplicate the feed before submitting it, because the databases disagree about what one statement writing a key twice means (postgres refuses it, mysql keeps the last one)`,
|
|
918
1078
|
meta: { model: model.name, rows: [first, index], columns: [...targetColumns] }
|
|
919
1079
|
});
|
|
@@ -938,7 +1098,7 @@ function assertNoDuplicateSecondaryUniques(params) {
|
|
|
938
1098
|
const key = conflictKeyOf({ row, columns });
|
|
939
1099
|
const first = firstIndexByKey.get(key);
|
|
940
1100
|
if (first !== undefined) {
|
|
941
|
-
throw
|
|
1101
|
+
throw unsupportedCapability3({
|
|
942
1102
|
dialect: dialect.name,
|
|
943
1103
|
feature: `${model.clientName}.upsertMany() with rows ${first} and ${index} carrying the same ${fields.map((field) => field.name).join(" + ")}`,
|
|
944
1104
|
hint: `ON DUPLICATE KEY UPDATE has no conflict target: it resolves through whichever unique index an incoming row matches, so row ${index} would UPDATE row ${first} through unique (${columns.join(", ")}) and one of the two rows you submitted would silently not exist. Deduplicate the feed on those columns as well as on the conflict target, or submit the colliding rows in separate calls. allowAnyUniqueConflict widens which STORED row a single incoming row may replace; it does not authorise two submitted rows collapsing into one`
|
|
@@ -978,7 +1138,7 @@ function assertConflictKeysFit(params) {
|
|
|
978
1138
|
const length = typeof value === "string" ? Array.from(value).length : value instanceof Uint8Array ? value.byteLength : 0;
|
|
979
1139
|
if (length <= capacity)
|
|
980
1140
|
continue;
|
|
981
|
-
throw
|
|
1141
|
+
throw validationError2({
|
|
982
1142
|
message: `${params.model.clientName}.upsertMany() row ${index} conflict key "${field.name}" exceeds its column capacity ${capacity}. The database can truncate it and merge distinct input keys; shorten the value or migrate to a wider column.`,
|
|
983
1143
|
meta: { model: params.model.name, field: field.name, row: index, capacity }
|
|
984
1144
|
});
|
|
@@ -1020,7 +1180,7 @@ function assertConflictKeysAreComparable(params) {
|
|
|
1020
1180
|
if (target.kind === "constraint") {
|
|
1021
1181
|
if (engineComparesEveryPair)
|
|
1022
1182
|
return;
|
|
1023
|
-
throw
|
|
1183
|
+
throw unsupportedCapability3({
|
|
1024
1184
|
dialect: dialect.name,
|
|
1025
1185
|
feature: `a ${statementCount > 1 ? "chunked " : ""}${model.clientName}.upsertMany() batch arbitrated by constraint "${target.constraint}"`,
|
|
1026
1186
|
hint: statementCount > 1 ? `a named constraint hides which columns decide the conflict, so only the engine can tell whether two incoming rows address the same stored row \u2014 and it only compares rows inside ONE statement, while this batch of ${model.name} rows needs ${statementCount}. Submit fewer rows per call, or name the unique group's fields instead of the constraint` : "a named constraint hides which columns decide the conflict, so this dialect would silently keep the last of two rows that address the same stored row"
|
|
@@ -1038,7 +1198,7 @@ function assertConflictKeysAreComparable(params) {
|
|
|
1038
1198
|
notes.push(`@collation("${field.collation}")`);
|
|
1039
1199
|
return notes.length === 0 ? `${field.name} (column "${field.columnName}")` : `${field.name} (column "${field.columnName}", ${notes.join(", ")})`;
|
|
1040
1200
|
}).join(", ");
|
|
1041
|
-
throw
|
|
1201
|
+
throw unsupportedCapability3({
|
|
1042
1202
|
dialect: dialect.name,
|
|
1043
1203
|
feature: `${model.clientName}.upsertMany() on conflict target ${described}`,
|
|
1044
1204
|
hint: statementCount > 1 ? `only the database can tell whether two of these keys address the same stored row \u2014 its column type, collation, scale or precision decides \u2014 and it only compares rows inside ONE statement, while this batch of ${model.name} rows needs ${statementCount}. Submit fewer rows per call, or ${remedyFor({ dialect, fields: opaque })}` : `only the database can tell whether two of these keys address the same stored row \u2014 its column type, collation, scale or precision decides \u2014 and this dialect answers a doubled key by silently keeping the last row instead of refusing it. Deduplicate the feed against the same rule the column uses, or ${remedyFor({ dialect, fields: opaque })}`
|
|
@@ -1046,7 +1206,7 @@ function assertConflictKeysAreComparable(params) {
|
|
|
1046
1206
|
}
|
|
1047
1207
|
|
|
1048
1208
|
// packages/runtime/src/keyset.ts
|
|
1049
|
-
import { VibeError as
|
|
1209
|
+
import { VibeError as VibeError8 } from "@vibeorm/schema";
|
|
1050
1210
|
var KEYSET_TOKEN_VERSION = 1;
|
|
1051
1211
|
var KEYSET_TOKEN_PREFIX = "vk1.";
|
|
1052
1212
|
var KEYSET_MAX_PAGE_SIZE = 1e4;
|
|
@@ -1056,10 +1216,10 @@ var DATETIME_NEEDS_PRECISION_ASSERTION = Object.freeze({
|
|
|
1056
1216
|
sqlite: false
|
|
1057
1217
|
});
|
|
1058
1218
|
function keysetValidation(params) {
|
|
1059
|
-
return new
|
|
1219
|
+
return new VibeError8({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
1060
1220
|
}
|
|
1061
1221
|
function keysetUnsupported(params) {
|
|
1062
|
-
return new
|
|
1222
|
+
return new VibeError8({
|
|
1063
1223
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
1064
1224
|
message: params.message,
|
|
1065
1225
|
meta: params.meta
|
|
@@ -1442,9 +1602,9 @@ function decodeKeysetToken(params) {
|
|
|
1442
1602
|
}
|
|
1443
1603
|
|
|
1444
1604
|
// packages/runtime/src/relation-plan.ts
|
|
1445
|
-
import { VibeError as
|
|
1446
|
-
function
|
|
1447
|
-
return new
|
|
1605
|
+
import { VibeError as VibeError9, implicitJoinTable } from "@vibeorm/schema";
|
|
1606
|
+
function validationError3(params) {
|
|
1607
|
+
return new VibeError9({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
1448
1608
|
}
|
|
1449
1609
|
function isPlainObject2(value) {
|
|
1450
1610
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
@@ -1461,7 +1621,7 @@ function findReverseRelation(params) {
|
|
|
1461
1621
|
function keyFields(params) {
|
|
1462
1622
|
const { model, names, relation, what } = params;
|
|
1463
1623
|
if (names === undefined || names.length === 0) {
|
|
1464
|
-
throw
|
|
1624
|
+
throw validationError3({
|
|
1465
1625
|
message: `relation "${relation.name}" on ${relation.model} has no ${what} \u2014 the schema cannot be loaded through`,
|
|
1466
1626
|
meta: { model: relation.model, relation: relation.name }
|
|
1467
1627
|
});
|
|
@@ -1469,7 +1629,7 @@ function keyFields(params) {
|
|
|
1469
1629
|
return names.map((name) => {
|
|
1470
1630
|
const field = model.fieldByName.get(name);
|
|
1471
1631
|
if (field === undefined) {
|
|
1472
|
-
throw new
|
|
1632
|
+
throw new VibeError9({
|
|
1473
1633
|
code: "VIBE_SCHEMA_INVALID",
|
|
1474
1634
|
message: `relation "${relation.name}" on ${relation.model} names unknown ${what} field "${name}"`,
|
|
1475
1635
|
meta: { model: relation.model, relation: relation.name }
|
|
@@ -1481,7 +1641,7 @@ function keyFields(params) {
|
|
|
1481
1641
|
function singleKeyPair(params) {
|
|
1482
1642
|
const { link, what } = params;
|
|
1483
1643
|
if (link.parentKeys.length !== 1 || link.childKeys.length !== 1) {
|
|
1484
|
-
throw new
|
|
1644
|
+
throw new VibeError9({
|
|
1485
1645
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
1486
1646
|
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`,
|
|
1487
1647
|
meta: { model: link.relation.model, relation: link.relation.name, arity: link.parentKeys.length }
|
|
@@ -1493,7 +1653,7 @@ function singlePrimaryKey(params) {
|
|
|
1493
1653
|
const { model, relation } = params;
|
|
1494
1654
|
const pk = model.primaryKey;
|
|
1495
1655
|
if (pk.length !== 1) {
|
|
1496
|
-
throw
|
|
1656
|
+
throw validationError3({
|
|
1497
1657
|
message: `implicit many-to-many relation "${relation.name}" needs a single-column primary key on ${model.name}`,
|
|
1498
1658
|
meta: { model: model.name, relation: relation.name }
|
|
1499
1659
|
});
|
|
@@ -1520,7 +1680,7 @@ function resolveRelationLinkUncached(params) {
|
|
|
1520
1680
|
const target = getModelMeta({ meta, model: relation.targetModel });
|
|
1521
1681
|
if (relation.type === "manyToMany") {
|
|
1522
1682
|
if (relation.model === relation.targetModel) {
|
|
1523
|
-
throw new
|
|
1683
|
+
throw new VibeError9({
|
|
1524
1684
|
code: "VIBE_SCHEMA_INVALID",
|
|
1525
1685
|
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.`,
|
|
1526
1686
|
meta: { model: relation.model, relation: relation.name, targetModel: relation.targetModel }
|
|
@@ -1562,7 +1722,7 @@ function resolveRelationLinkUncached(params) {
|
|
|
1562
1722
|
}
|
|
1563
1723
|
const reverse = findReverseRelation({ relation, target });
|
|
1564
1724
|
if (reverse === null || reverse.fields === undefined || reverse.fields.length === 0) {
|
|
1565
|
-
throw new
|
|
1725
|
+
throw new VibeError9({
|
|
1566
1726
|
code: "VIBE_SCHEMA_INVALID",
|
|
1567
1727
|
message: `relation "${relation.name}" on ${relation.model} has no foreign-key counterpart on ${relation.targetModel}`,
|
|
1568
1728
|
meta: { model: relation.model, relation: relation.name, target: relation.targetModel }
|
|
@@ -1587,7 +1747,7 @@ function pairedKeys(params) {
|
|
|
1587
1747
|
const parentKeys = keyFields({ model, names: parentNames, relation, what: "fields" });
|
|
1588
1748
|
const childKeys = keyFields({ model: target, names: childNames, relation, what: "references" });
|
|
1589
1749
|
if (parentKeys.length !== childKeys.length) {
|
|
1590
|
-
throw new
|
|
1750
|
+
throw new VibeError9({
|
|
1591
1751
|
code: "VIBE_SCHEMA_INVALID",
|
|
1592
1752
|
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`,
|
|
1593
1753
|
meta: { model: relation.model, relation: relation.name }
|
|
@@ -1600,7 +1760,7 @@ var TO_ONE_ARG_KEYS = ["where", "select", "include", "omit"];
|
|
|
1600
1760
|
function toNonNegativeInteger(params) {
|
|
1601
1761
|
const { value, what, relation } = params;
|
|
1602
1762
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
1603
|
-
throw
|
|
1763
|
+
throw validationError3({
|
|
1604
1764
|
message: `${what} on relation "${relation}" must be a non-negative integer`,
|
|
1605
1765
|
meta: { relation, argument: what, value }
|
|
1606
1766
|
});
|
|
@@ -1617,7 +1777,7 @@ function compileChildSelect(params) {
|
|
|
1617
1777
|
continue;
|
|
1618
1778
|
if (model.computed !== null && model.computed.has(name)) {
|
|
1619
1779
|
if (value !== true) {
|
|
1620
|
-
throw
|
|
1780
|
+
throw validationError3({
|
|
1621
1781
|
message: `select.${name} must be true or false on model "${model.name}"`,
|
|
1622
1782
|
meta: { model: model.name, field: name }
|
|
1623
1783
|
});
|
|
@@ -1626,13 +1786,13 @@ function compileChildSelect(params) {
|
|
|
1626
1786
|
}
|
|
1627
1787
|
const field = model.fieldByName.get(name);
|
|
1628
1788
|
if (field === undefined) {
|
|
1629
|
-
throw
|
|
1789
|
+
throw validationError3({
|
|
1630
1790
|
message: `unknown field "${name}" on model "${model.name}" in select`,
|
|
1631
1791
|
meta: { model: model.name, field: name }
|
|
1632
1792
|
});
|
|
1633
1793
|
}
|
|
1634
1794
|
if (value !== true) {
|
|
1635
|
-
throw
|
|
1795
|
+
throw validationError3({
|
|
1636
1796
|
message: `select.${name} must be true or false on model "${model.name}"`,
|
|
1637
1797
|
meta: { model: model.name, field: name }
|
|
1638
1798
|
});
|
|
@@ -1644,7 +1804,7 @@ function compileChildSelect(params) {
|
|
|
1644
1804
|
function compileChildOmit(params) {
|
|
1645
1805
|
const { model, omit, relation } = params;
|
|
1646
1806
|
if (!isPlainObject2(omit)) {
|
|
1647
|
-
throw
|
|
1807
|
+
throw validationError3({
|
|
1648
1808
|
message: `omit on relation "${relation}" must be an object`,
|
|
1649
1809
|
meta: { model: model.name, relation }
|
|
1650
1810
|
});
|
|
@@ -1654,13 +1814,13 @@ function compileChildOmit(params) {
|
|
|
1654
1814
|
if (value === undefined || value === false)
|
|
1655
1815
|
continue;
|
|
1656
1816
|
if (value !== true) {
|
|
1657
|
-
throw
|
|
1817
|
+
throw validationError3({
|
|
1658
1818
|
message: `omit.${name} must be true or false on model "${model.name}"`,
|
|
1659
1819
|
meta: { model: model.name, relation, field: name }
|
|
1660
1820
|
});
|
|
1661
1821
|
}
|
|
1662
1822
|
if (model.relationByName.has(name)) {
|
|
1663
|
-
throw
|
|
1823
|
+
throw validationError3({
|
|
1664
1824
|
message: `omit.${name} on model "${model.name}" \u2014 omit takes scalar fields; a relation is excluded by not including it`,
|
|
1665
1825
|
meta: { model: model.name, relation, field: name }
|
|
1666
1826
|
});
|
|
@@ -1668,7 +1828,7 @@ function compileChildOmit(params) {
|
|
|
1668
1828
|
if (model.computed !== null && model.computed.has(name))
|
|
1669
1829
|
continue;
|
|
1670
1830
|
if (!model.fieldByName.has(name)) {
|
|
1671
|
-
throw
|
|
1831
|
+
throw validationError3({
|
|
1672
1832
|
message: `unknown field "${name}" on model "${model.name}" in omit`,
|
|
1673
1833
|
meta: { model: model.name, relation, field: name }
|
|
1674
1834
|
});
|
|
@@ -1677,7 +1837,7 @@ function compileChildOmit(params) {
|
|
|
1677
1837
|
}
|
|
1678
1838
|
const kept = model.fields.filter((field) => !omitted.has(field.name)).map((field) => field.name);
|
|
1679
1839
|
if (kept.length === 0) {
|
|
1680
|
-
throw
|
|
1840
|
+
throw validationError3({
|
|
1681
1841
|
message: `omit on relation "${relation}" removes every field of model "${model.name}"`,
|
|
1682
1842
|
meta: { model: model.name, relation }
|
|
1683
1843
|
});
|
|
@@ -1702,14 +1862,14 @@ function resolveCountNode(params) {
|
|
|
1702
1862
|
const { meta, model, name, where } = params;
|
|
1703
1863
|
const relation = model.relationByName.get(name);
|
|
1704
1864
|
if (relation === undefined || !relation.isList) {
|
|
1705
|
-
throw
|
|
1865
|
+
throw validationError3({
|
|
1706
1866
|
message: `_count.select.${name} on model "${model.name}" must name a list relation`,
|
|
1707
1867
|
meta: { model: model.name, relation: name }
|
|
1708
1868
|
});
|
|
1709
1869
|
}
|
|
1710
1870
|
const link = resolveRelationLink({ meta, model, relation });
|
|
1711
1871
|
if (where !== undefined && link.target.policy !== undefined) {
|
|
1712
|
-
throw
|
|
1872
|
+
throw validationError3({
|
|
1713
1873
|
message: `_count of ${model.name}.${name} cannot take a filter because ${link.target.name} declares @@policy \u2014 a filtered count would not apply the policy predicate; count the relation with a scoped child query instead`,
|
|
1714
1874
|
meta: { model: model.name, relation: name }
|
|
1715
1875
|
});
|
|
@@ -1722,7 +1882,7 @@ function resolveCounts(params) {
|
|
|
1722
1882
|
return model.relations.filter((relation) => relation.isList).map((relation) => resolveCountNode({ meta, model, name: relation.name, where: undefined }));
|
|
1723
1883
|
}
|
|
1724
1884
|
if (!isPlainObject2(spec) || !isPlainObject2(spec.select)) {
|
|
1725
|
-
throw
|
|
1885
|
+
throw validationError3({
|
|
1726
1886
|
message: `_count on model "${model.name}" must be true or { select: { relation: true } }`,
|
|
1727
1887
|
meta: { model: model.name }
|
|
1728
1888
|
});
|
|
@@ -1736,7 +1896,7 @@ function resolveCounts(params) {
|
|
|
1736
1896
|
continue;
|
|
1737
1897
|
}
|
|
1738
1898
|
if (!isPlainObject2(value)) {
|
|
1739
|
-
throw
|
|
1899
|
+
throw validationError3({
|
|
1740
1900
|
message: `_count.select.${name} on model "${model.name}" must be true, false, or { where: \u2026 }`,
|
|
1741
1901
|
meta: { model: model.name, relation: name }
|
|
1742
1902
|
});
|
|
@@ -1745,7 +1905,7 @@ function resolveCounts(params) {
|
|
|
1745
1905
|
if (value[key] === undefined)
|
|
1746
1906
|
continue;
|
|
1747
1907
|
if (!COUNT_ENTRY_KEYS.includes(key)) {
|
|
1748
|
-
throw
|
|
1908
|
+
throw validationError3({
|
|
1749
1909
|
message: `_count.select.${name} on model "${model.name}" takes only "where" \u2014 got "${key}"`,
|
|
1750
1910
|
meta: { model: model.name, relation: name, argument: key }
|
|
1751
1911
|
});
|
|
@@ -1753,7 +1913,7 @@ function resolveCounts(params) {
|
|
|
1753
1913
|
}
|
|
1754
1914
|
const where = value.where;
|
|
1755
1915
|
if (where !== undefined && !isPlainObject2(where)) {
|
|
1756
|
-
throw
|
|
1916
|
+
throw validationError3({
|
|
1757
1917
|
message: `_count.select.${name}.where on model "${model.name}" must be an object`,
|
|
1758
1918
|
meta: { model: model.name, relation: name }
|
|
1759
1919
|
});
|
|
@@ -1773,27 +1933,27 @@ function resolveNode(params) {
|
|
|
1773
1933
|
if (raw[key] === undefined)
|
|
1774
1934
|
continue;
|
|
1775
1935
|
if (!allowed.includes(key)) {
|
|
1776
|
-
throw
|
|
1936
|
+
throw validationError3({
|
|
1777
1937
|
message: `unsupported argument "${key}" on relation "${relation.name}" \u2014 supported: ${allowed.join(", ")}`,
|
|
1778
1938
|
meta: { model: model.name, relation: relation.name, argument: key }
|
|
1779
1939
|
});
|
|
1780
1940
|
}
|
|
1781
1941
|
}
|
|
1782
1942
|
if (raw.select !== undefined && raw.include !== undefined) {
|
|
1783
|
-
throw
|
|
1943
|
+
throw validationError3({
|
|
1784
1944
|
message: `relation "${relation.name}" cannot use select and include together`,
|
|
1785
1945
|
meta: { model: model.name, relation: relation.name }
|
|
1786
1946
|
});
|
|
1787
1947
|
}
|
|
1788
1948
|
if (raw.select !== undefined && raw.omit !== undefined) {
|
|
1789
|
-
throw
|
|
1949
|
+
throw validationError3({
|
|
1790
1950
|
message: `relation "${relation.name}" cannot use select and omit together \u2014 select names fields to keep, omit fields to drop`,
|
|
1791
1951
|
meta: { model: model.name, relation: relation.name }
|
|
1792
1952
|
});
|
|
1793
1953
|
}
|
|
1794
1954
|
const where = raw.where === undefined ? undefined : (() => {
|
|
1795
1955
|
if (!isPlainObject2(raw.where)) {
|
|
1796
|
-
throw
|
|
1956
|
+
throw validationError3({
|
|
1797
1957
|
message: `where on relation "${relation.name}" must be an object`,
|
|
1798
1958
|
meta: { model: model.name, relation: relation.name }
|
|
1799
1959
|
});
|
|
@@ -1865,13 +2025,13 @@ function resolveRelationSelection(params) {
|
|
|
1865
2025
|
continue;
|
|
1866
2026
|
if (!model.relationByName.has(key)) {
|
|
1867
2027
|
const isScalar = model.fieldByName.has(key);
|
|
1868
|
-
throw
|
|
2028
|
+
throw validationError3({
|
|
1869
2029
|
message: isScalar ? `"${key}" on model "${model.name}" is a scalar field \u2014 include takes relations, use select for scalars` : `unknown relation "${key}" on model "${model.name}" in include`,
|
|
1870
2030
|
meta: { model: model.name, relation: key }
|
|
1871
2031
|
});
|
|
1872
2032
|
}
|
|
1873
2033
|
if (value !== true && !isPlainObject2(value)) {
|
|
1874
|
-
throw
|
|
2034
|
+
throw validationError3({
|
|
1875
2035
|
message: `include.${key} on model "${model.name}" must be true or an object`,
|
|
1876
2036
|
meta: { model: model.name, relation: key }
|
|
1877
2037
|
});
|
|
@@ -1936,8 +2096,8 @@ var AGG_FN_BY_KEY = {
|
|
|
1936
2096
|
_min: "min",
|
|
1937
2097
|
_max: "max"
|
|
1938
2098
|
};
|
|
1939
|
-
function
|
|
1940
|
-
return new
|
|
2099
|
+
function validationError4(params) {
|
|
2100
|
+
return new VibeError10({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
1941
2101
|
}
|
|
1942
2102
|
function isPlainObject3(value) {
|
|
1943
2103
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
@@ -1957,40 +2117,115 @@ function columnRef(params) {
|
|
|
1957
2117
|
const { field, table } = params;
|
|
1958
2118
|
return table === undefined ? { name: field.columnName } : { table, name: field.columnName };
|
|
1959
2119
|
}
|
|
2120
|
+
var ROW_LOCK_STRENGTHS = Object.freeze([
|
|
2121
|
+
"update",
|
|
2122
|
+
"noKeyUpdate",
|
|
2123
|
+
"share",
|
|
2124
|
+
"keyShare"
|
|
2125
|
+
]);
|
|
2126
|
+
var ROW_LOCK_WAIT_POLICIES = Object.freeze([
|
|
2127
|
+
"wait",
|
|
2128
|
+
"nowait",
|
|
2129
|
+
"skipLocked"
|
|
2130
|
+
]);
|
|
2131
|
+
var DEFAULT_ROW_LOCK = Object.freeze({ strength: "update", wait: "wait" });
|
|
2132
|
+
function readLockRequest(params) {
|
|
2133
|
+
const { value, model, method } = params;
|
|
2134
|
+
const where = `lock on ${model.clientName}.${method}()`;
|
|
2135
|
+
if (value === "update")
|
|
2136
|
+
return DEFAULT_ROW_LOCK;
|
|
2137
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2138
|
+
throw validationError4({
|
|
2139
|
+
message: `${where} must be "update" or { strength, wait? }, got ${JSON.stringify(value) ?? String(value)}`,
|
|
2140
|
+
meta: { model: model.name, method, lock: value }
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
const request = value;
|
|
2144
|
+
for (const key of Object.keys(request)) {
|
|
2145
|
+
if (key === "strength" || key === "wait")
|
|
2146
|
+
continue;
|
|
2147
|
+
throw validationError4({
|
|
2148
|
+
message: `${where} has no "${key}" member \u2014 a lock is { strength, wait? }, where wait is ` + `one of ${ROW_LOCK_WAIT_POLICIES.map((policy) => `"${policy}"`).join(" | ")} ` + `(one policy, never several flags)`,
|
|
2149
|
+
meta: { model: model.name, method, member: key }
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
const strength = request.strength;
|
|
2153
|
+
if (typeof strength !== "string" || !ROW_LOCK_STRENGTHS.includes(strength)) {
|
|
2154
|
+
throw validationError4({
|
|
2155
|
+
message: `${where} needs strength: ${ROW_LOCK_STRENGTHS.map((item) => `"${item}"`).join(" | ")}, got ${String(strength)}`,
|
|
2156
|
+
meta: { model: model.name, method, strength }
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
const wait = request.wait;
|
|
2160
|
+
if (wait !== undefined && (typeof wait !== "string" || !ROW_LOCK_WAIT_POLICIES.includes(wait))) {
|
|
2161
|
+
throw validationError4({
|
|
2162
|
+
message: `${where} needs wait: ${ROW_LOCK_WAIT_POLICIES.map((item) => `"${item}"`).join(" | ")}, got ${String(wait)}`,
|
|
2163
|
+
meta: { model: model.name, method, wait }
|
|
2164
|
+
});
|
|
2165
|
+
}
|
|
2166
|
+
return {
|
|
2167
|
+
strength,
|
|
2168
|
+
wait: wait === undefined ? "wait" : wait
|
|
2169
|
+
};
|
|
2170
|
+
}
|
|
1960
2171
|
function compileLock(params) {
|
|
1961
|
-
const { args, model, method, hasRelations } = params;
|
|
2172
|
+
const { dialect, args, model, method, hasRelations } = params;
|
|
1962
2173
|
if (args.lock === undefined)
|
|
1963
|
-
return
|
|
2174
|
+
return null;
|
|
1964
2175
|
if (model.isView) {
|
|
1965
|
-
throw
|
|
2176
|
+
throw validationError4({
|
|
1966
2177
|
message: `lock on ${model.clientName}.${method}() \u2014 a view row cannot be locked; lock the underlying table's rows instead`,
|
|
1967
2178
|
meta: { model: model.name, method }
|
|
1968
2179
|
});
|
|
1969
2180
|
}
|
|
1970
|
-
|
|
1971
|
-
throw validationError3({
|
|
1972
|
-
message: `lock on ${model.clientName}.${method}() must be "update", got ${String(args.lock)}`,
|
|
1973
|
-
meta: { model: model.name, method, lock: args.lock }
|
|
1974
|
-
});
|
|
1975
|
-
}
|
|
2181
|
+
const lock = readLockRequest({ value: args.lock, model, method });
|
|
1976
2182
|
if (args.distinct !== undefined) {
|
|
1977
|
-
throw
|
|
2183
|
+
throw validationError4({
|
|
1978
2184
|
message: `lock cannot combine with distinct on ${model.clientName}.${method}() \u2014 postgres rejects FOR UPDATE with DISTINCT`,
|
|
1979
2185
|
meta: { model: model.name, method }
|
|
1980
2186
|
});
|
|
1981
2187
|
}
|
|
1982
2188
|
if (hasRelations) {
|
|
1983
|
-
throw
|
|
2189
|
+
throw validationError4({
|
|
1984
2190
|
message: `lock cannot combine with relation loading on ${model.clientName}.${method}() \u2014 lock the parent row alone, then read relations inside the same transaction`,
|
|
1985
2191
|
meta: { model: model.name, method }
|
|
1986
2192
|
});
|
|
1987
2193
|
}
|
|
1988
|
-
|
|
2194
|
+
assertLockIsExpressible({ dialect, lock, model, method });
|
|
2195
|
+
return lock;
|
|
2196
|
+
}
|
|
2197
|
+
function assertLockIsExpressible(params) {
|
|
2198
|
+
const { dialect, lock, model, method } = params;
|
|
2199
|
+
const { rowLockStrengths, rowLockWaitPolicies } = dialect.capabilities;
|
|
2200
|
+
const site = `${describeRowLock({ lock })} on ${model.clientName}.${method}()`;
|
|
2201
|
+
if (rowLockStrengths.length === 0) {
|
|
2202
|
+
if (lock.strength === "update" && lock.wait === "wait")
|
|
2203
|
+
return;
|
|
2204
|
+
throw unsupportedCapability4({
|
|
2205
|
+
dialect: dialect.name,
|
|
2206
|
+
feature: site,
|
|
2207
|
+
hint: `${dialect.name} has no locking-read clause at all. Only lock: "update" is accepted there, ` + `as the documented no-op its single-writer transactions already provide`
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
if (!rowLockStrengths.includes(lock.strength)) {
|
|
2211
|
+
throw unsupportedCapability4({
|
|
2212
|
+
dialect: dialect.name,
|
|
2213
|
+
feature: site,
|
|
2214
|
+
hint: `${dialect.name} has these lock strengths: ${rowLockStrengths.join(", ")}`
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
if (!rowLockWaitPolicies.includes(lock.wait)) {
|
|
2218
|
+
throw unsupportedCapability4({
|
|
2219
|
+
dialect: dialect.name,
|
|
2220
|
+
feature: site,
|
|
2221
|
+
hint: `${dialect.name} has these lock wait policies: ${rowLockWaitPolicies.join(", ")}`
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
1989
2224
|
}
|
|
1990
2225
|
function compileConflictTarget(params) {
|
|
1991
2226
|
const { dialect, meta, model, method, target, where } = params;
|
|
1992
2227
|
if ((target !== undefined || where !== undefined) && dialect.doNothingForm === "insertIgnore") {
|
|
1993
|
-
throw
|
|
2228
|
+
throw unsupportedCapability4({
|
|
1994
2229
|
dialect: dialect.name,
|
|
1995
2230
|
feature: `a conflict target on ${model.clientName}.${method}()`,
|
|
1996
2231
|
hint: "mysql's INSERT IGNORE / ON DUPLICATE KEY fire on ANY duplicate key \u2014 no target can be named; gate the call per dialect"
|
|
@@ -2000,7 +2235,7 @@ function compileConflictTarget(params) {
|
|
|
2000
2235
|
if (target !== undefined) {
|
|
2001
2236
|
if (Array.isArray(target)) {
|
|
2002
2237
|
if (target.length === 0) {
|
|
2003
|
-
throw
|
|
2238
|
+
throw validationError4({
|
|
2004
2239
|
message: `onConflict target on ${model.clientName}.${method}() must name at least one field`,
|
|
2005
2240
|
meta: { model: model.name, method }
|
|
2006
2241
|
});
|
|
@@ -2008,7 +2243,7 @@ function compileConflictTarget(params) {
|
|
|
2008
2243
|
out.columns = target.map((name) => getFieldMeta({ model, field: String(name), context: "onConflict target" }).columnName);
|
|
2009
2244
|
} else if (isPlainObject3(target) && typeof target.constraint === "string" && target.constraint.length > 0 && Object.keys(target).length === 1) {
|
|
2010
2245
|
if (!dialect.capabilities.conflictTargetConstraint) {
|
|
2011
|
-
throw
|
|
2246
|
+
throw unsupportedCapability4({
|
|
2012
2247
|
dialect: dialect.name,
|
|
2013
2248
|
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
2014
2249
|
hint: "only postgres supports ON CONSTRAINT targets \u2014 name the indexed columns instead"
|
|
@@ -2016,7 +2251,7 @@ function compileConflictTarget(params) {
|
|
|
2016
2251
|
}
|
|
2017
2252
|
out.constraint = target.constraint;
|
|
2018
2253
|
} else {
|
|
2019
|
-
throw
|
|
2254
|
+
throw validationError4({
|
|
2020
2255
|
message: `onConflict target on ${model.clientName}.${method}() must be a field-name array or { constraint: string }`,
|
|
2021
2256
|
meta: { model: model.name, method }
|
|
2022
2257
|
});
|
|
@@ -2024,20 +2259,20 @@ function compileConflictTarget(params) {
|
|
|
2024
2259
|
}
|
|
2025
2260
|
if (where !== undefined) {
|
|
2026
2261
|
if (out.columns === undefined) {
|
|
2027
|
-
throw
|
|
2262
|
+
throw validationError4({
|
|
2028
2263
|
message: `onConflict where on ${model.clientName}.${method}() needs a column-list target \u2014 the predicate refines "(columns) WHERE \u2026"`,
|
|
2029
2264
|
meta: { model: model.name, method }
|
|
2030
2265
|
});
|
|
2031
2266
|
}
|
|
2032
2267
|
if (!dialect.capabilities.partialIndexes) {
|
|
2033
|
-
throw
|
|
2268
|
+
throw unsupportedCapability4({
|
|
2034
2269
|
dialect: dialect.name,
|
|
2035
2270
|
feature: "a partial-index conflict target",
|
|
2036
2271
|
hint: "this dialect has no partial indexes to target"
|
|
2037
2272
|
});
|
|
2038
2273
|
}
|
|
2039
2274
|
if (typeof where !== "string" || where.trim().length === 0) {
|
|
2040
|
-
throw
|
|
2275
|
+
throw validationError4({
|
|
2041
2276
|
message: `onConflict where on ${model.clientName}.${method}() must be the partial index's raw predicate string (the schema declaration's where: text)`,
|
|
2042
2277
|
meta: { model: model.name, method }
|
|
2043
2278
|
});
|
|
@@ -2049,13 +2284,13 @@ function compileConflictTarget(params) {
|
|
|
2049
2284
|
function compileDbNowWrite(params) {
|
|
2050
2285
|
const { model, field, value } = params;
|
|
2051
2286
|
if (field.scalarType !== "DateTime" || field.isList) {
|
|
2052
|
-
throw
|
|
2287
|
+
throw validationError4({
|
|
2053
2288
|
message: `dbNow() writes the database clock into a DateTime field \u2014 ${model.name}.${field.name} is ${String(field.enumName ?? field.scalarType)}${field.isList ? "[]" : ""}`,
|
|
2054
2289
|
meta: { model: model.name, field: field.name }
|
|
2055
2290
|
});
|
|
2056
2291
|
}
|
|
2057
2292
|
if (field.isId || field.isUnique || model.primaryKey.some((candidate) => candidate.name === field.name)) {
|
|
2058
|
-
throw
|
|
2293
|
+
throw validationError4({
|
|
2059
2294
|
message: `dbNow() on ${model.name}.${field.name} \u2014 a key field cannot take a database-computed value; the client could not re-address the written row`,
|
|
2060
2295
|
meta: { model: model.name, field: field.name }
|
|
2061
2296
|
});
|
|
@@ -2065,7 +2300,7 @@ function compileDbNowWrite(params) {
|
|
|
2065
2300
|
function compileDbNowFilter(params) {
|
|
2066
2301
|
const { model, field, value } = params;
|
|
2067
2302
|
if (field.scalarType !== "DateTime" || field.isList) {
|
|
2068
|
-
throw
|
|
2303
|
+
throw validationError4({
|
|
2069
2304
|
message: `dbNow() compares against a DateTime field \u2014 ${model.name}.${field.name} is ${String(field.enumName ?? field.scalarType)}${field.isList ? "[]" : ""}`,
|
|
2070
2305
|
meta: { model: model.name, field: field.name }
|
|
2071
2306
|
});
|
|
@@ -2075,7 +2310,7 @@ function compileDbNowFilter(params) {
|
|
|
2075
2310
|
function requireObject(params) {
|
|
2076
2311
|
const { value, what, model } = params;
|
|
2077
2312
|
if (!isPlainObject3(value)) {
|
|
2078
|
-
throw
|
|
2313
|
+
throw validationError4({
|
|
2079
2314
|
message: `${what} must be an object on model "${model.name}"`,
|
|
2080
2315
|
meta: { model: model.name, argument: what }
|
|
2081
2316
|
});
|
|
@@ -2085,13 +2320,13 @@ function requireObject(params) {
|
|
|
2085
2320
|
function toNonNegativeInteger2(params) {
|
|
2086
2321
|
const { value, what, model } = params;
|
|
2087
2322
|
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
|
2088
|
-
throw
|
|
2323
|
+
throw validationError4({
|
|
2089
2324
|
message: `${what} must be an integer on model "${model.name}", got ${String(value)}`,
|
|
2090
2325
|
meta: { model: model.name, argument: what, value }
|
|
2091
2326
|
});
|
|
2092
2327
|
}
|
|
2093
2328
|
if (value < 0) {
|
|
2094
|
-
throw
|
|
2329
|
+
throw validationError4({
|
|
2095
2330
|
message: `negative ${what} is not supported here \u2014 only findMany's take may be negative (backwards pagination)`,
|
|
2096
2331
|
meta: { model: model.name, argument: what, value }
|
|
2097
2332
|
});
|
|
@@ -2105,7 +2340,7 @@ function assertArgs(params) {
|
|
|
2105
2340
|
if (args[key] === undefined)
|
|
2106
2341
|
continue;
|
|
2107
2342
|
if (!allowed.includes(key)) {
|
|
2108
|
-
throw
|
|
2343
|
+
throw validationError4({
|
|
2109
2344
|
message: `unsupported argument "${key}" for ${model.clientName}.${method}() \u2014 supported: ${allowed.join(", ")}`,
|
|
2110
2345
|
meta: { model: model.name, method, argument: key, allowed }
|
|
2111
2346
|
});
|
|
@@ -2116,7 +2351,7 @@ function assertListFilterable(params) {
|
|
|
2116
2351
|
const { dialect, model, field, operator } = params;
|
|
2117
2352
|
if (dialect.capabilities.scalarArrays)
|
|
2118
2353
|
return;
|
|
2119
|
-
throw
|
|
2354
|
+
throw unsupportedCapability4({
|
|
2120
2355
|
dialect: dialect.name,
|
|
2121
2356
|
feature: `scalar list filter "${operator}" on ${model.name}.${field.name}`,
|
|
2122
2357
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -2126,7 +2361,7 @@ function assertListWritable(params) {
|
|
|
2126
2361
|
const { dialect, model, field } = params;
|
|
2127
2362
|
if (!field.isList || dialect.capabilities.scalarArrays)
|
|
2128
2363
|
return;
|
|
2129
|
-
throw
|
|
2364
|
+
throw unsupportedCapability4({
|
|
2130
2365
|
dialect: dialect.name,
|
|
2131
2366
|
feature: `writing the scalar list ${model.name}.${field.name}`,
|
|
2132
2367
|
hint: "only postgres has native array columns \u2014 model the list as a related table instead"
|
|
@@ -2135,7 +2370,7 @@ function assertListWritable(params) {
|
|
|
2135
2370
|
function compileStringPattern(params) {
|
|
2136
2371
|
const { operator, operand, field, model } = params;
|
|
2137
2372
|
if (typeof operand !== "string") {
|
|
2138
|
-
throw
|
|
2373
|
+
throw validationError4({
|
|
2139
2374
|
message: `"${operator}" on ${model.name}.${field.name} expects a string`,
|
|
2140
2375
|
meta: { model: model.name, field: field.name, operator }
|
|
2141
2376
|
});
|
|
@@ -2162,7 +2397,7 @@ function compileListFieldFilter(params) {
|
|
|
2162
2397
|
break;
|
|
2163
2398
|
}
|
|
2164
2399
|
if (!Array.isArray(operand)) {
|
|
2165
|
-
throw
|
|
2400
|
+
throw validationError4({
|
|
2166
2401
|
message: `"equals" on the list ${model.name}.${field.name} expects an array or null`,
|
|
2167
2402
|
meta: { model: model.name, field: field.name, operator }
|
|
2168
2403
|
});
|
|
@@ -2186,7 +2421,7 @@ function compileListFieldFilter(params) {
|
|
|
2186
2421
|
case "hasEvery":
|
|
2187
2422
|
case "hasSome": {
|
|
2188
2423
|
if (!Array.isArray(operand)) {
|
|
2189
|
-
throw
|
|
2424
|
+
throw validationError4({
|
|
2190
2425
|
message: `"${operator}" on ${model.name}.${field.name} expects an array`,
|
|
2191
2426
|
meta: { model: model.name, field: field.name, operator }
|
|
2192
2427
|
});
|
|
@@ -2197,7 +2432,7 @@ function compileListFieldFilter(params) {
|
|
|
2197
2432
|
}
|
|
2198
2433
|
case "isEmpty": {
|
|
2199
2434
|
if (typeof operand !== "boolean") {
|
|
2200
|
-
throw
|
|
2435
|
+
throw validationError4({
|
|
2201
2436
|
message: `"isEmpty" on ${model.name}.${field.name} expects a boolean`,
|
|
2202
2437
|
meta: { model: model.name, field: field.name, operator }
|
|
2203
2438
|
});
|
|
@@ -2206,7 +2441,7 @@ function compileListFieldFilter(params) {
|
|
|
2206
2441
|
break;
|
|
2207
2442
|
}
|
|
2208
2443
|
default:
|
|
2209
|
-
throw
|
|
2444
|
+
throw validationError4({
|
|
2210
2445
|
message: `unknown list filter "${operator}" on ${model.name}.${field.name} \u2014 lists take equals, has, hasEvery, hasSome, isEmpty`,
|
|
2211
2446
|
meta: { model: model.name, field: field.name, operator }
|
|
2212
2447
|
});
|
|
@@ -2225,7 +2460,7 @@ function compileJsonFieldFilter(params) {
|
|
|
2225
2460
|
const rawPath = filter.path;
|
|
2226
2461
|
if (rawPath !== undefined) {
|
|
2227
2462
|
if (!Array.isArray(rawPath) || rawPath.some((seg) => typeof seg !== "string" && typeof seg !== "number")) {
|
|
2228
|
-
throw
|
|
2463
|
+
throw validationError4({
|
|
2229
2464
|
message: `"path" on ${model.name}.${field.name} must be an array of keys (strings) and indexes (numbers)`,
|
|
2230
2465
|
meta: { model: model.name, field: field.name }
|
|
2231
2466
|
});
|
|
@@ -2234,7 +2469,7 @@ function compileJsonFieldFilter(params) {
|
|
|
2234
2469
|
const path = rawPath ?? [];
|
|
2235
2470
|
const mode = filter.mode;
|
|
2236
2471
|
if (mode !== undefined && mode !== "insensitive" && mode !== "default") {
|
|
2237
|
-
throw
|
|
2472
|
+
throw validationError4({
|
|
2238
2473
|
message: `mode must be "insensitive" or "default" on ${model.name}.${field.name}`,
|
|
2239
2474
|
meta: { model: model.name, field: field.name, mode }
|
|
2240
2475
|
});
|
|
@@ -2243,7 +2478,7 @@ function compileJsonFieldFilter(params) {
|
|
|
2243
2478
|
const requireOp = (op) => {
|
|
2244
2479
|
if (dialect.supportedJsonOps.includes(op))
|
|
2245
2480
|
return;
|
|
2246
|
-
throw
|
|
2481
|
+
throw unsupportedCapability4({
|
|
2247
2482
|
dialect: dialect.name,
|
|
2248
2483
|
feature: `json filter "${op}" on ${model.name}.${field.name}`,
|
|
2249
2484
|
hint: `this dialect can express: ${dialect.supportedJsonOps.join(", ")}`
|
|
@@ -2256,7 +2491,7 @@ function compileJsonFieldFilter(params) {
|
|
|
2256
2491
|
if (operator === "equals" || operator === "not") {
|
|
2257
2492
|
if (operand === null) {
|
|
2258
2493
|
if (path.length > 0) {
|
|
2259
|
-
throw
|
|
2494
|
+
throw validationError4({
|
|
2260
2495
|
message: `"${operator}: null" with a path on ${model.name}.${field.name} is ambiguous (SQL NULL vs JSON null) \u2014 omit the path to test column NULL`,
|
|
2261
2496
|
meta: { model: model.name, field: field.name, operator }
|
|
2262
2497
|
});
|
|
@@ -2277,7 +2512,7 @@ function compileJsonFieldFilter(params) {
|
|
|
2277
2512
|
}
|
|
2278
2513
|
if (JSON_STRING_OPS.includes(operator)) {
|
|
2279
2514
|
if (typeof operand !== "string") {
|
|
2280
|
-
throw
|
|
2515
|
+
throw validationError4({
|
|
2281
2516
|
message: `"${operator}" on ${model.name}.${field.name} expects a string`,
|
|
2282
2517
|
meta: { model: model.name, field: field.name, operator }
|
|
2283
2518
|
});
|
|
@@ -2300,13 +2535,13 @@ function compileJsonFieldFilter(params) {
|
|
|
2300
2535
|
conditions.push({ kind: "jsonFilter", column, path, op, value: operand });
|
|
2301
2536
|
continue;
|
|
2302
2537
|
}
|
|
2303
|
-
throw
|
|
2538
|
+
throw validationError4({
|
|
2304
2539
|
message: `unknown json filter "${operator}" on ${model.name}.${field.name} \u2014 supported: path, mode, equals, not, ${[...JSON_STRING_OPS, ...JSON_ARRAY_OPS].join(", ")}`,
|
|
2305
2540
|
meta: { model: model.name, field: field.name, operator }
|
|
2306
2541
|
});
|
|
2307
2542
|
}
|
|
2308
2543
|
if (conditions.length === 0) {
|
|
2309
|
-
throw
|
|
2544
|
+
throw validationError4({
|
|
2310
2545
|
message: `json filter on ${model.name}.${field.name} needs at least one operator`,
|
|
2311
2546
|
meta: { model: model.name, field: field.name }
|
|
2312
2547
|
});
|
|
@@ -2329,14 +2564,14 @@ function compileOperatorFilter(params) {
|
|
|
2329
2564
|
const mode = filter.mode;
|
|
2330
2565
|
if (mode !== undefined) {
|
|
2331
2566
|
if (mode !== "insensitive" && mode !== "strict" && mode !== "default") {
|
|
2332
|
-
throw
|
|
2567
|
+
throw validationError4({
|
|
2333
2568
|
message: `mode must be "insensitive", "strict" or "default" on ${model.name}.${field.name}`,
|
|
2334
2569
|
meta: { model: model.name, field: field.name, mode }
|
|
2335
2570
|
});
|
|
2336
2571
|
}
|
|
2337
2572
|
const others = entries.filter(([operator]) => operator !== "mode");
|
|
2338
2573
|
if (others.length === 0 || others.some(([operator]) => !modeCompanions.includes(operator))) {
|
|
2339
|
-
throw
|
|
2574
|
+
throw validationError4({
|
|
2340
2575
|
message: `mode is only supported with ${modeCompanions.join("/")} on ${model.name}.${field.name}`,
|
|
2341
2576
|
meta: { model: model.name, field: field.name }
|
|
2342
2577
|
});
|
|
@@ -2347,7 +2582,7 @@ function compileOperatorFilter(params) {
|
|
|
2347
2582
|
const ciCollated = (params2) => params2.dialect.name === "mysql" && params2.field.collation?.toLowerCase().endsWith("_ci") === true;
|
|
2348
2583
|
const requireStringOperand = (operator, operand) => {
|
|
2349
2584
|
if (typeof operand !== "string") {
|
|
2350
|
-
throw
|
|
2585
|
+
throw validationError4({
|
|
2351
2586
|
message: `mode: "${String(mode)}" with ${operator} on ${model.name}.${field.name} expects a string`,
|
|
2352
2587
|
meta: { model: model.name, field: field.name }
|
|
2353
2588
|
});
|
|
@@ -2421,14 +2656,14 @@ function compileOperatorFilter(params) {
|
|
|
2421
2656
|
case "in":
|
|
2422
2657
|
case "notIn": {
|
|
2423
2658
|
if (!Array.isArray(operand)) {
|
|
2424
|
-
throw
|
|
2659
|
+
throw validationError4({
|
|
2425
2660
|
message: `"${operator}" on ${model.name}.${field.name} expects an array`,
|
|
2426
2661
|
meta: { model: model.name, field: field.name, operator }
|
|
2427
2662
|
});
|
|
2428
2663
|
}
|
|
2429
2664
|
for (const item of operand) {
|
|
2430
2665
|
if (isDbNow(item)) {
|
|
2431
|
-
throw
|
|
2666
|
+
throw validationError4({
|
|
2432
2667
|
message: `dbNow() cannot ride an "${operator}" list on ${model.name}.${field.name} \u2014 compare with lt/lte/gt/gte/equals instead`,
|
|
2433
2668
|
meta: { model: model.name, field: field.name, operator }
|
|
2434
2669
|
});
|
|
@@ -2486,7 +2721,7 @@ function compileOperatorFilter(params) {
|
|
|
2486
2721
|
}));
|
|
2487
2722
|
break;
|
|
2488
2723
|
}
|
|
2489
|
-
throw
|
|
2724
|
+
throw validationError4({
|
|
2490
2725
|
message: `unknown filter operator "${operator}" on ${model.name}.${field.name}`,
|
|
2491
2726
|
meta: { model: model.name, field: field.name, operator }
|
|
2492
2727
|
});
|
|
@@ -2512,7 +2747,7 @@ function compileFieldFilter(params) {
|
|
|
2512
2747
|
}
|
|
2513
2748
|
if (Array.isArray(value)) {
|
|
2514
2749
|
if (!field.isList) {
|
|
2515
|
-
throw
|
|
2750
|
+
throw validationError4({
|
|
2516
2751
|
message: `array filter value on ${model.name}.${field.name} \u2014 only list fields compare against arrays`,
|
|
2517
2752
|
meta: { model: model.name, field: field.name }
|
|
2518
2753
|
});
|
|
@@ -2523,7 +2758,7 @@ function compileFieldFilter(params) {
|
|
|
2523
2758
|
if (isPlainObject3(value)) {
|
|
2524
2759
|
return compileOperatorFilter({ ctx, model, field, filter: value, qualify });
|
|
2525
2760
|
}
|
|
2526
|
-
throw
|
|
2761
|
+
throw validationError4({
|
|
2527
2762
|
message: `unsupported filter value for ${model.name}.${field.name}`,
|
|
2528
2763
|
meta: { model: model.name, field: field.name }
|
|
2529
2764
|
});
|
|
@@ -2607,7 +2842,7 @@ function buildRelationExists(params) {
|
|
|
2607
2842
|
function requirePredicateObject(params) {
|
|
2608
2843
|
const { model, relation, operator, operand } = params;
|
|
2609
2844
|
if (!isPlainObject3(operand)) {
|
|
2610
|
-
throw
|
|
2845
|
+
throw validationError4({
|
|
2611
2846
|
message: `"${operator}" on relation ${model.name}.${relation.name} expects a where object`,
|
|
2612
2847
|
meta: { model: model.name, relation: relation.name, operator }
|
|
2613
2848
|
});
|
|
@@ -2620,7 +2855,7 @@ function compileRelationFilter(params) {
|
|
|
2620
2855
|
const outer = qualify ?? model.tableName;
|
|
2621
2856
|
if (relation.isList) {
|
|
2622
2857
|
if (!isPlainObject3(value)) {
|
|
2623
|
-
throw
|
|
2858
|
+
throw validationError4({
|
|
2624
2859
|
message: `filter on relation ${model.name}.${relation.name} must be { some | every | none: \u2026 }`,
|
|
2625
2860
|
meta: { model: model.name, relation: relation.name }
|
|
2626
2861
|
});
|
|
@@ -2658,14 +2893,14 @@ function compileRelationFilter(params) {
|
|
|
2658
2893
|
}));
|
|
2659
2894
|
break;
|
|
2660
2895
|
default:
|
|
2661
|
-
throw
|
|
2896
|
+
throw validationError4({
|
|
2662
2897
|
message: `unknown relation filter "${operator}" on ${model.name}.${relation.name} \u2014 to-many relations take some/every/none`,
|
|
2663
2898
|
meta: { model: model.name, relation: relation.name, operator }
|
|
2664
2899
|
});
|
|
2665
2900
|
}
|
|
2666
2901
|
}
|
|
2667
2902
|
if (conditions2.length === 0) {
|
|
2668
|
-
throw
|
|
2903
|
+
throw validationError4({
|
|
2669
2904
|
message: `filter on relation ${model.name}.${relation.name} must contain some, every or none`,
|
|
2670
2905
|
meta: { model: model.name, relation: relation.name }
|
|
2671
2906
|
});
|
|
@@ -2692,7 +2927,7 @@ function compileRelationFilter(params) {
|
|
|
2692
2927
|
isNotArm = value[key];
|
|
2693
2928
|
hasIsNot = true;
|
|
2694
2929
|
} else {
|
|
2695
|
-
throw
|
|
2930
|
+
throw validationError4({
|
|
2696
2931
|
message: `relation filter on ${model.name}.${relation.name} cannot mix "${key}" with is/isNot`,
|
|
2697
2932
|
meta: { model: model.name, relation: relation.name, operator: key }
|
|
2698
2933
|
});
|
|
@@ -2702,7 +2937,7 @@ function compileRelationFilter(params) {
|
|
|
2702
2937
|
isArm = value;
|
|
2703
2938
|
hasIs = true;
|
|
2704
2939
|
} else {
|
|
2705
|
-
throw
|
|
2940
|
+
throw validationError4({
|
|
2706
2941
|
message: `filter on relation ${model.name}.${relation.name} must be a where object, { is | isNot: \u2026 } or null`,
|
|
2707
2942
|
meta: { model: model.name, relation: relation.name }
|
|
2708
2943
|
});
|
|
@@ -2756,6 +2991,12 @@ function compileWhereInternal(params) {
|
|
|
2756
2991
|
for (const [key, value] of Object.entries(where)) {
|
|
2757
2992
|
if (value === undefined)
|
|
2758
2993
|
continue;
|
|
2994
|
+
if (key === "$raw") {
|
|
2995
|
+
if (!isSqlFragment(value))
|
|
2996
|
+
throw validationError4({ message: "$raw requires a constructed SQL fragment" });
|
|
2997
|
+
operands.push({ kind: "composed", fragment: value });
|
|
2998
|
+
continue;
|
|
2999
|
+
}
|
|
2759
3000
|
if (key === "AND") {
|
|
2760
3001
|
operands.push({ kind: "and", operands: compileWhereGroup({ ctx, model, value, key, qualify }) });
|
|
2761
3002
|
continue;
|
|
@@ -2797,20 +3038,20 @@ function compileOrderTerm(params) {
|
|
|
2797
3038
|
if (isPlainObject3(spec)) {
|
|
2798
3039
|
const { sort, nulls } = spec;
|
|
2799
3040
|
if (sort !== "asc" && sort !== "desc") {
|
|
2800
|
-
throw
|
|
3041
|
+
throw validationError4({
|
|
2801
3042
|
message: `orderBy sort must be "asc" or "desc" on ${model.name}.${field.name}`,
|
|
2802
3043
|
meta: { model: model.name, field: field.name, sort }
|
|
2803
3044
|
});
|
|
2804
3045
|
}
|
|
2805
3046
|
if (nulls !== undefined && nulls !== "first" && nulls !== "last") {
|
|
2806
|
-
throw
|
|
3047
|
+
throw validationError4({
|
|
2807
3048
|
message: `orderBy nulls must be "first" or "last" on ${model.name}.${field.name}`,
|
|
2808
3049
|
meta: { model: model.name, field: field.name, nulls }
|
|
2809
3050
|
});
|
|
2810
3051
|
}
|
|
2811
3052
|
return nulls === undefined ? { column, direction: sort } : { column, direction: sort, nulls };
|
|
2812
3053
|
}
|
|
2813
|
-
throw
|
|
3054
|
+
throw validationError4({
|
|
2814
3055
|
message: `orderBy on ${model.name}.${field.name} must be "asc", "desc" or { sort, nulls }`,
|
|
2815
3056
|
meta: { model: model.name, field: field.name }
|
|
2816
3057
|
});
|
|
@@ -2846,7 +3087,7 @@ function compileRelationOrderTerms(params) {
|
|
|
2846
3087
|
const requireDirection = (value, what) => {
|
|
2847
3088
|
if (value === "asc" || value === "desc")
|
|
2848
3089
|
return value;
|
|
2849
|
-
throw
|
|
3090
|
+
throw validationError4({
|
|
2850
3091
|
message: `${what} on ${model.name}.${relation.name} must be "asc" or "desc" \u2014 nulls placement is not supported on relation orderBy`,
|
|
2851
3092
|
meta: { model: model.name, relation: relation.name }
|
|
2852
3093
|
});
|
|
@@ -2856,7 +3097,7 @@ function compileRelationOrderTerms(params) {
|
|
|
2856
3097
|
continue;
|
|
2857
3098
|
if (key === "_count") {
|
|
2858
3099
|
if (!isList) {
|
|
2859
|
-
throw
|
|
3100
|
+
throw validationError4({
|
|
2860
3101
|
message: `orderBy.${relation.name}._count on model "${model.name}" needs a list relation \u2014 "${relation.name}" is to-one`,
|
|
2861
3102
|
meta: { model: model.name, relation: relation.name }
|
|
2862
3103
|
});
|
|
@@ -2888,7 +3129,7 @@ function compileRelationOrderTerms(params) {
|
|
|
2888
3129
|
continue;
|
|
2889
3130
|
}
|
|
2890
3131
|
if (isList) {
|
|
2891
|
-
throw
|
|
3132
|
+
throw validationError4({
|
|
2892
3133
|
message: `orderBy.${relation.name}.${key} on model "${model.name}" \u2014 a to-many relation orders by _count only (a scalar across many rows is ambiguous)`,
|
|
2893
3134
|
meta: { model: model.name, relation: relation.name, field: key }
|
|
2894
3135
|
});
|
|
@@ -2946,7 +3187,7 @@ function compileSelect(params) {
|
|
|
2946
3187
|
if (model.computed !== null && model.computed.has(name))
|
|
2947
3188
|
continue;
|
|
2948
3189
|
if (value !== true) {
|
|
2949
|
-
throw
|
|
3190
|
+
throw validationError4({
|
|
2950
3191
|
message: `select.${name} must be true or false on model "${model.name}"`,
|
|
2951
3192
|
meta: { model: model.name, field: name }
|
|
2952
3193
|
});
|
|
@@ -2954,7 +3195,7 @@ function compileSelect(params) {
|
|
|
2954
3195
|
names.push(getFieldMeta({ model, field: name, context: "select" }).name);
|
|
2955
3196
|
}
|
|
2956
3197
|
if (names.length === 0 && !allowEmpty) {
|
|
2957
|
-
throw
|
|
3198
|
+
throw validationError4({
|
|
2958
3199
|
message: `select on model "${model.name}" must include at least one field`,
|
|
2959
3200
|
meta: { model: model.name }
|
|
2960
3201
|
});
|
|
@@ -2972,7 +3213,7 @@ function resolveComputedSelection(params) {
|
|
|
2972
3213
|
if (!specs.has(name) || value === undefined || value === false)
|
|
2973
3214
|
continue;
|
|
2974
3215
|
if (value !== true) {
|
|
2975
|
-
throw
|
|
3216
|
+
throw validationError4({
|
|
2976
3217
|
message: `select.${name} must be true or false on model "${model.name}"`,
|
|
2977
3218
|
meta: { model: model.name, field: name }
|
|
2978
3219
|
});
|
|
@@ -3040,7 +3281,7 @@ function expandCompoundWhere(params) {
|
|
|
3040
3281
|
for (const field of fields) {
|
|
3041
3282
|
const member = spec[field.name];
|
|
3042
3283
|
if (member === undefined) {
|
|
3043
|
-
throw
|
|
3284
|
+
throw validationError4({
|
|
3044
3285
|
message: `${what}.${key} on model "${model.name}" needs every member field (${fields.map((entry) => entry.name).join(", ")}) \u2014 "${field.name}" is missing`,
|
|
3045
3286
|
meta: { model: model.name, key, field: field.name }
|
|
3046
3287
|
});
|
|
@@ -3073,7 +3314,7 @@ function assertUniqueWhere(params) {
|
|
|
3073
3314
|
...model.singleIndexUniques.map((single) => single.fields[0].name),
|
|
3074
3315
|
...model.compoundUniques.keys()
|
|
3075
3316
|
];
|
|
3076
|
-
throw
|
|
3317
|
+
throw validationError4({
|
|
3077
3318
|
message: `${model.clientName}.${method}() needs a unique where \u2014 one of: ${candidates.join(", ")}`,
|
|
3078
3319
|
meta: { model: model.name, method, candidates }
|
|
3079
3320
|
});
|
|
@@ -3203,7 +3444,7 @@ function compileCreateRow(params) {
|
|
|
3203
3444
|
continue;
|
|
3204
3445
|
}
|
|
3205
3446
|
if (Array.isArray(value) && value.some((item) => isDbNow(item))) {
|
|
3206
|
-
throw
|
|
3447
|
+
throw validationError4({
|
|
3207
3448
|
message: `dbNow() cannot be a list element (${model.name}.${entry.field.name})`,
|
|
3208
3449
|
meta: { model: model.name, field: entry.field.name }
|
|
3209
3450
|
});
|
|
@@ -3246,14 +3487,14 @@ function orderedColumns(params) {
|
|
|
3246
3487
|
function compileAtomicOperation(params) {
|
|
3247
3488
|
const { model, field, operator, operand } = params;
|
|
3248
3489
|
if (field.scalarType === null || !NUMERIC_TYPES.includes(field.scalarType)) {
|
|
3249
|
-
throw
|
|
3490
|
+
throw validationError4({
|
|
3250
3491
|
message: `"${operator}" needs a numeric field, but ${model.name}.${field.name} is ${String(field.enumName ?? field.scalarType)}`,
|
|
3251
3492
|
meta: { model: model.name, field: field.name, operator }
|
|
3252
3493
|
});
|
|
3253
3494
|
}
|
|
3254
3495
|
const numeric = typeof operand === "bigint" ? Number(operand) : operand;
|
|
3255
3496
|
if (typeof numeric !== "number" || !Number.isFinite(numeric)) {
|
|
3256
|
-
throw
|
|
3497
|
+
throw validationError4({
|
|
3257
3498
|
message: `"${operator}" on ${model.name}.${field.name} expects a number`,
|
|
3258
3499
|
meta: { model: model.name, field: field.name, operator, operand }
|
|
3259
3500
|
});
|
|
@@ -3284,7 +3525,7 @@ function compileUpdateSet(params) {
|
|
|
3284
3525
|
continue;
|
|
3285
3526
|
}
|
|
3286
3527
|
if (Array.isArray(value) && value.some((item) => isDbNow(item))) {
|
|
3287
|
-
throw
|
|
3528
|
+
throw validationError4({
|
|
3288
3529
|
message: `dbNow() cannot be a list element (${model.name}.${field.name})`,
|
|
3289
3530
|
meta: { model: model.name, field: field.name }
|
|
3290
3531
|
});
|
|
@@ -3292,7 +3533,7 @@ function compileUpdateSet(params) {
|
|
|
3292
3533
|
if (isPlainObject3(value) && field.scalarType !== "Json") {
|
|
3293
3534
|
const entries = Object.entries(value).filter(([, operand2]) => operand2 !== undefined);
|
|
3294
3535
|
if (entries.length !== 1) {
|
|
3295
|
-
throw
|
|
3536
|
+
throw validationError4({
|
|
3296
3537
|
message: `update of ${model.name}.${field.name} must use exactly one operation`,
|
|
3297
3538
|
meta: { model: model.name, field: field.name, operations: entries.map(([key]) => key) }
|
|
3298
3539
|
});
|
|
@@ -3308,7 +3549,7 @@ function compileUpdateSet(params) {
|
|
|
3308
3549
|
}
|
|
3309
3550
|
const atomic = ATOMIC_OPERATIONS[operator];
|
|
3310
3551
|
if (atomic === undefined) {
|
|
3311
|
-
throw
|
|
3552
|
+
throw validationError4({
|
|
3312
3553
|
message: `unknown update operation "${operator}" on ${model.name}.${field.name}`,
|
|
3313
3554
|
meta: { model: model.name, field: field.name, operator }
|
|
3314
3555
|
});
|
|
@@ -3326,7 +3567,7 @@ function compileUpdateSet(params) {
|
|
|
3326
3567
|
}
|
|
3327
3568
|
}
|
|
3328
3569
|
if (Object.keys(set).length === 0) {
|
|
3329
|
-
throw
|
|
3570
|
+
throw validationError4({
|
|
3330
3571
|
message: `update of model "${model.name}" must change at least one field`,
|
|
3331
3572
|
meta: { model: model.name }
|
|
3332
3573
|
});
|
|
@@ -3337,7 +3578,7 @@ function compileCreateRows(params) {
|
|
|
3337
3578
|
const { dialect, model, data } = params;
|
|
3338
3579
|
const items = Array.isArray(data) ? data : [data];
|
|
3339
3580
|
if (items.length === 0) {
|
|
3340
|
-
throw
|
|
3581
|
+
throw validationError4({
|
|
3341
3582
|
message: `${params.method ?? "createMany"} on model "${model.name}" needs at least one row`,
|
|
3342
3583
|
meta: { model: model.name }
|
|
3343
3584
|
});
|
|
@@ -3388,7 +3629,7 @@ function compileCreateRows(params) {
|
|
|
3388
3629
|
function compileTake(params) {
|
|
3389
3630
|
const { value, model } = params;
|
|
3390
3631
|
if (typeof value !== "number" || !Number.isSafeInteger(value)) {
|
|
3391
|
-
throw
|
|
3632
|
+
throw validationError4({
|
|
3392
3633
|
message: `take must be an integer on model "${model.name}", got ${String(value)}`,
|
|
3393
3634
|
meta: { model: model.name, argument: "take", value }
|
|
3394
3635
|
});
|
|
@@ -3404,7 +3645,7 @@ function parseCursor(params) {
|
|
|
3404
3645
|
});
|
|
3405
3646
|
const entries = Object.entries(spec).filter(([, value]) => value !== undefined);
|
|
3406
3647
|
if (entries.length === 0) {
|
|
3407
|
-
throw
|
|
3648
|
+
throw validationError4({
|
|
3408
3649
|
message: `cursor on model "${model.name}" needs a unique field value`,
|
|
3409
3650
|
meta: { model: model.name }
|
|
3410
3651
|
});
|
|
@@ -3414,7 +3655,7 @@ function parseCursor(params) {
|
|
|
3414
3655
|
for (const [name, value] of entries) {
|
|
3415
3656
|
const field = getFieldMeta({ model, field: name, context: "cursor" });
|
|
3416
3657
|
if (!isDirectValue(value)) {
|
|
3417
|
-
throw
|
|
3658
|
+
throw validationError4({
|
|
3418
3659
|
message: `cursor.${name} on model "${model.name}" must be a direct value`,
|
|
3419
3660
|
meta: { model: model.name, field: name }
|
|
3420
3661
|
});
|
|
@@ -3426,7 +3667,7 @@ function parseCursor(params) {
|
|
|
3426
3667
|
const field = fields[0];
|
|
3427
3668
|
const isSingleIndexUnique = model.singleIndexUniques.some((single) => single.fields[0].name === field.name);
|
|
3428
3669
|
if (!field.isId && !field.isUnique && !isSingleIndexUnique) {
|
|
3429
|
-
throw
|
|
3670
|
+
throw validationError4({
|
|
3430
3671
|
message: `cursor field ${model.name}.${field.name} must be @id or @unique \u2014 a non-unique cursor cannot identify a position`,
|
|
3431
3672
|
meta: { model: model.name, field: field.name }
|
|
3432
3673
|
});
|
|
@@ -3440,7 +3681,7 @@ function parseCursor(params) {
|
|
|
3440
3681
|
model.primaryKey.map((field) => field.name).join(" + ") || "none",
|
|
3441
3682
|
...model.compoundUniques.keys()
|
|
3442
3683
|
];
|
|
3443
|
-
throw
|
|
3684
|
+
throw validationError4({
|
|
3444
3685
|
message: `multi-field cursor on model "${model.name}" must be exactly the composite primary key or a composite @@unique group (${candidates.join(", ")})`,
|
|
3445
3686
|
meta: { model: model.name, fields: [...names] }
|
|
3446
3687
|
});
|
|
@@ -3451,7 +3692,7 @@ function parseCursor(params) {
|
|
|
3451
3692
|
function keysetFieldOfTerm(params) {
|
|
3452
3693
|
const { dialect, model, term } = params;
|
|
3453
3694
|
if (!("column" in term)) {
|
|
3454
|
-
throw
|
|
3695
|
+
throw unsupportedCapability4({
|
|
3455
3696
|
dialect: dialect.name,
|
|
3456
3697
|
feature: `keyset pagination over a relation or aggregate orderBy on model "${model.name}"`,
|
|
3457
3698
|
hint: "order by scalar columns plus a unique tie-breaker; a correlated subquery value is not on the row, so no position can describe it"
|
|
@@ -3459,7 +3700,7 @@ function keysetFieldOfTerm(params) {
|
|
|
3459
3700
|
}
|
|
3460
3701
|
const field = model.fieldByColumn.get(term.column.name);
|
|
3461
3702
|
if (field === undefined) {
|
|
3462
|
-
throw
|
|
3703
|
+
throw unsupportedCapability4({
|
|
3463
3704
|
dialect: dialect.name,
|
|
3464
3705
|
feature: `keyset pagination over a computed order key on model "${model.name}"`,
|
|
3465
3706
|
hint: "order by scalar columns plus a unique tie-breaker"
|
|
@@ -3488,7 +3729,7 @@ function parseKeysetOptions(params) {
|
|
|
3488
3729
|
const spec = requireObject({ value, what: "keyset", model });
|
|
3489
3730
|
for (const key of Object.keys(spec)) {
|
|
3490
3731
|
if (key !== "timestampPrecision") {
|
|
3491
|
-
throw
|
|
3732
|
+
throw validationError4({
|
|
3492
3733
|
message: `unknown keyset option "${key}" on model "${model.name}" \u2014 only timestampPrecision is supported`,
|
|
3493
3734
|
meta: { model: model.name, option: key, reason: "keyset-option-unknown" }
|
|
3494
3735
|
});
|
|
@@ -3498,7 +3739,7 @@ function parseKeysetOptions(params) {
|
|
|
3498
3739
|
if (precision === undefined)
|
|
3499
3740
|
return {};
|
|
3500
3741
|
if (precision !== "milliseconds") {
|
|
3501
|
-
throw
|
|
3742
|
+
throw validationError4({
|
|
3502
3743
|
message: `keyset.timestampPrecision on model "${model.name}" must be "milliseconds"`,
|
|
3503
3744
|
meta: { model: model.name, value: precision, reason: "keyset-option-invalid" }
|
|
3504
3745
|
});
|
|
@@ -3508,25 +3749,25 @@ function parseKeysetOptions(params) {
|
|
|
3508
3749
|
function buildKeysetBound(params) {
|
|
3509
3750
|
const { dialect, model, args, userOrder, sqlOrder, colRefOf } = params;
|
|
3510
3751
|
if (args.cursor !== undefined) {
|
|
3511
|
-
throw
|
|
3752
|
+
throw validationError4({
|
|
3512
3753
|
message: `after and cursor cannot combine on model "${model.name}" \u2014 a keyset position and a unique-key cursor are two different pagination forms`,
|
|
3513
3754
|
meta: { model: model.name, reason: "keyset-cursor-conflict" }
|
|
3514
3755
|
});
|
|
3515
3756
|
}
|
|
3516
3757
|
if (args.distinct !== undefined) {
|
|
3517
|
-
throw
|
|
3758
|
+
throw validationError4({
|
|
3518
3759
|
message: `after cannot combine with distinct on model "${model.name}" \u2014 deduplication reorders the result, so a position in the requested order does not address it`,
|
|
3519
3760
|
meta: { model: model.name, reason: "keyset-distinct-unsupported" }
|
|
3520
3761
|
});
|
|
3521
3762
|
}
|
|
3522
3763
|
if (args.skip !== undefined) {
|
|
3523
|
-
throw
|
|
3764
|
+
throw validationError4({
|
|
3524
3765
|
message: `after cannot combine with skip on model "${model.name}" \u2014 keyset pagination has no offset, and an offset on top of it is not keyset pagination`,
|
|
3525
3766
|
meta: { model: model.name, reason: "keyset-skip-unsupported" }
|
|
3526
3767
|
});
|
|
3527
3768
|
}
|
|
3528
3769
|
if (userOrder.length === 0) {
|
|
3529
|
-
throw
|
|
3770
|
+
throw validationError4({
|
|
3530
3771
|
message: `after on model "${model.name}" needs an orderBy \u2014 a position has no meaning without an order`,
|
|
3531
3772
|
meta: { model: model.name, reason: "keyset-order-required" }
|
|
3532
3773
|
});
|
|
@@ -3621,7 +3862,7 @@ function normalizeDistinct(params) {
|
|
|
3621
3862
|
const { model, distinct } = params;
|
|
3622
3863
|
const names = typeof distinct === "string" ? [distinct] : distinct;
|
|
3623
3864
|
if (!Array.isArray(names) || names.length === 0 || names.some((name) => typeof name !== "string")) {
|
|
3624
|
-
throw
|
|
3865
|
+
throw validationError4({
|
|
3625
3866
|
message: `distinct on model "${model.name}" must be a field name or a non-empty array of field names`,
|
|
3626
3867
|
meta: { model: model.name }
|
|
3627
3868
|
});
|
|
@@ -3679,7 +3920,7 @@ function buildFindShape(params) {
|
|
|
3679
3920
|
baseOrder = model.primaryKey.map((field) => ({ column: colRefOf(field), direction: "asc" }));
|
|
3680
3921
|
}
|
|
3681
3922
|
if (backward && baseOrder.length === 0) {
|
|
3682
|
-
throw
|
|
3923
|
+
throw validationError4({
|
|
3683
3924
|
message: `negative take on model "${model.name}" needs an orderBy (or a primary key) to reverse`,
|
|
3684
3925
|
meta: { model: model.name }
|
|
3685
3926
|
});
|
|
@@ -3693,13 +3934,13 @@ function buildFindShape(params) {
|
|
|
3693
3934
|
let distinctOuterOrder = null;
|
|
3694
3935
|
if (args.distinct !== undefined) {
|
|
3695
3936
|
if (backward) {
|
|
3696
|
-
throw
|
|
3937
|
+
throw validationError4({
|
|
3697
3938
|
message: `distinct cannot combine with a negative take on model "${model.name}"`,
|
|
3698
3939
|
meta: { model: model.name }
|
|
3699
3940
|
});
|
|
3700
3941
|
}
|
|
3701
3942
|
if (orderTerms.some((term) => ("select" in term))) {
|
|
3702
|
-
throw
|
|
3943
|
+
throw validationError4({
|
|
3703
3944
|
message: `distinct cannot combine with a relation orderBy on model "${model.name}"`,
|
|
3704
3945
|
meta: { model: model.name }
|
|
3705
3946
|
});
|
|
@@ -3750,7 +3991,7 @@ function buildFindShape(params) {
|
|
|
3750
3991
|
const leadingColumns = leading.filter((term) => ("column" in term));
|
|
3751
3992
|
const coherent = leadingColumns.length === leading.length && leading.length === cursor.fields.length && new Set(leadingColumns.map((term) => term.column.name)).size === leading.length && leadingColumns.every((term) => cursorIndexByColumn.has(term.column.name));
|
|
3752
3993
|
if (!coherent) {
|
|
3753
|
-
throw
|
|
3994
|
+
throw validationError4({
|
|
3754
3995
|
message: `cursor pagination on model "${model.name}" needs orderBy to lead with the cursor field(s) ${cursor.fields.map((field) => `"${field.name}"`).join(", ")} \u2014 a cursor bound over a different ordering is not cursor pagination`,
|
|
3755
3996
|
meta: { model: model.name, cursorFields: cursor.fields.map((field) => field.name) }
|
|
3756
3997
|
});
|
|
@@ -3782,7 +4023,7 @@ function buildFindShape(params) {
|
|
|
3782
4023
|
}
|
|
3783
4024
|
if (args.after !== undefined) {
|
|
3784
4025
|
if (typeof args.after !== "string") {
|
|
3785
|
-
throw
|
|
4026
|
+
throw validationError4({
|
|
3786
4027
|
message: `after on model "${model.name}" must be a keyset token string`,
|
|
3787
4028
|
meta: { model: model.name, reason: "keyset-token-malformed" }
|
|
3788
4029
|
});
|
|
@@ -4060,13 +4301,13 @@ function compileOmit(params) {
|
|
|
4060
4301
|
if (value === undefined || value === false)
|
|
4061
4302
|
continue;
|
|
4062
4303
|
if (value !== true) {
|
|
4063
|
-
throw
|
|
4304
|
+
throw validationError4({
|
|
4064
4305
|
message: `omit.${name} must be true or false on model "${model.name}"`,
|
|
4065
4306
|
meta: { model: model.name, field: name }
|
|
4066
4307
|
});
|
|
4067
4308
|
}
|
|
4068
4309
|
if (model.relationByName.has(name)) {
|
|
4069
|
-
throw
|
|
4310
|
+
throw validationError4({
|
|
4070
4311
|
message: `omit.${name} on model "${model.name}" \u2014 omit takes scalar fields; a relation is excluded by not including it`,
|
|
4071
4312
|
meta: { model: model.name, field: name }
|
|
4072
4313
|
});
|
|
@@ -4077,7 +4318,7 @@ function compileOmit(params) {
|
|
|
4077
4318
|
}
|
|
4078
4319
|
const kept = model.fields.filter((field) => !omitted.has(field.name)).map((field) => field.name);
|
|
4079
4320
|
if (kept.length === 0) {
|
|
4080
|
-
throw
|
|
4321
|
+
throw validationError4({
|
|
4081
4322
|
message: `omit on model "${model.name}" removes every field`,
|
|
4082
4323
|
meta: { model: model.name }
|
|
4083
4324
|
});
|
|
@@ -4092,14 +4333,14 @@ function validateAggregatedField(params) {
|
|
|
4092
4333
|
if (key === "_count")
|
|
4093
4334
|
return;
|
|
4094
4335
|
if (field.isList) {
|
|
4095
|
-
throw
|
|
4336
|
+
throw validationError4({
|
|
4096
4337
|
message: `${key} cannot aggregate the list field ${model.name}.${field.name}`,
|
|
4097
4338
|
meta: { model: model.name, field: field.name, key }
|
|
4098
4339
|
});
|
|
4099
4340
|
}
|
|
4100
4341
|
if (key === "_avg" || key === "_sum") {
|
|
4101
4342
|
if (field.scalarType === null || !NUMERIC_TYPES.includes(field.scalarType)) {
|
|
4102
|
-
throw
|
|
4343
|
+
throw validationError4({
|
|
4103
4344
|
message: `${key} needs a numeric field, but ${model.name}.${field.name} is ${String(field.enumName ?? field.scalarType)}`,
|
|
4104
4345
|
meta: { model: model.name, field: field.name, key }
|
|
4105
4346
|
});
|
|
@@ -4107,7 +4348,7 @@ function validateAggregatedField(params) {
|
|
|
4107
4348
|
return;
|
|
4108
4349
|
}
|
|
4109
4350
|
if (field.scalarType === "Json" || field.scalarType === "Bytes" || field.scalarType === "Boolean") {
|
|
4110
|
-
throw
|
|
4351
|
+
throw validationError4({
|
|
4111
4352
|
message: `${key} cannot aggregate ${model.name}.${field.name} (${field.scalarType} has no ordering)`,
|
|
4112
4353
|
meta: { model: model.name, field: field.name, key }
|
|
4113
4354
|
});
|
|
@@ -4135,14 +4376,14 @@ function compileAggregations(params) {
|
|
|
4135
4376
|
if (flag === undefined || flag === false)
|
|
4136
4377
|
continue;
|
|
4137
4378
|
if (flag !== true) {
|
|
4138
|
-
throw
|
|
4379
|
+
throw validationError4({
|
|
4139
4380
|
message: `${key}.${name} must be true or false on model "${model.name}"`,
|
|
4140
4381
|
meta: { model: model.name, field: name, key }
|
|
4141
4382
|
});
|
|
4142
4383
|
}
|
|
4143
4384
|
if (name === "_all") {
|
|
4144
4385
|
if (key !== "_count") {
|
|
4145
|
-
throw
|
|
4386
|
+
throw validationError4({
|
|
4146
4387
|
message: `${key}._all is not valid \u2014 _all only counts rows (_count)`,
|
|
4147
4388
|
meta: { model: model.name, key }
|
|
4148
4389
|
});
|
|
@@ -4169,7 +4410,7 @@ function compileAggregations(params) {
|
|
|
4169
4410
|
}
|
|
4170
4411
|
}
|
|
4171
4412
|
if (selections.length === 0 && method === "aggregate") {
|
|
4172
|
-
throw
|
|
4413
|
+
throw validationError4({
|
|
4173
4414
|
message: `${model.clientName}.aggregate() needs at least one of ${AGGREGATE_KEYS.join(", ")}`,
|
|
4174
4415
|
meta: { model: model.name }
|
|
4175
4416
|
});
|
|
@@ -4190,7 +4431,7 @@ function compileAggregatePredicate(params) {
|
|
|
4190
4431
|
let field = null;
|
|
4191
4432
|
if (selector === "_all") {
|
|
4192
4433
|
if (key !== "_count") {
|
|
4193
|
-
throw
|
|
4434
|
+
throw validationError4({
|
|
4194
4435
|
message: `having ${key}._all is not valid \u2014 _all only counts rows (_count)`,
|
|
4195
4436
|
meta: { model: model.name, key }
|
|
4196
4437
|
});
|
|
@@ -4209,7 +4450,7 @@ function compileAggregatePredicate(params) {
|
|
|
4209
4450
|
continue;
|
|
4210
4451
|
const op = HAVING_OPERATOR_MAP[operator];
|
|
4211
4452
|
if (op === undefined) {
|
|
4212
|
-
throw
|
|
4453
|
+
throw validationError4({
|
|
4213
4454
|
message: `unknown having operator "${operator}" on ${key} of ${model.name}.${selector} \u2014 supported: ${Object.keys(HAVING_OPERATOR_MAP).join(", ")}`,
|
|
4214
4455
|
meta: { model: model.name, key, operator }
|
|
4215
4456
|
});
|
|
@@ -4218,7 +4459,7 @@ function compileAggregatePredicate(params) {
|
|
|
4218
4459
|
out.push({ kind: "aggCmp", op, agg, value });
|
|
4219
4460
|
}
|
|
4220
4461
|
if (out.length === 0) {
|
|
4221
|
-
throw
|
|
4462
|
+
throw validationError4({
|
|
4222
4463
|
message: `having ${key}.${selector} on model "${model.name}" needs an operator`,
|
|
4223
4464
|
meta: { model: model.name, key }
|
|
4224
4465
|
});
|
|
@@ -4264,7 +4505,7 @@ function compileHaving(params) {
|
|
|
4264
4505
|
const requireGrouped = () => {
|
|
4265
4506
|
if (byNames.has(field.name))
|
|
4266
4507
|
return;
|
|
4267
|
-
throw
|
|
4508
|
+
throw validationError4({
|
|
4268
4509
|
message: `having on ${model.name}.${field.name} needs the field in \`by\` \u2014 only grouped columns and aggregates exist after grouping`,
|
|
4269
4510
|
meta: { model: model.name, field: field.name }
|
|
4270
4511
|
});
|
|
@@ -4317,7 +4558,7 @@ function compileGroupByOrder(params) {
|
|
|
4317
4558
|
if (direction === undefined)
|
|
4318
4559
|
continue;
|
|
4319
4560
|
if (direction !== "asc" && direction !== "desc") {
|
|
4320
|
-
throw
|
|
4561
|
+
throw validationError4({
|
|
4321
4562
|
message: `orderBy ${name}.${selector} must be "asc" or "desc" on model "${model.name}"`,
|
|
4322
4563
|
meta: { model: model.name, key: name, selector }
|
|
4323
4564
|
});
|
|
@@ -4325,7 +4566,7 @@ function compileGroupByOrder(params) {
|
|
|
4325
4566
|
let agg;
|
|
4326
4567
|
if (selector === "_all") {
|
|
4327
4568
|
if (key !== "_count") {
|
|
4328
|
-
throw
|
|
4569
|
+
throw validationError4({
|
|
4329
4570
|
message: `orderBy ${name}._all is not valid \u2014 _all only counts rows (_count)`,
|
|
4330
4571
|
meta: { model: model.name, key: name }
|
|
4331
4572
|
});
|
|
@@ -4343,7 +4584,7 @@ function compileGroupByOrder(params) {
|
|
|
4343
4584
|
}
|
|
4344
4585
|
const field = getFieldMeta({ model, field: name, context: "orderBy" });
|
|
4345
4586
|
if (!byNames.has(field.name)) {
|
|
4346
|
-
throw
|
|
4587
|
+
throw validationError4({
|
|
4347
4588
|
message: `orderBy on ${model.name}.${field.name} \u2014 groupBy can only order by fields in \`by\` or by aggregates`,
|
|
4348
4589
|
meta: { model: model.name, field: field.name }
|
|
4349
4590
|
});
|
|
@@ -4389,7 +4630,7 @@ function resolveStrategy(params) {
|
|
|
4389
4630
|
const { dialect, model, method, args, hasRelations, defaultRelationStrategy } = params;
|
|
4390
4631
|
const explicit = args.relationStrategy;
|
|
4391
4632
|
if (explicit !== undefined && explicit !== "query" && explicit !== "join") {
|
|
4392
|
-
throw
|
|
4633
|
+
throw validationError4({
|
|
4393
4634
|
message: `relationStrategy on ${model.clientName}.${method}() must be "query" or "join"`,
|
|
4394
4635
|
meta: { model: model.name, method, relationStrategy: explicit }
|
|
4395
4636
|
});
|
|
@@ -4399,7 +4640,7 @@ function resolveStrategy(params) {
|
|
|
4399
4640
|
const strategy = explicit ?? defaultRelationStrategy ?? "query";
|
|
4400
4641
|
if (strategy === "join" && !dialect.capabilities.lateralJoin) {
|
|
4401
4642
|
if (explicit !== undefined || hasRelations) {
|
|
4402
|
-
throw
|
|
4643
|
+
throw unsupportedCapability4({
|
|
4403
4644
|
dialect: dialect.name,
|
|
4404
4645
|
feature: 'relationStrategy "join"',
|
|
4405
4646
|
hint: 'lateral joins are postgres-only \u2014 use the portable "query" strategy on this dialect'
|
|
@@ -4441,7 +4682,7 @@ function buildQuery(params) {
|
|
|
4441
4682
|
if (args.select[key] === undefined || args.select[key] === false)
|
|
4442
4683
|
continue;
|
|
4443
4684
|
if (key === "_count" || model.relationByName.has(key)) {
|
|
4444
|
-
throw
|
|
4685
|
+
throw validationError4({
|
|
4445
4686
|
message: `select.${key} on ${model.clientName}.${method}() \u2014 relations are not supported on this method`,
|
|
4446
4687
|
meta: { model: model.name, method, field: key }
|
|
4447
4688
|
});
|
|
@@ -4457,7 +4698,7 @@ function buildQuery(params) {
|
|
|
4457
4698
|
defaultRelationStrategy: params.defaultRelationStrategy
|
|
4458
4699
|
});
|
|
4459
4700
|
if (args.select !== undefined && args.omit !== undefined) {
|
|
4460
|
-
throw
|
|
4701
|
+
throw validationError4({
|
|
4461
4702
|
message: `select and omit cannot combine on ${model.clientName}.${method}() \u2014 select names fields to keep, omit fields to drop`,
|
|
4462
4703
|
meta: { model: model.name, method }
|
|
4463
4704
|
});
|
|
@@ -4523,7 +4764,7 @@ function buildQuery(params) {
|
|
|
4523
4764
|
switch (method) {
|
|
4524
4765
|
case "findMany":
|
|
4525
4766
|
case "findFirst": {
|
|
4526
|
-
const locked = compileLock({ args, model, method, hasRelations: relations !== null });
|
|
4767
|
+
const locked = compileLock({ dialect, args, model, method, hasRelations: relations !== null });
|
|
4527
4768
|
const shape = buildFindShape({
|
|
4528
4769
|
dialect,
|
|
4529
4770
|
meta,
|
|
@@ -4536,13 +4777,13 @@ function buildQuery(params) {
|
|
|
4536
4777
|
});
|
|
4537
4778
|
return {
|
|
4538
4779
|
...base,
|
|
4539
|
-
statement: locked ? { ...shape.statement,
|
|
4780
|
+
statement: locked === null ? shape.statement : { ...shape.statement, rowLock: locked },
|
|
4540
4781
|
reverseRows: shape.reverseRows,
|
|
4541
4782
|
resortBy: shape.resortBy
|
|
4542
4783
|
};
|
|
4543
4784
|
}
|
|
4544
4785
|
case "findUnique": {
|
|
4545
|
-
const locked = compileLock({ args, model, method, hasRelations: relations !== null });
|
|
4786
|
+
const locked = compileLock({ dialect, args, model, method, hasRelations: relations !== null });
|
|
4546
4787
|
const where = expandCompoundWhere({
|
|
4547
4788
|
model,
|
|
4548
4789
|
where: requireObject({ value: args.where, what: "where", model })
|
|
@@ -4559,7 +4800,7 @@ function buildQuery(params) {
|
|
|
4559
4800
|
});
|
|
4560
4801
|
return {
|
|
4561
4802
|
...base,
|
|
4562
|
-
statement: locked ? { ...shape.statement,
|
|
4803
|
+
statement: locked === null ? shape.statement : { ...shape.statement, rowLock: locked }
|
|
4563
4804
|
};
|
|
4564
4805
|
}
|
|
4565
4806
|
case "count": {
|
|
@@ -4583,7 +4824,7 @@ function buildQuery(params) {
|
|
|
4583
4824
|
if (flag === undefined || flag === false)
|
|
4584
4825
|
continue;
|
|
4585
4826
|
if (flag !== true) {
|
|
4586
|
-
throw
|
|
4827
|
+
throw validationError4({
|
|
4587
4828
|
message: `select.${name} must be true or false on ${model.clientName}.count()`,
|
|
4588
4829
|
meta: { model: model.name, field: name }
|
|
4589
4830
|
});
|
|
@@ -4604,7 +4845,7 @@ function buildQuery(params) {
|
|
|
4604
4845
|
aggSelects.push({ agg: { fn: "count", column: { name: field.columnName } }, alias: field.name });
|
|
4605
4846
|
}
|
|
4606
4847
|
if (selections.length === 0) {
|
|
4607
|
-
throw
|
|
4848
|
+
throw validationError4({
|
|
4608
4849
|
message: `count select on model "${model.name}" must include _all or at least one field`,
|
|
4609
4850
|
meta: { model: model.name }
|
|
4610
4851
|
});
|
|
@@ -4707,7 +4948,7 @@ function buildQuery(params) {
|
|
|
4707
4948
|
const byRaw = args.by;
|
|
4708
4949
|
const byList = typeof byRaw === "string" ? [byRaw] : byRaw;
|
|
4709
4950
|
if (!Array.isArray(byList) || byList.length === 0 || byList.some((name) => typeof name !== "string")) {
|
|
4710
|
-
throw
|
|
4951
|
+
throw validationError4({
|
|
4711
4952
|
message: `groupBy on model "${model.name}" needs \`by\`: a field name or a non-empty array of field names`,
|
|
4712
4953
|
meta: { model: model.name }
|
|
4713
4954
|
});
|
|
@@ -4715,7 +4956,7 @@ function buildQuery(params) {
|
|
|
4715
4956
|
const byFields = byList.map((name) => {
|
|
4716
4957
|
const field = getFieldMeta({ model, field: name, context: "by" });
|
|
4717
4958
|
if (field.isList || field.scalarType === "Json") {
|
|
4718
|
-
throw
|
|
4959
|
+
throw validationError4({
|
|
4719
4960
|
message: `groupBy cannot group by ${model.name}.${field.name} (${field.isList ? "list" : "Json"} fields have no grouping equality)`,
|
|
4720
4961
|
meta: { model: model.name, field: field.name }
|
|
4721
4962
|
});
|
|
@@ -4754,16 +4995,16 @@ function buildQuery(params) {
|
|
|
4754
4995
|
if (args.onConflict !== undefined) {
|
|
4755
4996
|
const spec = requireObject({ value: args.onConflict, what: "onConflict", model });
|
|
4756
4997
|
for (const key of Object.keys(spec)) {
|
|
4757
|
-
if (key !== "action" && key !== "target" && key !== "where") {
|
|
4758
|
-
throw
|
|
4759
|
-
message: `unknown onConflict key "${key}" on ${model.clientName}.create() \u2014 expected action/target/where`,
|
|
4998
|
+
if (key !== "action" && key !== "target" && key !== "where" && key !== "update") {
|
|
4999
|
+
throw validationError4({
|
|
5000
|
+
message: `unknown onConflict key "${key}" on ${model.clientName}.create() \u2014 expected action/target/where/update`,
|
|
4760
5001
|
meta: { model: model.name, key }
|
|
4761
5002
|
});
|
|
4762
5003
|
}
|
|
4763
5004
|
}
|
|
4764
|
-
if (spec.action !== "nothing") {
|
|
4765
|
-
throw
|
|
4766
|
-
message: `onConflict.action on ${model.clientName}.create() must be "nothing"
|
|
5005
|
+
if (spec.action !== "nothing" && spec.action !== "update") {
|
|
5006
|
+
throw validationError4({
|
|
5007
|
+
message: `onConflict.action on ${model.clientName}.create() must be "nothing" or "update"`,
|
|
4767
5008
|
meta: { model: model.name }
|
|
4768
5009
|
});
|
|
4769
5010
|
}
|
|
@@ -4775,17 +5016,39 @@ function buildQuery(params) {
|
|
|
4775
5016
|
target: spec.target,
|
|
4776
5017
|
where: spec.where
|
|
4777
5018
|
});
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
5019
|
+
if (spec.action === "nothing" && spec.update !== undefined)
|
|
5020
|
+
throw validationError4({ message: "DO NOTHING cannot carry update expressions" });
|
|
5021
|
+
if (spec.action === "update") {
|
|
5022
|
+
if (!dialect.capabilities.returning)
|
|
5023
|
+
throw unsupportedCapability4({ dialect: dialect.name, feature: "create onConflict update returning", hint: "use upsertMany for a count-only conflict update" });
|
|
5024
|
+
if (compiled.columns === undefined && compiled.constraint === undefined)
|
|
5025
|
+
throw validationError4({ message: "A conflict update requires an explicit target" });
|
|
5026
|
+
const updates = requireObject({ value: spec.update, what: "onConflict.update", model });
|
|
5027
|
+
const fields = resolveUpsertUpdateFields({ model, update: Object.keys(updates), targetColumns: new Set(compiled.columns ?? []), skipUpdatedAt: true });
|
|
5028
|
+
const columns2 = Object.fromEntries(model.fields.map((field) => [field.name, field.columnName]));
|
|
5029
|
+
conflictClause = {
|
|
5030
|
+
columns: compiled.columns ?? [],
|
|
5031
|
+
...compiled.constraint === undefined ? {} : { constraint: compiled.constraint },
|
|
5032
|
+
...compiled.conflictWhere === undefined ? {} : { conflictWhere: compiled.conflictWhere },
|
|
5033
|
+
update: Object.fromEntries(fields.map((field) => {
|
|
5034
|
+
const fragment = updates[field.name];
|
|
5035
|
+
if (!isSqlFragment(fragment))
|
|
5036
|
+
throw validationError4({ message: "Conflict update expressions must be constructed SQL fragments" });
|
|
5037
|
+
return [field.columnName, mapConflictColumns({ fragment, columns: columns2 })];
|
|
5038
|
+
}))
|
|
5039
|
+
};
|
|
5040
|
+
} else
|
|
5041
|
+
conflictClause = {
|
|
5042
|
+
doNothing: true,
|
|
5043
|
+
...compiled.columns === undefined ? {} : { columns: compiled.columns },
|
|
5044
|
+
...compiled.constraint === undefined ? {} : { constraint: compiled.constraint },
|
|
5045
|
+
...compiled.conflictWhere === undefined ? {} : { conflictWhere: compiled.conflictWhere }
|
|
5046
|
+
};
|
|
4784
5047
|
}
|
|
4785
5048
|
const row = compileCreateRow({ dialect, model, data: args.data });
|
|
4786
5049
|
const columns = orderedColumns({ model, values: row.values });
|
|
4787
5050
|
if (columns.length === 0) {
|
|
4788
|
-
throw
|
|
5051
|
+
throw validationError4({
|
|
4789
5052
|
message: `create on model "${model.name}" needs at least one field value`,
|
|
4790
5053
|
meta: { model: model.name }
|
|
4791
5054
|
});
|
|
@@ -4806,7 +5069,7 @@ function buildQuery(params) {
|
|
|
4806
5069
|
case "createMany":
|
|
4807
5070
|
case "createManyAndReturn": {
|
|
4808
5071
|
if (method === "createManyAndReturn" && !dialect.capabilities.createManyAndReturn) {
|
|
4809
|
-
throw
|
|
5072
|
+
throw unsupportedCapability4({
|
|
4810
5073
|
dialect: dialect.name,
|
|
4811
5074
|
feature: "createManyAndReturn",
|
|
4812
5075
|
hint: "this dialect cannot RETURNING multiple inserted rows \u2014 use createMany then findMany"
|
|
@@ -4820,7 +5083,7 @@ function buildQuery(params) {
|
|
|
4820
5083
|
const spec = requireObject({ value: args.skipDuplicates, what: "skipDuplicates", model });
|
|
4821
5084
|
for (const key of Object.keys(spec)) {
|
|
4822
5085
|
if (key !== "target" && key !== "where") {
|
|
4823
|
-
throw
|
|
5086
|
+
throw validationError4({
|
|
4824
5087
|
message: `unknown skipDuplicates key "${key}" on ${model.clientName}.${method}() \u2014 expected target/where`,
|
|
4825
5088
|
meta: { model: model.name, key }
|
|
4826
5089
|
});
|
|
@@ -4858,7 +5121,7 @@ function buildQuery(params) {
|
|
|
4858
5121
|
case "upsertMany": {
|
|
4859
5122
|
for (const flag of ["skipUpdatedAt", "allowAnyUniqueConflict"]) {
|
|
4860
5123
|
if (args[flag] !== undefined && typeof args[flag] !== "boolean") {
|
|
4861
|
-
throw
|
|
5124
|
+
throw validationError4({
|
|
4862
5125
|
message: `${flag} on ${model.clientName}.upsertMany() must be a boolean, got ${String(args[flag])}`,
|
|
4863
5126
|
meta: { model: model.name, argument: flag }
|
|
4864
5127
|
});
|
|
@@ -4871,9 +5134,10 @@ function buildQuery(params) {
|
|
|
4871
5134
|
});
|
|
4872
5135
|
const conflictTarget = resolveUpsertConflictTarget({ model, target: args.conflictTarget });
|
|
4873
5136
|
const targetColumns = conflictTarget.kind === "columns" ? conflictTarget.fields.map((field) => field.columnName) : [];
|
|
5137
|
+
const expressionUpdate = isPlainObject3(args.update) ? args.update : undefined;
|
|
4874
5138
|
const updateFields = resolveUpsertUpdateFields({
|
|
4875
5139
|
model,
|
|
4876
|
-
update: args.update,
|
|
5140
|
+
update: expressionUpdate === undefined ? args.update : Object.keys(expressionUpdate),
|
|
4877
5141
|
targetColumns: new Set(targetColumns),
|
|
4878
5142
|
skipUpdatedAt: args.skipUpdatedAt === true
|
|
4879
5143
|
});
|
|
@@ -4917,7 +5181,14 @@ function buildQuery(params) {
|
|
|
4917
5181
|
columns: targetColumns,
|
|
4918
5182
|
...namedConstraint === undefined ? {} : { constraint: namedConstraint },
|
|
4919
5183
|
...partialArbiter === undefined ? {} : { conflictWhere: partialArbiter },
|
|
4920
|
-
update: Object.fromEntries(updateFields.map((field) =>
|
|
5184
|
+
update: Object.fromEntries(updateFields.map((field) => {
|
|
5185
|
+
if (expressionUpdate === undefined || !Object.hasOwn(expressionUpdate, field.name))
|
|
5186
|
+
return [field.columnName, excludedRef({ column: field.columnName })];
|
|
5187
|
+
const fragment = expressionUpdate[field.name];
|
|
5188
|
+
if (!isSqlFragment(fragment))
|
|
5189
|
+
throw validationError4({ message: "Conflict update expressions must be constructed SQL fragments" });
|
|
5190
|
+
return [field.columnName, mapConflictColumns({ fragment, columns: Object.fromEntries(model.fields.map((entry) => [entry.name, entry.columnName])) })];
|
|
5191
|
+
}))
|
|
4921
5192
|
};
|
|
4922
5193
|
const upsertStatements = upsertGroups.map((group) => ({
|
|
4923
5194
|
kind: "insert",
|
|
@@ -4938,14 +5209,14 @@ function buildQuery(params) {
|
|
|
4938
5209
|
case "updateManyAndReturn": {
|
|
4939
5210
|
const isUnique = method === "update";
|
|
4940
5211
|
if (method === "updateManyAndReturn" && !dialect.capabilities.returning) {
|
|
4941
|
-
throw
|
|
5212
|
+
throw unsupportedCapability4({
|
|
4942
5213
|
dialect: dialect.name,
|
|
4943
5214
|
feature: "updateManyAndReturn",
|
|
4944
5215
|
hint: "this dialect has no RETURNING \u2014 use updateMany then findMany, accepting that the read is a separate, unprotected statement"
|
|
4945
5216
|
});
|
|
4946
5217
|
}
|
|
4947
5218
|
if (args.skipUpdatedAt !== undefined && typeof args.skipUpdatedAt !== "boolean") {
|
|
4948
|
-
throw
|
|
5219
|
+
throw validationError4({
|
|
4949
5220
|
message: `skipUpdatedAt on ${model.clientName}.${method}() must be a boolean, got ${String(args.skipUpdatedAt)}`,
|
|
4950
5221
|
meta: { model: model.name, method }
|
|
4951
5222
|
});
|
|
@@ -4977,13 +5248,13 @@ function buildQuery(params) {
|
|
|
4977
5248
|
if (args.onConflict !== undefined) {
|
|
4978
5249
|
const spec = requireObject({ value: args.onConflict, what: "onConflict", model });
|
|
4979
5250
|
if (typeof spec.constraint !== "string" || spec.constraint.length === 0 || Object.keys(spec).length !== 1) {
|
|
4980
|
-
throw
|
|
5251
|
+
throw validationError4({
|
|
4981
5252
|
message: `onConflict on ${model.clientName}.upsert() must be { constraint: string } \u2014 the named unique constraint that arbitrates the conflict`,
|
|
4982
5253
|
meta: { model: model.name }
|
|
4983
5254
|
});
|
|
4984
5255
|
}
|
|
4985
5256
|
if (!dialect.capabilities.conflictTargetConstraint) {
|
|
4986
|
-
throw
|
|
5257
|
+
throw unsupportedCapability4({
|
|
4987
5258
|
dialect: dialect.name,
|
|
4988
5259
|
feature: "a named-constraint conflict target on upsert",
|
|
4989
5260
|
hint: "only postgres supports ON CONFLICT ON CONSTRAINT \u2014 rely on the where-derived column target instead"
|
|
@@ -5001,7 +5272,7 @@ function buildQuery(params) {
|
|
|
5001
5272
|
const targetFields = conflictTargetGroup({ model, where }).fields;
|
|
5002
5273
|
for (const field of targetFields) {
|
|
5003
5274
|
if (!(field.columnName in row.values)) {
|
|
5004
|
-
throw new
|
|
5275
|
+
throw new VibeError10({
|
|
5005
5276
|
code: "VIBE_INTERNAL",
|
|
5006
5277
|
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`,
|
|
5007
5278
|
meta: { model: model.name, column: field.columnName }
|
|
@@ -5012,7 +5283,7 @@ function buildQuery(params) {
|
|
|
5012
5283
|
for (const [key, value] of Object.entries(where)) {
|
|
5013
5284
|
if (value === undefined || nativeTargetNames.has(key))
|
|
5014
5285
|
continue;
|
|
5015
|
-
throw new
|
|
5286
|
+
throw new VibeError10({
|
|
5016
5287
|
code: "VIBE_INTERNAL",
|
|
5017
5288
|
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`,
|
|
5018
5289
|
meta: { model: model.name, field: key }
|
|
@@ -5065,7 +5336,7 @@ function buildQuery(params) {
|
|
|
5065
5336
|
import { getSqlDialect, renderStatement } from "@vibeorm/sql";
|
|
5066
5337
|
|
|
5067
5338
|
// packages/runtime/src/relation-key.ts
|
|
5068
|
-
import { VibeError as
|
|
5339
|
+
import { VibeError as VibeError11 } from "@vibeorm/schema";
|
|
5069
5340
|
function relationValueKey(params) {
|
|
5070
5341
|
const { value } = params;
|
|
5071
5342
|
if (value === null || value === undefined)
|
|
@@ -5098,7 +5369,7 @@ function relationTupleKey(params) {
|
|
|
5098
5369
|
if (value === null || value === undefined)
|
|
5099
5370
|
return null;
|
|
5100
5371
|
if (typeof value === "object" && !(value instanceof Date) && !(value instanceof Uint8Array)) {
|
|
5101
|
-
throw new
|
|
5372
|
+
throw new VibeError11({
|
|
5102
5373
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
5103
5374
|
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",
|
|
5104
5375
|
meta: { received: Object.prototype.toString.call(value) }
|
|
@@ -5124,7 +5395,7 @@ function assertWriteScope(params) {
|
|
|
5124
5395
|
}
|
|
5125
5396
|
|
|
5126
5397
|
// packages/runtime/src/nested-writes.ts
|
|
5127
|
-
import { VibeError as
|
|
5398
|
+
import { VibeError as VibeError12 } from "@vibeorm/schema";
|
|
5128
5399
|
function parentTupleOf(params) {
|
|
5129
5400
|
const { link, row } = params;
|
|
5130
5401
|
return link.parentKeys.map((field) => row[field.name]);
|
|
@@ -5160,7 +5431,7 @@ function detachAssignment(params) {
|
|
|
5160
5431
|
const { fields, op, verb, target } = params;
|
|
5161
5432
|
const optional = fields.filter((field) => field.isOptional);
|
|
5162
5433
|
if (optional.length === 0) {
|
|
5163
|
-
throw
|
|
5434
|
+
throw validationError5({
|
|
5164
5435
|
message: `nested "${verb}" on ${op.link.relation.model}.${op.name} needs an optional foreign key on ${target}.${fields.map((field) => field.name).join(", ")} \u2014 a required relation cannot be detached`,
|
|
5165
5436
|
meta: { relation: op.name, verb }
|
|
5166
5437
|
});
|
|
@@ -5170,11 +5441,11 @@ function detachAssignment(params) {
|
|
|
5170
5441
|
out[field.name] = null;
|
|
5171
5442
|
return out;
|
|
5172
5443
|
}
|
|
5173
|
-
function
|
|
5174
|
-
return new
|
|
5444
|
+
function validationError5(params) {
|
|
5445
|
+
return new VibeError12({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
5175
5446
|
}
|
|
5176
5447
|
function notFound(params) {
|
|
5177
|
-
return new
|
|
5448
|
+
return new VibeError12({ code: "VIBE_NOT_FOUND", message: params.message, meta: params.meta });
|
|
5178
5449
|
}
|
|
5179
5450
|
function isPlainObject4(value) {
|
|
5180
5451
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
@@ -5225,7 +5496,7 @@ function partitionData(params) {
|
|
|
5225
5496
|
continue;
|
|
5226
5497
|
}
|
|
5227
5498
|
if (!isPlainObject4(value)) {
|
|
5228
|
-
throw
|
|
5499
|
+
throw validationError5({
|
|
5229
5500
|
message: `relation ${model.name}.${key} in data must be an object of nested-write operations`,
|
|
5230
5501
|
meta: { model: model.name, relation: key }
|
|
5231
5502
|
});
|
|
@@ -5239,7 +5510,7 @@ function partitionData(params) {
|
|
|
5239
5510
|
continue;
|
|
5240
5511
|
}
|
|
5241
5512
|
if (!allowed.includes(verb)) {
|
|
5242
|
-
throw
|
|
5513
|
+
throw validationError5({
|
|
5243
5514
|
message: `unknown nested-write operation "${verb}" on ${model.name}.${key} (${phase}) \u2014 supported: ${allowed.join(", ")}`,
|
|
5244
5515
|
meta: { model: model.name, relation: key, verb, phase }
|
|
5245
5516
|
});
|
|
@@ -5247,7 +5518,7 @@ function partitionData(params) {
|
|
|
5247
5518
|
verbs[verb] = operand;
|
|
5248
5519
|
}
|
|
5249
5520
|
if (Object.keys(verbs).length === 0) {
|
|
5250
|
-
throw
|
|
5521
|
+
throw validationError5({
|
|
5251
5522
|
message: `relation ${model.name}.${key} in data carries no nested-write operation`,
|
|
5252
5523
|
meta: { model: model.name, relation: key }
|
|
5253
5524
|
});
|
|
@@ -5260,14 +5531,14 @@ function toItems(params) {
|
|
|
5260
5531
|
const { op, verb, operand } = params;
|
|
5261
5532
|
const list = Array.isArray(operand) ? operand : [operand];
|
|
5262
5533
|
if (!op.link.relation.isList && Array.isArray(operand)) {
|
|
5263
|
-
throw
|
|
5534
|
+
throw validationError5({
|
|
5264
5535
|
message: `nested "${verb}" on to-one relation ${op.link.relation.model}.${op.name} cannot take an array`,
|
|
5265
5536
|
meta: { relation: op.name, verb }
|
|
5266
5537
|
});
|
|
5267
5538
|
}
|
|
5268
5539
|
return list.map((item) => {
|
|
5269
5540
|
if (!isPlainObject4(item)) {
|
|
5270
|
-
throw
|
|
5541
|
+
throw validationError5({
|
|
5271
5542
|
message: `nested "${verb}" on ${op.link.relation.model}.${op.name} expects object items`,
|
|
5272
5543
|
meta: { relation: op.name, verb }
|
|
5273
5544
|
});
|
|
@@ -5279,7 +5550,7 @@ function requireShape(params) {
|
|
|
5279
5550
|
const { op, verb, item, keys } = params;
|
|
5280
5551
|
for (const key of keys) {
|
|
5281
5552
|
if (!isPlainObject4(item[key])) {
|
|
5282
|
-
throw
|
|
5553
|
+
throw validationError5({
|
|
5283
5554
|
message: `nested "${verb}" on ${op.link.relation.model}.${op.name} needs an object "${key}"`,
|
|
5284
5555
|
meta: { relation: op.name, verb, missing: key }
|
|
5285
5556
|
});
|
|
@@ -5399,7 +5670,7 @@ function singleVerb(params) {
|
|
|
5399
5670
|
const { op } = params;
|
|
5400
5671
|
const entries = Object.entries(op.verbs);
|
|
5401
5672
|
if (entries.length !== 1) {
|
|
5402
|
-
throw
|
|
5673
|
+
throw validationError5({
|
|
5403
5674
|
message: `to-one relation ${op.link.relation.model}.${op.name} takes exactly one nested-write operation, got: ${entries.map(([verb]) => verb).join(", ")}`,
|
|
5404
5675
|
meta: { relation: op.name, verbs: entries.map(([verb]) => verb) }
|
|
5405
5676
|
});
|
|
@@ -5553,7 +5824,7 @@ function updateDataOf(params) {
|
|
|
5553
5824
|
if ("data" in operand && isPlainObject4(operand.data))
|
|
5554
5825
|
return operand.data;
|
|
5555
5826
|
if ("data" in operand) {
|
|
5556
|
-
throw
|
|
5827
|
+
throw validationError5({
|
|
5557
5828
|
message: `nested "update" on ${op.link.relation.model}.${op.name} needs an object "data"`,
|
|
5558
5829
|
meta: { relation: op.name }
|
|
5559
5830
|
});
|
|
@@ -5610,7 +5881,7 @@ async function resolveOwnerUpdateVerb(params) {
|
|
|
5610
5881
|
if (operand === false)
|
|
5611
5882
|
return;
|
|
5612
5883
|
if (!link.relation.isOptional) {
|
|
5613
|
-
throw
|
|
5884
|
+
throw validationError5({
|
|
5614
5885
|
message: `nested "disconnect" on required relation ${link.relation.model}.${op.name} \u2014 connect a replacement instead`,
|
|
5615
5886
|
meta: { relation: op.name }
|
|
5616
5887
|
});
|
|
@@ -5622,7 +5893,7 @@ async function resolveOwnerUpdateVerb(params) {
|
|
|
5622
5893
|
if (operand === false)
|
|
5623
5894
|
return;
|
|
5624
5895
|
if (!link.relation.isOptional) {
|
|
5625
|
-
throw
|
|
5896
|
+
throw validationError5({
|
|
5626
5897
|
message: `nested "delete" on required relation ${link.relation.model}.${op.name} \u2014 the parent cannot exist without it`,
|
|
5627
5898
|
meta: { relation: op.name }
|
|
5628
5899
|
});
|
|
@@ -5642,7 +5913,7 @@ async function resolveOwnerUpdateVerb(params) {
|
|
|
5642
5913
|
}
|
|
5643
5914
|
case "update": {
|
|
5644
5915
|
if (!isPlainObject4(operand)) {
|
|
5645
|
-
throw
|
|
5916
|
+
throw validationError5({
|
|
5646
5917
|
message: `nested "update" on ${link.relation.model}.${op.name} expects an object`,
|
|
5647
5918
|
meta: { relation: op.name }
|
|
5648
5919
|
});
|
|
@@ -5665,7 +5936,7 @@ async function resolveOwnerUpdateVerb(params) {
|
|
|
5665
5936
|
}
|
|
5666
5937
|
default: {
|
|
5667
5938
|
if (!isPlainObject4(operand)) {
|
|
5668
|
-
throw
|
|
5939
|
+
throw validationError5({
|
|
5669
5940
|
message: `nested "upsert" on ${link.relation.model}.${op.name} expects an object`,
|
|
5670
5941
|
meta: { relation: op.name }
|
|
5671
5942
|
});
|
|
@@ -5806,7 +6077,7 @@ async function applyChildUpdateOps(params) {
|
|
|
5806
6077
|
case "update": {
|
|
5807
6078
|
if (link.kind === "toOneInverse") {
|
|
5808
6079
|
if (!isPlainObject4(operand)) {
|
|
5809
|
-
throw
|
|
6080
|
+
throw validationError5({
|
|
5810
6081
|
message: `nested "update" on ${link.relation.model}.${op.name} expects an object`,
|
|
5811
6082
|
meta: { relation: op.name }
|
|
5812
6083
|
});
|
|
@@ -5869,7 +6140,7 @@ async function applyChildUpdateOps(params) {
|
|
|
5869
6140
|
for (const item of items) {
|
|
5870
6141
|
const data = item.data;
|
|
5871
6142
|
if (!isPlainObject4(data)) {
|
|
5872
|
-
throw
|
|
6143
|
+
throw validationError5({
|
|
5873
6144
|
message: `nested "updateMany" on ${link.relation.model}.${op.name} needs an object "data"`,
|
|
5874
6145
|
meta: { relation: op.name }
|
|
5875
6146
|
});
|
|
@@ -5940,7 +6211,7 @@ async function applyChildUpdateOps(params) {
|
|
|
5940
6211
|
default: {
|
|
5941
6212
|
if (link.kind === "toOneInverse") {
|
|
5942
6213
|
if (!isPlainObject4(operand)) {
|
|
5943
|
-
throw
|
|
6214
|
+
throw validationError5({
|
|
5944
6215
|
message: `nested "upsert" on ${link.relation.model}.${op.name} expects an object`,
|
|
5945
6216
|
meta: { relation: op.name }
|
|
5946
6217
|
});
|
|
@@ -6278,7 +6549,7 @@ function previewOperation(params) {
|
|
|
6278
6549
|
};
|
|
6279
6550
|
}
|
|
6280
6551
|
// packages/runtime/src/diagnostics/plan.ts
|
|
6281
|
-
import { VibeError as
|
|
6552
|
+
import { VibeError as VibeError13, unsupportedCapability as unsupportedCapability5 } from "@vibeorm/schema";
|
|
6282
6553
|
import { NATIVE_RLS, getSqlDialect as getSqlDialect2, renderPostgresExplain, renderPostgresWorkload, renderStatement as renderStatement2 } from "@vibeorm/sql";
|
|
6283
6554
|
var PLAN_DEFAULTS = Object.freeze({ maxNodes: 200, maxConditionLength: 2000 });
|
|
6284
6555
|
var CONDITION_KEYS = [
|
|
@@ -6297,7 +6568,7 @@ var CONDITION_KEYS = [
|
|
|
6297
6568
|
"Output"
|
|
6298
6569
|
];
|
|
6299
6570
|
function planError(params) {
|
|
6300
|
-
return new
|
|
6571
|
+
return new VibeError13({ code: "VIBE_ADAPTER", message: params.message, meta: {} });
|
|
6301
6572
|
}
|
|
6302
6573
|
function asRecord(value) {
|
|
6303
6574
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
@@ -6330,14 +6601,14 @@ function parsePostgresPlan(params) {
|
|
|
6330
6601
|
const maxNodes = params.maxNodes ?? PLAN_DEFAULTS.maxNodes;
|
|
6331
6602
|
const includeLiterals = params.includeLiterals === true;
|
|
6332
6603
|
if (!Number.isInteger(maxNodes) || maxNodes < 1) {
|
|
6333
|
-
throw new
|
|
6604
|
+
throw new VibeError13({ code: "VIBE_CONFIG", message: `maxNodes must be a positive integer, got ${String(maxNodes)}`, meta: { maxNodes } });
|
|
6334
6605
|
}
|
|
6335
6606
|
let parsed = payload;
|
|
6336
6607
|
if (typeof payload === "string") {
|
|
6337
6608
|
try {
|
|
6338
6609
|
parsed = JSON.parse(payload);
|
|
6339
6610
|
} catch {
|
|
6340
|
-
throw new
|
|
6611
|
+
throw new VibeError13({
|
|
6341
6612
|
code: "VIBE_ADAPTER",
|
|
6342
6613
|
message: "EXPLAIN returned malformed JSON",
|
|
6343
6614
|
meta: { reason: "malformed-plan-json", mode }
|
|
@@ -6404,7 +6675,7 @@ function parsePostgresPlan(params) {
|
|
|
6404
6675
|
}
|
|
6405
6676
|
var WRITE_LEADERS = ["INSERT", "UPDATE", "DELETE", "MERGE", "TRUNCATE", "CREATE", "DROP", "ALTER", "GRANT", "REVOKE", "COPY", "CALL", "DO"];
|
|
6406
6677
|
function refuse(params) {
|
|
6407
|
-
return new
|
|
6678
|
+
return new VibeError13({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta ?? {} });
|
|
6408
6679
|
}
|
|
6409
6680
|
function assertExecutableRead(params) {
|
|
6410
6681
|
const normalized = normalizeSql({ text: params.text });
|
|
@@ -6443,7 +6714,7 @@ async function readPlanContext(params) {
|
|
|
6443
6714
|
}
|
|
6444
6715
|
function requirePostgres(params) {
|
|
6445
6716
|
if (params.dialect !== "postgres") {
|
|
6446
|
-
throw
|
|
6717
|
+
throw unsupportedCapability5({
|
|
6447
6718
|
dialect: params.dialect,
|
|
6448
6719
|
feature: "query plan inspection",
|
|
6449
6720
|
hint: "plan inspection is PostgreSQL-shaped; operation tracing (telemetry) works on every dialect"
|
|
@@ -6452,7 +6723,7 @@ function requirePostgres(params) {
|
|
|
6452
6723
|
return getSqlDialect2({ dialect: params.dialect });
|
|
6453
6724
|
}
|
|
6454
6725
|
async function explainSql(params) {
|
|
6455
|
-
const { executor, sql, rlsContext } = params;
|
|
6726
|
+
const { executor, sql: sql2, rlsContext } = params;
|
|
6456
6727
|
const mode = params.mode ?? "estimate";
|
|
6457
6728
|
const dialect = requirePostgres({ dialect: params.dialect });
|
|
6458
6729
|
const warnings = [];
|
|
@@ -6469,7 +6740,7 @@ async function explainSql(params) {
|
|
|
6469
6740
|
meta: { mode }
|
|
6470
6741
|
});
|
|
6471
6742
|
}
|
|
6472
|
-
assertExecutableRead({ text:
|
|
6743
|
+
assertExecutableRead({ text: sql2.text });
|
|
6473
6744
|
warnings.push("this statement was EXECUTED to produce observed rows and milliseconds; a read-only transaction and a keyword check are not a proof of safety, because a SELECT can call a function with side effects");
|
|
6474
6745
|
}
|
|
6475
6746
|
const contextKeys = rlsContext === undefined ? [] : Object.keys(rlsContext);
|
|
@@ -6495,7 +6766,7 @@ async function explainSql(params) {
|
|
|
6495
6766
|
const context = await readPlanContext({ executor, dialect, rlsContextKeys: contextKeys });
|
|
6496
6767
|
if (!context.representative) {
|
|
6497
6768
|
if (params.requireRepresentativeContext === true) {
|
|
6498
|
-
throw new
|
|
6769
|
+
throw new VibeError13({
|
|
6499
6770
|
code: "VIBE_RLS_CONTEXT",
|
|
6500
6771
|
message: `refusing to report a plan taken under a non-representative identity: ${context.reason ?? "role bypasses row policies"}`,
|
|
6501
6772
|
meta: { currentUser: context.currentUser, bypassRls: context.bypassRls, superuser: context.superuser }
|
|
@@ -6505,7 +6776,7 @@ async function explainSql(params) {
|
|
|
6505
6776
|
}
|
|
6506
6777
|
const explain = renderPostgresExplain({
|
|
6507
6778
|
dialect,
|
|
6508
|
-
query: { text:
|
|
6779
|
+
query: { text: sql2.text, values: [...sql2.values], returning: "none" },
|
|
6509
6780
|
options: mode === "analyze" ? { analyze: true, buffers: true } : {}
|
|
6510
6781
|
});
|
|
6511
6782
|
const rows = await run(explain);
|
|
@@ -6577,7 +6848,7 @@ async function explainOperation(params) {
|
|
|
6577
6848
|
});
|
|
6578
6849
|
}
|
|
6579
6850
|
// packages/runtime/src/diagnostics/workload.ts
|
|
6580
|
-
import { unsupportedCapability as
|
|
6851
|
+
import { unsupportedCapability as unsupportedCapability6 } from "@vibeorm/schema";
|
|
6581
6852
|
import { getSqlDialect as getSqlDialect3, renderPostgresWorkload as renderPostgresWorkload2 } from "@vibeorm/sql";
|
|
6582
6853
|
var STATEMENTS_INFO_MIN_VERSION = 140000;
|
|
6583
6854
|
var STATEMENTS_MIN_VERSION = 130000;
|
|
@@ -6610,7 +6881,7 @@ async function readAvailability(params) {
|
|
|
6610
6881
|
async function readWorkloadStatistics(params) {
|
|
6611
6882
|
const { executor } = params;
|
|
6612
6883
|
if (params.dialect !== "postgres") {
|
|
6613
|
-
throw
|
|
6884
|
+
throw unsupportedCapability6({
|
|
6614
6885
|
dialect: params.dialect,
|
|
6615
6886
|
feature: "workload statistics (pg_stat_statements)",
|
|
6616
6887
|
hint: "server workload statistics are PostgreSQL-only; operation telemetry works on every dialect"
|
|
@@ -6688,7 +6959,7 @@ async function readWorkloadStatistics(params) {
|
|
|
6688
6959
|
};
|
|
6689
6960
|
}
|
|
6690
6961
|
// packages/runtime/src/diagnostics/insight.ts
|
|
6691
|
-
import { VibeError as
|
|
6962
|
+
import { VibeError as VibeError14 } from "@vibeorm/schema";
|
|
6692
6963
|
var DIAGNOSTIC_THRESHOLDS = Object.freeze({
|
|
6693
6964
|
repeatThreshold: 5,
|
|
6694
6965
|
minRepeatThreshold: 2,
|
|
@@ -6706,7 +6977,7 @@ function bounded(params) {
|
|
|
6706
6977
|
if (value === undefined)
|
|
6707
6978
|
return fallback;
|
|
6708
6979
|
if (!Number.isInteger(value) || value < min || value > max) {
|
|
6709
|
-
throw new
|
|
6980
|
+
throw new VibeError14({
|
|
6710
6981
|
code: "VIBE_CONFIG",
|
|
6711
6982
|
message: `${name} must be an integer between ${min} and ${max}, got ${String(value)}`,
|
|
6712
6983
|
meta: { [name]: value }
|
|
@@ -6929,11 +7200,11 @@ function recommendFromPlan(params) {
|
|
|
6929
7200
|
return findings.slice(0, maxFindings);
|
|
6930
7201
|
}
|
|
6931
7202
|
// packages/runtime/src/relation-loader.ts
|
|
6932
|
-
import { VibeError as
|
|
7203
|
+
import { VibeError as VibeError16 } from "@vibeorm/schema";
|
|
6933
7204
|
import { renderStatement as renderStatement3 } from "@vibeorm/sql";
|
|
6934
7205
|
|
|
6935
7206
|
// packages/runtime/src/computed.ts
|
|
6936
|
-
import { VibeError as
|
|
7207
|
+
import { VibeError as VibeError15 } from "@vibeorm/schema";
|
|
6937
7208
|
function sameNeeds(a, b) {
|
|
6938
7209
|
if (a.length !== b.length)
|
|
6939
7210
|
return false;
|
|
@@ -6969,7 +7240,7 @@ function assertComputedManifest(params) {
|
|
|
6969
7240
|
problems.push(`"${key}" is configured but missing from the generated client`);
|
|
6970
7241
|
}
|
|
6971
7242
|
if (problems.length > 0) {
|
|
6972
|
-
throw new
|
|
7243
|
+
throw new VibeError15({
|
|
6973
7244
|
code: "VIBE_STALE_CLIENT",
|
|
6974
7245
|
message: `computed-field config changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
|
|
6975
7246
|
meta: { problems }
|
|
@@ -7101,7 +7372,7 @@ function chunkKeys(params) {
|
|
|
7101
7372
|
const rendered = renderStatement3({ dialect: ctx.dialect, statement: build(keys.slice(0, 1)) });
|
|
7102
7373
|
fixed = rendered.values.length - (expands ? arity : 1);
|
|
7103
7374
|
if (fixed + arity > budget) {
|
|
7104
|
-
throw new
|
|
7375
|
+
throw new VibeError16({
|
|
7105
7376
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
7106
7377
|
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`,
|
|
7107
7378
|
meta: { relation, dialect: ctx.dialect.name, required: fixed + arity, budget }
|
|
@@ -7231,7 +7502,7 @@ async function loadManyToMany(params) {
|
|
|
7231
7502
|
const link = node.link;
|
|
7232
7503
|
const join = link.join;
|
|
7233
7504
|
if (join === null) {
|
|
7234
|
-
throw new
|
|
7505
|
+
throw new VibeError16({
|
|
7235
7506
|
code: "VIBE_INTERNAL",
|
|
7236
7507
|
message: `manyToMany relation "${node.name}" resolved without join-table wiring`
|
|
7237
7508
|
});
|
|
@@ -7510,7 +7781,7 @@ async function attachLateralRelations(params) {
|
|
|
7510
7781
|
try {
|
|
7511
7782
|
parsed = JSON.parse(raw);
|
|
7512
7783
|
} catch (cause) {
|
|
7513
|
-
throw new
|
|
7784
|
+
throw new VibeError16({
|
|
7514
7785
|
code: "VIBE_INTERNAL",
|
|
7515
7786
|
message: `relation column "${column}" returned text that is not valid JSON`,
|
|
7516
7787
|
meta: { relation: node.name },
|
|
@@ -7520,7 +7791,7 @@ async function attachLateralRelations(params) {
|
|
|
7520
7791
|
}
|
|
7521
7792
|
if (isList) {
|
|
7522
7793
|
if (!Array.isArray(parsed)) {
|
|
7523
|
-
throw new
|
|
7794
|
+
throw new VibeError16({ code: "VIBE_INTERNAL", message: "to-many relation payload must be an array", meta: { relation: node.name } });
|
|
7524
7795
|
}
|
|
7525
7796
|
const children = parsed.map(decodePosition);
|
|
7526
7797
|
for (const child of children) {
|
|
@@ -7552,7 +7823,7 @@ async function attachLateralRelations(params) {
|
|
|
7552
7823
|
const raw = row[column];
|
|
7553
7824
|
const count = typeof raw === "number" || typeof raw === "bigint" || typeof raw === "string" && /^\d+$/.test(raw) ? Number(raw) : NaN;
|
|
7554
7825
|
if (!Number.isSafeInteger(count) || count < 0) {
|
|
7555
|
-
throw new
|
|
7826
|
+
throw new VibeError16({ code: "VIBE_INTERNAL", message: "relation count payload must be a non-negative safe integer", meta: { relation: selection.counts[index]?.name } });
|
|
7556
7827
|
}
|
|
7557
7828
|
counts[selection.counts[index].name] = count;
|
|
7558
7829
|
delete row[column];
|
|
@@ -7561,7 +7832,7 @@ async function attachLateralRelations(params) {
|
|
|
7561
7832
|
}
|
|
7562
7833
|
}
|
|
7563
7834
|
// packages/runtime/src/extensions.ts
|
|
7564
|
-
import { VibeError as
|
|
7835
|
+
import { VibeError as VibeError17, extensionManifestEntry } from "@vibeorm/schema";
|
|
7565
7836
|
function assertExtensionManifest(params) {
|
|
7566
7837
|
const { manifest } = params;
|
|
7567
7838
|
const instances = params.instances ?? [];
|
|
@@ -7584,7 +7855,7 @@ function assertExtensionManifest(params) {
|
|
|
7584
7855
|
problems.push(`"${name}" is configured but missing from the generated client`);
|
|
7585
7856
|
}
|
|
7586
7857
|
if (problems.length > 0) {
|
|
7587
|
-
throw new
|
|
7858
|
+
throw new VibeError17({
|
|
7588
7859
|
code: "VIBE_STALE_CLIENT",
|
|
7589
7860
|
message: `extension list changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
|
|
7590
7861
|
meta: { manifest, problems }
|
|
@@ -7599,7 +7870,7 @@ function buildExtensionOperatorMap(params) {
|
|
|
7599
7870
|
for (const operator of extension.sql?.whereOperators ?? []) {
|
|
7600
7871
|
const existing = owner.get(operator.name);
|
|
7601
7872
|
if (existing !== undefined) {
|
|
7602
|
-
throw new
|
|
7873
|
+
throw new VibeError17({
|
|
7603
7874
|
code: "VIBE_CONFIG",
|
|
7604
7875
|
message: `where operator "${operator.name}" is claimed by both "${existing}" and "${extension.name}"`,
|
|
7605
7876
|
meta: { operator: operator.name }
|
|
@@ -7615,7 +7886,7 @@ function buildExtensionOperatorMap(params) {
|
|
|
7615
7886
|
options: extension.options
|
|
7616
7887
|
});
|
|
7617
7888
|
if ("error" in compiled) {
|
|
7618
|
-
throw new
|
|
7889
|
+
throw new VibeError17({
|
|
7619
7890
|
code: "VIBE_VALIDATION",
|
|
7620
7891
|
message: `"${operator.name}" on ${model}.${field}: ${compiled.error}`,
|
|
7621
7892
|
meta: { model, field, operator: operator.name, extension: extension.name }
|
|
@@ -7647,9 +7918,53 @@ function buildConnectHooks(params) {
|
|
|
7647
7918
|
return hooks;
|
|
7648
7919
|
}
|
|
7649
7920
|
// packages/runtime/src/views.ts
|
|
7650
|
-
import { VibeError as
|
|
7651
|
-
|
|
7652
|
-
|
|
7921
|
+
import { VibeError as VibeError19 } from "@vibeorm/schema";
|
|
7922
|
+
|
|
7923
|
+
// packages/runtime/src/sql-access.ts
|
|
7924
|
+
import { VibeError as VibeError18 } from "@vibeorm/schema";
|
|
7925
|
+
import { isSqlFragment as isSqlFragment2 } from "@vibeorm/sql";
|
|
7926
|
+
function containsSqlFragment(value, seen = new Set) {
|
|
7927
|
+
if (isSqlFragment2(value))
|
|
7928
|
+
return true;
|
|
7929
|
+
if (typeof value !== "object" || value === null || seen.has(value))
|
|
7930
|
+
return false;
|
|
7931
|
+
seen.add(value);
|
|
7932
|
+
if (Array.isArray(value))
|
|
7933
|
+
return value.some((entry) => containsSqlFragment(entry, seen));
|
|
7934
|
+
return Object.values(value).some((entry) => containsSqlFragment(entry, seen));
|
|
7935
|
+
}
|
|
7936
|
+
function restrictedSqlArgs(params) {
|
|
7937
|
+
const seen = new Map;
|
|
7938
|
+
const copy = (value) => {
|
|
7939
|
+
if (isSqlFragment2(value))
|
|
7940
|
+
throw new VibeError18({ code: "VIBE_VALIDATION", message: `SQL fragments are unavailable through ${params.path}; use the unrestricted client`, meta: { path: params.path } });
|
|
7941
|
+
if (typeof value !== "object" || value === null)
|
|
7942
|
+
return value;
|
|
7943
|
+
if (seen.has(value))
|
|
7944
|
+
return seen.get(value);
|
|
7945
|
+
if (Array.isArray(value)) {
|
|
7946
|
+
const out2 = [];
|
|
7947
|
+
seen.set(value, out2);
|
|
7948
|
+
for (const item of value)
|
|
7949
|
+
out2.push(copy(item));
|
|
7950
|
+
return out2;
|
|
7951
|
+
}
|
|
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
|
+
seen.set(value, out);
|
|
7958
|
+
for (const [key, child] of Object.entries(value))
|
|
7959
|
+
out[key] = copy(child);
|
|
7960
|
+
return out;
|
|
7961
|
+
};
|
|
7962
|
+
return copy(params.value);
|
|
7963
|
+
}
|
|
7964
|
+
|
|
7965
|
+
// packages/runtime/src/views.ts
|
|
7966
|
+
function validationError6(params) {
|
|
7967
|
+
return new VibeError19({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
7653
7968
|
}
|
|
7654
7969
|
|
|
7655
7970
|
class ViewResult {
|
|
@@ -7704,7 +8019,7 @@ function isPlainObject5(value) {
|
|
|
7704
8019
|
function resolveDefinition(params) {
|
|
7705
8020
|
const { meta, name, definition } = params;
|
|
7706
8021
|
if (!isPlainObject5(definition)) {
|
|
7707
|
-
throw
|
|
8022
|
+
throw validationError6({
|
|
7708
8023
|
message: `view "${name}": definition must be an object of { modelKey: { field: true } }`,
|
|
7709
8024
|
meta: { view: name }
|
|
7710
8025
|
});
|
|
@@ -7715,13 +8030,13 @@ function resolveDefinition(params) {
|
|
|
7715
8030
|
continue;
|
|
7716
8031
|
const model = meta.modelByName.get(key);
|
|
7717
8032
|
if (model === undefined) {
|
|
7718
|
-
throw
|
|
8033
|
+
throw validationError6({
|
|
7719
8034
|
message: `view "${name}": unknown model "${key}" in the definition`,
|
|
7720
8035
|
meta: { view: name, model: key }
|
|
7721
8036
|
});
|
|
7722
8037
|
}
|
|
7723
8038
|
if (!isPlainObject5(fieldSpec)) {
|
|
7724
|
-
throw
|
|
8039
|
+
throw validationError6({
|
|
7725
8040
|
message: `view "${name}": definition.${key} must be an object of { field: true }`,
|
|
7726
8041
|
meta: { view: name, model: model.name }
|
|
7727
8042
|
});
|
|
@@ -7731,14 +8046,14 @@ function resolveDefinition(params) {
|
|
|
7731
8046
|
if (flag === undefined || flag === false)
|
|
7732
8047
|
continue;
|
|
7733
8048
|
if (flag !== true) {
|
|
7734
|
-
throw
|
|
8049
|
+
throw validationError6({
|
|
7735
8050
|
message: `view "${name}": definition.${key}.${field} must be true`,
|
|
7736
8051
|
meta: { view: name, model: model.name, field }
|
|
7737
8052
|
});
|
|
7738
8053
|
}
|
|
7739
8054
|
if (!model.fieldByName.has(field)) {
|
|
7740
8055
|
const isRelation = model.relationByName.has(field);
|
|
7741
|
-
throw
|
|
8056
|
+
throw validationError6({
|
|
7742
8057
|
message: isRelation ? `view "${name}": "${field}" on model "${model.name}" is a relation \u2014 a view definition lists scalar fields; relations are restricted through the related model's own entry` : `view "${name}": unknown field "${field}" on model "${model.name}"`,
|
|
7743
8058
|
meta: { view: name, model: model.name, field }
|
|
7744
8059
|
});
|
|
@@ -7746,7 +8061,7 @@ function resolveDefinition(params) {
|
|
|
7746
8061
|
fields.add(field);
|
|
7747
8062
|
}
|
|
7748
8063
|
if (fields.size === 0) {
|
|
7749
|
-
throw
|
|
8064
|
+
throw validationError6({
|
|
7750
8065
|
message: `view "${name}": definition.${key} exposes no fields`,
|
|
7751
8066
|
meta: { view: name, model: model.name }
|
|
7752
8067
|
});
|
|
@@ -7754,7 +8069,7 @@ function resolveDefinition(params) {
|
|
|
7754
8069
|
resolved.set(model.clientName, { model, fields });
|
|
7755
8070
|
}
|
|
7756
8071
|
if (resolved.size === 0) {
|
|
7757
|
-
throw
|
|
8072
|
+
throw validationError6({
|
|
7758
8073
|
message: `view "${name}": the definition names no models`,
|
|
7759
8074
|
meta: { view: name }
|
|
7760
8075
|
});
|
|
@@ -7776,7 +8091,7 @@ function revealSet(params) {
|
|
|
7776
8091
|
if (reveal === undefined)
|
|
7777
8092
|
return revealed;
|
|
7778
8093
|
if (!isPlainObject5(reveal)) {
|
|
7779
|
-
throw
|
|
8094
|
+
throw validationError6({
|
|
7780
8095
|
message: `view "${name}": reveal must be an object of { field: true }`,
|
|
7781
8096
|
meta: { view: name, model: mask.model.name }
|
|
7782
8097
|
});
|
|
@@ -7785,13 +8100,13 @@ function revealSet(params) {
|
|
|
7785
8100
|
if (flag === undefined || flag === false)
|
|
7786
8101
|
continue;
|
|
7787
8102
|
if (flag !== true) {
|
|
7788
|
-
throw
|
|
8103
|
+
throw validationError6({
|
|
7789
8104
|
message: `view "${name}": reveal.${field} must be true or false`,
|
|
7790
8105
|
meta: { view: name, model: mask.model.name, field }
|
|
7791
8106
|
});
|
|
7792
8107
|
}
|
|
7793
8108
|
if (!mask.model.fieldByName.has(field)) {
|
|
7794
|
-
throw
|
|
8109
|
+
throw validationError6({
|
|
7795
8110
|
message: `view "${name}": cannot reveal "${field}" \u2014 not a scalar field of model "${mask.model.name}"`,
|
|
7796
8111
|
meta: { view: name, model: mask.model.name, field }
|
|
7797
8112
|
});
|
|
@@ -7805,7 +8120,7 @@ function mergeLevel(params) {
|
|
|
7805
8120
|
const { meta, name, definition, mask, args } = params;
|
|
7806
8121
|
const model = mask.model;
|
|
7807
8122
|
if (args.omit !== undefined) {
|
|
7808
|
-
throw
|
|
8123
|
+
throw validationError6({
|
|
7809
8124
|
message: `view "${name}": omit is not available on a view query \u2014 the definition already restricts ${model.name}'s fields`,
|
|
7810
8125
|
meta: { view: name, model: model.name }
|
|
7811
8126
|
});
|
|
@@ -7814,7 +8129,7 @@ function mergeLevel(params) {
|
|
|
7814
8129
|
let scalars;
|
|
7815
8130
|
const userSelect = args.select;
|
|
7816
8131
|
if (userSelect !== undefined && !isPlainObject5(userSelect)) {
|
|
7817
|
-
throw
|
|
8132
|
+
throw validationError6({
|
|
7818
8133
|
message: `view "${name}": select must be an object`,
|
|
7819
8134
|
meta: { view: name, model: model.name }
|
|
7820
8135
|
});
|
|
@@ -7838,7 +8153,7 @@ function mergeLevel(params) {
|
|
|
7838
8153
|
if (source === undefined)
|
|
7839
8154
|
return;
|
|
7840
8155
|
if (!isPlainObject5(source)) {
|
|
7841
|
-
throw
|
|
8156
|
+
throw validationError6({
|
|
7842
8157
|
message: `view "${name}": ${argName} must be an object`,
|
|
7843
8158
|
meta: { view: name, model: model.name }
|
|
7844
8159
|
});
|
|
@@ -7853,7 +8168,7 @@ function mergeLevel(params) {
|
|
|
7853
8168
|
if (model.relationByName.has(key))
|
|
7854
8169
|
relationSpecs.set(key, spec);
|
|
7855
8170
|
else if (argName === "include") {
|
|
7856
|
-
throw
|
|
8171
|
+
throw validationError6({
|
|
7857
8172
|
message: `view "${name}": unknown relation "${key}" on model "${model.name}" in include`,
|
|
7858
8173
|
meta: { view: name, model: model.name, relation: key }
|
|
7859
8174
|
});
|
|
@@ -7886,7 +8201,7 @@ function mergeLevel(params) {
|
|
|
7886
8201
|
continue;
|
|
7887
8202
|
}
|
|
7888
8203
|
if (!isPlainObject5(spec)) {
|
|
7889
|
-
throw
|
|
8204
|
+
throw validationError6({
|
|
7890
8205
|
message: `view "${name}": include.${relationName} must be true or an object`,
|
|
7891
8206
|
meta: { view: name, model: model.name, relation: relationName }
|
|
7892
8207
|
});
|
|
@@ -7947,9 +8262,9 @@ var VIEW_WRITE_METHODS = [
|
|
|
7947
8262
|
function createViewDelegate(params) {
|
|
7948
8263
|
const { meta, name, definition, mask, delegate } = params;
|
|
7949
8264
|
const merge = (args) => {
|
|
7950
|
-
const raw = args ?? {};
|
|
8265
|
+
const raw = restrictedSqlArgs({ value: args ?? {}, path: "$defineView" });
|
|
7951
8266
|
if (!isPlainObject5(raw)) {
|
|
7952
|
-
throw
|
|
8267
|
+
throw validationError6({
|
|
7953
8268
|
message: `view "${name}": query args must be an object`,
|
|
7954
8269
|
meta: { view: name, model: mask.model.name }
|
|
7955
8270
|
});
|
|
@@ -7982,13 +8297,13 @@ function createViewDelegate(params) {
|
|
|
7982
8297
|
const { merged, plan } = merge(args);
|
|
7983
8298
|
return wrapRows({ rows: [await delegate.findUniqueOrThrow(merged)], plan })[0];
|
|
7984
8299
|
},
|
|
7985
|
-
count: (args) => delegate.count(args),
|
|
7986
|
-
aggregate: (args) => delegate.aggregate(args),
|
|
7987
|
-
groupBy: (args) => delegate.groupBy(args)
|
|
8300
|
+
count: (args) => delegate.count(restrictedSqlArgs({ value: args, path: "$defineView" })),
|
|
8301
|
+
aggregate: (args) => delegate.aggregate(restrictedSqlArgs({ value: args, path: "$defineView" })),
|
|
8302
|
+
groupBy: (args) => delegate.groupBy(restrictedSqlArgs({ value: args, path: "$defineView" }))
|
|
7988
8303
|
};
|
|
7989
8304
|
for (const method of VIEW_WRITE_METHODS) {
|
|
7990
8305
|
view[method] = () => {
|
|
7991
|
-
throw
|
|
8306
|
+
throw validationError6({
|
|
7992
8307
|
message: `view "${name}" is read-only \u2014 ${method}() is not available on a field-masking view; write through the base client instead`,
|
|
7993
8308
|
meta: { view: name, model: mask.model.name, method }
|
|
7994
8309
|
});
|
|
@@ -8003,7 +8318,7 @@ function createView(params) {
|
|
|
8003
8318
|
for (const [clientName, mask] of resolved) {
|
|
8004
8319
|
const delegate = models[clientName];
|
|
8005
8320
|
if (delegate === undefined) {
|
|
8006
|
-
throw new
|
|
8321
|
+
throw new VibeError19({
|
|
8007
8322
|
code: "VIBE_INTERNAL",
|
|
8008
8323
|
message: `view "${name}": no delegate for model "${mask.model.name}"`,
|
|
8009
8324
|
meta: { view: name, model: mask.model.name }
|
|
@@ -8019,9 +8334,9 @@ function attachDefineView(params) {
|
|
|
8019
8334
|
Object.defineProperty(client, "$defineView", { value, enumerable: true });
|
|
8020
8335
|
}
|
|
8021
8336
|
// packages/runtime/src/validators.ts
|
|
8022
|
-
import { VibeError as
|
|
8337
|
+
import { VibeError as VibeError20 } from "@vibeorm/schema";
|
|
8023
8338
|
function validationFailed(params) {
|
|
8024
|
-
return new
|
|
8339
|
+
return new VibeError20({
|
|
8025
8340
|
code: "VIBE_VALIDATION",
|
|
8026
8341
|
message: `validation failed for ${params.model}.${params.field}: ${params.issues[0]?.message ?? "invalid value"}`,
|
|
8027
8342
|
meta: { model: params.model, field: params.field, issues: params.issues }
|
|
@@ -8030,7 +8345,7 @@ function validationFailed(params) {
|
|
|
8030
8345
|
function validateValue(params) {
|
|
8031
8346
|
const result = params.schema["~standard"].validate(params.value);
|
|
8032
8347
|
if (result instanceof Promise) {
|
|
8033
|
-
throw new
|
|
8348
|
+
throw new VibeError20({
|
|
8034
8349
|
code: "VIBE_VALIDATION",
|
|
8035
8350
|
message: `async validator on ${params.model}.${params.field} \u2014 write validators must be synchronous (validate before calling the client for async rules)`,
|
|
8036
8351
|
meta: { model: params.model, field: params.field }
|
|
@@ -8153,9 +8468,270 @@ function deferPolicyOperation(params) {
|
|
|
8153
8468
|
}
|
|
8154
8469
|
|
|
8155
8470
|
// packages/runtime/src/policy.ts
|
|
8156
|
-
import { VibeError as
|
|
8471
|
+
import { VibeError as VibeError21 } from "@vibeorm/schema";
|
|
8472
|
+
|
|
8473
|
+
// packages/runtime/src/read-only.ts
|
|
8474
|
+
import { ReadOnlyClientError } from "@vibeorm/schema";
|
|
8475
|
+
var READ_DELEGATE_METHODS = Object.freeze([
|
|
8476
|
+
"findMany",
|
|
8477
|
+
"findFirst",
|
|
8478
|
+
"findFirstOrThrow",
|
|
8479
|
+
"findUnique",
|
|
8480
|
+
"findUniqueOrThrow",
|
|
8481
|
+
"count",
|
|
8482
|
+
"aggregate",
|
|
8483
|
+
"groupBy"
|
|
8484
|
+
]);
|
|
8485
|
+
var WRITE_DELEGATE_METHODS = Object.freeze([
|
|
8486
|
+
"create",
|
|
8487
|
+
"createMany",
|
|
8488
|
+
"createManyAndReturn",
|
|
8489
|
+
"update",
|
|
8490
|
+
"updateMany",
|
|
8491
|
+
"updateManyAndReturn",
|
|
8492
|
+
"upsert",
|
|
8493
|
+
"upsertMany",
|
|
8494
|
+
"delete",
|
|
8495
|
+
"deleteMany"
|
|
8496
|
+
]);
|
|
8497
|
+
var DELEGATE_METHOD_ACCESS = Object.freeze(Object.fromEntries([
|
|
8498
|
+
...READ_DELEGATE_METHODS.map((method) => [method, "read"]),
|
|
8499
|
+
...WRITE_DELEGATE_METHODS.map((method) => [method, "write"])
|
|
8500
|
+
]));
|
|
8501
|
+
function delegateMethodAccess(params) {
|
|
8502
|
+
return Object.hasOwn(DELEGATE_METHOD_ACCESS, params.method) ? DELEGATE_METHOD_ACCESS[params.method] : undefined;
|
|
8503
|
+
}
|
|
8504
|
+
var CLIENT_MEMBER_ACCESS = Object.freeze({
|
|
8505
|
+
$models: "rebuilt",
|
|
8506
|
+
$on: "forwarded",
|
|
8507
|
+
$transaction: "rebuilt",
|
|
8508
|
+
$defineView: "rebuilt",
|
|
8509
|
+
$scoped: "rebuilt",
|
|
8510
|
+
$withPolicy: "rebuilt",
|
|
8511
|
+
$bypassPolicy: "rebuilt",
|
|
8512
|
+
$readOnly: "rebuilt",
|
|
8513
|
+
$querySql: "raw",
|
|
8514
|
+
$executeSql: "raw",
|
|
8515
|
+
$queryRaw: "raw",
|
|
8516
|
+
$executeRaw: "raw",
|
|
8517
|
+
$queryRawUnsafe: "raw",
|
|
8518
|
+
$executeRawUnsafe: "raw",
|
|
8519
|
+
$connect: "control",
|
|
8520
|
+
$disconnect: "control",
|
|
8521
|
+
$advisoryLock: "control",
|
|
8522
|
+
$tryAdvisoryLock: "control",
|
|
8523
|
+
$withContext: "control"
|
|
8524
|
+
});
|
|
8525
|
+
function clientMemberAccess(params) {
|
|
8526
|
+
return Object.hasOwn(CLIENT_MEMBER_ACCESS, params.member) ? CLIENT_MEMBER_ACCESS[params.member] : undefined;
|
|
8527
|
+
}
|
|
8528
|
+
var REASON_HINTS = Object.freeze({
|
|
8529
|
+
write: "it writes rows \u2014 run it on the writable client",
|
|
8530
|
+
raw: "raw SQL is not available on a read-only client (a SELECT-shaped statement is not proof of a read); run it on the writable client, or use a database read-only role when the database itself must enforce it",
|
|
8531
|
+
control: "it controls the connection or the transaction rather than reading; run it on the writable client",
|
|
8532
|
+
unclassified: "it is not classified as a read \u2014 extensions opt in per surface, and anything unclassified stays denied",
|
|
8533
|
+
foreign_operation: "$transaction([...]) on a read-only client takes read operations only; this one would write"
|
|
8534
|
+
});
|
|
8535
|
+
function readOnlyRefusal(params) {
|
|
8536
|
+
const { path, reason } = params;
|
|
8537
|
+
return new ReadOnlyClientError({
|
|
8538
|
+
path,
|
|
8539
|
+
reason,
|
|
8540
|
+
message: `$readOnly: ${path}${reason === "foreign_operation" ? "" : "()"} is not available on a read-only client \u2014 ${REASON_HINTS[reason]}`,
|
|
8541
|
+
...params.meta === undefined ? {} : { meta: params.meta }
|
|
8542
|
+
});
|
|
8543
|
+
}
|
|
8544
|
+
var contexts = new WeakMap;
|
|
8545
|
+
var facades = new WeakMap;
|
|
8546
|
+
function bound(params) {
|
|
8547
|
+
const { value, receiver } = params;
|
|
8548
|
+
return typeof value === "function" ? value.bind(receiver) : undefined;
|
|
8549
|
+
}
|
|
8550
|
+
function classifiedMount(params) {
|
|
8551
|
+
const { value, readOnly } = params;
|
|
8552
|
+
if (readOnly === undefined)
|
|
8553
|
+
return;
|
|
8554
|
+
if (readOnly === true)
|
|
8555
|
+
return typeof value === "function" ? (...args) => {
|
|
8556
|
+
if (containsSqlFragment(args))
|
|
8557
|
+
return Promise.reject(readOnlyRefusal({ path: "extension", reason: "raw" }));
|
|
8558
|
+
return value(...restrictedSqlArgs({ value: args, path: "$readOnly extension" }));
|
|
8559
|
+
} : undefined;
|
|
8560
|
+
if (typeof value !== "object" || value === null)
|
|
8561
|
+
return;
|
|
8562
|
+
const source = value;
|
|
8563
|
+
const namespace = {};
|
|
8564
|
+
for (const member of readOnly) {
|
|
8565
|
+
const fn = bound({ value: source[member], receiver: namespace });
|
|
8566
|
+
if (fn !== undefined)
|
|
8567
|
+
namespace[member] = (...args) => {
|
|
8568
|
+
if (containsSqlFragment(args))
|
|
8569
|
+
return Promise.reject(readOnlyRefusal({ path: member, reason: "raw" }));
|
|
8570
|
+
return fn(...restrictedSqlArgs({ value: args, path: "$readOnly extension" }));
|
|
8571
|
+
};
|
|
8572
|
+
}
|
|
8573
|
+
return Object.freeze(namespace);
|
|
8574
|
+
}
|
|
8575
|
+
function readOnlyDelegate(params) {
|
|
8576
|
+
const { model, mounts } = params;
|
|
8577
|
+
const base = params.base;
|
|
8578
|
+
const delegate = {};
|
|
8579
|
+
for (const method of READ_DELEGATE_METHODS) {
|
|
8580
|
+
const fn = bound({ value: base[method], receiver: base });
|
|
8581
|
+
if (fn !== undefined)
|
|
8582
|
+
delegate[method] = (...args) => {
|
|
8583
|
+
if (containsSqlFragment(args))
|
|
8584
|
+
return Promise.reject(readOnlyRefusal({ path: `${model.clientName}.${method}`, reason: "raw" }));
|
|
8585
|
+
const operation = fn(...args);
|
|
8586
|
+
params.operations.restrictSql?.({ value: operation });
|
|
8587
|
+
return operation;
|
|
8588
|
+
};
|
|
8589
|
+
}
|
|
8590
|
+
for (const method of WRITE_DELEGATE_METHODS) {
|
|
8591
|
+
const refusal = () => Promise.reject(readOnlyRefusal({
|
|
8592
|
+
path: `${model.clientName}.${method}`,
|
|
8593
|
+
reason: "write",
|
|
8594
|
+
meta: { model: model.name, method }
|
|
8595
|
+
}));
|
|
8596
|
+
delegate[method] = refusal;
|
|
8597
|
+
}
|
|
8598
|
+
for (const mount of mounts) {
|
|
8599
|
+
if (mount.scope !== "model" || mount.model !== model.name)
|
|
8600
|
+
continue;
|
|
8601
|
+
const classified = classifiedMount({ value: base[mount.property], readOnly: mount.readOnly });
|
|
8602
|
+
delegate[mount.property] = classified ?? (() => Promise.reject(readOnlyRefusal({
|
|
8603
|
+
path: `${model.clientName}.${mount.property}`,
|
|
8604
|
+
reason: "unclassified",
|
|
8605
|
+
meta: { model: model.name, method: mount.property, extension: mount.extension }
|
|
8606
|
+
})));
|
|
8607
|
+
}
|
|
8608
|
+
return Object.freeze(delegate);
|
|
8609
|
+
}
|
|
8610
|
+
function refuseBatch(params) {
|
|
8611
|
+
for (const operation of params.operations) {
|
|
8612
|
+
params.bridge.refuse({ value: operation, reason: params.reason });
|
|
8613
|
+
}
|
|
8614
|
+
return Promise.reject(params.reason);
|
|
8615
|
+
}
|
|
8616
|
+
function buildReadOnly(params) {
|
|
8617
|
+
const { client, context } = params;
|
|
8618
|
+
const { meta, mounts, operations } = context;
|
|
8619
|
+
const models = {};
|
|
8620
|
+
const facade = {};
|
|
8621
|
+
for (const model of meta.models) {
|
|
8622
|
+
const base = client.$models[model.name];
|
|
8623
|
+
if (base === undefined)
|
|
8624
|
+
continue;
|
|
8625
|
+
const delegate = readOnlyDelegate({ base, model, mounts, operations });
|
|
8626
|
+
models[model.name] = delegate;
|
|
8627
|
+
models[model.clientName] = delegate;
|
|
8628
|
+
}
|
|
8629
|
+
facade.$models = Object.freeze(models);
|
|
8630
|
+
for (const model of meta.models) {
|
|
8631
|
+
const delegate = models[model.clientName];
|
|
8632
|
+
if (delegate !== undefined) {
|
|
8633
|
+
Object.defineProperty(facade, model.clientName, { value: delegate, enumerable: true });
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
8636
|
+
facade.$on = client.$on.bind(client);
|
|
8637
|
+
facade.$transaction = (arg, options) => {
|
|
8638
|
+
if (typeof arg === "function") {
|
|
8639
|
+
const run = arg;
|
|
8640
|
+
return client.$transaction((tx) => run(readOnlyOver({ client: tx, context })), options);
|
|
8641
|
+
}
|
|
8642
|
+
if (Array.isArray(arg)) {
|
|
8643
|
+
for (const operation of arg) {
|
|
8644
|
+
const method = operations.methodOf({ value: operation });
|
|
8645
|
+
if (method === undefined)
|
|
8646
|
+
continue;
|
|
8647
|
+
if (delegateMethodAccess({ method }) === "read") {
|
|
8648
|
+
operations.restrictSql?.({ value: operation });
|
|
8649
|
+
continue;
|
|
8650
|
+
}
|
|
8651
|
+
return refuseBatch({
|
|
8652
|
+
operations: arg,
|
|
8653
|
+
reason: readOnlyRefusal({
|
|
8654
|
+
path: `$transaction([\u2026 ${method}() \u2026])`,
|
|
8655
|
+
reason: "foreign_operation",
|
|
8656
|
+
meta: { method }
|
|
8657
|
+
}),
|
|
8658
|
+
bridge: operations
|
|
8659
|
+
});
|
|
8660
|
+
}
|
|
8661
|
+
return client.$transaction(arg, options);
|
|
8662
|
+
}
|
|
8663
|
+
return client.$transaction(arg, options);
|
|
8664
|
+
};
|
|
8665
|
+
attachDefineView({ client: facade, meta, models });
|
|
8666
|
+
const derive = (member) => {
|
|
8667
|
+
const source = client[member];
|
|
8668
|
+
if (source === undefined)
|
|
8669
|
+
return;
|
|
8670
|
+
facade[member] = (arg) => {
|
|
8671
|
+
const derived = source.call(client, arg);
|
|
8672
|
+
return readOnlyOver({ client: derived, context });
|
|
8673
|
+
};
|
|
8674
|
+
};
|
|
8675
|
+
derive("$scoped");
|
|
8676
|
+
derive("$withPolicy");
|
|
8677
|
+
derive("$bypassPolicy");
|
|
8678
|
+
facade.$readOnly = () => facade;
|
|
8679
|
+
for (const [member, access] of Object.entries(CLIENT_MEMBER_ACCESS)) {
|
|
8680
|
+
if (access !== "raw" && access !== "control")
|
|
8681
|
+
continue;
|
|
8682
|
+
const reason = access === "raw" ? "raw" : "control";
|
|
8683
|
+
facade[member] = member === "$withContext" ? () => {
|
|
8684
|
+
throw readOnlyRefusal({ path: member, reason, meta: { method: member } });
|
|
8685
|
+
} : () => Promise.reject(readOnlyRefusal({ path: member, reason, meta: { method: member } }));
|
|
8686
|
+
}
|
|
8687
|
+
for (const mount of mounts) {
|
|
8688
|
+
if (mount.scope !== "client")
|
|
8689
|
+
continue;
|
|
8690
|
+
const classified = classifiedMount({ value: client[mount.property], readOnly: mount.readOnly });
|
|
8691
|
+
facade[mount.property] = classified ?? (() => Promise.reject(readOnlyRefusal({
|
|
8692
|
+
path: mount.property,
|
|
8693
|
+
reason: "unclassified",
|
|
8694
|
+
meta: { method: mount.property, extension: mount.extension }
|
|
8695
|
+
})));
|
|
8696
|
+
}
|
|
8697
|
+
return Object.freeze(facade);
|
|
8698
|
+
}
|
|
8699
|
+
function readOnlyOver(params) {
|
|
8700
|
+
const existing = facades.get(params.client);
|
|
8701
|
+
if (existing !== undefined)
|
|
8702
|
+
return existing;
|
|
8703
|
+
const built = buildReadOnly(params);
|
|
8704
|
+
facades.set(params.client, built);
|
|
8705
|
+
return built;
|
|
8706
|
+
}
|
|
8707
|
+
function attachReadOnly(params) {
|
|
8708
|
+
const context = {
|
|
8709
|
+
meta: params.meta,
|
|
8710
|
+
mounts: params.mounts ?? [],
|
|
8711
|
+
operations: params.operations
|
|
8712
|
+
};
|
|
8713
|
+
contexts.set(params.client, context);
|
|
8714
|
+
Object.defineProperty(params.client, "$readOnly", {
|
|
8715
|
+
value: () => readOnlyOver({ client: params.client, context }),
|
|
8716
|
+
enumerable: true,
|
|
8717
|
+
configurable: true
|
|
8718
|
+
});
|
|
8719
|
+
}
|
|
8720
|
+
function inheritReadOnly(params) {
|
|
8721
|
+
const context = contexts.get(params.from);
|
|
8722
|
+
if (context === undefined)
|
|
8723
|
+
return;
|
|
8724
|
+
contexts.set(params.to, context);
|
|
8725
|
+
Object.defineProperty(params.to, "$readOnly", {
|
|
8726
|
+
value: () => readOnlyOver({ client: params.to, context }),
|
|
8727
|
+
enumerable: true,
|
|
8728
|
+
configurable: true
|
|
8729
|
+
});
|
|
8730
|
+
}
|
|
8731
|
+
|
|
8732
|
+
// packages/runtime/src/policy.ts
|
|
8157
8733
|
function policyError(message, meta) {
|
|
8158
|
-
return new
|
|
8734
|
+
return new VibeError21({ code: "VIBE_VALIDATION", message, meta: meta ?? {} });
|
|
8159
8735
|
}
|
|
8160
8736
|
function policiedModels(meta) {
|
|
8161
8737
|
return meta.models.filter((model) => model.policy !== undefined);
|
|
@@ -8364,13 +8940,15 @@ function scopeSelection(params) {
|
|
|
8364
8940
|
return out;
|
|
8365
8941
|
}
|
|
8366
8942
|
function scopeArgs(params) {
|
|
8367
|
-
const { meta, model,
|
|
8943
|
+
const { meta, model, context } = params;
|
|
8944
|
+
const args = restrictedSqlArgs({ value: params.args, path: "$withPolicy" });
|
|
8368
8945
|
const scoped = scopeSelection({ meta, model, args, context });
|
|
8369
8946
|
return scoped.where === undefined ? scoped : { ...scoped, where: scopeWhere({ meta, model, where: scoped.where, context }) };
|
|
8370
8947
|
}
|
|
8371
8948
|
var REFERENCE_VERBS = ["connect", "connectOrCreate", "set", "disconnect", "update", "updateMany", "delete", "deleteMany", "upsert"];
|
|
8372
8949
|
function forceWriteData(params) {
|
|
8373
|
-
const { meta, model,
|
|
8950
|
+
const { meta, model, context, phase } = params;
|
|
8951
|
+
const data = restrictedSqlArgs({ value: params.data, path: "$withPolicy" });
|
|
8374
8952
|
let out = { ...data };
|
|
8375
8953
|
if (model.policy !== undefined) {
|
|
8376
8954
|
const value = predicateFor({ model, context })[model.policy.field];
|
|
@@ -8499,7 +9077,7 @@ function scopedDelegate(params) {
|
|
|
8499
9077
|
const pkWhere = pk.length === 1 ? { [pk[0]]: target[pk[0]] } : { [pkClientKey()]: Object.fromEntries(pk.map((name) => [name, target[name]])) };
|
|
8500
9078
|
return write(raw, pkWhere);
|
|
8501
9079
|
};
|
|
8502
|
-
const notFound2 = (method) => new
|
|
9080
|
+
const notFound2 = (method) => new VibeError21({ 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 } });
|
|
8503
9081
|
return {
|
|
8504
9082
|
findMany: (args) => base.findMany(scopeFind(args)),
|
|
8505
9083
|
findFirst: (args) => base.findFirst(scopeFind(args)),
|
|
@@ -8533,7 +9111,7 @@ function scopedDelegate(params) {
|
|
|
8533
9111
|
})
|
|
8534
9112
|
})),
|
|
8535
9113
|
upsertMany: () => {
|
|
8536
|
-
throw new
|
|
9114
|
+
throw new VibeError21({
|
|
8537
9115
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
8538
9116
|
message: "upsertMany is not available on a policy-bound client: its conflict target is not policy-checked yet",
|
|
8539
9117
|
meta: { model: model.name }
|
|
@@ -8542,7 +9120,7 @@ function scopedDelegate(params) {
|
|
|
8542
9120
|
updateManyAndReturn: (args) => deferred(() => {
|
|
8543
9121
|
const run = base.updateManyAndReturn;
|
|
8544
9122
|
if (run === undefined) {
|
|
8545
|
-
throw new
|
|
9123
|
+
throw new VibeError21({
|
|
8546
9124
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
8547
9125
|
message: "updateManyAndReturn is not available on this delegate",
|
|
8548
9126
|
meta: { model: model.name }
|
|
@@ -8613,7 +9191,7 @@ function globalDelegate(params) {
|
|
|
8613
9191
|
updateManyAndReturn: (args) => deferred(() => {
|
|
8614
9192
|
const run = base.updateManyAndReturn;
|
|
8615
9193
|
if (run === undefined) {
|
|
8616
|
-
throw new
|
|
9194
|
+
throw new VibeError21({
|
|
8617
9195
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
8618
9196
|
message: "updateManyAndReturn is not available on this delegate"
|
|
8619
9197
|
});
|
|
@@ -8624,7 +9202,7 @@ function globalDelegate(params) {
|
|
|
8624
9202
|
upsertMany: (args) => deferred(() => {
|
|
8625
9203
|
if (params.policyBound === false)
|
|
8626
9204
|
return base.upsertMany(args);
|
|
8627
|
-
throw new
|
|
9205
|
+
throw new VibeError21({
|
|
8628
9206
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
8629
9207
|
message: "upsertMany is not available on a policy-bound client: its conflict target is not policy-checked yet"
|
|
8630
9208
|
});
|
|
@@ -8722,6 +9300,7 @@ function attachPolicy(params) {
|
|
|
8722
9300
|
}
|
|
8723
9301
|
Object.defineProperty(scoped, "$models", { value: scopedModels, enumerable: true });
|
|
8724
9302
|
attachDefineView({ client: scoped, meta, models: scopedModels });
|
|
9303
|
+
inheritReadOnly({ from: client, to: scoped });
|
|
8725
9304
|
Object.defineProperty(scoped, "$transaction", {
|
|
8726
9305
|
value: (arg, options) => typeof arg === "function" ? client.$transaction((tx) => arg(buildScopedOver(tx, context)), options) : client.$transaction(arg, options),
|
|
8727
9306
|
enumerable: true
|
|
@@ -8753,6 +9332,7 @@ function attachPolicy(params) {
|
|
|
8753
9332
|
}
|
|
8754
9333
|
Object.defineProperty(inner, "$models", { value: scopedModels, enumerable: true });
|
|
8755
9334
|
attachDefineView({ client: inner, meta, models: scopedModels });
|
|
9335
|
+
inheritReadOnly({ from: tx, to: inner });
|
|
8756
9336
|
Object.defineProperty(inner, "$transaction", {
|
|
8757
9337
|
value: (arg, options) => typeof arg === "function" ? tx.$transaction((nested) => arg(buildScopedOver(nested, context)), options) : tx.$transaction(arg, options),
|
|
8758
9338
|
enumerable: true,
|
|
@@ -8773,13 +9353,14 @@ function attachPolicy(params) {
|
|
|
8773
9353
|
}
|
|
8774
9354
|
Object.defineProperty(bypass, "$models", { value: models, enumerable: true });
|
|
8775
9355
|
attachDefineView({ client: bypass, meta, models });
|
|
9356
|
+
inheritReadOnly({ from: client, to: bypass });
|
|
8776
9357
|
return bypass;
|
|
8777
9358
|
},
|
|
8778
9359
|
enumerable: true
|
|
8779
9360
|
});
|
|
8780
9361
|
}
|
|
8781
9362
|
// packages/runtime/src/scoped.ts
|
|
8782
|
-
import { ScopedQueryError, VibeError as
|
|
9363
|
+
import { ScopedQueryError, VibeError as VibeError22 } from "@vibeorm/schema";
|
|
8783
9364
|
function refusal(params) {
|
|
8784
9365
|
return new ScopedQueryError(params);
|
|
8785
9366
|
}
|
|
@@ -9511,7 +10092,7 @@ function rewriteScopedCall(params) {
|
|
|
9511
10092
|
const entry = entryOf(ctx, model);
|
|
9512
10093
|
const scoped = entry.kind !== "none";
|
|
9513
10094
|
const root = `${model.clientName}.${method}`;
|
|
9514
|
-
const args = {
|
|
10095
|
+
const args = restrictedSqlArgs({ value: params.args ?? {}, path: "$scoped" });
|
|
9515
10096
|
const ownPred = () => requirePred({ ctx, model, path: root });
|
|
9516
10097
|
const walkOwnWhere = (where, path) => isPlainObject8(where) ? walkWhere({ ctx, model, where, path }) : where;
|
|
9517
10098
|
if (FIND_LIKE.includes(method)) {
|
|
@@ -9689,7 +10270,7 @@ function forbidden(params) {
|
|
|
9689
10270
|
message: `$scoped: ${params.method} is not available on a scoped handle \u2014 raw SQL cannot be tenant-verified; run it on the BASE client (the deliberate unscoped escape)`
|
|
9690
10271
|
});
|
|
9691
10272
|
}
|
|
9692
|
-
var RAW_METHODS = ["$queryRaw", "$executeRaw", "$queryRawUnsafe", "$executeRawUnsafe"];
|
|
10273
|
+
var RAW_METHODS = ["$querySql", "$executeSql", "$queryRaw", "$executeRaw", "$queryRawUnsafe", "$executeRawUnsafe"];
|
|
9693
10274
|
function buildScopedOver(params) {
|
|
9694
10275
|
const { ctx, client } = params;
|
|
9695
10276
|
const scoped = Object.create(client);
|
|
@@ -9705,6 +10286,7 @@ function buildScopedOver(params) {
|
|
|
9705
10286
|
}
|
|
9706
10287
|
Object.defineProperty(scoped, "$models", { value: scopedModels, enumerable: true });
|
|
9707
10288
|
attachDefineView({ client: scoped, meta: ctx.meta, models: scopedModels });
|
|
10289
|
+
inheritReadOnly({ from: client, to: scoped });
|
|
9708
10290
|
Object.defineProperty(scoped, "$transaction", {
|
|
9709
10291
|
value: (arg, options) => typeof arg === "function" ? client.$transaction((tx) => arg(buildScopedOver({ ctx, client: tx })), options) : client.$transaction(arg, options),
|
|
9710
10292
|
enumerable: true
|
|
@@ -9733,7 +10315,7 @@ function attachScoped(params) {
|
|
|
9733
10315
|
Object.defineProperty(client, "$scoped", {
|
|
9734
10316
|
value: (config) => {
|
|
9735
10317
|
if (!isPlainObject8(config) || !isPlainObject8(config.models)) {
|
|
9736
|
-
throw new
|
|
10318
|
+
throw new VibeError22({
|
|
9737
10319
|
code: "VIBE_VALIDATION",
|
|
9738
10320
|
message: "$scoped takes { scope: string | number | bigint | null, models: { <Model>: \u2026 } }"
|
|
9739
10321
|
});
|
|
@@ -9749,7 +10331,7 @@ function attachScoped(params) {
|
|
|
9749
10331
|
}
|
|
9750
10332
|
const scope = config.scope;
|
|
9751
10333
|
if (scope !== null && typeof scope !== "string" && typeof scope !== "number" && typeof scope !== "bigint") {
|
|
9752
|
-
throw new
|
|
10334
|
+
throw new VibeError22({
|
|
9753
10335
|
code: "VIBE_VALIDATION",
|
|
9754
10336
|
message: `$scoped: scope must be a string, number, bigint or null \u2014 got ${typeof scope}`
|
|
9755
10337
|
});
|
|
@@ -9765,7 +10347,7 @@ function attachScoped(params) {
|
|
|
9765
10347
|
});
|
|
9766
10348
|
}
|
|
9767
10349
|
// packages/runtime/src/rls-readiness.ts
|
|
9768
|
-
import { VibeError as
|
|
10350
|
+
import { VibeError as VibeError23, assertValidSchemaIR, unsupportedCapability as unsupportedCapability7 } from "@vibeorm/schema";
|
|
9769
10351
|
import { NATIVE_RLS as NATIVE_RLS2, getSqlDialect as getSqlDialect4, renderRlsPolicy, renderRlsRole, NATIVE_RLS_PROBE_POLICY, renderRlsReadinessQuery, renderRlsProbeTable } from "@vibeorm/sql";
|
|
9770
10352
|
import { RLS_READINESS_SQL, NATIVE_RLS_PROBE_POLICY as NATIVE_RLS_PROBE_POLICY2 } from "@vibeorm/sql";
|
|
9771
10353
|
function textOf(params) {
|
|
@@ -9816,10 +10398,10 @@ function unprotectedTablesOf(params) {
|
|
|
9816
10398
|
function requireNativeRls(params) {
|
|
9817
10399
|
const dialect = getSqlDialect4({ dialect: params.adapter.dialect });
|
|
9818
10400
|
if (!dialect.capabilities.nativeRowLevelSecurity) {
|
|
9819
|
-
throw
|
|
10401
|
+
throw unsupportedCapability7({ dialect: dialect.name, feature: "nativeRowLevelSecurity" });
|
|
9820
10402
|
}
|
|
9821
10403
|
if (params.schema.rls === undefined) {
|
|
9822
|
-
throw new
|
|
10404
|
+
throw new VibeError23({
|
|
9823
10405
|
code: "VIBE_CONFIG",
|
|
9824
10406
|
message: "Native row-level security readiness requires a schema that declares native RLS"
|
|
9825
10407
|
});
|
|
@@ -10231,7 +10813,7 @@ async function inspectNativeRls(params) {
|
|
|
10231
10813
|
function requireEmbeddedRole(params) {
|
|
10232
10814
|
const { role } = params;
|
|
10233
10815
|
if (typeof role !== "string" || role.length === 0 || role.includes("\x00")) {
|
|
10234
|
-
throw new
|
|
10816
|
+
throw new VibeError23({
|
|
10235
10817
|
code: "VIBE_CONFIG",
|
|
10236
10818
|
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",
|
|
10237
10819
|
meta: { provider: "pglite" }
|
|
@@ -10243,7 +10825,7 @@ function assertNativeRlsReady(params) {
|
|
|
10243
10825
|
const { report } = params;
|
|
10244
10826
|
if (report.ready)
|
|
10245
10827
|
return;
|
|
10246
|
-
throw new
|
|
10828
|
+
throw new VibeError23({
|
|
10247
10829
|
code: "VIBE_CONFIG",
|
|
10248
10830
|
message: `Native row-level security is not ready on this database: ${report.problems.map((problem) => `${problem.name} (${problem.status})`).join(", ")}`,
|
|
10249
10831
|
meta: {
|
|
@@ -10268,11 +10850,13 @@ function formatNativeRlsReport(params) {
|
|
|
10268
10850
|
return lines;
|
|
10269
10851
|
}
|
|
10270
10852
|
// packages/runtime/src/client.ts
|
|
10271
|
-
import {
|
|
10272
|
-
import {
|
|
10853
|
+
import { compileSql } from "@vibeorm/sql";
|
|
10854
|
+
import { RlsContextError as RlsContextError2, VibeError as VibeError27, assertValidSchemaIR as assertValidSchemaIR2, unsupportedCapability as unsupportedCapability8 } from "@vibeorm/schema";
|
|
10855
|
+
import { ADVISORY_LOCK_RESULT_COLUMN, ParamCollector, getSqlDialect as getSqlDialect5, renderRlsContext, renderRlsPolicy as renderRlsPolicy2, renderRlsRole as renderRlsRole2, renderStatement as renderStatement5 } from "@vibeorm/sql";
|
|
10273
10856
|
|
|
10274
10857
|
// packages/runtime/src/render-cache.ts
|
|
10275
10858
|
import {
|
|
10859
|
+
isSqlFragment as isSqlFragment3,
|
|
10276
10860
|
isDbNowMarker,
|
|
10277
10861
|
isJsonParamCell,
|
|
10278
10862
|
isListSetOperation,
|
|
@@ -10285,12 +10869,25 @@ var MAX_ENTRIES = 512;
|
|
|
10285
10869
|
|
|
10286
10870
|
class StatementRenderCache {
|
|
10287
10871
|
entries = new Map;
|
|
10872
|
+
get size() {
|
|
10873
|
+
return this.entries.size;
|
|
10874
|
+
}
|
|
10288
10875
|
get(key) {
|
|
10289
|
-
|
|
10876
|
+
const slot = this.entries.get(key);
|
|
10877
|
+
if (slot === undefined)
|
|
10878
|
+
return;
|
|
10879
|
+
this.entries.delete(key);
|
|
10880
|
+
this.entries.set(key, slot);
|
|
10881
|
+
return slot;
|
|
10290
10882
|
}
|
|
10291
10883
|
set(key, slot) {
|
|
10292
|
-
if (this.entries.
|
|
10293
|
-
this.entries.
|
|
10884
|
+
if (this.entries.has(key)) {
|
|
10885
|
+
this.entries.delete(key);
|
|
10886
|
+
} else if (this.entries.size >= MAX_ENTRIES) {
|
|
10887
|
+
const oldest = this.entries.keys().next();
|
|
10888
|
+
if (!oldest.done)
|
|
10889
|
+
this.entries.delete(oldest.value);
|
|
10890
|
+
}
|
|
10294
10891
|
this.entries.set(key, slot);
|
|
10295
10892
|
}
|
|
10296
10893
|
}
|
|
@@ -10477,6 +11074,7 @@ function walkExpr(walk2, dialect, expr) {
|
|
|
10477
11074
|
return true;
|
|
10478
11075
|
case "jsonFilter":
|
|
10479
11076
|
return false;
|
|
11077
|
+
case "composed":
|
|
10480
11078
|
case "fragment":
|
|
10481
11079
|
return false;
|
|
10482
11080
|
case "raw":
|
|
@@ -10601,8 +11199,9 @@ function walkSelect(walk2, dialect, statement) {
|
|
|
10601
11199
|
walk2.key += `t${statement.limit}`;
|
|
10602
11200
|
if (statement.offset !== undefined)
|
|
10603
11201
|
walk2.key += `s${statement.offset}`;
|
|
10604
|
-
if (statement.
|
|
10605
|
-
walk2.key +=
|
|
11202
|
+
if (statement.rowLock !== undefined) {
|
|
11203
|
+
walk2.key += `u:${statement.rowLock.strength}/${statement.rowLock.wait}`;
|
|
11204
|
+
}
|
|
10606
11205
|
walk2.key += "}";
|
|
10607
11206
|
return true;
|
|
10608
11207
|
}
|
|
@@ -10651,6 +11250,8 @@ function walkInsert(walk2, dialect, statement) {
|
|
|
10651
11250
|
}
|
|
10652
11251
|
walk2.key += ")";
|
|
10653
11252
|
} else {
|
|
11253
|
+
if (Object.values(onConflict.update).some(isSqlFragment3))
|
|
11254
|
+
return false;
|
|
10654
11255
|
walk2.key += "U(";
|
|
10655
11256
|
if (onConflict.constraint !== undefined) {
|
|
10656
11257
|
walk2.key += "K";
|
|
@@ -10716,6 +11317,7 @@ function walkStatement(walk2, dialect, statement) {
|
|
|
10716
11317
|
case "rlsPolicy":
|
|
10717
11318
|
case "rlsContext":
|
|
10718
11319
|
case "rlsRole":
|
|
11320
|
+
case "advisoryLock":
|
|
10719
11321
|
return false;
|
|
10720
11322
|
}
|
|
10721
11323
|
}
|
|
@@ -10760,11 +11362,11 @@ function renderStatementCached(params) {
|
|
|
10760
11362
|
}
|
|
10761
11363
|
|
|
10762
11364
|
// packages/runtime/src/strict-args.ts
|
|
10763
|
-
import { VibeError as
|
|
11365
|
+
import { VibeError as VibeError24 } from "@vibeorm/schema";
|
|
10764
11366
|
var SKIP = Symbol.for("vibeorm.skip");
|
|
10765
11367
|
function explicitUndefined(params) {
|
|
10766
11368
|
const { model, method, path } = params;
|
|
10767
|
-
return new
|
|
11369
|
+
return new VibeError24({
|
|
10768
11370
|
code: "VIBE_VALIDATION",
|
|
10769
11371
|
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.`,
|
|
10770
11372
|
meta: { model, method, path }
|
|
@@ -10941,10 +11543,10 @@ function bindRlsContext(params) {
|
|
|
10941
11543
|
}
|
|
10942
11544
|
|
|
10943
11545
|
// packages/runtime/src/telemetry/recorder.ts
|
|
10944
|
-
import { VibeError as
|
|
11546
|
+
import { VibeError as VibeError26 } from "@vibeorm/schema";
|
|
10945
11547
|
|
|
10946
11548
|
// packages/runtime/src/telemetry/config.ts
|
|
10947
|
-
import { VibeError as
|
|
11549
|
+
import { VibeError as VibeError25 } from "@vibeorm/schema";
|
|
10948
11550
|
var TELEMETRY_DEFAULTS = Object.freeze({
|
|
10949
11551
|
captureSql: false,
|
|
10950
11552
|
captureParameterValues: false,
|
|
@@ -10959,7 +11561,7 @@ var TELEMETRY_DEFAULTS = Object.freeze({
|
|
|
10959
11561
|
latencyBucketsMs: Object.freeze([1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000])
|
|
10960
11562
|
});
|
|
10961
11563
|
function configError(params) {
|
|
10962
|
-
return new
|
|
11564
|
+
return new VibeError25({
|
|
10963
11565
|
code: "VIBE_CONFIG",
|
|
10964
11566
|
message: `telemetry: ${params.message}`,
|
|
10965
11567
|
...params.meta === undefined ? {} : { meta: params.meta }
|
|
@@ -11047,7 +11649,7 @@ function deriveStage(params) {
|
|
|
11047
11649
|
return "decode";
|
|
11048
11650
|
}
|
|
11049
11651
|
function errorCodeOf(error) {
|
|
11050
|
-
return error instanceof
|
|
11652
|
+
return error instanceof VibeError26 ? error.code : undefined;
|
|
11051
11653
|
}
|
|
11052
11654
|
function newHistogram(bounds) {
|
|
11053
11655
|
return { counts: new Array(bounds.length + 1).fill(0), sum: 0, count: 0 };
|
|
@@ -11421,15 +12023,23 @@ async function instrumentStatement(params) {
|
|
|
11421
12023
|
// packages/runtime/src/client.ts
|
|
11422
12024
|
function notFound2(params) {
|
|
11423
12025
|
const { model, method } = params;
|
|
11424
|
-
return new
|
|
12026
|
+
return new VibeError27({
|
|
11425
12027
|
code: "VIBE_NOT_FOUND",
|
|
11426
12028
|
message: `no ${model} record matched the where clause of ${method}()`,
|
|
11427
12029
|
meta: { model, method }
|
|
11428
12030
|
});
|
|
11429
12031
|
}
|
|
12032
|
+
function transactionLifecycleRefusal(params) {
|
|
12033
|
+
const { method } = params;
|
|
12034
|
+
return new VibeError27({
|
|
12035
|
+
code: "VIBE_VALIDATION",
|
|
12036
|
+
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
|
+
meta: { method, inTransaction: true }
|
|
12038
|
+
});
|
|
12039
|
+
}
|
|
11430
12040
|
function viewIsReadOnly(params) {
|
|
11431
12041
|
const { model, method } = params;
|
|
11432
|
-
return new
|
|
12042
|
+
return new VibeError27({
|
|
11433
12043
|
code: "VIBE_VALIDATION",
|
|
11434
12044
|
message: `${model} is a view \u2014 ${method}() is not available on views (they are read-only); write to the underlying table instead`,
|
|
11435
12045
|
meta: { model, method }
|
|
@@ -11535,7 +12145,7 @@ async function engineTransaction(params) {
|
|
|
11535
12145
|
const support = engine.adapter.budgets?.transactionDeadline;
|
|
11536
12146
|
const requested = options.deadline.enforcement ?? "between-statements";
|
|
11537
12147
|
if (support === undefined || support === "unsupported" || requested === "cancel-running-statements" && support !== "cancel-running-statements") {
|
|
11538
|
-
throw new
|
|
12148
|
+
throw new VibeError27({
|
|
11539
12149
|
code: "VIBE_UNSUPPORTED_CAPABILITY",
|
|
11540
12150
|
message: "the adapter does not declare support for the requested transaction deadline",
|
|
11541
12151
|
meta: { provider: engine.adapter.provider, feature: "transactionDeadline", requested, supported: support ?? "undeclared" }
|
|
@@ -11634,105 +12244,141 @@ function settleWith(params) {
|
|
|
11634
12244
|
scope.settle({ outcome: "failure", error });
|
|
11635
12245
|
});
|
|
11636
12246
|
}
|
|
12247
|
+
var createQueryPromise;
|
|
12248
|
+
var queryPromiseMethod;
|
|
12249
|
+
var queryPromiseOwnerError;
|
|
12250
|
+
var refuseQueryPromise;
|
|
12251
|
+
var claimQueryPromise;
|
|
12252
|
+
var restrictQueryPromise;
|
|
12253
|
+
var queryPromises = new WeakSet;
|
|
12254
|
+
function isQueryPromise(value) {
|
|
12255
|
+
return typeof value === "object" && value !== null && queryPromises.has(value);
|
|
12256
|
+
}
|
|
11637
12257
|
|
|
11638
12258
|
class QueryPromise extends Promise {
|
|
11639
12259
|
static get [Symbol.species]() {
|
|
11640
12260
|
return Promise;
|
|
11641
12261
|
}
|
|
11642
|
-
target;
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
12262
|
+
#target;
|
|
12263
|
+
#operationMethod;
|
|
12264
|
+
#dispatch;
|
|
12265
|
+
#settle;
|
|
12266
|
+
#started = false;
|
|
12267
|
+
#scope;
|
|
12268
|
+
static {
|
|
12269
|
+
createQueryPromise = QueryPromise.#create;
|
|
12270
|
+
queryPromiseMethod = ({ value }) => value.#operationMethod;
|
|
12271
|
+
queryPromiseOwnerError = ({ value, engine }) => value.#ownerError({ engine });
|
|
12272
|
+
refuseQueryPromise = ({ value, reason }) => value.#refuse({ reason });
|
|
12273
|
+
claimQueryPromise = ({ value }) => value.#claim();
|
|
12274
|
+
restrictQueryPromise = ({ value }) => {
|
|
12275
|
+
value.#target = { ...value.#target, readOnlySql: true };
|
|
12276
|
+
};
|
|
12277
|
+
}
|
|
12278
|
+
static #create(params) {
|
|
11648
12279
|
let resolve;
|
|
11649
12280
|
let reject;
|
|
11650
12281
|
const instance = new QueryPromise((res, rej) => {
|
|
11651
12282
|
resolve = res;
|
|
11652
12283
|
reject = rej;
|
|
11653
12284
|
});
|
|
11654
|
-
instance
|
|
11655
|
-
instance
|
|
11656
|
-
instance
|
|
11657
|
-
instance
|
|
12285
|
+
queryPromises.add(instance);
|
|
12286
|
+
instance.#target = params.engine;
|
|
12287
|
+
instance.#operationMethod = params.method ?? "unknown";
|
|
12288
|
+
instance.#dispatch = params.run;
|
|
12289
|
+
instance.#settle = { resolve, reject };
|
|
12290
|
+
instance.#scope = beginOperation({
|
|
11658
12291
|
engine: params.engine,
|
|
11659
12292
|
kind: params.kind ?? "model",
|
|
11660
12293
|
model: params.model ?? null,
|
|
11661
12294
|
method: params.method ?? "unknown"
|
|
11662
12295
|
});
|
|
11663
12296
|
queueMicrotask(() => {
|
|
11664
|
-
instance
|
|
12297
|
+
instance.#start();
|
|
11665
12298
|
});
|
|
11666
12299
|
return instance;
|
|
11667
12300
|
}
|
|
11668
|
-
start() {
|
|
11669
|
-
if (this
|
|
12301
|
+
#start() {
|
|
12302
|
+
if (this.#started)
|
|
11670
12303
|
return;
|
|
11671
|
-
this
|
|
11672
|
-
const scope = this
|
|
12304
|
+
this.#started = true;
|
|
12305
|
+
const scope = this.#scope;
|
|
11673
12306
|
scope?.dispatch();
|
|
11674
|
-
const target = scope === undefined ? this
|
|
11675
|
-
const running = target.rlsSchema === undefined && target.lifetime === undefined ? this
|
|
12307
|
+
const target = scope === undefined ? this.#target : { ...this.#target, operation: scope };
|
|
12308
|
+
const running = target.rlsSchema === undefined && target.lifetime === undefined ? this.#dispatch(target) : executeOperation({ engine: target, run: this.#dispatch });
|
|
11676
12309
|
if (scope !== undefined)
|
|
11677
12310
|
settleWith({ scope, running });
|
|
11678
|
-
running.then(this
|
|
12311
|
+
running.then(this.#settle.resolve, this.#settle.reject);
|
|
11679
12312
|
}
|
|
11680
12313
|
then(onfulfilled, onrejected) {
|
|
11681
|
-
this
|
|
12314
|
+
this.#start();
|
|
11682
12315
|
return super.then(onfulfilled, onrejected);
|
|
11683
12316
|
}
|
|
11684
|
-
ownerError(params) {
|
|
12317
|
+
#ownerError(params) {
|
|
11685
12318
|
const { engine } = params;
|
|
11686
|
-
const state = engineStateError({ engine: this
|
|
12319
|
+
const state = engineStateError({ engine: this.#target });
|
|
11687
12320
|
if (state !== undefined)
|
|
11688
12321
|
return state;
|
|
11689
|
-
if (engine.rlsSchema === undefined && this
|
|
12322
|
+
if (engine.rlsSchema === undefined && this.#target.rlsSchema === undefined)
|
|
11690
12323
|
return;
|
|
11691
|
-
if (this
|
|
12324
|
+
if (this.#target.rootIdentity !== engine.rootIdentity || this.#target.lifetime !== engine.lifetime || !sameRecord(this.#target.context?.settings ?? {}, engine.context?.settings ?? {}))
|
|
11692
12325
|
return new RlsContextError2({ reason: "conflicting" });
|
|
11693
12326
|
return;
|
|
11694
12327
|
}
|
|
11695
|
-
refuse(params) {
|
|
11696
|
-
if (this
|
|
12328
|
+
#refuse(params) {
|
|
12329
|
+
if (this.#started)
|
|
11697
12330
|
return;
|
|
11698
|
-
this
|
|
11699
|
-
this
|
|
11700
|
-
this
|
|
12331
|
+
this.#started = true;
|
|
12332
|
+
this.#scope?.settle({ outcome: "failure", stage: "refused", error: params.reason });
|
|
12333
|
+
this.#settle.reject(params.reason);
|
|
11701
12334
|
super.then(undefined, () => {});
|
|
11702
12335
|
}
|
|
11703
|
-
claim() {
|
|
11704
|
-
if (this
|
|
11705
|
-
throw new
|
|
12336
|
+
#claim() {
|
|
12337
|
+
if (this.#started) {
|
|
12338
|
+
throw new VibeError27({
|
|
11706
12339
|
code: "VIBE_VALIDATION",
|
|
11707
12340
|
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)"
|
|
11708
12341
|
});
|
|
11709
12342
|
}
|
|
11710
|
-
this
|
|
11711
|
-
const scope = this
|
|
12343
|
+
this.#started = true;
|
|
12344
|
+
const scope = this.#scope;
|
|
11712
12345
|
return {
|
|
11713
12346
|
start: (target) => {
|
|
12347
|
+
if (this.#target.readOnlySql === true)
|
|
12348
|
+
target = { ...target, readOnlySql: true };
|
|
11714
12349
|
if (scope !== undefined && target.operation !== undefined) {
|
|
11715
12350
|
scope.reparent({ parentOperationId: target.operation.id });
|
|
11716
12351
|
}
|
|
11717
12352
|
scope?.dispatch();
|
|
11718
12353
|
const running = executeOperation({
|
|
11719
12354
|
engine: scope === undefined ? target : { ...target, operation: scope },
|
|
11720
|
-
run: this
|
|
12355
|
+
run: this.#dispatch
|
|
11721
12356
|
});
|
|
11722
12357
|
if (scope !== undefined)
|
|
11723
12358
|
settleWith({ scope, running });
|
|
11724
|
-
running.then(this
|
|
12359
|
+
running.then(this.#settle.resolve, this.#settle.reject);
|
|
11725
12360
|
super.then(undefined, () => {});
|
|
11726
12361
|
return running;
|
|
11727
12362
|
},
|
|
11728
12363
|
abort: (reason) => {
|
|
11729
12364
|
scope?.settle({ outcome: "failure", stage: "refused", error: reason });
|
|
11730
|
-
this
|
|
12365
|
+
this.#settle.reject(reason);
|
|
11731
12366
|
super.then(undefined, () => {});
|
|
11732
12367
|
}
|
|
11733
12368
|
};
|
|
11734
12369
|
}
|
|
11735
12370
|
}
|
|
12371
|
+
var LAZY_OPERATIONS = {
|
|
12372
|
+
restrictSql: ({ value }) => {
|
|
12373
|
+
if (isQueryPromise(value))
|
|
12374
|
+
restrictQueryPromise({ value });
|
|
12375
|
+
},
|
|
12376
|
+
methodOf: (params) => isQueryPromise(params.value) ? queryPromiseMethod({ value: params.value }) : undefined,
|
|
12377
|
+
refuse: (params) => {
|
|
12378
|
+
if (isQueryPromise(params.value))
|
|
12379
|
+
refuseQueryPromise({ value: params.value, reason: params.reason });
|
|
12380
|
+
}
|
|
12381
|
+
};
|
|
11736
12382
|
async function runRows(params) {
|
|
11737
12383
|
const { engine, plan } = params;
|
|
11738
12384
|
const renderStarted = renderClock({ engine });
|
|
@@ -11759,9 +12405,9 @@ async function runRows(params) {
|
|
|
11759
12405
|
}
|
|
11760
12406
|
async function runBoundAffected(params) {
|
|
11761
12407
|
const { adapter, text: text2, values } = params;
|
|
11762
|
-
const
|
|
11763
|
-
if (
|
|
11764
|
-
return await
|
|
12408
|
+
const bound2 = adapter.executeAffected;
|
|
12409
|
+
if (bound2 !== undefined)
|
|
12410
|
+
return await bound2.call(adapter, { text: text2, values });
|
|
11765
12411
|
return await adapter.executeUnsafe({ text: text2, values });
|
|
11766
12412
|
}
|
|
11767
12413
|
async function runAffected(params) {
|
|
@@ -11800,14 +12446,26 @@ async function runAffected(params) {
|
|
|
11800
12446
|
});
|
|
11801
12447
|
return result.affectedRows;
|
|
11802
12448
|
}
|
|
12449
|
+
var LOCKABLE_METHODS = Object.freeze(["findMany", "findFirst", "findUnique"]);
|
|
11803
12450
|
function plan(params) {
|
|
11804
12451
|
const { engine, model, method, args } = params;
|
|
11805
|
-
if (
|
|
11806
|
-
throw
|
|
11807
|
-
|
|
11808
|
-
|
|
11809
|
-
|
|
11810
|
-
|
|
12452
|
+
if (engine.readOnlySql === true && containsSqlFragment(args))
|
|
12453
|
+
throw readOnlyRefusal({ path: `${model}.${method}`, reason: "raw" });
|
|
12454
|
+
if (args !== undefined && args["lock"] !== undefined) {
|
|
12455
|
+
if (engine.inTransaction !== true) {
|
|
12456
|
+
throw new VibeError27({
|
|
12457
|
+
code: "VIBE_VALIDATION",
|
|
12458
|
+
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
|
+
meta: { model, method }
|
|
12460
|
+
});
|
|
12461
|
+
}
|
|
12462
|
+
if (!LOCKABLE_METHODS.includes(method)) {
|
|
12463
|
+
throw new VibeError27({
|
|
12464
|
+
code: "VIBE_VALIDATION",
|
|
12465
|
+
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
|
+
meta: { model, method }
|
|
12467
|
+
});
|
|
12468
|
+
}
|
|
11811
12469
|
}
|
|
11812
12470
|
return buildQuery({
|
|
11813
12471
|
meta: engine.meta,
|
|
@@ -11822,6 +12480,19 @@ function plan(params) {
|
|
|
11822
12480
|
}
|
|
11823
12481
|
});
|
|
11824
12482
|
}
|
|
12483
|
+
var OVERLAY_ROW_LOCK = Object.freeze({ strength: "update", wait: "wait" });
|
|
12484
|
+
function readAcquiredVerdict(params) {
|
|
12485
|
+
const value = params.result.rows[0]?.[ADVISORY_LOCK_RESULT_COLUMN];
|
|
12486
|
+
if (value === true || value === 1 || value === "t" || value === "true" || value === "1")
|
|
12487
|
+
return true;
|
|
12488
|
+
if (value === false || value === 0 || value === "f" || value === "false" || value === "0")
|
|
12489
|
+
return false;
|
|
12490
|
+
throw new VibeError27({
|
|
12491
|
+
code: "VIBE_INTERNAL",
|
|
12492
|
+
message: `internal: ${params.method}() expected a boolean "${ADVISORY_LOCK_RESULT_COLUMN}" column, got ${String(value)}`,
|
|
12493
|
+
meta: { method: params.method, value: String(value) }
|
|
12494
|
+
});
|
|
12495
|
+
}
|
|
11825
12496
|
async function refetch(params) {
|
|
11826
12497
|
const { engine, source, where } = params;
|
|
11827
12498
|
const selectArg = source.selectArg;
|
|
@@ -11831,7 +12502,7 @@ async function refetch(params) {
|
|
|
11831
12502
|
method: "findUnique",
|
|
11832
12503
|
args: selectArg === null ? { where } : { where, select: selectArg }
|
|
11833
12504
|
});
|
|
11834
|
-
const locked = params.lock === true ? { ...followUp, statement: { ...followUp.statement,
|
|
12505
|
+
const locked = params.lock === true ? { ...followUp, statement: { ...followUp.statement, rowLock: OVERLAY_ROW_LOCK } } : followUp;
|
|
11835
12506
|
const rows = await runRows({ engine, plan: locked });
|
|
11836
12507
|
return rows[0] ?? null;
|
|
11837
12508
|
}
|
|
@@ -11858,7 +12529,7 @@ async function insertedKeyWhere(params) {
|
|
|
11858
12529
|
const keyField = source.model.primaryKey[0];
|
|
11859
12530
|
const lastInsertIdSql = engine.dialect.lastInsertIdSql;
|
|
11860
12531
|
if (lastInsertIdSql === null || source.model.primaryKey.length !== 1 || keyField === undefined) {
|
|
11861
|
-
throw
|
|
12532
|
+
throw unsupportedCapability8({
|
|
11862
12533
|
dialect: engine.dialect.name,
|
|
11863
12534
|
feature: `${source.method} on ${source.model.name} without RETURNING`,
|
|
11864
12535
|
hint: "this model's key is assigned by the database and cannot be read back on this dialect"
|
|
@@ -11876,7 +12547,7 @@ async function insertedKeyWhere(params) {
|
|
|
11876
12547
|
});
|
|
11877
12548
|
const id = result.rows[0]?.id;
|
|
11878
12549
|
if (id === undefined || id === null) {
|
|
11879
|
-
throw new
|
|
12550
|
+
throw new VibeError27({
|
|
11880
12551
|
code: "VIBE_INTERNAL",
|
|
11881
12552
|
message: `could not read back the generated key for ${source.model.name}`,
|
|
11882
12553
|
meta: { model: source.model.name }
|
|
@@ -12006,7 +12677,7 @@ async function runUpsertFallback(params) {
|
|
|
12006
12677
|
const probeSource = plan({ engine, model: model.name, method: "findUnique", args: { where: args.where } });
|
|
12007
12678
|
const probePlan = {
|
|
12008
12679
|
...probeSource,
|
|
12009
|
-
statement: { ...probeSource.statement,
|
|
12680
|
+
statement: { ...probeSource.statement, rowLock: OVERLAY_ROW_LOCK }
|
|
12010
12681
|
};
|
|
12011
12682
|
const createSource = plan({
|
|
12012
12683
|
engine,
|
|
@@ -12034,6 +12705,8 @@ async function runUpsertFallback(params) {
|
|
|
12034
12705
|
function relationExecutor(params) {
|
|
12035
12706
|
const { engine } = params;
|
|
12036
12707
|
return async ({ statement, model, method }) => {
|
|
12708
|
+
if (engine.readOnlySql === true && containsSqlFragment(statement))
|
|
12709
|
+
throw readOnlyRefusal({ path: `${model}.${method}`, reason: "raw" });
|
|
12037
12710
|
const rendered = renderStatementCached({ dialect: engine.dialect, statement, cache: engine.renderCache });
|
|
12038
12711
|
return await runStatement({
|
|
12039
12712
|
engine,
|
|
@@ -12133,7 +12806,7 @@ function makeWriteContext(params) {
|
|
|
12133
12806
|
transactional: (fn) => engine.adapter.transaction((txAdapter) => fn(makeRunner({ engine: { ...engine, adapter: txAdapter, inTransaction: true } })))
|
|
12134
12807
|
};
|
|
12135
12808
|
}
|
|
12136
|
-
function
|
|
12809
|
+
function readOnlyDelegate2(params) {
|
|
12137
12810
|
const { delegate, model } = params;
|
|
12138
12811
|
const refuse2 = (method) => async () => {
|
|
12139
12812
|
throw viewIsReadOnly({ model, method });
|
|
@@ -12193,7 +12866,7 @@ function createDelegate(params) {
|
|
|
12193
12866
|
const skipping = args.onConflict !== undefined;
|
|
12194
12867
|
if (hasNestedWrites({ model: modelMeta, data: args.data })) {
|
|
12195
12868
|
if (skipping) {
|
|
12196
|
-
throw new
|
|
12869
|
+
throw new VibeError27({
|
|
12197
12870
|
code: "VIBE_VALIDATION",
|
|
12198
12871
|
message: `onConflict cannot combine with nested writes on ${modelMeta.name}.create() \u2014 insert the parent row alone, then connect relations`,
|
|
12199
12872
|
meta: { model: modelMeta.name }
|
|
@@ -12294,7 +12967,7 @@ function createDelegate(params) {
|
|
|
12294
12967
|
const upsert = async (target, args) => {
|
|
12295
12968
|
const modelMeta = getModelMeta({ meta: target.meta, model });
|
|
12296
12969
|
if (hasNestedWrites({ model: modelMeta, data: args.create }) || hasNestedWrites({ model: modelMeta, data: args.update })) {
|
|
12297
|
-
throw new
|
|
12970
|
+
throw new VibeError27({
|
|
12298
12971
|
code: "VIBE_VALIDATION",
|
|
12299
12972
|
message: `nested writes are not supported inside upsert arms on ${modelMeta.name} \u2014 use create()/update() with nested data`,
|
|
12300
12973
|
meta: { model: modelMeta.name }
|
|
@@ -12302,7 +12975,7 @@ function createDelegate(params) {
|
|
|
12302
12975
|
}
|
|
12303
12976
|
if (!upsertUsesNativeStatement({ model: modelMeta, args })) {
|
|
12304
12977
|
if (args.onConflict !== undefined) {
|
|
12305
|
-
throw new
|
|
12978
|
+
throw new VibeError27({
|
|
12306
12979
|
code: "VIBE_VALIDATION",
|
|
12307
12980
|
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`,
|
|
12308
12981
|
meta: { model: modelMeta.name }
|
|
@@ -12397,7 +13070,7 @@ function createDelegate(params) {
|
|
|
12397
13070
|
return assembleAggregates({ engine: target, model: built.model, spec, row, into: out });
|
|
12398
13071
|
});
|
|
12399
13072
|
};
|
|
12400
|
-
const lazy = (method, run) =>
|
|
13073
|
+
const lazy = (method, run) => createQueryPromise({ engine, run, model, method });
|
|
12401
13074
|
const delegate = {
|
|
12402
13075
|
findMany: (args) => lazy("findMany", (target) => findMany(target, args)),
|
|
12403
13076
|
findFirst: (args) => lazy("findFirst", (target) => findFirst(target, args)),
|
|
@@ -12429,7 +13102,7 @@ function createDelegate(params) {
|
|
|
12429
13102
|
groupBy: (args) => lazy("groupBy", (target) => groupBy(target, args))
|
|
12430
13103
|
};
|
|
12431
13104
|
const meta = getModelMeta({ meta: engine.meta, model });
|
|
12432
|
-
return meta.isView ?
|
|
13105
|
+
return meta.isView ? readOnlyDelegate2({ delegate, model: meta.name }) : delegate;
|
|
12433
13106
|
}
|
|
12434
13107
|
var WRITE_METHODS = [
|
|
12435
13108
|
"create",
|
|
@@ -12495,13 +13168,13 @@ function buildClient(params) {
|
|
|
12495
13168
|
models[model.name] = delegate;
|
|
12496
13169
|
models[model.clientName] = delegate;
|
|
12497
13170
|
}
|
|
12498
|
-
const runRaw = async (method, statement) => {
|
|
13171
|
+
const runRaw = async (method, statement, decodeRows2) => {
|
|
12499
13172
|
const scope = beginOperation({ engine, kind: "raw", model: null, method });
|
|
12500
13173
|
scope?.dispatch();
|
|
12501
13174
|
const running = executeOperation({
|
|
12502
13175
|
engine: scope === undefined ? engine : { ...engine, operation: scope },
|
|
12503
13176
|
run: async (target) => {
|
|
12504
|
-
|
|
13177
|
+
const result = await runStatement({
|
|
12505
13178
|
engine: target,
|
|
12506
13179
|
kind: "raw",
|
|
12507
13180
|
model: null,
|
|
@@ -12511,6 +13184,8 @@ function buildClient(params) {
|
|
|
12511
13184
|
rowCount: (value) => method.startsWith("$query") ? value.rows.length : value.affectedRows,
|
|
12512
13185
|
run: () => statement.values.length > 0 ? runBoundAffected({ adapter: target.adapter, text: statement.text, values: statement.values }) : target.adapter.executeUnsafe(statement)
|
|
12513
13186
|
});
|
|
13187
|
+
decodeRows2?.(result.rows);
|
|
13188
|
+
return result;
|
|
12514
13189
|
}
|
|
12515
13190
|
});
|
|
12516
13191
|
if (scope === undefined)
|
|
@@ -12525,33 +13200,67 @@ function buildClient(params) {
|
|
|
12525
13200
|
});
|
|
12526
13201
|
return await running;
|
|
12527
13202
|
};
|
|
13203
|
+
const runAdvisoryLock = async (params2) => {
|
|
13204
|
+
const { method, options } = params2;
|
|
13205
|
+
assertEngineActive({ engine });
|
|
13206
|
+
const statement = compileAdvisoryLock({
|
|
13207
|
+
dialect: engine.dialect,
|
|
13208
|
+
method,
|
|
13209
|
+
inTransaction: engine.inTransaction === true,
|
|
13210
|
+
options
|
|
13211
|
+
});
|
|
13212
|
+
const rendered = renderStatement5({ dialect: engine.dialect, statement });
|
|
13213
|
+
const scope = beginOperation({ engine, kind: "raw", model: null, method });
|
|
13214
|
+
scope?.dispatch();
|
|
13215
|
+
const target = scope === undefined ? engine : { ...engine, operation: scope };
|
|
13216
|
+
const running = runStatement({
|
|
13217
|
+
engine: target,
|
|
13218
|
+
kind: "raw",
|
|
13219
|
+
model: null,
|
|
13220
|
+
method,
|
|
13221
|
+
text: rendered.text,
|
|
13222
|
+
values: rendered.values,
|
|
13223
|
+
rowCount: (value) => value.rows.length,
|
|
13224
|
+
run: () => runBoundAffected({
|
|
13225
|
+
adapter: target.adapter,
|
|
13226
|
+
text: rendered.text,
|
|
13227
|
+
values: bindValues({ adapter: target.adapter, values: rendered.values })
|
|
13228
|
+
})
|
|
13229
|
+
});
|
|
13230
|
+
if (scope !== undefined)
|
|
13231
|
+
settleWith({ scope, running });
|
|
13232
|
+
const result = await running;
|
|
13233
|
+
if (statement.wait === "wait")
|
|
13234
|
+
return true;
|
|
13235
|
+
return readAcquiredVerdict({ method, result });
|
|
13236
|
+
};
|
|
12528
13237
|
const runOperations = (operations, options) => {
|
|
12529
13238
|
const scope = beginOperation({ engine, kind: "transaction", model: null, method: "$transaction" });
|
|
12530
13239
|
const claimed = [];
|
|
12531
13240
|
let claimError = null;
|
|
12532
13241
|
let identityError = engineStateError({ engine });
|
|
12533
13242
|
for (const operation of operations) {
|
|
12534
|
-
if (identityError === undefined && operation
|
|
12535
|
-
identityError =
|
|
13243
|
+
if (identityError === undefined && isQueryPromise(operation))
|
|
13244
|
+
identityError = queryPromiseOwnerError({ value: operation, engine });
|
|
12536
13245
|
}
|
|
12537
13246
|
if (identityError !== undefined) {
|
|
12538
13247
|
for (const operation of operations) {
|
|
12539
|
-
if (operation
|
|
12540
|
-
|
|
13248
|
+
if (isQueryPromise(operation))
|
|
13249
|
+
refuseQueryPromise({ value: operation, reason: identityError });
|
|
12541
13250
|
}
|
|
12542
13251
|
scope?.settle({ outcome: "failure", stage: "refused", error: identityError });
|
|
12543
13252
|
return Promise.reject(identityError);
|
|
12544
13253
|
}
|
|
12545
13254
|
for (const operation of operations) {
|
|
12546
|
-
if (!(operation
|
|
12547
|
-
claimError = new
|
|
13255
|
+
if (!isQueryPromise(operation)) {
|
|
13256
|
+
claimError = new VibeError27({
|
|
12548
13257
|
code: "VIBE_VALIDATION",
|
|
12549
13258
|
message: "$transaction([...]) takes query promises from model delegate calls \u2014 a foreign promise is already running and cannot join the transaction"
|
|
12550
13259
|
});
|
|
12551
13260
|
break;
|
|
12552
13261
|
}
|
|
12553
13262
|
try {
|
|
12554
|
-
claimed.push(operation
|
|
13263
|
+
claimed.push(claimQueryPromise({ value: operation }));
|
|
12555
13264
|
} catch (error) {
|
|
12556
13265
|
claimError = error;
|
|
12557
13266
|
break;
|
|
@@ -12561,8 +13270,8 @@ function buildClient(params) {
|
|
|
12561
13270
|
for (const entry of claimed)
|
|
12562
13271
|
entry.abort(claimError);
|
|
12563
13272
|
for (const operation of operations) {
|
|
12564
|
-
if (operation
|
|
12565
|
-
|
|
13273
|
+
if (isQueryPromise(operation))
|
|
13274
|
+
refuseQueryPromise({ value: operation, reason: claimError });
|
|
12566
13275
|
}
|
|
12567
13276
|
scope?.settle({ outcome: "failure", stage: "refused", error: claimError });
|
|
12568
13277
|
return Promise.reject(claimError);
|
|
@@ -12609,7 +13318,7 @@ function buildClient(params) {
|
|
|
12609
13318
|
$models: models,
|
|
12610
13319
|
$on: (event, listener) => {
|
|
12611
13320
|
if (event !== "query") {
|
|
12612
|
-
throw new
|
|
13321
|
+
throw new VibeError27({
|
|
12613
13322
|
code: "VIBE_VALIDATION",
|
|
12614
13323
|
message: `unknown client event "${String(event)}" \u2014 supported: "query"`,
|
|
12615
13324
|
meta: { event }
|
|
@@ -12621,15 +13330,34 @@ function buildClient(params) {
|
|
|
12621
13330
|
};
|
|
12622
13331
|
},
|
|
12623
13332
|
$transaction: (fnOrOperations, options) => Array.isArray(fnOrOperations) ? runOperations(fnOrOperations, options) : runCallbackTransaction(fnOrOperations, options),
|
|
13333
|
+
$querySql: async ({ query, decoder }) => {
|
|
13334
|
+
assertEngineActive({ engine });
|
|
13335
|
+
if (decoder === null || typeof decoder !== "object" || typeof decoder.decode !== "function")
|
|
13336
|
+
throw new VibeError27({ code: "VIBE_VALIDATION", message: "$querySql requires an explicit result decoder" });
|
|
13337
|
+
const statement = compileSql({ dialect: engine.dialect, query });
|
|
13338
|
+
let output = [];
|
|
13339
|
+
await runRaw("$querySql", statement, (rows) => {
|
|
13340
|
+
output = rows.map((value) => decoder.decode({ value, dialect: engine.dialect.name, ...engine.adapter.wire === undefined ? {} : { wire: engine.adapter.wire } }));
|
|
13341
|
+
});
|
|
13342
|
+
return output;
|
|
13343
|
+
},
|
|
13344
|
+
$executeSql: async ({ query }) => {
|
|
13345
|
+
assertEngineActive({ engine });
|
|
13346
|
+
return (await runRaw("$executeSql", compileSql({ dialect: engine.dialect, query }))).affectedRows;
|
|
13347
|
+
},
|
|
12624
13348
|
$queryRaw: async (strings, ...values) => (await runRaw("$queryRaw", buildRawStatement({ dialect: engine.dialect, strings, values }))).rows,
|
|
12625
13349
|
$executeRaw: async (strings, ...values) => (await runRaw("$executeRaw", buildRawStatement({ dialect: engine.dialect, strings, values }))).affectedRows,
|
|
12626
13350
|
$queryRawUnsafe: async (text2, ...values) => (await runRaw("$queryRawUnsafe", { text: text2, values })).rows,
|
|
12627
13351
|
$executeRawUnsafe: async (text2, ...values) => (await runRaw("$executeRawUnsafe", { text: text2, values })).affectedRows,
|
|
13352
|
+
$advisoryLock: async (options) => {
|
|
13353
|
+
await runAdvisoryLock({ method: "$advisoryLock", options });
|
|
13354
|
+
},
|
|
13355
|
+
$tryAdvisoryLock: (options) => runAdvisoryLock({ method: "$tryAdvisoryLock", options }),
|
|
12628
13356
|
$connect: async () => {
|
|
12629
13357
|
assertEngineActive({ engine, requireContext: false });
|
|
13358
|
+
if (engine.inTransaction === true)
|
|
13359
|
+
throw transactionLifecycleRefusal({ method: "$connect" });
|
|
12630
13360
|
if (engine.rlsSchema !== undefined) {
|
|
12631
|
-
if (engine.inTransaction)
|
|
12632
|
-
throw new RlsContextError2({ reason: "conflicting" });
|
|
12633
13361
|
await engine.adapter.connect();
|
|
12634
13362
|
await engineTransaction({ engine, requireContext: false, run: async (target) => {
|
|
12635
13363
|
for (const hook of engine.connectHooks ?? []) {
|
|
@@ -12647,8 +13375,8 @@ function buildClient(params) {
|
|
|
12647
13375
|
},
|
|
12648
13376
|
$disconnect: async () => {
|
|
12649
13377
|
assertEngineActive({ engine, requireContext: false });
|
|
12650
|
-
if (engine.
|
|
12651
|
-
throw
|
|
13378
|
+
if (engine.inTransaction === true)
|
|
13379
|
+
throw transactionLifecycleRefusal({ method: "$disconnect" });
|
|
12652
13380
|
await engine.adapter.disconnect();
|
|
12653
13381
|
await engine.telemetry?.flush();
|
|
12654
13382
|
}
|
|
@@ -12665,7 +13393,7 @@ function buildClient(params) {
|
|
|
12665
13393
|
if (engine.rlsSchema === undefined)
|
|
12666
13394
|
attachScoped({ client, meta: engine.meta, ...wrapDelegate === undefined ? {} : { wrapDelegate } });
|
|
12667
13395
|
if (engine.rlsSchema === undefined) {
|
|
12668
|
-
registerPolicyOperations({ client, factory: (run) =>
|
|
13396
|
+
registerPolicyOperations({ client, factory: (run) => createQueryPromise({
|
|
12669
13397
|
engine,
|
|
12670
13398
|
model: null,
|
|
12671
13399
|
method: "$policyOperation",
|
|
@@ -12673,9 +13401,15 @@ function buildClient(params) {
|
|
|
12673
13401
|
run: (target) => engineTransaction({ engine: target, run: (transactionEngine) => run(buildClient({ engine: transactionEngine })) })
|
|
12674
13402
|
}) });
|
|
12675
13403
|
attachPolicy({ client, meta: engine.meta, ...wrapDelegate === undefined ? {} : { wrapDelegate } });
|
|
13404
|
+
attachReadOnly({
|
|
13405
|
+
client,
|
|
13406
|
+
meta: engine.meta,
|
|
13407
|
+
...engine.extensionMounts === undefined ? {} : { mounts: engine.extensionMounts },
|
|
13408
|
+
operations: LAZY_OPERATIONS
|
|
13409
|
+
});
|
|
12676
13410
|
} else {
|
|
12677
13411
|
const refuseLegacy = () => {
|
|
12678
|
-
throw new
|
|
13412
|
+
throw new VibeError27({ code: "VIBE_CONFIG", message: "Native row-level security cannot be combined with application policy or tenant scoping" });
|
|
12679
13413
|
};
|
|
12680
13414
|
Object.defineProperties(client, {
|
|
12681
13415
|
$withContext: { enumerable: true, value: (context) => {
|
|
@@ -12688,6 +13422,14 @@ function buildClient(params) {
|
|
|
12688
13422
|
$withPolicy: { value: refuseLegacy },
|
|
12689
13423
|
$bypassPolicy: { value: refuseLegacy }
|
|
12690
13424
|
});
|
|
13425
|
+
if (engine.context !== undefined) {
|
|
13426
|
+
attachReadOnly({
|
|
13427
|
+
client,
|
|
13428
|
+
meta: engine.meta,
|
|
13429
|
+
...engine.extensionMounts === undefined ? {} : { mounts: engine.extensionMounts },
|
|
13430
|
+
operations: LAZY_OPERATIONS
|
|
13431
|
+
});
|
|
13432
|
+
}
|
|
12691
13433
|
for (const delegate of Object.values(models))
|
|
12692
13434
|
Object.freeze(delegate);
|
|
12693
13435
|
Object.freeze(models);
|
|
@@ -12737,7 +13479,7 @@ function makeMountContext(params) {
|
|
|
12737
13479
|
compileWhere: ({ where, model: modelName, qualify }) => {
|
|
12738
13480
|
const target = modelName === undefined ? model : getModelMeta({ meta: engine.meta, model: modelName });
|
|
12739
13481
|
if (target === null) {
|
|
12740
|
-
throw new
|
|
13482
|
+
throw new VibeError27({
|
|
12741
13483
|
code: "VIBE_INTERNAL",
|
|
12742
13484
|
message: `extension "${extension}" compiled a where with no model in scope`,
|
|
12743
13485
|
meta: { extension }
|
|
@@ -12760,30 +13502,30 @@ function createMountSurface(params) {
|
|
|
12760
13502
|
return original;
|
|
12761
13503
|
const wrap = (value, path) => {
|
|
12762
13504
|
if (typeof value === "function") {
|
|
12763
|
-
return (...args) =>
|
|
13505
|
+
return (...args) => createQueryPromise({ engine, run: async (target) => {
|
|
12764
13506
|
let current = mount.create(makeMountContext({ engine: target, extension: mount.extension, model }));
|
|
12765
13507
|
let receiver = undefined;
|
|
12766
13508
|
for (const key of path) {
|
|
12767
13509
|
receiver = current;
|
|
12768
13510
|
if (current === null || typeof current !== "object" && typeof current !== "function")
|
|
12769
|
-
throw new
|
|
13511
|
+
throw new VibeError27({ code: "VIBE_CONFIG", message: "Extension mount changed its method shape" });
|
|
12770
13512
|
current = Reflect.get(current, key);
|
|
12771
13513
|
}
|
|
12772
13514
|
if (typeof current !== "function")
|
|
12773
|
-
throw new
|
|
13515
|
+
throw new VibeError27({ code: "VIBE_CONFIG", message: "Extension mount changed its method shape" });
|
|
12774
13516
|
return Reflect.apply(current, receiver, args);
|
|
12775
13517
|
} });
|
|
12776
13518
|
}
|
|
12777
13519
|
if (value === null || typeof value !== "object")
|
|
12778
13520
|
return value;
|
|
12779
13521
|
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
12780
|
-
throw new
|
|
13522
|
+
throw new VibeError27({ code: "VIBE_CONFIG", message: "Native RLS extension mounts must expose plain objects and methods" });
|
|
12781
13523
|
}
|
|
12782
13524
|
const result = {};
|
|
12783
13525
|
for (const key of Reflect.ownKeys(value)) {
|
|
12784
13526
|
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
12785
13527
|
if (!("value" in descriptor))
|
|
12786
|
-
throw new
|
|
13528
|
+
throw new VibeError27({ code: "VIBE_CONFIG", message: "Native RLS extension mount accessors are unsupported" });
|
|
12787
13529
|
Object.defineProperty(result, key, { value: wrap(descriptor.value, [...path, key]), enumerable: descriptor.enumerable });
|
|
12788
13530
|
}
|
|
12789
13531
|
return Object.freeze(result);
|
|
@@ -12800,7 +13542,7 @@ function attachExtensionMounts(params) {
|
|
|
12800
13542
|
const model = getModelMeta({ meta: engine.meta, model: mount.model ?? "" });
|
|
12801
13543
|
const delegate = models[model.name];
|
|
12802
13544
|
if (delegate === undefined || mount.property in delegate) {
|
|
12803
|
-
throw new
|
|
13545
|
+
throw new VibeError27({
|
|
12804
13546
|
code: "VIBE_CONFIG",
|
|
12805
13547
|
message: `extension "${mount.extension}" cannot mount "${mount.property}" on ${model.name} \u2014 the name is taken`,
|
|
12806
13548
|
meta: { extension: mount.extension, model: model.name, property: mount.property }
|
|
@@ -12811,7 +13553,7 @@ function attachExtensionMounts(params) {
|
|
|
12811
13553
|
continue;
|
|
12812
13554
|
}
|
|
12813
13555
|
if (!mount.property.startsWith("$") || mount.property in client) {
|
|
12814
|
-
throw new
|
|
13556
|
+
throw new VibeError27({
|
|
12815
13557
|
code: "VIBE_CONFIG",
|
|
12816
13558
|
message: `extension "${mount.extension}" cannot mount client property "${mount.property}" \u2014 root mounts are $-prefixed and must not collide`,
|
|
12817
13559
|
meta: { extension: mount.extension, property: mount.property }
|
|
@@ -12828,7 +13570,7 @@ function createClient(params) {
|
|
|
12828
13570
|
if (schema.rls !== undefined) {
|
|
12829
13571
|
const dialect = getSqlDialect5({ dialect: dialectName });
|
|
12830
13572
|
if (!dialect.capabilities.nativeRowLevelSecurity)
|
|
12831
|
-
throw
|
|
13573
|
+
throw unsupportedCapability8({ dialect: dialectName, feature: "nativeRowLevelSecurity" });
|
|
12832
13574
|
assertValidSchemaIR2({ schema });
|
|
12833
13575
|
for (const model of schema.models) {
|
|
12834
13576
|
if (model.rls !== "unprotected")
|
|
@@ -12837,9 +13579,9 @@ function createClient(params) {
|
|
|
12837
13579
|
const role = options?.nativeRls?.runtimeRole;
|
|
12838
13580
|
if (adapter.provider === "pglite") {
|
|
12839
13581
|
if (typeof role !== "string" || role.length === 0 || role.includes("\x00"))
|
|
12840
|
-
throw new
|
|
13582
|
+
throw new VibeError27({ 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" });
|
|
12841
13583
|
} else if (role !== undefined) {
|
|
12842
|
-
throw new
|
|
13584
|
+
throw new VibeError27({ code: "VIBE_CONFIG", message: "Server native row-level security must use the runtime login role, not a runtimeRole switch" });
|
|
12843
13585
|
}
|
|
12844
13586
|
}
|
|
12845
13587
|
if (extensions?.manifest !== undefined) {
|
|
@@ -12849,7 +13591,7 @@ function createClient(params) {
|
|
|
12849
13591
|
assertComputedManifest({ manifest: computed.manifest, specs: computed.specs });
|
|
12850
13592
|
}
|
|
12851
13593
|
if (computed === undefined && options !== undefined && options["computed"] !== undefined) {
|
|
12852
|
-
throw new
|
|
13594
|
+
throw new VibeError27({
|
|
12853
13595
|
code: "VIBE_CONFIG",
|
|
12854
13596
|
message: "this client was generated without computed fields \u2014 declare them in vibeorm.config.ts and re-run vibeorm generate",
|
|
12855
13597
|
meta: {}
|
|
@@ -12880,9 +13622,9 @@ function createClient(params) {
|
|
|
12880
13622
|
});
|
|
12881
13623
|
}
|
|
12882
13624
|
// packages/runtime/src/keyset-iterator.ts
|
|
12883
|
-
import { VibeError as
|
|
13625
|
+
import { VibeError as VibeError28 } from "@vibeorm/schema";
|
|
12884
13626
|
function validation(params) {
|
|
12885
|
-
return new
|
|
13627
|
+
return new VibeError28({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
12886
13628
|
}
|
|
12887
13629
|
function projectionFor(params) {
|
|
12888
13630
|
const { plan: plan2, select, omit } = params;
|
|
@@ -12959,7 +13701,7 @@ function iterateKeyset(options) {
|
|
|
12959
13701
|
});
|
|
12960
13702
|
if (advanced === false) {
|
|
12961
13703
|
stop();
|
|
12962
|
-
throw new
|
|
13704
|
+
throw new VibeError28({
|
|
12963
13705
|
code: "VIBE_DATA_INVALID",
|
|
12964
13706
|
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.",
|
|
12965
13707
|
meta: { model: options.model, order: plan2.signature, reason: "keyset-position-not-advancing" }
|
|
@@ -13032,7 +13774,7 @@ function iterateKeyset(options) {
|
|
|
13032
13774
|
return iterator;
|
|
13033
13775
|
}
|
|
13034
13776
|
// packages/runtime/src/telemetry/collector.ts
|
|
13035
|
-
import { VibeError as
|
|
13777
|
+
import { VibeError as VibeError29 } from "@vibeorm/schema";
|
|
13036
13778
|
function createTelemetryCollector(params) {
|
|
13037
13779
|
const throwOn = new Set(params?.throwOn ?? []);
|
|
13038
13780
|
const operations = [];
|
|
@@ -13042,7 +13784,7 @@ function createTelemetryCollector(params) {
|
|
|
13042
13784
|
let shutdowns = 0;
|
|
13043
13785
|
const hostile = (phase) => {
|
|
13044
13786
|
if (throwOn.has(phase)) {
|
|
13045
|
-
throw new
|
|
13787
|
+
throw new VibeError29({
|
|
13046
13788
|
code: "VIBE_INTERNAL",
|
|
13047
13789
|
message: `telemetry collector: deliberate ${phase} failure`,
|
|
13048
13790
|
meta: { reason: "telemetry-collector-deliberate-failure", phase }
|
|
@@ -13098,7 +13840,7 @@ function createTelemetryCollector(params) {
|
|
|
13098
13840
|
operationFor: (lookup) => {
|
|
13099
13841
|
const matches = operations.filter((event) => event.method === lookup.method && (lookup.model === undefined || event.model === lookup.model));
|
|
13100
13842
|
if (matches.length > 1) {
|
|
13101
|
-
throw new
|
|
13843
|
+
throw new VibeError29({
|
|
13102
13844
|
code: "VIBE_VALIDATION",
|
|
13103
13845
|
message: `telemetry collector: ${matches.length} operations match ${lookup.model ?? "*"}.${lookup.method}`,
|
|
13104
13846
|
meta: {
|
|
@@ -13118,6 +13860,8 @@ export {
|
|
|
13118
13860
|
updateWithNested,
|
|
13119
13861
|
toSqlExecutor,
|
|
13120
13862
|
toClientName,
|
|
13863
|
+
sqlProjection,
|
|
13864
|
+
sql2 as sql,
|
|
13121
13865
|
rewriteScopedCall,
|
|
13122
13866
|
resolveTelemetryConfig,
|
|
13123
13867
|
resolveScopedModels,
|
|
@@ -13126,6 +13870,7 @@ export {
|
|
|
13126
13870
|
resolveRelationLink,
|
|
13127
13871
|
recommendFromPlan,
|
|
13128
13872
|
readWorkloadStatistics,
|
|
13873
|
+
readOnlyRefusal,
|
|
13129
13874
|
queryFingerprint,
|
|
13130
13875
|
previewOperation,
|
|
13131
13876
|
parsePostgresPlan,
|
|
@@ -13138,6 +13883,7 @@ export {
|
|
|
13138
13883
|
isDbNow,
|
|
13139
13884
|
inspectNativeRlsTransaction,
|
|
13140
13885
|
inspectNativeRls,
|
|
13886
|
+
inheritReadOnly,
|
|
13141
13887
|
hasNestedWrites,
|
|
13142
13888
|
getModelMeta,
|
|
13143
13889
|
getFieldMeta,
|
|
@@ -13152,17 +13898,20 @@ export {
|
|
|
13152
13898
|
explainOperation,
|
|
13153
13899
|
encodeValue,
|
|
13154
13900
|
encodeKeysetToken,
|
|
13901
|
+
delegateMethodAccess,
|
|
13155
13902
|
decodeValue,
|
|
13156
13903
|
decodeRows,
|
|
13157
13904
|
decodeRow,
|
|
13158
13905
|
decodeKeysetToken,
|
|
13159
13906
|
decodeIsNoop2 as decodeIsNoop,
|
|
13160
13907
|
decodeAggregateValue,
|
|
13908
|
+
decode2 as decode,
|
|
13161
13909
|
dbNow,
|
|
13162
13910
|
createWithNested,
|
|
13163
13911
|
createView,
|
|
13164
13912
|
createTelemetryRecorder,
|
|
13165
13913
|
createTelemetryCollector,
|
|
13914
|
+
createSqlModel,
|
|
13166
13915
|
createScopeRewriter,
|
|
13167
13916
|
createClient,
|
|
13168
13917
|
compileWhere,
|
|
@@ -13170,6 +13919,8 @@ export {
|
|
|
13170
13919
|
compileOrderBy,
|
|
13171
13920
|
compileKeysetOrderPlan,
|
|
13172
13921
|
compileKeysetOrderInputs,
|
|
13922
|
+
compileAdvisoryLock,
|
|
13923
|
+
clientMemberAccess,
|
|
13173
13924
|
checkStrictArguments,
|
|
13174
13925
|
buildRuntimeMeta,
|
|
13175
13926
|
buildQuery,
|
|
@@ -13177,6 +13928,7 @@ export {
|
|
|
13177
13928
|
buildConnectHooks,
|
|
13178
13929
|
attachScoped,
|
|
13179
13930
|
attachRelations,
|
|
13931
|
+
attachReadOnly,
|
|
13180
13932
|
attachPolicy,
|
|
13181
13933
|
attachLateralRelations,
|
|
13182
13934
|
attachDefineView,
|
|
@@ -13187,10 +13939,12 @@ export {
|
|
|
13187
13939
|
applyComputedFields,
|
|
13188
13940
|
analyzeOperationTelemetry,
|
|
13189
13941
|
affectedRowsAreRowCounts,
|
|
13942
|
+
WRITE_DELEGATE_METHODS,
|
|
13190
13943
|
ViewResult,
|
|
13191
13944
|
TELEMETRY_DEFAULTS,
|
|
13192
13945
|
SQLITE_CODECS,
|
|
13193
13946
|
SKIP,
|
|
13947
|
+
READ_DELEGATE_METHODS,
|
|
13194
13948
|
POSTGRES_CODECS,
|
|
13195
13949
|
PLAN_DEFAULTS,
|
|
13196
13950
|
MYSQL_CODECS,
|
|
@@ -13199,7 +13953,9 @@ export {
|
|
|
13199
13953
|
KEYSET_MAX_PAGE_SIZE,
|
|
13200
13954
|
DIALECT_CODECS2 as DIALECT_CODECS,
|
|
13201
13955
|
DIAGNOSTIC_THRESHOLDS,
|
|
13202
|
-
|
|
13956
|
+
DELEGATE_METHOD_ACCESS,
|
|
13957
|
+
COUNT_ALIAS,
|
|
13958
|
+
CLIENT_MEMBER_ACCESS
|
|
13203
13959
|
};
|
|
13204
13960
|
|
|
13205
|
-
//# debugId=
|
|
13961
|
+
//# debugId=89575B8D7F621EF764756E2164756E21
|