@hy_ong/zod-kit 0.2.7 → 0.2.9

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 (37) hide show
  1. package/README.md +50 -51
  2. package/dist/{chunk-U2PB6XEO.js → chunk-2XLAXQGM.js} +43 -1
  3. package/dist/{chunk-KIUO2HIR.cjs → chunk-3GAFULXI.cjs} +15 -3
  4. package/dist/{chunk-RHKBT3M2.js → chunk-B6XAA3UV.js} +7 -7
  5. package/dist/{chunk-4AQB4RSU.cjs → chunk-FVWOLTP6.cjs} +10 -10
  6. package/dist/{chunk-TDEXEIHH.cjs → chunk-IFSUPBIF.cjs} +43 -1
  7. package/dist/{chunk-MUXYP6IK.js → chunk-IZEVVOXI.js} +20 -7
  8. package/dist/{chunk-OGU7AIZF.js → chunk-LUFTQDGA.js} +15 -3
  9. package/dist/{chunk-53EEWALQ.cjs → chunk-NLEKGYTV.cjs} +14 -14
  10. package/dist/{chunk-RVGCMQ4J.js → chunk-S5N6EFNB.js} +5 -5
  11. package/dist/{chunk-V2WFT5M2.cjs → chunk-SFJZLW6P.cjs} +21 -8
  12. package/dist/common/date.cjs +2 -2
  13. package/dist/common/date.js +1 -1
  14. package/dist/common/datetime.cjs +2 -2
  15. package/dist/common/datetime.js +1 -1
  16. package/dist/common/file.cjs +2 -2
  17. package/dist/common/file.js +1 -1
  18. package/dist/common/url.cjs +2 -2
  19. package/dist/common/url.js +1 -1
  20. package/dist/index.cjs +8 -6
  21. package/dist/index.d.cts +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +7 -5
  24. package/dist/taiwan/tel.cjs +4 -2
  25. package/dist/taiwan/tel.d.cts +41 -16
  26. package/dist/taiwan/tel.d.ts +41 -16
  27. package/dist/taiwan/tel.js +3 -1
  28. package/package.json +10 -10
  29. package/src/validators/common/date.ts +5 -5
  30. package/src/validators/common/datetime.ts +7 -7
  31. package/src/validators/common/file.ts +19 -3
  32. package/src/validators/common/url.ts +76 -1
  33. package/src/validators/taiwan/tel.ts +69 -26
  34. package/tests/common/file.test.ts +20 -0
  35. package/tests/common/url.test.ts +18 -0
  36. package/tests/taiwan/tel.test.ts +50 -14
  37. package/tsconfig.json +2 -1
package/README.md CHANGED
@@ -37,7 +37,7 @@ pnpm add @hy_ong/zod-kit zod
37
37
  ## 🚀 Quick Start
38
38
 
39
39
  ```typescript
40
- import { email, password, text, mobile, datetime, time, postalCode } from '@hy_ong/zod-kit'
40
+ import { email, password, text, twMobile, datetime, time, twPostalCode } from '@hy_ong/zod-kit'
41
41
 
42
42
  // Simple email validation (required by default)
43
43
  const emailSchema = email(true)
@@ -56,7 +56,7 @@ const passwordSchema = password(true, {
56
56
  })
57
57
 
58
58
  // Taiwan mobile phone validation
59
- const phoneSchema = mobile(true)
59
+ const phoneSchema = twMobile(true)
60
60
  phoneSchema.parse('0912345678') // ✅ "0912345678"
61
61
 
62
62
  // DateTime validation
@@ -68,7 +68,7 @@ const timeSchema = time(true)
68
68
  timeSchema.parse('14:30') // ✅ "14:30"
69
69
 
70
70
  // Taiwan postal code validation
71
- const postalSchema = postalCode(true)
71
+ const postalSchema = twPostalCode(true)
72
72
  postalSchema.parse('100001') // ✅ "100001"
73
73
  ```
74
74
 
@@ -121,15 +121,13 @@ import { password } from '@hy_ong/zod-kit'
121
121
 
122
122
  // Required password with complexity rules
123
123
  const passwordSchema = password(true, {
124
- minLength: 8, // Minimum length
125
- maxLength: 128, // Maximum length
126
- requireUppercase: true, // Require A-Z
127
- requireLowercase: true, // Require a-z
128
- requireDigits: true, // Require 0-9
129
- requireSpecialChars: true,// Require !@#$%^&*
130
- customPatterns: [
131
- { pattern: /[A-Z]/, message: 'Need uppercase' }
132
- ]
124
+ min: 8, // Minimum length
125
+ max: 128, // Maximum length
126
+ uppercase: true, // Require A-Z
127
+ lowercase: true, // Require a-z
128
+ digits: true, // Require 0-9
129
+ special: true, // Require !@#$%^&*
130
+ regex: /[A-Z]/ // Custom pattern
133
131
  })
134
132
 
135
133
  // Optional password
@@ -340,116 +338,117 @@ const optionalId = id(false)
340
338
 
