@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.mjs CHANGED
@@ -1449,165 +1449,10 @@ var EncodingType;
1449
1449
  EncodingType["Base64"] = "base64";
1450
1450
  })(EncodingType || (EncodingType = {}));
1451
1451
 
1452
- const symbolAsyncIterator = Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
1453
-
1454
1452
  function getDefaultExportFromCjs (x) {
1455
1453
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
1456
1454
  }
1457
1455
 
1458
- var dom = {};
1459
-
1460
- var eventIterator = {};
1461
-
1462
- var hasRequiredEventIterator;
1463
-
1464
- function requireEventIterator () {
1465
- if (hasRequiredEventIterator) return eventIterator;
1466
- hasRequiredEventIterator = 1;
1467
- Object.defineProperty(eventIterator, "__esModule", { value: true });
1468
- class EventQueue {
1469
- constructor() {
1470
- this.pullQueue = [];
1471
- this.pushQueue = [];
1472
- this.eventHandlers = {};
1473
- this.isPaused = false;
1474
- this.isStopped = false;
1475
- }
1476
- push(value) {
1477
- if (this.isStopped)
1478
- return;
1479
- const resolution = { value, done: false };
1480
- if (this.pullQueue.length) {
1481
- const placeholder = this.pullQueue.shift();
1482
- if (placeholder)
1483
- placeholder.resolve(resolution);
1484
- }
1485
- else {
1486
- this.pushQueue.push(Promise.resolve(resolution));
1487
- if (this.highWaterMark !== undefined &&
1488
- this.pushQueue.length >= this.highWaterMark &&
1489
- !this.isPaused) {
1490
- this.isPaused = true;
1491
- if (this.eventHandlers.highWater) {
1492
- this.eventHandlers.highWater();
1493
- }
1494
- else if (console) {
1495
- console.warn(`EventIterator queue reached ${this.pushQueue.length} items`);
1496
- }
1497
- }
1498
- }
1499
- }
1500
- stop() {
1501
- if (this.isStopped)
1502
- return;
1503
- this.isStopped = true;
1504
- this.remove();
1505
- for (const placeholder of this.pullQueue) {
1506
- placeholder.resolve({ value: undefined, done: true });
1507
- }
1508
- this.pullQueue.length = 0;
1509
- }
1510
- fail(error) {
1511
- if (this.isStopped)
1512
- return;
1513
- this.isStopped = true;
1514
- this.remove();
1515
- if (this.pullQueue.length) {
1516
- for (const placeholder of this.pullQueue) {
1517
- placeholder.reject(error);
1518
- }
1519
- this.pullQueue.length = 0;
1520
- }
1521
- else {
1522
- const rejection = Promise.reject(error);
1523
- /* Attach error handler to avoid leaking an unhandled promise rejection. */
1524
- rejection.catch(() => { });
1525
- this.pushQueue.push(rejection);
1526
- }
1527
- }
1528
- remove() {
1529
- Promise.resolve().then(() => {
1530
- if (this.removeCallback)
1531
- this.removeCallback();
1532
- });
1533
- }
1534
- [symbolAsyncIterator]() {
1535
- return {
1536
- next: (value) => {
1537
- const result = this.pushQueue.shift();
1538
- if (result) {
1539
- if (this.lowWaterMark !== undefined &&
1540
- this.pushQueue.length <= this.lowWaterMark &&
1541
- this.isPaused) {
1542
- this.isPaused = false;
1543
- if (this.eventHandlers.lowWater) {
1544
- this.eventHandlers.lowWater();
1545
- }
1546
- }
1547
- return result;
1548
- }
1549
- else if (this.isStopped) {
1550
- return Promise.resolve({ value: undefined, done: true });
1551
- }
1552
- else {
1553
- return new Promise((resolve, reject) => {
1554
- this.pullQueue.push({ resolve, reject });
1555
- });
1556
- }
1557
- },
1558
- return: () => {
1559
- this.isStopped = true;
1560
- this.pushQueue.length = 0;
1561
- this.remove();
1562
- return Promise.resolve({ value: undefined, done: true });
1563
- },
1564
- };
1565
- }
1566
- }
1567
- class EventIterator {
1568
- constructor(listen, { highWaterMark = 100, lowWaterMark = 1 } = {}) {
1569
- const queue = new EventQueue();
1570
- queue.highWaterMark = highWaterMark;
1571
- queue.lowWaterMark = lowWaterMark;
1572
- queue.removeCallback =
1573
- listen({
1574
- push: value => queue.push(value),
1575
- stop: () => queue.stop(),
1576
- fail: error => queue.fail(error),
1577
- on: (event, fn) => {
1578
- queue.eventHandlers[event] = fn;
1579
- },
1580
- }) || (() => { });
1581
- this[symbolAsyncIterator] = () => queue[symbolAsyncIterator]();
1582
- Object.freeze(this);
1583
- }
1584
- }
1585
- eventIterator.EventIterator = EventIterator;
1586
- eventIterator.default = EventIterator;
1587
- return eventIterator;
1588
- }
1589
-
1590
- var hasRequiredDom;
1591
-
1592
- function requireDom () {
1593
- if (hasRequiredDom) return dom;
1594
- hasRequiredDom = 1;
1595
- Object.defineProperty(dom, "__esModule", { value: true });
1596
- const event_iterator_1 = requireEventIterator();
1597
- dom.EventIterator = event_iterator_1.EventIterator;
1598
- function subscribe(event, options, evOptions) {
1599
- return new event_iterator_1.EventIterator(({ push }) => {
1600
- this.addEventListener(event, push, options);
1601
- return () => this.removeEventListener(event, push, options);
1602
- }, evOptions);
1603
- }
1604
- dom.subscribe = subscribe;
1605
- dom.default = event_iterator_1.EventIterator;
1606
- return dom;
1607
- }
1608
-
1609
- var domExports = requireDom();
1610
-
1611
1456
  var logger$1 = {exports: {}};
