or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

binding.mdconditional.mdcontainer.mddecorators.mdindex.mdlifecycle.mdmodules.md
tile.json

lifecycle.mddocs/

0

# Lifecycle Management

1

2

InversifyJS provides comprehensive lifecycle management features including scope control, activation/deactivation hooks, construction/destruction callbacks, and container event handling. These features enable proper resource management and fine-grained control over service lifetimes.

3

4

## Lifecycle Scopes

5

6

### Binding Scope Values

7

8

```typescript { .api }

9

const bindingScopeValues: {

10

Singleton: "Singleton";

11

Transient: "Transient";

12

Request: "Request";

13

};

14

15

type BindingScope = keyof typeof bindingScopeValues;

16

```

17

18

### Singleton Scope

19

20

Single instance shared across the entire container lifetime.

21

22

```typescript

23

@injectable()

24

class DatabaseConnectionPool {

25

private connections: Connection[] = [];

26

private maxConnections = 10;

27

28

constructor() {

29

console.log("Creating connection pool...");

30

}

31

32

getConnection(): Connection {

33

if (this.connections.length < this.maxConnections) {

34

const connection = new Connection();

35

this.connections.push(connection);

36

return connection;

37

}

38

return this.connections[Math.floor(Math.random() * this.connections.length)];

39

}

40

41

@preDestroy()

42

cleanup() {

43

console.log("Closing all connections...");

44

this.connections.forEach(conn => conn.close());

45

}

46

}

47

48

// Single instance for entire application

49

container.bind<DatabaseConnectionPool>("ConnectionPool")

50

.to(DatabaseConnectionPool)

51

.inSingletonScope();

52

53

// All requests return same instance

54

const pool1 = container.get<DatabaseConnectionPool>("ConnectionPool");

55

const pool2 = container.get<DatabaseConnectionPool>("ConnectionPool");

56

console.log(pool1 === pool2); // true

57

```

58

59

### Transient Scope

60

61

New instance created for every resolution request.

62

63

```typescript

64

@injectable()

65

class TaskProcessor {

66

private taskId = Math.random().toString(36);

67

68

constructor() {

69

console.log(`Creating task processor: ${this.taskId}`);

70

}

71

72

process(data: any) {

73

console.log(`Processing with ${this.taskId}`);

74

return { processedBy: this.taskId, data };

75

}

76

77

@preDestroy()

78

cleanup() {

79

console.log(`Cleaning up processor: ${this.taskId}`);

80

}

81

}

82

83

// New instance every time

84

container.bind<TaskProcessor>("TaskProcessor")

85

.to(TaskProcessor)

86

.inTransientScope();

87

88

const processor1 = container.get<TaskProcessor>("TaskProcessor");

89

const processor2 = container.get<TaskProcessor>("TaskProcessor");

90

console.log(processor1 === processor2); // false

91

```

92

93

### Request Scope

94

95

Single instance per dependency resolution graph.

96

97

```typescript

98

@injectable()

99

class RequestContext {

100

private requestId = Math.random().toString(36);

101

private startTime = Date.now();

102

103

constructor() {

104

console.log(`Request context created: ${this.requestId}`);

105

}

106

107

getRequestId() { return this.requestId; }

108

getElapsedTime() { return Date.now() - this.startTime; }

109

110

@preDestroy()

111

cleanup() {

112

console.log(`Request completed in ${this.getElapsedTime()}ms`);

113

}

114

}

115

116

@injectable()

117

class UserService {

118

constructor(@inject("RequestContext") private context: RequestContext) {}

119

120

getUser(id: string) {

121

console.log(`UserService using context: ${this.context.getRequestId()}`);

122

return { id, requestId: this.context.getRequestId() };

123

}

124

}

125

126

@injectable()

127

class OrderService {

128

constructor(@inject("RequestContext") private context: RequestContext) {}

129

130

getOrder(id: string) {

131

console.log(`OrderService using context: ${this.context.getRequestId()}`);

132

return { id, requestId: this.context.getRequestId() };

133

}

134

}

135

136

container.bind<RequestContext>("RequestContext").to(RequestContext).inRequestScope();

137

container.bind<UserService>("UserService").to(UserService);

138

container.bind<OrderService>("OrderService").to(OrderService);

139

140

// Same context shared within single resolution request

141

const userService = container.get<UserService>("UserService");

142

const orderService = container.get<OrderService>("OrderService");

143

// Both services share the same RequestContext instance

144

145

// New context for new resolution request

146

const userService2 = container.get<UserService>("UserService");

147

// userService2 gets a different RequestContext instance

148

```

149

150

## Lifecycle Hook Decorators

151

152

### @postConstruct

153

154

Executed after object construction and all dependency injection is complete.

155

156

```typescript { .api }

157

function postConstruct(target: any, propertyKey: string): void;

158

```

159

160

