UNPKG

@jsonjoy.com/reactive-rpc

Version:

Reactive-RPC is a library for building reactive APIs over WebSocket, HTTP, and other RPCs.

141 lines 6.05 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RpcCaller = void 0; const rxjs_1 = require("rxjs"); const operators_1 = require("rxjs/operators"); const rpc_error_1 = require("rpc-error"); const typed_1 = require("./error/typed"); const Value_1 = require("../../messages/Value"); const StaticRpcMethod_1 = require("../methods/StaticRpcMethod"); const BufferSubject_1 = require("../../../util/rx/BufferSubject"); const INVALID_REQUEST_ERROR_VALUE = typed_1.TypedRpcError.value(rpc_error_1.RpcError.badRequest()); const defaultWrapInternalError = (error) => typed_1.TypedRpcError.valueFrom(error); class RpcCaller { constructor({ getMethod, preCallBufferSize = 10, wrapInternalError = defaultWrapInternalError, }) { this.getMethod = getMethod; this.preCallBufferSize = preCallBufferSize; this.wrapInternalError = wrapInternalError; } exists(name) { return !!this.getMethod(name); } getMethodStrict(name) { const method = this.getMethod(name); if (!method) throw typed_1.TypedRpcError.valueFromCode(rpc_error_1.RpcErrorCodes.METHOD_UNK); return method; } info(name) { return this.getMethodStrict(name); } validate(method, request) { const validate = method.validate; if (!validate) return; try { const errors = validate(request); if (errors) throw errors; } catch (error) { throw this.wrapValidationError(error); } } wrapValidationError(error) { return typed_1.TypedRpcError.valueFrom(error, INVALID_REQUEST_ERROR_VALUE); } async call(name, request, ctx) { const method = this.getMethodStrict(name); this.validate(method, request); try { const preCall = method.onPreCall; if (preCall) await preCall(ctx, request); const data = await method.call(request, ctx); return new Value_1.RpcValue(data, method.res); } catch (error) { throw this.wrapInternalError(error); } } async notification(name, request, ctx) { const method = this.getMethodStrict(name); if (!(method instanceof StaticRpcMethod_1.StaticRpcMethod)) return; if (!method.acceptsNotifications) return; this.validate(method, request); try { if (method.onPreCall) await method.onPreCall(ctx, request); await method.call(request, ctx); } catch (error) { throw this.wrapInternalError(error); } } createCall(name, ctx) { const req$ = new rxjs_1.Subject(); const reqUnsubscribe$ = new rxjs_1.Subject(); const stop$ = new rxjs_1.Subject(); try { const method = this.getMethodStrict(name); if (!method.isStreaming) { const response$ = (0, rxjs_1.from)((async () => { const request = await (0, rxjs_1.firstValueFrom)(req$.pipe((0, operators_1.first)())); return await this.call(name, request, ctx); })()); const res$ = new rxjs_1.Subject(); response$.subscribe(res$); const $resWithErrorsFormatted = res$.pipe((0, operators_1.catchError)((error) => { throw this.wrapInternalError(error); })); return { req$, reqUnsubscribe$, stop$, res$: $resWithErrorsFormatted.pipe((0, operators_1.takeUntil)(stop$)) }; } const methodStreaming = method; const requestValidated$ = req$.pipe((0, operators_1.tap)((request) => { this.validate(methodStreaming, request); })); const bufferSize = methodStreaming.preCallBufferSize || this.preCallBufferSize; const requestBuffered$ = new BufferSubject_1.BufferSubject(bufferSize); const error$ = new rxjs_1.Subject(); requestBuffered$.subscribe({ error: (error) => { error$.error(error); }, }); requestValidated$.subscribe(requestBuffered$); const methodResponseType = method.res; const result$ = requestBuffered$.pipe((0, operators_1.take)(1), (0, operators_1.switchMap)((request) => { return methodStreaming.onPreCall ? (0, rxjs_1.from)(methodStreaming.onPreCall(ctx, request)) : (0, rxjs_1.from)([0]); }), (0, operators_1.switchMap)(() => { Promise.resolve().then(() => { requestBuffered$.flush(); }); return method.call$(requestBuffered$, ctx).pipe((0, operators_1.map)((response) => new Value_1.RpcValue(response, methodResponseType)), (0, operators_1.finalize)(() => { error$.complete(); })); }), (0, operators_1.share)(), (0, operators_1.mergeWith)(error$)); const $resWithErrorsFormatted = result$.pipe((0, operators_1.finalize)(() => { error$.complete(); }), (0, operators_1.catchError)((error) => { throw typed_1.TypedRpcError.valueFrom(error); })); return { req$, reqUnsubscribe$, stop$, res$: $resWithErrorsFormatted.pipe((0, operators_1.takeUntil)(stop$)) }; } catch (error) { const errorFormatted = typed_1.TypedRpcError.valueFrom(error); req$.error(errorFormatted); const res$ = new rxjs_1.Subject(); res$.error(errorFormatted); return { req$, reqUnsubscribe$, stop$, res$: res$.pipe((0, operators_1.takeUntil)(stop$)) }; } } call$(name, request$, ctx) { const call = this.createCall(name, ctx); request$.subscribe(call.req$); return call.res$; } } exports.RpcCaller = RpcCaller; //# sourceMappingURL=RpcCaller.js.map