prisma-guard 1.20.0 → 1.21.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 +218 -20
- package/dist/generator/index.js +1 -1
- package/dist/generator/index.js.map +1 -1
- package/dist/runtime/index.cjs +415 -97
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +3 -3
- package/dist/runtime/index.d.ts +3 -3
- package/dist/runtime/index.js +415 -97
- package/dist/runtime/index.js.map +1 -1
- package/package.json +1 -1
package/dist/runtime/index.cjs
CHANGED
|
@@ -57,11 +57,92 @@ var CallerError = class extends Error {
|
|
|
57
57
|
this.name = "CallerError";
|
|
58
58
|
}
|
|
59
59
|
};
|
|
60
|
+
function formatIssue(issue) {
|
|
61
|
+
const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
62
|
+
const code = issue.code;
|
|
63
|
+
if (code === "invalid_union") {
|
|
64
|
+
const unionErrors = issue.unionErrors;
|
|
65
|
+
if (unionErrors && unionErrors.length > 0) {
|
|
66
|
+
const branches = unionErrors.map((ue, i) => {
|
|
67
|
+
const nested = ue.issues.map(formatIssue).join(", ");
|
|
68
|
+
return `branch ${i + 1}: [${nested}]`;
|
|
69
|
+
}).join(" | ");
|
|
70
|
+
return `${path}No matching variant (${branches})`;
|
|
71
|
+
}
|
|
72
|
+
return `${path}No matching variant`;
|
|
73
|
+
}
|
|
74
|
+
if (issue.code === "unrecognized_keys") {
|
|
75
|
+
const keys = issue.keys;
|
|
76
|
+
if (keys && keys.length > 0) {
|
|
77
|
+
return `${path}Unrecognized key(s): ${keys.join(", ")}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (issue.code === "invalid_type") {
|
|
81
|
+
const expected = issue.expected;
|
|
82
|
+
const received = issue.received;
|
|
83
|
+
if (expected && received) {
|
|
84
|
+
return `${path}Expected ${expected}, received ${received}`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (code === "invalid_enum_value") {
|
|
88
|
+
const options = issue.options;
|
|
89
|
+
if (options) {
|
|
90
|
+
return `${path}Invalid value. Expected one of: ${options.join(", ")}`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (code === "too_small") {
|
|
94
|
+
const minimum = issue.minimum;
|
|
95
|
+
const type = issue.type;
|
|
96
|
+
if (type === "string" && minimum !== void 0) {
|
|
97
|
+
return `${path}String must contain at least ${minimum} character(s)`;
|
|
98
|
+
}
|
|
99
|
+
if (type === "array" && minimum !== void 0) {
|
|
100
|
+
return `${path}Array must contain at least ${minimum} element(s)`;
|
|
101
|
+
}
|
|
102
|
+
if (type === "number" && minimum !== void 0) {
|
|
103
|
+
return `${path}Number must be >= ${minimum}`;
|
|
104
|
+
}
|
|
105
|
+
return `${path}${issue.message}`;
|
|
106
|
+
}
|
|
107
|
+
if (code === "too_big") {
|
|
108
|
+
const maximum = issue.maximum;
|
|
109
|
+
const type = issue.type;
|
|
110
|
+
if (type === "string" && maximum !== void 0) {
|
|
111
|
+
return `${path}String must contain at most ${maximum} character(s)`;
|
|
112
|
+
}
|
|
113
|
+
if (type === "array" && maximum !== void 0) {
|
|
114
|
+
return `${path}Array must contain at most ${maximum} element(s)`;
|
|
115
|
+
}
|
|
116
|
+
if (type === "number" && maximum !== void 0) {
|
|
117
|
+
return `${path}Number must be <= ${maximum}`;
|
|
118
|
+
}
|
|
119
|
+
return `${path}${issue.message}`;
|
|
120
|
+
}
|
|
121
|
+
if (code === "invalid_format") {
|
|
122
|
+
const format = issue.format;
|
|
123
|
+
if (format) {
|
|
124
|
+
return `${path}Invalid ${format} format`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (code === "invalid_value") {
|
|
128
|
+
const values = issue.values;
|
|
129
|
+
if (values) {
|
|
130
|
+
return `${path}Invalid value. Expected one of: ${values.join(", ")}`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return `${path}${issue.message}`;
|
|
134
|
+
}
|
|
60
135
|
function formatZodError(err) {
|
|
61
|
-
return err.issues.map((
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
136
|
+
return err.issues.map(formatIssue).join("; ");
|
|
137
|
+
}
|
|
138
|
+
function wrapParseError(err, context) {
|
|
139
|
+
if (err instanceof ShapeError) {
|
|
140
|
+
throw new ShapeError(`${context}: ${err.message}`, { cause: err });
|
|
141
|
+
}
|
|
142
|
+
if (err && typeof err === "object" && "issues" in err) {
|
|
143
|
+
throw new ShapeError(`${context}: ${formatZodError(err)}`, { cause: err });
|
|
144
|
+
}
|
|
145
|
+
throw err;
|
|
65
146
|
}
|
|
66
147
|
|
|
67
148
|
// src/shared/scalar-base.ts
|
|
@@ -207,15 +288,19 @@ function coerceToArray(value) {
|
|
|
207
288
|
return value;
|
|
208
289
|
const keys = Object.keys(value);
|
|
209
290
|
if (keys.length === 0)
|
|
210
|
-
return
|
|
291
|
+
return value;
|
|
211
292
|
const allNumeric = keys.every((k) => /^\d+$/.test(k));
|
|
212
293
|
if (!allNumeric)
|
|
213
294
|
return value;
|
|
214
|
-
const
|
|
295
|
+
const indices = keys.map(Number).sort((a, b) => a - b);
|
|
296
|
+
for (let i = 0; i < indices.length; i++) {
|
|
297
|
+
if (indices[i] !== i)
|
|
298
|
+
return value;
|
|
299
|
+
}
|
|
215
300
|
const obj = value;
|
|
216
|
-
const result =
|
|
217
|
-
for (
|
|
218
|
-
result
|
|
301
|
+
const result = new Array(indices.length);
|
|
302
|
+
for (let i = 0; i < indices.length; i++) {
|
|
303
|
+
result[i] = obj[String(i)];
|
|
219
304
|
}
|
|
220
305
|
return result;
|
|
221
306
|
}
|
|
@@ -233,7 +318,8 @@ var SCALAR_OPERATORS = {
|
|
|
233
318
|
"gt",
|
|
234
319
|
"gte",
|
|
235
320
|
"lt",
|
|
236
|
-
"lte"
|
|
321
|
+
"lte",
|
|
322
|
+
"search"
|
|
237
323
|
]),
|
|
238
324
|
Int: /* @__PURE__ */ new Set(["equals", "not", "gt", "gte", "lt", "lte", "in", "notIn"]),
|
|
239
325
|
Float: /* @__PURE__ */ new Set(["equals", "not", "gt", "gte", "lt", "lte", "in", "notIn"]),
|
|
@@ -335,7 +421,7 @@ function createScalarListOperatorSchema(fieldMeta, operator, enumMap, scalarBase
|
|
|
335
421
|
}
|
|
336
422
|
function createJsonOperatorSchema(fieldMeta, operator) {
|
|
337
423
|
const jsonValue = import_zod2.z.unknown();
|
|
338
|
-
if (operator === "equals"
|
|
424
|
+
if (operator === "equals") {
|
|
339
425
|
return !fieldMeta.isRequired ? import_zod2.z.union([jsonValue, import_zod2.z.null()]) : jsonValue;
|
|
340
426
|
}
|
|
341
427
|
if (operator === "path") {
|
|
@@ -351,6 +437,29 @@ function createJsonOperatorSchema(fieldMeta, operator) {
|
|
|
351
437
|
`Operator "${operator}" not supported for Json fields`
|
|
352
438
|
);
|
|
353
439
|
}
|
|
440
|
+
function buildNotFilterSchema(fieldMeta, scalarSchema, enumMap, scalarBase) {
|
|
441
|
+
const allOps = getSupportedOperators(fieldMeta);
|
|
442
|
+
const nestedOps = allOps.filter((o) => o !== "not");
|
|
443
|
+
if (nestedOps.length === 0)
|
|
444
|
+
return scalarSchema;
|
|
445
|
+
const nestedSchemas = {};
|
|
446
|
+
for (const op of nestedOps) {
|
|
447
|
+
nestedSchemas[op] = createOperatorSchema(
|
|
448
|
+
fieldMeta,
|
|
449
|
+
op,
|
|
450
|
+
enumMap,
|
|
451
|
+
scalarBase
|
|
452
|
+
).optional();
|
|
453
|
+
}
|
|
454
|
+
const nestedKeys = Object.keys(nestedSchemas);
|
|
455
|
+
const nestedObj = import_zod2.z.object(nestedSchemas).strict().refine(
|
|
456
|
+
(v) => nestedKeys.some(
|
|
457
|
+
(k) => v[k] !== void 0
|
|
458
|
+
),
|
|
459
|
+
{ message: "not filter must specify at least one operator" }
|
|
460
|
+
);
|
|
461
|
+
return import_zod2.z.union([scalarSchema, nestedObj]);
|
|
462
|
+
}
|
|
354
463
|
function createOperatorSchema(fieldMeta, operator, enumMap, scalarBase) {
|
|
355
464
|
if (fieldMeta.isList) {
|
|
356
465
|
return createScalarListOperatorSchema(
|
|
@@ -371,9 +480,13 @@ function createOperatorSchema(fieldMeta, operator, enumMap, scalarBase) {
|
|
|
371
480
|
);
|
|
372
481
|
}
|
|
373
482
|
const enumSchema = import_zod2.z.enum(values);
|
|
374
|
-
if (operator === "equals"
|
|
483
|
+
if (operator === "equals") {
|
|
375
484
|
return !fieldMeta.isRequired ? import_zod2.z.union([enumSchema, import_zod2.z.null()]) : enumSchema;
|
|
376
485
|
}
|
|
486
|
+
if (operator === "not") {
|
|
487
|
+
const scalarSchema = !fieldMeta.isRequired ? import_zod2.z.union([enumSchema, import_zod2.z.null()]) : enumSchema;
|
|
488
|
+
return buildNotFilterSchema(fieldMeta, scalarSchema, enumMap, scalarBase);
|
|
489
|
+
}
|
|
377
490
|
const itemSchema = !fieldMeta.isRequired ? import_zod2.z.union([enumSchema, import_zod2.z.null()]) : enumSchema;
|
|
378
491
|
return import_zod2.z.preprocess(coerceToArray, import_zod2.z.array(itemSchema));
|
|
379
492
|
}
|
|
@@ -384,6 +497,11 @@ function createOperatorSchema(fieldMeta, operator, enumMap, scalarBase) {
|
|
|
384
497
|
`Operator "${operator}" not supported for type "Json"`
|
|
385
498
|
);
|
|
386
499
|
}
|
|
500
|
+
if (operator === "not") {
|
|
501
|
+
const jsonValue = import_zod2.z.unknown();
|
|
502
|
+
const scalarSchema = !fieldMeta.isRequired ? import_zod2.z.union([jsonValue, import_zod2.z.null()]) : jsonValue;
|
|
503
|
+
return buildNotFilterSchema(fieldMeta, scalarSchema, enumMap, scalarBase);
|
|
504
|
+
}
|
|
387
505
|
return createJsonOperatorSchema(fieldMeta, operator);
|
|
388
506
|
}
|
|
389
507
|
const supportedOps = SCALAR_OPERATORS[fieldMeta.type];
|
|
@@ -406,9 +524,13 @@ function createOperatorSchema(fieldMeta, operator, enumMap, scalarBase) {
|
|
|
406
524
|
}
|
|
407
525
|
const scalar = factory();
|
|
408
526
|
const coerced = wrapWithInputCoercion(fieldMeta.type, false, scalar);
|
|
409
|
-
if (operator === "equals"
|
|
527
|
+
if (operator === "equals") {
|
|
410
528
|
return !fieldMeta.isRequired ? import_zod2.z.union([coerced, import_zod2.z.null()]) : coerced;
|
|
411
529
|
}
|
|
530
|
+
if (operator === "not") {
|
|
531
|
+
const scalarSchema = !fieldMeta.isRequired ? import_zod2.z.union([coerced, import_zod2.z.null()]) : coerced;
|
|
532
|
+
return buildNotFilterSchema(fieldMeta, scalarSchema, enumMap, scalarBase);
|
|
533
|
+
}
|
|
412
534
|
if (operator === "in" || operator === "notIn") {
|
|
413
535
|
const itemSchema = !fieldMeta.isRequired ? import_zod2.z.union([coerced, import_zod2.z.null()]) : coerced;
|
|
414
536
|
return import_zod2.z.preprocess(coerceToArray, import_zod2.z.array(itemSchema));
|
|
@@ -817,6 +939,8 @@ function deepClone(value) {
|
|
|
817
939
|
return new Date(value.getTime());
|
|
818
940
|
if (value instanceof Uint8Array)
|
|
819
941
|
return value.slice();
|
|
942
|
+
if (value instanceof RegExp)
|
|
943
|
+
return new RegExp(value.source, value.flags);
|
|
820
944
|
if (Array.isArray(value))
|
|
821
945
|
return value.map(deepClone);
|
|
822
946
|
const proto = Object.getPrototypeOf(value);
|
|
@@ -834,7 +958,10 @@ function deepClone(value) {
|
|
|
834
958
|
}
|
|
835
959
|
|
|
836
960
|
// src/runtime/query-builder-forced.ts
|
|
837
|
-
var EMPTY_WHERE_FORCED = {
|
|
961
|
+
var EMPTY_WHERE_FORCED = {
|
|
962
|
+
conditions: {},
|
|
963
|
+
relations: {}
|
|
964
|
+
};
|
|
838
965
|
function hasWhereForced(f) {
|
|
839
966
|
return Object.keys(f.conditions).length > 0 || Object.keys(f.relations).length > 0;
|
|
840
967
|
}
|
|
@@ -894,7 +1021,7 @@ function mergeUniqueWhereForced(where, forced) {
|
|
|
894
1021
|
}
|
|
895
1022
|
return result;
|
|
896
1023
|
}
|
|
897
|
-
function applyBuiltShape(built, body, isUniqueMethod) {
|
|
1024
|
+
function applyBuiltShape(built, body, isUniqueMethod, modelName) {
|
|
898
1025
|
let parseable = body;
|
|
899
1026
|
const hasWhereInSchema = "where" in built.zodSchema.shape;
|
|
900
1027
|
if (isPlainObject(body)) {
|
|
@@ -920,7 +1047,20 @@ function applyBuiltShape(built, body, isUniqueMethod) {
|
|
|
920
1047
|
}
|
|
921
1048
|
}
|
|
922
1049
|
}
|
|
923
|
-
|
|
1050
|
+
let validated;
|
|
1051
|
+
try {
|
|
1052
|
+
validated = built.zodSchema.parse(parseable);
|
|
1053
|
+
} catch (err) {
|
|
1054
|
+
if (err instanceof ShapeError)
|
|
1055
|
+
throw err;
|
|
1056
|
+
if (err && typeof err === "object" && "issues" in err) {
|
|
1057
|
+
const context = modelName ? `Invalid query on model "${modelName}"` : "Invalid query";
|
|
1058
|
+
throw new ShapeError(`${context}: ${formatZodError(err)}`, {
|
|
1059
|
+
cause: err
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
throw err;
|
|
1063
|
+
}
|
|
924
1064
|
if (hasWhereForced(built.forcedWhere)) {
|
|
925
1065
|
validated.where = isUniqueMethod ? mergeUniqueWhereForced(
|
|
926
1066
|
validated.where,
|
|
@@ -1295,7 +1435,8 @@ function createWhereBuilder(typeMap, enumMap, scalarBase) {
|
|
|
1295
1435
|
model,
|
|
1296
1436
|
fieldSchemas,
|
|
1297
1437
|
relationForced,
|
|
1298
|
-
currentDepth
|
|
1438
|
+
currentDepth,
|
|
1439
|
+
scalarConditions
|
|
1299
1440
|
);
|
|
1300
1441
|
continue;
|
|
1301
1442
|
}
|
|
@@ -1377,7 +1518,7 @@ function createWhereBuilder(typeMap, enumMap, scalarBase) {
|
|
|
1377
1518
|
}
|
|
1378
1519
|
}
|
|
1379
1520
|
}
|
|
1380
|
-
function processRelationFilter(key, value, fieldMeta, model, fieldSchemas, parentRelations, depth) {
|
|
1521
|
+
function processRelationFilter(key, value, fieldMeta, model, fieldSchemas, parentRelations, depth, parentConditions) {
|
|
1381
1522
|
if (!isPlainObject(value)) {
|
|
1382
1523
|
throw new ShapeError(
|
|
1383
1524
|
`Relation filter for "${key}" must be an object with operators (some, every, none, is, isNot)`
|
|
@@ -1392,6 +1533,7 @@ function createWhereBuilder(typeMap, enumMap, scalarBase) {
|
|
|
1392
1533
|
const opSchemas = {};
|
|
1393
1534
|
const opForced = {};
|
|
1394
1535
|
let hasClientOps = false;
|
|
1536
|
+
let hasForcedNull = false;
|
|
1395
1537
|
for (const [op, opValue] of Object.entries(value)) {
|
|
1396
1538
|
if (!allowedOps.has(op)) {
|
|
1397
1539
|
const allowed = [...allowedOps].join(", ");
|
|
@@ -1399,9 +1541,24 @@ function createWhereBuilder(typeMap, enumMap, scalarBase) {
|
|
|
1399
1541
|
`Operator "${op}" not supported for ${fieldMeta.isList ? "to-many" : "to-one"} relation "${key}". Allowed: ${allowed}`
|
|
1400
1542
|
);
|
|
1401
1543
|
}
|
|
1544
|
+
if (opValue === null) {
|
|
1545
|
+
if (!TO_ONE_RELATION_OPS.has(op)) {
|
|
1546
|
+
throw new ShapeError(
|
|
1547
|
+
`Null value for operator "${op}" is only valid on to-one relation operators (is, isNot), not on "${key}"`
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1550
|
+
const existing = parentConditions[key];
|
|
1551
|
+
if (existing && isPlainObject(existing)) {
|
|
1552
|
+
existing[op] = null;
|
|
1553
|
+
} else {
|
|
1554
|
+
parentConditions[key] = { [op]: null };
|
|
1555
|
+
}
|
|
1556
|
+
hasForcedNull = true;
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1402
1559
|
if (!isPlainObject(opValue)) {
|
|
1403
1560
|
throw new ShapeError(
|
|
1404
|
-
`Relation filter operator "${op}" on "${key}" must be an object defining nested where fields`
|
|
1561
|
+
`Relation filter operator "${op}" on "${key}" must be an object defining nested where fields, or null for to-one existence checks`
|
|
1405
1562
|
);
|
|
1406
1563
|
}
|
|
1407
1564
|
const nested = buildWhereSchema(fieldMeta.type, opValue, depth + 1);
|
|
@@ -1427,7 +1584,7 @@ function createWhereBuilder(typeMap, enumMap, scalarBase) {
|
|
|
1427
1584
|
opForced[op] = nested.forced;
|
|
1428
1585
|
}
|
|
1429
1586
|
}
|
|
1430
|
-
if (!hasClientOps && Object.keys(opForced).length === 0) {
|
|
1587
|
+
if (!hasClientOps && Object.keys(opForced).length === 0 && !hasForcedNull) {
|
|
1431
1588
|
throw new ShapeError(
|
|
1432
1589
|
`Relation filter for "${key}" on model "${model}" produced no conditions. Define at least one nested field in the operator shape.`
|
|
1433
1590
|
);
|
|
@@ -1435,7 +1592,7 @@ function createWhereBuilder(typeMap, enumMap, scalarBase) {
|
|
|
1435
1592
|
if (hasClientOps) {
|
|
1436
1593
|
const clientOpKeys = Object.keys(opSchemas);
|
|
1437
1594
|
const opObjSchema = import_zod4.z.object(opSchemas).strict();
|
|
1438
|
-
if (Object.keys(opForced).length === 0) {
|
|
1595
|
+
if (Object.keys(opForced).length === 0 && !hasForcedNull) {
|
|
1439
1596
|
fieldSchemas[key] = opObjSchema.refine(
|
|
1440
1597
|
(v) => clientOpKeys.some(
|
|
1441
1598
|
(k) => v[k] !== void 0
|
|
@@ -1737,25 +1894,26 @@ function createArgsBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
|
1737
1894
|
return import_zod5.z.union([singleSchema, import_zod5.z.preprocess(coerceToArray, import_zod5.z.array(singleSchema).min(1))]).optional();
|
|
1738
1895
|
}
|
|
1739
1896
|
function buildTakeSchema(config) {
|
|
1740
|
-
|
|
1741
|
-
|
|
1897
|
+
const normalized = typeof config === "number" ? { max: config, default: config } : config;
|
|
1898
|
+
if (!Number.isFinite(normalized.max) || !Number.isInteger(normalized.max)) {
|
|
1899
|
+
throw new ShapeError(`take max must be a finite integer, got ${normalized.max}`);
|
|
1742
1900
|
}
|
|
1743
|
-
if (
|
|
1744
|
-
throw new ShapeError(`take max must be at least 1, got ${
|
|
1901
|
+
if (normalized.max < 1) {
|
|
1902
|
+
throw new ShapeError(`take max must be at least 1, got ${normalized.max}`);
|
|
1745
1903
|
}
|
|
1746
|
-
if (
|
|
1747
|
-
if (!Number.isFinite(
|
|
1748
|
-
throw new ShapeError(`take default must be a finite integer, got ${
|
|
1904
|
+
if (normalized.default !== void 0) {
|
|
1905
|
+
if (!Number.isFinite(normalized.default) || !Number.isInteger(normalized.default)) {
|
|
1906
|
+
throw new ShapeError(`take default must be a finite integer, got ${normalized.default}`);
|
|
1749
1907
|
}
|
|
1750
|
-
if (
|
|
1751
|
-
throw new ShapeError(`take default must be at least 1, got ${
|
|
1908
|
+
if (normalized.default < 1) {
|
|
1909
|
+
throw new ShapeError(`take default must be at least 1, got ${normalized.default}`);
|
|
1752
1910
|
}
|
|
1753
|
-
if (
|
|
1911
|
+
if (normalized.default > normalized.max) {
|
|
1754
1912
|
throw new ShapeError("take default cannot exceed max");
|
|
1755
1913
|
}
|
|
1756
|
-
return import_zod5.z.number().int().min(1).max(
|
|
1914
|
+
return import_zod5.z.number().int().min(1).max(normalized.max).default(normalized.default);
|
|
1757
1915
|
}
|
|
1758
|
-
return import_zod5.z.number().int().min(1).max(
|
|
1916
|
+
return import_zod5.z.number().int().min(1).max(normalized.max).optional();
|
|
1759
1917
|
}
|
|
1760
1918
|
function buildCursorSchema(model, cursorConfig) {
|
|
1761
1919
|
const modelFields = typeMap[model];
|
|
@@ -1862,10 +2020,21 @@ function createArgsBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
|
1862
2020
|
).optional();
|
|
1863
2021
|
}
|
|
1864
2022
|
const havingFieldKeys = Object.keys(fieldSchemas);
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2023
|
+
const havingSchema = import_zod5.z.lazy(() => {
|
|
2024
|
+
const allSchemas = { ...fieldSchemas };
|
|
2025
|
+
allSchemas["AND"] = import_zod5.z.preprocess(coerceToArray, import_zod5.z.array(havingSchema).min(1)).optional();
|
|
2026
|
+
allSchemas["OR"] = import_zod5.z.preprocess(coerceToArray, import_zod5.z.array(havingSchema).min(1)).optional();
|
|
2027
|
+
allSchemas["NOT"] = import_zod5.z.union([
|
|
2028
|
+
havingSchema,
|
|
2029
|
+
import_zod5.z.preprocess(coerceToArray, import_zod5.z.array(havingSchema).min(1))
|
|
2030
|
+
]).optional();
|
|
2031
|
+
const allKeys = [...havingFieldKeys, "AND", "OR", "NOT"];
|
|
2032
|
+
return import_zod5.z.object(allSchemas).strict().refine(
|
|
2033
|
+
(v) => allKeys.some((k) => v[k] !== void 0),
|
|
2034
|
+
{ message: "having must specify at least one field or combinator" }
|
|
2035
|
+
);
|
|
2036
|
+
});
|
|
2037
|
+
return havingSchema.optional();
|
|
1869
2038
|
}
|
|
1870
2039
|
function buildAggregateFieldSchema(model, opName, fieldConfig) {
|
|
1871
2040
|
const modelFields = typeMap[model];
|
|
@@ -2637,6 +2806,17 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
|
2637
2806
|
return null;
|
|
2638
2807
|
return { key: matched, shape: shapes[matched] };
|
|
2639
2808
|
}
|
|
2809
|
+
function resolveDefaultShape(config, model, method, normalizedBody, opts, builtCache, isUnique) {
|
|
2810
|
+
const shapeOrFn = config["default"];
|
|
2811
|
+
let built;
|
|
2812
|
+
if (typeof shapeOrFn === "function") {
|
|
2813
|
+
const resolved = resolveAndValidateShape(shapeOrFn, opts?.ctx);
|
|
2814
|
+
built = buildShapeZodSchema(model, method, resolved);
|
|
2815
|
+
} else {
|
|
2816
|
+
built = builtCache.get("default");
|
|
2817
|
+
}
|
|
2818
|
+
return applyBuiltShape(built, normalizedBody, isUnique);
|
|
2819
|
+
}
|
|
2640
2820
|
function buildQuerySchema(model, method, config) {
|
|
2641
2821
|
const isSingle = typeof config === "function" || isShapeConfig(config);
|
|
2642
2822
|
const builtCache = /* @__PURE__ */ new Map();
|
|
@@ -2688,23 +2868,39 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
|
2688
2868
|
"Pass caller via opts.caller, not in the request body."
|
|
2689
2869
|
);
|
|
2690
2870
|
}
|
|
2871
|
+
const namedConfig = config;
|
|
2691
2872
|
const caller = opts?.caller;
|
|
2692
2873
|
if (typeof caller !== "string") {
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2874
|
+
if ("default" in namedConfig) {
|
|
2875
|
+
return resolveDefaultShape(
|
|
2876
|
+
namedConfig,
|
|
2877
|
+
model,
|
|
2878
|
+
method,
|
|
2879
|
+
normalizedBody,
|
|
2880
|
+
opts,
|
|
2881
|
+
builtCache,
|
|
2882
|
+
isUnique
|
|
2883
|
+
);
|
|
2884
|
+
}
|
|
2885
|
+
const allowed = Object.keys(namedConfig);
|
|
2696
2886
|
throw new CallerError(
|
|
2697
2887
|
`Missing caller. This query uses named shape routing with keys: ${allowed.map((k) => `"${k}"`).join(", ")}. Provide caller via opts.caller.`
|
|
2698
2888
|
);
|
|
2699
2889
|
}
|
|
2700
|
-
const matched = matchCaller(
|
|
2701
|
-
config,
|
|
2702
|
-
caller
|
|
2703
|
-
);
|
|
2890
|
+
const matched = matchCaller(namedConfig, caller);
|
|
2704
2891
|
if (!matched) {
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2892
|
+
if ("default" in namedConfig) {
|
|
2893
|
+
return resolveDefaultShape(
|
|
2894
|
+
namedConfig,
|
|
2895
|
+
model,
|
|
2896
|
+
method,
|
|
2897
|
+
normalizedBody,
|
|
2898
|
+
opts,
|
|
2899
|
+
builtCache,
|
|
2900
|
+
isUnique
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
const allowed = Object.keys(namedConfig);
|
|
2708
2904
|
throw new CallerError(
|
|
2709
2905
|
`Unknown caller: "${caller}". Allowed: ${allowed.map((k) => `"${k}"`).join(", ")}`
|
|
2710
2906
|
);
|
|
@@ -2737,23 +2933,10 @@ var READ_OPS = /* @__PURE__ */ new Set([
|
|
|
2737
2933
|
"findFirstOrThrow",
|
|
2738
2934
|
"count"
|
|
2739
2935
|
]);
|
|
2740
|
-
var AGGREGATE_OPS = /* @__PURE__ */ new Set([
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
]);
|
|
2744
|
-
var FIND_UNIQUE_OPS = /* @__PURE__ */ new Set([
|
|
2745
|
-
"findUnique",
|
|
2746
|
-
"findUniqueOrThrow"
|
|
2747
|
-
]);
|
|
2748
|
-
var CREATE_OPS = /* @__PURE__ */ new Set([
|
|
2749
|
-
"create",
|
|
2750
|
-
"createMany",
|
|
2751
|
-
"createManyAndReturn"
|
|
2752
|
-
]);
|
|
2753
|
-
var UNIQUE_MUTATION_OPS = /* @__PURE__ */ new Set([
|
|
2754
|
-
"update",
|
|
2755
|
-
"delete"
|
|
2756
|
-
]);
|
|
2936
|
+
var AGGREGATE_OPS = /* @__PURE__ */ new Set(["aggregate", "groupBy"]);
|
|
2937
|
+
var FIND_UNIQUE_OPS = /* @__PURE__ */ new Set(["findUnique", "findUniqueOrThrow"]);
|
|
2938
|
+
var CREATE_OPS = /* @__PURE__ */ new Set(["create", "createMany", "createManyAndReturn"]);
|
|
2939
|
+
var UNIQUE_MUTATION_OPS = /* @__PURE__ */ new Set(["update", "delete"]);
|
|
2757
2940
|
var MULTI_MUTATION_OPS = /* @__PURE__ */ new Set([
|
|
2758
2941
|
"updateMany",
|
|
2759
2942
|
"updateManyAndReturn",
|
|
@@ -2882,7 +3065,9 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
2882
3065
|
for (const s of presentScopes) {
|
|
2883
3066
|
validateScopeValue(s.root, ctx[s.root]);
|
|
2884
3067
|
}
|
|
2885
|
-
const presentConditions = presentScopes.map((s) => ({
|
|
3068
|
+
const presentConditions = presentScopes.map((s) => ({
|
|
3069
|
+
[s.fk]: ctx[s.root]
|
|
3070
|
+
}));
|
|
2886
3071
|
const missingRoots = scopes.filter((s) => ctx[s.root] == null).map((s) => s.root);
|
|
2887
3072
|
const isMutation = CREATE_OPS.has(operation) || UNIQUE_MUTATION_OPS.has(operation) || MULTI_MUTATION_OPS.has(operation) || operation === "upsert";
|
|
2888
3073
|
if (missingRoots.length > 0) {
|
|
@@ -2912,14 +3097,32 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
2912
3097
|
throw new ShapeError(`upsert expects create to be an object`);
|
|
2913
3098
|
}
|
|
2914
3099
|
nextArgs.create = { ...args.create };
|
|
2915
|
-
enforceDataScope(
|
|
3100
|
+
enforceDataScope(
|
|
3101
|
+
nextArgs.create,
|
|
3102
|
+
scopes,
|
|
3103
|
+
overrides,
|
|
3104
|
+
log,
|
|
3105
|
+
model,
|
|
3106
|
+
operation,
|
|
3107
|
+
onScopeRelationWrite,
|
|
3108
|
+
"create"
|
|
3109
|
+
);
|
|
2916
3110
|
}
|
|
2917
3111
|
if (args.update !== void 0 && args.update !== null) {
|
|
2918
3112
|
if (typeof args.update !== "object" || Array.isArray(args.update)) {
|
|
2919
3113
|
throw new ShapeError(`upsert expects update to be an object`);
|
|
2920
3114
|
}
|
|
2921
3115
|
nextArgs.update = { ...args.update };
|
|
2922
|
-
enforceDataScope(
|
|
3116
|
+
enforceDataScope(
|
|
3117
|
+
nextArgs.update,
|
|
3118
|
+
scopes,
|
|
3119
|
+
overrides,
|
|
3120
|
+
log,
|
|
3121
|
+
model,
|
|
3122
|
+
operation,
|
|
3123
|
+
onScopeRelationWrite,
|
|
3124
|
+
"mutate"
|
|
3125
|
+
);
|
|
2923
3126
|
}
|
|
2924
3127
|
return query(nextArgs);
|
|
2925
3128
|
}
|
|
@@ -2929,7 +3132,15 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
2929
3132
|
`Scoped model "${model}" does not allow ${operation} via scope extension (findUniqueMode is "reject"). Use findFirst with explicit where conditions instead.`
|
|
2930
3133
|
);
|
|
2931
3134
|
}
|
|
2932
|
-
return handleFindUnique(
|
|
3135
|
+
return handleFindUnique(
|
|
3136
|
+
args,
|
|
3137
|
+
query,
|
|
3138
|
+
conditions,
|
|
3139
|
+
scopes,
|
|
3140
|
+
operation,
|
|
3141
|
+
log,
|
|
3142
|
+
model
|
|
3143
|
+
);
|
|
2933
3144
|
}
|
|
2934
3145
|
if (READ_OPS.has(operation)) {
|
|
2935
3146
|
nextArgs.where = buildAndConditions(args.where, conditions);
|
|
@@ -2957,7 +3168,16 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
2957
3168
|
}
|
|
2958
3169
|
nextArgs.data = args.data.map((d) => {
|
|
2959
3170
|
const item = { ...d };
|
|
2960
|
-
enforceDataScope(
|
|
3171
|
+
enforceDataScope(
|
|
3172
|
+
item,
|
|
3173
|
+
scopes,
|
|
3174
|
+
overrides,
|
|
3175
|
+
log,
|
|
3176
|
+
model,
|
|
3177
|
+
operation,
|
|
3178
|
+
onScopeRelationWrite,
|
|
3179
|
+
"create"
|
|
3180
|
+
);
|
|
2961
3181
|
return item;
|
|
2962
3182
|
});
|
|
2963
3183
|
} else {
|
|
@@ -2965,7 +3185,16 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
2965
3185
|
throw new ShapeError(`${operation} expects data to be an object`);
|
|
2966
3186
|
}
|
|
2967
3187
|
nextArgs.data = { ...args.data };
|
|
2968
|
-
enforceDataScope(
|
|
3188
|
+
enforceDataScope(
|
|
3189
|
+
nextArgs.data,
|
|
3190
|
+
scopes,
|
|
3191
|
+
overrides,
|
|
3192
|
+
log,
|
|
3193
|
+
model,
|
|
3194
|
+
operation,
|
|
3195
|
+
onScopeRelationWrite,
|
|
3196
|
+
"create"
|
|
3197
|
+
);
|
|
2969
3198
|
}
|
|
2970
3199
|
return query(nextArgs);
|
|
2971
3200
|
}
|
|
@@ -2976,7 +3205,16 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
2976
3205
|
throw new ShapeError(`${operation} expects data to be an object`);
|
|
2977
3206
|
}
|
|
2978
3207
|
nextArgs.data = { ...args.data };
|
|
2979
|
-
enforceDataScope(
|
|
3208
|
+
enforceDataScope(
|
|
3209
|
+
nextArgs.data,
|
|
3210
|
+
scopes,
|
|
3211
|
+
overrides,
|
|
3212
|
+
log,
|
|
3213
|
+
model,
|
|
3214
|
+
operation,
|
|
3215
|
+
onScopeRelationWrite,
|
|
3216
|
+
"mutate"
|
|
3217
|
+
);
|
|
2980
3218
|
}
|
|
2981
3219
|
return query(nextArgs);
|
|
2982
3220
|
}
|
|
@@ -2987,7 +3225,16 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
2987
3225
|
throw new ShapeError(`${operation} expects data to be an object`);
|
|
2988
3226
|
}
|
|
2989
3227
|
nextArgs.data = { ...args.data };
|
|
2990
|
-
enforceDataScope(
|
|
3228
|
+
enforceDataScope(
|
|
3229
|
+
nextArgs.data,
|
|
3230
|
+
scopes,
|
|
3231
|
+
overrides,
|
|
3232
|
+
log,
|
|
3233
|
+
model,
|
|
3234
|
+
operation,
|
|
3235
|
+
onScopeRelationWrite,
|
|
3236
|
+
"mutate"
|
|
3237
|
+
);
|
|
2991
3238
|
}
|
|
2992
3239
|
return query(nextArgs);
|
|
2993
3240
|
}
|
|
@@ -2998,7 +3245,7 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
2998
3245
|
}
|
|
2999
3246
|
};
|
|
3000
3247
|
}
|
|
3001
|
-
async function handleFindUnique(args, query, conditions, scopes, operation, log) {
|
|
3248
|
+
async function handleFindUnique(args, query, conditions, scopes, operation, log, model) {
|
|
3002
3249
|
const nextArgs = { ...args };
|
|
3003
3250
|
const injectedFks = [];
|
|
3004
3251
|
const originalSelect = args?.select;
|
|
@@ -3016,7 +3263,9 @@ async function handleFindUnique(args, query, conditions, scopes, operation, log)
|
|
|
3016
3263
|
if (result === null)
|
|
3017
3264
|
return result;
|
|
3018
3265
|
if (typeof result !== "object" || result === null) {
|
|
3019
|
-
throw new ShapeError(
|
|
3266
|
+
throw new ShapeError(
|
|
3267
|
+
`${operation} on model "${model}" returned a non-object, non-null result`
|
|
3268
|
+
);
|
|
3020
3269
|
}
|
|
3021
3270
|
const resultObj = result;
|
|
3022
3271
|
let verifyObj = resultObj;
|
|
@@ -3025,22 +3274,29 @@ async function handleFindUnique(args, query, conditions, scopes, operation, log)
|
|
|
3025
3274
|
const where = args?.where;
|
|
3026
3275
|
if (!where || typeof where !== "object" || Array.isArray(where)) {
|
|
3027
3276
|
throw new PolicyError(
|
|
3028
|
-
`prisma-guard: Cannot verify scope \u2014 missing FK fields (${missingFks.join(", ")}) and
|
|
3277
|
+
`prisma-guard: Cannot verify scope on model "${model}" for ${operation} \u2014 missing FK fields (${missingFks.join(", ")}) in result and args.where is not a valid object. Ensure the where clause is a plain object so the verification query can re-fetch FK values.`
|
|
3029
3278
|
);
|
|
3030
3279
|
}
|
|
3031
3280
|
let verifyResult;
|
|
3032
3281
|
try {
|
|
3033
3282
|
verifyResult = await query({ where, select: buildFkSelect(fks) });
|
|
3034
3283
|
} catch (err) {
|
|
3284
|
+
log.warn(
|
|
3285
|
+
`prisma-guard: Scope verification re-query failed for ${operation} on model "${model}": ${err?.message ?? String(err)}`
|
|
3286
|
+
);
|
|
3035
3287
|
throw new PolicyError(
|
|
3036
|
-
`prisma-guard: Scope verification query failed for
|
|
3288
|
+
`prisma-guard: Scope verification re-query failed for ${operation} on model "${model}". The re-query to fetch FK fields (${fks.join(", ")}) returned a database error. Check server logs for the underlying cause.`
|
|
3037
3289
|
);
|
|
3038
3290
|
}
|
|
3039
3291
|
if (verifyResult === null) {
|
|
3040
|
-
throw new PolicyError(
|
|
3292
|
+
throw new PolicyError(
|
|
3293
|
+
`prisma-guard: Scope verification re-query on model "${model}" returned null for an existing ${operation} result. The record may have been deleted between the original query and verification.`
|
|
3294
|
+
);
|
|
3041
3295
|
}
|
|
3042
3296
|
if (typeof verifyResult !== "object" || verifyResult === null) {
|
|
3043
|
-
throw new PolicyError(
|
|
3297
|
+
throw new PolicyError(
|
|
3298
|
+
`prisma-guard: Scope verification re-query on model "${model}" returned a non-object result.`
|
|
3299
|
+
);
|
|
3044
3300
|
}
|
|
3045
3301
|
verifyObj = verifyResult;
|
|
3046
3302
|
}
|
|
@@ -3048,12 +3304,14 @@ async function handleFindUnique(args, query, conditions, scopes, operation, log)
|
|
|
3048
3304
|
const [fk, value] = Object.entries(condition)[0];
|
|
3049
3305
|
if (!(fk in verifyObj)) {
|
|
3050
3306
|
throw new PolicyError(
|
|
3051
|
-
`prisma-guard: Cannot verify scope on "${
|
|
3307
|
+
`prisma-guard: Cannot verify scope on model "${model}" \u2014 FK field "${fk}" not present in verification result. Ensure "${fk}" is selectable on model "${model}" (not excluded by select or field-level access).`
|
|
3052
3308
|
);
|
|
3053
3309
|
}
|
|
3054
3310
|
if (!looseEqual(verifyObj[fk], value, log, fk)) {
|
|
3055
3311
|
if (operation === "findUniqueOrThrow") {
|
|
3056
|
-
throw new PolicyError(
|
|
3312
|
+
throw new PolicyError(
|
|
3313
|
+
`Record on model "${model}" not accessible in current scope`
|
|
3314
|
+
);
|
|
3057
3315
|
}
|
|
3058
3316
|
return null;
|
|
3059
3317
|
}
|
|
@@ -3758,11 +4016,22 @@ function buildDataSchema(model, dataConfig, mode, typeMap, schemaBuilder, zodDef
|
|
|
3758
4016
|
forced
|
|
3759
4017
|
};
|
|
3760
4018
|
}
|
|
3761
|
-
function validateAndMergeData(bodyData, cached, method) {
|
|
4019
|
+
function validateAndMergeData(bodyData, cached, method, modelName) {
|
|
3762
4020
|
if (bodyData === void 0 || bodyData === null) {
|
|
3763
4021
|
throw new ShapeError(`${method} requires "data" in request body`);
|
|
3764
4022
|
}
|
|
3765
|
-
|
|
4023
|
+
let validated;
|
|
4024
|
+
try {
|
|
4025
|
+
validated = cached.schema.parse(bodyData);
|
|
4026
|
+
} catch (err) {
|
|
4027
|
+
if (err instanceof ShapeError)
|
|
4028
|
+
throw err;
|
|
4029
|
+
if (err && typeof err === "object" && "issues" in err) {
|
|
4030
|
+
const context = modelName ? `Invalid data for ${method} on model "${modelName}"` : `Invalid data for ${method}`;
|
|
4031
|
+
throw new ShapeError(`${context}: ${formatZodError(err)}`, { cause: err });
|
|
4032
|
+
}
|
|
4033
|
+
throw err;
|
|
4034
|
+
}
|
|
3766
4035
|
return { ...validated, ...deepClone(cached.forced) };
|
|
3767
4036
|
}
|
|
3768
4037
|
function hasDataRefines(dataConfig) {
|
|
@@ -3832,14 +4101,26 @@ function resolveShape(input, body, contextFn, caller) {
|
|
|
3832
4101
|
);
|
|
3833
4102
|
}
|
|
3834
4103
|
if (typeof caller !== "string") {
|
|
4104
|
+
if ("default" in namedMap) {
|
|
4105
|
+
const shapeOrFn2 = namedMap["default"];
|
|
4106
|
+
const wasDynamic2 = typeof shapeOrFn2 === "function";
|
|
4107
|
+
const shape2 = wasDynamic2 ? resolveDynamicShape(shapeOrFn2, contextFn) : shapeOrFn2;
|
|
4108
|
+
return { shape: shape2, body: parsed, matchedKey: "default", wasDynamic: wasDynamic2 };
|
|
4109
|
+
}
|
|
3835
4110
|
const patterns2 = Object.keys(namedMap);
|
|
3836
4111
|
throw new CallerError(
|
|
3837
|
-
`Missing caller. This guard uses named shape routing with keys: ${patterns2.map((k) => `"${k}"`).join(", ")}. Provide caller as second argument to .guard() or set "caller" in the context function.`
|
|
4112
|
+
`Missing caller. This guard uses named shape routing with keys: ${patterns2.map((k) => `"${k}"`).join(", ")}. Provide caller as second argument to .guard() or set "caller" in the context function, or add a "default" variant.`
|
|
3838
4113
|
);
|
|
3839
4114
|
}
|
|
3840
4115
|
const patterns = Object.keys(namedMap);
|
|
3841
4116
|
const matched = matchCallerPattern(patterns, caller);
|
|
3842
4117
|
if (!matched) {
|
|
4118
|
+
if ("default" in namedMap) {
|
|
4119
|
+
const shapeOrFn2 = namedMap["default"];
|
|
4120
|
+
const wasDynamic2 = typeof shapeOrFn2 === "function";
|
|
4121
|
+
const shape2 = wasDynamic2 ? resolveDynamicShape(shapeOrFn2, contextFn) : shapeOrFn2;
|
|
4122
|
+
return { shape: shape2, body: parsed, matchedKey: "default", wasDynamic: wasDynamic2 };
|
|
4123
|
+
}
|
|
3843
4124
|
throw new CallerError(
|
|
3844
4125
|
`Unknown caller: "${caller}". Allowed: ${patterns.map((k) => `"${k}"`).join(", ")}`
|
|
3845
4126
|
);
|
|
@@ -4205,7 +4486,10 @@ function createModelGuardExtension(config) {
|
|
|
4205
4486
|
const cached = uniqueWhereCache.get(cacheKey);
|
|
4206
4487
|
if (cached)
|
|
4207
4488
|
return cached;
|
|
4208
|
-
const built = queryBuilder.buildUniqueWhereSchema(
|
|
4489
|
+
const built = queryBuilder.buildUniqueWhereSchema(
|
|
4490
|
+
modelName,
|
|
4491
|
+
whereConfig
|
|
4492
|
+
);
|
|
4209
4493
|
uniqueWhereCache.set(cacheKey, built);
|
|
4210
4494
|
return built;
|
|
4211
4495
|
}
|
|
@@ -4290,7 +4574,15 @@ function createModelGuardExtension(config) {
|
|
|
4290
4574
|
} else {
|
|
4291
4575
|
projectionBody = buildDefaultProjectionBody(shape);
|
|
4292
4576
|
}
|
|
4293
|
-
|
|
4577
|
+
let validated;
|
|
4578
|
+
try {
|
|
4579
|
+
validated = projection.zodSchema.parse(projectionBody);
|
|
4580
|
+
} catch (err) {
|
|
4581
|
+
wrapParseError(
|
|
4582
|
+
err,
|
|
4583
|
+
`Invalid select/include projection on model "${modelName}" for ${method}`
|
|
4584
|
+
);
|
|
4585
|
+
}
|
|
4294
4586
|
if (Object.keys(projection.forcedIncludeTree).length > 0) {
|
|
4295
4587
|
applyForcedTree(validated, "include", projection.forcedIncludeTree);
|
|
4296
4588
|
}
|
|
@@ -4327,7 +4619,11 @@ function createModelGuardExtension(config) {
|
|
|
4327
4619
|
}
|
|
4328
4620
|
sanitizedWhere = w;
|
|
4329
4621
|
}
|
|
4330
|
-
|
|
4622
|
+
try {
|
|
4623
|
+
validatedWhere = built.schema.parse(sanitizedWhere);
|
|
4624
|
+
} catch (err) {
|
|
4625
|
+
wrapParseError(err, `Invalid "where" clause on model "${modelName}"`);
|
|
4626
|
+
}
|
|
4331
4627
|
} else if (bodyWhere !== void 0) {
|
|
4332
4628
|
if (bodyWhere === null || !isPlainObject(bodyWhere) || Object.keys(bodyWhere).length > 0) {
|
|
4333
4629
|
let hasOnlyForcedKeys = false;
|
|
@@ -4357,7 +4653,10 @@ function createModelGuardExtension(config) {
|
|
|
4357
4653
|
wasDynamic
|
|
4358
4654
|
);
|
|
4359
4655
|
if (Object.keys(where).length === 0) {
|
|
4360
|
-
|
|
4656
|
+
const expectedFields = shape.where ? Object.keys(shape.where).join(", ") : "none defined";
|
|
4657
|
+
throw new ShapeError(
|
|
4658
|
+
`${method} on model "${modelName}" requires a where condition. Expected fields: ${expectedFields}`
|
|
4659
|
+
);
|
|
4361
4660
|
}
|
|
4362
4661
|
return where;
|
|
4363
4662
|
}
|
|
@@ -4369,20 +4668,30 @@ function createModelGuardExtension(config) {
|
|
|
4369
4668
|
if (built.schema) {
|
|
4370
4669
|
if (bodyWhere !== void 0 && bodyWhere !== null) {
|
|
4371
4670
|
if (!isPlainObject(bodyWhere)) {
|
|
4372
|
-
throw new ShapeError(
|
|
4671
|
+
throw new ShapeError(
|
|
4672
|
+
`Invalid "where" on model "${modelName}": unique where must be a plain object`
|
|
4673
|
+
);
|
|
4373
4674
|
}
|
|
4374
4675
|
const sanitized = { ...bodyWhere };
|
|
4375
4676
|
for (const key of built.forcedOnlyKeys) {
|
|
4376
4677
|
delete sanitized[key];
|
|
4377
4678
|
}
|
|
4378
|
-
|
|
4679
|
+
try {
|
|
4680
|
+
result = built.schema.parse(sanitized);
|
|
4681
|
+
} catch (err) {
|
|
4682
|
+
const allowedFields = Object.keys(shape.where).join(", ");
|
|
4683
|
+
wrapParseError(
|
|
4684
|
+
err,
|
|
4685
|
+
`Invalid unique "where" on model "${modelName}". Allowed fields: ${allowedFields}`
|
|
4686
|
+
);
|
|
4687
|
+
}
|
|
4379
4688
|
}
|
|
4380
4689
|
} else if (bodyWhere !== void 0 && bodyWhere !== null) {
|
|
4381
4690
|
if (isPlainObject(bodyWhere)) {
|
|
4382
4691
|
const hasOnlyForcedKeys = built.forcedOnlyKeys.size > 0 && Object.keys(bodyWhere).every((k) => built.forcedOnlyKeys.has(k));
|
|
4383
4692
|
if (!hasOnlyForcedKeys && Object.keys(bodyWhere).length > 0) {
|
|
4384
4693
|
throw new ShapeError(
|
|
4385
|
-
|
|
4694
|
+
`Unique where on model "${modelName}" contains only forced values. Client where input is not accepted.`
|
|
4386
4695
|
);
|
|
4387
4696
|
}
|
|
4388
4697
|
}
|
|
@@ -4400,7 +4709,12 @@ function createModelGuardExtension(config) {
|
|
|
4400
4709
|
wasDynamic
|
|
4401
4710
|
);
|
|
4402
4711
|
if (Object.keys(where).length === 0) {
|
|
4403
|
-
|
|
4712
|
+
const constraints = uniqueMap[modelName];
|
|
4713
|
+
const constraintDesc = constraints ? constraints.map((c) => `(${c.join(", ")})`).join(" | ") : "unknown";
|
|
4714
|
+
const expectedFields = shape.where ? Object.keys(shape.where).join(", ") : "none defined";
|
|
4715
|
+
throw new ShapeError(
|
|
4716
|
+
`${method} on model "${modelName}" requires a unique where condition. Unique constraints: ${constraintDesc}. Shape allows: ${expectedFields}`
|
|
4717
|
+
);
|
|
4404
4718
|
}
|
|
4405
4719
|
return where;
|
|
4406
4720
|
}
|
|
@@ -4443,7 +4757,7 @@ function createModelGuardExtension(config) {
|
|
|
4443
4757
|
);
|
|
4444
4758
|
const isUnique = UNIQUE_READ_METHODS.has(method);
|
|
4445
4759
|
const effectiveBody = buildEffectiveReadBody(resolved);
|
|
4446
|
-
const args = applyBuiltShape(built, effectiveBody, isUnique);
|
|
4760
|
+
const args = applyBuiltShape(built, effectiveBody, isUnique, modelName);
|
|
4447
4761
|
if (isUnique && args.where) {
|
|
4448
4762
|
validateResolvedUniqueWhere(
|
|
4449
4763
|
modelName,
|
|
@@ -4499,7 +4813,8 @@ function createModelGuardExtension(config) {
|
|
|
4499
4813
|
const data = validateAndMergeData(
|
|
4500
4814
|
resolved.body.data,
|
|
4501
4815
|
dataSchema,
|
|
4502
|
-
method
|
|
4816
|
+
method,
|
|
4817
|
+
modelName
|
|
4503
4818
|
);
|
|
4504
4819
|
args = { data };
|
|
4505
4820
|
} else {
|
|
@@ -4508,7 +4823,7 @@ function createModelGuardExtension(config) {
|
|
|
4508
4823
|
if (resolved.body.data.length === 0)
|
|
4509
4824
|
throw new ShapeError(`${method} received empty data array`);
|
|
4510
4825
|
const data = resolved.body.data.map(
|
|
4511
|
-
(item) => validateAndMergeData(item, dataSchema, method)
|
|
4826
|
+
(item) => validateAndMergeData(item, dataSchema, method, modelName)
|
|
4512
4827
|
);
|
|
4513
4828
|
args = { data };
|
|
4514
4829
|
}
|
|
@@ -4562,7 +4877,8 @@ function createModelGuardExtension(config) {
|
|
|
4562
4877
|
const data = validateAndMergeData(
|
|
4563
4878
|
resolved.body.data,
|
|
4564
4879
|
dataSchema,
|
|
4565
|
-
method
|
|
4880
|
+
method,
|
|
4881
|
+
modelName
|
|
4566
4882
|
);
|
|
4567
4883
|
let where;
|
|
4568
4884
|
if (isUniqueWhere) {
|
|
@@ -4728,7 +5044,8 @@ function createModelGuardExtension(config) {
|
|
|
4728
5044
|
const createData = validateAndMergeData(
|
|
4729
5045
|
resolved.body.create,
|
|
4730
5046
|
createDataSchema,
|
|
4731
|
-
"upsert (create)"
|
|
5047
|
+
"upsert (create)",
|
|
5048
|
+
modelName
|
|
4732
5049
|
);
|
|
4733
5050
|
const updateDataSchema = getDataSchema(
|
|
4734
5051
|
"update",
|
|
@@ -4739,7 +5056,8 @@ function createModelGuardExtension(config) {
|
|
|
4739
5056
|
const updateData = validateAndMergeData(
|
|
4740
5057
|
resolved.body.update,
|
|
4741
5058
|
updateDataSchema,
|
|
4742
|
-
"upsert (update)"
|
|
5059
|
+
"upsert (update)",
|
|
5060
|
+
modelName
|
|
4743
5061
|
);
|
|
4744
5062
|
const where = requireUniqueWhere(
|
|
4745
5063
|
resolved.shape,
|