Mugetsu
Mugetsu
TtRPC
Created by Mugetsu on 10/10/2024 in #❓-help
How to get a TRPCClientError shape as in initTRPC errorFormatter for testing
I want to test some UI logic handling errors from trpc but I struggle how to setup the error shape in the test so it triggers proper branches while test. here is my initTrpc
const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
errorFormatter({ error, shape }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
apiError: ApiError.isApiError(error.cause) ? error.cause.errors : [],
errorStatuses: [
shape.data.httpStatus,
ApiError.isApiError(error.cause) ? error.cause.status : undefined,
],
},
};
},
});
const t = initTRPC.context<typeof createTRPCContext>().create({
transformer: superjson,
errorFormatter({ error, shape }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.cause instanceof ZodError ? error.cause.flatten() : null,
apiError: ApiError.isApiError(error.cause) ? error.cause.errors : [],
errorStatuses: [
shape.data.httpStatus,
ApiError.isApiError(error.cause) ? error.cause.status : undefined,
],
},
};
},
});
Handler I want to test:
export const onFormApiError = (error: unknown) => {
if (!isTRPCClientError(error)) return; // should never trigger

// HOW DO I SETUP THE ERROR IN TEST WITH THIS SHAPE
const errors = error?.data?.apiError || [];
....
}
export const onFormApiError = (error: unknown) => {
if (!isTRPCClientError(error)) return; // should never trigger

// HOW DO I SETUP THE ERROR IN TEST WITH THIS SHAPE
const errors = error?.data?.apiError || [];
....
}
And How can I test it???
it('test test', () => {
// WHAT DO I PASS HERE AS PROPER ERRROR with the shape??
onFormApiError(new TRPCClientError('test3', { data: { apiError: [] } }));

});
it('test test', () => {
// WHAT DO I PASS HERE AS PROPER ERRROR with the shape??
onFormApiError(new TRPCClientError('test3', { data: { apiError: [] } }));

});
4 replies
TtRPC
Created by Mugetsu on 10/2/2024 in #❓-help
How to get mutation onError typescript type
I have two mutations on my frontend that should handle the response in the same way. So I extract the onError function so these two mutations can re-use the logic but I have a problem on how to type the error so the in function handling is pleased with typescript and the onError handler is also pleased.
// What type of the error should be here?
const onApiError = (error: ???) => {...}


const createMutation = api.location.create.useMutation({ onError: onApiError });
const editMutation = api.location.edit.useMutation({ onError: onApiError });
// What type of the error should be here?
const onApiError = (error: ???) => {...}


