Skip to content

feat(truapi): wire observe seam, wire debugger, and headless mock/forward debug host#295

Draft
decrypto21 wants to merge 3 commits into
mainfrom
nidish/debug-host-port
Draft

feat(truapi): wire observe seam, wire debugger, and headless mock/forward debug host#295
decrypto21 wants to merge 3 commits into
mainfrom
nidish/debug-host-port

Conversation

@decrypto21

Copy link
Copy Markdown

Adds the TrUAPI wire-debugging primitives in @parity/truapi: a payload-blind observe seam on the transport, an in-process wire debugger, and a headless mock/forward debug host. Together they let one product↔host operation be followed end to end under the single wire requestId.

Draft. This touches the client.ts transport surface that the in-flight versioned-wrapper work also edits; it is kept as a draft and rebased onto main once that lands. Not for merge before then.

What

  • createTransport(provider, { observe }) - surfaces every outbound and inbound frame as an ObservedFrame (direction, requestId, frameId, lifecycle role, byteLength, timestamp) and never the decoded payload. Frames are observed before postMessage, so traces stay causally ordered even over a synchronous provider; a throwing observer is swallowed; the hook is zero-cost when unset.
  • createWireDebugger - groups frames into per-requestId WireTraces (LRU-capped), with sink and forward relays isolated from the transport.
  • createMethodNameMap - resolves frameId → "account.getAccount" from the generated wire-table, so traces read as method names.
  • createDebugHost - a headless host that answers scripted mock entries through the real generated codecs and forwards every other frame verbatim to a real host (requestId untouched, so correlation holds across the hop). With no forward pipe it is a pure headless responder; unclaimed and undecodable frames warn by default. Dispose sends stop frames upstream for live forwarded subscriptions.

Why

One correlation id: every frame carries the transport-minted requestId - the id the host dispatcher carries and the product-sdk telemetry span adopts as its correlationId - so product → wire → host is one correlated trace with no second correlation scheme. Because the seam carries no payload bytes and no key material, the same recorder is safe to leave on in production.

Try it out

cd js/packages/truapi
bun examples/wire-debug-demo.mjs

createTransport accepts an optional observe callback surfacing every frame
as an ObservedFrame (direction, requestId, frameId, lifecycle role,
byteLength, timestamp — never the decoded payload). Frames are observed
before provider.postMessage so traces stay causally ordered even over a
synchronous in-memory provider, and an attempted-but-failed send is still
observed. A throwing observer is swallowed; the hook is zero-cost unset.

createWireDebugger groups frames into per-requestId WireTraces (LRU-capped)
with sink/forward relays isolated from the transport. createMethodNameMap
derives a frameId → "service.method" reverse map from the generated
wire-table plus the client's service names, so debug lines read
account.getAccount instead of id=22.

Because every frame carries the transport-minted requestId, a product-side
telemetry span (sdk-team#28) adopts that id and product → wire → host
becomes one correlated trace.

Tests: outbound/inbound roles, subscription lifecycle (start/receive/stop
under one id), name mapping across the full wire-table, payload-blindness
key-set check, observer/sink/forward failure isolation, e2e span↔trace
correlation.
examples/wire-debug-demo.mjs fires a real account.getAccount round trip over an
in-memory provider and prints the readable observe/wire-debugger trace, so the
observe hook (sdk-team#26 C1) is demoable with no host or network. README's
observability section gains a Try it out block pointing at it.
…ported to the current core

createDebugHost routes product frames by wire id: ids claimed by a mock
entry dispatch locally (payloads encoded by the caller with the generated
codecs, marked tier="mock"); everything else forwards byte-verbatim down
an optional pipe to a real host — including a WASM-backed mock host — with
answers relayed back, requestId untouched. Without a forward pipe it is
the headless responder: unclaimed and undecodable frames warn by default.
Dispose sends stop frames upstream for live forwarded subscriptions so a
real host never streams into a detached pipe.

Mock entries are byte-level (the generated TS host server this previously
composed onto was removed with the truapi-host codegen pipeline); a typed
per-method surface returns when codegen emits a codec table.
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