or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

batch-operations.mdconfiguration.mdcore-storage.mdevents-hooks.mdindex.mdraw-data-access.mdstorage-adapters.md
tile.json

events-hooks.mddocs/

0

# Events and Hooks

1

2

Event system for connection monitoring and lifecycle management, plus hooks system for custom functionality on CRUD operations.

3

4

## Capabilities

5

6

### Event System

7

8

Built-in EventEmitter functionality for monitoring Keyv lifecycle and storage adapter events.

9

10

```typescript { .api }

11

class Keyv extends EventManager {

12

/** Emit event with arguments */

13

emit(event: string, ...arguments_: any[]): void;

14

15

/** Add event listener */

16

on(event: string, listener: (...arguments_: any[]) => void): this;

17

18

/** Add one-time event listener */

19

once(event: string, listener: (...arguments_: any[]) => void): void;

20

21

/** Remove event listener */

22

off(event: string, listener: (...arguments_: any[]) => void): void;

23

24

/** Remove all listeners for event */

25

removeAllListeners(event?: string): void;

26

}

27

```

28

29

### Built-in Events

30

31

Keyv emits several lifecycle events automatically.

32

33

```typescript { .api }

34

// Standard events emitted by Keyv

35

// 'error' - Storage adapter errors

36

// 'clear' - When clear() method is called

37

// 'disconnect' - When disconnect() method is called

38

```

39

40

**Usage Examples:**

41

42

```typescript

43

import Keyv from "keyv";

44

import KeyvRedis from "@keyv/redis";

45

46

const keyv = new Keyv(new KeyvRedis('redis://localhost:6379'));

47

48

// Handle connection errors

49

keyv.on('error', (error) => {

50

console.error('Keyv error:', error);

51

// Don't let storage errors crash your app

52

});

53

54

// Monitor clear operations

55

keyv.on('clear', () => {

56

console.log('Cache was cleared');

57

});

58

59

// Monitor disconnections

60

keyv.on('disconnect', () => {

61

console.log('Storage adapter disconnected');

62

});

63

64

// One-time listener

65

keyv.once('error', (error) => {

66

console.log('First error occurred:', error);

67

});

68

69

// Remove specific listener

70

const errorHandler = (error) => console.log(error);

71

keyv.on('error', errorHandler);

72

keyv.off('error', errorHandler); // Remove this specific handler

73

74

// Remove all error listeners

75

keyv.removeAllListeners('error');

76

77

// Disable error events entirely

78

const quietKeyv = new Keyv({ emitErrors: false });

79

// No error events will be emitted

80

```

81

82

### Error Handling Configuration

83

84

Control how errors are handled and emitted.

85

86

```typescript { .api }

87

interface KeyvOptions {

88

/** Whether to emit error events (default: true) */

89

emitErrors?: boolean;

90

/** Whether to throw errors in addition to emitting them (default: false) */

91

throwOnErrors?: boolean;

92

}

93

94

class Keyv {

95

/** Get current throwOnErrors setting */

96

get throwOnErrors(): boolean;

97

98

/** Set throwOnErrors behavior */

99

set throwOnErrors(value: boolean);

100

}

101

```

102

103

**Usage Examples:**

104

105

```typescript

106

// Silent operation (no error events)

107

const silentKeyv = new Keyv({ emitErrors: false });

108

109

// Throw errors instead of just emitting

110

const throwingKeyv = new Keyv({ throwOnErrors: true });

111

112

try {

113

await throwingKeyv.set('key', 'value'); // May throw on storage error

114

} catch (error) {

115

console.error('Operation failed:', error);

116

}

117

118

// Change behavior at runtime

119

const keyv = new Keyv();

120

keyv.throwOnErrors = true; // Now operations will throw on error

121

```

122

123

### Hooks System

124

125

Pre/post operation hooks for custom functionality on all CRUD operations.

126

127

