valkyries 0.1.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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/bloom-filter/batching.d.mts +3 -0
- package/dist/bloom-filter/batching.d.mts.map +1 -0
- package/dist/bloom-filter/batching.mjs +18 -0
- package/dist/bloom-filter/batching.mjs.map +1 -0
- package/dist/bloom-filter/lookups.d.mts +6 -0
- package/dist/bloom-filter/lookups.d.mts.map +1 -0
- package/dist/bloom-filter/lookups.mjs +101 -0
- package/dist/bloom-filter/lookups.mjs.map +1 -0
- package/dist/bloom-filter/rebuilds.d.mts +9 -0
- package/dist/bloom-filter/rebuilds.d.mts.map +1 -0
- package/dist/bloom-filter/rebuilds.mjs +66 -0
- package/dist/bloom-filter/rebuilds.mjs.map +1 -0
- package/dist/bloom-filter/results.d.mts +3 -0
- package/dist/bloom-filter/results.d.mts.map +1 -0
- package/dist/bloom-filter/results.mjs +23 -0
- package/dist/bloom-filter/results.mjs.map +1 -0
- package/dist/bloom-filter/scripts.d.mts +9 -0
- package/dist/bloom-filter/scripts.d.mts.map +1 -0
- package/dist/bloom-filter/scripts.mjs +10 -0
- package/dist/bloom-filter/scripts.mjs.map +1 -0
- package/dist/bloom-filter/types.d.mts +12 -0
- package/dist/bloom-filter/types.d.mts.map +1 -0
- package/dist/bloom-filter/types.mjs +2 -0
- package/dist/bloom-filter/types.mjs.map +1 -0
- package/dist/bloom-filter/writes.d.mts +5 -0
- package/dist/bloom-filter/writes.d.mts.map +1 -0
- package/dist/bloom-filter/writes.mjs +54 -0
- package/dist/bloom-filter/writes.mjs.map +1 -0
- package/dist/bloom-filter.d.mts +90 -0
- package/dist/bloom-filter.d.mts.map +1 -0
- package/dist/bloom-filter.mjs +154 -0
- package/dist/bloom-filter.mjs.map +1 -0
- package/dist/cache/batch-read.d.mts +7 -0
- package/dist/cache/batch-read.d.mts.map +1 -0
- package/dist/cache/batch-read.mjs +142 -0
- package/dist/cache/batch-read.mjs.map +1 -0
- package/dist/cache/constants.d.mts +7 -0
- package/dist/cache/constants.d.mts.map +1 -0
- package/dist/cache/constants.mjs +8 -0
- package/dist/cache/constants.mjs.map +1 -0
- package/dist/cache/core.d.mts +38 -0
- package/dist/cache/core.d.mts.map +1 -0
- package/dist/cache/core.mjs +171 -0
- package/dist/cache/core.mjs.map +1 -0
- package/dist/cache/deletes.d.mts +8 -0
- package/dist/cache/deletes.d.mts.map +1 -0
- package/dist/cache/deletes.mjs +44 -0
- package/dist/cache/deletes.mjs.map +1 -0
- package/dist/cache/mutations.d.mts +25 -0
- package/dist/cache/mutations.d.mts.map +1 -0
- package/dist/cache/mutations.mjs +181 -0
- package/dist/cache/mutations.mjs.map +1 -0
- package/dist/cache/set-if-not-invalidated-events.d.mts +4 -0
- package/dist/cache/set-if-not-invalidated-events.d.mts.map +1 -0
- package/dist/cache/set-if-not-invalidated-events.mjs +21 -0
- package/dist/cache/set-if-not-invalidated-events.mjs.map +1 -0
- package/dist/cache/single-read.d.mts +11 -0
- package/dist/cache/single-read.d.mts.map +1 -0
- package/dist/cache/single-read.mjs +73 -0
- package/dist/cache/single-read.mjs.map +1 -0
- package/dist/cache/stale-refresh.d.mts +15 -0
- package/dist/cache/stale-refresh.d.mts.map +1 -0
- package/dist/cache/stale-refresh.mjs +70 -0
- package/dist/cache/stale-refresh.mjs.map +1 -0
- package/dist/cache-metrics.d.mts +10 -0
- package/dist/cache-metrics.d.mts.map +1 -0
- package/dist/cache-metrics.mjs +12 -0
- package/dist/cache-metrics.mjs.map +1 -0
- package/dist/cache-utils.d.mts +20 -0
- package/dist/cache-utils.d.mts.map +1 -0
- package/dist/cache-utils.mjs +67 -0
- package/dist/cache-utils.mjs.map +1 -0
- package/dist/cache.d.mts +4 -0
- package/dist/cache.d.mts.map +1 -0
- package/dist/cache.mjs +4 -0
- package/dist/cache.mjs.map +1 -0
- package/dist/clients.d.mts +51 -0
- package/dist/clients.d.mts.map +1 -0
- package/dist/clients.mjs +95 -0
- package/dist/clients.mjs.map +1 -0
- package/dist/config.d.mts +6 -0
- package/dist/config.d.mts.map +1 -0
- package/dist/config.mjs +10 -0
- package/dist/config.mjs.map +1 -0
- package/dist/delete.d.mts +3 -0
- package/dist/delete.d.mts.map +1 -0
- package/dist/delete.mjs +23 -0
- package/dist/delete.mjs.map +1 -0
- package/dist/dynamic-config/fields.d.mts +6 -0
- package/dist/dynamic-config/fields.d.mts.map +1 -0
- package/dist/dynamic-config/fields.mjs +50 -0
- package/dist/dynamic-config/fields.mjs.map +1 -0
- package/dist/dynamic-config/storage.d.mts +33 -0
- package/dist/dynamic-config/storage.d.mts.map +1 -0
- package/dist/dynamic-config/storage.mjs +55 -0
- package/dist/dynamic-config/storage.mjs.map +1 -0
- package/dist/dynamic-config.d.mts +34 -0
- package/dist/dynamic-config.d.mts.map +1 -0
- package/dist/dynamic-config.mjs +173 -0
- package/dist/dynamic-config.mjs.map +1 -0
- package/dist/errors.d.mts +4 -0
- package/dist/errors.d.mts.map +1 -0
- package/dist/errors.mjs +15 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/events.d.mts +84 -0
- package/dist/events.d.mts.map +1 -0
- package/dist/events.mjs +18 -0
- package/dist/events.mjs.map +1 -0
- package/dist/index.d.mts +14 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +13 -0
- package/dist/index.mjs.map +1 -0
- package/dist/key-normalization.d.mts +2 -0
- package/dist/key-normalization.d.mts.map +1 -0
- package/dist/key-normalization.mjs +4 -0
- package/dist/key-normalization.mjs.map +1 -0
- package/dist/rate-limiter.d.mts +40 -0
- package/dist/rate-limiter.d.mts.map +1 -0
- package/dist/rate-limiter.mjs +123 -0
- package/dist/rate-limiter.mjs.map +1 -0
- package/dist/scripts/bloom-filter-add.lua +26 -0
- package/dist/scripts/bloom-filter-ensure-exists.lua +13 -0
- package/dist/scripts/bloom-filter-exists-if-ready.lua +14 -0
- package/dist/scripts/bloom-filter-exists.lua +14 -0
- package/dist/scripts/bloom-filter-mexists-if-ready.lua +19 -0
- package/dist/scripts/bloom-filter-mexists.lua +17 -0
- package/dist/scripts/bloom-filter-reserve.lua +11 -0
- package/dist/scripts/cache-delete-with-invalidation.lua +29 -0
- package/dist/scripts/cache-set-if-not-invalidated.lua +47 -0
- package/dist/scripts/dynamic-config-set-fields.lua +23 -0
- package/dist/scripts/get-value-with-ttl.lua +35 -0
- package/dist/scripts/get-values-with-ttl.lua +81 -0
- package/dist/scripts/rate-limiter-add-and-check.lua +34 -0
- package/dist/scripts/rate-limiter-add.lua +26 -0
- package/dist/scripts/rate-limiter-get.lua +22 -0
- package/dist/scripts.d.mts +4 -0
- package/dist/scripts.d.mts.map +1 -0
- package/dist/scripts.mjs +31 -0
- package/dist/scripts.mjs.map +1 -0
- package/dist/shutdown.d.mts +3 -0
- package/dist/shutdown.d.mts.map +1 -0
- package/dist/shutdown.mjs +27 -0
- package/dist/shutdown.mjs.map +1 -0
- package/dist/types.d.mts +76 -0
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +2 -0
- package/dist/types.mjs.map +1 -0
- package/dist/utils.d.mts +16 -0
- package/dist/utils.d.mts.map +1 -0
- package/dist/utils.mjs +35 -0
- package/dist/utils.mjs.map +1 -0
- package/docs/bloom-filters.md +12 -0
- package/docs/cache.md +19 -0
- package/docs/clients.md +13 -0
- package/docs/configuration.md +16 -0
- package/docs/dynamic-config.md +13 -0
- package/docs/events-and-metrics.md +15 -0
- package/docs/lua-scripts.md +7 -0
- package/docs/migration.md +74 -0
- package/docs/rate-limiter.md +17 -0
- package/docs/testing-and-ci.md +21 -0
- package/package.json +80 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-config.mjs","sourceRoot":"","sources":["../dynamic-config.mts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.mts","sourceRoot":"","sources":["../errors.mts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AASxD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAEvE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAGtD"}
|
package/dist/errors.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* v8 ignore next 4 -- default process-level fallback is replaced in tests and by applications. */
|
|
2
|
+
let valkeyErrorHandler = (error) => {
|
|
3
|
+
if (process.env.NODE_ENV === "test")
|
|
4
|
+
return;
|
|
5
|
+
// oxlint-disable-next-line no-console -- default OSS error handler
|
|
6
|
+
console.error(error);
|
|
7
|
+
};
|
|
8
|
+
export function setValkeyErrorHandler(handler) {
|
|
9
|
+
valkeyErrorHandler = handler;
|
|
10
|
+
}
|
|
11
|
+
export function handleValkeyError(error) {
|
|
12
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
13
|
+
valkeyErrorHandler(normalized);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=errors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.mjs","sourceRoot":"","sources":["../errors.mts"],"names":[],"mappings":"AAEA,kGAAkG;AAClG,IAAI,kBAAkB,GAAuB,CAAC,KAAK,EAAE,EAAE;IACrD,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM;QAAE,OAAO;IAC5C,mEAAmE;IACnE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC,CAAC;AAEF,MAAM,UAAU,qBAAqB,CAAC,OAA2B;IAC/D,kBAAkB,GAAG,OAAO,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,MAAM,UAAU,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7E,kBAAkB,CAAC,UAAU,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
export interface ValkeyEventMap {
|
|
3
|
+
"cache:call": [
|
|
4
|
+
{
|
|
5
|
+
cacheName: string;
|
|
6
|
+
batch: boolean;
|
|
7
|
+
hits: number;
|
|
8
|
+
misses: number;
|
|
9
|
+
bloomMisses: number;
|
|
10
|
+
durationMs: number;
|
|
11
|
+
}
|
|
12
|
+
];
|
|
13
|
+
"rate-limiter:add": [{
|
|
14
|
+
prefix: string;
|
|
15
|
+
ids: string[];
|
|
16
|
+
}];
|
|
17
|
+
"rate-limiter:get": [{
|
|
18
|
+
prefix: string;
|
|
19
|
+
ids: string[];
|
|
20
|
+
counts: number[];
|
|
21
|
+
}];
|
|
22
|
+
"rate-limiter:delete": [{
|
|
23
|
+
prefix: string;
|
|
24
|
+
ids: string[];
|
|
25
|
+
}];
|
|
26
|
+
"rate-limiter:invalidate": [{
|
|
27
|
+
prefix: string;
|
|
28
|
+
}];
|
|
29
|
+
"cache:hit": [{
|
|
30
|
+
cacheName: string;
|
|
31
|
+
keys: string[];
|
|
32
|
+
count: number;
|
|
33
|
+
}];
|
|
34
|
+
"cache:miss": [{
|
|
35
|
+
cacheName: string;
|
|
36
|
+
keys: string[];
|
|
37
|
+
count: number;
|
|
38
|
+
}];
|
|
39
|
+
"cache:bloom-miss": [{
|
|
40
|
+
cacheName: string;
|
|
41
|
+
keys: string[];
|
|
42
|
+
count: number;
|
|
43
|
+
}];
|
|
44
|
+
"cache:set": [{
|
|
45
|
+
cacheName: string;
|
|
46
|
+
keys: string[];
|
|
47
|
+
}];
|
|
48
|
+
"cache:set-skipped": [{
|
|
49
|
+
cacheName: string;
|
|
50
|
+
keys: string[];
|
|
51
|
+
}];
|
|
52
|
+
"cache:delete": [{
|
|
53
|
+
cacheName: string;
|
|
54
|
+
keys: string[];
|
|
55
|
+
}];
|
|
56
|
+
"cache:invalidate": [{
|
|
57
|
+
cacheName: string;
|
|
58
|
+
}];
|
|
59
|
+
"bloom-filter:exists": [{
|
|
60
|
+
name: string;
|
|
61
|
+
item: string;
|
|
62
|
+
result: boolean | null;
|
|
63
|
+
}];
|
|
64
|
+
"bloom-filter:mexists": [{
|
|
65
|
+
name: string;
|
|
66
|
+
items: string[];
|
|
67
|
+
results: (boolean | null)[];
|
|
68
|
+
}];
|
|
69
|
+
/**
|
|
70
|
+
* Emitted after each write to the filter:
|
|
71
|
+
* - `add()`: fires once with all items after all internal batches complete (aggregate event)
|
|
72
|
+
* - `addStream()` and `rebuildFromStream()`: fires once per batch chunk
|
|
73
|
+
* During a rebuild, items are written to a staging key and not yet visible in the live
|
|
74
|
+
* filter until the atomic rename completes — do not treat this event as confirmation of
|
|
75
|
+
* live filter membership.
|
|
76
|
+
*/
|
|
77
|
+
"bloom-filter:add": [{
|
|
78
|
+
name: string;
|
|
79
|
+
items: string[];
|
|
80
|
+
}];
|
|
81
|
+
}
|
|
82
|
+
export declare const valkeyEvents: EventEmitter<ValkeyEventMap>;
|
|
83
|
+
export declare function emitValkeyEvent<K extends keyof ValkeyEventMap>(event: K, ...args: ValkeyEventMap[K]): void;
|
|
84
|
+
//# sourceMappingURL=events.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.mts","sourceRoot":"","sources":["../events.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE;QACZ;YACE,SAAS,EAAE,MAAM,CAAC;YAClB,KAAK,EAAE,OAAO,CAAC;YACf,IAAI,EAAE,MAAM,CAAC;YACb,MAAM,EAAE,MAAM,CAAC;YACf,WAAW,EAAE,MAAM,CAAC;YACpB,UAAU,EAAE,MAAM,CAAC;SACpB;KACF,CAAC;IACF,kBAAkB,EAAE,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACxD,kBAAkB,EAAE,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC1E,qBAAqB,EAAE,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC3D,yBAAyB,EAAE,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,WAAW,EAAE,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,YAAY,EAAE,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrE,kBAAkB,EAAE,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3E,WAAW,EAAE,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACrD,mBAAmB,EAAE,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC7D,cAAc,EAAE,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACxD,kBAAkB,EAAE,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5C,qBAAqB,EAAE,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAChF,sBAAsB,EAAE,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAAA;KAAE,CAAC,CAAC;IACzF;;;;;;;OAOG;IACH,kBAAkB,EAAE,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACzD;AAED,eAAO,MAAM,YAAY,8BAAqC,CAAC;AAO/D,wBAAgB,eAAe,CAAC,CAAC,SAAS,MAAM,cAAc,EAC5D,KAAK,EAAE,CAAC,EACR,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,QAQ3B"}
|
package/dist/events.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { handleValkeyError } from "./errors.mjs";
|
|
3
|
+
export const valkeyEvents = new EventEmitter();
|
|
4
|
+
/* v8 ignore next -- NODE_ENV is controlled by the test runner. */
|
|
5
|
+
if (process.env.NODE_ENV === "test") {
|
|
6
|
+
// Allow up to 1000 listeners — tests register multiple per-event handlers across parallel workers.
|
|
7
|
+
valkeyEvents.setMaxListeners(1000);
|
|
8
|
+
}
|
|
9
|
+
export function emitValkeyEvent(event, ...args) {
|
|
10
|
+
try {
|
|
11
|
+
// Cast needed: TypeScript can't resolve conditional types in EventEmitter.emit for generic K
|
|
12
|
+
valkeyEvents.emit(event, ...args);
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
handleValkeyError(err);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=events.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.mjs","sourceRoot":"","sources":["../events.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAoCjD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,YAAY,EAAkB,CAAC;AAC/D,kEAAkE;AAClE,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;IACpC,mGAAmG;IACnG,YAAY,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,KAAQ,EACR,GAAG,IAAuB;IAE1B,IAAI,CAAC;QACH,6FAA6F;QAC7F,YAAY,CAAC,IAAI,CAAC,KAAY,EAAE,GAAG,IAAI,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;AACH,CAAC"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./events.mts";
|
|
2
|
+
export * from "./rate-limiter.mts";
|
|
3
|
+
export * from "./cache.mts";
|
|
4
|
+
export * from "./bloom-filter.mts";
|
|
5
|
+
export * from "./dynamic-config.mts";
|
|
6
|
+
export * from "./clients.mts";
|
|
7
|
+
export * from "./shutdown.mts";
|
|
8
|
+
export * from "./scripts.mts";
|
|
9
|
+
export * from "./utils.mts";
|
|
10
|
+
export * from "./errors.mts";
|
|
11
|
+
export * from "./key-normalization.mts";
|
|
12
|
+
export * from "./cache-metrics.mts";
|
|
13
|
+
export type { RateLimiterOptions, ValkeyBloomFilterOptions, ValkeyCacheOptions, ValkeyCacheMode, ValkeyCacheResponse, DynamicConfigField, DynamicConfigFieldType, DynamicConfigOptions, } from "./types.mts";
|
|
14
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +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,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,YAAY,EACV,kBAAkB,EAClB,wBAAwB,EACxB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,aAAa,CAAC"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from "./events.mjs";
|
|
2
|
+
export * from "./rate-limiter.mjs";
|
|
3
|
+
export * from "./cache.mjs";
|
|
4
|
+
export * from "./bloom-filter.mjs";
|
|
5
|
+
export * from "./dynamic-config.mjs";
|
|
6
|
+
export * from "./clients.mjs";
|
|
7
|
+
export * from "./shutdown.mjs";
|
|
8
|
+
export * from "./scripts.mjs";
|
|
9
|
+
export * from "./utils.mjs";
|
|
10
|
+
export * from "./errors.mjs";
|
|
11
|
+
export * from "./key-normalization.mjs";
|
|
12
|
+
export * from "./cache-metrics.mjs";
|
|
13
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-normalization.d.mts","sourceRoot":"","sources":["../key-normalization.mts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-normalization.mjs","sourceRoot":"","sources":["../key-normalization.mts"],"names":[],"mappings":"AAAA,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { RateLimiterOptions } from "./types.mts";
|
|
2
|
+
import type { GlideClient } from "@valkey/valkey-glide";
|
|
3
|
+
export declare class RateLimiter {
|
|
4
|
+
prefix: string;
|
|
5
|
+
ttl: number;
|
|
6
|
+
private client;
|
|
7
|
+
constructor({ prefix, ttlSeconds, client }: RateLimiterOptions);
|
|
8
|
+
add(ids: string[]): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Atomically records this request and checks whether any ID exceeds the threshold.
|
|
11
|
+
* Returns `limited: true` when any post-add count >= threshold.
|
|
12
|
+
*
|
|
13
|
+
* NOTE: Unlike the old isRateLimited() + add() pattern (which only added on success),
|
|
14
|
+
* every call — including rejected ones — increments the counter. This means persistent
|
|
15
|
+
* abusers accumulate a larger count and their rate-limit window keeps refreshing,
|
|
16
|
+
* making the limiter more aggressive against sustained abuse.
|
|
17
|
+
*
|
|
18
|
+
* Falsy ids are silently filtered; `counts` aligns with the filtered ids, not the
|
|
19
|
+
* original array. Pre-filter ids before calling to maintain positional alignment.
|
|
20
|
+
*/
|
|
21
|
+
addAndCheck(ids: string[], threshold: number, ttlSeconds?: number): Promise<{
|
|
22
|
+
counts: number[];
|
|
23
|
+
limited: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Returns true if any ID is at or above the threshold within the TTL window.
|
|
27
|
+
* Falsy ids are silently filtered (delegates to get()).
|
|
28
|
+
*/
|
|
29
|
+
isRateLimited(ids: string[], threshold: number, ttlSeconds?: number): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* Returns the current request count for each ID within the TTL window.
|
|
32
|
+
* Falsy ids are silently filtered; returned counts align to filtered ids, not the input array.
|
|
33
|
+
*/
|
|
34
|
+
get(ids: string[], ttlSeconds?: number): Promise<number[]>;
|
|
35
|
+
delete(...ids: string[]): Promise<number>;
|
|
36
|
+
getKey(key: string): string;
|
|
37
|
+
invalidate(): Promise<void>;
|
|
38
|
+
static invalidate(prefix: string, client?: GlideClient): Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=rate-limiter.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.mts","sourceRoot":"","sources":["../rate-limiter.mts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAcxD,qBAAa,WAAW;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,MAAM,CAAc;gBAEhB,EAAE,MAAM,EAAE,UAAU,EAAE,MAAgC,EAAE,EAAE,kBAAkB;IAMlF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE;IAUvB;;;;;;;;;;;OAWG;IACG,WAAW,CACf,GAAG,EAAE,MAAM,EAAE,EACb,SAAS,EAAE,MAAM,EACjB,UAAU,SAAW,GACpB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IA8BlD;;;OAGG;IACG,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,SAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAK9F;;;OAGG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,UAAU,SAAW,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiB5D,MAAM,CAAC,GAAG,GAAG,EAAE,MAAM,EAAE;IAU7B,MAAM,CAAC,GAAG,EAAE,MAAM;IAIlB,UAAU;WAIG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,cAA0B;CAQzE"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { rateLimiterValkeyClient } from "./clients.mjs";
|
|
2
|
+
import { loadScript, registerScript } from "./scripts.mjs";
|
|
3
|
+
import { deleteKeysWithPrefix } from "./delete.mjs";
|
|
4
|
+
import { emitValkeyEvent } from "./events.mjs";
|
|
5
|
+
import { normalizeCountResult } from "./utils.mjs";
|
|
6
|
+
import { handleValkeyError } from "./errors.mjs";
|
|
7
|
+
const NAMESPACE = "rate-limiter";
|
|
8
|
+
const rateLimiterAddScript = registerScript(loadScript("rate-limiter-add.lua", import.meta.url));
|
|
9
|
+
const rateLimiterGetScript = registerScript(loadScript("rate-limiter-get.lua", import.meta.url));
|
|
10
|
+
const rateLimiterAddAndCheckScript = registerScript(loadScript("rate-limiter-add-and-check.lua", import.meta.url));
|
|
11
|
+
export class RateLimiter {
|
|
12
|
+
prefix;
|
|
13
|
+
ttl;
|
|
14
|
+
client;
|
|
15
|
+
constructor({ prefix, ttlSeconds, client = rateLimiterValkeyClient }) {
|
|
16
|
+
this.prefix = prefix;
|
|
17
|
+
this.ttl = ttlSeconds;
|
|
18
|
+
this.client = client;
|
|
19
|
+
}
|
|
20
|
+
async add(ids) {
|
|
21
|
+
const filteredIds = ids.filter(Boolean);
|
|
22
|
+
if (filteredIds.length === 0)
|
|
23
|
+
return;
|
|
24
|
+
const keys = filteredIds.map((id) => this.getKey(id));
|
|
25
|
+
// Single script call for all keys (server time is used in script)
|
|
26
|
+
const args = [this.ttl.toString(), ...keys.map(() => Math.random().toString())];
|
|
27
|
+
await this.client.invokeScript(rateLimiterAddScript, { keys, args });
|
|
28
|
+
emitValkeyEvent("rate-limiter:add", { prefix: this.prefix, ids: filteredIds });
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Atomically records this request and checks whether any ID exceeds the threshold.
|
|
32
|
+
* Returns `limited: true` when any post-add count >= threshold.
|
|
33
|
+
*
|
|
34
|
+
* NOTE: Unlike the old isRateLimited() + add() pattern (which only added on success),
|
|
35
|
+
* every call — including rejected ones — increments the counter. This means persistent
|
|
36
|
+
* abusers accumulate a larger count and their rate-limit window keeps refreshing,
|
|
37
|
+
* making the limiter more aggressive against sustained abuse.
|
|
38
|
+
*
|
|
39
|
+
* Falsy ids are silently filtered; `counts` aligns with the filtered ids, not the
|
|
40
|
+
* original array. Pre-filter ids before calling to maintain positional alignment.
|
|
41
|
+
*/
|
|
42
|
+
async addAndCheck(ids, threshold, ttlSeconds = this.ttl) {
|
|
43
|
+
const filteredIds = ids.filter(Boolean);
|
|
44
|
+
if (filteredIds.length === 0)
|
|
45
|
+
return { counts: [], limited: false };
|
|
46
|
+
const keys = filteredIds.map((id) => this.getKey(id));
|
|
47
|
+
const args = [ttlSeconds.toString(), ...keys.map(() => Math.random().toString())];
|
|
48
|
+
const results = await this.client.invokeScript(rateLimiterAddAndCheckScript, {
|
|
49
|
+
keys,
|
|
50
|
+
args,
|
|
51
|
+
});
|
|
52
|
+
// Valkey should always return an array. If not, the Lua script likely ran (atomic scripts
|
|
53
|
+
// either complete or error), but the return type is unexpected so we can't trust it.
|
|
54
|
+
// Fail open (allow the request). Skip events since the Valkey response is unreliable;
|
|
55
|
+
// return zeros so callers get a length-aligned array.
|
|
56
|
+
// This error path requires mocking invokeScript to test directly.
|
|
57
|
+
/* v8 ignore next 5 -- malformed script return requires a mocked corrupted Valkey response. */
|
|
58
|
+
if (!Array.isArray(results)) {
|
|
59
|
+
handleValkeyError(new Error(`addAndCheck: unexpected Valkey response type ${typeof results}`));
|
|
60
|
+
return { counts: filteredIds.map(() => 0), limited: false };
|
|
61
|
+
}
|
|
62
|
+
const counts = results.map((result) => normalizeCountResult(result));
|
|
63
|
+
const limited = counts.some((count) => count >= threshold);
|
|
64
|
+
// Both events fire even when limited=true, since ZADD always runs for every call.
|
|
65
|
+
// (Events are suppressed only on the fail-open path above where counts are unreliable.)
|
|
66
|
+
emitValkeyEvent("rate-limiter:add", { prefix: this.prefix, ids: filteredIds });
|
|
67
|
+
emitValkeyEvent("rate-limiter:get", { prefix: this.prefix, ids: filteredIds, counts });
|
|
68
|
+
return { counts, limited };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Returns true if any ID is at or above the threshold within the TTL window.
|
|
72
|
+
* Falsy ids are silently filtered (delegates to get()).
|
|
73
|
+
*/
|
|
74
|
+
async isRateLimited(ids, threshold, ttlSeconds = this.ttl) {
|
|
75
|
+
const results = await this.get(ids, ttlSeconds);
|
|
76
|
+
return results.some((result) => result >= threshold);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Returns the current request count for each ID within the TTL window.
|
|
80
|
+
* Falsy ids are silently filtered; returned counts align to filtered ids, not the input array.
|
|
81
|
+
*/
|
|
82
|
+
async get(ids, ttlSeconds = this.ttl) {
|
|
83
|
+
const filteredIds = ids.filter(Boolean);
|
|
84
|
+
if (filteredIds.length === 0)
|
|
85
|
+
return [];
|
|
86
|
+
const keys = filteredIds.map((id) => this.getKey(id));
|
|
87
|
+
// Single read-only script call for all keys (server time is used in script)
|
|
88
|
+
// Rate limiter checks need primary reads to avoid replica lag causing stale counts.
|
|
89
|
+
const results = await this.client.invokeScript(rateLimiterGetScript, {
|
|
90
|
+
keys,
|
|
91
|
+
args: [ttlSeconds.toString()],
|
|
92
|
+
});
|
|
93
|
+
// Results are an array of numbers from ZCOUNT; return zeros on unexpected type (fail open)
|
|
94
|
+
if (!Array.isArray(results))
|
|
95
|
+
return filteredIds.map(() => 0);
|
|
96
|
+
const counts = results.map((result) => normalizeCountResult(result));
|
|
97
|
+
emitValkeyEvent("rate-limiter:get", { prefix: this.prefix, ids: filteredIds, counts });
|
|
98
|
+
return counts;
|
|
99
|
+
}
|
|
100
|
+
async delete(...ids) {
|
|
101
|
+
if (ids.length === 0)
|
|
102
|
+
return 0;
|
|
103
|
+
const filteredIds = ids.filter(Boolean);
|
|
104
|
+
if (filteredIds.length === 0)
|
|
105
|
+
return 0;
|
|
106
|
+
const keys = filteredIds.map((id) => this.getKey(id));
|
|
107
|
+
const count = await this.client.unlink(keys);
|
|
108
|
+
emitValkeyEvent("rate-limiter:delete", { prefix: this.prefix, ids: filteredIds });
|
|
109
|
+
return count;
|
|
110
|
+
}
|
|
111
|
+
getKey(key) {
|
|
112
|
+
return `${NAMESPACE}:${this.prefix}:{${key}}`;
|
|
113
|
+
}
|
|
114
|
+
invalidate() {
|
|
115
|
+
return RateLimiter.invalidate(this.prefix, this.client);
|
|
116
|
+
}
|
|
117
|
+
static async invalidate(prefix, client = rateLimiterValkeyClient) {
|
|
118
|
+
const result = await deleteKeysWithPrefix(client, prefix ? `${NAMESPACE}:${prefix}:*` : `${NAMESPACE}:*`);
|
|
119
|
+
emitValkeyEvent("rate-limiter:invalidate", { prefix });
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=rate-limiter.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.mjs","sourceRoot":"","sources":["../rate-limiter.mts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
-- Add items to the live bloom filter and, if an active rebuild is running,
|
|
2
|
+
-- also to the building key. No-op if neither key exists (post-#1955) so a
|
|
3
|
+
-- missing filter stays missing and cache reads fall back to the DB rather
|
|
4
|
+
-- than to an empty / under-provisioned auto-created filter.
|
|
5
|
+
-- This script is write-only and must be executed using the write client.
|
|
6
|
+
-- KEYS[1] = live key
|
|
7
|
+
-- KEYS[2] = building key
|
|
8
|
+
-- ARGV[1..n] = items to add
|
|
9
|
+
-- Returns: 1 if any write occurred, 0 otherwise.
|
|
10
|
+
if #ARGV == 0 then
|
|
11
|
+
return 0
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
local wrote = 0
|
|
15
|
+
|
|
16
|
+
if redis.call('EXISTS', KEYS[1]) == 1 then
|
|
17
|
+
redis.call('BF.MADD', KEYS[1], unpack(ARGV))
|
|
18
|
+
wrote = 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if redis.call('EXISTS', KEYS[2]) == 1 then
|
|
22
|
+
redis.call('BF.MADD', KEYS[2], unpack(ARGV))
|
|
23
|
+
wrote = 1
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
return wrote
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Conditionally create a bloom filter only if the key does not already exist.
|
|
2
|
+
-- Unlike calling BF.RESERVE directly, this avoids a server-side "item exists"
|
|
3
|
+
-- error (which the Valkey Glide logger surfaces as a WARN even when caught in JS).
|
|
4
|
+
-- KEYS[1] = bloom filter key
|
|
5
|
+
-- ARGV[1] = error_rate (e.g. "0.01")
|
|
6
|
+
-- ARGV[2] = capacity (integer as string)
|
|
7
|
+
-- ARGV[3] = expansion rate (integer as string)
|
|
8
|
+
local key = KEYS[1]
|
|
9
|
+
if redis.call('EXISTS', key) == 1 then
|
|
10
|
+
return 0
|
|
11
|
+
end
|
|
12
|
+
redis.call('BF.RESERVE', key, ARGV[1], ARGV[2], 'EXPANSION', ARGV[3])
|
|
13
|
+
return 1
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Check a ready marker, bloom filter existence, and one item in one roundtrip.
|
|
2
|
+
-- KEYS[1] = ready marker key
|
|
3
|
+
-- KEYS[2] = bloom filter key
|
|
4
|
+
-- ARGV[1] = item
|
|
5
|
+
-- Returns: 1 if item may exist, 0 if definitely not present, -1 if not ready/unavailable.
|
|
6
|
+
if redis.call('EXISTS', KEYS[1]) == 0 then
|
|
7
|
+
return -1
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
if redis.call('EXISTS', KEYS[2]) == 0 then
|
|
11
|
+
return -1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
return redis.call('BF.EXISTS', KEYS[2], ARGV[1])
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
-- Check bloom filter existence in a single roundtrip
|
|
2
|
+
-- Read-only: execute with cacheValkeyClient
|
|
3
|
+
-- KEYS[1]: filter key
|
|
4
|
+
-- ARGV[1]: item to check
|
|
5
|
+
-- Returns: 1 if item may exist, 0 if definitely not present, -1 if filter doesn't exist
|
|
6
|
+
|
|
7
|
+
local key = KEYS[1]
|
|
8
|
+
local item = ARGV[1]
|
|
9
|
+
|
|
10
|
+
if redis.call('EXISTS', key) == 0 then
|
|
11
|
+
return -1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
return redis.call('BF.EXISTS', key, item)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- Check a ready marker, bloom filter existence, and multiple items in one roundtrip.
|
|
2
|
+
-- KEYS[1] = ready marker key
|
|
3
|
+
-- KEYS[2] = bloom filter key
|
|
4
|
+
-- ARGV[1..n] = items
|
|
5
|
+
-- Returns: array where 1=may exist, 0=definitely not present, -1=not ready/unavailable.
|
|
6
|
+
local result = {}
|
|
7
|
+
|
|
8
|
+
if #ARGV == 0 then
|
|
9
|
+
return result
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
if redis.call('EXISTS', KEYS[1]) == 0 or redis.call('EXISTS', KEYS[2]) == 0 then
|
|
13
|
+
for i = 1, #ARGV do
|
|
14
|
+
result[i] = -1
|
|
15
|
+
end
|
|
16
|
+
return result
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
return redis.call('BF.MEXISTS', KEYS[2], unpack(ARGV))
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- Check multiple items in bloom filter in a single roundtrip
|
|
2
|
+
-- Read-only: execute with cacheValkeyClient
|
|
3
|
+
-- KEYS[1]: filter key
|
|
4
|
+
-- ARGV[1..n]: items to check
|
|
5
|
+
-- Returns: array where 1=may exist, 0=definitely not present, -1=filter doesn't exist
|
|
6
|
+
|
|
7
|
+
local key = KEYS[1]
|
|
8
|
+
|
|
9
|
+
if redis.call('EXISTS', key) == 0 then
|
|
10
|
+
local result = {}
|
|
11
|
+
for i = 1, #ARGV do
|
|
12
|
+
result[i] = -1
|
|
13
|
+
end
|
|
14
|
+
return result
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return redis.call('BF.MEXISTS', key, unpack(ARGV))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Atomically unlink and re-create a bloom filter key.
|
|
2
|
+
-- Combining UNLINK + BF.RESERVE in a single Lua script prevents a race condition
|
|
3
|
+
-- where two concurrent callers both call UNLINK and then both call BF.RESERVE,
|
|
4
|
+
-- causing the second caller to receive an "item exists" error.
|
|
5
|
+
-- This script is write-only and must be executed using the write client.
|
|
6
|
+
-- KEYS[1] = bloom filter key
|
|
7
|
+
-- ARGV[1] = error_rate (e.g. "0.01")
|
|
8
|
+
-- ARGV[2] = capacity (integer as string)
|
|
9
|
+
-- ARGV[3] = expansion rate (integer as string)
|
|
10
|
+
redis.call('UNLINK', KEYS[1])
|
|
11
|
+
return redis.call('BF.RESERVE', KEYS[1], ARGV[1], ARGV[2], 'EXPANSION', ARGV[3])
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- Atomically delete cache keys and record short-lived invalidation timestamps.
|
|
2
|
+
-- KEYS[1..n] = cache keys
|
|
3
|
+
-- KEYS[n+1..2n] = invalidation marker keys matching each cache key
|
|
4
|
+
-- ARGV[1] = number of cache keys
|
|
5
|
+
-- ARGV[2] = invalidation marker TTL in seconds
|
|
6
|
+
if #KEYS == 0 then
|
|
7
|
+
return 0
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
local keyCount = tonumber(ARGV[1])
|
|
11
|
+
local invalidationTtl = tonumber(ARGV[2])
|
|
12
|
+
if keyCount == nil or invalidationTtl == nil then
|
|
13
|
+
error('cache-delete-with-invalidation: key count and invalidation TTL are required')
|
|
14
|
+
end
|
|
15
|
+
if keyCount == 0 then
|
|
16
|
+
return 0
|
|
17
|
+
end
|
|
18
|
+
if #KEYS ~= keyCount * 2 then
|
|
19
|
+
error('cache-delete-with-invalidation: expected cache keys followed by invalidation keys')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
local cacheKeys = {}
|
|
23
|
+
for i = 1, keyCount do
|
|
24
|
+
cacheKeys[i] = KEYS[i]
|
|
25
|
+
local invalidationKey = KEYS[keyCount + i]
|
|
26
|
+
redis.call('SET', invalidationKey, '1', 'EX', invalidationTtl)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
return redis.call('UNLINK', unpack(cacheKeys))
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
-- Conditionally set cache values unless a newer invalidation marker exists.
|
|
2
|
+
-- KEYS[1..n] = cache keys
|
|
3
|
+
-- KEYS[n+1..2n] = invalidation marker keys matching each cache key
|
|
4
|
+
-- ARGV[1] = number of cache keys
|
|
5
|
+
-- Repeated per key:
|
|
6
|
+
-- ttl seconds
|
|
7
|
+
-- serialized value
|
|
8
|
+
-- Returns one result per key: 1 when set, 0 when skipped.
|
|
9
|
+
if #KEYS == 0 then
|
|
10
|
+
return {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
local keyCount = tonumber(ARGV[1])
|
|
14
|
+
if keyCount == nil then
|
|
15
|
+
error('cache-set-if-not-invalidated: key count is required')
|
|
16
|
+
end
|
|
17
|
+
if keyCount == 0 then
|
|
18
|
+
return {}
|
|
19
|
+
end
|
|
20
|
+
if #KEYS ~= keyCount * 2 then
|
|
21
|
+
error('cache-set-if-not-invalidated: expected cache keys followed by invalidation keys')
|
|
22
|
+
end
|
|
23
|
+
if #ARGV ~= 1 + (keyCount * 2) then
|
|
24
|
+
error('cache-set-if-not-invalidated: expected ttl and value for each key')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
local results = {}
|
|
28
|
+
for i = 1, keyCount do
|
|
29
|
+
local cacheKey = KEYS[i]
|
|
30
|
+
local invalidationKey = KEYS[keyCount + i]
|
|
31
|
+
local argOffset = 1 + ((i - 1) * 2)
|
|
32
|
+
local ttl = tonumber(ARGV[argOffset + 1])
|
|
33
|
+
local value = ARGV[argOffset + 2]
|
|
34
|
+
|
|
35
|
+
if ttl == nil then
|
|
36
|
+
error('cache-set-if-not-invalidated: ttl must be a number')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if redis.call('EXISTS', invalidationKey) == 1 then
|
|
40
|
+
table.insert(results, 0)
|
|
41
|
+
else
|
|
42
|
+
redis.call('SET', cacheKey, value, 'EX', ttl)
|
|
43
|
+
table.insert(results, 1)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return results
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
-- Dynamic config set-fields operation (HSET + PUBLISH all in one roundtrip)
|
|
2
|
+
-- KEYS[1]: hash key
|
|
3
|
+
-- ARGV[1,3,5,...]: field names
|
|
4
|
+
-- ARGV[2,4,6,...]: field values
|
|
5
|
+
-- Sets all fields in the hash and publishes per-field update events
|
|
6
|
+
|
|
7
|
+
-- Guard: HSET with no fields is a Valkey error
|
|
8
|
+
if #ARGV == 0 then return 0 end
|
|
9
|
+
-- Guard: ARGV must contain field/value pairs (even length)
|
|
10
|
+
assert(#ARGV % 2 == 0, 'ARGV must contain field/value pairs (even length)')
|
|
11
|
+
|
|
12
|
+
-- HSET all fields at once
|
|
13
|
+
redis.call('HSET', KEYS[1], unpack(ARGV))
|
|
14
|
+
|
|
15
|
+
-- PUBLISH per field so subscribers get real-time updates
|
|
16
|
+
local numArgs = #ARGV
|
|
17
|
+
for i = 1, numArgs, 2 do
|
|
18
|
+
local field = ARGV[i]
|
|
19
|
+
local value = ARGV[i + 1]
|
|
20
|
+
redis.call('PUBLISH', KEYS[1] .. ':' .. field, value)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
return 1
|