@voltro/kv 0.86.0 → 0.88.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 (61) hide show
  1. package/CHANGELOG.md +5484 -0
  2. package/README.md +110 -0
  3. package/THIRD-PARTY-NOTICES.md +2 -482
  4. package/dist/Kv.js +1 -2
  5. package/dist/KvStore.js +1 -2
  6. package/dist/analytics.d.ts +337 -0
  7. package/dist/analytics.js +15 -0
  8. package/dist/backendMemory.js +1 -2
  9. package/dist/backendRedis.js +1 -2
  10. package/dist/cacheAtomicProtocol.js +257 -0
  11. package/dist/cacheAtomicScript.js +326 -0
  12. package/dist/cacheAtomicState.js +140 -0
  13. package/dist/connection.d.ts +990 -9
  14. package/dist/connection.js +336 -52
  15. package/dist/connectionAnalyticsConfig.js +152 -0
  16. package/dist/connectionConfigError.js +10 -0
  17. package/dist/connectionConfigProvider.js +110 -0
  18. package/dist/connectionDefinitions.js +284 -0
  19. package/dist/connectionMemory.js +206 -0
  20. package/dist/connectionMetrics.js +78 -0
  21. package/dist/connectionNamespace.js +18 -0
  22. package/dist/connectionNats.js +148 -0
  23. package/dist/connectionNatsConfig.js +86 -0
  24. package/dist/connectionNatsSubjects.js +15 -0
  25. package/dist/connectionOwner.js +43 -0
  26. package/dist/connectionPool.js +738 -0
  27. package/dist/connectionPrefix.js +36 -0
  28. package/dist/connectionScripts.js +3 -0
  29. package/dist/connectionSqlConfig.js +118 -0
  30. package/dist/connectionStorageConfig.js +189 -0
  31. package/dist/connectionStorageFacade.js +77 -0
  32. package/dist/connectionSubscriber.js +130 -0
  33. package/dist/driverModule.js +9 -0
  34. package/dist/errors.js +6 -3
  35. package/dist/expirySweep.js +1 -2
  36. package/dist/hot-contract.d.ts +297 -0
  37. package/dist/hot-contract.js +17 -0
  38. package/dist/hot-testing.d.ts +242 -0
  39. package/dist/hot-testing.js +1 -0
  40. package/dist/hot.d.ts +936 -0
  41. package/dist/hot.js +7 -0
  42. package/dist/hotMemory.js +45 -0
  43. package/dist/hotOperations.js +222 -0
  44. package/dist/hotRateLimit.js +102 -0
  45. package/dist/hotReadAvailability.js +18 -0
  46. package/dist/hotRedis.js +138 -0
  47. package/dist/hotRedisScript.js +3 -0
  48. package/dist/hotRouting.js +77 -0
  49. package/dist/hotState.js +181 -0
  50. package/dist/hotStoreConformance.js +365 -0
  51. package/dist/index.d.ts +113 -9
  52. package/dist/nativeRedisReads.js +77 -0
  53. package/dist/nativeRedisTopology.js +222 -0
  54. package/dist/redisDiagnostics.js +26 -0
  55. package/dist/redisReadPolicy.js +38 -0
  56. package/dist/sql.d.ts +77 -0
  57. package/dist/sql.js +0 -0
  58. package/dist/storage.d.ts +166 -0
  59. package/dist/storage.js +47 -0
  60. package/dist/storageAddress.js +19 -0
  61. package/package.json +49 -6
