UNPKG

@jsonjoy.com/json-expression

Version:

High-performance JSON Pointer implementation

441 lines (440 loc) 18.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.objDelRaw = exports.objSetRaw = exports.entries = exports.values = exports.keys = exports.asObj = exports.reduce = exports.map = exports.filter = exports.zip = exports.indexOf2 = exports.indexOf = exports.fromEntries = exports.isInArr2 = exports.isInArr = exports.concat = exports.head = exports.asArr = exports.u8 = exports.isDateTime = exports.isTime = exports.isDate = exports.isDuration = exports.isUri = exports.isUuid = exports.isIp6 = exports.isIp4 = exports.isHostname = exports.isEmail = exports.substr = exports.ends = exports.contains = exports.starts = exports.asStr = exports.asBin = exports.member = exports.len = exports.mod = exports.slash = exports.betweenEqEq = exports.betweenEqNe = exports.betweenNeEq = exports.betweenNeNe = exports.cmp = exports.str = exports.int = exports.num = exports.type = exports.throwOnUndef = exports.get = void 0; exports.parseVar = exports.operatorsToMap = exports.assertArity = exports.assertVariadicArity = exports.assertFixedArity = exports.literal = exports.asLiteral = exports.isLiteral = void 0; const deepEqual_1 = require("@jsonjoy.com/util/lib/json-equal/deepEqual"); const json_pointer_1 = require("@jsonjoy.com/json-pointer"); // ----------------------------------------------------- Input operator helpers const get = (path, data) => (0, json_pointer_1.get)(data, (0, json_pointer_1.toPath)(path)); exports.get = get; const throwOnUndef = (value, def) => { if (value !== undefined) return value; if (def === undefined) throw new Error('NOT_FOUND'); return def; }; exports.throwOnUndef = throwOnUndef; // ------------------------------------------------------ Type operator helpers const type = (value) => { if (value === null) return 'null'; if (Array.isArray(value)) return 'array'; if (value instanceof Uint8Array) return 'binary'; return typeof value; }; exports.type = type; const num = (value) => +value || 0; exports.num = num; const int = (value) => ~~value; exports.int = int; const str = (value) => { if (typeof value !== 'object') return '' + value; return JSON.stringify(value); }; exports.str = str; // ------------------------------------------------ Comparison operator helpers const cmp = (a, b) => (a > b ? 1 : a < b ? -1 : 0); exports.cmp = cmp; const betweenNeNe = (val, min, max) => val > min && val < max; exports.betweenNeNe = betweenNeNe; const betweenNeEq = (val, min, max) => val > min && val <= max; exports.betweenNeEq = betweenNeEq; const betweenEqNe = (val, min, max) => val >= min && val < max; exports.betweenEqNe = betweenEqNe; const betweenEqEq = (val, min, max) => val >= min && val <= max; exports.betweenEqEq = betweenEqEq; // ------------------------------------------------ Arithmetic operator helpers const slash = (a, b) => { const divisor = (0, exports.num)(b); if (divisor === 0) throw new Error('DIVISION_BY_ZERO'); const res = (0, exports.num)(a) / divisor; return Number.isFinite(res) ? res : 0; }; exports.slash = slash; const mod = (a, b) => { const divisor = (0, exports.num)(b); if (divisor === 0) throw new Error('DIVISION_BY_ZERO'); const res = (0, exports.num)(a) % divisor; return Number.isFinite(res) ? res : 0; }; exports.mod = mod; // ----------------------------------------- Generic container operator helpers const len = (value) => { switch (typeof value) { case 'string': return value.length; case 'object': { if (Array.isArray(value)) return value.length; if (value instanceof Uint8Array) return value.length; if (!value) return 0; return Object.keys(value).length; } default: return 0; } }; exports.len = len; const member = (container, index) => { switch (typeof container) { case 'string': { const i = (0, exports.int)(index); if (i < 0 || i >= container.length) return undefined; return container[i]; } case 'object': { if (!container) throw new Error('NOT_CONTAINER'); if (Array.isArray(container) || container instanceof Uint8Array) { const i = (0, exports.int)(index); if (i < 0 || i >= container.length) return undefined; return container[i]; } switch (typeof index) { case 'string': case 'number': return container[index]; default: throw new Error('NOT_STRING_INDEX'); } } default: throw new Error('NOT_CONTAINER'); } }; exports.member = member; const asBin = (value) => { if (value instanceof Uint8Array) return value; throw new Error('NOT_BINARY'); }; exports.asBin = asBin; // ---------------------------------------------------- String operator helpers const asStr = (value) => { if (typeof value === 'string') return value; throw new Error('NOT_STRING'); }; exports.asStr = asStr; const starts = (outer, inner) => { return (0, exports.str)(outer).startsWith((0, exports.str)(inner)); }; exports.starts = starts; const contains = (outer, inner) => { return (0, exports.str)(outer).indexOf((0, exports.str)(inner)) > -1; }; exports.contains = contains; const ends = (outer, inner) => { return (0, exports.str)(outer).endsWith((0, exports.str)(inner)); }; exports.ends = ends; const substr = (probablyString, from, to) => (0, exports.str)(probablyString).slice((0, exports.int)(from), (0, exports.int)(to)); exports.substr = substr; const EMAIL_REG = /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i; const HOSTNAME_REG = /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i; const IP4_REG = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/; const IP6_REG = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i; const UUID_REG = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i; const NOT_URI_FRAGMENT_REG = /\/|:/; const URI_REG = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; const DURATION_REG = /^P(?!$)((\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?|(\d+W)?)$/; const DATE_REG = /^(\d\d\d\d)-(\d\d)-(\d\d)$/; const TIME_REG = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i; const DATE_TIME_SEPARATOR_REG = /t|\s/i; const isEmail = (value) => typeof value === 'string' && EMAIL_REG.test(value); exports.isEmail = isEmail; const isHostname = (value) => typeof value === 'string' && HOSTNAME_REG.test(value); exports.isHostname = isHostname; const isIp4 = (value) => typeof value === 'string' && IP4_REG.test(value); exports.isIp4 = isIp4; const isIp6 = (value) => typeof value === 'string' && IP6_REG.test(value); exports.isIp6 = isIp6; const isUuid = (value) => typeof value === 'string' && UUID_REG.test(value); exports.isUuid = isUuid; const isUri = (value) => typeof value === 'string' && NOT_URI_FRAGMENT_REG.test(value) && URI_REG.test(value); exports.isUri = isUri; const isDuration = (value) => typeof value === 'string' && DURATION_REG.test(value); exports.isDuration = isDuration; const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; const isLeapYear = (year) => year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0); const isDate = (value) => { if (typeof value !== 'string') return false; const matches = DATE_REG.exec(value); if (!matches) return false; const year = +matches[1]; const month = +matches[2]; const day = +matches[3]; return month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]); }; exports.isDate = isDate; const isTime = (value) => { if (typeof value !== 'string') return false; const matches = TIME_REG.exec(value); if (!matches) return false; const hr = +matches[1]; const min = +matches[2]; const sec = +matches[3]; const tz = matches[4]; const tzSign = matches[5] === '-' ? -1 : 1; const tzH = +(matches[6] || 0); const tzM = +(matches[7] || 0); if (tzH > 23 || tzM > 59 || !tz) return false; if (hr <= 23 && min <= 59 && sec < 60) return true; const utcMin = min - tzM * tzSign; const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0); return (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61; }; exports.isTime = isTime; const isDateTime = (str) => { if (typeof str !== 'string') return false; const dateTime = str.split(DATE_TIME_SEPARATOR_REG); return dateTime.length === 2 && (0, exports.isDate)(dateTime[0]) && (0, exports.isTime)(dateTime[1]); }; exports.isDateTime = isDateTime; // ---------------------------------------------------- Binary operator helpers const u8 = (bin, pos) => { const buf = (0, exports.asBin)(bin); const index = (0, exports.int)(pos); if (index < 0 || index >= buf.length) throw new Error('OUT_OF_BOUNDS'); return buf[index]; }; exports.u8 = u8; // ----------------------------------------------------- Array operator helpers const asArr = (value) => { if (Array.isArray(value)) return value; throw new Error('NOT_ARRAY'); }; exports.asArr = asArr; const head = (operand1, operand2) => { const arr = (0, exports.asArr)(operand1); const count = (0, exports.int)(operand2); return count >= 0 ? arr.slice(0, count) : arr.slice(count); }; exports.head = head; const concat = (arrays) => { const result = []; for (const array of arrays) { (0, exports.asArr)(array); for (const item of array) result.push(item); } return result; }; exports.concat = concat; const isInArr = (arr, what) => { const arr2 = (0, exports.asArr)(arr); const length = arr2.length; for (let i = 0; i < length; i++) if ((0, deepEqual_1.deepEqual)(arr2[i], what)) return true; return false; }; exports.isInArr = isInArr; const isInArr2 = (arr, check) => { const arr2 = (0, exports.asArr)(arr); const length = arr2.length; for (let i = 0; i < length; i++) if (check(arr2[i])) return true; return false; }; exports.isInArr2 = isInArr2; const fromEntries = (maybeEntries) => { const entries = (0, exports.asArr)(maybeEntries); const result = {}; for (const maybeEntry of entries) { const entry = (0, exports.asArr)(maybeEntry); const [key, value] = (0, exports.asArr)(entry); if (entry.length !== 2) throw new Error('NOT_PAIR'); result[(0, exports.str)(key)] = value; } return result; }; exports.fromEntries = fromEntries; const indexOf = (container, item) => { const arr = (0, exports.asArr)(container); const length = arr.length; for (let i = 0; i < length; i++) if ((0, deepEqual_1.deepEqual)(arr[i], item)) return i; return -1; }; exports.indexOf = indexOf; const indexOf2 = (container, check) => { const arr = (0, exports.asArr)(container); const length = arr.length; for (let i = 0; i < length; i++) if (check(arr[i])) return i; return -1; }; exports.indexOf2 = indexOf2; const zip = (maybeArr1, maybeArr2) => { const arr1 = (0, exports.asArr)(maybeArr1); const arr2 = (0, exports.asArr)(maybeArr2); const length = Math.min(arr1.length, arr2.length); const result = []; for (let i = 0; i < length; i++) result.push([arr1[i], arr2[i]]); return result; }; exports.zip = zip; const filter = (arr, varname, vars, run) => { const result = arr.filter((item) => { vars.set(varname, item); return run(); }); vars.del(varname); return result; }; exports.filter = filter; const map = (arr, varname, vars, run) => { const result = arr.map((item) => { vars.set(varname, item); return run(); }); vars.del(varname); return result; }; exports.map = map; const reduce = (arr, initialValue, accname, varname, vars, run) => { vars.set(accname, initialValue); for (const item of arr) { vars.set(varname, item); const res = run(); vars.set(accname, res); } const result = vars.get(accname); vars.del(accname); vars.del(varname); return result; }; exports.reduce = reduce; // ---------------------------------------------------- Object operator helpers const asObj = (value) => { if ((0, exports.type)(value) === 'object') return value; throw new Error('NOT_OBJECT'); }; exports.asObj = asObj; const keys = (value) => Object.keys((0, exports.asObj)(value)); exports.keys = keys; const values = (value) => { const values = []; const theKeys = (0, exports.keys)(value); const length = theKeys.length; for (let i = 0; i < length; i++) values.push(value[theKeys[i]]); return values; }; exports.values = values; const entries = (value) => { const entries = []; const theKeys = (0, exports.keys)(value); const length = theKeys.length; for (let i = 0; i < length; i++) { const key = theKeys[i]; entries.push([key, value[key]]); } return entries; }; exports.entries = entries; const objSetRaw = (obj, key, value) => { const prop = (0, exports.str)(key); if (prop === '__proto__') throw new Error('PROTO_KEY'); obj[prop] = value; return obj; }; exports.objSetRaw = objSetRaw; const objDelRaw = (obj, key) => { delete obj[key]; return obj; }; exports.objDelRaw = objDelRaw; // -------------------------------------------------------------------- Various const isLiteral = (value) => { if (Array.isArray(value)) return value.length === 1; else return true; }; exports.isLiteral = isLiteral; const asLiteral = (value) => { if (Array.isArray(value)) { if (value.length !== 1) throw new Error('Invalid literal.'); return value[0]; } else return value; }; exports.asLiteral = asLiteral; const literal = (value) => (Array.isArray(value) ? [value] : value); exports.literal = literal; const assertFixedArity = (operator, arity, expr) => { if (expr.length !== arity + 1) throw new Error(`"${operator}" operator expects ${arity} operands.`); }; exports.assertFixedArity = assertFixedArity; const assertVariadicArity = (operator, expr) => { if (expr.length < 3) throw new Error(`"${operator}" operator expects at least two operands.`); }; exports.assertVariadicArity = assertVariadicArity; const assertArity = (operator, arity, expr) => { if (!arity) return; if (Array.isArray(arity)) { const [min, max] = arity; if (expr.length < min + 1) throw new Error(`"${operator}" operator expects at least ${min} operands.`); if (max !== -1 && expr.length > max + 1) throw new Error(`"${operator}" operator expects at most ${max} operands.`); } else if (arity !== -1) (0, exports.assertFixedArity)(operator, arity, expr); else (0, exports.assertVariadicArity)(operator, expr); }; exports.assertArity = assertArity; const operatorsToMap = (operators) => { const map = new Map(); for (const operator of operators) { const [name, aliases] = operator; map.set(name, operator); for (const alias of aliases) map.set(alias, operator); } return map; }; exports.operatorsToMap = operatorsToMap; const parseVar = (name) => { if (name[0] === '/') return ['', name]; const slashIndex = name.indexOf('/'); if (slashIndex === -1) return [name, '']; return [name.slice(0, slashIndex), name.slice(slashIndex)]; }; exports.parseVar = parseVar;