@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.
@@ -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, StreamingSyncImplementation, StreamingSyncImplementationListener, type RequiredAdditionalConnectionOptions } from './sync/stream/AbstractStreamingSyncImplementation.js';
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(signal?: AbortSignal): Promise<void>;
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 crud data to upload.
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
- * Returns dispose function to stop watching.
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, do not declare this as `async *onChange` as it will not work in React Native
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/throttle.js';
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(signal) {
133
- if (this.currentStatus.hasSynced) {
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.hasSynced) {
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.2.0 <1.0.0, got: ${this.sdkVersion}. Details: ${e.message}`);
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.2.0 <1.0.0
183
- if (versionInts[0] != 0 || versionInts[1] < 2 || versionInts[2] < 0) {
184
- throw new Error(`Unsupported powersync extension version. Need >=0.2.0 <1.0.0, got: ${this.sdkVersion}`);
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.get('SELECT powersync_last_synced_at() as synced_at');
189
- const hasSynced = result.synced_at != null;
190
- const syncedAt = result.synced_at != null ? new Date(result.synced_at + 'Z') : undefined;
191
- if (hasSynced != this.currentStatus.hasSynced) {
192
- this.currentStatus = new SyncStatus({ ...this.currentStatus.toJSON(), hasSynced, lastSyncedAt: syncedAt });
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 crud data to upload.
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
- * Returns dispose function to stop watching.
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, do not declare this as `async *onChange` as it will not work in React Native
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 bucketNames = checkpoint.buckets.map((b) => b.bucket);
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
- const rs = await this.db.execute('SELECT powersync_validate_checkpoint(?) as result', [JSON.stringify(checkpoint)]);
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
+ }