@powersync/common 0.0.0-dev-20250922105207 → 0.0.0-dev-20250922105508
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/dist/bundle.cjs +4 -4
- package/dist/bundle.mjs +5 -5
- package/dist/index.d.cts +1047 -838
- package/lib/client/AbstractPowerSyncDatabase.d.ts +16 -4
- package/lib/client/AbstractPowerSyncDatabase.js +36 -26
- package/lib/client/ConnectionManager.d.ts +26 -2
- package/lib/client/ConnectionManager.js +114 -2
- package/lib/client/SQLOpenFactory.d.ts +0 -2
- package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +9 -1
- package/lib/client/sync/bucket/BucketStorageAdapter.js +1 -0
- package/lib/client/sync/stream/AbstractRemote.js +3 -0
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +24 -3
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +54 -39
- package/lib/client/sync/stream/core-instruction.d.ts +20 -1
- package/lib/client/sync/stream/core-instruction.js +26 -1
- package/lib/client/sync/sync-streams.d.ts +98 -0
- package/lib/client/sync/sync-streams.js +1 -0
- package/lib/client/triggers/TriggerManager.d.ts +1 -1
- package/lib/client/watched/WatchedQuery.d.ts +2 -0
- package/lib/client/watched/WatchedQuery.js +1 -0
- package/lib/client/watched/processors/AbstractQueryProcessor.d.ts +2 -1
- package/lib/client/watched/processors/AbstractQueryProcessor.js +30 -15
- package/lib/client/watched/processors/DifferentialQueryProcessor.js +7 -1
- package/lib/client/watched/processors/OnChangeQueryProcessor.js +7 -1
- package/lib/db/crud/SyncStatus.d.ts +37 -1
- package/lib/db/crud/SyncStatus.js +61 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/package.json +1 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import Logger from 'js-logger';
|
|
2
|
-
import { FULL_SYNC_PRIORITY } from '../../../db/crud/SyncProgress.js';
|
|
3
2
|
import { SyncStatus } from '../../../db/crud/SyncStatus.js';
|
|
4
3
|
import { AbortOperation } from '../../../utils/AbortOperation.js';
|
|
5
4
|
import { BaseObserver } from '../../../utils/BaseObserver.js';
|
|
@@ -7,6 +6,7 @@ import { throttleLeadingTrailing } from '../../../utils/async.js';
|
|
|
7
6
|
import { PowerSyncControlCommand } from '../bucket/BucketStorageAdapter.js';
|
|
8
7
|
import { SyncDataBucket } from '../bucket/SyncDataBucket.js';
|
|
9
8
|
import { FetchStrategy } from './AbstractRemote.js';
|
|
9
|
+
import { coreStatusToJs } from './core-instruction.js';
|
|
10
10
|
import { isStreamingKeepalive, isStreamingSyncCheckpoint, isStreamingSyncCheckpointComplete, isStreamingSyncCheckpointDiff, isStreamingSyncCheckpointPartiallyComplete, isStreamingSyncData } from './streaming-sync-types.js';
|
|
11
11
|
export var LockType;
|
|
12
12
|
(function (LockType) {
|
|
@@ -72,7 +72,8 @@ export const DEFAULT_STREAM_CONNECTION_OPTIONS = {
|
|
|
72
72
|
clientImplementation: DEFAULT_SYNC_CLIENT_IMPLEMENTATION,
|
|
73
73
|
fetchStrategy: FetchStrategy.Buffered,
|
|
74
74
|
params: {},
|
|
75
|
-
serializedSchema: undefined
|
|
75
|
+
serializedSchema: undefined,
|
|
76
|
+
includeDefaultStreams: true
|
|
76
77
|
};
|
|
77
78
|
// The priority we assume when we receive checkpoint lines where no priority is set.
|
|
78
79
|
// This is the default priority used by the sync service, but can be set to an arbitrary
|
|
@@ -89,13 +90,16 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
|
89
90
|
crudUpdateListener;
|
|
90
91
|
streamingSyncPromise;
|
|
91
92
|
logger;
|
|
93
|
+
activeStreams;
|
|
92
94
|
isUploadingCrud = false;
|
|
93
95
|
notifyCompletedUploads;
|
|
96
|
+
handleActiveStreamsChange;
|
|
94
97
|
syncStatus;
|
|
95
98
|
triggerCrudUpload;
|
|
96
99
|
constructor(options) {
|
|
97
100
|
super();
|
|
98
|
-
this.options =
|
|
101
|
+
this.options = options;
|
|
102
|
+
this.activeStreams = options.subscriptions;
|
|
99
103
|
this.logger = options.logger ?? Logger.get('PowerSyncStream');
|
|
100
104
|
this.syncStatus = new SyncStatus({
|
|
101
105
|
connected: false,
|
|
@@ -343,11 +347,12 @@ The next upload iteration will be delayed.`);
|
|
|
343
347
|
while (true) {
|
|
344
348
|
this.updateSyncStatus({ connecting: true });
|
|
345
349
|
let shouldDelayRetry = true;
|
|
350
|
+
let result = null;
|
|
346
351
|
try {
|
|
347
352
|
if (signal?.aborted) {
|
|
348
353
|
break;
|
|
349
354
|
}
|
|
350
|
-
await this.streamingSyncIteration(nestedAbortController.signal, options);
|
|
355
|
+
result = await this.streamingSyncIteration(nestedAbortController.signal, options);
|
|
351
356
|
// Continue immediately, streamingSyncIteration will wait before completing if necessary.
|
|
352
357
|
}
|
|
353
358
|
catch (ex) {
|
|
@@ -380,13 +385,15 @@ The next upload iteration will be delayed.`);
|
|
|
380
385
|
nestedAbortController.abort(new AbortOperation('Closing sync stream network requests before retry.'));
|
|
381
386
|
nestedAbortController = new AbortController();
|
|
382
387
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
388
|
+
if (result?.immediateRestart != true) {
|
|
389
|
+
this.updateSyncStatus({
|
|
390
|
+
connected: false,
|
|
391
|
+
connecting: true // May be unnecessary
|
|
392
|
+
});
|
|
393
|
+
// On error, wait a little before retrying
|
|
394
|
+
if (shouldDelayRetry) {
|
|
395
|
+
await this.delayRetry(nestedAbortController.signal);
|
|
396
|
+
}
|
|
390
397
|
}
|
|
391
398
|
}
|
|
392
399
|
}
|
|
@@ -430,8 +437,8 @@ The next upload iteration will be delayed.`);
|
|
|
430
437
|
return hasMigrated;
|
|
431
438
|
}
|
|
432
439
|
}
|
|
433
|
-
|
|
434
|
-
|
|
440
|
+
streamingSyncIteration(signal, options) {
|
|
441
|
+
return this.obtainLock({
|
|
435
442
|
type: LockType.SYNC,
|
|
436
443
|
signal,
|
|
437
444
|
callback: async () => {
|
|
@@ -439,12 +446,15 @@ The next upload iteration will be delayed.`);
|
|
|
439
446
|
...DEFAULT_STREAM_CONNECTION_OPTIONS,
|
|
440
447
|
...(options ?? {})
|
|
441
448
|
};
|
|
442
|
-
|
|
449
|
+
const clientImplementation = resolvedOptions.clientImplementation;
|
|
450
|
+
this.updateSyncStatus({ clientImplementation });
|
|
451
|
+
if (clientImplementation == SyncClientImplementation.JAVASCRIPT) {
|
|
443
452
|
await this.legacyStreamingSyncIteration(signal, resolvedOptions);
|
|
453
|
+
return null;
|
|
444
454
|
}
|
|
445
455
|
else {
|
|
446
456
|
await this.requireKeyFormat(true);
|
|
447
|
-
await this.rustSyncIteration(signal, resolvedOptions);
|
|
457
|
+
return await this.rustSyncIteration(signal, resolvedOptions);
|
|
448
458
|
}
|
|
449
459
|
}
|
|
450
460
|
});
|
|
@@ -693,6 +703,10 @@ The next upload iteration will be delayed.`);
|
|
|
693
703
|
const remote = this.options.remote;
|
|
694
704
|
let receivingLines = null;
|
|
695
705
|
let hadSyncLine = false;
|
|
706
|
+
let hideDisconnectOnRestart = false;
|
|
707
|
+
if (signal.aborted) {
|
|
708
|
+
throw new AbortOperation('Connection request has been aborted');
|
|
709
|
+
}
|
|
696
710
|
const abortController = new AbortController();
|
|
697
711
|
signal.addEventListener('abort', () => abortController.abort());
|
|
698
712
|
// Pending sync lines received from the service, as well as local events that trigger a powersync_control
|
|
@@ -766,6 +780,8 @@ The next upload iteration will be delayed.`);
|
|
|
766
780
|
}
|
|
767
781
|
async function control(op, payload) {
|
|
768
782
|
const rawResponse = await adapter.control(op, payload ?? null);
|
|
783
|
+
const logger = syncImplementation.logger;
|
|
784
|
+
logger.trace('powersync_control', op, payload == null || typeof payload == 'string' ? payload : '<bytes>', rawResponse);
|
|
769
785
|
await handleInstructions(JSON.parse(rawResponse));
|
|
770
786
|
}
|
|
771
787
|
async function handleInstruction(instruction) {
|
|
@@ -783,27 +799,7 @@ The next upload iteration will be delayed.`);
|
|
|
783
799
|
}
|
|
784
800
|
}
|
|
785
801
|
else if ('UpdateSyncStatus' in instruction) {
|
|
786
|
-
|
|
787
|
-
return {
|
|
788
|
-
priority: status.priority,
|
|
789
|
-
hasSynced: status.has_synced ?? undefined,
|
|
790
|
-
lastSyncedAt: status?.last_synced_at != null ? new Date(status.last_synced_at * 1000) : undefined
|
|
791
|
-
};
|
|
792
|
-
}
|
|
793
|
-
const info = instruction.UpdateSyncStatus.status;
|
|
794
|
-
const coreCompleteSync = info.priority_status.find((s) => s.priority == FULL_SYNC_PRIORITY);
|
|
795
|
-
const completeSync = coreCompleteSync != null ? coreStatusToJs(coreCompleteSync) : null;
|
|
796
|
-
syncImplementation.updateSyncStatus({
|
|
797
|
-
connected: info.connected,
|
|
798
|
-
connecting: info.connecting,
|
|
799
|
-
dataFlow: {
|
|
800
|
-
downloading: info.downloading != null,
|
|
801
|
-
downloadProgress: info.downloading?.buckets
|
|
802
|
-
},
|
|
803
|
-
lastSyncedAt: completeSync?.lastSyncedAt,
|
|
804
|
-
hasSynced: completeSync?.hasSynced,
|
|
805
|
-
priorityStatusEntries: info.priority_status.map(coreStatusToJs)
|
|
806
|
-
});
|
|
802
|
+
syncImplementation.updateSyncStatus(coreStatusToJs(instruction.UpdateSyncStatus.status));
|
|
807
803
|
}
|
|
808
804
|
else if ('EstablishSyncStream' in instruction) {
|
|
809
805
|
if (receivingLines != null) {
|
|
@@ -828,6 +824,7 @@ The next upload iteration will be delayed.`);
|
|
|
828
824
|
}
|
|
829
825
|
else if ('CloseSyncStream' in instruction) {
|
|
830
826
|
abortController.abort();
|
|
827
|
+
hideDisconnectOnRestart = instruction.CloseSyncStream.hide_disconnect;
|
|
831
828
|
}
|
|
832
829
|
else if ('FlushFileSystem' in instruction) {
|
|
833
830
|
// Not necessary on JS platforms.
|
|
@@ -846,7 +843,11 @@ The next upload iteration will be delayed.`);
|
|
|
846
843
|
}
|
|
847
844
|
}
|
|
848
845
|
try {
|
|
849
|
-
const options = {
|
|
846
|
+
const options = {
|
|
847
|
+
parameters: resolvedOptions.params,
|
|
848
|
+
active_streams: this.activeStreams,
|
|
849
|
+
include_defaults: resolvedOptions.includeDefaultStreams
|
|
850
|
+
};
|
|
850
851
|
if (resolvedOptions.serializedSchema) {
|
|
851
852
|
options.schema = resolvedOptions.serializedSchema;
|
|
852
853
|
}
|
|
@@ -856,12 +857,21 @@ The next upload iteration will be delayed.`);
|
|
|
856
857
|
controlInvocations.enqueueData({ command: PowerSyncControlCommand.NOTIFY_CRUD_UPLOAD_COMPLETED });
|
|
857
858
|
}
|
|
858
859
|
};
|
|
860
|
+
this.handleActiveStreamsChange = () => {
|
|
861
|
+
if (controlInvocations && !controlInvocations?.closed) {
|
|
862
|
+
controlInvocations.enqueueData({
|
|
863
|
+
command: PowerSyncControlCommand.UPDATE_SUBSCRIPTIONS,
|
|
864
|
+
payload: JSON.stringify(this.activeStreams)
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
};
|
|
859
868
|
await receivingLines;
|
|
860
869
|
}
|
|
861
870
|
finally {
|
|
862
|
-
this.notifyCompletedUploads = undefined;
|
|
871
|
+
this.notifyCompletedUploads = this.handleActiveStreamsChange = undefined;
|
|
863
872
|
await stop();
|
|
864
873
|
}
|
|
874
|
+
return { immediateRestart: hideDisconnectOnRestart };
|
|
865
875
|
}
|
|
866
876
|
async updateSyncStatusForStartingCheckpoint(checkpoint) {
|
|
867
877
|
const localProgress = await this.options.adapter.getBucketOperationProgress();
|
|
@@ -934,7 +944,8 @@ The next upload iteration will be delayed.`);
|
|
|
934
944
|
...this.syncStatus.dataFlowStatus,
|
|
935
945
|
...options.dataFlow
|
|
936
946
|
},
|
|
937
|
-
priorityStatusEntries: options.priorityStatusEntries ?? this.syncStatus.priorityStatusEntries
|
|
947
|
+
priorityStatusEntries: options.priorityStatusEntries ?? this.syncStatus.priorityStatusEntries,
|
|
948
|
+
clientImplementation: options.clientImplementation ?? this.syncStatus.clientImplementation
|
|
938
949
|
});
|
|
939
950
|
if (!this.syncStatus.isEqual(updatedStatus)) {
|
|
940
951
|
this.syncStatus = updatedStatus;
|
|
@@ -965,4 +976,8 @@ The next upload iteration will be delayed.`);
|
|
|
965
976
|
timeoutId = setTimeout(endDelay, retryDelayMs);
|
|
966
977
|
});
|
|
967
978
|
}
|
|
979
|
+
updateSubscriptions(subscriptions) {
|
|
980
|
+
this.activeStreams = subscriptions;
|
|
981
|
+
this.handleActiveStreamsChange?.();
|
|
982
|
+
}
|
|
968
983
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StreamingSyncRequest } from './streaming-sync-types.js';
|
|
2
|
+
import * as sync_status from '../../../db/crud/SyncStatus.js';
|
|
2
3
|
/**
|
|
3
4
|
* An internal instruction emitted by the sync client in the core extension in response to the JS
|
|
4
5
|
* SDK passing sync data into the extension.
|
|
@@ -12,7 +13,9 @@ export type Instruction = {
|
|
|
12
13
|
} | {
|
|
13
14
|
FetchCredentials: FetchCredentials;
|
|
14
15
|
} | {
|
|
15
|
-
CloseSyncStream:
|
|
16
|
+
CloseSyncStream: {
|
|
17
|
+
hide_disconnect: boolean;
|
|
18
|
+
};
|
|
16
19
|
} | {
|
|
17
20
|
FlushFileSystem: any;
|
|
18
21
|
} | {
|
|
@@ -33,6 +36,21 @@ export interface CoreSyncStatus {
|
|
|
33
36
|
connecting: boolean;
|
|
34
37
|
priority_status: SyncPriorityStatus[];
|
|
35
38
|
downloading: DownloadProgress | null;
|
|
39
|
+
streams: CoreStreamSubscription[];
|
|
40
|
+
}
|
|
41
|
+
export interface CoreStreamSubscription {
|
|
42
|
+
progress: {
|
|
43
|
+
total: number;
|
|
44
|
+
downloaded: number;
|
|
45
|
+
};
|
|
46
|
+
name: string;
|
|
47
|
+
parameters: any;
|
|
48
|
+
priority: number | null;
|
|
49
|
+
active: boolean;
|
|
50
|
+
is_default: boolean;
|
|
51
|
+
has_explicit_subscription: boolean;
|
|
52
|
+
expires_at: number | null;
|
|
53
|
+
last_synced_at: number | null;
|
|
36
54
|
}
|
|
37
55
|
export interface SyncPriorityStatus {
|
|
38
56
|
priority: number;
|
|
@@ -51,3 +69,4 @@ export interface BucketProgress {
|
|
|
51
69
|
export interface FetchCredentials {
|
|
52
70
|
did_expire: boolean;
|
|
53
71
|
}
|
|
72
|
+
export declare function coreStatusToJs(status: CoreSyncStatus): sync_status.SyncStatusOptions;
|
|
@@ -1 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
import { FULL_SYNC_PRIORITY } from '../../../db/crud/SyncProgress.js';
|
|
2
|
+
function priorityToJs(status) {
|
|
3
|
+
return {
|
|
4
|
+
priority: status.priority,
|
|
5
|
+
hasSynced: status.has_synced ?? undefined,
|
|
6
|
+
lastSyncedAt: status.last_synced_at != null ? new Date(status.last_synced_at * 1000) : undefined
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function coreStatusToJs(status) {
|
|
10
|
+
const coreCompleteSync = status.priority_status.find((s) => s.priority == FULL_SYNC_PRIORITY);
|
|
11
|
+
const completeSync = coreCompleteSync != null ? priorityToJs(coreCompleteSync) : null;
|
|
12
|
+
return {
|
|
13
|
+
connected: status.connected,
|
|
14
|
+
connecting: status.connecting,
|
|
15
|
+
dataFlow: {
|
|
16
|
+
// We expose downloading as a boolean field, the core extension reports download information as a nullable
|
|
17
|
+
// download status. When that status is non-null, a download is in progress.
|
|
18
|
+
downloading: status.downloading != null,
|
|
19
|
+
downloadProgress: status.downloading?.buckets,
|
|
20
|
+
internalStreamSubscriptions: status.streams
|
|
21
|
+
},
|
|
22
|
+
lastSyncedAt: completeSync?.lastSyncedAt,
|
|
23
|
+
hasSynced: completeSync?.hasSynced,
|
|
24
|
+
priorityStatusEntries: status.priority_status.map(priorityToJs)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A description of a sync stream, consisting of its {@link name} and the {@link parameters} used when subscribing.
|
|
3
|
+
*/
|
|
4
|
+
export interface SyncStreamDescription {
|
|
5
|
+
/**
|
|
6
|
+
* The name of the stream as it appears in the stream definition for the PowerSync service.
|
|
7
|
+
*/
|
|
8
|
+
name: string;
|
|
9
|
+
/**
|
|
10
|
+
* The parameters used to subscribe to the stream, if any.
|
|
11
|
+
*
|
|
12
|
+
* The same stream can be subscribed to multiple times with different parameters.
|
|
13
|
+
*/
|
|
14
|
+
parameters: Record<string, any> | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Information about a subscribed sync stream.
|
|
18
|
+
*
|
|
19
|
+
* This includes the {@link SyncStreamDescription}, along with information about the current sync status.
|
|
20
|
+
*/
|
|
21
|
+
export interface SyncSubscriptionDescription extends SyncStreamDescription {
|
|
22
|
+
active: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Whether this stream subscription is included by default, regardless of whether the stream has explicitly been
|
|
25
|
+
* subscribed to or not.
|
|
26
|
+
*
|
|
27
|
+
* It's possible for both {@link isDefault} and {@link hasExplicitSubscription} to be true at the same time - this
|
|
28
|
+
* happens when a default stream was subscribed explicitly.
|
|
29
|
+
*/
|
|
30
|
+
isDefault: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Whether this stream has been subscribed to explicitly.
|
|
33
|
+
*
|
|
34
|
+
* It's possible for both {@link isDefault} and {@link hasExplicitSubscription} to be true at the same time - this
|
|
35
|
+
* happens when a default stream was subscribed explicitly.
|
|
36
|
+
*/
|
|
37
|
+
hasExplicitSubscription: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* For sync streams that have a time-to-live, the current time at which the stream would expire if not subscribed to
|
|
40
|
+
* again.
|
|
41
|
+
*/
|
|
42
|
+
expiresAt: Date | null;
|
|
43
|
+
/**
|
|
44
|
+
* Whether this stream subscription has been synced at least once.
|
|
45
|
+
*/
|
|
46
|
+
hasSynced: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* If {@link hasSynced} is true, the last time data from this stream has been synced.
|
|
49
|
+
*/
|
|
50
|
+
lastSyncedAt: Date | null;
|
|
51
|
+
}
|
|
52
|
+
export interface SyncStreamSubscribeOptions {
|
|
53
|
+
/**
|
|
54
|
+
* A "time to live" for this stream subscription, in seconds.
|
|
55
|
+
*
|
|
56
|
+
* The TTL control when a stream gets evicted after not having an active {@link SyncStreamSubscription} object
|
|
57
|
+
* attached to it.
|
|
58
|
+
*/
|
|
59
|
+
ttl?: number;
|
|
60
|
+
/**
|
|
61
|
+
* A priority to assign to this subscription. This overrides the default priority that may have been set on streams.
|
|
62
|
+
*
|
|
63
|
+
* For details on priorities, see [priotized sync](https://docs.powersync.com/usage/use-case-examples/prioritized-sync).
|
|
64
|
+
*/
|
|
65
|
+
priority?: 0 | 1 | 2 | 3;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A handle to a {@link SyncStreamDescription} that allows subscribing to the stream.
|
|
69
|
+
*
|
|
70
|
+
* To obtain an instance of {@link SyncStream}, call {@link AbstractPowerSyncDatabase.syncStream}.
|
|
71
|
+
*/
|
|
72
|
+
export interface SyncStream extends SyncStreamDescription {
|
|
73
|
+
/**
|
|
74
|
+
* Adds a subscription to this stream, requesting it to be included when connecting to the sync service.
|
|
75
|
+
*
|
|
76
|
+
* You should keep a reference to the returned {@link SyncStreamSubscription} object along as you need data for that
|
|
77
|
+
* stream. As soon as {@link SyncStreamSubscription.unsubscribe} is called for all subscriptions on this stream
|
|
78
|
+
* (including subscriptions created on other tabs), the {@link SyncStreamSubscribeOptions.ttl} starts ticking and will
|
|
79
|
+
* eventually evict the stream (unless {@link subscribe} is called again).
|
|
80
|
+
*/
|
|
81
|
+
subscribe(options?: SyncStreamSubscribeOptions): Promise<SyncStreamSubscription>;
|
|
82
|
+
/**
|
|
83
|
+
* Clears all subscriptions attached to this stream and resets the TTL for the stream.
|
|
84
|
+
*
|
|
85
|
+
* This is a potentially dangerous operations, as it interferes with other stream subscriptions.
|
|
86
|
+
*/
|
|
87
|
+
unsubscribeAll(): Promise<void>;
|
|
88
|
+
}
|
|
89
|
+
export interface SyncStreamSubscription extends SyncStreamDescription {
|
|
90
|
+
/**
|
|
91
|
+
* A promise that resolves once data from in this sync stream has been synced and applied.
|
|
92
|
+
*/
|
|
93
|
+
waitForFirstSync(abort?: AbortSignal): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Removes this stream subscription.
|
|
96
|
+
*/
|
|
97
|
+
unsubscribe(): void;
|
|
98
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -323,7 +323,7 @@ export interface TriggerManager {
|
|
|
323
323
|
* },
|
|
324
324
|
* onChange: async (context) => {
|
|
325
325
|
* // Fetches the todo records that were inserted during this diff
|
|
326
|
-
* const newTodos = await context.
|
|
326
|
+
* const newTodos = await context.withDiff<Database['todos']>(`
|
|
327
327
|
* SELECT
|
|
328
328
|
* todos.*
|
|
329
329
|
* FROM
|
|
@@ -65,12 +65,14 @@ export declare enum WatchedQueryListenerEvent {
|
|
|
65
65
|
ON_DATA = "onData",
|
|
66
66
|
ON_ERROR = "onError",
|
|
67
67
|
ON_STATE_CHANGE = "onStateChange",
|
|
68
|
+
SETTINGS_WILL_UPDATE = "settingsWillUpdate",
|
|
68
69
|
CLOSED = "closed"
|
|
69
70
|
}
|
|
70
71
|
export interface WatchedQueryListener<Data> extends BaseListener {
|
|
71
72
|
[WatchedQueryListenerEvent.ON_DATA]?: (data: Data) => void | Promise<void>;
|
|
72
73
|
[WatchedQueryListenerEvent.ON_ERROR]?: (error: Error) => void | Promise<void>;
|
|
73
74
|
[WatchedQueryListenerEvent.ON_STATE_CHANGE]?: (state: WatchedQueryState<Data>) => void | Promise<void>;
|
|
75
|
+
[WatchedQueryListenerEvent.SETTINGS_WILL_UPDATE]?: () => void;
|
|
74
76
|
[WatchedQueryListenerEvent.CLOSED]?: () => void | Promise<void>;
|
|
75
77
|
}
|
|
76
78
|
export declare const DEFAULT_WATCH_THROTTLE_MS = 30;
|
|
@@ -3,6 +3,7 @@ export var WatchedQueryListenerEvent;
|
|
|
3
3
|
WatchedQueryListenerEvent["ON_DATA"] = "onData";
|
|
4
4
|
WatchedQueryListenerEvent["ON_ERROR"] = "onError";
|
|
5
5
|
WatchedQueryListenerEvent["ON_STATE_CHANGE"] = "onStateChange";
|
|
6
|
+
WatchedQueryListenerEvent["SETTINGS_WILL_UPDATE"] = "settingsWillUpdate";
|
|
6
7
|
WatchedQueryListenerEvent["CLOSED"] = "closed";
|
|
7
8
|
})(WatchedQueryListenerEvent || (WatchedQueryListenerEvent = {}));
|
|
8
9
|
export const DEFAULT_WATCH_THROTTLE_MS = 30;
|
|
@@ -40,6 +40,7 @@ export declare abstract class AbstractQueryProcessor<Data = unknown[], Settings
|
|
|
40
40
|
constructor(options: AbstractQueryProcessorOptions<Data, Settings>);
|
|
41
41
|
protected constructInitialState(): WatchedQueryState<Data>;
|
|
42
42
|
protected get reportFetching(): boolean;
|
|
43
|
+
protected updateSettingsInternal(settings: Settings, signal: AbortSignal): Promise<void>;
|
|
43
44
|
/**
|
|
44
45
|
* Updates the underlying query.
|
|
45
46
|
*/
|
|
@@ -53,7 +54,7 @@ export declare abstract class AbstractQueryProcessor<Data = unknown[], Settings
|
|
|
53
54
|
/**
|
|
54
55
|
* Configures base DB listeners and links the query to listeners.
|
|
55
56
|
*/
|
|
56
|
-
protected init(): Promise<void>;
|
|
57
|
+
protected init(signal: AbortSignal): Promise<void>;
|
|
57
58
|
close(): Promise<void>;
|
|
58
59
|
/**
|
|
59
60
|
* Runs a callback and reports errors to the error listeners.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MetaBaseObserver } from '../../../utils/MetaBaseObserver.js';
|
|
2
|
+
import { WatchedQueryListenerEvent } from '../WatchedQuery.js';
|
|
2
3
|
/**
|
|
3
4
|
* Performs underlying watching and yields a stream of results.
|
|
4
5
|
* @internal
|
|
@@ -20,7 +21,7 @@ export class AbstractQueryProcessor extends MetaBaseObserver {
|
|
|
20
21
|
this._closed = false;
|
|
21
22
|
this.state = this.constructInitialState();
|
|
22
23
|
this.disposeListeners = null;
|
|
23
|
-
this.initialized = this.init();
|
|
24
|
+
this.initialized = this.init(this.abortController.signal);
|
|
24
25
|
}
|
|
25
26
|
constructInitialState() {
|
|
26
27
|
return {
|
|
@@ -34,25 +35,40 @@ export class AbstractQueryProcessor extends MetaBaseObserver {
|
|
|
34
35
|
get reportFetching() {
|
|
35
36
|
return this.options.watchOptions.reportFetching ?? true;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
async updateSettingsInternal(settings, signal) {
|
|
39
|
+
// This may have been aborted while awaiting or if multiple calls to `updateSettings` were made
|
|
40
|
+
if (this._closed || signal.aborted) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.options.watchOptions = settings;
|
|
44
|
+
this.iterateListeners((l) => l[WatchedQueryListenerEvent.SETTINGS_WILL_UPDATE]?.());
|
|
42
45
|
if (!this.state.isFetching && this.reportFetching) {
|
|
43
46
|
await this.updateState({
|
|
44
47
|
isFetching: true
|
|
45
48
|
});
|
|
46
49
|
}
|
|
47
|
-
this.options.watchOptions = settings;
|
|
48
|
-
this.abortController.abort();
|
|
49
|
-
this.abortController = new AbortController();
|
|
50
50
|
await this.runWithReporting(() => this.linkQuery({
|
|
51
|
-
abortSignal:
|
|
51
|
+
abortSignal: signal,
|
|
52
52
|
settings
|
|
53
53
|
}));
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Updates the underlying query.
|
|
57
|
+
*/
|
|
58
|
+
async updateSettings(settings) {
|
|
59
|
+
// Abort the previous request
|
|
60
|
+
this.abortController.abort();
|
|
61
|
+
// Keep track of this controller's abort status
|
|
62
|
+
const abortController = new AbortController();
|
|
63
|
+
// Allow this to be aborted externally
|
|
64
|
+
this.abortController = abortController;
|
|
65
|
+
await this.initialized;
|
|
66
|
+
return this.updateSettingsInternal(settings, abortController.signal);
|
|
67
|
+
}
|
|
55
68
|
async updateState(update) {
|
|
69
|
+
if (this._closed) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
56
72
|
if (typeof update.error !== 'undefined') {
|
|
57
73
|
await this.iterateAsyncListenersWithError(async (l) => l.onError?.(update.error));
|
|
58
74
|
// An error always stops for the current fetching state
|
|
@@ -68,7 +84,7 @@ export class AbstractQueryProcessor extends MetaBaseObserver {
|
|
|
68
84
|
/**
|
|
69
85
|
* Configures base DB listeners and links the query to listeners.
|
|
70
86
|
*/
|
|
71
|
-
async init() {
|
|
87
|
+
async init(signal) {
|
|
72
88
|
const { db } = this.options;
|
|
73
89
|
const disposeCloseListener = db.registerListener({
|
|
74
90
|
closing: async () => {
|
|
@@ -89,16 +105,15 @@ export class AbstractQueryProcessor extends MetaBaseObserver {
|
|
|
89
105
|
disposeSchemaListener();
|
|
90
106
|
};
|
|
91
107
|
// Initial setup
|
|
92
|
-
this.runWithReporting(async () => {
|
|
93
|
-
await this.
|
|
108
|
+
await this.runWithReporting(async () => {
|
|
109
|
+
await this.updateSettingsInternal(this.options.watchOptions, signal);
|
|
94
110
|
});
|
|
95
111
|
}
|
|
96
112
|
async close() {
|
|
97
|
-
|
|
113
|
+
this._closed = true;
|
|
98
114
|
this.abortController.abort();
|
|
99
115
|
this.disposeListeners?.();
|
|
100
116
|
this.disposeListeners = null;
|
|
101
|
-
this._closed = true;
|
|
102
117
|
this.iterateListeners((l) => l.closed?.());
|
|
103
118
|
this.listeners.clear();
|
|
104
119
|
}
|
|
@@ -113,7 +113,7 @@ export class DifferentialQueryProcessor extends AbstractQueryProcessor {
|
|
|
113
113
|
});
|
|
114
114
|
db.onChangeWithCallback({
|
|
115
115
|
onChange: async () => {
|
|
116
|
-
if (this.closed) {
|
|
116
|
+
if (this.closed || abortSignal.aborted) {
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
119
119
|
// This fires for each change of the relevant tables
|
|
@@ -130,6 +130,9 @@ export class DifferentialQueryProcessor extends AbstractQueryProcessor {
|
|
|
130
130
|
parameters: [...compiledQuery.parameters],
|
|
131
131
|
db: this.options.db
|
|
132
132
|
});
|
|
133
|
+
if (abortSignal.aborted) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
133
136
|
if (this.reportFetching) {
|
|
134
137
|
partialStateUpdate.isFetching = false;
|
|
135
138
|
}
|
|
@@ -145,6 +148,9 @@ export class DifferentialQueryProcessor extends AbstractQueryProcessor {
|
|
|
145
148
|
data: diff.all
|
|
146
149
|
});
|
|
147
150
|
}
|
|
151
|
+
if (this.state.error) {
|
|
152
|
+
partialStateUpdate.error = null;
|
|
153
|
+
}
|
|
148
154
|
if (Object.keys(partialStateUpdate).length > 0) {
|
|
149
155
|
await this.updateState(partialStateUpdate);
|
|
150
156
|
}
|
|
@@ -26,7 +26,7 @@ export class OnChangeQueryProcessor extends AbstractQueryProcessor {
|
|
|
26
26
|
});
|
|
27
27
|
db.onChangeWithCallback({
|
|
28
28
|
onChange: async () => {
|
|
29
|
-
if (this.closed) {
|
|
29
|
+
if (this.closed || abortSignal.aborted) {
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
// This fires for each change of the relevant tables
|
|
@@ -43,6 +43,9 @@ export class OnChangeQueryProcessor extends AbstractQueryProcessor {
|
|
|
43
43
|
parameters: [...compiledQuery.parameters],
|
|
44
44
|
db: this.options.db
|
|
45
45
|
});
|
|
46
|
+
if (abortSignal.aborted) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
46
49
|
if (this.reportFetching) {
|
|
47
50
|
partialStateUpdate.isFetching = false;
|
|
48
51
|
}
|
|
@@ -55,6 +58,9 @@ export class OnChangeQueryProcessor extends AbstractQueryProcessor {
|
|
|
55
58
|
data: result
|
|
56
59
|
});
|
|
57
60
|
}
|
|
61
|
+
if (this.state.error) {
|
|
62
|
+
partialStateUpdate.error = null;
|
|
63
|
+
}
|
|
58
64
|
if (Object.keys(partialStateUpdate).length > 0) {
|
|
59
65
|
await this.updateState(partialStateUpdate);
|
|
60
66
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CoreStreamSubscription } from '../../client/sync/stream/core-instruction.js';
|
|
2
|
+
import { SyncClientImplementation } from '../../client/sync/stream/AbstractStreamingSyncImplementation.js';
|
|
3
|
+
import { InternalProgressInformation, ProgressWithOperations, SyncProgress } from './SyncProgress.js';
|
|
4
|
+
import { SyncStreamDescription, SyncSubscriptionDescription } from '../../client/sync/sync-streams.js';
|
|
2
5
|
export type SyncDataFlowStatus = Partial<{
|
|
3
6
|
downloading: boolean;
|
|
4
7
|
uploading: boolean;
|
|
@@ -19,6 +22,7 @@ export type SyncDataFlowStatus = Partial<{
|
|
|
19
22
|
* Please use the {@link SyncStatus#downloadProgress} property to track sync progress.
|
|
20
23
|
*/
|
|
21
24
|
downloadProgress: InternalProgressInformation | null;
|
|
25
|
+
internalStreamSubscriptions: CoreStreamSubscription[] | null;
|
|
22
26
|
}>;
|
|
23
27
|
export interface SyncPriorityStatus {
|
|
24
28
|
priority: number;
|
|
@@ -32,10 +36,18 @@ export type SyncStatusOptions = {
|
|
|
32
36
|
lastSyncedAt?: Date;
|
|
33
37
|
hasSynced?: boolean;
|
|
34
38
|
priorityStatusEntries?: SyncPriorityStatus[];
|
|
39
|
+
clientImplementation?: SyncClientImplementation;
|
|
35
40
|
};
|
|
36
41
|
export declare class SyncStatus {
|
|
37
42
|
protected options: SyncStatusOptions;
|
|
38
43
|
constructor(options: SyncStatusOptions);
|
|
44
|
+
/**
|
|
45
|
+
* Returns the used sync client implementation (either the one implemented in JavaScript or the newer Rust-based
|
|
46
|
+
* implementation).
|
|
47
|
+
*
|
|
48
|
+
* This information is only available after a connection has been requested.
|
|
49
|
+
*/
|
|
50
|
+
get clientImplementation(): SyncClientImplementation | undefined;
|
|
39
51
|
/**
|
|
40
52
|
* Indicates if the client is currently connected to the PowerSync service.
|
|
41
53
|
*
|
|
@@ -90,7 +102,23 @@ export declare class SyncStatus {
|
|
|
90
102
|
* Please use the {@link SyncStatus#downloadProgress} property to track sync progress.
|
|
91
103
|
*/
|
|
92
104
|
downloadProgress: InternalProgressInformation | null;
|
|
105
|
+
internalStreamSubscriptions: CoreStreamSubscription[] | null;
|
|
93
106
|
}>;
|
|
107
|
+
/**
|
|
108
|
+
* All sync streams currently being tracked in teh database.
|
|
109
|
+
*
|
|
110
|
+
* This returns null when the database is currently being opened and we don't have reliable information about all
|
|
111
|
+
* included streams yet.
|
|
112
|
+
*
|
|
113
|
+
* @experimental Sync streams are currently in alpha.
|
|
114
|
+
*/
|
|
115
|
+
get syncStreams(): SyncStreamStatus[] | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* If the `stream` appears in {@link syncStreams}, returns the current status for that stream.
|
|
118
|
+
*
|
|
119
|
+
* @experimental Sync streams are currently in alpha.
|
|
120
|
+
*/
|
|
121
|
+
forStream(stream: SyncStreamDescription): SyncStreamStatus | undefined;
|
|
94
122
|
/**
|
|
95
123
|
* Provides sync status information for all bucket priorities, sorted by priority (highest first).
|
|
96
124
|
*
|
|
@@ -148,3 +176,11 @@ export declare class SyncStatus {
|
|
|
148
176
|
toJSON(): SyncStatusOptions;
|
|
149
177
|
private static comparePriorities;
|
|
150
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Information about a sync stream subscription.
|
|
181
|
+
*/
|
|
182
|
+
export interface SyncStreamStatus {
|
|
183
|
+
progress: ProgressWithOperations | null;
|
|
184
|
+
subscription: SyncSubscriptionDescription;
|
|
185
|
+
priority: number | null;
|
|
186
|
+
}
|