@powersync/common 0.0.0-dev-20250701144132 → 0.0.0-dev-20250710153817
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 +22 -0
- package/dist/bundle.mjs +2 -2
- package/lib/client/AbstractPowerSyncDatabase.d.ts +2 -3
- package/lib/client/AbstractPowerSyncDatabase.js +5 -7
- package/lib/client/ConnectionManager.js +0 -1
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +32 -22
- package/lib/db/crud/SyncProgress.d.ts +1 -1
- package/lib/utils/DataStream.d.ts +2 -1
- package/lib/utils/DataStream.js +23 -12
- package/package.json +4 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Mutex } from 'async-mutex';
|
|
2
|
-
import
|
|
2
|
+
import { ILogger } from 'js-logger';
|
|
3
3
|
import { DBAdapter, QueryResult, Transaction } from '../db/DBAdapter.js';
|
|
4
4
|
import { SyncStatus } from '../db/crud/SyncStatus.js';
|
|
5
5
|
import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js';
|
|
@@ -84,7 +84,6 @@ export declare const DEFAULT_POWERSYNC_CLOSE_OPTIONS: PowerSyncCloseOptions;
|
|
|
84
84
|
export declare const DEFAULT_WATCH_THROTTLE_MS = 30;
|
|
85
85
|
export declare const DEFAULT_POWERSYNC_DB_OPTIONS: {
|
|
86
86
|
retryDelayMs: number;
|
|
87
|
-
logger: Logger.ILogger;
|
|
88
87
|
crudUploadThrottleMs: number;
|
|
89
88
|
};
|
|
90
89
|
export declare const DEFAULT_CRUD_BATCH_LIMIT = 100;
|
|
@@ -123,6 +122,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
123
122
|
protected _schema: Schema;
|
|
124
123
|
private _database;
|
|
125
124
|
protected runExclusiveMutex: Mutex;
|
|
125
|
+
logger: ILogger;
|
|
126
126
|
constructor(options: PowerSyncDatabaseOptionsWithDBAdapter);
|
|
127
127
|
constructor(options: PowerSyncDatabaseOptionsWithOpenFactory);
|
|
128
128
|
constructor(options: PowerSyncDatabaseOptionsWithSettings);
|
|
@@ -185,7 +185,6 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
185
185
|
* Cannot be used while connected - this should only be called before {@link AbstractPowerSyncDatabase.connect}.
|
|
186
186
|
*/
|
|
187
187
|
updateSchema(schema: Schema): Promise<void>;
|
|
188
|
-
get logger(): Logger.ILogger;
|
|
189
188
|
/**
|
|
190
189
|
* Wait for initialization to complete.
|
|
191
190
|
* While initializing is automatic, this helps to catch and report initialization errors.
|
|
@@ -27,7 +27,6 @@ export const DEFAULT_POWERSYNC_CLOSE_OPTIONS = {
|
|
|
27
27
|
export const DEFAULT_WATCH_THROTTLE_MS = 30;
|
|
28
28
|
export const DEFAULT_POWERSYNC_DB_OPTIONS = {
|
|
29
29
|
retryDelayMs: 5000,
|
|
30
|
-
logger: Logger.get('PowerSyncDatabase'),
|
|
31
30
|
crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
|
|
32
31
|
};
|
|
33
32
|
export const DEFAULT_CRUD_BATCH_LIMIT = 100;
|
|
@@ -70,6 +69,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
70
69
|
_schema;
|
|
71
70
|
_database;
|
|
72
71
|
runExclusiveMutex;
|
|
72
|
+
logger;
|
|
73
73
|
constructor(options) {
|
|
74
74
|
super();
|
|
75
75
|
this.options = options;
|
|
@@ -89,6 +89,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
89
89
|
else {
|
|
90
90
|
throw new Error('The provided `database` option is invalid.');
|
|
91
91
|
}
|
|
92
|
+
this.logger = options.logger ?? Logger.get(`PowerSyncDatabase[${this._database.name}]`);
|
|
92
93
|
this.bucketStorageAdapter = this.generateBucketStorageAdapter();
|
|
93
94
|
this.closed = false;
|
|
94
95
|
this.currentStatus = new SyncStatus({});
|
|
@@ -268,16 +269,13 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
268
269
|
schema.validate();
|
|
269
270
|
}
|
|
270
271
|
catch (ex) {
|
|
271
|
-
this.
|
|
272
|
+
this.logger.warn('Schema validation failed. Unexpected behaviour could occur', ex);
|
|
272
273
|
}
|
|
273
274
|
this._schema = schema;
|
|
274
275
|
await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
|
|
275
276
|
await this.database.refreshSchema();
|
|
276
277
|
this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
|
|
277
278
|
}
|
|
278
|
-
get logger() {
|
|
279
|
-
return this.options.logger;
|
|
280
|
-
}
|
|
281
279
|
/**
|
|
282
280
|
* Wait for initialization to complete.
|
|
283
281
|
* While initializing is automatic, this helps to catch and report initialization errors.
|
|
@@ -617,7 +615,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
617
615
|
* @param options Options for configuring watch behavior
|
|
618
616
|
*/
|
|
619
617
|
watchWithCallback(sql, parameters, handler, options) {
|
|
620
|
-
const { onResult, onError = (e) => this.
|
|
618
|
+
const { onResult, onError = (e) => this.logger.error(e) } = handler ?? {};
|
|
621
619
|
if (!onResult) {
|
|
622
620
|
throw new Error('onResult is required');
|
|
623
621
|
}
|
|
@@ -723,7 +721,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
723
721
|
* @returns A dispose function to stop watching for changes
|
|
724
722
|
*/
|
|
725
723
|
onChangeWithCallback(handler, options) {
|
|
726
|
-
const { onChange, onError = (e) => this.
|
|
724
|
+
const { onChange, onError = (e) => this.logger.error(e) } = handler ?? {};
|
|
727
725
|
if (!onChange) {
|
|
728
726
|
throw new Error('onChange is required');
|
|
729
727
|
}
|
|
@@ -136,7 +136,6 @@ export class ConnectionManager extends BaseObserver {
|
|
|
136
136
|
await this.disconnectingPromise;
|
|
137
137
|
this.logger.debug('Attempting to connect to PowerSync instance');
|
|
138
138
|
await this.syncStreamImplementation?.connect(appliedOptions);
|
|
139
|
-
this.syncStreamImplementation?.triggerCrudUpload();
|
|
140
139
|
}
|
|
141
140
|
/**
|
|
142
141
|
* Close the sync connection.
|
|
@@ -169,9 +169,12 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
|
169
169
|
}
|
|
170
170
|
async getWriteCheckpoint() {
|
|
171
171
|
const clientId = await this.options.adapter.getClientId();
|
|
172
|
+
this.logger.debug(`Creating write checkpoint for ${clientId}`);
|
|
172
173
|
let path = `/write-checkpoint2.json?client_id=${clientId}`;
|
|
173
174
|
const response = await this.options.remote.get(path);
|
|
174
|
-
|
|
175
|
+
const checkpoint = response['data']['write_checkpoint'];
|
|
176
|
+
this.logger.debug(`Got write checkpoint: ${checkpoint}`);
|
|
177
|
+
return checkpoint;
|
|
175
178
|
}
|
|
176
179
|
async _uploadAllCrud() {
|
|
177
180
|
return this.obtainLock({
|
|
@@ -182,17 +185,17 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
|
182
185
|
*/
|
|
183
186
|
let checkedCrudItem;
|
|
184
187
|
while (true) {
|
|
185
|
-
this.updateSyncStatus({
|
|
186
|
-
dataFlow: {
|
|
187
|
-
uploading: true
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
188
|
try {
|
|
191
189
|
/**
|
|
192
190
|
* This is the first item in the FIFO CRUD queue.
|
|
193
191
|
*/
|
|
194
192
|
const nextCrudItem = await this.options.adapter.nextCrudItem();
|
|
195
193
|
if (nextCrudItem) {
|
|
194
|
+
this.updateSyncStatus({
|
|
195
|
+
dataFlow: {
|
|
196
|
+
uploading: true
|
|
197
|
+
}
|
|
198
|
+
});
|
|
196
199
|
if (nextCrudItem.clientId == checkedCrudItem?.clientId) {
|
|
197
200
|
// This will force a higher log level than exceptions which are caught here.
|
|
198
201
|
this.logger.warn(`Potentially previously uploaded CRUD entries are still present in the upload queue.
|
|
@@ -210,7 +213,11 @@ The next upload iteration will be delayed.`);
|
|
|
210
213
|
}
|
|
211
214
|
else {
|
|
212
215
|
// Uploading is completed
|
|
213
|
-
|
|
216
|
+
this.logger.debug('Upload complete, creating write checkpoint');
|
|
217
|
+
const neededUpdate = await this.options.adapter.updateLocalTarget(() => this.getWriteCheckpoint());
|
|
218
|
+
if (neededUpdate == false) {
|
|
219
|
+
this.logger.debug('No write checkpoint needed');
|
|
220
|
+
}
|
|
214
221
|
break;
|
|
215
222
|
}
|
|
216
223
|
}
|
|
@@ -247,23 +254,17 @@ The next upload iteration will be delayed.`);
|
|
|
247
254
|
const controller = new AbortController();
|
|
248
255
|
this.abortController = controller;
|
|
249
256
|
this.streamingSyncPromise = this.streamingSync(this.abortController.signal, options);
|
|
250
|
-
// Return a promise that resolves when the connection status is updated
|
|
257
|
+
// Return a promise that resolves when the connection status is updated to indicate that we're connected.
|
|
251
258
|
return new Promise((resolve) => {
|
|
252
259
|
const disposer = this.registerListener({
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (typeof update.connected == 'undefined') {
|
|
256
|
-
// only concern with connection updates
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
if (update.connected == false) {
|
|
260
|
-
/**
|
|
261
|
-
* This function does not reject if initial connect attempt failed.
|
|
262
|
-
* Connected can be false if the connection attempt was aborted or if the initial connection
|
|
263
|
-
* attempt failed.
|
|
264
|
-
*/
|
|
260
|
+
statusChanged: (status) => {
|
|
261
|
+
if (status.dataFlowStatus.downloadError != null) {
|
|
265
262
|
this.logger.warn('Initial connect attempt did not successfully connect to server');
|
|
266
263
|
}
|
|
264
|
+
else if (status.connecting) {
|
|
265
|
+
// Still connecting.
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
267
268
|
disposer();
|
|
268
269
|
resolve();
|
|
269
270
|
}
|
|
@@ -655,6 +656,7 @@ The next upload iteration will be delayed.`);
|
|
|
655
656
|
const adapter = this.options.adapter;
|
|
656
657
|
const remote = this.options.remote;
|
|
657
658
|
let receivingLines = null;
|
|
659
|
+
let hadSyncLine = false;
|
|
658
660
|
const abortController = new AbortController();
|
|
659
661
|
signal.addEventListener('abort', () => abortController.abort());
|
|
660
662
|
// Pending sync lines received from the service, as well as local events that trigger a powersync_control
|
|
@@ -698,6 +700,9 @@ The next upload iteration will be delayed.`);
|
|
|
698
700
|
}
|
|
699
701
|
});
|
|
700
702
|
}
|
|
703
|
+
// The rust client will set connected: true after the first sync line because that's when it gets invoked, but
|
|
704
|
+
// we're already connected here and can report that.
|
|
705
|
+
syncImplementation.updateSyncStatus({ connected: true });
|
|
701
706
|
try {
|
|
702
707
|
while (!controlInvocations.closed) {
|
|
703
708
|
const line = await controlInvocations.read();
|
|
@@ -705,6 +710,10 @@ The next upload iteration will be delayed.`);
|
|
|
705
710
|
return;
|
|
706
711
|
}
|
|
707
712
|
await control(line.command, line.payload);
|
|
713
|
+
if (!hadSyncLine) {
|
|
714
|
+
syncImplementation.triggerCrudUpload();
|
|
715
|
+
hadSyncLine = true;
|
|
716
|
+
}
|
|
708
717
|
}
|
|
709
718
|
}
|
|
710
719
|
finally {
|
|
@@ -742,7 +751,7 @@ The next upload iteration will be delayed.`);
|
|
|
742
751
|
return {
|
|
743
752
|
priority: status.priority,
|
|
744
753
|
hasSynced: status.has_synced ?? undefined,
|
|
745
|
-
lastSyncedAt: status?.last_synced_at != null ? new Date(status.last_synced_at) : undefined
|
|
754
|
+
lastSyncedAt: status?.last_synced_at != null ? new Date(status.last_synced_at * 1000) : undefined
|
|
746
755
|
};
|
|
747
756
|
}
|
|
748
757
|
const info = instruction.UpdateSyncStatus.status;
|
|
@@ -865,8 +874,9 @@ The next upload iteration will be delayed.`);
|
|
|
865
874
|
// We have pending entries in the local upload queue or are waiting to confirm a write
|
|
866
875
|
// checkpoint, which prevented this checkpoint from applying. Wait for that to complete and
|
|
867
876
|
// try again.
|
|
868
|
-
this.logger.debug(
|
|
877
|
+
this.logger.debug(`Could not apply checkpoint ${checkpoint.last_op_id} due to local data. Waiting for in-progress upload before retrying.`);
|
|
869
878
|
await Promise.race([pending, onAbortPromise(abort)]);
|
|
879
|
+
this.logger.debug(`Pending uploads complete, retrying local checkpoint at ${checkpoint.last_op_id}`);
|
|
870
880
|
if (abort.aborted) {
|
|
871
881
|
return { applied: false, endIteration: true };
|
|
872
882
|
}
|
|
@@ -34,6 +34,7 @@ export declare class DataStream<ParsedData, SourceData = any> extends BaseObserv
|
|
|
34
34
|
dataQueue: SourceData[];
|
|
35
35
|
protected isClosed: boolean;
|
|
36
36
|
protected processingPromise: Promise<void> | null;
|
|
37
|
+
protected notifyDataAdded: (() => void) | null;
|
|
37
38
|
protected logger: ILogger;
|
|
38
39
|
protected mapLine: (line: SourceData) => ParsedData;
|
|
39
40
|
constructor(options?: DataStreamOptions<ParsedData, SourceData> | undefined);
|
|
@@ -54,7 +55,7 @@ export declare class DataStream<ParsedData, SourceData = any> extends BaseObserv
|
|
|
54
55
|
* Executes a callback for each data item in the stream
|
|
55
56
|
*/
|
|
56
57
|
forEach(callback: DataStreamCallback<ParsedData>): () => void;
|
|
57
|
-
protected processQueue(): Promise<void
|
|
58
|
+
protected processQueue(): Promise<void> | undefined;
|
|
58
59
|
protected hasDataReader(): boolean;
|
|
59
60
|
protected _processQueue(): Promise<void>;
|
|
60
61
|
protected iterateAsyncErrored(cb: (l: Partial<DataStreamListener<ParsedData>>) => Promise<void>): Promise<void>;
|
package/lib/utils/DataStream.js
CHANGED
|
@@ -14,6 +14,7 @@ export class DataStream extends BaseObserver {
|
|
|
14
14
|
dataQueue;
|
|
15
15
|
isClosed;
|
|
16
16
|
processingPromise;
|
|
17
|
+
notifyDataAdded;
|
|
17
18
|
logger;
|
|
18
19
|
mapLine;
|
|
19
20
|
constructor(options) {
|
|
@@ -58,6 +59,7 @@ export class DataStream extends BaseObserver {
|
|
|
58
59
|
throw new Error('Cannot enqueue data into closed stream.');
|
|
59
60
|
}
|
|
60
61
|
this.dataQueue.push(data);
|
|
62
|
+
this.notifyDataAdded?.();
|
|
61
63
|
this.processQueue();
|
|
62
64
|
}
|
|
63
65
|
/**
|
|
@@ -98,10 +100,20 @@ export class DataStream extends BaseObserver {
|
|
|
98
100
|
data: callback
|
|
99
101
|
});
|
|
100
102
|
}
|
|
101
|
-
|
|
103
|
+
processQueue() {
|
|
102
104
|
if (this.processingPromise) {
|
|
103
105
|
return;
|
|
104
106
|
}
|
|
107
|
+
const promise = (this.processingPromise = this._processQueue());
|
|
108
|
+
promise.finally(() => {
|
|
109
|
+
return (this.processingPromise = null);
|
|
110
|
+
});
|
|
111
|
+
return promise;
|
|
112
|
+
}
|
|
113
|
+
hasDataReader() {
|
|
114
|
+
return Array.from(this.listeners.values()).some((l) => !!l.data);
|
|
115
|
+
}
|
|
116
|
+
async _processQueue() {
|
|
105
117
|
/**
|
|
106
118
|
* Allow listeners to mutate the queue before processing.
|
|
107
119
|
* This allows for operations such as dropping or compressing data
|
|
@@ -110,14 +122,7 @@ export class DataStream extends BaseObserver {
|
|
|
110
122
|
if (this.dataQueue.length >= this.highWatermark) {
|
|
111
123
|
await this.iterateAsyncErrored(async (l) => l.highWater?.());
|
|
112
124
|
}
|
|
113
|
-
return (this.processingPromise = this._processQueue());
|
|
114
|
-
}
|
|
115
|
-
hasDataReader() {
|
|
116
|
-
return Array.from(this.listeners.values()).some((l) => !!l.data);
|
|
117
|
-
}
|
|
118
|
-
async _processQueue() {
|
|
119
125
|
if (this.isClosed || !this.hasDataReader()) {
|
|
120
|
-
Promise.resolve().then(() => (this.processingPromise = null));
|
|
121
126
|
return;
|
|
122
127
|
}
|
|
123
128
|
if (this.dataQueue.length) {
|
|
@@ -126,16 +131,22 @@ export class DataStream extends BaseObserver {
|
|
|
126
131
|
await this.iterateAsyncErrored(async (l) => l.data?.(mapped));
|
|
127
132
|
}
|
|
128
133
|
if (this.dataQueue.length <= this.lowWatermark) {
|
|
129
|
-
|
|
134
|
+
const dataAdded = new Promise((resolve) => {
|
|
135
|
+
this.notifyDataAdded = resolve;
|
|
136
|
+
});
|
|
137
|
+
await Promise.race([this.iterateAsyncErrored(async (l) => l.lowWater?.()), dataAdded]);
|
|
138
|
+
this.notifyDataAdded = null;
|
|
130
139
|
}
|
|
131
|
-
this.
|
|
132
|
-
if (this.dataQueue.length) {
|
|
140
|
+
if (this.dataQueue.length > 0) {
|
|
133
141
|
// Next tick
|
|
134
142
|
setTimeout(() => this.processQueue());
|
|
135
143
|
}
|
|
136
144
|
}
|
|
137
145
|
async iterateAsyncErrored(cb) {
|
|
138
|
-
|
|
146
|
+
// Important: We need to copy the listeners, as calling a listener could result in adding another
|
|
147
|
+
// listener, resulting in infinite loops.
|
|
148
|
+
const listeners = Array.from(this.listeners.values());
|
|
149
|
+
for (let i of listeners) {
|
|
139
150
|
try {
|
|
140
151
|
await cb(i);
|
|
141
152
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/common",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20250710153817",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
15
|
"import": "./dist/bundle.mjs",
|
|
16
|
-
"
|
|
17
|
-
"types": "./lib/index.d.ts"
|
|
16
|
+
"require": "./dist/bundle.cjs",
|
|
17
|
+
"types": "./lib/index.d.ts",
|
|
18
|
+
"default": "./dist/bundle.mjs"
|
|
18
19
|
}
|
|
19
20
|
},
|
|
20
21
|
"author": "JOURNEYAPPS",
|