@@ -562,6 +562,67 @@ async function ensureBaseStructure(podUrl, fetch) {
562562 }
563563}
564564
565+ /** Per-user baton: openidId -> Promise that resolves when ensureBaseStructure has completed for that Pod */
566+ const baseStructureBatonMap = new Map ( ) ;
567+
568+ /**
569+ * Waits for the Pod base structure to be ready for this user (baton).
570+ * If login already started ensureBaseStructure in the background, we wait for it.
571+ * Otherwise we run it once (lazy) and cache the promise so other writes wait too.
572+ *
573+ * @param {Object } req - Express request (must have req.user.openidId)
574+ * @returns {Promise<void> }
575+ */
576+ async function ensureBaseStructureReady ( req ) {
577+ const openidId = req ?. user ?. openidId ;
578+ if ( ! openidId ) {
579+ throw new Error ( 'User not authenticated with Solid/OpenID' ) ;
580+ }
581+ let promise = baseStructureBatonMap . get ( openidId ) ;
582+ if ( ! promise ) {
583+ const authenticatedFetch = await getSolidFetch ( req ) ;
584+ const podUrl = await getPodUrl ( openidId , authenticatedFetch ) ;
585+ promise = ensureBaseStructure ( podUrl , authenticatedFetch ) . catch ( ( err ) => {
586+ baseStructureBatonMap . delete ( openidId ) ;
587+ throw err ;
588+ } ) ;
589+ baseStructureBatonMap . set ( openidId , promise ) ;
590+ }
591+ await promise ;
592+ }
593+
594+ /**
595+ * Starts ensureBaseStructure in the background for this user (call after Solid/OpenID login).
596+ * Writes will wait for this to complete via ensureBaseStructureReady (baton).
597+ * Safe to call multiple times per user; only the first run is used.
598+ *
599+ * @param {Object } req - Express request (must have req.user with openidId and session for fetch)
600+ */
601+ function startBaseStructureAfterLogin ( req ) {
602+ const openidId = req ?. user ?. openidId ;
603+ if ( ! openidId ) return Promise . resolve ( ) ;
604+ const existing = baseStructureBatonMap . get ( openidId ) ;
605+ if ( existing ) return existing ;
606+
607+ const promise = ( async ( ) => {
608+ try {
609+ const authenticatedFetch = await getSolidFetch ( req ) ;
610+ const podUrl = await getPodUrl ( openidId , authenticatedFetch ) ;
611+ await ensureBaseStructure ( podUrl , authenticatedFetch ) ;
612+ logger . debug ( '[SolidStorage] Base structure ready after login' , { openidId } ) ;
613+ } catch ( err ) {
614+ baseStructureBatonMap . delete ( openidId ) ;
615+ logger . warn ( '[SolidStorage] Base structure init after login failed' , {
616+ openidId,
617+ error : err ?. message ,
618+ } ) ;
619+ throw err ;
620+ }
621+ } ) ( ) ;
622+ baseStructureBatonMap . set ( openidId , promise ) ;
623+ return promise ;
624+ }
625+
565626/**
566627 * Save a message to Solid Pod.
567628 * Document content is built in the model layer (schema-aligned); this only persists it.
@@ -589,9 +650,9 @@ async function saveMessageToSolid(req, messageDocument, metadata) {
589650 throw new Error ( 'conversationId is required' ) ;
590651 }
591652
653+ await ensureBaseStructureReady ( req ) ;
592654 const authenticatedFetch = await getSolidFetch ( req ) ;
593655 const podUrl = await getPodUrl ( req . user . openidId , authenticatedFetch ) ;
594- await ensureBaseStructure ( podUrl , authenticatedFetch ) ;
595656
596657 const messagesContainerPath = getMessagesContainerPath ( podUrl , messageDocument . conversationId ) ;
597658 await ensureContainerExists ( messagesContainerPath , authenticatedFetch ) ;
@@ -1209,9 +1270,9 @@ async function saveConvoToSolid(req, convoDocument, metadata) {
12091270 throw new Error ( 'conversationId is required' ) ;
12101271 }
12111272
1273+ await ensureBaseStructureReady ( req ) ;
12121274 const authenticatedFetch = await getSolidFetch ( req ) ;
12131275 const podUrl = await getPodUrl ( req . user . openidId , authenticatedFetch ) ;
1214- await ensureBaseStructure ( podUrl , authenticatedFetch ) ;
12151276
12161277 const conversationPath = getConversationPath ( podUrl , finalConversationId ) ;
12171278
@@ -2948,6 +3009,7 @@ module.exports = {
29483009 getMessagePath,
29493010 ensureContainerExists,
29503011 ensureBaseStructure,
3012+ startBaseStructureAfterLogin,
29513013 saveMessageToSolid,
29523014 getMessagesFromSolid,
29533015 updateMessageInSolid,
0 commit comments