@powersync/common 1.20.1 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -76,6 +76,7 @@ export interface WatchOnChangeHandler {
76
76
  }
77
77
  export interface PowerSyncDBListener extends StreamingSyncImplementationListener {
78
78
  initialized: () => void;
79
+ schemaChanged: (schema: Schema) => void;
79
80
  }
80
81
  export interface PowerSyncCloseOptions {
81
82
  /**
@@ -92,6 +93,7 @@ export declare const DEFAULT_POWERSYNC_DB_OPTIONS: {
92
93
  logger: Logger.ILogger;
93
94
  crudUploadThrottleMs: number;
94
95
  };
96
+ export declare const DEFAULT_CRUD_BATCH_LIMIT = 100;
95
97
  /**
96
98
  * Requesting nested or recursive locks can block the application in some circumstances.
97
99
  * This default lock timeout will act as a failsafe to throw an error if a lock cannot
@@ -232,7 +234,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
232
234
  * data by transaction. One batch may contain data from multiple transactions,
233
235
  * and a single transaction may be split over multiple batches.
234
236
  */
235
- getCrudBatch(limit: number): Promise<CrudBatch | null>;
237
+ getCrudBatch(limit?: number): Promise<CrudBatch | null>;
236
238
  /**
237
239
  * Get the next recorded transaction to upload.
238
240
  *
@@ -14,6 +14,7 @@ import { CrudBatch } from './sync/bucket/CrudBatch.js';
14
14
  import { CrudEntry } from './sync/bucket/CrudEntry.js';
15
15
  import { CrudTransaction } from './sync/bucket/CrudTransaction.js';
16
16
  import { DEFAULT_CRUD_UPLOAD_THROTTLE_MS } from './sync/stream/AbstractStreamingSyncImplementation.js';
17
+ import { runOnSchemaChange } from './runOnSchemaChange.js';
17
18
  const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
18
19
  const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
19
20
  clearLocal: true
@@ -27,6 +28,7 @@ export const DEFAULT_POWERSYNC_DB_OPTIONS = {
27
28
  logger: Logger.get('PowerSyncDatabase'),
28
29
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
29
30
  };
31
+ export const DEFAULT_CRUD_BATCH_LIMIT = 100;
30
32
  /**
31
33
  * Requesting nested or recursive locks can block the application in some circumstances.
32
34
  * This default lock timeout will act as a failsafe to throw an error if a lock cannot
@@ -210,6 +212,8 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
210
212
  }
211
213
  this._schema = schema;
212
214
  await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
215
+ await this.database.refreshSchema();
216
+ this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
213
217
  }
214
218
  /**
215
219
  * Wait for initialization to complete.
@@ -326,7 +330,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
326
330
  * data by transaction. One batch may contain data from multiple transactions,
327
331
  * and a single transaction may be split over multiple batches.
328
332
  */
329
- async getCrudBatch(limit) {
333
+ async getCrudBatch(limit = DEFAULT_CRUD_BATCH_LIMIT) {
330
334
  const result = await this.getAll(`SELECT id, tx_id, data FROM ${PSInternalTable.CRUD} ORDER BY id ASC LIMIT ?`, [limit + 1]);
331
335
  const all = result.map((row) => CrudEntry.fromRow(row)) ?? [];
332
336
  let haveMore = false;
@@ -502,7 +506,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
502
506
  if (!onResult) {
503
507
  throw new Error('onResult is required');
504
508
  }
505
- (async () => {
509
+ const watchQuery = async (abortSignal) => {
506
510
  try {
507
511
  const resolvedTables = await this.resolveTables(sql, parameters, options);
508
512
  // Fetch initial data
@@ -521,13 +525,16 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
521
525
  onError
522
526
  }, {
523
527
  ...(options ?? {}),
524
- tables: resolvedTables
528
+ tables: resolvedTables,
529
+ // Override the abort signal since we intercept it
530
+ signal: abortSignal
525
531
  });
526
532
  }
527
533
  catch (error) {
528
534
  onError?.(error);
529
535
  }
530
- })();
536
+ };
537
+ runOnSchemaChange(watchQuery, this, options);
531
538
  }
532
539
  /**
533
540
  * Execute a read query every time the source tables are modified.
@@ -536,17 +543,18 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
536
543
  */
