or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

associations.mddata-types.mddatabase-connection.mderror-handling.mdhooks.mdindex.mdmodel-definition.mdquery-operators.mdquerying.mdtransactions.md

hooks.mddocs/

0

# Hooks

1

2

Lifecycle event system for extending model and query behavior with customizable hooks that execute at specific points during database operations.

3

4

## Capabilities

5

6

### Instance Lifecycle Hooks

7

8

Hooks that execute during individual model instance operations.

9

10

```typescript { .api }

11

/**

12

* Validation hooks

13

*/

14

interface ValidationHooks {

15

/** Before instance validation */

16

beforeValidate: (instance: Model, options: ValidationOptions) => Promise<void> | void;

17

/** After successful validation */

18

afterValidate: (instance: Model, options: ValidationOptions) => Promise<void> | void;

19

/** When validation fails */

20

validationFailed: (instance: Model, options: ValidationOptions, error: ValidationError) => Promise<void> | void;

21

}

22

23

/**

24

* Create/Update/Save hooks

25

*/

26

interface CRUDHooks {

27

/** Before creating new instance */

28

beforeCreate: (instance: Model, options: CreateOptions) => Promise<void> | void;

29

/** After creating new instance */

30

afterCreate: (instance: Model, options: CreateOptions) => Promise<void> | void;

31

32

/** Before updating instance */

33

beforeUpdate: (instance: Model, options: UpdateOptions) => Promise<void> | void;

34

/** After updating instance */

35

afterUpdate: (instance: Model, options: UpdateOptions) => Promise<void> | void;

36

37

/** Before saving (create or update) */

38

beforeSave: (instance: Model, options: SaveOptions) => Promise<void> | void;

39

/** After saving (create or update) */

40

afterSave: (instance: Model, options: SaveOptions) => Promise<void> | void;

41

42

/** Before destroying instance */

43

beforeDestroy: (instance: Model, options: InstanceDestroyOptions) => Promise<void> | void;

44

/** After destroying instance */

45

afterDestroy: (instance: Model, options: InstanceDestroyOptions) => Promise<void> | void;

46

47

/** Before restoring soft-deleted instance */

48

beforeRestore: (instance: Model, options: RestoreOptions) => Promise<void> | void;

49

/** After restoring soft-deleted instance */

50

afterRestore: (instance: Model, options: RestoreOptions) => Promise<void> | void;

51

52

/** Before upserting */

53

beforeUpsert: (values: any, options: UpsertOptions) => Promise<void> | void;

54

/** After upserting */

55

afterUpsert: (result: [Model, boolean], options: UpsertOptions) => Promise<void> | void;

56

}

57

```

58

59

**Usage Examples:**

60

61

```typescript

62

// Define hooks in model definition

63

class User extends Model {}

64

User.init({

65

firstName: DataTypes.STRING,

66

lastName: DataTypes.STRING,

67

email: DataTypes.STRING,

68

hashedPassword: DataTypes.STRING,

69

lastLoginAt: DataTypes.DATE

70

}, {

71

sequelize,

72

modelName: 'user',

73

hooks: {

74

// Hash password before creating user

75

beforeCreate: async (user, options) => {

76

if (user.password) {

77

user.hashedPassword = await bcrypt.hash(user.password, 10);

78

delete user.password;

79

}

80

},

81

82

// Update login timestamp after creation

83

afterCreate: async (user, options) => {

84

console.log(`User ${user.email} created at ${new Date()}`);

85

},

86

87

// Hash password before updates too

88

beforeUpdate: async (user, options) => {

89

if (user.changed('password')) {

90

user.hashedPassword = await bcrypt.hash(user.password, 10);

91

delete user.password;

92

}

93

},

94

95

// Log validation failures

96

validationFailed: (user, options, error) => {

97

console.log(`Validation failed for user: ${error.message}`);

98

}

99

}

100

});

101

102

// Add hooks after model definition

103

User.addHook('beforeSave', async (user, options) => {

104

user.email = user.email.toLowerCase();

105

});

106

107

User.addHook('afterDestroy', (user, options) => {

108

console.log(`User ${user.email} was deleted`);

109

});

110

```

111

112

### Bulk Operation Hooks

113

114

Hooks that execute during bulk database operations.

115

116

