@hy_ong/zod-kit 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hy_ong/zod-kit",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "A comprehensive TypeScript library providing pre-built Zod validation schemas with full internationalization support for common data types and Taiwan-specific formats",
5
5
  "keywords": [
6
6
  "zod",
@@ -238,38 +238,44 @@ export function date<IsRequired extends boolean = false>(required?: IsRequired,
238
238
 
239
239
  const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
240
240
 
241
- const schema = baseSchema.refine((val) => {
242
- if (val === null) return true
241
+ const schema = baseSchema.superRefine((val, ctx) => {
242
+ if (val === null) return
243
243
 
244
244
  // Required check
245
245
  if (isRequired && (val === "" || val === "null" || val === "undefined")) {
246
- throw new z.ZodError([{ code: "custom", message: getMessage("required"), path: [] }])
246
+ ctx.addIssue({ code: "custom", message: getMessage("required") })
247
+ return
247
248
  }
248
249
 
249
250
  // Format validation
250
251
  if (val !== null && !dayjs(val, format, true).isValid()) {
251
- throw new z.ZodError([{ code: "custom", message: getMessage("format", { format }), path: [] }])
252
+ ctx.addIssue({ code: "custom", message: getMessage("format", { format }) })
253
+ return
252
254
  }
253
255
 
254
256
  const dateObj = dayjs(val, format)
255
257
 
256
258
  // Range checks
257
259
  if (val !== null && min !== undefined && !dateObj.isSameOrAfter(dayjs(min, format))) {
258
- throw new z.ZodError([{ code: "custom", message: getMessage("min", { min }), path: [] }])
260
+ ctx.addIssue({ code: "custom", message: getMessage("min", { min }) })
261
+ return
259
262
  }
260
263
  if (val !== null && max !== undefined && !dateObj.isSameOrBefore(dayjs(max, format))) {
261
- throw new z.ZodError([{ code: "custom", message: getMessage("max", { max }), path: [] }])
264
+ ctx.addIssue({ code: "custom", message: getMessage("max", { max }) })
265
+ return
262
266
  }
263
267
 
264
268
  // String content checks
265
269
  if (val !== null && includes !== undefined && !val.includes(includes)) {
266
- throw new z.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }])
270
+ ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) })
271
+ return
267
272
  }
268
273
  if (val !== null && excludes !== undefined) {
269
274
  const excludeList = Array.isArray(excludes) ? excludes : [excludes]
270
275
  for (const exclude of excludeList) {
271
276
  if (val.includes(exclude)) {
272
- throw new z.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }])
277
+ ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) })
278
+ return
273
279
  }
274
280
  }
275
281
  }
@@ -279,27 +285,31 @@ export function date<IsRequired extends boolean = false>(required?: IsRequired,
279
285
  const targetDate = dateObj.startOf('day')
280
286
 
281
287
  if (val !== null && mustBePast && !targetDate.isBefore(today)) {
282
- throw new z.ZodError([{ code: "custom", message: getMessage("past"), path: [] }])
288
+ ctx.addIssue({ code: "custom", message: getMessage("past") })
289
+ return
283
290
  }
284
291
  if (val !== null && mustBeFuture && !targetDate.isAfter(today)) {
285
- throw new z.ZodError([{ code: "custom", message: getMessage("future"), path: [] }])
292
+ ctx.addIssue({ code: "custom", message: getMessage("future") })
293
+ return
286
294
  }
287
295
  if (val !== null && mustBeToday && !targetDate.isSame(today)) {
288
- throw new z.ZodError([{ code: "custom", message: getMessage("today"), path: [] }])
296
+ ctx.addIssue({ code: "custom", message: getMessage("today") })
297
+ return
289
298
  }
290
299
  if (val !== null && mustNotBeToday && targetDate.isSame(today)) {
291
- throw new z.ZodError([{ code: "custom", message: getMessage("notToday"), path: [] }])
300
+ ctx.addIssue({ code: "custom", message: getMessage("notToday") })
301
+ return
292
302
  }
293
303
 
294
304
  // Weekday/weekend validations
295
305
  if (val !== null && weekdaysOnly && (dateObj.day() === 0 || dateObj.day() === 6)) {
296
- throw new z.ZodError([{ code: "custom", message: getMessage("weekday"), path: [] }])
306
+ ctx.addIssue({ code: "custom", message: getMessage("weekday") })
307
+ return
297
308
  }
298
309
  if (val !== null && weekendsOnly && dateObj.day() !== 0 && dateObj.day() !== 6) {
299
- throw new z.ZodError([{ code: "custom", message: getMessage("weekend"), path: [] }])
310
+ ctx.addIssue({ code: "custom", message: getMessage("weekend") })
311
+ return
300
312
  }
301
-
302
- return true
303
313
  })
