Skip to content

Treasure Chest Service Container in TypeScript

License

Notifications You must be signed in to change notification settings

khapu2906/treasure-chest

Repository files navigation

Treasure Chest

npm version CI License: DIB TypeScript Node.js

A lightweight and powerful TypeScript dependency injection container for managing service dependencies with support for transient, singleton, scoped, lazy, conditional, and contextual bindings.

Features

Core Features

  • Lightweight: Zero runtime dependencies, minimal footprint
  • TypeScript First: Full TypeScript support with complete type safety
  • Flexible Lifecycle: Transient, singleton, and scoped bindings
  • Lazy Loading: Deferred initialization for performance optimization
  • Container Hierarchy: Child containers with inheritance
  • Nested Dependencies: Automatic resolution of dependency chains
  • Circular Detection: Automatic circular dependency detection
  • Conditional & Contextual: Environment and context-aware bindings
  • Alias Support: Multiple names for the same service

Performance (v1.2.0)

  • Map-based Storage: O(1) lookup complexity (10-100x faster)
  • Binding Cache: Memoized results for non-conditional bindings
  • High-Speed Resolution: 3.67M ops/sec (worst case), 24.7M ops/sec (cached)
  • Auto-Dispose: IDisposable interface with zero overhead
  • Modular Architecture: Better tree-shaking and code splitting

Developer Experience

  • Intuitive API: Fluent API with comprehensive JSDoc
  • Well Tested: 62+ test cases with full coverage
  • Dual Module Support: Both CommonJS and ES Modules (ESM)
  • Type Safe: Full TypeScript declarations
  • Production Ready: Battle-tested patterns
  • Modular Codebase: Clean separation of concerns

Installation

npm install @khapu2906/treasure-chest

Quick Start

import { Container } from '@khapu2906/treasure-chest';

const container = new Container();

// Register a service
container.bind('logger', () => ({
  log: (message: string) => console.log(`[LOG] ${message}`),
}));

// Use the service
const logger = container.resolve('logger');
logger.log('Hello World!');

Key Features

1. Bind (Transient)

Creates a new instance on each resolve:

container.bind('service', () => ({ id: Math.random() }));

const instance1 = container.resolve('service');
const instance2 = container.resolve('service');
console.log(instance1.id !== instance2.id); // true

2. Singleton

Creates only one instance:

container.singleton('cache', () => ({ data: {} }));

const cache1 = container.resolve('cache');
const cache2 = container.resolve('cache');
console.log(cache1 === cache2); // true

3. Alias

Create aliases for services:

container.singleton('logger', () => ({ log: console.log }));
container.alias('appLogger', 'logger');

const logger = container.resolve('appLogger'); // Same as 'logger'

4. Conditional Binding

Register services based on conditions:

const env = 'production';

container.bind(
  'storage',
  () => ({ type: 'local' }),
  () => env === 'development'
);
container.bind(
  'storage',
  () => ({ type: 's3' }),
  () => env === 'production'
);

const storage = container.resolve('storage'); // { type: 's3' }

5. Contextual Binding

Register different implementations for different contexts:

container
  .when('UserService')
  .needs('repository')
  .give(() => new UserRepository());
container
  .when('AdminService')
  .needs('repository')
  .give(() => new AdminRepository());

const userRepo = container.resolve('repository', 'UserService'); // UserRepository
const adminRepo = container.resolve('repository', 'AdminService'); // AdminRepository

6. Container Composition (NEW in v1.3.0)

Mix services from different domains without inheritance:

import { Container } from '@khapu2906/treasure-chest';

// Create domain-specific containers
const infra = new Container();
infra.singleton('db', () => new Database());

const services = new Container();
services.singleton('userService', () => new UserService());

const controllers = new Container();
controllers.singleton('userController', () => new UserController());

// Compose them together
const app = Container.compose([infra, services, controllers]);

// All services available in one container
const db = app.resolve('db');
const userService = app.resolve('userService');
const userController = app.resolve('userController');

7. Scoped Lifecycle (NEW in v1.2.0)

Per-scope instances with automatic cleanup:

import { Container } from '@khapu2906/treasure-chest';

const container = new Container();

// Register scoped service with cleanup
container.scoped(
  'dbConnection',
  () => new DbConnection(),
  function () {
    this.close();
  } // Cleanup function
);

// Create a scope (e.g., per HTTP request)
const scope = container.createScope();

const conn1 = container.resolve('dbConnection');
const conn2 = container.resolve('dbConnection');
console.log(conn1 === conn2); // true - same instance within scope

// Cleanup when done
await scope.dispose(); // Calls cleanup functions

7. Lazy Loading (NEW in v1.2.0)

Defer expensive initialization:

import { Container, Lazy } from '@khapu2906/treasure-chest';

