@powersync/service-core 0.0.0-dev-20240708120322 → 0.0.0-dev-20240718134716
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 +42 -2
- package/dist/entry/commands/migrate-action.js +12 -4
- package/dist/entry/commands/migrate-action.js.map +1 -1
- package/dist/metrics/Metrics.d.ts +3 -4
- package/dist/metrics/Metrics.js +0 -51
- package/dist/metrics/Metrics.js.map +1 -1
- package/dist/migrations/migrations.js +8 -0
- package/dist/migrations/migrations.js.map +1 -1
- package/dist/replication/WalStream.js +8 -6
- package/dist/replication/WalStream.js.map +1 -1
- package/dist/routes/endpoints/socket-route.js +13 -4
- package/dist/routes/endpoints/socket-route.js.map +1 -1
- package/dist/routes/endpoints/sync-stream.js +14 -5
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/routes/route-register.js +1 -0
- package/dist/routes/route-register.js.map +1 -1
- package/dist/sync/RequestTracker.d.ts +9 -0
- package/dist/sync/RequestTracker.js +20 -0
- package/dist/sync/RequestTracker.js.map +1 -0
- package/dist/sync/sync.d.ts +2 -0
- package/dist/sync/sync.js +31 -11
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.d.ts +2 -1
- package/dist/sync/util.js +2 -3
- package/dist/sync/util.js.map +1 -1
- package/dist/util/config/collectors/config-collector.d.ts +0 -12
- package/dist/util/config/collectors/config-collector.js +0 -43
- package/dist/util/config/collectors/config-collector.js.map +1 -1
- package/dist/util/config/compound-config-collector.d.ts +29 -3
- package/dist/util/config/compound-config-collector.js +69 -22
- package/dist/util/config/compound-config-collector.js.map +1 -1
- package/package.json +4 -6
- package/src/entry/commands/migrate-action.ts +12 -4
- package/src/metrics/Metrics.ts +2 -67
- package/src/migrations/migrations.ts +8 -0
- package/src/replication/WalStream.ts +10 -6
- package/src/routes/endpoints/socket-route.ts +14 -4
- package/src/routes/endpoints/sync-stream.ts +15 -5
- package/src/routes/route-register.ts +1 -0
- package/src/sync/RequestTracker.ts +21 -0
- package/src/sync/sync.ts +41 -11
- package/src/sync/util.ts +6 -3
- package/src/util/config/collectors/config-collector.ts +0 -48
- package/src/util/config/compound-config-collector.ts +87 -23
- package/test/src/sync.test.ts +8 -0
- package/test/src/util.ts +12 -6
- package/test/src/wal_stream.test.ts +16 -21
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as t from 'ts-codec';
|
|
1
2
|
import { configFile, normalizeConnection } from '@powersync/service-types';
|
|
2
3
|
import { ConfigCollector } from './collectors/config-collector.js';
|
|
3
4
|
import { ResolvedConnection, ResolvedPowerSyncConfig, RunnerConfig, SyncRulesConfig } from './types.js';
|
|
@@ -9,7 +10,7 @@ import { Base64SyncRulesCollector } from './sync-rules/impl/base64-sync-rules-co
|
|
|
9
10
|
import { InlineSyncRulesCollector } from './sync-rules/impl/inline-sync-rules-collector.js';
|
|
10
11
|
import { FileSystemSyncRulesCollector } from './sync-rules/impl/filesystem-sync-rules-collector.js';
|
|
11
12
|
import { FallbackConfigCollector } from './collectors/impl/fallback-config-collector.js';
|
|
12
|
-
import { logger } from '@powersync/lib-services-framework';
|
|
13
|
+
import { logger, schema } from '@powersync/lib-services-framework';
|
|
13
14
|
|
|
14
15
|
const POWERSYNC_DEV_KID = 'powersync-dev';
|
|
15
16
|
|
|
@@ -28,6 +29,12 @@ export type CompoundConfigCollectorOptions = {
|
|
|
28
29
|
syncRulesCollectors: SyncRulesCollector[];
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
export type ConfigCollectorGenerics = {
|
|
33
|
+
SERIALIZED: configFile.SerializedPowerSyncConfig;
|
|
34
|
+
DESERIALIZED: configFile.PowerSyncConfig;
|
|
35
|
+
RESOLVED: ResolvedPowerSyncConfig;
|
|
36
|
+
};
|
|
37
|
+
|
|
31
38
|
const DEFAULT_COLLECTOR_OPTIONS: CompoundConfigCollectorOptions = {
|
|
32
39
|
configCollectors: [new Base64ConfigCollector(), new FileSystemConfigCollector(), new FallbackConfigCollector()],
|
|
33
40
|
syncRulesCollectors: [
|
|
@@ -37,15 +44,56 @@ const DEFAULT_COLLECTOR_OPTIONS: CompoundConfigCollectorOptions = {
|
|
|
37
44
|
]
|
|
38
45
|
};
|
|
39
46
|
|
|
40
|
-
export class CompoundConfigCollector {
|
|
47
|
+
export class CompoundConfigCollector<Generics extends ConfigCollectorGenerics = ConfigCollectorGenerics> {
|
|
41
48
|
constructor(protected options: CompoundConfigCollectorOptions = DEFAULT_COLLECTOR_OPTIONS) {}
|
|
42
49
|
|
|
50
|
+
/**
|
|
51
|
+
* The default ts-codec for validations and decoding
|
|
52
|
+
*/
|
|
53
|
+
get codec(): t.AnyCodec {
|
|
54
|
+
return configFile.powerSyncConfig;
|
|
55
|
+
}
|
|
56
|
+
|
|
43
57
|
/**
|
|
44
58
|
* Collects and resolves base config
|
|
45
59
|
*/
|
|
46
|
-
async collectConfig(
|
|
47
|
-
const baseConfig = await this.collectBaseConfig(
|
|
60
|
+
async collectConfig(runnerConfig: RunnerConfig = {}): Promise<Generics['RESOLVED']> {
|
|
61
|
+
const baseConfig = await this.collectBaseConfig(runnerConfig);
|
|
62
|
+
const baseResolvedConfig = await this.resolveBaseConfig(baseConfig, runnerConfig);
|
|
63
|
+
return this.resolveConfig(baseConfig, baseResolvedConfig, runnerConfig);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Collects the base PowerSyncConfig from various registered collectors.
|
|
68
|
+
* @throws if no collector could return a configuration.
|
|
69
|
+
*/
|
|
70
|
+
protected async collectBaseConfig(runner_config: RunnerConfig): Promise<Generics['DESERIALIZED']> {
|
|
71
|
+
for (const collector of this.options.configCollectors) {
|
|
72
|
+
try {
|
|
73
|
+
const baseConfig = await collector.collectSerialized(runner_config);
|
|
74
|
+
if (baseConfig) {
|
|
75
|
+
const decoded = this.decode(baseConfig);
|
|
76
|
+
this.validate(decoded);
|
|
77
|
+
return decoded;
|
|
78
|
+
}
|
|
79
|
+
logger.debug(
|
|
80
|
+
`Could not collect PowerSync config with ${collector.name} method. Moving on to next method if available.`
|
|
81
|
+
);
|
|
82
|
+
} catch (ex) {
|
|
83
|
+
// An error in a collector is a hard stop
|
|
84
|
+
throw new Error(`Could not collect config using ${collector.name} method. Caught exception: ${ex}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
throw new Error('PowerSyncConfig could not be collected using any of the registered config collectors.');
|
|
88
|
+
}
|
|
48
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Performs the resolving of the common (shared) base configuration
|
|
92
|
+
*/
|
|
93
|
+
protected async resolveBaseConfig(
|
|
94
|
+
baseConfig: Generics['DESERIALIZED'],
|
|
95
|
+
runnerConfig: RunnerConfig = {}
|
|
96
|
+
): Promise<ResolvedPowerSyncConfig> {
|
|
49
97
|
const connections = baseConfig.replication?.connections ?? [];
|
|
50
98
|
if (connections.length > 1) {
|
|
51
99
|
throw new Error('Only a single replication connection is supported currently');
|
|
@@ -93,7 +141,7 @@ export class CompoundConfigCollector {
|
|
|
93
141
|
devKey = await auth.KeySpec.importKey(baseDevKey);
|
|
94
142
|
}
|
|
95
143
|
|
|
96
|
-
const sync_rules = await this.collectSyncRules(baseConfig,
|
|
144
|
+
const sync_rules = await this.collectSyncRules(baseConfig, runnerConfig);
|
|
97
145
|
|
|
98
146
|
let jwt_audiences: string[] = baseConfig.client_auth?.audience ?? [];
|
|
99
147
|
|
|
@@ -130,25 +178,17 @@ export class CompoundConfigCollector {
|
|
|
130
178
|
}
|
|
131
179
|
|
|
132
180
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
181
|
+
* Perform any additional resolving from {@link ResolvedPowerSyncConfig}
|
|
182
|
+
* to the extended {@link Generics['RESOLVED']}
|
|
183
|
+
*
|
|
135
184
|
*/
|
|
136
|
-
protected async
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
logger.debug(
|
|
144
|
-
`Could not collect PowerSync config with ${collector.name} method. Moving on to next method if available.`
|
|
145
|
-
);
|
|
146
|
-
} catch (ex) {
|
|
147
|
-
// An error in a collector is a hard stop
|
|
148
|
-
throw new Error(`Could not collect config using ${collector.name} method. Caught exception: ${ex}`);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
throw new Error('PowerSyncConfig could not be collected using any of the registered config collectors.');
|
|
185
|
+
protected async resolveConfig(
|
|
186
|
+
baseConfig: Generics['DESERIALIZED'],
|
|
187
|
+
resolvedBaseConfig: ResolvedPowerSyncConfig,
|
|
188
|
+
runnerConfig: RunnerConfig = {}
|
|
189
|
+
): Promise<Generics['RESOLVED']> {
|
|
190
|
+
// The base version has ResolvedPowerSyncConfig == Generics['RESOLVED']
|
|
191
|
+
return resolvedBaseConfig;
|
|
152
192
|
}
|
|
153
193
|
|
|
154
194
|
protected async collectSyncRules(
|
|
@@ -173,4 +213,28 @@ export class CompoundConfigCollector {
|
|
|
173
213
|
present: false
|
|
174
214
|
};
|
|
175
215
|
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Validates input config
|
|
219
|
+
* ts-codec itself doesn't give great validation errors, so we use json schema for that
|
|
220
|
+
*/
|
|
221
|
+
protected validate(config: Generics['DESERIALIZED']) {
|
|
222
|
+
// ts-codec itself doesn't give great validation errors, so we use json schema for that
|
|
223
|
+
const validator = schema
|
|
224
|
+
.parseJSONSchema(t.generateJSONSchema(this.codec, { allowAdditional: true, parsers: [configFile.portParser] }))
|
|
225
|
+
.validator();
|
|
226
|
+
|
|
227
|
+
const valid = validator.validate(config);
|
|
228
|
+
if (!valid.valid) {
|
|
229
|
+
throw new Error(`Failed to validate PowerSync config: ${valid.errors.join(', ')}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
protected decode(encoded: Generics['SERIALIZED']): Generics['DESERIALIZED'] {
|
|
234
|
+
try {
|
|
235
|
+
return this.codec.decode(encoded);
|
|
236
|
+
} catch (ex) {
|
|
237
|
+
throw new Error(`Failed to decode PowerSync config: ${ex}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
176
240
|
}
|
package/test/src/sync.test.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { streamResponse } from '../../src/sync/sync.js';
|
|
|
9
9
|
import * as timers from 'timers/promises';
|
|
10
10
|
import { lsnMakeComparable } from '@powersync/service-jpgwire';
|
|
11
11
|
import { RequestParameters } from '@powersync/service-sync-rules';
|
|
12
|
+
import { RequestTracker } from '@/sync/RequestTracker.js';
|
|
12
13
|
|
|
13
14
|
describe('sync - mongodb', function () {
|
|
14
15
|
defineTests(MONGO_STORAGE_FACTORY);
|
|
@@ -38,6 +39,8 @@ bucket_definitions:
|
|
|
38
39
|
`;
|
|
39
40
|
|
|
40
41
|
function defineTests(factory: StorageFactory) {
|
|
42
|
+
const tracker = new RequestTracker();
|
|
43
|
+
|
|
41
44
|
test('sync global data', async () => {
|
|
42
45
|
const f = await factory();
|
|
43
46
|
|
|
@@ -78,6 +81,7 @@ function defineTests(factory: StorageFactory) {
|
|
|
78
81
|
include_checksum: true,
|
|
79
82
|
raw_data: true
|
|
80
83
|
},
|
|
84
|
+
tracker,
|
|
81
85
|
syncParams: new RequestParameters({ sub: '' }, {}),
|
|
82
86
|
token: { exp: Date.now() / 1000 + 10 } as any
|
|
83
87
|
});
|
|
@@ -118,6 +122,7 @@ function defineTests(factory: StorageFactory) {
|
|
|
118
122
|
include_checksum: true,
|
|
119
123
|
raw_data: false
|
|
120
124
|
},
|
|
125
|
+
tracker,
|
|
121
126
|
syncParams: new RequestParameters({ sub: '' }, {}),
|
|
122
127
|
token: { exp: Date.now() / 1000 + 10 } as any
|
|
123
128
|
});
|
|
@@ -146,6 +151,7 @@ function defineTests(factory: StorageFactory) {
|
|
|
146
151
|
include_checksum: true,
|
|
147
152
|
raw_data: true
|
|
148
153
|
},
|
|
154
|
+
tracker,
|
|
149
155
|
syncParams: new RequestParameters({ sub: '' }, {}),
|
|
150
156
|
token: { exp: 0 } as any
|
|
151
157
|
});
|
|
@@ -172,6 +178,7 @@ function defineTests(factory: StorageFactory) {
|
|
|
172
178
|
include_checksum: true,
|
|
173
179
|
raw_data: true
|
|
174
180
|
},
|
|
181
|
+
tracker,
|
|
175
182
|
syncParams: new RequestParameters({ sub: '' }, {}),
|
|
176
183
|
token: { exp: Date.now() / 1000 + 10 } as any
|
|
177
184
|
});
|
|
@@ -232,6 +239,7 @@ function defineTests(factory: StorageFactory) {
|
|
|
232
239
|
include_checksum: true,
|
|
233
240
|
raw_data: true
|
|
234
241
|
},
|
|
242
|
+
tracker,
|
|
235
243
|
syncParams: new RequestParameters({ sub: '' }, {}),
|
|
236
244
|
token: { exp: exp } as any
|
|
237
245
|
});
|
package/test/src/util.ts
CHANGED
|
@@ -7,14 +7,20 @@ import { PowerSyncMongo } from '../../src/storage/mongo/db.js';
|
|
|
7
7
|
import { escapeIdentifier } from '../../src/util/pgwire_utils.js';
|
|
8
8
|
import { env } from './env.js';
|
|
9
9
|
import { Metrics } from '@/metrics/Metrics.js';
|
|
10
|
+
import { container } from '@powersync/lib-services-framework';
|
|
11
|
+
import { MeterProvider } from '@opentelemetry/sdk-metrics';
|
|
12
|
+
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
|
|
10
13
|
|
|
11
14
|
// The metrics need to be initialised before they can be used
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
})
|
|
17
|
-
|
|
15
|
+
const prometheus = new PrometheusExporter();
|
|
16
|
+
const metrics = new Metrics(
|
|
17
|
+
new MeterProvider({
|
|
18
|
+
readers: [prometheus]
|
|
19
|
+
}),
|
|
20
|
+
prometheus
|
|
21
|
+
);
|
|
22
|
+
container.register(Metrics, metrics);
|
|
23
|
+
metrics.resetCounters();
|
|
18
24
|
|
|
19
25
|
export const TEST_URI = env.PG_TEST_URL;
|
|
20
26
|
|
|
@@ -5,6 +5,7 @@ import { MONGO_STORAGE_FACTORY } from './util.js';
|
|
|
5
5
|
import { putOp, removeOp, walStreamTest } from './wal_stream_utils.js';
|
|
6
6
|
import { pgwireRows } from '@powersync/service-jpgwire';
|
|
7
7
|
import { Metrics } from '@/metrics/Metrics.js';
|
|
8
|
+
import { container } from '@powersync/lib-services-framework';
|
|
8
9
|
|
|
9
10
|
type StorageFactory = () => Promise<BucketStorageFactory>;
|
|
10
11
|
|
|
@@ -41,10 +42,9 @@ bucket_definitions:
|
|
|
41
42
|
|
|
42
43
|
await context.replicateSnapshot();
|
|
43
44
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
const startTxCount =
|
|
47
|
-
(await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
45
|
+
const metrics = container.getImplementation(Metrics);
|
|
46
|
+
const startRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
47
|
+
const startTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
48
48
|
|
|
49
49
|
context.startStreaming();
|
|
50
50
|
|
|
@@ -59,9 +59,8 @@ bucket_definitions:
|
|
|
59
59
|
expect(data).toMatchObject([
|
|
60
60
|
putOp('test_data', { id: test_id, description: 'test1', num: 1152921504606846976n })
|
|
61
61
|
]);
|
|
62
|
-
const endRowCount = (await
|
|
63
|
-
const endTxCount =
|
|
64
|
-
(await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
62
|
+
const endRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
63
|
+
const endTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
65
64
|
expect(endRowCount - startRowCount).toEqual(1);
|
|
66
65
|
expect(endTxCount - startTxCount).toEqual(1);
|
|
67
66
|
})
|
|
@@ -83,10 +82,9 @@ bucket_definitions:
|
|
|
83
82
|
|
|
84
83
|
await context.replicateSnapshot();
|
|
85
84
|
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
const startTxCount =
|
|
89
|
-
(await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
85
|
+
const metrics = container.getImplementation(Metrics);
|
|
86
|
+
const startRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
87
|
+
const startTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
90
88
|
|
|
91
89
|
context.startStreaming();
|
|
92
90
|
|
|
@@ -97,9 +95,8 @@ bucket_definitions:
|
|
|
97
95
|
const data = await context.getBucketData('global[]');
|
|
98
96
|
|
|
99
97
|
expect(data).toMatchObject([putOp('test_DATA', { id: test_id, description: 'test1' })]);
|
|
100
|
-
const endRowCount = (await
|
|
101
|
-
const endTxCount =
|
|
102
|
-
(await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
98
|
+
const endRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
99
|
+
const endTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
103
100
|
expect(endRowCount - startRowCount).toEqual(1);
|
|
104
101
|
expect(endTxCount - startTxCount).toEqual(1);
|
|
105
102
|
})
|
|
@@ -293,10 +290,9 @@ bucket_definitions:
|
|
|
293
290
|
|
|
294
291
|
await context.replicateSnapshot();
|
|
295
292
|
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
const startTxCount =
|
|
299
|
-
(await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
293
|
+
const metrics = container.getImplementation(Metrics);
|
|
294
|
+
const startRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
295
|
+
const startTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
300
296
|
|
|
301
297
|
context.startStreaming();
|
|
302
298
|
|
|
@@ -307,9 +303,8 @@ bucket_definitions:
|
|
|
307
303
|
const data = await context.getBucketData('global[]');
|
|
308
304
|
|
|
309
305
|
expect(data).toMatchObject([]);
|
|
310
|
-
const endRowCount = (await
|
|
311
|
-
const endTxCount =
|
|
312
|
-
(await Metrics.getInstance().getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
306
|
+
const endRowCount = (await metrics.getMetricValueForTests('powersync_rows_replicated_total')) ?? 0;
|
|
307
|
+
const endTxCount = (await metrics.getMetricValueForTests('powersync_transactions_replicated_total')) ?? 0;
|
|
313
308
|
|
|
314
309
|
// There was a transaction, but we should not replicate any actual data
|
|
315
310
|
expect(endRowCount - startRowCount).toEqual(0);
|