Skip to content

Commit

Permalink
fix(di): remove default scope configuration on DIConfiguration level
Browse files Browse the repository at this point in the history
BREAKING CHANGE: configuration.scope is removed. This options doesn't make sense since a $ctx exists. ProviderScope.REQUEST must to be declared explicitly on each controller.
  • Loading branch information
Romakita committed Nov 25, 2024
1 parent 4b8cf37 commit 7c06725
Show file tree
Hide file tree
Showing 11 changed files with 49 additions and 100 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ lib-cov
# Coverage directory used by tools like istanbul
coverage
coverage-*


# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

Expand Down
17 changes: 0 additions & 17 deletions docs/docs/configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,23 +265,6 @@ Add providers or modules here. These modules or provider will be built before th
</Tab>
</Tabs>

### scopes

- type: `{[key: string]: ProviderScope}`

Change the default scope for a given provider. See [injection scopes](/docs/injection-scopes) for more details.

```typescript
import {Configuration, ProviderScope, ProviderType} from "@tsed/di";

@Configuration({
scopes: {
[ProviderType.CONTROLLER]: ProviderScope.REQUEST
}
})
export class Server {}
```

### logger

- type: @@PlatformLoggerSettings@@
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/injection-scopes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The scope of a [Provider](/docs/providers.md) defines the lifecycle and visibility of that bean in the context in which it is used.

Ts.ED DI defines 3 types of @@Scope@@ which can be used on injectable classes:
Ts.ED DI defines 3 types of @@ProviderScope@@ which can be used on injectable classes:

- `singleton`: The default scope. The provider is created during server initialization and is shared across all requests.
- `request`: A new instance of the provider is created for each incoming request.
Expand Down
2 changes: 1 addition & 1 deletion packages/di/src/common/decorators/autoInjectable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function resolveAutoInjectableArgs(token: Type, args: unknown[]) {
list.push(args[i]);
} else {
const value = deps[i];
const instance = isArray(value) ? inj!.getMany(value[0], {locals, parent: token}) : inj!.invoke(value, {locals, parent: token});
const instance = isArray(value) ? inj.getMany(value[0], {locals, parent: token}) : inj.invoke(value, {locals, parent: token});

list.push(instance);
}
Expand Down
22 changes: 17 additions & 5 deletions packages/di/src/common/domain/Provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class Provider<T = any> implements ProviderOpts<T> {
return ProviderScope.SINGLETON;
}

return this.get("scope");
return this.get<ProviderScope>("scope", ProviderScope.SINGLETON);
}

/**
Expand All @@ -122,7 +122,7 @@ export class Provider<T = any> implements ProviderOpts<T> {
}

get configuration(): Partial<TsED.Configuration> {
return this.get("configuration");
return this.get("configuration")!;
}

set configuration(configuration: Partial<TsED.Configuration>) {
Expand All @@ -140,9 +140,21 @@ export class Provider<T = any> implements ProviderOpts<T> {
getArgOpts(index: number) {
return this.store.get(`${DI_USE_PARAM_OPTIONS}:${index}`);
}

get(key: string) {
return this.store.get(key) || this._tokenStore.get(key);
/**
* Retrieves a value from the provider's store.
* @param key The key to look up
* @returns The value if found, undefined otherwise
*/
get<Type = unknown>(key: string): Type | undefined;
/**
* Retrieves a value from the provider's store with a default fallback.
* @param key The key to look up
* @param defaultValue The value to return if key is not found
* @returns The found value or defaultValue
*/
get<Type = unknown>(key: string, defaultValue: Type): Type;
get<Type = unknown>(key: string, defaultValue?: Type): Type | undefined {
return this.store.get(key) || this._tokenStore.get(key) || defaultValue;
}

isAsync(): boolean {
Expand Down
11 changes: 10 additions & 1 deletion packages/di/src/common/fn/injectMany.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ import type {InvokeOptions} from "../interfaces/InvokeOptions.js";
import {injector} from "./injector.js";
import {localsContainer} from "./localsContainer.js";

/**
* Injects multiple instances of a given token using the injector service.
* @param token - The injection token to resolve
* @param opts - Optional configuration for the injection
* @param opts.useOpts - Options for instance creation
* @param opts.rebuild - Whether to rebuild the instance
* @param opts.locals - Local container overrides
* @returns Array of resolved instances
*/
export function injectMany<T>(token: string | symbol, opts?: Partial<Pick<InvokeOptions, "useOpts" | "rebuild" | "locals">>): T[] {
return injector().getMany<T>(token, {...opts, locals: opts?.locals || localsContainer()});
return injector().getMany<T>(token, {...opts, locals: opts?.locals || localsContainer()} as InvokeOptions);
}
13 changes: 1 addition & 12 deletions packages/di/src/common/services/DIConfiguration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,10 @@ describe("DIConfiguration", () => {
expect(obj).toEqual({
imports: [],
logger: {},
routes: [],
scopes: {}
routes: []
});
});
});
describe("scopes()", () => {
it("should get scopes", () => {
// GIVEN
const configuration = new DIConfiguration();

configuration.scopes = {};
expect(configuration.scopes).toEqual({});
});
});

