prisma-to-zod-v4 0.6.3 → 0.6.5

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.
Files changed (3) hide show
  1. package/README.md +26 -15
  2. package/dist/index.js +128 -49
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -125,27 +125,38 @@ This project uses pnpm.
125
125
 
126
126
  ```prisma
127
127
  generator zod {
128
- provider = "prisma-to-zod-v4"
129
- output = "./zod" // (default) the directory where generated zod schemas will be saved
128
+ provider = "prisma-to-zod-v4"
130
129
 
131
- relationModel = true // (default) Create and export both plain and related models.
132
- // relationModel = "default" // Do not export model without relations.
133
- // relationModel = false // Do not generate related model
130
+ // Output directory for generated Zod schemas
131
+ output = "./zod" // default
134
132
 
135
- modelCase = "PascalCase" // (default) Output models using pascal case (ex. UserModel, PostModel)
136
- // modelCase = "camelCase" // Output models using camel case (ex. userModel, postModel)
133
+ // Relation model generation
134
+ relationModel = true // default: generate both plain and related models
135
+ // relationModel = "default" // generate only related models (no plain models)
136
+ // relationModel = false // disable related model generation
137
137
 
138
- modelSuffix = "Model" // (default) Suffix to apply to your prisma models when naming Zod schemas
138
+ // Naming conventions
139
+ modelCase = "PascalCase" // default: UserModel, PostModel
140
+ // modelCase = "camelCase" // userModel, postModel
139
141
 
140
- // useDecimalJs = false // (default) represent the prisma Decimal type using as a JS number
141
- useDecimalJs = true // represent the prisma Decimal type using Decimal.js (as Prisma does)
142
+ // Suffix appended to generated Zod schemas
143
+ modelSuffix = "Model" // default
142
144
 
143
- imports = null // (default) will import the referenced file in generated schemas to be used via imports.someExportedVariable
145
+ // Decimal handling
146
+ // useDecimalJs = false // default: represent Prisma Decimal as number
147
+ useDecimalJs = true // represent Prisma Decimal using Decimal.js (matches Prisma behavior)
144
148
 
145
- // https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-by-null-values
146
- prismaJsonNullability = true // (default) uses prisma's scheme for JSON field nullability
147
- // prismaJsonNullability = false // allows null assignment to optional JSON fields
148
- }
149
+ // Enable coercion for input schemas (e.g. strings → numbers, dates)
150
+ useCoerce = true
151
+
152
+ // Custom imports for generated schemas
153
+ imports = null // default: no additional imports
154
+
155
+ // JSON field nullability behavior
156
+ // See: https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-by-null-values
157
+ prismaJsonNullability = true // default: follow Prisma's JSON nullability rules
158
+ // prismaJsonNullability = false // allow null assignment to optional JSON fields
159
+ }
149
160
  ```
150
161
 
151
162
  3. Run `npx prisma generate` or `pnpm prisma generate` to generate your zod schemas
package/dist/index.js CHANGED
@@ -213047,7 +213047,7 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
213047
213047
  });
213048
213048
 
213049
213049
  // package.json
213050
- var version = "0.6.2";
213050
+ var version = "0.6.5";
213051
213051
 
213052
213052
  // src/index.ts
213053
213053
  var import_generator_helper = require("@prisma/generator-helper");
@@ -213057,14 +213057,16 @@ var import_path2 = __toESM(require("path"));
213057
213057
  // src/config.ts
213058
213058
  var import_zod = require("zod");
213059
213059
  var stringBoolean = import_zod.z.enum(["true", "false"]);
