Skip to content

feat!: use tagged JSON end to end - #131

Merged
KyleJune merged 6 commits into
mainfrom
feat/json-hydration-payload
Sep 13, 2026
Merged

KyleJune merged 6 commits into
mainfrom
feat/json-hydration-payload

Conversation

@KyleJune

@KyleJune KyleJune commented Sep 11, 2026

Copy link
Copy Markdown
Member

Summary

Juniper now uses tagged JSON text for HTML hydration, settled client data, and streamed deferred data. This widens the existing hydration PR into the complete breaking minor switch requested for 0.12.0.

Changes

  • Remove cbor2 and numeric wire tags. Hydration is version 3 only, with public environment values inside the same tagged value; unsupported versions take the existing guarded reload path.
  • Mark framework data with X-Juniper: data. Settled responses use application/json and UTF-8 Content-Length; deferred responses use application/x-ndjson, pull-based back-pressure, cancellation, and Cache-Control: no-transform.
  • Keep registerType/registerError/registerContext signatures and interfaces. Recursively process serializer output, reject self-matching output and unknown registrations, isolate failed stream resolutions, and protect error envelope names and framework metadata.
  • Preserve own $t/proto data, script escaping, error privacy, build-skew checks, reload budgets, redirects, and the isbot wait-for-all-ready path. Update package guides, JSDoc, README, deferred example copy, and the minor release rule.

Testing

  • Documentation follow-up 0293095: package checks and 43 suites/480 steps pass; the six documentation-gate regressions pass, with red/green evidence and separate mutations for both Builder helpers. CI for this head is pending.

  • CI at 9ff4692: macOS, Windows, Ubuntu, formatting/lint, PostgreSQL template, coverage, PR title, and preview build all passed. CI run. Release is skipped until merge.

  • deno task check: passed, including configured documentation lint and JSDoc examples for all nine exports.

  • deno task test --parallel --reporter=dot: 43 suites, 480 steps passed with sanitizers enabled.

  • Production example build passed. Eager JS (static imports, each file Brotli quality 11): 103,824 B on released 0.11.6 -> 92,298 B, saving 11,526 B (11.1%). Raw JS: 366399 -> 326649 B. No cbor2 reference remains in src or deno.lock.

  • Four independent review perspectives completed. Findings fixed with red/green regressions: Unknown-name error collision; application registrations corrupting environment/diagnostic metadata; nested promises orphaned by failed deferred encoding; example copy conflating document HTML streaming with NDJSON navigation data.

Remaining gates and rollout

Literal deno doc --lint remains red with 23 npm cross-package type-reference diagnostics. The two Juniper missing-JSDoc diagnostics are fixed in 0293095 by using private identifiers for Builder helpers, and their former gate exemption is removed. Deno 2.9.6's cross-package filter retains every reference whose URL starts with file:, which includes resolved npm declarations. A standalone file importing only OpenTelemetry Span reproduces this outside Juniper. Exact aliases and dependency reexports do not solve it; no public API facade, new suppression, or widened allowlist was introduced. The remaining raw-clean gate requires an upstream Deno fix; the configured package documentation gate and examples pass.

The own-key production fix is merged as #133. The reported macOS timing failure was separately fixed in #134, which is merged; main released 0.11.6. This PR has not been merged. Udibo/website adoption, preview docs content-encoding: br, and pre-deploy-tab verification await founder merge and JSR 0.12.0. No adoption PR is open yet. The framework preview build succeeded, but its console link requires sign-in in the available browser, so a live preview stream check remains unverified. Local progressive streaming and cancellation tests passed.

Compatibility

BREAKING CHANGE: All Juniper payloads use tagged JSON; old hydration versions reload instead of decoding. All bigint values retain bigint type. Registered serializer output is recursive, unknown names throw, and error envelopes separate __errorType from data. Non-Error failures use a null error type so every string name remains available for registration. Server and browser builds must be deployed together. The 0.x breaking-release rule selects minor, yielding 0.12.0 from 0.11.6.

Red/green and mutation evidence

Initial focused tests against the original hydration-only implementation:

deno task test --reporter=dot _serialization_wire.test.ts failed at settled-text type, small bigint (0 vs 0n), recursive serializer Promise vs Object, self-matching serializer, error-envelope name, and publicEnv sibling assertions. Its unknown-registration/stream failures were protocol mismatches and are not counted; the dedicated mutations below provide their intended-assertion evidence.

