tRPC Conventions
SkillSearchApply when writing tRPC routers, procedures, or router tests. Esposter's tRPC conventions, how a router, a procedure and its input are laid out, named and guarded; the return-type generic on the method, useQuery/useMutation for every client read and write, read*/search*/generate* query names, a single-entity procedure replaced by a batch only when a caller acts on a set, the room RBAC builders, and the error constructors a router rejects with.
Use tRPC Conventions in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add tRPC Conventions and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the tRPC Conventions skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
What this skill tells your AI
The instructions your AI receives, as published by esposter/esposter in .agents/skills/trpc/SKILL.md and read by Ahel’s review.
Settled — do not re-propose
- A rule for the procedure builder — which of the three a route takes is a policy question about the route's data; the decidable rules are the
trpc-procedureplugin's (scripts/src/oxlint/trpcProcedure.ts). - A rule that the client path mirrors the file path — needs both trees, so it would be a test walking them rather than a lint rule.
Deep dives
references/router-tests.md— when writing or reviewing a test that drives a tRPC caller.references/subscriptions.md— when adding a subscription procedure, or deciding whether the caller of a mutation also updates its own store.references/read-endpoints.md— when writing aread*procedure, its pagination input schema, or theuseRead*composable that calls it.references/blob-mutations.md— when a mutation deletes or replaces a blob.references/procedure-arity.md— when a procedure acts on an entity, or a surface starts acting on a set of them.references/file-placement.md— when adding an input schema, a server helper, a shared service or an event emitter.references/client-calls.md— when client code calls a procedure.references/router-structure.md— when adding a router, a sub-router or a key, or mapping routers and stores to tables.references/procedure-naming.md— when naming a procedure, a result type, a subscription or a DB result variable.references/room-procedures.md— when writing a room-scoped procedure or choosing its builder.references/ownership-guards.md— when a mutation must touch only the caller's or the room's row, or a router file is about to hold a helper.
Procedures
- Return type generic on the method, not as a callback return annotation —
readFoos: standardAuthedProcedure.query<Foo[]>(async ({ ctx }) => { ... }). Same for.mutation<T>(...).- A procedure that returns nothing still writes
<void>. The generic pins a public API surface, so a handler that later grows areturnis a compile error rather than a silently widened response every client can now read.typescript/no-invalid-void-typeis off for exactly this: a generic type argument is a position upstream allows by default, oxlint does not implement that option, and the config yields rather than the correct call sites.
- A procedure that returns nothing still writes
- One entity until a caller acts on a set, then a batch that replaces it. Never a single and a batch procedure for the same operation: promotion deletes the single one, and its one-item callers send one id (
references/procedure-arity.md). - A body that can outgrow
MAX_REQUEST_SIZEis committed by reference, never carried under a raised limit — uploaded to Blob Storage through a reserved write SAS and named to a small mutation that reads and verifies it, as a staged content save is (apps/web/content/docs/architecture/file-uploads.md). - Omit
asyncwhen there is noawait— e.g. a body that onlyreturns a Drizzle query chain.
Where the Pieces Live
One input schema file per procedure under shared/models/db/<feature>/, even for identical shapes; helpers one per file under server/services/<feature>/, a store-shared one under shared/services/, a Functions-shared query under packages/db, and each emitter under its feature's events/ (references/file-placement.md).
Client-Side Calling Conventions
Every user-facing read and write goes through useQuery/useMutation; never .query({}) (trpc-procedure/no-empty-input); an empty optional id is omitted and an empty required one returns early (references/client-calls.md).
Router Structure
The client path mirrors the file path; sub-routers compose in the feature's index.ts through a base*Router; no Function.prototype name is a key (trpc-procedure/no-prototype-key) (references/router-structure.md).
Procedure & Result Naming
Every query names its verb — read*, search*, generate* (trpc-procedure/require-query-verb), a count is read*Count, a result type ends in Result, upsert* for an upsert, on<Mutation> for its subscription (references/procedure-naming.md).
Procedure Helpers (Room RBAC)
getMemberProcedure, getPermissionsProcedure or getOwnerProcedure from server/trpc/procedure/room/, and a read takes the builder its data deserves, never the one its caller's UI implies (references/room-procedures.md).
Ownership Guards in Mutations
ownedBy(table, id, userId) and inRoom(table, id, roomId), never a hand-written and(eq…); a router file holds its router({ ... }) and nothing else (references/ownership-guards.md).
Router and Store Structure
One router and one Pinia store per table, named after the table (references/router-structure.md).
Error Handling
BAD_REQUESTalways carries a message, and the router never assembles it —throw getInvalidOperationError(Operation.X, EntityType, name)fromserver/trpc/guards/, picking theOperationmatching the procedure (Operation.Readfor a query;Create/Update/Deletefor mutations), the entity type, and anameidentifying the invalid value (JSON.stringify(input), the relevant ID). A missing entity isgetNotFoundError; the constructors and the one bare code are theerror-handlingskill's (references/server-guards.md).- The
typescriptskill'sif/else ifchain rule (references/control-flow.md) applies inside procedure bodies: an early-exitifthat throws is followed byelse if, even when the conditions are logically independent.
Signals
- GitHub stars
- 23
- Forks
- 3
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
trpc-esposter- Source
- github.com/esposter/esposter