@powersync/service-core 0.0.0-dev-20241007145127 → 0.0.0-dev-20241015210820
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 +9 -5
- package/dist/api/RouteAPI.d.ts +6 -4
- package/dist/api/diagnostics.js +169 -105
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/api/schema.js +2 -2
- package/dist/api/schema.js.map +1 -1
- package/dist/entry/commands/compact-action.js +73 -9
- package/dist/entry/commands/compact-action.js.map +1 -1
- package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.d.ts +3 -0
- package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js +31 -0
- package/dist/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.js.map +1 -0
- package/dist/replication/AbstractReplicationJob.d.ts +1 -1
- package/dist/replication/AbstractReplicationJob.js +2 -2
- package/dist/replication/AbstractReplicationJob.js.map +1 -1
- package/dist/replication/AbstractReplicator.d.ts +2 -2
- package/dist/replication/AbstractReplicator.js +66 -3
- package/dist/replication/AbstractReplicator.js.map +1 -1
- package/dist/replication/ReplicationEngine.js.map +1 -1
- package/dist/replication/ReplicationModule.js +3 -0
- package/dist/replication/ReplicationModule.js.map +1 -1
- package/dist/replication/replication-index.d.ts +1 -1
- package/dist/replication/replication-index.js +1 -1
- package/dist/replication/replication-index.js.map +1 -1
- package/dist/routes/configure-fastify.js +12 -12
- package/dist/routes/configure-fastify.js.map +1 -1
- package/dist/routes/configure-rsocket.js +4 -1
- package/dist/routes/configure-rsocket.js.map +1 -1
- package/dist/routes/endpoints/admin.js.map +1 -1
- package/dist/routes/endpoints/checkpointing.js +5 -2
- package/dist/routes/endpoints/checkpointing.js.map +1 -1
- package/dist/routes/endpoints/sync-rules.js.map +1 -1
- package/dist/routes/router.d.ts +8 -1
- package/dist/routes/router.js.map +1 -1
- package/dist/runner/teardown.js +66 -4
- package/dist/runner/teardown.js.map +1 -1
- package/dist/storage/BucketStorage.d.ts +41 -18
- package/dist/storage/BucketStorage.js +6 -0
- package/dist/storage/BucketStorage.js.map +1 -1
- package/dist/storage/MongoBucketStorage.d.ts +12 -5
- package/dist/storage/MongoBucketStorage.js +44 -23
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/ReplicationEventPayload.d.ts +14 -0
- package/dist/storage/ReplicationEventPayload.js +2 -0
- package/dist/storage/ReplicationEventPayload.js.map +1 -0
- package/dist/storage/SourceTable.d.ts +8 -0
- package/dist/storage/SourceTable.js +9 -1
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/StorageEngine.d.ts +10 -2
- package/dist/storage/StorageEngine.js +23 -3
- package/dist/storage/StorageEngine.js.map +1 -1
- package/dist/storage/StorageProvider.d.ts +9 -2
- package/dist/storage/mongo/MongoBucketBatch.d.ts +12 -4
- package/dist/storage/mongo/MongoBucketBatch.js +60 -21
- package/dist/storage/mongo/MongoBucketBatch.js.map +1 -1
- package/dist/storage/mongo/MongoStorageProvider.d.ts +1 -1
- package/dist/storage/mongo/MongoStorageProvider.js +3 -2
- package/dist/storage/mongo/MongoStorageProvider.js.map +1 -1
- package/dist/storage/mongo/MongoSyncBucketStorage.d.ts +4 -5
- package/dist/storage/mongo/MongoSyncBucketStorage.js +74 -12
- package/dist/storage/mongo/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/mongo/MongoWriteCheckpointAPI.d.ts +18 -0
- package/dist/storage/mongo/MongoWriteCheckpointAPI.js +90 -0
- package/dist/storage/mongo/MongoWriteCheckpointAPI.js.map +1 -0
- package/dist/storage/mongo/db.d.ts +3 -2
- package/dist/storage/mongo/db.js +1 -0
- package/dist/storage/mongo/db.js.map +1 -1
- package/dist/storage/mongo/models.d.ts +7 -1
- package/dist/storage/storage-index.d.ts +2 -0
- package/dist/storage/storage-index.js +2 -0
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/storage/write-checkpoint.d.ts +55 -0
- package/dist/storage/write-checkpoint.js +16 -0
- package/dist/storage/write-checkpoint.js.map +1 -0
- package/dist/util/protocol-types.d.ts +2 -1
- package/package.json +5 -5
- package/src/api/RouteAPI.ts +7 -4
- package/src/api/diagnostics.ts +4 -2
- package/src/api/schema.ts +3 -3
- package/src/entry/commands/compact-action.ts +4 -2
- package/src/migrations/db/migrations/1727099539247-custom-write-checkpoint-index.ts +37 -0
- package/src/replication/AbstractReplicationJob.ts +4 -4
- package/src/replication/AbstractReplicator.ts +5 -4
- package/src/replication/ReplicationEngine.ts +1 -1
- package/src/replication/ReplicationModule.ts +4 -0
- package/src/replication/replication-index.ts +1 -1
- package/src/routes/configure-fastify.ts +16 -17
- package/src/routes/configure-rsocket.ts +7 -2
- package/src/routes/endpoints/admin.ts +2 -2
- package/src/routes/endpoints/checkpointing.ts +5 -2
- package/src/routes/endpoints/sync-rules.ts +1 -0
- package/src/routes/router.ts +7 -1
- package/src/runner/teardown.ts +3 -3
- package/src/storage/BucketStorage.ts +50 -19
- package/src/storage/MongoBucketStorage.ts +70 -29
- package/src/storage/ReplicationEventPayload.ts +16 -0
- package/src/storage/SourceTable.ts +10 -1
- package/src/storage/StorageEngine.ts +34 -5
- package/src/storage/StorageProvider.ts +10 -2
- package/src/storage/mongo/MongoBucketBatch.ts +83 -27
- package/src/storage/mongo/MongoStorageProvider.ts +4 -3
- package/src/storage/mongo/MongoSyncBucketStorage.ts +22 -18
- package/src/storage/mongo/MongoWriteCheckpointAPI.ts +136 -0
- package/src/storage/mongo/db.ts +4 -1
- package/src/storage/mongo/models.ts +8 -1
- package/src/storage/storage-index.ts +2 -0
- package/src/storage/write-checkpoint.ts +67 -0
- package/src/util/protocol-types.ts +1 -1
- package/test/src/compacting.test.ts +13 -15
- package/test/src/data_storage.test.ts +95 -63
- package/test/src/sync.test.ts +10 -9
- package/test/src/util.ts +1 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-checkpoint.js","sourceRoot":"","sources":["../../src/storage/write-checkpoint.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,mBAYX;AAZD,WAAY,mBAAmB;IAC7B;;;OAGG;IACH,wCAAiB,CAAA;IACjB;;;;OAIG;IACH,0CAAmB,CAAA;AACrB,CAAC,EAZW,mBAAmB,KAAnB,mBAAmB,QAY9B;AAsDD,MAAM,CAAC,MAAM,6BAA6B,GAAG,mBAAmB,CAAC,OAAO,CAAC"}
|
|
@@ -71,7 +71,7 @@ export type StreamingSyncLine = StreamingSyncData | StreamingSyncCheckpoint | St
|
|
|
71
71
|
* 64-bit unsigned number, as a base-10 string.
|
|
72
72
|
*/
|
|
73
73
|
export type OpId = string;
|
|
74
|
-
|
|
74
|
+
interface Checkpoint {
|
|
75
75
|
last_op_id: OpId;
|
|
76
76
|
write_checkpoint?: OpId;
|
|
77
77
|
buckets: BucketChecksum[];
|
|
@@ -119,3 +119,4 @@ export interface BucketChecksum {
|
|
|
119
119
|
*/
|
|
120
120
|
count: number;
|
|
121
121
|
}
|
|
122
|
+
export {};
|
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-20241015210820",
|
|
9
9
|
"main": "dist/index.js",
|
|
10
10
|
"license": "FSL-1.1-Apache-2.0",
|
|
11
11
|
"type": "module",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"uuid": "^9.0.1",
|
|
34
34
|
"winston": "^3.13.0",
|
|
35
35
|
"yaml": "^2.3.2",
|
|
36
|
-
"@powersync/lib-services-framework": "0.0.0-dev-
|
|
36
|
+
"@powersync/lib-services-framework": "0.0.0-dev-20241015210820",
|
|
37
37
|
"@powersync/service-jsonbig": "0.17.10",
|
|
38
|
-
"@powersync/service-rsocket-router": "0.0.0-dev-
|
|
39
|
-
"@powersync/service-sync-rules": "0.0.0-dev-
|
|
40
|
-
"@powersync/service-types": "0.0.0-dev-
|
|
38
|
+
"@powersync/service-rsocket-router": "0.0.0-dev-20241015210820",
|
|
39
|
+
"@powersync/service-sync-rules": "0.0.0-dev-20241015210820",
|
|
40
|
+
"@powersync/service-types": "0.0.0-dev-20241015210820"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/async": "^3.2.24",
|
package/src/api/RouteAPI.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SqlSyncRules, TablePattern } from '@powersync/service-sync-rules';
|
|
2
2
|
import * as types from '@powersync/service-types';
|
|
3
|
-
import { ParseSyncRulesOptions } from '../storage/BucketStorage.js';
|
|
3
|
+
import { ParseSyncRulesOptions, SyncRulesBucketStorage } from '../storage/BucketStorage.js';
|
|
4
4
|
|
|
5
5
|
export interface PatternResult {
|
|
6
6
|
schema: string;
|
|
@@ -10,6 +10,10 @@ export interface PatternResult {
|
|
|
10
10
|
table?: types.TableInfo;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface ReplicationLagOptions {
|
|
14
|
+
bucketStorage: SyncRulesBucketStorage;
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
/**
|
|
14
18
|
* Describes all the methods currently required to service the sync API endpoints.
|
|
15
19
|
*/
|
|
@@ -17,7 +21,7 @@ export interface RouteAPI {
|
|
|
17
21
|
/**
|
|
18
22
|
* @returns basic identification of the connection
|
|
19
23
|
*/
|
|
20
|
-
getSourceConfig(): Promise<types.configFile.
|
|
24
|
+
getSourceConfig(): Promise<types.configFile.ResolvedDataSourceConfig>;
|
|
21
25
|
|
|
22
26
|
/**
|
|
23
27
|
* Checks the current connection status of the data source.
|
|
@@ -42,9 +46,8 @@ export interface RouteAPI {
|
|
|
42
46
|
/**
|
|
43
47
|
* @returns The replication lag: that is the amount of data which has not been
|
|
44
48
|
* replicated yet, in bytes.
|
|
45
|
-
* @param {string} syncRulesId An identifier representing which set of sync rules the lag is required for.
|
|
46
49
|
*/
|
|
47
|
-
getReplicationLag(
|
|
50
|
+
getReplicationLag(options: ReplicationLagOptions): Promise<number | undefined>;
|
|
48
51
|
|
|
49
52
|
/**
|
|
50
53
|
* Get the current LSN or equivalent replication HEAD position identifier
|
package/src/api/diagnostics.ts
CHANGED
|
@@ -57,7 +57,7 @@ export async function getSyncRulesStatus(
|
|
|
57
57
|
// This method can run under some situations if no connection is configured yet.
|
|
58
58
|
// It will return a default tag in such a case. This default tag is not module specific.
|
|
59
59
|
const tag = sourceConfig.tag ?? DEFAULT_TAG;
|
|
60
|
-
|
|
60
|
+
using systemStorage = live_status ? bucketStorage.getInstance(sync_rules) : undefined;
|
|
61
61
|
const status = await systemStorage?.getStatus();
|
|
62
62
|
let replication_lag_bytes: number | undefined = undefined;
|
|
63
63
|
|
|
@@ -78,7 +78,9 @@ export async function getSyncRulesStatus(
|
|
|
78
78
|
|
|
79
79
|
if (systemStorage) {
|
|
80
80
|
try {
|
|
81
|
-
replication_lag_bytes = await apiHandler.getReplicationLag(
|
|
81
|
+
replication_lag_bytes = await apiHandler.getReplicationLag({
|
|
82
|
+
bucketStorage: systemStorage
|
|
83
|
+
});
|
|
82
84
|
} catch (e) {
|
|
83
85
|
// Ignore
|
|
84
86
|
logger.warn(`Unable to get replication lag`, e);
|
package/src/api/schema.ts
CHANGED
|
@@ -16,9 +16,9 @@ export async function getConnectionsSchema(api: api.RouteAPI): Promise<internal_
|
|
|
16
16
|
return {
|
|
17
17
|
connections: [
|
|
18
18
|
{
|
|
19
|
-
|
|
20
|
-
tag: baseConfig.tag
|
|
21
|
-
|
|
19
|
+
id: baseConfig.id,
|
|
20
|
+
tag: baseConfig.tag,
|
|
21
|
+
schemas: await api.getConnectionSchema()
|
|
22
22
|
}
|
|
23
23
|
],
|
|
24
24
|
defaultConnectionTag: baseConfig.tag!,
|
|
@@ -36,13 +36,15 @@ export function registerCompactAction(program: Command) {
|
|
|
36
36
|
const client = psdb.client;
|
|
37
37
|
await client.connect();
|
|
38
38
|
try {
|
|
39
|
-
const bucketStorage = new storage.MongoBucketStorage(psdb, {
|
|
39
|
+
const bucketStorage = new storage.MongoBucketStorage(psdb, {
|
|
40
|
+
slot_name_prefix: configuration.slot_name_prefix
|
|
41
|
+
});
|
|
40
42
|
const active = await bucketStorage.getActiveSyncRulesContent();
|
|
41
43
|
if (active == null) {
|
|
42
44
|
logger.info('No active instance to compact');
|
|
43
45
|
return;
|
|
44
46
|
}
|
|
45
|
-
|
|
47
|
+
using p = bucketStorage.getInstance(active);
|
|
46
48
|
logger.info('Performing compaction...');
|
|
47
49
|
await p.compact({ memoryLimitMB: COMPACT_MEMORY_LIMIT_MB });
|
|
48
50
|
logger.info('Successfully compacted storage.');
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as storage from '../../../storage/storage-index.js';
|
|
2
|
+
import * as utils from '../../../util/util-index.js';
|
|
3
|
+
|
|
4
|
+
const INDEX_NAME = 'user_sync_rule_unique';
|
|
5
|
+
|
|
6
|
+
export const up = async (context: utils.MigrationContext) => {
|
|
7
|
+
const { runner_config } = context;
|
|
8
|
+
const config = await utils.loadConfig(runner_config);
|
|
9
|
+
const db = storage.createPowerSyncMongo(config.storage);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
await db.custom_write_checkpoints.createIndex(
|
|
13
|
+
{
|
|
14
|
+
user_id: 1,
|
|
15
|
+
sync_rules_id: 1
|
|
16
|
+
},
|
|
17
|
+
{ name: INDEX_NAME, unique: true }
|
|
18
|
+
);
|
|
19
|
+
} finally {
|
|
20
|
+
await db.client.close();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const down = async (context: utils.MigrationContext) => {
|
|
25
|
+
const { runner_config } = context;
|
|
26
|
+
const config = await utils.loadConfig(runner_config);
|
|
27
|
+
|
|
28
|
+
const db = storage.createPowerSyncMongo(config.storage);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
if (await db.custom_write_checkpoints.indexExists(INDEX_NAME)) {
|
|
32
|
+
await db.custom_write_checkpoints.dropIndex(INDEX_NAME);
|
|
33
|
+
}
|
|
34
|
+
} finally {
|
|
35
|
+
await db.client.close();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as storage from '../storage/storage-index.js';
|
|
2
|
-
import { ErrorRateLimiter } from './ErrorRateLimiter.js';
|
|
3
1
|
import { container, logger } from '@powersync/lib-services-framework';
|
|
4
2
|
import winston from 'winston';
|
|
3
|
+
import * as storage from '../storage/storage-index.js';
|
|
4
|
+
import { ErrorRateLimiter } from './ErrorRateLimiter.js';
|
|
5
5
|
|
|
6
6
|
export interface AbstractReplicationJobOptions {
|
|
7
7
|
id: string;
|
|
@@ -16,7 +16,7 @@ export abstract class AbstractReplicationJob {
|
|
|
16
16
|
protected isReplicatingPromise: Promise<void> | null = null;
|
|
17
17
|
|
|
18
18
|
protected constructor(protected options: AbstractReplicationJobOptions) {
|
|
19
|
-
this.logger = logger.child({ name: `ReplicationJob: ${
|
|
19
|
+
this.logger = logger.child({ name: `ReplicationJob: ${this.id}` });
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -52,7 +52,7 @@ export abstract class AbstractReplicationJob {
|
|
|
52
52
|
* Safely stop the replication process
|
|
53
53
|
*/
|
|
54
54
|
public async stop(): Promise<void> {
|
|
55
|
-
this.logger.info(`Stopping
|
|
55
|
+
this.logger.info(`Stopping replication job for sync rule iteration: ${this.storage.group_id}`);
|
|
56
56
|
this.abortController.abort();
|
|
57
57
|
await this.isReplicatingPromise;
|
|
58
58
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { container, logger } from '@powersync/lib-services-framework';
|
|
1
2
|
import { hrtime } from 'node:process';
|
|
3
|
+
import winston from 'winston';
|
|
2
4
|
import * as storage from '../storage/storage-index.js';
|
|
3
|
-
import {
|
|
5
|
+
import { StorageEngine } from '../storage/storage-index.js';
|
|
4
6
|
import { SyncRulesProvider } from '../util/config/sync-rules/sync-rules-provider.js';
|
|
5
|
-
import winston from 'winston';
|
|
6
7
|
import { AbstractReplicationJob } from './AbstractReplicationJob.js';
|
|
7
|
-
import { StorageEngine } from '../storage/storage-index.js';
|
|
8
8
|
import { ErrorRateLimiter } from './ErrorRateLimiter.js';
|
|
9
9
|
|
|
10
10
|
// 5 minutes
|
|
@@ -192,6 +192,7 @@ export abstract class AbstractReplicator<T extends AbstractReplicationJob = Abst
|
|
|
192
192
|
try {
|
|
193
193
|
await job.stop();
|
|
194
194
|
await this.terminateSyncRules(job.storage);
|
|
195
|
+
job.storage[Symbol.dispose]();
|
|
195
196
|
} catch (e) {
|
|
196
197
|
// This will be retried
|
|
197
198
|
this.logger.warn('Failed to terminate old replication job}', e);
|
|
@@ -202,7 +203,7 @@ export abstract class AbstractReplicator<T extends AbstractReplicationJob = Abst
|
|
|
202
203
|
const stopped = await this.storage.getStoppedSyncRules();
|
|
203
204
|
for (let syncRules of stopped) {
|
|
204
205
|
try {
|
|
205
|
-
|
|
206
|
+
using syncRuleStorage = this.storage.getInstance(syncRules);
|
|
206
207
|
await this.terminateSyncRules(syncRuleStorage);
|
|
207
208
|
} catch (e) {
|
|
208
209
|
this.logger.warn(`Failed clean up replication config for sync rule: ${syncRules.id}`, e);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AbstractReplicator } from './AbstractReplicator.js';
|
|
2
1
|
import { logger } from '@powersync/lib-services-framework';
|
|
2
|
+
import { AbstractReplicator } from './AbstractReplicator.js';
|
|
3
3
|
|
|
4
4
|
export class ReplicationEngine {
|
|
5
5
|
private readonly replicators: Map<string, AbstractReplicator> = new Map();
|
|
@@ -58,6 +58,10 @@ export abstract class ReplicationModule<TConfig extends DataSourceConfig> extend
|
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
if (!matchingConfig.length) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
61
65
|
if (matchingConfig.length > 1) {
|
|
62
66
|
this.logger.warning(
|
|
63
67
|
`Multiple data sources of type ${this.type} found in the configuration. Only the first will be used.`
|
|
@@ -8,7 +8,7 @@ import { CHECKPOINT_ROUTES } from './endpoints/checkpointing.js';
|
|
|
8
8
|
import { SYNC_RULES_ROUTES } from './endpoints/sync-rules.js';
|
|
9
9
|
import { SYNC_STREAM_ROUTES } from './endpoints/sync-stream.js';
|
|
10
10
|
import { createRequestQueueHook, CreateRequestQueueParams } from './hooks.js';
|
|
11
|
-
import { RouteDefinition } from './router.js';
|
|
11
|
+
import { RouteDefinition, RouterServiceContext } from './router.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* A list of route definitions to be registered as endpoints.
|
|
@@ -56,6 +56,19 @@ export const DEFAULT_ROUTE_OPTIONS = {
|
|
|
56
56
|
*/
|
|
57
57
|
export function configureFastifyServer(server: fastify.FastifyInstance, options: FastifyServerConfig) {
|
|
58
58
|
const { service_context, routes = DEFAULT_ROUTE_OPTIONS } = options;
|
|
59
|
+
|
|
60
|
+
const generateContext = async () => {
|
|
61
|
+
const { routerEngine } = service_context;
|
|
62
|
+
if (!routerEngine) {
|
|
63
|
+
throw new Error(`RouterEngine has not been registered`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
user_id: undefined,
|
|
68
|
+
service_context: service_context as RouterServiceContext
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
59
72
|
/**
|
|
60
73
|
* Fastify creates an encapsulated context for each `.register` call.
|
|
61
74
|
* Creating a separate context here to separate the concurrency limits for Admin APIs
|
|
@@ -63,16 +76,7 @@ export function configureFastifyServer(server: fastify.FastifyInstance, options:
|
|
|
63
76
|
* https://github.com/fastify/fastify/blob/main/docs/Reference/Encapsulation.md
|
|
64
77
|
*/
|
|
65
78
|
server.register(async function (childContext) {
|
|
66
|
-
registerFastifyRoutes(
|
|
67
|
-
childContext,
|
|
68
|
-
async () => {
|
|
69
|
-
return {
|
|
70
|
-
user_id: undefined,
|
|
71
|
-
service_context
|
|
72
|
-
};
|
|
73
|
-
},
|
|
74
|
-
routes.api?.routes ?? DEFAULT_ROUTE_OPTIONS.api.routes
|
|
75
|
-
);
|
|
79
|
+
registerFastifyRoutes(childContext, generateContext, routes.api?.routes ?? DEFAULT_ROUTE_OPTIONS.api.routes);
|
|
76
80
|
// Limit the active concurrent requests
|
|
77
81
|
childContext.addHook(
|
|
78
82
|
'onRequest',
|
|
@@ -84,12 +88,7 @@ export function configureFastifyServer(server: fastify.FastifyInstance, options:
|
|
|
84
88
|
server.register(async function (childContext) {
|
|
85
89
|
registerFastifyRoutes(
|
|
86
90
|
childContext,
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
user_id: undefined,
|
|
90
|
-
service_context
|
|
91
|
-
};
|
|
92
|
-
},
|
|
91
|
+
generateContext,
|
|
93
92
|
routes.sync_stream?.routes ?? DEFAULT_ROUTE_OPTIONS.sync_stream.routes
|
|
94
93
|
);
|
|
95
94
|
// Limit the active concurrent requests
|
|
@@ -8,7 +8,7 @@ import { ServiceContext } from '../system/ServiceContext.js';
|
|
|
8
8
|
import { generateContext, getTokenFromHeader } from './auth.js';
|
|
9
9
|
import { syncStreamReactive } from './endpoints/socket-route.js';
|
|
10
10
|
import { RSocketContextMeta, SocketRouteGenerator } from './router-socket.js';
|
|
11
|
-
import { Context } from './router.js';
|
|
11
|
+
import { Context, RouterServiceContext } from './router.js';
|
|
12
12
|
|
|
13
13
|
export type RSockerRouterConfig = {
|
|
14
14
|
service_context: ServiceContext;
|
|
@@ -36,12 +36,17 @@ export function configureRSocket(router: ReactiveSocketRouter<Context>, options:
|
|
|
36
36
|
if (context?.token_payload == null) {
|
|
37
37
|
throw new errors.AuthorizationError(token_errors ?? 'Authentication required');
|
|
38
38
|
}
|
|
39
|
+
|
|
40
|
+
if (!service_context.routerEngine) {
|
|
41
|
+
throw new Error(`RouterEngine has not been registered`);
|
|
42
|
+
}
|
|
43
|
+
|
|
39
44
|
return {
|
|
40
45
|
token,
|
|
41
46
|
user_agent,
|
|
42
47
|
...context,
|
|
43
48
|
token_errors: token_errors,
|
|
44
|
-
service_context
|
|
49
|
+
service_context: service_context as RouterServiceContext
|
|
45
50
|
};
|
|
46
51
|
} else {
|
|
47
52
|
throw new errors.AuthorizationError('No token provided');
|
|
@@ -137,8 +137,8 @@ export const reprocess = routeDefinition({
|
|
|
137
137
|
connections: [
|
|
138
138
|
{
|
|
139
139
|
// Previously the connection was asserted with `!`
|
|
140
|
-
tag: baseConfig
|
|
141
|
-
id: baseConfig
|
|
140
|
+
tag: baseConfig.tag,
|
|
141
|
+
id: baseConfig.id,
|
|
142
142
|
slot_name: new_rules.slot_name
|
|
143
143
|
}
|
|
144
144
|
]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import * as t from 'ts-codec';
|
|
2
1
|
import { logger, router, schema } from '@powersync/lib-services-framework';
|
|
2
|
+
import * as t from 'ts-codec';
|
|
3
3
|
|
|
4
4
|
import * as util from '../../util/util-index.js';
|
|
5
5
|
import { authUser } from '../auth.js';
|
|
@@ -63,7 +63,10 @@ export const writeCheckpoint2 = routeDefinition({
|
|
|
63
63
|
storageEngine: { activeBucketStorage }
|
|
64
64
|
} = service_context;
|
|
65
65
|
|
|
66
|
-
const writeCheckpoint = await activeBucketStorage.
|
|
66
|
+
const writeCheckpoint = await activeBucketStorage.createManagedWriteCheckpoint({
|
|
67
|
+
user_id: full_user_id,
|
|
68
|
+
heads: { '1': currentCheckpoint }
|
|
69
|
+
});
|
|
67
70
|
logger.info(`Write checkpoint 2: ${JSON.stringify({ currentCheckpoint, id: String(full_user_id) })}`);
|
|
68
71
|
|
|
69
72
|
return {
|
|
@@ -3,6 +3,7 @@ import { SqlSyncRules, SyncRulesErrors } from '@powersync/service-sync-rules';
|
|
|
3
3
|
import type { FastifyPluginAsync } from 'fastify';
|
|
4
4
|
import * as t from 'ts-codec';
|
|
5
5
|
|
|
6
|
+
import * as system from '../../system/system-index.js';
|
|
6
7
|
import { authApi } from '../auth.js';
|
|
7
8
|
import { routeDefinition } from '../router.js';
|
|
8
9
|
import { RouteAPI } from '../../api/RouteAPI.js';
|
package/src/routes/router.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { router } from '@powersync/lib-services-framework';
|
|
2
2
|
import * as auth from '../auth/auth-index.js';
|
|
3
3
|
import { ServiceContext } from '../system/ServiceContext.js';
|
|
4
|
+
import { RouterEngine } from './RouterEngine.js';
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The {@link RouterEngine} must be provided for these routes
|
|
8
|
+
*/
|
|
9
|
+
export type RouterServiceContext = ServiceContext & { routerEngine: RouterEngine };
|
|
5
10
|
/**
|
|
6
11
|
* Common context for routes
|
|
7
12
|
*/
|
|
8
13
|
export type Context = {
|
|
9
14
|
user_id?: string;
|
|
10
|
-
|
|
15
|
+
|
|
16
|
+
service_context: RouterServiceContext;
|
|
11
17
|
|
|
12
18
|
token_payload?: auth.JwtPayload;
|
|
13
19
|
token_errors?: string[];
|
package/src/runner/teardown.ts
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
// 2. Delete the storage
|
|
5
5
|
|
|
6
6
|
import { container, logger } from '@powersync/lib-services-framework';
|
|
7
|
+
import timers from 'timers/promises';
|
|
7
8
|
import * as modules from '../modules/modules-index.js';
|
|
8
|
-
import * as system from '../system/system-index.js';
|
|
9
9
|
import * as storage from '../storage/storage-index.js';
|
|
10
|
+
import * as system from '../system/system-index.js';
|
|
10
11
|
import * as utils from '../util/util-index.js';
|
|
11
|
-
import timers from 'timers/promises';
|
|
12
12
|
|
|
13
13
|
export async function teardown(runnerConfig: utils.RunnerConfig) {
|
|
14
14
|
try {
|
|
@@ -51,7 +51,7 @@ async function terminateSyncRules(storageFactory: storage.BucketStorageFactory,
|
|
|
51
51
|
|
|
52
52
|
// Mark the sync rules as terminated
|
|
53
53
|
for (let syncRules of combinedSyncRules) {
|
|
54
|
-
|
|
54
|
+
using syncRulesStorage = storageFactory.getInstance(syncRules);
|
|
55
55
|
// The storage will be dropped at the end of the teardown, so we don't need to clear it here
|
|
56
56
|
await syncRulesStorage.terminate({ clearStorage: false });
|
|
57
57
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DisposableListener, DisposableObserverClient } from '@powersync/lib-services-framework';
|
|
1
2
|
import {
|
|
2
3
|
EvaluatedParameters,
|
|
3
4
|
EvaluatedRow,
|
|
@@ -8,11 +9,19 @@ import {
|
|
|
8
9
|
ToastableSqliteRow
|
|
9
10
|
} from '@powersync/service-sync-rules';
|
|
10
11
|
import * as util from '../util/util-index.js';
|
|
11
|
-
import {
|
|
12
|
+
import { ReplicationEventPayload } from './ReplicationEventPayload.js';
|
|
12
13
|
import { SourceEntityDescriptor } from './SourceEntity.js';
|
|
13
|
-
import {
|
|
14
|
+
import { SourceTable } from './SourceTable.js';
|
|
15
|
+
import { BatchedCustomWriteCheckpointOptions, ReplicaId, WriteCheckpointAPI } from './storage-index.js';
|
|
16
|
+
|
|
17
|
+
export interface BucketStorageFactoryListener extends DisposableListener {
|
|
18
|
+
syncStorageCreated: (storage: SyncRulesBucketStorage) => void;
|
|
19
|
+
replicationEvent: (event: ReplicationEventPayload) => void;
|
|
20
|
+
}
|
|
14
21
|
|
|
15
|
-
export interface BucketStorageFactory
|
|
22
|
+
export interface BucketStorageFactory
|
|
23
|
+
extends DisposableObserverClient<BucketStorageFactoryListener>,
|
|
24
|
+
WriteCheckpointAPI {
|
|
16
25
|
/**
|
|
17
26
|
* Update sync rules from configuration, if changed.
|
|
18
27
|
*/
|
|
@@ -81,10 +90,9 @@ export interface BucketStorageFactory {
|
|
|
81
90
|
*/
|
|
82
91
|
getActiveCheckpoint(): Promise<ActiveCheckpoint>;
|
|
83
92
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Yields the latest user write checkpoint whenever the sync checkpoint updates.
|
|
95
|
+
*/
|
|
88
96
|
watchWriteCheckpoint(user_id: string, signal: AbortSignal): AsyncIterable<WriteCheckpoint>;
|
|
89
97
|
|
|
90
98
|
/**
|
|
@@ -98,20 +106,22 @@ export interface BucketStorageFactory {
|
|
|
98
106
|
getPowerSyncInstanceId(): Promise<string>;
|
|
99
107
|
}
|
|
100
108
|
|
|
101
|
-
export interface
|
|
102
|
-
base: ActiveCheckpoint;
|
|
103
|
-
writeCheckpoint: bigint | null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface ActiveCheckpoint {
|
|
109
|
+
export interface Checkpoint {
|
|
107
110
|
readonly checkpoint: util.OpId;
|
|
108
111
|
readonly lsn: string | null;
|
|
112
|
+
}
|
|
109
113
|
|
|
114
|
+
export interface ActiveCheckpoint extends Checkpoint {
|
|
110
115
|
hasSyncRules(): boolean;
|
|
111
116
|
|
|
112
117
|
getBucketStorage(): Promise<SyncRulesBucketStorage | null>;
|
|
113
118
|
}
|
|
114
119
|
|
|
120
|
+
export interface WriteCheckpoint {
|
|
121
|
+
base: ActiveCheckpoint;
|
|
122
|
+
writeCheckpoint: bigint | null;
|
|
123
|
+
}
|
|
124
|
+
|
|
115
125
|
export interface StorageMetrics {
|
|
116
126
|
/**
|
|
117
127
|
* Size of operations (bucket_data)
|
|
@@ -194,7 +204,11 @@ export interface StartBatchOptions extends ParseSyncRulesOptions {
|
|
|
194
204
|
zeroLSN: string;
|
|
195
205
|
}
|
|
196
206
|
|
|
197
|
-
export interface
|
|
207
|
+
export interface SyncRulesBucketStorageListener extends DisposableListener {
|
|
208
|
+
batchStarted: (batch: BucketStorageBatch) => void;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface SyncRulesBucketStorage extends DisposableObserverClient<SyncRulesBucketStorageListener> {
|
|
198
212
|
readonly group_id: number;
|
|
199
213
|
readonly slot_name: string;
|
|
200
214
|
|
|
@@ -207,7 +221,7 @@ export interface SyncRulesBucketStorage {
|
|
|
207
221
|
callback: (batch: BucketStorageBatch) => Promise<void>
|
|
208
222
|
): Promise<FlushedResult | null>;
|
|
209
223
|
|
|
210
|
-
getCheckpoint(): Promise<
|
|
224
|
+
getCheckpoint(): Promise<Checkpoint>;
|
|
211
225
|
|
|
212
226
|
getParsedSyncRules(options: ParseSyncRulesOptions): SqlSyncRules;
|
|
213
227
|
|
|
@@ -293,7 +307,11 @@ export interface FlushedResult {
|
|
|
293
307
|
flushed_op: string;
|
|
294
308
|
}
|
|
295
309
|
|
|
296
|
-
export interface
|
|
310
|
+
export interface BucketBatchStorageListener extends DisposableListener {
|
|
311
|
+
replicationEvent: (payload: ReplicationEventPayload) => void;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export interface BucketStorageBatch extends DisposableObserverClient<BucketBatchStorageListener> {
|
|
297
315
|
/**
|
|
298
316
|
* Save an op, and potentially flush.
|
|
299
317
|
*
|
|
@@ -345,6 +363,11 @@ export interface BucketStorageBatch {
|
|
|
345
363
|
lastCheckpointLsn: string | null;
|
|
346
364
|
|
|
347
365
|
markSnapshotDone(tables: SourceTable[], no_checkpoint_before_lsn: string): Promise<SourceTable[]>;
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Queues the creation of a custom Write Checkpoint. This will be persisted after operations are flushed.
|
|
369
|
+
*/
|
|
370
|
+
addCustomWriteCheckpoint(checkpoint: BatchedCustomWriteCheckpointOptions): void;
|
|
348
371
|
}
|
|
349
372
|
|
|
350
373
|
export interface SaveParameterData {
|
|
@@ -362,10 +385,18 @@ export interface SaveBucketData {
|
|
|
362
385
|
evaluated: EvaluatedRow[];
|
|
363
386
|
}
|
|
364
387
|
|
|
388
|
+
export type SaveOp = 'insert' | 'update' | 'delete';
|
|
389
|
+
|
|
365
390
|
export type SaveOptions = SaveInsert | SaveUpdate | SaveDelete;
|
|
366
391
|
|
|
392
|
+
export enum SaveOperationTag {
|
|
393
|
+
INSERT = 'insert',
|
|
394
|
+
UPDATE = 'update',
|
|
395
|
+
DELETE = 'delete'
|
|
396
|
+
}
|
|
397
|
+
|
|
367
398
|
export interface SaveInsert {
|
|
368
|
-
tag:
|
|
399
|
+
tag: SaveOperationTag.INSERT;
|
|
369
400
|
sourceTable: SourceTable;
|
|
370
401
|
before?: undefined;
|
|
371
402
|
beforeReplicaId?: undefined;
|
|
@@ -374,7 +405,7 @@ export interface SaveInsert {
|
|
|
374
405
|
}
|
|
375
406
|
|
|
376
407
|
export interface SaveUpdate {
|
|
377
|
-
tag:
|
|
408
|
+
tag: SaveOperationTag.UPDATE;
|
|
378
409
|
sourceTable: SourceTable;
|
|
379
410
|
|
|
380
411
|
/**
|
|
@@ -393,7 +424,7 @@ export interface SaveUpdate {
|
|
|
393
424
|
}
|
|
394
425
|
|
|
395
426
|
export interface SaveDelete {
|
|
396
|
-
tag:
|
|
427
|
+
tag: SaveOperationTag.DELETE;
|
|
397
428
|
sourceTable: SourceTable;
|
|
398
429
|
before?: SqliteRow;
|
|
399
430
|
beforeReplicaId: ReplicaId;
|