@powersync/common 1.49.0 → 1.50.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 +123 -39
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.mjs +120 -37
- package/dist/bundle.mjs.map +1 -1
- package/dist/bundle.node.cjs +123 -38
- package/dist/bundle.node.cjs.map +1 -1
- package/dist/bundle.node.mjs +120 -36
- package/dist/bundle.node.mjs.map +1 -1
- package/dist/index.d.cts +46 -16
- package/lib/attachments/AttachmentQueue.d.ts +10 -4
- package/lib/attachments/AttachmentQueue.js +10 -4
- package/lib/attachments/AttachmentQueue.js.map +1 -1
- package/lib/attachments/AttachmentService.js +2 -3
- package/lib/attachments/AttachmentService.js.map +1 -1
- package/lib/attachments/SyncingService.d.ts +2 -1
- package/lib/attachments/SyncingService.js +4 -5
- package/lib/attachments/SyncingService.js.map +1 -1
- package/lib/client/AbstractPowerSyncDatabase.d.ts +5 -1
- package/lib/client/AbstractPowerSyncDatabase.js +6 -2
- package/lib/client/AbstractPowerSyncDatabase.js.map +1 -1
- package/lib/db/DBAdapter.d.ts +7 -1
- package/lib/db/DBAdapter.js.map +1 -1
- package/lib/utils/mutex.d.ts +18 -5
- package/lib/utils/mutex.js +97 -21
- package/lib/utils/mutex.js.map +1 -1
- package/package.json +1 -2
- package/src/attachments/AttachmentQueue.ts +10 -4
- package/src/attachments/AttachmentService.ts +2 -3
- package/src/attachments/README.md +6 -4
- package/src/attachments/SyncingService.ts +4 -5
- package/src/client/AbstractPowerSyncDatabase.ts +6 -2
- package/src/db/DBAdapter.ts +7 -1
- package/src/utils/mutex.ts +121 -26
package/dist/bundle.node.cjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var asyncMutex = require('async-mutex');
|
|
4
3
|
var eventIterator = require('event-iterator');
|
|
5
4
|
var node_buffer = require('node:buffer');
|
|
6
5
|
|
|
@@ -661,7 +660,7 @@ class SyncingService {
|
|
|
661
660
|
updatedAttachments.push(downloaded);
|
|
662
661
|
break;
|
|
663
662
|
case exports.AttachmentState.QUEUED_DELETE:
|
|
664
|
-
const deleted = await this.deleteAttachment(attachment);
|
|
663
|
+
const deleted = await this.deleteAttachment(attachment, context);
|
|
665
664
|
updatedAttachments.push(deleted);
|
|
666
665
|
break;
|
|
667
666
|
}
|
|
@@ -739,17 +738,16 @@ class SyncingService {
|
|
|
739
738
|
* On failure, defers to error handler or archives.
|
|
740
739
|
*
|
|
741
740
|
* @param attachment - The attachment record to delete
|
|
741
|
+
* @param context - Attachment context for database operations
|
|
742
742
|
* @returns Updated attachment record
|
|
743
743
|
*/
|
|
744
|
-
async deleteAttachment(attachment) {
|
|
744
|
+
async deleteAttachment(attachment, context) {
|
|
745
745
|
try {
|
|
746
746
|
await this.remoteStorage.deleteFile(attachment);
|
|
747
747
|
if (attachment.localUri) {
|
|
748
748
|
await this.localStorage.deleteFile(attachment.localUri);
|
|
749
749
|
}
|
|
750
|
-
await
|
|
751
|
-
await ctx.deleteAttachment(attachment.id);
|
|
752
|
-
});
|
|
750
|
+
await context.deleteAttachment(attachment.id);
|
|
753
751
|
return {
|
|
754
752
|
...attachment,
|
|
755
753
|
state: exports.AttachmentState.ARCHIVED
|
|
@@ -787,32 +785,108 @@ class SyncingService {
|
|
|
787
785
|
}
|
|
788
786
|
|
|
789
787
|
/**
|
|
790
|
-
*
|
|
788
|
+
* An asynchronous mutex implementation.
|
|
789
|
+
*
|
|
790
|
+
* @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
|
|
791
791
|
*/
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
792
|
+
class Mutex {
|
|
793
|
+
inCriticalSection = false;
|
|
794
|
+
// Linked list of waiters. We don't expect the wait list to become particularly large, and this allows removing
|
|
795
|
+
// aborted waiters from the middle of the list efficiently.
|
|
796
|
+
firstWaiter;
|
|
797
|
+
lastWaiter;
|
|
798
|
+
addWaiter(onAcquire) {
|
|
799
|
+
const node = {
|
|
800
|
+
isActive: true,
|
|
801
|
+
onAcquire,
|
|
802
|
+
prev: this.lastWaiter
|
|
803
|
+
};
|
|
804
|
+
if (this.lastWaiter) {
|
|
805
|
+
this.lastWaiter.next = node;
|
|
806
|
+
this.lastWaiter = node;
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
// First waiter
|
|
810
|
+
this.lastWaiter = this.firstWaiter = node;
|
|
811
|
+
}
|
|
812
|
+
return node;
|
|
813
|
+
}
|
|
814
|
+
deactivateWaiter(waiter) {
|
|
815
|
+
const { prev, next } = waiter;
|
|
816
|
+
waiter.isActive = false;
|
|
817
|
+
if (prev)
|
|
818
|
+
prev.next = next;
|
|
819
|
+
if (next)
|
|
820
|
+
next.prev = prev;
|
|
821
|
+
if (waiter == this.firstWaiter)
|
|
822
|
+
this.firstWaiter = next;
|
|
823
|
+
if (waiter == this.lastWaiter)
|
|
824
|
+
this.lastWaiter = prev;
|
|
825
|
+
}
|
|
826
|
+
acquire(abort) {
|
|
827
|
+
return new Promise((resolve, reject) => {
|
|
828
|
+
function rejectAborted() {
|
|
829
|
+
reject(abort?.reason ?? new Error('Mutex acquire aborted'));
|
|
810
830
|
}
|
|
811
|
-
|
|
812
|
-
|
|
831
|
+
if (abort?.aborted) {
|
|
832
|
+
return rejectAborted();
|
|
833
|
+
}
|
|
834
|
+
let holdsMutex = false;
|
|
835
|
+
const markCompleted = () => {
|
|
836
|
+
if (!holdsMutex)
|
|
837
|
+
return;
|
|
838
|
+
holdsMutex = false;
|
|
839
|
+
const waiter = this.firstWaiter;
|
|
840
|
+
if (waiter) {
|
|
841
|
+
this.deactivateWaiter(waiter);
|
|
842
|
+
// Still in critical section, but owned by next waiter now.
|
|
843
|
+
waiter.onAcquire();
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
this.inCriticalSection = false;
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
if (!this.inCriticalSection) {
|
|
850
|
+
this.inCriticalSection = true;
|
|
851
|
+
holdsMutex = true;
|
|
852
|
+
return resolve(markCompleted);
|
|
853
|
+
}
|
|
854
|
+
else {
|
|
855
|
+
let node;
|
|
856
|
+
const onAbort = () => {
|
|
857
|
+
abort?.removeEventListener('abort', onAbort);
|
|
858
|
+
if (node.isActive) {
|
|
859
|
+
this.deactivateWaiter(node);
|
|
860
|
+
rejectAborted();
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
node = this.addWaiter(() => {
|
|
864
|
+
abort?.removeEventListener('abort', onAbort);
|
|
865
|
+
holdsMutex = true;
|
|
866
|
+
resolve(markCompleted);
|
|
867
|
+
});
|
|
868
|
+
abort?.addEventListener('abort', onAbort);
|
|
813
869
|
}
|
|
814
870
|
});
|
|
815
|
-
}
|
|
871
|
+
}
|
|
872
|
+
async runExclusive(fn, abort) {
|
|
873
|
+
const returnMutex = await this.acquire(abort);
|
|
874
|
+
try {
|
|
875
|
+
return await fn();
|
|
876
|
+
}
|
|
877
|
+
finally {
|
|
878
|
+
returnMutex();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function timeoutSignal(timeout) {
|
|
883
|
+
if (timeout == null)
|
|
884
|
+
return;
|
|
885
|
+
if ('timeout' in AbortSignal)
|
|
886
|
+
return AbortSignal.timeout(timeout);
|
|
887
|
+
const controller = new AbortController();
|
|
888
|
+
setTimeout(() => controller.abort(new Error('Timeout waiting for lock')), timeout);
|
|
889
|
+
return controller.signal;
|
|
816
890
|
}
|
|
817
891
|
|
|
818
892
|
/**
|
|
@@ -824,7 +898,7 @@ class AttachmentService {
|
|
|
824
898
|
db;
|
|
825
899
|
logger;
|
|
826
900
|
tableName;
|
|
827
|
-
mutex = new
|
|
901
|
+
mutex = new Mutex();
|
|
828
902
|
context;
|
|
829
903
|
constructor(db, logger, tableName = 'attachments', archivedCacheLimit = 100) {
|
|
830
904
|
this.db = db;
|
|
@@ -861,7 +935,7 @@ class AttachmentService {
|
|
|
861
935
|
* Executes a callback with exclusive access to the attachment context.
|
|
862
936
|
*/
|
|
863
937
|
async withContext(callback) {
|
|
864
|
-
return
|
|
938
|
+
return this.mutex.runExclusive(async () => {
|
|
865
939
|
return callback(this.context);
|
|
866
940
|
});
|
|
867
941
|
}
|
|
@@ -897,9 +971,15 @@ class AttachmentQueue {
|
|
|
897
971
|
tableName;
|
|
898
972
|
/** Logger instance for diagnostic information */
|
|
899
973
|
logger;
|
|
900
|
-
/** Interval in milliseconds between periodic sync operations.
|
|
974
|
+
/** Interval in milliseconds between periodic sync operations. Acts as a polling timer to retry
|
|
975
|
+
* failed uploads/downloads, especially after the app goes offline. Default: 30000 (30 seconds) */
|
|
901
976
|
syncIntervalMs = 30 * 1000;
|
|
902
|
-
/**
|
|
977
|
+
/** Throttle duration in milliseconds for the reactive watch query on the attachments table.
|
|
978
|
+
* When attachment records change, a watch query detects the change and triggers a sync.
|
|
979
|
+
* This throttle prevents the sync from firing too rapidly when many changes happen in
|
|
980
|
+
* quick succession (e.g., bulk inserts). This is distinct from syncIntervalMs — it controls
|
|
981
|
+
* how quickly the queue reacts to changes, while syncIntervalMs controls how often it polls
|
|
982
|
+
* for retries. Default: 30 (from DEFAULT_WATCH_THROTTLE_MS) */
|
|
903
983
|
syncThrottleDuration;
|
|
904
984
|
/** Whether to automatically download remote attachments. Default: true */
|
|
905
985
|
downloadAttachments = true;
|
|
@@ -923,8 +1003,8 @@ class AttachmentQueue {
|
|
|
923
1003
|
* @param options.watchAttachments - Callback for monitoring attachment changes in your data model
|
|
924
1004
|
* @param options.tableName - Name of the table to store attachment records. Default: 'ps_attachment_queue'
|
|
925
1005
|
* @param options.logger - Logger instance. Defaults to db.logger
|
|
926
|
-
* @param options.syncIntervalMs -
|
|
927
|
-
* @param options.syncThrottleDuration - Throttle duration for
|
|
1006
|
+
* @param options.syncIntervalMs - Periodic polling interval in milliseconds for retrying failed uploads/downloads. Default: 30000
|
|
1007
|
+
* @param options.syncThrottleDuration - Throttle duration in milliseconds for the reactive watch query that detects attachment changes. Prevents rapid-fire syncs during bulk changes. Default: 30
|
|
928
1008
|
* @param options.downloadAttachments - Whether to automatically download remote attachments. Default: true
|
|
929
1009
|
* @param options.archivedCacheLimit - Maximum archived attachments before cleanup. Default: 100
|
|
930
1010
|
*/
|
|
@@ -8061,7 +8141,7 @@ function requireDist () {
|
|
|
8061
8141
|
|
|
8062
8142
|
var distExports = requireDist();
|
|
8063
8143
|
|
|
8064
|
-
var version = "1.
|
|
8144
|
+
var version = "1.50.0";
|
|
8065
8145
|
var PACKAGE = {
|
|
8066
8146
|
version: version};
|
|
8067
8147
|
|
|
@@ -10453,7 +10533,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
10453
10533
|
this._schema = schema;
|
|
10454
10534
|
this.ready = false;
|
|
10455
10535
|
this.sdkVersion = '';
|
|
10456
|
-
this.runExclusiveMutex = new
|
|
10536
|
+
this.runExclusiveMutex = new Mutex();
|
|
10457
10537
|
// Start async init
|
|
10458
10538
|
this.subscriptions = {
|
|
10459
10539
|
firstStatusMatching: (predicate, abort) => this.waitForStatus(predicate, abort),
|
|
@@ -10918,6 +10998,10 @@ SELECT * FROM crud_entries;
|
|
|
10918
10998
|
* Execute a SQL write (INSERT/UPDATE/DELETE) query
|
|
10919
10999
|
* and optionally return results.
|
|
10920
11000
|
*
|
|
11001
|
+
* When using the default client-side [JSON-based view system](https://docs.powersync.com/architecture/client-architecture#client-side-schema-and-sqlite-database-structure),
|
|
11002
|
+
* the returned result's `rowsAffected` may be `0` for successful `UPDATE` and `DELETE` statements.
|
|
11003
|
+
* Use a `RETURNING` clause and inspect `result.rows` when you need to confirm which rows changed.
|
|
11004
|
+
*
|
|
10921
11005
|
* @param sql The SQL query to execute
|
|
10922
11006
|
* @param parameters Optional array of parameters to bind to the query
|
|
10923
11007
|
* @returns The query result as an object with structured key-value pairs
|
|
@@ -11014,7 +11098,7 @@ SELECT * FROM crud_entries;
|
|
|
11014
11098
|
async readTransaction(callback, lockTimeout = DEFAULT_LOCK_TIMEOUT_MS) {
|
|
11015
11099
|
await this.waitForReady();
|
|
11016
11100
|
return this.database.readTransaction(async (tx) => {
|
|
11017
|
-
const res = await callback(
|
|
11101
|
+
const res = await callback(tx);
|
|
11018
11102
|
await tx.rollback();
|
|
11019
11103
|
return res;
|
|
11020
11104
|
}, { timeoutMs: lockTimeout });
|
|
@@ -12044,6 +12128,7 @@ exports.LogLevel = LogLevel;
|
|
|
12044
12128
|
exports.MAX_AMOUNT_OF_COLUMNS = MAX_AMOUNT_OF_COLUMNS;
|
|
12045
12129
|
exports.MAX_OP_ID = MAX_OP_ID;
|
|
12046
12130
|
exports.MEMORY_TRIGGER_CLAIM_MANAGER = MEMORY_TRIGGER_CLAIM_MANAGER;
|
|
12131
|
+
exports.Mutex = Mutex;
|
|
12047
12132
|
exports.OnChangeQueryProcessor = OnChangeQueryProcessor;
|
|
12048
12133
|
exports.OpType = OpType;
|
|
12049
12134
|
exports.OplogEntry = OplogEntry;
|
|
@@ -12077,9 +12162,9 @@ exports.isStreamingSyncCheckpointDiff = isStreamingSyncCheckpointDiff;
|
|
|
12077
12162
|
exports.isStreamingSyncCheckpointPartiallyComplete = isStreamingSyncCheckpointPartiallyComplete;
|
|
12078
12163
|
exports.isStreamingSyncData = isStreamingSyncData;
|
|
12079
12164
|
exports.isSyncNewCheckpointRequest = isSyncNewCheckpointRequest;
|
|
12080
|
-
exports.mutexRunExclusive = mutexRunExclusive;
|
|
12081
12165
|
exports.parseQuery = parseQuery;
|
|
12082
12166
|
exports.runOnSchemaChange = runOnSchemaChange;
|
|
12083
12167
|
exports.sanitizeSQL = sanitizeSQL;
|
|
12084
12168
|
exports.sanitizeUUID = sanitizeUUID;
|
|
12169
|
+
exports.timeoutSignal = timeoutSignal;
|
|
12085
12170
|
//# sourceMappingURL=bundle.node.cjs.map
|