213060
- var configBoolean = stringBoolean.default("true").transform((arg) => arg === "true");
213060
+ var configBoolean = stringBoolean.transform((arg) => arg === "true");
213061
213061
  var configSchema = import_zod.z.object({
213062
- relationModel: import_zod.z.union([import_zod.z.literal("default"), configBoolean]),
213062
+ imports: import_zod.z.string().optional(),
213063
213063
  modelSuffix: import_zod.z.string().default("Model"),
213064
213064
  modelCase: import_zod.z.enum(["PascalCase", "camelCase"]).default("PascalCase"),
213065
- useDecimalJs: configBoolean,
213066
- imports: import_zod.z.string().optional(),
213067
- prismaJsonNullability: configBoolean
213065
+ useCoerce: configBoolean.default(false),
213066
+ useDecimalJs: configBoolean.default(true),
213067
+ useStandaloneEnums: configBoolean.default(true),
213068
+ relationModel: import_zod.z.union([import_zod.z.literal("default"), configBoolean.default(true)]),
213069
+ prismaJsonNullability: configBoolean.default(true)
213068
213070
  });
213069
213071
 
213070
213072
  // src/generator.ts
@@ -213132,25 +213134,27 @@ var computeModifiers = (docString) => {
213132
213134
  };
213133
213135
 
213134
213136
  // src/types.ts
