@prosopo/util
Advanced tools
Comparing version 0.2.7 to 0.2.8
@@ -14,5 +14,91 @@ // Copyright 2021-2023 Prosopo (UK) Ltd. | ||
// limitations under the License. | ||
import { at, get, permutations, rng, seedLodash } from '../util.js'; | ||
import { at, get, merge, permutations, rng, seedLodash } from '../util.js'; | ||
import { describe, expect, test } from 'vitest'; | ||
describe('util', () => { | ||
describe('merge', () => { | ||
// factors: | ||
// - types | ||
// - primitive | ||
// - array | ||
// - object | ||
// - nesting | ||
// - 0 levels | ||
// - 1 levels | ||
// - 2 levels | ||
// - array strat | ||
// - 'update' | ||
// - 'replace' | ||
// - merge nested els in array | ||
// - true | ||
// - false | ||
// - merge nested fields in obj | ||
// - true | ||
// - false | ||
test('array in array', () => { | ||
expect(merge([ | ||
[0, 1, 2], | ||
[3, 4, 5], | ||
], [ | ||
[6, 7], | ||
[8, 9], | ||
])).to.deep.equal([ | ||
[6, 7, 2], | ||
[8, 9, 5], | ||
]); | ||
}); | ||
test('array in array atomic', () => { | ||
expect(merge([ | ||
[0, 1, 2], | ||
[3, 4, 5], | ||
], [ | ||
[6, 7], | ||
[8, 9], | ||
], { atomicArrays: true })).to.deep.equal([ | ||
[6, 7], | ||
[8, 9], | ||
]); | ||
}); | ||
test('array in object', () => { | ||
expect(merge({ a: [0, 1, 2] }, { a: [3, 4] })).to.deep.equal({ a: [3, 4, 2] }); | ||
}); | ||
test('array in object atomic', () => { | ||
expect(merge({ a: [0, 1, 2] }, { a: [3, 4] }, { atomicArrays: true })).to.deep.equal({ a: [3, 4] }); | ||
}); | ||
test('array in object in array', () => { | ||
expect(merge([{ a: [0, 1, 2] }, { b: [3, 4, 5] }], [{ a: [6, 7] }, { b: [8, 9] }])).to.deep.equal([ | ||
{ a: [6, 7, 2] }, | ||
{ b: [8, 9, 5] }, | ||
]); | ||
}); | ||
test('array in object in array atomic', () => { | ||
expect(merge([{ a: [0, 1, 2] }, { b: [3, 4, 5] }], [{ a: [6, 7] }, { b: [8, 9] }], { atomicArrays: true })).to.deep.equal([{ a: [6, 7] }, { b: [8, 9] }]); | ||
}); | ||
test('primitive replaces array', () => { | ||
expect(merge({ a: [1] }, { a: 1 })).to.deep.equal({ a: 1 }); | ||
}); | ||
test('primitive replaces object', () => { | ||
expect(merge({ a: { b: 1 } }, { a: 1 })).to.deep.equal({ a: 1 }); | ||
}); | ||
test('primitive replaces primitive', () => { | ||
expect(merge({ a: 1 }, { a: 2 })).to.deep.equal({ a: 2 }); | ||
}); | ||
test('array replaces primitive', () => { | ||
expect(merge({ a: 1 }, { a: [1] })).to.deep.equal({ a: [1] }); | ||
}); | ||
test('array replaces array', () => { | ||
expect(merge({ a: [1] }, { a: [2] })).to.deep.equal({ a: [2] }); | ||
}); | ||
test('array replaces object', () => { | ||
expect(merge({ a: { b: 1 } }, { a: [1] })).to.deep.equal({ a: [1] }); | ||
}); | ||
test('object replaces primitive', () => { | ||
expect(merge({ a: 1 }, { a: { b: 1 } })).to.deep.equal({ a: { b: 1 } }); | ||
}); | ||
test('object replaces array', () => { | ||
expect(merge({ a: [1] }, { a: { b: 1 } })).to.deep.equal({ a: { b: 1 } }); | ||
}); | ||
test('object replaces object', () => { | ||
expect(merge({ a: { b: 1 } }, { a: { c: 1 } })).to.deep.equal({ a: { b: 1, c: 1 } }); | ||
}); | ||
}); | ||
describe('at', () => { | ||
@@ -19,0 +105,0 @@ test('types', () => { |
import _lodash from 'lodash'; | ||
export declare const sleep: (ms: number) => Promise<unknown>; | ||
export declare const setSeedGlobal: (seed: number | string) => void; | ||
@@ -39,2 +40,9 @@ export declare const lodash: () => _lodash.LoDashStatic; | ||
export declare const flattenObj: (obj: object, prefix?: string) => Record<string, unknown>; | ||
export declare const kebabCase: (str: string) => string; | ||
export type MergeOptions = { | ||
atomicArrays?: boolean; | ||
}; | ||
export declare function merge<T extends object | A[], U extends object | B[], A, B>(dest: T, src: U, options?: MergeOptions): T & U; | ||
export declare const isArray: (value: unknown) => boolean; | ||
export declare const isObject: (value: unknown) => boolean; | ||
//# sourceMappingURL=util.d.ts.map |
import _lodash from 'lodash'; | ||
import seedrandom from 'seedrandom'; | ||
// sleep for some milliseconds | ||
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); | ||
// set the seed for the global rng, i.e. seed `Math.random()` | ||
@@ -148,2 +150,96 @@ export const setSeedGlobal = (seed) => { | ||
}; | ||
// https://stackoverflow.com/questions/63116039/camelcase-to-kebab-case | ||
export const kebabCase = (str) => str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase()); | ||
// Merge two objects or arrays together. | ||
// Nesting can be infinitely deep. | ||
// Arrays can be homogeneous or hetrogeneous. | ||
// The destination object/array is mutated directly. | ||
// Arrays can be merged in two ways: | ||
// - update (default): replace elements as required and extend array as required, e.g. [1,2,3] + [4,5] = [4,5,3] | ||
// - replace: treat the array as a primitive value and simply replace as-is, e.g. [1,2,3] + [4,5] = [4,5] | ||
// The 'atomicArrays' option controls whether arrays are treated as primitives or not. E.g. atomicArrays=true is the 'replace' strategy, atomicArrays=false is the 'update' strategy. | ||
// This method treats arrays as an object with numeric keys and merged using the object merge strategy. | ||
export function merge(dest, src, options) { | ||
const atomicArrays = options?.atomicArrays; | ||
// maintain a queue of object sources/destinations to merge | ||
const queue = [ | ||
{ | ||
src, | ||
dest, | ||
}, | ||
]; | ||
while (queue.length > 0) { | ||
const task = queue.pop(); | ||
if (task === undefined) { | ||
throw new Error('queue is empty'); | ||
} | ||
if (isArray(task.dest)) { | ||
// handling arrays | ||
const src = task.src; | ||
const dest = task.dest; | ||
if (atomicArrays) { | ||
// delete any items beyond the length of src | ||
while (dest.length > src.length) { | ||
dest.pop(); | ||
} | ||
// treat arrays as primitives / atomic | ||
for (let i = 0; i < src.length; i++) { | ||
dest[i] = src[i]; | ||
} | ||
} | ||
else { | ||
// else not treating arrays as primitives / atomic | ||
// so need to merge them | ||
// copy the elements from src into dest | ||
for (let i = 0; i < src.length; i++) { | ||
// if the element is an array or object, then we need to merge it | ||
if ((isArray(dest[i]) && isArray(src[i])) || (isObject(dest[i]) && isObject(src[i]))) { | ||
// need to merge arrays or objects | ||
queue.push({ | ||
src: src[i], | ||
dest: dest[i], | ||
}); | ||
} | ||
else { | ||
// primitive, so replace | ||
// or src[i] is array but dest[i] is not, so replace | ||
// or src[i] is object but dest[i] is not, so replace | ||
dest[i] = src[i]; | ||
} | ||
} | ||
} | ||
} | ||
else if (isObject(task.dest)) { | ||
const src = task.src; | ||
const destAny = task.dest; | ||
// for every entry in src | ||
for (const [key, value] of Object.entries(src)) { | ||
// if the value in src + dest is an array or object, then we need to merge it | ||
if ((isArray(value) && isArray(destAny[key])) || (isObject(value) && isObject(destAny[key]))) { | ||
// need to merge arrays or objects | ||
queue.push({ | ||
src: value, | ||
dest: destAny[key], | ||
}); | ||
} | ||
else { | ||
// primitive, so replace | ||
// or value is array but dest[key] is not, so replace | ||
// or value is object but dest[key] is not, so replace | ||
destAny[key] = value; | ||
} | ||
} | ||
} | ||
else { | ||
throw new Error(`cannot handle type in queue: ${typeof task.dest}`); | ||
} | ||
} | ||
return dest; | ||
} | ||
export const isArray = (value) => { | ||
return Array.isArray(value); | ||
}; | ||
export const isObject = (value) => { | ||
return value instanceof Object && !isArray(value); | ||
}; | ||
//# sourceMappingURL=util.js.map |
{ | ||
"name": "@prosopo/util", | ||
"version": "0.2.7", | ||
"version": "0.2.8", | ||
"author": "PROSOPO LIMITED <info@prosopo.io>", | ||
@@ -36,3 +36,2 @@ "license": "Apache-2.0", | ||
"@types/lodash": "^4.14.198", | ||
"@types/mocha": "^10.0.1", | ||
"@types/seedrandom": "^3.0.5", | ||
@@ -39,0 +38,0 @@ "@vitest/coverage-v8": "^0.34.3", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
685355
8
2681