API Usage Examples
This section provides examples of using the Contexify API to help you better understand how to use these APIs in real applications.
Context Class Examples
Creating Contexts
// Create a root context
const rootContext = new Context('root');
// Create a child context
const childContext = new Context(rootContext, 'child');
Binding and Resolving Values
// Bind a simple value
context.bind('greeting').to('Hello, world!');
// Resolve the value asynchronously
const greeting = await context.get<string>('greeting');
console.log(greeting); // Output: Hello, world!
// Resolve the value synchronously (if possible)
const greetingSync = context.getSync<string>('greeting');
console.log(greetingSync); // Output: Hello, world!
Binding Classes
@injectable()
class GreetingService {
sayHello(name: string) {
return `Hello, ${name}!`;
}
}
// Bind the class
context.bind('services.GreetingService').toClass(GreetingService);
// Resolve the class instance
const greetingService = await context.get<GreetingService>('services.GreetingService');
console.log(greetingService.sayHello('John')); // Output: Hello, John!
Using Dynamic Values
// Bind a dynamic value
context.bind('currentTime').toDynamicValue(() => new Date().toISOString());
// Each resolution gets a new value
const time1 = await context.get<string>('currentTime');
// Wait for some time
const time2 = await context.get<string>('currentTime');
console.log(time1 !== time2); // Output: true
Using Providers
@injectable()
class TimeProvider implements Provider<string> {
value() {
return new Date().toISOString();
}
}
// Bind the provider
context.bind('currentTime').toProvider(TimeProvider);
// Resolve the value
const time = await context.get<string>('currentTime');
console.log(time); // Outputs the current time
Using Aliases
// Bind a value
context.bind('config.apiUrl').to('https://api.example.com');
// Create an alias
context.bind('apiUrl').toAlias('config.apiUrl');
// Resolve through the alias
const apiUrl = await context.get<string>('apiUrl');
console.log(apiUrl); // Output: https://api.example.com
Setting Binding Scope
// Singleton scope (default)
context.bind('singleton').to(new Date()).inScope(BindingScope.SINGLETON);
// Transient scope (new instance for each resolution)
context.bind('transient').toDynamicValue(() => new Date()).inScope(BindingScope.TRANSIENT);
// Context scope (instance shared within the same context)
context.bind('contextScoped').toDynamicValue(() => new Date()).inScope(BindingScope.CONTEXT);
Using Tags
// Add tags
context.bind('service.user').toClass(UserService).tag('service');
context.bind('service.order').toClass(OrderService).tag('service');
context.bind('service.payment').toClass(PaymentService).tag('service');
// Find bindings by tag
const serviceBindings = await context.findByTag('service');
console.log(serviceBindings.length); // Output: 3
Using Context Views
// Create a view that tracks all bindings with the 'service' tag
const serviceView = context.createView<any>(binding => binding.tags.has('service'));
// Get all matching bindings
const bindings = serviceView.bindings();
console.log(bindings.length); // Output: 3
// Resolve all matching values
const services = await serviceView.resolve();
console.log(services.length); // Output: 3
Decorator Examples
@injectable() Decorator
@injectable()
class UserService {
constructor() {
console.log('UserService created');
}
getUsers() {
return ['user1', 'user2', 'user3'];
}
}
// Now UserService can be created through Context
context.bind('services.UserService').toClass(UserService);
const userService = await context.get<UserService>('services.UserService');
@inject() Decorator
@injectable()
class UserRepository {
findAll() {
return ['user1', 'user2', 'user3'];
}
}
@injectable()
class UserService {
constructor(@inject('repositories.UserRepository') private userRepo: UserRepository) {}
getUsers() {
return this.userRepo.findAll();
}
}
// Bind dependencies
context.bind('repositories.UserRepository').toClass(UserRepository);
context.bind('services.UserService').toClass(UserService);
// Resolve UserService (UserRepository is automatically injected)
const userService = await context.get<UserService>('services.UserService');
console.log(userService.getUsers()); // Output: ['user1', 'user2', 'user3']
@inject.tag() Decorator
@injectable()
class Logger {
constructor(private name: string) {}
log(message: string) {
console.log(`[${this.name}] ${message}`);
}
}
@injectable()
class Application {
constructor(@inject.tag('logger') private loggers: Logger[]) {}
run() {
this.loggers.forEach(logger => logger.log('Application started'));
}
}
// Bind multiple services with the same tag
context.bind('loggers.console').toClass(Logger).tag('logger').to(new Logger('console'));
context.bind('loggers.file').toClass(Logger).tag('logger').to(new Logger('file'));
context.bind('app').toClass(Application);
// Resolve the application (all services with the 'logger' tag are automatically injected)
const app = await context.get<Application>('app');
app.run();
// Output:
// [console] Application started
// [file] Application started
@inject.getter() Decorator
@injectable()
class ConfigService {
constructor(@inject.getter('config.database') private getDbConfig: Getter<any>) {}
async connectToDatabase() {
// Get the configuration only when needed
const dbConfig = await this.getDbConfig();
console.log(`Connecting to ${dbConfig.host}:${dbConfig.port}`);
}
}
// Bind configuration
context.bind('config.database').to({
host: 'localhost',
port: 5432,
username: 'admin',
password: 'secret'
});
context.bind('services.ConfigService').toClass(ConfigService);
// Resolve the service
const configService = await context.get<ConfigService>('services.ConfigService');
await configService.connectToDatabase(); // Output: Connecting to localhost:5432
@inject.view() Decorator
@injectable()
class Plugin {
constructor(public name: string) {}
initialize() {
console.log(`Plugin ${this.name} initialized`);
}
}
@injectable()
class PluginManager {
constructor(
@inject.view(binding => binding.tags.has('plugin'))
private pluginView: ContextView<Plugin>
) {}
async initializePlugins() {
const plugins = await this.pluginView.resolve();
plugins.forEach(plugin => plugin.initialize());
}
}
// Bind plugins and manager
context.bind('plugins.logger').to(new Plugin('logger')).tag('plugin');
context.bind('plugins.auth').to(new Plugin('auth')).tag('plugin');
context.bind('plugins.cache').to(new Plugin('cache')).tag('plugin');
context.bind('managers.PluginManager').toClass(PluginManager);
// Resolve manager and initialize plugins
const pluginManager = await context.get<PluginManager>('managers.PluginManager');
await pluginManager.initializePlugins();
// Output:
// Plugin logger initialized
// Plugin auth initialized
// Plugin cache initialized
@config() Decorator
@injectable()
class DatabaseService {
constructor(
@config('database.host') private host: string,
@config('database.port') private port: number
) {}
connect() {
console.log(`Connecting to database at ${this.host}:${this.port}`);
}
}
// Bind configuration
context.configure('services.DatabaseService').to({
database: {
host: 'localhost',
port: 5432
}
});
context.bind('services.DatabaseService').toClass(DatabaseService);
// Resolve the service
const dbService = await context.get<DatabaseService>('services.DatabaseService');
dbService.connect(); // Output: Connecting to database at localhost:5432
@intercept() Decorator
// Define an interceptor
class LoggingInterceptor implements Interceptor {
intercept(invocationCtx: InvocationContext, next: () => ValueOrPromise<any>) {
console.log(`Calling ${invocationCtx.methodName} with args:`, invocationCtx.args);
const start = Date.now();
const result = next();
if (result instanceof Promise) {
return result.then(value => {
console.log(`${invocationCtx.methodName} completed in ${Date.now() - start}ms`);
return value;
});
}
console.log(`${invocationCtx.methodName} completed in ${Date.now() - start}ms`);
return result;
}
}
// Use the interceptor
@injectable()
class UserService {
@intercept(new LoggingInterceptor())
async findUsers() {
// Simulate database query
await new Promise(resolve => setTimeout(resolve, 100));
return ['user1', 'user2', 'user3'];
}
}
// Bind the service
context.bind('services.UserService').toClass(UserService);
// Resolve the service and call the method
const userService = await context.get<UserService>('services.UserService');
const users = await userService.findUsers();
// Output:
// Calling findUsers with args: []
// findUsers completed in 100ms
Interface Examples
Provider Interface
@injectable()
class RandomNumberProvider implements Provider<number> {
value() {
return Math.random();
}
}
// Bind the provider
context.bind('random').toProvider(RandomNumberProvider);
// Resolve values
const random1 = await context.get<number>('random');
const random2 = await context.get<number>('random');
console.log(random1 !== random2); // Output: true
Interceptor Interface
// Define a caching interceptor
class CachingInterceptor implements Interceptor {
private cache = new Map<string, any>();
intercept(invocationCtx: InvocationContext, next: () => ValueOrPromise<any>) {
const cacheKey = `${invocationCtx.targetClass.name}.${invocationCtx.methodName}(${JSON.stringify(invocationCtx.args)})`;
if (this.cache.has(cacheKey)) {
console.log(`Cache hit for ${cacheKey}`);
return this.cache.get(cacheKey);
}
console.log(`Cache miss for ${cacheKey}`);
const result = next();
if (result instanceof Promise) {
return result.then(value => {
this.cache.set(cacheKey, value);
return value;
});
}
this.cache.set(cacheKey, result);
return result;
}
}
// Use the caching interceptor
@injectable()
class ExpensiveService {
@intercept(new CachingInterceptor())
async computeExpensiveValue(input: string) {
console.log(`Computing expensive value for ${input}`);
await new Promise(resolve => setTimeout(resolve, 500));
return `Result for ${input}`;
}
}
// Bind the service
context.bind('services.ExpensiveService').toClass(ExpensiveService);
// Resolve the service and call the method
const expensiveService = await context.get<ExpensiveService>('services.ExpensiveService');
// First call (cache miss)
let result1 = await expensiveService.computeExpensiveValue('test');
console.log(result1); // Output: Result for test
// Second call (cache hit)
let result2 = await expensiveService.computeExpensiveValue('test');
console.log(result2); // Output: Result for test
ContextObserver Interface
// Define an observer
class BindingObserver implements ContextObserver {
// Only observe bindings with the 'service' tag
filter = (binding: Readonly<Binding<unknown>>) => binding.tags.has('service');
observe(eventType: string, binding: Readonly<Binding<unknown>>, context: Context) {
console.log(`Event: ${eventType}, Binding: ${binding.key}`);
}
}
// Create a context and subscribe the observer
const context = new Context('app');
context.subscribe(new BindingObserver());
// Add a binding (will trigger the observer)
context.bind('services.UserService').toClass(UserService).tag('service');
// Output: Event: bind, Binding: services.UserService
// Resolve the binding (will trigger the observer)
await context.get('services.UserService');
// Output: Event: resolve:before, Binding: services.UserService
// Output: Event: resolve:after, Binding: services.UserService
More Examples
For more examples, see the Examples section or check out the example code in the GitHub repository.