Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions README.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
"lru-cache": "^11.5.2"
},
"devDependencies": {
"@valkey/valkey-glide": "^2.4.2",
"@valkey/valkey-glide": "2.0.0",
"@vitest/coverage-v8": "^4.0.14",
"hot-shots": "^17.0.0",
"prom-client": "^15.1.3",
Expand All @@ -111,10 +111,14 @@
"vitest": "^4.0.14"
},
"peerDependencies": {
"@valkey/valkey-glide": "^2.0.0",
"prom-client": "^15.1.3",
"redis": "~4.7.1"
},
"peerDependenciesMeta": {
"@valkey/valkey-glide": {
"optional": true
},
"prom-client": {
"optional": true
},
Expand Down
58 changes: 29 additions & 29 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 128 additions & 8 deletions scripts/test-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,12 @@ const rootConsumer = `import {
} from "dialcache";
// @ts-expect-error The unused MissingKeyConfigError class was removed instead of deprecated.
import { MissingKeyConfigError } from "dialcache";
import { createNodeRedisDialCacheClient } from "dialcache/node-redis";
import { createNodeRedisDialCacheClient, dialcacheRedisScripts } from "dialcache/node-redis";
import { decodeRedisFrame, decodeTrackedRedisFrame } from "dialcache/redis-protocol";
// @ts-expect-error Read Lua sources were removed from the mutation-only Redis protocol.
import { READ_CACHE_SCRIPT } from "dialcache/redis-protocol";
// @ts-expect-error Tracked read Lua was removed from the mutation-only Redis protocol.
import { READ_TRACKED_CACHE_SCRIPT } from "dialcache/redis-protocol";
import {
DatadogDialCacheMetrics,
createDatadogDialCacheMetrics,
Expand Down Expand Up @@ -137,6 +141,14 @@ const datadogClassAdapter = new DatadogDialCacheMetrics(datadogOptions);
const missingObservationType: DatadogMetricsOptions = { client: dogStatsDClient };
const cache = new DialCache({ namespace: "consumer-cache", metrics });
const redisProtocolError = new DialCacheRedisProtocolError("Invalid DialCache Redis write reply");
const emptyRedisFrame = Buffer.alloc(10);
emptyRedisFrame[0] = 1;
emptyRedisFrame.writeBigUInt64BE(1n, 1);
const decodedEmptyRedisPayload: string | Buffer | null = decodeRedisFrame(emptyRedisFrame);
const decodedStaleRedisPayload: string | Buffer | null = decodeTrackedRedisFrame(
emptyRedisFrame,
Buffer.from("1"),
);
const fallbackTimeoutError = new FallbackTimeoutError("Load", 1_000);
const redisReadTimeoutError = new RedisReadTimeoutError("Load", 100);
const coalescingState: CoalescingState = cache.getCoalescingState();
Expand Down Expand Up @@ -438,7 +450,14 @@ void disabledOverlay;
void metricErrorKinds;
void unboundedErrorKind;
void createNodeRedisDialCacheClient;
void decodedEmptyRedisPayload;
void decodedStaleRedisPayload;
// @ts-expect-error Native reads removed the legacy node-redis registration.
void dialcacheRedisScripts.dialcacheRead;
// @ts-expect-error Native tracked reads removed the legacy node-redis registration.
void dialcacheRedisScripts.dialcacheReadTracked;
void READ_CACHE_SCRIPT;
void READ_TRACKED_CACHE_SCRIPT;
void customRedisClient;
const globalSerializer: Serializer<unknown> = {
dump: () => "global",
Expand Down Expand Up @@ -486,6 +505,7 @@ void missingObservationType;
const integrationConsumer = `import * as valkeyGlide from "@valkey/valkey-glide";
import { DialCache } from "dialcache";
import StatsD from "hot-shots";
import { createClient as createRedisClient, createCluster as createRedisCluster } from "redis";
import {
DatadogDialCacheMetrics,
createDatadogDialCacheMetrics,
Expand All @@ -503,6 +523,7 @@ import {
type ValkeyGlideDialCacheClient,
type ValkeyGlideRuntime,
} from "dialcache/valkey-glide";
import { createNodeRedisDialCacheClient, dialcacheRedisScripts } from "dialcache/node-redis";
import { Registry, type OpenMetricsContentType } from "prom-client";

const registry = new Registry();
Expand All @@ -515,6 +536,13 @@ openMetricsRegistry.setContentType(Registry.OPENMETRICS_CONTENT_TYPE);
const openMetricsAdapter = new PrometheusDialCacheMetrics({ registry: openMetricsRegistry, prefix: "open_" });
const registryIsRequired: {} extends Pick<PrometheusMetricsOptions, "registry"> ? false : true = true;
const glideRedisClient: ValkeyGlideDialCacheClient | undefined = undefined;
const standaloneNodeRedisClient = createRedisClient({ scripts: dialcacheRedisScripts });
const clusterNodeRedisClient = createRedisCluster({
rootNodes: [{ url: "redis://127.0.0.1:6379" }],
scripts: dialcacheRedisScripts,
});
const standaloneNodeRedisAdapter = createNodeRedisDialCacheClient(standaloneNodeRedisClient);
const clusterNodeRedisAdapter = createNodeRedisDialCacheClient(clusterNodeRedisClient);
const glideRuntime: ValkeyGlideRuntime<valkeyGlide.Script, valkeyGlide.Decoder> = valkeyGlide;
declare const standaloneGlideClient: valkeyGlide.GlideClient;
declare const clusterGlideClient: valkeyGlide.GlideClusterClient;
Expand Down Expand Up @@ -544,6 +572,8 @@ void classAdapter;
void openMetricsAdapter;
void registryIsRequired;
void glideRedisClient;
void standaloneNodeRedisAdapter;
void clusterNodeRedisAdapter;
void standaloneGlideAdapter;
void clusterGlideAdapter;
void datadogClassAdapter;
Expand Down Expand Up @@ -597,7 +627,7 @@ try {
const nodeRedis = await import("dialcache/node-redis");
await import("dialcache/valkey-glide");
await import("dialcache/datadog");
await import("dialcache/redis-protocol");
const redisProtocol = await import("dialcache/redis-protocol");
const fallbackTimeoutError = new root.FallbackTimeoutError("PackageRuntime", 1000);
if (!(fallbackTimeoutError instanceof root.DialCacheError) || fallbackTimeoutError.timeoutMs !== 1000) {
throw new Error("The root ESM fallback-timeout error export is invalid");
Expand Down Expand Up @@ -639,6 +669,45 @@ try {
if ("MissingKeyConfigError" in root) {
throw new Error("The removed MissingKeyConfigError class must not be exported from the root ESM entry");
}
if (
"dialcacheRead" in nodeRedis.dialcacheRedisScripts
|| "dialcacheReadTracked" in nodeRedis.dialcacheRedisScripts
) {
throw new Error("The removed read scripts must not be registered by the packed ESM node-redis entry");
}
if (
"READ_CACHE_SCRIPT" in redisProtocol
|| "READ_TRACKED_CACHE_SCRIPT" in redisProtocol
) {
throw new Error("The removed read scripts must not be exported by the packed ESM Redis protocol entry");
}
const esmEmptyFrame = Buffer.alloc(10);
esmEmptyFrame[0] = 1;
esmEmptyFrame.writeBigUInt64BE(1n, 1);
if (redisProtocol.decodeRedisFrame(esmEmptyFrame) !== "") {
throw new Error("The packed ESM Redis protocol decoder did not preserve an empty UTF-8 payload");
}
if (redisProtocol.decodeTrackedRedisFrame(esmEmptyFrame, Buffer.from("1")) !== null) {
throw new Error("The packed ESM Redis protocol decoder did not reject a stale tracked frame");
}
try {
redisProtocol.decodeRedisFrame("not binary");
throw new Error("Expected the packed ESM Redis protocol decoder to reject a non-binary reply");
} catch (error) {
if (!(error instanceof root.DialCacheRedisPayloadError)) {
throw new Error("The Redis protocol payload error does not match the root ESM export");
}
}
const esmInvalidEncodingFrame = Buffer.from(esmEmptyFrame);
esmInvalidEncodingFrame[9] = 2;
try {
redisProtocol.decodeRedisFrame(esmInvalidEncodingFrame);
throw new Error("Expected the packed ESM Redis protocol decoder to reject an unsupported encoding");
} catch (error) {
if (!(error instanceof root.DialCacheRedisPayloadEncodingError)) {
throw new Error("The Redis protocol encoding error does not match the root ESM export");
}
}
const esmDisabledOverlay = root.DialCacheKeyConfig.disabled();
if (
esmDisabledOverlay.requestLocal !== false
Expand Down Expand Up @@ -834,7 +903,7 @@ console.log("${shadowPayloadReleaseMarker}");`,
const nodeRedis = require("dialcache/node-redis");
require("dialcache/valkey-glide");
require("dialcache/datadog");
require("dialcache/redis-protocol");
const redisProtocol = require("dialcache/redis-protocol");
const fallbackTimeoutError = new root.FallbackTimeoutError("PackageRuntime", 1000);
if (!(fallbackTimeoutError instanceof root.DialCacheError) || fallbackTimeoutError.timeoutMs !== 1000) {
throw new Error("The root CommonJS fallback-timeout error export is invalid");
Expand Down Expand Up @@ -878,6 +947,45 @@ try {
if ("MissingKeyConfigError" in root) {
throw new Error("The removed MissingKeyConfigError class must not be exported from the root CommonJS entry");
}
if (
"dialcacheRead" in nodeRedis.dialcacheRedisScripts
|| "dialcacheReadTracked" in nodeRedis.dialcacheRedisScripts
) {
throw new Error("The removed read scripts must not be registered by the packed CommonJS node-redis entry");
}
if (
"READ_CACHE_SCRIPT" in redisProtocol
|| "READ_TRACKED_CACHE_SCRIPT" in redisProtocol
) {
throw new Error("The removed read scripts must not be exported by the packed CommonJS Redis protocol entry");
}
const cjsEmptyFrame = Buffer.alloc(10);
cjsEmptyFrame[0] = 1;
cjsEmptyFrame.writeBigUInt64BE(1n, 1);
if (redisProtocol.decodeRedisFrame(cjsEmptyFrame) !== "") {
throw new Error("The packed CommonJS Redis protocol decoder did not preserve an empty UTF-8 payload");
}
if (redisProtocol.decodeTrackedRedisFrame(cjsEmptyFrame, Buffer.from("1")) !== null) {
throw new Error("The packed CommonJS Redis protocol decoder did not reject a stale tracked frame");
}
try {
redisProtocol.decodeRedisFrame("not binary");
throw new Error("Expected the packed CommonJS Redis protocol decoder to reject a non-binary reply");
} catch (error) {
if (!(error instanceof root.DialCacheRedisPayloadError)) {
throw new Error("The Redis protocol payload error does not match the root CommonJS export");
}
}
const cjsInvalidEncodingFrame = Buffer.from(cjsEmptyFrame);
cjsInvalidEncodingFrame[9] = 2;
try {
redisProtocol.decodeRedisFrame(cjsInvalidEncodingFrame);
throw new Error("Expected the packed CommonJS Redis protocol decoder to reject an unsupported encoding");
} catch (error) {
if (!(error instanceof root.DialCacheRedisPayloadEncodingError)) {
throw new Error("The Redis protocol encoding error does not match the root CommonJS export");
}
}
const cjsDisabledOverlay = root.DialCacheKeyConfig.disabled();
if (
cjsDisabledOverlay.requestLocal !== false
Expand Down Expand Up @@ -952,7 +1060,7 @@ void (async () => {
"redis@~4.7.1",
"typescript@5.9.3",
"prom-client@^15.1.3",
"@valkey/valkey-glide@2.2.10",
"@valkey/valkey-glide@2.0.0",
"dialcache-test-glide@npm:@valkey/valkey-glide@2.4.2",
"hot-shots@^17.0.0",
],
Expand Down Expand Up @@ -981,7 +1089,7 @@ await import("dialcache/node-redis");
if (appGlide.Script === otherGlide.Script) {
throw new Error("The package test requires two distinct GLIDE module instances");
}
const adapter = glide.createValkeyGlideDialCacheClient({
const esmFakeGlideClient = {
invokeScript: async (script, options) => {
if (!(script instanceof appGlide.Script) || script instanceof otherGlide.Script) {
throw new Error("The ESM adapter did not use the caller-supplied GLIDE Script constructor");
Expand All @@ -991,7 +1099,13 @@ const adapter = glide.createValkeyGlideDialCacheClient({
}
return 2;
},
}, appGlide);
};
const esmGlideRuntime = {
...appGlide,
GlideClient: { [Symbol.hasInstance]: (value) => value === esmFakeGlideClient },
GlideClusterClient: { [Symbol.hasInstance]: () => false },
};
const adapter = glide.createValkeyGlideDialCacheClient(esmFakeGlideClient, esmGlideRuntime);
try {
await adapter.write({ valueKey: "value", cacheTtlMs: 1_000, value: "payload" });
throw new Error("Expected an invalid GLIDE script reply to fail");
Expand Down Expand Up @@ -1021,7 +1135,7 @@ void (async () => {
if (appGlide.Script === otherGlide.Script) {
throw new Error("The package test requires two distinct GLIDE module instances");
}
const adapter = glide.createValkeyGlideDialCacheClient({
const cjsFakeGlideClient = {
invokeScript: async (script, options) => {
if (!(script instanceof appGlide.Script) || script instanceof otherGlide.Script) {
throw new Error("The CommonJS adapter did not use the caller-supplied GLIDE Script constructor");
Expand All @@ -1031,7 +1145,13 @@ void (async () => {
}
return 2;
},
}, appGlide);
};
const cjsGlideRuntime = {
...appGlide,
GlideClient: { [Symbol.hasInstance]: (value) => value === cjsFakeGlideClient },
GlideClusterClient: { [Symbol.hasInstance]: () => false },
};
const adapter = glide.createValkeyGlideDialCacheClient(cjsFakeGlideClient, cjsGlideRuntime);
try {
await adapter.write({ valueKey: "value", cacheTtlMs: 1_000, value: "payload" });
throw new Error("Expected an invalid GLIDE script reply to fail");
Expand Down
Loading
Loading