```typescript { .api }

128

enum KeyvHooks {

129

PRE_SET = 'preSet',

130

POST_SET = 'postSet',

131

PRE_GET = 'preGet',

132

POST_GET = 'postGet',

133

PRE_GET_MANY = 'preGetMany',

134

POST_GET_MANY = 'postGetMany',

135

PRE_GET_RAW = 'preGetRaw',

136

POST_GET_RAW = 'postGetRaw',

137

PRE_GET_MANY_RAW = 'preGetManyRaw',

138

POST_GET_MANY_RAW = 'postGetManyRaw',

139

PRE_DELETE = 'preDelete',

140

POST_DELETE = 'postDelete'

141

}

142

143

class HooksManager {

144

/** Add hook handler for specific event */

145

addHandler(event: string, handler: (...arguments_: any[]) => void): void;

146

147

/** Remove specific hook handler */

148

removeHandler(event: string, handler: (...arguments_: any[]) => void): void;

149

150

/** Trigger all handlers for event */

151

trigger(event: string, data: any): void;

152

153

/** Get read-only access to handlers */

154

get handlers(): Map<string, Function[]>;

155

}

156

157

class Keyv {

158

/** Access to hooks manager */

159

hooks: HooksManager;

160

}

161

```

162

163

**Usage Examples:**

164

165

```typescript

166

import Keyv, { KeyvHooks } from "keyv";

167

168

const keyv = new Keyv();

169

170

// Pre-set hook for validation

171

keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data) => {

172

console.log(`Setting key ${data.key} to ${data.value}`);

173

174

// Validation example

175

if (data.key.includes('admin') && !isAuthorized()) {

176

throw new Error('Unauthorized admin key access');

177

}

178

179

// Modify data before storing

180

if (typeof data.value === 'string') {

181

data.value = data.value.toLowerCase();

182

}

183

});

184

185

// Post-set hook for logging

186

keyv.hooks.addHandler(KeyvHooks.POST_SET, (data) => {

187

console.log(`Successfully set key ${data.key}`);

188

189

// Analytics tracking

190

analytics.track('cache_set', {

191

key: data.key,

192

ttl: data.ttl,

193

timestamp: Date.now()

194

});

195

});

196

197

// Pre-get hook for access logging

198

keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data) => {

199

console.log(`Accessing key ${data.key}`);

200

201

// Rate limiting check

202

if (rateLimiter.isExceeded(data.key)) {

203

throw new Error('Rate limit exceeded');

204

}

205

});

206

207

// Post-get hook for cache hit/miss tracking

208

keyv.hooks.addHandler(KeyvHooks.POST_GET, (data) => {

209

const hit = data.value !== undefined;

210

console.log(`Key ${data.key}: ${hit ? 'HIT' : 'MISS'}`);

211

212

metrics.increment(hit ? 'cache.hit' : 'cache.miss');

213

});

214

215

// Pre-delete hook for audit logging

216

keyv.hooks.addHandler(KeyvHooks.PRE_DELETE, (data) => {

217

// data.key can be string or string[] for deleteMany

218

const keys = Array.isArray(data.key) ? data.key : [data.key];

219

auditLog.log('DELETE_ATTEMPT', { keys, user: getCurrentUser() });

220

});

221

222

// Batch operation hooks

223

keyv.hooks.addHandler(KeyvHooks.PRE_GET_MANY, (data) => {

224

console.log(`Batch get for ${data.keys.length} keys`);

225

});

226

227

keyv.hooks.addHandler(KeyvHooks.POST_GET_MANY, (data) => {

228

const hits = data.filter(item => item !== undefined).length;

229

console.log(`Batch get: ${hits}/${data.length} hits`);

230

});

231

232

// Raw data access hooks

233

keyv.hooks.addHandler(KeyvHooks.PRE_GET_RAW, (data) => {

234

console.log(`Raw access for key ${data.key}`);

235

});

236

237

keyv.hooks.addHandler(KeyvHooks.POST_GET_RAW, (data) => {

238

if (data.value && data.value.expires) {

239

const timeLeft = data.value.expires - Date.now();

240

console.log(`Key ${data.key} expires in ${timeLeft}ms`);

241

}

242

});

243

```

244

245

