diff --git a/package-lock.json b/package-lock.json
index 6f7af7bac..643e842f2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -52,7 +52,6 @@
"remark-emoji": "4.0.0",
"remark-gfm": "3.0.1",
"styled-components": "5.3.11",
- "superjson": "1.12.3",
"throttle-debounce": "5.0.0",
"tinykeys": "1.4.0",
"zod": "3.22.3"
diff --git a/package.json b/package.json
index 92ecb7d47..032a69cf0 100644
--- a/package.json
+++ b/package.json
@@ -72,7 +72,6 @@
"remark-emoji": "4.0.0",
"remark-gfm": "3.0.1",
"styled-components": "5.3.11",
- "superjson": "1.12.3",
"throttle-debounce": "5.0.0",
"tinykeys": "1.4.0",
"zod": "3.22.3"
diff --git a/src/utils/declareSsrProps.ts b/src/utils/declareSsrProps.ts
index a218a1af6..c88fff1c7 100644
--- a/src/utils/declareSsrProps.ts
+++ b/src/utils/declareSsrProps.ts
@@ -2,12 +2,13 @@ import { GetServerSidePropsContext } from 'next';
import { Session } from 'next-auth';
import { getSession } from 'next-auth/react';
import { createServerSideHelpers, DecoratedProcedureSSGRecord } from '@trpc/react-query/server';
-import superjson from 'superjson';
import { routes } from '../hooks/router';
import { trpcRouter } from '../../trpc/router';
import type { TrpcRouter } from '../../trpc/router';
+import { transformer } from './transformer';
+
export interface SSRProps
{
user: Session['user'];
req: GetServerSidePropsContext['req'];
@@ -45,7 +46,7 @@ export function declareSsrProps(
session,
headers: req.headers,
},
- transformer: superjson,
+ transformer,
});
const ssrTime = Date.now();
diff --git a/src/utils/transformer.test.ts b/src/utils/transformer.test.ts
new file mode 100644
index 000000000..79575c7c9
--- /dev/null
+++ b/src/utils/transformer.test.ts
@@ -0,0 +1,30 @@
+import { expect } from '@jest/globals';
+
+import { deserialize, serialize } from './transformer';
+
+const dateString = '_serializedDate_1970-01-01T00:00:00.000Z';
+const undefinedString = '_undefined_';
+
+describe('Transformer', () => {
+ it('should serialize and deserialize', async () => {
+ const values = [
+ { from: 1, to: 1 },
+ { from: 'a', to: 'a' },
+ { from: new Date(0), to: dateString },
+ { from: undefined, to: undefinedString },
+ { from: [], to: [] },
+ { from: ['a', 1, new Date(0), [new Date(0)]], to: ['a', 1, dateString, [dateString]] },
+ { from: ['a', [new Date(0)]], to: ['a', [dateString]] },
+ { from: [undefined, [[undefined]]], to: [undefinedString, [[undefinedString]]] },
+ { from: {}, to: {} },
+ { from: { a: { b: new Date(0), c: {} } }, to: { a: { b: dateString, c: {} } } },
+ { from: { a: { b: null } }, to: { a: { b: null } } },
+ ];
+ for (const { from, to } of values) {
+ const serialized = serialize(from);
+ expect(serialized).toStrictEqual(to);
+ const deserialized = deserialize(to);
+ expect(deserialized).toStrictEqual(from);
+ }
+ });
+});
diff --git a/src/utils/transformer.ts b/src/utils/transformer.ts
new file mode 100644
index 000000000..4cb0f5922
--- /dev/null
+++ b/src/utils/transformer.ts
@@ -0,0 +1,36 @@
+import { CombinedDataTransformer } from '@trpc/server';
+
+const datePrefix = '_serializedDate_';
+const undefinedPlaceholder = '_undefined_';
+
+export const serialize = (data: any): any => {
+ if (typeof data === 'function') throw new Error('Cannot serialize function');
+ if (typeof data === 'symbol') throw new Error('Cannot serialize symbol');
+ if (typeof data === 'bigint') throw new Error('Cannot serialize bigint');
+ if (typeof data === 'undefined') return undefinedPlaceholder;
+ if (typeof data === 'object') {
+ if (data === null) return data;
+ if (data instanceof Date) return `${datePrefix}${data.toISOString()}`;
+ if (Array.isArray(data)) return data.map(serialize);
+ return Object.fromEntries(Object.entries(data).map(([k, v]) => [k, serialize(v)]));
+ }
+ return data;
+};
+
+export const deserialize = (data: any): any => {
+ if (typeof data === 'string') {
+ if (data.startsWith(datePrefix)) return new Date(data.slice(datePrefix.length));
+ if (data === undefinedPlaceholder) return undefined;
+ }
+ if (typeof data === 'object') {
+ if (data === null) return data;
+ if (Array.isArray(data)) return data.map(deserialize);
+ return Object.fromEntries(Object.entries(data).map(([k, v]) => [k, deserialize(v)]));
+ }
+ return data;
+};
+
+export const transformer: CombinedDataTransformer = {
+ input: { serialize, deserialize },
+ output: { serialize, deserialize },
+};
diff --git a/src/utils/trpcClient.ts b/src/utils/trpcClient.ts
index b1de60a24..869548da4 100644
--- a/src/utils/trpcClient.ts
+++ b/src/utils/trpcClient.ts
@@ -1,9 +1,10 @@
import { httpBatchLink } from '@trpc/client';
import { createTRPCNext } from '@trpc/next';
-import superjson from 'superjson';
import type { TrpcRouter } from '../../trpc/router';
+import { transformer } from './transformer';
+
function getBaseUrl() {
if (typeof window !== 'undefined') {
// browser should use relative path
@@ -15,7 +16,7 @@ function getBaseUrl() {
export const trpc = createTRPCNext({
config: ({ ctx }) => {
return {
- transformer: superjson,
+ transformer,
queryClientConfig: {
defaultOptions: {
diff --git a/trpc/trpcBackend.ts b/trpc/trpcBackend.ts
index 3b6256f39..6b40c6c07 100644
--- a/trpc/trpcBackend.ts
+++ b/trpc/trpcBackend.ts
@@ -1,10 +1,11 @@
import { TRPCError, initTRPC } from '@trpc/server';
-import superjson from 'superjson';
+
+import { transformer } from '../src/utils/transformer';
import type { TrpcContext } from './context';
const t = initTRPC.context().create({
- transformer: superjson,
+ transformer,
});
const sessionCheck = t.middleware(({ next, ctx }) => {