@trpc/next
Version:
119 lines (116 loc) • 4.66 kB
JavaScript
import { createTRPCUntypedClient, clientCallTypeToProcedureType } from '@trpc/client';
import { rethrowNextErrors } from '@trpc/server/adapters/next-app-dir';
import { createRecursiveProxy, formDataToObject, TRPCError, transformTRPCResponse, getTRPCErrorFromUnknown, getErrorShape } from '@trpc/server/unstable-core-do-not-import';
import { revalidateTag } from 'next/cache';
import { cache } from 'react';
import { generateCacheTag, isFormData } from './shared.mjs';
/// <reference types="next" />
// ts-prune-ignore-next
function experimental_createTRPCNextAppDirServer(opts) {
const getClient = cache(()=>{
const config = opts.config();
return createTRPCUntypedClient(config);
});
return createRecursiveProxy((callOpts)=>{
// lazily initialize client
const client = getClient();
const pathCopy = [
...callOpts.path
];
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const action = pathCopy.pop();
const procedurePath = pathCopy.join('.');
const procedureType = clientCallTypeToProcedureType(action);
const cacheTag = generateCacheTag(procedurePath, callOpts.args[0]);
if (action === 'revalidate') {
revalidateTag(cacheTag);
return;
}
return client[procedureType](procedurePath, ...callOpts.args);
});
}
function experimental_createServerActionHandler(t, opts) {
const config = t._config;
const { normalizeFormData = true, createContext, rethrowNextErrors: shouldRethrowNextErrors = true } = opts;
const transformer = config.transformer;
// TODO allow this to take a `TRouter` in addition to a `AnyProcedure`
return function createServerAction(proc) {
return async function actionHandler(rawInput) {
let ctx = undefined;
try {
ctx = await createContext?.() ?? {};
if (normalizeFormData && isFormData(rawInput)) {
// Normalizes FormData so we can use `z.object({})` etc on the server
try {
rawInput = formDataToObject(rawInput);
} catch {
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: 'Failed to convert FormData to an object'
});
}
} else if (rawInput && !isFormData(rawInput)) {
rawInput = transformer.input.deserialize(rawInput);
}
const data = proc._def.experimental_caller ? await proc(rawInput) : await proc({
input: undefined,
ctx,
path: '',
getRawInput: async ()=>rawInput,
type: proc._def.type,
// is it possible to get the AbortSignal from the request?
signal: undefined
});
const transformedJSON = transformTRPCResponse(config, {
result: {
data
}
});
return transformedJSON;
} catch (cause) {
const error = getTRPCErrorFromUnknown(cause);
opts.onError?.({
ctx,
error,
input: rawInput,
path: '',
type: proc._def.type
});
if (shouldRethrowNextErrors) {
rethrowNextErrors(error);
}
const shape = getErrorShape({
config,
ctx,
error,
input: rawInput,
path: '',
type: proc._def.type
});
return transformTRPCResponse(t._config, {
error: shape
});
}
};
};
}
// ts-prune-ignore-next
async function experimental_revalidateEndpoint(req) {
const { cacheTag } = await req.json();
if (typeof cacheTag !== 'string') {
return new Response(JSON.stringify({
revalidated: false,
error: 'cacheTag must be a string'
}), {
status: 400
});
}
revalidateTag(cacheTag);
return new Response(JSON.stringify({
revalidated: true,
now: Date.now()
}), {
status: 200
});
}
export { experimental_createServerActionHandler, experimental_createTRPCNextAppDirServer, experimental_revalidateEndpoint };