-
-
Notifications
You must be signed in to change notification settings - Fork 284
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(di): add AutoInjectable decorator
- Loading branch information
Showing
6 changed files
with
257 additions
and
20 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
137 changes: 137 additions & 0 deletions
137
packages/di/src/common/decorators/autoInjectable.spec.ts
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,137 @@ | ||
import {catchError} from "@tsed/core"; | ||
import {Logger} from "@tsed/logger"; | ||
import {beforeEach} from "vitest"; | ||
import {DITest} from "../../node/index.js"; | ||
import {registerProvider} from "../registries/ProviderRegistry.js"; | ||
import {InjectorService} from "../services/InjectorService.js"; | ||
import {AutoInjectable} from "./autoInjectable.js"; | ||
import {Inject} from "./inject.js"; | ||
import {Injectable} from "./injectable.js"; | ||
|
||
const TOKEN_GROUPS = Symbol.for("groups:1"); | ||
|
||
interface InterfaceGroup { | ||
type: string; | ||
} | ||
|
||
@Injectable({ | ||
type: TOKEN_GROUPS | ||
}) | ||
class MyService1 implements InterfaceGroup { | ||
readonly type: string = "service1"; | ||
|
||
constructor(@Inject(InjectorService) readonly injector: any) {} | ||
} | ||
|
||
@Injectable({ | ||
type: TOKEN_GROUPS | ||
}) | ||
class MyService2 implements InterfaceGroup { | ||
readonly type: string = "service2"; | ||
|
||
constructor(@Inject(InjectorService) readonly injector: any) {} | ||
} | ||
|
||
const TokenAsync = Symbol.for("MyService2"); | ||
|
||
registerProvider({ | ||
provide: TokenAsync, | ||
type: TOKEN_GROUPS, | ||
deps: [], | ||
useAsyncFactory() { | ||
return Promise.resolve({ | ||
type: "async" | ||
}); | ||
} | ||
}); | ||
|
||
describe("AutoInjectable", () => { | ||
describe("when the instance is created during an injection context", () => { | ||
beforeEach(() => DITest.create()); | ||
afterEach(() => DITest.reset()); | ||
it("should return a class that extends the original class", () => { | ||
@AutoInjectable() | ||
class Test { | ||
@Inject(Logger) | ||
logger: Logger; | ||
|
||
foo() { | ||
this.logger.info("test"); | ||
} | ||
} | ||
|
||
const test = new Test(); | ||
const test2 = new Test(); | ||
|
||
expect(test).toBeInstanceOf(Test); | ||
expect(test2).toBeInstanceOf(Test); | ||
expect(test).not.toBe(test2); | ||
|
||
vi.spyOn(test.logger, "info").mockResolvedValue(undefined as never); | ||
|
||
test.foo(); | ||
|
||
expect(test.logger.info).toHaveBeenCalledWith("test"); | ||
}); | ||
it("should return a class that extends the original class (with additional args)", () => { | ||
@AutoInjectable() | ||
class Test { | ||
@Inject(Logger) | ||
logger: Logger; | ||
|
||
private value: string; | ||
|
||
constructor(initialValue: string, @Inject(InjectorService) injector?: InjectorService) { | ||
this.value = initialValue; | ||
expect(injector).toBeInstanceOf(InjectorService); | ||
} | ||
|
||
foo() { | ||
this.logger.info("test_" + this.value); | ||
} | ||
} | ||
|
||
const test = new Test("test"); | ||
|
||
vi.spyOn(test.logger, "info").mockResolvedValue(undefined as never); | ||
|
||
test.foo(); | ||
|
||
expect(test.logger.info).toHaveBeenCalledWith("test_test"); | ||
}); | ||
it("should return a class that extends the original class (with inject many)", () => { | ||
@AutoInjectable() | ||
class Test { | ||
@Inject(Logger) | ||
logger: Logger; | ||
|
||
private value: string; | ||
|
||
constructor(initialValue: string, @Inject(TOKEN_GROUPS) instances?: InterfaceGroup[]) { | ||
this.value = initialValue; | ||
expect(instances).toHaveLength(3); | ||
} | ||
} | ||
|
||
new Test("test"); | ||
}); | ||
}); | ||
describe("when the instance is created outside of an injection context", () => { | ||
it("should throw an error", () => { | ||
@AutoInjectable() | ||
class Test { | ||
@Inject(Logger) | ||
logger: Logger; | ||
|
||
foo() { | ||
this.logger.info("test"); | ||
} | ||
} | ||
|
||
const error = catchError(() => new Test()); | ||
|
||
expect(error).toBeInstanceOf(Error); | ||
expect(error?.message).toEqual("InjectorService instance is not created yet."); | ||
}); | ||
}); | ||
}); |
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,14 @@ | ||
import {LocalsContainer} from "../domain/LocalsContainer.js"; | ||
import {InjectorService} from "../services/InjectorService.js"; | ||
|
||
export function AutoInjectable() { | ||
return <T extends {new (...args: any[]): NonNullable<unknown>}>(constr: T): T => { | ||
return class AutoInjectable extends constr { | ||
constructor(...args: any[]) { | ||
const locals = new LocalsContainer(); | ||
super(...InjectorService.resolveAutoInjectableArgs(constr, locals, args)); | ||
InjectorService.bind(this, locals); | ||
} | ||
} as unknown as T; | ||
}; | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,25 @@ | ||
import {ProviderScope} from "../domain/ProviderScope.js"; | ||
import {TokenProvider} from "./TokenProvider.js"; | ||
|
||
export interface InvokeOptions<T = any> { | ||
deps: any[]; | ||
imports: any[]; | ||
export interface InvokeOptions { | ||
/** | ||
* Define dependencies to build the provider and inject them in the constructor. | ||
*/ | ||
deps: unknown[]; | ||
/** | ||
* List of imports to be created before the provider. Imports list aren't injected directly in the provider constructor. | ||
*/ | ||
imports: unknown[]; | ||
/** | ||
* Parent provider. | ||
*/ | ||
parent?: TokenProvider; | ||
/** | ||
* Scope used by the injector to build the provider. | ||
*/ | ||
scope: ProviderScope; | ||
useScope: boolean; | ||
/** | ||
* If true, the injector will rebuild the instance. | ||
*/ | ||
rebuild?: boolean; | ||
} |
Oops, something went wrong.