Summary
@netscript/fresh/streams ships the generic StreamDB shape consumer (createNetScriptStreamDB + useLiveQuery) and documents it via the live-dashboard tutorial (a sagas / worker-executions data table) and web-layer/defer-streaming-ui.md. There is no durable-CHAT integration and no guidance that AI chat needs a different pattern. A developer building durable AI chat on netscript naturally reaches for the only documented streams-client surface (createNetScriptStreamDB + useLiveQuery) to back a chat transcript — and it does not work in the browser.
Symptom (real, from the eis-chat #13 build, alpha.18)
Backing a per-session chat transcript with a StreamDB collection — createNetScriptStreamDB({ streamPath, schema }) + useLiveQuery over a message collection — the server producer writes turns fine and the durable read endpoint returns the full transcript over plain HTTP (verified: GET /v1/stream/netscript/…/messages returns the rows, access-control-allow-origin: *, no auth required). But in the browser the ElectricSQL shape consumer throws:
[StreamDB] Stream consumer closed unexpectedly: TypeError: Decoding failed. (db.js:271)
[chat-stream] preload FAILED TypeError: Decoding failed.
preload() rejects, useLiveQuery never populates, and the transcript only appears after a full page reload (the SSR seed). No optimistic user turn, no live tokens, no resume. The error is Electric's TextDecoder({ fatal: true }) on the shape stream (content-encoding: gzip; multibyte model output such as — / …). It is inherent to using the StreamDB shape consumer to back a chat transcript in the browser.
Root cause — wrong abstraction
StreamDB shapes (createNetScriptStreamDB + useLiveQuery) are for reactive data tables — exactly the sagas / worker-executions monitor the live-dashboard tutorial demonstrates. AI chat is a different pattern: it needs a TanStack-AI connection adapter that subscribes to the durable message stream and drives useChat (giving optimistic + live + resume + multi-tab). Per TanStack-AI's own docs (chat/connection-adapters.md, "Persistent Transports"), durable/multi-tab chat is a SubscribeConnectionAdapter passed to useChat — not a useLiveQuery shape. netscript ships the shape half and documents only that, so chat gets built on the wrong primitive.
The correct, first-party pattern (ElectricSQL "Durable Sessions")
- Package
@durable-streams/tanstack-ai-transport:
- client
durableStreamConnection({ sendUrl, readUrl, initialOffset }) → useChat({ id, connection, live: true })
- server
toDurableChatSessionResponse({ stream: { writeUrl, headers }, newMessages, responseStream })
- The adapter swaps request/response for a sync paradigm: the chat client fetches history, then subscribes to the durable stream — optimistic + live + resume + multi-tab, natively, through
useChat.
readUrl is a proxy route the app builds (forwards offset + live + read auth to the durable read endpoint).
- Reference implementation:
electric-sql/collaborative-ai-editor (chat + tool calls + model streams via the Durable Session pattern).
- Docs:
durablestreams.com/tanstack-ai, electric-sql.com/blog/2026/01/12/durable-sessions-for-collaborative-ai, electric-sql.com/blog/2026/03/24/durable-transport-ai-sdks.
Ask
- Ship a first-class durable-CHAT integration in
@netscript/fresh/streams — re-export / wrap @durable-streams/tanstack-ai-transport (durableStreamConnection + toDurableChatSessionResponse) with netscript URL/auth resolution (the same getStreamsUrl / getStreamsAuth / buildStreamUrl seam createNetScriptStreamDB already uses), plus a documented /api/chat-stream proxy recipe. Given @netscript/plugin-streams is the durable-streams runtime, the AI transport is the missing client half.
- Docs: explicitly distinguish StreamDB-shapes (data tables) vs Durable-Sessions (AI chat) in
capabilities/streams.md + web-layer/defer-streaming-ui.md + the live-dashboard tutorial, and steer AI chat AWAY from createNetScriptStreamDB / useLiveQuery toward the durable connection adapter + useChat. Right now the only streams-client example is the shape consumer, which is a footgun for chat.
- Possibly a bug to verify upstream: the generic
createNetScriptStreamDB shape consumer throws TypeError: Decoding failed on gzip'd / multibyte shape streams in the browser (@durable-streams/state@0.3.1, db.js:271). Worth confirming whether the shape decoder needs TextDecoder({ stream: true }) or whether the streams runtime should not content-encoding: gzip a streaming shape — independent of the chat pattern, it affects any non-ASCII shape.
Context: eis-chat #13 durable-stream chat, netscript alpha.18. Related: the streams service is a proxy that forwards the durable-streams engine's content-encoding (plugins/streams/services/src/main.ts).
Part of #238
Summary
@netscript/fresh/streamsships the generic StreamDB shape consumer (createNetScriptStreamDB+useLiveQuery) and documents it via the live-dashboard tutorial (a sagas / worker-executions data table) andweb-layer/defer-streaming-ui.md. There is no durable-CHAT integration and no guidance that AI chat needs a different pattern. A developer building durable AI chat on netscript naturally reaches for the only documented streams-client surface (createNetScriptStreamDB+useLiveQuery) to back a chat transcript — and it does not work in the browser.Symptom (real, from the eis-chat #13 build, alpha.18)
Backing a per-session chat transcript with a StreamDB collection —
createNetScriptStreamDB({ streamPath, schema })+useLiveQueryover amessagecollection — the server producer writes turns fine and the durable read endpoint returns the full transcript over plain HTTP (verified:GET /v1/stream/netscript/…/messagesreturns the rows,access-control-allow-origin: *, no auth required). But in the browser the ElectricSQL shape consumer throws:preload()rejects,useLiveQuerynever populates, and the transcript only appears after a full page reload (the SSR seed). No optimistic user turn, no live tokens, no resume. The error is Electric'sTextDecoder({ fatal: true })on the shape stream (content-encoding: gzip; multibyte model output such as—/…). It is inherent to using the StreamDB shape consumer to back a chat transcript in the browser.Root cause — wrong abstraction
StreamDB shapes (
createNetScriptStreamDB+useLiveQuery) are for reactive data tables — exactly the sagas / worker-executions monitor the live-dashboard tutorial demonstrates. AI chat is a different pattern: it needs a TanStack-AI connection adapter that subscribes to the durable message stream and drivesuseChat(giving optimistic + live + resume + multi-tab). Per TanStack-AI's own docs (chat/connection-adapters.md, "Persistent Transports"), durable/multi-tab chat is aSubscribeConnectionAdapterpassed touseChat— not auseLiveQueryshape. netscript ships the shape half and documents only that, so chat gets built on the wrong primitive.The correct, first-party pattern (ElectricSQL "Durable Sessions")
@durable-streams/tanstack-ai-transport:durableStreamConnection({ sendUrl, readUrl, initialOffset })→useChat({ id, connection, live: true })toDurableChatSessionResponse({ stream: { writeUrl, headers }, newMessages, responseStream })useChat.readUrlis a proxy route the app builds (forwardsoffset+live+ read auth to the durable read endpoint).electric-sql/collaborative-ai-editor(chat + tool calls + model streams via the Durable Session pattern).durablestreams.com/tanstack-ai,electric-sql.com/blog/2026/01/12/durable-sessions-for-collaborative-ai,electric-sql.com/blog/2026/03/24/durable-transport-ai-sdks.Ask
@netscript/fresh/streams— re-export / wrap@durable-streams/tanstack-ai-transport(durableStreamConnection+toDurableChatSessionResponse) with netscript URL/auth resolution (the samegetStreamsUrl/getStreamsAuth/buildStreamUrlseamcreateNetScriptStreamDBalready uses), plus a documented/api/chat-streamproxy recipe. Given@netscript/plugin-streamsis the durable-streams runtime, the AI transport is the missing client half.capabilities/streams.md+web-layer/defer-streaming-ui.md+ the live-dashboard tutorial, and steer AI chat AWAY fromcreateNetScriptStreamDB/useLiveQuerytoward the durable connection adapter +useChat. Right now the only streams-client example is the shape consumer, which is a footgun for chat.createNetScriptStreamDBshape consumer throwsTypeError: Decoding failedon gzip'd / multibyte shape streams in the browser (@durable-streams/state@0.3.1,db.js:271). Worth confirming whether the shape decoder needsTextDecoder({ stream: true })or whether the streams runtime should notcontent-encoding: gzipa streaming shape — independent of the chat pattern, it affects any non-ASCII shape.Context: eis-chat #13 durable-stream chat, netscript alpha.18. Related: the streams service is a proxy that forwards the durable-streams engine's
content-encoding(plugins/streams/services/src/main.ts).Part of #238