valkyries 0.9.0 → 0.10.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.
package/README.md CHANGED
@@ -126,7 +126,7 @@ const client = await GlideClient.createClient(glideConfigFromUrl("redis://localh
126
126
  const cache = new ValkeyCache({ prefix: "custom", ttlSeconds: 60, client });
127
127
  ```
128
128
 
129
- Call `closeValkeyClients()` to close package-managed clients.
129
+ Call `closeValkeyClients()` to close package-managed clients. Pass `{ throwOnError: true }` to reject with an `AggregateError` if any close fails.
130
130
 
131
131
  ## Scan and Unlink Keys
132
132
 
@@ -146,6 +146,16 @@ The result reports `scannedKeys`, `matchedKeys`, and keys confirmed removed by `
146
146
  `unlinkedKeys`. `SCAN` is non-snapshot, so these counts can include duplicates or omit keys while
147
147
  the keyspace changes.
148
148
 
149
+ Use `scanAndUnlinkKeyPrefixes()` to flush several literal prefixes serially. Prefixes are
150
+ glob-escaped, later prefixes still run after a failure, and several failures are combined into an
151
+ `AggregateError` that reports `unlinkedKeys`:
152
+
153
+ ```ts
154
+ const { unlinkedKeys } = await scanAndUnlinkKeyPrefixes(client, ["cache:users:", "cache:teams:"], {
155
+ signal: abortController.signal,
156
+ });
157
+ ```
158
+
149
159
  ## Expire Keys Without a TTL
150
160
 
151
161
  Use `expireKeysWithNoExpiry()` to apply a TTL only when matching keys still have no expiry:
@@ -181,6 +191,7 @@ include duplicates or omit keys while the keyspace changes. `scanCount` is a SCA
181
191
  - [Conditional operations](docs/conditional-operations.md)
182
192
  - [Events and metrics](docs/events-and-metrics.md)
183
193
  - [Lua scripts](docs/lua-scripts.md)
194
+ - [Diagnostics](docs/diagnostics.md)
184
195
  - [Utilities](docs/utilities.md)
185
196
  - [Testing and CI](docs/testing-and-ci.md)
186
197
  - [Migration guide](docs/migration.md)
package/dist/delete.d.mts CHANGED
@@ -8,4 +8,16 @@ export declare function deleteKeysWithLiteralPrefixes(client: ScanAndUnlinkKeysC
8
8
  * omit keys, or differ from the number of keys that exist when this function returns.
9
9
  */
10
10
  export declare function scanAndUnlinkKeys(client: ScanAndUnlinkKeysClient, pattern: string, { signal, matches }?: ScanAndUnlinkKeysOptions): Promise<ScanAndUnlinkKeysResult>;
11
+ export type ScanAndUnlinkKeyPrefixesResult = {
12
+ unlinkedKeys: number;
13
+ };
14
+ /**
15
+ * Unlinks every key that starts with any of the literal prefixes, scanning one prefix at a time.
16
+ *
17
+ * Each prefix is glob-escaped, so `*`, `?`, `[`, `]` and `\` match literally. A failing prefix does
18
+ * not stop later prefixes. One failure is rethrown as-is; several are combined into an
19
+ * `AggregateError` with an `unlinkedKeys` property. Failures reach the Valkey error handler once,
20
+ * through `scanAndUnlinkKeys`. An aborted `signal` rethrows its exact reason immediately.
21
+ */
22
+ export declare function scanAndUnlinkKeyPrefixes(client: ScanAndUnlinkKeysClient, prefixes: readonly string[], options?: ScanAndUnlinkKeysOptions): Promise<ScanAndUnlinkKeyPrefixesResult>;
11
23
  //# sourceMappingURL=delete.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"delete.d.mts","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,uBAAuB,EACvB,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,aAAa,CAAC;AAIrB,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,uBAAuB,EAC/B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,uBAAuB,EAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,EAC/B,OAAO,EAAE,MAAM,EACf,EAAE,MAAM,EAAE,OAAoB,EAAE,GAAE,wBAA6B,GAC9D,OAAO,CAAC,uBAAuB,CAAC,CA4BlC"}
1
+ {"version":3,"file":"delete.d.mts","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,uBAAuB,EACvB,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,aAAa,CAAC;AAIrB,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,uBAAuB,EAC/B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAEf;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,uBAAuB,EAC/B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,uBAAuB,EAC/B,OAAO,EAAE,MAAM,EACf,EAAE,MAAM,EAAE,OAAoB,EAAE,GAAE,wBAA6B,GAC9D,OAAO,CAAC,uBAAuB,CAAC,CA4BlC;AAED,MAAM,MAAM,8BAA8B,GAAG;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC;AAMtE;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,uBAAuB,EAC/B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,8BAA8B,CAAC,CAiCzC"}
package/dist/delete.mjs CHANGED
@@ -46,4 +46,43 @@ export async function scanAndUnlinkKeys(client, pattern, { signal, matches = ()
46
46
  throw err;
47
47
  }
48
48
  }
49
+ function escapeGlobLiteral(value) {
50
+ return value.replace(/[\\*?[\]]/g, "\\$&");
51
+ }
52
+ /**
53
+ * Unlinks every key that starts with any of the literal prefixes, scanning one prefix at a time.
54
+ *
55
+ * Each prefix is glob-escaped, so `*`, `?`, `[`, `]` and `\` match literally. A failing prefix does
56
+ * not stop later prefixes. One failure is rethrown as-is; several are combined into an
57
+ * `AggregateError` with an `unlinkedKeys` property. Failures reach the Valkey error handler once,
58
+ * through `scanAndUnlinkKeys`. An aborted `signal` rethrows its exact reason immediately.
59
+ */
60
+ export async function scanAndUnlinkKeyPrefixes(client, prefixes, options = {}) {
61
+ for (const prefix of prefixes) {
62
+ if (typeof prefix !== "string" || prefix === "") {
63
+ throw new TypeError("scanAndUnlinkKeyPrefixes prefixes must be non-empty strings");
64
+ }
65
+ }
66
+ const { signal } = options;
67
+ const failures = [];
68
+ let unlinkedKeys = 0;
69
+ for (const prefix of new Set(prefixes)) {
70
+ throwIfAborted(signal);
71
+ try {
72
+ const result = await scanAndUnlinkKeys(client, `${escapeGlobLiteral(prefix)}*`, options);
73
+ unlinkedKeys += result.unlinkedKeys;
74
+ }
75
+ catch (err) {
76
+ if (signal?.aborted && err === signal.reason)
77
+ throw err;
78
+ failures.push(err);
79
+ }
80
+ }
81
+ if (failures.length === 1)
82
+ throw failures[0];
83
+ if (failures.length > 1) {
84
+ throw Object.assign(new AggregateError(failures, `${failures.length} prefix scans failed after unlinking ${unlinkedKeys} keys`), { unlinkedKeys });
85
+ }
86
+ return { unlinkedKeys };
87
+ }
49
88
  //# sourceMappingURL=delete.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"delete.mjs","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAO/E,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAA+B,EAC/B,OAAe;IAEf,MAAM,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,MAA+B,EAC/B,OAAe,EACf,QAA2B;IAE3B,MAAM,cAAc,GAAG,CAAC,GAAG,IAAI,GAAG,CAAU,QAAQ,CAAC,CAAC,CAAC,MAAM,CAC3D,CAAC,MAAM,EAAoB,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,CACzD,CAAC;IACF,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAExC,MAAM,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE;QACvC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;KAC/F,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,MAA+B,EAC/B,OAAe,EACf,EAAE,MAAM,EAAE,OAAO,GAAG,GAAG,EAAE,CAAC,IAAI,EAAE,GAA6B,EAAE;IAE/D,IAAI,CAAC;QACH,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,YAAY,GAAG,CAAC,CAAC;QAErB,IAAI,KAAK,EAAE,MAAM,OAAO,IAAI,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE;YACxD,KAAK,EAAE,kBAAkB;YACzB,MAAM;SACP,CAAC,EAAE,CAAC;YACH,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;YAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACrC,cAAc,CAAC,MAAM,CAAC,CAAC;YACvB,WAAW,IAAI,IAAI,CAAC,MAAM,CAAC;YAE3B,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpB,cAAc,CAAC,MAAM,CAAC,CAAC;gBACvB,YAAY,IAAI,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;gBAC1C,cAAc,CAAC,MAAM,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QAED,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,CAAC;IACpD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,MAAM,EAAE,OAAO,IAAI,GAAG,KAAK,MAAM,CAAC,MAAM;YAAE,MAAM,GAAG,CAAC;QACxD,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACvB,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"delete.mjs","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import { type GlideClient } from "@valkey/valkey-glide";
2
+ export type ValkeyMemorySummary = {
3
+ usedMemoryBytes: number;
4
+ usedMemoryRssBytes: number;
5
+ usedMemoryPeakBytes: number;
6
+ maxmemoryBytes: number;
7
+ maxmemoryPolicy: string;
8
+ usedMemoryDatasetBytes: number | null;
9
+ lazyfreePendingObjects: number | null;
10
+ memFragmentationRatio: number | null;
11
+ };
12
+ export type DiagnosticsOptions = {
13
+ signal?: AbortSignal;
14
+ };
15
+ export type CountKeysByPrefixOptions = DiagnosticsOptions & {
16
+ /** SCAN COUNT hint per page. Defaults to 500. */
17
+ count?: number;
18
+ };
19
+ /**
20
+ * Parses `INFO memory` output (CRLF or LF, `#` section headers) into typed numbers.
21
+ * Throws when a required field is missing or any present numeric field is malformed.
22
+ */
23
+ export declare function parseValkeyMemoryInfo(info: string): ValkeyMemorySummary;
24
+ /** Runs `INFO memory` and parses it. Checks `signal` before and after the command. */
25
+ export declare function getValkeyMemoryInfo(client: Pick<GlideClient, "info">, { signal }?: DiagnosticsOptions): Promise<ValkeyMemorySummary>;
26
+ /**
27
+ * Rejects empty prefixes, a group named `unclassified`, and prefixes that overlap across
28
+ * groups (identical, or one a prefix of another), so every key belongs to at most one group.
29
+ */
30
+ export declare function validateKeyPrefixRegistry(prefixes: Record<string, readonly string[]>): void;
31
+ /**
32
+ * Counts keys per prefix group with bounded SCAN pages. Keys matching no prefix are counted as
33
+ * `unclassified`. SCAN is non-snapshot, so counts may include duplicates or miss keys written
34
+ * during the walk. `signal` is checked before and after each page.
35
+ */
36
+ export declare function countKeysByPrefix<G extends string>(client: Pick<GlideClient, "scan">, prefixes: Record<G, readonly string[]>, { signal, count }?: CountKeysByPrefixOptions): Promise<Record<G | "unclassified", number>>;
37
+ //# sourceMappingURL=diagnostics.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.d.mts","sourceRoot":"","sources":["../diagnostics.mts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAMrE,MAAM,MAAM,mBAAmB,GAAG;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,sBAAsB,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,CAAC;AAE1D,MAAM,MAAM,wBAAwB,GAAG,kBAAkB,GAAG;IAC1D,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AA4BF;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAsBvE;AAED,sFAAsF;AACtF,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACjC,EAAE,MAAM,EAAE,GAAE,kBAAuB,GAClC,OAAO,CAAC,mBAAmB,CAAC,CAK9B;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,GAAG,IAAI,CAmB3F;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EACtD,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACjC,QAAQ,EAAE,MAAM,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC,EACtC,EAAE,MAAM,EAAE,KAA0B,EAAE,GAAE,wBAA6B,GACpE,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,cAAc,EAAE,MAAM,CAAC,CAAC,CAc7C"}
@@ -0,0 +1,104 @@
1
+ import { InfoOptions } from "@valkey/valkey-glide";
2
+ import { glideStringToString, scanKeyPages, throwIfAborted } from "./scan.mjs";
3
+ const DEFAULT_SCAN_COUNT = 500;
4
+ const UNCLASSIFIED = "unclassified";
5
+ function parseFields(info) {
6
+ const fields = new Map();
7
+ for (const raw of info.split("\n")) {
8
+ const line = raw.trim();
9
+ const separator = line.indexOf(":");
10
+ if (line.startsWith("#") || separator < 0)
11
+ continue;
12
+ fields.set(line.slice(0, separator), line.slice(separator + 1));
13
+ }
14
+ return fields;
15
+ }
16
+ function parseInteger(name, value) {
17
+ const parsed = Number(value);
18
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(parsed)) {
19
+ throw new Error(`INFO field ${name} is not a non-negative safe integer: ${value}`);
20
+ }
21
+ return parsed;
22
+ }
23
+ function parseRatio(name, value) {
24
+ if (!/^\d+(\.\d+)?$/.test(value) || !Number.isFinite(Number(value))) {
25
+ throw new Error(`INFO field ${name} is not a non-negative number: ${value}`);
26
+ }
27
+ return Number(value);
28
+ }
29
+ /**
30
+ * Parses `INFO memory` output (CRLF or LF, `#` section headers) into typed numbers.
31
+ * Throws when a required field is missing or any present numeric field is malformed.
32
+ */
33
+ export function parseValkeyMemoryInfo(info) {
34
+ const fields = parseFields(info);
35
+ const required = (name) => {
36
+ const value = fields.get(name);
37
+ if (!value)
38
+ throw new Error(`INFO field ${name} is missing`);
39
+ return value;
40
+ };
41
+ const optional = (name, parse) => {
42
+ const value = fields.get(name);
43
+ return value === undefined ? null : parse(name, value);
44
+ };
45
+ return {
46
+ usedMemoryBytes: parseInteger("used_memory", required("used_memory")),
47
+ usedMemoryRssBytes: parseInteger("used_memory_rss", required("used_memory_rss")),
48
+ usedMemoryPeakBytes: parseInteger("used_memory_peak", required("used_memory_peak")),
49
+ maxmemoryBytes: parseInteger("maxmemory", required("maxmemory")),
50
+ maxmemoryPolicy: required("maxmemory_policy"),
51
+ usedMemoryDatasetBytes: optional("used_memory_dataset", parseInteger),
52
+ lazyfreePendingObjects: optional("lazyfree_pending_objects", parseInteger),
53
+ memFragmentationRatio: optional("mem_fragmentation_ratio", parseRatio),
54
+ };
55
+ }
56
+ /** Runs `INFO memory` and parses it. Checks `signal` before and after the command. */
57
+ export async function getValkeyMemoryInfo(client, { signal } = {}) {
58
+ throwIfAborted(signal);
59
+ const info = await client.info([InfoOptions.Memory]);
60
+ throwIfAborted(signal);
61
+ return parseValkeyMemoryInfo(glideStringToString(info));
62
+ }
63
+ /**
64
+ * Rejects empty prefixes, a group named `unclassified`, and prefixes that overlap across
65
+ * groups (identical, or one a prefix of another), so every key belongs to at most one group.
66
+ */
67
+ export function validateKeyPrefixRegistry(prefixes) {
68
+ const seen = [];
69
+ for (const [group, groupPrefixes] of Object.entries(prefixes)) {
70
+ if (group === UNCLASSIFIED)
71
+ throw new Error(`Prefix group name is reserved: ${group}`);
72
+ for (const prefix of groupPrefixes) {
73
+ if (prefix === "")
74
+ throw new Error(`Prefix group ${group} contains an empty prefix`);
75
+ const other = seen.find((entry) => entry.group !== group &&
76
+ (entry.prefix.startsWith(prefix) || prefix.startsWith(entry.prefix)));
77
+ if (other) {
78
+ throw new Error(`Prefix ${JSON.stringify(prefix)} in ${group} overlaps ${JSON.stringify(other.prefix)} in ${other.group}`);
79
+ }
80
+ seen.push({ group, prefix });
81
+ }
82
+ }
83
+ }
84
+ /**
85
+ * Counts keys per prefix group with bounded SCAN pages. Keys matching no prefix are counted as
86
+ * `unclassified`. SCAN is non-snapshot, so counts may include duplicates or miss keys written
87
+ * during the walk. `signal` is checked before and after each page.
88
+ */
89
+ export async function countKeysByPrefix(client, prefixes, { signal, count = DEFAULT_SCAN_COUNT } = {}) {
90
+ validateKeyPrefixRegistry(prefixes);
91
+ const groups = Object.keys(prefixes);
92
+ const counts = { [UNCLASSIFIED]: 0 };
93
+ for (const group of groups)
94
+ counts[group] = 0;
95
+ for await (const keys of scanKeyPages(client, "*", { count, signal })) {
96
+ for (const key of keys) {
97
+ const name = glideStringToString(key);
98
+ const group = groups.find((g) => prefixes[g].some((prefix) => name.startsWith(prefix)));
99
+ counts[group ?? UNCLASSIFIED] += 1;
100
+ }
101
+ }
102
+ return counts;
103
+ }
104
+ //# sourceMappingURL=diagnostics.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.mjs","sourceRoot":"","sources":["../diagnostics.mts"],"names":[],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -13,7 +13,9 @@ export * from "./errors.mts";
13
13
  export * from "./key-normalization.mts";
14
14
  export * from "./cache-metrics.mts";
15
15
  export * from "./retry.mts";
16
- export { scanAndUnlinkKeys } from "./delete.mts";
16
+ export { scanAndUnlinkKeyPrefixes, scanAndUnlinkKeys } from "./delete.mts";
17
17
  export { expireKeysWithNoExpiry } from "./expiry.mts";
18
+ export { countKeysByPrefix, getValkeyMemoryInfo, parseValkeyMemoryInfo, validateKeyPrefixRegistry, } from "./diagnostics.mts";
19
+ export type { CountKeysByPrefixOptions, DiagnosticsOptions, ValkeyMemorySummary, } from "./diagnostics.mts";
18
20
  export type { RateLimiterAddAndCheckWindowsOptions, RateLimiterOptions, RateLimiterWindow, ValkeyBloomFilterOptions, ValkeyCacheOptions, ValkeyCacheMode, ValkeyCacheResponse, DynamicConfigField, DynamicConfigFieldType, DynamicConfigOptions, ScanAndUnlinkKeysClient, ScanAndUnlinkKeysOptions, ScanAndUnlinkKeysResult, ExpireKeysWithNoExpiryOptions, ExpireKeysWithNoExpiryResult, } from "./types.mts";
19
21
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../index.mts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,YAAY,EACV,oCAAoC,EACpC,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,uBAAuB,EACvB,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../index.mts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,oCAAoC,EACpC,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,uBAAuB,EACvB,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,aAAa,CAAC"}
package/dist/index.mjs CHANGED
@@ -13,6 +13,7 @@ export * from "./errors.mjs";
13
13
  export * from "./key-normalization.mjs";
14
14
  export * from "./cache-metrics.mjs";
15
15
  export * from "./retry.mjs";
16
- export { scanAndUnlinkKeys } from "./delete.mjs";
16
+ export { scanAndUnlinkKeyPrefixes, scanAndUnlinkKeys } from "./delete.mjs";
17
17
  export { expireKeysWithNoExpiry } from "./expiry.mjs";
18
+ export { countKeysByPrefix, getValkeyMemoryInfo, parseValkeyMemoryInfo, validateKeyPrefixRegistry, } from "./diagnostics.mjs";
18
19
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../index.mts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../index.mts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,yBAAyB,GAC1B,MAAM,mBAAmB,CAAC"}
package/dist/retry.d.mts CHANGED
@@ -33,10 +33,10 @@ export type RetryValkeyOperationOptions = {
33
33
  */
34
34
  export declare function retryValkeyOperation<T>(fn: () => Promise<T>, options?: RetryValkeyOperationOptions): Promise<T>;
35
35
  export type RetrySaturationErrorOptions = {
36
- /** Maximum number of attempts (default: 3). */
36
+ /** Maximum number of attempts (default: `VALKEY_INFLIGHT_RETRY_ATTEMPTS`, or 3). */
37
37
  attempts?: number;
38
38
  /**
39
- * Minimum milliseconds between attempts (default: 1000).
39
+ * Minimum milliseconds between attempts (default: `VALKEY_INFLIGHT_RETRY_DELAY_MS`, or 1000).
40
40
  * Actual delay is jittered in [delayMs, delayMs * 5] to spread concurrent retriers.
41
41
  */
42
42
  delayMs?: number;
@@ -45,6 +45,10 @@ export type RetrySaturationErrorOptions = {
45
45
  * Retries a Valkey operation only on inflight-saturation rejections
46
46
  * (`Reached maximum inflight requests`), with jittered delay in [delayMs, delayMs*5].
47
47
  *
48
+ * When `attempts` / `delayMs` are omitted they default to `config.inflight_retry_attempts` /
49
+ * `config.inflight_retry_delay_ms` (`VALKEY_INFLIGHT_RETRY_ATTEMPTS` /
50
+ * `VALKEY_INFLIGHT_RETRY_DELAY_MS`); explicit options take precedence.
51
+ *
48
52
  * All other errors are rethrown immediately. Use this at internal command boundaries
49
53
  * so callers don't need to wrap each call site.
50
54
  *
@@ -1 +1 @@
1
- {"version":3,"file":"retry.d.mts","sourceRoot":"","sources":["../retry.mts"],"names":[],"mappings":"AAgBA;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAI9D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGzD;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAC1C;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,CAAC,CAAC,CA8BZ;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
1
+ {"version":3,"file":"retry.d.mts","sourceRoot":"","sources":["../retry.mts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAI9D;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAGzD;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4FAA4F;IAC5F,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAC1C;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,CAAC,CAAC,CA8BZ;AAED,MAAM,MAAM,2BAA2B,GAAG;IACxC,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,2BAA2B,GACpC,OAAO,CAAC,CAAC,CAAC,CAOZ"}
package/dist/retry.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import { randomInt } from "node:crypto";
2
+ import { config } from "./config.mjs";
2
3
  /**
3
4
  * Substring patterns that identify transient Valkey / Glide errors which are safe to retry.
4
5
  * These originate from the @valkey/valkey-glide native Rust core.
@@ -72,6 +73,10 @@ export async function retryValkeyOperation(fn, options) {
72
73
  * Retries a Valkey operation only on inflight-saturation rejections
73
74
  * (`Reached maximum inflight requests`), with jittered delay in [delayMs, delayMs*5].
74
75
  *
76
+ * When `attempts` / `delayMs` are omitted they default to `config.inflight_retry_attempts` /
77
+ * `config.inflight_retry_delay_ms` (`VALKEY_INFLIGHT_RETRY_ATTEMPTS` /
78
+ * `VALKEY_INFLIGHT_RETRY_DELAY_MS`); explicit options take precedence.
79
+ *
75
80
  * All other errors are rethrown immediately. Use this at internal command boundaries
76
81
  * so callers don't need to wrap each call site.
77
82
  *
@@ -80,8 +85,8 @@ export async function retryValkeyOperation(fn, options) {
80
85
  */
81
86
  export function retrySaturationError(fn, options) {
82
87
  return retryValkeyOperation(fn, {
83
- attempts: options?.attempts,
84
- delayMs: options?.delayMs,
88
+ attempts: options?.attempts ?? config.inflight_retry_attempts,
89
+ delayMs: options?.delayMs ?? config.inflight_retry_delay_ms,
85
90
  shouldRetry: isSaturationError,
86
91
  jitter: true,
87
92
  });
@@ -1 +1 @@
1
- {"version":3,"file":"retry.mjs","sourceRoot":"","sources":["../retry.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC;;;GAGG;AACH,MAAM,kBAAkB,GAAG;IACzB,mCAAmC;IACnC,mBAAmB;IACnB,mBAAmB;IACnB,mBAAmB;IACnB,4BAA4B;CAC7B,CAAC;AAEF,MAAM,kBAAkB,GAAG,mCAAmC,CAAC;AAE/D;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC;IAC1B,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;AACpD,CAAC;AAgBD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,EAAoB,EACpB,OAAqC;IAErC,MAAM,EACJ,QAAQ,GAAG,CAAC,EACZ,OAAO,GAAG,IAAI,EACd,WAAW,GAAG,sBAAsB,EACpC,MAAM,GAAG,KAAK,GACf,GAAG,OAAO,IAAI,EAAE,CAAC;IAClB,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC1C,IAAI,SAAkB,CAAC;IACvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;gBAAE,MAAM,KAAK,CAAC;YACrC,SAAS,GAAG,KAAK,CAAC;YAClB,IAAI,OAAO,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;gBAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,SAAS,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACtF,oFAAoF;gBACpF,qFAAqF;gBACrF,uFAAuF;gBACvF,yFAAyF;gBACzF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;oBAClC,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;oBAC1C,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;gBACnB,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,SAAS,CAAC;AAClB,CAAC;AAYD;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAClC,EAAoB,EACpB,OAAqC;IAErC,OAAO,oBAAoB,CAAC,EAAE,EAAE;QAC9B,QAAQ,EAAE,OAAO,EAAE,QAAQ;QAC3B,OAAO,EAAE,OAAO,EAAE,OAAO;QACzB,WAAW,EAAE,iBAAiB;QAC9B,MAAM,EAAE,IAAI;KACb,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"retry.mjs","sourceRoot":"","sources":["../retry.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;;GAGG;AACH,MAAM,kBAAkB,GAAG;IACzB,mCAAmC;IACnC,mBAAmB;IACnB,mBAAmB;IACnB,mBAAmB;IACnB,4BAA4B;CAC7B,CAAC;AAEF,MAAM,kBAAkB,GAAG,mCAAmC,CAAC;AAE/D;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC;IAC1B,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;AACpD,CAAC;AAgBD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,EAAoB,EACpB,OAAqC;IAErC,MAAM,EACJ,QAAQ,GAAG,CAAC,EACZ,OAAO,GAAG,IAAI,EACd,WAAW,GAAG,sBAAsB,EACpC,MAAM,GAAG,KAAK,GACf,GAAG,OAAO,IAAI,EAAE,CAAC;IAClB,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC1C,IAAI,SAAkB,CAAC;IACvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC;QACvD,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;gBAAE,MAAM,KAAK,CAAC;YACrC,SAAS,GAAG,KAAK,CAAC;YAClB,IAAI,OAAO,GAAG,WAAW,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;gBAC1C,MAAM,MAAM,GAAG,MAAM,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,GAAG,SAAS,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;gBACtF,oFAAoF;gBACpF,qFAAqF;gBACrF,uFAAuF;gBACvF,yFAAyF;gBACzF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;oBAClC,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;oBAC1C,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;gBACnB,CAAC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,SAAS,CAAC;AAClB,CAAC;AAYD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,oBAAoB,CAClC,EAAoB,EACpB,OAAqC;IAErC,OAAO,oBAAoB,CAAC,EAAE,EAAE;QAC9B,QAAQ,EAAE,OAAO,EAAE,QAAQ,IAAI,MAAM,CAAC,uBAAuB;QAC7D,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,MAAM,CAAC,uBAAuB;QAC3D,WAAW,EAAE,iBAAiB;QAC9B,MAAM,EAAE,IAAI;KACb,CAAC,CAAC;AACL,CAAC"}
@@ -1,3 +1,21 @@
1
+ export interface CloseValkeyClientsOptions {
2
+ /**
3
+ * Reject with an `AggregateError` after every close has settled if any close failed.
4
+ * Failures are still reported to `handleValkeyError`. Defaults to `false`.
5
+ */
6
+ throwOnError?: boolean;
7
+ }
8
+ /**
9
+ * Closes every live `DynamicConfig`, then every package-managed client and the
10
+ * dynamic-config subscription client. Closing `dynamicConfigs` beforehand is unnecessary.
11
+ *
12
+ * Shutdown runs once per module instance. Later calls are no-ops that resolve immediately,
13
+ * even if they pass `throwOnError` and an earlier call did not (or the first run failed).
14
+ */
15
+ export declare const closeValkeyClients: (options?: CloseValkeyClientsOptions) => Promise<void>;
16
+ /**
17
+ * Same as `closeValkeyClients()` but takes no arguments, so it is safe to pass directly as a
18
+ * signal or lifecycle callback that supplies its own arguments. Never rejects on close failures.
19
+ */
1
20
  export declare const onGracefulShutdown: () => Promise<void>;
2
- export declare const closeValkeyClients: typeof onGracefulShutdown;
3
21
  //# sourceMappingURL=shutdown.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shutdown.d.mts","sourceRoot":"","sources":["../shutdown.mts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,kBAAkB,QAAa,OAAO,CAAC,IAAI,CAgBvD,CAAC;AAEF,eAAO,MAAM,kBAAkB,2BAAqB,CAAC"}
1
+ {"version":3,"file":"shutdown.d.mts","sourceRoot":"","sources":["../shutdown.mts"],"names":[],"mappings":"AAmBA,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,aAAoB,yBAAyB,KAAG,OAAO,CAAC,IAAI,CA0B1F,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,QAAO,OAAO,CAAC,IAAI,CAAyB,CAAC"}
package/dist/shutdown.mjs CHANGED
@@ -15,25 +15,41 @@ const safeClose = (obj) => {
15
15
  return Promise.reject(error);
16
16
  }
17
17
  };
18
- export const onGracefulShutdown = async () => {
18
+ /**
19
+ * Closes every live `DynamicConfig`, then every package-managed client and the
20
+ * dynamic-config subscription client. Closing `dynamicConfigs` beforehand is unnecessary.
21
+ *
22
+ * Shutdown runs once per module instance. Later calls are no-ops that resolve immediately,
23
+ * even if they pass `throwOnError` and an earlier call did not (or the first run failed).
24
+ */
25
+ export const closeValkeyClients = async (options) => {
19
26
  if (shutdown)
20
27
  return;
21
28
  shutdown = true;
22
- const configResults = await Promise.allSettled([...dynamicConfigs].map(safeClose));
23
- for (const result of configResults) {
24
- if (result.status === "rejected")
25
- handleValkeyError(toError(result.reason));
26
- }
27
- const results = await Promise.allSettled([
29
+ const errors = [];
30
+ const report = (results) => {
31
+ for (const result of results) {
32
+ if (result.status === "rejected") {
33
+ const error = toError(result.reason);
34
+ errors.push(error);
35
+ handleValkeyError(error);
36
+ }
37
+ }
38
+ };
39
+ report(await Promise.allSettled([...dynamicConfigs].map(safeClose)));
40
+ report(await Promise.allSettled([
28
41
  ...Array.from(urlsToClients.values(), safeClose),
29
42
  closeDynamicConfigValkeySubscriptionClient(),
30
- ]);
31
- for (const result of results) {
32
- if (result.status === "rejected")
33
- handleValkeyError(toError(result.reason));
43
+ ]));
44
+ if (options?.throwOnError && errors.length > 0) {
45
+ throw new AggregateError(errors, "Valkey shutdown failed");
34
46
  }
35
47
  };
36
- export const closeValkeyClients = onGracefulShutdown;
48
+ /**
49
+ * Same as `closeValkeyClients()` but takes no arguments, so it is safe to pass directly as a
50
+ * signal or lifecycle callback that supplies its own arguments. Never rejects on close failures.
51
+ */
52
+ export const onGracefulShutdown = () => closeValkeyClients();
37
53
  function toError(error) {
38
54
  return error instanceof Error ? error : new Error(String(error));
39
55
  }
@@ -1 +1 @@
1
- {"version":3,"file":"shutdown.mjs","sourceRoot":"","sources":["../shutdown.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,0CAA0C,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB;;;;GAIG;AACH,MAAM,SAAS,GAAG,CAAC,GAA6B,EAAE,EAAE;IAClD,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,IAAmB,EAAE;IAC1D,IAAI,QAAQ;QAAE,OAAO;IACrB,QAAQ,GAAG,IAAI,CAAC;IAEhB,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IACnF,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE,CAAC;QACnC,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU;YAAE,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC;QACvC,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,SAAS,CAAC;QAChD,0CAA0C,EAAE;KAC7C,CAAC,CAAC;IACH,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU;YAAE,iBAAiB,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,kBAAkB,CAAC;AAErD,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACnE,CAAC"}
1
+ {"version":3,"file":"shutdown.mjs","sourceRoot":"","sources":["../shutdown.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,0CAA0C,EAAE,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,IAAI,QAAQ,GAAG,KAAK,CAAC;AAErB;;;;GAIG;AACH,MAAM,SAAS,GAAG,CAAC,GAA6B,EAAE,EAAE;IAClD,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC,CAAC;AAUF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EAAE,OAAmC,EAAiB,EAAE;IAC7F,IAAI,QAAQ;QAAE,OAAO;IACrB,QAAQ,GAAG,IAAI,CAAC;IAEhB,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,CAAC,OAAwC,EAAE,EAAE;QAC1D,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;gBACjC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACrC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACnB,iBAAiB,CAAC,KAAK,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,CAAC,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,CACJ,MAAM,OAAO,CAAC,UAAU,CAAC;QACvB,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,EAAE,SAAS,CAAC;QAChD,0CAA0C,EAAE;KAC7C,CAAC,CACH,CAAC;IAEF,IAAI,OAAO,EAAE,YAAY,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,cAAc,CAAC,MAAM,EAAE,wBAAwB,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAkB,EAAE,CAAC,kBAAkB,EAAE,CAAC;AAE5E,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AACnE,CAAC"}
package/docs/api.md CHANGED
@@ -17,6 +17,7 @@ This is the entry point for the public `valkyries` API. Detailed API documentati
17
17
  - [Events and metrics](events-and-metrics.md) - `valkeyEvents`, cache metrics, event payloads, and error handling hooks.
18
18
  - [Lua scripts](lua-scripts.md) - packaged Lua scripts and script-loading helpers.
19
19
  - [Configuration](configuration.md) - environment variables and connection behavior.
20
+ - [Diagnostics](diagnostics.md) - `INFO memory` parsing and cancellable prefix key counting.
20
21
  - [Utilities](utilities.md) - key and Valkey result normalization helpers.
21
22
 
22
23
  ## Package Entry Points
@@ -31,6 +32,7 @@ import {
31
32
  reserveIdempotencyKey,
32
33
  closeValkeyClients,
33
34
  scanAndUnlinkKeys,
35
+ scanAndUnlinkKeyPrefixes,
34
36
  expireKeysWithNoExpiry,
35
37
  } from "valkyries";
36
38
  ```
package/docs/clients.md CHANGED
@@ -10,7 +10,7 @@ Connections are deduplicated by URL, read preference, and lazy-connect setting t
10
10
 
11
11
  For libraries, tests, and multi-tenant services, pass a `GlideClient` directly to `ValkeyCache`, `ValkeyBloomFilter`, `DynamicConfig`, or `RateLimiter`.
12
12
 
13
- Use `closeValkeyClients()` or `onGracefulShutdown()` to close package-managed clients and the dynamic-config pub/sub connection.
13
+ Use `closeValkeyClients()` (or the argument-free `onGracefulShutdown()`) to close package-managed clients and the dynamic-config pub/sub connection.
14
14
 
15
15
  ## Default Clients
16
16
 
@@ -102,8 +102,20 @@ Registers or removes handlers that receive dynamic-config pub/sub messages.
102
102
  ## Shutdown
103
103
 
104
104
  ```ts
105
- closeValkeyClients(): Promise<void>
105
+ closeValkeyClients(options?: { throwOnError?: boolean }): Promise<void>
106
106
  onGracefulShutdown(): Promise<void>
107
107
  ```
108
108
 
109
- Both exports point to the same idempotent shutdown function. It closes all live dynamic configs, package-managed clients, and the dynamic-config subscription client.
109
+ `closeValkeyClients()` closes all live dynamic configs first, then package-managed clients and the dynamic-config subscription client. Closing `dynamicConfigs` yourself before calling it is unnecessary. `onGracefulShutdown()` runs the same sequence but takes no arguments, so it is safe to pass directly as a signal or lifecycle callback.
110
+
111
+ By default, close failures are reported to `handleValkeyError` and the promise always resolves. With `throwOnError: true`, failures are still reported to `handleValkeyError`; after every close has settled, the promise rejects with `new AggregateError(errors, "Valkey shutdown failed")`.
112
+
113
+ ```ts
114
+ try {
115
+ await closeValkeyClients({ throwOnError: true });
116
+ } catch (error) {
117
+ if (error instanceof AggregateError) process.exitCode = 1;
118
+ }
119
+ ```
120
+
121
+ Shutdown runs once per process. Repeated calls are no-ops that resolve immediately, including a later call that passes `throwOnError: true` after an earlier call without it: it neither re-closes anything nor reports the earlier failures.
@@ -14,3 +14,7 @@ Redis-style URLs are supported:
14
14
  - `rediss://user:password@example.com:6380`
15
15
 
16
16
  The cache client uses `readFrom: "preferReplica"`. Rate limiter reads use `primary` to avoid replica lag. Dynamic config commands use `preferReplica`; pub/sub uses a dedicated eager connection.
17
+
18
+ ## Saturation retries
19
+
20
+ `VALKEY_INFLIGHT_RETRY_ATTEMPTS` (default `3`) and `VALKEY_INFLIGHT_RETRY_DELAY_MS` (default `1000`) set how internal command boundaries retry commands the client rejects locally with `Reached maximum inflight requests`. The exported `retrySaturationError(fn, options?)` uses the same values when `options.attempts` / `options.delayMs` are omitted; explicit options take precedence. Delays are jittered in `[delayMs, delayMs * 5]`.
@@ -0,0 +1,50 @@
1
+ # Diagnostics
2
+
3
+ Read-only helpers for inspecting memory and keyspace usage. Choosing a client, checking deployment
4
+ topology, and naming groups are left to the caller.
5
+
6
+ ## `parseValkeyMemoryInfo(info)`
7
+
8
+ ```ts
9
+ parseValkeyMemoryInfo(info: string): ValkeyMemorySummary
10
+ ```
11
+
12
+ Parses `INFO memory` text (`key:value` lines, `#` section headers, LF or CRLF). Throws when
13
+ `used_memory`, `used_memory_rss`, `used_memory_peak`, `maxmemory`, or `maxmemory_policy` is missing,
14
+ or when an integer field is not a non-negative safe integer. `usedMemoryDatasetBytes`,
15
+ `lazyfreePendingObjects`, and `memFragmentationRatio` are `null` when absent, but still throw when
16
+ present and malformed.
17
+
18
+ ## `getValkeyMemoryInfo(client, options?)`
19
+
20
+ ```ts
21
+ getValkeyMemoryInfo(
22
+ client: Pick<GlideClient, "info">,
23
+ options?: { signal?: AbortSignal },
24
+ ): Promise<ValkeyMemorySummary>
25
+ ```
26
+
27
+ Runs `INFO memory` and parses it. The signal is checked before and after the command.
28
+
29
+ ## `countKeysByPrefix(client, prefixes, options?)`
30
+
31
+ ```ts
32
+ import { countKeysByPrefix } from "valkyries";
33
+
34
+ const counts = await countKeysByPrefix(
35
+ client,
36
+ { cache: ["cache:"], sessions: ["session:", "sess:"] },
37
+ { signal, count: 500 },
38
+ );
39
+ // { cache: 12, sessions: 3, unclassified: 40 }
40
+ ```
41
+
42
+ Walks the keyspace with bounded `SCAN` pages (`count` defaults to 500) and checks `signal` before and
43
+ after each page, including the last. Keys matching no prefix are counted as `unclassified`. SCAN is
44
+ non-snapshot, so counts can include duplicates or miss keys written during the walk. The registry is
45
+ validated first.
46
+
47
+ ## `validateKeyPrefixRegistry(prefixes)`
48
+
49
+ Throws for empty prefixes, a group named `unclassified`, and prefixes that overlap across groups
50
+ (identical, or one a prefix of another), so every key belongs to at most one group.
package/docs/utilities.md CHANGED
@@ -24,6 +24,25 @@ from the keys that exist when the call returns. The optional `signal` is checked
24
24
  every SCAN and UNLINK boundary; its exact abort reason is rethrown without reporting it to the
25
25
  configured Valkey error handler.
26
26
 
27
+ ## `scanAndUnlinkKeyPrefixes(client, prefixes, options)`
28
+
29
+ ```ts
30
+ scanAndUnlinkKeyPrefixes(
31
+ client: ScanAndUnlinkKeysClient,
32
+ prefixes: readonly string[],
33
+ options?: ScanAndUnlinkKeysOptions, // { signal?, matches? }
34
+ ): Promise<{ unlinkedKeys: number }>
35
+ ```
36
+
37
+ Runs `scanAndUnlinkKeys` serially for each distinct prefix, glob-escaping `*`, `?`, `[`, `]`, and
38
+ `\` and appending `*`, so a prefix matches only keys that literally start with it. An empty or
39
+ non-string prefix throws a `TypeError` before any scan starts.
40
+
41
+ A failing prefix does not stop later prefixes. One failure is rethrown as-is; two or more are
42
+ combined into an `AggregateError` whose message and `unlinkedKeys` property report the keys already
43
+ removed. Failures reach the configured Valkey error handler once, from `scanAndUnlinkKeys`. The
44
+ `signal` is checked before each prefix, and its exact abort reason is rethrown immediately.
45
+
27
46
  ## `expireKeysWithNoExpiry(client, options)`
28
47
 
29
48
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valkyries",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Valkey utilities for caches, bloom filters, dynamic configuration, and rate limiting.",
5
5
  "homepage": "https://github.com/jonathanong/valkyries#readme",
6
6
  "bugs": {
@@ -70,7 +70,7 @@
70
70
  "@types/node": "^26.1.0",
71
71
  "@vitest/coverage-v8": "^5.0.0",
72
72
  "github-actionlint": "^1.7.12",
73
- "oxfmt": "^0.70.0",
73
+ "oxfmt": "^0.71.0",
74
74
  "oxlint": "^1.65.0",
75
75
  "oxlint-tsgolint": "^7.0.2001",
76
76
  "tsx": "^4.22.1",