Mugetsu
Mugetsu
Explore posts from servers
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
TTCTheo's Typesafe Cult
Created by Mugetsu on 10/8/2024 in #questions
React context in Server Component Page
I have a server component page where I want to provide a context having this file and adding it to the page it throws error?? What Im missing here
'use client';

import React, { createContext, useContext, useMemo, useState } from 'react';

import type { PropsWithChildren } from 'react';

export enum FormMode {
'VIEW' = 'VIEW',
'EDIT' = 'EDIT',
'CREATE' = 'CREATE',
}

type FormModeContextType = {
mode: FormMode;
setMode: (mode: FormMode) => void;
isViewMode: boolean;
isEditMode: boolean;
isCreateMode: boolean;
};

const FormModeContext = createContext<FormModeContextType | undefined>(
undefined,
);

export const useFormMode = (): FormModeContextType => {
const context = useContext(FormModeContext);

if (!context) {
throw new Error('useFormMode must be used within a FormModeProvider');
}

return context;
};

export const FormModeProvider = ({
children,
initialFormMode,
}: PropsWithChildren<{ initialFormMode: FormMode }>) => {
const [mode, setMode] = useState<FormMode>(() => initialFormMode);

// Memoize the context value to avoid unnecessary re-renders
const value = useMemo(
() => ({
mode,
setMode,
isViewMode: mode === FormMode.VIEW,
isEditMode: mode === FormMode.EDIT,
isCreateMode: mode === FormMode.CREATE,
}),
[mode],
);

return (
<FormModeContext.Provider value={value}>
{children}
</FormModeContext.Provider>
);
};
'use client';

import React, { createContext, useContext, useMemo, useState } from 'react';

import type { PropsWithChildren } from 'react';

export enum FormMode {
'VIEW' = 'VIEW',
'EDIT' = 'EDIT',
'CREATE' = 'CREATE',
}

type FormModeContextType = {
mode: FormMode;
setMode: (mode: FormMode) => void;
isViewMode: boolean;
isEditMode: boolean;
isCreateMode: boolean;
};

const FormModeContext = createContext<FormModeContextType | undefined>(
undefined,
);

export const useFormMode = (): FormModeContextType => {
const context = useContext(FormModeContext);

if (!context) {
throw new Error('useFormMode must be used within a FormModeProvider');
}

return context;
};

