diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e09c5f8..81863fe 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,6 +11,4 @@ updates: interval: daily open-pull-requests-limit: 10 ignore: - - dependency-name: '@typescript-eslint/eslint-plugin' - - dependency-name: '@typescript-eslint/parser' - dependency-name: '@types/node' diff --git a/.gitignore b/.gitignore index ebef62c..3a66f62 100644 --- a/.gitignore +++ b/.gitignore @@ -51,9 +51,6 @@ web_modules/ # Optional npm cache directory .npm -# Optional eslint cache -.eslintcache - # Optional stylelint cache .stylelintcache diff --git a/README.md b/README.md index b722d2b..72f18e1 100644 --- a/README.md +++ b/README.md @@ -147,12 +147,60 @@ const awilixManager = new AwilixManager({ asyncDispose: true, }) -// This will return dependency1 and dependency2 +// This will return a record with dependency1 and dependency2 const result1 = awilixManager.getWithTags(diContainer, ['queue']) -// This will return only dependency2 +// This will return a record with only dependency2 const result2 = awilixManager.getWithTags(diContainer, ['queue', 'low-priority']) ``` +## Fetching dependencies based on a predicate + +In some cases you may want to get dependencies based on whether they satisfy some condition. +You can use `getByPredicate` method for that: + +```js +import { AwilixManager } from 'awilix-manager' +import { asClass, createContainer } from 'awilix' + +const diContainer = createContainer({ + injectionMode: 'PROXY', +}) + +class QueueConsumerHighPriorityClass { +} + +class QueueConsumerLowPriorityClass { +} + +diContainer.register( + 'dependency1', + asClass(QueueConsumerHighPriorityClass, { + lifetime: 'SINGLETON', + asyncInit: true, + }), +) +diContainer.register( + 'dependency2', + asClass(QueueConsumerLowPriorityClass, { + lifetime: 'SINGLETON', + asyncInit: true, + }), +) + +const awilixManager = new AwilixManager({ + diContainer, + asyncInit: true, + asyncDispose: true, +}) + +// This will return a record with dependency1 +const result1 = awilixManager.getByPredicate((entry) => entry instanceof QueueConsumerHighPriorityClass) +// This will return a record with dependency2 +const result2 = awilixManager.getByPredicate((entry) => entry instanceof QueueConsumerLowPriorityClass)) +``` + +Note that this will resolve all non-disabled dependencies within the container, even the ones without eager injection enabled. + ## 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: diff --git a/biome.json b/biome.json index 9108d21..ddbf6d2 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,8 @@ { "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "files": { + "ignore": ["coverage", "dist", "node_modules"] + }, "javascript": { "formatter": { "arrowParentheses": "always", @@ -13,13 +16,18 @@ "linter": { "rules": { "style": { + "recommended": true, "noNonNullAssertion": "off" }, - "correctness": {}, + "correctness": { + "recommended": true + }, "suspicious": { + "recommended": true, "noExplicitAny": "off" }, "complexity": { + "recommended": true, "useOptionalChain": "off" } } diff --git a/lib/awilixManager.ts b/lib/awilixManager.ts index b0b00dd..b675a57 100644 --- a/lib/awilixManager.ts +++ b/lib/awilixManager.ts @@ -6,10 +6,8 @@ import { 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 @@ -67,10 +65,13 @@ export class AwilixManager { await asyncDispose(this.config.diContainer) } - // eslint-disable-next-line @typescript-eslint/no-explicit-any getWithTags(diContainer: AwilixContainer, tags: string[]): Record { return getWithTags(diContainer, tags) } + + getByPredicate(predicate: (entity: any) => boolean): Record { + return getByPredicate(this.config.diContainer, predicate) + } } export async function asyncInit(diContainer: AwilixContainer) { @@ -91,42 +92,66 @@ export async function asyncInit(diContainer: AwilixContainer) { return key1.localeCompare(key2) }) - for (const entry of dependenciesWithAsyncInit) { - const resolvedValue = diContainer.resolve(entry[0]) - if (entry[1].asyncInit === true) { + for (const [key, description] of dependenciesWithAsyncInit) { + const resolvedValue = diContainer.resolve(key) + if (description.asyncInit === true) { await resolvedValue.asyncInit(diContainer.cradle) } else { // @ts-ignore - await resolvedValue[entry[1].asyncInit](diContainer.cradle) + await resolvedValue[description.asyncInit](diContainer.cradle) } } } export function eagerInject(diContainer: AwilixContainer) { - const dependenciesWithEagerInject = Object.entries(diContainer.registrations).filter((entry) => { - return entry[1].eagerInject && entry[1].enabled !== false - }) - - for (const entry of dependenciesWithEagerInject) { - const resolvedComponent = diContainer.resolve(entry[0]) - if (typeof entry[1].eagerInject === 'string') { - resolvedComponent[entry[1].eagerInject]() + const dependenciesWithEagerInject = Object.entries(diContainer.registrations).filter( + ([_key, description]) => { + return description.eagerInject && description.enabled !== false + }, + ) + + for (const [key, description] of dependenciesWithEagerInject) { + const resolvedComponent = diContainer.resolve(key) + if (typeof description.eagerInject === 'string') { + resolvedComponent[description.eagerInject]() } } } -// eslint-disable-next-line @typescript-eslint/no-explicit-any export function getWithTags(diContainer: AwilixContainer, tags: string[]): Record { - const dependenciesWithTags = Object.entries(diContainer.registrations).filter((entry) => { - return ( - entry[1].enabled !== false && tags.every((v) => entry[1].tags && entry[1].tags.includes(v)) - ) - }) + const dependenciesWithTags = Object.entries(diContainer.registrations).filter( + ([_key, description]) => { + return ( + description.enabled !== false && + tags.every((v) => description.tags && description.tags.includes(v)) + ) + }, + ) - // eslint-disable-next-line @typescript-eslint/no-explicit-any const resolvedComponents: Record = {} - for (const entry of dependenciesWithTags) { - resolvedComponents[entry[0]] = diContainer.resolve(entry[0]) + for (const [key] of dependenciesWithTags) { + resolvedComponents[key] = diContainer.resolve(key) + } + + return resolvedComponents +} + +export function getByPredicate( + diContainer: AwilixContainer, + predicate: (entity: any) => boolean, +): Record { + const enabledDependencies = Object.entries(diContainer.registrations).filter( + ([_key, description]) => { + return description.enabled !== false + }, + ) + + const resolvedComponents: Record = {} + for (const [key] of enabledDependencies) { + const resolvedElement = diContainer.resolve(key) + if (predicate(resolvedElement)) { + resolvedComponents[key] = resolvedElement + } } return resolvedComponents @@ -134,8 +159,8 @@ export function getWithTags(diContainer: AwilixContainer, tags: string[]): Recor export async function asyncDispose(diContainer: AwilixContainer) { const dependenciesWithAsyncDispose = Object.entries(diContainer.registrations) - .filter((entry) => { - return entry[1].asyncDispose && entry[1].enabled !== false + .filter(([_key, description]) => { + return description.asyncDispose && description.enabled !== false }) .sort((entry1, entry2) => { const [key1, resolver1] = entry1 @@ -150,10 +175,10 @@ export async function asyncDispose(diContainer: AwilixContainer) { return key1.localeCompare(key2) }) - for (const entry of dependenciesWithAsyncDispose) { - const resolvedValue = diContainer.resolve(entry[0]) + for (const [key, description] of dependenciesWithAsyncDispose) { + const resolvedValue = diContainer.resolve(key) - const asyncDispose = entry[1].asyncDispose + const asyncDispose = description.asyncDispose if (typeof asyncDispose === 'function') { await asyncDispose(resolvedValue) diff --git a/package.json b/package.json index 2fdedc6..c3b67f4 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,37 @@ { - "name": "awilix-manager", - "description": "Wrapper over awilix to support more complex use-cases, such as async init and eager injection", - "version": "5.2.1", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "scripts": { - "build": "tsc", - "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 && tsc --project tsconfig.lint.json --noEmit", - "lint:fix": "biome check --apply index.ts lib test biome.json", - "prepublishOnly": "npm run build:release" - }, - "peerDependencies": { - "awilix": ">=9.0.0" - }, - "devDependencies": { - "@biomejs/biome": "^1.5.3", - "@types/node": "^20.11.16", - "@vitest/coverage-v8": "^1.2.2", - "del-cli": "^5.0.0", - "typescript": "^5.3.3", - "vitest": "^1.2.2" - }, - "engines": { - "node": ">=16" - }, - "repository": { - "type": "git", - "url": "git://github.com/kibertoad/awilix-manager.git" - }, - "keywords": [ - "init", - "async", - "eager", - "awilix", - "di" - ], - "homepage": "https://github.com/kibertoad/awilix-manager", - "files": [ - "README.md", - "LICENSE", - "dist/*" - ] + "name": "awilix-manager", + "description": "Wrapper over awilix to support more complex use-cases, such as async init and eager injection", + "version": "5.2.1", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "build:release": "del-cli dist && del-cli coverage && npm run lint && npm run build", + "test": "vitest", + "test:coverage": "npm test -- --coverage", + "lint": "biome check . && tsc --project tsconfig.lint.json --noEmit", + "lint:fix": "biome check --apply .", + "prepublishOnly": "npm run build:release" + }, + "peerDependencies": { + "awilix": ">=9.0.0" + }, + "devDependencies": { + "@biomejs/biome": "^1.7.3", + "@types/node": "^20.12.13", + "@vitest/coverage-v8": "^1.6.0", + "del-cli": "^5.1.0", + "typescript": "^5.4.5", + "vitest": "^1.6.0" + }, + "engines": { + "node": ">=16" + }, + "repository": { + "type": "git", + "url": "git://github.com/kibertoad/awilix-manager.git" + }, + "keywords": ["init", "async", "eager", "awilix", "di"], + "homepage": "https://github.com/kibertoad/awilix-manager", + "files": ["README.md", "LICENSE", "dist/*"] } diff --git a/test/awilixManager.spec.ts b/test/awilixManager.spec.ts index ccddc14..84c09f8 100644 --- a/test/awilixManager.spec.ts +++ b/test/awilixManager.spec.ts @@ -7,11 +7,14 @@ import { asMockClass, asyncDispose, asyncInit, - eagerInject, + getByPredicate, getWithTags, } from '../lib/awilixManager' -class AsyncInitClass { +class SuperClass1 {} +class SuperClass2 {} + +class AsyncInitClass extends SuperClass1 { isInitted = false isUpdated = false @@ -26,7 +29,7 @@ class AsyncInitClass { } } -class AsyncDisposeClass { +class AsyncDisposeClass extends SuperClass2 { isDisposed = false asyncDispose() { @@ -168,6 +171,66 @@ describe('awilixManager', () => { }) }) + describe('getByPredicate', () => { + it('retrieves entries by predicate', async () => { + const diContainer = createContainer({ + injectionMode: 'PROXY', + }) + const awilixManager = new AwilixManager({ + diContainer, + asyncInit: true, + asyncDispose: true, + }) + + diContainer.register( + 'dependency1', + asClass(AsyncInitClass, { + lifetime: 'SINGLETON', + asyncInit: true, + }), + ) + diContainer.register( + 'dependency2', + asClass(AsyncInitClass, { + lifetime: 'SINGLETON', + asyncInit: true, + }), + ) + diContainer.register( + 'dependency3', + asClass(AsyncDisposeClass, { + lifetime: 'SINGLETON', + }), + ) + diContainer.register( + 'dependency4', + asClass(AsyncDisposeClass, { + lifetime: 'SINGLETON', + enabled: false, + }), + ) + + await awilixManager.executeInit() + + const { dependency1, dependency2, dependency3 } = diContainer.cradle + const superClass1Entries = awilixManager.getByPredicate( + (entry) => entry instanceof SuperClass1, + ) + expect(superClass1Entries).toStrictEqual({ + dependency1: dependency1, + dependency2: dependency2, + }) + + const superClass2Entries = getByPredicate( + diContainer, + (entry) => entry instanceof SuperClass2, + ) + expect(superClass2Entries).toStrictEqual({ + dependency3: dependency3, + }) + }) + }) + describe('asyncInit', () => { it('execute asyncInit on registered dependencies', async () => { const diContainer = createContainer({ diff --git a/tsconfig.json b/tsconfig.json index 0cfacaf..b89418a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,28 +1,26 @@ { - "compilerOptions": { - "outDir": "dist", - "module": "CommonJS", - "target": "ES2022", - "lib": ["ES2022", "dom"], - "sourceMap": false, - "declaration": true, - "declarationMap": false, - "types": ["node"], - "strict": true, - "moduleResolution": "node", - "noUnusedLocals": false, - "noUnusedParameters": false, - "noFallthroughCasesInSwitch": true, - "strictNullChecks": true, - "importHelpers": false, - "skipLibCheck": true, - "baseUrl": ".", - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true - }, - "exclude": ["node_modules", "dist", "test/**/", - "vitest.config.mts" - ] + "compilerOptions": { + "outDir": "dist", + "module": "CommonJS", + "target": "ES2022", + "lib": ["ES2022", "dom"], + "sourceMap": false, + "declaration": true, + "declarationMap": false, + "types": ["node"], + "strict": true, + "moduleResolution": "node", + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + "strictNullChecks": true, + "importHelpers": false, + "skipLibCheck": true, + "baseUrl": ".", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true + }, + "exclude": ["node_modules", "dist", "test/**/", "vitest.config.mts"] } diff --git a/tsconfig.lint.json b/tsconfig.lint.json index 791e0d3..8668231 100644 --- a/tsconfig.lint.json +++ b/tsconfig.lint.json @@ -1,7 +1,5 @@ { - "extends": ["./tsconfig.json"], - "include": ["lib/**/*.ts", "test/**/*.ts", - "vitest.config.mts" - ], - "exclude": [] + "extends": ["./tsconfig.json"], + "include": ["lib/**/*.ts", "test/**/*.ts", "vitest.config.mts"], + "exclude": [] } diff --git a/vitest.config.mts b/vitest.config.mts index 8467a64..40c61ea 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -16,7 +16,7 @@ export default defineConfig({ branches: 100, functions: 100, lines: 100, - } + }, }, }, })