const createMutation = api.location.create.useMutation({ onError: onApiError });
const editMutation = api.location.edit.useMutation({ onError: onApiError });
I have tried like error: TRPCClientError<AppRouter> this does satisfy the function and internal of it but doesn't comply with the onError handlers onError: onApiError
2 replies
TtRPC
Created by Mugetsu on 2/26/2024 in #❓-help
Value attached to all queries
Is it possible to add a value the all queries so its preset within every procedure call on the client?? Lets say I have a cookie which Is determied on the server and I want it to be added to every response or the trpc query calls. Is it possible to add it in a single place rather adding it in every procedure I make making it very prone to error because I can forget it ?
2 replies
TtRPC
Created by Mugetsu on 2/14/2024 in #❓-help
onError callback type
I want to have a callback onError passed from parent component to the child which has mutation call. onError should be passed directly to the mutation options but also accept plan Error type and undefined. But I struggle how to type it correctly.
export const DownloadTrigger = ({
onError,
} {
onError: ReactQueryOptions['batch']['triggerDownload']['onError']; // This doesn't quire work is there more generic type or I should just add Error | undefined?
}) => {
const downloadMutation = api.batch.triggerDownload.useMutation({
onError,
});

useEffect(() => {
handleFile()
.catch((err: Error) => {
// @ts-ignore
onError(err);
})
.finally(() => {
// @ts-ignore
onError(undefined);
});
export const DownloadTrigger = ({
onError,
} {
onError: ReactQueryOptions['batch']['triggerDownload']['onError']; // This doesn't quire work is there more generic type or I should just add Error | undefined?
}) => {
const downloadMutation = api.batch.triggerDownload.useMutation({
onError,
});

useEffect(() => {
handleFile()
.catch((err: Error) => {
// @ts-ignore
onError(err);
})
.finally(() => {
// @ts-ignore
onError(undefined);
});
6 replies
TtRPC
Created by Mugetsu on 2/10/2024 in #❓-help
How to extract mutation type
Is it possible to extract mutation type? I would like to pass a mutation trigger to the parent component but I dont know how I could extract the mutation type so typescript is happy
///child component

const DownloadTrigger = ({
type,
disabled,
form,
mutateTrigger,
}: Pick<ComponentProps<typeof DdsButton>, 'type' | 'disabled' | 'form'> & {
mutateTrigger // DUNNO HOW TO TYPE THIS
}) => {
const downloadMutation = api.batch.triggerDownload.useMutation();
....

const handleOnClick = () => {
console.log('CLICK');
mutateTrigger(downloadMutation.mutate);
// can I extract somehow the type of mutation so in parent component typescript follows the required mutation input type??
};

return (
<DdsButton
type={type}
form={form}
disabled={disabled}
onClick={handleOnClick}
>
Submit
</DdsButton>
);
};
///child component

const DownloadTrigger = ({
type,
disabled,
form,
mutateTrigger,
}: Pick<ComponentProps<typeof DdsButton>, 'type' | 'disabled' | 'form'> & {
mutateTrigger // DUNNO HOW TO TYPE THIS
}) => {
const downloadMutation = api.batch.triggerDownload.useMutation();
....

const handleOnClick = () => {
console.log('CLICK');
mutateTrigger(downloadMutation.mutate);
// can I extract somehow the type of mutation so in parent component typescript follows the required mutation input type??
};

return (
<DdsButton
type={type}
form={form}
disabled={disabled}
onClick={handleOnClick}
>
Submit
</DdsButton>
);
};
11 replies
TtRPC
Created by Mugetsu on 1/22/2024 in #❓-help
Controller is already closed crash
Im using trpc with nextjs 14 app router and started to see that my app crashes due to such error. Did anyone encouter such error before ??
"error": {
"type": "TRPCError",
"message": "Invalid state: Controller is already closed: Invalid state: Controller is already closed",
"stack": "TypeError [ERR_INVALID_STATE]: Invalid state: Controller is already closed\n at new NodeError (node:internal/errors:406:5)\n at ReadableStreamDefaultController.enqueue (node:internal/webstreams/readablestream:1065:13)\n at unstable_onChunk (webpack-internal:///(rsc)/./node_modules/.pnpm/@trpc+server@10.45.0/node_modules/@trpc/server/dist/adapters/fetch/index.mjs:94:24)\n at resolveHTTPResponse (webpack-internal:///(rsc)/./node_modules/.pnpm/@trpc+server@10.45.0/node_modules/@trpc/server/dist/resolveHTTPResponse-37afa02e.mjs:242:17)\n at processTicksAndRejections (node:internal/process/task_queues:95:5)\ncaused by: TypeError [ERR_INVALID_STATE]: Invalid state: Controller is already closed\n at new NodeError (node:internal/errors:406:5)\n at ReadableStreamDefaultController.enqueue (node:internal/webstreams/readablestream:1065:13)\n at unstable_onChunk (webpack-internal:///(rsc)/./node_modules/.pnpm/@trpc+server@10.45.0/node_modules/@trpc/server/dist/adapters/fetch/index.mjs:94:24)\n at resolveHTTPResponse (webpack-internal:///(rsc)/./node_modules/.pnpm/@trpc+server@10.45.0/node_modules/@trpc/server/dist/resolveHTTPResponse-37afa02e.mjs:242:17)\n at processTicksAndRejections (node:internal/process/task_queues:95:5)",
"code": "INTERNAL_SERVER_ERROR",
"name": "TRPCError"
},
"error": {
"type": "TRPCError",
"message": "Invalid state: Controller is already closed: Invalid state: Controller is already closed",
"stack": "TypeError [ERR_INVALID_STATE]: Invalid state: Controller is already closed\n at new NodeError (node:internal/errors:406:5)\n at ReadableStreamDefaultController.enqueue (node:internal/webstreams/readablestream:1065:13)\n at unstable_onChunk (webpack-internal:///(rsc)/./node_modules/.pnpm/@trpc+server@10.45.0/node_modules/@trpc/server/dist/adapters/fetch/index.mjs:94:24)\n at resolveHTTPResponse (webpack-internal:///(rsc)/./node_modules/.pnpm/@trpc+server@10.45.0/node_modules/@trpc/server/dist/resolveHTTPResponse-37afa02e.mjs:242:17)\n at processTicksAndRejections (node:internal/process/task_queues:95:5)\ncaused by: TypeError [ERR_INVALID_STATE]: Invalid state: Controller is already closed\n at new NodeError (node:internal/errors:406:5)\n at ReadableStreamDefaultController.enqueue (node:internal/webstreams/readablestream:1065:13)\n at unstable_onChunk (webpack-internal:///(rsc)/./node_modules/.pnpm/@trpc+server@10.45.0/node_modules/@trpc/server/dist/adapters/fetch/index.mjs:94:24)\n at resolveHTTPResponse (webpack-internal:///(rsc)/./node_modules/.pnpm/@trpc+server@10.45.0/node_modules/@trpc/server/dist/resolveHTTPResponse-37afa02e.mjs:242:17)\n at processTicksAndRejections (node:internal/process/task_queues:95:5)",
"code": "INTERNAL_SERVER_ERROR",
"name": "TRPCError"
},
7 replies
TtRPC
Created by Mugetsu on 1/9/2024 in #❓-help
LoggerLink logging only via server logger in prod
I have a logger link setup as declared in https://trpc.io/docs/client/links/loggerLink#usage
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === 'development' ||
(op.direction === 'down' && op.result instanceof Error),
}),
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === 'development' ||
(op.direction === 'down' && op.result instanceof Error),
}),
This is logging to console.log by default as I understand correctly. I have a specific logger instance using pino which can be used only on the server side. How do I acommodate this loggerlink to log errors via my logger instance only if it ERRORS in PRODUCTION?
3 replies
TtRPC
Created by Mugetsu on 10/13/2023 in #❓-help
Express tRPC NextJS
I've custom expressjs server with Nextjs + im using internal package for handling OneLogin (OIDC) hooked as express middleware which runs. before the Nextjs trpc. I encounter a problem where I do fire a request api call via trpc query or mutation and the OIDC middleware catches that the request is 401 trpc throws on me that TRPCClientError: Unexpected token 'U', "Unauthorized" is not valid JSON if I change the response of the middleware as a json instead of text something like
app.use((req, res, next) => {
if (/* some condition checking for the cookie */) {
next();
} else {
// Respond with a 401 status and a JSON body
res.status(401).json({ error: "Unauthorized" });
}
});
app.use((req, res, next) => {
if (/* some condition checking for the cookie */) {
next();
} else {
// Respond with a 401 status and a JSON body
res.status(401).json({ error: "Unauthorized" });
}
});
Then I get app-index.js:31 TRPCClientError: Unexpected non-whitespace character after JSON at position 14 (line 1 column 15) with tRPC. What do i need to do to handle the 401 via trpc and be able to redirect the user to login "client side redirect at this point"
1 replies
TtRPC
Created by Mugetsu on 5/11/2023 in #❓-help
Stack trace in Client?
I've just found that in production I can see stack trace for TRPCErrors. Isin't it supposed to not show it? Checking on development build with isDev for initTRPC doesn't seem to work at all. I've set it to false but I can see still stack trace too. Also is it good idea to have loggerLink enabled in production?
6 replies
TtRPC
Created by Mugetsu on 5/11/2023 in #❓-help
Sequential batch mutation
How one would make a mutation as a sequential mutation batch call? Say I have to add 10 apples to the basket. My API allows only to add one at a time so I need to make 10 mutations one after another plus I would need to catch which one succeeded and which failed.
4 replies
TtRPC
Created by Mugetsu on 4/3/2023 in #❓-help
'req' of undefined in onError of express middleware
I've recently noticed that I get a bunch of errors regarding req object missing in ctx for onError property in trpc express middleware. Can't figure out how req obj might be undefined at the point of onerror handler??
app.use(
'/trpc',
trpcExpress.createExpressMiddleware({
router: appRouter,
createContext: createTRPCContext,
onError: ({ path: errorPath, error, ctx }) => {
if (isDevelopment) {
ctx.req.logger.debug(
`❌❌❌ tRPC failed on ${errorPath ?? '<no-path>'}: ${
error.message
}`,
)
}

if (error.code === 'INTERNAL_SERVER_ERROR') ctx.req.logger.error(error)
else ctx.req.logger.warn(error)
},
}),
)
app.use(
'/trpc',
trpcExpress.createExpressMiddleware({
router: appRouter,
createContext: createTRPCContext,
onError: ({ path: errorPath, error, ctx }) => {
if (isDevelopment) {
ctx.req.logger.debug(
`❌❌❌ tRPC failed on ${errorPath ?? '<no-path>'}: ${
error.message
}`,
)
}

if (error.code === 'INTERNAL_SERVER_ERROR') ctx.req.logger.error(error)
else ctx.req.logger.warn(error)
},
}),
)
Unhandled rejection, reason: TypeError: Cannot read property 'req' of undefined     at Object.onError (/home/app/server/index.js:135:90)     at onError (/home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/nodeHTTPRequestHandler-11f3df04.js:72:32)     at Object.resolveHTTPResponse (/home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/resolveHTTPResponse-94b380d2.js:187:18)     at /home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/nodeHTTPRequestHandler-11f3df04.js:63:50     at async /home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/adapters/express.js:16:9
}
Unhandled rejection, reason: TypeError: Cannot read property 'req' of undefined     at Object.onError (/home/app/server/index.js:135:90)     at onError (/home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/nodeHTTPRequestHandler-11f3df04.js:72:32)     at Object.resolveHTTPResponse (/home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/resolveHTTPResponse-94b380d2.js:187:18)     at /home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/nodeHTTPRequestHandler-11f3df04.js:63:50     at async /home/app/node_modules/.pnpm/@trpc+server@10.17.0/node_modules/@trpc/server/dist/adapters/express.js:16:9
}
15 replies
TtRPC
Created by Mugetsu on 3/24/2023 in #❓-help
Enigmatic INTERNAL SERVER ERROR
Im having a problem finding out about what is causing the INTERNAL SERVER ERROR from tRPC in my production. This is the error 👀
{
"level": "error",
"message": {
"cause": {},
"code": "INTERNAL_SERVER_ERROR",
"name": "TRPCError"
},
"timestamp": "2023-03-23T12:14:12.710Z",
"traceId": "...."
}
{
"level": "error",
"message": {
"cause": {},
"code": "INTERNAL_SERVER_ERROR",
"name": "TRPCError"
},
"timestamp": "2023-03-23T12:14:12.710Z",
"traceId": "...."
}
Are there any recommendation on how to handle onError handler for production ?? This is my current approach which seems to swallow some information. Can't really tell where it originates and how to possibly overcome it
trpcExpress.createExpressMiddleware({
router: appRouter,
createContext: createTRPCContext,
onError: ({ path: errorPath, error, ctx }) => {
const message = isDevelopment
? `❌❌❌ tRPC failed on ${errorPath ?? '<no-path>'}: ${
error.message
}`
: error

if (error.code === 'INTERNAL_SERVER_ERROR')
ctx.req.logger.error(message)
else ctx.req.logger.warn(message)
},
})
trpcExpress.createExpressMiddleware({
router: appRouter,
createContext: createTRPCContext,
onError: ({ path: errorPath, error, ctx }) => {
const message = isDevelopment
? `❌❌❌ tRPC failed on ${errorPath ?? '<no-path>'}: ${
error.message
}`
: error

if (error.code === 'INTERNAL_SERVER_ERROR')
ctx.req.logger.error(message)
else ctx.req.logger.warn(message)
},
})
3 replies
TtRPC
Created by Mugetsu on 3/23/2023 in #❓-help
Can I force metadata with the createCaller?
I have custom metadata which I check within the middleware. Im trying to write tests for this. Currently no single procedure is using the meta and I wonder If I can test this somehow. Can I add meta on the fly to the procedure with createCaller??
const t = initTRPC
.context<typeof createTRPCContext>()
.meta<MetaOptions>()
.create({
transformer: superjson,
errorFormatter({ shape, ctx }) {
const { data, ...rest } = shape

return {
...rest,
data: {
...data,
traceId: ctx?.req?.traceId,
schemaId: ctx?.req?.schemaId,
},
}
},
defaultMeta: { allowedRoles: [] },
})
const t = initTRPC
.context<typeof createTRPCContext>()
.meta<MetaOptions>()
.create({
transformer: superjson,
errorFormatter({ shape, ctx }) {
const { data, ...rest } = shape

return {
...rest,
data: {
...data,
traceId: ctx?.req?.traceId,
schemaId: ctx?.req?.schemaId,
},
}
},
defaultMeta: { allowedRoles: [] },
})
const enforceUserRoles = enforceUserIsAuthed.unstable_pipe(
({ ctx, meta, next }) => {
const currentRoles = ctx.req?.authorisation?.userSecurityGroups ?? []
const allowedRoles = meta?.allowedRoles ?? []

if (
allowedRoles.length &&
!allowedRoles.some((role) => currentRoles.includes(role))
) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'User role not allowed',
})
}

