CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-loopback--core

Define and implement core constructs such as Application and Component for LoopBack 4 framework

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

context-api.mddocs/

Context & Dependency Injection

LoopBack Core re-exports the complete @loopback/context API, providing a comprehensive IoC (Inversion of Control) container with hierarchical contexts, type-safe dependency injection, binding management, and interceptor support. This system forms the foundation for all dependency management in LoopBack applications.

Capabilities

Context Container

The main IoC container that manages bindings and provides dependency injection capabilities.

/**
 * Context provides an implementation of Inversion of Control (IoC) container
 */
class Context {
  /** Create a binding with the given key */
  bind<ValueType = BoundValue>(key: BindingAddress<ValueType>): Binding<ValueType>;
  
  /** Add a binding to the context */
  add<ValueType = BoundValue>(binding: Binding<ValueType>): Context;
  
  /** Get a value from the context by key */
  get<ValueType>(keyWithPath: BindingAddress<ValueType>): Promise<ValueType>;
  
  /** Get a value synchronously from the context by key */
  getSync<ValueType>(keyWithPath: BindingAddress<ValueType>): ValueType;
  
  /** Check if a binding exists for the given key */
  contains<ValueType>(key: BindingAddress<ValueType>): boolean;
  
  /** Find bindings matching a filter */
  find<ValueType = BoundValue>(filter?: BindingFilter): Readonly<Binding<ValueType>>[];
  
  /** Create a child context */
  createChild(name?: string): Context;
}

Usage Examples:

import { Context, BindingKey } from "@loopback/core";

// Create context
const context = new Context('my-app');

// Create typed binding key
const DB_CONFIG = BindingKey.create<DatabaseConfig>('config.database');

// Bind values
context.bind(DB_CONFIG).to({
  host: 'localhost',
  port: 5432,
  database: 'myapp'
});

// Bind classes
context.bind('services.logger').toClass(LoggerService);

// Bind providers
context.bind('services.config').toProvider(ConfigProvider);

// Get values
const dbConfig = await context.get(DB_CONFIG);
const logger = await context.get<LoggerService>('services.logger');

// Synchronous get
const syncLogger = context.getSync<LoggerService>('services.logger');

// Check existence
if (context.contains('services.cache')) {
  const cache = await context.get('services.cache');
}

// Child contexts
const childContext = context.createChild('request-scope');
childContext.bind('request.id').to('req-123');

Binding System

Core binding system that associates keys with values, classes, or providers.

/**
 * A binding represents a mapping from a key to a value
 */
class Binding<T = BoundValue> {
  /** The binding key */
  readonly key: string;
  
  /** The binding scope */
  scope: BindingScope;
  
  /** Bind to a constant value */
  to(value: T): Binding<T>;
  
  /** Bind to a class constructor */
  toClass<C>(ctor: Constructor<C>): Binding<C>;
  
  /** Bind to a provider */
  toProvider<P>(providerClass: Constructor<Provider<P>>): Binding<P>;
  
  /** Create an alias binding */
  toAlias(keyWithPath: BindingAddress<T>): Binding<T>;
  
  /** Set the binding scope */
  inScope(scope: BindingScope): Binding<T>;
  
  /** Add tags to the binding */
  tag(...tags: (string | object)[]): Binding<T>;
  
  /** Apply a binding template */
  apply(templateFn: BindingTemplate<T>): Binding<T>;
}

enum BindingScope {
  TRANSIENT = 'Transient',
  CONTEXT = 'Context', 
  SINGLETON = 'Singleton',
  REQUEST = 'Request',
  SESSION = 'Session',
  APPLICATION = 'Application'
}

Usage Examples:

import { Context, Binding, BindingScope, BindingKey } from "@loopback/core";

const context = new Context();

// Constant binding
context.bind('config.port').to(3000);

// Class binding with scope
context.bind('services.logger')
  .toClass(LoggerService)
  .inScope(BindingScope.SINGLETON);

// Provider binding
context.bind('services.database')
  .toProvider(DatabaseProvider)
  .inScope(BindingScope.SINGLETON);

// Alias binding
context.bind('logger').toAlias('services.logger');

// Tagged binding
context.bind('repositories.user')
  .toClass(UserRepository)
  .tag('repository', {entity: 'User'});

// Chained configuration
const binding = context.bind('services.email')
  .toClass(EmailService)
  .inScope(BindingScope.SINGLETON)
  .tag('service', 'communication')
  .apply(binding => {
    // Custom configuration
    binding.tag('priority', 'high');
  });

Dependency Injection

Core injection decorators and utilities for constructor, property, and method injection.

/**
 * A decorator to annotate method arguments for automatic injection by LoopBack's IoC container
 */