537
544
  watchWithAsyncGenerator(sql, parameters, options) {
538
545
  return new EventIterator((eventOptions) => {
539
- (async () => {
540
- const resolvedTables = await this.resolveTables(sql, parameters, options);
541
- // Fetch initial data
542
- eventOptions.push(await this.executeReadOnly(sql, parameters));
543
- for await (const event of this.onChangeWithAsyncGenerator({
544
- ...(options ?? {}),
545
- tables: resolvedTables
546
- })) {
547
- eventOptions.push(await this.executeReadOnly(sql, parameters));
546
+ const handler = {
547
+ onResult: (result) => {
548
+ eventOptions.push(result);
549
+ },
550
+ onError: (error) => {
551
+ eventOptions.fail(error);
548
552
  }
549
- })();
553
+ };
554
+ this.watchWithCallback(sql, parameters, handler, options);
555
+ options?.signal?.addEventListener('abort', () => {
556
+ eventOptions.stop();
557
+ });
550
558
  });
551
559
  }
552
560
  async resolveTables(sql, parameters, options) {
@@ -0,0 +1,2 @@
1
+ import { AbstractPowerSyncDatabase, SQLWatchOptions } from './AbstractPowerSyncDatabase.js';
2
+ export declare function runOnSchemaChange(callback: (signal: AbortSignal) => void, db: AbstractPowerSyncDatabase, options?: SQLWatchOptions): void;
@@ -0,0 +1,23 @@
1
+ export function runOnSchemaChange(callback, db, options) {
2
+ const triggerWatchedQuery = () => {
3
+ const abortController = new AbortController();
4
+ let disposeSchemaListener = null;
5
+ const stopWatching = () => {
6
+ abortController.abort('Abort triggered');
7
+ disposeSchemaListener?.();
8
+ disposeSchemaListener = null;
9
+ // Stop listening to upstream abort for this watch
10
+ options?.signal?.removeEventListener('abort', stopWatching);
11
+ };
12
+ options?.signal?.addEventListener('abort', stopWatching);
13
+ disposeSchemaListener = db.registerListener({
14
+ schemaChanged: async () => {
15
+ stopWatching();
16
+ // Re trigger the watched query (recursively), setTimeout ensures that we don't modify the list of listeners while iterating through them
17
+ setTimeout(() => triggerWatchedQuery(), 0);
18
+ }
19
+ });
20
+ callback(abortController.signal);
21
+ };
22
+ triggerWatchedQuery();
23
+ }
@@ -90,6 +90,10 @@ export interface DBAdapter extends BaseObserverInterface<DBAdapterListener>, DBG
90
90
  readTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
91
91
  writeLock: <T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) => Promise<T>;
92
92
  writeTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
93
+ /**
94
+ * This method refreshes the schema information across all connections. This is for advanced use cases, and should generally not be needed.
95
+ */
96
+ refreshSchema: () => Promise<void>;
93
97
  }
94
98
  export declare function isBatchedUpdateNotification(update: BatchedUpdateNotification | UpdateNotification): update is BatchedUpdateNotification;
95
99
  export declare function extractTableUpdates(update: BatchedUpdateNotification | UpdateNotification): string[];
package/lib/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export * from './client/SQLOpenFactory.js';
4
4
  export * from './client/connection/PowerSyncBackendConnector.js';
5
5
  export * from './client/connection/PowerSyncCredentials.js';
6
6
  export * from './client/sync/bucket/BucketStorageAdapter.js';
7
+ export { runOnSchemaChange } from './client/runOnSchemaChange.js';
7
8
  export { UpdateType, CrudEntry, OpId } from './client/sync/bucket/CrudEntry.js';
8
9
  export * from './client/sync/bucket/SqliteBucketStorage.js';
9
10
  export * from './client/sync/bucket/CrudBatch.js';
package/lib/index.js CHANGED
@@ -4,6 +4,7 @@ export * from './client/SQLOpenFactory.js';
4
4
  export * from './client/connection/PowerSyncBackendConnector.js';
5
5
  export * from './client/connection/PowerSyncCredentials.js';
6
6
  export * from './client/sync/bucket/BucketStorageAdapter.js';
7
+ export { runOnSchemaChange } from './client/runOnSchemaChange.js';
7
8
  export { UpdateType, CrudEntry } from './client/sync/bucket/CrudEntry.js';
8
9
  export * from './client/sync/bucket/SqliteBucketStorage.js';
9
10
  export * from './client/sync/bucket/CrudBatch.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/common",
3
- "version": "1.20.1",
3
+ "version": "1.21.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"