After fixes, deno task test --reporter=dot _serialization_wire.test.ts passed 20 steps. deno task test --reporter=dot _serialization_review.test.ts first failed four intended assertions (registered Unknown restored as undefined, publicEnv returned Tagged instead of production, missing diagnostic log, stream never closed), then passed with the review fixes. The final full suite passed all 480 steps.

tag-key-escape

Command: deno task test --reporter=dot _serialization_hydration.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Expected actual: "{"version":3,"data":{"matches":[{"id":"/"}],"loaderData":{"/":{"value":{"$t":"Date","v":0}}},"errors":{"$t":"undefined"},"publicEnv":{"$t":"undefined"}}}" to contain: "{"$t":"object","v":[["$t","Date"],["v",0]]}".
  throw new AssertionError(msg);
        ^
    at assertStringIncludes (https://jsr.io/@std/assert/1.0.19/string_includes.ts:29:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_hydration.test.ts:261:5)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

hydration payload version 3 ... escapes a plain object that carries the tag key => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) | 2 filtered out (15ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

own-proto

Command: deno task test --reporter=dot _serialization.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


    {
      "data request": {
+       hasOwnProto: true,
-       hasOwnProto: false,
        isAdmin: false,
        ownKeys: [
+         "__proto__",
          "name",
        ],
+       ownProtoConfigurable: true,
+       ownProtoValue: {
+         isAdmin: true,
+       },
+       ownProtoWritable: true,
        protoIsObjectPrototype: true,

script-escape-0

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: the first closing script belongs to Juniper
    throw new AssertionError(msg);
          ^
    at assert (https://jsr.io/@std/assert/1.0.19/assert.ts:22:11)
    at assertEscapedDocument (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:53:3)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:78:32)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)

the document's hydration payload ... escapes context, public environment and errors in rendered HTML => https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:15
error: AssertionError: the first closing script belongs to Juniper
    throw new AssertionError(msg);
          ^
    at assert (https://jsr.io/@std/assert/1.0.19/assert.ts:22:11)
    at assertEscapedDocument (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:53:3)
    at file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:116:52
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)

script-escape-1

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError
    throw new AssertionError(msg);
          ^
    at assertFalse (https://jsr.io/@std/assert/1.0.19/false.ts:25:11)
    at assertEscapedDocument (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:58:3)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:78:32)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)

the document's hydration payload ... escapes context, public environment and errors in rendered HTML => https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:15
error: AssertionError
    throw new AssertionError(msg);
          ^
    at assertFalse (https://jsr.io/@std/assert/1.0.19/false.ts:25:11)
    at assertEscapedDocument (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:58:3)
    at file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:116:52
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)

script-escape-2

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError
    throw new AssertionError(msg);
          ^
    at assertFalse (https://jsr.io/@std/assert/1.0.19/false.ts:25:11)
    at assertEscapedDocument (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:59:3)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:78:32)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)

the document's hydration payload ... escapes context, public environment and errors in rendered HTML => https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:15
error: AssertionError
    throw new AssertionError(msg);
          ^
    at assertFalse (https://jsr.io/@std/assert/1.0.19/false.ts:25:11)
    at assertEscapedDocument (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:59:3)
    at file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:116:52
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)

unknown-tag

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Expected function to throw.
    throw new AssertionError(msg);
          ^
    at assertThrows (https://jsr.io/@std/assert/1.0.19/throws.ts:108:11)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:468:7)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:16)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:33)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

one tagged JSON wire format ... rejects unknown string and obsolete numeric tags => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) (14ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

small-bigint

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   0
+   0n

  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:53:7)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:16)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:33)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

unknown-type

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Expected function to throw.
    throw new AssertionError(msg);
          ^
    at assertThrows (https://jsr.io/@std/assert/1.0.19/throws.ts:108:11)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:279:5)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:16)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:33)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

one tagged JSON wire format ... fails closed on unknown type and error registrations => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) (14ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

unknown-error

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: A non-Error object was rejected.
        throw new AssertionError(`A non-Error object was rejected${msgSuffix}`);
              ^
    at assertRejects (https://jsr.io/@std/assert/1.0.19/rejects.ts:105:15)
    at async Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:489:5)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

one tagged JSON wire format ... isolates an unknown error registration in a rejected stream line => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) (19ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

stream-failure-isolation

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


    [
      {
-       reason: Error: No deserializer registered for type "Absent"
-           at restoreValue (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization.ts:318:15)
-           at file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization.ts:645:17,
-       status: "rejected",
+       status: "fulfilled",
+       value: 1970-01-01T00:00:00.005Z,
      },
    ]

  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:320:5)