341
339
  ### Taiwan-Specific Validators
342
340
 
343
- #### `nationalId(required?, options?)`
341
+ #### `twNationalId(required?, options?)`
344
342
 
345
343
  Validates Taiwan National ID (身份證字號).
346
344
 
347
345
  ```typescript
348
- import { nationalId } from '@hy_ong/zod-kit'
346
+ import { twNationalId } from '@hy_ong/zod-kit'
349
347
 
350
- const idSchema = nationalId(true, {
351
- normalize: true, // Convert to uppercase
352
- whitelist: ['A123456789'] // Allow specific IDs
348
+ const idSchema = twNationalId(true, {
349
+ type: 'both',
350
+ allowOldResident: true
353
351
  })
354
352
 
355
353
  idSchema.parse('A123456789') // ✅ Valid Taiwan National ID
356
354
 
357
- const optionalId = nationalId(false)
355
+ const optionalId = twNationalId(false)
358
356
  ```
359
357
 
360
- #### `businessId(required?, options?)`
358
+ #### `twBusinessId(required?, options?)`
361
359
 
362
360
  Validates Taiwan Business ID (統一編號).
363
361
 
364
362
  ```typescript
365
- import { businessId } from '@hy_ong/zod-kit'
363
+ import { twBusinessId } from '@hy_ong/zod-kit'
366
364
 
367
- const bizSchema = businessId(true)
365
+ const bizSchema = twBusinessId(true)
368
366
  bizSchema.parse('12345675') // ✅ Valid business ID with checksum
369
367
 
370
- const optionalBizId = businessId(false)
368
+ const optionalBizId = twBusinessId(false)
371
369
  ```
372
370
 
373
- #### `mobile(required?, options?)`
371
+ #### `twMobile(required?, options?)`
374
372
 
375
373
  Validates Taiwan mobile phone numbers.
376
374
 
377
375
  ```typescript
378
- import { mobile } from '@hy_ong/zod-kit'
376
+ import { twMobile } from '@hy_ong/zod-kit'
379
377
 
380
- const phoneSchema = mobile(true, {
381
- allowInternational: true, // Allow +886 prefix
382
- allowSeparators: true, // Allow 0912-345-678
383
- operators: ['09'] // Restrict to specific operators
378
+ const phoneSchema = twMobile(true, {
379
+ transform: (value) => value.replace(/[-\s]/g, '')
384
380
  })
381
+ phoneSchema.parse('0912-345-678') // ✅ "0912345678"
385
382
 
386
- const optionalMobile = mobile(false)
383
+ const optionalMobile = twMobile(false)
387
384
  ```
388
385
 
389
- #### `tel(required?, options?)`
386
+ #### `twTel(required?, options?)`
390
387
 
391
- Validates Taiwan landline telephone numbers.
388
+ Validates Taiwan telephone numbers, including landline, 070 VoIP, domestic 0800/0809 recipient-paid numbers, and optional 008 ITFS/UIFN numbers.
392
389
 
393
390
  ```typescript
394
- import { tel } from '@hy_ong/zod-kit'
391
+ import { twTel } from '@hy_ong/zod-kit'
395
392
 
396
- const landlineSchema = tel(true, {
397
- allowSeparators: true, // Allow 02-1234-5678
398
- areaCodes: ['02', '03'] // Restrict to specific areas
399
- })
393
+ const landlineSchema = twTel(true)
394
+ landlineSchema.parse('02-2345-6789') // Valid landline number
395
+
396
+ const itfsSchema = twTel(true, { allowITFS: true })
397
+ itfsSchema.parse('00800-2468-1668') // ✅ Valid UIFN number
398
+ itfsSchema.parse('00801-852-747') // ✅ Valid ITFS number
400
399
 
401
- const optionalTel = tel(false)
400
+ const optionalTel = twTel(false)
402
401
  ```
403
402
 
404
- #### `fax(required?, options?)`
403
+ #### `twFax(required?, options?)`
405
404
 
406
405
  Validates Taiwan fax numbers (same format as landline).
407
406
 
408
407
  ```typescript
409
- import { fax } from '@hy_ong/zod-kit'
408
+ import { twFax } from '@hy_ong/zod-kit'
410
409
 
411
- const faxSchema = fax(true)
410
+ const faxSchema = twFax(true)
412
411
  faxSchema.parse('02-2345-6789') // ✅ Valid fax number
413
412
 
414
- const optionalFax = fax(false)
413
+ const optionalFax = twFax(false)
415
414
  ```
416
415
 
417
- #### `postalCode(required?, options?)`
416
+ #### `twPostalCode(required?, options?)`
418
417
 
419
418
  Validates Taiwan postal codes with support for 3-digit, 5-digit, and 6-digit formats.
420
419
 
