@vibeorm/runtime 2.0.0-alpha.3 → 2.0.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +1 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/db-now.d.ts +41 -0
- package/dist/db-now.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +525 -118
- package/dist/index.js.map +8 -7
- package/dist/model-meta.d.ts +20 -1
- package/dist/model-meta.d.ts.map +1 -1
- package/dist/query-builder.d.ts.map +1 -1
- package/dist/render-cache.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -10,8 +10,31 @@ function toSqlExecutor(params) {
|
|
|
10
10
|
return result.rows;
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
+
// packages/runtime/src/db-now.ts
|
|
14
|
+
import { VibeError } from "@vibeorm/schema";
|
|
15
|
+
function dbNow(options) {
|
|
16
|
+
const offset = options?.plusSeconds ?? 0;
|
|
17
|
+
if (typeof offset !== "number" || !Number.isFinite(offset)) {
|
|
18
|
+
throw new VibeError({
|
|
19
|
+
code: "VIBE_VALIDATION",
|
|
20
|
+
message: `dbNow plusSeconds must be a finite number of seconds (fractional is fine), got ${String(offset)}`,
|
|
21
|
+
meta: { plusSeconds: offset }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return { __vibeDbNow: true, offsetSeconds: offset };
|
|
25
|
+
}
|
|
26
|
+
function isDbNow(value) {
|
|
27
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
28
|
+
return false;
|
|
29
|
+
const keys = Object.keys(value);
|
|
30
|
+
if (keys.length !== 2 || !keys.includes("__vibeDbNow") || !keys.includes("offsetSeconds")) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const candidate = value;
|
|
34
|
+
return candidate.__vibeDbNow === true && typeof candidate.offsetSeconds === "number" && Number.isInteger(candidate.offsetSeconds);
|
|
35
|
+
}
|
|
13
36
|
// packages/runtime/src/model-meta.ts
|
|
14
|
-
import { VibeError, isEnumType, validateComputedConfig } from "@vibeorm/schema";
|
|
37
|
+
import { VibeError as VibeError2, isEnumType, validateComputedConfig } from "@vibeorm/schema";
|
|
15
38
|
function toClientName(params) {
|
|
16
39
|
const { name } = params;
|
|
17
40
|
if (name.length === 0)
|
|
@@ -132,7 +155,7 @@ function buildModelMeta(params) {
|
|
|
132
155
|
for (const name of model.primaryKey.fields) {
|
|
133
156
|
const field = fieldByName.get(name);
|
|
134
157
|
if (field === undefined) {
|
|
135
|
-
throw new
|
|
158
|
+
throw new VibeError2({
|
|
136
159
|
code: "VIBE_SCHEMA_INVALID",
|
|
137
160
|
message: `model "${model.name}" declares composite primary-key field "${name}", which does not exist`,
|
|
138
161
|
meta: { model: model.name, field: name }
|
|
@@ -144,26 +167,45 @@ function buildModelMeta(params) {
|
|
|
144
167
|
primaryKey.push(...fields.filter((field) => field.isId));
|
|
145
168
|
}
|
|
146
169
|
const compoundUniques = new Map;
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
return;
|
|
150
|
-
const key = names.join("_");
|
|
151
|
-
if (compoundUniques.has(key))
|
|
152
|
-
return;
|
|
170
|
+
const singleIndexUniques = [];
|
|
171
|
+
const resolveFields = (names) => {
|
|
153
172
|
const resolved = [];
|
|
154
173
|
for (const name of names) {
|
|
155
174
|
const field = fieldByName.get(name);
|
|
156
175
|
if (field === undefined)
|
|
157
|
-
return;
|
|
176
|
+
return null;
|
|
158
177
|
resolved.push(field);
|
|
159
178
|
}
|
|
160
|
-
|
|
179
|
+
return resolved;
|
|
180
|
+
};
|
|
181
|
+
const addCompound = (names, nullsNotDistinct) => {
|
|
182
|
+
if (names.length < 2)
|
|
183
|
+
return;
|
|
184
|
+
const key = names.join("_");
|
|
185
|
+
if (compoundUniques.has(key))
|
|
186
|
+
return;
|
|
187
|
+
const resolved = resolveFields(names);
|
|
188
|
+
if (resolved === null)
|
|
189
|
+
return;
|
|
190
|
+
compoundUniques.set(key, { fields: resolved, nullsNotDistinct });
|
|
161
191
|
};
|
|
162
192
|
if (primaryKey.length > 1)
|
|
163
|
-
addCompound(primaryKey.map((field) => field.name));
|
|
193
|
+
addCompound(primaryKey.map((field) => field.name), false);
|
|
164
194
|
for (const index of model.indexes) {
|
|
165
|
-
if (index.kind
|
|
166
|
-
|
|
195
|
+
if (index.kind !== "unique" || index.where !== undefined)
|
|
196
|
+
continue;
|
|
197
|
+
const names = index.fields.map((entry) => entry.name);
|
|
198
|
+
const nullsNotDistinct = index.nullsNotDistinct === true;
|
|
199
|
+
if (names.length === 1) {
|
|
200
|
+
const field = resolveFields(names)?.[0];
|
|
201
|
+
if (field === undefined || field.isId || field.isUnique)
|
|
202
|
+
continue;
|
|
203
|
+
if (singleIndexUniques.some((entry) => entry.fields[0]?.name === field.name))
|
|
204
|
+
continue;
|
|
205
|
+
singleIndexUniques.push({ fields: [field], nullsNotDistinct });
|
|
206
|
+
} else {
|
|
207
|
+
addCompound(names, nullsNotDistinct);
|
|
208
|
+
}
|
|
167
209
|
}
|
|
168
210
|
const computedSpecs = params.computed;
|
|
169
211
|
const computed = computedSpecs === undefined || Object.keys(computedSpecs).length === 0 ? null : new Map(Object.entries(computedSpecs).map(([name, spec]) => [
|
|
@@ -180,6 +222,7 @@ function buildModelMeta(params) {
|
|
|
180
222
|
primaryKey,
|
|
181
223
|
uniqueFields: fields.filter((field) => field.isId || field.isUnique),
|
|
182
224
|
compoundUniques,
|
|
225
|
+
singleIndexUniques,
|
|
183
226
|
relations: model.relations,
|
|
184
227
|
relationByName: new Map(model.relations.map((relation) => [relation.name, relation])),
|
|
185
228
|
isView: model.isView,
|
|
@@ -210,7 +253,7 @@ function getModelMeta(params) {
|
|
|
210
253
|
const { meta, model } = params;
|
|
211
254
|
const found = meta.modelByName.get(model);
|
|
212
255
|
if (found === undefined) {
|
|
213
|
-
throw new
|
|
256
|
+
throw new VibeError2({
|
|
214
257
|
code: "VIBE_VALIDATION",
|
|
215
258
|
message: `unknown model "${model}"`,
|
|
216
259
|
meta: { model, known: [...new Set(meta.models.map((entry) => entry.name))] }
|
|
@@ -223,20 +266,20 @@ function getFieldMeta(params) {
|
|
|
223
266
|
const found = model.fieldByName.get(field);
|
|
224
267
|
if (found === undefined) {
|
|
225
268
|
if (model.computed !== null && model.computed.has(field)) {
|
|
226
|
-
throw new
|
|
269
|
+
throw new VibeError2({
|
|
227
270
|
code: "VIBE_VALIDATION",
|
|
228
271
|
message: `"${field}" on model "${model.name}" is a computed field \u2014 computed fields appear in results (select/omit) only, not in ${context}`,
|
|
229
272
|
meta: { model: model.name, field, context, computed: true }
|
|
230
273
|
});
|
|
231
274
|
}
|
|
232
275
|
if (model.relationByName.has(field)) {
|
|
233
|
-
throw new
|
|
276
|
+
throw new VibeError2({
|
|
234
277
|
code: "VIBE_VALIDATION",
|
|
235
278
|
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)`,
|
|
236
279
|
meta: { model: model.name, field, context }
|
|
237
280
|
});
|
|
238
281
|
}
|
|
239
|
-
throw new
|
|
282
|
+
throw new VibeError2({
|
|
240
283
|
code: "VIBE_VALIDATION",
|
|
241
284
|
message: `unknown field "${field}" on model "${model.name}" in ${context}`,
|
|
242
285
|
meta: { model: model.name, field, context }
|
|
@@ -245,7 +288,7 @@ function getFieldMeta(params) {
|
|
|
245
288
|
return found;
|
|
246
289
|
}
|
|
247
290
|
// packages/runtime/src/codecs.ts
|
|
248
|
-
import { VibeError as
|
|
291
|
+
import { VibeError as VibeError3, isEnumType as isEnumType2 } from "@vibeorm/schema";
|
|
249
292
|
import { DIALECT_CODECS, decodeIsNoop } from "@vibeorm/sql";
|
|
250
293
|
import {
|
|
251
294
|
DIALECT_CODECS as DIALECT_CODECS2,
|
|
@@ -341,7 +384,7 @@ var DECIMAL_LITERAL = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
|
341
384
|
function toRealNumber(params) {
|
|
342
385
|
const { field, value } = params;
|
|
343
386
|
const refuse = () => {
|
|
344
|
-
throw new
|
|
387
|
+
throw new VibeError3({
|
|
345
388
|
code: "VIBE_VALIDATION",
|
|
346
389
|
message: `"${String(value)}" is not a numeric value for the Decimal field "${field.name}"`,
|
|
347
390
|
meta: { field: field.name, value }
|
|
@@ -556,10 +599,11 @@ function decodeAggregateValue(params) {
|
|
|
556
599
|
return field === null ? value : decodeValue({ dialect, field, value });
|
|
557
600
|
}
|
|
558
601
|
// packages/runtime/src/query-builder.ts
|
|
559
|
-
import { VibeError as
|
|
602
|
+
import { VibeError as VibeError5, unsupportedCapability } from "@vibeorm/schema";
|
|
560
603
|
import {
|
|
561
604
|
SQL_DEFAULT_CELL,
|
|
562
605
|
containsPattern,
|
|
606
|
+
dbNowMarker,
|
|
563
607
|
endsWithPattern,
|
|
564
608
|
escapeLikePattern,
|
|
565
609
|
jsonParamCell,
|
|
@@ -567,9 +611,9 @@ import {
|
|
|
567
611
|
} from "@vibeorm/sql";
|
|
568
612
|
|
|
569
613
|
// packages/runtime/src/relation-plan.ts
|
|
570
|
-
import { VibeError as
|
|
614
|
+
import { VibeError as VibeError4, implicitJoinTable } from "@vibeorm/schema";
|
|
571
615
|
function validationError(params) {
|
|
572
|
-
return new
|
|
616
|
+
return new VibeError4({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
573
617
|
}
|
|
574
618
|
function isPlainObject(value) {
|
|
575
619
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
@@ -593,7 +637,7 @@ function singleField(params) {
|
|
|
593
637
|
}
|
|
594
638
|
const field = model.fieldByName.get(names[0]);
|
|
595
639
|
if (field === undefined) {
|
|
596
|
-
throw new
|
|
640
|
+
throw new VibeError4({
|
|
597
641
|
code: "VIBE_SCHEMA_INVALID",
|
|
598
642
|
message: `relation "${relation.name}" on ${relation.model} names unknown ${what} field "${String(names[0])}"`,
|
|
599
643
|
meta: { model: relation.model, relation: relation.name }
|
|
@@ -662,7 +706,7 @@ function resolveRelationLinkUncached(params) {
|
|
|
662
706
|
}
|
|
663
707
|
const reverse = findReverseRelation({ relation, target });
|
|
664
708
|
if (reverse === null || reverse.fields === undefined || reverse.fields.length === 0) {
|
|
665
|
-
throw new
|
|
709
|
+
throw new VibeError4({
|
|
666
710
|
code: "VIBE_SCHEMA_INVALID",
|
|
667
711
|
message: `relation "${relation.name}" on ${relation.model} has no foreign-key counterpart on ${relation.targetModel}`,
|
|
668
712
|
meta: { model: relation.model, relation: relation.name, target: relation.targetModel }
|
|
@@ -959,15 +1003,15 @@ var ATOMIC_OPERATIONS = {
|
|
|
959
1003
|
divide: "divide"
|
|
960
1004
|
};
|
|
961
1005
|
var ALLOWED_ARGS = {
|
|
962
|
-
findMany: ["where", "orderBy", "take", "skip", "cursor", "distinct", "select", "include", "omit", "relationStrategy"],
|
|
963
|
-
findFirst: ["where", "orderBy", "take", "skip", "cursor", "distinct", "select", "include", "omit", "relationStrategy"],
|
|
964
|
-
findUnique: ["where", "select", "include", "omit", "relationStrategy"],
|
|
965
|
-
create: ["data", "select", "include", "omit"],
|
|
1006
|
+
findMany: ["where", "orderBy", "take", "skip", "cursor", "distinct", "select", "include", "omit", "relationStrategy", "lock"],
|
|
1007
|
+
findFirst: ["where", "orderBy", "take", "skip", "cursor", "distinct", "select", "include", "omit", "relationStrategy", "lock"],
|
|
1008
|
+
findUnique: ["where", "select", "include", "omit", "relationStrategy", "lock"],
|
|
1009
|
+
create: ["data", "select", "include", "omit", "onConflict"],
|
|
966
1010
|
createMany: ["data", "skipDuplicates"],
|
|
967
1011
|
createManyAndReturn: ["data", "skipDuplicates", "select", "include", "omit"],
|
|
968
|
-
update: ["where", "data", "select", "include", "omit"],
|
|
969
|
-
updateMany: ["where", "data"],
|
|
970
|
-
upsert: ["where", "create", "update", "select", "include", "omit"],
|
|
1012
|
+
update: ["where", "data", "select", "include", "omit", "skipUpdatedAt"],
|
|
1013
|
+
updateMany: ["where", "data", "skipUpdatedAt"],
|
|
1014
|
+
upsert: ["where", "create", "update", "select", "include", "omit", "onConflict"],
|
|
971
1015
|
delete: ["where", "select", "include", "omit"],
|
|
972
1016
|
deleteMany: ["where"],
|
|
973
1017
|
count: ["where", "select", "take", "skip", "orderBy"],
|
|
@@ -983,7 +1027,7 @@ var AGG_FN_BY_KEY = {
|
|
|
983
1027
|
_max: "max"
|
|
984
1028
|
};
|
|
985
1029
|
function validationError2(params) {
|
|
986
|
-
return new
|
|
1030
|
+
return new VibeError5({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
987
1031
|
}
|
|
988
1032
|
function isPlainObject2(value) {
|
|
989
1033
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
@@ -1003,6 +1047,121 @@ function columnRef(params) {
|
|
|
1003
1047
|
const { field, table } = params;
|
|
1004
1048
|
return table === undefined ? { name: field.columnName } : { table, name: field.columnName };
|
|
1005
1049
|
}
|
|
1050
|
+
function compileLock(params) {
|
|
1051
|
+
const { args, model, method, hasRelations } = params;
|
|
1052
|
+
if (args.lock === undefined)
|
|
1053
|
+
return false;
|
|
1054
|
+
if (model.isView) {
|
|
1055
|
+
throw validationError2({
|
|
1056
|
+
message: `lock on ${model.clientName}.${method}() \u2014 a view row cannot be locked; lock the underlying table's rows instead`,
|
|
1057
|
+
meta: { model: model.name, method }
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
if (args.lock !== "update") {
|
|
1061
|
+
throw validationError2({
|
|
1062
|
+
message: `lock on ${model.clientName}.${method}() must be "update", got ${String(args.lock)}`,
|
|
1063
|
+
meta: { model: model.name, method, lock: args.lock }
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
if (args.distinct !== undefined) {
|
|
1067
|
+
throw validationError2({
|
|
1068
|
+
message: `lock cannot combine with distinct on ${model.clientName}.${method}() \u2014 postgres rejects FOR UPDATE with DISTINCT`,
|
|
1069
|
+
meta: { model: model.name, method }
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
if (hasRelations) {
|
|
1073
|
+
throw validationError2({
|
|
1074
|
+
message: `lock cannot combine with relation loading on ${model.clientName}.${method}() \u2014 lock the parent row alone, then read relations inside the same transaction`,
|
|
1075
|
+
meta: { model: model.name, method }
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
return true;
|
|
1079
|
+
}
|
|
1080
|
+
function compileConflictTarget(params) {
|
|
1081
|
+
const { dialect, meta, model, method, target, where } = params;
|
|
1082
|
+
if ((target !== undefined || where !== undefined) && dialect.doNothingForm === "insertIgnore") {
|
|
1083
|
+
throw unsupportedCapability({
|
|
1084
|
+
dialect: dialect.name,
|
|
1085
|
+
feature: `a conflict target on ${model.clientName}.${method}()`,
|
|
1086
|
+
hint: "mysql's INSERT IGNORE / ON DUPLICATE KEY fire on ANY duplicate key \u2014 no target can be named; gate the call per dialect"
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
const out = {};
|
|
1090
|
+
if (target !== undefined) {
|
|
1091
|
+
if (Array.isArray(target)) {
|
|
1092
|
+
if (target.length === 0) {
|
|
1093
|
+
throw validationError2({
|
|
1094
|
+
message: `onConflict target on ${model.clientName}.${method}() must name at least one field`,
|
|
1095
|
+
meta: { model: model.name, method }
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
out.columns = target.map((name) => getFieldMeta({ model, field: String(name), context: "onConflict target" }).columnName);
|
|
1099
|
+
} else if (isPlainObject2(target) && typeof target.constraint === "string" && target.constraint.length > 0 && Object.keys(target).length === 1) {
|
|
1100
|
+
if (!dialect.capabilities.conflictTargetConstraint) {
|
|
1101
|
+
throw unsupportedCapability({
|
|
1102
|
+
dialect: dialect.name,
|
|
1103
|
+
feature: "a named-constraint conflict target (ON CONFLICT ON CONSTRAINT)",
|
|
1104
|
+
hint: "only postgres supports ON CONSTRAINT targets \u2014 name the indexed columns instead"
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
out.constraint = target.constraint;
|
|
1108
|
+
} else {
|
|
1109
|
+
throw validationError2({
|
|
1110
|
+
message: `onConflict target on ${model.clientName}.${method}() must be a field-name array or { constraint: string }`,
|
|
1111
|
+
meta: { model: model.name, method }
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (where !== undefined) {
|
|
1116
|
+
if (out.columns === undefined) {
|
|
1117
|
+
throw validationError2({
|
|
1118
|
+
message: `onConflict where on ${model.clientName}.${method}() needs a column-list target \u2014 the predicate refines "(columns) WHERE \u2026"`,
|
|
1119
|
+
meta: { model: model.name, method }
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
if (!dialect.capabilities.partialIndexes) {
|
|
1123
|
+
throw unsupportedCapability({
|
|
1124
|
+
dialect: dialect.name,
|
|
1125
|
+
feature: "a partial-index conflict target",
|
|
1126
|
+
hint: "this dialect has no partial indexes to target"
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
if (typeof where !== "string" || where.trim().length === 0) {
|
|
1130
|
+
throw validationError2({
|
|
1131
|
+
message: `onConflict where on ${model.clientName}.${method}() must be the partial index's raw predicate string (the schema declaration's where: text)`,
|
|
1132
|
+
meta: { model: model.name, method }
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
out.conflictWhere = where;
|
|
1136
|
+
}
|
|
1137
|
+
return out;
|
|
1138
|
+
}
|
|
1139
|
+
function compileDbNowWrite(params) {
|
|
1140
|
+
const { model, field, value } = params;
|
|
1141
|
+
if (field.scalarType !== "DateTime" || field.isList) {
|
|
1142
|
+
throw validationError2({
|
|
1143
|
+
message: `dbNow() writes the database clock into a DateTime field \u2014 ${model.name}.${field.name} is ${String(field.enumName ?? field.scalarType)}${field.isList ? "[]" : ""}`,
|
|
1144
|
+
meta: { model: model.name, field: field.name }
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
if (field.isId || field.isUnique || model.primaryKey.some((candidate) => candidate.name === field.name)) {
|
|
1148
|
+
throw validationError2({
|
|
1149
|
+
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`,
|
|
1150
|
+
meta: { model: model.name, field: field.name }
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
return dbNowMarker({ offsetSeconds: value.offsetSeconds });
|
|
1154
|
+
}
|
|
1155
|
+
function compileDbNowFilter(params) {
|
|
1156
|
+
const { model, field, value } = params;
|
|
1157
|
+
if (field.scalarType !== "DateTime" || field.isList) {
|
|
1158
|
+
throw validationError2({
|
|
1159
|
+
message: `dbNow() compares against a DateTime field \u2014 ${model.name}.${field.name} is ${String(field.enumName ?? field.scalarType)}${field.isList ? "[]" : ""}`,
|
|
1160
|
+
meta: { model: model.name, field: field.name }
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
return dbNowMarker({ offsetSeconds: value.offsetSeconds });
|
|
1164
|
+
}
|
|
1006
1165
|
function requireObject(params) {
|
|
1007
1166
|
const { value, what, model } = params;
|
|
1008
1167
|
if (!isPlainObject2(value)) {
|
|
@@ -1319,7 +1478,7 @@ function compileOperatorFilter(params) {
|
|
|
1319
1478
|
kind: "cmp",
|
|
1320
1479
|
op: "eq",
|
|
1321
1480
|
column,
|
|
1322
|
-
value: encodeFilterValue({ dialect: dialect.name, field, value: operand })
|
|
1481
|
+
value: isDbNow(operand) ? compileDbNowFilter({ model, field, value: operand }) : encodeFilterValue({ dialect: dialect.name, field, value: operand })
|
|
1323
1482
|
});
|
|
1324
1483
|
break;
|
|
1325
1484
|
}
|
|
@@ -1343,7 +1502,7 @@ function compileOperatorFilter(params) {
|
|
|
1343
1502
|
kind: "cmp",
|
|
1344
1503
|
op: "ne",
|
|
1345
1504
|
column,
|
|
1346
|
-
value: encodeFilterValue({ dialect: dialect.name, field, value: operand })
|
|
1505
|
+
value: isDbNow(operand) ? compileDbNowFilter({ model, field, value: operand }) : encodeFilterValue({ dialect: dialect.name, field, value: operand })
|
|
1347
1506
|
});
|
|
1348
1507
|
break;
|
|
1349
1508
|
}
|
|
@@ -1355,6 +1514,14 @@ function compileOperatorFilter(params) {
|
|
|
1355
1514
|
meta: { model: model.name, field: field.name, operator }
|
|
1356
1515
|
});
|
|
1357
1516
|
}
|
|
1517
|
+
for (const item of operand) {
|
|
1518
|
+
if (isDbNow(item)) {
|
|
1519
|
+
throw validationError2({
|
|
1520
|
+
message: `dbNow() cannot ride an "${operator}" list on ${model.name}.${field.name} \u2014 compare with lt/lte/gt/gte/equals instead`,
|
|
1521
|
+
meta: { model: model.name, field: field.name, operator }
|
|
1522
|
+
});
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1358
1525
|
conditions.push({
|
|
1359
1526
|
kind: "inArray",
|
|
1360
1527
|
column,
|
|
@@ -1371,7 +1538,7 @@ function compileOperatorFilter(params) {
|
|
|
1371
1538
|
kind: "cmp",
|
|
1372
1539
|
op: operator,
|
|
1373
1540
|
column,
|
|
1374
|
-
value: encodeFilterValue({ dialect: dialect.name, field, value: operand })
|
|
1541
|
+
value: isDbNow(operand) ? compileDbNowFilter({ model, field, value: operand }) : encodeFilterValue({ dialect: dialect.name, field, value: operand })
|
|
1375
1542
|
});
|
|
1376
1543
|
break;
|
|
1377
1544
|
}
|
|
@@ -1427,6 +1594,9 @@ function compileFieldFilter(params) {
|
|
|
1427
1594
|
if (isDirectValue(value)) {
|
|
1428
1595
|
return { kind: "cmp", op: "eq", column, value: encodeFilterValue({ dialect: dialect.name, field, value }) };
|
|
1429
1596
|
}
|
|
1597
|
+
if (isDbNow(value)) {
|
|
1598
|
+
return { kind: "cmp", op: "eq", column, value: compileDbNowFilter({ model, field, value }) };
|
|
1599
|
+
}
|
|
1430
1600
|
if (Array.isArray(value)) {
|
|
1431
1601
|
if (!field.isList) {
|
|
1432
1602
|
throw validationError2({
|
|
@@ -1945,7 +2115,7 @@ function expandCompoundWhere(params) {
|
|
|
1945
2115
|
for (const [key, value] of Object.entries(where)) {
|
|
1946
2116
|
if (value === undefined)
|
|
1947
2117
|
continue;
|
|
1948
|
-
const fields = model.compoundUniques.get(key);
|
|
2118
|
+
const fields = model.compoundUniques.get(key)?.fields;
|
|
1949
2119
|
if (fields === undefined)
|
|
1950
2120
|
continue;
|
|
1951
2121
|
const spec = requireObject({ value, what: `${what}.${key}`, model });
|
|
@@ -1964,33 +2134,52 @@ function expandCompoundWhere(params) {
|
|
|
1964
2134
|
}
|
|
1965
2135
|
return expanded ?? where;
|
|
1966
2136
|
}
|
|
2137
|
+
function isAddressableMember(params) {
|
|
2138
|
+
return isDirectValue(params.value) || params.nullsNotDistinct && params.value === null;
|
|
2139
|
+
}
|
|
1967
2140
|
function assertUniqueWhere(params) {
|
|
1968
2141
|
const { model, where, method } = params;
|
|
1969
2142
|
for (const field of model.uniqueFields) {
|
|
1970
2143
|
if (isDirectValue(where[field.name]))
|
|
1971
2144
|
return;
|
|
1972
2145
|
}
|
|
1973
|
-
for (const
|
|
1974
|
-
|
|
2146
|
+
for (const single of model.singleIndexUniques) {
|
|
2147
|
+
const field = single.fields[0];
|
|
2148
|
+
if (isAddressableMember({ value: where[field.name], nullsNotDistinct: single.nullsNotDistinct }))
|
|
1975
2149
|
return;
|
|
1976
2150
|
}
|
|
1977
|
-
const
|
|
2151
|
+
for (const group of model.compoundUniques.values()) {
|
|
2152
|
+
if (group.fields.every((field) => isAddressableMember({ value: where[field.name], nullsNotDistinct: group.nullsNotDistinct })))
|
|
2153
|
+
return;
|
|
2154
|
+
}
|
|
2155
|
+
const candidates = [
|
|
2156
|
+
...model.uniqueFields.map((field) => field.name),
|
|
2157
|
+
...model.singleIndexUniques.map((single) => single.fields[0].name),
|
|
2158
|
+
...model.compoundUniques.keys()
|
|
2159
|
+
];
|
|
1978
2160
|
throw validationError2({
|
|
1979
2161
|
message: `${model.clientName}.${method}() needs a unique where \u2014 one of: ${candidates.join(", ")}`,
|
|
1980
2162
|
meta: { model: model.name, method, candidates }
|
|
1981
2163
|
});
|
|
1982
2164
|
}
|
|
1983
|
-
function
|
|
2165
|
+
function conflictTargetGroup(params) {
|
|
1984
2166
|
const { model, where } = params;
|
|
1985
2167
|
for (const field of model.uniqueFields) {
|
|
1986
2168
|
if (isDirectValue(where[field.name]))
|
|
1987
|
-
return [field];
|
|
2169
|
+
return { fields: [field], nullsNotDistinct: false };
|
|
1988
2170
|
}
|
|
1989
|
-
for (const
|
|
1990
|
-
|
|
1991
|
-
|
|
2171
|
+
for (const single of model.singleIndexUniques) {
|
|
2172
|
+
const field = single.fields[0];
|
|
2173
|
+
if (isAddressableMember({ value: where[field.name], nullsNotDistinct: single.nullsNotDistinct })) {
|
|
2174
|
+
return single;
|
|
2175
|
+
}
|
|
1992
2176
|
}
|
|
1993
|
-
|
|
2177
|
+
for (const group of model.compoundUniques.values()) {
|
|
2178
|
+
if (group.fields.every((field) => isAddressableMember({ value: where[field.name], nullsNotDistinct: group.nullsNotDistinct }))) {
|
|
2179
|
+
return group;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
return { fields: model.primaryKey, nullsNotDistinct: false };
|
|
1994
2183
|
}
|
|
1995
2184
|
function sameDirectValue(a, b) {
|
|
1996
2185
|
if (a === b)
|
|
@@ -2010,15 +2199,21 @@ function upsertUsesNativeStatement(params) {
|
|
|
2010
2199
|
});
|
|
2011
2200
|
assertUniqueWhere({ model, where, method: "upsert" });
|
|
2012
2201
|
const create = requireObject({ value: args.create, what: "create", model });
|
|
2013
|
-
const
|
|
2014
|
-
const targetNames = new Set(
|
|
2202
|
+
const target = conflictTargetGroup({ model, where });
|
|
2203
|
+
const targetNames = new Set(target.fields.map((field) => field.name));
|
|
2015
2204
|
for (const [key, value] of Object.entries(where)) {
|
|
2016
2205
|
if (value === undefined)
|
|
2017
2206
|
continue;
|
|
2018
2207
|
if (!targetNames.has(key))
|
|
2019
2208
|
return false;
|
|
2020
2209
|
}
|
|
2021
|
-
return
|
|
2210
|
+
return target.fields.every((field) => {
|
|
2211
|
+
const createValue = create[field.name];
|
|
2212
|
+
const whereValue = where[field.name];
|
|
2213
|
+
if (target.nullsNotDistinct && createValue === null && whereValue === null)
|
|
2214
|
+
return true;
|
|
2215
|
+
return isDirectValue(createValue) && sameDirectValue(createValue, whereValue);
|
|
2216
|
+
});
|
|
2022
2217
|
}
|
|
2023
2218
|
var writePlanCache = new WeakMap;
|
|
2024
2219
|
function getWritePlan(params) {
|
|
@@ -2074,6 +2269,17 @@ function compileCreateRow(params) {
|
|
|
2074
2269
|
}
|
|
2075
2270
|
if (entry.listRefused)
|
|
2076
2271
|
assertListWritable({ dialect, model, field: entry.field });
|
|
2272
|
+
if (isDbNow(value)) {
|
|
2273
|
+
raw.set(entry.field.name, value);
|
|
2274
|
+
values[entry.field.columnName] = compileDbNowWrite({ model, field: entry.field, value });
|
|
2275
|
+
continue;
|
|
2276
|
+
}
|
|
2277
|
+
if (Array.isArray(value) && value.some((item) => isDbNow(item))) {
|
|
2278
|
+
throw validationError2({
|
|
2279
|
+
message: `dbNow() cannot be a list element (${model.name}.${entry.field.name})`,
|
|
2280
|
+
meta: { model: model.name, field: entry.field.name }
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2077
2283
|
raw.set(entry.field.name, value);
|
|
2078
2284
|
values[entry.field.columnName] = encodeWith(entry, value);
|
|
2079
2285
|
}
|
|
@@ -2145,6 +2351,16 @@ function compileUpdateSet(params) {
|
|
|
2145
2351
|
if (entry.listRefused)
|
|
2146
2352
|
assertListWritable({ dialect, model, field });
|
|
2147
2353
|
touched.add(field.name);
|
|
2354
|
+
if (isDbNow(value)) {
|
|
2355
|
+
set[field.columnName] = compileDbNowWrite({ model, field, value });
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2358
|
+
if (Array.isArray(value) && value.some((item) => isDbNow(item))) {
|
|
2359
|
+
throw validationError2({
|
|
2360
|
+
message: `dbNow() cannot be a list element (${model.name}.${field.name})`,
|
|
2361
|
+
meta: { model: model.name, field: field.name }
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2148
2364
|
if (isPlainObject2(value) && field.scalarType !== "Json") {
|
|
2149
2365
|
const entries = Object.entries(value).filter(([, operand2]) => operand2 !== undefined);
|
|
2150
2366
|
if (entries.length !== 1) {
|
|
@@ -2155,7 +2371,7 @@ function compileUpdateSet(params) {
|
|
|
2155
2371
|
}
|
|
2156
2372
|
const [operator, operand] = entries[0];
|
|
2157
2373
|
if (operator === "set") {
|
|
2158
|
-
set[field.columnName] = encodeWith(entry, operand);
|
|
2374
|
+
set[field.columnName] = isDbNow(operand) ? compileDbNowWrite({ model, field, value: operand }) : encodeWith(entry, operand);
|
|
2159
2375
|
continue;
|
|
2160
2376
|
}
|
|
2161
2377
|
if (operator === "push" && field.isList) {
|
|
@@ -2174,10 +2390,12 @@ function compileUpdateSet(params) {
|
|
|
2174
2390
|
}
|
|
2175
2391
|
set[field.columnName] = encodeWith(entry, value);
|
|
2176
2392
|
}
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2393
|
+
if (params.skipUpdatedAt !== true) {
|
|
2394
|
+
for (const entry of plan.injected) {
|
|
2395
|
+
if (!entry.field.isUpdatedAt || touched.has(entry.field.name))
|
|
2396
|
+
continue;
|
|
2397
|
+
set[entry.field.columnName] = encodeWith(entry, new Date);
|
|
2398
|
+
}
|
|
2181
2399
|
}
|
|
2182
2400
|
if (Object.keys(set).length === 0) {
|
|
2183
2401
|
throw validationError2({
|
|
@@ -2277,7 +2495,8 @@ function parseCursor(params) {
|
|
|
2277
2495
|
}
|
|
2278
2496
|
if (fields.length === 1) {
|
|
2279
2497
|
const field = fields[0];
|
|
2280
|
-
|
|
2498
|
+
const isSingleIndexUnique = model.singleIndexUniques.some((single) => single.fields[0].name === field.name);
|
|
2499
|
+
if (!field.isId && !field.isUnique && !isSingleIndexUnique) {
|
|
2281
2500
|
throw validationError2({
|
|
2282
2501
|
message: `cursor field ${model.name}.${field.name} must be @id or @unique \u2014 a non-unique cursor cannot identify a position`,
|
|
2283
2502
|
meta: { model: model.name, field: field.name }
|
|
@@ -2286,7 +2505,7 @@ function parseCursor(params) {
|
|
|
2286
2505
|
} else {
|
|
2287
2506
|
const names = new Set(fields.map((field) => field.name));
|
|
2288
2507
|
const matchesGroup = (group) => group.length === names.size && group.every((field) => names.has(field.name));
|
|
2289
|
-
const matches = matchesGroup(model.primaryKey) || [...model.compoundUniques.values()].some(matchesGroup);
|
|
2508
|
+
const matches = matchesGroup(model.primaryKey) || [...model.compoundUniques.values()].some((group) => matchesGroup(group.fields));
|
|
2290
2509
|
if (!matches) {
|
|
2291
2510
|
const candidates = [
|
|
2292
2511
|
model.primaryKey.map((field) => field.name).join(" + ") || "none",
|
|
@@ -3099,6 +3318,7 @@ function buildQuery(params) {
|
|
|
3099
3318
|
switch (method) {
|
|
3100
3319
|
case "findMany":
|
|
3101
3320
|
case "findFirst": {
|
|
3321
|
+
const locked = compileLock({ args, model, method, hasRelations: relations !== null });
|
|
3102
3322
|
const shape = buildFindShape({
|
|
3103
3323
|
dialect,
|
|
3104
3324
|
meta,
|
|
@@ -3111,12 +3331,13 @@ function buildQuery(params) {
|
|
|
3111
3331
|
});
|
|
3112
3332
|
return {
|
|
3113
3333
|
...base,
|
|
3114
|
-
statement: shape.statement,
|
|
3334
|
+
statement: locked ? { ...shape.statement, lockForUpdate: true } : shape.statement,
|
|
3115
3335
|
reverseRows: shape.reverseRows,
|
|
3116
3336
|
resortBy: shape.resortBy
|
|
3117
3337
|
};
|
|
3118
3338
|
}
|
|
3119
3339
|
case "findUnique": {
|
|
3340
|
+
const locked = compileLock({ args, model, method, hasRelations: relations !== null });
|
|
3120
3341
|
const where = expandCompoundWhere({
|
|
3121
3342
|
model,
|
|
3122
3343
|
where: requireObject({ value: args.where, what: "where", model })
|
|
@@ -3131,7 +3352,10 @@ function buildQuery(params) {
|
|
|
3131
3352
|
...useLateral ? { selection: relations } : {},
|
|
3132
3353
|
clientOptions: undefined
|
|
3133
3354
|
});
|
|
3134
|
-
return {
|
|
3355
|
+
return {
|
|
3356
|
+
...base,
|
|
3357
|
+
statement: locked ? { ...shape.statement, lockForUpdate: true } : shape.statement
|
|
3358
|
+
};
|
|
3135
3359
|
}
|
|
3136
3360
|
case "count": {
|
|
3137
3361
|
const where = args.where === undefined ? undefined : compileWhere({ dialect, meta, model, where: requireObject({ value: args.where, what: "where", model }) });
|
|
@@ -3321,6 +3545,38 @@ function buildQuery(params) {
|
|
|
3321
3545
|
};
|
|
3322
3546
|
}
|
|
3323
3547
|
case "create": {
|
|
3548
|
+
let conflictClause;
|
|
3549
|
+
if (args.onConflict !== undefined) {
|
|
3550
|
+
const spec = requireObject({ value: args.onConflict, what: "onConflict", model });
|
|
3551
|
+
for (const key of Object.keys(spec)) {
|
|
3552
|
+
if (key !== "action" && key !== "target" && key !== "where") {
|
|
3553
|
+
throw validationError2({
|
|
3554
|
+
message: `unknown onConflict key "${key}" on ${model.clientName}.create() \u2014 expected action/target/where`,
|
|
3555
|
+
meta: { model: model.name, key }
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
if (spec.action !== "nothing") {
|
|
3560
|
+
throw validationError2({
|
|
3561
|
+
message: `onConflict.action on ${model.clientName}.create() must be "nothing" (conflicting rows are skipped \u2014 to update instead, use upsert())`,
|
|
3562
|
+
meta: { model: model.name }
|
|
3563
|
+
});
|
|
3564
|
+
}
|
|
3565
|
+
const compiled = compileConflictTarget({
|
|
3566
|
+
dialect,
|
|
3567
|
+
meta,
|
|
3568
|
+
model,
|
|
3569
|
+
method,
|
|
3570
|
+
target: spec.target,
|
|
3571
|
+
where: spec.where
|
|
3572
|
+
});
|
|
3573
|
+
conflictClause = {
|
|
3574
|
+
doNothing: true,
|
|
3575
|
+
...compiled.columns === undefined ? {} : { columns: compiled.columns },
|
|
3576
|
+
...compiled.constraint === undefined ? {} : { constraint: compiled.constraint },
|
|
3577
|
+
...compiled.conflictWhere === undefined ? {} : { conflictWhere: compiled.conflictWhere }
|
|
3578
|
+
};
|
|
3579
|
+
}
|
|
3324
3580
|
const row = compileCreateRow({ dialect, model, data: args.data });
|
|
3325
3581
|
const columns = orderedColumns({ model, values: row.values });
|
|
3326
3582
|
if (columns.length === 0) {
|
|
@@ -3337,6 +3593,7 @@ function buildQuery(params) {
|
|
|
3337
3593
|
table: { name: model.tableName },
|
|
3338
3594
|
columns,
|
|
3339
3595
|
rows: [columns.map((column) => row.values[column])],
|
|
3596
|
+
...conflictClause === undefined ? {} : { onConflict: conflictClause },
|
|
3340
3597
|
returning: returningColumns({ model, selectedFields })
|
|
3341
3598
|
}
|
|
3342
3599
|
};
|
|
@@ -3351,13 +3608,40 @@ function buildQuery(params) {
|
|
|
3351
3608
|
});
|
|
3352
3609
|
}
|
|
3353
3610
|
const groups = compileCreateRows({ dialect, model, data: args.data });
|
|
3354
|
-
|
|
3611
|
+
let manyConflict;
|
|
3612
|
+
if (args.skipDuplicates === true) {
|
|
3613
|
+
manyConflict = { doNothing: true };
|
|
3614
|
+
} else if (args.skipDuplicates !== undefined && args.skipDuplicates !== false) {
|
|
3615
|
+
const spec = requireObject({ value: args.skipDuplicates, what: "skipDuplicates", model });
|
|
3616
|
+
for (const key of Object.keys(spec)) {
|
|
3617
|
+
if (key !== "target" && key !== "where") {
|
|
3618
|
+
throw validationError2({
|
|
3619
|
+
message: `unknown skipDuplicates key "${key}" on ${model.clientName}.${method}() \u2014 expected target/where`,
|
|
3620
|
+
meta: { model: model.name, key }
|
|
3621
|
+
});
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
const compiled = compileConflictTarget({
|
|
3625
|
+
dialect,
|
|
3626
|
+
meta,
|
|
3627
|
+
model,
|
|
3628
|
+
method,
|
|
3629
|
+
target: spec.target,
|
|
3630
|
+
where: spec.where
|
|
3631
|
+
});
|
|
3632
|
+
manyConflict = {
|
|
3633
|
+
doNothing: true,
|
|
3634
|
+
...compiled.columns === undefined ? {} : { columns: compiled.columns },
|
|
3635
|
+
...compiled.constraint === undefined ? {} : { constraint: compiled.constraint },
|
|
3636
|
+
...compiled.conflictWhere === undefined ? {} : { conflictWhere: compiled.conflictWhere }
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3355
3639
|
const statements = groups.map((group) => ({
|
|
3356
3640
|
kind: "insert",
|
|
3357
3641
|
table: { name: model.tableName },
|
|
3358
3642
|
columns: group.columns,
|
|
3359
3643
|
rows: group.rows,
|
|
3360
|
-
...
|
|
3644
|
+
...manyConflict === undefined ? {} : { onConflict: manyConflict },
|
|
3361
3645
|
...method === "createManyAndReturn" ? { returning: returningColumns({ model, selectedFields }) } : {}
|
|
3362
3646
|
}));
|
|
3363
3647
|
return {
|
|
@@ -3369,6 +3653,12 @@ function buildQuery(params) {
|
|
|
3369
3653
|
case "update":
|
|
3370
3654
|
case "updateMany": {
|
|
3371
3655
|
const isUnique = method === "update";
|
|
3656
|
+
if (args.skipUpdatedAt !== undefined && typeof args.skipUpdatedAt !== "boolean") {
|
|
3657
|
+
throw validationError2({
|
|
3658
|
+
message: `skipUpdatedAt on ${model.clientName}.${method}() must be a boolean, got ${String(args.skipUpdatedAt)}`,
|
|
3659
|
+
meta: { model: model.name, method }
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3372
3662
|
const raw = requireObject({ value: args.where, what: "where", model });
|
|
3373
3663
|
const where = isUnique ? expandCompoundWhere({ model, where: raw }) : raw;
|
|
3374
3664
|
if (isUnique)
|
|
@@ -3380,13 +3670,36 @@ function buildQuery(params) {
|
|
|
3380
3670
|
statement: {
|
|
3381
3671
|
kind: "update",
|
|
3382
3672
|
table: { name: model.tableName },
|
|
3383
|
-
set: compileUpdateSet({
|
|
3673
|
+
set: compileUpdateSet({
|
|
3674
|
+
dialect,
|
|
3675
|
+
model,
|
|
3676
|
+
data: args.data,
|
|
3677
|
+
...args.skipUpdatedAt === true ? { skipUpdatedAt: true } : {}
|
|
3678
|
+
}),
|
|
3384
3679
|
...compiled === undefined ? {} : { where: compiled },
|
|
3385
3680
|
...isUnique ? { returning: returningColumns({ model, selectedFields }) } : {}
|
|
3386
3681
|
}
|
|
3387
3682
|
};
|
|
3388
3683
|
}
|
|
3389
3684
|
case "upsert": {
|
|
3685
|
+
let constraintTarget;
|
|
3686
|
+
if (args.onConflict !== undefined) {
|
|
3687
|
+
const spec = requireObject({ value: args.onConflict, what: "onConflict", model });
|
|
3688
|
+
if (typeof spec.constraint !== "string" || spec.constraint.length === 0 || Object.keys(spec).length !== 1) {
|
|
3689
|
+
throw validationError2({
|
|
3690
|
+
message: `onConflict on ${model.clientName}.upsert() must be { constraint: string } \u2014 the named unique constraint that arbitrates the conflict`,
|
|
3691
|
+
meta: { model: model.name }
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
3694
|
+
if (!dialect.capabilities.conflictTargetConstraint) {
|
|
3695
|
+
throw unsupportedCapability({
|
|
3696
|
+
dialect: dialect.name,
|
|
3697
|
+
feature: "a named-constraint conflict target on upsert",
|
|
3698
|
+
hint: "only postgres supports ON CONFLICT ON CONSTRAINT \u2014 rely on the where-derived column target instead"
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
constraintTarget = spec.constraint;
|
|
3702
|
+
}
|
|
3390
3703
|
const where = expandCompoundWhere({
|
|
3391
3704
|
model,
|
|
3392
3705
|
where: requireObject({ value: args.where, what: "where", model })
|
|
@@ -3394,10 +3707,10 @@ function buildQuery(params) {
|
|
|
3394
3707
|
assertUniqueWhere({ model, where, method });
|
|
3395
3708
|
const row = compileCreateRow({ dialect, model, data: args.create });
|
|
3396
3709
|
const columns = orderedColumns({ model, values: row.values });
|
|
3397
|
-
const targetFields =
|
|
3710
|
+
const targetFields = conflictTargetGroup({ model, where }).fields;
|
|
3398
3711
|
for (const field of targetFields) {
|
|
3399
3712
|
if (!(field.columnName in row.values)) {
|
|
3400
|
-
throw new
|
|
3713
|
+
throw new VibeError5({
|
|
3401
3714
|
code: "VIBE_INTERNAL",
|
|
3402
3715
|
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`,
|
|
3403
3716
|
meta: { model: model.name, column: field.columnName }
|
|
@@ -3408,7 +3721,7 @@ function buildQuery(params) {
|
|
|
3408
3721
|
for (const [key, value] of Object.entries(where)) {
|
|
3409
3722
|
if (value === undefined || nativeTargetNames.has(key))
|
|
3410
3723
|
continue;
|
|
3411
|
-
throw new
|
|
3724
|
+
throw new VibeError5({
|
|
3412
3725
|
code: "VIBE_INTERNAL",
|
|
3413
3726
|
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`,
|
|
3414
3727
|
meta: { model: model.name, field: key }
|
|
@@ -3428,6 +3741,7 @@ function buildQuery(params) {
|
|
|
3428
3741
|
rows: [columns.map((column) => row.values[column])],
|
|
3429
3742
|
onConflict: {
|
|
3430
3743
|
columns: targetFields.map((field) => field.columnName),
|
|
3744
|
+
...constraintTarget === undefined ? {} : { constraint: constraintTarget },
|
|
3431
3745
|
update,
|
|
3432
3746
|
...odkuKeyColumn === undefined ? {} : { lastInsertIdColumn: odkuKeyColumn }
|
|
3433
3747
|
},
|
|
@@ -3457,10 +3771,10 @@ function buildQuery(params) {
|
|
|
3457
3771
|
}
|
|
3458
3772
|
}
|
|
3459
3773
|
// packages/runtime/src/relation-loader.ts
|
|
3460
|
-
import { VibeError as
|
|
3774
|
+
import { VibeError as VibeError7 } from "@vibeorm/schema";
|
|
3461
3775
|
|
|
3462
3776
|
// packages/runtime/src/computed.ts
|
|
3463
|
-
import { VibeError as
|
|
3777
|
+
import { VibeError as VibeError6 } from "@vibeorm/schema";
|
|
3464
3778
|
function sameNeeds(a, b) {
|
|
3465
3779
|
if (a.length !== b.length)
|
|
3466
3780
|
return false;
|
|
@@ -3496,7 +3810,7 @@ function assertComputedManifest(params) {
|
|
|
3496
3810
|
problems.push(`"${key}" is configured but missing from the generated client`);
|
|
3497
3811
|
}
|
|
3498
3812
|
if (problems.length > 0) {
|
|
3499
|
-
throw new
|
|
3813
|
+
throw new VibeError6({
|
|
3500
3814
|
code: "VIBE_STALE_CLIENT",
|
|
3501
3815
|
message: `computed-field config changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
|
|
3502
3816
|
meta: { problems }
|
|
@@ -3660,7 +3974,7 @@ async function loadManyToMany(params) {
|
|
|
3660
3974
|
const link = node.link;
|
|
3661
3975
|
const join = link.join;
|
|
3662
3976
|
if (join === null) {
|
|
3663
|
-
throw new
|
|
3977
|
+
throw new VibeError7({
|
|
3664
3978
|
code: "VIBE_INTERNAL",
|
|
3665
3979
|
message: `manyToMany relation "${node.name}" resolved without join-table wiring`
|
|
3666
3980
|
});
|
|
@@ -3858,7 +4172,7 @@ async function attachLateralRelations(params) {
|
|
|
3858
4172
|
try {
|
|
3859
4173
|
parsed = JSON.parse(raw);
|
|
3860
4174
|
} catch (cause) {
|
|
3861
|
-
throw new
|
|
4175
|
+
throw new VibeError7({
|
|
3862
4176
|
code: "VIBE_INTERNAL",
|
|
3863
4177
|
message: `relation column "${column}" returned text that is not valid JSON`,
|
|
3864
4178
|
meta: { relation: node.name },
|
|
@@ -3895,12 +4209,12 @@ async function attachLateralRelations(params) {
|
|
|
3895
4209
|
await loadCounts({ ctx, model, rows, counts: selection.counts });
|
|
3896
4210
|
}
|
|
3897
4211
|
// packages/runtime/src/nested-writes.ts
|
|
3898
|
-
import { VibeError as
|
|
4212
|
+
import { VibeError as VibeError8 } from "@vibeorm/schema";
|
|
3899
4213
|
function validationError3(params) {
|
|
3900
|
-
return new
|
|
4214
|
+
return new VibeError8({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
3901
4215
|
}
|
|
3902
4216
|
function notFound(params) {
|
|
3903
|
-
return new
|
|
4217
|
+
return new VibeError8({ code: "VIBE_NOT_FOUND", message: params.message, meta: params.meta });
|
|
3904
4218
|
}
|
|
3905
4219
|
function isPlainObject3(value) {
|
|
3906
4220
|
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof Uint8Array);
|
|
@@ -4730,7 +5044,7 @@ async function updateWithNested(params) {
|
|
|
4730
5044
|
});
|
|
4731
5045
|
}
|
|
4732
5046
|
// packages/runtime/src/extensions.ts
|
|
4733
|
-
import { VibeError as
|
|
5047
|
+
import { VibeError as VibeError9, extensionManifestEntry } from "@vibeorm/schema";
|
|
4734
5048
|
function assertExtensionManifest(params) {
|
|
4735
5049
|
const { manifest } = params;
|
|
4736
5050
|
const instances = params.instances ?? [];
|
|
@@ -4753,7 +5067,7 @@ function assertExtensionManifest(params) {
|
|
|
4753
5067
|
problems.push(`"${name}" is configured but missing from the generated client`);
|
|
4754
5068
|
}
|
|
4755
5069
|
if (problems.length > 0) {
|
|
4756
|
-
throw new
|
|
5070
|
+
throw new VibeError9({
|
|
4757
5071
|
code: "VIBE_STALE_CLIENT",
|
|
4758
5072
|
message: `extension list changed since generate; run vibeorm generate \u2014 ${problems.join("; ")}`,
|
|
4759
5073
|
meta: { manifest, problems }
|
|
@@ -4768,7 +5082,7 @@ function buildExtensionOperatorMap(params) {
|
|
|
4768
5082
|
for (const operator of extension.sql?.whereOperators ?? []) {
|
|
4769
5083
|
const existing = owner.get(operator.name);
|
|
4770
5084
|
if (existing !== undefined) {
|
|
4771
|
-
throw new
|
|
5085
|
+
throw new VibeError9({
|
|
4772
5086
|
code: "VIBE_CONFIG",
|
|
4773
5087
|
message: `where operator "${operator.name}" is claimed by both "${existing}" and "${extension.name}"`,
|
|
4774
5088
|
meta: { operator: operator.name }
|
|
@@ -4784,7 +5098,7 @@ function buildExtensionOperatorMap(params) {
|
|
|
4784
5098
|
options: extension.options
|
|
4785
5099
|
});
|
|
4786
5100
|
if ("error" in compiled) {
|
|
4787
|
-
throw new
|
|
5101
|
+
throw new VibeError9({
|
|
4788
5102
|
code: "VIBE_VALIDATION",
|
|
4789
5103
|
message: `"${operator.name}" on ${model}.${field}: ${compiled.error}`,
|
|
4790
5104
|
meta: { model, field, operator: operator.name, extension: extension.name }
|
|
@@ -4816,9 +5130,9 @@ function buildConnectHooks(params) {
|
|
|
4816
5130
|
return hooks;
|
|
4817
5131
|
}
|
|
4818
5132
|
// packages/runtime/src/views.ts
|
|
4819
|
-
import { VibeError as
|
|
5133
|
+
import { VibeError as VibeError10 } from "@vibeorm/schema";
|
|
4820
5134
|
function validationError4(params) {
|
|
4821
|
-
return new
|
|
5135
|
+
return new VibeError10({ code: "VIBE_VALIDATION", message: params.message, meta: params.meta });
|
|
4822
5136
|
}
|
|
4823
5137
|
|
|
4824
5138
|
class ViewResult {
|
|
@@ -5170,7 +5484,7 @@ function createView(params) {
|
|
|
5170
5484
|
for (const [clientName, mask] of resolved) {
|
|
5171
5485
|
const delegate = models[clientName];
|
|
5172
5486
|
if (delegate === undefined) {
|
|
5173
|
-
throw new
|
|
5487
|
+
throw new VibeError10({
|
|
5174
5488
|
code: "VIBE_INTERNAL",
|
|
5175
5489
|
message: `view "${name}": no delegate for model "${mask.model.name}"`,
|
|
5176
5490
|
meta: { view: name, model: mask.model.name }
|
|
@@ -5186,11 +5500,11 @@ function attachDefineView(params) {
|
|
|
5186
5500
|
Object.defineProperty(client, "$defineView", { value, enumerable: true });
|
|
5187
5501
|
}
|
|
5188
5502
|
// packages/runtime/src/client.ts
|
|
5189
|
-
import { VibeError as
|
|
5503
|
+
import { VibeError as VibeError11, unsupportedCapability as unsupportedCapability2 } from "@vibeorm/schema";
|
|
5190
5504
|
import { ParamCollector, getSqlDialect } from "@vibeorm/sql";
|
|
5191
5505
|
|
|
5192
5506
|
// packages/runtime/src/render-cache.ts
|
|
5193
|
-
import { isListSetOperation, isSetOperation, isSqlDefaultCell, renderStatement, strictMatchPattern } from "@vibeorm/sql";
|
|
5507
|
+
import { isDbNowMarker, isListSetOperation, isSetOperation, isSqlDefaultCell, renderStatement, strictMatchPattern } from "@vibeorm/sql";
|
|
5194
5508
|
var MAX_ENTRIES = 512;
|
|
5195
5509
|
|
|
5196
5510
|
class StatementRenderCache {
|
|
@@ -5240,6 +5554,13 @@ function addSetEntry(walk, name, value) {
|
|
|
5240
5554
|
walk.values.push(value.value);
|
|
5241
5555
|
return;
|
|
5242
5556
|
}
|
|
5557
|
+
if (isDbNowMarker(value)) {
|
|
5558
|
+
walk.key += value.offsetSeconds === 0 ? "=N0" : "=N1";
|
|
5559
|
+
addIdent(walk, name);
|
|
5560
|
+
if (value.offsetSeconds !== 0)
|
|
5561
|
+
walk.values.push(value.offsetSeconds);
|
|
5562
|
+
return;
|
|
5563
|
+
}
|
|
5243
5564
|
if (isListSetOperation(value)) {
|
|
5244
5565
|
walk.key += `=l${value.__listOp}`;
|
|
5245
5566
|
addIdent(walk, name);
|
|
@@ -5263,6 +5584,13 @@ function addReturning(walk, returning) {
|
|
|
5263
5584
|
function walkExpr(walk, dialect, expr) {
|
|
5264
5585
|
switch (expr.kind) {
|
|
5265
5586
|
case "cmp":
|
|
5587
|
+
if (isDbNowMarker(expr.value)) {
|
|
5588
|
+
walk.key += `cN${expr.op}${expr.value.offsetSeconds === 0 ? "0" : "1"}`;
|
|
5589
|
+
addCol(walk, expr.column);
|
|
5590
|
+
if (expr.value.offsetSeconds !== 0)
|
|
5591
|
+
walk.values.push(expr.value.offsetSeconds);
|
|
5592
|
+
return true;
|
|
5593
|
+
}
|
|
5266
5594
|
walk.key += `c${expr.op}`;
|
|
5267
5595
|
addCol(walk, expr.column);
|
|
5268
5596
|
walk.values.push(expr.value);
|
|
@@ -5478,7 +5806,7 @@ function walkSelect(walk, dialect, statement) {
|
|
|
5478
5806
|
walk.key += "}";
|
|
5479
5807
|
return true;
|
|
5480
5808
|
}
|
|
5481
|
-
function walkInsert(walk, statement) {
|
|
5809
|
+
function walkInsert(walk, dialect, statement) {
|
|
5482
5810
|
walk.key += "I{";
|
|
5483
5811
|
addTable(walk, statement.table);
|
|
5484
5812
|
walk.key += "c(";
|
|
@@ -5494,7 +5822,11 @@ function walkInsert(walk, statement) {
|
|
|
5494
5822
|
const value = row[cell];
|
|
5495
5823
|
if (isSqlDefaultCell(value))
|
|
5496
5824
|
walk.key += `D${rowIndex}:${cell};`;
|
|
5497
|
-
else
|
|
5825
|
+
else if (isDbNowMarker(value)) {
|
|
5826
|
+
walk.key += `T${rowIndex}:${cell}:${value.offsetSeconds === 0 ? "0" : "1"};`;
|
|
5827
|
+
if (value.offsetSeconds !== 0)
|
|
5828
|
+
walk.values.push(value.offsetSeconds);
|
|
5829
|
+
} else
|
|
5498
5830
|
walk.values.push(value);
|
|
5499
5831
|
}
|
|
5500
5832
|
}
|
|
@@ -5502,13 +5834,25 @@ function walkInsert(walk, statement) {
|
|
|
5502
5834
|
if (onConflict !== undefined) {
|
|
5503
5835
|
if ("doNothing" in onConflict) {
|
|
5504
5836
|
walk.key += "N(";
|
|
5837
|
+
if (onConflict.constraint !== undefined) {
|
|
5838
|
+
walk.key += "K";
|
|
5839
|
+
addIdent(walk, onConflict.constraint);
|
|
5840
|
+
}
|
|
5505
5841
|
if (onConflict.columns !== undefined) {
|
|
5506
5842
|
for (const name of onConflict.columns)
|
|
5507
5843
|
addIdent(walk, name);
|
|
5508
5844
|
}
|
|
5845
|
+
if (onConflict.conflictWhere !== undefined) {
|
|
5846
|
+
walk.key += "w";
|
|
5847
|
+
addIdent(walk, onConflict.conflictWhere);
|
|
5848
|
+
}
|
|
5509
5849
|
walk.key += ")";
|
|
5510
5850
|
} else {
|
|
5511
5851
|
walk.key += "U(";
|
|
5852
|
+
if (onConflict.constraint !== undefined) {
|
|
5853
|
+
walk.key += "K";
|
|
5854
|
+
addIdent(walk, onConflict.constraint);
|
|
5855
|
+
}
|
|
5512
5856
|
for (const name of onConflict.columns)
|
|
5513
5857
|
addIdent(walk, name);
|
|
5514
5858
|
walk.key += ":";
|
|
@@ -5556,7 +5900,7 @@ function walkStatement(walk, dialect, statement) {
|
|
|
5556
5900
|
case "select":
|
|
5557
5901
|
return walkSelect(walk, dialect, statement);
|
|
5558
5902
|
case "insert":
|
|
5559
|
-
return walkInsert(walk, statement);
|
|
5903
|
+
return walkInsert(walk, dialect, statement);
|
|
5560
5904
|
case "update":
|
|
5561
5905
|
return walkUpdate(walk, dialect, statement);
|
|
5562
5906
|
case "delete":
|
|
@@ -5606,7 +5950,7 @@ function renderStatementCached(params) {
|
|
|
5606
5950
|
// packages/runtime/src/client.ts
|
|
5607
5951
|
function notFound2(params) {
|
|
5608
5952
|
const { model, method } = params;
|
|
5609
|
-
return new
|
|
5953
|
+
return new VibeError11({
|
|
5610
5954
|
code: "VIBE_NOT_FOUND",
|
|
5611
5955
|
message: `no ${model} record matched the where clause of ${method}()`,
|
|
5612
5956
|
meta: { model, method }
|
|
@@ -5614,7 +5958,7 @@ function notFound2(params) {
|
|
|
5614
5958
|
}
|
|
5615
5959
|
function viewIsReadOnly(params) {
|
|
5616
5960
|
const { model, method } = params;
|
|
5617
|
-
return new
|
|
5961
|
+
return new VibeError11({
|
|
5618
5962
|
code: "VIBE_VALIDATION",
|
|
5619
5963
|
message: `${model} is a view \u2014 ${method}() is not available on views (they are read-only); write to the underlying table instead`,
|
|
5620
5964
|
meta: { model, method }
|
|
@@ -5743,7 +6087,7 @@ class QueryPromise extends Promise {
|
|
|
5743
6087
|
}
|
|
5744
6088
|
claim() {
|
|
5745
6089
|
if (this.started) {
|
|
5746
|
-
throw new
|
|
6090
|
+
throw new VibeError11({
|
|
5747
6091
|
code: "VIBE_VALIDATION",
|
|
5748
6092
|
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)"
|
|
5749
6093
|
});
|
|
@@ -5790,7 +6134,7 @@ async function runAffected(params) {
|
|
|
5790
6134
|
if (plan.batch !== undefined && plan.batch.length > 1) {
|
|
5791
6135
|
const batch = plan.batch;
|
|
5792
6136
|
return await engine.adapter.transaction(async (txAdapter) => {
|
|
5793
|
-
const txEngine = { ...engine, adapter: txAdapter };
|
|
6137
|
+
const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
|
|
5794
6138
|
let total = 0;
|
|
5795
6139
|
for (const statement of batch) {
|
|
5796
6140
|
total += await runAffected({ engine: txEngine, plan: { ...plan, statement, batch: undefined } });
|
|
@@ -5820,6 +6164,13 @@ async function runAffected(params) {
|
|
|
5820
6164
|
}
|
|
5821
6165
|
function plan(params) {
|
|
5822
6166
|
const { engine, model, method, args } = params;
|
|
6167
|
+
if (args !== undefined && args["lock"] !== undefined && engine.inTransaction !== true) {
|
|
6168
|
+
throw new VibeError11({
|
|
6169
|
+
code: "VIBE_VALIDATION",
|
|
6170
|
+
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)`,
|
|
6171
|
+
meta: { model, method }
|
|
6172
|
+
});
|
|
6173
|
+
}
|
|
5823
6174
|
return buildQuery({
|
|
5824
6175
|
meta: engine.meta,
|
|
5825
6176
|
dialect: engine.dialect,
|
|
@@ -5887,7 +6238,7 @@ async function insertedKeyWhere(params) {
|
|
|
5887
6238
|
});
|
|
5888
6239
|
const id = result.rows[0]?.id;
|
|
5889
6240
|
if (id === undefined || id === null) {
|
|
5890
|
-
throw new
|
|
6241
|
+
throw new VibeError11({
|
|
5891
6242
|
code: "VIBE_INTERNAL",
|
|
5892
6243
|
message: `could not read back the generated key for ${source.model.name}`,
|
|
5893
6244
|
meta: { model: source.model.name }
|
|
@@ -5902,8 +6253,11 @@ async function runSingleMutation(params) {
|
|
|
5902
6253
|
if (requested !== undefined && requested.length > 0 && engine.dialect.capabilities.returning) {
|
|
5903
6254
|
const rows = await runRows({ engine, plan: source });
|
|
5904
6255
|
const row = rows[0];
|
|
5905
|
-
if (row === undefined)
|
|
6256
|
+
if (row === undefined) {
|
|
6257
|
+
if (params.allowZeroRows === true)
|
|
6258
|
+
return null;
|
|
5906
6259
|
throw notFound2({ model: source.model.name, method: source.method });
|
|
6260
|
+
}
|
|
5907
6261
|
return row;
|
|
5908
6262
|
}
|
|
5909
6263
|
const rendered = renderStatementCached({ dialect: engine.dialect, statement, cache: engine.renderCache });
|
|
@@ -5928,7 +6282,7 @@ async function runSingleMutation(params) {
|
|
|
5928
6282
|
if (where === null)
|
|
5929
6283
|
throw notFound2({ model: source.model.name, method: source.method });
|
|
5930
6284
|
return await engine.adapter.transaction(async (txAdapter) => {
|
|
5931
|
-
const txEngine = { ...engine, adapter: txAdapter };
|
|
6285
|
+
const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
|
|
5932
6286
|
const before = await refetch({ engine: txEngine, source, where, lock: true });
|
|
5933
6287
|
if (before === null)
|
|
5934
6288
|
throw notFound2({ model: source.model.name, method: source.method });
|
|
@@ -5942,7 +6296,7 @@ async function runSingleMutation(params) {
|
|
|
5942
6296
|
const knownWhere = source.refetchWhere;
|
|
5943
6297
|
if (source.method === "upsert") {
|
|
5944
6298
|
return await engine.adapter.transaction(async (txAdapter) => {
|
|
5945
|
-
const txEngine = { ...engine, adapter: txAdapter };
|
|
6299
|
+
const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
|
|
5946
6300
|
await executeWrite(txEngine);
|
|
5947
6301
|
const byWhere = await refetch({ engine: txEngine, source, where: knownWhere });
|
|
5948
6302
|
if (byWhere !== null)
|
|
@@ -5966,9 +6320,9 @@ async function runSingleMutation(params) {
|
|
|
5966
6320
|
}
|
|
5967
6321
|
if (source.method === "update") {
|
|
5968
6322
|
return await engine.adapter.transaction(async (txAdapter) => {
|
|
5969
|
-
const txEngine = { ...engine, adapter: txAdapter };
|
|
5970
|
-
const
|
|
5971
|
-
if (
|
|
6323
|
+
const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
|
|
6324
|
+
const affected2 = await executeWrite(txEngine);
|
|
6325
|
+
if (affected2 === 0)
|
|
5972
6326
|
throw notFound2({ model: source.model.name, method: source.method });
|
|
5973
6327
|
const after2 = await refetch({ engine: txEngine, source, where: knownWhere });
|
|
5974
6328
|
if (after2 === null)
|
|
@@ -5976,15 +6330,19 @@ async function runSingleMutation(params) {
|
|
|
5976
6330
|
return after2;
|
|
5977
6331
|
});
|
|
5978
6332
|
}
|
|
5979
|
-
await executeWrite(engine);
|
|
6333
|
+
const affected = await executeWrite(engine);
|
|
6334
|
+
if (affected === 0 && params.allowZeroRows === true)
|
|
6335
|
+
return null;
|
|
5980
6336
|
const after = await refetch({ engine, source, where: knownWhere });
|
|
5981
6337
|
if (after === null)
|
|
5982
6338
|
throw notFound2({ model: source.model.name, method: source.method });
|
|
5983
6339
|
return after;
|
|
5984
6340
|
}
|
|
5985
6341
|
return await engine.adapter.transaction(async (txAdapter) => {
|
|
5986
|
-
const txEngine = { ...engine, adapter: txAdapter };
|
|
5987
|
-
await executeWrite(txEngine);
|
|
6342
|
+
const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
|
|
6343
|
+
const affected = await executeWrite(txEngine);
|
|
6344
|
+
if (affected === 0 && params.allowZeroRows === true)
|
|
6345
|
+
return null;
|
|
5988
6346
|
const where = await insertedKeyWhere({ engine: txEngine, source });
|
|
5989
6347
|
const after = await refetch({ engine: txEngine, source, where });
|
|
5990
6348
|
if (after === null)
|
|
@@ -5992,6 +6350,11 @@ async function runSingleMutation(params) {
|
|
|
5992
6350
|
return after;
|
|
5993
6351
|
});
|
|
5994
6352
|
}
|
|
6353
|
+
function requireRow(params) {
|
|
6354
|
+
if (params.row === null)
|
|
6355
|
+
throw notFound2({ model: params.model, method: params.method });
|
|
6356
|
+
return params.row;
|
|
6357
|
+
}
|
|
5995
6358
|
async function runUpsertFallback(params) {
|
|
5996
6359
|
const { engine, model, args } = params;
|
|
5997
6360
|
const passthrough = {};
|
|
@@ -6019,10 +6382,14 @@ async function runUpsertFallback(params) {
|
|
|
6019
6382
|
args: { ...passthrough, where: args.where, data: args.update }
|
|
6020
6383
|
});
|
|
6021
6384
|
return await engine.adapter.transaction(async (txAdapter) => {
|
|
6022
|
-
const txEngine = { ...engine, adapter: txAdapter };
|
|
6385
|
+
const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
|
|
6023
6386
|
const existing = (await runRows({ engine: txEngine, plan: probePlan }))[0];
|
|
6024
6387
|
const source = existing === undefined ? createSource : updateSource;
|
|
6025
|
-
return
|
|
6388
|
+
return requireRow({
|
|
6389
|
+
row: await runSingleMutation({ engine: txEngine, source, readBeforeWrite: false }),
|
|
6390
|
+
model: model.name,
|
|
6391
|
+
method: "upsert"
|
|
6392
|
+
});
|
|
6026
6393
|
});
|
|
6027
6394
|
}
|
|
6028
6395
|
function relationExecutor(params) {
|
|
@@ -6079,10 +6446,14 @@ async function finalizeRow(params) {
|
|
|
6079
6446
|
function makeRunner(params) {
|
|
6080
6447
|
const { engine } = params;
|
|
6081
6448
|
return {
|
|
6082
|
-
single: async ({ model, method, args }) =>
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6449
|
+
single: async ({ model, method, args }) => requireRow({
|
|
6450
|
+
row: await runSingleMutation({
|
|
6451
|
+
engine,
|
|
6452
|
+
source: plan({ engine, model, method, args }),
|
|
6453
|
+
readBeforeWrite: method === "delete"
|
|
6454
|
+
}),
|
|
6455
|
+
model,
|
|
6456
|
+
method
|
|
6086
6457
|
}),
|
|
6087
6458
|
rows: async ({ model, method, args }) => await runRows({ engine, plan: plan({ engine, model, method, args }) }),
|
|
6088
6459
|
affected: async ({ model, method, args }) => await runAffected({ engine, plan: plan({ engine, model, method, args }) }),
|
|
@@ -6108,7 +6479,7 @@ function makeWriteContext(params) {
|
|
|
6108
6479
|
meta: engine.meta,
|
|
6109
6480
|
dialect: engine.dialect,
|
|
6110
6481
|
run: makeRunner({ engine }),
|
|
6111
|
-
transactional: (fn) => engine.adapter.transaction((txAdapter) => fn(makeRunner({ engine: { ...engine, adapter: txAdapter } })))
|
|
6482
|
+
transactional: (fn) => engine.adapter.transaction((txAdapter) => fn(makeRunner({ engine: { ...engine, adapter: txAdapter, inTransaction: true } })))
|
|
6112
6483
|
};
|
|
6113
6484
|
}
|
|
6114
6485
|
function readOnlyDelegate(params) {
|
|
@@ -6166,7 +6537,15 @@ function createDelegate(params) {
|
|
|
6166
6537
|
};
|
|
6167
6538
|
const create = async (target, args) => {
|
|
6168
6539
|
const modelMeta = getModelMeta({ meta: target.meta, model });
|
|
6540
|
+
const skipping = args.onConflict !== undefined;
|
|
6169
6541
|
if (hasNestedWrites({ model: modelMeta, data: args.data })) {
|
|
6542
|
+
if (skipping) {
|
|
6543
|
+
throw new VibeError11({
|
|
6544
|
+
code: "VIBE_VALIDATION",
|
|
6545
|
+
message: `onConflict cannot combine with nested writes on ${modelMeta.name}.create() \u2014 insert the parent row alone, then connect relations`,
|
|
6546
|
+
meta: { model: modelMeta.name }
|
|
6547
|
+
});
|
|
6548
|
+
}
|
|
6170
6549
|
const shape = resolveResultShape({ meta: target.meta, model: modelMeta, method: "create", args });
|
|
6171
6550
|
const row2 = await createWithNested({
|
|
6172
6551
|
ctx: makeWriteContext({ engine: target }),
|
|
@@ -6181,7 +6560,14 @@ function createDelegate(params) {
|
|
|
6181
6560
|
});
|
|
6182
6561
|
}
|
|
6183
6562
|
const built = plan({ engine: target, model, method: "create", args });
|
|
6184
|
-
const row = await runSingleMutation({
|
|
6563
|
+
const row = await runSingleMutation({
|
|
6564
|
+
engine: target,
|
|
6565
|
+
source: built,
|
|
6566
|
+
readBeforeWrite: false,
|
|
6567
|
+
...skipping ? { allowZeroRows: true } : {}
|
|
6568
|
+
});
|
|
6569
|
+
if (row === null)
|
|
6570
|
+
return null;
|
|
6185
6571
|
return await finalizeRow({ engine: target, model: built.model, shape: shapeOf(built), row });
|
|
6186
6572
|
};
|
|
6187
6573
|
const update = async (target, args) => {
|
|
@@ -6202,7 +6588,11 @@ function createDelegate(params) {
|
|
|
6202
6588
|
});
|
|
6203
6589
|
}
|
|
6204
6590
|
const built = plan({ engine: target, model, method: "update", args });
|
|
6205
|
-
const row =
|
|
6591
|
+
const row = requireRow({
|
|
6592
|
+
row: await runSingleMutation({ engine: target, source: built, readBeforeWrite: false }),
|
|
6593
|
+
model: built.model.name,
|
|
6594
|
+
method: "update"
|
|
6595
|
+
});
|
|
6206
6596
|
return await finalizeRow({ engine: target, model: built.model, shape: shapeOf(built), row });
|
|
6207
6597
|
};
|
|
6208
6598
|
const createMany = async (target, args) => {
|
|
@@ -6234,13 +6624,20 @@ function createDelegate(params) {
|
|
|
6234
6624
|
const upsert = async (target, args) => {
|
|
6235
6625
|
const modelMeta = getModelMeta({ meta: target.meta, model });
|
|
6236
6626
|
if (hasNestedWrites({ model: modelMeta, data: args.create }) || hasNestedWrites({ model: modelMeta, data: args.update })) {
|
|
6237
|
-
throw new
|
|
6627
|
+
throw new VibeError11({
|
|
6238
6628
|
code: "VIBE_VALIDATION",
|
|
6239
6629
|
message: `nested writes are not supported inside upsert arms on ${modelMeta.name} \u2014 use create()/update() with nested data`,
|
|
6240
6630
|
meta: { model: modelMeta.name }
|
|
6241
6631
|
});
|
|
6242
6632
|
}
|
|
6243
6633
|
if (!upsertUsesNativeStatement({ model: modelMeta, args })) {
|
|
6634
|
+
if (args.onConflict !== undefined) {
|
|
6635
|
+
throw new VibeError11({
|
|
6636
|
+
code: "VIBE_VALIDATION",
|
|
6637
|
+
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`,
|
|
6638
|
+
meta: { model: modelMeta.name }
|
|
6639
|
+
});
|
|
6640
|
+
}
|
|
6244
6641
|
const shape = resolveResultShape({
|
|
6245
6642
|
meta: target.meta,
|
|
6246
6643
|
model: modelMeta,
|
|
@@ -6256,12 +6653,20 @@ function createDelegate(params) {
|
|
|
6256
6653
|
});
|
|
6257
6654
|
}
|
|
6258
6655
|
const built = plan({ engine: target, model, method: "upsert", args });
|
|
6259
|
-
const row =
|
|
6656
|
+
const row = requireRow({
|
|
6657
|
+
row: await runSingleMutation({ engine: target, source: built, readBeforeWrite: false }),
|
|
6658
|
+
model: built.model.name,
|
|
6659
|
+
method: "upsert"
|
|
6660
|
+
});
|
|
6260
6661
|
return await finalizeRow({ engine: target, model: built.model, shape: shapeOf(built), row });
|
|
6261
6662
|
};
|
|
6262
6663
|
const deleteOne = async (target, args) => {
|
|
6263
6664
|
const built = plan({ engine: target, model, method: "delete", args });
|
|
6264
|
-
const row =
|
|
6665
|
+
const row = requireRow({
|
|
6666
|
+
row: await runSingleMutation({ engine: target, source: built, readBeforeWrite: true }),
|
|
6667
|
+
model: built.model.name,
|
|
6668
|
+
method: "delete"
|
|
6669
|
+
});
|
|
6265
6670
|
return await finalizeRow({ engine: target, model: built.model, shape: shapeOf(built), row });
|
|
6266
6671
|
};
|
|
6267
6672
|
const deleteMany = async (target, args) => {
|
|
@@ -6389,7 +6794,7 @@ function buildClient(params) {
|
|
|
6389
6794
|
let claimError = null;
|
|
6390
6795
|
for (const operation of operations) {
|
|
6391
6796
|
if (!(operation instanceof QueryPromise)) {
|
|
6392
|
-
claimError = new
|
|
6797
|
+
claimError = new VibeError11({
|
|
6393
6798
|
code: "VIBE_VALIDATION",
|
|
6394
6799
|
message: "$transaction([...]) takes query promises from model delegate calls \u2014 a foreign promise is already running and cannot join the transaction"
|
|
6395
6800
|
});
|
|
@@ -6408,7 +6813,7 @@ function buildClient(params) {
|
|
|
6408
6813
|
return Promise.reject(claimError);
|
|
6409
6814
|
}
|
|
6410
6815
|
return engine.adapter.transaction(async (txAdapter) => {
|
|
6411
|
-
const txEngine = { ...engine, adapter: txAdapter };
|
|
6816
|
+
const txEngine = { ...engine, adapter: txAdapter, inTransaction: true };
|
|
6412
6817
|
const results = [];
|
|
6413
6818
|
let index = 0;
|
|
6414
6819
|
try {
|
|
@@ -6428,7 +6833,7 @@ function buildClient(params) {
|
|
|
6428
6833
|
$models: models,
|
|
6429
6834
|
$on: (event, listener) => {
|
|
6430
6835
|
if (event !== "query") {
|
|
6431
|
-
throw new
|
|
6836
|
+
throw new VibeError11({
|
|
6432
6837
|
code: "VIBE_VALIDATION",
|
|
6433
6838
|
message: `unknown client event "${String(event)}" \u2014 supported: "query"`,
|
|
6434
6839
|
meta: { event }
|
|
@@ -6439,7 +6844,7 @@ function buildClient(params) {
|
|
|
6439
6844
|
engine.listeners.delete(listener);
|
|
6440
6845
|
};
|
|
6441
6846
|
},
|
|
6442
|
-
$transaction: (fnOrOperations, options) => Array.isArray(fnOrOperations) ? runOperations(fnOrOperations, options) : engine.adapter.transaction((txAdapter) => fnOrOperations(buildClient({ engine: { ...engine, adapter: txAdapter } })), options),
|
|
6847
|
+
$transaction: (fnOrOperations, options) => Array.isArray(fnOrOperations) ? runOperations(fnOrOperations, options) : engine.adapter.transaction((txAdapter) => fnOrOperations(buildClient({ engine: { ...engine, adapter: txAdapter, inTransaction: true } })), options),
|
|
6443
6848
|
$queryRaw: async (strings, ...values) => (await runRaw("$queryRaw", buildRawStatement({ dialect: engine.dialect, strings, values }))).rows,
|
|
6444
6849
|
$executeRaw: async (strings, ...values) => (await runRaw("$executeRaw", buildRawStatement({ dialect: engine.dialect, strings, values }))).affectedRows,
|
|
6445
6850
|
$queryRawUnsafe: async (text, ...values) => (await runRaw("$queryRawUnsafe", { text, values })).rows,
|
|
@@ -6491,7 +6896,7 @@ function makeMountContext(params) {
|
|
|
6491
6896
|
compileWhere: ({ where, model: modelName, qualify }) => {
|
|
6492
6897
|
const target = modelName === undefined ? model : getModelMeta({ meta: engine.meta, model: modelName });
|
|
6493
6898
|
if (target === null) {
|
|
6494
|
-
throw new
|
|
6899
|
+
throw new VibeError11({
|
|
6495
6900
|
code: "VIBE_INTERNAL",
|
|
6496
6901
|
message: `extension "${extension}" compiled a where with no model in scope`,
|
|
6497
6902
|
meta: { extension }
|
|
@@ -6517,7 +6922,7 @@ function attachExtensionMounts(params) {
|
|
|
6517
6922
|
const model = getModelMeta({ meta: engine.meta, model: mount.model ?? "" });
|
|
6518
6923
|
const delegate = models[model.name];
|
|
6519
6924
|
if (delegate === undefined || mount.property in delegate) {
|
|
6520
|
-
throw new
|
|
6925
|
+
throw new VibeError11({
|
|
6521
6926
|
code: "VIBE_CONFIG",
|
|
6522
6927
|
message: `extension "${mount.extension}" cannot mount "${mount.property}" on ${model.name} \u2014 the name is taken`,
|
|
6523
6928
|
meta: { extension: mount.extension, model: model.name, property: mount.property }
|
|
@@ -6528,7 +6933,7 @@ function attachExtensionMounts(params) {
|
|
|
6528
6933
|
continue;
|
|
6529
6934
|
}
|
|
6530
6935
|
if (!mount.property.startsWith("$") || mount.property in client) {
|
|
6531
|
-
throw new
|
|
6936
|
+
throw new VibeError11({
|
|
6532
6937
|
code: "VIBE_CONFIG",
|
|
6533
6938
|
message: `extension "${mount.extension}" cannot mount client property "${mount.property}" \u2014 root mounts are $-prefixed and must not collide`,
|
|
6534
6939
|
meta: { extension: mount.extension, property: mount.property }
|
|
@@ -6548,7 +6953,7 @@ function createClient(params) {
|
|
|
6548
6953
|
assertComputedManifest({ manifest: computed.manifest, specs: computed.specs });
|
|
6549
6954
|
}
|
|
6550
6955
|
if (computed === undefined && options !== undefined && options["computed"] !== undefined) {
|
|
6551
|
-
throw new
|
|
6956
|
+
throw new VibeError11({
|
|
6552
6957
|
code: "VIBE_CONFIG",
|
|
6553
6958
|
message: "this client was generated without computed fields \u2014 declare them in vibeorm.config.ts and re-run vibeorm generate",
|
|
6554
6959
|
meta: {}
|
|
@@ -6583,6 +6988,7 @@ export {
|
|
|
6583
6988
|
resolveRelationSelection,
|
|
6584
6989
|
resolveRelationLink,
|
|
6585
6990
|
lateralColumnAlias,
|
|
6991
|
+
isDbNow,
|
|
6586
6992
|
hasNestedWrites,
|
|
6587
6993
|
getModelMeta,
|
|
6588
6994
|
getFieldMeta,
|
|
@@ -6598,6 +7004,7 @@ export {
|
|
|
6598
7004
|
decodeRow,
|
|
6599
7005
|
decodeIsNoop2 as decodeIsNoop,
|
|
6600
7006
|
decodeAggregateValue,
|
|
7007
|
+
dbNow,
|
|
6601
7008
|
createWithNested,
|
|
6602
7009
|
createView,
|
|
6603
7010
|
createClient,
|
|
@@ -6622,4 +7029,4 @@ export {
|
|
|
6622
7029
|
COUNT_ALIAS
|
|
6623
7030
|
};
|
|
6624
7031
|
|
|
6625
|
-
//# debugId=
|
|
7032
|
+
//# debugId=0C003BB2F334C66D64756E2164756E21
|