ppipe
Version:
piping without the operator support
160 lines (129 loc) • 3.83 kB
JavaScript
const isFn = require("./lib/isFunction");
const getPropertyByPath = require("./getPropertyByPath");
const isPromise = require("./lib/isPromise");
const unitFn = (x) => x;
const isUndef = (val) => typeof val === "undefined";
const truthy = (val) => !isUndef(val) && val !== null;
function createPpipe(extensions = {}) {
function ppipe(val, thisVal, err) {
function pipe(fn, ...params) {
if (isUndef(fn)) {
if (truthy(err)) {
throw err;
}
return val;
}
if (!isFn(fn)) {
if (fn instanceof Placeholder && params.length === 0) {
params = [fn];
fn = unitFn;
} else {
throw new Error("first parameter to a pipe should be a function or a single placeholder");
}
}
const callResultFn = (value) => {
let replacedPlaceHolder = false;
for (let i = params.length; i >= 0; i--) {
const pholdr = params[i];
if (!(pholdr instanceof Placeholder)) {
continue;
}
replacedPlaceHolder = true;
const replacedParam = !pholdr.prop ? value : getPropertyByPath(value, pholdr.prop);
pholdr.expandTarget === true
? params.splice(i, 1, ...replacedParam)
: params.splice(i, 1, replacedParam);
}
if (!replacedPlaceHolder) {
params.splice(params.length, 0, value);
}
return fn.call(thisVal, ...params);
};
let res;
if (isPromise(val)) {
res = val.then(callResultFn);
} else {
try {
res = truthy(err) ? undefined : callResultFn(val);
} catch (e) {
err = e;
}
}
return ppipe(res, undefined, err);
}
const piped = new Proxy(pipe, {
get(target, name) {
switch (name) {
case "then":
case "catch": {
const res = truthy(err) ? Promise.reject(err) : Promise.resolve(val);
return (...params) => (name === "then" ? res.then(...params) : res.catch(...params));
}
case "val":
if (truthy(err)) {
throw err;
}
return val;
case "with":
return (ctx) => {
thisVal = ctx;
return piped;
};
case "pipe":
return piped;
case "bind":
case "call":
case "apply":
return (...params) => pipe[name](...params);
}
if (isPromise(val)) {
return (...params) =>
piped((x) => {
if (isUndef(x[name])) {
throw new TypeError(`${name} is not defined on ${x}`);
}
return isFn(x[name]) ? x[name](...params) : x[name];
});
}
const fnExistsInCtx = truthy(thisVal) && isFn(thisVal[name]);
const valHasProp = !fnExistsInCtx && !isUndef(val[name]);
const extensionWithNameExists = !fnExistsInCtx && !valHasProp && isFn(extensions[name]);
if (fnExistsInCtx || valHasProp || extensionWithNameExists) {
const ctx = fnExistsInCtx ? thisVal : valHasProp ? val : extensions;
return (...params) =>
piped((...replacedParams) => {
const newParams = fnExistsInCtx || extensionWithNameExists ? replacedParams : params;
return !isFn(ctx[name]) ? ctx[name] : ctx[name](...newParams);
}, ...params);
}
},
});
return piped;
}
return Object.assign(ppipe, {
extend(newExtensions) {
return createPpipe(Object.assign(newExtensions, extensions));
},
_,
});
}
class Placeholder {
*[Symbol.iterator]() {
yield new Placeholder(this.prop, true);
}
constructor(prop, expandTarget) {
this.prop = prop;
this.expandTarget = expandTarget;
}
}
const placeholderProxy = (prop = undefined, expandTarget = false) =>
new Proxy(new Placeholder(prop, expandTarget), {
get(target, name) {
if (name === Symbol.iterator || Object.getOwnPropertyNames(target).includes(name)) {
return target[name];
}
return placeholderProxy([prop, name].filter((x) => !!x).join("."));
},
});
const _ = placeholderProxy();
module.exports = createPpipe();