experimental-a2 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/ai-server.d.ts +4 -5
  3. package/dist/ai-server.d.ts.map +1 -1
  4. package/dist/ai-server.js +8 -7
  5. package/dist/ai-server.js.map +1 -1
  6. package/dist/ai.d.ts +334 -2
  7. package/dist/ai.d.ts.map +1 -0
  8. package/dist/ai.js +1 -1
  9. package/dist/client.d.ts +202 -2
  10. package/dist/client.d.ts.map +1 -0
  11. package/dist/client.js +1025 -1
  12. package/dist/client.js.map +1 -0
  13. package/dist/errors-BQuJpe82.js.map +1 -1
  14. package/dist/index.d.ts +22 -3
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/{internal-DstsI6Re.js → internal-DRXJ56EI.js} +5 -28
  17. package/dist/internal-DRXJ56EI.js.map +1 -0
  18. package/dist/react.d.ts +1 -1
  19. package/dist/react.js +1 -1
  20. package/dist/scheduler-qstash.d.ts +3 -3
  21. package/dist/scheduler-qstash.js +4 -5
  22. package/dist/scheduler-qstash.js.map +1 -1
  23. package/dist/scheduler-vercel.d.ts +2 -2
  24. package/dist/scheduler-vercel.js +4 -4
  25. package/dist/scheduler-vercel.js.map +1 -1
  26. package/dist/{server-Duw6MVlB.js → server-286j79Mt.js} +708 -79
  27. package/dist/server-286j79Mt.js.map +1 -0
  28. package/dist/{server-DpvjhdoE.d.ts → server-DgXmORIq.d.ts} +67 -49
  29. package/dist/server-DgXmORIq.d.ts.map +1 -0
  30. package/dist/server.d.ts +3 -3
  31. package/dist/server.js +1 -1
  32. package/dist/store-N8PXxDAS.js.map +1 -1
  33. package/dist/{store-DysUkTH3.d.ts → store-flRz1OWh.d.ts} +2 -57
  34. package/dist/store-flRz1OWh.d.ts.map +1 -0
  35. package/dist/store-memory.d.ts +1 -1
  36. package/dist/store-memory.d.ts.map +1 -1
  37. package/dist/store-memory.js +1 -59
  38. package/dist/store-memory.js.map +1 -1
  39. package/dist/{store-polling-dSeLxzfb.js → store-polling-6DW7F1DT.js} +2 -2
  40. package/dist/{store-polling-dSeLxzfb.js.map → store-polling-6DW7F1DT.js.map} +1 -1
  41. package/dist/store-postgres.d.ts +1 -1
  42. package/dist/store-postgres.js +1 -81
  43. package/dist/store-postgres.js.map +1 -1
  44. package/dist/{store-redis-core-BFLwz0Wj.js → store-redis-core-DEYO8Ryv.js} +48 -134
  45. package/dist/store-redis-core-DEYO8Ryv.js.map +1 -0
  46. package/dist/store-redis-http.d.ts +1 -1
  47. package/dist/store-redis-http.js +2 -3
  48. package/dist/store-redis-http.js.map +1 -1
  49. package/dist/store-redis.d.ts +1 -1
  50. package/dist/store-redis.js +3 -4
  51. package/dist/store-redis.js.map +1 -1
  52. package/dist/store-sqlite.d.ts +1 -1
  53. package/dist/store-sqlite.d.ts.map +1 -1
  54. package/dist/store-sqlite.js +1 -72
  55. package/dist/store-sqlite.js.map +1 -1
  56. package/dist/{wire-BFQmSJ-9.js → wire-B6te_wns.js} +4 -3
  57. package/dist/wire-B6te_wns.js.map +1 -0
  58. package/docs/guides/03-react.mdx +20 -28
  59. package/docs/guides/05-production.mdx +9 -11
  60. package/docs/guides/06-ai-agents.mdx +8 -14
  61. package/docs/guides/09-presence.mdx +14 -19
  62. package/docs/guides/10-transports.mdx +104 -86
  63. package/docs/reference/01-api.mdx +143 -277
  64. package/docs/reference/02-errors.mdx +5 -7
  65. package/package.json +1 -14
  66. package/src/ai-server.ts +9 -5
  67. package/src/client.ts +2 -2
  68. package/src/errors.ts +1 -0
  69. package/src/internal.ts +3 -62
  70. package/src/push-envelope.ts +24 -21
  71. package/src/scheduler-qstash.ts +3 -3
  72. package/src/scheduler-vercel.ts +2 -2
  73. package/src/server-fetch.ts +344 -0
  74. package/src/server.ts +73 -225
  75. package/src/session-socket.ts +36 -20
  76. package/src/sse.ts +2 -2
  77. package/src/store-memory.ts +0 -81
  78. package/src/store-postgres.ts +0 -100
  79. package/src/store-redis-core.ts +47 -211
  80. package/src/store-redis-http.ts +0 -1
  81. package/src/store-redis.ts +0 -1
  82. package/src/store-sqlite.ts +0 -119
  83. package/src/store.ts +0 -60
  84. package/src/wire.ts +2 -1
  85. package/dist/ai-D_PGS-JR.d.ts +0 -334
  86. package/dist/ai-D_PGS-JR.d.ts.map +0 -1
  87. package/dist/cli-B3VuxoDe.js +0 -599
  88. package/dist/cli-B3VuxoDe.js.map +0 -1
  89. package/dist/cli-bin.d.ts +0 -1
  90. package/dist/cli-bin.js +0 -7
  91. package/dist/cli-bin.js.map +0 -1
  92. package/dist/cli.d.ts +0 -20
  93. package/dist/cli.d.ts.map +0 -1
  94. package/dist/cli.js +0 -2
  95. package/dist/client-CdMqi7mC.d.ts +0 -202
  96. package/dist/client-CdMqi7mC.d.ts.map +0 -1
  97. package/dist/client-Dj5d3SP_.js +0 -1026
  98. package/dist/client-Dj5d3SP_.js.map +0 -1
  99. package/dist/devtools-J_jZ2vQf.d.ts +0 -152
  100. package/dist/devtools-J_jZ2vQf.d.ts.map +0 -1
  101. package/dist/devtools-kJJaORn-.js +0 -340
  102. package/dist/devtools-kJJaORn-.js.map +0 -1
  103. package/dist/devtools-server.browser.d.ts +0 -1
  104. package/dist/devtools-server.browser.js +0 -6
  105. package/dist/devtools-server.browser.js.map +0 -1
  106. package/dist/devtools-server.d.ts +0 -23
  107. package/dist/devtools-server.d.ts.map +0 -1
  108. package/dist/devtools-server.js +0 -1270
  109. package/dist/devtools-server.js.map +0 -1
  110. package/dist/devtools.d.ts +0 -2
  111. package/dist/devtools.js +0 -2
  112. package/dist/errors-W6nwJ-fm.d.ts +0 -21
  113. package/dist/errors-W6nwJ-fm.d.ts.map +0 -1
  114. package/dist/http.d.ts +0 -151
  115. package/dist/http.d.ts.map +0 -1
  116. package/dist/http.js +0 -706
  117. package/dist/http.js.map +0 -1
  118. package/dist/inspection-DaxB5jM2.js +0 -13
  119. package/dist/inspection-DaxB5jM2.js.map +0 -1
  120. package/dist/internal-DstsI6Re.js.map +0 -1
  121. package/dist/platform-B4TnJtWu.js +0 -34
  122. package/dist/platform-B4TnJtWu.js.map +0 -1
  123. package/dist/server-DpvjhdoE.d.ts.map +0 -1
  124. package/dist/server-Duw6MVlB.js.map +0 -1
  125. package/dist/store-DysUkTH3.d.ts.map +0 -1
  126. package/dist/store-redis-core-BFLwz0Wj.js.map +0 -1
  127. package/dist/testing.browser.d.ts +0 -1
  128. package/dist/testing.browser.js +0 -6
  129. package/dist/testing.browser.js.map +0 -1
  130. package/dist/testing.d.ts +0 -32
  131. package/dist/testing.d.ts.map +0 -1
  132. package/dist/testing.js +0 -103
  133. package/dist/testing.js.map +0 -1
  134. package/dist/wire-BFQmSJ-9.js.map +0 -1
  135. package/docs/guides/07-devtools.mdx +0 -229
  136. package/src/cli-bin.ts +0 -5
  137. package/src/cli.ts +0 -1046
  138. package/src/devtools-app.ts +0 -989
  139. package/src/devtools-server.browser.ts +0 -5
  140. package/src/devtools-server.ts +0 -604
  141. package/src/devtools.ts +0 -716
  142. package/src/http.ts +0 -394
  143. package/src/inspection.ts +0 -39
  144. package/src/testing.browser.ts +0 -5
  145. package/src/testing.ts +0 -185
