valkyries 0.1.0 → 0.1.2
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.
- package/README.md +2 -0
- package/dist/cache/mutations.d.mts.map +1 -1
- package/dist/cache/mutations.mjs +8 -6
- package/dist/cache/mutations.mjs.map +1 -1
- package/dist/clients.d.mts.map +1 -1
- package/dist/clients.mjs +16 -5
- package/dist/clients.mjs.map +1 -1
- package/dist/delete.d.mts.map +1 -1
- package/dist/delete.mjs +1 -3
- package/dist/delete.mjs.map +1 -1
- package/dist/dynamic-config/fields.d.mts.map +1 -1
- package/dist/dynamic-config/fields.mjs +35 -7
- package/dist/dynamic-config/fields.mjs.map +1 -1
- package/dist/rate-limiter.d.mts.map +1 -1
- package/dist/rate-limiter.mjs +4 -0
- package/dist/rate-limiter.mjs.map +1 -1
- package/docs/api.md +40 -0
- package/docs/bloom-filters.md +219 -0
- package/docs/cache.md +191 -0
- package/docs/clients.md +98 -0
- package/docs/dynamic-config.md +132 -0
- package/docs/events-and-metrics.md +99 -0
- package/docs/lua-scripts.md +45 -0
- package/docs/rate-limiter.md +136 -0
- package/docs/utilities.md +65 -0
- package/package.json +23 -22
package/docs/rate-limiter.md
CHANGED
|
@@ -15,3 +15,139 @@ Use `addAndCheck(ids, threshold)` for request paths. It atomically adds an event
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
A threshold of `N` blocks the Nth request because the count is checked after incrementing.
|
|
18
|
+
|
|
19
|
+
## Import
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { RateLimiter } from "valkyries";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
or:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { RateLimiter } from "valkyries/rate-limiter";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Constructor
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const limiter = new RateLimiter({
|
|
35
|
+
prefix: "login",
|
|
36
|
+
ttlSeconds: 60,
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
type RateLimiterOptions = {
|
|
42
|
+
prefix: string;
|
|
43
|
+
ttlSeconds: number;
|
|
44
|
+
client?: GlideClient;
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- `prefix`: namespace for this limiter.
|
|
49
|
+
- `ttlSeconds`: sliding-window size in seconds.
|
|
50
|
+
- `client`: optional `@valkey/valkey-glide` client. Defaults to the package rate-limiter client.
|
|
51
|
+
|
|
52
|
+
The limiter reads from the primary Valkey client to avoid replica lag in request-path checks.
|
|
53
|
+
|
|
54
|
+
## `add(ids)`
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
limiter.add(ids: string[]): Promise<void>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Records events for all truthy IDs. Falsy IDs are ignored.
|
|
61
|
+
|
|
62
|
+
This method writes without checking a threshold. Use it when you want to count activity separately from request admission.
|
|
63
|
+
|
|
64
|
+
## `addAndCheck(ids, threshold, ttlSeconds?)`
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
limiter.addAndCheck(
|
|
68
|
+
ids: string[],
|
|
69
|
+
threshold: number,
|
|
70
|
+
ttlSeconds?: number,
|
|
71
|
+
): Promise<{ counts: number[]; limited: boolean }>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Atomically records events and returns the current counts.
|
|
75
|
+
|
|
76
|
+
- `counts` aligns with the filtered truthy IDs, not the original input.
|
|
77
|
+
- `limited` is `true` when any post-add count is greater than or equal to `threshold`.
|
|
78
|
+
- Empty or all-falsy input returns `{ counts: [], limited: false }`.
|
|
79
|
+
- `ttlSeconds` overrides the instance TTL for this check.
|
|
80
|
+
- Unexpected Valkey response types fail open with zero counts.
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const { counts, limited } = await limiter.addAndCheck(["ip:127.0.0.1"], 10);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## `isRateLimited(ids, threshold, ttlSeconds?)`
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
limiter.isRateLimited(
|
|
92
|
+
ids: string[],
|
|
93
|
+
threshold: number,
|
|
94
|
+
ttlSeconds?: number,
|
|
95
|
+
): Promise<boolean>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Reads current counts without adding a new event. Returns `true` when any ID count is greater than or equal to `threshold` within the TTL window.
|
|
99
|
+
|
|
100
|
+
## `get(ids, ttlSeconds?)`
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
limiter.get(ids: string[], ttlSeconds?: number): Promise<number[]>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Returns current counts for truthy IDs. Empty or all-falsy input returns `[]`.
|
|
107
|
+
|
|
108
|
+
The returned array aligns with the filtered truthy IDs.
|
|
109
|
+
|
|
110
|
+
## `delete(...ids)`
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
limiter.delete(...ids: string[]): Promise<number>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Deletes limiter keys for truthy IDs and returns the Valkey unlink count.
|
|
117
|
+
|
|
118
|
+
## `getKey(key)`
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
limiter.getKey(key: string): string
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Returns the Valkey sorted-set key for an ID:
|
|
125
|
+
|
|
126
|
+
```txt
|
|
127
|
+
rate-limiter:{prefix}:{id}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## `invalidate()`
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
limiter.invalidate(): Promise<void>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Deletes every limiter key for this instance prefix.
|
|
137
|
+
|
|
138
|
+
## `RateLimiter.invalidate(prefix, client?)`
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
RateLimiter.invalidate(prefix: string, client?: GlideClient): Promise<void>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Deletes every limiter key for `prefix`. Passing an empty prefix invalidates all limiter namespaces visible to the client.
|
|
145
|
+
|
|
146
|
+
## Events
|
|
147
|
+
|
|
148
|
+
The rate limiter emits:
|
|
149
|
+
|
|
150
|
+
- `rate-limiter:add` with `{ prefix, ids }`
|
|
151
|
+
- `rate-limiter:get` with `{ prefix, ids, counts }`
|
|
152
|
+
- `rate-limiter:delete` with `{ prefix, ids }`
|
|
153
|
+
- `rate-limiter:invalidate` with `{ prefix }`
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Utilities
|
|
2
|
+
|
|
3
|
+
These helpers are exported from the package root for applications that need the same normalization and serialization behavior as the built-in classes.
|
|
4
|
+
|
|
5
|
+
## `normalizeKey(key)`
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
normalizeKey(key: string): string
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Trims and lowercases cache keys.
|
|
12
|
+
|
|
13
|
+
## `normalizeTtlResult(value)`
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
normalizeTtlResult(value: unknown): number | null
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Normalizes Valkey millisecond TTL values to seconds. Preserves `-2` and `-1` sentinel values.
|
|
20
|
+
|
|
21
|
+
## `normalizeCountResult(value)`
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
normalizeCountResult(value: unknown): number
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Normalizes numeric or bigint Valkey counts. Returns `0` for unexpected values.
|
|
28
|
+
|
|
29
|
+
## `ValkeyCacheTypeError`
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
class ValkeyCacheTypeError extends TypeError
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Thrown when a cache value does not match the configured mode.
|
|
36
|
+
|
|
37
|
+
## `serializeValue(value, mode)`
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
serializeValue(
|
|
41
|
+
value: unknown,
|
|
42
|
+
mode: "json" | "text" | "buffer",
|
|
43
|
+
): Promise<string | Buffer>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Serializes values for cache storage and gzip-compresses payloads larger than 2 KiB.
|
|
47
|
+
|
|
48
|
+
## `decodeValue(result, mode)`
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
decodeValue(
|
|
52
|
+
result: GlideString | null,
|
|
53
|
+
mode: "json" | "text" | "buffer",
|
|
54
|
+
): Promise<string | Buffer | Record<string, unknown> | null>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Decodes and decompresses cache values. Returns `null` for Valkey misses.
|
|
58
|
+
|
|
59
|
+
## `durationInMilliseconds(start)`
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
durationInMilliseconds(start: bigint): number
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Converts a `process.hrtime.bigint()` start timestamp to elapsed milliseconds.
|
package/package.json
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "valkyries",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Valkey utilities for caches, bloom filters, dynamic configuration, and rate limiting.",
|
|
5
|
+
"homepage": "https://github.com/jonathanong/valkyries#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/jonathanong/valkyries/issues"
|
|
8
|
+
},
|
|
5
9
|
"license": "MIT",
|
|
6
10
|
"author": "",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/jonathanong/valkyries"
|
|
14
|
+
},
|
|
7
15
|
"files": [
|
|
8
16
|
"dist",
|
|
9
17
|
"README.md",
|
|
@@ -40,26 +48,13 @@
|
|
|
40
48
|
"default": "./dist/rate-limiter.mjs"
|
|
41
49
|
}
|
|
42
50
|
},
|
|
43
|
-
"scripts": {
|
|
44
|
-
"build": "tsc --project tsconfig.build.json && pnpm run copy:scripts",
|
|
45
|
-
"copy:scripts": "tsx build-scripts/copy-lua-scripts.mts",
|
|
46
|
-
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
47
|
-
"test": "vitest run",
|
|
48
|
-
"test:coverage": "vitest run --coverage",
|
|
49
|
-
"lint": "pnpm run oxlint && pnpm run oxfmt:check",
|
|
50
|
-
"oxlint": "oxlint --type-aware --deny-warnings",
|
|
51
|
-
"oxlint:fix": "oxlint --type-aware --fix --deny-warnings",
|
|
52
|
-
"oxfmt": "oxfmt",
|
|
53
|
-
"oxfmt:check": "oxfmt --check",
|
|
54
|
-
"actionlint": "actionlint"
|
|
55
|
-
},
|
|
56
51
|
"dependencies": {
|
|
57
52
|
"@valkey/valkey-glide": "^2.4.0"
|
|
58
53
|
},
|
|
59
54
|
"devDependencies": {
|
|
60
55
|
"@types/node": "^25.8.0",
|
|
61
56
|
"@vitest/coverage-v8": "^4.1.6",
|
|
62
|
-
"actionlint": "^
|
|
57
|
+
"github-actionlint": "^1.7.12",
|
|
63
58
|
"oxfmt": "^0.50.0",
|
|
64
59
|
"oxlint": "^1.65.0",
|
|
65
60
|
"oxlint-tsgolint": "^0.22.1",
|
|
@@ -70,11 +65,17 @@
|
|
|
70
65
|
"engines": {
|
|
71
66
|
"node": ">=24.0.0"
|
|
72
67
|
},
|
|
73
|
-
"
|
|
74
|
-
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsc --project tsconfig.build.json && pnpm run copy:scripts",
|
|
70
|
+
"copy:scripts": "tsx build-scripts/copy-lua-scripts.mts",
|
|
71
|
+
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
72
|
+
"test": "vitest run",
|
|
73
|
+
"test:coverage": "vitest run --coverage",
|
|
74
|
+
"lint": "pnpm run oxlint && pnpm run oxfmt:check",
|
|
75
|
+
"oxlint": "oxlint --type-aware --deny-warnings",
|
|
76
|
+
"oxlint:fix": "oxlint --type-aware --fix --deny-warnings",
|
|
77
|
+
"oxfmt": "oxfmt",
|
|
78
|
+
"oxfmt:check": "oxfmt --check",
|
|
79
|
+
"actionlint": "github-actionlint"
|
|
79
80
|
}
|
|
80
|
-
}
|
|
81
|
+
}
|