304
314
 
305
315
  return schema as unknown as DateSchema<IsRequired>
@@ -515,25 +515,27 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
515
515
 
516
516
  const baseSchema = isRequired ? z.preprocess(preprocessFn, z.string()) : z.preprocess(preprocessFn, z.string().nullable())
517
517
 
518
- const schema = baseSchema.refine((val) => {
519
- if (val === null) return true
518
+ const schema = baseSchema.superRefine((val, ctx) => {
519
+ if (val === null) return
520
520
 
521
521
  // Required check
522
522
  if (isRequired && (val === "" || val === "null" || val === "undefined")) {
523
- throw new z.ZodError([{ code: "custom", message: getMessage("required"), path: [] }])
523
+ ctx.addIssue({ code: "custom", message: getMessage("required") })
524
+ return
524
525
  }
525
526
 
526
- if (val === null) return true
527
- if (!isRequired && val === "") return true
527
+ if (val === null) return
528
+ if (!isRequired && val === "") return
528
529
 
529
530
  // Whitelist check
530
531
  if (whitelist && whitelist.length > 0) {
531
532
  if (whitelist.includes(val)) {
532
- return true
533
+ return
533
534
  }
534
535
  // If whitelistOnly is true, reject values not in whitelist
535
536
  if (whitelistOnly) {
536
- throw new z.ZodError([{ code: "custom", message: getMessage("notInWhitelist"), path: [] }])
537
+ ctx.addIssue({ code: "custom", message: getMessage("notInWhitelist") })
538
+ return
537
539
  }
538
540
  // Otherwise, continue with normal validation
539
541
  }
@@ -541,32 +543,36 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
541
543
  // Custom regex validation (overrides format validation)
542
544
  if (regex) {
543
545
  if (!regex.test(val)) {
544
- throw new z.ZodError([{ code: "custom", message: getMessage("customRegex"), path: [] }])
546
+ ctx.addIssue({ code: "custom", message: getMessage("customRegex") })
547
+ return
545
548
  }
546
549
  } else {
547
550
  // DateTime format validation (only if no regex is provided)
548
551
  if (!validateDateTimeFormat(val, format)) {
549
- throw new z.ZodError([{ code: "custom", message: getMessage("format", { format }), path: [] }])
552
+ ctx.addIssue({ code: "custom", message: getMessage("format", { format }) })
553
+ return
550
554
  }
551
555
  }
552
556
 
553
557
  // String content checks
554
558
  if (includes && !val.includes(includes)) {
555
- throw new z.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }])
559
+ ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) })
560
+ return
556
561
  }
557
562
 
558
563
  if (excludes) {
559
564
  const excludeList = Array.isArray(excludes) ? excludes : [excludes]
560
565
  for (const exclude of excludeList) {
561
566
  if (val.includes(exclude)) {
562
- throw new z.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }])
567
+ ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) })
568
+ return
563
569
  }
564
570
  }
565
571
  }
566
572
 
567
573
  // Skip datetime parsing and range validation if using custom regex
568
574
  if (regex) {
569
- return true
575
+ return
570
576
  }
571
577
 
