valkyries 0.1.0 → 0.1.3
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/bloom-filter/batching.d.mts +1 -0
- package/dist/bloom-filter/batching.d.mts.map +1 -1
- package/dist/bloom-filter/batching.mjs +1 -0
- package/dist/bloom-filter/batching.mjs.map +1 -1
- package/dist/bloom-filter/rebuilds.d.mts.map +1 -1
- package/dist/bloom-filter/rebuilds.mjs +35 -8
- package/dist/bloom-filter/rebuilds.mjs.map +1 -1
- package/dist/bloom-filter/writes.d.mts.map +1 -1
- package/dist/bloom-filter/writes.mjs +38 -12
- package/dist/bloom-filter/writes.mjs.map +1 -1
- 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 +9 -2
- 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/testing-and-ci.md +4 -0
- package/docs/utilities.md +65 -0
- package/package.json +24 -22
package/docs/bloom-filters.md
CHANGED
|
@@ -10,3 +10,222 @@ Important behavior:
|
|
|
10
10
|
- `rebuildFromStream()` builds under `bloom-filter:{name}:building` and renames it over the live key.
|
|
11
11
|
|
|
12
12
|
Large Lua-backed calls are clamped to `LUA_UNPACK_BATCH_SIZE` to avoid Valkey Lua argument limits.
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { ValkeyBloomFilter } from "valkyries";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
or:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { ValkeyBloomFilter } from "valkyries/bloom-filter";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Constructor
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const filter = new ValkeyBloomFilter({
|
|
30
|
+
name: "users",
|
|
31
|
+
capacity: 1_000_000,
|
|
32
|
+
errorRate: 0.01,
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
type ValkeyBloomFilterOptions = {
|
|
38
|
+
name: string;
|
|
39
|
+
capacity: number;
|
|
40
|
+
errorRate: number;
|
|
41
|
+
batchSize?: number;
|
|
42
|
+
expansionRate?: number;
|
|
43
|
+
client?: GlideClient;
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- `name`: filter namespace. The live key is `bloom-filter:${name}`.
|
|
48
|
+
- `capacity`: expected item count.
|
|
49
|
+
- `errorRate`: target false-positive rate between `0` and `1`.
|
|
50
|
+
- `batchSize`: write/read chunk size. Defaults to `10_000`; Lua paths clamp large chunks internally.
|
|
51
|
+
- `expansionRate`: Bloom filter expansion rate. Defaults to `2`.
|
|
52
|
+
- `client`: optional `@valkey/valkey-glide` client. Defaults to the package cache client.
|
|
53
|
+
|
|
54
|
+
The constructor throws when `name` is empty, `capacity` is not positive, `errorRate` is not between `0` and `1`, `expansionRate` is not positive, or `batchSize` is not positive.
|
|
55
|
+
|
|
56
|
+
## `ensureExists(capacity?)`
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
filter.ensureExists(capacity?: number): Promise<void>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Creates the live Bloom filter when it does not already exist. `capacity` overrides the configured capacity for this call.
|
|
63
|
+
|
|
64
|
+
## `exists(item)`
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
filter.exists(item: string): Promise<boolean | null>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Checks one item.
|
|
71
|
+
|
|
72
|
+
- `true`: the item may exist.
|
|
73
|
+
- `false`: the item definitely does not exist.
|
|
74
|
+
- `null`: the filter is missing; callers should fall back to the authoritative store.
|
|
75
|
+
|
|
76
|
+
## `mexists(items)`
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
filter.mexists(items: string[]): Promise<Array<boolean | null>>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Checks multiple items. Output order and length match input.
|
|
83
|
+
|
|
84
|
+
## `existsIfReady(readyKey, item)`
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
filter.existsIfReady(readyKey: string, item: string): Promise<boolean | null>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Checks one item only when both the ready marker and live filter exist. Returns `null` when either key is absent.
|
|
91
|
+
|
|
92
|
+
## `mexistsIfReady(readyKey, items)`
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
filter.mexistsIfReady(
|
|
96
|
+
readyKey: string,
|
|
97
|
+
items: string[],
|
|
98
|
+
): Promise<Array<boolean | null>>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Batch version of `existsIfReady`.
|
|
102
|
+
|
|
103
|
+
## `add(items)`
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
filter.add(items: string[]): Promise<void>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Adds items in chunks. Errors are routed to the configured Valkey error handler.
|
|
110
|
+
|
|
111
|
+
If neither the live key nor the building key exists, this is a no-op to avoid auto-creating an undersized filter.
|
|
112
|
+
|
|
113
|
+
## `addOrThrow(items)`
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
filter.addOrThrow(items: string[]): Promise<void>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Same write path as `add()`, but propagates write errors.
|
|
120
|
+
|
|
121
|
+
## `addStream(batches)`
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
filter.addStream(batches: AsyncIterable<string[]>): Promise<void>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Adds streamed batches and propagates errors.
|
|
128
|
+
|
|
129
|
+
## `rebuild(items)`
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
filter.rebuild(items: string[]): Promise<void>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Rebuilds from a complete list using a building key and atomic rename.
|
|
136
|
+
|
|
137
|
+
## `rebuildFromStream(batches, capacityOverride?)`
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
filter.rebuildFromStream(
|
|
141
|
+
batches: AsyncIterable<string[]>,
|
|
142
|
+
capacityOverride?: number,
|
|
143
|
+
): Promise<void>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Rebuilds from streamed batches. `capacityOverride` is useful when the live row count is known at rebuild time.
|
|
147
|
+
|
|
148
|
+
## `delete()`
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
filter.delete(): Promise<void>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Deletes the live and building keys.
|
|
155
|
+
|
|
156
|
+
## `deleteWithAdditionalKeys(additionalKeys)`
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
filter.deleteWithAdditionalKeys(additionalKeys: string[]): Promise<void>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Deletes the live key, building key, and additional caller-owned keys such as readiness markers.
|
|
163
|
+
|
|
164
|
+
## `keyExists()`
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
filter.keyExists(): Promise<boolean>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Returns whether the live filter key exists.
|
|
171
|
+
|
|
172
|
+
## `isReady(readyKey)`
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
filter.isReady(readyKey: string): Promise<boolean>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Returns `true` only when both the live filter key and `readyKey` exist.
|
|
179
|
+
|
|
180
|
+
## `getKey()`
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
filter.getKey(): string
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Returns the live filter key.
|
|
187
|
+
|
|
188
|
+
## `getBuildingKey()`
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
filter.getBuildingKey(): string
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Returns the staging key used by rebuilds.
|
|
195
|
+
|
|
196
|
+
## `getConfig()`
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
filter.getConfig(): {
|
|
200
|
+
name: string;
|
|
201
|
+
capacity: number;
|
|
202
|
+
errorRate: number;
|
|
203
|
+
batchSize: number;
|
|
204
|
+
liveKey: string;
|
|
205
|
+
buildingKey: string;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Returns runtime configuration values.
|
|
210
|
+
|
|
211
|
+
## Helper Exports
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import {
|
|
215
|
+
LUA_UNPACK_BATCH_SIZE,
|
|
216
|
+
isBloomMissingKeyError,
|
|
217
|
+
normalizeBloomCheckResult,
|
|
218
|
+
} from "valkyries";
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
- `LUA_UNPACK_BATCH_SIZE`: maximum Lua unpack batch size used internally.
|
|
222
|
+
- `isBloomMissingKeyError(error)`: detects missing Bloom filter errors.
|
|
223
|
+
- `normalizeBloomCheckResult(result)`: normalizes Valkey Bloom return values to booleans.
|
|
224
|
+
|
|
225
|
+
## Events
|
|
226
|
+
|
|
227
|
+
The Bloom filter emits:
|
|
228
|
+
|
|
229
|
+
- `bloom-filter:exists` with `{ name, item, result }`.
|
|
230
|
+
- `bloom-filter:mexists` with `{ name, items, results }`.
|
|
231
|
+
- `bloom-filter:add` with `{ name, items }`.
|
package/docs/cache.md
CHANGED
|
@@ -17,3 +17,194 @@ Values larger than 2 KiB are gzip-compressed. Null results are cached with `null
|
|
|
17
17
|
`cacheGetByAny()` and `cacheGetByAnyBatch()` protect against stale miss writes by setting short-lived invalidation markers during delete operations.
|
|
18
18
|
|
|
19
19
|
Optional Bloom filter integration can skip authoritative-store calls for definite misses. If the Bloom filter is unavailable or not ready, cache reads fall back to the normal miss path.
|
|
20
|
+
|
|
21
|
+
## Import
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { ValkeyCache } from "valkyries";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
or:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { ValkeyCache } from "valkyries/cache";
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Constructor
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
const cache = new ValkeyCache({ prefix: "users", ttlSeconds: 300 });
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
type ValkeyCacheOptions<K = string> = {
|
|
41
|
+
prefix: string;
|
|
42
|
+
ttlSeconds: number;
|
|
43
|
+
nullTtlSeconds?: number;
|
|
44
|
+
mode?: "json" | "text" | "buffer";
|
|
45
|
+
staleTtlAge?: number;
|
|
46
|
+
bloomFilter?: ValkeyBloomFilter;
|
|
47
|
+
bloomFilterEnabled?: () => boolean;
|
|
48
|
+
client?: GlideClient;
|
|
49
|
+
keySerializer?: (key: K) => string;
|
|
50
|
+
};
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For non-string keys, `keySerializer` is required.
|
|
54
|
+
|
|
55
|
+
- `prefix`: namespace for this cache.
|
|
56
|
+
- `ttlSeconds`: TTL for non-null values.
|
|
57
|
+
- `nullTtlSeconds`: TTL for cached nulls. Defaults to `Math.max(1, Math.floor(ttlSeconds / 60))`.
|
|
58
|
+
- `mode`: serialization mode. Defaults to `json`.
|
|
59
|
+
- `staleTtlAge`: refresh-ahead threshold from `0` to `1`. Defaults to `0.9`.
|
|
60
|
+
- `bloomFilter`: optional negative-lookup Bloom filter.
|
|
61
|
+
- `bloomFilterEnabled`: optional per-call gate for Bloom filter checks.
|
|
62
|
+
- `client`: optional `@valkey/valkey-glide` client. Defaults to the package cache client.
|
|
63
|
+
- `keySerializer`: converts keys to strings before trimming and lowercasing.
|
|
64
|
+
|
|
65
|
+
The constructor throws when `prefix` is empty, `ttlSeconds` is not positive, or `staleTtlAge` is outside `0..1`.
|
|
66
|
+
|
|
67
|
+
## `cacheGetByAny(fn)`
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
cache.cacheGetByAny<T>(
|
|
71
|
+
fn: (key: K) => Promise<T | null | undefined>,
|
|
72
|
+
): (key: K) => Promise<T | null>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Returns a read-through single-key cache wrapper.
|
|
76
|
+
|
|
77
|
+
Behavior:
|
|
78
|
+
|
|
79
|
+
- Invalid, null, undefined, or empty serialized keys return `null`.
|
|
80
|
+
- Cache hits return the decoded value.
|
|
81
|
+
- Cached nulls return `null` without calling `fn`.
|
|
82
|
+
- Misses call `fn(key)`.
|
|
83
|
+
- `undefined` from `fn` is normalized to `null`.
|
|
84
|
+
- Miss results are written asynchronously when no invalidation marker is present.
|
|
85
|
+
- Stale hits can trigger asynchronous refresh-ahead.
|
|
86
|
+
|
|
87
|
+
## `cacheGetByAnyBatch(batchFn)`
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
cache.cacheGetByAnyBatch<T>(
|
|
91
|
+
batchFn: (keys: K[]) => Promise<Array<T | null | undefined>>,
|
|
92
|
+
): (keys: K[]) => Promise<Array<T | null>>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Returns a read-through batch cache wrapper.
|
|
96
|
+
|
|
97
|
+
Behavior:
|
|
98
|
+
|
|
99
|
+
- Input order and length are preserved.
|
|
100
|
+
- Invalid keys return `null` in their original output positions.
|
|
101
|
+
- Duplicate serialized keys are fetched once and scattered back to every duplicate position.
|
|
102
|
+
- `batchFn` is called only for real misses.
|
|
103
|
+
- `batchFn` must return an array with exactly the same length as its input.
|
|
104
|
+
- `undefined` values are normalized to `null`.
|
|
105
|
+
- Fetched values are written asynchronously when no invalidation marker is present.
|
|
106
|
+
- Stale hits can trigger asynchronous batch refresh-ahead.
|
|
107
|
+
|
|
108
|
+
## `get(key)`
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
cache.get(key: K): Promise<string | Buffer | Record<string, unknown> | null>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Reads a cache entry directly without calling a fallback function. Returns `null` for misses, invalid keys, cached nulls, and Bloom-filter negative lookups.
|
|
115
|
+
|
|
116
|
+
## `getBatch(keys)`
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
cache.getBatch(keys: K[]): Promise<Array<string | Buffer | Record<string, unknown> | null>>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Reads multiple cache entries directly. Output order and length match the input.
|
|
123
|
+
|
|
124
|
+
## `set(key, value, ttl?)`
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
cache.set(key: K, value: unknown, ttl?: number): Promise<void>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Writes a value directly.
|
|
131
|
+
|
|
132
|
+
- Positive `ttl` overrides the cache TTL for this entry.
|
|
133
|
+
- `null` and `undefined` values use `nullTtlSeconds`.
|
|
134
|
+
- Invalid keys are ignored.
|
|
135
|
+
- In `text` mode, `value` must be a string.
|
|
136
|
+
- In `buffer` mode, `value` must be a `Buffer`.
|
|
137
|
+
|
|
138
|
+
## `setBatch(entries)`
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
cache.setBatch(
|
|
142
|
+
entries: Array<{ key: K; value: unknown; ttl?: number }>,
|
|
143
|
+
): Promise<void>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Writes multiple values directly using a Valkey batch.
|
|
147
|
+
|
|
148
|
+
## `refreshById(aliases, fetchByKey)`
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
cache.refreshById<T>(
|
|
152
|
+
aliases: K[],
|
|
153
|
+
fetchByKey: (key: K) => Promise<T | null | undefined>,
|
|
154
|
+
): Promise<T | null>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Fetches fresh data once using the first valid alias, then writes the result to every valid alias. Use this when one entity can be addressed by several cache keys.
|
|
158
|
+
|
|
159
|
+
## `delete(...keys)`
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
cache.delete(...keys: K[]): Promise<number>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Deletes cache entries and writes short-lived invalidation markers so in-flight read-through writes do not repopulate stale data.
|
|
166
|
+
|
|
167
|
+
## `invalidateCacheGetByAny(...keys)`
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
cache.invalidateCacheGetByAny(...keys: K[]): Promise<number>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Alias for `delete(...keys)`.
|
|
174
|
+
|
|
175
|
+
## `invalidate()`
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
cache.invalidate(): Promise<void>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Deletes every cache key for this cache prefix.
|
|
182
|
+
|
|
183
|
+
## `ValkeyCache.invalidate(prefix, client?)`
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
ValkeyCache.invalidate(prefix: string, client?: GlideClient): Promise<void>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Deletes every cache key for `prefix`. Passing an empty prefix invalidates all cache namespaces visible to the client.
|
|
190
|
+
|
|
191
|
+
## `getKey(key)`
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
cache.getKey(key: K): string
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Returns the Valkey cache key for a logical key. Throws when the logical key serializes to an invalid cache key.
|
|
198
|
+
|
|
199
|
+
## Events
|
|
200
|
+
|
|
201
|
+
The cache emits:
|
|
202
|
+
|
|
203
|
+
- `cache:call` with cache name, batch flag, hit/miss/bloom-miss counts, and duration.
|
|
204
|
+
- `cache:hit` with `{ cacheName, keys, count }`.
|
|
205
|
+
- `cache:miss` with `{ cacheName, keys, count }`.
|
|
206
|
+
- `cache:bloom-miss` with `{ cacheName, keys, count }`.
|
|
207
|
+
- `cache:set` with `{ cacheName, keys }`.
|
|
208
|
+
- `cache:set-skipped` with `{ cacheName, keys }`.
|
|
209
|
+
- `cache:delete` with `{ cacheName, keys }`.
|
|
210
|
+
- `cache:invalidate` with `{ cacheName }`.
|
package/docs/clients.md
CHANGED
|
@@ -11,3 +11,101 @@ Connections are deduplicated by URL, read preference, and lazy-connect setting t
|
|
|
11
11
|
For libraries, tests, and multi-tenant services, pass a `GlideClient` directly to `ValkeyCache`, `ValkeyBloomFilter`, `DynamicConfig`, or `RateLimiter`.
|
|
12
12
|
|
|
13
13
|
Use `closeValkeyClients()` or `onGracefulShutdown()` to close package-managed clients and the dynamic-config pub/sub connection.
|
|
14
|
+
|
|
15
|
+
## Default Clients
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { cacheValkeyClient, rateLimiterValkeyClient, dynamicConfigValkeyClient } from "valkyries";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Default clients are created at module load:
|
|
22
|
+
|
|
23
|
+
- `cacheValkeyClient`: uses `VALKEY_CACHE_URL` or `VALKEY_URL`, reads from replicas when possible.
|
|
24
|
+
- `rateLimiterValkeyClient`: uses `VALKEY_RATE_LIMITER_URL`, `VALKEY_CACHE_URL`, or `VALKEY_URL`, and reads from primary.
|
|
25
|
+
- `dynamicConfigValkeyClient`: uses `VALKEY_DYNAMIC_CONFIG_URL` or `VALKEY_URL`, reads from replicas when possible.
|
|
26
|
+
|
|
27
|
+
## `ValkeyClientOptions`
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
type ValkeyClientOptions = {
|
|
31
|
+
readFrom?: "primary" | "preferReplica";
|
|
32
|
+
lazyConnect?: boolean;
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- `readFrom`: Glide read preference.
|
|
37
|
+
- `lazyConnect`: whether Glide should connect lazily. Defaults to `true`.
|
|
38
|
+
|
|
39
|
+
## `urlsToClients`
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
const urlsToClients: Map<string, GlideClient>;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Registry of package-managed clients keyed by URL, read preference, and lazy-connect setting.
|
|
46
|
+
|
|
47
|
+
## `upsertValkeyClientByUrl(url, options?)`
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
upsertValkeyClientByUrl(
|
|
51
|
+
url: string,
|
|
52
|
+
options?: ValkeyClientOptions,
|
|
53
|
+
): Promise<GlideClient>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Returns an existing matching client or creates and stores a new one.
|
|
57
|
+
|
|
58
|
+
## `glideConfigFromUrl(url, options?)`
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
glideConfigFromUrl(url: string, options?: ValkeyClientOptions)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Builds a Glide client configuration from `redis://` or `rediss://` URLs.
|
|
65
|
+
|
|
66
|
+
Supported URL fields:
|
|
67
|
+
|
|
68
|
+
- host
|
|
69
|
+
- port
|
|
70
|
+
- username
|
|
71
|
+
- password
|
|
72
|
+
- TLS through `rediss://`
|
|
73
|
+
|
|
74
|
+
Throws `Invalid Valkey URL` for malformed URLs.
|
|
75
|
+
|
|
76
|
+
## Dynamic Config Pub/Sub Client
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
ensureDynamicConfigValkeySubscriptionClient(): Promise<GlideClient>
|
|
80
|
+
closeDynamicConfigValkeySubscriptionClient(): Promise<void>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`ensureDynamicConfigValkeySubscriptionClient()` creates or returns the shared pub/sub client subscribed to `dynamic-config:*`.
|
|
84
|
+
|
|
85
|
+
`closeDynamicConfigValkeySubscriptionClient()` unsubscribes and closes the shared pub/sub client.
|
|
86
|
+
|
|
87
|
+
## Pub/Sub Handlers
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
addPubSubMessageHandler(handler: (msg: PubSubMsg) => void): void
|
|
91
|
+
removePubSubMessageHandler(handler: (msg: PubSubMsg) => void): void
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Registers or removes handlers that receive dynamic-config pub/sub messages.
|
|
95
|
+
|
|
96
|
+
## Shutdown
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
closeValkeyClients(): Promise<void>
|
|
100
|
+
onGracefulShutdown(): Promise<void>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Both exports point to the same idempotent shutdown function. It closes all live dynamic configs, package-managed clients, and the dynamic-config subscription client.
|
|
104
|
+
|
|
105
|
+
## `deleteKeysWithPrefix(client, pattern)`
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
deleteKeysWithPrefix(client: GlideClient, pattern: string): Promise<void>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Scans keys matching `pattern` and unlinks them in batches. This helper is used by cache and rate-limiter invalidation APIs.
|
package/docs/dynamic-config.md
CHANGED
|
@@ -11,3 +11,135 @@ Fields are declared with `fieldTypes` and initialized from `defaultFields`. Miss
|
|
|
11
11
|
Use `setField()` or `setFields()` to update values. Updates are written with a Lua script that performs `HSET` and `PUBLISH` in one roundtrip.
|
|
12
12
|
|
|
13
13
|
Each instance subscribes to `dynamic-config:*` and applies matching field updates from pub/sub. Call `close()` in tests and shutdown paths to remove handlers and clear refresh timers.
|
|
14
|
+
|
|
15
|
+
## Import
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { DynamicConfig } from "valkyries";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
or:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { DynamicConfig } from "valkyries/dynamic-config";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Constructor
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const flags = new DynamicConfig({
|
|
31
|
+
key: "feature-flags",
|
|
32
|
+
fieldTypes: { enabled: "boolean", sampleRate: "number" },
|
|
33
|
+
defaultFields: { enabled: false, sampleRate: 0 },
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
type DynamicConfigOptions = {
|
|
39
|
+
staleTtlSeconds?: number | null;
|
|
40
|
+
key: string;
|
|
41
|
+
fieldTypes: Record<string, "string" | "number" | "boolean">;
|
|
42
|
+
defaultFields: Record<string, string | number | boolean>;
|
|
43
|
+
client?: GlideClient;
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- `staleTtlSeconds`: refresh interval for local fields. Defaults to `60`.
|
|
48
|
+
- `key`: logical config name. The hash key is `dynamic-config:${key}`.
|
|
49
|
+
- `fieldTypes`: allowed fields and primitive types.
|
|
50
|
+
- `defaultFields`: default values written when fields are missing.
|
|
51
|
+
- `client`: optional `@valkey/valkey-glide` client. Defaults to the package dynamic-config client.
|
|
52
|
+
|
|
53
|
+
The constructor starts initialization immediately. Call `waitForInitialization()` before reading fields during startup.
|
|
54
|
+
|
|
55
|
+
Outside `NODE_ENV=test`, constructing multiple `DynamicConfig` instances with the same key throws.
|
|
56
|
+
|
|
57
|
+
## Public Properties
|
|
58
|
+
|
|
59
|
+
- `staleTtl`: refresh interval in seconds.
|
|
60
|
+
- `key`: Valkey hash key.
|
|
61
|
+
- `fields`: local field map.
|
|
62
|
+
- `fieldTypes`: field schema.
|
|
63
|
+
- `defaultFields`: default values.
|
|
64
|
+
- `initialization`: initialization promise.
|
|
65
|
+
|
|
66
|
+
## `waitForInitialization()`
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
config.waitForInitialization(): Promise<void>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Waits for field validation, default writes, initial field loading, pub/sub subscription, and timer creation.
|
|
73
|
+
|
|
74
|
+
## `getFields()`
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
config.getFields(): Record<string, string | number | boolean>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Returns a plain object snapshot of local fields.
|
|
81
|
+
|
|
82
|
+
## `setField(name, value)`
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
config.setField(name: string, value: string | number | boolean): Promise<void>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Writes one field and publishes the update atomically. Throws for unknown fields or invalid values for the field type.
|
|
89
|
+
|
|
90
|
+
## `setFields(fields)`
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
config.setFields(fields: Record<string, string | number | boolean>): Promise<void>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Writes multiple fields and publishes updates atomically. Local state is updated only after the Valkey write succeeds.
|
|
97
|
+
|
|
98
|
+
## `refresh()`
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
config.refresh(): Promise<void>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Refreshes local fields from Valkey when the stale TTL has elapsed. Concurrent refreshes are coalesced by an optimistic timestamp update.
|
|
105
|
+
|
|
106
|
+
## `subscribe()`
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
config.subscribe(): Promise<void>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Ensures the shared dynamic-config subscription client exists and registers this config's message handler.
|
|
113
|
+
|
|
114
|
+
## `unsubscribe()`
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
config.unsubscribe(): void
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Removes this config's message handler from the shared pub/sub dispatcher.
|
|
121
|
+
|
|
122
|
+
## `close()`
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
config.close(): Promise<void>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Marks the config closed, unsubscribes, clears the refresh timer, and removes it from the global dynamic config registry.
|
|
129
|
+
|
|
130
|
+
## `stringifyField` and `parseField`
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
config.stringifyField(type, value): string;
|
|
134
|
+
config.parseField(type, value): string | number | boolean;
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Helpers used internally for Valkey storage and pub/sub messages.
|
|
138
|
+
|
|
139
|
+
## `dynamicConfigs`
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { dynamicConfigs } from "valkyries";
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Array of live `DynamicConfig` instances. It is used by `closeValkeyClients()` to close configs during shutdown.
|