describe("imports()", () => {
it("should get imports", () => {
// GIVEN
Expand Down
17 changes: 0 additions & 17 deletions packages/di/src/common/services/DIConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export class DIConfiguration {

constructor(initialProps = {}) {
Object.entries({
scopes: {},
imports: [],
routes: [],
logger: {},
Expand Down Expand Up @@ -46,22 +45,6 @@ export class DIConfiguration {
this.map.set("env", value);
}

get scopes(): Record<string, ProviderScope> {
return this.map.get("scopes");
}

set scopes(value: Record<string, ProviderScope>) {
this.map.set("scopes", value);
}
//
// get resolvers(): DIResolver[] {
// return this.getRaw("resolvers");
// }
//
// set resolvers(resolvers: DIResolver[]) {
// this.map.set("resolvers", resolvers);
// }

get imports(): (TokenProvider | ImportTokenProviderOpts)[] {
return this.get("imports")!;
}
Expand Down
25 changes: 5 additions & 20 deletions packages/di/src/common/services/InjectorService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -616,29 +616,18 @@ describe("InjectorService", () => {
// GIVEN
const injector = new InjectorService();

injector.settings.set({
scopes: {
[ProviderType.VALUE]: ProviderScope.SINGLETON
}
});

expect(injector.settings.get("scopes")).toEqual({
[ProviderType.VALUE]: ProviderScope.SINGLETON
});

injector.add(Symbol.for("TOKEN1"), {
configuration: {
custom: "config",
scopes: {
provider_custom: ProviderScope.SINGLETON
custom: {
config: "1"
}
}
});

injector.add(Symbol.for("TOKEN2"), {
configuration: {
scopes: {
provider_custom_2: ProviderScope.SINGLETON
custom: {
config2: "1"
}
}
});
Expand All @@ -647,11 +636,7 @@ describe("InjectorService", () => {
injector.resolveConfiguration();

// THEN
expect(injector.settings.scopes).toEqual({
provider_custom: "singleton",
provider_custom_2: "singleton",
value: "singleton"
});
expect(injector.settings.get("custom")).toEqual({config: "1", config2: "1"});
});
});

Expand Down
26 changes: 7 additions & 19 deletions packages/di/src/common/services/InjectorService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {createContainer} from "../utils/createContainer.js";
import {getConstructorDependencies} from "../utils/getConstructorDependencies.js";
import {DIConfiguration} from "./DIConfiguration.js";

const EXCLUDED_CONFIGURATION_KEYS = ["mount", "imports"];

/**
* This service contain all services collected by `@Service` or services declared manually with `InjectorService.factory()` or `InjectorService.service()`.
*
Expand Down Expand Up @@ -54,18 +56,6 @@ export class InjectorService extends Container {
this.#cache.set(InjectorService, this);
}

get scopes() {
return this.settings.scopes || {};
}

/**
* Retrieve default scope for a given provider.
* @param provider
*/
public scopeOf(provider: Provider) {
return provider.scope || this.scopes[String(provider.type)] || ProviderScope.SINGLETON;
}

/**
* Return a list of instance build by the injector.
*/
Expand Down Expand Up @@ -101,7 +91,7 @@ export class InjectorService extends Container {
*/
getMany<Type = any>(type: any, options?: Partial<InvokeOptions>): Type[] {
return this.getProviders(type).map((provider) => {
return this.invoke(provider.token, options)!;
return this.invoke<Type>(provider.token, options);
});
}

Expand Down Expand Up @@ -181,7 +171,7 @@ export class InjectorService extends Container {

instance = this.resolve(token, options);

switch (this.scopeOf(provider)) {
switch (provider.scope) {
case ProviderScope.SINGLETON:
if (provider.hooks && !options.rebuild) {
this.registerHooks(provider, instance);
Expand All @@ -206,9 +196,7 @@ export class InjectorService extends Container {
if (options.locals) {
options.locals.set(token, instance);

if (provider.hooks && provider.hooks.$onDestroy) {
options.locals.hooks.on("$onDestroy", (...args: any[]) => provider.hooks!.$onDestroy(instance, ...args));
}
options.locals?.hooks.on("$onDestroy", (...args: unknown[]) => provider.hooks?.$onDestroy(instance, ...args));
}

return instance;
Expand All @@ -233,7 +221,7 @@ export class InjectorService extends Container {
*/
loadSync() {
for (const [, provider] of this) {
if (!this.has(provider.token) && this.scopeOf(provider) === ProviderScope.SINGLETON) {
if (!this.has(provider.token) && provider.scope === ProviderScope.SINGLETON) {
this.invoke(provider.token);
}
}
Expand Down Expand Up @@ -269,7 +257,7 @@ export class InjectorService extends Container {
super.forEach((provider) => {
if (provider.configuration && provider.type !== "server:module") {
Object.entries(provider.configuration).forEach(([key, value]) => {
if (!["mount", "imports"].includes(key)) {
if (!EXCLUDED_CONFIGURATION_KEYS.includes(key)) {
value = mergedConfiguration.has(key) ? deepMerge(mergedConfiguration.get(key), value) : deepClone(value);
mergedConfiguration.set(key, value);
}
Expand Down
12 changes: 5 additions & 7 deletions packages/orm/adapters/src/services/Adapters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Type} from "@tsed/core";
import {Inject, Injectable, InjectorService} from "@tsed/di";
import {constant, inject, injectable} from "@tsed/di";

import {MemoryAdapter} from "../adapters/MemoryAdapter.js";
import {Adapter, AdapterConstructorOptions} from "../domain/Adapter.js";
Expand All @@ -8,16 +8,14 @@ export interface AdapterInvokeOptions<Model = any> extends AdapterConstructorOpt
adapter?: Type<Adapter<Model>>;
}

@Injectable()
export class Adapters {
@Inject()
injector: InjectorService;

invokeAdapter<T = any>(options: AdapterInvokeOptions): Adapter<T> {
const {adapter = this.injector.settings.get("adapters.Adapter", MemoryAdapter), ...props} = options;
const {adapter = constant("adapters.Adapter", MemoryAdapter), ...props} = options;

return this.injector.invoke<Adapter<T>>(adapter, {
return inject<Adapter<T>>(adapter, {
useOpts: props
});
}
}

injectable(Adapters);

0 comments on commit 7c06725

Please sign in to comment.