File size: 4,144 Bytes
b91e262
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import {
  arrayBufferToString,
  stringToUint8Array,
} from '../app-render/encryption-utils'
import type { CacheEntry } from '../lib/cache-handlers/types'
import type { CachedFetchValue } from '../response-cache/types'
import { DYNAMIC_EXPIRE } from '../use-cache/constants'

/**
 * A generic cache store type that provides a subset of Map functionality
 */
type CacheStore<T> = Pick<
  Map<string, T>,
  'entries' | 'keys' | 'size' | 'get' | 'set'
>

/**
 * A cache store specifically for fetch cache values
 */
export type FetchCacheStore = CacheStore<CachedFetchValue>

/**
 * A cache store for encrypted bound args of inline server functions.
 */
export type EncryptedBoundArgsCacheStore = CacheStore<string>

/**
 * An in-memory-only cache store for decrypted bound args of inline server
 * functions.
 */
export type DecryptedBoundArgsCacheStore = CacheStore<string>

/**
 * Serialized format for "use cache" entries
 */
export interface UseCacheCacheStoreSerialized {
  value: string
  tags: string[]
  stale: number
  timestamp: number
  expire: number
  revalidate: number
}

/**
 * A cache store specifically for "use cache" values that stores promises of
 * cache entries.
 */
export type UseCacheCacheStore = CacheStore<Promise<CacheEntry>>

/**
 * Parses serialized cache entries into a UseCacheCacheStore
 * @param entries - The serialized entries to parse
 * @returns A new UseCacheCacheStore containing the parsed entries
 */
export function parseUseCacheCacheStore(
  entries: Iterable<[string, UseCacheCacheStoreSerialized]>
): UseCacheCacheStore {
  const store = new Map<string, Promise<CacheEntry>>()

  for (const [
    key,
    { value, tags, stale, timestamp, expire, revalidate },
  ] of entries) {
    store.set(
      key,
      Promise.resolve({
        // Create a ReadableStream from the Uint8Array
        value: new ReadableStream<Uint8Array>({
          start(controller) {
            // Enqueue the Uint8Array to the stream
            controller.enqueue(stringToUint8Array(atob(value)))

            // Close the stream
            controller.close()
          },
        }),
        tags,
        stale,
        timestamp,
        expire,
        revalidate,
      })
    )
  }

  return store
}

/**
 * Serializes UseCacheCacheStore entries into an array of key-value pairs
 * @param entries - The store entries to stringify
 * @returns A promise that resolves to an array of key-value pairs with serialized values
 */
export async function serializeUseCacheCacheStore(
  entries: IterableIterator<[string, Promise<CacheEntry>]>,
  isCacheComponentsEnabled: boolean
): Promise<Array<[string, UseCacheCacheStoreSerialized] | null>> {
  return Promise.all(
    Array.from(entries).map(([key, value]) => {
      return value
        .then(async (entry) => {
          if (
            isCacheComponentsEnabled &&
            (entry.revalidate === 0 || entry.expire < DYNAMIC_EXPIRE)
          ) {
            // The entry was omitted from the prerender result, and subsequently
            // does not need to be included in the serialized RDC.
            return null
          }

          const [left, right] = entry.value.tee()
          entry.value = right

          let binaryString: string = ''

          // We want to encode the value as a string, but we aren't sure if the
          // value is a a stream of UTF-8 bytes or not, so let's just encode it
          // as a string using base64.
          for await (const chunk of left) {
            binaryString += arrayBufferToString(chunk)
          }

          return [
            key,
            {
              // Encode the value as a base64 string.
              value: btoa(binaryString),
              tags: entry.tags,
              stale: entry.stale,
              timestamp: entry.timestamp,
              expire: entry.expire,
              revalidate: entry.revalidate,
            },
          ] satisfies [string, UseCacheCacheStoreSerialized]
        })
        .catch(() => {
          // Any failed cache writes should be ignored as to not discard the
          // entire cache.
          return null
        })
    })
  )
}