github.com/quickfeed/quickfeed@v0.0.0-20240507093252-ed8ca812a09c/public/src/client.ts (about) 1 import { CallOptions, ConnectError, Transport, makeAnyClient } from "@bufbuild/connect"; 2 import { createAsyncIterable } from "@bufbuild/connect/protocol"; 3 import { 4 MethodInfo, 5 MethodInfoServerStreaming, 6 MethodInfoUnary, 7 PartialMessage, 8 ServiceType, 9 Message, 10 MethodKind, 11 AnyMessage, 12 } from "@bufbuild/protobuf"; 13 14 export type Response<T extends AnyMessage> = { 15 error: ConnectError | null 16 message: T 17 } 18 19 /** 20 * ResponseClient is a simple client that supports unary and server-streaming 21 * methods. Methods will produce a promise for the response message, 22 * or an asynchronous iterable of response messages. 23 */ 24 export type ResponseClient<T extends ServiceType> = { 25 [P in keyof T["methods"]]: 26 T["methods"][P] extends MethodInfoUnary<infer I, infer O> ? (request: PartialMessage<I>, options?: CallOptions) => Promise<Response<O>> 27 : T["methods"][P] extends MethodInfoServerStreaming<infer I, infer O> ? (request: PartialMessage<I>, options?: CallOptions) => AsyncIterable<O> 28 : never; 29 }; 30 31 /** 32 * Create a ResponseClient for the given service, invoking RPCs through the 33 * given transport. 34 */ 35 export function createResponseClient<T extends ServiceType>( 36 service: T, 37 transport: Transport, 38 errorHandler: (payload?: { method: string; error: ConnectError; } | undefined) => void 39 ) { 40 return makeAnyClient(service, (method) => { 41 switch (method.kind) { 42 case MethodKind.Unary: 43 return createUnaryFn(transport, service, method, errorHandler); 44 case MethodKind.ServerStreaming: 45 return createServerStreamingFn(transport, service, method); 46 default: 47 return null; 48 } 49 }) as ResponseClient<T>; 50 } 51 52 /** 53 * UnaryFn is the method signature for a unary method of a ResponseClient. 54 */ 55 type UnaryFn<I extends Message<I>, O extends Message<O>> = ( 56 request: PartialMessage<I>, 57 options?: CallOptions 58 ) => Promise<Response<O>>; 59 60 function createUnaryFn<I extends Message<I>, O extends Message<O>>( 61 transport: Transport, 62 service: ServiceType, 63 method: MethodInfo<I, O>, 64 errorHandler: (payload?: { method: string; error: ConnectError; } | undefined) => void 65 ): UnaryFn<I, O> { 66 return async function (input, options) { 67 try { 68 const response = await transport.unary( 69 service, 70 method, 71 options?.signal, 72 options?.timeoutMs, 73 options?.headers, 74 input 75 ); 76 options?.onHeader?.(response.header); 77 options?.onTrailer?.(response.trailer); 78 return { 79 error: null, 80 message: response.message 81 } as Response<O>; 82 } 83 catch (error) { 84 errorHandler({ method: method.name, error }); 85 return { 86 error, 87 } as Response<O>; 88 } 89 } 90 } 91 92 /** 93 * ServerStreamingFn is the method signature for a server-streaming method of 94 * a ResponseClient. 95 */ 96 type ServerStreamingFn<I extends Message<I>, O extends Message<O>> = ( 97 request: PartialMessage<I>, 98 options?: CallOptions 99 ) => AsyncIterable<O>; 100 101 export function createServerStreamingFn< 102 I extends Message<I>, 103 O extends Message<O> 104 >( 105 transport: Transport, 106 service: ServiceType, 107 method: MethodInfo<I, O> 108 ): ServerStreamingFn<I, O> { 109 return async function* (input, options): AsyncIterable<O> { 110 const inputMessage = 111 input instanceof method.I ? input : new method.I(input); 112 const response = await transport.stream<I, O>( 113 service, 114 method, 115 options?.signal, 116 options?.timeoutMs, 117 options?.headers, 118 createAsyncIterable([inputMessage]) 119 ); 120 options?.onHeader?.(response.header); 121 yield* response.message; 122 options?.onTrailer?.(response.trailer); 123 } 124 }