@powersync/common 1.9.0 → 1.10.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.
@@ -50,7 +50,7 @@ export interface WatchHandler {
50
50
  onError?: (error: Error) => void;
51
51
  }
52
52
  export interface WatchOnChangeHandler {
53
- onChange: (event: WatchOnChangeEvent) => void;
53
+ onChange: (event: WatchOnChangeEvent) => Promise<void> | void;
54
54
  onError?: (error: Error) => void;
55
55
  }
56
56
  export interface PowerSyncDBListener extends StreamingSyncImplementationListener {
@@ -13,6 +13,7 @@ import { CrudBatch } from './sync/bucket/CrudBatch';
13
13
  import { CrudEntry } from './sync/bucket/CrudEntry';
14
14
  import { CrudTransaction } from './sync/bucket/CrudTransaction';
15
15
  import { DEFAULT_CRUD_UPLOAD_THROTTLE_MS } from './sync/stream/AbstractStreamingSyncImplementation';
16
+ import { ControlledExecutor } from '../utils/ControlledExecutor';
16
17
  const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
17
18
  const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
18
19
  clearLocal: true
@@ -565,10 +566,13 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
565
566
  const watchedTables = new Set(resolvedOptions.tables ?? []);
566
567
  const changedTables = new Set();
567
568
  const throttleMs = resolvedOptions.throttleMs ?? DEFAULT_WATCH_THROTTLE_MS;
569
+ const executor = new ControlledExecutor(async (e) => {
570
+ await onChange(e);
571
+ });
568
572
  const flushTableUpdates = throttle(() => this.handleTableChanges(changedTables, watchedTables, (intersection) => {
569
573
  if (resolvedOptions?.signal?.aborted)
570
574
  return;
571
- onChange({ changedTables: intersection });
575
+ executor.schedule({ changedTables: intersection });
572
576
  }), throttleMs, { leading: false, trailing: true });
573
577
  const dispose = this.database.registerListener({
574
578
  tablesUpdated: async (update) => {
@@ -583,6 +587,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
583
587
  }
584
588
  });
585
589
  resolvedOptions.signal?.addEventListener('abort', () => {
590
+ executor.dispose();
586
591
  dispose();
587
592
  });
588
593
  return () => dispose();
@@ -0,0 +1,25 @@
1
+ export interface ControlledExecutorOptions {
2
+ /**
3
+ * If throttling is enabled, it ensures only one task runs at a time,
4
+ * and only one additional task can be scheduled to run after the current task completes. The pending task will be overwritten by the latest task.
5
+ * Enabled by default.
6
+ */
7
+ throttleEnabled?: boolean;
8
+ }
9
+ export declare class ControlledExecutor<T> {
10
+ private task;
11
+ /**
12
+ * Represents the currently running task, which could be a Promise or undefined if no task is running.
13
+ */
14
+ private runningTask;
15
+ private pendingTaskParam;
16
+ /**
17
+ * Flag to determine if throttling is enabled, which controls whether tasks are queued or run immediately.
18
+ */
19
+ private isThrottling;
20
+ private closed;
21
+ constructor(task: (param: T) => Promise<void> | void, options?: ControlledExecutorOptions);
22
+ schedule(param: T): void;
23
+ dispose(): void;
24
+ private execute;
25
+ }
@@ -0,0 +1,50 @@
1
+ export class ControlledExecutor {
2
+ task;
3
+ /**
4
+ * Represents the currently running task, which could be a Promise or undefined if no task is running.
5
+ */
6
+ runningTask;
7
+ pendingTaskParam;
8
+ /**
9
+ * Flag to determine if throttling is enabled, which controls whether tasks are queued or run immediately.
10
+ */
11
+ isThrottling;
12
+ closed;
13
+ constructor(task, options) {
14
+ this.task = task;
15
+ const { throttleEnabled = true } = options ?? {};
16
+ this.isThrottling = throttleEnabled;
17
+ this.closed = false;
18
+ }
19
+ schedule(param) {
20
+ if (this.closed) {
21
+ return;
22
+ }
23
+ if (!this.isThrottling) {
24
+ this.task(param);
25
+ return;
26
+ }
27
+ if (this.runningTask) {
28
+ // set or replace the pending task param with latest one
29
+ this.pendingTaskParam = param;
30
+ return;
31
+ }
32
+ this.execute(param);
33
+ }
34
+ dispose() {
35
+ this.closed = true;
36
+ if (this.runningTask) {
37
+ this.runningTask = undefined;
38
+ }
39
+ }
40
+ async execute(param) {
41
+ this.runningTask = this.task(param);
42
+ await this.runningTask;
43
+ this.runningTask = undefined;
44
+ if (this.pendingTaskParam) {
45
+ const pendingParam = this.pendingTaskParam;
46
+ this.pendingTaskParam = undefined;
47
+ this.execute(pendingParam);
48
+ }
49
+ }
50
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/common",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"