@nauth-toolkit/core 0.1.0 → 0.1.3

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.
Files changed (184) hide show
  1. package/LICENSE +90 -0
  2. package/README.md +30 -0
  3. package/package.json +7 -2
  4. package/jest.config.js +0 -15
  5. package/jest.setup.ts +0 -6
  6. package/src/adapters/database-columns.ts +0 -165
  7. package/src/adapters/express.adapter.ts +0 -385
  8. package/src/adapters/fastify.adapter.ts +0 -416
  9. package/src/adapters/index.ts +0 -16
  10. package/src/adapters/storage.factory.ts +0 -143
  11. package/src/bootstrap.ts +0 -374
  12. package/src/dto/auth-challenge.dto.ts +0 -231
  13. package/src/dto/auth-response.dto.ts +0 -253
  14. package/src/dto/challenge-response.dto.ts +0 -234
  15. package/src/dto/change-password-request.dto.ts +0 -50
  16. package/src/dto/change-password-response.dto.ts +0 -29
  17. package/src/dto/change-password.dto.ts +0 -57
  18. package/src/dto/error-response.dto.ts +0 -136
  19. package/src/dto/get-available-methods.dto.ts +0 -55
  20. package/src/dto/get-challenge-data-response.dto.ts +0 -28
  21. package/src/dto/get-challenge-data.dto.ts +0 -69
  22. package/src/dto/get-client-info.dto.ts +0 -104
  23. package/src/dto/get-device-token-response.dto.ts +0 -25
  24. package/src/dto/get-events-by-type.dto.ts +0 -76
  25. package/src/dto/get-ip-address-response.dto.ts +0 -24
  26. package/src/dto/get-mfa-status.dto.ts +0 -94
  27. package/src/dto/get-risk-assessment-history.dto.ts +0 -39
  28. package/src/dto/get-session-id-response.dto.ts +0 -25
  29. package/src/dto/get-setup-data-response.dto.ts +0 -31
  30. package/src/dto/get-setup-data.dto.ts +0 -75
  31. package/src/dto/get-suspicious-activity.dto.ts +0 -42
  32. package/src/dto/get-user-agent-response.dto.ts +0 -23
  33. package/src/dto/get-user-auth-history.dto.ts +0 -95
  34. package/src/dto/get-user-by-email.dto.ts +0 -61
  35. package/src/dto/get-user-by-id.dto.ts +0 -46
  36. package/src/dto/get-user-devices.dto.ts +0 -53
  37. package/src/dto/get-user-response.dto.ts +0 -17
  38. package/src/dto/has-provider.dto.ts +0 -56
  39. package/src/dto/index.ts +0 -57
  40. package/src/dto/is-trusted-device-response.dto.ts +0 -34
  41. package/src/dto/list-providers-response.dto.ts +0 -23
  42. package/src/dto/login.dto.ts +0 -95
  43. package/src/dto/logout-all-response.dto.ts +0 -24
  44. package/src/dto/logout-all.dto.ts +0 -65
  45. package/src/dto/logout-response.dto.ts +0 -25
  46. package/src/dto/logout.dto.ts +0 -64
  47. package/src/dto/refresh-token.dto.ts +0 -36
  48. package/src/dto/remove-devices.dto.ts +0 -85
  49. package/src/dto/resend-code-response.dto.ts +0 -32
  50. package/src/dto/resend-code.dto.ts +0 -51
  51. package/src/dto/reset-password.dto.ts +0 -115
  52. package/src/dto/respond-challenge.dto.ts +0 -272
  53. package/src/dto/set-mfa-exemption.dto.ts +0 -112
  54. package/src/dto/set-must-change-password-response.dto.ts +0 -27
  55. package/src/dto/set-must-change-password.dto.ts +0 -46
  56. package/src/dto/set-preferred-method.dto.ts +0 -80
  57. package/src/dto/setup-mfa.dto.ts +0 -98
  58. package/src/dto/signup.dto.ts +0 -174
  59. package/src/dto/social-auth.dto.ts +0 -422
  60. package/src/dto/trust-device-response.dto.ts +0 -30
  61. package/src/dto/trust-device.dto.ts +0 -9
  62. package/src/dto/update-user-attributes-request.dto.ts +0 -51
  63. package/src/dto/user-response.dto.ts +0 -138
  64. package/src/dto/user-update.dto.ts +0 -222
  65. package/src/dto/verify-email.dto.ts +0 -313
  66. package/src/dto/verify-mfa-code.dto.ts +0 -103
  67. package/src/dto/verify-phone-by-sub.dto.ts +0 -78
  68. package/src/dto/verify-phone.dto.ts +0 -245
  69. package/src/entities/auth-audit.entity.ts +0 -232
  70. package/src/entities/challenge-session.entity.ts +0 -116
  71. package/src/entities/index.ts +0 -29
  72. package/src/entities/login-attempt.entity.ts +0 -64
  73. package/src/entities/mfa-device.entity.ts +0 -151
  74. package/src/entities/rate-limit.entity.ts +0 -44
  75. package/src/entities/session.entity.ts +0 -180
  76. package/src/entities/social-account.entity.ts +0 -96
  77. package/src/entities/storage-lock.entity.ts +0 -39
  78. package/src/entities/trusted-device.entity.ts +0 -112
  79. package/src/entities/user.entity.ts +0 -243
  80. package/src/entities/verification-token.entity.ts +0 -141
  81. package/src/enums/auth-audit-event-type.enum.ts +0 -360
  82. package/src/enums/error-codes.enum.ts +0 -420
  83. package/src/enums/mfa-method.enum.ts +0 -97
  84. package/src/enums/risk-factor.enum.ts +0 -111
  85. package/src/exceptions/nauth.exception.ts +0 -231
  86. package/src/handlers/auth.handler.ts +0 -260
  87. package/src/handlers/client-info.handler.ts +0 -101
  88. package/src/handlers/csrf.handler.ts +0 -156
  89. package/src/handlers/token-delivery.handler.ts +0 -118
  90. package/src/index.ts +0 -118
  91. package/src/interfaces/client-info.interface.ts +0 -85
  92. package/src/interfaces/config.interface.ts +0 -2135
  93. package/src/interfaces/entities.interface.ts +0 -226
  94. package/src/interfaces/index.ts +0 -15
  95. package/src/interfaces/logger.interface.ts +0 -283
  96. package/src/interfaces/mfa-provider.interface.ts +0 -154
  97. package/src/interfaces/oauth.interface.ts +0 -148
  98. package/src/interfaces/provider.interface.ts +0 -47
  99. package/src/interfaces/social-auth-provider.interface.ts +0 -131
  100. package/src/interfaces/storage-adapter.interface.ts +0 -82
  101. package/src/interfaces/template.interface.ts +0 -510
  102. package/src/interfaces/token-verifier.interface.ts +0 -110
  103. package/src/internal.ts +0 -178
  104. package/src/platform/interfaces.ts +0 -299
  105. package/src/schemas/auth-config.schema.ts +0 -646
  106. package/src/services/adaptive-mfa-decision.service.spec.ts +0 -1058
  107. package/src/services/adaptive-mfa-decision.service.ts +0 -457
  108. package/src/services/auth-audit.service.spec.ts +0 -675
  109. package/src/services/auth-audit.service.ts +0 -558
  110. package/src/services/auth-challenge-helper.service.spec.ts +0 -3227
  111. package/src/services/auth-challenge-helper.service.ts +0 -825
  112. package/src/services/auth-flow-context-builder.service.ts +0 -520
  113. package/src/services/auth-flow-rules.ts +0 -202
  114. package/src/services/auth-flow-state-definitions.ts +0 -190
  115. package/src/services/auth-flow-state-machine.service.ts +0 -207
  116. package/src/services/auth-flow-state-machine.types.ts +0 -316
  117. package/src/services/auth.service.spec.ts +0 -4195
  118. package/src/services/auth.service.ts +0 -3727
  119. package/src/services/challenge.service.spec.ts +0 -1363
  120. package/src/services/challenge.service.ts +0 -696
  121. package/src/services/client-info.service.spec.ts +0 -572
  122. package/src/services/client-info.service.ts +0 -374
  123. package/src/services/csrf.service.ts +0 -54
  124. package/src/services/email-verification.service.spec.ts +0 -1229
  125. package/src/services/email-verification.service.ts +0 -578
  126. package/src/services/geo-location.service.spec.ts +0 -603
  127. package/src/services/geo-location.service.ts +0 -599
  128. package/src/services/index.ts +0 -13
  129. package/src/services/jwt.service.spec.ts +0 -882
  130. package/src/services/jwt.service.ts +0 -621
  131. package/src/services/mfa-base.service.spec.ts +0 -246
  132. package/src/services/mfa-base.service.ts +0 -611
  133. package/src/services/mfa.service.spec.ts +0 -693
  134. package/src/services/mfa.service.ts +0 -960
  135. package/src/services/password.service.spec.ts +0 -166
  136. package/src/services/password.service.ts +0 -309
  137. package/src/services/phone-verification.service.spec.ts +0 -1120
  138. package/src/services/phone-verification.service.ts +0 -751
  139. package/src/services/risk-detection.service.spec.ts +0 -1292
  140. package/src/services/risk-detection.service.ts +0 -1012
  141. package/src/services/risk-scoring.service.spec.ts +0 -204
  142. package/src/services/risk-scoring.service.ts +0 -131
  143. package/src/services/session.service.spec.ts +0 -1293
  144. package/src/services/session.service.ts +0 -803
  145. package/src/services/social-account.service.spec.ts +0 -725
  146. package/src/services/social-auth-base.service.spec.ts +0 -418
  147. package/src/services/social-auth-base.service.ts +0 -581
  148. package/src/services/social-auth.service.spec.ts +0 -238
  149. package/src/services/social-auth.service.ts +0 -436
  150. package/src/services/social-provider-registry.service.spec.ts +0 -238
  151. package/src/services/social-provider-registry.service.ts +0 -122
  152. package/src/services/trusted-device.service.spec.ts +0 -505
  153. package/src/services/trusted-device.service.ts +0 -339
  154. package/src/storage/account-lockout-storage.service.spec.ts +0 -310
  155. package/src/storage/account-lockout-storage.service.ts +0 -89
  156. package/src/storage/index.ts +0 -3
  157. package/src/storage/memory-storage.adapter.ts +0 -443
  158. package/src/storage/rate-limit-storage.service.spec.ts +0 -247
  159. package/src/storage/rate-limit-storage.service.ts +0 -38
  160. package/src/templates/html-template.engine.spec.ts +0 -161
  161. package/src/templates/html-template.engine.ts +0 -688
  162. package/src/templates/index.ts +0 -7
  163. package/src/utils/common-passwords.spec.ts +0 -230
  164. package/src/utils/common-passwords.ts +0 -170
  165. package/src/utils/context-storage.ts +0 -188
  166. package/src/utils/cookie-names.util.ts +0 -67
  167. package/src/utils/cookies.util.ts +0 -94
  168. package/src/utils/index.ts +0 -12
  169. package/src/utils/ip-extractor.spec.ts +0 -330
  170. package/src/utils/ip-extractor.ts +0 -220
  171. package/src/utils/nauth-logger.spec.ts +0 -388
  172. package/src/utils/nauth-logger.ts +0 -215
  173. package/src/utils/pii-redactor.spec.ts +0 -130
  174. package/src/utils/pii-redactor.ts +0 -288
  175. package/src/utils/setup/get-repositories.ts +0 -140
  176. package/src/utils/setup/init-services.ts +0 -422
  177. package/src/utils/setup/init-social.ts +0 -189
  178. package/src/utils/setup/init-storage.ts +0 -94
  179. package/src/utils/setup/register-mfa.ts +0 -165
  180. package/src/utils/setup/run-nauth-migrations.ts +0 -61
  181. package/src/utils/token-delivery-policy.ts +0 -38
  182. package/src/validators/template.validator.ts +0 -219
  183. package/tsconfig.json +0 -37
  184. package/tsconfig.lint.json +0 -6
