@powersync/common 1.48.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 +270 -46
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.mjs +265 -44
- package/dist/bundle.mjs.map +1 -1
- package/dist/bundle.node.cjs +270 -45
- package/dist/bundle.node.cjs.map +1 -1
- package/dist/bundle.node.mjs +265 -43
- package/dist/bundle.node.mjs.map +1 -1
- package/dist/index.d.cts +108 -26
- 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/client/triggers/TriggerManager.d.ts +13 -1
- package/lib/client/triggers/TriggerManagerImpl.d.ts +2 -2
- package/lib/client/triggers/TriggerManagerImpl.js +19 -7
- package/lib/client/triggers/TriggerManagerImpl.js.map +1 -1
- package/lib/db/DBAdapter.d.ts +55 -9
- package/lib/db/DBAdapter.js +126 -0
- 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/client/triggers/TriggerManager.ts +15 -2
- package/src/client/triggers/TriggerManagerImpl.ts +18 -6
- package/src/db/DBAdapter.ts +167 -9
- package/src/utils/mutex.ts +121 -26
package/src/utils/mutex.ts
CHANGED
|
@@ -1,34 +1,129 @@
|
|
|
1
|
-
|
|
1
|
+
export type UnlockFn = () => void;
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* An asynchronous mutex implementation.
|
|
5
|
+
*
|
|
6
|
+
* @internal This class is meant to be used in PowerSync SDKs only, and is not part of the public API.
|
|
5
7
|
*/
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
8
|
+
export class Mutex {
|
|
9
|
+
private inCriticalSection = false;
|
|
10
|
+
|
|
11
|
+
// Linked list of waiters. We don't expect the wait list to become particularly large, and this allows removing
|
|
12
|
+
// aborted waiters from the middle of the list efficiently.
|
|
13
|
+
private firstWaiter?: MutexWaitNode;
|
|
14
|
+
private lastWaiter?: MutexWaitNode;
|
|
15
|
+
|
|
16
|
+
private addWaiter(onAcquire: () => void): MutexWaitNode {
|
|
17
|
+
const node: MutexWaitNode = {
|
|
18
|
+
isActive: true,
|
|
19
|
+
onAcquire,
|
|
20
|
+
prev: this.lastWaiter
|
|
21
|
+
};
|
|
22
|
+
if (this.lastWaiter) {
|
|
23
|
+
this.lastWaiter.next = node;
|
|
24
|
+
this.lastWaiter = node;
|
|
25
|
+
} else {
|
|
26
|
+
// First waiter
|
|
27
|
+
this.lastWaiter = this.firstWaiter = node;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return node;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private deactivateWaiter(waiter: MutexWaitNode) {
|
|
34
|
+
const { prev, next } = waiter;
|
|
35
|
+
waiter.isActive = false;
|
|
36
|
+
|
|
37
|
+
if (prev) prev.next = next;
|
|
38
|
+
if (next) next.prev = prev;
|
|
39
|
+
if (waiter == this.firstWaiter) this.firstWaiter = next;
|
|
40
|
+
if (waiter == this.lastWaiter) this.lastWaiter = prev;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
acquire(abort?: AbortSignal): Promise<UnlockFn> {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
function rejectAborted() {
|
|
46
|
+
reject(abort?.reason ?? new Error('Mutex acquire aborted'));
|
|
24
47
|
}
|
|
25
|
-
if (
|
|
48
|
+
if (abort?.aborted) {
|
|
49
|
+
return rejectAborted();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let holdsMutex = false;
|
|
53
|
+
|
|
54
|
+
const markCompleted = () => {
|
|
55
|
+
if (!holdsMutex) return;
|
|
56
|
+
holdsMutex = false;
|
|
57
|
+
|
|
58
|
+
const waiter = this.firstWaiter;
|
|
59
|
+
if (waiter) {
|
|
60
|
+
this.deactivateWaiter(waiter);
|
|
61
|
+
// Still in critical section, but owned by next waiter now.
|
|
62
|
+
waiter.onAcquire();
|
|
63
|
+
} else {
|
|
64
|
+
this.inCriticalSection = false;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
if (!this.inCriticalSection) {
|
|
69
|
+
this.inCriticalSection = true;
|
|
70
|
+
holdsMutex = true;
|
|
71
|
+
return resolve(markCompleted);
|
|
72
|
+
} else {
|
|
73
|
+
let node: MutexWaitNode;
|
|
74
|
+
|
|
75
|
+
const onAbort = () => {
|
|
76
|
+
abort?.removeEventListener('abort', onAbort);
|
|
26
77
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
78
|
+
if (node.isActive) {
|
|
79
|
+
this.deactivateWaiter(node);
|
|
80
|
+
rejectAborted();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
node = this.addWaiter(() => {
|
|
85
|
+
abort?.removeEventListener('abort', onAbort);
|
|
86
|
+
holdsMutex = true;
|
|
87
|
+
resolve(markCompleted);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
abort?.addEventListener('abort', onAbort);
|
|
31
91
|
}
|
|
32
92
|
});
|
|
33
|
-
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async runExclusive<T>(fn: () => PromiseLike<T> | T, abort?: AbortSignal): Promise<T> {
|
|
96
|
+
const returnMutex = await this.acquire(abort);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
return await fn();
|
|
100
|
+
} finally {
|
|
101
|
+
returnMutex();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface MutexWaitNode {
|
|
107
|
+
/**
|
|
108
|
+
* Whether the waiter is currently active (not aborted and not fullfilled).
|
|
109
|
+
*/
|
|
110
|
+
isActive: boolean;
|
|
111
|
+
onAcquire: () => void;
|
|
112
|
+
prev?: MutexWaitNode;
|
|
113
|
+
next?: MutexWaitNode;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Creates a signal aborting after the set timeout.
|
|
118
|
+
*/
|
|
119
|
+
export function timeoutSignal(timeout: number): AbortSignal;
|
|
120
|
+
export function timeoutSignal(timeout?: number): AbortSignal | undefined;
|
|
121
|
+
|
|
122
|
+
export function timeoutSignal(timeout?: number): AbortSignal | undefined {
|
|
123
|
+
if (timeout == null) return;
|
|
124
|
+
if ('timeout' in AbortSignal) return AbortSignal.timeout(timeout);
|
|
125
|
+
|
|
126
|
+
const controller = new AbortController();
|
|
127
|
+
setTimeout(() => controller.abort(new Error('Timeout waiting for lock')), timeout);
|
|
128
|
+
return controller.signal;
|
|
34
129
|
}
|