react-obsidian 3.0.0-alpha.3 → 3.0.0-alpha.7

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 (107) 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.map +1 -1
  14. package/dist/src/graph/ObjectGraph.js +7 -3
  15. package/dist/src/graph/ObjectGraph.js.map +1 -1
  16. package/dist/src/graph/ServiceLocatorFactory.d.ts +1 -1
  17. package/dist/src/graph/ServiceLocatorFactory.d.ts.map +1 -1
  18. package/dist/src/graph/ServiceLocatorFactory.js +2 -2
  19. package/dist/src/graph/ServiceLocatorFactory.js.map +1 -1
  20. package/dist/src/graph/registry/GraphRegistry.d.ts +15 -2
  21. package/dist/src/graph/registry/GraphRegistry.d.ts.map +1 -1
  22. package/dist/src/graph/registry/GraphRegistry.js +108 -9
  23. package/dist/src/graph/registry/GraphRegistry.js.map +1 -1
  24. package/dist/src/injectors/class/ClassInjector.d.ts +1 -1
  25. package/dist/src/injectors/class/ClassInjector.d.ts.map +1 -1
  26. package/dist/src/injectors/class/ClassInjector.js +5 -14
  27. package/dist/src/injectors/class/ClassInjector.js.map +1 -1
  28. package/dist/src/injectors/class/LateInjector.d.ts +2 -1
  29. package/dist/src/injectors/class/LateInjector.d.ts.map +1 -1
  30. package/dist/src/injectors/class/LateInjector.js +13 -4
  31. package/dist/src/injectors/class/LateInjector.js.map +1 -1
  32. package/dist/src/injectors/components/ComponentInjector.d.ts +1 -1
  33. package/dist/src/injectors/components/ComponentInjector.d.ts.map +1 -1
  34. package/dist/src/injectors/components/ComponentInjector.js +5 -5
  35. package/dist/src/injectors/components/ComponentInjector.js.map +1 -1
  36. package/dist/src/injectors/components/InjectComponent.d.ts +1 -1
  37. package/dist/src/injectors/components/InjectComponent.d.ts.map +1 -1
  38. package/dist/src/injectors/components/InjectComponent.js +6 -5
  39. package/dist/src/injectors/components/InjectComponent.js.map +1 -1
  40. package/dist/src/injectors/components/useGraph.d.ts +1 -1
  41. package/dist/src/injectors/components/useGraph.d.ts.map +1 -1
  42. package/dist/src/injectors/components/useGraph.js +2 -2
  43. package/dist/src/injectors/components/useGraph.js.map +1 -1
  44. package/dist/src/injectors/components/useInjectionToken.d.ts +1 -1
  45. package/dist/src/injectors/components/useInjectionToken.d.ts.map +1 -1
  46. package/dist/src/injectors/components/useInjectionToken.js +5 -2
  47. package/dist/src/injectors/components/useInjectionToken.js.map +1 -1
  48. package/dist/src/injectors/hooks/HookInjector.d.ts +1 -1
  49. package/dist/src/injectors/hooks/HookInjector.d.ts.map +1 -1
  50. package/dist/src/injectors/hooks/HookInjector.js +2 -2
  51. package/dist/src/injectors/hooks/HookInjector.js.map +1 -1
  52. package/dist/src/injectors/hooks/InjectHook.d.ts +2 -2
  53. package/dist/src/injectors/hooks/InjectHook.d.ts.map +1 -1
  54. package/dist/src/injectors/hooks/InjectHook.js +4 -4
  55. package/dist/src/injectors/hooks/InjectHook.js.map +1 -1
  56. package/dist/src/utils/getGlobal.d.ts +2 -0
  57. package/dist/src/utils/getGlobal.d.ts.map +1 -0
  58. package/dist/src/utils/getGlobal.js +7 -0
  59. package/dist/src/utils/getGlobal.js.map +1 -0
  60. package/dist/src/utils/isString.d.ts +2 -0
  61. package/dist/src/utils/isString.d.ts.map +1 -0
  62. package/dist/src/utils/isString.js +7 -0
  63. package/dist/src/utils/isString.js.map +1 -0
  64. package/dist/src/utils/reflect.d.ts +1 -0
  65. package/dist/src/utils/reflect.d.ts.map +1 -1
  66. package/dist/src/utils/reflect.js +12 -1
  67. package/dist/src/utils/reflect.js.map +1 -1
  68. package/dist/test/fixtures/LifecycleBoundGraph.js +3 -3
  69. package/dist/test/fixtures/LifecycleBoundGraph.js.map +1 -1
  70. package/dist/transformers/babel-plugin-obsidian/helpers/index.d.ts +3 -2
  71. package/dist/transformers/babel-plugin-obsidian/helpers/index.d.ts.map +1 -1
  72. package/dist/transformers/babel-plugin-obsidian/helpers/index.js +11 -4
  73. package/dist/transformers/babel-plugin-obsidian/helpers/index.js.map +1 -1
  74. package/dist/transformers/babel-plugin-obsidian/index.d.ts.map +1 -1
  75. package/dist/transformers/babel-plugin-obsidian/index.js +5 -0
  76. package/dist/transformers/babel-plugin-obsidian/index.js.map +1 -1
  77. package/dist/transformers/babel-plugin-obsidian/unmagler/property.d.ts.map +1 -1
  78. package/dist/transformers/babel-plugin-obsidian/unmagler/property.js +2 -2
  79. package/dist/transformers/babel-plugin-obsidian/unmagler/property.js.map +1 -1
  80. package/eslint.config.mjs +2 -1
  81. package/package.json +13 -10
  82. package/src/Obsidian.ts +10 -6
  83. package/src/decorators/LifecycleBound.ts +1 -1
  84. package/src/decorators/inject/Inject.ts +1 -4
  85. package/src/decorators/inject/Injectable.ts +2 -2
  86. package/src/graph/ObjectGraph.ts +7 -3
  87. package/src/graph/ServiceLocatorFactory.ts +2 -2
  88. package/src/graph/registry/GraphRegistry.ts +120 -9
  89. package/src/injectors/class/ClassInjector.ts +6 -14
  90. package/src/injectors/class/LateInjector.ts +10 -3
  91. package/src/injectors/components/ComponentInjector.tsx +5 -5
  92. package/src/injectors/components/InjectComponent.ts +6 -5
  93. package/src/injectors/components/useGraph.ts +2 -2
  94. package/src/injectors/components/useInjectionToken.ts +5 -2
  95. package/src/injectors/hooks/HookInjector.ts +2 -2
  96. package/src/injectors/hooks/InjectHook.ts +4 -4
  97. package/src/utils/getGlobal.ts +3 -0
  98. package/src/utils/isString.ts +3 -0
  99. package/src/utils/reflect.ts +14 -1
  100. package/transformers/babel-plugin-obsidian/helpers/index.ts +11 -3
  101. package/transformers/babel-plugin-obsidian/index.ts +5 -0
  102. package/transformers/babel-plugin-obsidian/unmagler/property.ts +3 -3
  103. package/dist/src/decorators/Memoize.d.ts +0 -2
  104. package/dist/src/decorators/Memoize.d.ts.map +0 -1
  105. package/dist/src/decorators/Memoize.js +0 -12
  106. package/dist/src/decorators/Memoize.js.map +0 -1
  107. package/src/decorators/Memoize.ts +0 -11
