Auto-indexing service and GraphQL API for AT Protocol Records quickslice.slices.network/
atproto gleam graphql

docs: update changelog for v0.17.5

+272
+5
CHANGELOG.md
··· 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 7 8 + ## v0.17.5 9 + 10 + ### Fixed 11 + - Publish pubsub events after GraphQL mutations for immediate WebSocket subscription updates 12 + 8 13 ## quickslice-client-js v0.2.0 9 14 10 15 ### Added
+267
dev-docs/plans/2025-12-19-mutation-subscription-events.md
··· 1 + # Mutation Subscription Events Implementation Plan 2 + 3 + > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. 4 + 5 + **Goal:** Publish pubsub events after GraphQL mutations so WebSocket subscriptions receive updates immediately. 6 + 7 + **Architecture:** Add `pubsub.publish()` calls after database operations in create/update/delete mutation resolvers. Export existing `microseconds_to_iso8601` from event_handler and add a `current_timestamp_us` helper. 8 + 9 + **Tech Stack:** Gleam, Erlang FFI, existing pubsub module 10 + 11 + --- 12 + 13 + ## Task 1: Export timestamp functions from event_handler 14 + 15 + **Files:** 16 + - Modify: `server/src/event_handler.gleam:54-57` 17 + 18 + **Step 1: Make microseconds_to_iso8601 public and add current_timestamp_us** 19 + 20 + Replace lines 54-57: 21 + 22 + ```gleam 23 + /// Convert microseconds since Unix epoch to ISO8601 format 24 + /// Uses the event's original timestamp for accurate indexedAt values 25 + @external(erlang, "event_handler_ffi", "microseconds_to_iso8601") 26 + fn microseconds_to_iso8601(time_us: Int) -> String 27 + ``` 28 + 29 + With: 30 + 31 + ```gleam 32 + /// Convert microseconds since Unix epoch to ISO8601 format 33 + @external(erlang, "event_handler_ffi", "microseconds_to_iso8601") 34 + pub fn microseconds_to_iso8601(time_us: Int) -> String 35 + 36 + /// Get current timestamp in microseconds 37 + @external(erlang, "os", "system_time") 38 + fn system_time_native() -> Int 39 + 40 + /// Get current time as ISO8601 string 41 + pub fn current_iso8601() -> String { 42 + // os:system_time() returns nanoseconds, convert to microseconds 43 + microseconds_to_iso8601(system_time_native() / 1000) 44 + } 45 + ``` 46 + 47 + **Step 2: Verify it compiles** 48 + 49 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam build` 50 + Expected: Build succeeds 51 + 52 + **Step 3: Commit** 53 + 54 + ```bash 55 + git add server/src/event_handler.gleam 56 + git commit -m "feat: export timestamp helpers from event_handler" 57 + ``` 58 + 59 + --- 60 + 61 + ## Task 2: Add imports to mutations.gleam 62 + 63 + **Files:** 64 + - Modify: `server/src/graphql/lexicon/mutations.gleam:1-25` (add imports) 65 + 66 + **Step 1: Add event_handler and pubsub imports** 67 + 68 + In `server/src/graphql/lexicon/mutations.gleam`, after line 21 (`import honk/errors`), add: 69 + 70 + ```gleam 71 + import event_handler 72 + import pubsub 73 + ``` 74 + 75 + **Step 2: Verify it compiles** 76 + 77 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam build` 78 + Expected: Build succeeds 79 + 80 + **Step 3: Commit** 81 + 82 + ```bash 83 + git add server/src/graphql/lexicon/mutations.gleam 84 + git commit -m "feat: add event_handler and pubsub imports to mutations" 85 + ``` 86 + 87 + --- 88 + 89 + ## Task 3: Publish event after create mutation 90 + 91 + **Files:** 92 + - Modify: `server/src/graphql/lexicon/mutations.gleam:490-513` 93 + 94 + **Step 1: Add pubsub.publish after records.insert** 95 + 96 + Replace lines 490-513 with: 97 + 98 + ```gleam 99 + // Index the created record in the database 100 + use _ <- result.try( 101 + records.insert( 102 + ctx.db, 103 + uri, 104 + cid, 105 + auth.user_info.did, 106 + collection, 107 + record_json_string, 108 + ) 109 + |> result.map_error(fn(_) { "Failed to index record in database" }), 110 + ) 111 + 112 + // Publish event for GraphQL subscriptions 113 + pubsub.publish(pubsub.RecordEvent( 114 + uri: uri, 115 + cid: cid, 116 + did: auth.user_info.did, 117 + collection: collection, 118 + value: record_json_string, 119 + indexed_at: event_handler.current_iso8601(), 120 + operation: pubsub.Create, 121 + )) 122 + 123 + Ok( 124 + value.Object([ 125 + #("uri", value.String(uri)), 126 + #("cid", value.String(cid)), 127 + #("did", value.String(auth.user_info.did)), 128 + #("collection", value.String(collection)), 129 + #("indexedAt", value.String("")), 130 + #("value", input), 131 + ]), 132 + ) 133 + } 134 + } 135 + ``` 136 + 137 + **Step 2: Verify it compiles** 138 + 139 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam build` 140 + Expected: Build succeeds 141 + 142 + **Step 3: Commit** 143 + 144 + ```bash 145 + git add server/src/graphql/lexicon/mutations.gleam 146 + git commit -m "feat: publish subscription event after create mutation" 147 + ``` 148 + 149 + --- 150 + 151 + ## Task 4: Publish event after update mutation 152 + 153 + **Files:** 154 + - Modify: `server/src/graphql/lexicon/mutations.gleam:610-626` 155 + 156 + **Step 1: Add pubsub.publish after records.update** 157 + 158 + Replace lines 610-626 with: 159 + 160 + ```gleam 161 + // Update the record in the database 162 + use _ <- result.try( 163 + records.update(ctx.db, uri, cid, record_json_string) 164 + |> result.map_error(fn(_) { "Failed to update record in database" }), 165 + ) 166 + 167 + // Publish event for GraphQL subscriptions 168 + pubsub.publish(pubsub.RecordEvent( 169 + uri: uri, 170 + cid: cid, 171 + did: auth.user_info.did, 172 + collection: collection, 173 + value: record_json_string, 174 + indexed_at: event_handler.current_iso8601(), 175 + operation: pubsub.Update, 176 + )) 177 + 178 + Ok( 179 + value.Object([ 180 + #("uri", value.String(uri)), 181 + #("cid", value.String(cid)), 182 + #("did", value.String(auth.user_info.did)), 183 + #("collection", value.String(collection)), 184 + #("indexedAt", value.String("")), 185 + #("value", input), 186 + ]), 187 + ) 188 + } 189 + } 190 + ``` 191 + 192 + **Step 2: Verify it compiles** 193 + 194 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam build` 195 + Expected: Build succeeds 196 + 197 + **Step 3: Commit** 198 + 199 + ```bash 200 + git add server/src/graphql/lexicon/mutations.gleam 201 + git commit -m "feat: publish subscription event after update mutation" 202 + ``` 203 + 204 + --- 205 + 206 + ## Task 5: Publish event after delete mutation 207 + 208 + **Files:** 209 + - Modify: `server/src/graphql/lexicon/mutations.gleam:678-685` 210 + 211 + **Step 1: Add pubsub.publish after records.delete** 212 + 213 + Replace lines 678-685 with: 214 + 215 + ```gleam 216 + // Delete the record from the database 217 + use _ <- result.try( 218 + records.delete(ctx.db, uri) 219 + |> result.map_error(fn(_) { "Failed to delete record from database" }), 220 + ) 221 + 222 + // Publish event for GraphQL subscriptions 223 + pubsub.publish(pubsub.RecordEvent( 224 + uri: uri, 225 + cid: "", 226 + did: auth.user_info.did, 227 + collection: collection, 228 + value: "", 229 + indexed_at: event_handler.current_iso8601(), 230 + operation: pubsub.Delete, 231 + )) 232 + 233 + Ok(value.Object([#("uri", value.String(uri))])) 234 + } 235 + } 236 + ``` 237 + 238 + **Step 2: Verify it compiles** 239 + 240 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam build` 241 + Expected: Build succeeds 242 + 243 + **Step 3: Commit** 244 + 245 + ```bash 246 + git add server/src/graphql/lexicon/mutations.gleam 247 + git commit -m "feat: publish subscription event after delete mutation" 248 + ``` 249 + 250 + --- 251 + 252 + ## Task 6: Manual test 253 + 254 + **Step 1: Start the server** 255 + 256 + Run: `cd /Users/chadmiller/code/quickslice/server && gleam run` 257 + 258 + **Step 2: Test subscription receives mutation events** 259 + 260 + 1. Open GraphQL playground in browser 261 + 2. Start a subscription for a collection 262 + 3. In another tab, run a create mutation 263 + 4. Verify the subscription receives the event immediately (not delayed by Jetstream round-trip) 264 + 265 + **Step 3: Final commit (if any fixes needed)** 266 + 267 + If tests reveal issues, fix and commit with appropriate message.