@jarenjs/validate 0.8.4 → 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 (48) hide show
  1. package/ARCHITECTURE.md +1067 -0
  2. package/LICENSE +21 -0
  3. package/README.md +339 -2
  4. package/dist/types/array.d.ts +2 -0
  5. package/dist/types/bigint.d.ts +1 -0
  6. package/dist/types/combine.d.ts +1 -0
  7. package/dist/types/condition.d.ts +1 -0
  8. package/dist/types/content.d.ts +3 -0
  9. package/dist/types/data.d.ts +7 -0
  10. package/dist/types/dollar-data.d.ts +20 -0
  11. package/dist/types/dynamic-ref.d.ts +44 -0
  12. package/dist/types/enum.d.ts +1 -0
  13. package/dist/types/format.d.ts +21 -0
  14. package/dist/types/index.d.ts +874 -0
  15. package/dist/types/number.d.ts +1 -0
  16. package/dist/types/object.d.ts +3 -0
  17. package/dist/types/query-keyword.d.ts +19 -0
  18. package/dist/types/query.d.ts +29 -0
  19. package/dist/types/schema.d.ts +1 -0
  20. package/dist/types/string.d.ts +1 -0
  21. package/dist/types/tools.d.ts +51 -0
  22. package/dist/types/traverse.d.ts +32 -0
  23. package/dist/types/unevaluated.d.ts +12 -0
  24. package/package.json +32 -7
  25. package/src/array.js +565 -0
  26. package/src/bigint.js +97 -0
  27. package/src/combine.js +226 -0
  28. package/src/condition.js +109 -0
  29. package/src/content.js +83 -0
  30. package/src/data.js +477 -0
  31. package/src/dollar-data.js +629 -0
  32. package/src/dynamic-ref.js +121 -0
  33. package/src/enum.js +148 -0
  34. package/src/format.js +66 -0
  35. package/src/index.js +1854 -0
  36. package/src/number.js +159 -0
  37. package/src/object.js +755 -0
  38. package/src/query-keyword.js +99 -0
  39. package/src/query.js +59 -0
  40. package/src/schema.js +645 -0
  41. package/src/string.js +152 -0
  42. package/src/tools.js +205 -0
  43. package/src/traverse.js +433 -0
  44. package/src/unevaluated.js +151 -0
  45. package/dist/index.js +0 -1998
  46. package/dist/index.js.map +0 -7
  47. package/dist/index.min.js +0 -2
  48. package/dist/index.min.js.map +0 -7
