Class: LittleGhost::SessionStore

Inherits:
Object
  • Object
show all
Defined in:
lib/little_ghost/session_store.rb

Overview

SessionStore connects LittleGhost conversations to application persistence. Subclass it to keep sessions in a database, remote service, or other durable store.

class DatabaseSessionStore < LittleGhost::SessionStore
def load(id, actor_id: nil)
  Conversation.find_by(external_id: id, actor_id:)&.snapshot
end

def append(id, messages:, state:, metadata:, expected_count:, actor_id: nil)
  Conversation.append!(
    id, messages:, state:, metadata:, expected_count:, actor_id:
  )
end

def replace(id, messages:, state:, metadata:, actor_id: nil)
  Conversation.replace!(id, messages:, state:, metadata:, actor_id:)
end
end

A snapshot contains :messages, :state, and :metadata. Implementations provide #load, #append, and #replace; #append must check expected_count atomically so two writers cannot silently lose a turn.

Actor identity always comes from the caller. A store must not infer it from ambient process state.

Instance Method Summary collapse

Constructor Details

#initializeSessionStore

Prepares the per-session synchronization used by #synchronize.



32
33
34
35
# File 'lib/little_ghost/session_store.rb', line 32

def initialize
  @session_locks = {}
  @session_locks_mutex = Mutex.new
end

Instance Method Details

#append(_id, messages:, state:, metadata:, expected_count:, actor_id: nil) ⇒ Object

Atomically appends sanitized messages and stores state and metadata. Implementations raise ProtocolError if the persisted message count differs from expected_count.

Raises:

  • (NotImplementedError)


45
46
47
# File 'lib/little_ghost/session_store.rb', line 45

def append(_id, messages:, state:, metadata:, expected_count:, actor_id: nil)
  raise NotImplementedError
end

#load(_id, actor_id: nil) ⇒ Object

Finds the snapshot for id, or returns nil when it does not exist.

Raises:

  • (NotImplementedError)


38
39
40
# File 'lib/little_ghost/session_store.rb', line 38

def load(_id, actor_id: nil)
  raise NotImplementedError
end

#project_conversation(_id, messages:, metadata:, actor_id: nil) ⇒ Object

Stores may expose a clean conversational view without changing the stored session transcript. The default implementation is a no-op.



56
57
58
# File 'lib/little_ghost/session_store.rb', line 56

def project_conversation(_id, messages:, metadata:, actor_id: nil)
  nil
end

#replace(_id, messages:, state:, metadata:, actor_id: nil) ⇒ Object

Replaces the complete snapshot for id atomically.

Raises:

  • (NotImplementedError)


50
51
52
# File 'lib/little_ghost/session_store.rb', line 50

def replace(_id, messages:, state:, metadata:, actor_id: nil)
  raise NotImplementedError
end

#synchronize(id, actor_id: nil) ⇒ Object

Serializes work for one actor/session key within this store instance.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/little_ghost/session_store.rb', line 67

def synchronize(id, actor_id: nil)
  key = [actor_id&.to_s, id.to_s].freeze
  entry = @session_locks_mutex.synchronize do
    current = (@session_locks[key] ||= [Mutex.new, 0])
    current[1] += 1
    current
  end
  entry.first.synchronize { yield }
ensure
  if entry
    @session_locks_mutex.synchronize do
      entry[1] -= 1
      @session_locks.delete(key) if entry[1].zero?
    end
  end
end

#with_operation_context(_operation_id) ⇒ Object

Wraps a store operation with an optional telemetry parent operation. Custom stores may override this while preserving the block's return value.



62
63
64
# File 'lib/little_ghost/session_store.rb', line 62

def with_operation_context(_operation_id)
  yield
end