@powersync/common 1.54.0 → 1.56.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 +384 -434
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.mjs +384 -434
- package/dist/bundle.mjs.map +1 -1
- package/dist/bundle.node.cjs +384 -280
- package/dist/bundle.node.cjs.map +1 -1
- package/dist/bundle.node.mjs +384 -280
- package/dist/bundle.node.mjs.map +1 -1
- package/dist/index.d.cts +54 -52
- 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 +19 -27
- 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/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/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 +21 -30
- package/src/client/sync/stream/AbstractRemote.ts +46 -33
- package/src/db/crud/SyncStatus.ts +10 -7
- package/src/utils/async.ts +136 -28
- package/src/utils/compatibility.ts +9 -0
package/dist/bundle.node.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { EventIterator } from 'event-iterator';
|
|
2
1
|
import { Buffer } from 'node:buffer';
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -2138,11 +2137,7 @@ class SyncStatus {
|
|
|
2138
2137
|
*/
|
|
2139
2138
|
const replacer = (_, value) => {
|
|
2140
2139
|
if (value instanceof Error) {
|
|
2141
|
-
return
|
|
2142
|
-
name: value.name,
|
|
2143
|
-
message: value.message,
|
|
2144
|
-
stack: value.stack
|
|
2145
|
-
};
|
|
2140
|
+
return this.serializeError(value);
|
|
2146
2141
|
}
|
|
2147
2142
|
return value;
|
|
2148
2143
|
};
|
|
@@ -2185,11 +2180,18 @@ class SyncStatus {
|
|
|
2185
2180
|
if (typeof error == 'undefined') {
|
|
2186
2181
|
return undefined;
|
|
2187
2182
|
}
|
|
2188
|
-
|
|
2183
|
+
const serialized = {
|
|
2189
2184
|
name: error.name,
|
|
2190
2185
|
message: error.message,
|
|
2191
2186
|
stack: error.stack
|
|
2192
2187
|
};
|
|
2188
|
+
// `Error.cause` can be any value (the spec types it as unknown). Preserve it
|
|
2189
|
+
// so consumers reading uploadError/downloadError keep the failure context.
|
|
2190
|
+
// Recurse for Error causes so the whole chain is flattened the same way.
|
|
2191
|
+
if (typeof error.cause != 'undefined') {
|
|
2192
|
+
serialized.cause = error.cause instanceof Error ? this.serializeError(error.cause) : error.cause;
|
|
2193
|
+
}
|
|
2194
|
+
return serialized;
|
|
2193
2195
|
}
|
|
2194
2196
|
static comparePriorities(a, b) {
|
|
2195
2197
|
return b.priority - a.priority; // Reverse because higher priorities have lower numbers
|
|
@@ -2339,6 +2341,210 @@ class ControlledExecutor {
|
|
|
2339
2341
|
}
|
|
2340
2342
|
}
|
|
2341
2343
|
|
|
2344
|
+
/**
|
|
2345
|
+
* Some JavaScript engines, in particular older versions of React Native, don't support Symbol.asyncIterator.
|
|
2346
|
+
*
|
|
2347
|
+
* 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).
|
|
2348
|
+
* This definition is compatible with that polyfill, so transpiled apps can use async iterables created by the PowerSync
|
|
2349
|
+
* SDK.
|
|
2350
|
+
*/
|
|
2351
|
+
const symbolAsyncIterator = Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
|
|
2352
|
+
|
|
2353
|
+
const doneResult = { done: true, value: undefined };
|
|
2354
|
+
function valueResult(value) {
|
|
2355
|
+
return { done: false, value };
|
|
2356
|
+
}
|
|
2357
|
+
/**
|
|
2358
|
+
* Expands a source async iterator by allowing to inject events asynchronously.
|
|
2359
|
+
*
|
|
2360
|
+
* The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
|
|
2361
|
+
* events are dropped once the main iterator completes, but are otherwise forwarded.
|
|
2362
|
+
*
|
|
2363
|
+
* The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
|
|
2364
|
+
* in response to a `next()` call from downstream if no pending injected events can be dispatched.
|
|
2365
|
+
*/
|
|
2366
|
+
function injectable(source) {
|
|
2367
|
+
let sourceIsDone = false;
|
|
2368
|
+
let waiter = undefined; // An active, waiting next() call.
|
|
2369
|
+
// A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
|
|
2370
|
+
let pendingSourceEvent = null;
|
|
2371
|
+
let sourceFetchInFlight = false;
|
|
2372
|
+
let pendingInjectedEvents = [];
|
|
2373
|
+
const consumeWaiter = () => {
|
|
2374
|
+
const pending = waiter;
|
|
2375
|
+
waiter = undefined;
|
|
2376
|
+
return pending;
|
|
2377
|
+
};
|
|
2378
|
+
const fetchFromSource = () => {
|
|
2379
|
+
const resolveWaiter = (propagate) => {
|
|
2380
|
+
sourceFetchInFlight = false;
|
|
2381
|
+
const active = consumeWaiter();
|
|
2382
|
+
if (active) {
|
|
2383
|
+
propagate(active);
|
|
2384
|
+
}
|
|
2385
|
+
else {
|
|
2386
|
+
pendingSourceEvent = propagate;
|
|
2387
|
+
}
|
|
2388
|
+
};
|
|
2389
|
+
sourceFetchInFlight = true;
|
|
2390
|
+
const nextFromSource = source.next();
|
|
2391
|
+
nextFromSource.then((value) => {
|
|
2392
|
+
sourceIsDone = value.done == true;
|
|
2393
|
+
resolveWaiter((w) => w.resolve(value));
|
|
2394
|
+
}, (error) => {
|
|
2395
|
+
resolveWaiter((w) => w.reject(error));
|
|
2396
|
+
});
|
|
2397
|
+
};
|
|
2398
|
+
return {
|
|
2399
|
+
next: () => {
|
|
2400
|
+
return new Promise((resolve, reject) => {
|
|
2401
|
+
// First priority: Dispatch ready upstream events.
|
|
2402
|
+
if (sourceIsDone) {
|
|
2403
|
+
return resolve(doneResult);
|
|
2404
|
+
}
|
|
2405
|
+
if (pendingSourceEvent) {
|
|
2406
|
+
pendingSourceEvent({ resolve, reject });
|
|
2407
|
+
pendingSourceEvent = null;
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
// Second priority: Dispatch injected events
|
|
2411
|
+
if (pendingInjectedEvents.length) {
|
|
2412
|
+
return resolve(valueResult(pendingInjectedEvents.shift()));
|
|
2413
|
+
}
|
|
2414
|
+
// Nothing pending? Fetch from source
|
|
2415
|
+
waiter = { resolve, reject };
|
|
2416
|
+
if (!sourceFetchInFlight) {
|
|
2417
|
+
fetchFromSource();
|
|
2418
|
+
}
|
|
2419
|
+
});
|
|
2420
|
+
},
|
|
2421
|
+
inject: (event) => {
|
|
2422
|
+
const pending = consumeWaiter();
|
|
2423
|
+
if (pending != null) {
|
|
2424
|
+
pending.resolve(valueResult(event));
|
|
2425
|
+
}
|
|
2426
|
+
else {
|
|
2427
|
+
pendingInjectedEvents.push(event);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
/**
|
|
2433
|
+
* Splits a byte stream at line endings, emitting each line as a string.
|
|
2434
|
+
*/
|
|
2435
|
+
function extractJsonLines(source, decoder) {
|
|
2436
|
+
let buffer = '';
|
|
2437
|
+
const pendingLines = [];
|
|
2438
|
+
let isFinalEvent = false;
|
|
2439
|
+
return {
|
|
2440
|
+
next: async () => {
|
|
2441
|
+
while (true) {
|
|
2442
|
+
if (isFinalEvent) {
|
|
2443
|
+
return doneResult;
|
|
2444
|
+
}
|
|
2445
|
+
{
|
|
2446
|
+
const first = pendingLines.shift();
|
|
2447
|
+
if (first) {
|
|
2448
|
+
return { done: false, value: first };
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
const { done, value } = await source.next();
|
|
2452
|
+
if (done) {
|
|
2453
|
+
const remaining = buffer.trim();
|
|
2454
|
+
if (remaining.length != 0) {
|
|
2455
|
+
isFinalEvent = true;
|
|
2456
|
+
return { done: false, value: remaining };
|
|
2457
|
+
}
|
|
2458
|
+
return doneResult;
|
|
2459
|
+
}
|
|
2460
|
+
const data = decoder.decode(value, { stream: true });
|
|
2461
|
+
buffer += data;
|
|
2462
|
+
const lines = buffer.split('\n');
|
|
2463
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
2464
|
+
const l = lines[i].trim();
|
|
2465
|
+
if (l.length > 0) {
|
|
2466
|
+
pendingLines.push(l);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
buffer = lines[lines.length - 1];
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
/**
|
|
2475
|
+
* Splits a concatenated stream of BSON objects by emitting individual objects.
|
|
2476
|
+
*/
|
|
2477
|
+
function extractBsonObjects(source) {
|
|
2478
|
+
// Fully read but not emitted yet.
|
|
2479
|
+
const completedObjects = [];
|
|
2480
|
+
// Whether source has returned { done: true }. We do the same once completed objects have been emitted.
|
|
2481
|
+
let isDone = false;
|
|
2482
|
+
const lengthBuffer = new DataView(new ArrayBuffer(4));
|
|
2483
|
+
let objectBody = null;
|
|
2484
|
+
// If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
|
|
2485
|
+
// If we're consuming a document, the bytes remaining.
|
|
2486
|
+
let remainingLength = 4;
|
|
2487
|
+
return {
|
|
2488
|
+
async next() {
|
|
2489
|
+
while (true) {
|
|
2490
|
+
// Before fetching new data from upstream, return completed objects.
|
|
2491
|
+
if (completedObjects.length) {
|
|
2492
|
+
return valueResult(completedObjects.shift());
|
|
2493
|
+
}
|
|
2494
|
+
if (isDone) {
|
|
2495
|
+
return doneResult;
|
|
2496
|
+
}
|
|
2497
|
+
const upstreamEvent = await source.next();
|
|
2498
|
+
if (upstreamEvent.done) {
|
|
2499
|
+
isDone = true;
|
|
2500
|
+
if (objectBody || remainingLength != 4) {
|
|
2501
|
+
throw new Error('illegal end of stream in BSON object');
|
|
2502
|
+
}
|
|
2503
|
+
return doneResult;
|
|
2504
|
+
}
|
|
2505
|
+
const chunk = upstreamEvent.value;
|
|
2506
|
+
for (let i = 0; i < chunk.length;) {
|
|
2507
|
+
const availableInData = chunk.length - i;
|
|
2508
|
+
if (objectBody) {
|
|
2509
|
+
// We're in the middle of reading a BSON document.
|
|
2510
|
+
const bytesToRead = Math.min(availableInData, remainingLength);
|
|
2511
|
+
const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
|
|
2512
|
+
objectBody.set(copySource, objectBody.length - remainingLength);
|
|
2513
|
+
i += bytesToRead;
|
|
2514
|
+
remainingLength -= bytesToRead;
|
|
2515
|
+
if (remainingLength == 0) {
|
|
2516
|
+
completedObjects.push(objectBody);
|
|
2517
|
+
// Prepare to read another document, starting with its length
|
|
2518
|
+
objectBody = null;
|
|
2519
|
+
remainingLength = 4;
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
else {
|
|
2523
|
+
// Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
|
|
2524
|
+
const bytesToRead = Math.min(availableInData, remainingLength);
|
|
2525
|
+
for (let j = 0; j < bytesToRead; j++) {
|
|
2526
|
+
lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
|
|
2527
|
+
}
|
|
2528
|
+
i += bytesToRead;
|
|
2529
|
+
remainingLength -= bytesToRead;
|
|
2530
|
+
if (remainingLength == 0) {
|
|
2531
|
+
// Transition from reading length header to reading document. Subtracting 4 because the length of the
|
|
2532
|
+
// header is included in length.
|
|
2533
|
+
const length = lengthBuffer.getInt32(0, true /* little endian */);
|
|
2534
|
+
remainingLength = length - 4;
|
|
2535
|
+
if (remainingLength < 1) {
|
|
2536
|
+
throw new Error(`invalid length for bson: ${length}`);
|
|
2537
|
+
}
|
|
2538
|
+
objectBody = new Uint8Array(length);
|
|
2539
|
+
new DataView(objectBody.buffer).setInt32(0, length, true);
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2342
2548
|
/**
|
|
2343
2549
|
* Throttle a function to be called at most once every "wait" milliseconds,
|
|
2344
2550
|
* on the trailing edge.
|
|
@@ -2358,45 +2564,128 @@ function throttleTrailing(func, wait) {
|
|
|
2358
2564
|
};
|
|
2359
2565
|
}
|
|
2360
2566
|
function asyncNotifier() {
|
|
2361
|
-
|
|
2362
|
-
let hasPendingNotification = false;
|
|
2567
|
+
const queue = new EventQueue();
|
|
2363
2568
|
return {
|
|
2364
2569
|
notify() {
|
|
2365
|
-
if (
|
|
2366
|
-
waitingConsumer();
|
|
2367
|
-
waitingConsumer = null;
|
|
2368
|
-
}
|
|
2570
|
+
if (queue.countOutstandingEvents > 0) ;
|
|
2369
2571
|
else {
|
|
2370
|
-
|
|
2572
|
+
queue.notify();
|
|
2371
2573
|
}
|
|
2372
2574
|
},
|
|
2373
2575
|
waitForNotification(signal) {
|
|
2374
|
-
return
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2576
|
+
return queue.waitForEvent(signal);
|
|
2577
|
+
}
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
class EventQueue {
|
|
2581
|
+
options;
|
|
2582
|
+
waitingConsumer;
|
|
2583
|
+
outstandingEvents;
|
|
2584
|
+
constructor(options = {}) {
|
|
2585
|
+
this.options = options;
|
|
2586
|
+
this.outstandingEvents = [];
|
|
2587
|
+
}
|
|
2588
|
+
/**
|
|
2589
|
+
* The amount of buffered events not yet dispatched to listeners.
|
|
2590
|
+
*/
|
|
2591
|
+
get countOutstandingEvents() {
|
|
2592
|
+
return this.outstandingEvents.length;
|
|
2593
|
+
}
|
|
2594
|
+
notifyInner(dispatch) {
|
|
2595
|
+
const existing = this.waitingConsumer;
|
|
2596
|
+
this.waitingConsumer = undefined;
|
|
2597
|
+
const dispatchAndNotifyListeners = (waiter) => {
|
|
2598
|
+
dispatch(waiter);
|
|
2599
|
+
this.options.eventDelivered?.();
|
|
2600
|
+
};
|
|
2601
|
+
if (existing) {
|
|
2602
|
+
dispatchAndNotifyListeners(existing);
|
|
2603
|
+
}
|
|
2604
|
+
else {
|
|
2605
|
+
this.outstandingEvents.push(dispatchAndNotifyListeners);
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
notify(value) {
|
|
2609
|
+
this.notifyInner((l) => l.resolve(value));
|
|
2610
|
+
}
|
|
2611
|
+
notifyError(error) {
|
|
2612
|
+
this.notifyInner((l) => l.reject(error));
|
|
2613
|
+
}
|
|
2614
|
+
waitForEvent(signal) {
|
|
2615
|
+
return new Promise((resolve, reject) => {
|
|
2616
|
+
if (this.waitingConsumer != null) {
|
|
2617
|
+
throw new Error('Illegal call to waitForEvent, already has a waiter.');
|
|
2618
|
+
}
|
|
2619
|
+
const complete = () => {
|
|
2620
|
+
signal?.removeEventListener('abort', onAbort);
|
|
2621
|
+
};
|
|
2622
|
+
const onAbort = () => {
|
|
2623
|
+
complete();
|
|
2624
|
+
this.waitingConsumer = undefined;
|
|
2625
|
+
resolve(undefined);
|
|
2626
|
+
};
|
|
2627
|
+
const waiter = {
|
|
2628
|
+
resolve: (value) => {
|
|
2629
|
+
complete();
|
|
2630
|
+
resolve(value);
|
|
2631
|
+
},
|
|
2632
|
+
reject: (error) => {
|
|
2633
|
+
complete();
|
|
2634
|
+
reject(error);
|
|
2380
2635
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2636
|
+
};
|
|
2637
|
+
if (signal.aborted) {
|
|
2638
|
+
resolve(undefined);
|
|
2639
|
+
}
|
|
2640
|
+
else if (this.countOutstandingEvents > 0) {
|
|
2641
|
+
const [event] = this.outstandingEvents.splice(0, 1);
|
|
2642
|
+
event(waiter);
|
|
2643
|
+
}
|
|
2644
|
+
else {
|
|
2645
|
+
this.waitingConsumer = waiter;
|
|
2646
|
+
signal.addEventListener('abort', onAbort);
|
|
2647
|
+
}
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2650
|
+
/**
|
|
2651
|
+
* Creates an async iterable backed by event queues.
|
|
2652
|
+
*
|
|
2653
|
+
* @param run A function invoked for every new listener. It receives a queue backing the async iterator.
|
|
2654
|
+
* @param abort An additional abort signal. The `run` callback will also be aborted when `AsyncIterator.return` is
|
|
2655
|
+
* called.
|
|
2656
|
+
* @returns An object conforming to the async iterable protocol.
|
|
2657
|
+
*/
|
|
2658
|
+
static queueBasedAsyncIterable(run, abort) {
|
|
2659
|
+
return {
|
|
2660
|
+
[symbolAsyncIterator]: () => {
|
|
2661
|
+
const queue = new EventQueue();
|
|
2662
|
+
const controller = new AbortController();
|
|
2663
|
+
function dispose() {
|
|
2664
|
+
controller.abort();
|
|
2665
|
+
abort?.removeEventListener('abort', dispose);
|
|
2384
2666
|
}
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
resolve();
|
|
2667
|
+
if (abort) {
|
|
2668
|
+
if (abort.aborted) {
|
|
2669
|
+
controller.abort();
|
|
2389
2670
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
resolve();
|
|
2671
|
+
else {
|
|
2672
|
+
abort.addEventListener('abort', dispose);
|
|
2393
2673
|
}
|
|
2394
|
-
waitingConsumer = complete;
|
|
2395
|
-
signal.addEventListener('abort', onAbort);
|
|
2396
2674
|
}
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2675
|
+
run(queue, controller.signal);
|
|
2676
|
+
return {
|
|
2677
|
+
async next() {
|
|
2678
|
+
const event = await queue.waitForEvent(controller.signal);
|
|
2679
|
+
return event == null ? doneResult : valueResult(event);
|
|
2680
|
+
},
|
|
2681
|
+
async return() {
|
|
2682
|
+
dispose();
|
|
2683
|
+
return doneResult;
|
|
2684
|
+
}
|
|
2685
|
+
};
|
|
2686
|
+
}
|
|
2687
|
+
};
|
|
2688
|
+
}
|
|
2400
2689
|
}
|
|
2401
2690
|
|
|
2402
2691
|
/**
|
|
@@ -8256,7 +8545,7 @@ function requireDist () {
|
|
|
8256
8545
|
|
|
8257
8546
|
var distExports = requireDist();
|
|
8258
8547
|
|
|
8259
|
-
var version = "1.
|
|
8548
|
+
var version = "1.56.0";
|
|
8260
8549
|
var PACKAGE = {
|
|
8261
8550
|
version: version};
|
|
8262
8551
|
|
|
@@ -8423,201 +8712,6 @@ class WebsocketClientTransport {
|
|
|
8423
8712
|
}
|
|
8424
8713
|
}
|
|
8425
8714
|
|
|
8426
|
-
const doneResult = { done: true, value: undefined };
|
|
8427
|
-
function valueResult(value) {
|
|
8428
|
-
return { done: false, value };
|
|
8429
|
-
}
|
|
8430
|
-
/**
|
|
8431
|
-
* Expands a source async iterator by allowing to inject events asynchronously.
|
|
8432
|
-
*
|
|
8433
|
-
* The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
|
|
8434
|
-
* events are dropped once the main iterator completes, but are otherwise forwarded.
|
|
8435
|
-
*
|
|
8436
|
-
* The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
|
|
8437
|
-
* in response to a `next()` call from downstream if no pending injected events can be dispatched.
|
|
8438
|
-
*/
|
|
8439
|
-
function injectable(source) {
|
|
8440
|
-
let sourceIsDone = false;
|
|
8441
|
-
let waiter = undefined; // An active, waiting next() call.
|
|
8442
|
-
// A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
|
|
8443
|
-
let pendingSourceEvent = null;
|
|
8444
|
-
let sourceFetchInFlight = false;
|
|
8445
|
-
let pendingInjectedEvents = [];
|
|
8446
|
-
const consumeWaiter = () => {
|
|
8447
|
-
const pending = waiter;
|
|
8448
|
-
waiter = undefined;
|
|
8449
|
-
return pending;
|
|
8450
|
-
};
|
|
8451
|
-
const fetchFromSource = () => {
|
|
8452
|
-
const resolveWaiter = (propagate) => {
|
|
8453
|
-
sourceFetchInFlight = false;
|
|
8454
|
-
const active = consumeWaiter();
|
|
8455
|
-
if (active) {
|
|
8456
|
-
propagate(active);
|
|
8457
|
-
}
|
|
8458
|
-
else {
|
|
8459
|
-
pendingSourceEvent = propagate;
|
|
8460
|
-
}
|
|
8461
|
-
};
|
|
8462
|
-
sourceFetchInFlight = true;
|
|
8463
|
-
const nextFromSource = source.next();
|
|
8464
|
-
nextFromSource.then((value) => {
|
|
8465
|
-
sourceIsDone = value.done == true;
|
|
8466
|
-
resolveWaiter((w) => w.resolve(value));
|
|
8467
|
-
}, (error) => {
|
|
8468
|
-
resolveWaiter((w) => w.reject(error));
|
|
8469
|
-
});
|
|
8470
|
-
};
|
|
8471
|
-
return {
|
|
8472
|
-
next: () => {
|
|
8473
|
-
return new Promise((resolve, reject) => {
|
|
8474
|
-
// First priority: Dispatch ready upstream events.
|
|
8475
|
-
if (sourceIsDone) {
|
|
8476
|
-
return resolve(doneResult);
|
|
8477
|
-
}
|
|
8478
|
-
if (pendingSourceEvent) {
|
|
8479
|
-
pendingSourceEvent({ resolve, reject });
|
|
8480
|
-
pendingSourceEvent = null;
|
|
8481
|
-
return;
|
|
8482
|
-
}
|
|
8483
|
-
// Second priority: Dispatch injected events
|
|
8484
|
-
if (pendingInjectedEvents.length) {
|
|
8485
|
-
return resolve(valueResult(pendingInjectedEvents.shift()));
|
|
8486
|
-
}
|
|
8487
|
-
// Nothing pending? Fetch from source
|
|
8488
|
-
waiter = { resolve, reject };
|
|
8489
|
-
if (!sourceFetchInFlight) {
|
|
8490
|
-
fetchFromSource();
|
|
8491
|
-
}
|
|
8492
|
-
});
|
|
8493
|
-
},
|
|
8494
|
-
inject: (event) => {
|
|
8495
|
-
const pending = consumeWaiter();
|
|
8496
|
-
if (pending != null) {
|
|
8497
|
-
pending.resolve(valueResult(event));
|
|
8498
|
-
}
|
|
8499
|
-
else {
|
|
8500
|
-
pendingInjectedEvents.push(event);
|
|
8501
|
-
}
|
|
8502
|
-
}
|
|
8503
|
-
};
|
|
8504
|
-
}
|
|
8505
|
-
/**
|
|
8506
|
-
* Splits a byte stream at line endings, emitting each line as a string.
|
|
8507
|
-
*/
|
|
8508
|
-
function extractJsonLines(source, decoder) {
|
|
8509
|
-
let buffer = '';
|
|
8510
|
-
const pendingLines = [];
|
|
8511
|
-
let isFinalEvent = false;
|
|
8512
|
-
return {
|
|
8513
|
-
next: async () => {
|
|
8514
|
-
while (true) {
|
|
8515
|
-
if (isFinalEvent) {
|
|
8516
|
-
return doneResult;
|
|
8517
|
-
}
|
|
8518
|
-
{
|
|
8519
|
-
const first = pendingLines.shift();
|
|
8520
|
-
if (first) {
|
|
8521
|
-
return { done: false, value: first };
|
|
8522
|
-
}
|
|
8523
|
-
}
|
|
8524
|
-
const { done, value } = await source.next();
|
|
8525
|
-
if (done) {
|
|
8526
|
-
const remaining = buffer.trim();
|
|
8527
|
-
if (remaining.length != 0) {
|
|
8528
|
-
isFinalEvent = true;
|
|
8529
|
-
return { done: false, value: remaining };
|
|
8530
|
-
}
|
|
8531
|
-
return doneResult;
|
|
8532
|
-
}
|
|
8533
|
-
const data = decoder.decode(value, { stream: true });
|
|
8534
|
-
buffer += data;
|
|
8535
|
-
const lines = buffer.split('\n');
|
|
8536
|
-
for (let i = 0; i < lines.length - 1; i++) {
|
|
8537
|
-
const l = lines[i].trim();
|
|
8538
|
-
if (l.length > 0) {
|
|
8539
|
-
pendingLines.push(l);
|
|
8540
|
-
}
|
|
8541
|
-
}
|
|
8542
|
-
buffer = lines[lines.length - 1];
|
|
8543
|
-
}
|
|
8544
|
-
}
|
|
8545
|
-
};
|
|
8546
|
-
}
|
|
8547
|
-
/**
|
|
8548
|
-
* Splits a concatenated stream of BSON objects by emitting individual objects.
|
|
8549
|
-
*/
|
|
8550
|
-
function extractBsonObjects(source) {
|
|
8551
|
-
// Fully read but not emitted yet.
|
|
8552
|
-
const completedObjects = [];
|
|
8553
|
-
// Whether source has returned { done: true }. We do the same once completed objects have been emitted.
|
|
8554
|
-
let isDone = false;
|
|
8555
|
-
const lengthBuffer = new DataView(new ArrayBuffer(4));
|
|
8556
|
-
let objectBody = null;
|
|
8557
|
-
// If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
|
|
8558
|
-
// If we're consuming a document, the bytes remaining.
|
|
8559
|
-
let remainingLength = 4;
|
|
8560
|
-
return {
|
|
8561
|
-
async next() {
|
|
8562
|
-
while (true) {
|
|
8563
|
-
// Before fetching new data from upstream, return completed objects.
|
|
8564
|
-
if (completedObjects.length) {
|
|
8565
|
-
return valueResult(completedObjects.shift());
|
|
8566
|
-
}
|
|
8567
|
-
if (isDone) {
|
|
8568
|
-
return doneResult;
|
|
8569
|
-
}
|
|
8570
|
-
const upstreamEvent = await source.next();
|
|
8571
|
-
if (upstreamEvent.done) {
|
|
8572
|
-
isDone = true;
|
|
8573
|
-
if (objectBody || remainingLength != 4) {
|
|
8574
|
-
throw new Error('illegal end of stream in BSON object');
|
|
8575
|
-
}
|
|
8576
|
-
return doneResult;
|
|
8577
|
-
}
|
|
8578
|
-
const chunk = upstreamEvent.value;
|
|
8579
|
-
for (let i = 0; i < chunk.length;) {
|
|
8580
|
-
const availableInData = chunk.length - i;
|
|
8581
|
-
if (objectBody) {
|
|
8582
|
-
// We're in the middle of reading a BSON document.
|
|
8583
|
-
const bytesToRead = Math.min(availableInData, remainingLength);
|
|
8584
|
-
const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
|
|
8585
|
-
objectBody.set(copySource, objectBody.length - remainingLength);
|
|
8586
|
-
i += bytesToRead;
|
|
8587
|
-
remainingLength -= bytesToRead;
|
|
8588
|
-
if (remainingLength == 0) {
|
|
8589
|
-
completedObjects.push(objectBody);
|
|
8590
|
-
// Prepare to read another document, starting with its length
|
|
8591
|
-
objectBody = null;
|
|
8592
|
-
remainingLength = 4;
|
|
8593
|
-
}
|
|
8594
|
-
}
|
|
8595
|
-
else {
|
|
8596
|
-
// Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
|
|
8597
|
-
const bytesToRead = Math.min(availableInData, remainingLength);
|
|
8598
|
-
for (let j = 0; j < bytesToRead; j++) {
|
|
8599
|
-
lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
|
|
8600
|
-
}
|
|
8601
|
-
i += bytesToRead;
|
|
8602
|
-
remainingLength -= bytesToRead;
|
|
8603
|
-
if (remainingLength == 0) {
|
|
8604
|
-
// Transition from reading length header to reading document. Subtracting 4 because the length of the
|
|
8605
|
-
// header is included in length.
|
|
8606
|
-
const length = lengthBuffer.getInt32(0, true /* little endian */);
|
|
8607
|
-
remainingLength = length - 4;
|
|
8608
|
-
if (remainingLength < 1) {
|
|
8609
|
-
throw new Error(`invalid length for bson: ${length}`);
|
|
8610
|
-
}
|
|
8611
|
-
objectBody = new Uint8Array(length);
|
|
8612
|
-
new DataView(objectBody.buffer).setInt32(0, length, true);
|
|
8613
|
-
}
|
|
8614
|
-
}
|
|
8615
|
-
}
|
|
8616
|
-
}
|
|
8617
|
-
}
|
|
8618
|
-
};
|
|
8619
|
-
}
|
|
8620
|
-
|
|
8621
8715
|
const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
|
|
8622
8716
|
const POWERSYNC_JS_VERSION = PACKAGE.version;
|
|
8623
8717
|
const SYNC_QUEUE_REQUEST_HIGH_WATER = 10;
|
|
@@ -8836,8 +8930,19 @@ class AbstractRemote {
|
|
|
8836
8930
|
let pendingSocket = null;
|
|
8837
8931
|
let keepAliveTimeout;
|
|
8838
8932
|
let rsocket = null;
|
|
8839
|
-
let
|
|
8933
|
+
let paused = false;
|
|
8934
|
+
const queue = new EventQueue({
|
|
8935
|
+
eventDelivered: () => {
|
|
8936
|
+
if (queue.countOutstandingEvents <= SYNC_QUEUE_REQUEST_LOW_WATER) {
|
|
8937
|
+
paused = false;
|
|
8938
|
+
requestMore();
|
|
8939
|
+
}
|
|
8940
|
+
}
|
|
8941
|
+
});
|
|
8840
8942
|
let didClose = false;
|
|
8943
|
+
let connectionEstablished = false;
|
|
8944
|
+
let pendingEventsCount = syncQueueRequestSize;
|
|
8945
|
+
let res = null;
|
|
8841
8946
|
const abortRequest = () => {
|
|
8842
8947
|
if (didClose) {
|
|
8843
8948
|
return;
|
|
@@ -8850,10 +8955,23 @@ class AbstractRemote {
|
|
|
8850
8955
|
if (rsocket) {
|
|
8851
8956
|
rsocket.close();
|
|
8852
8957
|
}
|
|
8853
|
-
|
|
8854
|
-
|
|
8855
|
-
|
|
8958
|
+
// Send a bogus event to the queue to ensure a pending listener gets woken up. We check for didClose and would
|
|
8959
|
+
// return a doneEvent.
|
|
8960
|
+
queue.notify(null);
|
|
8856
8961
|
};
|
|
8962
|
+
function push(event) {
|
|
8963
|
+
queue.notify(event);
|
|
8964
|
+
if (queue.countOutstandingEvents >= SYNC_QUEUE_REQUEST_HIGH_WATER) {
|
|
8965
|
+
paused = true;
|
|
8966
|
+
}
|
|
8967
|
+
}
|
|
8968
|
+
function requestMore() {
|
|
8969
|
+
const delta = syncQueueRequestSize - pendingEventsCount;
|
|
8970
|
+
if (!paused && delta > 0) {
|
|
8971
|
+
res?.request(delta);
|
|
8972
|
+
pendingEventsCount = syncQueueRequestSize;
|
|
8973
|
+
}
|
|
8974
|
+
}
|
|
8857
8975
|
// Handle upstream abort
|
|
8858
8976
|
if (options.abortSignal.aborted) {
|
|
8859
8977
|
throw new AbortOperation('Connection request aborted');
|
|
@@ -8908,25 +9026,19 @@ class AbstractRemote {
|
|
|
8908
9026
|
// Helps to prevent double close scenarios
|
|
8909
9027
|
rsocket.onClose(() => (rsocket = null));
|
|
8910
9028
|
return await new Promise((resolve, reject) => {
|
|
8911
|
-
|
|
8912
|
-
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
9029
|
+
const queueAsIterator = {
|
|
9030
|
+
next: async () => {
|
|
9031
|
+
if (didClose)
|
|
9032
|
+
return doneResult;
|
|
9033
|
+
const notification = await queue.waitForEvent(options.abortSignal);
|
|
9034
|
+
if (didClose) {
|
|
9035
|
+
return doneResult;
|
|
9036
|
+
}
|
|
9037
|
+
else {
|
|
9038
|
+
return valueResult(notification);
|
|
9039
|
+
}
|
|
8920
9040
|
}
|
|
8921
|
-
}
|
|
8922
|
-
const events = new EventIterator((q) => {
|
|
8923
|
-
queue = q;
|
|
8924
|
-
q.on('highWater', () => (paused = true));
|
|
8925
|
-
q.on('lowWater', () => {
|
|
8926
|
-
paused = false;
|
|
8927
|
-
requestMore();
|
|
8928
|
-
});
|
|
8929
|
-
}, { highWaterMark: SYNC_QUEUE_REQUEST_HIGH_WATER, lowWaterMark: SYNC_QUEUE_REQUEST_LOW_WATER })[Symbol.asyncIterator]();
|
|
9041
|
+
};
|
|
8930
9042
|
res = rsocket.requestStream({
|
|
8931
9043
|
data: toBuffer(options.data),
|
|
8932
9044
|
metadata: toBuffer({
|
|
@@ -8962,11 +9074,11 @@ class AbstractRemote {
|
|
|
8962
9074
|
// The connection is active
|
|
8963
9075
|
if (!connectionEstablished) {
|
|
8964
9076
|
connectionEstablished = true;
|
|
8965
|
-
resolve(
|
|
9077
|
+
resolve(queueAsIterator);
|
|
8966
9078
|
}
|
|
8967
9079
|
const { data } = payload;
|
|
8968
9080
|
if (data) {
|
|
8969
|
-
|
|
9081
|
+
push(data);
|
|
8970
9082
|
}
|
|
8971
9083
|
// Less events are now pending
|
|
8972
9084
|
pendingEventsCount--;
|
|
@@ -10564,7 +10676,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
10564
10676
|
this.logger.warn('Schema validation failed. Unexpected behaviour could occur', ex);
|
|
10565
10677
|
}
|
|
10566
10678
|
this._schema = schema;
|
|
10567
|
-
await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
|
|
10679
|
+
await this.database.writeTransaction((tx) => tx.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]));
|
|
10568
10680
|
await this.database.refreshSchema();
|
|
10569
10681
|
this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
|
|
10570
10682
|
}
|
|
@@ -10734,7 +10846,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
10734
10846
|
* @returns A transaction of CRUD operations to upload, or null if there are none
|
|
10735
10847
|
*/
|
|
10736
10848
|
async getNextCrudTransaction() {
|
|
10737
|
-
const iterator = this.getCrudTransactions()[
|
|
10849
|
+
const iterator = this.getCrudTransactions()[symbolAsyncIterator]();
|
|
10738
10850
|
return (await iterator.next()).value;
|
|
10739
10851
|
}
|
|
10740
10852
|
/**
|
|
@@ -10770,7 +10882,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
10770
10882
|
*/
|
|
10771
10883
|
getCrudTransactions() {
|
|
10772
10884
|
return {
|
|
10773
|
-
[
|
|
10885
|
+
[symbolAsyncIterator]: () => {
|
|
10774
10886
|
let lastCrudItemId = -1;
|
|
10775
10887
|
const sql = `
|
|
10776
10888
|
WITH RECURSIVE crud_entries AS (
|
|
@@ -11084,20 +11196,17 @@ SELECT * FROM crud_entries;
|
|
|
11084
11196
|
* @returns An AsyncIterable that yields QueryResults whenever the data changes
|
|
11085
11197
|
*/
|
|
11086
11198
|
watchWithAsyncGenerator(sql, parameters, options) {
|
|
11087
|
-
return
|
|
11199
|
+
return EventQueue.queueBasedAsyncIterable((queue, abort) => {
|
|
11088
11200
|
const handler = {
|
|
11089
11201
|
onResult: (result) => {
|
|
11090
|
-
|
|
11202
|
+
queue.notify(result);
|
|
11091
11203
|
},
|
|
11092
11204
|
onError: (error) => {
|
|
11093
|
-
|
|
11205
|
+
queue.notifyError(error);
|
|
11094
11206
|
}
|
|
11095
11207
|
};
|
|
11096
|
-
this.watchWithCallback(sql, parameters, handler, options);
|
|
11097
|
-
|
|
11098
|
-
eventOptions.stop();
|
|
11099
|
-
});
|
|
11100
|
-
});
|
|
11208
|
+
this.watchWithCallback(sql, parameters, handler, { ...options, signal: abort });
|
|
11209
|
+
}, options?.signal);
|
|
11101
11210
|
}
|
|
11102
11211
|
/**
|
|
11103
11212
|
* Resolves the list of tables that are used in a SQL query.
|
|
@@ -11187,28 +11296,23 @@ SELECT * FROM crud_entries;
|
|
|
11187
11296
|
* This is preferred over {@link AbstractPowerSyncDatabase.watchWithAsyncGenerator} when multiple queries need to be
|
|
11188
11297
|
* performed together when data is changed.
|
|
11189
11298
|
*
|
|
11190
|
-
* Note: do not declare this as `async *onChange` as it will not work in React Native.
|
|
11191
|
-
*
|
|
11192
11299
|
* @param options - Options for configuring watch behavior
|
|
11193
11300
|
* @returns An AsyncIterable that yields change events whenever the specified tables change
|
|
11194
11301
|
*/
|
|
11302
|
+
// Note: do not declare this as `async *onChange` as it will not work in React Native.
|
|
11195
11303
|
onChangeWithAsyncGenerator(options) {
|
|
11196
|
-
|
|
11197
|
-
|
|
11198
|
-
const dispose = this.onChangeWithCallback({
|
|
11304
|
+
return EventQueue.queueBasedAsyncIterable((queue, abort) => {
|
|
11305
|
+
this.onChangeWithCallback({
|
|
11199
11306
|
onChange: (event) => {
|
|
11200
|
-
|
|
11307
|
+
queue.notify(event);
|
|
11201
11308
|
},
|
|
11202
11309
|
onError: (error) => {
|
|
11203
|
-
|
|
11310
|
+
queue.notifyError(error);
|
|
11204
11311
|
}
|
|
11205
|
-
}, options);
|
|
11206
|
-
|
|
11207
|
-
|
|
11208
|
-
|
|
11209
|
-
});
|
|
11210
|
-
return () => dispose();
|
|
11211
|
-
});
|
|
11312
|
+
}, { ...options, signal: abort });
|
|
11313
|
+
// Note: We don't have to track the dispose function returned by onChangeWithCallback, it cleans up
|
|
11314
|
+
// after the abort signal completes.
|
|
11315
|
+
}, options?.signal);
|
|
11212
11316
|
}
|
|
11213
11317
|
handleTableChanges(changedTables, watchedTables, onDetectedChanges) {
|
|
11214
11318
|
if (changedTables.size > 0) {
|