recursive-type

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Expected object to be an instance of "Promise" but was "Object".
  throw new AssertionError(msg);
        ^
    at assertInstanceOf (https://jsr.io/@std/assert/1.0.19/instance_of.ts:63:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:120:5)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

one tagged JSON wire format ... recursively restores serializer output with tag keys, own proto, Date, undefined and promises => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) (15ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

recursive-stream-type

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   application/json
+   application/x-ndjson


  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:360:5)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:16)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:33)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)

self-type

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Expected function to reject.
    throw new AssertionError(
          ^
    at assertRejects (https://jsr.io/@std/assert/1.0.19/rejects.ts:118:11)
    at async Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:131:5)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

one tagged JSON wire format ... refuses a serializer whose output matches itself => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) (13ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

self-error

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Expected function to throw.
    throw new AssertionError(msg);
          ^
    at assertThrows (https://jsr.io/@std/assert/1.0.19/throws.ts:108:11)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:223:5)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:16)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:33)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

one tagged JSON wire format ... rejects error serializer output matching its own predicate => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) (14ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

error-envelope

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   TypeError
+   DomainError


  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:152:5)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:16)
    at TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:33)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)

first-registration

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


    [
-     "Second",
+     "First",
    ]

  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:341:5)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)

back-pressure

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   1
+   0

  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:387:5)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

abort-cleanup

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   0
+   1

  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:438:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

response-marker

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   absent
+   data


  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:191:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)

content-length

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   73
+   72


  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:192:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)

no-transform

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   public
+   no-transform


  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:217:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)

registration-sorting

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


    [
-     "type:Zulu",
-     "type:Alpha",
+     "error:Error",
      "error:HttpError",
-     "error:TypeError",
      "error:RangeError",
      "error:ReferenceError",
      "error:SyntaxError",
+     "error:TypeError",
      "error:URIError",
-     "error:Error",
+     "type:Alpha",
+     "type:Zulu",
    ]

registration-diagnostics

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


+   [
+     "Missing Juniper registrations: type:Alpha, type:Zulu",
+   ]
-   []

  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:156:7
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)

hydration-version

Command: deno task test --reporter=dot _client_wire.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   0
+   1

  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_client_wire.test.tsx:63:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

http-error-exposure

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   The server encountered an unexpected condition.
+   Try again


  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:274:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)

abort-rejection

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Expected function to reject.
    throw new AssertionError(
          ^
    at assertRejects (https://jsr.io/@std/assert/1.0.19/rejects.ts:118:11)
    at async Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:420:5)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

one tagged JSON wire format ... aborts a waiting stream and stops processing later resolutions => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) (17ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

predicate-order

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


+   [
+     "First",
+   ]
-   1970-01-01T00:00:00.001Z

  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:344:5)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)

settled-content-type

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   application/cbor
+   application/json


  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:190:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)

client-data-dispatch

Command: deno task test --reporter=dot _client_wire.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


+   {
+     "$t": "Date",
+     at: 1970-01-01T00:00:00.005Z,
+     missing: undefined,
+   }
-   undefined

  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_client_wire.test.tsx:42:9)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)

client-api-marker

Command: deno task test --reporter=dot _client_wire.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


