@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/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