@powersync/service-core 0.0.0-dev-20260313100403 → 0.0.0-dev-20260515144844
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 +86 -7
- package/dist/api/RouteAPI.d.ts +17 -3
- package/dist/api/api-index.d.ts +1 -1
- package/dist/api/api-index.js +1 -1
- package/dist/api/api-index.js.map +1 -1
- package/dist/api/api-metrics.js.map +1 -1
- package/dist/api/diagnostics.d.ts +1 -1
- package/dist/api/diagnostics.js +32 -14
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/auth/CachedKeyCollector.js +1 -1
- package/dist/auth/CachedKeyCollector.js.map +1 -1
- package/dist/auth/CompoundKeyCollector.js.map +1 -1
- package/dist/auth/KeyStore.js.map +1 -1
- package/dist/auth/RemoteJWKSCollector.js.map +1 -1
- package/dist/auth/StaticKeyCollector.d.ts +1 -1
- package/dist/auth/StaticKeyCollector.js.map +1 -1
- package/dist/auth/StaticSupabaseKeyCollector.d.ts +1 -1
- package/dist/auth/StaticSupabaseKeyCollector.js.map +1 -1
- package/dist/entry/commands/compact-action.js +26 -5
- package/dist/entry/commands/compact-action.js.map +1 -1
- package/dist/entry/commands/teardown-action.js +2 -2
- package/dist/entry/commands/teardown-action.js.map +1 -1
- package/dist/entry/entry-index.d.ts +1 -1
- package/dist/entry/entry-index.js +1 -1
- package/dist/entry/entry-index.js.map +1 -1
- package/dist/events/EventsEngine.js +1 -1
- package/dist/events/EventsEngine.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/metrics/MetricsEngine.d.ts +1 -1
- package/dist/metrics/RollingBucketMax.d.ts +28 -0
- package/dist/metrics/RollingBucketMax.js +80 -0
- package/dist/metrics/RollingBucketMax.js.map +1 -0
- package/dist/metrics/metrics-index.d.ts +3 -2
- package/dist/metrics/metrics-index.js +3 -2
- package/dist/metrics/metrics-index.js.map +1 -1
- package/dist/metrics/open-telemetry/util.js +1 -1
- package/dist/metrics/open-telemetry/util.js.map +1 -1
- package/dist/metrics/register-metrics.js +2 -2
- package/dist/metrics/register-metrics.js.map +1 -1
- package/dist/modules/AbstractModule.d.ts +2 -2
- package/dist/modules/AbstractModule.js.map +1 -1
- package/dist/modules/modules-index.d.ts +1 -1
- package/dist/modules/modules-index.js +1 -1
- package/dist/modules/modules-index.js.map +1 -1
- package/dist/replication/AbstractReplicationJob.d.ts +2 -2
- package/dist/replication/AbstractReplicationJob.js +1 -1
- package/dist/replication/AbstractReplicationJob.js.map +1 -1
- package/dist/replication/AbstractReplicator.d.ts +7 -7
- package/dist/replication/AbstractReplicator.js +31 -28
- package/dist/replication/AbstractReplicator.js.map +1 -1
- package/dist/replication/ReplicationLagTracker.d.ts +50 -0
- package/dist/replication/ReplicationLagTracker.js +78 -0
- package/dist/replication/ReplicationLagTracker.js.map +1 -0
- package/dist/replication/replication-index.d.ts +3 -2
- package/dist/replication/replication-index.js +3 -2
- package/dist/replication/replication-index.js.map +1 -1
- package/dist/replication/replication-metrics.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +59 -32
- package/dist/routes/endpoints/admin.d.ts +108 -54
- package/dist/routes/endpoints/admin.js +7 -3
- 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 +10 -10
- package/dist/routes/endpoints/sync-rules.js.map +1 -1
- package/dist/routes/endpoints/sync-stream.d.ts +10 -10
- package/dist/routes/endpoints/sync-stream.js +2 -2
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/routes/hooks.js +1 -1
- package/dist/routes/hooks.js.map +1 -1
- package/dist/routes/route-register.js.map +1 -1
- package/dist/runner/teardown.js +4 -4
- package/dist/runner/teardown.js.map +1 -1
- package/dist/storage/BucketStorage.d.ts +9 -9
- package/dist/storage/BucketStorage.js +9 -9
- package/dist/storage/BucketStorageBatch.d.ts +1 -1
- package/dist/storage/BucketStorageFactory.d.ts +27 -20
- package/dist/storage/BucketStorageFactory.js +19 -16
- package/dist/storage/BucketStorageFactory.js.map +1 -1
- package/dist/storage/ChecksumCache.js.map +1 -1
- package/dist/storage/PersistedSyncRulesContent.d.ts +3 -1
- package/dist/storage/PersistedSyncRulesContent.js +24 -5
- package/dist/storage/PersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/ReplicationEventPayload.d.ts +1 -1
- package/dist/storage/ReportStorage.d.ts +3 -3
- package/dist/storage/SourceTable.d.ts +4 -4
- package/dist/storage/SourceTable.js +3 -3
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/StorageVersionConfig.d.ts +1 -1
- package/dist/storage/StorageVersionConfig.js +1 -1
- package/dist/storage/SyncRulesBucketStorage.d.ts +38 -6
- package/dist/storage/SyncRulesBucketStorage.js +14 -0
- package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
- package/dist/storage/WriteCheckpointAPI.d.ts +6 -6
- package/dist/storage/WriteCheckpointAPI.js +1 -1
- package/dist/storage/bson.d.ts +0 -1
- package/dist/storage/bson.js +0 -4
- package/dist/storage/bson.js.map +1 -1
- package/dist/storage/storage-index.d.ts +8 -8
- package/dist/storage/storage-index.js +8 -8
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/storage/storage-metrics.js.map +1 -1
- package/dist/streams/streams-index.d.ts +2 -2
- package/dist/streams/streams-index.js +2 -2
- package/dist/streams/streams-index.js.map +1 -1
- package/dist/sync/BucketChecksumState.d.ts +2 -5
- package/dist/sync/BucketChecksumState.js +119 -75
- package/dist/sync/BucketChecksumState.js.map +1 -1
- package/dist/sync/RequestTracker.js +1 -1
- package/dist/sync/RequestTracker.js.map +1 -1
- package/dist/sync/sync-index.d.ts +2 -2
- package/dist/sync/sync-index.js +2 -2
- package/dist/sync/sync-index.js.map +1 -1
- package/dist/sync/sync.js.map +1 -1
- package/dist/sync/util.js.map +1 -1
- package/dist/system/ServiceContext.d.ts +1 -1
- package/dist/system/ServiceContext.js +1 -1
- package/dist/system/ServiceContext.js.map +1 -1
- package/dist/tracing/PerformanceTracer.d.ts +44 -0
- package/dist/tracing/PerformanceTracer.js +102 -0
- package/dist/tracing/PerformanceTracer.js.map +1 -0
- package/dist/tracing/TraceWriter.d.ts +22 -0
- package/dist/tracing/TraceWriter.js +63 -0
- package/dist/tracing/TraceWriter.js.map +1 -0
- package/dist/util/checkpointing.js +1 -1
- package/dist/util/config/collectors/impl/base64-config-collector.d.ts +1 -1
- package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -1
- package/dist/util/config/collectors/impl/filesystem-config-collector.d.ts +1 -1
- package/dist/util/config/collectors/impl/filesystem-config-collector.js +1 -1
- package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -1
- package/dist/util/config/compound-config-collector.d.ts +1 -1
- package/dist/util/config/compound-config-collector.js +2 -2
- package/dist/util/config/compound-config-collector.js.map +1 -1
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js +1 -1
- package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
- package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -1
- package/dist/util/config.js +1 -1
- package/dist/util/config.js.map +1 -1
- package/dist/util/env.js +1 -1
- package/dist/util/errors.d.ts +3 -0
- package/dist/util/errors.js +15 -0
- package/dist/util/errors.js.map +1 -0
- package/dist/util/protocol-types.d.ts +3 -3
- package/dist/util/protocol-types.js +1 -1
- package/dist/util/util-index.d.ts +1 -1
- package/dist/util/util-index.js +1 -1
- package/dist/util/util-index.js.map +1 -1
- package/dist/util/utils.d.ts +1 -1
- package/package.json +11 -11
- package/src/api/RouteAPI.ts +20 -3
- package/src/api/api-index.ts +1 -1
- package/src/api/api-metrics.ts +1 -1
- package/src/api/diagnostics.ts +42 -20
- package/src/auth/CachedKeyCollector.ts +2 -3
- package/src/auth/CompoundKeyCollector.ts +2 -3
- package/src/auth/KeyStore.ts +1 -1
- package/src/auth/RemoteJWKSCollector.ts +0 -1
- package/src/auth/StaticKeyCollector.ts +1 -1
- package/src/auth/StaticSupabaseKeyCollector.ts +1 -1
- package/src/entry/commands/compact-action.ts +29 -5
- package/src/entry/commands/teardown-action.ts +2 -2
- package/src/entry/entry-index.ts +1 -1
- package/src/events/EventsEngine.ts +1 -1
- package/src/index.ts +2 -0
- package/src/metrics/MetricsEngine.ts +1 -1
- package/src/metrics/RollingBucketMax.ts +109 -0
- package/src/metrics/metrics-index.ts +3 -2
- package/src/metrics/open-telemetry/util.ts +1 -1
- package/src/metrics/register-metrics.ts +3 -3
- package/src/modules/AbstractModule.ts +2 -2
- package/src/modules/modules-index.ts +1 -1
- package/src/replication/AbstractReplicationJob.ts +3 -3
- package/src/replication/AbstractReplicator.ts +32 -30
- package/src/replication/ReplicationLagTracker.ts +86 -0
- package/src/replication/replication-index.ts +3 -2
- package/src/replication/replication-metrics.ts +1 -1
- package/src/routes/endpoints/admin.ts +7 -3
- package/src/routes/endpoints/checkpointing.ts +1 -1
- package/src/routes/endpoints/socket-route.ts +1 -1
- package/src/routes/endpoints/sync-rules.ts +10 -12
- package/src/routes/endpoints/sync-stream.ts +2 -2
- package/src/routes/hooks.ts +2 -2
- package/src/routes/route-register.ts +2 -10
- package/src/runner/teardown.ts +4 -4
- package/src/storage/BucketStorage.ts +9 -9
- package/src/storage/BucketStorageBatch.ts +1 -1
- package/src/storage/BucketStorageFactory.ts +45 -34
- package/src/storage/ChecksumCache.ts +1 -1
- package/src/storage/PersistedSyncRulesContent.ts +30 -6
- package/src/storage/ReplicationEventPayload.ts +1 -1
- package/src/storage/ReportStorage.ts +3 -3
- package/src/storage/SourceTable.ts +4 -4
- package/src/storage/StorageVersionConfig.ts +1 -1
- package/src/storage/SyncRulesBucketStorage.ts +46 -7
- package/src/storage/WriteCheckpointAPI.ts +6 -6
- package/src/storage/bson.ts +0 -5
- package/src/storage/storage-index.ts +8 -8
- package/src/storage/storage-metrics.ts +2 -2
- package/src/streams/streams-index.ts +2 -2
- package/src/sync/BucketChecksumState.ts +141 -93
- package/src/sync/RequestTracker.ts +1 -1
- package/src/sync/sync-index.ts +2 -2
- package/src/sync/sync.ts +2 -8
- package/src/sync/util.ts +1 -1
- package/src/system/ServiceContext.ts +1 -1
- package/src/tracing/PerformanceTracer.ts +126 -0
- package/src/tracing/TraceWriter.ts +67 -0
- package/src/util/checkpointing.ts +1 -1
- package/src/util/config/collectors/impl/base64-config-collector.ts +1 -1
- package/src/util/config/collectors/impl/filesystem-config-collector.ts +2 -2
- package/src/util/config/compound-config-collector.ts +3 -3
- package/src/util/config/sync-rules/impl/filesystem-sync-rules-collector.ts +1 -1
- package/src/util/config/sync-rules/sync-rules-provider.ts +1 -1
- package/src/util/config.ts +1 -1
- package/src/util/env.ts +1 -1
- package/src/util/errors.ts +21 -0
- package/src/util/protocol-types.ts +1 -1
- package/src/util/util-index.ts +1 -1
- package/src/util/utils.ts +1 -1
- package/test/src/ReplicationLagTracker.test.ts +53 -0
- package/test/src/RollingBucketMax.test.ts +106 -0
- package/test/src/auth.test.ts +115 -7
- package/test/src/diagnostics.test.ts +151 -0
- package/test/src/module-loader.test.ts +1 -1
- package/test/src/routes/mocks.ts +1 -1
- package/test/src/routes/stream.test.ts +1 -2
- package/test/src/sync/BucketChecksumState.test.ts +223 -67
- package/test/src/util/protocol_types.test.ts +1 -1
- package/test/tsconfig.json +0 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +1 -1
package/src/api/RouteAPI.ts
CHANGED
|
@@ -14,6 +14,16 @@ export interface ReplicationLagOptions {
|
|
|
14
14
|
bucketStorage: SyncRulesBucketStorage;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface SlotWalBudgetInfo {
|
|
18
|
+
wal_status: string;
|
|
19
|
+
safe_wal_size?: number;
|
|
20
|
+
max_slot_wal_keep_size?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SlotWalBudgetOptions {
|
|
24
|
+
slotName: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
/**
|
|
18
28
|
* Describes all the methods currently required to service the sync API endpoints.
|
|
19
29
|
*/
|
|
@@ -33,7 +43,7 @@ export interface RouteAPI {
|
|
|
33
43
|
* Generates replication table information from a given pattern of tables.
|
|
34
44
|
*
|
|
35
45
|
* @param tablePatterns A set of table patterns which typically come from
|
|
36
|
-
* the tables listed in sync
|
|
46
|
+
* the tables listed in sync config definitions.
|
|
37
47
|
*
|
|
38
48
|
* @param sqlSyncRules
|
|
39
49
|
* @returns A result of all the tables and columns which should be replicated
|
|
@@ -49,6 +59,13 @@ export interface RouteAPI {
|
|
|
49
59
|
*/
|
|
50
60
|
getReplicationLagBytes(options: ReplicationLagOptions): Promise<number | undefined>;
|
|
51
61
|
|
|
62
|
+
/**
|
|
63
|
+
* @returns WAL budget information for the replication slot, or undefined
|
|
64
|
+
* if the slot doesn't exist or the source doesn't support this.
|
|
65
|
+
* Only implemented by the Postgres adapter.
|
|
66
|
+
*/
|
|
67
|
+
getSlotWalBudget?(options: SlotWalBudgetOptions): Promise<SlotWalBudgetInfo | undefined>;
|
|
68
|
+
|
|
52
69
|
/**
|
|
53
70
|
* Get the current LSN or equivalent replication HEAD position identifier.
|
|
54
71
|
*
|
|
@@ -59,7 +76,7 @@ export interface RouteAPI {
|
|
|
59
76
|
|
|
60
77
|
/**
|
|
61
78
|
* @returns The schema for tables inside the connected database. This is typically
|
|
62
|
-
* used to validate sync
|
|
79
|
+
* used to validate sync config.
|
|
63
80
|
*/
|
|
64
81
|
getConnectionSchema(): Promise<types.DatabaseSchema[]>;
|
|
65
82
|
|
|
@@ -75,7 +92,7 @@ export interface RouteAPI {
|
|
|
75
92
|
shutdown(): Promise<void>;
|
|
76
93
|
|
|
77
94
|
/**
|
|
78
|
-
* Get the default schema (or database) when only a table name is specified in sync
|
|
95
|
+
* Get the default schema (or database) when only a table name is specified in sync config.
|
|
79
96
|
*/
|
|
80
97
|
getParseSyncRulesOptions(): ParseSyncRulesOptions;
|
|
81
98
|
}
|
package/src/api/api-index.ts
CHANGED
package/src/api/api-metrics.ts
CHANGED
package/src/api/diagnostics.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { logger } from '@powersync/lib-services-framework';
|
|
2
|
-
import { DEFAULT_TAG, SourceTableInterface,
|
|
2
|
+
import { DEFAULT_TAG, SourceTableInterface, SyncConfigWithErrors } from '@powersync/service-sync-rules';
|
|
3
3
|
import { ReplicationError, SyncRulesStatus, TableInfo } from '@powersync/service-types';
|
|
4
4
|
|
|
5
5
|
import * as storage from '../storage/storage-index.js';
|
|
6
|
-
import {
|
|
6
|
+
import { syncConfigYamlErrorToReplicationError } from '../util/errors.js';
|
|
7
|
+
import { RouteAPI, SlotWalBudgetInfo } from './RouteAPI.js';
|
|
7
8
|
|
|
8
9
|
export interface DiagnosticsOptions {
|
|
9
10
|
/**
|
|
10
|
-
* Include sync
|
|
11
|
+
* Include sync config content in response.
|
|
11
12
|
*/
|
|
12
13
|
include_content?: boolean;
|
|
13
14
|
|
|
@@ -62,6 +63,7 @@ export async function getSyncRulesStatus(
|
|
|
62
63
|
const systemStorage = live_status ? bucketStorage.getInstance(sync_rules) : undefined;
|
|
63
64
|
const status = await systemStorage?.getStatus();
|
|
64
65
|
let replication_lag_bytes: number | undefined = undefined;
|
|
66
|
+
let slot_wal_budget: SlotWalBudgetInfo | undefined = undefined;
|
|
65
67
|
|
|
66
68
|
let tables_flat: TableInfo[] = [];
|
|
67
69
|
|
|
@@ -87,6 +89,16 @@ export async function getSyncRulesStatus(
|
|
|
87
89
|
// Ignore
|
|
88
90
|
logger.warn(`Unable to get replication lag`, e);
|
|
89
91
|
}
|
|
92
|
+
|
|
93
|
+
if (apiHandler.getSlotWalBudget && sync_rules.slot_name) {
|
|
94
|
+
try {
|
|
95
|
+
slot_wal_budget = await apiHandler.getSlotWalBudget({
|
|
96
|
+
slotName: sync_rules.slot_name
|
|
97
|
+
});
|
|
98
|
+
} catch (e) {
|
|
99
|
+
logger.warn(`Unable to get WAL budget`, e);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
90
102
|
}
|
|
91
103
|
} else {
|
|
92
104
|
const source_table_patterns = rules.getSourceTables();
|
|
@@ -131,26 +143,33 @@ export async function getSyncRulesStatus(
|
|
|
131
143
|
ts: sync_rules.last_fatal_error_ts?.toISOString()
|
|
132
144
|
});
|
|
133
145
|
}
|
|
134
|
-
errors.push(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
146
|
+
errors.push(...syncRuleErrors.map((error) => syncConfigYamlErrorToReplicationError(error, now)));
|
|
147
|
+
|
|
148
|
+
if (
|
|
149
|
+
slot_wal_budget &&
|
|
150
|
+
slot_wal_budget.wal_status !== 'lost' &&
|
|
151
|
+
slot_wal_budget.safe_wal_size != null &&
|
|
152
|
+
slot_wal_budget.max_slot_wal_keep_size != null &&
|
|
153
|
+
slot_wal_budget.max_slot_wal_keep_size > 0
|
|
154
|
+
) {
|
|
155
|
+
const budgetPct = Math.max(
|
|
156
|
+
0,
|
|
157
|
+
Math.round((slot_wal_budget.safe_wal_size / slot_wal_budget.max_slot_wal_keep_size) * 100)
|
|
158
|
+
);
|
|
159
|
+
if (budgetPct <= 50) {
|
|
160
|
+
errors.push({
|
|
161
|
+
level: 'warning',
|
|
162
|
+
message:
|
|
163
|
+
`WAL budget is low: ${budgetPct}% remaining. ` +
|
|
164
|
+
`The replication slot may be invalidated if WAL consumption ` +
|
|
165
|
+
`continues at this rate. Consider increasing max_slot_wal_keep_size.`,
|
|
139
166
|
ts: now
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
start_offset: location.start,
|
|
144
|
-
end_offset: location.end
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return error;
|
|
149
|
-
})
|
|
150
|
-
);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
151
170
|
|
|
152
171
|
if (live_status && status?.active) {
|
|
153
|
-
// Check replication lag for active
|
|
172
|
+
// Check replication lag for active replication stream.
|
|
154
173
|
// Right now we exclude mysql, since it we don't have consistent keepalives for it.
|
|
155
174
|
if (sync_rules.last_checkpoint_ts == null && sync_rules.last_keepalive_ts == null) {
|
|
156
175
|
errors.push({
|
|
@@ -197,6 +216,9 @@ export async function getSyncRulesStatus(
|
|
|
197
216
|
last_checkpoint_ts: sync_rules.last_checkpoint_ts?.toISOString(),
|
|
198
217
|
last_keepalive_ts: sync_rules.last_keepalive_ts?.toISOString(),
|
|
199
218
|
replication_lag_bytes: replication_lag_bytes,
|
|
219
|
+
wal_status: slot_wal_budget?.wal_status,
|
|
220
|
+
safe_wal_size: slot_wal_budget?.safe_wal_size,
|
|
221
|
+
max_slot_wal_keep_size: slot_wal_budget?.max_slot_wal_keep_size,
|
|
200
222
|
tables: tables_flat
|
|
201
223
|
}
|
|
202
224
|
],
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { AuthorizationError, ErrorCode, logger } from '@powersync/lib-services-framework';
|
|
2
2
|
import timers from 'timers/promises';
|
|
3
|
+
import { KeyCollector, KeyResult } from './KeyCollector.js';
|
|
3
4
|
import { KeySpec } from './KeySpec.js';
|
|
4
5
|
import { LeakyBucket } from './LeakyBucket.js';
|
|
5
|
-
import { KeyCollector, KeyResult } from './KeyCollector.js';
|
|
6
|
-
import { AuthorizationError, ErrorCode, logger } from '@powersync/lib-services-framework';
|
|
7
6
|
import { mapAuthConfigError } from './utils.js';
|
|
8
7
|
|
|
9
8
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import * as jose from 'jose';
|
|
2
|
-
import { KeySpec } from './KeySpec.js';
|
|
3
|
-
import { KeyCollector, KeyResult } from './KeyCollector.js';
|
|
4
1
|
import { AuthorizationError } from '@powersync/lib-services-framework';
|
|
2
|
+
import { KeyCollector, KeyResult } from './KeyCollector.js';
|
|
3
|
+
import { KeySpec } from './KeySpec.js';
|
|
5
4
|
|
|
6
5
|
export class CompoundKeyCollector implements KeyCollector {
|
|
7
6
|
private collectors: KeyCollector[];
|
package/src/auth/KeyStore.ts
CHANGED
|
@@ -4,7 +4,7 @@ import secs from '../util/secs.js';
|
|
|
4
4
|
import { JwtPayload } from './JwtPayload.js';
|
|
5
5
|
import { KeyCollector } from './KeyCollector.js';
|
|
6
6
|
import { KeyOptions, KeySpec, SUPPORTED_ALGORITHMS } from './KeySpec.js';
|
|
7
|
-
import { debugKeyNotFound, mapAuthError, SupabaseAuthDetails
|
|
7
|
+
import { debugKeyNotFound, mapAuthError, SupabaseAuthDetails } from './utils.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* KeyStore to get keys and verify tokens.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as jose from 'jose';
|
|
2
|
-
import { KeySpec, KeyOptions } from './KeySpec.js';
|
|
3
2
|
import { KeyCollector, KeyResult } from './KeyCollector.js';
|
|
3
|
+
import { KeyOptions, KeySpec } from './KeySpec.js';
|
|
4
4
|
|
|
5
5
|
export const SUPABASE_KEY_OPTIONS: KeyOptions = {
|
|
6
6
|
requiresAudience: ['authenticated'],
|
|
@@ -25,7 +25,9 @@ const COMPACT_MEMORY_LIMIT_MB = Math.min(HEAP_LIMIT / 1024 / 1024 - 128, 1024);
|
|
|
25
25
|
export function registerCompactAction(program: Command) {
|
|
26
26
|
const compactCommand = program
|
|
27
27
|
.command(COMMAND_NAME)
|
|
28
|
-
.option(`-b, --buckets [buckets]`, 'Full bucket names, comma-separated (e.g., "global[],mybucket[\\"user1\\"]")')
|
|
28
|
+
.option(`-b, --buckets [buckets]`, 'Full bucket names, comma-separated (e.g., "global[],mybucket[\\"user1\\"]")')
|
|
29
|
+
.option('-p, --parameter-indexes', 'Compacting parameter indexes. Defaults to set unless --buckets is provided.')
|
|
30
|
+
.option('--no-parameter-indexes', 'Disabling compacting parameter indexes.');
|
|
29
31
|
|
|
30
32
|
wrapConfigCommand(compactCommand);
|
|
31
33
|
|
|
@@ -44,16 +46,31 @@ export function registerCompactAction(program: Command) {
|
|
|
44
46
|
process.exit(1);
|
|
45
47
|
}
|
|
46
48
|
}
|
|
49
|
+
|
|
50
|
+
let compactParameters: boolean | null = options.parameterIndexes;
|
|
51
|
+
|
|
47
52
|
if (buckets == null) {
|
|
48
53
|
logger.info('Compacting storage for all buckets...');
|
|
54
|
+
} else if (buckets.length == 0) {
|
|
55
|
+
logger.info('Skipping bucket compaction');
|
|
49
56
|
} else {
|
|
50
57
|
logger.info(`Compacting storage for ${buckets?.join(', ')}...`);
|
|
51
58
|
}
|
|
59
|
+
|
|
52
60
|
const config = await utils.loadConfig(extractRunnerOptions(options));
|
|
53
61
|
const serviceContext = new system.ServiceContextContainer({
|
|
54
62
|
serviceMode: system.ServiceContextMode.COMPACT,
|
|
55
63
|
configuration: config
|
|
56
64
|
});
|
|
65
|
+
const abortController = new AbortController();
|
|
66
|
+
const completion = Promise.withResolvers<void>();
|
|
67
|
+
|
|
68
|
+
serviceContext.lifeCycleEngine.withLifecycle(null, {
|
|
69
|
+
stop: async () => {
|
|
70
|
+
abortController.abort();
|
|
71
|
+
await completion.promise;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
57
74
|
|
|
58
75
|
// Register modules in order to allow custom module compacting
|
|
59
76
|
const moduleManager = container.getImplementation(modules.ModuleManager);
|
|
@@ -71,22 +88,29 @@ export function registerCompactAction(program: Command) {
|
|
|
71
88
|
logger.info('No active instance to compact');
|
|
72
89
|
return;
|
|
73
90
|
}
|
|
74
|
-
logger.info('Performing compaction...');
|
|
75
91
|
if (buckets != null) {
|
|
92
|
+
logger.info('Performing compaction...');
|
|
76
93
|
await active.compact({
|
|
77
94
|
memoryLimitMB: COMPACT_MEMORY_LIMIT_MB,
|
|
78
95
|
compactBuckets: buckets,
|
|
79
|
-
compactParameterData: false
|
|
96
|
+
compactParameterData: compactParameters ?? false,
|
|
97
|
+
signal: abortController.signal
|
|
80
98
|
});
|
|
81
99
|
} else {
|
|
82
|
-
await active.compact({
|
|
100
|
+
await active.compact({
|
|
101
|
+
memoryLimitMB: COMPACT_MEMORY_LIMIT_MB,
|
|
102
|
+
compactParameterData: compactParameters ?? true,
|
|
103
|
+
signal: abortController.signal
|
|
104
|
+
});
|
|
83
105
|
}
|
|
84
106
|
logger.info('Successfully compacted storage.');
|
|
85
107
|
} catch (e) {
|
|
86
|
-
logger.error(`Failed to compact
|
|
108
|
+
logger.error(`Failed to compact:`, e);
|
|
87
109
|
// Indirectly triggers lifeCycleEngine.stop
|
|
88
110
|
process.exit(1);
|
|
89
111
|
} finally {
|
|
112
|
+
// No need to propagate errors on completion - this merely signals that the process can exit.
|
|
113
|
+
completion.resolve();
|
|
90
114
|
// Indirectly triggers lifeCycleEngine.stop
|
|
91
115
|
process.exit(0);
|
|
92
116
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
|
|
3
|
+
import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
3
4
|
import { teardown } from '../../runner/teardown.js';
|
|
4
5
|
import { extractRunnerOptions, wrapConfigCommand } from './config-command.js';
|
|
5
|
-
import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
6
6
|
|
|
7
7
|
const COMMAND_NAME = 'teardown';
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ export function registerTearDownAction(program: Command) {
|
|
|
13
13
|
|
|
14
14
|
return teardownCommand
|
|
15
15
|
.argument('[ack]', 'Type `TEARDOWN` to confirm teardown should occur')
|
|
16
|
-
.description('Terminate all replicating
|
|
16
|
+
.description('Terminate all replicating replication streams, clear remote configuration and remove all data')
|
|
17
17
|
.action(async (ack, options) => {
|
|
18
18
|
if (ack !== 'TEARDOWN') {
|
|
19
19
|
throw new ServiceError(ErrorCode.PSYNC_S0102, 'TEARDOWN was not acknowledged.');
|
package/src/entry/entry-index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './cli-entry.js';
|
|
2
|
+
export * from './commands/compact-action.js';
|
|
2
3
|
export * from './commands/config-command.js';
|
|
3
4
|
export * from './commands/migrate-action.js';
|
|
4
5
|
export * from './commands/start-action.js';
|
|
5
6
|
export * from './commands/teardown-action.js';
|
|
6
|
-
export * from './commands/compact-action.js';
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
2
|
-
import { Counter,
|
|
2
|
+
import { Counter, MetricMetadata, MetricsFactory, ObservableGauge, UpDownCounter } from './metrics-interfaces.js';
|
|
3
3
|
|
|
4
4
|
export interface MetricsEngineOptions {
|
|
5
5
|
factory: MetricsFactory;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export interface RollingBucketMaxOptions {
|
|
2
|
+
bucketSizeMs?: number;
|
|
3
|
+
windowSizeMs?: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface Bucket {
|
|
7
|
+
// Absolute bucket id derived from floor(timestamp / bucketSizeMs).
|
|
8
|
+
id: number;
|
|
9
|
+
// Maximum reported value seen within this bucket.
|
|
10
|
+
max: number | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Tracks a rolling max over a fixed number of time buckets.
|
|
15
|
+
*
|
|
16
|
+
* The window is bucket-aligned: with the default 30s window and 5s buckets,
|
|
17
|
+
* the rolling max covers the current 5s bucket plus the previous 5 buckets.
|
|
18
|
+
*/
|
|
19
|
+
export class RollingBucketMax {
|
|
20
|
+
private readonly bucketSizeMs: number;
|
|
21
|
+
private readonly bucketCount: number;
|
|
22
|
+
// Fixed-size ring buffer keyed by bucket id modulo bucketCount.
|
|
23
|
+
private readonly buckets: Bucket[];
|
|
24
|
+
|
|
25
|
+
constructor(options: RollingBucketMaxOptions = {}) {
|
|
26
|
+
this.bucketSizeMs = options.bucketSizeMs ?? 5_000;
|
|
27
|
+
const windowSizeMs = options.windowSizeMs ?? 30_000;
|
|
28
|
+
|
|
29
|
+
if (!Number.isInteger(this.bucketSizeMs) || this.bucketSizeMs <= 0) {
|
|
30
|
+
throw new Error('bucketSizeMs must be a positive integer.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!Number.isInteger(windowSizeMs) || windowSizeMs <= 0) {
|
|
34
|
+
throw new Error('windowSizeMs must be a positive integer.');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (windowSizeMs % this.bucketSizeMs !== 0) {
|
|
38
|
+
throw new Error('windowSizeMs must be an exact multiple of bucketSizeMs.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.bucketCount = windowSizeMs / this.bucketSizeMs;
|
|
42
|
+
this.buckets = Array.from({ length: this.bucketCount }, () => ({
|
|
43
|
+
id: Number.NaN,
|
|
44
|
+
max: undefined
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Reports a new observed value into the bucket for the provided timestamp.
|
|
50
|
+
*/
|
|
51
|
+
report(value: number | undefined, timestampMs = Date.now()): void {
|
|
52
|
+
if (value == null) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.assertFiniteNumber(value, 'value');
|
|
56
|
+
this.assertFiniteNumber(timestampMs, 'timestampMs');
|
|
57
|
+
|
|
58
|
+
const bucket = this.getBucket(this.getBucketId(timestampMs));
|
|
59
|
+
bucket.max = bucket.max === undefined ? value : Math.max(bucket.max, value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Returns the maximum value across the current bucket and prior buckets still
|
|
64
|
+
* inside the rolling window, or undefined when the window has no samples.
|
|
65
|
+
*/
|
|
66
|
+
getRollingMax(timestampMs = Date.now()): number | undefined {
|
|
67
|
+
this.assertFiniteNumber(timestampMs, 'timestampMs');
|
|
68
|
+
|
|
69
|
+
const currentBucketId = this.getBucketId(timestampMs);
|
|
70
|
+
const minimumBucketId = currentBucketId - this.bucketCount + 1;
|
|
71
|
+
|
|
72
|
+
let rollingMax: number | undefined;
|
|
73
|
+
for (const bucket of this.buckets) {
|
|
74
|
+
if (bucket.max === undefined) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (bucket.id < minimumBucketId || bucket.id > currentBucketId) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
rollingMax = rollingMax === undefined ? bucket.max : Math.max(rollingMax, bucket.max);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return rollingMax;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private getBucketId(timestampMs: number): number {
|
|
89
|
+
return Math.floor(timestampMs / this.bucketSizeMs);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private getBucket(bucketId: number): Bucket {
|
|
93
|
+
const index = ((bucketId % this.bucketCount) + this.bucketCount) % this.bucketCount;
|
|
94
|
+
const bucket = this.buckets[index];
|
|
95
|
+
|
|
96
|
+
if (bucket.id !== bucketId) {
|
|
97
|
+
bucket.id = bucketId;
|
|
98
|
+
bucket.max = undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return bucket;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private assertFiniteNumber(value: number, name: string): void {
|
|
105
|
+
if (!Number.isFinite(value)) {
|
|
106
|
+
throw new Error(`${name} must be a finite number.`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export * from './MetricsEngine.js';
|
|
2
1
|
export * from './metrics-interfaces.js';
|
|
3
|
-
export * from './
|
|
2
|
+
export * from './MetricsEngine.js';
|
|
4
3
|
export * from './open-telemetry/OpenTelemetryMetricsFactory.js';
|
|
5
4
|
export * from './open-telemetry/util.js';
|
|
5
|
+
export * from './register-metrics.js';
|
|
6
|
+
export * from './RollingBucketMax.js';
|
|
@@ -6,8 +6,8 @@ import { ServiceContext } from '../../system/ServiceContext.js';
|
|
|
6
6
|
import { MetricsFactory } from '../metrics-interfaces.js';
|
|
7
7
|
import { OpenTelemetryMetricsFactory } from './OpenTelemetryMetricsFactory.js';
|
|
8
8
|
|
|
9
|
-
import pkg from '../../../package.json' with { type: 'json' };
|
|
10
9
|
import { resourceFromAttributes } from '@opentelemetry/resources';
|
|
10
|
+
import pkg from '../../../package.json' with { type: 'json' };
|
|
11
11
|
|
|
12
12
|
export function createOpenTelemetryMetricsFactory(context: ServiceContext): MetricsFactory {
|
|
13
13
|
const { configuration, lifeCycleEngine, storageEngine } = context;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { ServiceContextContainer } from '../system/ServiceContext.js';
|
|
2
|
-
import { createOpenTelemetryMetricsFactory } from './open-telemetry/util.js';
|
|
3
|
-
import { MetricsEngine } from './MetricsEngine.js';
|
|
4
1
|
import { createCoreAPIMetrics, initializeCoreAPIMetrics } from '../api/api-metrics.js';
|
|
5
2
|
import { createCoreReplicationMetrics, initializeCoreReplicationMetrics } from '../replication/replication-metrics.js';
|
|
6
3
|
import { createCoreStorageMetrics, initializeCoreStorageMetrics } from '../storage/storage-metrics.js';
|
|
4
|
+
import { ServiceContextContainer } from '../system/ServiceContext.js';
|
|
5
|
+
import { MetricsEngine } from './MetricsEngine.js';
|
|
6
|
+
import { createOpenTelemetryMetricsFactory } from './open-telemetry/util.js';
|
|
7
7
|
|
|
8
8
|
export enum MetricModes {
|
|
9
9
|
API = 'api',
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { ServiceContextContainer } from '../system/ServiceContext.js';
|
|
2
1
|
import { logger } from '@powersync/lib-services-framework';
|
|
3
2
|
import winston from 'winston';
|
|
4
3
|
import { PersistedSyncRulesContent } from '../storage/storage-index.js';
|
|
4
|
+
import { ServiceContextContainer } from '../system/ServiceContext.js';
|
|
5
5
|
|
|
6
6
|
export interface TearDownOptions {
|
|
7
7
|
/**
|
|
8
|
-
* If required, tear down any configuration/state for the specific
|
|
8
|
+
* If required, tear down any configuration/state for the specific replication stream
|
|
9
9
|
*/
|
|
10
10
|
syncRules?: PersistedSyncRulesContent[];
|
|
11
11
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { container, logger } from '@powersync/lib-services-framework';
|
|
2
2
|
import winston from 'winston';
|
|
3
|
+
import { MetricsEngine } from '../metrics/MetricsEngine.js';
|
|
3
4
|
import * as storage from '../storage/storage-index.js';
|
|
4
5
|
import { ErrorRateLimiter } from './ErrorRateLimiter.js';
|
|
5
|
-
import { MetricsEngine } from '../metrics/MetricsEngine.js';
|
|
6
6
|
|
|
7
7
|
export interface AbstractReplicationJobOptions {
|
|
8
8
|
id: string;
|
|
@@ -54,7 +54,7 @@ export abstract class AbstractReplicationJob {
|
|
|
54
54
|
* Safely stop the replication process
|
|
55
55
|
*/
|
|
56
56
|
public async stop(): Promise<void> {
|
|
57
|
-
this.logger.info(`Stopping replication job
|
|
57
|
+
this.logger.info(`Stopping replication job`);
|
|
58
58
|
this.abortController.abort();
|
|
59
59
|
await this.isReplicatingPromise;
|
|
60
60
|
}
|
|
@@ -82,5 +82,5 @@ export abstract class AbstractReplicationJob {
|
|
|
82
82
|
/**
|
|
83
83
|
* Get replication lag for this job in ms.
|
|
84
84
|
*/
|
|
85
|
-
abstract getReplicationLagMillis():
|
|
85
|
+
abstract getReplicationLagMillis(): number | undefined;
|
|
86
86
|
}
|