key-rotation-manager 1.0.9 → 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,98 +1,106 @@
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', '{{type}}'], // FROM 1.0.8 allow using variable: {{...}}
89
- file: ['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',
103
+ algorithm: 'aes-256-cbc',
96
104
  kdf: 'pbkdf2',
97
105
  hashAlgorithm: 'sha256',
98
106
  keyLength: 32,
@@ -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
  ```
@@ -117,259 +126,530 @@ keys/
117
126
  └── v_{{version}}.json
118
127
  ```
119
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
174
+ ```
175
+
120
176
  ---
121
177
 
122
- ## Creating Keys
178
+ ## 📚 Core Concepts
123
179
 
124
- ### 1. Non-expiring Key
180
+ ### Creating Keys
181
+
182
+ #### 1. Non-Expiring Key
125
183
 
126
184
  ```typescript
127
- const { key } = await km.newKey({
185
+ const { key, path } = await keyManager.newKey({
128
186
  type: 'service',
129
187
  });
130
188
  ```
131
189
 
132
- ### 2. Expiring / Rotating Key
190
+ #### 2. Expiring Key with Rotation
133
191
 
134
192
  ```typescript
135
- const { key } = await km.newKey({
136
- type: 'service',
193
+ const { key, path } = await keyManager.newKey({
194
+ type: 'api',
137
195
  duration: 30,
138
- unit: 'seconds',
139
- rotate: true,
196
+ unit: 'days', // 'seconds' | 'minutes' | 'hours' | 'days'
197
+ rotate: true, // Enable automatic rotation
140
198
  });
141
199
  ```
142
200
 
143
- The key expires after 30 seconds and requires rotation.
144
-
145
- ### 3. Merge Strategy
201
+ #### 3. Merge Strategy (Single File)
146
202
 
147
- Merge mode stores multiple key versions in a single file.
203
+ Store multiple key versions in a single file:
148
204
 
149
205
  ```typescript
150
- const { key } = await keyManager.newKey({
206
+ const { key, path } = await keyManager.newKey({
151
207
  type: 'service',
152
- merge: true, // Merge into 1 file {{path}}/{filename}
153
- ...options,
208
+ merge: true, // Store all versions in one file
209
+ duration: 7,
210
+ unit: 'days',
211
+ rotate: true,
154
212
  });
155
213
  ```
156
214
 
157
- ---
215
+ ### Retrieving Keys
158
216
 
159
- ## Custom Path & File Naming
217
+ #### Basic Retrieval
160
218
 
161
219
  ```typescript
162
- import { create } from 'key-rotation-manager';
220
+ const result = await keyManager.getKey({
221
+ path: '/path/to/key/file',
222
+ version: 'v1',
223
+ });
163
224
 
164
- const keyManager = create({
165
- path: ['keys', '{{type}}'],
166
- file: ['{{version}}', '{{custom_variables}}'],
167
- fileExt: 'txt',
168
- ...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
+ },
169
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
+ }
170
253
  ```
171
254
 
172
- Resulting structure:
255
+ #### Handling Missing Keys
173
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
+ }
174
267
  ```
175
- path: ['keys', '{{type}}']
176
- file: ['{{version}}', '{{custom_variables}}']
177
- fileExt: "txt"
178
- type: "service"
179
- variables: { custom_variables: "example" }
180
268
 
181
- getKey({ type }, variables) -> keys/service/17000000000_example.txt
269
+ ### Key Rotation
270
+
271
+ Key rotation occurs automatically when:
182
272
 
183
- >> .gitignore
184
- keys/*/*_*.txt
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
185
281
  ```
186
282
 
187
283
  ---
188
284
 
189
- ## Custom Save Logic
285
+ ## 🔧 Advanced Usage
190
286
 
191
- Override how and where keys are saved:
287
+ ### Custom Storage Logic
288
+
289
+ #### Custom Get Logic
290
+
291
+ Override how keys are retrieved:
192
292
 
193
293
  ```typescript
194
- keyManager.useSaveKey(async () => {
195
- 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
+ };
196
310
  });
197
311
  ```
198
312
 
199
- The returned value becomes `key.path`.
313
+ #### Custom Save Logic
200
314
 
201
- ---
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
+ ```
202
324
 
203
- ## Retrieving Keys
325
+ ### Custom Path & File Naming
204
326
 
205
- ### Rotate Key (Valid)
327
+ Use variables in path and file names:
206
328
 
207
329
  ```typescript
208
- const result = await keyManager.getKey({
209
- path: 'path (full path return from km.newKey)',
210
- version: 'rotate',
211
- onRotate: {
212
- duration: 30,
213
- unit: 'seconds',
214
- rotate: true,
215
- 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',
216
339
  },
217
- }, eventHandlers);
340
+ {
341
+ env: 'production',
342
+ region: 'us-east-1',
343
+ }
344
+ );
345
+
346
+ // Result: keys/api/production/1704067200000_us-east-1.json
218
347
  ```
219
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
+
220
383
  ```typescript
221
- // from 1.0.8 getKey allow user use events
222
-
223
- type TGetKeyEvents = {
224
- /**
225
- * This will fire when key is rotatable but expired and missing options to rotate
226
- */
227
- onMissingRotateOption: (key: TKeyGenerated, options: TGetKeyOptions) => void | Promise<void>;
228
- /**
229
- * This will fire when key is invalid includes validate types, from date, to date, etc...
230
- */
231
- onKeyInvalid: (
232
- key: TKeyGenerated,
233
- message: string,
234
- errorOn?: keyof TKeyGenerated
235
- ) => void | Promise<void>;
236
- /**
237
- * This will fire when key is renewed
238
- */
239
- onKeyRenewed: (getKey: TGetKey, options: TGetKeyOptions['onRotate']) => void | Promise<void>;
240
- /**
241
- * This will fire when key file is not found or version is not found in file
242
- * @description
243
- * IMPORTANT: every file invalid should return `{}` as key data and this will caused this event to be fired
244
- * - Invalid file (file not found or not valid json)
245
- * - Version not found in file
246
- * - From date in future
247
- * - Properties in key data is not valid types
248
- * - hashedBytes is less than 0
249
- */
250
- onKeyNotFound: (path: string, version: string | number) => void | Promise<void>;
251
- onExpired: (path: string, key: TKeyGenerated) => void | Promise<void>;
252
- };
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
+ });
396
+ },
397
+ });
253
398
  ```
254
399
 
255
- Returned structure:
400
+ **Set Single Hook:**
256
401
 
257
402
  ```typescript
258
- {
259
- ready: Key | null,
260
- expired: Key | null
261
- }
403
+ keyManager.setHook('onKeyRenewed', (getKey, options) => {
404
+ console.log('Key was renewed successfully');
405
+ });
262
406
  ```
263
407
 
264
- - `ready` → usable key
265
- - `expired` → expired key
408
+ **Async Hooks:**
266
409
 
267
- ### Rotate Key (Invalid – Missing Options)
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
+ });
418
+
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
268
430
 
269
431
  ```typescript
270
- await keyManager.getKey({
271
- path: 'path (full path return from km.newKey)',
272
- 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',
273
468
  });
274
469
  ```
275
470
 
276
- Return:
471
+ ### Logging
472
+
473
+ #### Custom Logger
474
+
475
+ ```typescript
476
+ // Synchronous logger
477
+ keyManager.setLogger((...args) => {
478
+ console.log('[KeyManager]', ...args);
479
+ });
277
480
 
481
+ // Async logger
482
+ keyManager.setLogger(async (...args) => {
483
+ await logToService(...args);
484
+ });
278
485
  ```
279
- { expired: null, ready: null }
486
+
487
+ #### System Logging
488
+
489
+ ```typescript
490
+ await keyManager.sysLog('Key operation completed');
280
491
  ```
281
492
 
282
- ### Non-Rotating Key
493
+ #### Custom Logging
283
494
 
284
495
  ```typescript
285
- const result = await keyManager.getKey({
286
- path: 'path (full path return from km.newKey)',
287
- 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'],
288
511
  });
512
+
513
+ // Cloned instance includes:
514
+ // - Custom save/get hooks
515
+ // - Store path configuration
516
+ // - Logger settings
517
+ // - Hooks settings
289
518
  ```
290
519
 
291
520
  ---
292
521
 
293
- ## Custom Get Logic
522
+ ## 📖 API Reference
294
523
 
295
- 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
296
533
 
534
+ **Example:**
297
535
  ```typescript
298
- keyManager.useGetKey(async () => {
299
- return {
300
- from: '2025-12-29T01:23:27.882Z',
301
- to: '2099-12-29T01:23:57.882Z',
302
- key: '...',
303
- hashed: '...',
304
- hashedBytes: 16,
305
- type: 'service',
306
- 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',
307
577
  rotate: true,
308
- };
578
+ },
309
579
  });
