UNPKG

dreamstate

Version:

Store management library based on react context and observers

215 lines (212 loc) 9.88 kB
import { DreamstateError } from '../error/DreamstateError.js'; import { noop } from '../error/noop.js'; import { throwAfterDisposal } from '../error/throw.js'; import { SCOPE_SYMBOL, SIGNALING_HANDLER_SYMBOL, SIGNAL_METADATA_SYMBOL, QUERY_METADATA_SYMBOL, SIGNAL_METADATA_REGISTRY, QUERY_METADATA_REGISTRY } from '../internals.js'; import { ContextManager } from '../management/ContextManager.js'; import { queryDataAsync } from '../queries/queryDataAsync.js'; import { queryDataSync } from '../queries/queryDataSync.js'; import { registerQueryProvider } from './queries/registerQueryProvider.js'; import { unRegisterQueryProvider } from './queries/unRegisterQueryProvider.js'; import { createRegistry } from './registry/createRegistry.js'; import { onMetadataSignalListenerCalled } from './signals/onMetadataSignalListenerCalled.js'; import { emitSignal } from '../signals/emitSignal.js'; import { processComputed } from '../storing/processComputed.js'; import { collectProtoChainMetadata } from '../utils/collectProtoChainMetadata.js'; import { EDreamstateErrorCode } from '../../types/error.js'; import { isFunction } from '../../utils/typechecking.js'; /** * Initializes the core scope context for managing stores, signals, and queries in the VDOM tree. * This function sets up the scope that is responsible for handling state and interactions within * the context of React's virtual DOM. * * @param {IRegistry} [registry] - Optional registry object to initialize. * @returns {IScopeContext} Mutable scope with a set of methods and registry stores for the React VDOM tree. */ function createScope(registry) { if (registry === void 0) { registry = createRegistry(); } var SIGNAL_LISTENERS_REGISTRY = registry.SIGNAL_LISTENERS_REGISTRY, CONTEXT_STATES_REGISTRY = registry.CONTEXT_STATES_REGISTRY, CONTEXT_OBSERVERS_REGISTRY = registry.CONTEXT_OBSERVERS_REGISTRY; registry.QUERY_PROVIDERS_REGISTRY; var CONTEXT_SERVICES_REFERENCES = registry.CONTEXT_SERVICES_REFERENCES, CONTEXT_INSTANCES_REGISTRY = registry.CONTEXT_INSTANCES_REGISTRY, CONTEXT_SUBSCRIBERS_REGISTRY = registry.CONTEXT_SUBSCRIBERS_REGISTRY; var scope = { INTERNAL: { REGISTRY: registry, registerManager: function (ManagerClass, initialState, initialContext) { // Only if registry is empty -> create new instance, remember its context and save it to registry. if (!CONTEXT_INSTANCES_REGISTRY.has(ManagerClass)) { var instance_1 = new ManagerClass(initialState); /* * Inject initial context fields if provided for overriding on manager construction. */ if (initialContext) { Object.assign(instance_1.context, initialContext); } // todo: Add checkContext method call for dev bundle with warnings for initial state nesting. processComputed(instance_1.context); instance_1[SCOPE_SYMBOL] = scope; instance_1[SIGNAL_METADATA_SYMBOL] = collectProtoChainMetadata(ManagerClass, SIGNAL_METADATA_REGISTRY); instance_1[QUERY_METADATA_SYMBOL] = collectProtoChainMetadata(ManagerClass, QUERY_METADATA_REGISTRY); instance_1[SIGNALING_HANDLER_SYMBOL] = onMetadataSignalListenerCalled.bind(instance_1); CONTEXT_STATES_REGISTRY.set(ManagerClass, instance_1.context); CONTEXT_SERVICES_REFERENCES.set(ManagerClass, 0); CONTEXT_OBSERVERS_REGISTRY.set(ManagerClass, new Set()); SIGNAL_LISTENERS_REGISTRY.add(instance_1[SIGNALING_HANDLER_SYMBOL]); CONTEXT_INSTANCES_REGISTRY.set(ManagerClass, instance_1); /* * Notify subscribers if they exist. * Create new entry if it is needed. */ if (CONTEXT_SUBSCRIBERS_REGISTRY.has(ManagerClass)) { CONTEXT_SUBSCRIBERS_REGISTRY.get(ManagerClass).forEach(function (it) { it(instance_1.context); }); } else { CONTEXT_SUBSCRIBERS_REGISTRY.set(ManagerClass, new Set()); } return true; } else { return false; } }, unRegisterManager: function (ManagerClass) { if (CONTEXT_INSTANCES_REGISTRY.has(ManagerClass)) { var instance = CONTEXT_INSTANCES_REGISTRY.get(ManagerClass); /* * Unset handlers and stop affecting scope after unregister. * For external calls throw an exception (queries). */ instance[SCOPE_SYMBOL] = null; instance["setContext"] = noop; instance["forceUpdate"] = noop; /* * Most likely code will fail with null pointer in case of warning. * Or it will start work in an unexpected way with 'null' check. */ instance["emitSignal"] = throwAfterDisposal; instance["queryDataSync"] = throwAfterDisposal; instance["queryDataAsync"] = throwAfterDisposal; /* * Mark instance as disposed to enable internal logic related to lifecycle and async actions/timers. */ instance.IS_DISPOSED = true; SIGNAL_LISTENERS_REGISTRY.delete(instance[SIGNALING_HANDLER_SYMBOL]); CONTEXT_INSTANCES_REGISTRY.delete(ManagerClass); CONTEXT_STATES_REGISTRY.delete(ManagerClass); return true; } else { CONTEXT_INSTANCES_REGISTRY.delete(ManagerClass); CONTEXT_STATES_REGISTRY.delete(ManagerClass); return false; } /* * Observers and subscribers should not be affected by un-registering. */ }, addServiceObserver: function (ManagerClass, observer, referencesCount) { if (referencesCount === void 0) { referencesCount = CONTEXT_SERVICES_REFERENCES.get(ManagerClass) + 1; } CONTEXT_OBSERVERS_REGISTRY.get(ManagerClass).add(observer); CONTEXT_SERVICES_REFERENCES.set(ManagerClass, referencesCount); if (referencesCount === 1) { CONTEXT_INSTANCES_REGISTRY.get(ManagerClass)["onProvisionStarted"](); } }, removeServiceObserver: function (ManagerClass, observer, referencesCount) { if (referencesCount === void 0) { referencesCount = CONTEXT_SERVICES_REFERENCES.get(ManagerClass) - 1; } CONTEXT_OBSERVERS_REGISTRY.get(ManagerClass).delete(observer); CONTEXT_SERVICES_REFERENCES.set(ManagerClass, referencesCount); if (referencesCount === 0) { CONTEXT_INSTANCES_REGISTRY.get(ManagerClass)["onProvisionEnded"](); this.unRegisterManager(ManagerClass); } }, notifyObservers: function (manager) { var nextContext = manager.context; CONTEXT_STATES_REGISTRY.set(manager.constructor, nextContext); /* * Update observers of context manager (react context providers). */ CONTEXT_OBSERVERS_REGISTRY.get(manager.constructor).forEach(function (it) { it(); }); /* * Update subscribers of context manager. */ CONTEXT_SUBSCRIBERS_REGISTRY.get(manager.constructor).forEach(function (it) { it(nextContext); }); }, subscribeToManager: function (ManagerClass, subscriber) { if (!(ManagerClass.prototype instanceof ContextManager)) { throw new DreamstateError(EDreamstateErrorCode.TARGET_CONTEXT_MANAGER_EXPECTED, "Cannot subscribe to '".concat(ManagerClass === null || ManagerClass === void 0 ? void 0 : ManagerClass.name, "'.")); } CONTEXT_SUBSCRIBERS_REGISTRY.get(ManagerClass).add(subscriber); return function () { CONTEXT_SUBSCRIBERS_REGISTRY.get(ManagerClass).delete(subscriber); }; }, unsubscribeFromManager: function (ManagerClass, subscriber) { if (!(ManagerClass.prototype instanceof ContextManager)) { throw new DreamstateError(EDreamstateErrorCode.TARGET_CONTEXT_MANAGER_EXPECTED, "Cannot unsubscribe from '".concat(ManagerClass === null || ManagerClass === void 0 ? void 0 : ManagerClass.name, "'.")); } registry.CONTEXT_SUBSCRIBERS_REGISTRY.get(ManagerClass).delete(subscriber); } }, getContextOf: function (manager) { var context = CONTEXT_STATES_REGISTRY.get(manager); /* * Return new shallow copy of state to prevent modifications. * In case of non-existing manager typecasting value as T because it is * rather not expected case to force check all the time. */ if (context) { return Object.assign({}, context); } else { return null; } }, getInstanceOf: function (manager) { return CONTEXT_INSTANCES_REGISTRY.get(manager) || null; }, emitSignal: function (base, emitter) { if (emitter === void 0) { emitter = null; } return emitSignal(base, emitter, registry); }, subscribeToSignals: function (listener) { if (!isFunction(listener)) { throw new DreamstateError(EDreamstateErrorCode.INCORRECT_SIGNAL_LISTENER, typeof listener); } SIGNAL_LISTENERS_REGISTRY.add(listener); return function () { SIGNAL_LISTENERS_REGISTRY.delete(listener); }; }, unsubscribeFromSignals: function (listener) { SIGNAL_LISTENERS_REGISTRY.delete(listener); }, registerQueryProvider: function (queryType, listener) { return registerQueryProvider(queryType, listener, registry); }, unRegisterQueryProvider: function (queryType, listener) { return unRegisterQueryProvider(queryType, listener, registry); }, queryDataSync: function (query) { return queryDataSync(query, registry); }, queryDataAsync: function (queryRequest) { return queryDataAsync(queryRequest, registry); } }; return scope; } export { createScope };