Class: RailsMarkup::Store
- Inherits:
-
Object
- Object
- RailsMarkup::Store
- Defined in:
- lib/rails_markup/store.rb
Overview
In-memory store for sessions and annotations. Ephemeral by design — data lives for one coding session.
Defined Under Namespace
Classes: Annotation, Session
Constant Summary collapse
- MAX_SESSIONS =
100- SESSION_TTL =
4 hours
4 * 3600
Instance Attribute Summary collapse
-
#sessions ⇒ Object
readonly
Returns the value of attribute sessions.
Instance Method Summary collapse
-
#acknowledge(annotation_id) ⇒ Object
--- Status transitions ---.
- #all_pending ⇒ Object
-
#create_annotation(session_id:, target:, content:, intent: "change", severity: "suggestion", selected_text: nil, metadata: {}) ⇒ Object
--- Annotations ---.
-
#create_session(url:, metadata: {}) ⇒ Object
--- Sessions ---.
- #dismiss(annotation_id, reason: nil) ⇒ Object
- #get_annotation(annotation_id) ⇒ Object
- #get_session(id) ⇒ Object
-
#initialize ⇒ Store
constructor
A new instance of Store.
- #list_sessions ⇒ Object
- #pending_for_session(session_id) ⇒ Object
- #reply(annotation_id, message:) ⇒ Object
- #resolve(annotation_id, summary: nil) ⇒ Object
- #serialize_annotation(ann) ⇒ Object
-
#serialize_session(session) ⇒ Object
--- Serialization ---.
-
#subscribe(session_id, &callback) ⇒ Object
--- SSE subscriptions ---.
- #unsubscribe(sub) ⇒ Object
Constructor Details
#initialize ⇒ Store
Returns a new instance of Store.
19 20 21 22 23 24 |
# File 'lib/rails_markup/store.rb', line 19 def initialize @sessions = {} @annotations_index = {} # id -> annotation (O(1) lookup) @subscribers = [] # SSE callbacks: [session_id, callback] @mutex = Mutex.new end |
Instance Attribute Details
#sessions ⇒ Object (readonly)
Returns the value of attribute sessions.
17 18 19 |
# File 'lib/rails_markup/store.rb', line 17 def sessions @sessions end |
Instance Method Details
#acknowledge(annotation_id) ⇒ Object
--- Status transitions ---
103 104 105 |
# File 'lib/rails_markup/store.rb', line 103 def acknowledge(annotation_id) update_status(annotation_id, "acknowledged") end |
#all_pending ⇒ Object
95 96 97 98 99 |
# File 'lib/rails_markup/store.rb', line 95 def all_pending @mutex.synchronize do @sessions.values.flat_map { |s| s.annotations.select { |a| a.status == "pending" } } end end |
#create_annotation(session_id:, target:, content:, intent: "change", severity: "suggestion", selected_text: nil, metadata: {}) ⇒ Object
--- Annotations ---
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/rails_markup/store.rb', line 54 def create_annotation(session_id:, target:, content:, intent: "change", severity: "suggestion", selected_text: nil, metadata: {}) id = SecureRandom.hex(8) annotation = Annotation.new( id: id, session_id: session_id, target: target, content: content, intent: intent, severity: severity, status: "pending", selected_text: selected_text, metadata: || {}, created_at: Time.now.iso8601, thread: [] ) # Single mutex block — no TOCTOU gap @mutex.synchronize do session = @sessions[session_id] return nil unless session session.annotations << annotation @annotations_index[id] = annotation end notify(session_id, type: "annotation_created", annotation: serialize_annotation(annotation)) annotation end |
#create_session(url:, metadata: {}) ⇒ Object
--- Sessions ---
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/rails_markup/store.rb', line 28 def create_session(url:, metadata: {}) id = SecureRandom.hex(8) session = Session.new( id: id, url: url, metadata: || {}, created_at: Time.now.iso8601, annotations: [] ) @mutex.synchronize do evict_stale_sessions @sessions[id] = session end session end |
#dismiss(annotation_id, reason: nil) ⇒ Object
117 118 119 120 121 122 123 124 125 |
# File 'lib/rails_markup/store.rb', line 117 def dismiss(annotation_id, reason: nil) ann = update_status(annotation_id, "dismissed") return nil unless ann ann.thread << { role: "agent", message: reason, timestamp: Time.now.iso8601 } if reason notify(ann.session_id, type: "annotation_update", annotation: serialize_annotation(ann), status: "dismissed", reason: reason) ann end |
#get_annotation(annotation_id) ⇒ Object
84 85 86 |
# File 'lib/rails_markup/store.rb', line 84 def get_annotation(annotation_id) @mutex.synchronize { @annotations_index[annotation_id] } end |
#get_session(id) ⇒ Object
44 45 46 |
# File 'lib/rails_markup/store.rb', line 44 def get_session(id) @mutex.synchronize { @sessions[id] } end |
#list_sessions ⇒ Object
48 49 50 |
# File 'lib/rails_markup/store.rb', line 48 def list_sessions @mutex.synchronize { @sessions.values } end |
#pending_for_session(session_id) ⇒ Object
88 89 90 91 92 93 |
# File 'lib/rails_markup/store.rb', line 88 def pending_for_session(session_id) session = get_session(session_id) return [] unless session @mutex.synchronize { session.annotations.select { |a| a.status == "pending" } } end |
#reply(annotation_id, message:) ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/rails_markup/store.rb', line 127 def reply(annotation_id, message:) ann = get_annotation(annotation_id) return nil unless ann @mutex.synchronize do ann.thread << { role: "agent", message: , timestamp: Time.now.iso8601 } end notify(ann.session_id, type: "annotation_update", annotation: serialize_annotation(ann), status: ann.status, message: ) ann end |
#resolve(annotation_id, summary: nil) ⇒ Object
107 108 109 110 111 112 113 114 115 |
# File 'lib/rails_markup/store.rb', line 107 def resolve(annotation_id, summary: nil) ann = update_status(annotation_id, "resolved") return nil unless ann ann.thread << { role: "agent", message: summary, timestamp: Time.now.iso8601 } if summary notify(ann.session_id, type: "annotation_update", annotation: serialize_annotation(ann), status: "resolved", summary: summary) ann end |
#serialize_annotation(ann) ⇒ Object
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/rails_markup/store.rb', line 163 def serialize_annotation(ann) { id: ann.id, sessionId: ann.session_id, target: ann.target, content: ann.content, intent: ann.intent, severity: ann.severity, status: ann.status, selectedText: ann.selected_text, authorName: ann.&.dig("author"), metadata: ann., createdAt: ann.created_at, thread: ann.thread } end |
#serialize_session(session) ⇒ Object
--- Serialization ---
153 154 155 156 157 158 159 160 161 |
# File 'lib/rails_markup/store.rb', line 153 def serialize_session(session) { id: session.id, url: session.url, metadata: session., createdAt: session.created_at, annotations: session.annotations.map { |a| serialize_annotation(a) } } end |
#subscribe(session_id, &callback) ⇒ Object
--- SSE subscriptions ---
141 142 143 144 145 |
# File 'lib/rails_markup/store.rb', line 141 def subscribe(session_id, &callback) sub = [session_id, callback] @mutex.synchronize { @subscribers << sub } sub end |
#unsubscribe(sub) ⇒ Object
147 148 149 |
# File 'lib/rails_markup/store.rb', line 147 def unsubscribe(sub) @mutex.synchronize { @subscribers.delete(sub) } end |