@@ -1086,106 +1086,6 @@ export function postgres(options: PostgresStoreOptions = {}): PostgresStore {
1086
1086
  },
1087
1087
  },
1088
1088
 
1089
- inspect: {
1090
- async listSessions(inspectionOptions) {
1091
- return wrap(async () => {
1092
- const c = await client()
1093
- const conditions = ['left(session_id, length($1)) = $1']
1094
- const params: unknown[] = [inspectionOptions.prefix]
1095
- if (inspectionOptions.cursor !== undefined) {
1096
- params.push(inspectionOptions.cursor)
1097
- conditions.push(`session_id > $${params.length}`)
1098
- }
1099
- params.push(inspectionOptions.limit + 1)
1100
- const { rows } = await c.query(
1101
- `select
1102
- session_id,
1103
- count(*) as event_count,
1104
- count(*) filter (where processed_at is null and failed_at is null) as pending_count,
1105
- count(*) filter (where failed_at is not null) as failed_count,
1106
- sum(attempt_count) as attempt_count,
1107
- sum(failure_count) as failure_count,
1108
- min(created_at) as first_event_at,
1109
- max(greatest(
1110
- created_at,
1111
- coalesce(first_claimed_at, 0),
1112
- coalesce(last_claimed_at, 0),
1113
- coalesce(last_failed_at, 0),
1114
- coalesce(processed_at, 0),
1115
- coalesce(failed_at, 0)
1116
- )) as updated_at
1117
- from a2_events
1118
- where ${conditions.join(' and ')}
1119
- group by session_id
1120
- order by session_id
1121
- limit $${params.length}`,
1122
- params,
1123
- )
1124
- const hasMore = rows.length > inspectionOptions.limit
1125
- const visible = rows.slice(0, inspectionOptions.limit)
1126
- return {
1127
- sessions: visible.map((row) => ({
1128
- sessionId: row['session_id'] as string,
1129
- eventCount: asNumber(row['event_count']),
1130
- pendingCount: asNumber(row['pending_count']),
1131
- failedCount: asNumber(row['failed_count']),
1132
- attemptCount: asNumber(row['attempt_count']),
1133
- failureCount: asNumber(row['failure_count']),
1134
- firstEventAt: new Date(asNumber(row['first_event_at'])),
1135
- updatedAt: new Date(asNumber(row['updated_at'])),
1136
- })),
1137
- cursor: hasMore
1138
- ? ((visible.at(-1)?.['session_id'] as string | undefined) ?? null)
1139
- : null,
1140
- }
1141
- })
1142
- },
1143
- async readEvents(sessionId, pageOptions) {
1144
- return wrap(async () => {
1145
- const c = await client()
1146
- const currentFrontier = asNumber(
1147
- (
1148
- await c.query(
1149
- 'select coalesce(max(idx), 0) as frontier from a2_events where session_id = $1',
1150
- [sessionId],
1151
- )
1152
- ).rows[0]?.['frontier'] ?? 0,
1153
- )
1154
- const throughIndex = Math.min(
1155
- pageOptions.throughIndex ?? currentFrontier,
1156
- currentFrontier,
1157
- )
1158
- const { rows } = await c.query(
1159
- `select * from a2_events
1160
- where session_id = $1 and idx > $2 and idx <= $3
1161
- order by idx limit $4`,
1162
- [
1163
- sessionId,
1164
- pageOptions.afterIndex,
1165
- throughIndex,
1166
- pageOptions.limit,
1167
- ],
1168
- )
1169
- return { events: rows.map(toStored), throughIndex }
1170
- })
1171
- },
1172
- async listSnapshots(sessionId) {
1173
- return wrap(async () => {
1174
- const c = await client()
1175
- const { rows } = await c.query(
1176
- `select reducer_name, up_to_index, updated_at
1177
- from a2_snapshots where session_id = $1 order by reducer_name`,
1178
- [sessionId],
1179
- )
1180
- return rows.map((row) => ({
1181
- reducerName: row['reducer_name'] as string,
1182
- index: asNumber(row['up_to_index']),
1183
- updatedAt: new Date(asNumber(row['updated_at'])),
1184
- }))
1185
- })
1186
- },
1187
- },
1188
-
1189
1089
  stream(sessionId, opts) {
1190
1090
  const readAfter = (afterIndex: number): Promise<StoredEvent[]> =>
1191
1091
  wrap(async () => {
@@ -23,7 +23,6 @@ import { nullProtoRecord } from './internal.ts'
23
23
  import { decodeReturnedEventIds } from './store-codec.ts'
24
24
  import type {
25
25
  A2Store,
26
- A2StoreInspection,
27
26
  Clock,
28
27
  CompleteAttemptResult,
29
28
  Event,
@@ -56,11 +55,7 @@ export type RedisStoreCoreOptions = {
56
55
  notify?: (sessionId: string, count: number) => void
57
56
  }
58
57
 
59
- export type RedisStoreCore = Omit<
60
- A2Store,
61
- 'stream' | 'inspect' | 'presence'
62
- > & {
63
- inspect: A2StoreInspection
58
+ export type RedisStoreCore = Omit<A2Store, 'stream' | 'presence'> & {
64
59
  /**
65
60
  * The storage half of the presence capability, shared by both
66
61
  * transports. `set` returns the applied patch — null when every
@@ -98,8 +93,8 @@ const READ_PAGE_SIZE = 256
98
93
  // ── Lua scripts (each runs atomically) ───────────────────────────────
99
94
 
100
95
  /**
101
- * KEYS: store stream, counter, global id hash, session index, metadata,
102
- * pending, ready, watched.
96
+ * KEYS: store stream, counter, global id hash, metadata, pending, ready,
97
+ * watched.
103
98
  * ARGV: sessionId, nowMs, n, then per event:
104
99
  * id, type, payloadJson, causeJson, lane, settled.
105
100
  * Returns ['empty', hasPending] | ['ok', firstIndex, hasPending, watched] |
@@ -111,24 +106,24 @@ local function lane_field(lane, suffix)
111
106
  return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix
112
107
  end
113
108
  local function enqueue(field, idx, lane)
114
- redis.call('ZADD', KEYS[6], idx, field)
109
+ redis.call('ZADD', KEYS[5], idx, field)
115
110
  if lane == '' then
116
- redis.call('ZADD', KEYS[7], idx, field)
111
+ redis.call('ZADD', KEYS[6], idx, field)
117
112
  return
118
113
  end
119
114
  local tail_key = lane_field(lane, 'tail')
120
- local tail = redis.call('HGET', KEYS[5], tail_key)
115
+ local tail = redis.call('HGET', KEYS[4], tail_key)
121
116
  if tail then
122
- redis.call('HSET', KEYS[5], tail .. ':next', field)
117
+ redis.call('HSET', KEYS[4], tail .. ':next', field)
123
118
  else
124
- redis.call('HSET', KEYS[5], lane_field(lane, 'head'), field)
125
- redis.call('ZADD', KEYS[7], idx, field)
119
+ redis.call('HSET', KEYS[4], lane_field(lane, 'head'), field)
120
+ redis.call('ZADD', KEYS[6], idx, field)
126
121
  end
127
- redis.call('HSET', KEYS[5], tail_key, field)
122
+ redis.call('HSET', KEYS[4], tail_key, field)
128
123
  end
129
124
  local n = tonumber(ARGV[3])
130
125
  local base = tonumber(redis.call('GET', KEYS[2]) or '0')
131
- local has_pending = redis.call('ZCARD', KEYS[6]) > 0 and '1' or '0'
126
+ local has_pending = redis.call('ZCARD', KEYS[5]) > 0 and '1' or '0'
132
127
  if n == 0 then return { 'empty', has_pending } end
133
128
  local dups = {}
134
129
  for i = 1, n do
@@ -155,9 +150,9 @@ for i = 1, n do
155
150
  return { 'no_parent', tostring(parsed.index) }
156
151
  end
157
152
  local field = tostring(pidx)
158
- local dispatches = tonumber(redis.call('HGET', KEYS[5], field .. ':d') or '0')
153
+ local dispatches = tonumber(redis.call('HGET', KEYS[4], field .. ':d') or '0')
159
154
  if dispatches ~= tonumber(parsed.attempt) or
160
- redis.call('HEXISTS', KEYS[5], field .. ':f') == 1 then
155
+ redis.call('HEXISTS', KEYS[4], field .. ':f') == 1 then
161
156
  return { 'superseded', field }
162
157
  end
163
158
  end
@@ -174,18 +169,17 @@ for i = 1, n do
174
169
  'id', id, 'type', typ, 'payload', payload, 'created_at', ARGV[2],
175
170
  'cause', cause, 'lane', lane)
176
171
  redis.call('HSET', KEYS[3], id, cjson.encode({ ARGV[1], idx }))
177
- redis.call('HSET', KEYS[5], tostring(idx) .. ':l', lane)
172
+ redis.call('HSET', KEYS[4], tostring(idx) .. ':l', lane)
178
173
  if settled == '1' then
179
- redis.call('HSET', KEYS[5], tostring(idx) .. ':p', ARGV[2])
174
+ redis.call('HSET', KEYS[4], tostring(idx) .. ':p', ARGV[2])
180
175
  else
181
176
  enqueue(tostring(idx), idx, lane)
182
177
  end
183
178
  end
184
179
  redis.call('SET', KEYS[2], tostring(base + n))
185
- redis.call('ZADD', KEYS[4], 0, ARGV[1])
186
- has_pending = redis.call('ZCARD', KEYS[6]) > 0 and '1' or '0'
180
+ has_pending = redis.call('ZCARD', KEYS[5]) > 0 and '1' or '0'
187
181
  return { 'ok', tostring(base + 1), has_pending,
188
- tostring(redis.call('EXISTS', KEYS[8])) }
182
+ tostring(redis.call('EXISTS', KEYS[7])) }
189
183
  `
190
184
 
191
185
  /** KEYS: counter, meta, pending, ready. ARGV: idx, attempt, error, maxFailures, nowMs. */
@@ -336,7 +330,7 @@ return result
336
330
  `
337
331
 
338
332
  /**
339
- * KEYS: store, counter, ids, session index, meta, pending, ready, watched.
333
+ * KEYS: store, counter, ids, meta, pending, ready, watched.
340
334
  * ARGV: sessionId, nowMs, parent index, attempt, n, then per child:
341
335
  * id, type, payloadJson, lane, settled.
342
336
  */
@@ -345,52 +339,52 @@ local function lane_field(lane, suffix)
345
339
  return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix
346
340
  end
347
341
  local function settle(field)
342
+ redis.call('ZREM', KEYS[5], field)
348
343
  redis.call('ZREM', KEYS[6], field)
349
- redis.call('ZREM', KEYS[7], field)
350
- local lane = redis.call('HGET', KEYS[5], field .. ':l') or ''
344
+ local lane = redis.call('HGET', KEYS[4], field .. ':l') or ''
351
345
  if lane == '' then return end
352
346
  local head_key = lane_field(lane, 'head')
353
- if redis.call('HGET', KEYS[5], head_key) ~= field then return end
354
- local next_field = redis.call('HGET', KEYS[5], field .. ':next')
355
- while next_field and redis.call('HEXISTS', KEYS[5], next_field .. ':p') == 1 do
356
- next_field = redis.call('HGET', KEYS[5], next_field .. ':next')
347
+ if redis.call('HGET', KEYS[4], head_key) ~= field then return end
348
+ local next_field = redis.call('HGET', KEYS[4], field .. ':next')
349
+ while next_field and redis.call('HEXISTS', KEYS[4], next_field .. ':p') == 1 do
350
+ next_field = redis.call('HGET', KEYS[4], next_field .. ':next')
357
351
  end
358
352
  if next_field then
359
- redis.call('HSET', KEYS[5], head_key, next_field)
360
- if redis.call('HEXISTS', KEYS[5], next_field .. ':f') == 0 then
361
- redis.call('ZADD', KEYS[7], tonumber(next_field), next_field)
353
+ redis.call('HSET', KEYS[4], head_key, next_field)
354
+ if redis.call('HEXISTS', KEYS[4], next_field .. ':f') == 0 then
355
+ redis.call('ZADD', KEYS[6], tonumber(next_field), next_field)
362
356
  end
363
357
  else
364
- redis.call('HDEL', KEYS[5], head_key, lane_field(lane, 'tail'))
358
+ redis.call('HDEL', KEYS[4], head_key, lane_field(lane, 'tail'))
365
359
  end
366
360
  end
367
361
  local function enqueue(field, idx, lane)
368
- redis.call('ZADD', KEYS[6], idx, field)
362
+ redis.call('ZADD', KEYS[5], idx, field)
369
363
  if lane == '' then
370
- redis.call('ZADD', KEYS[7], idx, field)
364
+ redis.call('ZADD', KEYS[6], idx, field)
371
365
  return
372
366
  end
373
367
  local tail_key = lane_field(lane, 'tail')
374
- local tail = redis.call('HGET', KEYS[5], tail_key)
368
+ local tail = redis.call('HGET', KEYS[4], tail_key)
375
369
  if tail then
376
- redis.call('HSET', KEYS[5], tail .. ':next', field)
370
+ redis.call('HSET', KEYS[4], tail .. ':next', field)
377
371
  else
378
- redis.call('HSET', KEYS[5], lane_field(lane, 'head'), field)
379
- redis.call('ZADD', KEYS[7], idx, field)
372
+ redis.call('HSET', KEYS[4], lane_field(lane, 'head'), field)
373
+ redis.call('ZADD', KEYS[6], idx, field)
380
374
  end
381
- redis.call('HSET', KEYS[5], tail_key, field)
375
+ redis.call('HSET', KEYS[4], tail_key, field)
382
376
  end
383
377
  local parent = ARGV[3]
384
378
  local attempt = tonumber(ARGV[4])
385
379
  local n = tonumber(ARGV[5])
386
380
  local total = tonumber(redis.call('GET', KEYS[2]) or '0')
387
381
  if tonumber(parent) < 1 or tonumber(parent) > total then return { 'missing' } end
388
- local dispatches = tonumber(redis.call('HGET', KEYS[5], parent .. ':d') or '0')
389
- local processed = redis.call('HGET', KEYS[5], parent .. ':p')
390
- local processed_attempt = tonumber(redis.call('HGET', KEYS[5], parent .. ':pa') or '0')
382
+ local dispatches = tonumber(redis.call('HGET', KEYS[4], parent .. ':d') or '0')
383
+ local processed = redis.call('HGET', KEYS[4], parent .. ':p')
384
+ local processed_attempt = tonumber(redis.call('HGET', KEYS[4], parent .. ':pa') or '0')
391
385
  if processed then
392
386
  if processed_attempt ~= attempt then return { 'superseded' } end
393
- local raw_recorded_ids = redis.call('HGET', KEYS[5], parent .. ':ri')
387
+ local raw_recorded_ids = redis.call('HGET', KEYS[4], parent .. ':ri')
394
388
  if not raw_recorded_ids then return { 'partial' } end
395
389
  local recorded_ids = cjson.decode(raw_recorded_ids)
396
390
  if #recorded_ids ~= n then return { 'partial' } end
@@ -420,9 +414,9 @@ if processed then
420
414
  return result
421
415
  end
422
416
  if dispatches ~= attempt then return { 'superseded' } end
423
- if redis.call('HEXISTS', KEYS[5], parent .. ':ch') == 0 or
424
- redis.call('HEXISTS', KEYS[5], parent .. ':f') == 1 or
425
- tonumber(redis.call('HGET', KEYS[5], parent .. ':lfa') or '0') == attempt then
417
+ if redis.call('HEXISTS', KEYS[4], parent .. ':ch') == 0 or
418
+ redis.call('HEXISTS', KEYS[4], parent .. ':f') == 1 or
419
+ tonumber(redis.call('HGET', KEYS[4], parent .. ':lfa') or '0') == attempt then
426
420
  return { 'superseded' }
427
421
  end
428
422
  for i = 1, n do
@@ -433,11 +427,11 @@ local returned_ids = {}
433
427
  for i = 1, n do returned_ids[i] = ARGV[5 + (i - 1) * 5 + 1] end
434
428
  local returned_json = '[]'
435
429
  if n > 0 then returned_json = cjson.encode(returned_ids) end
436
- redis.call('HSET', KEYS[5],
430
+ redis.call('HSET', KEYS[4],
437
431
  parent .. ':p', ARGV[2],
438
432
  parent .. ':pa', ARGV[4],
439
433
  parent .. ':ri', returned_json)
440
- redis.call('HDEL', KEYS[5], parent .. ':ch', parent .. ':ce')
434
+ redis.call('HDEL', KEYS[4], parent .. ':ch', parent .. ':ce')
441
435
  settle(parent)
442
436
  local cause = cjson.encode({ index = tonumber(parent), attempt = attempt })
443
437
  for i = 1, n do
@@ -451,17 +445,16 @@ for i = 1, n do
451
445
  'id', id, 'type', typ, 'payload', payload, 'created_at', ARGV[2],
452
446
  'cause', cause, 'lane', lane)
453
447
  redis.call('HSET', KEYS[3], id, cjson.encode({ ARGV[1], idx }))
454
- redis.call('HSET', KEYS[5], tostring(idx) .. ':l', lane)
448
+ redis.call('HSET', KEYS[4], tostring(idx) .. ':l', lane)
455
449
  if settled == '1' then
456
- redis.call('HSET', KEYS[5], tostring(idx) .. ':p', ARGV[2])
450
+ redis.call('HSET', KEYS[4], tostring(idx) .. ':p', ARGV[2])
457
451
  else
458
452
  enqueue(tostring(idx), idx, lane)
459
453
  end
460
454
  end
461
455
  redis.call('SET', KEYS[2], tostring(total + n))
462
- redis.call('ZADD', KEYS[4], 0, ARGV[1])
463
456
  return { 'ok', tostring(total + 1),
464
- tostring(redis.call('EXISTS', KEYS[8])) }
457
+ tostring(redis.call('EXISTS', KEYS[7])) }
465
458
  `
466
459
 
467
460
  /** KEYS: snapshot. ARGV: upToIndex, json. Guarded: never move backward. */
@@ -579,14 +572,6 @@ const toEvent = (sessionId: string, entry: StreamEntry): Event => {
579
572
 
580
573
  const json = (value: unknown): string => JSON.stringify(value) ?? 'null'
581
574
 
582
- const escapeGlob = (value: string): string =>
583
- value
584
- .replaceAll('\\', '\\\\')
585
- .replaceAll('*', '\\*')
586
- .replaceAll('?', '\\?')
587
- .replaceAll('[', '\\[')
588
- .replaceAll(']', '\\]')
589
-
590
575
  const toCause = (raw: string | undefined): EventCause | null => {
591
576
  if (raw === '' || raw === undefined) return null
592
577
  const value = JSON.parse(raw) as {
@@ -699,7 +684,6 @@ export function createRedisStoreCore(
699
684
  const watchedKey = (s: string): string => `${prefix}:${s}:watched`
700
685
  const presenceKey = (s: string): string => `${prefix}:${s}:presence`
701
686
  const idsKey = `${prefix}:ids`
702
- const sessionsKey = `${prefix}:sessions`
703
687
 
704
688
  const evalScript = (
705
689
  script: string,
@@ -924,7 +908,6 @@ export function createRedisStoreCore(
924
908
  storeKey(sessionId),
925
909
  countKey(sessionId),
926
910
  idsKey,
927
- sessionsKey,
928
911
  metaKey(sessionId),
929
912
  pendingKey(sessionId),
930
913
  readyKey(sessionId),
@@ -1081,7 +1064,6 @@ export function createRedisStoreCore(
1081
1064
  storeKey(sessionId),
1082
1065
  countKey(sessionId),
1083
1066
  idsKey,
1084
- sessionsKey,
1085
1067
  metaKey(sessionId),
1086
1068
  pendingKey(sessionId),
1087
1069
  readyKey(sessionId),
@@ -1325,152 +1307,6 @@ export function createRedisStoreCore(
1325
1307
  },
1326
1308
  },
1327
1309
 
1328
- inspect: {
1329
- async listSessions(inspectionOptions) {
1330
- return wrap(async () => {
1331
- const minimum =
1332
- inspectionOptions.cursor === undefined
1333
- ? `[${inspectionOptions.prefix}`
1334
- : `(${inspectionOptions.cursor}`
1335
- const candidates = (await call(
1336
- 'ZRANGEBYLEX',
1337
- sessionsKey,
1338
- minimum,
1339
- '+',
1340
- 'LIMIT',
1341
- 0,
1342
- inspectionOptions.limit + 1,
1343
- )) as string[]
1344
- const sessionIds = candidates
1345
- .filter((id) => id.startsWith(inspectionOptions.prefix))
1346
- .slice(0, inspectionOptions.limit)
1347
-
1348
- const sessions = await Promise.all(
1349
- sessionIds.map(async (sessionId) => {
1350
- const rows = await readRange(sessionId, 0)
1351
- const first = rows[0]!
1352
- let updatedAt = first.createdAt
1353
- let pendingCount = 0
1354
- let failedCount = 0
1355
- let attemptCount = 0
1356
- let failureCount = 0
1357
- for (const row of rows) {
1358
- attemptCount += row.attemptCount
1359
- failureCount += row.failureCount
1360
- if (row.processedAt === null && row.failedAt === null) {
1361
- pendingCount += 1
1362
- }
1363
- if (row.failedAt !== null) failedCount += 1
1364
- for (const timestamp of [
1365
- row.createdAt,
1366
- row.firstClaimedAt,
1367
- row.lastClaimedAt,
1368
- row.lastFailedAt,
1369
- row.processedAt,
1370
- row.failedAt,
1371
- ]) {
1372
- if (timestamp && timestamp > updatedAt) updatedAt = timestamp
1373
- }
1374
- }
1375
- return {
1376
- sessionId,
1377
- eventCount: rows.length,
1378
- pendingCount,
1379
- failedCount,
1380
- attemptCount,
1381
- failureCount,
1382
- firstEventAt: first.createdAt,
1383
- updatedAt,
1384
- }
1385
- }),
1386
- )
1387
- return {
1388
- sessions: sessions.toSorted((a, b) =>
1389
- a.sessionId.localeCompare(b.sessionId),
1390
- ),
1391
- cursor:
1392
- candidates.length > inspectionOptions.limit &&
1393
- candidates[inspectionOptions.limit]?.startsWith(
1394
- inspectionOptions.prefix,
1395
- )
1396
- ? (sessionIds.at(-1) ?? null)
1397
- : null,
1398
- }
1399
- })
1400
- },
1401
- async readEvents(sessionId, pageOptions) {
1402
- return wrap(async () => {
1403
- const currentFrontier = await readFrontier(sessionId)
1404
- const throughIndex = Math.min(
1405
- pageOptions.throughIndex ?? currentFrontier,
1406
- currentFrontier,
1407
- )
1408
- const events = await readRangePage(
1409
- sessionId,
1410
- pageOptions.afterIndex,
1411
- throughIndex,
1412
- pageOptions.limit,
1413
- )
1414
- if (throughIndex > pageOptions.afterIndex && events.length === 0) {
1415
- throw new A2Error(
1416
- 'STORE_UNAVAILABLE',
1417
- `redis event range for session '${sessionId}' ended before index ${throughIndex}`,
1418
- )
1419
- }
1420
- for (let position = 0; position < events.length; position += 1) {
1421
- if (
1422
- events[position]!.index !==
1423
- pageOptions.afterIndex + position + 1
1424
- ) {
1425
- throw new A2Error(
1426
- 'STORE_UNAVAILABLE',
1427
- `redis event range for session '${sessionId}' is not contiguous`,
1428
- )
1429
- }
1430
- }
1431
- return { events, throughIndex }
1432
- })
1433
- },
1434
- async listSnapshots(sessionId) {
1435
- return wrap(async () => {
1436
- const start = `${prefix}:${sessionId}:snap:`
1437
- let cursor = '0'
1438
- const keys: string[] = []
1439
- do {
1440
- // oxlint-disable-next-line no-await-in-loop -- each SCAN cursor comes from the prior reply
1441
- const reply = (await call(
1442
- 'SCAN',
1443
- cursor,
1444
- 'MATCH',
1445
- `${escapeGlob(start)}*`,
1446
- 'COUNT',
1447
- 100,
1448
- )) as [string, string[]]
1449
- cursor = reply[0]
1450
- keys.push(...reply[1].filter((key) => key.startsWith(start)))
1451
- } while (cursor !== '0')
1452
- const snapshots = await Promise.all(
1453
- keys.map(async (key) => {
1454
- const raw = (await call('GET', key)) as string | null
1455
- if (raw === null) return null
1456
- const parsed = JSON.parse(raw) as {
1457
- index: number
1458
- updatedAt?: number
1459
- }
1460
- return {
1461
- reducerName: key.slice(start.length),
1462
- index: parsed.index,
1463
- updatedAt: new Date(parsed.updatedAt ?? 0),
1464
- }
1465
- }),
1466
- )
1467
- return snapshots
1468
- .filter((snapshot) => snapshot !== null)
1469
- .toSorted((a, b) => a.reducerName.localeCompare(b.reducerName))
1470
- })
1471
- },
1472
- },
1473
-
1474
1310
  async readRange(sessionId, afterIndex, throughIndex) {
1475
1311
  return wrap(() => readRange(sessionId, afterIndex, throughIndex))
1476
1312
  },
@@ -102,7 +102,6 @@ export function redisHttp(options: RedisHttpStoreOptions): RedisHttpStore {
102
102
  },
103
103
  read: core.presence.read,
104
104
  },
105
- inspect: core.inspect,
106
105
 
107
106
  stream(sessionId, opts) {
108
107
  return pollingStream(
@@ -292,7 +292,6 @@ export function redis(options: RedisStoreOptions = {}): RedisStore {
292
292
  failAttempt: core.failAttempt,
293
293
  readState: core.readState,
294
294
  putSnapshot: core.putSnapshot,
295
- inspect: core.inspect,
296
295
 
297
296
  presence: {
298
297
  async set(ns, participant, values, meta) {
@@ -231,23 +231,6 @@ type EventRow = {
231
231
  claim_expires_at: number | bigint | null
232
232
  }
233
233
 
234
- type SessionSummaryRow = {
235
- session_id: string
236
- event_count: number | bigint
237
- pending_count: number | bigint
238
- failed_count: number | bigint
239
- attempt_count: number | bigint
240
- failure_count: number | bigint
241
- first_event_at: number | bigint
242
- updated_at: number | bigint
243
- }
244
-
245
- type SnapshotRow = {
246
- reducer_name: string
247
- up_to_index: number | bigint
248
- updated_at: number | bigint
249
- }
250
-
251
234
  type PresenceDbRow = {
252
235
  participant: string
253
236
  field: string
@@ -983,108 +966,6 @@ export function sqlite(options: SqliteStoreOptions = {}): SqliteStore {
983
966
  },
984
967
  },
985
968
 
986
- inspect: {
987
- async listSessions(inspectionOptions) {
988
- return wrap(() => {
989
- const conditions = ['substr(session_id, 1, ?) = ?']
990
- const params: (string | number)[] = [
991
- inspectionOptions.prefix.length,
992
- inspectionOptions.prefix,
993
- ]
994
- if (inspectionOptions.cursor !== undefined) {
995
- conditions.push('session_id > ?')
996
- params.push(inspectionOptions.cursor)
997
- }
998
- params.push(inspectionOptions.limit + 1)
999
- const rows = db
1000
- .prepare(
1001
- `select
1002
- session_id,
1003
- count(*) as event_count,
1004
- sum(case when processed_at is null and failed_at is null then 1 else 0 end) as pending_count,
1005
- sum(case when failed_at is not null then 1 else 0 end) as failed_count,
1006
- sum(attempt_count) as attempt_count,
1007
- sum(failure_count) as failure_count,
1008
- min(created_at) as first_event_at,
1009
- max(max(
1010
- created_at,
1011
- coalesce(first_claimed_at, 0),
1012
- coalesce(last_claimed_at, 0),
1013
- coalesce(last_failed_at, 0),
1014
- coalesce(processed_at, 0),
1015
- coalesce(failed_at, 0)
1016
- )) as updated_at
1017
- from a2_events
1018
- where ${conditions.join(' and ')}
1019
- group by session_id
1020
- order by session_id
1021
- limit ?`,
1022
- )
1023
- .all(...params) as unknown as SessionSummaryRow[]
1024
- const hasMore = rows.length > inspectionOptions.limit
1025
- const visible = rows.slice(0, inspectionOptions.limit)
1026
- return {
1027
- sessions: visible.map((row) => ({
1028
- sessionId: row.session_id,
1029
- eventCount: Number(row.event_count),
1030
- pendingCount: Number(row.pending_count),
1031
- failedCount: Number(row.failed_count),
1032
- attemptCount: Number(row.attempt_count),
1033
- failureCount: Number(row.failure_count),
1034
- firstEventAt: new Date(Number(row.first_event_at)),
1035
- updatedAt: new Date(Number(row.updated_at)),
1036
- })),
1037
- cursor: hasMore ? (visible.at(-1)?.session_id ?? null) : null,
1038
- }
1039
- })
1040
- },
1041
- async readEvents(sessionId, pageOptions) {
1042
- return wrap(() => {
1043
- const currentFrontier = Number(
1044
- (
1045
- db
1046
- .prepare(
1047
- 'select coalesce(max(idx), 0) as frontier from a2_events where session_id = ?',
1048
- )
1049
- .get(sessionId) as { frontier: number }
1050
- ).frontier,
1051
- )
1052
- const throughIndex = Math.min(
1053
- pageOptions.throughIndex ?? currentFrontier,
1054
- currentFrontier,
1055
- )
1056
- const rows = db
1057
- .prepare(
1058
- `select * from a2_events
1059
- where session_id = ? and idx > ? and idx <= ?
1060
- order by idx limit ?`,
1061
- )
1062
- .all(
1063
- sessionId,
1064
- pageOptions.afterIndex,
1065
- throughIndex,
1066
- pageOptions.limit,
1067
- ) as unknown as EventRow[]
1068
- return { events: rows.map(toStored), throughIndex }
1069
- })
1070
- },
1071
- async listSnapshots(sessionId) {
1072
- return wrap(() => {
1073
- const rows = db
1074
- .prepare(
1075
- `select reducer_name, up_to_index, updated_at
1076
- from a2_snapshots where session_id = ? order by reducer_name`,
1077
- )
1078
- .all(sessionId) as unknown as SnapshotRow[]
1079
- return rows.map((row) => ({
1080
- reducerName: row.reducer_name,
1081
- index: Number(row.up_to_index),
1082
- updatedAt: new Date(Number(row.updated_at)),
1083
- }))
1084
- })
1085
- },
1086
- },
1087
-
1088
969
  stream(sessionId, opts) {
1089
970
  const readAfter = (afterIndex: number): StoredEvent[] =>
1090
971
  wrap(() => {