ioredis-toolkit 0.0.10 → 0.5.0

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 (267) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/LICENSE +1 -1
  3. package/README.md +68 -1613
  4. package/dist/cache/cache.d.ts +30 -0
  5. package/dist/cache/cache.d.ts.map +1 -0
  6. package/dist/cache/cache.js +59 -0
  7. package/dist/cache/cache.js.map +1 -0
  8. package/dist/cache/config.d.ts +12 -0
  9. package/dist/cache/config.d.ts.map +1 -0
  10. package/dist/cache/config.js +13 -0
  11. package/dist/cache/config.js.map +1 -0
  12. package/dist/cache/types.d.ts +32 -0
  13. package/dist/cache/types.d.ts.map +1 -0
  14. package/dist/cache/types.js +5 -0
  15. package/dist/cache/types.js.map +1 -0
  16. package/dist/index.d.ts +43 -51
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +32 -44
  19. package/dist/index.js.map +1 -0
  20. package/dist/lock/config.d.ts +12 -0
  21. package/dist/lock/config.d.ts.map +1 -0
  22. package/dist/lock/config.js +8 -0
  23. package/dist/lock/config.js.map +1 -0
  24. package/dist/lock/lock.d.ts +20 -0
  25. package/dist/lock/lock.d.ts.map +1 -0
  26. package/dist/lock/lock.js +44 -0
  27. package/dist/lock/lock.js.map +1 -0
  28. package/dist/lock/types.d.ts +19 -0
  29. package/dist/lock/types.d.ts.map +1 -0
  30. package/dist/lock/types.js +2 -0
  31. package/dist/lock/types.js.map +1 -0
  32. package/dist/modules-config.d.ts +3 -0
  33. package/dist/modules-config.d.ts.map +1 -0
  34. package/dist/modules-config.js +2 -0
  35. package/dist/modules-config.js.map +1 -0
  36. package/dist/pubsub/config.d.ts +11 -0
  37. package/dist/pubsub/config.d.ts.map +1 -0
  38. package/dist/pubsub/config.js +6 -0
  39. package/dist/pubsub/config.js.map +1 -0
  40. package/dist/pubsub/pubsub.d.ts +20 -0
  41. package/dist/pubsub/pubsub.d.ts.map +1 -0
  42. package/dist/pubsub/pubsub.js +54 -0
  43. package/dist/pubsub/pubsub.js.map +1 -0
  44. package/dist/pubsub/types.d.ts +24 -0
  45. package/dist/pubsub/types.d.ts.map +1 -0
  46. package/dist/pubsub/types.js +2 -0
  47. package/dist/pubsub/types.js.map +1 -0
  48. package/dist/rate-limit/config.d.ts +12 -0
  49. package/dist/rate-limit/config.d.ts.map +1 -0
  50. package/dist/rate-limit/config.js +6 -0
  51. package/dist/rate-limit/config.js.map +1 -0
  52. package/dist/rate-limit/rate-limiter.d.ts +18 -0
  53. package/dist/rate-limit/rate-limiter.d.ts.map +1 -0
  54. package/dist/rate-limit/rate-limiter.js +37 -0
  55. package/dist/rate-limit/rate-limiter.js.map +1 -0
  56. package/dist/rate-limit/types.d.ts +27 -0
  57. package/dist/rate-limit/types.d.ts.map +1 -0
  58. package/dist/rate-limit/types.js +2 -0
  59. package/dist/rate-limit/types.js.map +1 -0
  60. package/dist/redis/client-facade.d.ts +77 -0
  61. package/dist/redis/client-facade.d.ts.map +1 -0
  62. package/dist/redis/client-facade.js +102 -0
  63. package/dist/redis/client-facade.js.map +1 -0
  64. package/dist/redis/client.d.ts +10 -0
  65. package/dist/redis/client.d.ts.map +1 -0
  66. package/dist/redis/client.js +29 -0
  67. package/dist/redis/client.js.map +1 -0
  68. package/dist/redis/cluster.d.ts +7 -0
  69. package/dist/redis/cluster.d.ts.map +1 -0
  70. package/dist/redis/cluster.js +47 -0
  71. package/dist/redis/cluster.js.map +1 -0
  72. package/dist/redis/config.d.ts +39 -0
  73. package/dist/redis/config.d.ts.map +1 -0
  74. package/dist/redis/config.js +52 -0
  75. package/dist/redis/config.js.map +1 -0
  76. package/dist/redis/errors.d.ts +5 -0
  77. package/dist/redis/errors.d.ts.map +1 -0
  78. package/dist/redis/errors.js +5 -0
  79. package/dist/redis/errors.js.map +1 -0
  80. package/dist/redis/types.d.ts +135 -0
  81. package/dist/redis/types.d.ts.map +1 -0
  82. package/dist/redis/types.js +2 -0
  83. package/dist/redis/types.js.map +1 -0
  84. package/dist/redis/wrapper.d.ts +88 -0
  85. package/dist/redis/wrapper.d.ts.map +1 -0
  86. package/dist/redis/wrapper.js +206 -0
  87. package/dist/redis/wrapper.js.map +1 -0
  88. package/dist/session/config.d.ts +47 -0
  89. package/dist/session/config.d.ts.map +1 -0
  90. package/dist/session/config.js +101 -0
  91. package/dist/session/config.js.map +1 -0
  92. package/dist/session/cookie.d.ts +16 -0
  93. package/dist/session/cookie.d.ts.map +1 -0
  94. package/dist/session/cookie.js +28 -0
  95. package/dist/session/cookie.js.map +1 -0
  96. package/dist/session/errors.d.ts +56 -0
  97. package/dist/session/errors.d.ts.map +1 -0
  98. package/dist/session/errors.js +58 -0
  99. package/dist/session/errors.js.map +1 -0
  100. package/dist/session/factory.d.ts +21 -0
  101. package/dist/session/factory.d.ts.map +1 -0
  102. package/dist/session/factory.js +30 -0
  103. package/dist/session/factory.js.map +1 -0
  104. package/dist/session/health.d.ts +12 -0
  105. package/dist/session/health.d.ts.map +1 -0
  106. package/dist/session/health.js +23 -0
  107. package/dist/session/health.js.map +1 -0
  108. package/dist/session/keys.d.ts +23 -0
  109. package/dist/session/keys.d.ts.map +1 -0
  110. package/dist/session/keys.js +27 -0
  111. package/dist/session/keys.js.map +1 -0
  112. package/dist/session/manager.d.ts +34 -0
  113. package/dist/session/manager.d.ts.map +1 -0
  114. package/dist/session/manager.js +31 -0
  115. package/dist/session/manager.js.map +1 -0
  116. package/dist/session/metrics.d.ts +11 -0
  117. package/dist/session/metrics.d.ts.map +1 -0
  118. package/dist/session/metrics.js +10 -0
  119. package/dist/session/metrics.js.map +1 -0
  120. package/dist/session/repository.d.ts +49 -0
  121. package/dist/session/repository.d.ts.map +1 -0
  122. package/dist/session/repository.js +203 -0
  123. package/dist/session/repository.js.map +1 -0
  124. package/dist/session/revocation.d.ts +22 -0
  125. package/dist/session/revocation.d.ts.map +1 -0
  126. package/dist/session/revocation.js +41 -0
  127. package/dist/session/revocation.js.map +1 -0
  128. package/dist/session/script-sources.d.ts +11 -0
  129. package/dist/session/script-sources.d.ts.map +1 -0
  130. package/dist/session/script-sources.js +140 -0
  131. package/dist/session/script-sources.js.map +1 -0
  132. package/dist/session/scripts.d.ts +15 -0
  133. package/dist/session/scripts.d.ts.map +1 -0
  134. package/dist/session/scripts.js +41 -0
  135. package/dist/session/scripts.js.map +1 -0
  136. package/dist/session/serializer.d.ts +12 -0
  137. package/dist/session/serializer.d.ts.map +1 -0
  138. package/dist/session/serializer.js +77 -0
  139. package/dist/session/serializer.js.map +1 -0
  140. package/dist/session/service.d.ts +48 -0
  141. package/dist/session/service.d.ts.map +1 -0
  142. package/dist/session/service.js +235 -0
  143. package/dist/session/service.js.map +1 -0
  144. package/dist/session/token.d.ts +16 -0
  145. package/dist/session/token.d.ts.map +1 -0
  146. package/dist/session/token.js +32 -0
  147. package/dist/session/token.js.map +1 -0
  148. package/dist/session/types.d.ts +134 -0
  149. package/dist/session/types.d.ts.map +1 -0
  150. package/dist/session/types.js +2 -0
  151. package/dist/session/types.js.map +1 -0
  152. package/dist/streams/config.d.ts +12 -0
  153. package/dist/streams/config.d.ts.map +1 -0
  154. package/dist/streams/config.js +6 -0
  155. package/dist/streams/config.js.map +1 -0
  156. package/dist/streams/streams.d.ts +24 -0
  157. package/dist/streams/streams.d.ts.map +1 -0
  158. package/dist/streams/streams.js +55 -0
  159. package/dist/streams/streams.js.map +1 -0
  160. package/dist/streams/types.d.ts +32 -0
  161. package/dist/streams/types.d.ts.map +1 -0
  162. package/dist/streams/types.js +2 -0
  163. package/dist/streams/types.js.map +1 -0
  164. package/docs/ACCEPTANCE-REPORT.md +70 -0
  165. package/docs/ARCHITECTURE.md +61 -0
  166. package/docs/CAPACITY.md +33 -0
  167. package/docs/DEPLOYMENT.md +22 -0
  168. package/docs/README-API.md +15 -0
  169. package/docs/STATE-MACHINE.md +38 -0
  170. package/docs/TESTING.md +37 -0
  171. package/docs/THREAT-MODEL.md +23 -0
  172. package/docs/TYPE-SAFETY.md +34 -0
  173. package/docs/modules/cache/README.md +7 -0
  174. package/docs/modules/cache/usage.md +156 -0
  175. package/docs/modules/lock/README.md +7 -0
  176. package/docs/modules/lock/usage.md +105 -0
  177. package/docs/modules/pubsub/README.md +7 -0
  178. package/docs/modules/pubsub/usage.md +106 -0
  179. package/docs/modules/rate-limit/README.md +7 -0
  180. package/docs/modules/rate-limit/usage.md +100 -0
  181. package/docs/modules/sessions/README.md +7 -0
  182. package/docs/modules/sessions/usage.md +262 -0
  183. package/docs/modules/streams/README.md +7 -0
  184. package/docs/modules/streams/usage.md +141 -0
  185. package/package.json +50 -60
  186. package/src/scripts/cleanup-index.lua +4 -0
  187. package/src/scripts/conditional-update.lua +21 -0
  188. package/src/scripts/consume-session.lua +21 -0
  189. package/src/scripts/create-session.lua +28 -0
  190. package/src/scripts/delete.lua +2 -0
  191. package/src/scripts/destroy-user.lua +13 -0
  192. package/src/scripts/enforce-limit.lua +17 -0
  193. package/src/scripts/revoke-session.lua +13 -0
  194. package/src/scripts/rotate.lua +24 -0
  195. package/src/scripts/touch-session.lua +28 -0
  196. package/src/scripts/update-session.lua +18 -0
  197. package/dist/cache.d.ts +0 -797
  198. package/dist/cache.js +0 -1115
  199. package/dist/client.d.ts +0 -287
  200. package/dist/client.js +0 -1113
  201. package/dist/cluster-slot.d.ts +0 -4
  202. package/dist/cluster-slot.js +0 -31
  203. package/dist/cluster.d.ts +0 -79
  204. package/dist/cluster.js +0 -156
  205. package/dist/errors.d.ts +0 -30
  206. package/dist/errors.js +0 -63
  207. package/dist/health.d.ts +0 -180
  208. package/dist/health.js +0 -239
  209. package/dist/lock.d.ts +0 -233
  210. package/dist/lock.js +0 -440
  211. package/dist/logger.d.ts +0 -12
  212. package/dist/logger.js +0 -40
  213. package/dist/pubsub.d.ts +0 -423
  214. package/dist/pubsub.js +0 -537
  215. package/dist/ratelimiter.d.ts +0 -441
  216. package/dist/ratelimiter.js +0 -539
  217. package/dist/session/index.d.ts +0 -23
  218. package/dist/session/index.js +0 -16
  219. package/dist/session/revocation-store.d.ts +0 -176
  220. package/dist/session/revocation-store.js +0 -318
  221. package/dist/session/scripts/cleanup-index.lua +0 -21
  222. package/dist/session/scripts/conditional-update-encrypted.lua +0 -60
  223. package/dist/session/scripts/conditional-update.lua +0 -63
  224. package/dist/session/scripts/create.lua +0 -83
  225. package/dist/session/scripts/delete-by-user.lua +0 -29
  226. package/dist/session/scripts/delete.lua +0 -15
  227. package/dist/session/scripts/enforce-limit.lua +0 -38
  228. package/dist/session/scripts/revoke.lua +0 -61
  229. package/dist/session/scripts/rotate-encrypted.lua +0 -149
  230. package/dist/session/scripts/rotate.lua +0 -167
  231. package/dist/session/scripts/touch-encrypted.lua +0 -89
  232. package/dist/session/scripts/touch.lua +0 -72
  233. package/dist/session/scripts/validate.lua +0 -90
  234. package/dist/session/session-circuit-breaker.d.ts +0 -42
  235. package/dist/session/session-circuit-breaker.js +0 -129
  236. package/dist/session/session-config.d.ts +0 -355
  237. package/dist/session/session-config.js +0 -171
  238. package/dist/session/session-cookie.d.ts +0 -72
  239. package/dist/session/session-cookie.js +0 -101
  240. package/dist/session/session-encryption.d.ts +0 -87
  241. package/dist/session/session-encryption.js +0 -139
  242. package/dist/session/session-errors.d.ts +0 -85
  243. package/dist/session/session-errors.js +0 -145
  244. package/dist/session/session-health.d.ts +0 -38
  245. package/dist/session/session-health.js +0 -60
  246. package/dist/session/session-keys.d.ts +0 -64
  247. package/dist/session/session-keys.js +0 -128
  248. package/dist/session/session-manager.d.ts +0 -73
  249. package/dist/session/session-manager.js +0 -94
  250. package/dist/session/session-metrics.d.ts +0 -41
  251. package/dist/session/session-metrics.js +0 -135
  252. package/dist/session/session-repository.d.ts +0 -184
  253. package/dist/session/session-repository.js +0 -763
  254. package/dist/session/session-scripts.d.ts +0 -36
  255. package/dist/session/session-scripts.js +0 -130
  256. package/dist/session/session-serializer.d.ts +0 -42
  257. package/dist/session/session-serializer.js +0 -267
  258. package/dist/session/session-service.d.ts +0 -123
  259. package/dist/session/session-service.js +0 -670
  260. package/dist/session/session-token.d.ts +0 -38
  261. package/dist/session/session-token.js +0 -86
  262. package/dist/session/session-types.d.ts +0 -281
  263. package/dist/session/session-types.js +0 -16
  264. package/dist/types.d.ts +0 -924
  265. package/dist/types.js +0 -151
  266. package/dist/utils/deepmerge.d.ts +0 -9
  267. package/dist/utils/deepmerge.js +0 -61
