@@ -47,7 +47,7 @@ function checkRateLimit(ip: string): { allowed: boolean; remaining: number; rese
4747 const resetIn = RATE_LIMIT_WINDOW_MS - ( now - entry . windowStart ) ;
4848 return { allowed : true , remaining : RATE_LIMIT_MAX_REQUESTS - entry . count , resetIn } ;
4949}
50- import { createSession , getSession , remixSession , publishSession , getSecondsUntilMidnightUTC } from './sessions' ;
50+ import { createSession , getSession , remixSessionFromState , publishSessionFromState , getSecondsUntilMidnightUTC } from './sessions' ;
5151import {
5252 isValidUUID ,
5353 validateSessionState ,
@@ -176,13 +176,32 @@ export default {
176176 // Session Pages: Inject dynamic meta tags for social sharing & SEO
177177 // Always inject for valid session IDs (not just crawlers) so validation
178178 // tools like OpenGraph.xyz, metatags.io, and schema.org see correct content
179+ // Phase 34: Route through DO for latest state (includes pending changes)
179180 // ========================================================================
180181 if ( path . startsWith ( '/s/' ) ) {
181182 const sessionMatch = path . match ( / ^ \/ s \/ ( [ a - f 0 - 9 - ] { 36 } ) $ / ) ;
182183
183184 if ( sessionMatch ) {
184185 const sessionId = sessionMatch [ 1 ] ;
185- const sessionData = await env . SESSIONS . get ( `session:${ sessionId } ` , 'json' ) as Session | null ;
186+
187+ // Phase 34: Get session from DO (source of truth) instead of direct KV read
188+ // This ensures social previews show the latest state including pending changes
189+ let sessionData : Session | null = null ;
190+ try {
191+ const doId = env . LIVE_SESSIONS . idFromName ( sessionId ) ;
192+ const stub = env . LIVE_SESSIONS . get ( doId ) ;
193+ const doResponse = await stub . fetch ( new Request (
194+ new URL ( `/api/sessions/${ sessionId } ` , request . url ) . toString ( ) ,
195+ { method : 'GET' }
196+ ) ) ;
197+ if ( doResponse . ok ) {
198+ sessionData = await doResponse . json ( ) as Session ;
199+ }
200+ } catch ( error ) {
201+ // Fall back to KV if DO fails (session might not exist or DO error)
202+ console . log ( `[meta] DO fetch failed for ${ sessionId } , falling back to KV:` , error ) ;
203+ sessionData = await env . SESSIONS . get ( `session:${ sessionId } ` , 'json' ) as Session | null ;
204+ }
186205
187206 if ( sessionData ) {
188207 // Fetch the base HTML (index.html for SPA)
@@ -479,6 +498,7 @@ async function handleApiRequest(
479498 }
480499
481500 // POST /api/sessions/:id/remix - Remix a session (create a copy)
501+ // Phase 34: Route through DO to get latest source state (may have pending changes)
482502 if ( remixMatch && method === 'POST' ) {
483503 const sourceId = remixMatch [ 1 ] ;
484504
@@ -488,13 +508,33 @@ async function handleApiRequest(
488508 return jsonError ( 'Invalid session ID format' , 400 ) ;
489509 }
490510
491- const result = await remixSession ( env , sourceId ) ;
511+ // Get source session from DO (includes pending changes not yet in KV)
512+ const doId = env . LIVE_SESSIONS . idFromName ( sourceId ) ;
513+ const stub = env . LIVE_SESSIONS . get ( doId ) ;
492514
493- if ( ! result ) {
515+ let sourceSession : Session | null = null ;
516+ try {
517+ const doResponse = await stub . fetch ( new Request (
518+ new URL ( `/api/sessions/${ sourceId } ` , request . url ) . toString ( ) ,
519+ { method : 'GET' }
520+ ) ) ;
521+ if ( doResponse . ok ) {
522+ sourceSession = await doResponse . json ( ) as Session ;
523+ }
524+ } catch ( error ) {
525+ console . error ( `[remix] DO error for source ${ sourceId } :` , error ) ;
526+ // Fall back to KV if DO fails
527+ sourceSession = await getSession ( env , sourceId , false ) ;
528+ }
529+
530+ if ( ! sourceSession ) {
494531 await completeLog ( 404 , undefined , 'Session not found' ) ;
495532 return jsonError ( 'Session not found' , 404 ) ;
496533 }
497534
535+ // Create remix using the DO-provided state
536+ const result = await remixSessionFromState ( env , sourceId , sourceSession ) ;
537+
498538 if ( ! result . success ) {
499539 if ( result . quotaExceeded ) {
500540 await completeLog ( 503 , undefined , 'KV quota exceeded' ) ;
@@ -529,6 +569,7 @@ async function handleApiRequest(
529569 // ==========================================================================
530570
531571 // POST /api/sessions/:id/publish - Publish a session (make it immutable)
572+ // Phase 34: Route through DO to get latest source state (may have pending changes)
532573 if ( publishMatch && method === 'POST' ) {
533574 const id = publishMatch [ 1 ] ;
534575
@@ -538,13 +579,33 @@ async function handleApiRequest(
538579 return jsonError ( 'Invalid session ID format' , 400 ) ;
539580 }
540581
541- const result = await publishSession ( env , id ) ;
582+ // Get source session from DO (includes pending changes not yet in KV)
583+ const doId = env . LIVE_SESSIONS . idFromName ( id ) ;
584+ const stub = env . LIVE_SESSIONS . get ( doId ) ;
585+
586+ let sourceSession : Session | null = null ;
587+ try {
588+ const doResponse = await stub . fetch ( new Request (
589+ new URL ( `/api/sessions/${ id } ` , request . url ) . toString ( ) ,
590+ { method : 'GET' }
591+ ) ) ;
592+ if ( doResponse . ok ) {
593+ sourceSession = await doResponse . json ( ) as Session ;
594+ }
595+ } catch ( error ) {
596+ console . error ( `[publish] DO error for source ${ id } :` , error ) ;
597+ // Fall back to KV if DO fails
598+ sourceSession = await getSession ( env , id , false ) ;
599+ }
542600
543- if ( ! result ) {
601+ if ( ! sourceSession ) {
544602 await completeLog ( 404 , undefined , 'Session not found' ) ;
545603 return jsonError ( 'Session not found' , 404 ) ;
546604 }
547605
606+ // Publish using the DO-provided state
607+ const result = await publishSessionFromState ( env , id , sourceSession ) ;
608+
548609 if ( ! result . success ) {
549610 if ( result . quotaExceeded ) {
550611 await completeLog ( 503 , undefined , 'KV quota exceeded' ) ;
@@ -592,6 +653,9 @@ async function handleApiRequest(
592653 }
593654
594655 // GET /api/sessions/:id - Get session
656+ // Phase 34: Route through Durable Object to get latest state (source of truth)
657+ // This fixes the architectural violation where we read stale data from KV
658+ // while DO had pending changes not yet persisted.
595659 if ( sessionMatch && method === 'GET' ) {
596660 const id = sessionMatch [ 1 ] ;
597661
@@ -601,23 +665,46 @@ async function handleApiRequest(
601665 return jsonError ( 'Invalid session ID format' , 400 ) ;
602666 }
603667
604- const session = await getSession ( env , id ) ;
668+ // Route through DO - it will return latest state (including pending changes)
669+ // and merge with KV metadata (name, timestamps, etc.)
670+ const doId = env . LIVE_SESSIONS . idFromName ( id ) ;
671+ const stub = env . LIVE_SESSIONS . get ( doId ) ;
605672
606- if ( ! session ) {
607- await completeLog ( 404 , undefined , 'Session not found' ) ;
608- return jsonError ( 'Session not found' , 404 ) ;
609- }
673+ try {
674+ const doResponse = await stub . fetch ( new Request ( request . url , { method : 'GET' } ) ) ;
610675
611- await trackSessionAccessed ( env ) ;
612- await completeLog ( 200 , {
613- trackCount : session . state . tracks . length ,
614- hasData : session . state . tracks . length > 0 ,
615- } ) ;
676+ if ( doResponse . status === 404 ) {
677+ await completeLog ( 404 , undefined , 'Session not found' ) ;
678+ return jsonError ( 'Session not found' , 404 ) ;
679+ }
616680
617- return new Response ( JSON . stringify ( session ) , {
618- status : 200 ,
619- headers : { 'Content-Type' : 'application/json' } ,
620- } ) ;
681+ if ( ! doResponse . ok ) {
682+ const errorBody = await doResponse . text ( ) ;
683+ await completeLog ( doResponse . status , undefined , errorBody ) ;
684+ return new Response ( errorBody , {
685+ status : doResponse . status ,
686+ headers : { 'Content-Type' : 'application/json' } ,
687+ } ) ;
688+ }
689+
690+ const session = await doResponse . json ( ) as { state : { tracks : unknown [ ] } } ;
691+
692+ await trackSessionAccessed ( env ) ;
693+ await completeLog ( 200 , {
694+ trackCount : session . state . tracks . length ,
695+ hasData : session . state . tracks . length > 0 ,
696+ } ) ;
697+
698+ return new Response ( JSON . stringify ( session ) , {
699+ status : 200 ,
700+ headers : { 'Content-Type' : 'application/json' } ,
701+ } ) ;
702+ } catch ( error ) {
703+ // If DO fails, log and return error (don't silently fall back to KV)
704+ console . error ( `[GET] DO error for session ${ id } :` , error ) ;
705+ await completeLog ( 500 , undefined , `DO error: ${ error } ` ) ;
706+ return jsonError ( 'Failed to retrieve session' , 500 ) ;
707+ }
621708 }
622709
623710 // PUT /api/sessions/:id - Update session
0 commit comments