Skip to content

Commit

Permalink
fix(di): fix DI.invoke when inject() is used on nested class
Browse files Browse the repository at this point in the history
  • Loading branch information
Romakita committed Oct 9, 2024
1 parent bea2874 commit af1c26d
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 13 deletions.
26 changes: 26 additions & 0 deletions packages/di/src/common/decorators/inject.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
import {catchAsyncError} from "@tsed/core";

import {DITest} from "../../node/index.js";
import {inject} from "../fn/inject.js";
import {injector} from "../fn/injector.js";
import {registerProvider} from "../registries/ProviderRegistry.js";
import {InjectorService} from "../services/InjectorService.js";
import {Inject} from "./inject.js";
import {Injectable} from "./injectable.js";

@Injectable()
class ProvidersList extends Map<string, string> {}

@Injectable()
class MyService {
@Inject(ProvidersList)
providersList: ProvidersList;

getValue() {
return this.providersList.get("key");
}
}

describe("@Inject()", () => {
beforeEach(() => DITest.create());
afterEach(() => DITest.reset());
Expand Down Expand Up @@ -279,4 +293,16 @@ describe("@Inject()", () => {
expect(error?.message).toContain("Object isn't a valid token. Please check the token set on Test.test");
});
});
it("should rebuild all dependencies using invoke", async () => {
const providersList = inject(ProvidersList);
const myService = inject(MyService);
providersList.set("key", "value");

expect(inject(ProvidersList).get("key")).toEqual("value");
expect(myService.getValue()).toEqual("value");

const newMyService = await DITest.invoke(MyService, []);
expect(newMyService.getValue()).toEqual(undefined);
expect(myService.getValue()).toEqual("value");
});
});
26 changes: 25 additions & 1 deletion packages/di/src/common/fn/inject.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import {DITest} from "../../node/index.js";
import {Injectable} from "../decorators/injectable.js";
import {InjectorService} from "../services/InjectorService.js";
import {inject} from "./inject.js";

@Injectable()
class ProvidersList extends Map<string, string> {}

@Injectable()
class MyService {
readonly providersList = inject(ProvidersList);

getValue() {
return this.providersList.get("key");
}
}

describe("inject()", () => {
beforeEach(() => DITest.create());
afterEach(() => DITest.reset());
Expand All @@ -26,5 +39,16 @@ describe("inject()", () => {
}
]);
});
it("should rebuild all dependencies using invoke", async () => {});
it("should rebuild all dependencies using invoke", async () => {
const providersList = inject(ProvidersList);
const myService = inject(MyService);
providersList.set("key", "value");

expect(inject(ProvidersList).get("key")).toEqual("value");
expect(myService.getValue()).toEqual("value");

const newMyService = await DITest.invoke(MyService, []);
expect(newMyService.getValue()).toEqual(undefined);
expect(myService.getValue()).toEqual("value");
});
});
7 changes: 5 additions & 2 deletions packages/di/src/common/fn/inject.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {InvokeOptions} from "../interfaces/InvokeOptions.js";
import {TokenProvider} from "../interfaces/TokenProvider.js";
import {injector} from "./injector.js";
import {localsContainer} from "./localsContainer.js";
import {invokeOptions, localsContainer} from "./localsContainer.js";

/**
* Inject a provider to another provider.
Expand All @@ -21,5 +21,8 @@ import {localsContainer} from "./localsContainer.js";
* @decorator
*/
export function inject<T>(token: TokenProvider<T>, opts?: Partial<Pick<InvokeOptions, "useOpts" | "rebuild" | "locals">>): T {
return injector().invoke(token, opts?.locals || localsContainer(), opts);
return injector().invoke(token, opts?.locals || localsContainer(), {
...opts,
...invokeOptions()
});
}
18 changes: 17 additions & 1 deletion packages/di/src/common/fn/localsContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ import {InjectorService} from "../services/InjectorService.js";
import {injector} from "./injector.js";

let globalLocals: LocalsContainer | undefined;
let globalInvOpts: any = {};
const stagedLocals: LocalsContainer[] = [];

/**
* Get the locals container initiated by DITest or .bootstrap() method.
*/
export function localsContainer({providers}: {providers?: UseImportTokenProviderOpts[]; rebuild?: boolean} = {}) {
export function localsContainer({
providers,
rebuild
}: {
providers?: UseImportTokenProviderOpts[];
rebuild?: boolean;
} = {}) {
if (!globalLocals || providers) {
globalLocals = new LocalsContainer();

Expand All @@ -20,17 +27,26 @@ export function localsContainer({providers}: {providers?: UseImportTokenProvider

globalLocals.set(InjectorService, injector());
}

if (rebuild) {
globalInvOpts.rebuild = rebuild;
}
}

return globalLocals;
}

export function invokeOptions() {
return {...globalInvOpts};
}

/**
* Reset the locals container.
*/
export function detachLocalsContainer() {
globalLocals && stagedLocals.push(globalLocals);
globalLocals = undefined;
globalInvOpts = {};
}

export function cleanAllLocalsContainer() {
Expand Down
1 change: 0 additions & 1 deletion packages/di/src/common/services/InjectorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,6 @@ export class InjectorService extends Container {
Reflect.defineProperty(instance, DI_INVOKE_OPTIONS, {
get: () => ({rebuild: options.rebuild, locals})
});
// TODO add a way to notify DI consumer when a class instance is build
}

return instance;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {PlatformBuilder, PlatformBuilderSettings} from "@tsed/common";
import type {PlatformBuilder, PlatformBuilderSettings} from "@tsed/common";
import {nameOf, Type} from "@tsed/core";
import {DITest} from "@tsed/di";
import {destroyInjector, DITest, hasInjector} from "@tsed/di";
import {APIGatewayEventDefaultAuthorizerContext, APIGatewayProxyEventBase, APIGatewayProxyHandler} from "aws-lambda";
import {APIGatewayProxyResult} from "aws-lambda/trigger/api-gateway-proxy.js";

Expand Down Expand Up @@ -153,7 +153,7 @@ export class PlatformServerlessTest extends DITest {
static request = LambdaClientRequest;

static bootstrap(
serverless: {bootstrap: (server: Type<any>, settings: PlatformBuilderSettings<any>) => PlatformBuilder},
serverless: {bootstrap: (server: Type<any>, settings: TsED.Configuration) => PlatformBuilder},
{server, ...settings}: PlatformBuilderSettings<any> & {server: Type<any>}
): () => Promise<any>;
static bootstrap(
Expand All @@ -177,8 +177,6 @@ export class PlatformServerlessTest extends DITest {
}

PlatformServerlessTest.callbacks.handler = instance.handler();
// used by inject method
DITest.injector = instance.injector;

return instance.promise;
};
Expand All @@ -191,9 +189,8 @@ export class PlatformServerlessTest extends DITest {
if (PlatformServerlessTest.instance) {
await PlatformServerlessTest.instance.stop();
}
if (DITest.hasInjector()) {
await DITest.injector.destroy();
DITest._injector = null;
if (hasInjector()) {
await destroyInjector();
}
}
}

0 comments on commit af1c26d

Please sign in to comment.