@@ -68,16 +68,25 @@ interface ServeOptions {
6868 versioned : boolean
6969}
7070
71+ /**
72+ * Whether a resolved response is a function of the stored object alone.
73+ *
74+ * `stored-bytes` may be cached for the life of the storage key: a content write stores the new
75+ * bytes under a NEW key, so a given key's response never changes. `derived-from-referenced-files`
76+ * may not — a sim page inlining its images, or a document compiled against the files it
77+ * references, is re-resolved per request and changes when a referenced file changes, while this
78+ * file's own key and `updatedAt` stay put.
79+ *
80+ * Declared rather than inferred, and REQUIRED, so a branch added to the resolver cannot inherit
81+ * the cacheable default by saying nothing — the same reason the transfer ceiling is asserted where
82+ * the branches converge rather than inside each one.
83+ */
84+ type ServableCacheability = 'stored-bytes' | 'derived-from-referenced-files'
85+
7186interface ServableBytes {
7287 buffer : Buffer
7388 contentType : string
74- /**
75- * These bytes were resolved against OTHER files' current content — a sim page inlining its
76- * images, or a document compiled against the files it references. The same storage key can
77- * therefore serve different bytes over time, with nothing about this file changing, so the
78- * response must stay revalidated even when the request carries a version.
79- */
80- dependsOnReferencedFiles ?: boolean
89+ cacheability : ServableCacheability
8190}
8291
8392/**
@@ -111,8 +120,12 @@ async function resolveServableBytes(params: {
111120 // `raw` is the stored source, already bounded by the read that produced it, but it
112121 // goes through the same check so the ceiling holds for everything this returns
113122 // rather than for every branch someone remembered to cover.
114- const resolved = params . options . raw
115- ? { buffer : params . buffer , contentType : getContentType ( params . filename ) }
123+ const resolved : ServableBytes = params . options . raw
124+ ? {
125+ buffer : params . buffer ,
126+ contentType : getContentType ( params . filename ) ,
127+ cacheability : 'stored-bytes' ,
128+ }
116129 : await resolveTransformedBytes ( params )
117130 assertKnownSizeWithinLimit (
118131 resolved . buffer . length ,
@@ -159,25 +172,35 @@ async function resolveTransformedBytes(params: {
159172 'utf8'
160173 )
161174 // Inlines the workspace images the page references, read at their CURRENT content.
162- return { buffer : rendered , contentType : 'text/html' , dependsOnReferencedFiles : true }
175+ return {
176+ buffer : rendered ,
177+ contentType : 'text/html' ,
178+ cacheability : 'derived-from-referenced-files' ,
179+ }
163180 }
164181 }
165182
166183 if ( options . preview ) {
167184 // Images resolve independently of the document path: a HEIF has no compiled-source
168185 // concept, so it never reaches the doc branch.
169186 const image = await resolveServableImageBytes ( buffer , storageKey )
170- if ( image ) return image
187+ // Transcoded from THIS file's stored bytes, so it lives and dies with the storage key.
188+ if ( image ) return { ...image , cacheability : 'stored-bytes' }
171189 }
172190
173- return resolveServableDocBytes ( {
191+ const doc = await resolveServableDocBytes ( {
174192 rawBuffer : buffer ,
175193 fileName : filename ,
176194 workspaceId,
177195 filePrincipal,
178196 ownerKey,
179197 signal,
180198 } )
199+ return {
200+ buffer : doc . buffer ,
201+ contentType : doc . contentType ,
202+ cacheability : doc . dependsOnReferencedFiles ? 'derived-from-referenced-files' : 'stored-bytes' ,
203+ }
181204}
182205
183206const STORAGE_KEY_PREFIX_RE = / ^ \d { 13 } - [ a - z 0 - 9 ] { 7 } - /
@@ -205,7 +228,7 @@ const PUBLIC_ASSET_CACHE_CONTROL = 'public, max-age=31536000'
205228 * then resolve from cache with no round trip.
206229 *
207230 * That promise does NOT hold when the response was resolved against other files' current content
208- * (`dependsOnReferencedFiles `): a document compiled against the files it references, or a sim page
231+ * (`derived-from-referenced-files `): a document compiled against the files it references, or a page
209232 * inlining its images, recompiles per request, so the same key serves different bytes once a
210233 * referenced file changes — while this file's key and `updatedAt`, and therefore the whole URL,
211234 * stay put. Promising immutability there pins a stale render in the browser cache for a year, so
@@ -214,12 +237,11 @@ const PUBLIC_ASSET_CACHE_CONTROL = 'public, max-age=31536000'
214237function resolveServeCacheControl (
215238 versioned : boolean ,
216239 context : string | undefined ,
217- dependsOnReferencedFiles : boolean | undefined
240+ cacheability : ServableCacheability
218241) : string | undefined {
219- if ( versioned && ! dependsOnReferencedFiles ) return IMMUTABLE_CACHE_CONTROL
220- return context === 'workspace' || dependsOnReferencedFiles
221- ? WORKSPACE_REVALIDATE_CACHE_CONTROL
222- : undefined
242+ const derived = cacheability === 'derived-from-referenced-files'
243+ if ( versioned && ! derived ) return IMMUTABLE_CACHE_CONTROL
244+ return context === 'workspace' || derived ? WORKSPACE_REVALIDATE_CACHE_CONTROL : undefined
223245}
224246
225247export const GET = withRouteHandler (
@@ -409,11 +431,7 @@ async function handleWorkspaceFile(
409431 buffer : resolved . buffer ,
410432 contentType : resolved . contentType ,
411433 filename : file . name ,
412- cacheControl : resolveServeCacheControl (
413- options . versioned ,
414- 'workspace' ,
415- resolved . dependsOnReferencedFiles
416- ) ,
434+ cacheControl : resolveServeCacheControl ( options . versioned , 'workspace' , resolved . cacheability ) ,
417435 } )
418436}
419437
@@ -458,7 +476,7 @@ async function handleLocalFile(
458476 const {
459477 buffer : fileBuffer ,
460478 contentType,
461- dependsOnReferencedFiles ,
479+ cacheability ,
462480 } = await resolveServableBytes ( {
463481 buffer : rawBuffer ,
464482 filename : displayName ,
@@ -475,7 +493,7 @@ async function handleLocalFile(
475493 buffer : fileBuffer ,
476494 contentType,
477495 filename : displayName ,
478- cacheControl : resolveServeCacheControl ( options . versioned , context , dependsOnReferencedFiles ) ,
496+ cacheControl : resolveServeCacheControl ( options . versioned , context , cacheability ) ,
479497 } )
480498 } catch ( error ) {
481499 logServeFailure ( 'Error reading local file:' , error )
@@ -529,7 +547,7 @@ async function handleCloudProxy(
529547 const {
530548 buffer : fileBuffer ,
531549 contentType,
532- dependsOnReferencedFiles ,
550+ cacheability ,
533551 } = await resolveServableBytes ( {
534552 buffer : rawBuffer ,
535553 filename : displayName ,
@@ -551,7 +569,7 @@ async function handleCloudProxy(
551569 buffer : fileBuffer ,
552570 contentType,
553571 filename : displayName ,
554- cacheControl : resolveServeCacheControl ( options . versioned , context , dependsOnReferencedFiles ) ,
572+ cacheControl : resolveServeCacheControl ( options . versioned , context , cacheability ) ,
555573 } )
556574 } catch ( error ) {
557575 logServeFailure ( 'Error downloading from cloud storage:' , error )
0 commit comments