diff --git a/README.md b/README.md index 917c360..d3d1259 100644 --- a/README.md +++ b/README.md @@ -152,3 +152,51 @@ const result1 = awilixManager.getWithTags(diContainer, ['queue']) // This will return only dependency2 const result2 = awilixManager.getWithTags(diContainer, ['queue', 'low-priority']) ``` + +## Type-safe resolver definition + +You can use `ResolvedDependencies` for defining your DI configuration as an object: + +```ts +type DiContainerType = { + testClass: TestClass +} +const diConfiguration: AwilixResolverRecord = { + testClass: asClass(TestClass), +} + +const diContainer = createContainer({ + injectionMode: 'PROXY', +}) + +for (const [dependencyKey, dependencyValue] of Object.entries(diConfiguration)) { + diContainer.register(dependencyKey, dependencyValue as Resolver) +} +``` + +## Mocking dependencies + +Sometimes you may want to intentionally inject objects that do not fully conform to the type definition of an original class. For that you can use `asMockClass` resolver: + +```ts +type DiContainerType = { + realClass: RealClass + realClass2: RealClass +} +const diConfiguration: AwilixResolverRecord = { + realClass: asClass(RealClass), + realClass2: asMockClass(FakeClass), +} + +const diContainer = createContainer({ + injectionMode: 'PROXY', +}) + +for (const [dependencyKey, dependencyValue] of Object.entries(diConfiguration)) { + diContainer.register(dependencyKey, dependencyValue as Resolver) +} + +const { realClass, realClass2 } = diContainer.cradle +expect(realClass).toBeInstanceOf(RealClass) +expect(realClass2).toBeInstanceOf(FakeClass) +``` diff --git a/index.ts b/index.ts index ad326bb..a30e025 100644 --- a/index.ts +++ b/index.ts @@ -1,8 +1,9 @@ export { AwilixManager, + asMockClass, eagerInject, asyncInit, asyncDispose, getWithTags, } from './lib/awilixManager' -export type { AwilixManagerConfig } from './lib/awilixManager' +export type { AwilixManagerConfig, AwilixResolverRecord } from './lib/awilixManager' diff --git a/lib/awilixManager.ts b/lib/awilixManager.ts index 9299387..946e6b1 100644 --- a/lib/awilixManager.ts +++ b/lib/awilixManager.ts @@ -1,11 +1,19 @@ -import type { AwilixContainer } from 'awilix' +import { + type AwilixContainer, + type BuildResolver, + type BuildResolverOptions, + type Constructor, + type DisposableResolver, + asClass, +} from 'awilix' +import type { Resolver } from 'awilix/lib/resolvers' declare module 'awilix' { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface ResolverOptions { asyncInit?: boolean | string asyncInitPriority?: number // lower means it gets initted earlier - asyncDispose?: boolean | string | ((instance: T) => Promise) + asyncDispose?: boolean | string | ((instance: U) => Promise) asyncDisposePriority?: number // lower means it gets disposed earlier eagerInject?: boolean | string tags?: string[] @@ -21,6 +29,17 @@ export type AwilixManagerConfig = { strictBooleanEnforced?: boolean } +export type AwilixResolverRecord = { + [Key in keyof TDependencies]: Resolver +} + +export function asMockClass( + Type: unknown, + opts?: BuildResolverOptions, +): BuildResolver & DisposableResolver { + return asClass(Type as Constructor, opts) +} + export class AwilixManager { public readonly config: AwilixManagerConfig @@ -149,11 +168,7 @@ export async function asyncDispose(diContainer: AwilixContainer) { await resolvedValue.asyncDispose() continue } - - // assume it's a string - { - // @ts-ignore - await resolvedValue[asyncDispose]() - } + // @ts-ignore + await resolvedValue[asyncDispose]() } } diff --git a/package.json b/package.json index be45853..21e60bf 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "build:release": "del-cli dist && del-cli coverage && npm run lint && npm run build", "test": "vitest", "test:coverage": "npm test -- --coverage", - "lint": "biome lint index.ts lib test biome.json", + "lint": "biome lint index.ts lib test biome.json && tsc --project tsconfig.lint.json --noEmit", "lint:fix": "biome check --apply index.ts lib test biome.json", "prepublishOnly": "npm run build:release" }, diff --git a/test/awilixManager.spec.ts b/test/awilixManager.spec.ts index 343397f..ce397f6 100644 --- a/test/awilixManager.spec.ts +++ b/test/awilixManager.spec.ts @@ -1,8 +1,11 @@ import { asClass, createContainer } from 'awilix' import { describe, expect, it } from 'vitest' +import type { Resolver } from 'awilix/lib/resolvers' import { AwilixManager, + type AwilixResolverRecord, + asMockClass, asyncDispose, asyncInit, eagerInject, @@ -82,6 +85,31 @@ class InitSetClass { } } +describe('asMockClass', () => { + it('Supports passing a mock instance that does not fully implement the real class', () => { + type DiContainerType = { + asyncInitClass: AsyncInitClass + asyncInitClass2: AsyncInitClass + } + const diConfiguration: AwilixResolverRecord = { + asyncInitClass: asClass(AsyncInitClass), + asyncInitClass2: asMockClass(AsyncDisposeClass), + } + + const diContainer = createContainer({ + injectionMode: 'PROXY', + }) + + for (const [dependencyKey, dependencyValue] of Object.entries(diConfiguration)) { + diContainer.register(dependencyKey, dependencyValue as Resolver) + } + + const { asyncInitClass, asyncInitClass2 } = diContainer.cradle + expect(asyncInitClass).toBeInstanceOf(AsyncInitClass) + expect(asyncInitClass2).toBeInstanceOf(AsyncDisposeClass) + }) +}) + describe('awilixManager', () => { describe('constructor', () => { it('throws an error if strictBooleanEnforced is set and undefined is passed', () => { diff --git a/vitest.config.mts b/vitest.config.mts index 7828e41..8467a64 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -11,10 +11,12 @@ export default defineConfig({ exclude: ['lib/**/*.spec.ts'], reporter: ['text', 'lcov'], all: true, - statements: 100, - branches: 100, - functions: 100, - lines: 100, + thresholds: { + statements: 100, + branches: 100, + functions: 100, + lines: 100, + } }, }, })