-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Emanuel Hein
committed
Sep 1, 2022
1 parent
975812c
commit 21bce84
Showing
9 changed files
with
503 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"name": "Test", | ||
"request": "launch", | ||
"runtimeArgs": [ | ||
"run-script", | ||
"test" | ||
], | ||
"runtimeExecutable": "npm", | ||
"skipFiles": [ | ||
"<node_internals>/**" | ||
], | ||
"type": "node" | ||
}, | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import { Initiator } from './private-base'; | ||
import { InjectFactory, InjectToken } from './public-base'; | ||
import { TypeInjector } from './type-injector'; | ||
|
||
export class ChildInjector extends TypeInjector { | ||
static withIdent(ident: symbol) { | ||
return { | ||
from(parent: TypeInjector): TypeInjector { | ||
return new ChildInjector(ident, parent); | ||
} | ||
}; | ||
} | ||
|
||
private _ownInstances = [] as any[]; | ||
|
||
private constructor( | ||
public readonly ident: symbol, | ||
private _parent: TypeInjector, | ||
) { | ||
super(); | ||
} | ||
|
||
provideValue<T>(token: InjectToken<T>, value: T): TypeInjector { | ||
this._ownInstances.push(value); | ||
return super.provideValue(token, value); | ||
} | ||
|
||
private _createInOwnScope<T>(token: InjectToken<T>, initiator: Initiator, factory: InjectFactory<T>): InstanceWithSource<T> { | ||
this._markAsInCreation(token, initiator); | ||
const args = factory.deps.map((dep) => this._get(dep, token)); | ||
const created = factory.create(...args); | ||
this._ownInstances.push(created); | ||
this._instances.set(token, created); | ||
this._finishedCreation(token); | ||
return { | ||
instance: created, | ||
fromParentScope: false, | ||
}; | ||
} | ||
|
||
/** | ||
* Do not create an own instance but ask parent scope for an instance. | ||
* | ||
* After checking that no own instance is needed this method can get called to | ||
* query the parent scope to resolve the token. This might create a new instance | ||
* in the (parent) parent if it doesn't exist yet. | ||
* | ||
* This instance is linked into _instances to prevent further calls with the same | ||
* token to repeat all dependency checks. | ||
* | ||
* @param token | ||
* @returns instance from parent + flag that it is from parent | ||
*/ | ||
private _useInstanceFromParentScope<T>(token: InjectToken<T>): InstanceWithSource<T> { | ||
const refFromParent = this._parent.get(token); | ||
this._instances.set(token, refFromParent); | ||
return { | ||
instance: refFromParent, | ||
fromParentScope: true, | ||
} | ||
} | ||
|
||
/** | ||
* Checks if there are own/overridden dependencies. | ||
* | ||
* Therefore this function will query for all dependencies which | ||
* might trigger lazy creation. But all of them are cached in the | ||
* appropriate scope and needed for the creation anyway so there's | ||
* not much wasted computing time (only a duplicate lookup). | ||
* | ||
* Even if it does not create the requested value it's important | ||
* to add it to the values in creation to detect dependency cycles. | ||
* | ||
* @param token | ||
* @param initiator | ||
*/ | ||
private _hasOwnDependencies(token: InjectToken<unknown>, initiator: Initiator, factory: InjectFactory<unknown>): boolean { | ||
this._markAsInCreation(token, initiator); | ||
const hasOwnDependencies = factory.deps.some((dep) => { | ||
const instance = this._get(dep, token); | ||
return this._ownInstances.includes(instance); | ||
}); | ||
this._finishedCreation(token); | ||
return hasOwnDependencies; | ||
} | ||
|
||
private _createWithSource<T>(token: InjectToken<T>, initiator: Initiator): InstanceWithSource<T> { | ||
const providedFactory = this._factories.get(token); | ||
if (providedFactory) { | ||
return this._createInOwnScope<T>(token, initiator, providedFactory); | ||
} | ||
|
||
const parentFactory = this._parent.getFactory<T>(token); | ||
if (this._hasOwnDependencies(token, initiator, parentFactory)) { | ||
return this._createInOwnScope(token, initiator, parentFactory); | ||
} else { | ||
return this._useInstanceFromParentScope(token); | ||
} | ||
} | ||
|
||
protected _create<T>(token: InjectToken<T>, initiator: Initiator): T { | ||
return this._createWithSource(token, initiator).instance; | ||
} | ||
} | ||
|
||
interface InstanceWithSource<T> { | ||
instance: T, | ||
fromParentScope: boolean, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,4 @@ | ||
export * from './child-injector'; | ||
export * from './public-base'; | ||
export * from './logger'; | ||
export * from './type-injector'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { InjectableClass, InjectToken } from './public-base'; | ||
|
||
export type Initiator = InjectToken<unknown> | typeof startOfCycle; | ||
|
||
export function hasInjectConfig<T>(token: unknown): token is InjectableClass<T> { | ||
return !!(token as Partial<InjectableClass<T>>).injectConfig; | ||
} | ||
|
||
export const startOfCycle = 'startOfCycle' as const; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/** | ||
* Every class can get an InjectableClass by adding a static injectConfig property. | ||
* | ||
* For classes that can get instantiated without constructor arguments it | ||
* is *not* required to add an injectConfig. An injectConfig is required to | ||
* tell the TypedInjector to use a constructor with arguments to create a | ||
* class instance. | ||
* | ||
* @see {@link InjectConfig InjectConfig} | ||
*/ | ||
export type InjectableClass<T> = (new (..._args: any[]) => T) & { | ||
injectConfig: InjectConfig; | ||
} | ||
|
||
export interface InjectConfig { | ||
/** | ||
* Inject tokens for all arguments required to create an injectable value. | ||
* | ||
* - For classes the dependencies have to match the consturctor parameters | ||
* - For all other functions (like factories) the tokens have to match the parameters | ||
* | ||
* In both cases "match" means that the inject tokens return the right types of | ||
* all parameters in the same order as they are needed for the function/constructor call. | ||
* | ||
* The dependencies of an inject token are not created before the inject token | ||
* itself gets created. | ||
*/ | ||
deps: InjectToken<unknown>[]; | ||
} | ||
|
||
export interface InjectFactory<T> extends InjectConfig { | ||
create: (...args: any[]) => T, | ||
} | ||
|
||
export type ConstructorWithoutArguments<T> = new () => T; | ||
|
||
export type InjectToken<T> = ConstructorWithoutArguments<T> | InjectableClass<T> | symbol; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.