experimental-a2 0.6.0 → 0.8.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 +41 -0
- package/dist/ai-server.d.ts +4 -5
- package/dist/ai-server.d.ts.map +1 -1
- package/dist/ai-server.js +20 -17
- 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-B2XNevQA.js} +830 -131
- package/dist/server-B2XNevQA.js.map +1 -0
- package/dist/{server-DpvjhdoE.d.ts → server-DjPhHnbI.d.ts} +71 -50
- package/dist/server-DjPhHnbI.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-RJO35BMj.d.ts} +24 -62
- package/dist/store-RJO35BMj.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 +80 -78
- 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.d.ts.map +1 -1
- package/dist/store-postgres.js +231 -182
- package/dist/store-postgres.js.map +1 -1
- package/dist/{store-redis-core-BFLwz0Wj.js → store-redis-core-DT01r4GZ.js} +213 -161
- package/dist/store-redis-core-DT01r4GZ.js.map +1 -0
- package/dist/store-redis-http.d.ts +1 -1
- package/dist/store-redis-http.js +3 -4
- package/dist/store-redis-http.js.map +1 -1
- package/dist/store-redis.d.ts +1 -1
- package/dist/store-redis.js +4 -5
- 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 +104 -91
- 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/concepts/02-handlers.mdx +4 -0
- package/docs/concepts/04-state.mdx +57 -9
- package/docs/guides/03-react.mdx +20 -28
- package/docs/guides/05-production.mdx +9 -11
- package/docs/guides/06-ai-agents.mdx +10 -15
- package/docs/guides/09-presence.mdx +14 -19
- package/docs/guides/10-transports.mdx +104 -86
- package/docs/reference/01-api.mdx +182 -293
- package/docs/reference/02-errors.mdx +5 -7
- package/package.json +1 -14
- package/src/ai-server.ts +36 -15
- 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 +315 -312
- package/src/session-socket.ts +36 -20
- package/src/sse.ts +2 -2
- package/src/store-memory.ts +138 -101
- package/src/store-postgres.ts +355 -238
- package/src/store-redis-core.ts +247 -237
- package/src/store-redis-http.ts +1 -2
- package/src/store-redis.ts +1 -2
- package/src/store-sqlite.ts +191 -153
- package/src/store.ts +24 -66
- 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
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { t as A2Error } from "./errors-BQuJpe82.js";
|
|
2
|
-
import {
|
|
2
|
+
import { h as nullProtoRecord } from "./internal-DRXJ56EI.js";
|
|
3
3
|
import { t as idempotentReplay } from "./idempotent-replay-DuqEkYA7.js";
|
|
4
4
|
import { t as decodeReturnedEventIds } from "./store-codec-DTG0Ftek.js";
|
|
5
5
|
//#region src/store-redis-core.ts
|
|
6
6
|
const READ_PAGE_SIZE = 256;
|
|
7
7
|
/**
|
|
8
|
-
* KEYS: store stream, counter, global id hash,
|
|
9
|
-
*
|
|
8
|
+
* KEYS: store stream, counter, global id hash, metadata, pending, ready,
|
|
9
|
+
* watched.
|
|
10
10
|
* ARGV: sessionId, nowMs, n, then per event:
|
|
11
11
|
* id, type, payloadJson, causeJson, lane, settled.
|
|
12
12
|
* Returns ['empty', hasPending] | ['ok', firstIndex, hasPending, watched] |
|
|
@@ -18,24 +18,24 @@ local function lane_field(lane, suffix)
|
|
|
18
18
|
return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix
|
|
19
19
|
end
|
|
20
20
|
local function enqueue(field, idx, lane)
|
|
21
|
-
redis.call('ZADD', KEYS[
|
|
21
|
+
redis.call('ZADD', KEYS[5], idx, field)
|
|
22
22
|
if lane == '' then
|
|
23
|
-
redis.call('ZADD', KEYS[
|
|
23
|
+
redis.call('ZADD', KEYS[6], idx, field)
|
|
24
24
|
return
|
|
25
25
|
end
|
|
26
26
|
local tail_key = lane_field(lane, 'tail')
|
|
27
|
-
local tail = redis.call('HGET', KEYS[
|
|
27
|
+
local tail = redis.call('HGET', KEYS[4], tail_key)
|
|
28
28
|
if tail then
|
|
29
|
-
redis.call('HSET', KEYS[
|
|
29
|
+
redis.call('HSET', KEYS[4], tail .. ':next', field)
|
|
30
30
|
else
|
|
31
|
-
redis.call('HSET', KEYS[
|
|
32
|
-
redis.call('ZADD', KEYS[
|
|
31
|
+
redis.call('HSET', KEYS[4], lane_field(lane, 'head'), field)
|
|
32
|
+
redis.call('ZADD', KEYS[6], idx, field)
|
|
33
33
|
end
|
|
34
|
-
redis.call('HSET', KEYS[
|
|
34
|
+
redis.call('HSET', KEYS[4], tail_key, field)
|
|
35
35
|
end
|
|
36
36
|
local n = tonumber(ARGV[3])
|
|
37
37
|
local base = tonumber(redis.call('GET', KEYS[2]) or '0')
|
|
38
|
-
local has_pending = redis.call('ZCARD', KEYS[
|
|
38
|
+
local has_pending = redis.call('ZCARD', KEYS[5]) > 0 and '1' or '0'
|
|
39
39
|
if n == 0 then return { 'empty', has_pending } end
|
|
40
40
|
local dups = {}
|
|
41
41
|
for i = 1, n do
|
|
@@ -62,9 +62,9 @@ for i = 1, n do
|
|
|
62
62
|
return { 'no_parent', tostring(parsed.index) }
|
|
63
63
|
end
|
|
64
64
|
local field = tostring(pidx)
|
|
65
|
-
local dispatches = tonumber(redis.call('HGET', KEYS[
|
|
65
|
+
local dispatches = tonumber(redis.call('HGET', KEYS[4], field .. ':d') or '0')
|
|
66
66
|
if dispatches ~= tonumber(parsed.attempt) or
|
|
67
|
-
redis.call('HEXISTS', KEYS[
|
|
67
|
+
redis.call('HEXISTS', KEYS[4], field .. ':f') == 1 then
|
|
68
68
|
return { 'superseded', field }
|
|
69
69
|
end
|
|
70
70
|
end
|
|
@@ -81,21 +81,36 @@ for i = 1, n do
|
|
|
81
81
|
'id', id, 'type', typ, 'payload', payload, 'created_at', ARGV[2],
|
|
82
82
|
'cause', cause, 'lane', lane)
|
|
83
83
|
redis.call('HSET', KEYS[3], id, cjson.encode({ ARGV[1], idx }))
|
|
84
|
-
redis.call('HSET', KEYS[
|
|
84
|
+
redis.call('HSET', KEYS[4], tostring(idx) .. ':l', lane)
|
|
85
85
|
if settled == '1' then
|
|
86
|
-
redis.call('HSET', KEYS[
|
|
86
|
+
redis.call('HSET', KEYS[4], tostring(idx) .. ':p', ARGV[2])
|
|
87
87
|
else
|
|
88
88
|
enqueue(tostring(idx), idx, lane)
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
redis.call('SET', KEYS[2], tostring(base + n))
|
|
92
|
-
redis.call('
|
|
93
|
-
has_pending = redis.call('ZCARD', KEYS[6]) > 0 and '1' or '0'
|
|
92
|
+
has_pending = redis.call('ZCARD', KEYS[5]) > 0 and '1' or '0'
|
|
94
93
|
return { 'ok', tostring(base + 1), has_pending,
|
|
95
|
-
tostring(redis.call('EXISTS', KEYS[
|
|
94
|
+
tostring(redis.call('EXISTS', KEYS[7])) }
|
|
96
95
|
`;
|
|
97
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* KEYS: counter, meta, pending, ready, event snapshot pins, snapshot pin counts.
|
|
98
|
+
* ARGV: idx, attempt, error, maxFailures, nowMs.
|
|
99
|
+
*/
|
|
98
100
|
const FAIL_ATTEMPT_LUA = `
|
|
101
|
+
local function release_snapshot_pins()
|
|
102
|
+
local refs = redis.call('SMEMBERS', KEYS[5])
|
|
103
|
+
for _, ref in ipairs(refs) do
|
|
104
|
+
local count = redis.call('HINCRBY', KEYS[6], ref, -1)
|
|
105
|
+
if count <= 0 then
|
|
106
|
+
redis.call('HDEL', KEYS[6], ref)
|
|
107
|
+
local checkpoint = cjson.decode(ref)
|
|
108
|
+
redis.call('ZREMRANGEBYSCORE', checkpoint[1], checkpoint[2], checkpoint[2])
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
redis.call('DEL', KEYS[5])
|
|
112
|
+
if redis.call('HLEN', KEYS[6]) == 0 then redis.call('DEL', KEYS[6]) end
|
|
113
|
+
end
|
|
99
114
|
local function lane_field(lane, suffix)
|
|
100
115
|
return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix
|
|
101
116
|
end
|
|
@@ -149,6 +164,7 @@ redis.call('HDEL', KEYS[2], field .. ':ch', field .. ':ce')
|
|
|
149
164
|
if failures >= tonumber(ARGV[4]) then
|
|
150
165
|
redis.call('HSET', KEYS[2], field .. ':f', ARGV[5])
|
|
151
166
|
redis.call('ZREM', KEYS[4], field)
|
|
167
|
+
release_snapshot_pins()
|
|
152
168
|
return { 'dead_lettered', tostring(failures) }
|
|
153
169
|
end
|
|
154
170
|
return { 'failed', tostring(failures) }
|
|
@@ -239,61 +255,75 @@ for i = 1, #superseded do result[#result + 1] = superseded[i] end
|
|
|
239
255
|
return result
|
|
240
256
|
`;
|
|
241
257
|
/**
|
|
242
|
-
* KEYS: store, counter, ids,
|
|
258
|
+
* KEYS: store, counter, ids, meta, pending, ready, watched,
|
|
259
|
+
* event snapshot pins, snapshot pin counts.
|
|
243
260
|
* ARGV: sessionId, nowMs, parent index, attempt, n, then per child:
|
|
244
261
|
* id, type, payloadJson, lane, settled.
|
|
245
262
|
*/
|
|
246
263
|
const COMPLETE_ATTEMPT_LUA = `
|
|
264
|
+
local function release_snapshot_pins()
|
|
265
|
+
local refs = redis.call('SMEMBERS', KEYS[8])
|
|
266
|
+
for _, ref in ipairs(refs) do
|
|
267
|
+
local count = redis.call('HINCRBY', KEYS[9], ref, -1)
|
|
268
|
+
if count <= 0 then
|
|
269
|
+
redis.call('HDEL', KEYS[9], ref)
|
|
270
|
+
local checkpoint = cjson.decode(ref)
|
|
271
|
+
redis.call('ZREMRANGEBYSCORE', checkpoint[1], checkpoint[2], checkpoint[2])
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
redis.call('DEL', KEYS[8])
|
|
275
|
+
if redis.call('HLEN', KEYS[9]) == 0 then redis.call('DEL', KEYS[9]) end
|
|
276
|
+
end
|
|
247
277
|
local function lane_field(lane, suffix)
|
|
248
278
|
return '__lane:' .. tostring(string.len(lane)) .. ':' .. lane .. ':' .. suffix
|
|
249
279
|
end
|
|
250
280
|
local function settle(field)
|
|
281
|
+
redis.call('ZREM', KEYS[5], field)
|
|
251
282
|
redis.call('ZREM', KEYS[6], field)
|
|
252
|
-
redis.call('
|
|
253
|
-
local lane = redis.call('HGET', KEYS[5], field .. ':l') or ''
|
|
283
|
+
local lane = redis.call('HGET', KEYS[4], field .. ':l') or ''
|
|
254
284
|
if lane == '' then return end
|
|
255
285
|
local head_key = lane_field(lane, 'head')
|
|
256
|
-
if redis.call('HGET', KEYS[
|
|
257
|
-
local next_field = redis.call('HGET', KEYS[
|
|
258
|
-
while next_field and redis.call('HEXISTS', KEYS[
|
|
259
|
-
next_field = redis.call('HGET', KEYS[
|
|
286
|
+
if redis.call('HGET', KEYS[4], head_key) ~= field then return end
|
|
287
|
+
local next_field = redis.call('HGET', KEYS[4], field .. ':next')
|
|
288
|
+
while next_field and redis.call('HEXISTS', KEYS[4], next_field .. ':p') == 1 do
|
|
289
|
+
next_field = redis.call('HGET', KEYS[4], next_field .. ':next')
|
|
260
290
|
end
|
|
261
291
|
if next_field then
|
|
262
|
-
redis.call('HSET', KEYS[
|
|
263
|
-
if redis.call('HEXISTS', KEYS[
|
|
264
|
-
redis.call('ZADD', KEYS[
|
|
292
|
+
redis.call('HSET', KEYS[4], head_key, next_field)
|
|
293
|
+
if redis.call('HEXISTS', KEYS[4], next_field .. ':f') == 0 then
|
|
294
|
+
redis.call('ZADD', KEYS[6], tonumber(next_field), next_field)
|
|
265
295
|
end
|
|
266
296
|
else
|
|
267
|
-
redis.call('HDEL', KEYS[
|
|
297
|
+
redis.call('HDEL', KEYS[4], head_key, lane_field(lane, 'tail'))
|
|
268
298
|
end
|
|
269
299
|
end
|
|
270
300
|
local function enqueue(field, idx, lane)
|
|
271
|
-
redis.call('ZADD', KEYS[
|
|
301
|
+
redis.call('ZADD', KEYS[5], idx, field)
|
|
272
302
|
if lane == '' then
|
|
273
|
-
redis.call('ZADD', KEYS[
|
|
303
|
+
redis.call('ZADD', KEYS[6], idx, field)
|
|
274
304
|
return
|
|
275
305
|
end
|
|
276
306
|
local tail_key = lane_field(lane, 'tail')
|
|
277
|
-
local tail = redis.call('HGET', KEYS[
|
|
307
|
+
local tail = redis.call('HGET', KEYS[4], tail_key)
|
|
278
308
|
if tail then
|
|
279
|
-
redis.call('HSET', KEYS[
|
|
309
|
+
redis.call('HSET', KEYS[4], tail .. ':next', field)
|
|
280
310
|
else
|
|
281
|
-
redis.call('HSET', KEYS[
|
|
282
|
-
redis.call('ZADD', KEYS[
|
|
311
|
+
redis.call('HSET', KEYS[4], lane_field(lane, 'head'), field)
|
|
312
|
+
redis.call('ZADD', KEYS[6], idx, field)
|
|
283
313
|
end
|
|
284
|
-
redis.call('HSET', KEYS[
|
|
314
|
+
redis.call('HSET', KEYS[4], tail_key, field)
|
|
285
315
|
end
|
|
286
316
|
local parent = ARGV[3]
|
|
287
317
|
local attempt = tonumber(ARGV[4])
|
|
288
318
|
local n = tonumber(ARGV[5])
|
|
289
319
|
local total = tonumber(redis.call('GET', KEYS[2]) or '0')
|
|
290
320
|
if tonumber(parent) < 1 or tonumber(parent) > total then return { 'missing' } end
|
|
291
|
-
local dispatches = tonumber(redis.call('HGET', KEYS[
|
|
292
|
-
local processed = redis.call('HGET', KEYS[
|
|
293
|
-
local processed_attempt = tonumber(redis.call('HGET', KEYS[
|
|
321
|
+
local dispatches = tonumber(redis.call('HGET', KEYS[4], parent .. ':d') or '0')
|
|
322
|
+
local processed = redis.call('HGET', KEYS[4], parent .. ':p')
|
|
323
|
+
local processed_attempt = tonumber(redis.call('HGET', KEYS[4], parent .. ':pa') or '0')
|
|
294
324
|
if processed then
|
|
295
325
|
if processed_attempt ~= attempt then return { 'superseded' } end
|
|
296
|
-
local raw_recorded_ids = redis.call('HGET', KEYS[
|
|
326
|
+
local raw_recorded_ids = redis.call('HGET', KEYS[4], parent .. ':ri')
|
|
297
327
|
if not raw_recorded_ids then return { 'partial' } end
|
|
298
328
|
local recorded_ids = cjson.decode(raw_recorded_ids)
|
|
299
329
|
if #recorded_ids ~= n then return { 'partial' } end
|
|
@@ -323,9 +353,9 @@ if processed then
|
|
|
323
353
|
return result
|
|
324
354
|
end
|
|
325
355
|
if dispatches ~= attempt then return { 'superseded' } end
|
|
326
|
-
if redis.call('HEXISTS', KEYS[
|
|
327
|
-
redis.call('HEXISTS', KEYS[
|
|
328
|
-
tonumber(redis.call('HGET', KEYS[
|
|
356
|
+
if redis.call('HEXISTS', KEYS[4], parent .. ':ch') == 0 or
|
|
357
|
+
redis.call('HEXISTS', KEYS[4], parent .. ':f') == 1 or
|
|
358
|
+
tonumber(redis.call('HGET', KEYS[4], parent .. ':lfa') or '0') == attempt then
|
|
329
359
|
return { 'superseded' }
|
|
330
360
|
end
|
|
331
361
|
for i = 1, n do
|
|
@@ -336,12 +366,13 @@ local returned_ids = {}
|
|
|
336
366
|
for i = 1, n do returned_ids[i] = ARGV[5 + (i - 1) * 5 + 1] end
|
|
337
367
|
local returned_json = '[]'
|
|
338
368
|
if n > 0 then returned_json = cjson.encode(returned_ids) end
|
|
339
|
-
redis.call('HSET', KEYS[
|
|
369
|
+
redis.call('HSET', KEYS[4],
|
|
340
370
|
parent .. ':p', ARGV[2],
|
|
341
371
|
parent .. ':pa', ARGV[4],
|
|
342
372
|
parent .. ':ri', returned_json)
|
|
343
|
-
redis.call('HDEL', KEYS[
|
|
373
|
+
redis.call('HDEL', KEYS[4], parent .. ':ch', parent .. ':ce')
|
|
344
374
|
settle(parent)
|
|
375
|
+
release_snapshot_pins()
|
|
345
376
|
local cause = cjson.encode({ index = tonumber(parent), attempt = attempt })
|
|
346
377
|
for i = 1, n do
|
|
347
378
|
local id = ARGV[5 + (i - 1) * 5 + 1]
|
|
@@ -354,25 +385,72 @@ for i = 1, n do
|
|
|
354
385
|
'id', id, 'type', typ, 'payload', payload, 'created_at', ARGV[2],
|
|
355
386
|
'cause', cause, 'lane', lane)
|
|
356
387
|
redis.call('HSET', KEYS[3], id, cjson.encode({ ARGV[1], idx }))
|
|
357
|
-
redis.call('HSET', KEYS[
|
|
388
|
+
redis.call('HSET', KEYS[4], tostring(idx) .. ':l', lane)
|
|
358
389
|
if settled == '1' then
|
|
359
|
-
redis.call('HSET', KEYS[
|
|
390
|
+
redis.call('HSET', KEYS[4], tostring(idx) .. ':p', ARGV[2])
|
|
360
391
|
else
|
|
361
392
|
enqueue(tostring(idx), idx, lane)
|
|
362
393
|
end
|
|
363
394
|
end
|
|
364
395
|
redis.call('SET', KEYS[2], tostring(total + n))
|
|
365
|
-
redis.call('ZADD', KEYS[4], 0, ARGV[1])
|
|
366
396
|
return { 'ok', tostring(total + 1),
|
|
367
|
-
tostring(redis.call('EXISTS', KEYS[
|
|
397
|
+
tostring(redis.call('EXISTS', KEYS[7])) }
|
|
368
398
|
`;
|
|
369
|
-
/**
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
399
|
+
/**
|
|
400
|
+
* KEYS: head, history, event counter, event meta, pin counts.
|
|
401
|
+
* ARGV: nowMs, event pin key prefix, write count, then per write:
|
|
402
|
+
* index, snapshot JSON, pin count, pin event indexes.
|
|
403
|
+
*/
|
|
404
|
+
const PUT_SNAPSHOTS_LUA = `
|
|
405
|
+
local total = tonumber(redis.call('GET', KEYS[3]) or '0')
|
|
406
|
+
local writes = {}
|
|
407
|
+
local offset = 4
|
|
408
|
+
for i = 1, tonumber(ARGV[3]) do
|
|
409
|
+
local index = tonumber(ARGV[offset])
|
|
410
|
+
local snapshot = ARGV[offset + 1]
|
|
411
|
+
local pin_count = tonumber(ARGV[offset + 2])
|
|
412
|
+
local ref = cjson.encode({ KEYS[2], tostring(index) })
|
|
413
|
+
for p = 1, pin_count do
|
|
414
|
+
local event_index = tonumber(ARGV[offset + 2 + p])
|
|
415
|
+
if event_index >= 1 and event_index <= total and
|
|
416
|
+
redis.call('HEXISTS', KEYS[4], tostring(event_index) .. ':p') == 0 and
|
|
417
|
+
redis.call('HEXISTS', KEYS[4], tostring(event_index) .. ':f') == 0 then
|
|
418
|
+
local pin_key = ARGV[2] .. tostring(event_index)
|
|
419
|
+
if redis.call('SADD', pin_key, ref) == 1 then
|
|
420
|
+
redis.call('HINCRBY', KEYS[5], ref, 1)
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
writes[#writes + 1] = { index = index, snapshot = snapshot, ref = ref }
|
|
425
|
+
offset = offset + 3 + pin_count
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
local head = redis.call('GET', KEYS[1])
|
|
429
|
+
local head_index = nil
|
|
430
|
+
if head then head_index = tonumber(cjson.decode(head)['index']) end
|
|
431
|
+
local latest = writes[#writes]
|
|
432
|
+
local writes_head = not head_index or latest.index >= head_index
|
|
433
|
+
local advances = not head_index or latest.index > head_index
|
|
434
|
+
local final_head_index = advances and latest.index or head_index
|
|
435
|
+
|
|
436
|
+
if advances and head and tonumber(redis.call('HGET', KEYS[5], cjson.encode({ KEYS[2], tostring(head_index) })) or '0') > 0 then
|
|
437
|
+
redis.call('ZREMRANGEBYSCORE', KEYS[2], head_index, head_index)
|
|
438
|
+
redis.call('ZADD', KEYS[2], head_index, head)
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
for _, write in ipairs(writes) do
|
|
442
|
+
if write.index < final_head_index then
|
|
443
|
+
redis.call('ZREMRANGEBYSCORE', KEYS[2], write.index, write.index)
|
|
444
|
+
if tonumber(redis.call('HGET', KEYS[5], write.ref) or '0') > 0 then
|
|
445
|
+
redis.call('ZADD', KEYS[2], write.index, write.snapshot)
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
if writes_head then
|
|
451
|
+
redis.call('SET', KEYS[1], latest.snapshot)
|
|
452
|
+
redis.call('ZREMRANGEBYSCORE', KEYS[2], latest.index, latest.index)
|
|
374
453
|
end
|
|
375
|
-
redis.call('SET', KEYS[1], ARGV[2])
|
|
376
454
|
return 1
|
|
377
455
|
`;
|
|
378
456
|
/**
|
|
@@ -426,11 +504,26 @@ for i = 1, #rows, 2 do
|
|
|
426
504
|
end
|
|
427
505
|
return live
|
|
428
506
|
`;
|
|
429
|
-
/**
|
|
507
|
+
/**
|
|
508
|
+
* KEYS: head, history, event counter. ARGV: snapshot frontier or ''.
|
|
509
|
+
* Returns one atomic multiversion-cache-plus-log-frontier read.
|
|
510
|
+
*/
|
|
430
511
|
const READ_STATE_LUA = `
|
|
431
|
-
local
|
|
432
|
-
local
|
|
433
|
-
|
|
512
|
+
local head = redis.call('GET', KEYS[1])
|
|
513
|
+
local cap = ARGV[1]
|
|
514
|
+
local snapshot = nil
|
|
515
|
+
if head and (cap == '' or tonumber(cjson.decode(head)['index']) <= tonumber(cap)) then
|
|
516
|
+
snapshot = head
|
|
517
|
+
end
|
|
518
|
+
local upper = cap == '' and '+inf' or cap
|
|
519
|
+
local historical = redis.call('ZREVRANGEBYSCORE', KEYS[2], upper, '-inf', 'LIMIT', 0, 1)
|
|
520
|
+
if #historical > 0 then
|
|
521
|
+
if not snapshot or tonumber(cjson.decode(historical[1])['index']) > tonumber(cjson.decode(snapshot)['index']) then
|
|
522
|
+
snapshot = historical[1]
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
local frontier = redis.call('GET', KEYS[3]) or '0'
|
|
526
|
+
return { head or '', snapshot or '', frontier }
|
|
434
527
|
`;
|
|
435
528
|
const fieldMap = (fields) => {
|
|
436
529
|
const map = {};
|
|
@@ -465,7 +558,6 @@ const toEvent = (sessionId, entry) => {
|
|
|
465
558
|
};
|
|
466
559
|
};
|
|
467
560
|
const json = (value) => JSON.stringify(value) ?? "null";
|
|
468
|
-
const escapeGlob = (value) => value.replaceAll("\\", "\\\\").replaceAll("*", "\\*").replaceAll("?", "\\?").replaceAll("[", "\\[").replaceAll("]", "\\]");
|
|
469
561
|
const toCause = (raw) => {
|
|
470
562
|
if (raw === "" || raw === void 0) return null;
|
|
471
563
|
const value = JSON.parse(raw);
|
|
@@ -535,10 +627,13 @@ function createRedisStoreCore(options) {
|
|
|
535
627
|
const pendingKey = (s) => `${prefix}:${s}:pending`;
|
|
536
628
|
const readyKey = (s) => `${prefix}:${s}:ready`;
|
|
537
629
|
const snapKey = (s, r) => `${prefix}:${s}:snap:${r}`;
|
|
630
|
+
const snapHistoryKey = (s, r) => `${prefix}:${s}:snap-history:${r}`;
|
|
631
|
+
const snapshotEventPinsKey = (s, index) => `${prefix}:${s}:snapshot-pins:${index}`;
|
|
632
|
+
const snapshotEventPinsPrefix = (s) => `${prefix}:${s}:snapshot-pins:`;
|
|
633
|
+
const snapshotPinCountsKey = (s) => `${prefix}:${s}:snapshot-pin-counts`;
|
|
538
634
|
const watchedKey = (s) => `${prefix}:${s}:watched`;
|
|
539
635
|
const presenceKey = (s) => `${prefix}:${s}:presence`;
|
|
540
636
|
const idsKey = `${prefix}:ids`;
|
|
541
|
-
const sessionsKey = `${prefix}:sessions`;
|
|
542
637
|
const evalScript = (script, keys, args) => call("EVAL", script, keys.length, ...keys, ...args);
|
|
543
638
|
const readFrontier = async (sessionId) => {
|
|
544
639
|
const frontier = Number(await call("GET", countKey(sessionId)) ?? 0);
|
|
@@ -658,7 +753,6 @@ function createRedisStoreCore(options) {
|
|
|
658
753
|
storeKey(sessionId),
|
|
659
754
|
countKey(sessionId),
|
|
660
755
|
idsKey,
|
|
661
|
-
sessionsKey,
|
|
662
756
|
metaKey(sessionId),
|
|
663
757
|
pendingKey(sessionId),
|
|
664
758
|
readyKey(sessionId),
|
|
@@ -767,11 +861,12 @@ function createRedisStoreCore(options) {
|
|
|
767
861
|
storeKey(sessionId),
|
|
768
862
|
countKey(sessionId),
|
|
769
863
|
idsKey,
|
|
770
|
-
sessionsKey,
|
|
771
864
|
metaKey(sessionId),
|
|
772
865
|
pendingKey(sessionId),
|
|
773
866
|
readyKey(sessionId),
|
|
774
|
-
watchedKey(sessionId)
|
|
867
|
+
watchedKey(sessionId),
|
|
868
|
+
snapshotEventPinsKey(sessionId, index),
|
|
869
|
+
snapshotPinCountsKey(sessionId)
|
|
775
870
|
], [
|
|
776
871
|
sessionId,
|
|
777
872
|
now,
|
|
@@ -848,7 +943,9 @@ function createRedisStoreCore(options) {
|
|
|
848
943
|
countKey(sessionId),
|
|
849
944
|
metaKey(sessionId),
|
|
850
945
|
pendingKey(sessionId),
|
|
851
|
-
readyKey(sessionId)
|
|
946
|
+
readyKey(sessionId),
|
|
947
|
+
snapshotEventPinsKey(sessionId, index),
|
|
948
|
+
snapshotPinCountsKey(sessionId)
|
|
852
949
|
], [
|
|
853
950
|
index,
|
|
854
951
|
attempt,
|
|
@@ -864,30 +961,65 @@ function createRedisStoreCore(options) {
|
|
|
864
961
|
};
|
|
865
962
|
});
|
|
866
963
|
},
|
|
867
|
-
async readState(sessionId, reducerName) {
|
|
964
|
+
async readState(sessionId, reducerName, stateOptions) {
|
|
868
965
|
return wrap(async () => {
|
|
869
|
-
const reply = await evalScript(READ_STATE_LUA, [
|
|
870
|
-
|
|
966
|
+
const reply = await evalScript(READ_STATE_LUA, [
|
|
967
|
+
snapKey(sessionId, reducerName),
|
|
968
|
+
snapHistoryKey(sessionId, reducerName),
|
|
969
|
+
countKey(sessionId)
|
|
970
|
+
], [stateOptions?.snapshotThroughIndex ?? stateOptions?.throughIndex ?? ""]);
|
|
971
|
+
const rawHead = reply[0];
|
|
972
|
+
const raw = reply[1];
|
|
973
|
+
const head = rawHead === "" ? null : JSON.parse(rawHead);
|
|
871
974
|
const parsed = raw === "" ? null : JSON.parse(raw);
|
|
872
|
-
const
|
|
873
|
-
if (!Number.isSafeInteger(
|
|
874
|
-
const
|
|
975
|
+
const currentFrontier = Number(reply[2]);
|
|
976
|
+
if (!Number.isSafeInteger(currentFrontier) || currentFrontier < 0 || head !== null && (!Number.isSafeInteger(head.index) || head.index < 0) || parsed !== null && (!Number.isSafeInteger(parsed.index) || parsed.index < 0)) throw new A2Error("STORE_UNAVAILABLE", `redis state frontier for session '${sessionId}' is invalid`);
|
|
977
|
+
const frontier = Math.min(currentFrontier, stateOptions?.throughIndex ?? currentFrontier);
|
|
978
|
+
const snapshot = parsed;
|
|
979
|
+
const events = await readEventRange(sessionId, snapshot?.index ?? 0, frontier);
|
|
875
980
|
return {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
981
|
+
headIndex: head?.index ?? null,
|
|
982
|
+
snapshot: snapshot ? {
|
|
983
|
+
index: snapshot.index,
|
|
984
|
+
state: snapshot.state
|
|
879
985
|
} : null,
|
|
880
986
|
events
|
|
881
987
|
};
|
|
882
988
|
});
|
|
883
989
|
},
|
|
884
|
-
async
|
|
990
|
+
async putSnapshots(sessionId, reducerName, writes) {
|
|
991
|
+
const byIndex = /* @__PURE__ */ new Map();
|
|
992
|
+
for (const write of writes) {
|
|
993
|
+
const existing = byIndex.get(write.index);
|
|
994
|
+
if (existing) {
|
|
995
|
+
existing.state = write.state;
|
|
996
|
+
for (const eventIndex of write.pinEventIndexes ?? []) existing.pinEventIndexes.add(eventIndex);
|
|
997
|
+
} else byIndex.set(write.index, {
|
|
998
|
+
index: write.index,
|
|
999
|
+
state: write.state,
|
|
1000
|
+
pinEventIndexes: new Set(write.pinEventIndexes ?? [])
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
const normalized = [...byIndex.values()].toSorted((a, b) => a.index - b.index);
|
|
1004
|
+
if (normalized.length === 0) return;
|
|
885
1005
|
await wrap(async () => {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1006
|
+
const args = [
|
|
1007
|
+
clock.now().getTime(),
|
|
1008
|
+
snapshotEventPinsPrefix(sessionId),
|
|
1009
|
+
normalized.length
|
|
1010
|
+
];
|
|
1011
|
+
for (const write of normalized) args.push(write.index, json({
|
|
1012
|
+
index: write.index,
|
|
1013
|
+
state: write.state,
|
|
889
1014
|
updatedAt: clock.now().getTime()
|
|
890
|
-
})
|
|
1015
|
+
}), write.pinEventIndexes.size, ...write.pinEventIndexes);
|
|
1016
|
+
await evalScript(PUT_SNAPSHOTS_LUA, [
|
|
1017
|
+
snapKey(sessionId, reducerName),
|
|
1018
|
+
snapHistoryKey(sessionId, reducerName),
|
|
1019
|
+
countKey(sessionId),
|
|
1020
|
+
metaKey(sessionId),
|
|
1021
|
+
snapshotPinCountsKey(sessionId)
|
|
1022
|
+
], args);
|
|
891
1023
|
});
|
|
892
1024
|
},
|
|
893
1025
|
presence: {
|
|
@@ -942,86 +1074,6 @@ function createRedisStoreCore(options) {
|
|
|
942
1074
|
});
|
|
943
1075
|
}
|
|
944
1076
|
},
|
|
945
|
-
inspect: {
|
|
946
|
-
async listSessions(inspectionOptions) {
|
|
947
|
-
return wrap(async () => {
|
|
948
|
-
const minimum = inspectionOptions.cursor === void 0 ? `[${inspectionOptions.prefix}` : `(${inspectionOptions.cursor}`;
|
|
949
|
-
const candidates = await call("ZRANGEBYLEX", sessionsKey, minimum, "+", "LIMIT", 0, inspectionOptions.limit + 1);
|
|
950
|
-
const sessionIds = candidates.filter((id) => id.startsWith(inspectionOptions.prefix)).slice(0, inspectionOptions.limit);
|
|
951
|
-
return {
|
|
952
|
-
sessions: (await Promise.all(sessionIds.map(async (sessionId) => {
|
|
953
|
-
const rows = await readRange(sessionId, 0);
|
|
954
|
-
const first = rows[0];
|
|
955
|
-
let updatedAt = first.createdAt;
|
|
956
|
-
let pendingCount = 0;
|
|
957
|
-
let failedCount = 0;
|
|
958
|
-
let attemptCount = 0;
|
|
959
|
-
let failureCount = 0;
|
|
960
|
-
for (const row of rows) {
|
|
961
|
-
attemptCount += row.attemptCount;
|
|
962
|
-
failureCount += row.failureCount;
|
|
963
|
-
if (row.processedAt === null && row.failedAt === null) pendingCount += 1;
|
|
964
|
-
if (row.failedAt !== null) failedCount += 1;
|
|
965
|
-
for (const timestamp of [
|
|
966
|
-
row.createdAt,
|
|
967
|
-
row.firstClaimedAt,
|
|
968
|
-
row.lastClaimedAt,
|
|
969
|
-
row.lastFailedAt,
|
|
970
|
-
row.processedAt,
|
|
971
|
-
row.failedAt
|
|
972
|
-
]) if (timestamp && timestamp > updatedAt) updatedAt = timestamp;
|
|
973
|
-
}
|
|
974
|
-
return {
|
|
975
|
-
sessionId,
|
|
976
|
-
eventCount: rows.length,
|
|
977
|
-
pendingCount,
|
|
978
|
-
failedCount,
|
|
979
|
-
attemptCount,
|
|
980
|
-
failureCount,
|
|
981
|
-
firstEventAt: first.createdAt,
|
|
982
|
-
updatedAt
|
|
983
|
-
};
|
|
984
|
-
}))).toSorted((a, b) => a.sessionId.localeCompare(b.sessionId)),
|
|
985
|
-
cursor: candidates.length > inspectionOptions.limit && candidates[inspectionOptions.limit]?.startsWith(inspectionOptions.prefix) ? sessionIds.at(-1) ?? null : null
|
|
986
|
-
};
|
|
987
|
-
});
|
|
988
|
-
},
|
|
989
|
-
async readEvents(sessionId, pageOptions) {
|
|
990
|
-
return wrap(async () => {
|
|
991
|
-
const currentFrontier = await readFrontier(sessionId);
|
|
992
|
-
const throughIndex = Math.min(pageOptions.throughIndex ?? currentFrontier, currentFrontier);
|
|
993
|
-
const events = await readRangePage(sessionId, pageOptions.afterIndex, throughIndex, pageOptions.limit);
|
|
994
|
-
if (throughIndex > pageOptions.afterIndex && events.length === 0) throw new A2Error("STORE_UNAVAILABLE", `redis event range for session '${sessionId}' ended before index ${throughIndex}`);
|
|
995
|
-
for (let position = 0; position < events.length; position += 1) if (events[position].index !== pageOptions.afterIndex + position + 1) throw new A2Error("STORE_UNAVAILABLE", `redis event range for session '${sessionId}' is not contiguous`);
|
|
996
|
-
return {
|
|
997
|
-
events,
|
|
998
|
-
throughIndex
|
|
999
|
-
};
|
|
1000
|
-
});
|
|
1001
|
-
},
|
|
1002
|
-
async listSnapshots(sessionId) {
|
|
1003
|
-
return wrap(async () => {
|
|
1004
|
-
const start = `${prefix}:${sessionId}:snap:`;
|
|
1005
|
-
let cursor = "0";
|
|
1006
|
-
const keys = [];
|
|
1007
|
-
do {
|
|
1008
|
-
const reply = await call("SCAN", cursor, "MATCH", `${escapeGlob(start)}*`, "COUNT", 100);
|
|
1009
|
-
cursor = reply[0];
|
|
1010
|
-
keys.push(...reply[1].filter((key) => key.startsWith(start)));
|
|
1011
|
-
} while (cursor !== "0");
|
|
1012
|
-
return (await Promise.all(keys.map(async (key) => {
|
|
1013
|
-
const raw = await call("GET", key);
|
|
1014
|
-
if (raw === null) return null;
|
|
1015
|
-
const parsed = JSON.parse(raw);
|
|
1016
|
-
return {
|
|
1017
|
-
reducerName: key.slice(start.length),
|
|
1018
|
-
index: parsed.index,
|
|
1019
|
-
updatedAt: new Date(parsed.updatedAt ?? 0)
|
|
1020
|
-
};
|
|
1021
|
-
}))).filter((snapshot) => snapshot !== null).toSorted((a, b) => a.reducerName.localeCompare(b.reducerName));
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
},
|
|
1025
1077
|
async readRange(sessionId, afterIndex, throughIndex) {
|
|
1026
1078
|
return wrap(() => readRange(sessionId, afterIndex, throughIndex));
|
|
1027
1079
|
},
|
|
@@ -1038,4 +1090,4 @@ function createRedisStoreCore(options) {
|
|
|
1038
1090
|
//#endregion
|
|
1039
1091
|
export { createRedisStoreCore as t };
|
|
1040
1092
|
|
|
1041
|
-
//# sourceMappingURL=store-redis-core-
|
|
1093
|
+
//# sourceMappingURL=store-redis-core-DT01r4GZ.js.map
|