@@ -34,6 +34,8 @@ const MARKDOWN_RESOLVED_INDEX_PATH = "/tmp/markdown-index-8133b4cf3c66.sqlite"
3434const MARKDOWN_CHANGES_PATH = "/tmp/wp-codebox-canonical-changes.json"
3535const R2_MARKDOWN_REVISION_PREFIX = "sites/default/markdown/revisions"
3636const R2_MARKDOWN_OBJECT_PREFIX = "sites/default/markdown/objects"
37+ const R2_WORDPRESS_PAGE_PREFIX = "sites/default/pages"
38+ const WORDPRESS_PAGE_CACHE_SCHEMA = "v2"
3739const SERIALIZED_MARKDOWN_MUTATION_CODE = `<?php
3840define('SHORTINIT', true);
3941require '/wordpress/wp-load.php';
@@ -114,6 +116,18 @@ interface RuntimeFile {
114116 bytes : Uint8Array
115117}
116118
119+ interface CoordinatorState {
120+ pointer : MarkdownPointer | null
121+ }
122+
123+ interface WordPressPageSnapshot {
124+ schema : "wp-codebox/wordpress-page/v1"
125+ status : number
126+ statusText : string
127+ headers : Array < [ string , string ] >
128+ body : string
129+ }
130+
117131interface CanonicalSeedManifest {
118132 schema : string
119133 markdownDatabaseIntegrationRevision : string
@@ -201,6 +215,13 @@ async function secretsMatch(left: string, right: string): Promise<boolean> {
201215
202216async function runCoordinatedWordPressRequest ( request : Request , env : Env , coordinator : DurableObjectStub , route : "wordpress" | "health" | "r2-mutate" ) : Promise < Response > {
203217 if ( route === "r2-mutate" && request . method !== "POST" ) return new Response ( "WordPress state mutation requires POST." , { status : 405 } )
218+ if ( route === "wordpress" && isCacheableWordPressPageRequest ( request ) ) {
219+ const state = await coordinatorCall < CoordinatorState > ( coordinator , request . url , "state" )
220+ if ( state . pointer ) {
221+ const cachedPage = await matchWordPressPageCache ( request , state . pointer , env . WORDPRESS_STATE_BUCKET )
222+ if ( cachedPage ) return cachedPage
223+ }
224+ }
204225 let lease = await acquireLease ( coordinator , request . url )
205226 let runtime : Runtime | undefined
206227 let finalized = false
@@ -212,7 +233,7 @@ async function runCoordinatedWordPressRequest(request: Request, env: Env, coordi
212233 if ( ! lease . pointer || lease . pointer . revision !== bootstrapped . pointer . revision ) throw new Error ( "Canonical bootstrap promotion was not observed by its next lease." )
213234 cacheRuntime ( lease . pointer , bootstrapped )
214235 }
215- const cachedPage = route === "wordpress" ? await matchWordPressPageCache ( request , lease . pointer ) : null
236+ const cachedPage = route === "wordpress" ? await matchWordPressPageCache ( request , lease . pointer , env . WORDPRESS_STATE_BUCKET ) : null
216237 if ( cachedPage ) {
217238 await releaseLease ( coordinator , request . url , lease )
218239 finalized = true
@@ -242,7 +263,7 @@ async function runCoordinatedWordPressRequest(request: Request, env: Env, coordi
242263 }
243264 finalized = true
244265 if ( mutatesCanonicalState ) await discardRuntime ( runtime )
245- else if ( route === "wordpress" ) response = await cacheWordPressPage ( request , lease . pointer , response )
266+ else if ( route === "wordpress" ) response = await cacheWordPressPage ( request , lease . pointer , response , env . WORDPRESS_STATE_BUCKET )
246267 return response
247268 } catch ( error ) {
248269 if ( ! finalized ) await abortLease ( coordinator , request . url , lease )
@@ -259,43 +280,65 @@ function isCacheableWordPressPageRequest(request: Request): boolean {
259280 return ! url . searchParams . has ( "preview" ) && ! url . searchParams . has ( "rest_route" )
260281}
261282
262- async function matchWordPressPageCache ( request : Request , pointer : MarkdownPointer ) : Promise < Response | null > {
283+ async function matchWordPressPageCache ( request : Request , pointer : MarkdownPointer , bucket : R2Bucket ) : Promise < Response | null > {
263284 if ( ! isCacheableWordPressPageRequest ( request ) ) return null
264285 const cache = typeof caches === "undefined" ? undefined : ( caches as CacheStorage & { default ?: Cache } ) . default
265- if ( ! cache ) return null
266286 try {
267- const cached = await cache . match ( wordPressPageCacheKey ( request , pointer ) )
268- return cached ? pageCacheResponse ( cached , request . method === "HEAD" , "hit" ) : null
287+ const cached = cache ? await cache . match ( wordPressPageCacheKey ( request , pointer ) ) : null
288+ if ( cached ) return pageCacheResponse ( cached , request . method === "HEAD" , "hit" , "edge" )
289+ const object = await bucket . get ( await wordPressPageSnapshotKey ( request , pointer ) )
290+ if ( ! object ) return null
291+ const snapshot = JSON . parse ( await object . text ( ) ) as WordPressPageSnapshot
292+ if ( snapshot . schema !== "wp-codebox/wordpress-page/v1" || snapshot . status !== 200 || ! Array . isArray ( snapshot . headers ) || typeof snapshot . body !== "string" ) return null
293+ const response = new Response ( snapshot . body , { status : snapshot . status , statusText : snapshot . statusText , headers : snapshot . headers } )
294+ if ( cache ) await cache . put ( wordPressPageCacheKey ( request , pointer ) , response . clone ( ) )
295+ return pageCacheResponse ( response , request . method === "HEAD" , "hit" , "r2" )
269296 } catch {
270297 return null
271298 }
272299}
273300
274- async function cacheWordPressPage ( request : Request , pointer : MarkdownPointer , response : Response ) : Promise < Response > {
301+ async function cacheWordPressPage ( request : Request , pointer : MarkdownPointer , response : Response , bucket : R2Bucket ) : Promise < Response > {
275302 if ( ! isCacheableWordPressPageRequest ( request ) || response . status !== 200 || ! response . headers . get ( "content-type" ) ?. includes ( "text/html" ) || response . headers . has ( "set-cookie" ) ) return response
276- if ( request . method === "HEAD" ) return pageCacheResponse ( response , true , "miss" )
303+ if ( request . method === "HEAD" ) return pageCacheResponse ( response , true , "miss" , "render" )
277304 const cache = typeof caches === "undefined" ? undefined : ( caches as CacheStorage & { default ?: Cache } ) . default
278- const cacheable = pageCacheResponse ( response , false , "miss" )
279- if ( cache ) {
280- try {
281- await cache . put ( wordPressPageCacheKey ( request , pointer ) , cacheable . clone ( ) )
282- } catch {
283- // Page caching is an optimization; canonical rendering remains authoritative.
305+ const cacheable = pageCacheResponse ( response , false , "miss" , "render" )
306+ try {
307+ const snapshot : WordPressPageSnapshot = {
308+ schema : "wp-codebox/wordpress-page/v1" ,
309+ status : cacheable . status ,
310+ statusText : cacheable . statusText ,
311+ headers : Array . from ( cacheable . headers . entries ( ) ) ,
312+ body : await cacheable . clone ( ) . text ( ) ,
284313 }
314+ await Promise . all ( [
315+ cache ? cache . put ( wordPressPageCacheKey ( request , pointer ) , cacheable . clone ( ) ) : Promise . resolve ( ) ,
316+ bucket . put ( await wordPressPageSnapshotKey ( request , pointer ) , JSON . stringify ( snapshot ) , { httpMetadata : { contentType : "application/json" } } ) ,
317+ ] )
318+ } catch {
319+ // Page caching is an optimization; canonical rendering remains authoritative.
285320 }
286321 return cacheable
287322}
288323
289324function wordPressPageCacheKey ( request : Request , pointer : MarkdownPointer ) : Request {
290325 const url = new URL ( request . url )
291326 url . searchParams . set ( "__wp_codebox_revision" , pointer . revision )
327+ url . searchParams . set ( "__wp_codebox_page_cache" , WORDPRESS_PAGE_CACHE_SCHEMA )
292328 return new Request ( url , { method : "GET" } )
293329}
294330
295- function pageCacheResponse ( response : Response , head : boolean , status : "hit" | "miss" ) : Response {
331+ async function wordPressPageSnapshotKey ( request : Request , pointer : MarkdownPointer ) : Promise < string > {
332+ const digest = await crypto . subtle . digest ( "SHA-256" , new TextEncoder ( ) . encode ( new URL ( request . url ) . toString ( ) ) )
333+ const hash = Array . from ( new Uint8Array ( digest ) , ( byte ) => byte . toString ( 16 ) . padStart ( 2 , "0" ) ) . join ( "" )
334+ return `${ R2_WORDPRESS_PAGE_PREFIX } /${ pointer . revision } /${ hash } .json`
335+ }
336+
337+ function pageCacheResponse ( response : Response , head : boolean , status : "hit" | "miss" , source : "edge" | "r2" | "render" ) : Response {
296338 const headers = new Headers ( response . headers )
297339 headers . set ( "cache-control" , "public, max-age=60, s-maxage=31536000" )
298340 headers . set ( "x-wp-codebox-page-cache" , status )
341+ headers . set ( "x-wp-codebox-page-cache-source" , source )
299342 return new Response ( head ? null : response . body , { status : response . status , statusText : response . statusText , headers } )
300343}
301344
0 commit comments