or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdcomponents.mdcontext-api.mdextensions.mdindex.mdlifecycle.mdservices.md

context-api.mddocs/

0

# Context & Dependency Injection

1

2

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.

3

4

## Capabilities

5

6

### Context Container

7

8

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

9

10

```typescript { .api }

11

/**

12

* Context provides an implementation of Inversion of Control (IoC) container

13

*/

14

class Context {

15

/** Create a binding with the given key */

16

bind<ValueType = BoundValue>(key: BindingAddress<ValueType>): Binding<ValueType>;

17

18

/** Add a binding to the context */

19

add<ValueType = BoundValue>(binding: Binding<ValueType>): Context;

20

21

/** Get a value from the context by key */

22

get<ValueType>(keyWithPath: BindingAddress<ValueType>): Promise<ValueType>;

23

24

/** Get a value synchronously from the context by key */

25

getSync<ValueType>(keyWithPath: BindingAddress<ValueType>): ValueType;

26

27

/** Check if a binding exists for the given key */

28

contains<ValueType>(key: BindingAddress<ValueType>): boolean;

29

30

/** Find bindings matching a filter */

31

find<ValueType = BoundValue>(filter?: BindingFilter): Readonly<Binding<ValueType>>[];

32

33

/** Create a child context */

34

createChild(name?: string): Context;

35

}

36

```

37

38

**Usage Examples:**

39

40

```typescript

41

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

42

43

// Create context

44

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

45

46

// Create typed binding key

47

const DB_CONFIG = BindingKey.create<DatabaseConfig>('config.database');

48

49

// Bind values

50

context.bind(DB_CONFIG).to({

51

host: 'localhost',

52

port: 5432,

53

database: 'myapp'

54

});

55

56

// Bind classes

57

context.bind('services.logger').toClass(LoggerService);

58

59

// Bind providers

60

context.bind('services.config').toProvider(ConfigProvider);

61

62

// Get values

63

const dbConfig = await context.get(DB_CONFIG);

64

const logger = await context.get<LoggerService>('services.logger');

65

66

// Synchronous get

67

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

68

69

// Check existence

70

if (context.contains('services.cache')) {

71

const cache = await context.get('services.cache');

72

}

73

74

// Child contexts

75

const childContext = context.createChild('request-scope');

76

childContext.bind('request.id').to('req-123');

77

```

78

79

### Binding System

80

81

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

82

83

```typescript { .api }

84

/**

85

* A binding represents a mapping from a key to a value

86

*/

87

class Binding<T = BoundValue> {

88

/** The binding key */

89

readonly key: string;

90

91

/** The binding scope */

92

scope: BindingScope;

93

94

/** Bind to a constant value */

95

to(value: T): Binding<T>;

96

97

/** Bind to a class constructor */

98

toClass<C>(ctor: Constructor<C>): Binding<C>;

99

100

/** Bind to a provider */

101

toProvider<P>(providerClass: Constructor<Provider<P>>): Binding<P>;

102

103

/** Create an alias binding */

104

toAlias(keyWithPath: BindingAddress<T>): Binding<T>;

105

106

/** Set the binding scope */

107

inScope(scope: BindingScope): Binding<T>;

108

109

/** Add tags to the binding */

110

tag(...tags: (string | object)[]): Binding<T>;

111

112

/** Apply a binding template */

113

apply(templateFn: BindingTemplate<T>): Binding<T>;

114

}

115

116

enum BindingScope {

117

TRANSIENT = 'Transient',

118

CONTEXT = 'Context',

119

SINGLETON = 'Singleton',

120

REQUEST = 'Request',

121

SESSION = 'Session',

122

APPLICATION = 'Application'

123

}

124

```

125

126

**Usage Examples:**

127

128