package/src/string.js ADDED
@@ -0,0 +1,152 @@
1
+ //@ts-check
2
+
3
+ import {
4
+ isStringType,
5
+ getStringType,
6
+ } from '@jarenjs/core';
7
+
8
+ import {
9
+ getIntishType,
10
+ } from '@jarenjs/core/number';
11
+
12
+ import {
13
+ createRegExp,
14
+ getStringLength,
15
+ getSegmenter,
16
+ } from '@jarenjs/core/string';
17
+
18
+ import {
19
+ trueThat,
20
+ } from '@jarenjs/core/function';
21
+
22
+ function compileMinLength(schemaObj, jsonSchema) {
23
+ const min = getIntishType(jsonSchema.minLength) || 0;
24
+ if (min < 1) return undefined;
25
+
26
+ const addError = schemaObj.createErrorHandler(min, 'minLength');
27
+
28
+ return function validateIsMinLength(len = 0, dataPath) {
29
+ return len >= min
30
+ || addError(len, dataPath);
31
+ };
32
+ }
33
+
34
+ function compileMaxLength(schemaObj, jsonSchema) {
35
+ const max = getIntishType(jsonSchema.maxLength) || -1;
36
+ if (max < 0) return undefined;
37
+
38
+ const addError = schemaObj.createErrorHandler(max, 'maxLength');
39
+
40
+ return function validateIsMaxLength(len = 0, dataPath) {
41
+ return len <= max
42
+ || addError(len, dataPath);
43
+ };
44
+ }
45
+
46
+ function compilePattern(schemaObj, jsonSchema) {
47
+ // Skip if pattern is a $data reference object (has $data property)
48
+ if (jsonSchema.pattern && typeof jsonSchema.pattern === 'object' && jsonSchema.pattern.$data) return undefined;
49
+
50
+ const pattern = createRegExp(jsonSchema.pattern);
51
+ if (pattern == null) return undefined;
52
+
53
+ const addError = schemaObj.createErrorHandler(pattern, 'pattern');
54
+
55
+ return function validateIsMatch(str = '', dataPath) {
56
+ return pattern.test(str)
57
+ || addError(str, dataPath);
58
+ };
59
+ }
60
+
61
+ function compileStringIntern(schemaObj, jsonSchema) {
62
+ const minLength = compileMinLength(schemaObj, jsonSchema);
63
+ const maxLength = compileMaxLength(schemaObj, jsonSchema);
64
+ const pattern = compilePattern(schemaObj, jsonSchema);
65
+
66
+ if ((minLength || maxLength || pattern) == null) return undefined;
67
+
68
+ const isMinLength = minLength || trueThat;
69
+ const isMaxLength = maxLength || trueThat;
70
+ const isMatch = pattern || trueThat;
71
+ const useGrapheme = schemaObj.options.useGrapheme;
72
+
73
+ return function validateStringIntern(data, dataPath) {
74
+ const len = getStringLength(data, useGrapheme);
75
+ return isMinLength(len, dataPath)
76
+ && isMaxLength(len, dataPath)
77
+ && isMatch(data, dataPath);
78
+ };
79
+ }
80
+
81
+ export function compileStringBasic(schemaObj, jsonSchema) {
82
+ const intern = compileStringIntern(schemaObj, jsonSchema);
83
+ if (intern == null) return undefined;
84
+
85
+ // Fast path for simple maxLength-only schemas (most common case)
86
+ // This inlines the validation to reduce function call overhead
87
+ const max = getIntishType(jsonSchema.maxLength) ?? -1;
88
+ const min = getIntishType(jsonSchema.minLength) || 0;
89
+ const hasPattern = jsonSchema.pattern != null;
90
+ const useGrapheme = schemaObj.options.useGrapheme;
91
+
92
+ if (!hasPattern) {
93
+ if (!useGrapheme) {
94
+ // Simple maxLength-only without grapheme counting
95
+ if (max >= 0 && min < 1) {
96
+ const addError = schemaObj.createErrorHandler(max, 'maxLength');
97
+ return function validateStringMaxLength(data, dataPath) {
98
+ if (!isStringType(data)) return true;
99
+ return data.length <= max || addError(data.length, dataPath);
100
+ };
101
+ }
102
+
103
+ // Simple minLength-only without grapheme counting
104
+ if (min > 0 && max < 0) {
105
+ const addError = schemaObj.createErrorHandler(min, 'minLength');
106
+ return function validateStringMinLength(data, dataPath) {
107
+ if (!isStringType(data)) return true;
108
+ return data.length >= min || addError(data.length, dataPath);
109
+ };
110
+ }
111
+ } else {
112
+ // Fast path WITH grapheme counting - inline the ASCII fast-path logic
113
+ // This avoids the function call overhead of getStringLength for ASCII strings
114
+ if (max >= 0 && min < 1) {
115
+ const addError = schemaObj.createErrorHandler(max, 'maxLength');
116
+ return function validateStringMaxLengthGrapheme(data, dataPath) {
117
+ if (!isStringType(data)) return true;
118
+ const len = getStringLength(data, true);
119
+ return len <= max || addError(len, dataPath);
120
+ };
121
+ }
122
+
123
+ if (min > 0 && max < 0) {
124
+ const addError = schemaObj.createErrorHandler(min, 'minLength');
125
+ return function validateStringMinLengthGrapheme(data, dataPath) {
126
+ if (!isStringType(data)) return true;
127
+ const len = getStringLength(data, true);
128
+ return len >= min || addError(len, dataPath);
129
+ };
130
+ }
131
+ }
132
+ }
133
+
134
+ // Fast path for pattern-only schemas (common in ecmascript-regex tests)
135
+ // This eliminates the intermediate function call overhead
136
+ if (hasPattern && max < 0 && min < 1) {
137
+ const pattern = createRegExp(jsonSchema.pattern);
138
+ if (pattern != null) {
139
+ const addError = schemaObj.createErrorHandler(pattern, 'pattern');
140
+ return function validateStringPatternOnly(data, dataPath) {
141
+ if (!isStringType(data)) return true;
142
+ return pattern.test(data) || addError(data, dataPath);
143
+ };
144
+ }
145
+ }
146
+
147
+ // Generic case
148
+ return function validateStringBasic(data, dataPath) {
149
+ return !isStringType(data)
150
+ || intern(data, dataPath);
151
+ };
152
+ }
package/src/tools.js ADDED
@@ -0,0 +1,205 @@
1
+ //@ts-check
2
+
3
+ import {
4
+ isBigIntType,
5
+ isNullValue,
6
+ isBooleanType,
7
+ isIntegerType,
8
+ isNumberType,
9
+ isStringType,
10
+ isObjectClass,
11
+ isObjectType,
12
+ isMapClass,
13
+ isArrayClass,
14
+ isSetClass,
15
+ } from '@jarenjs/core';
16
+
17
+ import {
18
+ isRegExpType,
19
+ isStringWhiteSpace,
20
+ } from '@jarenjs/core/string';
21
+
22
+ import {
23
+ isArrayish,
24
+ } from '@jarenjs/core/array';
25
+
26
+ //#region Object
27
+ export function isBoolOrObjectClass(obj) {
28
+ return isBooleanType(obj)
29
+ || isObjectClass(obj);
30
+ }
31
+
32
+ /**
33
+ * getBoolOrObjectClass
34
+ * extract the data of first parameter if data is boolean or object type otherwise return default
35
+ * @param {any} obj any data data has to be tested on boolean or object type
36
+ * @param {boolean | object | undefined} def default return type if not boolean or object type
37
+ * @returns {boolean | undefined} return value when boolean or object otherwise def
38
+ */
39
+ export function getBoolOrObjectClass(obj, def = undefined) {
40
+ return isBoolOrObjectClass(obj) ? obj : def;
41
+ }
42
+
43
+ export function getArrayClassMinItems(obj, len = 1, def = undefined) {
44
+ return (isArrayClass(obj) && obj.length >= len && obj) || def;
45
+ }
46
+ //#endregion
47
+
48
+ //#region Schema Helpers
49
+ export function isOfSchemaType(schema, type) {
50
+ const stype = schema.type;
51
+ if (stype == null) return false;
52
+ if (stype === type) return true;
53
+ if (stype.constructor === Array) {
54
+ return stype.includes(type);
55
+ }
56
+ if (stype.constructor === Set) {
57
+ return stype.has(type);
58
+ }
59
+ return false;
60
+ }
61
+
62
+ export function hasSchemaRef(schema) {
63
+ return isObjectClass(schema)
64
+ && isStringType(schema.$ref)
65
+ && !isStringWhiteSpace(schema.$ref);
66
+ }
67
+
68
+ export function hasSchemaRecursiveRef(schema) {
69
+ return isObjectClass(schema)
70
+ && isStringType(schema.$recursiveRef)
71
+ && !isStringWhiteSpace(schema.$recursiveRef);
72
+ }
73
+
74
+ export function hasSchemaDynamicRef(schema) {
75
+ return isObjectClass(schema)
76
+ && isStringType(schema.$dynamicRef)
77
+ && !isStringWhiteSpace(schema.$dynamicRef);
78
+ }
79
+
80
+ export function createIsSchemaTypeHandler(type, isStrict = false) {
81
+ switch (type) {
82
+ case 'null': return isNullValue;
83
+ case 'boolean': return isBooleanType;
84
+ case 'integer': return isIntegerType;
85
+ case 'bigint': return isBigIntType;
86
+ case 'number': return isNumberType;
87
+ case 'string': return isStringType;
88
+ case 'object': return isStrict
89
+ ? isObjectClass
90
+ : isObjectType;
91
+ case 'array': return isStrict
92
+ ? isArrayish
93
+ : isArrayClass;
94
+ case 'set': return isSetClass;
95
+ case 'map': return isMapClass;
96
+ case 'tuple': return isArrayClass;
97
+ case 'regex': return isRegExpType;
98
+ default: break;
99
+ }
100
+
101
+ if (type === null)
102
+ return isNullValue;
103
+
104
+ if (typeof type === 'function')
105
+ throw new Error('This is interesting!');
106
+
107
+ return undefined;
108
+ }
109
+
110
+ //#endregion
111
+
112
+ /**
113
+ * Records which properties (string keys) and items (numeric indexes) of a
114
+ * data instance were successfully evaluated during validation, so that
115
+ * unevaluatedProperties/unevaluatedItems can be checked afterwards.
116
+ *
117
+ * Entries are (data reference, key) pairs appended in application order.
118
+ * Applicators that discard annotations (failed anyOf/oneOf branches, not,
119
+ * failed if) take a mark() before running and rollback(mark) afterwards.
120
+ * The numeric key -1 means "all items of this array were evaluated".
121
+ */
122
+ export class EvalLog {
123
+ #data = [];
124
+ #keys = [];
125
+ #len = 0;
126
+
127
+ /** Clears the log; called at the start of each root validation. */
128
+ reset() {
129
+ this.#data.length = 0;
130
+ this.#keys.length = 0;
131
+ this.#len = 0;
132
+ }
133
+
134
+ /** @returns {number} The current log position */
135
+ mark() {
136
+ return this.#len;
137
+ }
138
+
139
+ /** Discards all entries recorded after the given mark. */
140
+ rollback(mark) {
141
+ this.#len = mark;
142
+ }
143
+
144
+ /** Records that `key` of instance `data` was evaluated. */
145
+ add(data, key) {
146
+ this.#data[this.#len] = data;
147
+ this.#keys[this.#len] = key;
148
+ this.#len++;
149
+ }
150
+
151
+ /** @returns {boolean} True when property `key` of `data` was evaluated at or after `from` */
152
+ hasKey(data, key, from) {
153
+ const len = this.#len;
154
+ const datas = this.#data;
155
+ const keys = this.#keys;
156
+ for (let i = from; i < len; ++i) {
157
+ if (datas[i] === data && keys[i] === key) return true;
158
+ }
159
+ return false;
160
+ }
161
+
162
+ /** @returns {boolean} True when item `index` of `data` was evaluated at or after `from` (-1 entries cover all items) */
163
+ hasItem(data, index, from) {
164
+ const len = this.#len;
165
+ const datas = this.#data;
166
+ const keys = this.#keys;
167
+ for (let i = from; i < len; ++i) {
168
+ if (datas[i] === data) {
169
+ const k = keys[i];
170
+ if (k === index || k === -1) return true;
171
+ }
172
+ }
173
+ return false;
174
+ }
175
+ }
176
+
177
+ export class ValidationResult {
178
+ static undefThat() {
179
+ return new ValidationResult();
180
+ }
181
+
182
+ constructor(match = false, errors = 0) {
183
+ this.match = match;
184
+ this.errors = Number(errors);
185
+ }
186
+
187
+ addValid(valid = true) {
188
+ if (valid === false)// this.errors += valid|0
189
+ this.errors++;
190
+ return this;
191
+ }
192
+
193
+ addMatch(valid = true) {
194
+ this.match = true;
195
+ if (valid === false)// this.errors += valid|0
196
+ this.errors++;
197
+ return this;
198
+ }
199
+
200
+ addResult(result = new ValidationResult()) {
201
+ this.match = this.match || result.match;
202
+ this.errors += result.errors;
203
+ return this;
204
+ }
205
+ }