key-rotation-manager 1.0.7 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,99 +1,107 @@
1
- # Rotate Key
1
+ # 🔐 Key Rotation Manager
2
2
 
3
- Flexible, file-based key management for Node.js.
4
- It helps you **generate, store, rotate, and retrieve cryptographic keys** with support for expiration, versioning, merge strategies, custom storage logic, and lifecycle events.
3
+ > **Flexible, file-based cryptographic key management for Node.js**
5
4
 
6
- This library is designed for backend systems that need safe, extensible, and transparent key handling.
5
+ A production-ready library for generating, storing, rotating, and retrieving cryptographic keys with support for expiration, versioning, merge strategies, custom storage logic, and lifecycle hooks.
7
6
 
8
- ---
7
+ [![npm version](https://img.shields.io/npm/v/key-rotation-manager)](https://www.npmjs.com/package/key-rotation-manager)
8
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen)](https://nodejs.org/)
9
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
10
 
10
- ## Issues & Bug Reports
11
+ ---
11
12
 
12
- Found a bug or have a feature request?
13
-
14
- **[🐛 Report an Issue](https://github.com/DucAnh2611/key-rotation-manager/issues/new)**
15
-
16
- Please include:
17
- - A clear description of the issue
18
- - Steps to reproduce
19
- - Expected vs actual behavior
20
- - Your environment (Node.js version, OS, package version)
21
- - Code samples if applicable
13
+ ## 📋 Table of Contents
14
+
15
+ - [Features](#-features)
16
+ - [Installation](#-installation)
17
+ - [Quick Start](#-quick-start)
18
+ - [Configuration](#-configuration)
19
+ - [Core Concepts](#-core-concepts)
20
+ - [Creating Keys](#creating-keys)
21
+ - [Retrieving Keys](#retrieving-keys)
22
+ - [Key Rotation](#key-rotation)
23
+ - [Advanced Usage](#-advanced-usage)
24
+ - [Custom Storage Logic](#custom-storage-logic)
25
+ - [Custom Path & File Naming](#custom-path--file-naming)
26
+ - [Hooks & Lifecycle Events](#hooks--lifecycle-events)
27
+ - [Logging](#logging)
28
+ - [Instance Cloning](#instance-cloning)
29
+ - [API Reference](#-api-reference)
30
+ - [Use Cases](#-use-cases)
31
+ - [Changelog](#-changelog)
32
+ - [Contributing](#-contributing)
33
+ - [License](#-license)
22
34
 
23
35
  ---
24
36
 
25
- ## Features
37
+ ## Features
26
38
 
27
- - 🔐 Secure key generation (AES-256-GCM by default)
28
- - 🔄 Key expiration & rotation
29
- - 🗂 File-based storage with configurable structure
30
- - 🧩 Merge & non-merge key strategies
31
- - 🔧 Custom save & get hooks
32
- - 📡 Event-driven lifecycle
33
- - 📁 Automatic `.gitignore` integration
39
+ - 🔐 **Secure Key Generation** - AES-256-CBC encryption with PBKDF2 key derivation
40
+ - 🔄 **Automatic Rotation** - Built-in expiration and rotation support
41
+ - 🗂 **Flexible Storage** - File-based storage with configurable structure
42
+ - 🧩 **Merge Strategies** - Support for single-file or multi-file key storage
43
+ - 🔧 **Extensible** - Custom save/get hooks and storage logic
44
+ - 📡 **Event-Driven** - Lifecycle hooks for key events
45
+ - 📁 **Git Integration** - Automatic `.gitignore` management
46
+ - 🎯 **TypeScript** - Full TypeScript support with comprehensive types
34
47
 
35
48
  ---
36
49
 
37
- ## Installation
50
+ ## 📦 Installation
38
51
 
39
52
  ```bash
40
53
  npm install key-rotation-manager
41
54
  ```
42
55
 
56
+ **Requirements:** Node.js >= 18.0.0
57
+
43
58
  ---
44
59
 
45
- ## Quick Start
60
+ ## 🚀 Quick Start
46
61
 
47
62
  ```typescript
48
- import { create, km } from 'key-rotation-manager';
63
+ import { create } from 'key-rotation-manager';
49
64
 
50
- const keyManager = create({});
51
- // or
52
- const keyManager = km({});
65
+ // Initialize key manager
66
+ const keyManager = create();
53
67
 
68
+ // Generate a new key
54
69
  const { key, path } = await keyManager.newKey({
55
70
  type: 'api',
56
- ...options
71
+ duration: 30,
72
+ unit: 'days',
73
+ rotate: true,
57
74
  });
58
- ```
59
-
60
- This will:
61
- - Create a `keys/` directory base on `{{path}}/{{filename}}.{{fileExt}}`
62
- - Generate a new key
63
- - Save it using default storage rules
64
-
65
- ---
66
75
 
67
- ## Basic Usage
76
+ console.log('Key generated:', key.key);
77
+ console.log('Storage path:', path);
68
78
 
69
- ### Initialize KeyManager
70
-
71
- ```typescript
72
- import { create, km } from 'key-rotation-manager';
73
-
74
- const keyManager = create();
75
- // or
76
- const keyManager = km();
79
+ // Retrieve the key
80
+ const result = await keyManager.getKey({
81
+ path,
82
+ version: key.version,
83
+ });
77
84
  ```
78
85
 
79
- On initialization:
80
- - The key storage folder is created
81
- - `.gitignore` is updated (if enabled)
82
- - A store initialization event is emitted
86
+ ---
87
+
88
+ ## ⚙️ Configuration
83
89
 
84
- ### Default Configuration
90
+ ### Default Options
85
91
 
86
92
  ```typescript
87
93
  {
88
- path: ['keys'],
89
- file: ['{{type}}', 'v', '{{version}}'],
90
- fileSplitor: '_',
91
- fileExt: 'json',
92
- gitIgnore: true, // add resolved path to .gitignore
93
-
94
+ // Storage configuration
95
+ path: ['keys', '{{type}}'], // Directory structure (supports variables)
96
+ file: ['v', '{{version}}'], // File naming pattern
97
+ fileSplitor: '_', // File name separator
98
+ fileExt: 'json', // File extension
99
+ gitIgnore: true, // Auto-update .gitignore (boolean | string | string[])
100
+
101
+ // Cryptographic settings
94
102
  crypto: {
95
- algorithm: 'aes-256-gcm',
96
- kdf: 'scrypt',
103
+ algorithm: 'aes-256-cbc',
104
+ kdf: 'pbkdf2',
97
105
  hashAlgorithm: 'sha256',
98
106
  keyLength: 32,
99
107
  ivLength: 16,
@@ -103,6 +111,7 @@ On initialization:
103
111
  encoding: 'base64',
104
112
  },
105
113
 
114
+ // Version generation
106
115
  versionGenerator: () => Date.now().toString()
107
116
  }
108
117
  ```
@@ -113,222 +122,534 @@ With default settings, keys are stored as:
113
122
 
114
123
  ```
115
124
  keys/
116
- └── api_v_1700000000000.json
125
+ └── {{type}}/
126
+ └── v_{{version}}.json
127
+ ```
128
+
129
+ **Example:** `keys/api/v_1704067200000.json`
130
+
131
+ ### Git Ignore Configuration
132
+
133
+ The `gitIgnore` option controls how the key storage folder is added to `.gitignore`.
134
+
135
+ **Type:** `boolean | string | string[]`
136
+
137
+ | Value | Behavior |
138
+ |-------|----------|
139
+ | `false` | Disable auto-update `.gitignore` |
140
+ | `true` | Uses `path + file + fileExt` pattern |
141
+ | `string \| string[]` | Uses custom path pattern |
142
+
143
+ **Default:** `true` → adds `path + file + fileExt` to `.gitignore`
144
+
145
+ **Variable Replacement:** Any `{{variable}}` in the path will be replaced with `*` in `.gitignore`.
146
+
147
+ **Wildcard Support:** You can use `'*'` directly in the path array for explicit wildcard matching.
148
+
149
+ **Examples:**
150
+
151
+ ```typescript
152
+ // Disable gitignore management
153
+ const km1 = create({ gitIgnore: false });
154
+
155
+ // Use full path pattern (path + file + fileExt)
156
+ const km2 = create({ gitIgnore: true });
157
+ // .gitignore: keys/{{type}}/v_{{version}}.json → keys/*/v_*.json
158
+
159
+ // Custom folder with wildcard (default)
160
+ const km3 = create({ gitIgnore: ['keys', '*'] });
161
+ // .gitignore: keys/*
162
+
163
+ // Custom path with variables
164
+ const km4 = create({ gitIgnore: ['secrets', '{{env}}', 'keys'] });
165
+ // .gitignore: secrets/*/keys
166
+
167
+ // Custom path with explicit wildcard
168
+ const km5 = create({ gitIgnore: ['keys', '{{type}}', '*'] });
169
+ // .gitignore: keys/*/*
170
+
171
+ // Simple string
172
+ const km6 = create({ gitIgnore: 'keys' });
173
+ // .gitignore: keys
117
174
  ```
118
175
 
119
176
  ---
120
177
 
121
- ## Creating Keys
178
+ ## 📚 Core Concepts
179
+
180
+ ### Creating Keys
122
181
 
123
- ### 1. Non-expiring Key
182
+ #### 1. Non-Expiring Key
124
183
 
125
184
  ```typescript
126
- const { key } = await km.newKey({
185
+ const { key, path } = await keyManager.newKey({
127
186
  type: 'service',
128
187
  });
129
188
  ```
130
189
 
131
- ### 2. Expiring / Rotating Key
190
+ #### 2. Expiring Key with Rotation
132
191
 
133
192
  ```typescript
134
- const { key } = await km.newKey({
135
- type: 'service',
193
+ const { key, path } = await keyManager.newKey({
194
+ type: 'api',
136
195
  duration: 30,
137
- unit: 'seconds',
138
- rotate: true,
196
+ unit: 'days', // 'seconds' | 'minutes' | 'hours' | 'days'
197
+ rotate: true, // Enable automatic rotation
139
198
  });
140
199
  ```
141
200
 
142
- The key expires after 30 seconds and requires rotation.
201
+ #### 3. Merge Strategy (Single File)
143
202
 
144
- ### 3. Merge Strategy
145
-
146
- Merge mode stores multiple key versions in a single file.
203
+ Store multiple key versions in a single file:
147
204
 
148
205
  ```typescript
149
- const { key } = await keyManager.newKey({
206
+ const { key, path } = await keyManager.newKey({
150
207
  type: 'service',
151
- duration: 30,
152
- unit: 'seconds',
208
+ merge: true, // Store all versions in one file
209
+ duration: 7,
210
+ unit: 'days',
153
211
  rotate: true,
154
- merge: true, // Merge into 1 file {{path}}/{filename}
155
212
  });
156
213
  ```
157
214
 
158
- ---
215
+ ### Retrieving Keys
159
216
 
160
- ## Custom Path & File Naming
217
+ #### Basic Retrieval
161
218
 
162
219
  ```typescript
163
- import { create } from 'key-rotation-manager';
220
+ const result = await keyManager.getKey({
221
+ path: '/path/to/key/file',
222
+ version: 'v1',
223
+ });
164
224
 
165
- const keyManager = create({
166
- path: ['keys', 'custom'],
167
- file: '{{type}}',
168
- fileExt: 'txt',
169
- ...options,
225
+ // Result structure
226
+ {
227
+ ready: TKeyGenerated | null, // Valid, usable key
228
+ expired: TKeyGenerated | null // Expired key (if rotation occurred)
229
+ }
230
+ ```
231
+
232
+ #### Key Rotation
233
+
234
+ When a key is expired and rotatable, provide rotation options:
235
+
236
+ ```typescript
237
+ const result = await keyManager.getKey({
238
+ path: '/path/to/key/file',
239
+ version: 'v1',
240
+ onRotate: {
241
+ duration: 30,
242
+ unit: 'days',
243
+ rotate: true,
244
+ merge: true, // Optional: merge strategy for new key
245
+ },
170
246
  });
247
+
248
+ if (result.expired) {
249
+ console.log('Key was rotated');
250
+ console.log('Old version:', result.expired.version);
251
+ console.log('New version:', result.ready?.version);
252
+ }
171
253
  ```
172
254
 
173
- Resulting structure:
255
+ #### Handling Missing Keys
174
256
 
257
+ ```typescript
258
+ const result = await keyManager.getKey({
259
+ path: '/path/to/key/file',
260
+ version: 'v1',
261
+ });
262
+
263
+ if (!result.ready) {
264
+ // Key not found or invalid
265
+ console.log('Key unavailable');
266
+ }
175
267
  ```
176
- keys/custom/service.txt
177
268
 
178
- >> .gitignore
179
- keys/custom/*
269
+ ### Key Rotation
270
+
271
+ Key rotation occurs automatically when:
272
+
273
+ 1. A key is expired (`to` date has passed)
274
+ 2. The key has `rotate: true`
275
+ 3. `onRotate` options are provided in `getKey()`
276
+
277
+ **Rotation Flow:**
278
+
279
+ ```
280
+ Expired Key → Check rotate flag → Generate new key → Return both keys
180
281
  ```
181
282
 
182
283
  ---
183
284
 
184
- ## Custom Save Logic
285
+ ## 🔧 Advanced Usage
185
286
 
186
- Override how and where keys are saved:
287
+ ### Custom Storage Logic
288
+
289
+ #### Custom Get Logic
290
+
291
+ Override how keys are retrieved:
187
292
 
188
293
  ```typescript
189
- keyManager.useSaveKey(async () => {
190
- return '/absolute/custom/path/key.json';
294
+ keyManager.useGetKey(async (path, version) => {
295
+ // Your custom retrieval logic
296
+ const data = await fetchFromDatabase(path, version);
297
+
298
+ if (!data) return null;
299
+
300
+ return {
301
+ from: data.createdAt,
302
+ to: data.expiresAt,
303
+ key: data.rawKey,
304
+ hashed: data.hashedKey,
305
+ hashedBytes: 32,
306
+ type: data.type,
307
+ version: data.version,
308
+ rotate: data.rotate,
309
+ };
191
310
  });
192
311
  ```
193
312
 
194
- The returned value becomes `key.path`.
313
+ #### Custom Save Logic
195
314
 
196
- ---
315
+ Override how keys are saved:
316
+
317
+ ```typescript
318
+ keyManager.useSaveKey(async (keyData, options) => {
319
+ // Your custom save logic
320
+ const savedPath = await saveToDatabase(keyData);
321
+ return savedPath; // This becomes key.path
322
+ });
323
+ ```
197
324
 
198
- ## Retrieving Keys
325
+ ### Custom Path & File Naming
199
326
 
200
- ### Rotate Key (Valid)
327
+ Use variables in path and file names:
201
328
 
202
329
  ```typescript
203
- const result = await keyManager.getKey({
204
- path: 'keys/service_merge.json',
205
- version: 'rotate',
206
- onRotate: {
207
- duration: 30,
208
- unit: 'seconds',
209
- rotate: true,
210
- merge: true,
330
+ const keyManager = create({
331
+ path: ['keys', '{{type}}', '{{env}}'],
332
+ file: ['{{version}}', '{{region}}'],
333
+ fileExt: 'json',
334
+ });
335
+
336
+ const { key, path } = await keyManager.newKey(
337
+ {
338
+ type: 'api',
339
+ },
340
+ {
341
+ env: 'production',
342
+ region: 'us-east-1',
343
+ }
344
+ );
345
+
346
+ // Result: keys/api/production/1704067200000_us-east-1.json
347
+ ```
348
+
349
+ **Available Variables:**
350
+ - `{{type}}` - Key type
351
+ - `{{version}}` - Key version
352
+ - Any custom variables passed to `newKey()`
353
+
354
+ ### Hooks & Lifecycle Events
355
+
356
+ > **Available since v1.0.10**
357
+
358
+ Hooks allow you to execute custom logic when specific key lifecycle events occur.
359
+
360
+ #### Available Hooks
361
+
362
+ **Key Manager Hooks:**
363
+
364
+ | Hook | Description | Parameters |
365
+ |------|-------------|------------|
366
+ | `onKeyNotFound` | Fires when key file is not found or version doesn't exist | `(path: string, version: string \| number)` |
367
+ | `onKeyInvalid` | Fires when key validation fails | `(key: TKeyGenerated, message: string, errorOn?: keyof TKeyGenerated)` |
368
+ | `onKeyMissingRotateOption` | Fires when expired key needs rotation but options are missing | `(key: TKeyGenerated, options: TGetKeyOptions)` |
369
+ | `onKeyRenewed` | Fires when a key is successfully rotated | `(getKey: TGetKey, options: TGetKeyOptions)` |
370
+ | `onKeyExpired` | Fires when a key expires but is not renewable | `(path: string, key: TKeyGenerated)` |
371
+
372
+ **Base Hooks:**
373
+
374
+ | Hook | Description | Parameters |
375
+ |------|-------------|------------|
376
+ | `onHookNotFound` | Fires when a hook is called but not registered | `(name: string)` |
377
+ | `onHookOverriding` | Fires when a hook is being overridden | `(name: string)` |
378
+
379
+ #### Setting Hooks
380
+
381
+ **Set Multiple Hooks:**
382
+
383
+ ```typescript
384
+ keyManager.useHooks({
385
+ onKeyNotFound: (path, version) => {
386
+ console.log(`Key not found: ${path}@${version}`);
387
+ },
388
+ onKeyInvalid: (key, message, errorOn) => {
389
+ console.error(`Invalid key: ${message}`, { errorOn, key });
390
+ },
391
+ onKeyRenewed: (getKey, options) => {
392
+ console.log('Key renewed:', {
393
+ expired: getKey.expired?.version,
394
+ ready: getKey.ready?.version,
395
+ });
211
396
  },
212
397
  });
213
398
  ```
214
399
 
215
- Returned structure:
400
+ **Set Single Hook:**
216
401
 
217
402
  ```typescript
218
- {
219
- ready: Key | null,
220
- expired: Key | null
221
- }
403
+ keyManager.setHook('onKeyRenewed', (getKey, options) => {
404
+ console.log('Key was renewed successfully');
405
+ });
222
406
  ```
223
407
 
224
- - `ready` → usable key
225
- - `expired` → expired key (if rotation occurred)
408
+ **Async Hooks:**
409
+
410
+ ```typescript
411
+ keyManager.setHook('onKeyRenewed', async (getKey, options) => {
412
+ await sendNotification({
413
+ message: 'Key was rotated',
414
+ expiredVersion: getKey.expired?.version,
415
+ newVersion: getKey.ready?.version,
416
+ });
417
+ });
226
418
 
227
- ### Rotate Key (Invalid Missing Options)
419
+ keyManager.setHook('onKeyInvalid', async (key, message, errorOn) => {
420
+ await logToDatabase({
421
+ event: 'key_invalid',
422
+ message,
423
+ errorOn,
424
+ keyVersion: key.version,
425
+ });
426
+ });
427
+ ```
428
+
429
+ #### Complete Hook Example
228
430
 
229
431
  ```typescript
230
- await keyManager.getKey({
231
- path: 'keys/service_merge.json',
232
- version: 'rotate-invalid',
432
+ import { create } from 'key-rotation-manager';
433
+
434
+ const keyManager = create({
435
+ versionGenerator: () => 'v1',
436
+ });
437
+
438
+ // Configure all hooks
439
+ keyManager.useHooks({
440
+ onKeyNotFound: (path, version) => {
441
+ console.log(`[Hook] Key not found: ${path}@${version}`);
442
+ },
443
+ onKeyInvalid: (key, message, errorOn) => {
444
+ console.error(`[Hook] Invalid key: ${message}`, {
445
+ version: key.version,
446
+ errorOn,
447
+ });
448
+ },
449
+ onKeyRenewed: async (getKey, options) => {
450
+ console.log('[Hook] Key renewed:', {
451
+ from: getKey.expired?.version,
452
+ to: getKey.ready?.version,
453
+ });
454
+ // Perform async operations (notifications, logging, etc.)
455
+ },
456
+ onKeyMissingRotateOption: (key, options) => {
457
+ console.warn(`[Hook] Missing rotate option for key: ${key.version}`);
458
+ },
459
+ onKeyExpired: (path, key) => {
460
+ console.log(`[Hook] Key expired: ${path}@${key.version}`);
461
+ },
462
+ });
463
+
464
+ // Use the key manager
465
+ const result = await keyManager.getKey({
466
+ path: '/keys/api',
467
+ version: 'v1',
233
468
  });
234
469
  ```
235
470
 
236
- Throws:
471
+ ### Logging
472
+
473
+ #### Custom Logger
237
474
 
475
+ ```typescript
476
+ // Synchronous logger
477
+ keyManager.setLogger((...args) => {
478
+ console.log('[KeyManager]', ...args);
479
+ });
480
+
481
+ // Async logger
482
+ keyManager.setLogger(async (...args) => {
483
+ await logToService(...args);
484
+ });
238
485
  ```
239
- Expired rotate options not provided
486
+
487
+ #### System Logging
488
+
489
+ ```typescript
490
+ await keyManager.sysLog('Key operation completed');
240
491
  ```
241
492
 
242
- ### Non-Rotating Key
493
+ #### Custom Logging
243
494
 
244
495
  ```typescript
245
- const result = await keyManager.getKey({
246
- path: 'path (full path return from km.newKey)',
247
- version: 'version',
496
+ await keyManager.customLog(async (...args) => {
497
+ await customLoggingService.log(...args);
498
+ }, 'Log message');
499
+ ```
500
+
501
+ ### Instance Cloning
502
+
503
+ Create a new instance with shared configuration:
504
+
505
+ ```typescript
506
+ const original = create({ versionGenerator: () => 'v1' });
507
+
508
+ // Clone with additional options
509
+ const cloned = original.clone({
510
+ path: ['custom', 'keys'],
248
511
  });
512
+
513
+ // Cloned instance includes:
514
+ // - Custom save/get hooks
515
+ // - Store path configuration
516
+ // - Logger settings
517
+ // - Hooks settings
249
518
  ```
250
519
 
251
520
  ---
252
521
 
253
- ## Custom Get Logic
522
+ ## 📖 API Reference
254
523
 
255
- Override how keys are retrieved:
524
+ ### `create(options?, only?)`
525
+
526
+ Creates a new KeyManager instance.
527
+
528
+ **Parameters:**
529
+ - `options` (optional): Partial `TModuleOptions`
530
+ - `only` (optional): `boolean` - If `false`, returns a singleton instance
531
+
532
+ **Returns:** `KM` instance
256
533
 
534
+ **Example:**
257
535
  ```typescript
258
- keyManager.useGetKey(async () => {
259
- return {
260
- from: '2025-12-29T01:23:27.882Z',
261
- to: '2099-12-29T01:23:57.882Z',
262
- key: '...',
263
- hashed: '...',
264
- type: 'service',
265
- version: 'version',
536
+ const km = create({ versionGenerator: () => 'v1' });
537
+ // or
538
+ const km = create({}, false); // Singleton instance
539
+ ```
540
+
541
+ ### `newKey(options, variables?)`
542
+
543
+ Generates a new cryptographic key.
544
+
545
+ **Parameters:**
546
+ - `options`: `TGenerateKeyOptions`
547
+ - `type`: `string` - Key type identifier
548
+ - `duration?`: `number` - Expiration duration
549
+ - `unit?`: `'seconds' | 'minutes' | 'hours' | 'days'` - Time unit
550
+ - `rotate?`: `boolean` - Enable rotation
551
+ - `merge?`: `boolean` - Merge strategy
552
+ - `keyLength?`: `number` - Key length in bytes
553
+ - `variables?`: `TKeyVariables` - Custom variables for path/file naming
554
+
555
+ **Returns:** `Promise<{ key: TKeyGenerated, path: string }>`
556
+
557
+ ### `getKey(options)`
558
+
559
+ Retrieves a key by path and version.
560
+
561
+ **Parameters:**
562
+ - `options`: `TGetKeyOptions`
563
+ - `path`: `string` - Storage path
564
+ - `version`: `string` - Key version
565
+ - `onRotate?`: Rotation options (if key is expired and rotatable)
566
+
567
+ **Returns:** `Promise<TGetKey>`
568
+
569
+ **Example:**
570
+ ```typescript
571
+ const result = await keyManager.getKey({
572
+ path: '/keys/api',
573
+ version: 'v1',
574
+ onRotate: {
575
+ duration: 30,
576
+ unit: 'days',
266
577
  rotate: true,
267
- };
578
+ },
268
579
  });
269
580
  ```
270
581
 
271
- Return `null` to indicate an invalid or missing key.
582
+ ### `useHooks(hooks)`
272
583
 
273
- ## Custom logger
584
+ Sets multiple hooks at once.
274
585
 
275
- Override how logger work
586
+ **Parameters:**
587
+ - `hooks`: `Partial<TModuleHooks>`
276
588
 
277
- ```typescript
278
- km.setLogger((...args: unknown[]) => console.log(...args))
279
- // or Async
280
- km.setLogger(async (...args: unknown[]) => console.log(...args))
589
+ **Returns:** `this` (chainable)
281
590
 
282
- km.sysLog(...args);
283
- km.customLog(async (...args: unknown[]) => console.log(...args))
284
- ```
591
+ ### `setHook(name, handler)`
285
592
 
286
- ## Clone instance
593
+ Sets a single hook.
287
594
 
288
- Clone instance to new instance include custom saveKey, getKey and storePath
595
+ **Parameters:**
596
+ - `name`: `keyof TModuleHooks`
597
+ - `handler`: `TModuleHooks[K]`
289
598
 
290
- ```typescript
291
- const k = km();
292
- const k2 = k.clone(options);
293
- ```
599
+ **Returns:** `this` (chainable)
294
600
 
295
601
  ---
296
602
 
297
- ## Events
603
+ ## 🎯 Use Cases
298
604
 
299
- KeyManager emits lifecycle events.
605
+ - **API Key Management** - Secure storage and rotation of API keys
606
+ - **Token Rotation** - Automatic credential rotation for services
607
+ - **Secret Management** - File-based secret storage with expiration
608
+ - **Multi-Version Keys** - Support for multiple key versions simultaneously
609
+ - **Custom Persistence** - Integrate with databases or cloud storage
610
+ - **Backend Services** - Production-ready key management for Node.js applications
300
611
 
301
- ### Store Initialization Event
612
+ ---
302
613
 
303
- ```typescript
304
- import { create, types } from 'rotate-key';
305
- const { EEvent } = types;
306
- const keyManager = create({});
614
+ ## 📝 Changelog
307
615
 
308
- keyManager.once(EEvent.STORE_INIT_FOLDER, ({ storePath }) => {
309
- console.log('Key store initialized at:', storePath);
310
- });
311
- ```
616
+ See the full changelog for each version:
617
+
618
+ - **[v1.0.10](./changelogs/1.0.10.md)** - Hooks system, enhanced gitIgnore configuration
312
619
 
313
620
  ---
314
621
 
315
- ## Use Cases
622
+ ## 🐛 Issues & Bug Reports
316
623
 
317
- - API key management
318
- - Token & credential rotation
319
- - Secure file-based secrets
320
- - Multi-version key handling
321
- - Custom persistence strategies
322
- - Backend Node.js services
624
+ Found a bug or have a feature request?
625
+
626
+ **[🐛 Report an Issue](https://github.com/DucAnh2611/key-rotation-manager/issues/new)**
627
+
628
+ Please include:
629
+ - Clear description of the issue
630
+ - Steps to reproduce
631
+ - Expected vs actual behavior
632
+ - Environment details (Node.js version, OS, package version)
633
+ - Code samples if applicable
323
634
 
324
635
  ---
325
636
 
326
- ## Contributing
637
+ ## 🤝 Contributing
327
638
 
328
639
  Contributions are welcome! Please feel free to submit a Pull Request.
329
640
 
641
+ 1. Fork the repository
642
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
643
+ 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
644
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
645
+ 5. Open a Pull Request
646
+
330
647
  ---
331
648
 
332
- ## License
649
+ ## 📄 License
650
+
651
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
652
+
653
+ ---
333
654
 
334
- MIT
655
+ **Made with ❤️ for secure key management**