@jarenjs/core 0.9.2

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 (58) hide show
  1. package/ARCHITECTURE.md +800 -0
  2. package/LICENSE +21 -0
  3. package/README.md +68 -0
  4. package/dist/types/array.d.ts +28 -0
  5. package/dist/types/bigint.d.ts +5 -0
  6. package/dist/types/dates.d.ts +79 -0
  7. package/dist/types/float.d.ts +32 -0
  8. package/dist/types/function.d.ts +22 -0
  9. package/dist/types/index.d.ts +119 -0
  10. package/dist/types/integer.d.ts +24 -0
  11. package/dist/types/math/float64.d.ts +121 -0
  12. package/dist/types/math/index.d.ts +5 -0
  13. package/dist/types/math/int32.d.ts +41 -0
  14. package/dist/types/math/vec2f64.d.ts +346 -0
  15. package/dist/types/math/vec2i32.d.ts +43 -0
  16. package/dist/types/math/vec3f64.d.ts +61 -0
  17. package/dist/types/number.d.ts +39 -0
  18. package/dist/types/object.d.ts +44 -0
  19. package/dist/types/scan.d.ts +64 -0
  20. package/dist/types/string.d.ts +65 -0
  21. package/dist/types/text/base64.d.ts +4 -0
  22. package/dist/types/text/basic.d.ts +5 -0
  23. package/dist/types/text/email.d.ts +3 -0
  24. package/dist/types/text/host.d.ts +14 -0
  25. package/dist/types/text/i18n.d.ts +13 -0
  26. package/dist/types/text/identifiers.d.ts +5 -0
  27. package/dist/types/text/index.d.ts +8 -0
  28. package/dist/types/text/iregexp.d.ts +36 -0
  29. package/dist/types/text/misc.d.ts +4 -0
  30. package/dist/types/text/punycode.d.ts +86 -0
  31. package/package.json +101 -0
  32. package/src/array.js +57 -0
  33. package/src/bigint.js +30 -0
  34. package/src/dates.js +371 -0
  35. package/src/float.js +107 -0
  36. package/src/function.js +56 -0
  37. package/src/index.js +223 -0
  38. package/src/integer.js +77 -0
  39. package/src/math/float64.js +316 -0
  40. package/src/math/index.js +5 -0
  41. package/src/math/int32.js +235 -0
  42. package/src/math/vec2f64.js +706 -0
  43. package/src/math/vec2i32.js +250 -0
  44. package/src/math/vec3f64.js +225 -0
  45. package/src/number.js +63 -0
  46. package/src/object.js +240 -0
  47. package/src/scan.js +96 -0
  48. package/src/string.js +194 -0
  49. package/src/text/base64.js +54 -0
  50. package/src/text/basic.js +23 -0
  51. package/src/text/email.js +61 -0
  52. package/src/text/host.js +335 -0
  53. package/src/text/i18n.js +294 -0
  54. package/src/text/identifiers.js +27 -0
  55. package/src/text/index.js +11 -0
  56. package/src/text/iregexp.js +308 -0
  57. package/src/text/misc.js +19 -0
  58. package/src/text/punycode.js +407 -0
