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(next/app-dir): contextCache to control context values to add to cacheTag #5458

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 2 additions & 4 deletions examples/.experimental/next-app-dir/src/trpc/client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
'use client';

import { loggerLink } from '@trpc/client';
import { httpBatchLink, loggerLink } from '@trpc/client';
import {
experimental_createActionHook,
experimental_createTRPCNextAppDirClient,
experimental_serverActionLink,
} from '@trpc/next/app-dir/client';
import { experimental_nextHttpLink } from '@trpc/next/app-dir/links/nextHttp';
import type { AppRouter } from '~/server/routers/_app';
import superjson from 'superjson';
import { getUrl } from './shared';
Expand All @@ -18,9 +17,8 @@ export const api = experimental_createTRPCNextAppDirClient<AppRouter>({
loggerLink({
enabled: (op) => true,
}),
experimental_nextHttpLink({
httpBatchLink({
transformer: superjson,
batch: true,
url: getUrl(),
headers() {
return {
Expand Down
10 changes: 5 additions & 5 deletions examples/.experimental/next-app-dir/src/trpc/server-http.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { loggerLink } from '@trpc/client';
import { experimental_nextHttpLink } from '@trpc/next/app-dir/links/nextHttp';
import { httpBatchLink, loggerLink } from '@trpc/client';
import { experimental_createTRPCNextAppDirServer } from '@trpc/next/app-dir/server';
import type { AppRouter } from '~/server/routers/_app';
import { cookies } from 'next/headers';
import superjson from 'superjson';
import { getUrl } from './shared';
import { createContext } from './shared-server';

export const api = experimental_createTRPCNextAppDirServer<AppRouter>({
config() {
return {
createContext,
links: [
loggerLink({
enabled: (op) => true,
enabled: (_op) => true,
}),
experimental_nextHttpLink({
batch: true,
httpBatchLink({
url: getUrl(),
transformer: superjson,
headers() {
Expand Down
15 changes: 4 additions & 11 deletions examples/.experimental/next-app-dir/src/trpc/server-invoker.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { loggerLink } from '@trpc/client';
import { experimental_nextCacheLink } from '@trpc/next/app-dir/links/nextCache';
import { experimental_createTRPCNextAppDirServer } from '@trpc/next/app-dir/server';
import { auth } from '~/auth';
import { appRouter } from '~/server/routers/_app';
import { cookies } from 'next/headers';
import superjson from 'superjson';
import { createContext } from './shared-server';

/**
* This client invokes procedures directly on the server without fetching over HTTP.
*/
export const api = experimental_createTRPCNextAppDirServer<typeof appRouter>({
config() {
return {
createContext,
links: [
loggerLink({
enabled: (op) => true,
Expand All @@ -21,15 +21,8 @@ export const api = experimental_createTRPCNextAppDirServer<typeof appRouter>({
revalidate: 5,
router: appRouter,
transformer: superjson,
createContext: async () => {
return {
session: await auth(),
headers: {
cookie: cookies().toString(),
'x-trpc-source': 'rsc-invoke',
},
};
},
// include the user id in the cache key
cacheContext: (ctx) => [ctx.session?.user.id],
}),
],
};
Expand Down
12 changes: 12 additions & 0 deletions examples/.experimental/next-app-dir/src/trpc/shared-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { auth } from '~/auth';
import { cookies } from 'next/headers';

export async function createContext() {
return {
session: await auth(),
headers: {
cookie: cookies().toString(),
'x-trpc-source': 'rsc-invoke',
},
};
}
18 changes: 12 additions & 6 deletions packages/client/src/internals/TRPCUntypedClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { observableToPromise, share } from '@trpc/server/observable';
import type {
AnyRouter,
InferrableClientTypes,
inferRouterContext,
TypeError,
} from '@trpc/server/unstable-core-do-not-import';
import { createChain } from '../links/internals/createChain';
Expand Down Expand Up @@ -35,10 +36,15 @@ export interface TRPCSubscriptionObserver<TValue, TError> {
}

/** @internal */
export type CreateTRPCClientOptions<TRouter extends InferrableClientTypes> = {
links: TRPCLink<TRouter>[];
transformer?: TypeError<'The transformer property has moved to httpLink/httpBatchLink/wsLink'>;
};
export type CreateTRPCClientOptions<TInferrable extends InferrableClientTypes> =
{
links: TRPCLink<TInferrable>[];
transformer?: TypeError<'The transformer property has moved to httpLink/httpBatchLink/wsLink'>;
} & (TInferrable extends AnyRouter
? {
createContext?: () => Promise<inferRouterContext<TInferrable>>;
}
: {});

/** @internal */
export type UntypedClientProperties =
Expand All @@ -53,13 +59,13 @@ export type UntypedClientProperties =

export class TRPCUntypedClient<TRouter extends AnyRouter> {
private readonly links: OperationLink<AnyRouter>[];
public readonly runtime: TRPCClientRuntime;
public readonly runtime: TRPCClientRuntime<TRouter>;
private requestId: number;

constructor(opts: CreateTRPCClientOptions<TRouter>) {
this.requestId = 0;

this.runtime = {};
this.runtime = { createContext: opts.createContext } as any;

// Initialize the links
this.links = opts.links.map((link) => link(this.runtime));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { getUrl, resolveHTTPLinkOptions } from './httpUtils';
*/
export type RequesterFn<TOptions extends HTTPBatchLinkOptions<AnyRootTypes>> = (
requesterOpts: ResolvedHTTPLinkOptions & {
runtime: TRPCClientRuntime;
runtime: TRPCClientRuntime<AnyRouter>;
type: ProcedureType;
opts: TOptions;
},
Expand Down
14 changes: 10 additions & 4 deletions packages/client/src/links/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { Observable, Observer } from '@trpc/server/observable';
import type {
AnyRouter,
InferrableClientTypes,
inferRouterContext,
TRPCResultMessage,
TRPCSuccessResponse,
} from '@trpc/server/unstable-core-do-not-import';
Expand Down Expand Up @@ -56,9 +58,13 @@ export type TRPCFetch = (
options?: RequestInit,
) => Promise<ResponseEsque>;

export interface TRPCClientRuntime {
// nothing here anymore
}
export type TRPCClientRuntime<
TInferrable extends InferrableClientTypes | never = never,
> = TInferrable extends AnyRouter
? {
createContext?: () => Promise<inferRouterContext<TInferrable>>;
}
: {};

/**
* @internal
Expand Down Expand Up @@ -104,5 +110,5 @@ export type OperationLink<
* @public
*/
export type TRPCLink<TInferrable extends InferrableClientTypes> = (
opts: TRPCClientRuntime,
opts: TRPCClientRuntime<TInferrable>,
) => OperationLink<TInferrable>;
6 changes: 0 additions & 6 deletions packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@
"require": "./dist/app-dir/links/nextCache.js",
"default": "./dist/app-dir/links/nextCache.js"
},
"./app-dir/links/nextHttp": {
"import": "./dist/app-dir/links/nextHttp.mjs",
"require": "./dist/app-dir/links/nextHttp.js",
"default": "./dist/app-dir/links/nextHttp.js"
},
"./app-dir/server": {
"import": "./dist/app-dir/server.mjs",
"require": "./dist/app-dir/server.js",
Expand All @@ -71,7 +66,6 @@
"ssrPrepass",
"!**/*.test.*"
],
"dependencies": {},
"peerDependencies": {
"@tanstack/react-query": "^5.0.0",
"@trpc/client": "10.45.1",
Expand Down
1 change: 0 additions & 1 deletion packages/next/rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export const input = [
'src/app-dir/server.ts',
'src/app-dir/client.ts',
'src/app-dir/links/nextCache.ts',
'src/app-dir/links/nextHttp.ts',
'src/ssrPrepass.ts',
];

Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/app-dir/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type QueryResult = {
// ts-prune-ignore-next
export function experimental_createTRPCNextAppDirClient<
TRouter extends AnyRouter,
>(opts: CreateTRPCNextAppRouterOptions<TRouter>) {
>(opts: Omit<CreateTRPCNextAppRouterOptions<TRouter>, 'createContext'>) {
const client = createTRPCUntypedClient<TRouter>(opts.config());
// const useProxy = createUseProxy<TRouter>(client);

Expand Down
26 changes: 20 additions & 6 deletions packages/next/src/app-dir/links/nextCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ import { generateCacheTag } from '../shared';

type NextCacheLinkOptions<TRouter extends AnyRouter> = {
router: TRouter;
createContext: () => Promise<inferRouterContext<TRouter>>;
/**
* define which values from the context should be considered into the cache
* key
*/
cacheContext: ((ctx: inferRouterContext<TRouter>) => any[]) | undefined;
/** how many seconds the cache should hold before revalidating */
revalidate?: number | false;
} & TransformerOptions<inferClientTypes<TRouter>>;
Expand All @@ -28,12 +32,16 @@ export function experimental_nextCacheLink<TRouter extends AnyRouter>(
opts: NextCacheLinkOptions<TRouter>,
): TRPCLink<TRouter> {
const transformer = getTransformer(opts.transformer);
return () =>
({ op }) =>
return ({ createContext }) => {
if (!createContext)
throw new Error(
'`createContext` is required to be passed to use `experimental_nextCacheLink`.',
);

return ({ op }) =>
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' ||
Expand All @@ -42,9 +50,14 @@ export function experimental_nextCacheLink<TRouter extends AnyRouter>(
: undefined;
const revalidate = requestRevalidate ?? opts.revalidate ?? false;

const promise = opts
.createContext()
const promise = createContext()
.then(async (ctx) => {
const cacheTag = await generateCacheTag(
path,
input,
opts.cacheContext?.(ctx),
);

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 Expand Up @@ -84,4 +97,5 @@ export function experimental_nextCacheLink<TRouter extends AnyRouter>(
observer.error(TRPCClientError.from(cause));
});
});
};
}
74 changes: 0 additions & 74 deletions packages/next/src/app-dir/links/nextHttp.ts

This file was deleted.

9 changes: 7 additions & 2 deletions packages/next/src/app-dir/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export function experimental_createTRPCNextAppDirServer<
return createTRPCUntypedClient(config);
});

return createRecursiveProxy((callOpts) => {
return createRecursiveProxy(async (callOpts) => {
// lazily initialize client
const client = getClient();

Expand All @@ -50,7 +50,12 @@ export function experimental_createTRPCNextAppDirServer<
const action = pathCopy.pop()!;
const procedurePath = pathCopy.join('.');
const procedureType = clientCallTypeToProcedureType(action);
const cacheTag = generateCacheTag(procedurePath, callOpts.args[0]);

const cacheTag = await generateCacheTag(
procedurePath,
callOpts.args[0],
await client.runtime.createContext?.(),
);

if (action === 'revalidate') {
revalidateTag(cacheTag);
Expand Down