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