diff --git a/conformance/Cargo.toml b/conformance/Cargo.toml index eb0e570..fea84dd 100644 --- a/conformance/Cargo.toml +++ b/conformance/Cargo.toml @@ -16,4 +16,4 @@ comline-core = { git = "https://github.com/ComlineProject/core", rev = "50c17cc7 [dev-dependencies] comline-codegen = { path = "../codegen" } comline-codegen-rust = { git = "https://github.com/ComlineProject/comline-rust", rev = "17c7b2318d89926458ec92e1b223dbf58ff36f1f" } -comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "c535f141cdae5c73257c8236826560554c5e7719" } +comline-codegen-typescript = { git = "https://github.com/ComlineProject/comline-typescript", rev = "70b617d9fbf9f4c90bce5bc11f406db66a8e2d26" } diff --git a/conformance/tests/golden/protocol/typescript.ts b/conformance/tests/golden/protocol/typescript.ts index 450913d..6539a22 100644 --- a/conformance/tests/golden/protocol/typescript.ts +++ b/conformance/tests/golden/protocol/typescript.ts @@ -1,12 +1,35 @@ // Generated by Comline -// Canonical digest of the frozen IR this file was generated from — the -// two ends of a connection must agree on it. +import { + Client, + Server, + Handshake, + RuntimeError, + resolveKind, + DatagramFraming, + type Codec, + type Dispatch, + type Framing, + type Kind, + type Reply, + type Transport, +} from "@comline/runtime"; + +/** Canonical digest of the frozen IR this file was generated from — the +* two ends of a connection must agree on it. */ export const IR_HASH = 0xbdbe5c6fd7420bd0n; export interface NotFound { } +export class NotFoundError extends Error { + static readonly ordinal = 0; + constructor(readonly data: NotFound) { + super("NotFound"); + this.name = "NotFoundError"; + } +} + export interface ServiceLookupParams { id: number; } @@ -15,16 +38,81 @@ export interface ServiceNotifyParams { code: number; } -export type ServiceLookupError = - | { code: 0; name: "NotFound"; data: NotFound }; - -export type ServiceError = - | { code: 0; name: "NotFound"; data: NotFound }; - export interface Service { - /** @throws {ServiceLookupError} */ + /** @throws {NotFoundError} */ lookup(params: ServiceLookupParams): Promise; count(): Promise; notify(params: ServiceNotifyParams): Promise; } +export const SERVICE_CALLS = ["lookup", "count", "notify"] as const; + +export class ServiceDispatcher implements Dispatch { + constructor(private readonly impl: Service) {} + + calls(): readonly string[] { + return SERVICE_CALLS; + } + + async dispatch(call: Kind, params: Uint8Array, codec: Codec, reply: Reply): Promise { + switch (resolveKind(call, SERVICE_CALLS)) { + case 0: { + const p = codec.decode(params); + try { + reply.ok(codec.encode((await this.impl.lookup(p)) ?? null)); + } catch (e) { + if (e instanceof NotFoundError) { reply.err(NotFoundError.ordinal, codec.encode(e.data)); return; } + throw e; + } + return; + } + case 1: { + reply.ok(codec.encode((await this.impl.count()) ?? null)); + return; + } + case 2: { + const p = codec.decode(params); + await this.impl.notify(p); + return; + } + default: + throw RuntimeError.unknownCall(); + } + } +} + +export class ServiceClient { + constructor(private readonly client: Client) {} + + static async connect(transport: Transport, codec: Codec, framing: Framing = new DatagramFraming()): Promise { + const hs = new Handshake({ irHash: IR_HASH, wireFormat: codec.name, framing: framing.name }); + return new ServiceClient(await Client.connect(transport, codec, hs, framing)); + } + + async lookup(params: ServiceLookupParams): Promise { + const env = await this.client.call({ id: 0, name: "lookup" }, params); + if ("ok" in env) return this.client.codec.decode(env.ok); + switch (env.err.id) { + case NotFoundError.ordinal: + throw new NotFoundError(this.client.codec.decode(env.err.body)); + default: + throw RuntimeError.remote(env.err.id); + } + } + + async count(): Promise { + const env = await this.client.call({ id: 1, name: "count" }, null); + if ("ok" in env) return this.client.codec.decode(env.ok); + throw RuntimeError.remote(env.err.id); + } + + async notify(params: ServiceNotifyParams): Promise { + await this.client.notify({ id: 2, name: "notify" }, params); + } +} + +export function serveService(impl: Service, transport: Transport, codec: Codec, framing: Framing = new DatagramFraming()): Promise { + const hs = new Handshake({ irHash: IR_HASH, wireFormat: codec.name, framing: framing.name }); + return new Server(new ServiceDispatcher(impl), codec, framing).serveHandshaked(transport, hs); +} +