@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/dist/index.d.ts
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import * as _starkeep_protocol_primitives from '@starkeep/protocol-primitives';
|
|
2
|
+
import { HLCTimestamp, StarkeepId, AnyRecord, HLCClock, StarkeepError } from '@starkeep/protocol-primitives';
|
|
3
|
+
import { ObjectStorageAdapter, DatabaseAdapter } from '@starkeep/storage-adapter';
|
|
4
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
6
|
+
|
|
7
|
+
interface AppSyncableTableInfo {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly pkColumns: string[];
|
|
10
|
+
}
|
|
11
|
+
interface AppSyncableNamespace {
|
|
12
|
+
readonly appId: string;
|
|
13
|
+
readonly tables: AppSyncableTableInfo[];
|
|
14
|
+
readonly filesEnabled: boolean;
|
|
15
|
+
/** Derived from tables — convenience accessor. */
|
|
16
|
+
readonly tableNames: string[];
|
|
17
|
+
}
|
|
18
|
+
interface AppSyncableNamespaceStore {
|
|
19
|
+
get(appId: string): AppSyncableNamespace | null;
|
|
20
|
+
list(): AppSyncableNamespace[];
|
|
21
|
+
}
|
|
22
|
+
interface AppSyncableRowEntry {
|
|
23
|
+
readonly timestamp: HLCTimestamp;
|
|
24
|
+
readonly appId: string;
|
|
25
|
+
/** Bare table name (no engine prefix on the wire). */
|
|
26
|
+
readonly table: string;
|
|
27
|
+
readonly op: "insert" | "update" | "delete";
|
|
28
|
+
readonly row?: Record<string, unknown>;
|
|
29
|
+
readonly where?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
interface AppSyncableApplier {
|
|
32
|
+
apply(entry: AppSyncableRowEntry): Promise<void> | void;
|
|
33
|
+
}
|
|
34
|
+
/** Pagination options for `ScanCapableApplier.scanSince`. */
|
|
35
|
+
interface ScanSinceOptions {
|
|
36
|
+
/** Max rows to return in this page. */
|
|
37
|
+
readonly limit?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Serialized HLC of the last row returned by the previous page. The next
|
|
40
|
+
* page returns rows with `updated_at > cursor`. When omitted, the page
|
|
41
|
+
* starts from `sinceHlcStr`.
|
|
42
|
+
*/
|
|
43
|
+
readonly cursor?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Page returned by `ScanCapableApplier.scanSince`. */
|
|
46
|
+
interface ScanSincePage {
|
|
47
|
+
readonly rows: AppSyncableRowEntry[];
|
|
48
|
+
/**
|
|
49
|
+
* Cursor to pass on the next call to continue the scan. `null` when no
|
|
50
|
+
* further rows exist past this page.
|
|
51
|
+
*/
|
|
52
|
+
readonly nextCursor: string | null;
|
|
53
|
+
readonly hasMore: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Optional capability that appliers can implement to support exchange
|
|
57
|
+
* synthesis. Scans rows with `updated_at > sinceHlcStr` (the global floor)
|
|
58
|
+
* in HLC order, paginated by cursor so the engine can stop after `limit`
|
|
59
|
+
* matches without buffering the whole table.
|
|
60
|
+
*/
|
|
61
|
+
interface ScanCapableApplier extends AppSyncableApplier {
|
|
62
|
+
scanSince(appId: string, table: string, sinceHlcStr: string, options?: ScanSinceOptions): Promise<ScanSincePage>;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* A row read from the framework-owned `_starkeep_sync_records` table.
|
|
66
|
+
* Mirrors the column shape declared in `@starkeep/shared-space-api`'s
|
|
67
|
+
* `FILE_RECORDS_COLUMNS` plus the always-appended HLC bookkeeping columns.
|
|
68
|
+
* The sync engine's file-transfer pass derives upload/download decisions
|
|
69
|
+
* from blob presence (`localObjectStorage.has(key)`), not from any stored
|
|
70
|
+
* status — there is no `sync_status` column on this row. See
|
|
71
|
+
* `residency.ts` (`RecordResidency`, `residencyOf`) for the named derived
|
|
72
|
+
* state, and `system-design.md` "Per-record residency" for the rationale.
|
|
73
|
+
*/
|
|
74
|
+
interface FileRecordRow {
|
|
75
|
+
readonly id: string;
|
|
76
|
+
readonly object_storage_key: string;
|
|
77
|
+
readonly content_hash: string;
|
|
78
|
+
readonly mime_type: string;
|
|
79
|
+
readonly size_bytes: number;
|
|
80
|
+
readonly original_filename: string | null;
|
|
81
|
+
readonly origin_app_id: string;
|
|
82
|
+
readonly created_at: string;
|
|
83
|
+
readonly updated_at: string;
|
|
84
|
+
readonly deleted_at: string | null;
|
|
85
|
+
}
|
|
86
|
+
type Watermarks = Record<string, HLCTimestamp>;
|
|
87
|
+
interface SyncExchangeRequest {
|
|
88
|
+
/** Caller's view of what it has seen per nodeId. */
|
|
89
|
+
readonly watermarks: Watermarks;
|
|
90
|
+
/** Records the caller believes the peer hasn't seen yet. */
|
|
91
|
+
readonly records?: AnyRecord[];
|
|
92
|
+
/** App-syncable row deltas the caller believes the peer hasn't seen. */
|
|
93
|
+
readonly appSyncableRows?: AppSyncableRowEntry[];
|
|
94
|
+
/** Max records the responder should ship in this round. */
|
|
95
|
+
readonly limit?: number;
|
|
96
|
+
}
|
|
97
|
+
interface SyncExchangeResponse {
|
|
98
|
+
/** Records the caller hasn't seen (`updated_at > callerWatermarks[nodeId]`). */
|
|
99
|
+
readonly records: AnyRecord[];
|
|
100
|
+
/** Same delta logic per app schema. */
|
|
101
|
+
readonly appSyncableRows: AppSyncableRowEntry[];
|
|
102
|
+
readonly hasMore: boolean;
|
|
103
|
+
}
|
|
104
|
+
interface SyncTransport {
|
|
105
|
+
exchange(request: SyncExchangeRequest): Promise<SyncExchangeResponse>;
|
|
106
|
+
}
|
|
107
|
+
interface FileSyncManifest {
|
|
108
|
+
readonly fileHash: string;
|
|
109
|
+
readonly objectStorageKey: string;
|
|
110
|
+
readonly sizeBytes: number;
|
|
111
|
+
readonly mimeType?: string;
|
|
112
|
+
}
|
|
113
|
+
interface FileEntry {
|
|
114
|
+
readonly key: string;
|
|
115
|
+
readonly mimeType?: string;
|
|
116
|
+
}
|
|
117
|
+
interface FileSyncEngine {
|
|
118
|
+
/** True if a transferFile for this key is currently running in this process. */
|
|
119
|
+
isTransferInFlight(key: string): boolean;
|
|
120
|
+
getFilesToPush(localStorage: ObjectStorageAdapter, remoteStorage: ObjectStorageAdapter, entries: FileEntry[]): Promise<FileSyncManifest[]>;
|
|
121
|
+
getFilesToPull(localStorage: ObjectStorageAdapter, remoteStorage: ObjectStorageAdapter, entries: FileEntry[]): Promise<FileSyncManifest[]>;
|
|
122
|
+
/**
|
|
123
|
+
* Resolve true on a successful transfer (or if the source key is now present
|
|
124
|
+
* at the destination). Resolves false if the transfer is already in flight
|
|
125
|
+
* or the source file doesn't exist.
|
|
126
|
+
*/
|
|
127
|
+
transferFile(manifest: FileSyncManifest, source: ObjectStorageAdapter, destination: ObjectStorageAdapter): Promise<boolean>;
|
|
128
|
+
}
|
|
129
|
+
type ChangeEventType = "remote-update-available" | "local-data-synced" | "local-change-recorded";
|
|
130
|
+
interface ChangeEvent {
|
|
131
|
+
readonly eventType: ChangeEventType;
|
|
132
|
+
readonly recordIds: StarkeepId[];
|
|
133
|
+
readonly timestamp: HLCTimestamp;
|
|
134
|
+
/**
|
|
135
|
+
* For `local-change-recorded`: the appId whose sync channel owns this write.
|
|
136
|
+
* Set when an app-specific data write happens (the calling app); left unset
|
|
137
|
+
* for shared-record writes (those are owned by the always-on Drive channel
|
|
138
|
+
* by Shape-A convention, which is a deployment fact the SDK doesn't name).
|
|
139
|
+
* The sync supervisor uses this to nudge only the affected engine.
|
|
140
|
+
*/
|
|
141
|
+
readonly originAppId?: string;
|
|
142
|
+
}
|
|
143
|
+
type ChangeListener = (event: ChangeEvent) => void;
|
|
144
|
+
interface ChangeNotifier {
|
|
145
|
+
subscribe(listener: ChangeListener): () => void;
|
|
146
|
+
emit(event: ChangeEvent): void;
|
|
147
|
+
}
|
|
148
|
+
interface SyncStateStore {
|
|
149
|
+
/** Caller's "what I've seen per nodeId" — advanced by records actually applied from peers. */
|
|
150
|
+
getWatermarks(): Promise<Watermarks>;
|
|
151
|
+
setWatermarks(watermarks: Watermarks): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Last-known peer-side watermarks, returned by the peer on the previous
|
|
154
|
+
* exchange. Used by the caller to compute outbound deltas without an extra
|
|
155
|
+
* round-trip. Defaults to {} on first exchange.
|
|
156
|
+
*/
|
|
157
|
+
getPeerWatermarks(): Promise<Watermarks>;
|
|
158
|
+
setPeerWatermarks(watermarks: Watermarks): Promise<void>;
|
|
159
|
+
getHlcClockState(): Promise<{
|
|
160
|
+
wallTime: number;
|
|
161
|
+
counter: number;
|
|
162
|
+
} | null>;
|
|
163
|
+
setHlcClockState(state: {
|
|
164
|
+
wallTime: number;
|
|
165
|
+
counter: number;
|
|
166
|
+
}): Promise<void>;
|
|
167
|
+
}
|
|
168
|
+
interface ExchangeResult {
|
|
169
|
+
readonly applied: number;
|
|
170
|
+
readonly shipped: number;
|
|
171
|
+
readonly hasMore: boolean;
|
|
172
|
+
}
|
|
173
|
+
interface SyncEngine {
|
|
174
|
+
/**
|
|
175
|
+
* One version-vector exchange round with the peer:
|
|
176
|
+
* 1. Read own + last-known peer watermarks
|
|
177
|
+
* 2. For each outbound record (peer hasn't seen): push its blob if any,
|
|
178
|
+
* then ship metadata. Blob push failure excludes that record from the
|
|
179
|
+
* round; peerWatermarks stays behind it for an automatic retry.
|
|
180
|
+
* 3. For each inbound record: apply metadata, then pull its blob if any.
|
|
181
|
+
* Blob pull failure leaves own watermark behind it; next round the
|
|
182
|
+
* responder still ships it.
|
|
183
|
+
* 4. Persist updated watermarks.
|
|
184
|
+
*/
|
|
185
|
+
exchange(): Promise<ExchangeResult>;
|
|
186
|
+
readonly changeNotifier: ChangeNotifier;
|
|
187
|
+
}
|
|
188
|
+
interface SyncEngineOptions {
|
|
189
|
+
readonly localDatabaseAdapter: DatabaseAdapter;
|
|
190
|
+
readonly localObjectStorage: ObjectStorageAdapter;
|
|
191
|
+
readonly remoteObjectStorage: ObjectStorageAdapter;
|
|
192
|
+
readonly transport: SyncTransport;
|
|
193
|
+
readonly clock: _starkeep_protocol_primitives.HLCClock;
|
|
194
|
+
readonly syncState?: SyncStateStore;
|
|
195
|
+
/**
|
|
196
|
+
* Provides the applier (for applying incoming exchange rows) and namespace
|
|
197
|
+
* store (for scanning local rows on the outbound side). Without it,
|
|
198
|
+
* app-syncable rows are silently skipped on both directions.
|
|
199
|
+
*/
|
|
200
|
+
readonly appSyncableSource?: {
|
|
201
|
+
readonly namespaces: AppSyncableNamespaceStore;
|
|
202
|
+
readonly applier: AppSyncableApplier & ScanCapableApplier;
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Channel split. When true (default), this engine ships and applies
|
|
206
|
+
* shared records (SR — the `shared.records` table). The always-on Starkeep
|
|
207
|
+
* Drive channel sets this true and provides no `appSyncableSource`, so it is
|
|
208
|
+
* the *only* channel that carries shared records. Per-app channels set this
|
|
209
|
+
* false and carry only their own app-specific (`appSyncableSource`) rows, so
|
|
210
|
+
* shared-record sync is identical regardless of which apps are cloud-installed.
|
|
211
|
+
*/
|
|
212
|
+
readonly syncSharedRecords?: boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Max items per exchange round, applied to both the outbound local scan and
|
|
215
|
+
* the inbound request limit. Default 1000. Tests use small values (e.g. 5)
|
|
216
|
+
* to exercise multi-round pagination without seeding thousands of records;
|
|
217
|
+
* production callers may tune this against poll frequency / throughput.
|
|
218
|
+
*/
|
|
219
|
+
readonly pageLimit?: number;
|
|
220
|
+
/**
|
|
221
|
+
* Internal page size for the cursor-paginated outbound scan loop. Default
|
|
222
|
+
* 500. Tests can set this small to force the cursor to advance across
|
|
223
|
+
* multiple DB queries within one exchange round (otherwise a small test
|
|
224
|
+
* dataset fits in a single page and the cursor never moves).
|
|
225
|
+
*/
|
|
226
|
+
readonly scanPageSize?: number;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
interface SqliteSyncStateStoreOptions {
|
|
230
|
+
readonly db: DatabaseSync;
|
|
231
|
+
}
|
|
232
|
+
declare function createSqliteSyncStateStore(options: SqliteSyncStateStoreOptions): SyncStateStore;
|
|
233
|
+
|
|
234
|
+
declare function createChangeNotifier(): ChangeNotifier;
|
|
235
|
+
|
|
236
|
+
/** Advance `watermarks[hlc.nodeId]` to `max(current, hlc)`. */
|
|
237
|
+
declare function advanceWatermark(watermarks: Watermarks, hlc: HLCTimestamp): void;
|
|
238
|
+
/** Merge `incoming` into `into`, taking the max per nodeId. */
|
|
239
|
+
declare function mergeWatermarks(into: Watermarks, incoming: Watermarks): Watermarks;
|
|
240
|
+
/** Watermark for `nodeId`, or `ZERO_HLC` if unseen. */
|
|
241
|
+
declare function watermarkFor(watermarks: Watermarks, nodeId: string): HLCTimestamp;
|
|
242
|
+
/**
|
|
243
|
+
* Return records the peer hasn't seen yet, judged against `peerWatermarks`:
|
|
244
|
+
* `record.updatedAt > peerWatermarks[record.updatedAt.nodeId] ?? ZERO_HLC`.
|
|
245
|
+
*/
|
|
246
|
+
declare function selectUnseen<T extends {
|
|
247
|
+
updatedAt: HLCTimestamp;
|
|
248
|
+
}>(records: T[], peerWatermarks: Watermarks): T[];
|
|
249
|
+
|
|
250
|
+
declare function createFileSyncEngine(): FileSyncEngine;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Sync engine: drives one version-vector exchange round per tick.
|
|
254
|
+
*
|
|
255
|
+
* Blob transfer is gated on the same watermark that drives metadata transfer.
|
|
256
|
+
* A record's blob is pushed before its metadata ships; a record's blob is
|
|
257
|
+
* pulled before its receipt is acknowledged. If either fails, the watermark
|
|
258
|
+
* doesn't advance past it, and the next round naturally retries.
|
|
259
|
+
*
|
|
260
|
+
* Shared records (SR) and app-record rows in the reserved `_starkeep_sync_records`
|
|
261
|
+
* table (AR) are interleaved per nodeId in HLC order so the contiguous-prefix
|
|
262
|
+
* watermark rule covers both streams: a blob failure on an AR row blocks any
|
|
263
|
+
* later SR record on the same nodeId from shipping in the same round (and vice
|
|
264
|
+
* versa). Without that, the per-nodeId watermark could leapfrog a failed item.
|
|
265
|
+
*
|
|
266
|
+
* There is no scan-everything reconciliation pass. There is no `sync_status`.
|
|
267
|
+
* Steady state issues zero storage HEAD requests: the watermark delta tells
|
|
268
|
+
* us exactly which records (and therefore which blobs) need attention.
|
|
269
|
+
*/
|
|
270
|
+
declare function createSyncEngine(options: SyncEngineOptions): SyncEngine;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Per-record state on a single side, derived from facts already on disk.
|
|
274
|
+
* There is intentionally no persisted `sync_status` column; this type names
|
|
275
|
+
* what the combination of (row presence, blob presence, deletedAt) means.
|
|
276
|
+
*
|
|
277
|
+
* See system-design.md "Per-record residency" for the full rationale and how
|
|
278
|
+
* the watermark serves as the durable backstop for the Staged state.
|
|
279
|
+
*
|
|
280
|
+
* - absent — no row for this id on this side.
|
|
281
|
+
* - staged — row present, blob required, blob not yet present locally.
|
|
282
|
+
* - resident — row present, blob present locally.
|
|
283
|
+
* - tombstoned — `deletedAt` is set. Propagates like resident; blob GC is a
|
|
284
|
+
* separate concern.
|
|
285
|
+
*/
|
|
286
|
+
type RecordResidency = "absent" | "staged" | "resident" | "tombstoned";
|
|
287
|
+
/**
|
|
288
|
+
* Classify a record's residency on this side. Pass `null` for `recordRow` to
|
|
289
|
+
* model "row not present" (returns `absent`).
|
|
290
|
+
*
|
|
291
|
+
* This is the single canonical derivation. Code and tests should call it
|
|
292
|
+
* rather than reconstructing the predicate from `localStorage.has(key)` etc.
|
|
293
|
+
*
|
|
294
|
+
* Note: rows in `_starkeep_sync_records` always have a blob (the table's
|
|
295
|
+
* purpose). Records that opt out of file storage live in app-syncable
|
|
296
|
+
* metadata tables instead and don't reach this function.
|
|
297
|
+
*/
|
|
298
|
+
declare function residencyOf(recordRow: FileRecordRow | null, localStorage: ObjectStorageAdapter): Promise<RecordResidency>;
|
|
299
|
+
|
|
300
|
+
interface InProcessTransportOptions {
|
|
301
|
+
readonly databaseAdapter: DatabaseAdapter;
|
|
302
|
+
readonly clock: HLCClock;
|
|
303
|
+
/**
|
|
304
|
+
* When provided, the transport synthesizes app-syncable row entries on
|
|
305
|
+
* exchange (by scanning updated_at per table) and applies incoming rows on
|
|
306
|
+
* apply (LWW UPSERT).
|
|
307
|
+
*/
|
|
308
|
+
readonly appSyncableSource?: {
|
|
309
|
+
readonly namespaces: AppSyncableNamespaceStore;
|
|
310
|
+
readonly applier: AppSyncableApplier;
|
|
311
|
+
};
|
|
312
|
+
/**
|
|
313
|
+
* Object storage backing the records this transport serves. Used only as a
|
|
314
|
+
* reference for the file-transfer pass elsewhere; the exchange protocol
|
|
315
|
+
* itself does no blob inspection.
|
|
316
|
+
*/
|
|
317
|
+
readonly objectStorage: ObjectStorageAdapter;
|
|
318
|
+
/**
|
|
319
|
+
* Channel split (responder side). When true (default), this transport
|
|
320
|
+
* applies and scans shared records (the `shared.records` table). The
|
|
321
|
+
* cloud-side Drive channel sets this true with no `appSyncableSource`; per-app
|
|
322
|
+
* channels set it false and serve only that app's app-specific rows. Mirrors
|
|
323
|
+
* `SyncEngineOptions.syncSharedRecords` on the requester side.
|
|
324
|
+
*/
|
|
325
|
+
readonly syncSharedRecords?: boolean;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* `SyncTransport` that talks directly to an in-process database adapter.
|
|
329
|
+
* Used for tests and for running a "cloud" side in the same Node process.
|
|
330
|
+
*
|
|
331
|
+
* Exchange semantics:
|
|
332
|
+
* - Apply incoming records via `put(snapshot)` with HLC LWW.
|
|
333
|
+
* - Scan local records the caller hasn't seen (per-nodeId watermark filter).
|
|
334
|
+
* - Return `responderWatermarks` = MAX(updated_at) per nodeId.
|
|
335
|
+
*
|
|
336
|
+
* Conflict resolution is pure HLC LWW — no rejected[], no OCC.
|
|
337
|
+
*/
|
|
338
|
+
declare function createInProcessSyncTransport(options: InProcessTransportOptions): SyncTransport;
|
|
339
|
+
|
|
340
|
+
interface HttpSyncTransportOptions {
|
|
341
|
+
readonly baseUrl: string;
|
|
342
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
343
|
+
readonly getAuthHeader?: () => string | undefined;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* `SyncTransport` that talks to a remote Starkeep-compatible HTTP server
|
|
347
|
+
* over `fetch`. Single endpoint: `POST {baseUrl}/sync/exchange`.
|
|
348
|
+
*/
|
|
349
|
+
declare function createHttpSyncTransport(options: HttpSyncTransportOptions): SyncTransport;
|
|
350
|
+
|
|
351
|
+
interface HttpSyncServerOptions {
|
|
352
|
+
readonly databaseAdapter: DatabaseAdapter;
|
|
353
|
+
readonly objectStorageAdapter: ObjectStorageAdapter;
|
|
354
|
+
readonly clock: HLCClock;
|
|
355
|
+
/**
|
|
356
|
+
* Optional transport override — if provided, takes precedence over the
|
|
357
|
+
* default in-process transport.
|
|
358
|
+
*/
|
|
359
|
+
readonly transport?: SyncTransport;
|
|
360
|
+
}
|
|
361
|
+
type Handler = (req: IncomingMessage, res: ServerResponse) => Promise<boolean>;
|
|
362
|
+
/**
|
|
363
|
+
* Request handler that recognizes the Starkeep sync + file routes.
|
|
364
|
+
* Returns `true` if the request was handled; callers can compose this with
|
|
365
|
+
* their own routing layer.
|
|
366
|
+
*/
|
|
367
|
+
declare function createHttpSyncHandler(options: HttpSyncServerOptions): Handler;
|
|
368
|
+
|
|
369
|
+
declare class SyncError extends StarkeepError {
|
|
370
|
+
constructor(message: string, cause?: unknown);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export { type AppSyncableApplier, type AppSyncableNamespace, type AppSyncableNamespaceStore, type AppSyncableRowEntry, type AppSyncableTableInfo, type ChangeEvent, type ChangeEventType, type ChangeListener, type ChangeNotifier, type ExchangeResult, type FileEntry, type FileRecordRow, type FileSyncEngine, type FileSyncManifest, type HttpSyncServerOptions, type HttpSyncTransportOptions, type RecordResidency, type ScanCapableApplier, type ScanSinceOptions, type ScanSincePage, type SyncEngine, type SyncEngineOptions, SyncError, type SyncExchangeRequest, type SyncExchangeResponse, type SyncStateStore, type SyncTransport, type Watermarks, advanceWatermark, createChangeNotifier, createFileSyncEngine, createHttpSyncHandler, createHttpSyncTransport, createInProcessSyncTransport, createSqliteSyncStateStore, createSyncEngine, mergeWatermarks, residencyOf, selectUnseen, watermarkFor };
|