worker.observe(a, b)
runs b each time a runs. A live click feed is a function that runs once per click, and anyone
who wants to hear about clicks observes it, the same way you observed http::on-request and
queue::on-message in earlier chapters. You’ll give the feed to a dedicated click-streamer worker
so the link worker stays focused on links.
A notification is not storage. The clicks already live in the clicks table from Chapter 3, so a
listener that joins late reads the current total with a query, then counts the notifications that
follow.
Earlier versions of this chapter used
iii-stream and pubsub. If your project still has them,
see Migrate from iii-stream and pubsub.Add the worker
Theclick-streamer worker owns the live click feed. Uncomment the Ch. 5 block under containers:
worker-compose.yaml
Host the click feed in click-streamer
The feed is click-streamer::on-click, a function that runs once per recorded click and returns
that click. What a function returns is what its observers receive, so every listener gets
{ id, code, clicked_at } for each click. click-streamer runs the feed each time
link::record_click runs, by observing it.
Create click-streamer/src/index.ts:
click-streamer/src/index.ts
- The feed returns the click.
click-streamer::on-clickbuilds the click from the three fields a listener needs and returns it. Its observers receive that value. - Only
click-streamercalls the feed.private: truemakes the engine accept calls ofclick-streamer::on-clickfrom its own worker only, so no other worker can announce a click that never happened. Other workers can still observe it, which matters in Chapter 7, when a browser tab observes it. - The listeners are capped.
onObserverAddedruns before iii accepts a new observer of the feed, and overrides iii’s default of accepting every one: past 256 listeners it throws, and the observer is rejected. announceconnects the feed tolink. It observeslink::record_clickwithcapture: ["output"], so it runs after each successful call oflink::record_clickand receives what that call returned as the payload’soutput. It then calls the feed with the click.
Return the click from link
click-streamer::announce receives what link::record_click returns, so have it return the click
it recorded. database::execute returns the new row’s id as last_insert_id; it travels with the
click so listeners can tell a new click from one they have already counted. Replace
link::record_click in link/src/index.ts:
link/src/index.ts
link worker stays focused on links: it records the click and returns it, and click-streamer
reacts to that on its own.
A live counter is best effort. iii hands each click to the listeners without waiting for them, so
the click write never waits on a slow listener, and a click a listener misses is not replayed. The
listener’s next read of the total corrects it.
Read the current total
A listener that connects now has missed every earlier click, and notifications are never replayed. Give it a query to start from: addlink::click_summary to link/src/index.ts. It returns the
number of clicks and the highest click id, so a listener knows which notifications are already
included in the total.
link/src/index.ts
click-streamer::on-click and counts clicks live.
See it work
A listener is any worker that observesclick-streamer::on-click. The scaffold ships a small one
that prints each click, commented out in click-streamer/watch.ts: uncomment its Ch. 5 block (a
fully commented file runs, prints nothing and exits). The code:
click-streamer/watch.ts
ready() resolves once the observer is active, so every click after that line reaches the watcher.
Run it from the click-streamer folder and leave it running:
POST /links answers 409 with
{"error":"code `stream-1` is already taken"}, and each redirect still records a new click.
Change the numbers in seq to create new links.
Each redirect shows up in the watcher as soon as its click row is written:
Ctrl+C. Its observer is suspended when the watcher disconnects, and iii
removes it once the grace period (30 seconds by default) ends without a reconnect.
Conclusion
Linkly now pushes every click to its listeners in real time:click-streamer observes
link::record_click and runs its click-streamer::on-click feed once per click, and every listener
observes the feed. The clicks table stays the source of truth behind link::click_summary. Next,
in Ch. 6: Move bulk data with channels, you bulk-load links from a CSV in a single
streamed upload.