1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88import { createContext, useContext, ReactNode, Context } from "react";
import { IPropertyTransformerFactory, IReadableObj, Readable } from "./IPropertyTransformer";
// =============================================================================
// Service Key
// =============================================================================
export interface ServiceKey<T> {
readonly _brand: T;
readonly id: symbol;
readonly name: string;
}
export function createServiceKey<T>(name: string): ServiceKey<T> {
return { _brand: undefined as T, id: Symbol(name), name };
}
// =============================================================================
// DI Container
// =============================================================================
export class DIContainer {
private readonly _services = new Map<symbol, unknown>();
private readonly _parent: DIContainer | null;
constructor(parent: DIContainer | null = null) {
this._parent = parent;
}
register<T>(key: ServiceKey<T>, service: T): this {
this._services.set(key.id, service);
return this;
}
get<T>(key: ServiceKey<T>): T {
const service = this._services.get(key.id);
if (service !== undefined) return service as T;
if (this._parent) return this._parent.get(key);
throw new Error(`Service "${key.name}" not registered`);
}
has<T>(key: ServiceKey<T>): boolean {
return this._services.has(key.id) || (this._parent?.has(key) ?? false);
}
createChild(): DIContainer {
return new DIContainer(this);
}
}
// =============================================================================
// React Context
// =============================================================================
export const DIContext: Context<DIContainer | null> = createContext<DIContainer | null>(null);
export function DIProvider({ container, children }: { container: DIContainer; children: ReactNode }): ReactNode {
return <DIContext.Provider value={container}>{children}</DIContext.Provider>;
}
export function useDIContainer(): DIContainer {
const container = useContext(DIContext);
if (!container) throw new Error("DIProvider not found");
return container;
}
// =============================================================================
// inject() - Property Transformer for DI
// =============================================================================
class InjectTransformerFactory<T> implements IPropertyTransformerFactory<never, T> {
readonly _requiredContext = DIContext as Context<unknown>;
constructor(public readonly serviceKey: ServiceKey<T>) { }
create(_readable: Readable<never>, contextValue: unknown): IReadableObj<T> {
const container = contextValue as DIContainer | null;
if (!container) throw new Error(`inject(${this.serviceKey.name}): DIProvider not found`);
const service = container.get(this.serviceKey);
return { read: () => service };
}
}
/** Inject a service from DIContainer into a ViewModel property */
export function inject<T>(key: ServiceKey<T>): IPropertyTransformerFactory<never, T> & { _requiredContext: Context<unknown> } {
return new InjectTransformerFactory(key);
}