z-schema 10.0.0 → 12.0.0

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 (138) hide show
  1. package/README.md +35 -17
  2. package/cjs/index.d.ts +345 -34
  3. package/cjs/index.js +4446 -1685
  4. package/dist/errors.js +5 -0
  5. package/dist/format-validators.js +131 -107
  6. package/dist/json-schema-versions.js +4 -1
  7. package/dist/json-schema.js +50 -16
  8. package/dist/json-validation.js +524 -669
  9. package/dist/report.js +37 -16
  10. package/dist/schema-cache.js +76 -18
  11. package/dist/schema-compiler.js +72 -47
  12. package/dist/schema-validator.js +117 -52
  13. package/dist/schemas/draft-07-schema.json +172 -0
  14. package/dist/schemas/draft-2019-09-meta-applicator.json +52 -0
  15. package/dist/schemas/draft-2019-09-meta-content.json +12 -0
  16. package/dist/schemas/draft-2019-09-meta-core.json +53 -0
  17. package/dist/schemas/draft-2019-09-meta-format.json +10 -0
  18. package/dist/schemas/draft-2019-09-meta-meta-data.json +32 -0
  19. package/dist/schemas/draft-2019-09-meta-validation.json +94 -0
  20. package/dist/schemas/draft-2019-09-schema.json +41 -0
  21. package/dist/schemas/draft-2020-12-meta-applicator.json +44 -0
  22. package/dist/schemas/draft-2020-12-meta-content.json +12 -0
  23. package/dist/schemas/draft-2020-12-meta-core.json +47 -0
  24. package/dist/schemas/draft-2020-12-meta-format-annotation.json +10 -0
  25. package/dist/schemas/draft-2020-12-meta-format-assertion.json +10 -0
  26. package/dist/schemas/draft-2020-12-meta-meta-data.json +32 -0
  27. package/dist/schemas/draft-2020-12-meta-unevaluated.json +11 -0
  28. package/dist/schemas/draft-2020-12-meta-validation.json +94 -0
  29. package/dist/schemas/draft-2020-12-schema.json +57 -0
  30. package/dist/types/errors.d.ts +4 -0
  31. package/dist/types/index.d.ts +2 -1
  32. package/dist/types/json-schema-versions.d.ts +128 -9
  33. package/dist/types/json-schema.d.ts +28 -11
  34. package/dist/types/json-validation.d.ts +2 -3
  35. package/dist/types/report.d.ts +14 -4
  36. package/dist/types/schema-cache.d.ts +7 -0
  37. package/dist/types/schema-compiler.d.ts +5 -3
  38. package/dist/types/schema-validator.d.ts +2 -2
  39. package/dist/types/utils/array.d.ts +8 -1
  40. package/dist/types/utils/base64.d.ts +2 -0
  41. package/dist/types/utils/clone.d.ts +1 -1
  42. package/dist/types/utils/date.d.ts +1 -0
  43. package/dist/types/utils/hostname.d.ts +2 -0
  44. package/dist/types/utils/json.d.ts +2 -1
  45. package/dist/types/utils/properties.d.ts +0 -1
  46. package/dist/types/utils/time.d.ts +12 -0
  47. package/dist/types/utils/unicode.d.ts +3 -12
  48. package/dist/types/validation/array.d.ts +12 -0
  49. package/dist/types/validation/combinators.d.ts +10 -0
  50. package/dist/types/validation/numeric.d.ts +8 -0
  51. package/dist/types/validation/object.d.ts +13 -0
  52. package/dist/types/validation/ref.d.ts +11 -0
  53. package/dist/types/validation/shared.d.ts +26 -0
  54. package/dist/types/validation/string.d.ts +9 -0
  55. package/dist/types/validation/type.d.ts +6 -0
  56. package/dist/types/z-schema-base.d.ts +39 -1
  57. package/dist/types/z-schema-options.d.ts +3 -0
  58. package/dist/types/z-schema.d.ts +144 -8
  59. package/dist/utils/array.js +49 -7
  60. package/dist/utils/base64.js +29 -0
  61. package/dist/utils/clone.js +13 -12
  62. package/dist/utils/date.js +21 -0
  63. package/dist/utils/hostname.js +146 -0
  64. package/dist/utils/json.js +11 -6
  65. package/dist/utils/properties.js +1 -6
  66. package/dist/utils/time.js +50 -0
  67. package/dist/utils/unicode.js +8 -41
  68. package/dist/utils/uri.js +1 -1
  69. package/dist/validation/array.js +128 -0
  70. package/dist/validation/combinators.js +107 -0
  71. package/dist/validation/numeric.js +97 -0
  72. package/dist/validation/object.js +238 -0
  73. package/dist/validation/ref.js +70 -0
  74. package/dist/validation/shared.js +136 -0
  75. package/dist/validation/string.js +178 -0
  76. package/dist/validation/type.js +55 -0
  77. package/dist/z-schema-base.js +52 -32
  78. package/dist/z-schema-options.js +12 -8
  79. package/dist/z-schema-versions.js +92 -9
  80. package/dist/z-schema.js +135 -38
  81. package/package.json +22 -8
  82. package/src/errors.ts +8 -0
  83. package/src/format-validators.ts +146 -105
  84. package/src/index.ts +10 -1
  85. package/src/json-schema-versions.ts +181 -11
  86. package/src/json-schema.ts +102 -35
  87. package/src/json-validation.ts +653 -724
  88. package/src/report.ts +42 -20
  89. package/src/schema-cache.ts +94 -18
  90. package/src/schema-compiler.ts +94 -51
  91. package/src/schema-validator.ts +132 -56
  92. package/src/schemas/draft-07-schema.json +172 -0
  93. package/src/schemas/draft-2019-09-meta-applicator.json +53 -0
  94. package/src/schemas/draft-2019-09-meta-content.json +14 -0
  95. package/src/schemas/draft-2019-09-meta-core.json +54 -0
  96. package/src/schemas/draft-2019-09-meta-format.json +11 -0
  97. package/src/schemas/draft-2019-09-meta-meta-data.json +34 -0
  98. package/src/schemas/draft-2019-09-meta-validation.json +95 -0
  99. package/src/schemas/draft-2019-09-schema.json +42 -0
  100. package/src/schemas/draft-2020-12-meta-applicator.json +45 -0
  101. package/src/schemas/draft-2020-12-meta-content.json +14 -0
  102. package/src/schemas/draft-2020-12-meta-core.json +48 -0
  103. package/src/schemas/draft-2020-12-meta-format-annotation.json +11 -0
  104. package/src/schemas/draft-2020-12-meta-format-assertion.json +11 -0
  105. package/src/schemas/draft-2020-12-meta-meta-data.json +34 -0
  106. package/src/schemas/draft-2020-12-meta-unevaluated.json +12 -0
  107. package/src/schemas/draft-2020-12-meta-validation.json +95 -0
  108. package/src/schemas/draft-2020-12-schema.json +58 -0
  109. package/src/utils/array.ts +51 -7
  110. package/src/utils/base64.ts +32 -0
  111. package/src/utils/clone.ts +16 -12
  112. package/src/utils/date.ts +23 -0
  113. package/src/utils/hostname.ts +174 -0
  114. package/src/utils/json.ts +15 -6
  115. package/src/utils/properties.ts +1 -7
  116. package/src/utils/time.ts +73 -0
  117. package/src/utils/unicode.ts +8 -39
  118. package/src/utils/uri.ts +1 -1
  119. package/src/validation/array.ts +158 -0
  120. package/src/validation/combinators.ts +132 -0
  121. package/src/validation/numeric.ts +120 -0
  122. package/src/validation/object.ts +318 -0
  123. package/src/validation/ref.ts +85 -0
  124. package/src/validation/shared.ts +191 -0
  125. package/src/validation/string.ts +224 -0
  126. package/src/validation/type.ts +66 -0
  127. package/src/z-schema-base.ts +54 -36
  128. package/src/z-schema-options.ts +15 -8
  129. package/src/z-schema-versions.ts +107 -12
  130. package/src/z-schema.ts +158 -42
  131. package/umd/ZSchema.js +4446 -1685
  132. package/umd/ZSchema.min.js +1 -1
  133. package/dist/schemas/draft-04-hyper-schema.json +0 -135
  134. package/dist/schemas/draft-06-hyper-schema.json +0 -132
  135. package/dist/schemas/draft-06-links.json +0 -43
  136. package/src/schemas/draft-04-hyper-schema.json +0 -136
  137. package/src/schemas/draft-06-hyper-schema.json +0 -133
  138. package/src/schemas/draft-06-links.json +0 -43
