@powersync/web 1.37.1 → 1.37.2
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/worker/SharedSyncImplementation.umd.js +431 -445
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +431 -445
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/lib/package.json +3 -3
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -1698,7 +1698,6 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1698
1698
|
/* harmony export */ DEFAULT_LOCK_TIMEOUT_MS: () => (/* binding */ DEFAULT_LOCK_TIMEOUT_MS),
|
|
1699
1699
|
/* harmony export */ DEFAULT_POWERSYNC_CLOSE_OPTIONS: () => (/* binding */ DEFAULT_POWERSYNC_CLOSE_OPTIONS),
|
|
1700
1700
|
/* harmony export */ DEFAULT_POWERSYNC_DB_OPTIONS: () => (/* binding */ DEFAULT_POWERSYNC_DB_OPTIONS),
|
|
1701
|
-
/* harmony export */ DEFAULT_PRESSURE_LIMITS: () => (/* binding */ DEFAULT_PRESSURE_LIMITS),
|
|
1702
1701
|
/* harmony export */ DEFAULT_REMOTE_LOGGER: () => (/* binding */ DEFAULT_REMOTE_LOGGER),
|
|
1703
1702
|
/* harmony export */ DEFAULT_REMOTE_OPTIONS: () => (/* binding */ DEFAULT_REMOTE_OPTIONS),
|
|
1704
1703
|
/* harmony export */ DEFAULT_RETRY_DELAY_MS: () => (/* binding */ DEFAULT_RETRY_DELAY_MS),
|
|
@@ -1709,7 +1708,6 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
1709
1708
|
/* harmony export */ DEFAULT_TABLE_OPTIONS: () => (/* binding */ DEFAULT_TABLE_OPTIONS),
|
|
1710
1709
|
/* harmony export */ DEFAULT_WATCH_QUERY_OPTIONS: () => (/* binding */ DEFAULT_WATCH_QUERY_OPTIONS),
|
|
1711
1710
|
/* harmony export */ DEFAULT_WATCH_THROTTLE_MS: () => (/* binding */ DEFAULT_WATCH_THROTTLE_MS),
|
|
1712
|
-
/* harmony export */ DataStream: () => (/* binding */ DataStream),
|
|
1713
1711
|
/* harmony export */ DiffTriggerOperation: () => (/* binding */ DiffTriggerOperation),
|
|
1714
1712
|
/* harmony export */ DifferentialQueryProcessor: () => (/* binding */ DifferentialQueryProcessor),
|
|
1715
1713
|
/* harmony export */ EMPTY_DIFFERENTIAL: () => (/* binding */ EMPTY_DIFFERENTIAL),
|
|
@@ -3173,6 +3171,8 @@ var EncodingType;
|
|
|
3173
3171
|
EncodingType["Base64"] = "base64";
|
|
3174
3172
|
})(EncodingType || (EncodingType = {}));
|
|
3175
3173
|
|
|
3174
|
+
const symbolAsyncIterator = Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
|
|
3175
|
+
|
|
3176
3176
|
function getDefaultExportFromCjs (x) {
|
|
3177
3177
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
|
|
3178
3178
|
}
|
|
@@ -3253,7 +3253,7 @@ function requireEventIterator () {
|
|
|
3253
3253
|
this.removeCallback();
|
|
3254
3254
|
});
|
|
3255
3255
|
}
|
|
3256
|
-
[
|
|
3256
|
+
[symbolAsyncIterator]() {
|
|
3257
3257
|
return {
|
|
3258
3258
|
next: (value) => {
|
|
3259
3259
|
const result = this.pushQueue.shift();
|
|
@@ -3300,7 +3300,7 @@ function requireEventIterator () {
|
|
|
3300
3300
|
queue.eventHandlers[event] = fn;
|
|
3301
3301
|
},
|
|
3302
3302
|
}) || (() => { });
|
|
3303
|
-
this[
|
|
3303
|
+
this[symbolAsyncIterator] = () => queue[symbolAsyncIterator]();
|
|
3304
3304
|
Object.freeze(this);
|
|
3305
3305
|
}
|
|
3306
3306
|
}
|
|
@@ -4182,15 +4182,6 @@ class ControlledExecutor {
|
|
|
4182
4182
|
}
|
|
4183
4183
|
}
|
|
4184
4184
|
|
|
4185
|
-
/**
|
|
4186
|
-
* A ponyfill for `Symbol.asyncIterator` that is compatible with the
|
|
4187
|
-
* [recommended 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)
|
|
4188
|
-
* we recommend for React Native.
|
|
4189
|
-
*
|
|
4190
|
-
* As long as we use this symbol (instead of `for await` and `async *`) in this package, we can be compatible with async
|
|
4191
|
-
* iterators without requiring them.
|
|
4192
|
-
*/
|
|
4193
|
-
const symbolAsyncIterator = Symbol.asyncIterator ?? Symbol.for('Symbol.asyncIterator');
|
|
4194
4185
|
/**
|
|
4195
4186
|
* Throttle a function to be called at most once every "wait" milliseconds,
|
|
4196
4187
|
* on the trailing edge.
|
|
@@ -12527,177 +12518,10 @@ function requireDist () {
|
|
|
12527
12518
|
|
|
12528
12519
|
var distExports = requireDist();
|
|
12529
12520
|
|
|
12530
|
-
var version = "1.
|
|
12521
|
+
var version = "1.52.0";
|
|
12531
12522
|
var PACKAGE = {
|
|
12532
12523
|
version: version};
|
|
12533
12524
|
|
|
12534
|
-
const DEFAULT_PRESSURE_LIMITS = {
|
|
12535
|
-
highWater: 10,
|
|
12536
|
-
lowWater: 0
|
|
12537
|
-
};
|
|
12538
|
-
/**
|
|
12539
|
-
* A very basic implementation of a data stream with backpressure support which does not use
|
|
12540
|
-
* native JS streams or async iterators.
|
|
12541
|
-
* This is handy for environments such as React Native which need polyfills for the above.
|
|
12542
|
-
*/
|
|
12543
|
-
class DataStream extends BaseObserver {
|
|
12544
|
-
options;
|
|
12545
|
-
dataQueue;
|
|
12546
|
-
isClosed;
|
|
12547
|
-
processingPromise;
|
|
12548
|
-
notifyDataAdded;
|
|
12549
|
-
logger;
|
|
12550
|
-
mapLine;
|
|
12551
|
-
constructor(options) {
|
|
12552
|
-
super();
|
|
12553
|
-
this.options = options;
|
|
12554
|
-
this.processingPromise = null;
|
|
12555
|
-
this.isClosed = false;
|
|
12556
|
-
this.dataQueue = [];
|
|
12557
|
-
this.mapLine = options?.mapLine ?? ((line) => line);
|
|
12558
|
-
this.logger = options?.logger ?? Logger.get('DataStream');
|
|
12559
|
-
if (options?.closeOnError) {
|
|
12560
|
-
const l = this.registerListener({
|
|
12561
|
-
error: (ex) => {
|
|
12562
|
-
l?.();
|
|
12563
|
-
this.close();
|
|
12564
|
-
}
|
|
12565
|
-
});
|
|
12566
|
-
}
|
|
12567
|
-
}
|
|
12568
|
-
get highWatermark() {
|
|
12569
|
-
return this.options?.pressure?.highWaterMark ?? DEFAULT_PRESSURE_LIMITS.highWater;
|
|
12570
|
-
}
|
|
12571
|
-
get lowWatermark() {
|
|
12572
|
-
return this.options?.pressure?.lowWaterMark ?? DEFAULT_PRESSURE_LIMITS.lowWater;
|
|
12573
|
-
}
|
|
12574
|
-
get closed() {
|
|
12575
|
-
return this.isClosed;
|
|
12576
|
-
}
|
|
12577
|
-
async close() {
|
|
12578
|
-
this.isClosed = true;
|
|
12579
|
-
await this.processingPromise;
|
|
12580
|
-
this.iterateListeners((l) => l.closed?.());
|
|
12581
|
-
// Discard any data in the queue
|
|
12582
|
-
this.dataQueue = [];
|
|
12583
|
-
this.listeners.clear();
|
|
12584
|
-
}
|
|
12585
|
-
/**
|
|
12586
|
-
* Enqueues data for the consumers to read
|
|
12587
|
-
*/
|
|
12588
|
-
enqueueData(data) {
|
|
12589
|
-
if (this.isClosed) {
|
|
12590
|
-
throw new Error('Cannot enqueue data into closed stream.');
|
|
12591
|
-
}
|
|
12592
|
-
this.dataQueue.push(data);
|
|
12593
|
-
this.notifyDataAdded?.();
|
|
12594
|
-
this.processQueue();
|
|
12595
|
-
}
|
|
12596
|
-
/**
|
|
12597
|
-
* Reads data once from the data stream
|
|
12598
|
-
* @returns a Data payload or Null if the stream closed.
|
|
12599
|
-
*/
|
|
12600
|
-
async read() {
|
|
12601
|
-
if (this.closed) {
|
|
12602
|
-
return null;
|
|
12603
|
-
}
|
|
12604
|
-
// Wait for any pending processing to complete first.
|
|
12605
|
-
// This ensures we register our listener before calling processQueue(),
|
|
12606
|
-
// avoiding a race where processQueue() sees no reader and returns early.
|
|
12607
|
-
if (this.processingPromise) {
|
|
12608
|
-
await this.processingPromise;
|
|
12609
|
-
}
|
|
12610
|
-
// Re-check after await - stream may have closed while we were waiting
|
|
12611
|
-
if (this.closed) {
|
|
12612
|
-
return null;
|
|
12613
|
-
}
|
|
12614
|
-
return new Promise((resolve, reject) => {
|
|
12615
|
-
const l = this.registerListener({
|
|
12616
|
-
data: async (data) => {
|
|
12617
|
-
resolve(data);
|
|
12618
|
-
// Remove the listener
|
|
12619
|
-
l?.();
|
|
12620
|
-
},
|
|
12621
|
-
closed: () => {
|
|
12622
|
-
resolve(null);
|
|
12623
|
-
l?.();
|
|
12624
|
-
},
|
|
12625
|
-
error: (ex) => {
|
|
12626
|
-
reject(ex);
|
|
12627
|
-
l?.();
|
|
12628
|
-
}
|
|
12629
|
-
});
|
|
12630
|
-
this.processQueue();
|
|
12631
|
-
});
|
|
12632
|
-
}
|
|
12633
|
-
/**
|
|
12634
|
-
* Executes a callback for each data item in the stream
|
|
12635
|
-
*/
|
|
12636
|
-
forEach(callback) {
|
|
12637
|
-
if (this.dataQueue.length <= this.lowWatermark) {
|
|
12638
|
-
this.iterateAsyncErrored(async (l) => l.lowWater?.());
|
|
12639
|
-
}
|
|
12640
|
-
return this.registerListener({
|
|
12641
|
-
data: callback
|
|
12642
|
-
});
|
|
12643
|
-
}
|
|
12644
|
-
processQueue() {
|
|
12645
|
-
if (this.processingPromise) {
|
|
12646
|
-
return;
|
|
12647
|
-
}
|
|
12648
|
-
const promise = (this.processingPromise = this._processQueue());
|
|
12649
|
-
promise.finally(() => {
|
|
12650
|
-
this.processingPromise = null;
|
|
12651
|
-
});
|
|
12652
|
-
return promise;
|
|
12653
|
-
}
|
|
12654
|
-
hasDataReader() {
|
|
12655
|
-
return Array.from(this.listeners.values()).some((l) => !!l.data);
|
|
12656
|
-
}
|
|
12657
|
-
async _processQueue() {
|
|
12658
|
-
/**
|
|
12659
|
-
* Allow listeners to mutate the queue before processing.
|
|
12660
|
-
* This allows for operations such as dropping or compressing data
|
|
12661
|
-
* on high water or requesting more data on low water.
|
|
12662
|
-
*/
|
|
12663
|
-
if (this.dataQueue.length >= this.highWatermark) {
|
|
12664
|
-
await this.iterateAsyncErrored(async (l) => l.highWater?.());
|
|
12665
|
-
}
|
|
12666
|
-
if (this.isClosed || !this.hasDataReader()) {
|
|
12667
|
-
return;
|
|
12668
|
-
}
|
|
12669
|
-
if (this.dataQueue.length) {
|
|
12670
|
-
const data = this.dataQueue.shift();
|
|
12671
|
-
const mapped = this.mapLine(data);
|
|
12672
|
-
await this.iterateAsyncErrored(async (l) => l.data?.(mapped));
|
|
12673
|
-
}
|
|
12674
|
-
if (this.dataQueue.length <= this.lowWatermark) {
|
|
12675
|
-
const dataAdded = new Promise((resolve) => {
|
|
12676
|
-
this.notifyDataAdded = resolve;
|
|
12677
|
-
});
|
|
12678
|
-
await Promise.race([this.iterateAsyncErrored(async (l) => l.lowWater?.()), dataAdded]);
|
|
12679
|
-
this.notifyDataAdded = null;
|
|
12680
|
-
}
|
|
12681
|
-
if (this.dataQueue.length > 0) {
|
|
12682
|
-
setTimeout(() => this.processQueue());
|
|
12683
|
-
}
|
|
12684
|
-
}
|
|
12685
|
-
async iterateAsyncErrored(cb) {
|
|
12686
|
-
// Important: We need to copy the listeners, as calling a listener could result in adding another
|
|
12687
|
-
// listener, resulting in infinite loops.
|
|
12688
|
-
const listeners = Array.from(this.listeners.values());
|
|
12689
|
-
for (let i of listeners) {
|
|
12690
|
-
try {
|
|
12691
|
-
await cb(i);
|
|
12692
|
-
}
|
|
12693
|
-
catch (ex) {
|
|
12694
|
-
this.logger.error(ex);
|
|
12695
|
-
this.iterateListeners((l) => l.error?.(ex));
|
|
12696
|
-
}
|
|
12697
|
-
}
|
|
12698
|
-
}
|
|
12699
|
-
}
|
|
12700
|
-
|
|
12701
12525
|
var WebsocketDuplexConnection = {};
|
|
12702
12526
|
|
|
12703
12527
|
var hasRequiredWebsocketDuplexConnection;
|
|
@@ -12860,8 +12684,215 @@ class WebsocketClientTransport {
|
|
|
12860
12684
|
}
|
|
12861
12685
|
}
|
|
12862
12686
|
|
|
12687
|
+
const doneResult = { done: true, value: undefined };
|
|
12688
|
+
function valueResult(value) {
|
|
12689
|
+
return { done: false, value };
|
|
12690
|
+
}
|
|
12691
|
+
/**
|
|
12692
|
+
* A variant of {@link Array.map} for async iterators.
|
|
12693
|
+
*/
|
|
12694
|
+
function map(source, map) {
|
|
12695
|
+
return {
|
|
12696
|
+
next: async () => {
|
|
12697
|
+
const value = await source.next();
|
|
12698
|
+
if (value.done) {
|
|
12699
|
+
return value;
|
|
12700
|
+
}
|
|
12701
|
+
else {
|
|
12702
|
+
return { value: map(value.value) };
|
|
12703
|
+
}
|
|
12704
|
+
}
|
|
12705
|
+
};
|
|
12706
|
+
}
|
|
12707
|
+
/**
|
|
12708
|
+
* Expands a source async iterator by allowing to inject events asynchronously.
|
|
12709
|
+
*
|
|
12710
|
+
* The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
|
|
12711
|
+
* events are dropped once the main iterator completes, but are otherwise forwarded.
|
|
12712
|
+
*
|
|
12713
|
+
* The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
|
|
12714
|
+
* in response to a `next()` call from downstream if no pending injected events can be dispatched.
|
|
12715
|
+
*/
|
|
12716
|
+
function injectable(source) {
|
|
12717
|
+
let sourceIsDone = false;
|
|
12718
|
+
let waiter = undefined; // An active, waiting next() call.
|
|
12719
|
+
// A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
|
|
12720
|
+
let pendingSourceEvent = null;
|
|
12721
|
+
let pendingInjectedEvents = [];
|
|
12722
|
+
const consumeWaiter = () => {
|
|
12723
|
+
const pending = waiter;
|
|
12724
|
+
waiter = undefined;
|
|
12725
|
+
return pending;
|
|
12726
|
+
};
|
|
12727
|
+
const fetchFromSource = () => {
|
|
12728
|
+
const resolveWaiter = (propagate) => {
|
|
12729
|
+
const active = consumeWaiter();
|
|
12730
|
+
if (active) {
|
|
12731
|
+
propagate(active);
|
|
12732
|
+
}
|
|
12733
|
+
else {
|
|
12734
|
+
pendingSourceEvent = propagate;
|
|
12735
|
+
}
|
|
12736
|
+
};
|
|
12737
|
+
const nextFromSource = source.next();
|
|
12738
|
+
nextFromSource.then((value) => {
|
|
12739
|
+
sourceIsDone = value.done == true;
|
|
12740
|
+
resolveWaiter((w) => w.resolve(value));
|
|
12741
|
+
}, (error) => {
|
|
12742
|
+
resolveWaiter((w) => w.reject(error));
|
|
12743
|
+
});
|
|
12744
|
+
};
|
|
12745
|
+
return {
|
|
12746
|
+
next: () => {
|
|
12747
|
+
return new Promise((resolve, reject) => {
|
|
12748
|
+
// First priority: Dispatch ready upstream events.
|
|
12749
|
+
if (sourceIsDone) {
|
|
12750
|
+
return resolve(doneResult);
|
|
12751
|
+
}
|
|
12752
|
+
if (pendingSourceEvent) {
|
|
12753
|
+
pendingSourceEvent({ resolve, reject });
|
|
12754
|
+
pendingSourceEvent = null;
|
|
12755
|
+
return;
|
|
12756
|
+
}
|
|
12757
|
+
// Second priority: Dispatch injected events
|
|
12758
|
+
if (pendingInjectedEvents.length) {
|
|
12759
|
+
return resolve(valueResult(pendingInjectedEvents.shift()));
|
|
12760
|
+
}
|
|
12761
|
+
// Nothing pending? Fetch from source
|
|
12762
|
+
waiter = { resolve, reject };
|
|
12763
|
+
return fetchFromSource();
|
|
12764
|
+
});
|
|
12765
|
+
},
|
|
12766
|
+
inject: (event) => {
|
|
12767
|
+
const pending = consumeWaiter();
|
|
12768
|
+
if (pending != null) {
|
|
12769
|
+
pending.resolve(valueResult(event));
|
|
12770
|
+
}
|
|
12771
|
+
else {
|
|
12772
|
+
pendingInjectedEvents.push(event);
|
|
12773
|
+
}
|
|
12774
|
+
}
|
|
12775
|
+
};
|
|
12776
|
+
}
|
|
12777
|
+
/**
|
|
12778
|
+
* Splits a byte stream at line endings, emitting each line as a string.
|
|
12779
|
+
*/
|
|
12780
|
+
function extractJsonLines(source, decoder) {
|
|
12781
|
+
let buffer = '';
|
|
12782
|
+
const pendingLines = [];
|
|
12783
|
+
let isFinalEvent = false;
|
|
12784
|
+
return {
|
|
12785
|
+
next: async () => {
|
|
12786
|
+
while (true) {
|
|
12787
|
+
if (isFinalEvent) {
|
|
12788
|
+
return doneResult;
|
|
12789
|
+
}
|
|
12790
|
+
{
|
|
12791
|
+
const first = pendingLines.shift();
|
|
12792
|
+
if (first) {
|
|
12793
|
+
return { done: false, value: first };
|
|
12794
|
+
}
|
|
12795
|
+
}
|
|
12796
|
+
const { done, value } = await source.next();
|
|
12797
|
+
if (done) {
|
|
12798
|
+
const remaining = buffer.trim();
|
|
12799
|
+
if (remaining.length != 0) {
|
|
12800
|
+
isFinalEvent = true;
|
|
12801
|
+
return { done: false, value: remaining };
|
|
12802
|
+
}
|
|
12803
|
+
return doneResult;
|
|
12804
|
+
}
|
|
12805
|
+
const data = decoder.decode(value, { stream: true });
|
|
12806
|
+
buffer += data;
|
|
12807
|
+
const lines = buffer.split('\n');
|
|
12808
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
12809
|
+
const l = lines[i].trim();
|
|
12810
|
+
if (l.length > 0) {
|
|
12811
|
+
pendingLines.push(l);
|
|
12812
|
+
}
|
|
12813
|
+
}
|
|
12814
|
+
buffer = lines[lines.length - 1];
|
|
12815
|
+
}
|
|
12816
|
+
}
|
|
12817
|
+
};
|
|
12818
|
+
}
|
|
12819
|
+
/**
|
|
12820
|
+
* Splits a concatenated stream of BSON objects by emitting individual objects.
|
|
12821
|
+
*/
|
|
12822
|
+
function extractBsonObjects(source) {
|
|
12823
|
+
// Fully read but not emitted yet.
|
|
12824
|
+
const completedObjects = [];
|
|
12825
|
+
// Whether source has returned { done: true }. We do the same once completed objects have been emitted.
|
|
12826
|
+
let isDone = false;
|
|
12827
|
+
const lengthBuffer = new DataView(new ArrayBuffer(4));
|
|
12828
|
+
let objectBody = null;
|
|
12829
|
+
// If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
|
|
12830
|
+
// If we're consuming a document, the bytes remaining.
|
|
12831
|
+
let remainingLength = 4;
|
|
12832
|
+
return {
|
|
12833
|
+
async next() {
|
|
12834
|
+
while (true) {
|
|
12835
|
+
// Before fetching new data from upstream, return completed objects.
|
|
12836
|
+
if (completedObjects.length) {
|
|
12837
|
+
return valueResult(completedObjects.shift());
|
|
12838
|
+
}
|
|
12839
|
+
if (isDone) {
|
|
12840
|
+
return doneResult;
|
|
12841
|
+
}
|
|
12842
|
+
const upstreamEvent = await source.next();
|
|
12843
|
+
if (upstreamEvent.done) {
|
|
12844
|
+
isDone = true;
|
|
12845
|
+
if (objectBody || remainingLength != 4) {
|
|
12846
|
+
throw new Error('illegal end of stream in BSON object');
|
|
12847
|
+
}
|
|
12848
|
+
return doneResult;
|
|
12849
|
+
}
|
|
12850
|
+
const chunk = upstreamEvent.value;
|
|
12851
|
+
for (let i = 0; i < chunk.length;) {
|
|
12852
|
+
const availableInData = chunk.length - i;
|
|
12853
|
+
if (objectBody) {
|
|
12854
|
+
// We're in the middle of reading a BSON document.
|
|
12855
|
+
const bytesToRead = Math.min(availableInData, remainingLength);
|
|
12856
|
+
const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
|
|
12857
|
+
objectBody.set(copySource, objectBody.length - remainingLength);
|
|
12858
|
+
i += bytesToRead;
|
|
12859
|
+
remainingLength -= bytesToRead;
|
|
12860
|
+
if (remainingLength == 0) {
|
|
12861
|
+
completedObjects.push(objectBody);
|
|
12862
|
+
// Prepare to read another document, starting with its length
|
|
12863
|
+
objectBody = null;
|
|
12864
|
+
remainingLength = 4;
|
|
12865
|
+
}
|
|
12866
|
+
}
|
|
12867
|
+
else {
|
|
12868
|
+
// Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
|
|
12869
|
+
const bytesToRead = Math.min(availableInData, remainingLength);
|
|
12870
|
+
for (let j = 0; j < bytesToRead; j++) {
|
|
12871
|
+
lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
|
|
12872
|
+
}
|
|
12873
|
+
i += bytesToRead;
|
|
12874
|
+
remainingLength -= bytesToRead;
|
|
12875
|
+
if (remainingLength == 0) {
|
|
12876
|
+
// Transition from reading length header to reading document. Subtracting 4 because the length of the
|
|
12877
|
+
// header is included in length.
|
|
12878
|
+
const length = lengthBuffer.getInt32(0, true /* little endian */);
|
|
12879
|
+
remainingLength = length - 4;
|
|
12880
|
+
if (remainingLength < 1) {
|
|
12881
|
+
throw new Error(`invalid length for bson: ${length}`);
|
|
12882
|
+
}
|
|
12883
|
+
objectBody = new Uint8Array(length);
|
|
12884
|
+
new DataView(objectBody.buffer).setInt32(0, length, true);
|
|
12885
|
+
}
|
|
12886
|
+
}
|
|
12887
|
+
}
|
|
12888
|
+
}
|
|
12889
|
+
}
|
|
12890
|
+
};
|
|
12891
|
+
}
|
|
12892
|
+
|
|
12863
12893
|
const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
|
|
12864
12894
|
const POWERSYNC_JS_VERSION = PACKAGE.version;
|
|
12895
|
+
const SYNC_QUEUE_REQUEST_HIGH_WATER = 10;
|
|
12865
12896
|
const SYNC_QUEUE_REQUEST_LOW_WATER = 5;
|
|
12866
12897
|
// Keep alive message is sent every period
|
|
12867
12898
|
const KEEP_ALIVE_MS = 20_000;
|
|
@@ -13041,13 +13072,14 @@ class AbstractRemote {
|
|
|
13041
13072
|
return new WebSocket(url);
|
|
13042
13073
|
}
|
|
13043
13074
|
/**
|
|
13044
|
-
* Returns a data stream of sync line data.
|
|
13075
|
+
* Returns a data stream of sync line data, fetched via RSocket-over-WebSocket.
|
|
13076
|
+
*
|
|
13077
|
+
* The only mechanism to abort the returned stream is to use the abort signal in {@link SocketSyncStreamOptions}.
|
|
13045
13078
|
*
|
|
13046
|
-
* @param map Maps received payload frames to the typed event value.
|
|
13047
13079
|
* @param bson A BSON encoder and decoder. When set, the data stream will be requested with a BSON payload
|
|
13048
13080
|
* (required for compatibility with older sync services).
|
|
13049
13081
|
*/
|
|
13050
|
-
async socketStreamRaw(options,
|
|
13082
|
+
async socketStreamRaw(options, bson) {
|
|
13051
13083
|
const { path, fetchStrategy = FetchStrategy.Buffered } = options;
|
|
13052
13084
|
const mimeType = bson == null ? 'application/json' : 'application/bson';
|
|
13053
13085
|
function toBuffer(js) {
|
|
@@ -13062,52 +13094,55 @@ class AbstractRemote {
|
|
|
13062
13094
|
}
|
|
13063
13095
|
const syncQueueRequestSize = fetchStrategy == FetchStrategy.Buffered ? 10 : 1;
|
|
13064
13096
|
const request = await this.buildRequest(path);
|
|
13097
|
+
const url = this.options.socketUrlTransformer(request.url);
|
|
13065
13098
|
// Add the user agent in the setup payload - we can't set custom
|
|
13066
13099
|
// headers with websockets on web. The browser userAgent is however added
|
|
13067
13100
|
// automatically as a header.
|
|
13068
13101
|
const userAgent = this.getUserAgent();
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
|
|
13075
|
-
|
|
13102
|
+
// While we're connecting (a process that can't be aborted in RSocket), the WebSocket instance to close if we wanted
|
|
13103
|
+
// to abort the connection.
|
|
13104
|
+
let pendingSocket = null;
|
|
13105
|
+
let keepAliveTimeout;
|
|
13106
|
+
let rsocket = null;
|
|
13107
|
+
let queue = null;
|
|
13108
|
+
let didClose = false;
|
|
13109
|
+
const abortRequest = () => {
|
|
13110
|
+
if (didClose) {
|
|
13111
|
+
return;
|
|
13112
|
+
}
|
|
13113
|
+
didClose = true;
|
|
13114
|
+
clearTimeout(keepAliveTimeout);
|
|
13115
|
+
if (pendingSocket) {
|
|
13116
|
+
pendingSocket.close();
|
|
13117
|
+
}
|
|
13118
|
+
if (rsocket) {
|
|
13119
|
+
rsocket.close();
|
|
13120
|
+
}
|
|
13121
|
+
if (queue) {
|
|
13122
|
+
queue.stop();
|
|
13123
|
+
}
|
|
13124
|
+
};
|
|
13076
13125
|
// Handle upstream abort
|
|
13077
|
-
if (options.abortSignal
|
|
13126
|
+
if (options.abortSignal.aborted) {
|
|
13078
13127
|
throw new AbortOperation('Connection request aborted');
|
|
13079
13128
|
}
|
|
13080
13129
|
else {
|
|
13081
|
-
options.abortSignal
|
|
13082
|
-
stream.close();
|
|
13083
|
-
}, { once: true });
|
|
13130
|
+
options.abortSignal.addEventListener('abort', abortRequest);
|
|
13084
13131
|
}
|
|
13085
|
-
let keepAliveTimeout;
|
|
13086
13132
|
const resetTimeout = () => {
|
|
13087
13133
|
clearTimeout(keepAliveTimeout);
|
|
13088
13134
|
keepAliveTimeout = setTimeout(() => {
|
|
13089
13135
|
this.logger.error(`No data received on WebSocket in ${SOCKET_TIMEOUT_MS}ms, closing connection.`);
|
|
13090
|
-
|
|
13136
|
+
abortRequest();
|
|
13091
13137
|
}, SOCKET_TIMEOUT_MS);
|
|
13092
13138
|
};
|
|
13093
13139
|
resetTimeout();
|
|
13094
|
-
// Typescript complains about this being `never` if it's not assigned here.
|
|
13095
|
-
// This is assigned in `wsCreator`.
|
|
13096
|
-
let disposeSocketConnectionTimeout = () => { };
|
|
13097
|
-
const url = this.options.socketUrlTransformer(request.url);
|
|
13098
13140
|
const connector = new distExports.RSocketConnector({
|
|
13099
13141
|
transport: new WebsocketClientTransport({
|
|
13100
13142
|
url,
|
|
13101
13143
|
wsCreator: (url) => {
|
|
13102
|
-
const socket = this.createSocket(url);
|
|
13103
|
-
|
|
13104
|
-
closed: () => {
|
|
13105
|
-
// Allow closing the underlying WebSocket if the stream was closed before the
|
|
13106
|
-
// RSocket connect completed. This should effectively abort the request.
|
|
13107
|
-
socket.close();
|
|
13108
|
-
}
|
|
13109
|
-
});
|
|
13110
|
-
socket.addEventListener('message', (event) => {
|
|
13144
|
+
const socket = (pendingSocket = this.createSocket(url));
|
|
13145
|
+
socket.addEventListener('message', () => {
|
|
13111
13146
|
resetTimeout();
|
|
13112
13147
|
});
|
|
13113
13148
|
return socket;
|
|
@@ -13127,43 +13162,40 @@ class AbstractRemote {
|
|
|
13127
13162
|
}
|
|
13128
13163
|
}
|
|
13129
13164
|
});
|
|
13130
|
-
let rsocket;
|
|
13131
13165
|
try {
|
|
13132
13166
|
rsocket = await connector.connect();
|
|
13133
13167
|
// The connection is established, we no longer need to monitor the initial timeout
|
|
13134
|
-
|
|
13168
|
+
pendingSocket = null;
|
|
13135
13169
|
}
|
|
13136
13170
|
catch (ex) {
|
|
13137
13171
|
this.logger.error(`Failed to connect WebSocket`, ex);
|
|
13138
|
-
|
|
13139
|
-
if (!stream.closed) {
|
|
13140
|
-
await stream.close();
|
|
13141
|
-
}
|
|
13172
|
+
abortRequest();
|
|
13142
13173
|
throw ex;
|
|
13143
13174
|
}
|
|
13144
13175
|
resetTimeout();
|
|
13145
|
-
let socketIsClosed = false;
|
|
13146
|
-
const closeSocket = () => {
|
|
13147
|
-
clearTimeout(keepAliveTimeout);
|
|
13148
|
-
if (socketIsClosed) {
|
|
13149
|
-
return;
|
|
13150
|
-
}
|
|
13151
|
-
socketIsClosed = true;
|
|
13152
|
-
rsocket.close();
|
|
13153
|
-
};
|
|
13154
13176
|
// Helps to prevent double close scenarios
|
|
13155
|
-
rsocket.onClose(() => (
|
|
13156
|
-
|
|
13157
|
-
let pendingEventsCount = syncQueueRequestSize;
|
|
13158
|
-
const disposeClosedListener = stream.registerListener({
|
|
13159
|
-
closed: () => {
|
|
13160
|
-
closeSocket();
|
|
13161
|
-
disposeClosedListener();
|
|
13162
|
-
}
|
|
13163
|
-
});
|
|
13164
|
-
const socket = await new Promise((resolve, reject) => {
|
|
13177
|
+
rsocket.onClose(() => (rsocket = null));
|
|
13178
|
+
return await new Promise((resolve, reject) => {
|
|
13165
13179
|
let connectionEstablished = false;
|
|
13166
|
-
|
|
13180
|
+
let pendingEventsCount = syncQueueRequestSize;
|
|
13181
|
+
let paused = false;
|
|
13182
|
+
let res = null;
|
|
13183
|
+
function requestMore() {
|
|
13184
|
+
const delta = syncQueueRequestSize - pendingEventsCount;
|
|
13185
|
+
if (!paused && delta > 0) {
|
|
13186
|
+
res?.request(delta);
|
|
13187
|
+
pendingEventsCount = syncQueueRequestSize;
|
|
13188
|
+
}
|
|
13189
|
+
}
|
|
13190
|
+
const events = new domExports.EventIterator((q) => {
|
|
13191
|
+
queue = q;
|
|
13192
|
+
q.on('highWater', () => (paused = true));
|
|
13193
|
+
q.on('lowWater', () => {
|
|
13194
|
+
paused = false;
|
|
13195
|
+
requestMore();
|
|
13196
|
+
});
|
|
13197
|
+
}, { highWaterMark: SYNC_QUEUE_REQUEST_HIGH_WATER, lowWaterMark: SYNC_QUEUE_REQUEST_LOW_WATER })[symbolAsyncIterator]();
|
|
13198
|
+
res = rsocket.requestStream({
|
|
13167
13199
|
data: toBuffer(options.data),
|
|
13168
13200
|
metadata: toBuffer({
|
|
13169
13201
|
path
|
|
@@ -13188,7 +13220,7 @@ class AbstractRemote {
|
|
|
13188
13220
|
}
|
|
13189
13221
|
// RSocket will close the RSocket stream automatically
|
|
13190
13222
|
// Close the downstream stream as well - this will close the RSocket connection and WebSocket
|
|
13191
|
-
|
|
13223
|
+
abortRequest();
|
|
13192
13224
|
// Handles cases where the connection failed e.g. auth error or connection error
|
|
13193
13225
|
if (!connectionEstablished) {
|
|
13194
13226
|
reject(e);
|
|
@@ -13198,41 +13230,40 @@ class AbstractRemote {
|
|
|
13198
13230
|
// The connection is active
|
|
13199
13231
|
if (!connectionEstablished) {
|
|
13200
13232
|
connectionEstablished = true;
|
|
13201
|
-
resolve(
|
|
13233
|
+
resolve(events);
|
|
13202
13234
|
}
|
|
13203
13235
|
const { data } = payload;
|
|
13236
|
+
if (data) {
|
|
13237
|
+
queue.push(data);
|
|
13238
|
+
}
|
|
13204
13239
|
// Less events are now pending
|
|
13205
13240
|
pendingEventsCount--;
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
}
|
|
13209
|
-
stream.enqueueData(data);
|
|
13241
|
+
// Request another event (unless the downstream consumer is paused).
|
|
13242
|
+
requestMore();
|
|
13210
13243
|
},
|
|
13211
13244
|
onComplete: () => {
|
|
13212
|
-
|
|
13245
|
+
abortRequest(); // this will also emit a done event
|
|
13213
13246
|
},
|
|
13214
13247
|
onExtension: () => { }
|
|
13215
13248
|
});
|
|
13216
13249
|
});
|
|
13217
|
-
const l = stream.registerListener({
|
|
13218
|
-
lowWater: async () => {
|
|
13219
|
-
// Request to fill up the queue
|
|
13220
|
-
const required = syncQueueRequestSize - pendingEventsCount;
|
|
13221
|
-
if (required > 0) {
|
|
13222
|
-
socket.request(syncQueueRequestSize - pendingEventsCount);
|
|
13223
|
-
pendingEventsCount = syncQueueRequestSize;
|
|
13224
|
-
}
|
|
13225
|
-
},
|
|
13226
|
-
closed: () => {
|
|
13227
|
-
l();
|
|
13228
|
-
}
|
|
13229
|
-
});
|
|
13230
|
-
return stream;
|
|
13231
13250
|
}
|
|
13232
13251
|
/**
|
|
13233
|
-
*
|
|
13252
|
+
* @returns Whether the HTTP implementation on this platform can receive streamed binary responses. This is true on
|
|
13253
|
+
* all platforms except React Native (who would have guessed...), where we must not request BSON responses.
|
|
13254
|
+
*
|
|
13255
|
+
* @see https://github.com/react-native-community/fetch?tab=readme-ov-file#motivation
|
|
13256
|
+
*/
|
|
13257
|
+
get supportsStreamingBinaryResponses() {
|
|
13258
|
+
return true;
|
|
13259
|
+
}
|
|
13260
|
+
/**
|
|
13261
|
+
* Posts a `/sync/stream` request, asserts that it completes successfully and returns the streaming response as an
|
|
13262
|
+
* async iterator of byte blobs.
|
|
13263
|
+
*
|
|
13264
|
+
* To cancel the async iterator, use the abort signal from {@link SyncStreamOptions} passed to this method.
|
|
13234
13265
|
*/
|
|
13235
|
-
async
|
|
13266
|
+
async fetchStreamRaw(options) {
|
|
13236
13267
|
const { data, path, headers, abortSignal } = options;
|
|
13237
13268
|
const request = await this.buildRequest(path);
|
|
13238
13269
|
/**
|
|
@@ -13244,119 +13275,94 @@ class AbstractRemote {
|
|
|
13244
13275
|
* Aborting the active fetch request while it is being consumed seems to throw
|
|
13245
13276
|
* an unhandled exception on the window level.
|
|
13246
13277
|
*/
|
|
13247
|
-
if (abortSignal
|
|
13248
|
-
throw new AbortOperation('Abort request received before making
|
|
13278
|
+
if (abortSignal.aborted) {
|
|
13279
|
+
throw new AbortOperation('Abort request received before making fetchStreamRaw request');
|
|
13249
13280
|
}
|
|
13250
13281
|
const controller = new AbortController();
|
|
13251
|
-
let
|
|
13252
|
-
abortSignal
|
|
13253
|
-
|
|
13282
|
+
let reader = null;
|
|
13283
|
+
abortSignal.addEventListener('abort', () => {
|
|
13284
|
+
const reason = abortSignal.reason ??
|
|
13285
|
+
new AbortOperation('Cancelling network request before it resolves. Abort signal has been received.');
|
|
13286
|
+
if (reader == null) {
|
|
13254
13287
|
// Only abort via the abort controller if the request has not resolved yet
|
|
13255
|
-
controller.abort(
|
|
13256
|
-
|
|
13288
|
+
controller.abort(reason);
|
|
13289
|
+
}
|
|
13290
|
+
else {
|
|
13291
|
+
reader.cancel(reason).catch(() => {
|
|
13292
|
+
// Cancelling the reader might rethrow an exception we would have handled by throwing in next(). So we can
|
|
13293
|
+
// ignore it here.
|
|
13294
|
+
});
|
|
13257
13295
|
}
|
|
13258
13296
|
});
|
|
13259
|
-
|
|
13260
|
-
|
|
13261
|
-
|
|
13262
|
-
|
|
13263
|
-
|
|
13264
|
-
|
|
13265
|
-
|
|
13266
|
-
|
|
13267
|
-
|
|
13297
|
+
let res;
|
|
13298
|
+
let responseIsBson = false;
|
|
13299
|
+
try {
|
|
13300
|
+
const ndJson = 'application/x-ndjson';
|
|
13301
|
+
const bson = 'application/vnd.powersync.bson-stream';
|
|
13302
|
+
res = await this.fetch(request.url, {
|
|
13303
|
+
method: 'POST',
|
|
13304
|
+
headers: {
|
|
13305
|
+
...headers,
|
|
13306
|
+
...request.headers,
|
|
13307
|
+
accept: this.supportsStreamingBinaryResponses ? `${bson};q=0.9,${ndJson};q=0.8` : ndJson
|
|
13308
|
+
},
|
|
13309
|
+
body: JSON.stringify(data),
|
|
13310
|
+
signal: controller.signal,
|
|
13311
|
+
cache: 'no-store',
|
|
13312
|
+
...(this.options.fetchOptions ?? {}),
|
|
13313
|
+
...options.fetchOptions
|
|
13314
|
+
});
|
|
13315
|
+
if (!res.ok || !res.body) {
|
|
13316
|
+
const text = await res.text();
|
|
13317
|
+
this.logger.error(`Could not POST streaming to ${path} - ${res.status} - ${res.statusText}: ${text}`);
|
|
13318
|
+
const error = new Error(`HTTP ${res.statusText}: ${text}`);
|
|
13319
|
+
error.status = res.status;
|
|
13320
|
+
throw error;
|
|
13321
|
+
}
|
|
13322
|
+
const contentType = res.headers.get('content-type');
|
|
13323
|
+
responseIsBson = contentType == bson;
|
|
13324
|
+
}
|
|
13325
|
+
catch (ex) {
|
|
13268
13326
|
if (ex.name == 'AbortError') {
|
|
13269
13327
|
throw new AbortOperation(`Pending fetch request to ${request.url} has been aborted.`);
|
|
13270
13328
|
}
|
|
13271
13329
|
throw ex;
|
|
13272
|
-
});
|
|
13273
|
-
if (!res) {
|
|
13274
|
-
throw new Error('Fetch request was aborted');
|
|
13275
13330
|
}
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
13281
|
-
error.status = res.status;
|
|
13282
|
-
throw error;
|
|
13283
|
-
}
|
|
13284
|
-
// Create a new stream splitting the response at line endings while also handling cancellations
|
|
13285
|
-
// by closing the reader.
|
|
13286
|
-
const reader = res.body.getReader();
|
|
13287
|
-
let readerReleased = false;
|
|
13288
|
-
// This will close the network request and read stream
|
|
13289
|
-
const closeReader = async () => {
|
|
13290
|
-
try {
|
|
13291
|
-
readerReleased = true;
|
|
13292
|
-
await reader.cancel();
|
|
13293
|
-
}
|
|
13294
|
-
catch (ex) {
|
|
13295
|
-
// an error will throw if the reader hasn't been used yet
|
|
13296
|
-
}
|
|
13297
|
-
reader.releaseLock();
|
|
13298
|
-
};
|
|
13299
|
-
const stream = new DataStream({
|
|
13300
|
-
logger: this.logger,
|
|
13301
|
-
mapLine: mapLine,
|
|
13302
|
-
pressure: {
|
|
13303
|
-
highWaterMark: 20,
|
|
13304
|
-
lowWaterMark: 10
|
|
13305
|
-
}
|
|
13306
|
-
});
|
|
13307
|
-
abortSignal?.addEventListener('abort', () => {
|
|
13308
|
-
closeReader();
|
|
13309
|
-
stream.close();
|
|
13310
|
-
});
|
|
13311
|
-
const decoder = this.createTextDecoder();
|
|
13312
|
-
let buffer = '';
|
|
13313
|
-
const consumeStream = async () => {
|
|
13314
|
-
while (!stream.closed && !abortSignal?.aborted && !readerReleased) {
|
|
13315
|
-
const { done, value } = await reader.read();
|
|
13316
|
-
if (done) {
|
|
13317
|
-
const remaining = buffer.trim();
|
|
13318
|
-
if (remaining.length != 0) {
|
|
13319
|
-
stream.enqueueData(remaining);
|
|
13320
|
-
}
|
|
13321
|
-
stream.close();
|
|
13322
|
-
await closeReader();
|
|
13323
|
-
return;
|
|
13331
|
+
reader = res.body.getReader();
|
|
13332
|
+
const stream = {
|
|
13333
|
+
next: async () => {
|
|
13334
|
+
if (controller.signal.aborted) {
|
|
13335
|
+
return doneResult;
|
|
13324
13336
|
}
|
|
13325
|
-
|
|
13326
|
-
|
|
13327
|
-
const lines = buffer.split('\n');
|
|
13328
|
-
for (var i = 0; i < lines.length - 1; i++) {
|
|
13329
|
-
var l = lines[i].trim();
|
|
13330
|
-
if (l.length > 0) {
|
|
13331
|
-
stream.enqueueData(l);
|
|
13332
|
-
}
|
|
13337
|
+
try {
|
|
13338
|
+
return await reader.read();
|
|
13333
13339
|
}
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
|
|
13338
|
-
|
|
13339
|
-
|
|
13340
|
-
|
|
13341
|
-
dispose();
|
|
13342
|
-
},
|
|
13343
|
-
closed: () => {
|
|
13344
|
-
resolve();
|
|
13345
|
-
dispose();
|
|
13346
|
-
}
|
|
13347
|
-
});
|
|
13348
|
-
});
|
|
13340
|
+
catch (ex) {
|
|
13341
|
+
if (controller.signal.aborted) {
|
|
13342
|
+
// .read() completes with an error if we cancel the reader, which we do to disconnect. So this is just
|
|
13343
|
+
// things working as intended, we can return a done event and consider the exception handled.
|
|
13344
|
+
return doneResult;
|
|
13345
|
+
}
|
|
13346
|
+
throw ex;
|
|
13349
13347
|
}
|
|
13350
13348
|
}
|
|
13351
13349
|
};
|
|
13352
|
-
|
|
13353
|
-
|
|
13354
|
-
|
|
13355
|
-
|
|
13356
|
-
|
|
13357
|
-
|
|
13358
|
-
|
|
13359
|
-
|
|
13350
|
+
return { isBson: responseIsBson, stream };
|
|
13351
|
+
}
|
|
13352
|
+
/**
|
|
13353
|
+
* Posts a `/sync/stream` request.
|
|
13354
|
+
*
|
|
13355
|
+
* Depending on the `Content-Type` of the response, this returns strings for sync lines or encoded BSON documents as
|
|
13356
|
+
* {@link Uint8Array}s.
|
|
13357
|
+
*/
|
|
13358
|
+
async fetchStream(options) {
|
|
13359
|
+
const { isBson, stream } = await this.fetchStreamRaw(options);
|
|
13360
|
+
if (isBson) {
|
|
13361
|
+
return extractBsonObjects(stream);
|
|
13362
|
+
}
|
|
13363
|
+
else {
|
|
13364
|
+
return extractJsonLines(stream, this.createTextDecoder());
|
|
13365
|
+
}
|
|
13360
13366
|
}
|
|
13361
13367
|
}
|
|
13362
13368
|
|
|
@@ -13864,6 +13870,19 @@ The next upload iteration will be delayed.`);
|
|
|
13864
13870
|
}
|
|
13865
13871
|
});
|
|
13866
13872
|
}
|
|
13873
|
+
async receiveSyncLines(data) {
|
|
13874
|
+
const { options, connection, bson } = data;
|
|
13875
|
+
const remote = this.options.remote;
|
|
13876
|
+
if (connection.connectionMethod == SyncStreamConnectionMethod.HTTP) {
|
|
13877
|
+
return await remote.fetchStream(options);
|
|
13878
|
+
}
|
|
13879
|
+
else {
|
|
13880
|
+
return await this.options.remote.socketStreamRaw({
|
|
13881
|
+
...options,
|
|
13882
|
+
...{ fetchStrategy: connection.fetchStrategy }
|
|
13883
|
+
}, bson);
|
|
13884
|
+
}
|
|
13885
|
+
}
|
|
13867
13886
|
async legacyStreamingSyncIteration(signal, resolvedOptions) {
|
|
13868
13887
|
const rawTables = resolvedOptions.serializedSchema?.raw_tables;
|
|
13869
13888
|
if (rawTables != null && rawTables.length) {
|
|
@@ -13893,42 +13912,27 @@ The next upload iteration will be delayed.`);
|
|
|
13893
13912
|
client_id: clientId
|
|
13894
13913
|
}
|
|
13895
13914
|
};
|
|
13896
|
-
|
|
13897
|
-
|
|
13898
|
-
|
|
13899
|
-
|
|
13900
|
-
|
|
13901
|
-
|
|
13902
|
-
|
|
13903
|
-
|
|
13904
|
-
|
|
13905
|
-
|
|
13906
|
-
|
|
13907
|
-
|
|
13908
|
-
|
|
13909
|
-
|
|
13910
|
-
stream = await this.options.remote.socketStreamRaw({
|
|
13911
|
-
...syncOptions,
|
|
13912
|
-
...{ fetchStrategy: resolvedOptions.fetchStrategy }
|
|
13913
|
-
}, (payload) => {
|
|
13914
|
-
if (payload instanceof Uint8Array) {
|
|
13915
|
-
return bson.deserialize(payload);
|
|
13916
|
-
}
|
|
13917
|
-
else {
|
|
13918
|
-
// Directly enqueued by us
|
|
13919
|
-
return payload;
|
|
13920
|
-
}
|
|
13921
|
-
}, bson);
|
|
13922
|
-
}
|
|
13915
|
+
const bson = await this.options.remote.getBSON();
|
|
13916
|
+
const source = await this.receiveSyncLines({
|
|
13917
|
+
options: syncOptions,
|
|
13918
|
+
connection: resolvedOptions,
|
|
13919
|
+
bson
|
|
13920
|
+
});
|
|
13921
|
+
const stream = injectable(map(source, (line) => {
|
|
13922
|
+
if (typeof line == 'string') {
|
|
13923
|
+
return JSON.parse(line);
|
|
13924
|
+
}
|
|
13925
|
+
else {
|
|
13926
|
+
return bson.deserialize(line);
|
|
13927
|
+
}
|
|
13928
|
+
}));
|
|
13923
13929
|
this.logger.debug('Stream established. Processing events');
|
|
13924
13930
|
this.notifyCompletedUploads = () => {
|
|
13925
|
-
|
|
13926
|
-
stream.enqueueData({ crud_upload_completed: null });
|
|
13927
|
-
}
|
|
13931
|
+
stream.inject({ crud_upload_completed: null });
|
|
13928
13932
|
};
|
|
13929
|
-
while (
|
|
13930
|
-
const line = await stream.
|
|
13931
|
-
if (
|
|
13933
|
+
while (true) {
|
|
13934
|
+
const { value: line, done } = await stream.next();
|
|
13935
|
+
if (done) {
|
|
13932
13936
|
// The stream has closed while waiting
|
|
13933
13937
|
return;
|
|
13934
13938
|
}
|
|
@@ -14107,14 +14111,17 @@ The next upload iteration will be delayed.`);
|
|
|
14107
14111
|
const syncImplementation = this;
|
|
14108
14112
|
const adapter = this.options.adapter;
|
|
14109
14113
|
const remote = this.options.remote;
|
|
14114
|
+
const controller = new AbortController();
|
|
14115
|
+
const abort = () => {
|
|
14116
|
+
return controller.abort(signal.reason);
|
|
14117
|
+
};
|
|
14118
|
+
signal.addEventListener('abort', abort);
|
|
14110
14119
|
let receivingLines = null;
|
|
14111
14120
|
let hadSyncLine = false;
|
|
14112
14121
|
let hideDisconnectOnRestart = false;
|
|
14113
14122
|
if (signal.aborted) {
|
|
14114
14123
|
throw new AbortOperation('Connection request has been aborted');
|
|
14115
14124
|
}
|
|
14116
|
-
const abortController = new AbortController();
|
|
14117
|
-
signal.addEventListener('abort', () => abortController.abort());
|
|
14118
14125
|
// Pending sync lines received from the service, as well as local events that trigger a powersync_control
|
|
14119
14126
|
// invocation (local events include refreshed tokens and completed uploads).
|
|
14120
14127
|
// This is a single data stream so that we can handle all control calls from a single place.
|
|
@@ -14122,49 +14129,36 @@ The next upload iteration will be delayed.`);
|
|
|
14122
14129
|
async function connect(instr) {
|
|
14123
14130
|
const syncOptions = {
|
|
14124
14131
|
path: '/sync/stream',
|
|
14125
|
-
abortSignal:
|
|
14132
|
+
abortSignal: controller.signal,
|
|
14126
14133
|
data: instr.request
|
|
14127
14134
|
};
|
|
14128
|
-
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
|
|
14132
|
-
|
|
14133
|
-
|
|
14134
|
-
|
|
14135
|
-
|
|
14136
|
-
|
|
14137
|
-
|
|
14138
|
-
|
|
14139
|
-
|
|
14140
|
-
|
|
14141
|
-
|
|
14142
|
-
|
|
14143
|
-
|
|
14144
|
-
|
|
14145
|
-
fetchStrategy: resolvedOptions.fetchStrategy
|
|
14146
|
-
}, (payload) => {
|
|
14147
|
-
if (payload instanceof Uint8Array) {
|
|
14148
|
-
return {
|
|
14149
|
-
command: PowerSyncControlCommand.PROCESS_BSON_LINE,
|
|
14150
|
-
payload: payload
|
|
14151
|
-
};
|
|
14152
|
-
}
|
|
14153
|
-
else {
|
|
14154
|
-
// Directly enqueued by us
|
|
14155
|
-
return payload;
|
|
14156
|
-
}
|
|
14157
|
-
});
|
|
14158
|
-
}
|
|
14135
|
+
controlInvocations = injectable(map(await syncImplementation.receiveSyncLines({
|
|
14136
|
+
options: syncOptions,
|
|
14137
|
+
connection: resolvedOptions
|
|
14138
|
+
}), (line) => {
|
|
14139
|
+
if (typeof line == 'string') {
|
|
14140
|
+
return {
|
|
14141
|
+
command: PowerSyncControlCommand.PROCESS_TEXT_LINE,
|
|
14142
|
+
payload: line
|
|
14143
|
+
};
|
|
14144
|
+
}
|
|
14145
|
+
else {
|
|
14146
|
+
return {
|
|
14147
|
+
command: PowerSyncControlCommand.PROCESS_BSON_LINE,
|
|
14148
|
+
payload: line
|
|
14149
|
+
};
|
|
14150
|
+
}
|
|
14151
|
+
}));
|
|
14159
14152
|
// The rust client will set connected: true after the first sync line because that's when it gets invoked, but
|
|
14160
14153
|
// we're already connected here and can report that.
|
|
14161
14154
|
syncImplementation.updateSyncStatus({ connected: true });
|
|
14162
14155
|
try {
|
|
14163
|
-
while (
|
|
14164
|
-
|
|
14165
|
-
if (
|
|
14166
|
-
|
|
14156
|
+
while (true) {
|
|
14157
|
+
let event = await controlInvocations.next();
|
|
14158
|
+
if (event.done) {
|
|
14159
|
+
break;
|
|
14167
14160
|
}
|
|
14161
|
+
const line = event.value;
|
|
14168
14162
|
await control(line.command, line.payload);
|
|
14169
14163
|
if (!hadSyncLine) {
|
|
14170
14164
|
syncImplementation.triggerCrudUpload();
|
|
@@ -14173,12 +14167,8 @@ The next upload iteration will be delayed.`);
|
|
|
14173
14167
|
}
|
|
14174
14168
|
}
|
|
14175
14169
|
finally {
|
|
14176
|
-
|
|
14177
|
-
|
|
14178
|
-
// refreshed. That would throw after closing (and we can't handle those events either way), so set this back
|
|
14179
|
-
// to null.
|
|
14180
|
-
controlInvocations = null;
|
|
14181
|
-
await activeInstructions.close();
|
|
14170
|
+
abort();
|
|
14171
|
+
signal.removeEventListener('abort', abort);
|
|
14182
14172
|
}
|
|
14183
14173
|
}
|
|
14184
14174
|
async function stop() {
|
|
@@ -14222,14 +14212,14 @@ The next upload iteration will be delayed.`);
|
|
|
14222
14212
|
remote.invalidateCredentials();
|
|
14223
14213
|
// Restart iteration after the credentials have been refreshed.
|
|
14224
14214
|
remote.fetchCredentials().then((_) => {
|
|
14225
|
-
controlInvocations?.
|
|
14215
|
+
controlInvocations?.inject({ command: PowerSyncControlCommand.NOTIFY_TOKEN_REFRESHED });
|
|
14226
14216
|
}, (err) => {
|
|
14227
14217
|
syncImplementation.logger.warn('Could not prefetch credentials', err);
|
|
14228
14218
|
});
|
|
14229
14219
|
}
|
|
14230
14220
|
}
|
|
14231
14221
|
else if ('CloseSyncStream' in instruction) {
|
|
14232
|
-
|
|
14222
|
+
controller.abort();
|
|
14233
14223
|
hideDisconnectOnRestart = instruction.CloseSyncStream.hide_disconnect;
|
|
14234
14224
|
}
|
|
14235
14225
|
else if ('FlushFileSystem' in instruction) ;
|
|
@@ -14258,17 +14248,13 @@ The next upload iteration will be delayed.`);
|
|
|
14258
14248
|
}
|
|
14259
14249
|
await control(PowerSyncControlCommand.START, JSON.stringify(options));
|
|
14260
14250
|
this.notifyCompletedUploads = () => {
|
|
14261
|
-
|
|
14262
|
-
controlInvocations.enqueueData({ command: PowerSyncControlCommand.NOTIFY_CRUD_UPLOAD_COMPLETED });
|
|
14263
|
-
}
|
|
14251
|
+
controlInvocations?.inject({ command: PowerSyncControlCommand.NOTIFY_CRUD_UPLOAD_COMPLETED });
|
|
14264
14252
|
};
|
|
14265
14253
|
this.handleActiveStreamsChange = () => {
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
});
|
|
14271
|
-
}
|
|
14254
|
+
controlInvocations?.inject({
|
|
14255
|
+
command: PowerSyncControlCommand.UPDATE_SUBSCRIPTIONS,
|
|
14256
|
+
payload: JSON.stringify(this.activeStreams)
|
|
14257
|
+
});
|
|
14272
14258
|
};
|
|
14273
14259
|
await receivingLines;
|
|
14274
14260
|
}
|