UNPKG

lib0

Version:

isomorphic utility functions

204 lines • 12.3 kB
export { deltaRDT } from "./rdt/delta.js"; /** * @template T * @typedef {import('../schema.js').Schema<T>} Schema */ /** * Abstract interface for a delta-based Replicated Data Type. * * An RDT is an observable that emits a `'delta'` (and, on teardown, a `'destroy'`) event, exposes the * {@link Schema schema} of the deltas it produces via `$delta` (so a {@link Binding} can initialize a * transformer for it), exposes its current state as a delta via the `delta` getter, and accepts foreign * deltas via `applyDelta`. * * `applyDelta(d, origin)` applies `d` and then, if the change would violate the RDT's own invariants, * applies a **fix** of its own (e.g. reversing part of `d`, or inserting missing content) and returns * that fix — `null` when none was needed. The fix is a regular change on this RDT, so a {@link Binding} * maps it onto the other side just like any other change. `applyDelta` also emits the *effective* change * (`d` together with the fix) on the `'delta'` channel. * * ## Origins * * Every `'delta'` event carries a second argument, `origin` — an opaque `any` value (modelled after * {@link https://docs.yjs.dev/api/transactions Yjs transaction origins}) that identifies **where the * change came from**. Whoever produces the change sets it, and it is usually the producing instance * itself: a communication provider, an editor binding, or the {@link Binding} that mapped the change * over from the other side. A change an RDT observes locally (e.g. a `MutationObserver` firing on a DOM * edit) uses that RDT itself as the origin. * * Origins let a listener tell **its own** changes apart from foreign ones so it can skip the ones it * already knows about — a change it produced is not looped back to where it came from. The canonical use * is a network provider: it tags every remote update it applies with itself (`rdt.applyDelta(update, * this)`) and then ignores `'delta'` events whose `origin === this`, so it never re-broadcasts an update * it just received. The `origin` argument to `applyDelta` is optional and defaults to `null` (an * anonymous/local change); the emitted event always carries whatever value was supplied. * * A {@link Binding} distinguishes the two kinds of changes it produces. An ordinary change mapped * over from the other side carries the binding itself as its origin. A change that adjusts a side's * own earlier change after the fact — a fix returned by the opposite RDT transformed back, or a * transformer self-heal — carries {@link correctionOrigin}, so a consumer that applied a delta can recognise * that (part of) its change was reverted or amended. * * ## `Delta` vs `DeltaBuilder` * * The interface is parameterized by a {@link import('./delta.js').DeltaConf DeltaConf} (like a * {@link import('./transformer.js').Transformer transformer}). Almost everything it exposes is the * *read* form — {@link import('./delta.js').Delta Delta} — because those values are **shared** and must * not be mutated in place: `$delta` is the delta schema, the `delta` getter returns a state snapshot, * `applyDelta` reads a change into the RDT, and the `'delta'` event payload is broadcast to every * listener. The one *owned, mutable* form — {@link import('./delta.js').DeltaBuilder DeltaBuilder} — is * the fix `applyDelta` returns to the {@link Binding}. The binding never mutates a shared read delta: it * {@link import('./delta.js').cloneDeep deep-clones} every change into a private builder before the * transformer rebases it (transformers manipulate deltas in place — see {@link Binding}). * * @template {import('./delta.js').DeltaConf} Conf * @typedef {import('../observable.js').ObservableV2<{ delta: (delta: delta.Delta<Conf>, origin: any) => void, destroy: (rdt: RDT<Conf>) => void }> & { * $delta: Schema<delta.Delta<Conf>>, * delta: delta.Delta<Conf>, * applyDelta: (delta: delta.Delta<Conf>, origin?: any) => delta.DeltaBuilder<Conf> | null, * destroy: () => void * }} RDT */ /** * Schema guard for any {@link RDT}. RDTs are **duck-typed** — they have several independent * implementations (`deltaRDT`, `domRDT`, …) and no common constructor — so this matches by shape * rather than by `instanceof`: an object exposing a `$delta` {@link Schema}, an `applyDelta` method, * and the observable `on`/`destroy` channel. The (possibly expensive) `delta` getter is not invoked. * * @type {Schema<RDT<any>>} */ export const $rdt: Schema<RDT<any>>; /** * The {@link RDT origin} carried by every change a {@link Binding} applies as an after-the-fact * adjustment to a side's own change: a fix returned by the opposite RDT's `applyDelta` transformed back * onto the side that produced the original change, a transformer self-heal (including during the * initial-state sync), and every later round of the fix-convergence loop (fixes of fixes). A "fix" * covers invariant corrections as well as rebased concurrent edits (see `./rdt/dom.js`). An ordinary * mapped change carries the binding itself instead — see the "Origins" section of {@link RDT}. The * value is a plain string, so `origin === 'correction'` works without importing this constant. * * @type {'correction'} */ export const correctionOrigin: "correction"; /** * @typedef {object} BindOptions * @property {delta.DiffOptions['compare']} [diffCompare] **Experimental** (may be changed or removed * in a future release). Forwarded as `compare` to {@link delta.diff} when the initial-state sync diffs * `a`'s projection against `b`'s current state, controlling how nodes are paired into `modify` ops. */ /** * Connects two RDTs so that changes on either side are transformed and reflected on the other. * * The {@link dt.Transformer transformer} it drives **manipulates deltas in place** (it splices op lists * and rebases nodes directly, rather than only via `apply`/`rebase`) — a deliberate exception to the * general rule that deltas are edited through `apply`/`rebase`. Consequently every change handed to it * must be a privately-owned, fully-mutable delta, so the binding {@link delta.cloneDeep deep-clones} * each incoming change (which is a shared read delta — an event payload or a snapshot) before the * transformer touches it. See the initial-state sync below and {@link propagate}. * * @template {delta.DeltaConf} A * @template {delta.DeltaConf} B */ export class Binding<A extends delta.DeltaConf, B extends delta.DeltaConf> { /** * @param {RDT<A>} a * @param {RDT<B>} b * @param {dt.TemplateFactory<A,B>} [template] a `$d => Template` factory; defaults to the * {@link dt.id identity} transformer. `bind` injects `a`'s schema, then materializes via `.init()`. * @param {BindOptions} [options] */ constructor(a: RDT<A>, b: RDT<B>, template?: dt.TemplateFactory<A, B>, options?: BindOptions); /** * @type {dt.Transformer<A,B>} */ t: dt.Transformer<A, B>; /** * @type {RDT<A>} */ a: RDT<A>; /** * @type {RDT<B>} */ b: RDT<B>; _mux: mux.mutex; _achanged: (delta: delta.Delta<A>, origin: any) => void; _bchanged: (delta: delta.Delta<B>, origin: any) => void; /** * Tear the binding down: unsubscribe both sides' `'delta'` and `'destroy'` listeners so changes are no * longer propagated. Call this when you no longer need the two RDTs kept in sync. It is also invoked * automatically when either bound RDT emits `'destroy'`. The RDTs themselves are NOT destroyed. */ destroy: () => void; } export function bind<A extends delta.DeltaConf, B extends delta.DeltaConf>(a: RDT<A>, b: RDT<B>, template?: dt.TemplateFactory<A, B>, options?: BindOptions): Binding<A, B>; export type Schema<T> = import("../schema.js").Schema<T>; /** * Abstract interface for a delta-based Replicated Data Type. * * An RDT is an observable that emits a `'delta'` (and, on teardown, a `'destroy'`) event, exposes the * {@link Schema schema} of the deltas it produces via `$delta` (so a {@link Binding} can initialize a * transformer for it), exposes its current state as a delta via the `delta` getter, and accepts foreign * deltas via `applyDelta`. * * `applyDelta(d, origin)` applies `d` and then, if the change would violate the RDT's own invariants, * applies a **fix** of its own (e.g. reversing part of `d`, or inserting missing content) and returns * that fix — `null` when none was needed. The fix is a regular change on this RDT, so a {@link Binding} * maps it onto the other side just like any other change. `applyDelta` also emits the *effective* change * (`d` together with the fix) on the `'delta'` channel. * * ## Origins * * Every `'delta'` event carries a second argument, `origin` — an opaque `any` value (modelled after * {@link https://docs.yjs.dev/api/transactions Yjs transaction origins}) that identifies **where the * change came from**. Whoever produces the change sets it, and it is usually the producing instance * itself: a communication provider, an editor binding, or the {@link Binding} that mapped the change * over from the other side. A change an RDT observes locally (e.g. a `MutationObserver` firing on a DOM * edit) uses that RDT itself as the origin. * * Origins let a listener tell **its own** changes apart from foreign ones so it can skip the ones it * already knows about — a change it produced is not looped back to where it came from. The canonical use * is a network provider: it tags every remote update it applies with itself (`rdt.applyDelta(update, * this)`) and then ignores `'delta'` events whose `origin === this`, so it never re-broadcasts an update * it just received. The `origin` argument to `applyDelta` is optional and defaults to `null` (an * anonymous/local change); the emitted event always carries whatever value was supplied. * * A {@link Binding} distinguishes the two kinds of changes it produces. An ordinary change mapped * over from the other side carries the binding itself as its origin. A change that adjusts a side's * own earlier change after the fact — a fix returned by the opposite RDT transformed back, or a * transformer self-heal — carries {@link correctionOrigin}, so a consumer that applied a delta can recognise * that (part of) its change was reverted or amended. * * ## `Delta` vs `DeltaBuilder` * * The interface is parameterized by a {@link import ('./delta.js').DeltaConf DeltaConf} (like a * {@link import ('./transformer.js').Transformer transformer}). Almost everything it exposes is the * *read* form — {@link import ('./delta.js').Delta Delta} — because those values are **shared** and must * not be mutated in place: `$delta` is the delta schema, the `delta` getter returns a state snapshot, * `applyDelta` reads a change into the RDT, and the `'delta'` event payload is broadcast to every * listener. The one *owned, mutable* form — {@link import ('./delta.js').DeltaBuilder DeltaBuilder} — is * the fix `applyDelta` returns to the {@link Binding}. The binding never mutates a shared read delta: it * {@link import ('./delta.js').cloneDeep deep-clones} every change into a private builder before the * transformer rebases it (transformers manipulate deltas in place — see {@link Binding}). */ export type RDT<Conf extends import("./delta.js").DeltaConf> = import("../observable.js").ObservableV2<{ delta: (delta: delta.Delta<Conf>, origin: any) => void; destroy: (rdt: RDT<Conf>) => void; }> & { $delta: Schema<delta.Delta<Conf>>; delta: delta.Delta<Conf>; applyDelta: (delta: delta.Delta<Conf>, origin?: any) => delta.DeltaBuilder<Conf> | null; destroy: () => void; }; export type BindOptions = { /** * **Experimental** (may be changed or removed * in a future release). Forwarded as `compare` to {@link delta.diff} when the initial-state sync diffs * `a`'s projection against `b`'s current state, controlling how nodes are paired into `modify` ops. */ diffCompare?: delta.DiffOptions["compare"]; }; import * as delta from './delta.js'; import * as dt from './transformer.js'; import * as mux from '../mutex.js'; export { $domDelta, domRDT } from "./rdt/dom.js"; //# sourceMappingURL=rdt.d.ts.map