572
578
  // Parse datetime for validation
@@ -575,32 +581,38 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
575
581
  // Check if it's a format issue or parsing issue
576
582
  const pattern = DATETIME_PATTERNS[format]
577
583
  if (!pattern.test(val.trim())) {
578
- throw new z.ZodError([{ code: "custom", message: getMessage("format", { format }), path: [] }])
584
+ ctx.addIssue({ code: "custom", message: getMessage("format", { format }) })
585
+ return
579
586
  } else {
580
- throw new z.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }])
587
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") })
588
+ return
581
589
  }
582
590
  }
583
591
 
584
592
  // Hour validation
585
593
  const hour = parsed.hour()
586
594
  if (minHour !== undefined && hour < minHour) {
587
- throw new z.ZodError([{ code: "custom", message: getMessage("hour", { minHour, maxHour: maxHour ?? 23 }), path: [] }])
595
+ ctx.addIssue({ code: "custom", message: getMessage("hour", { minHour, maxHour: maxHour ?? 23 }) })
596
+ return
588
597
  }
589
598
  if (maxHour !== undefined && hour > maxHour) {
590
- throw new z.ZodError([{ code: "custom", message: getMessage("hour", { minHour: minHour ?? 0, maxHour }), path: [] }])
599
+ ctx.addIssue({ code: "custom", message: getMessage("hour", { minHour: minHour ?? 0, maxHour }) })
600
+ return
591
601
  }
592
602
 
593
603
  // Allowed hours check
594
604
  if (allowedHours && allowedHours.length > 0) {
595
605
  if (!allowedHours.includes(hour)) {
596
- throw new z.ZodError([{ code: "custom", message: getMessage("hour", { allowedHours: allowedHours.join(", ") }), path: [] }])
606
+ ctx.addIssue({ code: "custom", message: getMessage("hour", { allowedHours: allowedHours.join(", ") }) })
607
+ return
597
608
  }
598
609
  }
599
610
 
600
611
  // Minute step validation
601
612
  const minute = parsed.minute()
602
613
  if (minuteStep !== undefined && minute % minuteStep !== 0) {
603
- throw new z.ZodError([{ code: "custom", message: getMessage("minute", { minuteStep }), path: [] }])
614
+ ctx.addIssue({ code: "custom", message: getMessage("minute", { minuteStep }) })
615
+ return
604
616
  }
605
617
 
606
618
  // DateTime range validation (min/max)
@@ -608,7 +620,8 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
608
620
  const minParsed = typeof min === "string" ? parseDateTimeValue(min, format, timezone) : dayjs(min)
609
621
  if (minParsed && parsed.isBefore(minParsed)) {
610
622
  const minFormatted = typeof min === "string" ? min : minParsed.format(format)
611
- throw new z.ZodError([{ code: "custom", message: getMessage("min", { min: minFormatted }), path: [] }])
623
+ ctx.addIssue({ code: "custom", message: getMessage("min", { min: minFormatted }) })
624
+ return
612
625
  }
613
626
  }
614
627
 
@@ -616,7 +629,8 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
616
629
  const maxParsed = typeof max === "string" ? parseDateTimeValue(max, format, timezone) : dayjs(max)
617
630
  if (maxParsed && parsed.isAfter(maxParsed)) {
618
631
  const maxFormatted = typeof max === "string" ? max : maxParsed.format(format)
619
- throw new z.ZodError([{ code: "custom", message: getMessage("max", { max: maxFormatted }), path: [] }])
632
+ ctx.addIssue({ code: "custom", message: getMessage("max", { max: maxFormatted }) })
633
+ return
620
634
  }
621
635
  }
622
636
 
