joi 14.2.0 → 17.2.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 (59) hide show
  1. package/LICENSE.md +10 -0
  2. package/README.md +9 -118
  3. package/dist/joi-browser.min.js +1 -0
  4. package/lib/annotate.js +175 -0
  5. package/lib/base.js +1068 -0
  6. package/lib/cache.js +143 -0
  7. package/lib/common.js +216 -0
  8. package/lib/compile.js +283 -0
  9. package/lib/errors.js +160 -269
  10. package/lib/extend.js +312 -0
  11. package/lib/index.d.ts +2200 -0
  12. package/lib/index.js +179 -347
  13. package/lib/manifest.js +476 -0
  14. package/lib/messages.js +178 -0
  15. package/lib/modify.js +267 -0
  16. package/lib/ref.js +386 -25
  17. package/lib/schemas.js +291 -15
  18. package/lib/state.js +152 -0
  19. package/lib/template.js +427 -0
  20. package/lib/trace.js +346 -0
  21. package/lib/types/alternatives.js +329 -0
  22. package/lib/types/any.js +174 -0
  23. package/lib/types/array.js +775 -0
  24. package/lib/types/binary.js +98 -0
  25. package/lib/types/boolean.js +150 -0
  26. package/lib/types/date.js +233 -0
  27. package/lib/types/function.js +93 -0
  28. package/lib/types/keys.js +1043 -0
  29. package/lib/types/link.js +168 -0
  30. package/lib/types/number.js +335 -0
  31. package/lib/types/object.js +22 -0
  32. package/lib/types/string.js +820 -0
  33. package/lib/types/symbol.js +102 -0
  34. package/lib/validator.js +650 -0
  35. package/lib/values.js +263 -0
  36. package/package.json +34 -29
  37. package/CHANGELOG.md +0 -3
  38. package/LICENSE +0 -25
  39. package/lib/cast.js +0 -64
  40. package/lib/language.js +0 -166
  41. package/lib/set.js +0 -191
  42. package/lib/types/alternatives/index.js +0 -218
  43. package/lib/types/any/index.js +0 -978
  44. package/lib/types/any/settings.js +0 -36
  45. package/lib/types/array/index.js +0 -707
  46. package/lib/types/binary/index.js +0 -100
  47. package/lib/types/boolean/index.js +0 -100
  48. package/lib/types/date/index.js +0 -182
  49. package/lib/types/func/index.js +0 -90
  50. package/lib/types/lazy/index.js +0 -82
  51. package/lib/types/number/index.js +0 -248
  52. package/lib/types/object/index.js +0 -957
  53. package/lib/types/state.js +0 -11
  54. package/lib/types/string/index.js +0 -703
  55. package/lib/types/string/ip.js +0 -54
  56. package/lib/types/string/rfc3986.js +0 -219
  57. package/lib/types/string/uri.js +0 -46
  58. package/lib/types/symbol/index.js +0 -93
  59. package/lib/types/symbols.js +0 -5
