@powersync/service-module-mongodb-storage 0.19.0 → 0.21.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 +64 -0
- package/dist/storage/MongoBucketStorage.d.ts +31 -3
- package/dist/storage/MongoBucketStorage.js +365 -206
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +15 -4
- package/dist/storage/implementation/MongoBucketBatch.js +35 -9
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +19 -79
- package/dist/storage/implementation/MongoCompactor.js +27 -238
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
- package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +29 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +4 -2
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +3 -1
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +27 -11
- package/dist/storage/implementation/MongoSyncBucketStorage.js +78 -31
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +21 -9
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +7 -28
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
- package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +22 -2
- package/dist/storage/implementation/common/PersistedBatch.d.ts +27 -0
- package/dist/storage/implementation/common/PersistedBatch.js +43 -2
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +4 -1
- package/dist/storage/implementation/db.js +14 -1
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +35 -0
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +6 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +80 -84
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +36 -5
- package/dist/storage/implementation/v1/MongoCompactorV1.js +229 -8
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -11
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +65 -16
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.js +1 -0
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
- package/dist/storage/implementation/v3/CompactionLease.js +131 -0
- package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +8 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +120 -39
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +77 -25
- package/dist/storage/implementation/v3/MongoCompactorV3.js +585 -141
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +91 -31
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +2 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js +52 -11
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.js +2 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
- package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
- package/dist/storage/implementation/v3/compact-utils.js +216 -0
- package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
- package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
- package/dist/storage/implementation/v3/compaction-constants.js +6 -0
- package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +89 -5
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +8 -4
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +3 -5
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +10 -2
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +37 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +167 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +177 -23
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +15 -7
- package/dist/storage/implementation/v3/source-table-utils.js +49 -57
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
- package/dist/storage/storage-index.d.ts +2 -1
- package/dist/storage/storage-index.js +1 -0
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +23 -1
- package/dist/types/types.js +16 -7
- package/dist/types/types.js.map +1 -1
- package/dist/utils/util.js +3 -1
- package/dist/utils/util.js.map +1 -1
- package/package.json +10 -9
- package/src/storage/MongoBucketStorage.ts +495 -268
- package/src/storage/implementation/MongoBucketBatch.ts +54 -14
- package/src/storage/implementation/MongoCompactor.ts +35 -346
- package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +34 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +4 -2
- package/src/storage/implementation/MongoStorageProvider.ts +3 -1
- package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
- package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +12 -44
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
- package/src/storage/implementation/common/PersistedBatch.ts +54 -2
- package/src/storage/implementation/db.ts +21 -2
- package/src/storage/implementation/models.ts +38 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +107 -96
- package/src/storage/implementation/v1/MongoCompactorV1.ts +303 -19
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
- package/src/storage/implementation/v1/models.ts +1 -0
- package/src/storage/implementation/v3/CompactionLease.ts +152 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +159 -42
- package/src/storage/implementation/v3/MongoCompactorV3.ts +744 -190
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +111 -41
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +57 -12
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
- package/src/storage/implementation/v3/bucket-format.ts +2 -0
- package/src/storage/implementation/v3/compact-utils.ts +320 -0
- package/src/storage/implementation/v3/compaction-constants.ts +5 -0
- package/src/storage/implementation/v3/models.ts +96 -3
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +16 -8
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +15 -4
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +257 -27
- package/src/storage/implementation/v3/source-table-utils.ts +82 -76
- package/src/storage/storage-index.ts +2 -0
- package/src/types/types.ts +24 -7
- package/src/utils/util.ts +3 -1
- package/test/src/__snapshots__/storage.test.ts.snap +25 -0
- package/test/src/__snapshots__/storage_sync.test.ts.snap +605 -16
- package/test/src/cleanup-stopped-sync-configs.test.ts +40 -13
- package/test/src/compact-utils.test.ts +226 -0
- package/test/src/helpers/MemoryObjectStorage.ts +14 -6
- package/test/src/object_storage_usage.test.ts +255 -0
- package/test/src/parameter_compacting_v1.test.ts +120 -0
- package/test/src/parameter_compacting_v3.test.ts +192 -0
- package/test/src/parameter_compaction_fence.test.ts +276 -0
- package/test/src/storage.test.ts +273 -22
- package/test/src/storage_compacting.test.ts +714 -244
- package/test/src/storage_s3_checksums.test.ts +3 -4
- package/test/src/storage_s3_compaction_lifecycle.test.ts +138 -12
- package/test/src/storage_s3_reading.test.ts +243 -1
- package/test/src/storage_s3_writing.test.ts +1 -0
- package/test/src/storage_sync.test.ts +365 -41
- package/test/src/util.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
2
|
-
import { BucketDefinitionMapping, ColumnDescriptor, storage } from '@powersync/service-core';
|
|
2
|
+
import { BucketDefinitionMapping, ColumnDescriptor, JsonValue, storage } from '@powersync/service-core';
|
|
3
3
|
import {
|
|
4
4
|
BucketDataSource,
|
|
5
5
|
BucketDefinitionId,
|
|
6
|
+
EventDefinitionId,
|
|
7
|
+
HydratedEventDescriptor,
|
|
6
8
|
HydratedSyncConfig,
|
|
7
9
|
MatchingSources,
|
|
8
10
|
ParameterIndexId,
|
|
@@ -22,12 +24,13 @@ export interface SourceTableIdentity {
|
|
|
22
24
|
export interface SourceTableMembershipIds {
|
|
23
25
|
bucketDataSourceIds: BucketDefinitionId[];
|
|
24
26
|
parameterLookupSourceIds: ParameterIndexId[];
|
|
27
|
+
eventDefinitionIds: EventDefinitionId[];
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export interface SourceTableDesiredResolution {
|
|
28
31
|
bucketSourceById: Map<BucketDefinitionId, BucketDataSource>;
|
|
29
32
|
parameterLookupSourceById: Map<ParameterIndexId, ParameterIndexLookupCreator>;
|
|
30
|
-
|
|
33
|
+
eventDefinitionById: Map<EventDefinitionId, HydratedEventDescriptor>;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
export interface SourceTableReconciliationContext {
|
|
@@ -38,6 +41,14 @@ export interface SourceTableReconciliationContext {
|
|
|
38
41
|
syncConfig: HydratedSyncConfig;
|
|
39
42
|
mapping: BucketDefinitionMapping;
|
|
40
43
|
desired: SourceTableDesiredResolution;
|
|
44
|
+
/**
|
|
45
|
+
* Candidates the source connector considers safe to reuse.
|
|
46
|
+
*/
|
|
47
|
+
sourceCompatibleTables: readonly storage.SourceTableCandidate[];
|
|
48
|
+
/**
|
|
49
|
+
* Source metadata for records created by this resolution.
|
|
50
|
+
*/
|
|
51
|
+
newTableSourceMetadata: JsonValue;
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
export interface SourceTableReconciliationPlan {
|
|
@@ -47,7 +58,7 @@ export interface SourceTableReconciliationPlan {
|
|
|
47
58
|
narrowingUpdates: SourceTableMembershipUpdate[];
|
|
48
59
|
/**
|
|
49
60
|
* Memberships for a new source-table doc covering desired ids no existing doc covers,
|
|
50
|
-
* or null if no new doc is needed.
|
|
61
|
+
* or null if no new doc is needed.
|
|
51
62
|
*/
|
|
52
63
|
newTableMemberships: SourceTableMembershipIds | null;
|
|
53
64
|
/** Identity-overlapping docs that conflict with the current identity and must be dropped. */
|
|
@@ -67,7 +78,8 @@ export interface NewSourceTable {
|
|
|
67
78
|
export function sourceTableDesiredResolution(
|
|
68
79
|
syncConfig: HydratedSyncConfig,
|
|
69
80
|
ref: SourceTableRef,
|
|
70
|
-
mapping: BucketDefinitionMapping
|
|
81
|
+
mapping: BucketDefinitionMapping,
|
|
82
|
+
eventById: ReadonlyMap<EventDefinitionId, HydratedEventDescriptor>
|
|
71
83
|
): SourceTableDesiredResolution {
|
|
72
84
|
const matchingSources = syncConfig.getMatchingSources(ref);
|
|
73
85
|
return {
|
|
@@ -77,7 +89,7 @@ export function sourceTableDesiredResolution(
|
|
|
77
89
|
parameterLookupSourceById: new Map(
|
|
78
90
|
matchingSources.parameterLookupSources.map((source) => [mapping.parameterLookupId(source), source] as const)
|
|
79
91
|
),
|
|
80
|
-
|
|
92
|
+
eventDefinitionById: new Map([...eventById].filter(([, event]) => event.tableTriggersEvent(ref)))
|
|
81
93
|
};
|
|
82
94
|
}
|
|
83
95
|
|
|
@@ -108,35 +120,39 @@ function intersectMembershipIds(
|
|
|
108
120
|
): SourceTableMembershipIds {
|
|
109
121
|
return {
|
|
110
122
|
bucketDataSourceIds: doc.bucket_data_source_ids.filter((id) => desired.bucketSourceById.has(id)),
|
|
111
|
-
parameterLookupSourceIds: doc.parameter_lookup_source_ids.filter((id) => desired.parameterLookupSourceById.has(id))
|
|
123
|
+
parameterLookupSourceIds: doc.parameter_lookup_source_ids.filter((id) => desired.parameterLookupSourceById.has(id)),
|
|
124
|
+
eventDefinitionIds: doc.event_definition_ids.filter((id) => desired.eventDefinitionById.has(id))
|
|
112
125
|
};
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
function hasMembershipIds(memberships: SourceTableMembershipIds) {
|
|
116
|
-
return
|
|
129
|
+
return (
|
|
130
|
+
memberships.bucketDataSourceIds.length > 0 ||
|
|
131
|
+
memberships.parameterLookupSourceIds.length > 0 ||
|
|
132
|
+
memberships.eventDefinitionIds.length > 0
|
|
133
|
+
);
|
|
117
134
|
}
|
|
118
135
|
|
|
119
136
|
function sameMembershipIds(doc: SourceTableDocumentV3, memberships: SourceTableMembershipIds) {
|
|
120
137
|
return (
|
|
121
138
|
sameStringArray(doc.bucket_data_source_ids, memberships.bucketDataSourceIds) &&
|
|
122
|
-
sameStringArray(doc.parameter_lookup_source_ids, memberships.parameterLookupSourceIds)
|
|
139
|
+
sameStringArray(doc.parameter_lookup_source_ids, memberships.parameterLookupSourceIds) &&
|
|
140
|
+
sameStringArray(doc.event_definition_ids, memberships.eventDefinitionIds)
|
|
123
141
|
);
|
|
124
142
|
}
|
|
125
143
|
|
|
126
144
|
class SourceTableReconciliationPlanner {
|
|
127
145
|
private readonly coveredBucketDataSourceIds = new Set<string>();
|
|
128
146
|
private readonly coveredParameterLookupSourceIds = new Set<string>();
|
|
129
|
-
private readonly
|
|
147
|
+
private readonly coveredEventDefinitionIds = new Set<string>();
|
|
130
148
|
private readonly tables: storage.SourceTable[] = [];
|
|
131
149
|
private readonly narrowingUpdates: SourceTableMembershipUpdate[] = [];
|
|
132
|
-
private retainedEventOnlyTable = false;
|
|
133
150
|
|
|
134
151
|
constructor(private readonly context: SourceTableReconciliationContext) {}
|
|
135
152
|
|
|
136
153
|
plan(candidateDocs: SourceTableDocumentV3[]): SourceTableReconciliationPlan {
|
|
137
|
-
const { identity } = this.context;
|
|
138
154
|
for (const doc of candidateDocs) {
|
|
139
|
-
if (
|
|
155
|
+
if (this.isCompatible(doc)) {
|
|
140
156
|
this.retainDoc(doc);
|
|
141
157
|
}
|
|
142
158
|
}
|
|
@@ -145,35 +161,31 @@ class SourceTableReconciliationPlanner {
|
|
|
145
161
|
tables: this.tables,
|
|
146
162
|
narrowingUpdates: this.narrowingUpdates,
|
|
147
163
|
newTableMemberships: this.newTableMemberships(),
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
dropDocs: candidateDocs.filter(
|
|
151
|
-
(doc) => !this.retainedDocIds.has(doc._id.toHexString()) && !matchingSourceTableIdentity(doc, identity)
|
|
152
|
-
)
|
|
164
|
+
// Compatible records remain available for reuse even when not needed by this config.
|
|
165
|
+
dropDocs: candidateDocs.filter((doc) => !this.isCompatible(doc))
|
|
153
166
|
};
|
|
154
167
|
}
|
|
155
168
|
|
|
169
|
+
private isCompatible(doc: SourceTableDocumentV3) {
|
|
170
|
+
return this.compatibleTableFor(doc) != null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private compatibleTableFor(doc: SourceTableDocumentV3): storage.SourceTableCandidate | undefined {
|
|
174
|
+
return this.context.sourceCompatibleTables.find((table) => storage.sourceTableIdEquals(table.id, doc._id));
|
|
175
|
+
}
|
|
176
|
+
|
|
156
177
|
private retainDoc(doc: SourceTableDocumentV3) {
|
|
157
178
|
const memberships = intersectMembershipIds(doc, this.context.desired);
|
|
158
179
|
const coversDesiredMembership = hasMembershipIds(memberships);
|
|
159
|
-
const coversEventOnlyTable =
|
|
160
|
-
!this.desiredHasMembership() && this.context.desired.triggersEvent && !this.retainedEventOnlyTable;
|
|
161
180
|
|
|
162
181
|
this.recordCoverage(doc, memberships);
|
|
163
|
-
this.planNarrowingUpdate(doc, memberships, coversDesiredMembership
|
|
182
|
+
this.planNarrowingUpdate(doc, memberships, coversDesiredMembership);
|
|
164
183
|
|
|
165
|
-
if (coversDesiredMembership
|
|
166
|
-
this.retainedEventOnlyTable ||= coversEventOnlyTable;
|
|
167
|
-
this.retainedDocIds.add(doc._id.toHexString());
|
|
184
|
+
if (coversDesiredMembership) {
|
|
168
185
|
this.tables.push(this.sourceTableFor(doc, memberships));
|
|
169
186
|
}
|
|
170
187
|
}
|
|
171
188
|
|
|
172
|
-
private desiredHasMembership() {
|
|
173
|
-
const { desired } = this.context;
|
|
174
|
-
return desired.bucketSourceById.size > 0 || desired.parameterLookupSourceById.size > 0;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
189
|
private recordCoverage(doc: SourceTableDocumentV3, memberships: SourceTableMembershipIds) {
|
|
178
190
|
this.addCoverage(doc, 'bucket data source', this.coveredBucketDataSourceIds, memberships.bucketDataSourceIds);
|
|
179
191
|
this.addCoverage(
|
|
@@ -182,12 +194,14 @@ class SourceTableReconciliationPlanner {
|
|
|
182
194
|
this.coveredParameterLookupSourceIds,
|
|
183
195
|
memberships.parameterLookupSourceIds
|
|
184
196
|
);
|
|
197
|
+
this.addCoverage(doc, 'event definition', this.coveredEventDefinitionIds, memberships.eventDefinitionIds);
|
|
185
198
|
}
|
|
186
199
|
|
|
187
200
|
// Membership sets must be pairwise disjoint across the docs of one physical table:
|
|
188
|
-
// each desired id is covered by exactly one doc, so each definition
|
|
189
|
-
//
|
|
190
|
-
//
|
|
201
|
+
// each desired id is covered by exactly one doc, so each definition is evaluated through
|
|
202
|
+
// only one SourceTable for a physical row. Different docs may own different event ids.
|
|
203
|
+
// The algorithm maintains this (new docs only get uncovered ids, narrowing only removes ids) -
|
|
204
|
+
// overlap means the persisted state is corrupt.
|
|
191
205
|
private addCoverage(doc: SourceTableDocumentV3, kind: string, covered: Set<string>, ids: string[]) {
|
|
192
206
|
const { schema, name } = this.context.identity;
|
|
193
207
|
for (const id of ids) {
|
|
@@ -203,11 +217,9 @@ class SourceTableReconciliationPlanner {
|
|
|
203
217
|
private planNarrowingUpdate(
|
|
204
218
|
doc: SourceTableDocumentV3,
|
|
205
219
|
memberships: SourceTableMembershipIds,
|
|
206
|
-
coversDesiredMembership: boolean
|
|
207
|
-
coversEventOnlyTable: boolean
|
|
220
|
+
coversDesiredMembership: boolean
|
|
208
221
|
) {
|
|
209
|
-
const shouldNarrow =
|
|
210
|
-
(coversDesiredMembership || coversEventOnlyTable) && !doc.snapshot_done && !sameMembershipIds(doc, memberships);
|
|
222
|
+
const shouldNarrow = coversDesiredMembership && !doc.snapshot_done && !sameMembershipIds(doc, memberships);
|
|
211
223
|
|
|
212
224
|
if (!shouldNarrow) {
|
|
213
225
|
return;
|
|
@@ -227,9 +239,12 @@ class SourceTableReconciliationPlanner {
|
|
|
227
239
|
),
|
|
228
240
|
parameterLookupSourceIds: [...desired.parameterLookupSourceById.keys()].filter(
|
|
229
241
|
(id) => !this.coveredParameterLookupSourceIds.has(id)
|
|
242
|
+
),
|
|
243
|
+
eventDefinitionIds: [...desired.eventDefinitionById.keys()].filter(
|
|
244
|
+
(id) => !this.coveredEventDefinitionIds.has(id)
|
|
230
245
|
)
|
|
231
246
|
};
|
|
232
|
-
if (hasMembershipIds(uncovered)
|
|
247
|
+
if (hasMembershipIds(uncovered)) {
|
|
233
248
|
return uncovered;
|
|
234
249
|
}
|
|
235
250
|
return null;
|
|
@@ -237,39 +252,23 @@ class SourceTableReconciliationPlanner {
|
|
|
237
252
|
|
|
238
253
|
private sourceTableFor(doc: SourceTableDocumentV3, memberships: SourceTableMembershipIds): storage.SourceTable {
|
|
239
254
|
const { connectionTag, syncConfig, mapping, desired, storeCurrentData } = this.context;
|
|
240
|
-
const
|
|
255
|
+
const built = sourceTableFromDocument(
|
|
241
256
|
doc,
|
|
242
257
|
connectionTag,
|
|
243
258
|
syncConfig,
|
|
244
259
|
mapping,
|
|
260
|
+
desired.eventDefinitionById,
|
|
245
261
|
matchingSourcesFor(desired, memberships),
|
|
246
262
|
memberships
|
|
247
263
|
);
|
|
264
|
+
// Use any metadata update returned by the reconciler.
|
|
265
|
+
const resolved = this.compatibleTableFor(doc);
|
|
266
|
+
const table = resolved == null ? built : built.withSourceMetadata(resolved.sourceMetadata);
|
|
248
267
|
table.storeCurrentData = storeCurrentData;
|
|
249
268
|
return table;
|
|
250
269
|
}
|
|
251
270
|
}
|
|
252
271
|
|
|
253
|
-
function sameReplicaIdColumns(left: ReplicaIdColumn[] | undefined, right: ReplicaIdColumn[]) {
|
|
254
|
-
return (
|
|
255
|
-
left != null &&
|
|
256
|
-
left.length == right.length &&
|
|
257
|
-
left.every(
|
|
258
|
-
(column, index) =>
|
|
259
|
-
column.name == right[index].name && column.type == right[index].type && column.type_oid == right[index].type_oid
|
|
260
|
-
)
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
function matchingSourceTableIdentity(doc: SourceTableDocumentV3, identity: SourceTableIdentity) {
|
|
265
|
-
return (
|
|
266
|
-
doc.schema_name == identity.schema &&
|
|
267
|
-
doc.table_name == identity.name &&
|
|
268
|
-
(identity.objectId == null || doc.relation_id == identity.objectId) &&
|
|
269
|
-
sameReplicaIdColumns(doc.replica_id_columns, identity.replicaIdColumns)
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
272
|
export function overlappingSourceTableFilter(
|
|
274
273
|
connectionId: number,
|
|
275
274
|
identity: Pick<SourceTableIdentity, 'schema' | 'name' | 'objectId'>
|
|
@@ -290,7 +289,16 @@ export function createNewSourceTable(
|
|
|
290
289
|
memberships: SourceTableMembershipIds,
|
|
291
290
|
context: SourceTableReconciliationContext
|
|
292
291
|
): NewSourceTable {
|
|
293
|
-
const {
|
|
292
|
+
const {
|
|
293
|
+
connectionId,
|
|
294
|
+
connectionTag,
|
|
295
|
+
identity,
|
|
296
|
+
syncConfig,
|
|
297
|
+
mapping,
|
|
298
|
+
desired,
|
|
299
|
+
storeCurrentData,
|
|
300
|
+
newTableSourceMetadata
|
|
301
|
+
} = context;
|
|
294
302
|
const doc: SourceTableDocumentV3 = {
|
|
295
303
|
_id: id,
|
|
296
304
|
connection_id: connectionId,
|
|
@@ -301,13 +309,17 @@ export function createNewSourceTable(
|
|
|
301
309
|
snapshot_done: false,
|
|
302
310
|
snapshot_status: undefined,
|
|
303
311
|
bucket_data_source_ids: memberships.bucketDataSourceIds,
|
|
304
|
-
parameter_lookup_source_ids: memberships.parameterLookupSourceIds
|
|
312
|
+
parameter_lookup_source_ids: memberships.parameterLookupSourceIds,
|
|
313
|
+
event_definition_ids: memberships.eventDefinitionIds,
|
|
314
|
+
// Records created together share the same source metadata.
|
|
315
|
+
source_metadata: newTableSourceMetadata
|
|
305
316
|
};
|
|
306
317
|
const table = sourceTableFromDocument(
|
|
307
318
|
doc,
|
|
308
319
|
connectionTag,
|
|
309
320
|
syncConfig,
|
|
310
321
|
mapping,
|
|
322
|
+
desired.eventDefinitionById,
|
|
311
323
|
matchingSourcesFor(desired, memberships),
|
|
312
324
|
memberships
|
|
313
325
|
);
|
|
@@ -316,17 +328,6 @@ export function createNewSourceTable(
|
|
|
316
328
|
return { doc, table };
|
|
317
329
|
}
|
|
318
330
|
|
|
319
|
-
export function designateEventCarrier(tables: storage.SourceTable[], triggersEvent: boolean) {
|
|
320
|
-
if (!triggersEvent) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const eventCarrier = tables.find((table) => table.snapshotComplete) ?? tables[0];
|
|
325
|
-
for (const table of tables) {
|
|
326
|
-
table.syncEvent = table === eventCarrier;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
331
|
function sourceTableMembershipsFromDocument(
|
|
331
332
|
doc: SourceTableDocumentV3,
|
|
332
333
|
syncConfig: HydratedSyncConfig,
|
|
@@ -350,6 +351,7 @@ export function sourceTableFromDocument(
|
|
|
350
351
|
connectionTag: string,
|
|
351
352
|
syncConfig: HydratedSyncConfig,
|
|
352
353
|
mapping: BucketDefinitionMapping,
|
|
354
|
+
eventById: ReadonlyMap<EventDefinitionId, HydratedEventDescriptor>,
|
|
353
355
|
memberships?: MatchingSources,
|
|
354
356
|
membershipIds?: SourceTableMembershipIds
|
|
355
357
|
): storage.SourceTable {
|
|
@@ -358,7 +360,8 @@ export function sourceTableFromDocument(
|
|
|
358
360
|
bucketDataSourceIds: resolvedMemberships.bucketDataSources.map((source) => mapping.bucketSourceId(source)),
|
|
359
361
|
parameterLookupSourceIds: resolvedMemberships.parameterLookupSources.map((source) =>
|
|
360
362
|
mapping.parameterLookupId(source)
|
|
361
|
-
)
|
|
363
|
+
),
|
|
364
|
+
eventDefinitionIds: doc.event_definition_ids.filter((id) => eventById.has(id))
|
|
362
365
|
};
|
|
363
366
|
const table = new storage.SourceTable({
|
|
364
367
|
id: doc._id,
|
|
@@ -368,18 +371,21 @@ export function sourceTableFromDocument(
|
|
|
368
371
|
name: doc.table_name
|
|
369
372
|
},
|
|
370
373
|
objectId: doc.relation_id,
|
|
371
|
-
replicaIdColumns:
|
|
372
|
-
(
|
|
373
|
-
|
|
374
|
+
replicaIdColumns:
|
|
375
|
+
doc.replica_id_columns?.map(
|
|
376
|
+
(column) => ({ name: column.name, typeId: column.type_oid, type: column.type }) satisfies ColumnDescriptor
|
|
377
|
+
) ?? [],
|
|
374
378
|
snapshotComplete: doc.snapshot_done,
|
|
375
379
|
bucketDataSources: resolvedMemberships.bucketDataSources,
|
|
376
380
|
parameterLookupSources: resolvedMemberships.parameterLookupSources,
|
|
377
381
|
bucketDataSourceIds: new Set(resolvedMembershipIds.bucketDataSourceIds),
|
|
378
|
-
parameterLookupSourceIds: new Set(resolvedMembershipIds.parameterLookupSourceIds)
|
|
382
|
+
parameterLookupSourceIds: new Set(resolvedMembershipIds.parameterLookupSourceIds),
|
|
383
|
+
eventDefinitionIds: new Set(resolvedMembershipIds.eventDefinitionIds),
|
|
384
|
+
sourceMetadata: doc.source_metadata ?? null
|
|
379
385
|
});
|
|
380
386
|
table.syncData = table.bucketDataSources.length > 0;
|
|
381
387
|
table.syncParameters = table.parameterLookupSources.length > 0;
|
|
382
|
-
table.syncEvent =
|
|
388
|
+
table.syncEvent = table.eventDefinitionIds!.size > 0;
|
|
383
389
|
table.snapshotStatus =
|
|
384
390
|
doc.snapshot_status == null
|
|
385
391
|
? undefined
|
|
@@ -15,11 +15,13 @@ export { loadBucketDataDocument, serializeBucketData } from './implementation/v3
|
|
|
15
15
|
export {
|
|
16
16
|
BucketDataDocumentV3,
|
|
17
17
|
BucketOperation,
|
|
18
|
+
ObjectStorageUsageDocument,
|
|
18
19
|
RecordedLookupV3,
|
|
19
20
|
ReplicationStreamDocumentV3,
|
|
20
21
|
SyncConfigDefinition,
|
|
21
22
|
SyncRuleConfigStateV3,
|
|
22
23
|
taggedBucketParameterDocumentToTagged
|
|
23
24
|
} from './implementation/v3/models.js';
|
|
25
|
+
export * from './implementation/v3/object-storage/ObjectStorageUsage.js';
|
|
24
26
|
export * from './MongoBucketStorage.js';
|
|
25
27
|
export * from './MongoReportStorage.js';
|
package/src/types/types.ts
CHANGED
|
@@ -3,6 +3,20 @@ import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
|
3
3
|
import * as service_types from '@powersync/service-types';
|
|
4
4
|
import * as t from 'ts-codec';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* AWS defaults mode, as used by the AWS SDK.
|
|
8
|
+
*
|
|
9
|
+
* This is the baseline for the object storage request timeouts, so that they do not each need to be
|
|
10
|
+
* configured individually.
|
|
11
|
+
*
|
|
12
|
+
* The SDK's `legacy` and `auto` modes are not selectable here: `legacy` defines no timeouts at all,
|
|
13
|
+
* and `auto` makes the timeouts depend on where the process happens to be running. Both are still
|
|
14
|
+
* handled when they come from the AWS environment, and are then treated as `standard`.
|
|
15
|
+
*/
|
|
16
|
+
export const S3DefaultsMode = service_types.enumLiteral('standard', 'in-region', 'cross-region', 'mobile');
|
|
17
|
+
|
|
18
|
+
export type S3DefaultsMode = t.Encoded<typeof S3DefaultsMode>;
|
|
19
|
+
|
|
6
20
|
const S3ObjectStorageConfig = t.object({
|
|
7
21
|
type: t.literal('s3'),
|
|
8
22
|
bucket: t.string,
|
|
@@ -13,21 +27,24 @@ const S3ObjectStorageConfig = t.object({
|
|
|
13
27
|
access_key_id: t.string.optional(),
|
|
14
28
|
secret_access_key: t.string.optional(),
|
|
15
29
|
concurrency_limit: t.number.optional(),
|
|
30
|
+
// Defaults to the AWS_DEFAULTS_MODE environment variable.
|
|
31
|
+
defaults_mode: S3DefaultsMode.optional(),
|
|
16
32
|
// Chunks whose BSON-serialized size falls below this byte threshold
|
|
17
33
|
// stay inline in MongoDB instead of being offloaded to S3. Default 1024.
|
|
18
34
|
inline_threshold_bytes: t.number.optional()
|
|
19
35
|
});
|
|
20
36
|
|
|
21
|
-
export const MongoStorageReadPreference =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
export const MongoStorageReadPreference = service_types.enumLiteral(
|
|
38
|
+
'primary',
|
|
39
|
+
'primaryPreferred',
|
|
40
|
+
'secondary',
|
|
41
|
+
'secondaryPreferred',
|
|
42
|
+
'nearest'
|
|
43
|
+
);
|
|
27
44
|
|
|
28
45
|
export type MongoStorageReadPreference = t.Encoded<typeof MongoStorageReadPreference>;
|
|
29
46
|
|
|
30
|
-
export const MongoStorageConfig = lib_mongo.BaseMongoConfig.and(
|
|
47
|
+
export const MongoStorageConfig = service_types.configFile.BaseStorageConfig.and(lib_mongo.BaseMongoConfig).and(
|
|
31
48
|
t.object({
|
|
32
49
|
/**
|
|
33
50
|
* Read preference for bulk checksum and bucket data reads.
|
package/src/utils/util.ts
CHANGED
|
@@ -121,7 +121,9 @@ export function mapOpEntry(row: BucketDataDoc): utils.OplogEntry {
|
|
|
121
121
|
object_type: row.table,
|
|
122
122
|
object_id: row.row_id,
|
|
123
123
|
checksum: Number(row.checksum),
|
|
124
|
-
|
|
124
|
+
// Storage V3 always persists this during replication. The fallback serves V1 data
|
|
125
|
+
// written before subkey persistence was introduced.
|
|
126
|
+
subkey: row.subkey ?? replicaIdToSubkey(row.source_table!, row.source_key!),
|
|
125
127
|
data: row.data
|
|
126
128
|
};
|
|
127
129
|
} else {
|
|
@@ -75,6 +75,31 @@ exports[`Mongo Sync Bucket Storage - Data - v3 > (insert, delete, insert), (dele
|
|
|
75
75
|
]
|
|
76
76
|
`;
|
|
77
77
|
|
|
78
|
+
exports[`Mongo Sync Bucket Storage - Data - v4 > (insert, delete, insert), (delete) 1`] = `
|
|
79
|
+
[
|
|
80
|
+
{
|
|
81
|
+
"checksum": 2871785649,
|
|
82
|
+
"object_id": "test1",
|
|
83
|
+
"op": "PUT",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"checksum": 2872534815,
|
|
87
|
+
"object_id": "test1",
|
|
88
|
+
"op": "REMOVE",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"checksum": 2871785649,
|
|
92
|
+
"object_id": "test1",
|
|
93
|
+
"op": "PUT",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"checksum": 2872534815,
|
|
97
|
+
"object_id": "test1",
|
|
98
|
+
"op": "REMOVE",
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
`;
|
|
102
|
+
|
|
78
103
|
exports[`Mongo Sync Bucket Storage - split buckets > (insert, delete, insert), (delete) 1`] = `
|
|
79
104
|
[
|
|
80
105
|
{
|