Skip to content

fix(compile): namespace import of CJS default re-export resolves member value (#4841)#4874

Merged
proggeramlug merged 1 commit into
mainfrom
fix-4841-namespace-cjs-reexport
Jun 10, 2026
Merged

fix(compile): namespace import of CJS default re-export resolves member value (#4841)#4874
proggeramlug merged 1 commit into
mainfrom
fix-4841-namespace-cjs-reexport

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Fixes #4841 — Stripe SDK stripe.products.create({ name }) threw TypeError: replace is not a function on every request.

Root cause

import * as qs from "qs" then qs.stringify(data, opts).replace(/%5B/g, "[") (Stripe's utils.js). qs.stringify(...) returned the qs stringify function instead of a string, so .replace was undefined.

The real bug is in import * as ns namespace-member resolution. qs/lib/index.js is CommonJS:

var stringify = require('./stringify');   // ./stringify is `module.exports = function(...)`
module.exports = { formats, parse, stringify };

The submodule records its export under the synthetic "default" suffix, not the consumer-visible member name stringify. The namespace-import var-vs-function classifier keyed only on (origin_path, "stringify"), missed the (origin_path, "default") entry, and classified stringify as a function. That routed ns.stringify through the singleton-closure wrap of the default getter, so ns.stringify(args) returned the function value instead of being it (typeof ns.stringify(...) === "function", .length === 0).

The default import (import qs from "qs"; qs.stringify) worked because it reads module.exports.stringify directly — only the import * as namespace path was broken.

Minimal repro:

// node_modules/mini/index.js : var sfy = require('./sfy'); module.exports = { sfy };
// node_modules/mini/sfy.js   : module.exports = function (o) { return "S:" + o; };
import * as m from "mini";
(m as any).sfy("X");   // before: returns a function (len 0); after: "S:X"

Fix

The namespace arm in compile.rs now probes both (origin_path, member) and (origin_path, origin_name) against exported_var_names, mirroring the existing named-import arm (the origin_key_under_origin_name dual-probe). With the member correctly classified as a var, ns.stringify reads the getter's value.

One-arm change + an e2e regression test.

Verification

Built on Linux x86_64 (where the bug reproduced) and ran the real Stripe SDK repro:

  • Before: ERR: TypeError replace is not a function
  • After: stripe.products.create({ name }) builds and dispatches the request, reaching Stripe's API (the request layer is no longer broken).
  • qs.stringify({a:1,b:"x"})"a=1&b=x" (was the function value).

The remaining "unsettled top-level await" when awaiting the real network call is a separate, pre-existing async-HTTP-settling issue (already noted in #4841 as out of scope) — identical on macOS, and unrelated to the .replace failure this PR fixes.

Regression-checked normal namespace imports (real function/const exports, default imports) and the existing issue_4858 / native_link_cache integration tests — all green.

…er value (#4841)

`import * as ns from "<cjs-pkg>"` where a namespace member is a re-export of a
CommonJS submodule's `default` (`var sfy = require('./sfy'); module.exports =
{ sfy }`, with `./sfy` being `module.exports = function`) resolved the member to
a wrapper closure instead of the underlying value.

The submodule records its export under the synthetic `"default"` suffix, not the
consumer-visible member name. The namespace-import var-vs-function classifier
keyed only on `(origin_path, member)`, missed the `(origin_path, "default")`
entry, and classified the member as a FUNCTION. That routed `ns.sfy` through the
singleton-closure wrap of the default getter, so `ns.sfy(args)` RETURNED the
function value rather than being it (`typeof ns.sfy(...) === "function"`,
length 0).

This was the root cause of the Stripe SDK failure: Stripe's `utils.js` does
`import * as qs from 'qs'` and `qs.stringify(data, opts).replace(/%5B/g, '[')`.
`qs.stringify(...)` returned the qs stringify *function* (not a string), so
`.replace` was undefined → `TypeError: replace is not a function` on every
request. Verified on Linux: `stripe.products.create({ name })` now builds and
dispatches the request (reaches Stripe's API) instead of throwing.

Fix: the namespace arm now probes both `(origin_path, member)` and
`(origin_path, origin_name)` against `exported_var_names`, mirroring the
existing named-import arm. Adds an e2e regression test.
@proggeramlug proggeramlug merged commit f32b486 into main Jun 10, 2026
13 checks passed
@proggeramlug proggeramlug deleted the fix-4841-namespace-cjs-reexport branch June 10, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Stripe SDK: stripe.products.create throws 'replace is not a function' (request dispatch, flat body) after #4831

1 participant