Wrap a typed RPC client so every call resolves to its unwrapped value or
throws an Error carrying the JSON-RPC {code, message, data?} shape.
Implementation is a Proxy because the underlying create_rpc_client
return is itself a Proxy with no concrete keys โ a key-by-key wrap would
need to enumerate the typed surface, which only the consumer's generated
ActionsApi interface knows.
Pass-through on non-Result returns is deliberate: sync local_call
Proxy methods return values directly (see create_sync_local_call_method
above). The Proxy can't distinguish those at get-time, so the wrapper
inspects result shape at call-time and only unwraps when it sees a
Result. Non-object returns pass through unchanged.
Only {code, data} cross onto the thrown Error โ name / stack are
left as the Error's own properties so attacker-shaped result.error
payloads cannot overwrite them.
Recommended consumer convention: create_frontend_rpc_client ships
both shapes by default โ api (throwing) for hot-path call sites and
api_result (Result) for sites that inspect error.data.reason
without try/catch. Result is the protocol primitive; this wrapper is
the ergonomic layer over it. Picking is per call site โ both Proxies
share the same underlying transport.
Catch blocks read err.data?.reason โ optional chaining required
because JSON-RPC data is spec-level optional.
On unknown string-keyed methods, the get trap returns a function that
throws "rpc method not found: <prop>" on invocation โ clearer than
the JS default "api.foo is not a function". Symbol props and then
stay undefined so the Proxy isn't accidentally treated as a thenable
(await api would otherwise probe then and trip the thrower).