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.
@@ -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}
@@ -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", "by", "having", "_count", "_avg", "_sum", "_min", "_max", "orderBy", "take", "skip"],
267
- create: ["data", "select", "include"],
268
- createMany: ["data"],
269
- createManyAndReturn: ["data", "select", "include"],
270
- update: ["data", "where", "select", "include"],
271
- updateMany: ["data", "where"],
272
- updateManyAndReturn: ["data", "where", "select", "include"],
273
- upsert: ["where", "create", "update", "select", "include"],
274
- delete: ["where", "select", "include"],
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 header = `import type { TYPE_MAP, UNIQUE_MAP } from './index'
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 'prisma-guard'
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 null;
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
- 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);
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
- 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);
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 (!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);
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
- 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);
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 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);
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 isPrismaClientProvider(provider) {
1141
+ function classifyPrismaProvider(provider) {
1096
1142
  const value = getProviderValue(provider);
1097
- return value === "prisma-client-js" || value.endsWith("/prisma-client-js");
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
- 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));
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 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
- );
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(dmmf, prismaClientImport);
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
- if (typedGuardShapes) {
1226
+ const shapesPath = `${output}/shapes.ts`;
1227
+ if (cfg.typedGuardShapes) {
1194
1228
  writeFileSync(
1195
- `${output}/shapes.ts`,
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
  });