Incoming changes
The server's changes as your app receives them, applied as the server's or held for a person to look at first.
Your app receives the server's changes some way (a socket, a relay, a poll of its own). api.data.stream() is where
they come into the grid. They apply as the server's, or they wait while someone works and apply when that person
chooses. The grid needs no collaboration service, and the stream brings no transport of its own.
import type { GridApi } from '@witbybit/wit-grid-react';
type Invoice = { id: string; status: string; amount: number };
declare const api: GridApi<Invoice>;
declare const socket: { on(event: 'change', listener: (message: { id: string; amount: number; version: number }) => void): void };
const incoming = api.data.stream();
socket.on('change', ({ id, amount, version }) => incoming.push({ patched: [{ id, fields: { amount }, version }] }));push takes the shape a pull returns: upserted (whole records), patched (changed fields, by path) and
removed, each with an optional version. The server's changes have one shape, however they arrive.
The server's, not an edit
Applying a change is one commit from the server:
- No setters,
parse, capabilities or rules decide it. The rules mark what arrives, as they mark any row that arrives, and never refuse it. A server's value is never reinterpreted as if a person had typed it. - Nothing is queued to save, nothing is sent back, and undo does not step through it.
- With
data.save, it takes the pull's own path:- A field changed only here keeps its queued value until the server answers that change.
- A field changed on both sides is decided by
data.save.resolve(by default, a conflict for a person). - The record's version is kept, and this device's own change coming back is recognised as such.
- Without
data.save, a field someone is editing, or one already in conflict, keeps this side's value, and the server's value waits as a conflict. - A draft on the record keeps its values. A value the server changed under one of them
shows in the draft's
conflicts.
Versions in order
By default changes apply in the order they arrive, and a change repeating its record's last version is ignored. When
your server's versions order, say how, once, and every server change of a record is judged by it: the stream, the
pull, a save's answer and (with data.save) a load.
import { createClientGrid } from '@witbybit/wit-grid-react';
const api = createClientGrid<{ id: string; amount: number }>({
rows: [],
columns: [{ field: 'amount', header: 'Amount' }],
getRowId: (row) => row.id,
data: { order: { compare: 'number' } }, // or (a, b) => a < b ? -1 : a > b ? 1 : 0, or null when they cannot compare
});- An older version than the record's here is stale: never applied, and it changes nothing remembered.
- The same version applies once. A change without a version applies in arrival order.
- A version that cannot be compared (a string under
'number',nullfrom your comparator) is incomparable: not applied. Text is never ordered as text: an ETag says which version, not which is newer. - A removal with a version is remembered (
tombstones, default 10,000), so an older change cannot bring the record back; a newer one does. When room runs out the oldest is forgotten (forgottenin the status) — replaying older events past that window needs a reload. - Replacing the records (
setRows, a new datasource) forgets what was remembered. getStatus().data.incomingcountsstale,incomparableandforgottenbesiderepeated.
Holding
import type { GridApi } from '@witbybit/wit-grid-react';
declare const api: GridApi;
const incoming = api.data.stream({ hold: true }); // or incoming.hold() at any time
incoming.held().records; // per record: kind, version, arrivedAt, and each field's current and incoming value
await incoming.apply(['INV-1042']); // these records' changes now, still holding
incoming.discard(['INV-1043']); // dropped: the server's record still changed, so reload to catch up
await incoming.release(); // everything waiting, in arrival order; from then on changes apply as they come
api.getStatus().data.incoming; // { holding, held, applied, repeated, refreshNeeded, revision }- Against the work here: each held field has its
currentvalue, theincomingone, andlocal.localis'draft'when a draft holds a value for that field,'unsaved'when a change to it waits to be saved, and null otherwise. Applying keeps unsaved work: the server's answer decides it. - Per record, in arrival order:
- Patches to one record merge.
- A removal replaces anything before it, and an add after a removal is an add.
- A patch after an add patches the added record.
- A change that repeats the record's last version is ignored and counted in
repeated. - Within one push, whole records apply first, then patches, then removals.
- Records are independent. Applying some records' changes never reorders a record's own. If your records depend on each other, apply them all.
- Bounded: at most
maxHeldrecords wait (10,000 by default). Past that limit the stream keeps no more and setsrefreshNeeded. What already waits still applies; reload the records and open a new stream. Changes are never dropped silently, and a release applies 500 records per frame instead of all at once. destroy()drops what waits without applying it. Opening a new stream closes the one before.
What holding does not hold
Holding covers only what this stream receives. These are never held:
- the server's answers to a save
- refused changes
- the rules and the capabilities
- the connection
data.save.pull is not routed through the stream either: pulled changes apply as they come.
Last updated on