Streaming Partial streams

Partial streams

Update one fixed row ID from partial async-iterable values.

Use connectPartialStream when one row grows over time, such as a token-streamed assistant message. Row msg-1 below is seeded before its stream connects, so that connection only ever patches it. Row msg-2 isn't seeded — its first partial is reported through onIssue, then createRow turns the accumulated changes into the row. Both fill in one partial at a time:

Partial row stream

Grow one row's content cell in place with connectPartialStream, covering both the seeded-row and createRow/onIssue patterns.

.md

Signature

ts
function connectPartialStream<
  TRow extends object,
  TRowId extends string | number,
>(
  rowModel: RowModelLike<TRow, TRowId>,
  stream: AsyncIterable<Partial<TRow>>,
  options: PartialStreamOptions<TRow, TRowId>,
): StreamConnection;

Every partial becomes an exact { id: options.rowId, changes: partial } update.

Existing row

msg-1 above is seeded in the row model's initial rows before its connection is made; seeding through a transaction after creation works the same way:

ts
rowModel.applyTransaction({
  add: [{ id: "msg-1", role: "assistant", content: "", tokens: 0 }],
});
 
const connection = connectPartialStream(rowModel, partials, {
  rowId: "msg-1",
  onIssue: (issue) => console.warn(issue.code, issue.rowId),
});

Create a missing row explicitly

The connector never asserts that a partial is a complete row. Supply a factory when creation is allowed — msg-2 above follows exactly this pattern:

ts
connectPartialStream(rowModel, partials, {
  rowId: "msg-2",
  createRow(partial, id) {
    return {
      id,
      role: "assistant",
      content: partial.content ?? "",
      tokens: partial.tokens ?? 0,
    };
  },
});

onIssue fires whether or not createRow is supplied — the connector reports the unknown target first, then builds the row if it has a factory. That ordering is what msg-2 shows above: it logs the unknown-update-id warning for its first partial and then gains its row. Without createRow, the report is all that happens and no row is fabricated.

createRow receives the changes accumulated across that frame, not just the one partial that triggered it: partials arriving in the same animation frame are batched into a single transaction, so a row created on the first frame already carries every field that landed in it.

Lifecycle

done resolves when the iterator finishes or the connection is disposed. Source and model failures reject it with the original error.