2025-07-08 16:49:52 -07:00
|
|
|
# @peoplesgrocers/lseq
|
|
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
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.
|
2025-07-08 16:49:52 -07:00
|
|
|
|
|
|
|
|
## Installation
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
npm install @peoplesgrocers/lseq
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Usage
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-12-12 22:44:01 -08:00
|
|
|
import { LSEQ } from '@peoplesgrocers/lseq';
|
2025-07-08 16:49:52 -07:00
|
|
|
|
|
|
|
|
const lseq = new LSEQ();
|
|
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
const first = lseq.alloc(null, null);
|
|
|
|
|
const second = lseq.alloc(first, null);
|
|
|
|
|
const between = lseq.alloc(first, second);
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
// String comparison gives correct order
|
|
|
|
|
[second, first, between].sort(); // [first, between, second]
|
2025-07-08 16:49:52 -07:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## API
|
|
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
### `new LSEQ(random?: () => number)`
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
Create an allocator. Accepts an optional random function for deterministic testing.
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
### `lseq.alloc(before: string | null, after: string | null): string`
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
Allocate a key between `before` and `after`.
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
- `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
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
### `compareLSEQ(a: string, b: string): number`
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
Compare two keys. Returns -1, 0, or 1.
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
### `EvenSpacingIterator`
|
|
|
|
|
|
|
|
|
|
Generate evenly-spaced keys for bulk initialization:
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
import { EvenSpacingIterator } from '@peoplesgrocers/lseq';
|
|
|
|
|
|
|
|
|
|
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
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
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.
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
## References
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
- [LSEQ: an Adaptive Structure for Sequences in Distributed Collaborative Editing](https://hal.science/hal-00921633/document) (Nédelec et al., 2013)
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
## License
|
2025-07-08 16:49:52 -07:00
|
|
|
|
2025-12-12 22:44:01 -08:00
|
|
|
AGPL-3.0
|