@parziva-1/zod-mongoose 5.0.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/LICENSE +22 -0
- package/README.md +320 -0
- package/dist/index.d.ts +234 -0
- package/dist/index.js +703 -0
- package/dist/index.js.map +1 -0
- package/package.json +93 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import { Schema, SchemaTypes, Types, isValidObjectId } from "mongoose";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/assertions/custom.ts
|
|
4
|
+
let zmAssertIds;
|
|
5
|
+
(function(_zmAssertIds) {
|
|
6
|
+
function objectId(f) {
|
|
7
|
+
return "__zm_type" in f && f.__zm_type === "ObjectId";
|
|
8
|
+
}
|
|
9
|
+
_zmAssertIds.objectId = objectId;
|
|
10
|
+
function uuid(f) {
|
|
11
|
+
return "__zm_type" in f && f.__zm_type === "UUID";
|
|
12
|
+
}
|
|
13
|
+
_zmAssertIds.uuid = uuid;
|
|
14
|
+
})(zmAssertIds || (zmAssertIds = {}));
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/assertions/assertions.ts
|
|
17
|
+
var assertions_default = {
|
|
18
|
+
string(f) {
|
|
19
|
+
return f._zod.def.type === "string";
|
|
20
|
+
},
|
|
21
|
+
number(f) {
|
|
22
|
+
return f._zod.def.type === "number";
|
|
23
|
+
},
|
|
24
|
+
object(f) {
|
|
25
|
+
return f._zod.def.type === "object";
|
|
26
|
+
},
|
|
27
|
+
array(f) {
|
|
28
|
+
return f._zod.def.type === "array";
|
|
29
|
+
},
|
|
30
|
+
boolean(f) {
|
|
31
|
+
return f._zod.def.type === "boolean";
|
|
32
|
+
},
|
|
33
|
+
enumerable(f) {
|
|
34
|
+
return f._zod.def.type === "enum";
|
|
35
|
+
},
|
|
36
|
+
date(f) {
|
|
37
|
+
return f._zod.def.type === "date";
|
|
38
|
+
},
|
|
39
|
+
def(f) {
|
|
40
|
+
return f._zod.def.type === "default";
|
|
41
|
+
},
|
|
42
|
+
optional(f) {
|
|
43
|
+
return f._zod.def.type === "optional";
|
|
44
|
+
},
|
|
45
|
+
nullable(f) {
|
|
46
|
+
return f._zod.def.type === "nullable";
|
|
47
|
+
},
|
|
48
|
+
union(f) {
|
|
49
|
+
return f._zod.def.type === "union";
|
|
50
|
+
},
|
|
51
|
+
any(f) {
|
|
52
|
+
const type = f._zod.def.type;
|
|
53
|
+
return type === "any" || type === "unknown";
|
|
54
|
+
},
|
|
55
|
+
mapOrRecord(f) {
|
|
56
|
+
return f._zod.def.type === "map" || f._zod.def.type === "record";
|
|
57
|
+
},
|
|
58
|
+
pipe(f) {
|
|
59
|
+
return f._zod.def.type === "pipe";
|
|
60
|
+
},
|
|
61
|
+
tuple(f) {
|
|
62
|
+
return f._zod.def.type === "tuple";
|
|
63
|
+
},
|
|
64
|
+
literal(f) {
|
|
65
|
+
return f._zod.def.type === "literal";
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* `z.discriminatedUnion()` is represented by Zod v4 as a plain `ZodUnion`
|
|
69
|
+
* (`_zod.def.type === "union"`) that additionally carries a `discriminator`
|
|
70
|
+
* key on its def. It must be checked for *before* the generic `union`
|
|
71
|
+
* assertion in `parseField`, since a discriminated union also satisfies
|
|
72
|
+
* that check.
|
|
73
|
+
*/
|
|
74
|
+
discriminatedUnion(f) {
|
|
75
|
+
return f._zod.def.type === "union" && typeof f._zod.def.discriminator === "string";
|
|
76
|
+
},
|
|
77
|
+
intersection(f) {
|
|
78
|
+
return f._zod.def.type === "intersection";
|
|
79
|
+
},
|
|
80
|
+
lazy(f) {
|
|
81
|
+
return f._zod.def.type === "lazy";
|
|
82
|
+
},
|
|
83
|
+
catch(f) {
|
|
84
|
+
return f._zod.def.type === "catch";
|
|
85
|
+
},
|
|
86
|
+
...zmAssertIds
|
|
87
|
+
};
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/extension.ts
|
|
90
|
+
let zod_extended = false;
|
|
91
|
+
/**
|
|
92
|
+
* Extends the Zod library with additional functionality.
|
|
93
|
+
*
|
|
94
|
+
* This function modifies the Zod library to add custom mongoose-specific
|
|
95
|
+
* metadata methods. It ensures that the extension is only applied once.
|
|
96
|
+
*
|
|
97
|
+
* @param z_0 - The Zod library to extend.
|
|
98
|
+
*
|
|
99
|
+
* @remarks
|
|
100
|
+
* - Adds a `unique` method to `ZodString`, `ZodNumber`, and `ZodDate` to mark them as unique.
|
|
101
|
+
* - Adds a `sparse` method to `ZodString`, `ZodNumber`, and `ZodDate` to mark them as sparse.
|
|
102
|
+
*
|
|
103
|
+
* As of Zod v4, refinement metadata (validator + message) no longer needs to
|
|
104
|
+
* be captured via a `refine()` override: Zod's own internal `checks` array
|
|
105
|
+
* already exposes the validator function and error message directly, so
|
|
106
|
+
* `zodSchema()` reads that straight off the schema instead.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```typescript
|
|
110
|
+
* import { z } from "zod";
|
|
111
|
+
* import { extendZod } from "./extension";
|
|
112
|
+
*
|
|
113
|
+
* extendZod(z);
|
|
114
|
+
*
|
|
115
|
+
* const schema = z.object({
|
|
116
|
+
* name: z.string().unique();
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
function extendZod(z_0) {
|
|
121
|
+
if (zod_extended) return;
|
|
122
|
+
zod_extended = true;
|
|
123
|
+
const UNIQUE_SUPPORT_LIST = [
|
|
124
|
+
z_0.ZodString,
|
|
125
|
+
z_0.ZodNumber,
|
|
126
|
+
z_0.ZodDate
|
|
127
|
+
];
|
|
128
|
+
for (const type of UNIQUE_SUPPORT_LIST) {
|
|
129
|
+
type.prototype.unique = function(arg = true) {
|
|
130
|
+
return this.meta({
|
|
131
|
+
...this.meta(),
|
|
132
|
+
__zm_unique: arg
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
type.prototype.sparse = function(arg = true) {
|
|
136
|
+
return this.meta({
|
|
137
|
+
...this.meta(),
|
|
138
|
+
__zm_sparse: arg
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const createId = () => {
|
|
144
|
+
return z.string().refine((v) => isValidObjectId(v), { message: "Invalid ObjectId" }).or(z.instanceof(Types.ObjectId));
|
|
145
|
+
};
|
|
146
|
+
const zId = (ref) => {
|
|
147
|
+
const output = createId();
|
|
148
|
+
output.__zm_type = "ObjectId";
|
|
149
|
+
output.__zm_ref = ref;
|
|
150
|
+
output.ref = function(ref) {
|
|
151
|
+
this.__zm_ref = ref;
|
|
152
|
+
return this;
|
|
153
|
+
};
|
|
154
|
+
output.refPath = function(ref) {
|
|
155
|
+
this.__zm_refPath = ref;
|
|
156
|
+
return this;
|
|
157
|
+
};
|
|
158
|
+
output.unique = function(val = true) {
|
|
159
|
+
this.__zm_unique = val;
|
|
160
|
+
return this;
|
|
161
|
+
};
|
|
162
|
+
output.sparse = function(val = true) {
|
|
163
|
+
this.__zm_sparse = val;
|
|
164
|
+
return this;
|
|
165
|
+
};
|
|
166
|
+
return output;
|
|
167
|
+
};
|
|
168
|
+
const createUUID = () => {
|
|
169
|
+
return z.string().uuid({ message: "Invalid UUID" }).or(z.instanceof(Types.UUID));
|
|
170
|
+
};
|
|
171
|
+
const zUUID = (ref) => {
|
|
172
|
+
const output = createUUID();
|
|
173
|
+
output.__zm_type = "UUID";
|
|
174
|
+
output.__zm_ref = ref;
|
|
175
|
+
output.ref = function(ref) {
|
|
176
|
+
this.__zm_ref = ref;
|
|
177
|
+
return this;
|
|
178
|
+
};
|
|
179
|
+
output.refPath = function(ref) {
|
|
180
|
+
this.__zm_refPath = ref;
|
|
181
|
+
return this;
|
|
182
|
+
};
|
|
183
|
+
output.unique = function(val = true) {
|
|
184
|
+
this.__zm_unique = val;
|
|
185
|
+
return this;
|
|
186
|
+
};
|
|
187
|
+
output.sparse = function(val = true) {
|
|
188
|
+
this.__zm_sparse = val;
|
|
189
|
+
return this;
|
|
190
|
+
};
|
|
191
|
+
return output;
|
|
192
|
+
};
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/index.ts
|
|
195
|
+
/**
|
|
196
|
+
* Maximum number of times `parseField` will follow a given `z.lazy()`
|
|
197
|
+
* getter into itself before bottoming out at `SchemaTypes.Mixed`. Guards
|
|
198
|
+
* against unbounded recursion when parsing a genuinely self-referencing
|
|
199
|
+
* schema (e.g. a comment type whose `replies` field is `z.array(z.lazy(() =>
|
|
200
|
+
* CommentSchema))`) - Mongoose has no native equivalent of an infinitely
|
|
201
|
+
* recursive embedded subdocument, so the structure has to be unrolled to a
|
|
202
|
+
* finite depth. Keyed per-getter (not globally) via `lazyDepth` below, so
|
|
203
|
+
* unrelated lazy schemas in the same document don't share a budget.
|
|
204
|
+
*/
|
|
205
|
+
const LAZY_DEPTH_LIMIT = 5;
|
|
206
|
+
const lazyDepth = /* @__PURE__ */ new WeakMap();
|
|
207
|
+
/**
|
|
208
|
+
* Converts a Zod schema to a Mongoose schema
|
|
209
|
+
* @param schema zod schema to parse
|
|
210
|
+
* @returns mongoose schema
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* import { extendZod, zodSchema } from '@zodyac/zod-mongoose';
|
|
214
|
+
* import { model } from 'mongoose';
|
|
215
|
+
* import { z } from 'zod';
|
|
216
|
+
*
|
|
217
|
+
* extendZod(z);
|
|
218
|
+
*
|
|
219
|
+
* const zUser = z.object({
|
|
220
|
+
* name: z.string().min(3).max(255),
|
|
221
|
+
* age: z.number().min(18).max(100),
|
|
222
|
+
* active: z.boolean().default(false),
|
|
223
|
+
* access: z.enum(['admin', 'user']).default('user'),
|
|
224
|
+
* companyId: zId('Company'),
|
|
225
|
+
* address: z.object({
|
|
226
|
+
* street: z.string(),
|
|
227
|
+
* city: z.string(),
|
|
228
|
+
* state: z.enum(['CA', 'NY', 'TX']),
|
|
229
|
+
* }),
|
|
230
|
+
* tags: z.array(z.string()),
|
|
231
|
+
* createdAt: z.date(),
|
|
232
|
+
* updatedAt: z.date(),
|
|
233
|
+
* });
|
|
234
|
+
*
|
|
235
|
+
* const schema = zodSchema(zDoc);
|
|
236
|
+
* const userModel = model('User', schema);
|
|
237
|
+
*/
|
|
238
|
+
function zodSchema(schema, options) {
|
|
239
|
+
const definition = parseObject(schema, true);
|
|
240
|
+
return new Schema(definition, options);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Converts a Zod schema to a raw Mongoose schema object
|
|
244
|
+
* @param schema zod schema to parse
|
|
245
|
+
* @returns mongoose schema
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* import { extendZod, zodSchemaRaw } from '@zodyac/zod-mongoose';
|
|
249
|
+
* import { model, Schema } from 'mongoose';
|
|
250
|
+
* import { z } from 'zod';
|
|
251
|
+
*
|
|
252
|
+
* extendZod(z);
|
|
253
|
+
*
|
|
254
|
+
* const zUser = z.object({
|
|
255
|
+
* name: z.string().min(3).max(255),
|
|
256
|
+
* age: z.number().min(18).max(100),
|
|
257
|
+
* active: z.boolean().default(false),
|
|
258
|
+
* access: z.enum(['admin', 'user']).default('user'),
|
|
259
|
+
* companyId: zId('Company'),
|
|
260
|
+
* address: z.object({
|
|
261
|
+
* street: z.string(),
|
|
262
|
+
* city: z.string(),
|
|
263
|
+
* state: z.enum(['CA', 'NY', 'TX']),
|
|
264
|
+
* }),
|
|
265
|
+
* tags: z.array(z.string()),
|
|
266
|
+
* createdAt: z.date(),
|
|
267
|
+
* updatedAt: z.date(),
|
|
268
|
+
* });
|
|
269
|
+
*
|
|
270
|
+
* const rawSchema = zodSchemaRaw(zDoc);
|
|
271
|
+
* const schema = new Schema(rawSchema);
|
|
272
|
+
* const userModel = model('User', schema);
|
|
273
|
+
*/
|
|
274
|
+
function zodSchemaRaw(schema) {
|
|
275
|
+
return parseObject(schema, true);
|
|
276
|
+
}
|
|
277
|
+
function parseObject(obj, required = true, def) {
|
|
278
|
+
const object = parseShape(obj.shape);
|
|
279
|
+
if (!required || typeof def !== "undefined") return {
|
|
280
|
+
type: object,
|
|
281
|
+
required,
|
|
282
|
+
default: def
|
|
283
|
+
};
|
|
284
|
+
return object;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Parses a raw Zod shape (a plain `{ key: ZodType }` map, as found on
|
|
288
|
+
* `ZodObject.shape`) into a Mongoose field-definition object. Shared between
|
|
289
|
+
* `parseObject` (which operates on an actual `ZodObject`) and the
|
|
290
|
+
* `z.intersection()` handler, which needs to merge two shapes into one flat
|
|
291
|
+
* object without constructing a synthetic `ZodObject` instance.
|
|
292
|
+
*/
|
|
293
|
+
function parseShape(shape) {
|
|
294
|
+
const object = {};
|
|
295
|
+
for (const [key, field] of Object.entries(shape)) if (assertions_default.object(field)) object[key] = parseObject(field, true);
|
|
296
|
+
else {
|
|
297
|
+
const f = parseField(field);
|
|
298
|
+
if (!f) throw new Error(`Unsupported field type: ${field.constructor}`);
|
|
299
|
+
object[key] = f;
|
|
300
|
+
}
|
|
301
|
+
return object;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Walks a schema's own `checks` array (Zod v4) and returns the metadata for
|
|
305
|
+
* *every* `.refine()` custom check found on it, in declaration order.
|
|
306
|
+
*
|
|
307
|
+
* Zod v4 no longer wraps refined schemas in a `ZodEffects`-like type: calling
|
|
308
|
+
* `.refine()` simply appends a `"custom"` check to the schema's own
|
|
309
|
+
* `_zod.def.checks` array (or, when chained after `.transform()`, to the
|
|
310
|
+
* resulting `ZodPipe`'s own `checks` array). The check object itself already
|
|
311
|
+
* carries the validator function (`fn`) and a normalized error accessor
|
|
312
|
+
* (`error`), so there is no need to monkey-patch `refine()` to capture this
|
|
313
|
+
* metadata as was necessary under Zod v3.
|
|
314
|
+
*
|
|
315
|
+
* A single Zod type can carry multiple `.refine()` checks (e.g.
|
|
316
|
+
* `z.string().refine(a).refine(b)`), and `parseField` also needs to combine
|
|
317
|
+
* checks found on *different* nodes of a `ZodPipe` (a pre-transform refine on
|
|
318
|
+
* the pipe's `in` side plus a post-transform refine on the pipe itself) - so
|
|
319
|
+
* this returns all matches rather than just the last one, leaving the
|
|
320
|
+
* caller free to merge them with refinements collected elsewhere.
|
|
321
|
+
*/
|
|
322
|
+
function extractRefinements(field) {
|
|
323
|
+
const checks = field._zod?.def?.checks;
|
|
324
|
+
if (!checks || checks.length === 0) return [];
|
|
325
|
+
const refinements = [];
|
|
326
|
+
for (const check of checks) {
|
|
327
|
+
const checkDef = check?._zod?.def;
|
|
328
|
+
if (!checkDef || checkDef.check !== "custom") continue;
|
|
329
|
+
let message;
|
|
330
|
+
if (typeof checkDef.error === "function") try {
|
|
331
|
+
message = checkDef.error({});
|
|
332
|
+
} catch {
|
|
333
|
+
message = void 0;
|
|
334
|
+
}
|
|
335
|
+
else if (typeof checkDef.error === "string") message = checkDef.error;
|
|
336
|
+
refinements.push({
|
|
337
|
+
validator: checkDef.fn,
|
|
338
|
+
message
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
return refinements;
|
|
342
|
+
}
|
|
343
|
+
function toRefinementArray(refinement) {
|
|
344
|
+
if (!refinement) return [];
|
|
345
|
+
return Array.isArray(refinement) ? refinement : [refinement];
|
|
346
|
+
}
|
|
347
|
+
function parseField(field, required = true, def, refinement) {
|
|
348
|
+
if (assertions_default.objectId(field)) {
|
|
349
|
+
const ref = field.__zm_ref;
|
|
350
|
+
const refPath = field.__zm_refPath;
|
|
351
|
+
const unique = field.__zm_unique;
|
|
352
|
+
const sparse = field.__zm_sparse;
|
|
353
|
+
return parseObjectId(required, ref, unique, refPath, sparse, def);
|
|
354
|
+
}
|
|
355
|
+
if (assertions_default.uuid(field)) {
|
|
356
|
+
const ref = field.__zm_ref;
|
|
357
|
+
const refPath = field.__zm_refPath;
|
|
358
|
+
const unique = field.__zm_unique;
|
|
359
|
+
const sparse = field.__zm_sparse;
|
|
360
|
+
return parseUUID(required, ref, unique, refPath, sparse, def);
|
|
361
|
+
}
|
|
362
|
+
if (assertions_default.object(field)) return parseObject(field, required, def);
|
|
363
|
+
const combinedRefinements = [...extractRefinements(field), ...toRefinementArray(refinement)];
|
|
364
|
+
const ownRefinement = combinedRefinements.length === 0 ? void 0 : combinedRefinements.length === 1 ? combinedRefinements[0] : combinedRefinements;
|
|
365
|
+
if (assertions_default.number(field)) {
|
|
366
|
+
const numberField = field;
|
|
367
|
+
const meta = numberField.meta();
|
|
368
|
+
return parseNumber(numberField, required, def, meta?.__zm_unique ?? false, ownRefinement, meta?.__zm_sparse ?? false);
|
|
369
|
+
}
|
|
370
|
+
if (assertions_default.string(field)) {
|
|
371
|
+
const stringField = field;
|
|
372
|
+
const meta = stringField.meta();
|
|
373
|
+
return parseString(stringField, required, def, meta?.__zm_unique ?? false, ownRefinement, meta?.__zm_sparse ?? false);
|
|
374
|
+
}
|
|
375
|
+
if (assertions_default.enumerable(field)) return parseEnum(Object.values(field.enum), required, def);
|
|
376
|
+
if (assertions_default.boolean(field)) return parseBoolean(required, def);
|
|
377
|
+
if (assertions_default.date(field)) {
|
|
378
|
+
const meta = field.meta?.();
|
|
379
|
+
return parseDate(required, def, ownRefinement, meta?.__zm_unique ?? false, meta?.__zm_sparse ?? false);
|
|
380
|
+
}
|
|
381
|
+
if (assertions_default.array(field)) return parseArray(field.element, required, def);
|
|
382
|
+
if (assertions_default.def(field)) {
|
|
383
|
+
const defField = field;
|
|
384
|
+
const innerType = defField._zod.def.innerType;
|
|
385
|
+
return parseField(innerType, required, () => defField._zod.def.defaultValue);
|
|
386
|
+
}
|
|
387
|
+
if (assertions_default.optional(field)) {
|
|
388
|
+
const innerType = field._zod.def.innerType;
|
|
389
|
+
return parseField(innerType, false, def);
|
|
390
|
+
}
|
|
391
|
+
if (assertions_default.nullable(field)) {
|
|
392
|
+
const innerType = field._zod.def.innerType;
|
|
393
|
+
return parseField(innerType, false, typeof def !== "undefined" ? def : () => null);
|
|
394
|
+
}
|
|
395
|
+
if (assertions_default.discriminatedUnion(field)) return parseDiscriminatedUnion(field, required, def);
|
|
396
|
+
if (assertions_default.union(field)) {
|
|
397
|
+
const firstOption = field._zod.def.options[0];
|
|
398
|
+
if (!firstOption) throw new Error("Union type must have at least one option");
|
|
399
|
+
return parseField(firstOption);
|
|
400
|
+
}
|
|
401
|
+
if (assertions_default.any(field)) return parseMixed(required, def);
|
|
402
|
+
if (assertions_default.tuple(field)) return parseTuple(field, required, def);
|
|
403
|
+
if (assertions_default.literal(field)) {
|
|
404
|
+
const values = field._zod.def.values;
|
|
405
|
+
return parseLiteral(values, required, def);
|
|
406
|
+
}
|
|
407
|
+
if (assertions_default.intersection(field)) {
|
|
408
|
+
const { left, right } = field._zod.def;
|
|
409
|
+
if (!assertions_default.object(left) || !assertions_default.object(right)) throw new Error("Unsupported intersection: zod-mongoose can only merge two object-shape schemas (z.object(...).and(z.object(...))) into a flat Mongoose sub-schema");
|
|
410
|
+
const merged = parseShape({
|
|
411
|
+
...left.shape,
|
|
412
|
+
...right.shape
|
|
413
|
+
});
|
|
414
|
+
if (!required) return {
|
|
415
|
+
type: merged,
|
|
416
|
+
required: false
|
|
417
|
+
};
|
|
418
|
+
return merged;
|
|
419
|
+
}
|
|
420
|
+
if (assertions_default.lazy(field)) {
|
|
421
|
+
const getter = field._zod.def.getter;
|
|
422
|
+
const depth = lazyDepth.get(getter) ?? 0;
|
|
423
|
+
if (depth >= LAZY_DEPTH_LIMIT) return parseMixed(required, def);
|
|
424
|
+
lazyDepth.set(getter, depth + 1);
|
|
425
|
+
try {
|
|
426
|
+
return parseField(getter(), required, def, refinement);
|
|
427
|
+
} finally {
|
|
428
|
+
lazyDepth.set(getter, depth);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (assertions_default.catch(field)) {
|
|
432
|
+
const catchField = field;
|
|
433
|
+
const innerType = catchField._zod.def.innerType;
|
|
434
|
+
const catchValueFn = catchField._zod.def.catchValue;
|
|
435
|
+
const inner = parseField(innerType, required, def, refinement);
|
|
436
|
+
if (!inner) return inner;
|
|
437
|
+
const previousSet = inner.set;
|
|
438
|
+
inner.set = (v) => {
|
|
439
|
+
const result = innerType.safeParse(v);
|
|
440
|
+
const resolved = result.success ? result.data : catchValueFn({
|
|
441
|
+
value: v,
|
|
442
|
+
issues: result.error.issues,
|
|
443
|
+
error: result.error
|
|
444
|
+
});
|
|
445
|
+
return previousSet ? previousSet(resolved) : resolved;
|
|
446
|
+
};
|
|
447
|
+
return inner;
|
|
448
|
+
}
|
|
449
|
+
if (assertions_default.mapOrRecord(field)) return parseMap(field.valueType, required, def);
|
|
450
|
+
if (assertions_default.pipe(field)) {
|
|
451
|
+
const pipeDef = field._zod.def;
|
|
452
|
+
return parseField(pipeDef.in._zod.def.type === "transform" ? pipeDef.out : pipeDef.in, required, def, ownRefinement);
|
|
453
|
+
}
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
function parseNumber(field, required = true, def, unique = false, validate, sparse = false) {
|
|
457
|
+
const output = {
|
|
458
|
+
type: Number,
|
|
459
|
+
default: def,
|
|
460
|
+
min: Number.isFinite(field.minValue) ? field.minValue ?? void 0 : void 0,
|
|
461
|
+
max: Number.isFinite(field.maxValue) ? field.maxValue ?? void 0 : void 0,
|
|
462
|
+
required,
|
|
463
|
+
unique,
|
|
464
|
+
sparse
|
|
465
|
+
};
|
|
466
|
+
if (validate) output.validate = validate;
|
|
467
|
+
return output;
|
|
468
|
+
}
|
|
469
|
+
function parseString(field, required = true, def, unique = false, validate, sparse = false) {
|
|
470
|
+
const output = {
|
|
471
|
+
type: String,
|
|
472
|
+
default: def,
|
|
473
|
+
required,
|
|
474
|
+
minLength: field.minLength ?? void 0,
|
|
475
|
+
maxLength: field.maxLength ?? void 0,
|
|
476
|
+
unique,
|
|
477
|
+
sparse
|
|
478
|
+
};
|
|
479
|
+
if (validate) output.validate = validate;
|
|
480
|
+
return output;
|
|
481
|
+
}
|
|
482
|
+
function parseEnum(values, required = true, def) {
|
|
483
|
+
return {
|
|
484
|
+
type: String,
|
|
485
|
+
unique: false,
|
|
486
|
+
sparse: false,
|
|
487
|
+
default: def,
|
|
488
|
+
enum: values,
|
|
489
|
+
required
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function parseBoolean(required = true, def) {
|
|
493
|
+
return {
|
|
494
|
+
type: Boolean,
|
|
495
|
+
default: def,
|
|
496
|
+
required
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function parseDate(required = true, def, validate, unique = false, sparse = false) {
|
|
500
|
+
const output = {
|
|
501
|
+
type: Date,
|
|
502
|
+
default: def,
|
|
503
|
+
required,
|
|
504
|
+
unique,
|
|
505
|
+
sparse
|
|
506
|
+
};
|
|
507
|
+
if (validate) output.validate = validate;
|
|
508
|
+
return output;
|
|
509
|
+
}
|
|
510
|
+
function parseObjectId(required = true, ref, unique = false, refPath, sparse = false, def) {
|
|
511
|
+
const output = {
|
|
512
|
+
type: SchemaTypes.ObjectId,
|
|
513
|
+
required,
|
|
514
|
+
unique,
|
|
515
|
+
sparse,
|
|
516
|
+
default: def
|
|
517
|
+
};
|
|
518
|
+
if (ref) output.ref = ref;
|
|
519
|
+
if (refPath) output.refPath = refPath;
|
|
520
|
+
return output;
|
|
521
|
+
}
|
|
522
|
+
function parseArray(element, required = true, def) {
|
|
523
|
+
const innerType = parseField(element);
|
|
524
|
+
if (!innerType) throw new Error("Unsupported array type");
|
|
525
|
+
return {
|
|
526
|
+
type: [innerType],
|
|
527
|
+
default: def,
|
|
528
|
+
required
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
function parseMap(valueType, required = true, def) {
|
|
532
|
+
const pointer = parseMapValue(valueType);
|
|
533
|
+
return {
|
|
534
|
+
type: Map,
|
|
535
|
+
of: pointer,
|
|
536
|
+
default: def,
|
|
537
|
+
required
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Resolves a `z.map()`/`z.record()` value type into a Mongoose field
|
|
542
|
+
* definition for the Map's `of`.
|
|
543
|
+
*
|
|
544
|
+
* A plain `z.union([...])` value type (e.g. `z.record(z.string(),
|
|
545
|
+
* z.union([z.string(), z.number()]))`, the real production `params` shape)
|
|
546
|
+
* must NOT go through the generic `parseField` union handling, which
|
|
547
|
+
* collapses to the *first* union member's type - for a `Map<string, string
|
|
548
|
+
* | number>` that silently coerces every numeric value to a string on save
|
|
549
|
+
* (Mongoose's `Map`/`String` casting), which is silent data corruption, not
|
|
550
|
+
* just a missing feature. Instead, the value is stored as `Mixed` (so all
|
|
551
|
+
* union member types round-trip untouched) with a `validate` that re-checks
|
|
552
|
+
* each value against the original union schema via `.safeParse()`, so an
|
|
553
|
+
* invalid value is rejected rather than silently narrowed/coerced.
|
|
554
|
+
*/
|
|
555
|
+
function parseMapValue(valueType) {
|
|
556
|
+
if (assertions_default.union(valueType) && !assertions_default.discriminatedUnion(valueType)) return {
|
|
557
|
+
type: SchemaTypes.Mixed,
|
|
558
|
+
required: false,
|
|
559
|
+
validate: {
|
|
560
|
+
validator: (v) => valueType.safeParse(v).success,
|
|
561
|
+
message: "Value does not match any member of the declared union value type"
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
const pointer = parseField(valueType);
|
|
565
|
+
if (!pointer) throw new Error("Unsupported map value type");
|
|
566
|
+
return pointer;
|
|
567
|
+
}
|
|
568
|
+
function parseUUID(required = true, ref, unique = false, refPath, sparse = false, def) {
|
|
569
|
+
const output = {
|
|
570
|
+
type: SchemaTypes.UUID,
|
|
571
|
+
required,
|
|
572
|
+
unique,
|
|
573
|
+
sparse,
|
|
574
|
+
default: def
|
|
575
|
+
};
|
|
576
|
+
if (ref) output.ref = ref;
|
|
577
|
+
if (refPath) output.refPath = refPath;
|
|
578
|
+
return output;
|
|
579
|
+
}
|
|
580
|
+
function parseMixed(required = true, def) {
|
|
581
|
+
return {
|
|
582
|
+
type: SchemaTypes.Mixed,
|
|
583
|
+
default: def,
|
|
584
|
+
required
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* `z.tuple()` has no native Mongoose equivalent (Mongoose arrays are
|
|
589
|
+
* homogeneous and unbounded). It's represented as a Mongoose array of
|
|
590
|
+
* `Mixed` - so it still round-trips through Mongo as a JSON array - with a
|
|
591
|
+
* `validate` that enforces the tuple's actual contract (exact arity, or a
|
|
592
|
+
* minimum arity plus a rest type, and the correct type at each position).
|
|
593
|
+
* Rather than re-deriving per-position type checks by hand, the validator
|
|
594
|
+
* reuses the original Zod item schemas' own `.safeParse()`, which is both
|
|
595
|
+
* simpler and guaranteed to match Zod's own validation semantics exactly.
|
|
596
|
+
*/
|
|
597
|
+
function parseTuple(field, required = true, def) {
|
|
598
|
+
const tupleDef = field._zod.def;
|
|
599
|
+
const items = tupleDef.items;
|
|
600
|
+
const rest = tupleDef.rest;
|
|
601
|
+
const validator = (value) => {
|
|
602
|
+
if (!Array.isArray(value)) return false;
|
|
603
|
+
if (rest) {
|
|
604
|
+
if (value.length < items.length) return false;
|
|
605
|
+
} else if (value.length !== items.length) return false;
|
|
606
|
+
for (let i = 0; i < items.length; i++) {
|
|
607
|
+
const itemSchema = items[i];
|
|
608
|
+
if (!itemSchema || !itemSchema.safeParse(value[i]).success) return false;
|
|
609
|
+
}
|
|
610
|
+
if (rest) {
|
|
611
|
+
for (let i = items.length; i < value.length; i++) if (!rest.safeParse(value[i]).success) return false;
|
|
612
|
+
}
|
|
613
|
+
return true;
|
|
614
|
+
};
|
|
615
|
+
const message = rest ? `Expected a tuple of at least ${items.length} element(s) matching the declared types` : `Expected a tuple of exactly ${items.length} element(s) matching the declared types`;
|
|
616
|
+
return {
|
|
617
|
+
type: [{
|
|
618
|
+
type: SchemaTypes.Mixed,
|
|
619
|
+
required: false
|
|
620
|
+
}],
|
|
621
|
+
default: def,
|
|
622
|
+
required,
|
|
623
|
+
validate: {
|
|
624
|
+
validator,
|
|
625
|
+
message
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Maps `z.literal()` to the closest native Mongoose representation of its
|
|
631
|
+
* value(s):
|
|
632
|
+
* - all-string values -> `String` with Mongoose's native `enum` constraint
|
|
633
|
+
* - all-number / all-boolean values -> that primitive type plus a
|
|
634
|
+
* `validate` enforcing membership (Mongoose has no native `enum` for
|
|
635
|
+
* non-string types)
|
|
636
|
+
* - anything else (mixed types, or types Mongoose has no primitive for,
|
|
637
|
+
* e.g. `bigint`) -> `Mixed` plus the same membership `validate`
|
|
638
|
+
* `z.literal()` supports multiple values in Zod v4 (`z.literal(["a", "b"])`),
|
|
639
|
+
* which is why this always validates against the full `values` array rather
|
|
640
|
+
* than assuming a single value.
|
|
641
|
+
*/
|
|
642
|
+
function parseLiteral(values, required = true, def) {
|
|
643
|
+
const types = new Set(values.map((v) => typeof v));
|
|
644
|
+
const message = `Value must be one of: ${values.map((v) => JSON.stringify(v)).join(", ")}`;
|
|
645
|
+
if (types.size === 1 && types.has("string")) return parseEnum(values, required, def);
|
|
646
|
+
if (types.size === 1 && types.has("number")) return {
|
|
647
|
+
type: Number,
|
|
648
|
+
required,
|
|
649
|
+
unique: false,
|
|
650
|
+
sparse: false,
|
|
651
|
+
default: def,
|
|
652
|
+
validate: {
|
|
653
|
+
validator: (v) => values.includes(v),
|
|
654
|
+
message
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
if (types.size === 1 && types.has("boolean")) return {
|
|
658
|
+
type: Boolean,
|
|
659
|
+
required,
|
|
660
|
+
default: def,
|
|
661
|
+
validate: {
|
|
662
|
+
validator: (v) => values.includes(v),
|
|
663
|
+
message
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
return {
|
|
667
|
+
type: SchemaTypes.Mixed,
|
|
668
|
+
required,
|
|
669
|
+
default: def,
|
|
670
|
+
validate: {
|
|
671
|
+
validator: (v) => values.includes(v),
|
|
672
|
+
message
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* `z.discriminatedUnion()` models real polymorphic documents (variants that
|
|
678
|
+
* share a discriminant key but otherwise diverge in shape), which Mongoose
|
|
679
|
+
* has no first-class support for on a plain nested field (Mongoose's own
|
|
680
|
+
* "discriminator" feature only applies to top-level models / array
|
|
681
|
+
* subdocuments, not to an arbitrary object-valued field). Rather than pick
|
|
682
|
+
* one variant's shape and lose the others (as the plain `union` handling
|
|
683
|
+
* does), this maps the field to `Mixed` and validates it against the
|
|
684
|
+
* *entire* original discriminated-union schema via `.safeParse()` - which
|
|
685
|
+
* already implements exactly the "dispatch on the discriminant, then
|
|
686
|
+
* validate against the matching variant" behavior this needs, so there is no
|
|
687
|
+
* reason to reimplement it.
|
|
688
|
+
*/
|
|
689
|
+
function parseDiscriminatedUnion(field, required = true, def) {
|
|
690
|
+
return {
|
|
691
|
+
type: SchemaTypes.Mixed,
|
|
692
|
+
required,
|
|
693
|
+
default: def,
|
|
694
|
+
validate: {
|
|
695
|
+
validator: (v) => field.safeParse(v).success,
|
|
696
|
+
message: "Value does not match any variant of the discriminated union"
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
//#endregion
|
|
701
|
+
export { zodSchema as default, zodSchema, extendZod, zId, zUUID, zodSchemaRaw };
|
|
702
|
+
|
|
703
|
+
//# sourceMappingURL=index.js.map
|