function inject(
  bindingSelector?: BindingSelector<unknown>,
  metadata?: InjectionMetadata
): PropertyDecorator & ParameterDecorator;

namespace inject {
  /** Inject a binding object */
  function binding(
    selector?: BindingSelector<unknown>,
    metadata?: InjectionMetadata
  ): PropertyDecorator & ParameterDecorator;
  
  /** Inject the current context */
  function context(): PropertyDecorator & ParameterDecorator;
  
  /** Inject a getter function */
  function getter(
    bindingSelector: BindingSelector<unknown>,
    metadata?: InjectionMetadata
  ): PropertyDecorator & ParameterDecorator;
  
  /** Inject a setter function */
  function setter(
    bindingSelector: BindingSelector<unknown>
  ): PropertyDecorator & ParameterDecorator;
  
  /** Inject values by tag */
  function tag(
    tagName: string | RegExp,
    metadata?: InjectionMetadata
  ): PropertyDecorator & ParameterDecorator;
  
  /** Inject a context view */
  function view(
    filter: BindingFilter,
    metadata?: InjectionMetadata
  ): PropertyDecorator & ParameterDecorator;
}

Usage Examples:

import { inject, Context, BindingKey } from "@loopback/core";

// Service class with constructor injection
class OrderService {
  constructor(
    @inject('repositories.order') private orderRepo: OrderRepository,
    @inject('services.payment') private paymentService: PaymentService,
    @inject.context() private context: Context
  ) {}
  
  @inject('services.logger')
  private logger: LoggerService;
  
  async createOrder(orderData: OrderData): Promise<Order> {
    this.logger.log('Creating order');
    
    const order = await this.orderRepo.create(orderData);
    await this.paymentService.processPayment(order.total);
    
    return order;
  }
  
  // Getter injection for lazy loading
  @inject.getter('services.email')
  private getEmailService: Getter<EmailService>;
  
  async sendOrderConfirmation(orderId: string): Promise<void> {
    const emailService = await this.getEmailService();
    await emailService.sendOrderConfirmation(orderId);
  }
}

// Controller with various injection types
class UserController {
  constructor(
    @inject('repositories.user') private userRepo: UserRepository,
    @inject.tag('validator') private validators: Validator[]
  ) {}
  
  // Context view injection
  @inject.view(filterByTag('middleware'))
  private middlewareView: ContextView<Middleware>;
  
  async createUser(userData: UserData): Promise<User> {
    // Use injected dependencies
    for (const validator of this.validators) {
      await validator.validate(userData);
    }
    
    return this.userRepo.create(userData);
  }
}

Configuration Injection

Specialized injection for configuration values with support for nested properties.

/**
 * Decorator for configuration injection
 */
function config(
  propertyPath?: ConfigurationPropertyPath,
  metadata?: ConfigInjectionMetadata
): PropertyDecorator & ParameterDecorator;

namespace config {
  /** Inject configuration as a context view */
  function view(
    propertyPath?: ConfigurationPropertyPath,
    metadata?: ConfigInjectionMetadata
  ): PropertyDecorator & ParameterDecorator;
}

Usage Examples:

import { config, Application } from "@loopback/core";

interface DatabaseConfig {
  host: string;
  port: number;
  database: string;
  ssl: boolean;
}

interface AppConfig {
  database: DatabaseConfig;
  redis: {
    url: string;
    ttl: number;
  };
}

class DatabaseService {
  constructor(
    @config('database') private dbConfig: DatabaseConfig,
    @config('database.host') private host: string,
    @config() private fullConfig: AppConfig // Inject entire config
  ) {}
  
  async connect(): Promise<void> {
    console.log(`Connecting to ${this.host}:${this.dbConfig.port}`);
    // Connection logic
  }
}

// Application setup
const app = new Application();

app.bind('config').to({
  database: {
    host: 'localhost',
    port: 5432,
    database: 'myapp',
    ssl: false
  },
  redis: {
    url: 'redis://localhost:6379',
    ttl: 3600
  }
});

app.service(DatabaseService);

Binding Keys

Type-safe binding keys with deep property access support.

/**
 * Binding key is used to uniquely identify a binding in the context
 */
class BindingKey<ValueType = unknown> {
  /** Create a new binding key */
  static create<T>(key: string): BindingKey<T>;
  
  /** Get a property path from the binding key */
  propertyPath<P>(path: string): BindingKey<P>;
  
  /** The key string */
  readonly key: string;
}

type BindingAddress<T = unknown> = string | BindingKey<T>;

Usage Examples:

import { BindingKey, Context } from "@loopback/core";