```typescript { .api }

117

/**

118

* Bulk operation hooks

119

*/

120

interface BulkHooks {

121

/** Before bulk create operation */

122

beforeBulkCreate: (instances: Model[], options: BulkCreateOptions) => Promise<void> | void;

123

/** After bulk create operation */

124

afterBulkCreate: (instances: Model[], options: BulkCreateOptions) => Promise<void> | void;

125

126

/** Before bulk update operation */

127

beforeBulkUpdate: (options: UpdateOptions) => Promise<void> | void;

128

/** After bulk update operation */

129

afterBulkUpdate: (options: UpdateOptions) => Promise<void> | void;

130

131

/** Before bulk destroy operation */

132

beforeBulkDestroy: (options: DestroyOptions) => Promise<void> | void;

133

/** After bulk destroy operation */

134

afterBulkDestroy: (options: DestroyOptions) => Promise<void> | void;

135

136

/** Before bulk restore operation */

137

beforeBulkRestore: (options: RestoreOptions) => Promise<void> | void;

138

/** After bulk restore operation */

139

afterBulkRestore: (options: RestoreOptions) => Promise<void> | void;

140

141

/** Before bulk sync operation */

142

beforeBulkSync: (options: SyncOptions) => Promise<void> | void;

143

/** After bulk sync operation */

144

afterBulkSync: (options: SyncOptions) => Promise<void> | void;

145

}

146

```

147

148

**Usage Examples:**

149

150

```typescript

151

// Bulk operation hooks

152

User.addHook('beforeBulkCreate', (instances, options) => {

153

console.log(`About to create ${instances.length} users`);

154

155

// Modify all instances before creation

156

instances.forEach(user => {

157

user.email = user.email.toLowerCase();

158

user.createdAt = new Date();

159

});

160

});

161

162

User.addHook('afterBulkUpdate', (options) => {

163

console.log('Bulk update completed');

164

165

// Clear cache after bulk operations

166

cache.clear('users');

167

});

168

169

User.addHook('beforeBulkDestroy', (options) => {

170

// Log what's about to be deleted

171

console.log('About to delete users matching:', options.where);

172

});

173

```

174

175

### Query Hooks

176

177

Hooks that execute around database query operations.

178

179

```typescript { .api }

180

/**

181

* Query-related hooks

182

*/

183

interface QueryHooks {

184

/** Before find operations */

185

beforeFind: (options: FindOptions) => Promise<void> | void;

186

/** After find operations */

187

afterFind: (instances: Model | Model[] | null, options: FindOptions) => Promise<void> | void;

188

189

/** Before count operations */

190

beforeCount: (options: CountOptions) => Promise<void> | void;

191

192

/** Before find, after include expansion */

193

beforeFindAfterExpandIncludeAll: (options: FindOptions) => Promise<void> | void;

194

/** Before find, after options processing */

195

beforeFindAfterOptions: (options: FindOptions) => Promise<void> | void;

196

197

/** Before any SQL query execution */

198

beforeQuery: (options: QueryOptions, query: string) => Promise<void> | void;

199

/** After any SQL query execution */

200

afterQuery: (options: QueryOptions, query: string) => Promise<void> | void;

201

}

202

```

203

204

**Usage Examples:**

205

206

```typescript

207

// Query hooks for caching and logging

208

User.addHook('beforeFind', (options) => {

209

console.log('Finding users with options:', options);

210

211

// Add default ordering if not specified

212

if (!options.order) {

213

options.order = [['createdAt', 'DESC']];

214

}

215

});

216

217

User.addHook('afterFind', (result, options) => {

218

if (Array.isArray(result)) {

219

console.log(`Found ${result.length} users`);

220

} else if (result) {

221

console.log(`Found user: ${result.email}`);

222

} else {

223

console.log('No user found');

224

}

225

});

226

227

// Global query logging

228

sequelize.addHook('beforeQuery', (options, query) => {

229

console.time('query');

230

console.log('Executing query:', query);

231

});

232

233

sequelize.addHook('afterQuery', (options, query) => {

234

console.timeEnd('query');

235

});

236

```

237

238

### Model Definition Hooks

239

240

Hooks that execute during model definition and association setup.

241

242

```typescript { .api }

243

/**

244

* Model definition hooks

245

*/

246

interface ModelDefinitionHooks {

247

/** Before model definition */

248

beforeDefine: (attributes: ModelAttributes, options: DefineOptions) => Promise<void> | void;

249

/** After model definition */

250

afterDefine: (model: typeof Model) => Promise<void> | void;

251

252

/** Before model initialization */

253

beforeInit: (attributes: ModelAttributes, options: InitOptions) => Promise<void> | void;

254

/** After model initialization */

255

afterInit: (model: typeof Model) => Promise<void> | void;

256

257

/** Before association setup */

258

beforeAssociate: (model: typeof Model, associations: any) => Promise<void> | void;

259

/** After association setup */

260

afterAssociate: (model: typeof Model, associations: any) => Promise<void> | void;

261

}

262

```

263

264

**Usage Examples:**

265

266