const container = new Container();

// Register lazy service
container.lazy('heavyService', () => new HeavyMLModel());

// Resolve as Lazy wrapper
const lazy = container.resolve<Lazy<HeavyMLModel>>('heavyService');

console.log(lazy.isInitialized); // false - not loaded yet

// Access when needed
const model = lazy.value; // NOW it's initialized

console.log(lazy.isInitialized); // true

8. Child Containers (NEW in v1.2.0)

Container hierarchy for multi-tenant apps:

const parent = new Container();
parent.singleton('config', () => new Config());

// Create child container
const tenant1 = parent.createChild();
tenant1.singleton('logger', () => new TenantLogger('tenant1'));

const tenant2 = parent.createChild();
tenant2.singleton('logger', () => new TenantLogger('tenant2'));

// Each tenant has its own logger but shares config
tenant1.resolve('config'); // From parent
tenant1.resolve('logger'); // From tenant1

9. Circular Dependency Detection (NEW in v1.2.0)

Automatic detection with clear error messages:

container.bind('A', (c) => {
  const b = c.resolve('B');
  return { name: 'A', dep: b };
});

container.bind('B', (c) => {
  const a = c.resolve('A'); // Circular!
  return { name: 'B', dep: a };
});

try {
  container.resolve('A');
} catch (error) {
  console.log(error.message);
  // "Circular dependency detected: A -> B -> A"
}

10. IDisposable Interface & Auto-Dispose (NEW in v1.2.0)

Automatic cleanup detection with zero overhead:

import { Container, IDisposable } from '@khapu2906/treasure-chest';

// Implement IDisposable for auto-cleanup
class DbConnection implements IDisposable {
  async connect() {
    console.log('Connected');
  }

  async dispose() {
    console.log('Disconnected');
  }
}

const container = new Container();

// Auto-detection: No need to pass dispose function!
container.scoped('db', () => new DbConnection());

// Using withScope() - C# using pattern
await container.withScope(async (scope) => {
  const db = container.resolve<DbConnection>('db');
  await db.connect();
  // Auto-disposed when scope exits
});
// Dispose called automatically here

11. Web Framework Integration Pattern

Pattern for automatic per-request scope management. See examples/10-middleware.ts for full implementation.

Note: Middleware helpers are not exported from the core package to keep it framework-agnostic. For production use, consider creating separate adapter packages.

Pattern Example:

import { Container } from '@khapu2906/treasure-chest';

const container = new Container();
container.scoped('db', () => new DbConnection());

// Express pattern
function expressScope(container: Container) {
  return (req, res, next) => {
    const scope = container.createScope();
    req.scope = scope;
    res.on('finish', () => scope.dispose());
    next();
  };
}

app.use(expressScope(container));

Supported Patterns:

  • ✅ Express middleware
  • ✅ Fastify hooks (onRequest/onResponse)
  • ✅ Koa middleware

See examples/10-middleware.ts for complete implementations.

API Reference

Container Class

Lifecycle Methods

bind<T>(key: ServiceKey, factory: FactoryFn<T>, condition?: ConditionFn)

  • Register a transient service (new instance each time)

singleton<T>(key: ServiceKey, factory: FactoryFn<T>, condition?: ConditionFn)

  • Register a singleton service (single instance shared)

scoped<T>(key: ServiceKey, factory: FactoryFn<T>, dispose?: DisposeFn) ⭐ NEW

  • Register a scoped service (instance per scope)
  • Optional cleanup function called on dispose

lazy<T>(key: ServiceKey, factory: FactoryFn<T>, lifecycle?: Lifecycle) ⭐ NEW

  • Register a lazy service (deferred initialization)
  • Returns Lazy<T> wrapper
  • Default lifecycle: singleton

Resolution Methods

resolve<T>(key: ServiceKey, context?: ServiceKey): T

  • Resolve a service from the container
  • Supports circular dependency detection

has(key: ServiceKey): boolean ⭐ NEW

  • Check if a binding exists for a key

keys(): ServiceKey[] ⭐ NEW

  • Get all registered service keys

Contextual Binding

when(context: ServiceKey).needs(key: ServiceKey).give(factory: FactoryFn)

  • Create context-specific bindings
  • Fluent API for readability

alias(aliasKey: ServiceKey, originalKey: ServiceKey)

  • Create an alias for a service

Container Composition

Container.compose(containers: Container[]): Container ⭐ NEW v1.3.0

  • Combine multiple containers into one
  • Allows mixing services from different domains
  • First-wins conflict resolution
  • Maintains container isolation

Container Hierarchy

createChild(): Container ⭐ NEW

  • Create a child container that inherits from parent
  • Children can override parent bindings

