react-obsidian 3.0.0-alpha.6 → 3.0.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/src/Obsidian.d.ts +4 -3
  2. package/dist/src/Obsidian.d.ts.map +1 -1
  3. package/dist/src/Obsidian.js +7 -4
  4. package/dist/src/Obsidian.js.map +1 -1
  5. package/dist/src/decorators/LifecycleBound.d.ts +1 -1
  6. package/dist/src/decorators/LifecycleBound.d.ts.map +1 -1
  7. package/dist/src/decorators/inject/Inject.d.ts.map +1 -1
  8. package/dist/src/decorators/inject/Inject.js.map +1 -1
  9. package/dist/src/decorators/inject/Injectable.d.ts +1 -1
  10. package/dist/src/decorators/inject/Injectable.d.ts.map +1 -1
  11. package/dist/src/decorators/inject/Injectable.js +2 -2
  12. package/dist/src/decorators/inject/Injectable.js.map +1 -1
  13. package/dist/src/graph/ObjectGraph.d.ts +1 -1
  14. package/dist/src/graph/ObjectGraph.d.ts.map +1 -1
  15. package/dist/src/graph/ObjectGraph.js +9 -2
  16. package/dist/src/graph/ObjectGraph.js.map +1 -1
  17. package/dist/src/graph/ServiceLocatorFactory.d.ts +1 -1
  18. package/dist/src/graph/ServiceLocatorFactory.d.ts.map +1 -1
  19. package/dist/src/graph/ServiceLocatorFactory.js +2 -2
  20. package/dist/src/graph/ServiceLocatorFactory.js.map +1 -1
  21. package/dist/src/graph/registry/GraphRegistry.d.ts +15 -2
  22. package/dist/src/graph/registry/GraphRegistry.d.ts.map +1 -1
  23. package/dist/src/graph/registry/GraphRegistry.js +107 -10
  24. package/dist/src/graph/registry/GraphRegistry.js.map +1 -1
  25. package/dist/src/injectors/class/ClassInjector.d.ts +1 -1
  26. package/dist/src/injectors/class/ClassInjector.d.ts.map +1 -1
  27. package/dist/src/injectors/class/ClassInjector.js +5 -14
  28. package/dist/src/injectors/class/ClassInjector.js.map +1 -1
  29. package/dist/src/injectors/class/LateInjector.d.ts +2 -1
  30. package/dist/src/injectors/class/LateInjector.d.ts.map +1 -1
  31. package/dist/src/injectors/class/LateInjector.js +13 -4
  32. package/dist/src/injectors/class/LateInjector.js.map +1 -1
  33. package/dist/src/injectors/components/ComponentInjector.d.ts +1 -1
  34. package/dist/src/injectors/components/ComponentInjector.d.ts.map +1 -1
  35. package/dist/src/injectors/components/ComponentInjector.js +5 -5
  36. package/dist/src/injectors/components/ComponentInjector.js.map +1 -1
  37. package/dist/src/injectors/components/InjectComponent.d.ts +1 -1
  38. package/dist/src/injectors/components/InjectComponent.d.ts.map +1 -1
  39. package/dist/src/injectors/components/InjectComponent.js +6 -5
  40. package/dist/src/injectors/components/InjectComponent.js.map +1 -1
  41. package/dist/src/injectors/components/useGraph.d.ts +1 -1
  42. package/dist/src/injectors/components/useGraph.d.ts.map +1 -1
  43. package/dist/src/injectors/components/useGraph.js +2 -2
  44. package/dist/src/injectors/components/useGraph.js.map +1 -1
  45. package/dist/src/injectors/components/useInjectionToken.d.ts +1 -1
  46. package/dist/src/injectors/components/useInjectionToken.d.ts.map +1 -1
  47. package/dist/src/injectors/components/useInjectionToken.js +5 -2
  48. package/dist/src/injectors/components/useInjectionToken.js.map +1 -1
  49. package/dist/src/injectors/hooks/HookInjector.d.ts +1 -1
  50. package/dist/src/injectors/hooks/HookInjector.d.ts.map +1 -1
  51. package/dist/src/injectors/hooks/HookInjector.js +2 -2
  52. package/dist/src/injectors/hooks/HookInjector.js.map +1 -1
  53. package/dist/src/injectors/hooks/InjectHook.d.ts +2 -2
  54. package/dist/src/injectors/hooks/InjectHook.d.ts.map +1 -1
  55. package/dist/src/injectors/hooks/InjectHook.js +4 -4
  56. package/dist/src/injectors/hooks/InjectHook.js.map +1 -1
  57. package/dist/src/utils/isString.d.ts +2 -0
  58. package/dist/src/utils/isString.d.ts.map +1 -0
  59. package/dist/src/utils/isString.js +7 -0
  60. package/dist/src/utils/isString.js.map +1 -0
  61. package/dist/src/utils/reflect.d.ts +1 -0
  62. package/dist/src/utils/reflect.d.ts.map +1 -1
  63. package/dist/src/utils/reflect.js +12 -1
  64. package/dist/src/utils/reflect.js.map +1 -1
  65. package/dist/test/fixtures/LifecycleBoundGraph.js +3 -3
  66. package/dist/test/fixtures/LifecycleBoundGraph.js.map +1 -1
  67. package/eslint.config.mjs +2 -1
  68. package/package.json +13 -10
  69. package/src/Obsidian.ts +10 -6
  70. package/src/decorators/LifecycleBound.ts +1 -1
  71. package/src/decorators/inject/Inject.ts +1 -4
  72. package/src/decorators/inject/Injectable.ts +2 -2
  73. package/src/graph/ObjectGraph.ts +10 -2
  74. package/src/graph/ServiceLocatorFactory.ts +2 -2
  75. package/src/graph/registry/GraphRegistry.ts +119 -10
  76. package/src/injectors/class/ClassInjector.ts +6 -14
  77. package/src/injectors/class/LateInjector.ts +10 -3
  78. package/src/injectors/components/ComponentInjector.tsx +5 -5
  79. package/src/injectors/components/InjectComponent.ts +6 -5
  80. package/src/injectors/components/useGraph.ts +2 -2
  81. package/src/injectors/components/useInjectionToken.ts +5 -2
  82. package/src/injectors/hooks/HookInjector.ts +2 -2
  83. package/src/injectors/hooks/InjectHook.ts +4 -4
  84. package/src/utils/isString.ts +3 -0
  85. package/src/utils/reflect.ts +14 -1
