@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.
- package/README.md +50 -51
- package/dist/{chunk-U2PB6XEO.js → chunk-2XLAXQGM.js} +43 -1
- package/dist/{chunk-KIUO2HIR.cjs → chunk-3GAFULXI.cjs} +15 -3
- package/dist/{chunk-RHKBT3M2.js → chunk-B6XAA3UV.js} +7 -7
- package/dist/{chunk-4AQB4RSU.cjs → chunk-FVWOLTP6.cjs} +10 -10
- package/dist/{chunk-TDEXEIHH.cjs → chunk-IFSUPBIF.cjs} +43 -1
- package/dist/{chunk-ADKXVZHH.js → chunk-IZEVVOXI.js} +17 -4
- package/dist/{chunk-OGU7AIZF.js → chunk-LUFTQDGA.js} +15 -3
- package/dist/{chunk-53EEWALQ.cjs → chunk-NLEKGYTV.cjs} +14 -14
- package/dist/{chunk-RVGCMQ4J.js → chunk-S5N6EFNB.js} +5 -5
- package/dist/{chunk-7EUHTSMF.cjs → chunk-SFJZLW6P.cjs} +18 -5
- package/dist/common/date.cjs +2 -2
- package/dist/common/date.js +1 -1
- package/dist/common/datetime.cjs +2 -2
- package/dist/common/datetime.js +1 -1
- package/dist/common/file.cjs +2 -2
- package/dist/common/file.js +1 -1
- package/dist/common/url.cjs +2 -2
- package/dist/common/url.js +1 -1
- package/dist/index.cjs +8 -6
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -5
- package/dist/taiwan/tel.cjs +4 -2
- package/dist/taiwan/tel.d.cts +40 -15
- package/dist/taiwan/tel.d.ts +40 -15
- package/dist/taiwan/tel.js +3 -1
- package/package.json +10 -10
- package/src/validators/common/date.ts +5 -5
- package/src/validators/common/datetime.ts +7 -7
- package/src/validators/common/file.ts +19 -3
- package/src/validators/common/url.ts +76 -1
- package/src/validators/taiwan/tel.ts +63 -20
- package/tests/common/file.test.ts +20 -0
- package/tests/common/url.test.ts +18 -0
- package/tests/taiwan/tel.test.ts +39 -5
- 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,
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
#### `
|
|
341
|
+
#### `twNationalId(required?, options?)`
|
|
344
342
|
|
|
345
343
|
Validates Taiwan National ID (身份證字號).
|
|
346
344
|
|
|
347
345
|
```typescript
|
|
348
|
-
import {
|
|
346
|
+
import { twNationalId } from '@hy_ong/zod-kit'
|
|
349
347
|
|
|
350
|
-
const idSchema =
|
|
351
|
-
|
|
352
|
-
|
|
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 =
|
|
355
|
+
const optionalId = twNationalId(false)
|
|
358
356
|
```
|
|
359
357
|
|
|
360
|
-
#### `
|
|
358
|
+
#### `twBusinessId(required?, options?)`
|
|
361
359
|
|
|
362
360
|
Validates Taiwan Business ID (統一編號).
|
|
363
361
|
|
|
364
362
|
```typescript
|
|
365
|
-
import {
|
|
363
|
+
import { twBusinessId } from '@hy_ong/zod-kit'
|
|
366
364
|
|
|
367
|
-
const bizSchema =
|
|
365
|
+
const bizSchema = twBusinessId(true)
|
|
368
366
|
bizSchema.parse('12345675') // ✅ Valid business ID with checksum
|
|
369
367
|
|
|
370
|
-
const optionalBizId =
|
|
368
|
+
const optionalBizId = twBusinessId(false)
|
|
371
369
|
```
|
|
372
370
|
|
|
373
|
-
#### `
|
|
371
|
+
#### `twMobile(required?, options?)`
|
|
374
372
|
|
|
375
373
|
Validates Taiwan mobile phone numbers.
|
|
376
374
|
|
|
377
375
|
```typescript
|
|
378
|
-
import {
|
|
376
|
+
import { twMobile } from '@hy_ong/zod-kit'
|
|
379
377
|
|
|
380
|
-
const phoneSchema =
|
|
381
|
-
|
|
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 =
|
|
383
|
+
const optionalMobile = twMobile(false)
|
|
387
384
|
```
|
|
388
385
|
|
|
389
|
-
#### `
|
|
386
|
+
#### `twTel(required?, options?)`
|
|
390
387
|
|
|
391
|
-
Validates Taiwan landline
|
|
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 {
|
|
391
|
+
import { twTel } from '@hy_ong/zod-kit'
|
|
395
392
|
|
|
396
|
-
const landlineSchema =
|
|
397
|
-
|
|
398
|
-
|
|
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 =
|
|
400
|
+
const optionalTel = twTel(false)
|
|
402
401
|
```
|
|
403
402
|
|
|
404
|
-
#### `
|
|
403
|
+
#### `twFax(required?, options?)`
|
|
405
404
|
|
|
406
405
|
Validates Taiwan fax numbers (same format as landline).
|
|
407
406
|
|
|
408
407
|
```typescript
|
|
409
|
-
import {
|
|
408
|
+
import { twFax } from '@hy_ong/zod-kit'
|
|
410
409
|
|
|
411
|
-
const faxSchema =
|
|
410
|
+
const faxSchema = twFax(true)
|
|
412
411
|
faxSchema.parse('02-2345-6789') // ✅ Valid fax number
|
|
413
412
|
|
|
414
|
-
const optionalFax =
|
|
413
|
+
const optionalFax = twFax(false)
|
|
415
414
|
```
|
|
416
415
|
|
|
417
|
-
#### `
|
|
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 {
|
|
421
|
+
import { twPostalCode } from '@hy_ong/zod-kit'
|
|
423
422
|
|
|
424
423
|
// Accept 3-digit or 6-digit formats (recommended)
|
|
425
|
-
const modernSchema =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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, {
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
_dayjs2.default.extend(
|
|
15
|
-
_dayjs2.default.extend(
|
|
16
|
-
_dayjs2.default.extend(
|
|
17
|
-
_dayjs2.default.extend(
|
|
18
|
-
_dayjs2.default.extend(
|
|
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 =
|
|
178
|
+
const isLocalhost = isLocalNetworkHostname(hostname);
|
|
137
179
|
if (blockLocalhost && isLocalhost) {
|
|
138
180
|
ctx.addIssue({ code: "custom", message: getMessage("noLocalhost") });
|
|
139
181
|
return;
|
|
@@ -5,8 +5,20 @@ import {
|
|
|
5
5
|
|
|
6
6
|
// src/validators/taiwan/tel.ts
|
|
7
7
|
import { z } from "zod";
|
|
8
|
-
var
|
|
9
|
-
|
|
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
|
}
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
10
|
-
var
|
|
11
|
-
var
|
|
12
|
-
var
|
|
13
|
-
var
|
|
14
|
-
var
|
|
15
|
-
var
|
|
16
|
-
_dayjs2.default.extend(
|
|
17
|
-
_dayjs2.default.extend(
|
|
18
|
-
_dayjs2.default.extend(
|
|
19
|
-
_dayjs2.default.extend(
|
|
20
|
-
_dayjs2.default.extend(
|
|
21
|
-
_dayjs2.default.extend(
|
|
22
|
-
_dayjs2.default.extend(
|
|
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,8 +5,20 @@ var _chunkQ7TUNJD4cjs = require('./chunk-Q7TUNJD4.cjs');
|
|
|
5
5
|
|
|
6
6
|
// src/validators/taiwan/tel.ts
|
|
7
7
|
var _zod = require('zod');
|
|
8
|
-
var
|
|
9
|
-
|
|
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
|
}
|
|
@@ -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
|
-
|
|
145
|
+
|
|
146
|
+
exports.validateTaiwanInternationalTollFree = validateTaiwanInternationalTollFree; exports.validateTaiwanTel = validateTaiwanTel; exports.twTel = twTel;
|
package/dist/common/date.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _chunkFVWOLTP6cjs = require('../chunk-FVWOLTP6.cjs');
|
|
4
4
|
require('../chunk-Q7TUNJD4.cjs');
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
exports.date =
|
|
7
|
+
exports.date = _chunkFVWOLTP6cjs.date;
|