Class: Ask::AppServer::SessionStore

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

Overview

Session store backed by Ask::State::Adapter.

Stores session metadata and subscriptions in the state backend. AgentAdapter instances (which hold in-memory agent sessions) are kept separately and not persisted.

Default backend is Ask::State::Memory (in-process, ephemeral). Pass Ask::State::Providers::SQLite for persistent storage:

store = SessionStore.new(
state: Ask::State::Providers::SQLite.new(path: "~/.ask-app-server/state.db")
)

Constant Summary collapse

SESSION_PREFIX =
"session:"
SUBSCRIPTION_PREFIX =
"sub:"
SESSION_LIST_KEY =
"sessions"
EVENT_PREFIX =
"events:"

Instance Method Summary collapse

Constructor Details

#initialize(state: nil) ⇒ SessionStore

Returns a new instance of SessionStore.



25
26
27
28
29
# File 'lib/ask/app_server/session_store.rb', line 25

def initialize(state: nil)
  @state = state || Ask::State::Memory.new
  @adapters = {}   # session_id => AgentAdapter (in-memory only)
  @mutex = Mutex.new
end

Instance Method Details

#_remove(session_id) ⇒ Object

Internal remove (no locking — caller must hold @mutex).



58
59
60
61
62
63
# File 'lib/ask/app_server/session_store.rb', line 58

def _remove(session_id)
  @state.delete("#{SESSION_PREFIX}#{session_id}")
  @state.delete("#{SUBSCRIPTION_PREFIX}#{session_id}")
  @state.delete("#{EVENT_PREFIX}#{session_id}")
  @adapters.delete(session_id)
end

#add(session_id, adapter) ⇒ Object

Register a new session.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/ask/app_server/session_store.rb', line 32

def add(session_id, adapter)
  model = adapter.respond_to?(:instance_variable_get) ? adapter.instance_variable_get(:@model) : nil
   = {
    sessionId: session_id,
    model: model,
    createdAt: adapter.respond_to?(:created_at) ? adapter.created_at.iso8601 : Time.now.iso8601
  }

  @mutex.synchronize do
    existing = @state.get("#{SESSION_PREFIX}#{session_id}")
    raise Ask::AppServer::SessionAlreadyExists, "Session #{session_id} already exists" if existing

    @state.set("#{SESSION_PREFIX}#{session_id}", )
    @state.list_append(SESSION_LIST_KEY, session_id, max_length: 200)
    @adapters[session_id] = adapter
  end
end

#append_event(session_id, event) ⇒ Object

Append an event to a session's event log.



126
127
128
# File 'lib/ask/app_server/session_store.rb', line 126

def append_event(session_id, event)
  @state.list_append("#{EVENT_PREFIX}#{session_id}", event, max_length: 1000)
end

#clearObject

Clear all sessions.



112
113
114
115
116
# File 'lib/ask/app_server/session_store.rb', line 112

def clear
  @mutex.synchronize do
    @adapters.each_key { |sid| _remove(sid) }
  end
end

#countObject

Count of active sessions.



107
108
109
# File 'lib/ask/app_server/session_store.rb', line 107

def count
  @adapters.size
end

#each(&block) ⇒ Object

Iterate over all session adapters.



119
120
121
122
123
# File 'lib/ask/app_server/session_store.rb', line 119

def each(&block)
  # Iterate over a snapshot to avoid holding the mutex during yield
  snapshot = @mutex.synchronize { @adapters.values.dup }
  snapshot.each(&block)
end

#events_after(session_id, after_seq, limit: nil) ⇒ Object

Get events for a session after a given sequence number.



131
132
133
134
135
# File 'lib/ask/app_server/session_store.rb', line 131

def events_after(session_id, after_seq, limit: nil)
  events = @state.list_range("#{EVENT_PREFIX}#{session_id}") || []
  filtered = events.select { |e| e[:seq].to_i > after_seq.to_i }
  limit ? filtered.first(limit) : filtered
end

#get(session_id) ⇒ Object

Get a session adapter by ID.



66
67
68
# File 'lib/ask/app_server/session_store.rb', line 66

def get(session_id)
  @mutex.synchronize { @adapters[session_id] }
end

#list(limit: 20) ⇒ Object

List all sessions (summaries).



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/ask/app_server/session_store.rb', line 71

def list(limit: 20)
  session_ids = @state.list_range(SESSION_LIST_KEY, 0, limit - 1)
  session_ids.map do |sid|
     = @state.get("#{SESSION_PREFIX}#{sid}")
    if 
      adapter = @adapters[sid]
      .merge(
        running: adapter&.running || false,
        idle: adapter&.idle? || true
      )
    end
  end.compact
end

#remove(session_id) ⇒ Object

Remove a session.



51
52
53
54
55
# File 'lib/ask/app_server/session_store.rb', line 51

def remove(session_id)
  @mutex.synchronize do
    _remove(session_id)
  end
end

#subscribe(session_id, delivery_kind:) ⇒ Object

Subscribe to a session's events.



86
87
88
89
90
91
# File 'lib/ask/app_server/session_store.rb', line 86

def subscribe(session_id, delivery_kind:)
  @mutex.synchronize do
    raise Ask::AppServer::SessionNotFound, "Session #{session_id} not found" unless @adapters.key?(session_id)
    @state.set("#{SUBSCRIPTION_PREFIX}#{session_id}", { subscribed: true, deliveryKind: delivery_kind })
  end
end

#subscribed?(session_id) ⇒ Boolean

Is the session subscribed?

Returns:

  • (Boolean)


101
102
103
104
# File 'lib/ask/app_server/session_store.rb', line 101

def subscribed?(session_id)
  sub = @state.get("#{SUBSCRIPTION_PREFIX}#{session_id}")
  sub && sub[:subscribed]
end

#unsubscribe(session_id) ⇒ Object

Unsubscribe from a session.



94
95
96
97
98
# File 'lib/ask/app_server/session_store.rb', line 94

def unsubscribe(session_id)
  @mutex.synchronize do
    @state.delete("#{SUBSCRIPTION_PREFIX}#{session_id}")
  end
end