```typescript

161

@injectable()

162

class EmailService {

163

@inject("Config") private config!: IConfig;

164

@inject("Logger") private logger!: ILogger;

165

166

private smtpClient?: SmtpClient;

167

private isInitialized = false;

168

169

@postConstruct()

170

private async initialize() {

171

this.logger.log("Initializing email service...");

172

173

this.smtpClient = new SmtpClient({

174

host: this.config.smtp.host,

175

port: this.config.smtp.port,

176

secure: this.config.smtp.secure

177

});

178

179

await this.smtpClient.connect();

180

this.isInitialized = true;

181

182

this.logger.log("Email service initialized successfully");

183

}

184

185

async sendEmail(to: string, subject: string, body: string) {

186

if (!this.isInitialized) {

187

throw new Error("Email service not initialized");

188

}

189

190

return this.smtpClient!.sendMail({ to, subject, html: body });

191

}

192

}

193

```

194

195

### @preDestroy

196

197

Executed before object destruction or container disposal.

198

199

```typescript { .api }

200

function preDestroy(target: any, propertyKey: string): void;

201

```

202

203

```typescript

204

@injectable()

205

class FileProcessingService {

206

private fileHandles: FileHandle[] = [];

207

private tempFiles: string[] = [];

208

private processingSessions = new Map<string, ProcessingSession>();

209

210

async processFile(filePath: string): Promise<ProcessingResult> {

211

const handle = await fs.open(filePath, 'r');

212

this.fileHandles.push(handle);

213

214

const tempFile = `/tmp/processing_${Date.now()}.tmp`;

215

this.tempFiles.push(tempFile);

216

217

const sessionId = Math.random().toString(36);

218

const session = new ProcessingSession(sessionId);

219

this.processingSessions.set(sessionId, session);

220

221

// Processing logic...

222

return { sessionId, processed: true };

223

}

224

225

@preDestroy()

226

private async cleanup() {

227

console.log("Cleaning up file processing service...");

228

229

// Close all file handles

230

for (const handle of this.fileHandles) {

231

try {

232

await handle.close();

233

} catch (error) {

234

console.error("Error closing file handle:", error);

235

}

236

}

237

238

// Remove temporary files

239

for (const tempFile of this.tempFiles) {

240

try {

241

await fs.unlink(tempFile);

242

} catch (error) {

243

console.error("Error removing temp file:", error);

244

}

245

}

246

247

// Clean up processing sessions

248

for (const [sessionId, session] of this.processingSessions) {

249

try {

250

await session.terminate();

251

} catch (error) {

252

console.error(`Error terminating session ${sessionId}:`, error);

253

}

254

}

255

256

console.log("File processing service cleanup completed");

257

}

258

}

259

```

260

261

## Activation and Deactivation Hooks

262

263

### OnActivation Interface

264

265

```typescript { .api }

266

interface OnActivation<T> {

267

(context: ResolutionContext, injectable: T): T | Promise<T>;

268

}

269

270

interface BindOnFluentSyntax<T> {

271

onActivation(handler: OnActivation<T>): BindInFluentSyntax<T>;

272

}

273

```

274

275

### Activation Handlers

276

277

Called immediately after service instantiation but before returning to requester.

278

279

```typescript

280

@injectable()

281

class CacheService {

282

private cache = new Map<string, any>();

283

284

get(key: string) { return this.cache.get(key); }

285

set(key: string, value: any) { this.cache.set(key, value); }

286

clear() { this.cache.clear(); }

287

}

288

289

container.bind<CacheService>("CacheService")

290

.to(CacheService)

291

.inSingletonScope()

292

.onActivation((context, cacheService) => {

293

console.log("Cache service activated");

294

295

// Pre-populate cache with initial data

296

cacheService.set("initialized", true);

297

cacheService.set("activatedAt", new Date().toISOString());

298

299

// Set up periodic cleanup

300

setInterval(() => {

301

console.log("Performing cache cleanup...");

302

// Cleanup logic here

303

}, 60000);

304

305

return cacheService;

306

});

307

308

// Async activation handler

309

container.bind<DatabaseService>("DatabaseService")

310

.to(DatabaseService)

311

.inSingletonScope()

312

.onActivation(async (context, dbService) => {

313

console.log("Activating database service...");

314

315

await dbService.connect();

316

await dbService.runMigrations();

317

318

console.log("Database service ready");

319

return dbService;

320

});

321

```

322

323

### Deactivation Handlers

324

325

```typescript { .api }

326

interface OnDeactivation<T> {

327

(injectable: T): void | Promise<void>;

328

}

329

330

interface BindOnFluentSyntax<T> {

331

onDeactivation(handler: OnDeactivation<T>): BindInFluentSyntax<T>;

332

}

333

```

334

335

Called before service destruction or container disposal.

336

337