package/src/object.js ADDED
@@ -0,0 +1,240 @@
1
+ //@ts-check
2
+
3
+ import {
4
+ isFn,
5
+ isScalarType,
6
+ isBooleanType,
7
+ isTypedArray,
8
+ } from './index.js';
9
+
10
+ const hasOwn = Object.hasOwn;
11
+
12
+ /**
13
+ * Deep equality comparison for arbitrary values.
14
+ *
15
+ * Generic JavaScript equality: understands Maps, Sets, RegExps,
16
+ * functions, typed arrays and class instances (constructors must
17
+ * match). Not the same as `equalsJson`, which compares JSON values
18
+ * only and is the hot-path variant — keep both.
19
+ * @param {any} target
20
+ * @param {any} source
21
+ * @returns {boolean}
22
+ */
23
+ export function equalsDeep(target, source) {
24
+ if (target === source) return true;
25
+ if (target == null) return false;
26
+ if (source == null) return false;
27
+ if (isBooleanType(target)) return false;
28
+ if (isBooleanType(source)) return false;
29
+
30
+ if (isFn(target))
31
+ return target.toString() === source.toString();
32
+
33
+ if (isScalarType(target))
34
+ return false;
35
+
36
+ if (target.constructor !== source.constructor)
37
+ return false;
38
+
39
+ if (target.constructor === Object) {
40
+ const tks = Object.keys(target);
41
+ const sks = Object.keys(source);
42
+ if (tks.length !== sks.length)
43
+ return false;
44
+ for (let i = 0; i < tks.length; ++i) {
45
+ const key = tks[i];
46
+ if (!equalsDeep(target[key], source[key]))
47
+ return false;
48
+ }
49
+ return true;
50
+ }
51
+
52
+ if (target.constructor === Map) {
53
+ if (target.size !== source.size)
54
+ return false;
55
+ for (const [key, value] of target) {
56
+ if (source.has(key) === false)
57
+ return false;
58
+ if (!equalsDeep(value, source.get(key)))
59
+ return false;
60
+ }
61
+ return true;
62
+ }
63
+
64
+ if (target.constructor === Array) {
65
+ if (target.length !== source.length)
66
+ return false;
67
+ for (let i = 0; i < target.length; ++i) {
68
+ if (!equalsDeep(target[i], source[i]))
69
+ return false;
70
+ }
71
+ return true;
72
+ }
73
+
74
+ if (target.constructor === Set) {
75
+ if (target.size !== source.size)
76
+ return false;
77
+ for (const value of target) {
78
+ if (source.has(value) === false)
79
+ return false;
80
+ }
81
+ return true;
82
+ }
83
+
84
+ if (target.constructor === RegExp) {
85
+ return target.toString() === source.toString();
86
+ }
87
+
88
+ if (isTypedArray(target)) {
89
+ if (target.length !== source.length)
90
+ return false;
91
+ for (let i = 0; i < target.length; ++i) {
92
+ if (target[i] !== source[i])
93
+ return false;
94
+ }
95
+ return true;
96
+ }
97
+
98
+ // we could test for instance of Array, Map and Set in order
99
+ // to differentiate between types of equality.. but we dont.
100
+ const tkeys = Object.keys(target);
101
+ const skeys = Object.keys(source);
102
+ if (tkeys.length !== skeys.length) return false;
103
+ if (tkeys.length === 0) return true;
104
+ for (let i = 0; i < tkeys.length; ++i) {
105
+ const key = tkeys[i];
106
+ if (!equalsDeep(target[key], source[key]))
107
+ return false;
108
+ }
109
+ return true;
110
+ }
111
+
112
+ /**
113
+ * Structural equality of two JSON values per RFC 9535 section 2.3.5.2.2.
114
+ *
115
+ * JSON-only equality: objects compare by own enumerable keys, arrays by
116
+ * index, primitives by `===` (so `1 === 1.0`, and `NaN` is never equal).
117
+ * Anything a JSON value cannot be — Map, Set, RegExp, function, class
118
+ * instance — compares by identity only. Deliberately not the same as
119
+ * `equalsDeep`, the generic JavaScript variant: this is the hot-path
120
+ * comparator of the JSONPath engine — do not merge the two.
121
+ * @param {any} a
122
+ * @param {any} b
123
+ * @returns {boolean}
124
+ */
125
+ export function equalsJson(a, b) {
126
+ if (a === b)
127
+ return true;
128
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null)
129
+ return false;
130
+ const aIsArray = Array.isArray(a);
131
+ if (aIsArray !== Array.isArray(b))
132
+ return false;
133
+ if (aIsArray) {
134
+ const alen = a.length;
135
+ if (alen !== b.length)
136
+ return false;
137
+ for (let i = 0; i < alen; i++) {
138
+ if (!equalsJson(a[i], b[i]))
139
+ return false;
140
+ }
141
+ return true;
142
+ }
143
+ let count = 0;
144
+ for (const key in a) {
145
+ if (!hasOwn(a, key))
146
+ continue;
147
+ if (!hasOwn(b, key) || !equalsJson(a[key], b[key]))
148
+ return false;
149
+ count++;
150
+ }
151
+ for (const key in b) {
152
+ if (hasOwn(b, key))
153
+ count--;
154
+ }
155
+ return count === 0;
156
+ }
157
+
158
+ /**
159
+ * Check if all items in an array are unique using deep equality
160
+ * @param {any[]} arr - The array to check
161
+ * @returns {boolean} True if all items are unique
162
+ */
163
+ export function isUniqueDeepArray(arr) {
164
+ if (!Array.isArray(arr) || arr.length < 2) return true;
165
+
166
+ // Small all-scalar arrays: pairwise === beats allocating a Set.
167
+ if (arr.length <= 8) {
168
+ let allScalar = true;
169
+ for (let i = 0; i < arr.length; i++) {
170
+ const item = arr[i];
171
+ if ((typeof item === 'object' && item !== null) || typeof item === 'function') {
172
+ allScalar = false;
173
+ break;
174
+ }
175
+ }
176
+ if (allScalar) {
177
+ for (let i = 0; i < arr.length; i++) {
178
+ for (let j = i + 1; j < arr.length; j++) {
179
+ if (arr[i] === arr[j]) return false;
180
+ }
181
+ }
182
+ return true;
183
+ }
184
+ }
185
+
186
+ // Primitives are compared with === by equalsDeep and can never deep-equal
187
+ // an object, so they dedupe in O(n) through a Set (SameValueZero); only
188
+ // objects, arrays and functions need the pairwise deep comparison.
189
+ let seen = null;
190
+ let complex = null;
191
+ for (let i = 0; i < arr.length; i++) {
192
+ const item = arr[i];
193
+ if ((typeof item === 'object' && item !== null) || typeof item === 'function') {
194
+ if (complex === null) complex = [item];
195
+ else complex.push(item);
196
+ }
197
+ else {
198
+ // NaN !== NaN under equalsDeep; keep NaN values always unique
199
+ if (typeof item === 'number' && item !== item) continue;
200
+ if (seen === null) seen = new Set();
201
+ else if (seen.has(item)) return false;
202
+ seen.add(item);
203
+ }
204
+ }
205
+
206
+ if (complex !== null && complex.length > 1) {
207
+ for (let i = 0; i < complex.length; i++) {
208
+ for (let j = i + 1; j < complex.length; j++) {
209
+ if (equalsDeep(complex[i], complex[j])) return false;
210
+ }
211
+ }
212
+ }
213
+ return true;
214
+ }
215
+
216
+ /**
217
+ *
218
+ * @param {Map<any, any>} map
219
+ * @param {...Map<any, any>} iterables
220
+ */
221
+ export function mergeMap(map, ...iterables) {
222
+ for (const iterable of iterables) {
223
+ for (const item of iterable) {
224
+ map.set(...item);
225
+ }
226
+ }
227
+ }
228
+
229
+ /**
230
+ *
231
+ * @param {Set<any>} set
232
+ * @param {...Array<Set<any>>} iterables
233
+ */
234
+ export function mergeSet(set, ...iterables) {
235
+ for (const iterable of iterables) {
236
+ for (const item of iterable) {
237
+ set.add(item);
238
+ }
239
+ }
240
+ }
package/src/scan.js ADDED
@@ -0,0 +1,96 @@
1
+ //@ts-check
2
+
3
+ // Scanner primitives shared by the char-code-level recursive-descent
4
+ // parsers (JSONPath in json/path.js, I-Regexp in text/iregexp.js).
5
+ // Constants and pure predicates only — stateful token readers belong
6
+ // to the parser that owns the cursor.
7
+
8
+ //#region char codes
9
+
10
+ export const CC_TAB = 0x09;
11
+ export const CC_LF = 0x0A;
12
+ export const CC_CR = 0x0D;
13
+ export const CC_SPACE = 0x20;
14
+ export const CC_BANG = 0x21;
15
+ export const CC_DQUOTE = 0x22;
16
+ export const CC_HASH = 0x23;
17
+ export const CC_DOLLAR = 0x24;
18
+ export const CC_PERCENT = 0x25;
19
+ export const CC_AMP = 0x26;
20
+ export const CC_SQUOTE = 0x27;
21
+ export const CC_LPAREN = 0x28;
22
+ export const CC_RPAREN = 0x29;
23
+ export const CC_STAR = 0x2A;
24
+ export const CC_COMMA = 0x2C;
25
+ export const CC_MINUS = 0x2D;
26
+ export const CC_DOT = 0x2E;
27
+ export const CC_SLASH = 0x2F;
28
+ export const CC_0 = 0x30;
29
+ export const CC_1 = 0x31;
30
+ export const CC_9 = 0x39;
31
+ export const CC_COLON = 0x3A;
32
+ export const CC_LT = 0x3C;
33
+ export const CC_EQ = 0x3D;
34
+ export const CC_GT = 0x3E;
35
+ export const CC_QUESTION = 0x3F;
36
+ export const CC_AT = 0x40;
37
+ export const CC_LBRACKET = 0x5B;
38
+ export const CC_BACKSLASH = 0x5C;
39
+ export const CC_RBRACKET = 0x5D;
40
+ export const CC_UNDERSCORE = 0x5F;
41
+ export const CC_PIPE = 0x7C;
42
+ export const CC_TILDE = 0x7E;
43
+
44
+ //#endregion
45
+
46
+ //#region predicates
47
+
48
+ /**
49
+ * Checks if a char code is an ASCII digit (0-9).
50
+ * @param {number} c - The char code
51
+ * @returns {boolean}
52
+ */
53
+ export function isDigitCode(c) {
54
+ return c >= CC_0 && c <= CC_9;
55
+ }
56
+
57
+ /**
58
+ * Checks if a char code is an ASCII hexadecimal digit (0-9, A-F, a-f).
59
+ * @param {number} c - The char code
60
+ * @returns {boolean}
61
+ */
62
+ export function isHexDigitCode(c) {
63
+ return (c >= CC_0 && c <= CC_9)
64
+ || (c >= 0x41 && c <= 0x46) // A-F
65
+ || (c >= 0x61 && c <= 0x66); // a-f
66
+ }
67
+
68
+ /**
69
+ * Checks if a char code is blank space per RFC 9535 (space, tab,
70
+ * line feed or carriage return).
71
+ * @param {number} c - The char code
72
+ * @returns {boolean}
73
+ */
74
+ export function isWhitespaceCode(c) {
75
+ return c === CC_SPACE || c === CC_TAB || c === CC_LF || c === CC_CR;
76
+ }
77
+
78
+ /**
79
+ * Checks if a char code is an ASCII lowercase letter (a-z).
80
+ * @param {number} c - The char code
81
+ * @returns {boolean}
82
+ */
83
+ export function isAsciiLowerCode(c) {
84
+ return c >= 0x61 && c <= 0x7A;
85
+ }
86
+
87
+ /**
88
+ * Checks if a char code is an ASCII uppercase letter (A-Z).
89
+ * @param {number} c - The char code
90
+ * @returns {boolean}
91
+ */
92
+ export function isAsciiUpperCode(c) {
93
+ return c >= 0x41 && c <= 0x5A;
94
+ }
95
+
96
+ //#endregion
package/src/string.js ADDED
@@ -0,0 +1,194 @@
1
+ //@ts-check
2
+
3
+ import {
4
+ isStringType,
5
+ isObjectOfClass,
6
+ } from './index.js';
7
+
8
+ /**
9
+ * @param {string | null | undefined} data
10
+ * @returns {boolean}
11
+ */
12
+ export function isStringEmpty(data) {
13
+ return typeof data === 'string' && !data;
14
+ }
15
+
16
+ /**
17
+ * @param {string | null | undefined} data
18
+ * @returns {boolean}
19
+ */
20
+ export function isStringWhiteSpace(data) {
21
+ return data == null || /^\s*$/.test(data);
22
+ }
23
+
24
+ /**
25
+ * @param {string} str
26
+ * @returns {boolean}
27
+ */
28
+ export function isStringUpperCase(str) {
29
+ return str === str.toUpperCase();
30
+ }
31
+
32
+ /**
33
+ * @param {string} str
34
+ * @returns {boolean}
35
+ */
36
+ export function isStringLowerCase(str) {
37
+ return str === str.toLowerCase();
38
+ }
39
+
40
+ /**
41
+ * @param {unknown} data
42
+ * @returns {data is RegExp}
43
+ */
44
+ export function isRegExpType(data) {
45
+ return isObjectOfClass(data, RegExp);
46
+ }
47
+
48
+
49
+ /**
50
+ * @param {string | RegExp | null | undefined } data
51
+ * @returns {boolean}
52
+ */
53
+ export function isStringRegExp(data) {
54
+ try {
55
+ return createRegExp(data) != null;
56
+ }
57
+ // eslint-disable-next-line no-unused-vars
58
+ catch (e) {
59
+ return false;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * @param {string | RegExp | null | undefined } pattern
65
+ * @returns {RegExp | undefined}
66
+ */
67
+ export function createRegExp(pattern) {
68
+ if (pattern == null)
69
+ return undefined;
70
+
71
+ if (isRegExpType(pattern))
72
+ return pattern;
73
+
74
+ if (isStringType(pattern)) {
75
+ if (pattern[0] === '/') {
76
+ const e = pattern.lastIndexOf('/');
77
+ if (e >= 0) {
78
+ const r = pattern.substring(1, e);
79
+ const g = pattern.substring(e + 1);
80
+ // Add unicode flag if not already present
81
+ return g.includes('u') ? new RegExp(r, g) : new RegExp(r, g + 'u');
82
+ }
83
+ }
84
+ else
85
+ return new RegExp(pattern, 'u');
86
+ }
87
+ // TODO: should we return false instead?
88
+ throw new Error(`Unknown Regular Expression Pattern Type: ${pattern}`);
89
+ }
90
+
91
+ /**
92
+ * @type {Intl.Segmenter | null}
93
+ */
94
+ let segmenterCache = null;
95
+ export function getSegmenter() {
96
+ if (segmenterCache === null) {
97
+ segmenterCache = new Intl.Segmenter(undefined, { granularity: "grapheme" });
98
+ }
99
+ return segmenterCache;
100
+ }
101
+
102
+ /**
103
+ * @param {string} str
104
+ * @returns {boolean}
105
+ */
106
+ export function isAsciiString(str) {
107
+ const len = str.length;
108
+ for (let i = 0; i < len; i++) {
109
+ if (str.charCodeAt(i) > 127) return false;
110
+ }
111
+ return true;
112
+ }
113
+
114
+ // Characters that can merge with neighbours into a multi-codepoint grapheme
115
+ // cluster: CR (CRLF), combining marks, ZWJ/ZWNJ, variation selectors,
116
+ // emoji modifiers, regional indicators (flags), conjoining Hangul jamo,
117
+ // prepended concatenation marks and tag characters. When none of these are
118
+ // present, grapheme count equals code point count and the (expensive)
119
+ // Intl.Segmenter can be skipped.
120
+ // (U+0600-0605, 06DD, 070F, 0890, 0891, 08E2, 110BD, 110CD are the
121
+ // Prepended_Concatenation_Mark set, which V8 does not expose as \p{...})
122
+ const COMPLEX_GRAPHEME_REGEX = /[\r\p{M}\p{Join_Control}\p{Emoji_Modifier}\p{Regional_Indicator}\u0600-\u0605\u06DD\u070F\u0890\u0891\u08E2\uFE00-\uFE0F\u1100-\u11FF\uA960-\uA97F\uD7B0-\uD7FF\u{110BD}\u{110CD}\u{E0000}-\u{E007F}]/u;
123
+
124
+ /**
125
+ * @param {string} str
126
+ * @param {boolean} [useGrapheme=false]
127
+ * @returns {number}
128
+ */
129
+ export function getStringLength(str, useGrapheme = false) {
130
+ if (!useGrapheme) {
131
+ return str.length;
132
+ }
133
+ // Fast-path: check ASCII inline to avoid function call overhead
134
+ const len = str.length;
135
+ for (let i = 0; i < len; i++) {
136
+ if (str.charCodeAt(i) > 127) {
137
+ // Non-ASCII found - use grapheme counting.
138
+ // Segment the whole string: a cluster may span the ASCII boundary
139
+ // (e.g. 'e' followed by a combining accent).
140
+ if (COMPLEX_GRAPHEME_REGEX.test(str)) {
141
+ let count = 0;
142
+ for (const _ of getSegmenter().segment(str)) count++;
143
+ return count;
144
+ }
145
+ // No cluster-forming characters: count code points (surrogate aware)
146
+ return countCodePoints(str);
147
+ }
148
+ }
149
+ return len;
150
+ }
151
+
152
+ /**
153
+ * Count the Unicode code points of a string (surrogate-pair aware;
154
+ * a lone surrogate counts as one code point).
155
+ * @param {string} str
156
+ * @returns {number}
157
+ */
158
+ export function countCodePoints(str) {
159
+ const slen = str.length;
160
+ let count = 0;
161
+ for (let i = 0; i < slen; i++) {
162
+ const c = str.charCodeAt(i);
163
+ if (c >= 0xD800 && c <= 0xDBFF && i + 1 < slen) {
164
+ const d = str.charCodeAt(i + 1);
165
+ if (d >= 0xDC00 && d <= 0xDFFF)
166
+ i++;
167
+ }
168
+ count++;
169
+ }
170
+ return count;
171
+ }
172
+
173
+ /**
174
+ * Compare two strings by Unicode scalar values (code points), per
175
+ * RFC 9535 section 2.3.5.2.2. This differs from JavaScript's native
176
+ * `<`, which compares UTF-16 code units and orders surrogate pairs
177
+ * (U+10000 and up) below unpaired BMP characters in U+E000-U+FFFF.
178
+ * When one string is a prefix of the other, the shorter sorts first.
179
+ * @param {string} a
180
+ * @param {string} b
181
+ * @returns {number} -1 when a < b, 0 when equal, 1 when a > b
182
+ */
183
+ export function compareCodePoints(a, b) {
184
+ const alen = a.length;
185
+ const blen = b.length;
186
+ const m = alen < blen ? alen : blen;
187
+ let i = 0;
188
+ while (i < m && a.charCodeAt(i) === b.charCodeAt(i))
189
+ i++;
190
+ if (i === m)
191
+ return alen === blen ? 0 : (alen < blen ? -1 : 1);
192
+ // differing code units at i can never decode to equal code points
193
+ return a.codePointAt(i) < b.codePointAt(i) ? -1 : 1;
194
+ }
@@ -0,0 +1,54 @@
1
+ const CONST_REGEXP_BASE64 = /^(?:[a-zA-Z0-9+\/]{4})*(?:|(?:[a-zA-Z0-9+\/]{3}=)|(?:[a-zA-Z0-9+\/]{2}==)|(?:[a-zA-Z0-9+\/]{1}===))$/;
2
+ export function isValidBase64Full(str) {
3
+ return CONST_REGEXP_BASE64.test(str);
4
+ }
5
+
6
+ // Base64 validation regex (RFC 4648)
7
+ const BASE64_REGEX_SHORT = /^[A-Za-z0-9+/]*={0,2}$/;
8
+
9
+ export function isValidBase64Old(str) {
10
+ // Check length is valid for base64 (multiple of 4)
11
+ if (str.length % 4 !== 0) return false;
12
+ // Check characters are valid base64
13
+ if (!BASE64_REGEX_SHORT.test(str)) return false;
14
+ return true;
15
+ }
16
+
17
+ const BASE64_REGEX = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})?$/;
18
+
19
+ export function isValidBase64(str) {
20
+ return BASE64_REGEX.test(str);
21
+ }
22
+
23
+ // Fast base64 validation - inline for performance
24
+ // Valid base64 chars: A-Z (65-90), a-z (97-122), 0-9 (48-57), + (43), / (47)
25
+ export function isValidBase64Fast(str) {
26
+ const len = str.length;
27
+ if (len === 0) return true;
28
+
29
+ // Check length is valid for base64 (must be multiple of 4)
30
+ if (len % 4 !== 0) return false;
31
+
32
+ // Check all characters are valid base64 (A-Z, a-z, 0-9, +, /, =)
33
+ for (let i = 0; i < len; i++) {
34
+ const code = str.charCodeAt(i);
35
+ // A-Z, a-z, 0-9, +, /, =
36
+ if (!((code >= 65 && code <= 90) ||
37
+ (code >= 97 && code <= 122) ||
38
+ (code >= 48 && code <= 57) ||
39
+ code === 43 || code === 47 || code === 61)) {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ // Check padding rules
45
+ // Valid endings: xxxx (no padding), xxx= (1 padding), xx== (2 padding)
46
+ const c1 = str.charCodeAt(len - 2);
47
+ const c2 = str.charCodeAt(len - 1);
48
+
49
+ // If second to last is =, last must also be =
50
+ if (c1 === 61 && c2 !== 61) return false;
51
+
52
+ return true;
53
+ }
54
+
@@ -0,0 +1,23 @@
1
+ const CONST_REGEXP_ALPHA = /^[a-zA-Z]+$/;
2
+ export function isValidAlpha(str) {
3
+ return CONST_REGEXP_ALPHA.test(str);
4
+ }
5
+ const CONST_REGEXP_ALPHANUMERIC = /^[a-zA-Z0-9]+$/;
6
+ export function isValidAlphaNumeric(str) {
7
+ return CONST_REGEXP_ALPHANUMERIC.test(str);
8
+ }
9
+
10
+ const CONST_REGEXP_NUMERIC = /^[0-9]+$/;
11
+ export function isValidNumeric(str) {
12
+ return CONST_REGEXP_NUMERIC.test(str);
13
+ }
14
+
15
+ const CONST_REGEXP_HEXADECIMAL = /^[a-fA-F0-9]+$/;
16
+ export function isValidHexaDecimal(str) {
17
+ return CONST_REGEXP_HEXADECIMAL.test(str);
18
+ }
19
+
20
+ const CONST_REGEXP_HEXCOLOR = /^#(?:[0-9a-f]{3}){1,2}\b$/i;
21
+ export function isValidHexColor(str) {
22
+ return CONST_REGEXP_HEXCOLOR.test(str);
23
+ }
@@ -0,0 +1,61 @@
1
+ import {
2
+ isValidIPv4,
3
+ isValidIPv6,
4
+ } from './host.js';
5
+
6
+ // email (sources from json validator):
7
+ // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363
8
+ // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'willful violation')
9
+ const CONST_REGEXP_EMAIL_FAST = /^[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;
10
+
11
+ // RFC 5321 slow-path pieces: quoted-string local part (qtext / quoted-pair),
12
+ // dot-atom local part and dot-atom domain.
13
+ const CONST_REGEXP_EMAIL_QUOTED_LOCAL = /^"(?:[\x20-\x21\x23-\x5b\x5d-\x7e]|\\[\x20-\x7e])*"$/;
14
+ const CONST_REGEXP_EMAIL_LOCAL = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/i;
15
+ const CONST_REGEXP_EMAIL_DOMAIN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)+$/i;
16
+
17
+ function isValidEmailDomain(domain) {
18
+ // RFC 5321 address literal: [IPv4] or [IPv6:...]
19
+ if (domain.charCodeAt(0) === 0x5b /* [ */) {
20
+ if (domain.charCodeAt(domain.length - 1) !== 0x5d /* ] */) return false;
21
+ const literal = domain.slice(1, -1);
22
+ if (literal.slice(0, 5).toLowerCase() === 'ipv6:')
23
+ return isValidIPv6(literal.slice(5));
24
+ return isValidIPv4(literal);
25
+ }
26
+ return CONST_REGEXP_EMAIL_DOMAIN.test(domain);
27
+ }
28
+
29
+ export function isValidEmail(str) {
30
+ // Fast path: dot-atom local part with a regular domain
31
+ if (CONST_REGEXP_EMAIL_FAST.test(str)) return true;
32
+
33
+ // Slow path (RFC 5321): quoted-string local part and/or address literal
34
+ if (str.charCodeAt(0) === 0x22 /* " */) {
35
+ // Find the end of the quoted string, honoring quoted-pairs
36
+ let i = 1;
37
+ for (; i < str.length; ++i) {
38
+ const c = str.charCodeAt(i);
39
+ if (c === 0x5c /* \ */) { i++; continue; }
40
+ if (c === 0x22 /* " */) break;
41
+ }
42
+ if (i >= str.length || str.charCodeAt(i + 1) !== 0x40 /* @ */) return false;
43
+ return CONST_REGEXP_EMAIL_QUOTED_LOCAL.test(str.slice(0, i + 1))
44
+ && isValidEmailDomain(str.slice(i + 2));
45
+ }
46
+
47
+ const at = str.lastIndexOf('@');
48
+ if (at <= 0) return false;
49
+ return CONST_REGEXP_EMAIL_LOCAL.test(str.slice(0, at))
50
+ && isValidEmailDomain(str.slice(at + 1));
51
+ }
52
+
53
+ const CONST_REGEXP_EMAIL_FULL = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
54
+ export function isValidEmailFull(str) {
55
+ return CONST_REGEXP_EMAIL_FULL.test(str);
56
+ }
57
+
58
+ const CONST_REGEXP_IDNEMAIL = /^[^@]+@[^@]+\.[^@]+$/;
59
+ export function isValidIdnEmail(str) {
60
+ return CONST_REGEXP_IDNEMAIL.test(str);
61
+ }