diff --git a/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts b/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts index 6d1689238..5825c3a5a 100644 --- a/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts +++ b/packages/plugin-rsc/examples/use-cache-callable/callable-cache-plugin.ts @@ -60,7 +60,9 @@ export function callableCachePlugin(): Plugin { runtime: (value, name, meta) => runtime(value, name, getCacheWrapperOptions(meta)), encode: (value) => `$$encryptCacheCaptures(${value})`, - decode: (value) => `await $$decryptCacheCaptures(${value})`, + // The cache runtime replaces the envelope with decoded captures + // before invoking this private implementation. + decode: (value) => value, }) if (!result.output.hasChanged()) { manager.serverReferences.deleteClaim(pluginName, id) @@ -72,7 +74,7 @@ export function callableCachePlugin(): Plugin { exportNames: 'names' in result ? result.names : result.exportNames, }) result.output.prepend( - `import $$cacheWrapper, { encryptCacheCaptures as $$encryptCacheCaptures, decryptCacheCaptures as $$decryptCacheCaptures } from "/src/framework/use-cache-runtime";\n` + + `import $$cacheWrapper, { encryptCacheCaptures as $$encryptCacheCaptures } from "/src/framework/use-cache-runtime";\n` + `import * as $$ReactServer from "@vitejs/plugin-rsc/react/rsc/server";\n`, ) return { diff --git a/packages/plugin-rsc/examples/use-cache-callable/src/framework/use-cache-runtime.tsx b/packages/plugin-rsc/examples/use-cache-callable/src/framework/use-cache-runtime.tsx index 001f136cd..70a929062 100644 --- a/packages/plugin-rsc/examples/use-cache-callable/src/framework/use-cache-runtime.tsx +++ b/packages/plugin-rsc/examples/use-cache-callable/src/framework/use-cache-runtime.tsx @@ -64,24 +64,18 @@ export default function cacheWrapper( // "use cache static shell + dynamic children props" pattern. // cf. https://nextjs.org/docs/app/api-reference/directives/use-cache#non-serializable-arguments const clientTemporaryReferences = createClientTemporaryReferenceSet() - const encodedArguments = await encodeReply(admittedArgs, { - temporaryReferences: clientTemporaryReferences, - }) - let encodedCacheArguments = encodedArguments - // Re-encode decrypted captures so cache identity reflects their logical values - // rather than the randomized ciphertext used by the transport arguments. + let executionArguments = admittedArgs if (captureEnvelope) { - // TODO: On a cache miss, the hoister-generated implementation decrypts the - // original envelope again. A tighter adapter could reuse these captures. - const cacheArguments = [ - ...(await decryptCacheCaptures(captureEnvelope)), - ...admittedArgs.slice(1), - ] - encodedCacheArguments = await encodeReply(cacheArguments, { - temporaryReferences: createClientTemporaryReferenceSet(), - }) + // Decrypt in the framework runtime so cache identity and execution share + // these values; the transformed implementation only destructures the array. + const captures = await decryptCacheCaptures(captureEnvelope) + const invocationArguments = admittedArgs.slice(1) + executionArguments = [captures, ...invocationArguments] } - const serializedCacheKey = await replyToCacheKey(encodedCacheArguments) + const encodedArguments = await encodeReply(executionArguments, { + temporaryReferences: clientTemporaryReferences, + }) + const serializedCacheKey = await replyToCacheKey(encodedArguments) // cache `fn` result as stream // (cache value is promise so that it dedupes concurrent async calls) @@ -180,7 +174,7 @@ export function encryptCacheCaptures( } } -export async function decryptCacheCaptures( +async function decryptCacheCaptures( envelope: CacheCaptureEnvelope, ): Promise { const { encrypted } = envelope