prisma-guard 1.1.0 → 1.2.1
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 +700 -104
- package/dist/generator/index.js +189 -48
- package/dist/generator/index.js.map +1 -1
- package/dist/runtime/index.d.ts +16 -5
- package/dist/runtime/index.js +1793 -1057
- package/dist/runtime/index.js.map +1 -1
- package/package.json +1 -1
package/dist/runtime/index.js
CHANGED
|
@@ -26,19 +26,25 @@ var CallerError = class extends Error {
|
|
|
26
26
|
this.name = "CallerError";
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
function formatZodError(err) {
|
|
30
|
+
return err.issues.map((i) => {
|
|
31
|
+
const p = i.path.length > 0 ? `${i.path.join(".")}: ` : "";
|
|
32
|
+
return `${p}${i.message}`;
|
|
33
|
+
}).join("; ");
|
|
34
|
+
}
|
|
35
35
|
|
|
36
36
|
// src/shared/scalar-base.ts
|
|
37
37
|
import { z } from "zod";
|
|
38
38
|
function isJsonSafe(value) {
|
|
39
|
-
const stack = [value];
|
|
39
|
+
const stack = [{ tag: "visit", value }];
|
|
40
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
40
41
|
while (stack.length > 0) {
|
|
41
|
-
const
|
|
42
|
+
const entry = stack.pop();
|
|
43
|
+
if (entry.tag === "exit") {
|
|
44
|
+
ancestors.delete(entry.ref);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const current = entry.value;
|
|
42
48
|
if (current === void 0)
|
|
43
49
|
return false;
|
|
44
50
|
if (current === null)
|
|
@@ -52,9 +58,13 @@ function isJsonSafe(value) {
|
|
|
52
58
|
return false;
|
|
53
59
|
continue;
|
|
54
60
|
case "object": {
|
|
61
|
+
if (ancestors.has(current))
|
|
62
|
+
return false;
|
|
63
|
+
ancestors.add(current);
|
|
64
|
+
stack.push({ tag: "exit", ref: current });
|
|
55
65
|
if (Array.isArray(current)) {
|
|
56
66
|
for (let i = 0; i < current.length; i++) {
|
|
57
|
-
stack.push(current[i]);
|
|
67
|
+
stack.push({ tag: "visit", value: current[i] });
|
|
58
68
|
}
|
|
59
69
|
continue;
|
|
60
70
|
}
|
|
@@ -63,7 +73,7 @@ function isJsonSafe(value) {
|
|
|
63
73
|
return false;
|
|
64
74
|
const values = Object.values(current);
|
|
65
75
|
for (let i = 0; i < values.length; i++) {
|
|
66
|
-
stack.push(values[i]);
|
|
76
|
+
stack.push({ tag: "visit", value: values[i] });
|
|
67
77
|
}
|
|
68
78
|
continue;
|
|
69
79
|
}
|
|
@@ -73,39 +83,54 @@ function isJsonSafe(value) {
|
|
|
73
83
|
}
|
|
74
84
|
return true;
|
|
75
85
|
}
|
|
76
|
-
var
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
z.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
86
|
+
var DECIMAL_REGEX = /^-?(\d+\.?\d*|\.\d+)([eE]-?\d+)?$/;
|
|
87
|
+
var decimalStringSchema = z.string().refine(
|
|
88
|
+
(s) => DECIMAL_REGEX.test(s),
|
|
89
|
+
"Invalid decimal string"
|
|
90
|
+
);
|
|
91
|
+
var decimalObjectSchema = z.custom(
|
|
92
|
+
(v) => v !== null && typeof v === "object" && typeof v.toFixed === "function" && typeof v.toNumber === "function",
|
|
93
|
+
"Expected Decimal-compatible object"
|
|
94
|
+
);
|
|
95
|
+
function createDecimalFactory(strict) {
|
|
96
|
+
if (strict) {
|
|
97
|
+
return () => z.union([decimalStringSchema, decimalObjectSchema]);
|
|
98
|
+
}
|
|
99
|
+
return () => z.union([z.number(), decimalStringSchema, decimalObjectSchema]);
|
|
100
|
+
}
|
|
101
|
+
function createScalarBase(strictDecimal) {
|
|
102
|
+
return {
|
|
103
|
+
String: () => z.string(),
|
|
104
|
+
Int: () => z.number().int(),
|
|
105
|
+
Float: () => z.number(),
|
|
106
|
+
Decimal: createDecimalFactory(strictDecimal),
|
|
107
|
+
BigInt: () => z.union([
|
|
108
|
+
z.bigint(),
|
|
109
|
+
z.number().int().refine(
|
|
110
|
+
(v) => v >= Number.MIN_SAFE_INTEGER && v <= Number.MAX_SAFE_INTEGER,
|
|
111
|
+
"Number exceeds safe integer range for BigInt conversion"
|
|
112
|
+
).transform((v) => BigInt(v)),
|
|
113
|
+
z.string().regex(/^-?\d+$/).transform((v) => BigInt(v))
|
|
114
|
+
]),
|
|
115
|
+
Boolean: () => z.boolean(),
|
|
116
|
+
DateTime: () => z.union([
|
|
117
|
+
z.date(),
|
|
118
|
+
z.string().datetime({ offset: true })
|
|
119
|
+
]).pipe(z.coerce.date()),
|
|
120
|
+
Json: () => z.unknown().refine(isJsonSafe, "Value must be JSON-serializable (no undefined, functions, symbols, class instances, NaN, Infinity, or circular references)"),
|
|
121
|
+
Bytes: () => z.union([
|
|
122
|
+
z.string(),
|
|
123
|
+
z.custom((v) => v instanceof Uint8Array)
|
|
124
|
+
])
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
var SCALAR_BASE = createScalarBase(false);
|
|
128
|
+
|
|
129
|
+
// src/runtime/schema-builder.ts
|
|
130
|
+
import { z as z3 } from "zod";
|
|
107
131
|
|
|
108
132
|
// src/runtime/zod-type-map.ts
|
|
133
|
+
import { z as z2 } from "zod";
|
|
109
134
|
var SCALAR_OPERATORS = {
|
|
110
135
|
String: /* @__PURE__ */ new Set(["equals", "not", "contains", "startsWith", "endsWith", "in", "notIn"]),
|
|
111
136
|
Int: /* @__PURE__ */ new Set(["equals", "not", "gt", "gte", "lt", "lte", "in", "notIn"]),
|
|
@@ -130,7 +155,7 @@ function getSupportedOperators(fieldMeta) {
|
|
|
130
155
|
return [];
|
|
131
156
|
return [...ops];
|
|
132
157
|
}
|
|
133
|
-
function createBaseType(fieldMeta, enumMap) {
|
|
158
|
+
function createBaseType(fieldMeta, enumMap, scalarBase) {
|
|
134
159
|
let base;
|
|
135
160
|
if (fieldMeta.isEnum) {
|
|
136
161
|
const values = enumMap[fieldMeta.type];
|
|
@@ -139,7 +164,7 @@ function createBaseType(fieldMeta, enumMap) {
|
|
|
139
164
|
}
|
|
140
165
|
base = z2.enum(values);
|
|
141
166
|
} else {
|
|
142
|
-
const factory =
|
|
167
|
+
const factory = scalarBase[fieldMeta.type];
|
|
143
168
|
if (!factory) {
|
|
144
169
|
throw new ShapeError(`Unknown scalar type: ${fieldMeta.type}`);
|
|
145
170
|
}
|
|
@@ -150,7 +175,7 @@ function createBaseType(fieldMeta, enumMap) {
|
|
|
150
175
|
}
|
|
151
176
|
return base;
|
|
152
177
|
}
|
|
153
|
-
function createScalarListOperatorSchema(fieldMeta, operator, enumMap) {
|
|
178
|
+
function createScalarListOperatorSchema(fieldMeta, operator, enumMap, scalarBase) {
|
|
154
179
|
if (!SCALAR_LIST_OPERATORS.has(operator)) {
|
|
155
180
|
throw new ShapeError(`Operator "${operator}" not supported for scalar list fields`);
|
|
156
181
|
}
|
|
@@ -159,19 +184,19 @@ function createScalarListOperatorSchema(fieldMeta, operator, enumMap) {
|
|
|
159
184
|
}
|
|
160
185
|
if (operator === "equals") {
|
|
161
186
|
const itemMeta2 = { ...fieldMeta, isList: false };
|
|
162
|
-
const itemBase2 = createBaseType(itemMeta2, enumMap);
|
|
187
|
+
const itemBase2 = createBaseType(itemMeta2, enumMap, scalarBase);
|
|
163
188
|
return fieldMeta.isRequired ? z2.array(itemBase2) : z2.union([z2.array(itemBase2), z2.null()]);
|
|
164
189
|
}
|
|
165
190
|
const itemMeta = { ...fieldMeta, isList: false };
|
|
166
|
-
const itemBase = createBaseType(itemMeta, enumMap);
|
|
191
|
+
const itemBase = createBaseType(itemMeta, enumMap, scalarBase);
|
|
167
192
|
if (operator === "has") {
|
|
168
193
|
return !fieldMeta.isRequired ? z2.union([itemBase, z2.null()]) : itemBase;
|
|
169
194
|
}
|
|
170
195
|
return z2.array(itemBase);
|
|
171
196
|
}
|
|
172
|
-
function createOperatorSchema(fieldMeta, operator, enumMap) {
|
|
197
|
+
function createOperatorSchema(fieldMeta, operator, enumMap, scalarBase) {
|
|
173
198
|
if (fieldMeta.isList) {
|
|
174
|
-
return createScalarListOperatorSchema(fieldMeta, operator, enumMap);
|
|
199
|
+
return createScalarListOperatorSchema(fieldMeta, operator, enumMap, scalarBase);
|
|
175
200
|
}
|
|
176
201
|
if (fieldMeta.isEnum) {
|
|
177
202
|
const values = enumMap[fieldMeta.type];
|
|
@@ -200,7 +225,7 @@ function createOperatorSchema(fieldMeta, operator, enumMap) {
|
|
|
200
225
|
if (!supportedOps.has(operator)) {
|
|
201
226
|
throw new ShapeError(`Operator "${operator}" not supported for type "${fieldMeta.type}"`);
|
|
202
227
|
}
|
|
203
|
-
const factory =
|
|
228
|
+
const factory = scalarBase[fieldMeta.type];
|
|
204
229
|
if (!factory) {
|
|
205
230
|
throw new ShapeError(`Unknown scalar type: ${fieldMeta.type}`);
|
|
206
231
|
}
|
|
@@ -217,6 +242,24 @@ function createOperatorSchema(fieldMeta, operator, enumMap) {
|
|
|
217
242
|
return scalar;
|
|
218
243
|
}
|
|
219
244
|
|
|
245
|
+
// src/shared/utils.ts
|
|
246
|
+
function isPlainObject(v) {
|
|
247
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
248
|
+
return false;
|
|
249
|
+
const proto = Object.getPrototypeOf(v);
|
|
250
|
+
return proto === Object.prototype || proto === null;
|
|
251
|
+
}
|
|
252
|
+
function schemaProducesValueForUndefined(schema) {
|
|
253
|
+
const result = schema.safeParse(void 0);
|
|
254
|
+
return result.success && result.data !== void 0;
|
|
255
|
+
}
|
|
256
|
+
function isZodSchema(value) {
|
|
257
|
+
if (value == null || typeof value !== "object")
|
|
258
|
+
return false;
|
|
259
|
+
const v = value;
|
|
260
|
+
return typeof v.parse === "function" && typeof v.optional === "function";
|
|
261
|
+
}
|
|
262
|
+
|
|
220
263
|
// src/runtime/schema-builder.ts
|
|
221
264
|
var DEFAULT_MAX_CACHE = 500;
|
|
222
265
|
var DEFAULT_MAX_DEPTH = 5;
|
|
@@ -238,7 +281,7 @@ function lruSet(cache, key, value, maxSize) {
|
|
|
238
281
|
cache.delete(oldest);
|
|
239
282
|
}
|
|
240
283
|
}
|
|
241
|
-
function createSchemaBuilder(typeMap, zodChains, enumMap) {
|
|
284
|
+
function createSchemaBuilder(typeMap, zodChains, enumMap, scalarBase, zodDefaults) {
|
|
242
285
|
const chainCache = /* @__PURE__ */ new Map();
|
|
243
286
|
function buildFieldSchema(model, field) {
|
|
244
287
|
const cacheKey = `${model}.${field}`;
|
|
@@ -251,7 +294,7 @@ function createSchemaBuilder(typeMap, zodChains, enumMap) {
|
|
|
251
294
|
const fieldMeta = modelFields[field];
|
|
252
295
|
if (!fieldMeta)
|
|
253
296
|
throw new ShapeError(`Unknown field "${field}" on model "${model}"`);
|
|
254
|
-
const base = createBaseType(fieldMeta, enumMap);
|
|
297
|
+
const base = createBaseType(fieldMeta, enumMap, scalarBase);
|
|
255
298
|
let result = base;
|
|
256
299
|
const chainFn = zodChains[model]?.[field];
|
|
257
300
|
if (chainFn) {
|
|
@@ -274,17 +317,19 @@ function createSchemaBuilder(typeMap, zodChains, enumMap) {
|
|
|
274
317
|
const fieldMeta = modelFields[field];
|
|
275
318
|
if (!fieldMeta)
|
|
276
319
|
throw new ShapeError(`Unknown field "${field}" on model "${model}"`);
|
|
277
|
-
return createBaseType(fieldMeta, enumMap);
|
|
320
|
+
return createBaseType(fieldMeta, enumMap, scalarBase);
|
|
278
321
|
}
|
|
279
322
|
function buildInputSchema(model, opts) {
|
|
280
323
|
if (opts.pick && opts.omit) {
|
|
281
324
|
throw new ShapeError('InputOpts cannot define both "pick" and "omit"');
|
|
282
325
|
}
|
|
283
326
|
const mode = opts.mode ?? "create";
|
|
284
|
-
const allowNull = opts.allowNull ??
|
|
327
|
+
const allowNull = opts.allowNull ?? true;
|
|
285
328
|
const modelFields = typeMap[model];
|
|
286
329
|
if (!modelFields)
|
|
287
330
|
throw new ShapeError(`Unknown model: ${model}`);
|
|
331
|
+
const zodDefaultFields = zodDefaults[model];
|
|
332
|
+
const zodDefaultSet = zodDefaultFields ? new Set(zodDefaultFields) : void 0;
|
|
288
333
|
let fieldNames = Object.keys(modelFields).filter((name) => {
|
|
289
334
|
const meta = modelFields[name];
|
|
290
335
|
return !meta.isRelation && !meta.isUpdatedAt;
|
|
@@ -310,16 +355,39 @@ function createSchemaBuilder(typeMap, zodChains, enumMap) {
|
|
|
310
355
|
for (const name of fieldNames) {
|
|
311
356
|
const fieldMeta = modelFields[name];
|
|
312
357
|
let fieldSchema;
|
|
358
|
+
let handlesUndefined;
|
|
313
359
|
if (opts.refine?.[name]) {
|
|
314
|
-
|
|
360
|
+
let refined;
|
|
361
|
+
try {
|
|
362
|
+
refined = opts.refine[name](buildBaseFieldSchema(model, name));
|
|
363
|
+
} catch (err) {
|
|
364
|
+
throw new ShapeError(
|
|
365
|
+
`Refine function for "${model}.${name}" threw: ${err.message}`,
|
|
366
|
+
{ cause: err }
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
if (!isZodSchema(refined)) {
|
|
370
|
+
throw new ShapeError(
|
|
371
|
+
`Refine function for "${model}.${name}" must return a Zod schema`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
fieldSchema = refined;
|
|
375
|
+
handlesUndefined = schemaProducesValueForUndefined(fieldSchema);
|
|
315
376
|
} else {
|
|
316
377
|
fieldSchema = buildFieldSchema(model, name);
|
|
378
|
+
handlesUndefined = zodDefaultSet !== void 0 && zodDefaultSet.has(name);
|
|
317
379
|
}
|
|
318
380
|
if (mode === "create") {
|
|
319
381
|
if (!fieldMeta.isRequired) {
|
|
320
|
-
|
|
382
|
+
if (handlesUndefined) {
|
|
383
|
+
fieldSchema = allowNull ? fieldSchema.nullable() : fieldSchema;
|
|
384
|
+
} else {
|
|
385
|
+
fieldSchema = allowNull ? fieldSchema.nullable().optional() : fieldSchema.optional();
|
|
386
|
+
}
|
|
321
387
|
} else if (fieldMeta.hasDefault) {
|
|
322
|
-
|
|
388
|
+
if (!handlesUndefined) {
|
|
389
|
+
fieldSchema = fieldSchema.optional();
|
|
390
|
+
}
|
|
323
391
|
}
|
|
324
392
|
} else {
|
|
325
393
|
if (!fieldMeta.isRequired && allowNull) {
|
|
@@ -380,7 +448,7 @@ function createSchemaBuilder(typeMap, zodChains, enumMap) {
|
|
|
380
448
|
const schemaMap = {};
|
|
381
449
|
for (const name of scalarNames) {
|
|
382
450
|
const fieldMeta = modelFields[name];
|
|
383
|
-
let fieldSchema = createBaseType(fieldMeta, enumMap);
|
|
451
|
+
let fieldSchema = createBaseType(fieldMeta, enumMap, scalarBase);
|
|
384
452
|
if (!fieldMeta.isRequired) {
|
|
385
453
|
fieldSchema = fieldSchema.nullable();
|
|
386
454
|
}
|
|
@@ -410,10 +478,12 @@ function createSchemaBuilder(typeMap, zodChains, enumMap) {
|
|
|
410
478
|
schemaMap[relName] = relSchema;
|
|
411
479
|
}
|
|
412
480
|
if (opts._count) {
|
|
413
|
-
const
|
|
481
|
+
const listRelationNames = Object.keys(modelFields).filter(
|
|
482
|
+
(n) => modelFields[n].isRelation && modelFields[n].isList
|
|
483
|
+
);
|
|
414
484
|
if (opts._count === true) {
|
|
415
485
|
const countFields = {};
|
|
416
|
-
for (const relName of
|
|
486
|
+
for (const relName of listRelationNames) {
|
|
417
487
|
countFields[relName] = z3.number().int().min(0);
|
|
418
488
|
}
|
|
419
489
|
schemaMap["_count"] = z3.object(countFields);
|
|
@@ -424,6 +494,8 @@ function createSchemaBuilder(typeMap, zodChains, enumMap) {
|
|
|
424
494
|
throw new ShapeError(`Unknown field "${relName}" on model "${model}" in _count`);
|
|
425
495
|
if (!modelFields[relName].isRelation)
|
|
426
496
|
throw new ShapeError(`Field "${relName}" is not a relation on model "${model}" in _count`);
|
|
497
|
+
if (!modelFields[relName].isList)
|
|
498
|
+
throw new ShapeError(`Field "${relName}" is a to-one relation on model "${model}" in _count. Only to-many relations support _count.`);
|
|
427
499
|
countFields[relName] = z3.number().int().min(0);
|
|
428
500
|
}
|
|
429
501
|
schemaMap["_count"] = z3.object(countFields);
|
|
@@ -439,7 +511,7 @@ function createSchemaBuilder(typeMap, zodChains, enumMap) {
|
|
|
439
511
|
}
|
|
440
512
|
|
|
441
513
|
// src/runtime/query-builder.ts
|
|
442
|
-
import { z as
|
|
514
|
+
import { z as z7 } from "zod";
|
|
443
515
|
|
|
444
516
|
// src/shared/constants.ts
|
|
445
517
|
var SHAPE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -461,8 +533,26 @@ var SHAPE_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
461
533
|
]);
|
|
462
534
|
var GUARD_SHAPE_KEYS = /* @__PURE__ */ new Set([
|
|
463
535
|
"data",
|
|
536
|
+
"create",
|
|
537
|
+
"update",
|
|
464
538
|
...SHAPE_CONFIG_KEYS
|
|
465
539
|
]);
|
|
540
|
+
var COMBINATOR_KEYS = /* @__PURE__ */ new Set(["AND", "OR", "NOT"]);
|
|
541
|
+
var TO_MANY_RELATION_OPS = /* @__PURE__ */ new Set(["some", "every", "none"]);
|
|
542
|
+
var TO_ONE_RELATION_OPS = /* @__PURE__ */ new Set(["is", "isNot"]);
|
|
543
|
+
var ALL_RELATION_OPS = /* @__PURE__ */ new Set([...TO_MANY_RELATION_OPS, ...TO_ONE_RELATION_OPS]);
|
|
544
|
+
function toDelegateKey(modelName) {
|
|
545
|
+
return modelName[0].toLowerCase() + modelName.slice(1);
|
|
546
|
+
}
|
|
547
|
+
var FORCED_MARKER = Symbol.for("prisma-guard.forced");
|
|
548
|
+
function isForcedValue(v) {
|
|
549
|
+
return v !== null && typeof v === "object" && v[FORCED_MARKER] === true;
|
|
550
|
+
}
|
|
551
|
+
function force(value) {
|
|
552
|
+
const wrapper = { value };
|
|
553
|
+
wrapper[FORCED_MARKER] = true;
|
|
554
|
+
return wrapper;
|
|
555
|
+
}
|
|
466
556
|
|
|
467
557
|
// src/shared/match-caller.ts
|
|
468
558
|
function matchCallerPattern(patterns, caller) {
|
|
@@ -504,124 +594,124 @@ function requireContext(ctx, label) {
|
|
|
504
594
|
throw new PolicyError(`Context required for ${label}`);
|
|
505
595
|
}
|
|
506
596
|
}
|
|
597
|
+
function validateContext(ctx) {
|
|
598
|
+
if (ctx === null || ctx === void 0) {
|
|
599
|
+
throw new PolicyError(
|
|
600
|
+
`guard.extension() context function must return a plain object, got ${String(ctx)}`
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
if (!isPlainObject(ctx)) {
|
|
604
|
+
throw new PolicyError(
|
|
605
|
+
`guard.extension() context function must return a plain object, got ${Array.isArray(ctx) ? "array" : typeof ctx}`
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
return ctx;
|
|
609
|
+
}
|
|
507
610
|
|
|
508
|
-
// src/runtime/query-builder.ts
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
"
|
|
517
|
-
"
|
|
518
|
-
"
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
"
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
findUnique: /* @__PURE__ */ new Set(["where", "include", "select"]),
|
|
541
|
-
findUniqueOrThrow: /* @__PURE__ */ new Set(["where", "include", "select"]),
|
|
542
|
-
count: /* @__PURE__ */ new Set(["where", "select", "cursor", "orderBy", "skip", "take"]),
|
|
543
|
-
aggregate: /* @__PURE__ */ new Set([
|
|
544
|
-
"where",
|
|
545
|
-
"orderBy",
|
|
546
|
-
"cursor",
|
|
547
|
-
"take",
|
|
548
|
-
"skip",
|
|
549
|
-
"_count",
|
|
550
|
-
"_avg",
|
|
551
|
-
"_sum",
|
|
552
|
-
"_min",
|
|
553
|
-
"_max"
|
|
554
|
-
]),
|
|
555
|
-
groupBy: /* @__PURE__ */ new Set([
|
|
556
|
-
"where",
|
|
557
|
-
"by",
|
|
558
|
-
"having",
|
|
559
|
-
"_count",
|
|
560
|
-
"_avg",
|
|
561
|
-
"_sum",
|
|
562
|
-
"_min",
|
|
563
|
-
"_max",
|
|
564
|
-
"orderBy",
|
|
565
|
-
"take",
|
|
566
|
-
"skip"
|
|
567
|
-
])
|
|
568
|
-
};
|
|
569
|
-
var UNIQUE_WHERE_METHODS = /* @__PURE__ */ new Set([
|
|
570
|
-
"findUnique",
|
|
571
|
-
"findUniqueOrThrow"
|
|
572
|
-
]);
|
|
573
|
-
var STRING_MODE_OPS = /* @__PURE__ */ new Set([
|
|
574
|
-
"contains",
|
|
575
|
-
"startsWith",
|
|
576
|
-
"endsWith",
|
|
577
|
-
"equals"
|
|
578
|
-
]);
|
|
579
|
-
var UNSUPPORTED_WHERE_TYPES = /* @__PURE__ */ new Set(["Json", "Bytes"]);
|
|
580
|
-
var UNSUPPORTED_BY_TYPES = /* @__PURE__ */ new Set(["Json", "Bytes"]);
|
|
581
|
-
var KNOWN_NESTED_INCLUDE_KEYS = /* @__PURE__ */ new Set([
|
|
582
|
-
"where",
|
|
583
|
-
"include",
|
|
584
|
-
"select",
|
|
585
|
-
"orderBy",
|
|
586
|
-
"cursor",
|
|
587
|
-
"take",
|
|
588
|
-
"skip"
|
|
589
|
-
]);
|
|
590
|
-
var KNOWN_NESTED_SELECT_KEYS = /* @__PURE__ */ new Set([
|
|
591
|
-
"select",
|
|
592
|
-
"where",
|
|
593
|
-
"orderBy",
|
|
594
|
-
"cursor",
|
|
595
|
-
"take",
|
|
596
|
-
"skip"
|
|
597
|
-
]);
|
|
598
|
-
var KNOWN_COUNT_SELECT_ENTRY_KEYS = /* @__PURE__ */ new Set(["where"]);
|
|
599
|
-
function isPlainObject(v) {
|
|
600
|
-
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
601
|
-
return false;
|
|
602
|
-
const proto = Object.getPrototypeOf(v);
|
|
603
|
-
return proto === Object.prototype || proto === null;
|
|
611
|
+
// src/runtime/query-builder-where.ts
|
|
612
|
+
import { z as z4 } from "zod";
|
|
613
|
+
|
|
614
|
+
// src/shared/deep-clone.ts
|
|
615
|
+
function deepClone(value) {
|
|
616
|
+
if (value === null || value === void 0)
|
|
617
|
+
return value;
|
|
618
|
+
switch (typeof value) {
|
|
619
|
+
case "string":
|
|
620
|
+
case "number":
|
|
621
|
+
case "boolean":
|
|
622
|
+
case "bigint":
|
|
623
|
+
return value;
|
|
624
|
+
case "object": {
|
|
625
|
+
if (value instanceof Date)
|
|
626
|
+
return new Date(value.getTime());
|
|
627
|
+
if (value instanceof Uint8Array)
|
|
628
|
+
return value.slice();
|
|
629
|
+
if (Array.isArray(value))
|
|
630
|
+
return value.map(deepClone);
|
|
631
|
+
const proto = Object.getPrototypeOf(value);
|
|
632
|
+
if (proto !== Object.prototype && proto !== null)
|
|
633
|
+
return value;
|
|
634
|
+
const result = {};
|
|
635
|
+
for (const [k, v] of Object.entries(value)) {
|
|
636
|
+
result[k] = deepClone(v);
|
|
637
|
+
}
|
|
638
|
+
return result;
|
|
639
|
+
}
|
|
640
|
+
default:
|
|
641
|
+
return value;
|
|
642
|
+
}
|
|
604
643
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
644
|
+
|
|
645
|
+
// src/runtime/query-builder-forced.ts
|
|
646
|
+
var EMPTY_WHERE_FORCED = { conditions: {}, relations: {} };
|
|
647
|
+
function hasWhereForced(f) {
|
|
648
|
+
return Object.keys(f.conditions).length > 0 || Object.keys(f.relations).length > 0;
|
|
649
|
+
}
|
|
650
|
+
function mergeWhereForced(where, forced) {
|
|
651
|
+
if (!hasWhereForced(forced))
|
|
652
|
+
return where ?? {};
|
|
653
|
+
let result = where ? deepClone(where) : {};
|
|
654
|
+
for (const [relName, opMap] of Object.entries(forced.relations)) {
|
|
655
|
+
if (!result[relName] || typeof result[relName] !== "object") {
|
|
656
|
+
result[relName] = {};
|
|
657
|
+
}
|
|
658
|
+
const relObj = result[relName];
|
|
659
|
+
for (const [op, nestedForced] of Object.entries(opMap)) {
|
|
660
|
+
relObj[op] = mergeWhereForced(
|
|
661
|
+
relObj[op],
|
|
662
|
+
nestedForced
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (Object.keys(forced.conditions).length > 0) {
|
|
667
|
+
const scalarClone = deepClone(forced.conditions);
|
|
668
|
+
if (Object.keys(result).length === 0) {
|
|
669
|
+
result = scalarClone;
|
|
670
|
+
} else {
|
|
671
|
+
result = { AND: [result, scalarClone] };
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return result;
|
|
609
675
|
}
|
|
610
|
-
function
|
|
611
|
-
if (!
|
|
612
|
-
return
|
|
613
|
-
|
|
676
|
+
function mergeUniqueWhereForced(where, forced) {
|
|
677
|
+
if (!hasWhereForced(forced))
|
|
678
|
+
return where ?? {};
|
|
679
|
+
let result = where ? { ...where } : {};
|
|
680
|
+
for (const [relName, opMap] of Object.entries(forced.relations)) {
|
|
681
|
+
if (!result[relName] || typeof result[relName] !== "object") {
|
|
682
|
+
result[relName] = {};
|
|
683
|
+
}
|
|
684
|
+
const relObj = result[relName];
|
|
685
|
+
for (const [op, nestedForced] of Object.entries(opMap)) {
|
|
686
|
+
relObj[op] = mergeWhereForced(
|
|
687
|
+
relObj[op],
|
|
688
|
+
nestedForced
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (Object.keys(forced.conditions).length > 0) {
|
|
693
|
+
const conditions = [deepClone(forced.conditions)];
|
|
694
|
+
const existing = result.AND;
|
|
695
|
+
if (existing) {
|
|
696
|
+
if (Array.isArray(existing)) {
|
|
697
|
+
conditions.unshift(...existing);
|
|
698
|
+
} else {
|
|
699
|
+
conditions.unshift(existing);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
result.AND = conditions;
|
|
703
|
+
}
|
|
704
|
+
return result;
|
|
614
705
|
}
|
|
615
706
|
function applyBuiltShape(built, body, isUniqueMethod) {
|
|
616
707
|
const validated = built.zodSchema.parse(body);
|
|
617
|
-
if (
|
|
618
|
-
|
|
619
|
-
validated.where = isUniqueMethod ? mergeUniqueForced(
|
|
708
|
+
if (hasWhereForced(built.forcedWhere)) {
|
|
709
|
+
validated.where = isUniqueMethod ? mergeUniqueWhereForced(
|
|
620
710
|
validated.where,
|
|
621
|
-
|
|
622
|
-
) :
|
|
711
|
+
built.forcedWhere
|
|
712
|
+
) : mergeWhereForced(
|
|
623
713
|
validated.where,
|
|
624
|
-
|
|
714
|
+
built.forcedWhere
|
|
625
715
|
);
|
|
626
716
|
}
|
|
627
717
|
if (Object.keys(built.forcedIncludeTree).length > 0) {
|
|
@@ -631,19 +721,24 @@ function applyBuiltShape(built, body, isUniqueMethod) {
|
|
|
631
721
|
applyForcedTree(validated, "select", built.forcedSelectTree);
|
|
632
722
|
}
|
|
633
723
|
if (Object.keys(built.forcedIncludeCountWhere).length > 0) {
|
|
634
|
-
const
|
|
635
|
-
if (
|
|
636
|
-
applyForcedCountWhere(
|
|
637
|
-
}
|
|
724
|
+
const ic = validated.include;
|
|
725
|
+
if (ic)
|
|
726
|
+
applyForcedCountWhere(ic, built.forcedIncludeCountWhere);
|
|
638
727
|
}
|
|
639
728
|
if (Object.keys(built.forcedSelectCountWhere).length > 0) {
|
|
640
|
-
const
|
|
641
|
-
if (
|
|
642
|
-
applyForcedCountWhere(
|
|
643
|
-
}
|
|
729
|
+
const sc = validated.select;
|
|
730
|
+
if (sc)
|
|
731
|
+
applyForcedCountWhere(sc, built.forcedSelectCountWhere);
|
|
644
732
|
}
|
|
645
733
|
return validated;
|
|
646
734
|
}
|
|
735
|
+
function buildCountForPlacement(countWhere) {
|
|
736
|
+
const countSelect = {};
|
|
737
|
+
for (const [countRel, countForced] of Object.entries(countWhere)) {
|
|
738
|
+
countSelect[countRel] = { where: mergeWhereForced(void 0, countForced) };
|
|
739
|
+
}
|
|
740
|
+
return { _count: { select: countSelect } };
|
|
741
|
+
}
|
|
647
742
|
function applyForcedTree(validated, key, tree) {
|
|
648
743
|
const container = validated[key];
|
|
649
744
|
if (!container)
|
|
@@ -654,8 +749,9 @@ function applyForcedTree(validated, key, tree) {
|
|
|
654
749
|
continue;
|
|
655
750
|
if (relVal === true) {
|
|
656
751
|
const expanded = {};
|
|
657
|
-
if (forced.where)
|
|
658
|
-
expanded.where =
|
|
752
|
+
if (forced.where && hasWhereForced(forced.where)) {
|
|
753
|
+
expanded.where = mergeWhereForced(void 0, forced.where);
|
|
754
|
+
}
|
|
659
755
|
if (forced.include) {
|
|
660
756
|
expanded.include = buildForcedOnlyContainer(forced.include);
|
|
661
757
|
applyForcedTree(expanded, "include", forced.include);
|
|
@@ -665,13 +761,11 @@ function applyForcedTree(validated, key, tree) {
|
|
|
665
761
|
applyForcedTree(expanded, "select", forced.select);
|
|
666
762
|
}
|
|
667
763
|
if (forced._countWhere && Object.keys(forced._countWhere).length > 0) {
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
expanded._count = { select: countSelect };
|
|
764
|
+
const placement = forced._countWherePlacement ?? "include";
|
|
765
|
+
if (!expanded[placement])
|
|
766
|
+
expanded[placement] = {};
|
|
767
|
+
const placementObj = expanded[placement];
|
|
768
|
+
Object.assign(placementObj, buildCountForPlacement(forced._countWhere));
|
|
675
769
|
}
|
|
676
770
|
if (expanded.include && expanded.select) {
|
|
677
771
|
throw new ShapeError(
|
|
@@ -683,10 +777,10 @@ function applyForcedTree(validated, key, tree) {
|
|
|
683
777
|
}
|
|
684
778
|
if (isPlainObject(relVal)) {
|
|
685
779
|
const relObj = relVal;
|
|
686
|
-
if (forced.where) {
|
|
687
|
-
relObj.where =
|
|
780
|
+
if (forced.where && hasWhereForced(forced.where)) {
|
|
781
|
+
relObj.where = mergeWhereForced(
|
|
688
782
|
relObj.where,
|
|
689
|
-
|
|
783
|
+
forced.where
|
|
690
784
|
);
|
|
691
785
|
}
|
|
692
786
|
if (forced.include) {
|
|
@@ -700,7 +794,11 @@ function applyForcedTree(validated, key, tree) {
|
|
|
700
794
|
applyForcedTree(relObj, "select", forced.select);
|
|
701
795
|
}
|
|
702
796
|
if (forced._countWhere && Object.keys(forced._countWhere).length > 0) {
|
|
703
|
-
|
|
797
|
+
const placement = forced._countWherePlacement ?? "include";
|
|
798
|
+
const projContainer = relObj[placement];
|
|
799
|
+
if (projContainer) {
|
|
800
|
+
applyForcedCountWhere(projContainer, forced._countWhere);
|
|
801
|
+
}
|
|
704
802
|
}
|
|
705
803
|
if (relObj.include && relObj.select) {
|
|
706
804
|
throw new ShapeError(
|
|
@@ -714,20 +812,19 @@ function buildForcedOnlyContainer(tree) {
|
|
|
714
812
|
const result = {};
|
|
715
813
|
for (const [relName, forced] of Object.entries(tree)) {
|
|
716
814
|
const nested = {};
|
|
717
|
-
if (forced.where)
|
|
718
|
-
nested.where =
|
|
815
|
+
if (forced.where && hasWhereForced(forced.where)) {
|
|
816
|
+
nested.where = mergeWhereForced(void 0, forced.where);
|
|
817
|
+
}
|
|
719
818
|
if (forced.include)
|
|
720
819
|
nested.include = buildForcedOnlyContainer(forced.include);
|
|
721
820
|
if (forced.select)
|
|
722
821
|
nested.select = buildForcedOnlyContainer(forced.select);
|
|
723
822
|
if (forced._countWhere && Object.keys(forced._countWhere).length > 0) {
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
730
|
-
nested._count = { select: countSelect };
|
|
823
|
+
const placement = forced._countWherePlacement ?? "include";
|
|
824
|
+
if (!nested[placement])
|
|
825
|
+
nested[placement] = {};
|
|
826
|
+
const placementObj = nested[placement];
|
|
827
|
+
Object.assign(placementObj, buildCountForPlacement(forced._countWhere));
|
|
731
828
|
}
|
|
732
829
|
result[relName] = Object.keys(nested).length > 0 ? nested : true;
|
|
733
830
|
}
|
|
@@ -747,12 +844,12 @@ function applyForcedCountWhere(container, forcedCountWhere) {
|
|
|
747
844
|
if (relVal === void 0)
|
|
748
845
|
continue;
|
|
749
846
|
if (relVal === true) {
|
|
750
|
-
selectObj[relName] = { where:
|
|
847
|
+
selectObj[relName] = { where: mergeWhereForced(void 0, forced) };
|
|
751
848
|
} else if (isPlainObject(relVal)) {
|
|
752
849
|
const relObj = relVal;
|
|
753
|
-
relObj.where =
|
|
850
|
+
relObj.where = mergeWhereForced(
|
|
754
851
|
relObj.where,
|
|
755
|
-
|
|
852
|
+
forced
|
|
756
853
|
);
|
|
757
854
|
}
|
|
758
855
|
}
|
|
@@ -789,21 +886,38 @@ function validateResolvedUniqueWhere(model, where, method, uniqueMap) {
|
|
|
789
886
|
);
|
|
790
887
|
}
|
|
791
888
|
}
|
|
792
|
-
function
|
|
889
|
+
function collectEqualityFields(where, model, typeMap) {
|
|
890
|
+
const combinators = /* @__PURE__ */ new Set(["AND", "OR", "NOT"]);
|
|
891
|
+
const fields = /* @__PURE__ */ new Set();
|
|
892
|
+
for (const [key, value] of Object.entries(where)) {
|
|
893
|
+
if (key === "AND") {
|
|
894
|
+
if (isPlainObject(value)) {
|
|
895
|
+
for (const f of collectEqualityFields(value, model, typeMap)) {
|
|
896
|
+
fields.add(f);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
901
|
+
if (combinators.has(key))
|
|
902
|
+
continue;
|
|
903
|
+
if (typeMap && model && typeMap[model]?.[key]?.isRelation)
|
|
904
|
+
continue;
|
|
905
|
+
if (!value || !isPlainObject(value))
|
|
906
|
+
continue;
|
|
907
|
+
if (Object.keys(value).every((op) => op === "equals")) {
|
|
908
|
+
fields.add(key);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return fields;
|
|
912
|
+
}
|
|
913
|
+
function validateUniqueEquality(model, where, method, uniqueMap, typeMap) {
|
|
793
914
|
const constraints = uniqueMap[model];
|
|
794
915
|
if (!constraints || constraints.length === 0)
|
|
795
916
|
return;
|
|
796
|
-
const
|
|
797
|
-
const valid = constraints.some(
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
return constraint.every((field) => {
|
|
801
|
-
const ops = where[field];
|
|
802
|
-
if (!ops)
|
|
803
|
-
return false;
|
|
804
|
-
return Object.keys(ops).every((op) => op === "equals");
|
|
805
|
-
});
|
|
806
|
-
});
|
|
917
|
+
const equalityFields = collectEqualityFields(where, model, typeMap);
|
|
918
|
+
const valid = constraints.some(
|
|
919
|
+
(constraint) => constraint.every((field) => equalityFields.has(field))
|
|
920
|
+
);
|
|
807
921
|
if (!valid) {
|
|
808
922
|
const constraintDesc = constraints.map((c) => `(${c.join(", ")})`).join(" | ");
|
|
809
923
|
throw new ShapeError(
|
|
@@ -811,226 +925,430 @@ function validateUniqueEquality(model, where, method, uniqueMap) {
|
|
|
811
925
|
);
|
|
812
926
|
}
|
|
813
927
|
}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
928
|
+
|
|
929
|
+
// src/runtime/query-builder-where.ts
|
|
930
|
+
var UNSUPPORTED_WHERE_TYPES = /* @__PURE__ */ new Set(["Json", "Bytes"]);
|
|
931
|
+
var STRING_MODE_OPS = /* @__PURE__ */ new Set(["contains", "startsWith", "endsWith", "equals"]);
|
|
932
|
+
var MAX_WHERE_DEPTH = 10;
|
|
933
|
+
function safeStringify(v) {
|
|
934
|
+
if (typeof v === "bigint")
|
|
935
|
+
return `${v}n`;
|
|
936
|
+
try {
|
|
937
|
+
return JSON.stringify(v);
|
|
938
|
+
} catch {
|
|
939
|
+
return String(v);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
function forcedValuesEqual(a, b) {
|
|
943
|
+
if (a === b)
|
|
944
|
+
return true;
|
|
945
|
+
if (a === null || b === null)
|
|
946
|
+
return false;
|
|
947
|
+
if (typeof a !== typeof b)
|
|
948
|
+
return false;
|
|
949
|
+
if (typeof a === "bigint")
|
|
950
|
+
return a === b;
|
|
951
|
+
if (a instanceof Date && b instanceof Date)
|
|
952
|
+
return a.getTime() === b.getTime();
|
|
953
|
+
if (a instanceof RegExp && b instanceof RegExp) {
|
|
954
|
+
return a.source === b.source && a.flags === b.flags;
|
|
955
|
+
}
|
|
956
|
+
if (typeof a !== "object")
|
|
957
|
+
return false;
|
|
958
|
+
if (Array.isArray(a)) {
|
|
959
|
+
if (!Array.isArray(b))
|
|
960
|
+
return false;
|
|
961
|
+
if (a.length !== b.length)
|
|
962
|
+
return false;
|
|
963
|
+
return a.every((v, i) => forcedValuesEqual(v, b[i]));
|
|
964
|
+
}
|
|
965
|
+
if (!isPlainObject(a) || !isPlainObject(b))
|
|
966
|
+
return false;
|
|
967
|
+
const aKeys = Object.keys(a);
|
|
968
|
+
const bKeys = Object.keys(b);
|
|
969
|
+
if (aKeys.length !== bKeys.length)
|
|
970
|
+
return false;
|
|
971
|
+
return aKeys.every((k) => k in b && forcedValuesEqual(a[k], b[k]));
|
|
972
|
+
}
|
|
973
|
+
function mergeScalarConditions(target, source) {
|
|
974
|
+
for (const [field, ops] of Object.entries(source)) {
|
|
975
|
+
const existing = target[field];
|
|
976
|
+
if (existing === void 0) {
|
|
977
|
+
target[field] = ops;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
if (field === "NOT") {
|
|
981
|
+
const existingArr = Array.isArray(existing) ? existing : [existing];
|
|
982
|
+
const sourceArr = Array.isArray(ops) ? ops : [ops];
|
|
983
|
+
target[field] = [...existingArr, ...sourceArr];
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
if (isPlainObject(existing) && isPlainObject(ops)) {
|
|
987
|
+
for (const [op, val] of Object.entries(ops)) {
|
|
988
|
+
if (op in existing) {
|
|
989
|
+
const existingVal = existing[op];
|
|
990
|
+
if (!forcedValuesEqual(existingVal, val)) {
|
|
991
|
+
throw new ShapeError(
|
|
992
|
+
`Conflicting forced where values for "${field}.${op}": shape defines both ${safeStringify(existingVal)} and ${safeStringify(val)}`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
Object.assign(existing, ops);
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
if (!forcedValuesEqual(existing, ops)) {
|
|
817
1001
|
throw new ShapeError(
|
|
818
|
-
`
|
|
1002
|
+
`Conflicting forced where values for "${field}"`
|
|
819
1003
|
);
|
|
820
1004
|
}
|
|
821
1005
|
}
|
|
822
1006
|
}
|
|
823
|
-
function
|
|
824
|
-
|
|
825
|
-
if (!
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1007
|
+
function mergeRelationForcedMaps(target, source) {
|
|
1008
|
+
for (const [relName, ops] of Object.entries(source)) {
|
|
1009
|
+
if (!target[relName]) {
|
|
1010
|
+
target[relName] = ops;
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
for (const [op, forced] of Object.entries(ops)) {
|
|
1014
|
+
if (!target[relName][op]) {
|
|
1015
|
+
target[relName][op] = forced;
|
|
1016
|
+
} else {
|
|
1017
|
+
mergeScalarConditions(target[relName][op].conditions, forced.conditions);
|
|
1018
|
+
mergeRelationForcedMaps(target[relName][op].relations, forced.relations);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
829
1021
|
}
|
|
830
|
-
|
|
1022
|
+
}
|
|
1023
|
+
function createWhereBuilder(typeMap, enumMap, scalarBase) {
|
|
1024
|
+
function buildWhereSchema(model, whereConfig, depth) {
|
|
1025
|
+
const currentDepth = depth ?? 0;
|
|
1026
|
+
if (currentDepth > MAX_WHERE_DEPTH) {
|
|
1027
|
+
throw new ShapeError(
|
|
1028
|
+
`Where schema for model "${model}" exceeds maximum nesting depth (${MAX_WHERE_DEPTH}). Check for circular relation references in the shape.`
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
831
1031
|
const modelFields = typeMap[model];
|
|
832
1032
|
if (!modelFields)
|
|
833
1033
|
throw new ShapeError(`Unknown model: ${model}`);
|
|
834
1034
|
const fieldSchemas = {};
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
if (
|
|
839
|
-
|
|
840
|
-
|
|
1035
|
+
const scalarConditions = {};
|
|
1036
|
+
const relationForced = {};
|
|
1037
|
+
for (const [key, value] of Object.entries(whereConfig)) {
|
|
1038
|
+
if (COMBINATOR_KEYS.has(key)) {
|
|
1039
|
+
processCombinator(
|
|
1040
|
+
key,
|
|
1041
|
+
value,
|
|
1042
|
+
model,
|
|
1043
|
+
fieldSchemas,
|
|
1044
|
+
scalarConditions,
|
|
1045
|
+
relationForced,
|
|
1046
|
+
currentDepth
|
|
841
1047
|
);
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1048
|
+
continue;
|
|
1049
|
+
}
|
|
1050
|
+
const fieldMeta = modelFields[key];
|
|
1051
|
+
if (!fieldMeta)
|
|
1052
|
+
throw new ShapeError(`Unknown field "${key}" on model "${model}"`);
|
|
1053
|
+
if (fieldMeta.isRelation) {
|
|
1054
|
+
processRelationFilter(
|
|
1055
|
+
key,
|
|
1056
|
+
value,
|
|
1057
|
+
fieldMeta,
|
|
1058
|
+
model,
|
|
1059
|
+
fieldSchemas,
|
|
1060
|
+
relationForced,
|
|
1061
|
+
currentDepth
|
|
845
1062
|
);
|
|
1063
|
+
continue;
|
|
1064
|
+
}
|
|
846
1065
|
if (UNSUPPORTED_WHERE_TYPES.has(fieldMeta.type) && !fieldMeta.isList) {
|
|
847
1066
|
throw new ShapeError(
|
|
848
|
-
`${fieldMeta.type} field "${
|
|
1067
|
+
`${fieldMeta.type} field "${key}" cannot be used in where filters`
|
|
849
1068
|
);
|
|
850
1069
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
fieldMeta,
|
|
860
|
-
op,
|
|
861
|
-
enumMap
|
|
862
|
-
).optional();
|
|
863
|
-
hasClientOps = true;
|
|
864
|
-
clientOpKeys.push(op);
|
|
865
|
-
if (fieldMeta.type === "String" && !fieldMeta.isList && STRING_MODE_OPS.has(op)) {
|
|
866
|
-
hasStringModeOp = true;
|
|
867
|
-
}
|
|
868
|
-
} else {
|
|
869
|
-
const opSchema = createOperatorSchema(fieldMeta, op, enumMap);
|
|
870
|
-
let parsed;
|
|
871
|
-
try {
|
|
872
|
-
parsed = opSchema.parse(value);
|
|
873
|
-
} catch (err) {
|
|
874
|
-
throw new ShapeError(
|
|
875
|
-
`Invalid forced value for "${model}.${fieldName}.${op}": ${err.message}`
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
|
-
fieldForced[op] = parsed;
|
|
879
|
-
}
|
|
1070
|
+
processScalarField(key, value, model, fieldMeta, fieldSchemas, scalarConditions);
|
|
1071
|
+
}
|
|
1072
|
+
const schema = Object.keys(fieldSchemas).length > 0 ? z4.object(fieldSchemas).strict().optional() : null;
|
|
1073
|
+
return {
|
|
1074
|
+
schema,
|
|
1075
|
+
forced: {
|
|
1076
|
+
conditions: scalarConditions,
|
|
1077
|
+
relations: relationForced
|
|
880
1078
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
function processCombinator(key, value, model, fieldSchemas, parentConditions, parentRelations, depth) {
|
|
1082
|
+
if (!isPlainObject(value)) {
|
|
1083
|
+
throw new ShapeError(`"${key}" in where shape must be an object defining allowed fields`);
|
|
1084
|
+
}
|
|
1085
|
+
const result = buildWhereSchema(model, value, depth + 1);
|
|
1086
|
+
if (!result.schema && !hasWhereForced(result.forced)) {
|
|
1087
|
+
throw new ShapeError(
|
|
1088
|
+
`Empty "${key}" combinator in where shape for model "${model}". Define at least one field.`
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
if (result.schema) {
|
|
1092
|
+
let elementSchema = result.schema;
|
|
1093
|
+
if (!hasWhereForced(result.forced)) {
|
|
1094
|
+
elementSchema = result.schema.refine(
|
|
1095
|
+
(v) => v !== void 0 && v !== null && Object.keys(v).some((k) => v[k] !== void 0),
|
|
1096
|
+
{ message: `"${key}" member must specify at least one condition` }
|
|
884
1097
|
);
|
|
885
1098
|
}
|
|
886
|
-
if (
|
|
887
|
-
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
const opObj = z4.object(opSchemas).strict();
|
|
891
|
-
fieldSchemas[fieldName] = opObj.refine(
|
|
892
|
-
(v) => clientOpKeys.some(
|
|
893
|
-
(k) => v[k] !== void 0
|
|
894
|
-
),
|
|
895
|
-
{
|
|
896
|
-
message: `At least one operator required for where field "${fieldName}"`
|
|
897
|
-
}
|
|
898
|
-
).optional();
|
|
899
|
-
}
|
|
900
|
-
if (Object.keys(fieldForced).length > 0) {
|
|
901
|
-
forced[fieldName] = fieldForced;
|
|
1099
|
+
if (key === "NOT") {
|
|
1100
|
+
fieldSchemas[key] = z4.union([elementSchema, z4.array(elementSchema).min(1)]).optional();
|
|
1101
|
+
} else {
|
|
1102
|
+
fieldSchemas[key] = z4.array(elementSchema).min(1).optional();
|
|
902
1103
|
}
|
|
903
1104
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
if (!fieldMeta)
|
|
919
|
-
throw new ShapeError(
|
|
920
|
-
`Unknown field "${fieldName}" on model "${model}" in ${context}`
|
|
921
|
-
);
|
|
922
|
-
if (fieldMeta.isRelation)
|
|
923
|
-
throw new ShapeError(
|
|
924
|
-
`Relation field "${fieldName}" cannot be used in ${context}`
|
|
925
|
-
);
|
|
1105
|
+
if (hasWhereForced(result.forced)) {
|
|
1106
|
+
if (key === "AND" || key === "OR") {
|
|
1107
|
+
mergeScalarConditions(parentConditions, result.forced.conditions);
|
|
1108
|
+
mergeRelationForcedMaps(parentRelations, result.forced.relations);
|
|
1109
|
+
} else {
|
|
1110
|
+
const notWhere = mergeWhereForced(void 0, result.forced);
|
|
1111
|
+
if (Object.keys(notWhere).length > 0) {
|
|
1112
|
+
const existing = parentConditions[key];
|
|
1113
|
+
if (existing) {
|
|
1114
|
+
parentConditions[key] = Array.isArray(existing) ? [...existing, notWhere] : [existing, notWhere];
|
|
1115
|
+
} else {
|
|
1116
|
+
parentConditions[key] = notWhere;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
926
1119
|
}
|
|
927
|
-
fieldSchemas[fieldName] = z4.literal(true).optional();
|
|
928
1120
|
}
|
|
929
|
-
return z4.object(fieldSchemas).strict().optional();
|
|
930
1121
|
}
|
|
931
|
-
function
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
throw new ShapeError(`Unknown model: ${model}`);
|
|
935
|
-
if (config === true) {
|
|
936
|
-
return { schema: z4.literal(true).optional(), forcedCountWhere: {} };
|
|
1122
|
+
function processRelationFilter(key, value, fieldMeta, model, fieldSchemas, parentRelations, depth) {
|
|
1123
|
+
if (!isPlainObject(value)) {
|
|
1124
|
+
throw new ShapeError(`Relation filter for "${key}" must be an object with operators (some, every, none, is, isNot)`);
|
|
937
1125
|
}
|
|
938
|
-
|
|
1126
|
+
const allowedOps = fieldMeta.isList ? TO_MANY_RELATION_OPS : TO_ONE_RELATION_OPS;
|
|
1127
|
+
if (Object.keys(value).length === 0) {
|
|
939
1128
|
throw new ShapeError(
|
|
940
|
-
`
|
|
1129
|
+
`Empty relation filter for "${key}" on model "${model}". Define at least one operator: ${[...allowedOps].join(", ")}`
|
|
941
1130
|
);
|
|
942
1131
|
}
|
|
943
|
-
|
|
944
|
-
|
|
1132
|
+
const opSchemas = {};
|
|
1133
|
+
const opForced = {};
|
|
1134
|
+
let hasClientOps = false;
|
|
1135
|
+
for (const [op, opValue] of Object.entries(value)) {
|
|
1136
|
+
if (!allowedOps.has(op)) {
|
|
1137
|
+
const allowed = [...allowedOps].join(", ");
|
|
945
1138
|
throw new ShapeError(
|
|
946
|
-
`
|
|
1139
|
+
`Operator "${op}" not supported for ${fieldMeta.isList ? "to-many" : "to-one"} relation "${key}". Allowed: ${allowed}`
|
|
947
1140
|
);
|
|
948
1141
|
}
|
|
949
|
-
|
|
950
|
-
const selectObj = config.select;
|
|
951
|
-
const countSelectFields = {};
|
|
952
|
-
const forcedCountWhere = {};
|
|
953
|
-
for (const [fieldName, fieldConfig] of Object.entries(selectObj)) {
|
|
954
|
-
const fieldMeta = modelFields[fieldName];
|
|
955
|
-
if (!fieldMeta)
|
|
1142
|
+
if (!isPlainObject(opValue)) {
|
|
956
1143
|
throw new ShapeError(
|
|
957
|
-
`
|
|
1144
|
+
`Relation filter operator "${op}" on "${key}" must be an object defining nested where fields`
|
|
958
1145
|
);
|
|
959
|
-
|
|
1146
|
+
}
|
|
1147
|
+
const nested = buildWhereSchema(fieldMeta.type, opValue, depth + 1);
|
|
1148
|
+
if (!nested.schema && !hasWhereForced(nested.forced)) {
|
|
960
1149
|
throw new ShapeError(
|
|
961
|
-
`
|
|
962
|
-
);
|
|
963
|
-
if (fieldConfig === true) {
|
|
964
|
-
countSelectFields[fieldName] = z4.literal(true).optional();
|
|
965
|
-
} else if (isPlainObject(fieldConfig)) {
|
|
966
|
-
validateNestedKeys(
|
|
967
|
-
Object.keys(fieldConfig),
|
|
968
|
-
KNOWN_COUNT_SELECT_ENTRY_KEYS,
|
|
969
|
-
`_count.select.${fieldName} on model "${model}"`
|
|
1150
|
+
`Empty nested where for relation "${key}.${op}" on model "${model}". Define at least one field.`
|
|
970
1151
|
);
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1152
|
+
}
|
|
1153
|
+
if (nested.schema) {
|
|
1154
|
+
if (!hasWhereForced(nested.forced)) {
|
|
1155
|
+
opSchemas[op] = nested.schema.refine(
|
|
1156
|
+
(v) => v === void 0 || Object.keys(v).length > 0,
|
|
1157
|
+
{ message: `Relation filter "${key}.${op}" requires at least one condition` }
|
|
976
1158
|
);
|
|
977
|
-
const nestedSchemas = {};
|
|
978
|
-
if (whereSchema)
|
|
979
|
-
nestedSchemas["where"] = whereSchema;
|
|
980
|
-
const nestedObj = z4.object(nestedSchemas).strict();
|
|
981
|
-
countSelectFields[fieldName] = z4.union([z4.literal(true), nestedObj]).optional();
|
|
982
|
-
if (Object.keys(forced).length > 0) {
|
|
983
|
-
forcedCountWhere[fieldName] = forced;
|
|
984
|
-
}
|
|
985
1159
|
} else {
|
|
986
|
-
|
|
1160
|
+
opSchemas[op] = nested.schema;
|
|
987
1161
|
}
|
|
1162
|
+
hasClientOps = true;
|
|
1163
|
+
}
|
|
1164
|
+
if (hasWhereForced(nested.forced)) {
|
|
1165
|
+
opForced[op] = nested.forced;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
if (!hasClientOps && Object.keys(opForced).length === 0) {
|
|
1169
|
+
throw new ShapeError(
|
|
1170
|
+
`Relation filter for "${key}" on model "${model}" produced no conditions. Define at least one nested field in the operator shape.`
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
if (hasClientOps) {
|
|
1174
|
+
const clientOpKeys = Object.keys(opSchemas);
|
|
1175
|
+
const opObjSchema = z4.object(opSchemas).strict();
|
|
1176
|
+
if (Object.keys(opForced).length === 0) {
|
|
1177
|
+
fieldSchemas[key] = opObjSchema.refine(
|
|
1178
|
+
(v) => clientOpKeys.some((k) => v[k] !== void 0),
|
|
1179
|
+
{ message: `At least one relation operator required for "${key}"` }
|
|
1180
|
+
).optional();
|
|
988
1181
|
} else {
|
|
989
|
-
|
|
990
|
-
`Invalid config for _count.select.${fieldName} on model "${model}". Expected true or { where: { ... } }`
|
|
991
|
-
);
|
|
1182
|
+
fieldSchemas[key] = opObjSchema.optional();
|
|
992
1183
|
}
|
|
993
1184
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
forcedCountWhere
|
|
998
|
-
};
|
|
1185
|
+
if (Object.keys(opForced).length > 0) {
|
|
1186
|
+
parentRelations[key] = opForced;
|
|
1187
|
+
}
|
|
999
1188
|
}
|
|
1000
|
-
function
|
|
1189
|
+
function processScalarField(fieldName, operators, model, fieldMeta, fieldSchemas, scalarConditions) {
|
|
1190
|
+
if (!isPlainObject(operators)) {
|
|
1191
|
+
throw new ShapeError(
|
|
1192
|
+
`Where config for scalar field "${fieldName}" on model "${model}" must be an object of operators`
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
const opSchemas = {};
|
|
1196
|
+
const fieldForced = {};
|
|
1197
|
+
let hasClientOps = false;
|
|
1198
|
+
let hasStringModeOp = false;
|
|
1199
|
+
const clientOpKeys = [];
|
|
1200
|
+
for (const [op, opValue] of Object.entries(operators)) {
|
|
1201
|
+
if (opValue === true) {
|
|
1202
|
+
opSchemas[op] = createOperatorSchema(fieldMeta, op, enumMap, scalarBase).optional();
|
|
1203
|
+
hasClientOps = true;
|
|
1204
|
+
clientOpKeys.push(op);
|
|
1205
|
+
if (fieldMeta.type === "String" && !fieldMeta.isList && STRING_MODE_OPS.has(op)) {
|
|
1206
|
+
hasStringModeOp = true;
|
|
1207
|
+
}
|
|
1208
|
+
} else {
|
|
1209
|
+
const actualOpValue = isForcedValue(opValue) ? opValue.value : opValue;
|
|
1210
|
+
const opSchema = createOperatorSchema(fieldMeta, op, enumMap, scalarBase);
|
|
1211
|
+
let parsed;
|
|
1212
|
+
try {
|
|
1213
|
+
parsed = opSchema.parse(actualOpValue);
|
|
1214
|
+
} catch (err) {
|
|
1215
|
+
throw new ShapeError(
|
|
1216
|
+
`Invalid forced value for "${model}.${fieldName}.${op}": ${err.message}`
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
fieldForced[op] = parsed;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
if (!hasClientOps && Object.keys(fieldForced).length === 0) {
|
|
1223
|
+
throw new ShapeError(
|
|
1224
|
+
`Empty operator config for where field "${fieldName}" on model "${model}". Define at least one operator.`
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
if (hasStringModeOp) {
|
|
1228
|
+
opSchemas["mode"] = z4.enum(["default", "insensitive"]).optional();
|
|
1229
|
+
}
|
|
1230
|
+
if (hasClientOps) {
|
|
1231
|
+
const opObj = z4.object(opSchemas).strict();
|
|
1232
|
+
fieldSchemas[fieldName] = opObj.refine(
|
|
1233
|
+
(v) => clientOpKeys.some((k) => v[k] !== void 0),
|
|
1234
|
+
{ message: `At least one operator required for where field "${fieldName}"` }
|
|
1235
|
+
).optional();
|
|
1236
|
+
}
|
|
1237
|
+
if (Object.keys(fieldForced).length > 0) {
|
|
1238
|
+
scalarConditions[fieldName] = fieldForced;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
return { buildWhereSchema };
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// src/runtime/query-builder-args.ts
|
|
1245
|
+
import { z as z5 } from "zod";
|
|
1246
|
+
var UNSUPPORTED_BY_TYPES = /* @__PURE__ */ new Set(["Json", "Bytes"]);
|
|
1247
|
+
function requireConfigTrue(config, context) {
|
|
1248
|
+
for (const [key, value] of Object.entries(config)) {
|
|
1249
|
+
if (value !== true) {
|
|
1250
|
+
throw new ShapeError(
|
|
1251
|
+
`Config value for "${key}" in ${context} must be true, got ${typeof value}`
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
function createArgsBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
1257
|
+
function buildOrderBySchema(model, orderByConfig) {
|
|
1001
1258
|
const modelFields = typeMap[model];
|
|
1002
1259
|
if (!modelFields)
|
|
1003
1260
|
throw new ShapeError(`Unknown model: ${model}`);
|
|
1004
|
-
|
|
1005
|
-
const isComparableOnly = opName === "_min" || opName === "_max";
|
|
1261
|
+
requireConfigTrue(orderByConfig, `orderBy on model "${model}"`);
|
|
1006
1262
|
const fieldSchemas = {};
|
|
1007
|
-
for (const fieldName of Object.keys(
|
|
1008
|
-
if (fieldName === "_all" && opName === "_count") {
|
|
1009
|
-
fieldSchemas[fieldName] = z4.literal(true).optional();
|
|
1010
|
-
continue;
|
|
1011
|
-
}
|
|
1263
|
+
for (const fieldName of Object.keys(orderByConfig)) {
|
|
1012
1264
|
const fieldMeta = modelFields[fieldName];
|
|
1013
1265
|
if (!fieldMeta)
|
|
1014
|
-
throw new ShapeError(
|
|
1015
|
-
`Unknown field "${fieldName}" on model "${model}" in ${opName}`
|
|
1016
|
-
);
|
|
1266
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}"`);
|
|
1017
1267
|
if (fieldMeta.isRelation)
|
|
1018
|
-
throw new ShapeError(
|
|
1019
|
-
|
|
1020
|
-
);
|
|
1021
|
-
if (
|
|
1022
|
-
throw new ShapeError(
|
|
1023
|
-
|
|
1024
|
-
|
|
1268
|
+
throw new ShapeError(`Relation field "${fieldName}" cannot be used in orderBy`);
|
|
1269
|
+
if (fieldMeta.type === "Json")
|
|
1270
|
+
throw new ShapeError(`Json field "${fieldName}" cannot be used in orderBy`);
|
|
1271
|
+
if (fieldMeta.isList)
|
|
1272
|
+
throw new ShapeError(`List field "${fieldName}" cannot be used in orderBy`);
|
|
1273
|
+
fieldSchemas[fieldName] = z5.enum(["asc", "desc"]).optional();
|
|
1274
|
+
}
|
|
1275
|
+
const fieldKeys = Object.keys(fieldSchemas);
|
|
1276
|
+
const singleSchema = z5.object(fieldSchemas).strict().refine(
|
|
1277
|
+
(v) => fieldKeys.some((k) => v[k] !== void 0),
|
|
1278
|
+
{ message: "orderBy must specify at least one field" }
|
|
1279
|
+
);
|
|
1280
|
+
return z5.union([singleSchema, z5.array(singleSchema).min(1)]).optional();
|
|
1281
|
+
}
|
|
1282
|
+
function buildTakeSchema(config) {
|
|
1283
|
+
if (!Number.isFinite(config.max) || !Number.isInteger(config.max)) {
|
|
1284
|
+
throw new ShapeError(`take max must be a finite integer, got ${config.max}`);
|
|
1285
|
+
}
|
|
1286
|
+
if (config.max < 1) {
|
|
1287
|
+
throw new ShapeError(`take max must be at least 1, got ${config.max}`);
|
|
1288
|
+
}
|
|
1289
|
+
if (config.default !== void 0) {
|
|
1290
|
+
if (!Number.isFinite(config.default) || !Number.isInteger(config.default)) {
|
|
1291
|
+
throw new ShapeError(`take default must be a finite integer, got ${config.default}`);
|
|
1025
1292
|
}
|
|
1026
|
-
if (
|
|
1293
|
+
if (config.default < 1) {
|
|
1294
|
+
throw new ShapeError(`take default must be at least 1, got ${config.default}`);
|
|
1295
|
+
}
|
|
1296
|
+
if (config.default > config.max) {
|
|
1297
|
+
throw new ShapeError("take default cannot exceed max");
|
|
1298
|
+
}
|
|
1299
|
+
return z5.number().int().min(1).max(config.max).default(config.default);
|
|
1300
|
+
}
|
|
1301
|
+
return z5.number().int().min(1).max(config.max).optional();
|
|
1302
|
+
}
|
|
1303
|
+
function buildCursorSchema(model, cursorConfig) {
|
|
1304
|
+
const modelFields = typeMap[model];
|
|
1305
|
+
if (!modelFields)
|
|
1306
|
+
throw new ShapeError(`Unknown model: ${model}`);
|
|
1307
|
+
requireConfigTrue(cursorConfig, `cursor on model "${model}"`);
|
|
1308
|
+
const cursorFields = new Set(Object.keys(cursorConfig));
|
|
1309
|
+
const constraints = uniqueMap[model];
|
|
1310
|
+
if (constraints && constraints.length > 0) {
|
|
1311
|
+
const covered = constraints.some(
|
|
1312
|
+
(constraint) => constraint.length === cursorFields.size && constraint.every((field) => cursorFields.has(field))
|
|
1313
|
+
);
|
|
1314
|
+
if (!covered) {
|
|
1315
|
+
const constraintDesc = constraints.map((c) => `(${c.join(", ")})`).join(" | ");
|
|
1027
1316
|
throw new ShapeError(
|
|
1028
|
-
`
|
|
1317
|
+
`cursor on model "${model}" must exactly match a unique constraint: ${constraintDesc}`
|
|
1029
1318
|
);
|
|
1030
1319
|
}
|
|
1031
|
-
fieldSchemas[fieldName] = z4.literal(true).optional();
|
|
1032
1320
|
}
|
|
1033
|
-
|
|
1321
|
+
const fieldSchemas = {};
|
|
1322
|
+
for (const fieldName of Object.keys(cursorConfig)) {
|
|
1323
|
+
const fieldMeta = modelFields[fieldName];
|
|
1324
|
+
if (!fieldMeta)
|
|
1325
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}" in cursor`);
|
|
1326
|
+
if (fieldMeta.isRelation)
|
|
1327
|
+
throw new ShapeError(`Relation field "${fieldName}" cannot be used in cursor`);
|
|
1328
|
+
if (fieldMeta.isList)
|
|
1329
|
+
throw new ShapeError(`List field "${fieldName}" cannot be used in cursor`);
|
|
1330
|
+
fieldSchemas[fieldName] = createBaseType(fieldMeta, enumMap, scalarBase);
|
|
1331
|
+
}
|
|
1332
|
+
return z5.object(fieldSchemas).strict().optional();
|
|
1333
|
+
}
|
|
1334
|
+
function buildDistinctSchema(model, distinctConfig) {
|
|
1335
|
+
if (distinctConfig.length === 0) {
|
|
1336
|
+
throw new ShapeError("distinct must contain at least one field");
|
|
1337
|
+
}
|
|
1338
|
+
const modelFields = typeMap[model];
|
|
1339
|
+
if (!modelFields)
|
|
1340
|
+
throw new ShapeError(`Unknown model: ${model}`);
|
|
1341
|
+
for (const fieldName of distinctConfig) {
|
|
1342
|
+
const fieldMeta = modelFields[fieldName];
|
|
1343
|
+
if (!fieldMeta)
|
|
1344
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}" in distinct`);
|
|
1345
|
+
if (fieldMeta.isRelation)
|
|
1346
|
+
throw new ShapeError(`Relation field "${fieldName}" cannot be used in distinct`);
|
|
1347
|
+
if (fieldMeta.isList)
|
|
1348
|
+
throw new ShapeError(`List field "${fieldName}" cannot be used in distinct`);
|
|
1349
|
+
}
|
|
1350
|
+
const enumSchema = z5.enum(distinctConfig);
|
|
1351
|
+
return z5.union([enumSchema, z5.array(enumSchema).min(1)]).optional();
|
|
1034
1352
|
}
|
|
1035
1353
|
function buildBySchema(model, byConfig) {
|
|
1036
1354
|
if (byConfig.length === 0) {
|
|
@@ -1042,84 +1360,285 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1042
1360
|
for (const fieldName of byConfig) {
|
|
1043
1361
|
const fieldMeta = modelFields[fieldName];
|
|
1044
1362
|
if (!fieldMeta)
|
|
1045
|
-
throw new ShapeError(
|
|
1046
|
-
`Unknown field "${fieldName}" on model "${model}" in by`
|
|
1047
|
-
);
|
|
1363
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}" in by`);
|
|
1048
1364
|
if (fieldMeta.isRelation)
|
|
1049
|
-
throw new ShapeError(
|
|
1050
|
-
`Relation field "${fieldName}" cannot be used in by`
|
|
1051
|
-
);
|
|
1365
|
+
throw new ShapeError(`Relation field "${fieldName}" cannot be used in by`);
|
|
1052
1366
|
if (UNSUPPORTED_BY_TYPES.has(fieldMeta.type)) {
|
|
1053
|
-
throw new ShapeError(
|
|
1054
|
-
`${fieldMeta.type} field "${fieldName}" cannot be used in by`
|
|
1055
|
-
);
|
|
1367
|
+
throw new ShapeError(`${fieldMeta.type} field "${fieldName}" cannot be used in by`);
|
|
1056
1368
|
}
|
|
1057
|
-
if (fieldMeta.isList)
|
|
1369
|
+
if (fieldMeta.isList)
|
|
1058
1370
|
throw new ShapeError(`List field "${fieldName}" cannot be used in by`);
|
|
1059
|
-
}
|
|
1060
1371
|
}
|
|
1061
|
-
const enumSchema =
|
|
1062
|
-
return
|
|
1372
|
+
const enumSchema = z5.enum(byConfig);
|
|
1373
|
+
return z5.union([enumSchema, z5.array(enumSchema).min(1)]);
|
|
1063
1374
|
}
|
|
1064
|
-
function
|
|
1375
|
+
function buildHavingSchema(model, havingConfig) {
|
|
1065
1376
|
const modelFields = typeMap[model];
|
|
1066
1377
|
if (!modelFields)
|
|
1067
1378
|
throw new ShapeError(`Unknown model: ${model}`);
|
|
1068
|
-
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
const
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
if (
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1379
|
+
requireConfigTrue(havingConfig, `having on model "${model}"`);
|
|
1380
|
+
const fieldSchemas = {};
|
|
1381
|
+
for (const fieldName of Object.keys(havingConfig)) {
|
|
1382
|
+
const fieldMeta = modelFields[fieldName];
|
|
1383
|
+
if (!fieldMeta)
|
|
1384
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}" in having`);
|
|
1385
|
+
if (fieldMeta.isRelation)
|
|
1386
|
+
throw new ShapeError(`Relation field "${fieldName}" cannot be used in having`);
|
|
1387
|
+
if (fieldMeta.isList)
|
|
1388
|
+
throw new ShapeError(`List field "${fieldName}" cannot be used in having`);
|
|
1389
|
+
const ops = getSupportedOperators(fieldMeta);
|
|
1390
|
+
if (ops.length === 0) {
|
|
1391
|
+
throw new ShapeError(`${fieldMeta.type} field "${fieldName}" cannot be used in having filters`);
|
|
1079
1392
|
}
|
|
1393
|
+
const opSchemas = {};
|
|
1394
|
+
const opKeys = [];
|
|
1395
|
+
for (const op of ops) {
|
|
1396
|
+
opSchemas[op] = createOperatorSchema(fieldMeta, op, enumMap, scalarBase).optional();
|
|
1397
|
+
opKeys.push(op);
|
|
1398
|
+
}
|
|
1399
|
+
if (fieldMeta.type === "String" && !fieldMeta.isList) {
|
|
1400
|
+
opSchemas["mode"] = z5.enum(["default", "insensitive"]).optional();
|
|
1401
|
+
}
|
|
1402
|
+
fieldSchemas[fieldName] = z5.object(opSchemas).strict().refine(
|
|
1403
|
+
(v) => opKeys.some((k) => v[k] !== void 0),
|
|
1404
|
+
{ message: `At least one operator required for having field "${fieldName}"` }
|
|
1405
|
+
).optional();
|
|
1080
1406
|
}
|
|
1407
|
+
const havingFieldKeys = Object.keys(fieldSchemas);
|
|
1408
|
+
return z5.object(fieldSchemas).strict().refine(
|
|
1409
|
+
(v) => havingFieldKeys.some((k) => v[k] !== void 0),
|
|
1410
|
+
{ message: "having must specify at least one field" }
|
|
1411
|
+
).optional();
|
|
1412
|
+
}
|
|
1413
|
+
function buildAggregateFieldSchema(model, opName, fieldConfig) {
|
|
1414
|
+
const modelFields = typeMap[model];
|
|
1415
|
+
if (!modelFields)
|
|
1416
|
+
throw new ShapeError(`Unknown model: ${model}`);
|
|
1417
|
+
requireConfigTrue(fieldConfig, `${opName} on model "${model}"`);
|
|
1418
|
+
const isNumericOnly = opName === "_avg" || opName === "_sum";
|
|
1419
|
+
const isComparableOnly = opName === "_min" || opName === "_max";
|
|
1081
1420
|
const fieldSchemas = {};
|
|
1082
|
-
for (const fieldName of Object.keys(
|
|
1421
|
+
for (const fieldName of Object.keys(fieldConfig)) {
|
|
1422
|
+
if (fieldName === "_all" && opName === "_count") {
|
|
1423
|
+
fieldSchemas[fieldName] = z5.literal(true).optional();
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1083
1426
|
const fieldMeta = modelFields[fieldName];
|
|
1084
1427
|
if (!fieldMeta)
|
|
1085
|
-
throw new ShapeError(
|
|
1086
|
-
`Unknown field "${fieldName}" on model "${model}" in cursor`
|
|
1087
|
-
);
|
|
1428
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}" in ${opName}`);
|
|
1088
1429
|
if (fieldMeta.isRelation)
|
|
1430
|
+
throw new ShapeError(`Relation field "${fieldName}" cannot be used in ${opName}`);
|
|
1431
|
+
if (fieldMeta.isList)
|
|
1432
|
+
throw new ShapeError(`List field "${fieldName}" cannot be used in ${opName}`);
|
|
1433
|
+
if (isNumericOnly && !NUMERIC_TYPES.has(fieldMeta.type)) {
|
|
1089
1434
|
throw new ShapeError(
|
|
1090
|
-
`
|
|
1435
|
+
`Field "${fieldName}" (${fieldMeta.type}) cannot be used in ${opName}. Only numeric types (Int, Float, Decimal, BigInt) are supported.`
|
|
1091
1436
|
);
|
|
1092
|
-
|
|
1437
|
+
}
|
|
1438
|
+
if (isComparableOnly && !COMPARABLE_TYPES.has(fieldMeta.type)) {
|
|
1093
1439
|
throw new ShapeError(
|
|
1094
|
-
`
|
|
1440
|
+
`Field "${fieldName}" (${fieldMeta.type}) cannot be used in ${opName}. Only comparable types (Int, Float, Decimal, BigInt, String, DateTime) are supported.`
|
|
1095
1441
|
);
|
|
1096
|
-
|
|
1442
|
+
}
|
|
1443
|
+
fieldSchemas[fieldName] = z5.literal(true).optional();
|
|
1097
1444
|
}
|
|
1098
|
-
|
|
1445
|
+
const aggFieldKeys = Object.keys(fieldSchemas);
|
|
1446
|
+
return z5.object(fieldSchemas).strict().refine(
|
|
1447
|
+
(v) => aggFieldKeys.some((k) => v[k] !== void 0),
|
|
1448
|
+
{ message: `${opName} must specify at least one field` }
|
|
1449
|
+
).optional();
|
|
1099
1450
|
}
|
|
1100
|
-
function
|
|
1451
|
+
function buildCountFieldSchema(model, config, context) {
|
|
1452
|
+
if (config === true) {
|
|
1453
|
+
return z5.literal(true).optional();
|
|
1454
|
+
}
|
|
1101
1455
|
const modelFields = typeMap[model];
|
|
1102
1456
|
if (!modelFields)
|
|
1103
1457
|
throw new ShapeError(`Unknown model: ${model}`);
|
|
1104
|
-
|
|
1458
|
+
requireConfigTrue(config, `${context} on model "${model}"`);
|
|
1459
|
+
const fieldSchemas = {};
|
|
1460
|
+
for (const fieldName of Object.keys(config)) {
|
|
1461
|
+
if (fieldName !== "_all") {
|
|
1462
|
+
const fieldMeta = modelFields[fieldName];
|
|
1463
|
+
if (!fieldMeta)
|
|
1464
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}" in ${context}`);
|
|
1465
|
+
if (fieldMeta.isRelation)
|
|
1466
|
+
throw new ShapeError(`Relation field "${fieldName}" cannot be used in ${context}`);
|
|
1467
|
+
}
|
|
1468
|
+
fieldSchemas[fieldName] = z5.literal(true).optional();
|
|
1469
|
+
}
|
|
1470
|
+
const countFieldKeys = Object.keys(fieldSchemas);
|
|
1471
|
+
return z5.object(fieldSchemas).strict().refine(
|
|
1472
|
+
(v) => countFieldKeys.some((k) => v[k] !== void 0),
|
|
1473
|
+
{ message: `${context} must specify at least one field` }
|
|
1474
|
+
).optional();
|
|
1475
|
+
}
|
|
1476
|
+
function buildCountSelectSchema(model, selectConfig) {
|
|
1477
|
+
const modelFields = typeMap[model];
|
|
1478
|
+
if (!modelFields)
|
|
1479
|
+
throw new ShapeError(`Unknown model: ${model}`);
|
|
1480
|
+
requireConfigTrue(selectConfig, `count select on model "${model}"`);
|
|
1481
|
+
const fieldSchemas = {};
|
|
1482
|
+
for (const fieldName of Object.keys(selectConfig)) {
|
|
1483
|
+
if (fieldName === "_all") {
|
|
1484
|
+
fieldSchemas["_all"] = z5.literal(true).optional();
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1105
1487
|
const fieldMeta = modelFields[fieldName];
|
|
1106
1488
|
if (!fieldMeta)
|
|
1107
|
-
throw new ShapeError(
|
|
1108
|
-
`Unknown field "${fieldName}" on model "${model}" in distinct`
|
|
1109
|
-
);
|
|
1489
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}" in count select`);
|
|
1110
1490
|
if (fieldMeta.isRelation)
|
|
1491
|
+
throw new ShapeError(`Relation field "${fieldName}" cannot be used in count select`);
|
|
1492
|
+
fieldSchemas[fieldName] = z5.literal(true).optional();
|
|
1493
|
+
}
|
|
1494
|
+
const countSelectKeys = Object.keys(fieldSchemas);
|
|
1495
|
+
return z5.object(fieldSchemas).strict().refine(
|
|
1496
|
+
(v) => countSelectKeys.some((k) => v[k] !== void 0),
|
|
1497
|
+
{ message: "count select must specify at least one field" }
|
|
1498
|
+
).optional();
|
|
1499
|
+
}
|
|
1500
|
+
return {
|
|
1501
|
+
buildOrderBySchema,
|
|
1502
|
+
buildTakeSchema,
|
|
1503
|
+
buildCursorSchema,
|
|
1504
|
+
buildDistinctSchema,
|
|
1505
|
+
buildBySchema,
|
|
1506
|
+
buildHavingSchema,
|
|
1507
|
+
buildAggregateFieldSchema,
|
|
1508
|
+
buildCountFieldSchema,
|
|
1509
|
+
buildCountSelectSchema
|
|
1510
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
// src/runtime/query-builder-projection.ts
|
|
1514
|
+
import { z as z6 } from "zod";
|
|
1515
|
+
var KNOWN_NESTED_INCLUDE_KEYS = /* @__PURE__ */ new Set([
|
|
1516
|
+
"where",
|
|
1517
|
+
"include",
|
|
1518
|
+
"select",
|
|
1519
|
+
"orderBy",
|
|
1520
|
+
"cursor",
|
|
1521
|
+
"take",
|
|
1522
|
+
"skip"
|
|
1523
|
+
]);
|
|
1524
|
+
var KNOWN_NESTED_SELECT_KEYS = /* @__PURE__ */ new Set([
|
|
1525
|
+
"select",
|
|
1526
|
+
"where",
|
|
1527
|
+
"orderBy",
|
|
1528
|
+
"cursor",
|
|
1529
|
+
"take",
|
|
1530
|
+
"skip"
|
|
1531
|
+
]);
|
|
1532
|
+
var KNOWN_COUNT_SELECT_ENTRY_KEYS = /* @__PURE__ */ new Set(["where"]);
|
|
1533
|
+
var MAX_PROJECTION_DEPTH = 10;
|
|
1534
|
+
function validateNestedKeys(keys, allowed, context) {
|
|
1535
|
+
for (const key of keys) {
|
|
1536
|
+
if (!allowed.has(key)) {
|
|
1537
|
+
throw new ShapeError(
|
|
1538
|
+
`Unknown key "${key}" in ${context}. Allowed: ${[...allowed].join(", ")}`
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
function createProjectionBuilder(typeMap, enumMap, deps) {
|
|
1544
|
+
function buildIncludeCountSchema(model, config) {
|
|
1545
|
+
const modelFields = typeMap[model];
|
|
1546
|
+
if (!modelFields)
|
|
1547
|
+
throw new ShapeError(`Unknown model: ${model}`);
|
|
1548
|
+
if (config === true) {
|
|
1549
|
+
return { schema: z6.literal(true).optional(), forcedCountWhere: {} };
|
|
1550
|
+
}
|
|
1551
|
+
if (!isPlainObject(config) || !("select" in config)) {
|
|
1552
|
+
throw new ShapeError(
|
|
1553
|
+
`Invalid _count config on model "${model}". Expected true or { select: { ... } }`
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
for (const key of Object.keys(config)) {
|
|
1557
|
+
if (key !== "select") {
|
|
1111
1558
|
throw new ShapeError(
|
|
1112
|
-
`
|
|
1559
|
+
`Unknown key "${key}" in _count config on model "${model}". Only "select" is allowed.`
|
|
1113
1560
|
);
|
|
1114
|
-
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
if (!isPlainObject(config.select)) {
|
|
1564
|
+
throw new ShapeError(
|
|
1565
|
+
`Invalid _count.select on model "${model}". Expected a plain object with relation field keys.`
|
|
1566
|
+
);
|
|
1567
|
+
}
|
|
1568
|
+
const selectObj = config.select;
|
|
1569
|
+
if (Object.keys(selectObj).length === 0) {
|
|
1570
|
+
throw new ShapeError(
|
|
1571
|
+
`Empty _count.select on model "${model}". Define at least one relation field.`
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
const countSelectFields = {};
|
|
1575
|
+
const forcedCountWhere = {};
|
|
1576
|
+
for (const [fieldName, fieldConfig] of Object.entries(selectObj)) {
|
|
1577
|
+
const fieldMeta = modelFields[fieldName];
|
|
1578
|
+
if (!fieldMeta)
|
|
1579
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}" in _count.select`);
|
|
1580
|
+
if (!fieldMeta.isRelation)
|
|
1581
|
+
throw new ShapeError(`Field "${fieldName}" is not a relation on model "${model}" in _count.select`);
|
|
1582
|
+
if (!fieldMeta.isList)
|
|
1583
|
+
throw new ShapeError(`Field "${fieldName}" is a to-one relation on model "${model}" in _count.select. Only to-many relations support _count.`);
|
|
1584
|
+
if (fieldConfig === true) {
|
|
1585
|
+
countSelectFields[fieldName] = z6.literal(true).optional();
|
|
1586
|
+
} else if (isPlainObject(fieldConfig)) {
|
|
1587
|
+
if (Object.keys(fieldConfig).length === 0) {
|
|
1588
|
+
throw new ShapeError(
|
|
1589
|
+
`Empty config for _count.select.${fieldName} on model "${model}". Use true or { where: { ... } }.`
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
validateNestedKeys(
|
|
1593
|
+
Object.keys(fieldConfig),
|
|
1594
|
+
KNOWN_COUNT_SELECT_ENTRY_KEYS,
|
|
1595
|
+
`_count.select.${fieldName} on model "${model}"`
|
|
1596
|
+
);
|
|
1597
|
+
if (fieldConfig.where) {
|
|
1598
|
+
const relatedType = fieldMeta.type;
|
|
1599
|
+
const { schema: whereSchema, forced } = deps.buildWhereSchema(
|
|
1600
|
+
relatedType,
|
|
1601
|
+
fieldConfig.where
|
|
1602
|
+
);
|
|
1603
|
+
const nestedSchemas = {};
|
|
1604
|
+
if (whereSchema)
|
|
1605
|
+
nestedSchemas["where"] = whereSchema;
|
|
1606
|
+
const nestedObj = z6.object(nestedSchemas).strict();
|
|
1607
|
+
countSelectFields[fieldName] = z6.union([z6.literal(true), nestedObj]).optional();
|
|
1608
|
+
if (hasWhereForced(forced)) {
|
|
1609
|
+
forcedCountWhere[fieldName] = forced;
|
|
1610
|
+
}
|
|
1611
|
+
} else {
|
|
1612
|
+
countSelectFields[fieldName] = z6.literal(true).optional();
|
|
1613
|
+
}
|
|
1614
|
+
} else {
|
|
1115
1615
|
throw new ShapeError(
|
|
1116
|
-
`
|
|
1616
|
+
`Invalid config for _count.select.${fieldName} on model "${model}". Expected true or { where: { ... } }`
|
|
1117
1617
|
);
|
|
1618
|
+
}
|
|
1118
1619
|
}
|
|
1119
|
-
const
|
|
1120
|
-
|
|
1620
|
+
const countSelectKeys = Object.keys(countSelectFields);
|
|
1621
|
+
const selectSchema = z6.object(countSelectFields).strict().refine(
|
|
1622
|
+
(v) => countSelectKeys.some((k) => v[k] !== void 0),
|
|
1623
|
+
{ message: "_count.select must specify at least one field" }
|
|
1624
|
+
);
|
|
1625
|
+
return {
|
|
1626
|
+
schema: z6.object({ select: selectSchema }).strict().optional(),
|
|
1627
|
+
forcedCountWhere
|
|
1628
|
+
};
|
|
1121
1629
|
}
|
|
1122
|
-
function buildIncludeSchema(model, includeConfig) {
|
|
1630
|
+
function buildIncludeSchema(model, includeConfig, depth) {
|
|
1631
|
+
const currentDepth = depth ?? 0;
|
|
1632
|
+
if (currentDepth > MAX_PROJECTION_DEPTH) {
|
|
1633
|
+
throw new ShapeError(
|
|
1634
|
+
`Include schema for model "${model}" exceeds maximum nesting depth (${MAX_PROJECTION_DEPTH}). Check for circular relation references in the shape.`
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
if (Object.keys(includeConfig).length === 0) {
|
|
1638
|
+
throw new ShapeError(
|
|
1639
|
+
`Empty include config on model "${model}". Define at least one relation.`
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1123
1642
|
const modelFields = typeMap[model];
|
|
1124
1643
|
if (!modelFields)
|
|
1125
1644
|
throw new ShapeError(`Unknown model: ${model}`);
|
|
@@ -1128,10 +1647,7 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1128
1647
|
let topLevelForcedCountWhere = {};
|
|
1129
1648
|
for (const [relName, config] of Object.entries(includeConfig)) {
|
|
1130
1649
|
if (relName === "_count") {
|
|
1131
|
-
const countResult = buildIncludeCountSchema(
|
|
1132
|
-
model,
|
|
1133
|
-
config
|
|
1134
|
-
);
|
|
1650
|
+
const countResult = buildIncludeCountSchema(model, config);
|
|
1135
1651
|
fieldSchemas["_count"] = countResult.schema;
|
|
1136
1652
|
topLevelForcedCountWhere = countResult.forcedCountWhere;
|
|
1137
1653
|
continue;
|
|
@@ -1140,11 +1656,9 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1140
1656
|
if (!fieldMeta)
|
|
1141
1657
|
throw new ShapeError(`Unknown field "${relName}" on model "${model}"`);
|
|
1142
1658
|
if (!fieldMeta.isRelation)
|
|
1143
|
-
throw new ShapeError(
|
|
1144
|
-
`Field "${relName}" is not a relation on model "${model}"`
|
|
1145
|
-
);
|
|
1659
|
+
throw new ShapeError(`Field "${relName}" is not a relation on model "${model}"`);
|
|
1146
1660
|
if (config === true) {
|
|
1147
|
-
fieldSchemas[relName] =
|
|
1661
|
+
fieldSchemas[relName] = z6.literal(true).optional();
|
|
1148
1662
|
} else {
|
|
1149
1663
|
validateNestedKeys(
|
|
1150
1664
|
Object.keys(config),
|
|
@@ -1152,9 +1666,7 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1152
1666
|
`nested include for "${relName}" on model "${model}"`
|
|
1153
1667
|
);
|
|
1154
1668
|
if (config.select && config.include) {
|
|
1155
|
-
throw new ShapeError(
|
|
1156
|
-
`Nested include for "${relName}" cannot define both "select" and "include".`
|
|
1157
|
-
);
|
|
1669
|
+
throw new ShapeError(`Nested include for "${relName}" cannot define both "select" and "include".`);
|
|
1158
1670
|
}
|
|
1159
1671
|
if (!fieldMeta.isList) {
|
|
1160
1672
|
if (config.where || config.orderBy || config.cursor || config.take || config.skip) {
|
|
@@ -1166,62 +1678,77 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1166
1678
|
const nestedSchemas = {};
|
|
1167
1679
|
const relForced = {};
|
|
1168
1680
|
if (config.where) {
|
|
1169
|
-
const { schema: whereSchema, forced } = buildWhereSchema(
|
|
1681
|
+
const { schema: whereSchema, forced } = deps.buildWhereSchema(
|
|
1170
1682
|
fieldMeta.type,
|
|
1171
1683
|
config.where
|
|
1172
1684
|
);
|
|
1173
1685
|
if (whereSchema)
|
|
1174
1686
|
nestedSchemas["where"] = whereSchema;
|
|
1175
|
-
if (
|
|
1687
|
+
if (hasWhereForced(forced))
|
|
1176
1688
|
relForced.where = forced;
|
|
1177
1689
|
}
|
|
1178
1690
|
if (config.include) {
|
|
1179
|
-
const nested = buildIncludeSchema(fieldMeta.type, config.include);
|
|
1691
|
+
const nested = buildIncludeSchema(fieldMeta.type, config.include, currentDepth + 1);
|
|
1180
1692
|
nestedSchemas["include"] = nested.schema;
|
|
1181
1693
|
if (Object.keys(nested.forcedTree).length > 0)
|
|
1182
1694
|
relForced.include = nested.forcedTree;
|
|
1183
|
-
if (Object.keys(nested.forcedCountWhere).length > 0)
|
|
1695
|
+
if (Object.keys(nested.forcedCountWhere).length > 0) {
|
|
1184
1696
|
relForced._countWhere = nested.forcedCountWhere;
|
|
1697
|
+
relForced._countWherePlacement = "include";
|
|
1698
|
+
}
|
|
1185
1699
|
}
|
|
1186
1700
|
if (config.select) {
|
|
1187
|
-
const nested = buildSelectSchema(fieldMeta.type, config.select);
|
|
1701
|
+
const nested = buildSelectSchema(fieldMeta.type, config.select, currentDepth + 1);
|
|
1188
1702
|
nestedSchemas["select"] = nested.schema;
|
|
1189
1703
|
if (Object.keys(nested.forcedTree).length > 0)
|
|
1190
1704
|
relForced.select = nested.forcedTree;
|
|
1191
|
-
if (Object.keys(nested.forcedCountWhere).length > 0)
|
|
1705
|
+
if (Object.keys(nested.forcedCountWhere).length > 0) {
|
|
1192
1706
|
relForced._countWhere = nested.forcedCountWhere;
|
|
1707
|
+
relForced._countWherePlacement = "select";
|
|
1708
|
+
}
|
|
1193
1709
|
}
|
|
1194
1710
|
if (config.orderBy) {
|
|
1195
|
-
nestedSchemas["orderBy"] = buildOrderBySchema(
|
|
1196
|
-
fieldMeta.type,
|
|
1197
|
-
config.orderBy
|
|
1198
|
-
);
|
|
1711
|
+
nestedSchemas["orderBy"] = deps.buildOrderBySchema(fieldMeta.type, config.orderBy);
|
|
1199
1712
|
}
|
|
1200
1713
|
if (config.cursor) {
|
|
1201
|
-
nestedSchemas["cursor"] = buildCursorSchema(
|
|
1202
|
-
fieldMeta.type,
|
|
1203
|
-
config.cursor
|
|
1204
|
-
);
|
|
1714
|
+
nestedSchemas["cursor"] = deps.buildCursorSchema(fieldMeta.type, config.cursor);
|
|
1205
1715
|
}
|
|
1206
1716
|
if (config.take) {
|
|
1207
|
-
nestedSchemas["take"] = buildTakeSchema(config.take);
|
|
1717
|
+
nestedSchemas["take"] = deps.buildTakeSchema(config.take);
|
|
1208
1718
|
}
|
|
1209
1719
|
if (config.skip) {
|
|
1210
|
-
nestedSchemas["skip"] =
|
|
1720
|
+
nestedSchemas["skip"] = z6.number().int().min(0).optional();
|
|
1211
1721
|
}
|
|
1212
|
-
const nestedObj =
|
|
1213
|
-
fieldSchemas[relName] =
|
|
1722
|
+
const nestedObj = z6.object(nestedSchemas).strict();
|
|
1723
|
+
fieldSchemas[relName] = z6.union([z6.literal(true), nestedObj]).optional();
|
|
1214
1724
|
if (Object.keys(relForced).length > 0)
|
|
1215
1725
|
forcedTree[relName] = relForced;
|
|
1216
1726
|
}
|
|
1217
1727
|
}
|
|
1728
|
+
const includeFieldKeys = Object.keys(fieldSchemas);
|
|
1729
|
+
const baseSchema = z6.object(fieldSchemas).strict();
|
|
1730
|
+
const schema = includeFieldKeys.length > 0 ? baseSchema.refine(
|
|
1731
|
+
(v) => includeFieldKeys.some((k) => v[k] !== void 0),
|
|
1732
|
+
{ message: "include must specify at least one field" }
|
|
1733
|
+
).optional() : baseSchema.optional();
|
|
1218
1734
|
return {
|
|
1219
|
-
schema
|
|
1735
|
+
schema,
|
|
1220
1736
|
forcedTree,
|
|
1221
1737
|
forcedCountWhere: topLevelForcedCountWhere
|
|
1222
1738
|
};
|
|
1223
1739
|
}
|
|
1224
|
-
function buildSelectSchema(model, selectConfig) {
|
|
1740
|
+
function buildSelectSchema(model, selectConfig, depth) {
|
|
1741
|
+
const currentDepth = depth ?? 0;
|
|
1742
|
+
if (currentDepth > MAX_PROJECTION_DEPTH) {
|
|
1743
|
+
throw new ShapeError(
|
|
1744
|
+
`Select schema for model "${model}" exceeds maximum nesting depth (${MAX_PROJECTION_DEPTH}). Check for circular relation references in the shape.`
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
if (Object.keys(selectConfig).length === 0) {
|
|
1748
|
+
throw new ShapeError(
|
|
1749
|
+
`Empty select config on model "${model}". Define at least one field.`
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1225
1752
|
const modelFields = typeMap[model];
|
|
1226
1753
|
if (!modelFields)
|
|
1227
1754
|
throw new ShapeError(`Unknown model: ${model}`);
|
|
@@ -1230,26 +1757,19 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1230
1757
|
let topLevelForcedCountWhere = {};
|
|
1231
1758
|
for (const [fieldName, config] of Object.entries(selectConfig)) {
|
|
1232
1759
|
if (fieldName === "_count") {
|
|
1233
|
-
const countResult = buildIncludeCountSchema(
|
|
1234
|
-
model,
|
|
1235
|
-
config
|
|
1236
|
-
);
|
|
1760
|
+
const countResult = buildIncludeCountSchema(model, config);
|
|
1237
1761
|
fieldSchemas["_count"] = countResult.schema;
|
|
1238
1762
|
topLevelForcedCountWhere = countResult.forcedCountWhere;
|
|
1239
1763
|
continue;
|
|
1240
1764
|
}
|
|
1241
1765
|
const fieldMeta = modelFields[fieldName];
|
|
1242
1766
|
if (!fieldMeta)
|
|
1243
|
-
throw new ShapeError(
|
|
1244
|
-
`Unknown field "${fieldName}" on model "${model}"`
|
|
1245
|
-
);
|
|
1767
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}"`);
|
|
1246
1768
|
if (config === true) {
|
|
1247
|
-
fieldSchemas[fieldName] =
|
|
1769
|
+
fieldSchemas[fieldName] = z6.literal(true).optional();
|
|
1248
1770
|
} else {
|
|
1249
1771
|
if (!fieldMeta.isRelation) {
|
|
1250
|
-
throw new ShapeError(
|
|
1251
|
-
`Nested select args only valid for relations, not scalar "${fieldName}"`
|
|
1252
|
-
);
|
|
1772
|
+
throw new ShapeError(`Nested select args only valid for relations, not scalar "${fieldName}" on model "${model}"`);
|
|
1253
1773
|
}
|
|
1254
1774
|
validateNestedKeys(
|
|
1255
1775
|
Object.keys(config),
|
|
@@ -1266,208 +1786,114 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1266
1786
|
const nestedSchemas = {};
|
|
1267
1787
|
const relForced = {};
|
|
1268
1788
|
if (config.select) {
|
|
1269
|
-
const nested = buildSelectSchema(fieldMeta.type, config.select);
|
|
1789
|
+
const nested = buildSelectSchema(fieldMeta.type, config.select, currentDepth + 1);
|
|
1270
1790
|
nestedSchemas["select"] = nested.schema;
|
|
1271
1791
|
if (Object.keys(nested.forcedTree).length > 0)
|
|
1272
1792
|
relForced.select = nested.forcedTree;
|
|
1273
|
-
if (Object.keys(nested.forcedCountWhere).length > 0)
|
|
1793
|
+
if (Object.keys(nested.forcedCountWhere).length > 0) {
|
|
1274
1794
|
relForced._countWhere = nested.forcedCountWhere;
|
|
1795
|
+
relForced._countWherePlacement = "select";
|
|
1796
|
+
}
|
|
1275
1797
|
}
|
|
1276
1798
|
if (config.where) {
|
|
1277
|
-
const { schema: whereSchema, forced } = buildWhereSchema(
|
|
1799
|
+
const { schema: whereSchema, forced } = deps.buildWhereSchema(
|
|
1278
1800
|
fieldMeta.type,
|
|
1279
1801
|
config.where
|
|
1280
1802
|
);
|
|
1281
1803
|
if (whereSchema)
|
|
1282
1804
|
nestedSchemas["where"] = whereSchema;
|
|
1283
|
-
if (
|
|
1805
|
+
if (hasWhereForced(forced))
|
|
1284
1806
|
relForced.where = forced;
|
|
1285
1807
|
}
|
|
1286
1808
|
if (config.orderBy) {
|
|
1287
|
-
nestedSchemas["orderBy"] = buildOrderBySchema(
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
);
|
|
1291
|
-
}
|
|
1292
|
-
if (config.
|
|
1293
|
-
nestedSchemas["
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
);
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
nestedSchemas["skip"] = z4.number().int().min(0).optional();
|
|
1303
|
-
}
|
|
1304
|
-
const nestedObj = z4.object(nestedSchemas).strict();
|
|
1305
|
-
fieldSchemas[fieldName] = z4.union([z4.literal(true), nestedObj]).optional();
|
|
1306
|
-
if (Object.keys(relForced).length > 0)
|
|
1307
|
-
forcedTree[fieldName] = relForced;
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
return {
|
|
1311
|
-
schema: z4.object(fieldSchemas).strict().optional(),
|
|
1312
|
-
forcedTree,
|
|
1313
|
-
forcedCountWhere: topLevelForcedCountWhere
|
|
1314
|
-
};
|
|
1315
|
-
}
|
|
1316
|
-
function buildOrderBySchema(model, orderByConfig) {
|
|
1317
|
-
const modelFields = typeMap[model];
|
|
1318
|
-
if (!modelFields)
|
|
1319
|
-
throw new ShapeError(`Unknown model: ${model}`);
|
|
1320
|
-
const fieldSchemas = {};
|
|
1321
|
-
for (const fieldName of Object.keys(orderByConfig)) {
|
|
1322
|
-
const fieldMeta = modelFields[fieldName];
|
|
1323
|
-
if (!fieldMeta)
|
|
1324
|
-
throw new ShapeError(
|
|
1325
|
-
`Unknown field "${fieldName}" on model "${model}"`
|
|
1326
|
-
);
|
|
1327
|
-
if (fieldMeta.isRelation)
|
|
1328
|
-
throw new ShapeError(
|
|
1329
|
-
`Relation field "${fieldName}" cannot be used in orderBy`
|
|
1330
|
-
);
|
|
1331
|
-
if (fieldMeta.type === "Json")
|
|
1332
|
-
throw new ShapeError(
|
|
1333
|
-
`Json field "${fieldName}" cannot be used in orderBy`
|
|
1334
|
-
);
|
|
1335
|
-
if (fieldMeta.isList)
|
|
1336
|
-
throw new ShapeError(
|
|
1337
|
-
`List field "${fieldName}" cannot be used in orderBy`
|
|
1338
|
-
);
|
|
1339
|
-
fieldSchemas[fieldName] = z4.enum(["asc", "desc"]).optional();
|
|
1340
|
-
}
|
|
1341
|
-
const singleSchema = z4.object(fieldSchemas).strict();
|
|
1342
|
-
return z4.union([singleSchema, z4.array(singleSchema)]).optional();
|
|
1343
|
-
}
|
|
1344
|
-
function buildTakeSchema(config) {
|
|
1345
|
-
if (!Number.isFinite(config.max) || !Number.isInteger(config.max)) {
|
|
1346
|
-
throw new ShapeError(
|
|
1347
|
-
`take max must be a finite integer, got ${config.max}`
|
|
1348
|
-
);
|
|
1349
|
-
}
|
|
1350
|
-
if (config.max < 1) {
|
|
1351
|
-
throw new ShapeError(`take max must be at least 1, got ${config.max}`);
|
|
1352
|
-
}
|
|
1353
|
-
if (config.default !== void 0) {
|
|
1354
|
-
if (!Number.isFinite(config.default) || !Number.isInteger(config.default)) {
|
|
1355
|
-
throw new ShapeError(
|
|
1356
|
-
`take default must be a finite integer, got ${config.default}`
|
|
1357
|
-
);
|
|
1358
|
-
}
|
|
1359
|
-
if (config.default < 1) {
|
|
1360
|
-
throw new ShapeError(
|
|
1361
|
-
`take default must be at least 1, got ${config.default}`
|
|
1362
|
-
);
|
|
1363
|
-
}
|
|
1364
|
-
if (config.default > config.max) {
|
|
1365
|
-
throw new ShapeError("take default cannot exceed max");
|
|
1366
|
-
}
|
|
1367
|
-
return z4.number().int().min(1).max(config.max).default(config.default);
|
|
1368
|
-
}
|
|
1369
|
-
return z4.number().int().min(1).max(config.max).optional();
|
|
1370
|
-
}
|
|
1371
|
-
function buildHavingSchema(model, havingConfig) {
|
|
1372
|
-
const modelFields = typeMap[model];
|
|
1373
|
-
if (!modelFields)
|
|
1374
|
-
throw new ShapeError(`Unknown model: ${model}`);
|
|
1375
|
-
const fieldSchemas = {};
|
|
1376
|
-
for (const fieldName of Object.keys(havingConfig)) {
|
|
1377
|
-
const fieldMeta = modelFields[fieldName];
|
|
1378
|
-
if (!fieldMeta)
|
|
1379
|
-
throw new ShapeError(
|
|
1380
|
-
`Unknown field "${fieldName}" on model "${model}" in having`
|
|
1381
|
-
);
|
|
1382
|
-
if (fieldMeta.isRelation)
|
|
1383
|
-
throw new ShapeError(
|
|
1384
|
-
`Relation field "${fieldName}" cannot be used in having`
|
|
1385
|
-
);
|
|
1386
|
-
if (fieldMeta.isList)
|
|
1387
|
-
throw new ShapeError(
|
|
1388
|
-
`List field "${fieldName}" cannot be used in having`
|
|
1389
|
-
);
|
|
1390
|
-
const ops = getSupportedOperators(fieldMeta);
|
|
1391
|
-
if (ops.length === 0) {
|
|
1392
|
-
throw new ShapeError(
|
|
1393
|
-
`${fieldMeta.type} field "${fieldName}" cannot be used in having filters`
|
|
1394
|
-
);
|
|
1395
|
-
}
|
|
1396
|
-
const opSchemas = {};
|
|
1397
|
-
const opKeys = [];
|
|
1398
|
-
for (const op of ops) {
|
|
1399
|
-
opSchemas[op] = createOperatorSchema(fieldMeta, op, enumMap).optional();
|
|
1400
|
-
opKeys.push(op);
|
|
1401
|
-
}
|
|
1402
|
-
if (fieldMeta.type === "String" && !fieldMeta.isList) {
|
|
1403
|
-
opSchemas["mode"] = z4.enum(["default", "insensitive"]).optional();
|
|
1404
|
-
}
|
|
1405
|
-
fieldSchemas[fieldName] = z4.object(opSchemas).strict().refine(
|
|
1406
|
-
(v) => opKeys.some((k) => v[k] !== void 0),
|
|
1407
|
-
{
|
|
1408
|
-
message: `At least one operator required for having field "${fieldName}"`
|
|
1409
|
-
}
|
|
1410
|
-
).optional();
|
|
1411
|
-
}
|
|
1412
|
-
return z4.object(fieldSchemas).strict().optional();
|
|
1413
|
-
}
|
|
1414
|
-
function buildCountSelectSchema(model, selectConfig) {
|
|
1415
|
-
const modelFields = typeMap[model];
|
|
1416
|
-
if (!modelFields)
|
|
1417
|
-
throw new ShapeError(`Unknown model: ${model}`);
|
|
1418
|
-
const fieldSchemas = {};
|
|
1419
|
-
for (const fieldName of Object.keys(selectConfig)) {
|
|
1420
|
-
if (fieldName === "_all") {
|
|
1421
|
-
fieldSchemas["_all"] = z4.literal(true).optional();
|
|
1422
|
-
continue;
|
|
1809
|
+
nestedSchemas["orderBy"] = deps.buildOrderBySchema(fieldMeta.type, config.orderBy);
|
|
1810
|
+
}
|
|
1811
|
+
if (config.cursor) {
|
|
1812
|
+
nestedSchemas["cursor"] = deps.buildCursorSchema(fieldMeta.type, config.cursor);
|
|
1813
|
+
}
|
|
1814
|
+
if (config.take) {
|
|
1815
|
+
nestedSchemas["take"] = deps.buildTakeSchema(config.take);
|
|
1816
|
+
}
|
|
1817
|
+
if (config.skip) {
|
|
1818
|
+
nestedSchemas["skip"] = z6.number().int().min(0).optional();
|
|
1819
|
+
}
|
|
1820
|
+
const nestedObj = z6.object(nestedSchemas).strict();
|
|
1821
|
+
fieldSchemas[fieldName] = z6.union([z6.literal(true), nestedObj]).optional();
|
|
1822
|
+
if (Object.keys(relForced).length > 0)
|
|
1823
|
+
forcedTree[fieldName] = relForced;
|
|
1423
1824
|
}
|
|
1424
|
-
const fieldMeta = modelFields[fieldName];
|
|
1425
|
-
if (!fieldMeta)
|
|
1426
|
-
throw new ShapeError(
|
|
1427
|
-
`Unknown field "${fieldName}" on model "${model}" in count select`
|
|
1428
|
-
);
|
|
1429
|
-
if (fieldMeta.isRelation)
|
|
1430
|
-
throw new ShapeError(
|
|
1431
|
-
`Relation field "${fieldName}" cannot be used in count select`
|
|
1432
|
-
);
|
|
1433
|
-
fieldSchemas[fieldName] = z4.literal(true).optional();
|
|
1434
1825
|
}
|
|
1435
|
-
|
|
1826
|
+
const selectFieldKeys = Object.keys(fieldSchemas);
|
|
1827
|
+
const baseSchema = z6.object(fieldSchemas).strict();
|
|
1828
|
+
const schema = selectFieldKeys.length > 0 ? baseSchema.refine(
|
|
1829
|
+
(v) => selectFieldKeys.some((k) => v[k] !== void 0),
|
|
1830
|
+
{ message: "select must specify at least one field" }
|
|
1831
|
+
).optional() : baseSchema.optional();
|
|
1832
|
+
return {
|
|
1833
|
+
schema,
|
|
1834
|
+
forcedTree,
|
|
1835
|
+
forcedCountWhere: topLevelForcedCountWhere
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
return { buildIncludeSchema, buildSelectSchema, buildIncludeCountSchema };
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
// src/runtime/query-builder.ts
|
|
1842
|
+
var METHOD_ALLOWED_ARGS = {
|
|
1843
|
+
findMany: /* @__PURE__ */ new Set(["where", "include", "select", "orderBy", "cursor", "take", "skip", "distinct"]),
|
|
1844
|
+
findFirst: /* @__PURE__ */ new Set(["where", "include", "select", "orderBy", "cursor", "take", "skip", "distinct"]),
|
|
1845
|
+
findFirstOrThrow: /* @__PURE__ */ new Set(["where", "include", "select", "orderBy", "cursor", "take", "skip", "distinct"]),
|
|
1846
|
+
findUnique: /* @__PURE__ */ new Set(["where", "include", "select"]),
|
|
1847
|
+
findUniqueOrThrow: /* @__PURE__ */ new Set(["where", "include", "select"]),
|
|
1848
|
+
count: /* @__PURE__ */ new Set(["where", "select", "cursor", "orderBy", "skip", "take"]),
|
|
1849
|
+
aggregate: /* @__PURE__ */ new Set(["where", "orderBy", "cursor", "take", "skip", "_count", "_avg", "_sum", "_min", "_max"]),
|
|
1850
|
+
groupBy: /* @__PURE__ */ new Set(["where", "by", "having", "_count", "_avg", "_sum", "_min", "_max", "orderBy", "take", "skip"])
|
|
1851
|
+
};
|
|
1852
|
+
var UNIQUE_WHERE_METHODS = /* @__PURE__ */ new Set(["findUnique", "findUniqueOrThrow"]);
|
|
1853
|
+
function createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase) {
|
|
1854
|
+
const whereBuilder = createWhereBuilder(typeMap, enumMap, scalarBase);
|
|
1855
|
+
const argsBuilder = createArgsBuilder(typeMap, enumMap, uniqueMap, scalarBase);
|
|
1856
|
+
const projectionBuilder = createProjectionBuilder(typeMap, enumMap, {
|
|
1857
|
+
buildWhereSchema: whereBuilder.buildWhereSchema,
|
|
1858
|
+
buildOrderBySchema: argsBuilder.buildOrderBySchema,
|
|
1859
|
+
buildCursorSchema: argsBuilder.buildCursorSchema,
|
|
1860
|
+
buildTakeSchema: argsBuilder.buildTakeSchema
|
|
1861
|
+
});
|
|
1862
|
+
function isShapeConfig(obj) {
|
|
1863
|
+
if (!isPlainObject(obj))
|
|
1864
|
+
return false;
|
|
1865
|
+
const keys = Object.keys(obj);
|
|
1866
|
+
return keys.length === 0 || keys.every((k) => SHAPE_CONFIG_KEYS.has(k));
|
|
1436
1867
|
}
|
|
1437
1868
|
function validateShapeArgs(method, shape) {
|
|
1438
1869
|
const allowed = METHOD_ALLOWED_ARGS[method];
|
|
1439
1870
|
for (const key of Object.keys(shape)) {
|
|
1440
|
-
if (!SHAPE_CONFIG_KEYS.has(key))
|
|
1871
|
+
if (!SHAPE_CONFIG_KEYS.has(key))
|
|
1441
1872
|
throw new ShapeError(`Unknown shape config key "${key}"`);
|
|
1442
|
-
|
|
1443
|
-
if (!allowed.has(key)) {
|
|
1873
|
+
if (!allowed.has(key))
|
|
1444
1874
|
throw new ShapeError(`Arg "${key}" not allowed for method "${method}"`);
|
|
1445
|
-
|
|
1875
|
+
}
|
|
1876
|
+
if (UNIQUE_WHERE_METHODS.has(method) && !shape.where) {
|
|
1877
|
+
throw new ShapeError(`${method} shape must define "where"`);
|
|
1446
1878
|
}
|
|
1447
1879
|
if (shape.include && shape.select) {
|
|
1448
|
-
throw new ShapeError(
|
|
1449
|
-
'Shape config cannot define both "include" and "select".'
|
|
1450
|
-
);
|
|
1880
|
+
throw new ShapeError('Shape config cannot define both "include" and "select".');
|
|
1451
1881
|
}
|
|
1452
|
-
if (method === "groupBy" && !shape.by)
|
|
1882
|
+
if (method === "groupBy" && !shape.by)
|
|
1453
1883
|
throw new ShapeError('groupBy shape must define "by"');
|
|
1454
|
-
}
|
|
1455
1884
|
if (method === "groupBy" && (shape.include || shape.select)) {
|
|
1456
1885
|
throw new ShapeError('groupBy does not support "include" or "select"');
|
|
1457
1886
|
}
|
|
1458
1887
|
if (method === "aggregate" && (shape.include || shape.select)) {
|
|
1459
1888
|
throw new ShapeError('aggregate does not support "include" or "select"');
|
|
1460
1889
|
}
|
|
1461
|
-
if (method === "count" && shape.include)
|
|
1890
|
+
if (method === "count" && shape.include)
|
|
1462
1891
|
throw new ShapeError('count does not support "include"');
|
|
1463
|
-
}
|
|
1464
1892
|
if (method === "groupBy" && shape.orderBy) {
|
|
1465
1893
|
const bySet = new Set(shape.by);
|
|
1466
1894
|
for (const fieldName of Object.keys(shape.orderBy)) {
|
|
1467
1895
|
if (!bySet.has(fieldName)) {
|
|
1468
|
-
throw new ShapeError(
|
|
1469
|
-
`orderBy field "${fieldName}" must be included in "by" for groupBy`
|
|
1470
|
-
);
|
|
1896
|
+
throw new ShapeError(`orderBy field "${fieldName}" must be included in "by" for groupBy`);
|
|
1471
1897
|
}
|
|
1472
1898
|
}
|
|
1473
1899
|
}
|
|
@@ -1475,9 +1901,7 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1475
1901
|
const bySet = new Set(shape.by);
|
|
1476
1902
|
for (const fieldName of Object.keys(shape.having)) {
|
|
1477
1903
|
if (!bySet.has(fieldName)) {
|
|
1478
|
-
throw new ShapeError(
|
|
1479
|
-
`having field "${fieldName}" must be included in "by" for groupBy`
|
|
1480
|
-
);
|
|
1904
|
+
throw new ShapeError(`having field "${fieldName}" must be included in "by" for groupBy`);
|
|
1481
1905
|
}
|
|
1482
1906
|
}
|
|
1483
1907
|
}
|
|
@@ -1487,97 +1911,80 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1487
1911
|
return;
|
|
1488
1912
|
if (!shape.where)
|
|
1489
1913
|
return;
|
|
1490
|
-
validateUniqueEquality(model, shape.where, method, uniqueMap);
|
|
1914
|
+
validateUniqueEquality(model, shape.where, method, uniqueMap, typeMap);
|
|
1915
|
+
}
|
|
1916
|
+
function resolveAndValidateShape(shapeOrFn, ctx) {
|
|
1917
|
+
if (typeof shapeOrFn === "function") {
|
|
1918
|
+
requireContext(ctx, "shape function");
|
|
1919
|
+
const result = shapeOrFn(ctx);
|
|
1920
|
+
if (!isPlainObject(result)) {
|
|
1921
|
+
throw new ShapeError("Dynamic shape function must return a plain object");
|
|
1922
|
+
}
|
|
1923
|
+
return result;
|
|
1924
|
+
}
|
|
1925
|
+
return shapeOrFn;
|
|
1491
1926
|
}
|
|
1492
1927
|
function buildShapeZodSchema(model, method, shape) {
|
|
1493
1928
|
validateShapeArgs(method, shape);
|
|
1494
1929
|
validateUniqueWhere(model, method, shape);
|
|
1495
1930
|
const schemaFields = {};
|
|
1496
|
-
let forcedWhere =
|
|
1931
|
+
let forcedWhere = EMPTY_WHERE_FORCED;
|
|
1497
1932
|
let forcedIncludeTree = {};
|
|
1498
1933
|
let forcedSelectTree = {};
|
|
1499
1934
|
let forcedIncludeCountWhere = {};
|
|
1500
1935
|
let forcedSelectCountWhere = {};
|
|
1501
1936
|
if (shape.where) {
|
|
1502
|
-
const { schema, forced } = buildWhereSchema(model, shape.where);
|
|
1937
|
+
const { schema, forced } = whereBuilder.buildWhereSchema(model, shape.where);
|
|
1503
1938
|
if (schema)
|
|
1504
1939
|
schemaFields["where"] = schema;
|
|
1505
1940
|
forcedWhere = forced;
|
|
1506
1941
|
}
|
|
1507
1942
|
if (shape.include) {
|
|
1508
|
-
const result = buildIncludeSchema(model, shape.include);
|
|
1943
|
+
const result = projectionBuilder.buildIncludeSchema(model, shape.include);
|
|
1509
1944
|
schemaFields["include"] = result.schema;
|
|
1510
1945
|
forcedIncludeTree = result.forcedTree;
|
|
1511
1946
|
forcedIncludeCountWhere = result.forcedCountWhere;
|
|
1512
1947
|
}
|
|
1513
1948
|
if (shape.select) {
|
|
1514
1949
|
if (method === "count") {
|
|
1515
|
-
schemaFields["select"] = buildCountSelectSchema(model, shape.select);
|
|
1950
|
+
schemaFields["select"] = argsBuilder.buildCountSelectSchema(model, shape.select);
|
|
1516
1951
|
} else {
|
|
1517
|
-
const result = buildSelectSchema(model, shape.select);
|
|
1952
|
+
const result = projectionBuilder.buildSelectSchema(model, shape.select);
|
|
1518
1953
|
schemaFields["select"] = result.schema;
|
|
1519
1954
|
forcedSelectTree = result.forcedTree;
|
|
1520
1955
|
forcedSelectCountWhere = result.forcedCountWhere;
|
|
1521
1956
|
}
|
|
1522
1957
|
}
|
|
1523
|
-
if (shape.orderBy)
|
|
1524
|
-
schemaFields["orderBy"] = buildOrderBySchema(model, shape.orderBy);
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
if (shape.
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
schemaFields["skip"] =
|
|
1534
|
-
}
|
|
1535
|
-
if (shape.distinct)
|
|
1536
|
-
schemaFields["distinct"] = buildDistinctSchema(model, shape.distinct);
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
if (shape.
|
|
1546
|
-
schemaFields["
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
);
|
|
1551
|
-
}
|
|
1552
|
-
if (shape._sum) {
|
|
1553
|
-
schemaFields["_sum"] = buildAggregateFieldSchema(
|
|
1554
|
-
model,
|
|
1555
|
-
"_sum",
|
|
1556
|
-
shape._sum
|
|
1557
|
-
);
|
|
1558
|
-
}
|
|
1559
|
-
if (shape._min) {
|
|
1560
|
-
schemaFields["_min"] = buildAggregateFieldSchema(
|
|
1561
|
-
model,
|
|
1562
|
-
"_min",
|
|
1563
|
-
shape._min
|
|
1564
|
-
);
|
|
1565
|
-
}
|
|
1566
|
-
if (shape._max) {
|
|
1567
|
-
schemaFields["_max"] = buildAggregateFieldSchema(
|
|
1568
|
-
model,
|
|
1569
|
-
"_max",
|
|
1570
|
-
shape._max
|
|
1571
|
-
);
|
|
1572
|
-
}
|
|
1573
|
-
if (shape.by) {
|
|
1574
|
-
schemaFields["by"] = buildBySchema(model, shape.by);
|
|
1575
|
-
}
|
|
1576
|
-
if (shape.having) {
|
|
1577
|
-
schemaFields["having"] = buildHavingSchema(model, shape.having);
|
|
1578
|
-
}
|
|
1958
|
+
if (shape.orderBy)
|
|
1959
|
+
schemaFields["orderBy"] = argsBuilder.buildOrderBySchema(model, shape.orderBy);
|
|
1960
|
+
if (shape.cursor)
|
|
1961
|
+
schemaFields["cursor"] = argsBuilder.buildCursorSchema(model, shape.cursor);
|
|
1962
|
+
if (shape.take)
|
|
1963
|
+
schemaFields["take"] = argsBuilder.buildTakeSchema(shape.take);
|
|
1964
|
+
if (shape.skip !== void 0) {
|
|
1965
|
+
if (shape.skip !== true) {
|
|
1966
|
+
throw new ShapeError('Shape config "skip" must be true');
|
|
1967
|
+
}
|
|
1968
|
+
schemaFields["skip"] = z7.number().int().min(0).optional();
|
|
1969
|
+
}
|
|
1970
|
+
if (shape.distinct)
|
|
1971
|
+
schemaFields["distinct"] = argsBuilder.buildDistinctSchema(model, shape.distinct);
|
|
1972
|
+
if (shape._count)
|
|
1973
|
+
schemaFields["_count"] = argsBuilder.buildCountFieldSchema(model, shape._count, "_count");
|
|
1974
|
+
if (shape._avg)
|
|
1975
|
+
schemaFields["_avg"] = argsBuilder.buildAggregateFieldSchema(model, "_avg", shape._avg);
|
|
1976
|
+
if (shape._sum)
|
|
1977
|
+
schemaFields["_sum"] = argsBuilder.buildAggregateFieldSchema(model, "_sum", shape._sum);
|
|
1978
|
+
if (shape._min)
|
|
1979
|
+
schemaFields["_min"] = argsBuilder.buildAggregateFieldSchema(model, "_min", shape._min);
|
|
1980
|
+
if (shape._max)
|
|
1981
|
+
schemaFields["_max"] = argsBuilder.buildAggregateFieldSchema(model, "_max", shape._max);
|
|
1982
|
+
if (shape.by)
|
|
1983
|
+
schemaFields["by"] = argsBuilder.buildBySchema(model, shape.by);
|
|
1984
|
+
if (shape.having)
|
|
1985
|
+
schemaFields["having"] = argsBuilder.buildHavingSchema(model, shape.having);
|
|
1579
1986
|
return {
|
|
1580
|
-
zodSchema:
|
|
1987
|
+
zodSchema: z7.object(schemaFields).strict(),
|
|
1581
1988
|
forcedWhere,
|
|
1582
1989
|
forcedIncludeTree,
|
|
1583
1990
|
forcedSelectTree,
|
|
@@ -1592,30 +1999,20 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1592
1999
|
return { key: matched, shape: shapes[matched] };
|
|
1593
2000
|
}
|
|
1594
2001
|
function buildQuerySchema(model, method, config) {
|
|
1595
|
-
const
|
|
2002
|
+
const isSingle = typeof config === "function" || isShapeConfig(config);
|
|
1596
2003
|
const builtCache = /* @__PURE__ */ new Map();
|
|
1597
|
-
if (
|
|
1598
|
-
|
|
1599
|
-
builtCache.set("_default", built);
|
|
2004
|
+
if (isSingle && typeof config !== "function") {
|
|
2005
|
+
builtCache.set("_default", buildShapeZodSchema(model, method, config));
|
|
1600
2006
|
}
|
|
1601
|
-
if (!
|
|
2007
|
+
if (!isSingle) {
|
|
1602
2008
|
for (const key of Object.keys(config)) {
|
|
1603
2009
|
if (SHAPE_CONFIG_KEYS.has(key)) {
|
|
1604
|
-
throw new ShapeError(
|
|
1605
|
-
`Caller key "${key}" collides with reserved shape config key. Rename the caller path.`
|
|
1606
|
-
);
|
|
2010
|
+
throw new ShapeError(`Caller key "${key}" collides with reserved shape config key. Rename the caller path.`);
|
|
1607
2011
|
}
|
|
1608
2012
|
}
|
|
1609
|
-
for (const [key, shapeOrFn] of Object.entries(
|
|
1610
|
-
config
|
|
1611
|
-
)) {
|
|
2013
|
+
for (const [key, shapeOrFn] of Object.entries(config)) {
|
|
1612
2014
|
if (typeof shapeOrFn !== "function") {
|
|
1613
|
-
|
|
1614
|
-
model,
|
|
1615
|
-
method,
|
|
1616
|
-
shapeOrFn
|
|
1617
|
-
);
|
|
1618
|
-
builtCache.set(key, built);
|
|
2015
|
+
builtCache.set(key, buildShapeZodSchema(model, method, shapeOrFn));
|
|
1619
2016
|
}
|
|
1620
2017
|
}
|
|
1621
2018
|
}
|
|
@@ -1625,57 +2022,54 @@ function createQueryBuilder(typeMap, enumMap, uniqueMap = {}) {
|
|
|
1625
2022
|
[...builtCache.entries()].map(([k, v]) => [k, v.zodSchema])
|
|
1626
2023
|
),
|
|
1627
2024
|
parse(body, opts) {
|
|
2025
|
+
const normalizedBody = body === void 0 || body === null ? {} : body;
|
|
1628
2026
|
let built;
|
|
1629
|
-
if (
|
|
2027
|
+
if (isSingle) {
|
|
1630
2028
|
if (typeof config === "function") {
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
built = buildShapeZodSchema(model, method, resolvedShape);
|
|
2029
|
+
const resolved = resolveAndValidateShape(config, opts?.ctx);
|
|
2030
|
+
built = buildShapeZodSchema(model, method, resolved);
|
|
1634
2031
|
} else {
|
|
1635
2032
|
built = builtCache.get("_default");
|
|
1636
2033
|
}
|
|
1637
2034
|
} else {
|
|
1638
|
-
if (!isPlainObject(
|
|
2035
|
+
if (!isPlainObject(normalizedBody))
|
|
1639
2036
|
throw new ShapeError("Request body must be an object");
|
|
2037
|
+
if ("caller" in normalizedBody) {
|
|
2038
|
+
throw new CallerError(
|
|
2039
|
+
"Pass caller via opts.caller, not in the request body."
|
|
2040
|
+
);
|
|
1640
2041
|
}
|
|
1641
|
-
const caller =
|
|
2042
|
+
const caller = opts?.caller;
|
|
1642
2043
|
if (typeof caller !== "string") {
|
|
1643
|
-
|
|
2044
|
+
const allowed = Object.keys(config);
|
|
2045
|
+
throw new CallerError(
|
|
2046
|
+
`Missing caller. This query uses named shape routing with keys: ${allowed.map((k) => `"${k}"`).join(", ")}. Provide caller via opts.caller.`
|
|
2047
|
+
);
|
|
1644
2048
|
}
|
|
1645
|
-
const matched = matchCaller(
|
|
1646
|
-
config,
|
|
1647
|
-
caller
|
|
1648
|
-
);
|
|
2049
|
+
const matched = matchCaller(config, caller);
|
|
1649
2050
|
if (!matched) {
|
|
1650
|
-
const allowed = Object.keys(
|
|
1651
|
-
config
|
|
1652
|
-
);
|
|
2051
|
+
const allowed = Object.keys(config);
|
|
1653
2052
|
throw new CallerError(
|
|
1654
2053
|
`Unknown caller: "${caller}". Allowed: ${allowed.map((k) => `"${k}"`).join(", ")}`
|
|
1655
2054
|
);
|
|
1656
2055
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
requireContext(opts?.ctx, "shape function");
|
|
1661
|
-
const resolvedShape = shapeOrFn(opts.ctx);
|
|
1662
|
-
built = buildShapeZodSchema(model, method, resolvedShape);
|
|
2056
|
+
if (typeof matched.shape === "function") {
|
|
2057
|
+
const resolved = resolveAndValidateShape(matched.shape, opts?.ctx);
|
|
2058
|
+
built = buildShapeZodSchema(model, method, resolved);
|
|
1663
2059
|
} else {
|
|
1664
|
-
built = builtCache.get(
|
|
2060
|
+
built = builtCache.get(matched.key);
|
|
1665
2061
|
}
|
|
1666
|
-
const { caller: _, ...rest } = body;
|
|
1667
|
-
body = rest;
|
|
1668
2062
|
}
|
|
1669
|
-
return applyBuiltShape(built,
|
|
2063
|
+
return applyBuiltShape(built, normalizedBody, isUnique);
|
|
1670
2064
|
}
|
|
1671
2065
|
};
|
|
1672
2066
|
}
|
|
1673
2067
|
return {
|
|
1674
2068
|
buildQuerySchema,
|
|
1675
2069
|
buildShapeZodSchema,
|
|
1676
|
-
buildWhereSchema,
|
|
1677
|
-
buildIncludeSchema,
|
|
1678
|
-
buildSelectSchema
|
|
2070
|
+
buildWhereSchema: whereBuilder.buildWhereSchema,
|
|
2071
|
+
buildIncludeSchema: projectionBuilder.buildIncludeSchema,
|
|
2072
|
+
buildSelectSchema: projectionBuilder.buildSelectSchema
|
|
1679
2073
|
};
|
|
1680
2074
|
}
|
|
1681
2075
|
|
|
@@ -1853,10 +2247,24 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
1853
2247
|
const overrides = Object.fromEntries(
|
|
1854
2248
|
presentScopes.map((s) => [s.fk, ctx[s.root]])
|
|
1855
2249
|
);
|
|
2250
|
+
const nextArgs = { ...args };
|
|
1856
2251
|
if (operation === "upsert") {
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
2252
|
+
nextArgs.where = buildScopedUniqueWhere(args.where, conditions);
|
|
2253
|
+
if (args.create !== void 0 && args.create !== null) {
|
|
2254
|
+
if (typeof args.create !== "object" || Array.isArray(args.create)) {
|
|
2255
|
+
throw new ShapeError(`upsert expects create to be an object`);
|
|
2256
|
+
}
|
|
2257
|
+
nextArgs.create = { ...args.create };
|
|
2258
|
+
enforceDataScope(nextArgs.create, scopes, overrides, log, model, operation, onScopeRelationWrite, "create");
|
|
2259
|
+
}
|
|
2260
|
+
if (args.update !== void 0 && args.update !== null) {
|
|
2261
|
+
if (typeof args.update !== "object" || Array.isArray(args.update)) {
|
|
2262
|
+
throw new ShapeError(`upsert expects update to be an object`);
|
|
2263
|
+
}
|
|
2264
|
+
nextArgs.update = { ...args.update };
|
|
2265
|
+
enforceDataScope(nextArgs.update, scopes, overrides, log, model, operation, onScopeRelationWrite, "mutate");
|
|
2266
|
+
}
|
|
2267
|
+
return query(nextArgs);
|
|
1860
2268
|
}
|
|
1861
2269
|
if (FIND_UNIQUE_OPS.has(operation)) {
|
|
1862
2270
|
if (findUniqueMode === "reject") {
|
|
@@ -1866,7 +2274,6 @@ function createScopeExtension(scopeMap, contextFn, guardConfig, logger) {
|
|
|
1866
2274
|
}
|
|
1867
2275
|
return handleFindUnique(args, query, conditions, scopes, operation, log);
|
|
1868
2276
|
}
|
|
1869
|
-
const nextArgs = { ...args };
|
|
1870
2277
|
if (READ_OPS.has(operation)) {
|
|
1871
2278
|
nextArgs.where = buildAndConditions(args.where, conditions);
|
|
1872
2279
|
return query(nextArgs);
|
|
@@ -1948,77 +2355,332 @@ async function handleFindUnique(args, query, conditions, scopes, operation, log)
|
|
|
1948
2355
|
}
|
|
1949
2356
|
}
|
|
1950
2357
|
}
|
|
1951
|
-
const result = await query(nextArgs);
|
|
1952
|
-
if (result === null)
|
|
1953
|
-
return result;
|
|
1954
|
-
if (typeof result !== "object" || result === null) {
|
|
1955
|
-
throw new ShapeError("findUnique result must be an object or null");
|
|
2358
|
+
const result = await query(nextArgs);
|
|
2359
|
+
if (result === null)
|
|
2360
|
+
return result;
|
|
2361
|
+
if (typeof result !== "object" || result === null) {
|
|
2362
|
+
throw new ShapeError("findUnique result must be an object or null");
|
|
2363
|
+
}
|
|
2364
|
+
const resultObj = result;
|
|
2365
|
+
let verifyObj = resultObj;
|
|
2366
|
+
const missingFks = pickMissingFksFromResult(resultObj, fks);
|
|
2367
|
+
if (missingFks.length > 0) {
|
|
2368
|
+
const where = args?.where;
|
|
2369
|
+
if (!where || typeof where !== "object" || Array.isArray(where)) {
|
|
2370
|
+
throw new PolicyError(
|
|
2371
|
+
`prisma-guard: Cannot verify scope \u2014 missing FK fields (${missingFks.join(", ")}) and findUnique args.where is not a valid object.`
|
|
2372
|
+
);
|
|
2373
|
+
}
|
|
2374
|
+
let verifyResult;
|
|
2375
|
+
try {
|
|
2376
|
+
verifyResult = await query({ where, select: buildFkSelect(fks) });
|
|
2377
|
+
} catch (err) {
|
|
2378
|
+
throw new PolicyError(
|
|
2379
|
+
`prisma-guard: Scope verification query failed for findUnique: ${err?.message ?? String(err)}`
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2382
|
+
if (verifyResult === null) {
|
|
2383
|
+
throw new PolicyError("prisma-guard: Scope verification query returned null for an existing findUnique result");
|
|
2384
|
+
}
|
|
2385
|
+
if (typeof verifyResult !== "object" || verifyResult === null) {
|
|
2386
|
+
throw new PolicyError("prisma-guard: Scope verification result must be an object");
|
|
2387
|
+
}
|
|
2388
|
+
verifyObj = verifyResult;
|
|
2389
|
+
}
|
|
2390
|
+
for (const condition of conditions) {
|
|
2391
|
+
const [fk, value] = Object.entries(condition)[0];
|
|
2392
|
+
if (!(fk in verifyObj)) {
|
|
2393
|
+
throw new PolicyError(
|
|
2394
|
+
`prisma-guard: Cannot verify scope on "${fk}" \u2014 field not present in verification result. Ensure FK fields are selectable.`
|
|
2395
|
+
);
|
|
2396
|
+
}
|
|
2397
|
+
if (!looseEqual(verifyObj[fk], value, log, fk)) {
|
|
2398
|
+
if (operation === "findUniqueOrThrow") {
|
|
2399
|
+
throw new PolicyError("Record not accessible in current scope");
|
|
2400
|
+
}
|
|
2401
|
+
return null;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
if (injectedFks.length > 0) {
|
|
2405
|
+
const cleaned = { ...resultObj };
|
|
2406
|
+
for (const fk of injectedFks) {
|
|
2407
|
+
delete cleaned[fk];
|
|
2408
|
+
}
|
|
2409
|
+
return cleaned;
|
|
2410
|
+
}
|
|
2411
|
+
return result;
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// src/runtime/model-guard.ts
|
|
2415
|
+
import { z as z9 } from "zod";
|
|
2416
|
+
|
|
2417
|
+
// src/runtime/model-guard-data.ts
|
|
2418
|
+
import { z as z8 } from "zod";
|
|
2419
|
+
var ALLOWED_BODY_KEYS_CREATE = /* @__PURE__ */ new Set(["data"]);
|
|
2420
|
+
var ALLOWED_BODY_KEYS_CREATE_PROJECTION = /* @__PURE__ */ new Set(["data", "select", "include"]);
|
|
2421
|
+
var ALLOWED_BODY_KEYS_CREATE_MANY = /* @__PURE__ */ new Set(["data", "skipDuplicates"]);
|
|
2422
|
+
var ALLOWED_BODY_KEYS_CREATE_MANY_PROJECTION = /* @__PURE__ */ new Set(["data", "select", "include", "skipDuplicates"]);
|
|
2423
|
+
var ALLOWED_BODY_KEYS_UPDATE = /* @__PURE__ */ new Set(["data", "where"]);
|
|
2424
|
+
var ALLOWED_BODY_KEYS_UPDATE_PROJECTION = /* @__PURE__ */ new Set(["data", "where", "select", "include"]);
|
|
2425
|
+
var ALLOWED_BODY_KEYS_DELETE = /* @__PURE__ */ new Set(["where"]);
|
|
2426
|
+
var ALLOWED_BODY_KEYS_DELETE_PROJECTION = /* @__PURE__ */ new Set(["where", "select", "include"]);
|
|
2427
|
+
var ALLOWED_BODY_KEYS_UPSERT = /* @__PURE__ */ new Set(["where", "create", "update", "select", "include"]);
|
|
2428
|
+
var VALID_SHAPE_KEYS_CREATE = /* @__PURE__ */ new Set(["data"]);
|
|
2429
|
+
var VALID_SHAPE_KEYS_CREATE_PROJECTION = /* @__PURE__ */ new Set(["data", "select", "include"]);
|
|
2430
|
+
var VALID_SHAPE_KEYS_UPDATE = /* @__PURE__ */ new Set(["data", "where"]);
|
|
2431
|
+
var VALID_SHAPE_KEYS_UPDATE_PROJECTION = /* @__PURE__ */ new Set(["data", "where", "select", "include"]);
|
|
2432
|
+
var VALID_SHAPE_KEYS_DELETE = /* @__PURE__ */ new Set(["where"]);
|
|
2433
|
+
var VALID_SHAPE_KEYS_DELETE_PROJECTION = /* @__PURE__ */ new Set(["where", "select", "include"]);
|
|
2434
|
+
var VALID_SHAPE_KEYS_UPSERT = /* @__PURE__ */ new Set(["where", "create", "update", "select", "include"]);
|
|
2435
|
+
function validateMutationBodyKeys(body, allowed, method) {
|
|
2436
|
+
for (const key of Object.keys(body)) {
|
|
2437
|
+
if (!allowed.has(key)) {
|
|
2438
|
+
throw new ShapeError(
|
|
2439
|
+
`Unexpected key "${key}" in ${method} body. Allowed keys: ${[...allowed].join(", ")}`
|
|
2440
|
+
);
|
|
2441
|
+
}
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
function validateMutationShapeKeys(shape, allowed, method) {
|
|
2445
|
+
for (const key of Object.keys(shape)) {
|
|
2446
|
+
if (!allowed.has(key)) {
|
|
2447
|
+
throw new ShapeError(
|
|
2448
|
+
`Shape key "${key}" not valid for ${method}. Allowed: ${[...allowed].join(", ")}`
|
|
2449
|
+
);
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
function validateCreateCompleteness(modelName, dataConfig, typeMap, scopeFks, zodDefaults) {
|
|
2454
|
+
const modelFields = typeMap[modelName];
|
|
2455
|
+
if (!modelFields)
|
|
2456
|
+
return;
|
|
2457
|
+
const zodDefaultFields = zodDefaults[modelName];
|
|
2458
|
+
const zodDefaultSet = zodDefaultFields ? new Set(zodDefaultFields) : void 0;
|
|
2459
|
+
for (const [fieldName, meta] of Object.entries(modelFields)) {
|
|
2460
|
+
if (meta.isRelation)
|
|
2461
|
+
continue;
|
|
2462
|
+
if (meta.isUpdatedAt)
|
|
2463
|
+
continue;
|
|
2464
|
+
if (meta.hasDefault)
|
|
2465
|
+
continue;
|
|
2466
|
+
if (!meta.isRequired)
|
|
2467
|
+
continue;
|
|
2468
|
+
if (fieldName in dataConfig)
|
|
2469
|
+
continue;
|
|
2470
|
+
if (scopeFks.has(fieldName))
|
|
2471
|
+
continue;
|
|
2472
|
+
if (zodDefaultSet && zodDefaultSet.has(fieldName))
|
|
2473
|
+
continue;
|
|
2474
|
+
throw new ShapeError(
|
|
2475
|
+
`Required field "${fieldName}" on model "${modelName}" is missing from create data shape, has no default, and is not a scope FK`
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
function buildDataSchema(model, dataConfig, mode, typeMap, schemaBuilder, zodDefaults) {
|
|
2480
|
+
const modelFields = typeMap[model];
|
|
2481
|
+
if (!modelFields)
|
|
2482
|
+
throw new ShapeError(`Unknown model: ${model}`);
|
|
2483
|
+
const zodDefaultFields = zodDefaults[model];
|
|
2484
|
+
const zodDefaultSet = zodDefaultFields ? new Set(zodDefaultFields) : void 0;
|
|
2485
|
+
const schemaMap = {};
|
|
2486
|
+
const forced = {};
|
|
2487
|
+
for (const [fieldName, value] of Object.entries(dataConfig)) {
|
|
2488
|
+
const fieldMeta = modelFields[fieldName];
|
|
2489
|
+
if (!fieldMeta)
|
|
2490
|
+
throw new ShapeError(`Unknown field "${fieldName}" on model "${model}"`);
|
|
2491
|
+
if (fieldMeta.isRelation)
|
|
2492
|
+
throw new ShapeError(`Relation field "${fieldName}" cannot be used in data shape`);
|
|
2493
|
+
if (fieldMeta.isUpdatedAt)
|
|
2494
|
+
throw new ShapeError(`updatedAt field "${fieldName}" cannot be used in data shape`);
|
|
2495
|
+
if (typeof value === "function") {
|
|
2496
|
+
let baseSchema = schemaBuilder.buildBaseFieldSchema(model, fieldName);
|
|
2497
|
+
let refined;
|
|
2498
|
+
try {
|
|
2499
|
+
refined = value(baseSchema);
|
|
2500
|
+
} catch (err) {
|
|
2501
|
+
throw new ShapeError(
|
|
2502
|
+
`Invalid inline refine for "${model}.${fieldName}": ${err.message}`,
|
|
2503
|
+
{ cause: err }
|
|
2504
|
+
);
|
|
2505
|
+
}
|
|
2506
|
+
if (!isZodSchema(refined)) {
|
|
2507
|
+
throw new ShapeError(`Inline refine for "${model}.${fieldName}" must return a Zod schema`);
|
|
2508
|
+
}
|
|
2509
|
+
let fieldSchema = refined;
|
|
2510
|
+
const handlesUndefined = schemaProducesValueForUndefined(fieldSchema);
|
|
2511
|
+
if (mode === "create") {
|
|
2512
|
+
if (!fieldMeta.isRequired) {
|
|
2513
|
+
fieldSchema = handlesUndefined ? fieldSchema.nullable() : fieldSchema.nullable().optional();
|
|
2514
|
+
} else if (fieldMeta.hasDefault) {
|
|
2515
|
+
if (!handlesUndefined) {
|
|
2516
|
+
fieldSchema = fieldSchema.optional();
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
} else {
|
|
2520
|
+
if (!fieldMeta.isRequired) {
|
|
2521
|
+
fieldSchema = fieldSchema.nullable().optional();
|
|
2522
|
+
} else {
|
|
2523
|
+
fieldSchema = fieldSchema.optional();
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
schemaMap[fieldName] = fieldSchema;
|
|
2527
|
+
} else if (value === true) {
|
|
2528
|
+
let fieldSchema = schemaBuilder.buildFieldSchema(model, fieldName);
|
|
2529
|
+
const isZodDefaultField = zodDefaultSet !== void 0 && zodDefaultSet.has(fieldName);
|
|
2530
|
+
if (mode === "create") {
|
|
2531
|
+
if (!fieldMeta.isRequired) {
|
|
2532
|
+
fieldSchema = isZodDefaultField ? fieldSchema.nullable() : fieldSchema.nullable().optional();
|
|
2533
|
+
} else if (fieldMeta.hasDefault) {
|
|
2534
|
+
if (!isZodDefaultField) {
|
|
2535
|
+
fieldSchema = fieldSchema.optional();
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
} else {
|
|
2539
|
+
if (!fieldMeta.isRequired) {
|
|
2540
|
+
fieldSchema = fieldSchema.nullable().optional();
|
|
2541
|
+
} else {
|
|
2542
|
+
fieldSchema = fieldSchema.optional();
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
schemaMap[fieldName] = fieldSchema;
|
|
2546
|
+
} else {
|
|
2547
|
+
const actualValue = isForcedValue(value) ? value.value : value;
|
|
2548
|
+
let fieldSchema = schemaBuilder.buildFieldSchema(model, fieldName);
|
|
2549
|
+
if (!fieldMeta.isRequired) {
|
|
2550
|
+
fieldSchema = fieldSchema.nullable();
|
|
2551
|
+
}
|
|
2552
|
+
let parsed;
|
|
2553
|
+
try {
|
|
2554
|
+
parsed = fieldSchema.parse(actualValue);
|
|
2555
|
+
} catch (err) {
|
|
2556
|
+
throw new ShapeError(
|
|
2557
|
+
`Invalid forced data value for "${model}.${fieldName}": ${err.message}`
|
|
2558
|
+
);
|
|
2559
|
+
}
|
|
2560
|
+
forced[fieldName] = parsed;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
if (mode === "create" && zodDefaultFields) {
|
|
2564
|
+
for (const fieldName of zodDefaultFields) {
|
|
2565
|
+
if (fieldName in dataConfig)
|
|
2566
|
+
continue;
|
|
2567
|
+
const fieldMeta = modelFields[fieldName];
|
|
2568
|
+
if (!fieldMeta)
|
|
2569
|
+
continue;
|
|
2570
|
+
if (fieldMeta.isRelation)
|
|
2571
|
+
continue;
|
|
2572
|
+
if (fieldMeta.isUpdatedAt)
|
|
2573
|
+
continue;
|
|
2574
|
+
const fieldSchema = schemaBuilder.buildFieldSchema(model, fieldName);
|
|
2575
|
+
const result = fieldSchema.safeParse(void 0);
|
|
2576
|
+
if (result.success && result.data !== void 0) {
|
|
2577
|
+
forced[fieldName] = result.data;
|
|
2578
|
+
} else {
|
|
2579
|
+
throw new ShapeError(
|
|
2580
|
+
`Field "${fieldName}" on model "${model}" has @zod default/catch but its schema does not produce a value for undefined input`
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
return {
|
|
2586
|
+
schema: z8.object(schemaMap).strict(),
|
|
2587
|
+
forced
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
function validateAndMergeData(bodyData, cached, method) {
|
|
2591
|
+
if (bodyData === void 0 || bodyData === null) {
|
|
2592
|
+
throw new ShapeError(`${method} requires "data" in request body`);
|
|
2593
|
+
}
|
|
2594
|
+
const validated = cached.schema.parse(bodyData);
|
|
2595
|
+
return { ...validated, ...deepClone(cached.forced) };
|
|
2596
|
+
}
|
|
2597
|
+
function hasDataRefines(dataConfig) {
|
|
2598
|
+
for (const value of Object.values(dataConfig)) {
|
|
2599
|
+
if (typeof value === "function")
|
|
2600
|
+
return true;
|
|
2601
|
+
}
|
|
2602
|
+
return false;
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
// src/runtime/model-guard-resolve.ts
|
|
2606
|
+
function isGuardShape(obj) {
|
|
2607
|
+
if (!isPlainObject(obj))
|
|
2608
|
+
return false;
|
|
2609
|
+
const keys = Object.keys(obj);
|
|
2610
|
+
return keys.length === 0 || keys.every((k) => GUARD_SHAPE_KEYS.has(k));
|
|
2611
|
+
}
|
|
2612
|
+
function isSingleShape(input) {
|
|
2613
|
+
return typeof input === "function" || isGuardShape(input);
|
|
2614
|
+
}
|
|
2615
|
+
function requireBody(body) {
|
|
2616
|
+
if (!isPlainObject(body))
|
|
2617
|
+
throw new ShapeError("Request body must be an object");
|
|
2618
|
+
return body;
|
|
2619
|
+
}
|
|
2620
|
+
function resolveDynamicShape(fn, contextFn) {
|
|
2621
|
+
const ctx = validateContext(contextFn());
|
|
2622
|
+
let result;
|
|
2623
|
+
try {
|
|
2624
|
+
result = fn(ctx);
|
|
2625
|
+
} catch (err) {
|
|
2626
|
+
throw new ShapeError(
|
|
2627
|
+
`Dynamic shape function threw: ${err.message}`,
|
|
2628
|
+
{ cause: err }
|
|
2629
|
+
);
|
|
2630
|
+
}
|
|
2631
|
+
if (!isPlainObject(result)) {
|
|
2632
|
+
throw new ShapeError("Dynamic shape function must return a plain object");
|
|
1956
2633
|
}
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
if (
|
|
1961
|
-
const
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
2634
|
+
return result;
|
|
2635
|
+
}
|
|
2636
|
+
function resolveShape(input, body, contextFn, caller) {
|
|
2637
|
+
if (isSingleShape(input)) {
|
|
2638
|
+
const wasDynamic2 = typeof input === "function";
|
|
2639
|
+
const shape2 = wasDynamic2 ? resolveDynamicShape(input, contextFn) : input;
|
|
2640
|
+
const parsed2 = body === void 0 || body === null ? {} : requireBody(body);
|
|
2641
|
+
return { shape: shape2, body: parsed2, matchedKey: "_default", wasDynamic: wasDynamic2 };
|
|
2642
|
+
}
|
|
2643
|
+
const namedMap = input;
|
|
2644
|
+
for (const key of Object.keys(namedMap)) {
|
|
2645
|
+
if (GUARD_SHAPE_KEYS.has(key)) {
|
|
2646
|
+
throw new ShapeError(
|
|
2647
|
+
`Caller key "${key}" collides with reserved shape config key. Rename the caller path.`
|
|
1965
2648
|
);
|
|
1966
2649
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
throw new PolicyError(
|
|
1972
|
-
`prisma-guard: Scope verification query failed for findUnique: ${err?.message ?? String(err)}`
|
|
2650
|
+
const val = namedMap[key];
|
|
2651
|
+
if (typeof val !== "function" && !isGuardShape(val)) {
|
|
2652
|
+
throw new ShapeError(
|
|
2653
|
+
`Named shape value for "${key}" must be a guard shape object or function`
|
|
1973
2654
|
);
|
|
1974
2655
|
}
|
|
1975
|
-
if (verifyResult === null) {
|
|
1976
|
-
throw new PolicyError("prisma-guard: Scope verification query returned null for an existing findUnique result");
|
|
1977
|
-
}
|
|
1978
|
-
if (typeof verifyResult !== "object" || verifyResult === null) {
|
|
1979
|
-
throw new PolicyError("prisma-guard: Scope verification result must be an object");
|
|
1980
|
-
}
|
|
1981
|
-
verifyObj = verifyResult;
|
|
1982
2656
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
);
|
|
1989
|
-
}
|
|
1990
|
-
if (!looseEqual(verifyObj[fk], value, log, fk)) {
|
|
1991
|
-
if (operation === "findUniqueOrThrow") {
|
|
1992
|
-
throw new PolicyError("Record not accessible in current scope");
|
|
1993
|
-
}
|
|
1994
|
-
return null;
|
|
1995
|
-
}
|
|
2657
|
+
const parsed = body === void 0 || body === null ? {} : requireBody(body);
|
|
2658
|
+
if ("caller" in parsed) {
|
|
2659
|
+
throw new CallerError(
|
|
2660
|
+
"Pass caller as second argument to .guard() or via context function, not in the request body."
|
|
2661
|
+
);
|
|
1996
2662
|
}
|
|
1997
|
-
if (
|
|
1998
|
-
const
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
return cleaned;
|
|
2663
|
+
if (typeof caller !== "string") {
|
|
2664
|
+
const patterns2 = Object.keys(namedMap);
|
|
2665
|
+
throw new CallerError(
|
|
2666
|
+
`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.`
|
|
2667
|
+
);
|
|
2003
2668
|
}
|
|
2004
|
-
|
|
2669
|
+
const patterns = Object.keys(namedMap);
|
|
2670
|
+
const matched = matchCallerPattern(patterns, caller);
|
|
2671
|
+
if (!matched) {
|
|
2672
|
+
throw new CallerError(
|
|
2673
|
+
`Unknown caller: "${caller}". Allowed: ${patterns.map((k) => `"${k}"`).join(", ")}`
|
|
2674
|
+
);
|
|
2675
|
+
}
|
|
2676
|
+
const shapeOrFn = namedMap[matched];
|
|
2677
|
+
const wasDynamic = typeof shapeOrFn === "function";
|
|
2678
|
+
const shape = wasDynamic ? resolveDynamicShape(shapeOrFn, contextFn) : shapeOrFn;
|
|
2679
|
+
return { shape, body: parsed, matchedKey: matched, wasDynamic };
|
|
2005
2680
|
}
|
|
2006
2681
|
|
|
2007
2682
|
// src/runtime/model-guard.ts
|
|
2008
|
-
|
|
2009
|
-
var ALLOWED_BODY_KEYS_CREATE = /* @__PURE__ */ new Set(["data"]);
|
|
2010
|
-
var ALLOWED_BODY_KEYS_CREATE_PROJECTION = /* @__PURE__ */ new Set(["data", "select", "include"]);
|
|
2011
|
-
var ALLOWED_BODY_KEYS_UPDATE = /* @__PURE__ */ new Set(["data", "where"]);
|
|
2012
|
-
var ALLOWED_BODY_KEYS_UPDATE_PROJECTION = /* @__PURE__ */ new Set(["data", "where", "select", "include"]);
|
|
2013
|
-
var ALLOWED_BODY_KEYS_DELETE = /* @__PURE__ */ new Set(["where"]);
|
|
2014
|
-
var ALLOWED_BODY_KEYS_DELETE_PROJECTION = /* @__PURE__ */ new Set(["where", "select", "include"]);
|
|
2015
|
-
var VALID_SHAPE_KEYS_CREATE = /* @__PURE__ */ new Set(["data"]);
|
|
2016
|
-
var VALID_SHAPE_KEYS_CREATE_PROJECTION = /* @__PURE__ */ new Set(["data", "select", "include"]);
|
|
2017
|
-
var VALID_SHAPE_KEYS_UPDATE = /* @__PURE__ */ new Set(["data", "where"]);
|
|
2018
|
-
var VALID_SHAPE_KEYS_UPDATE_PROJECTION = /* @__PURE__ */ new Set(["data", "where", "select", "include"]);
|
|
2019
|
-
var VALID_SHAPE_KEYS_DELETE = /* @__PURE__ */ new Set(["where"]);
|
|
2020
|
-
var VALID_SHAPE_KEYS_DELETE_PROJECTION = /* @__PURE__ */ new Set(["where", "select", "include"]);
|
|
2021
|
-
var UNIQUE_MUTATION_METHODS = /* @__PURE__ */ new Set(["update", "delete"]);
|
|
2683
|
+
var UNIQUE_MUTATION_METHODS = /* @__PURE__ */ new Set(["update", "delete", "upsert"]);
|
|
2022
2684
|
var UNIQUE_READ_METHODS = /* @__PURE__ */ new Set(["findUnique", "findUniqueOrThrow"]);
|
|
2023
2685
|
var BULK_MUTATION_METHODS = /* @__PURE__ */ new Set([
|
|
2024
2686
|
"updateMany",
|
|
@@ -2028,228 +2690,187 @@ var BULK_MUTATION_METHODS = /* @__PURE__ */ new Set([
|
|
|
2028
2690
|
var PROJECTION_MUTATION_METHODS = /* @__PURE__ */ new Set([
|
|
2029
2691
|
"create",
|
|
2030
2692
|
"update",
|
|
2693
|
+
"upsert",
|
|
2031
2694
|
"delete",
|
|
2032
2695
|
"createManyAndReturn",
|
|
2033
2696
|
"updateManyAndReturn"
|
|
2034
2697
|
]);
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
}
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
if (
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2698
|
+
var BATCH_CREATE_METHODS = /* @__PURE__ */ new Set([
|
|
2699
|
+
"createMany",
|
|
2700
|
+
"createManyAndReturn"
|
|
2701
|
+
]);
|
|
2702
|
+
function buildDefaultSelectInput(config) {
|
|
2703
|
+
const result = {};
|
|
2704
|
+
for (const [key, value] of Object.entries(config)) {
|
|
2705
|
+
if (key === "_count") {
|
|
2706
|
+
result[key] = buildDefaultCountInput(value);
|
|
2707
|
+
continue;
|
|
2708
|
+
}
|
|
2709
|
+
if (value === true) {
|
|
2710
|
+
result[key] = true;
|
|
2711
|
+
} else {
|
|
2712
|
+
const nested = {};
|
|
2713
|
+
if (value.select)
|
|
2714
|
+
nested.select = buildDefaultSelectInput(value.select);
|
|
2715
|
+
result[key] = Object.keys(nested).length > 0 ? nested : true;
|
|
2050
2716
|
}
|
|
2051
2717
|
}
|
|
2718
|
+
return result;
|
|
2052
2719
|
}
|
|
2053
|
-
function
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2720
|
+
function buildDefaultIncludeInput(config) {
|
|
2721
|
+
const result = {};
|
|
2722
|
+
for (const [key, value] of Object.entries(config)) {
|
|
2723
|
+
if (key === "_count") {
|
|
2724
|
+
result[key] = buildDefaultCountInput(value);
|
|
2725
|
+
continue;
|
|
2726
|
+
}
|
|
2727
|
+
if (value === true) {
|
|
2728
|
+
result[key] = true;
|
|
2729
|
+
} else {
|
|
2730
|
+
const nested = {};
|
|
2731
|
+
if (value.include)
|
|
2732
|
+
nested.include = buildDefaultIncludeInput(value.include);
|
|
2733
|
+
if (value.select)
|
|
2734
|
+
nested.select = buildDefaultSelectInput(value.select);
|
|
2735
|
+
result[key] = Object.keys(nested).length > 0 ? nested : true;
|
|
2059
2736
|
}
|
|
2060
2737
|
}
|
|
2738
|
+
return result;
|
|
2061
2739
|
}
|
|
2062
|
-
function
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2740
|
+
function buildDefaultCountInput(config) {
|
|
2741
|
+
if (config === true)
|
|
2742
|
+
return true;
|
|
2743
|
+
if (!isPlainObject(config) || !config.select || !isPlainObject(config.select))
|
|
2744
|
+
return true;
|
|
2745
|
+
const selectObj = config.select;
|
|
2746
|
+
const result = {};
|
|
2747
|
+
for (const key of Object.keys(selectObj)) {
|
|
2748
|
+
result[key] = true;
|
|
2749
|
+
}
|
|
2750
|
+
return { select: result };
|
|
2067
2751
|
}
|
|
2068
|
-
function
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
const schemaBuilder = createSchemaBuilder(typeMap, zodChains, enumMap);
|
|
2072
|
-
const queryBuilder = createQueryBuilder(typeMap, enumMap, uniqueMap);
|
|
2073
|
-
const modelScopeFks = /* @__PURE__ */ new Map();
|
|
2074
|
-
for (const [model, entries] of Object.entries(scopeMap)) {
|
|
2075
|
-
const fks = /* @__PURE__ */ new Set();
|
|
2076
|
-
for (const entry of entries) {
|
|
2077
|
-
fks.add(entry.fk);
|
|
2078
|
-
}
|
|
2079
|
-
modelScopeFks.set(model, fks);
|
|
2752
|
+
function buildDefaultProjectionBody(shape) {
|
|
2753
|
+
if (shape.select) {
|
|
2754
|
+
return { select: buildDefaultSelectInput(shape.select) };
|
|
2080
2755
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
if (!modelFields)
|
|
2084
|
-
return;
|
|
2085
|
-
const fks = modelScopeFks.get(modelName) ?? /* @__PURE__ */ new Set();
|
|
2086
|
-
for (const [fieldName, meta] of Object.entries(modelFields)) {
|
|
2087
|
-
if (meta.isRelation)
|
|
2088
|
-
continue;
|
|
2089
|
-
if (meta.isUpdatedAt)
|
|
2090
|
-
continue;
|
|
2091
|
-
if (meta.hasDefault)
|
|
2092
|
-
continue;
|
|
2093
|
-
if (!meta.isRequired)
|
|
2094
|
-
continue;
|
|
2095
|
-
if (fieldName in dataConfig)
|
|
2096
|
-
continue;
|
|
2097
|
-
if (fks.has(fieldName))
|
|
2098
|
-
continue;
|
|
2099
|
-
throw new ShapeError(
|
|
2100
|
-
`Required field "${fieldName}" on model "${modelName}" is missing from create data shape, has no default, and is not a scope FK`
|
|
2101
|
-
);
|
|
2102
|
-
}
|
|
2756
|
+
if (shape.include) {
|
|
2757
|
+
return { include: buildDefaultIncludeInput(shape.include) };
|
|
2103
2758
|
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
);
|
|
2128
|
-
}
|
|
2129
|
-
if (mode === "create") {
|
|
2130
|
-
if (!fieldMeta.isRequired || fieldMeta.hasDefault) {
|
|
2131
|
-
fieldSchema = fieldSchema.optional();
|
|
2132
|
-
}
|
|
2133
|
-
} else {
|
|
2134
|
-
if (!fieldMeta.isRequired) {
|
|
2135
|
-
fieldSchema = fieldSchema.nullable().optional();
|
|
2136
|
-
} else {
|
|
2137
|
-
fieldSchema = fieldSchema.optional();
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
schemaMap[fieldName] = fieldSchema;
|
|
2141
|
-
} else if (value === true) {
|
|
2142
|
-
let fieldSchema = schemaBuilder.buildFieldSchema(model, fieldName);
|
|
2143
|
-
if (mode === "create") {
|
|
2144
|
-
if (!fieldMeta.isRequired || fieldMeta.hasDefault) {
|
|
2145
|
-
fieldSchema = fieldSchema.optional();
|
|
2146
|
-
}
|
|
2147
|
-
} else {
|
|
2148
|
-
if (!fieldMeta.isRequired) {
|
|
2149
|
-
fieldSchema = fieldSchema.nullable().optional();
|
|
2150
|
-
} else {
|
|
2151
|
-
fieldSchema = fieldSchema.optional();
|
|
2759
|
+
return {};
|
|
2760
|
+
}
|
|
2761
|
+
function hasClientControlledValues(obj) {
|
|
2762
|
+
for (const value of Object.values(obj)) {
|
|
2763
|
+
if (isForcedValue(value))
|
|
2764
|
+
continue;
|
|
2765
|
+
if (value === true)
|
|
2766
|
+
return true;
|
|
2767
|
+
if (isPlainObject(value) && hasClientControlledValues(value))
|
|
2768
|
+
return true;
|
|
2769
|
+
}
|
|
2770
|
+
return false;
|
|
2771
|
+
}
|
|
2772
|
+
function checkIncludeForClientArgs(config) {
|
|
2773
|
+
for (const [key, value] of Object.entries(config)) {
|
|
2774
|
+
if (key === "_count") {
|
|
2775
|
+
if (value !== true && isPlainObject(value) && isPlainObject(value.select)) {
|
|
2776
|
+
const selectObj = value.select;
|
|
2777
|
+
for (const entryVal of Object.values(selectObj)) {
|
|
2778
|
+
if (isPlainObject(entryVal) && entryVal.where) {
|
|
2779
|
+
const w = entryVal.where;
|
|
2780
|
+
if (isPlainObject(w) && hasClientControlledValues(w))
|
|
2781
|
+
return true;
|
|
2152
2782
|
}
|
|
2153
2783
|
}
|
|
2154
|
-
schemaMap[fieldName] = fieldSchema;
|
|
2155
|
-
} else {
|
|
2156
|
-
let fieldSchema = schemaBuilder.buildFieldSchema(model, fieldName);
|
|
2157
|
-
if (!fieldMeta.isRequired) {
|
|
2158
|
-
fieldSchema = fieldSchema.nullable();
|
|
2159
|
-
}
|
|
2160
|
-
let parsed;
|
|
2161
|
-
try {
|
|
2162
|
-
parsed = fieldSchema.parse(value);
|
|
2163
|
-
} catch (err) {
|
|
2164
|
-
throw new ShapeError(
|
|
2165
|
-
`Invalid forced data value for "${model}.${fieldName}": ${err.message}`
|
|
2166
|
-
);
|
|
2167
|
-
}
|
|
2168
|
-
forced[fieldName] = parsed;
|
|
2169
2784
|
}
|
|
2785
|
+
continue;
|
|
2170
2786
|
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
if (
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
if (typeof val !== "function" && !isGuardShape(val)) {
|
|
2197
|
-
throw new ShapeError(
|
|
2198
|
-
`Named shape value for "${key}" must be a guard shape object or function`
|
|
2199
|
-
);
|
|
2787
|
+
if (value === true)
|
|
2788
|
+
continue;
|
|
2789
|
+
if (value.orderBy || value.cursor || value.take || value.skip)
|
|
2790
|
+
return true;
|
|
2791
|
+
if (value.where && isPlainObject(value.where) && hasClientControlledValues(value.where))
|
|
2792
|
+
return true;
|
|
2793
|
+
if (value.include && checkIncludeForClientArgs(value.include))
|
|
2794
|
+
return true;
|
|
2795
|
+
if (value.select && checkSelectForClientArgs(value.select))
|
|
2796
|
+
return true;
|
|
2797
|
+
}
|
|
2798
|
+
return false;
|
|
2799
|
+
}
|
|
2800
|
+
function checkSelectForClientArgs(config) {
|
|
2801
|
+
for (const [key, value] of Object.entries(config)) {
|
|
2802
|
+
if (key === "_count") {
|
|
2803
|
+
if (value !== true && isPlainObject(value) && isPlainObject(value.select)) {
|
|
2804
|
+
const selectObj = value.select;
|
|
2805
|
+
for (const entryVal of Object.values(selectObj)) {
|
|
2806
|
+
if (isPlainObject(entryVal) && entryVal.where) {
|
|
2807
|
+
const w = entryVal.where;
|
|
2808
|
+
if (isPlainObject(w) && hasClientControlledValues(w))
|
|
2809
|
+
return true;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2200
2812
|
}
|
|
2813
|
+
continue;
|
|
2201
2814
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
if (
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
const { caller: _, ...rest } = parsed;
|
|
2218
|
-
return { shape, body: rest, matchedKey: matched, wasDynamic };
|
|
2815
|
+
if (value === true)
|
|
2816
|
+
continue;
|
|
2817
|
+
if (value.orderBy || value.cursor || value.take || value.skip)
|
|
2818
|
+
return true;
|
|
2819
|
+
if (value.where && isPlainObject(value.where) && hasClientControlledValues(value.where))
|
|
2820
|
+
return true;
|
|
2821
|
+
if (value.select && checkSelectForClientArgs(value.select))
|
|
2822
|
+
return true;
|
|
2823
|
+
}
|
|
2824
|
+
return false;
|
|
2825
|
+
}
|
|
2826
|
+
function hasNestedClientControlledArgs(shape) {
|
|
2827
|
+
if (shape.include) {
|
|
2828
|
+
if (checkIncludeForClientArgs(shape.include))
|
|
2829
|
+
return true;
|
|
2219
2830
|
}
|
|
2220
|
-
|
|
2221
|
-
if (
|
|
2222
|
-
|
|
2223
|
-
return body;
|
|
2831
|
+
if (shape.select) {
|
|
2832
|
+
if (checkSelectForClientArgs(shape.select))
|
|
2833
|
+
return true;
|
|
2224
2834
|
}
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2835
|
+
return false;
|
|
2836
|
+
}
|
|
2837
|
+
function createModelGuardExtension(config) {
|
|
2838
|
+
const { typeMap, enumMap, zodChains, zodDefaults, uniqueMap, scopeMap, guardConfig, contextFn } = config;
|
|
2839
|
+
const wrapZodErrors = config.wrapZodErrors ?? false;
|
|
2840
|
+
const enforceProjection = guardConfig.enforceProjection ?? false;
|
|
2841
|
+
const scalarBase = createScalarBase(guardConfig.strictDecimal ?? false);
|
|
2842
|
+
const schemaBuilder = createSchemaBuilder(typeMap, zodChains, enumMap, scalarBase, zodDefaults);
|
|
2843
|
+
const queryBuilder = createQueryBuilder(typeMap, enumMap, uniqueMap, scalarBase);
|
|
2844
|
+
const modelScopeFks = /* @__PURE__ */ new Map();
|
|
2845
|
+
for (const [model, entries] of Object.entries(scopeMap)) {
|
|
2846
|
+
const fks = /* @__PURE__ */ new Set();
|
|
2847
|
+
for (const entry of entries)
|
|
2848
|
+
fks.add(entry.fk);
|
|
2849
|
+
modelScopeFks.set(model, fks);
|
|
2231
2850
|
}
|
|
2232
2851
|
function maybeValidateUniqueWhere(modelName, shape, method) {
|
|
2233
2852
|
if (!UNIQUE_MUTATION_METHODS.has(method))
|
|
2234
2853
|
return;
|
|
2235
2854
|
if (!shape.where)
|
|
2236
2855
|
return;
|
|
2237
|
-
validateUniqueEquality(modelName, shape.where, method, uniqueMap);
|
|
2238
|
-
}
|
|
2239
|
-
function hasDataRefines(dataConfig) {
|
|
2240
|
-
for (const value of Object.values(dataConfig)) {
|
|
2241
|
-
if (typeof value === "function")
|
|
2242
|
-
return true;
|
|
2243
|
-
}
|
|
2244
|
-
return false;
|
|
2856
|
+
validateUniqueEquality(modelName, shape.where, method, uniqueMap, typeMap);
|
|
2245
2857
|
}
|
|
2246
|
-
function createGuardedMethods(modelName, modelDelegate, input) {
|
|
2858
|
+
function createGuardedMethods(modelName, modelDelegate, input, explicitCaller) {
|
|
2247
2859
|
function callDelegate(method, args) {
|
|
2248
2860
|
if (typeof modelDelegate[method] !== "function") {
|
|
2249
2861
|
throw new ShapeError(`Method "${method}" is not available on this model`);
|
|
2250
2862
|
}
|
|
2251
2863
|
return modelDelegate[method](args);
|
|
2252
2864
|
}
|
|
2865
|
+
function resolveCaller() {
|
|
2866
|
+
if (explicitCaller !== void 0)
|
|
2867
|
+
return explicitCaller;
|
|
2868
|
+
const ctx = validateContext(contextFn());
|
|
2869
|
+
const c = ctx.caller;
|
|
2870
|
+
if (typeof c === "string")
|
|
2871
|
+
return c;
|
|
2872
|
+
return void 0;
|
|
2873
|
+
}
|
|
2253
2874
|
const readShapeCache = /* @__PURE__ */ new Map();
|
|
2254
2875
|
const dataSchemaCache = /* @__PURE__ */ new Map();
|
|
2255
2876
|
const whereBuiltCache = /* @__PURE__ */ new Map();
|
|
@@ -2272,11 +2893,11 @@ function createModelGuardExtension(config) {
|
|
|
2272
2893
|
const cached = dataSchemaCache.get(cacheKey);
|
|
2273
2894
|
if (cached)
|
|
2274
2895
|
return cached;
|
|
2275
|
-
const built = buildDataSchema(modelName, dataConfig, mode);
|
|
2896
|
+
const built = buildDataSchema(modelName, dataConfig, mode, typeMap, schemaBuilder, zodDefaults);
|
|
2276
2897
|
dataSchemaCache.set(cacheKey, built);
|
|
2277
2898
|
return built;
|
|
2278
2899
|
}
|
|
2279
|
-
return buildDataSchema(modelName, dataConfig, mode);
|
|
2900
|
+
return buildDataSchema(modelName, dataConfig, mode, typeMap, schemaBuilder, zodDefaults);
|
|
2280
2901
|
}
|
|
2281
2902
|
function getWhereBuilt(whereConfig, matchedKey, wasDynamic) {
|
|
2282
2903
|
if (!wasDynamic) {
|
|
@@ -2317,7 +2938,7 @@ function createModelGuardExtension(config) {
|
|
|
2317
2938
|
forcedSelectCountWhere = result.forcedCountWhere;
|
|
2318
2939
|
}
|
|
2319
2940
|
return {
|
|
2320
|
-
zodSchema:
|
|
2941
|
+
zodSchema: z9.object(schemaFields).strict(),
|
|
2321
2942
|
forcedIncludeTree,
|
|
2322
2943
|
forcedSelectTree,
|
|
2323
2944
|
forcedIncludeCountWhere,
|
|
@@ -2346,12 +2967,26 @@ function createModelGuardExtension(config) {
|
|
|
2346
2967
|
}
|
|
2347
2968
|
if (!hasShapeProjection)
|
|
2348
2969
|
return {};
|
|
2970
|
+
if (!hasBodyProjection && !enforceProjection)
|
|
2971
|
+
return {};
|
|
2972
|
+
if (!hasBodyProjection && enforceProjection) {
|
|
2973
|
+
if (hasNestedClientControlledArgs(shape)) {
|
|
2974
|
+
throw new ShapeError(
|
|
2975
|
+
`Guard shape defines nested client-controlled projection args (orderBy/take/cursor/skip/where) but the client did not provide select/include in the ${method} body. With enforceProjection enabled, either always provide projection in the body or remove client-controlled nested args from the shape.`
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2349
2979
|
const projection = getProjection(shape, matchedKey, wasDynamic);
|
|
2350
|
-
|
|
2351
|
-
if (
|
|
2352
|
-
projectionBody
|
|
2353
|
-
|
|
2354
|
-
|
|
2980
|
+
let projectionBody;
|
|
2981
|
+
if (hasBodyProjection) {
|
|
2982
|
+
projectionBody = {};
|
|
2983
|
+
if ("select" in parsed)
|
|
2984
|
+
projectionBody.select = parsed.select;
|
|
2985
|
+
if ("include" in parsed)
|
|
2986
|
+
projectionBody.include = parsed.include;
|
|
2987
|
+
} else {
|
|
2988
|
+
projectionBody = buildDefaultProjectionBody(shape);
|
|
2989
|
+
}
|
|
2355
2990
|
const validated = projection.zodSchema.parse(projectionBody);
|
|
2356
2991
|
if (Object.keys(projection.forcedIncludeTree).length > 0) {
|
|
2357
2992
|
applyForcedTree(validated, "include", projection.forcedIncludeTree);
|
|
@@ -2382,9 +3017,15 @@ function createModelGuardExtension(config) {
|
|
|
2382
3017
|
let validatedWhere;
|
|
2383
3018
|
if (built.schema) {
|
|
2384
3019
|
validatedWhere = built.schema.parse(bodyWhere);
|
|
3020
|
+
} else if (bodyWhere !== void 0) {
|
|
3021
|
+
if (bodyWhere === null || !isPlainObject(bodyWhere) || Object.keys(bodyWhere).length > 0) {
|
|
3022
|
+
throw new ShapeError(
|
|
3023
|
+
"Guard shape where contains only forced conditions. Client where input is not accepted."
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
2385
3026
|
}
|
|
2386
|
-
if (
|
|
2387
|
-
return preserveUnique ?
|
|
3027
|
+
if (hasWhereForced(built.forced)) {
|
|
3028
|
+
return preserveUnique ? mergeUniqueWhereForced(validatedWhere, built.forced) : mergeWhereForced(validatedWhere, built.forced);
|
|
2388
3029
|
}
|
|
2389
3030
|
return validatedWhere ?? {};
|
|
2390
3031
|
}
|
|
@@ -2397,14 +3038,15 @@ function createModelGuardExtension(config) {
|
|
|
2397
3038
|
}
|
|
2398
3039
|
function makeReadMethod(method) {
|
|
2399
3040
|
return (body) => {
|
|
2400
|
-
const
|
|
2401
|
-
|
|
3041
|
+
const caller = resolveCaller();
|
|
3042
|
+
const resolved = resolveShape(input, body, contextFn, caller);
|
|
3043
|
+
if (resolved.shape.data) {
|
|
2402
3044
|
throw new ShapeError(`Guard shape "data" is not valid for ${method}`);
|
|
2403
3045
|
}
|
|
2404
|
-
const { data: _, ...queryShape } = shape;
|
|
2405
|
-
const built = getReadShape(method, queryShape, matchedKey, wasDynamic);
|
|
3046
|
+
const { data: _, ...queryShape } = resolved.shape;
|
|
3047
|
+
const built = getReadShape(method, queryShape, resolved.matchedKey, resolved.wasDynamic);
|
|
2406
3048
|
const isUnique = UNIQUE_READ_METHODS.has(method);
|
|
2407
|
-
const args = applyBuiltShape(built,
|
|
3049
|
+
const args = applyBuiltShape(built, resolved.body, isUnique);
|
|
2408
3050
|
if (isUnique && args.where) {
|
|
2409
3051
|
validateResolvedUniqueWhere(
|
|
2410
3052
|
modelName,
|
|
@@ -2417,33 +3059,51 @@ function createModelGuardExtension(config) {
|
|
|
2417
3059
|
};
|
|
2418
3060
|
}
|
|
2419
3061
|
function makeCreateMethod(method) {
|
|
3062
|
+
const isBatch = BATCH_CREATE_METHODS.has(method);
|
|
2420
3063
|
const supportsProjection = PROJECTION_MUTATION_METHODS.has(method);
|
|
2421
|
-
|
|
3064
|
+
let allowedBodyKeys;
|
|
3065
|
+
if (isBatch && supportsProjection) {
|
|
3066
|
+
allowedBodyKeys = ALLOWED_BODY_KEYS_CREATE_MANY_PROJECTION;
|
|
3067
|
+
} else if (isBatch) {
|
|
3068
|
+
allowedBodyKeys = ALLOWED_BODY_KEYS_CREATE_MANY;
|
|
3069
|
+
} else if (supportsProjection) {
|
|
3070
|
+
allowedBodyKeys = ALLOWED_BODY_KEYS_CREATE_PROJECTION;
|
|
3071
|
+
} else {
|
|
3072
|
+
allowedBodyKeys = ALLOWED_BODY_KEYS_CREATE;
|
|
3073
|
+
}
|
|
2422
3074
|
const allowedShapeKeys = supportsProjection ? VALID_SHAPE_KEYS_CREATE_PROJECTION : VALID_SHAPE_KEYS_CREATE;
|
|
2423
3075
|
return (body) => {
|
|
2424
|
-
const
|
|
2425
|
-
|
|
3076
|
+
const caller = resolveCaller();
|
|
3077
|
+
const resolved = resolveShape(input, body, contextFn, caller);
|
|
3078
|
+
if (!resolved.shape.data)
|
|
2426
3079
|
throw new ShapeError(`Guard shape requires "data" for ${method}`);
|
|
2427
|
-
validateMutationShapeKeys(shape, allowedShapeKeys, method);
|
|
2428
|
-
validateMutationBodyKeys(
|
|
2429
|
-
|
|
2430
|
-
|
|
3080
|
+
validateMutationShapeKeys(resolved.shape, allowedShapeKeys, method);
|
|
3081
|
+
validateMutationBodyKeys(resolved.body, allowedBodyKeys, method);
|
|
3082
|
+
const fks = modelScopeFks.get(modelName) ?? /* @__PURE__ */ new Set();
|
|
3083
|
+
validateCreateCompleteness(modelName, resolved.shape.data, typeMap, fks, zodDefaults);
|
|
3084
|
+
const dataSchema = getDataSchema("create", resolved.shape.data, resolved.matchedKey, resolved.wasDynamic);
|
|
2431
3085
|
let args;
|
|
2432
3086
|
if (method === "create") {
|
|
2433
|
-
const data = validateAndMergeData(
|
|
3087
|
+
const data = validateAndMergeData(resolved.body.data, dataSchema, method);
|
|
2434
3088
|
args = { data };
|
|
2435
3089
|
} else {
|
|
2436
|
-
if (!Array.isArray(
|
|
3090
|
+
if (!Array.isArray(resolved.body.data))
|
|
2437
3091
|
throw new ShapeError(`${method} expects data to be an array`);
|
|
2438
|
-
if (
|
|
3092
|
+
if (resolved.body.data.length === 0)
|
|
2439
3093
|
throw new ShapeError(`${method} received empty data array`);
|
|
2440
|
-
const data =
|
|
3094
|
+
const data = resolved.body.data.map(
|
|
2441
3095
|
(item) => validateAndMergeData(item, dataSchema, method)
|
|
2442
3096
|
);
|
|
2443
3097
|
args = { data };
|
|
2444
3098
|
}
|
|
3099
|
+
if (isBatch && resolved.body.skipDuplicates !== void 0) {
|
|
3100
|
+
if (typeof resolved.body.skipDuplicates !== "boolean") {
|
|
3101
|
+
throw new ShapeError(`${method} skipDuplicates must be a boolean`);
|
|
3102
|
+
}
|
|
3103
|
+
args.skipDuplicates = resolved.body.skipDuplicates;
|
|
3104
|
+
}
|
|
2445
3105
|
if (supportsProjection) {
|
|
2446
|
-
const projectionArgs = resolveProjection(shape,
|
|
3106
|
+
const projectionArgs = resolveProjection(resolved.shape, resolved.body, method, resolved.matchedKey, resolved.wasDynamic);
|
|
2447
3107
|
Object.assign(args, projectionArgs);
|
|
2448
3108
|
}
|
|
2449
3109
|
return callDelegate(method, args);
|
|
@@ -2456,18 +3116,19 @@ function createModelGuardExtension(config) {
|
|
|
2456
3116
|
const allowedBodyKeys = supportsProjection ? ALLOWED_BODY_KEYS_UPDATE_PROJECTION : ALLOWED_BODY_KEYS_UPDATE;
|
|
2457
3117
|
const allowedShapeKeys = supportsProjection ? VALID_SHAPE_KEYS_UPDATE_PROJECTION : VALID_SHAPE_KEYS_UPDATE;
|
|
2458
3118
|
return (body) => {
|
|
2459
|
-
const
|
|
2460
|
-
|
|
3119
|
+
const caller = resolveCaller();
|
|
3120
|
+
const resolved = resolveShape(input, body, contextFn, caller);
|
|
3121
|
+
if (!resolved.shape.data)
|
|
2461
3122
|
throw new ShapeError(`Guard shape requires "data" for ${method}`);
|
|
2462
|
-
validateMutationShapeKeys(shape, allowedShapeKeys, method);
|
|
2463
|
-
validateMutationBodyKeys(
|
|
2464
|
-
if (isBulk && !shape.where) {
|
|
3123
|
+
validateMutationShapeKeys(resolved.shape, allowedShapeKeys, method);
|
|
3124
|
+
validateMutationBodyKeys(resolved.body, allowedBodyKeys, method);
|
|
3125
|
+
if (isBulk && !resolved.shape.where) {
|
|
2465
3126
|
throw new ShapeError(`Guard shape requires "where" for ${method} to prevent unconstrained bulk mutations`);
|
|
2466
3127
|
}
|
|
2467
|
-
maybeValidateUniqueWhere(modelName, shape, method);
|
|
2468
|
-
const dataSchema = getDataSchema("update", shape.data, matchedKey, wasDynamic);
|
|
2469
|
-
const data = validateAndMergeData(
|
|
2470
|
-
const where = isUniqueWhere ? requireWhere(shape,
|
|
3128
|
+
maybeValidateUniqueWhere(modelName, resolved.shape, method);
|
|
3129
|
+
const dataSchema = getDataSchema("update", resolved.shape.data, resolved.matchedKey, resolved.wasDynamic);
|
|
3130
|
+
const data = validateAndMergeData(resolved.body.data, dataSchema, method);
|
|
3131
|
+
const where = isUniqueWhere ? requireWhere(resolved.shape, resolved.body.where, method, true, resolved.matchedKey, resolved.wasDynamic) : buildWhereFromShape(resolved.shape, resolved.body.where, false, resolved.matchedKey, resolved.wasDynamic);
|
|
2471
3132
|
if (isBulk && Object.keys(where).length === 0) {
|
|
2472
3133
|
throw new ShapeError(`${method} requires at least one where condition`);
|
|
2473
3134
|
}
|
|
@@ -2476,7 +3137,7 @@ function createModelGuardExtension(config) {
|
|
|
2476
3137
|
}
|
|
2477
3138
|
const args = { data, where };
|
|
2478
3139
|
if (supportsProjection) {
|
|
2479
|
-
const projectionArgs = resolveProjection(shape,
|
|
3140
|
+
const projectionArgs = resolveProjection(resolved.shape, resolved.body, method, resolved.matchedKey, resolved.wasDynamic);
|
|
2480
3141
|
Object.assign(args, projectionArgs);
|
|
2481
3142
|
}
|
|
2482
3143
|
return callDelegate(method, args);
|
|
@@ -2489,16 +3150,17 @@ function createModelGuardExtension(config) {
|
|
|
2489
3150
|
const allowedBodyKeys = supportsProjection ? ALLOWED_BODY_KEYS_DELETE_PROJECTION : ALLOWED_BODY_KEYS_DELETE;
|
|
2490
3151
|
const allowedShapeKeys = supportsProjection ? VALID_SHAPE_KEYS_DELETE_PROJECTION : VALID_SHAPE_KEYS_DELETE;
|
|
2491
3152
|
return (body) => {
|
|
2492
|
-
const
|
|
2493
|
-
|
|
3153
|
+
const caller = resolveCaller();
|
|
3154
|
+
const resolved = resolveShape(input, body, contextFn, caller);
|
|
3155
|
+
if (resolved.shape.data)
|
|
2494
3156
|
throw new ShapeError(`Guard shape "data" is not valid for ${method}`);
|
|
2495
|
-
validateMutationShapeKeys(shape, allowedShapeKeys, method);
|
|
2496
|
-
validateMutationBodyKeys(
|
|
2497
|
-
if (isBulk && !shape.where) {
|
|
3157
|
+
validateMutationShapeKeys(resolved.shape, allowedShapeKeys, method);
|
|
3158
|
+
validateMutationBodyKeys(resolved.body, allowedBodyKeys, method);
|
|
3159
|
+
if (isBulk && !resolved.shape.where) {
|
|
2498
3160
|
throw new ShapeError(`Guard shape requires "where" for ${method} to prevent unconstrained bulk mutations`);
|
|
2499
3161
|
}
|
|
2500
|
-
maybeValidateUniqueWhere(modelName, shape, method);
|
|
2501
|
-
const where = isUniqueWhere ? requireWhere(shape,
|
|
3162
|
+
maybeValidateUniqueWhere(modelName, resolved.shape, method);
|
|
3163
|
+
const where = isUniqueWhere ? requireWhere(resolved.shape, resolved.body.where, method, true, resolved.matchedKey, resolved.wasDynamic) : buildWhereFromShape(resolved.shape, resolved.body.where, false, resolved.matchedKey, resolved.wasDynamic);
|
|
2502
3164
|
if (isBulk && Object.keys(where).length === 0) {
|
|
2503
3165
|
throw new ShapeError(`${method} requires at least one where condition`);
|
|
2504
3166
|
}
|
|
@@ -2507,12 +3169,76 @@ function createModelGuardExtension(config) {
|
|
|
2507
3169
|
}
|
|
2508
3170
|
const args = { where };
|
|
2509
3171
|
if (supportsProjection) {
|
|
2510
|
-
const projectionArgs = resolveProjection(shape,
|
|
3172
|
+
const projectionArgs = resolveProjection(resolved.shape, resolved.body, method, resolved.matchedKey, resolved.wasDynamic);
|
|
2511
3173
|
Object.assign(args, projectionArgs);
|
|
2512
3174
|
}
|
|
2513
3175
|
return callDelegate(method, args);
|
|
2514
3176
|
};
|
|
2515
3177
|
}
|
|
3178
|
+
function makeUpsertMethod() {
|
|
3179
|
+
return (body) => {
|
|
3180
|
+
const caller = resolveCaller();
|
|
3181
|
+
const resolved = resolveShape(input, body, contextFn, caller);
|
|
3182
|
+
if (resolved.shape.data) {
|
|
3183
|
+
throw new ShapeError('Guard shape "data" is not valid for upsert. Use "create" and "update" instead.');
|
|
3184
|
+
}
|
|
3185
|
+
if (!resolved.shape.create) {
|
|
3186
|
+
throw new ShapeError('Guard shape requires "create" for upsert');
|
|
3187
|
+
}
|
|
3188
|
+
if (!resolved.shape.update) {
|
|
3189
|
+
throw new ShapeError('Guard shape requires "update" for upsert');
|
|
3190
|
+
}
|
|
3191
|
+
if (!resolved.shape.where) {
|
|
3192
|
+
throw new ShapeError('Guard shape requires "where" for upsert');
|
|
3193
|
+
}
|
|
3194
|
+
validateMutationShapeKeys(
|
|
3195
|
+
resolved.shape,
|
|
3196
|
+
VALID_SHAPE_KEYS_UPSERT,
|
|
3197
|
+
"upsert"
|
|
3198
|
+
);
|
|
3199
|
+
validateMutationBodyKeys(resolved.body, ALLOWED_BODY_KEYS_UPSERT, "upsert");
|
|
3200
|
+
maybeValidateUniqueWhere(modelName, resolved.shape, "upsert");
|
|
3201
|
+
const fks = modelScopeFks.get(modelName) ?? /* @__PURE__ */ new Set();
|
|
3202
|
+
validateCreateCompleteness(modelName, resolved.shape.create, typeMap, fks, zodDefaults);
|
|
3203
|
+
const createDataSchema = getDataSchema(
|
|
3204
|
+
"create",
|
|
3205
|
+
resolved.shape.create,
|
|
3206
|
+
`upsert:create\0${resolved.matchedKey}`,
|
|
3207
|
+
resolved.wasDynamic
|
|
3208
|
+
);
|
|
3209
|
+
const createData = validateAndMergeData(resolved.body.create, createDataSchema, "upsert (create)");
|
|
3210
|
+
const updateDataSchema = getDataSchema(
|
|
3211
|
+
"update",
|
|
3212
|
+
resolved.shape.update,
|
|
3213
|
+
`upsert:update\0${resolved.matchedKey}`,
|
|
3214
|
+
resolved.wasDynamic
|
|
3215
|
+
);
|
|
3216
|
+
const updateData = validateAndMergeData(resolved.body.update, updateDataSchema, "upsert (update)");
|
|
3217
|
+
const where = requireWhere(
|
|
3218
|
+
resolved.shape,
|
|
3219
|
+
resolved.body.where,
|
|
3220
|
+
"upsert",
|
|
3221
|
+
true,
|
|
3222
|
+
resolved.matchedKey,
|
|
3223
|
+
resolved.wasDynamic
|
|
3224
|
+
);
|
|
3225
|
+
validateResolvedUniqueWhere(modelName, where, "upsert", uniqueMap);
|
|
3226
|
+
const args = {
|
|
3227
|
+
where,
|
|
3228
|
+
create: createData,
|
|
3229
|
+
update: updateData
|
|
3230
|
+
};
|
|
3231
|
+
const projectionArgs = resolveProjection(
|
|
3232
|
+
resolved.shape,
|
|
3233
|
+
resolved.body,
|
|
3234
|
+
"upsert",
|
|
3235
|
+
resolved.matchedKey,
|
|
3236
|
+
resolved.wasDynamic
|
|
3237
|
+
);
|
|
3238
|
+
Object.assign(args, projectionArgs);
|
|
3239
|
+
return callDelegate("upsert", args);
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
2516
3242
|
return {
|
|
2517
3243
|
findMany: makeReadMethod("findMany"),
|
|
2518
3244
|
findFirst: makeReadMethod("findFirst"),
|
|
@@ -2528,6 +3254,7 @@ function createModelGuardExtension(config) {
|
|
|
2528
3254
|
update: makeUpdateMethod("update"),
|
|
2529
3255
|
updateMany: makeUpdateMethod("updateMany"),
|
|
2530
3256
|
updateManyAndReturn: makeUpdateMethod("updateManyAndReturn"),
|
|
3257
|
+
upsert: makeUpsertMethod(),
|
|
2531
3258
|
delete: makeDeleteMethod("delete"),
|
|
2532
3259
|
deleteMany: makeDeleteMethod("deleteMany")
|
|
2533
3260
|
};
|
|
@@ -2539,7 +3266,7 @@ function createModelGuardExtension(config) {
|
|
|
2539
3266
|
try {
|
|
2540
3267
|
return fn(body);
|
|
2541
3268
|
} catch (err) {
|
|
2542
|
-
if (err instanceof
|
|
3269
|
+
if (err instanceof z9.ZodError) {
|
|
2543
3270
|
throw new ShapeError(
|
|
2544
3271
|
`Validation failed: ${formatZodError(err)}`,
|
|
2545
3272
|
{ cause: err }
|
|
@@ -2553,16 +3280,16 @@ function createModelGuardExtension(config) {
|
|
|
2553
3280
|
}
|
|
2554
3281
|
return {
|
|
2555
3282
|
$allModels: {
|
|
2556
|
-
guard(input) {
|
|
3283
|
+
guard(input, caller) {
|
|
2557
3284
|
const modelName = this.$name;
|
|
2558
|
-
const delegateKey =
|
|
3285
|
+
const delegateKey = toDelegateKey(modelName);
|
|
2559
3286
|
const modelDelegate = this.$parent[delegateKey];
|
|
2560
3287
|
if (!modelDelegate) {
|
|
2561
3288
|
throw new ShapeError(
|
|
2562
3289
|
`Could not resolve Prisma delegate for model "${modelName}" (key: "${delegateKey}")`
|
|
2563
3290
|
);
|
|
2564
3291
|
}
|
|
2565
|
-
const methods = createGuardedMethods(modelName, modelDelegate, input);
|
|
3292
|
+
const methods = createGuardedMethods(modelName, modelDelegate, input, caller);
|
|
2566
3293
|
if (!wrapZodErrors)
|
|
2567
3294
|
return methods;
|
|
2568
3295
|
return wrapMethods(methods);
|
|
@@ -2572,28 +3299,26 @@ function createModelGuardExtension(config) {
|
|
|
2572
3299
|
}
|
|
2573
3300
|
|
|
2574
3301
|
// src/runtime/guard.ts
|
|
2575
|
-
function formatZodError2(err) {
|
|
2576
|
-
return err.issues.map((i) => {
|
|
2577
|
-
const p = i.path.length > 0 ? `${i.path.join(".")}: ` : "";
|
|
2578
|
-
return `${p}${i.message}`;
|
|
2579
|
-
}).join("; ");
|
|
2580
|
-
}
|
|
2581
3302
|
function createGuard(config) {
|
|
3303
|
+
const scalarBase = createScalarBase(config.guardConfig.strictDecimal ?? false);
|
|
2582
3304
|
const schemaBuilder = createSchemaBuilder(
|
|
2583
3305
|
config.typeMap,
|
|
2584
3306
|
config.zodChains,
|
|
2585
|
-
config.enumMap
|
|
3307
|
+
config.enumMap,
|
|
3308
|
+
scalarBase,
|
|
3309
|
+
config.zodDefaults ?? {}
|
|
2586
3310
|
);
|
|
2587
3311
|
const queryBuilder = createQueryBuilder(
|
|
2588
3312
|
config.typeMap,
|
|
2589
3313
|
config.enumMap,
|
|
2590
|
-
config.uniqueMap ?? {}
|
|
3314
|
+
config.uniqueMap ?? {},
|
|
3315
|
+
scalarBase
|
|
2591
3316
|
);
|
|
2592
3317
|
const log = config.logger ?? { warn: (msg) => console.warn(msg) };
|
|
2593
3318
|
const wrapZodErrors = config.wrapZodErrors ?? false;
|
|
2594
3319
|
function rethrowZod(err) {
|
|
2595
3320
|
if (err instanceof ZodError) {
|
|
2596
|
-
throw new ShapeError(`Validation failed: ${
|
|
3321
|
+
throw new ShapeError(`Validation failed: ${formatZodError(err)}`, { cause: err });
|
|
2597
3322
|
}
|
|
2598
3323
|
throw err;
|
|
2599
3324
|
}
|
|
@@ -2630,16 +3355,24 @@ function createGuard(config) {
|
|
|
2630
3355
|
};
|
|
2631
3356
|
},
|
|
2632
3357
|
extension: (contextFn) => {
|
|
3358
|
+
const scopeRoots = /* @__PURE__ */ new Set();
|
|
3359
|
+
for (const entries of Object.values(config.scopeMap)) {
|
|
3360
|
+
for (const entry of entries) {
|
|
3361
|
+
scopeRoots.add(entry.root);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
2633
3364
|
const scopeCtxFn = () => {
|
|
2634
|
-
const ctx = contextFn();
|
|
3365
|
+
const ctx = validateContext(contextFn());
|
|
2635
3366
|
const scopeCtx = {};
|
|
2636
3367
|
for (const key of Object.keys(ctx)) {
|
|
3368
|
+
if (!scopeRoots.has(key))
|
|
3369
|
+
continue;
|
|
2637
3370
|
const val = ctx[key];
|
|
2638
3371
|
if (typeof val === "string" || typeof val === "number" || typeof val === "bigint") {
|
|
2639
3372
|
scopeCtx[key] = val;
|
|
2640
3373
|
} else if (val !== null && val !== void 0) {
|
|
2641
|
-
|
|
2642
|
-
`prisma-guard:
|
|
3374
|
+
throw new PolicyError(
|
|
3375
|
+
`prisma-guard: Scope root "${key}" has non-primitive value (${typeof val}). Only string, number, and bigint values are accepted for scope context.`
|
|
2643
3376
|
);
|
|
2644
3377
|
}
|
|
2645
3378
|
}
|
|
@@ -2655,8 +3388,10 @@ function createGuard(config) {
|
|
|
2655
3388
|
typeMap: config.typeMap,
|
|
2656
3389
|
enumMap: config.enumMap,
|
|
2657
3390
|
zodChains: config.zodChains,
|
|
3391
|
+
zodDefaults: config.zodDefaults ?? {},
|
|
2658
3392
|
uniqueMap: config.uniqueMap ?? {},
|
|
2659
3393
|
scopeMap: config.scopeMap,
|
|
3394
|
+
guardConfig: config.guardConfig,
|
|
2660
3395
|
contextFn,
|
|
2661
3396
|
wrapZodErrors
|
|
2662
3397
|
});
|
|
@@ -2672,6 +3407,7 @@ export {
|
|
|
2672
3407
|
CallerError,
|
|
2673
3408
|
PolicyError,
|
|
2674
3409
|
ShapeError,
|
|
2675
|
-
createGuard
|
|
3410
|
+
createGuard,
|
|
3411
|
+
force
|
|
2676
3412
|
};
|
|
2677
3413
|
//# sourceMappingURL=index.js.map
|