@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
JavaScript
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
;