```typescript

338

@injectable()

339

class WebSocketService {

340

private connections = new Set<WebSocket>();

341

private heartbeatInterval?: NodeJS.Timeout;

342

343

constructor() {

344

this.heartbeatInterval = setInterval(() => {

345

this.sendHeartbeat();

346

}, 30000);

347

}

348

349

addConnection(ws: WebSocket) {

350

this.connections.add(ws);

351

}

352

353

removeConnection(ws: WebSocket) {

354

this.connections.delete(ws);

355

}

356

357

private sendHeartbeat() {

358

for (const ws of this.connections) {

359

if (ws.readyState === WebSocket.OPEN) {

360

ws.ping();

361

}

362

}

363

}

364

}

365

366

container.bind<WebSocketService>("WebSocketService")

367

.to(WebSocketService)

368

.inSingletonScope()

369

.onDeactivation((wsService) => {

370

console.log("Deactivating WebSocket service...");

371

372

// Clear heartbeat interval

373

if (wsService.heartbeatInterval) {

374

clearInterval(wsService.heartbeatInterval);

375

}

376

377

// Close all connections

378

for (const ws of wsService.connections) {

379

if (ws.readyState === WebSocket.OPEN) {

380

ws.close(1000, "Service shutting down");

381

}

382

}

383

384

console.log("WebSocket service deactivated");

385

});

386

387

// Async deactivation handler

388

container.bind<DatabaseService>("DatabaseService")

389

.to(DatabaseService)

390

.inSingletonScope()

391

.onDeactivation(async (dbService) => {

392

console.log("Deactivating database service...");

393

394

await dbService.flushPendingOperations();

395

await dbService.disconnect();

396

397

console.log("Database service deactivated");

398

});

399

```

400

401

## Container Lifecycle Events

402

403

### Container Disposal

404

405

```typescript

406

// Container with lifecycle management

407

const container = new Container();

408

409

// Register services with lifecycle hooks

410

container.bind<FileService>("FileService")

411

.to(FileService)

412

.inSingletonScope()

413

.onDeactivation(async (service) => {

414

await service.closeAllFiles();

415

});

416

417

container.bind<NetworkService>("NetworkService")

418

.to(NetworkService)

419

.inSingletonScope()

420

.onDeactivation(async (service) => {

421

await service.closeConnections();

422

});

423

424

// Graceful shutdown

425

process.on('SIGTERM', async () => {

426

console.log('Received SIGTERM, shutting down gracefully...');

427

428

// This will trigger all deactivation handlers

429

await container.unbindAllAsync();

430

431

process.exit(0);

432

});

433

```

434

435

## Advanced Lifecycle Patterns

436

437

### Circular Dependency Handling

438

439

```typescript

440

@injectable()

441

class ServiceA {

442

private serviceB?: ServiceB;

443

444

@postConstruct()

445

initialize() {

446

// Safe to access circular dependencies in postConstruct

447

this.serviceB = container.get<ServiceB>("ServiceB");

448

}

449

}

450

451

@injectable()

452

class ServiceB {

453

private serviceA?: ServiceA;

454

455

@postConstruct()

456

initialize() {

457

this.serviceA = container.get<ServiceA>("ServiceA");

458

}

459

}

460

```

461

462

### Lazy Initialization

463

464

```typescript

465

@injectable()

466

class ExpensiveService {

467

private _heavyResource?: HeavyResource;

468

469

get heavyResource() {

470

if (!this._heavyResource) {

471

console.log("Lazy loading heavy resource...");

472

this._heavyResource = new HeavyResource();

473

}

474

return this._heavyResource;

475

}

476

477

@preDestroy()

478

cleanup() {

479

if (this._heavyResource) {

480

this._heavyResource.dispose();

481

}

482

}

483

}

484

```

485

486

### Health Check Integration

487

488

```typescript

489

@injectable()

490

class HealthCheckService {

491

private services = new Map<string, HealthCheckable>();

492

493

registerService(name: string, service: HealthCheckable) {

494

this.services.set(name, service);

495

}

496

497

async checkHealth(): Promise<HealthStatus> {

498

const results = new Map<string, boolean>();

499

500

for (const [name, service] of this.services) {

501

try {

502

const isHealthy = await service.isHealthy();

503

results.set(name, isHealthy);

504

} catch (error) {

505

results.set(name, false);

506

}

507

}

508

509

return {

510

overall: Array.from(results.values()).every(Boolean),

511

services: Object.fromEntries(results)

512

};

513

}

514

}

515

516

// Register services with health checks via activation handlers

517

container.bind<DatabaseService>("DatabaseService")

518

.to(DatabaseService)

519

.inSingletonScope()

520

.onActivation((context, dbService) => {

521

const healthCheck = context.container.get<HealthCheckService>("HealthCheck");

522

healthCheck.registerService("database", dbService);

523

return dbService;

524

});

525

```

526

527

## Best Practices

528

529

1. **Use appropriate scopes**: Match scope to service purpose and resource usage

530

2. **Implement @preDestroy**: Always clean up resources in preDestroy methods

531

3. **Avoid heavy work in constructors**: Use @postConstruct for complex initialization

532

4. **Handle async operations**: Use async/await in lifecycle hooks when needed

533

5. **Register health checks**: Use activation handlers to integrate with health monitoring

534

6. **Graceful shutdown**: Implement proper container disposal on application exit

535

7. **Test lifecycle**: Verify construction, initialization, and cleanup work correctly

536

8. **Document dependencies**: Make initialization order dependencies clear