Skip to content

feat: unevalStream - #218

Draft
elliott-with-the-longest-name-on-github wants to merge 4 commits into
mainfrom
elliott/warp-simple
Draft

elliott-with-the-longest-name-on-github wants to merge 4 commits into
mainfrom
elliott/warp-simple

Conversation

@elliott-with-the-longest-name-on-github

@elliott-with-the-longest-name-on-github elliott-with-the-longest-name-on-github commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Adds unevalStream, a streaming version of uneval for values that contain promises. This is a much smaller alternative to #183, which had grown to ~3,450 lines plus ~9,500 lines of tests.

const { head, tail, id } = devalue.unevalStream(value, replacer, options); // synchronous
  • head is an expression that recreates the value with every promise pending, no matter what has already settled, so the output is deterministic and doesn't depend on timing. Callers that want already-settled promises inlined can fold them in themselves with the replacer (js`Promise.resolve(${value})` ).
  • tail is an async iterable of statement blocks that settle those promises as they settle on the server. A settled value can contain more promises, which later blocks settle.
  • Object identity is preserved across head and every block, and the replacer is called at most once per object for the whole stream.
  • Options: id (stable session key, for when heads need to be byte-identical), scope (where the client session table lives, globalThis.__d by default), signal and onerror.

It's built on the existing uneval emitter. The emitter gets a few internal hooks (enter, replace, reference, retain), so the stream can record where each object lives on the client during the emitter's own walk and refer to it by path in later blocks.

Tests are a table-driven round-trip harness. It checks every case across settlement orders, evaluating blocks separately vs. concatenated, identity, once-per-object replacer calls and session cleanup. On top of that are random-graph, UTF-8 transport and security regressions ported from #183.

Stack created with GitHub Stacks CLI • Give Feedback 💬

Synchronous unevalStream(value, replacer, options) returning { head, tail, id }, built on the uneval emitter. Adds an internal `enter` emitter hook so the session records client locations during the emitter's own walk, and moves the replacer result check to a shared `call_replacer` util.
Adds an expect_roundtrip harness that checks every case across settlement orders, separate and concatenated evaluation, identity, once-per-identity replacer calls, and session cleanup. Ports random-graph, UTF-8 transport and security regressions from elliott/warp. Tail blocks now end in a semicolon so they can be concatenated.
Exports UnevalStreamOptions and UnevalStreamResult and uses them in unevalStream's JSDoc. Documents usage, guarantees, options, constraints and custom thenables.
@changeset-bot

changeset-bot Bot commented Oct 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f738f78

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
devalue Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@elliott-with-the-longest-name-on-github
elliott-with-the-longest-name-on-github added this pull request to stack #220 October 6, 2026 19:13
@elliott-with-the-longest-name-on-github elliott-with-the-longest-name-on-github changed the title elliott/warp simple feat: unevalStream Oct 6, 2026
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