package/dist/lock.js DELETED
@@ -1,440 +0,0 @@
1
- import { RedisError } from './errors.js';
2
- import { randomBytes } from 'node:crypto';
3
- import { defaultLogger } from './logger.js';
4
- /**
5
- * Information about a distributed lock.
6
- *
7
- * **Fields:**
8
- * - `locked`: Whether the lock is currently held.
9
- * - `ttl`: Remaining TTL in seconds (when held and TTL set).
10
- * - `lockId`: Unique owner id of the lock.
11
- *
12
- * **Example:**
13
- * ```ts
14
- * const info = await lock.getLockInfo('order:42');
15
- * // { locked: true, ttl: 29, lockId: 'a1b2c3...' }
16
- * ```
17
- */
18
- // export type LockInfo = {
19
- // /** Whether the lock is currently held. */
20
- // locked: boolean;
21
- // /** Remaining TTL in seconds (when held and TTL set). */
22
- // ttl?: number;
23
- // /** Unique owner id of the lock. */
24
- // lockId?: string;
25
- // };
26
- /**
27
- * Options for the distributed lock.
28
- *
29
- * **Fields:**
30
- * - `ttl`: Lock TTL in milliseconds. Default: `30000`.
31
- * - `retryCount`: Number of acquisition attempts. Default: `3`.
32
- * - `retryDelay`: Base delay between retries in ms (grows exponentially). Default: `200`.
33
- *
34
- * **Example:**
35
- * ```ts
36
- * const lock = new DistributedLock(client, { ttl: 10000, retryCount: 5 });
37
- * ```
38
- */
39
- // export interface DistributedLockOptions {
40
- // /** Lock TTL in milliseconds. Default: `30000`. */
41
- // ttl?: number;
42
- // /** Number of acquisition attempts. Default: `3`. */
43
- // retryCount?: number;
44
- // /** Base delay between retries in ms (grows exponentially). Default: `200`. */
45
- // retryDelay?: number;
46
- // }
47
- /**
48
- * Distributed mutual-exclusion lock backed by Redis.
49
- *
50
- * Works in standalone, sentinel and cluster modes. Acquisition uses atomic
51
- * `SET ... PX NX`; release and extension use Lua scripts so only the lock owner
52
- * can release or extend. `withLock` auto-extends the lock at half TTL while the
53
- * critical section runs and always releases afterwards.
54
- *
55
- * @example
56
- * ```ts
57
- * const lock = new DistributedLock(client, { ttl: 30000, retryCount: 5 });
58
- * const acquired = await lock.acquire('order:42');
59
- * if (acquired) {
60
- * try {
61
- * // critical section
62
- * } finally {
63
- * await lock.release('order:42');
64
- * }
65
- * }
66
- * ```
67
- */
68
- export class DistributedLock {
69
- client;
70
- logger;
71
- defaultTTL;
72
- defaultRetryCount;
73
- defaultRetryDelay;
74
- /**
75
- * Creates a distributed lock bound to a Redis client.
76
- *
77
- * @param client - The underlying {@link RedisClientWrapper}.
78
- * @param logger - Optional pino-compatible logger; defaults to `console`.
79
- * @param options - Defaults for `ttl` (ms), `retryCount` and `retryDelay`.
80
- *
81
- * @example
82
- * ```ts
83
- * const lock = new DistributedLock(client, { ttl: 10000, retryCount: 3 });
84
- * ```
85
- */
86
- constructor(client, logger = defaultLogger, options = {}) {
87
- this.client = client;
88
- this.logger = logger.child({ component: 'DistributedLock' });
89
- this.defaultTTL = options.ttl || 30000;
90
- this.defaultRetryCount = options.retryCount || 3;
91
- this.defaultRetryDelay = options.retryDelay || 200;
92
- }
93
- getLockKey(key) {
94
- return `lock:${key}`;
95
- }
96
- generateLockId() {
97
- return randomBytes(16).toString('hex');
98
- }
99
- async executeWithRetry(fn, retryCount = this.defaultRetryCount, retryDelay = this.defaultRetryDelay) {
100
- let lastError = null;
101
- for (let i = 0; i < retryCount; i++) {
102
- try {
103
- return await fn();
104
- }
105
- catch (error) {
106
- lastError = error;
107
- if (i < retryCount - 1) {
108
- const delay = retryDelay * Math.pow(2, i) * (0.5 + Math.random() * 0.5);
109
- await new Promise(resolve => setTimeout(resolve, delay));
110
- }
111
- }
112
- }
113
- throw lastError || new Error('Retry failed');
114
- }
115
- /**
116
- * Attempts to acquire the lock for a key.
117
- *
118
- * Uses atomic `SET lock:<key> <id> PX <ttl> NX` with exponential backoff
119
- * retries. Locks expire automatically after `ttl` ms, so a crashed holder
120
- * never blocks others forever.
121
- *
122
- * @param key - The resource to lock, e.g. `'order:42'` (stored as `lock:order:42`).
123
- * @param ttl - Lock TTL in milliseconds (default: `30000`).
124
- *
125
- * @returns `true` when the lock was acquired.
126
- *
127
- * @example
128
- * ```ts
129
- * const acquired = await lock.acquire('order:42', 10000);
130
- * // acquired === true when lock was successfully acquired
131
- * ```
132
- */
133
- async acquire(key, ttl = this.defaultTTL) {
134
- const lockKey = this.getLockKey(key);
135
- const lockId = this.generateLockId();
136
- return this.executeWithRetry(async () => {
137
- // Using SET with PX and NX for atomic lock acquisition
138
- const result = await this.client.raw.set(lockKey, lockId, 'PX', ttl, 'NX');
139
- return result === 'OK';
140
- });
141
- }
142
- /**
143
- * Releases the lock, but only if this process still owns it.
144
- *
145
- * Uses an atomic Lua check-and-delete so a lock whose TTL expired (and was
146
- * re-acquired by someone else) is never removed by the old owner.
147
- *
148
- * @param key - The locked resource.
149
- *
150
- * @returns `true` if the lock was released, `false` if not owned or missing.
151
- *
152
- * @example
153
- * ```ts
154
- * await lock.release('order:42');
155
- * ```
156
- */
157
- async release(key) {
158
- const lockKey = this.getLockKey(key);
159
- try {
160
- // Use Lua script for atomic check-and-delete
161
- const script = `
162
- if redis.call('get', KEYS[1]) == ARGV[1] then
163
- return redis.call('del', KEYS[1])
164
- else
165
- return 0
166
- end
167
- `;
168
- const lockId = await this.client.raw.get(lockKey);
169
- if (!lockId) {
170
- this.logger.warn('Lock not found for release', { key });
171
- return false;
172
- }
173
- const result = await this.client.raw.eval(script, 1, lockKey, lockId);
174
- return result === 1;
175
- }
176
- catch (error) {
177
- this.logger.error('Failed to release lock', { key, error });
178
- return false;
179
- }
180
- }
181
- /**
182
- * Force-releases a lock without checking ownership.
183
- *
184
- * Use with care: only for emergency cleanup or when the holder is known to
185
- * be gone. This is what `withLock` falls back to when a normal release fails.
186
- *
187
- * @param key - The locked resource.
188
- *
189
- * @returns `true` if a lock existed and was deleted.
190
- *
191
- * @example
192
- * ```ts
193
- * await lock.releaseForce('order:42');
194
- * ```
195
- */
196
- async releaseForce(key) {
197
- const lockKey = this.getLockKey(key);
198
- const result = await this.client.raw.del(lockKey);
199
- return result === 1;
200
- }
201
- /**
202
- * Extends the TTL of a lock this process still owns.
203
- *
204
- * Uses an atomic Lua script so a re-acquired lock is never extended by the
205
- * old owner.
206
- *
207
- * @param key - The locked resource.
208
- * @param ttl - New TTL in milliseconds (default: `30000`).
209
- *
210
- * @returns `true` if the lock was extended.
211
- *
212
- * @example
213
- * ```ts
214
- * const extended = await lock.extend('order:42', 30000);
215
- * // extended === true when lock TTL was renewed
216
- * ```
217
- */
218
- async extend(key, ttl = this.defaultTTL) {
219
- const lockKey = this.getLockKey(key);
220
- const script = `
221
- if redis.call('get', KEYS[1]) == ARGV[1] then
222
- return redis.call('pexpire', KEYS[1], ARGV[2])
223
- else
224
- return 0
225
- end
226
- `;
227
- try {
228
- const lockId = await this.client.raw.get(lockKey);
229
- if (!lockId) {
230
- return false;
231
- }
232
- const result = await this.client.raw.eval(script, 1, lockKey, lockId, ttl);
233
- return result === 1;
234
- }
235
- catch (error) {
236
- this.logger.error('Failed to extend lock', { key, error });
237
- return false;
238
- }
239
- }
240
- /**
241
- * Runs a critical section while holding a lock.
242
- *
243
- * Acquires the lock (with retries), auto-extends it at half TTL while `fn`
244
- * runs, detects a lost lock, and always releases afterwards (force-releasing
245
- * if a normal release fails).
246
- *
247
- * @param key - The resource to lock.
248
- * @param fn - The critical section to run exclusively.
249
- * @param options - Per-call `ttl` (ms), `retryCount`, `retryDelay`.
250
- *
251
- * @returns The return value of `fn`.
252
- *
253
- * @throws {@link RedisError} with code `LOCK_ACQUISITION_FAILED` when the lock
254
- * cannot be acquired, or `LOCK_LOST` when the lock expired mid-execution.
255
- *
256
- * @example
257
- * ```ts
258
- * const result = await lock.withLock('inventory:sku-1', async () => {
259
- * return await updateStock();
260
- * });
261
- * ```
262
- */
263
- async withLock(key, fn, options = {}) {
264
- const ttl = options.ttl || this.defaultTTL;
265
- const retryCount = options.retryCount || this.defaultRetryCount;
266
- const retryDelay = options.retryDelay || this.defaultRetryDelay;
267
- // Try to acquire the lock with retries
268
- const acquired = await this.acquire(key, ttl);
269
- if (!acquired) {
270
- throw new RedisError(`Failed to acquire lock for key: ${key} after ${retryCount} attempts`, 'LOCK_ACQUISITION_FAILED');
271
- }
272
- let extensionTimer = null;
273
- let lockRenewed = true;
274
- try {
275
- // Start auto-extension timer at half TTL
276
- const extendInterval = Math.floor(ttl / 2);
277
- let isExtending = false;
278
- const extendLock = async () => {
279
- if (isExtending || !lockRenewed)
280
- return;
281
- isExtending = true;
282
- try {
283
- const extended = await this.extend(key, ttl);
284
- if (!extended) {
285
- lockRenewed = false;
286
- this.logger.warn('Lock extension failed', { key });
287
- }
288
- }
289
- catch (error) {
290
- this.logger.error('Lock extension error', { key, error });
291
- lockRenewed = false;
292
- }
293
- finally {
294
- isExtending = false;
295
- }
296
- };
297
- // Schedule auto-extension
298
- extensionTimer = setInterval(() => {
299
- extendLock().catch((error) => {
300
- this.logger.error('Extension interval error', { key, error });
301
- });
302
- }, extendInterval);
303
- // Execute the function
304
- const result = await fn();
305
- // Check if lock was maintained during execution
306
- if (!lockRenewed) {
307
- throw new RedisError(`Lock was lost during execution for key: ${key}`, 'LOCK_LOST');
308
- }
309
- return result;
310
- }
311
- catch (error) {
312
- this.logger.error('Error in locked operation', { key, error });
313
- throw error;
314
- }
315
- finally {
316
- // Clean up extension timer
317
- if (extensionTimer) {
318
- clearInterval(extensionTimer);
319
- extensionTimer = null;
320
- }
321
- // Release the lock
322
- try {
323
- await this.release(key);
324
- }
325
- catch (releaseError) {
326
- this.logger.error('Failed to release lock', { key, releaseError });
327
- try {
328
- await this.releaseForce(key);
329
- }
330
- catch (forceError) {
331
- this.logger.error('Failed to force release lock', { key, forceError });
332
- }
333
- }
334
- }
335
- }
336
- /**
337
- * Checks whether a lock is currently held.
338
- *
339
- * @param key - The locked resource.
340
- *
341
- * @returns `true` if the lock exists (held by anyone).
342
- *
343
- * @example
344
- * ```ts
345
- * const busy = await lock.isLocked('order:42');
346
- * ```
347
- */
348
- async isLocked(key) {
349
- const lockKey = this.getLockKey(key);
350
- const exists = await this.client.raw.exists(lockKey);
351
- return exists === 1;
352
- }
353
- /**
354
- * Returns details about a lock.
355
- *
356
- * @param key - The locked resource.
357
- *
358
- * @returns `{ locked: false }` when not held, otherwise `{ locked: true, ttl, lockId }`.
359
- *
360
- * @example
361
- * ```ts
362
- * const info = await lock.getLockInfo('order:42');
363
- * // { locked: true, ttl: 29, lockId: 'a1b2c3...' }
364
- * ```
365
- */
366
- async getLockInfo(key) {
367
- const lockKey = this.getLockKey(key);
368
- const exists = await this.client.raw.exists(lockKey);
369
- if (!exists) {
370
- return { locked: false };
371
- }
372
- const [lockId, ttl] = await Promise.all([
373
- this.client.raw.get(lockKey),
374
- this.client.raw.ttl(lockKey),
375
- ]);
376
- // Build the result object with proper undefined handling
377
- const result = { locked: true };
378
- if (lockId !== null && lockId !== undefined) {
379
- result.lockId = lockId;
380
- }
381
- if (ttl !== null && ttl !== undefined && ttl > 0) {
382
- result.ttl = ttl;
383
- }
384
- return result;
385
- }
386
- /**
387
- * Returns the owner id of a lock.
388
- *
389
- * @param key - The locked resource.
390
- *
391
- * @returns The lock id (random hex token), or `null` when not held.
392
- *
393
- * @example
394
- * ```ts
395
- * const owner = await lock.getLockOwner('order:42');
396
- * ```
397
- */
398
- async getLockOwner(key) {
399
- const lockKey = this.getLockKey(key);
400
- return this.client.raw.get(lockKey);
401
- }
402
- /**
403
- * Returns the remaining TTL of a lock in seconds.
404
- *
405
- * @param key - The locked resource.
406
- *
407
- * @returns Remaining seconds (`0` when not held or expired).
408
- *
409
- * @example
410
- * ```ts
411
- * const remaining = await lock.getLockTTL('order:42');
412
- * ```
413
- */
414
- async getLockTTL(key) {
415
- const lockKey = this.getLockKey(key);
416
- const ttl = await this.client.raw.ttl(lockKey);
417
- return ttl > 0 ? ttl : 0;
418
- }
419
- // Clean up all locks (for testing or emergency)
420
- /**
421
- * Deletes every lock key (`lock:*`) from Redis.
422
- *
423
- * Intended for tests and emergency recovery only.
424
- *
425
- * @returns The number of deleted locks.
426
- *
427
- * @example
428
- * ```ts
429
- * const removed = await lock.cleanupAll();
430
- * ```
431
- */
432
- async cleanupAll() {
433
- let deleted = 0;
434
- for await (const key of this.client.scanIterator('lock:*')) {
435
- const result = await this.client.raw.del(key);
436
- deleted += result;
437
- }
438
- return deleted;
439
- }
440
- }
package/dist/logger.d.ts DELETED
@@ -1,12 +0,0 @@
1
- export type LogMeta = Record<string, unknown>;
2
- export interface LoggerLike {
3
- trace(message: string, meta?: LogMeta): void;
4
- debug(message: string, meta?: LogMeta): void;
5
- info(message: string, meta?: LogMeta): void;
6
- warn(message: string, meta?: LogMeta): void;
7
- error(message: string, meta?: LogMeta): void;
8
- fatal(message: string, meta?: LogMeta): void;
9
- child(bindings: LogMeta): LoggerLike;
10
- }
11
- export declare const defaultLogger: LoggerLike;
12
- export declare function createConsoleLogger(bindings?: LogMeta): LoggerLike;
package/dist/logger.js DELETED
@@ -1,40 +0,0 @@
1
- function mergeMeta(bindings, meta) {
2
- return {
3
- ...bindings,
4
- ...(meta ?? {}),
5
- };
6
- }
7
- class ConsoleLogger {
8
- bindings;
9
- constructor(bindings = {}) {
10
- this.bindings = bindings;
11
- }
12
- trace(message, meta) {
13
- console.trace(message, mergeMeta(this.bindings, meta));
14
- }
15
- debug(message, meta) {
16
- console.debug(message, mergeMeta(this.bindings, meta));
17
- }
18
- info(message, meta) {
19
- console.info(message, mergeMeta(this.bindings, meta));
20
- }
21
- warn(message, meta) {
22
- console.warn(message, mergeMeta(this.bindings, meta));
23
- }
24
- error(message, meta) {
25
- console.error(message, mergeMeta(this.bindings, meta));
26
- }
27
- fatal(message, meta) {
28
- console.error(message, mergeMeta(this.bindings, meta));
29
- }
30
- child(bindings) {
31
- return new ConsoleLogger({
32
- ...this.bindings,
33
- ...bindings,
34
- });
35
- }
36
- }
37
- export const defaultLogger = new ConsoleLogger();
38
- export function createConsoleLogger(bindings) {
39
- return new ConsoleLogger(bindings);
40
- }