Lightweight dependency injection container for JavaScript/TypeScript
—
Advanced factory utilities for creating sophisticated dependency construction patterns with caching strategies, conditional instantiation, and performance optimization.
Base factory function signature used throughout the factory system.
/**
* Factory function signature for creating instances
* Receives dependency container for accessing other dependencies
* @param dependencyContainer - Container for resolving dependencies
* @returns Instance of type T
*/
type FactoryFunction<T> = (dependencyContainer: DependencyContainer) => T;Usage Examples:
// Basic factory function
const loggerFactory: FactoryFunction<Logger> = (container) => {
const config = container.resolve<LogConfig>("LogConfig");
return new Logger(config.level, config.format);
};
// Complex factory with multiple dependencies
const databaseFactory: FactoryFunction<Database> = (container) => {
const config = container.resolve<DatabaseConfig>("DatabaseConfig");
const logger = container.resolve<Logger>("Logger");
const connectionPool = container.resolve<ConnectionPool>("ConnectionPool");
const db = new Database(config.connectionString);
db.setLogger(logger);
db.setConnectionPool(connectionPool);
return db;
};
// Register factory
container.register("Database", { useFactory: databaseFactory });Factory wrapper that caches instances globally across all containers and resolution calls.
/**
* Creates factory that caches instance globally
* Instance is shared across all containers and resolution calls
* Perfect for expensive-to-create singletons
* @param factoryFunc - Base factory function to wrap
* @returns Cached factory function
*/
function instanceCachingFactory<T>(factoryFunc: FactoryFunction<T>): FactoryFunction<T>;Usage Examples:
// Expensive service that should be created only once globally
const expensiveServiceFactory: FactoryFunction<ExpensiveService> = (container) => {
console.log("Creating expensive service (this should only happen once)");
const config = container.resolve<ServiceConfig>("ServiceConfig");
return new ExpensiveService(config);
};
// Wrap with global caching
const cachedFactory = instanceCachingFactory(expensiveServiceFactory);
// Register cached factory
container.register("ExpensiveService", { useFactory: cachedFactory });
// Multiple containers share same instance
const container1 = container.createChildContainer();
const container2 = container.createChildContainer();
const service1 = container1.resolve<ExpensiveService>("ExpensiveService");
const service2 = container2.resolve<ExpensiveService>("ExpensiveService");
// service1 === service2 (same instance)
// Configuration service example
const configFactory = instanceCachingFactory<Configuration>((container) => {
console.log("Loading configuration from file...");
return Configuration.loadFromFile("./config.json");
});
container.register("Configuration", { useFactory: configFactory });Factory wrapper that caches instances per container, allowing different instances in child containers while maintaining caching within each container scope.
/**
* Creates factory that caches instance per container
* Each container maintains its own cached instance
* Child containers can have different instances than parents
* @param factoryFunc - Base factory function to wrap
* @returns Per-container cached factory function
*/
function instancePerContainerCachingFactory<T>(factoryFunc: FactoryFunction<T>): FactoryFunction<T>;Usage Examples:
// Request context that should be unique per container (e.g., per HTTP request)
const requestContextFactory: FactoryFunction<RequestContext> = (container) => {
console.log("Creating request context for container");
const requestId = container.resolve<string>("RequestId");
const userId = container.resolve<string>("UserId");
return new RequestContext(requestId, userId);
};
// Wrap with per-container caching
const perContainerCachedFactory = instancePerContainerCachingFactory(requestContextFactory);
container.register("RequestContext", { useFactory: perContainerCachedFactory });
// Parent container has one instance
const parentContext = container.resolve<RequestContext>("RequestContext");
const sameParentContext = container.resolve<RequestContext>("RequestContext");
// parentContext === sameParentContext
// Child containers have their own instances
const childContainer1 = container.createChildContainer();
const childContainer2 = container.createChildContainer();
const child1Context = childContainer1.resolve<RequestContext>("RequestContext");
const child2Context = childContainer2.resolve<RequestContext>("RequestContext");
// child1Context !== child2Context !== parentContext
// Database connection pool per environment
const dbPoolFactory = instancePerContainerCachingFactory<ConnectionPool>((container) => {
const env = container.resolve<string>("Environment");
console.log(`Creating connection pool for ${env}`);
return new ConnectionPool(`db-${env}.example.com`);
});
container.register("ConnectionPool", { useFactory: dbPoolFactory });Advanced factory that conditionally chooses between different constructors based on runtime conditions, with optional caching support.
/**
* Creates factory that chooses constructor based on predicate
* Enables conditional instantiation patterns
* @param predicate - Function determining which constructor to use
* @param trueConstructor - Constructor used when predicate returns true
* @param falseConstructor - Constructor used when predicate returns false
* @param useCaching - Whether to cache instances (default: false)
* @returns Conditional factory function
*/
function predicateAwareClassFactory<T>(
predicate: (dependencyContainer: DependencyContainer) => boolean,
trueConstructor: constructor<T>,
falseConstructor: constructor<T>,
useCaching?: boolean
): FactoryFunction<T>;Usage Examples:
// Environment-based service selection
const isProduction = (container: DependencyContainer) => {
const env = container.resolve<string>("Environment");
return env === "production";
};
const loggerFactory = predicateAwareClassFactory(
isProduction,
ProductionLogger, // Use in production
DevelopmentLogger, // Use in development
true // Cache the instance
);
container.register("Logger", { useFactory: loggerFactory });
// Feature flag based implementation
const hasFeatureFlag = (container: DependencyContainer) => {
const featureFlags = container.resolve<FeatureFlags>("FeatureFlags");
return featureFlags.isEnabled("new-payment-processor");
};
const paymentProcessorFactory = predicateAwareClassFactory(
hasFeatureFlag,
NewPaymentProcessor,
LegacyPaymentProcessor
);
container.register("PaymentProcessor", { useFactory: paymentProcessorFactory });
// Configuration-based repository selection
const usePostgres = (container: DependencyContainer) => {
const dbConfig = container.resolve<DatabaseConfig>("DatabaseConfig");
return dbConfig.type === "postgresql";
};
const userRepositoryFactory = predicateAwareClassFactory(
usePostgres,
PostgresUserRepository,
MongoUserRepository,
true // Cache for performance
);
container.register("UserRepository", { useFactory: userRepositoryFactory });
// A/B testing implementation selection
const isTestGroupA = (container: DependencyContainer) => {
const userId = container.resolve<string>("UserId");
const hash = simpleHash(userId);
return hash % 2 === 0; // 50/50 split
};
const analyticsFactory = predicateAwareClassFactory(
isTestGroupA,
EnhancedAnalytics,
StandardAnalytics
);
container.register("Analytics", { useFactory: analyticsFactory });Examples of combining multiple factory patterns for sophisticated dependency management.
Usage Examples:
// Cached conditional factory
const cachedConditionalLogger = instanceCachingFactory(
predicateAwareClassFactory(
(container) => container.resolve<string>("LogLevel") === "debug",
DebugLogger,
ProductionLogger,
false // Don't double-cache at predicate level
)
);
container.register("Logger", { useFactory: cachedConditionalLogger });
// Per-container cached conditional database
const perContainerDbFactory = instancePerContainerCachingFactory(
predicateAwareClassFactory(
(container) => {
try {
const testMode = container.resolve<boolean>("TestMode");
return testMode;
} catch {
return false;
}
},
InMemoryDatabase,
PostgresDatabase,
false // Caching handled at container level
)
);
container.register("Database", { useFactory: perContainerDbFactory });
// Multi-condition factory
const multiConditionFactory: FactoryFunction<NotificationService> = (container) => {
const env = container.resolve<string>("Environment");
const features = container.resolve<FeatureFlags>("FeatureFlags");
if (env === "test") {
return new MockNotificationService();
}
if (features.isEnabled("push-notifications")) {
return new PushNotificationService(
container.resolve("PushConfig")
);
}
return new EmailNotificationService(
container.resolve("EmailConfig")
);
};
container.register("NotificationService", {
useFactory: instanceCachingFactory(multiConditionFactory)
});// Core factory function type
type FactoryFunction<T> = (dependencyContainer: DependencyContainer) => T;
// Constructor type for predicate-aware factories
type constructor<T> = {new (...args: any[]): T};
// Predicate function type
type PredicateFunction = (dependencyContainer: DependencyContainer) => boolean;
// Factory wrapper function types
type CachingFactoryWrapper<T> = (factoryFunc: FactoryFunction<T>) => FactoryFunction<T>;
type ConditionalFactoryBuilder<T> = (
predicate: PredicateFunction,
trueConstructor: constructor<T>,
falseConstructor: constructor<T>,
useCaching?: boolean
) => FactoryFunction<T>;Install with Tessl CLI
npx tessl i tessl/npm-tsyringe