dreamstate
Version:
Store management library based on react context and observers
215 lines (212 loc) • 9.88 kB
JavaScript
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 };