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.
Files changed (37) hide show
  1. package/README.md +2 -0
  2. package/dist/bloom-filter/batching.d.mts +1 -0
  3. package/dist/bloom-filter/batching.d.mts.map +1 -1
  4. package/dist/bloom-filter/batching.mjs +1 -0
  5. package/dist/bloom-filter/batching.mjs.map +1 -1
  6. package/dist/bloom-filter/rebuilds.d.mts.map +1 -1
  7. package/dist/bloom-filter/rebuilds.mjs +35 -8
  8. package/dist/bloom-filter/rebuilds.mjs.map +1 -1
  9. package/dist/bloom-filter/writes.d.mts.map +1 -1
  10. package/dist/bloom-filter/writes.mjs +38 -12
  11. package/dist/bloom-filter/writes.mjs.map +1 -1
  12. package/dist/cache/mutations.d.mts.map +1 -1
  13. package/dist/cache/mutations.mjs +8 -6
  14. package/dist/cache/mutations.mjs.map +1 -1
  15. package/dist/clients.d.mts.map +1 -1
  16. package/dist/clients.mjs +16 -5
  17. package/dist/clients.mjs.map +1 -1
  18. package/dist/delete.d.mts.map +1 -1
  19. package/dist/delete.mjs +1 -3
  20. package/dist/delete.mjs.map +1 -1
  21. package/dist/dynamic-config/fields.d.mts.map +1 -1
  22. package/dist/dynamic-config/fields.mjs +35 -7
  23. package/dist/dynamic-config/fields.mjs.map +1 -1
  24. package/dist/rate-limiter.d.mts.map +1 -1
  25. package/dist/rate-limiter.mjs +9 -2
  26. package/dist/rate-limiter.mjs.map +1 -1
  27. package/docs/api.md +40 -0
  28. package/docs/bloom-filters.md +219 -0
  29. package/docs/cache.md +191 -0
  30. package/docs/clients.md +98 -0
  31. package/docs/dynamic-config.md +132 -0
  32. package/docs/events-and-metrics.md +99 -0
  33. package/docs/lua-scripts.md +45 -0
  34. package/docs/rate-limiter.md +136 -0
  35. package/docs/testing-and-ci.md +4 -0
  36. package/docs/utilities.md +65 -0
  37. package/package.json +24 -22
@@ -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.
@@ -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.