Skip to content

Commit

Permalink
feat: add onError event handler
Browse files Browse the repository at this point in the history
  • Loading branch information
lihbr committed Mar 14, 2023
1 parent c600aaa commit 7da6924
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 12 deletions.
4 changes: 3 additions & 1 deletion src/createRPCMiddleware.ts
@@ -1,7 +1,7 @@
import { Buffer } from "node:buffer";
import { IncomingMessage, ServerResponse } from "node:http";

import { Procedures } from "./types";
import { OnErrorEventHandler, Procedures } from "./types";
import { handleRPCRequest } from "./handleRPCRequest";

export type RPCMiddleware<TProcedures extends Procedures> = {
Expand All @@ -12,6 +12,7 @@ export type RPCMiddleware<TProcedures extends Procedures> = {

export type CreateRPCMiddlewareArgs<TProcedures extends Procedures> = {
procedures: TProcedures;
onError?: OnErrorEventHandler;
};

export const createRPCMiddleware = <TProcedures extends Procedures>(
Expand All @@ -36,6 +37,7 @@ export const createRPCMiddleware = <TProcedures extends Procedures>(
const { body, headers, statusCode } = await handleRPCRequest({
procedures: args.procedures,
body: Buffer.concat(requestBodyChunks),
onError: args.onError,
});

if (statusCode) {
Expand Down
30 changes: 22 additions & 8 deletions src/handleRPCRequest.ts
Expand Up @@ -4,7 +4,12 @@ import { decode, encode } from "@msgpack/msgpack";
import { isErrorLike } from "./lib/isErrorLike";
import { replaceLeaves } from "./lib/replaceLeaves";

import { Procedure, Procedures, ProcedureCallServerArgs } from "./types";
import {
Procedure,
Procedures,
ProcedureCallServerArgs,
OnErrorEventHandler,
} from "./types";

const findProcedure = (
procedures: Procedures,
Expand Down Expand Up @@ -35,6 +40,7 @@ const findProcedure = (
type HandleRPCRequestArgs<TProcedures extends Procedures> = {
procedures: TProcedures;
body: ArrayBuffer | Buffer | undefined;
onError?: OnErrorEventHandler;
};

type HandleRPCRequestReturnType = {
Expand All @@ -60,13 +66,15 @@ export const handleRPCRequest = async <TProcedures extends Procedures>(
};

if (!procedure) {
const body = encode({
error: {
name: "RPCError",
message: `Invalid procedure name: ${clientArgs.procedurePath.join(
".",
)}`,
},
const rawBody = {
name: "RPCError",
message: `Invalid procedure name: ${clientArgs.procedurePath.join(".")}`,
};
const body = encode(rawBody);

args.onError?.({
error: new Error(`${rawBody.name}: ${rawBody.message}`),
...clientArgs,
});

return {
Expand Down Expand Up @@ -94,6 +102,8 @@ export const handleRPCRequest = async <TProcedures extends Procedures>(

res = await replaceLeaves(res, async (value) => {
if (isErrorLike(value)) {
args.onError?.({ error: value, ...clientArgs });

return {
name: value.name,
message: value.message,
Expand Down Expand Up @@ -122,6 +132,8 @@ export const handleRPCRequest = async <TProcedures extends Procedures>(
{ ignoreUndefined: true },
);

args.onError?.({ error, ...clientArgs });

return {
body,
headers,
Expand Down Expand Up @@ -156,6 +168,8 @@ export const handleRPCRequest = async <TProcedures extends Procedures>(
},
});

args.onError?.({ error, ...clientArgs });

return {
body,
headers,
Expand Down
7 changes: 6 additions & 1 deletion src/index.ts
Expand Up @@ -12,4 +12,9 @@ export {

export { handleRPCRequest } from "./handleRPCRequest";

export { Procedure, Procedures, ExtractProcedures } from "./types";
export {
Procedure,
Procedures,
ExtractProcedures,
OnErrorEventHandler,
} from "./types";
7 changes: 7 additions & 0 deletions src/types.ts
@@ -1,4 +1,5 @@
import { RPCMiddleware } from "./createRPCMiddleware";
import { ErrorLike } from "./lib/isErrorLike";

export type Procedures = Record<
string,
Expand Down Expand Up @@ -45,3 +46,9 @@ export type ExtractProcedures<
> = TRPCMiddleware extends RPCMiddleware<infer TProcedures>
? TProcedures
: never;

export type OnErrorEventHandler = (
args: {
error: ErrorLike;
} & ProcedureCallServerArgs,
) => Promise<void> | void;
40 changes: 38 additions & 2 deletions test/index.test.ts
Expand Up @@ -4,17 +4,24 @@ import { AddressInfo } from "node:net";
import express from "express";
import fetch from "node-fetch";

import { createRPCMiddleware, proceduresFromInstance } from "../src";
import {
createRPCMiddleware,
proceduresFromInstance,
OnErrorEventHandler,
} from "../src";
import { createRPCClient } from "../src/client";

type StartRPCTestServerArgs = {
procedures: Parameters<typeof createRPCMiddleware>[0]["procedures"];
onError?: OnErrorEventHandler;
};

const startRPCTestServer = (args: StartRPCTestServerArgs) => {
const app = express();

app.use(createRPCMiddleware({ procedures: args.procedures }));
app.use(
createRPCMiddleware({ procedures: args.procedures, onError: args.onError }),
);

const server = app.listen();

Expand Down Expand Up @@ -263,6 +270,35 @@ it("does not support class return values with methods", async () => {
expect(res).toStrictEqual({});
});

it("supports `onError` event handler", async () => {
const procedures = {
throw: (args: { input: string }) => {
throw new Error(args.input);
},
};
const onError = vi.fn();
const server = startRPCTestServer({ procedures, onError });

const client = createRPCClient<typeof procedures>({
serverURL: server.url,
fetch,
});

await expect(client.throw({ input: "foo" })).rejects.toMatchInlineSnapshot(
"[Error: foo]",
);

server.close();

expect(onError).toHaveBeenLastCalledWith({
error: expect.any(Error),
procedureArgs: {
input: "foo",
},
procedurePath: ["throw"],
});
});

it("returns 405 if POST method is not used", async () => {
const procedures = {
ping: (args: { input: string }) => ({ pong: args.input }),
Expand Down

0 comments on commit 7da6924

Please sign in to comment.