## v0.5.836 — fix(cjs-wrap): #665 — `module.exports = { X, Y }` object-literal aggregator now forwards class identity through index files. **Symptom.** `import { RateLimiterMemory } from "rate-limiter-flexible"` returns an instance with every prototype method undefined: `typeof limiter.consume === "undefined"`, `await limiter.consume(...)` becomes `TypeError: value is not a function`. Discovered by the user's native-server boot path during the first authed POST after #609 + #639 + #652 all landed. **Architecture.** `rate-limiter-flexible/index.js` (and the published shape of many older npm packages) aggregates leaf classes via object-literal exports: `const X = require('./lib/X'); ... module.exports = { X, Y, ... };`. The v0.5.808 fix handled the property-assignment shape (`exports.X = require('./Y')`) but not the object-literal-with-shorthand shape. Pre-fix the wrap emitted `export const X = _cjs.X;` for these names — a runtime property read on the IIFE result. HIR can't see through that read to the underlying class declaration in the required file, so `new X(...)` produced an empty object with no methods. The v0.5.808 emit `export { _req_N as X };` already exists for the property-assignment shape; this commit extends it to the object-literal shape. **Fix.** New helper `extract_object_literal_exports_from_require` in `crates/perry/src/commands/compile/cjs_wrap.rs`: locates the LAST top-level `module.exports = { ... }` (later assignments win at runtime, so they win in our static analysis too), brace-balanced parses the body, splits at top-level commas (respecting nested braces/brackets/strings/templates/comments), then matches each entry as shorthand `X` or longhand `X: Y` and looks the alias up against the `const|let|var X = require('Y')` binding table. Computed keys, spreads, methods, and non-alias RHS expressions are skipped — those still need the `_cjs.X` runtime path. The detected pairs union into `named_reexport_requires` and flow through the existing `export { _req_N as X };` emit pipeline (the same one v0.5.808 wired up for `exports.X = require(...)`), so compile.rs's `Export::Named` arm walks default-import specifiers and forwards the leaf module's `"default"`-keyed class into this module's `exported_classes` under the aliased name — class identity survives the indirection. **Validation.** New 3-file Perry repro (`/tmp/test_665_main/{node_modules/test665pkg/{index.js,lib/{Child,Base}.js},main.ts}`) modeling the rate-limiter-flexible aggregator shape: `index.js` has `const Child = require('./lib/Child'); const Base = require('./lib/Base'); module.exports = { Child, Base };`, `lib/Child.js` has `class Child extends Base { ... }; module.exports = Child;`. Pre-fix output: `typeof c.greetChild: undefined / typeof c.greetBase: undefined`. Post-fix output: `function / function / child:hi / base:hi` — byte-identical to `npx tsx main.ts`. The published CJS-wrap output for the index.js confirms the emit shape: `export { _req_0 as Child };\nexport { _req_1 as Base };` instead of the previous `export const Child = _cjs.Child;\nexport const Base = _cjs.Base;`. Six new unit tests in `cjs_wrap::tests` cover shorthand, longhand, mixed-with-skipped-entries, no-require-aliases-fallthrough, last-assignment-wins, and end-to-end wrap emit. Full `cargo test -p perry cjs_wrap`: 30 tests pass (24 existing + 6 new). Full workspace test suite green. **Out of scope.** A separate `super(opts)` plumbing gap surfaces when the leaf class's constructor body sets `this.X = X` in the parent and the child calls `super(...)` — fields don't land on `this`. This is a pre-existing inheritance bug independent of #665's class-identity-forwarding ask and warrants its own focused issue. The issue's headline ("every method on the instance is undefined") is fully resolved.
0 commit comments