```typescript

129

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

130

131

const context = new Context();

132

133

// Constant binding

134

context.bind('config.port').to(3000);

135

136

// Class binding with scope

137

context.bind('services.logger')

138

.toClass(LoggerService)

139

.inScope(BindingScope.SINGLETON);

140

141

// Provider binding

142

context.bind('services.database')

143

.toProvider(DatabaseProvider)

144

.inScope(BindingScope.SINGLETON);

145

146

// Alias binding

147

context.bind('logger').toAlias('services.logger');

148

149

// Tagged binding

150

context.bind('repositories.user')

151

.toClass(UserRepository)

152

.tag('repository', {entity: 'User'});

153

154

// Chained configuration

155

const binding = context.bind('services.email')

156

.toClass(EmailService)

157

.inScope(BindingScope.SINGLETON)

158

.tag('service', 'communication')

159

.apply(binding => {

160

// Custom configuration

161

binding.tag('priority', 'high');

162

});

163

```

164

165

### Dependency Injection

166

167

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

168

169

```typescript { .api }

170

/**

171

* A decorator to annotate method arguments for automatic injection by LoopBack's IoC container

172

*/

173

function inject(

174

bindingSelector?: BindingSelector<unknown>,

175

metadata?: InjectionMetadata

176

): PropertyDecorator & ParameterDecorator;

177

178

namespace inject {

179

/** Inject a binding object */

180

function binding(

181

selector?: BindingSelector<unknown>,

182

metadata?: InjectionMetadata

183

): PropertyDecorator & ParameterDecorator;

184

185

/** Inject the current context */

186

function context(): PropertyDecorator & ParameterDecorator;

187

188

/** Inject a getter function */

189

function getter(

190

bindingSelector: BindingSelector<unknown>,

191

metadata?: InjectionMetadata

192

): PropertyDecorator & ParameterDecorator;

193

194

/** Inject a setter function */

195

function setter(

196

bindingSelector: BindingSelector<unknown>

197

): PropertyDecorator & ParameterDecorator;

198

199

/** Inject values by tag */

200

function tag(

201

tagName: string | RegExp,

202

metadata?: InjectionMetadata

203

): PropertyDecorator & ParameterDecorator;

204

205

/** Inject a context view */

206

function view(

207

filter: BindingFilter,

208

metadata?: InjectionMetadata

209

): PropertyDecorator & ParameterDecorator;

210

}

211

```

212

213

**Usage Examples:**

214

215

```typescript

216

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

217

218

// Service class with constructor injection

219

class OrderService {

220

constructor(

221

@inject('repositories.order') private orderRepo: OrderRepository,

222

@inject('services.payment') private paymentService: PaymentService,

223

@inject.context() private context: Context

224

) {}

225

226

@inject('services.logger')

227

private logger: LoggerService;

228

229

async createOrder(orderData: OrderData): Promise<Order> {

230

this.logger.log('Creating order');

231

232

const order = await this.orderRepo.create(orderData);

233

await this.paymentService.processPayment(order.total);

234

235

return order;

236

}

237

238

// Getter injection for lazy loading

239

@inject.getter('services.email')

240

private getEmailService: Getter<EmailService>;

241

242

async sendOrderConfirmation(orderId: string): Promise<void> {

243

const emailService = await this.getEmailService();

244

await emailService.sendOrderConfirmation(orderId);

245

}

246

}

247

248

// Controller with various injection types

249

class UserController {

250

constructor(

251

@inject('repositories.user') private userRepo: UserRepository,

252

@inject.tag('validator') private validators: Validator[]

253

) {}

254

255

// Context view injection

256

@inject.view(filterByTag('middleware'))

257

private middlewareView: ContextView<Middleware>;

258

259

async createUser(userData: UserData): Promise<User> {

260

// Use injected dependencies

261

for (const validator of this.validators) {

262

await validator.validate(userData);

263

}

264

265

return this.userRepo.create(userData);

266

}

267

}

268

```

269

270

### Configuration Injection

271

272

Specialized injection for configuration values with support for nested properties.

273

274

```typescript { .api }

275

/**

276

* Decorator for configuration injection

277

*/

278

function config(

279

propertyPath?: ConfigurationPropertyPath,

280

metadata?: ConfigInjectionMetadata

281

): PropertyDecorator & ParameterDecorator;

282

283

namespace config {

284

/** Inject configuration as a context view */

285

function view(

286

propertyPath?: ConfigurationPropertyPath,

287

metadata?: ConfigInjectionMetadata

288

): PropertyDecorator & ParameterDecorator;

289

}

290

```

