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