@@ -5,6 +5,8 @@ import GraphMiddlewareChain from './GraphMiddlewareChain';
5
5
  import { ObtainLifecycleBoundGraphException } from './ObtainLifecycleBoundGraphException';
6
6
  import { getMetadata } from '../../utils/reflect';
7
7
  import { getGlobal } from '../../utils/getGlobal';
8
+ import { isString } from '../../utils/isString';
9
+ import referenceCounter from '../../ReferenceCounter';
8
10
 
9
11
  export class GraphRegistry {
10
12
  private readonly constructorToInstance = new Map<Constructable<Graph>, Set<Graph>>();
@@ -14,16 +16,30 @@ export class GraphRegistry {
14
16
  private readonly nameToInstance = new Map<string, Graph>();
15
17
  private readonly graphToSubgraphs = new Map<Constructable<Graph>, Set<Constructable<Graph>>>();
16
18
  private readonly graphMiddlewares = new GraphMiddlewareChain();
19
+ private readonly keyToGenerator = new Map<string, () => Constructable<Graph>>();
20
+ private readonly keyToGraph = new Map<string, Constructable<Graph>>();
21
+ private readonly onClearListeners = new Map<Graph, Set<() => void>>();
17
22
 
18
23
  register(constructor: Constructable<Graph>, subgraphs: Constructable<Graph>[] = []) {
19
24
  this.graphToSubgraphs.set(constructor, new Set(subgraphs));
20
25
  }
21
26
 
22
- ensureRegistered(graph: Graph) {
27
+ registerGraphGenerator(key: string, generator: () => Constructable<Graph>) {
28
+ if (this.keyToGenerator.has(key)) throw new Error(`Attempted to register a graph generator for key "${key}" that is already registered.`);
29
+ this.keyToGenerator.set(key, generator);
30
+ }
31
+
32
+ ensureRegistered(keyOrGraph: string | Graph) {
33
+ if (isString(keyOrGraph)) return;
34
+ const graph = keyOrGraph;
23
35
  if (this.instanceToConstructor.get(graph)) return;
24
36
  this.set(graph.constructor as any, graph);
25
37
  }
26
38
 
39
+ public isInstantiated(G: Constructable<Graph>): boolean {
40
+ return (this.constructorToInstance.get(G)?.size ?? 0) > 0;
41
+ }
42
+
27
43
  getSubgraphs(graph: Graph): Graph[] {
28
44
  const Graph = this.instanceToConstructor.get(graph)!;
29
45
  const subgraphs = this.graphToSubgraphs.get(Graph) ?? new Set();
@@ -35,24 +51,85 @@ export class GraphRegistry {
35
51
  }
36
52
 
37
53
  resolve<T extends Graph>(
38
- Graph: Constructable<T>,
54
+ keyOrGraph: string | Constructable<T>,
39
55
  source: 'lifecycleOwner' | 'classInjection' | 'serviceLocator' = 'lifecycleOwner',
40
56
  props: any = undefined,
41
57
  injectionToken?: string,
42
58
  ): T {
59
+ const Graph = isString(keyOrGraph) ?
60
+ this.getGraphConstructorByKey<T>(keyOrGraph) :
61
+ keyOrGraph;
43
62
  if ((this.isSingleton(Graph) || this.isBoundToReactLifecycle(Graph)) && this.has(Graph, injectionToken)) {
44
- return this.isComponentScopedLifecycleBound(Graph)
45
- ? this.getByInjectionToken(Graph, injectionToken)
46
- : this.getFirst(Graph);
63
+ return this.isComponentScopedLifecycleBound(Graph) ?
64
+ this.getByInjectionToken(Graph, injectionToken) :
65
+ this.getFirst(Graph);
47
66
  }
48
67
  if (this.isBoundToReactLifecycle(Graph) && source !== 'lifecycleOwner') {
49
68
  throw new ObtainLifecycleBoundGraphException(Graph);
50
69
  }
51
70
  const graph = this.graphMiddlewares.resolve(Graph, props);
52
71
  this.set(Graph, graph, injectionToken);
72
+ this.instantiateCustomScopedSubgraphs(graph, props);
53
73
  return graph as T;
54
74
  }
55
75
 
76
+ private instantiateCustomScopedSubgraphs(graph: Graph, props: any) {
77
+ this.assertInstantiatingCustomScopedSubgraphFromSameScope(graph);
78
+ if (!this.isCustomScopedLifecycleBound(this.instanceToConstructor.get(graph)!)) return;
79
+ const customScope = getMetadata(this.instanceToConstructor.get(graph)!, 'lifecycleScope');
80
+ const subgraphs = this.getSubgraphsConstructors(graph);
81
+ const sameScopeSubgraphs = subgraphs.filter(
82
+ subgraph => getMetadata(subgraph, 'lifecycleScope') === customScope,
83
+ );
84
+ const instantiatedSubgraphs = sameScopeSubgraphs.map(
85
+ (subgraph) => {
86
+ return this.resolve(subgraph, 'lifecycleOwner', props);
87
+ },
88
+ );
89
+ instantiatedSubgraphs.forEach(subgraph => referenceCounter.retain(subgraph));
90
+ this.registerOnClearListener(graph, () => {
91
+ instantiatedSubgraphs.forEach(subgraph => referenceCounter.release(subgraph, () => this.clear(subgraph)));
92
+ });
93
+ }
94
+
95
+ private assertInstantiatingCustomScopedSubgraphFromSameScope(graph: Graph) {
96
+ const graphScope = getMetadata(this.instanceToConstructor.get(graph)!, 'lifecycleScope');
97
+ const subgraphs = this.getSubgraphsConstructors(graph);
98
+ subgraphs.forEach((subgraph) => {
99
+ const subgraphScope = getMetadata(subgraph, 'lifecycleScope');
100
+ if (
101
+ !this.isInstantiated(subgraph)
102
+ && this.isCustomScopedLifecycleBound(subgraph)
103
+ && graphScope !== subgraphScope
104
+ ) {
105
+ 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}`);
106
+ }
107
+ });
108
+ }
109
+
110
+ private getSubgraphsConstructors(graph: Graph | Constructable<Graph>): Constructable<Graph>[] {
111
+ const Graph = typeof graph === 'function' ? graph : this.instanceToConstructor.get(graph)!;
112
+ const directSubgraphs = Array.from(this.graphToSubgraphs.get(Graph) ?? new Set<Constructable<Graph>>());
113
+ if (directSubgraphs.length === 0) return [];
114
+ return [
115
+ ...directSubgraphs,
116
+ ...new Set(
117
+ directSubgraphs
118
+ .map(subgraph => this.getSubgraphsConstructors(subgraph))
119
+ .flat(),
120
+ ),
121
+ ];
122
+ }
123
+
124
+ private getGraphConstructorByKey<T extends Graph>(key: string): Constructable<T> {
125
+ if (this.keyToGraph.has(key)) return this.keyToGraph.get(key) as Constructable<T>;
126
+ const generator = this.keyToGenerator.get(key);
127
+ if (!generator) throw new Error(`Attempted to resolve a graph by key "${key}" that is not registered. Did you forget to call Obsidian.registerGraph?`);
128
+ const constructor = generator();
129
+ this.keyToGraph.set(key, constructor);
130
+ return constructor as Constructable<T>;
131
+ }
132
+
56
133
  private has(Graph: Constructable<Graph>, injectionToken?: string): boolean {
57
134
  const instances = this.constructorToInstance.get(Graph);
58
135
  if (!instances) return false;
@@ -102,6 +179,11 @@ export class GraphRegistry {
102
179
  return getMetadata(Graph, 'lifecycleScope') === 'component';
103
180
  }
104
181
 
182
+ private isCustomScopedLifecycleBound(Graph: Constructable<Graph>): boolean {
183
+ const scope = getMetadata(Graph, 'lifecycleScope');
184
+ return typeof scope === 'string' && scope !== 'component' && scope !== 'feature';
185
+ }
186
+
105
187
  clearGraphAfterItWasMockedInTests(graphName: string) {
106
188
  const graphNames = this.nameToInstance.keys();
107
189
  for (const name of graphNames) {
@@ -119,6 +201,7 @@ export class GraphRegistry {
119
201
  this.injectionTokenToInstance.delete(token);
120
202
  this.instanceToInjectionToken.delete(graph);
121
203
  }
204
+ this.invokeOnClearListeners(graph);
122
205
  }
123
206
  }
124
207
  }
@@ -134,6 +217,32 @@ export class GraphRegistry {
134
217
  this.injectionTokenToInstance.delete(token);
135
218
  this.instanceToInjectionToken.delete(graph);
136
219
  }
220
+
221
+ this.clearGraphsRegisteredByKey(Graph);
222
+ this.invokeOnClearListeners(graph);
223
+ }
224
+
225
+ private registerOnClearListener(graph: Graph, callback: () => void) {
226
+ const listeners = this.onClearListeners.get(graph) ?? new Set();
227
+ listeners.add(callback);
228
+ this.onClearListeners.set(graph, listeners);
229
+ }
230
+
231
+ private invokeOnClearListeners(graph: Graph) {
232
+ const listeners = this.onClearListeners.get(graph);
233
+ if (!listeners) return;
234
+ listeners.forEach(listener => listener());
235
+ this.onClearListeners.delete(graph);
236
+ }
237
+
238
+ private clearGraphsRegisteredByKey(Graph: Constructable<Graph>) {
239
+ [...this.keyToGraph.keys()]
240
+ .map(key => [key, this.keyToGraph.get(key)!] as [string, Constructable<Graph>])
241
+ .filter(([_, $Graph]) => $Graph === Graph)
242
+ .forEach(([key, _]) => {
243
+ this.keyToGraph.delete(key);
244
+ this.keyToGenerator.delete(key);
245
+ });
137
246
  }
138
247
 
139
248
  addGraphMiddleware(middleware: Middleware<Graph>) {
@@ -150,11 +259,11 @@ export class GraphRegistry {
150
259
  this.nameToInstance.clear();
151
260
  this.injectionTokenToInstance.clear();
152
261
  this.instanceToInjectionToken.clear();
262
+ this.keyToGenerator.clear();
263
+ this.keyToGraph.clear();
153
264
  }
154
265
  }
155
266
 
156
- // // @ts-expect-error - workaround an issue in jest tests where the registry was created multiple times
157
- const _global = getGlobal();
158
- _global.graphRegistry = _global.graphRegistry || new GraphRegistry();
159
- // // @ts-expect-error - see above
160
- export default _global.graphRegistry as GraphRegistry;
267
+ const globalObject = getGlobal();
268
+ globalObject.graphRegistry = globalObject.graphRegistry || new GraphRegistry();
269
+ export default globalObject.graphRegistry as GraphRegistry;
@@ -12,14 +12,14 @@ export default class ClassInjector {
12
12
  private injectionMetadata: InjectionMetadata = new InjectionMetadata(),
13
13
  ) {}
14
14
 
15
- inject(Graph: Constructable<Graph>) {
15
+ inject(keyOrGraph: string | Constructable<Graph>) {
16
16
  return (Target: Constructable<any>) => {
17
- return new Proxy(Target, this.createProxyHandler(Graph, this.graphRegistry, this.injectionMetadata));
17
+ return new Proxy(Target, this.createProxyHandler(keyOrGraph, this.graphRegistry, this.injectionMetadata));
18
18
  };
19
19
  }
20
20
 
21
21
  private createProxyHandler(
22
- Graph: Constructable<Graph>,
22
+ keyOrGraph: string | Constructable<Graph>,
23
23
  graphRegistry: GraphRegistry,
24
24
  injectionMetadata: InjectionMetadata,
25
25
  ): ProxyHandler<any> {
@@ -27,14 +27,14 @@ export default class ClassInjector {
27
27
  construct(target: any, args: any[], newTarget: Function): any {
28
28
  const isReactClassComponent = target.prototype?.isReactComponent;
29
29
  const source = isReactClassComponent ? 'lifecycleOwner' : 'classInjection';
30
- const graph = graphRegistry.resolve(Graph, source, args.length > 0 ? args[0] : undefined);
30
+ const graph = graphRegistry.resolve(keyOrGraph, source, args.length > 0 ? args[0] : undefined);
31
31
  if (isReactClassComponent) {
32
32
  referenceCounter.retain(graph);
33
33
  }
34
34
  defineMetadata(target, GRAPH_INSTANCE_NAME_KEY, graph.name);
35
- const argsToInject = this.injectConstructorArgs(args, graph, target);
35
+
36
36
  graph.onBind(target);
37
- const createdObject = Reflect.construct(target, argsToInject, newTarget);
37
+ const createdObject = Reflect.construct(target, args, newTarget);
38
38
  this.injectProperties(target, createdObject, graph);
39
39
  const originalComponentWillUnmount: () => void | undefined = createdObject.componentWillUnmount;
40
40
  createdObject.componentWillUnmount = () => {
@@ -44,14 +44,6 @@ export default class ClassInjector {
44
44
  return createdObject;
45
45
  }
46
46
 
47
- private injectConstructorArgs(args: any[], graph: Graph, target: any): any[] {
48
- const argsToInject = injectionMetadata.getConstructorArgsToInject(target);
49
- if (!argsToInject.hasArgs()) return args;
50
- return [...args, ...new Array(Math.abs(args.length - argsToInject.size()))].map((value, idx): any => {
51
- return value ?? graph.retrieve(argsToInject.getProperty(idx));
52
- });
53
- }
54
-
55
47
  private injectProperties(target: any, createdObject: any, graph: Graph) {
56
48
  injectionMetadata.getPropertiesToInject(target).forEach((key) => {
57
49
  Reflect.set(createdObject, key, graph.retrieve(key));
@@ -1,3 +1,4 @@
1
+ import { isString } from 'lodash';
1
2
  import { ObjectGraph } from '../../graph/ObjectGraph';
2
3
  import graphRegistry from '../../graph/registry/GraphRegistry';
3
4
  import { getMetadata } from '../../utils/reflect';
@@ -6,16 +7,22 @@ import InjectionMetadata from './InjectionMetadata';
6
7
  export const GRAPH_INSTANCE_NAME_KEY = 'GRAPH_INSTANCE_NAME';
7
8
 
8
9
  class LateInjector<T extends object> {
9
- inject(target: T, sourceGraph?: ObjectGraph): T {
10
- if (sourceGraph) graphRegistry.ensureRegistered(sourceGraph);
10
+ inject(target: T, keyOrGraph?: string | ObjectGraph): T {
11
+ if (keyOrGraph) graphRegistry.ensureRegistered(keyOrGraph);
11
12
  const injectionMetadata = new InjectionMetadata();
12
- const graph = sourceGraph ?? this.getGraphInstance(target);
13
+ const graph = this.getGraph(target, keyOrGraph);
13
14
  injectionMetadata.getLatePropertiesToInject(target.constructor).forEach((key) => {
14
15
  Reflect.set(target, key, graph.retrieve(key));
15
16
  });
16
17
  return target;
17
18
  }
18
19
 
20
+ private getGraph(target: T, keyOrGraph?: string | ObjectGraph) {
21
+ if (keyOrGraph instanceof ObjectGraph) return keyOrGraph;
22
+ if (isString(keyOrGraph)) return graphRegistry.resolve(keyOrGraph, 'classInjection');
23
+ return this.getGraphInstance(target);
24
+ }
25
+
19
26
  private getGraphInstance(target: T) {
20
27
  const graphInstanceName = getMetadata(target.constructor, GRAPH_INSTANCE_NAME_KEY);
21
28
  return graphRegistry.getGraphInstance(graphInstanceName);
@@ -11,24 +11,24 @@ import { useInjectionToken } from './useInjectionToken';
11
11
  export default class ComponentInjector {
12
12
  inject<P>(
13
13
  Target: React.FunctionComponent<P>,
14
- Graph: Constructable<ObjectGraph>,
14
+ keyOrGraph: string | Constructable<ObjectGraph>,
15
15
  ): React.FunctionComponent<Partial<P>> {
16
- const Wrapped = this.wrapComponent(Target, Graph);
16
+ const Wrapped = this.wrapComponent(Target, keyOrGraph);
17
17
  hoistNonReactStatics(Wrapped, Target);
18
18
  return Wrapped;
19
19
  }
20
20
 
21
21
  private wrapComponent<P>(
22
22
  InjectionCandidate: React.FunctionComponent<P>,
23
- Graph: Constructable<ObjectGraph>,
23
+ keyOrGraph: string | Constructable<ObjectGraph>,
24
24
  ): React.FunctionComponent<Partial<P>> {
25
25
  const isMemoized = isMemoizedComponent(InjectionCandidate);
26
26
  const Target = isMemoized ? InjectionCandidate.type : InjectionCandidate;
27
27
  const compare = isMemoized ? InjectionCandidate.compare : undefined;
28
28
 
29
29
  return genericMemo((passedProps: P) => {
30
- const injectionToken = useInjectionToken(Graph);
31
- const graph = useGraph<P>(Graph, Target, passedProps, injectionToken);
30
+ const injectionToken = useInjectionToken(keyOrGraph);
31
+ const graph = useGraph<P>(keyOrGraph, Target, passedProps, injectionToken);
32
32
  const proxiedProps = new PropsInjector(graph).inject(passedProps);
33
33
 
34
34
  return (
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { ObjectGraph } from '../../graph/ObjectGraph';
3
3
  import { Constructable } from '../../types';
4
+ import { isString } from '../../utils/isString';
4
5
  import ComponentInjector from './ComponentInjector';
5
6
 
6
7
  interface Discriminator {
@@ -14,18 +15,18 @@ export const injectComponent = <OwnProps = Discriminator, InjectedProps = Discri
14
15
  (OwnProps extends infer P ? OwnProps extends Discriminator ? P : OwnProps : never) &
15
16
  (InjectedProps extends Discriminator ? any : InjectedProps)
16
17
  >,
17
- Graph: Constructable<ObjectGraph>,
18
+ keyOrGraph: string | Constructable<ObjectGraph>,
18
19
  ) => {
19
- assertGraph(Graph, Target);
20
+ assertGraph(keyOrGraph, Target);
20
21
 
21
- return componentInjector.inject(Target, Graph) as React.FunctionComponent<
22
+ return componentInjector.inject(Target, keyOrGraph) as React.FunctionComponent<
22
23
  InjectedProps extends Discriminator ?
23
24
  OwnProps extends Discriminator ? Partial<OwnProps> : OwnProps :
24
25
  OwnProps extends InjectedProps ? Partial<OwnProps> : OwnProps & Partial<InjectedProps>
25
26
  >;
26
27
  };
27
- function assertGraph(Graph: Constructable<ObjectGraph<unknown>>, Target: any) {
28
- if (!Graph) {
28
+ function assertGraph(keyOrGraph: string | Constructable<ObjectGraph>, Target: any) {
29
+ if (!isString(keyOrGraph) && !keyOrGraph) {
29
30
  throw new Error(
30
31
  `injectComponent was called with an undefined Graph.`
31
32
  + `This is probably not an issue with Obsidian.`
@@ -5,13 +5,13 @@ import graphRegistry from '../../graph/registry/GraphRegistry';
5
5
  import referenceCounter from '../../ReferenceCounter';
6
6
 
7
7
  export default <P>(
8
- Graph: Constructable<ObjectGraph>,
8
+ keyOrGraph: string | Constructable<ObjectGraph>,
9
9
  target: any,
10
10
  props?: Partial<P>,
11
11
  injectionToken?: string,
12
12
  ) => {
13
13
  const [graph] = useState(() => {
14
- const resolvedGraph = graphRegistry.resolve(Graph, 'lifecycleOwner', props, injectionToken);
14
+ const resolvedGraph = graphRegistry.resolve(keyOrGraph, 'lifecycleOwner', props, injectionToken);
15
15
  resolvedGraph.onBind(target);
16
16
  return resolvedGraph;
17
17
  });
@@ -2,9 +2,12 @@ import { useContext, useState } from 'react';
2
2
  import { GraphContext } from './graphContext';
3
3
  import type { Constructable, ObjectGraph } from '../..';
4
4
  import { uniqueId } from '../../utils/uniqueId';
5
+ import { isString } from '../../utils/isString';
5
6
 
6
- export const useInjectionToken = (Graph: Constructable<ObjectGraph>) => {
7
+ export const useInjectionToken = (keyOrGraph: string | Constructable<ObjectGraph>) => {
7
8
  const ctx = useContext(GraphContext);
8
- const [injectionToken] = useState(() => ctx?.injectionToken ?? uniqueId(Graph.name));
9
+ const [injectionToken] = useState(() => {
10
+ return ctx?.injectionToken ?? uniqueId(isString(keyOrGraph) ? keyOrGraph : keyOrGraph.name);
11
+ });
9
12
  return injectionToken;
10
13
  };
@@ -6,10 +6,10 @@ import { Constructable } from '../../types';
6
6
  export default class HookInjector {
7
7
  inject<Args, Result>(
8
8
  hook: (args: Args) => Result,
9
- Graph: Constructable<ObjectGraph>,
9
+ keyOrGraph: string | Constructable<ObjectGraph>,
10
10
  ): (args?: Partial<Args>) => Result {
11
11
  return (args?: Partial<Args>): Result => {
12
- const graph = useGraph(Graph, hook, args);
12
+ const graph = useGraph(keyOrGraph, hook, args);
13
13
  return hook(new Proxy(args ?? {}, new Injector(graph)));
14
14
  };
15
15
  }
@@ -11,14 +11,14 @@ const hookInjector = new HookInjector();
11
11
 
12
12
  export function injectHookWithArguments<Injected, Own, Result = {}>(
13
13
  hook: (args: Injected & Own) => Result,
14
- Graph: Constructable<ObjectGraph>,
14
+ keyOrGraph: string | Constructable<ObjectGraph>,
15
15
  ): (props: Own & Partial<Injected>) => Result {
16
- return hookInjector.inject(hook, Graph) as (props: Own & Partial<Injected>) => Result;
16
+ return hookInjector.inject(hook, keyOrGraph) as (props: Own & Partial<Injected>) => Result;
17
17
  }
18
18
 
19
19
  export function injectHook<Injected, Result = {}>(
20
20
  hook: (args: Injected) => Result,
21
- Graph: Constructable<ObjectGraph>,
21
+ keyOrGraph: string | Constructable<ObjectGraph>,
22
22
  ): (props?: Partial<Injected>) => Result {
23
- return hookInjector.inject(hook, Graph);
23
+ return hookInjector.inject(hook, keyOrGraph);
24
24
  }
@@ -0,0 +1,3 @@
1
+ export function isString(value: any): value is string {
2
+ return typeof value === 'string';
3
+ }
@@ -1,4 +1,12 @@
1
- const metadataStore = new WeakMap();
1
+ import { getGlobal } from './getGlobal';
2
+
3
+ function getStore(): WeakMap<any, any> {
4
+ const global = getGlobal();
5
+ global.__metadataStore = global.__metadataStore || new WeakMap();
6
+ return global.__metadataStore;
7
+ }
8
+
9
+ const metadataStore = getStore();
2
10
 
3
11
  export function defineMetadata(target: any, key: string, value: any) {
4
12
  let metadata = metadataStore.get(target);
@@ -15,3 +23,8 @@ export function getMetadata(target: any, key: string) {
15
23
  const metadata = metadataStore.get(target);
16
24
  return metadata ? metadata[key] : undefined;
17
25
  }
26
+
27
+ export function hasMetadata(target: any, key: string) {
28
+ const metadata = metadataStore.get(target);
29
+ return metadata ? metadata[key] !== undefined : false;
30
+ }