createScope(): Scope ⭐ NEW

  • Create a new scope for scoped instances
  • Returns a Scope object

withScope<T>(callback: (scope: Scope) => T | Promise<T>): Promise<T> ⭐ NEW v1.2.0

  • Execute callback with auto-managed scope
  • C#-style using statement pattern
  • Automatic disposal even on errors
  • Exception-safe cleanup

Cleanup

reset()

  • Clear all bindings and instances

dispose(): Promise<void> ⭐ NEW

  • Dispose container and all scoped instances
  • Calls cleanup functions

Exported Types

import {
  Container,
  Lazy,
  Scope,
  Lifecycle,
  IDisposable,
  DisposeFn,
  ServiceKey,
} from '@khapu2906/treasure-chest';

// ServiceKey: 'string' | 'Symbol' | 'Constructor' ⭐ NEW v1.4.1
// Lifecycle: 'transient' | 'singleton' | 'scoped'
// Lazy<T>: Wrapper with .value and .isInitialized
// Scope: Scope management with .dispose()
// IDisposable: Interface for auto-cleanup detection ⭐ NEW v1.2.0
// DisposeFn: Type for cleanup functions

Global Container

import { Container } from '@khapu2906/treasure-chest';
const container = new Container();
// Use global container
container.bind('config', () => ({ env: 'production' }));

Real-world Example

import { Container } from '@khapu2906/treasure-chest';

class Database {
  connect() {
    return { status: 'connected' };
  }
}

class UserRepository {
  constructor(private db: Database) {}

  findUser(id: number) {
    return { id, name: 'John Doe' };
  }
}

class UserService {
  constructor(private repo: UserRepository) {}

  getUser(id: number) {
    return this.repo.findUser(id);
  }
}

// Setup container
const container = new Container();

container.singleton('db', () => new Database());
container.singleton('userRepo', (c) => new UserRepository(c.resolve('db')));
container.singleton(
  'userService',
  (c) => new UserService(c.resolve('userRepo'))
);

// Contextual binding
container
  .when('AdminService')
  .needs('userRepo')
  .give((c) => ({
    ...c.resolve('userRepo'),
    adminAccess: true,
  }));

// Usage
const userService = container.resolve<UserService>('userService');
console.log(userService.getUser(1)); // { id: 1, name: 'John Doe' }

Examples

Check out the examples directory for comprehensive real-world usage patterns:

Basic Features

  1. Basic Usage: Transient and singleton bindings
  2. Dependency Injection: Nested dependency resolution
  3. Conditional Binding: Environment-based configuration
  4. Contextual Binding: Context-aware service resolution
  5. Alias: Multiple names for the same service

Advanced Features (v1.2.0)

  1. Scoped Lifecycle: Per-request services with cleanup
  2. Lazy Loading: Performance optimization with deferred init
  3. Child Containers: Multi-tenant and plugin systems
  4. Circular Dependency: Detection and best practices
  5. Container Composition: Mix services from different domains

Performance & Integration (v1.2.0) ⭐ NEW

  1. Web Framework Middleware: Express, Fastify, Koa integration
    • Auto-scoping per HTTP request
    • IDisposable auto-detection
    • withScope() pattern demos

Each example includes detailed comments and runnable code.

Development

Install Dependencies

npm install

Running Tests

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Run tests with coverage
npm run test:coverage

# Run tests with UI
npm run test:ui

Linting and Formatting

# Run ESLint
npm run lint

# Fix linting issues
npm run lint:fix

# Format code with Prettier
npm run format

# Check formatting
npm run format:check

Building

# Build for production (generates CJS, ESM, and type declarations)
npm run build

# Clean build artifacts
npm run clean

Running Benchmarks ⭐ NEW v1.2.0

# Run performance benchmarks
npm run benchmark

# Results auto-saved to:
# - benchmarks/results/run-YYYY-MM-DD-HH-MM-SS.json (timestamped)
# - benchmarks/results/history.jsonl (append-only log)
# - benchmarks/results/LATEST.md (human-readable report)

See benchmarks/README.md for detailed documentation on:

  • Benchmark categories
  • Result formats
  • Comparing performance over time
  • Expected performance metrics

Running Examples

# Run a specific example
npm run example:01  # Basic usage
npm run example:02  # Dependency injection
npm run example:10  # Middleware (v1.2.0)

# Run all examples sequentially
npm run examples

Contributing

Contributions are welcome! Please read the CONTRIBUTING.md for details on our code of conduct and development process.

Changelog

See CHANGELOG.md for a list of changes and version history.

License

This project is licensed under the DIB License - see the LICENSE file for details.

Author

Kent Phung

Support

If you encounter any issues or have questions:


Made with ❤️ by Kent Phung

About

Treasure Chest Service Container in TypeScript

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

Packages

No packages published