@powersync/common 0.0.0-dev-20250701144132 → 0.0.0-dev-20250710151329
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 +26 -20
- 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,6 +213,7 @@ The next upload iteration will be delayed.`);
|
|
|
210
213
|
}
|
|
211
214
|
else {
|
|
212
215
|
// Uploading is completed
|
|
216
|
+
this.logger.debug('Upload complete, updating write checkpoint');
|
|
213
217
|
await this.options.adapter.updateLocalTarget(() => this.getWriteCheckpoint());
|
|
214
218
|
break;
|
|
215
219
|
}
|
|
@@ -247,23 +251,17 @@ The next upload iteration will be delayed.`);
|
|
|
247
251
|
const controller = new AbortController();
|
|
248
252
|
this.abortController = controller;
|
|
249
253
|
this.streamingSyncPromise = this.streamingSync(this.abortController.signal, options);
|
|
250
|
-
// Return a promise that resolves when the connection status is updated
|
|
254
|
+
// Return a promise that resolves when the connection status is updated to indicate that we're connected.
|
|
251
255
|
return new Promise((resolve) => {
|
|
252
256
|
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
|
-
*/
|
|
257
|
+
statusChanged: (status) => {
|
|
258
|
+
if (status.dataFlowStatus.downloadError != null) {
|
|
265
259
|
this.logger.warn('Initial connect attempt did not successfully connect to server');
|
|
266
260
|
}
|
|
261
|
+
else if (status.connecting) {
|
|
262
|
+
// Still connecting.
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
267
265
|
disposer();
|
|
268
266
|
resolve();
|
|
269
267
|
}
|
|
@@ -655,6 +653,7 @@ The next upload iteration will be delayed.`);
|
|
|
655
653
|
const adapter = this.options.adapter;
|
|
656
654
|
const remote = this.options.remote;
|
|
657
655
|
let receivingLines = null;
|
|
656
|
+
let hadSyncLine = false;
|
|
658
657
|
const abortController = new AbortController();
|
|
659
658
|
signal.addEventListener('abort', () => abortController.abort());
|
|
660
659
|
// Pending sync lines received from the service, as well as local events that trigger a powersync_control
|
|
@@ -698,6 +697,9 @@ The next upload iteration will be delayed.`);
|
|
|
698
697
|
}
|
|
699
698
|
});
|
|
700
699
|
}
|
|
700
|
+
// The rust client will set connected: true after the first sync line because that's when it gets invoked, but
|
|
701
|
+
// we're already connected here and can report that.
|
|
702
|
+
syncImplementation.updateSyncStatus({ connected: true });
|
|
701
703
|
try {
|
|
702
704
|
while (!controlInvocations.closed) {
|
|
703
705
|
const line = await controlInvocations.read();
|
|
@@ -705,6 +707,10 @@ The next upload iteration will be delayed.`);
|
|
|
705
707
|
return;
|
|
706
708
|
}
|
|
707
709
|
await control(line.command, line.payload);
|
|
710
|
+
if (!hadSyncLine) {
|
|
711
|
+
syncImplementation.triggerCrudUpload();
|
|
712
|
+
hadSyncLine = true;
|
|
713
|
+
}
|
|
708
714
|
}
|
|
709
715
|
}
|
|
710
716
|
finally {
|
|
@@ -742,7 +748,7 @@ The next upload iteration will be delayed.`);
|
|
|
742
748
|
return {
|
|
743
749
|
priority: status.priority,
|
|
744
750
|
hasSynced: status.has_synced ?? undefined,
|
|
745
|
-
lastSyncedAt: status?.last_synced_at != null ? new Date(status.last_synced_at) : undefined
|
|
751
|
+
lastSyncedAt: status?.last_synced_at != null ? new Date(status.last_synced_at * 1000) : undefined
|
|
746
752
|
};
|
|
747
753
|
}
|
|
748
754
|
const info = instruction.UpdateSyncStatus.status;
|
|
@@ -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-20250710151329",
|
|
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",
|