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":"cache-utils.mjs","sourceRoot":"","sources":["../cache-utils.mts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAItC,MAAM,OAAO,oBAAqB,SAAQ,SAAS;IACjD,YAAY,OAAe;QACzB,KAAK,CAAC,gBAAgB,OAAO,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,KAAa,EAAU,EAAE,CAC9D,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,GAAG,SAAS,CAAC;AAEtD,MAAM,qBAAqB,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM;AAC9C,MAAM,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AAClC,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;AAEtC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAAc,EACd,IAAqB;IAErB,IAAI,UAA2B,CAAC;IAEhC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,IAAI,oBAAoB,CAAC,mCAAmC,CAAC,CAAC;QACtE,CAAC;QACD,UAAU,GAAG,KAAK,CAAC;IACrB,CAAC;SAAM,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,oBAAoB,CAAC,qCAAqC,CAAC,CAAC;QACxE,CAAC;QACD,UAAU,GAAG,KAAK,CAAC;IACrB,CAAC;SAAM,CAAC;QACN,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,qBAAqB,EAAE,CAAC;QAC1D,UAAU,GAAG,MAAM,SAAS,CAAC,UAAU,CAAC,CAAC;IAC3C,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAA0B,EAC1B,IAAqB;IAErB,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,oBAAoB,CAAC,wBAAwB,OAAO,MAAM,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,MAAM,GAAW,MAAM,CAAC;IAE5B,0EAA0E;IAC1E,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IACjF,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IAED,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtB,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAErC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
package/dist/cache.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.mts","sourceRoot":"","sources":["../cache.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,qBAAa,WAAW,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,kBAAkB,CAAC,CAAC,CAAC;CAAG"}
|
package/dist/cache.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.mjs","sourceRoot":"","sources":["../cache.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,OAAO,WAAwB,SAAQ,kBAAqB;CAAG"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { GlideClient, type PubSubMsg } from "@valkey/valkey-glide";
|
|
2
|
+
export type ValkeyReadFrom = "primary" | "preferReplica";
|
|
3
|
+
export type ValkeyClientOptions = {
|
|
4
|
+
readFrom?: ValkeyReadFrom;
|
|
5
|
+
lazyConnect?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare const urlsToClients: Map<string, GlideClient>;
|
|
8
|
+
export declare const cacheValkeyClient: GlideClient;
|
|
9
|
+
export declare const rateLimiterValkeyClient: GlideClient;
|
|
10
|
+
export declare const dynamicConfigValkeyClient: GlideClient;
|
|
11
|
+
type PubSubMessageHandler = (msg: PubSubMsg) => void;
|
|
12
|
+
export declare function addPubSubMessageHandler(handler: PubSubMessageHandler): void;
|
|
13
|
+
export declare function removePubSubMessageHandler(handler: PubSubMessageHandler): void;
|
|
14
|
+
export declare function ensureDynamicConfigValkeySubscriptionClient(): Promise<GlideClient>;
|
|
15
|
+
export declare function closeDynamicConfigValkeySubscriptionClient(): Promise<void>;
|
|
16
|
+
declare function handlePubSubMessage(msg: PubSubMsg): void;
|
|
17
|
+
export declare function upsertValkeyClientByUrl(url: string, options?: ValkeyClientOptions): Promise<GlideClient>;
|
|
18
|
+
export declare function glideConfigFromUrl(url: string, options?: ValkeyClientOptions): {
|
|
19
|
+
addresses: {
|
|
20
|
+
host: string;
|
|
21
|
+
port: number;
|
|
22
|
+
}[];
|
|
23
|
+
useTLS: boolean;
|
|
24
|
+
credentials: {
|
|
25
|
+
password: string;
|
|
26
|
+
username?: string | undefined;
|
|
27
|
+
} | undefined;
|
|
28
|
+
readFrom: ValkeyReadFrom | undefined;
|
|
29
|
+
lazyConnect: boolean;
|
|
30
|
+
};
|
|
31
|
+
export declare function buildDynamicConfigSubscriptionClientConfig(url: string): {
|
|
32
|
+
pubsubSubscriptions: {
|
|
33
|
+
channelsAndPatterns: {
|
|
34
|
+
1: Set<string>;
|
|
35
|
+
};
|
|
36
|
+
callback: typeof handlePubSubMessage;
|
|
37
|
+
};
|
|
38
|
+
addresses: {
|
|
39
|
+
host: string;
|
|
40
|
+
port: number;
|
|
41
|
+
}[];
|
|
42
|
+
useTLS: boolean;
|
|
43
|
+
credentials: {
|
|
44
|
+
password: string;
|
|
45
|
+
username?: string | undefined;
|
|
46
|
+
} | undefined;
|
|
47
|
+
readFrom: ValkeyReadFrom | undefined;
|
|
48
|
+
lazyConnect: boolean;
|
|
49
|
+
};
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=clients.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clients.d.mts","sourceRoot":"","sources":["../clients.mts"],"names":[],"mappings":"AACA,OAAO,EAEL,WAAW,EAEX,KAAK,SAAS,EACf,MAAM,sBAAsB,CAAC;AAM9B,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,eAAe,CAAC;AAEzD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,aAAa,0BAAiC,CAAC;AAE5D,eAAO,MAAM,iBAAiB,aAE5B,CAAC;AACH,eAAO,MAAM,uBAAuB,aAElC,CAAC;AACH,eAAO,MAAM,yBAAyB,aAEpC,CAAC;AAIH,KAAK,oBAAoB,GAAG,CAAC,GAAG,EAAE,SAAS,KAAK,IAAI,CAAC;AAGrD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAE3E;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAE9E;AAED,wBAAgB,2CAA2C,IAAI,OAAO,CAAC,WAAW,CAAC,CAclF;AAED,wBAAsB,0CAA0C,IAAI,OAAO,CAAC,IAAI,CAAC,CAShF;AAED,iBAAS,mBAAmB,CAAC,GAAG,EAAE,SAAS,GAAG,IAAI,CAEjD;AAED,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,mBAAmB,GAC5B,OAAO,CAAC,WAAW,CAAC,CAQtB;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB;;;;;;;;;;;;EAwB5E;AAED,wBAAgB,0CAA0C,CAAC,GAAG,EAAE,MAAM;;;;;;;;;;;;;;;;;;EAUrE"}
|
package/dist/clients.mjs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
2
|
+
import { ALL_PATTERNS, GlideClient, GlideClientConfiguration, } from "@valkey/valkey-glide";
|
|
3
|
+
import { config } from "./config.mjs";
|
|
4
|
+
import { handleValkeyError } from "./errors.mjs";
|
|
5
|
+
const CLIENT_CLOSE_SETTLE_MS = 100;
|
|
6
|
+
export const urlsToClients = new Map();
|
|
7
|
+
export const cacheValkeyClient = await upsertValkeyClientByUrl(config.cache_url, {
|
|
8
|
+
readFrom: "preferReplica",
|
|
9
|
+
});
|
|
10
|
+
export const rateLimiterValkeyClient = await upsertValkeyClientByUrl(config.rate_limiter_url, {
|
|
11
|
+
readFrom: "primary",
|
|
12
|
+
});
|
|
13
|
+
export const dynamicConfigValkeyClient = await upsertValkeyClientByUrl(config.dynamic_config_url, {
|
|
14
|
+
readFrom: "preferReplica",
|
|
15
|
+
});
|
|
16
|
+
let dynamicConfigValkeySubscriptionClientPromise = null;
|
|
17
|
+
const pubSubMessageHandlers = new Set();
|
|
18
|
+
export function addPubSubMessageHandler(handler) {
|
|
19
|
+
pubSubMessageHandlers.add(handler);
|
|
20
|
+
}
|
|
21
|
+
export function removePubSubMessageHandler(handler) {
|
|
22
|
+
pubSubMessageHandlers.delete(handler);
|
|
23
|
+
}
|
|
24
|
+
export function ensureDynamicConfigValkeySubscriptionClient() {
|
|
25
|
+
if (dynamicConfigValkeySubscriptionClientPromise)
|
|
26
|
+
return dynamicConfigValkeySubscriptionClientPromise;
|
|
27
|
+
const clientPromise = GlideClient.createClient(buildDynamicConfigSubscriptionClientConfig(config.dynamic_config_url)).catch((error) => {
|
|
28
|
+
dynamicConfigValkeySubscriptionClientPromise = null;
|
|
29
|
+
handleValkeyError(error);
|
|
30
|
+
throw error;
|
|
31
|
+
});
|
|
32
|
+
dynamicConfigValkeySubscriptionClientPromise = clientPromise;
|
|
33
|
+
return clientPromise;
|
|
34
|
+
}
|
|
35
|
+
export async function closeDynamicConfigValkeySubscriptionClient() {
|
|
36
|
+
const clientPromise = dynamicConfigValkeySubscriptionClientPromise;
|
|
37
|
+
if (!clientPromise)
|
|
38
|
+
return;
|
|
39
|
+
dynamicConfigValkeySubscriptionClientPromise = null;
|
|
40
|
+
const client = await clientPromise;
|
|
41
|
+
await client.punsubscribe(ALL_PATTERNS, 5_000);
|
|
42
|
+
client.close();
|
|
43
|
+
await sleep(CLIENT_CLOSE_SETTLE_MS);
|
|
44
|
+
}
|
|
45
|
+
function handlePubSubMessage(msg) {
|
|
46
|
+
for (const handler of pubSubMessageHandlers)
|
|
47
|
+
handler(msg);
|
|
48
|
+
}
|
|
49
|
+
export async function upsertValkeyClientByUrl(url, options) {
|
|
50
|
+
const effectiveLazyConnect = options?.lazyConnect ?? true;
|
|
51
|
+
const cacheKey = `${url}:${options?.readFrom ?? "default"}:${effectiveLazyConnect}`;
|
|
52
|
+
const existing = urlsToClients.get(cacheKey);
|
|
53
|
+
if (existing)
|
|
54
|
+
return existing;
|
|
55
|
+
const client = await GlideClient.createClient(glideConfigFromUrl(url, options));
|
|
56
|
+
urlsToClients.set(cacheKey, client);
|
|
57
|
+
return client;
|
|
58
|
+
}
|
|
59
|
+
export function glideConfigFromUrl(url, options) {
|
|
60
|
+
try {
|
|
61
|
+
const parsed = new URL(url);
|
|
62
|
+
return {
|
|
63
|
+
addresses: [
|
|
64
|
+
{
|
|
65
|
+
host: parsed.hostname,
|
|
66
|
+
port: parsed.port ? Number(parsed.port) : 6379,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
useTLS: parsed.protocol === "rediss:",
|
|
70
|
+
credentials: parsed.password
|
|
71
|
+
? {
|
|
72
|
+
...(parsed.username ? { username: parsed.username } : {}),
|
|
73
|
+
password: parsed.password,
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
76
|
+
readFrom: options?.readFrom,
|
|
77
|
+
lazyConnect: options?.lazyConnect ?? true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch (cause) {
|
|
81
|
+
throw new Error(`Invalid Valkey URL: ${url}`, { cause });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function buildDynamicConfigSubscriptionClientConfig(url) {
|
|
85
|
+
return {
|
|
86
|
+
...glideConfigFromUrl(url, { readFrom: "preferReplica", lazyConnect: false }),
|
|
87
|
+
pubsubSubscriptions: {
|
|
88
|
+
channelsAndPatterns: {
|
|
89
|
+
[GlideClientConfiguration.PubSubChannelModes.Pattern]: new Set(["dynamic-config:*"]),
|
|
90
|
+
},
|
|
91
|
+
callback: handlePubSubMessage,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=clients.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clients.mjs","sourceRoot":"","sources":["../clients.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EACL,YAAY,EACZ,WAAW,EACX,wBAAwB,GAEzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,sBAAsB,GAAG,GAAG,CAAC;AASnC,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAuB,CAAC;AAE5D,MAAM,CAAC,MAAM,iBAAiB,GAAG,MAAM,uBAAuB,CAAC,MAAM,CAAC,SAAS,EAAE;IAC/E,QAAQ,EAAE,eAAe;CAC1B,CAAC,CAAC;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,uBAAuB,CAAC,MAAM,CAAC,gBAAgB,EAAE;IAC5F,QAAQ,EAAE,SAAS;CACpB,CAAC,CAAC;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,uBAAuB,CAAC,MAAM,CAAC,kBAAkB,EAAE;IAChG,QAAQ,EAAE,eAAe;CAC1B,CAAC,CAAC;AAEH,IAAI,4CAA4C,GAAgC,IAAI,CAAC;AAGrF,MAAM,qBAAqB,GAAG,IAAI,GAAG,EAAwB,CAAC;AAE9D,MAAM,UAAU,uBAAuB,CAAC,OAA6B;IACnE,qBAAqB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,OAA6B;IACtE,qBAAqB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,2CAA2C;IACzD,IAAI,4CAA4C;QAC9C,OAAO,4CAA4C,CAAC;IAEtD,MAAM,aAAa,GAAG,WAAW,CAAC,YAAY,CAC5C,0CAA0C,CAAC,MAAM,CAAC,kBAAkB,CAAC,CACtE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QAChB,4CAA4C,GAAG,IAAI,CAAC;QACpD,iBAAiB,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;IAEH,4CAA4C,GAAG,aAAa,CAAC;IAC7D,OAAO,aAAa,CAAC;AACvB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0CAA0C;IAC9D,MAAM,aAAa,GAAG,4CAA4C,CAAC;IACnE,IAAI,CAAC,aAAa;QAAE,OAAO;IAE3B,4CAA4C,GAAG,IAAI,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;IACnC,MAAM,MAAM,CAAC,YAAY,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAC/C,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,MAAM,KAAK,CAAC,sBAAsB,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,mBAAmB,CAAC,GAAc;IACzC,KAAK,MAAM,OAAO,IAAI,qBAAqB;QAAE,OAAO,CAAC,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,GAAW,EACX,OAA6B;IAE7B,MAAM,oBAAoB,GAAG,OAAO,EAAE,WAAW,IAAI,IAAI,CAAC;IAC1D,MAAM,QAAQ,GAAG,GAAG,GAAG,IAAI,OAAO,EAAE,QAAQ,IAAI,SAAS,IAAI,oBAAoB,EAAE,CAAC;IACpF,MAAM,QAAQ,GAAG,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,YAAY,CAAC,kBAAkB,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC;IAChF,aAAa,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACpC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,OAA6B;IAC3E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAE5B,OAAO;YACL,SAAS,EAAE;gBACT;oBACE,IAAI,EAAE,MAAM,CAAC,QAAQ;oBACrB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;iBAC/C;aACF;YACD,MAAM,EAAE,MAAM,CAAC,QAAQ,KAAK,SAAS;YACrC,WAAW,EAAE,MAAM,CAAC,QAAQ;gBAC1B,CAAC,CAAC;oBACE,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACzD,QAAQ,EAAE,MAAM,CAAC,QAAQ;iBAC1B;gBACH,CAAC,CAAC,SAAS;YACb,QAAQ,EAAE,OAAO,EAAE,QAAQ;YAC3B,WAAW,EAAE,OAAO,EAAE,WAAW,IAAI,IAAI;SAC1C,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0CAA0C,CAAC,GAAW;IACpE,OAAO;QACL,GAAG,kBAAkB,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;QAC7E,mBAAmB,EAAE;YACnB,mBAAmB,EAAE;gBACnB,CAAC,wBAAwB,CAAC,kBAAkB,CAAC,OAAO,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,kBAAkB,CAAC,CAAC;aACrF;YACD,QAAQ,EAAE,mBAAmB;SAC9B;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.mts","sourceRoot":"","sources":["../config.mts"],"names":[],"mappings":"AAGA,eAAO,MAAM,MAAM;;;;CAUlB,CAAC"}
|
package/dist/config.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const DEFAULT_VALKEY_URL = process.env.VALKEY_URL || `redis://${process.env.DOCKER_HOST_IP || "localhost"}:6379`;
|
|
2
|
+
export const config = {
|
|
3
|
+
// used for caching; preferReplica routes writes to primary automatically
|
|
4
|
+
cache_url: process.env.VALKEY_CACHE_URL || DEFAULT_VALKEY_URL,
|
|
5
|
+
// used for rate limiting; must always read from primary to avoid replica lag
|
|
6
|
+
rate_limiter_url: process.env.VALKEY_RATE_LIMITER_URL || process.env.VALKEY_CACHE_URL || DEFAULT_VALKEY_URL,
|
|
7
|
+
// used for distributed configuration
|
|
8
|
+
dynamic_config_url: process.env.VALKEY_DYNAMIC_CONFIG_URL || DEFAULT_VALKEY_URL,
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.mjs","sourceRoot":"","sources":["../config.mts"],"names":[],"mappings":"AAAA,MAAM,kBAAkB,GACtB,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,WAAW,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,WAAW,OAAO,CAAC;AAExF,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,yEAAyE;IACzE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,kBAAkB;IAE7D,6EAA6E;IAC7E,gBAAgB,EACd,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,kBAAkB;IAE3F,qCAAqC;IACrC,kBAAkB,EAAE,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,kBAAkB;CAChF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.d.mts","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,eAAO,MAAM,oBAAoB,GAAU,QAAQ,WAAW,EAAE,SAAS,MAAM,KAAG,OAAO,CAAC,IAAI,CAsB7F,CAAC"}
|
package/dist/delete.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { handleValkeyError } from "./errors.mjs";
|
|
2
|
+
export const deleteKeysWithPrefix = async (client, pattern) => {
|
|
3
|
+
const deletionPromises = [];
|
|
4
|
+
let cursor = "0";
|
|
5
|
+
do {
|
|
6
|
+
try {
|
|
7
|
+
// GlideClient scan method signature: scan(cursor: string, options?: ScanOptions)
|
|
8
|
+
/* oxlint-disable no-await-in-loop */
|
|
9
|
+
const result = await client.scan(cursor, { match: pattern });
|
|
10
|
+
cursor = result[0];
|
|
11
|
+
const keys = result[1];
|
|
12
|
+
if (keys.length > 0) {
|
|
13
|
+
deletionPromises.push(client.unlink(keys).catch(handleValkeyError));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
handleValkeyError(err);
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
} while (cursor !== "0");
|
|
21
|
+
await Promise.all(deletionPromises);
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=delete.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete.mjs","sourceRoot":"","sources":["../delete.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,EAAE,MAAmB,EAAE,OAAe,EAAiB,EAAE;IAChG,MAAM,gBAAgB,GAA6B,EAAE,CAAC;IACtD,IAAI,MAAM,GAAG,GAAG,CAAC;IAEjB,GAAG,CAAC;QACF,IAAI,CAAC;YACH,iFAAiF;YACjF,qCAAqC;YACrC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;YAC7D,MAAM,GAAG,MAAM,CAAC,CAAC,CAAW,CAAC;YAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAa,CAAC;YAEnC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpB,gBAAgB,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;YACtE,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,iBAAiB,CAAC,GAAY,CAAC,CAAC;YAChC,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,QAAQ,MAAM,KAAK,GAAG,EAAE;IAEzB,MAAM,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;AACtC,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DynamicConfigField, DynamicConfigFieldType } from "../types.mts";
|
|
2
|
+
export declare function validateFieldTypes(fieldTypes: Record<string, DynamicConfigFieldType>, defaultFields: Record<string, DynamicConfigField>): void;
|
|
3
|
+
export declare function processFieldValue(type: DynamicConfigFieldType, value: DynamicConfigField): DynamicConfigField;
|
|
4
|
+
export declare function stringifyField(type: DynamicConfigFieldType, value: DynamicConfigField): string;
|
|
5
|
+
export declare function parseField(type: DynamicConfigFieldType, value: string): DynamicConfigField;
|
|
6
|
+
//# sourceMappingURL=fields.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.d.mts","sourceRoot":"","sources":["../../dynamic-config/fields.mts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE/E,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAClD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,QAelD;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,sBAAsB,EAC5B,KAAK,EAAE,kBAAkB,GACxB,kBAAkB,CAUpB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAK9F;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,MAAM,GAAG,kBAAkB,CAK1F"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function validateFieldTypes(fieldTypes, defaultFields) {
|
|
2
|
+
for (const [name, type] of Object.entries(fieldTypes)) {
|
|
3
|
+
if (!(name in defaultFields))
|
|
4
|
+
throw new Error(`Default field ${name} is not defined`);
|
|
5
|
+
const defaultValue = defaultFields[name];
|
|
6
|
+
if (type === "string" && typeof defaultValue !== "string") {
|
|
7
|
+
throw new Error(`Default field ${name} is not of type string`);
|
|
8
|
+
}
|
|
9
|
+
if (type === "number" && typeof defaultValue !== "number") {
|
|
10
|
+
throw new Error(`Default field ${name} is not of type number`);
|
|
11
|
+
}
|
|
12
|
+
if (type === "boolean" && typeof defaultValue !== "boolean") {
|
|
13
|
+
throw new Error(`Default field ${name} is not of type boolean`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function processFieldValue(type, value) {
|
|
18
|
+
if (type === "string")
|
|
19
|
+
return value.toString();
|
|
20
|
+
if (type === "number")
|
|
21
|
+
return Number(value);
|
|
22
|
+
if (type === "boolean") {
|
|
23
|
+
if (typeof value === "boolean")
|
|
24
|
+
return value;
|
|
25
|
+
if (typeof value === "string")
|
|
26
|
+
return value === "1";
|
|
27
|
+
if (typeof value === "number")
|
|
28
|
+
return value === 1;
|
|
29
|
+
return Boolean(value);
|
|
30
|
+
}
|
|
31
|
+
throw new Error("Unknown field type");
|
|
32
|
+
}
|
|
33
|
+
export function stringifyField(type, value) {
|
|
34
|
+
const normalizedValue = processFieldValue(type, value);
|
|
35
|
+
if (type === "string" || type === "number")
|
|
36
|
+
return normalizedValue.toString();
|
|
37
|
+
if (type === "boolean")
|
|
38
|
+
return normalizedValue ? "1" : "0";
|
|
39
|
+
throw new Error("Unknown field type");
|
|
40
|
+
}
|
|
41
|
+
export function parseField(type, value) {
|
|
42
|
+
if (type === "string")
|
|
43
|
+
return value;
|
|
44
|
+
if (type === "number")
|
|
45
|
+
return Number(value);
|
|
46
|
+
if (type === "boolean")
|
|
47
|
+
return value === "1";
|
|
48
|
+
throw new Error("Unknown field type");
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=fields.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fields.mjs","sourceRoot":"","sources":["../../dynamic-config/fields.mts"],"names":[],"mappings":"AAEA,MAAM,UAAU,kBAAkB,CAChC,UAAkD,EAClD,aAAiD;IAEjD,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC,CAAC,IAAI,IAAI,aAAa,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,iBAAiB,CAAC,CAAC;QACtF,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,QAAQ,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,wBAAwB,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,IAAI,KAAK,QAAQ,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,wBAAwB,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,IAAI,KAAK,SAAS,IAAI,OAAO,YAAY,KAAK,SAAS,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,yBAAyB,CAAC,CAAC;QAClE,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAA4B,EAC5B,KAAyB;IAEzB,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC/C,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,OAAO,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,KAAK,GAAG,CAAC;QACpD,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,KAAK,CAAC,CAAC;QAClD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAA4B,EAAE,KAAyB;IACpF,MAAM,eAAe,GAAG,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,eAAe,CAAC,QAAQ,EAAE,CAAC;IAC9E,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAC3D,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAA4B,EAAE,KAAa;IACpE,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACpC,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5C,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,KAAK,KAAK,GAAG,CAAC;IAC7C,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { DynamicConfigField, DynamicConfigFieldType } from "../types.mts";
|
|
2
|
+
import type { GlideClient } from "@valkey/valkey-glide";
|
|
3
|
+
export declare const dynamicConfigSetFieldsScript: import("@valkey/valkey-glide").Script;
|
|
4
|
+
export declare function getDynamicConfigFieldsMap(key: string, client?: GlideClient): Promise<Map<string, {
|
|
5
|
+
field: unknown;
|
|
6
|
+
value: unknown;
|
|
7
|
+
}>>;
|
|
8
|
+
export declare function applyFieldsFromMap({ fields, fieldsMap, fieldTypes, defaultFields, }: {
|
|
9
|
+
fields: Map<string, DynamicConfigField>;
|
|
10
|
+
fieldsMap: Map<string, {
|
|
11
|
+
field: unknown;
|
|
12
|
+
value: unknown;
|
|
13
|
+
}>;
|
|
14
|
+
fieldTypes: Record<string, DynamicConfigFieldType>;
|
|
15
|
+
defaultFields: Record<string, DynamicConfigField>;
|
|
16
|
+
}): void;
|
|
17
|
+
export declare function writeDynamicConfigFields({ key, args, client, }: {
|
|
18
|
+
key: string;
|
|
19
|
+
args: string[];
|
|
20
|
+
client?: GlideClient;
|
|
21
|
+
}): Promise<void>;
|
|
22
|
+
export declare function buildMissingDefaultWrites({ fieldsMap, fieldTypes, defaultFields, }: {
|
|
23
|
+
fieldsMap: Map<string, {
|
|
24
|
+
field: unknown;
|
|
25
|
+
value: unknown;
|
|
26
|
+
}>;
|
|
27
|
+
fieldTypes: Record<string, DynamicConfigFieldType>;
|
|
28
|
+
defaultFields: Record<string, DynamicConfigField>;
|
|
29
|
+
}): {
|
|
30
|
+
toApply: [string, DynamicConfigField][];
|
|
31
|
+
writeArgs: string[];
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=storage.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.mts","sourceRoot":"","sources":["../../dynamic-config/storage.mts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGxD,eAAO,MAAM,4BAA4B,uCAExC,CAAC;AAEF,wBAAsB,yBAAyB,CAC7C,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,WAAuC,GAC9C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC,CAW1D;AAED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,SAAS,EACT,UAAU,EACV,aAAa,GACd,EAAE;IACD,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACxC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC3D,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IACnD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CACnD,QASA;AAED,wBAAsB,wBAAwB,CAAC,EAC7C,GAAG,EACH,IAAI,EACJ,MAAkC,GACnC,EAAE;IACD,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,iBAGA;AAED,wBAAgB,yBAAyB,CAAC,EACxC,SAAS,EACT,UAAU,EACV,aAAa,GACd,EAAE;IACD,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC3D,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IACnD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CACnD;;;EAaA"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { dynamicConfigValkeyClient } from "../clients.mjs";
|
|
2
|
+
import { loadScript, registerScript } from "../scripts.mjs";
|
|
3
|
+
import { parseField, stringifyField } from "./fields.mjs";
|
|
4
|
+
export const dynamicConfigSetFieldsScript = registerScript(loadScript("dynamic-config-set-fields.lua", new URL("../", import.meta.url)));
|
|
5
|
+
export async function getDynamicConfigFieldsMap(key, client = dynamicConfigValkeyClient) {
|
|
6
|
+
const fields = await client.hgetall(key);
|
|
7
|
+
const fieldsMap = new Map();
|
|
8
|
+
if (Array.isArray(fields)) {
|
|
9
|
+
for (const entry of fields) {
|
|
10
|
+
if (entry && typeof entry === "object" && "field" in entry && "value" in entry) {
|
|
11
|
+
fieldsMap.set(entry.field.toString(), entry);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return fieldsMap;
|
|
16
|
+
}
|
|
17
|
+
export function applyFieldsFromMap({ fields, fieldsMap, fieldTypes, defaultFields, }) {
|
|
18
|
+
for (const [name, type] of Object.entries(fieldTypes)) {
|
|
19
|
+
const valkeyEntry = fieldsMap.get(name);
|
|
20
|
+
const value = valkeyEntry?.value != null
|
|
21
|
+
? parseField(type, stringifyValkeyField(valkeyEntry.value))
|
|
22
|
+
: defaultFields[name];
|
|
23
|
+
fields.set(name, value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function writeDynamicConfigFields({ key, args, client = dynamicConfigValkeyClient, }) {
|
|
27
|
+
if (args.length === 0)
|
|
28
|
+
return;
|
|
29
|
+
await client.invokeScript(dynamicConfigSetFieldsScript, { keys: [key], args });
|
|
30
|
+
}
|
|
31
|
+
export function buildMissingDefaultWrites({ fieldsMap, fieldTypes, defaultFields, }) {
|
|
32
|
+
const toApply = [];
|
|
33
|
+
const writeArgs = [];
|
|
34
|
+
for (const [name, type] of Object.entries(fieldTypes)) {
|
|
35
|
+
const valkeyEntry = fieldsMap.get(name);
|
|
36
|
+
const value = valkeyEntry?.value != null
|
|
37
|
+
? parseField(type, stringifyValkeyField(valkeyEntry.value))
|
|
38
|
+
: defaultFields[name];
|
|
39
|
+
if (valkeyEntry?.value == null)
|
|
40
|
+
writeArgs.push(name, stringifyField(type, value));
|
|
41
|
+
toApply.push([name, value]);
|
|
42
|
+
}
|
|
43
|
+
return { toApply, writeArgs };
|
|
44
|
+
}
|
|
45
|
+
function stringifyValkeyField(value) {
|
|
46
|
+
if (Buffer.isBuffer(value))
|
|
47
|
+
return value.toString();
|
|
48
|
+
if (typeof value === "string")
|
|
49
|
+
return value;
|
|
50
|
+
if (typeof value === "number" || typeof value === "bigint" || typeof value === "boolean") {
|
|
51
|
+
return String(value);
|
|
52
|
+
}
|
|
53
|
+
return JSON.stringify(value) ?? "";
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=storage.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.mjs","sourceRoot":"","sources":["../../dynamic-config/storage.mts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DynamicConfigOptions, DynamicConfigField, DynamicConfigFieldType } from "./types.mts";
|
|
2
|
+
import { parseField, stringifyField } from "./dynamic-config/fields.mts";
|
|
3
|
+
export declare const dynamicConfigs: DynamicConfig[];
|
|
4
|
+
export declare class DynamicConfig {
|
|
5
|
+
staleTtl: number;
|
|
6
|
+
key: string;
|
|
7
|
+
fields: Map<string, DynamicConfigField>;
|
|
8
|
+
fieldTypes: Record<string, DynamicConfigFieldType>;
|
|
9
|
+
defaultFields: Record<string, DynamicConfigField>;
|
|
10
|
+
initialization: Promise<void>;
|
|
11
|
+
private client;
|
|
12
|
+
private closed;
|
|
13
|
+
private lastRefresh;
|
|
14
|
+
private refreshTimer;
|
|
15
|
+
private readonly messageHandler;
|
|
16
|
+
constructor(options: DynamicConfigOptions);
|
|
17
|
+
initialize(): Promise<void>;
|
|
18
|
+
waitForInitialization(): Promise<void>;
|
|
19
|
+
private validateFieldTypes;
|
|
20
|
+
stringifyField: typeof stringifyField;
|
|
21
|
+
parseField: typeof parseField;
|
|
22
|
+
private getFieldsMap;
|
|
23
|
+
private applyFieldsFromMap;
|
|
24
|
+
subscribe(): Promise<void>;
|
|
25
|
+
unsubscribe(): void;
|
|
26
|
+
private handlePubSubMessage;
|
|
27
|
+
createRefreshTimer(): void;
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
refresh(): Promise<void>;
|
|
30
|
+
getFields(): Record<string, DynamicConfigField>;
|
|
31
|
+
setFields(fields: Record<string, DynamicConfigField>): Promise<void>;
|
|
32
|
+
setField(name: string, value: DynamicConfigField): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=dynamic-config.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dynamic-config.d.mts","sourceRoot":"","sources":["../dynamic-config.mts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAQpG,OAAO,EACL,UAAU,EAEV,cAAc,EAEf,MAAM,6BAA6B,CAAC;AAIrC,eAAO,MAAM,cAAc,EAAE,aAAa,EAAO,CAAC;AAElD,qBAAa,aAAa;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;IACnD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAClD,cAAc,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAqD;gBAExE,OAAO,EAAE,oBAAoB;IAqBnC,UAAU;IAqBV,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5C,OAAO,CAAC,kBAAkB;IAI1B,cAAc,wBAAkB;IAChC,UAAU,oBAAc;IAExB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,kBAAkB;IASpB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAShC,WAAW,IAAI,IAAI;IAInB,OAAO,CAAC,mBAAmB;IAe3B,kBAAkB;IASlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYhB,OAAO;IAgBb,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC;IAUzC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB1E,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;CAGjE"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { ensureDynamicConfigValkeySubscriptionClient, addPubSubMessageHandler, removePubSubMessageHandler, dynamicConfigValkeyClient, } from "./clients.mjs";
|
|
2
|
+
import { handleValkeyError } from "./errors.mjs";
|
|
3
|
+
import { applyFieldsFromMap, buildMissingDefaultWrites, getDynamicConfigFieldsMap, writeDynamicConfigFields, } from "./dynamic-config/storage.mjs";
|
|
4
|
+
import { parseField, processFieldValue, stringifyField, validateFieldTypes, } from "./dynamic-config/fields.mjs";
|
|
5
|
+
const NAMESPACE = "dynamic-config";
|
|
6
|
+
const isTest = process.env.NODE_ENV === "test";
|
|
7
|
+
export const dynamicConfigs = [];
|
|
8
|
+
export class DynamicConfig {
|
|
9
|
+
staleTtl;
|
|
10
|
+
key;
|
|
11
|
+
fields;
|
|
12
|
+
fieldTypes;
|
|
13
|
+
defaultFields;
|
|
14
|
+
initialization;
|
|
15
|
+
client;
|
|
16
|
+
closed = false;
|
|
17
|
+
lastRefresh = 0;
|
|
18
|
+
refreshTimer = null;
|
|
19
|
+
messageHandler = (msg) => this.handlePubSubMessage(msg);
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.staleTtl = options.staleTtlSeconds || 60;
|
|
22
|
+
this.key = `${NAMESPACE}:${options.key}`;
|
|
23
|
+
this.fieldTypes = options.fieldTypes;
|
|
24
|
+
this.defaultFields = options.defaultFields;
|
|
25
|
+
this.client = options.client ?? dynamicConfigValkeyClient;
|
|
26
|
+
this.fields = new Map();
|
|
27
|
+
/* v8 ignore next 3 -- duplicate construction is guarded only outside NODE_ENV=test. */
|
|
28
|
+
if (!isTest && dynamicConfigs.some((config) => config.key === this.key)) {
|
|
29
|
+
throw new Error(`DynamicConfig already initialized: ${this.key}`);
|
|
30
|
+
}
|
|
31
|
+
dynamicConfigs.push(this);
|
|
32
|
+
this.initialization = this.initialize();
|
|
33
|
+
/* v8 ignore next -- tests run with NODE_ENV=test and do not attach startup catch handlers. */
|
|
34
|
+
if (!isTest) {
|
|
35
|
+
this.initialization.catch((err) => {
|
|
36
|
+
handleValkeyError(err);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async initialize() {
|
|
41
|
+
this.validateFieldTypes();
|
|
42
|
+
const fieldsMap = await this.getFieldsMap();
|
|
43
|
+
const { toApply, writeArgs } = buildMissingDefaultWrites({
|
|
44
|
+
fieldsMap,
|
|
45
|
+
fieldTypes: this.fieldTypes,
|
|
46
|
+
defaultFields: this.defaultFields,
|
|
47
|
+
});
|
|
48
|
+
await writeDynamicConfigFields({ key: this.key, args: writeArgs, client: this.client });
|
|
49
|
+
for (const [name, value] of toApply) {
|
|
50
|
+
this.fields.set(name, value);
|
|
51
|
+
}
|
|
52
|
+
// Mark initialized so the refresh timer doesn't race with initialization
|
|
53
|
+
this.lastRefresh = Date.now();
|
|
54
|
+
await this.subscribe();
|
|
55
|
+
/* v8 ignore next -- closing during the subscription await is a shutdown race guard. */
|
|
56
|
+
if (this.closed)
|
|
57
|
+
return;
|
|
58
|
+
this.createRefreshTimer();
|
|
59
|
+
}
|
|
60
|
+
async waitForInitialization() {
|
|
61
|
+
await this.initialization;
|
|
62
|
+
}
|
|
63
|
+
validateFieldTypes() {
|
|
64
|
+
validateFieldTypes(this.fieldTypes, this.defaultFields);
|
|
65
|
+
}
|
|
66
|
+
stringifyField = stringifyField;
|
|
67
|
+
parseField = parseField;
|
|
68
|
+
getFieldsMap() {
|
|
69
|
+
return getDynamicConfigFieldsMap(this.key, this.client);
|
|
70
|
+
}
|
|
71
|
+
applyFieldsFromMap(fieldsMap) {
|
|
72
|
+
applyFieldsFromMap({
|
|
73
|
+
fields: this.fields,
|
|
74
|
+
fieldsMap,
|
|
75
|
+
fieldTypes: this.fieldTypes,
|
|
76
|
+
defaultFields: this.defaultFields,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
async subscribe() {
|
|
80
|
+
if (this.closed)
|
|
81
|
+
return;
|
|
82
|
+
await ensureDynamicConfigValkeySubscriptionClient();
|
|
83
|
+
/* v8 ignore next -- closing during the subscription await is a shutdown race guard. */
|
|
84
|
+
if (this.closed)
|
|
85
|
+
return;
|
|
86
|
+
addPubSubMessageHandler(this.messageHandler);
|
|
87
|
+
}
|
|
88
|
+
// Unsubscribe from pub/sub
|
|
89
|
+
unsubscribe() {
|
|
90
|
+
removePubSubMessageHandler(this.messageHandler);
|
|
91
|
+
}
|
|
92
|
+
handlePubSubMessage(msg) {
|
|
93
|
+
const channel = msg.channel.toString();
|
|
94
|
+
const prefix = `${this.key}:`;
|
|
95
|
+
// Only process messages for this config's key
|
|
96
|
+
if (!channel.startsWith(prefix))
|
|
97
|
+
return;
|
|
98
|
+
const fieldName = channel.slice(prefix.length);
|
|
99
|
+
const type = this.fieldTypes[fieldName];
|
|
100
|
+
if (!type)
|
|
101
|
+
return;
|
|
102
|
+
const value = parseField(type, msg.message.toString());
|
|
103
|
+
this.fields.set(fieldName, value);
|
|
104
|
+
}
|
|
105
|
+
createRefreshTimer() {
|
|
106
|
+
const refreshInterval = Math.max(this.staleTtl * 1000, 1000); // Convert to ms, minimum 1 second
|
|
107
|
+
/* v8 ignore next 3 -- interval callback is defensive background refresh plumbing. */
|
|
108
|
+
this.refreshTimer = setInterval(() => {
|
|
109
|
+
this.refresh().catch(handleValkeyError);
|
|
110
|
+
}, refreshInterval);
|
|
111
|
+
this.refreshTimer.unref(); // Do not hold the event loop open if the process wants to exit
|
|
112
|
+
}
|
|
113
|
+
close() {
|
|
114
|
+
this.closed = true;
|
|
115
|
+
this.unsubscribe();
|
|
116
|
+
if (this.refreshTimer) {
|
|
117
|
+
clearInterval(this.refreshTimer);
|
|
118
|
+
this.refreshTimer = null;
|
|
119
|
+
}
|
|
120
|
+
const idx = dynamicConfigs.indexOf(this);
|
|
121
|
+
if (idx !== -1)
|
|
122
|
+
dynamicConfigs.splice(idx, 1);
|
|
123
|
+
return Promise.resolve();
|
|
124
|
+
}
|
|
125
|
+
async refresh() {
|
|
126
|
+
if (this.closed)
|
|
127
|
+
return;
|
|
128
|
+
const now = Date.now();
|
|
129
|
+
if (now - this.lastRefresh < this.staleTtl * 1000)
|
|
130
|
+
return;
|
|
131
|
+
this.lastRefresh = now; // Optimistic claim: prevents concurrent duplicate refreshes
|
|
132
|
+
try {
|
|
133
|
+
const fieldsMap = await this.getFieldsMap();
|
|
134
|
+
this.applyFieldsFromMap(fieldsMap);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
/* v8 ignore next -- transient Valkey failures are environment-dependent. */
|
|
138
|
+
this.lastRefresh = 0; // Reset so the next interval can retry after a transient failure
|
|
139
|
+
/* v8 ignore next -- rethrow preserves caller-visible refresh failure semantics. */
|
|
140
|
+
throw err;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
getFields() {
|
|
144
|
+
const result = {};
|
|
145
|
+
for (const [name, value] of this.fields.entries()) {
|
|
146
|
+
result[name] = value;
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
// set the fields in valkey and publish change events atomically via Lua script,
|
|
151
|
+
// then apply local state only after a successful write
|
|
152
|
+
async setFields(fields) {
|
|
153
|
+
const toApply = [];
|
|
154
|
+
const args = [];
|
|
155
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
156
|
+
if (!this.fieldTypes[name]) {
|
|
157
|
+
throw new Error(`Unknown field: ${name}`);
|
|
158
|
+
}
|
|
159
|
+
const type = this.fieldTypes[name];
|
|
160
|
+
const processedValue = processFieldValue(type, value);
|
|
161
|
+
toApply.push([name, processedValue]);
|
|
162
|
+
args.push(name, stringifyField(type, processedValue));
|
|
163
|
+
}
|
|
164
|
+
await writeDynamicConfigFields({ key: this.key, args, client: this.client });
|
|
165
|
+
for (const [name, processedValue] of toApply) {
|
|
166
|
+
this.fields.set(name, processedValue);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
setField(name, value) {
|
|
170
|
+
return this.setFields({ [name]: value });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=dynamic-config.mjs.map
|