291

292

**Usage Examples:**

293

294

```typescript

295

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

296

297

interface DatabaseConfig {

298

host: string;

299

port: number;

300

database: string;

301

ssl: boolean;

302

}

303

304

interface AppConfig {

305

database: DatabaseConfig;

306

redis: {

307

url: string;

308

ttl: number;

309

};

310

}

311

312

class DatabaseService {

313

constructor(

314

@config('database') private dbConfig: DatabaseConfig,

315

@config('database.host') private host: string,

316

@config() private fullConfig: AppConfig // Inject entire config

317

) {}

318

319

async connect(): Promise<void> {

320

console.log(`Connecting to ${this.host}:${this.dbConfig.port}`);

321

// Connection logic

322

}

323

}

324

325

// Application setup

326

const app = new Application();

327

328

app.bind('config').to({

329

database: {

330

host: 'localhost',

331

port: 5432,

332

database: 'myapp',

333

ssl: false

334

},

335

redis: {

336

url: 'redis://localhost:6379',

337

ttl: 3600

338

}

339

});

340

341

app.service(DatabaseService);

342

```

343

344

### Binding Keys

345

346

Type-safe binding keys with deep property access support.

347

348

```typescript { .api }

349

/**

350

* Binding key is used to uniquely identify a binding in the context

351

*/

352

class BindingKey<ValueType = unknown> {

353

/** Create a new binding key */

354

static create<T>(key: string): BindingKey<T>;

355

356

/** Get a property path from the binding key */

357

propertyPath<P>(path: string): BindingKey<P>;

358

359

/** The key string */

360

readonly key: string;

361

}

362

363

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

364

```

365

366

**Usage Examples:**

367

368

```typescript

369

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

370

371

// Type-safe binding keys

372

const DATABASE_CONFIG = BindingKey.create<DatabaseConfig>('config.database');

373

const LOGGER_SERVICE = BindingKey.create<LoggerService>('services.logger');

374

const USER_REPOSITORY = BindingKey.create<UserRepository>('repositories.user');

375

376

// Property path access

377

const DB_HOST = DATABASE_CONFIG.propertyPath<string>('host');

378

const DB_PORT = DATABASE_CONFIG.propertyPath<number>('port');

379

380

const context = new Context();

381

382

// Bind with typed keys

383

context.bind(DATABASE_CONFIG).to({

384

host: 'localhost',

385

port: 5432,

386

database: 'myapp',

387

ssl: false

388

});

389

390

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

391

392

// Type-safe access

393

const dbConfig: DatabaseConfig = await context.get(DATABASE_CONFIG);

394

const logger: LoggerService = await context.get(LOGGER_SERVICE);

395

396

// Property path access

397

const host: string = await context.get(DB_HOST); // 'localhost'

398

const port: number = await context.get(DB_PORT); // 5432

399

400

// Use in injection

401

class MyService {

402

constructor(

403

@inject(DATABASE_CONFIG) private dbConfig: DatabaseConfig,

404

@inject(DB_HOST) private host: string

405

) {}

406

}

407

```

408

409

### Context Views

410

411

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

412

413

```typescript { .api }

414

/**

415

* ContextView provides a view for a given context chain to maintain a live list

416

* of matching bindings

417

*/

418

class ContextView<T = unknown> {

419

constructor(

420

context: Context,

421

filter: BindingFilter,

422

comparator?: BindingComparator

423

);

424

425

/** Get all matching values */

426

values(): Promise<T[]>;

427

428

/** Get all matching bindings */

429

bindings: Readonly<Binding<T>>[];

430

431

/** Listen for binding events */

432

on(eventName: string, listener: ContextViewEventListener): void;

433

434

/** Create a getter function for the view */

435

asGetter(): Getter<T[]>;

436

}

437

438

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

439

```

