Getting the type of context

Is it possible to get the type on a context object that is passed into a specific TRPC procedure after it is modified by the middleware? If so, whats the best way to go about this? Thanks
B
BeBoRE32d ago
What do you mean by this? What are you trying to do? When you are using a middleware, context the mutation or query receives is modified by that context. Type-inference might need your help however. https://trpc.io/docs/server/middlewares#authorization, in this example you don't pass the context object back to the opts.next because then TypeScript won't infer that the user is not undefined. That's why you pass the user object directly instead. Idk if this answers your question?
T
Tribe31d ago
I am basically looking to create a type that gives me the shape of the context after it is extended by the middleware. I am sure it’s possible considering the context is properly typed within mutations and queries. I have just been failing at creating it. So in the example you linked I want a type that tells me the shape of the context including the user that was added into it by the middleware.
B
BeBoRE31d ago
I couldn't find if tRPC exposes that, you could try to infer it like so:
import type { MiddlewareBuilder, ProcedureBuilder } from '@trpc/server/dist/unstable-core-do-not-import';

type inferMiddlewareContext<TMiddleware> = TMiddleware extends MiddlewareBuilder<infer TCtx, any, any, any> ? TCtx : never;
type inferProcedureContext<TProcedure> = TProcedure extends ProcedureBuilder<infer TCtx, any, any, any, any, any, any> ? TCtx : never;
import type { MiddlewareBuilder, ProcedureBuilder } from '@trpc/server/dist/unstable-core-do-not-import';

type inferMiddlewareContext<TMiddleware> = TMiddleware extends MiddlewareBuilder<infer TCtx, any, any, any> ? TCtx : never;
type inferProcedureContext<TProcedure> = TProcedure extends ProcedureBuilder<infer TCtx, any, any, any, any, any, any> ? TCtx : never;
But this could break when tRPC updates, so use at your own risk You may want to open an issue if you want this type of inference out of the box
T
Tribe31d ago
Much appreciated. I’ll see if that works just for curiosity sake but you’re probably right, might be safer to just open a ticket.
B
BeBoRE31d ago
I just tested my solution, it doesn't work. The first generic is the incoming context, the third generic is the overwritten context, in the builder these get combined for the procedure, but that is an internal type util that I cannot make use of, you'd have to copy the Overwrite generic yourself if you want to combine the two. You can use:
type inferMiddlewareContext<TMiddleware> = TMiddleware extends MiddlewareBuilder<any, any, infer TContextOverwrite, any> ? TContextOverwrite : never;
type inferProcedureContext<TProcedure> = TProcedure extends ProcedureBuilder<any, any, infer TContextOverwrite, any, any, any, any> ? TContextOverwrite : never;
type inferMiddlewareContext<TMiddleware> = TMiddleware extends MiddlewareBuilder<any, any, infer TContextOverwrite, any> ? TContextOverwrite : never;
type inferProcedureContext<TProcedure> = TProcedure extends ProcedureBuilder<any, any, infer TContextOverwrite, any, any, any, any> ? TContextOverwrite : never;
And just make sure that the overwritten context is the same as the incoming context (for example)
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.dbUser || !ctx.session || !ctx.discordUser) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
...ctx, // Makes sure the overwritten context is the same as the incoming context
ctx: {
// infers the as non-nullable
dbUser: { ...ctx.dbUser },
session: { ...ctx.session},
discordUser: { ...ctx.discordUser },
},
});
});
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.dbUser || !ctx.session || !ctx.discordUser) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
return next({
...ctx, // Makes sure the overwritten context is the same as the incoming context
ctx: {
// infers the as non-nullable
dbUser: { ...ctx.dbUser },
session: { ...ctx.session},
discordUser: { ...ctx.discordUser },
},
});
});
If you don't want to always make sure that they are the same you can copy the Overwrite util.
export type WithoutIndexSignature<TObj> = {
[K in keyof TObj as string extends K
? never
: number extends K
? never
: K]: TObj[K];
};

export type Overwrite<TType, TWith> = TWith extends any
? TType extends object
? {
[K in // Exclude index signature from keys
| keyof WithoutIndexSignature<TType>
| keyof WithoutIndexSignature<TWith>]: K extends keyof TWith
? TWith[K]
: K extends keyof TType
? TType[K]
: never;
} & (string extends keyof TWith // Handle cases with an index signature
? { [key: string]: TWith[string] }
: number extends keyof TWith
? { [key: number]: TWith[number] }
: // eslint-disable-next-line @typescript-eslint/ban-types
{})
: TWith
: never;
export type WithoutIndexSignature<TObj> = {
[K in keyof TObj as string extends K
? never
: number extends K
? never
: K]: TObj[K];
};