421
420
  ```typescript
422
- import { postalCode } from '@hy_ong/zod-kit'
421
+ import { twPostalCode } from '@hy_ong/zod-kit'
423
422
 
424
423
  // Accept 3-digit or 6-digit formats (recommended)
425
- const modernSchema = postalCode(true)
424
+ const modernSchema = twPostalCode(true)
426
425
  modernSchema.parse('100') // ✅ Valid 3-digit
427
426
  modernSchema.parse('100001') // ✅ Valid 6-digit
428
427
 
429
428
  // Accept all formats
430
- const flexibleSchema = postalCode(true, { format: 'all' })
429
+ const flexibleSchema = twPostalCode(true, { format: 'all' })
431
430
  flexibleSchema.parse('100') // ✅ Valid
432
431
  flexibleSchema.parse('10001') // ✅ Valid (5-digit legacy)
433
432
  flexibleSchema.parse('100001') // ✅ Valid
434
433
 
435
434
  // Only 6-digit format (current standard)
436
- const modernOnlySchema = postalCode(true, { format: '6' })
435
+ const modernOnlySchema = twPostalCode(true, { format: '6' })
437
436
  modernOnlySchema.parse('100001') // ✅ Valid
438
437
  modernOnlySchema.parse('100') // ❌ Invalid
439
438
 
440
439
  // With dashes allowed
441
- const dashSchema = postalCode(true, { allowDashes: true })
440
+ const dashSchema = twPostalCode(true, { allowDashes: true })
442
441
  dashSchema.parse('100-001') // ✅ Valid (normalized to '100001')
443
442
 
444
443
  // Specific areas only
445
- const taipeiSchema = postalCode(true, {
444
+ const taipeiSchema = twPostalCode(true, {
446
445
  allowedPrefixes: ['100', '103', '104', '105', '106']
447
446
  })
448
447
  taipeiSchema.parse('100001') // ✅ Valid (Taipei area)
449
448
  taipeiSchema.parse('200001') // ❌ Invalid (not in allowlist)
450
449
 
451
450
  // Optional postal code
452
- const optionalPostal = postalCode(false)
451
+ const optionalPostal = twPostalCode(false)
453
452
  ```
454
453
 
455
454
  ## 🌐 Internationalization
@@ -542,7 +541,7 @@ import { email, password } from '@hy_ong/zod-kit'
542
541
 