export const FormModeProvider = ({
children,
initialFormMode,
}: PropsWithChildren<{ initialFormMode: FormMode }>) => {
const [mode, setMode] = useState<FormMode>(() => initialFormMode);

// Memoize the context value to avoid unnecessary re-renders
const value = useMemo(
() => ({
mode,
setMode,
isViewMode: mode === FormMode.VIEW,
isEditMode: mode === FormMode.EDIT,
isCreateMode: mode === FormMode.CREATE,
}),
[mode],
);

return (
<FormModeContext.Provider value={value}>
{children}
</FormModeContext.Provider>
);
};
Page RSC
export default async function LocationView({
params,
}: {
params: { locationId: string };
}) {
...

return (
<FormModeProvider initialFormMode={FormMode.VIEW}>
<LocationForm defaultFormValues={defaultFormValues} />
</FormModeProvider>
);
}
export default async function LocationView({
params,
}: {
params: { locationId: string };
}) {
...

return (
<FormModeProvider initialFormMode={FormMode.VIEW}>
<LocationForm defaultFormValues={defaultFormValues} />
</FormModeProvider>
);
}
Its telling me to use it withing parent that is client component? Why to? why I cant directly use it in RSC
3 replies
TTCTheo's Typesafe Cult
Created by Mugetsu on 10/4/2024 in #questions
nextjs refresh, trpc invalidate order
Does anyone know if order does matter and has any implication in case of trpc invalidation and router.refresh ?
await apiUtils.invalidate();
router.push('/location');
router.refresh();
await apiUtils.invalidate();
router.push('/location');
router.refresh();
vs
router.push('/location');
router.refresh();
await apiUtils.invalidate();
router.push('/location');
router.refresh();
await apiUtils.invalidate();
From what I see in the network tab: 1st case network calls: 1. trpc.query 2. rsc fetch 3. rsc fetch 4. layout fetch 5. page fetch 2nd case network calls: 1. rsc fetch 2. trpc.query 3. rsc fetch 4. layout fetch 5. page fetch
2 replies
TTCTheo's Typesafe Cult
Created by Mugetsu on 10/2/2024 in #questions
tRPC mutation onError handler error type for shared logic
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
5 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
TTCTheo's Typesafe Cult
Created by Mugetsu on 9/21/2024 in #questions
Transform/map source object to target object with schema/map
I am looking for a robust way of mapping API fields to UI fields in a sense of something like DTOs but that doesn’t require much boilerplate code.
I have found what I think a great library that would fit my need but unfortunately it’s not longer maintained https://www.npmjs.com/package/morphism so can’t use it as is in newer projects so I wonder about making a copy of it or trying to do something similar as I can’t find anything else with such functionalities. I wonder if something similar could be achieved with use of zod and its transform option. Anyone aware of similar libs? Would zod fit in this case? The thing with zod that bother me is after failed parsing I only get the error and the part of successfully parsed input is lost. Or does zod support returning error alongside already parsed data?
2 replies
TTCTheo's Typesafe Cult
Created by Mugetsu on 9/9/2024 in #questions
New Zod schema based on an existing one, with some fields having different validation rules?
Having some schema how I could re-use the schema and access some of its fields and change their validation to make up new schema.
const schema = z.object({
details: z.object({
name: z.string(),
street: z.string(),
coordinates: z.object({
lat: z.string(),
long: z.string(),
}),
}).partial().optional(),
id: z.string().uuid(),
})
const schema = z.object({
details: z.object({
name: z.string(),
street: z.string(),
coordinates: z.object({
lat: z.string(),
long: z.string(),
}),
}).partial().optional(),
id: z.string().uuid(),
})
Given this schema I would like the name field to be required/min length and have a new schema from this initial one with the rest fields being as is. In the inital schema only id would be required with the sub-schema id and name should be required rest optional
5 replies
TTCTheo's Typesafe Cult
Created by Mugetsu on 9/3/2024 in #questions
typescript object util library
Is there any good typescript library for object manipulation? Ie changing values of deeply nested objects, getting values via dot notation keys. Setting via dot notation etc
9 replies
TTCTheo's Typesafe Cult
Created by Mugetsu on 9/3/2024 in #questions
Use Zod schema after parse & transform as a new schema
I am looking how I could handle from validation with one schema so it can be used on the ReactHookForm zod resolver and tRPC input validation. lets say my form accepts two values
const schema = z.object({
name: z.string(),
bir: z.object({
value: z.string(),
label: z.string(),
}).transform(val => val.value),

})

type SchemaT = z.infer<typeof asd>

const asdzz: SchemaT = {
bir: 'asd',name: 'as'
}
const schema = z.object({
name: z.string(),
bir: z.object({
value: z.string(),
label: z.string(),
}).transform(val => val.value),

})

type SchemaT = z.infer<typeof asd>

const asdzz: SchemaT = {
bir: 'asd',name: 'as'
}
schema is a parser for client form input which I would like to use resulting type of that schema as a new schema for tRPC input validation. Can I do this somehow? Or my bet is just don't do the transform and use that schema both on client and backend and on backend extract needed fields and do further processing. This will be simpler but unnecessary bloat of data payload would be then sent to the server
7 replies
TTCTheo's Typesafe Cult
Created by Mugetsu on 8/30/2024 in #questions
BUILD TypeCheck and Lint disabled
I’ve seen Theo disabling typecheck and eslint during the build process, which greatly improves the build time. How can I apply these steps in a separate workflow? What are the equivalent npm commands for running typecheck and eslint independently as they run during the build? Will these separate commands execute the same steps as during the build process, or are there any differences?
3 replies
TTCTheo's Typesafe Cult
Created by Mugetsu on 8/28/2024 in #questions
Use ReactQuery to fetch different api resource than trpc resource
I have an api endpoint which I can call using fetch. How one would re-use reactquery and call that endpoint directly. Do I need separate reactquery client?
5 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/@[email protected]/node_modules/@trpc/server/dist/adapters/fetch/index.mjs:94:24)\n at resolveHTTPResponse (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]/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/@[email protected]/node_modules/@trpc/server/dist/adapters/fetch/index.mjs:94:24)\n at resolveHTTPResponse (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]/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/@[email protected]/node_modules/@trpc/server/dist/adapters/fetch/index.mjs:94:24)\n at resolveHTTPResponse (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]/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/@[email protected]/node_modules/@trpc/server/dist/adapters/fetch/index.mjs:94:24)\n at resolveHTTPResponse (webpack-internal:///(rsc)/./node_modules/.pnpm/@[email protected]/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
TTCTheo's Typesafe Cult
Created by Mugetsu on 1/9/2024 in #questions
Logging trpc errors in production
What is the proper place for logging errors in production originating from trpc when I use NEXT 14 app router trpc setup. Should I log from nextjs trpc api route handler?
const createContext = async (req: NextRequest) => {
return createTRPCContext({
headers: req.headers,
});
};

const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => createContext(req),
onError: ({ path, error }) => {
if (serverEnv.NODE_ENV === 'development') {
trpcLogger.error(
`❌ tRPC failed on ${path ?? '<no-path>'}: ${error.message}`,
);
}
},
});

