Skip to content

Commit 6dba27e

Browse files
committed
fix: add type safety on dependency array and object
1 parent ae4c757 commit 6dba27e

File tree

2 files changed

+185
-114
lines changed

2 files changed

+185
-114
lines changed

specs/registry.spec.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {createContainer, TypedContainer} from '../src';
22
import {
33
FakeLogger,
4+
ServiceClass,
45
ServiceClassNoDeps,
56
ServiceClassWithDeps,
67
ServiceClassWithObjectDeps,
@@ -152,10 +153,70 @@ describe('Registry', () => {
152153
.toThrowError('No binding found for key: USER_SERVICE');
153154
});
154155
});
155-
});
156156

157+
describe('When using a typed container', () => {
158+
describe('When using string keys', () => {
159+
it('should not allow dependency names outside the registry', () => {
160+
// Arrange
161+
container.bind('DEP1').toValue('dep1');
162+
container.bind('DEP2').toValue(1);
163+
164+
// Act & Assert
165+
// @ts-expect-error - dependency name is not part of the registry
166+
container.bind('CLASS_WITH_DEPENDENCIES').toClass(ServiceClassWithDeps, ['UNKNOWN_DEP']);
167+
});
168+
169+
it('should not allow named dependencies outside the registry', () => {
170+
// Arrange
171+
container.bind('DEP1').toValue('dep1');
172+
container.bind('DEP2').toValue(1);
157173

174+
// Act & Assert
175+
// @ts-expect-error - dependency name is not part of the registry
176+
container.bind('CLASS_WITH_DEPENDENCIES').toClass(ServiceClassWithObjectDeps, {
177+
dep1: 'DEP1',
178+
dep2: 'UNKNOWN_DEP'
179+
});
180+
});
181+
});
158182

183+
describe('When using symbol keys', () => {
184+
const DEP1 = Symbol('DEP1');
185+
const DEP2 = Symbol('DEP2');
186+
const CLASS_WITH_DEPENDENCIES = Symbol('CLASS_WITH_DEPENDENCIES');
187+
const UNKNOWN_DEP = Symbol('UNKNOWN_DEP');
159188

189+
type SymbolRegistry = {
190+
[DEP1]: string;
191+
[DEP2]: number;
192+
[CLASS_WITH_DEPENDENCIES]: ServiceClass;
193+
}
160194

195+
it('should not allow dependency symbols outside the registry', () => {
196+
// Arrange
197+
const symbolContainer = createContainer<SymbolRegistry>();
198+
symbolContainer.bind(DEP1).toValue('dep1');
199+
symbolContainer.bind(DEP2).toValue(1);
200+
201+
// Act & Assert
202+
// @ts-expect-error - dependency symbol is not part of the registry
203+
symbolContainer.bind(CLASS_WITH_DEPENDENCIES).toClass(ServiceClassWithDeps, [DEP1, UNKNOWN_DEP]);
204+
});
205+
206+
it('should not allow named dependency symbols outside the registry', () => {
207+
// Arrange
208+
const symbolContainer = createContainer<SymbolRegistry>();
209+
symbolContainer.bind(DEP1).toValue('dep1');
210+
symbolContainer.bind(DEP2).toValue(1);
211+
212+
// Act & Assert
213+
// @ts-expect-error - dependency symbol is not part of the registry
214+
symbolContainer.bind(CLASS_WITH_DEPENDENCIES).toClass(ServiceClassWithObjectDeps, {
215+
dep1: DEP1,
216+
dep2: UNKNOWN_DEP
217+
});
218+
});
219+
});
220+
});
221+
});
161222

src/types.ts

Lines changed: 123 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -3,161 +3,171 @@
33
export type ModuleKey = symbol | string;
44

55
export interface DependencyObject {
6-
[key: string]: DependencyKey;
6+
[key: string]: DependencyKey;
77
}
88

99
export type DependencyArray = DependencyKey[];
1010

1111
export type Scope = 'singleton' | 'transient' | 'scoped';
1212

1313
export interface DefaultRegistry {
14-
[key: string]: unknown;
14+
[key: string]: unknown;
1515
}
1616

1717
type RegistryKey<TRegistry> = Extract<keyof TRegistry, DependencyKey>;
1818

19+
type RegistryDependencyArray<TRegistry> = readonly RegistryKey<TRegistry>[];
20+
21+
type RegistryDependencyObject<TRegistry> = {
22+
[key: string]: RegistryKey<TRegistry>;
23+
};
24+
25+
type RegistryDependencies<TRegistry> = RegistryDependencyArray<TRegistry> | RegistryDependencyObject<TRegistry>;
26+
1927
type IncompatibleOverride<K extends PropertyKey, Expected, Provided> = {
20-
__error: 'Incompatible override type for registry key';
21-
key: K;
22-
expected: Expected;
23-
provided: Provided;
28+
__error: 'Incompatible override type for registry key';
29+
key: K;
30+
expected: Expected;
31+
provided: Provided;
2432
};
2533