440

441

**Usage Examples:**

442

443

```typescript

444

import {

445

Context,

446

ContextView,

447

filterByTag,

448

ContextViewEvent

449

} from "@loopback/core";

450

451

interface Plugin {

452

name: string;

453

initialize(): Promise<void>;

454

}

455

456

class PluginManager {

457

private pluginView: ContextView<Plugin>;

458

459

constructor(context: Context) {

460

// Create view for all plugins

461

this.pluginView = new ContextView(

462

context,

463

filterByTag('plugin')

464

);

465

466

// Listen for plugin additions/removals

467

this.pluginView.on('bind', this.onPluginAdded.bind(this));

468

this.pluginView.on('unbind', this.onPluginRemoved.bind(this));

469

}

470

471

private async onPluginAdded(event: ContextViewEvent<Plugin>): Promise<void> {

472

const plugin = await event.binding.getValue(event.context);

473

console.log(`Plugin added: ${plugin.name}`);

474

await plugin.initialize();

475

}

476

477

private onPluginRemoved(event: ContextViewEvent<Plugin>): void {

478

console.log(`Plugin removed: ${event.binding.key}`);

479

}

480

481

async getAllPlugins(): Promise<Plugin[]> {

482

return this.pluginView.values();

483

}

484

485

getPluginCount(): number {

486

return this.pluginView.bindings.length;

487

}

488

}

489

490

// Usage

491

const context = new Context();

492

const pluginManager = new PluginManager(context);

493

494

// Add plugins

495

context.bind('plugins.auth')

496

.toClass(AuthPlugin)

497

.tag('plugin');

498

499

context.bind('plugins.logging')

500

.toClass(LoggingPlugin)

501

.tag('plugin');

502

```

503

504

### Interceptors

505

506

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

507

508

```typescript { .api }

509

/**

510

* Interceptor function to intercept method invocations

511

*/

512

type Interceptor = (

513

context: InvocationContext,

514

next: () => ValueOrPromise<InvocationResult>

515

) => ValueOrPromise<InvocationResult>;

516

517

/**

518

* Decorator to apply interceptors to methods or classes

519

*/

520

function intercept(...interceptorOrKeys: InterceptorOrKey[]): MethodDecorator & ClassDecorator;

521

522

/**

523

* Decorator to register global interceptors

524

*/

525

function globalInterceptor(group?: string, ...specs: BindingSpec[]): ClassDecorator;

526

527

/**

528

* Invoke a method with interceptors

529

*/

530

function invokeMethodWithInterceptors(

531

context: Context,

532

target: object,

533

methodName: string,

534

args: InvocationArgs,

535

options?: InvocationOptions

536

): Promise<InvocationResult>;

537

```

538

539

**Usage Examples:**

540

541

```typescript

542

import {

543

intercept,

544

globalInterceptor,

545

Interceptor,

546

InvocationContext

547

} from "@loopback/core";

548

549

// Logging interceptor

550

const loggingInterceptor: Interceptor = async (context, next) => {

551

console.log(`Calling ${context.targetName}.${context.methodName}`);

552

const startTime = Date.now();

553

554

try {

555

const result = await next();

556

const duration = Date.now() - startTime;

557

console.log(`Completed ${context.methodName} in ${duration}ms`);

558

return result;

559

} catch (error) {

560

console.error(`Error in ${context.methodName}:`, error.message);

561

throw error;

562

}

563

};

564

565

// Authentication interceptor

566

const authInterceptor: Interceptor = async (context, next) => {

567

const request = context.args[0];

568

if (!request.headers.authorization) {

569

throw new Error('Authentication required');

570

}

571

572

// Validate token

573

const token = request.headers.authorization.replace('Bearer ', '');

574

const user = await validateToken(token);

575

576

// Add user to context

577

context.source = { ...context.source, currentUser: user };

578

579

return next();

580

};

581

582

// Apply interceptors to methods

583

class UserService {

584

@intercept(loggingInterceptor, authInterceptor)

585

async createUser(userData: UserData): Promise<User> {

586

// Method implementation

587

return new User(userData);

588

}

589

590

@intercept(loggingInterceptor)

591

async getUser(id: string): Promise<User | null> {

592

// Method implementation

593

return null;

594

}

595

}

596

597

// Apply interceptors to entire class

598

@intercept(loggingInterceptor)

599

class OrderService {

600

async createOrder(orderData: OrderData): Promise<Order> {

601

// All methods will be intercepted

602

return new Order(orderData);

603

}

604

605

async updateOrder(id: string, updates: Partial<OrderData>): Promise<Order> {

606

// This method is also intercepted

607

return new Order({});

608

}

609

}

610

611

// Global interceptor

612

@globalInterceptor('audit')

613

class AuditInterceptor implements Provider<Interceptor> {

614

value(): Interceptor {

615

return async (context, next) => {

616

// Log all method calls globally

617

console.log(`Audit: ${context.targetName}.${context.methodName} called`);

618

return next();

619

};

620

}

621

}

622

```

