prisma-guard 1.27.1 → 1.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -33
- package/dist/generator/index.js +254 -218
- package/dist/generator/index.js.map +1 -1
- package/dist/runtime/index.cjs +1665 -1669
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +78 -44
- package/dist/runtime/index.d.ts +78 -44
- package/dist/runtime/index.js +1665 -1669
- package/dist/runtime/index.js.map +1 -1
- package/package.json +3 -2
package/dist/generator/index.js
CHANGED
|
@@ -2,8 +2,91 @@
|
|
|
2
2
|
|
|
3
3
|
// src/generator/index.ts
|
|
4
4
|
import pkg from "@prisma/generator-helper";
|
|
5
|
-
import { mkdirSync, writeFileSync } from "fs";
|
|
5
|
+
import { existsSync as existsSync2, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
6
6
|
import { relative } from "path";
|
|
7
|
+
import { z as z3 } from "zod";
|
|
8
|
+
|
|
9
|
+
// src/generator/import-style.ts
|
|
10
|
+
import { existsSync, readFileSync } from "fs";
|
|
11
|
+
import { dirname, join, resolve } from "path";
|
|
12
|
+
import { getTsconfig } from "get-tsconfig";
|
|
13
|
+
var NODE_ESM_MODES = /* @__PURE__ */ new Set(["node16", "node18", "nodenext"]);
|
|
14
|
+
var NO_EXTENSION_RESOLUTIONS = /* @__PURE__ */ new Set(["bundler", "classic", "node", "node10"]);
|
|
15
|
+
function findUpwards(startDir, filename) {
|
|
16
|
+
let dir = resolve(startDir);
|
|
17
|
+
while (true) {
|
|
18
|
+
const candidate = join(dir, filename);
|
|
19
|
+
if (existsSync(candidate))
|
|
20
|
+
return candidate;
|
|
21
|
+
const parent = dirname(dir);
|
|
22
|
+
if (parent === dir)
|
|
23
|
+
return null;
|
|
24
|
+
dir = parent;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function readCompilerOptions(startDir) {
|
|
28
|
+
try {
|
|
29
|
+
const result = getTsconfig(startDir);
|
|
30
|
+
if (!result)
|
|
31
|
+
return {};
|
|
32
|
+
const co = result.config.compilerOptions;
|
|
33
|
+
if (!co || typeof co !== "object")
|
|
34
|
+
return {};
|
|
35
|
+
return co;
|
|
36
|
+
} catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function readPackageType(startDir) {
|
|
41
|
+
const pkgPath = findUpwards(startDir, "package.json");
|
|
42
|
+
if (!pkgPath)
|
|
43
|
+
return null;
|
|
44
|
+
try {
|
|
45
|
+
const raw = readFileSync(pkgPath, "utf-8");
|
|
46
|
+
const pkg2 = JSON.parse(raw);
|
|
47
|
+
return typeof pkg2.type === "string" ? pkg2.type : null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function resolveImportStyle(startDir, override) {
|
|
53
|
+
if (override !== "auto")
|
|
54
|
+
return override;
|
|
55
|
+
const co = readCompilerOptions(startDir);
|
|
56
|
+
if (co.allowImportingTsExtensions === true)
|
|
57
|
+
return "ts";
|
|
58
|
+
const moduleValue = typeof co.module === "string" ? co.module.toLowerCase() : "";
|
|
59
|
+
const resolutionValue = typeof co.moduleResolution === "string" ? co.moduleResolution.toLowerCase() : "";
|
|
60
|
+
if (NODE_ESM_MODES.has(moduleValue) || NODE_ESM_MODES.has(resolutionValue)) {
|
|
61
|
+
return "js";
|
|
62
|
+
}
|
|
63
|
+
if (NO_EXTENSION_RESOLUTIONS.has(resolutionValue)) {
|
|
64
|
+
return "none";
|
|
65
|
+
}
|
|
66
|
+
if (readPackageType(startDir) === "module")
|
|
67
|
+
return "js";
|
|
68
|
+
return "none";
|
|
69
|
+
}
|
|
70
|
+
function withImportStyle(path, style) {
|
|
71
|
+
if (style === "js")
|
|
72
|
+
return `${path}.js`;
|
|
73
|
+
if (style === "ts")
|
|
74
|
+
return `${path}.ts`;
|
|
75
|
+
return path;
|
|
76
|
+
}
|
|
77
|
+
function withClientImportStyle(path, style, kind) {
|
|
78
|
+
if (kind === "package")
|
|
79
|
+
return path;
|
|
80
|
+
if (!path.startsWith("."))
|
|
81
|
+
return path;
|
|
82
|
+
if (kind === "prisma-client") {
|
|
83
|
+
const entry = `${path}/client`;
|
|
84
|
+
return withImportStyle(entry, style);
|
|
85
|
+
}
|
|
86
|
+
if (style === "js")
|
|
87
|
+
return `${path}/index.js`;
|
|
88
|
+
return path;
|
|
89
|
+
}
|
|
7
90
|
|
|
8
91
|
// src/generator/emit-client.ts
|
|
9
92
|
function delegateKey(modelName) {
|
|
@@ -21,12 +104,18 @@ ${entries}
|
|
|
21
104
|
}
|
|
22
105
|
`;
|
|
23
106
|
}
|
|
24
|
-
function emitClient(dmmf, prismaClientImport) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
107
|
+
function emitClient(dmmf, prismaClientImport, prismaClientKind, importStyle, runtimeImportPath) {
|
|
108
|
+
const indexImport = withImportStyle("./index", importStyle);
|
|
109
|
+
const clientImport = withClientImportStyle(
|
|
110
|
+
prismaClientImport,
|
|
111
|
+
importStyle,
|
|
112
|
+
prismaClientKind
|
|
113
|
+
);
|
|
114
|
+
return `import type { PrismaClient } from '${clientImport}'
|
|
115
|
+
import type { GuardInput, GuardedModel } from '${runtimeImportPath}'
|
|
116
|
+
import { createGuard } from '${runtimeImportPath}'
|
|
117
|
+
import { SCOPE_MAP, TYPE_MAP, ENUM_MAP, ZOD_CHAINS, GUARD_CONFIG, UNIQUE_MAP, ZOD_DEFAULTS } from '${indexImport}'
|
|
118
|
+
import type { ScopeRoot } from '${indexImport}'
|
|
30
119
|
|
|
31
120
|
` + emitGuardModelExtension(dmmf) + `
|
|
32
121
|
export const guard = createGuard<typeof TYPE_MAP, ScopeRoot, GuardModelExtension>({
|
|
@@ -48,6 +137,15 @@ function isScopeRoot(documentation) {
|
|
|
48
137
|
const tokens = documentation.split(/[\s\n\r]+/);
|
|
49
138
|
return tokens.some((t) => t === "@scope-root");
|
|
50
139
|
}
|
|
140
|
+
function reportScopeIssue(mode, errorMsg, warnMsg) {
|
|
141
|
+
if (mode === "error")
|
|
142
|
+
throw new Error(`prisma-guard: ${errorMsg}`);
|
|
143
|
+
if (mode === "warn")
|
|
144
|
+
console.warn(`prisma-guard: ${warnMsg}`);
|
|
145
|
+
}
|
|
146
|
+
function serializeScopeEntry(e) {
|
|
147
|
+
return `{ fk: ${JSON.stringify(e.fk)}, root: ${JSON.stringify(e.root)}, relationName: ${JSON.stringify(e.relationName)} }`;
|
|
148
|
+
}
|
|
51
149
|
function emitScopeMap(dmmf, onAmbiguousScope) {
|
|
52
150
|
const rootModels = /* @__PURE__ */ new Set();
|
|
53
151
|
for (const model of dmmf.datamodel.models) {
|
|
@@ -66,13 +164,9 @@ function emitScopeMap(dmmf, onAmbiguousScope) {
|
|
|
66
164
|
if (!rootModels.has(field.type))
|
|
67
165
|
continue;
|
|
68
166
|
if (field.relationFromFields.length > 1) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
if (onAmbiguousScope === "warn") {
|
|
74
|
-
console.warn(`prisma-guard: ${msg} Excluding relation "${field.name}" to scope root "${field.type}" from scope map for model "${model.name}".`);
|
|
75
|
-
}
|
|
167
|
+
const compositeMsg = `Model "${model.name}" has a composite foreign key to scope root "${field.type}" via relation "${field.name}" (fields: ${field.relationFromFields.join(", ")}). Composite scope relations are not supported.`;
|
|
168
|
+
const excludeMsg = `${compositeMsg} Excluding relation "${field.name}" to scope root "${field.type}" from scope map for model "${model.name}".`;
|
|
169
|
+
reportScopeIssue(onAmbiguousScope, compositeMsg, excludeMsg);
|
|
76
170
|
continue;
|
|
77
171
|
}
|
|
78
172
|
relations.push({
|
|
@@ -93,16 +187,11 @@ function emitScopeMap(dmmf, onAmbiguousScope) {
|
|
|
93
187
|
for (const [root, rels] of Object.entries(relationsByRoot)) {
|
|
94
188
|
if (rels.length > 1) {
|
|
95
189
|
const relNames = rels.map((r) => r.relationName);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
if (onAmbiguousScope === "warn") {
|
|
104
|
-
console.warn(`prisma-guard: ${msg} Excluding relations to scope root "${root}" from scope map for model "${model.name}".`);
|
|
105
|
-
}
|
|
190
|
+
const ambiguousMsg = `Model "${model.name}" has multiple relations to scope root "${root}" (${relNames.join(", ")}).`;
|
|
191
|
+
const errorMsg = `Ambiguous scope detected. Resolve these or set onAmbiguousScope to "warn" or "ignore":
|
|
192
|
+
- ${ambiguousMsg}`;
|
|
193
|
+
const warnMsg = `${ambiguousMsg} Excluding relations to scope root "${root}" from scope map for model "${model.name}".`;
|
|
194
|
+
reportScopeIssue(onAmbiguousScope, errorMsg, warnMsg);
|
|
106
195
|
continue;
|
|
107
196
|
}
|
|
108
197
|
entries.push({
|
|
@@ -117,7 +206,7 @@ function emitScopeMap(dmmf, onAmbiguousScope) {
|
|
|
117
206
|
}
|
|
118
207
|
const roots = Array.from(rootModels).sort();
|
|
119
208
|
const mapEntries = Object.entries(scopeMap).map(([model, entries]) => {
|
|
120
|
-
const entriesStr = entries.map(
|
|
209
|
+
const entriesStr = entries.map(serializeScopeEntry).join(", ");
|
|
121
210
|
return ` ${model}: [${entriesStr}],`;
|
|
122
211
|
}).join("\n");
|
|
123
212
|
const scopeRootType = roots.length > 0 ? roots.map((r) => `'${r}'`).join(" | ") : "never";
|
|
@@ -130,19 +219,19 @@ export type ScopeRoot = ${scopeRootType}
|
|
|
130
219
|
return { source };
|
|
131
220
|
}
|
|
132
221
|
|
|
133
|
-
// src/
|
|
222
|
+
// src/shared/unique-constraints.ts
|
|
134
223
|
function uniqueSelector(fields, name) {
|
|
135
224
|
if (typeof name === "string" && name.trim().length > 0)
|
|
136
225
|
return name;
|
|
137
226
|
return fields.join("_");
|
|
138
227
|
}
|
|
228
|
+
function fieldsKey(fields) {
|
|
229
|
+
return fields.join("\0");
|
|
230
|
+
}
|
|
139
231
|
function collectUniqueConstraints(model) {
|
|
140
232
|
const fieldSetSeen = /* @__PURE__ */ new Set();
|
|
141
233
|
const selectorToFields = /* @__PURE__ */ new Map();
|
|
142
234
|
const constraints = [];
|
|
143
|
-
function fieldsKey(fields) {
|
|
144
|
-
return fields.join("\0");
|
|
145
|
-
}
|
|
146
235
|
function add(fields, selector) {
|
|
147
236
|
if (fields.length === 0)
|
|
148
237
|
return;
|
|
@@ -183,6 +272,11 @@ function collectUniqueConstraints(model) {
|
|
|
183
272
|
}
|
|
184
273
|
return constraints;
|
|
185
274
|
}
|
|
275
|
+
|
|
276
|
+
// src/generator/emit-type-map.ts
|
|
277
|
+
function serializeMeta(entries) {
|
|
278
|
+
return entries.map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ");
|
|
279
|
+
}
|
|
186
280
|
function emitTypeMap(dmmf) {
|
|
187
281
|
const enumNames = new Set(dmmf.datamodel.enums.map((e) => e.name));
|
|
188
282
|
for (const e of dmmf.datamodel.enums) {
|
|
@@ -195,22 +289,22 @@ function emitTypeMap(dmmf) {
|
|
|
195
289
|
const isRelation = field.kind === "object" || field.relationName != null;
|
|
196
290
|
const isEnum = enumNames.has(field.type);
|
|
197
291
|
const isUnsupported = field.kind === "unsupported";
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
292
|
+
const metaPairs = [
|
|
293
|
+
["type", field.type],
|
|
294
|
+
["isList", field.isList],
|
|
295
|
+
["isRequired", field.isRequired],
|
|
296
|
+
["isId", field.isId],
|
|
297
|
+
["isRelation", isRelation],
|
|
298
|
+
["hasDefault", field.hasDefaultValue],
|
|
299
|
+
["isUpdatedAt", field.isUpdatedAt]
|
|
206
300
|
];
|
|
207
301
|
if (isEnum)
|
|
208
|
-
|
|
302
|
+
metaPairs.push(["isEnum", true]);
|
|
209
303
|
if (isUnsupported)
|
|
210
|
-
|
|
304
|
+
metaPairs.push(["isUnsupported", true]);
|
|
211
305
|
if (field.isUnique)
|
|
212
|
-
|
|
213
|
-
return ` ${JSON.stringify(field.name)}: { ${
|
|
306
|
+
metaPairs.push(["isUnique", true]);
|
|
307
|
+
return ` ${JSON.stringify(field.name)}: { ${serializeMeta(metaPairs)} },`;
|
|
214
308
|
}).join("\n");
|
|
215
309
|
return ` ${JSON.stringify(model.name)}: {
|
|
216
310
|
${fieldEntries}
|
|
@@ -256,22 +350,22 @@ ${typesSource}
|
|
|
256
350
|
// src/shared/operation-shape-keys.ts
|
|
257
351
|
var OPERATION_SHAPE_KEYS = {
|
|
258
352
|
findMany: ["where", "include", "select", "orderBy", "cursor", "take", "skip", "distinct"],
|
|
353
|
+
findManyPaginated: ["where", "include", "select", "orderBy", "cursor", "take", "skip", "distinct"],
|
|
259
354
|
findFirst: ["where", "include", "select", "orderBy", "cursor", "take", "skip", "distinct"],
|
|
260
355
|
findFirstOrThrow: ["where", "include", "select", "orderBy", "cursor", "take", "skip", "distinct"],
|
|
261
356
|
findUnique: ["where", "include", "select"],
|
|
262
357
|
findUniqueOrThrow: ["where", "include", "select"],
|
|
263
|
-
findManyPaginated: ["where", "include", "select", "orderBy", "cursor", "take", "skip", "distinct"],
|
|
264
358
|
count: ["where", "select", "cursor", "orderBy", "skip", "take"],
|
|
265
359
|
aggregate: ["where", "orderBy", "cursor", "take", "skip", "_count", "_avg", "_sum", "_min", "_max"],
|
|
266
|
-
groupBy: ["where", "
|
|
267
|
-
create: ["data", "
|
|
268
|
-
createMany: ["data"],
|
|
269
|
-
createManyAndReturn: ["data", "select", "
|
|
270
|
-
update: ["
|
|
271
|
-
updateMany: ["
|
|
272
|
-
updateManyAndReturn: ["
|
|
273
|
-
upsert: ["where", "create", "update", "
|
|
274
|
-
delete: ["where", "
|
|
360
|
+
groupBy: ["where", "orderBy", "by", "having", "take", "skip", "_count", "_avg", "_sum", "_min", "_max"],
|
|
361
|
+
create: ["data", "include", "select"],
|
|
362
|
+
createMany: ["data", "skipDuplicates"],
|
|
363
|
+
createManyAndReturn: ["data", "select", "skipDuplicates"],
|
|
364
|
+
update: ["where", "data", "include", "select"],
|
|
365
|
+
updateMany: ["where", "data"],
|
|
366
|
+
updateManyAndReturn: ["where", "data", "select"],
|
|
367
|
+
upsert: ["where", "create", "update", "include", "select"],
|
|
368
|
+
delete: ["where", "include", "select"],
|
|
275
369
|
deleteMany: ["where"]
|
|
276
370
|
};
|
|
277
371
|
var READ_METHOD_ALLOWED_ARGS = {
|
|
@@ -301,8 +395,9 @@ var OPERATIONS = Object.keys(OPERATION_SHAPE_KEYS);
|
|
|
301
395
|
function cap(s) {
|
|
302
396
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
303
397
|
}
|
|
304
|
-
function emitTypedShapes(dmmf, depth) {
|
|
305
|
-
const
|
|
398
|
+
function emitTypedShapes(dmmf, depth, importStyle, runtimeImportPath) {
|
|
399
|
+
const indexImport = withImportStyle("./index", importStyle);
|
|
400
|
+
const header = `import type { TYPE_MAP, UNIQUE_MAP } from '${indexImport}'
|
|
306
401
|
import type {
|
|
307
402
|
TypedGuardShape,
|
|
308
403
|
OperationShape,
|
|
@@ -310,7 +405,7 @@ import type {
|
|
|
310
405
|
TypedProjection,
|
|
311
406
|
TypedInclude,
|
|
312
407
|
TypedCountSelect,
|
|
313
|
-
} from '
|
|
408
|
+
} from '${runtimeImportPath}'
|
|
314
409
|
|
|
315
410
|
type TM = typeof TYPE_MAP
|
|
316
411
|
type UM = typeof UNIQUE_MAP
|
|
@@ -766,6 +861,18 @@ function validateDirective(raw) {
|
|
|
766
861
|
|
|
767
862
|
// src/shared/scalar-base.ts
|
|
768
863
|
import { z } from "zod";
|
|
864
|
+
function isPrismaNullSentinel(value) {
|
|
865
|
+
if (value === null || typeof value !== "object")
|
|
866
|
+
return false;
|
|
867
|
+
const tag = value._tag;
|
|
868
|
+
if (tag === "DbNull" || tag === "JsonNull" || tag === "AnyNull")
|
|
869
|
+
return true;
|
|
870
|
+
const constructorName = value.constructor?.name;
|
|
871
|
+
if (constructorName === "DbNull" || constructorName === "JsonNull" || constructorName === "AnyNull") {
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
769
876
|
function isJsonSafe(value) {
|
|
770
877
|
const stack = [{ tag: "visit", value }];
|
|
771
878
|
const ancestors = /* @__PURE__ */ new Set();
|
|
@@ -780,6 +887,8 @@ function isJsonSafe(value) {
|
|
|
780
887
|
return false;
|
|
781
888
|
if (current === null)
|
|
782
889
|
continue;
|
|
890
|
+
if (isPrismaNullSentinel(current))
|
|
891
|
+
continue;
|
|
783
892
|
switch (typeof current) {
|
|
784
893
|
case "string":
|
|
785
894
|
case "boolean":
|
|
@@ -873,39 +982,11 @@ function buildGenerationBase(fieldType, isList, isEnum, enumValues) {
|
|
|
873
982
|
base = z2.array(base);
|
|
874
983
|
return base;
|
|
875
984
|
}
|
|
876
|
-
var TYPE_CHANGING_METHODS = /* @__PURE__ */ new Set([
|
|
877
|
-
"optional",
|
|
878
|
-
"nullable",
|
|
879
|
-
"nullish",
|
|
880
|
-
"readonly",
|
|
881
|
-
"default",
|
|
882
|
-
"catch"
|
|
883
|
-
]);
|
|
884
|
-
function checkChainCompatibility(fieldType, isList, isEnum, enumValues, methods) {
|
|
885
|
-
let current = buildGenerationBase(fieldType, isList, isEnum, enumValues);
|
|
886
|
-
if (!current)
|
|
887
|
-
return null;
|
|
888
|
-
for (const method of methods) {
|
|
889
|
-
if (typeof current[method] !== "function") {
|
|
890
|
-
return method;
|
|
891
|
-
}
|
|
892
|
-
if (TYPE_CHANGING_METHODS.has(method)) {
|
|
893
|
-
try {
|
|
894
|
-
if (method === "default" || method === "catch") {
|
|
895
|
-
current = current[method](void 0);
|
|
896
|
-
} else {
|
|
897
|
-
current = current[method]();
|
|
898
|
-
}
|
|
899
|
-
} catch {
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
return null;
|
|
904
|
-
}
|
|
905
985
|
function verifyChainExecution(fieldType, isList, isEnum, enumValues, chainStr) {
|
|
906
986
|
const base = buildGenerationBase(fieldType, isList, isEnum, enumValues);
|
|
907
|
-
if (!base)
|
|
908
|
-
return
|
|
987
|
+
if (!base) {
|
|
988
|
+
return `Unsupported field type "${fieldType}" for @zod directive`;
|
|
989
|
+
}
|
|
909
990
|
let fn;
|
|
910
991
|
try {
|
|
911
992
|
fn = new Function("base", `'use strict'; return base${chainStr}`);
|
|
@@ -936,6 +1017,11 @@ function emitZodChains(dmmf, onInvalidZod) {
|
|
|
936
1017
|
}
|
|
937
1018
|
const modelChains = {};
|
|
938
1019
|
const defaults = {};
|
|
1020
|
+
function fail(msg) {
|
|
1021
|
+
if (onInvalidZod === "error")
|
|
1022
|
+
throw new Error(msg);
|
|
1023
|
+
console.warn(msg);
|
|
1024
|
+
}
|
|
939
1025
|
for (const model of dmmf.datamodel.models) {
|
|
940
1026
|
for (const field of model.fields) {
|
|
941
1027
|
if (!field.documentation)
|
|
@@ -944,49 +1030,22 @@ function emitZodChains(dmmf, onInvalidZod) {
|
|
|
944
1030
|
if (zodLines.length === 0)
|
|
945
1031
|
continue;
|
|
946
1032
|
if (zodLines.length > 1) {
|
|
947
|
-
|
|
948
|
-
if (onInvalidZod === "error") {
|
|
949
|
-
throw new Error(msg);
|
|
950
|
-
}
|
|
951
|
-
console.warn(msg);
|
|
1033
|
+
fail(`prisma-guard: Multiple @zod directives on ${model.name}.${field.name}. Only one @zod per field allowed.`);
|
|
952
1034
|
continue;
|
|
953
1035
|
}
|
|
954
1036
|
const line = zodLines[0];
|
|
955
1037
|
const idx = line.indexOf("@zod");
|
|
956
1038
|
const chainStr = line.slice(idx + 4).trim();
|
|
957
1039
|
if (chainStr.length === 0) {
|
|
958
|
-
|
|
959
|
-
if (onInvalidZod === "error") {
|
|
960
|
-
throw new Error(msg);
|
|
961
|
-
}
|
|
962
|
-
console.warn(msg);
|
|
1040
|
+
fail(`prisma-guard: Empty @zod directive on ${model.name}.${field.name}. Add a method chain (e.g. @zod .min(1)) or remove the directive.`);
|
|
963
1041
|
continue;
|
|
964
1042
|
}
|
|
965
1043
|
const result = validateDirective(chainStr);
|
|
966
|
-
if (
|
|
967
|
-
|
|
968
|
-
if (onInvalidZod === "error") {
|
|
969
|
-
throw new Error(msg);
|
|
970
|
-
}
|
|
971
|
-
console.warn(msg);
|
|
1044
|
+
if (result.valid === false) {
|
|
1045
|
+
fail(`prisma-guard: Invalid @zod directive on ${model.name}.${field.name}: ${result.reason}`);
|
|
972
1046
|
continue;
|
|
973
1047
|
}
|
|
974
1048
|
const isEnum = enumNames.has(field.type);
|
|
975
|
-
const incompatible = checkChainCompatibility(
|
|
976
|
-
field.type,
|
|
977
|
-
field.isList,
|
|
978
|
-
isEnum,
|
|
979
|
-
isEnum ? enumValues[field.type] : void 0,
|
|
980
|
-
result.methods
|
|
981
|
-
);
|
|
982
|
-
if (incompatible) {
|
|
983
|
-
const msg = `prisma-guard: @zod method "${incompatible}" on ${model.name}.${field.name} is not compatible with type "${field.type}"${field.isList ? "[]" : ""}`;
|
|
984
|
-
if (onInvalidZod === "error") {
|
|
985
|
-
throw new Error(msg);
|
|
986
|
-
}
|
|
987
|
-
console.warn(msg);
|
|
988
|
-
continue;
|
|
989
|
-
}
|
|
990
1049
|
const execError = verifyChainExecution(
|
|
991
1050
|
field.type,
|
|
992
1051
|
field.isList,
|
|
@@ -995,11 +1054,7 @@ function emitZodChains(dmmf, onInvalidZod) {
|
|
|
995
1054
|
chainStr
|
|
996
1055
|
);
|
|
997
1056
|
if (execError) {
|
|
998
|
-
|
|
999
|
-
if (onInvalidZod === "error") {
|
|
1000
|
-
throw new Error(msg);
|
|
1001
|
-
}
|
|
1002
|
-
console.warn(msg);
|
|
1057
|
+
fail(`prisma-guard: @zod directive on ${model.name}.${field.name} fails at schema construction: ${execError}`);
|
|
1003
1058
|
continue;
|
|
1004
1059
|
}
|
|
1005
1060
|
if (!modelChains[model.name])
|
|
@@ -1034,38 +1089,29 @@ ${entries}
|
|
|
1034
1089
|
|
|
1035
1090
|
// src/generator/index.ts
|
|
1036
1091
|
var { generatorHandler } = pkg;
|
|
1037
|
-
var
|
|
1038
|
-
var
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
|
-
function validateDepthConfig(raw) {
|
|
1062
|
-
const value = raw ?? "1";
|
|
1063
|
-
if (!VALID_TYPED_GUARD_DEPTH.has(value)) {
|
|
1064
|
-
throw new Error(
|
|
1065
|
-
`prisma-guard: Invalid generator config "typedGuardRelationDepth": "${value}". Allowed values: 0, 1, 2, 3`
|
|
1066
|
-
);
|
|
1067
|
-
}
|
|
1068
|
-
return Number(value);
|
|
1092
|
+
var booleanConfig = z3.enum(["true", "false"]).transform((v) => v === "true");
|
|
1093
|
+
var configSchema = z3.object({
|
|
1094
|
+
onInvalidZod: z3.enum(["error", "warn"]).default("error"),
|
|
1095
|
+
onAmbiguousScope: z3.enum(["error", "warn", "ignore"]).default("error"),
|
|
1096
|
+
onMissingScopeContext: z3.enum(["error", "warn", "ignore"]).default("error"),
|
|
1097
|
+
findUniqueMode: z3.enum(["verify", "reject"]).default("reject"),
|
|
1098
|
+
onScopeRelationWrite: z3.enum(["error", "warn", "strip"]).default("error"),
|
|
1099
|
+
strictDecimal: booleanConfig.default(false),
|
|
1100
|
+
enforceProjection: booleanConfig.default(false),
|
|
1101
|
+
typedGuardShapes: booleanConfig.default(true),
|
|
1102
|
+
typedGuardRelationDepth: z3.enum(["0", "1", "2", "3"]).default("1").transform((v) => Number(v)),
|
|
1103
|
+
importStyle: z3.enum(["auto", "none", "js", "ts"]).default("auto"),
|
|
1104
|
+
runtimeImportPath: z3.string().trim().min(1, "runtimeImportPath must be a non-empty string").default("prisma-guard")
|
|
1105
|
+
});
|
|
1106
|
+
function parseGeneratorConfig(raw) {
|
|
1107
|
+
const result = configSchema.safeParse(raw);
|
|
1108
|
+
if (result.success)
|
|
1109
|
+
return result.data;
|
|
1110
|
+
const issues = result.error.issues.map((issue) => {
|
|
1111
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
1112
|
+
return `"${path}": ${issue.message}`;
|
|
1113
|
+
}).join("; ");
|
|
1114
|
+
throw new Error(`prisma-guard: Invalid generator config: ${issues}`);
|
|
1069
1115
|
}
|
|
1070
1116
|
function emitZodDefaults(defaults) {
|
|
1071
1117
|
const entries = Object.entries(defaults);
|
|
@@ -1092,24 +1138,46 @@ function getProviderValue(provider) {
|
|
|
1092
1138
|
}
|
|
1093
1139
|
return "";
|
|
1094
1140
|
}
|
|
1095
|
-
function
|
|
1141
|
+
function classifyPrismaProvider(provider) {
|
|
1096
1142
|
const value = getProviderValue(provider);
|
|
1097
|
-
|
|
1143
|
+
if (value === "prisma-client-js" || value.endsWith("/prisma-client-js")) {
|
|
1144
|
+
return "prisma-client-js";
|
|
1145
|
+
}
|
|
1146
|
+
if (value === "prisma-client" || value.endsWith("/prisma-client")) {
|
|
1147
|
+
return "prisma-client";
|
|
1148
|
+
}
|
|
1149
|
+
return null;
|
|
1098
1150
|
}
|
|
1099
1151
|
function normalizeImportPath(path) {
|
|
1100
1152
|
const normalized = path.replace(/\\/g, "/");
|
|
1153
|
+
if (normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized)) {
|
|
1154
|
+
return "@prisma/client";
|
|
1155
|
+
}
|
|
1101
1156
|
if (normalized.startsWith("."))
|
|
1102
1157
|
return normalized;
|
|
1103
1158
|
return `./${normalized}`;
|
|
1104
1159
|
}
|
|
1105
1160
|
function resolvePrismaClientImport(options, guardOutput) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1161
|
+
let matched = null;
|
|
1162
|
+
for (const generator of options.otherGenerators) {
|
|
1163
|
+
const providerKind = classifyPrismaProvider(generator.provider);
|
|
1164
|
+
if (!providerKind)
|
|
1165
|
+
continue;
|
|
1166
|
+
const output = generator.output?.value;
|
|
1167
|
+
if (!output)
|
|
1168
|
+
continue;
|
|
1169
|
+
matched = { provider: providerKind, output };
|
|
1170
|
+
break;
|
|
1171
|
+
}
|
|
1172
|
+
if (!matched)
|
|
1173
|
+
return { path: "@prisma/client", kind: "package" };
|
|
1174
|
+
const rel = relative(guardOutput, matched.output);
|
|
1175
|
+
if (rel.length === 0)
|
|
1176
|
+
return { path: "@prisma/client", kind: "package" };
|
|
1177
|
+
return {
|
|
1178
|
+
path: normalizeImportPath(rel),
|
|
1179
|
+
kind: matched.provider
|
|
1180
|
+
};
|
|
1113
1181
|
}
|
|
1114
1182
|
generatorHandler({
|
|
1115
1183
|
onManifest() {
|
|
@@ -1122,80 +1190,48 @@ generatorHandler({
|
|
|
1122
1190
|
const output = options.generator.output?.value;
|
|
1123
1191
|
if (!output)
|
|
1124
1192
|
throw new Error("prisma-guard: No output directory specified");
|
|
1125
|
-
const
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
config.onInvalidZod ?? "error",
|
|
1129
|
-
VALID_ON_INVALID_ZOD
|
|
1130
|
-
);
|
|
1131
|
-
const onAmbiguousScope = validateConfigEnum(
|
|
1132
|
-
"onAmbiguousScope",
|
|
1133
|
-
config.onAmbiguousScope ?? "error",
|
|
1134
|
-
VALID_ON_AMBIGUOUS_SCOPE
|
|
1135
|
-
);
|
|
1136
|
-
const onMissingScopeContext = validateConfigEnum(
|
|
1137
|
-
"onMissingScopeContext",
|
|
1138
|
-
config.onMissingScopeContext ?? "error",
|
|
1139
|
-
VALID_ON_MISSING_SCOPE_CONTEXT
|
|
1140
|
-
);
|
|
1141
|
-
const findUniqueMode = validateConfigEnum(
|
|
1142
|
-
"findUniqueMode",
|
|
1143
|
-
config.findUniqueMode ?? "reject",
|
|
1144
|
-
VALID_FIND_UNIQUE_MODE
|
|
1145
|
-
);
|
|
1146
|
-
const onScopeRelationWrite = validateConfigEnum(
|
|
1147
|
-
"onScopeRelationWrite",
|
|
1148
|
-
config.onScopeRelationWrite ?? "error",
|
|
1149
|
-
VALID_ON_SCOPE_RELATION_WRITE
|
|
1150
|
-
);
|
|
1151
|
-
const strictDecimal = validateBooleanConfig(
|
|
1152
|
-
"strictDecimal",
|
|
1153
|
-
config.strictDecimal,
|
|
1154
|
-
false
|
|
1155
|
-
);
|
|
1156
|
-
const enforceProjection = validateBooleanConfig(
|
|
1157
|
-
"enforceProjection",
|
|
1158
|
-
config.enforceProjection,
|
|
1159
|
-
false
|
|
1160
|
-
);
|
|
1161
|
-
const typedGuardShapes = validateBooleanConfig(
|
|
1162
|
-
"typedGuardShapes",
|
|
1163
|
-
config.typedGuardShapes,
|
|
1164
|
-
true
|
|
1165
|
-
);
|
|
1166
|
-
const typedGuardRelationDepth = validateDepthConfig(
|
|
1167
|
-
config.typedGuardRelationDepth
|
|
1168
|
-
);
|
|
1193
|
+
const rawConfig = options.generator.config ?? {};
|
|
1194
|
+
const cfg = parseGeneratorConfig(rawConfig);
|
|
1195
|
+
const importStyle = resolveImportStyle(output, cfg.importStyle);
|
|
1169
1196
|
const dmmf = options.dmmf;
|
|
1170
1197
|
const parts = [];
|
|
1171
1198
|
parts.push(
|
|
1172
1199
|
`export const GUARD_CONFIG = {
|
|
1173
|
-
onMissingScopeContext: ${JSON.stringify(onMissingScopeContext)},
|
|
1174
|
-
findUniqueMode: ${JSON.stringify(findUniqueMode)},
|
|
1175
|
-
onScopeRelationWrite: ${JSON.stringify(onScopeRelationWrite)},
|
|
1176
|
-
strictDecimal: ${JSON.stringify(strictDecimal)},
|
|
1177
|
-
enforceProjection: ${JSON.stringify(enforceProjection)},
|
|
1200
|
+
onMissingScopeContext: ${JSON.stringify(cfg.onMissingScopeContext)},
|
|
1201
|
+
findUniqueMode: ${JSON.stringify(cfg.findUniqueMode)},
|
|
1202
|
+
onScopeRelationWrite: ${JSON.stringify(cfg.onScopeRelationWrite)},
|
|
1203
|
+
strictDecimal: ${JSON.stringify(cfg.strictDecimal)},
|
|
1204
|
+
enforceProjection: ${JSON.stringify(cfg.enforceProjection)},
|
|
1178
1205
|
} as const
|
|
1179
1206
|
`
|
|
1180
1207
|
);
|
|
1181
|
-
const { source: scopeSource } = emitScopeMap(dmmf, onAmbiguousScope);
|
|
1208
|
+
const { source: scopeSource } = emitScopeMap(dmmf, cfg.onAmbiguousScope);
|
|
1182
1209
|
parts.push(scopeSource);
|
|
1183
1210
|
const typeMapSource = emitTypeMap(dmmf);
|
|
1184
1211
|
parts.push(typeMapSource);
|
|
1185
|
-
const { source: zodChainsSource, defaults } = emitZodChains(dmmf, onInvalidZod);
|
|
1212
|
+
const { source: zodChainsSource, defaults } = emitZodChains(dmmf, cfg.onInvalidZod);
|
|
1186
1213
|
parts.push(zodChainsSource);
|
|
1187
1214
|
parts.push(emitZodDefaults(defaults));
|
|
1188
1215
|
mkdirSync(output, { recursive: true });
|
|
1189
1216
|
writeFileSync(`${output}/index.ts`, parts.join("\n"), "utf-8");
|
|
1190
|
-
const prismaClientImport = resolvePrismaClientImport(options, output);
|
|
1191
|
-
const clientSource = emitClient(
|
|
1217
|
+
const { path: prismaClientImport, kind: prismaClientKind } = resolvePrismaClientImport(options, output);
|
|
1218
|
+
const clientSource = emitClient(
|
|
1219
|
+
dmmf,
|
|
1220
|
+
prismaClientImport,
|
|
1221
|
+
prismaClientKind,
|
|
1222
|
+
importStyle,
|
|
1223
|
+
cfg.runtimeImportPath
|
|
1224
|
+
);
|
|
1192
1225
|
writeFileSync(`${output}/client.ts`, clientSource, "utf-8");
|
|
1193
|
-
|
|
1226
|
+
const shapesPath = `${output}/shapes.ts`;
|
|
1227
|
+
if (cfg.typedGuardShapes) {
|
|
1194
1228
|
writeFileSync(
|
|
1195
|
-
|
|
1196
|
-
emitTypedShapes(dmmf, typedGuardRelationDepth),
|
|
1229
|
+
shapesPath,
|
|
1230
|
+
emitTypedShapes(dmmf, cfg.typedGuardRelationDepth, importStyle, cfg.runtimeImportPath),
|
|
1197
1231
|
"utf-8"
|
|
1198
1232
|
);
|
|
1233
|
+
} else if (existsSync2(shapesPath)) {
|
|
1234
|
+
rmSync(shapesPath, { force: true });
|
|
1199
1235
|
}
|
|
1200
1236
|
}
|
|
1201
1237
|
});
|