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