@robicue/use-context
Version:
The official useContext implementation for the Robicue Hook Architecture
283 lines (282 loc) • 8.45 kB
JavaScript
;
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;