react-obsidian
Version:
Dependency injection framework for React and React Native applications
250 lines • 11.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GraphRegistry = void 0;
const GraphMiddlewareChain_1 = __importDefault(require("./GraphMiddlewareChain"));
const ObtainLifecycleBoundGraphException_1 = require("./ObtainLifecycleBoundGraphException");
const getGlobal_1 = require("../../utils/getGlobal");
const isString_1 = require("../../utils/isString");
const ReferenceCounter_1 = __importDefault(require("../../ReferenceCounter"));
const reflect_1 = require("../../utils/reflect");
class GraphRegistry {
constructor() {
this.constructorToInstance = new Map();
this.instanceToConstructor = new Map();
this.injectionTokenToInstance = new Map();
this.instanceToInjectionToken = new Map();
this.nameToInstance = new Map();
this.graphToSubgraphs = new Map();
this.graphMiddlewares = new GraphMiddlewareChain_1.default();
this.keyToGenerator = new Map();
this.keyToGraph = new Map();
this.onClearListeners = new Map();
}
register(constructor, subgraphs = []) {
this.graphToSubgraphs.set(constructor, new Set(subgraphs));
}
registerGraphGenerator(key, generator) {
if (this.keyToGenerator.has(key))
throw new Error(`Attempted to register a graph generator for key "${key}" that is already registered.`);
this.keyToGenerator.set(key, generator);
}
ensureRegistered(keyOrGraph) {
if ((0, isString_1.isString)(keyOrGraph))
return;
const graph = keyOrGraph;
if (this.instanceToConstructor.get(graph))
return;
this.set(graph.constructor, graph);
}
isInstantiated(G) {
var _a, _b;
return ((_b = (_a = this.constructorToInstance.get(G)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0) > 0;
}
getSubgraphs(graph) {
var _a;
const Graph = this.instanceToConstructor.get(graph);
const subgraphs = (_a = this.graphToSubgraphs.get(Graph)) !== null && _a !== void 0 ? _a : new Set();
return Array.from(subgraphs).map((G) => this.resolve(G));
}
getGraphInstance(name) {
return this.nameToInstance.get(name);
}
resolve(keyOrGraph, source = 'lifecycleOwner', props = undefined, injectionToken) {
const Graph = (0, isString_1.isString)(keyOrGraph) ?
this.getGraphConstructorByKey(keyOrGraph) :
keyOrGraph;
if ((this.isSingleton(Graph) || this.isBoundToReactLifecycle(Graph)) && this.has(Graph, injectionToken)) {
return this.isComponentScopedLifecycleBound(Graph) ?
this.getByInjectionToken(Graph, injectionToken) :
this.getFirst(Graph);
}
if (this.isBoundToReactLifecycle(Graph) && source !== 'lifecycleOwner') {
throw new ObtainLifecycleBoundGraphException_1.ObtainLifecycleBoundGraphException(Graph);
}
const graph = this.graphMiddlewares.resolve(Graph, props);
this.set(Graph, graph, injectionToken);
this.instantiateCustomScopedSubgraphs(graph, props);
return graph;
}
instantiateCustomScopedSubgraphs(graph, props) {
this.assertInstantiatingCustomScopedSubgraphFromSameScope(graph);
if (!this.isCustomScopedLifecycleBound(this.instanceToConstructor.get(graph)))
return;
const customScope = reflect_1.Reflect.getMetadata('lifecycleScope', this.instanceToConstructor.get(graph));
const subgraphs = this.getSubgraphsConstructors(graph);
const sameScopeSubgraphs = subgraphs.filter(subgraph => reflect_1.Reflect.getMetadata('lifecycleScope', subgraph) === customScope);
const instantiatedSubgraphs = sameScopeSubgraphs.map(subgraph => {
return this.resolve(subgraph, 'lifecycleOwner', props);
});
instantiatedSubgraphs.forEach((subgraph) => ReferenceCounter_1.default.retain(subgraph));
this.registerOnClearListener(graph, () => {
instantiatedSubgraphs.forEach((subgraph) => ReferenceCounter_1.default.release(subgraph, () => this.clear(subgraph)));
});
}
assertInstantiatingCustomScopedSubgraphFromSameScope(graph) {
const graphScope = reflect_1.Reflect.getMetadata('lifecycleScope', this.instanceToConstructor.get(graph));
const subgraphs = this.getSubgraphsConstructors(graph);
subgraphs.forEach(subgraph => {
const subgraphScope = reflect_1.Reflect.getMetadata('lifecycleScope', subgraph);
if (!this.isInstantiated(subgraph) &&
this.isCustomScopedLifecycleBound(subgraph) &&
graphScope !== subgraphScope) {
throw new Error(`Cannot instantiate the scoped graph '${subgraph.name}' as a subgraph of '${graph.constructor.name}' because the scopes do not match. ${graphScope} !== ${subgraphScope}`);
}
});
}
getSubgraphsConstructors(graph) {
var _a;
const Graph = typeof graph === 'function' ? graph : this.instanceToConstructor.get(graph);
const directSubgraphs = Array.from((_a = this.graphToSubgraphs.get(Graph)) !== null && _a !== void 0 ? _a : new Set());
if (directSubgraphs.length === 0)
return [];
return [
...directSubgraphs,
...new Set(directSubgraphs
.map(subgraph => this.getSubgraphsConstructors(subgraph))
.flat()),
];
}
getGraphConstructorByKey(key) {
if (this.keyToGraph.has(key))
return this.keyToGraph.get(key);
const generator = this.keyToGenerator.get(key);
if (!generator)
throw new Error(`Attempted to resolve a graph by key "${key}" that is not registered. Did you forget to call Obsidian.registerGraph?`);
const constructor = generator();
this.keyToGraph.set(key, constructor);
return constructor;
}
has(Graph, injectionToken) {
var _a, _b;
const instances = this.constructorToInstance.get(Graph);
if (!instances)
return false;
if (this.isComponentScopedLifecycleBound(Graph)) {
return Array
.from(instances)
.some((graph) => this.instanceToInjectionToken.get(graph) === injectionToken);
}
return ((_b = (_a = this.constructorToInstance.get(Graph)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0) > 0;
}
getFirst(Graph) {
return this.constructorToInstance.get(Graph).values().next().value;
}
getByInjectionToken(Graph, injectionToken) {
return Array
.from(this.constructorToInstance.get(Graph))
.find((graph) => {
return this.instanceToInjectionToken.get(graph) === injectionToken;
});
}
set(Graph, graph, injectionToken) {
var _a;
const graphs = (_a = this.constructorToInstance.get(Graph)) !== null && _a !== void 0 ? _a : new Set();
if (injectionToken && this.isComponentScopedLifecycleBound(Graph)) {
this.injectionTokenToInstance.set(injectionToken, graph);
this.instanceToInjectionToken.set(graph, injectionToken);
}
graphs.add(graph);
this.constructorToInstance.set(Graph, graphs);
this.instanceToConstructor.set(graph, Graph);
this.nameToInstance.set(graph.name, graph);
}
isSingleton(Graph) {
var _a;
return (_a = reflect_1.Reflect.getMetadata('isSingleton', Graph)) !== null && _a !== void 0 ? _a : false;
}
isBoundToReactLifecycle(Graph) {
var _a;
return (_a = reflect_1.Reflect.getMetadata('isLifecycleBound', Graph)) !== null && _a !== void 0 ? _a : false;
}
isComponentScopedLifecycleBound(Graph) {
return reflect_1.Reflect.getMetadata('lifecycleScope', Graph) === 'component';
}
isCustomScopedLifecycleBound(Graph) {
const scope = reflect_1.Reflect.getMetadata('lifecycleScope', Graph);
return typeof scope === 'string' && scope !== 'component' && scope !== 'feature';
}
clearGraphAfterItWasMockedInTests(graphName) {
const graphNames = this.nameToInstance.keys();
for (const name of graphNames) {
if (name.match(graphName)) {
const graph = this.nameToInstance.get(name);
if (!graph)
return;
const Graph = this.instanceToConstructor.get(graph);
if (!Graph)
return;
this.instanceToConstructor.delete(graph);
this.constructorToInstance.get(Graph).delete(graph);
this.nameToInstance.delete(graph.name);
const token = this.instanceToInjectionToken.get(graph);
if (token) {
this.injectionTokenToInstance.delete(token);
this.instanceToInjectionToken.delete(graph);
}
this.invokeOnClearListeners(graph);
}
}
}
clear(graph) {
const Graph = this.instanceToConstructor.get(graph);
if (!Graph || this.isSingleton(Graph))
return;
this.instanceToConstructor.delete(graph);
this.constructorToInstance.get(Graph).delete(graph);
this.nameToInstance.delete(graph.name);
const token = this.instanceToInjectionToken.get(graph);
if (token) {
this.injectionTokenToInstance.delete(token);
this.instanceToInjectionToken.delete(graph);
}
this.clearGraphsRegisteredByKey(Graph);
this.invokeOnClearListeners(graph);
}
registerOnClearListener(graph, callback) {
var _a;
const listeners = (_a = this.onClearListeners.get(graph)) !== null && _a !== void 0 ? _a : new Set();
listeners.add(callback);
this.onClearListeners.set(graph, listeners);
}
invokeOnClearListeners(graph) {
const listeners = this.onClearListeners.get(graph);
if (!listeners)
return;
listeners.forEach((listener) => listener());
this.onClearListeners.delete(graph);
}
clearGraphsRegisteredByKey(Graph) {
[...this.keyToGraph.keys()]
.map((key) => [key, this.keyToGraph.get(key)])
.filter(([_, $Graph]) => $Graph === Graph)
.forEach(([key, _]) => {
this.keyToGraph.delete(key);
this.keyToGenerator.delete(key);
});
}
addGraphMiddleware(middleware) {
this.graphMiddlewares.add(middleware);
}
clearGraphMiddlewares() {
this.graphMiddlewares.clear();
}
clearAll() {
this.instanceToConstructor.clear();
this.constructorToInstance.clear();
this.nameToInstance.clear();
this.injectionTokenToInstance.clear();
this.instanceToInjectionToken.clear();
this.keyToGenerator.clear();
this.keyToGraph.clear();
}
}
exports.GraphRegistry = GraphRegistry;
const globalObject = (0, getGlobal_1.getGlobal)();
globalObject.graphRegistry = globalObject.graphRegistry || new GraphRegistry();
exports.default = globalObject.graphRegistry;
//# sourceMappingURL=GraphRegistry.js.map