```typescript

267

// Model definition hooks

268

sequelize.addHook('beforeDefine', (attributes, options) => {

269

// Automatically add timestamps if not present

270

if (!options.timestamps && !attributes.createdAt) {

271

attributes.createdAt = DataTypes.DATE;

272

attributes.updatedAt = DataTypes.DATE;

273

options.timestamps = true;

274

}

275

});

276

277

sequelize.addHook('afterDefine', (model) => {

278

console.log(`Model ${model.name} defined with attributes:`, Object.keys(model.getAttributes()));

279

});

280

```

281

282

### Connection and Sync Hooks

283

284

Hooks that execute during database connection and synchronization operations.

285

286

```typescript { .api }

287

/**

288

* Connection and sync hooks

289

*/

290

interface ConnectionSyncHooks {

291

/** Before database connection */

292

beforeConnect: (config: any) => Promise<void> | void;

293

/** After database connection */

294

afterConnect: (connection: any, config: any) => Promise<void> | void;

295

296

/** Before connection disconnect */

297

beforeDisconnect: (connection: any) => Promise<void> | void;

298

/** After connection disconnect */

299

afterDisconnect: (connection: any) => Promise<void> | void;

300

301

/** Before connection pool acquire */

302

beforePoolAcquire: (config: any) => Promise<void> | void;

303

/** After connection pool acquire */

304

afterPoolAcquire: (connection: any, config: any) => Promise<void> | void;

305

306

/** Before database sync */

307

beforeSync: (options: SyncOptions) => Promise<void> | void;

308

/** After database sync */

309

afterSync: (options: SyncOptions) => Promise<void> | void;

310

}

311

```

312

313

**Usage Examples:**

314

315

```typescript

316

// Connection hooks

317

sequelize.addHook('beforeConnect', (config) => {

318

console.log('Attempting to connect to database:', config.database);

319

});

320

321

sequelize.addHook('afterConnect', (connection, config) => {

322

console.log('Successfully connected to database');

323

});

324

325

sequelize.addHook('beforeSync', (options) => {

326

console.log('Starting database synchronization');

327

});

328

329

sequelize.addHook('afterSync', (options) => {

330

console.log('Database synchronization completed');

331

});

332

```

333

334

## Hook Management

335

336

### Adding and Removing Hooks

337

338

Methods for managing hooks dynamically.

339

340

```typescript { .api }

341

/**

342

* Add hook to model or sequelize instance

343

* @param hookType - Type of hook

344

* @param name - Hook name (optional)

345

* @param fn - Hook function

346

*/

347

addHook(hookType: string, name: string, fn: Function): void;

348

addHook(hookType: string, fn: Function): void;

349

350

/**

351

* Remove hook from model or sequelize instance

352

* @param hookType - Type of hook

353

* @param name - Hook name

354

*/

355

removeHook(hookType: string, name: string): boolean;

356

357

/**

358

* Check if hook exists

359

* @param hookType - Type of hook

360

* @param name - Hook name

361

*/

362

hasHook(hookType: string, name?: string): boolean;

363

364

/**

365

* Run hooks manually

366

* @param hookType - Type of hook

367

* @param args - Arguments to pass to hooks

368

*/

369

runHooks(hookType: string, ...args: any[]): Promise<void>;

370

```

371

372

**Usage Examples:**

373

374

```typescript

375

// Named hooks can be removed later

376

User.addHook('beforeCreate', 'hashPassword', async (user, options) => {

377

user.hashedPassword = await bcrypt.hash(user.password, 10);

378

});

379

380

User.addHook('beforeCreate', 'setDefaults', (user, options) => {

381

user.isActive = user.isActive !== false;

382

user.role = user.role || 'user';

383

});

384

385

// Remove specific hook

386

User.removeHook('beforeCreate', 'hashPassword');

387

388

// Check if hook exists

389

if (User.hasHook('beforeCreate', 'setDefaults')) {

390

console.log('setDefaults hook is active');

391

}

392

393

// Run hooks manually

394

await User.runHooks('beforeCreate', userInstance, options);

395

```

396

397

### Hook Options and Context

398

399

Advanced hook configuration and context access.

400

401

```typescript { .api }

402

/**

403

* Hook function signature with context

404

*/

405

type HookFunction<T = any> = (

406

this: typeof Model | Model,

407

...args: any[]

408

) => Promise<void> | void;

409

410

/**

411

* Hook options

412

*/

413

interface HookOptions {

414

/** Hook priority (higher runs first) */

415

priority?: number;

416

/** Hook context */

417

context?: any;

418

}

419

```

420

421

**Usage Examples:**

422

423

```typescript

424

// Hook with priority (higher priority runs first)

425

User.addHook('beforeCreate', 'validation', async function(user, options) {

426

// Validation logic

427

}, { priority: 100 });

428

429

User.addHook('beforeCreate', 'defaults', function(user, options) {

430

// Set defaults (runs after validation)

431

}, { priority: 50 });

432

433

// Access model context in hook

434

User.addHook('afterFind', function(result, options) {

435

// 'this' refers to the User model

436

console.log(`Query executed on ${this.name} model`);

437

});

438

439

// Instance method with hook context

440

User.addHook('beforeSave', function(user, options) {

441

// 'this' refers to the model class

442

console.log(`Saving ${this.name} instance`);

443

});

444

```

