Summary
export * as ns from "./m.ts" re-exports correctly as a named binding but is missing from the re-exporting module's namespace object. import * as B from "./b.ts" sees B.ns === undefined and Object.keys(B) omits it entirely, while import { ns } from "./b.ts" works.
Deterministic, GC-independent, reproduces at d8be5f4a2 in the default configuration (PERRY_NO_AUTO_OPTIMIZE=1, perry-dev build, macOS arm64).
Minimal repro (3 files)
ns2.ts
export const alpha = 1;
export function beta() { return 2; }
ns3.ts
export * as deep from "./ns2.ts";
export const gamma = 3;
nsmain.ts
import * as B from "./ns3.ts";
import { deep } from "./ns3.ts";
console.log("namespace-object member :", typeof B.deep);
console.log("named import of same :", typeof deep, deep && (deep as any).alpha);
console.log("keys of B :", JSON.stringify(Object.keys(B).sort()));
console.log("B.gamma :", B.gamma);
|
node 26.5.1 |
perry d8be5f4a2 |
typeof B.deep |
object |
undefined |
typeof deep, deep.alpha |
object 1 |
object 1 ✅ |
Object.keys(B) |
["deep","gamma"] |
["gamma"] |
B.gamma |
3 |
3 ✅ |
So the binding exists — it just never lands on the namespace object built for import * as.
Why it matters: zod
zod's src/index.ts is import * as z from "./v4/classic/external.js"; export { z };, and external.ts re-exports four sub-namespaces exactly this way:
export * as core from "../core/index.js";
export * as locales from "../locales/index.js";
export * as iso from "./iso.js";
export * as coerce from "./coerce.js";
All four are undefined under perry, so every consumer of these breaks:
import { z } from "zod";
z.coerce.number().safeParse("42"); // TypeError: Cannot read properties of undefined (reading 'number')
z.iso.datetime().safeParse("..."); // TypeError: Cannot read properties of undefined (reading 'datetime')
z.core and z.locales are likewise undefined. Interestingly Object.keys(z).length is 238 under both node and perry, so perry is contributing four other keys in their place — worth checking whether the namespace-object builder is mis-keying rather than merely dropping.
Environment
zod 4.4.3 via perry.compilePackages, Node 26.5.1 (.node-version pin), perry @ d8be5f4a2, macOS arm64. Found while investigating #7154.
Refs #7154.
Summary
export * as ns from "./m.ts"re-exports correctly as a named binding but is missing from the re-exporting module's namespace object.import * as B from "./b.ts"seesB.ns === undefinedandObject.keys(B)omits it entirely, whileimport { ns } from "./b.ts"works.Deterministic, GC-independent, reproduces at
d8be5f4a2in the default configuration (PERRY_NO_AUTO_OPTIMIZE=1, perry-dev build, macOS arm64).Minimal repro (3 files)
ns2.tsns3.tsnsmain.tsd8be5f4a2typeof B.deepobjectundefinedtypeof deep,deep.alphaobject 1object 1✅Object.keys(B)["deep","gamma"]["gamma"]B.gamma33✅So the binding exists — it just never lands on the namespace object built for
import * as.Why it matters: zod
zod's
src/index.tsisimport * as z from "./v4/classic/external.js"; export { z };, andexternal.tsre-exports four sub-namespaces exactly this way:All four are
undefinedunder perry, so every consumer of these breaks:z.coreandz.localesare likewiseundefined. InterestinglyObject.keys(z).lengthis 238 under both node and perry, so perry is contributing four other keys in their place — worth checking whether the namespace-object builder is mis-keying rather than merely dropping.Environment
zod 4.4.3 via
perry.compilePackages, Node 26.5.1 (.node-versionpin), perry @d8be5f4a2, macOS arm64. Found while investigating #7154.Refs #7154.