// Type-safe binding keys
const DATABASE_CONFIG = BindingKey.create<DatabaseConfig>('config.database');
const LOGGER_SERVICE = BindingKey.create<LoggerService>('services.logger');
const USER_REPOSITORY = BindingKey.create<UserRepository>('repositories.user');

// Property path access
const DB_HOST = DATABASE_CONFIG.propertyPath<string>('host');
const DB_PORT = DATABASE_CONFIG.propertyPath<number>('port');

const context = new Context();

// Bind with typed keys
context.bind(DATABASE_CONFIG).to({
  host: 'localhost',
  port: 5432,
  database: 'myapp',
  ssl: false
});

context.bind(LOGGER_SERVICE).toClass(LoggerService);

// Type-safe access
const dbConfig: DatabaseConfig = await context.get(DATABASE_CONFIG);
const logger: LoggerService = await context.get(LOGGER_SERVICE);

// Property path access
const host: string = await context.get(DB_HOST); // 'localhost'
const port: number = await context.get(DB_PORT); // 5432

// Use in injection
class MyService {
  constructor(
    @inject(DATABASE_CONFIG) private dbConfig: DatabaseConfig,
    @inject(DB_HOST) private host: string
  ) {}
}

Context Views

Dynamic collections of bindings that update automatically as the context changes.

/**
 * ContextView provides a view for a given context chain to maintain a live list
 * of matching bindings
 */
class ContextView<T = unknown> {
  constructor(
    context: Context,
    filter: BindingFilter,
    comparator?: BindingComparator
  );
  
  /** Get all matching values */
  values(): Promise<T[]>;
  
  /** Get all matching bindings */
  bindings: Readonly<Binding<T>>[];
  
  /** Listen for binding events */
  on(eventName: string, listener: ContextViewEventListener): void;
  
  /** Create a getter function for the view */
  asGetter(): Getter<T[]>;
}

type ContextViewEventListener<T = unknown> = (event: ContextViewEvent<T>) => void;

Usage Examples:

import { 
  Context, 
  ContextView, 
  filterByTag,
  ContextViewEvent 
} from "@loopback/core";

interface Plugin {
  name: string;
  initialize(): Promise<void>;
}

class PluginManager {
  private pluginView: ContextView<Plugin>;
  
  constructor(context: Context) {
    // Create view for all plugins
    this.pluginView = new ContextView(
      context,
      filterByTag('plugin')
    );
    
    // Listen for plugin additions/removals
    this.pluginView.on('bind', this.onPluginAdded.bind(this));
    this.pluginView.on('unbind', this.onPluginRemoved.bind(this));
  }
  
  private async onPluginAdded(event: ContextViewEvent<Plugin>): Promise<void> {
    const plugin = await event.binding.getValue(event.context);
    console.log(`Plugin added: ${plugin.name}`);
    await plugin.initialize();
  }
  
  private onPluginRemoved(event: ContextViewEvent<Plugin>): void {
    console.log(`Plugin removed: ${event.binding.key}`);
  }
  
  async getAllPlugins(): Promise<Plugin[]> {
    return this.pluginView.values();
  }
  
  getPluginCount(): number {
    return this.pluginView.bindings.length;
  }
}

// Usage
const context = new Context();
const pluginManager = new PluginManager(context);

// Add plugins
context.bind('plugins.auth')
  .toClass(AuthPlugin)
  .tag('plugin');

context.bind('plugins.logging')
  .toClass(LoggingPlugin)  
  .tag('plugin');

Interceptors

Aspect-oriented programming support with method interceptors and cross-cutting concerns.

/**
 * Interceptor function to intercept method invocations
 */
type Interceptor = (
  context: InvocationContext,
  next: () => ValueOrPromise<InvocationResult>
) => ValueOrPromise<InvocationResult>;

/**
 * Decorator to apply interceptors to methods or classes
 */
function intercept(...interceptorOrKeys: InterceptorOrKey[]): MethodDecorator & ClassDecorator;

/**
 * Decorator to register global interceptors
 */
function globalInterceptor(group?: string, ...specs: BindingSpec[]): ClassDecorator;

/**
 * Invoke a method with interceptors
 */
function invokeMethodWithInterceptors(
  context: Context,
  target: object,
  methodName: string,
  args: InvocationArgs,
  options?: InvocationOptions
): Promise<InvocationResult>;

Usage Examples:

import { 
  intercept, 
  globalInterceptor,
  Interceptor,
  InvocationContext 
} from "@loopback/core";

// Logging interceptor
const loggingInterceptor: Interceptor = async (context, next) => {
  console.log(`Calling ${context.targetName}.${context.methodName}`);
  const startTime = Date.now();
  
  try {
    const result = await next();
    const duration = Date.now() - startTime;
    console.log(`Completed ${context.methodName} in ${duration}ms`);
    return result;
  } catch (error) {
    console.error(`Error in ${context.methodName}:`, error.message);
    throw error;
  }
};

