UNPKG

react-async-states

Version:

A low-level multi paradigm state management library

206 lines (203 loc) 8.93 kB
import { assign, __DEV__ } from '../../shared/index.js'; import { requestContext, isSource, createContext, nextKey, createSource } from 'async-states'; import { isServer } from '../../provider/context.js'; let currentOverrides; function setCurrentHookOverrides(overrides) { currentOverrides = overrides; } function cloneSourceInTheServer(globalSource, context, useGlobalSourceState) { let instance = globalSource.inst; let { config, fn, key } = instance; let newConfig = assign({}, config, { context }); let source = createSource(key, fn, newConfig); let newInstance = source.inst; if (useGlobalSourceState === true) { let globalInstance = globalSource.inst; // we will clone all relevant things: state, cache, lastRun newInstance.state = globalInstance.state; newInstance.cache = globalInstance.cache; newInstance.latestRun = globalInstance.latestRun; newInstance.lastSuccess = globalInstance.lastSuccess; } newInstance.global = true; return source; } // the goal of this function is to retrieve the following objects: // - a configuration object to use { key, producer, source, lazy ... } // - the state instance function parseConfig(currentLibContext, options, overrides) { requireAnExecContextInServer(currentLibContext, options); let executionContext; let instance; let parsedConfiguration; switch (typeof options) { // the user provided an object configuration or a Source // In the case of a source, we will detect if it was a global source // (the heuristic to detect that is to check if it belongs to another // context object), if that's the case, a new source will be created // in the current context and used case "object": { if (isSource(options)) { if (isServer) { // requireAnExecContextInServer would throw if nullish let ctx = currentLibContext.ctx; instance = cloneSourceInTheServer(options, ctx).inst; } else { instance = options.inst; } parsedConfiguration = assign({}, overrides, currentOverrides); parsedConfiguration.source = options; break; } let config = options; if (config.source && isSource(config.source)) { let baseSource = config.source; if (isServer) { // requireAnExecContextInServer would throw if nullish let ctx = currentLibContext.ctx; let useServerState = config.useServerState; baseSource = cloneSourceInTheServer(baseSource, ctx, useServerState); } let realSource = baseSource.getLane(config.lane); instance = realSource.inst; parsedConfiguration = assign({}, config, overrides, currentOverrides); parsedConfiguration.source = realSource; break; } let nullableExecContext = currentLibContext; if (config.context) { executionContext = createContext(config.context); } else if (nullableExecContext) { executionContext = nullableExecContext; } else { executionContext = requestContext(null); } parsedConfiguration = assign({}, options, overrides, currentOverrides); // parsedConfig is created by the library, so okay to mutate it internally parsedConfiguration.context = executionContext.ctx; if (!executionContext) { throw new Error("Exec context not defined, this is a bug"); } instance = resolveFromObjectConfig(executionContext, parsedConfiguration); break; } // this is a string provided to useAsync, that we will lookup the instance // by the given key in the context, if not found it will be created // with the given config and it will stay there case "string": { parsedConfiguration = assign({}, overrides, currentOverrides); parsedConfiguration.key = options; let nullableExecContext = currentLibContext; if (nullableExecContext) { executionContext = nullableExecContext; } else { executionContext = requestContext(null); } // parsedConfig is created by the library, so okay to mutate it internally parsedConfiguration.context = executionContext.ctx; instance = resolveFromStringConfig(executionContext, parsedConfiguration); break; } // this is a function provided to useAsync, means the state instance // will be removed when the component unmounts and it won't be stored in the // context case "function": { parsedConfiguration = assign({}, overrides, currentOverrides); parsedConfiguration.producer = options; parsedConfiguration.context = currentLibContext?.ctx ?? null; instance = resolveFromFunctionConfig(parsedConfiguration); break; } // at this point, config is a plain object default: { parsedConfiguration = assign({}, overrides, currentOverrides); let nullableExecContext = currentLibContext; if (nullableExecContext) { executionContext = nullableExecContext; } else { executionContext = requestContext(null); } // the parsed config is created by the library, so okay to mutate it. parsedConfiguration.context = executionContext.ctx; instance = resolveFromObjectConfig(executionContext, parsedConfiguration); } } return { instance, config: parsedConfiguration, }; } // object type has these specific rules: // - it is not a source // - the user provided a configuration object (not through overrides) // - cases when it contains { source } should be supported before calling this function resolveFromObjectConfig(executionContext, parsedConfiguration) { let { key, producer } = parsedConfiguration; if (!key) { requireAKeyInTheServer(); key = nextKey(); // anonymous states won't be stored in the context for easier GC parsedConfiguration.storeInContext = false; } let existingInstance = executionContext.get(key); if (existingInstance) { return existingInstance.actions.getLane(parsedConfiguration.lane).inst; } return createSource(key, producer, parsedConfiguration).getLane(parsedConfiguration.lane).inst; } // the user provided a string to useAsync(key, deps) function resolveFromStringConfig(executionContext, parsedConfiguration) { // key should never be undefined in this path let key = parsedConfiguration.key; let existingInstance = executionContext.get(key); if (existingInstance) { return existingInstance; } return createSource(key, null, parsedConfiguration).inst; } function resolveFromFunctionConfig(parsedConfiguration) { requireAKeyInTheServer(); let key = nextKey(); // anonymous states won't be stored in the context for easier GC parsedConfiguration.storeInContext = false; // todo: reuse instance from previous render return createSource(key, parsedConfiguration.producer, parsedConfiguration) .inst; } // this function throws in the server when there is no context provided function requireAnExecContextInServer(parentExecContext, mixedConfig) { // opt-out for these cases: // - not in server // - we are in a Library Context provider tree (and not using a source) // - the provided config is not an object (then, we will attach to parent provider) if (!isServer || typeof mixedConfig !== "object") { return; } if (parentExecContext) { return; } let baseConfig = mixedConfig; // at this point, we have an object (not a source) if (!baseConfig.context) { if (__DEV__) { console.error("A context object is mandatory when working in the server " + "to avoid leaks between requests. \nAdd the following up in the tree:\n" + "import { Provider } from 'react-async-states';\n" + "<Provider>{yourChildrenTree}</Provider>;\n"); } throw new Error("A Provider is mandatory in the server"); } } function requireAKeyInTheServer() { if (!isServer) { return; } throw new Error("A key is required in the server"); } export { parseConfig, setCurrentHookOverrides }; //# sourceMappingURL=HookResolveConfig.js.map