UNPKG

react-obsidian

Version:

Dependency injection framework for React and React Native applications

158 lines (135 loc) 5.9 kB
import { Constructable } from '../../types'; import { Graph } from '../Graph'; import { Middleware } from './Middleware'; import GraphMiddlewareChain from './GraphMiddlewareChain'; import { ObtainLifecycleBoundGraphException } from './ObtainLifecycleBoundGraphException'; import { getGlobal } from '../../utils/getGlobal'; export class GraphRegistry { private readonly constructorToInstance = new Map<Constructable<Graph>, Set<Graph>>(); private readonly instanceToConstructor = new Map<Graph, Constructable<Graph>>(); private readonly injectionTokenToInstance = new Map<string, Graph>(); private readonly instanceToInjectionToken = new Map<Graph, string>(); private readonly nameToInstance = new Map<string, Graph>(); private readonly graphToSubgraphs = new Map<Constructable<Graph>, Set<Constructable<Graph>>>(); private readonly graphMiddlewares = new GraphMiddlewareChain(); register(constructor: Constructable<Graph>, subgraphs: Constructable<Graph>[] = []) { this.graphToSubgraphs.set(constructor, new Set(subgraphs)); } ensureRegistered(graph: Graph) { if (this.instanceToConstructor.get(graph)) return; this.set(graph.constructor as any, graph); } getSubgraphs(graph: Graph): Graph[] { const Graph = this.instanceToConstructor.get(graph)!; const subgraphs = this.graphToSubgraphs.get(Graph) ?? new Set(); return Array.from(subgraphs).map((G) => this.resolve(G)); } getGraphInstance(name: string): Graph { return this.nameToInstance.get(name)!; } resolve<T extends Graph>( Graph: Constructable<T>, source: 'lifecycleOwner' | 'classInjection' | 'serviceLocator' = 'lifecycleOwner', props: any = undefined, injectionToken?: string, ): T { 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(Graph); } const graph = this.graphMiddlewares.resolve(Graph, props); this.set(Graph, graph, injectionToken); return graph as T; } private has(Graph: Constructable<Graph>, injectionToken?: string): boolean { 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 (this.constructorToInstance.get(Graph)?.size ?? 0) > 0; } private getFirst<T extends Graph>(Graph: Constructable<T>): T { return this.constructorToInstance.get(Graph)!.values().next().value as T; } private getByInjectionToken<T extends Graph>(Graph: Constructable<T>, injectionToken?: string): T { return Array .from(this.constructorToInstance.get(Graph)!) .find((graph) => { return this.instanceToInjectionToken.get(graph) === injectionToken; }) as T; } private set(Graph: Constructable<Graph>, graph: Graph, injectionToken?: string) { const graphs = this.constructorToInstance.get(Graph) ?? 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); } private isSingleton(Graph: Constructable<Graph>): boolean { return Reflect.getMetadata('isSingleton', Graph) ?? false; } private isBoundToReactLifecycle(Graph: Constructable<Graph>): boolean { return Reflect.getMetadata('isLifecycleBound', Graph) ?? false; } private isComponentScopedLifecycleBound(Graph: Constructable<Graph>): boolean { return Reflect.getMetadata('lifecycleScope', Graph) === 'component'; } clearGraphAfterItWasMockedInTests(graphName: string) { 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); } } } } clear(graph: 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); } } addGraphMiddleware(middleware: Middleware<Graph>) { this.graphMiddlewares.add(middleware); } clearGraphMiddlewares() { this.graphMiddlewares.clear(); } clearAll() { this.instanceToConstructor.clear(); this.constructorToInstance.clear(); this.nameToInstance.clear(); this.injectionTokenToInstance.clear(); this.instanceToInjectionToken.clear(); } } const globalObject = getGlobal(); globalObject.graphRegistry = globalObject.graphRegistry || new GraphRegistry(); export default globalObject.graphRegistry as GraphRegistry;