refactor: improve explanation on readme

This commit is contained in:
nobody 2025-12-12 22:44:01 -08:00
commit 3eef15ba0b
Signed by: GrocerPublishAgent
GPG key ID: D460CD54A9E3AB86
2 changed files with 63 additions and 31 deletions

View file

@ -1,6 +1,18 @@
# @peoplesgrocers/lseq
TypeScript implementation of the L-SEQ algorithm for fractional indexing and list CRDTs.
TypeScript implementation of the L-SEQ algorithm for fractional indexing and
list CRDTs.
The library handles edge cases that break other more naive implementations of
fractional indexing. With this library you can always insert before the first
item or after the last item, indefinitely.
This is exactly what you need to build realtime collaborative apps with
ordering for lists or trees of items. Users can reorder or insert at arbitrary
positions but in practice people really like moving items to the top or end of
a list. So don't crash accept random crashes from other libraries when Alice
moves yet another element to the front of the list.
## Installation
@ -11,51 +23,71 @@ npm install @peoplesgrocers/lseq
## Usage
```typescript
import { LSEQ, compareLSEQ } from '@peoplesgrocers/lseq';
import { LSEQ } from '@peoplesgrocers/lseq';
// Create a new L-SEQ instance
const lseq = new LSEQ();
// Allocate identifiers
const id1 = lseq.alloc(null, null); // First identifier
const id2 = lseq.alloc(id1, null); // After id1
const id3 = lseq.alloc(id1, id2); // Between id1 and id2
const first = lseq.alloc(null, null);
const second = lseq.alloc(first, null);
const between = lseq.alloc(first, second);
// Sort identifiers
const ids = [id3, id1, id2];
ids.sort(compareLSEQ);
console.log(ids); // [id1, id3, id2] - properly ordered
// Custom random function (useful for deterministic testing)
const deterministicLSEQ = new LSEQ(() => 0.5);
// String comparison gives correct order
[second, first, between].sort(); // [first, between, second]
```
## API
### `LSEQ`
### `new LSEQ(random?: () => number)`
#### `constructor(random?: () => number)`
Create an allocator. Accepts an optional random function for deterministic testing.
Creates a new L-SEQ instance.
### `lseq.alloc(before: string | null, after: string | null): string`
- `random`: Optional custom random function (defaults to `Math.random`)
Allocate a key between `before` and `after`.
#### `alloc(before: string | null, after: string | null): string`
Allocates a new identifier between two existing identifiers.
- `before`: The identifier that should come before the new one (or `null` for beginning)
- `after`: The identifier that should come after the new one (or `null` for end)
- Returns: A new identifier that sorts between `before` and `after`
- `lseq.alloc(null, null)` — first key in an empty list
- `lseq.alloc(null, first)` — insert at head
- `lseq.alloc(last, null)` — insert at tail
- `lseq.alloc(a, b)` — insert between a and b
### `compareLSEQ(a: string, b: string): number`
Compares two L-SEQ identifiers for sorting.
Compare two keys. Returns -1, 0, or 1.
- Returns: `-1` if `a < b`, `1` if `a > b`, `0` if `a === b`
### `EvenSpacingIterator`
## How it works
Generate evenly-spaced keys for bulk initialization:
L-SEQ generates identifiers using a base-64 alphabet that maintains lexicographic ordering. Each identifier is a sequence of characters from this alphabet, and new identifiers are generated by finding space between existing ones at different depths.
```typescript
import { EvenSpacingIterator } from '@peoplesgrocers/lseq';
The algorithm uses alternating allocation strategies (bias toward min or max) at different depths to avoid degenerative cases and maintain good performance characteristics.
const { k, iterator } = EvenSpacingIterator.create(1000);
const keys = [];
for (const position of iterator) {
keys.push(EvenSpacingIterator.positionToKey(k, position));
}
```
## Design
Keys are base-64 encoded strings using the base64 url-safe alphabet
`-0-9A-Z_a-z`. There is a compact binary representation inside. Reasonablt
efficient and no serialization issues with JSON, databases, or URLs.
Standard string comparison produces correct ordering. Keys work in SQL `ORDER
BY`, database indexes, and any language with string sorting.
## Cross-language support
I wrote matching implementations for Rust, Golang, and Python; validated
against a shared conformance test suite with fuzzing. If your backend isn't
TypeScript, you can still allocate and work with these keys natively. This
makes LSEQ a reasonable building block for your application's data model.
## References
- [LSEQ: an Adaptive Structure for Sequences in Distributed Collaborative Editing](https://hal.science/hal-00921633/document) (Nédelec et al., 2013)
## License
AGPL-3.0