From 2fba7ab5485f28ba234161ddcd0ca20784506867 Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Tue, 24 Oct 2023 13:18:02 -0400 Subject: [PATCH 01/13] wip --- packages/connect/src/implementation.ts | 41 ++++++++--- packages/connect/src/method-type-temp.ts | 70 +++++++++++++++++++ packages/connect/src/router-transport.spec.ts | 35 ++++++++-- packages/connect/src/router.ts | 51 ++++++++++---- 4 files changed, 170 insertions(+), 27 deletions(-) create mode 100644 packages/connect/src/method-type-temp.ts diff --git a/packages/connect/src/implementation.ts b/packages/connect/src/implementation.ts index 97ea6d638..d506fb212 100644 --- a/packages/connect/src/implementation.ts +++ b/packages/connect/src/implementation.ts @@ -30,6 +30,7 @@ import { } from "./protocol/signals.js"; import { createContextValues } from "./context-values.js"; import type { ContextValues } from "./context-values.js"; +import type { MethodType } from "./method-type-temp.js"; // prettier-ignore /** @@ -155,7 +156,7 @@ interface HandlerContextController extends HandlerContext { * a context. */ export function createHandlerContext( - init: HandlerContextInit, + init: HandlerContextInit ): HandlerContextController { let timeoutMs: () => undefined | number; if (init.timeoutMs !== undefined) { @@ -168,7 +169,7 @@ export function createHandlerContext( const abortController = createLinkedAbortController( deadline.signal, init.requestSignal, - init.shutdownSignal, + init.shutdownSignal ); return { ...init, @@ -190,7 +191,7 @@ export function createHandlerContext( */ export type UnaryImpl, O extends Message> = ( request: I, - context: HandlerContext, + context: HandlerContext ) => Promise> | O | PartialMessage; /** @@ -199,7 +200,7 @@ export type UnaryImpl, O extends Message> = ( */ export type ClientStreamingImpl, O extends Message> = ( requests: AsyncIterable, - context: HandlerContext, + context: HandlerContext ) => Promise>; /** @@ -208,7 +209,7 @@ export type ClientStreamingImpl, O extends Message> = ( */ export type ServerStreamingImpl, O extends Message> = ( request: I, - context: HandlerContext, + context: HandlerContext ) => AsyncIterable>; /** @@ -217,7 +218,7 @@ export type ServerStreamingImpl, O extends Message> = ( */ export type BiDiStreamingImpl, O extends Message> = ( requests: AsyncIterable, - context: HandlerContext, + context: HandlerContext ) => AsyncIterable>; // prettier-ignore @@ -251,14 +252,36 @@ export type ServiceImplSpec = { * Create an MethodImplSpec - a user-provided implementation for a method, * wrapped in a discriminated union type along with service and method metadata. */ +export function createMethodImplSpec( + method: M, + impl: MethodImpl +): MethodImplSpec; export function createMethodImplSpec( service: ServiceType, method: M, - impl: MethodImpl, + impl: MethodImpl +): MethodImplSpec; +export function createMethodImplSpec< + M extends MethodInfo, + MT extends MethodType, +>( + ...args: [MT, MethodImpl] | [ServiceType, M, MethodImpl] ): MethodImplSpec { + if ("typeName" in args[0] && "kind" in args[1]) { + return { + kind: args[1].kind, + service: args[0], + method: args[1], + impl: args[2], + } as MethodImplSpec; + } + const [method, impl] = args as [MT, MethodImpl]; return { kind: method.kind, - service, + service: { + ...method.service, + methods: {}, + }, method, impl, } as MethodImplSpec; @@ -270,7 +293,7 @@ export function createMethodImplSpec( */ export function createServiceImplSpec( service: T, - impl: Partial>, + impl: Partial> ): ServiceImplSpec { const s: ServiceImplSpec = { service, methods: {} }; for (const [localName, methodInfo] of Object.entries(service.methods)) { diff --git a/packages/connect/src/method-type-temp.ts b/packages/connect/src/method-type-temp.ts new file mode 100644 index 000000000..777dd05a1 --- /dev/null +++ b/packages/connect/src/method-type-temp.ts @@ -0,0 +1,70 @@ +import type { + AnyMessage, + Message, + MethodInfoBiDiStreaming, + MethodInfoClientStreaming, + MethodInfoServerStreaming, + MethodInfoUnary, + ServiceType, +} from "@bufbuild/protobuf"; + +interface mtShared { + readonly localName: string; + readonly service: Omit; +} + +/** + * A unary method: rpc (Input) returns (Output) + */ +export interface MethodTypeUnary, O extends Message> + extends mtShared, + MethodInfoUnary {} + +/** + * A server streaming method: rpc (Input) returns (stream Output) + */ +export interface MethodTypeServerStreaming< + I extends Message, + O extends Message, +> extends mtShared, + MethodInfoServerStreaming {} + +/** + * A client streaming method: rpc (stream Input) returns (Output) + */ +export interface MethodTypeClientStreaming< + I extends Message, + O extends Message, +> extends mtShared, + MethodInfoClientStreaming {} + +/** + * A method that streams bi-directionally: rpc (stream Input) returns (stream Output) + */ +export interface MethodTypeBiDiStreaming< + I extends Message, + O extends Message, +> extends mtShared, + MethodInfoBiDiStreaming {} + +/** + * MethodType represents a self-contained method type. It must contain + * references to the service that implements it. + * + * - "name": The original name of the protobuf rpc. + * - "I": The input message type. + * - "O": The output message type. + * - "kind": The method type. + * - "idempotency": User-provided indication whether the method will cause + * the same effect every time it is called. + * - "localName": The local name of the method, safe to use in ECMAScript. + * - "service": The service that implements the method, without methods. + */ +export type MethodType< + I extends Message = AnyMessage, + O extends Message = AnyMessage, +> = + | MethodTypeUnary + | MethodTypeServerStreaming + | MethodTypeClientStreaming + | MethodTypeBiDiStreaming; diff --git a/packages/connect/src/router-transport.spec.ts b/packages/connect/src/router-transport.spec.ts index f1b3838bb..1af579c0d 100644 --- a/packages/connect/src/router-transport.spec.ts +++ b/packages/connect/src/router-transport.spec.ts @@ -15,6 +15,7 @@ import { Int32Value, Message, + MethodIdempotency, MethodKind, proto3, StringValue, @@ -96,7 +97,7 @@ describe("createRoutesTransport", function () { }); it("should work for client steam", async function () { const res = await client.client( - createAsyncIterable([{ value: 12 }, { value: 13 }]), + createAsyncIterable([{ value: 12 }, { value: 13 }]) ); expect(res.value).toBe("13"); }); @@ -121,7 +122,7 @@ describe("createRoutesTransport", function () { } catch (e) { expect(e).toBeInstanceOf(ConnectError); expect(ConnectError.from(e).message).toBe( - "[unimplemented] RouterHttpClient: no handler registered for /TestService/Unary", + "[unimplemented] RouterHttpClient: no handler registered for /TestService/Unary" ); } }); @@ -138,7 +139,7 @@ describe("createRoutesTransport", function () { kind: "scalar", T: 9 /* ScalarType.STRING */, }, - ], + ] ); interface SayResponse extends Message { sentence: string; @@ -152,7 +153,7 @@ describe("createRoutesTransport", function () { kind: "scalar", T: 9 /* ScalarType.STRING */, }, - ], + ] ); const ElizaService = { typeName: "connectrpc.eliza.v1.ElizaService", @@ -204,7 +205,7 @@ describe("createRoutesTransport", function () { if (sentences.length > 3) { throw new ConnectError( "I have no words anymore.", - Code.ResourceExhausted, + Code.ResourceExhausted ); } return new SayResponse({ @@ -219,9 +220,31 @@ describe("createRoutesTransport", function () { await client.say({ sentence: "2" }); await client.say({ sentence: "3" }); await expectAsync(client.say({ sentence: "4" })).toBeRejectedWithError( - /I have no words anymore/, + /I have no words anymore/ ); }); }); }); + describe("supports self describing method type", function () { + const methodDefinition = { + kind: MethodKind.Unary, + I: Int32Value, + localName: "unary", + name: "Unary", + O: StringValue, + service: testService, + idempotency: MethodIdempotency.NoSideEffects, + } as const; + const transport = createRouterTransport(({ rpc }) => { + rpc(methodDefinition, (request) => { + return { value: `${request.value}-RESPONSE` }; + }); + }); + + const client = createPromiseClient(testService, transport); + it("should work for unary", async function () { + const res = await client.unary({ value: 13 }); + expect(res.value).toBe("13-RESPONSE"); + }); + }); }); diff --git a/packages/connect/src/router.ts b/packages/connect/src/router.ts index 7a8077dc2..5ad0b7128 100644 --- a/packages/connect/src/router.ts +++ b/packages/connect/src/router.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { MethodInfo, ServiceType } from "@bufbuild/protobuf"; +import type { AnyMessage, MethodInfo, ServiceType } from "@bufbuild/protobuf"; import { ConnectError } from "./connect-error.js"; import { Code } from "./code.js"; import { @@ -33,6 +33,7 @@ import type { UniversalHandlerOptions, } from "./protocol/universal-handler.js"; import type { ProtocolHandlerFactory } from "./protocol/protocol-handler-factory.js"; +import type { MethodType } from "./method-type-temp.js"; /** * ConnectRouter is your single registration point for RPCs. @@ -55,13 +56,18 @@ export interface ConnectRouter { service( service: T, implementation: Partial>, - options?: Partial, + options?: Partial ): this; rpc( service: ServiceType, method: M, impl: MethodImpl, - options?: Partial, + options?: Partial + ): this; + rpc( + method: M, + impl: MethodImpl, + options?: Partial ): this; } @@ -115,7 +121,7 @@ export interface ConnectRouterOptions extends Partial { * Create a new ConnectRouter. */ export function createConnectRouter( - routerOptions?: ConnectRouterOptions, + routerOptions?: ConnectRouterOptions ): ConnectRouter { const base = whichProtocols(routerOptions); const handlers: UniversalHandler[] = []; @@ -126,18 +132,39 @@ export function createConnectRouter( handlers.push( ...createUniversalServiceHandlers( createServiceImplSpec(service, implementation), - protocols, - ), + protocols + ) ); return this; }, - rpc(service, method, implementation, options) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- overload is intentionally vague. + rpc(...args: any[]) { + if ("typeName" in args[0]) { + const [service, method, implementation, options] = args as [ + ServiceType, + MethodInfo, + MethodImpl>, + UniversalHandlerOptions, + ]; + const { protocols } = whichProtocols(options, base); + handlers.push( + createUniversalMethodHandler( + createMethodImplSpec(service, method, implementation), + protocols + ) + ); + } + const [method, implementation, options] = args as [ + MethodType, + MethodImpl>, + UniversalHandlerOptions, + ]; const { protocols } = whichProtocols(options, base); handlers.push( createUniversalMethodHandler( - createMethodImplSpec(service, method, implementation), - protocols, - ), + createMethodImplSpec(method, implementation), + protocols + ) ); return this; }, @@ -146,7 +173,7 @@ export function createConnectRouter( function whichProtocols( options: ConnectRouterOptions | undefined, - base?: { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] }, + base?: { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] } ): { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] } { if (base && !options) { return base; @@ -174,7 +201,7 @@ function whichProtocols( if (protocols.length === 0) { throw new ConnectError( "cannot create handler, all protocols are disabled", - Code.InvalidArgument, + Code.InvalidArgument ); } return { From 047212a495bae74b9175cb09f65381eadc92f71a Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Wed, 25 Oct 2023 10:51:25 -0400 Subject: [PATCH 02/13] Reworked implementation a little --- packages/connect/src/implementation.ts | 2 +- .../{method-type-temp.ts => method-type.ts} | 0 packages/connect/src/router.ts | 43 +++++++++++-------- 3 files changed, 26 insertions(+), 19 deletions(-) rename packages/connect/src/{method-type-temp.ts => method-type.ts} (100%) diff --git a/packages/connect/src/implementation.ts b/packages/connect/src/implementation.ts index d506fb212..7bed45f2b 100644 --- a/packages/connect/src/implementation.ts +++ b/packages/connect/src/implementation.ts @@ -30,7 +30,7 @@ import { } from "./protocol/signals.js"; import { createContextValues } from "./context-values.js"; import type { ContextValues } from "./context-values.js"; -import type { MethodType } from "./method-type-temp.js"; +import type { MethodType } from "./method-type.js"; // prettier-ignore /** diff --git a/packages/connect/src/method-type-temp.ts b/packages/connect/src/method-type.ts similarity index 100% rename from packages/connect/src/method-type-temp.ts rename to packages/connect/src/method-type.ts diff --git a/packages/connect/src/router.ts b/packages/connect/src/router.ts index 5ad0b7128..8d9ba684e 100644 --- a/packages/connect/src/router.ts +++ b/packages/connect/src/router.ts @@ -33,7 +33,7 @@ import type { UniversalHandlerOptions, } from "./protocol/universal-handler.js"; import type { ProtocolHandlerFactory } from "./protocol/protocol-handler-factory.js"; -import type { MethodType } from "./method-type-temp.js"; +import type { MethodType } from "./method-type.js"; /** * ConnectRouter is your single registration point for RPCs. @@ -125,6 +125,7 @@ export function createConnectRouter( ): ConnectRouter { const base = whichProtocols(routerOptions); const handlers: UniversalHandler[] = []; + return { handlers, service(service, implementation, options) { @@ -137,32 +138,38 @@ export function createConnectRouter( ); return this; }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- overload is intentionally vague. - rpc(...args: any[]) { - if ("typeName" in args[0]) { - const [service, method, implementation, options] = args as [ - ServiceType, - MethodInfo, - MethodImpl>, - UniversalHandlerOptions, - ]; + rpc( + serviceOrMethod: ServiceType | MethodType, + methodOrImpl: MethodInfo | MethodImpl>, + implementationOrOptions?: + | MethodImpl> + | Partial, + options?: Partial + ) { + if ("typeName" in serviceOrMethod) { const { protocols } = whichProtocols(options, base); + handlers.push( createUniversalMethodHandler( - createMethodImplSpec(service, method, implementation), + createMethodImplSpec( + serviceOrMethod, + methodOrImpl as MethodInfo, + implementationOrOptions as MethodImpl> + ), protocols ) ); } - const [method, implementation, options] = args as [ - MethodType, - MethodImpl>, - UniversalHandlerOptions, - ]; - const { protocols } = whichProtocols(options, base); + const { protocols } = whichProtocols( + implementationOrOptions as Partial, + base + ); handlers.push( createUniversalMethodHandler( - createMethodImplSpec(method, implementation), + createMethodImplSpec( + serviceOrMethod as MethodType, + methodOrImpl as MethodImpl> + ), protocols ) ); From fa03a64060090f55d6531bd5e95da424fc453632 Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Wed, 25 Oct 2023 11:01:42 -0400 Subject: [PATCH 03/13] Format code --- packages/connect/src/implementation.ts | 18 +++++------ packages/connect/src/method-type.ts | 14 ++++++++ packages/connect/src/router-transport.spec.ts | 12 +++---- packages/connect/src/router.ts | 32 +++++++++---------- 4 files changed, 45 insertions(+), 31 deletions(-) diff --git a/packages/connect/src/implementation.ts b/packages/connect/src/implementation.ts index 7bed45f2b..6ac262d74 100644 --- a/packages/connect/src/implementation.ts +++ b/packages/connect/src/implementation.ts @@ -156,7 +156,7 @@ interface HandlerContextController extends HandlerContext { * a context. */ export function createHandlerContext( - init: HandlerContextInit + init: HandlerContextInit, ): HandlerContextController { let timeoutMs: () => undefined | number; if (init.timeoutMs !== undefined) { @@ -169,7 +169,7 @@ export function createHandlerContext( const abortController = createLinkedAbortController( deadline.signal, init.requestSignal, - init.shutdownSignal + init.shutdownSignal, ); return { ...init, @@ -191,7 +191,7 @@ export function createHandlerContext( */ export type UnaryImpl, O extends Message> = ( request: I, - context: HandlerContext + context: HandlerContext, ) => Promise> | O | PartialMessage; /** @@ -200,7 +200,7 @@ export type UnaryImpl, O extends Message> = ( */ export type ClientStreamingImpl, O extends Message> = ( requests: AsyncIterable, - context: HandlerContext + context: HandlerContext, ) => Promise>; /** @@ -209,7 +209,7 @@ export type ClientStreamingImpl, O extends Message> = ( */ export type ServerStreamingImpl, O extends Message> = ( request: I, - context: HandlerContext + context: HandlerContext, ) => AsyncIterable>; /** @@ -218,7 +218,7 @@ export type ServerStreamingImpl, O extends Message> = ( */ export type BiDiStreamingImpl, O extends Message> = ( requests: AsyncIterable, - context: HandlerContext + context: HandlerContext, ) => AsyncIterable>; // prettier-ignore @@ -254,12 +254,12 @@ export type ServiceImplSpec = { */ export function createMethodImplSpec( method: M, - impl: MethodImpl + impl: MethodImpl, ): MethodImplSpec; export function createMethodImplSpec( service: ServiceType, method: M, - impl: MethodImpl + impl: MethodImpl, ): MethodImplSpec; export function createMethodImplSpec< M extends MethodInfo, @@ -293,7 +293,7 @@ export function createMethodImplSpec< */ export function createServiceImplSpec( service: T, - impl: Partial> + impl: Partial>, ): ServiceImplSpec { const s: ServiceImplSpec = { service, methods: {} }; for (const [localName, methodInfo] of Object.entries(service.methods)) { diff --git a/packages/connect/src/method-type.ts b/packages/connect/src/method-type.ts index 777dd05a1..8d111ff9b 100644 --- a/packages/connect/src/method-type.ts +++ b/packages/connect/src/method-type.ts @@ -1,3 +1,17 @@ +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import type { AnyMessage, Message, diff --git a/packages/connect/src/router-transport.spec.ts b/packages/connect/src/router-transport.spec.ts index 1af579c0d..5e0e6f9fd 100644 --- a/packages/connect/src/router-transport.spec.ts +++ b/packages/connect/src/router-transport.spec.ts @@ -97,7 +97,7 @@ describe("createRoutesTransport", function () { }); it("should work for client steam", async function () { const res = await client.client( - createAsyncIterable([{ value: 12 }, { value: 13 }]) + createAsyncIterable([{ value: 12 }, { value: 13 }]), ); expect(res.value).toBe("13"); }); @@ -122,7 +122,7 @@ describe("createRoutesTransport", function () { } catch (e) { expect(e).toBeInstanceOf(ConnectError); expect(ConnectError.from(e).message).toBe( - "[unimplemented] RouterHttpClient: no handler registered for /TestService/Unary" + "[unimplemented] RouterHttpClient: no handler registered for /TestService/Unary", ); } }); @@ -139,7 +139,7 @@ describe("createRoutesTransport", function () { kind: "scalar", T: 9 /* ScalarType.STRING */, }, - ] + ], ); interface SayResponse extends Message { sentence: string; @@ -153,7 +153,7 @@ describe("createRoutesTransport", function () { kind: "scalar", T: 9 /* ScalarType.STRING */, }, - ] + ], ); const ElizaService = { typeName: "connectrpc.eliza.v1.ElizaService", @@ -205,7 +205,7 @@ describe("createRoutesTransport", function () { if (sentences.length > 3) { throw new ConnectError( "I have no words anymore.", - Code.ResourceExhausted + Code.ResourceExhausted, ); } return new SayResponse({ @@ -220,7 +220,7 @@ describe("createRoutesTransport", function () { await client.say({ sentence: "2" }); await client.say({ sentence: "3" }); await expectAsync(client.say({ sentence: "4" })).toBeRejectedWithError( - /I have no words anymore/ + /I have no words anymore/, ); }); }); diff --git a/packages/connect/src/router.ts b/packages/connect/src/router.ts index 8d9ba684e..e5f835e3f 100644 --- a/packages/connect/src/router.ts +++ b/packages/connect/src/router.ts @@ -56,18 +56,18 @@ export interface ConnectRouter { service( service: T, implementation: Partial>, - options?: Partial + options?: Partial, ): this; rpc( service: ServiceType, method: M, impl: MethodImpl, - options?: Partial + options?: Partial, ): this; rpc( method: M, impl: MethodImpl, - options?: Partial + options?: Partial, ): this; } @@ -121,7 +121,7 @@ export interface ConnectRouterOptions extends Partial { * Create a new ConnectRouter. */ export function createConnectRouter( - routerOptions?: ConnectRouterOptions + routerOptions?: ConnectRouterOptions, ): ConnectRouter { const base = whichProtocols(routerOptions); const handlers: UniversalHandler[] = []; @@ -133,8 +133,8 @@ export function createConnectRouter( handlers.push( ...createUniversalServiceHandlers( createServiceImplSpec(service, implementation), - protocols - ) + protocols, + ), ); return this; }, @@ -144,7 +144,7 @@ export function createConnectRouter( implementationOrOptions?: | MethodImpl> | Partial, - options?: Partial + options?: Partial, ) { if ("typeName" in serviceOrMethod) { const { protocols } = whichProtocols(options, base); @@ -154,24 +154,24 @@ export function createConnectRouter( createMethodImplSpec( serviceOrMethod, methodOrImpl as MethodInfo, - implementationOrOptions as MethodImpl> + implementationOrOptions as MethodImpl>, ), - protocols - ) + protocols, + ), ); } const { protocols } = whichProtocols( implementationOrOptions as Partial, - base + base, ); handlers.push( createUniversalMethodHandler( createMethodImplSpec( serviceOrMethod as MethodType, - methodOrImpl as MethodImpl> + methodOrImpl as MethodImpl>, ), - protocols - ) + protocols, + ), ); return this; }, @@ -180,7 +180,7 @@ export function createConnectRouter( function whichProtocols( options: ConnectRouterOptions | undefined, - base?: { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] } + base?: { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] }, ): { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] } { if (base && !options) { return base; @@ -208,7 +208,7 @@ function whichProtocols( if (protocols.length === 0) { throw new ConnectError( "cannot create handler, all protocols are disabled", - Code.InvalidArgument + Code.InvalidArgument, ); } return { From 55c88233fc13ab231edf3da61ead365f1e82f82f Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Wed, 25 Oct 2023 11:04:22 -0400 Subject: [PATCH 04/13] Make sure to not add twice --- packages/connect/src/router.ts | 51 +++++++++++++++++----------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/packages/connect/src/router.ts b/packages/connect/src/router.ts index e5f835e3f..cca67ec8e 100644 --- a/packages/connect/src/router.ts +++ b/packages/connect/src/router.ts @@ -56,18 +56,18 @@ export interface ConnectRouter { service( service: T, implementation: Partial>, - options?: Partial, + options?: Partial ): this; rpc( service: ServiceType, method: M, impl: MethodImpl, - options?: Partial, + options?: Partial ): this; rpc( method: M, impl: MethodImpl, - options?: Partial, + options?: Partial ): this; } @@ -121,7 +121,7 @@ export interface ConnectRouterOptions extends Partial { * Create a new ConnectRouter. */ export function createConnectRouter( - routerOptions?: ConnectRouterOptions, + routerOptions?: ConnectRouterOptions ): ConnectRouter { const base = whichProtocols(routerOptions); const handlers: UniversalHandler[] = []; @@ -133,8 +133,8 @@ export function createConnectRouter( handlers.push( ...createUniversalServiceHandlers( createServiceImplSpec(service, implementation), - protocols, - ), + protocols + ) ); return this; }, @@ -144,7 +144,7 @@ export function createConnectRouter( implementationOrOptions?: | MethodImpl> | Partial, - options?: Partial, + options?: Partial ) { if ("typeName" in serviceOrMethod) { const { protocols } = whichProtocols(options, base); @@ -154,25 +154,26 @@ export function createConnectRouter( createMethodImplSpec( serviceOrMethod, methodOrImpl as MethodInfo, - implementationOrOptions as MethodImpl>, + implementationOrOptions as MethodImpl> ), - protocols, - ), + protocols + ) + ); + } else { + const { protocols } = whichProtocols( + implementationOrOptions as Partial, + base + ); + handlers.push( + createUniversalMethodHandler( + createMethodImplSpec( + serviceOrMethod, + methodOrImpl as MethodImpl> + ), + protocols + ) ); } - const { protocols } = whichProtocols( - implementationOrOptions as Partial, - base, - ); - handlers.push( - createUniversalMethodHandler( - createMethodImplSpec( - serviceOrMethod as MethodType, - methodOrImpl as MethodImpl>, - ), - protocols, - ), - ); return this; }, }; @@ -180,7 +181,7 @@ export function createConnectRouter( function whichProtocols( options: ConnectRouterOptions | undefined, - base?: { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] }, + base?: { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] } ): { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] } { if (base && !options) { return base; @@ -208,7 +209,7 @@ function whichProtocols( if (protocols.length === 0) { throw new ConnectError( "cannot create handler, all protocols are disabled", - Code.InvalidArgument, + Code.InvalidArgument ); } return { From 75517d293d60df0a6adc6345c3dc858f99255b7c Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Wed, 25 Oct 2023 11:21:00 -0400 Subject: [PATCH 05/13] Fit format --- packages/connect/src/router.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/connect/src/router.ts b/packages/connect/src/router.ts index cca67ec8e..b63b8929f 100644 --- a/packages/connect/src/router.ts +++ b/packages/connect/src/router.ts @@ -56,18 +56,18 @@ export interface ConnectRouter { service( service: T, implementation: Partial>, - options?: Partial + options?: Partial, ): this; rpc( service: ServiceType, method: M, impl: MethodImpl, - options?: Partial + options?: Partial, ): this; rpc( method: M, impl: MethodImpl, - options?: Partial + options?: Partial, ): this; } @@ -121,7 +121,7 @@ export interface ConnectRouterOptions extends Partial { * Create a new ConnectRouter. */ export function createConnectRouter( - routerOptions?: ConnectRouterOptions + routerOptions?: ConnectRouterOptions, ): ConnectRouter { const base = whichProtocols(routerOptions); const handlers: UniversalHandler[] = []; @@ -133,8 +133,8 @@ export function createConnectRouter( handlers.push( ...createUniversalServiceHandlers( createServiceImplSpec(service, implementation), - protocols - ) + protocols, + ), ); return this; }, @@ -144,7 +144,7 @@ export function createConnectRouter( implementationOrOptions?: | MethodImpl> | Partial, - options?: Partial + options?: Partial, ) { if ("typeName" in serviceOrMethod) { const { protocols } = whichProtocols(options, base); @@ -154,24 +154,24 @@ export function createConnectRouter( createMethodImplSpec( serviceOrMethod, methodOrImpl as MethodInfo, - implementationOrOptions as MethodImpl> + implementationOrOptions as MethodImpl>, ), - protocols - ) + protocols, + ), ); } else { const { protocols } = whichProtocols( implementationOrOptions as Partial, - base + base, ); handlers.push( createUniversalMethodHandler( createMethodImplSpec( serviceOrMethod, - methodOrImpl as MethodImpl> + methodOrImpl as MethodImpl>, ), - protocols - ) + protocols, + ), ); } return this; @@ -181,7 +181,7 @@ export function createConnectRouter( function whichProtocols( options: ConnectRouterOptions | undefined, - base?: { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] } + base?: { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] }, ): { options: ConnectRouterOptions; protocols: ProtocolHandlerFactory[] } { if (base && !options) { return base; @@ -209,7 +209,7 @@ function whichProtocols( if (protocols.length === 0) { throw new ConnectError( "cannot create handler, all protocols are disabled", - Code.InvalidArgument + Code.InvalidArgument, ); } return { From 097ef859747847c468d5632d879229fa25e41b41 Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Wed, 25 Oct 2023 13:08:44 -0400 Subject: [PATCH 06/13] Remove unused localName field --- packages/connect/src/method-type.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/connect/src/method-type.ts b/packages/connect/src/method-type.ts index 8d111ff9b..121bdddc7 100644 --- a/packages/connect/src/method-type.ts +++ b/packages/connect/src/method-type.ts @@ -23,7 +23,6 @@ import type { } from "@bufbuild/protobuf"; interface mtShared { - readonly localName: string; readonly service: Omit; } From 32f071544173143ea2f6e8fff86e8aea6f3d6bee Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Wed, 25 Oct 2023 13:10:43 -0400 Subject: [PATCH 07/13] Update spec to not include localName --- packages/connect/src/router-transport.spec.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/connect/src/router-transport.spec.ts b/packages/connect/src/router-transport.spec.ts index 5e0e6f9fd..f984dbaa1 100644 --- a/packages/connect/src/router-transport.spec.ts +++ b/packages/connect/src/router-transport.spec.ts @@ -97,7 +97,7 @@ describe("createRoutesTransport", function () { }); it("should work for client steam", async function () { const res = await client.client( - createAsyncIterable([{ value: 12 }, { value: 13 }]), + createAsyncIterable([{ value: 12 }, { value: 13 }]) ); expect(res.value).toBe("13"); }); @@ -122,7 +122,7 @@ describe("createRoutesTransport", function () { } catch (e) { expect(e).toBeInstanceOf(ConnectError); expect(ConnectError.from(e).message).toBe( - "[unimplemented] RouterHttpClient: no handler registered for /TestService/Unary", + "[unimplemented] RouterHttpClient: no handler registered for /TestService/Unary" ); } }); @@ -139,7 +139,7 @@ describe("createRoutesTransport", function () { kind: "scalar", T: 9 /* ScalarType.STRING */, }, - ], + ] ); interface SayResponse extends Message { sentence: string; @@ -153,7 +153,7 @@ describe("createRoutesTransport", function () { kind: "scalar", T: 9 /* ScalarType.STRING */, }, - ], + ] ); const ElizaService = { typeName: "connectrpc.eliza.v1.ElizaService", @@ -205,7 +205,7 @@ describe("createRoutesTransport", function () { if (sentences.length > 3) { throw new ConnectError( "I have no words anymore.", - Code.ResourceExhausted, + Code.ResourceExhausted ); } return new SayResponse({ @@ -220,17 +220,16 @@ describe("createRoutesTransport", function () { await client.say({ sentence: "2" }); await client.say({ sentence: "3" }); await expectAsync(client.say({ sentence: "4" })).toBeRejectedWithError( - /I have no words anymore/, + /I have no words anymore/ ); }); }); }); describe("supports self describing method type", function () { const methodDefinition = { + name: "Unary", kind: MethodKind.Unary, I: Int32Value, - localName: "unary", - name: "Unary", O: StringValue, service: testService, idempotency: MethodIdempotency.NoSideEffects, From 3a35a269d53454f873fa056d0aa260b3d935d918 Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Wed, 25 Oct 2023 15:15:24 -0400 Subject: [PATCH 08/13] More prettier fixes --- packages/connect/src/router-transport.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/connect/src/router-transport.spec.ts b/packages/connect/src/router-transport.spec.ts index f984dbaa1..076455415 100644 --- a/packages/connect/src/router-transport.spec.ts +++ b/packages/connect/src/router-transport.spec.ts @@ -97,7 +97,7 @@ describe("createRoutesTransport", function () { }); it("should work for client steam", async function () { const res = await client.client( - createAsyncIterable([{ value: 12 }, { value: 13 }]) + createAsyncIterable([{ value: 12 }, { value: 13 }]), ); expect(res.value).toBe("13"); }); @@ -122,7 +122,7 @@ describe("createRoutesTransport", function () { } catch (e) { expect(e).toBeInstanceOf(ConnectError); expect(ConnectError.from(e).message).toBe( - "[unimplemented] RouterHttpClient: no handler registered for /TestService/Unary" + "[unimplemented] RouterHttpClient: no handler registered for /TestService/Unary", ); } }); @@ -139,7 +139,7 @@ describe("createRoutesTransport", function () { kind: "scalar", T: 9 /* ScalarType.STRING */, }, - ] + ], ); interface SayResponse extends Message { sentence: string; @@ -153,7 +153,7 @@ describe("createRoutesTransport", function () { kind: "scalar", T: 9 /* ScalarType.STRING */, }, - ] + ], ); const ElizaService = { typeName: "connectrpc.eliza.v1.ElizaService", @@ -205,7 +205,7 @@ describe("createRoutesTransport", function () { if (sentences.length > 3) { throw new ConnectError( "I have no words anymore.", - Code.ResourceExhausted + Code.ResourceExhausted, ); } return new SayResponse({ @@ -220,7 +220,7 @@ describe("createRoutesTransport", function () { await client.say({ sentence: "2" }); await client.say({ sentence: "3" }); await expectAsync(client.say({ sentence: "4" })).toBeRejectedWithError( - /I have no words anymore/ + /I have no words anymore/, ); }); }); From b01b18e7373ff4cebfbc9dde8ce211552c13b2fe Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Thu, 26 Oct 2023 09:24:38 -0400 Subject: [PATCH 09/13] Added aother test --- packages/connect/src/router-transport.spec.ts | 63 +++++++++++++++---- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/packages/connect/src/router-transport.spec.ts b/packages/connect/src/router-transport.spec.ts index 076455415..907363328 100644 --- a/packages/connect/src/router-transport.spec.ts +++ b/packages/connect/src/router-transport.spec.ts @@ -25,6 +25,7 @@ import { createAsyncIterable } from "./protocol/async-iterable.js"; import { createRouterTransport } from "./router-transport.js"; import { ConnectError } from "./connect-error.js"; import { Code } from "./code.js"; +import { MethodType } from "./method-type.js"; describe("createRoutesTransport", function () { const testService = { @@ -226,22 +227,58 @@ describe("createRoutesTransport", function () { }); }); describe("supports self describing method type", function () { - const methodDefinition = { - name: "Unary", - kind: MethodKind.Unary, - I: Int32Value, - O: StringValue, - service: testService, - idempotency: MethodIdempotency.NoSideEffects, - } as const; - const transport = createRouterTransport(({ rpc }) => { - rpc(methodDefinition, (request) => { - return { value: `${request.value}-RESPONSE` }; + it("should work for unary", async function () { + const methodDefinition = { + name: "Unary", + kind: MethodKind.Unary, + I: Int32Value, + O: StringValue, + service: testService, + idempotency: MethodIdempotency.NoSideEffects, + } as const; + const transport = createRouterTransport(({ rpc }) => { + rpc(methodDefinition, (request) => { + return { value: `${request.value}-RESPONSE` }; + }); }); + + const client = createPromiseClient(testService, transport); + + const res = await client.unary({ value: 13 }); + expect(res.value).toBe("13-RESPONSE"); }); - const client = createPromiseClient(testService, transport); - it("should work for unary", async function () { + it("should work with method built from service", async function () { + const methodDefinition = { + ...testService.methods.unary, + service: testService, + } as const; + const transport = createRouterTransport(({ rpc }) => { + rpc(methodDefinition, (request) => { + return { value: `${request.value}-RESPONSE` }; + }); + }); + + const client = createPromiseClient(testService, transport); + + const res = await client.unary({ value: 13 }); + expect(res.value).toBe("13-RESPONSE"); + }); + + it("should work with method built with explicit type", async function () { + const methodDefinition: MethodType = { + ...testService.methods.unary, + service: testService, + }; + + const transport = createRouterTransport(({ rpc }) => { + rpc(methodDefinition, (request) => { + return { value: `${request.value}-RESPONSE` }; + }); + }); + + const client = createPromiseClient(testService, transport); + const res = await client.unary({ value: 13 }); expect(res.value).toBe("13-RESPONSE"); }); From fd27d2dc3ba7aa7b65321d93a723fa1ee31caece Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Thu, 26 Oct 2023 11:08:08 -0400 Subject: [PATCH 10/13] Code review changes --- packages/connect/src/implementation.ts | 25 +----- packages/connect/src/method-type.ts | 4 + packages/connect/src/router-transport.spec.ts | 59 -------------- packages/connect/src/router.spec.ts | 78 +++++++++++++++++++ packages/connect/src/router.ts | 60 +++++++------- 5 files changed, 115 insertions(+), 111 deletions(-) create mode 100644 packages/connect/src/router.spec.ts diff --git a/packages/connect/src/implementation.ts b/packages/connect/src/implementation.ts index 6ac262d74..97ea6d638 100644 --- a/packages/connect/src/implementation.ts +++ b/packages/connect/src/implementation.ts @@ -30,7 +30,6 @@ import { } from "./protocol/signals.js"; import { createContextValues } from "./context-values.js"; import type { ContextValues } from "./context-values.js"; -import type { MethodType } from "./method-type.js"; // prettier-ignore /** @@ -252,36 +251,14 @@ export type ServiceImplSpec = { * Create an MethodImplSpec - a user-provided implementation for a method, * wrapped in a discriminated union type along with service and method metadata. */ -export function createMethodImplSpec( - method: M, - impl: MethodImpl, -): MethodImplSpec; export function createMethodImplSpec( service: ServiceType, method: M, impl: MethodImpl, -): MethodImplSpec; -export function createMethodImplSpec< - M extends MethodInfo, - MT extends MethodType, ->( - ...args: [MT, MethodImpl] | [ServiceType, M, MethodImpl] ): MethodImplSpec { - if ("typeName" in args[0] && "kind" in args[1]) { - return { - kind: args[1].kind, - service: args[0], - method: args[1], - impl: args[2], - } as MethodImplSpec; - } - const [method, impl] = args as [MT, MethodImpl]; return { kind: method.kind, - service: { - ...method.service, - methods: {}, - }, + service, method, impl, } as MethodImplSpec; diff --git a/packages/connect/src/method-type.ts b/packages/connect/src/method-type.ts index 121bdddc7..f99f6137c 100644 --- a/packages/connect/src/method-type.ts +++ b/packages/connect/src/method-type.ts @@ -64,6 +64,10 @@ export interface MethodTypeBiDiStreaming< * MethodType represents a self-contained method type. It must contain * references to the service that implements it. * + * This type should ultimately live inside @bufbuild/protobuf but will + * exist here for now until https://github.com/bufbuild/protobuf-es/pull/594 + * can be merged/resolved. + * * - "name": The original name of the protobuf rpc. * - "I": The input message type. * - "O": The output message type. diff --git a/packages/connect/src/router-transport.spec.ts b/packages/connect/src/router-transport.spec.ts index 907363328..f1b3838bb 100644 --- a/packages/connect/src/router-transport.spec.ts +++ b/packages/connect/src/router-transport.spec.ts @@ -15,7 +15,6 @@ import { Int32Value, Message, - MethodIdempotency, MethodKind, proto3, StringValue, @@ -25,7 +24,6 @@ import { createAsyncIterable } from "./protocol/async-iterable.js"; import { createRouterTransport } from "./router-transport.js"; import { ConnectError } from "./connect-error.js"; import { Code } from "./code.js"; -import { MethodType } from "./method-type.js"; describe("createRoutesTransport", function () { const testService = { @@ -226,61 +224,4 @@ describe("createRoutesTransport", function () { }); }); }); - describe("supports self describing method type", function () { - it("should work for unary", async function () { - const methodDefinition = { - name: "Unary", - kind: MethodKind.Unary, - I: Int32Value, - O: StringValue, - service: testService, - idempotency: MethodIdempotency.NoSideEffects, - } as const; - const transport = createRouterTransport(({ rpc }) => { - rpc(methodDefinition, (request) => { - return { value: `${request.value}-RESPONSE` }; - }); - }); - - const client = createPromiseClient(testService, transport); - - const res = await client.unary({ value: 13 }); - expect(res.value).toBe("13-RESPONSE"); - }); - - it("should work with method built from service", async function () { - const methodDefinition = { - ...testService.methods.unary, - service: testService, - } as const; - const transport = createRouterTransport(({ rpc }) => { - rpc(methodDefinition, (request) => { - return { value: `${request.value}-RESPONSE` }; - }); - }); - - const client = createPromiseClient(testService, transport); - - const res = await client.unary({ value: 13 }); - expect(res.value).toBe("13-RESPONSE"); - }); - - it("should work with method built with explicit type", async function () { - const methodDefinition: MethodType = { - ...testService.methods.unary, - service: testService, - }; - - const transport = createRouterTransport(({ rpc }) => { - rpc(methodDefinition, (request) => { - return { value: `${request.value}-RESPONSE` }; - }); - }); - - const client = createPromiseClient(testService, transport); - - const res = await client.unary({ value: 13 }); - expect(res.value).toBe("13-RESPONSE"); - }); - }); }); diff --git a/packages/connect/src/router.spec.ts b/packages/connect/src/router.spec.ts new file mode 100644 index 000000000..1e8a5d182 --- /dev/null +++ b/packages/connect/src/router.spec.ts @@ -0,0 +1,78 @@ +// Copyright 2021-2023 The Connect Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { + Int32Value, + MethodIdempotency, + MethodKind, + StringValue, +} from "@bufbuild/protobuf"; +import { createConnectRouter } from "./router.js"; + +const testService = { + typeName: "TestService", + methods: { + unary: { + name: "Unary", + I: Int32Value, + O: StringValue, + kind: MethodKind.Unary, + }, + server: { + name: "Server", + I: Int32Value, + O: StringValue, + kind: MethodKind.ServerStreaming, + }, + client: { + name: "Client", + I: Int32Value, + O: StringValue, + kind: MethodKind.ClientStreaming, + }, + biDi: { + name: "BiDi", + I: Int32Value, + O: StringValue, + kind: MethodKind.BiDiStreaming, + }, + }, +} as const; + +describe("createConnectRouter", function () { + describe("supports self describing method type", function () { + it("should work for unary", function () { + const methodDefinition = { + name: "Unary", + kind: MethodKind.Unary, + I: Int32Value, + O: StringValue, + service: testService, + idempotency: MethodIdempotency.NoSideEffects, + } as const; + const router = createConnectRouter({}).rpc( + methodDefinition, + (request) => { + return { value: `${request.value}-RESPONSE` }; + }, + ); + + expect(router.handlers).toHaveSize(1); + expect(router.handlers[0].service).toEqual({ + ...testService, + methods: {}, + }); + }); + }); +}); diff --git a/packages/connect/src/router.ts b/packages/connect/src/router.ts index b63b8929f..41e3d568d 100644 --- a/packages/connect/src/router.ts +++ b/packages/connect/src/router.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import type { AnyMessage, MethodInfo, ServiceType } from "@bufbuild/protobuf"; +import type { MethodInfo, ServiceType } from "@bufbuild/protobuf"; import { ConnectError } from "./connect-error.js"; import { Code } from "./code.js"; import { @@ -53,17 +53,26 @@ import type { MethodType } from "./method-type.js"; */ export interface ConnectRouter { readonly handlers: UniversalHandler[]; + /** + * Provides implementation for a set of RPCs on the service. + */ service( service: T, implementation: Partial>, options?: Partial, ): this; + /** + * Provides implementation for a single RPC given service and associated method. + */ rpc( service: ServiceType, method: M, impl: MethodImpl, options?: Partial, ): this; + /** + * Provides implementation for a single RPC given a method type. + */ rpc( method: M, impl: MethodImpl, @@ -140,40 +149,35 @@ export function createConnectRouter( }, rpc( serviceOrMethod: ServiceType | MethodType, - methodOrImpl: MethodInfo | MethodImpl>, + methodOrImpl: MethodInfo | MethodImpl, implementationOrOptions?: - | MethodImpl> + | MethodImpl | Partial, options?: Partial, ) { + let service: ServiceType; + let method: MethodInfo; + let impl: MethodImpl; + let opt: Partial | undefined; if ("typeName" in serviceOrMethod) { - const { protocols } = whichProtocols(options, base); - - handlers.push( - createUniversalMethodHandler( - createMethodImplSpec( - serviceOrMethod, - methodOrImpl as MethodInfo, - implementationOrOptions as MethodImpl>, - ), - protocols, - ), - ); + service = serviceOrMethod; + method = methodOrImpl as MethodInfo; + impl = implementationOrOptions as MethodImpl; + opt = options; } else { - const { protocols } = whichProtocols( - implementationOrOptions as Partial, - base, - ); - handlers.push( - createUniversalMethodHandler( - createMethodImplSpec( - serviceOrMethod, - methodOrImpl as MethodImpl>, - ), - protocols, - ), - ); + service = { ...serviceOrMethod.service, methods: {} }; + method = serviceOrMethod; + impl = methodOrImpl as MethodImpl; + opt = implementationOrOptions as Partial; } + const { protocols } = whichProtocols(opt, base); + + handlers.push( + createUniversalMethodHandler( + createMethodImplSpec(service, method, impl), + protocols, + ), + ); return this; }, }; From 2215291c3974ddc1d669ad5df896068d51446878 Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Thu, 26 Oct 2023 11:10:16 -0400 Subject: [PATCH 11/13] Add extra validation on method --- packages/connect/src/router.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/connect/src/router.spec.ts b/packages/connect/src/router.spec.ts index 1e8a5d182..ebc18527b 100644 --- a/packages/connect/src/router.spec.ts +++ b/packages/connect/src/router.spec.ts @@ -69,6 +69,7 @@ describe("createConnectRouter", function () { ); expect(router.handlers).toHaveSize(1); + expect(router.handlers[0].method).toEqual(methodDefinition); expect(router.handlers[0].service).toEqual({ ...testService, methods: {}, From 523fdd184e6b823822bd3a5a0de00fe37522719d Mon Sep 17 00:00:00 2001 From: Paul Sachs Date: Thu, 26 Oct 2023 11:11:30 -0400 Subject: [PATCH 12/13] Remove useless extra describe block --- packages/connect/src/router.spec.ts | 39 +++++++++++++---------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/packages/connect/src/router.spec.ts b/packages/connect/src/router.spec.ts index ebc18527b..fa7fc338b 100644 --- a/packages/connect/src/router.spec.ts +++ b/packages/connect/src/router.spec.ts @@ -51,29 +51,24 @@ const testService = { } as const; describe("createConnectRouter", function () { - describe("supports self describing method type", function () { - it("should work for unary", function () { - const methodDefinition = { - name: "Unary", - kind: MethodKind.Unary, - I: Int32Value, - O: StringValue, - service: testService, - idempotency: MethodIdempotency.NoSideEffects, - } as const; - const router = createConnectRouter({}).rpc( - methodDefinition, - (request) => { - return { value: `${request.value}-RESPONSE` }; - }, - ); + it("supports self describing method type", function () { + const methodDefinition = { + name: "Unary", + kind: MethodKind.Unary, + I: Int32Value, + O: StringValue, + service: testService, + idempotency: MethodIdempotency.NoSideEffects, + } as const; + const router = createConnectRouter({}).rpc(methodDefinition, (request) => { + return { value: `${request.value}-RESPONSE` }; + }); - expect(router.handlers).toHaveSize(1); - expect(router.handlers[0].method).toEqual(methodDefinition); - expect(router.handlers[0].service).toEqual({ - ...testService, - methods: {}, - }); + expect(router.handlers).toHaveSize(1); + expect(router.handlers[0].method).toEqual(methodDefinition); + expect(router.handlers[0].service).toEqual({ + ...testService, + methods: {}, }); }); }); From ec94291678094e1a424eba080b6aee46fa7a8507 Mon Sep 17 00:00:00 2001 From: Paul Sachs <11449728+paul-sachs@users.noreply.github.com> Date: Thu, 26 Oct 2023 11:42:16 -0400 Subject: [PATCH 13/13] Update packages/connect/src/router.ts Co-authored-by: Timo Stamm --- packages/connect/src/router.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/connect/src/router.ts b/packages/connect/src/router.ts index 41e3d568d..5ef42b4fd 100644 --- a/packages/connect/src/router.ts +++ b/packages/connect/src/router.ts @@ -72,6 +72,8 @@ export interface ConnectRouter { ): this; /** * Provides implementation for a single RPC given a method type. + * + * @private This is an experimental API. Please do not rely on it yet. */ rpc( method: M,