schema-shield 1.0.0 → 1.0.1

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 (39) hide show
  1. package/README.md +38 -12
  2. package/dist/formats.d.ts.map +1 -1
  3. package/dist/index.d.ts +14 -3
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1445 -447
  6. package/dist/index.min.js +1 -1
  7. package/dist/index.min.js.map +1 -1
  8. package/dist/index.mjs +1445 -447
  9. package/dist/keywords/array-keywords.d.ts.map +1 -1
  10. package/dist/keywords/object-keywords.d.ts.map +1 -1
  11. package/dist/keywords/other-keywords.d.ts.map +1 -1
  12. package/dist/keywords/string-keywords.d.ts.map +1 -1
  13. package/dist/types.d.ts.map +1 -1
  14. package/dist/utils/deep-freeze.d.ts +5 -0
  15. package/dist/utils/deep-freeze.d.ts.map +1 -0
  16. package/dist/utils/has-changed.d.ts +2 -0
  17. package/dist/utils/has-changed.d.ts.map +1 -0
  18. package/dist/utils/index.d.ts +5 -0
  19. package/dist/utils/index.d.ts.map +1 -0
  20. package/dist/{utils.d.ts → utils/main-utils.d.ts} +3 -6
  21. package/dist/utils/main-utils.d.ts.map +1 -0
  22. package/dist/utils/pattern-matcher.d.ts +3 -0
  23. package/dist/utils/pattern-matcher.d.ts.map +1 -0
  24. package/lib/formats.ts +402 -84
  25. package/lib/index.ts +494 -46
  26. package/lib/keywords/array-keywords.ts +215 -21
  27. package/lib/keywords/number-keywords.ts +1 -1
  28. package/lib/keywords/object-keywords.ts +218 -113
  29. package/lib/keywords/other-keywords.ts +229 -76
  30. package/lib/keywords/string-keywords.ts +97 -7
  31. package/lib/types.ts +4 -5
  32. package/lib/utils/deep-freeze.ts +208 -0
  33. package/lib/utils/has-changed.ts +51 -0
  34. package/lib/utils/index.ts +4 -0
  35. package/lib/utils/main-utils.ts +190 -0
  36. package/lib/utils/pattern-matcher.ts +66 -0
  37. package/package.json +1 -1
  38. package/dist/utils.d.ts.map +0 -1
  39. package/lib/utils.ts +0 -362
@@ -1,6 +1,99 @@
1
- import { hasChanged, isCompiledSchema, isObject } from "../utils";
1
+ import { isCompiledSchema } from "../utils/main-utils";
2
2
 
3
3
  import { KeywordFunction } from "../index";
4
+ import { hasChanged } from "../utils/has-changed";
5
+
6
+ function isUniquePrimitive(value: any) {
7
+ return (
8
+ value === null ||
9
+ typeof value === "string" ||
10
+ typeof value === "number" ||
11
+ typeof value === "boolean"
12
+ );
13
+ }
14
+
15
+ function getArrayBucketKey(value: any[]): string {
16
+ const length = value.length;
17
+ if (length === 0) {
18
+ return "0";
19
+ }
20
+
21
+ const first = value[0];
22
+ const last = value[length - 1];
23
+ const firstType = first === null ? "null" : typeof first;
24
+ const lastType = last === null ? "null" : typeof last;
25
+
26
+ let firstArrayMarker = "";
27
+ if (Array.isArray(first)) {
28
+ const firstSignature = getPrimitiveArraySignature(first);
29
+ firstArrayMarker = firstSignature === null ? `a:${first.length}` : firstSignature;
30
+ }
31
+
32
+ let lastArrayMarker = "";
33
+ if (Array.isArray(last)) {
34
+ const lastSignature = getPrimitiveArraySignature(last);
35
+ lastArrayMarker = lastSignature === null ? `a:${last.length}` : lastSignature;
36
+ }
37
+
38
+ return `${length}:${firstType}:${firstArrayMarker}:${lastType}:${lastArrayMarker}`;
39
+ }
40
+
41
+ function getObjectShapeKey(value: Record<string, any>): string {
42
+ const keys = Object.keys(value).sort();
43
+ return `${keys.length}:${keys.join("\u0001")}`;
44
+ }
45
+
46
+ function getPrimitiveArraySignature(value: any[]): string | null {
47
+ const length = value.length;
48
+
49
+ if (length === 0) {
50
+ return "a:0";
51
+ }
52
+
53
+ if (!isUniquePrimitive(value[0]) || !isUniquePrimitive(value[length - 1])) {
54
+ return null;
55
+ }
56
+
57
+ let signature = `a:${length}:`;
58
+
59
+ for (let i = 0; i < length; i++) {
60
+ const item = value[i];
61
+
62
+ if (item === null) {
63
+ signature += "l;";
64
+ continue;
65
+ }
66
+
67
+ if (typeof item === "string") {
68
+ signature += `s${item.length}:${item};`;
69
+ continue;
70
+ }
71
+
72
+ if (typeof item === "number") {
73
+ if (Number.isNaN(item)) {
74
+ signature += "n:NaN;";
75
+ continue;
76
+ }
77
+
78
+ if (Object.is(item, -0)) {
79
+ signature += "n:-0;";
80
+ continue;
81
+ }
82
+
83
+ signature += `n:${item};`;
84
+ continue;
85
+ }
86
+
87
+ if (typeof item === "boolean") {
88
+ signature += item ? "b:1;" : "b:0;";
89
+ continue;
90
+ }
91
+
92
+ return null;
93
+ }
94
+
95
+ return signature;
96
+ }
4
97
 
