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
@@ -1,145 +0,0 @@
1
- import { RedisError } from '../errors.js';
2
- /* -------------------------------------------------------------------------- */
3
- /* Typed, safe session errors. */
4
- /* */
5
- /* Rules: */
6
- /* - Never embed raw session tokens, cookies, passwords, encryption keys, */
7
- /* or full session payloads in messages. */
8
- /* - JTI/userId are only included in `details` (structured metadata) after */
9
- /* redaction, never as plain strings in `message`. */
10
- /* - Authentication layers map these errors to HTTP semantics: */
11
- /* SessionStorageError -> 503 (infrastructure unavailable) */
12
- /* SessionNotFoundError -> 401 */
13
- /* SessionExpiredError -> 401 */
14
- /* SessionRevokedError -> 401 */
15
- /* CircuitBreakerOpenError -> 503 */
16
- /* -------------------------------------------------------------------------- */
17
- /**
18
- * Base class for every session subsystem error.
19
- * Extends {@link RedisError} so existing Redis error handling keeps working.
20
- */
21
- export class SessionError extends RedisError {
22
- constructor(message, code = 'SESSION_ERROR', details) {
23
- super(message, code, details);
24
- this.name = 'SessionError';
25
- }
26
- }
27
- /** The session does not exist (or no longer exists). */
28
- export class SessionNotFoundError extends SessionError {
29
- constructor(details) {
30
- super('Session not found.', 'SESSION_NOT_FOUND', details);
31
- this.name = 'SessionNotFoundError';
32
- }
33
- }
34
- /** The session expired (absolute or idle timeout), or was created in the past. */
35
- export class SessionExpiredError extends SessionError {
36
- constructor(details) {
37
- super('Session has expired.', 'SESSION_EXPIRED', details);
38
- this.name = 'SessionExpiredError';
39
- }
40
- }
41
- /** The session was revoked or consumed by a rotation (replay detected). */
42
- export class SessionRevokedError extends SessionError {
43
- constructor(details) {
44
- super('Session is no longer valid.', 'SESSION_REVOKED', details);
45
- this.name = 'SessionRevokedError';
46
- }
47
- }
48
- /** The session record exists but is invalid (corrupt, tampered, mismatched). */
49
- export class SessionInvalidError extends SessionError {
50
- constructor(details) {
51
- super('Session is invalid.', 'SESSION_INVALID', details);
52
- this.name = 'SessionInvalidError';
53
- }
54
- }
55
- /** A security-sensitive session transition (rotation) failed. */
56
- export class SessionRotationError extends SessionError {
57
- constructor(details) {
58
- super('Session rotation failed.', 'SESSION_ROTATION_FAILED', details);
59
- this.name = 'SessionRotationError';
60
- }
61
- }
62
- /** Reuse of an already-consumed session token was detected. */
63
- export class SessionReplayError extends SessionError {
64
- constructor(details) {
65
- super('Session reuse detected.', 'SESSION_REPLAY', details);
66
- this.name = 'SessionReplayError';
67
- }
68
- }
69
- /**
70
- * Redis (or the session storage backend) is unavailable.
71
- *
72
- * Authentication MUST fail closed on this error: never treat it as an
73
- * invalid session, and never fall back to assuming the session is valid.
74
- */
75
- export class SessionStorageError extends SessionError {
76
- constructor(message = 'Session storage unavailable.', details) {
77
- super(message, 'SESSION_STORAGE_UNAVAILABLE', details);
78
- this.name = 'SessionStorageError';
79
- }
80
- }
81
- /** Stored session data could not be deserialized/decrypted. */
82
- export class SessionSerializationError extends SessionError {
83
- constructor(details) {
84
- super('Stored session data is malformed.', 'SESSION_SERIALIZATION_ERROR', details);
85
- this.name = 'SessionSerializationError';
86
- }
87
- }
88
- /** Session configuration is invalid (fails at manager construction). */
89
- export class SessionConfigurationError extends SessionError {
90
- constructor(message, details) {
91
- super(message, 'SESSION_CONFIGURATION_ERROR', details);
92
- this.name = 'SessionConfigurationError';
93
- }
94
- }
95
- /** Optimistic-concurrency conflict on a session update. */
96
- export class SessionConcurrencyError extends SessionError {
97
- constructor(details) {
98
- super('Session was modified concurrently.', 'SESSION_CONCURRENCY', details);
99
- this.name = 'SessionConcurrencyError';
100
- }
101
- }
102
- /** A revocation could not be persisted (fail closed, do not swallow). */
103
- export class RevocationError extends SessionError {
104
- constructor(details) {
105
- super('Revocation could not be persisted.', 'REVOCATION_ERROR', details);
106
- this.name = 'RevocationError';
107
- }
108
- }
109
- /** A batch revocation partially failed; check `failures` for details. */
110
- export class RevocationBatchError extends SessionError {
111
- /** Safe identifiers of the entries whose pipeline command failed. */
112
- failures;
113
- constructor(failures) {
114
- super(`Revocation batch failed for ${failures.length} entr${failures.length === 1 ? 'y' : 'ies'}.`, 'REVOCATION_BATCH_ERROR', { failureCount: failures.length });
115
- this.name = 'RevocationBatchError';
116
- this.failures = failures;
117
- }
118
- /** Compatibility alias: the affected jtis. */
119
- get ids() {
120
- return this.failures.map((f) => f.jti);
121
- }
122
- }
123
- /** The circuit breaker is open; requests fail closed without touching Redis. */
124
- export class CircuitBreakerOpenError extends SessionError {
125
- constructor(details) {
126
- super('Authentication infrastructure is temporarily unavailable.', 'CIRCUIT_OPEN', details);
127
- this.name = 'CircuitBreakerOpenError';
128
- }
129
- }
130
- /**
131
- * Redacts an identifier for safe inclusion in logs/errors/metrics labels.
132
- *
133
- * Only the length and a short opaque suffix are revealed; never the full
134
- * value. Use for jti/userId/deviceId/ipAddress in structured details.
135
- *
136
- * @example
137
- * redactIdentifier('dG9rZW5oYXNo...') // => 'token#c3V'
138
- */
139
- export function redactIdentifier(value) {
140
- if (!value)
141
- return 'none';
142
- if (value.length <= 6)
143
- return `#${'*'.repeat(value.length)}`;
144
- return `${value.slice(0, 3)}…${value.slice(-3)}`;
145
- }
@@ -1,38 +0,0 @@
1
- import type { RedisClientWrapper } from '../client.js';
2
- import type { SessionHealthConfig } from './session-config.js';
3
- export interface SessionHealthStatus {
4
- /** True when PING latency and the recent error rate are within thresholds. */
5
- healthy: boolean;
6
- /** PING round trip in milliseconds, or null when the probe itself failed. */
7
- latencyMs: number | null;
8
- /** Recent operation error rate (0..1) from the sliding window. */
9
- errorRate: number;
10
- /** True when Redis is reachable at all (PING succeeded). */
11
- reachable: boolean;
12
- /** Unix ms of the last probe. */
13
- checkedAt: number;
14
- }
15
- /**
16
- * Periodic PING probe + sliding-window error rate for the session dependency.
17
- *
18
- * Error-rate sampling is fed by {@link recordOp}; a PING probe runs on every
19
- * {@link check}. Both signals must be healthy for the overall status to be
20
- * healthy.
21
- */
22
- export declare class SessionHealthChecker {
23
- private readonly client;
24
- private readonly config;
25
- private readonly now;
26
- private readonly results;
27
- constructor(client: RedisClientWrapper, config: SessionHealthConfig, options?: {
28
- now?: () => number;
29
- });
30
- /** Feeds one operation outcome into the sliding window. */
31
- recordOp(success: boolean): void;
32
- private errorRate;
33
- /**
34
- * Runs a PING probe and returns the current health status.
35
- * Never throws: probe failures surface as `reachable: false`.
36
- */
37
- check(): Promise<SessionHealthStatus>;
38
- }
@@ -1,60 +0,0 @@
1
- /**
2
- * Periodic PING probe + sliding-window error rate for the session dependency.
3
- *
4
- * Error-rate sampling is fed by {@link recordOp}; a PING probe runs on every
5
- * {@link check}. Both signals must be healthy for the overall status to be
6
- * healthy.
7
- */
8
- export class SessionHealthChecker {
9
- client;
10
- config;
11
- now;
12
- results = [];
13
- constructor(client, config, options = {}) {
14
- this.client = client;
15
- this.config = config;
16
- this.now = options.now ?? (() => Date.now());
17
- }
18
- /** Feeds one operation outcome into the sliding window. */
19
- recordOp(success) {
20
- this.results.push(success);
21
- const max = this.config.errorWindowSize;
22
- if (this.results.length > max) {
23
- this.results.splice(0, this.results.length - max);
24
- }
25
- }
26
- errorRate() {
27
- if (this.results.length === 0)
28
- return 0;
29
- let failures = 0;
30
- for (const ok of this.results) {
31
- if (!ok)
32
- failures += 1;
33
- }
34
- return failures / this.results.length;
35
- }
36
- /**
37
- * Runs a PING probe and returns the current health status.
38
- * Never throws: probe failures surface as `reachable: false`.
39
- */
40
- async check() {
41
- const checkedAt = this.now();
42
- const started = performance.now();
43
- let reachable = true;
44
- let latencyMs = null;
45
- try {
46
- const pong = await this.client.raw.ping();
47
- reachable = pong === 'PONG' || pong === true || pong === 'pong';
48
- latencyMs = Math.round(performance.now() - started);
49
- }
50
- catch {
51
- reachable = false;
52
- latencyMs = null;
53
- }
54
- const errorRate = this.errorRate();
55
- const healthy = reachable &&
56
- (latencyMs === null || latencyMs <= this.config.latencyThresholdMs) &&
57
- errorRate <= this.config.errorRateThreshold;
58
- return { healthy, latencyMs, errorRate, reachable, checkedAt };
59
- }
60
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * Deterministically encodes a userId for safe embedding in Redis keys.
3
- *
4
- * Every byte not in [A-Za-z0-9._-] is hex-encoded as %XX (UTF-8 aware),
5
- * so `{`, `}`, `:` and glob metacharacters can never appear. The encoding
6
- * is injective: distinct userIds always produce distinct encodings.
7
- */
8
- export declare function encodeUserId(userId: string): string;
9
- /**
10
- * Deterministic, Cluster-safe key strategy.
11
- *
12
- * Every key derived here is stable for the lifetime of the process and
13
- * identical across horizontally scaled instances (no randomness).
14
- */
15
- export declare class SessionKeyStrategy {
16
- private readonly namespace;
17
- /**
18
- * @param namespace - Key namespace, e.g. `'authcore'`.
19
- */
20
- constructor(namespace: string);
21
- private ns;
22
- /**
23
- * Key of a single session record. Hash-tagged by userId, so all of one
24
- * user's session keys share a slot.
25
- */
26
- sessionKey(userId: string, jti: string): string;
27
- /** Key of the user's session index (ZSET, member = jti, score = createdAt). */
28
- userIndexKey(userId: string): string;
29
- /** Key of the user's security version counter. Same user slot. */
30
- securityVersionKey(userId: string): string;
31
- /**
32
- * Short-lived idempotent-creation claim key (user slot). Bounded TTL is
33
- * set by the create script; a claim only ever suppresses a duplicate.
34
- */
35
- createClaimKey(userId: string, jti: string): string;
36
- /**
37
- * Key of the optional global JTI -> userId index.
38
- * Deliberately NOT hash-tagged: it is cross-slot from the session record
39
- * and treated as derived state (see docs/architecture).
40
- */
41
- jtiIndexKey(jti: string): string;
42
- /** Key of a revocation entry (single-key, cluster-safe, no tag needed). */
43
- revokedKey(jti: string): string;
44
- /**
45
- * Session key prefix for a user, used by Lua eviction to construct keys
46
- * from jtis. The `{userId}` hash tag guarantees same-slot construction.
47
- */
48
- sessionKeyPrefix(userId: string): string;
49
- /** Index key prefix for namespace-scoped administration. */
50
- namespacePrefix(): string;
51
- }
@@ -1,113 +0,0 @@
1
- import { SessionConfigurationError } from './session-errors.js';
2
- /* -------------------------------------------------------------------------- */
3
- /* Redis key design for Redis Cluster. */
4
- /* */
5
- /* session record : {ns}:session:{userId}:session:{jti} */
6
- /* user index : {ns}:user-sessions:{userId} (ZSET) */
7
- /* security ver : {ns}:security-version:{userId} (string) */
8
- /* jti index (opt): {ns}:jti-index:{jti} (cross-slot, global) */
9
- /* revoked (opt) : {ns}:revoked:{jti} (cross-slot, single) */
10
- /* */
11
- /* The literal text `{userId}` is the Redis Cluster hash tag: every key of */
12
- /* one user shares one slot, enabling atomic Lua scripts per user. No global */
13
- /* hash tag is used, so sessions are spread across slots. */
14
- /* */
15
- /* userId values are percent-encoded before embedding: values containing */
16
- /* `{`, `}`, `:`, `*`, `?`, `[`, `]` etc. would otherwise break hash tags, */
17
- /* glob patterns and key parsing. The encoding is deterministic and */
18
- /* collision-free. */
19
- /* -------------------------------------------------------------------------- */
20
- /** Characters kept verbatim in the encoded user id. */
21
- const SAFE = /^[A-Za-z0-9._-]$/;
22
- /**
23
- * Deterministically encodes a userId for safe embedding in Redis keys.
24
- *
25
- * Every byte not in [A-Za-z0-9._-] is hex-encoded as %XX (UTF-8 aware),
26
- * so `{`, `}`, `:` and glob metacharacters can never appear. The encoding
27
- * is injective: distinct userIds always produce distinct encodings.
28
- */
29
- export function encodeUserId(userId) {
30
- if (typeof userId !== 'string' || userId.length === 0 || userId.length > 512) {
31
- throw new SessionConfigurationError('userId must be a non-empty string of at most 512 chars.');
32
- }
33
- let out = '';
34
- for (let i = 0; i < userId.length; i++) {
35
- const ch = userId[i];
36
- if (SAFE.test(ch)) {
37
- out += ch;
38
- }
39
- else {
40
- for (const byte of Buffer.from(ch, 'utf8')) {
41
- out += `%${byte.toString(16).padStart(2, '0')}`;
42
- }
43
- }
44
- }
45
- return out;
46
- }
47
- /**
48
- * Deterministic, Cluster-safe key strategy.
49
- *
50
- * Every key derived here is stable for the lifetime of the process and
51
- * identical across horizontally scaled instances (no randomness).
52
- */
53
- export class SessionKeyStrategy {
54
- namespace;
55
- /**
56
- * @param namespace - Key namespace, e.g. `'authcore'`.
57
- */
58
- constructor(namespace) {
59
- const trimmed = namespace.trim();
60
- if (!trimmed || trimmed.length > 64 || /[\s{}/:*?[\]]/.test(trimmed)) {
61
- throw new SessionConfigurationError('namespace must be 1-64 chars without whitespace or glob/hash-tag metacharacters.');
62
- }
63
- this.namespace = trimmed;
64
- }
65
- ns(part) {
66
- return `${this.namespace}:${part}`;
67
- }
68
- /**
69
- * Key of a single session record. Hash-tagged by userId, so all of one
70
- * user's session keys share a slot.
71
- */
72
- sessionKey(userId, jti) {
73
- return this.ns(`session:{${encodeUserId(userId)}}:session:${jti}`);
74
- }
75
- /** Key of the user's session index (ZSET, member = jti, score = createdAt). */
76
- userIndexKey(userId) {
77
- return this.ns(`user-sessions:{${encodeUserId(userId)}}`);
78
- }
79
- /** Key of the user's security version counter. Same user slot. */
80
- securityVersionKey(userId) {
81
- return this.ns(`security-version:{${encodeUserId(userId)}}`);
82
- }
83
- /**
84
- * Short-lived idempotent-creation claim key (user slot). Bounded TTL is
85
- * set by the create script; a claim only ever suppresses a duplicate.
86
- */
87
- createClaimKey(userId, jti) {
88
- return this.ns(`create-claim:{${encodeUserId(userId)}}:${jti}`);
89
- }
90
- /**
91
- * Key of the optional global JTI -> userId index.
92
- * Deliberately NOT hash-tagged: it is cross-slot from the session record
93
- * and treated as derived state (see docs/architecture).
94
- */
95
- jtiIndexKey(jti) {
96
- return this.ns(`jti-index:${jti}`);
97
- }
98
- /** Key of a revocation entry (single-key, cluster-safe, no tag needed). */
99
- revokedKey(jti) {
100
- return this.ns(`revoked:${jti}`);
101
- }
102
- /**
103
- * Session key prefix for a user, used by Lua eviction to construct keys
104
- * from jtis. The `{userId}` hash tag guarantees same-slot construction.
105
- */
106
- sessionKeyPrefix(userId) {
107
- return this.ns(`session:{${encodeUserId(userId)}}:session:`);
108
- }
109
- /** Index key prefix for namespace-scoped administration. */
110
- namespacePrefix() {
111
- return `${this.namespace}:`;
112
- }
113
- }
@@ -1,73 +0,0 @@
1
- import type { RedisClientWrapper } from '../client.js';
2
- import type { RevocationStore } from './session-types.js';
3
- import { SessionCircuitBreaker } from './session-circuit-breaker.js';
4
- import type { PartialSessionConfig, SessionConfig } from './session-config.js';
5
- import { SessionCookieManager } from './session-cookie.js';
6
- import { SessionHealthChecker } from './session-health.js';
7
- import type { SessionKeyProvider } from './session-encryption.js';
8
- import { SessionKeyStrategy } from './session-keys.js';
9
- import { SessionMetrics } from './session-metrics.js';
10
- import type { SessionMetricsAdapter } from './session-metrics.js';
11
- import { SessionRepository } from './session-repository.js';
12
- import { SessionService } from './session-service.js';
13
- import { SessionTokenManager } from './session-token.js';
14
- export type SessionManagerOptions = {
15
- /** Redis client (standalone, sentinel or cluster - all supported). */
16
- client: RedisClientWrapper;
17
- /** Session configuration (defaults applied; see SessionConfigSchema). */
18
- config?: PartialSessionConfig;
19
- /**
20
- * Encryption key provider. REQUIRED when config.encryption.enabled is
21
- * true (fail at construction rather than at first write). The provider
22
- * should be backed by a KMS/vault in production.
23
- */
24
- encryptionKeyProvider?: SessionKeyProvider;
25
- /** External revocation store (JWT jti denylists etc.). */
26
- revocationStore?: RevocationStore;
27
- /** Metrics adapter (no-op without it). */
28
- metricsAdapter?: SessionMetricsAdapter;
29
- /** Optional circuit breaker (enabled via config.circuitBreaker.enabled). */
30
- circuitBreaker?: SessionCircuitBreaker;
31
- /** Injectable clock for tests. */
32
- now?: () => number;
33
- };
34
- export type WithSessionManagerOptions = {
35
- /** Session configuration (defaults applied; see SessionConfigSchema). */
36
- config?: PartialSessionConfig;
37
- /**
38
- * Encryption key provider. REQUIRED when config.encryption.enabled is
39
- * true (fail at construction rather than at first write). The provider
40
- * should be backed by a KMS/vault in production.
41
- */
42
- encryptionKeyProvider?: SessionKeyProvider;
43
- /** Metrics adapter (no-op without it). */
44
- metricsAdapter?: SessionMetricsAdapter;
45
- /** Injectable clock for tests. */
46
- now?: () => number;
47
- };
48
- export declare class SessionManager {
49
- readonly config: SessionConfig;
50
- readonly service: SessionService;
51
- readonly repository: SessionRepository;
52
- readonly metrics: SessionMetrics;
53
- readonly circuitBreaker: SessionCircuitBreaker | null;
54
- readonly health: SessionHealthChecker;
55
- readonly cookies: SessionCookieManager;
56
- readonly token: SessionTokenManager;
57
- readonly keys: SessionKeyStrategy;
58
- private readonly scripts;
59
- private readonly client;
60
- constructor(options: SessionManagerOptions);
61
- /** Preloads Lua scripts now (awaits SCRIPT LOAD on all nodes). */
62
- init(): Promise<void>;
63
- /** No-op for symmetry: the client is owned by the application. */
64
- close(): void;
65
- }
66
- /**
67
- * Creates a session manager. Synchronous: use `await manager.init()` when
68
- * eager script preloading matters (first call latency).
69
- *
70
- * @throws {SessionConfigurationError} when the config is invalid, encryption
71
- * is enabled without a key provider, or sessions are not explicitly enabled.
72
- */
73
- export declare function createSessionManager(options: SessionManagerOptions): SessionManager;
@@ -1,94 +0,0 @@
1
- import { SessionConfigurationError } from './session-errors.js';
2
- import { SessionCircuitBreaker } from './session-circuit-breaker.js';
3
- import { parseSessionConfig } from './session-config.js';
4
- import { SessionCookieManager } from './session-cookie.js';
5
- import { SessionHealthChecker } from './session-health.js';
6
- import { SessionKeyStrategy } from './session-keys.js';
7
- import { SessionMetrics } from './session-metrics.js';
8
- import { SessionRepository } from './session-repository.js';
9
- import { SessionScriptRegistry } from './session-scripts.js';
10
- import { SessionService } from './session-service.js';
11
- import { SessionTokenManager } from './session-token.js';
12
- export class SessionManager {
13
- config;
14
- service;
15
- repository;
16
- metrics;
17
- circuitBreaker;
18
- health;
19
- cookies;
20
- token;
21
- keys;
22
- scripts;
23
- client;
24
- constructor(options) {
25
- const config = parseSessionConfig(options.config);
26
- if (config.encryption.enabled && !options.encryptionKeyProvider) {
27
- throw new SessionConfigurationError('encryptionKeyProvider is required when config.encryption.enabled is true.');
28
- }
29
- if (!config.enabled) {
30
- // Constructing an enabled-by-default subsystem would surprise; the
31
- // manager is inert until config.enabled is explicitly set.
32
- throw new SessionConfigurationError('Session subsystem is not enabled: set config.enabled = true to opt in.');
33
- }
34
- this.client = options.client;
35
- this.config = config;
36
- this.token = new SessionTokenManager(config.tokenBytes);
37
- this.keys = new SessionKeyStrategy(config.namespace);
38
- this.scripts = new SessionScriptRegistry(options.client);
39
- this.repository = new SessionRepository({
40
- client: options.client,
41
- keys: this.keys,
42
- config,
43
- scripts: this.scripts,
44
- keyProvider: options.encryptionKeyProvider ?? null,
45
- });
46
- this.metrics = new SessionMetrics(options.metricsAdapter, options.client.mode);
47
- this.circuitBreaker =
48
- options.circuitBreaker ??
49
- (config.circuitBreaker.enabled
50
- ? new SessionCircuitBreaker(config.circuitBreaker, {
51
- onTransition: (state) => this.metrics.breakerState(state),
52
- })
53
- : null);
54
- this.health = new SessionHealthChecker(options.client, config.health, {
55
- ...(options.now !== undefined ? { now: options.now } : {}),
56
- });
57
- this.cookies = new SessionCookieManager(config.cookie);
58
- this.service = new SessionService({
59
- config,
60
- client: options.client,
61
- repository: this.repository,
62
- token: this.token,
63
- keys: this.keys,
64
- ...(options.revocationStore !== undefined
65
- ? { revocationStore: options.revocationStore }
66
- : {}),
67
- metrics: this.metrics,
68
- ...(this.circuitBreaker !== null ? { circuitBreaker: this.circuitBreaker } : {}),
69
- health: this.health,
70
- ...(options.now !== undefined ? { now: options.now } : {}),
71
- });
72
- // Preload scripts in the background; the EVALSHA + NOSCRIPT fallback
73
- // keeps working until (and after) the preload finishes.
74
- void this.scripts.preload();
75
- }
76
- /** Preloads Lua scripts now (awaits SCRIPT LOAD on all nodes). */
77
- async init() {
78
- await this.scripts.preload();
79
- }
80
- /** No-op for symmetry: the client is owned by the application. */
81
- close() {
82
- this.scripts.invalidate();
83
- }
84
- }
85
- /**
86
- * Creates a session manager. Synchronous: use `await manager.init()` when
87
- * eager script preloading matters (first call latency).
88
- *
89
- * @throws {SessionConfigurationError} when the config is invalid, encryption
90
- * is enabled without a key provider, or sessions are not explicitly enabled.
91
- */
92
- export function createSessionManager(options) {
93
- return new SessionManager(options);
94
- }
@@ -1,33 +0,0 @@
1
- /** Application-provided metrics sink. Implementations must be non-throwing. */
2
- export interface SessionMetricsAdapter {
3
- /** Increments a counter by delta (default 1). */
4
- incCounter(name: string, delta?: number, attributes?: Record<string, string | number>): void;
5
- /** Records a duration sample (milliseconds). */
6
- recordHistogram(name: string, value: number, attributes?: Record<string, string | number>): void;
7
- /** Sets a gauge to a value. */
8
- setGauge(name: string, value: number, attributes?: Record<string, string | number>): void;
9
- }
10
- export type SessionOperation = 'create' | 'validate' | 'touch' | 'rotate' | 'update' | 'destroy' | 'revoke' | 'revoke_all' | 'delete_by_user' | 'list' | 'find_by_user' | 'set_security_version' | 'health';
11
- /**
12
- * Internal session metrics facade. Safe no-op without an adapter.
13
- */
14
- export declare class SessionMetrics {
15
- private readonly adapter;
16
- private readonly topology;
17
- constructor(adapter?: SessionMetricsAdapter | null, topology?: string);
18
- /** Adds the constant topology label to an attribute set. */
19
- private withTopology;
20
- /** Counts a completed session operation, with its outcome. */
21
- operation(op: SessionOperation, outcome: 'ok' | 'error' | 'invalid', code?: string): void;
22
- /** Records operation latency in milliseconds. */
23
- latency(op: SessionOperation, ms: number): void;
24
- /** Records the circuit breaker state transition. */
25
- breakerState(state: 'closed' | 'open' | 'half_open'): void;
26
- /** Records a fail-closed revocation-store failure (a security-relevant miss). */
27
- revocationMiss(): void;
28
- /** Records encryption failures (key rotation issues, corruption). */
29
- encryptionError(reason: string): void;
30
- /** Records a failed best-effort jti index write (derived-state degradation). */
31
- jtiIndexWriteFailure(): void;
32
- }
33
- export declare const SESSION_OPERATIONS: readonly SessionOperation[];