Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions js/packages/truapi/src/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,6 @@ describe("generated client transport", () => {
expectedFrame.set(expectedPayload, str.enc("p:1").length + 1);

expect(toHex(fixture.sent[0])).toBe(toHex(expectedFrame));
expect(transport.truapiVersion).toBe(1);
expect(transport.codecVersion).toBe(1);
});

it("uses the transport codec version for generated handshake calls", () => {
Expand Down
56 changes: 15 additions & 41 deletions js/packages/truapi/src/scale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import {
Bytes,
Enum,
Struct,
_void,
createCodec,
createDecoder,
enhanceCodec,
str as scaleStr,
str,
u8,
_void,
type Codec,
} from "scale-ts";
import {
Expand Down Expand Up @@ -123,49 +123,23 @@ export function TaggedUnion<O extends TaggedUnionCodecs>(
return Enum(inner) as unknown as Codec<TaggedUnionValue<O>>;
}

/**
* Wire codec for Rust `CallError<D>`, projected to the public domain error `D`.
*
* Generated TypeScript APIs expose only the domain error union in
* `ResultAsync<Ok, D>`. The Rust host still wraps that value in
* `CallError::Domain` on the wire so framework errors can share the response
* channel. Encoding always emits `Domain`; decoding returns the inner domain
* value and throws for framework-level failures that have no public `D` shape.
*/
export function CallError<D>(domain: Codec<D>): Codec<D> {
type WireCallError =
| { tag: "Domain"; value: D }
| { tag: "Denied"; value?: undefined }
| { tag: "Unsupported"; value?: undefined }
| { tag: "MalformedFrame"; value: { reason: string } }
| { tag: "HostFailure"; value: { reason: string } };
/** Public TS value for Rust's derived `CallError<D>` enum. */
export type CallErrorValue<D> =
| { tag: "Domain"; value: D }
| { tag: "Denied"; value?: undefined }
| { tag: "Unsupported"; value?: undefined }
| { tag: "MalformedFrame"; value: { reason: string } }
| { tag: "HostFailure"; value: { reason: string } };

const wire = Enum({
/** SCALE codec for Rust's derived `CallError<D>` enum. */
export function CallError<D>(domain: Codec<D>): Codec<CallErrorValue<D>> {
return TaggedUnion({
Domain: domain,
Denied: _void,
Unsupported: _void,
MalformedFrame: Struct({ reason: scaleStr }),
HostFailure: Struct({ reason: scaleStr }),
}) as unknown as Codec<WireCallError>;

return enhanceCodec(
wire,
(value: D): WireCallError => ({ tag: "Domain", value }),
(value: WireCallError): D => {
switch (value.tag) {
case "Domain":
return value.value;
case "Denied":
throw new Error("Host denied the request");
case "Unsupported":
throw new Error("Host does not support this request");
case "MalformedFrame":
throw new Error(`Malformed request frame: ${value.value.reason}`);
case "HostFailure":
throw new Error(`Host failure: ${value.value.reason}`);
}
},
);
MalformedFrame: Struct({ reason: str }),
HostFailure: Struct({ reason: str }),
}) as Codec<CallErrorValue<D>>;
}

type TaggedUnionCodecs = {
Expand Down
4 changes: 3 additions & 1 deletion playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"dependencies": {
"@monaco-editor/react": "^4",
"@parity/truapi": "link:../js/packages/truapi",
"@polkadot-api/substrate-bindings": "^0.12.0",
"@polkadot-api/metadata-builders": "0.14.2",
"@polkadot-api/substrate-bindings": "0.20.2",
"@polkadot-api/utils": "0.4.0",
"monaco-editor": "^0.52",
"neverthrow": "^8.2.0",
"next": "15.5.18",
Expand Down
Loading