Skip to content

feat(codemode): expose host classes and functions through extensions - #48941

Merged
rekram1-node merged 4 commits into
v2from
extensions
Sep 14, 2026
Merged

rekram1-node merged 4 commits into
v2from
extensions

Conversation

@rekram1-node

Copy link
Copy Markdown
Collaborator

Hosts can hand a program real classes and functions. They behave like JavaScript inside the program; every value that crosses the boundary is converted, never shared, so the program cannot reach or mutate host objects.

const web = Extension.make({ name: "bag", globals: { Bag, fetchThing } })
const runtime = CodeMode.make({ tools, extensions: [web] })
// program
const b = new Bag(["a"])
b.add("b").size            // 2       — method chain, accessor
Bag.of("x") instanceof Bag // true    — statics, instanceof
await fetchThing(1)        // host Promise → program promise

What crosses, and how

program → host (arguments, setter values)
  primitive / plain object / array        copy (__proto__ dropped)
  handle                                  the host instance, brand-checked
  Map / Set                               new host Map/Set, each key and value converted
  Date / RegExp / URL / URLSearchParams   fresh host copy
  Error                                   host Error with name + message
  function / generator / promise          TypeError

host → program (returns, getter values, promise settlements)
  primitive / plain object / array        copy, recursing into nested handles
  instance of an exposed class            handle — same instance, same handle, per run
  Map / Set / Date / RegExp / URL / …     fresh program wrapper
  Error                                   program Error of the matching type
  Promise                                 program promise
  anything else                           TypeError

The handle

ProgramHandle extends ProgramObject
  proto      per-run prototype built from the class (methods, accessors, statics, inherited members)
  instance   the host object — a TS field, not a property, so no enumeration or spread reaches it
const b = new Bag()
Object.keys(b)             // []
JSON.stringify(b)          // InvalidDataValue
return b                   // InvalidDataValue
await tools.echo({ v: b }) // InvalidDataValue

The one hole the reviews found, closed

A program can copy any prototype method onto any object. Every member checks its receiver is a handle whose instance is instanceof the owning class:

const v = vault()
v.add = Bag.prototype.add
v.add("x")   // TypeError: Illegal invocation: Bag.prototype.add called on a Vault

Two latent side doors, closed while here

 // data.ts — fromData never sees program objects; one arriving would come back as a host object
+if (value instanceof ProgramObject && mode === "data") throw …

 // object.ts — Object(x) returned any object; now only program objects
-if (typeof first === "object") return first
+if (first instanceof ProgramObject) return first

Files

src/extension.ts                  Extension.make — validates globals are classes or functions
src/interpreter/extensions.ts     per-run prototypes, handles, toHost / fromHost conversion
src/interpreter/globals.ts        built-ins become a table so names are known at make time (collisions throw)
src/interpreter/objects.ts        ProgramHandle
src/data.ts, references.ts        handles refuse the data boundary; fromData rejects program objects
src/codemode.ts                   `extensions` option
test/extensions.test.ts           25 tests
interpreter-support.md            Extensions section

Not in this PR: program callbacks into extension code, binary values, and the fetch/Headers/Response extension itself (next PR).

Checks

  • bun run check clean; bun test 5535 pass / 0 fail.
  • Design reviewed against engine262, QuickJS, Boa, workerd jsg, and Hermes JSI, plus an adversarial review of the codebase for host-corruption paths.

@rekram1-node
rekram1-node merged commit 3468a09 into v2 Sep 14, 2026
8 checks passed
@rekram1-node
rekram1-node deleted the extensions branch September 14, 2026 17:29
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.

1 participant