export { handler as GET, handler as POST };
const createContext = async (req: NextRequest) => {
return createTRPCContext({
headers: req.headers,
});
};

const handler = (req: NextRequest) =>
fetchRequestHandler({
endpoint: '/api/trpc',
req,
router: appRouter,
createContext: () => createContext(req),
onError: ({ path, error }) => {
if (serverEnv.NODE_ENV === 'development') {
trpcLogger.error(
`❌ tRPC failed on ${path ?? '<no-path>'}: ${error.message}`,
);
}
},
});

export { handler as GET, handler as POST };
Or maybe from server trpcproxyclient handler??
export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === 'development' ||
(op.direction === 'down' && op.result instanceof Error),
}),
/**
* Custom RSC link that lets us invoke procedures without using http requests. Since Server
* Components always run on the server, we can just call the procedure as a function.
*/
() =>
({ op }) =>
observable((observer) => {
createContext()
.then((ctx) => {
return callProcedure({
procedures: appRouter._def.procedures,
path: op.path,
rawInput: op.input,
ctx,
type: op.type,
});
})
.then((data) => {
observer.next({ result: { data } });
observer.complete();
})
.catch((cause: TRPCErrorResponse) => {
observer.error(TRPCClientError.from(cause));
});
}),
],
});
export const api = createTRPCProxyClient<AppRouter>({
transformer,
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === 'development' ||
(op.direction === 'down' && op.result instanceof Error),
}),
/**
* Custom RSC link that lets us invoke procedures without using http requests. Since Server
* Components always run on the server, we can just call the procedure as a function.
*/
() =>
({ op }) =>
observable((observer) => {
createContext()
.then((ctx) => {
return callProcedure({
procedures: appRouter._def.procedures,
path: op.path,
rawInput: op.input,
ctx,
type: op.type,
});
})
.then((data) => {
observer.next({ result: { data } });
observer.complete();
})
.catch((cause: TRPCErrorResponse) => {
observer.error(TRPCClientError.from(cause));
});
}),
],
});
In the loggerlink? or catch of the observable?? Im but confused where errors originate from
4 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
TTCTheo's Typesafe Cult
Created by Mugetsu on 11/30/2023 in #questions
TRPC Server error handling?
So I've been refactoring my old project with T3 +app router that @julius recently added to T3 stack and I was just fiddling around trpc and its context I stumbled upon a problem. I have a trpc middleware that checks for authed user enforceUserIsAuthed
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.sessionIdToken) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}

return next({
ctx: {
...
},
});
});
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
if (!ctx.sessionIdToken) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}

return next({
ctx: {
...
},
});
});
And there is a problem. When I use the client trpc calls (react) I have a onError props for queryclient that I can check for that error and react (especially for 401 and redirect). But when it throws in the RSC this triggers error boundaries and throws uncaught TRPCClienterror. How am I supposed to handle server errors in this case? createTRPCProxyClient doesnt seem to have any onerror handler.
6 replies
TTCTheo's Typesafe Cult
Created by Mugetsu on 11/7/2023 in #questions
CSP with NextJs 14 and express
Hello, I am having a problem with CSP when using NExtjs with custom express server. I use app router and wanted to add CSP headers using helmet for express. As soon as I add the helmet middleware I see that CSP is blocking all my scripts and can't figure it out how to tackle this. Has someone such setup or any tips how do I work it out? All I can find online is for pages dir...
1 replies
TTCTheo's Typesafe Cult
Created by Mugetsu on 10/15/2023 in #questions
trpc nextjs and expressjs middleware
I've custom expressjs server with Nextjs + im using internal package for handling OneLogin (OIDC) hooked as express middleware which runs. before the Nextjs handler for custom server. 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 then trpc throws 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