@powersync/common 1.8.1 → 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.
- package/lib/client/AbstractPowerSyncDatabase.d.ts +1 -1
- package/lib/client/AbstractPowerSyncDatabase.js +6 -1
- package/lib/client/sync/stream/AbstractRemote.d.ts +6 -0
- package/lib/client/sync/stream/AbstractRemote.js +5 -5
- package/lib/utils/ControlledExecutor.d.ts +25 -0
- package/lib/utils/ControlledExecutor.js +50 -0
- package/package.json +3 -3
|
@@ -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
|
-
|
|
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();
|
|
@@ -3,6 +3,8 @@ import { fetch } from 'cross-fetch';
|
|
|
3
3
|
import { PowerSyncCredentials } from '../../connection/PowerSyncCredentials';
|
|
4
4
|
import { StreamingSyncLine, StreamingSyncRequest } from './streaming-sync-types';
|
|
5
5
|
import { DataStream } from '../../../utils/DataStream';
|
|
6
|
+
import type { BSON } from 'bson';
|
|
7
|
+
export type BSONImplementation = typeof BSON;
|
|
6
8
|
export type RemoteConnector = {
|
|
7
9
|
fetchCredentials: () => Promise<PowerSyncCredentials | null>;
|
|
8
10
|
};
|
|
@@ -47,6 +49,10 @@ export declare abstract class AbstractRemote {
|
|
|
47
49
|
post(path: string, data: any, headers?: Record<string, string>): Promise<any>;
|
|
48
50
|
get(path: string, headers?: Record<string, string>): Promise<any>;
|
|
49
51
|
postStreaming(path: string, data: any, headers?: Record<string, string>, signal?: AbortSignal): Promise<any>;
|
|
52
|
+
/**
|
|
53
|
+
* Provides a BSON implementation. The import nature of this varies depending on the platform
|
|
54
|
+
*/
|
|
55
|
+
abstract getBSON(): Promise<BSONImplementation>;
|
|
50
56
|
/**
|
|
51
57
|
* Connects to the sync/stream websocket endpoint
|
|
52
58
|
*/
|
|
@@ -4,7 +4,6 @@ import { DataStream } from '../../../utils/DataStream';
|
|
|
4
4
|
import ndjsonStream from 'can-ndjson-stream';
|
|
5
5
|
import { RSocketConnector } from 'rsocket-core';
|
|
6
6
|
import { WebsocketClientTransport } from 'rsocket-websocket-client';
|
|
7
|
-
import { serialize, deserialize } from 'bson';
|
|
8
7
|
import { AbortOperation } from '../../../utils/AbortOperation';
|
|
9
8
|
import { Buffer } from 'buffer';
|
|
10
9
|
// Refresh at least 30 sec before it expires
|
|
@@ -120,6 +119,7 @@ export class AbstractRemote {
|
|
|
120
119
|
async socketStream(options) {
|
|
121
120
|
const { path } = options;
|
|
122
121
|
const request = await this.buildRequest(path);
|
|
122
|
+
const bson = await this.getBSON();
|
|
123
123
|
const connector = new RSocketConnector({
|
|
124
124
|
transport: new WebsocketClientTransport({
|
|
125
125
|
url: this.options.socketUrlTransformer(request.url)
|
|
@@ -131,7 +131,7 @@ export class AbstractRemote {
|
|
|
131
131
|
metadataMimeType: 'application/bson',
|
|
132
132
|
payload: {
|
|
133
133
|
data: null,
|
|
134
|
-
metadata: Buffer.from(serialize({
|
|
134
|
+
metadata: Buffer.from(bson.serialize({
|
|
135
135
|
token: request.headers.Authorization
|
|
136
136
|
}))
|
|
137
137
|
}
|
|
@@ -167,8 +167,8 @@ export class AbstractRemote {
|
|
|
167
167
|
const socket = await new Promise((resolve, reject) => {
|
|
168
168
|
let connectionEstablished = false;
|
|
169
169
|
const res = rsocket.requestStream({
|
|
170
|
-
data: Buffer.from(serialize(options.data)),
|
|
171
|
-
metadata: Buffer.from(serialize({
|
|
170
|
+
data: Buffer.from(bson.serialize(options.data)),
|
|
171
|
+
metadata: Buffer.from(bson.serialize({
|
|
172
172
|
path
|
|
173
173
|
}))
|
|
174
174
|
}, SYNC_QUEUE_REQUEST_N, // The initial N amount
|
|
@@ -199,7 +199,7 @@ export class AbstractRemote {
|
|
|
199
199
|
if (!data) {
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
202
|
-
const deserializedData = deserialize(data);
|
|
202
|
+
const deserializedData = bson.deserialize(data);
|
|
203
203
|
stream.enqueueData(deserializedData);
|
|
204
204
|
},
|
|
205
205
|
onComplete: () => {
|
|
@@ -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.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
"homepage": "https://docs.powersync.com/resources/api-reference",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"async-mutex": "^0.4.0",
|
|
26
|
-
"bson": "^6.6.0",
|
|
27
26
|
"buffer": "^6.0.3",
|
|
28
27
|
"can-ndjson-stream": "^1.0.2",
|
|
29
28
|
"cross-fetch": "^4.0.0",
|
|
@@ -38,7 +37,8 @@
|
|
|
38
37
|
"@types/node": "^20.5.9",
|
|
39
38
|
"@types/uuid": "^9.0.1",
|
|
40
39
|
"typescript": "^5.1.3",
|
|
41
|
-
"vitest": "^1.5.2"
|
|
40
|
+
"vitest": "^1.5.2",
|
|
41
|
+
"bson": "^6.6.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc -b",
|