refactor: improve explanation on readme
This commit is contained in:
parent
ca0fc09a01
commit
3eef15ba0b
2 changed files with 63 additions and 31 deletions
|
|
@ -1,6 +1,18 @@
|
||||||
# @peoplesgrocers/lseq
|
# @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
|
## Installation
|
||||||
|
|
||||||
|
|
@ -11,51 +23,71 @@ npm install @peoplesgrocers/lseq
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { LSEQ, compareLSEQ } from '@peoplesgrocers/lseq';
|
import { LSEQ } from '@peoplesgrocers/lseq';
|
||||||
|
|
||||||
// Create a new L-SEQ instance
|
|
||||||
const lseq = new LSEQ();
|
const lseq = new LSEQ();
|
||||||
|
|
||||||
// Allocate identifiers
|
const first = lseq.alloc(null, null);
|
||||||
const id1 = lseq.alloc(null, null); // First identifier
|
const second = lseq.alloc(first, null);
|
||||||
const id2 = lseq.alloc(id1, null); // After id1
|
const between = lseq.alloc(first, second);
|
||||||
const id3 = lseq.alloc(id1, id2); // Between id1 and id2
|
|
||||||
|
|
||||||
// Sort identifiers
|
// String comparison gives correct order
|
||||||
const ids = [id3, id1, id2];
|
[second, first, between].sort(); // [first, between, second]
|
||||||
ids.sort(compareLSEQ);
|
|
||||||
console.log(ids); // [id1, id3, id2] - properly ordered
|
|
||||||
|
|
||||||
// Custom random function (useful for deterministic testing)
|
|
||||||
const deterministicLSEQ = new LSEQ(() => 0.5);
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## API
|
## 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`
|
- `lseq.alloc(null, null)` — first key in an empty list
|
||||||
|
- `lseq.alloc(null, first)` — insert at head
|
||||||
Allocates a new identifier between two existing identifiers.
|
- `lseq.alloc(last, null)` — insert at tail
|
||||||
|
- `lseq.alloc(a, b)` — insert between a and b
|
||||||
- `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`
|
|
||||||
|
|
||||||
### `compareLSEQ(a: string, b: string): number`
|
### `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
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@peoplesgrocers/lseq",
|
"name": "@peoplesgrocers/lseq",
|
||||||
"version": "0.99.0",
|
"version": "0.99.1",
|
||||||
"description": "L-SEQ algorithm implementation for fractional indexing and list CRDTs",
|
"description": "L-SEQ algorithm implementation for fractional indexing and list CRDTs",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue