(event: TableEvent)
| 232 | * cell-write. |
| 233 | */ |
| 234 | export async function appendTableEvent(event: TableEvent): Promise<TableEventEntry | null> { |
| 235 | const redis = getRedisClient() |
| 236 | if (!redis) { |
| 237 | if (canUseMemoryBuffer()) { |
| 238 | try { |
| 239 | return appendMemory(event) |
| 240 | } catch (error) { |
| 241 | logger.warn('appendTableEvent: memory append failed', { |
| 242 | tableId: event.tableId, |
| 243 | error: toError(error).message, |
| 244 | }) |
| 245 | return null |
| 246 | } |
| 247 | } |
| 248 | return null |
| 249 | } |
| 250 | try { |
| 251 | // Build the entry JSON in two halves so Lua can splice the new eventId |
| 252 | // between them without us needing a round-trip just to mint the id first. |
| 253 | const tail = `,"tableId":${JSON.stringify(event.tableId)},"event":${JSON.stringify(event)}}` |
| 254 | const head = `{"eventId":` |
| 255 | const result = await redis.eval( |
| 256 | APPEND_EVENT_SCRIPT, |
| 257 | 3, |
| 258 | getEventsKey(event.tableId), |
| 259 | getSeqKey(event.tableId), |
| 260 | getMetaKey(event.tableId), |
| 261 | TABLE_EVENT_TTL_SECONDS, |
| 262 | TABLE_EVENT_CAP, |
| 263 | new Date().toISOString(), |
| 264 | head, |
| 265 | tail |
| 266 | ) |
| 267 | const eventId = typeof result === 'number' ? result : Number(result) |
| 268 | if (!Number.isFinite(eventId)) return null |
| 269 | return { eventId, tableId: event.tableId, event } |
| 270 | } catch (error) { |
| 271 | logger.warn('appendTableEvent: Redis append failed', { |
| 272 | tableId: event.tableId, |
| 273 | error: toError(error).message, |
| 274 | }) |
| 275 | return null |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | /** |
| 280 | * Read events for a table where eventId > afterEventId. Returns 'pruned' if |
no test coverage detected