Following up on #652 (closed in v0.5.795 via dee8659): the ESM export class shape is now fixed — @perryts/mysql's Pool instantiates and pool.query(...) is a function. But the CJS module.exports = Class + extends shape still crashes:
TypeError: value is not a function
at <anonymous>
Hit this trying to use rate-limiter-flexible's RateLimiterMemory from a Perry-compiled native server: every method on the instance is undefined (typeof limiter.consume === "undefined"), and our request handler crashes with the TypeError above before any HTTP body is written.
Reproducer (3 files, total ~12 lines of code)
// /tmp/cjs-class/base.js
class Base {
greetBase(n) { return "base:" + n; }
}
module.exports = Base;
// /tmp/cjs-class/child.js
const Base = require('./base');
class Child extends Base {
constructor(opts) { super(opts); this.opts = opts; }
greetChild(n) { return "child:" + n; }
}
module.exports = Child;
// /tmp/cjs-class/main.ts
const Child = require('./child');
async function main() {
const c = new Child({ x: 1 });
console.log("typeof c.greetChild:", typeof c.greetChild);
console.log("typeof c.greetBase:", typeof c.greetBase);
if (typeof c.greetChild === "function") console.log(c.greetChild("hi"));
if (typeof c.greetBase === "function") console.log(c.greetBase("hi"));
}
main().catch((e) => { console.error(e); process.exit(2); });
$ perry compile main.ts -o probe && ./probe
TypeError: value is not a function
at <anonymous>
$ echo $?
1
The crash fires during new Child({ x: 1 }) itself — before either typeof log can run. So it's not just the methods that vanish; the construction path bails too. Same code under tsx main.ts works correctly:
$ npx tsx main.ts
typeof c.greetChild: function
typeof c.greetBase: function
child:hi
base:hi
Difference from the original #652 case
@perryts/mysql's Pool is an ESM module: import { connect } from './connection'; export class Pool { ... } with bare class fields and no inheritance. dee8659's cjs_wrap fix unblocked that.
rate-limiter-flexible's RateLimiterMemory (and the published shape of many older npm packages) is CommonJS:
const RateLimiterAbstract = require('./RateLimiterAbstract');
class RateLimiterMemory extends RateLimiterAbstract {
constructor(opts = {}) { super(opts); ... }
consume(key, ...) { ... }
}
module.exports = RateLimiterMemory;
Three things stack here that the ESM repro didn't have:
module.exports = Class (single-export, no destructuring on the consumer side)
class … extends Base
require('./relative-cjs-file') returning the bare class
Any one of those may be the trigger; they appear together in rate-limiter-flexible's output.
Discovery context
Native server boots through migrations + crypto KID + listening (#609 + #639 + #652 ESM-class fix all good ✓), serves /healthz / /readyz / /metrics correctly, but the very first POST /v1/auth/signup dies in our rate-limit middleware:
await signupLimiter.consume(ipKey(req, "signup")); // ← TypeError here
signupLimiter is new RateLimiterMemory({...}). With the precompiled-CJS instance methods missing, .consume is undefined.
Same code under tsx server/main.ts runs the entire 24-suite e2e green. Only the perry-compiled native binary path fails.
Environment
perry 0.5.795 (target/release perry-runtime + perry-stdlib rebuilt from dee86595)
rate-limiter-flexible@5.0.x (precompiled CJS in node_modules)
--target macos, arm64
Why we filed
After your dee8659 ESM-class fix, the native server made it all the way through boot for the first time — major progress. This CJS variant blocks it on the very first authed request. Once it lands, every other CJS-published npm package's classes (most of the ecosystem) becomes usable from Perry-compiled binaries.
Following up on #652 (closed in v0.5.795 via dee8659): the ESM
export classshape is now fixed —@perryts/mysql'sPoolinstantiates andpool.query(...)is a function. But the CJSmodule.exports = Class+extendsshape still crashes:Hit this trying to use
rate-limiter-flexible'sRateLimiterMemoryfrom a Perry-compiled native server: every method on the instance isundefined(typeof limiter.consume === "undefined"), and our request handler crashes with the TypeError above before any HTTP body is written.Reproducer (3 files, total ~12 lines of code)
The crash fires during
new Child({ x: 1 })itself — before eithertypeoflog can run. So it's not just the methods that vanish; the construction path bails too. Same code undertsx main.tsworks correctly:Difference from the original #652 case
@perryts/mysql'sPoolis an ESM module:import { connect } from './connection'; export class Pool { ... }with bare class fields and no inheritance. dee8659'scjs_wrapfix unblocked that.rate-limiter-flexible'sRateLimiterMemory(and the published shape of many older npm packages) is CommonJS:Three things stack here that the ESM repro didn't have:
module.exports = Class(single-export, no destructuring on the consumer side)class … extends Baserequire('./relative-cjs-file')returning the bare classAny one of those may be the trigger; they appear together in
rate-limiter-flexible's output.Discovery context
Native server boots through migrations + crypto KID + listening (#609 + #639 + #652 ESM-class fix all good ✓), serves
/healthz//readyz//metricscorrectly, but the very firstPOST /v1/auth/signupdies in our rate-limit middleware:signupLimiterisnew RateLimiterMemory({...}). With the precompiled-CJS instance methods missing,.consumeis undefined.Same code under
tsx server/main.tsruns the entire 24-suite e2e green. Only the perry-compiled native binary path fails.Environment
perry 0.5.795(target/releaseperry-runtime+perry-stdlibrebuilt fromdee86595)rate-limiter-flexible@5.0.x(precompiled CJS in node_modules)--target macos, arm64Why we filed
After your dee8659 ESM-class fix, the native server made it all the way through boot for the first time — major progress. This CJS variant blocks it on the very first authed request. Once it lands, every other CJS-published npm package's classes (most of the ecosystem) becomes usable from Perry-compiled binaries.