237 lines
24 KiB
JavaScript
237 lines
24 KiB
JavaScript
"use strict";
|
|||
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
|
exports.applyDefaults = applyDefaults;
|
||
|
|
exports.isEmpty = isEmpty;
|
||
|
|
exports.deepClone = deepClone;
|
||
|
|
exports.mapObject = mapObject;
|
||
|
|
exports.makeObject = makeObject;
|
||
|
|
exports.deepGet = deepGet;
|
||
|
|
exports.deepSet = deepSet;
|
||
|
|
exports.deepMerge = deepMerge;
|
||
|
|
exports.splitBySize = splitBySize;
|
||
|
|
exports.transformObjectKeys = transformObjectKeys;
|
||
|
|
exports.noUndefined = noUndefined;
|
||
|
|
const types_1 = require("./types");
|
||
|
|
const toolkit_error_1 = require("../toolkit/toolkit-error");
|
||
|
|
/**
|
||
|
|
* Return a new object by adding missing keys into another object
|
||
|
|
*/
|
||
|
|
function applyDefaults(hash, defaults) {
|
||
|
|
const result = {};
|
||
|
|
Object.keys(hash).forEach(k => result[k] = hash[k]);
|
||
|
|
Object.keys(defaults)
|
||
|
|
.filter(k => !(k in result))
|
||
|
|
.forEach(k => result[k] = defaults[k]);
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Return whether the given parameter is an empty object or empty list.
|
||
|
|
*/
|
||
|
|
function isEmpty(x) {
|
||
|
|
if (x == null) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
if ((0, types_1.isArray)(x)) {
|
||
|
|
return x.length === 0;
|
||
|
|
}
|
||
|
|
return Object.keys(x).length === 0;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Deep clone a tree of objects, lists or scalars
|
||
|
|
*
|
||
|
|
* Does not support cycles.
|
||
|
|
*/
|
||
|
|
function deepClone(x) {
|
||
|
|
if (typeof x === 'undefined') {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
if (x === null) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
if ((0, types_1.isArray)(x)) {
|
||
|
|
return x.map(deepClone);
|
||
|
|
}
|
||
|
|
if ((0, types_1.isObject)(x)) {
|
||
|
|
return makeObject(mapObject(x, (k, v) => [k, deepClone(v)]));
|
||
|
|
}
|
||
|
|
return x;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Map over an object, treating it as a dictionary
|
||
|
|
*/
|
||
|
|
function mapObject(x, fn) {
|
||
|
|
const ret = [];
|
||
|
|
Object.keys(x).forEach(key => {
|
||
|
|
ret.push(fn(key, x[key]));
|
||
|
|
});
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Construct an object from a list of (k, v) pairs
|
||
|
|
*/
|
||
|
|
function makeObject(pairs) {
|
||
|
|
const ret = {};
|
||
|
|
for (const pair of pairs) {
|
||
|
|
ret[pair[0]] = pair[1];
|
||
|
|
}
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Deep get a value from a tree of nested objects
|
||
|
|
*
|
||
|
|
* Returns undefined if any part of the path was unset or
|
||
|
|
* not an object.
|
||
|
|
*/
|
||
|
|
function deepGet(x, path) {
|
||
|
|
path = path.slice();
|
||
|
|
while (path.length > 0 && (0, types_1.isObject)(x)) {
|
||
|
|
const key = path.shift();
|
||
|
|
x = x[key];
|
||
|
|
}
|
||
|
|
return path.length === 0 ? x : undefined;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Deep set a value in a tree of nested objects
|
||
|
|
*
|
||
|
|
* Throws an error if any part of the path is not an object.
|
||
|
|
*/
|
||
|
|
function deepSet(x, path, value) {
|
||
|
|
path = path.slice();
|
||
|
|
if (path.length === 0) {
|
||
|
|
throw new toolkit_error_1.ToolkitError('Path may not be empty');
|
||
|
|
}
|
||
|
|
while (path.length > 1 && (0, types_1.isObject)(x)) {
|
||
|
|
const key = path.shift();
|
||
|
|
if (isPrototypePollutingKey(key)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (!(key in x)) {
|
||
|
|
x[key] = {};
|
||
|
|
}
|
||
|
|
x = x[key];
|
||
|
|
}
|
||
|
|
if (!(0, types_1.isObject)(x)) {
|
||
|
|
throw new toolkit_error_1.ToolkitError(`Expected an object, got '${x}'`);
|
||
|
|
}
|
||
|
|
const finalKey = path[0];
|
||
|
|
if (isPrototypePollutingKey(finalKey)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (value !== undefined) {
|
||
|
|
x[finalKey] = value;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
delete x[finalKey];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Helper to detect prototype polluting keys
|
||
|
|
*
|
||
|
|
* A key matching this, MUST NOT be used in an assignment.
|
||
|
|
* Use this to check user-input.
|
||
|
|
*/
|
||
|
|
function isPrototypePollutingKey(key) {
|
||
|
|
return key === '__proto__' || key === 'constructor' || key === 'prototype';
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Recursively merge objects together
|
||
|
|
*
|
||
|
|
* The leftmost object is mutated and returned. Arrays are not merged
|
||
|
|
* but overwritten just like scalars.
|
||
|
|
*
|
||
|
|
* If an object is merged into a non-object, the non-object is lost.
|
||
|
|
*/
|
||
|
|
function deepMerge(...objects) {
|
||
|
|
function mergeOne(target, source) {
|
||
|
|
for (const key of Object.keys(source)) {
|
||
|
|
if (isPrototypePollutingKey(key)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const value = source[key];
|
||
|
|
if ((0, types_1.isObject)(value)) {
|
||
|
|
if (!(0, types_1.isObject)(target[key])) {
|
||
|
|
target[key] = {};
|
||
|
|
} // Overwrite on purpose
|
||
|
|
mergeOne(target[key], value);
|
||
|
|
}
|
||
|
|
else if (typeof value !== 'undefined') {
|
||
|
|
target[key] = value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const others = objects.filter(x => x != null);
|
||
|
|
if (others.length === 0) {
|
||
|
|
return {};
|
||
|
|
}
|
||
|
|
const into = others.splice(0, 1)[0];
|
||
|
|
others.forEach(other => mergeOne(into, other));
|
||
|
|
return into;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Splits the given object into two, such that:
|
||
|
|
*
|
||
|
|
* 1. The size of the first object (after stringified in UTF-8) is less than or equal to the provided size limit.
|
||
|
|
* 2. Merging the two objects results in the original one.
|
||
|
|
*/
|
||
|
|
function splitBySize(data, maxSizeBytes) {
|
||
|
|
if (maxSizeBytes < 2) {
|
||
|
|
// It's impossible to fit anything in the first object
|
||
|
|
return [undefined, data];
|
||
|
|
}
|
||
|
|
const entries = Object.entries(data);
|
||
|
|
return recurse(0, 0);
|
||
|
|
function recurse(index, runningTotalSize) {
|
||
|
|
if (index >= entries.length) {
|
||
|
|
// Everything fits in the first object
|
||
|
|
return [data, undefined];
|
||
|
|
}
|
||
|
|
const size = runningTotalSize + entrySize(entries[index]);
|
||
|
|
return (size > maxSizeBytes) ? cutAt(index) : recurse(index + 1, size);
|
||
|
|
}
|
||
|
|
function entrySize(entry) {
|
||
|
|
return Buffer.byteLength(JSON.stringify(Object.fromEntries([entry])));
|
||
|
|
}
|
||
|
|
function cutAt(index) {
|
||
|
|
return [
|
||
|
|
Object.fromEntries(entries.slice(0, index)),
|
||
|
|
Object.fromEntries(entries.slice(index)),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* This function transforms all keys (recursively) in the provided `val` object.
|
||
|
|
*
|
||
|
|
* @param val - The object whose keys need to be transformed.
|
||
|
|
* @param transform - The function that will be applied to each key.
|
||
|
|
* @param exclude - The keys that will not be transformed and copied to output directly
|
||
|
|
* @returns A new object with the same values as `val`, but with all keys transformed according to `transform`.
|
||
|
|
*/
|
||
|
|
function transformObjectKeys(val, transform, exclude = {}) {
|
||
|
|
if (val == null || typeof val !== 'object') {
|
||
|
|
return val;
|
||
|
|
}
|
||
|
|
if (Array.isArray(val)) {
|
||
|
|
// For arrays we just pass parent's exclude object directly
|
||
|
|
// since it makes no sense to specify different exclude options for each array element
|
||
|
|
return val.map((input) => transformObjectKeys(input, transform, exclude));
|
||
|
|
}
|
||
|
|
const ret = {};
|
||
|
|
for (const [k, v] of Object.entries(val)) {
|
||
|
|
const childExclude = exclude[k];
|
||
|
|
if (childExclude === true) {
|
||
|
|
// we don't transform this object if the key is specified in exclude
|
||
|
|
ret[transform(k)] = v;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
ret[transform(k)] = transformObjectKeys(v, transform, childExclude);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Remove undefined values from a dictionary
|
||
|
|
*/
|
||
|
|
function noUndefined(xs) {
|
||
|
|
return Object.fromEntries(Object.entries(xs).filter(([_, v]) => v !== undefined));
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib2JqZWN0cy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIm9iamVjdHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFPQSxzQ0FVQztBQUtELDBCQVFDO0FBT0QsOEJBY0M7QUFLRCw4QkFNQztBQUtELGdDQU1DO0FBUUQsMEJBUUM7QUFPRCwwQkFtQ0M7QUFvQkQsOEJBNkJDO0FBUUQsa0NBNEJDO0FBWUQsa0RBb0JDO0FBS0Qsa0NBRUM7QUE5UEQsbUNBQTRDO0FBQzVDLDREQUF3RDtBQUV4RDs7R0FFRztBQUNILFNBQWdCLGFBQWEsQ0FBQyxJQUFTLEVBQUUsUUFBYTtJQUNwRCxNQUFNLE1BQU0sR0FBUSxFQUFHLENBQUM7SUFFeEIsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFcEQsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUM7U0FDbEIsTUFBTSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxNQUFNLENBQUMsQ0FBQztTQUMzQixPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFFekMsT0FBTyxNQUFNLENBQUM7QUFDaEIsQ0FBQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0IsT0FBTyxDQUFDLENBQU07SUFDNUIsSUFBSSxDQUFDLElBQUksSUFBSSxFQUFFLENBQUM7UUFDZCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFDRCxJQUFJLElBQUEsZUFBTyxFQUFDLENBQUMsQ0FBQyxFQUFFLENBQUM7UUFDZixPQUFPLENBQUMsQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUFDO0lBQ3hCLENBQUM7SUFDRCxPQUFPLE1BQU0s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
|