@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.
@@ -1,4 +1,3 @@
1
- import { EventIterator } from 'event-iterator';
2
1
  import { Buffer } from 'node:buffer';
3
2
 
4
3
  /**
@@ -2339,6 +2338,210 @@ class ControlledExecutor {
2339
2338
  }
2340
2339
  }
2341
2340
 
2341
+ /**
2342
+ * Some JavaScript engines, in particular older versions of React Native, don't support Symbol.asyncIterator.
2343
+ *
2344
+ * 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).
2345
+ * This definition is compatible with that polyfill, so transpiled apps can use async iterables created by the PowerSync
2346
+ * SDK.
2347
+ */
2348
+ const symbolAsyncIterator = Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
2349
+
2350
+ const doneResult = { done: true, value: undefined };
2351
+ function valueResult(value) {
2352
+ return { done: false, value };
2353
+ }
2354
+ /**
2355
+ * Expands a source async iterator by allowing to inject events asynchronously.
2356
+ *
2357
+ * The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
2358
+ * events are dropped once the main iterator completes, but are otherwise forwarded.
2359
+ *
2360
+ * The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
2361
+ * in response to a `next()` call from downstream if no pending injected events can be dispatched.
2362
+ */
2363
+ function injectable(source) {
2364
+ let sourceIsDone = false;
2365
+ let waiter = undefined; // An active, waiting next() call.
2366
+ // A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
2367
+ let pendingSourceEvent = null;
2368
+ let sourceFetchInFlight = false;
2369
+ let pendingInjectedEvents = [];
2370
+ const consumeWaiter = () => {
2371
+ const pending = waiter;
2372
+ waiter = undefined;
2373
+ return pending;
2374
+ };
2375
+ const fetchFromSource = () => {
2376
+ const resolveWaiter = (propagate) => {
2377
+ sourceFetchInFlight = false;
2378
+ const active = consumeWaiter();
2379
+ if (active) {
2380
+ propagate(active);
2381
+ }
2382
+ else {
2383
+ pendingSourceEvent = propagate;
2384
+ }
2385
+ };
2386
+ sourceFetchInFlight = true;
2387
+ const nextFromSource = source.next();
2388
+ nextFromSource.then((value) => {
2389
+ sourceIsDone = value.done == true;
2390
+ resolveWaiter((w) => w.resolve(value));
2391
+ }, (error) => {
2392
+ resolveWaiter((w) => w.reject(error));
2393
+ });
2394
+ };
2395
+ return {
2396
+ next: () => {
2397
+ return new Promise((resolve, reject) => {
2398
+ // First priority: Dispatch ready upstream events.
2399
+ if (sourceIsDone) {
2400
+ return resolve(doneResult);
2401
+ }
2402
+ if (pendingSourceEvent) {
2403
+ pendingSourceEvent({ resolve, reject });
2404
+ pendingSourceEvent = null;
2405
+ return;
2406
+ }
2407
+ // Second priority: Dispatch injected events
2408
+ if (pendingInjectedEvents.length) {
2409
+ return resolve(valueResult(pendingInjectedEvents.shift()));
2410
+ }
2411
+ // Nothing pending? Fetch from source
2412
+ waiter = { resolve, reject };
2413
+ if (!sourceFetchInFlight) {
2414
+ fetchFromSource();
2415
+ }
2416
+ });
2417
+ },
2418
+ inject: (event) => {
2419
+ const pending = consumeWaiter();
2420
+ if (pending != null) {
2421
+ pending.resolve(valueResult(event));
2422
+ }
2423
+ else {
2424
+ pendingInjectedEvents.push(event);
2425
+ }
2426
+ }
2427
+ };
2428
+ }
2429
+ /**
2430
+ * Splits a byte stream at line endings, emitting each line as a string.
2431
+ */
2432
+ function extractJsonLines(source, decoder) {
2433
+ let buffer = '';
2434
+ const pendingLines = [];
2435
+ let isFinalEvent = false;
2436
+ return {
2437
+ next: async () => {
2438
+ while (true) {
2439
+ if (isFinalEvent) {
2440
+ return doneResult;
2441
+ }
2442
+ {
2443
+ const first = pendingLines.shift();
2444
+ if (first) {
2445
+ return { done: false, value: first };
2446
+ }
2447
+ }
2448
+ const { done, value } = await source.next();
2449
+ if (done) {
2450
+ const remaining = buffer.trim();
2451
+ if (remaining.length != 0) {
2452
+ isFinalEvent = true;
2453
+ return { done: false, value: remaining };
2454
+ }
2455
+ return doneResult;
2456
+ }
2457
+ const data = decoder.decode(value, { stream: true });
2458
+ buffer += data;
2459
+ const lines = buffer.split('\n');
2460
+ for (let i = 0; i < lines.length - 1; i++) {
2461
+ const l = lines[i].trim();
2462
+ if (l.length > 0) {
2463
+ pendingLines.push(l);
2464
+ }
2465
+ }
2466
+ buffer = lines[lines.length - 1];
2467
+ }
2468
+ }
2469
+ };
2470
+ }
2471
+ /**
2472
+ * Splits a concatenated stream of BSON objects by emitting individual objects.
2473
+ */
2474
+ function extractBsonObjects(source) {
2475
+ // Fully read but not emitted yet.
2476
+ const completedObjects = [];
2477
+ // Whether source has returned { done: true }. We do the same once completed objects have been emitted.
2478
+ let isDone = false;
2479
+ const lengthBuffer = new DataView(new ArrayBuffer(4));
2480
+ let objectBody = null;
2481
+ // If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
2482
+ // If we're consuming a document, the bytes remaining.
2483
+ let remainingLength = 4;
2484
+ return {
2485
+ async next() {
2486
+ while (true) {
2487
+ // Before fetching new data from upstream, return completed objects.
2488
+ if (completedObjects.length) {
2489
+ return valueResult(completedObjects.shift());
2490
+ }
2491
+ if (isDone) {
2492
+ return doneResult;
2493
+ }
2494
+ const upstreamEvent = await source.next();
2495
+ if (upstreamEvent.done) {
2496
+ isDone = true;
2497
+ if (objectBody || remainingLength != 4) {
2498
+ throw new Error('illegal end of stream in BSON object');
2499
+ }
2500
+ return doneResult;
2501
+ }
2502
+ const chunk = upstreamEvent.value;
2503
+ for (let i = 0; i < chunk.length;) {
2504
+ const availableInData = chunk.length - i;
2505
+ if (objectBody) {
2506
+ // We're in the middle of reading a BSON document.
2507
+ const bytesToRead = Math.min(availableInData, remainingLength);
2508
+ const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
2509
+ objectBody.set(copySource, objectBody.length - remainingLength);
2510
+ i += bytesToRead;
2511
+ remainingLength -= bytesToRead;
2512
+ if (remainingLength == 0) {
2513
+ completedObjects.push(objectBody);
2514
+ // Prepare to read another document, starting with its length
2515
+ objectBody = null;
2516
+ remainingLength = 4;
2517
+ }
2518
+ }
2519
+ else {
2520
+ // Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
2521
+ const bytesToRead = Math.min(availableInData, remainingLength);
2522
+ for (let j = 0; j < bytesToRead; j++) {
2523
+ lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
2524
+ }
2525
+ i += bytesToRead;
2526
+ remainingLength -= bytesToRead;
2527
+ if (remainingLength == 0) {
2528
+ // Transition from reading length header to reading document. Subtracting 4 because the length of the
2529
+ // header is included in length.
2530
+ const length = lengthBuffer.getInt32(0, true /* little endian */);
2531
+ remainingLength = length - 4;
2532
+ if (remainingLength < 1) {
2533
+ throw new Error(`invalid length for bson: ${length}`);
2534
+ }
2535
+ objectBody = new Uint8Array(length);
2536
+ new DataView(objectBody.buffer).setInt32(0, length, true);
2537
+ }
2538
+ }
2539
+ }
2540
+ }
2541
+ }
2542
+ };
2543
+ }
2544
+
2342
2545
  /**
2343
2546
  * Throttle a function to be called at most once every "wait" milliseconds,
2344
2547
  * on the trailing edge.
@@ -2358,45 +2561,128 @@ function throttleTrailing(func, wait) {
2358
2561
  };
2359
2562
  }
2360
2563
  function asyncNotifier() {
2361
- let waitingConsumer = null;
2362
- let hasPendingNotification = false;
2564
+ const queue = new EventQueue();
2363
2565
  return {
2364
2566
  notify() {
2365
- if (waitingConsumer != null) {
2366
- waitingConsumer();
2367
- waitingConsumer = null;
2368
- }
2567
+ if (queue.countOutstandingEvents > 0) ;
2369
2568
  else {
2370
- hasPendingNotification = true;
2569
+ queue.notify();
2371
2570
  }
2372
2571
  },
2373
2572
  waitForNotification(signal) {
2374
- return new Promise((resolve) => {
2375
- if (waitingConsumer != null) {
2376
- throw new Error('Illegal call to waitForNotification, already has a waiter.');
2377
- }
2378
- if (signal.aborted) {
2379
- resolve();
2573
+ return queue.waitForEvent(signal);
2574
+ }
2575
+ };
2576
+ }
2577
+ class EventQueue {
2578
+ options;
2579
+ waitingConsumer;
2580
+ outstandingEvents;
2581
+ constructor(options = {}) {
2582
+ this.options = options;
2583
+ this.outstandingEvents = [];
2584
+ }
2585
+ /**
2586
+ * The amount of buffered events not yet dispatched to listeners.
2587
+ */
2588
+ get countOutstandingEvents() {
2589
+ return this.outstandingEvents.length;
2590
+ }
2591
+ notifyInner(dispatch) {
2592
+ const existing = this.waitingConsumer;
2593
+ this.waitingConsumer = undefined;
2594
+ const dispatchAndNotifyListeners = (waiter) => {
2595
+ dispatch(waiter);
2596
+ this.options.eventDelivered?.();
2597
+ };
2598
+ if (existing) {
2599
+ dispatchAndNotifyListeners(existing);
2600
+ }
2601
+ else {
2602
+ this.outstandingEvents.push(dispatchAndNotifyListeners);
2603
+ }
2604
+ }
2605
+ notify(value) {
2606
+ this.notifyInner((l) => l.resolve(value));
2607
+ }
2608
+ notifyError(error) {
2609
+ this.notifyInner((l) => l.reject(error));
2610
+ }
2611
+ waitForEvent(signal) {
2612
+ return new Promise((resolve, reject) => {
2613
+ if (this.waitingConsumer != null) {
2614
+ throw new Error('Illegal call to waitForEvent, already has a waiter.');
2615
+ }
2616
+ const complete = () => {
2617
+ signal?.removeEventListener('abort', onAbort);
2618
+ };
2619
+ const onAbort = () => {
2620
+ complete();
2621
+ this.waitingConsumer = undefined;
2622
+ resolve(undefined);
2623
+ };
2624
+ const waiter = {
2625
+ resolve: (value) => {
2626
+ complete();
2627
+ resolve(value);
2628
+ },
2629
+ reject: (error) => {
2630
+ complete();
2631
+ reject(error);
2380
2632
  }
2381
- else if (hasPendingNotification) {
2382
- resolve();
2383
- hasPendingNotification = false;
2633
+ };
2634
+ if (signal.aborted) {
2635
+ resolve(undefined);
2636
+ }
2637
+ else if (this.countOutstandingEvents > 0) {
2638
+ const [event] = this.outstandingEvents.splice(0, 1);
2639
+ event(waiter);
2640
+ }
2641
+ else {
2642
+ this.waitingConsumer = waiter;
2643
+ signal.addEventListener('abort', onAbort);
2644
+ }
2645
+ });
2646
+ }
2647
+ /**
2648
+ * Creates an async iterable backed by event queues.
2649
+ *
2650
+ * @param run A function invoked for every new listener. It receives a queue backing the async iterator.
2651
+ * @param abort An additional abort signal. The `run` callback will also be aborted when `AsyncIterator.return` is
2652
+ * called.
2653
+ * @returns An object conforming to the async iterable protocol.
2654
+ */
2655
+ static queueBasedAsyncIterable(run, abort) {
2656
+ return {
2657
+ [symbolAsyncIterator]: () => {
2658
+ const queue = new EventQueue();
2659
+ const controller = new AbortController();
2660
+ function dispose() {
2661
+ controller.abort();
2662
+ abort?.removeEventListener('abort', dispose);
2384
2663
  }
2385
- else {
2386
- function complete() {
2387
- signal.removeEventListener('abort', onAbort);
2388
- resolve();
2664
+ if (abort) {
2665
+ if (abort.aborted) {
2666
+ controller.abort();
2389
2667
  }
2390
- function onAbort() {
2391
- waitingConsumer = null;
2392
- resolve();
2668
+ else {
2669
+ abort.addEventListener('abort', dispose);
2393
2670
  }
2394
- waitingConsumer = complete;
2395
- signal.addEventListener('abort', onAbort);
2396
2671
  }
2397
- });
2398
- }
2399
- };
2672
+ run(queue, controller.signal);
2673
+ return {
2674
+ async next() {
2675
+ const event = await queue.waitForEvent(controller.signal);
2676
+ return event == null ? doneResult : valueResult(event);
2677
+ },
2678
+ async return() {
2679
+ dispose();
2680
+ return doneResult;
2681
+ }
2682
+ };
2683
+ }
2684
+ };
2685
+ }
2400
2686
  }
2401
2687
 
2402
2688
  /**
@@ -8256,7 +8542,7 @@ function requireDist () {
8256
8542
 
8257
8543
  var distExports = requireDist();
8258
8544
 
8259
- var version = "1.54.0";
8545
+ var version = "1.55.0";
8260
8546
  var PACKAGE = {
8261
8547
  version: version};
8262
8548
 
@@ -8423,201 +8709,6 @@ class WebsocketClientTransport {
8423
8709
  }
8424
8710
  }
8425
8711
 
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
8712
  const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
8622
8713
  const POWERSYNC_JS_VERSION = PACKAGE.version;
8623
8714
  const SYNC_QUEUE_REQUEST_HIGH_WATER = 10;
@@ -8836,8 +8927,19 @@ class AbstractRemote {
8836
8927
  let pendingSocket = null;
8837
8928
  let keepAliveTimeout;
8838
8929
  let rsocket = null;
8839
- let queue = null;
8930
+ let paused = false;
8931
+ const queue = new EventQueue({
8932
+ eventDelivered: () => {
8933
+ if (queue.countOutstandingEvents <= SYNC_QUEUE_REQUEST_LOW_WATER) {
8934
+ paused = false;
8935
+ requestMore();
8936
+ }
8937
+ }
8938
+ });
8840
8939
  let didClose = false;
8940
+ let connectionEstablished = false;
8941
+ let pendingEventsCount = syncQueueRequestSize;
8942
+ let res = null;
8841
8943
  const abortRequest = () => {
8842
8944
  if (didClose) {
8843
8945
  return;
@@ -8850,10 +8952,23 @@ class AbstractRemote {
8850
8952
  if (rsocket) {
8851
8953
  rsocket.close();
8852
8954
  }
8853
- if (queue) {
8854
- queue.stop();
8855
- }
8955
+ // Send a bogus event to the queue to ensure a pending listener gets woken up. We check for didClose and would
8956
+ // return a doneEvent.
8957
+ queue.notify(null);
8856
8958
  };
8959
+ function push(event) {
8960
+ queue.notify(event);
8961
+ if (queue.countOutstandingEvents >= SYNC_QUEUE_REQUEST_HIGH_WATER) {
8962
+ paused = true;
8963
+ }
8964
+ }
8965
+ function requestMore() {
8966
+ const delta = syncQueueRequestSize - pendingEventsCount;
8967
+ if (!paused && delta > 0) {
8968
+ res?.request(delta);
8969
+ pendingEventsCount = syncQueueRequestSize;
8970
+ }
8971
+ }
8857
8972
  // Handle upstream abort
8858
8973
  if (options.abortSignal.aborted) {
8859
8974
  throw new AbortOperation('Connection request aborted');
@@ -8908,25 +9023,19 @@ class AbstractRemote {
8908
9023
  // Helps to prevent double close scenarios
8909
9024
  rsocket.onClose(() => (rsocket = null));
8910
9025
  return await new Promise((resolve, reject) => {
8911
- let connectionEstablished = false;
8912
- let pendingEventsCount = syncQueueRequestSize;
8913
- let paused = false;
8914
- let res = null;
8915
- function requestMore() {
8916
- const delta = syncQueueRequestSize - pendingEventsCount;
8917
- if (!paused && delta > 0) {
8918
- res?.request(delta);
8919
- pendingEventsCount = syncQueueRequestSize;
9026
+ const queueAsIterator = {
9027
+ next: async () => {
9028
+ if (didClose)
9029
+ return doneResult;
9030
+ const notification = await queue.waitForEvent(options.abortSignal);
9031
+ if (didClose) {
9032
+ return doneResult;
9033
+ }
9034
+ else {
9035
+ return valueResult(notification);
9036
+ }
8920
9037
  }
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]();
9038
+ };
8930
9039
  res = rsocket.requestStream({
8931
9040
  data: toBuffer(options.data),
8932
9041
  metadata: toBuffer({
@@ -8962,11 +9071,11 @@ class AbstractRemote {
8962
9071
  // The connection is active
8963
9072
  if (!connectionEstablished) {
8964
9073
  connectionEstablished = true;
8965
- resolve(events);
9074
+ resolve(queueAsIterator);
8966
9075
  }
8967
9076
  const { data } = payload;
8968
9077
  if (data) {
8969
- queue.push(data);
9078
+ push(data);
8970
9079
  }
8971
9080
  // Less events are now pending
8972
9081
  pendingEventsCount--;
@@ -10734,7 +10843,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
10734
10843
  * @returns A transaction of CRUD operations to upload, or null if there are none
10735
10844
  */
10736
10845
  async getNextCrudTransaction() {
10737
- const iterator = this.getCrudTransactions()[Symbol.asyncIterator]();
10846
+ const iterator = this.getCrudTransactions()[symbolAsyncIterator]();
10738
10847
  return (await iterator.next()).value;
10739
10848
  }
10740
10849
  /**
@@ -10770,7 +10879,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
10770
10879
  */
10771
10880
  getCrudTransactions() {
10772
10881
  return {
10773
- [Symbol.asyncIterator]: () => {
10882
+ [symbolAsyncIterator]: () => {
10774
10883
  let lastCrudItemId = -1;
10775
10884
  const sql = `
10776
10885
  WITH RECURSIVE crud_entries AS (
@@ -11084,20 +11193,17 @@ SELECT * FROM crud_entries;
11084
11193
  * @returns An AsyncIterable that yields QueryResults whenever the data changes
11085
11194
  */
11086
11195
  watchWithAsyncGenerator(sql, parameters, options) {
11087
- return new EventIterator((eventOptions) => {
11196
+ return EventQueue.queueBasedAsyncIterable((queue, abort) => {
11088
11197
  const handler = {
11089
11198
  onResult: (result) => {
11090
- eventOptions.push(result);
11199
+ queue.notify(result);
11091
11200
  },
11092
11201
  onError: (error) => {
11093
- eventOptions.fail(error);
11202
+ queue.notifyError(error);
11094
11203
  }
11095
11204
  };
11096
- this.watchWithCallback(sql, parameters, handler, options);
11097
- options?.signal?.addEventListener('abort', () => {
11098
- eventOptions.stop();
11099
- });
11100
- });
11205
+ this.watchWithCallback(sql, parameters, handler, { ...options, signal: abort });
11206
+ }, options?.signal);
11101
11207
  }
11102
11208
  /**
11103
11209
  * Resolves the list of tables that are used in a SQL query.
@@ -11187,28 +11293,23 @@ SELECT * FROM crud_entries;
11187
11293
  * This is preferred over {@link AbstractPowerSyncDatabase.watchWithAsyncGenerator} when multiple queries need to be
11188
11294
  * performed together when data is changed.
11189
11295
  *
11190
- * Note: do not declare this as `async *onChange` as it will not work in React Native.
11191
- *
11192
11296
  * @param options - Options for configuring watch behavior
11193
11297
  * @returns An AsyncIterable that yields change events whenever the specified tables change
11194
11298
  */
11299
+ // Note: do not declare this as `async *onChange` as it will not work in React Native.
11195
11300
  onChangeWithAsyncGenerator(options) {
11196
- const resolvedOptions = options ?? {};
11197
- return new EventIterator((eventOptions) => {
11198
- const dispose = this.onChangeWithCallback({
11301
+ return EventQueue.queueBasedAsyncIterable((queue, abort) => {
11302
+ this.onChangeWithCallback({
11199
11303
  onChange: (event) => {
11200
- eventOptions.push(event);
11304
+ queue.notify(event);
11201
11305
  },
11202
11306
  onError: (error) => {
11203
- eventOptions.fail(error);
11307
+ queue.notifyError(error);
11204
11308
  }
11205
- }, options);
11206
- resolvedOptions.signal?.addEventListener('abort', () => {
11207
- eventOptions.stop();
11208
- // Maybe fail?
11209
- });
11210
- return () => dispose();
11211
- });
11309
+ }, { ...options, signal: abort });
11310
+ // Note: We don't have to track the dispose function returned by onChangeWithCallback, it cleans up
11311
+ // after the abort signal completes.
11312
+ }, options?.signal);
11212
11313
  }
11213
11314
  handleTableChanges(changedTables, watchedTables, onDetectedChanges) {
11214
11315
  if (changedTables.size > 0) {