Summary
createClient(...) from the redis npm package returns a value whose .connect() is undefined under perry. Same code on Node creates a working RedisClient and calls .connect() cleanly.
Reproducer
tests/release/packages/redis-pubsub/ fixture (added to the release-sweep harness this cycle):
// entry.ts
import { createClient } from "redis";
async function main(): Promise<void> {
const url = process.env.REDIS_URL ?? "redis://127.0.0.1:18903";
const sub = createClient({ url });
const pub = createClient({ url });
await sub.connect();
await pub.connect();
const received: string = await new Promise<string>((resolve) => {
sub.subscribe("greetings", (message: string) => resolve(message));
setTimeout(() => { pub.publish("greetings", "hello"); }, 50);
});
console.log(`received=${received}`);
await sub.unsubscribe("greetings");
await sub.quit();
await pub.quit();
}
main();
{
"dependencies": { "redis": "^4.7.0" },
"perry": { "compilePackages": ["redis"] }
}
A real redis-server binary on port 18903 (the fixture launches one with redis-server --port 18903 --bind 127.0.0.1 --save '' before the run; teardown is kill on the trapped EXIT).
Expected (from Node)
Actual (from perry-compiled binary)
TypeError: Cannot read properties of undefined (reading 'connect')
— so sub (the value returned by createClient({ url })) is either undefined itself, or its prototype doesn't have a connect method. The call dies at await sub.connect() before any actual redis connection is attempted.
Why this matters
redis is a pure-JS npm package (no native bindings). It's the canonical example for compilePackages — if it doesn't work, neither do most pubsub / cache / queue libs.
Likely root cause: factory-function-returning-object pattern that perry's class/proto resolution doesn't handle. createClient({...}) is not a constructor — it's a factory that builds and returns a RedisClient instance. If perry's HIR doesn't recognize the returned object's class, all method calls miss the vtable.
Worth bisecting against the simpler createClient().connect() (no await, no options) shape to confirm the issue is in the factory return path, not in the await chain.
Environment
- Perry
0.5.699
- redis
^4.7.0
- redis-server (Homebrew) v8.4.0
- macOS 26.4 / arm64
- Discovered via
scripts/release_sweep.sh --tier=3 (tests/release/packages/redis-pubsub/)
Summary
createClient(...)from theredisnpm package returns a value whose.connect()isundefinedunder perry. Same code on Node creates a workingRedisClientand calls.connect()cleanly.Reproducer
tests/release/packages/redis-pubsub/fixture (added to the release-sweep harness this cycle):{ "dependencies": { "redis": "^4.7.0" }, "perry": { "compilePackages": ["redis"] } }A real
redis-serverbinary on port 18903 (the fixture launches one withredis-server --port 18903 --bind 127.0.0.1 --save ''before the run; teardown iskillon the trapped EXIT).Expected (from Node)
Actual (from perry-compiled binary)
— so
sub(the value returned bycreateClient({ url })) is eitherundefineditself, or its prototype doesn't have aconnectmethod. The call dies atawait sub.connect()before any actual redis connection is attempted.Why this matters
redisis a pure-JS npm package (no native bindings). It's the canonical example forcompilePackages— if it doesn't work, neither do most pubsub / cache / queue libs.Likely root cause: factory-function-returning-object pattern that perry's class/proto resolution doesn't handle.
createClient({...})is not a constructor — it's a factory that builds and returns aRedisClientinstance. If perry's HIR doesn't recognize the returned object's class, all method calls miss the vtable.Worth bisecting against the simpler
createClient().connect()(noawait, no options) shape to confirm the issue is in the factory return path, not in theawaitchain.Environment
0.5.699^4.7.0scripts/release_sweep.sh --tier=3(tests/release/packages/redis-pubsub/)