@powersync/service-core 0.0.0-dev-20240709124106 → 0.0.0-dev-20240725112650
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/CHANGELOG.md +39 -2
- package/dist/entry/cli-entry.js +2 -1
- package/dist/entry/cli-entry.js.map +1 -1
- package/dist/entry/commands/compact-action.d.ts +2 -0
- package/dist/entry/commands/compact-action.js +48 -0
- package/dist/entry/commands/compact-action.js.map +1 -0
- package/dist/entry/entry-index.d.ts +1 -0
- package/dist/entry/entry-index.js +1 -0
- package/dist/entry/entry-index.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +883 -0
- package/dist/routes/configure-fastify.js +58 -0
- package/dist/routes/configure-fastify.js.map +1 -0
- package/dist/routes/configure-rsocket.d.ts +13 -0
- package/dist/routes/configure-rsocket.js +46 -0
- package/dist/routes/configure-rsocket.js.map +1 -0
- package/dist/routes/endpoints/socket-route.js +10 -9
- package/dist/routes/endpoints/socket-route.js.map +1 -1
- package/dist/routes/endpoints/sync-stream.js +9 -1
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/routes/route-register.d.ts +1 -1
- package/dist/routes/route-register.js +2 -1
- package/dist/routes/route-register.js.map +1 -1
- package/dist/routes/router-socket.d.ts +4 -4
- package/dist/routes/router-socket.js.map +1 -1
- package/dist/routes/router.d.ts +1 -0
- package/dist/routes/router.js.map +1 -1
- package/dist/routes/routes-index.d.ts +2 -0
- package/dist/routes/routes-index.js +2 -0
- package/dist/routes/routes-index.js.map +1 -1
- package/dist/storage/BucketStorage.d.ts +31 -1
- package/dist/storage/BucketStorage.js.map +1 -1
- package/dist/storage/mongo/MongoCompactor.d.ts +40 -0
- package/dist/storage/mongo/MongoCompactor.js +292 -0
- package/dist/storage/mongo/MongoCompactor.js.map +1 -0
- package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +3 -2
- package/dist/storage/mongo/MongoSyncBucketStorage.js +19 -13
- package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/mongo/models.d.ts +5 -4
- package/dist/storage/mongo/models.js.map +1 -1
- package/dist/storage/mongo/util.d.ts +3 -0
- package/dist/storage/mongo/util.js +22 -0
- package/dist/storage/mongo/util.js.map +1 -1
- package/dist/sync/RequestTracker.d.ts +9 -0
- package/dist/sync/RequestTracker.js +19 -0
- package/dist/sync/RequestTracker.js.map +1 -0
- package/dist/sync/sync-index.d.ts +1 -0
- package/dist/sync/sync-index.js +1 -0
- package/dist/sync/sync-index.js.map +1 -1
- package/dist/sync/sync.d.ts +2 -0
- package/dist/sync/sync.js +51 -18
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.d.ts +2 -1
- package/dist/sync/util.js +2 -3
- package/dist/sync/util.js.map +1 -1
- package/package.json +6 -6
- package/src/entry/cli-entry.ts +2 -1
- package/src/entry/commands/compact-action.ts +54 -0
- package/src/entry/entry-index.ts +1 -0
- package/src/routes/configure-fastify.ts +102 -0
- package/src/routes/configure-rsocket.ts +59 -0
- package/src/routes/endpoints/socket-route.ts +10 -9
- package/src/routes/endpoints/sync-stream.ts +10 -1
- package/src/routes/route-register.ts +3 -2
- package/src/routes/router-socket.ts +5 -5
- package/src/routes/router.ts +2 -0
- package/src/routes/routes-index.ts +2 -0
- package/src/storage/BucketStorage.ts +36 -1
- package/src/storage/mongo/MongoCompactor.ts +371 -0
- package/src/storage/mongo/MongoSyncBucketStorage.ts +25 -14
- package/src/storage/mongo/models.ts +5 -4
- package/src/storage/mongo/util.ts +25 -0
- package/src/sync/RequestTracker.ts +21 -0
- package/src/sync/sync-index.ts +1 -0
- package/src/sync/sync.ts +61 -17
- package/src/sync/util.ts +6 -2
- package/test/src/__snapshots__/sync.test.ts.snap +85 -0
- package/test/src/bucket_validation.test.ts +142 -0
- package/test/src/bucket_validation.ts +116 -0
- package/test/src/compacting.test.ts +207 -0
- package/test/src/data_storage.test.ts +19 -60
- package/test/src/slow_tests.test.ts +144 -102
- package/test/src/sync.test.ts +176 -28
- package/test/src/util.ts +65 -1
- package/test/src/wal_stream_utils.ts +13 -4
- package/tsconfig.tsbuildinfo +1 -1
package/dist/sync/sync.js
CHANGED
|
@@ -3,7 +3,6 @@ import { Semaphore } from 'async-mutex';
|
|
|
3
3
|
import { AbortError } from 'ix/aborterror.js';
|
|
4
4
|
import * as util from '../util/util-index.js';
|
|
5
5
|
import { logger } from '@powersync/lib-services-framework';
|
|
6
|
-
import { Metrics } from '../metrics/Metrics.js';
|
|
7
6
|
import { mergeAsyncIterables } from './merge.js';
|
|
8
7
|
import { tokenStream } from './util.js';
|
|
9
8
|
/**
|
|
@@ -12,7 +11,7 @@ import { tokenStream } from './util.js';
|
|
|
12
11
|
const MAX_ACTIVE_CONNECTIONS = 10;
|
|
13
12
|
const syncSemaphore = new Semaphore(MAX_ACTIVE_CONNECTIONS);
|
|
14
13
|
export async function* streamResponse(options) {
|
|
15
|
-
const { storage, params, syncParams, token, tokenStreamOptions, signal } = options;
|
|
14
|
+
const { storage, params, syncParams, token, tokenStreamOptions, tracker, signal } = options;
|
|
16
15
|
// We also need to be able to abort, so we create our own controller.
|
|
17
16
|
const controller = new AbortController();
|
|
18
17
|
if (signal) {
|
|
@@ -24,7 +23,7 @@ export async function* streamResponse(options) {
|
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
25
|
const ki = tokenStream(token, controller.signal, tokenStreamOptions);
|
|
27
|
-
const stream = streamResponseInner(storage, params, syncParams, controller.signal);
|
|
26
|
+
const stream = streamResponseInner(storage, params, syncParams, tracker, controller.signal);
|
|
28
27
|
// Merge the two streams, and abort as soon as one of the streams end.
|
|
29
28
|
const merged = mergeAsyncIterables([stream, ki], controller.signal);
|
|
30
29
|
try {
|
|
@@ -44,7 +43,7 @@ export async function* streamResponse(options) {
|
|
|
44
43
|
controller.abort();
|
|
45
44
|
}
|
|
46
45
|
}
|
|
47
|
-
async function* streamResponseInner(storage, params, syncParams, signal) {
|
|
46
|
+
async function* streamResponseInner(storage, params, syncParams, tracker, signal) {
|
|
48
47
|
// Bucket state of bucket id -> op_id.
|
|
49
48
|
// This starts with the state from the client. May contain buckets that the user do not have access to (anymore).
|
|
50
49
|
let dataBuckets = new Map();
|
|
@@ -73,6 +72,11 @@ async function* streamResponseInner(storage, params, syncParams, signal) {
|
|
|
73
72
|
parameters: syncParams
|
|
74
73
|
});
|
|
75
74
|
if (allBuckets.length > 1000) {
|
|
75
|
+
logger.error(`Too many buckets`, {
|
|
76
|
+
checkpoint,
|
|
77
|
+
user_id: syncParams.user_id,
|
|
78
|
+
buckets: allBuckets.length
|
|
79
|
+
});
|
|
76
80
|
// TODO: Limit number of buckets even before we get to this point
|
|
77
81
|
throw new Error(`Too many buckets: ${allBuckets.length}`);
|
|
78
82
|
}
|
|
@@ -94,11 +98,18 @@ async function* streamResponseInner(storage, params, syncParams, signal) {
|
|
|
94
98
|
continue;
|
|
95
99
|
}
|
|
96
100
|
bucketsToFetch = diff.updatedBuckets.map((c) => c.bucket);
|
|
97
|
-
let message = `Updated checkpoint: ${checkpoint} |
|
|
101
|
+
let message = `Updated checkpoint: ${checkpoint} | `;
|
|
102
|
+
message += `write: ${writeCheckpoint} | `;
|
|
98
103
|
message += `buckets: ${allBuckets.length} | `;
|
|
99
104
|
message += `updated: ${limitedBuckets(diff.updatedBuckets, 20)} | `;
|
|
100
|
-
message += `removed: ${limitedBuckets(diff.removedBuckets, 20)}
|
|
101
|
-
logger.info(message
|
|
105
|
+
message += `removed: ${limitedBuckets(diff.removedBuckets, 20)}`;
|
|
106
|
+
logger.info(message, {
|
|
107
|
+
checkpoint,
|
|
108
|
+
user_id: syncParams.user_id,
|
|
109
|
+
buckets: allBuckets.length,
|
|
110
|
+
updated: diff.updatedBuckets.length,
|
|
111
|
+
removed: diff.removedBuckets.length
|
|
112
|
+
});
|
|
102
113
|
const checksum_line = {
|
|
103
114
|
checkpoint_diff: {
|
|
104
115
|
last_op_id: checkpoint,
|
|
@@ -112,7 +123,7 @@ async function* streamResponseInner(storage, params, syncParams, signal) {
|
|
|
112
123
|
else {
|
|
113
124
|
let message = `New checkpoint: ${checkpoint} | write: ${writeCheckpoint} | `;
|
|
114
125
|
message += `buckets: ${allBuckets.length} ${limitedBuckets(allBuckets, 20)}`;
|
|
115
|
-
logger.info(message);
|
|
126
|
+
logger.info(message, { checkpoint, user_id: syncParams.user_id, buckets: allBuckets.length });
|
|
116
127
|
bucketsToFetch = allBuckets;
|
|
117
128
|
const checksum_line = {
|
|
118
129
|
checkpoint: {
|
|
@@ -127,7 +138,16 @@ async function* streamResponseInner(storage, params, syncParams, signal) {
|
|
|
127
138
|
lastWriteCheckpoint = writeCheckpoint;
|
|
128
139
|
// This incrementally updates dataBuckets with each individual bucket position.
|
|
129
140
|
// At the end of this, we can be sure that all buckets have data up to the checkpoint.
|
|
130
|
-
yield* bucketDataInBatches({
|
|
141
|
+
yield* bucketDataInBatches({
|
|
142
|
+
storage,
|
|
143
|
+
checkpoint,
|
|
144
|
+
bucketsToFetch,
|
|
145
|
+
dataBuckets,
|
|
146
|
+
raw_data,
|
|
147
|
+
binary_data,
|
|
148
|
+
signal,
|
|
149
|
+
tracker
|
|
150
|
+
});
|
|
131
151
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
132
152
|
}
|
|
133
153
|
}
|
|
@@ -168,7 +188,9 @@ async function* bucketDataInBatches(request) {
|
|
|
168
188
|
* Extracted as a separate internal function just to avoid memory leaks.
|
|
169
189
|
*/
|
|
170
190
|
async function* bucketDataBatch(request) {
|
|
171
|
-
const { storage, checkpoint, bucketsToFetch, dataBuckets, raw_data, binary_data, signal } = request;
|
|
191
|
+
const { storage, checkpoint, bucketsToFetch, dataBuckets, raw_data, binary_data, tracker, signal } = request;
|
|
192
|
+
const checkpointOp = BigInt(checkpoint);
|
|
193
|
+
let checkpointInvalidated = false;
|
|
172
194
|
const [_, release] = await syncSemaphore.acquire();
|
|
173
195
|
try {
|
|
174
196
|
// Optimization: Only fetch buckets for which the checksums have changed since the last checkpoint
|
|
@@ -176,13 +198,16 @@ async function* bucketDataBatch(request) {
|
|
|
176
198
|
const filteredBuckets = new Map(bucketsToFetch.map((bucket) => [bucket, dataBuckets.get(bucket)]));
|
|
177
199
|
const data = storage.getBucketDataBatch(checkpoint, filteredBuckets);
|
|
178
200
|
let has_more = false;
|
|
179
|
-
for await (let r of data) {
|
|
201
|
+
for await (let { batch: r, targetOp } of data) {
|
|
180
202
|
if (signal.aborted) {
|
|
181
203
|
return;
|
|
182
204
|
}
|
|
183
205
|
if (r.has_more) {
|
|
184
206
|
has_more = true;
|
|
185
207
|
}
|
|
208
|
+
if (targetOp != null && targetOp > checkpointOp) {
|
|
209
|
+
checkpointInvalidated = true;
|
|
210
|
+
}
|
|
186
211
|
if (r.data.length == 0) {
|
|
187
212
|
continue;
|
|
188
213
|
}
|
|
@@ -213,16 +238,24 @@ async function* bucketDataBatch(request) {
|
|
|
213
238
|
// iterator memory in case if large data sent.
|
|
214
239
|
yield { data: null, done: false };
|
|
215
240
|
}
|
|
216
|
-
|
|
241
|
+
tracker.addOperationsSynced(r.data.length);
|
|
217
242
|
dataBuckets.set(r.bucket, r.next_after);
|
|
218
243
|
}
|
|
219
244
|
if (!has_more) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
245
|
+
if (checkpointInvalidated) {
|
|
246
|
+
// Checkpoint invalidated by a CLEAR or MOVE op.
|
|
247
|
+
// Don't send the checkpoint_complete line in this case.
|
|
248
|
+
// More data should be available immediately for a new checkpoint.
|
|
249
|
+
yield { data: null, done: true };
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const line = {
|
|
253
|
+
checkpoint_complete: {
|
|
254
|
+
last_op_id: checkpoint
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
yield { data: line, done: true };
|
|
258
|
+
}
|
|
226
259
|
}
|
|
227
260
|
}
|
|
228
261
|
finally {
|
package/dist/sync/sync.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/sync/sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAEpE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAI9C,OAAO,KAAK,IAAI,MAAM,uBAAuB,CAAC;AAE9C,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/sync/sync.ts"],"names":[],"mappings":"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"}
|
package/dist/sync/util.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
/// <reference types="node" resolution-mode="require"/>
|
|
3
3
|
import * as util from '../util/util-index.js';
|
|
4
|
+
import { RequestTracker } from './RequestTracker.js';
|
|
4
5
|
export type TokenStreamOptions = {
|
|
5
6
|
/**
|
|
6
7
|
* Adds periodic keepalive events
|
|
@@ -23,4 +24,4 @@ export declare function tokenStream(token: {
|
|
|
23
24
|
exp: number;
|
|
24
25
|
}, signal: AbortSignal, options?: Partial<TokenStreamOptions>): AsyncGenerator<util.StreamingSyncKeepalive>;
|
|
25
26
|
export declare function ndjson(iterator: AsyncIterable<string | null | Record<string, any>>): AsyncGenerator<string>;
|
|
26
|
-
export declare function transformToBytesTracked(iterator: AsyncIterable<string
|
|
27
|
+
export declare function transformToBytesTracked(iterator: AsyncIterable<string>, tracker: RequestTracker): AsyncGenerator<Buffer>;
|
package/dist/sync/util.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as timers from 'timers/promises';
|
|
2
|
-
import { Metrics } from '../metrics/Metrics.js';
|
|
3
2
|
const KEEPALIVE_INTERVAL = 20000;
|
|
4
3
|
const DEFAULT_TOKEN_STREAM_OPTIONS = {
|
|
5
4
|
keep_alive: true,
|
|
@@ -63,10 +62,10 @@ export async function* ndjson(iterator) {
|
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
|
-
export async function* transformToBytesTracked(iterator) {
|
|
65
|
+
export async function* transformToBytesTracked(iterator, tracker) {
|
|
67
66
|
for await (let data of iterator) {
|
|
68
67
|
const encoded = Buffer.from(data, 'utf8');
|
|
69
|
-
|
|
68
|
+
tracker.addDataSynced(encoded.length);
|
|
70
69
|
yield encoded;
|
|
71
70
|
}
|
|
72
71
|
}
|
package/dist/sync/util.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/sync/util.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../../src/sync/util.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
8
|
-
"version": "0.0.0-dev-
|
|
8
|
+
"version": "0.0.0-dev-20240725112650",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"license": "FSL-1.1-Apache-2.0",
|
|
11
11
|
"type": "module",
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"uuid": "^9.0.1",
|
|
34
34
|
"winston": "^3.13.0",
|
|
35
35
|
"yaml": "^2.3.2",
|
|
36
|
-
"@powersync/lib-services-framework": "0.1.
|
|
36
|
+
"@powersync/lib-services-framework": "0.1.1",
|
|
37
|
+
"@powersync/service-jsonbig": "0.17.10",
|
|
38
|
+
"@powersync/service-rsocket-router": "0.0.10",
|
|
39
|
+
"@powersync/service-sync-rules": "0.18.1",
|
|
37
40
|
"@powersync/service-jpgwire": "0.17.13",
|
|
38
|
-
"@powersync/service-
|
|
39
|
-
"@powersync/service-types": "0.1.0",
|
|
40
|
-
"@powersync/service-rsocket-router": "0.0.8",
|
|
41
|
-
"@powersync/service-jsonbig": "0.17.10"
|
|
41
|
+
"@powersync/service-types": "0.1.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/async": "^3.2.24",
|
package/src/entry/cli-entry.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Command } from 'commander';
|
|
|
3
3
|
import * as utils from '../util/util-index.js';
|
|
4
4
|
import { registerMigrationAction } from './commands/migrate-action.js';
|
|
5
5
|
import { registerTearDownAction } from './commands/teardown-action.js';
|
|
6
|
-
import { registerStartAction } from './entry-index.js';
|
|
6
|
+
import { registerCompactAction, registerStartAction } from './entry-index.js';
|
|
7
7
|
import { logger } from '@powersync/lib-services-framework';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -18,6 +18,7 @@ export function generateEntryProgram(startHandlers?: Record<utils.ServiceRunner,
|
|
|
18
18
|
|
|
19
19
|
registerTearDownAction(entryProgram);
|
|
20
20
|
registerMigrationAction(entryProgram);
|
|
21
|
+
registerCompactAction(entryProgram);
|
|
21
22
|
|
|
22
23
|
if (startHandlers) {
|
|
23
24
|
registerStartAction(entryProgram, startHandlers);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
import { logger } from '@powersync/lib-services-framework';
|
|
4
|
+
import * as v8 from 'v8';
|
|
5
|
+
import { createPowerSyncMongo, MongoBucketStorage } from '../../storage/storage-index.js';
|
|
6
|
+
import { loadConfig } from '../../util/config.js';
|
|
7
|
+
import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
|
|
8
|
+
|
|
9
|
+
const COMMAND_NAME = 'compact';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Approximately max-old-space-size + 64MB.
|
|
13
|
+
*/
|
|
14
|
+
const HEAP_LIMIT = v8.getHeapStatistics().heap_size_limit;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Subtract 128MB for process overhead.
|
|
18
|
+
*
|
|
19
|
+
* Limit to 1024MB overall.
|
|
20
|
+
*/
|
|
21
|
+
const COMPACT_MEMORY_LIMIT_MB = Math.min(HEAP_LIMIT / 1024 / 1024 - 128, 1024);
|
|
22
|
+
|
|
23
|
+
export function registerCompactAction(program: Command) {
|
|
24
|
+
const compactCommand = program.command(COMMAND_NAME);
|
|
25
|
+
|
|
26
|
+
wrapConfigCommand(compactCommand);
|
|
27
|
+
|
|
28
|
+
return compactCommand.description('Compact storage').action(async (options) => {
|
|
29
|
+
const runnerConfig = extractRunnerOptions(options);
|
|
30
|
+
|
|
31
|
+
const config = await loadConfig(runnerConfig);
|
|
32
|
+
const { storage } = config;
|
|
33
|
+
const psdb = createPowerSyncMongo(storage);
|
|
34
|
+
const client = psdb.client;
|
|
35
|
+
await client.connect();
|
|
36
|
+
try {
|
|
37
|
+
const bucketStorage = new MongoBucketStorage(psdb, { slot_name_prefix: config.slot_name_prefix });
|
|
38
|
+
const active = await bucketStorage.getActiveSyncRules();
|
|
39
|
+
if (active == null) {
|
|
40
|
+
logger.info('No active instance to compact');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const p = bucketStorage.getInstance(active);
|
|
44
|
+
await p.compact({ memoryLimitMB: COMPACT_MEMORY_LIMIT_MB });
|
|
45
|
+
logger.info('done');
|
|
46
|
+
} catch (e) {
|
|
47
|
+
logger.error(`Failed to compact: ${e.toString()}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
} finally {
|
|
50
|
+
await client.close();
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
package/src/entry/entry-index.ts
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type fastify from 'fastify';
|
|
2
|
+
import { registerFastifyRoutes } from './route-register.js';
|
|
3
|
+
|
|
4
|
+
import * as system from '../system/system-index.js';
|
|
5
|
+
|
|
6
|
+
import { ADMIN_ROUTES } from './endpoints/admin.js';
|
|
7
|
+
import { CHECKPOINT_ROUTES } from './endpoints/checkpointing.js';
|
|
8
|
+
import { DEV_ROUTES } from './endpoints/dev.js';
|
|
9
|
+
import { SYNC_RULES_ROUTES } from './endpoints/sync-rules.js';
|
|
10
|
+
import { SYNC_STREAM_ROUTES } from './endpoints/sync-stream.js';
|
|
11
|
+
import { createRequestQueueHook, CreateRequestQueueParams } from './hooks.js';
|
|
12
|
+
import { RouteDefinition } from './router.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A list of route definitions to be registered as endpoints.
|
|
16
|
+
* Supplied concurrency limits will be applied to the grouped routes.
|
|
17
|
+
*/
|
|
18
|
+
export type RouteRegistrationOptions = {
|
|
19
|
+
routes: RouteDefinition[];
|
|
20
|
+
queueOptions: CreateRequestQueueParams;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* HTTP routes separated by API and Sync stream categories.
|
|
25
|
+
* This allows for separate concurrency limits.
|
|
26
|
+
*/
|
|
27
|
+
export type RouteDefinitions = {
|
|
28
|
+
api?: Partial<RouteRegistrationOptions>;
|
|
29
|
+
syncStream?: Partial<RouteRegistrationOptions>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type FastifyServerConfig = {
|
|
33
|
+
system: system.CorePowerSyncSystem;
|
|
34
|
+
routes?: RouteDefinitions;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const DEFAULT_ROUTE_OPTIONS = {
|
|
38
|
+
api: {
|
|
39
|
+
routes: [...ADMIN_ROUTES, ...CHECKPOINT_ROUTES, ...DEV_ROUTES, ...SYNC_RULES_ROUTES],
|
|
40
|
+
queueOptions: {
|
|
41
|
+
concurrency: 10,
|
|
42
|
+
max_queue_depth: 20
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
syncStream: {
|
|
46
|
+
routes: [...SYNC_STREAM_ROUTES],
|
|
47
|
+
queueOptions: {
|
|
48
|
+
concurrency: 200,
|
|
49
|
+
max_queue_depth: 0
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Registers default routes on a Fastify server. Consumers can optionally configure
|
|
56
|
+
* concurrency queue limits or override routes.
|
|
57
|
+
*/
|
|
58
|
+
export function configureFastifyServer(server: fastify.FastifyInstance, options: FastifyServerConfig) {
|
|
59
|
+
const { system, routes = DEFAULT_ROUTE_OPTIONS } = options;
|
|
60
|
+
/**
|
|
61
|
+
* Fastify creates an encapsulated context for each `.register` call.
|
|
62
|
+
* Creating a separate context here to separate the concurrency limits for Admin APIs
|
|
63
|
+
* and Sync Streaming routes.
|
|
64
|
+
* https://github.com/fastify/fastify/blob/main/docs/Reference/Encapsulation.md
|
|
65
|
+
*/
|
|
66
|
+
server.register(async function (childContext) {
|
|
67
|
+
registerFastifyRoutes(
|
|
68
|
+
childContext,
|
|
69
|
+
async () => {
|
|
70
|
+
return {
|
|
71
|
+
user_id: undefined,
|
|
72
|
+
system: system
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
routes.api?.routes ?? DEFAULT_ROUTE_OPTIONS.api.routes
|
|
76
|
+
);
|
|
77
|
+
// Limit the active concurrent requests
|
|
78
|
+
childContext.addHook(
|
|
79
|
+
'onRequest',
|
|
80
|
+
createRequestQueueHook(routes.api?.queueOptions ?? DEFAULT_ROUTE_OPTIONS.api.queueOptions)
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Create a separate context for concurrency queueing
|
|
85
|
+
server.register(async function (childContext) {
|
|
86
|
+
registerFastifyRoutes(
|
|
87
|
+
childContext,
|
|
88
|
+
async () => {
|
|
89
|
+
return {
|
|
90
|
+
user_id: undefined,
|
|
91
|
+
system: system
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
routes.syncStream?.routes ?? DEFAULT_ROUTE_OPTIONS.syncStream.routes
|
|
95
|
+
);
|
|
96
|
+
// Limit the active concurrent requests
|
|
97
|
+
childContext.addHook(
|
|
98
|
+
'onRequest',
|
|
99
|
+
createRequestQueueHook(routes.syncStream?.queueOptions ?? DEFAULT_ROUTE_OPTIONS.syncStream.queueOptions)
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { deserialize } from 'bson';
|
|
2
|
+
import * as http from 'http';
|
|
3
|
+
|
|
4
|
+
import { errors, logger } from '@powersync/lib-services-framework';
|
|
5
|
+
import { ReactiveSocketRouter, RSocketRequestMeta } from '@powersync/service-rsocket-router';
|
|
6
|
+
|
|
7
|
+
import { CorePowerSyncSystem } from '../system/CorePowerSyncSystem.js';
|
|
8
|
+
import { generateContext, getTokenFromHeader } from './auth.js';
|
|
9
|
+
import { syncStreamReactive } from './endpoints/socket-route.js';
|
|
10
|
+
import { RSocketContextMeta, SocketRouteGenerator } from './router-socket.js';
|
|
11
|
+
import { Context } from './router.js';
|
|
12
|
+
|
|
13
|
+
export type RSockerRouterConfig = {
|
|
14
|
+
system: CorePowerSyncSystem;
|
|
15
|
+
server: http.Server;
|
|
16
|
+
routeGenerators?: SocketRouteGenerator[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_SOCKET_ROUTES = [syncStreamReactive];
|
|
20
|
+
|
|
21
|
+
export function configureRSocket(router: ReactiveSocketRouter<Context>, options: RSockerRouterConfig) {
|
|
22
|
+
const { routeGenerators = DEFAULT_SOCKET_ROUTES, server, system } = options;
|
|
23
|
+
|
|
24
|
+
router.applyWebSocketEndpoints(server, {
|
|
25
|
+
contextProvider: async (data: Buffer) => {
|
|
26
|
+
const { token } = RSocketContextMeta.decode(deserialize(data) as any);
|
|
27
|
+
|
|
28
|
+
if (!token) {
|
|
29
|
+
throw new errors.AuthorizationError('No token provided');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const extracted_token = getTokenFromHeader(token);
|
|
34
|
+
if (extracted_token != null) {
|
|
35
|
+
const { context, errors: token_errors } = await generateContext(system, extracted_token);
|
|
36
|
+
if (context?.token_payload == null) {
|
|
37
|
+
throw new errors.AuthorizationError(token_errors ?? 'Authentication required');
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
token,
|
|
41
|
+
...context,
|
|
42
|
+
token_errors: token_errors,
|
|
43
|
+
system
|
|
44
|
+
};
|
|
45
|
+
} else {
|
|
46
|
+
throw new errors.AuthorizationError('No token provided');
|
|
47
|
+
}
|
|
48
|
+
} catch (ex) {
|
|
49
|
+
logger.error(ex);
|
|
50
|
+
throw ex;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
endpoints: routeGenerators.map((generator) => generator(router)),
|
|
54
|
+
metaDecoder: async (meta: Buffer) => {
|
|
55
|
+
return RSocketRequestMeta.decode(deserialize(meta) as any);
|
|
56
|
+
},
|
|
57
|
+
payloadDecoder: async (rawData?: Buffer) => rawData && deserialize(rawData)
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -3,19 +3,13 @@ import { RequestParameters } from '@powersync/service-sync-rules';
|
|
|
3
3
|
import { serialize } from 'bson';
|
|
4
4
|
|
|
5
5
|
import { Metrics } from '../../metrics/Metrics.js';
|
|
6
|
-
import
|
|
6
|
+
import * as sync from '../../sync/sync-index.js';
|
|
7
7
|
import * as util from '../../util/util-index.js';
|
|
8
8
|
import { SocketRouteGenerator } from '../router-socket.js';
|
|
9
9
|
import { SyncRoutes } from './sync-stream.js';
|
|
10
10
|
|
|
11
11
|
export const syncStreamReactive: SocketRouteGenerator = (router) =>
|
|
12
12
|
router.reactiveStream<util.StreamingSyncRequest, any>(SyncRoutes.STREAM, {
|
|
13
|
-
authorize: ({ context }) => {
|
|
14
|
-
return {
|
|
15
|
-
authorized: !!context.token_payload,
|
|
16
|
-
errors: ['Authentication required'].concat(context.token_errors ?? [])
|
|
17
|
-
};
|
|
18
|
-
},
|
|
19
13
|
validator: schema.createTsCodecValidator(util.StreamingSyncRequest, { allowAdditional: true }),
|
|
20
14
|
handler: async ({ context, params, responder, observer, initialN }) => {
|
|
21
15
|
const { system } = context;
|
|
@@ -66,8 +60,9 @@ export const syncStreamReactive: SocketRouteGenerator = (router) =>
|
|
|
66
60
|
});
|
|
67
61
|
|
|
68
62
|
Metrics.getInstance().concurrent_connections.add(1);
|
|
63
|
+
const tracker = new sync.RequestTracker();
|
|
69
64
|
try {
|
|
70
|
-
for await (const data of streamResponse({
|
|
65
|
+
for await (const data of sync.streamResponse({
|
|
71
66
|
storage,
|
|
72
67
|
params: {
|
|
73
68
|
...params,
|
|
@@ -79,6 +74,7 @@ export const syncStreamReactive: SocketRouteGenerator = (router) =>
|
|
|
79
74
|
// RSocket handles keepalive events by default
|
|
80
75
|
keep_alive: false
|
|
81
76
|
},
|
|
77
|
+
tracker,
|
|
82
78
|
signal: controller.signal
|
|
83
79
|
})) {
|
|
84
80
|
if (data == null) {
|
|
@@ -94,7 +90,7 @@ export const syncStreamReactive: SocketRouteGenerator = (router) =>
|
|
|
94
90
|
const serialized = serialize(data) as Buffer;
|
|
95
91
|
responder.onNext({ data: serialized }, false);
|
|
96
92
|
requestedN--;
|
|
97
|
-
|
|
93
|
+
tracker.addDataSynced(serialized.length);
|
|
98
94
|
}
|
|
99
95
|
|
|
100
96
|
if (requestedN <= 0) {
|
|
@@ -126,6 +122,11 @@ export const syncStreamReactive: SocketRouteGenerator = (router) =>
|
|
|
126
122
|
responder.onComplete();
|
|
127
123
|
removeStopHandler();
|
|
128
124
|
disposer();
|
|
125
|
+
logger.info(`Sync stream complete`, {
|
|
126
|
+
user_id: syncParams.user_id,
|
|
127
|
+
operations_synced: tracker.operationsSynced,
|
|
128
|
+
data_synced_bytes: tracker.dataSyncedBytes
|
|
129
|
+
});
|
|
129
130
|
Metrics.getInstance().concurrent_connections.add(-1);
|
|
130
131
|
}
|
|
131
132
|
}
|
|
@@ -8,6 +8,7 @@ import * as util from '../../util/util-index.js';
|
|
|
8
8
|
import { Metrics } from '../../metrics/Metrics.js';
|
|
9
9
|
import { authUser } from '../auth.js';
|
|
10
10
|
import { routeDefinition } from '../router.js';
|
|
11
|
+
import { RequestTracker } from '../../sync/RequestTracker.js';
|
|
11
12
|
|
|
12
13
|
export enum SyncRoutes {
|
|
13
14
|
STREAM = '/sync/stream'
|
|
@@ -43,6 +44,7 @@ export const syncStreamed = routeDefinition({
|
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
46
|
const controller = new AbortController();
|
|
47
|
+
const tracker = new RequestTracker();
|
|
46
48
|
try {
|
|
47
49
|
Metrics.getInstance().concurrent_connections.add(1);
|
|
48
50
|
const stream = Readable.from(
|
|
@@ -53,9 +55,11 @@ export const syncStreamed = routeDefinition({
|
|
|
53
55
|
params,
|
|
54
56
|
syncParams,
|
|
55
57
|
token: payload.context.token_payload!,
|
|
58
|
+
tracker,
|
|
56
59
|
signal: controller.signal
|
|
57
60
|
})
|
|
58
|
-
)
|
|
61
|
+
),
|
|
62
|
+
tracker
|
|
59
63
|
),
|
|
60
64
|
{ objectMode: false, highWaterMark: 16 * 1024 }
|
|
61
65
|
);
|
|
@@ -86,6 +90,11 @@ export const syncStreamed = routeDefinition({
|
|
|
86
90
|
afterSend: async () => {
|
|
87
91
|
controller.abort();
|
|
88
92
|
Metrics.getInstance().concurrent_connections.add(-1);
|
|
93
|
+
logger.info(`Sync stream complete`, {
|
|
94
|
+
user_id: syncParams.user_id,
|
|
95
|
+
operations_synced: tracker.operationsSynced,
|
|
96
|
+
data_synced_bytes: tracker.dataSyncedBytes
|
|
97
|
+
});
|
|
89
98
|
}
|
|
90
99
|
});
|
|
91
100
|
} catch (ex) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fastify from 'fastify';
|
|
1
|
+
import type fastify from 'fastify';
|
|
2
2
|
|
|
3
|
-
import { errors,
|
|
3
|
+
import { errors, HTTPMethod, logger, router } from '@powersync/lib-services-framework';
|
|
4
4
|
import { Context, ContextProvider, RequestEndpoint, RequestEndpointHandlerPayload } from './router.js';
|
|
5
5
|
|
|
6
6
|
export type FastifyEndpoint<I, O, C> = RequestEndpoint<I, O, C> & {
|
|
@@ -63,6 +63,7 @@ export function registerFastifyRoutes(
|
|
|
63
63
|
}
|
|
64
64
|
} catch (ex) {
|
|
65
65
|
const journeyError = errors.JourneyError.isJourneyError(ex) ? ex : new errors.InternalServerError(ex);
|
|
66
|
+
logger.error(`Request failed`, journeyError);
|
|
66
67
|
|
|
67
68
|
response = new router.RouterResponse({
|
|
68
69
|
status: journeyError.errorData.status || 500,
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { IReactiveStream, ReactiveSocketRouter } from '@powersync/service-rsocket-router';
|
|
1
2
|
import * as t from 'ts-codec';
|
|
2
|
-
import { ReactiveSocketRouter, IReactiveStream } from '@powersync/service-rsocket-router';
|
|
3
3
|
|
|
4
4
|
import { Context } from './router.js';
|
|
5
5
|
|
|
6
|
-
export const RSocketContextMeta = t.object({
|
|
7
|
-
token: t.string
|
|
8
|
-
});
|
|
9
|
-
|
|
10
6
|
/**
|
|
11
7
|
* Creates a socket route handler given a router instance
|
|
12
8
|
*/
|
|
13
9
|
export type SocketRouteGenerator = (router: ReactiveSocketRouter<Context>) => IReactiveStream;
|
|
10
|
+
|
|
11
|
+
export const RSocketContextMeta = t.object({
|
|
12
|
+
token: t.string
|
|
13
|
+
});
|
package/src/routes/router.ts
CHANGED