623

624

### Provider Pattern

625

626

Interface for dynamic value providers with lazy evaluation.

627

628

```typescript { .api }

629

/**

630

* A provider is a class that defines a value function to return a value

631

*/

632

interface Provider<T> {

633

value(): T | Promise<T>;

634

}

635

636

/**

637

* Check if a class is a provider

638

*/

639

function isProviderClass<T>(providerClass: Constructor<unknown>): boolean;

640

```

641

642

**Usage Examples:**

643

644

```typescript

645

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

646

647

// Configuration provider

648

class DatabaseConfigProvider implements Provider<DatabaseConfig> {

649

value(): DatabaseConfig {

650

return {

651

host: process.env.DB_HOST || 'localhost',

652

port: parseInt(process.env.DB_PORT || '5432'),

653

database: process.env.DB_NAME || 'myapp',

654

ssl: process.env.NODE_ENV === 'production'

655

};

656

}

657

}

658

659

// Async provider

660

class ExternalServiceProvider implements Provider<ExternalService> {

661

async value(): Promise<ExternalService> {

662

const config = await loadRemoteConfig();

663

return new ExternalService(config);

664

}

665

}

666

667

// Provider with dependencies

668

class EmailServiceProvider implements Provider<EmailService> {

669

constructor(

670

@inject('config.email') private emailConfig: EmailConfig,

671

@inject('services.logger') private logger: LoggerService

672

) {}

673

674

value(): EmailService {

675

this.logger.log('Creating email service');

676

return new EmailService(this.emailConfig);

677

}

678

}

679

680

// Context setup

681

const context = new Context();

682

683

context.bind('config.database').toProvider(DatabaseConfigProvider);

684

context.bind('services.external').toProvider(ExternalServiceProvider);

685

context.bind('services.email').toProvider(EmailServiceProvider);

686

687

// Usage

688

const dbConfig = await context.get<DatabaseConfig>('config.database');

689

const emailService = await context.get<EmailService>('services.email');

690

```

691

692

## Types

693

694

```typescript { .api }

695

type BoundValue = any;

696

697

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

698

699

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

700

701

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

702

703

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

704

705

type BindingComparator = (

706

a: Readonly<Binding<unknown>>,

707

b: Readonly<Binding<unknown>>

708

) => number;

709

710

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

711

712

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

713

714

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

715

716

interface InjectionMetadata {

717

optional?: boolean;

718

asProxyWithInterceptors?: boolean;

719

bindingComparator?: BindingComparator;

720

}

721

722

interface ConfigInjectionMetadata extends InjectionMetadata {

723

fromBinding?: BindingAddress<unknown>;

724

}

725

726

type ConfigurationPropertyPath = string;

727

728

type InvocationArgs = any[];

729

type InvocationResult = any;

730

731

interface InvocationContext {

732

target: object;

733

methodName: string;

734

args: InvocationArgs;

735

source?: object;

736

targetName?: string;

737

}

738

739

interface InvocationOptions {

740

skipInterceptors?: boolean;

741

source?: object;

742

}

743

```