@quereus/plugin-sync 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -0
- package/dist/src/clock/hlc.d.ts +105 -0
- package/dist/src/clock/hlc.d.ts.map +1 -0
- package/dist/src/clock/hlc.js +251 -0
- package/dist/src/clock/hlc.js.map +1 -0
- package/dist/src/clock/index.d.ts +6 -0
- package/dist/src/clock/index.d.ts.map +1 -0
- package/dist/src/clock/index.js +6 -0
- package/dist/src/clock/index.js.map +1 -0
- package/dist/src/clock/site.d.ts +58 -0
- package/dist/src/clock/site.d.ts.map +1 -0
- package/dist/src/clock/site.js +137 -0
- package/dist/src/clock/site.js.map +1 -0
- package/dist/src/create-sync-module.d.ts +85 -0
- package/dist/src/create-sync-module.d.ts.map +1 -0
- package/dist/src/create-sync-module.js +54 -0
- package/dist/src/create-sync-module.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +42 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/metadata/change-log.d.ts +67 -0
- package/dist/src/metadata/change-log.d.ts.map +1 -0
- package/dist/src/metadata/change-log.js +107 -0
- package/dist/src/metadata/change-log.js.map +1 -0
- package/dist/src/metadata/column-version.d.ts +58 -0
- package/dist/src/metadata/column-version.d.ts.map +1 -0
- package/dist/src/metadata/column-version.js +100 -0
- package/dist/src/metadata/column-version.js.map +1 -0
- package/dist/src/metadata/index.d.ts +11 -0
- package/dist/src/metadata/index.d.ts.map +1 -0
- package/dist/src/metadata/index.js +11 -0
- package/dist/src/metadata/index.js.map +1 -0
- package/dist/src/metadata/keys.d.ts +180 -0
- package/dist/src/metadata/keys.d.ts.map +1 -0
- package/dist/src/metadata/keys.js +390 -0
- package/dist/src/metadata/keys.js.map +1 -0
- package/dist/src/metadata/peer-state.d.ts +52 -0
- package/dist/src/metadata/peer-state.d.ts.map +1 -0
- package/dist/src/metadata/peer-state.js +87 -0
- package/dist/src/metadata/peer-state.js.map +1 -0
- package/dist/src/metadata/schema-migration.d.ts +60 -0
- package/dist/src/metadata/schema-migration.d.ts.map +1 -0
- package/dist/src/metadata/schema-migration.js +126 -0
- package/dist/src/metadata/schema-migration.js.map +1 -0
- package/dist/src/metadata/schema-version.d.ts +163 -0
- package/dist/src/metadata/schema-version.d.ts.map +1 -0
- package/dist/src/metadata/schema-version.js +307 -0
- package/dist/src/metadata/schema-version.js.map +1 -0
- package/dist/src/metadata/tombstones.d.ts +67 -0
- package/dist/src/metadata/tombstones.d.ts.map +1 -0
- package/dist/src/metadata/tombstones.js +125 -0
- package/dist/src/metadata/tombstones.js.map +1 -0
- package/dist/src/sync/events.d.ts +117 -0
- package/dist/src/sync/events.d.ts.map +1 -0
- package/dist/src/sync/events.js +56 -0
- package/dist/src/sync/events.js.map +1 -0
- package/dist/src/sync/index.d.ts +8 -0
- package/dist/src/sync/index.d.ts.map +1 -0
- package/dist/src/sync/index.js +8 -0
- package/dist/src/sync/index.js.map +1 -0
- package/dist/src/sync/manager.d.ts +146 -0
- package/dist/src/sync/manager.d.ts.map +1 -0
- package/dist/src/sync/manager.js +8 -0
- package/dist/src/sync/manager.js.map +1 -0
- package/dist/src/sync/protocol.d.ts +282 -0
- package/dist/src/sync/protocol.d.ts.map +1 -0
- package/dist/src/sync/protocol.js +16 -0
- package/dist/src/sync/protocol.js.map +1 -0
- package/dist/src/sync/store-adapter.d.ts +42 -0
- package/dist/src/sync/store-adapter.d.ts.map +1 -0
- package/dist/src/sync/store-adapter.js +232 -0
- package/dist/src/sync/store-adapter.js.map +1 -0
- package/dist/src/sync/sync-manager-impl.d.ts +91 -0
- package/dist/src/sync/sync-manager-impl.d.ts.map +1 -0
- package/dist/src/sync/sync-manager-impl.js +1123 -0
- package/dist/src/sync/sync-manager-impl.js.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @quereus/plugin-sync
|
|
2
|
+
|
|
3
|
+
CRDT-based multi-master sync plugin for [Quereus](https://github.com/gotchoices/quereus). Enables offline-first applications with automatic conflict resolution.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multi-master replication**: Any replica can write, changes merge automatically
|
|
8
|
+
- **Column-level LWW**: Last-Write-Wins at the column level for fine-grained conflict resolution
|
|
9
|
+
- **Hybrid Logical Clocks**: Causally-ordered timestamps that work offline
|
|
10
|
+
- **Transport agnostic**: Bring your own WebSocket, HTTP, or WebRTC transport
|
|
11
|
+
- **Offline-first**: Local changes sync when connectivity returns
|
|
12
|
+
- **Schema sync**: DDL changes (CREATE TABLE, ALTER TABLE) propagate across replicas
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @quereus/plugin-sync @quereus/plugin-store
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Database } from '@quereus/quereus';
|
|
24
|
+
import { StoreEventEmitter, LevelDBStore } from '@quereus/plugin-store';
|
|
25
|
+
import { createSyncModule, createStoreAdapter } from '@quereus/plugin-sync';
|
|
26
|
+
|
|
27
|
+
// Create the store with event emitter
|
|
28
|
+
const storeEvents = new StoreEventEmitter();
|
|
29
|
+
const store = await LevelDBStore.open({ path: './data' });
|
|
30
|
+
|
|
31
|
+
// Create sync-enabled module
|
|
32
|
+
const { syncModule, syncManager, syncEvents } = createSyncModule(store, storeEvents);
|
|
33
|
+
|
|
34
|
+
// Register with database
|
|
35
|
+
const db = new Database();
|
|
36
|
+
db.registerVtabModule('store', syncModule);
|
|
37
|
+
|
|
38
|
+
// Create tables and use normally - all changes are tracked
|
|
39
|
+
await db.exec(`
|
|
40
|
+
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)
|
|
41
|
+
USING store
|
|
42
|
+
`);
|
|
43
|
+
|
|
44
|
+
await db.exec(`INSERT INTO users (id, name, email) VALUES (1, 'Alice', 'alice@example.com')`);
|
|
45
|
+
|
|
46
|
+
// Get changes to send to another replica
|
|
47
|
+
const changes = await syncManager.getChangesSince(peerSiteId);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Architecture
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
54
|
+
│ Application │
|
|
55
|
+
├─────────────────────────────────────────────────────────────┤
|
|
56
|
+
│ SyncManager │
|
|
57
|
+
│ ├── HLCManager (Hybrid Logical Clock) │
|
|
58
|
+
│ ├── ColumnVersionStore (LWW metadata) │
|
|
59
|
+
│ ├── TombstoneStore (deletion tracking) │
|
|
60
|
+
│ ├── ChangeLogStore (HLC-indexed changes) │
|
|
61
|
+
│ └── PeerStateStore (delta sync state) │
|
|
62
|
+
├─────────────────────────────────────────────────────────────┤
|
|
63
|
+
│ @quereus/plugin-store (KVStore) │
|
|
64
|
+
└─────────────────────────────────────────────────────────────┘
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Sync Protocol
|
|
68
|
+
|
|
69
|
+
### Delta Sync
|
|
70
|
+
|
|
71
|
+
When replicas have synced before:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// Get changes since last sync
|
|
75
|
+
const changes = await syncManager.getChangesSince(peerSiteId);
|
|
76
|
+
|
|
77
|
+
// Apply received changes
|
|
78
|
+
const result = await syncManager.applyChanges(changeSets, applyToStoreCallback);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Snapshot Sync
|
|
82
|
+
|
|
83
|
+
For new replicas or when delta sync isn't available:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Stream snapshot chunks
|
|
87
|
+
for await (const chunk of syncManager.streamSnapshot({ chunkSize: 1000 })) {
|
|
88
|
+
sendToPeer(chunk);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Apply received snapshot
|
|
92
|
+
await syncManager.applyStreamedSnapshot(chunks, applyToStoreCallback);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Events
|
|
96
|
+
|
|
97
|
+
Subscribe to sync events for UI updates:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
syncEvents.onLocalChange((event) => {
|
|
101
|
+
console.log('Local change:', event.tableName, event.type);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
syncEvents.onRemoteChange((event) => {
|
|
105
|
+
console.log('Remote change:', event.tableName, event.type);
|
|
106
|
+
refreshUI();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
syncEvents.onConflict((event) => {
|
|
110
|
+
console.log('Conflict resolved:', event.resolution);
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Conflict Resolution
|
|
115
|
+
|
|
116
|
+
Conflicts are resolved automatically using Last-Write-Wins at the column level:
|
|
117
|
+
|
|
118
|
+
- Each column has an associated HLC timestamp
|
|
119
|
+
- When merging, the column with the higher HLC wins
|
|
120
|
+
- Ties are broken by site ID (deterministic ordering)
|
|
121
|
+
|
|
122
|
+
This means concurrent updates to *different* columns of the same row both apply, while updates to the *same* column use the latest value.
|
|
123
|
+
|
|
124
|
+
## API
|
|
125
|
+
|
|
126
|
+
### Core Exports
|
|
127
|
+
|
|
128
|
+
- `createSyncModule(store, storeEvents, config?)` - Factory to create sync-enabled store module
|
|
129
|
+
- `createStoreAdapter(db, store, storeEvents)` - Adapter for applying remote changes to store
|
|
130
|
+
- `SyncManager` - Main sync coordination interface
|
|
131
|
+
- `SyncEventEmitter` - Event subscription interface
|
|
132
|
+
|
|
133
|
+
### Clock Exports
|
|
134
|
+
|
|
135
|
+
- `HLCManager` - Hybrid Logical Clock manager
|
|
136
|
+
- `generateSiteId()` - Generate unique 16-byte site identifier
|
|
137
|
+
- `siteIdToBase64(id)` / `siteIdFromBase64(str)` - Site ID serialization
|
|
138
|
+
|
|
139
|
+
### Protocol Types
|
|
140
|
+
|
|
141
|
+
- `ChangeSet` - Collection of changes from one transaction
|
|
142
|
+
- `Change` - Single column or row change
|
|
143
|
+
- `SchemaMigration` - Schema change (CREATE/ALTER/DROP TABLE)
|
|
144
|
+
- `SnapshotChunk` - Streaming snapshot data
|
|
145
|
+
|
|
146
|
+
## Related Packages
|
|
147
|
+
|
|
148
|
+
- [`@quereus/plugin-store`](../quereus-plugin-store/) - Storage layer (required)
|
|
149
|
+
- [`@quereus/sync-coordinator`](../sync-coordinator/) - Server-side coordinator
|
|
150
|
+
|
|
151
|
+
## License
|
|
152
|
+
|
|
153
|
+
MIT
|
|
154
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid Logical Clock (HLC) implementation.
|
|
3
|
+
*
|
|
4
|
+
* HLC combines physical time with a logical counter to provide:
|
|
5
|
+
* - Monotonically increasing timestamps
|
|
6
|
+
* - Causality tracking across distributed nodes
|
|
7
|
+
* - Bounded clock drift tolerance
|
|
8
|
+
*
|
|
9
|
+
* Ordering: (wallTime, counter, siteId) compared lexicographically
|
|
10
|
+
*/
|
|
11
|
+
import type { SiteId } from './site.js';
|
|
12
|
+
/**
|
|
13
|
+
* Hybrid Logical Clock timestamp.
|
|
14
|
+
*/
|
|
15
|
+
export interface HLC {
|
|
16
|
+
/** Physical wall time in milliseconds since epoch */
|
|
17
|
+
readonly wallTime: bigint;
|
|
18
|
+
/** Logical counter for events in the same millisecond (0-65535) */
|
|
19
|
+
readonly counter: number;
|
|
20
|
+
/** 16-byte UUID identifying the replica */
|
|
21
|
+
readonly siteId: SiteId;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compare two HLCs for ordering.
|
|
25
|
+
* Returns negative if a < b, positive if a > b, zero if equal.
|
|
26
|
+
*/
|
|
27
|
+
export declare function compareHLC(a: HLC, b: HLC): number;
|
|
28
|
+
/**
|
|
29
|
+
* Check if two HLCs are equal.
|
|
30
|
+
*/
|
|
31
|
+
export declare function hlcEquals(a: HLC, b: HLC): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Create a new HLC with the given values.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createHLC(wallTime: bigint, counter: number, siteId: SiteId): HLC;
|
|
36
|
+
/**
|
|
37
|
+
* Serialize HLC to a Uint8Array for storage.
|
|
38
|
+
* Format: 8 bytes wallTime (BE) + 2 bytes counter (BE) + 16 bytes siteId = 26 bytes
|
|
39
|
+
*/
|
|
40
|
+
export declare function serializeHLC(hlc: HLC): Uint8Array;
|
|
41
|
+
/**
|
|
42
|
+
* Deserialize HLC from a Uint8Array.
|
|
43
|
+
*/
|
|
44
|
+
export declare function deserializeHLC(buffer: Uint8Array): HLC;
|
|
45
|
+
/**
|
|
46
|
+
* JSON-serializable representation of an HLC.
|
|
47
|
+
* Uses strings for bigint and base64url for binary data.
|
|
48
|
+
*/
|
|
49
|
+
export interface SerializedHLC {
|
|
50
|
+
/** Wall time in milliseconds (string to preserve bigint precision) */
|
|
51
|
+
wallTime: string;
|
|
52
|
+
/** Logical counter (0-65535) */
|
|
53
|
+
counter: number;
|
|
54
|
+
/** Site ID as 22-character base64url string */
|
|
55
|
+
siteId: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Convert HLC to a JSON-serializable object.
|
|
59
|
+
* Useful for schema seeds, HTTP transport, or debugging.
|
|
60
|
+
*/
|
|
61
|
+
export declare function hlcToJson(hlc: HLC): SerializedHLC;
|
|
62
|
+
/**
|
|
63
|
+
* Parse HLC from a JSON-serializable object.
|
|
64
|
+
*/
|
|
65
|
+
export declare function hlcFromJson(json: SerializedHLC): HLC;
|
|
66
|
+
/**
|
|
67
|
+
* HLC Manager - maintains clock state for a single replica.
|
|
68
|
+
*/
|
|
69
|
+
export declare class HLCManager {
|
|
70
|
+
private wallTime;
|
|
71
|
+
private counter;
|
|
72
|
+
private readonly siteId;
|
|
73
|
+
constructor(siteId: SiteId, initialState?: {
|
|
74
|
+
wallTime: bigint;
|
|
75
|
+
counter: number;
|
|
76
|
+
});
|
|
77
|
+
/**
|
|
78
|
+
* Get the current site ID.
|
|
79
|
+
*/
|
|
80
|
+
getSiteId(): SiteId;
|
|
81
|
+
/**
|
|
82
|
+
* Get current clock state (for persistence).
|
|
83
|
+
*/
|
|
84
|
+
getState(): {
|
|
85
|
+
wallTime: bigint;
|
|
86
|
+
counter: number;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Generate a new HLC for a local event.
|
|
90
|
+
* Advances the clock and returns the new timestamp.
|
|
91
|
+
*/
|
|
92
|
+
tick(): HLC;
|
|
93
|
+
/**
|
|
94
|
+
* Update clock state upon receiving a remote HLC.
|
|
95
|
+
* Ensures our clock is always >= received clock.
|
|
96
|
+
* Returns a new HLC for the local receive event.
|
|
97
|
+
*/
|
|
98
|
+
receive(remote: HLC): HLC;
|
|
99
|
+
/**
|
|
100
|
+
* Create an HLC at the current clock state without advancing.
|
|
101
|
+
* Useful for read operations.
|
|
102
|
+
*/
|
|
103
|
+
now(): HLC;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=hlc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hlc.d.ts","sourceRoot":"","sources":["../../../src/clock/hlc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC;;GAEG;AACH,MAAM,WAAW,GAAG;IAClB,qDAAqD;IACrD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAaD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,MAAM,CAWjD;AAcD;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CAEjD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAEhF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAcjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,GAAG,CAYtD;AAMD;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,aAAa,CAMjD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,GAAG,CAMpD;AAkDD;;GAEG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAMhF;;OAEG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACH,QAAQ,IAAI;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE;IAIjD;;;OAGG;IACH,IAAI,IAAI,GAAG;IAoBX;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG;IAuCzB;;;OAGG;IACH,GAAG,IAAI,GAAG;CAGX"}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hybrid Logical Clock (HLC) implementation.
|
|
3
|
+
*
|
|
4
|
+
* HLC combines physical time with a logical counter to provide:
|
|
5
|
+
* - Monotonically increasing timestamps
|
|
6
|
+
* - Causality tracking across distributed nodes
|
|
7
|
+
* - Bounded clock drift tolerance
|
|
8
|
+
*
|
|
9
|
+
* Ordering: (wallTime, counter, siteId) compared lexicographically
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Maximum counter value before forcing time advancement.
|
|
13
|
+
*/
|
|
14
|
+
const MAX_COUNTER = 0xFFFF;
|
|
15
|
+
/**
|
|
16
|
+
* Maximum allowed clock drift in milliseconds (1 minute).
|
|
17
|
+
* Rejects remote timestamps that are too far in the future.
|
|
18
|
+
*/
|
|
19
|
+
const MAX_DRIFT_MS = 60000n;
|
|
20
|
+
/**
|
|
21
|
+
* Compare two HLCs for ordering.
|
|
22
|
+
* Returns negative if a < b, positive if a > b, zero if equal.
|
|
23
|
+
*/
|
|
24
|
+
export function compareHLC(a, b) {
|
|
25
|
+
// First compare wall time
|
|
26
|
+
if (a.wallTime < b.wallTime)
|
|
27
|
+
return -1;
|
|
28
|
+
if (a.wallTime > b.wallTime)
|
|
29
|
+
return 1;
|
|
30
|
+
// Same wall time: compare counter
|
|
31
|
+
if (a.counter < b.counter)
|
|
32
|
+
return -1;
|
|
33
|
+
if (a.counter > b.counter)
|
|
34
|
+
return 1;
|
|
35
|
+
// Same counter: compare site ID lexicographically
|
|
36
|
+
return compareSiteIds(a.siteId, b.siteId);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Compare two site IDs lexicographically.
|
|
40
|
+
*/
|
|
41
|
+
function compareSiteIds(a, b) {
|
|
42
|
+
const len = Math.min(a.length, b.length);
|
|
43
|
+
for (let i = 0; i < len; i++) {
|
|
44
|
+
if (a[i] < b[i])
|
|
45
|
+
return -1;
|
|
46
|
+
if (a[i] > b[i])
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
return a.length - b.length;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if two HLCs are equal.
|
|
53
|
+
*/
|
|
54
|
+
export function hlcEquals(a, b) {
|
|
55
|
+
return compareHLC(a, b) === 0;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create a new HLC with the given values.
|
|
59
|
+
*/
|
|
60
|
+
export function createHLC(wallTime, counter, siteId) {
|
|
61
|
+
return Object.freeze({ wallTime, counter, siteId });
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Serialize HLC to a Uint8Array for storage.
|
|
65
|
+
* Format: 8 bytes wallTime (BE) + 2 bytes counter (BE) + 16 bytes siteId = 26 bytes
|
|
66
|
+
*/
|
|
67
|
+
export function serializeHLC(hlc) {
|
|
68
|
+
const buffer = new Uint8Array(26);
|
|
69
|
+
const view = new DataView(buffer.buffer);
|
|
70
|
+
// Wall time as big-endian 64-bit
|
|
71
|
+
view.setBigUint64(0, hlc.wallTime, false);
|
|
72
|
+
// Counter as big-endian 16-bit
|
|
73
|
+
view.setUint16(8, hlc.counter, false);
|
|
74
|
+
// Site ID (16 bytes)
|
|
75
|
+
buffer.set(hlc.siteId, 10);
|
|
76
|
+
return buffer;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Deserialize HLC from a Uint8Array.
|
|
80
|
+
*/
|
|
81
|
+
export function deserializeHLC(buffer) {
|
|
82
|
+
if (buffer.length !== 26) {
|
|
83
|
+
throw new Error(`Invalid HLC buffer length: ${buffer.length}, expected 26`);
|
|
84
|
+
}
|
|
85
|
+
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
86
|
+
const wallTime = view.getBigUint64(0, false);
|
|
87
|
+
const counter = view.getUint16(8, false);
|
|
88
|
+
const siteId = new Uint8Array(buffer.slice(10, 26));
|
|
89
|
+
return createHLC(wallTime, counter, siteId);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Convert HLC to a JSON-serializable object.
|
|
93
|
+
* Useful for schema seeds, HTTP transport, or debugging.
|
|
94
|
+
*/
|
|
95
|
+
export function hlcToJson(hlc) {
|
|
96
|
+
return {
|
|
97
|
+
wallTime: hlc.wallTime.toString(),
|
|
98
|
+
counter: hlc.counter,
|
|
99
|
+
siteId: siteIdToBase64Local(hlc.siteId),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Parse HLC from a JSON-serializable object.
|
|
104
|
+
*/
|
|
105
|
+
export function hlcFromJson(json) {
|
|
106
|
+
return createHLC(BigInt(json.wallTime), json.counter, siteIdFromBase64Local(json.siteId));
|
|
107
|
+
}
|
|
108
|
+
// Base64url alphabet (RFC 4648 Section 5)
|
|
109
|
+
const BASE64URL_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
110
|
+
/**
|
|
111
|
+
* Convert site ID to base64url string (local helper to avoid circular import).
|
|
112
|
+
*/
|
|
113
|
+
function siteIdToBase64Local(siteId) {
|
|
114
|
+
let result = '';
|
|
115
|
+
for (let i = 0; i < siteId.length; i += 3) {
|
|
116
|
+
const byte1 = siteId[i];
|
|
117
|
+
const byte2 = i + 1 < siteId.length ? siteId[i + 1] : 0;
|
|
118
|
+
const byte3 = i + 2 < siteId.length ? siteId[i + 2] : 0;
|
|
119
|
+
const triplet = (byte1 << 16) | (byte2 << 8) | byte3;
|
|
120
|
+
result += BASE64URL_CHARS[(triplet >>> 18) & 0x3f];
|
|
121
|
+
result += BASE64URL_CHARS[(triplet >>> 12) & 0x3f];
|
|
122
|
+
if (i + 1 < siteId.length)
|
|
123
|
+
result += BASE64URL_CHARS[(triplet >>> 6) & 0x3f];
|
|
124
|
+
if (i + 2 < siteId.length)
|
|
125
|
+
result += BASE64URL_CHARS[triplet & 0x3f];
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Parse site ID from base64url string (local helper to avoid circular import).
|
|
131
|
+
*/
|
|
132
|
+
function siteIdFromBase64Local(base64) {
|
|
133
|
+
if (base64.length !== 22) {
|
|
134
|
+
throw new Error(`Invalid site ID base64 length: ${base64.length}, expected 22`);
|
|
135
|
+
}
|
|
136
|
+
// Build reverse lookup table
|
|
137
|
+
const lookup = {};
|
|
138
|
+
for (let i = 0; i < BASE64URL_CHARS.length; i++) {
|
|
139
|
+
lookup[BASE64URL_CHARS[i]] = i;
|
|
140
|
+
}
|
|
141
|
+
const result = new Uint8Array(16);
|
|
142
|
+
let writePos = 0;
|
|
143
|
+
for (let i = 0; i < base64.length; i += 4) {
|
|
144
|
+
const c1 = lookup[base64[i]] ?? 0;
|
|
145
|
+
const c2 = lookup[base64[i + 1]] ?? 0;
|
|
146
|
+
const c3 = i + 2 < base64.length ? lookup[base64[i + 2]] ?? 0 : 0;
|
|
147
|
+
const c4 = i + 3 < base64.length ? lookup[base64[i + 3]] ?? 0 : 0;
|
|
148
|
+
const triplet = (c1 << 18) | (c2 << 12) | (c3 << 6) | c4;
|
|
149
|
+
if (writePos < 16)
|
|
150
|
+
result[writePos++] = (triplet >>> 16) & 0xff;
|
|
151
|
+
if (writePos < 16)
|
|
152
|
+
result[writePos++] = (triplet >>> 8) & 0xff;
|
|
153
|
+
if (writePos < 16)
|
|
154
|
+
result[writePos++] = triplet & 0xff;
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* HLC Manager - maintains clock state for a single replica.
|
|
160
|
+
*/
|
|
161
|
+
export class HLCManager {
|
|
162
|
+
wallTime;
|
|
163
|
+
counter;
|
|
164
|
+
siteId;
|
|
165
|
+
constructor(siteId, initialState) {
|
|
166
|
+
this.siteId = siteId;
|
|
167
|
+
this.wallTime = initialState?.wallTime ?? 0n;
|
|
168
|
+
this.counter = initialState?.counter ?? 0;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get the current site ID.
|
|
172
|
+
*/
|
|
173
|
+
getSiteId() {
|
|
174
|
+
return this.siteId;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get current clock state (for persistence).
|
|
178
|
+
*/
|
|
179
|
+
getState() {
|
|
180
|
+
return { wallTime: this.wallTime, counter: this.counter };
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Generate a new HLC for a local event.
|
|
184
|
+
* Advances the clock and returns the new timestamp.
|
|
185
|
+
*/
|
|
186
|
+
tick() {
|
|
187
|
+
const now = BigInt(Date.now());
|
|
188
|
+
if (now > this.wallTime) {
|
|
189
|
+
// Physical time has advanced
|
|
190
|
+
this.wallTime = now;
|
|
191
|
+
this.counter = 0;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// Same or earlier physical time, increment counter
|
|
195
|
+
this.counter++;
|
|
196
|
+
if (this.counter > MAX_COUNTER) {
|
|
197
|
+
// Counter overflow: force time advancement
|
|
198
|
+
this.wallTime++;
|
|
199
|
+
this.counter = 0;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return createHLC(this.wallTime, this.counter, this.siteId);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Update clock state upon receiving a remote HLC.
|
|
206
|
+
* Ensures our clock is always >= received clock.
|
|
207
|
+
* Returns a new HLC for the local receive event.
|
|
208
|
+
*/
|
|
209
|
+
receive(remote) {
|
|
210
|
+
const now = BigInt(Date.now());
|
|
211
|
+
// Check for excessive drift
|
|
212
|
+
if (remote.wallTime > now + MAX_DRIFT_MS) {
|
|
213
|
+
throw new Error(`Remote clock too far in future: ${remote.wallTime - now}ms ahead (max ${MAX_DRIFT_MS}ms)`);
|
|
214
|
+
}
|
|
215
|
+
// Merge: take max of local, remote, and now
|
|
216
|
+
const maxWall = now > this.wallTime
|
|
217
|
+
? (now > remote.wallTime ? now : remote.wallTime)
|
|
218
|
+
: (this.wallTime > remote.wallTime ? this.wallTime : remote.wallTime);
|
|
219
|
+
if (maxWall === this.wallTime && maxWall === remote.wallTime) {
|
|
220
|
+
// All three are equal: take max counter + 1
|
|
221
|
+
this.counter = Math.max(this.counter, remote.counter) + 1;
|
|
222
|
+
}
|
|
223
|
+
else if (maxWall === this.wallTime) {
|
|
224
|
+
// Local wins: increment local counter
|
|
225
|
+
this.counter++;
|
|
226
|
+
}
|
|
227
|
+
else if (maxWall === remote.wallTime) {
|
|
228
|
+
// Remote wins: take remote counter + 1
|
|
229
|
+
this.wallTime = remote.wallTime;
|
|
230
|
+
this.counter = remote.counter + 1;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
// Physical time wins: reset counter
|
|
234
|
+
this.wallTime = maxWall;
|
|
235
|
+
this.counter = 0;
|
|
236
|
+
}
|
|
237
|
+
if (this.counter > MAX_COUNTER) {
|
|
238
|
+
this.wallTime++;
|
|
239
|
+
this.counter = 0;
|
|
240
|
+
}
|
|
241
|
+
return createHLC(this.wallTime, this.counter, this.siteId);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Create an HLC at the current clock state without advancing.
|
|
245
|
+
* Useful for read operations.
|
|
246
|
+
*/
|
|
247
|
+
now() {
|
|
248
|
+
return createHLC(this.wallTime, this.counter, this.siteId);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=hlc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hlc.js","sourceRoot":"","sources":["../../../src/clock/hlc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/clock/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/clock/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site ID management - unique identifier for each replica.
|
|
3
|
+
*
|
|
4
|
+
* Site IDs are 16-byte UUIDs that uniquely identify a replica in the
|
|
5
|
+
* distributed system. They are used for:
|
|
6
|
+
* - Breaking ties in HLC comparison
|
|
7
|
+
* - Tracking which changes came from which replica
|
|
8
|
+
* - Peer-to-peer sync state tracking
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 16-byte unique identifier for a replica.
|
|
12
|
+
*/
|
|
13
|
+
export type SiteId = Uint8Array;
|
|
14
|
+
/**
|
|
15
|
+
* Generate a new random site ID (UUID v4).
|
|
16
|
+
*/
|
|
17
|
+
export declare function generateSiteId(): SiteId;
|
|
18
|
+
/**
|
|
19
|
+
* Convert a Uint8Array to base64url encoding (no padding).
|
|
20
|
+
*/
|
|
21
|
+
export declare function toBase64Url(bytes: Uint8Array): string;
|
|
22
|
+
/**
|
|
23
|
+
* Convert a base64url string to Uint8Array.
|
|
24
|
+
*/
|
|
25
|
+
export declare function fromBase64Url(str: string): Uint8Array;
|
|
26
|
+
/**
|
|
27
|
+
* Convert site ID to base64url string for serialization.
|
|
28
|
+
* 16 bytes → 22 characters (no padding).
|
|
29
|
+
*/
|
|
30
|
+
export declare function siteIdToBase64(siteId: SiteId): string;
|
|
31
|
+
/**
|
|
32
|
+
* Parse site ID from base64url string.
|
|
33
|
+
*/
|
|
34
|
+
export declare function siteIdFromBase64(base64: string): SiteId;
|
|
35
|
+
/**
|
|
36
|
+
* Compare two site IDs for equality.
|
|
37
|
+
*/
|
|
38
|
+
export declare function siteIdEquals(a: SiteId, b: SiteId): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Storage key for site identity.
|
|
41
|
+
*/
|
|
42
|
+
export declare const SITE_ID_KEY = "si:";
|
|
43
|
+
/**
|
|
44
|
+
* Site identity record stored in the KV store.
|
|
45
|
+
*/
|
|
46
|
+
export interface SiteIdentity {
|
|
47
|
+
siteId: SiteId;
|
|
48
|
+
createdAt: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Serialize site identity for storage.
|
|
52
|
+
*/
|
|
53
|
+
export declare function serializeSiteIdentity(identity: SiteIdentity): Uint8Array;
|
|
54
|
+
/**
|
|
55
|
+
* Deserialize site identity from storage.
|
|
56
|
+
*/
|
|
57
|
+
export declare function deserializeSiteIdentity(buffer: Uint8Array): SiteIdentity;
|
|
58
|
+
//# sourceMappingURL=site.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site.d.ts","sourceRoot":"","sources":["../../../src/clock/site.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,UAAU,CAAC;AAEhC;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAmBvC;AAKD;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAmBrD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CA2BrD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAM1D;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,QAAQ,CAAC;AAEjC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,UAAU,CAQxE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,CAUxE"}
|