UNPKG

@doeixd/make-with

Version:

Lightweight function application utilities

858 lines (856 loc) 30.3 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { LayeredError: () => LayeredError, _with: () => _with, collectFns: () => collectFns, compose: () => compose, createLens: () => createLens, createMerger: () => createMerger, createProxy: () => createProxy, default: () => src_default, enrich: () => enrich, fallback: () => fallback, getKeyDescriptions: () => getKeyDescriptions, getSet: () => getSet, ignoreCase: () => ignoreCase, make: () => make, makeChainable: () => makeChainable, makeLayered: () => makeLayered, makeWith: () => makeWith, makeWithCompose: () => makeWithCompose, merge: () => merge, noSpecialChars: () => noSpecialChars, provide: () => provide, provideTo: () => provideTo, rebind: () => rebind, withFallback: () => withFallback }); module.exports = __toCommonJS(src_exports); var IS_CHAINABLE = Symbol("isChainable"); var API_CACHE = /* @__PURE__ */ new WeakMap(); var VALIDATION_CACHE = /* @__PURE__ */ new WeakSet(); var IS_COMPOSABLE = Symbol("isComposable"); var INTERNAL_STATE = Symbol("internalState"); function validateMethods(methods, context) { if (VALIDATION_CACHE.has(methods)) { return; } for (const [key, value] of Object.entries(methods)) { if (key === IS_CHAINABLE.toString()) continue; if (typeof value !== "function") { throw new TypeError( `Invalid method "${key}" in ${context}: expected function, got ${typeof value}` ); } } VALIDATION_CACHE.add(methods); } function isLayerFunction(value) { if (typeof value !== "function") return false; return value.length === 1 || value.length === 0 && value.toString().includes("..."); } var LayeredError = class extends Error { }; function createError(context, message, cause) { const error = new LayeredError(`[${context}] ${message}`, { cause }); return error; } function getCachedApi(subject, functionsMap, factory) { let subjectCache = API_CACHE.get(subject); if (!subjectCache) { const newCache = /* @__PURE__ */ new WeakMap(); subjectCache = API_CACHE.get(subject) || newCache; if (subjectCache === newCache) { API_CACHE.set(subject, subjectCache); } } let cachedApi = subjectCache.get(functionsMap); if (!cachedApi) { const newApi = factory(); cachedApi = subjectCache.get(functionsMap) || newApi; if (cachedApi === newApi) { subjectCache.set(functionsMap, cachedApi); } } return cachedApi; } function _with(subject) { if (subject === null || subject === void 0) { throw createError("_with", "Subject cannot be null or undefined"); } return function(...fns) { if (fns.length === 0) { throw createError("_with", "At least one function must be provided"); } for (let i = 0; i < fns.length; i++) { if (typeof fns[i] !== "function") { throw createError("_with", `Argument at index ${i} must be a function, got ${typeof fns[i]}`); } } return fns.map( (fn, index) => (...args) => { try { return fn(subject, ...args); } catch (error) { throw createError("_with", `Function at index ${index} threw an error`, error); } } ); }; } function make(...fnsOrObj) { if (fnsOrObj.length === 0) { throw createError("make", "At least one argument must be provided"); } if (fnsOrObj.length === 1 && typeof fnsOrObj[0] === "object" && !Array.isArray(fnsOrObj[0]) && fnsOrObj[0] !== null) { const functionsMap2 = fnsOrObj[0]; try { validateMethods(functionsMap2, "make"); return functionsMap2; } catch (error) { throw createError("make", "Object validation failed", error); } } const functionsMap = {}; const seenNames = /* @__PURE__ */ new Set(); for (let i = 0; i < fnsOrObj.length; i++) { const fn = fnsOrObj[i]; if (typeof fn !== "function") { throw createError("make", `Argument at index ${i} must be a function, got ${typeof fn}`); } if (!fn.name || fn.name.trim() === "") { throw createError("make", `Function at index ${i} must have a non-empty name`); } if (seenNames.has(fn.name)) { throw createError("make", `Duplicate function name "${fn.name}" found`); } seenNames.add(fn.name); functionsMap[fn.name] = fn; } return functionsMap; } function rebind(...fnsOrObj) { try { const originalFunctions = make(...fnsOrObj); originalFunctions[IS_CHAINABLE] = true; return originalFunctions; } catch (error) { throw createError("rebind", "Failed to create chainable functions", error); } } function makeWith(subject) { if (subject === null || subject === void 0) { throw createError("makeWith", "Subject cannot be null or undefined"); } if (typeof subject !== "object") { throw createError("makeWith", `Subject must be an object, got ${typeof subject}`); } return function(functionsMap) { if (!functionsMap || typeof functionsMap !== "object") { throw createError("makeWith", "Functions map must be a non-null object"); } return getCachedApi(subject, functionsMap, () => { try { validateMethods(functionsMap, "makeWith"); const finalApi = {}; const isChainable = functionsMap[IS_CHAINABLE]; const methodNames = Object.keys(functionsMap).filter((k) => k !== IS_CHAINABLE.toString()); for (const key of methodNames) { const fn = functionsMap[key]; if (isChainable) { finalApi[key] = (...args) => { try { const newSubject = fn(subject, ...args); if (newSubject === void 0) { throw createError( "makeWith", `Chainable method "${key}" returned undefined. Chainable methods must return a new state object.` ); } if (newSubject === null || typeof newSubject !== "object") { throw createError( "makeWith", `Chainable method "${key}" returned ${newSubject === null ? "null" : typeof newSubject}. Chainable methods must return a new state object.` ); } return makeWith(newSubject)(functionsMap); } catch (error) { throw createError("makeWith", `Chainable method "${key}" failed`, error); } }; } else { finalApi[key] = (...args) => { try { return fn(subject, ...args); } catch (error) { throw createError("makeWith", `Method "${key}" failed`, error); } }; } } finalApi[INTERNAL_STATE] = subject; return finalApi; } catch (error) { throw createError("makeWith", "API creation failed", error); } }); }; } function compose(methods) { if (!methods || typeof methods !== "object") { throw createError("compose", "Methods must be a non-null object"); } for (const [key, value] of Object.entries(methods)) { if (typeof value !== "function") { throw createError("compose", `Method "${key}" must be a function, got ${typeof value}`); } } return Object.assign(methods, { [IS_COMPOSABLE]: true }); } function merge(...objects) { if (objects.length === 0) { throw createError("merge", "At least one object must be provided"); } const firstObj = objects[0]; if (!firstObj || typeof firstObj !== "object") { throw createError("merge", "First argument must be a non-null object"); } try { validateMethods(firstObj, "merge first argument"); } catch (error) { throw createError("merge", "First object validation failed", error); } if (objects.length === 1) { return function(...additionalObjects) { if (additionalObjects.length === 0) { throw createError("merge", "At least one additional object must be provided to merge"); } for (let i = 0; i < additionalObjects.length; i++) { const obj = additionalObjects[i]; if (!obj || typeof obj !== "object") { throw createError("merge", `Additional argument at index ${i} must be a non-null object`); } try { validateMethods(obj, `merge additional argument ${i}`); } catch (error) { throw createError("merge", `Additional object at index ${i} validation failed`, error); } } return Object.assign({}, firstObj, ...additionalObjects); }; } for (let i = 1; i < objects.length; i++) { const obj = objects[i]; if (!obj || typeof obj !== "object") { throw createError("merge", `Argument at index ${i} must be a non-null object`); } try { validateMethods(obj, `merge argument ${i}`); } catch (error) { throw createError("merge", `Object at index ${i} validation failed`, error); } } return Object.assign({}, ...objects); } function getKeyDescriptions(objA, objB) { const allKeys = /* @__PURE__ */ new Set([ ...Object.getOwnPropertyNames(objA), ...Object.getOwnPropertySymbols(objA), ...Object.getOwnPropertyNames(objB), ...Object.getOwnPropertySymbols(objB) ]); return Array.from(allKeys).map((key) => { const descA = Object.getOwnPropertyDescriptor(objA, key) || { enumerable: false, configurable: false, writable: false, key }; const descB = Object.getOwnPropertyDescriptor(objB, key) || { enumerable: false, configurable: false, writable: false, key }; return [key, descA, descB]; }); } function createMerger(definition) { if (!definition) { throw createError("createMerger", "Merge definition cannot be null or undefined"); } let normalizedDefinition; if (Array.isArray(definition)) { normalizedDefinition = {}; for (const [keyOrDescriptor, mergeFn] of definition) { if (typeof keyOrDescriptor === "object" && "key" in keyOrDescriptor) { const key = keyOrDescriptor.key; normalizedDefinition[key] = mergeFn; } else { const key = keyOrDescriptor; normalizedDefinition[key] = mergeFn; } } } else { normalizedDefinition = definition; } for (const [key, mergeFn] of Object.entries(normalizedDefinition)) { if (typeof mergeFn !== "function") { throw createError("createMerger", `Merge function for key "${key}" must be a function, got ${typeof mergeFn}`); } } function performMerge(objA, objB) { if (!objA || typeof objA !== "object") { throw createError("createMerger", "First object must be a non-null object"); } if (!objB || typeof objB !== "object") { throw createError("createMerger", "Second object must be a non-null object"); } const result = {}; const failures = []; const allKeys = /* @__PURE__ */ new Set([ ...Object.getOwnPropertyNames(objA), ...Object.getOwnPropertySymbols(objA), ...Object.getOwnPropertyNames(objB), ...Object.getOwnPropertySymbols(objB) ]); for (const key of allKeys) { const typedKey = key; const mergeFn = normalizedDefinition[typedKey]; if (mergeFn) { try { const objAWithKey = objA.hasOwnProperty(key) ? { [key]: objA[typedKey] } : {}; const objBWithKey = objB.hasOwnProperty(key) ? { [key]: objB[typedKey] } : {}; const mergeResult = mergeFn(objAWithKey, objBWithKey, typedKey); if (mergeResult && typeof mergeResult === "object" && "error" in mergeResult) { failures.push([key, mergeResult.error]); } else { result[typedKey] = mergeResult; } } catch (error) { failures.push([key, error]); } } else { if (objB.hasOwnProperty(key)) { result[typedKey] = objB[typedKey]; } else if (objA.hasOwnProperty(key)) { result[typedKey] = objA[typedKey]; } } } if (failures.length > 0) { return { success: false, failures }; } return { success: true, data: result }; } function merger(objA, objB) { if (objB === void 0) { return (secondObj) => performMerge(objA, secondObj); } return performMerge(objA, objB); } return merger; } function getSet(state, methodName, ...args) { const method = String(methodName); if (method.startsWith("get") && method.length > 3) { const field = method.charAt(3).toLowerCase() + method.slice(4); return state[field]; } if (method.startsWith("set") && method.length > 3 && args.length > 0) { const field = method.charAt(3).toLowerCase() + method.slice(4); if (field in state) { return { ...state, [field]: args[0] }; } } return void 0; } function ignoreCase(handler) { return (state, methodName, ...args) => { const normalizedName = typeof methodName === "string" ? methodName.toLowerCase() : methodName; return handler(state, normalizedName, ...args); }; } function noSpecialChars(handler) { return (state, methodName, ...args) => { const cleanName = typeof methodName === "string" ? methodName.replace(/[^a-zA-Z0-9]/g, "") : methodName; return handler(state, cleanName, ...args); }; } function fallback(handlers) { return (state, methodName, ...args) => { for (const handler of handlers) { const result = handler(state, methodName, ...args); if (result !== void 0) { return result; } } return void 0; }; } function createProxy(handler) { if (typeof handler !== "function") { throw createError("createProxy", "Handler must be a function"); } return function(initialState) { if (!initialState || typeof initialState !== "object") { throw createError("createProxy", "Initial state must be a non-null object"); } const baseAPI = { [INTERNAL_STATE]: initialState }; return new Proxy(baseAPI, { get(target, prop) { if (prop === INTERNAL_STATE) { return target[INTERNAL_STATE]; } if (typeof prop === "symbol" || prop === "constructor" || prop === "toString" || prop === "valueOf") { return target[prop]; } return function(...args) { try { const currentState = target[INTERNAL_STATE]; const result = handler(currentState, prop, ...args); if (result && typeof result === "object" && "error" in result) { throw createError("createProxy", `Method "${String(prop)}" failed: ${result.error}`); } if (result === void 0) { throw createError("createProxy", `Method "${String(prop)}" is not supported`); } if (result && typeof result === "object" && typeof currentState === "object") { const stateKeys = Object.keys(currentState); const resultKeys = Object.keys(result); const isStateUpdate = stateKeys.some((key) => key in result) || resultKeys.length > 0; if (isStateUpdate) { return createProxy(handler)(result); } } return result; } catch (error) { if (error instanceof LayeredError) { throw error; } throw createError("createProxy", `Method "${String(prop)}" execution failed`, error); } }; }, has(_target, prop) { if (prop === INTERNAL_STATE) return true; return typeof prop === "string"; }, ownKeys(target) { const state = target[INTERNAL_STATE]; const stateKeys = Object.keys(state); const methodNames = stateKeys.flatMap((key) => [ `get${key.charAt(0).toUpperCase() + key.slice(1)}`, `set${key.charAt(0).toUpperCase() + key.slice(1)}` ]); return [...methodNames, INTERNAL_STATE]; }, getOwnPropertyDescriptor(_target, prop) { if (prop === INTERNAL_STATE) { return { configurable: true, enumerable: false, writable: true }; } if (typeof prop === "string") { return { configurable: true, enumerable: true, writable: false }; } return void 0; } }); }; } function createLens(getter, setter) { if (typeof getter !== "function") { throw createError("createLens", "Getter must be a function"); } if (typeof setter !== "function") { throw createError("createLens", "Setter must be a function"); } return function(methods) { if (!methods || typeof methods !== "object") { throw createError("createLens", "Methods must be a non-null object"); } try { validateMethods(methods, "createLens"); } catch (error) { throw createError("createLens", "Methods validation failed", error); } const focusedMethods = {}; const isChainable = methods[IS_CHAINABLE] === true; if (isChainable) { focusedMethods[IS_CHAINABLE] = true; } for (const [methodName, method] of Object.entries(methods)) { if (methodName === IS_CHAINABLE.toString()) continue; if (typeof method !== "function") { throw createError("createLens", `Method "${methodName}" must be a function`); } focusedMethods[methodName] = (fullState, ...args) => { try { const focusedState = getter(fullState); const result = method(focusedState, ...args); if (isChainable && result !== void 0 && result !== null) { return setter(fullState, result); } return result; } catch (error) { throw createError("createLens", `Focused method "${methodName}" failed`, error); } }; } return focusedMethods; }; } var defaultValidator = (value) => { return value !== null && value !== void 0; }; function withFallback(primaryObject, validator = defaultValidator) { if (!primaryObject || typeof primaryObject !== "object") { throw createError("withFallback", "Primary object must be a non-null object"); } if (typeof validator !== "function") { throw createError("withFallback", "Validator must be a function"); } const fallbackChain = []; function createBuilder() { function builder(fallbackObject) { if (fallbackObject === void 0) { return createFallbackProxy(primaryObject, fallbackChain, validator); } if (!fallbackObject || typeof fallbackObject !== "object") { throw createError("withFallback", "Fallback object must be a non-null object"); } fallbackChain.push(fallbackObject); return createBuilder(); } return builder; } return createBuilder(); } function createFallbackProxy(primaryObject, fallbackChain, validator) { return new Proxy(primaryObject, { get(target, prop) { if (typeof prop === "symbol" || prop === "constructor" || prop === "toString" || prop === "valueOf") { return target[prop]; } const objectsToCheck = [target, ...fallbackChain]; for (const obj of objectsToCheck) { if (obj && typeof obj === "object" && prop in obj) { const value = obj[prop]; try { if (validator(value)) { if (value && typeof value === "object" && !Array.isArray(value)) { const nestedFallbacks = []; for (const fallbackObj of fallbackChain) { if (fallbackObj && typeof fallbackObj === "object" && prop in fallbackObj) { const nestedValue = fallbackObj[prop]; if (nestedValue && typeof nestedValue === "object" && !Array.isArray(nestedValue)) { nestedFallbacks.push(nestedValue); } } } if (nestedFallbacks.length > 0) { return createFallbackProxy(value, nestedFallbacks, validator); } } return value; } } catch (error) { continue; } } } return void 0; }, set(target, prop, value) { try { target[prop] = value; return true; } catch (error) { return false; } }, has(target, prop) { const objectsToCheck = [target, ...fallbackChain]; for (const obj of objectsToCheck) { if (obj && typeof obj === "object" && prop in obj) { return true; } } return false; }, ownKeys(target) { const allKeys = /* @__PURE__ */ new Set(); const objectsToCheck = [target, ...fallbackChain]; for (const obj of objectsToCheck) { if (obj && typeof obj === "object") { Object.getOwnPropertyNames(obj).forEach((key) => allKeys.add(key)); Object.getOwnPropertySymbols(obj).forEach((symbol) => allKeys.add(symbol)); } } return Array.from(allKeys); }, getOwnPropertyDescriptor(target, prop) { const primaryDesc = Object.getOwnPropertyDescriptor(target, prop); if (primaryDesc) { return primaryDesc; } for (const obj of fallbackChain) { if (obj && typeof obj === "object") { const desc = Object.getOwnPropertyDescriptor(obj, prop); if (desc) { return { configurable: true, enumerable: desc.enumerable, writable: true, value: void 0 // Will be overridden by the proxy get trap }; } } } return void 0; }, deleteProperty(target, prop) { try { delete target[prop]; return true; } catch (error) { return false; } }, defineProperty(target, prop, descriptor) { try { Object.defineProperty(target, prop, descriptor); return true; } catch (error) { return false; } } }); } function makeWithCompose(subject) { if (subject === null || subject === void 0) { throw createError("makeWithCompose", "Subject cannot be null or undefined"); } if (typeof subject !== "object") { throw createError("makeWithCompose", `Subject must be an object, got ${typeof subject}`); } return function(...methodObjects) { if (methodObjects.length === 0) { throw createError("makeWithCompose", "At least one method object must be provided"); } const composedMethods = {}; for (const methodObj of methodObjects) { if (!methodObj || typeof methodObj !== "object") { throw createError("makeWithCompose", "All arguments must be non-null objects"); } for (const [key, method] of Object.entries(methodObj)) { if (key === IS_CHAINABLE.toString()) { composedMethods[key] = method; continue; } if (typeof method !== "function") { throw createError("makeWithCompose", `Method "${key}" must be a function`); } const existingMethod = composedMethods[key]; if (existingMethod) { composedMethods[key] = (s, ...args) => { return method(s, ...args, existingMethod); }; } else { composedMethods[key] = method; } } } return makeWith(subject)(composedMethods); }; } function enrich(primaryFactory, secondaryFactory) { if (typeof primaryFactory !== "function") { throw createError("enrich", "Primary factory must be a function"); } if (typeof secondaryFactory !== "function") { throw createError("enrich", "Secondary factory must be a function"); } return function(...args) { try { const primaryResult = primaryFactory(...args); if (!primaryResult || typeof primaryResult !== "object") { throw createError( "enrich", `Primary factory must return an object, got ${typeof primaryResult}` ); } const secondaryResult = secondaryFactory(primaryResult); if (!secondaryResult || typeof secondaryResult !== "object") { throw createError( "enrich", `Secondary factory must return an object, got ${typeof secondaryResult}` ); } return { ...primaryResult, ...secondaryResult }; } catch (error) { throw createError("enrich", "Factory composition failed", error); } }; } function makeLayered(subject) { if (subject === null || subject === void 0) { throw createError("makeLayered", "Subject cannot be null or undefined"); } if (typeof subject !== "object") { throw createError("makeLayered", `Subject must be an object, got ${typeof subject}`); } return function(baseFns) { if (!baseFns || typeof baseFns !== "object") { throw createError("makeLayered", "Base functions must be a non-null object"); } try { const baseInstance = makeWith(subject)(baseFns); let layerCount = 0; const createNextLayer = (currentInstance) => { return (enhancerFnsOrLayerFn) => { if (enhancerFnsOrLayerFn === void 0) { return currentInstance; } layerCount++; try { let enhancerFns; if (typeof enhancerFnsOrLayerFn === "function") { if (!isLayerFunction(enhancerFnsOrLayerFn)) { throw createError( "makeLayered", `Layer function must accept exactly one parameter (the current API), got function with ${enhancerFnsOrLayerFn.length} parameters` ); } enhancerFns = enhancerFnsOrLayerFn(currentInstance); if (!enhancerFns || typeof enhancerFns !== "object") { throw createError( "makeLayered", `Layer function must return an object of methods, got ${typeof enhancerFns}` ); } } else { if (!enhancerFnsOrLayerFn || typeof enhancerFnsOrLayerFn !== "object") { throw createError( "makeLayered", "Layer must be either a function or an object of methods" ); } enhancerFns = enhancerFnsOrLayerFn; } validateMethods(enhancerFns, `makeLayered layer ${layerCount}`); const isComposable = enhancerFns[IS_COMPOSABLE] === true; let nextLayer; if (isComposable) { nextLayer = {}; for (const [key, newMethod] of Object.entries(enhancerFns)) { if (key === IS_COMPOSABLE.toString()) continue; const fn = newMethod; const previousMethod = currentInstance[key]; if (previousMethod && typeof previousMethod === "function") { nextLayer[key] = (...args) => { const smartPreviousMethod = (...methodArgs) => { const result = previousMethod(currentInstance, ...methodArgs); if (result && typeof result === "object" && Object.prototype.hasOwnProperty.call(result, INTERNAL_STATE)) { return result[INTERNAL_STATE]; } return result; }; try { return fn(currentInstance, ...args, smartPreviousMethod); } catch (error) { throw createError("compose", `Composed method "${key}" failed during execution`, error); } }; } else { nextLayer[key] = (...args) => { const noPreviousMethod = () => { throw createError("compose", `No previous method "${key}" found to compose with`); }; try { return fn(currentInstance, ...args, noPreviousMethod); } catch (error) { throw createError("compose", `Composed method "${key}" failed during execution`, error); } }; } } nextLayer = provideTo(currentInstance)(nextLayer); } else { nextLayer = provideTo(currentInstance)(enhancerFns); } const newInstance = Object.assign( Object.create(Object.getPrototypeOf(currentInstance)), currentInstance, nextLayer ); return createNextLayer(newInstance); } catch (error) { throw createError("makeLayered", `Layer ${layerCount} creation failed`, error); } }; }; return createNextLayer(baseInstance); } catch (error) { throw createError("makeLayered", "Layered API initialization failed", error); } }; } var provide = _with; var collectFns = make; var provideTo = makeWith; var makeChainable = rebind; var src_default = { with: _with, provide, make, collectFns, makeWith, makeWithCompose, provideTo, rebind, makeChainable, enrich, makeLayered, compose, merge, createMerger, getKeyDescriptions, createProxy, getSet, ignoreCase, noSpecialChars, fallback, createLens, withFallback }; /** * @file A functional utility library for creating powerful, immutable, and chainable APIs. * It provides tools for partial application and function composition, enabling elegant state * management patterns. * * @version 0.0.5 * @license MIT */ //# sourceMappingURL=index.js.map