@powersync/service-core 1.11.3 → 1.12.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/CHANGELOG.md +46 -0
- package/dist/auth/CachedKeyCollector.js +2 -7
- package/dist/auth/CachedKeyCollector.js.map +1 -1
- package/dist/auth/CompoundKeyCollector.js.map +1 -1
- package/dist/auth/KeyCollector.d.ts +2 -2
- package/dist/auth/KeyStore.js +32 -14
- package/dist/auth/KeyStore.js.map +1 -1
- package/dist/auth/RemoteJWKSCollector.d.ts +1 -0
- package/dist/auth/RemoteJWKSCollector.js +39 -16
- package/dist/auth/RemoteJWKSCollector.js.map +1 -1
- package/dist/auth/auth-index.d.ts +1 -0
- package/dist/auth/auth-index.js +1 -0
- package/dist/auth/auth-index.js.map +1 -1
- package/dist/auth/utils.d.ts +6 -0
- package/dist/auth/utils.js +97 -0
- package/dist/auth/utils.js.map +1 -0
- package/dist/entry/commands/compact-action.js +4 -1
- package/dist/entry/commands/compact-action.js.map +1 -1
- package/dist/entry/commands/migrate-action.js +4 -1
- package/dist/entry/commands/migrate-action.js.map +1 -1
- package/dist/entry/commands/test-connection-action.js +4 -1
- package/dist/entry/commands/test-connection-action.js.map +1 -1
- package/dist/routes/RouterEngine.d.ts +2 -0
- package/dist/routes/RouterEngine.js +15 -10
- package/dist/routes/RouterEngine.js.map +1 -1
- package/dist/routes/auth.d.ts +5 -16
- package/dist/routes/auth.js +6 -4
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +3 -21
- package/dist/routes/configure-fastify.js +3 -6
- package/dist/routes/configure-fastify.js.map +1 -1
- package/dist/routes/configure-rsocket.js +28 -14
- package/dist/routes/configure-rsocket.js.map +1 -1
- package/dist/routes/endpoints/admin.js.map +1 -1
- package/dist/routes/endpoints/checkpointing.d.ts +4 -28
- package/dist/routes/endpoints/checkpointing.js.map +1 -1
- package/dist/routes/endpoints/route-endpoints-index.d.ts +1 -0
- package/dist/routes/endpoints/route-endpoints-index.js +1 -0
- package/dist/routes/endpoints/route-endpoints-index.js.map +1 -1
- package/dist/routes/endpoints/socket-route.js +22 -8
- package/dist/routes/endpoints/socket-route.js.map +1 -1
- package/dist/routes/endpoints/sync-rules.js.map +1 -1
- package/dist/routes/endpoints/sync-stream.d.ts +2 -14
- package/dist/routes/endpoints/sync-stream.js +28 -9
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/routes/route-register.js +10 -6
- package/dist/routes/route-register.js.map +1 -1
- package/dist/routes/router.d.ts +8 -7
- package/dist/routes/router.js.map +1 -1
- package/dist/runner/teardown.js +4 -1
- package/dist/runner/teardown.js.map +1 -1
- package/dist/sync/BucketChecksumState.d.ts +40 -18
- package/dist/sync/BucketChecksumState.js +120 -72
- package/dist/sync/BucketChecksumState.js.map +1 -1
- package/dist/sync/RequestTracker.d.ts +22 -1
- package/dist/sync/RequestTracker.js +51 -2
- package/dist/sync/RequestTracker.js.map +1 -1
- package/dist/sync/sync.d.ts +3 -5
- package/dist/sync/sync.js +47 -32
- package/dist/sync/sync.js.map +1 -1
- package/dist/system/ServiceContext.d.ts +19 -4
- package/dist/system/ServiceContext.js +20 -8
- package/dist/system/ServiceContext.js.map +1 -1
- package/dist/util/config/collectors/config-collector.js +4 -33
- package/dist/util/config/collectors/config-collector.js.map +1 -1
- package/dist/util/config/collectors/impl/yaml-env.d.ts +7 -0
- package/dist/util/config/collectors/impl/yaml-env.js +59 -0
- package/dist/util/config/collectors/impl/yaml-env.js.map +1 -0
- package/dist/util/config/compound-config-collector.js +18 -1
- package/dist/util/config/compound-config-collector.js.map +1 -1
- package/dist/util/config/types.d.ts +11 -0
- package/package.json +6 -7
- package/src/auth/CachedKeyCollector.ts +4 -6
- package/src/auth/CompoundKeyCollector.ts +2 -1
- package/src/auth/KeyCollector.ts +2 -2
- package/src/auth/KeyStore.ts +45 -20
- package/src/auth/RemoteJWKSCollector.ts +39 -16
- package/src/auth/auth-index.ts +1 -0
- package/src/auth/utils.ts +102 -0
- package/src/entry/commands/compact-action.ts +4 -1
- package/src/entry/commands/migrate-action.ts +4 -1
- package/src/entry/commands/test-connection-action.ts +4 -1
- package/src/routes/RouterEngine.ts +21 -11
- package/src/routes/auth.ts +7 -6
- package/src/routes/configure-fastify.ts +6 -8
- package/src/routes/configure-rsocket.ts +33 -18
- package/src/routes/endpoints/admin.ts +5 -5
- package/src/routes/endpoints/checkpointing.ts +2 -2
- package/src/routes/endpoints/route-endpoints-index.ts +1 -0
- package/src/routes/endpoints/socket-route.ts +27 -11
- package/src/routes/endpoints/sync-rules.ts +4 -4
- package/src/routes/endpoints/sync-stream.ts +34 -11
- package/src/routes/route-register.ts +10 -7
- package/src/routes/router.ts +11 -4
- package/src/runner/teardown.ts +5 -1
- package/src/sync/BucketChecksumState.ts +160 -75
- package/src/sync/RequestTracker.ts +70 -3
- package/src/sync/sync.ts +69 -46
- package/src/system/ServiceContext.ts +31 -12
- package/src/util/config/collectors/config-collector.ts +4 -40
- package/src/util/config/collectors/impl/yaml-env.ts +67 -0
- package/src/util/config/compound-config-collector.ts +22 -5
- package/src/util/config/types.ts +13 -0
- package/test/src/auth.test.ts +29 -11
- package/test/src/config.test.ts +72 -0
- package/test/src/sync/BucketChecksumState.test.ts +32 -18
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ErrorCode, errors,
|
|
1
|
+
import { ErrorCode, errors, router, schema } from '@powersync/lib-services-framework';
|
|
2
2
|
import { RequestParameters } from '@powersync/service-sync-rules';
|
|
3
3
|
import { Readable } from 'stream';
|
|
4
4
|
import * as sync from '../../sync/sync-index.js';
|
|
@@ -16,11 +16,18 @@ export const syncStreamed = routeDefinition({
|
|
|
16
16
|
authorize: authUser,
|
|
17
17
|
validator: schema.createTsCodecValidator(util.StreamingSyncRequest, { allowAdditional: true }),
|
|
18
18
|
handler: async (payload) => {
|
|
19
|
-
const { service_context } = payload.context;
|
|
19
|
+
const { service_context, logger } = payload.context;
|
|
20
20
|
const { routerEngine, storageEngine, metricsEngine, syncContext } = service_context;
|
|
21
21
|
const headers = payload.request.headers;
|
|
22
22
|
const userAgent = headers['x-user-agent'] ?? headers['user-agent'];
|
|
23
23
|
const clientId = payload.params.client_id;
|
|
24
|
+
const streamStart = Date.now();
|
|
25
|
+
logger.defaultMeta = {
|
|
26
|
+
...logger.defaultMeta,
|
|
27
|
+
user_agent: userAgent,
|
|
28
|
+
client_id: clientId,
|
|
29
|
+
user_id: payload.context.user_id
|
|
30
|
+
};
|
|
24
31
|
if (routerEngine.closed) {
|
|
25
32
|
throw new errors.ServiceError({
|
|
26
33
|
status: 503,
|
|
@@ -51,17 +58,28 @@ export const syncStreamed = routeDefinition({
|
|
|
51
58
|
syncParams,
|
|
52
59
|
token: payload.context.token_payload,
|
|
53
60
|
tracker,
|
|
54
|
-
signal: controller.signal
|
|
61
|
+
signal: controller.signal,
|
|
62
|
+
logger
|
|
55
63
|
})), tracker), { objectMode: false, highWaterMark: 16 * 1024 });
|
|
64
|
+
// Best effort guess on why the stream was closed.
|
|
65
|
+
// We use the `??=` operator everywhere, so that we catch the first relevant
|
|
66
|
+
// event, which is usually the most specific.
|
|
67
|
+
let closeReason = undefined;
|
|
56
68
|
const deregister = routerEngine.addStopHandler(() => {
|
|
57
69
|
// This error is not currently propagated to the client
|
|
58
70
|
controller.abort();
|
|
71
|
+
closeReason ??= 'process shutdown';
|
|
59
72
|
stream.destroy(new Error('Shutting down system'));
|
|
60
73
|
});
|
|
74
|
+
stream.on('end', () => {
|
|
75
|
+
// Auth failure or switch to new sync rules
|
|
76
|
+
closeReason ??= 'service closing stream';
|
|
77
|
+
});
|
|
61
78
|
stream.on('close', () => {
|
|
62
79
|
deregister();
|
|
63
80
|
});
|
|
64
81
|
stream.on('error', (error) => {
|
|
82
|
+
closeReason ??= 'stream error';
|
|
65
83
|
controller.abort();
|
|
66
84
|
// Note: This appears as a 200 response in the logs.
|
|
67
85
|
if (error.message != 'Shutting down system') {
|
|
@@ -74,15 +92,16 @@ export const syncStreamed = routeDefinition({
|
|
|
74
92
|
'Content-Type': 'application/x-ndjson'
|
|
75
93
|
},
|
|
76
94
|
data: stream,
|
|
77
|
-
afterSend: async () => {
|
|
95
|
+
afterSend: async (details) => {
|
|
96
|
+
if (details.clientClosed) {
|
|
97
|
+
closeReason ??= 'client closing stream';
|
|
98
|
+
}
|
|
78
99
|
controller.abort();
|
|
79
100
|
metricsEngine.getUpDownCounter(APIMetric.CONCURRENT_CONNECTIONS).add(-1);
|
|
80
101
|
logger.info(`Sync stream complete`, {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
operations_synced: tracker.operationsSynced,
|
|
85
|
-
data_synced_bytes: tracker.dataSyncedBytes
|
|
102
|
+
...tracker.getLogMeta(),
|
|
103
|
+
stream_ms: Date.now() - streamStart,
|
|
104
|
+
close_reason: closeReason ?? 'unknown'
|
|
86
105
|
});
|
|
87
106
|
}
|
|
88
107
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-stream.js","sourceRoot":"","sources":["../../../src/routes/endpoints/sync-stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"sync-stream.js","sourceRoot":"","sources":["../../../src/routes/endpoints/sync-stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as uuid from 'uuid';
|
|
1
2
|
import { errors, logger, router } from '@powersync/lib-services-framework';
|
|
2
3
|
/**
|
|
3
4
|
* Registers endpoint definitions as routes on a Fastify app instance.
|
|
@@ -12,8 +13,12 @@ export function registerFastifyRoutes(app, contextProvider, endpoints) {
|
|
|
12
13
|
handler: async (request, reply) => {
|
|
13
14
|
const startTime = new Date();
|
|
14
15
|
let response;
|
|
16
|
+
const requestLogger = logger.child({
|
|
17
|
+
route: e.path,
|
|
18
|
+
rid: `h/${uuid.v7()}`
|
|
19
|
+
});
|
|
15
20
|
try {
|
|
16
|
-
const context = await contextProvider(request);
|
|
21
|
+
const context = await contextProvider(request, { logger: requestLogger });
|
|
17
22
|
let combined = {
|
|
18
23
|
...request.params,
|
|
19
24
|
...request.query
|
|
@@ -48,7 +53,7 @@ export function registerFastifyRoutes(app, contextProvider, endpoints) {
|
|
|
48
53
|
}
|
|
49
54
|
catch (ex) {
|
|
50
55
|
const serviceError = errors.asServiceError(ex);
|
|
51
|
-
|
|
56
|
+
requestLogger.error(`Request failed`, serviceError);
|
|
52
57
|
response = new router.RouterResponse({
|
|
53
58
|
status: serviceError.errorData.status || 500,
|
|
54
59
|
headers: {
|
|
@@ -67,13 +72,12 @@ export function registerFastifyRoutes(app, contextProvider, endpoints) {
|
|
|
67
72
|
await reply.send(response.data);
|
|
68
73
|
}
|
|
69
74
|
finally {
|
|
70
|
-
await response.afterSend?.();
|
|
71
|
-
|
|
75
|
+
await response.afterSend?.({ clientClosed: request.socket.closed });
|
|
76
|
+
requestLogger.info(`${e.method} ${request.url}`, {
|
|
72
77
|
duration_ms: Math.round(new Date().valueOf() - startTime.valueOf() + Number.EPSILON),
|
|
73
78
|
status: response.status,
|
|
74
79
|
method: e.method,
|
|
75
|
-
path: request.url
|
|
76
|
-
route: e.path
|
|
80
|
+
path: request.url
|
|
77
81
|
});
|
|
78
82
|
}
|
|
79
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-register.js","sourceRoot":"","sources":["../../src/routes/route-register.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"route-register.js","sourceRoot":"","sources":["../../src/routes/route-register.ts"],"names":[],"mappings":"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"}
|
package/dist/routes/router.d.ts
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { router } from '@powersync/lib-services-framework';
|
|
1
|
+
import { router, ServiceError, Logger } from '@powersync/lib-services-framework';
|
|
2
2
|
import type { JwtPayload } from '../auth/auth-index.js';
|
|
3
3
|
import { ServiceContext } from '../system/ServiceContext.js';
|
|
4
|
-
import { RouterEngine } from './RouterEngine.js';
|
|
5
4
|
/**
|
|
6
5
|
* The {@link RouterEngine} must be provided for these routes
|
|
7
6
|
*/
|
|
8
|
-
export type RouterServiceContext = ServiceContext
|
|
9
|
-
routerEngine: RouterEngine;
|
|
10
|
-
};
|
|
7
|
+
export type RouterServiceContext = ServiceContext;
|
|
11
8
|
/**
|
|
12
9
|
* Common context for routes
|
|
13
10
|
*/
|
|
@@ -15,18 +12,22 @@ export type Context = {
|
|
|
15
12
|
user_id?: string;
|
|
16
13
|
service_context: RouterServiceContext;
|
|
17
14
|
token_payload?: JwtPayload;
|
|
18
|
-
|
|
15
|
+
token_error?: ServiceError;
|
|
19
16
|
/**
|
|
20
17
|
* Only on websocket endpoints.
|
|
21
18
|
*/
|
|
22
19
|
user_agent?: string;
|
|
20
|
+
logger: Logger;
|
|
23
21
|
};
|
|
24
22
|
export type BasicRouterRequest = {
|
|
25
23
|
headers: Record<string, string | string[] | undefined>;
|
|
26
24
|
protocol: string;
|
|
27
25
|
hostname: string;
|
|
28
26
|
};
|
|
29
|
-
export type
|
|
27
|
+
export type ConextProviderOptions = {
|
|
28
|
+
logger: Logger;
|
|
29
|
+
};
|
|
30
|
+
export type ContextProvider = (request: BasicRouterRequest, options: ConextProviderOptions) => Promise<Context>;
|
|
30
31
|
export type RequestEndpoint<I, O, C = Context, Payload = RequestEndpointHandlerPayload<I, C, BasicRouterRequest>> = router.Endpoint<I, O, C, Payload> & {};
|
|
31
32
|
export type RequestEndpointHandlerPayload<I = any, C = Context, Request = BasicRouterRequest> = router.EndpointHandlerPayload<I, C> & {
|
|
32
33
|
request: Request;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/routes/router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/routes/router.ts"],"names":[],"mappings":"AAwDA;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,MAA4C;IAE5C,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/runner/teardown.js
CHANGED
|
@@ -11,7 +11,10 @@ export async function teardown(runnerConfig) {
|
|
|
11
11
|
try {
|
|
12
12
|
logger.info(`Tearing down PowerSync instance...`);
|
|
13
13
|
const config = await utils.loadConfig(runnerConfig);
|
|
14
|
-
const serviceContext = new system.ServiceContextContainer(
|
|
14
|
+
const serviceContext = new system.ServiceContextContainer({
|
|
15
|
+
serviceMode: system.ServiceContextMode.TEARDOWN,
|
|
16
|
+
configuration: config
|
|
17
|
+
});
|
|
15
18
|
const moduleManager = container.getImplementation(modules.ModuleManager);
|
|
16
19
|
await moduleManager.initialize(serviceContext);
|
|
17
20
|
// This is mostly done to ensure that the storage is ready
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"teardown.js","sourceRoot":"","sources":["../../src/runner/teardown.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,eAAe;AACf,mFAAmF;AACnF,wBAAwB;AAExB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,MAAM,MAAM,iBAAiB,CAAC;AACrC,OAAO,KAAK,OAAO,MAAM,6BAA6B,CAAC;AAEvD,OAAO,KAAK,MAAM,MAAM,2BAA2B,CAAC;AACpD,OAAO,KAAK,KAAK,MAAM,uBAAuB,CAAC;AAE/C,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,YAAgC;IAC7D,IAAI,CAAC;QACH,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;QACpD,MAAM,cAAc,GAAG,IAAI,MAAM,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"teardown.js","sourceRoot":"","sources":["../../src/runner/teardown.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { BucketDescription, RequestParameters, SqlSyncRules } from '@powersync/service-sync-rules';
|
|
2
2
|
import * as storage from '../storage/storage-index.js';
|
|
3
3
|
import * as util from '../util/util-index.js';
|
|
4
|
-
import {
|
|
4
|
+
import { Logger } from '@powersync/lib-services-framework';
|
|
5
5
|
import { SyncContext } from './SyncContext.js';
|
|
6
6
|
export interface BucketChecksumStateOptions {
|
|
7
7
|
syncContext: SyncContext;
|
|
8
8
|
bucketStorage: BucketChecksumStateStorage;
|
|
9
9
|
syncRules: SqlSyncRules;
|
|
10
10
|
syncParams: RequestParameters;
|
|
11
|
+
logger?: Logger;
|
|
11
12
|
initialBucketPositions?: {
|
|
12
13
|
name: string;
|
|
13
14
|
after: util.InternalOpId;
|
|
14
15
|
}[];
|
|
15
16
|
}
|
|
17
|
+
type BucketSyncState = {
|
|
18
|
+
start_op_id: util.InternalOpId;
|
|
19
|
+
};
|
|
16
20
|
/**
|
|
17
21
|
* Represents the state of the checksums and data for a specific connection.
|
|
18
22
|
*
|
|
@@ -24,8 +28,10 @@ export declare class BucketChecksumState {
|
|
|
24
28
|
/**
|
|
25
29
|
* Bucket state of bucket id -> op_id.
|
|
26
30
|
* This starts with the state from the client. May contain buckets that the user do not have access to (anymore).
|
|
31
|
+
*
|
|
32
|
+
* This is always updated in-place.
|
|
27
33
|
*/
|
|
28
|
-
bucketDataPositions: Map<string, BucketSyncState>;
|
|
34
|
+
readonly bucketDataPositions: Map<string, BucketSyncState>;
|
|
29
35
|
/**
|
|
30
36
|
* Last checksums sent to the client. We keep this to calculate checkpoint diffs.
|
|
31
37
|
*/
|
|
@@ -37,26 +43,20 @@ export declare class BucketChecksumState {
|
|
|
37
43
|
* partial checkpoints are sent.
|
|
38
44
|
*/
|
|
39
45
|
private pendingBucketDownloads;
|
|
46
|
+
private readonly logger;
|
|
40
47
|
constructor(options: BucketChecksumStateOptions);
|
|
41
|
-
buildNextCheckpointLine(next: storage.StorageCheckpointUpdate): Promise<CheckpointLine | null>;
|
|
42
48
|
/**
|
|
43
|
-
*
|
|
49
|
+
* Build a new checkpoint line for an underlying storage checkpoint update if any buckets have changed.
|
|
44
50
|
*
|
|
45
|
-
*
|
|
46
|
-
* @returns
|
|
47
|
-
*/
|
|
48
|
-
getFilteredBucketPositions(bucketsToFetch: BucketDescription[]): Map<string, util.InternalOpId>;
|
|
49
|
-
/**
|
|
50
|
-
* Update the position of bucket data the client has.
|
|
51
|
+
* This call is idempotent - no internal state is updated directly when this is called.
|
|
51
52
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
53
|
+
* When the checkpoint line is sent to the client, call `CheckpointLine.advance()` to update the internal state.
|
|
54
|
+
* A line may be safely discarded (not sent to the client) if `advance()` is not called.
|
|
55
|
+
*
|
|
56
|
+
* @param next The updated checkpoint
|
|
57
|
+
* @returns A {@link CheckpointLine} if any of the buckets watched by this connected was updated, or otherwise `null`.
|
|
54
58
|
*/
|
|
55
|
-
|
|
56
|
-
bucket: string;
|
|
57
|
-
nextAfter: util.InternalOpId;
|
|
58
|
-
hasMore: boolean;
|
|
59
|
-
}): void;
|
|
59
|
+
buildNextCheckpointLine(next: storage.StorageCheckpointUpdate): Promise<CheckpointLine | null>;
|
|
60
60
|
}
|
|
61
61
|
declare const INVALIDATE_ALL_BUCKETS: unique symbol;
|
|
62
62
|
export interface CheckpointUpdate {
|
|
@@ -78,10 +78,11 @@ export declare class BucketParameterState {
|
|
|
78
78
|
readonly syncParams: RequestParameters;
|
|
79
79
|
private readonly querier;
|
|
80
80
|
private readonly staticBuckets;
|
|
81
|
+
private readonly logger;
|
|
81
82
|
private cachedDynamicBuckets;
|
|
82
83
|
private cachedDynamicBucketSet;
|
|
83
84
|
private readonly lookups;
|
|
84
|
-
constructor(context: SyncContext, bucketStorage: BucketChecksumStateStorage, syncRules: SqlSyncRules, syncParams: RequestParameters);
|
|
85
|
+
constructor(context: SyncContext, bucketStorage: BucketChecksumStateStorage, syncRules: SqlSyncRules, syncParams: RequestParameters, logger: Logger);
|
|
85
86
|
getCheckpointUpdate(checkpoint: storage.StorageCheckpointUpdate): Promise<CheckpointUpdate>;
|
|
86
87
|
/**
|
|
87
88
|
* For static buckets, we can keep track of which buckets have been updated.
|
|
@@ -95,6 +96,27 @@ export declare class BucketParameterState {
|
|
|
95
96
|
export interface CheckpointLine {
|
|
96
97
|
checkpointLine: util.StreamingSyncCheckpointDiff | util.StreamingSyncCheckpoint;
|
|
97
98
|
bucketsToFetch: BucketDescription[];
|
|
99
|
+
/**
|
|
100
|
+
* Call when a checkpoint line is being sent to a client, to update the internal state.
|
|
101
|
+
*/
|
|
102
|
+
advance: () => void;
|
|
103
|
+
/**
|
|
104
|
+
* Get bucket positions to sync, given the list of buckets.
|
|
105
|
+
*
|
|
106
|
+
* @param bucketsToFetch List of buckets to fetch - either this.bucketsToFetch, or a subset of it. Defaults to this.bucketsToFetch.
|
|
107
|
+
*/
|
|
108
|
+
getFilteredBucketPositions(bucketsToFetch?: BucketDescription[]): Map<string, util.InternalOpId>;
|
|
109
|
+
/**
|
|
110
|
+
* Update the position of bucket data the client has, after it was sent to the client.
|
|
111
|
+
*
|
|
112
|
+
* @param bucket the bucket name
|
|
113
|
+
* @param nextAfter sync operations >= this value in the next batch
|
|
114
|
+
*/
|
|
115
|
+
updateBucketPosition(options: {
|
|
116
|
+
bucket: string;
|
|
117
|
+
nextAfter: util.InternalOpId;
|
|
118
|
+
hasMore: boolean;
|
|
119
|
+
}): void;
|
|
98
120
|
}
|
|
99
121
|
export type BucketChecksumStateStorage = Pick<storage.SyncRulesBucketStorage, 'getChecksums' | 'getParameterSets'>;
|
|
100
122
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as util from '../util/util-index.js';
|
|
2
|
-
import { ErrorCode,
|
|
2
|
+
import { ErrorCode, ServiceAssertionError, ServiceError, logger as defaultLogger } from '@powersync/lib-services-framework';
|
|
3
3
|
import { JSONBig } from '@powersync/service-jsonbig';
|
|
4
4
|
import { getIntersection, hasIntersection } from './util.js';
|
|
5
5
|
/**
|
|
@@ -13,6 +13,8 @@ export class BucketChecksumState {
|
|
|
13
13
|
/**
|
|
14
14
|
* Bucket state of bucket id -> op_id.
|
|
15
15
|
* This starts with the state from the client. May contain buckets that the user do not have access to (anymore).
|
|
16
|
+
*
|
|
17
|
+
* This is always updated in-place.
|
|
16
18
|
*/
|
|
17
19
|
bucketDataPositions = new Map();
|
|
18
20
|
/**
|
|
@@ -26,34 +28,38 @@ export class BucketChecksumState {
|
|
|
26
28
|
* partial checkpoints are sent.
|
|
27
29
|
*/
|
|
28
30
|
pendingBucketDownloads = new Set();
|
|
31
|
+
logger;
|
|
29
32
|
constructor(options) {
|
|
30
33
|
this.context = options.syncContext;
|
|
31
34
|
this.bucketStorage = options.bucketStorage;
|
|
32
|
-
this.
|
|
35
|
+
this.logger = options.logger ?? defaultLogger;
|
|
36
|
+
this.parameterState = new BucketParameterState(options.syncContext, options.bucketStorage, options.syncRules, options.syncParams, this.logger);
|
|
33
37
|
this.bucketDataPositions = new Map();
|
|
34
38
|
for (let { name, after: start } of options.initialBucketPositions ?? []) {
|
|
35
39
|
this.bucketDataPositions.set(name, { start_op_id: start });
|
|
36
40
|
}
|
|
37
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Build a new checkpoint line for an underlying storage checkpoint update if any buckets have changed.
|
|
44
|
+
*
|
|
45
|
+
* This call is idempotent - no internal state is updated directly when this is called.
|
|
46
|
+
*
|
|
47
|
+
* When the checkpoint line is sent to the client, call `CheckpointLine.advance()` to update the internal state.
|
|
48
|
+
* A line may be safely discarded (not sent to the client) if `advance()` is not called.
|
|
49
|
+
*
|
|
50
|
+
* @param next The updated checkpoint
|
|
51
|
+
* @returns A {@link CheckpointLine} if any of the buckets watched by this connected was updated, or otherwise `null`.
|
|
52
|
+
*/
|
|
38
53
|
async buildNextCheckpointLine(next) {
|
|
39
54
|
const { writeCheckpoint, base } = next;
|
|
40
55
|
const user_id = this.parameterState.syncParams.user_id;
|
|
41
56
|
const storage = this.bucketStorage;
|
|
42
57
|
const update = await this.parameterState.getCheckpointUpdate(next);
|
|
43
|
-
if (update == null && this.lastWriteCheckpoint == writeCheckpoint) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
58
|
const { buckets: allBuckets, updatedBuckets } = update;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
start_op_id: this.bucketDataPositions.get(bucket.bucket)?.start_op_id ?? 0n
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
this.bucketDataPositions = dataBucketsNew;
|
|
55
|
-
if (dataBucketsNew.size > this.context.maxBuckets) {
|
|
56
|
-
throw new ServiceError(ErrorCode.PSYNC_S2305, `Too many buckets: ${dataBucketsNew.size} (limit of ${this.context.maxBuckets})`);
|
|
59
|
+
/** Set of all buckets in this checkpoint. */
|
|
60
|
+
const bucketDescriptionMap = new Map(allBuckets.map((b) => [b.bucket, b]));
|
|
61
|
+
if (bucketDescriptionMap.size > this.context.maxBuckets) {
|
|
62
|
+
throw new ServiceError(ErrorCode.PSYNC_S2305, `Too many buckets: ${bucketDescriptionMap.size} (limit of ${this.context.maxBuckets})`);
|
|
57
63
|
}
|
|
58
64
|
let checksumMap;
|
|
59
65
|
if (updatedBuckets != INVALIDATE_ALL_BUCKETS) {
|
|
@@ -63,7 +69,7 @@ export class BucketChecksumState {
|
|
|
63
69
|
// Re-check updated buckets only
|
|
64
70
|
let checksumLookups = [];
|
|
65
71
|
let newChecksums = new Map();
|
|
66
|
-
for (let bucket of
|
|
72
|
+
for (let bucket of bucketDescriptionMap.keys()) {
|
|
67
73
|
if (!updatedBuckets.has(bucket)) {
|
|
68
74
|
const existing = this.lastChecksums.get(bucket);
|
|
69
75
|
if (existing == null) {
|
|
@@ -87,12 +93,14 @@ export class BucketChecksumState {
|
|
|
87
93
|
}
|
|
88
94
|
else {
|
|
89
95
|
// Re-check all buckets
|
|
90
|
-
const bucketList = [...
|
|
96
|
+
const bucketList = [...bucketDescriptionMap.keys()];
|
|
91
97
|
checksumMap = await storage.getChecksums(base.checkpoint, bucketList);
|
|
92
98
|
}
|
|
93
99
|
// Subset of buckets for which there may be new data in this batch.
|
|
94
100
|
let bucketsToFetch;
|
|
95
101
|
let checkpointLine;
|
|
102
|
+
// Log function that is deferred until the checkpoint line is sent to the client.
|
|
103
|
+
let deferredLog;
|
|
96
104
|
if (this.lastChecksums) {
|
|
97
105
|
// TODO: If updatedBuckets is present, we can use that to more efficiently calculate a diff,
|
|
98
106
|
// and avoid any unnecessary loops through the entire list of buckets.
|
|
@@ -116,26 +124,28 @@ export class BucketChecksumState {
|
|
|
116
124
|
}
|
|
117
125
|
const updatedBucketDescriptions = diff.updatedBuckets.map((e) => ({
|
|
118
126
|
...e,
|
|
119
|
-
priority:
|
|
127
|
+
priority: bucketDescriptionMap.get(e.bucket).priority
|
|
120
128
|
}));
|
|
121
129
|
bucketsToFetch = [...generateBucketsToFetch].map((b) => {
|
|
122
130
|
return {
|
|
123
131
|
bucket: b,
|
|
124
|
-
priority:
|
|
132
|
+
priority: bucketDescriptionMap.get(b).priority
|
|
125
133
|
};
|
|
126
134
|
});
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
135
|
+
deferredLog = () => {
|
|
136
|
+
let message = `Updated checkpoint: ${base.checkpoint} | `;
|
|
137
|
+
message += `write: ${writeCheckpoint} | `;
|
|
138
|
+
message += `buckets: ${allBuckets.length} | `;
|
|
139
|
+
message += `updated: ${limitedBuckets(diff.updatedBuckets, 20)} | `;
|
|
140
|
+
message += `removed: ${limitedBuckets(diff.removedBuckets, 20)}`;
|
|
141
|
+
this.logger.info(message, {
|
|
142
|
+
checkpoint: base.checkpoint,
|
|
143
|
+
user_id: user_id,
|
|
144
|
+
buckets: allBuckets.length,
|
|
145
|
+
updated: diff.updatedBuckets.length,
|
|
146
|
+
removed: diff.removedBuckets.length
|
|
147
|
+
});
|
|
148
|
+
};
|
|
139
149
|
checkpointLine = {
|
|
140
150
|
checkpoint_diff: {
|
|
141
151
|
last_op_id: util.internalToExternalOpId(base.checkpoint),
|
|
@@ -146,9 +156,11 @@ export class BucketChecksumState {
|
|
|
146
156
|
};
|
|
147
157
|
}
|
|
148
158
|
else {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
159
|
+
deferredLog = () => {
|
|
160
|
+
let message = `New checkpoint: ${base.checkpoint} | write: ${writeCheckpoint} | `;
|
|
161
|
+
message += `buckets: ${allBuckets.length} ${limitedBuckets(allBuckets, 20)}`;
|
|
162
|
+
this.logger.info(message, { checkpoint: base.checkpoint, user_id: user_id, buckets: allBuckets.length });
|
|
163
|
+
};
|
|
152
164
|
bucketsToFetch = allBuckets;
|
|
153
165
|
checkpointLine = {
|
|
154
166
|
checkpoint: {
|
|
@@ -156,49 +168,83 @@ export class BucketChecksumState {
|
|
|
156
168
|
write_checkpoint: writeCheckpoint ? String(writeCheckpoint) : undefined,
|
|
157
169
|
buckets: [...checksumMap.values()].map((e) => ({
|
|
158
170
|
...e,
|
|
159
|
-
priority:
|
|
171
|
+
priority: bucketDescriptionMap.get(e.bucket).priority
|
|
160
172
|
}))
|
|
161
173
|
}
|
|
162
174
|
};
|
|
163
175
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
this.pendingBucketDownloads = new Set(bucketsToFetch.map((b) => b.bucket));
|
|
176
|
+
const pendingBucketDownloads = new Set(bucketsToFetch.map((b) => b.bucket));
|
|
177
|
+
let hasAdvanced = false;
|
|
167
178
|
return {
|
|
168
179
|
checkpointLine,
|
|
169
|
-
bucketsToFetch
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
bucketsToFetch,
|
|
181
|
+
advance: () => {
|
|
182
|
+
hasAdvanced = true;
|
|
183
|
+
// bucketDataPositions must be updated in-place - it represents the current state of
|
|
184
|
+
// the connection, not of the checkpoint line.
|
|
185
|
+
// The following could happen:
|
|
186
|
+
// 1. A = buildCheckpointLine()
|
|
187
|
+
// 2. A.advance()
|
|
188
|
+
// 3. B = buildCheckpointLine()
|
|
189
|
+
// 4. A.updateBucketPosition()
|
|
190
|
+
// 5. B.advance()
|
|
191
|
+
// In that case, it is important that the updated bucket position for A takes effect
|
|
192
|
+
// for checkpoint B.
|
|
193
|
+
let bucketsToRemove = [];
|
|
194
|
+
for (let bucket of this.bucketDataPositions.keys()) {
|
|
195
|
+
if (!bucketDescriptionMap.has(bucket)) {
|
|
196
|
+
bucketsToRemove.push(bucket);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (let bucket of bucketsToRemove) {
|
|
200
|
+
this.bucketDataPositions.delete(bucket);
|
|
201
|
+
}
|
|
202
|
+
for (let bucket of allBuckets) {
|
|
203
|
+
if (!this.bucketDataPositions.has(bucket.bucket)) {
|
|
204
|
+
// Bucket the client hasn't seen before - initialize with 0.
|
|
205
|
+
this.bucketDataPositions.set(bucket.bucket, { start_op_id: 0n });
|
|
206
|
+
}
|
|
207
|
+
// If the bucket position is already present, we keep the current position.
|
|
208
|
+
}
|
|
209
|
+
this.lastChecksums = checksumMap;
|
|
210
|
+
this.lastWriteCheckpoint = writeCheckpoint;
|
|
211
|
+
this.pendingBucketDownloads = pendingBucketDownloads;
|
|
212
|
+
deferredLog();
|
|
213
|
+
},
|
|
214
|
+
getFilteredBucketPositions: (buckets) => {
|
|
215
|
+
if (!hasAdvanced) {
|
|
216
|
+
throw new ServiceAssertionError('Call line.advance() before getFilteredBucketPositions()');
|
|
217
|
+
}
|
|
218
|
+
buckets ??= bucketsToFetch;
|
|
219
|
+
const filtered = new Map();
|
|
220
|
+
for (let bucket of buckets) {
|
|
221
|
+
const state = this.bucketDataPositions.get(bucket.bucket);
|
|
222
|
+
if (state) {
|
|
223
|
+
filtered.set(bucket.bucket, state.start_op_id);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return filtered;
|
|
227
|
+
},
|
|
228
|
+
updateBucketPosition: (options) => {
|
|
229
|
+
if (!hasAdvanced) {
|
|
230
|
+
throw new ServiceAssertionError('Call line.advance() before updateBucketPosition()');
|
|
231
|
+
}
|
|
232
|
+
const state = this.bucketDataPositions.get(options.bucket);
|
|
233
|
+
if (state) {
|
|
234
|
+
state.start_op_id = options.nextAfter;
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// If we hit this, another checkpoint has removed the bucket in the meantime, meaning
|
|
238
|
+
// line.advance() has been called on it. In that case we don't need the bucket state anymore.
|
|
239
|
+
// It is generally not expected to happen, but we still cover the case.
|
|
240
|
+
}
|
|
241
|
+
if (!options.hasMore) {
|
|
242
|
+
// This specifically updates the per-checkpoint line. Completing a download for one line,
|
|
243
|
+
// does not remove it from the next line, since it could have new updates there.
|
|
244
|
+
pendingBucketDownloads.delete(options.bucket);
|
|
245
|
+
}
|
|
184
246
|
}
|
|
185
|
-
}
|
|
186
|
-
return filtered;
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Update the position of bucket data the client has.
|
|
190
|
-
*
|
|
191
|
-
* @param bucket the bucket name
|
|
192
|
-
* @param nextAfter sync operations >= this value in the next batch
|
|
193
|
-
*/
|
|
194
|
-
updateBucketPosition(options) {
|
|
195
|
-
const state = this.bucketDataPositions.get(options.bucket);
|
|
196
|
-
if (state) {
|
|
197
|
-
state.start_op_id = options.nextAfter;
|
|
198
|
-
}
|
|
199
|
-
if (!options.hasMore) {
|
|
200
|
-
this.pendingBucketDownloads.delete(options.bucket);
|
|
201
|
-
}
|
|
247
|
+
};
|
|
202
248
|
}
|
|
203
249
|
}
|
|
204
250
|
const INVALIDATE_ALL_BUCKETS = Symbol('INVALIDATE_ALL_BUCKETS');
|
|
@@ -209,14 +255,16 @@ export class BucketParameterState {
|
|
|
209
255
|
syncParams;
|
|
210
256
|
querier;
|
|
211
257
|
staticBuckets;
|
|
258
|
+
logger;
|
|
212
259
|
cachedDynamicBuckets = null;
|
|
213
260
|
cachedDynamicBucketSet = null;
|
|
214
261
|
lookups;
|
|
215
|
-
constructor(context, bucketStorage, syncRules, syncParams) {
|
|
262
|
+
constructor(context, bucketStorage, syncRules, syncParams, logger) {
|
|
216
263
|
this.context = context;
|
|
217
264
|
this.bucketStorage = bucketStorage;
|
|
218
265
|
this.syncRules = syncRules;
|
|
219
266
|
this.syncParams = syncParams;
|
|
267
|
+
this.logger = logger;
|
|
220
268
|
this.querier = syncRules.getBucketParameterQuerier(this.syncParams);
|
|
221
269
|
this.staticBuckets = new Map(this.querier.staticBuckets.map((b) => [b.bucket, b]));
|
|
222
270
|
this.lookups = new Set(this.querier.parameterQueryLookups.map((l) => JSONBig.stringify(l.values)));
|
|
@@ -234,7 +282,7 @@ export class BucketParameterState {
|
|
|
234
282
|
// TODO: Limit number of results even before we get to this point
|
|
235
283
|
// This limit applies _before_ we get the unique set
|
|
236
284
|
const error = new ServiceError(ErrorCode.PSYNC_S2305, `Too many parameter query results: ${update.buckets.length} (limit of ${this.context.maxParameterQueryResults})`);
|
|
237
|
-
logger.error(error.message, {
|
|
285
|
+
this.logger.error(error.message, {
|
|
238
286
|
checkpoint: checkpoint,
|
|
239
287
|
user_id: this.syncParams.user_id,
|
|
240
288
|
buckets: update.buckets.length
|