@@ -624,33 +638,37 @@ export function datetime<IsRequired extends boolean = false>(required?: IsRequir
624
638
  const now = timezone ? dayjs().tz(timezone) : dayjs()
625
639
 
626
640
  if (mustBePast && !parsed.isBefore(now)) {
627
- throw new z.ZodError([{ code: "custom", message: getMessage("past"), path: [] }])
641
+ ctx.addIssue({ code: "custom", message: getMessage("past") })
642
+ return
628
643
  }
629
644
 
630
645
  if (mustBeFuture && !parsed.isAfter(now)) {
631
- throw new z.ZodError([{ code: "custom", message: getMessage("future"), path: [] }])
646
+ ctx.addIssue({ code: "custom", message: getMessage("future") })
647
+ return
632
648
  }
633
649
 
634
650
  if (mustBeToday && !parsed.isSame(now, "day")) {
635
- throw new z.ZodError([{ code: "custom", message: getMessage("today"), path: [] }])
651
+ ctx.addIssue({ code: "custom", message: getMessage("today") })
652
+ return
636
653
  }
637
654
 
638
655
  if (mustNotBeToday && parsed.isSame(now, "day")) {
639
- throw new z.ZodError([{ code: "custom", message: getMessage("notToday"), path: [] }])
656
+ ctx.addIssue({ code: "custom", message: getMessage("notToday") })
657
+ return
640
658
  }
641
659
 
642
660
  // Weekday validations
643
661
  const dayOfWeek = parsed.day() // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
644
662
 
645
663
  if (weekdaysOnly && (dayOfWeek === 0 || dayOfWeek === 6)) {
646
- throw new z.ZodError([{ code: "custom", message: getMessage("weekday"), path: [] }])
664
+ ctx.addIssue({ code: "custom", message: getMessage("weekday") })
665
+ return
647
666
  }
648
667
 
649
668
  if (weekendsOnly && dayOfWeek !== 0 && dayOfWeek !== 6) {
650
- throw new z.ZodError([{ code: "custom", message: getMessage("weekend"), path: [] }])
669
+ ctx.addIssue({ code: "custom", message: getMessage("weekend") })
670
+ return
651
671
  }
652
-
653
- return true
654
672
  })
655
673
 
656
674
  return schema as unknown as DateTimeSchema<IsRequired>
@@ -209,37 +209,61 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
209
209
  z.union([z.string().email(), z.null()])
210
210
  )
211
211
 
212
- const schema = baseSchema.refine((val) => {
212
+ const schema = baseSchema.superRefine((val, ctx) => {
213
213
  // Required check first
214
214
  if (isRequired && val === null) {
215
- throw new z.ZodError([{ code: "custom", message: getMessage("required"), path: [] }])
215
+ ctx.addIssue({
216
+ code: "custom",
217
+ message: getMessage("required")
218
+ })
219
+ return
216
220
  }
217
221
 
218
- if (val === null) return true
222
+ if (val === null) return
219
223
 
220
224
  // Invalid email check
221
225
  if (typeof val !== "string") {
222
- throw new z.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }])
226
+ ctx.addIssue({
227
+ code: "custom",
228
+ message: getMessage("invalid")
229
+ })
230
+ return
223
231
  }
224
232
 
225
233
  // Length checks
226
234
  if (minLength !== undefined && val.length < minLength) {
227
- throw new z.ZodError([{ code: "custom", message: getMessage("minLength", { minLength }), path: [] }])
235
+ ctx.addIssue({
236
+ code: "custom",
237
+ message: getMessage("minLength", { minLength })
238
+ })
239
+ return
228
240
  }
229
241
  if (maxLength !== undefined && val.length > maxLength) {
230
- throw new z.ZodError([{ code: "custom", message: getMessage("maxLength", { maxLength }), path: [] }])
242
+ ctx.addIssue({
243
+ code: "custom",
244
+ message: getMessage("maxLength", { maxLength })
245
+ })
246
+ return
231
247
  }
232
248
 
233
249
  // Content checks
234
250
  if (includes !== undefined && !val.includes(includes)) {
235
- throw new z.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }])
251
+ ctx.addIssue({
252
+ code: "custom",
253
+ message: getMessage("includes", { includes })
254
+ })
255
+ return
236
256
  }