@@ -6,17 +6,60 @@ import type { SchemaReader } from './z-schema-reader.js';
6
6
  import './z-schema-versions.js';
7
7
  import { ZSchemaBase } from './z-schema-base.js';
8
8
  export declare class ZSchema extends ZSchemaBase {
9
- /** @deprecated Use ZSchema.create() instead. */
10
- private constructor();
9
+ /** @internal Use ZSchema.create() instead. */
10
+ constructor(options: ZSchemaOptions | undefined, token: symbol);
11
+ /**
12
+ * Register a global format validator available to all instances.
13
+ * @param name - The format name (e.g. `'email'`, `'date'`).
14
+ * @param validatorFunction - A sync or async function `(value: unknown) => boolean | Promise<boolean>`.
15
+ */
11
16
  static registerFormat(name: string, validatorFunction: FormatValidatorFn): void;
17
+ /**
18
+ * Remove a globally registered format validator.
19
+ * @param name - The format name to unregister.
20
+ */
12
21
  static unregisterFormat(name: string): void;
22
+ /** Returns the names of all globally registered format validators. */
13
23
  static getRegisteredFormats(): string[];
24
+ /** Returns a deep clone of the default options. */
14
25
  static getDefaultOptions(): ZSchemaOptions;
26
+ /**
27
+ * Register a remote schema in the global cache so any instance can resolve `$ref` to it.
28
+ * @param uri - The URI the schema will be known by.
29
+ * @param schema - The schema object or JSON string.
30
+ * @param validationOptions - Optional options used for schema preparation.
31
+ */
15
32
  static setRemoteReference(uri: string, schema: string | JsonSchema, validationOptions?: ZSchemaOptions): void;
33
+ /** Returns the current global schema reader, or `undefined` if none is set. */
16
34
  static getSchemaReader(): SchemaReader | undefined;
35
+ /**
36
+ * Set a global schema reader function used to resolve remote `$ref` URIs.
37
+ * @param schemaReader - A function `(uri: string) => JsonSchema | undefined`, or `undefined` to clear.
38
+ */
17
39
  static setSchemaReader(schemaReader: SchemaReader | undefined): void;
18
40
  static schemaSymbol: symbol;
19
41
  static jsonSymbol: symbol;
42
+ /**
43
+ * Create a validator instance.
44
+ *
45
+ * The returned type depends on the `async` and `safe` options:
46
+ * - `{}` → `ZSchema` — `validate()` returns `true` or throws.
47
+ * - `{ safe: true }` → `ZSchemaSafe` — `validate()` returns `{ valid, err? }`.
48
+ * - `{ async: true }` → `ZSchemaAsync` — `validate()` returns `Promise<true>` or rejects.
49
+ * - `{ async: true, safe: true }` → `ZSchemaAsyncSafe` — `validate()` returns `Promise<{ valid, err? }>`.
50
+ *
51
+ * @param options - Validator configuration. See `ZSchemaOptions` for all available settings.
52
+ * @returns A validator instance of the appropriate variant.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const validator = ZSchema.create();
57
+ * validator.validate(data, schema); // throws on error
58
+ *
59
+ * const safe = ZSchema.create({ safe: true });
60
+ * const result = safe.validate(data, schema); // { valid, err? }
61
+ * ```
62
+ */
20
63
  static create(options: ZSchemaOptions & {
21
64
  async: true;
22
65
  safe: true;
@@ -28,28 +71,121 @@ export declare class ZSchema extends ZSchemaBase {
28
71
  safe: true;
29
72
  }): ZSchemaSafe;
30
73
  static create(options?: ZSchemaOptions): ZSchema;
74
+ /**
75
+ * Validate JSON data against a schema.
76
+ * @param json - The data to validate.
77
+ * @param schema - A JSON Schema object or a schema id string (previously registered via `validateSchema`).
78
+ * @param options - Per-call options (`schemaPath`, `includeErrors`, `excludeErrors`).
79
+ * @returns `true` if valid.
80
+ * @throws {@link ValidateError} if validation fails, with a `details` array of structured errors.
81
+ */
31
82
  validate(json: unknown, schema: JsonSchema | string, options?: ValidateOptions): true;
83
+ /**
84
+ * Validate JSON data against a schema, returning a result object instead of throwing.
85
+ * @param json - The data to validate.
86
+ * @param schema - A JSON Schema object or a schema id string.
87
+ * @param options - Per-call options.
88
+ * @returns `{ valid: true }` on success, or `{ valid: false, err: ValidateError }` on failure.
89
+ */
32
90
  validateSafe(json: unknown, schema: JsonSchema | string, options?: ValidateOptions): ValidateResponse;
91
+ /**
92
+ * Validate JSON data against a schema asynchronously (supports async format validators).
93
+ * @param json - The data to validate.
94
+ * @param schema - A JSON Schema object or a schema id string.
95
+ * @param options - Per-call options.
96
+ * @returns A promise that resolves to `true` if valid.
97
+ * @throws {@link ValidateError} if validation fails (the promise rejects).
98
+ */
33
99
  validateAsync(json: unknown, schema: JsonSchema | string, options?: ValidateOptions): Promise<true>;
100
+ /**
101
+ * Validate JSON data against a schema asynchronously, returning a result object.
102
+ * The promise always resolves (never rejects).
103
+ * @param json - The data to validate.
104
+ * @param schema - A JSON Schema object or a schema id string.
105
+ * @param options - Per-call options.
106
+ * @returns A promise resolving to `{ valid: true }` or `{ valid: false, err: ValidateError }`.
107
+ */
34
108
  validateAsyncSafe(json: unknown, schema: JsonSchema | string, options?: ValidateOptions): Promise<ValidateResponse>;
109
+ /**
110
+ * Validate one or more JSON Schemas, compiling and caching them for later use with `validate()`.
111
+ * @param schemaOrArr - A single schema or an array of schemas (for cross-referencing).
112
+ * @returns `true` if all schemas are valid.
113
+ * @throws {@link ValidateError} if any schema is invalid.
114
+ */
35
115
  validateSchema(schemaOrArr: JsonSchema | JsonSchema[]): true;
116
+ /**
117
+ * Validate one or more JSON Schemas, returning a result object instead of throwing.
118
+ * @param schemaOrArr - A single schema or an array of schemas.
119
+ * @returns `{ valid: true }` on success, or `{ valid: false, err: ValidateError }` on failure.
120
+ */
36
121
  validateSchemaSafe(schemaOrArr: JsonSchema | JsonSchema[]): ValidateResponse;
37
122
  }
123
+ /**
124
+ * Synchronous safe validator — `validate()` returns `{ valid, err? }` instead of throwing.
125
+ * Created via `ZSchema.create({ safe: true })`.
126
+ */
38
127
  export declare class ZSchemaSafe extends ZSchemaBase {
39
- /** @deprecated Use ZSchema.create() instead. */
40
- private constructor();
128
+ /** @internal Use ZSchema.create() instead. */
129
+ constructor(options: ZSchemaOptions | undefined, token: symbol);
130
+ /**
131
+ * Validate JSON data against a schema.
132
+ * @param json - The data to validate.
133
+ * @param schema - A JSON Schema object or a schema id string.
134
+ * @param options - Per-call options.
135
+ * @returns `{ valid: true }` on success, or `{ valid: false, err: ValidateError }` on failure.
136
+ */
41
137
  validate(json: unknown, schema: JsonSchema | string, options?: ValidateOptions): ValidateResponse;
138
+ /**
139
+ * Validate one or more JSON Schemas.
140
+ * @param schemaOrArr - A single schema or an array of schemas.
141
+ * @returns `{ valid: true }` on success, or `{ valid: false, err: ValidateError }` on failure.
142
+ */
42
143
  validateSchema(schemaOrArr: JsonSchema | JsonSchema[]): ValidateResponse;
43
144
  }
145
+ /**
146
+ * Asynchronous throw validator — `validate()` returns `Promise<true>` or rejects.
147
+ * Created via `ZSchema.create({ async: true })`.
148
+ */
44
149
  export declare class ZSchemaAsync extends ZSchemaBase {
45
- /** @deprecated Use ZSchema.create() instead. */
46
- private constructor();
150
+ /** @internal Use ZSchema.create() instead. */
151
+ constructor(options: ZSchemaOptions | undefined, token: symbol);
152
+ /**
153
+ * Validate JSON data against a schema asynchronously.
154
+ * @param json - The data to validate.
155
+ * @param schema - A JSON Schema object or a schema id string.
156
+ * @param options - Per-call options.
157
+ * @returns A promise that resolves to `true` if valid.
158
+ * @throws {@link ValidateError} if validation fails (the promise rejects).
159
+ */
47
160
  validate(json: unknown, schema: JsonSchema | string, options?: ValidateOptions): Promise<true>;
161
+ /**
162
+ * Validate one or more JSON Schemas (synchronous, throws on error).
163
+ * @param schemaOrArr - A single schema or an array of schemas.
164
+ * @returns `true` if all schemas are valid.
165
+ * @throws {@link ValidateError} if any schema is invalid.
166
+ */
48
167
  validateSchema(schemaOrArr: JsonSchema | JsonSchema[]): true;
49
168
  }
169
+ /**
170
+ * Asynchronous safe validator — `validate()` returns `Promise<{ valid, err? }>` (never rejects).
171
+ * Created via `ZSchema.create({ async: true, safe: true })`.
172
+ */
50
173
  export declare class ZSchemaAsyncSafe extends ZSchemaBase {
51
- /** @deprecated Use ZSchema.create() instead. */
52
- private constructor();
174
+ /** @internal Use ZSchema.create() instead. */
175
+ constructor(options: ZSchemaOptions | undefined, token: symbol);
176
+ /**
177
+ * Validate JSON data against a schema asynchronously.
178
+ * The promise always resolves (never rejects).
179
+ * @param json - The data to validate.
180
+ * @param schema - A JSON Schema object or a schema id string.
181
+ * @param options - Per-call options.
182
+ * @returns A promise resolving to `{ valid: true }` or `{ valid: false, err: ValidateError }`.
183
+ */
53
184
  validate(json: unknown, schema: JsonSchema | string, options?: ValidateOptions): Promise<ValidateResponse>;
185
+ /**
186
+ * Validate one or more JSON Schemas.
187
+ * @param schemaOrArr - A single schema or an array of schemas.
188
+ * @returns `{ valid: true }` on success, or `{ valid: false, err: ValidateError }` on failure.
189
+ */
54
190
  validateSchema(schemaOrArr: JsonSchema | JsonSchema[]): ValidateResponse;
55
191
  }
@@ -1,11 +1,52 @@
1
1
  import { areEqual } from './json.js';
2
- export const isUniqueArray = (arr, indexes) => {
3
- let i;
4
- let j;
2
+ /**
3
+ * Check if all elements in an array are unique.
4
+ *
5
+ * Uses a Set-based fast path for arrays of pure primitives (O(n)).
6
+ * Falls back to pairwise deep comparison (O(n²)) when the array contains
7
+ * objects or arrays that need structural equality checks.
8
+ */
9
+ export const isUniqueArray = (arr, indexes, maxDepth) => {
5
10
  const l = arr.length;
6
- for (i = 0; i < l; i++) {
7
- for (j = i + 1; j < l; j++) {
8
- if (areEqual(arr[i], arr[j])) {
11
+ if (l <= 1)
12
+ return true;
13
+ // Fast path: if every element is a primitive, use a Set.
14
+ // We distinguish types so that e.g. 1 !== '1' and 0 !== false.
15
+ let allPrimitive = true;
16
+ for (let i = 0; i < l; i++) {
17
+ const v = arr[i];
18
+ if (v !== null && typeof v === 'object') {
19
+ allPrimitive = false;
20
+ break;
21
+ }
22
+ }
23
+ if (allPrimitive) {
24
+ // Prefix each value with its type so "1" (number) !== "1" (string).
25
+ const seen = new Set();
26
+ for (let i = 0; i < l; i++) {
27
+ const v = arr[i];
28
+ const key = typeof v + ':' + String(v);
29
+ if (seen.has(key)) {
30
+ // Find the first occurrence for the indexes report.
31
+ if (indexes) {
32
+ for (let j = 0; j < i; j++) {
33
+ const prev = arr[j];
34
+ if (typeof prev === typeof v && prev === v) {
35
+ indexes.push(j, i);
36
+ break;
37
+ }
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+ seen.add(key);
43
+ }
44
+ return true;
45
+ }
46
+ // Slow path: at least one element is an object/array — need deep comparison.
47
+ for (let i = 0; i < l; i++) {
48
+ for (let j = i + 1; j < l; j++) {
49
+ if (areEqual(arr[i], arr[j], { maxDepth })) {
9
50
  if (indexes) {
10
51
  indexes.push(i, j);
11
52
  }
@@ -16,10 +57,11 @@ export const isUniqueArray = (arr, indexes) => {
16
57
  return true;
17
58
  };
18
59
  export const difference = (bigSet, subSet) => {
60
+ const exclusions = new Set(subSet);
19
61
  const arr = [];
20
62
  let idx = bigSet.length;
21
63
  while (idx--) {
22
- if (subSet.indexOf(bigSet[idx]) === -1) {
64
+ if (!exclusions.has(bigSet[idx])) {
23
65
  arr.push(bigSet[idx]);
24
66
  }
25
67
  }
@@ -0,0 +1,29 @@
1
+ const base64Pattern = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
2
+ export const isValidBase64 = (value) => {
3
+ if (value.length % 4 !== 0) {
4
+ return false;
5
+ }
6
+ return base64Pattern.test(value);
7
+ };
8
+ export const decodeBase64 = (value) => {
9
+ if (!isValidBase64(value)) {
10
+ return undefined;
11
+ }
12
+ if (typeof atob === 'function') {
13
+ try {
14
+ return atob(value);
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ }
20
+ if (typeof Buffer !== 'undefined') {
21
+ try {
22
+ return Buffer.from(value, 'base64').toString('utf8');
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ return undefined;
29
+ };
@@ -1,15 +1,14 @@
1
+ import { DEFAULT_MAX_RECURSION_DEPTH } from '../z-schema-options.js';
1
2
  import { copyProp } from './properties.js';
2
3
  export const shallowClone = (src) => {
3
4
  if (src == null || typeof src !== 'object') {
4
5
  return src;
5
6
  }
6
7
  let res;
7
- let idx;
8
8
  if (Array.isArray(src)) {
9
9
  res = [];
10
- idx = src.length;
11
- while (idx--) {
12
- res[idx] = src[idx];
10
+ for (let i = 0; i < src.length; i++) {
11
+ res[i] = src[i];
13
12
  }
14
13
  }
15
14
  else {
@@ -21,16 +20,19 @@ export const shallowClone = (src) => {
21
20
  }
22
21
  return res;
23
22
  };
24
- export const deepClone = (src) => {
23
+ export const deepClone = (src, maxDepth = DEFAULT_MAX_RECURSION_DEPTH) => {
25
24
  let vidx = 0;
26
25
  const visited = new Map();
27
26
  const cloned = [];
28
- const cloneDeepInner = (src) => {
27
+ const cloneDeepInner = (src, _depth) => {
29
28
  if (typeof src !== 'object' || src === null) {
30
29
  return src;
31
30
  }
31
+ if (_depth >= maxDepth) {
32
+ throw new Error(`Maximum recursion depth (${maxDepth}) exceeded in deepClone. ` +
33
+ 'If your schema or data is deeply nested and valid, increase the maxRecursionDepth option.');
34
+ }
32
35
  let res;
33
- let idx;
34
36
  const cidx = visited.get(src);
35
37
  if (cidx !== undefined) {
36
38
  return cloned[cidx];
@@ -39,9 +41,8 @@ export const deepClone = (src) => {
39
41
  if (Array.isArray(src)) {
40
42
  res = [];
41
43
  cloned.push(res);
42
- idx = src.length;
43
- while (idx--) {
44
- res[idx] = cloneDeepInner(src[idx]);
44
+ for (let i = 0; i < src.length; i++) {
45
+ res[i] = cloneDeepInner(src[i], _depth + 1);
45
46
  }
46
47
  }
47
48
  else {
@@ -49,10 +50,10 @@ export const deepClone = (src) => {
49
50
  cloned.push(res);
50
51
  const keys = Object.keys(src).sort();
51
52
  for (const key of keys) {
52
- copyProp(src, res, key, cloneDeepInner);
53
+ copyProp(src, res, key, (v) => cloneDeepInner(v, _depth + 1));
53
54
  }
54
55
  }
55
56
  return res;
56
57
  };
57
- return cloneDeepInner(src);
58
+ return cloneDeepInner(src, 0);
58
59
  };
@@ -0,0 +1,21 @@
1
+ const isLeapYear = (year) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
2
+ const getDaysInMonth = (year, month) => {
3
+ switch (month) {
4
+ case 2:
5
+ return isLeapYear(year) ? 29 : 28;
6
+ case 4:
7
+ case 6:
8
+ case 9:
9
+ case 11:
10
+ return 30;
11
+ default:
12
+ return 31;
13
+ }
14
+ };
15
+ export const isValidRfc3339Date = (year, month, day) => {
16
+ if (month < 1 || month > 12) {
17
+ return false;
18
+ }
19
+ const maxDay = getDaysInMonth(year, month);
20
+ return day >= 1 && day <= maxDay;
21
+ };
@@ -0,0 +1,146 @@
1
+ import punycode from 'punycode/punycode.js';
2
+ import isIPModule from 'validator/lib/isIP.js';
3
+ const IDN_SEPARATOR_REGEX = /[\u3002\uff0e\uff61]/g;
4
+ const IDN_SEPARATOR_TEST_REGEX = /[\u3002\uff0e\uff61]/;
5
+ const splitHostnameLabels = (hostname) => {
6
+ if (hostname.length === 0 || hostname.length > 255) {
7
+ return null;
8
+ }
9
+ if (hostname.startsWith('.') || hostname.endsWith('.')) {
10
+ return null;
11
+ }
12
+ const labels = hostname.split('.');
13
+ if (labels.some((label) => label.length === 0 || label.length > 63)) {
14
+ return null;
15
+ }
16
+ return labels;
17
+ };
18
+ const isAsciiHostnameLabel = (label) => /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(label);
19
+ const isGreek = (char) => /\p{Script=Greek}/u.test(char);
20
+ const isHebrew = (char) => /\p{Script=Hebrew}/u.test(char);
21
+ const hasCjkKanaOrHan = (input) => /[\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Han}]/u.test(input);
22
+ const toUnicodeLabel = (label) => {
23
+ if (!/^xn--/i.test(label)) {
24
+ return label;
25
+ }
26
+ try {
27
+ return punycode.toUnicode(label.toLowerCase());
28
+ }
29
+ catch (_e) {
30
+ return null;
31
+ }
32
+ };
33
+ const isValidIdnUnicodeLabel = (label) => {
34
+ if (label.startsWith('-') || label.endsWith('-')) {
35
+ return false;
36
+ }
37
+ if (label.length >= 4 && label[2] === '-' && label[3] === '-' && !/^xn--/i.test(label)) {
38
+ return false;
39
+ }
40
+ if (/^\p{M}/u.test(label)) {
41
+ return false;
42
+ }
43
+ if (/[\u302e\u302f\u0640\u07fa]/u.test(label)) {
44
+ return false;
45
+ }
46
+ for (let idx = 0; idx < label.length; idx++) {
47
+ const char = label[idx];
48
+ if (char === '\u00b7') {
49
+ if (idx === 0 || idx === label.length - 1 || label[idx - 1] !== 'l' || label[idx + 1] !== 'l') {
50
+ return false;
51
+ }
52
+ }
53
+ if (char === '\u0375') {
54
+ if (idx === label.length - 1 || !isGreek(label[idx + 1])) {
55
+ return false;
56
+ }
57
+ }
58
+ if (char === '\u05f3' || char === '\u05f4') {
59
+ if (idx === 0 || !isHebrew(label[idx - 1])) {
60
+ return false;
61
+ }
62
+ }
63
+ if (char === '\u200d') {
64
+ if (idx === 0 || label[idx - 1] !== '\u094d') {
65
+ return false;
66
+ }
67
+ }
68
+ }
69
+ if (label.includes('\u30fb') && !hasCjkKanaOrHan(label.replace(/\u30fb/g, ''))) {
70
+ return false;
71
+ }
72
+ const hasArabicIndic = /[\u0660-\u0669]/.test(label);
73
+ const hasExtendedArabicIndic = /[\u06f0-\u06f9]/.test(label);
74
+ if (hasArabicIndic && hasExtendedArabicIndic) {
75
+ return false;
76
+ }
77
+ return true;
78
+ };
79
+ /*
80
+ http://json-schema.org/latest/json-schema-validation.html#anchor114
81
+ A string instance is valid against this attribute if it is a valid
82
+ representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
83
+
84
+ http://tools.ietf.org/html/rfc1034#section-3.5
85
+
86
+ <digit> ::= any one of the ten digits 0 through 9
87
+ var digit = /[0-9]/;
88
+
89
+ <letter> ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
90
+ var letter = /[a-zA-Z]/;
91
+
92
+ <let-dig> ::= <letter> | <digit>
93
+ var letDig = /[0-9a-zA-Z]/;
94
+
95
+ <let-dig-hyp> ::= <let-dig> | "-"
96
+ var letDigHyp = /[-0-9a-zA-Z]/;
97
+
98
+ <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
99
+ var ldhStr = /[-0-9a-zA-Z]+/;
100
+
101
+ <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
102
+ var label = /[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?/;
103
+
104
+ <subdomain> ::= <label> | <subdomain> "." <label>
105
+ var subdomain = /^[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?(\.[a-zA-Z](([-0-9a-zA-Z]+)?[0-9a-zA-Z])?)*$/;
106
+
107
+ <domain> ::= <subdomain> | " "
108
+ var domain = null;
109
+ */
110
+ export const isValidHostname = (hostname) => {
111
+ // eslint-disable-next-line no-control-regex
112
+ if (IDN_SEPARATOR_TEST_REGEX.test(hostname) || /[^\x00-\x7F]/.test(hostname)) {
113
+ return false;
114
+ }
115
+ if (isIPModule.default(hostname, 4)) {
116
+ return false;
117
+ }
118
+ const labels = splitHostnameLabels(hostname);
119
+ if (labels === null) {
120
+ return false;
121
+ }
122
+ for (const label of labels) {
123
+ if (!isAsciiHostnameLabel(label)) {
124
+ return false;
125
+ }
126
+ const unicodeLabel = toUnicodeLabel(label);
127
+ if (unicodeLabel === null || !isValidIdnUnicodeLabel(unicodeLabel)) {
128
+ return false;
129
+ }
130
+ }
131
+ return true;
132
+ };
133
+ export const isValidIdnHostname = (hostname) => {
134
+ const normalizedHostname = hostname.replace(IDN_SEPARATOR_REGEX, '.');
135
+ const labels = splitHostnameLabels(normalizedHostname);
136
+ if (labels === null) {
137
+ return false;
138
+ }
139
+ for (const label of labels) {
140
+ const unicodeLabel = toUnicodeLabel(label);
141
+ if (unicodeLabel === null || !isValidIdnUnicodeLabel(unicodeLabel)) {
142
+ return false;
143
+ }
144
+ }
145
+ return true;
146
+ };
@@ -1,7 +1,8 @@
1
+ import { DEFAULT_MAX_RECURSION_DEPTH } from '../z-schema-options.js';
1
2
  import { isObject } from './what-is.js';
2
- export const areEqual = (json1, json2, options) => {
3
- options = options || {};
4
- const caseInsensitiveComparison = options.caseInsensitiveComparison || false;
3
+ export const areEqual = (json1, json2, options, _depth = 0) => {
4
+ const caseInsensitiveComparison = options?.caseInsensitiveComparison || false;
5
+ const maxDepth = options?.maxDepth ?? DEFAULT_MAX_RECURSION_DEPTH;
5
6
  // http://json-schema.org/latest/json-schema-core.html#rfc.section.3.6
6
7
  // Two JSON values are said to be equal if and only if:
7
8
  // both are nulls; or
@@ -17,6 +18,10 @@ export const areEqual = (json1, json2, options) => {
17
18
  json1.toUpperCase() === json2.toUpperCase()) {
18
19
  return true;
19
20
  }
21
+ if (_depth >= maxDepth) {
22
+ throw new Error(`Maximum recursion depth (${maxDepth}) exceeded in areEqual. ` +
23
+ 'If your data is deeply nested and valid, increase the maxRecursionDepth option.');
24
+ }
20
25
  let i, len;
21
26
  // both are arrays, and:
22
27
  if (Array.isArray(json1) && Array.isArray(json2)) {
@@ -27,7 +32,7 @@ export const areEqual = (json1, json2, options) => {
27
32
  // items at the same index are equal according to this definition; or
28
33
  len = json1.length;
29
34
  for (i = 0; i < len; i++) {
30
- if (!areEqual(json1[i], json2[i], { caseInsensitiveComparison: caseInsensitiveComparison })) {
35
+ if (!areEqual(json1[i], json2[i], options, _depth + 1)) {
31
36
  return false;
32
37
  }
33
38
  }
@@ -38,13 +43,13 @@ export const areEqual = (json1, json2, options) => {
38
43
  // have the same set of property names; and
39
44
  const keys1 = sortedKeys(json1);
40
45
  const keys2 = sortedKeys(json2);
41
- if (!areEqual(keys1, keys2, { caseInsensitiveComparison: caseInsensitiveComparison })) {
46
+ if (!areEqual(keys1, keys2, options, _depth + 1)) {
42
47
  return false;
43
48
  }
44
49
  // values for a same property name are equal according to this definition.
45
50
  len = keys1.length;
46
51
  for (i = 0; i < len; i++) {
47
- if (!areEqual(json1[keys1[i]], json2[keys1[i]], { caseInsensitiveComparison: caseInsensitiveComparison })) {
52
+ if (!areEqual(json1[keys1[i]], json2[keys1[i]], options, _depth + 1)) {
48
53
  return false;
49
54
  }
50
55
  }
@@ -1,10 +1,5 @@
1
- // Safe own-property check utility
2
- // Returns true if the object has the property as its own (not inherited)
3
- export function hasOwn(obj, key) {
4
- return Object.prototype.hasOwnProperty.call(obj, key);
5
- }
6
1
  export function copyProp(from, to, key, fn) {
7
- if (hasOwn(from, key)) {
2
+ if (Object.hasOwn(from, key)) {
8
3
  Object.defineProperty(to, key, {
9
4
  value: fn ? fn(from[key]) : from[key],
10
5
  enumerable: true,
@@ -0,0 +1,50 @@
1
+ export const toUtcTime = (hour, minute, offsetSign, offsetHour, offsetMinute) => {
2
+ const localTotalMinutes = hour * 60 + minute;
3
+ const offsetTotalMinutes = offsetHour * 60 + offsetMinute;
4
+ const utcTotalMinutes = offsetSign === '+' ? localTotalMinutes - offsetTotalMinutes : localTotalMinutes + offsetTotalMinutes;
5
+ const normalizedUtcTotalMinutes = ((utcTotalMinutes % 1440) + 1440) % 1440;
6
+ return {
7
+ hour: Math.floor(normalizedUtcTotalMinutes / 60),
8
+ minute: normalizedUtcTotalMinutes % 60,
9
+ };
10
+ };
11
+ const RFC3339_TIME_REGEX = /^([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]+)?(z|([+-][0-9]{2}:[0-9]{2}))$/i;
12
+ export const parseRfc3339Time = (time) => {
13
+ const matches = RFC3339_TIME_REGEX.exec(time);
14
+ if (matches === null) {
15
+ return null;
16
+ }
17
+ const hour = parseInt(matches[1], 10);
18
+ const minute = parseInt(matches[2], 10);
19
+ const second = parseInt(matches[3], 10);
20
+ if (hour > 23 || minute > 59 || second > 60) {
21
+ return null;
22
+ }
23
+ let utcHour = hour;
24
+ let utcMinute = minute;
25
+ if (matches[5].toLowerCase() !== 'z') {
26
+ const offsetMatches = /^([+-])([0-9]{2}):([0-9]{2})$/.exec(matches[5]);
27
+ if (offsetMatches === null) {
28
+ return null;
29
+ }
30
+ const offsetSign = offsetMatches[1];
31
+ const offsetHour = parseInt(offsetMatches[2], 10);
32
+ const offsetMinute = parseInt(offsetMatches[3], 10);
33
+ if (offsetHour > 23 || offsetMinute > 59) {
34
+ return null;
35
+ }
36
+ const utc = toUtcTime(hour, minute, offsetSign, offsetHour, offsetMinute);
37
+ utcHour = utc.hour;
38
+ utcMinute = utc.minute;
39
+ }
40
+ if (second === 60 && (utcHour !== 23 || utcMinute !== 59)) {
41
+ return null;
42
+ }
43
+ return {
44
+ hour,
45
+ minute,
46
+ second,
47
+ utcHour,
48
+ utcMinute,
49
+ };
50
+ };