### Advanced Hook Patterns

246

247

Complex hook implementations for common use cases.

248

249

**Cache Warming Hook:**

250

251

```typescript

252

const keyv = new Keyv();

253

254

// Pre-get hook that implements cache warming

255

keyv.hooks.addHandler(KeyvHooks.POST_GET, async (data) => {

256

// If cache miss, try to warm the cache

257

if (data.value === undefined && isWarmable(data.key)) {

258

console.log(`Cache miss for ${data.key}, attempting to warm`);

259

260

try {

261

const freshValue = await fetchFreshData(data.key);

262

await keyv.set(data.key, freshValue, 300000); // 5 minute TTL

263

console.log(`Warmed cache for ${data.key}`);

264

} catch (error) {

265

console.error(`Failed to warm cache for ${data.key}:`, error);

266

}

267

}

268

});

269

270

function isWarmable(key: string): boolean {

271

return key.startsWith('user:') || key.startsWith('product:');

272

}

273

274

async function fetchFreshData(key: string) {

275

// Implement data fetching logic

276

if (key.startsWith('user:')) {

277

const userId = key.split(':')[1];

278

return await database.getUserById(userId);

279

}

280

// ... other data sources

281

}

282

```

283

284

**Encryption Hook:**

285

286

```typescript

287

import crypto from 'crypto';

288

289

const keyv = new Keyv();

290

const secretKey = 'your-secret-key';

291

292

// Encrypt data before storing

293

keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data) => {

294

if (data.key.startsWith('secure:')) {

295

const cipher = crypto.createCipher('aes-256-cbc', secretKey);

296

let encrypted = cipher.update(JSON.stringify(data.value), 'utf8', 'hex');

297

encrypted += cipher.final('hex');

298

data.value = encrypted;

299

}

300

});

301

302

// Decrypt data after retrieving

303

keyv.hooks.addHandler(KeyvHooks.POST_GET, (data) => {

304

if (data.key.startsWith('secure:') && data.value) {

305

const decipher = crypto.createDecipher('aes-256-cbc', secretKey);

306

let decrypted = decipher.update(data.value, 'hex', 'utf8');

307

decrypted += decipher.final('utf8');

308

data.value = JSON.parse(decrypted);

309

}

310

});

311

```

312

313

**Namespace Validation Hook:**

314

315

```typescript

316

const keyv = new Keyv();

317

318

// Validate namespace access

319

keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data) => {

320

const [namespace] = data.key.split(':');

321

322

if (!isAllowedNamespace(namespace)) {

323

throw new Error(`Access denied to namespace: ${namespace}`);

324

}

325

});

326

327

keyv.hooks.addHandler(KeyvHooks.PRE_GET, (data) => {

328

const [namespace] = data.key.split(':');

329

330

if (!isAllowedNamespace(namespace)) {

331

throw new Error(`Access denied to namespace: ${namespace}`);

332

}

333

});

334

335

function isAllowedNamespace(namespace: string): boolean {

336

const allowedNamespaces = ['user', 'session', 'cache', 'temp'];

337

return allowedNamespaces.includes(namespace);

338

}

339

```

340

341

### Hook Error Handling

342

343

Hook errors are emitted as events and can be handled.

344

345

```typescript

346

const keyv = new Keyv();

347

348

// Handle hook errors

349

keyv.hooks.on('error', (error) => {

350

console.error('Hook error:', error.message);

351

// Don't let hook errors break the main operation

352

});

353

354

// Hook that might throw

355

keyv.hooks.addHandler(KeyvHooks.PRE_SET, (data) => {

356

if (data.key === 'forbidden') {

357

throw new Error('This key is forbidden');

358

}

359

});

360

361

try {

362

await keyv.set('forbidden', 'value');

363

} catch (error) {

364

// This won't be thrown due to hook error handling

365

console.log('This won\'t execute');

366

}

367

368

// Hook errors are emitted instead

369

keyv.hooks.on('error', (error) => {

370

console.log('Hook error caught:', error.message); // "This key is forbidden"

371

});

372

```