@powersync/common 1.54.0 → 1.55.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 +374 -427
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.mjs +374 -427
- package/dist/bundle.mjs.map +1 -1
- package/dist/bundle.node.cjs +374 -273
- package/dist/bundle.node.cjs.map +1 -1
- package/dist/bundle.node.mjs +374 -273
- package/dist/bundle.node.mjs.map +1 -1
- package/dist/index.d.cts +53 -47
- package/lib/attachments/AttachmentQueue.d.ts +52 -44
- package/lib/attachments/AttachmentQueue.js.map +1 -1
- package/lib/client/AbstractPowerSyncDatabase.d.ts +0 -2
- package/lib/client/AbstractPowerSyncDatabase.js +18 -26
- package/lib/client/AbstractPowerSyncDatabase.js.map +1 -1
- package/lib/client/sync/stream/AbstractRemote.js +44 -26
- package/lib/client/sync/stream/AbstractRemote.js.map +1 -1
- package/lib/utils/async.d.ts +26 -0
- package/lib/utils/async.js +114 -27
- package/lib/utils/async.js.map +1 -1
- package/lib/utils/compatibility.d.ts +8 -0
- package/lib/utils/compatibility.js +9 -0
- package/lib/utils/compatibility.js.map +1 -0
- package/package.json +1 -2
- package/src/attachments/AttachmentQueue.ts +54 -44
- package/src/client/AbstractPowerSyncDatabase.ts +18 -29
- package/src/client/sync/stream/AbstractRemote.ts +46 -33
- package/src/utils/async.ts +136 -28
- package/src/utils/compatibility.ts +9 -0
package/src/utils/async.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { symbolAsyncIterator } from './compatibility.js';
|
|
2
|
+
import { doneResult, valueResult } from './stream_transform.js';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Throttle a function to be called at most once every "wait" milliseconds,
|
|
3
6
|
* on the trailing edge.
|
|
@@ -34,44 +37,149 @@ export interface AsyncNotifier {
|
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
export function asyncNotifier(): AsyncNotifier {
|
|
37
|
-
|
|
38
|
-
let hasPendingNotification = false;
|
|
40
|
+
const queue = new EventQueue<void>();
|
|
39
41
|
|
|
40
42
|
return {
|
|
41
43
|
notify() {
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
waitingConsumer = null;
|
|
44
|
+
if (queue.countOutstandingEvents > 0) {
|
|
45
|
+
// Already has an outstanding event, no need to buffer another one.
|
|
45
46
|
} else {
|
|
46
|
-
|
|
47
|
+
queue.notify();
|
|
47
48
|
}
|
|
48
49
|
},
|
|
49
50
|
waitForNotification(signal: AbortSignal) {
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
return queue.waitForEvent(signal);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type QueueWaiter<T> = { resolve: (event: T) => void; reject: (error: unknown) => void };
|
|
57
|
+
|
|
58
|
+
export interface QueueOptions {
|
|
59
|
+
eventDelivered?: () => void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class EventQueue<T> {
|
|
63
|
+
private waitingConsumer: QueueWaiter<T> | undefined;
|
|
64
|
+
private readonly outstandingEvents: Array<(waiter: QueueWaiter<T>) => void>;
|
|
65
|
+
|
|
66
|
+
constructor(private readonly options: QueueOptions = {}) {
|
|
67
|
+
this.outstandingEvents = [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The amount of buffered events not yet dispatched to listeners.
|
|
72
|
+
*/
|
|
73
|
+
get countOutstandingEvents(): number {
|
|
74
|
+
return this.outstandingEvents.length;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private notifyInner(dispatch: (waiter: QueueWaiter<T>) => void) {
|
|
78
|
+
const existing = this.waitingConsumer;
|
|
79
|
+
this.waitingConsumer = undefined;
|
|
80
|
+
|
|
81
|
+
const dispatchAndNotifyListeners = (waiter: QueueWaiter<T>) => {
|
|
82
|
+
dispatch(waiter);
|
|
83
|
+
this.options.eventDelivered?.();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (existing) {
|
|
87
|
+
dispatchAndNotifyListeners(existing);
|
|
88
|
+
} else {
|
|
89
|
+
this.outstandingEvents.push(dispatchAndNotifyListeners);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
notify(value: T) {
|
|
94
|
+
this.notifyInner((l) => l.resolve(value));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
notifyError(error: unknown) {
|
|
98
|
+
this.notifyInner((l) => l.reject(error));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
waitForEvent(signal: AbortSignal): Promise<T | undefined> {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
if (this.waitingConsumer != null) {
|
|
104
|
+
throw new Error('Illegal call to waitForEvent, already has a waiter.');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const complete = () => {
|
|
108
|
+
signal?.removeEventListener('abort', onAbort);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const onAbort = () => {
|
|
112
|
+
complete();
|
|
113
|
+
this.waitingConsumer = undefined;
|
|
114
|
+
resolve(undefined);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const waiter: QueueWaiter<T> = {
|
|
118
|
+
resolve: (value) => {
|
|
119
|
+
complete();
|
|
120
|
+
resolve(value);
|
|
121
|
+
},
|
|
122
|
+
reject: (error) => {
|
|
123
|
+
complete();
|
|
124
|
+
reject(error);
|
|
53
125
|
}
|
|
126
|
+
};
|
|
54
127
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
128
|
+
if (signal.aborted) {
|
|
129
|
+
resolve(undefined);
|
|
130
|
+
} else if (this.countOutstandingEvents > 0) {
|
|
131
|
+
const [event] = this.outstandingEvents.splice(0, 1);
|
|
132
|
+
event(waiter);
|
|
133
|
+
} else {
|
|
134
|
+
this.waitingConsumer = waiter;
|
|
135
|
+
signal.addEventListener('abort', onAbort);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
65
139
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Creates an async iterable backed by event queues.
|
|
142
|
+
*
|
|
143
|
+
* @param run A function invoked for every new listener. It receives a queue backing the async iterator.
|
|
144
|
+
* @param abort An additional abort signal. The `run` callback will also be aborted when `AsyncIterator.return` is
|
|
145
|
+
* called.
|
|
146
|
+
* @returns An object conforming to the async iterable protocol.
|
|
147
|
+
*/
|
|
148
|
+
static queueBasedAsyncIterable<T>(
|
|
149
|
+
run: (queue: EventQueue<T>, abort: AbortSignal) => void,
|
|
150
|
+
abort?: AbortSignal
|
|
151
|
+
): AsyncIterable<T> {
|
|
152
|
+
return {
|
|
153
|
+
[symbolAsyncIterator]: () => {
|
|
154
|
+
const queue = new EventQueue<T>();
|
|
155
|
+
const controller = new AbortController();
|
|
70
156
|
|
|
71
|
-
|
|
72
|
-
|
|
157
|
+
function dispose() {
|
|
158
|
+
controller.abort();
|
|
159
|
+
abort?.removeEventListener('abort', dispose);
|
|
73
160
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
161
|
+
|
|
162
|
+
if (abort) {
|
|
163
|
+
if (abort.aborted) {
|
|
164
|
+
controller.abort();
|
|
165
|
+
} else {
|
|
166
|
+
abort.addEventListener('abort', dispose);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
run(queue, controller.signal);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
async next(): Promise<IteratorResult<T>> {
|
|
174
|
+
const event = await queue.waitForEvent(controller.signal);
|
|
175
|
+
return event == null ? doneResult : valueResult(event);
|
|
176
|
+
},
|
|
177
|
+
async return(): Promise<IteratorResult<T>> {
|
|
178
|
+
dispose();
|
|
179
|
+
return doneResult;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
77
185
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Some JavaScript engines, in particular older versions of React Native, don't support Symbol.asyncIterator.
|
|
3
|
+
*
|
|
4
|
+
* For those, users relying on async generators typically lower them with a transpiler and [this polyfill](https://github.com/Azure/azure-sdk-for-js/blob/%40azure/core-asynciterator-polyfill_1.0.2/sdk/core/core-asynciterator-polyfill/src/index.ts#L4-L6).
|
|
5
|
+
* This definition is compatible with that polyfill, so transpiled apps can use async iterables created by the PowerSync
|
|
6
|
+
* SDK.
|
|
7
|
+
*/
|
|
8
|
+
export const symbolAsyncIterator: typeof Symbol.asyncIterator =
|
|
9
|
+
Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
|