237
257
 
238
258
  if (excludes !== undefined) {
239
259
  const excludeList = Array.isArray(excludes) ? excludes : [excludes]
240
260
  for (const exclude of excludeList) {
241
261
  if (val.includes(exclude)) {
242
- throw new z.ZodError([{ code: "custom", message: getMessage("includes", { includes: exclude }), path: [] }])
262
+ ctx.addIssue({
263
+ code: "custom",
264
+ message: getMessage("includes", { includes: exclude })
265
+ })
266
+ return
243
267
  }
244
268
  }
245
269
  }
@@ -247,7 +271,11 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
247
271
  // Extract domain from email
248
272
  const emailDomain = val.split("@")[1]?.toLowerCase()
249
273
  if (!emailDomain) {
250
- throw new z.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }])
274
+ ctx.addIssue({
275
+ code: "custom",
276
+ message: getMessage("invalid")
277
+ })
278
+ return
251
279
  }
252
280
 
253
281
  // Business email check (should come before domain validation)
@@ -260,7 +288,11 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
260
288
  })
261
289
 
262
290
  if (isFreeProvider) {
263
- throw new z.ZodError([{ code: "custom", message: getMessage("businessOnly"), path: [] }])
291
+ ctx.addIssue({
292
+ code: "custom",
293
+ message: getMessage("businessOnly")
294
+ })
295
+ return
264
296
  }
265
297
  }
266
298
 
@@ -275,7 +307,11 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
275
307
  })
276
308
 
277
309
  if (isBlacklisted) {
278
- throw new z.ZodError([{ code: "custom", message: getMessage("domainBlacklist", { domain: emailDomain }), path: [] }])
310
+ ctx.addIssue({
311
+ code: "custom",
312
+ message: getMessage("domainBlacklist", { domain: emailDomain })
313
+ })
314
+ return
279
315
  }
280
316
  }
281
317
 
@@ -291,7 +327,11 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
291
327
  })
292
328
 
293
329
  if (!isAllowed) {
294
- throw new z.ZodError([{ code: "custom", message: getMessage("domain", { domain: Array.isArray(domain) ? domain.join(", ") : domain }), path: [] }])
330
+ ctx.addIssue({
331
+ code: "custom",
332
+ message: getMessage("domain", { domain: Array.isArray(domain) ? domain.join(", ") : domain })
333
+ })
334
+ return
295
335
  }
296
336
  }
297
337
 
@@ -305,11 +345,13 @@ export function email<IsRequired extends boolean = false>(required?: IsRequired,
305
345
  })
306
346
 
307
347
  if (isDisposable) {
308
- throw new z.ZodError([{ code: "custom", message: getMessage("noDisposable"), path: [] }])
348
+ ctx.addIssue({
349
+ code: "custom",
350
+ message: getMessage("noDisposable")
351
+ })
352
+ return
309
353
  }
310
354
  }
311
-
312
- return true
313
355
  })
314
356
 
315
357
  return schema as unknown as EmailSchema<IsRequired>
@@ -369,34 +369,37 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
369
369
  // Create base schema based on type
