@@ -102,13 +102,17 @@ abstract class SessionStateMachine[UC <: Command, SR, UserSchema <: Tuple]
102102 * per-session state in the user schema.
103103 *
104104 * @param sessionId The expired session ID
105+ * @param capabilities The session capabilities (retrieved from metadata)
105106 * @return State transition returning list of final server requests
106107 *
107108 * @note Receives full combined state - modify only UserSchema prefixes
108109 * @note Session metadata and cache are automatically removed by base class
109110 * @note Must be pure and deterministic
110111 */
111- protected def handleSessionExpired (sessionId : SessionId ): State [HMap [CombinedSchema [UserSchema ]], List [SR ]]
112+ protected def handleSessionExpired (
113+ sessionId : SessionId ,
114+ capabilities : Map [String , String ]
115+ ): State [HMap [CombinedSchema [UserSchema ]], List [SR ]]
112116
113117 // ====================================================================================
114118 // StateMachine INTERFACE - Implemented by base class
@@ -173,10 +177,16 @@ abstract class SessionStateMachine[UC <: Command, SR, UserSchema <: Tuple]
173177 def restoreFromSnapshot (stream : Stream [Nothing , Byte ]): UIO [HMap [CombinedSchema [UserSchema ]]]
174178
175179 /**
176- * Default snapshot policy - take snapshot every 1000 entries.
180+ * Snapshot policy - determines when to take snapshots.
181+ *
182+ * Users must implement this to define their snapshot strategy.
183+ *
184+ * @param lastSnapshotIndex Index of the last snapshot
185+ * @param lastSnapshotSize Size of the last snapshot in bytes
186+ * @param commitIndex Current commit index
187+ * @return true if a snapshot should be taken
177188 */
178- def shouldTakeSnapshot (lastSnapshotIndex : Index , lastSnapshotSize : Long , commitIndex : Index ): Boolean =
179- (commitIndex.value - lastSnapshotIndex.value) >= 1000
189+ def shouldTakeSnapshot (lastSnapshotIndex : Index , lastSnapshotSize : Long , commitIndex : Index ): Boolean
180190
181191 // ====================================================================================
182192 // INTERNAL COMMAND HANDLERS
@@ -270,8 +280,13 @@ abstract class SessionStateMachine[UC <: Command, SR, UserSchema <: Tuple]
270280 */
271281 private def handleSessionExpired_internal (cmd : SessionCommand .SessionExpired ): State [HMap [CombinedSchema [UserSchema ]], List [SR ]] =
272282 State .modify { state =>
273- // Call user's session expired handler first
274- val (newState, serverRequests) = handleSessionExpired(cmd.sessionId).run(state)
283+ // Get capabilities from metadata before expiring
284+ val capabilities = state.get[" metadata" ](SessionId .unwrap(cmd.sessionId))
285+ .map(_.capabilities)
286+ .getOrElse(Map .empty[String , String ])
287+
288+ // Call user's session expired handler with capabilities
289+ val (newState, serverRequests) = handleSessionExpired(cmd.sessionId, capabilities).run(state)
275290
276291 // Remove all session data
277292 val finalState = expireSession(newState, cmd.sessionId)
@@ -339,13 +354,18 @@ abstract class SessionStateMachine[UC <: Command, SR, UserSchema <: Tuple]
339354 newLastId
340355 )
341356
342- // Add requests to pending list
343- val stateWithRequests = requestsWithIds.foldLeft(stateWithNewId) { (s, req) =>
344- s.updated[" serverRequests" ](
345- s " ${SessionId .unwrap(sessionId)}- ${RequestId .unwrap(req.id)}" ,
346- req
347- )
348- }
357+ // Get existing pending requests for this session
358+ val existingRequests = state.get[" serverRequests" ](SessionId .unwrap(sessionId))
359+ .getOrElse(List .empty[PendingServerRequest [SR ]])
360+
361+ // Append new requests to the list
362+ val allRequests = existingRequests ++ requestsWithIds
363+
364+ // Update with the complete list
365+ val stateWithRequests = stateWithNewId.updated[" serverRequests" ](
366+ SessionId .unwrap(sessionId),
367+ allRequests
368+ )
349369
350370 (stateWithRequests, serverRequests)
351371
@@ -359,35 +379,25 @@ abstract class SessionStateMachine[UC <: Command, SR, UserSchema <: Tuple]
359379 sessionId : SessionId ,
360380 ackRequestId : RequestId
361381 ): HMap [CombinedSchema [UserSchema ]] =
362- // Get all pending requests for this session
363- val pending = getPendingServerRequests(state, sessionId)
382+ // Get pending requests list for this session
383+ val pending = state.get[" serverRequests" ](SessionId .unwrap(sessionId))
384+ .getOrElse(List .empty[PendingServerRequest [SR ]])
364385
365- // Remove all with ID ≤ ackRequestId (cumulative)
366- val toRemove = pending.filter(req => RequestId .unwrap(req.id) <= RequestId .unwrap(ackRequestId))
386+ // Keep only requests with ID > ackRequestId (cumulative acknowledgment )
387+ val remaining = pending.filter(req => RequestId .unwrap(req.id) > RequestId .unwrap(ackRequestId))
367388
368- // Remove from state using HMap.removed
369- toRemove.foldLeft(state) { (s, req) =>
370- s.removed[" serverRequests" ](s " ${SessionId .unwrap(sessionId)}- ${RequestId .unwrap(req.id)}" )
371- }
389+ // Update the list
390+ state.updated[" serverRequests" ](SessionId .unwrap(sessionId), remaining)
372391
373392 /**
374393 * Get all pending server requests for a session.
375- *
376- * Note: This is inefficient as it requires checking all possible keys.
377- * A better implementation would iterate the internal map.
378- * For now, we return empty list as a placeholder.
379394 */
380395 private def getPendingServerRequests (
381396 state : HMap [CombinedSchema [UserSchema ]],
382397 sessionId : SessionId
383398 ): List [PendingServerRequest [SR ]] =
384- // TODO: Need to iterate internal map or maintain separate index
385- // For now, return empty list
386- // In a real implementation, we'd need either:
387- // 1. Access to HMap's internal map for iteration
388- // 2. A separate index of request IDs per session
389- // 3. A keys() method on HMap
390- List .empty
399+ state.get[" serverRequests" ](SessionId .unwrap(sessionId))
400+ .getOrElse(List .empty[PendingServerRequest [SR ]])
391401
392402 /**
393403 * Remove all session data when session expires.
@@ -398,22 +408,15 @@ abstract class SessionStateMachine[UC <: Command, SR, UserSchema <: Tuple]
398408 ): HMap [CombinedSchema [UserSchema ]] =
399409 val sessionKey = SessionId .unwrap(sessionId)
400410
401- // Remove metadata and lastServerRequestId
402- val state1 = state
411+ // Remove all session data
412+ state
403413 .removed[" metadata" ](sessionKey)
404414 .removed[" lastServerRequestId" ](sessionKey)
415+ .removed[" serverRequests" ](sessionKey) // Remove the entire list for this session
405416
406- // Remove all pending server requests for this session
407- val pending = getPendingServerRequests(state1, sessionId)
408- val state2 = pending.foldLeft(state1) { (s, req) =>
409- s.removed[" serverRequests" ](s " $sessionKey- ${RequestId .unwrap(req.id)}" )
410- }
411-
412- // Remove cache entries for this session
413- // Note: This is inefficient - ideally we'd iterate all cache keys
414- // For now, we can't efficiently remove all cache entries without
415- // access to internal map or a keys() method
416- state2
417+ // Note: Cache entries for this session would need to be removed too,
418+ // but this requires iterating cache keys (not currently supported by HMap)
419+ // TODO: Implement cache cleanup when HMap supports iteration
417420
418421 // ====================================================================================
419422 // HELPER METHODS
0 commit comments