UNPKG

@trpc/next

Version:

The tRPC Next.js library

119 lines (116 loc) 4.66 kB
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 };