@@ -1,89 +0,0 @@
1
- import { AccountLockoutStorage, StorageAdapter } from '../interfaces/storage-adapter.interface';
2
-
3
- /**
4
- * Account lockout storage implementation using StorageAdapter (Platform-Agnostic)
5
- *
6
- * SECURITY: Uses IP addresses instead of user identifiers to prevent
7
- * attackers from locking out legitimate users by guessing their email/username.
8
- */
9
- export class AccountLockoutStorageService implements AccountLockoutStorage {
10
- private readonly keyPrefix = 'nauth:lockout:ip:';
11
- private readonly lockKeyPrefix = 'nauth:locked:ip:';
12
-
13
- constructor(private readonly storageAdapter: StorageAdapter) {}
14
-
15
- /**
16
- * Record failed login attempt for an IP address
17
- * @param ipAddress - IP address that made the failed attempt
18
- * @returns Number of failed attempts for this IP
19
- */
20
- async recordFailedAttempt(ipAddress: string): Promise<number> {
21
- const key = this.getKey(ipAddress);
22
- return await this.storageAdapter.incr(key);
23
- }
24
-
25
- /**
26
- * Get failed attempts count for an IP address
27
- * @param ipAddress - IP address to check
28
- * @returns Number of failed attempts
29
- */
30
- async getFailedAttempts(ipAddress: string): Promise<number> {
31
- const key = this.getKey(ipAddress);
32
- const value = await this.storageAdapter.get(key);
33
- return value ? parseInt(value, 10) : 0;
34
- }
35
-
36
- /**
37
- * Check if an IP address is locked out
38
- * @param ipAddress - IP address to check
39
- * @returns True if IP is locked out
40
- */
41
- async isAccountLocked(ipAddress: string): Promise<boolean> {
42
- const lockKey = this.getLockKey(ipAddress);
43
- return await this.storageAdapter.exists(lockKey);
44
- }
45
-
46
- /**
47
- * Lock an IP address for a specified duration
48
- * @param ipAddress - IP address to lock
49
- * @param duration - Lock duration in seconds
50
- * @param reason - Reason for lockout
51
- */
52
- async blockIpAdresss(ipAddress: string, duration: number, reason: string): Promise<void> {
53
- const lockKey = this.getLockKey(ipAddress);
54
- const lockData = JSON.stringify({
55
- reason,
56
- lockedAt: new Date().toISOString(),
57
- lockedUntil: new Date(Date.now() + duration * 1000).toISOString(),
58
- });
59
-
60
- await this.storageAdapter.set(lockKey, lockData, duration);
61
- }
62
-
63
- /**
64
- * Unlock an IP address and reset failed attempts
65
- * @param ipAddress - IP address to unlock
66
- */
67
- async unblockIPAdress(ipAddress: string): Promise<void> {
68
- const lockKey = this.getLockKey(ipAddress);
69
- await this.storageAdapter.del(lockKey);
70
- await this.resetFailedAttempts(ipAddress);
71
- }
72
-
73
- /**
74
- * Reset failed attempts counter for an IP address
75
- * @param ipAddress - IP address to reset
76
- */
77
- async resetFailedAttempts(ipAddress: string): Promise<void> {
78
- const key = this.getKey(ipAddress);
79
- await this.storageAdapter.del(key);
80
- }
81
-
82
- private getKey(ipAddress: string): string {
83
- return `${this.keyPrefix}${ipAddress}`;
84
- }
85
-
86
- private getLockKey(ipAddress: string): string {
87
- return `${this.lockKeyPrefix}${ipAddress}`;
88
- }
89
- }
@@ -1,3 +0,0 @@
1
- export * from './memory-storage.adapter';
2
- export * from './rate-limit-storage.service';
3
- export * from './account-lockout-storage.service';
@@ -1,443 +0,0 @@
1
- import { StorageAdapter } from '../interfaces/storage-adapter.interface';
2
-
3
- /**
4
- * Internal structure for storing values with optional expiration
5
- */
6
- interface StoredValue {
7
- value: string;
8
- expiresAt?: number; // Unix timestamp in milliseconds
9
- }
10
-
11
- /**
12
- * In-memory storage adapter for development and single-server deployments
13
- *
14
- * ⚠️ CRITICAL LIMITATIONS FOR PRODUCTION:
15
- * - Data is lost on server restart
16
- * - Data is NOT shared across multiple server instances/containers
17
- * - Rate limiting may be bypassed in ECS/multi-container deployments
18
- * - NOT suitable for production clusters with multiple containers
19
- *
20
- * RECOMMENDATIONS:
21
- * - Single ECS task: Acceptable (data lost on restart)
22
- * - Multi-task/container ECS: Use Redis adapter (coming soon)
23
- * - Production: Plan to implement Redis-backed storage adapter
24
- *
25
- * CURRENT BEHAVIOR:
26
- * Rate limiting works per-container, not globally across containers
27
- *
28
- * @example
29
- * ```typescript
30
- * const storage = new MemoryStorageAdapter();
31
- * await storage.initialize();
32
- * await storage.set('key', 'value', 60); // Set with 60 second TTL
33
- * const value = await storage.get('key');
34
- * ```
35
- */
36
- export class MemoryStorageAdapter implements StorageAdapter {
37
- // Main key-value store with expiration support
38
- private store: Map<string, StoredValue> = new Map();
39
-
40
- // Hash storage for complex data structures (similar to Redis hashes)
41
- private hashes: Map<string, Map<string, string>> = new Map();
42
-
43
- // List storage for ordered collections (similar to Redis lists)
44
- private lists: Map<string, string[]> = new Map();
45
-
46
- // Interval timer for automatic cleanup of expired keys
47
- private cleanupInterval: NodeJS.Timeout | null = null;
48
-
49
- /**
50
- * Initialize the storage adapter
51
- * Starts a background cleanup job to remove expired keys
52
- */
53
- async initialize(): Promise<void> {
54
- // Start cleanup interval to remove expired keys every minute
55
- this.cleanupInterval = setInterval(() => {
56
- this.cleanupExpired();
57
- }, 60000); // 60,000ms = 1 minute
58
- }
59
-
60
- /**
61
- * Check if the storage adapter is healthy and operational
62
- * @returns Always returns true for in-memory storage
63
- */
64
- async isHealthy(): Promise<boolean> {
65
- return true;
66
- }
67
-
68
- // ============================================================================
69
- // Basic Key-Value Operations
70
- // ============================================================================
71
-
72
- /**
73
- * Get a value by key
74
- * Automatically removes and returns null if the key has expired
75
- *
76
- * @param key - The key to retrieve
77
- * @returns The stored value or null if not found/expired
78
- */
79
- async get(key: string): Promise<string | null> {
80
- const stored = this.store.get(key);
81
-
82
- // Key doesn't exist
83
- if (!stored) return null;
84
-
85
- // Check if key has expired
86
- if (stored.expiresAt && stored.expiresAt < Date.now()) {
87
- this.store.delete(key); // Clean up expired key
88
- return null;
89
- }
90
-
91
- return stored.value;
92
- }
93
-
94
- /**
95
- * Set a key-value pair with optional TTL (time to live)
96
- *
97
- * @param key - The key to store
98
- * @param value - The value to store
99
- * @param ttl - Time to live in seconds (optional)
100
- */
101
- async set(key: string, value: string, ttlSeconds?: number, options?: { nx?: boolean }): Promise<string | null> {
102
- // For NX option, check if key exists and is not expired
103
- if (options?.nx) {
104
- const existing = this.store.get(key);
105
- if (existing) {
106
- // Check if existing key is expired
107
- if (existing.expiresAt && existing.expiresAt < Date.now()) {
108
- // Key exists but is expired - treat as non-existent and allow set
109
- this.store.delete(key);
110
- } else {
111
- // Key exists and is not expired - NX failed
112
- return null;
113
- }
114
- }
115
- }
116
-
117
- const stored: StoredValue = { value };
118
-
119
- // If TTL is provided, calculate expiration timestamp
120
- if (ttlSeconds) {
121
- stored.expiresAt = Date.now() + ttlSeconds * 1000; // Convert seconds to milliseconds
122
- }
123
-
124
- this.store.set(key, stored);
125
- return value;
126
- }
127
-
128
- /**
129
- * Delete a key from storage
130
- * @param key - The key to delete
131
- */
132
- async del(key: string): Promise<void> {
133
- this.store.delete(key);
134
- }
135
-
136
- /**
137
- * Check if a key exists and is not expired
138
- * @param key - The key to check
139
- * @returns True if key exists and is valid, false otherwise
140
- */
141
- async exists(key: string): Promise<boolean> {
142
- const value = await this.get(key);
143
- return value !== null;
144
- }
145
-
146
- // ============================================================================
147
- // Atomic Operations (for counters and rate limiting)
148
- // ============================================================================
149
-
150
- /**
151
- * Increment a counter stored at key
152
- * If the key doesn't exist, it's initialized to 0 before incrementing
153
- * Preserves TTL if the key already exists with an expiration
154
- *
155
- * @param key - The key to increment
156
- * @param ttlSeconds - Optional TTL in seconds to set when creating a new key (only applied if key doesn't exist)
157
- * @returns The new value after incrementing
158
- */
159
- async incr(key: string, ttlSeconds?: number): Promise<number> {
160
- const stored = this.store.get(key);
161
-
162
- // Check if key exists and is not expired
163
- let currentValue = '0';
164
- let existingExpiry: number | undefined;
165
- const wasNewKey = !stored || (stored.expiresAt && stored.expiresAt < Date.now());
166
-
167
- if (stored) {
168
- if (stored.expiresAt && stored.expiresAt < Date.now()) {
169
- // Key expired - treat as non-existent
170
- this.store.delete(key);
171
- } else {
172
- // Key exists and is valid - preserve expiry
173
- currentValue = stored.value;
174
- existingExpiry = stored.expiresAt;
175
- }
176
- }
177
-
178
- const newValue = (parseInt(currentValue || '0', 10) + 1).toString();
179
- const newStored: StoredValue = { value: newValue };
180
-
181
- // Use provided TTL for new keys, otherwise preserve existing expiry
182
- if (wasNewKey && ttlSeconds !== undefined) {
183
- newStored.expiresAt = Date.now() + ttlSeconds * 1000;
184
- } else if (existingExpiry) {
185
- newStored.expiresAt = existingExpiry;
186
- }
187
-
188
- this.store.set(key, newStored);
189
- return parseInt(newValue, 10);
190
- }
191
-
192
- /**
193
- * Decrement a counter stored at key
194
- * If the key doesn't exist, it's initialized to 0 before decrementing
195
- *
196
- * @param key - The key to decrement
197
- * @returns The new value after decrementing
198
- */
199
- async decr(key: string): Promise<number> {
200
- const current = await this.get(key);
201
- const newValue = (parseInt(current || '0', 10) - 1).toString();
202
- await this.set(key, newValue);
203
- return parseInt(newValue, 10);
204
- }
205
-
206
- /**
207
- * Set expiration time on an existing key
208
- * @param key - The key to set expiration on
209
- * @param ttl - Time to live in seconds
210
- */
211
- async expire(key: string, ttl: number): Promise<void> {
212
- const stored = this.store.get(key);
213
- if (stored) {
214
- stored.expiresAt = Date.now() + ttl * 1000; // Convert to milliseconds
215
- this.store.set(key, stored);
216
- }
217
- }
218
-
219
- /**
220
- * Get the time to live (TTL) for a key
221
- * @param key - The key to check
222
- * @returns Seconds until expiration, -1 if no expiration, -2 if key doesn't exist
223
- */
224
- async ttl(key: string): Promise<number> {
225
- const stored = this.store.get(key);
226
-
227
- // Key doesn't exist
228
- if (!stored) return -2;
229
-
230
- // Key exists but has no expiration
231
- if (!stored.expiresAt) return -1;
232
-
233
- // Calculate remaining seconds
234
- const remaining = Math.floor((stored.expiresAt - Date.now()) / 1000);
235
- return remaining > 0 ? remaining : -2; // Return -2 if already expired
236
- }
237
-
238
- // ============================================================================
239
- // Hash Operations (for complex data structures)
240
- // ============================================================================
241
-
242
- /**
243
- * Get a field value from a hash
244
- * @param key - Hash key
245
- * @param field - Field name
246
- * @returns Field value or null if not found
247
- */
248
- async hget(key: string, field: string): Promise<string | null> {
249
- const hash = this.hashes.get(key);
250
- return hash?.get(field) ?? null;
251
- }
252
-
253
- /**
254
- * Set a field value in a hash
255
- * @param key - Hash key
256
- * @param field - Field name
257
- * @param value - Field value
258
- */
259
- async hset(key: string, field: string, value: string): Promise<void> {
260
- let hash = this.hashes.get(key);
261
- if (!hash) {
262
- hash = new Map();
263
- this.hashes.set(key, hash);
264
- }
265
- hash.set(field, value);
266
- }
267
-
268
- /**
269
- * Get all fields and values from a hash
270
- * @param key - Hash key
271
- * @returns Object with all field-value pairs
272
- */
273
- async hgetall(key: string): Promise<Record<string, string>> {
274
- const hash = this.hashes.get(key);
275
- if (!hash) return {};
276
-
277
- return Object.fromEntries(hash.entries());
278
- }
279
-
280
- /**
281
- * Delete one or more fields from a hash
282
- * @param key - Hash key
283
- * @param fields - Field names to delete
284
- * @returns Number of fields deleted
285
- */
286
- async hdel(key: string, ...fields: string[]): Promise<number> {
287
- const hash = this.hashes.get(key);
288
- if (!hash) return 0;
289
-
290
- let deleted = 0;
291
- for (const field of fields) {
292
- if (hash.delete(field)) {
293
- deleted++;
294
- }
295
- }
296
-
297
- // Clean up empty hash
298
- if (hash.size === 0) {
299
- this.hashes.delete(key);
300
- }
301
-
302
- return deleted;
303
- }
304
-
305
- // ============================================================================
306
- // List Operations (for ordered collections)
307
- // ============================================================================
308
-
309
- /**
310
- * Push value to the left (beginning) of a list
311
- * @param key - List key
312
- * @param value - Value to push
313
- */
314
- async lpush(key: string, value: string): Promise<void> {
315
- let list = this.lists.get(key);
316
- if (!list) {
317
- list = [];
318
- this.lists.set(key, list);
319
- }
320
- list.unshift(value); // Add to beginning
321
- }
322
-
323
- /**
324
- * Get a range of elements from a list
325
- * @param key - List key
326
- * @param start - Start index (0-based)
327
- * @param stop - Stop index (-1 for end of list)
328
- * @returns Array of values in range
329
- */
330
- async lrange(key: string, start: number, stop: number): Promise<string[]> {
331
- const list = this.lists.get(key);
332
- if (!list) return [];
333
-
334
- const end = stop === -1 ? list.length : stop + 1;
335
- return list.slice(start, end);
336
- }
337
-
338
- /**
339
- * Get the length of a list
340
- * @param key - List key
341
- * @returns List length
342
- */
343
- async llen(key: string): Promise<number> {
344
- const list = this.lists.get(key);
345
- return list?.length ?? 0;
346
- }
347
-
348
- // ============================================================================
349
- // Pattern Operations (for bulk operations)
350
- // ============================================================================
351
-
352
- /**
353
- * Find all keys matching a pattern
354
- * @param pattern - Glob pattern (* and ? wildcards supported)
355
- * @returns Array of matching keys
356
- */
357
- async keys(pattern: string): Promise<string[]> {
358
- const regex = this.patternToRegex(pattern);
359
- return Array.from(this.store.keys()).filter((key) => regex.test(key));
360
- }
361
-
362
- /**
363
- * Iterate over keys matching a pattern (cursor-based)
364
- * @param cursor - Cursor position (0 to start)
365
- * @param pattern - Glob pattern
366
- * @param count - Number of keys to return
367
- * @returns Tuple of [new cursor, keys array]
368
- */
369
- async scan(cursor: number, pattern: string, count: number): Promise<[number, string[]]> {
370
- const allKeys = await this.keys(pattern);
371
- const start = cursor;
372
- const end = Math.min(cursor + count, allKeys.length);
373
- const keys = allKeys.slice(start, end);
374
- const newCursor = end >= allKeys.length ? 0 : end;
375
- return [newCursor, keys];
376
- }
377
-
378
- // ============================================================================
379
- // Cleanup & Lifecycle
380
- // ============================================================================
381
-
382
- /**
383
- * Run cleanup of expired keys
384
- */
385
- async cleanup(): Promise<void> {
386
- this.cleanupExpired();
387
- }
388
-
389
- /**
390
- * Disconnect and cleanup all resources
391
- */
392
- async disconnect(): Promise<void> {
393
- // Stop cleanup interval
394
- if (this.cleanupInterval) {
395
- clearInterval(this.cleanupInterval);
396
- this.cleanupInterval = null;
397
- }
398
-
399
- // Clear all storage
400
- this.store.clear();
401
- this.hashes.clear();
402
- this.lists.clear();
403
- }
404
-
405
- // ============================================================================
406
- // Private Helper Methods
407
- // ============================================================================
408
-
409
- /**
410
- * Remove all expired keys from storage
411
- */
412
- private cleanupExpired(): void {
413
- const now = Date.now();
414
- const keysToDelete: string[] = [];
415
-
416
- // Find all expired keys
417
- for (const [key, stored] of this.store.entries()) {
418
- if (stored.expiresAt && stored.expiresAt < now) {
419
- keysToDelete.push(key);
420
- }
421
- }
422
-
423
- // Delete expired keys
424
- for (const key of keysToDelete) {
425
- this.store.delete(key);
426
- }
427
- }
428
-
429
- /**
430
- * Convert a glob pattern to a regular expression
431
- * @param pattern - Glob pattern (* and ? wildcards)
432
- * @returns RegExp for pattern matching
433
- */
434
- private patternToRegex(pattern: string): RegExp {
435
- // Escape regex special characters except * and ?
436
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
437
-
438
- // Convert glob wildcards to regex
439
- const regex = escaped.replace(/\*/g, '.*').replace(/\?/g, '.');
440
-
441
- return new RegExp(`^${regex}$`);
442
- }
443
- }