local-lite 0.0.2 → 0.0.4

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.
Files changed (57) hide show
  1. package/README.md +421 -362
  2. package/dist/adapters/local/better-sqlite3.d.ts +2 -0
  3. package/dist/adapters/local/better-sqlite3.js +46 -25
  4. package/dist/adapters/local/expo-sqlite.d.ts +5 -1
  5. package/dist/adapters/local/expo-sqlite.js +40 -7
  6. package/dist/adapters/local/index.d.ts +1 -0
  7. package/dist/adapters/local/node-sqlite.d.ts +2 -0
  8. package/dist/adapters/local/node-sqlite.js +45 -22
  9. package/dist/adapters/local/op-sqlite.d.ts +1 -0
  10. package/dist/adapters/local/op-sqlite.js +6 -0
  11. package/dist/adapters/local/sql-js.d.ts +1 -0
  12. package/dist/adapters/local/sql-js.js +5 -0
  13. package/dist/adapters/network/index.js +33 -0
  14. package/dist/adapters/remote/convex.d.ts +2 -2
  15. package/dist/adapters/remote/convex.js +8 -12
  16. package/dist/adapters/remote/demo.d.ts +1 -1
  17. package/dist/adapters/remote/demo.js +3 -0
  18. package/dist/adapters/remote/firestore.d.ts +3 -2
  19. package/dist/adapters/remote/firestore.js +69 -46
  20. package/dist/adapters/remote/index.d.ts +6 -4
  21. package/dist/adapters/remote/supabase.d.ts +34 -6
  22. package/dist/adapters/remote/supabase.js +340 -79
  23. package/dist/config/index.js +7 -10
  24. package/dist/config/resolve-local.js +12 -1
  25. package/dist/config/resolve-remote.js +33 -28
  26. package/dist/config/types.d.ts +30 -5
  27. package/dist/core/client.d.ts +86 -3
  28. package/dist/core/client.js +423 -41
  29. package/dist/core/index.d.ts +14 -0
  30. package/dist/core/index.js +29 -0
  31. package/dist/core/outbox-store.d.ts +8 -4
  32. package/dist/core/outbox-store.js +84 -65
  33. package/dist/core/query.d.ts +8 -0
  34. package/dist/core/query.js +37 -0
  35. package/dist/core/schema-manager.d.ts +9 -0
  36. package/dist/core/schema-manager.js +68 -0
  37. package/dist/core/sync-worker.d.ts +38 -1
  38. package/dist/core/sync-worker.js +351 -104
  39. package/dist/core/table-manager.js +26 -29
  40. package/dist/core/types.d.ts +8 -0
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.js +0 -1
  43. package/dist/react/BenchmarkModal.d.ts +32 -0
  44. package/dist/react/BenchmarkModal.js +777 -0
  45. package/dist/react/SyncInspector.d.ts +31 -0
  46. package/dist/react/SyncInspector.js +655 -0
  47. package/dist/react/index.d.ts +49 -19
  48. package/dist/react/index.js +266 -95
  49. package/dist/react/runner.d.ts +7 -0
  50. package/dist/react/runner.js +42 -0
  51. package/dist/react/status.d.ts +24 -0
  52. package/dist/react/status.js +123 -0
  53. package/dist/react/theme.d.ts +18 -0
  54. package/dist/react/theme.js +46 -0
  55. package/dist/utils/logger.d.ts +4 -0
  56. package/dist/utils/logger.js +20 -0
  57. package/package.json +138 -133
@@ -1,6 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OutboxStore = void 0;
4
+ /**
5
+ * Maps a raw sync_outbox row into an OutboxRecord.
6
+ */
7
+ function mapOutboxRow(r) {
8
+ return {
9
+ id: r.id,
10
+ tableName: r.table_name,
11
+ rowId: r.row_id,
12
+ operation: r.operation,
13
+ payload: r.payload ? (typeof r.payload === 'string' ? JSON.parse(r.payload) : r.payload) : null,
14
+ timestamp: Number(r.timestamp),
15
+ status: r.status,
16
+ retryCount: Number(r.retry_count || 0),
17
+ lastError: r.last_error,
18
+ };
19
+ }
4
20
  class OutboxStore {
5
21
  /**
6
22
  * Initializes the sync_outbox schema if it does not already exist.
@@ -17,6 +33,10 @@ class OutboxStore {
17
33
  retry_count INTEGER NOT NULL DEFAULT 0,
18
34
  last_error TEXT
19
35
  );`);
36
+ // Indexes for the hot paths: pending scans (push), per-row conflict lookups (pull),
37
+ // and status counts (telemetry). Executed separately: many drivers prepare one statement.
38
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_sync_outbox_status_ts ON sync_outbox (status, timestamp);`);
39
+ await db.execute(`CREATE INDEX IF NOT EXISTS idx_sync_outbox_row ON sync_outbox (table_name, row_id, timestamp);`);
20
40
  }
21
41
  /**
22
42
  * Inserts a new outbox entry within an existing database transaction.
@@ -33,11 +53,29 @@ class OutboxStore {
33
53
  ]);
34
54
  }
35
55
  /**
36
- * Inserts multiple outbox entries within an existing database transaction.
56
+ * Inserts multiple outbox entries in high-throughput multi-row SQL chunks within an existing transaction.
37
57
  */
38
58
  static async insertRecords(tx, ops) {
39
- for (const op of ops) {
40
- await this.insertRecord(tx, op);
59
+ if (!ops || ops.length === 0)
60
+ return;
61
+ const CHUNK_SIZE = 50;
62
+ for (let i = 0; i < ops.length; i += CHUNK_SIZE) {
63
+ const chunk = ops.slice(i, i + CHUNK_SIZE);
64
+ const valueClauses = [];
65
+ const params = [];
66
+ for (const op of chunk) {
67
+ valueClauses.push('(?, ?, ?, ?, ?, ?, "PENDING", 0)');
68
+ params.push(op.id, op.tableName, op.rowId, op.operation, op.payload ? JSON.stringify(op.payload) : null, op.timestamp);
69
+ }
70
+ try {
71
+ await tx.execute(`INSERT INTO sync_outbox (id, table_name, row_id, operation, payload, timestamp, status, retry_count) VALUES ${valueClauses.join(', ')}`, params);
72
+ }
73
+ catch {
74
+ // Transparent fallback to per-record insert for mock drivers or custom engines
75
+ for (const op of chunk) {
76
+ await this.insertRecord(tx, op);
77
+ }
78
+ }
41
79
  }
42
80
  }
43
81
  /**
@@ -56,17 +94,7 @@ class OutboxStore {
56
94
  const rows = await db.execute(query, params);
57
95
  if (!rows || rows.length === 0)
58
96
  return [];
59
- return rows.map((r) => ({
60
- id: r.id,
61
- tableName: r.table_name,
62
- rowId: r.row_id,
63
- operation: r.operation,
64
- payload: r.payload ? (typeof r.payload === 'string' ? JSON.parse(r.payload) : r.payload) : null,
65
- timestamp: Number(r.timestamp),
66
- status: r.status,
67
- retryCount: Number(r.retry_count || 0),
68
- lastError: r.last_error,
69
- }));
97
+ return rows.map(mapOutboxRow);
70
98
  }
71
99
  /**
72
100
  * Finds the latest pending outbox record for a given table and row.
@@ -77,18 +105,7 @@ class OutboxStore {
77
105
  ORDER BY timestamp DESC LIMIT 1`, [tableName, rowId]);
78
106
  if (!rows || rows.length === 0)
79
107
  return undefined;
80
- const r = rows[0];
81
- return {
82
- id: r.id,
83
- tableName: r.table_name,
84
- rowId: r.row_id,
85
- operation: r.operation,
86
- payload: r.payload ? (typeof r.payload === 'string' ? JSON.parse(r.payload) : r.payload) : null,
87
- timestamp: Number(r.timestamp),
88
- status: r.status,
89
- retryCount: Number(r.retry_count || 0),
90
- lastError: r.last_error,
91
- };
108
+ return mapOutboxRow(rows[0]);
92
109
  }
93
110
  /**
94
111
  * Marks a batch of outbox IDs as 'PROCESSING'.
@@ -127,10 +144,22 @@ class OutboxStore {
127
144
  await tx.execute(`DELETE FROM sync_outbox WHERE id = ?`, [id]);
128
145
  }
129
146
  /**
130
- * Updates an outbox record's payload (used during conflict merge).
147
+ * Updates an outbox record's payload (used during conflict merge or coalescing).
131
148
  */
132
- static async updatePayload(tx, id, payload) {
133
- await tx.execute(`UPDATE sync_outbox SET payload = ? WHERE id = ?`, [JSON.stringify(payload), id]);
149
+ static async updatePayload(tx, id, payload, timestamp) {
150
+ if (timestamp) {
151
+ await tx.execute(`UPDATE sync_outbox SET payload = ?, timestamp = ? WHERE id = ?`, [JSON.stringify(payload), timestamp, id]);
152
+ }
153
+ else {
154
+ await tx.execute(`UPDATE sync_outbox SET payload = ? WHERE id = ?`, [JSON.stringify(payload), id]);
155
+ }
156
+ }
157
+ /**
158
+ * Updates an outbox record's operation and payload (used during mutation coalescing).
159
+ */
160
+ static async updateOperation(tx, id, operation, payload, timestamp) {
161
+ const ts = timestamp ?? Date.now();
162
+ await tx.execute(`UPDATE sync_outbox SET operation = ?, payload = ?, timestamp = ? WHERE id = ?`, [operation, payload ? JSON.stringify(payload) : null, ts, id]);
134
163
  }
135
164
  /**
136
165
  * Marks a batch of outbox IDs as 'FAILED' and increments retry counters.
@@ -150,27 +179,23 @@ class OutboxStore {
150
179
  */
151
180
  static async getCounts(db) {
152
181
  try {
153
- const rows = await db.execute(`SELECT * FROM sync_outbox`);
154
- if (!rows || rows.length === 0) {
155
- return { pending: 0, failed: 0, processing: 0, total: 0 };
156
- }
182
+ // Aggregate in SQLite (index-only scan) instead of loading every row and payload into JS.
183
+ const rows = await db.execute(`SELECT status, COUNT(*) AS count FROM sync_outbox GROUP BY status`);
157
184
  let pending = 0;
158
185
  let failed = 0;
159
186
  let processing = 0;
160
- for (const row of rows) {
187
+ let total = 0;
188
+ for (const row of rows || []) {
189
+ const count = Number(row.count ?? 0);
190
+ total += count;
161
191
  if (row.status === 'PENDING')
162
- pending++;
192
+ pending = count;
163
193
  else if (row.status === 'FAILED')
164
- failed++;
194
+ failed = count;
165
195
  else if (row.status === 'PROCESSING')
166
- processing++;
196
+ processing = count;
167
197
  }
168
- return {
169
- pending,
170
- failed,
171
- processing,
172
- total: rows.length,
173
- };
198
+ return { pending, failed, processing, total };
174
199
  }
175
200
  catch {
176
201
  return { pending: 0, failed: 0, processing: 0, total: 0 };
@@ -231,31 +256,25 @@ class OutboxStore {
231
256
  */
232
257
  static async getAllRecords(db, options) {
233
258
  try {
234
- const rows = await db.execute(`SELECT * FROM sync_outbox`);
235
- if (!rows || rows.length === 0)
236
- return [];
237
- let mapped = rows.map((r) => ({
238
- id: r.id,
239
- tableName: r.table_name,
240
- rowId: r.row_id,
241
- operation: r.operation,
242
- payload: r.payload ? (typeof r.payload === 'string' ? JSON.parse(r.payload) : r.payload) : null,
243
- timestamp: Number(r.timestamp),
244
- status: r.status,
245
- retryCount: Number(r.retry_count || 0),
246
- lastError: r.last_error,
247
- }));
259
+ // Filter, order and paginate in SQLite so only the requested page is parsed.
260
+ const params = [];
261
+ let query = `SELECT * FROM sync_outbox`;
248
262
  if (options?.status) {
249
- mapped = mapped.filter((r) => r.status === options.status);
250
- }
251
- mapped.sort((a, b) => b.timestamp - a.timestamp);
252
- if (options?.offset && options.offset > 0) {
253
- mapped = mapped.slice(options.offset);
263
+ query += ` WHERE status = ?`;
264
+ params.push(options.status);
254
265
  }
255
- if (options?.limit && options.limit > 0) {
256
- mapped = mapped.slice(0, options.limit);
266
+ query += ` ORDER BY timestamp DESC`;
267
+ const limit = options?.limit && options.limit > 0 ? options.limit : -1;
268
+ const offset = options?.offset && options.offset > 0 ? options.offset : 0;
269
+ if (limit > 0 || offset > 0) {
270
+ // SQLite requires LIMIT when OFFSET is used; -1 means "no limit".
271
+ query += ` LIMIT ? OFFSET ?`;
272
+ params.push(limit, offset);
257
273
  }
258
- return mapped;
274
+ const rows = await db.execute(query, params);
275
+ if (!rows || rows.length === 0)
276
+ return [];
277
+ return rows.map(mapOutboxRow);
259
278
  }
260
279
  catch {
261
280
  return [];
@@ -7,6 +7,14 @@ export declare function matchesField(fieldValue: any, filter: FieldFilter): bool
7
7
  * Evaluates whether a given row matches a WhereFilter.
8
8
  */
9
9
  export declare function matchesWhere(row: Record<string, any>, where?: WhereFilter): boolean;
10
+ /**
11
+ * Checks whether a row is considered soft-deleted.
12
+ */
13
+ export declare function isDeletedRecord(row: any): boolean;
14
+ /**
15
+ * Checks if a where filter explicitly specifies a condition on the 'deleted' column.
16
+ */
17
+ export declare function hasExplicitDeletedFilter(where?: WhereFilter): boolean;
10
18
  /**
11
19
  * Applies in-memory filtering, sorting, offset, and limit to an array of rows.
12
20
  */
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.matchesField = matchesField;
4
4
  exports.matchesWhere = matchesWhere;
5
+ exports.isDeletedRecord = isDeletedRecord;
6
+ exports.hasExplicitDeletedFilter = hasExplicitDeletedFilter;
5
7
  exports.applyQueryOptions = applyQueryOptions;
6
8
  /**
7
9
  * Checks whether a single field value matches a field filter condition.
@@ -106,6 +108,23 @@ function matchesWhere(row, where) {
106
108
  }
107
109
  return true;
108
110
  }
111
+ /**
112
+ * Checks whether a row is considered soft-deleted.
113
+ */
114
+ function isDeletedRecord(row) {
115
+ if (!row || typeof row !== 'object')
116
+ return false;
117
+ const d = row.deleted;
118
+ return d === 1 || d === true || d === '1' || d === 'true';
119
+ }
120
+ /**
121
+ * Checks if a where filter explicitly specifies a condition on the 'deleted' column.
122
+ */
123
+ function hasExplicitDeletedFilter(where) {
124
+ if (!where || typeof where !== 'object' || Array.isArray(where))
125
+ return false;
126
+ return 'deleted' in where;
127
+ }
109
128
  /**
110
129
  * Applies in-memory filtering, sorting, offset, and limit to an array of rows.
111
130
  */
@@ -113,6 +132,11 @@ function applyQueryOptions(rows, options) {
113
132
  if (!rows || rows.length === 0)
114
133
  return [];
115
134
  let result = [...rows];
135
+ // 0. Automatic soft-delete filtering: omit deleted records unless includeDeleted is true
136
+ // or the where filter explicitly targets 'deleted'.
137
+ if (!options?.includeDeleted && !hasExplicitDeletedFilter(options?.where)) {
138
+ result = result.filter((row) => !isDeletedRecord(row));
139
+ }
116
140
  // 1. Where filtering
117
141
  if (options?.where) {
118
142
  result = result.filter((row) => matchesWhere(row, options.where));
@@ -146,5 +170,18 @@ function applyQueryOptions(rows, options) {
146
170
  if (options?.limit !== undefined && options.limit > 0) {
147
171
  result = result.slice(0, options.limit);
148
172
  }
173
+ // 4. Column Projection
174
+ if (options?.select && Array.isArray(options.select) && options.select.length > 0) {
175
+ const fields = options.select;
176
+ result = result.map((row) => {
177
+ const projected = {};
178
+ for (const field of fields) {
179
+ if (field in row) {
180
+ projected[field] = row[field];
181
+ }
182
+ }
183
+ return projected;
184
+ });
185
+ }
149
186
  return result;
150
187
  }
@@ -23,10 +23,19 @@ export declare function formatValueForStorage(value: any): any;
23
23
  export declare class SchemaManager {
24
24
  private knownTables;
25
25
  private knownColumns;
26
+ private knownIndexes;
26
27
  /**
27
28
  * Resets the schema cache (useful for testing or full database resets).
28
29
  */
29
30
  clearCache(): void;
31
+ /**
32
+ * Checks whether a column is known to exist on the specified table.
33
+ */
34
+ hasColumn(tableName: string, column: string): boolean;
35
+ /**
36
+ * Introspects table columns via PRAGMA table_info if not previously cached.
37
+ */
38
+ ensureTableIntrospected(db: DBExecutable, tableName: string): Promise<void>;
30
39
  /**
31
40
  * Ensures that the target table exists in SQLite with the specified primary key.
32
41
  */
@@ -58,12 +58,43 @@ function formatValueForStorage(value) {
58
58
  class SchemaManager {
59
59
  knownTables = new Set();
60
60
  knownColumns = new Set();
61
+ knownIndexes = new Set();
61
62
  /**
62
63
  * Resets the schema cache (useful for testing or full database resets).
63
64
  */
64
65
  clearCache() {
65
66
  this.knownTables.clear();
66
67
  this.knownColumns.clear();
68
+ this.knownIndexes.clear();
69
+ }
70
+ /**
71
+ * Checks whether a column is known to exist on the specified table.
72
+ */
73
+ hasColumn(tableName, column) {
74
+ return this.knownColumns.has(`${tableName}:${column.toLowerCase()}`);
75
+ }
76
+ /**
77
+ * Introspects table columns via PRAGMA table_info if not previously cached.
78
+ */
79
+ async ensureTableIntrospected(db, tableName) {
80
+ const cacheKey = `${tableName}:__introspected__`;
81
+ if (this.knownColumns.has(cacheKey))
82
+ return;
83
+ this.knownColumns.add(cacheKey);
84
+ const safeTable = (0, sql_js_1.escapeIdentifier)(tableName);
85
+ try {
86
+ const tableInfo = await db.execute(`PRAGMA table_info(${safeTable});`);
87
+ if (Array.isArray(tableInfo)) {
88
+ for (const col of tableInfo) {
89
+ if (col && col.name) {
90
+ this.knownColumns.add(`${tableName}:${String(col.name).toLowerCase()}`);
91
+ }
92
+ }
93
+ }
94
+ }
95
+ catch {
96
+ // Ignore
97
+ }
67
98
  }
68
99
  /**
69
100
  * Ensures that the target table exists in SQLite with the specified primary key.
@@ -137,6 +168,43 @@ class SchemaManager {
137
168
  }
138
169
  this.knownColumns.add(cacheKey);
139
170
  }
171
+ // Auto-index foreign keys and common query columns
172
+ const lowerKey = key.toLowerCase();
173
+ if (lowerKey.endsWith('_id') ||
174
+ lowerKey === 'business_id' ||
175
+ lowerKey === 'deleted' ||
176
+ lowerKey === 'created_at' ||
177
+ lowerKey === 'payment_date') {
178
+ const indexKey = `${tableName}:${lowerKey}`;
179
+ if (!this.knownIndexes.has(indexKey)) {
180
+ this.knownIndexes.add(indexKey);
181
+ try {
182
+ const idxName = (0, sql_js_1.escapeIdentifier)(`idx_${tableName}_${key}`);
183
+ const safeCol = (0, sql_js_1.escapeIdentifier)(key);
184
+ await db.execute(`CREATE INDEX IF NOT EXISTS ${idxName} ON ${safeTable} (${safeCol});`);
185
+ }
186
+ catch {
187
+ // Ignore if indexes are not supported by adapter or already exist
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ // Auto-create high performance composite index for multi-tenant soft-deleted queries
194
+ if (this.knownColumns.has(`${tableName}:business_id`) &&
195
+ this.knownColumns.has(`${tableName}:deleted`)) {
196
+ const compositeKey = `${tableName}:business_id_deleted`;
197
+ if (!this.knownIndexes.has(compositeKey)) {
198
+ this.knownIndexes.add(compositeKey);
199
+ try {
200
+ const idxName = (0, sql_js_1.escapeIdentifier)(`idx_${tableName}_biz_del`);
201
+ const safeBiz = (0, sql_js_1.escapeIdentifier)('business_id');
202
+ const safeDel = (0, sql_js_1.escapeIdentifier)('deleted');
203
+ await db.execute(`CREATE INDEX IF NOT EXISTS ${idxName} ON ${safeTable} (${safeBiz}, ${safeDel});`);
204
+ }
205
+ catch {
206
+ // Ignore
207
+ }
140
208
  }
141
209
  }
142
210
  // Format all values for SQLite storage
@@ -16,6 +16,9 @@ export interface SyncWorkerOptions {
16
16
  primaryKey?: string;
17
17
  autoSchema?: boolean;
18
18
  schemaManager?: SchemaManager;
19
+ autoSync?: boolean;
20
+ syncScope?: Record<string, any> | (() => Record<string, any> | Promise<Record<string, any>>);
21
+ autoPauseOnBackground?: boolean;
19
22
  }
20
23
  export declare class SyncWorker {
21
24
  private db;
@@ -26,20 +29,29 @@ export declare class SyncWorker {
26
29
  private primaryKey;
27
30
  private autoSchema;
28
31
  private schemaManager?;
32
+ private syncScope?;
29
33
  logger: SyncLogger;
30
34
  retryConfig: RetryConfig;
31
35
  private pushIntervalMs;
32
36
  private pullIntervalMs;
33
37
  private maxRetries;
38
+ private autoPauseOnBackground;
39
+ private isPaused;
40
+ private lifecycleUnsubscribe?;
34
41
  private pushIntervalTimer?;
35
42
  private pullIntervalTimer?;
36
43
  private pushDebounceTimer?;
37
44
  private networkUnsubscribe?;
45
+ private remoteUnsubscribe?;
46
+ private subscriptionGeneration;
47
+ private automaticSync;
38
48
  private isPushing;
39
49
  private isPulling;
40
50
  private lastPulledTimestamp;
41
51
  private cursorLoaded;
42
52
  private deltaQueue;
53
+ private statusRun?;
54
+ private statusRerunRequested;
43
55
  private totalPushedOperations;
44
56
  private totalPulledDeltas;
45
57
  private totalPushCycles;
@@ -59,11 +71,34 @@ export declare class SyncWorker {
59
71
  * Stops all active intervals, timers, and listeners.
60
72
  */
61
73
  stop(): void;
74
+ /**
75
+ * Pauses recurring interval timers and active sync loops to conserve battery and data.
76
+ */
77
+ pause(): void;
78
+ /**
79
+ * Resumes recurring interval timers and triggers an immediate catch-up push and pull.
80
+ */
81
+ resume(): void;
82
+ /**
83
+ * Returns whether the sync worker is currently suspended due to app backgrounding.
84
+ */
85
+ get isBackgroundPaused(): boolean;
86
+ private isAppBackgrounded;
87
+ private setupLifecycleListener;
62
88
  /**
63
89
  * Schedules an immediate push with a short debounce.
64
90
  */
65
91
  schedulePush(delayMs?: number): void;
92
+ /**
93
+ * Dynamically sets or updates the active sync scope (e.g. switching active businessId or userId).
94
+ */
95
+ setSyncScope(scope?: Record<string, any> | (() => Record<string, any> | Promise<Record<string, any>>)): void;
66
96
  private isOnline;
97
+ /**
98
+ * Emits a `sync:status` snapshot. Calls are coalesced: while one snapshot is being
99
+ * computed, further requests collapse into a single trailing run that reflects the
100
+ * latest state, so bursts of push/pull transitions cost at most two COUNT queries.
101
+ */
67
102
  private emitStatus;
68
103
  /**
69
104
  * Pushes pending outbox records to the remote database.
@@ -78,7 +113,9 @@ export declare class SyncWorker {
78
113
  * Applies incoming server delta changes to local tables with conflict resolution.
79
114
  * Serializes execution in a queue to prevent concurrent SQLite transactions.
80
115
  */
81
- applyDeltasLocally(deltas: DeltaChanges): Promise<void>;
116
+ applyDeltasLocally(deltas: DeltaChanges, options?: {
117
+ advanceCursor?: boolean;
118
+ }): Promise<void>;
82
119
  private processDeltasLocally;
83
120
  getLastPulledTimestamp(): Promise<number>;
84
121
  resetSyncCursor(): Promise<void>;