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.
- package/CHANGELOG.md +22 -0
- package/dist/ai-server.d.ts +4 -5
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +8 -7
- package/dist/ai-server.js.map +1 -1
- package/dist/ai.d.ts +334 -2
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +1 -1
- package/dist/client.d.ts +202 -2
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +1025 -1
- package/dist/client.js.map +1 -0
- package/dist/errors-BQuJpe82.js.map +1 -1
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/{internal-DstsI6Re.js → internal-DRXJ56EI.js} +5 -28
- package/dist/internal-DRXJ56EI.js.map +1 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/scheduler-qstash.d.ts +3 -3
- package/dist/scheduler-qstash.js +4 -5
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +4 -4
- package/dist/scheduler-vercel.js.map +1 -1
- package/dist/{server-Duw6MVlB.js → server-286j79Mt.js} +708 -79
- package/dist/server-286j79Mt.js.map +1 -0
- package/dist/{server-DpvjhdoE.d.ts → server-DgXmORIq.d.ts} +67 -49
- package/dist/server-DgXmORIq.d.ts.map +1 -0
- package/dist/server.d.ts +3 -3
- package/dist/server.js +1 -1
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/{store-DysUkTH3.d.ts → store-flRz1OWh.d.ts} +2 -57
- package/dist/store-flRz1OWh.d.ts.map +1 -0
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.d.ts.map +1 -1
- package/dist/store-memory.js +1 -59
- package/dist/store-memory.js.map +1 -1
- package/dist/{store-polling-dSeLxzfb.js → store-polling-6DW7F1DT.js} +2 -2
- package/dist/{store-polling-dSeLxzfb.js.map → store-polling-6DW7F1DT.js.map} +1 -1
- package/dist/store-postgres.d.ts +1 -1
- package/dist/store-postgres.js +1 -81
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-redis-core-BFLwz0Wj.js → store-redis-core-DEYO8Ryv.js} +48 -134
- package/dist/store-redis-core-DEYO8Ryv.js.map +1 -0
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +2 -3
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +3 -4
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.d.ts.map +1 -1
- package/dist/store-sqlite.js +1 -72
- package/dist/store-sqlite.js.map +1 -1
- package/dist/{wire-BFQmSJ-9.js → wire-B6te_wns.js} +4 -3
- package/dist/wire-B6te_wns.js.map +1 -0
- package/docs/guides/03-react.mdx +20 -28
- package/docs/guides/05-production.mdx +9 -11
- package/docs/guides/06-ai-agents.mdx +8 -14
- package/docs/guides/09-presence.mdx +14 -19
- package/docs/guides/10-transports.mdx +104 -86
- package/docs/reference/01-api.mdx +143 -277
- package/docs/reference/02-errors.mdx +5 -7
- package/package.json +1 -14
- package/src/ai-server.ts +9 -5
- package/src/client.ts +2 -2
- package/src/errors.ts +1 -0
- package/src/internal.ts +3 -62
- package/src/push-envelope.ts +24 -21
- package/src/scheduler-qstash.ts +3 -3
- package/src/scheduler-vercel.ts +2 -2
- package/src/server-fetch.ts +344 -0
- package/src/server.ts +73 -225
- package/src/session-socket.ts +36 -20
- package/src/sse.ts +2 -2
- package/src/store-memory.ts +0 -81
- package/src/store-postgres.ts +0 -100
- package/src/store-redis-core.ts +47 -211
- package/src/store-redis-http.ts +0 -1
- package/src/store-redis.ts +0 -1
- package/src/store-sqlite.ts +0 -119
- package/src/store.ts +0 -60
- package/src/wire.ts +2 -1
- package/dist/ai-D_PGS-JR.d.ts +0 -334
- package/dist/ai-D_PGS-JR.d.ts.map +0 -1
- package/dist/cli-B3VuxoDe.js +0 -599
- package/dist/cli-B3VuxoDe.js.map +0 -1
- package/dist/cli-bin.d.ts +0 -1
- package/dist/cli-bin.js +0 -7
- package/dist/cli-bin.js.map +0 -1
- package/dist/cli.d.ts +0 -20
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -2
- package/dist/client-CdMqi7mC.d.ts +0 -202
- package/dist/client-CdMqi7mC.d.ts.map +0 -1
- package/dist/client-Dj5d3SP_.js +0 -1026
- package/dist/client-Dj5d3SP_.js.map +0 -1
- package/dist/devtools-J_jZ2vQf.d.ts +0 -152
- package/dist/devtools-J_jZ2vQf.d.ts.map +0 -1
- package/dist/devtools-kJJaORn-.js +0 -340
- package/dist/devtools-kJJaORn-.js.map +0 -1
- package/dist/devtools-server.browser.d.ts +0 -1
- package/dist/devtools-server.browser.js +0 -6
- package/dist/devtools-server.browser.js.map +0 -1
- package/dist/devtools-server.d.ts +0 -23
- package/dist/devtools-server.d.ts.map +0 -1
- package/dist/devtools-server.js +0 -1270
- package/dist/devtools-server.js.map +0 -1
- package/dist/devtools.d.ts +0 -2
- package/dist/devtools.js +0 -2
- package/dist/errors-W6nwJ-fm.d.ts +0 -21
- package/dist/errors-W6nwJ-fm.d.ts.map +0 -1
- package/dist/http.d.ts +0 -151
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js +0 -706
- package/dist/http.js.map +0 -1
- package/dist/inspection-DaxB5jM2.js +0 -13
- package/dist/inspection-DaxB5jM2.js.map +0 -1
- package/dist/internal-DstsI6Re.js.map +0 -1
- package/dist/platform-B4TnJtWu.js +0 -34
- package/dist/platform-B4TnJtWu.js.map +0 -1
- package/dist/server-DpvjhdoE.d.ts.map +0 -1
- package/dist/server-Duw6MVlB.js.map +0 -1
- package/dist/store-DysUkTH3.d.ts.map +0 -1
- package/dist/store-redis-core-BFLwz0Wj.js.map +0 -1
- package/dist/testing.browser.d.ts +0 -1
- package/dist/testing.browser.js +0 -6
- package/dist/testing.browser.js.map +0 -1
- package/dist/testing.d.ts +0 -32
- package/dist/testing.d.ts.map +0 -1
- package/dist/testing.js +0 -103
- package/dist/testing.js.map +0 -1
- package/dist/wire-BFQmSJ-9.js.map +0 -1
- package/docs/guides/07-devtools.mdx +0 -229
- package/src/cli-bin.ts +0 -5
- package/src/cli.ts +0 -1046
- package/src/devtools-app.ts +0 -989
- package/src/devtools-server.browser.ts +0 -5
- package/src/devtools-server.ts +0 -604
- package/src/devtools.ts +0 -716
- package/src/http.ts +0 -394
- package/src/inspection.ts +0 -39
- package/src/testing.browser.ts +0 -5
- package/src/testing.ts +0 -185
package/src/store-postgres.ts
CHANGED
|
@@ -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 () => {
|
package/src/store-redis-core.ts
CHANGED
|
@@ -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,
|
|
102
|
-
*
|
|
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[
|
|
109
|
+
redis.call('ZADD', KEYS[5], idx, field)
|
|
115
110
|
if lane == '' then
|
|
116
|
-
redis.call('ZADD', KEYS[
|
|
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[
|
|
115
|
+
local tail = redis.call('HGET', KEYS[4], tail_key)
|
|
121
116
|
if tail then
|
|
122
|
-
redis.call('HSET', KEYS[
|
|
117
|
+
redis.call('HSET', KEYS[4], tail .. ':next', field)
|
|
123
118
|
else
|
|
124
|
-
redis.call('HSET', KEYS[
|
|
125
|
-
redis.call('ZADD', KEYS[
|
|
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[
|
|
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[
|
|
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[
|
|
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[
|
|
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[
|
|
172
|
+
redis.call('HSET', KEYS[4], tostring(idx) .. ':l', lane)
|
|
178
173
|
if settled == '1' then
|
|
179
|
-
redis.call('HSET', KEYS[
|
|
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('
|
|
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[
|
|
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,
|
|
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('
|
|
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[
|
|
354
|
-
local next_field = redis.call('HGET', KEYS[
|
|
355
|
-
while next_field and redis.call('HEXISTS', KEYS[
|
|
356
|
-
next_field = redis.call('HGET', KEYS[
|
|
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[
|
|
360
|
-
if redis.call('HEXISTS', KEYS[
|
|
361
|
-
redis.call('ZADD', KEYS[
|
|
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[
|
|
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[
|
|
362
|
+
redis.call('ZADD', KEYS[5], idx, field)
|
|
369
363
|
if lane == '' then
|
|
370
|
-
redis.call('ZADD', KEYS[
|
|
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[
|
|
368
|
+
local tail = redis.call('HGET', KEYS[4], tail_key)
|
|
375
369
|
if tail then
|
|
376
|
-
redis.call('HSET', KEYS[
|
|
370
|
+
redis.call('HSET', KEYS[4], tail .. ':next', field)
|
|
377
371
|
else
|
|
378
|
-
redis.call('HSET', KEYS[
|
|
379
|
-
redis.call('ZADD', KEYS[
|
|
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[
|
|
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[
|
|
389
|
-
local processed = redis.call('HGET', KEYS[
|
|
390
|
-
local processed_attempt = tonumber(redis.call('HGET', KEYS[
|
|
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[
|
|
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[
|
|
424
|
-
redis.call('HEXISTS', KEYS[
|
|
425
|
-
tonumber(redis.call('HGET', KEYS[
|
|
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[
|
|
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[
|
|
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[
|
|
448
|
+
redis.call('HSET', KEYS[4], tostring(idx) .. ':l', lane)
|
|
455
449
|
if settled == '1' then
|
|
456
|
-
redis.call('HSET', KEYS[
|
|
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[
|
|
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
|
},
|
package/src/store-redis-http.ts
CHANGED
package/src/store-redis.ts
CHANGED
|
@@ -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) {
|
package/src/store-sqlite.ts
CHANGED
|
@@ -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(() => {
|