experimental-a2 0.10.0 → 0.12.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 +45 -0
- package/dist/actor-DJi3RsNu.d.ts +310 -0
- package/dist/actor-DJi3RsNu.d.ts.map +1 -0
- package/dist/actor-client.d.ts +13 -4
- package/dist/actor-client.d.ts.map +1 -1
- package/dist/actor-client.js +63 -7
- package/dist/actor-client.js.map +1 -1
- package/dist/actor-react.d.ts +5 -4
- package/dist/actor-react.d.ts.map +1 -1
- package/dist/actor-react.js +16 -2
- package/dist/actor-react.js.map +1 -1
- package/dist/actor.d.ts +2 -176
- package/dist/actor.js +13 -2
- package/dist/actor.js.map +1 -1
- package/dist/ai-server.d.ts +1 -1
- package/dist/ai-server.js +2 -2
- package/dist/ai.d.ts +1 -1
- package/dist/ai.js +1 -1
- package/dist/client-Bf6uSEAk.js +1342 -0
- package/dist/client-Bf6uSEAk.js.map +1 -0
- package/dist/client-P_NNNRM-.d.ts +243 -0
- package/dist/client-P_NNNRM-.d.ts.map +1 -0
- package/dist/client.d.ts +2 -208
- package/dist/client.js +2 -1206
- package/dist/errors-DCk6ch5n.js.map +1 -1
- package/dist/errors-DvhSXnxk.d.ts +28 -0
- package/dist/errors-DvhSXnxk.d.ts.map +1 -0
- package/dist/index.d.ts +3 -35
- package/dist/{internal-DRXJ56EI.js → internal-Dq2qYxou.js} +2 -2
- package/dist/{internal-DRXJ56EI.js.map → internal-Dq2qYxou.js.map} +1 -1
- package/dist/platform-B4TnJtWu.js +34 -0
- package/dist/platform-B4TnJtWu.js.map +1 -0
- package/dist/react.d.ts +3 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +2 -1
- package/dist/react.js.map +1 -1
- package/dist/scheduler-qstash.d.ts +2 -2
- package/dist/scheduler-qstash.js +3 -2
- package/dist/scheduler-qstash.js.map +1 -1
- package/dist/scheduler-vercel.d.ts +2 -2
- package/dist/scheduler-vercel.js +2 -2
- package/dist/{server-DlLyvaSH.js → server-BeNADlCI.js} +171 -65
- package/dist/server-BeNADlCI.js.map +1 -0
- package/dist/{server-DgCrSuhB.d.ts → server-DjZZa1wr.d.ts} +9 -3
- package/dist/{server-DgCrSuhB.d.ts.map → server-DjZZa1wr.d.ts.map} +1 -1
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -2
- package/dist/{store-DGHeBtIQ.d.ts → store-DtDOWLSn.d.ts} +4 -5
- package/dist/{store-DGHeBtIQ.d.ts.map → store-DtDOWLSn.d.ts.map} +1 -1
- package/dist/store-N8PXxDAS.js.map +1 -1
- package/dist/store-memory.d.ts +1 -1
- package/dist/store-memory.js +1 -1
- package/dist/{store-polling-6DW7F1DT.js → store-polling-CmxUbV93.js} +56 -7
- package/dist/store-polling-CmxUbV93.js.map +1 -0
- package/dist/store-postgres.d.ts +6 -4
- package/dist/store-postgres.d.ts.map +1 -1
- package/dist/store-postgres.js +701 -53
- package/dist/store-postgres.js.map +1 -1
- package/dist/store-presence-polling-C7-XZyW9.js +94 -0
- package/dist/store-presence-polling-C7-XZyW9.js.map +1 -0
- package/dist/store-redis-http.d.ts +2 -2
- package/dist/store-redis-http.d.ts.map +1 -1
- package/dist/store-redis-http.js +184 -38
- package/dist/store-redis-http.js.map +1 -1
- package/dist/{store-redis-core-z-ykbyMg.js → store-redis-notify-BUCyXOn0.js} +491 -27
- package/dist/store-redis-notify-BUCyXOn0.js.map +1 -0
- package/dist/store-redis.d.ts +5 -13
- package/dist/store-redis.d.ts.map +1 -1
- package/dist/store-redis.js +27 -271
- package/dist/store-redis.js.map +1 -1
- package/dist/store-sqlite.d.ts +1 -1
- package/dist/store-sqlite.js +1 -1
- package/dist/{wire--yji6mO3.js → wire-BO5wWCb1.js} +18 -16
- package/dist/wire-BO5wWCb1.js.map +1 -0
- package/docs/actors/04-routes.mdx +40 -8
- package/docs/guides/03-react.mdx +52 -10
- package/docs/guides/05-production.mdx +54 -5
- package/docs/guides/09-presence.mdx +12 -7
- package/docs/guides/10-transports.mdx +15 -2
- package/docs/reference/01-api.mdx +124 -17
- package/docs/reference/02-errors.mdx +45 -11
- package/examples/playground/package.json +1 -1
- package/package.json +1 -1
- package/src/actor-client.ts +114 -14
- package/src/actor-react.ts +18 -3
- package/src/actor.ts +16 -1
- package/src/client-errors.ts +51 -0
- package/src/client.ts +253 -96
- package/src/errors.ts +4 -9
- package/src/index.ts +1 -1
- package/src/internal.ts +1 -1
- package/src/postgres-notification-scope.ts +134 -0
- package/src/postgres-notifications.ts +244 -0
- package/src/postgres-pool.ts +65 -0
- package/src/postgres-resources.ts +185 -0
- package/src/presence-recovery.ts +120 -0
- package/src/react.ts +3 -0
- package/src/redis-http-subscriptions.ts +199 -0
- package/src/server.ts +160 -22
- package/src/session-socket.ts +25 -7
- package/src/sse.ts +17 -0
- package/src/store-polling.ts +32 -12
- package/src/store-postgres.ts +292 -75
- package/src/store-presence-polling.ts +125 -0
- package/src/store-redis-core.ts +37 -30
- package/src/store-redis-http.ts +51 -45
- package/src/store-redis-notify.ts +464 -0
- package/src/store-redis.ts +9 -364
- package/src/store.ts +3 -4
- package/src/stream-activity.ts +32 -0
- package/src/wire.ts +39 -15
- package/dist/actor-shared-USo5MyuF.d.ts +0 -136
- package/dist/actor-shared-USo5MyuF.d.ts.map +0 -1
- package/dist/actor.d.ts.map +0 -1
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/server-DlLyvaSH.js.map +0 -1
- package/dist/store-polling-6DW7F1DT.js.map +0 -1
- package/dist/store-redis-core-z-ykbyMg.js.map +0 -1
- package/dist/wire--yji6mO3.js.map +0 -1
package/src/session-socket.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { A2Error } from './errors.ts'
|
|
10
10
|
import { SOCKET_TIMINGS, STREAM_TIMINGS } from './internal.ts'
|
|
11
|
+
import { streamActivities } from './stream-activity.ts'
|
|
11
12
|
import type { Event } from './store.ts'
|
|
12
13
|
import type { EventDefs, PresencePatch, PresenceSnapshot } from './contract.ts'
|
|
13
14
|
import type { ParsedPushEvent, ParsedPushPresence } from './push-envelope.ts'
|
|
@@ -312,6 +313,7 @@ const handleSocketPush = async (
|
|
|
312
313
|
req: number,
|
|
313
314
|
ackSessionId: string | undefined,
|
|
314
315
|
gatePush: SessionsSocketOptions['gatePush'],
|
|
316
|
+
activate: () => void,
|
|
315
317
|
): Promise<void> => {
|
|
316
318
|
try {
|
|
317
319
|
const events = parsePushEvents(frame['events'])
|
|
@@ -320,8 +322,10 @@ const handleSocketPush = async (
|
|
|
320
322
|
shell.send(socketErrorAckFor(req, denial, ackSessionId))
|
|
321
323
|
return
|
|
322
324
|
}
|
|
325
|
+
activate()
|
|
323
326
|
// oxlint-disable-next-line a2/await-handler-append -- this transport append is awaited before its ack
|
|
324
327
|
const appended = await target.append(...events)
|
|
328
|
+
activate()
|
|
325
329
|
shell.send(socketAckFor(req, appended, ackSessionId))
|
|
326
330
|
} catch (error) {
|
|
327
331
|
shell.send(socketErrorAckFor(req, asA2Error(error), ackSessionId))
|
|
@@ -418,6 +422,7 @@ export function sessionSocket<D extends EventDefs>(
|
|
|
418
422
|
req,
|
|
419
423
|
undefined,
|
|
420
424
|
options?.gatePush,
|
|
425
|
+
() => streamActivities.get(subscription.iterator)?.activate(),
|
|
421
426
|
),
|
|
422
427
|
)
|
|
423
428
|
return
|
|
@@ -511,10 +516,10 @@ export function sessionsSocket<D extends EventDefs>(
|
|
|
511
516
|
if (shell.torn() || subscription.stopped) return
|
|
512
517
|
retire(id, subscription)
|
|
513
518
|
shell.send(socketUnsubscribedFor(id))
|
|
514
|
-
} catch {
|
|
519
|
+
} catch (error) {
|
|
515
520
|
if (shell.torn() || subscription.stopped) return
|
|
516
521
|
retire(id, subscription)
|
|
517
|
-
shell.send(socketUnsubscribedFor(id, 'stream failed'))
|
|
522
|
+
shell.send(socketUnsubscribedFor(id, 'stream failed', asA2Error(error)))
|
|
518
523
|
}
|
|
519
524
|
}
|
|
520
525
|
|
|
@@ -522,23 +527,32 @@ export function sessionsSocket<D extends EventDefs>(
|
|
|
522
527
|
id: string,
|
|
523
528
|
index: number,
|
|
524
529
|
): Promise<void> => {
|
|
530
|
+
const existing = subscriptions.get(id)
|
|
531
|
+
if (existing) {
|
|
532
|
+
stopSubscription(existing)
|
|
533
|
+
subscriptions.delete(id)
|
|
534
|
+
}
|
|
525
535
|
let session: Pick<Session<D>, 'stream'> | null
|
|
526
536
|
try {
|
|
527
537
|
session = await resolve(id, index)
|
|
528
|
-
} catch {
|
|
538
|
+
} catch (error) {
|
|
529
539
|
if (!shell.torn()) {
|
|
530
|
-
shell.send(socketUnsubscribedFor(id, 'stream failed'))
|
|
540
|
+
shell.send(socketUnsubscribedFor(id, 'stream failed', asA2Error(error)))
|
|
531
541
|
}
|
|
532
542
|
return
|
|
533
543
|
}
|
|
534
544
|
if (shell.torn()) return
|
|
535
545
|
if (session === null) {
|
|
536
|
-
shell.send(
|
|
546
|
+
shell.send(
|
|
547
|
+
socketUnsubscribedFor(
|
|
548
|
+
id,
|
|
549
|
+
'subscribe rejected',
|
|
550
|
+
new A2Error('FORBIDDEN', 'the subscription is not authorized'),
|
|
551
|
+
),
|
|
552
|
+
)
|
|
537
553
|
return
|
|
538
554
|
}
|
|
539
555
|
const target = session as unknown as SocketSession
|
|
540
|
-
const existing = subscriptions.get(id)
|
|
541
|
-
if (existing) stopSubscription(existing)
|
|
542
556
|
const subscription = socketSubscription(target, index, presence)
|
|
543
557
|
subscriptions.set(id, subscription)
|
|
544
558
|
shell.send(socketSubscribedFor(id))
|
|
@@ -582,6 +596,10 @@ export function sessionsSocket<D extends EventDefs>(
|
|
|
582
596
|
req,
|
|
583
597
|
sessionId,
|
|
584
598
|
options?.gatePush,
|
|
599
|
+
() => {
|
|
600
|
+
const current = subscriptions.get(sessionId)
|
|
601
|
+
if (current) streamActivities.get(current.iterator)?.activate()
|
|
602
|
+
},
|
|
585
603
|
)
|
|
586
604
|
}
|
|
587
605
|
|
package/src/sse.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { STREAM_TIMINGS } from './internal.ts'
|
|
|
10
10
|
import type { Event } from './store.ts'
|
|
11
11
|
import type { PresencePatch, PresenceSnapshot } from './contract.ts'
|
|
12
12
|
import { invocationDeadlineMs } from './platform.ts'
|
|
13
|
+
import { streamActivities } from './stream-activity.ts'
|
|
13
14
|
import {
|
|
14
15
|
eventToWire,
|
|
15
16
|
presencePatchToWire,
|
|
@@ -41,7 +42,10 @@ export function sseResponse(
|
|
|
41
42
|
let deadlineTimer: ReturnType<typeof setTimeout> | undefined
|
|
42
43
|
let closed = false
|
|
43
44
|
let iteratorReturned = false
|
|
45
|
+
let stopActivity: (() => void) | undefined
|
|
44
46
|
const stopTimers = (): void => {
|
|
47
|
+
stopActivity?.()
|
|
48
|
+
stopActivity = undefined
|
|
45
49
|
if (heartbeat !== undefined) clearInterval(heartbeat)
|
|
46
50
|
if (deadlineTimer !== undefined) clearTimeout(deadlineTimer)
|
|
47
51
|
heartbeat = undefined
|
|
@@ -61,6 +65,19 @@ export function sseResponse(
|
|
|
61
65
|
// early proof this is a stream. Comment frames are invisible to
|
|
62
66
|
// SSE consumers.
|
|
63
67
|
controller.enqueue(encoder.encode(': connected\n\n'))
|
|
68
|
+
const activity = streamActivities.get(iterator)
|
|
69
|
+
if (activity) {
|
|
70
|
+
const announce = (idle: boolean): void => {
|
|
71
|
+
if (closed) return
|
|
72
|
+
controller.enqueue(
|
|
73
|
+
encoder.encode(
|
|
74
|
+
`event: a2-activity\ndata: ${idle ? 'idle' : 'active'}\n\n`,
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
stopActivity = activity.subscribe(announce)
|
|
79
|
+
if (activity.idle) announce(true)
|
|
80
|
+
}
|
|
64
81
|
// Heartbeat: comment frames are ignored by SSE parsers but are
|
|
65
82
|
// bytes on the wire — liveness proof for the client's watchdog
|
|
66
83
|
// and for idle-timeout-happy intermediaries.
|
package/src/store-polling.ts
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { POLL_TIMINGS } from './internal.ts'
|
|
17
|
-
import type {
|
|
17
|
+
import type { Event } from './store.ts'
|
|
18
|
+
import { createStreamActivity, streamActivities } from './stream-activity.ts'
|
|
18
19
|
|
|
19
20
|
/** One cancellable sleep — the injection seam for cadence tests. */
|
|
20
21
|
export type Sleeper = { promise: Promise<void>; cancel: () => void }
|
|
@@ -56,47 +57,66 @@ export type PollingStreamOptions = {
|
|
|
56
57
|
* SSE client), waking any pending sleep immediately.
|
|
57
58
|
*/
|
|
58
59
|
export function pollingStream(
|
|
59
|
-
readAfter: (afterIndex: number) =>
|
|
60
|
+
readAfter: (afterIndex: number) => Event[] | Promise<Event[]>,
|
|
60
61
|
options: PollingStreamOptions,
|
|
61
|
-
): AsyncIterable<
|
|
62
|
+
): AsyncIterable<Event> {
|
|
62
63
|
const startAfter = options.startAfter ?? 0
|
|
63
64
|
const sleep = options.sleep ?? defaultSleep
|
|
64
65
|
|
|
65
66
|
return {
|
|
66
|
-
[Symbol.asyncIterator](): AsyncIterator<
|
|
67
|
+
[Symbol.asyncIterator](): AsyncIterator<Event> {
|
|
67
68
|
let last = startAfter
|
|
68
|
-
let buffer:
|
|
69
|
+
let buffer: Event[] = []
|
|
69
70
|
let closed = false
|
|
70
71
|
let pending: Sleeper | null = null
|
|
71
72
|
let interval = floor()
|
|
73
|
+
let activityVersion = 0
|
|
74
|
+
const activity = createStreamActivity(() => {
|
|
75
|
+
if (closed) return
|
|
76
|
+
activityVersion += 1
|
|
77
|
+
interval = floor()
|
|
78
|
+
pending?.cancel()
|
|
79
|
+
})
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
async next(): Promise<IteratorResult<
|
|
81
|
+
const iterator: AsyncIterator<Event> = {
|
|
82
|
+
async next(): Promise<IteratorResult<Event>> {
|
|
75
83
|
for (;;) {
|
|
76
84
|
if (closed) return { value: undefined, done: true }
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
try {
|
|
86
|
+
// oxlint-disable-next-line no-await-in-loop -- poll loop
|
|
87
|
+
if (buffer.length === 0) buffer = await readAfter(last)
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (closed) return { value: undefined, done: true }
|
|
90
|
+
throw error
|
|
91
|
+
}
|
|
92
|
+
if (closed) return { value: undefined, done: true }
|
|
79
93
|
const row = buffer.shift()
|
|
80
94
|
if (row) {
|
|
81
95
|
// Activity: tighten back to the floor.
|
|
82
|
-
|
|
96
|
+
activity.activate()
|
|
83
97
|
last = row.index
|
|
84
98
|
return { value: row, done: false }
|
|
85
99
|
}
|
|
86
100
|
// Empty poll: wait, then back off toward the idle ceiling.
|
|
101
|
+
activity.setIdle(interval === ceiling())
|
|
102
|
+
const version = activityVersion
|
|
87
103
|
pending = sleep(interval)
|
|
88
104
|
// oxlint-disable-next-line no-await-in-loop -- poll loop
|
|
89
105
|
await pending.promise
|
|
90
106
|
pending = null
|
|
91
|
-
|
|
107
|
+
if (version === activityVersion) {
|
|
108
|
+
interval = Math.min(interval * 2, ceiling())
|
|
109
|
+
}
|
|
92
110
|
}
|
|
93
111
|
},
|
|
94
|
-
async return(): Promise<IteratorResult<
|
|
112
|
+
async return(): Promise<IteratorResult<Event>> {
|
|
95
113
|
closed = true
|
|
96
114
|
pending?.cancel()
|
|
97
115
|
return { value: undefined, done: true }
|
|
98
116
|
},
|
|
99
117
|
}
|
|
118
|
+
streamActivities.set(iterator, activity)
|
|
119
|
+
return iterator
|
|
100
120
|
},
|
|
101
121
|
}
|
|
102
122
|
}
|