370
370
  if (isNumericType) {
371
371
  // Use z.any() to avoid Zod's built-in type checking, then validate manually
372
- const numericSchema = z.preprocess(preprocessNumericFn, z.any()).refine((val) => {
372
+ const numericSchema = z.preprocess(preprocessNumericFn, z.any()).superRefine((val, ctx) => {
373
373
  // Allow null for optional fields
374
- if (!isRequired && val === null) return true
374
+ if (!isRequired && val === null) return
375
375
 
376
376
  // Required check for undefined/null/empty (empty string when required)
377
377
  if (val === undefined || (isRequired && val === null)) {
378
- throw new z.ZodError([{ code: "custom", message: getMessage("required"), path: [] }])
378
+ ctx.addIssue({ code: "custom", message: getMessage("required") })
379
+ return
379
380
  }
380
381
 
381
382
  // Numeric validation - check if it's an actual number (not NaN)
382
383
  if (typeof val !== "number" || isNaN(val)) {
383
- throw new z.ZodError([{ code: "custom", message: getMessage("numeric"), path: [] }])
384
+ ctx.addIssue({ code: "custom", message: getMessage("numeric") })
385
+ return
384
386
  }
385
387
 
386
388
  // Length checks on string representation
387
389
  const strVal = String(val)
388
390
  if (!ID_PATTERNS.numeric.test(strVal)) {
389
- throw new z.ZodError([{ code: "custom", message: getMessage("numeric"), path: [] }])
391
+ ctx.addIssue({ code: "custom", message: getMessage("numeric") })
392
+ return
390
393
  }
391
394
 
392
395
  if (minLength !== undefined && strVal.length < minLength) {
393
- throw new z.ZodError([{ code: "custom", message: getMessage("minLength", { minLength }), path: [] }])
396
+ ctx.addIssue({ code: "custom", message: getMessage("minLength", { minLength }) })
397
+ return
394
398
  }
395
399
  if (maxLength !== undefined && strVal.length > maxLength) {
396
- throw new z.ZodError([{ code: "custom", message: getMessage("maxLength", { maxLength }), path: [] }])
400
+ ctx.addIssue({ code: "custom", message: getMessage("maxLength", { maxLength }) })
401
+ return
397
402
  }
398
-
399
- return true
400
403
  })
401
404
 
402
405
  return numericSchema as unknown as IdSchema<IsRequired, Type>
@@ -406,12 +409,13 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
406
409
  const baseSchema = isRequired ? z.preprocess(preprocessStringFn, z.string()) : z.preprocess(preprocessStringFn, z.string().nullable())
407
410
 
408
411
  const schema = baseSchema
409
- .refine((val) => {
410
- if (val === null) return true
412
+ .superRefine((val, ctx) => {
413
+ if (val === null) return
411
414
 
412
415
  // Required check
413
416
  if (isRequired && (val === "" || val === "null" || val === "undefined")) {
414
- throw new z.ZodError([{ code: "custom", message: getMessage("required"), path: [] }])
417
+ ctx.addIssue({ code: "custom", message: getMessage("required") })
418
+ return
415
419
  }
416
420
 
417
421
  // Create comparison value for case-insensitive checks
@@ -419,10 +423,12 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
419
423
 
420
424
  // Length checks
421
425
  if (val !== null && minLength !== undefined && val.length < minLength) {
422
- throw new z.ZodError([{ code: "custom", message: getMessage("minLength", { minLength }), path: [] }])
426
+ ctx.addIssue({ code: "custom", message: getMessage("minLength", { minLength }) })
427
+ return
423
428
  }
424
429
  if (val !== null && maxLength !== undefined && val.length > maxLength) {
425
- throw new z.ZodError([{ code: "custom", message: getMessage("maxLength", { maxLength }), path: [] }])
430
+ ctx.addIssue({ code: "custom", message: getMessage("maxLength", { maxLength }) })
431
+ return
426
432
  }
427
433
 
428
434
  // Check if we have content-based validations that override format checking
@@ -431,7 +437,8 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
431
437
  // Custom regex validation (overrides ID format validation)
432
438
  if (val !== null && customRegex !== undefined) {
433
439
  if (!customRegex.test(val)) {
434
- throw new z.ZodError([{ code: "custom", message: getMessage("customFormat"), path: [] }])
440
+ ctx.addIssue({ code: "custom", message: getMessage("customFormat") })
441
+ return
435
442
  }
436
443
  } else if (val !== null && !hasContentValidations) {
437
444
  // ID type validation (only if no custom regex or content validations)
@@ -442,19 +449,22 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
442
449
  isValidId = allowedTypes.some((allowedType: IdType) => validateIdType(val, allowedType))
443
450
  if (!isValidId) {
444
451
  const typeNames = allowedTypes.join(", ")
445
- throw new z.ZodError([{ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})`, path: [] }])
452
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})` })
453
+ return
446
454
  }
447
455
  } else if (type && type !== "auto") {
448
456
  // Validate specific type
449
457
  isValidId = validateIdType(val, type)
450
458
  if (!isValidId) {
451
- throw new z.ZodError([{ code: "custom", message: getMessage(type as keyof IdMessages) || getMessage("invalid"), path: [] }])
459
+ ctx.addIssue({ code: "custom", message: getMessage(type as keyof IdMessages) || getMessage("invalid") })
460
+ return
452
461
  }
453
462
  } else {
454
463
  // Auto-detection - must match at least one known pattern
455
464
  isValidId = detectIdType(val) !== null
456
465
  if (!isValidId) {
457
- throw new z.ZodError([{ code: "custom", message: getMessage("invalid"), path: [] }])
466
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") })
467
+ return
458
468
  }
459
469
  }
460
470
  } else if (val !== null && hasContentValidations && type && type !== "auto" && !customRegex) {
@@ -463,11 +473,13 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
463
473
  const isValidType = allowedTypes.some((allowedType: IdType) => validateIdType(val, allowedType))
464
474
  if (!isValidType) {
465
475
  const typeNames = allowedTypes.join(", ")
466
- throw new z.ZodError([{ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})`, path: [] }])
476
+ ctx.addIssue({ code: "custom", message: getMessage("invalid") + ` (allowed types: ${typeNames})` })
477
+ return
467
478
  }
468
479
  } else {
469
480
  if (!validateIdType(val, type)) {
470
- throw new z.ZodError([{ code: "custom", message: getMessage(type as keyof IdMessages) || getMessage("invalid"), path: [] }])
481
+ ctx.addIssue({ code: "custom", message: getMessage(type as keyof IdMessages) || getMessage("invalid") })
482
+ return
471
483
  }
472
484
  }
473
485
  }
@@ -478,25 +490,27 @@ export function id<IsRequired extends boolean = false, Type extends IdType | und
478
490
  const searchIncludes = !caseSensitive && includes ? includes.toLowerCase() : includes
479
491
 
480
492
  if (val !== null && startsWith !== undefined && !comparisonVal.startsWith(searchStartsWith!)) {
481
- throw new z.ZodError([{ code: "custom", message: getMessage("startsWith", { startsWith }), path: [] }])
493
+ ctx.addIssue({ code: "custom", message: getMessage("startsWith", { startsWith }) })
494
+ return
482
495
  }
483
496
  if (val !== null && endsWith !== undefined && !comparisonVal.endsWith(searchEndsWith!)) {
484
- throw new z.ZodError([{ code: "custom", message: getMessage("endsWith", { endsWith }), path: [] }])
497
+ ctx.addIssue({ code: "custom", message: getMessage("endsWith", { endsWith }) })
498
+ return
485
499
  }
486
500
  if (val !== null && includes !== undefined && !comparisonVal.includes(searchIncludes!)) {
487
- throw new z.ZodError([{ code: "custom", message: getMessage("includes", { includes }), path: [] }])
501
+ ctx.addIssue({ code: "custom", message: getMessage("includes", { includes }) })
502
+ return
488
503
  }
489
504
  if (val !== null && excludes !== undefined) {
490
505
  const excludeList = Array.isArray(excludes) ? excludes : [excludes]
491
506
  for (const exclude of excludeList) {
492
507
  const searchExclude = !caseSensitive ? exclude.toLowerCase() : exclude
493
508
  if (comparisonVal.includes(searchExclude)) {
494
- throw new z.ZodError([{ code: "custom", message: getMessage("excludes", { excludes: exclude }), path: [] }])
509
+ ctx.addIssue({ code: "custom", message: getMessage("excludes", { excludes: exclude }) })
510
+ return
495
511
  }
496
512
  }
497
513
  }
498
-
499
- return true
500
514
  })
501
515
  .transform((val) => {
502
516
  if (val === null) return val