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
```