1612
1457
 
1613
1458
  /*!
@@ -2290,11 +2135,7 @@ class SyncStatus {
2290
2135
  */
2291
2136
  const replacer = (_, value) => {
2292
2137
  if (value instanceof Error) {
2293
- return {
2294
- name: value.name,
2295
- message: value.message,
2296
- stack: value.stack
2297
- };
2138
+ return this.serializeError(value);
2298
2139
  }
2299
2140
  return value;
2300
2141
  };
@@ -2337,11 +2178,18 @@ class SyncStatus {
2337
2178
  if (typeof error == 'undefined') {
2338
2179
  return undefined;
2339
2180
  }
2340
- return {
2181
+ const serialized = {
2341
2182
  name: error.name,
2342
2183
  message: error.message,
2343
2184
  stack: error.stack
2344
2185
  };
2186
+ // `Error.cause` can be any value (the spec types it as unknown). Preserve it
2187
+ // so consumers reading uploadError/downloadError keep the failure context.
2188
+ // Recurse for Error causes so the whole chain is flattened the same way.
2189
+ if (typeof error.cause != 'undefined') {
2190
+ serialized.cause = error.cause instanceof Error ? this.serializeError(error.cause) : error.cause;
2191
+ }
2192
+ return serialized;
2345
2193
  }
2346
2194
  static comparePriorities(a, b) {
2347
2195
  return b.priority - a.priority; // Reverse because higher priorities have lower numbers
@@ -2491,6 +2339,210 @@ class ControlledExecutor {
2491
2339
  }
2492
2340
  }
2493
2341
 
2342
+ /**
2343
+ * Some JavaScript engines, in particular older versions of React Native, don't support Symbol.asyncIterator.
2344
+ *
2345
+ * 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).
2346
+ * This definition is compatible with that polyfill, so transpiled apps can use async iterables created by the PowerSync
2347
+ * SDK.
2348
+ */
2349
+ const symbolAsyncIterator = Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
2350
+
2351
+ const doneResult = { done: true, value: undefined };
2352
+ function valueResult(value) {
2353
+ return { done: false, value };
2354
+ }
2355
+ /**
2356
+ * Expands a source async iterator by allowing to inject events asynchronously.
2357
+ *
2358
+ * The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
2359
+ * events are dropped once the main iterator completes, but are otherwise forwarded.
2360
+ *
2361
+ * The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
2362
+ * in response to a `next()` call from downstream if no pending injected events can be dispatched.
2363
+ */
2364
+ function injectable(source) {
2365
+ let sourceIsDone = false;
2366
+ let waiter = undefined; // An active, waiting next() call.
2367
+ // A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
2368
+ let pendingSourceEvent = null;
2369
+ let sourceFetchInFlight = false;
2370
+ let pendingInjectedEvents = [];
2371
+ const consumeWaiter = () => {
2372
+ const pending = waiter;
2373
+ waiter = undefined;
2374
+ return pending;
2375
+ };
2376
+ const fetchFromSource = () => {
2377
+ const resolveWaiter = (propagate) => {
2378
+ sourceFetchInFlight = false;
2379
+ const active = consumeWaiter();
2380
+ if (active) {
2381
+ propagate(active);
2382
+ }
2383
+ else {
2384
+ pendingSourceEvent = propagate;
2385
+ }
2386
+ };
2387
+ sourceFetchInFlight = true;
2388
+ const nextFromSource = source.next();
2389
+ nextFromSource.then((value) => {
2390
+ sourceIsDone = value.done == true;
2391
+ resolveWaiter((w) => w.resolve(value));
2392
+ }, (error) => {
2393
+ resolveWaiter((w) => w.reject(error));
2394
+ });
2395
+ };
2396
+ return {
2397
+ next: () => {
2398
+ return new Promise((resolve, reject) => {
2399
+ // First priority: Dispatch ready upstream events.
2400
+ if (sourceIsDone) {
2401
+ return resolve(doneResult);
2402
+ }
2403
+ if (pendingSourceEvent) {
2404
+ pendingSourceEvent({ resolve, reject });
2405
+ pendingSourceEvent = null;
2406
+ return;
2407
+ }
2408
+ // Second priority: Dispatch injected events
2409
+ if (pendingInjectedEvents.length) {
2410
+ return resolve(valueResult(pendingInjectedEvents.shift()));
2411
+ }
2412
+ // Nothing pending? Fetch from source
2413
+ waiter = { resolve, reject };
2414
+ if (!sourceFetchInFlight) {
2415
+ fetchFromSource();
2416
+ }
2417
+ });
2418
+ },
2419
+ inject: (event) => {
2420
+ const pending = consumeWaiter();
2421
+ if (pending != null) {
2422
+ pending.resolve(valueResult(event));
2423
+ }
2424
+ else {
2425
+ pendingInjectedEvents.push(event);
2426
+ }
2427
+ }
2428
+ };
2429
+ }
2430
+ /**
2431
+ * Splits a byte stream at line endings, emitting each line as a string.
2432
+ */
2433
+ function extractJsonLines(source, decoder) {
2434
+ let buffer = '';
2435
+ const pendingLines = [];
2436
+ let isFinalEvent = false;
2437
+ return {
2438
+ next: async () => {
2439
+ while (true) {
2440
+ if (isFinalEvent) {
2441
+ return doneResult;
2442
+ }
2443
+ {
2444
+ const first = pendingLines.shift();
2445
+ if (first) {
2446
+ return { done: false, value: first };
2447
+ }
2448
+ }
2449
+ const { done, value } = await source.next();
2450
+ if (done) {
2451
+ const remaining = buffer.trim();
2452
+ if (remaining.length != 0) {
2453
+ isFinalEvent = true;
2454
+ return { done: false, value: remaining };
2455
+ }
2456
+ return doneResult;
2457
+ }
2458
+ const data = decoder.decode(value, { stream: true });
2459
+ buffer += data;
2460
+ const lines = buffer.split('\n');
2461
+ for (let i = 0; i < lines.length - 1; i++) {
2462
+ const l = lines[i].trim();
2463
+ if (l.length > 0) {
2464
+ pendingLines.push(l);
2465
+ }
2466
+ }
2467
+ buffer = lines[lines.length - 1];
2468
+ }
2469
+ }
2470
+ };
2471
+ }
2472
+ /**
2473
+ * Splits a concatenated stream of BSON objects by emitting individual objects.
2474
+ */
2475
+ function extractBsonObjects(source) {
2476
+ // Fully read but not emitted yet.
2477
+ const completedObjects = [];
2478
+ // Whether source has returned { done: true }. We do the same once completed objects have been emitted.
2479
+ let isDone = false;
2480
+ const lengthBuffer = new DataView(new ArrayBuffer(4));
2481
+ let objectBody = null;
2482
+ // If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
2483
+ // If we're consuming a document, the bytes remaining.
2484
+ let remainingLength = 4;
2485
+ return {
2486
+ async next() {
2487
+ while (true) {
2488
+ // Before fetching new data from upstream, return completed objects.
2489
+ if (completedObjects.length) {
2490
+ return valueResult(completedObjects.shift());
2491
+ }
2492
+ if (isDone) {
2493
+ return doneResult;
2494
+ }
2495
+ const upstreamEvent = await source.next();
2496
+ if (upstreamEvent.done) {
2497
+ isDone = true;
2498
+ if (objectBody || remainingLength != 4) {
2499
+ throw new Error('illegal end of stream in BSON object');
2500
+ }
2501
+ return doneResult;
2502
+ }
2503
+ const chunk = upstreamEvent.value;
2504
+ for (let i = 0; i < chunk.length;) {
2505
+ const availableInData = chunk.length - i;
2506
+ if (objectBody) {
2507
+ // We're in the middle of reading a BSON document.
2508
+ const bytesToRead = Math.min(availableInData, remainingLength);
2509
+ const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
2510
+ objectBody.set(copySource, objectBody.length - remainingLength);
2511
+ i += bytesToRead;
2512
+ remainingLength -= bytesToRead;
2513
+ if (remainingLength == 0) {
2514
+ completedObjects.push(objectBody);
2515
+ // Prepare to read another document, starting with its length
2516
+ objectBody = null;
2517
+ remainingLength = 4;
2518
+ }
2519
+ }
2520
+ else {
2521
+ // Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
2522
+ const bytesToRead = Math.min(availableInData, remainingLength);
2523
+ for (let j = 0; j < bytesToRead; j++) {
2524
+ lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
2525
+ }
2526
+ i += bytesToRead;
2527
+ remainingLength -= bytesToRead;
2528
+ if (remainingLength == 0) {
2529
+ // Transition from reading length header to reading document. Subtracting 4 because the length of the
2530
+ // header is included in length.
2531
+ const length = lengthBuffer.getInt32(0, true /* little endian */);
2532
+ remainingLength = length - 4;
2533
+ if (remainingLength < 1) {
2534
+ throw new Error(`invalid length for bson: ${length}`);
2535
+ }
2536
+ objectBody = new Uint8Array(length);
2537
+ new DataView(objectBody.buffer).setInt32(0, length, true);
2538
+ }
2539
+ }
2540
+ }
2541
+ }
2542
+ }
2543
+ };
2544
+ }
2545
+
2494
2546
  /**
2495
2547
  * Throttle a function to be called at most once every "wait" milliseconds,
2496
2548
  * on the trailing edge.
@@ -2510,45 +2562,128 @@ function throttleTrailing(func, wait) {
2510
2562
  };
2511
2563
  }
2512
2564
  function asyncNotifier() {
2513
- let waitingConsumer = null;
2514
- let hasPendingNotification = false;
2565
+ const queue = new EventQueue();
2515
2566
  return {
2516
2567
  notify() {
2517
- if (waitingConsumer != null) {
2518
- waitingConsumer();
2519
- waitingConsumer = null;
2568
+ if (queue.countOutstandingEvents > 0) ;
2569
+ else {
2570
+ queue.notify();
2571
+ }
2572
+ },
2573
+ waitForNotification(signal) {
2574
+ return queue.waitForEvent(signal);
2575
+ }
2576
+ };
2577
+ }
2578
+ class EventQueue {
2579
+ options;
2580
+ waitingConsumer;
2581
+ outstandingEvents;
2582
+ constructor(options = {}) {
2583
+ this.options = options;
2584
+ this.outstandingEvents = [];
2585
+ }
2586
+ /**
2587
+ * The amount of buffered events not yet dispatched to listeners.
2588
+ */
2589
+ get countOutstandingEvents() {
2590
+ return this.outstandingEvents.length;
2591
+ }
2592
+ notifyInner(dispatch) {
2593
+ const existing = this.waitingConsumer;
2594
+ this.waitingConsumer = undefined;
2595
+ const dispatchAndNotifyListeners = (waiter) => {
2596
+ dispatch(waiter);
2597
+ this.options.eventDelivered?.();
2598
+ };
2599
+ if (existing) {
2600
+ dispatchAndNotifyListeners(existing);
2601
+ }
2602
+ else {
2603
+ this.outstandingEvents.push(dispatchAndNotifyListeners);
2604
+ }
2605
+ }
2606
+ notify(value) {
2607
+ this.notifyInner((l) => l.resolve(value));
2608
+ }
2609
+ notifyError(error) {
2610
+ this.notifyInner((l) => l.reject(error));
2611
+ }
2612
+ waitForEvent(signal) {
2613
+ return new Promise((resolve, reject) => {
2614
+ if (this.waitingConsumer != null) {
2615
+ throw new Error('Illegal call to waitForEvent, already has a waiter.');
2616
+ }
2617
+ const complete = () => {
2618
+ signal?.removeEventListener('abort', onAbort);
2619
+ };
2620
+ const onAbort = () => {
2621
+ complete();
2622
+ this.waitingConsumer = undefined;
2623
+ resolve(undefined);
2624
+ };
2625
+ const waiter = {
2626
+ resolve: (value) => {
2627
+ complete();
2628
+ resolve(value);
2629
+ },
2630
+ reject: (error) => {
2631
+ complete();
2632
+ reject(error);
2633
+ }
2634
+ };
2635
+ if (signal.aborted) {
2636
+ resolve(undefined);
2637
+ }
2638
+ else if (this.countOutstandingEvents > 0) {
2639
+ const [event] = this.outstandingEvents.splice(0, 1);
2640
+ event(waiter);
2520
2641
  }
2521
2642
  else {
2522
- hasPendingNotification = true;
2643
+ this.waitingConsumer = waiter;
2644
+ signal.addEventListener('abort', onAbort);
2523
2645
  }
2524
- },
2525
- waitForNotification(signal) {
2526
- return new Promise((resolve) => {
2527
- if (waitingConsumer != null) {
2528
- throw new Error('Illegal call to waitForNotification, already has a waiter.');
2529
- }
2530
- if (signal.aborted) {
2531
- resolve();
2532
- }
2533
- else if (hasPendingNotification) {
2534
- resolve();
2535
- hasPendingNotification = false;
2646
+ });
2647
+ }
2648
+ /**
2649
+ * Creates an async iterable backed by event queues.
2650
+ *
2651
+ * @param run A function invoked for every new listener. It receives a queue backing the async iterator.
2652
+ * @param abort An additional abort signal. The `run` callback will also be aborted when `AsyncIterator.return` is
2653
+ * called.
2654
+ * @returns An object conforming to the async iterable protocol.
2655
+ */
2656
+ static queueBasedAsyncIterable(run, abort) {
2657
+ return {
2658
+ [symbolAsyncIterator]: () => {
2659
+ const queue = new EventQueue();
2660
+ const controller = new AbortController();
2661
+ function dispose() {
2662
+ controller.abort();
2663
+ abort?.removeEventListener('abort', dispose);
2536
2664
  }
2537
- else {
2538
- function complete() {
2539
- signal.removeEventListener('abort', onAbort);
2540
- resolve();
2665
+ if (abort) {
2666
+ if (abort.aborted) {
2667
+ controller.abort();
2541
2668
  }
2542
- function onAbort() {
2543
- waitingConsumer = null;
2544
- resolve();
2669
+ else {
2670
+ abort.addEventListener('abort', dispose);
2545
2671
  }
2546
- waitingConsumer = complete;
2547
- signal.addEventListener('abort', onAbort);
2548
2672
  }
2549
- });
2550
- }
2551
- };
2673
+ run(queue, controller.signal);
2674
+ return {
2675
+ async next() {
2676
+ const event = await queue.waitForEvent(controller.signal);
2677
+ return event == null ? doneResult : valueResult(event);
2678
+ },
2679
+ async return() {
2680
+ dispose();
2681
+ return doneResult;
2682
+ }
2683
+ };
2684
+ }
2685
+ };
2686
+ }
2552
2687
  }
2553
2688
 
2554
2689
  /**
@@ -10779,7 +10914,7 @@ function requireDist () {
10779
10914
 
10780
10915
  var distExports = requireDist();
10781
10916
 
10782
- var version = "1.54.0";
10917
+ var version = "1.56.0";
10783
10918
  var PACKAGE = {
10784
10919
  version: version};
10785
10920
 
@@ -10946,201 +11081,6 @@ class WebsocketClientTransport {
10946
11081
  }
10947
11082
  }
10948
11083
 
10949
- const doneResult = { done: true, value: undefined };
10950
- function valueResult(value) {
10951
- return { done: false, value };
10952
- }
10953
- /**
10954
- * Expands a source async iterator by allowing to inject events asynchronously.
10955
- *
10956
- * The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
10957
- * events are dropped once the main iterator completes, but are otherwise forwarded.
10958
- *
10959
- * The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
10960
- * in response to a `next()` call from downstream if no pending injected events can be dispatched.
10961
- */
10962
- function injectable(source) {
10963
- let sourceIsDone = false;
10964
- let waiter = undefined; // An active, waiting next() call.
10965
- // A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
10966
- let pendingSourceEvent = null;
10967
- let sourceFetchInFlight = false;
10968
- let pendingInjectedEvents = [];
10969
- const consumeWaiter = () => {
10970
- const pending = waiter;
10971
- waiter = undefined;
10972
- return pending;
10973
- };
10974
- const fetchFromSource = () => {
10975
- const resolveWaiter = (propagate) => {
10976
- sourceFetchInFlight = false;
10977
- const active = consumeWaiter();
10978
- if (active) {
10979
- propagate(active);
10980
- }
10981
- else {
10982
- pendingSourceEvent = propagate;
10983
- }
10984
- };
10985
- sourceFetchInFlight = true;
10986
- const nextFromSource = source.next();
10987
- nextFromSource.then((value) => {
10988
- sourceIsDone = value.done == true;
10989
- resolveWaiter((w) => w.resolve(value));
10990
- }, (error) => {
10991
- resolveWaiter((w) => w.reject(error));
10992
- });
10993
- };
10994
- return {
10995
- next: () => {
10996
- return new Promise((resolve, reject) => {
10997
- // First priority: Dispatch ready upstream events.
10998
- if (sourceIsDone) {
10999
- return resolve(doneResult);
11000
- }
11001
- if (pendingSourceEvent) {
11002
- pendingSourceEvent({ resolve, reject });
11003
- pendingSourceEvent = null;
11004
- return;
11005
- }
11006
- // Second priority: Dispatch injected events
11007
- if (pendingInjectedEvents.length) {
11008
- return resolve(valueResult(pendingInjectedEvents.shift()));
11009
- }
11010
- // Nothing pending? Fetch from source
11011
- waiter = { resolve, reject };
11012
- if (!sourceFetchInFlight) {
11013
- fetchFromSource();
11014
- }
11015
- });
11016
- },
11017
- inject: (event) => {
11018
- const pending = consumeWaiter();
11019
- if (pending != null) {
11020
- pending.resolve(valueResult(event));
11021
- }
11022
- else {
11023
- pendingInjectedEvents.push(event);
11024
- }
11025
- }
11026
- };
11027
- }
11028
- /**
11029
- * Splits a byte stream at line endings, emitting each line as a string.
11030
- */
11031
- function extractJsonLines(source, decoder) {
11032
- let buffer = '';
11033
- const pendingLines = [];
11034
- let isFinalEvent = false;
11035
- return {
11036
- next: async () => {
11037
- while (true) {
11038
- if (isFinalEvent) {
11039
- return doneResult;
11040
- }
11041
- {
11042
- const first = pendingLines.shift();
11043
- if (first) {
11044
- return { done: false, value: first };
11045
- }
11046
- }
11047
- const { done, value } = await source.next();
11048
- if (done) {
11049
- const remaining = buffer.trim();
11050
- if (remaining.length != 0) {
11051
- isFinalEvent = true;
11052
- return { done: false, value: remaining };
11053
- }
11054
- return doneResult;
11055
- }
11056
- const data = decoder.decode(value, { stream: true });
11057
- buffer += data;
11058
- const lines = buffer.split('\n');
11059
- for (let i = 0; i < lines.length - 1; i++) {
11060
- const l = lines[i].trim();
11061
- if (l.length > 0) {
11062
- pendingLines.push(l);
11063
- }
11064
- }
11065
- buffer = lines[lines.length - 1];
11066
- }
11067
- }
11068
- };
11069
- }
11070
- /**
11071
- * Splits a concatenated stream of BSON objects by emitting individual objects.
11072
- */
11073
- function extractBsonObjects(source) {
11074
- // Fully read but not emitted yet.
11075
- const completedObjects = [];
11076
- // Whether source has returned { done: true }. We do the same once completed objects have been emitted.
11077
- let isDone = false;
11078
- const lengthBuffer = new DataView(new ArrayBuffer(4));
11079
- let objectBody = null;
11080
- // If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
11081
- // If we're consuming a document, the bytes remaining.
11082
- let remainingLength = 4;
11083
- return {
11084
- async next() {
11085
- while (true) {
11086
- // Before fetching new data from upstream, return completed objects.
11087
- if (completedObjects.length) {
11088
- return valueResult(completedObjects.shift());
11089
- }
11090
- if (isDone) {
11091
- return doneResult;
11092
- }
11093
- const upstreamEvent = await source.next();
11094
- if (upstreamEvent.done) {
11095
- isDone = true;
11096
- if (objectBody || remainingLength != 4) {
11097
- throw new Error('illegal end of stream in BSON object');
11098
- }
11099
- return doneResult;
11100
- }
11101
- const chunk = upstreamEvent.value;
11102
- for (let i = 0; i < chunk.length;) {
11103
- const availableInData = chunk.length - i;
11104
- if (objectBody) {
11105
- // We're in the middle of reading a BSON document.
11106
- const bytesToRead = Math.min(availableInData, remainingLength);
11107
- const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
11108
- objectBody.set(copySource, objectBody.length - remainingLength);
11109
- i += bytesToRead;
11110
- remainingLength -= bytesToRead;
11111
- if (remainingLength == 0) {
11112
- completedObjects.push(objectBody);
11113
- // Prepare to read another document, starting with its length
11114
- objectBody = null;
11115
- remainingLength = 4;
11116
- }
11117
- }
11118
- else {
11119
- // Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
11120
- const bytesToRead = Math.min(availableInData, remainingLength);
11121
- for (let j = 0; j < bytesToRead; j++) {
11122
- lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
11123
- }
11124
- i += bytesToRead;
11125
- remainingLength -= bytesToRead;
11126
- if (remainingLength == 0) {
11127
- // Transition from reading length header to reading document. Subtracting 4 because the length of the
11128
- // header is included in length.
11129
- const length = lengthBuffer.getInt32(0, true /* little endian */);
11130
- remainingLength = length - 4;
11131
- if (remainingLength < 1) {
11132
- throw new Error(`invalid length for bson: ${length}`);
11133
- }
11134
- objectBody = new Uint8Array(length);
11135
- new DataView(objectBody.buffer).setInt32(0, length, true);
11136
- }
11137
- }
11138
- }
11139
- }
11140
- }
11141
- };
11142
- }
11143
-
11144
11084
  const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
11145
11085
  const POWERSYNC_JS_VERSION = PACKAGE.version;
11146
11086
  const SYNC_QUEUE_REQUEST_HIGH_WATER = 10;
@@ -11359,8 +11299,19 @@ class AbstractRemote {
11359
11299
  let pendingSocket = null;
11360
11300
  let keepAliveTimeout;
11361
11301
  let rsocket = null;
11362
- let queue = null;
11302
+ let paused = false;
11303
+ const queue = new EventQueue({
11304
+ eventDelivered: () => {
11305
+ if (queue.countOutstandingEvents <= SYNC_QUEUE_REQUEST_LOW_WATER) {
11306
+ paused = false;
11307
+ requestMore();
11308
+ }
11309
+ }
11310
+ });
11363
11311
  let didClose = false;
11312
+ let connectionEstablished = false;
11313
+ let pendingEventsCount = syncQueueRequestSize;
11314
+ let res = null;
11364
11315
  const abortRequest = () => {
11365
11316
  if (didClose) {
11366
11317
  return;
@@ -11373,10 +11324,23 @@ class AbstractRemote {
11373
11324
  if (rsocket) {
11374
11325
  rsocket.close();
11375
11326
  }
11376
- if (queue) {
11377
- queue.stop();
11378
- }
11327
+ // Send a bogus event to the queue to ensure a pending listener gets woken up. We check for didClose and would
11328
+ // return a doneEvent.
11329
+ queue.notify(null);
11379
11330
  };
11331
+ function push(event) {
11332
+ queue.notify(event);
11333
+ if (queue.countOutstandingEvents >= SYNC_QUEUE_REQUEST_HIGH_WATER) {
11334
+ paused = true;
11335
+ }
11336
+ }
11337
+ function requestMore() {
11338
+ const delta = syncQueueRequestSize - pendingEventsCount;
11339
+ if (!paused && delta > 0) {
11340
+ res?.request(delta);
11341
+ pendingEventsCount = syncQueueRequestSize;
11342
+ }
11343
+ }
11380
11344
  // Handle upstream abort
11381
11345
  if (options.abortSignal.aborted) {
11382
11346
  throw new AbortOperation('Connection request aborted');
@@ -11431,25 +11395,19 @@ class AbstractRemote {
11431
11395
  // Helps to prevent double close scenarios
11432
11396
  rsocket.onClose(() => (rsocket = null));
11433
11397
  return await new Promise((resolve, reject) => {
11434
- let connectionEstablished = false;
11435
- let pendingEventsCount = syncQueueRequestSize;
11436
- let paused = false;
11437
- let res = null;
11438
- function requestMore() {
11439
- const delta = syncQueueRequestSize - pendingEventsCount;
11440
- if (!paused && delta > 0) {
11441
- res?.request(delta);
11442
- pendingEventsCount = syncQueueRequestSize;
11398
+ const queueAsIterator = {
11399
+ next: async () => {
11400
+ if (didClose)
11401
+ return doneResult;
11402
+ const notification = await queue.waitForEvent(options.abortSignal);
11403
+ if (didClose) {
11404
+ return doneResult;
11405
+ }
11406
+ else {
11407
+ return valueResult(notification);
11408
+ }
11443
11409
  }
11444
- }
11445
- const events = new domExports.EventIterator((q) => {
11446
- queue = q;
11447
- q.on('highWater', () => (paused = true));
11448
- q.on('lowWater', () => {
11449
- paused = false;
11450
- requestMore();
11451
- });
11452
- }, { highWaterMark: SYNC_QUEUE_REQUEST_HIGH_WATER, lowWaterMark: SYNC_QUEUE_REQUEST_LOW_WATER })[symbolAsyncIterator]();
11410
+ };
11453
11411
  res = rsocket.requestStream({
11454
11412
  data: toBuffer(options.data),
11455
11413
  metadata: toBuffer({
@@ -11485,11 +11443,11 @@ class AbstractRemote {
11485
11443
  // The connection is active
11486
11444
  if (!connectionEstablished) {
11487
11445
  connectionEstablished = true;
11488
- resolve(events);
11446
+ resolve(queueAsIterator);
11489
11447
  }
11490
11448
  const { data } = payload;
11491
11449
  if (data) {
11492
- queue.push(data);
11450
+ push(data);
11493
11451
  }
11494
11452
  // Less events are now pending
11495
11453
  pendingEventsCount--;
@@ -13087,7 +13045,7 @@ class AbstractPowerSyncDatabase extends BaseObserver {
13087
13045
  this.logger.warn('Schema validation failed. Unexpected behaviour could occur', ex);
13088
13046
  }
13089
13047
  this._schema = schema;
13090
- await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
13048
+ await this.database.writeTransaction((tx) => tx.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]));
13091
13049
  await this.database.refreshSchema();
13092
13050
  this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
13093
13051
  }
@@ -13607,20 +13565,17 @@ SELECT * FROM crud_entries;
13607
13565
  * @returns An AsyncIterable that yields QueryResults whenever the data changes
13608
13566
  */
13609
13567
  watchWithAsyncGenerator(sql, parameters, options) {
13610
- return new domExports.EventIterator((eventOptions) => {
13568
+ return EventQueue.queueBasedAsyncIterable((queue, abort) => {
13611
13569
  const handler = {
13612
13570
  onResult: (result) => {
13613
- eventOptions.push(result);
13571
+ queue.notify(result);
13614
13572
  },
13615
13573
  onError: (error) => {
13616
- eventOptions.fail(error);
13574
+ queue.notifyError(error);
13617
13575
  }
13618
13576
  };
13619
- this.watchWithCallback(sql, parameters, handler, options);
13620
- options?.signal?.addEventListener('abort', () => {
13621
- eventOptions.stop();
13622
- });
13623
- });
13577
+ this.watchWithCallback(sql, parameters, handler, { ...options, signal: abort });
13578
+ }, options?.signal);
13624
13579
  }
13625
13580
  /**
13626
13581
  * Resolves the list of tables that are used in a SQL query.
@@ -13710,28 +13665,23 @@ SELECT * FROM crud_entries;
13710
13665
  * This is preferred over {@link AbstractPowerSyncDatabase.watchWithAsyncGenerator} when multiple queries need to be
13711
13666
  * performed together when data is changed.
13712
13667
  *
13713
- * Note: do not declare this as `async *onChange` as it will not work in React Native.
13714
- *
13715
13668
  * @param options - Options for configuring watch behavior
13716
13669
  * @returns An AsyncIterable that yields change events whenever the specified tables change
13717
13670
  */
13671
+ // Note: do not declare this as `async *onChange` as it will not work in React Native.
13718
13672
  onChangeWithAsyncGenerator(options) {
13719
- const resolvedOptions = options ?? {};
13720
- return new domExports.EventIterator((eventOptions) => {
13721
- const dispose = this.onChangeWithCallback({
13673
+ return EventQueue.queueBasedAsyncIterable((queue, abort) => {
13674
+ this.onChangeWithCallback({
13722
13675
  onChange: (event) => {
13723
- eventOptions.push(event);
13676
+ queue.notify(event);
13724
13677
  },
13725
13678
  onError: (error) => {
13726
- eventOptions.fail(error);
13679
+ queue.notifyError(error);
13727
13680
  }
13728
- }, options);
13729
- resolvedOptions.signal?.addEventListener('abort', () => {
13730
- eventOptions.stop();
13731
- // Maybe fail?
13732
- });
13733
- return () => dispose();
13734
- });
13681
+ }, { ...options, signal: abort });
13682
+ // Note: We don't have to track the dispose function returned by onChangeWithCallback, it cleans up
13683
+ // after the abort signal completes.
13684
+ }, options?.signal);
13735
13685
  }
13736
13686
  handleTableChanges(changedTables, watchedTables, onDetectedChanges) {
13737
13687
  if (changedTables.size > 0) {