@@ -0,0 +1,775 @@
1
+ 'use strict';
2
+
3
+ const Assert = require('@hapi/hoek/lib/assert');
4
+ const DeepEqual = require('@hapi/hoek/lib/deepEqual');
5
+ const Reach = require('@hapi/hoek/lib/reach');
6
+
7
+ const Any = require('./any');
8
+ const Common = require('../common');
9
+ const Compile = require('../compile');
10
+
11
+
12
+ const internals = {};
13
+
14
+
15
+ module.exports = Any.extend({
16
+
17
+ type: 'array',
18
+
19
+ flags: {
20
+
21
+ single: { default: false },
22
+ sparse: { default: false }
23
+ },
24
+
25
+ terms: {
26
+
27
+ items: { init: [], manifest: 'schema' },
28
+ ordered: { init: [], manifest: 'schema' },
29
+
30
+ _exclusions: { init: [] },
31
+ _inclusions: { init: [] },
32
+ _requireds: { init: [] }
33
+ },
34
+
35
+ coerce: {
36
+ from: 'object',
37
+ method(value, { schema, state, prefs }) {
38
+
39
+ if (!Array.isArray(value)) {
40
+ return;
41
+ }
42
+
43
+ const sort = schema.$_getRule('sort');
44
+ if (!sort) {
45
+ return;
46
+ }
47
+
48
+ return internals.sort(schema, value, sort.args.options, state, prefs);
49
+ }
50
+ },
51
+
52
+ validate(value, { schema, error }) {
53
+
54
+ if (!Array.isArray(value)) {
55
+ if (schema._flags.single) {
56
+ const single = [value];
57
+ single[Common.symbols.arraySingle] = true;
58
+ return { value: single };
59
+ }
60
+
61
+ return { errors: error('array.base') };
62
+ }
63
+
64
+ if (!schema.$_getRule('items') &&
65
+ !schema.$_terms.externals) {
66
+
67
+ return;
68
+ }
69
+
70
+ return { value: value.slice() }; // Clone the array so that we don't modify the original
71
+ },
72
+
73
+ rules: {
74
+
75
+ has: {
76
+ method(schema) {
77
+
78
+ schema = this.$_compile(schema, { appendPath: true });
79
+ const obj = this.$_addRule({ name: 'has', args: { schema } });
80
+ obj.$_mutateRegister(schema);
81
+ return obj;
82
+ },
83
+ validate(value, { state, prefs, error }, { schema: has }) {
84
+
85
+ const ancestors = [value, ...state.ancestors];
86
+ for (let i = 0; i < value.length; ++i) {
87
+ const localState = state.localize([...state.path, i], ancestors, has);
88
+ if (has.$_match(value[i], localState, prefs)) {
89
+ return value;
90
+ }
91
+ }
92
+
93
+ const patternLabel = has._flags.label;
94
+ if (patternLabel) {
95
+ return error('array.hasKnown', { patternLabel });
96
+ }
97
+
98
+ return error('array.hasUnknown', null);
99
+ },
100
+ multi: true
101
+ },
102
+
103
+ items: {
104
+ method(...schemas) {
105
+
106
+ Common.verifyFlat(schemas, 'items');
107
+
108
+ const obj = this.$_addRule('items');
109
+
110
+ for (let i = 0; i < schemas.length; ++i) {
111
+ const type = Common.tryWithPath(() => this.$_compile(schemas[i]), i, { append: true });
112
+ obj.$_terms.items.push(type);
113
+ }
114
+
115
+ return obj.$_mutateRebuild();
116
+ },
117
+ validate(value, { schema, error, state, prefs, errorsArray }) {
118
+
119
+ const requireds = schema.$_terms._requireds.slice();
120
+ const ordereds = schema.$_terms.ordered.slice();
121
+ const inclusions = [...schema.$_terms._inclusions, ...requireds];
122
+
123
+ const wasArray = !value[Common.symbols.arraySingle];
124
+ delete value[Common.symbols.arraySingle];
125
+
126
+ const errors = errorsArray();
127
+
128
+ let il = value.length;
129
+ for (let i = 0; i < il; ++i) {
130
+ const item = value[i];
131
+
132
+ let errored = false;
133
+ let isValid = false;
134
+
135
+ const key = wasArray ? i : new Number(i); // eslint-disable-line no-new-wrappers
136
+ const path = [...state.path, key];
137
+
138
+ // Sparse
139
+
140
+ if (!schema._flags.sparse &&
141
+ item === undefined) {
142
+
143
+ errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));
144
+ if (prefs.abortEarly) {
145
+ return errors;
146
+ }
147
+
148
+ ordereds.shift();
149
+ continue;
150
+ }
151
+
152
+ // Exclusions
153
+
154
+ const ancestors = [value, ...state.ancestors];
155
+
156
+ for (const exclusion of schema.$_terms._exclusions) {
157
+ if (!exclusion.$_match(item, state.localize(path, ancestors, exclusion), prefs, { presence: 'ignore' })) {
158
+ continue;
159
+ }
160
+
161
+ errors.push(error('array.excludes', { pos: i, value: item }, state.localize(path)));
162
+ if (prefs.abortEarly) {
163
+ return errors;
164
+ }
165
+
166
+ errored = true;
167
+ ordereds.shift();
168
+ break;
169
+ }
170
+
171
+ if (errored) {
172
+ continue;
173
+ }
174
+
175
+ // Ordered
176
+
177
+ if (schema.$_terms.ordered.length) {
178
+ if (ordereds.length) {
179
+ const ordered = ordereds.shift();
180
+ const res = ordered.$_validate(item, state.localize(path, ancestors, ordered), prefs);
181
+ if (!res.errors) {
182
+ if (ordered._flags.result === 'strip') {
183
+ internals.fastSplice(value, i);
184
+ --i;
185
+ --il;
186
+ }
187
+ else if (!schema._flags.sparse && res.value === undefined) {
188
+ errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));
189
+ if (prefs.abortEarly) {
190
+ return errors;
191
+ }
192
+
193
+ continue;
194
+ }
195
+ else {
196
+ value[i] = res.value;
197
+ }
198
+ }
199
+ else {
200
+ errors.push(...res.errors);
201
+ if (prefs.abortEarly) {
202
+ return errors;
203
+ }
204
+ }
205
+
206
+ continue;
207
+ }
208
+ else if (!schema.$_terms.items.length) {
209
+ errors.push(error('array.orderedLength', { pos: i, limit: schema.$_terms.ordered.length }));
210
+ if (prefs.abortEarly) {
211
+ return errors;
212
+ }
213
+
214
+ break; // No reason to continue since there are no other rules to validate other than array.orderedLength
215
+ }
216
+ }
217
+
218
+ // Requireds
219
+
220
+ const requiredChecks = [];
221
+ let jl = requireds.length;
222
+ for (let j = 0; j < jl; ++j) {
223
+ const localState = state.localize(path, ancestors, requireds[j]);
224
+ localState.snapshot();
225
+
226
+ const res = requireds[j].$_validate(item, localState, prefs);
227
+ requiredChecks[j] = res;
228
+
229
+ if (!res.errors) {
230
+ value[i] = res.value;
231
+ isValid = true;
232
+ internals.fastSplice(requireds, j);
233
+ --j;
234
+ --jl;
235
+
236
+ if (!schema._flags.sparse &&
237
+ res.value === undefined) {
238
+
239
+ errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));
240
+ if (prefs.abortEarly) {
241
+ return errors;
242
+ }
243
+ }
244
+
245
+ break;
246
+ }
247
+
248
+ localState.restore();
249
+ }
250
+
251
+ if (isValid) {
252
+ continue;
253
+ }
254
+
255
+ // Inclusions
256
+
257
+ const stripUnknown = prefs.stripUnknown && !!prefs.stripUnknown.arrays || false;
258
+
259
+ jl = inclusions.length;
260
+ for (const inclusion of inclusions) {
261
+
262
+ // Avoid re-running requireds that already didn't match in the previous loop
263
+
264
+ let res;
265
+ const previousCheck = requireds.indexOf(inclusion);
266
+ if (previousCheck !== -1) {
267
+ res = requiredChecks[previousCheck];
268
+ }
269
+ else {
270
+ const localState = state.localize(path, ancestors, inclusion);
271
+ localState.snapshot();
272
+
273
+ res = inclusion.$_validate(item, localState, prefs);
274
+ if (!res.errors) {
275
+ if (inclusion._flags.result === 'strip') {
276
+ internals.fastSplice(value, i);
277
+ --i;
278
+ --il;
279
+ }
280
+ else if (!schema._flags.sparse &&
281
+ res.value === undefined) {
282
+
283
+ errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));
284
+ errored = true;
285
+ }
286
+ else {
287
+ value[i] = res.value;
288
+ }
289
+
290
+ isValid = true;
291
+ break;
292
+ }
293
+
294
+ localState.restore();
295
+ }
296
+
297
+ // Return the actual error if only one inclusion defined
298
+
299
+ if (jl === 1) {
300
+ if (stripUnknown) {
301
+ internals.fastSplice(value, i);
302
+ --i;
303
+ --il;
304
+ isValid = true;
305
+ break;
306
+ }
307
+
308
+ errors.push(...res.errors);
309
+ if (prefs.abortEarly) {
310
+ return errors;
311
+ }
312
+
313
+ errored = true;
314
+ break;
315
+ }
316
+ }
317
+
318
+ if (errored) {
319
+ continue;
320
+ }
321
+
322
+ if (schema.$_terms._inclusions.length &&
323
+ !isValid) {
324
+
325
+ if (stripUnknown) {
326
+ internals.fastSplice(value, i);
327
+ --i;
328
+ --il;
329
+ continue;
330
+ }
331
+
332
+ errors.push(error('array.includes', { pos: i, value: item }, state.localize(path)));
333
+ if (prefs.abortEarly) {
334
+ return errors;
335
+ }
336
+ }
337
+ }
338
+
339
+ if (requireds.length) {
340
+ internals.fillMissedErrors(schema, errors, requireds, value, state, prefs);
341
+ }
342
+
343
+ if (ordereds.length) {
344
+ internals.fillOrderedErrors(schema, errors, ordereds, value, state, prefs);
345
+ }
346
+
347
+ return errors.length ? errors : value;
348
+ },
349
+
350
+ priority: true,
351
+ manifest: false
352
+ },
353
+
354
+ length: {
355
+ method(limit) {
356
+
357
+ return this.$_addRule({ name: 'length', args: { limit }, operator: '=' });
358
+ },
359
+ validate(value, helpers, { limit }, { name, operator, args }) {
360
+
361
+ if (Common.compare(value.length, limit, operator)) {
362
+ return value;
363
+ }
364
+
365
+ return helpers.error('array.' + name, { limit: args.limit, value });
366
+ },
367
+ args: [
368
+ {
369
+ name: 'limit',
370
+ ref: true,
371
+ assert: Common.limit,
372
+ message: 'must be a positive integer'
373
+ }
374
+ ]
375
+ },
376
+
377
+ max: {
378
+ method(limit) {
379
+
380
+ return this.$_addRule({ name: 'max', method: 'length', args: { limit }, operator: '<=' });
381
+ }
382
+ },
383
+
384
+ min: {
385
+ method(limit) {
386
+
387
+ return this.$_addRule({ name: 'min', method: 'length', args: { limit }, operator: '>=' });
388
+ }
389
+ },
390
+
391
+ ordered: {
392
+ method(...schemas) {
393
+
394
+ Common.verifyFlat(schemas, 'ordered');
395
+
396
+ const obj = this.$_addRule('items');
397
+
398
+ for (let i = 0; i < schemas.length; ++i) {
399
+ const type = Common.tryWithPath(() => this.$_compile(schemas[i]), i, { append: true });
400
+ internals.validateSingle(type, obj);
401
+
402
+ obj.$_mutateRegister(type);
403
+ obj.$_terms.ordered.push(type);
404
+ }
405
+
406
+ return obj.$_mutateRebuild();
407
+ }
408
+ },
409
+
410
+ single: {
411
+ method(enabled) {
412
+
413
+ const value = enabled === undefined ? true : !!enabled;
414
+ Assert(!value || !this._flags._arrayItems, 'Cannot specify single rule when array has array items');
415
+
416
+ return this.$_setFlag('single', value);
417
+ }
418
+ },
419
+
420
+ sort: {
421
+ method(options = {}) {
422
+
423
+ Common.assertOptions(options, ['by', 'order']);
424
+
425
+ const settings = {
426
+ order: options.order || 'ascending'
427
+ };
428
+
429
+ if (options.by) {
430
+ settings.by = Compile.ref(options.by, { ancestor: 0 });
431
+ Assert(!settings.by.ancestor, 'Cannot sort by ancestor');
432
+ }
433
+
434
+ return this.$_addRule({ name: 'sort', args: { options: settings } });
435
+ },
436
+ validate(value, { error, state, prefs, schema }, { options }) {
437
+
438
+ const { value: sorted, errors } = internals.sort(schema, value, options, state, prefs);
439
+ if (errors) {
440
+ return errors;
441
+ }
442
+
443
+ for (let i = 0; i < value.length; ++i) {
444
+ if (value[i] !== sorted[i]) {
445
+ return error('array.sort', { order: options.order, by: options.by ? options.by.key : 'value' });
446
+ }
447
+ }
448
+
449
+ return value;
450
+ },
451
+ convert: true
452
+ },
453
+
454
+ sparse: {
455
+ method(enabled) {
456
+
457
+ const value = enabled === undefined ? true : !!enabled;
458
+
459
+ if (this._flags.sparse === value) {
460
+ return this;
461
+ }
462
+
463
+ const obj = value ? this.clone() : this.$_addRule('items');
464
+ return obj.$_setFlag('sparse', value, { clone: false });
465
+ }
466
+ },
467
+
468
+ unique: {
469
+ method(comparator, options = {}) {
470
+
471
+ Assert(!comparator || typeof comparator === 'function' || typeof comparator === 'string', 'comparator must be a function or a string');
472
+ Common.assertOptions(options, ['ignoreUndefined', 'separator']);
473
+
474
+ const rule = { name: 'unique', args: { options, comparator } };
475
+
476
+ if (comparator) {
477
+ if (typeof comparator === 'string') {
478
+ const separator = Common.default(options.separator, '.');
479
+ rule.path = separator ? comparator.split(separator) : [comparator];
480
+ }
481
+ else {
482
+ rule.comparator = comparator;
483
+ }
484
+ }
485
+
486
+ return this.$_addRule(rule);
487
+ },
488
+ validate(value, { state, error, schema }, { comparator: raw, options }, { comparator, path }) {
489
+
490
+ const found = {
491
+ string: Object.create(null),
492
+ number: Object.create(null),
493
+ undefined: Object.create(null),
494
+ boolean: Object.create(null),
495
+ object: new Map(),
496
+ function: new Map(),
497
+ custom: new Map()
498
+ };
499
+
500
+ const compare = comparator || DeepEqual;
501
+ const ignoreUndefined = options.ignoreUndefined;
502
+
503
+ for (let i = 0; i < value.length; ++i) {
504
+ const item = path ? Reach(value[i], path) : value[i];
505
+ const records = comparator ? found.custom : found[typeof item];
506
+ Assert(records, 'Failed to find unique map container for type', typeof item);
507
+
508
+ if (records instanceof Map) {
509
+ const entries = records.entries();
510
+ let current;
511
+ while (!(current = entries.next()).done) {
512
+ if (compare(current.value[0], item)) {
513
+ const localState = state.localize([...state.path, i], [value, ...state.ancestors]);
514
+ const context = {
515
+ pos: i,
516
+ value: value[i],
517
+ dupePos: current.value[1],
518
+ dupeValue: value[current.value[1]]
519
+ };
520
+
521
+ if (path) {
522
+ context.path = raw;
523
+ }
524
+
525
+ return error('array.unique', context, localState);
526
+ }
527
+ }
528
+
529
+ records.set(item, i);
530
+ }
531
+ else {
532
+ if ((!ignoreUndefined || item !== undefined) &&
533
+ records[item] !== undefined) {
534
+
535
+ const context = {
536
+ pos: i,
537
+ value: value[i],
538
+ dupePos: records[item],
539
+ dupeValue: value[records[item]]
540
+ };
541
+
542
+ if (path) {
543
+ context.path = raw;
544
+ }
545
+
546
+ const localState = state.localize([...state.path, i], [value, ...state.ancestors]);
547
+ return error('array.unique', context, localState);
548
+ }
549
+
550
+ records[item] = i;
551
+ }
552
+ }
553
+
554
+ return value;
555
+ },
556
+ args: ['comparator', 'options'],
557
+ multi: true
558
+ }
559
+ },
560
+
561
+ cast: {
562
+ set: {
563
+ from: Array.isArray,
564
+ to(value, helpers) {
565
+
566
+ return new Set(value);
567
+ }
568
+ }
569
+ },
570
+
571
+ rebuild(schema) {
572
+
573
+ schema.$_terms._inclusions = [];
574
+ schema.$_terms._exclusions = [];
575
+ schema.$_terms._requireds = [];
576
+
577
+ for (const type of schema.$_terms.items) {
578
+ internals.validateSingle(type, schema);
579
+
580
+ if (type._flags.presence === 'required') {
581
+ schema.$_terms._requireds.push(type);
582
+ }
583
+ else if (type._flags.presence === 'forbidden') {
584
+ schema.$_terms._exclusions.push(type);
585
+ }
586
+ else {
587
+ schema.$_terms._inclusions.push(type);
588
+ }
589
+ }
590
+
591
+ for (const type of schema.$_terms.ordered) {
592
+ internals.validateSingle(type, schema);
593
+ }
594
+ },
595
+
596
+ manifest: {
597
+
598
+ build(obj, desc) {
599
+
600
+ if (desc.items) {
601
+ obj = obj.items(...desc.items);
602
+ }
603
+
604
+ if (desc.ordered) {
605
+ obj = obj.ordered(...desc.ordered);
606
+ }
607
+
608
+ return obj;
609
+ }
610
+ },
611
+
612
+ messages: {
613
+ 'array.base': '{{#label}} must be an array',
614
+ 'array.excludes': '{{#label}} contains an excluded value',
615
+ 'array.hasKnown': '{{#label}} does not contain at least one required match for type {:#patternLabel}',
616
+ 'array.hasUnknown': '{{#label}} does not contain at least one required match',
617
+ 'array.includes': '{{#label}} does not match any of the allowed types',
618
+ 'array.includesRequiredBoth': '{{#label}} does not contain {{#knownMisses}} and {{#unknownMisses}} other required value(s)',
619
+ 'array.includesRequiredKnowns': '{{#label}} does not contain {{#knownMisses}}',
620
+ 'array.includesRequiredUnknowns': '{{#label}} does not contain {{#unknownMisses}} required value(s)',
621
+ 'array.length': '{{#label}} must contain {{#limit}} items',
622
+ 'array.max': '{{#label}} must contain less than or equal to {{#limit}} items',
623
+ 'array.min': '{{#label}} must contain at least {{#limit}} items',
624
+ 'array.orderedLength': '{{#label}} must contain at most {{#limit}} items',
625
+ 'array.sort': '{{#label}} must be sorted in {#order} order by {{#by}}',
626
+ 'array.sort.mismatching': '{{#label}} cannot be sorted due to mismatching types',
627
+ 'array.sort.unsupported': '{{#label}} cannot be sorted due to unsupported type {#type}',
628
+ 'array.sparse': '{{#label}} must not be a sparse array item',
629
+ 'array.unique': '{{#label}} contains a duplicate value'
630
+ }
631
+ });
632
+
633
+
634
+ // Helpers
635
+
636
+ internals.fillMissedErrors = function (schema, errors, requireds, value, state, prefs) {
637
+
638
+ const knownMisses = [];
639
+ let unknownMisses = 0;
640
+ for (const required of requireds) {
641
+ const label = required._flags.label;
642
+ if (label) {
643
+ knownMisses.push(label);
644
+ }
645
+ else {
646
+ ++unknownMisses;
647
+ }
648
+ }
649
+
650
+ if (knownMisses.length) {
651
+ if (unknownMisses) {
652
+ errors.push(schema.$_createError('array.includesRequiredBoth', value, { knownMisses, unknownMisses }, state, prefs));
653
+ }
654
+ else {
655
+ errors.push(schema.$_createError('array.includesRequiredKnowns', value, { knownMisses }, state, prefs));
656
+ }
657
+ }
658
+ else {
659
+ errors.push(schema.$_createError('array.includesRequiredUnknowns', value, { unknownMisses }, state, prefs));
660
+ }
661
+ };
662
+
663
+
664
+ internals.fillOrderedErrors = function (schema, errors, ordereds, value, state, prefs) {
665
+
666
+ const requiredOrdereds = [];
667
+
668
+ for (const ordered of ordereds) {
669
+ if (ordered._flags.presence === 'required') {
670
+ requiredOrdereds.push(ordered);
671
+ }
672
+ }
673
+
674
+ if (requiredOrdereds.length) {
675
+ internals.fillMissedErrors(schema, errors, requiredOrdereds, value, state, prefs);
676
+ }
677
+ };
678
+
679
+
680
+ internals.fastSplice = function (arr, i) {
681
+
682
+ let pos = i;
683
+ while (pos < arr.length) {
684
+ arr[pos++] = arr[pos];
685
+ }
686
+
687
+ --arr.length;
688
+ };
689
+
690
+
691
+ internals.validateSingle = function (type, obj) {
692
+
693
+ if (type.type === 'array' ||
694
+ type._flags._arrayItems) {
695
+
696
+ Assert(!obj._flags.single, 'Cannot specify array item with single rule enabled');
697
+ obj.$_setFlag('_arrayItems', true, { clone: false });
698
+ }
699
+ };
700
+
701
+
702
+ internals.sort = function (schema, value, settings, state, prefs) {
703
+
704
+ const order = settings.order === 'ascending' ? 1 : -1;
705
+ const aFirst = -1 * order;
706
+ const bFirst = order;
707
+
708
+ const sort = (a, b) => {
709
+
710
+ let compare = internals.compare(a, b, aFirst, bFirst);
711
+ if (compare !== null) {
712
+ return compare;
713
+ }
714
+
715
+ if (settings.by) {
716
+ a = settings.by.resolve(a, state, prefs);
717
+ b = settings.by.resolve(b, state, prefs);
718
+ }
719
+
720
+ compare = internals.compare(a, b, aFirst, bFirst);
721
+ if (compare !== null) {
722
+ return compare;
723
+ }
724
+
725
+ const type = typeof a;
726
+ if (type !== typeof b) {
727
+ throw schema.$_createError('array.sort.mismatching', value, null, state, prefs);
728
+ }
729
+
730
+ if (type !== 'number' &&
731
+ type !== 'string') {
732
+
733
+ throw schema.$_createError('array.sort.unsupported', value, { type }, state, prefs);
734
+ }
735
+
736
+ if (type === 'number') {
737
+ return (a - b) * order;
738
+ }
739
+
740
+ return a < b ? aFirst : bFirst;
741
+ };
742
+
743
+ try {
744
+ return { value: value.slice().sort(sort) };
745
+ }
746
+ catch (err) {
747
+ return { errors: err };
748
+ }
749
+ };
750
+
751
+
752
+ internals.compare = function (a, b, aFirst, bFirst) {
753
+
754
+ if (a === b) {
755
+ return 0;
756
+ }
757
+
758
+ if (a === undefined) {
759
+ return 1; // Always last regardless of sort order
760
+ }
761
+
762
+ if (b === undefined) {
763
+ return -1; // Always last regardless of sort order
764
+ }
765
+
766
+ if (a === null) {
767
+ return bFirst;
768
+ }
769
+
770
+ if (b === null) {
771
+ return aFirst;
772
+ }
773
+
774
+ return null;
775
+ };