@powersync/service-core 1.22.0 → 1.23.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 +17 -0
- package/dist/api/RouteAPI.d.ts +2 -2
- package/dist/api/diagnostics.d.ts +12 -1
- package/dist/api/diagnostics.js +34 -22
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/entry/commands/compact-action.js +1 -1
- package/dist/entry/commands/compact-action.js.map +1 -1
- package/dist/modules/AbstractModule.d.ts +6 -2
- package/dist/modules/AbstractModule.js.map +1 -1
- package/dist/replication/AbstractReplicator.d.ts +2 -2
- package/dist/replication/AbstractReplicator.js +24 -23
- package/dist/replication/AbstractReplicator.js.map +1 -1
- package/dist/replication/ReplicationModule.d.ts +3 -3
- package/dist/replication/ReplicationModule.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +12 -12
- package/dist/routes/endpoints/admin.d.ts +24 -24
- package/dist/routes/endpoints/admin.js +44 -34
- package/dist/routes/endpoints/admin.js.map +1 -1
- package/dist/routes/endpoints/checkpointing.js +1 -1
- package/dist/routes/endpoints/checkpointing.js.map +1 -1
- package/dist/routes/endpoints/socket-route.js +1 -1
- package/dist/routes/endpoints/socket-route.js.map +1 -1
- package/dist/routes/endpoints/sync-rules.js +22 -13
- package/dist/routes/endpoints/sync-rules.js.map +1 -1
- package/dist/routes/endpoints/sync-stream.js +1 -1
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/runner/teardown.js +8 -8
- package/dist/runner/teardown.js.map +1 -1
- package/dist/storage/BucketStorageBatch.d.ts +0 -6
- package/dist/storage/BucketStorageBatch.js.map +1 -1
- package/dist/storage/BucketStorageFactory.d.ts +22 -27
- package/dist/storage/BucketStorageFactory.js +4 -18
- package/dist/storage/BucketStorageFactory.js.map +1 -1
- package/dist/storage/ParsedSyncConfigSet.d.ts +23 -0
- package/dist/storage/ParsedSyncConfigSet.js +2 -0
- package/dist/storage/ParsedSyncConfigSet.js.map +1 -0
- package/dist/storage/PersistedReplicationStream.d.ts +32 -0
- package/dist/storage/PersistedReplicationStream.js +26 -0
- package/dist/storage/PersistedReplicationStream.js.map +1 -0
- package/dist/storage/PersistedSyncConfigContent.d.ts +60 -0
- package/dist/storage/PersistedSyncConfigContent.js +123 -0
- package/dist/storage/PersistedSyncConfigContent.js.map +1 -0
- package/dist/storage/PersistedSyncConfigStatus.d.ts +13 -0
- package/dist/storage/PersistedSyncConfigStatus.js +2 -0
- package/dist/storage/PersistedSyncConfigStatus.js.map +1 -0
- package/dist/storage/SourceTable.d.ts +9 -3
- package/dist/storage/SourceTable.js +13 -3
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/SyncRulesBucketStorage.d.ts +59 -16
- package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +117 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js +277 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
- package/dist/storage/implementation/IncrementalReprocessingCompatibility.d.ts +6 -0
- package/dist/storage/implementation/IncrementalReprocessingCompatibility.js +31 -0
- package/dist/storage/implementation/IncrementalReprocessingCompatibility.js.map +1 -0
- package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.d.ts +17 -0
- package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.js +62 -0
- package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.js.map +1 -0
- package/dist/storage/implementation/ReplicationStreamStorageIds.d.ts +15 -0
- package/dist/storage/implementation/ReplicationStreamStorageIds.js +20 -0
- package/dist/storage/implementation/ReplicationStreamStorageIds.js.map +1 -0
- package/dist/storage/storage-index.d.ts +8 -1
- package/dist/storage/storage-index.js +8 -1
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/sync/sync.js +5 -0
- package/dist/sync/sync.js.map +1 -1
- package/dist/util/checkpointing.js +1 -1
- package/dist/util/checkpointing.js.map +1 -1
- package/dist/util/config/types.d.ts +1 -1
- package/dist/util/lsn.d.ts +4 -0
- package/dist/util/lsn.js +10 -0
- package/dist/util/lsn.js.map +1 -1
- package/package.json +6 -7
- package/src/api/RouteAPI.ts +2 -2
- package/src/api/diagnostics.ts +43 -26
- package/src/entry/commands/compact-action.ts +1 -1
- package/src/modules/AbstractModule.ts +6 -2
- package/src/replication/AbstractReplicator.ts +27 -26
- package/src/replication/ReplicationModule.ts +2 -3
- package/src/routes/endpoints/admin.ts +70 -49
- package/src/routes/endpoints/checkpointing.ts +1 -1
- package/src/routes/endpoints/socket-route.ts +1 -1
- package/src/routes/endpoints/sync-rules.ts +23 -13
- package/src/routes/endpoints/sync-stream.ts +1 -1
- package/src/runner/teardown.ts +8 -8
- package/src/storage/BucketStorageBatch.ts +0 -7
- package/src/storage/BucketStorageFactory.ts +30 -39
- package/src/storage/ParsedSyncConfigSet.ts +25 -0
- package/src/storage/PersistedReplicationStream.ts +55 -0
- package/src/storage/PersistedSyncConfigContent.ts +188 -0
- package/src/storage/PersistedSyncConfigStatus.ts +14 -0
- package/src/storage/SourceTable.ts +20 -3
- package/src/storage/SyncRulesBucketStorage.ts +64 -16
- package/src/storage/implementation/BucketDefinitionMapping.ts +436 -0
- package/src/storage/implementation/IncrementalReprocessingCompatibility.ts +41 -0
- package/src/storage/implementation/IncrementalReprocessingSyncConfigLog.ts +109 -0
- package/src/storage/implementation/ReplicationStreamStorageIds.ts +21 -0
- package/src/storage/storage-index.ts +9 -1
- package/src/sync/sync.ts +5 -0
- package/src/util/checkpointing.ts +1 -1
- package/src/util/config/types.ts +1 -1
- package/src/util/lsn.ts +9 -0
- package/test/src/diagnostics.test.ts +61 -34
- package/test/src/module-loader.test.ts +33 -1
- package/test/src/routes/admin.test.ts +180 -18
- package/test/src/routes/mocks.ts +6 -2
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/PersistedSyncRulesContent.d.ts +0 -59
- package/dist/storage/PersistedSyncRulesContent.js +0 -100
- package/dist/storage/PersistedSyncRulesContent.js.map +0 -1
- package/src/storage/PersistedSyncRulesContent.ts +0 -174
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { DataSourceConfig } from '@powersync/service-types/dist/config/PowerSyncConfig.js';
|
|
2
1
|
import * as t from 'ts-codec';
|
|
3
2
|
|
|
4
3
|
import { schema } from '@powersync/lib-services-framework';
|
|
@@ -18,7 +17,7 @@ export interface ConnectionTestResult {
|
|
|
18
17
|
/**
|
|
19
18
|
* Provides a common interface for testing the connection to a DataSource.
|
|
20
19
|
*/
|
|
21
|
-
export interface ConnectionTester<TConfig extends DataSourceConfig> {
|
|
20
|
+
export interface ConnectionTester<TConfig extends types.configFile.DataSourceConfig> {
|
|
22
21
|
/**
|
|
23
22
|
* Confirm if a connection can be established to the datasource for the provided datasource configuration
|
|
24
23
|
* @param config
|
|
@@ -35,7 +34,7 @@ export interface ReplicationModuleOptions extends modules.AbstractModuleOptions
|
|
|
35
34
|
* A replication module describes all the functionality that PowerSync requires to
|
|
36
35
|
* replicate data from a DataSource. Whenever a new data source is added to powersync this class should be extended.
|
|
37
36
|
*/
|
|
38
|
-
export abstract class ReplicationModule<TConfig extends DataSourceConfig>
|
|
37
|
+
export abstract class ReplicationModule<TConfig extends types.configFile.DataSourceConfig>
|
|
39
38
|
extends modules.AbstractModule
|
|
40
39
|
implements ConnectionTester<TConfig>
|
|
41
40
|
{
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import * as sqlite from 'node:sqlite';
|
|
2
2
|
|
|
3
3
|
import { ErrorCode, errors, router, schema } from '@powersync/lib-services-framework';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
HydratedSyncConfig,
|
|
6
|
+
nodeSqlite,
|
|
7
|
+
SourceSchema,
|
|
8
|
+
SqlSyncRules,
|
|
9
|
+
StaticSchema
|
|
10
|
+
} from '@powersync/service-sync-rules';
|
|
5
11
|
import { internal_routes } from '@powersync/service-types';
|
|
6
12
|
|
|
7
13
|
import { DEFAULT_HYDRATION_STATE } from '@powersync/service-sync-rules';
|
|
@@ -65,20 +71,35 @@ export const diagnostics = routeDefinition({
|
|
|
65
71
|
const {
|
|
66
72
|
storageEngine: { activeBucketStorage }
|
|
67
73
|
} = service_context;
|
|
68
|
-
const active = await activeBucketStorage.
|
|
69
|
-
const
|
|
74
|
+
const active = await activeBucketStorage.getActiveSyncConfig();
|
|
75
|
+
const deploying = await activeBucketStorage.getDeployingSyncConfig();
|
|
70
76
|
|
|
71
|
-
const active_status = await api.getSyncRulesStatus(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
const active_status = await api.getSyncRulesStatus(
|
|
78
|
+
apiHandler,
|
|
79
|
+
active?.content ?? null,
|
|
80
|
+
{
|
|
81
|
+
include_content,
|
|
82
|
+
check_connection: status.connected,
|
|
83
|
+
live_status: true,
|
|
84
|
+
active: true
|
|
85
|
+
},
|
|
86
|
+
active?.storage
|
|
87
|
+
);
|
|
76
88
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
89
|
+
const deploying_status =
|
|
90
|
+
deploying == null
|
|
91
|
+
? undefined
|
|
92
|
+
: await api.getSyncRulesStatus(
|
|
93
|
+
apiHandler,
|
|
94
|
+
deploying.content,
|
|
95
|
+
{
|
|
96
|
+
include_content,
|
|
97
|
+
check_connection: status.connected,
|
|
98
|
+
live_status: true,
|
|
99
|
+
active: false
|
|
100
|
+
},
|
|
101
|
+
deploying.storage
|
|
102
|
+
);
|
|
82
103
|
|
|
83
104
|
return internal_routes.DiagnosticsResponse.encode({
|
|
84
105
|
connections: [
|
|
@@ -89,7 +110,7 @@ export const diagnostics = routeDefinition({
|
|
|
89
110
|
}
|
|
90
111
|
],
|
|
91
112
|
active_sync_rules: active_status,
|
|
92
|
-
deploying_sync_rules:
|
|
113
|
+
deploying_sync_rules: deploying_status
|
|
93
114
|
});
|
|
94
115
|
}
|
|
95
116
|
});
|
|
@@ -119,12 +140,16 @@ export const reprocess = routeDefinition({
|
|
|
119
140
|
storageEngine: { activeBucketStorage }
|
|
120
141
|
} = service_context;
|
|
121
142
|
const apiHandler = service_context.routerEngine.getAPI();
|
|
122
|
-
const next = await activeBucketStorage.
|
|
143
|
+
const next = await activeBucketStorage.getDeployingSyncConfig();
|
|
123
144
|
if (next != null) {
|
|
124
|
-
throw new
|
|
145
|
+
throw new errors.ServiceError({
|
|
146
|
+
status: 409,
|
|
147
|
+
code: ErrorCode.PSYNC_S4106,
|
|
148
|
+
description: 'Busy processing sync config - cannot reprocess'
|
|
149
|
+
});
|
|
125
150
|
}
|
|
126
151
|
|
|
127
|
-
const active = await activeBucketStorage.
|
|
152
|
+
const active = await activeBucketStorage.getActiveSyncConfig();
|
|
128
153
|
if (active == null) {
|
|
129
154
|
throw new errors.ServiceError({
|
|
130
155
|
status: 422,
|
|
@@ -132,13 +157,12 @@ export const reprocess = routeDefinition({
|
|
|
132
157
|
description: 'No active sync config'
|
|
133
158
|
});
|
|
134
159
|
}
|
|
135
|
-
|
|
136
160
|
// There are some differences between this and using asUpdateOptions():
|
|
137
161
|
// 1. This always re-parses the source YAML. If there are changes to the sync stream compiler, that can affect the sync plan.
|
|
138
162
|
// 2. If the source does not set the storage version, this will update it do the current version.
|
|
139
163
|
// We can consider tweaking this behavior in the future.
|
|
140
164
|
const new_rules = await activeBucketStorage.updateSyncRules(
|
|
141
|
-
storage.updateSyncRulesFromYaml(active.
|
|
165
|
+
storage.updateSyncRulesFromYaml(active.content.sync_rules_content, {
|
|
142
166
|
// This sync config already passed validation. But if the config is not valid anymore due
|
|
143
167
|
// to a service change, we do want to report the error here.
|
|
144
168
|
validate: true
|
|
@@ -153,44 +177,47 @@ export const reprocess = routeDefinition({
|
|
|
153
177
|
// Previously the connection was asserted with `!`
|
|
154
178
|
tag: baseConfig.tag,
|
|
155
179
|
id: baseConfig.id,
|
|
156
|
-
slot_name: new_rules.
|
|
180
|
+
slot_name: new_rules.replicationStreamName
|
|
157
181
|
}
|
|
158
182
|
]
|
|
159
183
|
});
|
|
160
184
|
}
|
|
161
185
|
});
|
|
162
186
|
|
|
163
|
-
class FakeSyncRulesContentForValidation extends storage.
|
|
187
|
+
class FakeSyncRulesContentForValidation extends storage.PersistedSyncConfigContent {
|
|
164
188
|
constructor(
|
|
165
189
|
private readonly apiHandler: api.RouteAPI,
|
|
166
190
|
private readonly schema: SourceSchema,
|
|
167
|
-
data: storage.
|
|
191
|
+
data: storage.PersistedSyncConfigContentData
|
|
168
192
|
) {
|
|
169
193
|
super(data);
|
|
170
194
|
}
|
|
171
195
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
196
|
+
parsed(options: storage.ParseSyncConfigOptions): storage.ParsedSyncConfigSet {
|
|
197
|
+
const syncConfig = SqlSyncRules.fromYaml(this.sync_rules_content, {
|
|
198
|
+
...this.apiHandler.getParseSyncRulesOptions(),
|
|
199
|
+
schema: this.schema
|
|
200
|
+
});
|
|
177
201
|
|
|
178
|
-
|
|
202
|
+
let hydrated: HydratedSyncConfig | undefined;
|
|
179
203
|
return {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
schema: this.schema
|
|
184
|
-
}),
|
|
204
|
+
replicationStreamId: this.replicationStreamId,
|
|
205
|
+
replicationStreamName: this.replicationStreamName,
|
|
206
|
+
syncConfigs: [syncConfig],
|
|
185
207
|
hydrationState: DEFAULT_HYDRATION_STATE,
|
|
186
|
-
hydratedSyncConfig() {
|
|
187
|
-
|
|
208
|
+
get hydratedSyncConfig(): HydratedSyncConfig {
|
|
209
|
+
hydrated ??= syncConfig.config.hydrate({
|
|
188
210
|
hydrationState: DEFAULT_HYDRATION_STATE,
|
|
189
211
|
sqlite: nodeSqlite(sqlite)
|
|
190
212
|
});
|
|
213
|
+
return hydrated;
|
|
191
214
|
}
|
|
192
215
|
};
|
|
193
216
|
}
|
|
217
|
+
|
|
218
|
+
async getSyncConfigStatus(): Promise<storage.PersistedSyncConfigStatus | null> {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
194
221
|
}
|
|
195
222
|
|
|
196
223
|
export const validate = routeDefinition({
|
|
@@ -210,10 +237,8 @@ export const validate = routeDefinition({
|
|
|
210
237
|
|
|
211
238
|
const sync_rules = new FakeSyncRulesContentForValidation(apiHandler, schema, {
|
|
212
239
|
// Dummy values
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
active: false,
|
|
216
|
-
last_checkpoint_lsn: '',
|
|
240
|
+
replicationStreamId: 0,
|
|
241
|
+
replicationStreamName: '',
|
|
217
242
|
storageVersion: storage.LEGACY_STORAGE_VERSION,
|
|
218
243
|
sync_rules_content: content,
|
|
219
244
|
compiled_plan: null
|
|
@@ -227,16 +252,12 @@ export const validate = routeDefinition({
|
|
|
227
252
|
});
|
|
228
253
|
}
|
|
229
254
|
|
|
230
|
-
const status = (await api.getSyncRulesStatus(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
check_connection: connectionStatus.connected,
|
|
237
|
-
live_status: false
|
|
238
|
-
}
|
|
239
|
-
))!;
|
|
255
|
+
const status = (await api.getSyncRulesStatus(apiHandler, sync_rules, {
|
|
256
|
+
include_content: false,
|
|
257
|
+
check_connection: connectionStatus.connected,
|
|
258
|
+
live_status: false,
|
|
259
|
+
active: false
|
|
260
|
+
}))!;
|
|
240
261
|
|
|
241
262
|
if (connectionStatus == null) {
|
|
242
263
|
status.errors.push({ level: 'fatal', message: 'No connection configured', ts: new Date().toISOString() });
|
|
@@ -30,7 +30,7 @@ export const writeCheckpoint = routeDefinition({
|
|
|
30
30
|
|
|
31
31
|
logger.info(`Waiting for LSN checkpoint: ${head}`);
|
|
32
32
|
while (Date.now() - start < timeout) {
|
|
33
|
-
const bucketStorage = await service_context.storageEngine.activeBucketStorage.
|
|
33
|
+
const bucketStorage = (await service_context.storageEngine.activeBucketStorage.getActiveSyncConfig())?.storage;
|
|
34
34
|
const cp = await bucketStorage?.getCheckpoint();
|
|
35
35
|
if (cp == null) {
|
|
36
36
|
throw new Error('No sync config available');
|
|
@@ -71,7 +71,7 @@ export const syncStreamReactive: SocketRouteGenerator = (router) =>
|
|
|
71
71
|
storageEngine: { activeBucketStorage }
|
|
72
72
|
} = service_context;
|
|
73
73
|
|
|
74
|
-
const bucketStorage = await activeBucketStorage.
|
|
74
|
+
const bucketStorage = (await activeBucketStorage.getActiveSyncConfig())?.storage;
|
|
75
75
|
if (bucketStorage == null) {
|
|
76
76
|
responder.onError(
|
|
77
77
|
new errors.ServiceError({
|
|
@@ -73,7 +73,7 @@ export const deploySyncRules = routeDefinition({
|
|
|
73
73
|
const sync_rules = await storageEngine.activeBucketStorage.updateSyncRules(updateSyncRulesFromConfig(syncConfig));
|
|
74
74
|
|
|
75
75
|
return {
|
|
76
|
-
slot_name: sync_rules.
|
|
76
|
+
slot_name: sync_rules.replicationStreamName
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
});
|
|
@@ -110,8 +110,8 @@ export const currentSyncRules = routeDefinition({
|
|
|
110
110
|
storageEngine: { activeBucketStorage }
|
|
111
111
|
} = service_context;
|
|
112
112
|
|
|
113
|
-
const
|
|
114
|
-
if (!
|
|
113
|
+
const active = await activeBucketStorage.getActiveSyncConfig();
|
|
114
|
+
if (!active) {
|
|
115
115
|
throw new errors.ServiceError({
|
|
116
116
|
status: 422,
|
|
117
117
|
code: ErrorCode.PSYNC_S4104,
|
|
@@ -119,15 +119,16 @@ export const currentSyncRules = routeDefinition({
|
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
const sync_rules = active.content;
|
|
122
123
|
const apiHandler = service_context.routerEngine.getAPI();
|
|
123
124
|
const info = await debugSyncRules(apiHandler, sync_rules.sync_rules_content);
|
|
124
|
-
const next = await activeBucketStorage.
|
|
125
|
+
const next = await activeBucketStorage.getDeployingSyncConfig();
|
|
125
126
|
|
|
126
|
-
const next_info = next ? await debugSyncRules(apiHandler, next.sync_rules_content) : null;
|
|
127
|
+
const next_info = next ? await debugSyncRules(apiHandler, next.content.sync_rules_content) : null;
|
|
127
128
|
|
|
128
129
|
const response = {
|
|
129
130
|
current: {
|
|
130
|
-
slot_name: sync_rules.
|
|
131
|
+
slot_name: sync_rules.replicationStreamName,
|
|
131
132
|
content: sync_rules.sync_rules_content,
|
|
132
133
|
...info
|
|
133
134
|
},
|
|
@@ -135,8 +136,8 @@ export const currentSyncRules = routeDefinition({
|
|
|
135
136
|
next == null
|
|
136
137
|
? null
|
|
137
138
|
: {
|
|
138
|
-
slot_name: next.
|
|
139
|
-
content: next.sync_rules_content,
|
|
139
|
+
slot_name: next.content.replicationStreamName,
|
|
140
|
+
content: next.content.sync_rules_content,
|
|
140
141
|
...next_info
|
|
141
142
|
}
|
|
142
143
|
};
|
|
@@ -156,9 +157,17 @@ export const reprocessSyncRules = routeDefinition({
|
|
|
156
157
|
const {
|
|
157
158
|
storageEngine: { activeBucketStorage }
|
|
158
159
|
} = payload.context.service_context;
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
const next = await activeBucketStorage.getDeployingSyncConfig();
|
|
161
|
+
if (next != null) {
|
|
162
|
+
throw new errors.ServiceError({
|
|
163
|
+
status: 409,
|
|
164
|
+
code: ErrorCode.PSYNC_S4106,
|
|
165
|
+
description: 'Busy processing sync config - cannot reprocess'
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const active = await activeBucketStorage.getActiveSyncConfig();
|
|
170
|
+
if (active == null) {
|
|
162
171
|
throw new errors.ServiceError({
|
|
163
172
|
status: 422,
|
|
164
173
|
code: ErrorCode.PSYNC_S4104,
|
|
@@ -166,15 +175,16 @@ export const reprocessSyncRules = routeDefinition({
|
|
|
166
175
|
});
|
|
167
176
|
}
|
|
168
177
|
|
|
178
|
+
const sync_rules = active.content;
|
|
169
179
|
const new_rules = await activeBucketStorage.updateSyncRules(
|
|
170
|
-
updateSyncRulesFromYaml(sync_rules.
|
|
180
|
+
updateSyncRulesFromYaml(sync_rules.sync_rules_content, {
|
|
171
181
|
// This sync config already passed validation. But if the rules are not valid anymore due
|
|
172
182
|
// to a service change, we do want to report the error here.
|
|
173
183
|
validate: true
|
|
174
184
|
})
|
|
175
185
|
);
|
|
176
186
|
return {
|
|
177
|
-
slot_name: new_rules.
|
|
187
|
+
slot_name: new_rules.replicationStreamName
|
|
178
188
|
};
|
|
179
189
|
}
|
|
180
190
|
});
|
|
@@ -62,7 +62,7 @@ export const syncStreamed = routeDefinition({
|
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
const bucketStorage = await storageEngine.activeBucketStorage.
|
|
65
|
+
const bucketStorage = (await storageEngine.activeBucketStorage.getActiveSyncConfig())?.storage;
|
|
66
66
|
|
|
67
67
|
if (bucketStorage == null) {
|
|
68
68
|
throw new errors.ServiceError({
|
package/src/runner/teardown.ts
CHANGED
|
@@ -40,22 +40,22 @@ async function terminateSyncRules(storageFactory: storage.BucketStorageFactory,
|
|
|
40
40
|
const locks: storage.ReplicationLock[] = [];
|
|
41
41
|
while (Date.now() - start < 120_000) {
|
|
42
42
|
let retry = false;
|
|
43
|
-
const
|
|
43
|
+
const replicatingStreams = await storageFactory.getReplicatingReplicationStreams();
|
|
44
44
|
// Lock all the replicating replication streams
|
|
45
|
-
for (const
|
|
46
|
-
const lock = await
|
|
45
|
+
for (const replicatingStream of replicatingStreams) {
|
|
46
|
+
const lock = await replicatingStream.lock();
|
|
47
47
|
locks.push(lock);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
const
|
|
51
|
-
const
|
|
50
|
+
const stoppedStreams = await storageFactory.getStoppedReplicationStreams();
|
|
51
|
+
const combinedStreams = [...replicatingStreams, ...stoppedStreams];
|
|
52
52
|
try {
|
|
53
53
|
// Clean up any module specific configuration for the replication stream
|
|
54
|
-
await moduleManager.tearDown({ syncRules:
|
|
54
|
+
await moduleManager.tearDown({ replicationStreams: combinedStreams, syncRules: combinedStreams });
|
|
55
55
|
|
|
56
56
|
// Mark the replication stream as terminated
|
|
57
|
-
for (let
|
|
58
|
-
const syncRulesStorage = storageFactory.getInstance(
|
|
57
|
+
for (let replicationStream of combinedStreams) {
|
|
58
|
+
const syncRulesStorage = storageFactory.getInstance(replicationStream);
|
|
59
59
|
// The storage will be dropped at the end of the teardown, so we don't need to clear it here
|
|
60
60
|
await syncRulesStorage.terminate({ clearStorage: false });
|
|
61
61
|
}
|
|
@@ -83,11 +83,6 @@ export interface BucketStorageBatch extends ObserverClient<BucketBatchStorageLis
|
|
|
83
83
|
*/
|
|
84
84
|
setResumeLsn(lsn: string): Promise<void>;
|
|
85
85
|
|
|
86
|
-
/**
|
|
87
|
-
* Get the last checkpoint LSN, from either commit or keepalive.
|
|
88
|
-
*/
|
|
89
|
-
lastCheckpointLsn: string | null;
|
|
90
|
-
|
|
91
86
|
/**
|
|
92
87
|
* LSN to resume from.
|
|
93
88
|
*
|
|
@@ -216,8 +211,6 @@ export interface CheckpointResult {
|
|
|
216
211
|
/**
|
|
217
212
|
* True if a checkpoint was actually created by this operation. This can be false even if checkpointBlocked is false,
|
|
218
213
|
* if the checkpoint was empty.
|
|
219
|
-
*
|
|
220
|
-
* This is primarily used for testing.
|
|
221
214
|
*/
|
|
222
215
|
checkpointCreated: boolean;
|
|
223
216
|
}
|
|
@@ -2,13 +2,15 @@ import { BaseObserver, logger } from '@powersync/lib-services-framework';
|
|
|
2
2
|
import {
|
|
3
3
|
PrecompiledSyncConfig,
|
|
4
4
|
SerializedCompatibilityContext,
|
|
5
|
+
SerializedSyncPlanV1,
|
|
5
6
|
serializeSyncPlan,
|
|
6
7
|
SqlSyncRules,
|
|
7
8
|
SyncConfigWithErrors
|
|
8
9
|
} from '@powersync/service-sync-rules';
|
|
9
10
|
import { ReplicationError } from '@powersync/service-types';
|
|
10
11
|
import { syncConfigYamlErrorToReplicationError } from '../util/errors.js';
|
|
11
|
-
import {
|
|
12
|
+
import { PersistedReplicationStream } from './PersistedReplicationStream.js';
|
|
13
|
+
import { PersistedSyncConfigContent } from './PersistedSyncConfigContent.js';
|
|
12
14
|
import { ReplicationEventPayload } from './ReplicationEventPayload.js';
|
|
13
15
|
import { ReplicationLock } from './ReplicationLock.js';
|
|
14
16
|
import { ReportStorage } from './ReportStorage.js';
|
|
@@ -31,14 +33,14 @@ export abstract class BucketStorageFactory
|
|
|
31
33
|
*/
|
|
32
34
|
async configureSyncRules(
|
|
33
35
|
options: UpdateSyncRulesOptions
|
|
34
|
-
): Promise<{ updated: boolean; persisted_sync_rules?:
|
|
35
|
-
const
|
|
36
|
-
const active = await this.
|
|
36
|
+
): Promise<{ updated: boolean; persisted_sync_rules?: PersistedReplicationStream; lock?: ReplicationLock }> {
|
|
37
|
+
const deploying = await this.getDeployingSyncConfig();
|
|
38
|
+
const active = await this.getActiveSyncConfig();
|
|
37
39
|
|
|
38
|
-
if (
|
|
40
|
+
if (deploying?.content.sync_rules_content == options.config.yaml) {
|
|
39
41
|
logger.info('Sync config unchanged');
|
|
40
42
|
return { updated: false };
|
|
41
|
-
} else if (
|
|
43
|
+
} else if (deploying == null && active?.content.sync_rules_content == options.config.yaml) {
|
|
42
44
|
logger.info('Sync config unchanged');
|
|
43
45
|
return { updated: false };
|
|
44
46
|
} else {
|
|
@@ -51,12 +53,15 @@ export abstract class BucketStorageFactory
|
|
|
51
53
|
/**
|
|
52
54
|
* Get a storage instance to query sync data for specific sync config.
|
|
53
55
|
*/
|
|
54
|
-
abstract getInstance(
|
|
56
|
+
abstract getInstance(
|
|
57
|
+
replicationStream: PersistedReplicationStream,
|
|
58
|
+
options?: GetIntanceOptions
|
|
59
|
+
): SyncRulesBucketStorage;
|
|
55
60
|
|
|
56
61
|
/**
|
|
57
62
|
* Deploy new sync config.
|
|
58
63
|
*/
|
|
59
|
-
abstract updateSyncRules(options: UpdateSyncRulesOptions): Promise<
|
|
64
|
+
abstract updateSyncRules(options: UpdateSyncRulesOptions): Promise<PersistedReplicationStream>;
|
|
60
65
|
|
|
61
66
|
/**
|
|
62
67
|
* Indicate that a slot was removed, and we should re-sync by creating
|
|
@@ -68,48 +73,27 @@ export abstract class BucketStorageFactory
|
|
|
68
73
|
*
|
|
69
74
|
* Replication should be restarted after this.
|
|
70
75
|
*/
|
|
71
|
-
abstract restartReplication(
|
|
76
|
+
abstract restartReplication(replicationStreamId: number): Promise<void>;
|
|
72
77
|
|
|
73
78
|
/**
|
|
74
|
-
* Get the sync config used for querying.
|
|
79
|
+
* Get the sync config and storage used for querying.
|
|
75
80
|
*/
|
|
76
|
-
|
|
77
|
-
const content = await this.getActiveSyncRulesContent();
|
|
78
|
-
return content?.parsed(options) ?? null;
|
|
79
|
-
}
|
|
81
|
+
abstract getActiveSyncConfig(): Promise<ResolvedSyncConfig | null>;
|
|
80
82
|
|
|
81
83
|
/**
|
|
82
|
-
* Get the sync config
|
|
84
|
+
* Get the sync config and storage that is still deploying.
|
|
83
85
|
*/
|
|
84
|
-
abstract
|
|
86
|
+
abstract getDeployingSyncConfig(): Promise<ResolvedSyncConfig | null>;
|
|
85
87
|
|
|
86
88
|
/**
|
|
87
|
-
* Get
|
|
89
|
+
* Get all replication streams currently replicating.
|
|
88
90
|
*/
|
|
89
|
-
|
|
90
|
-
const content = await this.getNextSyncRulesContent();
|
|
91
|
-
return content?.parsed(options) ?? null;
|
|
92
|
-
}
|
|
91
|
+
abstract getReplicatingReplicationStreams(): Promise<PersistedReplicationStream[]>;
|
|
93
92
|
|
|
94
93
|
/**
|
|
95
|
-
* Get
|
|
94
|
+
* Get all replication streams stopped but not terminated yet.
|
|
96
95
|
*/
|
|
97
|
-
abstract
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Get all sync config currently replicating. Typically this is the "active" and "next" sync config.
|
|
101
|
-
*/
|
|
102
|
-
abstract getReplicatingSyncRules(): Promise<PersistedSyncRulesContent[]>;
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Get all sync config stopped but not terminated yet.
|
|
106
|
-
*/
|
|
107
|
-
abstract getStoppedSyncRules(): Promise<PersistedSyncRulesContent[]>;
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Get the active storage instance.
|
|
111
|
-
*/
|
|
112
|
-
abstract getActiveStorage(): Promise<SyncRulesBucketStorage | null>;
|
|
96
|
+
abstract getStoppedReplicationStreams(): Promise<PersistedReplicationStream[]>;
|
|
113
97
|
|
|
114
98
|
/**
|
|
115
99
|
* Get storage size of active replication stream.
|
|
@@ -134,6 +118,12 @@ export interface BucketStorageFactoryListener {
|
|
|
134
118
|
replicationEvent: (event: ReplicationEventPayload) => void;
|
|
135
119
|
}
|
|
136
120
|
|
|
121
|
+
export interface ResolvedSyncConfig {
|
|
122
|
+
content: PersistedSyncConfigContent;
|
|
123
|
+
replicationStream: PersistedReplicationStream;
|
|
124
|
+
storage: SyncRulesBucketStorage;
|
|
125
|
+
}
|
|
126
|
+
|
|
137
127
|
export interface StorageMetrics {
|
|
138
128
|
/**
|
|
139
129
|
* Size of operations (bucket_data)
|
|
@@ -181,7 +171,7 @@ export interface SerializedSyncPlan {
|
|
|
181
171
|
/**
|
|
182
172
|
* The serialized plan, from {@link serializeSyncPlan}.
|
|
183
173
|
*/
|
|
184
|
-
plan:
|
|
174
|
+
plan: SerializedSyncPlanV1;
|
|
185
175
|
compatibility: SerializedCompatibilityContext;
|
|
186
176
|
/**
|
|
187
177
|
* Event descriptors are not currently represented in the sync plan because they don't use the sync streams compiler
|
|
@@ -278,4 +268,5 @@ export interface TestStorageConfig {
|
|
|
278
268
|
factory: TestStorageFactory;
|
|
279
269
|
tableIdStrings: boolean;
|
|
280
270
|
storageVersion?: number;
|
|
271
|
+
compressedBucketStorage?: boolean;
|
|
281
272
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { HydratedSyncConfig, HydrationState, SyncConfigWithErrors } from '@powersync/service-sync-rules';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A parsed sync config set is the identity boundary for parsed sync config state.
|
|
5
|
+
*
|
|
6
|
+
* All values on a single instance - the parsed configs, hydration state and hydrated
|
|
7
|
+
* sync config - are derived from the same parse, and may be associated with each other
|
|
8
|
+
* by object identity. Values from two different instances must never be mixed, even if
|
|
9
|
+
* they were parsed from the same persisted content.
|
|
10
|
+
*/
|
|
11
|
+
export interface ParsedSyncConfigSet {
|
|
12
|
+
readonly replicationStreamId: number;
|
|
13
|
+
readonly replicationStreamName: string;
|
|
14
|
+
readonly syncConfigs: SyncConfigWithErrors[];
|
|
15
|
+
/**
|
|
16
|
+
* For testing only.
|
|
17
|
+
*/
|
|
18
|
+
readonly hydrationState: HydrationState;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The hydrated sync config for this parse. This is a stable property: repeated reads
|
|
22
|
+
* return the same instance.
|
|
23
|
+
*/
|
|
24
|
+
readonly hydratedSyncConfig: HydratedSyncConfig;
|
|
25
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { logger as defaultLogger, ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
2
|
+
import { Logger } from 'winston';
|
|
3
|
+
import { SyncRuleState } from './BucketStorage.js';
|
|
4
|
+
import type { ParsedSyncConfigSet } from './ParsedSyncConfigSet.js';
|
|
5
|
+
import type { ParseSyncConfigOptions, PersistedSyncConfigContent } from './PersistedSyncConfigContent.js';
|
|
6
|
+
import { ReplicationLock } from './ReplicationLock.js';
|
|
7
|
+
import { STORAGE_VERSION_CONFIG, StorageVersionConfig } from './StorageVersionConfig.js';
|
|
8
|
+
|
|
9
|
+
export abstract class PersistedReplicationStream implements PersistedReplicationStreamData {
|
|
10
|
+
readonly replicationStreamId: number;
|
|
11
|
+
readonly replicationJobId: string;
|
|
12
|
+
readonly replicationStreamName: string;
|
|
13
|
+
readonly state: SyncRuleState;
|
|
14
|
+
readonly storageVersion: number;
|
|
15
|
+
readonly logger: Logger;
|
|
16
|
+
|
|
17
|
+
abstract readonly syncConfigContent: readonly PersistedSyncConfigContent[];
|
|
18
|
+
abstract readonly current_lock: ReplicationLock | null;
|
|
19
|
+
|
|
20
|
+
constructor(data: PersistedReplicationStreamData) {
|
|
21
|
+
this.replicationStreamId = data.replicationStreamId;
|
|
22
|
+
this.replicationJobId = data.replicationJobId ?? String(data.replicationStreamId);
|
|
23
|
+
this.replicationStreamName = data.replicationStreamName;
|
|
24
|
+
this.state = data.state;
|
|
25
|
+
this.storageVersion = data.storageVersion;
|
|
26
|
+
this.logger = defaultLogger.child({ prefix: `[${this.replicationStreamName}] ` });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getStorageConfig(): StorageVersionConfig {
|
|
30
|
+
const storageConfig = STORAGE_VERSION_CONFIG[this.storageVersion];
|
|
31
|
+
if (storageConfig == null) {
|
|
32
|
+
throw new ServiceError(
|
|
33
|
+
ErrorCode.PSYNC_S1005,
|
|
34
|
+
`Unsupported storage version ${this.storageVersion} for replication stream ${this.replicationStreamId}`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return storageConfig;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
abstract lock(): Promise<ReplicationLock>;
|
|
41
|
+
|
|
42
|
+
abstract parsed(options: ParseSyncConfigOptions): ParsedSyncConfigSet;
|
|
43
|
+
}
|
|
44
|
+
export interface PersistedReplicationStreamData {
|
|
45
|
+
readonly replicationStreamId: number;
|
|
46
|
+
readonly replicationStreamName: string;
|
|
47
|
+
readonly state: SyncRuleState;
|
|
48
|
+
readonly storageVersion: number;
|
|
49
|
+
/**
|
|
50
|
+
* Uniquely identifies a job, as a combination of replication stream and processing sync configs.
|
|
51
|
+
*
|
|
52
|
+
* Used in-memory only.
|
|
53
|
+
*/
|
|
54
|
+
readonly replicationJobId?: string;
|
|
55
|
+
}
|