Found while validating the removal of the native decimal.js binding (#10684) — with the binding
gone, import Decimal from "decimal.js" now compiles the real npm package from source (via #10699).
Every value checked matches Node 26.5.1 exactly (1/3, 10/4, the large multiplication that used to
abort the process, instanceof, sqrt, pow, toFixed, toPrecision, cmp, chained arithmetic) —
except constructor.name, which is empty instead of "Decimal".
Reproduction
{ "dependencies": { "decimal.js": "^10.6.0" }, "type": "module" }
import Decimal from "decimal.js";
const d = new Decimal(5);
console.log(d instanceof Decimal); // true on both (fixed by #10477)
console.log(d.constructor.name); // Node: "Decimal" Perry: ""
console.log((Decimal as any).name); // Node: "Decimal" Perry: ""
Root cause (as far as I traced it)
decimal.js's real source has no class Decimal; it is an ES5-style function constructor built by a
clone() factory and exported CommonJS-style (module.exports = Decimal;, decimal.js:4935) — the
same shape #10477 already tracked for instanceof (now fixed).
Isolated repros:
- A function constructor built by a same-file factory (
function clone() { function Decimal(v) {...} return Decimal; }) — .name and .constructor.name are both correct.
- The same shape with the prototype wholesale-replaced (
Decimal.prototype = { constructor: Decimal, ... }, matching decimal.js exactly) in the same file — still correct.
- The same shape moved to a separate module and exported via
module.exports = Decimal (CJS),
then imported with import Decimal from "./lib.js" — Decimal.name itself is already empty
before .constructor even enters the picture.
So the loss happens specifically in the CJS-import interop path for a plain (non-class) function
value: whatever rebinds the CJS export at the import site is not preserving the underlying function
object's own .name. instanceof (#10477) and ordinary calls/construction work fine through the same
path; only the .name property is lost.
Impact
Any npm package (CJS or UMD, decimal.js/bignumber.js among them) that exports an ES5-style function
constructor and whose callers inspect .name / .constructor.name (error reporting, assert-style
type tags, some validation/serialization libraries) will see an empty string where Node reports the
real name.
Not fixed by, and not caused by, #10684's binding removal — decimal.js's own arithmetic, instanceof,
and everything else checked in that PR's acceptance test match Node exactly.
Found while validating the removal of the native
decimal.jsbinding (#10684) — with the bindinggone,
import Decimal from "decimal.js"now compiles the real npm package from source (via #10699).Every value checked matches Node 26.5.1 exactly (
1/3,10/4, the large multiplication that used toabort the process,
instanceof,sqrt,pow,toFixed,toPrecision,cmp, chained arithmetic) —except
constructor.name, which is empty instead of"Decimal".Reproduction
{ "dependencies": { "decimal.js": "^10.6.0" }, "type": "module" }Root cause (as far as I traced it)
decimal.js's real source has no
class Decimal; it is an ES5-style function constructor built by aclone()factory and exported CommonJS-style (module.exports = Decimal;,decimal.js:4935) — thesame shape #10477 already tracked for
instanceof(now fixed).Isolated repros:
function clone() { function Decimal(v) {...} return Decimal; }) —.nameand.constructor.nameare both correct.Decimal.prototype = { constructor: Decimal, ... }, matching decimal.js exactly) in the same file — still correct.module.exports = Decimal(CJS),then imported with
import Decimal from "./lib.js"—Decimal.nameitself is already emptybefore
.constructoreven enters the picture.So the loss happens specifically in the CJS-import interop path for a plain (non-class) function
value: whatever rebinds the CJS export at the import site is not preserving the underlying function
object's own
.name.instanceof(#10477) and ordinary calls/construction work fine through the samepath; only the
.nameproperty is lost.Impact
Any npm package (CJS or UMD, decimal.js/bignumber.js among them) that exports an ES5-style function
constructor and whose callers inspect
.name/.constructor.name(error reporting,assert-styletype tags, some validation/serialization libraries) will see an empty string where Node reports the
real name.
Not fixed by, and not caused by, #10684's binding removal — decimal.js's own arithmetic,
instanceof,and everything else checked in that PR's acceptance test match Node exactly.