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.
@@ -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
- return `import type { PrismaClient } from '${prismaClientImport}'
26
- import type { GuardInput, GuardedModel } from 'prisma-guard'
27
- import { createGuard } from 'prisma-guard'
28
- import { SCOPE_MAP, TYPE_MAP, ENUM_MAP, ZOD_CHAINS, GUARD_CONFIG, UNIQUE_MAP, ZOD_DEFAULTS } from './index'
29
- import type { ScopeRoot } from './index'
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 msg = `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.`;
70
- if (onAmbiguousScope === "error") {
71
- throw new Error(`prisma-guard: ${msg}`);
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 msg = `Model "${model.name}" has multiple relations to scope root "${root}" (${relNames.join(", ")}).`;
97
- if (onAmbiguousScope === "error") {
98
- throw new Error(
99
- `prisma-guard: Ambiguous scope detected. Resolve these or set onAmbiguousScope to "warn" or "ignore":
100
- - ${msg}`
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((e) => `{ fk: ${JSON.stringify(e.fk)}, root: ${JSON.stringify(e.root)}, relationName: ${JSON.stringify(e.relationName)} }`).join(", ");
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/generator/emit-type-map.ts
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 meta = [
199
- `type: ${JSON.stringify(field.type)}`,
200
- `isList: ${field.isList}`,
201
- `isRequired: ${field.isRequired}`,
202
- `isId: ${field.isId}`,
203
- `isRelation: ${isRelation}`,
204
- `hasDefault: ${field.hasDefaultValue}`,
205
- `isUpdatedAt: ${field.isUpdatedAt}`
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
- meta.push(`isEnum: true`);
302
+ metaPairs.push(["isEnum", true]);
209
303
  if (isUnsupported)
210
- meta.push(`isUnsupported: true`);
304
+ metaPairs.push(["isUnsupported", true]);
211
305
  if (field.isUnique)
212
- meta.push(`isUnique: true`);
213
- return ` ${JSON.stringify(field.name)}: { ${meta.join(", ")} },`;
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 header = `import type { TYPE_MAP, UNIQUE_MAP } from './index'
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 'prisma-guard'
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 null;
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
- const msg = `prisma-guard: Multiple @zod directives on ${model.name}.${field.name}. Only one @zod per field allowed.`;
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
- const msg = `prisma-guard: Empty @zod directive on ${model.name}.${field.name}. Add a method chain (e.g. @zod .min(1)) or remove the directive.`;
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 (!result.valid) {
967
- const msg = `prisma-guard: Invalid @zod directive on ${model.name}.${field.name}: ${result.reason}`;
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
- const msg = `prisma-guard: @zod directive on ${model.name}.${field.name} fails at schema construction: ${execError}`;
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 VALID_ON_INVALID_ZOD = /* @__PURE__ */ new Set(["error", "warn"]);
1038
- var VALID_ON_AMBIGUOUS_SCOPE = /* @__PURE__ */ new Set(["error", "warn", "ignore"]);
1039
- var VALID_ON_MISSING_SCOPE_CONTEXT = /* @__PURE__ */ new Set(["error", "warn", "ignore"]);
1040
- var VALID_FIND_UNIQUE_MODE = /* @__PURE__ */ new Set(["verify", "reject"]);
1041
- var VALID_ON_SCOPE_RELATION_WRITE = /* @__PURE__ */ new Set(["error", "warn", "strip"]);
1042
- var VALID_BOOLEAN_CONFIG = /* @__PURE__ */ new Set(["true", "false"]);
1043
- var VALID_TYPED_GUARD_DEPTH = /* @__PURE__ */ new Set(["0", "1", "2", "3"]);
1044
- function validateConfigEnum(name, value, allowed) {
1045
- if (!allowed.has(value)) {
1046
- throw new Error(
1047
- `prisma-guard: Invalid generator config "${name}": "${value}". Allowed values: ${[...allowed].join(", ")}`
1048
- );
1049
- }
1050
- return value;
1051
- }
1052
- function validateBooleanConfig(name, raw, fallback) {
1053
- const value = raw ?? (fallback ? "true" : "false");
1054
- if (!VALID_BOOLEAN_CONFIG.has(value)) {
1055
- throw new Error(
1056
- `prisma-guard: Invalid generator config "${name}": "${value}". Allowed values: true, false`
1057
- );
1058
- }
1059
- return value === "true";
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 isPrismaClientProvider(provider) {
1140
+ function classifyPrismaProvider(provider) {
1096
1141
  const value = getProviderValue(provider);
1097
- return value === "prisma-client-js" || value.endsWith("/prisma-client-js");
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
- const prismaClientGenerator = options.otherGenerators.find(
1107
- (generator) => isPrismaClientProvider(generator.provider)
1108
- );
1109
- const clientOutput = prismaClientGenerator?.output?.value;
1110
- if (!clientOutput)
1111
- return "@prisma/client";
1112
- return normalizeImportPath(relative(guardOutput, clientOutput));
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 config = options.generator.config ?? {};
1126
- const onInvalidZod = validateConfigEnum(
1127
- "onInvalidZod",
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(dmmf, prismaClientImport);
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
- if (typedGuardShapes) {
1225
+ const shapesPath = `${output}/shapes.ts`;
1226
+ if (cfg.typedGuardShapes) {
1194
1227
  writeFileSync(
1195
- `${output}/shapes.ts`,
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
  });