libs fully refactored

This commit is contained in:
Boki 2025-06-21 19:00:10 -04:00
parent 63baeaec70
commit 1b34da9a69
10 changed files with 181 additions and 21 deletions

View file

@ -30,11 +30,38 @@ export class ServiceContainer implements ServiceResolver {
this.logger.debug('Service registered', { name: registration.name, singleton: registration.singleton });
}
resolve<T>(name: string, options?: any): T {
const instance = this.resolveAsync<T>(name, options);
resolve<T>(name: string, _options?: any): T {
// Check scoped instances first
if (this.scopedInstances.has(name)) {
return this.scopedInstances.get(name);
}
// Check singleton instances
if (this.instances.has(name)) {
return this.instances.get(name);
}
// Get registration from this container or parent
const registration = this.getRegistration(name);
if (!registration) {
throw new Error(`Service ${name} not registered`);
}
// Create instance synchronously
const instance = registration.factory();
// Check if factory returned a promise
if (instance instanceof Promise) {
throw new Error(`Service ${name} is async. Use resolveAsync() instead.`);
}
// Store based on singleton flag
if (registration.singleton) {
this.instances.set(name, instance);
} else {
this.scopedInstances.set(name, instance);
}
return instance as T;
}

View file

@ -0,0 +1,147 @@
/**
* Test DI library functionality
*/
import { test, expect, describe } from 'bun:test';
import { ServiceContainer, ConnectionFactory, OperationContext, PoolSizeCalculator } from '../src/index';
describe('DI Library', () => {
test('ServiceContainer - sync resolution', () => {
const container = new ServiceContainer('test');
container.register({
name: 'testService',
factory: () => ({ value: 'test' }),
singleton: true,
});
const service = container.resolve<{ value: string }>('testService');
expect(service.value).toBe('test');
});
test('ServiceContainer - async resolution', async () => {
const container = new ServiceContainer('test');
container.register({
name: 'asyncService',
factory: async () => ({ value: 'async-test' }),
singleton: true,
});
const service = await container.resolveAsync<{ value: string }>('asyncService');
expect(service.value).toBe('async-test');
});
test('ServiceContainer - scoped container', () => {
const container = new ServiceContainer('test');
container.register({
name: 'testService',
factory: () => ({ value: 'test' }),
singleton: true,
});
const scopedContainer = container.createScope();
const service = scopedContainer.resolve<{ value: string }>('testService');
expect(service.value).toBe('test');
});
test('ServiceContainer - error on unregistered service', () => {
const container = new ServiceContainer('test');
expect(() => {
container.resolve('nonexistent');
}).toThrow('Service nonexistent not registered');
});
test('ServiceContainer - async service throws error on sync resolve', () => {
const container = new ServiceContainer('test');
container.register({
name: 'asyncService',
factory: async () => ({ value: 'async' }),
singleton: true,
});
expect(() => {
container.resolve('asyncService');
}).toThrow('Service asyncService is async. Use resolveAsync() instead.');
});
test('ServiceContainer - disposal', async () => {
const container = new ServiceContainer('test');
let disposed = false;
container.register({
name: 'disposableService',
factory: () => ({ value: 'test' }),
singleton: true,
dispose: async () => {
disposed = true;
},
});
// Create instance
container.resolve('disposableService');
// Dispose container
await container.dispose();
expect(disposed).toBe(true);
});
test('ConnectionFactory - creation', () => {
const factory = new ConnectionFactory({
service: 'test',
environment: 'development',
});
expect(factory).toBeDefined();
expect(factory.listPools()).toEqual([]);
});
test('OperationContext - creation', () => {
const container = new ServiceContainer('test');
const context = OperationContext.create('test-handler', 'test-operation', {
container,
});
expect(context).toBeDefined();
expect(context.logger).toBeDefined();
});
test('OperationContext - child context', () => {
const context = OperationContext.create('test-handler', 'test-operation');
const child = context.createChild('child-operation');
expect(child).toBeDefined();
expect(child.logger).toBeDefined();
});
test('PoolSizeCalculator - service defaults', () => {
const poolSize = PoolSizeCalculator.calculate('data-ingestion');
expect(poolSize).toEqual({ min: 5, max: 50, idle: 10 });
});
test('PoolSizeCalculator - handler defaults', () => {
const poolSize = PoolSizeCalculator.calculate('unknown-service', 'batch-import');
expect(poolSize).toEqual({ min: 10, max: 100, idle: 20 });
});
test('PoolSizeCalculator - fallback defaults', () => {
const poolSize = PoolSizeCalculator.calculate('unknown-service', 'unknown-handler');
expect(poolSize).toEqual({ min: 2, max: 10, idle: 3 });
});
test('PoolSizeCalculator - custom config', () => {
const poolSize = PoolSizeCalculator.calculate('test-service', undefined, {
minConnections: 5,
maxConnections: 15,
});
expect(poolSize).toEqual({ min: 5, max: 15, idle: 5 });
});
test('PoolSizeCalculator - optimal size calculation', () => {
const optimalSize = PoolSizeCalculator.getOptimalPoolSize(10, 100, 50);
expect(optimalSize).toBeGreaterThan(0);
expect(typeof optimalSize).toBe('number');
});
});

View file

@ -1,15 +1,15 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"rootDir": "./",
"rootDir": "./src",
"outDir": "./dist",
"composite": true,
"declaration": true,
"declarationMap": true,
"types": ["node", "bun-types"]
},
"include": ["./**/*.ts"],
"exclude": ["node_modules", "dist"],
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist", "test"],
"references": [
{ "path": "../config" },
{ "path": "../logger" }