Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
831 lines
26 KiB
Text
Executable file
831 lines
26 KiB
Text
Executable file
'use strict';
|
|
|
|
const { assert, deepEqual, reach } = require('@hapi/hoek');
|
|
|
|
const Any = require('./any');
|
|
const Common = require('../common');
|
|
const Compile = require('../compile');
|
|
|
|
|
|
const internals = {};
|
|
|
|
|
|
module.exports = Any.extend({
|
|
|
|
type: 'array',
|
|
|
|
flags: {
|
|
|
|
single: { default: false },
|
|
sparse: { default: false }
|
|
},
|
|
|
|
terms: {
|
|
|
|
items: { init: [], manifest: 'schema' },
|
|
ordered: { init: [], manifest: 'schema' },
|
|
|
|
_exclusions: { init: [] },
|
|
_inclusions: { init: [] },
|
|
_requireds: { init: [] }
|
|
},
|
|
|
|
coerce: {
|
|
from: 'object',
|
|
method(value, { schema, state, prefs }) {
|
|
|
|
if (!Array.isArray(value)) {
|
|
return;
|
|
}
|
|
|
|
const sort = schema.$_getRule('sort');
|
|
if (!sort) {
|
|
return;
|
|
}
|
|
|
|
return internals.sort(schema, value, sort.args.options, state, prefs);
|
|
}
|
|
},
|
|
|
|
validate(value, { schema, error }) {
|
|
|
|
if (!Array.isArray(value)) {
|
|
if (schema._flags.single) {
|
|
const single = [value];
|
|
single[Common.symbols.arraySingle] = true;
|
|
return { value: single };
|
|
}
|
|
|
|
return { errors: error('array.base') };
|
|
}
|
|
|
|
if (!schema.$_getRule('items') &&
|
|
!schema.$_terms.externals) {
|
|
|
|
return;
|
|
}
|
|
|
|
return { value: value.slice() }; // Clone the array so that we don't modify the original
|
|
},
|
|
|
|
rules: {
|
|
|
|
has: {
|
|
method(schema) {
|
|
|
|
schema = this.$_compile(schema, { appendPath: true });
|
|
const obj = this.$_addRule({ name: 'has', args: { schema } });
|
|
obj.$_mutateRegister(schema);
|
|
return obj;
|
|
},
|
|
validate(value, { state, prefs, error }, { schema: has }) {
|
|
|
|
const ancestors = [value, ...state.ancestors];
|
|
for (let i = 0; i < value.length; ++i) {
|
|
const localState = state.localize([...state.path, i], ancestors, has);
|
|
if (has.$_match(value[i], localState, prefs)) {
|
|
return value;
|
|
}
|
|
}
|
|
|
|
const patternLabel = has._flags.label;
|
|
if (patternLabel) {
|
|
return error('array.hasKnown', { patternLabel });
|
|
}
|
|
|
|
return error('array.hasUnknown', null);
|
|
},
|
|
multi: true
|
|
},
|
|
|
|
items: {
|
|
method(...schemas) {
|
|
|
|
Common.verifyFlat(schemas, 'items');
|
|
|
|
const obj = this.$_addRule('items');
|
|
|
|
for (let i = 0; i < schemas.length; ++i) {
|
|
const type = Common.tryWithPath(() => this.$_compile(schemas[i]), i, { append: true });
|
|
obj.$_terms.items.push(type);
|
|
}
|
|
|
|
return obj.$_mutateRebuild();
|
|
},
|
|
validate(value, { schema, error, state, prefs, errorsArray }) {
|
|
|
|
const requireds = schema.$_terms._requireds.slice();
|
|
const ordereds = schema.$_terms.ordered.slice();
|
|
const inclusions = [...schema.$_terms._inclusions, ...requireds];
|
|
|
|
const wasArray = !value[Common.symbols.arraySingle];
|
|
delete value[Common.symbols.arraySingle];
|
|
|
|
const errors = errorsArray();
|
|
|
|
let il = value.length;
|
|
for (let i = 0; i < il; ++i) {
|
|
const item = value[i];
|
|
|
|
let errored = false;
|
|
let isValid = false;
|
|
|
|
const key = wasArray ? i : new Number(i); // eslint-disable-line no-new-wrappers
|
|
const path = [...state.path, key];
|
|
|
|
// Sparse
|
|
|
|
if (!schema._flags.sparse &&
|
|
item === undefined) {
|
|
|
|
errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));
|
|
if (prefs.abortEarly) {
|
|
return errors;
|
|
}
|
|
|
|
ordereds.shift();
|
|
continue;
|
|
}
|
|
|
|
// Exclusions
|
|
|
|
const ancestors = [value, ...state.ancestors];
|
|
|
|
for (const exclusion of schema.$_terms._exclusions) {
|
|
if (!exclusion.$_match(item, state.localize(path, ancestors, exclusion), prefs, { presence: 'ignore' })) {
|
|
continue;
|
|
}
|
|
|
|
errors.push(error('array.excludes', { pos: i, value: item }, state.localize(path)));
|
|
if (prefs.abortEarly) {
|
|
return errors;
|
|
}
|
|
|
|
errored = true;
|
|
ordereds.shift();
|
|
break;
|
|
}
|
|
|
|
if (errored) {
|
|
continue;
|
|
}
|
|
|
|
// Ordered
|
|
|
|
if (schema.$_terms.ordered.length) {
|
|
if (ordereds.length) {
|
|
const ordered = ordereds.shift();
|
|
const res = ordered.$_validate(item, state.localize(path, ancestors, ordered), prefs);
|
|
if (!res.errors) {
|
|
if (ordered._flags.result === 'strip') {
|
|
internals.fastSplice(value, i);
|
|
--i;
|
|
--il;
|
|
}
|
|
else if (!schema._flags.sparse && res.value === undefined) {
|
|
errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));
|
|
if (prefs.abortEarly) {
|
|
return errors;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
else {
|
|
value[i] = res.value;
|
|
}
|
|
}
|
|
else {
|
|
errors.push(...res.errors);
|
|
if (prefs.abortEarly) {
|
|
return errors;
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
else if (!schema.$_terms.items.length) {
|
|
errors.push(error('array.orderedLength', { pos: i, limit: schema.$_terms.ordered.length }));
|
|
if (prefs.abortEarly) {
|
|
return errors;
|
|
}
|
|
|
|
break; // No reason to continue since there are no other rules to validate other than array.orderedLength
|
|
}
|
|
}
|
|
|
|
// Requireds
|
|
|
|
const requiredChecks = [];
|
|
let jl = requireds.length;
|
|
for (let j = 0; j < jl; ++j) {
|
|
const localState = state.localize(path, ancestors, requireds[j]);
|
|
localState.snapshot();
|
|
|
|
const res = requireds[j].$_validate(item, localState, prefs);
|
|
requiredChecks[j] = res;
|
|
|
|
if (!res.errors) {
|
|
localState.commit();
|
|
value[i] = res.value;
|
|
isValid = true;
|
|
internals.fastSplice(requireds, j);
|
|
--j;
|
|
--jl;
|
|
|
|
if (!schema._flags.sparse &&
|
|
res.value === undefined) {
|
|
|
|
errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));
|
|
if (prefs.abortEarly) {
|
|
return errors;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
localState.restore();
|
|
}
|
|
|
|
if (isValid) {
|
|
continue;
|
|
}
|
|
|
|
// Inclusions
|
|
|
|
const stripUnknown = prefs.stripUnknown && !!prefs.stripUnknown.arrays || false;
|
|
|
|
jl = inclusions.length;
|
|
for (const inclusion of inclusions) {
|
|
|
|
// Avoid re-running requireds that already didn't match in the previous loop
|
|
|
|
let res;
|
|
const previousCheck = requireds.indexOf(inclusion);
|
|
if (previousCheck !== -1) {
|
|
res = requiredChecks[previousCheck];
|
|
}
|
|
else {
|
|
const localState = state.localize(path, ancestors, inclusion);
|
|
localState.snapshot();
|
|
|
|
res = inclusion.$_validate(item, localState, prefs);
|
|
if (!res.errors) {
|
|
localState.commit();
|
|
if (inclusion._flags.result === 'strip') {
|
|
internals.fastSplice(value, i);
|
|
--i;
|
|
--il;
|
|
}
|
|
else if (!schema._flags.sparse &&
|
|
res.value === undefined) {
|
|
|
|
errors.push(error('array.sparse', { key, path, pos: i, value: undefined }, state.localize(path)));
|
|
errored = true;
|
|
}
|
|
else {
|
|
value[i] = res.value;
|
|
}
|
|
|
|
isValid = true;
|
|
break;
|
|
}
|
|
|
|
localState.restore();
|
|
}
|
|
|
|
// Return the actual error if only one inclusion defined
|
|
|
|
if (jl === 1) {
|
|
if (stripUnknown) {
|
|
internals.fastSplice(value, i);
|
|
--i;
|
|
--il;
|
|
isValid = true;
|
|
break;
|
|
}
|
|
|
|
errors.push(...res.errors);
|
|
if (prefs.abortEarly) {
|
|
return errors;
|
|
}
|
|
|
|
errored = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (errored) {
|
|
continue;
|
|
}
|
|
|
|
if ((schema.$_terms._inclusions.length || schema.$_terms._requireds.length) &&
|
|
!isValid) {
|
|
|
|
if (stripUnknown) {
|
|
internals.fastSplice(value, i);
|
|
--i;
|
|
--il;
|
|
continue;
|
|
}
|
|
|
|
errors.push(error('array.includes', { pos: i, value: item }, state.localize(path)));
|
|
if (prefs.abortEarly) {
|
|
return errors;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (requireds.length) {
|
|
internals.fillMissedErrors(schema, errors, requireds, value, state, prefs);
|
|
}
|
|
|
|
if (ordereds.length) {
|
|
internals.fillOrderedErrors(schema, errors, ordereds, value, state, prefs);
|
|
|
|
if (!errors.length) {
|
|
internals.fillDefault(ordereds, value, state, prefs);
|
|
}
|
|
}
|
|
|
|
return errors.length ? errors : value;
|
|
},
|
|
|
|
priority: true,
|
|
manifest: false
|
|
},
|
|
|
|
length: {
|
|
method(limit) {
|
|
|
|
return this.$_addRule({ name: 'length', args: { limit }, operator: '=' });
|
|
},
|
|
validate(value, helpers, { limit }, { name, operator, args }) {
|
|
|
|
if (Common.compare(value.length, limit, operator)) {
|
|
return value;
|
|
}
|
|
|
|
return helpers.error('array.' + name, { limit: args.limit, value });
|
|
},
|
|
args: [
|
|
{
|
|
name: 'limit',
|
|
ref: true,
|
|
assert: Common.limit,
|
|
message: 'must be a positive integer'
|
|
}
|
|
]
|
|
},
|
|
|
|
max: {
|
|
method(limit) {
|
|
|
|
return this.$_addRule({ name: 'max', method: 'length', args: { limit }, operator: '<=' });
|
|
}
|
|
},
|
|
|
|
min: {
|
|
method(limit) {
|
|
|
|
return this.$_addRule({ name: 'min', method: 'length', args: { limit }, operator: '>=' });
|
|
}
|
|
},
|
|
|
|
ordered: {
|
|
method(...schemas) {
|
|
|
|
Common.verifyFlat(schemas, 'ordered');
|
|
|
|
const obj = this.$_addRule('items');
|
|
|
|
for (let i = 0; i < schemas.length; ++i) {
|
|
const type = Common.tryWithPath(() => this.$_compile(schemas[i]), i, { append: true });
|
|
internals.validateSingle(type, obj);
|
|
|
|
obj.$_mutateRegister(type);
|
|
obj.$_terms.ordered.push(type);
|
|
}
|
|
|
|
return obj.$_mutateRebuild();
|
|
}
|
|
},
|
|
|
|
single: {
|
|
method(enabled) {
|
|
|
|
const value = enabled === undefined ? true : !!enabled;
|
|
assert(!value || !this._flags._arrayItems, 'Cannot specify single rule when array has array items');
|
|
|
|
return this.$_setFlag('single', value);
|
|
}
|
|
},
|
|
|
|
sort: {
|
|
method(options = {}) {
|
|
|
|
Common.assertOptions(options, ['by', 'order']);
|
|
|
|
const settings = {
|
|
order: options.order || 'ascending'
|
|
};
|
|
|
|
if (options.by) {
|
|
settings.by = Compile.ref(options.by, { ancestor: 0 });
|
|
assert(!settings.by.ancestor, 'Cannot sort by ancestor');
|
|
}
|
|
|
|
return this.$_addRule({ name: 'sort', args: { options: settings } });
|
|
},
|
|
validate(value, { error, state, prefs, schema }, { options }) {
|
|
|
|
const { value: sorted, errors } = internals.sort(schema, value, options, state, prefs);
|
|
if (errors) {
|
|
return errors;
|
|
}
|
|
|
|
for (let i = 0; i < value.length; ++i) {
|
|
if (value[i] !== sorted[i]) {
|
|
return error('array.sort', { order: options.order, by: options.by ? options.by.key : 'value' });
|
|
}
|
|
}
|
|
|
|
return value;
|
|
},
|
|
convert: true
|
|
},
|
|
|
|
sparse: {
|
|
method(enabled) {
|
|
|
|
const value = enabled === undefined ? true : !!enabled;
|
|
|
|
if (this._flags.sparse === value) {
|
|
return this;
|
|
}
|
|
|
|
const obj = value ? this.clone() : this.$_addRule('items');
|
|
return obj.$_setFlag('sparse', value, { clone: false });
|
|
}
|
|
},
|
|
|
|
unique: {
|
|
method(comparator, options = {}) {
|
|
|
|
assert(!comparator || typeof comparator === 'function' || typeof comparator === 'string', 'comparator must be a function or a string');
|
|
Common.assertOptions(options, ['ignoreUndefined', 'separator']);
|
|
|
|
const rule = { name: 'unique', args: { options, comparator } };
|
|
|
|
if (comparator) {
|
|
if (typeof comparator === 'string') {
|
|
const separator = Common.default(options.separator, '.');
|
|
rule.path = separator ? comparator.split(separator) : [comparator];
|
|
}
|
|
else {
|
|
rule.comparator = comparator;
|
|
}
|
|
}
|
|
|
|
return this.$_addRule(rule);
|
|
},
|
|
validate(value, { state, error, schema }, { comparator: raw, options }, { comparator, path }) {
|
|
|
|
const found = {
|
|
string: Object.create(null),
|
|
number: Object.create(null),
|
|
undefined: Object.create(null),
|
|
boolean: Object.create(null),
|
|
bigint: Object.create(null),
|
|
object: new Map(),
|
|
function: new Map(),
|
|
custom: new Map()
|
|
};
|
|
|
|
const compare = comparator || deepEqual;
|
|
const ignoreUndefined = options.ignoreUndefined;
|
|
|
|
for (let i = 0; i < value.length; ++i) {
|
|
const item = path ? reach(value[i], path) : value[i];
|
|
const records = comparator ? found.custom : found[typeof item];
|
|
assert(records, 'Failed to find unique map container for type', typeof item);
|
|
|
|
if (records instanceof Map) {
|
|
const entries = records.entries();
|
|
let current;
|
|
while (!(current = entries.next()).done) {
|
|
if (compare(current.value[0], item)) {
|
|
const localState = state.localize([...state.path, i], [value, ...state.ancestors]);
|
|
const context = {
|
|
pos: i,
|
|
value: value[i],
|
|
dupePos: current.value[1],
|
|
dupeValue: value[current.value[1]]
|
|
};
|
|
|
|
if (path) {
|
|
context.path = raw;
|
|
}
|
|
|
|
return error('array.unique', context, localState);
|
|
}
|
|
}
|
|
|
|
records.set(item, i);
|
|
}
|
|
else {
|
|
if ((!ignoreUndefined || item !== undefined) &&
|
|
records[item] !== undefined) {
|
|
|
|
const context = {
|
|
pos: i,
|
|
value: value[i],
|
|
dupePos: records[item],
|
|
dupeValue: value[records[item]]
|
|
};
|
|
|
|
if (path) {
|
|
context.path = raw;
|
|
}
|
|
|
|
const localState = state.localize([...state.path, i], [value, ...state.ancestors]);
|
|
return error('array.unique', context, localState);
|
|
}
|
|
|
|
records[item] = i;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
},
|
|
args: ['comparator', 'options'],
|
|
multi: true
|
|
}
|
|
},
|
|
|
|
overrides: {
|
|
|
|
isAsync() {
|
|
|
|
if (this.$_terms.externals?.length) {
|
|
return true;
|
|
}
|
|
|
|
for (const item of this.$_terms.items) {
|
|
if (item.isAsync()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
for (const item of this.$_terms.ordered) {
|
|
if (item.isAsync()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
},
|
|
|
|
cast: {
|
|
set: {
|
|
from: Array.isArray,
|
|
to(value, helpers) {
|
|
|
|
return new Set(value);
|
|
}
|
|
}
|
|
},
|
|
|
|
rebuild(schema) {
|
|
|
|
schema.$_terms._inclusions = [];
|
|
schema.$_terms._exclusions = [];
|
|
schema.$_terms._requireds = [];
|
|
|
|
for (const type of schema.$_terms.items) {
|
|
internals.validateSingle(type, schema);
|
|
|
|
if (type._flags.presence === 'required') {
|
|
schema.$_terms._requireds.push(type);
|
|
}
|
|
else if (type._flags.presence === 'forbidden') {
|
|
schema.$_terms._exclusions.push(type);
|
|
}
|
|
else {
|
|
schema.$_terms._inclusions.push(type);
|
|
}
|
|
}
|
|
|
|
for (const type of schema.$_terms.ordered) {
|
|
internals.validateSingle(type, schema);
|
|
}
|
|
},
|
|
|
|
manifest: {
|
|
|
|
build(obj, desc) {
|
|
|
|
if (desc.items) {
|
|
obj = obj.items(...desc.items);
|
|
}
|
|
|
|
if (desc.ordered) {
|
|
obj = obj.ordered(...desc.ordered);
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
},
|
|
|
|
messages: {
|
|
'array.base': '{{#label}} must be an array',
|
|
'array.excludes': '{{#label}} contains an excluded value',
|
|
'array.hasKnown': '{{#label}} does not contain at least one required match for type {:#patternLabel}',
|
|
'array.hasUnknown': '{{#label}} does not contain at least one required match',
|
|
'array.includes': '{{#label}} does not match any of the allowed types',
|
|
'array.includesRequiredBoth': '{{#label}} does not contain {{#knownMisses}} and {{#unknownMisses}} other required value(s)',
|
|
'array.includesRequiredKnowns': '{{#label}} does not contain {{#knownMisses}}',
|
|
'array.includesRequiredUnknowns': '{{#label}} does not contain {{#unknownMisses}} required value(s)',
|
|
'array.length': '{{#label}} must contain {{#limit}} items',
|
|
'array.max': '{{#label}} must contain less than or equal to {{#limit}} items',
|
|
'array.min': '{{#label}} must contain at least {{#limit}} items',
|
|
'array.orderedLength': '{{#label}} must contain at most {{#limit}} items',
|
|
'array.sort': '{{#label}} must be sorted in {#order} order by {{#by}}',
|
|
'array.sort.mismatching': '{{#label}} cannot be sorted due to mismatching types',
|
|
'array.sort.unsupported': '{{#label}} cannot be sorted due to unsupported type {#type}',
|
|
'array.sparse': '{{#label}} must not be a sparse array item',
|
|
'array.unique': '{{#label}} contains a duplicate value'
|
|
}
|
|
});
|
|
|
|
|
|
// Helpers
|
|
|
|
internals.fillMissedErrors = function (schema, errors, requireds, value, state, prefs) {
|
|
|
|
const knownMisses = [];
|
|
let unknownMisses = 0;
|
|
for (const required of requireds) {
|
|
const label = required._flags.label;
|
|
if (label) {
|
|
knownMisses.push(label);
|
|
}
|
|
else {
|
|
++unknownMisses;
|
|
}
|
|
}
|
|
|
|
if (knownMisses.length) {
|
|
if (unknownMisses) {
|
|
errors.push(schema.$_createError('array.includesRequiredBoth', value, { knownMisses, unknownMisses }, state, prefs));
|
|
}
|
|
else {
|
|
errors.push(schema.$_createError('array.includesRequiredKnowns', value, { knownMisses }, state, prefs));
|
|
}
|
|
}
|
|
else {
|
|
errors.push(schema.$_createError('array.includesRequiredUnknowns', value, { unknownMisses }, state, prefs));
|
|
}
|
|
};
|
|
|
|
|
|
internals.fillOrderedErrors = function (schema, errors, ordereds, value, state, prefs) {
|
|
|
|
const requiredOrdereds = [];
|
|
|
|
for (const ordered of ordereds) {
|
|
if (ordered._flags.presence === 'required') {
|
|
requiredOrdereds.push(ordered);
|
|
}
|
|
}
|
|
|
|
if (requiredOrdereds.length) {
|
|
internals.fillMissedErrors(schema, errors, requiredOrdereds, value, state, prefs);
|
|
}
|
|
};
|
|
|
|
|
|
internals.fillDefault = function (ordereds, value, state, prefs) {
|
|
|
|
const overrides = [];
|
|
let trailingUndefined = true;
|
|
|
|
for (let i = ordereds.length - 1; i >= 0; --i) {
|
|
const ordered = ordereds[i];
|
|
const ancestors = [value, ...state.ancestors];
|
|
const override = ordered.$_validate(undefined, state.localize(state.path, ancestors, ordered), prefs).value;
|
|
|
|
if (trailingUndefined) {
|
|
if (override === undefined) {
|
|
continue;
|
|
}
|
|
|
|
trailingUndefined = false;
|
|
}
|
|
|
|
overrides.unshift(override);
|
|
}
|
|
|
|
if (overrides.length) {
|
|
value.push(...overrides);
|
|
}
|
|
};
|
|
|
|
|
|
internals.fastSplice = function (arr, i) {
|
|
|
|
let pos = i;
|
|
while (pos < arr.length) {
|
|
arr[pos++] = arr[pos];
|
|
}
|
|
|
|
--arr.length;
|
|
};
|
|
|
|
|
|
internals.validateSingle = function (type, obj) {
|
|
|
|
if (type.type === 'array' ||
|
|
type._flags._arrayItems) {
|
|
|
|
assert(!obj._flags.single, 'Cannot specify array item with single rule enabled');
|
|
obj.$_setFlag('_arrayItems', true, { clone: false });
|
|
}
|
|
};
|
|
|
|
|
|
internals.sort = function (schema, value, settings, state, prefs) {
|
|
|
|
const order = settings.order === 'ascending' ? 1 : -1;
|
|
const aFirst = -1 * order;
|
|
const bFirst = order;
|
|
|
|
const sort = (a, b) => {
|
|
|
|
let compare = internals.compare(a, b, aFirst, bFirst);
|
|
if (compare !== null) {
|
|
return compare;
|
|
}
|
|
|
|
if (settings.by) {
|
|
a = settings.by.resolve(a, state, prefs);
|
|
b = settings.by.resolve(b, state, prefs);
|
|
}
|
|
|
|
compare = internals.compare(a, b, aFirst, bFirst);
|
|
if (compare !== null) {
|
|
return compare;
|
|
}
|
|
|
|
const type = typeof a;
|
|
if (type !== typeof b) {
|
|
throw schema.$_createError('array.sort.mismatching', value, null, state, prefs);
|
|
}
|
|
|
|
if (type !== 'number' &&
|
|
type !== 'string') {
|
|
|
|
throw schema.$_createError('array.sort.unsupported', value, { type }, state, prefs);
|
|
}
|
|
|
|
if (type === 'number') {
|
|
return (a - b) * order;
|
|
}
|
|
|
|
return a < b ? aFirst : bFirst;
|
|
};
|
|
|
|
try {
|
|
return { value: value.slice().sort(sort) };
|
|
}
|
|
catch (err) {
|
|
return { errors: err };
|
|
}
|
|
};
|
|
|
|
|
|
internals.compare = function (a, b, aFirst, bFirst) {
|
|
|
|
if (a === b) {
|
|
return 0;
|
|
}
|
|
|
|
if (a === undefined) {
|
|
return 1; // Always last regardless of sort order
|
|
}
|
|
|
|
if (b === undefined) {
|
|
return -1; // Always last regardless of sort order
|
|
}
|
|
|
|
if (a === null) {
|
|
return bFirst;
|
|
}
|
|
|
|
if (b === null) {
|
|
return aFirst;
|
|
}
|
|
|
|
return null;
|
|
};
|