return next({ ctx })
},
)
const enforceUserRoles = enforceUserIsAuthed.unstable_pipe(
({ ctx, meta, next }) => {
const currentRoles = ctx.req?.authorisation?.userSecurityGroups ?? []
const allowedRoles = meta?.allowedRoles ?? []

if (
allowedRoles.length &&
!allowedRoles.some((role) => currentRoles.includes(role))
) {
throw new TRPCError({
code: 'FORBIDDEN',
message: 'User role not allowed',
})
}

return next({ ctx })
},
)
6 replies
TtRPC
Created by Mugetsu on 3/23/2023 in #❓-help
Vitest error test
How can I test for specific TRPCError code with vitest? I managed to get the message but can't figure out how to get the code.
it('should throw if user roles are missing', async ({ trpcContextMock }) => {
const ctx = await createInnerTRPCContext(
trpcContextMock({ authorisation: { userSecurityGroups: [] } }),
)
const caller = appRouter.createCaller(ctx)

await expect(caller.locations.searchLocations({})).rejects.toThrowError(
'User roles are missing',
)
})
it('should throw if user roles are missing', async ({ trpcContextMock }) => {
const ctx = await createInnerTRPCContext(
trpcContextMock({ authorisation: { userSecurityGroups: [] } }),
)
const caller = appRouter.createCaller(ctx)

await expect(caller.locations.searchLocations({})).rejects.toThrowError(
'User roles are missing',
)
})
3 replies
TtRPC
Created by Mugetsu on 3/21/2023 in #❓-help
How to enforce usequery as NonNullable
I have a custom hook with infinite query. I check for undefined at the app first render and then it is reused so I know by then, the type should not be undefined. I would like to be able to infer its type without undefined conditionally. Can I do that? How to if so??
export const useAppContext = (enabled = true) =>
api.appContext.getAppContext.useQuery(undefined, {
staleTime: Infinity,
enabled,
select: (data) => {
currentRoles = data?.authorisation?.roles ?? []
return data
},
})
export const useAppContext = (enabled = true) =>
api.appContext.getAppContext.useQuery(undefined, {
staleTime: Infinity,
enabled,
select: (data) => {
currentRoles = data?.authorisation?.roles ?? []
return data
},
})
8 replies
TtRPC
Created by Mugetsu on 3/21/2023 in #❓-help
Sharing Enum
On the backend: I have colocated files with types and procedures (index.ts - procedures, schemas.ts - zod/ts types). If I define an Enum on the backend in the schemas.ts and would like to use it on the client(frontend) does it meat that importing that enum would boundle this file in my client? With type I can just do import type { SomeType } from '../backend/schemas' and that would be stripped If I understand correctly. But what about enum?
4 replies
TtRPC
Created by Mugetsu on 3/20/2023 in #❓-help
Meta - unable to create default meta
19 replies
TtRPC
Created by Mugetsu on 3/10/2023 in #❓-help
application/octet-stream response
Im refactoring my old backend to trpc so far it was a pleasure and fairly straight forward process ❤️ Im amazed. Now I faced the issue with the files. I have one endpoint from external API that returns the data as application/octet-stream. (csv/xlsx) Can I handle this with trpc?? Probably I can't pass directly the response from the API throught trpc procedure and read it on client. I was thinking if I could parse response on backend (read it as text) and send text to client and download it? Would that work? Is it some kind of security? One problem could occur if the file would be big I guess??
4 replies
TtRPC
Created by Mugetsu on 3/10/2023 in #❓-help
Discriminated union handle
How Do I handle Input type if its discriminated union??
export const batchTasksRouter = createTRPCRouter({
requestExport: procedure
.input(
z.discriminatedUnion('type', [
z.object({
type: ExportsZSchema,
}),
z.object({
type: ExportsZSchema,
id: z.string().uuid(),
}),
z.object({
type: ExportsZSchema,
id: z.string().uuid(),
query: z.object({
from: z.string(),
to: z.string(),
type: z.string().optional(),
}),
}),
]),
)
.query(async ({ ctx, input }) => {
const { id, query, type } = input // <------- TYPESCRIPT ERRRORS that id and query not exists
let urlSuffix = type

if (id) urlSuffix += `/${id}`

if (query) urlSuffix += `?${querystring.stringify(query)}`
...
}),
})
export const batchTasksRouter = createTRPCRouter({
requestExport: procedure
.input(
z.discriminatedUnion('type', [
z.object({
type: ExportsZSchema,
}),
z.object({
type: ExportsZSchema,
id: z.string().uuid(),
}),
z.object({
type: ExportsZSchema,
id: z.string().uuid(),
query: z.object({
from: z.string(),
to: z.string(),
type: z.string().optional(),
}),
}),
]),
)
.query(async ({ ctx, input }) => {
const { id, query, type } = input // <------- TYPESCRIPT ERRRORS that id and query not exists
let urlSuffix = type

if (id) urlSuffix += `/${id}`

if (query) urlSuffix += `?${querystring.stringify(query)}`
...
}),
})
TS2339: Property 'id' does not exist on type ... TS2339: Property 'query' does not exist on type ...
5 replies
TtRPC
Created by Mugetsu on 3/6/2023 in #❓-help
Fetching different server url than defined in config
Is it possible to access the reactQuery instance and fetch different server url? I would like to use reactQuery to get data from my server which is at say /address-middleware instead of hitting /trpc/.... Can I do that? I dont want to setup separate reactquery provider or something just to call one separate endpoint
4 replies