+   Response {
+     body: ReadableStream {
+       locked: false,
+       state: "closed",
+       supportsBYOB: false,
+     },
+     bodyUsed: true,
+     headers: Headers {
+       "content-type": "application/json",
+     },
+     ok: true,
+     redirected: false,
+     status: 200,
+     statusText: "",
+     url: "",

recursive-error

Command: deno task test --reporter=dot _serialization_wire.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Values are not equal.


    [Diff] Actual / Expected


-   {}
+   1970-01-01T00:00:00.006Z

  throw new AssertionError(message);
        ^
    at assertEquals (https://jsr.io/@std/assert/1.0.19/equals.ts:67:9)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_wire.test.ts:208:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

diagnostic-error-names

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError
    throw new AssertionError(msg);
          ^
    at assert (https://jsr.io/@std/assert/1.0.19/assert.ts:22:11)
    at file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:156:7
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

the document's hydration payload ... carries sorted development registrations and logs names missing on the client => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) | 1 filtered out (33ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

diagnostic-context-names

Command: deno task test --reporter=dot _server_hydration.test.tsx (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError
    throw new AssertionError(msg);
          ^
    at assert (https://jsr.io/@std/assert/1.0.19/assert.ts:22:11)
    at file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_server_hydration.test.tsx:156:7
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

the document's hydration payload ... carries sorted development registrations and logs names missing on the client => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) | 1 filtered out (32ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

unknown-sentinel-fail-closed

Command: deno task test --reporter=dot _serialization_review.test.ts (only the relevant existing test/describe selected; mutation applied alone and restored).

error: AssertionError: Expected function to throw.
    throw new AssertionError(msg);
          ^
    at assertThrows (https://jsr.io/@std/assert/1.0.19/throws.ts:108:11)
    at Object.<anonymous> (file:///C:/Users/Kyle%20June/Documents/projects/udibo/.claude/worktrees/juniper-tagged-json/src/_serialization_review.test.ts:63:5)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:450:7)
    at async TestSuiteInternal.runTest (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:438:9)
    at async fn (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:399:13)
    at async innerWrapped (ext:cli/40_test.js:300:5)
    at async exitSanitizer (ext:cli/40_test.js:201:27)
    at async Object.outerWrapped [as fn] (ext:cli/40_test.js:239:22)
    at async TestContext.step (ext:cli/40_test.js:855:22)
    at async TestSuiteInternal.run (https://jsr.io/@std/testing/1.0.20/_test_suite.ts:415:7)

 FAILURES 

tagged JSON review regressions ... round-trips a registered error named Unknown and fails closed when its decoder is missing => ext:cli/40_test.js:239:28

FAILED | 0 passed | 1 failed (1 step) (20ms)

Warning "nodeModulesDir" field can only be specified in the workspace root deno.json file.

All 34 focused mutations exited nonzero at behavioral assertions. The $t escape, defineOwnValue replacement, and each of < / U+2028 / U+2029 escaping were mutated independently; script assertions inspect rendered HTML. The proto test installs the browser accessor and verifies hydration, data requests, initial stream, and deferred stream in both encoder/decoder modes.

Closes

Nothing in this repository. udibo/udibo#974 stays open until adoption and rollout validation are complete.

@KyleJune

Copy link
Copy Markdown
Member Author

test: build lone surrogates at runtime in hydration table — d821733

Fixes the Ubuntu coverage step (run 34596435636), where every test passed and then Deno 2.9.6 panicked at runtime/coverage.rs:54 with unexpected end of hex escape. The trigger was the parity-table row ["a lone surrogate", () => ({ "key\uD800": "value\uDC00" })] placed after the anonymous class Box row: V8's Profiler.takePreciseCoverage response for that module contains an escape serde_json rejects. The row now builds both surrogates with String.fromCharCode, so it round-trips the same key and value.

Evidence (Deno 2.9.6 locally, CI's exact deno task test --coverage):

  • PR head b883efc: panics. origin/main 3bf15d8: completes.
  • _serialization_hydration.test.ts alone with coverage: panics before the fix, completes after. Deleting only that row also completes; deleting the separator row or the toInlineScriptJson block does not help.
  • Counts unchanged: that file 3 passed (53 steps); the full suite 39 passed (443 steps), with and without coverage. deno coverage --detailed and --lcov succeed. deno task check is green.

@KyleJune

Copy link
Copy Markdown
Member Author

Security review at b883efc: merge from a security standpoint.

  • Script breakout: the < and U+2028/U+2029 escape runs on the whole {version, data, publicEnv} string. 22 hostile values (every </script variant, <!--, -->, lone surrogates, NUL/C0, BOM, a 1 MB run of </script>) were placed as values, keys, nested values, registered-type output, deferred promises and error messages, rendered through a real server, parsed with jsdom and evaluated as a module: exactly one hydration script each time, nothing injected, exact round trip.
  • Tag confusion: any object with an own $t key is escaped at every level; arrays never decode as tags; unknown tags can only come from the server.
  • Prototype keys: the __proto__ escape holds through the embed. A separate, pre-existing problem in the decoders (assigning with =) is filed as its own issue; it does not block this PR.
  • CSP/nonce: unchanged.
  • Mutations: dropping the < escape, the __proto__ escape or the $t escape each turns the named tests red.

Merge blocker unrelated to security: the Ubuntu job's coverage collection crashes in Deno (unexpected end of hex escape); a fix is in progress on this branch.

Adoption in udibo/udibo owes threat-model updates: T10's "cannot break out because the payload is base64" and T9's "not a demonstrated request-controlled injection path" must be rewritten now that loader data is readable text protected by escaping.

@KyleJune

Copy link
Copy Markdown
Member Author

Founder decision: do not merge this as scoped. The framework should have exactly one encoding and decoding approach across HTML hydration, data requests and streaming. A partial switch leaves two formats and two decode paths to keep in agreement, which is where bugs and conflicts come from — juniper#132's __proto__ defect had to be fixed in both paths, and this PR already documents a behavioural divergence between them as an open question.

Three constraints for whatever lands instead:

  1. One format end to end, or stay on CBOR. No split.
  2. Streaming deferred promises stays first-class. It was a stated reason for adopting CBOR in feat: Use cbor2 for all server state serialization instead of SuperJSON #63, and the example app has a deferred page. Udibo not using it much yet is not evidence we won't.
  3. Juniper keeps its own type-registration API as the public surface, so a future encoding change does not force end users to rewrite anything.

Also being assessed before any decision: whether this PR's tagged-JSON scheme is as secure as what it replaces. superjson was originally chosen partly on its claim of doing this safely and efficiently, and nobody has yet judged the {"$t": tag, "v": value} design on those terms rather than on its tests.

Measurements from the udibo side, for the record (brotli q5, 19 real payloads): compressed wire size is a wash — tagged JSON wins 2.6-10.8% above roughly 2.5KB of CBOR and loses up to 39.6% below it, largest absolute win 505 bytes. The real number is the client bundle: cbor2 is 12,844 bytes brotli, about 11.5% of eagerly loaded JavaScript, and it only leaves the browser if the data path moves too. Full tables: https://github.com/udibo/udibo/issues/974#issuecomment-5647170496

🤖 Generated with Claude Code

KyleJune and others added 4 commits September 12, 2026 16:21
The document's hydration payload is now version 3: loader data travels as
JSON text with values JSON cannot carry written as {"$t": tag, "v": value}
objects, instead of base64 of CBOR. Base64 is a third larger and hides the
page's text from the compressor; a prose page drops from ~2.5x to ~1.4x its
own markup in brotli. Data requests keep CBOR and streaming.

The client still decodes version 2, so documents cached before an upgrade
hydrate with the new bundle.

Refs udibo/udibo#974

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deno 2.9.6 panics collecting coverage for a module that has an
anonymous class expression followed by an object literal whose key
is a lone-surrogate escape ("key\uD800"): V8's takePreciseCoverage
response carries an escape serde_json rejects ("unexpected end of
hex escape"), so `deno task test --coverage` dies after every test
passed. Build the surrogates with String.fromCharCode instead; the
row round-trips the same key and value as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
BREAKING CHANGE: hydration, data requests, and deferred streams use tagged JSON; legacy hydration versions trigger guarded reloads.
@KyleJune
KyleJune force-pushed the feat/json-hydration-payload branch from d821733 to 9ff4692 Compare September 12, 2026 20:34
@KyleJune KyleJune changed the title feat: send hydration data as tagged JSON text feat!: use tagged JSON end to end Sep 12, 2026
@KyleJune

Copy link
Copy Markdown
Member Author

feat!: use tagged JSON end to end — Rebased onto current main and widened this PR to the complete 0.12.0 switch. All transports use tagged JSON, deferred data remains progressive, cbor2 is removed, and the public registration shapes are unchanged. Review fixes and 34 mutation runs are included; local checks and all 480 test steps pass. The PR body records the unchanged raw documentation-lint gate and rollout work still pending.

@KyleJune

Copy link
Copy Markdown
Member Author

Updated the documentation in 8c6dac0: removed the README's 0.12 migration notice and rewrote the hydration guide to describe current behavior without version numbers or legacy-decoder language. The remaining guides already describe current behavior directly.

Validation: deno task check and deno task test --parallel --reporter=dot passed (43 suites, 480 steps). This commit changes only two Markdown files; the raw documentation-lint limitation reported above remains unchanged.

@KyleJune
KyleJune merged commit f873721 into main Sep 13, 2026
11 checks passed
@KyleJune
KyleJune deleted the feat/json-hydration-payload branch September 13, 2026 00:27
KyleJune pushed a commit that referenced this pull request Sep 13, 2026
# [0.12.0](0.11.6...0.12.0) (2026-09-13)

* feat!: use tagged JSON end to end ([#131](#131)) ([f873721](f873721)), closes [udibo/udibo#974](https://github.com/udibo/udibo/issues/974)

### BREAKING CHANGES

* hydration, data requests, and deferred streams use tagged JSON; legacy hydration versions trigger guarded reloads.

* fix: cover tagged JSON review edge cases

* docs: describe current serialization behavior

* fix: keep private helpers out of API docs
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 0.12.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant