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