@powersync/common 1.51.0 → 1.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.cjs +510 -1129
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.mjs +511 -1116
- package/dist/bundle.mjs.map +1 -1
- package/dist/bundle.node.cjs +508 -1129
- package/dist/bundle.node.cjs.map +1 -1
- package/dist/bundle.node.mjs +509 -1116
- package/dist/bundle.node.mjs.map +1 -1
- package/dist/index.d.cts +73 -433
- package/legacy/sync_protocol.d.ts +103 -0
- package/lib/client/AbstractPowerSyncDatabase.js +3 -3
- package/lib/client/AbstractPowerSyncDatabase.js.map +1 -1
- package/lib/client/ConnectionManager.js +1 -1
- package/lib/client/ConnectionManager.js.map +1 -1
- package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +6 -64
- package/lib/client/sync/bucket/BucketStorageAdapter.js +4 -0
- package/lib/client/sync/bucket/BucketStorageAdapter.js.map +1 -1
- package/lib/client/sync/bucket/SqliteBucketStorage.d.ts +1 -28
- package/lib/client/sync/bucket/SqliteBucketStorage.js +0 -162
- package/lib/client/sync/bucket/SqliteBucketStorage.js.map +1 -1
- package/lib/client/sync/stream/AbstractRemote.d.ts +29 -18
- package/lib/client/sync/stream/AbstractRemote.js +155 -188
- package/lib/client/sync/stream/AbstractRemote.js.map +1 -1
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +13 -35
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +150 -448
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js.map +1 -1
- package/lib/client/sync/stream/JsonValue.d.ts +7 -0
- package/lib/client/sync/stream/JsonValue.js +2 -0
- package/lib/client/sync/stream/JsonValue.js.map +1 -0
- package/lib/client/sync/stream/core-instruction.d.ts +14 -9
- package/lib/client/sync/stream/core-instruction.js +3 -0
- package/lib/client/sync/stream/core-instruction.js.map +1 -1
- package/lib/db/DBAdapter.d.ts +9 -0
- package/lib/db/DBAdapter.js +8 -1
- package/lib/db/DBAdapter.js.map +1 -1
- package/lib/db/crud/SyncStatus.d.ts +3 -4
- package/lib/db/crud/SyncStatus.js +0 -4
- package/lib/db/crud/SyncStatus.js.map +1 -1
- package/lib/db/schema/RawTable.d.ts +0 -5
- package/lib/db/schema/Schema.d.ts +0 -2
- package/lib/db/schema/Schema.js +0 -2
- package/lib/db/schema/Schema.js.map +1 -1
- package/lib/index.d.ts +2 -6
- package/lib/index.js +1 -6
- package/lib/index.js.map +1 -1
- package/lib/utils/async.d.ts +0 -9
- package/lib/utils/async.js +0 -9
- package/lib/utils/async.js.map +1 -1
- package/lib/utils/stream_transform.d.ts +39 -0
- package/lib/utils/stream_transform.js +206 -0
- package/lib/utils/stream_transform.js.map +1 -0
- package/package.json +15 -10
- package/src/client/AbstractPowerSyncDatabase.ts +3 -3
- package/src/client/ConnectionManager.ts +1 -1
- package/src/client/sync/bucket/BucketStorageAdapter.ts +6 -71
- package/src/client/sync/bucket/SqliteBucketStorage.ts +1 -197
- package/src/client/sync/stream/AbstractRemote.ts +183 -229
- package/src/client/sync/stream/AbstractStreamingSyncImplementation.ts +181 -510
- package/src/client/sync/stream/JsonValue.ts +8 -0
- package/src/client/sync/stream/core-instruction.ts +15 -5
- package/src/db/DBAdapter.ts +20 -2
- package/src/db/crud/SyncStatus.ts +4 -5
- package/src/db/schema/RawTable.ts +0 -5
- package/src/db/schema/Schema.ts +0 -2
- package/src/index.ts +2 -6
- package/src/utils/async.ts +0 -11
- package/src/utils/stream_transform.ts +252 -0
- package/lib/client/sync/bucket/OpType.d.ts +0 -16
- package/lib/client/sync/bucket/OpType.js +0 -23
- package/lib/client/sync/bucket/OpType.js.map +0 -1
- package/lib/client/sync/bucket/OplogEntry.d.ts +0 -23
- package/lib/client/sync/bucket/OplogEntry.js +0 -36
- package/lib/client/sync/bucket/OplogEntry.js.map +0 -1
- package/lib/client/sync/bucket/SyncDataBatch.d.ts +0 -6
- package/lib/client/sync/bucket/SyncDataBatch.js +0 -12
- package/lib/client/sync/bucket/SyncDataBatch.js.map +0 -1
- package/lib/client/sync/bucket/SyncDataBucket.d.ts +0 -40
- package/lib/client/sync/bucket/SyncDataBucket.js +0 -40
- package/lib/client/sync/bucket/SyncDataBucket.js.map +0 -1
- package/lib/client/sync/stream/streaming-sync-types.d.ts +0 -143
- package/lib/client/sync/stream/streaming-sync-types.js +0 -26
- package/lib/client/sync/stream/streaming-sync-types.js.map +0 -1
- package/lib/utils/DataStream.d.ts +0 -62
- package/lib/utils/DataStream.js +0 -169
- package/lib/utils/DataStream.js.map +0 -1
- package/src/client/sync/bucket/OpType.ts +0 -23
- package/src/client/sync/bucket/OplogEntry.ts +0 -50
- package/src/client/sync/bucket/SyncDataBatch.ts +0 -11
- package/src/client/sync/bucket/SyncDataBucket.ts +0 -49
- package/src/client/sync/stream/streaming-sync-types.ts +0 -210
- package/src/utils/DataStream.ts +0 -222
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
export const doneResult = { done: true, value: undefined };
|
|
2
|
+
export function valueResult(value) {
|
|
3
|
+
return { done: false, value };
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* A variant of {@link Array.map} for async iterators.
|
|
7
|
+
*/
|
|
8
|
+
export function map(source, map) {
|
|
9
|
+
return {
|
|
10
|
+
next: async () => {
|
|
11
|
+
const value = await source.next();
|
|
12
|
+
if (value.done) {
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
return { value: map(value.value) };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Expands a source async iterator by allowing to inject events asynchronously.
|
|
23
|
+
*
|
|
24
|
+
* The resulting iterator will emit all events from its source. Additionally though, events can be injected. These
|
|
25
|
+
* events are dropped once the main iterator completes, but are otherwise forwarded.
|
|
26
|
+
*
|
|
27
|
+
* The iterator completes when its source completes, and it supports backpressure by only calling `next()` on the source
|
|
28
|
+
* in response to a `next()` call from downstream if no pending injected events can be dispatched.
|
|
29
|
+
*/
|
|
30
|
+
export function injectable(source) {
|
|
31
|
+
let sourceIsDone = false;
|
|
32
|
+
let waiter = undefined; // An active, waiting next() call.
|
|
33
|
+
// A pending upstream event that couldn't be dispatched because inject() has been called before it was resolved.
|
|
34
|
+
let pendingSourceEvent = null;
|
|
35
|
+
let pendingInjectedEvents = [];
|
|
36
|
+
const consumeWaiter = () => {
|
|
37
|
+
const pending = waiter;
|
|
38
|
+
waiter = undefined;
|
|
39
|
+
return pending;
|
|
40
|
+
};
|
|
41
|
+
const fetchFromSource = () => {
|
|
42
|
+
const resolveWaiter = (propagate) => {
|
|
43
|
+
const active = consumeWaiter();
|
|
44
|
+
if (active) {
|
|
45
|
+
propagate(active);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
pendingSourceEvent = propagate;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const nextFromSource = source.next();
|
|
52
|
+
nextFromSource.then((value) => {
|
|
53
|
+
sourceIsDone = value.done == true;
|
|
54
|
+
resolveWaiter((w) => w.resolve(value));
|
|
55
|
+
}, (error) => {
|
|
56
|
+
resolveWaiter((w) => w.reject(error));
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
next: () => {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
// First priority: Dispatch ready upstream events.
|
|
63
|
+
if (sourceIsDone) {
|
|
64
|
+
return resolve(doneResult);
|
|
65
|
+
}
|
|
66
|
+
if (pendingSourceEvent) {
|
|
67
|
+
pendingSourceEvent({ resolve, reject });
|
|
68
|
+
pendingSourceEvent = null;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Second priority: Dispatch injected events
|
|
72
|
+
if (pendingInjectedEvents.length) {
|
|
73
|
+
return resolve(valueResult(pendingInjectedEvents.shift()));
|
|
74
|
+
}
|
|
75
|
+
// Nothing pending? Fetch from source
|
|
76
|
+
waiter = { resolve, reject };
|
|
77
|
+
return fetchFromSource();
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
inject: (event) => {
|
|
81
|
+
const pending = consumeWaiter();
|
|
82
|
+
if (pending != null) {
|
|
83
|
+
pending.resolve(valueResult(event));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
pendingInjectedEvents.push(event);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Splits a byte stream at line endings, emitting each line as a string.
|
|
93
|
+
*/
|
|
94
|
+
export function extractJsonLines(source, decoder) {
|
|
95
|
+
let buffer = '';
|
|
96
|
+
const pendingLines = [];
|
|
97
|
+
let isFinalEvent = false;
|
|
98
|
+
return {
|
|
99
|
+
next: async () => {
|
|
100
|
+
while (true) {
|
|
101
|
+
if (isFinalEvent) {
|
|
102
|
+
return doneResult;
|
|
103
|
+
}
|
|
104
|
+
{
|
|
105
|
+
const first = pendingLines.shift();
|
|
106
|
+
if (first) {
|
|
107
|
+
return { done: false, value: first };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const { done, value } = await source.next();
|
|
111
|
+
if (done) {
|
|
112
|
+
const remaining = buffer.trim();
|
|
113
|
+
if (remaining.length != 0) {
|
|
114
|
+
isFinalEvent = true;
|
|
115
|
+
return { done: false, value: remaining };
|
|
116
|
+
}
|
|
117
|
+
return doneResult;
|
|
118
|
+
}
|
|
119
|
+
const data = decoder.decode(value, { stream: true });
|
|
120
|
+
buffer += data;
|
|
121
|
+
const lines = buffer.split('\n');
|
|
122
|
+
for (let i = 0; i < lines.length - 1; i++) {
|
|
123
|
+
const l = lines[i].trim();
|
|
124
|
+
if (l.length > 0) {
|
|
125
|
+
pendingLines.push(l);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
buffer = lines[lines.length - 1];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Splits a concatenated stream of BSON objects by emitting individual objects.
|
|
135
|
+
*/
|
|
136
|
+
export function extractBsonObjects(source) {
|
|
137
|
+
// Fully read but not emitted yet.
|
|
138
|
+
const completedObjects = [];
|
|
139
|
+
// Whether source has returned { done: true }. We do the same once completed objects have been emitted.
|
|
140
|
+
let isDone = false;
|
|
141
|
+
const lengthBuffer = new DataView(new ArrayBuffer(4));
|
|
142
|
+
let objectBody = null;
|
|
143
|
+
// If we're parsing the length field, a number between 1 and 4 (inclusive) describing remaining bytes in the header.
|
|
144
|
+
// If we're consuming a document, the bytes remaining.
|
|
145
|
+
let remainingLength = 4;
|
|
146
|
+
return {
|
|
147
|
+
async next() {
|
|
148
|
+
while (true) {
|
|
149
|
+
// Before fetching new data from upstream, return completed objects.
|
|
150
|
+
if (completedObjects.length) {
|
|
151
|
+
return valueResult(completedObjects.shift());
|
|
152
|
+
}
|
|
153
|
+
if (isDone) {
|
|
154
|
+
return doneResult;
|
|
155
|
+
}
|
|
156
|
+
const upstreamEvent = await source.next();
|
|
157
|
+
if (upstreamEvent.done) {
|
|
158
|
+
isDone = true;
|
|
159
|
+
if (objectBody || remainingLength != 4) {
|
|
160
|
+
throw new Error('illegal end of stream in BSON object');
|
|
161
|
+
}
|
|
162
|
+
return doneResult;
|
|
163
|
+
}
|
|
164
|
+
const chunk = upstreamEvent.value;
|
|
165
|
+
for (let i = 0; i < chunk.length;) {
|
|
166
|
+
const availableInData = chunk.length - i;
|
|
167
|
+
if (objectBody) {
|
|
168
|
+
// We're in the middle of reading a BSON document.
|
|
169
|
+
const bytesToRead = Math.min(availableInData, remainingLength);
|
|
170
|
+
const copySource = new Uint8Array(chunk.buffer, chunk.byteOffset + i, bytesToRead);
|
|
171
|
+
objectBody.set(copySource, objectBody.length - remainingLength);
|
|
172
|
+
i += bytesToRead;
|
|
173
|
+
remainingLength -= bytesToRead;
|
|
174
|
+
if (remainingLength == 0) {
|
|
175
|
+
completedObjects.push(objectBody);
|
|
176
|
+
// Prepare to read another document, starting with its length
|
|
177
|
+
objectBody = null;
|
|
178
|
+
remainingLength = 4;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
// Copy up to 4 bytes into lengthBuffer, depending on how many we still need.
|
|
183
|
+
const bytesToRead = Math.min(availableInData, remainingLength);
|
|
184
|
+
for (let j = 0; j < bytesToRead; j++) {
|
|
185
|
+
lengthBuffer.setUint8(4 - remainingLength + j, chunk[i + j]);
|
|
186
|
+
}
|
|
187
|
+
i += bytesToRead;
|
|
188
|
+
remainingLength -= bytesToRead;
|
|
189
|
+
if (remainingLength == 0) {
|
|
190
|
+
// Transition from reading length header to reading document. Subtracting 4 because the length of the
|
|
191
|
+
// header is included in length.
|
|
192
|
+
const length = lengthBuffer.getInt32(0, true /* little endian */);
|
|
193
|
+
remainingLength = length - 4;
|
|
194
|
+
if (remainingLength < 1) {
|
|
195
|
+
throw new Error(`invalid length for bson: ${length}`);
|
|
196
|
+
}
|
|
197
|
+
objectBody = new Uint8Array(length);
|
|
198
|
+
new DataView(objectBody.buffer).setInt32(0, length, true);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=stream_transform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream_transform.js","sourceRoot":"","sources":["../../src/utils/stream_transform.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/common",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.53.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"description": "API definitions for
|
|
8
|
+
"description": "API definitions for PowerSync",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"main": "dist/bundle.mjs",
|
|
11
11
|
"module": "dist/bundle.mjs",
|
|
@@ -30,14 +30,18 @@
|
|
|
30
30
|
"types": "./dist/index.d.cts",
|
|
31
31
|
"require": "./dist/bundle.cjs"
|
|
32
32
|
}
|
|
33
|
+
},
|
|
34
|
+
"./internal/sync_protocol": {
|
|
35
|
+
"types": "./legacy/sync_protocol.d.ts"
|
|
33
36
|
}
|
|
34
37
|
},
|
|
35
|
-
"author": "
|
|
38
|
+
"author": "PowerSync",
|
|
36
39
|
"license": "Apache-2.0",
|
|
37
40
|
"files": [
|
|
38
41
|
"lib",
|
|
39
42
|
"dist",
|
|
40
|
-
"src"
|
|
43
|
+
"src",
|
|
44
|
+
"legacy"
|
|
41
45
|
],
|
|
42
46
|
"repository": {
|
|
43
47
|
"type": "git",
|
|
@@ -48,7 +52,8 @@
|
|
|
48
52
|
},
|
|
49
53
|
"homepage": "https://docs.powersync.com",
|
|
50
54
|
"dependencies": {
|
|
51
|
-
"event-iterator": "^2.0.0"
|
|
55
|
+
"event-iterator": "^2.0.0",
|
|
56
|
+
"js-logger": "^1.6.1"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|
|
54
59
|
"@rollup/plugin-commonjs": "^29.0.0",
|
|
@@ -58,19 +63,19 @@
|
|
|
58
63
|
"@types/node": "^24.0.0",
|
|
59
64
|
"@types/uuid": "^9.0.6",
|
|
60
65
|
"buffer": "^6.0.3",
|
|
66
|
+
"cross-fetch": "^4.1.0",
|
|
67
|
+
"estree-walker": "^3.0.3",
|
|
68
|
+
"magic-string": "^0.30.21",
|
|
61
69
|
"rollup": "^4.52.5",
|
|
62
70
|
"rollup-plugin-dts": "^6.2.1",
|
|
63
|
-
"cross-fetch": "^4.1.0",
|
|
64
|
-
"js-logger": "^1.6.1",
|
|
65
71
|
"rsocket-core": "1.0.0-alpha.3",
|
|
66
|
-
"rsocket-websocket-client": "1.0.0-alpha.3"
|
|
67
|
-
"bson": "^6.10.4"
|
|
72
|
+
"rsocket-websocket-client": "1.0.0-alpha.3"
|
|
68
73
|
},
|
|
69
74
|
"scripts": {
|
|
70
75
|
"build": "tsc -b && rollup -c rollup.config.mjs",
|
|
71
76
|
"build:prod": "tsc -b && rollup -c rollup.config.mjs",
|
|
72
77
|
"clean": "rm -rf lib dist tsconfig.tsbuildinfo",
|
|
73
78
|
"test": "vitest",
|
|
74
|
-
"test:exports": "attw --pack ."
|
|
79
|
+
"test:exports": "attw --pack . --exclude-entrypoints internal/sync_protocol"
|
|
75
80
|
}
|
|
76
81
|
}
|
|
@@ -13,7 +13,7 @@ import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js';
|
|
|
13
13
|
import { Schema } from '../db/schema/Schema.js';
|
|
14
14
|
import { BaseObserver } from '../utils/BaseObserver.js';
|
|
15
15
|
import { ControlledExecutor } from '../utils/ControlledExecutor.js';
|
|
16
|
-
import {
|
|
16
|
+
import { throttleTrailing } from '../utils/async.js';
|
|
17
17
|
import {
|
|
18
18
|
ConnectionManager,
|
|
19
19
|
CreateSyncImplementationOptions,
|
|
@@ -704,7 +704,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
|
|
|
704
704
|
* @returns A transaction of CRUD operations to upload, or null if there are none
|
|
705
705
|
*/
|
|
706
706
|
async getNextCrudTransaction(): Promise<CrudTransaction | null> {
|
|
707
|
-
const iterator = this.getCrudTransactions()[
|
|
707
|
+
const iterator = this.getCrudTransactions()[Symbol.asyncIterator]();
|
|
708
708
|
return (await iterator.next()).value;
|
|
709
709
|
}
|
|
710
710
|
|
|
@@ -741,7 +741,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
|
|
|
741
741
|
*/
|
|
742
742
|
getCrudTransactions(): AsyncIterable<CrudTransaction, null> {
|
|
743
743
|
return {
|
|
744
|
-
[
|
|
744
|
+
[Symbol.asyncIterator]: () => {
|
|
745
745
|
let lastCrudItemId = -1;
|
|
746
746
|
const sql = `
|
|
747
747
|
WITH RECURSIVE crud_entries AS (
|
|
@@ -368,7 +368,7 @@ class SyncStreamSubscriptionHandle implements SyncStreamSubscription {
|
|
|
368
368
|
|
|
369
369
|
constructor(readonly subscription: ActiveSubscription) {
|
|
370
370
|
subscription.refcount++;
|
|
371
|
-
_finalizer?.register(this, subscription);
|
|
371
|
+
_finalizer?.register(this, subscription, this);
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
get name() {
|
|
@@ -1,62 +1,6 @@
|
|
|
1
1
|
import { BaseListener, BaseObserverInterface, Disposable } from '../../../utils/BaseObserver.js';
|
|
2
2
|
import { CrudBatch } from './CrudBatch.js';
|
|
3
|
-
import { CrudEntry
|
|
4
|
-
import { SyncDataBatch } from './SyncDataBatch.js';
|
|
5
|
-
|
|
6
|
-
export interface BucketDescription {
|
|
7
|
-
name: string;
|
|
8
|
-
priority: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface Checkpoint {
|
|
12
|
-
last_op_id: OpId;
|
|
13
|
-
buckets: BucketChecksum[];
|
|
14
|
-
write_checkpoint?: string;
|
|
15
|
-
streams?: any[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface BucketState {
|
|
19
|
-
bucket: string;
|
|
20
|
-
op_id: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface ChecksumCache {
|
|
24
|
-
checksums: Map<string, { checksum: BucketChecksum; last_op_id: OpId }>;
|
|
25
|
-
lastOpId: OpId;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface SyncLocalDatabaseResult {
|
|
29
|
-
ready: boolean;
|
|
30
|
-
checkpointValid: boolean;
|
|
31
|
-
checkpointFailures?: string[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type SavedProgress = {
|
|
35
|
-
atLast: number;
|
|
36
|
-
sinceLast: number;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export type BucketOperationProgress = Record<string, SavedProgress>;
|
|
40
|
-
|
|
41
|
-
export interface BucketChecksum {
|
|
42
|
-
bucket: string;
|
|
43
|
-
priority?: number;
|
|
44
|
-
/**
|
|
45
|
-
* 32-bit unsigned hash.
|
|
46
|
-
*/
|
|
47
|
-
checksum: number;
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Count of operations - informational only.
|
|
51
|
-
*/
|
|
52
|
-
count?: number;
|
|
53
|
-
/**
|
|
54
|
-
* The JavaScript client does not use this field, which is why it's defined to be `any`. We rely on the structure of
|
|
55
|
-
* this interface to pass custom `BucketChecksum`s to the Rust client in unit tests, which so all fields need to be
|
|
56
|
-
* present.
|
|
57
|
-
*/
|
|
58
|
-
subscriptions?: any;
|
|
59
|
-
}
|
|
3
|
+
import { CrudEntry } from './CrudEntry.js';
|
|
60
4
|
|
|
61
5
|
export enum PSInternalTable {
|
|
62
6
|
DATA = 'ps_data',
|
|
@@ -73,7 +17,11 @@ export enum PowerSyncControlCommand {
|
|
|
73
17
|
START = 'start',
|
|
74
18
|
NOTIFY_TOKEN_REFRESHED = 'refreshed_token',
|
|
75
19
|
NOTIFY_CRUD_UPLOAD_COMPLETED = 'completed_upload',
|
|
76
|
-
UPDATE_SUBSCRIPTIONS = 'update_subscriptions'
|
|
20
|
+
UPDATE_SUBSCRIPTIONS = 'update_subscriptions',
|
|
21
|
+
/**
|
|
22
|
+
* An `established` or `end` event for response streams.
|
|
23
|
+
*/
|
|
24
|
+
CONNECTION_STATE = 'connection'
|
|
77
25
|
}
|
|
78
26
|
|
|
79
27
|
export interface BucketStorageListener extends BaseListener {
|
|
@@ -82,27 +30,14 @@ export interface BucketStorageListener extends BaseListener {
|
|
|
82
30
|
|
|
83
31
|
export interface BucketStorageAdapter extends BaseObserverInterface<BucketStorageListener>, Disposable {
|
|
84
32
|
init(): Promise<void>;
|
|
85
|
-
saveSyncData(batch: SyncDataBatch, fixedKeyFormat?: boolean): Promise<void>;
|
|
86
|
-
removeBuckets(buckets: string[]): Promise<void>;
|
|
87
|
-
setTargetCheckpoint(checkpoint: Checkpoint): Promise<void>;
|
|
88
|
-
|
|
89
|
-
startSession(): void;
|
|
90
33
|
|
|
91
|
-
getBucketStates(): Promise<BucketState[]>;
|
|
92
|
-
getBucketOperationProgress(): Promise<BucketOperationProgress>;
|
|
93
34
|
hasMigratedSubkeys(): Promise<boolean>;
|
|
94
35
|
migrateToFixedSubkeys(): Promise<void>;
|
|
95
36
|
|
|
96
|
-
syncLocalDatabase(
|
|
97
|
-
checkpoint: Checkpoint,
|
|
98
|
-
priority?: number
|
|
99
|
-
): Promise<{ checkpointValid: boolean; ready: boolean; failures?: any[] }>;
|
|
100
|
-
|
|
101
37
|
nextCrudItem(): Promise<CrudEntry | undefined>;
|
|
102
38
|
hasCrud(): Promise<boolean>;
|
|
103
39
|
getCrudBatch(limit?: number): Promise<CrudBatch | null>;
|
|
104
40
|
|
|
105
|
-
hasCompletedSync(): Promise<boolean>;
|
|
106
41
|
updateLocalTarget(cb: () => Promise<string>): Promise<boolean>;
|
|
107
42
|
getMaxOpId(): string;
|
|
108
43
|
|
|
@@ -3,23 +3,16 @@ import { DBAdapter, extractTableUpdates, Transaction } from '../../../db/DBAdapt
|
|
|
3
3
|
import { BaseObserver } from '../../../utils/BaseObserver.js';
|
|
4
4
|
import { MAX_OP_ID } from '../../constants.js';
|
|
5
5
|
import {
|
|
6
|
-
BucketChecksum,
|
|
7
|
-
BucketOperationProgress,
|
|
8
|
-
BucketState,
|
|
9
6
|
BucketStorageAdapter,
|
|
10
7
|
BucketStorageListener,
|
|
11
|
-
Checkpoint,
|
|
12
8
|
PowerSyncControlCommand,
|
|
13
|
-
PSInternalTable
|
|
14
|
-
SyncLocalDatabaseResult
|
|
9
|
+
PSInternalTable
|
|
15
10
|
} from './BucketStorageAdapter.js';
|
|
16
11
|
import { CrudBatch } from './CrudBatch.js';
|
|
17
12
|
import { CrudEntry, CrudEntryJSON } from './CrudEntry.js';
|
|
18
|
-
import { SyncDataBatch } from './SyncDataBatch.js';
|
|
19
13
|
|
|
20
14
|
export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> implements BucketStorageAdapter {
|
|
21
15
|
public tableNames: Set<string>;
|
|
22
|
-
private _hasCompletedSync: boolean;
|
|
23
16
|
private updateListener: () => void;
|
|
24
17
|
private _clientId?: Promise<string>;
|
|
25
18
|
|
|
@@ -28,7 +21,6 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
|
|
|
28
21
|
private logger: ILogger = Logger.get('SqliteBucketStorage')
|
|
29
22
|
) {
|
|
30
23
|
super();
|
|
31
|
-
this._hasCompletedSync = false;
|
|
32
24
|
this.tableNames = new Set();
|
|
33
25
|
this.updateListener = db.registerListener({
|
|
34
26
|
tablesUpdated: (update) => {
|
|
@@ -41,7 +33,6 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
|
|
|
41
33
|
}
|
|
42
34
|
|
|
43
35
|
async init() {
|
|
44
|
-
this._hasCompletedSync = false;
|
|
45
36
|
const existingTableRows = await this.db.getAll<{ name: string }>(
|
|
46
37
|
`SELECT name FROM sqlite_master WHERE type='table' AND name GLOB 'ps_data_*'`
|
|
47
38
|
);
|
|
@@ -70,182 +61,6 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
|
|
|
70
61
|
return MAX_OP_ID;
|
|
71
62
|
}
|
|
72
63
|
|
|
73
|
-
/**
|
|
74
|
-
* Reset any caches.
|
|
75
|
-
*/
|
|
76
|
-
startSession(): void {}
|
|
77
|
-
|
|
78
|
-
async getBucketStates(): Promise<BucketState[]> {
|
|
79
|
-
const result = await this.db.getAll<BucketState>(
|
|
80
|
-
"SELECT name as bucket, cast(last_op as TEXT) as op_id FROM ps_buckets WHERE pending_delete = 0 AND name != '$local'"
|
|
81
|
-
);
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async getBucketOperationProgress(): Promise<BucketOperationProgress> {
|
|
86
|
-
const rows = await this.db.getAll<{ name: string; count_at_last: number; count_since_last: number }>(
|
|
87
|
-
'SELECT name, count_at_last, count_since_last FROM ps_buckets'
|
|
88
|
-
);
|
|
89
|
-
return Object.fromEntries(rows.map((r) => [r.name, { atLast: r.count_at_last, sinceLast: r.count_since_last }]));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async saveSyncData(batch: SyncDataBatch, fixedKeyFormat: boolean = false) {
|
|
93
|
-
await this.writeTransaction(async (tx) => {
|
|
94
|
-
for (const b of batch.buckets) {
|
|
95
|
-
await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', [
|
|
96
|
-
'save',
|
|
97
|
-
JSON.stringify({ buckets: [b.toJSON(fixedKeyFormat)] })
|
|
98
|
-
]);
|
|
99
|
-
this.logger.debug(`Saved batch of data for bucket: ${b.bucket}, operations: ${b.data.length}`);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async removeBuckets(buckets: string[]): Promise<void> {
|
|
105
|
-
for (const bucket of buckets) {
|
|
106
|
-
await this.deleteBucket(bucket);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Mark a bucket for deletion.
|
|
112
|
-
*/
|
|
113
|
-
private async deleteBucket(bucket: string) {
|
|
114
|
-
await this.writeTransaction(async (tx) => {
|
|
115
|
-
await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', ['delete_bucket', bucket]);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
this.logger.debug(`Done deleting bucket ${bucket}`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async hasCompletedSync() {
|
|
122
|
-
if (this._hasCompletedSync) {
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
const r = await this.db.get<{ synced_at: string | null }>(`SELECT powersync_last_synced_at() as synced_at`);
|
|
126
|
-
const completed = r.synced_at != null;
|
|
127
|
-
if (completed) {
|
|
128
|
-
this._hasCompletedSync = true;
|
|
129
|
-
}
|
|
130
|
-
return completed;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async syncLocalDatabase(checkpoint: Checkpoint, priority?: number): Promise<SyncLocalDatabaseResult> {
|
|
134
|
-
const r = await this.validateChecksums(checkpoint, priority);
|
|
135
|
-
if (!r.checkpointValid) {
|
|
136
|
-
this.logger.error('Checksums failed for', r.checkpointFailures);
|
|
137
|
-
for (const b of r.checkpointFailures ?? []) {
|
|
138
|
-
await this.deleteBucket(b);
|
|
139
|
-
}
|
|
140
|
-
return { ready: false, checkpointValid: false, checkpointFailures: r.checkpointFailures };
|
|
141
|
-
}
|
|
142
|
-
if (priority == null) {
|
|
143
|
-
this.logger.debug(`Validated checksums checkpoint ${checkpoint.last_op_id}`);
|
|
144
|
-
} else {
|
|
145
|
-
this.logger.debug(`Validated checksums for partial checkpoint ${checkpoint.last_op_id}, priority ${priority}`);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
let buckets = checkpoint.buckets;
|
|
149
|
-
if (priority !== undefined) {
|
|
150
|
-
buckets = buckets.filter((b) => hasMatchingPriority(priority, b));
|
|
151
|
-
}
|
|
152
|
-
const bucketNames = buckets.map((b) => b.bucket);
|
|
153
|
-
await this.writeTransaction(async (tx) => {
|
|
154
|
-
await tx.execute(`UPDATE ps_buckets SET last_op = ? WHERE name IN (SELECT json_each.value FROM json_each(?))`, [
|
|
155
|
-
checkpoint.last_op_id,
|
|
156
|
-
JSON.stringify(bucketNames)
|
|
157
|
-
]);
|
|
158
|
-
|
|
159
|
-
if (priority == null && checkpoint.write_checkpoint) {
|
|
160
|
-
await tx.execute("UPDATE ps_buckets SET last_op = ? WHERE name = '$local'", [checkpoint.write_checkpoint]);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const valid = await this.updateObjectsFromBuckets(checkpoint, priority);
|
|
165
|
-
if (!valid) {
|
|
166
|
-
return { ready: false, checkpointValid: true };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
ready: true,
|
|
171
|
-
checkpointValid: true
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Atomically update the local state to the current checkpoint.
|
|
177
|
-
*
|
|
178
|
-
* This includes creating new tables, dropping old tables, and copying data over from the oplog.
|
|
179
|
-
*/
|
|
180
|
-
private async updateObjectsFromBuckets(checkpoint: Checkpoint, priority: number | undefined) {
|
|
181
|
-
let arg = '';
|
|
182
|
-
if (priority !== undefined) {
|
|
183
|
-
const affectedBuckets: string[] = [];
|
|
184
|
-
for (const desc of checkpoint.buckets) {
|
|
185
|
-
if (hasMatchingPriority(priority, desc)) {
|
|
186
|
-
affectedBuckets.push(desc.bucket);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
arg = JSON.stringify({ priority, buckets: affectedBuckets });
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return this.writeTransaction(async (tx) => {
|
|
194
|
-
const { insertId: result } = await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', [
|
|
195
|
-
'sync_local',
|
|
196
|
-
arg
|
|
197
|
-
]);
|
|
198
|
-
if (result == 1) {
|
|
199
|
-
if (priority == null) {
|
|
200
|
-
const bucketToCount = Object.fromEntries(checkpoint.buckets.map((b) => [b.bucket, b.count]));
|
|
201
|
-
// The two parameters could be replaced with one, but: https://github.com/powersync-ja/better-sqlite3/pull/6
|
|
202
|
-
const jsonBucketCount = JSON.stringify(bucketToCount);
|
|
203
|
-
await tx.execute(
|
|
204
|
-
"UPDATE ps_buckets SET count_since_last = 0, count_at_last = ?->name WHERE name != '$local' AND ?->name IS NOT NULL",
|
|
205
|
-
[jsonBucketCount, jsonBucketCount]
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return true;
|
|
210
|
-
} else {
|
|
211
|
-
return false;
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async validateChecksums(checkpoint: Checkpoint, priority: number | undefined): Promise<SyncLocalDatabaseResult> {
|
|
217
|
-
if (priority !== undefined) {
|
|
218
|
-
// Only validate the buckets within the priority we care about
|
|
219
|
-
const newBuckets = checkpoint.buckets.filter((cs) => hasMatchingPriority(priority, cs));
|
|
220
|
-
checkpoint = { ...checkpoint, buckets: newBuckets };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const rs = await this.db.execute('SELECT powersync_validate_checkpoint(?) as result', [
|
|
224
|
-
JSON.stringify({ ...checkpoint })
|
|
225
|
-
]);
|
|
226
|
-
|
|
227
|
-
const resultItem = rs.rows?.item(0);
|
|
228
|
-
if (!resultItem) {
|
|
229
|
-
return {
|
|
230
|
-
checkpointValid: false,
|
|
231
|
-
ready: false,
|
|
232
|
-
checkpointFailures: []
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const result = JSON.parse(resultItem['result']);
|
|
237
|
-
|
|
238
|
-
if (result['valid']) {
|
|
239
|
-
return { ready: true, checkpointValid: true };
|
|
240
|
-
} else {
|
|
241
|
-
return {
|
|
242
|
-
checkpointValid: false,
|
|
243
|
-
ready: false,
|
|
244
|
-
checkpointFailures: result['failed_buckets']
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
64
|
async updateLocalTarget(cb: () => Promise<string>): Promise<boolean> {
|
|
250
65
|
const rs1 = await this.db.getAll(
|
|
251
66
|
"SELECT target_op FROM ps_buckets WHERE name = '$local' AND target_op = CAST(? as INTEGER)",
|
|
@@ -356,13 +171,6 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
|
|
|
356
171
|
return this.db.writeTransaction(callback, options);
|
|
357
172
|
}
|
|
358
173
|
|
|
359
|
-
/**
|
|
360
|
-
* Set a target checkpoint.
|
|
361
|
-
*/
|
|
362
|
-
async setTargetCheckpoint(checkpoint: Checkpoint) {
|
|
363
|
-
// No-op for now
|
|
364
|
-
}
|
|
365
|
-
|
|
366
174
|
async control(op: PowerSyncControlCommand, payload: string | Uint8Array | ArrayBuffer | null): Promise<string> {
|
|
367
175
|
return await this.writeTransaction(async (tx) => {
|
|
368
176
|
const [[raw]] = await tx.executeRaw('SELECT powersync_control(?, ?)', [op, payload]);
|
|
@@ -389,7 +197,3 @@ export class SqliteBucketStorage extends BaseObserver<BucketStorageListener> imp
|
|
|
389
197
|
|
|
390
198
|
static _subkeyMigrationKey = 'powersync_js_migrated_subkeys';
|
|
391
199
|
}
|
|
392
|
-
|
|
393
|
-
function hasMatchingPriority(priority: number, bucket: BucketChecksum) {
|
|
394
|
-
return bucket.priority != null && bucket.priority <= priority;
|
|
395
|
-
}
|