@strata-sync/client 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/src/types.ts ADDED
@@ -0,0 +1,425 @@
1
+ import type {
2
+ BatchLoadOptions,
3
+ BootstrapMetadata,
4
+ BootstrapOptions,
5
+ ConnectionState,
6
+ DeltaPacket,
7
+ DeltaSubscription,
8
+ ModelRegistrySnapshot,
9
+ ModelRow,
10
+ MutateResult,
11
+ ReactivityAdapter,
12
+ SchemaDefinition,
13
+ SubscribeOptions,
14
+ SyncAction,
15
+ SyncClientState,
16
+ Transaction,
17
+ TransactionBatch,
18
+ } from "@strata-sync/core";
19
+ import type {
20
+ YjsDocumentManager,
21
+ YjsPresenceManager,
22
+ YjsTransport,
23
+ } from "@strata-sync/yjs";
24
+
25
+ /**
26
+ * Storage adapter interface (from sync-storage-idb or similar)
27
+ */
28
+ export interface StorageAdapter {
29
+ open(options: {
30
+ name?: string;
31
+ userId?: string;
32
+ version?: number;
33
+ userVersion?: number;
34
+ schema?: SchemaDefinition | ModelRegistrySnapshot;
35
+ }): Promise<void>;
36
+ close(): Promise<void>;
37
+ get<T>(modelName: string, id: string): Promise<T | null>;
38
+ getAll<T>(modelName: string): Promise<T[]>;
39
+ put<T extends Record<string, unknown>>(
40
+ modelName: string,
41
+ row: T
42
+ ): Promise<void>;
43
+ delete(modelName: string, id: string): Promise<void>;
44
+ getByIndex<T>(
45
+ modelName: string,
46
+ indexName: string,
47
+ key: string
48
+ ): Promise<T[]>;
49
+ writeBatch(
50
+ ops: Array<{
51
+ type: "put" | "delete";
52
+ modelName: string;
53
+ id?: string;
54
+ data?: Record<string, unknown>;
55
+ }>
56
+ ): Promise<void>;
57
+ getMeta(): Promise<StorageMeta>;
58
+ setMeta(meta: Partial<StorageMeta>): Promise<void>;
59
+ getModelPersistence(modelName: string): Promise<ModelPersistenceMeta>;
60
+ setModelPersistence(modelName: string, persisted: boolean): Promise<void>;
61
+ getOutbox(): Promise<Transaction[]>;
62
+ addToOutbox(tx: Transaction): Promise<void>;
63
+ removeFromOutbox(clientTxId: string): Promise<void>;
64
+ updateOutboxTransaction(
65
+ clientTxId: string,
66
+ updates: Partial<Transaction>
67
+ ): Promise<void>;
68
+ hasPartialIndex(
69
+ modelName: string,
70
+ indexedKey: string,
71
+ keyValue: string
72
+ ): Promise<boolean>;
73
+ setPartialIndex(
74
+ modelName: string,
75
+ indexedKey: string,
76
+ keyValue: string
77
+ ): Promise<void>;
78
+ addSyncActions(actions: SyncAction[]): Promise<void>;
79
+ getSyncActions(afterSyncId?: number, limit?: number): Promise<SyncAction[]>;
80
+ clearSyncActions(): Promise<void>;
81
+ clear(): Promise<void>;
82
+ count(modelName: string): Promise<number>;
83
+ }
84
+
85
+ /**
86
+ * Store interface for model instances (lazy relations)
87
+ */
88
+ export interface ModelStore {
89
+ get<T extends Record<string, unknown>>(
90
+ modelName: string,
91
+ id: string
92
+ ): Promise<T | null>;
93
+ getByIndex?<T extends Record<string, unknown>>(
94
+ modelName: string,
95
+ indexName: string,
96
+ key: string
97
+ ): Promise<T[]>;
98
+ loadByIndex?<T extends Record<string, unknown>>(
99
+ modelName: string,
100
+ indexName: string,
101
+ key: string
102
+ ): Promise<T[]>;
103
+ hasPartialIndex?(
104
+ modelName: string,
105
+ indexName: string,
106
+ key: string
107
+ ): Promise<boolean>;
108
+ setPartialIndex?(
109
+ modelName: string,
110
+ indexName: string,
111
+ key: string
112
+ ): Promise<void>;
113
+ }
114
+
115
+ /**
116
+ * Model factory for creating model instances from raw data
117
+ */
118
+ export type ModelFactory = (
119
+ modelName: string,
120
+ data: Record<string, unknown>
121
+ ) => Record<string, unknown>;
122
+
123
+ /**
124
+ * Context passed to model factory builders
125
+ */
126
+ export interface ModelFactoryContext {
127
+ store: ModelStore;
128
+ }
129
+
130
+ /**
131
+ * Model factory builder (receives store context)
132
+ */
133
+ export type ModelFactoryFactory = (
134
+ context: ModelFactoryContext
135
+ ) => ModelFactory;
136
+
137
+ /**
138
+ * Storage metadata
139
+ */
140
+ export interface StorageMeta {
141
+ schemaHash?: string;
142
+ lastSyncId: number;
143
+ firstSyncId?: number;
144
+ subscribedSyncGroups?: string[];
145
+ clientId?: string;
146
+ bootstrapComplete?: boolean;
147
+ lastSyncAt?: number;
148
+ databaseVersion?: number;
149
+ updatedAt?: number;
150
+ }
151
+
152
+ export interface ModelPersistenceMeta {
153
+ modelName: string;
154
+ persisted: boolean;
155
+ updatedAt?: number;
156
+ }
157
+
158
+ /**
159
+ * Transport adapter interface (from sync-transport-graphql or similar)
160
+ */
161
+ export interface TransportAdapter {
162
+ bootstrap(
163
+ options: BootstrapOptions
164
+ ): AsyncGenerator<ModelRow, BootstrapMetadata, unknown>;
165
+ batchLoad(options: BatchLoadOptions): AsyncIterable<ModelRow>;
166
+ mutate(batch: TransactionBatch): Promise<MutateResult>;
167
+ subscribe(options: SubscribeOptions): DeltaSubscription;
168
+ fetchDeltas(after: number, limit?: number): Promise<DeltaPacket>;
169
+ getConnectionState(): ConnectionState;
170
+ onConnectionStateChange(
171
+ callback: (state: ConnectionState) => void
172
+ ): () => void;
173
+ close(): Promise<void>;
174
+ }
175
+
176
+ /**
177
+ * Sync client options
178
+ */
179
+ export interface SyncClientOptions {
180
+ /** Storage adapter (e.g., IndexedDB) */
181
+ storage: StorageAdapter;
182
+ /** Transport adapter (e.g., GraphQL) */
183
+ transport: TransportAdapter;
184
+ /** Reactivity adapter (e.g., MobX) */
185
+ reactivity: ReactivityAdapter;
186
+ /** Schema definition or registry snapshot */
187
+ schema?: SchemaDefinition | ModelRegistrySnapshot;
188
+ /** Optional model factory (or factory builder) */
189
+ modelFactory?: ModelFactory | ModelFactoryFactory;
190
+ /** Database name for storage */
191
+ dbName?: string;
192
+ /** Logged-in user ID for storage naming */
193
+ userId?: string;
194
+ /** Client version for storage naming */
195
+ version?: number;
196
+ /** Per-user version for storage naming */
197
+ userVersion?: number;
198
+ /** Groups to sync (for multi-tenancy) */
199
+ groups?: string[];
200
+ /** Enable optimistic updates */
201
+ optimistic?: boolean;
202
+ /** Batch mutations before sending */
203
+ batchMutations?: boolean;
204
+ /** Mutation batch delay in ms */
205
+ batchDelay?: number;
206
+ /** Bootstrap mode selection */
207
+ bootstrapMode?: "auto" | "full" | "local";
208
+ /** Optional Yjs transport for live editing */
209
+ yjsTransport?: YjsTransport;
210
+ /** Default conflict resolution strategy for transaction rebasing (default: "server-wins") */
211
+ rebaseStrategy?: "server-wins" | "client-wins" | "merge";
212
+ /** Enable field-level conflict detection for rebasing (default: true) */
213
+ fieldLevelConflicts?: boolean;
214
+ }
215
+
216
+ /**
217
+ * Model instance with sync metadata
218
+ */
219
+ export interface SyncModelInstance<T = Record<string, unknown>> {
220
+ /** The model data */
221
+ data: T;
222
+ /** Whether the model is fully hydrated */
223
+ __isHydrated: boolean;
224
+ /** Whether the model has unsaved changes */
225
+ __dirty: boolean;
226
+ /** Fields that have been modified */
227
+ __modifiedFields: Set<string>;
228
+ /** Model name */
229
+ __model: string;
230
+ /** Model ID */
231
+ __id: string;
232
+ }
233
+
234
+ /**
235
+ * Query options for fetching models
236
+ */
237
+ export interface QueryOptions<T> {
238
+ /** Filter function */
239
+ where?: (item: T) => boolean;
240
+ /** Sort function */
241
+ orderBy?: (a: T, b: T) => number;
242
+ /** Maximum number of results */
243
+ limit?: number;
244
+ /** Number of results to skip */
245
+ offset?: number;
246
+ /** Include soft-deleted items */
247
+ includeArchived?: boolean;
248
+ }
249
+
250
+ /**
251
+ * Query result with metadata
252
+ */
253
+ export interface QueryResult<T> {
254
+ /** Query results */
255
+ data: T[];
256
+ /** Whether more results are available */
257
+ hasMore: boolean;
258
+ /** Total count (if available) */
259
+ totalCount?: number;
260
+ }
261
+
262
+ export type ModelChangeAction =
263
+ | "insert"
264
+ | "update"
265
+ | "delete"
266
+ | "archive"
267
+ | "unarchive";
268
+
269
+ export type SyncClientEvent =
270
+ | { type: "syncStart" }
271
+ | { type: "syncComplete"; lastSyncId: number }
272
+ | { type: "syncError"; error: Error }
273
+ | { type: "stateChange"; state: SyncClientState }
274
+ | { type: "connectionChange"; state: ConnectionState }
275
+ | { type: "outboxChange"; pendingCount: number }
276
+ | {
277
+ type: "modelChange";
278
+ modelName: string;
279
+ modelId: string;
280
+ action: ModelChangeAction;
281
+ }
282
+ | {
283
+ type: "rebaseConflict";
284
+ modelName: string;
285
+ modelId: string;
286
+ conflictType: string;
287
+ resolution: string;
288
+ };
289
+
290
+ /**
291
+ * Sync client interface
292
+ */
293
+ export interface SyncClient {
294
+ /** Current client state */
295
+ state: SyncClientState;
296
+
297
+ /** Current connection state */
298
+ connectionState: ConnectionState;
299
+
300
+ /** Last sync ID received from server */
301
+ lastSyncId: number;
302
+
303
+ /** Last error (if any) */
304
+ lastError: Error | null;
305
+
306
+ /** Client ID */
307
+ clientId: string;
308
+
309
+ /** Yjs managers for live editing (if configured) */
310
+ yjs?: {
311
+ documentManager: YjsDocumentManager;
312
+ presenceManager: YjsPresenceManager;
313
+ };
314
+
315
+ /** Start the sync client */
316
+ start(): Promise<void>;
317
+
318
+ /** Stop the sync client */
319
+ stop(): Promise<void>;
320
+
321
+ /** Get a model by ID */
322
+ get<T>(modelName: string, id: string): Promise<T | null>;
323
+
324
+ /** Get a cached model by ID (no network) */
325
+ getCached<T>(modelName: string, id: string): T | null;
326
+
327
+ /** Ensure a model is available locally, loading if needed */
328
+ ensureModel<T>(modelName: string, id: string): Promise<T | null>;
329
+
330
+ /** Get all models of a type */
331
+ getAll<T>(modelName: string, options?: QueryOptions<T>): Promise<T[]>;
332
+
333
+ /** Query models */
334
+ query<T>(
335
+ modelName: string,
336
+ options?: QueryOptions<T>
337
+ ): Promise<QueryResult<T>>;
338
+
339
+ /** Create a new model */
340
+ create<T extends Record<string, unknown>>(
341
+ modelName: string,
342
+ data: T,
343
+ options?: { onTransactionCreated?: (tx: Transaction) => void }
344
+ ): Promise<T>;
345
+
346
+ /** Update a model */
347
+ update<T extends Record<string, unknown>>(
348
+ modelName: string,
349
+ id: string,
350
+ changes: Partial<T>,
351
+ options?: {
352
+ original?: Record<string, unknown>;
353
+ onTransactionCreated?: (tx: Transaction) => void;
354
+ }
355
+ ): Promise<T>;
356
+
357
+ /** Delete a model */
358
+ delete(
359
+ modelName: string,
360
+ id: string,
361
+ options?: {
362
+ original?: Record<string, unknown>;
363
+ onTransactionCreated?: (tx: Transaction) => void;
364
+ }
365
+ ): Promise<void>;
366
+
367
+ /** Archive a model (soft delete) */
368
+ archive(
369
+ modelName: string,
370
+ id: string,
371
+ options?: {
372
+ original?: Record<string, unknown>;
373
+ archivedAt?: string;
374
+ onTransactionCreated?: (tx: Transaction) => void;
375
+ }
376
+ ): Promise<void>;
377
+
378
+ /** Unarchive a model */
379
+ unarchive(
380
+ modelName: string,
381
+ id: string,
382
+ options?: {
383
+ original?: Record<string, unknown>;
384
+ onTransactionCreated?: (tx: Transaction) => void;
385
+ }
386
+ ): Promise<void>;
387
+
388
+ /** Whether undo is available */
389
+ canUndo(): boolean;
390
+
391
+ /** Whether redo is available */
392
+ canRedo(): boolean;
393
+
394
+ /** Undo the last operation */
395
+ undo(): Promise<void>;
396
+
397
+ /** Redo the last undone operation */
398
+ redo(): Promise<void>;
399
+
400
+ /** Subscribe to events */
401
+ onEvent(callback: (event: SyncClientEvent) => void): () => void;
402
+
403
+ /** Subscribe to state changes */
404
+ onStateChange(callback: (state: SyncClientState) => void): () => void;
405
+
406
+ /** Subscribe to connection state changes */
407
+ onConnectionStateChange(
408
+ callback: (state: ConnectionState) => void
409
+ ): () => void;
410
+
411
+ /** Get pending transaction count */
412
+ getPendingCount(): Promise<number>;
413
+
414
+ /** Force a sync now */
415
+ syncNow(): Promise<void>;
416
+
417
+ /** Clear all local data */
418
+ clearAll(): Promise<void>;
419
+
420
+ /** Get the identity map for a model type */
421
+ getIdentityMap<T>(modelName: string): Map<string, T>;
422
+
423
+ /** Whether a model was previously missing in storage/network */
424
+ isModelMissing(modelName: string, id: string): boolean;
425
+ }