Summary
Importing a class from a precompiled .js file in node_modules produces instances whose prototype methods are missing. typeof instance.someMethod returns "undefined" and calling it throws TypeError. The same class hand-written as a sibling .ts file and imported normally works correctly.
This breaks any precompiled npm-published package that exposes a class with instance methods — concretely it currently blocks @perryts/mysql's Pool.query() and (presumably) Connection.query(), which means the entire driver is unusable from a Perry-compiled binary.
Reproducer (3 files)
Working case — hand-written cross-module .ts:
// /tmp/xmod/pool-lib.ts
export class MiniPool {
url: string;
constructor(opts: { url: string }) { this.url = opts.url; }
async query(sql: string): Promise<{ rows: unknown[] }> {
return { rows: [{ sql, ok: true }] };
}
}
// /tmp/xmod/main.ts
import { MiniPool } from "./pool-lib";
async function main() {
const p = new MiniPool({ url: "x://test" });
console.log("typeof p.query:", typeof (p as { query?: unknown }).query);
const r = await p.query("SELECT 1");
console.log("result:", JSON.stringify(r));
}
main().catch((e) => { console.error(e); process.exit(2); });
$ perry compile main.ts -o probe && ./probe
typeof p.query: function ✓
result: {"rows":[{"sql":"SELECT 1","ok":true}]}
Broken case — same shape, but importing from precompiled JS in node_modules:
// uses @perryts/mysql which is symlinked to a sibling repo with prebuilt dist/
import { Pool } from "@perryts/mysql";
async function main() {
const p = new Pool({ url: "mysql://root:@127.0.0.1:3306/test" });
console.log("typeof p.query:", typeof (p as { query?: unknown }).query);
console.log("typeof p.end:", typeof (p as { end?: unknown }).end);
console.log("Object.keys(p):", Object.keys(p as object).join(","));
console.log("proto props:",
Object.getOwnPropertyNames(Object.getPrototypeOf(p) as object).join(","));
}
main().catch((e) => { console.error(e); process.exit(2); });
$ perry compile probe.ts -o probe && ./probe
typeof p.query: undefined ✗
typeof p.end: undefined ✗
Object.keys(p):
proto props:
Same code under tsx probe.ts works correctly:
$ npx tsx probe.ts
typeof p.query: function
typeof p.end: function
The driver's dist/pool.js defines class Pool { ... async query(sql, params) { ... } ... } exactly the way perry's own minimal class smoke test handles correctly when authored as .ts. The difference is purely the file extension on disk: .ts cross-module imports preserve methods; .js cross-module imports do not.
Direct-subpath bypass also fails
Tried import { Pool } from "@perryts/mysql/dist/pool.js" to skip the index re-export chain. Same result — typeof p.query is undefined.
Environment
perry 0.5.771 (target/release perry-runtime + perry-stdlib rebuilt from c5adb488 immediately before testing)
@perryts/mysql 0.1.3 (symlinked from local sibling repo, dist/ is the published precompiled output)
--target macos, arm64
Discovery context
Hit this trying to confirm #639's residual symptom in the native server. The earlier "pool.query returns empty object" symptom turned out to be downstream of this: pool.query was returning the object literal that the await-wrapper produces for an undefined function call, which has no own properties. With the prototype methods missing, every read path on the native server is dead.
This regressed somewhere in the v0.5.760..0.5.771 window — earlier in this project (when perry was around 0.5.706 / 0.5.739) pool.query was a callable function on Pool instances; the bug surfaced as the result of pool.query being malformed (#639), not the callability of pool.query itself.
Suggested triage area
crates/perry-codegen/src/lower_call.rs and crates/perry-codegen/src/lower_class.rs — the c538bc61 / 9081c206 cross-module class fixes from v0.5.758–v0.5.760 may have added a check that fires for .ts cross-module imports but skips for .js files (or vice versa). Worth diffing the class-method-installation path between source-graph kinds.
Summary
Importing a
classfrom a precompiled.jsfile innode_modulesproduces instances whose prototype methods are missing.typeof instance.someMethodreturns"undefined"and calling it throwsTypeError. The same class hand-written as a sibling.tsfile and imported normally works correctly.This breaks any precompiled npm-published package that exposes a class with instance methods — concretely it currently blocks
@perryts/mysql'sPool.query()and (presumably)Connection.query(), which means the entire driver is unusable from a Perry-compiled binary.Reproducer (3 files)
Working case — hand-written cross-module
.ts:Broken case — same shape, but importing from precompiled JS in node_modules:
$ perry compile probe.ts -o probe && ./probe typeof p.query: undefined ✗ typeof p.end: undefined ✗ Object.keys(p): proto props:Same code under
tsx probe.tsworks correctly:The driver's
dist/pool.jsdefinesclass Pool { ... async query(sql, params) { ... } ... }exactly the way perry's own minimal class smoke test handles correctly when authored as.ts. The difference is purely the file extension on disk:.tscross-module imports preserve methods;.jscross-module imports do not.Direct-subpath bypass also fails
Tried
import { Pool } from "@perryts/mysql/dist/pool.js"to skip the index re-export chain. Same result —typeof p.queryis undefined.Environment
perry 0.5.771(target/releaseperry-runtime+perry-stdlibrebuilt fromc5adb488immediately before testing)@perryts/mysql 0.1.3(symlinked from local sibling repo, dist/ is the published precompiled output)--target macos, arm64Discovery context
Hit this trying to confirm #639's residual symptom in the native server. The earlier "pool.query returns empty object" symptom turned out to be downstream of this:
pool.querywas returning the object literal that the await-wrapper produces for an undefined function call, which has no own properties. With the prototype methods missing, every read path on the native server is dead.This regressed somewhere in the v0.5.760..0.5.771 window — earlier in this project (when perry was around 0.5.706 / 0.5.739)
pool.querywas a callable function on Pool instances; the bug surfaced as the result ofpool.querybeing malformed (#639), not the callability ofpool.queryitself.Suggested triage area
crates/perry-codegen/src/lower_call.rsandcrates/perry-codegen/src/lower_class.rs— thec538bc61/9081c206cross-module class fixes from v0.5.758–v0.5.760 may have added a check that fires for.tscross-module imports but skips for.jsfiles (or vice versa). Worth diffing the class-method-installation path between source-graph kinds.