@powersync/common 1.55.0 → 1.57.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/dist/bundle.cjs +108 -45
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.mjs +108 -46
- package/dist/bundle.mjs.map +1 -1
- package/dist/bundle.node.cjs +108 -45
- package/dist/bundle.node.cjs.map +1 -1
- package/dist/bundle.node.mjs +108 -46
- package/dist/bundle.node.mjs.map +1 -1
- package/dist/index.d.cts +55 -13
- package/lib/attachments/AttachmentQueue.d.ts +18 -1
- package/lib/attachments/AttachmentQueue.js +43 -7
- package/lib/attachments/AttachmentQueue.js.map +1 -1
- package/lib/attachments/Schema.d.ts +21 -2
- package/lib/attachments/Schema.js +14 -10
- package/lib/attachments/Schema.js.map +1 -1
- package/lib/attachments/SyncingService.d.ts +14 -4
- package/lib/attachments/SyncingService.js +39 -21
- package/lib/attachments/SyncingService.js.map +1 -1
- package/lib/client/AbstractPowerSyncDatabase.js +1 -1
- package/lib/client/AbstractPowerSyncDatabase.js.map +1 -1
- package/lib/db/crud/SyncStatus.d.ts +1 -5
- package/lib/db/crud/SyncStatus.js +9 -6
- package/lib/db/crud/SyncStatus.js.map +1 -1
- package/package.json +1 -1
- package/src/attachments/AttachmentQueue.ts +46 -7
- package/src/attachments/Schema.ts +29 -20
- package/src/attachments/SyncingService.ts +46 -23
- package/src/client/AbstractPowerSyncDatabase.ts +3 -1
- package/src/db/crud/SyncStatus.ts +10 -7
package/dist/bundle.node.cjs
CHANGED
|
@@ -391,6 +391,19 @@ exports.AttachmentState = void 0;
|
|
|
391
391
|
AttachmentState[AttachmentState["SYNCED"] = 3] = "SYNCED";
|
|
392
392
|
AttachmentState[AttachmentState["ARCHIVED"] = 4] = "ARCHIVED"; // Attachment has been orphaned, i.e. the associated record has been deleted
|
|
393
393
|
})(exports.AttachmentState || (exports.AttachmentState = {}));
|
|
394
|
+
/**
|
|
395
|
+
* @alpha
|
|
396
|
+
*/
|
|
397
|
+
const ATTACHMENT_TABLE_COLUMNS = {
|
|
398
|
+
filename: column.text,
|
|
399
|
+
local_uri: column.text,
|
|
400
|
+
timestamp: column.integer,
|
|
401
|
+
size: column.integer,
|
|
402
|
+
media_type: column.text,
|
|
403
|
+
state: column.integer, // Corresponds to AttachmentState
|
|
404
|
+
has_synced: column.integer,
|
|
405
|
+
meta_data: column.text
|
|
406
|
+
};
|
|
394
407
|
/**
|
|
395
408
|
* AttachmentTable defines the schema for the attachment queue table.
|
|
396
409
|
*
|
|
@@ -398,16 +411,7 @@ exports.AttachmentState = void 0;
|
|
|
398
411
|
*/
|
|
399
412
|
class AttachmentTable extends Table {
|
|
400
413
|
constructor(options) {
|
|
401
|
-
super({
|
|
402
|
-
filename: column.text,
|
|
403
|
-
local_uri: column.text,
|
|
404
|
-
timestamp: column.integer,
|
|
405
|
-
size: column.integer,
|
|
406
|
-
media_type: column.text,
|
|
407
|
-
state: column.integer, // Corresponds to AttachmentState
|
|
408
|
-
has_synced: column.integer,
|
|
409
|
-
meta_data: column.text
|
|
410
|
-
}, {
|
|
414
|
+
super(ATTACHMENT_TABLE_COLUMNS, {
|
|
411
415
|
...options,
|
|
412
416
|
viewName: options?.viewName ?? ATTACHMENT_TABLE,
|
|
413
417
|
localOnly: true,
|
|
@@ -939,31 +943,51 @@ class SyncingService {
|
|
|
939
943
|
}
|
|
940
944
|
/**
|
|
941
945
|
* Processes attachments based on their state (upload, download, or delete).
|
|
942
|
-
*
|
|
946
|
+
*
|
|
947
|
+
* Each attachment's I/O runs outside the attachment-service mutex, and the row's
|
|
948
|
+
* state transition is persisted immediately after it completes. This keeps the
|
|
949
|
+
* mutex available to concurrent `saveFile` / `deleteFile` / watched-attachment
|
|
950
|
+
* processing while a batch is in flight, and means consumer queries against the
|
|
951
|
+
* attachments queue see incremental progress instead of one atomic commit at the
|
|
952
|
+
* end of the batch.
|
|
943
953
|
*
|
|
944
954
|
* @param attachments - Array of attachment records to process
|
|
945
|
-
* @param
|
|
946
|
-
*
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
955
|
+
* @param options - Optional controls. Pass `signal` (an `AbortSignal`) to interrupt
|
|
956
|
+
* the batch: it is checked between attachments and, once aborted, the
|
|
957
|
+
* loop exits early — letting `stopSync` stop a running batch within
|
|
958
|
+
* one attachment's processing time.
|
|
959
|
+
*/
|
|
960
|
+
async processAttachments(attachments, options) {
|
|
961
|
+
const signal = options?.signal;
|
|
962
|
+
this.logger.info(`Starting processAttachments with ${attachments.length} attachments`);
|
|
950
963
|
for (const attachment of attachments) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
+
if (signal?.aborted) {
|
|
965
|
+
this.logger.info('Sync cancelled; stopping iteration early');
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
try {
|
|
969
|
+
let updated;
|
|
970
|
+
switch (attachment.state) {
|
|
971
|
+
case exports.AttachmentState.QUEUED_UPLOAD:
|
|
972
|
+
updated = await this.uploadAttachment(attachment);
|
|
973
|
+
break;
|
|
974
|
+
case exports.AttachmentState.QUEUED_DOWNLOAD:
|
|
975
|
+
updated = await this.downloadAttachment(attachment);
|
|
976
|
+
break;
|
|
977
|
+
case exports.AttachmentState.QUEUED_DELETE:
|
|
978
|
+
// `deleteAttachment` needs a context (it removes the row in a
|
|
979
|
+
// transaction); briefly re-acquire the mutex for just this row.
|
|
980
|
+
updated = await this.attachmentService.withContext((ctx) => this.deleteAttachment(attachment, ctx));
|
|
981
|
+
break;
|
|
982
|
+
default:
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
await this.attachmentService.withContext((ctx) => ctx.saveAttachments([updated]));
|
|
986
|
+
}
|
|
987
|
+
catch (error) {
|
|
988
|
+
this.logger.warn(`Error during sync for ${attachment.id}`, error);
|
|
964
989
|
}
|
|
965
990
|
}
|
|
966
|
-
await context.saveAttachments(updatedAttachments);
|
|
967
991
|
}
|
|
968
992
|
/**
|
|
969
993
|
* Uploads an attachment from local storage to remote storage.
|
|
@@ -1134,6 +1158,19 @@ class AttachmentQueue {
|
|
|
1134
1158
|
statusListenerDispose;
|
|
1135
1159
|
watchActiveAttachments;
|
|
1136
1160
|
watchAttachmentsAbortController;
|
|
1161
|
+
/**
|
|
1162
|
+
* Serializes concurrent `syncStorage()` triggers (periodic timer, watch-onDiff,
|
|
1163
|
+
* status-changed). Held across the whole batch, but only contended by other
|
|
1164
|
+
* sync triggers — foreground `saveFile` / `deleteFile` / watched-attachment
|
|
1165
|
+
* processing don't take this lock and proceed in parallel via the
|
|
1166
|
+
* `AttachmentService` mutex, which is acquired only briefly per row.
|
|
1167
|
+
*/
|
|
1168
|
+
syncLoopMutex = new Mutex();
|
|
1169
|
+
/**
|
|
1170
|
+
* Aborted by `stopSync()` to interrupt an in-flight batch within one
|
|
1171
|
+
* attachment's processing time. Polled between rows by `SyncingService`.
|
|
1172
|
+
*/
|
|
1173
|
+
syncAbortController;
|
|
1137
1174
|
/**
|
|
1138
1175
|
* Creates a new AttachmentQueue instance.
|
|
1139
1176
|
*
|
|
@@ -1173,6 +1210,7 @@ class AttachmentQueue {
|
|
|
1173
1210
|
*/
|
|
1174
1211
|
async startSync() {
|
|
1175
1212
|
await this.stopSync();
|
|
1213
|
+
this.syncAbortController = new AbortController();
|
|
1176
1214
|
this.watchActiveAttachments = this.attachmentService.watchActiveAttachments({
|
|
1177
1215
|
throttleMs: this.syncThrottleDuration
|
|
1178
1216
|
});
|
|
@@ -1287,23 +1325,44 @@ class AttachmentQueue {
|
|
|
1287
1325
|
*
|
|
1288
1326
|
* This is called automatically at regular intervals when sync is started,
|
|
1289
1327
|
* but can also be called manually to trigger an immediate sync.
|
|
1328
|
+
*
|
|
1329
|
+
* Concurrent invocations are serialized via `syncLoopMutex`.
|
|
1290
1330
|
*/
|
|
1291
1331
|
async syncStorage() {
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
await this.
|
|
1297
|
-
|
|
1332
|
+
const signal = this.syncAbortController?.signal;
|
|
1333
|
+
if (signal?.aborted)
|
|
1334
|
+
return;
|
|
1335
|
+
try {
|
|
1336
|
+
await this.syncLoopMutex.runExclusive(async () => {
|
|
1337
|
+
const activeAttachments = await this.attachmentService.withContext((ctx) => ctx.getActiveAttachments());
|
|
1338
|
+
await this.localStorage.initialize();
|
|
1339
|
+
await this.syncingService.processAttachments(activeAttachments, { signal });
|
|
1340
|
+
if (signal?.aborted)
|
|
1341
|
+
return;
|
|
1342
|
+
await this.attachmentService.withContext((ctx) => this.syncingService.deleteArchivedAttachments(ctx));
|
|
1343
|
+
}, signal);
|
|
1344
|
+
}
|
|
1345
|
+
catch (error) {
|
|
1346
|
+
// A queued batch's acquire rejects when `stopSync` aborts — expected, not an error.
|
|
1347
|
+
if (signal?.aborted)
|
|
1348
|
+
return;
|
|
1349
|
+
throw error;
|
|
1350
|
+
}
|
|
1298
1351
|
}
|
|
1299
1352
|
/**
|
|
1300
1353
|
* Stops the attachment synchronization process.
|
|
1301
1354
|
*
|
|
1302
|
-
* Clears the periodic sync timer
|
|
1355
|
+
* Clears the periodic sync timer, closes all active attachment watchers, and
|
|
1356
|
+
* aborts any in-flight `syncStorage()` call so it exits within one
|
|
1357
|
+
* attachment's processing time instead of running the batch to completion.
|
|
1303
1358
|
*/
|
|
1304
1359
|
async stopSync() {
|
|
1305
1360
|
clearInterval(this.periodicSyncTimer);
|
|
1306
1361
|
this.periodicSyncTimer = undefined;
|
|
1362
|
+
if (this.syncAbortController) {
|
|
1363
|
+
this.syncAbortController.abort();
|
|
1364
|
+
this.syncAbortController = undefined;
|
|
1365
|
+
}
|
|
1307
1366
|
if (this.watchActiveAttachments)
|
|
1308
1367
|
await this.watchActiveAttachments.close();
|
|
1309
1368
|
if (this.watchAttachmentsAbortController) {
|
|
@@ -2139,11 +2198,7 @@ class SyncStatus {
|
|
|
2139
2198
|
*/
|
|
2140
2199
|
const replacer = (_, value) => {
|
|
2141
2200
|
if (value instanceof Error) {
|
|
2142
|
-
return
|
|
2143
|
-
name: value.name,
|
|
2144
|
-
message: value.message,
|
|
2145
|
-
stack: value.stack
|
|
2146
|
-
};
|
|
2201
|
+
return this.serializeError(value);
|
|
2147
2202
|
}
|
|
2148
2203
|
return value;
|
|
2149
2204
|
};
|
|
@@ -2186,11 +2241,18 @@ class SyncStatus {
|
|
|
2186
2241
|
if (typeof error == 'undefined') {
|
|
2187
2242
|
return undefined;
|
|
2188
2243
|
}
|
|
2189
|
-
|
|
2244
|
+
const serialized = {
|
|
2190
2245
|
name: error.name,
|
|
2191
2246
|
message: error.message,
|
|
2192
2247
|
stack: error.stack
|
|
2193
2248
|
};
|
|
2249
|
+
// `Error.cause` can be any value (the spec types it as unknown). Preserve it
|
|
2250
|
+
// so consumers reading uploadError/downloadError keep the failure context.
|
|
2251
|
+
// Recurse for Error causes so the whole chain is flattened the same way.
|
|
2252
|
+
if (typeof error.cause != 'undefined') {
|
|
2253
|
+
serialized.cause = error.cause instanceof Error ? this.serializeError(error.cause) : error.cause;
|
|
2254
|
+
}
|
|
2255
|
+
return serialized;
|
|
2194
2256
|
}
|
|
2195
2257
|
static comparePriorities(a, b) {
|
|
2196
2258
|
return b.priority - a.priority; // Reverse because higher priorities have lower numbers
|
|
@@ -8544,7 +8606,7 @@ function requireDist () {
|
|
|
8544
8606
|
|
|
8545
8607
|
var distExports = requireDist();
|
|
8546
8608
|
|
|
8547
|
-
var version = "1.
|
|
8609
|
+
var version = "1.57.0";
|
|
8548
8610
|
var PACKAGE = {
|
|
8549
8611
|
version: version};
|
|
8550
8612
|
|
|
@@ -10675,7 +10737,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
10675
10737
|
this.logger.warn('Schema validation failed. Unexpected behaviour could occur', ex);
|
|
10676
10738
|
}
|
|
10677
10739
|
this._schema = schema;
|
|
10678
|
-
await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
|
|
10740
|
+
await this.database.writeTransaction((tx) => tx.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]));
|
|
10679
10741
|
await this.database.refreshSchema();
|
|
10680
10742
|
this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
|
|
10681
10743
|
}
|
|
@@ -11872,6 +11934,7 @@ const parseQuery = (query, parameters) => {
|
|
|
11872
11934
|
};
|
|
11873
11935
|
|
|
11874
11936
|
exports.ATTACHMENT_TABLE = ATTACHMENT_TABLE;
|
|
11937
|
+
exports.ATTACHMENT_TABLE_COLUMNS = ATTACHMENT_TABLE_COLUMNS;
|
|
11875
11938
|
exports.AbortOperation = AbortOperation;
|
|
11876
11939
|
exports.AbstractPowerSyncDatabase = AbstractPowerSyncDatabase;
|
|
11877
11940
|
exports.AbstractPowerSyncDatabaseOpenFactory = AbstractPowerSyncDatabaseOpenFactory;
|