typebox 1.1.10 → 1.1.11

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.
@@ -1,2 +1,5 @@
1
- /** Returns true if the value is an Email */
1
+ /**
2
+ * Returns true if the value is an Email
3
+ * @specification Json Schema 2020-12
4
+ */
2
5
  export declare function IsEmail(value: string): boolean;
@@ -1,5 +1,29 @@
1
- const Email = /^(?!.*\.\.)[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i;
2
- /** Returns true if the value is an Email */
1
+ import { IsIPv4Internal } from './ipv4.mjs';
2
+ /**
3
+ * Returns true if the value is an Email
4
+ * @specification Json Schema 2020-12
5
+ */
3
6
  export function IsEmail(value) {
4
- return Email.test(value);
7
+ const dot = value.indexOf('.');
8
+ const at = value.indexOf('@');
9
+ const quoted = value[0] === '"' && value[at - 1] === '"';
10
+ const ipLiteral = value[at + 1] === '[' && value[value.length - 1] === ']';
11
+ const ipv6 = ipLiteral && value.indexOf(':', at) !== -1;
12
+ const ipv4 = ipLiteral && !ipv6 && IsIPv4Internal(value, at + 2, value.length - 1);
13
+ return (at > 0 && at < value.length - 1) &&
14
+ !(
15
+ // .test@example.com
16
+ (!quoted && dot === 0) ||
17
+ // te..st@example.com
18
+ (!quoted && dot !== -1 && value.indexOf('.', dot + 1) === dot + 1) ||
19
+ // test.@example.com
20
+ (dot !== -1 && value.indexOf('@', dot) === dot + 1) ||
21
+ // joe bloggs@example.com
22
+ (!quoted && value.indexOf(' ') !== -1) ||
23
+ // user1@oceania.org, user2@oceania.org
24
+ (value.indexOf(',') !== -1) ||
25
+ // joe.bloggs@[127.0.0.300]
26
+ (ipLiteral && !ipv4 && !ipv6) ||
27
+ // joe.bloggs@invalid=domain.com
28
+ (value.indexOf('=', at) !== -1));
5
29
  }
@@ -1,22 +1,22 @@
1
1
  export * from './_registry.mjs';
2
- export * from './date-time.mjs';
3
- export * from './date.mjs';
4
- export * from './duration.mjs';
5
- export * from './email.mjs';
6
- export * from './hostname.mjs';
7
- export * from './idn-email.mjs';
8
- export * from './idn-hostname.mjs';
9
- export * from './ipv4.mjs';
10
- export * from './ipv6.mjs';
11
- export * from './iri-reference.mjs';
12
- export * from './iri.mjs';
13
- export * from './json-pointer-uri-fragment.mjs';
14
- export * from './json-pointer.mjs';
15
- export * from './regex.mjs';
16
- export * from './relative-json-pointer.mjs';
17
- export * from './time.mjs';
18
- export * from './uri-reference.mjs';
19
- export * from './uri-template.mjs';
20
- export * from './uri.mjs';
21
- export * from './url.mjs';
22
- export * from './uuid.mjs';
2
+ export { IsDateTime } from './date-time.mjs';
3
+ export { IsDate } from './date.mjs';
4
+ export { IsDuration } from './duration.mjs';
5
+ export { IsEmail } from './email.mjs';
6
+ export { IsHostname } from './hostname.mjs';
7
+ export { IsIdnEmail } from './idn-email.mjs';
8
+ export { IsIdnHostname } from './idn-hostname.mjs';
9
+ export { IsIPv4 } from './ipv4.mjs';
10
+ export { IsIPv6 } from './ipv6.mjs';
11
+ export { IsIriReference } from './iri-reference.mjs';
12
+ export { IsIri } from './iri.mjs';
13
+ export { IsJsonPointerUriFragment } from './json-pointer-uri-fragment.mjs';
14
+ export { IsJsonPointer } from './json-pointer.mjs';
15
+ export { IsRegex } from './regex.mjs';
16
+ export { IsRelativeJsonPointer } from './relative-json-pointer.mjs';
17
+ export { IsTime } from './time.mjs';
18
+ export { IsUriReference } from './uri-reference.mjs';
19
+ export { IsUriTemplate } from './uri-template.mjs';
20
+ export { IsUri } from './uri.mjs';
21
+ export { IsUrl } from './url.mjs';
22
+ export { IsUuid } from './uuid.mjs';
@@ -1,22 +1,22 @@
1
1
  export * from './_registry.mjs';
2
- export * from './date-time.mjs';
3
- export * from './date.mjs';
4
- export * from './duration.mjs';
5
- export * from './email.mjs';
6
- export * from './hostname.mjs';
7
- export * from './idn-email.mjs';
8
- export * from './idn-hostname.mjs';
9
- export * from './ipv4.mjs';
10
- export * from './ipv6.mjs';
11
- export * from './iri-reference.mjs';
12
- export * from './iri.mjs';
13
- export * from './json-pointer-uri-fragment.mjs';
14
- export * from './json-pointer.mjs';
15
- export * from './regex.mjs';
16
- export * from './relative-json-pointer.mjs';
17
- export * from './time.mjs';
18
- export * from './uri-reference.mjs';
19
- export * from './uri-template.mjs';
20
- export * from './uri.mjs';
21
- export * from './url.mjs';
22
- export * from './uuid.mjs';
2
+ export { IsDateTime } from './date-time.mjs';
3
+ export { IsDate } from './date.mjs';
4
+ export { IsDuration } from './duration.mjs';
5
+ export { IsEmail } from './email.mjs';
6
+ export { IsHostname } from './hostname.mjs';
7
+ export { IsIdnEmail } from './idn-email.mjs';
8
+ export { IsIdnHostname } from './idn-hostname.mjs';
9
+ export { IsIPv4 } from './ipv4.mjs';
10
+ export { IsIPv6 } from './ipv6.mjs';
11
+ export { IsIriReference } from './iri-reference.mjs';
12
+ export { IsIri } from './iri.mjs';
13
+ export { IsJsonPointerUriFragment } from './json-pointer-uri-fragment.mjs';
14
+ export { IsJsonPointer } from './json-pointer.mjs';
15
+ export { IsRegex } from './regex.mjs';
16
+ export { IsRelativeJsonPointer } from './relative-json-pointer.mjs';
17
+ export { IsTime } from './time.mjs';
18
+ export { IsUriReference } from './uri-reference.mjs';
19
+ export { IsUriTemplate } from './uri-template.mjs';
20
+ export { IsUri } from './uri.mjs';
21
+ export { IsUrl } from './url.mjs';
22
+ export { IsUuid } from './uuid.mjs';
@@ -1,2 +1,5 @@
1
- /** Returns true if the value is a Hostname */
1
+ /**
2
+ * Returns true if the value matches RFC 1123 hostname syntax.
3
+ * @specification https://tools.ietf.org/html/rfc1123
4
+ */
2
5
  export declare function IsHostname(value: string): boolean;
@@ -1,5 +1,34 @@
1
- const Hostname = /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i;
2
- /** Returns true if the value is a Hostname */
1
+ /**
2
+ * Returns true if the value matches RFC 1123 hostname syntax.
3
+ * @specification https://tools.ietf.org/html/rfc1123
4
+ */
3
5
  export function IsHostname(value) {
4
- return Hostname.test(value);
6
+ if (value.length > 253 || value.length === 0)
7
+ return false;
8
+ let start = 0;
9
+ let prev = 0;
10
+ for (let i = 0; i < value.length; i++) {
11
+ const ch = value.charCodeAt(i);
12
+ if (ch === 46) { // '.'
13
+ // trailing dot is valid e.g. "example.com." but not "."
14
+ if (i === value.length - 1 && start < i)
15
+ break;
16
+ const len = i - start;
17
+ if (len === 0 || len > 63 || value.charCodeAt(start) === 45 || prev === 45)
18
+ return false;
19
+ start = i + 1;
20
+ }
21
+ else if (!((ch >= 97 && ch <= 122) || // a-z
22
+ (ch >= 65 && ch <= 90) || // A-Z
23
+ (ch >= 48 && ch <= 57) || // 0-9
24
+ ch === 45 // '-'
25
+ )) {
26
+ return false;
27
+ }
28
+ prev = ch;
29
+ }
30
+ const length = value.length - start;
31
+ const first = value.charCodeAt(start);
32
+ const last = value.charCodeAt(value.length - 1);
33
+ return length > 0 && length <= 63 && first !== 45 && last !== 45;
5
34
  }
@@ -1,2 +1,5 @@
1
- /** Returns true if the value is a Idn Email */
1
+ /**
2
+ * Returns true if the value is an IdnEmail
3
+ * @specification Json Schema 2020-12
4
+ */
2
5
  export declare function IsIdnEmail(value: string): boolean;
@@ -1,5 +1,8 @@
1
- const IdnEmail = /^(?!.*\.\.)[\p{L}\p{N}!#$%&'*+/=?^_`{|}~-]+(?:\.[\p{L}\p{N}!#$%&'*+/=?^_`{|}~-]+)*@[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,61}[\p{L}\p{N}])?(?:\.[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,61}[\p{L}\p{N}])?)*$/iu;
2
- /** Returns true if the value is a Idn Email */
1
+ import { IsEmail } from './email.mjs';
2
+ /**
3
+ * Returns true if the value is an IdnEmail
4
+ * @specification Json Schema 2020-12
5
+ */
3
6
  export function IsIdnEmail(value) {
4
- return IdnEmail.test(value);
7
+ return IsEmail(value);
5
8
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Returns true if the value is a Hostname
3
- * @specification
2
+ * Returns true if the value is an IDN Hostname
3
+ * @specification Json Schema 2020-12
4
4
  */
5
5
  export declare function IsIdnHostname(value: string): boolean;
@@ -1,124 +1,115 @@
1
- function IsValidAdjacentForKatakanaMiddleDot(char) {
2
- const codePoint = char.codePointAt(0);
3
- // deno-coverage-ignore - internal condition never reached
4
- if (codePoint === undefined)
5
- return false;
6
- return ((codePoint >= 0x3040 && codePoint <= 0x309F) || // Hiragana
7
- (codePoint >= 0x30A0 && codePoint <= 0x30FF && codePoint !== 0x30FB) || // Katakana (excluding U+30FB)
8
- (codePoint >= 0x4E00 && codePoint <= 0x9FFF) // Han (CJK Unified Ideographs)
1
+ function IsValidAdjacentForKatakanaMiddleDot(cp) {
2
+ return ((cp >= 0x3040 && cp <= 0x309F) || // Hiragana
3
+ (cp >= 0x30A0 && cp <= 0x30FF && cp !== 0x30FB) || // Katakana (excluding U+30FB)
4
+ (cp >= 0x4E00 && cp <= 0x9FFF) // Han (CJK Unified Ideographs)
9
5
  );
10
6
  }
11
7
  /**
12
- * Returns true if the value is a Hostname
13
- * @specification
8
+ * Returns true if the value is an IDN Hostname
9
+ * @specification Json Schema 2020-12
14
10
  */
15
11
  export function IsIdnHostname(value) {
16
- if (value.length === 0)
17
- return false;
18
- if (value.includes(' '))
12
+ if (value.length === 0 || value.includes(' '))
19
13
  return false;
20
- // Allowed label separators per RFC3490: U+002E, U+3002, U+FF0E, U+FF61.
21
- const separators = /[\u002E\u3002\uFF0E\uFF61]/g;
22
- // Normalize (NFC) and replace allowed separators with a dot.
23
- const normalized = value.normalize('NFC').replace(separators, '.');
14
+ // Normalize (NFC) and replace allowed separators with a dot
15
+ // Allowed label separators per RFC3490: U+002E, U+3002, U+FF0E, U+FF61
16
+ const normalized = value.normalize('NFC').replace(/[\u002E\u3002\uFF0E\uFF61]/g, '.');
24
17
  if (normalized.length > 253)
25
18
  return false;
26
- // Split into labels; disallow empty labels.
27
19
  const labels = normalized.split('.');
28
- if (labels.some((label) => label.length === 0))
29
- return false;
30
20
  for (const label of labels) {
31
- // Each label must be 63 characters.
32
- if (label.length > 63)
21
+ if (label.length === 0 || label.length > 63)
33
22
  return false;
34
- // Labels must not begin or end with a hyphen.
35
- if (label.startsWith('-') || label.endsWith('-'))
23
+ // Labels must not begin or end with a hyphen
24
+ if (label.charCodeAt(0) === 45 || label.charCodeAt(label.length - 1) === 45)
36
25
  return false;
37
- // A-label (punycode) checks.
38
- if (/^xn--/i.test(label)) {
26
+ // A-label (punycode) checks
27
+ if ((label.charCodeAt(0) === 120 || label.charCodeAt(0) === 88) && // 'x' or 'X'
28
+ (label.charCodeAt(1) === 110 || label.charCodeAt(1) === 78) && // 'n' or 'N'
29
+ label.charCodeAt(2) === 45 && // '-'
30
+ label.charCodeAt(3) === 45 // '-'
31
+ ) {
39
32
  const punycodePart = label.slice(4);
40
- if (punycodePart.length < 2)
41
- return false;
42
- if (punycodePart.includes('---'))
33
+ if (punycodePart.length < 2 || punycodePart.includes('---'))
43
34
  return false;
44
35
  continue;
45
36
  }
46
- // U-label: Reject if any disallowed code points occur.
47
- // Disallowed: U+302E, U+302F, U+3031, U+3032, U+3033, U+3034, U+3035, U+303B, U+0640, U+07FA.
48
- if (/[\u302E\u302F\u3031\u3032\u3033\u3034\u3035\u303B\u0640\u07FA]/.test(label)) {
49
- return false;
50
- }
51
- // Disallow labels starting with certain combining marks.
52
- const firstChar = label.charAt(0);
53
- if (/[\u0903\u0300\u0488]/.test(firstChar))
54
- return false;
55
- // Check each character within the label.
37
+ // U-label checks
38
+ let hasArabicIndic = false;
39
+ let hasExtendedArabicIndic = false;
56
40
  for (let i = 0; i < label.length; i++) {
57
- const char = label.charAt(i);
58
- // --- MIDDLE DOT (U+00B7) ---
59
- // Must be flanked on both sides by "l" or "L".
60
- if (char === '\u00B7') {
41
+ // deno-coverage-ignore
42
+ const cp = label.codePointAt(i) ?? 0;
43
+ // Disallowed code points
44
+ if (cp === 0x302E || cp === 0x302F ||
45
+ cp === 0x3031 || cp === 0x3032 || cp === 0x3033 || cp === 0x3034 || cp === 0x3035 ||
46
+ cp === 0x303B || cp === 0x0640 || cp === 0x07FA)
47
+ return false;
48
+ // Disallow labels starting with certain combining marks
49
+ if (i === 0 && (cp === 0x0903 || cp === 0x0300 || cp === 0x0488))
50
+ return false;
51
+ // MIDDLE DOT (U+00B7) must be flanked by 'l' or 'L'
52
+ if (cp === 0x00B7) {
61
53
  if (i === 0 || i === label.length - 1)
62
54
  return false;
63
- const prev = label.charAt(i - 1);
64
- const next = label.charAt(i + 1);
65
- if (!/^[lL]$/.test(prev) || !/^[lL]$/.test(next))
55
+ // deno-coverage-ignore
56
+ const prev = label.codePointAt(i - 1) ?? 0;
57
+ // deno-coverage-ignore
58
+ const next = label.codePointAt(i + 1) ?? 0;
59
+ if ((prev !== 108 && prev !== 76) || (next !== 108 && next !== 76))
66
60
  return false;
67
61
  }
68
- // --- KATAKANA MIDDLE DOT (U+30FB) ---
69
- if (char === '\u30FB') {
70
- // If label is a single character, it's invalid.
62
+ // KATAKANA MIDDLE DOT (U+30FB) | U+30FB is below U+FFFF so stride is always 1
63
+ if (cp === 0x30FB) {
71
64
  if (label.length === 1)
72
65
  return false;
73
66
  if (i === 0) {
74
- // At beginning: check following character.
75
- const next = label.charAt(i + 1);
67
+ // deno-coverage-ignore
68
+ const next = label.codePointAt(i + 1) ?? 0;
76
69
  if (!IsValidAdjacentForKatakanaMiddleDot(next))
77
70
  return false;
78
71
  }
79
72
  else {
80
- // In the middle: check both adjacent characters.
81
- const prev = label.charAt(i - 1);
82
- const next = label.charAt(i + 1);
83
- if (!IsValidAdjacentForKatakanaMiddleDot(prev) || !IsValidAdjacentForKatakanaMiddleDot(next)) {
73
+ // deno-coverage-ignore
74
+ const prev = label.codePointAt(i - 1) ?? 0;
75
+ // deno-coverage-ignore
76
+ const next = label.codePointAt(i + 1) ?? 0;
77
+ if (!IsValidAdjacentForKatakanaMiddleDot(prev) || !IsValidAdjacentForKatakanaMiddleDot(next))
84
78
  return false;
85
- }
86
79
  }
87
80
  }
88
- // --- Greek Keraia (U+0375) ---
89
- if (char === '\u0375') {
81
+ // Greek KERAIA (U+0375) | U+0375 is below U+FFFF so stride is always 1
82
+ if (cp === 0x0375) {
90
83
  if (i === label.length - 1)
91
84
  return false;
92
- const next = label.charAt(i + 1);
93
- if (!/[\u0370-\u03FF]/.test(next))
85
+ // deno-coverage-ignore
86
+ const next = label.codePointAt(i + 1) ?? 0;
87
+ if (next < 0x0370 || next > 0x03FF)
94
88
  return false;
95
89
  }
96
- // --- Hebrew GERESH (U+05F3) and GERSHAYIM (U+05F4) ---
97
- if (char === '\u05F3' || char === '\u05F4') {
90
+ // Hebrew GERESH (U+05F3) and GERSHAYIM (U+05F4)
91
+ if (cp === 0x05F3 || cp === 0x05F4) {
98
92
  if (i === 0)
99
93
  return false;
100
- const prev = label.charAt(i - 1);
101
- if (!/[\u05D0-\u05EA]/.test(prev))
94
+ // deno-coverage-ignore
95
+ const prev = label.codePointAt(i - 1) ?? 0;
96
+ if (prev < 0x05D0 || prev > 0x05EA)
102
97
  return false;
103
98
  }
104
- // --- ZERO WIDTH JOINER (U+200D) ---
105
- if (char === '\u200D') {
99
+ // ZERO WIDTH JOINER (U+200D)
100
+ if (cp === 0x200D) {
106
101
  if (i === 0)
107
102
  return false;
108
- const prev = label.charAt(i - 1);
109
- if (prev !== '\u094D')
103
+ // deno-coverage-ignore
104
+ const prev = label.codePointAt(i - 1) ?? 0;
105
+ if (prev !== 0x094D)
110
106
  return false;
111
107
  }
112
- // ZERO WIDTH NON-JOINER (U+200C) is allowed.
113
- }
114
- // --- Arabic-Indic digits vs. Extended Arabic-Indic digits ---
115
- let hasArabicIndic = false;
116
- let hasExtendedArabicIndic = false;
117
- for (let i = 0; i < label.length; i++) {
118
- const char = label.charAt(i);
119
- if (/[\u0660-\u0669]/.test(char))
108
+ // Arabic-Indic digits
109
+ if (cp >= 0x0660 && cp <= 0x0669)
120
110
  hasArabicIndic = true;
121
- if (/[\u06F0-\u06F9]/.test(char))
111
+ // Extended Arabic-Indic digits
112
+ if (cp >= 0x06F0 && cp <= 0x06F9)
122
113
  hasExtendedArabicIndic = true;
123
114
  }
124
115
  if (hasArabicIndic && hasExtendedArabicIndic)
@@ -1,6 +1,6 @@
1
+ export declare function IsIPv4Internal(value: string, start: number, end: number): boolean;
1
2
  /**
2
3
  * Returns true if the value is a IPV4 address
3
- * @source ajv-formats
4
4
  * @specification http://tools.ietf.org/html/rfc2673#section-3.2
5
5
  */
6
6
  export declare function IsIPv4(value: string): boolean;
@@ -1,9 +1,38 @@
1
- const IPv4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
1
+ // ------------------------------------------------------------------
2
+ // Ranged Fast Path
3
+ // ------------------------------------------------------------------
4
+ /* Returns true if the value is a IPV4 address from index range offsets */
5
+ export function IsIPv4Internal(value, start, end) {
6
+ let dots = 0;
7
+ let num = 0;
8
+ let digits = 0;
9
+ let leading = 0;
10
+ for (let i = start; i < end; i++) {
11
+ const ch = value.charCodeAt(i);
12
+ if (ch === 46) { // '.'
13
+ if (digits === 0 || num > 255 || (leading === 48 && digits > 1))
14
+ return false;
15
+ dots++;
16
+ num = 0;
17
+ digits = 0;
18
+ leading = 0;
19
+ }
20
+ else if (ch >= 48 && ch <= 57) { // '0'-'9'
21
+ if (digits === 0)
22
+ leading = ch;
23
+ num = num * 10 + (ch - 48);
24
+ digits++;
25
+ }
26
+ else {
27
+ return false;
28
+ }
29
+ }
30
+ return dots === 3 && digits > 0 && num <= 255 && !(leading === 48 && digits > 1);
31
+ }
2
32
  /**
3
33
  * Returns true if the value is a IPV4 address
4
- * @source ajv-formats
5
34
  * @specification http://tools.ietf.org/html/rfc2673#section-3.2
6
35
  */
7
36
  export function IsIPv4(value) {
8
- return IPv4.test(value);
37
+ return IsIPv4Internal(value, 0, value.length);
9
38
  }
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Returns true if the value is a IPV6 address
3
- * @source ajv-formats
2
+ * Returns true if the value is an IPv6 address
4
3
  * @specification http://tools.ietf.org/html/rfc2373#section-2.2
5
4
  */
6
5
  export declare function IsIPv6(value: string): boolean;
@@ -1,9 +1,67 @@
1
- const IPv6 = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i;
1
+ import { IsIPv4Internal } from './ipv4.mjs';
2
+ function InRange(ch) {
3
+ return (ch >= 48 && ch <= 57) || // 0-9
4
+ (ch >= 65 && ch <= 70) || // A-F
5
+ (ch >= 97 && ch <= 102); // a-f
6
+ }
2
7
  /**
3
- * Returns true if the value is a IPV6 address
4
- * @source ajv-formats
8
+ * Returns true if the value is an IPv6 address
5
9
  * @specification http://tools.ietf.org/html/rfc2373#section-2.2
6
10
  */
7
11
  export function IsIPv6(value) {
8
- return IPv6.test(value);
12
+ const length = value.length;
13
+ if (length === 0)
14
+ return false;
15
+ let groups = 0;
16
+ let compressed = false;
17
+ let i = 0;
18
+ // handle leading '::'
19
+ if (value.charCodeAt(0) === 58 && value.charCodeAt(1) === 58) {
20
+ if (length === 2)
21
+ return true; // '::' is valid
22
+ compressed = true;
23
+ i = 2;
24
+ }
25
+ while (i < length) {
26
+ // read hex digits
27
+ let digits = 0;
28
+ const start = i;
29
+ while (i < length && InRange(value.charCodeAt(i))) {
30
+ i++;
31
+ digits++;
32
+ }
33
+ if (digits === 0)
34
+ return false;
35
+ const next = value.charCodeAt(i);
36
+ // check for embedded IPv4 at the end
37
+ if (next === 46) { // '.'
38
+ if (!IsIPv4Internal(value, start, length))
39
+ return false;
40
+ groups += 2;
41
+ i = length;
42
+ break;
43
+ }
44
+ if (digits > 4)
45
+ return false;
46
+ groups++;
47
+ if (i === length)
48
+ break;
49
+ // expect ':' separator
50
+ if (next !== 58)
51
+ return false;
52
+ i++;
53
+ // check for '::' compression
54
+ if (value.charCodeAt(i) === 58) {
55
+ if (compressed)
56
+ return false; // only one '::' allowed
57
+ // check for ':::'
58
+ if (value.charCodeAt(i + 1) === 58)
59
+ return false;
60
+ compressed = true;
61
+ i++;
62
+ if (i === length)
63
+ break; // trailing '::'
64
+ }
65
+ }
66
+ return compressed ? groups <= 7 : groups === 8;
9
67
  }
@@ -1,6 +1,5 @@
1
1
  /**
2
- * Returns true if the value is a uri
3
- * @specification
4
- * @source ajv-formats
2
+ * Returns true if the value matches RFC 3986 URI syntax.
3
+ * @specification https://tools.ietf.org/html/rfc3986
5
4
  */
6
5
  export declare function IsUri(value: string): boolean;
@@ -1,9 +1,135 @@
1
- const Uri = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/)?[^\s]*$/i;
1
+ function IsAlpha(ch) {
2
+ return (ch >= 97 && ch <= 122) || (ch >= 65 && ch <= 90); // a-z, A-Z
3
+ }
4
+ function IsAlphaNumeric(ch) {
5
+ return IsAlpha(ch) || (ch >= 48 && ch <= 57); // a-z, A-Z, 0-9
6
+ }
7
+ function IsHex(ch) {
8
+ return (ch >= 48 && ch <= 57) || // 0-9
9
+ (ch >= 65 && ch <= 70) || // A-F
10
+ (ch >= 97 && ch <= 102); // a-f
11
+ }
12
+ function IsSchemeChar(ch) {
13
+ return IsAlphaNumeric(ch) ||
14
+ ch === 43 || ch === 45 || ch === 46; // '+', '-', '.'
15
+ }
16
+ function IsUnreserved(ch) {
17
+ return IsAlphaNumeric(ch) ||
18
+ ch === 45 || ch === 46 || // '-', '.'
19
+ ch === 95 || ch === 126; // '_', '~'
20
+ }
21
+ function IsSubDelim(ch) {
22
+ return ch === 33 || ch === 36 || ch === 38 || ch === 39 ||
23
+ ch === 40 || ch === 41 || ch === 42 || ch === 43 ||
24
+ ch === 44 || ch === 59 || ch === 61; // ! $ & ' ( ) * + , ; =
25
+ }
26
+ function IsPchar(ch) {
27
+ return IsUnreserved(ch) || IsSubDelim(ch) || ch === 58 || ch === 64; // ':', '@'
28
+ }
2
29
  /**
3
- * Returns true if the value is a uri
4
- * @specification
5
- * @source ajv-formats
30
+ * Returns true if the value matches RFC 3986 URI syntax.
31
+ * @specification https://tools.ietf.org/html/rfc3986
6
32
  */
7
33
  export function IsUri(value) {
8
- return Uri.test(value);
34
+ const length = value.length;
35
+ if (length === 0)
36
+ return false;
37
+ // Scheme: must start with a letter
38
+ if (!IsAlpha(value.charCodeAt(0)))
39
+ return false;
40
+ // Scheme: continues until ':'
41
+ let i = 1;
42
+ while (i < length) {
43
+ const ch = value.charCodeAt(i);
44
+ if (ch === 58)
45
+ break; // ':'
46
+ if (!IsSchemeChar(ch))
47
+ return false;
48
+ i++;
49
+ }
50
+ // Ensure scheme is terminated by ':'
51
+ if (value.charCodeAt(i) !== 58)
52
+ return false;
53
+ i++;
54
+ // Authority: optional '//'
55
+ if (value.charCodeAt(i) === 47 && value.charCodeAt(i + 1) === 47) {
56
+ i += 2;
57
+ // Userinfo: check for '@' before path/query/fragment delimiters
58
+ const authorityStart = i;
59
+ let atPos = -1;
60
+ for (let j = i; j < length; j++) {
61
+ const ch = value.charCodeAt(j);
62
+ if (ch === 64) {
63
+ atPos = j;
64
+ break;
65
+ } // '@'
66
+ if (ch === 47 || ch === 63 || ch === 35)
67
+ break; // '/', '?', '#'
68
+ }
69
+ if (atPos !== -1) {
70
+ // Userinfo: validate allowed chars and percent-encoding
71
+ for (let j = authorityStart; j < atPos; j++) {
72
+ const ch = value.charCodeAt(j);
73
+ if (ch === 91 || ch === 93)
74
+ return false; // No '[' or ']' in userinfo
75
+ if (ch === 37) { // '%' percent-encoding
76
+ if (j + 2 >= atPos || !IsHex(value.charCodeAt(j + 1)) || !IsHex(value.charCodeAt(j + 2)))
77
+ return false;
78
+ j += 2;
79
+ }
80
+ else if (!IsUnreserved(ch) && !IsSubDelim(ch) && ch !== 58)
81
+ return false;
82
+ }
83
+ i = atPos + 1;
84
+ }
85
+ // Host: IP-literal [addr] or reg-name
86
+ if (value.charCodeAt(i) === 91) { // '['
87
+ i++;
88
+ while (i < length && value.charCodeAt(i) !== 93)
89
+ i++;
90
+ if (value.charCodeAt(i) !== 93)
91
+ return false; // ']'
92
+ i++;
93
+ }
94
+ else {
95
+ // Host: validate ASCII; skip validation for non-ASCII (Unicode hosts)
96
+ while (i < length) {
97
+ const ch = value.charCodeAt(i);
98
+ if (ch === 47 || ch === 63 || ch === 35 || ch === 58)
99
+ break;
100
+ if (ch < 128 && !IsUnreserved(ch) && !IsSubDelim(ch))
101
+ return false;
102
+ i++;
103
+ }
104
+ }
105
+ // Port: optional digits after ':'
106
+ if (value.charCodeAt(i) === 58) { // ':'
107
+ i++;
108
+ while (i < length) {
109
+ const ch = value.charCodeAt(i);
110
+ if (ch === 47 || ch === 63 || ch === 35)
111
+ break;
112
+ if (ch < 48 || ch > 57)
113
+ return false; // 0-9
114
+ i++;
115
+ }
116
+ }
117
+ }
118
+ // Path, Query, and Fragment
119
+ while (i < length) {
120
+ const ch = value.charCodeAt(i);
121
+ if (ch === 37) { // '%' percent-encoding
122
+ if (i + 2 >= length || !IsHex(value.charCodeAt(i + 1)) || !IsHex(value.charCodeAt(i + 2)))
123
+ return false;
124
+ i += 2;
125
+ }
126
+ else if (ch > 127) {
127
+ return false; // URI is ASCII-only
128
+ }
129
+ else if (!(IsPchar(ch) || ch === 47 || ch === 63 || ch === 35)) {
130
+ return false; // '/', '?', '#'
131
+ }
132
+ i++;
133
+ }
134
+ return true;
9
135
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "typebox",
3
3
  "description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
4
- "version": "1.1.10",
4
+ "version": "1.1.11",
5
5
  "keywords": [
6
6
  "typescript",
7
7
  "jsonschema"