# Data flow — trpc/trpc

> Canonical page: https://codevetter.com/unpack/trpc/data-flow

# Data flow — trpc/trpc

Scanned commit 8b649ad874a8fe97c73962d4617912231b975736 (2026-09-17). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

A call is a typed operation that becomes an observable, passes through a link chain, and is encoded as JSON (or JSONL/SSE/WebSocket frames). On the server, getRequestInfo splits batch paths, deserializes inputs with the configured transformer, and resolveResponse invokes procedures with one shared context. Middleware, including validators, runs before the resolver. Outputs are serialized by transformTRPCResponse. Errors are a fixed shape keyed by JSON-RPC-style codes. Persistence, when present, is inside user resolvers, not the framework.
- createTRPCClient constructs a TRPCUntypedClient and wraps it in createTRPCClientProxy. The proxy pops the last path segment, maps query/mutate/subscribe through clientCallTypeMap, joins the rest with dots, and calls client.query, client.mutation, or client.subscription. — [packages/client/src/createTRPCClient.ts#L122-L166](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/client/src/createTRPCClient.ts#L122-L166)
- TRPCUntypedClient.$request increments requestId and calls createChain with the operation. createChain walks links[index].next until a link terminates the observable. requestAsPromise converts the observable with observableToPromise and returns envelope.result.data, wrapping failures in TRPCClientError.from. — [packages/client/src/internals/TRPCUntypedClient.ts#L47-L96](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/client/src/internals/TRPCUntypedClient.ts#L47-L96) [packages/client/src/links/internals/createChain.ts#L10-L39](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/client/src/links/internals/createChain.ts#L10-L39)
- httpBatchLink builds separate dataLoaders for query and mutation. A batch is valid only when length <= maxItems (default Infinity) and the joined URL length <= maxURLLength (default Infinity). fetch() joins paths with commas, combines abort signals, and calls jsonHttpRequester. Subscriptions throw and must use httpSubscriptionLink or wsLink. — [packages/client/src/links/httpBatchLink.ts#L21-L100](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/client/src/links/httpBatchLink.ts#L21-L100)
- jsonContentTypeHandler treats batch=1 as a batch, splits opts.path on commas, enforces maxBatchSize, and reads inputs from the input query param on GET or from req.json() otherwise. Each input is passed through router._def._config.transformer.input.deserialize before the procedure sees it. — [packages/server/src/unstable-core-do-not-import/http/contentType.ts#L78-L149](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/http/contentType.ts#L78-L149) [packages/server/src/unstable-core-do-not-import/transformer.ts#L70-L85](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/transformer.ts#L70-L85)
- resolveResponse creates context exactly once via ctxManager.create. Each call in info.calls loads the procedure, checks TYPE_ACCEPTED_METHOD_MAP, and invokes proc with path, getRawInput, ctx, type, signal, and batchIndex. Non-batch JSON responses set content-type application/json and JSON.stringify(transformTRPCResponse(config, res)). — [packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts#L213-L251](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts#L213-L251) [packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts#L328-L377](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts#L328-L377) [packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts#L405-L449](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts#L405-L449)
- createProcedureCaller refuses direct calls, then callRecursive runs _def.middlewares[index] and threads ctx, input, and getRawInput overrides into next(). A missing result throws INTERNAL_SERVER_ERROR ('did you forget to return next()'). A failed middleware rethrows its TRPCError. The resolver middleware is appended last by createResolver. — [packages/server/src/unstable-core-do-not-import/procedureBuilder.ts#L568-L703](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/procedureBuilder.ts#L568-L703)
- defaultTransformer is an identity serialize/deserialize pair. getDataTransformer splits a single DataTransformer into {input, output}. transformTRPCResponse serializes error payloads and result.data with config.transformer.output.serialize and leaves other envelopes unchanged. — [packages/server/src/unstable-core-do-not-import/transformer.ts#L70-L119](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/transformer.ts#L70-L119)
- getErrorShape builds {message, code: TRPC_ERROR_CODES_BY_KEY[code], data:{code, httpStatus}} and, only when config.isDev and error.stack is a string, copies the stack onto data.stack. The user errorFormatter then receives that shape. isDev defaults to NODE_ENV !== production. — [packages/server/src/unstable-core-do-not-import/error/getErrorShape.ts#L11-L35](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/error/getErrorShape.ts#L11-L35) [packages/server/src/unstable-core-do-not-import/initTRPC.ts#L156-L159](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/initTRPC.ts#L156-L159) [packages/server/src/unstable-core-do-not-import/rpc/codes.ts#L11-L44](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/rpc/codes.ts#L11-L44)
- Mutations accept only POST. Queries and subscriptions accept GET, or GET and POST when allowMethodOverride is true and the request method is POST. allowMethodOverride defaults false. allowBatching defaults to batching.enabled or true. A batch call when batching is disabled throws BAD_REQUEST. — [packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts#L49-L62](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts#L49-L62) [packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts#L229-L313](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/unstable-core-do-not-import/http/resolveResponse.ts#L229-L313)
- fetchRequestHandler trims slashes, slices the endpoint off the pathname, and delegates to resolveResponse with error: null. createNextApiHandler reads req.query.trpc (string or array joined by /) and delegates to nodeHTTPRequestHandler. The Cloudflare example mounts endpoint /trpc on the Worker fetch handler. — [packages/server/src/adapters/fetch/fetchRequestHandler.ts#L17-L47](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/adapters/fetch/fetchRequestHandler.ts#L17-L47) [packages/server/src/adapters/next.ts#L33-L59](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/packages/server/src/adapters/next.ts#L33-L59) [examples/cloudflare-workers/src/index.ts#L15-L23](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/examples/cloudflare-workers/src/index.ts#L15-L23)
- In next-sse-chat, createContext calls NextAuth auth() and returns {session}. authedProcedure throws UNAUTHORIZED unless ctx.session.user.name is set, then overrides ctx.user. post.add inserts a Drizzle Post row, emits on an in-memory emitter, and post.onAdd yields tracked(post.id, post) from both a DB replay and that emitter. — [examples/next-sse-chat/src/server/context.ts#L8-L18](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/examples/next-sse-chat/src/server/context.ts#L8-L18) [examples/next-sse-chat/src/server/trpc.ts#L81-L96](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/examples/next-sse-chat/src/server/trpc.ts#L81-L96) [examples/next-sse-chat/src/server/routers/post.ts#L9-L140](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/examples/next-sse-chat/src/server/routers/post.ts#L9-L140)
- In next-prisma-starter, post.list/byId/add call prisma.post with an explicit defaultPostSelect (id, title, text, createdAt, updatedAt). byId throws TRPCError NOT_FOUND when missing. Context is an empty object; createContextInner exists so tests can skip the Next req/res. — [examples/next-prisma-starter/src/server/routers/post.ts#L16-L103](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/examples/next-prisma-starter/src/server/routers/post.ts#L16-L103) [examples/next-prisma-starter/src/server/context.ts#L13-L29](https://github.com/trpc/trpc/blob/8b649ad874a8fe97c73962d4617912231b975736/examples/next-prisma-starter/src/server/context.ts#L13-L29)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
