@powersync/service-module-mongodb-storage 0.16.0 → 0.17.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 +34 -0
- package/dist/storage/MongoBucketStorage.d.ts +6 -4
- package/dist/storage/MongoBucketStorage.js +110 -36
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +4 -6
- package/dist/storage/implementation/BucketDefinitionMapping.js +3 -3
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -1
- package/dist/storage/implementation/CheckpointState.d.ts +20 -0
- package/dist/storage/implementation/CheckpointState.js +31 -0
- package/dist/storage/implementation/CheckpointState.js.map +1 -0
- package/dist/storage/implementation/MongoBucketBatch.d.ts +33 -22
- package/dist/storage/implementation/MongoBucketBatch.js +45 -271
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +2 -1
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +4 -4
- package/dist/storage/implementation/MongoPersistedSyncRules.js +11 -8
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +19 -5
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +53 -19
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +21 -10
- package/dist/storage/implementation/MongoSyncBucketStorage.js +18 -163
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +5 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +7 -3
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +14 -0
- package/dist/storage/implementation/SyncRuleStateUpdate.js +36 -0
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -0
- package/dist/storage/implementation/common/BucketDataDoc.d.ts +1 -1
- package/dist/storage/implementation/common/PersistedBatch.d.ts +2 -2
- package/dist/storage/implementation/common/SourceRecordStore.d.ts +1 -2
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +1 -1
- package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
- package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +10 -2
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +31 -47
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +15 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +385 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +16 -7
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +77 -6
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +1 -2
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.d.ts +12 -1
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +17 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +429 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +1 -1
- package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +1 -2
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +29 -7
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +117 -16
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +1 -2
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +3 -2
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +3 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/models.d.ts +61 -3
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/package.json +6 -6
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +1 -1
- package/src/storage/MongoBucketStorage.ts +166 -44
- package/src/storage/implementation/BucketDefinitionMapping.ts +12 -9
- package/src/storage/implementation/CheckpointState.ts +59 -0
- package/src/storage/implementation/MongoBucketBatch.ts +81 -355
- package/src/storage/implementation/MongoChecksums.ts +2 -1
- package/src/storage/implementation/MongoCompactor.ts +1 -1
- package/src/storage/implementation/MongoPersistedSyncRules.ts +13 -7
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +69 -24
- package/src/storage/implementation/MongoSyncBucketStorage.ts +40 -215
- package/src/storage/implementation/MongoSyncRulesLock.ts +9 -3
- package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
- package/src/storage/implementation/common/BucketDataDoc.ts +1 -1
- package/src/storage/implementation/common/PersistedBatch.ts +2 -2
- package/src/storage/implementation/common/SourceRecordStore.ts +1 -2
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +2 -2
- package/src/storage/implementation/db.ts +5 -2
- package/src/storage/implementation/models.ts +35 -58
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +478 -1
- package/src/storage/implementation/v1/MongoCompactorV1.ts +1 -1
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +111 -16
- package/src/storage/implementation/v1/PersistedBatchV1.ts +1 -2
- package/src/storage/implementation/v1/models.ts +15 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +564 -1
- package/src/storage/implementation/v3/MongoCompactorV3.ts +1 -1
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +1 -2
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +150 -22
- package/src/storage/implementation/v3/PersistedBatchV3.ts +1 -2
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +7 -2
- package/src/storage/implementation/v3/models.ts +70 -2
- package/test/src/storage_sync.test.ts +422 -6
- package/test/src/storeCurrentData.test.ts +211 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as sqlite from 'node:sqlite';
|
|
2
|
+
|
|
1
3
|
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
2
4
|
import { storage } from '@powersync/service-core';
|
|
3
5
|
import {
|
|
@@ -5,9 +7,11 @@ import {
|
|
|
5
7
|
BucketDataSource,
|
|
6
8
|
CompatibilityOption,
|
|
7
9
|
DEFAULT_HYDRATION_STATE,
|
|
8
|
-
|
|
10
|
+
HydratedSyncConfig,
|
|
9
11
|
HydrationState,
|
|
12
|
+
nodeSqlite,
|
|
10
13
|
ParameterIndexLookupCreator,
|
|
14
|
+
ParameterLookupScope,
|
|
11
15
|
SyncConfigWithErrors,
|
|
12
16
|
versionedHydrationState
|
|
13
17
|
} from '@powersync/service-sync-rules';
|
|
@@ -19,7 +23,7 @@ export class MongoPersistedSyncRules implements storage.PersistedSyncRules {
|
|
|
19
23
|
|
|
20
24
|
constructor(
|
|
21
25
|
public readonly id: number,
|
|
22
|
-
public readonly
|
|
26
|
+
public readonly syncConfigWithErrors: SyncConfigWithErrors,
|
|
23
27
|
public readonly slot_name: string,
|
|
24
28
|
private readonly mapping: BucketDefinitionMapping | null,
|
|
25
29
|
private readonly storageConfig: StorageConfig
|
|
@@ -30,7 +34,7 @@ export class MongoPersistedSyncRules implements storage.PersistedSyncRules {
|
|
|
30
34
|
}
|
|
31
35
|
this.hydrationState = new MongoHydrationState(this.mapping, this.id);
|
|
32
36
|
} else if (
|
|
33
|
-
!this.
|
|
37
|
+
!this.syncConfigWithErrors.config.compatibility.isEnabled(CompatibilityOption.versionedBucketIds) &&
|
|
34
38
|
!this.storageConfig.versionedBuckets
|
|
35
39
|
) {
|
|
36
40
|
this.hydrationState = DEFAULT_HYDRATION_STATE;
|
|
@@ -39,8 +43,11 @@ export class MongoPersistedSyncRules implements storage.PersistedSyncRules {
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
return this.
|
|
46
|
+
hydratedSyncConfig(): HydratedSyncConfig {
|
|
47
|
+
return this.syncConfigWithErrors.config.hydrate({
|
|
48
|
+
hydrationState: this.hydrationState,
|
|
49
|
+
sqlite: nodeSqlite(sqlite)
|
|
50
|
+
});
|
|
44
51
|
}
|
|
45
52
|
}
|
|
46
53
|
|
|
@@ -54,7 +61,6 @@ class MongoHydrationState implements HydrationState {
|
|
|
54
61
|
// Keep this aligned with versionedHydrationState() for now.
|
|
55
62
|
//
|
|
56
63
|
// Previous Mongo-specific behavior:
|
|
57
|
-
// const defId = this.mapping.bucketSourceId(source);
|
|
58
64
|
// return {
|
|
59
65
|
// bucketPrefix: defId,
|
|
60
66
|
// source
|
|
@@ -65,7 +71,7 @@ class MongoHydrationState implements HydrationState {
|
|
|
65
71
|
};
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
getParameterIndexLookupScope(source: ParameterIndexLookupCreator) {
|
|
74
|
+
getParameterIndexLookupScope(source: ParameterIndexLookupCreator): ParameterLookupScope {
|
|
69
75
|
const defId = this.mapping.parameterLookupId(source);
|
|
70
76
|
return {
|
|
71
77
|
lookupName: defId,
|
|
@@ -1,34 +1,31 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import {
|
|
2
|
+
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
+
import { storage, SyncRuleState } from '@powersync/service-core';
|
|
4
|
+
import * as bson from 'bson';
|
|
5
|
+
import { ReplicationStreamDocumentV3, SyncConfigDefinition } from '../storage-index.js';
|
|
3
6
|
import { BucketDefinitionMapping } from './BucketDefinitionMapping.js';
|
|
4
7
|
import { MongoPersistedSyncRules } from './MongoPersistedSyncRules.js';
|
|
5
8
|
import { MongoSyncRulesLock } from './MongoSyncRulesLock.js';
|
|
6
9
|
import { PowerSyncMongo } from './db.js';
|
|
7
|
-
import { getMongoStorageConfig
|
|
10
|
+
import { getMongoStorageConfig } from './models.js';
|
|
11
|
+
import { SyncRuleDocumentV1 } from './v1/models.js';
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
abstract class MongoPersistedSyncRulesContentBase extends storage.PersistedSyncRulesContent {
|
|
10
14
|
public current_lock: MongoSyncRulesLock | null = null;
|
|
11
15
|
public readonly mapping: BucketDefinitionMapping;
|
|
16
|
+
public readonly syncConfigId: bson.ObjectId | null;
|
|
12
17
|
|
|
13
|
-
constructor(
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
protected constructor(
|
|
19
|
+
protected readonly db: PowerSyncMongo,
|
|
20
|
+
options: ConstructorParameters<typeof storage.PersistedSyncRulesContent>[0] & {
|
|
21
|
+
mapping: BucketDefinitionMapping;
|
|
22
|
+
syncConfigId: bson.ObjectId | null;
|
|
23
|
+
}
|
|
16
24
|
) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
last_checkpoint_lsn: doc.last_checkpoint_lsn,
|
|
22
|
-
// Handle legacy values
|
|
23
|
-
slot_name: doc.slot_name ?? `powersync_${doc._id}`,
|
|
24
|
-
last_fatal_error: doc.last_fatal_error,
|
|
25
|
-
last_fatal_error_ts: doc.last_fatal_error_ts,
|
|
26
|
-
last_checkpoint_ts: doc.last_checkpoint_ts,
|
|
27
|
-
last_keepalive_ts: doc.last_keepalive_ts,
|
|
28
|
-
active: doc.state == 'ACTIVE',
|
|
29
|
-
storageVersion: doc.storage_version ?? storage.LEGACY_STORAGE_VERSION
|
|
30
|
-
});
|
|
31
|
-
this.mapping = BucketDefinitionMapping.fromSyncRules(doc);
|
|
25
|
+
const { mapping, syncConfigId, ...base } = options;
|
|
26
|
+
super(base);
|
|
27
|
+
this.mapping = mapping;
|
|
28
|
+
this.syncConfigId = syncConfigId;
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
getStorageConfig() {
|
|
@@ -41,16 +38,64 @@ export class MongoPersistedSyncRulesContent extends storage.PersistedSyncRulesCo
|
|
|
41
38
|
|
|
42
39
|
return new MongoPersistedSyncRules(
|
|
43
40
|
parsed.id,
|
|
44
|
-
parsed.
|
|
41
|
+
parsed.syncConfigWithErrors,
|
|
45
42
|
parsed.slot_name,
|
|
46
43
|
storageConfig.incrementalReprocessing ? this.mapping : null,
|
|
47
44
|
storageConfig
|
|
48
45
|
);
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
async lock() {
|
|
52
|
-
const lock = await MongoSyncRulesLock.createLock(this.db.versioned(this.getStorageConfig()), this);
|
|
48
|
+
async lock(session?: mongo.ClientSession) {
|
|
49
|
+
const lock = await MongoSyncRulesLock.createLock(this.db.versioned(this.getStorageConfig()), this, session);
|
|
53
50
|
this.current_lock = lock;
|
|
54
51
|
return lock;
|
|
55
52
|
}
|
|
56
53
|
}
|
|
54
|
+
|
|
55
|
+
export class MongoPersistedSyncRulesContentV1 extends MongoPersistedSyncRulesContentBase {
|
|
56
|
+
constructor(db: PowerSyncMongo, doc: SyncRuleDocumentV1) {
|
|
57
|
+
super(db, {
|
|
58
|
+
id: doc._id,
|
|
59
|
+
sync_rules_content: doc.content,
|
|
60
|
+
compiled_plan: doc.serialized_plan ?? null,
|
|
61
|
+
last_checkpoint_lsn: doc.last_checkpoint_lsn,
|
|
62
|
+
// Handle legacy values
|
|
63
|
+
slot_name: doc.slot_name ?? `powersync_${doc._id}`,
|
|
64
|
+
last_fatal_error: doc.last_fatal_error,
|
|
65
|
+
last_fatal_error_ts: doc.last_fatal_error_ts,
|
|
66
|
+
last_checkpoint_ts: doc.last_checkpoint_ts,
|
|
67
|
+
last_keepalive_ts: doc.last_keepalive_ts,
|
|
68
|
+
active: doc.state == SyncRuleState.ACTIVE,
|
|
69
|
+
storageVersion: doc.storage_version ?? storage.LEGACY_STORAGE_VERSION,
|
|
70
|
+
mapping: new BucketDefinitionMapping(),
|
|
71
|
+
syncConfigId: null
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class MongoPersistedSyncRulesContentV3 extends MongoPersistedSyncRulesContentBase {
|
|
77
|
+
declare public readonly syncConfigId: bson.ObjectId;
|
|
78
|
+
|
|
79
|
+
constructor(db: PowerSyncMongo, doc: ReplicationStreamDocumentV3, config: SyncConfigDefinition) {
|
|
80
|
+
const state = doc.sync_configs.find((c) => c._id.equals(config._id));
|
|
81
|
+
if (state == null) {
|
|
82
|
+
throw new ServiceAssertionError(`Cannot find sync config ${config._id} in replication stream ${doc._id}`);
|
|
83
|
+
}
|
|
84
|
+
super(db, {
|
|
85
|
+
id: doc._id,
|
|
86
|
+
sync_rules_content: config.content,
|
|
87
|
+
compiled_plan: config.serialized_plan ?? null,
|
|
88
|
+
|
|
89
|
+
last_checkpoint_lsn: state?.last_checkpoint_lsn ?? null,
|
|
90
|
+
slot_name: doc.slot_name ?? `powersync_${doc._id}`,
|
|
91
|
+
last_fatal_error: doc.last_fatal_error,
|
|
92
|
+
last_fatal_error_ts: doc.last_fatal_error_ts,
|
|
93
|
+
last_checkpoint_ts: doc.last_checkpoint_ts,
|
|
94
|
+
last_keepalive_ts: doc.last_keepalive_ts,
|
|
95
|
+
active: doc.state == SyncRuleState.ACTIVE && state.state == SyncRuleState.ACTIVE,
|
|
96
|
+
storageVersion: doc.storage_version,
|
|
97
|
+
mapping: BucketDefinitionMapping.fromSyncConfig(config),
|
|
98
|
+
syncConfigId: config._id
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
CheckpointChanges,
|
|
14
14
|
GetCheckpointChangesOptions,
|
|
15
15
|
InternalOpId,
|
|
16
|
-
maxLsn,
|
|
17
16
|
mergeAsyncIterables,
|
|
18
17
|
PopulateChecksumCacheOptions,
|
|
19
18
|
PopulateChecksumCacheResults,
|
|
@@ -22,7 +21,7 @@ import {
|
|
|
22
21
|
utils,
|
|
23
22
|
WatchWriteCheckpointOptions
|
|
24
23
|
} from '@powersync/service-core';
|
|
25
|
-
import {
|
|
24
|
+
import { HydratedSyncConfig, ParameterLookupRows, ScopedParameterLookup } from '@powersync/service-sync-rules';
|
|
26
25
|
import * as bson from 'bson';
|
|
27
26
|
import { LRUCache } from 'lru-cache';
|
|
28
27
|
import * as timers from 'timers/promises';
|
|
@@ -30,12 +29,12 @@ import { retryOnMongoMaxTimeMSExpired } from '../../utils/util.js';
|
|
|
30
29
|
import { MongoBucketStorage } from '../MongoBucketStorage.js';
|
|
31
30
|
import { MongoSyncBucketStorageContext } from './common/MongoSyncBucketStorageContext.js';
|
|
32
31
|
import type { VersionedPowerSyncMongo } from './db.js';
|
|
33
|
-
import {
|
|
32
|
+
import { StorageConfig } from './models.js';
|
|
34
33
|
import { MongoBucketBatchOptions } from './MongoBucketBatch.js';
|
|
35
34
|
import { MongoChecksumOptions, MongoChecksums } from './MongoChecksums.js';
|
|
36
35
|
import { MongoCompactOptions, MongoCompactor } from './MongoCompactor.js';
|
|
37
36
|
import { MongoParameterCompactor } from './MongoParameterCompactor.js';
|
|
38
|
-
import {
|
|
37
|
+
import { MongoPersistedSyncRulesContentV1 } from './MongoPersistedSyncRulesContent.js';
|
|
39
38
|
import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
|
|
40
39
|
|
|
41
40
|
export interface MongoSyncBucketStorageOptions {
|
|
@@ -48,6 +47,13 @@ interface InternalCheckpointChanges extends CheckpointChanges {
|
|
|
48
47
|
invalidateWriteCheckpoints: boolean;
|
|
49
48
|
}
|
|
50
49
|
|
|
50
|
+
interface WriterSyncState {
|
|
51
|
+
lastCheckpointLsn: string | null;
|
|
52
|
+
resumeFromLsn: string | null;
|
|
53
|
+
keepaliveOp: InternalOpId | null;
|
|
54
|
+
syncConfigId?: bson.ObjectId | null;
|
|
55
|
+
}
|
|
56
|
+
|
|
51
57
|
/**
|
|
52
58
|
* Only keep checkpoints around for a minute, before fetching a fresh one.
|
|
53
59
|
*
|
|
@@ -68,21 +74,23 @@ export abstract class MongoSyncBucketStorage
|
|
|
68
74
|
|
|
69
75
|
readonly checksums: MongoChecksums;
|
|
70
76
|
|
|
71
|
-
private parsedSyncRulesCache: { parsed:
|
|
77
|
+
private parsedSyncRulesCache: { parsed: HydratedSyncConfig; options: storage.ParseSyncRulesOptions } | undefined;
|
|
72
78
|
private writeCheckpointAPI: MongoWriteCheckpointAPI;
|
|
73
79
|
public readonly logger: Logger;
|
|
80
|
+
public readonly storageConfig: StorageConfig;
|
|
74
81
|
#storageInitialized = false;
|
|
75
82
|
|
|
76
83
|
constructor(
|
|
77
84
|
public readonly factory: MongoBucketStorage,
|
|
78
85
|
public readonly group_id: number,
|
|
79
|
-
protected readonly sync_rules:
|
|
86
|
+
protected readonly sync_rules: MongoPersistedSyncRulesContentV1,
|
|
80
87
|
public readonly slot_name: string,
|
|
81
88
|
writeCheckpointMode: storage.WriteCheckpointMode | undefined,
|
|
82
89
|
options: MongoSyncBucketStorageOptions
|
|
83
90
|
) {
|
|
84
91
|
super();
|
|
85
|
-
this.
|
|
92
|
+
this.storageConfig = options.storageConfig;
|
|
93
|
+
this.db = factory.db.versioned(this.storageConfig);
|
|
86
94
|
this.checksums = this.createMongoChecksums(options);
|
|
87
95
|
this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
|
|
88
96
|
db: this.db,
|
|
@@ -136,10 +144,10 @@ export abstract class MongoSyncBucketStorage
|
|
|
136
144
|
});
|
|
137
145
|
}
|
|
138
146
|
|
|
139
|
-
getParsedSyncRules(options: storage.ParseSyncRulesOptions):
|
|
147
|
+
getParsedSyncRules(options: storage.ParseSyncRulesOptions): HydratedSyncConfig {
|
|
140
148
|
const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
|
|
141
149
|
if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
|
|
142
|
-
this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).
|
|
150
|
+
this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).hydratedSyncConfig(), options };
|
|
143
151
|
}
|
|
144
152
|
|
|
145
153
|
return this.parsedSyncRulesCache!.parsed;
|
|
@@ -149,16 +157,14 @@ export abstract class MongoSyncBucketStorage
|
|
|
149
157
|
return (await this.getCheckpointInternal()) ?? new EmptyReplicationCheckpoint();
|
|
150
158
|
}
|
|
151
159
|
|
|
160
|
+
protected abstract fetchCheckpointState(
|
|
161
|
+
session: mongo.ClientSession
|
|
162
|
+
): Promise<{ checkpoint: bigint; lsn: string | null } | null>;
|
|
163
|
+
|
|
152
164
|
async getCheckpointInternal(): Promise<storage.ReplicationCheckpoint | null> {
|
|
153
165
|
return await this.db.client.withSession({ snapshot: true }, async (session) => {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
{
|
|
157
|
-
session,
|
|
158
|
-
projection: { _id: 1, state: 1, last_checkpoint: 1, last_checkpoint_lsn: 1, snapshot_done: 1 }
|
|
159
|
-
}
|
|
160
|
-
);
|
|
161
|
-
if (!doc?.snapshot_done || !['ACTIVE', 'ERRORED'].includes(doc.state)) {
|
|
166
|
+
const state = await this.fetchCheckpointState(session);
|
|
167
|
+
if (state == null) {
|
|
162
168
|
return null;
|
|
163
169
|
}
|
|
164
170
|
|
|
@@ -166,12 +172,7 @@ export abstract class MongoSyncBucketStorage
|
|
|
166
172
|
if (snapshotTime == null) {
|
|
167
173
|
throw new ServiceAssertionError('Missing snapshotTime in getCheckpoint()');
|
|
168
174
|
}
|
|
169
|
-
return new MongoReplicationCheckpoint(
|
|
170
|
-
this,
|
|
171
|
-
doc.last_checkpoint ?? 0n,
|
|
172
|
-
doc.last_checkpoint_lsn ?? null,
|
|
173
|
-
snapshotTime
|
|
174
|
-
);
|
|
175
|
+
return new MongoReplicationCheckpoint(this, state.checkpoint, state.lsn, snapshotTime);
|
|
175
176
|
});
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -188,31 +189,28 @@ export abstract class MongoSyncBucketStorage
|
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
protected abstract createWriterImpl(batchOptions: MongoBucketBatchOptions): storage.BucketStorageBatch;
|
|
192
|
+
protected abstract getWriterSyncState(): Promise<WriterSyncState>;
|
|
191
193
|
|
|
192
194
|
async createWriter(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
|
|
193
195
|
await this.initializeStorage();
|
|
194
196
|
|
|
195
|
-
const
|
|
196
|
-
{
|
|
197
|
-
_id: this.group_id
|
|
198
|
-
},
|
|
199
|
-
{ projection: { last_checkpoint_lsn: 1, no_checkpoint_before: 1, keepalive_op: 1, snapshot_lsn: 1 } }
|
|
200
|
-
);
|
|
201
|
-
const checkpoint_lsn = doc?.last_checkpoint_lsn ?? null;
|
|
197
|
+
const state = await this.getWriterSyncState();
|
|
202
198
|
|
|
203
199
|
const batchOptions: MongoBucketBatchOptions = {
|
|
204
200
|
logger: options.logger ?? this.logger,
|
|
205
201
|
db: this.db,
|
|
206
|
-
syncRules: this.sync_rules.parsed(options).
|
|
202
|
+
syncRules: this.sync_rules.parsed(options).hydratedSyncConfig(),
|
|
207
203
|
mapping: this.sync_rules.mapping,
|
|
208
204
|
groupId: this.group_id,
|
|
209
205
|
slotName: this.slot_name,
|
|
210
|
-
lastCheckpointLsn:
|
|
211
|
-
resumeFromLsn:
|
|
212
|
-
keepaliveOp:
|
|
206
|
+
lastCheckpointLsn: state.lastCheckpointLsn,
|
|
207
|
+
resumeFromLsn: state.resumeFromLsn,
|
|
208
|
+
keepaliveOp: state.keepaliveOp,
|
|
213
209
|
storeCurrentData: options.storeCurrentData,
|
|
214
210
|
skipExistingRows: options.skipExistingRows ?? false,
|
|
215
211
|
markRecordUnavailable: options.markRecordUnavailable,
|
|
212
|
+
hooks: options.hooks,
|
|
213
|
+
syncConfigId: state.syncConfigId,
|
|
216
214
|
tracer: options.tracer
|
|
217
215
|
};
|
|
218
216
|
const writer = this.createWriterImpl(batchOptions);
|
|
@@ -230,134 +228,6 @@ export abstract class MongoSyncBucketStorage
|
|
|
230
228
|
return writer.last_flushed_op != null ? { flushed_op: writer.last_flushed_op } : null;
|
|
231
229
|
}
|
|
232
230
|
|
|
233
|
-
protected abstract sourceTableBaseId(): Partial<CommonSourceTableDocument>;
|
|
234
|
-
|
|
235
|
-
protected abstract augmentCreatedSourceTableDocument(
|
|
236
|
-
createDoc: CommonSourceTableDocument,
|
|
237
|
-
options: storage.ResolveTableOptions,
|
|
238
|
-
candidateSourceTable: storage.SourceTable
|
|
239
|
-
): void;
|
|
240
|
-
|
|
241
|
-
protected abstract initializeResolvedSourceRecords(sourceTableId: bson.ObjectId): Promise<void>;
|
|
242
|
-
|
|
243
|
-
async resolveTable(options: storage.ResolveTableOptions): Promise<storage.ResolveTableResult> {
|
|
244
|
-
const { group_id, connection_id, connection_tag, entity_descriptor } = options;
|
|
245
|
-
|
|
246
|
-
const { schema, name, objectId, replicaIdColumns } = entity_descriptor;
|
|
247
|
-
|
|
248
|
-
const normalizedReplicaIdColumns = replicaIdColumns.map((column) => ({
|
|
249
|
-
name: column.name,
|
|
250
|
-
type: column.type,
|
|
251
|
-
type_oid: column.typeId
|
|
252
|
-
}));
|
|
253
|
-
let result: storage.ResolveTableResult | null = null;
|
|
254
|
-
let initializeSourceRecordsFor: bson.ObjectId | null = null;
|
|
255
|
-
|
|
256
|
-
const baseId = this.sourceTableBaseId();
|
|
257
|
-
await this.db.client.withSession(async (session) => {
|
|
258
|
-
const col = this.db.commonSourceTables(group_id);
|
|
259
|
-
let filter: Partial<CommonSourceTableDocument> = {
|
|
260
|
-
...baseId,
|
|
261
|
-
connection_id: connection_id,
|
|
262
|
-
schema_name: schema,
|
|
263
|
-
table_name: name,
|
|
264
|
-
replica_id_columns2: normalizedReplicaIdColumns
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
if (objectId != null) {
|
|
268
|
-
filter.relation_id = objectId;
|
|
269
|
-
}
|
|
270
|
-
let doc = await col.findOne(filter, { session });
|
|
271
|
-
if (doc == null) {
|
|
272
|
-
const candidateSourceTable = new storage.SourceTable({
|
|
273
|
-
id: new bson.ObjectId(),
|
|
274
|
-
connectionTag: connection_tag,
|
|
275
|
-
objectId: objectId,
|
|
276
|
-
schema: schema,
|
|
277
|
-
name: name,
|
|
278
|
-
replicaIdColumns: replicaIdColumns,
|
|
279
|
-
snapshotComplete: false
|
|
280
|
-
});
|
|
281
|
-
const createDoc: CommonSourceTableDocument = {
|
|
282
|
-
_id: candidateSourceTable.id as bson.ObjectId,
|
|
283
|
-
...(baseId as any),
|
|
284
|
-
connection_id: connection_id,
|
|
285
|
-
relation_id: objectId,
|
|
286
|
-
schema_name: schema,
|
|
287
|
-
table_name: name,
|
|
288
|
-
replica_id_columns: null,
|
|
289
|
-
replica_id_columns2: normalizedReplicaIdColumns,
|
|
290
|
-
snapshot_done: false,
|
|
291
|
-
snapshot_status: undefined
|
|
292
|
-
};
|
|
293
|
-
this.augmentCreatedSourceTableDocument(createDoc, options, candidateSourceTable);
|
|
294
|
-
doc = createDoc;
|
|
295
|
-
|
|
296
|
-
await col.insertOne(doc, { session });
|
|
297
|
-
initializeSourceRecordsFor = doc._id;
|
|
298
|
-
}
|
|
299
|
-
const sourceTable = new storage.SourceTable({
|
|
300
|
-
id: doc._id,
|
|
301
|
-
connectionTag: connection_tag,
|
|
302
|
-
objectId: objectId,
|
|
303
|
-
schema: schema,
|
|
304
|
-
name: name,
|
|
305
|
-
replicaIdColumns: replicaIdColumns,
|
|
306
|
-
snapshotComplete: doc.snapshot_done ?? true
|
|
307
|
-
});
|
|
308
|
-
sourceTable.syncEvent = options.sync_rules.tableTriggersEvent(sourceTable);
|
|
309
|
-
sourceTable.syncData = options.sync_rules.tableSyncsData(sourceTable);
|
|
310
|
-
sourceTable.syncParameters = options.sync_rules.tableSyncsParameters(sourceTable);
|
|
311
|
-
sourceTable.snapshotStatus =
|
|
312
|
-
doc.snapshot_status == null
|
|
313
|
-
? undefined
|
|
314
|
-
: {
|
|
315
|
-
lastKey: doc.snapshot_status.last_key?.buffer ?? null,
|
|
316
|
-
totalEstimatedCount: doc.snapshot_status.total_estimated_count,
|
|
317
|
-
replicatedCount: doc.snapshot_status.replicated_count
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
let dropTables: storage.SourceTable[] = [];
|
|
321
|
-
let truncateFilter = [{ schema_name: schema, table_name: name }] as any[];
|
|
322
|
-
if (objectId != null) {
|
|
323
|
-
truncateFilter.push({ relation_id: objectId });
|
|
324
|
-
}
|
|
325
|
-
const truncate = await col
|
|
326
|
-
.find(
|
|
327
|
-
{
|
|
328
|
-
...baseId,
|
|
329
|
-
connection_id: connection_id,
|
|
330
|
-
_id: { $ne: doc._id },
|
|
331
|
-
$or: truncateFilter
|
|
332
|
-
},
|
|
333
|
-
{ session }
|
|
334
|
-
)
|
|
335
|
-
.toArray();
|
|
336
|
-
dropTables = truncate.map(
|
|
337
|
-
(doc) =>
|
|
338
|
-
new storage.SourceTable({
|
|
339
|
-
id: doc._id,
|
|
340
|
-
connectionTag: connection_tag,
|
|
341
|
-
objectId: doc.relation_id,
|
|
342
|
-
schema: doc.schema_name,
|
|
343
|
-
name: doc.table_name,
|
|
344
|
-
replicaIdColumns:
|
|
345
|
-
doc.replica_id_columns2?.map((c) => ({ name: c.name, typeOid: c.type_oid, type: c.type })) ?? [],
|
|
346
|
-
snapshotComplete: doc.snapshot_done ?? true
|
|
347
|
-
})
|
|
348
|
-
);
|
|
349
|
-
|
|
350
|
-
result = {
|
|
351
|
-
table: sourceTable,
|
|
352
|
-
dropTables: dropTables
|
|
353
|
-
};
|
|
354
|
-
});
|
|
355
|
-
if (initializeSourceRecordsFor != null) {
|
|
356
|
-
await this.initializeResolvedSourceRecords(initializeSourceRecordsFor);
|
|
357
|
-
}
|
|
358
|
-
return result!;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
231
|
protected abstract getParameterSetsImpl(
|
|
362
232
|
checkpoint: MongoReplicationCheckpoint,
|
|
363
233
|
lookups: ScopedParameterLookup[],
|
|
@@ -397,49 +267,20 @@ export abstract class MongoSyncBucketStorage
|
|
|
397
267
|
this.checksums.clearCache();
|
|
398
268
|
}
|
|
399
269
|
|
|
270
|
+
protected abstract terminateSyncRuleState(): Promise<void>;
|
|
271
|
+
|
|
400
272
|
async terminate(options?: storage.TerminateOptions) {
|
|
401
273
|
if (!options || options?.clearStorage) {
|
|
402
274
|
await this.clear(options);
|
|
403
275
|
}
|
|
404
|
-
await this.
|
|
405
|
-
{
|
|
406
|
-
_id: this.group_id
|
|
407
|
-
},
|
|
408
|
-
{
|
|
409
|
-
$set: {
|
|
410
|
-
state: storage.SyncRuleState.TERMINATED,
|
|
411
|
-
persisted_lsn: null,
|
|
412
|
-
snapshot_done: false
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
);
|
|
276
|
+
await this.terminateSyncRuleState();
|
|
416
277
|
await this.db.notifyCheckpoint();
|
|
417
278
|
}
|
|
418
279
|
|
|
419
|
-
|
|
420
|
-
const doc = await this.db.sync_rules.findOne(
|
|
421
|
-
{
|
|
422
|
-
_id: this.group_id
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
projection: {
|
|
426
|
-
snapshot_done: 1,
|
|
427
|
-
last_checkpoint_lsn: 1,
|
|
428
|
-
state: 1,
|
|
429
|
-
snapshot_lsn: 1
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
);
|
|
433
|
-
if (doc == null) {
|
|
434
|
-
throw new ServiceAssertionError('Cannot find replication stream status');
|
|
435
|
-
}
|
|
280
|
+
protected abstract getStatusImpl(): Promise<storage.SyncRuleStatus>;
|
|
436
281
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
snapshot_lsn: doc.snapshot_lsn ?? null,
|
|
440
|
-
active: doc.state == 'ACTIVE',
|
|
441
|
-
checkpoint_lsn: doc.last_checkpoint_lsn
|
|
442
|
-
};
|
|
282
|
+
async getStatus(): Promise<storage.SyncRuleStatus> {
|
|
283
|
+
return this.getStatusImpl();
|
|
443
284
|
}
|
|
444
285
|
|
|
445
286
|
protected abstract clearBucketData(signal?: AbortSignal): Promise<void>;
|
|
@@ -451,6 +292,7 @@ export abstract class MongoSyncBucketStorage
|
|
|
451
292
|
protected abstract clearBucketState(signal?: AbortSignal): Promise<void>;
|
|
452
293
|
|
|
453
294
|
protected abstract clearSourceTables(signal?: AbortSignal): Promise<void>;
|
|
295
|
+
protected abstract clearSyncRuleState(): Promise<void>;
|
|
454
296
|
|
|
455
297
|
async clear(options?: storage.ClearStorageOptions): Promise<void> {
|
|
456
298
|
const signal = options?.signal;
|
|
@@ -459,24 +301,7 @@ export abstract class MongoSyncBucketStorage
|
|
|
459
301
|
throw new ReplicationAbortedError('Aborted clearing data', signal.reason);
|
|
460
302
|
}
|
|
461
303
|
|
|
462
|
-
await this.
|
|
463
|
-
{
|
|
464
|
-
_id: this.group_id
|
|
465
|
-
},
|
|
466
|
-
{
|
|
467
|
-
$set: {
|
|
468
|
-
snapshot_done: false,
|
|
469
|
-
persisted_lsn: null,
|
|
470
|
-
last_checkpoint_lsn: null,
|
|
471
|
-
last_checkpoint: null,
|
|
472
|
-
no_checkpoint_before: null
|
|
473
|
-
},
|
|
474
|
-
$unset: {
|
|
475
|
-
snapshot_lsn: 1
|
|
476
|
-
}
|
|
477
|
-
},
|
|
478
|
-
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
479
|
-
);
|
|
304
|
+
await this.clearSyncRuleState();
|
|
480
305
|
|
|
481
306
|
await this.clearBucketData(signal);
|
|
482
307
|
await this.clearParameterIndexes(signal);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from 'crypto';
|
|
2
2
|
|
|
3
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
4
|
import { ErrorCode, Logger, ServiceError } from '@powersync/lib-services-framework';
|
|
4
5
|
import { storage } from '@powersync/service-core';
|
|
5
6
|
import { VersionedPowerSyncMongo } from './db.js';
|
|
@@ -11,9 +12,13 @@ import { VersionedPowerSyncMongo } from './db.js';
|
|
|
11
12
|
export class MongoSyncRulesLock implements storage.ReplicationLock {
|
|
12
13
|
private readonly refreshInterval: NodeJS.Timeout;
|
|
13
14
|
|
|
15
|
+
/**
|
|
16
|
+
* @param session optional session to create the lock within another transaction
|
|
17
|
+
*/
|
|
14
18
|
static async createLock(
|
|
15
19
|
db: VersionedPowerSyncMongo,
|
|
16
|
-
sync_rules: storage.PersistedSyncRulesContent
|
|
20
|
+
sync_rules: storage.PersistedSyncRulesContent,
|
|
21
|
+
session?: mongo.ClientSession
|
|
17
22
|
): Promise<MongoSyncRulesLock> {
|
|
18
23
|
const lockId = crypto.randomBytes(8).toString('hex');
|
|
19
24
|
const doc = await db.sync_rules.findOneAndUpdate(
|
|
@@ -28,13 +33,14 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
|
|
|
28
33
|
},
|
|
29
34
|
{
|
|
30
35
|
projection: { lock: 1 },
|
|
31
|
-
returnDocument: 'before'
|
|
36
|
+
returnDocument: 'before',
|
|
37
|
+
session
|
|
32
38
|
}
|
|
33
39
|
);
|
|
34
40
|
|
|
35
41
|
if (doc == null) {
|
|
36
42
|
// Query the existing lock to get the expiration time (best effort - it may have been released in the meantime).
|
|
37
|
-
const heldLock = await db.sync_rules.findOne({ _id: sync_rules.id }, { projection: { lock: 1 } });
|
|
43
|
+
const heldLock = await db.sync_rules.findOne({ _id: sync_rules.id }, { projection: { lock: 1 }, session });
|
|
38
44
|
if (heldLock?.lock?.expires_at) {
|
|
39
45
|
throw new ServiceError(
|
|
40
46
|
ErrorCode.PSYNC_S1003,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { storage } from '@powersync/service-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Update pipeline to update replication stream status, covering all storage versions.
|
|
6
|
+
*
|
|
7
|
+
* Roughly equivalent to:
|
|
8
|
+
* $set: {
|
|
9
|
+
* state: state,
|
|
10
|
+
* 'sync_configs.$[].state': state
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* The difference is that this also handles v1 storage cases, where `sync_configs` is not present.
|
|
14
|
+
*/
|
|
15
|
+
export function syncRuleStateUpdatePipeline(state: storage.SyncRuleState): mongo.Document[] {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
$set: {
|
|
19
|
+
state,
|
|
20
|
+
sync_configs: {
|
|
21
|
+
$cond: [
|
|
22
|
+
{ $isArray: '$sync_configs' },
|
|
23
|
+
{
|
|
24
|
+
$map: {
|
|
25
|
+
input: '$sync_configs',
|
|
26
|
+
as: 'config',
|
|
27
|
+
in: {
|
|
28
|
+
$mergeObjects: ['$$config', { state }]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
'$$REMOVE'
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { BucketDataSource, EvaluatedParameters, EvaluatedRow } from '@powersync/service-sync-rules';
|
|
2
|
+
import { BucketDataSource, BucketDefinitionId, EvaluatedParameters, EvaluatedRow } from '@powersync/service-sync-rules';
|
|
3
3
|
import * as bson from 'bson';
|
|
4
4
|
|
|
5
5
|
import { logger as defaultLogger, Logger } from '@powersync/lib-services-framework';
|
|
6
6
|
import { InternalOpId, storage, utils } from '@powersync/service-core';
|
|
7
7
|
import { JSONBig } from '@powersync/service-jsonbig';
|
|
8
8
|
import { mongoTableId, replicaIdToSubkey } from '../../../utils/util.js';
|
|
9
|
-
import {
|
|
9
|
+
import { BucketDefinitionMapping } from '../BucketDefinitionMapping.js';
|
|
10
10
|
import { currentBucketKey, MAX_ROW_SIZE } from '../MongoBucketBatchShared.js';
|
|
11
11
|
import { MongoIdSequence } from '../MongoIdSequence.js';
|
|
12
12
|
import type { VersionedPowerSyncMongo } from '../db.js';
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
2
|
import { Logger } from '@powersync/lib-services-framework';
|
|
3
3
|
import { storage } from '@powersync/service-core';
|
|
4
|
-
import { EvaluatedParameters, EvaluatedRow } from '@powersync/service-sync-rules';
|
|
4
|
+
import { BucketDefinitionId, EvaluatedParameters, EvaluatedRow, ParameterIndexId } from '@powersync/service-sync-rules';
|
|
5
5
|
import * as bson from 'bson';
|
|
6
|
-
import { BucketDefinitionId, ParameterIndexId } from '../BucketDefinitionMapping.js';
|
|
7
6
|
|
|
8
7
|
export interface SourceRecordLookupEntry {
|
|
9
8
|
sourceTableId: bson.ObjectId;
|