2634
type CompatibleKey<TRegistry, TOverride> = [TOverride] extends [never]
27-
? RegistryKey<TRegistry>
28-
: {
29-
[K in RegistryKey<TRegistry>]: TOverride extends TRegistry[K]
30-
? K
31-
: IncompatibleOverride<K, TRegistry[K], TOverride>
32-
}[RegistryKey<TRegistry>];
35+
? RegistryKey<TRegistry>
36+
: {
37+
[K in RegistryKey<TRegistry>]: TOverride extends TRegistry[K]
38+
? K
39+
: IncompatibleOverride<K, TRegistry[K], TOverride>
40+
}[RegistryKey<TRegistry>];
3341

3442

3543
interface Bindable {
36-
bind(key: DependencyKey): {
37-
toValue: (value: unknown) => void;
38-
toFunction: (fn: CallableFunction) => void;
39-
toHigherOrderFunction: (
40-
fn: CallableFunction,
41-
dependencies?: DependencyArray | DependencyObject,
42-
scope?: Scope
43-
) => void;
44-
toCurry: (
45-
fn: CallableFunction,
46-
dependencies?: DependencyArray | DependencyObject,
47-
scope?: Scope
48-
) => void;
49-
toFactory: (factory: CallableFunction, scope?: Scope) => void;
50-
toClass: <C>(
51-
constructor: new (...args: any[]) => C,
52-
dependencies?: DependencyArray | DependencyObject,
53-
scope?: Scope
54-
) => void;
55-
};
44+
bind(key: DependencyKey): {
45+
toValue: (value: unknown) => void;
46+
toFunction: (fn: CallableFunction) => void;
47+
toHigherOrderFunction: (
48+
fn: CallableFunction,
49+
dependencies?: DependencyArray | DependencyObject,
50+
scope?: Scope
51+
) => void;
52+
toCurry: (
53+
fn: CallableFunction,
54+
dependencies?: DependencyArray | DependencyObject,
55+
scope?: Scope
56+
) => void;
57+
toFactory: (factory: CallableFunction, scope?: Scope) => void;
58+
toClass: <C>(
59+
constructor: new (...args: any[]) => C,
60+
dependencies?: DependencyArray | DependencyObject,
61+
scope?: Scope
62+
) => void;
63+
};
5664
}
5765

5866
interface TypedBindable<TRegistry> {
59-
bind<K extends RegistryKey<TRegistry>>(key: K): {
60-
toValue: (value: TRegistry[K]) => void;
61-
toFunction: (fn: CallableFunction) => void;
62-
toHigherOrderFunction: (
63-
fn: CallableFunction,
64-
dependencies?: DependencyArray | DependencyObject,
65-
scope?: Scope
66-
) => void;
67-
toCurry: (
68-
fn: CallableFunction,
69-
dependencies?: DependencyArray | DependencyObject,
70-
scope?: Scope
71-
) => void;
72-
toFactory: (factory: CallableFunction, scope?: Scope) => void;
73-
toClass: <C>(
74-
constructor: new (...args: any[]) => C,
75-
dependencies?: DependencyArray | DependencyObject,
76-
scope?: Scope
77-
) => void;
78-
};
79-
bind(key: DependencyKey): {
80-
toValue: (value: unknown) => void;
81-
toFunction: (fn: CallableFunction) => void;
82-
toHigherOrderFunction: (
83-
fn: CallableFunction,
84-
dependencies?: DependencyArray | DependencyObject,
85-
scope?: Scope
86-
) => void;
87-
toCurry: (
88-
fn: CallableFunction,
89-
dependencies?: DependencyArray | DependencyObject,
90-
scope?: Scope
91-
) => void;
92-
toFactory: (factory: CallableFunction, scope?: Scope) => void;
93-
toClass: <C>(
94-
constructor: new (...args: any[]) => C,
95-
dependencies?: DependencyArray | DependencyObject,
96-
scope?: Scope
97-
) => void;
98-
};
67+
bind<K extends RegistryKey<TRegistry>>(key: K): {
68+
toValue: (value: TRegistry[K]) => void;
69+
toFunction: (fn: CallableFunction) => void;
70+
toHigherOrderFunction: (
71+
fn: CallableFunction,
72+
dependencies?: RegistryDependencies<TRegistry>,
73+
scope?: Scope
74+
) => void;
75+
toCurry: (
76+
fn: CallableFunction,
77+
dependencies?: RegistryDependencies<TRegistry>,
78+
scope?: Scope
79+
) => void;
80+
toFactory: (factory: CallableFunction, scope?: Scope) => void;
81+
toClass: <C>(
82+
constructor: new (...args: any[]) => C,
83+
dependencies?: RegistryDependencies<TRegistry>,
84+
scope?: Scope
85+
) => void;
86+
};
87+
88+
bind(key: DependencyKey): {
89+
toValue: (value: unknown) => void;
90+
toFunction: (fn: CallableFunction) => void;
91+
toHigherOrderFunction: (
92+
fn: CallableFunction,
93+
dependencies?: DependencyArray | DependencyObject,
94+
scope?: Scope
95+
) => void;
96+
toCurry: (
97+
fn: CallableFunction,
98+
dependencies?: DependencyArray | DependencyObject,
99+
scope?: Scope
100+
) => void;
101+
toFactory: (factory: CallableFunction, scope?: Scope) => void;
102+
toClass: <C>(
103+
constructor: new (...args: any[]) => C,
104+
dependencies?: DependencyArray | DependencyObject,
105+
scope?: Scope
106+
) => void;
107+
};
99108
}
100109

