Commit 22dd6fd6 authored by Alejandro Celaya's avatar Alejandro Celaya Committed by Alejandro Celaya

Migrate injector to TS

parent 9029313f
...@@ -45,7 +45,7 @@ function serializeError(err: Error | unknown): ErrorData { ...@@ -45,7 +45,7 @@ function serializeError(err: Error | unknown): ErrorData {
/** /**
* Convert error data serialized by {@link serializeError} back into an Error. * Convert error data serialized by {@link serializeError} back into an Error.
*/ */
function deserializeError(data: ErrorData): ErrorData { function deserializeError(data: ErrorData): Error {
const err = new Error(data.message); const err = new Error(data.message);
err.stack = data.stack; err.stack = data.stack;
return err; return err;
......
/** type ValueProvider = { value: unknown };
* @typedef Provider
* @prop {unknown} [value] - The value for the object
* @prop {Function} [class] - A class that should be instantiated to create the object
* @prop {Function} [factory] - Function that should be called to create the object
*/
/** type Constructible = {
* @param {Provider} provider new (...args: any[]): unknown;
*/ };
function isValidProvider(provider) {
type ClassProvider = { class: Constructible & { $inject?: string[] } };
type FactoryProvider = {
factory: ((...args: any[]) => unknown) & { $inject?: string[] };
};
type Provider = ValueProvider | ClassProvider | FactoryProvider;
function isValueProvider(provider: object): provider is ValueProvider {
return 'value' in provider;
}
function isClassProvider(provider: object): provider is ClassProvider {
return 'class' in provider && typeof provider.class === 'function';
}
function isFactoryProvider(provider: object): provider is FactoryProvider {
return 'factory' in provider && typeof provider.factory === 'function';
}
function isValidProvider(provider: unknown): provider is Provider {
if (typeof provider !== 'object' || provider === null) { if (typeof provider !== 'object' || provider === null) {
return false; return false;
} }
return ( return (
'value' in provider || isValueProvider(provider) ||
typeof provider.class === 'function' || isClassProvider(provider) ||
typeof provider.factory === 'function' isFactoryProvider(provider)
); );
} }
...@@ -41,25 +57,26 @@ function isValidProvider(provider) { ...@@ -41,25 +57,26 @@ function isValidProvider(provider) {
* use the `run` method. * use the `run` method.
*/ */
export class Injector { export class Injector {
constructor() { /** Map of name to object specifying how to create/provide that object. */
// Map of name to object specifying how to create/provide that object. private _providers: Map<string, Provider>;
this._providers = new Map(); /** Map of name to existing instance. */
private _instances: Map<string, unknown>;
/** Set of instances already being constructed. Used to detect circular dependencies. */
private _constructing: Set<string>;
// Map of name to existing instance. constructor() {
this._instances = new Map(); this._providers = new Map<string, Provider>();
this._instances = new Map<string, unknown>();
// Set of instances already being constructed. Used to detect circular this._constructing = new Set<string>();
// dependencies.
this._constructing = new Set();
} }
/** /**
* Construct or return the existing instance of an object with a given `name` * Construct or return the existing instance of an object with a given `name`
* *
* @param {string} name - Name of object to construct * @param name - Name of object to construct
* @return {unknown} - The constructed object * @return The constructed object
*/ */
get(name) { get(name: string): unknown {
if (this._instances.has(name)) { if (this._instances.has(name)) {
return this._instances.get(name); return this._instances.get(name);
} }
...@@ -70,7 +87,7 @@ export class Injector { ...@@ -70,7 +87,7 @@ export class Injector {
throw new Error(`"${name}" is not registered`); throw new Error(`"${name}" is not registered`);
} }
if ('value' in provider) { if (isValueProvider(provider)) {
this._instances.set(name, provider.value); this._instances.set(name, provider.value);
return provider.value; return provider.value;
} }
...@@ -85,8 +102,8 @@ export class Injector { ...@@ -85,8 +102,8 @@ export class Injector {
try { try {
const resolvedDependencies = []; const resolvedDependencies = [];
const dependencies = const dependencies =
('class' in provider && provider.class.$inject) || (isClassProvider(provider) && provider.class.$inject) ||
('factory' in provider && provider.factory.$inject) || (isFactoryProvider(provider) && provider.factory.$inject) ||
[]; [];
for (const dependency of dependencies) { for (const dependency of dependencies) {
...@@ -103,7 +120,7 @@ export class Injector { ...@@ -103,7 +120,7 @@ export class Injector {
} }
let instance; let instance;
if (provider.class) { if (isClassProvider(provider)) {
// eslint-disable-next-line new-cap // eslint-disable-next-line new-cap
instance = new provider.class(...resolvedDependencies); instance = new provider.class(...resolvedDependencies);
} else { } else {
...@@ -124,12 +141,10 @@ export class Injector { ...@@ -124,12 +141,10 @@ export class Injector {
* If `provider` is a function, it is treated like a class. In other words * If `provider` is a function, it is treated like a class. In other words
* `register(name, SomeClass)` is the same as `register(name, { class: SomeClass })`. * `register(name, SomeClass)` is the same as `register(name, { class: SomeClass })`.
* *
* @param {string} name - Name of object * @param name - Name of object
* @param {Function|Provider} provider - * @param provider - The class or other provider to use to create the object.
* The class or other provider to use to create the object.
* @return {this}
*/ */
register(name, provider) { register(name: string, provider: Constructible | Provider): this {
if (typeof provider === 'function') { if (typeof provider === 'function') {
provider = { class: provider }; provider = { class: provider };
} else if (!isValidProvider(provider)) { } else if (!isValidProvider(provider)) {
...@@ -144,18 +159,17 @@ export class Injector { ...@@ -144,18 +159,17 @@ export class Injector {
* Run a function which uses one or more dependencies provided by the * Run a function which uses one or more dependencies provided by the
* container. * container.
* *
* @template T * @param callback -
* @param {(...args: any[]) => T} callback -
* A callback to run, with dependencies annotated in the same way as * A callback to run, with dependencies annotated in the same way as
* functions or classes passed to `register`. * functions or classes passed to `register`.
* @return {any} - Returns the result of running the function. * @return Returns the result of running the function.
*/ */
run(callback) { run<T>(callback: (...args: any[]) => T): T {
const tempName = 'Injector.run'; const tempName = 'Injector.run';
this.register(tempName, { factory: callback }); this.register(tempName, { factory: callback });
try { try {
return this.get(tempName); return this.get(tempName) as T;
} finally { } finally {
this._instances.delete(tempName); this._instances.delete(tempName);
this._providers.delete(tempName); this._providers.delete(tempName);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment