Passing context (db etc) to a 'use cache' fn
Unanswered
Siberian posted this in #help-forum
SiberianOP
I'm trying to upgrade a codebase to use cache components. A common pattern in the code is to pass around a ctx argument with db client (regular/transaction), logger etc. These can not be serialised and thus can't be part of the arguments passed to a cache component. I tried to solve this by splitting it into a separate function but still run into this error
Is the only way around this to use async local storage?
let count = 0;
class SomeClass {}
const wrapper = <T extends (...args: any[]) => Promise<any>>(
fn: (ctx: { someClass: SomeClass }) => T,
): T => fn({ someClass: new SomeClass() });
const getCount = wrapper((ctx) =>
// ^
// ⨯ Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
async () => {
"use cache";
console.log(ctx.someClass);
count++;
return count;
},
);
export default async function CacheCompTestPage() {
const localCount = await getCount();
return (
<div>
<p>Count: {localCount}</p>
</div>
);
}Is the only way around this to use async local storage?
5 Replies
@Siberian I'm trying to upgrade a codebase to use cache components. A common pattern in the code is to pass around a ctx argument with db client (regular/transaction), logger etc. These can not be serialised and thus can't be part of the arguments passed to a cache component. I tried to solve this by splitting it into a separate function but still run into this error
ts
let count = 0;
class SomeClass {}
const wrapper = <T extends (...args: any[]) => Promise<any>>(
fn: (ctx: { someClass: SomeClass }) => T,
): T => fn({ someClass: new SomeClass() });
const getCount = wrapper((ctx) =>
// ^
// ⨯ Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.
async () => {
"use cache";
console.log(ctx.someClass);
count++;
return count;
},
);
export default async function CacheCompTestPage() {
const localCount = await getCount();
return (
<div>
<p>Count: {localCount}</p>
</div>
);
}
Is the only way around this to use async local storage?
you should ideally only cache the data fetching part and do post-processing outside of the use cache like filtering, etc etc.
i wouldn't recommend using async local storage since it would stain the purity of the cache.
what are the cases where you needed to access the ctx inside a data fetching method?
logger should be done outside the cached function not inside
i wouldn't recommend using async local storage since it would stain the purity of the cache.
what are the cases where you needed to access the ctx inside a data fetching method?
logger should be done outside the cached function not inside
@alfonsüs ardani you should ideally only cache the data fetching part and do post-processing outside of the use cache like filtering, etc etc.
i wouldn't recommend using async local storage since it would stain the purity of the cache.
what are the cases where you needed to access the ctx inside a data fetching method?
logger should be done outside the cached function not inside
SiberianOP
Fair on the logging, but the entire codebase is set up with “crude DI” with passing deps/services as args, including DB. Both for easier testing but also managing of transaction contexts etc. So a getUserById looks like (forgive formatting, on mobile)
const getUserById = dbRequest({ Request: UserId, Result: User, execute: (db) => db.select(….
const getUserById = dbRequest({ Request: UserId, Result: User, execute: (db) => db.select(….
I feel like this is not atypical pattern at all, especially for lather/more mature codebases? Which this isnt tbf haha
But yes I get the issues this can cause, but right now this seems unsolveable 🤷♂️
A symbol.for(‘next_dangerous_cache_identifier’) or smth that you could use to make services part of the cache key would be great