Skip to content

Commit

Permalink
fix: change serialization format from FormData to MessagePack (#5)
Browse files Browse the repository at this point in the history
* refactor: change serialization format from FormData to MessagePack

* fix: support Node.js 14

* style: organize imports

* chore: remove unused file

* fix: support Mock Service Worker

* chore: rebuild `package-lock.json`

* fix: accept Buffer or ArrayBuffer

* chore(deps): update dependencies

* style: run Prettier
  • Loading branch information
angeloashmore committed Jan 13, 2023
1 parent 58141fb commit 408c5cb
Show file tree
Hide file tree
Showing 22 changed files with 2,268 additions and 1,703 deletions.
3,064 changes: 2,005 additions & 1,059 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 9 additions & 10 deletions package.json
Expand Up @@ -58,30 +58,29 @@
"test": "npm run lint && npm run types && npm run unit && npm run build && npm run size"
},
"dependencies": {
"busboy": "^1.6.0",
"devalue": "^4.2.0",
"formdata-node": "^5.0.0",
"@msgpack/msgpack": "^2.8.0",
"h3": "^1.0.2"
},
"devDependencies": {
"@size-limit/preset-small-lib": "^8.1.0",
"@size-limit/preset-small-lib": "^8.1.1",
"@types/busboy": "^1.5.0",
"@typescript-eslint/eslint-plugin": "^5.48.0",
"@typescript-eslint/parser": "^5.48.0",
"@vitest/coverage-c8": "^0.26.3",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.1",
"@vitest/coverage-c8": "^0.27.1",
"eslint": "^8.31.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-tsdoc": "^0.2.17",
"msw": "^0.49.2",
"node-fetch": "^3.3.0",
"prettier": "^2.8.1",
"prettier": "^2.8.2",
"prettier-plugin-jsdoc": "^0.4.2",
"size-limit": "^8.1.0",
"size-limit": "^8.1.1",
"standard-version": "^9.5.0",
"typescript": "^4.9.4",
"vite": "^4.0.4",
"vite-plugin-sdk": "^0.1.0",
"vitest": "^0.26.3"
"vitest": "^0.27.1"
},
"engines": {
"node": ">=14.15.0"
Expand Down
63 changes: 49 additions & 14 deletions src/client/createRPCClient.ts
@@ -1,8 +1,9 @@
import { formDataToObject } from "../lib/formDataToObject.client";
import { encode, decode } from "@msgpack/msgpack";

import { replaceLeaves } from "../lib/replaceLeaves";
import { isErrorLike } from "../lib/isErrorLike";
import { objectToFormData } from "../lib/objectToFormData.client";

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

const createArbitrarilyNestedFunction = <T>(
handler: (path: string[], args: unknown[]) => unknown,
Expand Down Expand Up @@ -68,11 +69,16 @@ type TransformProcedureReturnType<TReturnType> = TReturnType extends
: TReturnType;

export type ResponseLike = {
formData(): Promise<FormData>;
arrayBuffer: () => Promise<ArrayBuffer>;
};
export type FetchLike = (
input: string,
init: { method: "POST"; body: FormData },
init: {
method: "POST";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
body: any;
headers: Record<string, string>;
},
) => Promise<ResponseLike>;

export type CreateRPCClientArgs = {
Expand All @@ -87,22 +93,43 @@ export const createRPCClient = <TProcedures extends Procedures>(
args.fetch || globalThis.fetch.bind(globalThis);

return createArbitrarilyNestedFunction(async (path, fnArgs) => {
const body = objectToFormData({
procedurePath: path,
procedureArgs: fnArgs[0],
});
const preparedProcedureArgs = await replaceLeaves(
fnArgs[0],
async (value) => {
if (value instanceof Blob) {
return new Uint8Array(await value.arrayBuffer());
}

if (typeof value === "function") {
throw new Error("r19 does not support function arguments.");
}

return value;
},
);

const body = encode(
{
procedurePath: path,
procedureArgs: preparedProcedureArgs,
},
{ ignoreUndefined: true },
);

const res = await resolvedFetch(args.serverURL, {
method: "POST",
body,
headers: {
"Content-Type": "application/msgpack",
},
});

const formData = await res.formData();
const resObject = formDataToObject(formData);
const arrayBuffer = await res.arrayBuffer();
const resObject = decode(
new Uint8Array(arrayBuffer),
) as ProcedureCallServerResponse;

if ("data" in resObject) {
return resObject.data;
} else {
if ("error" in resObject) {
const resError = resObject.error;

if (isErrorLike(resError)) {
Expand All @@ -119,6 +146,14 @@ export const createRPCClient = <TProcedures extends Procedures>(
},
);
}
} else {
return replaceLeaves(resObject.data, async (value) => {
if (value instanceof Uint8Array) {
return new Blob([value]);
}

return value;
});
}
});
};
1 change: 0 additions & 1 deletion src/constants.ts

This file was deleted.

15 changes: 8 additions & 7 deletions src/createRPCMiddleware.ts
Expand Up @@ -3,15 +3,15 @@ import {
createRouter,
defineNodeMiddleware,
eventHandler,
getHeader,
NodeMiddleware,
readRawBody,
sendStream,
send,
setHeaders,
} from "h3";
import { handleRPCRequest } from "./handleRPCRequest";
import { Buffer } from "node:buffer";

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

export type RPCMiddleware<TProcedures extends Procedures> = NodeMiddleware & {
_procedures: TProcedures;
Expand All @@ -29,10 +29,11 @@ export const createRPCMiddleware = <TProcedures extends Procedures>(
router.post(
"/",
eventHandler(async (event): Promise<void> => {
const { stream, headers, statusCode } = await handleRPCRequest({
const eventBody = await readRawBody(event, false);

const { body, headers, statusCode } = await handleRPCRequest({
procedures: args.procedures,
contentTypeHeader: getHeader(event, "Content-Type"),
body: await readRawBody(event),
body: eventBody,
});

if (statusCode) {
Expand All @@ -41,7 +42,7 @@ export const createRPCMiddleware = <TProcedures extends Procedures>(

setHeaders(event, headers);

return sendStream(event, stream);
return send(event, Buffer.from(body));
}),
);

Expand Down

0 comments on commit 408c5cb

Please sign in to comment.