lib0
Version:
isomorphic utility functions
204 lines • 12.3 kB
TypeScript
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