rubico
Version:
[a]synchronous functional programming
152 lines (132 loc) • 3.92 kB
JavaScript
/**
* rubico v2.6.2
* https://github.com/a-synchronous/rubico
* (c) 2019-2024 Richard Tong
* rubico may be freely distributed under the MIT license.
*/
const isPromise = value => value != null && typeof value.then == 'function'
const isArray = Array.isArray
const isObject = value => {
if (value == null) {
return false
}
const typeofValue = typeof value
return (typeofValue == 'object') || (typeofValue == 'function')
}
const memoizeCappedUnary = function (func, cap) {
const cache = new Map(),
memoized = function memoized(arg0) {
if (cache.has(arg0)) {
return cache.get(arg0)
}
const result = func(arg0)
cache.set(arg0, result)
if (cache.size > cap) {
cache.clear()
}
return result
}
memoized.cache = cache
return memoized
}
// a[0].b.c
const pathDelimiters = /[.|[|\]]+/
const parsePropertyPath = function (pathString) {
const pathStringLastIndex = pathString.length - 1,
firstChar = pathString[0],
lastChar = pathString[pathStringLastIndex],
isFirstCharLeftBracket = firstChar == '[',
isLastCharRightBracket = lastChar == ']'
if (isFirstCharLeftBracket && isLastCharRightBracket) {
return pathString.slice(1, pathStringLastIndex).split(pathDelimiters)
} else if (isFirstCharLeftBracket) {
return pathString.slice(1).split(pathDelimiters)
} else if (isLastCharRightBracket) {
return pathString.slice(0, pathStringLastIndex).split(pathDelimiters)
}
return pathString.split(pathDelimiters)
}
// memoized version of parsePropertyPath, max cache size 500
const memoizedCappedParsePropertyPath = memoizeCappedUnary(parsePropertyPath, 500)
const propertyPathToArray = path => isArray(path) ? path
: typeof path == 'string' ? memoizedCappedParsePropertyPath(path)
: [path]
const setByPath = function (obj, value, path) {
if (!isObject(obj)){
return obj
}
const pathArray = propertyPathToArray(path)
const pathLength = pathArray.length
const lastIndex = pathLength - 1
const result = { ...obj }
let nested = result
let index = -1
while (++index < pathLength){
const pathKey = pathArray[index]
if (index == lastIndex){
nested[pathKey] = value
} else {
const existingNextNested = nested[pathKey]
const nextNested = isArray(existingNextNested)
? existingNextNested.slice() : { ...existingNextNested }
nested[pathKey] = nextNested
nested = nextNested
}
}
return result
}
const __ = Symbol.for('placeholder')
// argument resolver for curry3
const curry3ResolveArg0 = (
baseFunc, arg1, arg2,
) => function arg0Resolver(arg0) {
return baseFunc(arg0, arg1, arg2)
}
// argument resolver for curry3
const curry3ResolveArg1 = (
baseFunc, arg0, arg2,
) => function arg1Resolver(arg1) {
return baseFunc(arg0, arg1, arg2)
}
// argument resolver for curry3
const curry3ResolveArg2 = (
baseFunc, arg0, arg1,
) => function arg2Resolver(arg2) {
return baseFunc(arg0, arg1, arg2)
}
const curry3 = function (baseFunc, arg0, arg1, arg2) {
if (arg0 == __) {
return curry3ResolveArg0(baseFunc, arg1, arg2)
}
if (arg1 == __) {
return curry3ResolveArg1(baseFunc, arg0, arg2)
}
return curry3ResolveArg2(baseFunc, arg0, arg1)
}
const _set = function (obj, path, value) {
if (typeof value == 'function') {
const actualValue = value(obj)
if (isPromise(actualValue)) {
return actualValue.then(
curry3(setByPath, obj, __, path)
)
}
return setByPath(obj, actualValue, path)
}
if (isPromise(value)) {
return value.then(
curry3(setByPath, obj, __, path)
)
}
return setByPath(obj, value, path)
}
const set = function (arg0, arg1, arg2) {
if (arg2 == null) {
return curry3(_set, __, arg0, arg1)
}
if (isPromise(arg0)) {
return arg0.then(curry3(_set, __, arg1, arg2))
}
return _set(arg0, arg1, arg2)
}
export default set