@valentinkolb/sync 0.1.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.
@@ -0,0 +1,119 @@
1
+ import { test, expect, beforeEach } from "bun:test";
2
+ import { redis } from "bun";
3
+ import { ratelimit, RateLimitError } from "../index";
4
+
5
+ // Clean up Redis before each test
6
+ beforeEach(async () => {
7
+ const keys = await redis.send("KEYS", ["ratelimit:test:*"]);
8
+ if (Array.isArray(keys) && keys.length > 0) {
9
+ await redis.send("DEL", keys as string[]);
10
+ }
11
+ });
12
+
13
+ test("allows requests within limit", async () => {
14
+ const limiter = ratelimit.create({
15
+ limit: 5,
16
+ windowSecs: 60,
17
+ prefix: "ratelimit:test",
18
+ });
19
+
20
+ for (let i = 0; i < 5; i++) {
21
+ const result = await limiter.check("user:1");
22
+ expect(result.limited).toBe(false);
23
+ expect(result.remaining).toBe(5 - i - 1);
24
+ }
25
+ });
26
+
27
+ test("blocks requests over limit", async () => {
28
+ const limiter = ratelimit.create({
29
+ limit: 3,
30
+ windowSecs: 60,
31
+ prefix: "ratelimit:test",
32
+ });
33
+
34
+ // Use up the limit
35
+ for (let i = 0; i < 3; i++) {
36
+ await limiter.check("user:2");
37
+ }
38
+
39
+ // Next request should be limited
40
+ const result = await limiter.check("user:2");
41
+ expect(result.limited).toBe(true);
42
+ expect(result.remaining).toBe(0);
43
+ });
44
+
45
+ test("checkOrThrow throws RateLimitError when limited", async () => {
46
+ const limiter = ratelimit.create({
47
+ limit: 1,
48
+ windowSecs: 60,
49
+ prefix: "ratelimit:test",
50
+ });
51
+
52
+ // Use up the limit
53
+ await limiter.check("user:3");
54
+
55
+ // Should throw
56
+ try {
57
+ await limiter.checkOrThrow("user:3");
58
+ expect(true).toBe(false); // Should not reach here
59
+ } catch (e) {
60
+ expect(e).toBeInstanceOf(RateLimitError);
61
+ expect((e as RateLimitError).remaining).toBe(0);
62
+ expect((e as RateLimitError).resetIn).toBeGreaterThan(0);
63
+ }
64
+ });
65
+
66
+ test("different identifiers have separate limits", async () => {
67
+ const limiter = ratelimit.create({
68
+ limit: 2,
69
+ windowSecs: 60,
70
+ prefix: "ratelimit:test",
71
+ });
72
+
73
+ // User A uses their limit
74
+ await limiter.check("user:a");
75
+ await limiter.check("user:a");
76
+ const resultA = await limiter.check("user:a");
77
+ expect(resultA.limited).toBe(true);
78
+
79
+ // User B should still have their limit
80
+ const resultB = await limiter.check("user:b");
81
+ expect(resultB.limited).toBe(false);
82
+ expect(resultB.remaining).toBe(1);
83
+ });
84
+
85
+ test("resetIn returns time until window reset", async () => {
86
+ const limiter = ratelimit.create({
87
+ limit: 10,
88
+ windowSecs: 60,
89
+ prefix: "ratelimit:test",
90
+ });
91
+
92
+ const result = await limiter.check("user:4");
93
+ expect(result.resetIn).toBeGreaterThan(0);
94
+ expect(result.resetIn).toBeLessThanOrEqual(60000);
95
+ });
96
+
97
+ test("sliding window counts previous window", async () => {
98
+ const limiter = ratelimit.create({
99
+ limit: 10,
100
+ windowSecs: 1, // 1 second window for faster testing
101
+ prefix: "ratelimit:test",
102
+ });
103
+
104
+ // Make 8 requests
105
+ for (let i = 0; i < 8; i++) {
106
+ await limiter.check("user:5");
107
+ }
108
+
109
+ // Wait for half the window
110
+ await Bun.sleep(500);
111
+
112
+ // Previous window count is weighted, so we should still have limited capacity
113
+ // 8 requests * 0.5 weight = 4 weighted from previous
114
+ // So we should have ~6 remaining
115
+ const result = await limiter.check("user:5");
116
+ expect(result.limited).toBe(false);
117
+ expect(result.remaining).toBeLessThan(10);
118
+ expect(result.remaining).toBeGreaterThan(0);
119
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false,
28
+ },
29
+ "include": ["index.ts", "src/**/*.ts"],
30
+ "exclude": ["node_modules", "old_impl"],
31
+ }