Skip to content

Commit

Permalink
feat(next/app-dir): cacheTag generation to consider context
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dalechyn committed Feb 7, 2024
1 parent b72c7aa commit 6f32a91
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 24 deletions.
6 changes: 4 additions & 2 deletions packages/next/package.json
Expand Up @@ -71,7 +71,6 @@
"ssrPrepass",
"!**/*.test.*"
],
"dependencies": {},
"peerDependencies": {
"@tanstack/react-query": "^5.0.0",
"@trpc/client": "10.45.0",
Expand Down Expand Up @@ -112,5 +111,8 @@
},
"funding": [
"https://trpc.io/sponsor"
]
],
"dependencies": {
"@noble/hashes": "^1.3.3"
}
}
23 changes: 14 additions & 9 deletions packages/next/src/app-dir/links/nextCache.ts
Expand Up @@ -18,6 +18,11 @@ import { generateCacheTag } from '../shared';

type NextCacheLinkOptions<TRouter extends AnyRouter> = {
router: TRouter;
/**
* define which values from the context should be considered into the cache
* key
*/
cacheContext?: (ctx: inferRouterContext<TRouter>) => any[];
createContext: () => Promise<inferRouterContext<TRouter>>;
/** how many seconds the cache should hold before revalidating */
revalidate?: number | false;
Expand All @@ -33,18 +38,18 @@ export function experimental_nextCacheLink<TRouter extends AnyRouter>(
observable((observer) => {
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;

const promise = opts
.createContext()
.then(async (ctx) => {
const cacheTag = generateCacheTag(path, input, opts.cacheContext?.(ctx));
// 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;

const callProc = async (_cachebuster: string) => {
// // _cachebuster is not used by us but to make sure
// // that calls with different tags are properly separated
Expand Down
7 changes: 3 additions & 4 deletions packages/next/src/app-dir/shared.ts
Expand Up @@ -14,6 +14,7 @@ import type {
RouterRecord,
} from '@trpc/server/unstable-core-do-not-import';
import { createRecursiveProxy } from '@trpc/server/unstable-core-do-not-import';
import { sha256 } from '@noble/hashes/sha256'

/**
* @internal
Expand Down Expand Up @@ -86,10 +87,8 @@ export interface CreateTRPCNextAppRouterOptions<TRouter extends AnyRouter> {
/**
* @internal
*/
export function generateCacheTag(procedurePath: string, input: any) {
return input
? `${procedurePath}?input=${JSON.stringify(input)}`
: procedurePath;
export function generateCacheTag(procedurePath: string, input: any, context?: any) {
return `${procedurePath}?hash=${sha256(JSON.stringify({input, context}))}`
}

export function isFormData(value: unknown): value is FormData {
Expand Down
27 changes: 18 additions & 9 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 6f32a91

Please sign in to comment.