213135
- var getZodConstructor = (field, getRelatedModelName = (name) => name.toString(), nativeType) => {
213137
+ var getZodConstructor = (field, getRelatedModelName = (name) => name.toString(), nativeType, config) => {
213136
213138
  var _a, _b, _c, _d;
213137
213139
  let zodType = "z.unknown()";
213138
- let extraModifiers = [""];
213140
+ const useCoerce = (config == null ? void 0 : config.useCoerce) ?? false;
213141
+ const zodVar = useCoerce ? "z.coerce" : "z";
213142
+ const extraModifiers = [""];
213139
213143
  if (field.kind === "scalar") {
213140
213144
  switch (field.type) {
213141
213145
  case "String":
213142
- zodType = "z.string()";
213146
+ zodType = zodVar + ".string()";
213143
213147
  if (nativeType == null ? void 0 : nativeType.match(/^Uuid/)) zodType = "z.uuid()";
213144
- else if (nativeType == null ? void 0 : nativeType.match(/^Citext/)) zodType = "z.string().toLowerCase()";
213148
+ else if (nativeType == null ? void 0 : nativeType.match(/^Citext/)) zodType = zodVar + ".string().toLowerCase()";
213145
213149
  else if (nativeType == null ? void 0 : nativeType.match(/^VarChar\(\d+\)/)) {
213146
213150
  const length = (_a = nativeType.match(/VarChar\((\d+)\)/)) == null ? void 0 : _a[1];
213147
213151
  if (length) extraModifiers.push(`max(${length})`);
213148
213152
  } else if (nativeType == null ? void 0 : nativeType.match(/^Char\(\d+\)/)) {
213149
213153
  const length = (_b = nativeType.match(/Char\((\d+)\)/)) == null ? void 0 : _b[1];
213150
213154
  if (length) extraModifiers.push(`max(${length})`);
213151
- } else if (nativeType == null ? void 0 : nativeType.match(/^Text/)) zodType = "z.string()";
213152
- else if (nativeType == null ? void 0 : nativeType.match(/^VarBit/)) zodType = "z.string()";
213153
- else if (nativeType == null ? void 0 : nativeType.match(/^Bit/)) zodType = "z.string()";
213155
+ } else if (nativeType == null ? void 0 : nativeType.match(/^Text/)) zodType = zodVar + ".string()";
213156
+ else if (nativeType == null ? void 0 : nativeType.match(/^VarBit/)) zodType = zodVar + ".string()";
213157
+ else if (nativeType == null ? void 0 : nativeType.match(/^Bit/)) zodType = zodVar + ".string()";
213154
213158
  else if (nativeType == null ? void 0 : nativeType.match(/^VarBinary/)) zodType = "z.unknown()";
213155
213159
  else if (nativeType == null ? void 0 : nativeType.match(/^Binary/)) zodType = "z.unknown()";
213156
213160
  else if (nativeType == null ? void 0 : nativeType.match(/^NVarChar\(\d+\)/)) {
@@ -213159,11 +213163,11 @@ var getZodConstructor = (field, getRelatedModelName = (name) => name.toString(),
213159
213163
  } else if (nativeType == null ? void 0 : nativeType.match(/^NChar\(\d+\)/)) {
213160
213164
  const length = (_d = nativeType.match(/NChar\((\d+)\)/)) == null ? void 0 : _d[1];
213161
213165
  if (length) extraModifiers.push(`max(${length})`);
213162
- } else if (nativeType == null ? void 0 : nativeType.match(/^NText/)) zodType = "z.string()";
213166
+ } else if (nativeType == null ? void 0 : nativeType.match(/^NText/)) zodType = zodVar + ".string()";
213163
213167
  else if (nativeType == null ? void 0 : nativeType.match(/^ObjectId/)) extraModifiers.push("regex(/^[0-9a-f]{24}$/i)");
213164
213168
  break;
213165
213169
  case "Int":
213166
- zodType = "z.number()";
213170
+ zodType = zodVar + ".number()";
213167
213171
  extraModifiers.push("int()");
213168
213172
  if (nativeType == null ? void 0 : nativeType.match(/^SmallInt/)) extraModifiers.push("min(-32768)", "max(32767)");
213169
213173
  else if (nativeType == null ? void 0 : nativeType.match(/^UnsignedInt/)) extraModifiers.push("min(0)");
@@ -213172,13 +213176,13 @@ var getZodConstructor = (field, getRelatedModelName = (name) => name.toString(),
213172
213176
  else if (nativeType == null ? void 0 : nativeType.match(/^TinyInt/)) extraModifiers.push("min(-128)", "max(127)");
213173
213177
  break;
213174
213178
  case "BigInt":
213175
- zodType = "z.bigint()";
213179
+ zodType = zodVar + ".bigint()";
213176
213180
  break;
213177
213181
  case "Float":
213178
- zodType = "z.coerce.number()";
213182
+ zodType = zodVar + ".number()";
213179
213183
  break;
213180
213184
  case "Decimal":
213181
- zodType = "z.coerce.number()";
213185
+ zodType = zodVar + ".number()";
213182
213186
  if (nativeType == null ? void 0 : nativeType.match(/^Numeric\(\d+,\d+\)/)) {
213183
213187
  const match = nativeType.match(/Numeric\((\d+),(\d+)\)/);
213184
213188
  if (match) {
@@ -213198,19 +213202,21 @@ var getZodConstructor = (field, getRelatedModelName = (name) => name.toString(),
213198
213202
  }
213199
213203
  break;
213200
213204
  case "DateTime":
213201
- zodType = "z.date()";
213202
- if (nativeType == null ? void 0 : nativeType.match(/^TimestampTz/)) zodType = "z.date()";
213203
- else if (nativeType == null ? void 0 : nativeType.match(/^Timestamp/)) zodType = "z.date()";
213205
+ zodType = zodVar + ".date()";
213206
+ if (nativeType == null ? void 0 : nativeType.match(/^TimestampTz/)) zodType = zodVar + ".date()";
213207
+ else if (nativeType == null ? void 0 : nativeType.match(/^Timestamp/)) zodType = zodVar + ".date()";
213204
213208
  else if (nativeType == null ? void 0 : nativeType.match(/^TimeTz/))
213205
- zodType = "z.string().regex(/^\\d{2}:\\d{2}:\\d{2}[+-]\\d{2}:\\d{2}$/)";
213206
- else if (nativeType == null ? void 0 : nativeType.match(/^Time/)) zodType = "z.string().regex(/^\\d{2}:\\d{2}:\\d{2}$/)";
213207
- else if (nativeType == null ? void 0 : nativeType.match(/^Date/)) zodType = "z.string().regex(/^\\d{4}-\\d{2}-\\d{2}$/)";
213208
- else if (nativeType == null ? void 0 : nativeType.match(/^DateTime/)) zodType = "z.date()";
213209
- else if (nativeType == null ? void 0 : nativeType.match(/^DateTimeOffset/)) zodType = "z.date()";
213210
- else if (nativeType == null ? void 0 : nativeType.match(/^DateTime2/)) zodType = "z.date()";
213209
+ zodType = zodVar + ".string().regex(/^\\d{2}:\\d{2}:\\d{2}[+-]\\d{2}:\\d{2}$/)";
213210
+ else if (nativeType == null ? void 0 : nativeType.match(/^Time/))
213211
+ zodType = zodVar + ".string().regex(/^\\d{2}:\\d{2}:\\d{2}$/)";
213212
+ else if (nativeType == null ? void 0 : nativeType.match(/^Date/))
213213
+ zodType = zodVar + ".string().regex(/^\\d{4}-\\d{2}-\\d{2}$/)";
213214
+ else if (nativeType == null ? void 0 : nativeType.match(/^DateTime/)) zodType = zodVar + ".date()";
213215
+ else if (nativeType == null ? void 0 : nativeType.match(/^DateTimeOffset/)) zodType = zodVar + ".date()";
213216
+ else if (nativeType == null ? void 0 : nativeType.match(/^DateTime2/)) zodType = zodVar + ".date()";
213211
213217
  break;
213212
213218
  case "Boolean":
213213
- zodType = "z.boolean()";
213219
+ zodType = zodVar + ".boolean()";
213214
213220
  break;
213215
213221
  case "Bytes":
213216
213222
  zodType = "z.instanceof(Buffer)";
@@ -213228,7 +213234,12 @@ var getZodConstructor = (field, getRelatedModelName = (name) => name.toString(),
213228
213234
  break;
213229
213235
  }
213230
213236
  } else if (field.kind === "enum") {
213231
- zodType = `z.enum(${field.type})`;
213237
+ if (config == null ? void 0 : config.useStandaloneEnums) {
213238
+ const camelCase = field.type.charAt(0).toLowerCase() + field.type.slice(1);
213239
+ zodType = `${camelCase}Schema`;
213240
+ } else {
213241
+ zodType = `z.enum(${field.type})`;
213242
+ }
213232
213243
  } else if (field.kind === "object") {
213233
213244
  zodType = getRelatedModelName(field.type);
213234
213245
  }
@@ -213338,12 +213349,28 @@ var writeImportsForModel = (model, sourceFile, config, { schemaPath, outputPath,
213338
213349
  const relationFields = model.fields.filter((f) => f.kind === "object");
213339
213350
  const relativePath = import_path.default.relative(outputPath, clientPath);
213340
213351
  if (enumFields.length > 0) {
213341
- importList.push({
213342
- kind: import_ts_morph.StructureKind.ImportDeclaration,
213343
- isTypeOnly: enumFields.length === 0,
213344
- moduleSpecifier: dotSlash(relativePath),
213345
- namedImports: enumFields.map((f) => f.type)
213346
- });
213352
+ if (config.useStandaloneEnums) {
213353
+ const uniqueEnumSchemas = Array.from(
213354
+ new Set(
213355
+ enumFields.map((f) => {
213356
+ const camelCase = f.type.charAt(0).toLowerCase() + f.type.slice(1);
213357
+ return `${camelCase}Schema`;
213358
+ })
213359
+ )
213360
+ );
213361
+ importList.push({
213362
+ kind: import_ts_morph.StructureKind.ImportDeclaration,
213363
+ moduleSpecifier: "./enums",
213364
+ namedImports: uniqueEnumSchemas
213365
+ });
213366
+ } else {
213367
+ importList.push({
213368
+ kind: import_ts_morph.StructureKind.ImportDeclaration,
213369
+ isTypeOnly: false,
213370
+ moduleSpecifier: dotSlash(relativePath),
213371
+ namedImports: enumFields.map((f) => f.type)
213372
+ });
213373
+ }
213347
213374
  }
213348
213375
  if (config.relationModel !== false && relationFields.length > 0) {
213349
213376
  const filteredFields = relationFields.filter((f) => f.type !== model.name);
@@ -213362,15 +213389,14 @@ var writeImportsForModel = (model, sourceFile, config, { schemaPath, outputPath,
213362
213389
  var writeTypeSpecificSchemas = (model, sourceFile, config, _prismaOptions) => {
213363
213390
  if (model.fields.some((f) => f.type === "Json")) {
213364
213391
  sourceFile.addStatements((writer) => {
213365
- writer.newLine();
213366
213392
  writeArray(writer, [
213393
+ "",
213367
213394
  "// Helper schema for JSON fields",
213368
- `type Literal = boolean | number | string${config.prismaJsonNullability ? "" : "| null"}`,
213395
+ `type Literal = boolean | number | string${config.prismaJsonNullability ? "" : " | null"}`,
213369
213396
  "type Json = Literal | { [key: string]: Json } | Json[]",
213370
213397
  `const literalSchema = z.union([z.string(), z.number(), z.boolean()${config.prismaJsonNullability ? "" : ", z.null()"}])`,
213371
- "const jsonSchema: z.ZodSchema<Json> = z.lazy(() =>",
213372
- " z.union([literalSchema, z.array(jsonSchema), z.record(z.string(), jsonSchema)])",
213373
- ")"
213398
+ // Keep jsonSchema initializer fully on one line
213399
+ "const jsonSchema: z.ZodSchema<Json> = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(z.string(), jsonSchema)]))"
213374
213400
  ]);
213375
213401
  });
213376
213402
  }
@@ -213413,7 +213439,9 @@ var generateSchemaForModel = (model, sourceFile, config, { schemaPath }) => {
213413
213439
  model.fields.filter((f) => f.kind !== "object").forEach((field) => {
213414
213440
  writeArray(writer, getJSDocs(field.documentation));
213415
213441
  const nativeType = modelNativeTypes.get(field.name);
213416
- writer.write(`${field.name}: ${getZodConstructor(field, void 0, nativeType)}`).write(",").newLine();
213442
+ writer.write(
213443
+ `${field.name}: ${getZodConstructor(field, void 0, nativeType, config)}`
213444
+ ).write(",").newLine();
213417
213445
  });
213418
213446
  }).write(")");
213419
213447
  }
@@ -213454,12 +213482,21 @@ var generateRelatedSchemaForModel = (model, sourceFile, config, _prismaOptions)
213454
213482
  name: relatedModelName(model.name),
213455
213483
  type: `z.ZodSchema<Complete${model.name}>`,
213456
213484
  initializer(writer) {
213457
- writer.write(`z.lazy(() => ${modelName(model.name)}.extend(`).inlineBlock(() => {
213458
- relationFields.forEach((field) => {
213459
- writeArray(writer, getJSDocs(field.documentation));
213460
- writer.write(`${field.name}: ${getZodConstructor(field, relatedModelName)}`).write(",").newLine();
213461
- });
213462
- }).write("))");
213485
+ writer.write("z.lazy(() =>").newLine().indent(() => {
213486
+ writer.write(`${modelName(model.name)}.extend({`).newLine().indent(() => {
213487
+ relationFields.forEach((field) => {
213488
+ writeArray(writer, getJSDocs(field.documentation));
213489
+ writer.write(
213490
+ `${field.name}: ${getZodConstructor(
213491
+ field,
213492
+ relatedModelName,
213493
+ null,
213494
+ config
213495
+ )},`
213496
+ ).newLine();
213497
+ });
213498
+ }).write("})");
213499
+ }).newLine().write(")");
213463
213500
  }
213464
213501
  }
213465
213502
  ]
@@ -213472,13 +213509,45 @@ var populateModelFile = (model, sourceFile, config, prismaOptions) => {
213472
213509
  if (needsRelatedModel(model, config))
213473
213510
  generateRelatedSchemaForModel(model, sourceFile, config, prismaOptions);
213474
213511
  };
213475
- var generateBarrelFile = (models, indexFile) => {
213512
+ var generateBarrelFile = (models, indexFile, config, hasEnums) => {
213513
+ if ((config == null ? void 0 : config.useStandaloneEnums) && hasEnums) {
213514
+ indexFile.addExportDeclaration({
213515
+ moduleSpecifier: "./enums"
213516
+ });
213517
+ }
213476
213518
  models.forEach((model) => {
213477
213519
  indexFile.addExportDeclaration({
213478
213520
  moduleSpecifier: `./${model.name.toLowerCase()}`
213479
213521
  });
213480
213522
  });
213481
213523
  };
213524
+ var generateEnumsFile = (enums, sourceFile) => {
213525
+ sourceFile.addImportDeclaration({
213526
+ kind: import_ts_morph.StructureKind.ImportDeclaration,
213527
+ namespaceImport: "z",
213528
+ moduleSpecifier: "zod"
213529
+ });
213530
+ enums.forEach((enumDef) => {
213531
+ const values = enumDef.values.map((v) => v.name);
213532
+ const enumInitializer = `z.enum([${values.map((v) => `'${v}'`).join(", ")}])`;
213533
+ const camelCaseName = enumDef.name.charAt(0).toLowerCase() + enumDef.name.slice(1);
213534
+ sourceFile.addVariableStatement({
213535
+ isExported: true,
213536
+ declarationKind: import_ts_morph.VariableDeclarationKind.Const,
213537
+ declarations: [
213538
+ {
213539
+ name: `${camelCaseName}Schema`,
213540
+ initializer: enumInitializer
213541
+ }
213542
+ ]
213543
+ });
213544
+ sourceFile.addTypeAlias({
213545
+ isExported: true,
213546
+ name: `${enumDef.name}Schema`,
213547
+ type: `z.infer<typeof ${camelCaseName}Schema>`
213548
+ });
213549
+ });
213550
+ };
213482
213551
 
213483
213552
  // src/index.ts
213484
213553
  var import_ts_morph2 = require("ts-morph");
@@ -213497,6 +213566,7 @@ var import_ts_morph2 = require("ts-morph");
213497
213566
  }
213498
213567
  });
213499
213568
  const models = [...options.dmmf.datamodel.models];
213569
+ const enums = [...options.dmmf.datamodel.enums];
213500
213570
  const schemaPath = options.schemaPath;
213501
213571
  const schemaDir = import_path2.default.dirname(schemaPath);
213502
213572
  const outputPath = options.generator.output.value;
@@ -213516,12 +213586,21 @@ var import_ts_morph2 = require("ts-morph");
213516
213586
  // Pass the directory, not the file
213517
213587
  };
213518
213588
  const indexFile = project.createSourceFile(`${outputPath}/index.ts`, {}, { overwrite: true });
213519
- generateBarrelFile(models, indexFile);
213589
+ generateBarrelFile(models, indexFile, config, enums.length > 0);
213520
213590
  indexFile.formatText({
213521
213591
  indentSize: 2,
213522
213592
  convertTabsToSpaces: true,
213523
213593
  semicolons: import_typescript.SemicolonPreference.Remove
213524
213594
  });
213595
+ if (config.useStandaloneEnums && enums.length > 0) {
213596
+ const enumsFile = project.createSourceFile(`${outputPath}/enums.ts`, {}, { overwrite: true });
213597
+ generateEnumsFile(enums, enumsFile);
213598
+ enumsFile.formatText({
213599
+ indentSize: 2,
213600
+ convertTabsToSpaces: true,
213601
+ semicolons: import_typescript.SemicolonPreference.Remove
213602
+ });
213603
+ }
213525
213604
  models.forEach((model) => {
213526
213605
  const sourceFile = project.createSourceFile(
213527
213606
  `${outputPath}/${model.name.toLowerCase()}.ts`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prisma-to-zod-v4",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "A Prisma generator that creates Zod schemas for all of your models",
5
5
  "license": "MIT",
6
6
  "author": "yassinrais",
@@ -84,4 +84,4 @@
84
84
  "engines": {
85
85
  "node": ">=14"
86
86
  }
87
- }
87
+ }