445

446

## Common Hook Patterns

447

448

### Audit Trail Pattern

449

450

Automatic tracking of data changes.

451

452

```typescript { .api }

453

// Audit trail implementation

454

class AuditLog extends Model {}

455

AuditLog.init({

456

tableName: DataTypes.STRING,

457

recordId: DataTypes.INTEGER,

458

action: DataTypes.ENUM('CREATE', 'UPDATE', 'DELETE'),

459

oldValues: DataTypes.JSON,

460

newValues: DataTypes.JSON,

461

userId: DataTypes.INTEGER,

462

timestamp: DataTypes.DATE

463

}, { sequelize, modelName: 'auditLog' });

464

465

// Add audit hooks to User model

466

User.addHook('afterCreate', async (user, options) => {

467

await AuditLog.create({

468

tableName: 'users',

469

recordId: user.id,

470

action: 'CREATE',

471

newValues: user.toJSON(),

472

userId: options.userId,

473

timestamp: new Date()

474

});

475

});

476

477

User.addHook('afterUpdate', async (user, options) => {

478

await AuditLog.create({

479

tableName: 'users',

480

recordId: user.id,

481

action: 'UPDATE',

482

oldValues: user._previousDataValues,

483

newValues: user.dataValues,

484

userId: options.userId,

485

timestamp: new Date()

486

});

487

});

488

489

User.addHook('afterDestroy', async (user, options) => {

490

await AuditLog.create({

491

tableName: 'users',

492

recordId: user.id,

493

action: 'DELETE',

494

oldValues: user.toJSON(),

495

userId: options.userId,

496

timestamp: new Date()

497

});

498

});

499

```

500

501

### Caching Pattern

502

503

Automatic cache invalidation.

504

505

```typescript { .api }

506

// Cache management hooks

507

const cache = new Map();

508

509

User.addHook('afterCreate', (user, options) => {

510

// Invalidate user list cache

511

cache.delete('users:list');

512

cache.delete(`users:${user.id}`);

513

});

514

515

User.addHook('afterUpdate', (user, options) => {

516

// Invalidate specific user cache

517

cache.delete(`users:${user.id}`);

518

cache.delete('users:list');

519

});

520

521

User.addHook('afterDestroy', (user, options) => {

522

// Remove from cache

523

cache.delete(`users:${user.id}`);

524

cache.delete('users:list');

525

});

526

527

User.addHook('afterFind', (result, options) => {

528

// Cache found results

529

if (Array.isArray(result)) {

530

cache.set('users:list', result);

531

} else if (result) {

532

cache.set(`users:${result.id}`, result);

533

}

534

});

535

```

536

537

### Validation and Sanitization Pattern

538

539

Data cleaning and validation.

540

541

```typescript { .api }

542

// Comprehensive validation and sanitization

543

User.addHook('beforeValidate', (user, options) => {

544

// Trim whitespace

545

if (user.firstName) user.firstName = user.firstName.trim();

546

if (user.lastName) user.lastName = user.lastName.trim();

547

if (user.email) user.email = user.email.trim().toLowerCase();

548

549

// Remove extra spaces

550

if (user.bio) user.bio = user.bio.replace(/\s+/g, ' ').trim();

551

});

552

553

User.addHook('beforeSave', async (user, options) => {

554

// Encrypt sensitive data

555

if (user.changed('socialSecurityNumber')) {

556

user.socialSecurityNumber = await encrypt(user.socialSecurityNumber);

557

}

558

559

// Generate slugs

560

if (user.changed('firstName') || user.changed('lastName')) {

561

user.slug = slugify(`${user.firstName}-${user.lastName}`);

562

}

563

});

564

```

565

566

### Notification Pattern

567

568

Event-driven notifications.

569

570

```typescript { .api }

571

// Notification system

572

User.addHook('afterCreate', async (user, options) => {

573

// Send welcome email

574

await emailService.sendWelcomeEmail(user.email, {

575

firstName: user.firstName

576

});

577

578

// Create notification

579

await Notification.create({

580

userId: user.id,

581

type: 'welcome',

582

message: 'Welcome to our platform!',

583

isRead: false

584

});

585

});

586

587

User.addHook('afterUpdate', async (user, options) => {

588

// Notify on important changes

589

if (user.changed('email')) {

590

await emailService.sendEmailChangeNotification(

591

user.previous('email'),

592

user.email

593

);

594

}

595

596

if (user.changed('password')) {

597

await emailService.sendPasswordChangeNotification(user.email);

598

}

599

});

600

```