@@ -1,10 +1,7 @@
1
1
  import InjectionMetadata from '../../injectors/class/InjectionMetadata';
2
2
 
3
3
  export function inject<This, Return>(name?: string) {
4
- return (
5
- _target: undefined,
6
- context: ClassFieldDecoratorContext<This, Return>,
7
- ) => {
4
+ return (_target: undefined, context: ClassFieldDecoratorContext<This, Return>) => {
8
5
  context.addInitializer(function (this: This) {
9
6
  const metadata = new InjectionMetadata();
10
7
  metadata.savePropertyMetadata((this as object).constructor, name!);
@@ -3,6 +3,6 @@ import { Graph } from '../../graph/Graph';
3
3
  import graphRegistry from '../../graph/registry/GraphRegistry';
4
4
  import ClassInjector from '../../injectors/class/ClassInjector';
5
5
 
6
- export function injectable(Graph: Constructable<Graph>): any {
7
- return new ClassInjector(graphRegistry).inject(Graph);
6
+ export function injectable(keyOrGraph: string | Constructable<Graph>): any {
7
+ return new ClassInjector(graphRegistry).inject(keyOrGraph);
8
8
  }
@@ -1,17 +1,21 @@
1
1
  import { uniqueId } from '../utils/uniqueId';
2
- import memoize from '../decorators/Memoize';
3
2
  import { bindProviders } from './ProviderBinder';
4
3
  import { Graph } from './Graph';
5
4
  import PropertyRetriever from './PropertyRetriever';
6
5
  import { Constructable } from '../types';
7
6
  import { CircularDependenciesDetector } from './CircularDependenciesDetector';
7
+ import { defineMetadata, getMetadata, hasMetadata } from '../utils/reflect';
8
8
 
9
9
  export abstract class ObjectGraph<T = unknown> implements Graph {
10
10
  private propertyRetriever = new PropertyRetriever(this);
11
11
 
12
- @memoize
13
12
  get name(): string {
14
- return uniqueId(this.constructor.name);
13
+ if (hasMetadata(this.constructor, 'memoizedName')) {
14
+ return getMetadata(this.constructor, 'memoizedName');
15
+ }
16
+ const name = uniqueId(this.constructor.name);
17
+ defineMetadata(this.constructor, 'memoizedName', name);
18
+ return name;
15
19
  }
16
20
 
17
21
  constructor(protected _props?: T) {
@@ -3,8 +3,8 @@ import { Constructable, ServiceLocator as ServiceLocatorType } from '../types';
3
3
  import graphRegistry from './registry/GraphRegistry';
4
4
 
5
5
  export default class ServiceLocatorFactory {
6
- static fromGraph<T extends ObjectGraph<P>, P = any>(Graph: Constructable<T>, props?: P) {
7
- const resolved = graphRegistry.resolve(Graph, 'serviceLocator', props);
6
+ static fromGraph<T extends ObjectGraph<P>, P = any>(keyOrGraph: string | Constructable<T>, props?: P) {
7
+ const resolved = graphRegistry.resolve(keyOrGraph, 'serviceLocator', props);
8
8
  const wrapped = new Proxy(resolved, {
9
9
  get(_target: any, property: string, receiver: any) {
10
10
  return () => resolved.retrieve(property, receiver);
@@ -4,6 +4,9 @@ import { Middleware } from './Middleware';
4
4
  import GraphMiddlewareChain from './GraphMiddlewareChain';
5
5
  import { ObtainLifecycleBoundGraphException } from './ObtainLifecycleBoundGraphException';
6
6
  import { getMetadata } from '../../utils/reflect';
7
+ import { getGlobal } from '../../utils/getGlobal';
8
+ import { isString } from '../../utils/isString';
9
+ import referenceCounter from '../../ReferenceCounter';
7
10
 
8
11
  export class GraphRegistry {
9
12
  private readonly constructorToInstance = new Map<Constructable<Graph>, Set<Graph>>();
@@ -13,16 +16,30 @@ export class GraphRegistry {
13
16
  private readonly nameToInstance = new Map<string, Graph>();
14
17
  private readonly graphToSubgraphs = new Map<Constructable<Graph>, Set<Constructable<Graph>>>();
15
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>>();
16
22
 
17
23
  register(constructor: Constructable<Graph>, subgraphs: Constructable<Graph>[] = []) {
18
24
  this.graphToSubgraphs.set(constructor, new Set(subgraphs));
19
25
  }
20
26
 
21
- 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;
22
35
  if (this.instanceToConstructor.get(graph)) return;
23
36
  this.set(graph.constructor as any, graph);
24
37
  }
25
38
 
39
+ public isInstantiated(G: Constructable<Graph>): boolean {
40
+ return (this.constructorToInstance.get(G)?.size ?? 0) > 0;
41
+ }
42
+
26
43
  getSubgraphs(graph: Graph): Graph[] {
27
44
  const Graph = this.instanceToConstructor.get(graph)!;
28
45
  const subgraphs = this.graphToSubgraphs.get(Graph) ?? new Set();
@@ -34,24 +51,85 @@ export class GraphRegistry {
34
51
  }
35
52
 
36
53
  resolve<T extends Graph>(
37
- Graph: Constructable<T>,
54
+ keyOrGraph: string | Constructable<T>,
38
55
  source: 'lifecycleOwner' | 'classInjection' | 'serviceLocator' = 'lifecycleOwner',
39
56
  props: any = undefined,
40
57
  injectionToken?: string,
41
58
  ): T {
59
+ const Graph = isString(keyOrGraph) ?
60
+ this.getGraphConstructorByKey<T>(keyOrGraph) :
61
+ keyOrGraph;
42
62
  if ((this.isSingleton(Graph) || this.isBoundToReactLifecycle(Graph)) && this.has(Graph, injectionToken)) {
43
- return this.isComponentScopedLifecycleBound(Graph)
44
- ? this.getByInjectionToken(Graph, injectionToken)
45
- : this.getFirst(Graph);
63
+ return this.isComponentScopedLifecycleBound(Graph) ?
64
+ this.getByInjectionToken(Graph, injectionToken) :
65
+ this.getFirst(Graph);
46
66
  }
47
67
  if (this.isBoundToReactLifecycle(Graph) && source !== 'lifecycleOwner') {
48
68
  throw new ObtainLifecycleBoundGraphException(Graph);
49
69
  }
50
70
  const graph = this.graphMiddlewares.resolve(Graph, props);
51
71
  this.set(Graph, graph, injectionToken);
72
+ this.instantiateCustomScopedSubgraphs(graph, props);
52
73
  return graph as T;
53
74
  }
54
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
+
55
133
  private has(Graph: Constructable<Graph>, injectionToken?: string): boolean {
56
134
  const instances = this.constructorToInstance.get(Graph);
57
135
  if (!instances) return false;
@@ -101,6 +179,11 @@ export class GraphRegistry {
101
179
  return getMetadata(Graph, 'lifecycleScope') === 'component';
102
180
  }
103
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
+
104
187
  clearGraphAfterItWasMockedInTests(graphName: string) {
105
188
  const graphNames = this.nameToInstance.keys();
106
189
  for (const name of graphNames) {
@@ -118,6 +201,7 @@ export class GraphRegistry {
118
201
  this.injectionTokenToInstance.delete(token);
119
202
  this.instanceToInjectionToken.delete(graph);
120
203
  }
204
+ this.invokeOnClearListeners(graph);
121
205
  }
122
206
  }
123
207
  }
@@ -133,6 +217,32 @@ export class GraphRegistry {
133
217
  this.injectionTokenToInstance.delete(token);
134
218
  this.instanceToInjectionToken.delete(graph);
135
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
+ });
136
246
  }
137
247
 
138
248
  addGraphMiddleware(middleware: Middleware<Graph>) {
@@ -149,10 +259,11 @@ export class GraphRegistry {
149
259
  this.nameToInstance.clear();
150
260
  this.injectionTokenToInstance.clear();
151
261
  this.instanceToInjectionToken.clear();
262
+ this.keyToGenerator.clear();
263
+ this.keyToGraph.clear();
152
264
  }
153
265
  }
154
266
 
155
- // @ts-expect-error - workaround an issue in jest tests where the registry was created multiple times
156
- global.graphRegistry = global.graphRegistry || new GraphRegistry();
157
- // @ts-expect-error - see above
158
- 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 getGlobal(): any {
2
+ return typeof window !== 'undefined' ? window : global;
3
+ }
@@ -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
+ }
@@ -9,6 +9,7 @@ import {
9
9
  ObjectPattern,
10
10
  TSParameterProperty,
11
11
  Node,
12
+ type StringLiteral,
12
13
  } from '@babel/types';
13
14
 
14
15
  const never = '';
@@ -16,7 +17,7 @@ const never = '';
16
17
  export type AcceptedNodeType = Identifier | TSParameterProperty | ClassProperty;
17
18
 
18
19
  export function providerIsNotNamed(decorator: Decorator): boolean {
19
- const argument = getDecoratorArgument(decorator);
20
+ const argument = getDecoratorObjectArgument(decorator);
20
21
  if (t.isObjectExpression(argument)) {
21
22
  return argument.properties.find((p) => {
22
23
  if (t.isObjectProperty(p)) {
@@ -29,7 +30,7 @@ export function providerIsNotNamed(decorator: Decorator): boolean {
29
30
  }
30
31
 
31
32
  export function addNameToProviderArguments(node: ClassMethod, decorator: Decorator) {
32
- const argument = getDecoratorArgument(decorator) ?? t.objectExpression([]);
33
+ const argument = getDecoratorObjectArgument(decorator) ?? t.objectExpression([]);
33
34
  argument.properties.push(t.objectProperty(
34
35
  t.identifier('name'),
35
36
  t.stringLiteral(getMethodName(node)),
@@ -37,13 +38,20 @@ export function addNameToProviderArguments(node: ClassMethod, decorator: Decorat
37
38
  (decorator.expression as CallExpression).arguments = [argument];
38
39
  }
39
40
 
40
- export function getDecoratorArgument(decorator: Decorator): ObjectExpression | undefined {
41
+ export function getDecoratorObjectArgument(decorator: Decorator): ObjectExpression | undefined {
41
42
  if (t.isCallExpression(decorator.expression)) {
42
43
  return decorator.expression.arguments.find(a => t.isObjectExpression(a)) as ObjectExpression;
43
44
  }
44
45
  return undefined;
45
46
  }
46
47
 
48
+ export function getDecoratorStringArgument(decorator: Decorator): StringLiteral | undefined {
49
+ if (t.isCallExpression(decorator.expression)) {
50
+ return decorator.expression.arguments.find(a => t.isStringLiteral(a)) as StringLiteral;
51
+ }
52
+ return undefined;
53
+ }
54
+
47
55
  export function getMethodName(node: ClassMethod): string {
48
56
  if (t.isIdentifier(node.key)) return node.key.name;
49
57
  throw new Error(`Tried to get class name but encountered unexpected key of type: ${node.key.type}`);
@@ -20,22 +20,27 @@ const internalVisitor = {
20
20
  ClassMethod: {
21
21
  enter({ node }: NodePath<ClassMethod>) {
22
22
  unmagler.saveClassMethod('provides', node);
23
+ unmagler.saveClassMethod('Provides', node);
23
24
  },
24
25
  },
25
26
  ClassProperty: {
26
27
  enter({ node }: NodePath<ClassProperty>) {
27
28
  unmagler.saveClassProperty('inject', node);
28
29
  unmagler.saveClassProperty('lateInject', node);
30
+ unmagler.saveClassProperty('Inject', node);
31
+ unmagler.saveClassProperty('LateInject', node);
29
32
  },
30
33
  },
31
34
  Identifier: {
32
35
  enter({ node }: NodePath<Identifier>) {
33
36
  unmagler.saveIdentifier('inject', node);
37
+ unmagler.saveIdentifier('Inject', node);
34
38
  },
35
39
  },
36
40
  TSParameterProperty: {
37
41
  enter({ node }: NodePath<TSParameterProperty>) {
38
42
  unmagler.saveTSParameterProperty('inject', node);
43
+ unmagler.saveTSParameterProperty('Inject', node);
39
44
  },
40
45
  },
41
46
  };
@@ -3,19 +3,19 @@ import {
3
3
  getDecoratorName,
4
4
  getDecoratorByName,
5
5
  passParamNameAsInjectArgument,
6
- getDecoratorArgument,
7
6
  AcceptedNodeType,
7
+ getDecoratorStringArgument,
8
8
  } from '../helpers';
9
9
 
10
10
  function savePropertyName(name: string, node: AcceptedNodeType) {
11
11
  const decorator = getDecoratorByName(node.decorators, name);
12
- if (getDecoratorName(decorator) === name && injectIsNotNamed(decorator!)) {
12
+ if ((getDecoratorName(decorator) === name) && injectIsNotNamed(decorator!)) {
13
13
  passParamNameAsInjectArgument(node, decorator!);
14
14
  }
15
15
  }
16
16
 
17
17
  function injectIsNotNamed(decorator: Decorator): boolean {
18
- return getDecoratorArgument(decorator) === undefined;
18
+ return getDecoratorStringArgument(decorator) === undefined;
19
19
  }
20
20
 
21
21
  export default savePropertyName;
@@ -1,2 +0,0 @@
1
- export default function memoize<This, Return>(target: (this: This) => Return, context: ClassGetterDecoratorContext): (this: This) => Return;
2
- //# sourceMappingURL=Memoize.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Memoize.d.ts","sourceRoot":"","sources":["../../../src/decorators/Memoize.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,IAAI,EAAE,MAAM,EAC1C,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,EAC9B,OAAO,EAAE,2BAA2B,UAEZ,IAAI,KAAG,MAAM,CAMtC"}
@@ -1,12 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = memoize;
4
- function memoize(target, context) {
5
- function memoizer() {
6
- const value = target.call(this);
7
- Object.defineProperty(this, context.name, { value, enumerable: true });
8
- return value;
9
- }
10
- return memoizer;
11
- }
12
- //# sourceMappingURL=Memoize.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"Memoize.js","sourceRoot":"","sources":["../../../src/decorators/Memoize.ts"],"names":[],"mappings":";;AAAA,0BAUC;AAVD,SAAwB,OAAO,CAC7B,MAA8B,EAC9B,OAAoC;IAEpC,SAAS,QAAQ;QACf,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QACvE,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -1,11 +0,0 @@
1
- export default function memoize<This, Return>(
2
- target: (this: This) => Return,
3
- context: ClassGetterDecoratorContext,
4
- ) {
5
- function memoizer(this: This): Return {
6
- const value = target.call(this);
7
- Object.defineProperty(this, context.name, { value, enumerable: true });
8
- return value;
9
- }
10
- return memoizer;
11
- }