valkyries 0.6.2 → 0.7.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
@@ -113,6 +113,45 @@ const cache = new ValkeyCache({ prefix: "custom", ttlSeconds: 60, client });
113
113
 
114
114
  Call `closeValkeyClients()` to close package-managed clients.
115
115
 
116
+ ## Scan and Unlink Keys
117
+
118
+ Use `scanAndUnlinkKeys()` for cancellable, pattern-based cleanup with explicit scan and deletion
119
+ counts:
120
+
121
+ ```ts
122
+ import { scanAndUnlinkKeys } from "valkyries";
123
+
124
+ const result = await scanAndUnlinkKeys(client, "cache:*", {
125
+ matches: (key) => Buffer.from(key).toString("utf8").startsWith("cache:users:"),
126
+ signal: abortController.signal,
127
+ });
128
+ ```
129
+
130
+ The result reports `scannedKeys`, `matchedKeys`, and keys confirmed removed by `UNLINK` as
131
+ `unlinkedKeys`. `SCAN` is non-snapshot, so these counts can include duplicates or omit keys while
132
+ the keyspace changes.
133
+
134
+ ## Expire Keys Without a TTL
135
+
136
+ Use `expireKeysWithNoExpiry()` to apply a TTL only when matching keys still have no expiry:
137
+
138
+ ```ts
139
+ import { expireKeysWithNoExpiry } from "valkyries";
140
+
141
+ const result = await expireKeysWithNoExpiry(client, {
142
+ pattern: "cache:*",
143
+ ttl: 86_400,
144
+ shouldExpire: (key) => Buffer.from(key).toString("utf8").startsWith("cache:users:"),
145
+ signal: abortController.signal,
146
+ batchSize: 250,
147
+ });
148
+ ```
149
+
150
+ The result reports `scannedKeys`, `matchedKeys`, and `expiredKeys`. The helper uses `EXPIRE NX`,
151
+ so it never replaces a TTL that was added concurrently. `SCAN` is non-snapshot, so counts can
152
+ include duplicates or omit keys while the keyspace changes. `scanCount` is a SCAN hint, while
153
+ `batchSize` independently bounds each EXPIRE pipeline.
154
+
116
155
  ## Documentation
117
156
 
118
157
  - [Configuration](docs/configuration.md)
package/dist/delete.d.mts CHANGED
@@ -1,4 +1,12 @@
1
+ import type { ScanAndUnlinkKeysOptions, ScanAndUnlinkKeysResult } from "./types.mts";
1
2
  import type { GlideClient } from "@valkey/valkey-glide";
2
3
  export declare function deleteKeysWithPrefix(client: GlideClient, pattern: string): Promise<void>;
3
4
  export declare function deleteKeysWithLiteralPrefixes(client: GlideClient, pattern: string, prefixes: readonly string[]): Promise<void>;
5
+ /**
6
+ * Scans keys matching a Valkey pattern and unlinks keys accepted by an optional predicate.
7
+ *
8
+ * SCAN is non-snapshot: concurrent writes can make the returned counts include duplicates,
9
+ * omit keys, or differ from the number of keys that exist when this function returns.
10
+ */
11
+ export declare function scanAndUnlinkKeys(client: GlideClient, pattern: string, { signal, matches }?: ScanAndUnlinkKeysOptions): Promise<ScanAndUnlinkKeysResult>;
4
12
  //# sourceMappingURL=delete.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"delete.d.mts","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,sBAAsB,CAAC;AAIrE,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9F;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CASf"}
1
+ {"version":3,"file":"delete.d.mts","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAIxD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE9F;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,SAAS,MAAM,EAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,EAAE,MAAM,EAAE,OAAoB,EAAE,GAAE,wBAA6B,GAC9D,OAAO,CAAC,uBAAuB,CAAC,CA4BlC"}
package/dist/delete.mjs CHANGED
@@ -1,30 +1,47 @@
1
- import { Buffer } from "node:buffer";
2
1
  import { handleValkeyError } from "./errors.mjs";
