prisma-guard 1.27.0 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -33
- package/dist/generator/index.js +244 -209
- package/dist/generator/index.js.map +1 -1
- package/dist/runtime/index.cjs +1664 -1663
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +68 -35
- package/dist/runtime/index.d.ts +68 -35
- package/dist/runtime/index.js +1663 -1662
- 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}
|
|
@@ -260,7 +354,6 @@ var OPERATION_SHAPE_KEYS = {
|
|
|
260
354
|
findFirstOrThrow: ["where", "include", "select", "orderBy", "cursor", "take", "skip", "distinct"],
|
|
261
355
|
findUnique: ["where", "include", "select"],
|
|
262
356
|
findUniqueOrThrow: ["where", "include", "select"],
|
|
263
|
-
findManyPaginated: ["where", "include", "select", "orderBy", "cursor", "take", "skip", "distinct"],
|
|
264
357
|
count: ["where", "select", "cursor", "orderBy", "skip", "take"],
|
|
265
358
|
aggregate: ["where", "orderBy", "cursor", "take", "skip", "_count", "_avg", "_sum", "_min", "_max"],
|
|
266
359
|
groupBy: ["where", "by", "having", "_count", "_avg", "_sum", "_min", "_max", "orderBy", "take", "skip"],
|
|
@@ -301,8 +394,9 @@ var OPERATIONS = Object.keys(OPERATION_SHAPE_KEYS);
|
|
|
301
394
|
function cap(s) {
|
|
302
395
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
303
396
|
}
|
|
304
|
-
function emitTypedShapes(dmmf, depth) {
|
|
305
|
-
const
|
|
397
|
+
function emitTypedShapes(dmmf, depth, importStyle, runtimeImportPath) {
|
|
398
|
+
const indexImport = withImportStyle("./index", importStyle);
|
|
399
|
+
const header = `import type { TYPE_MAP, UNIQUE_MAP } from '${indexImport}'
|
|
306
400
|
import type {
|
|
307
401
|
TypedGuardShape,
|
|
308
402
|
OperationShape,
|
|
@@ -310,7 +404,7 @@ import type {
|
|
|
310
404
|
TypedProjection,
|
|
311
405
|
TypedInclude,
|
|
312
406
|
TypedCountSelect,
|
|
313
|
-
} from '
|
|
407
|
+
} from '${runtimeImportPath}'
|
|
314
408
|
|
|
315
409
|
type TM = typeof TYPE_MAP
|
|
316
410
|
type UM = typeof UNIQUE_MAP
|
|
@@ -766,6 +860,18 @@ function validateDirective(raw) {
|
|
|
766
860
|
|
|
767
861
|
// src/shared/scalar-base.ts
|
|
768
862
|
import { z } from "zod";
|
|
863
|
+
function isPrismaNullSentinel(value) {
|
|
864
|
+
if (value === null || typeof value !== "object")
|
|
865
|
+
return false;
|
|
866
|
+
const tag = value._tag;
|
|
867
|
+
if (tag === "DbNull" || tag === "JsonNull" || tag === "AnyNull")
|
|
868
|
+
return true;
|
|
869
|
+
const constructorName = value.constructor?.name;
|
|
870
|
+
if (constructorName === "DbNull" || constructorName === "JsonNull" || constructorName === "AnyNull") {
|
|
871
|
+
return true;
|
|
872
|
+
}
|
|
873
|
+
return false;
|
|
874
|
+
}
|
|
769
875
|
function isJsonSafe(value) {
|
|
770
876
|
const stack = [{ tag: "visit", value }];
|
|
771
877
|
const ancestors = /* @__PURE__ */ new Set();
|
|
@@ -780,6 +886,8 @@ function isJsonSafe(value) {
|
|
|
780
886
|
return false;
|
|
781
887
|
if (current === null)
|
|
782
888
|
continue;
|
|
889
|
+
if (isPrismaNullSentinel(current))
|
|
890
|
+
continue;
|
|
783
891
|
switch (typeof current) {
|
|
784
892
|
case "string":
|
|
785
893
|
case "boolean":
|
|
@@ -873,39 +981,11 @@ function buildGenerationBase(fieldType, isList, isEnum, enumValues) {
|
|
|
873
981
|
base = z2.array(base);
|
|
874
982
|
return base;
|
|
875
983
|
}
|
|
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
984
|
function verifyChainExecution(fieldType, isList, isEnum, enumValues, chainStr) {
|
|
906
985
|
const base = buildGenerationBase(fieldType, isList, isEnum, enumValues);
|
|
907
|
-
if (!base)
|
|
908
|
-
return
|
|
986
|
+
if (!base) {
|
|
987
|
+
return `Unsupported field type "${fieldType}" for @zod directive`;
|
|
988
|
+
}
|
|
909
989
|
let fn;
|
|
910
990
|
try {
|
|
911
991
|
fn = new Function("base", `'use strict'; return base${chainStr}`);
|
|
@@ -936,6 +1016,11 @@ function emitZodChains(dmmf, onInvalidZod) {
|
|
|
936
1016
|
}
|
|
937
1017
|
const modelChains = {};
|
|
938
1018
|
const defaults = {};
|
|
1019
|
+
function fail(msg) {
|
|
1020
|
+
if (onInvalidZod === "error")
|
|
1021
|
+
throw new Error(msg);
|
|
1022
|
+
console.warn(msg);
|
|
1023
|
+
}
|
|
939
1024
|
for (const model of dmmf.datamodel.models) {
|
|
940
1025
|
for (const field of model.fields) {
|
|
941
1026
|
if (!field.documentation)
|
|
@@ -944,49 +1029,22 @@ function emitZodChains(dmmf, onInvalidZod) {
|
|
|
944
1029
|
if (zodLines.length === 0)
|
|
945
1030
|
continue;
|
|
946
1031
|
if (zodLines.length > 1) {
|
|
947
|
-
|
|
948
|
-
if (onInvalidZod === "error") {
|
|
949
|
-
throw new Error(msg);
|
|
950
|
-
}
|
|
951
|
-
console.warn(msg);
|
|
1032
|
+
fail(`prisma-guard: Multiple @zod directives on ${model.name}.${field.name}. Only one @zod per field allowed.`);
|
|
952
1033
|
continue;
|
|
953
1034
|
}
|
|
954
1035
|
const line = zodLines[0];
|
|
955
1036
|
const idx = line.indexOf("@zod");
|
|
956
1037
|
const chainStr = line.slice(idx + 4).trim();
|
|
957
1038
|
if (chainStr.length === 0) {
|
|
958
|
-
|
|
959
|
-
if (onInvalidZod === "error") {
|
|
960
|
-
throw new Error(msg);
|
|
961
|
-
}
|
|
962
|
-
console.warn(msg);
|
|
1039
|
+
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
1040
|
continue;
|
|
964
1041
|
}
|
|
965
1042
|
const result = validateDirective(chainStr);
|
|
966
|
-
if (
|
|
967
|
-
|
|
968
|
-
if (onInvalidZod === "error") {
|
|
969
|
-
throw new Error(msg);
|
|
970
|
-
}
|
|
971
|
-
console.warn(msg);
|
|
1043
|
+
if (result.valid === false) {
|
|
1044
|
+
fail(`prisma-guard: Invalid @zod directive on ${model.name}.${field.name}: ${result.reason}`);
|
|
972
1045
|
continue;
|
|
973
1046
|
}
|
|
974
1047
|
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
1048
|
const execError = verifyChainExecution(
|
|
991
1049
|
field.type,
|
|
992
1050
|
field.isList,
|
|
@@ -995,11 +1053,7 @@ function emitZodChains(dmmf, onInvalidZod) {
|
|
|
995
1053
|
chainStr
|
|
996
1054
|
);
|
|
997
1055
|
if (execError) {
|
|
998
|
-
|
|
999
|
-
if (onInvalidZod === "error") {
|
|
1000
|
-
throw new Error(msg);
|
|
1001
|
-
}
|
|
1002
|
-
console.warn(msg);
|
|
1056
|
+
fail(`prisma-guard: @zod directive on ${model.name}.${field.name} fails at schema construction: ${execError}`);
|
|
1003
1057
|
continue;
|
|
1004
1058
|
}
|
|
1005
1059
|
if (!modelChains[model.name])
|
|
@@ -1034,38 +1088,29 @@ ${entries}
|
|
|
1034
1088
|
|
|
1035
1089
|
// src/generator/index.ts
|
|
1036
1090
|
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);
|
|
1091
|
+
var booleanConfig = z3.enum(["true", "false"]).transform((v) => v === "true");
|
|
1092
|
+
var configSchema = z3.object({
|
|
1093
|
+
onInvalidZod: z3.enum(["error", "warn"]).default("error"),
|
|
1094
|
+
onAmbiguousScope: z3.enum(["error", "warn", "ignore"]).default("error"),
|
|
1095
|
+
onMissingScopeContext: z3.enum(["error", "warn", "ignore"]).default("error"),
|
|
1096
|
+
findUniqueMode: z3.enum(["verify", "reject"]).default("reject"),
|
|
1097
|
+
onScopeRelationWrite: z3.enum(["error", "warn", "strip"]).default("error"),
|
|
1098
|
+
strictDecimal: booleanConfig.default(false),
|
|
1099
|
+
enforceProjection: booleanConfig.default(false),
|
|
1100
|
+
typedGuardShapes: booleanConfig.default(true),
|
|
1101
|
+
typedGuardRelationDepth: z3.enum(["0", "1", "2", "3"]).default("1").transform((v) => Number(v)),
|
|
1102
|
+
importStyle: z3.enum(["auto", "none", "js", "ts"]).default("auto"),
|
|
1103
|
+
runtimeImportPath: z3.string().trim().min(1, "runtimeImportPath must be a non-empty string").default("prisma-guard")
|
|
1104
|
+
});
|
|
1105
|
+
function parseGeneratorConfig(raw) {
|
|
1106
|
+
const result = configSchema.safeParse(raw);
|
|
1107
|
+
if (result.success)
|
|
1108
|
+
return result.data;
|
|
1109
|
+
const issues = result.error.issues.map((issue) => {
|
|
1110
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "(root)";
|
|
1111
|
+
return `"${path}": ${issue.message}`;
|
|
1112
|
+
}).join("; ");
|
|
1113
|
+
throw new Error(`prisma-guard: Invalid generator config: ${issues}`);
|
|
1069
1114
|
}
|
|
1070
1115
|
function emitZodDefaults(defaults) {
|
|
1071
1116
|
const entries = Object.entries(defaults);
|
|
@@ -1092,24 +1137,46 @@ function getProviderValue(provider) {
|
|
|
1092
1137
|
}
|
|
1093
1138
|
return "";
|
|
1094
1139
|
}
|
|
1095
|
-
function
|
|
1140
|
+
function classifyPrismaProvider(provider) {
|
|
1096
1141
|
const value = getProviderValue(provider);
|
|
1097
|
-
|
|
1142
|
+
if (value === "prisma-client-js" || value.endsWith("/prisma-client-js")) {
|
|
1143
|
+
return "prisma-client-js";
|
|
1144
|
+
}
|
|
1145
|
+
if (value === "prisma-client" || value.endsWith("/prisma-client")) {
|
|
1146
|
+
return "prisma-client";
|
|
1147
|
+
}
|
|
1148
|
+
return null;
|
|
1098
1149
|
}
|
|
1099
1150
|
function normalizeImportPath(path) {
|
|
1100
1151
|
const normalized = path.replace(/\\/g, "/");
|
|
1152
|
+
if (normalized.startsWith("/") || /^[A-Za-z]:/.test(normalized)) {
|
|
1153
|
+
return "@prisma/client";
|
|
1154
|
+
}
|
|
1101
1155
|
if (normalized.startsWith("."))
|
|
1102
1156
|
return normalized;
|
|
1103
1157
|
return `./${normalized}`;
|
|
1104
1158
|
}
|
|
1105
1159
|
function resolvePrismaClientImport(options, guardOutput) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1160
|
+
let matched = null;
|
|
1161
|
+
for (const generator of options.otherGenerators) {
|
|
1162
|
+
const providerKind = classifyPrismaProvider(generator.provider);
|
|
1163
|
+
if (!providerKind)
|
|
1164
|
+
continue;
|
|
1165
|
+
const output = generator.output?.value;
|
|
1166
|
+
if (!output)
|
|
1167
|
+
continue;
|
|
1168
|
+
matched = { provider: providerKind, output };
|
|
1169
|
+
break;
|
|
1170
|
+
}
|
|
1171
|
+
if (!matched)
|
|
1172
|
+
return { path: "@prisma/client", kind: "package" };
|
|
1173
|
+
const rel = relative(guardOutput, matched.output);
|
|
1174
|
+
if (rel.length === 0)
|
|
1175
|
+
return { path: "@prisma/client", kind: "package" };
|
|
1176
|
+
return {
|
|
1177
|
+
path: normalizeImportPath(rel),
|
|
1178
|
+
kind: matched.provider
|
|
1179
|
+
};
|
|
1113
1180
|
}
|
|
1114
1181
|
generatorHandler({
|
|
1115
1182
|
onManifest() {
|
|
@@ -1122,80 +1189,48 @@ generatorHandler({
|
|
|
1122
1189
|
const output = options.generator.output?.value;
|
|
1123
1190
|
if (!output)
|
|
1124
1191
|
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
|
-
);
|
|
1192
|
+
const rawConfig = options.generator.config ?? {};
|
|
1193
|
+
const cfg = parseGeneratorConfig(rawConfig);
|
|
1194
|
+
const importStyle = resolveImportStyle(output, cfg.importStyle);
|
|
1169
1195
|
const dmmf = options.dmmf;
|
|
1170
1196
|
const parts = [];
|
|
1171
1197
|
parts.push(
|
|
1172
1198
|
`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)},
|
|
1199
|
+
onMissingScopeContext: ${JSON.stringify(cfg.onMissingScopeContext)},
|
|
1200
|
+
findUniqueMode: ${JSON.stringify(cfg.findUniqueMode)},
|
|
1201
|
+
onScopeRelationWrite: ${JSON.stringify(cfg.onScopeRelationWrite)},
|
|
1202
|
+
strictDecimal: ${JSON.stringify(cfg.strictDecimal)},
|
|
1203
|
+
enforceProjection: ${JSON.stringify(cfg.enforceProjection)},
|
|
1178
1204
|
} as const
|
|
1179
1205
|
`
|
|
1180
1206
|
);
|
|
1181
|
-
const { source: scopeSource } = emitScopeMap(dmmf, onAmbiguousScope);
|
|
1207
|
+
const { source: scopeSource } = emitScopeMap(dmmf, cfg.onAmbiguousScope);
|
|
1182
1208
|
parts.push(scopeSource);
|
|
1183
1209
|
const typeMapSource = emitTypeMap(dmmf);
|
|
1184
1210
|
parts.push(typeMapSource);
|
|
1185
|
-
const { source: zodChainsSource, defaults } = emitZodChains(dmmf, onInvalidZod);
|
|
1211
|
+
const { source: zodChainsSource, defaults } = emitZodChains(dmmf, cfg.onInvalidZod);
|
|
1186
1212
|
parts.push(zodChainsSource);
|
|
1187
1213
|
parts.push(emitZodDefaults(defaults));
|
|
1188
1214
|
mkdirSync(output, { recursive: true });
|
|
1189
1215
|
writeFileSync(`${output}/index.ts`, parts.join("\n"), "utf-8");
|
|
1190
|
-
const prismaClientImport = resolvePrismaClientImport(options, output);
|
|
1191
|
-
const clientSource = emitClient(
|
|
1216
|
+
const { path: prismaClientImport, kind: prismaClientKind } = resolvePrismaClientImport(options, output);
|
|
1217
|
+
const clientSource = emitClient(
|
|
1218
|
+
dmmf,
|
|
1219
|
+
prismaClientImport,
|
|
1220
|
+
prismaClientKind,
|
|
1221
|
+
importStyle,
|
|
1222
|
+
cfg.runtimeImportPath
|
|
1223
|
+
);
|
|
1192
1224
|
writeFileSync(`${output}/client.ts`, clientSource, "utf-8");
|
|
1193
|
-
|
|
1225
|
+
const shapesPath = `${output}/shapes.ts`;
|
|
1226
|
+
if (cfg.typedGuardShapes) {
|
|
1194
1227
|
writeFileSync(
|
|
1195
|
-
|
|
1196
|
-
emitTypedShapes(dmmf, typedGuardRelationDepth),
|
|
1228
|
+
shapesPath,
|
|
1229
|
+
emitTypedShapes(dmmf, cfg.typedGuardRelationDepth, importStyle, cfg.runtimeImportPath),
|
|
1197
1230
|
"utf-8"
|
|
1198
1231
|
);
|
|
1232
|
+
} else if (existsSync2(shapesPath)) {
|
|
1233
|
+
rmSync(shapesPath, { force: true });
|
|
1199
1234
|
}
|
|
1200
1235
|
}
|
|
1201
1236
|
});
|