5
98
  export const ArrayKeywords: Record<string, KeywordFunction> = {
6
99
  // lib/keywords/array-keywords.ts
@@ -110,20 +203,36 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
110
203
  },
111
204
 
112
205
  additionalItems(schema, data, defineError) {
113
- if (!schema.items || isObject(schema.items)) {
206
+ if (!Array.isArray(data) || !Array.isArray(schema.items)) {
114
207
  return;
115
208
  }
116
209
 
117
- if (schema.additionalItems === false) {
118
- if (data.length > schema.items.length) {
119
- return defineError("Array is too long", { data });
120
- }
210
+ let tupleLength = (schema as any)._tupleItemsLength as number | undefined;
211
+ if (tupleLength === undefined) {
212
+ tupleLength = schema.items.length;
213
+ Object.defineProperty(schema, "_tupleItemsLength", {
214
+ value: tupleLength,
215
+ enumerable: false,
216
+ configurable: false,
217
+ writable: false
218
+ });
219
+ }
220
+
221
+ if (data.length <= tupleLength) {
121
222
  return;
122
223
  }
123
224
 
124
- if (isObject(schema.additionalItems)) {
225
+ if (schema.additionalItems === false) {
226
+ return defineError("Array is too long", { data });
227
+ }
228
+
229
+ if (
230
+ schema.additionalItems &&
231
+ typeof schema.additionalItems === "object" &&
232
+ !Array.isArray(schema.additionalItems)
233
+ ) {
125
234
  if (isCompiledSchema(schema.additionalItems)) {
126
- for (let i = schema.items.length; i < data.length; i++) {
235
+ for (let i = tupleLength; i < data.length; i++) {
127
236
  const error = schema.additionalItems.$validate(data[i]);
128
237
  if (error) {
129
238
  return defineError("Array item is invalid", {
@@ -152,18 +261,50 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
152
261
  return;
153
262
  }
154
263
 
264
+ if (len <= 8) {
265
+ for (let i = 0; i < len; i++) {
266
+ const left = data[i];
267
+
268
+ for (let j = i + 1; j < len; j++) {
269
+ const right = data[j];
270
+
271
+ if (left === right) {
272
+ return defineError("Array items are not unique", { data: right });
273
+ }
274
+
275
+ if (
276
+ typeof left === "number" &&
277
+ typeof right === "number" &&
278
+ Number.isNaN(left) &&
279
+ Number.isNaN(right)
280
+ ) {
281
+ return defineError("Array items are not unique", { data: right });
282
+ }
283
+
284
+ if (
285
+ left &&
286
+ right &&
287
+ typeof left === "object" &&
288
+ typeof right === "object" &&
289
+ !hasChanged(left, right)
290
+ ) {
291
+ return defineError("Array items are not unique", { data: right });
292
+ }
293
+ }
294
+ }
295
+
296
+ return;
297
+ }
298
+
155
299
  const primitiveSeen = new Set<any>();
300
+ let primitiveArraySignatures: Set<string> | undefined;
301
+ let arrayBuckets: Map<string, any[]> | undefined;
302
+ let objectBuckets: Map<string, any[]> | undefined;
156
303
 
157
304
  for (let i = 0; i < len; i++) {
158
305
  const item = data[i];
159
- const type = typeof item;
160
-
161
- if (
162
- item === null ||
163
- type === "string" ||
164
- type === "number" ||
165
- type === "boolean"
166
- ) {
306
+
307
+ if (isUniquePrimitive(item)) {
167
308
  if (primitiveSeen.has(item)) {
168
309
  return defineError("Array items are not unique", { data: item });
169
310
  }
@@ -171,14 +312,66 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
171
312
  continue;
172
313
  }
173
314
 
174
- if (item && typeof item === "object") {
175
- for (let j = 0; j < i; j++) {
176
- const prev = data[j];
177
- if (prev && typeof prev === "object" && !hasChanged(prev, item)) {
315
+ if (!item || typeof item !== "object") {
316
+ continue;
317
+ }
318
+
319
+ if (Array.isArray(item)) {
320
+ const signature = getPrimitiveArraySignature(item);
321
+ if (signature !== null) {
322
+ if (!primitiveArraySignatures) {
323
+ primitiveArraySignatures = new Set<string>();
324
+ }
325
+
326
+ if (primitiveArraySignatures.has(signature)) {
327
+ return defineError("Array items are not unique", { data: item });
328
+ }
329
+
330
+ primitiveArraySignatures.add(signature);
331
+ continue;
332
+ }
333
+
334
+ if (!arrayBuckets) {
335
+ arrayBuckets = new Map<string, any[]>();
336
+ }
337
+
338
+ const bucketKey = getArrayBucketKey(item);
339
+ let candidates = arrayBuckets.get(bucketKey);
340
+
341
+ if (!candidates) {
342
+ candidates = [];
343
+ arrayBuckets.set(bucketKey, candidates);
344
+ }
345
+
346
+ for (let j = 0; j < candidates.length; j++) {
347
+ if (!hasChanged(candidates[j], item)) {
178
348
  return defineError("Array items are not unique", { data: item });
179
349
  }
180
350
  }
351
+
352
+ candidates.push(item);
353
+ continue;
181
354
  }
355
+
356
+ if (!objectBuckets) {
357
+ objectBuckets = new Map<string, any[]>();
358
+ }
359
+
360
+ const bucketKey = getObjectShapeKey(item);
361
+ let candidates = objectBuckets.get(bucketKey);
362
+
363
+ if (!candidates) {
364
+ candidates = [];
365
+ objectBuckets.set(bucketKey, candidates);
366
+ }
367
+
368
+ for (let j = 0; j < candidates.length; j++) {
369
+ if (!hasChanged(candidates[j], item)) {
370
+ return defineError("Array items are not unique", { data: item });
371
+ }
372
+ }
373
+
374
+ candidates.push(item);
182
375
  }
183
376
  },
184
377
 
@@ -197,8 +390,9 @@ export const ArrayKeywords: Record<string, KeywordFunction> = {
197
390
  return defineError("Array must not contain any items", { data });
198
391
  }
199
392
 
393
+ const containsValidate = schema.contains.$validate;
200
394
  for (let i = 0; i < data.length; i++) {
201
- const error = schema.contains.$validate(data[i]);
395
+ const error = containsValidate(data[i]);
202
396
  if (!error) {
203
397
  return;
204
398
  }
@@ -1,5 +1,5 @@
1
1
  import { KeywordFunction } from "../index";
2
- import { areCloseEnough } from "../utils";
2
+ import { areCloseEnough } from "../utils/index";
3
3
 
4
4
  export const NumberKeywords: Record<string, KeywordFunction> = {
5
5
  minimum(schema, data, defineError, instance) {