@powersync/service-core 0.0.0-dev-20260602073133 → 0.0.0-dev-20260909133214
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 +152 -4
- package/dist/api/RouteAPI.d.ts +42 -14
- package/dist/api/diagnostics.d.ts +12 -1
- package/dist/api/diagnostics.js +36 -22
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/entry/commands/compact-action.js +30 -22
- package/dist/entry/commands/compact-action.js.map +1 -1
- package/dist/metrics/metrics-interfaces.d.ts +5 -1
- package/dist/metrics/metrics-interfaces.js.map +1 -1
- package/dist/metrics/open-telemetry/MetricAttributeFilteringExporter.d.ts +36 -0
- package/dist/metrics/open-telemetry/MetricAttributeFilteringExporter.js +70 -0
- package/dist/metrics/open-telemetry/MetricAttributeFilteringExporter.js.map +1 -0
- package/dist/metrics/open-telemetry/OpenTelemetryMetricsFactory.js +6 -1
- package/dist/metrics/open-telemetry/OpenTelemetryMetricsFactory.js.map +1 -1
- package/dist/metrics/open-telemetry/util.js +7 -3
- package/dist/metrics/open-telemetry/util.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 +31 -7
- package/dist/replication/AbstractReplicator.js +168 -74
- 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/RouterEngine.d.ts +1 -0
- package/dist/routes/RouterEngine.js +6 -1
- package/dist/routes/RouterEngine.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +104 -56
- package/dist/routes/configure-fastify.js +15 -2
- package/dist/routes/configure-fastify.js.map +1 -1
- package/dist/routes/endpoints/admin.d.ts +42 -24
- package/dist/routes/endpoints/admin.js +49 -36
- package/dist/routes/endpoints/admin.js.map +1 -1
- package/dist/routes/endpoints/checkpointing.d.ts +62 -0
- package/dist/routes/endpoints/checkpointing.js +65 -7
- package/dist/routes/endpoints/checkpointing.js.map +1 -1
- package/dist/routes/endpoints/socket-route.js +3 -2
- package/dist/routes/endpoints/socket-route.js.map +1 -1
- package/dist/routes/endpoints/sync-rules.js +25 -14
- 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 +64 -17
- package/dist/storage/BucketStorageBatch.js.map +1 -1
- package/dist/storage/BucketStorageFactory.d.ts +50 -31
- package/dist/storage/BucketStorageFactory.js +10 -20
- package/dist/storage/BucketStorageFactory.js.map +1 -1
- package/dist/storage/CheckpointChecksumInvalidatedError.d.ts +12 -0
- package/dist/storage/CheckpointChecksumInvalidatedError.js +17 -0
- package/dist/storage/CheckpointChecksumInvalidatedError.js.map +1 -0
- package/dist/storage/ChecksumCache.d.ts +11 -6
- package/dist/storage/ChecksumCache.js +5 -4
- package/dist/storage/ChecksumCache.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 +65 -0
- package/dist/storage/PersistedSyncConfigContent.js +138 -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/ReplicationEventPayload.d.ts +6 -1
- package/dist/storage/SourceEntity.d.ts +13 -2
- package/dist/storage/SourceTable.d.ts +53 -4
- package/dist/storage/SourceTable.js +62 -7
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/SourceTableReconciler.d.ts +83 -0
- package/dist/storage/SourceTableReconciler.js +95 -0
- package/dist/storage/SourceTableReconciler.js.map +1 -0
- package/dist/storage/StorageVersionConfig.d.ts +4 -0
- package/dist/storage/StorageVersionConfig.js +10 -1
- package/dist/storage/StorageVersionConfig.js.map +1 -1
- package/dist/storage/SyncRulesBucketStorage.d.ts +162 -26
- package/dist/storage/SyncRulesBucketStorage.js +3 -0
- package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
- package/dist/storage/WriteCheckpointAPI.d.ts +85 -6
- package/dist/storage/WriteCheckpointAPI.js +33 -0
- package/dist/storage/WriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +133 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js +351 -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 +70 -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 +10 -1
- package/dist/storage/storage-index.js +10 -1
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/storage/storage-metrics.js +120 -4
- package/dist/storage/storage-metrics.js.map +1 -1
- package/dist/sync/BucketChecksumState.d.ts +12 -2
- package/dist/sync/BucketChecksumState.js +69 -21
- package/dist/sync/BucketChecksumState.js.map +1 -1
- package/dist/sync/RequestTracker.d.ts +7 -0
- package/dist/sync/RequestTracker.js +12 -0
- package/dist/sync/RequestTracker.js.map +1 -1
- package/dist/sync/sync.js +184 -43
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.d.ts +5 -0
- package/dist/sync/util.js +19 -1
- package/dist/sync/util.js.map +1 -1
- package/dist/system/ServiceContext.d.ts +2 -0
- package/dist/system/ServiceContext.js +2 -0
- package/dist/system/ServiceContext.js.map +1 -1
- package/dist/tracing/PerformanceTracer.d.ts +23 -1
- package/dist/tracing/PerformanceTracer.js +11 -0
- package/dist/tracing/PerformanceTracer.js.map +1 -1
- package/dist/util/checkpointing.d.ts +4 -8
- package/dist/util/checkpointing.js +1 -16
- package/dist/util/checkpointing.js.map +1 -1
- package/dist/util/config/compound-config-collector.js +23 -2
- package/dist/util/config/compound-config-collector.js.map +1 -1
- package/dist/util/config/defaults.d.ts +2 -0
- package/dist/util/config/defaults.js +2 -0
- package/dist/util/config/defaults.js.map +1 -1
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js +1 -0
- package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js +1 -0
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/sync-rules-provider.d.ts +2 -0
- package/dist/util/config/sync-rules/sync-rules-provider.js +3 -0
- package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -1
- package/dist/util/config/types.d.ts +4 -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/dist/util/protocol-types.d.ts +4 -4
- package/dist/util/protocol-types.js +3 -2
- package/dist/util/protocol-types.js.map +1 -1
- package/dist/util/util-index.d.ts +1 -0
- package/dist/util/util-index.js +1 -0
- package/dist/util/util-index.js.map +1 -1
- package/dist/util/utils.d.ts +3 -2
- package/dist/util/utils.js +17 -2
- package/dist/util/utils.js.map +1 -1
- package/dist/util/write-checkpoint-batcher.d.ts +19 -0
- package/dist/util/write-checkpoint-batcher.js +100 -0
- package/dist/util/write-checkpoint-batcher.js.map +1 -0
- package/package.json +7 -7
- package/src/api/RouteAPI.ts +43 -14
- package/src/api/diagnostics.ts +45 -26
- package/src/entry/commands/compact-action.ts +37 -23
- package/src/metrics/metrics-interfaces.ts +6 -1
- package/src/metrics/open-telemetry/MetricAttributeFilteringExporter.ts +85 -0
- package/src/metrics/open-telemetry/OpenTelemetryMetricsFactory.ts +7 -2
- package/src/metrics/open-telemetry/util.ts +7 -3
- package/src/modules/AbstractModule.ts +6 -2
- package/src/replication/AbstractReplicator.ts +212 -78
- package/src/replication/ReplicationModule.ts +2 -3
- package/src/routes/RouterEngine.ts +7 -1
- package/src/routes/configure-fastify.ts +25 -2
- package/src/routes/endpoints/admin.ts +75 -51
- package/src/routes/endpoints/checkpointing.ts +79 -8
- package/src/routes/endpoints/socket-route.ts +3 -2
- package/src/routes/endpoints/sync-rules.ts +26 -14
- package/src/routes/endpoints/sync-stream.ts +1 -1
- package/src/runner/teardown.ts +8 -8
- package/src/storage/BucketStorageBatch.ts +65 -18
- package/src/storage/BucketStorageFactory.ts +73 -46
- package/src/storage/CheckpointChecksumInvalidatedError.ts +17 -0
- package/src/storage/ChecksumCache.ts +27 -14
- package/src/storage/ParsedSyncConfigSet.ts +25 -0
- package/src/storage/PersistedReplicationStream.ts +55 -0
- package/src/storage/PersistedSyncConfigContent.ts +207 -0
- package/src/storage/PersistedSyncConfigStatus.ts +14 -0
- package/src/storage/ReplicationEventPayload.ts +6 -1
- package/src/storage/SourceEntity.ts +12 -2
- package/src/storage/SourceTable.ts +96 -8
- package/src/storage/SourceTableReconciler.ts +188 -0
- package/src/storage/StorageVersionConfig.ts +11 -1
- package/src/storage/SyncRulesBucketStorage.ts +189 -27
- package/src/storage/WriteCheckpointAPI.ts +140 -6
- package/src/storage/implementation/BucketDefinitionMapping.ts +539 -0
- package/src/storage/implementation/IncrementalReprocessingCompatibility.ts +41 -0
- package/src/storage/implementation/IncrementalReprocessingSyncConfigLog.ts +126 -0
- package/src/storage/implementation/ReplicationStreamStorageIds.ts +21 -0
- package/src/storage/storage-index.ts +11 -1
- package/src/storage/storage-metrics.ts +154 -8
- package/src/sync/BucketChecksumState.ts +105 -21
- package/src/sync/RequestTracker.ts +20 -0
- package/src/sync/sync.ts +214 -50
- package/src/sync/util.ts +33 -8
- package/src/system/ServiceContext.ts +7 -0
- package/src/tracing/PerformanceTracer.ts +41 -1
- package/src/util/checkpointing.ts +4 -22
- package/src/util/config/compound-config-collector.ts +31 -1
- package/src/util/config/defaults.ts +2 -0
- package/src/util/config/sync-rules/impl/base64-sync-rules-collector.ts +1 -0
- package/src/util/config/sync-rules/impl/filesystem-sync-rules-collector.ts +1 -0
- package/src/util/config/sync-rules/sync-rules-provider.ts +5 -0
- package/src/util/config/types.ts +4 -1
- package/src/util/lsn.ts +9 -0
- package/src/util/protocol-types.ts +3 -2
- package/src/util/util-index.ts +1 -0
- package/src/util/utils.ts +16 -2
- package/src/util/write-checkpoint-batcher.ts +142 -0
- package/test/src/AbstractReplicator.test.ts +177 -0
- package/test/src/MetricAttributeFilteringExporter.test.ts +96 -0
- package/test/src/PersistedSyncConfigContent.test.ts +152 -0
- package/test/src/checksum_cache.test.ts +22 -0
- package/test/src/config.test.ts +142 -0
- package/test/src/diagnostics.test.ts +87 -34
- package/test/src/module-loader.test.ts +33 -1
- package/test/src/routes/admin.test.ts +188 -18
- package/test/src/routes/checkpointing.test.ts +54 -0
- package/test/src/routes/mocks.ts +6 -2
- package/test/src/routes/stream.test.ts +123 -2
- package/test/src/source-table-reconciler.test.ts +244 -0
- package/test/src/storage/SourceTable.test.ts +92 -0
- package/test/src/storage-metrics.test.ts +148 -0
- package/test/src/sync/BucketChecksumState.test.ts +98 -12
- package/test/src/sync/util.test.ts +28 -1
- package/test/src/util/checkpointing.test.ts +249 -0
- package/test/src/util/utils.test.ts +23 -0
- 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
package/dist/util/utils.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as bson from 'bson';
|
|
|
2
2
|
import crypto from 'crypto';
|
|
3
3
|
import * as uuid from 'uuid';
|
|
4
4
|
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
5
|
-
export const ID_NAMESPACE = 'a396dd91-09fc-4017-a28d-3df722f651e9';
|
|
5
|
+
export const ID_NAMESPACE = uuid.parse('a396dd91-09fc-4017-a28d-3df722f651e9');
|
|
6
6
|
export function escapeIdentifier(identifier) {
|
|
7
7
|
return `"${identifier.replace(/"/g, '""').replace(/\./g, '"."')}"`;
|
|
8
8
|
}
|
|
@@ -165,7 +165,7 @@ export function isCompleteRow(storeData, row) {
|
|
|
165
165
|
* All other operations are replaced with a single CLEAR operation,
|
|
166
166
|
* summing their checksums, and using a 0 as an op_id.
|
|
167
167
|
*
|
|
168
|
-
* This is the function $r(B)$, as described in /docs/bucket-properties.md.
|
|
168
|
+
* This is the function $r(B)$, as described in /docs/storage/bucket-properties.md.
|
|
169
169
|
*
|
|
170
170
|
* Used for tests.
|
|
171
171
|
*/
|
|
@@ -252,4 +252,19 @@ export function estimateRowSize(record) {
|
|
|
252
252
|
}
|
|
253
253
|
return size;
|
|
254
254
|
}
|
|
255
|
+
export function formatBytes(bytes) {
|
|
256
|
+
if (typeof bytes == 'bigint') {
|
|
257
|
+
bytes = Number(bytes); // We round either way
|
|
258
|
+
}
|
|
259
|
+
if (bytes >= 1024 * 1024 * 1024) {
|
|
260
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
|
|
261
|
+
}
|
|
262
|
+
else if (bytes >= 1024 * 1024) {
|
|
263
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
264
|
+
}
|
|
265
|
+
else if (bytes >= 1024) {
|
|
266
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
267
|
+
}
|
|
268
|
+
return `${bytes}B`;
|
|
269
|
+
}
|
|
255
270
|
//# sourceMappingURL=utils.js.map
|
package/dist/util/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/util/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAK7B,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AA2B1E,MAAM,CAAC,MAAM,YAAY,GAAG,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/util/utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RouteAPI } from '../api/RouteAPI.js';
|
|
2
|
+
import { BucketStorageFactory } from '../storage/storage-index.js';
|
|
3
|
+
export interface CreateWriteCheckpointResult {
|
|
4
|
+
writeCheckpoint: string;
|
|
5
|
+
replicationHead: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class WriteCheckpointBatcher {
|
|
8
|
+
private readonly getAPI;
|
|
9
|
+
private readonly getStorage;
|
|
10
|
+
private pending;
|
|
11
|
+
private inFlight;
|
|
12
|
+
private scheduledPump;
|
|
13
|
+
constructor(getAPI: () => RouteAPI, getStorage: () => BucketStorageFactory);
|
|
14
|
+
enqueue(userId: string, checkpointRequestId?: bigint): Promise<CreateWriteCheckpointResult>;
|
|
15
|
+
private schedulePump;
|
|
16
|
+
private pump;
|
|
17
|
+
private executeBatch;
|
|
18
|
+
private createBatchWriteCheckpoints;
|
|
19
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ErrorCode, ServiceAssertionError, ServiceError } from '@powersync/lib-services-framework';
|
|
2
|
+
// Keep up to three source-head/storage batches executing concurrently under load.
|
|
3
|
+
// There is intentionally no explicit batch-size cap here: the HTTP request queue
|
|
4
|
+
// already bounds how many requests can be waiting in this process.
|
|
5
|
+
// Smaller values here have better efficiency, while larger numbers here may give slight improvements in latency.
|
|
6
|
+
// We want to limit the number of database connections in use for write checkpoints, so we keep the numbers low here.
|
|
7
|
+
const MAX_IN_FLIGHT_WRITE_CHECKPOINT_BATCHES = 3;
|
|
8
|
+
export class WriteCheckpointBatcher {
|
|
9
|
+
getAPI;
|
|
10
|
+
getStorage;
|
|
11
|
+
pending = [];
|
|
12
|
+
inFlight = 0;
|
|
13
|
+
scheduledPump;
|
|
14
|
+
constructor(getAPI, getStorage) {
|
|
15
|
+
this.getAPI = getAPI;
|
|
16
|
+
this.getStorage = getStorage;
|
|
17
|
+
}
|
|
18
|
+
enqueue(userId, checkpointRequestId) {
|
|
19
|
+
const resolvers = Promise.withResolvers();
|
|
20
|
+
this.pending.push({ userId, checkpointRequestId, resolvers });
|
|
21
|
+
this.schedulePump();
|
|
22
|
+
return resolvers.promise;
|
|
23
|
+
}
|
|
24
|
+
schedulePump() {
|
|
25
|
+
if (this.scheduledPump != null) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Delay dispatch by one timer turn so requests arriving together can share
|
|
29
|
+
// one source head and one storage write batch.
|
|
30
|
+
// While normally we wouldn't have http requests arriving at the exact same time, this is relevant when
|
|
31
|
+
// requests are queued by fastify: All current requests are typically batched together, and finish at the
|
|
32
|
+
// same time. Then we get many requests from the queue arriving at the same time. Without this delayed dispatch,
|
|
33
|
+
// the first request would form its own batch, and the rest will then wait for a new batch to become available.
|
|
34
|
+
// The delayed dispatch allow all those requests to be batched together.
|
|
35
|
+
this.scheduledPump = setTimeout(() => {
|
|
36
|
+
this.scheduledPump = undefined;
|
|
37
|
+
this.pump();
|
|
38
|
+
}, 0);
|
|
39
|
+
}
|
|
40
|
+
pump() {
|
|
41
|
+
while (this.inFlight < MAX_IN_FLIGHT_WRITE_CHECKPOINT_BATCHES && this.pending.length > 0) {
|
|
42
|
+
const batch = this.pending;
|
|
43
|
+
this.pending = [];
|
|
44
|
+
this.inFlight++;
|
|
45
|
+
void this.executeBatch(batch).finally(() => {
|
|
46
|
+
this.inFlight--;
|
|
47
|
+
this.schedulePump();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async executeBatch(batch) {
|
|
52
|
+
try {
|
|
53
|
+
const syncBucketStorage = (await this.getStorage().getActiveSyncConfig())?.storage;
|
|
54
|
+
if (!syncBucketStorage) {
|
|
55
|
+
throw new ServiceError(ErrorCode.PSYNC_S2302, `Cannot create Write Checkpoint since no sync config is active.`);
|
|
56
|
+
}
|
|
57
|
+
// The source adapter reads the head, hands it to this callback to persist the
|
|
58
|
+
// write-checkpoint mapping, then forces a source marker only when storage
|
|
59
|
+
// reports an advance. Keeping the marker inside the callback means it is
|
|
60
|
+
// causally ordered after the head within a single source session.
|
|
61
|
+
const { writeCheckpoints, currentCheckpoint } = await this.getAPI().createReplicationHead(async (currentCheckpoint) => {
|
|
62
|
+
const { writeCheckpoints, shouldAdvance } = await this.createBatchWriteCheckpoints(syncBucketStorage, batch, currentCheckpoint);
|
|
63
|
+
return { response: { writeCheckpoints, currentCheckpoint }, shouldAdvance };
|
|
64
|
+
});
|
|
65
|
+
const results = batch.map((request) => {
|
|
66
|
+
const writeCheckpoint = writeCheckpoints.get(request.userId);
|
|
67
|
+
if (writeCheckpoint == null) {
|
|
68
|
+
throw new ServiceAssertionError(`Write checkpoint storage did not return a checkpoint for user ${request.userId}`);
|
|
69
|
+
}
|
|
70
|
+
return { request, writeCheckpoint };
|
|
71
|
+
});
|
|
72
|
+
for (const { request, writeCheckpoint } of results) {
|
|
73
|
+
request.resolvers.resolve({
|
|
74
|
+
writeCheckpoint: String(writeCheckpoint),
|
|
75
|
+
replicationHead: currentCheckpoint
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
// Do not retry here. The route should pass failures through so clients can
|
|
81
|
+
// apply their normal write-checkpoint retry policy.
|
|
82
|
+
for (const request of batch) {
|
|
83
|
+
request.resolvers.reject(error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async createBatchWriteCheckpoints(syncBucketStorage, batch, currentCheckpoint) {
|
|
88
|
+
return syncBucketStorage.createManagedWriteCheckpoints(batch.map((request) => {
|
|
89
|
+
const checkpoint = {
|
|
90
|
+
user_id: request.userId,
|
|
91
|
+
heads: { '1': currentCheckpoint }
|
|
92
|
+
};
|
|
93
|
+
if (request.checkpointRequestId != null) {
|
|
94
|
+
checkpoint.checkpoint_request_id = request.checkpointRequestId;
|
|
95
|
+
}
|
|
96
|
+
return checkpoint;
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=write-checkpoint-batcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-checkpoint-batcher.js","sourceRoot":"","sources":["../../src/util/write-checkpoint-batcher.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/service-core",
|
|
3
3
|
"repository": "https://github.com/powersync-ja/powersync-service",
|
|
4
|
-
"types": "dist/index.d.ts",
|
|
5
4
|
"publishConfig": {
|
|
6
5
|
"access": "public"
|
|
7
6
|
},
|
|
8
|
-
"version": "0.0.0-dev-
|
|
9
|
-
"main": "dist/index.js",
|
|
7
|
+
"version": "0.0.0-dev-20260909133214",
|
|
10
8
|
"license": "FSL-1.1-ALv2",
|
|
11
9
|
"type": "module",
|
|
10
|
+
"exports": "./dist/index.js",
|
|
12
11
|
"dependencies": {
|
|
13
12
|
"@js-sdsl/ordered-set": "^4.4.2",
|
|
14
13
|
"@opentelemetry/api": "^1.9.0",
|
|
14
|
+
"@opentelemetry/core": "^2.0.1",
|
|
15
15
|
"@opentelemetry/exporter-metrics-otlp-http": "^0.203.0",
|
|
16
16
|
"@opentelemetry/exporter-prometheus": "^0.203.0",
|
|
17
17
|
"@opentelemetry/resources": "^2.0.1",
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"uuid": "^14.0.0",
|
|
34
34
|
"winston": "^3.13.0",
|
|
35
35
|
"yaml": "^2.8.3",
|
|
36
|
-
"@powersync/lib-services-framework": "0.0.0-dev-
|
|
36
|
+
"@powersync/lib-services-framework": "0.0.0-dev-20260909133214",
|
|
37
37
|
"@powersync/service-jsonbig": "0.17.13",
|
|
38
|
-
"@powersync/service-rsocket-router": "0.0.0-dev-
|
|
39
|
-
"@powersync/service-sync-rules": "0.0.0-dev-
|
|
40
|
-
"@powersync/service-types": "0.
|
|
38
|
+
"@powersync/service-rsocket-router": "0.0.0-dev-20260909133214",
|
|
39
|
+
"@powersync/service-sync-rules": "0.0.0-dev-20260909133214",
|
|
40
|
+
"@powersync/service-types": "0.18.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/async": "^3.2.24",
|
package/src/api/RouteAPI.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SyncConfig, TablePattern } from '@powersync/service-sync-rules';
|
|
2
2
|
import * as types from '@powersync/service-types';
|
|
3
|
-
import {
|
|
3
|
+
import { ParseSyncConfigOptions, SyncRulesBucketStorage } from '../storage/storage-index.js';
|
|
4
4
|
|
|
5
5
|
export interface PatternResult {
|
|
6
6
|
schema: string;
|
|
@@ -25,7 +25,8 @@ export interface SlotWalBudgetOptions {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* Source-specific capabilities used by admin routes, sync config validation,
|
|
29
|
+
* checkpointing, diagnostics, and the sync API.
|
|
29
30
|
*/
|
|
30
31
|
export interface RouteAPI {
|
|
31
32
|
/**
|
|
@@ -40,16 +41,17 @@ export interface RouteAPI {
|
|
|
40
41
|
getConnectionStatus(): Promise<types.ConnectionStatusV2>;
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
|
-
*
|
|
44
|
+
* Expand sync config table patterns into table metadata from the source connection.
|
|
45
|
+
*
|
|
46
|
+
* This is used by validation and diagnostics paths to explain what will be
|
|
47
|
+
* replicated for the current sync config.
|
|
44
48
|
*
|
|
45
49
|
* @param tablePatterns A set of table patterns which typically come from
|
|
46
50
|
* the tables listed in sync config definitions.
|
|
47
51
|
*
|
|
48
52
|
* @param sqlSyncRules
|
|
49
|
-
* @returns
|
|
50
|
-
*
|
|
51
|
-
* tables to ensure syncing should function according to the input
|
|
52
|
-
* pattern. Debug errors and warnings are reported per table.
|
|
53
|
+
* @returns Matching tables or collections, their columns or fields where available,
|
|
54
|
+
* and per-table warnings or errors for the requested table patterns.
|
|
53
55
|
*/
|
|
54
56
|
getDebugTablesInfo(tablePatterns: TablePattern[], sqlSyncRules: SyncConfig): Promise<PatternResult[]>;
|
|
55
57
|
|
|
@@ -69,14 +71,27 @@ export interface RouteAPI {
|
|
|
69
71
|
/**
|
|
70
72
|
* Get the current LSN or equivalent replication HEAD position identifier.
|
|
71
73
|
*
|
|
72
|
-
* The position is provided to the callback
|
|
73
|
-
*
|
|
74
|
+
* The position is provided to the callback so the caller can persist its
|
|
75
|
+
* write-checkpoint mapping before the source adapter forces any required
|
|
76
|
+
* source-side marker or keepalive. The callback returns whether storage
|
|
77
|
+
* actually needs a source marker: when `shouldAdvance` is true the adapter
|
|
78
|
+
* must ensure that the replication stream will observe this position or a
|
|
79
|
+
* greater one, even when the source is otherwise idle. When it is false, the
|
|
80
|
+
* adapter may skip the source marker.
|
|
81
|
+
*
|
|
82
|
+
* Reading the head and forcing the marker happen within a single source
|
|
83
|
+
* session/connection where applicable, so the marker is causally ordered
|
|
84
|
+
* after the head that was handed to the callback.
|
|
74
85
|
*/
|
|
75
86
|
createReplicationHead<T>(callback: ReplicationHeadCallback<T>): Promise<T>;
|
|
76
87
|
|
|
77
88
|
/**
|
|
78
|
-
* @returns The
|
|
79
|
-
*
|
|
89
|
+
* @returns The connected source schema in service-friendly table and column
|
|
90
|
+
* formats. Strict-schema sources should include actual tables,
|
|
91
|
+
* collections, columns, and types. Schemaless sources should return
|
|
92
|
+
* the best metadata available and document validation limitations in
|
|
93
|
+
* their adapter behavior. This is typically used to validate sync
|
|
94
|
+
* config and generate client schemas.
|
|
80
95
|
*/
|
|
81
96
|
getConnectionSchema(): Promise<types.DatabaseSchema[]>;
|
|
82
97
|
|
|
@@ -92,9 +107,23 @@ export interface RouteAPI {
|
|
|
92
107
|
shutdown(): Promise<void>;
|
|
93
108
|
|
|
94
109
|
/**
|
|
95
|
-
* Get
|
|
110
|
+
* Get source-specific sync config parsing defaults, such as the default
|
|
111
|
+
* schema or database to use when sync config references an unqualified table
|
|
112
|
+
* name.
|
|
113
|
+
*
|
|
114
|
+
* This is used wherever sync config is parsed for this source, including
|
|
115
|
+
* validation, diagnostics, and sync routing.
|
|
116
|
+
*/
|
|
117
|
+
getParseSyncRulesOptions(): ParseSyncConfigOptions;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ReplicationHeadResult<T> {
|
|
121
|
+
response: T;
|
|
122
|
+
/**
|
|
123
|
+
* True when storage needs the source to force a later observable position.
|
|
124
|
+
* False when storage can prove no marker is needed.
|
|
96
125
|
*/
|
|
97
|
-
|
|
126
|
+
shouldAdvance: boolean;
|
|
98
127
|
}
|
|
99
128
|
|
|
100
|
-
export type ReplicationHeadCallback<T> = (head: string) => Promise<T
|
|
129
|
+
export type ReplicationHeadCallback<T> = (head: string) => Promise<ReplicationHeadResult<T>>;
|
package/src/api/diagnostics.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { logger } from '@powersync/lib-services-framework';
|
|
1
|
+
import { logger, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
2
2
|
import { DEFAULT_TAG, SourceTableRef, SyncConfigWithErrors } from '@powersync/service-sync-rules';
|
|
3
3
|
import { ReplicationError, SyncRulesStatus, TableInfo } from '@powersync/service-types';
|
|
4
4
|
|
|
@@ -19,6 +19,11 @@ export interface DiagnosticsOptions {
|
|
|
19
19
|
*/
|
|
20
20
|
live_status: boolean;
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Whether this is the active sync config or not.
|
|
24
|
+
*/
|
|
25
|
+
active: boolean;
|
|
26
|
+
|
|
22
27
|
/**
|
|
23
28
|
* Check against the source postgres connection.
|
|
24
29
|
*/
|
|
@@ -28,12 +33,18 @@ export interface DiagnosticsOptions {
|
|
|
28
33
|
export const DEFAULT_DATASOURCE_ID = 'default';
|
|
29
34
|
|
|
30
35
|
export async function getSyncRulesStatus(
|
|
31
|
-
bucketStorage: storage.BucketStorageFactory,
|
|
32
36
|
apiHandler: RouteAPI,
|
|
33
|
-
|
|
34
|
-
options: DiagnosticsOptions
|
|
37
|
+
syncConfig: storage.PersistedSyncConfigContent | null,
|
|
38
|
+
options: DiagnosticsOptions,
|
|
39
|
+
/**
|
|
40
|
+
* Storage instance for the replication stream of this config.
|
|
41
|
+
*
|
|
42
|
+
* Required to populate live status (snapshot/checkpoint info). The content object
|
|
43
|
+
* itself is no longer a replication stream, so the caller must resolve this.
|
|
44
|
+
*/
|
|
45
|
+
systemStorage?: storage.SyncRulesBucketStorage
|
|
35
46
|
): Promise<SyncRulesStatus | undefined> {
|
|
36
|
-
if (
|
|
47
|
+
if (syncConfig == null) {
|
|
37
48
|
return undefined;
|
|
38
49
|
}
|
|
39
50
|
|
|
@@ -43,13 +54,19 @@ export async function getSyncRulesStatus(
|
|
|
43
54
|
const now = new Date().toISOString();
|
|
44
55
|
|
|
45
56
|
let parsed: SyncConfigWithErrors;
|
|
46
|
-
let persisted: storage.
|
|
57
|
+
let persisted: storage.ParsedSyncConfigSet;
|
|
47
58
|
try {
|
|
48
|
-
persisted =
|
|
49
|
-
parsed
|
|
59
|
+
persisted = syncConfig.parsed(apiHandler.getParseSyncRulesOptions());
|
|
60
|
+
// A content object represents a single sync config, so its parsed result has exactly one entry.
|
|
61
|
+
const [singleConfig] = persisted.syncConfigs;
|
|
62
|
+
if (singleConfig == null) {
|
|
63
|
+
throw new ServiceAssertionError('Expected one sync config');
|
|
64
|
+
}
|
|
65
|
+
parsed = singleConfig;
|
|
50
66
|
} catch (e) {
|
|
51
67
|
return {
|
|
52
|
-
|
|
68
|
+
version_label: syncConfig.version_label,
|
|
69
|
+
content: include_content ? syncConfig.sync_rules_content : undefined,
|
|
53
70
|
connections: [],
|
|
54
71
|
errors: [{ level: 'fatal', message: e.message, ts: now }]
|
|
55
72
|
};
|
|
@@ -60,8 +77,7 @@ export async function getSyncRulesStatus(
|
|
|
60
77
|
// This method can run under some situations if no connection is configured yet.
|
|
61
78
|
// It will return a default tag in such a case. This default tag is not module specific.
|
|
62
79
|
const tag = sourceConfig.tag ?? DEFAULT_TAG;
|
|
63
|
-
const
|
|
64
|
-
const status = await systemStorage?.getStatus();
|
|
80
|
+
const status = live_status ? await systemStorage?.getStatus() : undefined;
|
|
65
81
|
let replication_lag_bytes: number | undefined = undefined;
|
|
66
82
|
let slot_wal_budget: SlotWalBudgetInfo | undefined = undefined;
|
|
67
83
|
|
|
@@ -90,10 +106,10 @@ export async function getSyncRulesStatus(
|
|
|
90
106
|
logger.warn(`Unable to get replication lag`, e);
|
|
91
107
|
}
|
|
92
108
|
|
|
93
|
-
if (apiHandler.getSlotWalBudget &&
|
|
109
|
+
if (apiHandler.getSlotWalBudget && syncConfig.replicationStreamName) {
|
|
94
110
|
try {
|
|
95
111
|
slot_wal_budget = await apiHandler.getSlotWalBudget({
|
|
96
|
-
slotName:
|
|
112
|
+
slotName: syncConfig.replicationStreamName
|
|
97
113
|
});
|
|
98
114
|
} catch (e) {
|
|
99
115
|
logger.warn(`Unable to get WAL budget`, e);
|
|
@@ -136,11 +152,13 @@ export async function getSyncRulesStatus(
|
|
|
136
152
|
}
|
|
137
153
|
|
|
138
154
|
const errors = tables_flat.flatMap((info) => info.errors);
|
|
139
|
-
|
|
155
|
+
const statusSource = await syncConfig.getSyncConfigStatus();
|
|
156
|
+
|
|
157
|
+
if (statusSource?.last_fatal_error) {
|
|
140
158
|
errors.push({
|
|
141
159
|
level: 'fatal',
|
|
142
|
-
message:
|
|
143
|
-
ts:
|
|
160
|
+
message: statusSource.last_fatal_error,
|
|
161
|
+
ts: statusSource.last_fatal_error_ts?.toISOString()
|
|
144
162
|
});
|
|
145
163
|
}
|
|
146
164
|
errors.push(...syncRuleErrors.map((error) => syncConfigYamlErrorToReplicationError(error, now)));
|
|
@@ -168,10 +186,10 @@ export async function getSyncRulesStatus(
|
|
|
168
186
|
}
|
|
169
187
|
}
|
|
170
188
|
|
|
171
|
-
if (live_status &&
|
|
189
|
+
if (live_status && options.active) {
|
|
172
190
|
// Check replication lag for active replication stream.
|
|
173
191
|
// Right now we exclude mysql, since it we don't have consistent keepalives for it.
|
|
174
|
-
if (
|
|
192
|
+
if (statusSource?.last_checkpoint_ts == null && statusSource?.last_keepalive_ts == null) {
|
|
175
193
|
errors.push({
|
|
176
194
|
level: 'warning',
|
|
177
195
|
message: 'No checkpoint found, cannot calculate replication lag',
|
|
@@ -179,8 +197,8 @@ export async function getSyncRulesStatus(
|
|
|
179
197
|
});
|
|
180
198
|
} else {
|
|
181
199
|
const lastTime = Math.max(
|
|
182
|
-
|
|
183
|
-
|
|
200
|
+
statusSource.last_checkpoint_ts?.getTime() ?? 0,
|
|
201
|
+
statusSource.last_keepalive_ts?.getTime() ?? 0
|
|
184
202
|
);
|
|
185
203
|
const lagSeconds = Math.round((Date.now() - lastTime) / 1000);
|
|
186
204
|
// On idle instances, keepalive messages are only persisted every 60 seconds.
|
|
@@ -204,17 +222,18 @@ export async function getSyncRulesStatus(
|
|
|
204
222
|
}
|
|
205
223
|
|
|
206
224
|
return {
|
|
207
|
-
|
|
225
|
+
version_label: syncConfig.version_label,
|
|
226
|
+
content: include_content ? syncConfig.sync_rules_content : undefined,
|
|
208
227
|
connections: [
|
|
209
228
|
{
|
|
210
229
|
id: sourceConfig.id ?? DEFAULT_DATASOURCE_ID,
|
|
211
230
|
tag: tag,
|
|
212
|
-
slot_name:
|
|
213
|
-
initial_replication_done: status?.
|
|
231
|
+
slot_name: syncConfig.replicationStreamName,
|
|
232
|
+
initial_replication_done: status?.snapshotDone ?? false,
|
|
214
233
|
// TODO: Rename?
|
|
215
|
-
last_lsn: status?.
|
|
216
|
-
last_checkpoint_ts:
|
|
217
|
-
last_keepalive_ts:
|
|
234
|
+
last_lsn: status?.resumeLsn ?? undefined,
|
|
235
|
+
last_checkpoint_ts: statusSource?.last_checkpoint_ts?.toISOString(),
|
|
236
|
+
last_keepalive_ts: statusSource?.last_keepalive_ts?.toISOString(),
|
|
218
237
|
replication_lag_bytes: replication_lag_bytes,
|
|
219
238
|
wal_status: slot_wal_budget?.wal_status,
|
|
220
239
|
safe_wal_size: slot_wal_budget?.safe_wal_size,
|
|
@@ -21,13 +21,18 @@ const HEAP_LIMIT = v8.getHeapStatistics().heap_size_limit;
|
|
|
21
21
|
* Limit to 1024MB overall.
|
|
22
22
|
*/
|
|
23
23
|
const COMPACT_MEMORY_LIMIT_MB = Math.min(HEAP_LIMIT / 1024 / 1024 - 128, 1024);
|
|
24
|
+
const MINUTE_MS = 60 * 1000;
|
|
24
25
|
|
|
25
26
|
export function registerCompactAction(program: Command) {
|
|
26
27
|
const compactCommand = program
|
|
27
28
|
.command(COMMAND_NAME)
|
|
28
29
|
.option(`-b, --buckets [buckets]`, 'Full bucket names, comma-separated (e.g., "global[],mybucket[\\"user1\\"]")')
|
|
29
30
|
.option('-p, --parameter-indexes', 'Compacting parameter indexes. Defaults to set unless --buckets is provided.')
|
|
30
|
-
.option('--no-parameter-indexes', 'Disabling compacting parameter indexes.')
|
|
31
|
+
.option('--no-parameter-indexes', 'Disabling compacting parameter indexes.')
|
|
32
|
+
.option(
|
|
33
|
+
'--incremental-only',
|
|
34
|
+
'[EXPERIMENTAL] Perform incremental compacting only. Parameter compaction runs on supported storage versions.'
|
|
35
|
+
);
|
|
31
36
|
|
|
32
37
|
wrapConfigCommand(compactCommand);
|
|
33
38
|
|
|
@@ -47,8 +52,9 @@ export function registerCompactAction(program: Command) {
|
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
|
|
50
|
-
|
|
55
|
+
const incremental: boolean = options.incrementalOnly ?? false;
|
|
51
56
|
|
|
57
|
+
let compactParameters: boolean | null = options.parameterIndexes;
|
|
52
58
|
if (buckets == null) {
|
|
53
59
|
logger.info('Compacting storage for all buckets...');
|
|
54
60
|
} else if (buckets.length == 0) {
|
|
@@ -82,28 +88,36 @@ export function registerCompactAction(program: Command) {
|
|
|
82
88
|
// Start the storage engine in order to create the appropriate BucketStorage
|
|
83
89
|
await serviceContext.lifeCycleEngine.start();
|
|
84
90
|
const bucketStorage = serviceContext.storageEngine.activeBucketStorage;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
91
|
+
const deleteCheckpointRequestsBefore = new Date(
|
|
92
|
+
Date.now() - config.api_parameters.checkpoint_request_retention_minutes * MINUTE_MS
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const streams = await bucketStorage.getReplicatingReplicationStreams();
|
|
96
|
+
for (let stream of streams) {
|
|
97
|
+
const storage = bucketStorage.getInstance(stream);
|
|
98
|
+
const compactParameterData =
|
|
99
|
+
(compactParameters ?? buckets == null) && (!incremental || storage.supportsIncrementalParameterCompaction());
|
|
100
|
+
logger.info(`[${stream.replicationStreamName}] Performing compaction...`);
|
|
101
|
+
if (buckets != null) {
|
|
102
|
+
await storage.compact({
|
|
103
|
+
memoryLimitMB: COMPACT_MEMORY_LIMIT_MB,
|
|
104
|
+
compactBuckets: buckets,
|
|
105
|
+
compactParameterData,
|
|
106
|
+
incrementalOnly: incremental,
|
|
107
|
+
deleteCheckpointRequestsBefore,
|
|
108
|
+
signal: abortController.signal
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
await storage.compact({
|
|
112
|
+
memoryLimitMB: COMPACT_MEMORY_LIMIT_MB,
|
|
113
|
+
compactParameterData,
|
|
114
|
+
incrementalOnly: incremental,
|
|
115
|
+
deleteCheckpointRequestsBefore,
|
|
116
|
+
signal: abortController.signal
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
logger.info(`[${stream.replicationStreamName}] Successfully compacted storage.`);
|
|
105
120
|
}
|
|
106
|
-
logger.info('Successfully compacted storage.');
|
|
107
121
|
} catch (e) {
|
|
108
122
|
logger.error(`Failed to compact:`, e);
|
|
109
123
|
// Indirectly triggers lifeCycleEngine.stop
|
|
@@ -19,7 +19,12 @@ export interface ObservableGauge {
|
|
|
19
19
|
* Set a value provider that provides the value for the gauge at the time of observation.
|
|
20
20
|
* @param valueProvider
|
|
21
21
|
*/
|
|
22
|
-
setValueProvider(valueProvider: () => Promise<number | undefined>): void;
|
|
22
|
+
setValueProvider(valueProvider: () => Promise<number | ObservableGaugeObservation[] | undefined>): void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ObservableGaugeObservation {
|
|
26
|
+
value: number;
|
|
27
|
+
attributes?: Record<string, string>;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
export enum Precision {
|