Skip to content

Commit 5823a8e

Browse files
committed
feat: add SSE live transport (live-client + live-server)
- src/live-client.ts: createLiveClient (EventSource + auto-reconnect), liveCollectionOptions, useLiveCollection, per-record/field subscription filters, in-memory subscriber queue - src/live-server.ts: createLiveEventBus, createLiveSseHandler (30s heartbeat), createLiveControlHandler, per-connection handle, field-level intersection optimization - new subpaths: ./live-client, ./live-server - 41 new tests (16 client + 20 server + 5 integration); 180 total - bump to 0.0.4
1 parent 57fc91a commit 5823a8e

9 files changed

Lines changed: 2524 additions & 1 deletion

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.0.4] - 2026-06-03
9+
10+
### Added
11+
12+
- First-party SSE live transport (subpath exports `./live-client` and `./live-server`)
13+
- `createLiveClient({ url, controlUrl?, eventSourceCtor?, fetch?, reconnect?, ... })` — one per-app EventSource with automatic exponential-backoff reconnect
14+
- `liveCollectionOptions<TItem>({ id?, entity, getKey?, live, initialFetch?, fields?, onDeleteMissing? })` — TanStack DB `Collection` bound to a live stream
15+
- `useLiveCollection<TItem, View>({ collection, view? })` — reactive hook with view projection
16+
- Per-connection field-level subscription filters (`fields ∩ changed` intersection)
17+
- Per-record subscription filters (`subscribe(entity, cb, { id })`)
18+
- In-memory bounded subscriber queue with `__dropped` accounting
19+
- Resumable via `Last-Event-ID` (exposed, ready for durable backends)
20+
- `createLiveEventBus({ maxQueueSize? })` — server-side event multiplexer
21+
- `createLiveSseHandler(bus)(request)``text/event-stream` response with 30s heartbeat
22+
- `createLiveControlHandler(bus)(request)` — POST JSON for control messages (subscribe/unsubscribe)
23+
- Per-connection handle for server-pushed edge/invalidate events
24+
- Tests: 41 new tests (16 live-client, 20 live-server, 5 integration) — total 180
25+
826
## [0.0.3] - 2026-06-03
927

1028
### Added
@@ -54,3 +72,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5472
[0.0.3]: https://github.com/doeixd/tanstackstart-db/releases/tag/v0.0.3
5573
[0.0.2]: https://github.com/doeixd/tanstackstart-db/releases/tag/v0.0.2
5674
[0.0.1]: https://github.com/doeixd/tanstackstart-db/releases/tag/v0.0.1
75+
[0.0.4]: https://github.com/doeixd/tanstackstart-db/releases/tag/v0.0.4

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,82 @@ The collection exposes all pagination primitives via `collection.utils`:
579579
- `getState()` - get the current pagination state snapshot
580580
- `getCollection()` - get the underlying collection instance
581581

582+
## Live transport (SSE)
583+
584+
`@doeixd/tanstackstart-db/live-client` and `@doeixd/tanstackstart-db/live-server`
585+
provide a complete client/server transport for streaming entity updates over
586+
Server-Sent Events. The client opens a single EventSource per app; the server
587+
multiplexes entity events across connections with a per-connection bounded
588+
queue, control messages for subscribe/unsubscribe, and per-record + per-field
589+
filtering.
590+
591+
```ts
592+
// Server (e.g. a TanStack Start API route)
593+
import {
594+
createLiveEventBus,
595+
createLiveSseHandler,
596+
createLiveControlHandler,
597+
} from "@doeixd/tanstackstart-db/live-server";
598+
599+
const bus = createLiveEventBus();
600+
601+
export const GET = createLiveSseHandler(bus);
602+
export const POST = createLiveControlHandler(bus);
603+
604+
// Push a server-side event
605+
bus.update("Post", "1", { changed: ["likes"], value: { id: "1", likes: 99 } });
606+
```
607+
608+
```tsx
609+
// Client
610+
import { createCollection } from "@tanstack/db";
611+
import {
612+
createLiveClient,
613+
liveCollectionOptions,
614+
useLiveCollection,
615+
} from "@doeixd/tanstackstart-db/live-client";
616+
617+
const live = createLiveClient({ url: "/api/live" });
618+
619+
const posts = createCollection(
620+
liveCollectionOptions<Post>({
621+
id: "posts",
622+
entity: "Post",
623+
live,
624+
initialFetch: () => fetch("/api/posts").then((r) => r.json()),
625+
}),
626+
);
627+
628+
function PostList() {
629+
const list = useLiveCollection({ collection: posts, view: PostView });
630+
return (
631+
<ul>
632+
{list.map((p) => (
633+
<li key={p.id}>{p.title}</li>
634+
))}
635+
</ul>
636+
);
637+
}
638+
```
639+
640+
Field-level subscription:
641+
642+
```ts
643+
// Only receive "likes" changes for the Post entity
644+
live.subscribe(
645+
"Post",
646+
(event) => {
647+
/* ... */
648+
},
649+
{ fields: ["likes"] },
650+
);
651+
```
652+
653+
The wire format is SSE `event: entity.create|update|delete|invalid` with JSON
654+
`data:` and an optional `id:` (used as `Last-Event-ID` for resumable streams).
655+
Control messages are POSTed to `${url}/control` (or `controlUrl` if provided)
656+
as JSON: `{ type: "subscribe"|"unsubscribe", connectionId, entity, id?, fields? }`.
657+
582658
## DB file routes
583659

584660
The main application-level value of the React entrypoint is its DB file-route
@@ -800,6 +876,9 @@ expect(data.post.title).toBe("Hello");
800876
@doeixd/tanstackstart-db/react
801877
@doeixd/tanstackstart-db/server
802878
@doeixd/tanstackstart-db/testing
879+
@doeixd/tanstackstart-db/pagination
880+
@doeixd/tanstackstart-db/live-client
881+
@doeixd/tanstackstart-db/live-server
803882
@doeixd/tanstackstart-db/query-collection
804883
@doeixd/tanstackstart-db/local-storage-collection
805884
@doeixd/tanstackstart-db/sync-collection

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
{
22
"name": "@doeixd/tanstackstart-db",
3-
"version": "0.0.3",
3+
"version": "0.0.4",
44
"description": "Typed schema, view, query, action, route, and hydration helpers for TanStack DB.",
55
"keywords": [
66
"crud",
77
"cursor",
88
"database",
9+
"event-source",
910
"hydration",
1011
"infinite-scroll",
12+
"live",
1113
"optimistic",
1214
"pagination",
1315
"react",
16+
"realtime",
1417
"schema",
18+
"sse",
1519
"ssr",
1620
"tanstack",
1721
"tanstack-db",
@@ -34,6 +38,8 @@
3438
"sideEffects": false,
3539
"exports": {
3640
".": "./dist/index.mjs",
41+
"./live-client": "./dist/live-client.mjs",
42+
"./live-server": "./dist/live-server.mjs",
3743
"./local-storage-collection": "./dist/local-storage-collection.mjs",
3844
"./pagination": "./dist/pagination.mjs",
3945
"./query-collection": "./dist/query-collection.mjs",

0 commit comments

Comments
 (0)