@starkeep/sync-engine 0.1.0
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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/index.cjs +793 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +373 -0
- package/dist/index.d.ts +373 -0
- package/dist/index.js +762 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aaron Koller, Pedro Pinto, Craig Schroeder
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @starkeep/sync-engine
|
|
2
|
+
|
|
3
|
+
Cross-cutting sync engine for Starkeep: a single `exchange()` round performs a per-`nodeId` HLC version-vector exchange between a local node and a peer (the cloud data server in production; an in-process peer in tests), transferring shared records and app-specific rows together with their blobs.
|
|
4
|
+
|
|
5
|
+
The package is data-kind-aware: shared data (always file-backed, no owning app) and app-specific data (owned by exactly one app, optionally file-backed) flow as two first-class streams on each side. Conflict resolution is pure HLC last-write-wins. Partial failures don't corrupt state: a blob failure halts watermark advance for the affected `nodeId` only, so the next round retries naturally.
|
|
6
|
+
|
|
7
|
+
See `starkeep-core/meta-docs/docs/functional-doc-data-sync-2026-06-08.md` for the full functional description.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @starkeep/sync-engine
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {
|
|
19
|
+
createSyncEngine,
|
|
20
|
+
createInProcessSyncTransport,
|
|
21
|
+
createSqliteSyncStateStore,
|
|
22
|
+
} from "@starkeep/sync-engine";
|
|
23
|
+
|
|
24
|
+
const transport = createInProcessSyncTransport({
|
|
25
|
+
databaseAdapter: peerDatabase,
|
|
26
|
+
objectStorage: peerStorage,
|
|
27
|
+
clock: peerClock,
|
|
28
|
+
// Channel split: true (default) handles shared records; false handles a
|
|
29
|
+
// single app's app-specific rows via `appSyncableSource`.
|
|
30
|
+
syncSharedRecords: true,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const syncEngine = createSyncEngine({
|
|
34
|
+
localDatabaseAdapter: localDatabase,
|
|
35
|
+
localObjectStorage: localStorage,
|
|
36
|
+
transport,
|
|
37
|
+
clock: localClock,
|
|
38
|
+
syncState: createSqliteSyncStateStore(sqliteDb),
|
|
39
|
+
// Optional: omit for the always-on Drive channel; set for a per-app channel.
|
|
40
|
+
// appSyncableSource: { namespaces, applier },
|
|
41
|
+
// syncSharedRecords: true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Run one exchange round. The supervisor calls this on its schedule; if
|
|
45
|
+
// result.hasMore is true the supervisor schedules another round immediately.
|
|
46
|
+
const result = await syncEngine.exchange();
|
|
47
|
+
console.log(result.shipped, "shipped,", result.applied, "applied, hasMore:", result.hasMore);
|
|
48
|
+
|
|
49
|
+
// Subscribe to change notifications.
|
|
50
|
+
const unsubscribe = syncEngine.changeNotifier.subscribe((event) => {
|
|
51
|
+
// event.type: "remote-update-available" | "local-data-synced" | "local-change-recorded"
|
|
52
|
+
console.log(event.type, event.recordIds);
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## API surface
|
|
57
|
+
|
|
58
|
+
### Factory functions
|
|
59
|
+
|
|
60
|
+
| Function | Description |
|
|
61
|
+
|---|---|
|
|
62
|
+
| `createSyncEngine(options)` | Creates a `SyncEngine` exposing `exchange()` and `changeNotifier`. |
|
|
63
|
+
| `createInProcessSyncTransport(options)` | Responder-side `SyncTransport` that calls a peer `DatabaseAdapter` directly. Used in tests and for in-process peers. |
|
|
64
|
+
| `createHttpSyncTransport(options)` | Client-side `SyncTransport` that POSTs to `${baseUrl}/sync/exchange`. |
|
|
65
|
+
| `createHttpSyncHandler(options)` | Request handler for the responder side; handles `POST /sync/exchange` and `/files/:key` (`HEAD`/`GET`/`PUT`/`DELETE`). |
|
|
66
|
+
| `createChangeNotifier()` | Standalone synchronous in-memory pub/sub over `ChangeEvent`s. |
|
|
67
|
+
| `createFileSyncEngine()` | Wraps an `ObjectStorageAdapter` with `transferFile` (in-flight dedupe + destination short-circuit). |
|
|
68
|
+
| `createSqliteSyncStateStore(db)` | Built-in `SyncStateStore` over `node:sqlite`; persists watermarks, peer watermarks, and HLC clock state. |
|
|
69
|
+
|
|
70
|
+
### `SyncEngine`
|
|
71
|
+
|
|
72
|
+
| Member | Description |
|
|
73
|
+
|---|---|
|
|
74
|
+
| `exchange()` | Run one version-vector exchange round. Returns `ExchangeResult` with `{ applied, shipped, hasMore }`. |
|
|
75
|
+
| `changeNotifier` | The engine's `ChangeNotifier`. Emits `local-data-synced` after each round; callers emit `remote-update-available` and `local-change-recorded`. |
|
|
76
|
+
|
|
77
|
+
### Watermark helpers
|
|
78
|
+
|
|
79
|
+
| Function | Description |
|
|
80
|
+
|---|---|
|
|
81
|
+
| `advanceWatermark(w, hlc)` | Max-per-`nodeId` advance. |
|
|
82
|
+
| `mergeWatermarks(a, b)` | Per-`nodeId` max merge. |
|
|
83
|
+
| `watermarkFor(w, nodeId)` | Lookup with default. |
|
|
84
|
+
| `selectUnseen(items, w, hlcOf)` | Filter to items whose HLC exceeds the per-`nodeId` watermark. |
|
|
85
|
+
|
|
86
|
+
### Residency
|
|
87
|
+
|
|
88
|
+
| Function | Description |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `residencyOf(row, localStorage)` | Canonical derivation of `Absent` / `Staged` / `Resident` / `Tombstoned`. |
|
|
91
|
+
| `RecordResidency` (type) | The four-state enum. |
|
|
92
|
+
|
|
93
|
+
### Key types
|
|
94
|
+
|
|
95
|
+
| Type | Description |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `SyncEngineOptions` | Local DB adapter, local object storage, transport, clock, optional `syncState`, `appSyncableSource`, `syncSharedRecords` (default `true`), `pageLimit` (default 1000), `scanPageSize` (default 500). |
|
|
98
|
+
| `SyncTransport` | `{ exchange(request) }`. |
|
|
99
|
+
| `SyncExchangeRequest` / `SyncExchangeResponse` | Wire shape: `watermarks`, `records?` (shared), `appSyncableRows?` (app-specific), `limit?` / `hasMore`. |
|
|
100
|
+
| `ExchangeResult` | `{ applied, shipped, hasMore }`. |
|
|
101
|
+
| `Watermarks` | Per-`nodeId` HLC map. |
|
|
102
|
+
| `AppSyncableNamespace` / `AppSyncableNamespaceStore` | App table descriptors. |
|
|
103
|
+
| `AppSyncableApplier` / `ScanCapableApplier` | Apply incoming rows; scan local rows by HLC. |
|
|
104
|
+
| `AppSyncableRowEntry` | `{ appId, table, op, row, timestamp }`. |
|
|
105
|
+
| `FileRecordRow` | Row shape for the reserved file-backed app table `_starkeep_sync_records`. |
|
|
106
|
+
| `FileSyncEngine` / `FileSyncManifest` / `FileEntry` | Blob transfer surface. |
|
|
107
|
+
| `ChangeEvent` / `ChangeEventType` / `ChangeListener` / `ChangeNotifier` | Pub/sub types. Event types: `"remote-update-available"`, `"local-data-synced"`, `"local-change-recorded"`. |
|
|
108
|
+
| `SyncStateStore` | Watermark + HLC clock persistence interface. |
|
|
109
|
+
|
|
110
|
+
### Errors
|
|
111
|
+
|
|
112
|
+
| Error | Description |
|
|
113
|
+
|---|---|
|
|
114
|
+
| `SyncError` | General sync operation failure (e.g. non-2xx HTTP response). |
|
|
115
|
+
|
|
116
|
+
## Testing
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pnpm --filter @starkeep/sync-engine test
|
|
120
|
+
```
|