Skip to content

Commit 9516fc2

Browse files
committed
feat: default last_event_id to None (live events only)
Gateway no longer sends last_event_id in the join payload by default, so fresh connections receive only live events without replaying history. Pass last_event_id explicitly to opt into replay. Also restructures README to separate basic Gateway usage from the catch-up-on-missed-events workflow.
1 parent f0d29dc commit 9516fc2

2 files changed

Lines changed: 47 additions & 37 deletions

File tree

README.md

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,38 +30,43 @@ asyncio.run(main())
3030

3131
## Gateway (real-time events)
3232

33-
Pass a `client` to the Gateway so it can automatically catch up on missed events
34-
via the REST API if the bot has been offline too long (>100 events). Without a
35-
client, a `TooManyMissedEventsError` is raised and you must handle catch-up
36-
yourself.
37-
3833
```python
3934
import stackcoin
4035

41-
async with stackcoin.Client(token="...") as client:
42-
gateway = stackcoin.Gateway(token="...", client=client)
36+
gateway = stackcoin.Gateway(token="...")
4337

44-
@gateway.on("transfer.completed")
45-
async def on_transfer(event: stackcoin.TransferCompletedEvent):
46-
print(f"Transfer of {event.data.amount} STK from #{event.data.from_id} to #{event.data.to_id}")
38+
@gateway.on("transfer.completed")
39+
async def on_transfer(event: stackcoin.TransferCompletedEvent):
40+
print(f"Transfer of {event.data.amount} STK from #{event.data.from_id} to #{event.data.to_id}")
4741

48-
@gateway.on("request.accepted")
49-
async def on_accepted(event: stackcoin.RequestAcceptedEvent):
50-
print(f"Request #{event.data.request_id} accepted")
42+
@gateway.on("request.accepted")
43+
async def on_accepted(event: stackcoin.RequestAcceptedEvent):
44+
print(f"Request #{event.data.request_id} accepted")
5145

52-
await gateway.connect()
46+
await gateway.connect()
5347
```
5448

55-
The Gateway also accepts `last_event_id` to resume from a known cursor, and
56-
`on_event_id` as a callback to persist the cursor position after each event:
49+
## Catching up on missed events
50+
51+
If your bot persists its cursor position and reconnects with a `last_event_id`,
52+
the server replays up to 100 missed events. If more than 100 were missed, the
53+
join is rejected — pass a `client` so the Gateway can automatically catch up
54+
via the REST API. Without a client, a `TooManyMissedEventsError` is raised.
5755

5856
```python
59-
gateway = stackcoin.Gateway(
60-
token="...",
61-
client=client,
62-
last_event_id=saved_cursor,
63-
on_event_id=lambda eid: save_cursor(eid),
64-
)
57+
async with stackcoin.Client(token="...") as client:
58+
gateway = stackcoin.Gateway(
59+
token="...",
60+
client=client,
61+
last_event_id=saved_cursor,
62+
on_event_id=lambda eid: save_cursor(eid),
63+
)
64+
65+
@gateway.on("transfer.completed")
66+
async def on_transfer(event: stackcoin.TransferCompletedEvent):
67+
...
68+
69+
await gateway.connect()
6570
```
6671

6772
## Examples

src/stackcoin/gateway.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ class Gateway:
2626
2727
Usage::
2828
29-
async with stackcoin.Client(token="...") as client:
30-
gateway = stackcoin.Gateway(token="...", client=client)
29+
gateway = stackcoin.Gateway(token="...")
3130
32-
@gateway.on("request.accepted")
33-
async def handle_accepted(event: stackcoin.RequestAcceptedEvent):
34-
print(event.data.request_id)
31+
@gateway.on("request.accepted")
32+
async def handle_accepted(event: stackcoin.RequestAcceptedEvent):
33+
print(event.data.request_id)
3534
36-
await gateway.connect()
35+
await gateway.connect()
3736
38-
If a ``client`` is provided and the bot has been offline too long (>100
39-
missed events), the gateway automatically catches up via the REST API
40-
before reconnecting. Without a ``client``, the error is raised to the
41-
caller.
37+
By default, the gateway receives only live events. Pass ``last_event_id``
38+
to replay missed events from a cursor position. If more than 100 events
39+
were missed and a ``client`` is provided, the gateway automatically catches
40+
up via the REST API before reconnecting. Without a ``client``, a
41+
``TooManyMissedEventsError`` is raised.
4242
"""
4343

4444
def __init__(
@@ -47,7 +47,7 @@ def __init__(
4747
*,
4848
ws_url: str = "wss://stackcoin.world/ws",
4949
client: Client | None = None,
50-
last_event_id: int = 0,
50+
last_event_id: int | None = None,
5151
on_event_id: Callable[[int], None] | None = None,
5252
):
5353
self._ws_url = ws_url.rstrip("/")
@@ -61,7 +61,7 @@ def __init__(
6161
self._ref_counter = 0
6262

6363
@property
64-
def last_event_id(self) -> int:
64+
def last_event_id(self) -> int | None:
6565
return self._last_event_id
6666

6767
def on(self, event_type: str) -> Callable[[_F], _F]:
@@ -133,13 +133,15 @@ async def _catch_up_via_rest(self) -> None:
133133
"""
134134
if self._client is None:
135135
raise RuntimeError("Cannot catch up via REST without a client")
136-
events = await self._client.get_events(since_id=self._last_event_id)
136+
# _last_event_id is always an int here — TooManyMissedEventsError only
137+
# fires when a last_event_id was sent in the join payload.
138+
events = await self._client.get_events(since_id=self._last_event_id or 0)
137139
for event in events:
138140
await self._dispatch_event(event)
139141

140142
async def _dispatch_event(self, typed_event: AnyEvent) -> None:
141143
"""Dispatch a typed event to registered handlers and update the cursor."""
142-
if typed_event.id > self._last_event_id:
144+
if self._last_event_id is None or typed_event.id > self._last_event_id:
143145
self._last_event_id = typed_event.id
144146

145147
for handler in self._handlers.get(typed_event.type, []):
@@ -159,13 +161,16 @@ async def _join_channel(self, ws: Any) -> None:
159161
from .errors import TooManyMissedEventsError
160162

161163
self._ref_counter += 1
164+
join_payload: dict[str, Any] = {}
165+
if self._last_event_id is not None:
166+
join_payload["last_event_id"] = self._last_event_id
162167
join_msg = json.dumps(
163168
[
164169
None,
165170
str(self._ref_counter),
166171
"user:self",
167172
"phx_join",
168-
{"last_event_id": self._last_event_id},
173+
join_payload,
169174
]
170175
)
171176
await ws.send(join_msg)

0 commit comments

Comments
 (0)