2
+ import { glideStringToString, scanKeyPages, throwIfAborted } from "./scan.mjs";
3
3
  const DEFAULT_SCAN_COUNT = 500;
4
4
  export async function deleteKeysWithPrefix(client, pattern) {
5
- await scanAndUnlink(client, pattern);
5
+ await scanAndUnlinkKeys(client, pattern);
6
6
  }
7
7
  export async function deleteKeysWithLiteralPrefixes(client, pattern, prefixes) {
8
8
  const uniquePrefixes = [...new Set(prefixes)].filter((prefix) => typeof prefix === "string");
9
9
  if (uniquePrefixes.length === 0)
10
10
  return;
11
- await scanAndUnlink(client, pattern, (key) => uniquePrefixes.some((prefix) => key.startsWith(prefix)));
11
+ await scanAndUnlinkKeys(client, pattern, {
12
+ matches: (key) => uniquePrefixes.some((prefix) => glideStringToString(key).startsWith(prefix)),
13
+ });
12
14
  }
13
- async function scanAndUnlink(client, pattern, matches = () => true) {
15
+ /**
16
+ * Scans keys matching a Valkey pattern and unlinks keys accepted by an optional predicate.
17
+ *
18
+ * SCAN is non-snapshot: concurrent writes can make the returned counts include duplicates,
19
+ * omit keys, or differ from the number of keys that exist when this function returns.
20
+ */
21
+ export async function scanAndUnlinkKeys(client, pattern, { signal, matches = () => true } = {}) {
14
22
  try {
15
- let cursor = "0";
16
- do {
17
- const result = await client.scan(cursor, { match: pattern, count: DEFAULT_SCAN_COUNT });
18
- cursor = result[0];
19
- const keys = result[1]
20
- .map((key) => (typeof key === "string" ? key : Buffer.from(key).toString("utf8")))
21
- .filter(matches);
23
+ let scannedKeys = 0;
24
+ let matchedKeys = 0;
25
+ let unlinkedKeys = 0;
26
+ for await (const scanned of scanKeyPages(client, pattern, {
27
+ count: DEFAULT_SCAN_COUNT,
28
+ signal,
29
+ })) {
30
+ scannedKeys += scanned.length;
31
+ const keys = scanned.filter(matches);
32
+ throwIfAborted(signal);
33
+ matchedKeys += keys.length;
22
34
  if (keys.length > 0) {
23
- await client.unlink(keys);
35
+ throwIfAborted(signal);
36
+ unlinkedKeys += await client.unlink(keys);
37
+ throwIfAborted(signal);
24
38
  }
25
- } while (cursor !== "0");
39
+ }
40
+ return { scannedKeys, matchedKeys, unlinkedKeys };
26
41
  }
27
42
  catch (err) {
43
+ if (signal?.aborted && err === signal.reason)
44
+ throw err;
28
45
  handleValkeyError(err);
29
46
  throw err;
30
47
  }
@@ -1 +1 @@
1
- {"version":3,"file":"delete.mjs","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAAmB,EAAE,OAAe;IAC7E,MAAM,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,MAAmB,EACnB,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,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAC3C,cAAc,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CACxD,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,MAAmB,EACnB,OAAe,EACf,OAAO,GAA6B,GAAG,EAAE,CAAC,IAAI;IAE9C,IAAI,CAAC;QACH,IAAI,MAAM,GAAG,GAAG,CAAC;QAEjB,GAAG,CAAC;YACF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC,CAAC;YACxF,MAAM,GAAG,MAAM,CAAC,CAAC,CAAW,CAAC;YAC7B,MAAM,IAAI,GAAI,MAAM,CAAC,CAAC,CAAmB;iBACtC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;iBACjF,MAAM,CAAC,OAAO,CAAC,CAAC;YAEnB,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpB,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC,QAAQ,MAAM,KAAK,GAAG,EAAE;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,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":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAI/E,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAE/B,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAAmB,EAAE,OAAe;IAC7E,MAAM,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,MAAmB,EACnB,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,MAAmB,EACnB,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"}
@@ -0,0 +1,10 @@
1
+ import { type GlideClient } from "@valkey/valkey-glide";
2
+ import type { ExpireKeysWithNoExpiryOptions, ExpireKeysWithNoExpiryResult } from "./types.mts";
3
+ /**
4
+ * Sets a TTL only on matching keys that have no existing expiry.
5
+ *
6
+ * SCAN is non-snapshot: concurrent writes can make the returned counts include duplicates,
7
+ * omit keys, or differ from the number of keys that exist when this function returns.
8
+ */
9
+ export declare function expireKeysWithNoExpiry(client: GlideClient, { pattern, ttl, shouldExpire, signal, scanCount, batchSize, }: ExpireKeysWithNoExpiryOptions): Promise<ExpireKeysWithNoExpiryResult>;
10
+ //# sourceMappingURL=expiry.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expiry.d.mts","sourceRoot":"","sources":["../expiry.mts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,WAAW,EAAoB,MAAM,sBAAsB,CAAC;AAGhG,OAAO,KAAK,EAAE,6BAA6B,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAK/F;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,WAAW,EACnB,EACE,OAAO,EACP,GAAG,EACH,YAAyB,EACzB,MAAM,EACN,SAA8B,EAC9B,SAA8B,GAC/B,EAAE,6BAA6B,GAC/B,OAAO,CAAC,4BAA4B,CAAC,CAuBvC"}
@@ -0,0 +1,70 @@
1
+ import { Batch, ExpireOptions } from "@valkey/valkey-glide";
2
+ import { handleValkeyError } from "./errors.mjs";
3
+ import { scanKeyPages, throwIfAborted } from "./scan.mjs";
4
+ const DEFAULT_SCAN_COUNT = 500;
5
+ const DEFAULT_BATCH_SIZE = 500;
6
+ /**
7
+ * Sets a TTL only on matching keys that have no existing expiry.
8
+ *
9
+ * SCAN is non-snapshot: concurrent writes can make the returned counts include duplicates,
10
+ * omit keys, or differ from the number of keys that exist when this function returns.
11
+ */
12
+ export async function expireKeysWithNoExpiry(client, { pattern, ttl, shouldExpire = () => true, signal, scanCount = DEFAULT_SCAN_COUNT, batchSize = DEFAULT_BATCH_SIZE, }) {
13
+ validateExpirySweepOptions(ttl, scanCount, batchSize);
14
+ try {
15
+ let scannedKeys = 0;
16
+ let matchedKeys = 0;
17
+ let expiredKeys = 0;
18
+ for await (const scanned of scanKeyPages(client, pattern, { count: scanCount, signal })) {
19
+ scannedKeys += scanned.length;
20
+ const keys = scanned.filter(shouldExpire);
21
+ throwIfAborted(signal);
22
+ matchedKeys += keys.length;
23
+ expiredKeys += await expireKeys(client, keys, ttl, batchSize, signal);
24
+ }
25
+ return { scannedKeys, matchedKeys, expiredKeys };
26
+ }
27
+ catch (error) {
28
+ if (signal?.aborted && error === signal.reason)
29
+ throw error;
30
+ handleValkeyError(error);
31
+ throw error;
32
+ }
33
+ }
34
+ async function expireKeys(client, keys, ttl, batchSize, signal) {
35
+ let expiredKeys = 0;
36
+ for (let offset = 0; offset < keys.length; offset += batchSize) {
37
+ expiredKeys += await expireBatch(client, keys.slice(offset, offset + batchSize), ttl, signal);
38
+ }
39
+ return expiredKeys;
40
+ }
41
+ async function expireBatch(client, keys, ttl, signal) {
42
+ throwIfAborted(signal);
43
+ const batch = new Batch(false);
44
+ for (const key of keys) {
45
+ batch.expire(key, ttl, { expireOption: ExpireOptions.HasNoExpiry });
46
+ }
47
+ const results = await client.exec(batch, true);
48
+ throwIfAborted(signal);
49
+ if (!Array.isArray(results) || results.length !== keys.length) {
50
+ throw new Error("expireKeysWithNoExpiry: unexpected EXPIRE batch response");
51
+ }
52
+ let expiredKeys = 0;
53
+ for (const result of results) {
54
+ if (result === true)
55
+ expiredKeys += 1;
56
+ }
57
+ return expiredKeys;
58
+ }
59
+ function validateExpirySweepOptions(ttl, scanCount, batchSize) {
60
+ if (!Number.isSafeInteger(ttl) || ttl <= 0) {
61
+ throw new RangeError("expireKeysWithNoExpiry: ttl must be a positive safe integer");
62
+ }
63
+ if (!Number.isSafeInteger(scanCount) || scanCount <= 0) {
64
+ throw new RangeError("expireKeysWithNoExpiry: scanCount must be a positive safe integer");
65
+ }
66
+ if (!Number.isSafeInteger(batchSize) || batchSize <= 0) {
67
+ throw new RangeError("expireKeysWithNoExpiry: batchSize must be a positive safe integer");
68
+ }
69
+ }
70
+ //# sourceMappingURL=expiry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expiry.mjs","sourceRoot":"","sources":["../expiry.mts"],"names":[],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -13,5 +13,7 @@ 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 type { RateLimiterAddAndCheckWindowsOptions, RateLimiterOptions, RateLimiterWindow, ValkeyBloomFilterOptions, ValkeyCacheOptions, ValkeyCacheMode, ValkeyCacheResponse, DynamicConfigField, DynamicConfigFieldType, DynamicConfigOptions, } from "./types.mts";
16
+ export { scanAndUnlinkKeys } from "./delete.mts";
17
+ export { expireKeysWithNoExpiry } from "./expiry.mts";
18
+ export type { RateLimiterAddAndCheckWindowsOptions, RateLimiterOptions, RateLimiterWindow, ValkeyBloomFilterOptions, ValkeyCacheOptions, ValkeyCacheMode, ValkeyCacheResponse, DynamicConfigField, DynamicConfigFieldType, DynamicConfigOptions, ScanAndUnlinkKeysOptions, ScanAndUnlinkKeysResult, ExpireKeysWithNoExpiryOptions, ExpireKeysWithNoExpiryResult, } from "./types.mts";
17
19
  //# 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,YAAY,EACV,oCAAoC,EACpC,kBAAkB,EAClB,iBAAiB,EACjB,wBAAwB,EACxB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACrB,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,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,wBAAwB,EACxB,uBAAuB,EACvB,6BAA6B,EAC7B,4BAA4B,GAC7B,MAAM,aAAa,CAAC"}
package/dist/index.mjs CHANGED
@@ -13,4 +13,6 @@ 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";
17
+ export { expireKeysWithNoExpiry } from "./expiry.mjs";
16
18
  //# 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"}
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"}
@@ -0,0 +1,10 @@
1
+ import { type GlideClient, type GlideString } from "@valkey/valkey-glide";
2
+ type ScanKeyPagesOptions = {
3
+ count: number;
4
+ signal?: AbortSignal;
5
+ };
6
+ export declare function scanKeyPages(client: GlideClient, pattern: string, { count, signal }: ScanKeyPagesOptions): AsyncGenerator<GlideString[]>;
7
+ export declare function throwIfAborted(signal: AbortSignal | undefined): void;
8
+ export declare function glideStringToString(key: GlideString): string;
9
+ export {};
10
+ //# sourceMappingURL=scan.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.mts","sourceRoot":"","sources":["../scan.mts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnF,KAAK,mBAAmB,GAAG;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF,wBAAuB,YAAY,CACjC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,mBAAmB,GACrC,cAAc,CAAC,WAAW,EAAE,CAAC,CAa/B;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAEpE;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAE5D"}
package/dist/scan.mjs ADDED
@@ -0,0 +1,24 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { Decoder } from "@valkey/valkey-glide";
3
+ export async function* scanKeyPages(client, pattern, { count, signal }) {
4
+ let cursor = "0";
5
+ do {
6
+ throwIfAborted(signal);
7
+ const [nextCursor, keys] = await client.scan(cursor, {
8
+ match: pattern,
9
+ count,
10
+ decoder: Decoder.Bytes,
11
+ });
12
+ throwIfAborted(signal);
13
+ cursor = glideStringToString(nextCursor);
14
+ yield keys;
15
+ } while (cursor !== "0");
16
+ }
17
+ export function throwIfAborted(signal) {
18
+ if (signal?.aborted)
19
+ throw signal.reason;
20
+ }
21
+ export function glideStringToString(key) {
22
+ return typeof key === "string" ? key : Buffer.from(key).toString("utf8");
23
+ }
24
+ //# sourceMappingURL=scan.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.mjs","sourceRoot":"","sources":["../scan.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,OAAO,EAAsC,MAAM,sBAAsB,CAAC;AAOnF,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,YAAY,CACjC,MAAmB,EACnB,OAAe,EACf,EAAE,KAAK,EAAE,MAAM,EAAuB;IAEtC,IAAI,MAAM,GAAG,GAAG,CAAC;IACjB,GAAG,CAAC;QACF,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE;YACnD,KAAK,EAAE,OAAO;YACd,KAAK;YACL,OAAO,EAAE,OAAO,CAAC,KAAK;SACvB,CAAC,CAAC;QACH,cAAc,CAAC,MAAM,CAAC,CAAC;QACvB,MAAM,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;QACzC,MAAM,IAAI,CAAC;IACb,CAAC,QAAQ,MAAM,KAAK,GAAG,EAAE;AAC3B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAA+B;IAC5D,IAAI,MAAM,EAAE,OAAO;QAAE,MAAM,MAAM,CAAC,MAAM,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,GAAgB;IAClD,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC3E,CAAC"}
package/dist/types.d.mts CHANGED
@@ -1,4 +1,40 @@
1
- import type { GlideClient } from "@valkey/valkey-glide";
1
+ import type { GlideClient, GlideString } from "@valkey/valkey-glide";
2
+ export type ScanAndUnlinkKeysOptions = {
3
+ /** Stops before or after each SCAN and UNLINK command boundary. */
4
+ signal?: AbortSignal;
5
+ /** Selects scanned string or Buffer keys to unlink. Defaults to every scanned key. */
6
+ matches?: (key: GlideString) => boolean;
7
+ };
8
+ export type ScanAndUnlinkKeysResult = {
9
+ /** Number of keys returned by SCAN, including any duplicates from its non-snapshot iteration. */
10
+ scannedKeys: number;
11
+ /** Number of scanned keys accepted by matches. */
12
+ matchedKeys: number;
13
+ /** Number of keys confirmed by UNLINK as removed. */
14
+ unlinkedKeys: number;
15
+ };
16
+ export type ExpireKeysWithNoExpiryOptions = {
17
+ /** Valkey SCAN pattern for keys to evaluate. */
18
+ pattern: string;
19
+ /** TTL in seconds to apply only where the key has no expiry. */
20
+ ttl: number;
21
+ /** Selects scanned string or Buffer keys to expire. Defaults to every scanned key. */
22
+ shouldExpire?: (key: GlideString) => boolean;
23
+ /** Stops before or after each SCAN and EXPIRE-batch command boundary. */
24
+ signal?: AbortSignal;
25
+ /** SCAN COUNT hint. Defaults to 500. */
26
+ scanCount?: number;
27
+ /** Maximum number of EXPIRE commands in one non-atomic pipeline. Defaults to 500. */
28
+ batchSize?: number;
29
+ };
30
+ export type ExpireKeysWithNoExpiryResult = {
31
+ /** Number of keys returned by SCAN, including any duplicates from its non-snapshot iteration. */
32
+ scannedKeys: number;
33
+ /** Number of scanned keys accepted by shouldExpire. */
34
+ matchedKeys: number;
35
+ /** Number of keys confirmed by EXPIRE NX as newly given a TTL. */
36
+ expiredKeys: number;
37
+ };
2
38
  export type ValkeyClientsOption = {
3
39
  cacheClient?: GlideClient;
4
40
  rateLimiterClient?: GlideClient;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../types.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../types.mts"],"names":[],"mappings":"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"}
package/docs/api.md CHANGED
@@ -29,6 +29,8 @@ import {
29
29
  getAndDelete,
30
30
  reserveIdempotencyKey,
31
31
  closeValkeyClients,
32
+ scanAndUnlinkKeys,
33
+ expireKeysWithNoExpiry,
32
34
  } from "valkyries";
33
35
  ```
34
36
 
package/docs/utilities.md CHANGED
@@ -2,6 +2,54 @@
2
2
 
3
3
  These normalization helpers are exported from the package root.
4
4
 
5
+ ## `scanAndUnlinkKeys(client, pattern, options)`
6
+
7
+ ```ts
8
+ scanAndUnlinkKeys(
9
+ client: GlideClient,
10
+ pattern: string,
11
+ options?: {
12
+ signal?: AbortSignal;
13
+ matches?: (key: GlideString) => boolean;
14
+ },
15
+ ): Promise<{ scannedKeys: number; matchedKeys: number; unlinkedKeys: number }>
16
+ ```
17
+
18
+ Scans with `COUNT 500`, unlinks every scanned key accepted by `matches`, and returns the number of
19
+ keys scanned, predicate-matched, and confirmed removed by `UNLINK`. Keys are provided to `matches`
20
+ and `UNLINK` as their original string or `Buffer` `GlideString` values.
21
+
22
+ `SCAN` is non-snapshot: concurrent writes can make counts include duplicates, omit keys, or differ
23
+ from the keys that exist when the call returns. The optional `signal` is checked before and after
24
+ every SCAN and UNLINK boundary; its exact abort reason is rethrown without reporting it to the
25
+ configured Valkey error handler.
26
+
27
+ ## `expireKeysWithNoExpiry(client, options)`
28
+
29
+ ```ts
30
+ expireKeysWithNoExpiry(
31
+ client: GlideClient,
32
+ options: {
33
+ pattern: string;
34
+ ttl: number;
35
+ shouldExpire?: (key: GlideString) => boolean;
36
+ signal?: AbortSignal;
37
+ scanCount?: number;
38
+ batchSize?: number;
39
+ },
40
+ ): Promise<{ scannedKeys: number; matchedKeys: number; expiredKeys: number }>
41
+ ```
42
+
43
+ Scans matching keys and pipelines `EXPIRE NX` commands for every key accepted by `shouldExpire`.
44
+ `EXPIRE NX` atomically sets the TTL only when a key has no existing expiry, so a concurrent TTL
45
+ change is never overwritten. `scanCount` defaults to 500 as a SCAN hint. `batchSize` separately
46
+ defaults to 500 and bounds one non-atomic pipeline.
47
+
48
+ `SCAN` is non-snapshot: concurrent writes can make counts include duplicates, omit keys, or differ
49
+ from the keys that exist when the call returns. The optional `signal` is checked before and after
50
+ each SCAN and EXPIRE-batch boundary; its exact abort reason is rethrown without reporting it to the
51
+ configured Valkey error handler.
52
+
5
53
  ## `normalizeKey(key)`
6
54
 
7
55
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valkyries",
3
- "version": "0.6.2",
3
+ "version": "0.7.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": {
@@ -65,9 +65,9 @@
65
65
  "@types/node": "^26.1.0",
66
66
  "@vitest/coverage-v8": "^4.1.6",
67
67
  "github-actionlint": "^1.7.12",
68
- "oxfmt": "^0.59.0",
68
+ "oxfmt": "^0.63.0",
69
69
  "oxlint": "^1.65.0",
70
- "oxlint-tsgolint": "^0.25.0",
70
+ "oxlint-tsgolint": "^7.0.2001",
71
71
  "tsx": "^4.22.1",
72
72
  "typescript": "^7.0.2",
73
73
  "vitest": "^4.1.6"