ioredis-toolkit 0.0.10 → 0.0.12

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 (256) hide show
  1. package/CHANGELOG.md +79 -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/dist/cache.d.ts +0 -797
  187. package/dist/cache.js +0 -1115
  188. package/dist/client.d.ts +0 -287
  189. package/dist/client.js +0 -1113
  190. package/dist/cluster-slot.d.ts +0 -4
  191. package/dist/cluster-slot.js +0 -31
  192. package/dist/cluster.d.ts +0 -79
  193. package/dist/cluster.js +0 -156
  194. package/dist/errors.d.ts +0 -30
  195. package/dist/errors.js +0 -63
  196. package/dist/health.d.ts +0 -180
  197. package/dist/health.js +0 -239
  198. package/dist/lock.d.ts +0 -233
  199. package/dist/lock.js +0 -440
  200. package/dist/logger.d.ts +0 -12
  201. package/dist/logger.js +0 -40
  202. package/dist/pubsub.d.ts +0 -423
  203. package/dist/pubsub.js +0 -537
  204. package/dist/ratelimiter.d.ts +0 -441
  205. package/dist/ratelimiter.js +0 -539
  206. package/dist/session/index.d.ts +0 -23
  207. package/dist/session/index.js +0 -16
  208. package/dist/session/revocation-store.d.ts +0 -176
  209. package/dist/session/revocation-store.js +0 -318
  210. package/dist/session/scripts/cleanup-index.lua +0 -21
  211. package/dist/session/scripts/conditional-update-encrypted.lua +0 -60
  212. package/dist/session/scripts/conditional-update.lua +0 -63
  213. package/dist/session/scripts/create.lua +0 -83
  214. package/dist/session/scripts/delete-by-user.lua +0 -29
  215. package/dist/session/scripts/delete.lua +0 -15
  216. package/dist/session/scripts/enforce-limit.lua +0 -38
  217. package/dist/session/scripts/revoke.lua +0 -61
  218. package/dist/session/scripts/rotate-encrypted.lua +0 -149
  219. package/dist/session/scripts/rotate.lua +0 -167
  220. package/dist/session/scripts/touch-encrypted.lua +0 -89
  221. package/dist/session/scripts/touch.lua +0 -72
  222. package/dist/session/scripts/validate.lua +0 -90
  223. package/dist/session/session-circuit-breaker.d.ts +0 -42
  224. package/dist/session/session-circuit-breaker.js +0 -129
  225. package/dist/session/session-config.d.ts +0 -355
  226. package/dist/session/session-config.js +0 -171
  227. package/dist/session/session-cookie.d.ts +0 -72
  228. package/dist/session/session-cookie.js +0 -101
  229. package/dist/session/session-encryption.d.ts +0 -87
  230. package/dist/session/session-encryption.js +0 -139
  231. package/dist/session/session-errors.d.ts +0 -85
  232. package/dist/session/session-errors.js +0 -145
  233. package/dist/session/session-health.d.ts +0 -38
  234. package/dist/session/session-health.js +0 -60
  235. package/dist/session/session-keys.d.ts +0 -64
  236. package/dist/session/session-keys.js +0 -128
  237. package/dist/session/session-manager.d.ts +0 -73
  238. package/dist/session/session-manager.js +0 -94
  239. package/dist/session/session-metrics.d.ts +0 -41
  240. package/dist/session/session-metrics.js +0 -135
  241. package/dist/session/session-repository.d.ts +0 -184
  242. package/dist/session/session-repository.js +0 -763
  243. package/dist/session/session-scripts.d.ts +0 -36
  244. package/dist/session/session-scripts.js +0 -130
  245. package/dist/session/session-serializer.d.ts +0 -42
  246. package/dist/session/session-serializer.js +0 -267
  247. package/dist/session/session-service.d.ts +0 -123
  248. package/dist/session/session-service.js +0 -670
  249. package/dist/session/session-token.d.ts +0 -38
  250. package/dist/session/session-token.js +0 -86
  251. package/dist/session/session-types.d.ts +0 -281
  252. package/dist/session/session-types.js +0 -16
  253. package/dist/types.d.ts +0 -924
  254. package/dist/types.js +0 -151
  255. package/dist/utils/deepmerge.d.ts +0 -9
  256. package/dist/utils/deepmerge.js +0 -61