export type Overwrite<TType, TWith> = TWith extends any
? TType extends object
? {
[K in // Exclude index signature from keys
| keyof WithoutIndexSignature<TType>
| keyof WithoutIndexSignature<TWith>]: K extends keyof TWith
? TWith[K]
: K extends keyof TType
? TType[K]
: never;
} & (string extends keyof TWith // Handle cases with an index signature
? { [key: string]: TWith[string] }
: number extends keyof TWith
? { [key: number]: TWith[number] }
: // eslint-disable-next-line @typescript-eslint/ban-types
{})
: TWith
: never;
And change the infer utilities to:
type inferMiddlewareContext<TMiddleware> = TMiddleware extends MiddlewareBuilder<infer $Context, any, infer $ContextOverwrite, any> ? Overwrite<$Context, $ContextOverwrite> : never;
type inferProcedureContext<TProcedure> = TProcedure extends ProcedureBuilder<infer $Context, any, infer $ContextOverwrite, any, any, any, any> ? Overwrite<$Context, $ContextOverwrite> : never;
type inferMiddlewareContext<TMiddleware> = TMiddleware extends MiddlewareBuilder<infer $Context, any, infer $ContextOverwrite, any> ? Overwrite<$Context, $ContextOverwrite> : never;
type inferProcedureContext<TProcedure> = TProcedure extends ProcedureBuilder<infer $Context, any, infer $ContextOverwrite, any, any, any, any> ? Overwrite<$Context, $ContextOverwrite> : never;
T
Tribe28d ago
Thank you for diving this deep into this, just looking into what you provided now. So since in some of my middlewares I am adding new properties to the context, I dont need the incoming context to match the outgoing so I wanna go with the second Overwrite option I believe
More Posts
Is it possible to perform attribute-based authorization after the .query?I have a TRPC method, `getStudentGradeById,` that accepts the `gradeId`. I want to return the grade How is inner context persistent if we call 'createContext' for every batch?As per title. According to the docs, inner context doesn't depend on the request, and is useful for does anyone know how put vs post calls map to the trpc procedures provided? (query vs mutation)I did check out the documentation but i only mentions GET and POST, not PUT . https://trpc.io/docs/Next.js. Migrating to turbopack casues context errorError: ``` react-dom.development.js:20662 Uncaught Error: Unable to retrieve application context. crypto not define while using generateId in trpchow to proxy a routerhey im building a chrome extension that has two adapters .. one is for communicating with the api seIs there a way to pass parameters to procedure on call?I would like to pass the required permission to the procedure like in this example: ``` create: authComplex type inference on router outputs?What are the best practices on complex outputs from routers and typing on the FE? We are doing quiteWhere to put clean up code?I have a DB connection setup in my `createContext` that I must explicitly close per-request. But I cTrigger lambda configured with trpc using other eventsCan I trigger a lambda configured with trpc api gateway adapter using events other than httpApi likeuseInfiniteQuery does not exist on appRouter t3-Stack PrismaI'm using appRouter and trying to do pagination with useInfiniteQuery() but i've got the next typescDatadog tracesHey, has anyone had any luck setting up tracing with tRPC? Specifically with dd-trace-js. We’re usiAsynchronous subscribe and unsubscribe methods in observableHello, I have been looking at the documentation about subscriptions but I am not sure about one thitrpc v11 error: The transformer property has moved to httpLink/httpBatchLink/wsLinkJust upgraded tRPC to v11 and I directly got this error: Type typeof SuperJSON is not assignable to Typescript optimisationI'm trying to optimise the generated types from the router, as I see that lots of types for context Is it possible to change how TRPC maps routers to URL paths?Hello there, in my current project I would like to map merged routers to subpaths in the url. For eCreate user on the function mutate, with TRPC and PayloadHi everyone, I have faced issues with create user on the function mutate, with TRPC, I cant trace anHow to not prefetch on getServerSideProps if it's already fetched?I have a `getServerSideProps` that utilizes `createServerSideHelpers`, and when I access a page I prReturn undefined if param is not thereIn this code cityById could be undefined and I want that this checks this and then weatherData gets useQuery caching dataHello, I have a problem. I want to do such a thing as checking for the existence of a nickname in th