ioredis-toolkit 0.0.9 → 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 -1058
  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 -796
  198. package/dist/cache.js +0 -1120
  199. package/dist/client.d.ts +0 -284
  200. package/dist/client.js +0 -1114
  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 -248
  210. package/dist/lock.js +0 -397
  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 -110
  230. package/dist/session/scripts/rotate.lua +0 -122
  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 -335
  237. package/dist/session/session-config.js +0 -162
  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 -51
  247. package/dist/session/session-keys.js +0 -113
  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 -33
  251. package/dist/session/session-metrics.js +0 -112
  252. package/dist/session/session-repository.d.ts +0 -161
  253. package/dist/session/session-repository.js +0 -683
  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 -248
  258. package/dist/session/session-service.d.ts +0 -104
  259. package/dist/session/session-service.js +0 -611
  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 -253
  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
@@ -0,0 +1,156 @@
1
+ # Cache Module — Complete Usage Guide
2
+
3
+ `RedisCache` is a namespaced JSON cache built on the package's shared `RedisClientWrapper`. It supports TTLs, conditional writes, bounded value sizes, and cluster-aware invalidation.
4
+
5
+ ## 1. Setup
6
+
7
+ ```ts
8
+ import { createRedisClient } from 'ioredis-toolkit';
9
+
10
+ const redis = createRedisClient({
11
+ mode: 'standalone',
12
+ host: '127.0.0.1',
13
+ port: 6379,
14
+ cache: {
15
+ enabled: true,
16
+ namespace: 'myapp:cache',
17
+ defaultTtl: 300,
18
+ maxValueBytes: 1024 * 1024,
19
+ },
20
+ });
21
+ ```
22
+
23
+ The module uses the same Redis connection as the rest of the package. Do not create a second Redis client just for caching.
24
+
25
+ `cache.enabled` defaults to `false`. Accessing `redis.cache` while disabled throws `RedisConfigurationError`; set `enabled: true` to use the module.
26
+
27
+ ## 2. Configuration
28
+
29
+ | Option | Type | Default | Description |
30
+ |---|---|---:|---|
31
+ | `enabled` | `boolean` | `false` | Enables the module. `redis.cache` throws `RedisConfigurationError` while disabled; `new RedisCache(...)` remains directly constructible either way. |
32
+ | `namespace` | `string` | `cache` | Prefix placed before every cache key. |
33
+ | `defaultTtl` | `number` | `300` | Default TTL in seconds. |
34
+ | `maxValueBytes` | `number` | `1048576` | Maximum UTF-8 encoded JSON size. |
35
+
36
+ ## 3. Methods
37
+
38
+ ### `key(key)`
39
+
40
+ Returns the physical Redis key. Example: `cache:user:42`.
41
+
42
+ ```ts
43
+ redis.cache.key('user:42');
44
+ ```
45
+
46
+ ### `get<T>(key)`
47
+
48
+ Returns `{ hit, value }`. A miss returns `{ hit: false, value: null }`. A cached value that is not valid JSON (for example, written directly by another, non-conforming client) throws a typed `CacheError` rather than crashing with an uncaught `SyntaxError` or silently returning a miss.
49
+
50
+ ```ts
51
+ import { CacheError } from 'ioredis-toolkit';
52
+
53
+ try {
54
+ const result = await redis.cache.get<User>('user:42');
55
+ if (result.hit) console.log(result.value.email);
56
+ } catch (error) {
57
+ if (error instanceof CacheError) {
58
+ // The stored value could not be decoded as JSON.
59
+ }
60
+ }
61
+ ```
62
+
63
+ ### `getValue<T>(key)`
64
+
65
+ Convenience form returning the decoded value or `null`.
66
+
67
+ ```ts
68
+ const user = await redis.cache.getValue<User>('user:42');
69
+ if (!user) {
70
+ // cache miss
71
+ }
72
+ ```
73
+
74
+ ### `set<T>(key, value, options?)`
75
+
76
+ Stores a JSON value. `ttl` is in seconds. `nx` means create only when absent; `xx` means update only when present. `nx` and `xx` cannot both be true.
77
+
78
+ ```ts
79
+ await redis.cache.set('user:42', { id: '42', name: 'Anwar' }, { ttl: 600 });
80
+ await redis.cache.set('config', { version: 4 }, { nx: true, ttl: 60 });
81
+ await redis.cache.set('config', { version: 5 }, { xx: true, ttl: 60 });
82
+ ```
83
+
84
+ Returns `true` when Redis accepted the write and `false` for a failed NX/XX condition.
85
+
86
+ ### `setIfAbsent<T>(key, value, ttl?)`
87
+
88
+ Atomic create-if-absent helper.
89
+
90
+ ```ts
91
+ const acquired = await redis.cache.setIfAbsent('job:123', { state: 'queued' }, 30);
92
+ ```
93
+
94
+ ### `has(key)`
95
+
96
+ Checks whether the physical key exists.
97
+
98
+ ```ts
99
+ if (await redis.cache.has('user:42')) {
100
+ console.log('cached');
101
+ }
102
+ ```
103
+
104
+ ### `ttl(key)`
105
+
106
+ Returns Redis TTL in seconds using normal Redis semantics (`-1` means no expiration and `-2` means missing).
107
+
108
+ ```ts
109
+ const seconds = await redis.cache.ttl('user:42');
110
+ ```
111
+
112
+ ### `delete(...keys)` / `del(key)`
113
+
114
+ Deletes one or more entries. Cluster mode groups keys by Redis hash slot so a cross-slot `DEL` is not issued.
115
+
116
+ ```ts
117
+ await redis.cache.delete('user:1', 'user:2', 'user:3');
118
+ await redis.cache.del('user:4');
119
+ ```
120
+
121
+ ### `clear(pattern?)`
122
+
123
+ Scans the cache namespace and deletes matching entries. It never uses `KEYS` or `FLUSHALL`.
124
+
125
+ ```ts
126
+ await redis.cache.clear();
127
+ await redis.cache.clear('user:*');
128
+ ```
129
+
130
+ For production, keep patterns bounded and avoid calling large namespace clears on latency-sensitive request paths.
131
+
132
+ ## 4. Typed values
133
+
134
+ The generic parameter describes the value you expect to receive; Redis itself does not store TypeScript type information.
135
+
136
+ ```ts
137
+ type Product = { id: string; price: number };
138
+ await redis.cache.set<Product>('product:1', { id: '1', price: 19.99 });
139
+ const product = await redis.cache.getValue<Product>('product:1');
140
+ ```
141
+
142
+ ## 5. Serialization limits
143
+
144
+ Values must be JSON-serializable. `undefined`, functions, symbols, cyclic structures, and values that cannot be represented by JSON are rejected. The encoded UTF-8 payload must not exceed `maxValueBytes`.
145
+
146
+ ## 6. Per-client overrides
147
+
148
+ Global configuration can be merged or replaced:
149
+
150
+ ```ts
151
+ redis.withCache({ defaultTtl: 60 });
152
+ // namespace and maxValueBytes remain unchanged.
153
+
154
+ redis.withCache({ namespace: 'short-cache' }, 'replace');
155
+ // Unspecified values return to module defaults.
156
+ ```
@@ -0,0 +1,7 @@
1
+ # Lock module
2
+
3
+ This directory contains the complete usage documentation for the lock module.
4
+
5
+ **Full guide:** [usage.md](./usage.md)
6
+
7
+ The usage guide documents configuration, public types, every public method, arguments, return values, semantics, error/edge-case behavior, and multiple examples.
@@ -0,0 +1,105 @@
1
+ # Lock Module — Complete Usage Guide
2
+
3
+ `RedisLock` provides a small distributed-lock primitive based on a random ownership token and atomic compare-and-delete/compare-and-expire Lua operations.
4
+
5
+ ## Setup
6
+
7
+ ```ts
8
+ import { createRedisClient } from 'ioredis-toolkit';
9
+
10
+ const redis = createRedisClient({
11
+ mode: 'standalone',
12
+ host: '127.0.0.1',
13
+ port: 6379,
14
+ lock: { enabled: true, namespace: 'myapp:locks', defaultTtl: 30, maxTtl: 300 },
15
+ });
16
+ ```
17
+
18
+ `lock.enabled` defaults to `false`. Accessing `redis.lock` while disabled throws `RedisConfigurationError`; set `enabled: true` to use the module.
19
+
20
+ ## Configuration
21
+
22
+ | Option | Type | Default | Description |
23
+ |---|---|---:|---|
24
+ | `enabled` | `boolean` | `false` | Enables the module. `redis.lock` throws `RedisConfigurationError` while disabled; `new RedisLock(...)` remains directly constructible either way. |
25
+ | `namespace` | `string` | `lock` | Physical key prefix. |
26
+ | `defaultTtl` | `number` | `30` | Default lease in seconds. Must not exceed `maxTtl`. |
27
+ | `maxTtl` | `number` | `300` | Maximum permitted lease. |
28
+
29
+ `parseLockConfig()` rejects a configuration where `defaultTtl > maxTtl` at config-parse time, instead of letting every `acquire()`/`extend()` call using the default TTL throw a `RangeError` later.
30
+
31
+ ## Methods
32
+
33
+ ### `key(name)`
34
+
35
+ Builds the physical lock key.
36
+
37
+ ```ts
38
+ redis.lock.key('order:123'); // lock:order:123
39
+ ```
40
+
41
+ ### `acquire(name, ttl?)`
42
+
43
+ Attempts an atomic `SET ... NX PX`. The returned token proves ownership and must be treated as a secret.
44
+
45
+ ```ts
46
+ const result = await redis.lock.acquire('order:123', 15);
47
+ if (!result.acquired) {
48
+ return; // another worker owns it
49
+ }
50
+ try {
51
+ await processOrder();
52
+ } finally {
53
+ await redis.lock.release('order:123', result.token);
54
+ }
55
+ ```
56
+
57
+ ### `release(name, token)`
58
+
59
+ Deletes the lock only if the current Redis value equals the supplied ownership token. This prevents an expired lock from being deleted by a previous owner.
60
+
61
+ ```ts
62
+ const lock = await redis.lock.acquire('invoice:42');
63
+ if (lock.acquired) await redis.lock.release('invoice:42', lock.token);
64
+ ```
65
+
66
+ ### `extend(name, token, ttl?)`
67
+
68
+ Atomically extends the lease only for the current owner.
69
+
70
+ ```ts
71
+ const lock = await redis.lock.acquire('long-job', 20);
72
+ if (lock.acquired) {
73
+ await doFirstPart();
74
+ const extended = await redis.lock.extend('long-job', lock.token, 20);
75
+ if (!extended) throw new Error('Lock ownership was lost');
76
+ await doSecondPart();
77
+ await redis.lock.release('long-job', lock.token);
78
+ }
79
+ ```
80
+
81
+ ### `using(name, fn, ttl?)`
82
+
83
+ Acquires a lock, executes the callback, and releases the lock in `finally`. The callback receives the ownership token so it can extend the lease.
84
+
85
+ ```ts
86
+ const result = await redis.lock.using('report:daily', async token => {
87
+ // token is available if the job needs an extension.
88
+ return await generateReport();
89
+ }, 60);
90
+ ```
91
+
92
+ If acquisition fails, `using()` throws instead of executing the callback.
93
+
94
+ ## Important semantics
95
+
96
+ This is a lease, not a consensus protocol. If the process pauses longer than the TTL, another owner may acquire the lock. Choose TTLs that safely cover the critical section and extend them when necessary.
97
+
98
+ Do not use the lock as proof that a database transaction committed. Prefer fencing tokens when an external resource requires strict stale-writer protection.
99
+
100
+ ## Overrides
101
+
102
+ ```ts
103
+ redis.withLock({ defaultTtl: 10, maxTtl: 60 });
104
+ redis.withLock({ namespace: 'maintenance' }, 'replace');
105
+ ```
@@ -0,0 +1,7 @@
1
+ # Pubsub module
2
+
3
+ This directory contains the complete usage documentation for the pubsub module.
4
+
5
+ **Full guide:** [usage.md](./usage.md)
6
+
7
+ The usage guide documents configuration, public types, every public method, arguments, return values, semantics, error/edge-case behavior, and multiple examples.
@@ -0,0 +1,106 @@
1
+ # Pub/Sub Module — Complete Usage Guide
2
+
3
+ `RedisPubSub` provides JSON Pub/Sub with namespaced channels and a dedicated subscriber connection. Pub/Sub is ephemeral: messages are not persisted for offline subscribers.
4
+
5
+ ## Setup
6
+
7
+ ```ts
8
+ import { createRedisClient } from 'ioredis-toolkit';
9
+
10
+ const redis = createRedisClient({
11
+ mode: 'standalone',
12
+ host: '127.0.0.1',
13
+ port: 6379,
14
+ pubsub: { enabled: true, channelPrefix: 'myapp:events', maxMessageBytes: 1024 * 1024 },
15
+ });
16
+ ```
17
+
18
+ `pubsub.enabled` defaults to `false`. Accessing `redis.pubsub` while disabled throws `RedisConfigurationError`; set `enabled: true` to use the module.
19
+
20
+ ## Configuration
21
+
22
+ | Option | Type | Default | Description |
23
+ |---|---|---:|---|
24
+ | `enabled` | `boolean` | `false` | Enables the module. `redis.pubsub` throws `RedisConfigurationError` while disabled; `new RedisPubSub(...)` remains directly constructible either way. |
25
+ | `channelPrefix` | `string` | `events` | Physical channel prefix. |
26
+ | `maxMessageBytes` | `number` | `1048576` | Maximum encoded JSON message size. |
27
+
28
+ ## Resilience
29
+
30
+ The dedicated subscriber connection has a default `'error'` listener attached automatically, so a connection drop, auth failure, or network error on it can no longer crash the process with an uncaught exception. Attach your own listener on the connection if you need alerting/observability beyond that default.
31
+
32
+ A malformed (non-JSON) message delivered on a subscribed channel — for example, published by a non-conforming external client — is silently dropped instead of throwing inside ioredis's `message` event and crashing the process. Handlers are only invoked for messages that parse successfully.
33
+
34
+ Repeated `subscribe()` calls for the same physical channel issue a single underlying `SUBSCRIBE`; the physical `UNSUBSCRIBE` is only issued once the last logical handler for that channel unsubscribes.
35
+
36
+ ## `publish<T>(channel, value)`
37
+
38
+ JSON-encodes a value and publishes it. The return value is Redis's subscriber count at publication time.
39
+
40
+ ```ts
41
+ const subscribers = await redis.pubsub.publish('orders.created', {
42
+ orderId: 'ord_123',
43
+ userId: 'user_42',
44
+ });
45
+ console.log(`Delivered to ${subscribers} subscribers`);
46
+ ```
47
+
48
+ ## `subscribe<T>(channel, handler)`
49
+
50
+ Creates a logical subscription and returns a `Subscription` handle.
51
+
52
+ ```ts
53
+ const subscription = await redis.pubsub.subscribe<OrderCreated>(
54
+ 'orders.created',
55
+ message => {
56
+ console.log(message.channel, message.value.orderId);
57
+ },
58
+ );
59
+
60
+ await subscription.unsubscribe();
61
+ ```
62
+
63
+ The callback receives a `PubSubMessage<T>` containing the logical channel and decoded value.
64
+
65
+ Multiple subscriptions to the same logical channel share the dedicated subscriber connection.
66
+
67
+ ```ts
68
+ const a = await redis.pubsub.subscribe('cache.invalidate', msg => console.log('A', msg.value));
69
+ const b = await redis.pubsub.subscribe('cache.invalidate', msg => console.log('B', msg.value));
70
+ await a.unsubscribe();
71
+ // B remains subscribed.
72
+ await b.unsubscribe();
73
+ ```
74
+
75
+ ## `fullChannel(channel)`
76
+
77
+ Returns the namespaced physical channel.
78
+
79
+ ```ts
80
+ redis.pubsub.fullChannel('orders.created');
81
+ // myapp:events:orders.created
82
+ ```
83
+
84
+ ## `close()`
85
+
86
+ Closes the dedicated subscriber connection. Call it during application shutdown.
87
+
88
+ ```ts
89
+ process.on('SIGTERM', async () => {
90
+ await redis.pubsub.close();
91
+ process.exit(0);
92
+ });
93
+ ```
94
+
95
+ ## Pub/Sub vs Streams
96
+
97
+ Use Pub/Sub when losing messages while nobody is listening is acceptable and low-latency fan-out is the priority. Use Streams when messages need persistence, consumer groups, acknowledgements, replay, or controlled processing.
98
+
99
+ ## Overrides
100
+
101
+ ```ts
102
+ redis.withPubSub({ channelPrefix: 'critical' });
103
+ redis.withPubSub({ maxMessageBytes: 256 * 1024 }, 'replace');
104
+ ```
105
+
106
+ When replacing or reconfiguring Pub/Sub, close the previous subscriber connection before relying on the new configuration.
@@ -0,0 +1,7 @@
1
+ # Rate limit module
2
+
3
+ This directory contains the complete usage documentation for the rate limit module.
4
+
5
+ **Full guide:** [usage.md](./usage.md)
6
+
7
+ The usage guide documents configuration, public types, every public method, arguments, return values, semantics, error/edge-case behavior, and multiple examples.
@@ -0,0 +1,100 @@
1
+ # Rate Limiting Module — Complete Usage Guide
2
+
3
+ `RedisRateLimiter` implements an atomic fixed-window counter. It is suitable for request, API-key, user, IP, job, or other subject-based quotas.
4
+
5
+ ## Setup
6
+
7
+ ```ts
8
+ import { createRedisClient } from 'ioredis-toolkit';
9
+
10
+ const redis = createRedisClient({
11
+ mode: 'standalone',
12
+ host: '127.0.0.1',
13
+ port: 6379,
14
+ rateLimit: {
15
+ enabled: true,
16
+ namespace: 'api:limit',
17
+ windowSeconds: 60,
18
+ maxRequests: 100,
19
+ },
20
+ });
21
+ ```
22
+
23
+ `rateLimit.enabled` defaults to `false`. Accessing `redis.rateLimiter` while disabled throws `RedisConfigurationError`; set `enabled: true` to use the module.
24
+
25
+ ## Configuration
26
+
27
+ | Option | Type | Default | Description |
28
+ |---|---|---:|---|
29
+ | `enabled` | `boolean` | `false` | Enables the module. `redis.rateLimiter` throws `RedisConfigurationError` while disabled; `new RedisRateLimiter(...)` remains directly constructible either way. |
30
+ | `namespace` | `string` | `rate-limit` | Counter key prefix. |
31
+ | `windowSeconds` | `number` | `60` | Fixed-window duration. |
32
+ | `maxRequests` | `number` | `100` | Maximum units per window. |
33
+
34
+ ## Methods
35
+
36
+ ### `key(subject, nowSeconds?)`
37
+
38
+ Returns the counter key for the current fixed window. Supplying `nowSeconds` makes tests deterministic.
39
+
40
+ ```ts
41
+ redis.rateLimiter.key('user:42', 120); // rate-limit:user:42:2 for a 60s window
42
+ ```
43
+
44
+ ### `consume(subject, cost?, nowSeconds?)`
45
+
46
+ Atomically increments the current window and creates its TTL on the first increment.
47
+
48
+ ```ts
49
+ const decision = await redis.rateLimiter.consume('user:42');
50
+ if (!decision.allowed) {
51
+ throw new Error(`Rate limited; retry in ${decision.retryAfterSeconds}s`);
52
+ }
53
+ ```
54
+
55
+ Weighted costs are useful for expensive operations:
56
+
57
+ ```ts
58
+ const decision = await redis.rateLimiter.consume('user:42', 5);
59
+ ```
60
+
61
+ ### `check(subject, nowSeconds?)`
62
+
63
+ Reads the current counter without incrementing it.
64
+
65
+ ```ts
66
+ const state = await redis.rateLimiter.check('user:42');
67
+ console.log(state.remaining, state.resetAt);
68
+ ```
69
+
70
+ `check()` is advisory. A concurrent `consume()` can change the result immediately afterward.
71
+
72
+ ### `reset(subject, nowSeconds?)`
73
+
74
+ Deletes the current window counter.
75
+
76
+ ```ts
77
+ await redis.rateLimiter.reset('user:42');
78
+ ```
79
+
80
+ ## Result type
81
+
82
+ `RateLimitResult` contains:
83
+
84
+ - `allowed`: whether the operation fits within the limit.
85
+ - `limit`: configured maximum.
86
+ - `remaining`: remaining units, never below zero.
87
+ - `resetAt`: estimated Unix timestamp when the current window expires.
88
+ - `retryAfterSeconds`: TTL to wait when denied, otherwise `0`.
89
+ - `count`: current consumed units.
90
+
91
+ ## Fixed-window behavior
92
+
93
+ A fixed window can allow bursts at a boundary. For example, a request near the end of one window and another immediately after the boundary count against different windows. If smoother traffic shaping is required, use a different algorithm rather than assuming this module is a sliding-window limiter.
94
+
95
+ ## Overrides
96
+
97
+ ```ts
98
+ redis.withRateLimit({ maxRequests: 500 });
99
+ redis.withRateLimit({ namespace: 'login' }, 'replace');
100
+ ```
@@ -0,0 +1,7 @@
1
+ # Sessions module
2
+
3
+ This directory contains the complete usage documentation for the sessions module.
4
+
5
+ **Full guide:** [usage.md](./usage.md)
6
+
7
+ The usage guide documents configuration, public types, every public method, arguments, return values, semantics, error/edge-case behavior, and multiple examples.