@@ -1,72 +0,0 @@
1
- -- touch.lua (version 1) - plain (v1) envelopes
2
- -- Throttled, monotonic activity refresh for plain sessions.
3
- --
4
- -- The script is authoritative for all state checks (exists, status,
5
- -- absolute expiry, idle expiry, throttle). Timestamps come from Redis
6
- -- server time, so distributed application clocks cannot race each other.
7
- --
8
- -- KEYS[1] = session record key
9
- --
10
- -- ARGV[1] = touchInterval (seconds)
11
- -- ARGV[2] = idleTimeout (seconds, '' when idle timeout disabled)
12
- -- ARGV[3] = force (1 = ignore throttle)
13
- --
14
- -- Returns:
15
- -- 1 touched (write performed)
16
- -- 2 skipped: inside touchInterval
17
- -- 3 skipped: request older than recorded activity (not possible with
18
- -- server time; retained for parity with the encrypted path)
19
- -- 0 not found
20
- -- -1 consumed or revoked
21
- -- -2 absolute expiry passed; record deleted
22
- -- -3 idle expired; NOT resurrected
23
- -- 4 envelope is encrypted (caller must use the encrypted touch path)
24
- local raw = redis.call('GET', KEYS[1])
25
-
26
- if not raw then
27
- return 0
28
- end
29
-
30
- local env = cjson.decode(raw)
31
-
32
- if env.v ~= 1 then
33
- return 4
34
- end
35
-
36
- local s = env.s
37
-
38
- if s.status ~= 'active' then
39
- return -1
40
- end
41
-
42
- local now = tonumber(redis.call('TIME')[1])
43
-
44
- if tonumber(s.absoluteExpiresAt) <= now then
45
- redis.call('DEL', KEYS[1])
46
- return -2
47
- end
48
-
49
- if s.idleExpiresAt and s.idleExpiresAt ~= cjson.null and tonumber(s.idleExpiresAt) <= now then
50
- return -3
51
- end
52
-
53
- local interval = tonumber(ARGV[1])
54
-
55
- if tonumber(ARGV[3]) ~= 1 and now - tonumber(s.lastAccessedAt) < interval then
56
- return 2
57
- end
58
-
59
- s.lastAccessedAt = now
60
-
61
- if ARGV[2] ~= '' then
62
- local idle = now + tonumber(ARGV[2])
63
- local abs = tonumber(s.absoluteExpiresAt)
64
- -- Rolling extends the idle boundary but NEVER the absolute boundary.
65
- s.idleExpiresAt = math.min(idle, abs)
66
- end
67
-
68
- local ttl = math.max(1, tonumber(s.absoluteExpiresAt) - now)
69
-
70
- redis.call('SET', KEYS[1], cjson.encode(env), 'EX', ttl)
71
-
72
- return 1
@@ -1,90 +0,0 @@
1
- -- validate.lua (version 1)
2
- -- Single-round-trip validation read: fetches the session record, the
3
- -- user's security version (when the key exists) and lazily cleans up
4
- -- expired records and their index members, all in one same-slot script.
5
- --
6
- -- The script performs the cheap, non-cryptographic state checks; binding
7
- -- checks and (for encrypted sessions) decryption happen app-side on the
8
- -- returned payload. A missing security-version key means the check is
9
- -- skipped (the version is only enforced once setSecurityVersion has run).
10
- --
11
- -- KEYS[1] = session record key
12
- -- KEYS[2] = user security version key (same slot)
13
- -- KEYS[3] = user session index key (same slot)
14
- --
15
- -- ARGV[1] = jti (used for the lazy index cleanup of encrypted records,
16
- -- whose headers do not carry their own jti)
17
- --
18
- -- Returns:
19
- -- {1, raw, securityVersion?} record exists and passed script checks;
20
- -- raw is the stored envelope; securityVersion
21
- -- is the current user version when set
22
- -- {0} not found
23
- -- {-1, status} not active (consumed/revoked)
24
- -- {-2} expired (absolute); record + index entry
25
- -- removed
26
- -- {-3} idle expired; record + index entry removed
27
- -- {-4} security version mismatch (plain records)
28
- local raw = redis.call('GET', KEYS[1])
29
-
30
- if not raw then
31
- return { 0 }
32
- end
33
-
34
- local currentRaw = redis.call('GET', KEYS[2])
35
- local currentVersion = nil
36
-
37
- if currentRaw then
38
- currentVersion = tonumber(currentRaw)
39
- end
40
-
41
- local env = cjson.decode(raw)
42
- local now = tonumber(redis.call('TIME')[1])
43
-
44
- if env.v == 1 then
45
- local s = env.s
46
-
47
- if s.status ~= 'active' then
48
- return { -1, s.status }
49
- end
50
-
51
- if tonumber(s.absoluteExpiresAt) <= now then
52
- redis.call('DEL', KEYS[1])
53
- redis.call('ZREM', KEYS[3], s.jti)
54
- return { -2 }
55
- end
56
-
57
- if s.idleExpiresAt and s.idleExpiresAt ~= cjson.null and tonumber(s.idleExpiresAt) <= now then
58
- redis.call('DEL', KEYS[1])
59
- redis.call('ZREM', KEYS[3], s.jti)
60
- return { -3 }
61
- end
62
-
63
- if currentVersion and (s.securityVersion == nil or tonumber(s.securityVersion) ~= currentVersion) then
64
- return { -4 }
65
- end
66
- elseif env.v == 2 then
67
- -- Encrypted envelope: only the plaintext header mirrors are readable in
68
- -- Lua. They are the script-level authority for state decisions; the
69
- -- ciphertext remains authoritative for the payload (assertHeaderMatches).
70
- if env.st ~= 'active' then
71
- return { -1, env.st }
72
- end
73
-
74
- if tonumber(env.exp) <= now then
75
- redis.call('DEL', KEYS[1])
76
- redis.call('ZREM', KEYS[3], ARGV[1])
77
- return { -2 }
78
- end
79
-
80
- if env.idle ~= cjson.null and tonumber(env.idle) <= now then
81
- redis.call('DEL', KEYS[1])
82
- redis.call('ZREM', KEYS[3], ARGV[1])
83
- return { -3 }
84
- end
85
-
86
- -- Security version is checked app-side for encrypted records (it lives
87
- -- only inside the ciphertext).
88
- end
89
-
90
- return { 1, raw, currentVersion }
@@ -1,42 +0,0 @@
1
- import type { SessionCircuitBreakerConfig } from './session-config.js';
2
- export type CircuitBreakerState = 'closed' | 'open' | 'half_open';
3
- /**
4
- * Fail-closed circuit breaker around session storage operations.
5
- *
6
- * `run()` executes a function while the circuit is closed or a half-open
7
- * probe is allowed, and throws {@link CircuitBreakerOpenError} when open.
8
- * Callers may instead use {@link tryAcquire} / {@link recordSuccess} /
9
- * {@link recordFailure} explicitly.
10
- */
11
- export declare class SessionCircuitBreaker {
12
- private readonly config;
13
- private readonly circuit;
14
- private readonly now;
15
- private readonly onTransition;
16
- constructor(config: SessionCircuitBreakerConfig, options?: {
17
- now?: () => number;
18
- onTransition?: (state: CircuitBreakerState) => void;
19
- });
20
- get state(): CircuitBreakerState;
21
- /**
22
- * Runs an operation under circuit protection. Records success/failure
23
- * based on the promise outcome.
24
- *
25
- * @throws {CircuitBreakerOpenError} when the circuit is open.
26
- */
27
- run<T>(fn: () => Promise<T>): Promise<T>;
28
- /**
29
- * Attempts to acquire a call slot synchronously. Returns false (fail
30
- * closed) when the circuit is open and not yet ready for probes.
31
- */
32
- tryAcquire(): boolean;
33
- /** Records a successful operation (closes a half-open circuit). */
34
- recordSuccess(): void;
35
- /** Records a failed operation (may open the circuit). */
36
- recordFailure(): void;
37
- /** Evaluates whether the open timer has elapsed, rolling to half-open. */
38
- private rollHalfOpen;
39
- private transitionTo;
40
- /** Resets the breaker to closed (admin/repair). */
41
- reset(): void;
42
- }
@@ -1,129 +0,0 @@
1
- import { CircuitBreakerOpenError } from './session-errors.js';
2
- /**
3
- * Fail-closed circuit breaker around session storage operations.
4
- *
5
- * `run()` executes a function while the circuit is closed or a half-open
6
- * probe is allowed, and throws {@link CircuitBreakerOpenError} when open.
7
- * Callers may instead use {@link tryAcquire} / {@link recordSuccess} /
8
- * {@link recordFailure} explicitly.
9
- */
10
- export class SessionCircuitBreaker {
11
- config;
12
- circuit;
13
- now;
14
- onTransition;
15
- constructor(config, options = {}) {
16
- this.config = config;
17
- this.now = options.now ?? (() => Date.now());
18
- this.onTransition = options.onTransition ?? null;
19
- this.circuit = {
20
- state: 'closed',
21
- consecutiveFailures: 0,
22
- openedAt: 0,
23
- halfOpenInFlight: 0,
24
- halfOpenSucceeded: 0,
25
- };
26
- }
27
- get state() {
28
- return this.circuit.state;
29
- }
30
- /**
31
- * Runs an operation under circuit protection. Records success/failure
32
- * based on the promise outcome.
33
- *
34
- * @throws {CircuitBreakerOpenError} when the circuit is open.
35
- */
36
- async run(fn) {
37
- if (!this.tryAcquire()) {
38
- throw new CircuitBreakerOpenError();
39
- }
40
- try {
41
- const result = await fn();
42
- this.recordSuccess();
43
- return result;
44
- }
45
- catch (error) {
46
- this.recordFailure();
47
- throw error;
48
- }
49
- }
50
- /**
51
- * Attempts to acquire a call slot synchronously. Returns false (fail
52
- * closed) when the circuit is open and not yet ready for probes.
53
- */
54
- tryAcquire() {
55
- this.rollHalfOpen();
56
- if (this.circuit.state === 'closed')
57
- return true;
58
- if (this.circuit.state === 'half_open') {
59
- if (this.circuit.halfOpenInFlight >= this.config.halfOpenMaxRequests) {
60
- return false;
61
- }
62
- this.circuit.halfOpenInFlight += 1;
63
- return true;
64
- }
65
- return false;
66
- }
67
- /** Records a successful operation (closes a half-open circuit). */
68
- recordSuccess() {
69
- if (this.circuit.state === 'closed') {
70
- this.circuit.consecutiveFailures = 0;
71
- return;
72
- }
73
- if (this.circuit.state !== 'half_open')
74
- return;
75
- this.circuit.halfOpenInFlight = Math.max(0, this.circuit.halfOpenInFlight - 1);
76
- this.circuit.halfOpenSucceeded += 1;
77
- if (this.circuit.halfOpenInFlight === 0 && this.circuit.halfOpenSucceeded >= 1) {
78
- this.transitionTo('closed');
79
- }
80
- }
81
- /** Records a failed operation (may open the circuit). */
82
- recordFailure() {
83
- if (this.circuit.state === 'closed') {
84
- this.circuit.consecutiveFailures += 1;
85
- if (this.circuit.consecutiveFailures >= this.config.failureThreshold) {
86
- this.transitionTo('open');
87
- }
88
- return;
89
- }
90
- if (this.circuit.state === 'half_open') {
91
- this.circuit.halfOpenInFlight = Math.max(0, this.circuit.halfOpenInFlight - 1);
92
- this.transitionTo('open');
93
- }
94
- }
95
- /** Evaluates whether the open timer has elapsed, rolling to half-open. */
96
- rollHalfOpen() {
97
- if (this.circuit.state === 'open' &&
98
- this.now() - this.circuit.openedAt >= this.config.resetTimeoutMs) {
99
- this.circuit.state = 'half_open';
100
- this.circuit.halfOpenInFlight = 0;
101
- this.circuit.halfOpenSucceeded = 0;
102
- this.onTransition?.('half_open');
103
- }
104
- }
105
- transitionTo(state) {
106
- if (this.circuit.state === state)
107
- return;
108
- this.circuit.state = state;
109
- if (state === 'open') {
110
- this.circuit.openedAt = this.now();
111
- this.circuit.halfOpenInFlight = 0;
112
- this.circuit.halfOpenSucceeded = 0;
113
- }
114
- if (state === 'half_open') {
115
- this.circuit.halfOpenInFlight = 0;
116
- this.circuit.halfOpenSucceeded = 0;
117
- }
118
- if (state === 'closed') {
119
- this.circuit.consecutiveFailures = 0;
120
- this.circuit.halfOpenInFlight = 0;
121
- this.circuit.halfOpenSucceeded = 0;
122
- }
123
- this.onTransition?.(state);
124
- }
125
- /** Resets the breaker to closed (admin/repair). */
126
- reset() {
127
- this.transitionTo('closed');
128
- }
129
- }
@@ -1,355 +0,0 @@
1
- import { z } from 'zod';
2
- /** Default absolute session lifetime: 7 days. */
3
- export declare const TTL: number;
4
- /** Default idle timeout: 24 hours. */
5
- export declare const IDLE_TIMEOUT: number;
6
- /** Default touch throttle interval: 5 minutes. */
7
- export declare const TOUCH_INTERVAL: number;
8
- export declare const SessionStatusSchema: z.ZodEnum<{
9
- active: "active";
10
- consumed: "consumed";
11
- revoked: "revoked";
12
- }>;
13
- /** How strictly session binding metadata (IP/UA/device) is enforced. */
14
- export declare const SessionBindingPolicySchema: z.ZodEnum<{
15
- disabled: "disabled";
16
- advisory: "advisory";
17
- strict: "strict";
18
- }>;
19
- /** Optional fail-closed circuit breaker around session operations. */
20
- export interface SessionCircuitBreakerConfig {
21
- /** Enable the fail-closed circuit breaker. Default: false. */
22
- enabled: boolean;
23
- /** Consecutive failures needed to open the circuit. */
24
- failureThreshold: number;
25
- /** Milliseconds the circuit stays open before half-open probes. */
26
- resetTimeoutMs: number;
27
- /** Maximum concurrent probe requests while half-open. */
28
- halfOpenMaxRequests: number;
29
- }
30
- export declare const SessionCircuitBreakerConfigSchema: z.ZodPrefault<z.ZodObject<{
31
- enabled: z.ZodDefault<z.ZodBoolean>;
32
- failureThreshold: z.ZodDefault<z.ZodNumber>;
33
- resetTimeoutMs: z.ZodDefault<z.ZodNumber>;
34
- halfOpenMaxRequests: z.ZodDefault<z.ZodNumber>;
35
- }, z.core.$strip>>;
36
- /** Optional AES-256-GCM encryption of session data at rest. */
37
- export interface SessionEncryptionConfig {
38
- /**
39
- * Enable AES-256-GCM encryption at rest. Default: false.
40
- *
41
- * Evaluate first whether transport TLS, ACLs, private networking and
42
- * infrastructure controls already cover your threat model. Encryption
43
- * protects session data against a compromised Redis instance or its
44
- * disk; it does NOT protect against a compromised application process.
45
- */
46
- enabled: boolean;
47
- /**
48
- * Re-encrypt with the current key version on touch/update (lazy key
49
- * rotation). Default: true.
50
- */
51
- reEncryptOnWrite: boolean;
52
- }
53
- export declare const SessionEncryptionConfigSchema: z.ZodObject<{
54
- enabled: z.ZodDefault<z.ZodBoolean>;
55
- reEncryptOnWrite: z.ZodDefault<z.ZodBoolean>;
56
- }, z.core.$strip>;
57
- /** Metrics collection for session operations. */
58
- export interface SessionMetricsConfig {
59
- /**
60
- * Collect internal session metrics through the injected metrics adapter.
61
- * When no adapter is provided, metrics are a safe no-op regardless.
62
- */
63
- enabled: boolean;
64
- }
65
- export declare const SessionMetricsConfigSchema: z.ZodObject<{
66
- enabled: z.ZodDefault<z.ZodBoolean>;
67
- }, z.core.$strip>;
68
- /** Health check thresholds for the session dependency. */
69
- export interface SessionHealthConfig {
70
- /** PING latency above this (ms) marks the dependency degraded. */
71
- latencyThresholdMs: number;
72
- /** Recent operation error rate above this marks the dependency degraded. */
73
- errorRateThreshold: number;
74
- /** Number of recent operations sampled for the error rate. */
75
- errorWindowSize: number;
76
- }
77
- export declare const SessionHealthConfigSchema: z.ZodObject<{
78
- latencyThresholdMs: z.ZodDefault<z.ZodNumber>;
79
- errorRateThreshold: z.ZodDefault<z.ZodNumber>;
80
- errorWindowSize: z.ZodDefault<z.ZodNumber>;
81
- }, z.core.$strip>;
82
- /** Cookie defaults for the framework-independent cookie manager. */
83
- export interface SessionCookieConfig {
84
- /** Cookie name. Default: 'sid'. */
85
- name: string;
86
- /** Cookie Path attribute. Default: '/'. */
87
- path: string;
88
- /** Cookie Domain attribute (empty = host-only cookie). */
89
- domain?: string;
90
- /** HttpOnly attribute. Default: true. */
91
- httpOnly: boolean;
92
- /** Secure attribute. Default: true. */
93
- secure: boolean;
94
- /** SameSite attribute. Default: 'lax'. */
95
- sameSite: 'strict' | 'lax' | 'none';
96
- /** Max-Age in seconds (falls back to the session TTL when unset). */
97
- maxAge?: number;
98
- }
99
- export declare const SessionCookieConfigSchema: z.ZodObject<{
100
- name: z.ZodDefault<z.ZodString>;
101
- path: z.ZodDefault<z.ZodString>;
102
- domain: z.ZodOptional<z.ZodString>;
103
- httpOnly: z.ZodDefault<z.ZodBoolean>;
104
- secure: z.ZodDefault<z.ZodBoolean>;
105
- sameSite: z.ZodDefault<z.ZodEnum<{
106
- none: "none";
107
- strict: "strict";
108
- lax: "lax";
109
- }>>;
110
- maxAge: z.ZodOptional<z.ZodNumber>;
111
- }, z.core.$strip>;
112
- /** Limits protecting Redis memory, Lua and pipelines. */
113
- export interface SessionLimitsConfig {
114
- /** Maximum serialized metadata size in bytes (reject larger writes). */
115
- maxMetadataSize: number;
116
- /** Maximum sessions fetched per list page. */
117
- maxListPageSize: number;
118
- /** Maximum session keys touched by one Lua script invocation. */
119
- maxBatchSize: number;
120
- /** Maximum concurrent cross-slot pipelines (revokeAll, jti cleanup). */
121
- maxFanOutConcurrency: number;
122
- /** Maximum sessions evicted by a single enforce-limit script call. */
123
- maxEvictionsPerCall: number;
124
- /** Maximum session deletions per user-request path (revokeAll/destroy-all). */
125
- maxSessionsPerUserHardCap: number;
126
- }
127
- export declare const SessionLimitsConfigSchema: z.ZodObject<{
128
- maxMetadataSize: z.ZodDefault<z.ZodNumber>;
129
- maxListPageSize: z.ZodDefault<z.ZodNumber>;
130
- maxBatchSize: z.ZodDefault<z.ZodNumber>;
131
- maxFanOutConcurrency: z.ZodDefault<z.ZodNumber>;
132
- maxEvictionsPerCall: z.ZodDefault<z.ZodNumber>;
133
- maxSessionsPerUserHardCap: z.ZodDefault<z.ZodNumber>;
134
- }, z.core.$strip>;
135
- /** Full parsed session configuration (defaults applied by the schema). */
136
- export interface SessionConfig {
137
- /**
138
- * Master switch. Sessions are NOT enabled implicitly; an application
139
- * must explicitly opt in. Default: false.
140
- */
141
- enabled: boolean;
142
- /** Key namespace for all session keys. Default: 'authcore'. */
143
- namespace: string;
144
- /**
145
- * Raw session token entropy in bytes (32 = 256 bits). Minimum 16
146
- * (128 bits). Default: 32.
147
- */
148
- tokenBytes: number;
149
- /**
150
- * Absolute session lifetime in seconds (the hard maximum). Redis TTL is
151
- * derived from this boundary; touch/rolling NEVER extends past it.
152
- * Default: 7 days.
153
- */
154
- ttl: number;
155
- /**
156
- * Idle timeout in seconds. When null, sessions never expire through
157
- * inactivity. Default: 1 day.
158
- */
159
- idleTimeout: number | null;
160
- /**
161
- * Rolling sessions: valid activity extends the idle boundary (never the
162
- * absolute boundary). Only meaningful when idleTimeout is set.
163
- * Default: true.
164
- */
165
- rolling: boolean;
166
- /**
167
- * Touch throttling in seconds: touch() performs no write when the last
168
- * activity is more recent than this interval. Default: 300.
169
- */
170
- touchInterval: number;
171
- /**
172
- * Maximum concurrent sessions per user. 0 disables the limit.
173
- * Default: 20.
174
- *
175
- * Enforcement is atomic per create (same-slot Lua): the create that
176
- * pushes the count over the limit evicts the oldest excess sessions in
177
- * the same script, so concurrent logins cannot both observe spare
178
- * capacity. For extremely large per-user session counts, eviction is
179
- * bounded per script call and converges over subsequent creates.
180
- */
181
- maxSessionsPerUser: number;
182
- /** Store the device id on creation (advisory binding). Default: false. */
183
- storeDeviceId: boolean;
184
- /** Store the IP address on creation (advisory binding). Default: false. */
185
- storeIpAddress: boolean;
186
- /** Store the user agent on creation (advisory binding). Default: false. */
187
- storeUserAgent: boolean;
188
- /**
189
- * Session binding policy. 'disabled' (default) ignores binding fields;
190
- * 'advisory' reports mismatches on validation; 'strict' rejects with
191
- * reason 'binding_mismatch'. IP addresses change (NAT, mobile), user
192
- * agents are spoofable, device ids may be absent — do not enable strict
193
- * binding lightly.
194
- */
195
- bindingPolicy: 'disabled' | 'advisory' | 'strict';
196
- /**
197
- * Security versioning. When enabled, validate() requires the session's
198
- * securityVersion to match the current version stored at
199
- * `{ns}:security-version:{userId}`. Use setSecurityVersion(userId, v)
200
- * after password changes / MFA resets to invalidate all older sessions.
201
- */
202
- securityVersion: {
203
- enabled: boolean;
204
- };
205
- /**
206
- * Optional global JTI -> userId lookup index.
207
- *
208
- * Default: false. Prefer passing userId to validate()/get()/rotate() —
209
- * the authentication layer already knows it, and the index adds a write,
210
- * a read, a second consistency boundary and a second key family. The
211
- * index is NEVER authoritative: the session record is. It has its own
212
- * TTL, self-heals on read, and a missing entry is not proof of absence
213
- * (see docs/architecture for exact semantics).
214
- */
215
- jtiIndex: {
216
- enabled: boolean;
217
- };
218
- /**
219
- * Check the revocation store during validate(). Off by default:
220
- * in-record revocation (status revoked/consumed) already covers rotation
221
- * reuse and session-level revoke; the revocation store is for external
222
- * JTI revocations (e.g. JWT jti denylists) and adds a second read.
223
- */
224
- checkRevocationStore: boolean;
225
- /** Optional AES-256-GCM encryption at rest. Default: disabled. */
226
- encryption: SessionEncryptionConfig;
227
- /** Optional fail-closed circuit breaker. Default: disabled. */
228
- circuitBreaker: SessionCircuitBreakerConfig;
229
- /** Metrics collection. Default: enabled (no-op without an adapter). */
230
- metrics: SessionMetricsConfig;
231
- /** Health check thresholds. */
232
- health: SessionHealthConfig;
233
- /** Cookie defaults for the framework-independent cookie manager. */
234
- cookie: SessionCookieConfig;
235
- /** Operational limits (memory, Lua, pipeline bounds). */
236
- limits: SessionLimitsConfig;
237
- /**
238
- * Idempotent creation: when SessionCreateInput.idempotencyKey is set,
239
- * store a short-lived claim so retries return the original session.
240
- * Default: false.
241
- */
242
- enableCreateIdempotency: boolean;
243
- /**
244
- * Retain a short-lived consumed tombstone after rotation instead of
245
- * deleting the old record, enabling replay detection. The tombstone is
246
- * bounded by the remaining absolute lifetime. Default: true.
247
- */
248
- retainConsumedTombstones: boolean;
249
- /**
250
- * On genuine reuse of an already-rotated-away (consumed) session token -
251
- * not a same-nonce idempotent retry of an in-flight rotation - atomically
252
- * revoke the entire rotation lineage's current active generation instead
253
- * of only rejecting the replayed request. This is the strongest response
254
- * to stolen refresh-token reuse (specification ยง5): if an attacker
255
- * captured an old token and the legitimate client has since rotated past
256
- * it, this kills the session the attacker could otherwise keep riding,
257
- * rather than leaving it live while only the stale replay is rejected.
258
- *
259
- * Has no effect unless retainConsumedTombstones is also true: a replay
260
- * can only be detected while a consumed tombstone still exists to be
261
- * replayed against. Default: false (opt-in - a false positive here is a
262
- * full lineage kill, so it should be turned on deliberately once the
263
- * caller's retry semantics are understood; the resulting
264
- * SessionReplayError with reason 'family_revoked' should be handled as
265
- * a security event, e.g. forcing full re-authentication and logging).
266
- */
267
- revokeFamilyOnReplay: boolean;
268
- }
269
- export declare const SessionConfigSchema: z.ZodObject<{
270
- enabled: z.ZodDefault<z.ZodBoolean>;
271
- namespace: z.ZodDefault<z.ZodString>;
272
- tokenBytes: z.ZodDefault<z.ZodNumber>;
273
- ttl: z.ZodDefault<z.ZodNumber>;
274
- idleTimeout: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
275
- rolling: z.ZodDefault<z.ZodBoolean>;
276
- touchInterval: z.ZodDefault<z.ZodNumber>;
277
- maxSessionsPerUser: z.ZodDefault<z.ZodNumber>;
278
- storeDeviceId: z.ZodDefault<z.ZodBoolean>;
279
- storeIpAddress: z.ZodDefault<z.ZodBoolean>;
280
- storeUserAgent: z.ZodDefault<z.ZodBoolean>;
281
- bindingPolicy: z.ZodDefault<z.ZodEnum<{
282
- disabled: "disabled";
283
- advisory: "advisory";
284
- strict: "strict";
285
- }>>;
286
- securityVersion: z.ZodPrefault<z.ZodObject<{
287
- enabled: z.ZodDefault<z.ZodBoolean>;
288
- }, z.core.$strip>>;
289
- jtiIndex: z.ZodPrefault<z.ZodObject<{
290
- enabled: z.ZodDefault<z.ZodBoolean>;
291
- }, z.core.$strip>>;
292
- checkRevocationStore: z.ZodDefault<z.ZodBoolean>;
293
- encryption: z.ZodPrefault<z.ZodObject<{
294
- enabled: z.ZodDefault<z.ZodBoolean>;
295
- reEncryptOnWrite: z.ZodDefault<z.ZodBoolean>;
296
- }, z.core.$strip>>;
297
- circuitBreaker: z.ZodPrefault<z.ZodObject<{
298
- enabled: z.ZodDefault<z.ZodBoolean>;
299
- failureThreshold: z.ZodDefault<z.ZodNumber>;
300
- resetTimeoutMs: z.ZodDefault<z.ZodNumber>;
301
- halfOpenMaxRequests: z.ZodDefault<z.ZodNumber>;
302
- }, z.core.$strip>>;
303
- metrics: z.ZodPrefault<z.ZodObject<{
304
- enabled: z.ZodDefault<z.ZodBoolean>;
305
- }, z.core.$strip>>;
306
- health: z.ZodPrefault<z.ZodObject<{
307
- latencyThresholdMs: z.ZodDefault<z.ZodNumber>;
308
- errorRateThreshold: z.ZodDefault<z.ZodNumber>;
309
- errorWindowSize: z.ZodDefault<z.ZodNumber>;
310
- }, z.core.$strip>>;
311
- cookie: z.ZodPrefault<z.ZodObject<{
312
- name: z.ZodDefault<z.ZodString>;
313
- path: z.ZodDefault<z.ZodString>;
314
- domain: z.ZodOptional<z.ZodString>;
315
- httpOnly: z.ZodDefault<z.ZodBoolean>;
316
- secure: z.ZodDefault<z.ZodBoolean>;
317
- sameSite: z.ZodDefault<z.ZodEnum<{
318
- none: "none";
319
- strict: "strict";
320
- lax: "lax";
321
- }>>;
322
- maxAge: z.ZodOptional<z.ZodNumber>;
323
- }, z.core.$strip>>;
324
- limits: z.ZodPrefault<z.ZodObject<{
325
- maxMetadataSize: z.ZodDefault<z.ZodNumber>;
326
- maxListPageSize: z.ZodDefault<z.ZodNumber>;
327
- maxBatchSize: z.ZodDefault<z.ZodNumber>;
328
- maxFanOutConcurrency: z.ZodDefault<z.ZodNumber>;
329
- maxEvictionsPerCall: z.ZodDefault<z.ZodNumber>;
330
- maxSessionsPerUserHardCap: z.ZodDefault<z.ZodNumber>;
331
- }, z.core.$strip>>;
332
- enableCreateIdempotency: z.ZodDefault<z.ZodBoolean>;
333
- retainConsumedTombstones: z.ZodDefault<z.ZodBoolean>;
334
- revokeFamilyOnReplay: z.ZodDefault<z.ZodBoolean>;
335
- }, z.core.$strip>;
336
- /** Recursively makes every config field optional (matches the schema's input shape). */
337
- export type DeepPartial<T> = {
338
- [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
339
- };
340
- /** Raw unparsed config input (all fields optional, nested included). */
341
- export type SessionConfigInput = DeepPartial<SessionConfig>;
342
- /** Raw unparsed config (partial, defaults applied). */
343
- export type PartialSessionConfig = Partial<SessionConfigInput>;
344
- /**
345
- * Parses and validates session configuration.
346
- *
347
- * @throws {SessionConfigurationError} with a safe message on invalid config.
348
- */
349
- export declare function parseSessionConfig(input?: PartialSessionConfig): SessionConfig;
350
- /**
351
- * Returns a redacted copy of the config suitable for logging.
352
- * Strips nothing by default (no secrets are allowed in config), but the
353
- * serializer is explicit so future secret-bearing fields cannot leak.
354
- */
355
- export declare function redactSessionConfig(config: SessionConfig): Record<string, unknown>;