// Authentication interceptor
const authInterceptor: Interceptor = async (context, next) => {
  const request = context.args[0];
  if (!request.headers.authorization) {
    throw new Error('Authentication required');
  }
  
  // Validate token
  const token = request.headers.authorization.replace('Bearer ', '');
  const user = await validateToken(token);
  
  // Add user to context
  context.source = { ...context.source, currentUser: user };
  
  return next();
};

// Apply interceptors to methods
class UserService {
  @intercept(loggingInterceptor, authInterceptor)
  async createUser(userData: UserData): Promise<User> {
    // Method implementation
    return new User(userData);
  }
  
  @intercept(loggingInterceptor)
  async getUser(id: string): Promise<User | null> {
    // Method implementation
    return null;
  }
}

// Apply interceptors to entire class
@intercept(loggingInterceptor)
class OrderService {
  async createOrder(orderData: OrderData): Promise<Order> {
    // All methods will be intercepted
    return new Order(orderData);
  }
  
  async updateOrder(id: string, updates: Partial<OrderData>): Promise<Order> {
    // This method is also intercepted
    return new Order({});
  }
}

// Global interceptor
@globalInterceptor('audit')
class AuditInterceptor implements Provider<Interceptor> {
  value(): Interceptor {
    return async (context, next) => {
      // Log all method calls globally
      console.log(`Audit: ${context.targetName}.${context.methodName} called`);
      return next();
    };
  }
}

Provider Pattern

Interface for dynamic value providers with lazy evaluation.

/**
 * A provider is a class that defines a value function to return a value
 */
interface Provider<T> {
  value(): T | Promise<T>;
}

/**
 * Check if a class is a provider
 */
function isProviderClass<T>(providerClass: Constructor<unknown>): boolean;

Usage Examples:

import { Provider, Context, inject } from "@loopback/core";

// Configuration provider
class DatabaseConfigProvider implements Provider<DatabaseConfig> {
  value(): DatabaseConfig {
    return {
      host: process.env.DB_HOST || 'localhost',
      port: parseInt(process.env.DB_PORT || '5432'),
      database: process.env.DB_NAME || 'myapp',
      ssl: process.env.NODE_ENV === 'production'
    };
  }
}

// Async provider
class ExternalServiceProvider implements Provider<ExternalService> {
  async value(): Promise<ExternalService> {
    const config = await loadRemoteConfig();
    return new ExternalService(config);
  }
}

// Provider with dependencies
class EmailServiceProvider implements Provider<EmailService> {
  constructor(
    @inject('config.email') private emailConfig: EmailConfig,
    @inject('services.logger') private logger: LoggerService
  ) {}
  
  value(): EmailService {
    this.logger.log('Creating email service');
    return new EmailService(this.emailConfig);
  }
}

// Context setup
const context = new Context();

context.bind('config.database').toProvider(DatabaseConfigProvider);
context.bind('services.external').toProvider(ExternalServiceProvider);
context.bind('services.email').toProvider(EmailServiceProvider);

// Usage
const dbConfig = await context.get<DatabaseConfig>('config.database');
const emailService = await context.get<EmailService>('services.email');

Types

type BoundValue = any;

type BindingAddress<T = unknown> = string | BindingKey<T>;

type BindingSelector<T = unknown> = BindingAddress<T> | BindingFilter;

type BindingFilter = (binding: Readonly<Binding<unknown>>) => boolean;

type BindingTemplate<T = unknown> = (binding: Binding<T>) => void;

type BindingComparator = (
  a: Readonly<Binding<unknown>>, 
  b: Readonly<Binding<unknown>>
) => number;

type Constructor<T = {}> = new (...args: any[]) => T;

type ValueOrPromise<T> = T | Promise<T>;

type Getter<T> = () => Promise<T>;

interface InjectionMetadata {
  optional?: boolean;
  asProxyWithInterceptors?: boolean;
  bindingComparator?: BindingComparator;
}

interface ConfigInjectionMetadata extends InjectionMetadata {
  fromBinding?: BindingAddress<unknown>;
}

type ConfigurationPropertyPath = string;

type InvocationArgs = any[];
type InvocationResult = any;

interface InvocationContext {
  target: object;
  methodName: string;
  args: InvocationArgs;
  source?: object;
  targetName?: string;
}

interface InvocationOptions {
  skipInterceptors?: boolean;
  source?: object;
}

docs

application.md

components.md

context-api.md

extensions.md

index.md

lifecycle.md

services.md

tile.json