UNPKG

@robicue/use-context

Version:

The official useContext implementation for the Robicue Hook Architecture

283 lines (282 loc) 8.45 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useContext = exports.buoy = exports.util = exports.extend = exports.extendable = exports.anchor = exports.factory = exports.clone = exports.hook = exports.isInherited = exports.fork = exports.get = exports.call = exports.use = exports.init = exports.set = exports.isUsed = exports.isForked = exports.isContext = void 0; /** * Holds all the context states */ const contexts = new WeakMap(); /** * Holds all the links to parent contexts */ const parents = new WeakMap(); /** * Holds all the extendable hook functions */ const extendables = new WeakMap(); /** * Get the states associated with the specified initializer */ const getStates = (context, initializer) => { const states = contexts.get(context); if (states === null || states === void 0 ? void 0 : states.has(initializer)) { return states; } const parent = parents.get(context); if (!parent) { return undefined; } return getStates(parent, initializer); }; /** * Checks if the provided value is a valid context object */ const isContext = (value) => { return contexts.has(value); }; exports.isContext = isContext; /** * Checks if the specified context is a forked context */ const isForked = (context) => { return parents.has(context); }; exports.isForked = isForked; /** * Returns TRUE if the specified initializer is used in the context */ const isUsed = (context, initializer) => { return !!getStates(context, initializer); }; exports.isUsed = isUsed; /** * Set a new state into the context */ const set = (context, initializer, state) => { let states = contexts.get(context); if (!states) { states = new Map(); contexts.set(context, states); } states.set(initializer, state); return state; }; exports.set = set; /** * Initializes a new state into the context */ const init = (context, initializer, ...args) => { const state = initializer(context, ...args); return (0, exports.set)(context, initializer, state); }; exports.init = init; /** * Uses or initializes a state in the context */ const use = (context, initializer, ...args) => { const states = getStates(context, initializer); if (!states) { return (0, exports.init)(context, initializer, ...args); } return states.get(initializer); }; exports.use = use; /** * Calls the initializer or gets a state in the context */ const call = (context, initializer, ...args) => { const states = getStates(context, initializer); if (!states) { return initializer(context, ...args); } return states.get(initializer); }; exports.call = call; /** * Gets a state that has already been initialized */ const get = (context, initializer) => { const states = getStates(context, initializer); if (!states) { throw new Error("Not yet initialized"); } return states.get(initializer); }; exports.get = get; /** * Makes a fork of all forkable states the current context. * The forked context allows you to keep access to the states * of the current context, but newly initialized states, created * in the forked context, are not seen by hooks using the current context. */ const fork = (context, forkedContext = {}) => { if (context === forkedContext) { throw new Error("The parent context and the forked context cannot be the same"); } if ((0, exports.isContext)(forkedContext)) { throw new Error("The forked context is already in use"); } if (!(0, exports.isContext)(context)) { contexts.set(context, new Map()); } contexts.set(forkedContext, new Map()); parents.set(forkedContext, context); return forkedContext; }; exports.fork = fork; /** * Checks if the state of an initializer is inherited form a parent context */ const isInherited = (context, initializer) => { const states = getStates(context, initializer); if (!states) { return false; } return states !== contexts.get(context); }; exports.isInherited = isInherited; /** * Creates a hook function */ const hook = (func) => { return func; }; exports.hook = hook; /** * Creates a clone of a hook function. */ const clone = (func) => { return (context, ...args) => { return func(context, ...args); }; }; exports.clone = clone; /** * Creates an anchor hook factory */ const factory = (func) => { return () => (0, exports.anchor)((0, exports.clone)(func)); }; exports.factory = factory; /** * Creates a hook that memorizes the result in the context */ const anchor = (func) => { return (context, ...args) => { return (0, exports.use)(context, func, ...args); }; }; exports.anchor = anchor; /** * Creates a hook that can be extended */ const extendable = (func) => { const extendableFunc = () => (originalHook, ...args) => originalHook(...args); const result = (context, ...args) => { return (0, exports.call)(context, extendableFunc)(func, context, ...args); }; extendables.set(result, extendableFunc); return result; }; exports.extendable = extendable; /** * Extends a hook within a certain context */ const extend = (context, func, extension) => { const extendableFunc = extendables.get(func); if (!extendableFunc) { throw new Error("The hook does not support extensions"); } const previousHook = (0, exports.call)(context, extendableFunc); (0, exports.set)(context, extendableFunc, (originalHook, ...args) => { return extension((...a) => previousHook(originalHook, ...a), ...args); }); }; exports.extend = extend; /** * Creates a utility hook that memorizes the result of the context. * The context is optional here. If not provided, the specified * function itself will be used as context. */ const util = (func) => { return (context, ...args) => { return (0, exports.use)(context !== null && context !== void 0 ? context : func, func, ...args); }; }; exports.util = util; /** * Creates an hook that memorizes the result in the unforkable context */ const buoy = (func) => { return (context, ...args) => { if ((0, exports.isInherited)(context, func)) { return (0, exports.init)(context, func, ...args); } else { return (0, exports.use)(context, func, ...args); } }; }; exports.buoy = buoy; /** * Major hook that can be used to: * - create a new context * - create a fork of a context * - get or initialize a contextual state */ const useContext = (context = {}) => { return { context, /** * Returns TRUE if the specified initializer is used in the context */ isUsed(initializer) { return (0, exports.isUsed)(context, initializer); }, /** * Sets a new state into the context */ set(initializer, state) { return (0, exports.set)(context, initializer, state); }, /** * Initializes a new state into the context */ init(initializer, ...args) { return (0, exports.init)(context, initializer, ...args); }, /** * Uses or initializes a state in the context */ use(initializer, ...args) { return (0, exports.use)(context, initializer, ...args); }, /** * Gets a state that has already been initialized */ get(initializer) { return (0, exports.get)(context, initializer); }, /** * Extends a hook within a certain context */ extend(initializer, extension) { return (0, exports.extend)(context, initializer, extension); }, /** * Makes a fork of the current context. * The forked context allows you to keep access to the states * of the current context, but newly initialized states, created * in the forked context, are not seen by hooks using the current context. */ fork(forkedContext = {}) { return (0, exports.fork)(context, forkedContext); }, /** * Checks if the state of an initializer is inherited form a parent context */ isInherited(initializer) { return (0, exports.isInherited)(context, initializer); }, }; }; exports.useContext = useContext;