valkyries 0.6.2 → 0.8.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 +55 -0
- package/dist/channel-pubsub-internals.d.mts +13 -0
- package/dist/channel-pubsub-internals.d.mts.map +1 -0
- package/dist/channel-pubsub-internals.mjs +82 -0
- package/dist/channel-pubsub-internals.mjs.map +1 -0
- package/dist/channel-pubsub-types.d.mts +24 -0
- package/dist/channel-pubsub-types.d.mts.map +1 -0
- package/dist/channel-pubsub-types.mjs +2 -0
- package/dist/channel-pubsub-types.mjs.map +1 -0
- package/dist/channel-pubsub.d.mts +4 -0
- package/dist/channel-pubsub.d.mts.map +1 -0
- package/dist/channel-pubsub.mjs +184 -0
- package/dist/channel-pubsub.mjs.map +1 -0
- package/dist/delete.d.mts +8 -0
- package/dist/delete.d.mts.map +1 -1
- package/dist/delete.mjs +30 -13
- package/dist/delete.mjs.map +1 -1
- package/dist/expiry.d.mts +10 -0
- package/dist/expiry.d.mts.map +1 -0
- package/dist/expiry.mjs +70 -0
- package/dist/expiry.mjs.map +1 -0
- package/dist/index.d.mts +3 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -1
- package/dist/scan.d.mts +10 -0
- package/dist/scan.d.mts.map +1 -0
- package/dist/scan.mjs +24 -0
- package/dist/scan.mjs.map +1 -0
- package/dist/types.d.mts +37 -1
- package/dist/types.d.mts.map +1 -1
- package/docs/api.md +4 -0
- package/docs/channel-pubsub.md +59 -0
- package/docs/utilities.md +48 -0
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -88,6 +88,21 @@ await flags.setField("enabled", true);
|
|
|
88
88
|
|
|
89
89
|
Dynamic config stores fields in a Valkey hash and publishes changes over pub/sub.
|
|
90
90
|
|
|
91
|
+
## Channel Pub/Sub
|
|
92
|
+
|
|
93
|
+
`valkyries/channel-pubsub` provides ephemeral, key-scoped pub/sub with caller-owned client
|
|
94
|
+
configuration and lifecycle. It is not re-exported from the package root.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { createChannelPubSub } from "valkyries/channel-pubsub";
|
|
98
|
+
|
|
99
|
+
const events = createChannelPubSub<{ status: string }>("exports", {
|
|
100
|
+
clientConfig: { addresses: [{ host: "localhost", port: 6379 }] },
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
See [Channel pub/sub](docs/channel-pubsub.md) for lifecycle and error-handling details.
|
|
105
|
+
|
|
91
106
|
## Rate Limiter
|
|
92
107
|
|
|
93
108
|
```ts
|
|
@@ -113,6 +128,45 @@ const cache = new ValkeyCache({ prefix: "custom", ttlSeconds: 60, client });
|
|
|
113
128
|
|
|
114
129
|
Call `closeValkeyClients()` to close package-managed clients.
|
|
115
130
|
|
|
131
|
+
## Scan and Unlink Keys
|
|
132
|
+
|
|
133
|
+
Use `scanAndUnlinkKeys()` for cancellable, pattern-based cleanup with explicit scan and deletion
|
|
134
|
+
counts:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import { scanAndUnlinkKeys } from "valkyries";
|
|
138
|
+
|
|
139
|
+
const result = await scanAndUnlinkKeys(client, "cache:*", {
|
|
140
|
+
matches: (key) => Buffer.from(key).toString("utf8").startsWith("cache:users:"),
|
|
141
|
+
signal: abortController.signal,
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The result reports `scannedKeys`, `matchedKeys`, and keys confirmed removed by `UNLINK` as
|
|
146
|
+
`unlinkedKeys`. `SCAN` is non-snapshot, so these counts can include duplicates or omit keys while
|
|
147
|
+
the keyspace changes.
|
|
148
|
+
|
|
149
|
+
## Expire Keys Without a TTL
|
|
150
|
+
|
|
151
|
+
Use `expireKeysWithNoExpiry()` to apply a TTL only when matching keys still have no expiry:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { expireKeysWithNoExpiry } from "valkyries";
|
|
155
|
+
|
|
156
|
+
const result = await expireKeysWithNoExpiry(client, {
|
|
157
|
+
pattern: "cache:*",
|
|
158
|
+
ttl: 86_400,
|
|
159
|
+
shouldExpire: (key) => Buffer.from(key).toString("utf8").startsWith("cache:users:"),
|
|
160
|
+
signal: abortController.signal,
|
|
161
|
+
batchSize: 250,
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The result reports `scannedKeys`, `matchedKeys`, and `expiredKeys`. The helper uses `EXPIRE NX`,
|
|
166
|
+
so it never replaces a TTL that was added concurrently. `SCAN` is non-snapshot, so counts can
|
|
167
|
+
include duplicates or omit keys while the keyspace changes. `scanCount` is a SCAN hint, while
|
|
168
|
+
`batchSize` independently bounds each EXPIRE pipeline.
|
|
169
|
+
|
|
116
170
|
## Documentation
|
|
117
171
|
|
|
118
172
|
- [Configuration](docs/configuration.md)
|
|
@@ -121,6 +175,7 @@ Call `closeValkeyClients()` to close package-managed clients.
|
|
|
121
175
|
- [Cache](docs/cache.md)
|
|
122
176
|
- [Bloom filters](docs/bloom-filters.md)
|
|
123
177
|
- [Dynamic config](docs/dynamic-config.md)
|
|
178
|
+
- [Channel pub/sub](docs/channel-pubsub.md)
|
|
124
179
|
- [Rate limiter](docs/rate-limiter.md)
|
|
125
180
|
- [Idempotency keys](docs/idempotency-key.md)
|
|
126
181
|
- [Conditional operations](docs/conditional-operations.md)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { GlideClient, GlideString } from "@valkey/valkey-glide";
|
|
2
|
+
export declare function decrement(counts: Map<string, number>, key: string): void;
|
|
3
|
+
export declare function removePending<T>(pending: Map<string, Set<T>>, key: string, state: T): void;
|
|
4
|
+
export declare function getOrCreateSet<T>(map: Map<string, Set<T>>, key: string): Set<T>;
|
|
5
|
+
export declare function removeHandler<T>(handlers: Map<string, Set<T>>, key: string, handler: T): void;
|
|
6
|
+
export declare function serializeForPublish<T>(serialize: (value: T) => string, value: T, report: (error: unknown) => void): string;
|
|
7
|
+
export declare function toError(error: unknown): Error;
|
|
8
|
+
export declare function reportError(onError: ((error: Error) => void) | undefined, error: unknown): void;
|
|
9
|
+
export declare function glideStringToString(value: GlideString): string;
|
|
10
|
+
export declare function assertChannelPart(name: string, value: string): void;
|
|
11
|
+
export declare function deliver<T>(handler: (value: T) => void, value: T, onError: (error: unknown) => void): void;
|
|
12
|
+
export declare function closeSubscriberClient(client: GlideClient, pattern: string, timeoutMs: number, report: (error: unknown) => void): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=channel-pubsub-internals.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-pubsub-internals.d.mts","sourceRoot":"","sources":["../channel-pubsub-internals.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAErE,wBAAgB,SAAS,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAIxE;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAK1F;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAM/E;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,CAK7F;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,EAC/B,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAC/B,MAAM,CAOR;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAE7C;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,SAAS,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAI/F;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAE9D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAInE;AAED,wBAAgB,OAAO,CAAC,CAAC,EACvB,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,EAC3B,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAChC,IAAI,CAMN;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAC/B,OAAO,CAAC,IAAI,CAAC,CAYf"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export function decrement(counts, key) {
|
|
2
|
+
const count = (counts.get(key) ?? 1) - 1;
|
|
3
|
+
if (count <= 0)
|
|
4
|
+
counts.delete(key);
|
|
5
|
+
else
|
|
6
|
+
counts.set(key, count);
|
|
7
|
+
}
|
|
8
|
+
export function removePending(pending, key, state) {
|
|
9
|
+
const states = pending.get(key);
|
|
10
|
+
if (!states)
|
|
11
|
+
return;
|
|
12
|
+
states.delete(state);
|
|
13
|
+
if (states.size === 0)
|
|
14
|
+
pending.delete(key);
|
|
15
|
+
}
|
|
16
|
+
export function getOrCreateSet(map, key) {
|
|
17
|
+
const existing = map.get(key);
|
|
18
|
+
if (existing)
|
|
19
|
+
return existing;
|
|
20
|
+
const created = new Set();
|
|
21
|
+
map.set(key, created);
|
|
22
|
+
return created;
|
|
23
|
+
}
|
|
24
|
+
export function removeHandler(handlers, key, handler) {
|
|
25
|
+
const active = handlers.get(key);
|
|
26
|
+
if (!active)
|
|
27
|
+
return;
|
|
28
|
+
active.delete(handler);
|
|
29
|
+
if (active.size === 0)
|
|
30
|
+
handlers.delete(key);
|
|
31
|
+
}
|
|
32
|
+
export function serializeForPublish(serialize, value, report) {
|
|
33
|
+
try {
|
|
34
|
+
return serialize(value);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
report(error);
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function toError(error) {
|
|
42
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
43
|
+
}
|
|
44
|
+
export function reportError(onError, error) {
|
|
45
|
+
try {
|
|
46
|
+
onError?.(toError(error));
|
|
47
|
+
}
|
|
48
|
+
catch { }
|
|
49
|
+
}
|
|
50
|
+
export function glideStringToString(value) {
|
|
51
|
+
return typeof value === "string" ? value : value.toString();
|
|
52
|
+
}
|
|
53
|
+
export function assertChannelPart(name, value) {
|
|
54
|
+
if (value.length === 0 || /[?*[\]\\]/.test(value)) {
|
|
55
|
+
throw new Error(`${name} must be non-empty and cannot contain Valkey glob characters`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function deliver(handler, value, onError) {
|
|
59
|
+
try {
|
|
60
|
+
handler(value);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
onError(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export async function closeSubscriberClient(client, pattern, timeoutMs, report) {
|
|
67
|
+
try {
|
|
68
|
+
await client.punsubscribe(new Set([pattern]), timeoutMs);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
report(error);
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
try {
|
|
75
|
+
await Promise.resolve(client.close());
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
report(error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=channel-pubsub-internals.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-pubsub-internals.mjs","sourceRoot":"","sources":["../channel-pubsub-internals.mts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { GlideClient } from "@valkey/valkey-glide";
|
|
2
|
+
type GlideClientConfig = Parameters<typeof GlideClient.createClient>[0];
|
|
3
|
+
export type ChannelPubSubHandler<T> = (value: T) => void;
|
|
4
|
+
export type ChannelSubscription<T> = {
|
|
5
|
+
setHandler(handler: ChannelPubSubHandler<T> | null): void;
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
export type ChannelPubSub<T> = {
|
|
9
|
+
publish(key: string, value: T): Promise<void>;
|
|
10
|
+
subscribe(key: string): Promise<ChannelSubscription<T>>;
|
|
11
|
+
closeSubscriber(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
export type ChannelPubSubOptions<T> = {
|
|
15
|
+
clientConfig: GlideClientConfig;
|
|
16
|
+
createClient?: (config: GlideClientConfig) => Promise<GlideClient>;
|
|
17
|
+
serialize?: (value: T) => string;
|
|
18
|
+
deserialize?: (value: string) => T;
|
|
19
|
+
closeSubscriberWhenIdle?: boolean;
|
|
20
|
+
subscriberCloseTimeoutMs?: number;
|
|
21
|
+
onError?: (error: Error) => void;
|
|
22
|
+
};
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=channel-pubsub-types.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-pubsub-types.d.mts","sourceRoot":"","sources":["../channel-pubsub-types.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;AACxE,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;AAEzD,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI;IACnC,UAAU,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,oBAAoB,CAAC,CAAC,IAAI;IACpC,YAAY,EAAE,iBAAiB,CAAC;IAChC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;IACnE,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC;IACjC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,CAAC,CAAC;IACnC,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-pubsub-types.mjs","sourceRoot":"","sources":["../channel-pubsub-types.mts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ChannelPubSub, ChannelPubSubOptions } from "./channel-pubsub-types.mts";
|
|
2
|
+
export type { ChannelPubSub, ChannelPubSubOptions, ChannelSubscription, } from "./channel-pubsub-types.mts";
|
|
3
|
+
export declare function createChannelPubSub<T>(prefix: string, options: ChannelPubSubOptions<T>): ChannelPubSub<T>;
|
|
4
|
+
//# sourceMappingURL=channel-pubsub.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-pubsub.d.mts","sourceRoot":"","sources":["../channel-pubsub.mts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,aAAa,EAEb,oBAAoB,EAErB,MAAM,4BAA4B,CAAC;AAEpC,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,4BAA4B,CAAC;AAEpC,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC,GAC/B,aAAa,CAAC,CAAC,CAAC,CAqKlB"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { GlideClient, GlideClientConfiguration } from "@valkey/valkey-glide";
|
|
2
|
+
import { assertChannelPart, closeSubscriberClient, decrement, deliver, getOrCreateSet, glideStringToString, removePending, removeHandler, reportError, serializeForPublish, } from "./channel-pubsub-internals.mjs";
|
|
3
|
+
export function createChannelPubSub(prefix, options) {
|
|
4
|
+
assertChannelPart("prefix", prefix);
|
|
5
|
+
if (options.clientConfig.pubsubSubscriptions) {
|
|
6
|
+
throw new Error("clientConfig cannot include pubsubSubscriptions");
|
|
7
|
+
}
|
|
8
|
+
const closeTimeoutMs = options.subscriberCloseTimeoutMs ?? 5_000;
|
|
9
|
+
if (!Number.isSafeInteger(closeTimeoutMs) || closeTimeoutMs <= 0) {
|
|
10
|
+
throw new Error("subscriberCloseTimeoutMs must be a positive safe integer");
|
|
11
|
+
}
|
|
12
|
+
const report = (error) => reportError(options.onError, error);
|
|
13
|
+
const serialize = options.serialize ?? ((value) => JSON.stringify(value));
|
|
14
|
+
const deserialize = options.deserialize ?? ((value) => JSON.parse(value));
|
|
15
|
+
const createClient = options.createClient ?? ((config) => GlideClient.createClient(config));
|
|
16
|
+
const pattern = `${prefix}:*`;
|
|
17
|
+
const handlers = new Map();
|
|
18
|
+
const pending = new Map();
|
|
19
|
+
const counts = new Map();
|
|
20
|
+
let publisher;
|
|
21
|
+
let subscriber;
|
|
22
|
+
let subscriberClose;
|
|
23
|
+
let ownerClose;
|
|
24
|
+
let closed = false;
|
|
25
|
+
const assertOpen = () => {
|
|
26
|
+
if (closed)
|
|
27
|
+
throw new Error("Channel pub/sub is closed");
|
|
28
|
+
};
|
|
29
|
+
const totalCount = () => [...counts.values()].reduce((total, count) => total + count, 0);
|
|
30
|
+
const shutdownSubscriber = () => {
|
|
31
|
+
if (subscriberClose)
|
|
32
|
+
return subscriberClose;
|
|
33
|
+
const current = subscriber;
|
|
34
|
+
subscriber = undefined;
|
|
35
|
+
if (!current)
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
const closePromise = (async () => {
|
|
38
|
+
try {
|
|
39
|
+
await closeSubscriberClient(await current, pattern, closeTimeoutMs, report);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
report(error);
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
subscriberClose = closePromise;
|
|
46
|
+
void closePromise.finally(() => {
|
|
47
|
+
subscriberClose = undefined;
|
|
48
|
+
});
|
|
49
|
+
return closePromise;
|
|
50
|
+
};
|
|
51
|
+
const closeSubscriberIfIdle = () => {
|
|
52
|
+
return options.closeSubscriberWhenIdle && totalCount() === 0
|
|
53
|
+
? shutdownSubscriber()
|
|
54
|
+
: Promise.resolve();
|
|
55
|
+
};
|
|
56
|
+
const getPublisher = () => {
|
|
57
|
+
publisher ??= createClient(options.clientConfig).catch((error) => {
|
|
58
|
+
publisher = undefined;
|
|
59
|
+
throw error;
|
|
60
|
+
});
|
|
61
|
+
return publisher;
|
|
62
|
+
};
|
|
63
|
+
const receive = (message) => {
|
|
64
|
+
const messageChannel = glideStringToString(message.channel);
|
|
65
|
+
if (!messageChannel.startsWith(`${prefix}:`))
|
|
66
|
+
return;
|
|
67
|
+
const key = messageChannel.slice(prefix.length + 1);
|
|
68
|
+
if (!counts.has(key))
|
|
69
|
+
return;
|
|
70
|
+
let value;
|
|
71
|
+
try {
|
|
72
|
+
value = deserialize(glideStringToString(message.message));
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
report(error);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const currentHandlers = new Set(handlers.get(key));
|
|
79
|
+
for (const entry of currentHandlers)
|
|
80
|
+
deliver(entry.handler, value, report);
|
|
81
|
+
for (const state of pending.get(key) ?? [])
|
|
82
|
+
state.buffer.push(value);
|
|
83
|
+
};
|
|
84
|
+
const getSubscriber = async () => {
|
|
85
|
+
if (subscriberClose)
|
|
86
|
+
await subscriberClose;
|
|
87
|
+
assertOpen();
|
|
88
|
+
subscriber ??= createClient({
|
|
89
|
+
...options.clientConfig,
|
|
90
|
+
pubsubSubscriptions: {
|
|
91
|
+
channelsAndPatterns: {
|
|
92
|
+
[GlideClientConfiguration.PubSubChannelModes.Pattern]: new Set([pattern]),
|
|
93
|
+
},
|
|
94
|
+
callback: receive,
|
|
95
|
+
context: undefined,
|
|
96
|
+
},
|
|
97
|
+
}).catch((error) => {
|
|
98
|
+
subscriber = undefined;
|
|
99
|
+
throw error;
|
|
100
|
+
});
|
|
101
|
+
return await subscriber;
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
async publish(key, value) {
|
|
105
|
+
assertOpen();
|
|
106
|
+
assertChannelPart("key", key);
|
|
107
|
+
await (await getPublisher()).publish(serializeForPublish(serialize, value, report), `${prefix}:${key}`);
|
|
108
|
+
},
|
|
109
|
+
async subscribe(key) {
|
|
110
|
+
assertOpen();
|
|
111
|
+
assertChannelPart("key", key);
|
|
112
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
113
|
+
const state = { buffer: [] };
|
|
114
|
+
let handler;
|
|
115
|
+
let closePromise;
|
|
116
|
+
getOrCreateSet(pending, key).add(state);
|
|
117
|
+
const rollback = async () => {
|
|
118
|
+
removePending(pending, key, state);
|
|
119
|
+
if (handler)
|
|
120
|
+
removeHandler(handlers, key, handler);
|
|
121
|
+
decrement(counts, key);
|
|
122
|
+
await closeSubscriberIfIdle();
|
|
123
|
+
};
|
|
124
|
+
try {
|
|
125
|
+
await getSubscriber();
|
|
126
|
+
if (closed)
|
|
127
|
+
throw new Error("Channel pub/sub is closed");
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
await rollback();
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
setHandler(nextHandler) {
|
|
135
|
+
if (closePromise)
|
|
136
|
+
return;
|
|
137
|
+
if (handler)
|
|
138
|
+
removeHandler(handlers, key, handler);
|
|
139
|
+
if (!nextHandler) {
|
|
140
|
+
handler = undefined;
|
|
141
|
+
getOrCreateSet(pending, key).add(state);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
removePending(pending, key, state);
|
|
145
|
+
handler = { handler: nextHandler };
|
|
146
|
+
getOrCreateSet(handlers, key).add(handler);
|
|
147
|
+
for (const value of state.buffer)
|
|
148
|
+
deliver(nextHandler, value, report);
|
|
149
|
+
state.buffer = [];
|
|
150
|
+
},
|
|
151
|
+
close() {
|
|
152
|
+
closePromise ??= rollback();
|
|
153
|
+
return closePromise;
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
},
|
|
157
|
+
closeSubscriber() {
|
|
158
|
+
if (totalCount() > 0) {
|
|
159
|
+
return Promise.reject(new Error("Cannot close subscriber while subscriptions are active"));
|
|
160
|
+
}
|
|
161
|
+
return shutdownSubscriber();
|
|
162
|
+
},
|
|
163
|
+
close() {
|
|
164
|
+
if (ownerClose)
|
|
165
|
+
return ownerClose;
|
|
166
|
+
closed = true;
|
|
167
|
+
ownerClose = (async () => {
|
|
168
|
+
await shutdownSubscriber();
|
|
169
|
+
const current = publisher;
|
|
170
|
+
publisher = undefined;
|
|
171
|
+
if (!current)
|
|
172
|
+
return;
|
|
173
|
+
try {
|
|
174
|
+
await Promise.resolve((await current).close());
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
report(error);
|
|
178
|
+
}
|
|
179
|
+
})();
|
|
180
|
+
return ownerClose;
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=channel-pubsub.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-pubsub.mjs","sourceRoot":"","sources":["../channel-pubsub.mts"],"names":[],"mappings":"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"}
|
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
|
package/dist/delete.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delete.d.mts","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,
|
|
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
|
|
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
|
|
11
|
+
await scanAndUnlinkKeys(client, pattern, {
|
|
12
|
+
matches: (key) => uniquePrefixes.some((prefix) => glideStringToString(key).startsWith(prefix)),
|
|
13
|
+
});
|
|
12
14
|
}
|
|
13
|
-
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
35
|
+
throwIfAborted(signal);
|
|
36
|
+
unlinkedKeys += await client.unlink(keys);
|
|
37
|
+
throwIfAborted(signal);
|
|
24
38
|
}
|
|
25
|
-
}
|
|
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
|
}
|
package/dist/delete.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delete.mjs","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
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"}
|
package/dist/expiry.mjs
ADDED
|
@@ -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
|
|
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
|
package/dist/index.d.mts.map
CHANGED
|
@@ -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,
|
|
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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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"}
|
package/dist/scan.d.mts
ADDED
|
@@ -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;
|
package/dist/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../types.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.mts","sourceRoot":"","sources":["../types.mts"],"names":[],"mappings":"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"}
|
package/docs/api.md
CHANGED
|
@@ -7,6 +7,7 @@ This is the entry point for the public `valkyries` API. Detailed API documentati
|
|
|
7
7
|
- [`ValkeyCache`](cache.md) - read-through cache helpers, direct get/set APIs, invalidation, stale refresh, and Bloom-filter integration.
|
|
8
8
|
- [`ValkeyBloomFilter`](bloom-filters.md) - Bloom filter lookup, writes, readiness markers, streaming rebuilds, and key helpers.
|
|
9
9
|
- [`DynamicConfig`](dynamic-config.md) - distributed runtime fields backed by Valkey hashes and pub/sub updates.
|
|
10
|
+
- [Channel pub/sub](channel-pubsub.md) - ephemeral, caller-managed key-scoped pub/sub.
|
|
10
11
|
- [`RateLimiter`](rate-limiter.md) - sliding-window rate limiting backed by sorted sets and Lua scripts.
|
|
11
12
|
- [Idempotency keys](idempotency-key.md) - consume-once values and token-fenced idempotency-key lifecycle helpers.
|
|
12
13
|
|
|
@@ -29,6 +30,8 @@ import {
|
|
|
29
30
|
getAndDelete,
|
|
30
31
|
reserveIdempotencyKey,
|
|
31
32
|
closeValkeyClients,
|
|
33
|
+
scanAndUnlinkKeys,
|
|
34
|
+
expireKeysWithNoExpiry,
|
|
32
35
|
} from "valkyries";
|
|
33
36
|
```
|
|
34
37
|
|
|
@@ -40,5 +43,6 @@ Supported subpath exports:
|
|
|
40
43
|
- `valkyries/dynamic-config`
|
|
41
44
|
- `valkyries/idempotency-key`
|
|
42
45
|
- `valkyries/rate-limiter`
|
|
46
|
+
- `valkyries/channel-pubsub`
|
|
43
47
|
|
|
44
48
|
`valkyries` is ESM-only and requires Node.js 24 or newer.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Channel Pub/Sub
|
|
2
|
+
|
|
3
|
+
`createChannelPubSub()` creates a dedicated publisher and pattern subscriber for messages scoped by
|
|
4
|
+
an application-defined prefix and key. It is an ephemeral delivery primitive: messages are not
|
|
5
|
+
persisted, and subscribers do not receive messages published before their Valkey subscription is
|
|
6
|
+
active.
|
|
7
|
+
|
|
8
|
+
## Import
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import { createChannelPubSub, type ChannelSubscription } from "valkyries/channel-pubsub";
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This subpath is intentionally not exported from `valkyries`; importing it never initializes a
|
|
15
|
+
package-managed client.
|
|
16
|
+
|
|
17
|
+
## Create a channel
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const events = createChannelPubSub<{ state: string }>("exports", {
|
|
21
|
+
clientConfig: { addresses: [{ host: "localhost", port: 6379 }] },
|
|
22
|
+
onError: (error) => logger.error(error),
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`clientConfig` is passed to `GlideClient.createClient()`. Supply `createClient` to own connection
|
|
27
|
+
creation, for example when an application injects clients in tests. `serialize` and `deserialize`
|
|
28
|
+
default to JSON. `onError` receives codec, handler, and subscriber-shutdown failures; publish and
|
|
29
|
+
initial subscriber-connection failures reject their calling promise.
|
|
30
|
+
|
|
31
|
+
`clientConfig` must not include `pubsubSubscriptions`; this API owns the subscriber's pattern and
|
|
32
|
+
callback. `onError` failures are contained so consumer and cleanup isolation is preserved.
|
|
33
|
+
|
|
34
|
+
Prefixes and keys must be non-empty and cannot contain Valkey glob characters (`?`, `*`, `[`, `]`,
|
|
35
|
+
or `\`). This prevents a prefix from widening the subscriber pattern unintentionally.
|
|
36
|
+
|
|
37
|
+
`closeSubscriberWhenIdle` closes the subscriber when the final subscription closes. Set
|
|
38
|
+
`subscriberCloseTimeoutMs` to a positive safe integer to bound its `PUNSUBSCRIBE` request; it
|
|
39
|
+
defaults to `5000`.
|
|
40
|
+
|
|
41
|
+
## Use and close
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const subscription: ChannelSubscription<{ state: string }> = await events.subscribe(exportId);
|
|
45
|
+
subscription.setHandler((event) => send(event));
|
|
46
|
+
await events.publish(exportId, { state: "ready" });
|
|
47
|
+
await subscription.close();
|
|
48
|
+
await events.close();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Subscriptions buffer messages received before a handler is installed and replay them when it is.
|
|
52
|
+
Multiple subscriptions for one key each receive the message. Calling `setHandler()` replaces the
|
|
53
|
+
previous handler. Both subscription `close()` and owner `close()` are idempotent. Owner `close()`
|
|
54
|
+
also closes the lazy publisher; future `publish()` and `subscribe()` calls reject.
|
|
55
|
+
|
|
56
|
+
`closeSubscriber()` closes only an idle subscriber connection. It rejects while subscriptions are
|
|
57
|
+
active, preventing live handles from silently losing delivery. `close()` is the explicit owner
|
|
58
|
+
shutdown operation and closes active subscriptions' connection safely; it coalesces concurrent
|
|
59
|
+
callers until cleanup completes.
|
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.
|
|
3
|
+
"version": "0.8.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": {
|
|
@@ -32,6 +32,11 @@
|
|
|
32
32
|
"import": "./dist/cache.mjs",
|
|
33
33
|
"default": "./dist/cache.mjs"
|
|
34
34
|
},
|
|
35
|
+
"./channel-pubsub": {
|
|
36
|
+
"types": "./dist/channel-pubsub.d.mts",
|
|
37
|
+
"import": "./dist/channel-pubsub.mjs",
|
|
38
|
+
"default": "./dist/channel-pubsub.mjs"
|
|
39
|
+
},
|
|
35
40
|
"./conditional": {
|
|
36
41
|
"types": "./dist/conditional.d.mts",
|
|
37
42
|
"import": "./dist/conditional.mjs",
|
|
@@ -65,9 +70,9 @@
|
|
|
65
70
|
"@types/node": "^26.1.0",
|
|
66
71
|
"@vitest/coverage-v8": "^4.1.6",
|
|
67
72
|
"github-actionlint": "^1.7.12",
|
|
68
|
-
"oxfmt": "^0.
|
|
73
|
+
"oxfmt": "^0.63.0",
|
|
69
74
|
"oxlint": "^1.65.0",
|
|
70
|
-
"oxlint-tsgolint": "^0.
|
|
75
|
+
"oxlint-tsgolint": "^7.0.2001",
|
|
71
76
|
"tsx": "^4.22.1",
|
|
72
77
|
"typescript": "^7.0.2",
|
|
73
78
|
"vitest": "^4.1.6"
|