101110
export interface Container extends Bindable {
102-
load(moduleKey: ModuleKey, module: Module): void;
111+
load(moduleKey: ModuleKey, module: Module): void;
103112

104-
get<T>(key: DependencyKey): T;
113+
get<T>(key: DependencyKey): T;
105114

106-
unload(key: ModuleKey): void;
115+
unload(key: ModuleKey): void;
107116

108-
runInScope<T>(callback: () => T): T;
117+
runInScope<T>(callback: () => T): T;
109118
}
110119

111120
export interface TypedContainer<TRegistry> {
112-
bind<K extends keyof TRegistry & DependencyKey>(key: K): {
113-
toValue: (value: TRegistry[K]) => void;
114-
toFunction: (fn: TRegistry[K] extends CallableFunction ? TRegistry[K] : never) => void;
115-
toHigherOrderFunction: (
116-
fn: CallableFunction,
117-
dependencies?: DependencyArray | DependencyObject,
118-
scope?: Scope
119-
) => void;
120-
toCurry: (
121-
fn: CallableFunction,
122-
dependencies?: DependencyArray | DependencyObject,
123-
scope?: Scope
124-
) => void;
125-
toFactory: (factory: (resolve: (key: DependencyKey) => unknown) => TRegistry[K], scope?: Scope) => void;
126-
toClass: (
127-
constructor: new (...args: any[]) => TRegistry[K],
128-
dependencies?: DependencyArray | DependencyObject,
129-
scope?: Scope
130-
) => void;
131-
};
132-
133-
load<TModuleRegistry>(moduleKey: ModuleKey, module: TypedModule<TModuleRegistry>): void;
134-
load(moduleKey: ModuleKey, module: Module): void;
135-
136-
get<
137-
TOverride = never,
138-
K extends CompatibleKey<TRegistry, TOverride> = CompatibleKey<TRegistry, TOverride>
139-
>(key: K): [TOverride] extends [never] ? TRegistry[K & keyof TRegistry] : TOverride;
140-
141-
unload(key: ModuleKey): void;
142-
143-
runInScope<T>(callback: () => T): T;
121+
bind<K extends RegistryKey<TRegistry>>(key: K): {
122+
toValue: (value: TRegistry[K]) => void;
123+
toFunction: (fn: TRegistry[K] extends CallableFunction ? TRegistry[K] : never) => void;
124+
toHigherOrderFunction: (
125+
fn: CallableFunction,
126+
dependencies?: RegistryDependencies<TRegistry>,
127+
scope?: Scope
128+
) => void;
129+
toCurry: (
130+
fn: CallableFunction,
131+
dependencies?: RegistryDependencies<TRegistry>,
132+
scope?: Scope
133+
) => void;
134+
toFactory: (factory: (resolve: (key: DependencyKey) => unknown) => TRegistry[K], scope?: Scope) => void;
135+
toClass: (
136+
constructor: new (...args: any[]) => TRegistry[K],
137+
dependencies?: RegistryDependencies<TRegistry>,
138+
scope?: Scope
139+
) => void;
140+
};
141+
142+
load<TModuleRegistry>(moduleKey: ModuleKey, module: TypedModule<TModuleRegistry>): void;
143+
144+
load(moduleKey: ModuleKey, module: Module): void;
145+
146+
get<
147+
TOverride = never,
148+
K extends CompatibleKey<TRegistry, TOverride> = CompatibleKey<TRegistry, TOverride>
149+
>(key: K): [TOverride] extends [never] ? TRegistry[K & keyof TRegistry] : TOverride;
150+
151+
unload(key: ModuleKey): void;
152+
153+
runInScope<T>(callback: () => T): T;
144154
}
145155

146156
export interface Module extends Bindable {
147-
bindings: Map<DependencyKey, Binding>;
157+
bindings: Map<DependencyKey, Binding>;
148158
}
149159

150160
export interface TypedModule<TRegistry> extends TypedBindable<TRegistry> {
151-
bindings: Map<DependencyKey, Binding>;
161+
bindings: Map<DependencyKey, Binding>;
152162
}
153163

154164
export interface InjectionTokens {
155-
[key: string]: DependencyKey;
165+
[key: string]: DependencyKey;
156166
}
157167

158168
export type ResolveFunction = (dep: DependencyKey) => unknown;
159169

160170
export interface Binding {
161-
factory: (resolve: (key: DependencyKey) => unknown) => unknown;
162-
scope: Scope;
171+
factory: (resolve: (key: DependencyKey) => unknown) => unknown;
172+
scope: Scope;
163173
}

0 commit comments

Comments
 (0)