@@ -0,0 +1,181 @@
1
+ import { consumeHotRateLimit as e } from "./hotRateLimit.js";
2
+ import { validateHotOp as t } from "./hotOperations.js";
3
+ import { HotError as n } from "./hot-contract.js";
4
+ // src/hotState.ts
5
+ var r = (e, t) => {
6
+ let n = Array.from(e, (e) => e.codePointAt(0) ?? 0), r = Array.from(t, (e) => e.codePointAt(0) ?? 0);
7
+ for (let e = 0; e < Math.min(n.length, r.length); e++) {
8
+ let t = n[e] - r[e];
9
+ if (t !== 0) return t;
10
+ }
11
+ return n.length - r.length;
12
+ }, i = (e, t) => Array.from(e, ([e, t]) => ({
13
+ member: e,
14
+ score: t
15
+ })).sort((e, n) => (t === "asc" ? 1 : -1) * (e.score - n.score || r(e.member, n.member))), a = (e, t) => {
16
+ if ("byScore" in t) return e.filter((e) => e.score >= t.byScore[0] && e.score <= t.byScore[1]);
17
+ let [n, r] = t.byRank, i = Math.max(0, n < 0 ? e.length + n : n), a = r < 0 ? e.length + r : r;
18
+ return a < i ? [] : e.slice(i, a + 1);
19
+ }, o = (e) => {
20
+ switch (e.kind) {
21
+ case "z": return {
22
+ ...e,
23
+ values: new Map(e.values)
24
+ };
25
+ case "h": return {
26
+ ...e,
27
+ values: new Map(e.values)
28
+ };
29
+ case "s": return {
30
+ ...e,
31
+ values: new Set(e.values)
32
+ };
33
+ default: return { ...e };
34
+ }
35
+ }, s = (s, c, l) => {
36
+ c.forEach(t);
37
+ let u = /* @__PURE__ */ new Map(), d = new Set(c.filter((e) => [
38
+ "zadd",
39
+ "zrem",
40
+ "zremRange",
41
+ "incrBy",
42
+ "tokenBucket",
43
+ "rateLimit",
44
+ "hset",
45
+ "hdel",
46
+ "sadd",
47
+ "srem",
48
+ "setNx",
49
+ "compareDelete",
50
+ "compareExpire",
51
+ "expire",
52
+ "delete"
53
+ ].includes(e.op)).map((e) => e.key));
54
+ for (let e of c) {
55
+ let t = s.get(e.key);
56
+ t !== void 0 && t.expiresAt > l && !u.has(e.key) && u.set(e.key, d.has(e.key) ? o(t) : t);
57
+ }
58
+ let f = c.map((t) => {
59
+ let o = u.get(t.key), s = (e) => {
60
+ throw new n({
61
+ operation: t.op,
62
+ key: t.key,
63
+ reason: e
64
+ });
65
+ }, c = (e) => (o !== void 0 && o.kind !== e && s("wrongType"), o), d = (e) => (o = {
66
+ ...e,
67
+ expiresAt: Infinity
68
+ }, u.set(t.key, o), o), f = (e) => {
69
+ e !== void 0 && o !== void 0 && (o.expiresAt = l + e);
70
+ }, p = (e) => {
71
+ e === 0 && u.delete(t.key);
72
+ };
73
+ switch (t.op) {
74
+ case "zadd": {
75
+ let e = c("z")?.values ?? /* @__PURE__ */ new Map();
76
+ o === void 0 && t.members.length > 0 && d({
77
+ kind: "z",
78
+ values: e
79
+ });
80
+ for (let n of t.members) {
81
+ let r = e.get(n.member), i = t.options?.mode ?? "set", a = r === void 0 || i === "set" ? n.score : i === "max" ? Math.max(r, n.score) : i === "min" ? Math.min(r, n.score) : r + n.score;
82
+ Number.isFinite(a) || s("invalid"), e.set(n.member, a);
83
+ }
84
+ return f(t.options?.ttlMs), null;
85
+ }
86
+ case "zrem": {
87
+ let e = c("z")?.values, n = 0;
88
+ for (let r of t.members) e?.delete(r) && n++;
89
+ return p(e?.size ?? 0), n;
90
+ }
91
+ case "zrank": {
92
+ let e = c("z")?.values, n = e === void 0 ? -1 : i(e, t.order).findIndex((e) => e.member === t.member);
93
+ return n < 0 ? null : n;
94
+ }
95
+ case "zscore": return c("z")?.values.get(t.member) ?? null;
96
+ case "zrange": {
97
+ let e = c("z")?.values, n = a(e === void 0 ? [] : i(e, t.order), t.range);
98
+ return t.options?.limit === void 0 ? n : n.slice(0, t.options.limit);
99
+ }
100
+ case "zcard": return c("z")?.values.size ?? 0;
101
+ case "zremRange": {
102
+ let e = c("z")?.values, n = a(e === void 0 ? [] : i(e, "asc"), t.range);
103
+ for (let t of n) e?.delete(t.member);
104
+ return p(e?.size ?? 0), n.length;
105
+ }
106
+ case "incrBy": {
107
+ let e = (c("counter")?.value ?? 0) + t.delta;
108
+ return Number.isSafeInteger(e) || s("invalid"), t.options?.max !== void 0 && e > t.options.max && s("limit"), o === void 0 ? d({
109
+ kind: "counter",
110
+ value: e
111
+ }) : o.kind === "counter" && (o.value = e), f(t.options?.ttlMs), e;
112
+ }
113
+ case "tokenBucket": {
114
+ let e = c("bucket"), n = e === void 0 ? 0 : Math.max(0, l - e.updatedAt), r = Math.min(t.config.capacity, (e?.tokens ?? t.config.capacity) + n * t.config.refillPerSec / 1e3), i = r >= t.cost, a = i ? r - t.cost : r;
115
+ return d({
116
+ kind: "bucket",
117
+ tokens: a,
118
+ updatedAt: Math.max(l, e?.updatedAt ?? l)
119
+ }), f(Math.max(1, Math.ceil(t.config.capacity / t.config.refillPerSec * 1e3))), {
120
+ allowed: i,
121
+ remaining: a,
122
+ retryAfterMs: i ? 0 : Math.ceil((t.cost - r) / t.config.refillPerSec * 1e3)
123
+ };
124
+ }
125
+ case "rateLimit": {
126
+ let n = c("rateLimit")?.state, r = e(n, t.config, t.nowMs ?? l);
127
+ return d({
128
+ kind: "rateLimit",
129
+ state: r.state
130
+ }), f(Math.ceil(Math.max(t.config.windowMs * 2, 6e4) + 1e3)), r.decision;
131
+ }
132
+ case "hset": {
133
+ let e = c("h")?.values ?? /* @__PURE__ */ new Map(), n = Object.entries(t.fields);
134
+ o === void 0 && n.length > 0 && d({
135
+ kind: "h",
136
+ values: e
137
+ });
138
+ for (let [t, r] of n) e.set(t, r);
139
+ return f(t.options?.ttlMs), null;
140
+ }
141
+ case "hget": return c("h")?.values.get(t.field) ?? null;
142
+ case "hgetAll": return Object.fromEntries(c("h")?.values ?? []);
143
+ case "hdel": {
144
+ let e = c("h")?.values, n = 0;
145
+ for (let r of t.fields) e?.delete(r) && n++;
146
+ return p(e?.size ?? 0), n;
147
+ }
148
+ case "sadd": {
149
+ let e = c("s")?.values ?? /* @__PURE__ */ new Set();
150
+ o === void 0 && t.members.length > 0 && d({
151
+ kind: "s",
152
+ values: e
153
+ });
154
+ let n = e.size;
155
+ for (let n of t.members) e.add(n);
156
+ return f(t.options?.ttlMs), e.size - n;
157
+ }
158
+ case "srem": {
159
+ let e = c("s")?.values, n = 0;
160
+ for (let r of t.members) e?.delete(r) && n++;
161
+ return p(e?.size ?? 0), n;
162
+ }
163
+ case "smembers": return [...c("s")?.values ?? []].sort(r);
164
+ case "sismember": return c("s")?.values.has(t.member) ?? !1;
165
+ case "setNx": return o === void 0 && (d({
166
+ kind: "lease",
167
+ value: t.value
168
+ }), f(t.ttlMs), !0);
169
+ case "expire": return f(t.ttlMs), null;
170
+ case "compareDelete": return c("lease")?.value === t.value && (u.delete(t.key), !0);
171
+ case "compareExpire": return c("lease")?.value === t.value && (f(t.ttlMs), !0);
172
+ case "delete": return u.delete(t.key);
173
+ }
174
+ });
175
+ for (let e of c) {
176
+ let t = u.get(e.key);
177
+ t === void 0 ? s.delete(e.key) : s.set(e.key, t);
178
+ }
179
+ return f;
180
+ };
181
+ export { s as applyHotOperations, r as compareHotMembers };
@@ -0,0 +1,365 @@
1
+ import { HotStore as e } from "./hot-contract.js";
2
+ import { Effect as t, Option as n, Result as r, Stream as i } from "effect";
3
+ // src/hotStoreConformance.ts
4
+ var a = (a, o, s) => {
5
+ let { describe: c, expect: l, it: u } = s, d = (n, r) => u(n, async () => {
6
+ let n = o();
7
+ await t.runPromise(t.gen(function* () {
8
+ let t = yield* e;
9
+ l(t.capabilities).toEqual(n.capabilities), yield* r(t, n.advance);
10
+ }).pipe(t.provide(n.layer)));
11
+ });
12
+ c(`hotStoreConformance — ${a}`, () => {
13
+ d("sorted-set order, reverse ties, negative ranks, inclusive score bounds and limits", (e) => t.gen(function* () {
14
+ yield* e.zadd("board", [
15
+ {
16
+ member: "b",
17
+ score: 10
18
+ },
19
+ {
20
+ member: "a",
21
+ score: 10
22
+ },
23
+ {
24
+ member: "c",
25
+ score: 20
26
+ },
27
+ {
28
+ member: "𐀀",
29
+ score: 30
30
+ },
31
+ {
32
+ member: "",
33
+ score: 30
34
+ }
35
+ ]), l((yield* e.zrange("board", { byRank: [0, -1] }, "asc")).map((e) => e.member)).toEqual([
36
+ "a",
37
+ "b",
38
+ "c",
39
+ "",
40
+ "𐀀"
41
+ ]), l((yield* e.zrange("board", { byScore: [10, 20] }, "desc")).map((e) => e.member)).toEqual([
42
+ "c",
43
+ "b",
44
+ "a"
45
+ ]), l(yield* e.zrange("board", { byRank: [0, -1] }, "asc", { limit: 0 })).toEqual([]), l((yield* e.zrange("board", { byRank: [-2, -1] }, "asc")).length).toBe(2), l((yield* e.zrange("board", { byScore: [10, 20] }, "asc", { limit: 1 }))[0]?.member).toBe("a"), l(n.getOrNull(yield* e.zrank("board", "b", "asc"))).toBe(1), l(n.getOrNull(yield* e.zrank("board", "b", "desc"))).toBe(3), l(n.isNone(yield* e.zrank("board", "absent", "asc"))).toBe(!0), l(yield* e.zcard("board")).toBe(5);
46
+ })), d("score modes, duplicate members and removal by rank/score", (e) => t.gen(function* () {
47
+ yield* e.zadd("z", [{
48
+ member: "a",
49
+ score: 10
50
+ }]), yield* e.zadd("z", [{
51
+ member: "a",
52
+ score: 5
53
+ }], { mode: "max" }), l(n.getOrNull(yield* e.zscore("z", "a"))).toBe(10), yield* e.zadd("z", [{
54
+ member: "a",
55
+ score: 7
56
+ }], { mode: "min" }), yield* e.zadd("z", [{
57
+ member: "a",
58
+ score: 2
59
+ }, {
60
+ member: "a",
61
+ score: 3
62
+ }], { mode: "incr" }), l(n.getOrNull(yield* e.zscore("z", "a"))).toBe(12), yield* e.zadd("z", [{
63
+ member: "b",
64
+ score: 20
65
+ }, {
66
+ member: "c",
67
+ score: 30
68
+ }]), l(yield* e.zremRange("z", { byScore: [20, 20] })).toBe(1), l(yield* e.zremRange("z", { byRank: [-1, -1] })).toBe(1), l(yield* e.zrem("z", ["a", "a"])).toBe(1), l(yield* e.zrange("z", { byRank: [0, -1] }, "asc")).toEqual([]), l(n.isNone(yield* e.zscore("z", "a"))).toBe(!0), yield* e.zadd("precise", [{
69
+ member: "p",
70
+ score: 1.2345678901234567
71
+ }]), l(n.getOrNull(yield* e.zscore("precise", "p"))).toBe(1.2345678901234567), l((yield* e.zrange("precise", { byRank: [0, -1] }, "asc"))[0]?.score).toBe(1.2345678901234567), l((yield* e.zrange("precise", { byScore: [1.2345678901234567, 1.2345678901234567] }, "asc"))[0]?.score).toBe(1.2345678901234567), l(yield* e.zremRange("precise", { byScore: [1.2345678901234567, 1.2345678901234567] })).toBe(1);
72
+ })), d("hashes preserve arbitrary field names and sets count unique changes", (e) => t.gen(function* () {
73
+ yield* e.hset("hash", Object.fromEntries([
74
+ ["__proto__", "p"],
75
+ ["constructor", "c"],
76
+ ["a", "1"]
77
+ ])), l(n.getOrNull(yield* e.hget("hash", "__proto__"))).toBe("p"), l(yield* e.hgetAll("hash")).toEqual(Object.fromEntries([
78
+ ["__proto__", "p"],
79
+ ["constructor", "c"],
80
+ ["a", "1"]
81
+ ])), l(yield* e.hdel("hash", ["a", "a"])).toBe(1), l(n.isNone(yield* e.hget("hash", "a"))).toBe(!0), l(yield* e.sadd("set", [
82
+ "b",
83
+ "a",
84
+ "a"
85
+ ])).toBe(2), l(yield* e.sadd("set", ["b"])).toBe(0), l(yield* e.smembers("set")).toEqual(["a", "b"]), l(yield* e.sismember("set", "a")).toBe(!0), l(yield* e.srem("set", ["a", "a"])).toBe(1), l(yield* e.sismember("set", "a")).toBe(!1), l(yield* e.hgetAll("absent")).toEqual({});
86
+ })), d("counter bounds fail without changing the value; increments are atomic", (e) => t.gen(function* () {
87
+ let n = yield* t.all(Array.from({ length: 100 }, () => e.incrBy("counter", 1)), { concurrency: "unbounded" });
88
+ l(new Set(n).size).toBe(100), l(yield* e.incrBy("counter", 0)).toBe(100);
89
+ let i = yield* e.incrBy("counter", 1, { max: 100 }).pipe(t.result);
90
+ l(r.isFailure(i)).toBe(!0), r.isFailure(i) && l(i.failure.reason).toBe("limit"), l(yield* e.incrBy("counter", -20)).toBe(80);
91
+ })), d("concurrent batches do not interleave and concurrent leases have one winner", (e) => t.gen(function* () {
92
+ let n = yield* t.all(Array.from({ length: 20 }, () => e.multi([{
93
+ op: "incrBy",
94
+ key: "atomic",
95
+ delta: 1
96
+ }, {
97
+ op: "incrBy",
98
+ key: "atomic",
99
+ delta: 1
100
+ }])), { concurrency: "unbounded" });
101
+ for (let e of n) l(e[1]).toBe(Number(e[0]) + 1);
102
+ l(yield* e.incrBy("atomic", 0)).toBe(40);
103
+ let r = yield* t.all(Array.from({ length: 100 }, (t, n) => e.setNx("seat", String(n), 2e3)), { concurrency: "unbounded" });
104
+ l(r.filter(Boolean)).toHaveLength(1);
105
+ })), d("conditional lease release and renewal refuse expired owners without changing a successor", (e, n) => t.gen(function* () {
106
+ l(yield* e.compareDelete("conditional", "old")).toBe(!1), l(yield* e.compareExpire("conditional", "old", 1e3)).toBe(!1), l(yield* e.setNx("conditional", "old", 1e3)).toBe(!0), l(yield* e.compareDelete("conditional", "other")).toBe(!1), l(yield* e.compareExpire("conditional", "other", 1e4)).toBe(!1), yield* n(1100), l(yield* e.setNx("conditional", "successor", 1e3)).toBe(!0), l(yield* e.compareDelete("conditional", "old")).toBe(!1), l(yield* e.compareExpire("conditional", "old", 1e4)).toBe(!1), l(yield* e.setNx("conditional", "contender", 1e3)).toBe(!1), yield* n(1100), l(yield* e.setNx("conditional", "next", 1e3)).toBe(!0), l(yield* e.compareDelete("conditional", "next")).toBe(!0), l(yield* e.compareDelete("conditional", "next")).toBe(!1);
107
+ })), d("matching renewals reset TTL and batches retain ordered lease comparisons", (e, n) => t.gen(function* () {
108
+ yield* e.setNx("renewal", "Grüße 🎮", 1e3), l(yield* e.compareExpire("renewal", "Grüße 🎮", 3e3)).toBe(!0), yield* n(1100), l(yield* e.setNx("renewal", "other", 1e3)).toBe(!1), yield* n(2100), l(yield* e.setNx("renewal", "", 1e3)).toBe(!0), l(yield* e.multi([
109
+ {
110
+ op: "compareExpire",
111
+ key: "renewal",
112
+ value: "",
113
+ ttlMs: 1e3
114
+ },
115
+ {
116
+ op: "compareDelete",
117
+ key: "renewal",
118
+ value: ""
119
+ },
120
+ {
121
+ op: "setNx",
122
+ key: "renewal",
123
+ value: "successor",
124
+ ttlMs: 1e3
125
+ },
126
+ {
127
+ op: "compareDelete",
128
+ key: "renewal",
129
+ value: ""
130
+ }
131
+ ])).toEqual([
132
+ !0,
133
+ !0,
134
+ !0,
135
+ !1
136
+ ]), l(yield* e.compareDelete("renewal", "successor")).toBe(!0);
137
+ })), d("conditional lease operations refuse other structures and validate before batch mutation", (e) => t.gen(function* () {
138
+ yield* e.hset("conditional:hash", { value: "retained" }), yield* e.incrBy("conditional:counter", 7), yield* e.tokenBucket("conditional:bucket", {
139
+ capacity: 1,
140
+ refillPerSec: .001
141
+ }), yield* e.rateLimit("conditional:quota", {
142
+ algorithm: "fixed-window",
143
+ limit: 1,
144
+ windowMs: 6e4,
145
+ burst: 1
146
+ });
147
+ for (let n of [
148
+ "conditional:hash",
149
+ "conditional:counter",
150
+ "conditional:bucket",
151
+ "conditional:quota"
152
+ ]) for (let i of [e.compareDelete(n, "7"), e.compareExpire(n, "7", 1e3)]) {
153
+ let e = yield* i.pipe(t.result);
154
+ l(r.isFailure(e)).toBe(!0), r.isFailure(e) && l(e.failure.reason).toBe("wrongType");
155
+ }
156
+ l(yield* e.hgetAll("conditional:hash")).toEqual({ value: "retained" }), l(yield* e.incrBy("conditional:counter", 0)).toBe(7);
157
+ let n = yield* e.multi([{
158
+ op: "setNx",
159
+ key: "conditional:untouched",
160
+ value: "owner",
161
+ ttlMs: 1e3
162
+ }, {
163
+ op: "compareExpire",
164
+ key: "conditional:untouched",
165
+ value: "owner",
166
+ ttlMs: 0
167
+ }]).pipe(t.result);
168
+ l(r.isFailure(n)).toBe(!0), l(yield* e.setNx("conditional:untouched", "new", 1e3)).toBe(!0);
169
+ let i = yield* t.all(Array.from({ length: 40 }, () => e.compareDelete("conditional:untouched", "new")), { concurrency: "unbounded" });
170
+ l(i.filter(Boolean)).toHaveLength(1);
171
+ })), d("token bucket admits once per token and refills with elapsed time", (e, n) => t.gen(function* () {
172
+ let t = {
173
+ capacity: 2,
174
+ refillPerSec: 1
175
+ };
176
+ l((yield* e.tokenBucket("bucket", t)).allowed).toBe(!0), l((yield* e.tokenBucket("bucket", t)).allowed).toBe(!0);
177
+ let r = yield* e.tokenBucket("bucket", t);
178
+ l(r.allowed).toBe(!1), l(r.retryAfterMs).toBeGreaterThan(0), l(r.retryAfterMs).toBeLessThanOrEqual(1e3), yield* n(1100), l((yield* e.tokenBucket("bucket", t)).allowed).toBe(!0);
179
+ })), d("concurrent token consumption cannot oversubscribe capacity", (e) => t.gen(function* () {
180
+ let n = yield* t.all(Array.from({ length: 50 }, () => e.tokenBucket("parallel:bucket", {
181
+ capacity: 10,
182
+ refillPerSec: .001
183
+ })), { concurrency: "unbounded" });
184
+ l(n.filter((e) => e.allowed)).toHaveLength(10);
185
+ }));
186
+ for (let e of [
187
+ "fixed-window",
188
+ "sliding-window",
189
+ "token-bucket"
190
+ ]) d(`${e} quotas cannot oversubscribe under concurrent requests`, (n) => t.gen(function* () {
191
+ let r = {
192
+ algorithm: e,
193
+ limit: 10,
194
+ windowMs: 6e4,
195
+ burst: 10
196
+ }, i = yield* t.all(Array.from({ length: 120 }, () => n.rateLimit("quota:" + e, r, 1e3)), { concurrency: "unbounded" });
197
+ l(i.filter((e) => e.allowed)).toHaveLength(10), l(i.filter((e) => !e.allowed).every((e) => e.remaining === 0 && e.retryAfterMs > 0)).toBe(!0);
198
+ }));
199
+ d("rate-limit window edges, weighted previous window and token refill preserve exact decisions", (e) => t.gen(function* () {
200
+ let t = {
201
+ algorithm: "fixed-window",
202
+ limit: 2,
203
+ windowMs: 1e3,
204
+ burst: 2
205
+ };
206
+ l(yield* e.rateLimit("fixed", t, 0)).toEqual({
207
+ allowed: !0,
208
+ remaining: 1,
209
+ resetAtMs: 1e3,
210
+ retryAfterMs: 0
211
+ }), yield* e.rateLimit("fixed", t, 0), l(yield* e.rateLimit("fixed", t, 999)).toEqual({
212
+ allowed: !1,
213
+ remaining: 0,
214
+ resetAtMs: 1e3,
215
+ retryAfterMs: 1
216
+ }), l((yield* e.rateLimit("fixed", t, 1e3)).remaining).toBe(1);
217
+ let n = {
218
+ ...t,
219
+ algorithm: "sliding-window"
220
+ };
221
+ yield* e.rateLimit("sliding", n, 0), yield* e.rateLimit("sliding", n, 0), l((yield* e.rateLimit("sliding", n, 1e3)).allowed).toBe(!1), l(yield* e.rateLimit("sliding", n, 1500)).toEqual({
222
+ allowed: !0,
223
+ remaining: 0,
224
+ resetAtMs: 2e3,
225
+ retryAfterMs: 0
226
+ }), l((yield* e.rateLimit("sliding", n, 1500)).allowed).toBe(!1), l((yield* e.rateLimit("sliding", n, 4e3)).remaining).toBe(1);
227
+ let r = {
228
+ ...t,
229
+ algorithm: "token-bucket",
230
+ burst: 1
231
+ };
232
+ l((yield* e.rateLimit("token", r, 0)).allowed).toBe(!0), l(yield* e.rateLimit("token", r, 499)).toEqual({
233
+ allowed: !1,
234
+ remaining: 0,
235
+ resetAtMs: 501,
236
+ retryAfterMs: 2
237
+ }), l((yield* e.rateLimit("token", r, 500)).allowed).toBe(!0), l((yield* e.rateLimit("fixed", r, 1100)).allowed).toBe(!0), l((yield* e.rateLimit("zero", {
238
+ ...t,
239
+ limit: 0,
240
+ burst: 0
241
+ }, 0)).allowed).toBe(!1);
242
+ })), d("rate-limit validation precedes batch mutation; wrong types do not destroy data", (e) => t.gen(function* () {
243
+ let n = {
244
+ algorithm: "fixed-window",
245
+ limit: 1,
246
+ windowMs: 1e3,
247
+ burst: 1
248
+ }, i = yield* e.multi([{
249
+ op: "incrBy",
250
+ key: "validation",
251
+ delta: 1
252
+ }, {
253
+ op: "rateLimit",
254
+ key: "quota",
255
+ config: {
256
+ ...n,
257
+ limit: -1
258
+ }
259
+ }]).pipe(t.result);
260
+ l(r.isFailure(i)).toBe(!0), l(yield* e.incrBy("validation", 0)).toBe(0), yield* e.hset("typed", { value: "retained" });
261
+ let a = yield* e.rateLimit("typed", n).pipe(t.result);
262
+ l(r.isFailure(a)).toBe(!0), r.isFailure(a) && l(a.failure.reason).toBe("wrongType"), l(yield* e.hgetAll("typed")).toEqual({ value: "retained" });
263
+ let o = yield* e.multi([{
264
+ op: "rateLimit",
265
+ key: "batched",
266
+ config: n,
267
+ nowMs: 0
268
+ }, {
269
+ op: "rateLimit",
270
+ key: "batched",
271
+ config: n,
272
+ nowMs: 0
273
+ }]);
274
+ l(o).toEqual([{
275
+ allowed: !0,
276
+ remaining: 0,
277
+ resetAtMs: 1e3,
278
+ retryAfterMs: 0
279
+ }, {
280
+ allowed: !1,
281
+ remaining: 0,
282
+ resetAtMs: 1e3,
283
+ retryAfterMs: 1e3
284
+ }]);
285
+ })), d("rate-limit state expires with its structure TTL and remains scan/delete addressable", (e, n) => t.gen(function* () {
286
+ let t = {
287
+ algorithm: "fixed-window",
288
+ limit: 1,
289
+ windowMs: 1e3,
290
+ burst: 1
291
+ };
292
+ yield* e.rateLimit("ttl:quota", t, 0), yield* e.expire("ttl:quota", 20), yield* n(40), l((yield* e.rateLimit("ttl:quota", t, 0)).allowed).toBe(!0), l(yield* i.runCollect(e.scanPrefix("ttl:quota"))).toEqual(["ttl:quota"]), l(yield* e.delete("ttl:quota")).toBe(!0);
293
+ })), d("TTL expires every structure; reads and rejected lease attempts do not extend it", (e, n) => t.gen(function* () {
294
+ yield* e.zadd("ttl:z", [{
295
+ member: "a",
296
+ score: 1
297
+ }], { ttlMs: 1e3 }), yield* e.hset("ttl:h", { a: "b" }, { ttlMs: 1e3 }), yield* e.sadd("ttl:s", ["a"], { ttlMs: 1e3 }), yield* e.incrBy("ttl:c", 1, { ttlMs: 1e3 }), l(yield* e.setNx("ttl:l", "owner", 1e3)).toBe(!0), l(yield* e.setNx("ttl:l", "other", 1e4)).toBe(!1), l(yield* e.zcard("ttl:z")).toBe(1), yield* n(1100), l(yield* e.zcard("ttl:z")).toBe(0), l(yield* e.hgetAll("ttl:h")).toEqual({}), l(yield* e.smembers("ttl:s")).toEqual([]), l(yield* e.incrBy("ttl:c", 1)).toBe(1), l(yield* e.setNx("ttl:l", "newOwner", 1e3)).toBe(!0);
298
+ })), d("explicit expiry and deletion are scoped; scans treat glob symbols literally", (e, n) => t.gen(function* () {
299
+ yield* e.sadd("literal:*?[]a", ["a"]), yield* e.sadd("literal:other", ["a"]);
300
+ let t = yield* i.runCollect(e.scanPrefix("literal:*?[]"));
301
+ l(t).toEqual(["literal:*?[]a"]), yield* e.expire("literal:*?[]a", 1e3), yield* n(1100), l(yield* i.runCollect(e.scanPrefix("literal:*?[]"))).toEqual([]), l(yield* e.delete("literal:other")).toBe(!0), l(yield* e.delete("literal:other")).toBe(!1);
302
+ })), d("scans traverse multiple pages without duplicates or other prefixes", (e) => t.gen(function* () {
303
+ let t = Array.from({ length: 250 }, (e, t) => `pages:${t}`);
304
+ yield* e.multi(t.map((e) => ({
305
+ op: "sadd",
306
+ key: e,
307
+ members: ["a"]
308
+ }))), yield* e.sadd("unrelated", ["a"]);
309
+ let n = yield* i.runCollect(e.scanPrefix("pages:"));
310
+ l(n.length).toBe(t.length), l([...n].sort()).toEqual([...t].sort());
311
+ })), d("batch results reflect ordered writes and reads; arguments validate before writes", (e) => t.gen(function* () {
312
+ l(yield* e.multi([])).toEqual([]), l(yield* e.multi([
313
+ {
314
+ op: "incrBy",
315
+ key: "batch:c",
316
+ delta: 2
317
+ },
318
+ {
319
+ op: "incrBy",
320
+ key: "batch:c",
321
+ delta: 3
322
+ },
323
+ {
324
+ op: "sadd",
325
+ key: "batch:s",
326
+ members: ["a"]
327
+ },
328
+ {
329
+ op: "sismember",
330
+ key: "batch:s",
331
+ member: "a"
332
+ }
333
+ ])).toEqual([
334
+ 2,
335
+ 5,
336
+ 1,
337
+ !0
338
+ ]), l(r.isFailure(yield* e.multi([{
339
+ op: "incrBy",
340
+ key: "untouched",
341
+ delta: 10
342
+ }, {
343
+ op: "expire",
344
+ key: "bad",
345
+ ttlMs: -1
346
+ }]).pipe(t.result))).toBe(!0), l(yield* e.incrBy("untouched", 0)).toBe(0);
347
+ })), d("wrong types and non-finite/unsafe arguments are typed failures", (e) => t.gen(function* () {
348
+ yield* e.sadd("typed", ["a"]);
349
+ let n = yield* e.zcard("typed").pipe(t.result);
350
+ l(r.isFailure(n)).toBe(!0), r.isFailure(n) && l(n.failure.reason).toBe("wrongType"), l(r.isFailure(yield* e.incrBy("bad", .5).pipe(t.result))).toBe(!0), l(r.isFailure(yield* e.zadd("bad", [{
351
+ member: "a",
352
+ score: NaN
353
+ }]).pipe(t.result))).toBe(!0), l(r.isFailure(yield* e.setNx("bad", "a", 0).pipe(t.result))).toBe(!0), l(r.isFailure(yield* e.tokenBucket("bad", {
354
+ capacity: 1,
355
+ refillPerSec: 1
356
+ }, 2).pipe(t.result))).toBe(!0), l(r.isFailure(yield* e.sadd("\ud800", ["a"]).pipe(t.result))).toBe(!0), l(r.isFailure(yield* e.tokenBucket("bad", {
357
+ capacity: 1e300,
358
+ refillPerSec: 1
359
+ }).pipe(t.result))).toBe(!0), yield* e.incrBy("safe", 2 ** 53 - 1), l(r.isFailure(yield* e.incrBy("safe", 1).pipe(t.result))).toBe(!0), l(yield* e.incrBy("safe", 0)).toBe(2 ** 53 - 1);
360
+ })), d("empty writes do not create keys and deleting last members removes structures", (e) => t.gen(function* () {
361
+ yield* e.zadd("empty:z", []), yield* e.hset("empty:h", {}), yield* e.sadd("empty:s", []), l(yield* i.runCollect(e.scanPrefix("empty:"))).toEqual([]), yield* e.hset("empty:h", { a: "1" }), yield* e.hdel("empty:h", ["a"]), yield* e.sadd("empty:s", ["a"]), yield* e.srem("empty:s", ["a"]), l(yield* i.runCollect(e.scanPrefix("empty:"))).toEqual([]);
362
+ }));
363
+ });
364
+ };
365
+ export { a as hotStoreConformance };