310
580
  ```
311
581
 
312
- Return `null` to indicate an invalid or missing key.
582
+ ### `useHooks(hooks)`
313
583
 
314
- ## Custom logger
584
+ Sets multiple hooks at once.
315
585
 
316
- Override how logger work
586
+ **Parameters:**
587
+ - `hooks`: `Partial<TModuleHooks>`
317
588
 
318
- ```typescript
319
- km.setLogger((...args: unknown[]) => console.log(...args))
320
- // or Async
321
- km.setLogger(async (...args: unknown[]) => console.log(...args))
589
+ **Returns:** `this` (chainable)
322
590
 
323
- km.sysLog(...args);
324
- km.customLog(async (...args: unknown[]) => console.log(...args))
325
- ```
591
+ ### `setHook(name, handler)`
326
592
 
327
- ## Clone instance
593
+ Sets a single hook.
328
594
 
329
- Clone instance to new instance include custom saveKey, getKey and storePath
595
+ **Parameters:**
596
+ - `name`: `keyof TModuleHooks`
597
+ - `handler`: `TModuleHooks[K]`
330
598
 
331
- ```typescript
332
- const k = km();
333
- const k2 = k.clone(options);
334
- ```
599
+ **Returns:** `this` (chainable)
335
600
 
336
601
  ---
337
602
 
338
- ## Events
603
+ ## 🎯 Use Cases
339
604
 
340
- 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
341
611
 
342
- ### Store Initialization Event
612
+ ---
343
613
 
344
- ```typescript
345
- import { create, types } from 'rotate-key';
346
- const { EEvent } = types;
347
- const keyManager = create({});
614
+ ## 📝 Changelog
348
615
 
349
- keyManager.once(EEvent.STORE_INIT_FOLDER, ({ storePath }) => {
350
- console.log('Key store initialized at:', storePath);
351
- });
352
- ```
616
+ See the full changelog for each version:
617
+
618
+ - **[v1.0.10](./changelogs/1.0.10.md)** - Hooks system, enhanced gitIgnore configuration
353
619
 
354
620
  ---
355
621
 
356
- ## Use Cases
622
+ ## 🐛 Issues & Bug Reports
357
623
 
358
- - API key management
359
- - Token & credential rotation
360
- - Secure file-based secrets
361
- - Multi-version key handling
362
- - Custom persistence strategies
363
- - 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
364
634
 
365
635
  ---
366
636
 
367
- ## Contributing
637
+ ## 🤝 Contributing
368
638
 
369
639
  Contributions are welcome! Please feel free to submit a Pull Request.
370
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
+
371
647
  ---
372
648
 
373
- ## License
649
+ ## 📄 License
650
+
651
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
652
+
653
+ ---
374
654
 
375
- MIT
655
+ **Made with ❤️ for secure key management**