Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: better cacheTag control in next app dir layouts #5455

Open
1 task done
dalechyn opened this issue Feb 6, 2024 · 0 comments
Open
1 task done

feat: better cacheTag control in next app dir layouts #5455

dalechyn opened this issue Feb 6, 2024 · 0 comments

Comments

@dalechyn
Copy link

dalechyn commented Feb 6, 2024

Describe the feature you'd like to request

TLDR

ctx in queries is often being used as inputs when some are defined in the middlewares.

This is not taken in account with experimental app dir support, as the cacheTag only caches the inputs, but never the context.

Therefore, I am suffering a bug when a query that just uses ctx to retrieve user-specific data from ctx.session, is being cached with no inputs [], and I am able to see the cached data of another user.

Introduction

Experimental tRPC's NextJS AppRouter layout support involves caching of query inputs, and is implemented in the next way:

const { path, input, type, context } = op;
const cacheTag = generateCacheTag(path, input);
// Let per-request revalidate override global revalidate
const requestRevalidate =
typeof context['revalidate'] === 'number' ||
context['revalidate'] === false
? context['revalidate']
: undefined;
const revalidate = requestRevalidate ?? opts.revalidate ?? false;

Problem

While it works for most of the cases, problems arise when your query logic depends on trpc's middlewares.

Here's an example implementation of a protected procedure taken from the tRPC's documentation:

import { initTRPC, TRPCError } from '@trpc/server';

export const t = initTRPC.context<Context>().create();

// you can reuse this for any procedure
export const protectedProcedure = t.procedure.use(async function isAuthed(
  opts,
) {
  const { ctx } = opts;
  // `ctx.user` is nullable
  if (!ctx.user) {
    //     ^?
    throw new TRPCError({ code: 'UNAUTHORIZED' });
  }

  return opts.next({
    ctx: {
      // ✅ user value is known to be non-null now
      user: ctx.user,
      // ^?
    },
  });
});

t.router({
  // this is accessible for everyone
  hello: t.procedure
    .input(z.string().nullish())
    .query((opts) => `hello ${opts.input ?? opts.ctx.user?.name ?? 'world'}`),
  admin: t.router({
    // this is accessible only to admins
    secret: protectedProcedure.query((opts) => {
      return {
        secret: 'sauce',
      };
    }),
  }),
});

As you can see, the ctx.user?.name in this example is implicitly becoming an input, because you use it in the query implementation, yet it's not being included in the cacheKey!

In this particular case, which is taken from the docs, the consequence is that the first user querying the hello query, would make it cache the response hello alice (i.e), then when the second user queries the same procedure (bob i.e.), he would see hello alice as it was cached with a cacheKey of [] as inputs to the functions simply don't exist.

Describe the solution you'd like to see

contextCache or similar to be implemented in the options of experimental_nextCacheLink to specify which fields from the context should be cached, to still make use of caching.
This would also involve changing the way the cacheTag is generated. Specifically, it would need to have the cachedContextValues (or similar) as input, to pass the values that should be cached.
Also, due to the nature of NextJS caching, the cacheTag should not be longer than 256 characters, therefore the cacheTag should rely on a hash of all inputs, instead of passing the stringified data.

A hacky patch is implemented at #5447

Describe alternate solutions

noCache procedure middleware to opt out from caching, which then would be used in protectedProcedure's by default, as a developer might depend on ctx data.
@KATT, https://discord.com/channels/867764511159091230/1204483271342301264/1204483291701317732

Additional information

Discussions ref: #5447

👨‍👧‍👦 Contributing

  • 🙋‍♂️ Yes, I'd be down to file a PR implementing this feature!

Funding

  • You can sponsor this specific effort via a Polar.sh pledge below
  • We receive the pledge once the issue is completed & verified
Fund with Polar
dalechyn added a commit to dalechyn/trpc that referenced this issue Feb 7, 2024
This change allows a user to specify `contextCache` method in the
`experimental_nextCacheLink`, which lets one select the values
from the context that should be considered when a `cacheTag` is
generated.

It also changes the way `cacheTag` is generated, taking care of
the fact that the `cacheTag` cannot be longer then 256 characters,
thus instead of stringifying input values, it will form a sha-256 hash
of those.

Implements trpc#5455
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant