@hy_ong/zod-kit 0.2.8 → 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-ADKXVZHH.js → chunk-IZEVVOXI.js} +17 -4
  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-7EUHTSMF.cjs → chunk-SFJZLW6P.cjs} +18 -5
  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 +40 -15
  26. package/dist/taiwan/tel.d.ts +40 -15
  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 +63 -20
  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 +39 -5
  37. package/tsconfig.json +2 -1
@@ -116,6 +116,81 @@ export type UrlOptions<IsRequired extends boolean = true> = {
116
116
  */
117
117
  export type UrlSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
118
118
 
119
+ const getNormalizedHostname = (hostname: string): string => hostname.toLowerCase().replace(/^\[(.*)]$/, "$1")
120
+
121
+ const parseIPv4 = (hostname: string): number[] | null => {
122
+ const parts = hostname.split(".")
123
+ if (parts.length !== 4) return null
124
+
125
+ const octets = parts.map((part) => {
126
+ if (!/^\d+$/.test(part)) return null
127
+ const value = Number(part)
128
+ return Number.isInteger(value) && value >= 0 && value <= 255 ? value : null
129
+ })
130
+
131
+ return octets.every((part) => part !== null) ? octets as number[] : null
132
+ }
133
+
134
+ const isLocalIPv4 = ([first, second]: number[]): boolean => (
135
+ first === 0 ||
136
+ first === 10 ||
137
+ first === 127 ||
138
+ (first === 169 && second === 254) ||
139
+ (first === 172 && second >= 16 && second <= 31) ||
140
+ (first === 192 && second === 168)
141
+ )
142
+
143
+ const parseHextet = (value: string): number | null => {
144
+ if (!/^[0-9a-f]{1,4}$/i.test(value)) return null
145
+
146
+ const parsed = Number.parseInt(value, 16)
147
+ return Number.isInteger(parsed) && parsed >= 0 && parsed <= 0xffff ? parsed : null
148
+ }
149
+
150
+ const parseIPv4MappedIPv6 = (hostname: string): number[] | null => {
151
+ if (!hostname.startsWith("::ffff:")) return null
152
+
153
+ const mappedValue = hostname.slice("::ffff:".length)
154
+ const dottedIPv4 = parseIPv4(mappedValue)
155
+ if (dottedIPv4) return dottedIPv4
156
+
157
+ const hextets = mappedValue.split(":")
158
+ if (hextets.length !== 2) return null
159
+
160
+ const high = parseHextet(hextets[0])
161
+ const low = parseHextet(hextets[1])
162
+ if (high === null || low === null) return null
163
+
164
+ return [(high >> 8) & 0xff, high & 0xff, (low >> 8) & 0xff, low & 0xff]
165
+ }
166
+
167
+ const isLocalNetworkHostname = (hostname: string): boolean => {
168
+ const normalizedHostname = getNormalizedHostname(hostname)
169
+
170
+ if (normalizedHostname === "localhost") return true
171
+
172
+ const ipv4 = parseIPv4(normalizedHostname)
173
+ if (ipv4) {
174
+ return isLocalIPv4(ipv4)
175
+ }
176
+
177
+ if (!normalizedHostname.includes(":")) return false
178
+
179
+ const mappedIPv4 = parseIPv4MappedIPv6(normalizedHostname)
180
+ if (mappedIPv4) return isLocalIPv4(mappedIPv4)
181
+
182
+ const firstHextet = parseHextet(normalizedHostname.split(":")[0])
183
+
184
+ return (
185
+ normalizedHostname === "::" ||
186
+ normalizedHostname === "::1" ||
187
+ (firstHextet !== null && (
188
+ (firstHextet & 0xfe00) === 0xfc00 ||
189
+ (firstHextet & 0xffc0) === 0xfe80
190
+ ))
191
+ )
192
+ }
193
+
119
194
  /**
120
195
  * Creates a Zod schema for URL validation with comprehensive constraints
121
196
  *
@@ -356,7 +431,7 @@ export function url<IsRequired extends boolean = false>(required?: IsRequired, o
356
431
  }
357
432
 
358
433
  // Localhost validation
359
- 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])\./)
434
+ const isLocalhost = isLocalNetworkHostname(hostname)
360
435
  if (blockLocalhost && isLocalhost) {
361
436
  ctx.addIssue({ code: "custom", message: getMessage("noLocalhost") })
362
437
  return
@@ -1,8 +1,8 @@
1
1
  /**
2
- * @fileoverview Taiwan Landline Telephone Number validator for Zod Kit
2
+ * @fileoverview Taiwan Telephone Number validator for Zod Kit
3
3
  *
4
- * Provides validation for Taiwan landline telephone numbers according to the
5
- * official 2024 telecom numbering plan with comprehensive area code support.
4
+ * Provides validation for Taiwan telephone numbers according to the official
5
+ * 2024 telecom numbering plan with area code and service code support.
6
6
  *
7
7
  * @author Ong Hoe Yuan
8
8
  * @version 0.0.5
@@ -27,19 +27,21 @@ export type TwTelMessages = {
27
27
  }
28
28
 
29
29
  /**
30
- * Configuration options for Taiwan landline telephone validation
30
+ * Configuration options for Taiwan telephone validation
31
31
  *
32
32
  * @template IsRequired - Whether the field is required (affects return type)
33
33
  *
34
34
  * @interface TwTelOptions
35
35
  * @property {IsRequired} [required=true] - Whether the field is required
36
36
  * @property {string[]} [whitelist] - Array of specific telephone numbers that are always allowed
37
+ * @property {boolean} [allowITFS=false] - Whether to allow 008 international toll-free numbers
37
38
  * @property {Function} [transform] - Custom transformation function for telephone number
38
39
  * @property {string | null} [defaultValue] - Default value when input is empty
39
40
  * @property {Record<Locale, TwTelMessages>} [i18n] - Custom error messages for different locales
40
41
  */
41
42
  export type TwTelOptions<IsRequired extends boolean = true> = {
42
43
  whitelist?: string[]
44
+ allowITFS?: boolean
43
45
  transform?: (value: string) => string
44
46
  defaultValue?: IsRequired extends true ? string : string | null
45
47
  i18n?: Partial<Record<Locale, Partial<TwTelMessages>>>
@@ -54,16 +56,39 @@ export type TwTelOptions<IsRequired extends boolean = true> = {
54
56
  */
55
57
  export type TwTelSchema<IsRequired extends boolean> = IsRequired extends true ? ZodString : ZodNullable<ZodString>
56
58
 
59
+ export type ValidateTaiwanTelOptions = {
60
+ allowITFS?: boolean
61
+ }
62
+
63
+ const normalizeTaiwanTel = (value: string): string => value.replace(/[-\s]/g, "")
64
+
65
+ /**
66
+ * Validates Taiwan-dialed international toll-free number format.
67
+ *
68
+ * Supported formats:
69
+ * - 00800 + 8-digit Global Subscriber Number (UIFN)
70
+ * - 00801~00809 + 1-12 digit carrier-defined subscriber number (ITFS)
71
+ */
72
+ const validateTaiwanInternationalTollFree = (value: string): boolean => {
73
+ const cleanValue = normalizeTaiwanTel(value)
74
+
75
+ if (/^00800\d{8}$/.test(cleanValue)) {
76
+ return true
77
+ }
78
+
79
+ return /^0080[1-9]\d{1,12}$/.test(cleanValue)
80
+ }
81
+
57
82
  /**
58
- * Validates Taiwan landline telephone number format (Official 2024 rules)
83
+ * Validates Taiwan telephone number format (Official 2024 rules)
59
84
  *
60
85
  * @param {string} value - The telephone number to validate
86
+ * @param {ValidateTaiwanTelOptions} [options] - Optional validation settings
61
87
  * @returns {boolean} True if the telephone number is valid
62
88
  *
63
89
  * @description
64
- * Validates Taiwan landline telephone numbers according to the official 2024
65
- * telecom numbering plan. Supports all Taiwan area codes and their specific
66
- * number patterns.
90
+ * Validates Taiwan telephone numbers according to the official 2024 telecom
91
+ * numbering plan. Supports Taiwan area codes and selected service codes.
67
92
  *
68
93
  * Supported area codes and formats:
69
94
  * - 02: Taipei, New Taipei, Keelung - 8 digits
@@ -76,8 +101,10 @@ export type TwTelSchema<IsRequired extends boolean> = IsRequired extends true ?
76
101
  * - 07: Kaohsiung - 7-8 digits
77
102
  * - 08: Pingtung - 7 digits
78
103
  * - 070: VoIP / Internet Telephony (網路電話) - 8 digits (11 total)
79
- * - 0800: Toll-free - 6 digits
80
- * - 0809: Toll-free - 6 digits
104
+ * - 0800: Domestic recipient-paid/toll-free - 6 digits
105
+ * - 0809: Domestic recipient-paid/toll-free - 6 digits
106
+ * - 00800: Universal International Freephone Number (UIFN) when allowITFS is true
107
+ * - 00801~00809: International Toll-Free Service (ITFS) when allowITFS is true
81
108
  * - 082: Kinmen - 6 digits
82
109
  * - 0836: Matsu - 5-6 digits
83
110
  * - 089: Taitung - 6 digits
@@ -89,9 +116,12 @@ export type TwTelSchema<IsRequired extends boolean> = IsRequired extends true ?
89
116
  * validateTaiwanTel("037234567") // true (Miaoli area)
90
117
  * validateTaiwanTel("082234567") // true (Kinmen area)
91
118
  * validateTaiwanTel("02-2345-6789") // true (with separators)
119
+ * validateTaiwanTel("00801-852-747", { allowITFS: true }) // true (ITFS)
92
120
  * ```
93
121
  */
94
- const validateTaiwanTel = (value: string): boolean => {
122
+ const validateTaiwanTel = (value: string, options: ValidateTaiwanTelOptions = {}): boolean => {
123
+ const { allowITFS = false } = options
124
+
95
125
  // Official Taiwan landline formats according to telecom numbering plan:
96
126
  // 02: Taipei, New Taipei, Keelung - 8 digits
97
127
  // 03: Taoyuan, Hsinchu, Yilan, Hualien - 7-8 digits
@@ -103,13 +133,17 @@ const validateTaiwanTel = (value: string): boolean => {
103
133
  // 07: Kaohsiung - 7-8 digits
104
134
  // 08: Pingtung - 7 digits
105
135
  // 070: VoIP / Internet Telephony (網路電話) - 8 digits (11 total)
106
- // 0800/0809: Toll-free - 6 digits
136
+ // 0800/0809: Domestic recipient-paid/toll-free - 6 digits
107
137
  // 082: Kinmen - 6 digits
108
138
  // 0836: Matsu - 5-6 digits
109
139
  // 089: Taitung - 6 digits
110
140
 
111
141
  // Remove common separators for validation
112
- const cleanValue = value.replace(/[-\s]/g, "")
142
+ const cleanValue = normalizeTaiwanTel(value)
143
+
144
+ if (allowITFS && validateTaiwanInternationalTollFree(cleanValue)) {
145
+ return true
146
+ }
113
147
 
114
148
  // Basic format: starts with 0, then area code, then number
115
149
  if (!/^0\d{7,10}$/.test(cleanValue)) {
@@ -119,7 +153,7 @@ const validateTaiwanTel = (value: string): boolean => {
119
153
  // Check 4-digit area codes first
120
154
  const areaCode4 = cleanValue.substring(0, 4)
121
155
  if (areaCode4 === "0800" || areaCode4 === "0809") {
122
- // Toll-free: 0800/0809 + 6 digits, total 10 digits
156
+ // Domestic recipient-paid/toll-free: 0800/0809 + 6 digits, total 10 digits
123
157
  return cleanValue.length === 10 && /^080[09]\d{6}$/.test(cleanValue)
124
158
  }
125
159
  if (areaCode4 === "0836") {
@@ -206,7 +240,7 @@ const validateTaiwanTel = (value: string): boolean => {
206
240
  }
207
241
 
208
242
  /**
209
- * Creates a Zod schema for Taiwan landline telephone number validation
243
+ * Creates a Zod schema for Taiwan telephone number validation
210
244
  *
211
245
  * @template IsRequired - Whether the field is required (affects return type)
212
246
  * @param {IsRequired} [required=false] - Whether the field is required
@@ -214,11 +248,13 @@ const validateTaiwanTel = (value: string): boolean => {
214
248
  * @returns {TwTelSchema<IsRequired>} Zod schema for telephone number validation
215
249
  *
216
250
  * @description
217
- * Creates a comprehensive Taiwan landline telephone number validator with support for
218
- * all Taiwan area codes according to the official 2024 telecom numbering plan.
251
+ * Creates a comprehensive Taiwan telephone number validator with support for
252
+ * Taiwan area codes and selected service codes according to the official 2024
253
+ * telecom numbering plan.
219
254
  *
220
255
  * Features:
221
256
  * - Complete Taiwan area code support (02, 03, 037, 04, 049, 05, 06, 07, 08, 082, 0826, 0836, 089)
257
+ * - Optional 008 international toll-free support (UIFN/ITFS)
222
258
  * - Automatic separator handling (hyphens and spaces)
223
259
  * - Area-specific number length and pattern validation
224
260
  * - Whitelist functionality for specific allowed numbers
@@ -262,6 +298,13 @@ const validateTaiwanTel = (value: string): boolean => {
262
298
  * transformSchema.parse("02-2345-6789") // ✓ Valid (separators removed)
263
299
  * transformSchema.parse("02 2345 6789") // ✓ Valid (spaces removed)
264
300
  *
301
+ * // With Taiwan-dialed international toll-free support
302
+ * const itfsSchema = twTel(false, {
303
+ * allowITFS: true
304
+ * })
305
+ * itfsSchema.parse("00800-2468-1668") // ✓ Valid (UIFN)
306
+ * itfsSchema.parse("00801-852-747") // ✓ Valid (ITFS)
307
+ *
265
308
  * // With custom error messages
266
309
  * const customSchema = twTel(false, {
267
310
  * i18n: {
@@ -276,7 +319,7 @@ const validateTaiwanTel = (value: string): boolean => {
276
319
  * @see {@link validateTaiwanTel} for validation logic details
277
320
  */
278
321
  export function twTel<IsRequired extends boolean = false>(required?: IsRequired, options?: Omit<TwTelOptions<IsRequired>, 'required'>): TwTelSchema<IsRequired> {
279
- const { whitelist, transform, defaultValue, i18n } = options ?? {}
322
+ const { whitelist, allowITFS = false, transform, defaultValue, i18n } = options ?? {}
280
323
 
281
324
  const isRequired = required ?? false as IsRequired
282
325
 
@@ -345,7 +388,7 @@ export function twTel<IsRequired extends boolean = false>(required?: IsRequired,
345
388
  }
346
389
 
347
390
  // Taiwan telephone format validation
348
- if (!validateTaiwanTel(val)) {
391
+ if (!validateTaiwanTel(val, { allowITFS })) {
349
392
  ctx.addIssue({ code: "custom", message: getMessage("invalid") })
350
393
  return
351
394
  }
@@ -369,4 +412,4 @@ export function twTel<IsRequired extends boolean = false>(required?: IsRequired,
369
412
  * const isValid = validateTaiwanTel("0223456789") // boolean
370
413
  * ```
371
414
  */
372
- export { validateTaiwanTel }
415
+ export { validateTaiwanTel, validateTaiwanInternationalTollFree }
@@ -49,6 +49,26 @@ describe("file(true) features", () => {
49
49
  expect(schema.parse(undefined)).toBe(null)
50
50
  expect(schema.parse("")).toBe(null)
51
51
  })
52
+
53
+ it("should not throw while creating a schema when File is unavailable", () => {
54
+ const originalFile = globalThis.File
55
+
56
+ try {
57
+ Object.defineProperty(globalThis, "File", {
58
+ value: undefined,
59
+ configurable: true,
60
+ })
61
+
62
+ const schema = file(false)
63
+ expect(schema.parse(null)).toBe(null)
64
+ expect(() => schema.parse("not a file")).toThrow("Invalid file")
65
+ } finally {
66
+ Object.defineProperty(globalThis, "File", {
67
+ value: originalFile,
68
+ configurable: true,
69
+ })
70
+ }
71
+ })
52
72
  })
53
73
 
54
74
  describe("file size validation", () => {
@@ -267,6 +267,15 @@ describe("url", () => {
267
267
  expect(() => schema.parse("http://192.168.1.1")).toThrow()
268
268
  })
269
269
 
270
+ it("should block all loopback and unspecified hosts when blockLocalhost is true", () => {
271
+ const schema = url(true, { blockLocalhost: true })
272
+
273
+ expect(() => schema.parse("http://127.0.0.2")).toThrow()
274
+ expect(() => schema.parse("http://0.0.0.0")).toThrow()
275
+ expect(() => schema.parse("http://[::1]")).toThrow()
276
+ expect(() => schema.parse("http://[::]")).toThrow()
277
+ })
278
+
270
279
  it("should block localhost when allowLocalhost is false", () => {
271
280
  const schema = url(true, { allowLocalhost: false })
272
281
  expect(() => schema.parse("http://localhost:3000")).toThrow()
@@ -456,7 +465,16 @@ describe("url", () => {
456
465
  expect(() => schema.parse("http://10.0.0.1")).toThrow()
457
466
  expect(() => schema.parse("http://172.16.0.1")).toThrow()
458
467
  expect(() => schema.parse("http://192.168.0.1")).toThrow()
468
+ expect(() => schema.parse("http://169.254.0.1")).toThrow()
469
+ expect(() => schema.parse("http://[fc00::1]")).toThrow()
470
+ expect(() => schema.parse("http://[fe80::1]")).toThrow()
471
+ expect(() => schema.parse("http://[fe81::1]")).toThrow()
472
+ expect(() => schema.parse("http://[febf::1]")).toThrow()
473
+ expect(() => schema.parse("http://[::ffff:127.0.0.1]")).toThrow()
474
+ expect(() => schema.parse("http://[::ffff:10.0.0.1]")).toThrow()
459
475
  expect(schema.parse("https://8.8.8.8")).toBe("https://8.8.8.8") // Public IP
476
+ expect(schema.parse("http://[::ffff:8.8.8.8]")).toBe("http://[::ffff:8.8.8.8]") // IPv4-mapped public IP
477
+ expect(schema.parse("https://fc.example.com")).toBe("https://fc.example.com")
460
478
  })
461
479
 
462
480
  it("should handle edge cases with ports", () => {
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect, beforeEach } from "vitest"
2
- import { twTel, setLocale, validateTaiwanTel } from "../../src"
2
+ import { twTel, setLocale, validateTaiwanTel, validateTaiwanInternationalTollFree } from "../../src"
3
3
 
4
4
  describe("Taiwan twTel(true) validator", () => {
5
5
  beforeEach(() => setLocale("en-US"))
@@ -45,7 +45,7 @@ describe("Taiwan twTel(true) validator", () => {
45
45
  expect(schema.parse("07012345678")).toBe("07012345678") // 070-1234-5678 (11 digits)
46
46
  expect(schema.parse("070-1234-5678")).toBe("070-1234-5678") // with separators
47
47
 
48
- // Toll-free numbers
48
+ // Domestic recipient-paid/toll-free numbers
49
49
  expect(schema.parse("0800123456")).toBe("0800123456") // 0800-123-456 (10 digits)
50
50
  expect(schema.parse("0809123456")).toBe("0809123456") // 0809-123-456 (10 digits)
51
51
  expect(schema.parse("0800-012-345")).toBe("0800-012-345") // with separators
@@ -70,6 +70,40 @@ describe("Taiwan twTel(true) validator", () => {
70
70
  expect(schema.parse("07 234-5678")).toBe("07 234-5678")
71
71
  })
72
72
 
73
+ it("should validate Taiwan international toll-free numbers when ITFS is allowed", () => {
74
+ const schema = twTel(true, { allowITFS: true })
75
+
76
+ expect(schema.parse("00800-2468-1668")).toBe("00800-2468-1668")
77
+ expect(schema.parse("00801128000")).toBe("00801128000")
78
+ expect(schema.parse("00801-852-747")).toBe("00801-852-747")
79
+ expect(schema.parse("00809 123456789012")).toBe("00809 123456789012")
80
+
81
+ expect(validateTaiwanTel("00800-2468-1668", { allowITFS: true })).toBe(true)
82
+ expect(validateTaiwanTel("00801-852-747", { allowITFS: true })).toBe(true)
83
+ })
84
+
85
+ it("should reject Taiwan international toll-free numbers unless ITFS is allowed", () => {
86
+ const schema = twTel(true)
87
+
88
+ expect(() => schema.parse("00800-2468-1668")).toThrow("Invalid Taiwan telephone format")
89
+ expect(() => schema.parse("00801-852-747")).toThrow("Invalid Taiwan telephone format")
90
+ expect(validateTaiwanTel("00801-852-747")).toBe(false)
91
+ })
92
+
93
+ it("should reject invalid Taiwan international toll-free number lengths", () => {
94
+ const schema = twTel(true, { allowITFS: true })
95
+
96
+ expect(() => schema.parse("00800-1234-567")).toThrow("Invalid Taiwan telephone format")
97
+ expect(() => schema.parse("00800-1234-56789")).toThrow("Invalid Taiwan telephone format")
98
+ expect(() => schema.parse("00801")).toThrow("Invalid Taiwan telephone format")
99
+ expect(() => schema.parse("00801-1234567890123")).toThrow("Invalid Taiwan telephone format")
100
+
101
+ expect(validateTaiwanInternationalTollFree("00800-1234-5678")).toBe(true)
102
+ expect(validateTaiwanInternationalTollFree("00800-1234-567")).toBe(false)
103
+ expect(validateTaiwanInternationalTollFree("00801-123456789012")).toBe(true)
104
+ expect(validateTaiwanInternationalTollFree("00801-1234567890123")).toBe(false)
105
+ })
106
+
73
107
  it("should reject invalid Taiwan telephone numbers", () => {
74
108
  const schema = twTel(true)
75
109
 
@@ -265,7 +299,7 @@ describe("Taiwan twTel(true) validator", () => {
265
299
  expect(validateTaiwanTel("070-1234-5678")).toBe(true) // 070 with separators
266
300
  expect(validateTaiwanTel("07001234567")).toBe(true) // 070 starting with 0 (valid VoIP)
267
301
 
268
- // Toll-free numbers
302
+ // Domestic recipient-paid/toll-free numbers
269
303
  expect(validateTaiwanTel("0800123456")).toBe(true) // 0800
270
304
  expect(validateTaiwanTel("0809123456")).toBe(true) // 0809
271
305
  expect(validateTaiwanTel("0800-123-456")).toBe(true) // with separators
@@ -393,8 +427,8 @@ describe("Taiwan twTel(true) validator", () => {
393
427
  { code: "082", numbers: ["082234567"] }, // Kinmen (9 digits, first digit 2) - from utility test ✓
394
428
  { code: "089", numbers: ["089234567"] }, // Taitung (9 digits, first digit 2) - from utility test ✓
395
429
  { code: "070", numbers: ["07012345678"] }, // VoIP (11 digits)
396
- { code: "0800", numbers: ["0800123456"] }, // Toll-free (10 digits)
397
- { code: "0809", numbers: ["0809123456"] }, // Toll-free (10 digits)
430
+ { code: "0800", numbers: ["0800123456"] }, // Domestic recipient-paid/toll-free (10 digits)
431
+ { code: "0809", numbers: ["0809123456"] }, // Domestic recipient-paid/toll-free (10 digits)
398
432
  { code: "0826", numbers: ["082661234"] }, // Wuqiu (9 digits, first digit 6) - from utility test ✓
399
433
  { code: "0836", numbers: ["083621234"] }, // Matsu (9 digits, first digit 2) - from utility test ✓
400
434
  ]
package/tsconfig.json CHANGED
@@ -2,7 +2,8 @@
2
2
  "compilerOptions": {
3
3
  "target": "ESNext",
4
4
  "module": "ESNext",
5
- "moduleResolution": "Node",
5
+ "moduleResolution": "Bundler",
6
+ "ignoreDeprecations": "6.0",
6
7
  "esModuleInterop": true,
7
8
  "declaration": true,
8
9
  "outDir": "./dist",