UNPKG

position-strings

Version:

Lexicographically-ordered position strings for collaborative lists and text

128 lines (127 loc) 4.98 kB
/** * A source of lexicographically-ordered "position strings" for * collaborative lists and text. * * In a collaborative list (or text string), you need a way to refer * to "positions" within that list that: * 1. Point to a specific list element (or text character). * 2. Are global (all users agree on them) and immutable (they do not * change over time). * 3. Can be sorted. * 4. Are unique, even if different users concurrently create positions * at the same place. * * `PositionSource` gives you such positions, in the form * of lexicographically-ordered strings. Specifically, `createBetween` * returns a new "position string" in between two existing position strings. * * These strings have the bonus properties: * - 5. (Non-Interleaving) If two `PositionSource`s concurrently create a (forward or backward) * sequence of positions at the same place, * their sequences will not be interleaved. * For example, if * Alice types "Hello" while Bob types "World" at the same place, * and they each use a `PositionSource` to create a position for each * character, then * the resulting order will be "HelloWorld" or "WorldHello", not * "HWeolrllod". * - 6. If a `PositionSource` creates positions in a forward (increasing) * sequence, their lengths as strings will only grow logarithmically, * not linearly. * * Position strings are printable ASCII. Specifically, they * contain alphanumeric characters, `','`, and `'.'`. * Also, the special string `PositionSource.LAST` is `'~'`. * * Further reading: * - [Fractional indexing](https://www.figma.com/blog/realtime-editing-of-ordered-sequences/#fractional-indexing), * a related scheme that satisfies 1-3 but not 4-6. * - [List CRDTs](https://mattweidner.com/2022/10/21/basic-list-crdt.html) * and how they map to position strings. `PositionSource` uses an optimized * variant of that link's string implementation. * - [Paper about interleaving](https://www.repository.cam.ac.uk/handle/1810/290391) * in collaborative text editors. */ export declare class PositionSource { /** * A string that is less than all positions. * * Value: `""`. */ static readonly FIRST: string; /** * A string that is greater than all positions. * * Value: `"~"`. */ static readonly LAST: string; /** * The unique ID for this `PositionSource`. */ readonly ID: string; /** * Our waypoints' long name: `,${ID}.`. */ private readonly longName; /** * Variant of longName used for a position's first ID: `${ID}.`. * (Otherwise every position would start with a redundant ','.) */ private readonly firstName; /** * For each waypoint that we created, maps a prefix (see getPrefix) * for that waypoint to its last (most recent) valueSeq. * We always store the right-side version (odd valueSeq). */ private lastValueSeqs; /** * Constructs a new `PositionSource`. * * It is okay to share a single `PositionSource` between * all documents (lists/text strings) in the same JavaScript runtime. * * For efficiency (shorter position strings), * within each JavaScript runtime, you should not use * more than one `PositionSource` for the same document. * An exception is if multiple logical users share the same runtime; * we then recommend one `PositionSource` per user. * * @param options.ID A unique ID for this `PositionSource`. Defaults to * `IDs.random()`. * * If provided, `options.ID` must satisfy: * - It is unique across the entire collaborative application, i.e., * all `PositionSource`s whose positions may be compared to ours. This * includes past `PositionSource`s, even if they correspond to the same * user/device. * - It does not contain `','` or `'.'`. * - The first character is lexicographically less than `'~'` (code point 126). * * If `options.ID` contains non-alphanumeric characters, then created * positions will contain those characters in addition to * alphanumeric characters, `','`, and `'.'`. */ constructor(options?: { ID?: string; }); /** * Returns a new position between `left` and `right` * (`left < new < right`). * * The new position is unique across the entire collaborative application, * even in the face of concurrent calls to this method on other * `PositionSource`s. * * @param left Defaults to `PositionSource.FIRST` (insert at the beginning). * * @param right Defaults to `PositionSource.LAST` (insert at the end). */ createBetween(left?: string, right?: string): string; /** * Appends a wayoint to the given ancestor (= prefix adjusted for * side), returning a unique new position using that waypoint. * * lastValueSeqs is also updated as needed for the waypoint. */ private appendWaypoint; }