@powersync/web 1.38.4 → 1.38.6
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/worker/SharedSyncImplementation.umd.js +108 -45
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +107 -44
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/lib/package.json +2 -2
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -2799,6 +2799,7 @@ const isSharedWorker = 'SharedWorkerGlobalScope' in globalThis;
|
|
|
2799
2799
|
__webpack_require__.r(__webpack_exports__);
|
|
2800
2800
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
2801
2801
|
/* harmony export */ ATTACHMENT_TABLE: () => (/* binding */ ATTACHMENT_TABLE),
|
|
2802
|
+
/* harmony export */ ATTACHMENT_TABLE_COLUMNS: () => (/* binding */ ATTACHMENT_TABLE_COLUMNS),
|
|
2802
2803
|
/* harmony export */ AbortOperation: () => (/* binding */ AbortOperation),
|
|
2803
2804
|
/* harmony export */ AbstractPowerSyncDatabase: () => (/* binding */ AbstractPowerSyncDatabase),
|
|
2804
2805
|
/* harmony export */ AbstractPowerSyncDatabaseOpenFactory: () => (/* binding */ AbstractPowerSyncDatabaseOpenFactory),
|
|
@@ -3280,6 +3281,19 @@ var AttachmentState;
|
|
|
3280
3281
|
AttachmentState[AttachmentState["SYNCED"] = 3] = "SYNCED";
|
|
3281
3282
|
AttachmentState[AttachmentState["ARCHIVED"] = 4] = "ARCHIVED"; // Attachment has been orphaned, i.e. the associated record has been deleted
|
|
3282
3283
|
})(AttachmentState || (AttachmentState = {}));
|
|
3284
|
+
/**
|
|
3285
|
+
* @alpha
|
|
3286
|
+
*/
|
|
3287
|
+
const ATTACHMENT_TABLE_COLUMNS = {
|
|
3288
|
+
filename: column.text,
|
|
3289
|
+
local_uri: column.text,
|
|
3290
|
+
timestamp: column.integer,
|
|
3291
|
+
size: column.integer,
|
|
3292
|
+
media_type: column.text,
|
|
3293
|
+
state: column.integer, // Corresponds to AttachmentState
|
|
3294
|
+
has_synced: column.integer,
|
|
3295
|
+
meta_data: column.text
|
|
3296
|
+
};
|
|
3283
3297
|
/**
|
|
3284
3298
|
* AttachmentTable defines the schema for the attachment queue table.
|
|
3285
3299
|
*
|
|
@@ -3287,16 +3301,7 @@ var AttachmentState;
|
|
|
3287
3301
|
*/
|
|
3288
3302
|
class AttachmentTable extends Table {
|
|
3289
3303
|
constructor(options) {
|
|
3290
|
-
super({
|
|
3291
|
-
filename: column.text,
|
|
3292
|
-
local_uri: column.text,
|
|
3293
|
-
timestamp: column.integer,
|
|
3294
|
-
size: column.integer,
|
|
3295
|
-
media_type: column.text,
|
|
3296
|
-
state: column.integer, // Corresponds to AttachmentState
|
|
3297
|
-
has_synced: column.integer,
|
|
3298
|
-
meta_data: column.text
|
|
3299
|
-
}, {
|
|
3304
|
+
super(ATTACHMENT_TABLE_COLUMNS, {
|
|
3300
3305
|
...options,
|
|
3301
3306
|
viewName: options?.viewName ?? ATTACHMENT_TABLE,
|
|
3302
3307
|
localOnly: true,
|
|
@@ -3828,31 +3833,51 @@ class SyncingService {
|
|
|
3828
3833
|
}
|
|
3829
3834
|
/**
|
|
3830
3835
|
* Processes attachments based on their state (upload, download, or delete).
|
|
3831
|
-
*
|
|
3836
|
+
*
|
|
3837
|
+
* Each attachment's I/O runs outside the attachment-service mutex, and the row's
|
|
3838
|
+
* state transition is persisted immediately after it completes. This keeps the
|
|
3839
|
+
* mutex available to concurrent `saveFile` / `deleteFile` / watched-attachment
|
|
3840
|
+
* processing while a batch is in flight, and means consumer queries against the
|
|
3841
|
+
* attachments queue see incremental progress instead of one atomic commit at the
|
|
3842
|
+
* end of the batch.
|
|
3832
3843
|
*
|
|
3833
3844
|
* @param attachments - Array of attachment records to process
|
|
3834
|
-
* @param
|
|
3835
|
-
*
|
|
3845
|
+
* @param options - Optional controls. Pass `signal` (an `AbortSignal`) to interrupt
|
|
3846
|
+
* the batch: it is checked between attachments and, once aborted, the
|
|
3847
|
+
* loop exits early — letting `stopSync` stop a running batch within
|
|
3848
|
+
* one attachment's processing time.
|
|
3836
3849
|
*/
|
|
3837
|
-
async processAttachments(attachments,
|
|
3838
|
-
const
|
|
3850
|
+
async processAttachments(attachments, options) {
|
|
3851
|
+
const signal = options?.signal;
|
|
3852
|
+
this.logger.info(`Starting processAttachments with ${attachments.length} attachments`);
|
|
3839
3853
|
for (const attachment of attachments) {
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3854
|
+
if (signal?.aborted) {
|
|
3855
|
+
this.logger.info('Sync cancelled; stopping iteration early');
|
|
3856
|
+
return;
|
|
3857
|
+
}
|
|
3858
|
+
try {
|
|
3859
|
+
let updated;
|
|
3860
|
+
switch (attachment.state) {
|
|
3861
|
+
case AttachmentState.QUEUED_UPLOAD:
|
|
3862
|
+
updated = await this.uploadAttachment(attachment);
|
|
3863
|
+
break;
|
|
3864
|
+
case AttachmentState.QUEUED_DOWNLOAD:
|
|
3865
|
+
updated = await this.downloadAttachment(attachment);
|
|
3866
|
+
break;
|
|
3867
|
+
case AttachmentState.QUEUED_DELETE:
|
|
3868
|
+
// `deleteAttachment` needs a context (it removes the row in a
|
|
3869
|
+
// transaction); briefly re-acquire the mutex for just this row.
|
|
3870
|
+
updated = await this.attachmentService.withContext((ctx) => this.deleteAttachment(attachment, ctx));
|
|
3871
|
+
break;
|
|
3872
|
+
default:
|
|
3873
|
+
continue;
|
|
3874
|
+
}
|
|
3875
|
+
await this.attachmentService.withContext((ctx) => ctx.saveAttachments([updated]));
|
|
3876
|
+
}
|
|
3877
|
+
catch (error) {
|
|
3878
|
+
this.logger.warn(`Error during sync for ${attachment.id}`, error);
|
|
3853
3879
|
}
|
|
3854
3880
|
}
|
|
3855
|
-
await context.saveAttachments(updatedAttachments);
|
|
3856
3881
|
}
|
|
3857
3882
|
/**
|
|
3858
3883
|
* Uploads an attachment from local storage to remote storage.
|
|
@@ -4023,6 +4048,19 @@ class AttachmentQueue {
|
|
|
4023
4048
|
statusListenerDispose;
|
|
4024
4049
|
watchActiveAttachments;
|
|
4025
4050
|
watchAttachmentsAbortController;
|
|
4051
|
+
/**
|
|
4052
|
+
* Serializes concurrent `syncStorage()` triggers (periodic timer, watch-onDiff,
|
|
4053
|
+
* status-changed). Held across the whole batch, but only contended by other
|
|
4054
|
+
* sync triggers — foreground `saveFile` / `deleteFile` / watched-attachment
|
|
4055
|
+
* processing don't take this lock and proceed in parallel via the
|
|
4056
|
+
* `AttachmentService` mutex, which is acquired only briefly per row.
|
|
4057
|
+
*/
|
|
4058
|
+
syncLoopMutex = new Mutex();
|
|
4059
|
+
/**
|
|
4060
|
+
* Aborted by `stopSync()` to interrupt an in-flight batch within one
|
|
4061
|
+
* attachment's processing time. Polled between rows by `SyncingService`.
|
|
4062
|
+
*/
|
|
4063
|
+
syncAbortController;
|
|
4026
4064
|
/**
|
|
4027
4065
|
* Creates a new AttachmentQueue instance.
|
|
4028
4066
|
*
|
|
@@ -4062,6 +4100,7 @@ class AttachmentQueue {
|
|
|
4062
4100
|
*/
|
|
4063
4101
|
async startSync() {
|
|
4064
4102
|
await this.stopSync();
|
|
4103
|
+
this.syncAbortController = new AbortController();
|
|
4065
4104
|
this.watchActiveAttachments = this.attachmentService.watchActiveAttachments({
|
|
4066
4105
|
throttleMs: this.syncThrottleDuration
|
|
4067
4106
|
});
|
|
@@ -4176,23 +4215,44 @@ class AttachmentQueue {
|
|
|
4176
4215
|
*
|
|
4177
4216
|
* This is called automatically at regular intervals when sync is started,
|
|
4178
4217
|
* but can also be called manually to trigger an immediate sync.
|
|
4218
|
+
*
|
|
4219
|
+
* Concurrent invocations are serialized via `syncLoopMutex`.
|
|
4179
4220
|
*/
|
|
4180
4221
|
async syncStorage() {
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
await this.
|
|
4186
|
-
|
|
4222
|
+
const signal = this.syncAbortController?.signal;
|
|
4223
|
+
if (signal?.aborted)
|
|
4224
|
+
return;
|
|
4225
|
+
try {
|
|
4226
|
+
await this.syncLoopMutex.runExclusive(async () => {
|
|
4227
|
+
const activeAttachments = await this.attachmentService.withContext((ctx) => ctx.getActiveAttachments());
|
|
4228
|
+
await this.localStorage.initialize();
|
|
4229
|
+
await this.syncingService.processAttachments(activeAttachments, { signal });
|
|
4230
|
+
if (signal?.aborted)
|
|
4231
|
+
return;
|
|
4232
|
+
await this.attachmentService.withContext((ctx) => this.syncingService.deleteArchivedAttachments(ctx));
|
|
4233
|
+
}, signal);
|
|
4234
|
+
}
|
|
4235
|
+
catch (error) {
|
|
4236
|
+
// A queued batch's acquire rejects when `stopSync` aborts — expected, not an error.
|
|
4237
|
+
if (signal?.aborted)
|
|
4238
|
+
return;
|
|
4239
|
+
throw error;
|
|
4240
|
+
}
|
|
4187
4241
|
}
|
|
4188
4242
|
/**
|
|
4189
4243
|
* Stops the attachment synchronization process.
|
|
4190
4244
|
*
|
|
4191
|
-
* Clears the periodic sync timer
|
|
4245
|
+
* Clears the periodic sync timer, closes all active attachment watchers, and
|
|
4246
|
+
* aborts any in-flight `syncStorage()` call so it exits within one
|
|
4247
|
+
* attachment's processing time instead of running the batch to completion.
|
|
4192
4248
|
*/
|
|
4193
4249
|
async stopSync() {
|
|
4194
4250
|
clearInterval(this.periodicSyncTimer);
|
|
4195
4251
|
this.periodicSyncTimer = undefined;
|
|
4252
|
+
if (this.syncAbortController) {
|
|
4253
|
+
this.syncAbortController.abort();
|
|
4254
|
+
this.syncAbortController = undefined;
|
|
4255
|
+
}
|
|
4196
4256
|
if (this.watchActiveAttachments)
|
|
4197
4257
|
await this.watchActiveAttachments.close();
|
|
4198
4258
|
if (this.watchAttachmentsAbortController) {
|
|
@@ -5028,11 +5088,7 @@ class SyncStatus {
|
|
|
5028
5088
|
*/
|
|
5029
5089
|
const replacer = (_, value) => {
|
|
5030
5090
|
if (value instanceof Error) {
|
|
5031
|
-
return
|
|
5032
|
-
name: value.name,
|
|
5033
|
-
message: value.message,
|
|
5034
|
-
stack: value.stack
|
|
5035
|
-
};
|
|
5091
|
+
return this.serializeError(value);
|
|
5036
5092
|
}
|
|
5037
5093
|
return value;
|
|
5038
5094
|
};
|
|
@@ -5075,11 +5131,18 @@ class SyncStatus {
|
|
|
5075
5131
|
if (typeof error == 'undefined') {
|
|
5076
5132
|
return undefined;
|
|
5077
5133
|
}
|
|
5078
|
-
|
|
5134
|
+
const serialized = {
|
|
5079
5135
|
name: error.name,
|
|
5080
5136
|
message: error.message,
|
|
5081
5137
|
stack: error.stack
|
|
5082
5138
|
};
|
|
5139
|
+
// `Error.cause` can be any value (the spec types it as unknown). Preserve it
|
|
5140
|
+
// so consumers reading uploadError/downloadError keep the failure context.
|
|
5141
|
+
// Recurse for Error causes so the whole chain is flattened the same way.
|
|
5142
|
+
if (typeof error.cause != 'undefined') {
|
|
5143
|
+
serialized.cause = error.cause instanceof Error ? this.serializeError(error.cause) : error.cause;
|
|
5144
|
+
}
|
|
5145
|
+
return serialized;
|
|
5083
5146
|
}
|
|
5084
5147
|
static comparePriorities(a, b) {
|
|
5085
5148
|
return b.priority - a.priority; // Reverse because higher priorities have lower numbers
|
|
@@ -13804,7 +13867,7 @@ function requireDist () {
|
|
|
13804
13867
|
|
|
13805
13868
|
var distExports = requireDist();
|
|
13806
13869
|
|
|
13807
|
-
var version = "1.
|
|
13870
|
+
var version = "1.57.0";
|
|
13808
13871
|
var PACKAGE = {
|
|
13809
13872
|
version: version};
|
|
13810
13873
|
|
|
@@ -15935,7 +15998,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
15935
15998
|
this.logger.warn('Schema validation failed. Unexpected behaviour could occur', ex);
|
|
15936
15999
|
}
|
|
15937
16000
|
this._schema = schema;
|
|
15938
|
-
await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
|
|
16001
|
+
await this.database.writeTransaction((tx) => tx.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]));
|
|
15939
16002
|
await this.database.refreshSchema();
|
|
15940
16003
|
this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
|
|
15941
16004
|
}
|