Skip to content

Commit ddd1bf9

Browse files
feat: implement @Inject() effect on Container#get
Signed-off-by: SalathielGenese <salathielgenese@gmail.com>
1 parent 5f5a327 commit ddd1bf9

2 files changed

Lines changed: 79 additions & 16 deletions

File tree

index.ts

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,12 @@ export function Inject<T extends ({ (): Class } | Class)[]>(...tokens: T) {
4444
export class InjectError extends ScopeError {}
4545
export class InjectCircularDependencyError extends InjectError {
4646
constructor(
47-
readonly dependency: Class,
47+
readonly chain: { index?: number; target: Class }[],
4848
readonly target: Class,
49-
readonly index: number,
5049
options?: ErrorOptions,
5150
) {
5251
super(
53-
`Circular dependencies detected while resolving dependency #${index} of #${target}`,
52+
`Circular dependencies detected while resolving dependencies of #${target}`,
5453
options,
5554
);
5655
}
@@ -169,27 +168,86 @@ export class Container {
169168
#get<T>(key: Class<Token<T>> | Class<T>, invocation: Container): T {
170169
if (this.#values.has(key)) return this.#values.get(key) as T;
171170

172-
const scope = this.#scopes.get(key);
173-
let scoped: Container | undefined = invocation;
174-
if (scope) {
175-
while (scoped && scope !== scoped.#scope) scoped = scoped.#parent;
176-
if (!scoped) throw new ContainerUndefinedScopeError(scope);
171+
if (this.#generators.has(key)) {
172+
const scope = this.#scopes.get(key);
173+
const _scoped = scope ? invocation.#scoped(scope) : this;
174+
return this.#generators.get(key)!() as T;
177175
}
178-
if (scoped && scoped.#values.has(key)) return scoped.#values.get(key) as T;
179-
180-
scoped = scope ? (scoped ?? this) : this;
181-
if (this.#generators.has(key)) return this.#generators.get(key)!() as T;
182176

183177
if (this.#resolvers.has(key)) {
184-
const value = this.#resolvers.get(key)!();
178+
const scope = this.#scopes.get(key);
179+
const scoped = scope ? invocation.#scoped(scope) : this;
180+
const value = this.#resolvers.get(key)!() as T;
181+
185182
scoped.#values.set(key, value);
186-
return scoped.#values.get(key) as T;
183+
return value;
187184
}
188185

189186
if (this.#parent) return this.#parent.#get(key, invocation);
187+
if (injects.has(key)) {
188+
// Detect & handle circular dependencies
189+
if (invocation.#creating.some(({ target }) => key === target)) {
190+
const chain = invocation.#creating.map((_) => ({
191+
..._,
192+
target: _.target ?? key,
193+
}));
194+
invocation.#creating.splice(0);
195+
throw new InjectCircularDependencyError(chain, key);
196+
}
197+
198+
let value: T;
199+
const args: unknown[] = [];
200+
const tokens = injects.get(key)!;
201+
202+
// Register the target (should be first) in #creating)
203+
if (!invocation.#creating.length)
204+
invocation.#creating.push({ target: key });
205+
else if (!invocation.#creating.at(-1)?.target)
206+
invocation.#creating.at(-1)!.target = key;
207+
208+
for (let i = 0, l = tokens.length; i < l; i++) {
209+
const token =
210+
false ===
211+
Reflect.getOwnPropertyDescriptor(tokens[i], "prototype")?.writable
212+
? (tokens[i] as Class)
213+
: (tokens[i] as () => Class)();
214+
// Register the target's dependency
215+
invocation.#creating.push({ index: i });
216+
217+
try {
218+
args.push(invocation.#get(token, invocation));
219+
invocation.#creating.pop();
220+
} catch (err) {
221+
invocation.#creating.splice(0);
222+
223+
if (err instanceof ContainerUndefinedKeyError)
224+
throw new InjectMissingDependencyError(token, key, i, {
225+
cause: err,
226+
});
227+
throw err;
228+
}
229+
}
230+
231+
try {
232+
value = Reflect.construct(key as Class, args) as T;
233+
} finally {
234+
invocation.#creating.pop();
235+
}
236+
this.#values.set(key, value);
237+
return value as T;
238+
}
190239

191240
throw new ContainerUndefinedKeyError(key);
192241
}
242+
readonly #creating = [] as { target?: Class; index?: number }[];
243+
244+
#scoped(scope: symbol): Container {
245+
let container = this as undefined | Container;
246+
while (container && scope !== container.#scope)
247+
container = container.#parent;
248+
if (!container) throw new ContainerUndefinedScopeError(scope);
249+
return container;
250+
}
193251

194252
/**
195253
* Register a value generator for the given {@linkcode key}.

index_test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,14 @@ describe("@Inject(...tokens)", () => {
140140
} catch (error) {
141141
err = error as InjectCircularDependencyError;
142142
}
143-
expect(err?.dependency).toBe(C);
144143
expect(err?.target).toBe(A);
145-
expect(err?.index).toBe(1);
144+
expect(err?.chain).toHaveLength(4);
145+
expect(err?.chain).toEqual([
146+
{ target: A },
147+
{ index: 1, target: B },
148+
{ index: 0, target: C },
149+
{ index: 0, target: A },
150+
]);
146151
});
147152
});
148153

0 commit comments

Comments
 (0)