543
542
  const userSchema = z.object({
544
543
  email: email(true),
545
- password: password(true, { minLength: 8 }),
544
+ password: password(true, { min: 8 }),
546
545
  confirmPassword: z.string()
547
546
  }).refine(data => data.password === data.confirmPassword, {
548
547
  message: "Passwords don't match"
@@ -614,4 +613,4 @@ Contributions are welcome! Please feel free to submit a Pull Request. For major
614
613
 
615
614
  **Made with ❤️ by [Ong Hoe Yuan](https://github.com/hy-ong)**
616
615
 
617
- For questions or support, please [open an issue](https://github.com/hy-ong/zod-kit/issues) on GitHub.
616
+ For questions or support, please [open an issue](https://github.com/hy-ong/zod-kit/issues) on GitHub.
@@ -5,6 +5,48 @@ import {
5
5
 
6
6
  // src/validators/common/url.ts
7
7
  import { z } from "zod";
8
+ var getNormalizedHostname = (hostname) => hostname.toLowerCase().replace(/^\[(.*)]$/, "$1");
9
+ var parseIPv4 = (hostname) => {
10
+ const parts = hostname.split(".");
11
+ if (parts.length !== 4) return null;
12
+ const octets = parts.map((part) => {
13
+ if (!/^\d+$/.test(part)) return null;
14
+ const value = Number(part);
15
+ return Number.isInteger(value) && value >= 0 && value <= 255 ? value : null;
16
+ });
17
+ return octets.every((part) => part !== null) ? octets : null;
18
+ };
19
+ var isLocalIPv4 = ([first, second]) => first === 0 || first === 10 || first === 127 || first === 169 && second === 254 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168;
20
+ var parseHextet = (value) => {
21
+ if (!/^[0-9a-f]{1,4}$/i.test(value)) return null;
22
+ const parsed = Number.parseInt(value, 16);
23
+ return Number.isInteger(parsed) && parsed >= 0 && parsed <= 65535 ? parsed : null;
24
+ };
25
+ var parseIPv4MappedIPv6 = (hostname) => {
26
+ if (!hostname.startsWith("::ffff:")) return null;
27
+ const mappedValue = hostname.slice("::ffff:".length);
28
+ const dottedIPv4 = parseIPv4(mappedValue);
29
+ if (dottedIPv4) return dottedIPv4;
30
+ const hextets = mappedValue.split(":");
31
+ if (hextets.length !== 2) return null;
32
+ const high = parseHextet(hextets[0]);
33
+ const low = parseHextet(hextets[1]);
34
+ if (high === null || low === null) return null;
35
+ return [high >> 8 & 255, high & 255, low >> 8 & 255, low & 255];
36
+ };
37
+ var isLocalNetworkHostname = (hostname) => {
38
+ const normalizedHostname = getNormalizedHostname(hostname);
39
+ if (normalizedHostname === "localhost") return true;
40
+ const ipv4 = parseIPv4(normalizedHostname);
41
+ if (ipv4) {
42
+ return isLocalIPv4(ipv4);
43
+ }
44
+ if (!normalizedHostname.includes(":")) return false;
45
+ const mappedIPv4 = parseIPv4MappedIPv6(normalizedHostname);
46
+ if (mappedIPv4) return isLocalIPv4(mappedIPv4);
47
+ const firstHextet = parseHextet(normalizedHostname.split(":")[0]);
48
+ return normalizedHostname === "::" || normalizedHostname === "::1" || firstHextet !== null && ((firstHextet & 65024) === 64512 || (firstHextet & 65472) === 65152);
49
+ };
8
50
  function url(required, options) {
9
51
  const {
10
52
  min,
@@ -133,7 +175,7 @@ function url(required, options) {
133
175
  ctx.addIssue({ code: "custom", message: getMessage("noFragment") });
134
176
  return;
135
177
  }
136
- const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./);
178
+ const isLocalhost = isLocalNetworkHostname(hostname);
137
179
  if (blockLocalhost && isLocalhost) {
138
180
  ctx.addIssue({ code: "custom", message: getMessage("noLocalhost") });
139
181
  return;
@@ -5,6 +5,18 @@ var _chunkQ7TUNJD4cjs = require('./chunk-Q7TUNJD4.cjs');
5
5
 
6
6
  // src/validators/common/file.ts
7
7
  var _zod = require('zod');
8
+ function getFileConstructor() {
9
+ return typeof globalThis.File === "function" ? globalThis.File : void 0;
10
+ }
11
+ function isFileLike(value) {
12
+ if (value === null || typeof value !== "object") return false;
13
+ const candidate = value;
14
+ return typeof candidate.name === "string" && typeof candidate.size === "number" && typeof candidate.type === "string";
15
+ }
16
+ function isFileValue(value) {
17
+ const FileConstructor = getFileConstructor();
18
+ return FileConstructor ? value instanceof FileConstructor : isFileLike(value);
19
+ }
8
20
  function file(required, options) {
9
21
  const {
10
22
  maxSize,
@@ -53,12 +65,12 @@ function file(required, options) {
53
65
  const audioTypes = ["audio/mpeg", "audio/wav", "audio/ogg", "audio/aac", "audio/webm", "audio/mp3", "audio/x-wav"];
54
66
  const archiveTypes = ["application/zip", "application/x-rar-compressed", "application/x-7z-compressed", "application/x-tar", "application/gzip"];
55
67
  const actualDefaultValue = _nullishCoalesce(defaultValue, () => ( null));
56
- const fileOrNullSchema = _zod.z.union([_zod.z.instanceof(File), _zod.z.null()]);
68
+ const fileOrNullSchema = _zod.z.union([_zod.z.custom(isFileValue, { message: getMessage("invalid") }), _zod.z.null()]);
57
69
  const baseSchema = _zod.z.preprocess((val) => {
58
70
  if (val === "" || val === null || val === void 0) {
59
71
  return actualDefaultValue;
60
72
  }
61
- if (!(val instanceof File)) {
73
+ if (!isFileValue(val)) {
62
74
  return val;
63
75
  }
64
76
  let processed = val;
@@ -69,7 +81,7 @@ function file(required, options) {
69
81
  }, fileOrNullSchema);
70
82
  const schema = baseSchema.refine((val) => !isRequired || val !== null, {
71
83
  message: getMessage("required")
72
- }).refine((val) => val === null || val instanceof File, {
84
+ }).refine((val) => val === null || isFileValue(val), {
73
85
  message: getMessage("invalid")
74
86
  }).refine((val) => val === null || minSize === void 0 || val.size >= minSize, {
75
87
  message: getMessage("minSize", { minSize: formatFileSize(minSize || 0) })
@@ -6,13 +6,13 @@ import {
6
6
  // src/validators/common/datetime.ts
7
7
  import { z } from "zod";
8
8
  import dayjs from "dayjs";
9
- import customParseFormat from "dayjs/plugin/customParseFormat";
10
- import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
11
- import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
12
- import isToday from "dayjs/plugin/isToday";
13
- import weekday from "dayjs/plugin/weekday";
14
- import timezone from "dayjs/plugin/timezone";
15
- import utc from "dayjs/plugin/utc";
9
+ import customParseFormat from "dayjs/plugin/customParseFormat.js";
10
+ import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
11
+ import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js";
12
+ import isToday from "dayjs/plugin/isToday.js";
13
+ import weekday from "dayjs/plugin/weekday.js";
14
+ import timezone from "dayjs/plugin/timezone.js";
15
+ import utc from "dayjs/plugin/utc.js";
16
16
  dayjs.extend(isSameOrAfter);
17
17
  dayjs.extend(isSameOrBefore);
18
18
  dayjs.extend(customParseFormat);
@@ -6,16 +6,16 @@ var _chunkQ7TUNJD4cjs = require('./chunk-Q7TUNJD4.cjs');
6
6
  // src/validators/common/date.ts
7
7
  var _zod = require('zod');
8
8
  var _dayjs = require('dayjs'); var _dayjs2 = _interopRequireDefault(_dayjs);
9
- var _customParseFormat = require('dayjs/plugin/customParseFormat'); var _customParseFormat2 = _interopRequireDefault(_customParseFormat);
10
- var _isSameOrAfter = require('dayjs/plugin/isSameOrAfter'); var _isSameOrAfter2 = _interopRequireDefault(_isSameOrAfter);
11
- var _isSameOrBefore = require('dayjs/plugin/isSameOrBefore'); var _isSameOrBefore2 = _interopRequireDefault(_isSameOrBefore);
12
- var _isToday = require('dayjs/plugin/isToday'); var _isToday2 = _interopRequireDefault(_isToday);
13
- var _weekday = require('dayjs/plugin/weekday'); var _weekday2 = _interopRequireDefault(_weekday);
14
- _dayjs2.default.extend(_isSameOrAfter2.default);
15
- _dayjs2.default.extend(_isSameOrBefore2.default);
16
- _dayjs2.default.extend(_customParseFormat2.default);
17
- _dayjs2.default.extend(_isToday2.default);
18
- _dayjs2.default.extend(_weekday2.default);
9
+ var _customParseFormatjs = require('dayjs/plugin/customParseFormat.js'); var _customParseFormatjs2 = _interopRequireDefault(_customParseFormatjs);
10
+ var _isSameOrAfterjs = require('dayjs/plugin/isSameOrAfter.js'); var _isSameOrAfterjs2 = _interopRequireDefault(_isSameOrAfterjs);
11
+ var _isSameOrBeforejs = require('dayjs/plugin/isSameOrBefore.js'); var _isSameOrBeforejs2 = _interopRequireDefault(_isSameOrBeforejs);
12
+ var _isTodayjs = require('dayjs/plugin/isToday.js'); var _isTodayjs2 = _interopRequireDefault(_isTodayjs);
13
+ var _weekdayjs = require('dayjs/plugin/weekday.js'); var _weekdayjs2 = _interopRequireDefault(_weekdayjs);
14
+ _dayjs2.default.extend(_isSameOrAfterjs2.default);
15
+ _dayjs2.default.extend(_isSameOrBeforejs2.default);
16
+ _dayjs2.default.extend(_customParseFormatjs2.default);
17
+ _dayjs2.default.extend(_isTodayjs2.default);
18
+ _dayjs2.default.extend(_weekdayjs2.default);
19
19
  function date(required, options) {
20
20
  const {
21
21
  min,
@@ -5,6 +5,48 @@ var _chunkQ7TUNJD4cjs = require('./chunk-Q7TUNJD4.cjs');
5
5
 
6
6
  // src/validators/common/url.ts
7
7
  var _zod = require('zod');
8
+ var getNormalizedHostname = (hostname) => hostname.toLowerCase().replace(/^\[(.*)]$/, "$1");
9
+ var parseIPv4 = (hostname) => {
10
+ const parts = hostname.split(".");
11
+ if (parts.length !== 4) return null;
12
+ const octets = parts.map((part) => {
13
+ if (!/^\d+$/.test(part)) return null;
14
+ const value = Number(part);
15
+ return Number.isInteger(value) && value >= 0 && value <= 255 ? value : null;
16
+ });
17
+ return octets.every((part) => part !== null) ? octets : null;
18
+ };
19
+ var isLocalIPv4 = ([first, second]) => first === 0 || first === 10 || first === 127 || first === 169 && second === 254 || first === 172 && second >= 16 && second <= 31 || first === 192 && second === 168;
20
+ var parseHextet = (value) => {
21
+ if (!/^[0-9a-f]{1,4}$/i.test(value)) return null;
22
+ const parsed = Number.parseInt(value, 16);
23
+ return Number.isInteger(parsed) && parsed >= 0 && parsed <= 65535 ? parsed : null;
24
+ };
25
+ var parseIPv4MappedIPv6 = (hostname) => {
26
+ if (!hostname.startsWith("::ffff:")) return null;
27
+ const mappedValue = hostname.slice("::ffff:".length);
28
+ const dottedIPv4 = parseIPv4(mappedValue);
29
+ if (dottedIPv4) return dottedIPv4;
30
+ const hextets = mappedValue.split(":");
31
+ if (hextets.length !== 2) return null;
32
+ const high = parseHextet(hextets[0]);
33
+ const low = parseHextet(hextets[1]);
34
+ if (high === null || low === null) return null;
35
+ return [high >> 8 & 255, high & 255, low >> 8 & 255, low & 255];
36
+ };
37
+ var isLocalNetworkHostname = (hostname) => {
38
+ const normalizedHostname = getNormalizedHostname(hostname);
39
+ if (normalizedHostname === "localhost") return true;
40
+ const ipv4 = parseIPv4(normalizedHostname);
41
+ if (ipv4) {
42
+ return isLocalIPv4(ipv4);
43
+ }
44
+ if (!normalizedHostname.includes(":")) return false;
45
+ const mappedIPv4 = parseIPv4MappedIPv6(normalizedHostname);
46
+ if (mappedIPv4) return isLocalIPv4(mappedIPv4);
47
+ const firstHextet = parseHextet(normalizedHostname.split(":")[0]);
48
+ return normalizedHostname === "::" || normalizedHostname === "::1" || firstHextet !== null && ((firstHextet & 65024) === 64512 || (firstHextet & 65472) === 65152);
49
+ };
8
50
  function url(required, options) {
9
51
  const {
10
52
  min,
@@ -133,7 +175,7 @@ function url(required, options) {
133
175
  ctx.addIssue({ code: "custom", message: getMessage("noFragment") });
134
176
  return;
135
177
  }
136
- const isLocalhost = hostname === "localhost" || hostname === "127.0.0.1" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./);
178
+ const isLocalhost = isLocalNetworkHostname(hostname);
137
179
  if (blockLocalhost && isLocalhost) {
138
180
  ctx.addIssue({ code: "custom", message: getMessage("noLocalhost") });
139
181
  return;
@@ -5,15 +5,24 @@ import {
5
5
 
6
6
  // src/validators/taiwan/tel.ts
7
7
  import { z } from "zod";
8
- var validateTaiwanTel = (value) => {
9
- const cleanValue = value.replace(/[-\s]/g, "");
8
+ var normalizeTaiwanTel = (value) => value.replace(/[-\s]/g, "");
9
+ var validateTaiwanInternationalTollFree = (value) => {
10
+ const cleanValue = normalizeTaiwanTel(value);
11
+ if (/^00800\d{8}$/.test(cleanValue)) {
12
+ return true;
13
+ }
14
+ return /^0080[1-9]\d{1,12}$/.test(cleanValue);
15
+ };
16
+ var validateTaiwanTel = (value, options = {}) => {
17
+ const { allowITFS = false } = options;
18
+ const cleanValue = normalizeTaiwanTel(value);
19
+ if (allowITFS && validateTaiwanInternationalTollFree(cleanValue)) {
20
+ return true;
21
+ }
10
22
  if (!/^0\d{7,10}$/.test(cleanValue)) {
11
23
  return false;
12
24
  }
13
25
  const areaCode4 = cleanValue.substring(0, 4);
14
- if (areaCode4 === "0700") {
15
- return cleanValue.length === 10 && /^0700\d{6}$/.test(cleanValue);
16
- }
17
26
  if (areaCode4 === "0800" || areaCode4 === "0809") {
18
27
  return cleanValue.length === 10 && /^080[09]\d{6}$/.test(cleanValue);
19
28
  }
@@ -21,6 +30,9 @@ var validateTaiwanTel = (value) => {
21
30
  return (cleanValue.length === 9 || cleanValue.length === 10) && /^0836\d{5,6}$/.test(cleanValue);
22
31
  }
23
32
  const areaCode3 = cleanValue.substring(0, 3);
33
+ if (areaCode3 === "070") {
34
+ return cleanValue.length === 11 && /^070\d{8}$/.test(cleanValue);
35
+ }
24
36
  if (areaCode3 === "037") {
25
37
  const firstDigit = cleanValue[3];
26
38
  if (firstDigit === "0" || firstDigit === "1") {
@@ -74,7 +86,7 @@ var validateTaiwanTel = (value) => {
74
86
  return false;
75
87
  };
76
88
  function twTel(required, options) {
77
- const { whitelist, transform, defaultValue, i18n } = options ?? {};
89
+ const { whitelist, allowITFS = false, transform, defaultValue, i18n } = options ?? {};
78
90
  const isRequired = required ?? false;
79
91
  const actualDefaultValue = defaultValue ?? (isRequired ? "" : null);
80
92
  const getMessage = (key, params) => {
@@ -119,7 +131,7 @@ function twTel(required, options) {
119
131
  if (whitelist && whitelist.length > 0 && whitelist.includes(val)) {
120
132
  return;
121
133
  }
122
- if (!validateTaiwanTel(val)) {
134
+ if (!validateTaiwanTel(val, { allowITFS })) {
123
135
  ctx.addIssue({ code: "custom", message: getMessage("invalid") });
124
136
  return;
125
137
  }
@@ -128,6 +140,7 @@ function twTel(required, options) {
128
140
  }
129
141
 
130
142
  export {
143
+ validateTaiwanInternationalTollFree,
131
144
  validateTaiwanTel,
132
145
  twTel
133
146
  };
@@ -5,6 +5,18 @@ import {
5
5
 
6
6
  // src/validators/common/file.ts
7
7
  import { z } from "zod";
8
+ function getFileConstructor() {
9
+ return typeof globalThis.File === "function" ? globalThis.File : void 0;
10
+ }
11
+ function isFileLike(value) {
12
+ if (value === null || typeof value !== "object") return false;
13
+ const candidate = value;
14
+ return typeof candidate.name === "string" && typeof candidate.size === "number" && typeof candidate.type === "string";
15
+ }
16
+ function isFileValue(value) {
17
+ const FileConstructor = getFileConstructor();
18
+ return FileConstructor ? value instanceof FileConstructor : isFileLike(value);
19
+ }
8
20
  function file(required, options) {
9
21
  const {
10
22
  maxSize,
@@ -53,12 +65,12 @@ function file(required, options) {
53
65
  const audioTypes = ["audio/mpeg", "audio/wav", "audio/ogg", "audio/aac", "audio/webm", "audio/mp3", "audio/x-wav"];
54
66
  const archiveTypes = ["application/zip", "application/x-rar-compressed", "application/x-7z-compressed", "application/x-tar", "application/gzip"];
55
67
  const actualDefaultValue = defaultValue ?? null;
56
- const fileOrNullSchema = z.union([z.instanceof(File), z.null()]);
68
+ const fileOrNullSchema = z.union([z.custom(isFileValue, { message: getMessage("invalid") }), z.null()]);
57
69
  const baseSchema = z.preprocess((val) => {
58
70
  if (val === "" || val === null || val === void 0) {
59
71
  return actualDefaultValue;
60
72
  }
61
- if (!(val instanceof File)) {
73
+ if (!isFileValue(val)) {
62
74
  return val;
63
75
  }
64
76
  let processed = val;
@@ -69,7 +81,7 @@ function file(required, options) {
69
81
  }, fileOrNullSchema);
70
82
  const schema = baseSchema.refine((val) => !isRequired || val !== null, {
71
83
  message: getMessage("required")
72
- }).refine((val) => val === null || val instanceof File, {
84
+ }).refine((val) => val === null || isFileValue(val), {
73
85
  message: getMessage("invalid")
74
86
  }).refine((val) => val === null || minSize === void 0 || val.size >= minSize, {
75
87
  message: getMessage("minSize", { minSize: formatFileSize(minSize || 0) })
@@ -6,20 +6,20 @@ var _chunkQ7TUNJD4cjs = require('./chunk-Q7TUNJD4.cjs');
6
6
  // src/validators/common/datetime.ts
7
7
  var _zod = require('zod');
8
8
  var _dayjs = require('dayjs'); var _dayjs2 = _interopRequireDefault(_dayjs);
9
- var _customParseFormat = require('dayjs/plugin/customParseFormat'); var _customParseFormat2 = _interopRequireDefault(_customParseFormat);
10
- var _isSameOrAfter = require('dayjs/plugin/isSameOrAfter'); var _isSameOrAfter2 = _interopRequireDefault(_isSameOrAfter);
11
- var _isSameOrBefore = require('dayjs/plugin/isSameOrBefore'); var _isSameOrBefore2 = _interopRequireDefault(_isSameOrBefore);
12
- var _isToday = require('dayjs/plugin/isToday'); var _isToday2 = _interopRequireDefault(_isToday);
13
- var _weekday = require('dayjs/plugin/weekday'); var _weekday2 = _interopRequireDefault(_weekday);
14
- var _timezone = require('dayjs/plugin/timezone'); var _timezone2 = _interopRequireDefault(_timezone);
15
- var _utc = require('dayjs/plugin/utc'); var _utc2 = _interopRequireDefault(_utc);
16
- _dayjs2.default.extend(_isSameOrAfter2.default);
17
- _dayjs2.default.extend(_isSameOrBefore2.default);
18
- _dayjs2.default.extend(_customParseFormat2.default);
19
- _dayjs2.default.extend(_isToday2.default);
20
- _dayjs2.default.extend(_weekday2.default);
21
- _dayjs2.default.extend(_timezone2.default);
22
- _dayjs2.default.extend(_utc2.default);
9
+ var _customParseFormatjs = require('dayjs/plugin/customParseFormat.js'); var _customParseFormatjs2 = _interopRequireDefault(_customParseFormatjs);
10
+ var _isSameOrAfterjs = require('dayjs/plugin/isSameOrAfter.js'); var _isSameOrAfterjs2 = _interopRequireDefault(_isSameOrAfterjs);
11
+ var _isSameOrBeforejs = require('dayjs/plugin/isSameOrBefore.js'); var _isSameOrBeforejs2 = _interopRequireDefault(_isSameOrBeforejs);
12
+ var _isTodayjs = require('dayjs/plugin/isToday.js'); var _isTodayjs2 = _interopRequireDefault(_isTodayjs);
13
+ var _weekdayjs = require('dayjs/plugin/weekday.js'); var _weekdayjs2 = _interopRequireDefault(_weekdayjs);
14
+ var _timezonejs = require('dayjs/plugin/timezone.js'); var _timezonejs2 = _interopRequireDefault(_timezonejs);
15
+ var _utcjs = require('dayjs/plugin/utc.js'); var _utcjs2 = _interopRequireDefault(_utcjs);
16
+ _dayjs2.default.extend(_isSameOrAfterjs2.default);
17
+ _dayjs2.default.extend(_isSameOrBeforejs2.default);
18
+ _dayjs2.default.extend(_customParseFormatjs2.default);
19
+ _dayjs2.default.extend(_isTodayjs2.default);
20
+ _dayjs2.default.extend(_weekdayjs2.default);
21
+ _dayjs2.default.extend(_timezonejs2.default);
22
+ _dayjs2.default.extend(_utcjs2.default);
23
23
  var DATETIME_PATTERNS = {
24
24
  "YYYY-MM-DD HH:mm": /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/,
25
25
  "YYYY-MM-DD HH:mm:ss": /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
@@ -6,11 +6,11 @@ import {
6
6
  // src/validators/common/date.ts
7
7
  import { z } from "zod";
8
8
  import dayjs from "dayjs";
9
- import customParseFormat from "dayjs/plugin/customParseFormat";
10
- import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
11
- import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
12
- import isToday from "dayjs/plugin/isToday";
13
- import weekday from "dayjs/plugin/weekday";
9
+ import customParseFormat from "dayjs/plugin/customParseFormat.js";
10
+ import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js";
11
+ import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js";
12
+ import isToday from "dayjs/plugin/isToday.js";
13
+ import weekday from "dayjs/plugin/weekday.js";
14
14
  dayjs.extend(isSameOrAfter);
15
15
  dayjs.extend(isSameOrBefore);
16
16
  dayjs.extend(customParseFormat);
@@ -5,15 +5,24 @@ var _chunkQ7TUNJD4cjs = require('./chunk-Q7TUNJD4.cjs');
5
5
 
6
6
  // src/validators/taiwan/tel.ts
7
7
  var _zod = require('zod');
8
- var validateTaiwanTel = (value) => {
9
- const cleanValue = value.replace(/[-\s]/g, "");
8
+ var normalizeTaiwanTel = (value) => value.replace(/[-\s]/g, "");
9
+ var validateTaiwanInternationalTollFree = (value) => {
10
+ const cleanValue = normalizeTaiwanTel(value);
11
+ if (/^00800\d{8}$/.test(cleanValue)) {
12
+ return true;
13
+ }
14
+ return /^0080[1-9]\d{1,12}$/.test(cleanValue);
15
+ };
16
+ var validateTaiwanTel = (value, options = {}) => {
17
+ const { allowITFS = false } = options;
18
+ const cleanValue = normalizeTaiwanTel(value);
19
+ if (allowITFS && validateTaiwanInternationalTollFree(cleanValue)) {
20
+ return true;
21
+ }
10
22
  if (!/^0\d{7,10}$/.test(cleanValue)) {
11
23
  return false;
12
24
  }
13
25
  const areaCode4 = cleanValue.substring(0, 4);
14
- if (areaCode4 === "0700") {
15
- return cleanValue.length === 10 && /^0700\d{6}$/.test(cleanValue);
16
- }
17
26
  if (areaCode4 === "0800" || areaCode4 === "0809") {
18
27
  return cleanValue.length === 10 && /^080[09]\d{6}$/.test(cleanValue);
19
28
  }
@@ -21,6 +30,9 @@ var validateTaiwanTel = (value) => {
21
30
  return (cleanValue.length === 9 || cleanValue.length === 10) && /^0836\d{5,6}$/.test(cleanValue);
22
31
  }
23
32
  const areaCode3 = cleanValue.substring(0, 3);
33
+ if (areaCode3 === "070") {
34
+ return cleanValue.length === 11 && /^070\d{8}$/.test(cleanValue);
35
+ }
24
36
  if (areaCode3 === "037") {
25
37
  const firstDigit = cleanValue[3];
26
38
  if (firstDigit === "0" || firstDigit === "1") {
@@ -74,7 +86,7 @@ var validateTaiwanTel = (value) => {
74
86
  return false;
75
87
  };
76
88
  function twTel(required, options) {
77
- const { whitelist, transform, defaultValue, i18n } = _nullishCoalesce(options, () => ( {}));
89
+ const { whitelist, allowITFS = false, transform, defaultValue, i18n } = _nullishCoalesce(options, () => ( {}));
78
90
  const isRequired = _nullishCoalesce(required, () => ( false));
79
91
  const actualDefaultValue = _nullishCoalesce(defaultValue, () => ( (isRequired ? "" : null)));
80
92
  const getMessage = (key, params) => {
@@ -119,7 +131,7 @@ function twTel(required, options) {
119
131
  if (whitelist && whitelist.length > 0 && whitelist.includes(val)) {
120
132
  return;
121
133
  }
122
- if (!validateTaiwanTel(val)) {
134
+ if (!validateTaiwanTel(val, { allowITFS })) {
123
135
  ctx.addIssue({ code: "custom", message: getMessage("invalid") });
124
136
  return;
125
137
  }
@@ -130,4 +142,5 @@ function twTel(required, options) {
130
142
 
131
143
 
132
144
 
133
- exports.validateTaiwanTel = validateTaiwanTel; exports.twTel = twTel;
145
+
146
+ exports.validateTaiwanInternationalTollFree = validateTaiwanInternationalTollFree; exports.validateTaiwanTel = validateTaiwanTel; exports.twTel = twTel;