Skip to content

Commit

Permalink
Merge pull request #15 from AVykhrystyuk/feature/container-useClass
Browse files Browse the repository at this point in the history
feat: impl and test container.useValue
  • Loading branch information
AVykhrystyuk committed Jun 3, 2021
2 parents d0272be + feeee9b commit 2e8903f
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 16 deletions.
83 changes: 83 additions & 0 deletions src/impl/container-impl.spec.ts
Expand Up @@ -7,6 +7,7 @@ import { createModernFactoryRegistry } from './create-modern-factory-registry';
import { Cat, Dog, Fish, Meat, Milk, stringToken, symbolToken } from './container-types.spec';

describe('ContainerImpl', () => {
// TODO: use legacyFactoryRegistry
describe('with ModernFactoryRegistry', () => {
let container: Container;

Expand All @@ -18,6 +19,88 @@ describe('ContainerImpl', () => {
assert.throws(() => container.resolve(Cat), DependencyInjectionError, 'Error is not being thrown');
});

describe('useClass', () => {
it('class token', () => {
container
.useClass({ for: Fish, use: Fish })
.useClass({ for: Milk, use: Milk })
.useClass({ for: Cat, use: Cat, inject: [Fish, Milk] });

assert.ok(container.resolve(Cat).isCat, "Cat wasn't resolved");
});

it('symbol token', () => {
const { fish, milk, cat } = symbolToken;

container
.useClass({ for: fish, use: Fish })
.useClass({ for: milk, use: Milk })
.useClass({ for: cat, use: Cat, inject: [fish, milk] });

assert.ok(container.resolve(cat).isCat, "Cat wasn't resolved");
});

it('string token', () => {
const { fish, milk, cat } = stringToken;

container
.useClass({ for: fish, use: Fish })
.useClass({ for: milk, use: Milk })
.useClass({ for: cat, use: Cat, inject: [fish, milk] });

assert.ok(container.resolve(cat).isCat, "Cat wasn't resolved");
});

it('regular - not a singleton', () => {
container
.useClass({ for: Fish, use: Fish })
.useClass({ for: Milk, use: Milk })
.useClass({ for: Cat, use: Cat, inject: [Fish, Milk] });

assert.notStrictEqual(container.resolve(Fish), container.resolve(Fish));
assert.notStrictEqual(container.resolve(Milk), container.resolve(Milk));
assert.notStrictEqual(container.resolve(Cat), container.resolve(Cat));
});

it('singleton', () => {
container
.useClass({ for: Fish, use: Fish })
.useClass({ for: Milk, use: Milk, singleton: true })
.useClass({ for: Cat, use: Cat, inject: [Fish, Milk], singleton: true });

assert.notStrictEqual(container.resolve(Fish), container.resolve(Fish));
assert.strictEqual(container.resolve(Milk), container.resolve(Milk));
assert.strictEqual(container.resolve(Cat), container.resolve(Cat));
assert.strictEqual(container.resolve(Cat).milk, container.resolve(Milk));
});

it('error path - error during resolve', () => {
const thrownError = new Error('Ops!');
container
.useClass({
for: Meat,
use: class ThrowableMeat extends Meat {
constructor() {
throw thrownError;
super();
}
},
})
.useClass({ for: Dog, use: Dog, inject: [Meat] });

assert.throws(
() => container.resolve(Meat),
(err: DependencyInjectionError) => err.innerError === thrownError,
'Error is not being thrown'
);
assert.throws(
() => container.resolve(Dog),
(err: DependencyInjectionError) => (err.innerError as DependencyInjectionError).innerError === thrownError,
'Error is not being thrown'
);
});
});

describe('useFactory', () => {
it('class token', () => {
container
Expand Down
77 changes: 70 additions & 7 deletions src/impl/container-impl.ts
@@ -1,18 +1,27 @@
import {
Container,
Resolver,
ResolveFactoryFunction,
FactoryRegistry,
DependencyInjectionError,
Token,
Constructor,
Constructor1,
Constructor2,
Constructor3,
Constructor4,
Constructor5,
Constructor6,
Constructor7,
ClassProvider1,
ClassProvider2,
ClassProvider3,
ClassProvider4,
ClassProvider5,
ClassProvider6,
ClassProvider7,
ResolveProvider,
ValueProvider,
ClassBaseProvider,
} from '../interfaces';
import { memoize } from './utils';

Expand All @@ -23,9 +32,7 @@ export class ContainerImpl extends Container {
}

public useFactory<T, TResult extends T>(provider: ResolveProvider<T, TResult>): Container {
const factory: ResolveFactoryFunction<T> = provider.singleton ? memoize(provider.use) : provider.use;

this.register(provider.for, factory);
this.register(provider.for, provider.use, provider.singleton);
return this;
}

Expand All @@ -41,21 +48,77 @@ export class ContainerImpl extends Container {
provider: ClassProvider3<T, TCtor, TCtorArg1, TCtorArg2, TCtorArg3>
): Container;

public useClass(provider: any): Container {
public useClass<
T,
TCtor extends Constructor4<T, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4>,
TCtorArg1,
TCtorArg2,
TCtorArg3,
TCtorArg4
>(provider: ClassProvider4<T, TCtor, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4>): Container;

public useClass<
T,
TCtor extends Constructor5<T, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5>,
TCtorArg1,
TCtorArg2,
TCtorArg3,
TCtorArg4,
TCtorArg5
>(provider: ClassProvider5<T, TCtor, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5>): Container;

public useClass<
T,
TCtor extends Constructor6<T, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5, TCtorArg6>,
TCtorArg1,
TCtorArg2,
TCtorArg3,
TCtorArg4,
TCtorArg5,
TCtorArg6
>(provider: ClassProvider6<T, TCtor, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5, TCtorArg6>): Container;

public useClass<
T,
TCtor extends Constructor7<T, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5, TCtorArg6, TCtorArg7>,
TCtorArg1,
TCtorArg2,
TCtorArg3,
TCtorArg4,
TCtorArg5,
TCtorArg6,
TCtorArg7
>(
provider: ClassProvider7<T, TCtor, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5, TCtorArg6, TCtorArg7>
): Container;

public useClass(provider: ClassBaseProvider<unknown, Constructor<unknown>>): Container {
this.register(
provider.for,
() => {
const Ctor = provider.use;
const depTokens = provider.inject ?? [];
const deps = depTokens.map((token) => this.resolve(token));
return new Ctor(...deps);
},
provider.singleton
);

return this;
}

public constructor(private readonly factoryRegistry: FactoryRegistry) {
super();
}

private register<T>(token: Token<T>, factory: ResolveFactoryFunction<T>): this {
private register<T>(token: Token<T>, factory: ResolveFactoryFunction<T>, singleton?: boolean): this {
if (this.factoryRegistry.hasFactory(token)) {
const displayToken = this.getTokenDisplayName(token);
throw new DependencyInjectionError(`Factory is already registered for '${displayToken}'`);
}

this.factoryRegistry.setFactory(token, factory);
const finalFactory = singleton ? memoize(factory) : factory;
this.factoryRegistry.setFactory(token, finalFactory);
return this;
}

Expand Down
29 changes: 29 additions & 0 deletions src/interfaces/constructor.ts
Expand Up @@ -8,3 +8,32 @@ export type Constructor1<T, TArg1> = new (arg1: TArg1) => T;
export type Constructor2<T, TArg1, TArg2> = new (arg1: TArg1, arg2: TArg2) => T;

export type Constructor3<T, TArg1, TArg2, TArg3> = new (arg1: TArg1, arg2: TArg2, arg3: TArg3) => T;

export type Constructor4<T, TArg1, TArg2, TArg3, TArg4> = new (arg1: TArg1, arg2: TArg2, arg3: TArg3, arg4: TArg4) => T;

export type Constructor5<T, TArg1, TArg2, TArg3, TArg4, TArg5> = new (
arg1: TArg1,
arg2: TArg2,
arg3: TArg3,
arg4: TArg4,
arg5: TArg5
) => T;

export type Constructor6<T, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6> = new (
arg1: TArg1,
arg2: TArg2,
arg3: TArg3,
arg4: TArg4,
arg5: TArg5,
arg6: TArg6
) => T;

export type Constructor7<T, TArg1, TArg2, TArg3, TArg4, TArg5, TArg6, TArg7> = new (
arg1: TArg1,
arg2: TArg2,
arg3: TArg3,
arg4: TArg4,
arg5: TArg5,
arg6: TArg6,
arg7: TArg7
) => T;
66 changes: 64 additions & 2 deletions src/interfaces/container.ts
@@ -1,6 +1,24 @@
import { Constructor1, Constructor2, Constructor3 } from './constructor';
import {
Constructor1,
Constructor2,
Constructor3,
Constructor4,
Constructor5,
Constructor6,
Constructor7,
} from './constructor';
import { Resolver } from './resolver';
import { ClassProvider1, ClassProvider2, ClassProvider3, ResolveProvider, ValueProvider } from './providers';
import {
ClassProvider1,
ClassProvider2,
ClassProvider3,
ClassProvider4,
ClassProvider5,
ClassProvider6,
ClassProvider7,
ResolveProvider,
ValueProvider,
} from './providers';

export abstract class Container extends Resolver {
public abstract useFactory<T, TResult extends T>(provider: ResolveProvider<T, TResult>): Container;
Expand All @@ -22,6 +40,50 @@ export abstract class Container extends Resolver {
TCtorArg3
>(provider: ClassProvider3<T, TCtor, TCtorArg1, TCtorArg2, TCtorArg3>): Container;

public abstract useClass<
T,
TCtor extends Constructor4<T, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4>,
TCtorArg1,
TCtorArg2,
TCtorArg3,
TCtorArg4
>(provider: ClassProvider4<T, TCtor, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4>): Container;

public abstract useClass<
T,
TCtor extends Constructor5<T, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5>,
TCtorArg1,
TCtorArg2,
TCtorArg3,
TCtorArg4,
TCtorArg5
>(provider: ClassProvider5<T, TCtor, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5>): Container;

public abstract useClass<
T,
TCtor extends Constructor6<T, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5, TCtorArg6>,
TCtorArg1,
TCtorArg2,
TCtorArg3,
TCtorArg4,
TCtorArg5,
TCtorArg6
>(provider: ClassProvider6<T, TCtor, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5, TCtorArg6>): Container;

public abstract useClass<
T,
TCtor extends Constructor7<T, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5, TCtorArg6, TCtorArg7>,
TCtorArg1,
TCtorArg2,
TCtorArg3,
TCtorArg4,
TCtorArg5,
TCtorArg6,
TCtorArg7
>(
provider: ClassProvider7<T, TCtor, TCtorArg1, TCtorArg2, TCtorArg3, TCtorArg4, TCtorArg5, TCtorArg6, TCtorArg7>
): Container;

public abstract tryVerifyAll(): boolean;

/**
Expand Down

0 comments on commit 2e8903f

Please sign in to comment.