From ab42660e80b317b985e559a97ddd3372ec8851d2 Mon Sep 17 00:00:00 2001 From: Alexander Zinn Date: Sat, 15 Nov 2025 05:00:02 -0500 Subject: [PATCH] Implement configuration management and dependency injection framework - Added ConfigurationReader for loading dependency definitions - Introduced FileConfigurationLoader for file-based configuration loading - Created IDependencyManager and IDependencyProvider interfaces for managing dependencies - Implemented DependencyProvider for providing instances based on lifecycle - Added Default class for logging level configuration - Enhanced HealthCheckApi with improved route setup and health check handling --- bun.lock | 25 +++--- src/{Defaults.ts => Default.ts} | 2 +- src/api/HealthCheckApi.ts | 33 ++++++-- src/di/ConfigurationReader.ts | 104 +++++++++++++++++++++++++ src/di/FileConfigurationLoader.ts | 42 ++++++++++ src/di/IConfigurationLoader.ts | 6 ++ src/di/IDependencyManger.ts | 13 ++++ src/di/providers/DependencyProvider.ts | 53 +++++++++++++ src/logger/Threshold.ts | 2 +- 9 files changed, 260 insertions(+), 20 deletions(-) rename src/{Defaults.ts => Default.ts} (88%) create mode 100644 src/di/ConfigurationReader.ts create mode 100644 src/di/FileConfigurationLoader.ts create mode 100644 src/di/IConfigurationLoader.ts create mode 100644 src/di/IDependencyManger.ts create mode 100644 src/di/providers/DependencyProvider.ts diff --git a/bun.lock b/bun.lock index 685e88a..223ee55 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 1, "workspaces": { "": { "name": "myoopapp", @@ -31,7 +32,7 @@ "@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="], - "@eslint/config-helpers": ["@eslint/config-helpers@0.4.1", "", { "dependencies": { "@eslint/core": "^0.16.0" } }, "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw=="], + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], "@eslint/core": ["@eslint/core@0.15.2", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg=="], @@ -67,7 +68,7 @@ "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], - "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], + "@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="], "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], @@ -91,13 +92,13 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@24.9.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA=="], + "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], "@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="], "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="], - "@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="], + "@types/react": ["@types/react@19.2.5", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw=="], "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], @@ -141,7 +142,7 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="], + "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], @@ -159,7 +160,7 @@ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "csstype": ["csstype@3.2.0", "", {}, "sha512-si++xzRAY9iPp60roQiFta7OFbhrgvcthrhlNAGeQptSY25uJjkfUV8OArC3KLocB8JT8ohz+qgxWCmz8RhjIg=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -239,7 +240,7 @@ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], @@ -421,13 +422,13 @@ "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], - "@eslint/config-helpers/@eslint/core": ["@eslint/core@0.16.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q=="], + "@eslint/config-helpers/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], "@eslint/markdown/@eslint/core": ["@eslint/core@0.16.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q=="], - "@eslint/markdown/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0", "levn": "^0.4.1" } }, "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A=="], + "@eslint/markdown/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="], @@ -437,7 +438,7 @@ "eslint/@eslint/core": ["@eslint/core@0.16.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q=="], - "eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0", "levn": "^0.4.1" } }, "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A=="], + "eslint/@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], @@ -445,6 +446,10 @@ "mdast-util-frontmatter/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "@eslint/markdown/@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "eslint/@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], } } diff --git a/src/Defaults.ts b/src/Default.ts similarity index 88% rename from src/Defaults.ts rename to src/Default.ts index 26c9b93..72d990f 100644 --- a/src/Defaults.ts +++ b/src/Default.ts @@ -1,6 +1,6 @@ import {LoggingLevel} from './logger/LoggingLevel'; -export default class Defaults { +export default class Default { static get loggingLevel(): LoggingLevel { return LoggingLevel.Info; } diff --git a/src/api/HealthCheckApi.ts b/src/api/HealthCheckApi.ts index 948e74e..11cb941 100644 --- a/src/api/HealthCheckApi.ts +++ b/src/api/HealthCheckApi.ts @@ -5,14 +5,17 @@ import type express from 'express'; export class HealthCheckApi implements IApiRoute { private readonly _getRoutes: Record; - private readonly _postRoutes: Record = {}; - private readonly _putRoutes: Record = {}; - private readonly _deleteRoutes: Record = {}; + private readonly _postRoutes: Record; + private readonly _putRoutes: Record; + private readonly _deleteRoutes: Record; private readonly _healthCheck: HealthCheck; constructor(healthCheck: HealthCheck) { this._healthCheck = healthCheck; this._getRoutes = this.setupGETRoutes(); + this._postRoutes = this.setupPOSTRoutes(); + this._putRoutes = this.setupPUTRoutes(); + this._deleteRoutes = this.setupDELETERoutes(); } public getGETRoutes(): Record { @@ -33,11 +36,25 @@ export class HealthCheckApi implements IApiRoute { private setupGETRoutes(): Record { return { - '/_health': (req: express.Request, res: express.Response) => { - const health = this._healthCheck.getHealth(); - - res.send(health); - } + '/_health': this.getHealth.bind(this) }; } + + private setupPOSTRoutes(): Record { + return {}; + } + + private setupPUTRoutes(): Record { + return {}; + } + + private setupDELETERoutes(): Record { + return {}; + } + + private getHealth(req: express.Request, res: express.Response): void { + const health = this._healthCheck.getHealth(); + + res.send(health); + } } diff --git a/src/di/ConfigurationReader.ts b/src/di/ConfigurationReader.ts new file mode 100644 index 0000000..758c638 --- /dev/null +++ b/src/di/ConfigurationReader.ts @@ -0,0 +1,104 @@ +import type { IConfigurationLoader } from "./IConfigurationLoader"; +import type { IDependencyManager } from "./IDependencyManger"; +import { NamedType } from "./NamedType"; +import { Type } from "./Type"; +import { DependencyProvider } from "./providers/DependencyProvider"; + + +export type Definition = { + include?: string; + class?: string; + name?: string; + inject?: Array; + instanceType?: string; + lifecycle?: string; + eager?: boolean; +} + +export class ConfigurationReader { + private readonly _dependencyManager: IDependencyManager; + private readonly _configurationLoader: IConfigurationLoader; + + constructor(dependencyManager: IDependencyManager, configurationLoader: IConfigurationLoader) { + this._dependencyManager = dependencyManager; + this._configurationLoader = configurationLoader; + } + + public load(definitions: Definition[] | Definition): void { + const getType = (definition: Definition): Type | NamedType => { + if (typeof definition.class !== 'string' || definition.class === '') { + throw new Error('Definition must define a class (' + JSON.stringify(definition) + ')'); + } + + let type: Type | NamedType; + + if (definition.name !== undefined) { + type = new NamedType(definition.name, definition.class); + } else { + type = new Type(definition.class); + } + + return type; + }; + + if (!Array.isArray(definitions)) { + definitions = [definitions]; + } + + definitions.forEach((definition, idx) => { + if (definition.include) { + const importDefinitions = this._configurationLoader.load(definition.include); + + if (!Array.isArray(importDefinitions)) { + throw new Error('Invalid import: ' + definition.include); + } + + this.load(importDefinitions); + + return; + } + + if (typeof definition.class !== 'string') { + throw new Error('Definition must define a class (' + idx + ', ' + JSON.stringify(definition) + ')'); + } + + const type = getType(definition); + const dependencies: Type[] = []; + + if (definition.inject) { + if (!Array.isArray(definition.inject)) { + throw new Error('Injection must an array (' + idx + ', ' + JSON.stringify(definition) + ')'); + } + + definition.inject.forEach(dependency => { + let depType: Type | NamedType; + + if (typeof dependency === 'object') { + depType = getType(dependency); + } else { + depType = new Type(dependency); + } + + dependencies.push(depType); + }); + } + + const instanceType = definition.instanceType ? new Type(definition.instanceType) : type; + const lifecycle = definition.lifecycle ? definition.lifecycle : 'instance'; + const provider = new DependencyProvider(this._dependencyManager, type, instanceType, lifecycle); + + this._dependencyManager.defineDependencies(type, dependencies); + this._dependencyManager.addProvider(provider); + + if (definition.eager) { + this._dependencyManager.addEagerTypes(type); + } + }); + } + + public toString(): string { + return 'ConfigurationReader'; + } + + +} diff --git a/src/di/FileConfigurationLoader.ts b/src/di/FileConfigurationLoader.ts new file mode 100644 index 0000000..a25d668 --- /dev/null +++ b/src/di/FileConfigurationLoader.ts @@ -0,0 +1,42 @@ +import path from 'node:path'; +import fs from 'node:fs'; +import type {IConfigurationLoader} from './IConfigurationLoader'; + +export default class FileConfigurationLoader implements IConfigurationLoader { + private readonly _baseDirectory: string; + + constructor(baseDirectory: string) { + this._baseDirectory = baseDirectory; + } + + public load(name: string) { + let config = path.join(this._baseDirectory, name); + + if (!fs.existsSync(config) || !fs.lstatSync(config).isFile()) { + config = path.join(this._baseDirectory, name + '.json'); + } + + if (!fs.existsSync(config) || !fs.lstatSync(config).isFile()) { + config = path.join(this._baseDirectory, name, 'di.json'); + } + + if (!fs.existsSync(config) || !fs.lstatSync(config).isFile()) { + throw new Error('DI Configuration "' + name + '" not found'); + } + + try { + const diConfigText = fs.readFileSync(config); + const diConfig = JSON.parse(diConfigText.toString('utf8')); + + return diConfig; + } catch (e) { + console.error(`Failed to load "${name}" from file "${config}": ${e}`); + + throw e; + } + } + + public toString(): string { + return 'FileConfigurationLoader'; + } +} diff --git a/src/di/IConfigurationLoader.ts b/src/di/IConfigurationLoader.ts new file mode 100644 index 0000000..055d814 --- /dev/null +++ b/src/di/IConfigurationLoader.ts @@ -0,0 +1,6 @@ +import type { Definition } from "./ConfigurationReader"; + +export interface IConfigurationLoader { + load(name: string): Definition | Definition[]; + toString(): string; +} diff --git a/src/di/IDependencyManger.ts b/src/di/IDependencyManger.ts new file mode 100644 index 0000000..cf767e3 --- /dev/null +++ b/src/di/IDependencyManger.ts @@ -0,0 +1,13 @@ +import type { IDependencyProvider } from "./providers/IDependencyProvider"; +import type { Type } from "./Type"; + +export interface IDependencyManager { + addProvider(provider: IDependencyProvider): void; + defineDependencies(type: Type, dependencies: Type[]): void; + resolveProvider(type: Type): IDependencyProvider; + instantiateType(type: Type): unknown; + addEagerTypes(type: Type): void; + getEagerTypes(): Type[]; + getTypes(): Type[]; + toString(): string; +} diff --git a/src/di/providers/DependencyProvider.ts b/src/di/providers/DependencyProvider.ts new file mode 100644 index 0000000..cd7e1c3 --- /dev/null +++ b/src/di/providers/DependencyProvider.ts @@ -0,0 +1,53 @@ +import type { IDependencyProvider } from './IDependencyProvider'; +import type { IDependencyManager } from '../IDependencyManger'; +import type { Type } from '../Type'; +import type { NamedType } from '../NamedType'; + +export class DependencyProvider implements IDependencyProvider { + private readonly _dependencyManager: IDependencyManager; + private readonly _type: Type | NamedType; + private readonly _instanceType: Type | NamedType; + private readonly _lifecycle: string; + private _instance: unknown = null; + + constructor( + dependencyManager: IDependencyManager, + type: Type | NamedType, + instanceType: Type | NamedType, + lifecycle: string + ) { + this._dependencyManager = dependencyManager; + this._type = type; + this._instanceType = instanceType; + this._lifecycle = lifecycle; + } + + public canProvide(type: Type | NamedType): boolean { + // Handle NamedType which has equals(), otherwise use equal() from Type + if ('equals' in this._type && typeof this._type.equals === 'function') { + return (this._type as NamedType).equals(type as NamedType); + } + return (this._type as Type).equal(type as Type); + } + + public provide(type: Type | NamedType): unknown { + if (!this.canProvide(type)) { + throw new Error(`Cannot provide type: ${type.toString()}`); + } + + // Singleton lifecycle - reuse the same instance + if (this._lifecycle === 'singleton') { + if (this._instance === null) { + this._instance = this._dependencyManager.instantiateType(this._instanceType); + } + return this._instance; + } + + // Instance lifecycle - create a new instance every time + return this._dependencyManager.instantiateType(this._instanceType); + } + + public toString(): string { + return `DependencyProvider[${this._type.toString()}]`; + } +} diff --git a/src/logger/Threshold.ts b/src/logger/Threshold.ts index c58e09b..b88854e 100644 --- a/src/logger/Threshold.ts +++ b/src/logger/Threshold.ts @@ -1,4 +1,4 @@ -import Defaults from '../Defaults'; +import Defaults from '../Default'; import {Subject} from '../lang/observables'; import {LoggingLevel} from './LoggingLevel';