11import type { ComposableCacheEntry , ComposableCacheHandler } from "types/cache" ;
2- import type { CacheValue } from "types/overrides" ;
32import { writeTags } from "utils/cache" ;
43import { fromReadableStream , toReadableStream } from "utils/stream" ;
54import { debug } from "./logger" ;
65
7- const pendingWritePromiseMap = new Map <
8- string ,
9- Promise < CacheValue < "composable" > >
10- > ( ) ;
6+ const pendingWritePromiseMap = new Map < string , Promise < ComposableCacheEntry > > ( ) ;
117
128export default {
139 async get ( cacheKey : string ) {
1410 try {
1511 // We first check if we have a pending write for this cache key
1612 // If we do, we return the pending promise instead of fetching the cache
17- if ( pendingWritePromiseMap . has ( cacheKey ) ) {
18- const stored = pendingWritePromiseMap . get ( cacheKey ) ;
19- if ( stored ) {
20- return stored . then ( ( entry ) => ( {
21- ...entry ,
22- value : toReadableStream ( entry . value ) ,
23- } ) ) ;
24- }
25- }
13+ const stored = pendingWritePromiseMap . get ( cacheKey ) ;
14+ if ( stored ) return stored ;
15+
2616 const result = await globalThis . incrementalCache . get (
2717 cacheKey ,
2818 "composable" ,
@@ -69,28 +59,45 @@ export default {
6959 } ,
7060
7161 async set ( cacheKey : string , pendingEntry : Promise < ComposableCacheEntry > ) {
72- const promiseEntry = pendingEntry . then ( async ( entry ) => ( {
73- ...entry ,
74- value : await fromReadableStream ( entry . value ) ,
75- } ) ) ;
76- pendingWritePromiseMap . set ( cacheKey , promiseEntry ) ;
62+ const teedPromise = pendingEntry . then ( ( entry ) => {
63+ // Optimization: We avoid consuming and stringifying the stream here,
64+ // because it creates double copies just to be discarded when this function
65+ // ends. This avoids unnecessary memory usage, and reduces GC pressure.
66+ const [ stream1 , stream2 ] = entry . value . tee ( ) ;
67+ return [
68+ { ...entry , value : stream1 } ,
69+ { ...entry , value : stream2 } ,
70+ ] as const ;
71+ } ) ;
7772
78- const entry = await promiseEntry . finally ( ( ) => {
73+ pendingWritePromiseMap . set (
74+ cacheKey ,
75+ teedPromise . then ( ( [ entry ] ) => entry ) ,
76+ ) ;
77+
78+ const [ , entryForStorage ] = await teedPromise . finally ( ( ) => {
7979 pendingWritePromiseMap . delete ( cacheKey ) ;
8080 } ) ;
81+
8182 await globalThis . incrementalCache . set (
8283 cacheKey ,
8384 {
84- ...entry ,
85- value : entry . value ,
85+ ...entryForStorage ,
86+ value : await fromReadableStream ( entryForStorage . value ) ,
8687 } ,
8788 "composable" ,
8889 ) ;
90+
8991 if ( globalThis . tagCache . mode === "original" ) {
9092 const storedTags = await globalThis . tagCache . getByPath ( cacheKey ) ;
91- const tagsToWrite = entry . tags . filter ( ( tag ) => ! storedTags . includes ( tag ) ) ;
93+ const tagsToWrite = [ ] ;
94+ for ( const tag of entryForStorage . tags ) {
95+ if ( ! storedTags . includes ( tag ) ) {
96+ tagsToWrite . push ( { tag, path : cacheKey } ) ;
97+ }
98+ }
9299 if ( tagsToWrite . length > 0 ) {
93- await writeTags ( tagsToWrite . map ( ( tag ) => ( { tag , path : cacheKey } ) ) ) ;
100+ await writeTags ( tagsToWrite ) ;
94101 }
95102 }
96103 } ,
@@ -100,12 +107,11 @@ export default {
100107 return ;
101108 } ,
102109 async getExpiration ( ...tags : string [ ] ) {
103- if ( globalThis . tagCache . mode === "nextMode" ) {
104- return globalThis . tagCache . getLastRevalidated ( tags ) ;
105- }
106110 // We always return 0 here, original tag cache are handled directly in the get part
107111 // TODO: We need to test this more, i'm not entirely sure that this is working as expected
108- return 0 ;
112+ return globalThis . tagCache . mode === "nextMode"
113+ ? globalThis . tagCache . getLastRevalidated ( tags )
114+ : 0 ;
109115 } ,
110116 async expireTags ( ...tags : string [ ] ) {
111117 if ( globalThis . tagCache . mode === "nextMode" ) {
@@ -125,17 +131,14 @@ export default {
125131 } ) ) ;
126132 } ) ,
127133 ) ;
128- // We need to deduplicate paths, we use a set for that
129- const setToWrite = new Set < { path : string ; tag : string } > ( ) ;
134+
135+ const dedupeMap = new Map ( ) ;
130136 for ( const entry of pathsToUpdate . flat ( ) ) {
131- setToWrite . add ( entry ) ;
137+ dedupeMap . set ( ` ${ entry . path } | ${ entry . tag } ` , entry ) ;
132138 }
133- await writeTags ( Array . from ( setToWrite ) ) ;
139+ await writeTags ( Array . from ( dedupeMap . values ( ) ) ) ;
134140 } ,
135141
136142 // This one is necessary for older versions of next
137- async receiveExpiredTags ( ...tags : string [ ] ) {
138- // This function does absolutely nothing
139- return ;
140- } ,
143+ async receiveExpiredTags ( ) { } ,
141144} satisfies ComposableCacheHandler ;
0 commit comments