fix: restore legacy-app module type via loose SWC lowering - #65
Conversation
The toolchain migration (2.18.0) switched the ES5 build from tsc to SWC. SWC's default (spec-faithful) lowering compiles super-constructor calls to `Reflect.construct(Parent, args, newTarget)` and class fields to `_defineProperty(...)`. This breaks the CoffeeScript-style inheritance that built-in gateway apps (e.g. gateway:CustomWebHook) rely on: those apps invoke the parent constructor as a plain function on an existing `this` (`Super.apply(this)` / `__super__.constructor.call(this)`). Under `Reflect.construct` the base field initializer (`type = ModuleType.TRIGGER`) runs on a detached throwaway object instead of that `this`, so the module the engine uses ends up with `type === undefined`. Every `switch (moduleType)` in the engine then falls through to its default branch, producing errors such as "UNKNOWN TYPE of a MODULE" at operation-end logging. Enable `jsc.loose` in .swcrc so super calls compile to `Super.apply(this, args) || this` and fields to `this.x = ...` assignments — matching the pre-migration tsc ES5 output and preserving the contract the .inherits() compat shim promises. The narrower `useDefineForClassFields: false` is not sufficient: it fixes the field emit but leaves `Reflect.construct` in the super helper, so the instance is still detached. Test infra: - vitest.config.ts: mirror `.swcrc` with `loose: true` so tests exercise the same lowering the lib ships (the inline transform previously diverged from the build, which is why this shipped undetected). - legacy-compatibility.test.ts: the existing CoffeeScript/.inherits tests only asserted `instanceof` — which passes even under the detaching fork. Assert `instance.type` against a native-`new` instance so the regression is caught. Bump version to 2.18.1 (never reship a fixed artifact under 2.18.0). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generated |
There was a problem hiding this comment.
Pull request overview
This PR restores legacy CoffeeScript-style inheritance compatibility for ES5 builds by switching SWC to jsc.loose lowering (avoiding Reflect.construct + _defineProperty helpers that can detach instance field initialization from the legacy this). It also aligns the Vitest SWC transform with the library’s build lowering and adds regression assertions to catch the type === undefined failure mode, then bumps the package version for release.
Changes:
- Enable SWC
jsc.loose: truein the build (.swcrc) to match the pre-migration ES5 semantics for super calls and class fields. - Mirror the same lowering in
vitest.config.tsso tests exercise the shipped compilation behavior. - Strengthen legacy compatibility tests by asserting that
typeis initialized consistently vs a nativenewinstance, and bump version to2.18.1.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
vitest.config.ts |
Mirrors .swcrc by enabling jsc.loose in the Vitest SWC transform so tests match the shipped ES5 lowering. |
test/legacy-compatibility.test.ts |
Adds regression assertions ensuring base instance fields (e.g. type) land on the legacy this, not a detached object. |
package.json |
Bumps package version to 2.18.1 for the fix release. |
.swcrc |
Enables jsc.loose: true for the library build to restore legacy-compatible ES5 output. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|





The toolchain migration (2.18.0) switched the ES5 build from tsc to SWC. SWC's default (spec-faithful) lowering compiles super-constructor calls to
Reflect.construct(Parent, args, newTarget)and class fields to_defineProperty(...). This breaks the CoffeeScript-style inheritance that built-in gateway apps (e.g. gateway:CustomWebHook) rely on: those apps invoke the parent constructor as a plain function on an existingthis(Super.apply(this)/__super__.constructor.call(this)). UnderReflect.constructthe base field initializer (type = ModuleType.TRIGGER) runs on a detached throwaway object instead of thatthis, so the module the engine uses ends up withtype === undefined. Everyswitch (moduleType)in the engine then falls through to its default branch, producing errors such as "UNKNOWN TYPE of a MODULE" at operation-end logging.Enable
jsc.loosein .swcrc so super calls compile toSuper.apply(this, args) || thisand fields tothis.x = ...assignments — matching the pre-migration tsc ES5 output and preserving the contract the .inherits() compat shim promises. The narroweruseDefineForClassFields: falseis not sufficient: it fixes the field emit but leavesReflect.constructin the super helper, so the instance is still detached.Test infra:
.swcrcwithloose: trueso tests exercise the same lowering the lib ships (the inline transform previously diverged from the build, which is why this shipped undetected).instanceof— which passes even under the detaching fork. Assertinstance.typeagainst a native-newinstance so the regression is caught.Bump version to 2.18.1 (never reship a fixed artifact under 2.18.0).
https://make.atlassian.net/browse/BARI-12