Skip to content

class methods from precompiled .js (node_modules) drop on instance — typeof p.query is undefined #652

Description

@proggeramlug

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions