@powersync/common 0.0.0-dev-20250210155038 → 0.0.0-dev-20250416114737
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 +1 -4
- package/dist/bundle.mjs +3 -3
- package/lib/client/AbstractPowerSyncDatabase.d.ts +88 -6
- package/lib/client/AbstractPowerSyncDatabase.js +131 -20
- package/lib/client/SQLOpenFactory.d.ts +3 -0
- package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +6 -1
- package/lib/client/sync/bucket/SqliteBucketStorage.d.ts +2 -2
- package/lib/client/sync/bucket/SqliteBucketStorage.js +34 -10
- package/lib/client/sync/stream/AbstractRemote.d.ts +16 -1
- package/lib/client/sync/stream/AbstractRemote.js +20 -7
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +11 -4
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +169 -84
- package/lib/client/sync/stream/streaming-sync-types.d.ts +8 -1
- package/lib/client/sync/stream/streaming-sync-types.js +3 -0
- package/lib/db/DBAdapter.d.ts +17 -1
- package/lib/db/crud/SyncStatus.d.ts +96 -6
- package/lib/db/crud/SyncStatus.js +89 -8
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/utils/DataStream.js +5 -6
- package/lib/utils/Logger.d.ts +16 -0
- package/lib/utils/Logger.js +21 -0
- package/lib/utils/{throttle.d.ts → async.d.ts} +1 -0
- package/lib/utils/{throttle.js → async.js} +10 -0
- package/package.json +1 -1
|
@@ -10,7 +10,7 @@ import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnecto
|
|
|
10
10
|
import { BucketStorageAdapter } from './sync/bucket/BucketStorageAdapter.js';
|
|
11
11
|
import { CrudBatch } from './sync/bucket/CrudBatch.js';
|
|
12
12
|
import { CrudTransaction } from './sync/bucket/CrudTransaction.js';
|
|
13
|
-
import { type AdditionalConnectionOptions, type PowerSyncConnectionOptions,
|
|
13
|
+
import { StreamingSyncImplementation, StreamingSyncImplementationListener, type AdditionalConnectionOptions, type PowerSyncConnectionOptions, type RequiredAdditionalConnectionOptions } from './sync/stream/AbstractStreamingSyncImplementation.js';
|
|
14
14
|
export interface DisconnectAndClearOptions {
|
|
15
15
|
/** When set to false, data in local-only tables is preserved. */
|
|
16
16
|
clearLocal?: boolean;
|
|
@@ -153,9 +153,18 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
153
153
|
*/
|
|
154
154
|
waitForReady(): Promise<void>;
|
|
155
155
|
/**
|
|
156
|
+
* Wait for the first sync operation to complete.
|
|
157
|
+
*
|
|
158
|
+
* @param request Either an abort signal (after which the promise will complete regardless of
|
|
159
|
+
* whether a full sync was completed) or an object providing an abort signal and a priority target.
|
|
160
|
+
* When a priority target is set, the promise may complete when all buckets with the given (or higher)
|
|
161
|
+
* priorities have been synchronized. This can be earlier than a complete sync.
|
|
156
162
|
* @returns A promise which will resolve once the first full sync has completed.
|
|
157
163
|
*/
|
|
158
|
-
waitForFirstSync(
|
|
164
|
+
waitForFirstSync(request?: AbortSignal | {
|
|
165
|
+
signal?: AbortSignal;
|
|
166
|
+
priority?: number;
|
|
167
|
+
}): Promise<void>;
|
|
159
168
|
/**
|
|
160
169
|
* Allows for extended implementations to execute custom initialization
|
|
161
170
|
* logic as part of the total init process
|
|
@@ -213,7 +222,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
213
222
|
*/
|
|
214
223
|
getUploadQueueStats(includeSize?: boolean): Promise<UploadQueueStats>;
|
|
215
224
|
/**
|
|
216
|
-
* Get a batch of
|
|
225
|
+
* Get a batch of CRUD data to upload.
|
|
217
226
|
*
|
|
218
227
|
* Returns null if there is no data to upload.
|
|
219
228
|
*
|
|
@@ -228,6 +237,9 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
228
237
|
* This method does include transaction ids in the result, but does not group
|
|
229
238
|
* data by transaction. One batch may contain data from multiple transactions,
|
|
230
239
|
* and a single transaction may be split over multiple batches.
|
|
240
|
+
*
|
|
241
|
+
* @param limit Maximum number of CRUD entries to include in the batch
|
|
242
|
+
* @returns A batch of CRUD operations to upload, or null if there are none
|
|
231
243
|
*/
|
|
232
244
|
getCrudBatch(limit?: number): Promise<CrudBatch | null>;
|
|
233
245
|
/**
|
|
@@ -242,36 +254,71 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
242
254
|
*
|
|
243
255
|
* Unlike {@link getCrudBatch}, this only returns data from a single transaction at a time.
|
|
244
256
|
* All data for the transaction is loaded into memory.
|
|
257
|
+
*
|
|
258
|
+
* @returns A transaction of CRUD operations to upload, or null if there are none
|
|
245
259
|
*/
|
|
246
260
|
getNextCrudTransaction(): Promise<CrudTransaction | null>;
|
|
247
261
|
/**
|
|
248
262
|
* Get an unique client id for this database.
|
|
249
263
|
*
|
|
250
264
|
* The id is not reset when the database is cleared, only when the database is deleted.
|
|
265
|
+
*
|
|
266
|
+
* @returns A unique identifier for the database instance
|
|
251
267
|
*/
|
|
252
268
|
getClientId(): Promise<string>;
|
|
253
269
|
private handleCrudCheckpoint;
|
|
254
270
|
/**
|
|
255
|
-
* Execute a write (INSERT/UPDATE/DELETE) query
|
|
271
|
+
* Execute a SQL write (INSERT/UPDATE/DELETE) query
|
|
256
272
|
* and optionally return results.
|
|
273
|
+
*
|
|
274
|
+
* @param sql The SQL query to execute
|
|
275
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
276
|
+
* @returns The query result as an object with structured key-value pairs
|
|
257
277
|
*/
|
|
258
278
|
execute(sql: string, parameters?: any[]): Promise<QueryResult>;
|
|
279
|
+
/**
|
|
280
|
+
* Execute a SQL write (INSERT/UPDATE/DELETE) query directly on the database without any PowerSync processing.
|
|
281
|
+
* This bypasses certain PowerSync abstractions and is useful for accessing the raw database results.
|
|
282
|
+
*
|
|
283
|
+
* @param sql The SQL query to execute
|
|
284
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
285
|
+
* @returns The raw query result from the underlying database as a nested array of raw values, where each row is
|
|
286
|
+
* represented as an array of column values without field names.
|
|
287
|
+
*/
|
|
288
|
+
executeRaw(sql: string, parameters?: any[]): Promise<any[][]>;
|
|
259
289
|
/**
|
|
260
290
|
* Execute a write query (INSERT/UPDATE/DELETE) multiple times with each parameter set
|
|
261
291
|
* and optionally return results.
|
|
262
292
|
* This is faster than executing separately with each parameter set.
|
|
293
|
+
*
|
|
294
|
+
* @param sql The SQL query to execute
|
|
295
|
+
* @param parameters Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution
|
|
296
|
+
* @returns The query result
|
|
263
297
|
*/
|
|
264
298
|
executeBatch(sql: string, parameters?: any[][]): Promise<QueryResult>;
|
|
265
299
|
/**
|
|
266
300
|
* Execute a read-only query and return results.
|
|
301
|
+
*
|
|
302
|
+
* @param sql The SQL query to execute
|
|
303
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
304
|
+
* @returns An array of results
|
|
267
305
|
*/
|
|
268
306
|
getAll<T>(sql: string, parameters?: any[]): Promise<T[]>;
|
|
269
307
|
/**
|
|
270
308
|
* Execute a read-only query and return the first result, or null if the ResultSet is empty.
|
|
309
|
+
*
|
|
310
|
+
* @param sql The SQL query to execute
|
|
311
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
312
|
+
* @returns The first result if found, or null if no results are returned
|
|
271
313
|
*/
|
|
272
314
|
getOptional<T>(sql: string, parameters?: any[]): Promise<T | null>;
|
|
273
315
|
/**
|
|
274
316
|
* Execute a read-only query and return the first result, error if the ResultSet is empty.
|
|
317
|
+
*
|
|
318
|
+
* @param sql The SQL query to execute
|
|
319
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
320
|
+
* @returns The first result matching the query
|
|
321
|
+
* @throws Error if no rows are returned
|
|
275
322
|
*/
|
|
276
323
|
get<T>(sql: string, parameters?: any[]): Promise<T>;
|
|
277
324
|
/**
|
|
@@ -288,12 +335,22 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
288
335
|
* Open a read-only transaction.
|
|
289
336
|
* Read transactions can run concurrently to a write transaction.
|
|
290
337
|
* Changes from any write transaction are not visible to read transactions started before it.
|
|
338
|
+
*
|
|
339
|
+
* @param callback Function to execute within the transaction
|
|
340
|
+
* @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
|
|
341
|
+
* @returns The result of the callback
|
|
342
|
+
* @throws Error if the lock cannot be obtained within the timeout period
|
|
291
343
|
*/
|
|
292
344
|
readTransaction<T>(callback: (tx: Transaction) => Promise<T>, lockTimeout?: number): Promise<T>;
|
|
293
345
|
/**
|
|
294
346
|
* Open a read-write transaction.
|
|
295
347
|
* This takes a global lock - only one write transaction can execute against the database at a time.
|
|
296
348
|
* Statements within the transaction must be done on the provided {@link Transaction} interface.
|
|
349
|
+
*
|
|
350
|
+
* @param callback Function to execute within the transaction
|
|
351
|
+
* @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
|
|
352
|
+
* @returns The result of the callback
|
|
353
|
+
* @throws Error if the lock cannot be obtained within the timeout period
|
|
297
354
|
*/
|
|
298
355
|
writeTransaction<T>(callback: (tx: Transaction) => Promise<T>, lockTimeout?: number): Promise<T>;
|
|
299
356
|
/**
|
|
@@ -336,14 +393,34 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
336
393
|
* Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
|
|
337
394
|
*
|
|
338
395
|
* Note that the `onChange` callback member of the handler is required.
|
|
396
|
+
*
|
|
397
|
+
* @param sql The SQL query to execute
|
|
398
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
399
|
+
* @param handler Callbacks for handling results and errors
|
|
400
|
+
* @param options Options for configuring watch behavior
|
|
339
401
|
*/
|
|
340
402
|
watchWithCallback(sql: string, parameters?: any[], handler?: WatchHandler, options?: SQLWatchOptions): void;
|
|
341
403
|
/**
|
|
342
404
|
* Execute a read query every time the source tables are modified.
|
|
343
405
|
* Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
|
|
344
406
|
* Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
|
|
407
|
+
*
|
|
408
|
+
* @param sql The SQL query to execute
|
|
409
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
410
|
+
* @param options Options for configuring watch behavior
|
|
411
|
+
* @returns An AsyncIterable that yields QueryResults whenever the data changes
|
|
345
412
|
*/
|
|
346
413
|
watchWithAsyncGenerator(sql: string, parameters?: any[], options?: SQLWatchOptions): AsyncIterable<QueryResult>;
|
|
414
|
+
/**
|
|
415
|
+
* Resolves the list of tables that are used in a SQL query.
|
|
416
|
+
* If tables are specified in the options, those are used directly.
|
|
417
|
+
* Otherwise, analyzes the query using EXPLAIN to determine which tables are accessed.
|
|
418
|
+
*
|
|
419
|
+
* @param sql The SQL query to analyze
|
|
420
|
+
* @param parameters Optional parameters for the SQL query
|
|
421
|
+
* @param options Optional watch options that may contain explicit table list
|
|
422
|
+
* @returns Array of table names that the query depends on
|
|
423
|
+
*/
|
|
347
424
|
resolveTables(sql: string, parameters?: any[], options?: SQLWatchOptions): Promise<string[]>;
|
|
348
425
|
/**
|
|
349
426
|
* This version of `onChange` uses {@link AsyncGenerator}, for documentation see {@link onChangeWithAsyncGenerator}.
|
|
@@ -382,7 +459,9 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
382
459
|
*
|
|
383
460
|
* Note that the `onChange` callback member of the handler is required.
|
|
384
461
|
*
|
|
385
|
-
*
|
|
462
|
+
* @param handler Callbacks for handling change events and errors
|
|
463
|
+
* @param options Options for configuring watch behavior
|
|
464
|
+
* @returns A dispose function to stop watching for changes
|
|
386
465
|
*/
|
|
387
466
|
onChangeWithCallback(handler?: WatchOnChangeHandler, options?: SQLWatchOptions): () => void;
|
|
388
467
|
/**
|
|
@@ -391,7 +470,10 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
391
470
|
* This is preferred over {@link watchWithAsyncGenerator} when multiple queries need to be performed
|
|
392
471
|
* together when data is changed.
|
|
393
472
|
*
|
|
394
|
-
* Note
|
|
473
|
+
* Note: do not declare this as `async *onChange` as it will not work in React Native.
|
|
474
|
+
*
|
|
475
|
+
* @param options Options for configuring watch behavior
|
|
476
|
+
* @returns An AsyncIterable that yields change events whenever the specified tables change
|
|
395
477
|
*/
|
|
396
478
|
onChangeWithAsyncGenerator(options?: SQLWatchOptions): AsyncIterable<WatchOnChangeEvent>;
|
|
397
479
|
private handleTableChanges;
|
|
@@ -7,14 +7,14 @@ import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js';
|
|
|
7
7
|
import { BaseObserver } from '../utils/BaseObserver.js';
|
|
8
8
|
import { ControlledExecutor } from '../utils/ControlledExecutor.js';
|
|
9
9
|
import { mutexRunExclusive } from '../utils/mutex.js';
|
|
10
|
-
import { throttleTrailing } from '../utils/
|
|
10
|
+
import { throttleTrailing } from '../utils/async.js';
|
|
11
11
|
import { isDBAdapter, isSQLOpenFactory, isSQLOpenOptions } from './SQLOpenFactory.js';
|
|
12
|
+
import { runOnSchemaChange } from './runOnSchemaChange.js';
|
|
12
13
|
import { PSInternalTable } from './sync/bucket/BucketStorageAdapter.js';
|
|
13
14
|
import { CrudBatch } from './sync/bucket/CrudBatch.js';
|
|
14
15
|
import { CrudEntry } from './sync/bucket/CrudEntry.js';
|
|
15
16
|
import { CrudTransaction } from './sync/bucket/CrudTransaction.js';
|
|
16
17
|
import { DEFAULT_CRUD_UPLOAD_THROTTLE_MS, DEFAULT_RETRY_DELAY_MS } from './sync/stream/AbstractStreamingSyncImplementation.js';
|
|
17
|
-
import { runOnSchemaChange } from './runOnSchemaChange.js';
|
|
18
18
|
const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
|
|
19
19
|
const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
|
|
20
20
|
clearLocal: true
|
|
@@ -42,6 +42,10 @@ export const DEFAULT_LOCK_TIMEOUT_MS = 120_000; // 2 mins
|
|
|
42
42
|
export const isPowerSyncDatabaseOptionsWithSettings = (test) => {
|
|
43
43
|
return typeof test == 'object' && isSQLOpenOptions(test.database);
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* The priority used by the core extension to indicate that a full sync was completed.
|
|
47
|
+
*/
|
|
48
|
+
const FULL_SYNC_PRIORITY = 2147483647;
|
|
45
49
|
export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
46
50
|
options;
|
|
47
51
|
/**
|
|
@@ -127,16 +131,27 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
127
131
|
await this._isReadyPromise;
|
|
128
132
|
}
|
|
129
133
|
/**
|
|
134
|
+
* Wait for the first sync operation to complete.
|
|
135
|
+
*
|
|
136
|
+
* @param request Either an abort signal (after which the promise will complete regardless of
|
|
137
|
+
* whether a full sync was completed) or an object providing an abort signal and a priority target.
|
|
138
|
+
* When a priority target is set, the promise may complete when all buckets with the given (or higher)
|
|
139
|
+
* priorities have been synchronized. This can be earlier than a complete sync.
|
|
130
140
|
* @returns A promise which will resolve once the first full sync has completed.
|
|
131
141
|
*/
|
|
132
|
-
async waitForFirstSync(
|
|
133
|
-
|
|
142
|
+
async waitForFirstSync(request) {
|
|
143
|
+
const signal = request instanceof AbortSignal ? request : request?.signal;
|
|
144
|
+
const priority = request && 'priority' in request ? request.priority : undefined;
|
|
145
|
+
const statusMatches = priority === undefined
|
|
146
|
+
? (status) => status.hasSynced
|
|
147
|
+
: (status) => status.statusForPriority(priority).hasSynced;
|
|
148
|
+
if (statusMatches(this.currentStatus)) {
|
|
134
149
|
return;
|
|
135
150
|
}
|
|
136
151
|
return new Promise((resolve) => {
|
|
137
152
|
const dispose = this.registerListener({
|
|
138
153
|
statusChanged: (status) => {
|
|
139
|
-
if (status
|
|
154
|
+
if (statusMatches(status)) {
|
|
140
155
|
dispose();
|
|
141
156
|
resolve();
|
|
142
157
|
}
|
|
@@ -177,19 +192,36 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
177
192
|
.map((n) => parseInt(n));
|
|
178
193
|
}
|
|
179
194
|
catch (e) {
|
|
180
|
-
throw new Error(`Unsupported powersync extension version. Need >=0.
|
|
195
|
+
throw new Error(`Unsupported powersync extension version. Need >=0.3.11 <1.0.0, got: ${this.sdkVersion}. Details: ${e.message}`);
|
|
181
196
|
}
|
|
182
|
-
// Validate >=0.
|
|
183
|
-
if (versionInts[0] != 0 || versionInts[1] <
|
|
184
|
-
throw new Error(`Unsupported powersync extension version. Need >=0.
|
|
197
|
+
// Validate >=0.3.11 <1.0.0
|
|
198
|
+
if (versionInts[0] != 0 || versionInts[1] < 3 || (versionInts[1] == 3 && versionInts[2] < 11)) {
|
|
199
|
+
throw new Error(`Unsupported powersync extension version. Need >=0.3.11 <1.0.0, got: ${this.sdkVersion}`);
|
|
185
200
|
}
|
|
186
201
|
}
|
|
187
202
|
async updateHasSynced() {
|
|
188
|
-
const result = await this.database.
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
const result = await this.database.getAll('SELECT priority, last_synced_at FROM ps_sync_state ORDER BY priority DESC');
|
|
204
|
+
let lastCompleteSync;
|
|
205
|
+
const priorityStatusEntries = [];
|
|
206
|
+
for (const { priority, last_synced_at } of result) {
|
|
207
|
+
const parsedDate = new Date(last_synced_at + 'Z');
|
|
208
|
+
if (priority == FULL_SYNC_PRIORITY) {
|
|
209
|
+
// This lowest-possible priority represents a complete sync.
|
|
210
|
+
lastCompleteSync = parsedDate;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
priorityStatusEntries.push({ priority, hasSynced: true, lastSyncedAt: parsedDate });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const hasSynced = lastCompleteSync != null;
|
|
217
|
+
const updatedStatus = new SyncStatus({
|
|
218
|
+
...this.currentStatus.toJSON(),
|
|
219
|
+
hasSynced,
|
|
220
|
+
priorityStatusEntries,
|
|
221
|
+
lastSyncedAt: lastCompleteSync
|
|
222
|
+
});
|
|
223
|
+
if (!updatedStatus.isEqual(this.currentStatus)) {
|
|
224
|
+
this.currentStatus = updatedStatus;
|
|
193
225
|
this.iterateListeners((l) => l.statusChanged?.(this.currentStatus));
|
|
194
226
|
}
|
|
195
227
|
}
|
|
@@ -245,7 +277,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
245
277
|
const { retryDelayMs, crudUploadThrottleMs } = this.resolvedConnectionOptions(options);
|
|
246
278
|
this.syncStreamImplementation = this.generateSyncStreamImplementation(connector, {
|
|
247
279
|
retryDelayMs,
|
|
248
|
-
crudUploadThrottleMs
|
|
280
|
+
crudUploadThrottleMs
|
|
249
281
|
});
|
|
250
282
|
this.syncStatusListenerDisposer = this.syncStreamImplementation.registerListener({
|
|
251
283
|
statusChanged: (status) => {
|
|
@@ -302,12 +334,15 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
302
334
|
*/
|
|
303
335
|
async close(options = DEFAULT_POWERSYNC_CLOSE_OPTIONS) {
|
|
304
336
|
await this.waitForReady();
|
|
337
|
+
if (this.closed) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
305
340
|
const { disconnect } = options;
|
|
306
341
|
if (disconnect) {
|
|
307
342
|
await this.disconnect();
|
|
308
343
|
}
|
|
309
344
|
await this.syncStreamImplementation?.dispose();
|
|
310
|
-
this.database.close();
|
|
345
|
+
await this.database.close();
|
|
311
346
|
this.closed = true;
|
|
312
347
|
}
|
|
313
348
|
/**
|
|
@@ -328,7 +363,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
328
363
|
});
|
|
329
364
|
}
|
|
330
365
|
/**
|
|
331
|
-
* Get a batch of
|
|
366
|
+
* Get a batch of CRUD data to upload.
|
|
332
367
|
*
|
|
333
368
|
* Returns null if there is no data to upload.
|
|
334
369
|
*
|
|
@@ -343,6 +378,9 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
343
378
|
* This method does include transaction ids in the result, but does not group
|
|
344
379
|
* data by transaction. One batch may contain data from multiple transactions,
|
|
345
380
|
* and a single transaction may be split over multiple batches.
|
|
381
|
+
*
|
|
382
|
+
* @param limit Maximum number of CRUD entries to include in the batch
|
|
383
|
+
* @returns A batch of CRUD operations to upload, or null if there are none
|
|
346
384
|
*/
|
|
347
385
|
async getCrudBatch(limit = DEFAULT_CRUD_BATCH_LIMIT) {
|
|
348
386
|
const result = await this.getAll(`SELECT id, tx_id, data FROM ${PSInternalTable.CRUD} ORDER BY id ASC LIMIT ?`, [limit + 1]);
|
|
@@ -370,6 +408,8 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
370
408
|
*
|
|
371
409
|
* Unlike {@link getCrudBatch}, this only returns data from a single transaction at a time.
|
|
372
410
|
* All data for the transaction is loaded into memory.
|
|
411
|
+
*
|
|
412
|
+
* @returns A transaction of CRUD operations to upload, or null if there are none
|
|
373
413
|
*/
|
|
374
414
|
async getNextCrudTransaction() {
|
|
375
415
|
return await this.readTransaction(async (tx) => {
|
|
@@ -394,6 +434,8 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
394
434
|
* Get an unique client id for this database.
|
|
395
435
|
*
|
|
396
436
|
* The id is not reset when the database is cleared, only when the database is deleted.
|
|
437
|
+
*
|
|
438
|
+
* @returns A unique identifier for the database instance
|
|
397
439
|
*/
|
|
398
440
|
async getClientId() {
|
|
399
441
|
return this.bucketStorageAdapter.getClientId();
|
|
@@ -417,17 +459,38 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
417
459
|
});
|
|
418
460
|
}
|
|
419
461
|
/**
|
|
420
|
-
* Execute a write (INSERT/UPDATE/DELETE) query
|
|
462
|
+
* Execute a SQL write (INSERT/UPDATE/DELETE) query
|
|
421
463
|
* and optionally return results.
|
|
464
|
+
*
|
|
465
|
+
* @param sql The SQL query to execute
|
|
466
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
467
|
+
* @returns The query result as an object with structured key-value pairs
|
|
422
468
|
*/
|
|
423
469
|
async execute(sql, parameters) {
|
|
424
470
|
await this.waitForReady();
|
|
425
471
|
return this.database.execute(sql, parameters);
|
|
426
472
|
}
|
|
473
|
+
/**
|
|
474
|
+
* Execute a SQL write (INSERT/UPDATE/DELETE) query directly on the database without any PowerSync processing.
|
|
475
|
+
* This bypasses certain PowerSync abstractions and is useful for accessing the raw database results.
|
|
476
|
+
*
|
|
477
|
+
* @param sql The SQL query to execute
|
|
478
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
479
|
+
* @returns The raw query result from the underlying database as a nested array of raw values, where each row is
|
|
480
|
+
* represented as an array of column values without field names.
|
|
481
|
+
*/
|
|
482
|
+
async executeRaw(sql, parameters) {
|
|
483
|
+
await this.waitForReady();
|
|
484
|
+
return this.database.executeRaw(sql, parameters);
|
|
485
|
+
}
|
|
427
486
|
/**
|
|
428
487
|
* Execute a write query (INSERT/UPDATE/DELETE) multiple times with each parameter set
|
|
429
488
|
* and optionally return results.
|
|
430
489
|
* This is faster than executing separately with each parameter set.
|
|
490
|
+
*
|
|
491
|
+
* @param sql The SQL query to execute
|
|
492
|
+
* @param parameters Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution
|
|
493
|
+
* @returns The query result
|
|
431
494
|
*/
|
|
432
495
|
async executeBatch(sql, parameters) {
|
|
433
496
|
await this.waitForReady();
|
|
@@ -435,6 +498,10 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
435
498
|
}
|
|
436
499
|
/**
|
|
437
500
|
* Execute a read-only query and return results.
|
|
501
|
+
*
|
|
502
|
+
* @param sql The SQL query to execute
|
|
503
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
504
|
+
* @returns An array of results
|
|
438
505
|
*/
|
|
439
506
|
async getAll(sql, parameters) {
|
|
440
507
|
await this.waitForReady();
|
|
@@ -442,6 +509,10 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
442
509
|
}
|
|
443
510
|
/**
|
|
444
511
|
* Execute a read-only query and return the first result, or null if the ResultSet is empty.
|
|
512
|
+
*
|
|
513
|
+
* @param sql The SQL query to execute
|
|
514
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
515
|
+
* @returns The first result if found, or null if no results are returned
|
|
445
516
|
*/
|
|
446
517
|
async getOptional(sql, parameters) {
|
|
447
518
|
await this.waitForReady();
|
|
@@ -449,6 +520,11 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
449
520
|
}
|
|
450
521
|
/**
|
|
451
522
|
* Execute a read-only query and return the first result, error if the ResultSet is empty.
|
|
523
|
+
*
|
|
524
|
+
* @param sql The SQL query to execute
|
|
525
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
526
|
+
* @returns The first result matching the query
|
|
527
|
+
* @throws Error if no rows are returned
|
|
452
528
|
*/
|
|
453
529
|
async get(sql, parameters) {
|
|
454
530
|
await this.waitForReady();
|
|
@@ -477,6 +553,11 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
477
553
|
* Open a read-only transaction.
|
|
478
554
|
* Read transactions can run concurrently to a write transaction.
|
|
479
555
|
* Changes from any write transaction are not visible to read transactions started before it.
|
|
556
|
+
*
|
|
557
|
+
* @param callback Function to execute within the transaction
|
|
558
|
+
* @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
|
|
559
|
+
* @returns The result of the callback
|
|
560
|
+
* @throws Error if the lock cannot be obtained within the timeout period
|
|
480
561
|
*/
|
|
481
562
|
async readTransaction(callback, lockTimeout = DEFAULT_LOCK_TIMEOUT_MS) {
|
|
482
563
|
await this.waitForReady();
|
|
@@ -490,6 +571,11 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
490
571
|
* Open a read-write transaction.
|
|
491
572
|
* This takes a global lock - only one write transaction can execute against the database at a time.
|
|
492
573
|
* Statements within the transaction must be done on the provided {@link Transaction} interface.
|
|
574
|
+
*
|
|
575
|
+
* @param callback Function to execute within the transaction
|
|
576
|
+
* @param lockTimeout Time in milliseconds to wait for a lock before throwing an error
|
|
577
|
+
* @returns The result of the callback
|
|
578
|
+
* @throws Error if the lock cannot be obtained within the timeout period
|
|
493
579
|
*/
|
|
494
580
|
async writeTransaction(callback, lockTimeout = DEFAULT_LOCK_TIMEOUT_MS) {
|
|
495
581
|
await this.waitForReady();
|
|
@@ -514,6 +600,11 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
514
600
|
* Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
|
|
515
601
|
*
|
|
516
602
|
* Note that the `onChange` callback member of the handler is required.
|
|
603
|
+
*
|
|
604
|
+
* @param sql The SQL query to execute
|
|
605
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
606
|
+
* @param handler Callbacks for handling results and errors
|
|
607
|
+
* @param options Options for configuring watch behavior
|
|
517
608
|
*/
|
|
518
609
|
watchWithCallback(sql, parameters, handler, options) {
|
|
519
610
|
const { onResult, onError = (e) => this.options.logger?.error(e) } = handler ?? {};
|
|
@@ -554,6 +645,11 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
554
645
|
* Execute a read query every time the source tables are modified.
|
|
555
646
|
* Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
|
|
556
647
|
* Source tables are automatically detected using `EXPLAIN QUERY PLAN`.
|
|
648
|
+
*
|
|
649
|
+
* @param sql The SQL query to execute
|
|
650
|
+
* @param parameters Optional array of parameters to bind to the query
|
|
651
|
+
* @param options Options for configuring watch behavior
|
|
652
|
+
* @returns An AsyncIterable that yields QueryResults whenever the data changes
|
|
557
653
|
*/
|
|
558
654
|
watchWithAsyncGenerator(sql, parameters, options) {
|
|
559
655
|
return new EventIterator((eventOptions) => {
|
|
@@ -571,6 +667,16 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
571
667
|
});
|
|
572
668
|
});
|
|
573
669
|
}
|
|
670
|
+
/**
|
|
671
|
+
* Resolves the list of tables that are used in a SQL query.
|
|
672
|
+
* If tables are specified in the options, those are used directly.
|
|
673
|
+
* Otherwise, analyzes the query using EXPLAIN to determine which tables are accessed.
|
|
674
|
+
*
|
|
675
|
+
* @param sql The SQL query to analyze
|
|
676
|
+
* @param parameters Optional parameters for the SQL query
|
|
677
|
+
* @param options Optional watch options that may contain explicit table list
|
|
678
|
+
* @returns Array of table names that the query depends on
|
|
679
|
+
*/
|
|
574
680
|
async resolveTables(sql, parameters, options) {
|
|
575
681
|
const resolvedTables = options?.tables ? [...options.tables] : [];
|
|
576
682
|
if (!options?.tables) {
|
|
@@ -602,7 +708,9 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
602
708
|
*
|
|
603
709
|
* Note that the `onChange` callback member of the handler is required.
|
|
604
710
|
*
|
|
605
|
-
*
|
|
711
|
+
* @param handler Callbacks for handling change events and errors
|
|
712
|
+
* @param options Options for configuring watch behavior
|
|
713
|
+
* @returns A dispose function to stop watching for changes
|
|
606
714
|
*/
|
|
607
715
|
onChangeWithCallback(handler, options) {
|
|
608
716
|
const { onChange, onError = (e) => this.options.logger?.error(e) } = handler ?? {};
|
|
@@ -644,7 +752,10 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
644
752
|
* This is preferred over {@link watchWithAsyncGenerator} when multiple queries need to be performed
|
|
645
753
|
* together when data is changed.
|
|
646
754
|
*
|
|
647
|
-
* Note
|
|
755
|
+
* Note: do not declare this as `async *onChange` as it will not work in React Native.
|
|
756
|
+
*
|
|
757
|
+
* @param options Options for configuring watch behavior
|
|
758
|
+
* @returns An AsyncIterable that yields change events whenever the specified tables change
|
|
648
759
|
*/
|
|
649
760
|
onChangeWithAsyncGenerator(options) {
|
|
650
761
|
const resolvedOptions = options ?? {};
|
|
@@ -6,6 +6,9 @@ export interface SQLOpenOptions {
|
|
|
6
6
|
dbFilename: string;
|
|
7
7
|
/**
|
|
8
8
|
* Directory where the database file is located.
|
|
9
|
+
*
|
|
10
|
+
* When set, the directory must exist when the database is opened, it will
|
|
11
|
+
* not be created automatically.
|
|
9
12
|
*/
|
|
10
13
|
dbLocation?: string;
|
|
11
14
|
/**
|
|
@@ -2,6 +2,10 @@ import { BaseListener, BaseObserver, Disposable } from '../../../utils/BaseObser
|
|
|
2
2
|
import { CrudBatch } from './CrudBatch.js';
|
|
3
3
|
import { CrudEntry, OpId } from './CrudEntry.js';
|
|
4
4
|
import { SyncDataBatch } from './SyncDataBatch.js';
|
|
5
|
+
export interface BucketDescription {
|
|
6
|
+
name: string;
|
|
7
|
+
priority: number;
|
|
8
|
+
}
|
|
5
9
|
export interface Checkpoint {
|
|
6
10
|
last_op_id: OpId;
|
|
7
11
|
buckets: BucketChecksum[];
|
|
@@ -25,6 +29,7 @@ export interface SyncLocalDatabaseResult {
|
|
|
25
29
|
}
|
|
26
30
|
export interface BucketChecksum {
|
|
27
31
|
bucket: string;
|
|
32
|
+
priority?: number;
|
|
28
33
|
/**
|
|
29
34
|
* 32-bit unsigned hash.
|
|
30
35
|
*/
|
|
@@ -51,7 +56,7 @@ export interface BucketStorageAdapter extends BaseObserver<BucketStorageListener
|
|
|
51
56
|
setTargetCheckpoint(checkpoint: Checkpoint): Promise<void>;
|
|
52
57
|
startSession(): void;
|
|
53
58
|
getBucketStates(): Promise<BucketState[]>;
|
|
54
|
-
syncLocalDatabase(checkpoint: Checkpoint): Promise<{
|
|
59
|
+
syncLocalDatabase(checkpoint: Checkpoint, priority?: number): Promise<{
|
|
55
60
|
checkpointValid: boolean;
|
|
56
61
|
ready: boolean;
|
|
57
62
|
failures?: any[];
|
|
@@ -37,14 +37,14 @@ export declare class SqliteBucketStorage extends BaseObserver<BucketStorageListe
|
|
|
37
37
|
*/
|
|
38
38
|
private deleteBucket;
|
|
39
39
|
hasCompletedSync(): Promise<boolean>;
|
|
40
|
-
syncLocalDatabase(checkpoint: Checkpoint): Promise<SyncLocalDatabaseResult>;
|
|
40
|
+
syncLocalDatabase(checkpoint: Checkpoint, priority?: number): Promise<SyncLocalDatabaseResult>;
|
|
41
41
|
/**
|
|
42
42
|
* Atomically update the local state to the current checkpoint.
|
|
43
43
|
*
|
|
44
44
|
* This includes creating new tables, dropping old tables, and copying data over from the oplog.
|
|
45
45
|
*/
|
|
46
46
|
private updateObjectsFromBuckets;
|
|
47
|
-
validateChecksums(checkpoint: Checkpoint): Promise<SyncLocalDatabaseResult>;
|
|
47
|
+
validateChecksums(checkpoint: Checkpoint, priority: number | undefined): Promise<SyncLocalDatabaseResult>;
|
|
48
48
|
/**
|
|
49
49
|
* Force a compact, for tests.
|
|
50
50
|
*/
|
|
@@ -106,8 +106,8 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
106
106
|
}
|
|
107
107
|
return completed;
|
|
108
108
|
}
|
|
109
|
-
async syncLocalDatabase(checkpoint) {
|
|
110
|
-
const r = await this.validateChecksums(checkpoint);
|
|
109
|
+
async syncLocalDatabase(checkpoint, priority) {
|
|
110
|
+
const r = await this.validateChecksums(checkpoint, priority);
|
|
111
111
|
if (!r.checkpointValid) {
|
|
112
112
|
this.logger.error('Checksums failed for', r.checkpointFailures);
|
|
113
113
|
for (const b of r.checkpointFailures ?? []) {
|
|
@@ -115,17 +115,21 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
115
115
|
}
|
|
116
116
|
return { ready: false, checkpointValid: false, checkpointFailures: r.checkpointFailures };
|
|
117
117
|
}
|
|
118
|
-
const
|
|
118
|
+
const buckets = checkpoint.buckets;
|
|
119
|
+
if (priority !== undefined) {
|
|
120
|
+
buckets.filter((b) => hasMatchingPriority(priority, b));
|
|
121
|
+
}
|
|
122
|
+
const bucketNames = buckets.map((b) => b.bucket);
|
|
119
123
|
await this.writeTransaction(async (tx) => {
|
|
120
124
|
await tx.execute(`UPDATE ps_buckets SET last_op = ? WHERE name IN (SELECT json_each.value FROM json_each(?))`, [
|
|
121
125
|
checkpoint.last_op_id,
|
|
122
126
|
JSON.stringify(bucketNames)
|
|
123
127
|
]);
|
|
124
|
-
if (checkpoint.write_checkpoint) {
|
|
128
|
+
if (priority == null && checkpoint.write_checkpoint) {
|
|
125
129
|
await tx.execute("UPDATE ps_buckets SET last_op = ? WHERE name = '$local'", [checkpoint.write_checkpoint]);
|
|
126
130
|
}
|
|
127
131
|
});
|
|
128
|
-
const valid = await this.updateObjectsFromBuckets(checkpoint);
|
|
132
|
+
const valid = await this.updateObjectsFromBuckets(checkpoint, priority);
|
|
129
133
|
if (!valid) {
|
|
130
134
|
this.logger.debug('Not at a consistent checkpoint - cannot update local db');
|
|
131
135
|
return { ready: false, checkpointValid: true };
|
|
@@ -141,19 +145,36 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
141
145
|
*
|
|
142
146
|
* This includes creating new tables, dropping old tables, and copying data over from the oplog.
|
|
143
147
|
*/
|
|
144
|
-
async updateObjectsFromBuckets(checkpoint) {
|
|
148
|
+
async updateObjectsFromBuckets(checkpoint, priority) {
|
|
149
|
+
let arg = '';
|
|
150
|
+
if (priority !== undefined) {
|
|
151
|
+
const affectedBuckets = [];
|
|
152
|
+
for (const desc of checkpoint.buckets) {
|
|
153
|
+
if (hasMatchingPriority(priority, desc)) {
|
|
154
|
+
affectedBuckets.push(desc.bucket);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
arg = JSON.stringify({ priority, buckets: affectedBuckets });
|
|
158
|
+
}
|
|
145
159
|
return this.writeTransaction(async (tx) => {
|
|
146
160
|
const { insertId: result } = await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', [
|
|
147
161
|
'sync_local',
|
|
148
|
-
|
|
162
|
+
arg
|
|
149
163
|
]);
|
|
150
164
|
return result == 1;
|
|
151
165
|
});
|
|
152
166
|
}
|
|
153
|
-
async validateChecksums(checkpoint) {
|
|
154
|
-
|
|
167
|
+
async validateChecksums(checkpoint, priority) {
|
|
168
|
+
if (priority !== undefined) {
|
|
169
|
+
// Only validate the buckets within the priority we care about
|
|
170
|
+
const newBuckets = checkpoint.buckets.filter((cs) => hasMatchingPriority(priority, cs));
|
|
171
|
+
checkpoint = { ...checkpoint, buckets: newBuckets };
|
|
172
|
+
}
|
|
173
|
+
const rs = await this.db.execute('SELECT powersync_validate_checkpoint(?) as result', [
|
|
174
|
+
JSON.stringify({ ...checkpoint })
|
|
175
|
+
]);
|
|
155
176
|
const resultItem = rs.rows?.item(0);
|
|
156
|
-
this.logger.debug('validateChecksums result item', resultItem);
|
|
177
|
+
this.logger.debug('validateChecksums priority, checkpoint, result item', priority, checkpoint, resultItem);
|
|
157
178
|
if (!resultItem) {
|
|
158
179
|
return {
|
|
159
180
|
checkpointValid: false,
|
|
@@ -304,3 +325,6 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
304
325
|
// No-op for now
|
|
305
326
|
}
|
|
306
327
|
}
|
|
328
|
+
function hasMatchingPriority(priority, bucket) {
|
|
329
|
+
return bucket.priority != null && bucket.priority <= priority;
|
|
330
|
+
}
|