@powersync/service-core 0.0.0-dev-20260225160713 → 0.0.0-dev-20260511080634
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 +102 -5
- 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/auth/utils.d.ts +2 -1
- package/dist/auth/utils.js +1 -1
- package/dist/auth/utils.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 +8 -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 +8 -3
- 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 +30 -9
- package/dist/storage/BucketStorageBatch.js.map +1 -1
- package/dist/storage/BucketStorageFactory.d.ts +32 -20
- package/dist/storage/BucketStorageFactory.js +19 -16
- package/dist/storage/BucketStorageFactory.js.map +1 -1
- package/dist/storage/ChecksumCache.d.ts +5 -2
- package/dist/storage/ChecksumCache.js +8 -4
- package/dist/storage/ChecksumCache.js.map +1 -1
- package/dist/storage/PersistedSyncRulesContent.d.ts +9 -3
- package/dist/storage/PersistedSyncRulesContent.js +25 -5
- package/dist/storage/PersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/ReplicationEventPayload.d.ts +1 -1
- package/dist/storage/SourceTable.d.ts +10 -5
- package/dist/storage/SourceTable.js +3 -3
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/StorageVersionConfig.d.ts +34 -1
- package/dist/storage/StorageVersionConfig.js +40 -7
- package/dist/storage/StorageVersionConfig.js.map +1 -1
- package/dist/storage/SyncRulesBucketStorage.d.ts +63 -11
- 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 +5 -8
- package/dist/sync/BucketChecksumState.js +118 -116
- 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.d.ts +1 -0
- package/dist/sync/util.js +10 -0
- 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/auth/utils.ts +5 -2
- 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 +8 -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 +8 -3
- 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 +34 -10
- package/src/storage/BucketStorageFactory.ts +51 -34
- package/src/storage/ChecksumCache.ts +14 -6
- package/src/storage/PersistedSyncRulesContent.ts +36 -7
- package/src/storage/ReplicationEventPayload.ts +1 -1
- package/src/storage/SourceTable.ts +10 -4
- package/src/storage/StorageVersionConfig.ts +55 -7
- package/src/storage/SyncRulesBucketStorage.ts +77 -11
- 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 +142 -139
- package/src/sync/RequestTracker.ts +1 -1
- package/src/sync/sync-index.ts +2 -2
- package/src/sync/sync.ts +4 -4
- package/src/sync/util.ts +11 -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 +26 -7
- package/test/src/checksum_cache.test.ts +102 -57
- package/test/src/config.test.ts +1 -0
- 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 +209 -80
- package/test/src/util/protocol_types.test.ts +1 -1
- package/test/src/utils.ts +9 -0
- package/test/tsconfig.json +0 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/vitest.config.ts +1 -1
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
ChecksumMap,
|
|
8
8
|
InternalOpId,
|
|
9
9
|
JwtPayload,
|
|
10
|
+
ParameterSetLimitExceededError,
|
|
10
11
|
ReplicationCheckpoint,
|
|
11
12
|
StreamingSyncRequest,
|
|
12
13
|
SyncContext,
|
|
@@ -14,15 +15,42 @@ import {
|
|
|
14
15
|
} from '@/index.js';
|
|
15
16
|
import { JSONBig } from '@powersync/service-jsonbig';
|
|
16
17
|
import {
|
|
17
|
-
|
|
18
|
+
ParameterIndexLookupCreator,
|
|
18
19
|
ScopedParameterLookup,
|
|
20
|
+
SourceTableInterface,
|
|
19
21
|
SqliteJsonRow,
|
|
22
|
+
SqliteRow,
|
|
20
23
|
SqlSyncRules,
|
|
24
|
+
TablePattern,
|
|
21
25
|
versionedHydrationState
|
|
22
26
|
} from '@powersync/service-sync-rules';
|
|
27
|
+
import { ParameterLookupScope } from '@powersync/service-sync-rules/src/HydrationState.js';
|
|
23
28
|
import { beforeEach, describe, expect, test } from 'vitest';
|
|
24
29
|
|
|
25
30
|
describe('BucketChecksumState', () => {
|
|
31
|
+
const LOOKUP_SOURCE: ParameterIndexLookupCreator = {
|
|
32
|
+
get defaultLookupScope(): ParameterLookupScope {
|
|
33
|
+
return {
|
|
34
|
+
lookupName: 'lookup',
|
|
35
|
+
queryId: '0',
|
|
36
|
+
source: LOOKUP_SOURCE
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
getSourceTables(): Set<TablePattern> {
|
|
40
|
+
return new Set();
|
|
41
|
+
},
|
|
42
|
+
evaluateParameterRow(_sourceTable: SourceTableInterface, _row: SqliteRow) {
|
|
43
|
+
return [];
|
|
44
|
+
},
|
|
45
|
+
tableSyncsParameters(_table: SourceTableInterface): boolean {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function lookupScope(lookupName: string, queryId: string): ParameterLookupScope {
|
|
51
|
+
return { lookupName, queryId, source: LOOKUP_SOURCE };
|
|
52
|
+
}
|
|
53
|
+
|
|
26
54
|
// Single global[] bucket.
|
|
27
55
|
// We don't care about data in these tests
|
|
28
56
|
const SYNC_RULES_GLOBAL = SqlSyncRules.fromYaml(
|
|
@@ -67,6 +95,10 @@ bucket_definitions:
|
|
|
67
95
|
const syncRequest: StreamingSyncRequest = {};
|
|
68
96
|
const tokenPayload = new JwtPayload({ sub: '' });
|
|
69
97
|
|
|
98
|
+
function bucketStarts(requests: { bucket: string; start: InternalOpId }[]) {
|
|
99
|
+
return new Map(requests.map((request) => [request.bucket, request.start]));
|
|
100
|
+
}
|
|
101
|
+
|
|
70
102
|
test('global bucket with update', async () => {
|
|
71
103
|
const storage = new MockBucketChecksumStateStorage();
|
|
72
104
|
// Set intial state
|
|
@@ -94,14 +126,14 @@ bucket_definitions:
|
|
|
94
126
|
streams: [{ name: 'global', is_default: true, errors: [] }]
|
|
95
127
|
}
|
|
96
128
|
});
|
|
97
|
-
expect(line.bucketsToFetch).
|
|
129
|
+
expect(line.bucketsToFetch).toMatchObject([
|
|
98
130
|
{
|
|
99
131
|
bucket: '1#global[]',
|
|
100
132
|
priority: 3
|
|
101
133
|
}
|
|
102
134
|
]);
|
|
103
135
|
// This is the bucket data to be fetched
|
|
104
|
-
expect(line.getFilteredBucketPositions()).toEqual(new Map([['1#global[]', 0n]]));
|
|
136
|
+
expect(bucketStarts(line.getFilteredBucketPositions())).toEqual(new Map([['1#global[]', 0n]]));
|
|
105
137
|
|
|
106
138
|
// This similuates the bucket data being sent
|
|
107
139
|
line.advance();
|
|
@@ -132,7 +164,7 @@ bucket_definitions:
|
|
|
132
164
|
write_checkpoint: undefined
|
|
133
165
|
}
|
|
134
166
|
});
|
|
135
|
-
expect(line2.getFilteredBucketPositions()).toEqual(new Map([['1#global[]', 1n]]));
|
|
167
|
+
expect(bucketStarts(line2.getFilteredBucketPositions())).toEqual(new Map([['1#global[]', 1n]]));
|
|
136
168
|
});
|
|
137
169
|
|
|
138
170
|
test('global bucket with initial state', async () => {
|
|
@@ -166,14 +198,14 @@ bucket_definitions:
|
|
|
166
198
|
streams: [{ name: 'global', is_default: true, errors: [] }]
|
|
167
199
|
}
|
|
168
200
|
});
|
|
169
|
-
expect(line.bucketsToFetch).
|
|
201
|
+
expect(line.bucketsToFetch).toMatchObject([
|
|
170
202
|
{
|
|
171
203
|
bucket: '1#global[]',
|
|
172
204
|
priority: 3
|
|
173
205
|
}
|
|
174
206
|
]);
|
|
175
207
|
// This is the main difference between this and the previous test
|
|
176
|
-
expect(line.getFilteredBucketPositions()).toEqual(new Map([['1#global[]', 1n]]));
|
|
208
|
+
expect(bucketStarts(line.getFilteredBucketPositions())).toEqual(new Map([['1#global[]', 1n]]));
|
|
177
209
|
});
|
|
178
210
|
|
|
179
211
|
test('multiple static buckets', async () => {
|
|
@@ -206,7 +238,7 @@ bucket_definitions:
|
|
|
206
238
|
streams: [{ name: 'global', is_default: true, errors: [] }]
|
|
207
239
|
}
|
|
208
240
|
});
|
|
209
|
-
expect(line.bucketsToFetch).
|
|
241
|
+
expect(line.bucketsToFetch).toMatchObject([
|
|
210
242
|
{
|
|
211
243
|
bucket: '2#global[1]',
|
|
212
244
|
priority: 3
|
|
@@ -274,13 +306,13 @@ bucket_definitions:
|
|
|
274
306
|
streams: [{ name: 'global', is_default: true, errors: [] }]
|
|
275
307
|
}
|
|
276
308
|
});
|
|
277
|
-
expect(line.bucketsToFetch).
|
|
309
|
+
expect(line.bucketsToFetch).toMatchObject([
|
|
278
310
|
{
|
|
279
311
|
bucket: '1#global[]',
|
|
280
312
|
priority: 3
|
|
281
313
|
}
|
|
282
314
|
]);
|
|
283
|
-
expect(line.getFilteredBucketPositions()).toEqual(new Map([['1#global[]', 0n]]));
|
|
315
|
+
expect(bucketStarts(line.getFilteredBucketPositions())).toEqual(new Map([['1#global[]', 0n]]));
|
|
284
316
|
});
|
|
285
317
|
|
|
286
318
|
test('invalidating individual bucket', async () => {
|
|
@@ -337,7 +369,7 @@ bucket_definitions:
|
|
|
337
369
|
write_checkpoint: undefined
|
|
338
370
|
}
|
|
339
371
|
});
|
|
340
|
-
expect(line2.bucketsToFetch).
|
|
372
|
+
expect(line2.bucketsToFetch).toMatchObject([{ bucket: '2#global[1]', priority: 3 }]);
|
|
341
373
|
});
|
|
342
374
|
|
|
343
375
|
test('invalidating all buckets', async () => {
|
|
@@ -387,7 +419,7 @@ bucket_definitions:
|
|
|
387
419
|
write_checkpoint: undefined
|
|
388
420
|
}
|
|
389
421
|
});
|
|
390
|
-
expect(line2.bucketsToFetch).
|
|
422
|
+
expect(line2.bucketsToFetch).toMatchObject([
|
|
391
423
|
{ bucket: '2#global[1]', priority: 3 },
|
|
392
424
|
{ bucket: '2#global[2]', priority: 3 }
|
|
393
425
|
]);
|
|
@@ -424,7 +456,7 @@ bucket_definitions:
|
|
|
424
456
|
streams: [{ name: 'global', is_default: true, errors: [] }]
|
|
425
457
|
}
|
|
426
458
|
});
|
|
427
|
-
expect(line.bucketsToFetch).
|
|
459
|
+
expect(line.bucketsToFetch).toMatchObject([
|
|
428
460
|
{
|
|
429
461
|
bucket: '2#global[1]',
|
|
430
462
|
priority: 3
|
|
@@ -436,7 +468,7 @@ bucket_definitions:
|
|
|
436
468
|
]);
|
|
437
469
|
|
|
438
470
|
// This is the bucket data to be fetched
|
|
439
|
-
expect(line.getFilteredBucketPositions()).toEqual(
|
|
471
|
+
expect(bucketStarts(line.getFilteredBucketPositions())).toEqual(
|
|
440
472
|
new Map([
|
|
441
473
|
['2#global[1]', 0n],
|
|
442
474
|
['2#global[2]', 0n]
|
|
@@ -477,7 +509,7 @@ bucket_definitions:
|
|
|
477
509
|
}
|
|
478
510
|
});
|
|
479
511
|
// This should contain both buckets, even though only one changed.
|
|
480
|
-
expect(line2.bucketsToFetch).
|
|
512
|
+
expect(line2.bucketsToFetch).toMatchObject([
|
|
481
513
|
{
|
|
482
514
|
bucket: '2#global[1]',
|
|
483
515
|
priority: 3
|
|
@@ -488,7 +520,7 @@ bucket_definitions:
|
|
|
488
520
|
}
|
|
489
521
|
]);
|
|
490
522
|
|
|
491
|
-
expect(line2.getFilteredBucketPositions()).toEqual(
|
|
523
|
+
expect(bucketStarts(line2.getFilteredBucketPositions())).toEqual(
|
|
492
524
|
new Map([
|
|
493
525
|
['2#global[1]', 3n],
|
|
494
526
|
['2#global[2]', 1n]
|
|
@@ -513,7 +545,7 @@ bucket_definitions:
|
|
|
513
545
|
|
|
514
546
|
const line = (await state.buildNextCheckpointLine({
|
|
515
547
|
base: storage.makeCheckpoint(1n, (lookups) => {
|
|
516
|
-
expect(lookups).toEqual([ScopedParameterLookup.direct(
|
|
548
|
+
expect(lookups).toEqual([ScopedParameterLookup.direct(lookupScope('by_project', '1'), ['u1'])]);
|
|
517
549
|
return [{ id: 1 }, { id: 2 }];
|
|
518
550
|
}),
|
|
519
551
|
writeCheckpoint: null,
|
|
@@ -548,7 +580,7 @@ bucket_definitions:
|
|
|
548
580
|
write_checkpoint: undefined
|
|
549
581
|
}
|
|
550
582
|
});
|
|
551
|
-
expect(line.bucketsToFetch).
|
|
583
|
+
expect(line.bucketsToFetch).toMatchObject([
|
|
552
584
|
{
|
|
553
585
|
bucket: '3#by_project[1]',
|
|
554
586
|
priority: 3
|
|
@@ -560,7 +592,7 @@ bucket_definitions:
|
|
|
560
592
|
]);
|
|
561
593
|
line.advance();
|
|
562
594
|
// This is the bucket data to be fetched
|
|
563
|
-
expect(line.getFilteredBucketPositions()).toEqual(
|
|
595
|
+
expect(bucketStarts(line.getFilteredBucketPositions())).toEqual(
|
|
564
596
|
new Map([
|
|
565
597
|
['3#by_project[1]', 0n],
|
|
566
598
|
['3#by_project[2]', 0n]
|
|
@@ -574,7 +606,7 @@ bucket_definitions:
|
|
|
574
606
|
// Now we get a new line
|
|
575
607
|
const line2 = (await state.buildNextCheckpointLine({
|
|
576
608
|
base: storage.makeCheckpoint(2n, (lookups) => {
|
|
577
|
-
expect(lookups).toEqual([ScopedParameterLookup.direct(
|
|
609
|
+
expect(lookups).toEqual([ScopedParameterLookup.direct(lookupScope('by_project', '1'), ['u1'])]);
|
|
578
610
|
return [{ id: 1 }, { id: 2 }, { id: 3 }];
|
|
579
611
|
}),
|
|
580
612
|
writeCheckpoint: null,
|
|
@@ -602,7 +634,7 @@ bucket_definitions:
|
|
|
602
634
|
write_checkpoint: undefined
|
|
603
635
|
}
|
|
604
636
|
});
|
|
605
|
-
expect(line2.getFilteredBucketPositions()).toEqual(new Map([['3#by_project[3]', 0n]]));
|
|
637
|
+
expect(bucketStarts(line2.getFilteredBucketPositions())).toEqual(new Map([['3#by_project[3]', 0n]]));
|
|
606
638
|
});
|
|
607
639
|
|
|
608
640
|
describe('streams', () => {
|
|
@@ -869,19 +901,127 @@ config:
|
|
|
869
901
|
expect(logData[0].parameter_query_results).toBe(3);
|
|
870
902
|
});
|
|
871
903
|
|
|
872
|
-
test('throws error with breakdown when
|
|
904
|
+
test('throws error with breakdown when too many parameters were fetched', async () => {
|
|
905
|
+
const syncRules = SqlSyncRules.fromYaml(
|
|
906
|
+
`
|
|
907
|
+
config:
|
|
908
|
+
edition: 3
|
|
909
|
+
|
|
910
|
+
streams:
|
|
911
|
+
a:
|
|
912
|
+
auto_subscribe: true
|
|
913
|
+
query: SELECT * FROM a WHERE id IN (SELECT id FROM magic_sequence WHERE count0 = auth.parameter('a'))
|
|
914
|
+
b:
|
|
915
|
+
auto_subscribe: true
|
|
916
|
+
query: SELECT * FROM a WHERE id IN (SELECT id FROM magic_sequence WHERE count1 = auth.parameter('b'))
|
|
917
|
+
c:
|
|
918
|
+
auto_subscribe: true
|
|
919
|
+
query: SELECT * FROM a WHERE id IN (SELECT id FROM magic_sequence WHERE count1 = auth.parameter('c'))
|
|
920
|
+
`,
|
|
921
|
+
{ defaultSchema: 'public' }
|
|
922
|
+
).config.hydrate({
|
|
923
|
+
hydrationState: versionedHydrationState(1)
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
const storage = new MockBucketChecksumStateStorage();
|
|
927
|
+
|
|
928
|
+
const errorMessages: string[] = [];
|
|
929
|
+
const errorData: any[] = [];
|
|
930
|
+
const mockLogger = {
|
|
931
|
+
info: () => {},
|
|
932
|
+
error: (message: string, data: any) => {
|
|
933
|
+
errorMessages.push(message);
|
|
934
|
+
errorData.push(data);
|
|
935
|
+
},
|
|
936
|
+
warn: () => {},
|
|
937
|
+
debug: () => {}
|
|
938
|
+
};
|
|
939
|
+
|
|
940
|
+
const smallContext = new SyncContext({
|
|
941
|
+
maxBuckets: 100,
|
|
942
|
+
maxParameterQueryResults: 55,
|
|
943
|
+
maxDataFetchConcurrency: 10
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
const state = new BucketChecksumState({
|
|
947
|
+
syncContext: smallContext,
|
|
948
|
+
tokenPayload: new JwtPayload({
|
|
949
|
+
sub: 'u1',
|
|
950
|
+
// Create 60 total results: 30 a + 20 b + 10 c
|
|
951
|
+
a: BigInt(30),
|
|
952
|
+
b: BigInt(20),
|
|
953
|
+
c: BigInt(10)
|
|
954
|
+
}),
|
|
955
|
+
syncRequest,
|
|
956
|
+
syncRules,
|
|
957
|
+
bucketStorage: storage,
|
|
958
|
+
logger: mockLogger as any
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
await expect(
|
|
962
|
+
state.buildNextCheckpointLine({
|
|
963
|
+
base: storage.makeCheckpoint(1n, (lookups, limit) => {
|
|
964
|
+
expect(lookups).toHaveLength(1);
|
|
965
|
+
const [{ values }] = lookups;
|
|
966
|
+
expect(values[0]).toStrictEqual('lookup');
|
|
967
|
+
|
|
968
|
+
const count = Number(values[2]); // The count parameter from streams
|
|
969
|
+
if (count > limit) {
|
|
970
|
+
throw new ParameterSetLimitExceededError(limit);
|
|
971
|
+
}
|
|
972
|
+
return Array.from({ length: count }, (_, i) => ({ '0': BigInt(i) }));
|
|
973
|
+
}),
|
|
974
|
+
writeCheckpoint: null,
|
|
975
|
+
update: CHECKPOINT_INVALIDATE_ALL
|
|
976
|
+
})
|
|
977
|
+
).rejects.toThrow('Too many parameter query results (limit of 55)');
|
|
978
|
+
|
|
979
|
+
// Verify error log includes breakdown
|
|
980
|
+
expect(errorMessages[0]).toContain('Invoked parameter queries by definition:');
|
|
981
|
+
expect(errorMessages[0]).toContain('Stream a evaluating parameter on magic_sequence: 30 results.');
|
|
982
|
+
expect(errorMessages[0]).toContain('Stream b evaluating parameter on magic_sequence: 20 results.');
|
|
983
|
+
expect(errorMessages[0]).toContain(
|
|
984
|
+
'Stream c evaluating parameter on magic_sequence exceeded the remaining limit of 5 available results.'
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
expect(errorData[0].checkpoint).toEqual(1n);
|
|
988
|
+
expect(errorData[0].parameterResults).toEqual([
|
|
989
|
+
{
|
|
990
|
+
definition: 'Stream a evaluating parameter on magic_sequence',
|
|
991
|
+
didExceedLimit: false,
|
|
992
|
+
resultsOrLimit: 30
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
definition: 'Stream b evaluating parameter on magic_sequence',
|
|
996
|
+
didExceedLimit: false,
|
|
997
|
+
resultsOrLimit: 20
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
definition: 'Stream c evaluating parameter on magic_sequence',
|
|
1001
|
+
didExceedLimit: true,
|
|
1002
|
+
resultsOrLimit: 5
|
|
1003
|
+
}
|
|
1004
|
+
]);
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
test('throws error with breakdown when bucket limit is exceeded', async () => {
|
|
1008
|
+
// These streams are designed to return buckets without consuming too many parameters (as exceeding that limit is
|
|
1009
|
+
// a different error).
|
|
873
1010
|
const SYNC_RULES_MULTI = SqlSyncRules.fromYaml(
|
|
874
1011
|
`
|
|
875
|
-
|
|
1012
|
+
config:
|
|
1013
|
+
edition: 3
|
|
1014
|
+
|
|
1015
|
+
streams:
|
|
876
1016
|
projects:
|
|
877
|
-
|
|
878
|
-
|
|
1017
|
+
auto_subscribe: true
|
|
1018
|
+
query: SELECT id FROM projects WHERE p IN auth.parameter('a')
|
|
879
1019
|
tasks:
|
|
880
|
-
|
|
881
|
-
|
|
1020
|
+
auto_subscribe: true
|
|
1021
|
+
query: SELECT id FROM tasks WHERE p IN auth.parameter('b')
|
|
882
1022
|
comments:
|
|
883
|
-
|
|
884
|
-
|
|
1023
|
+
auto_subscribe: true
|
|
1024
|
+
query: SELECT id FROM comments WHERE p IN auth.parameter('c')
|
|
885
1025
|
`,
|
|
886
1026
|
{ defaultSchema: 'public' }
|
|
887
1027
|
).config.hydrate({ hydrationState: versionedHydrationState(4) });
|
|
@@ -901,74 +1041,66 @@ bucket_definitions:
|
|
|
901
1041
|
};
|
|
902
1042
|
|
|
903
1043
|
const smallContext = new SyncContext({
|
|
904
|
-
maxBuckets:
|
|
905
|
-
maxParameterQueryResults:
|
|
1044
|
+
maxBuckets: 50,
|
|
1045
|
+
maxParameterQueryResults: 10,
|
|
906
1046
|
maxDataFetchConcurrency: 10
|
|
907
1047
|
});
|
|
908
1048
|
|
|
909
1049
|
const state = new BucketChecksumState({
|
|
910
1050
|
syncContext: smallContext,
|
|
911
|
-
tokenPayload: new JwtPayload({
|
|
1051
|
+
tokenPayload: new JwtPayload({
|
|
1052
|
+
sub: 'u1',
|
|
1053
|
+
// Create 60 total results: 30 projects + 20 tasks + 10 comments
|
|
1054
|
+
a: Array.from({ length: 30 }, (_, i) => BigInt(i)),
|
|
1055
|
+
b: Array.from({ length: 20 }, (_, i) => BigInt(i)),
|
|
1056
|
+
c: Array.from({ length: 10 }, (_, i) => BigInt(i))
|
|
1057
|
+
}),
|
|
912
1058
|
syncRequest,
|
|
913
1059
|
syncRules: SYNC_RULES_MULTI,
|
|
914
1060
|
bucketStorage: storage,
|
|
915
1061
|
logger: mockLogger as any
|
|
916
1062
|
});
|
|
917
1063
|
|
|
918
|
-
|
|
919
|
-
const projectIds = Array.from({ length: 30 }, (_, i) => ({ id: i + 1 }));
|
|
920
|
-
const taskIds = Array.from({ length: 20 }, (_, i) => ({ id: i + 1 }));
|
|
921
|
-
const commentIds = Array.from({ length: 10 }, (_, i) => ({ id: i + 1 }));
|
|
922
|
-
|
|
923
|
-
for (let i = 1; i <= 30; i++) {
|
|
924
|
-
storage.updateTestChecksum({ bucket: `projects[${i}]`, checksum: 1, count: 1 });
|
|
925
|
-
}
|
|
926
|
-
for (let i = 1; i <= 20; i++) {
|
|
927
|
-
storage.updateTestChecksum({ bucket: `tasks[${i}]`, checksum: 1, count: 1 });
|
|
928
|
-
}
|
|
929
|
-
for (let i = 1; i <= 10; i++) {
|
|
930
|
-
storage.updateTestChecksum({ bucket: `comments[${i}]`, checksum: 1, count: 1 });
|
|
931
|
-
}
|
|
932
|
-
|
|
1064
|
+
let invokedParameterQueries = 0;
|
|
933
1065
|
await expect(
|
|
934
1066
|
state.buildNextCheckpointLine({
|
|
935
|
-
base: storage.makeCheckpoint(1n, (
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (lookupName === 'projects') {
|
|
939
|
-
return projectIds;
|
|
940
|
-
} else if (lookupName === 'tasks') {
|
|
941
|
-
return taskIds;
|
|
942
|
-
} else {
|
|
943
|
-
return commentIds;
|
|
944
|
-
}
|
|
1067
|
+
base: storage.makeCheckpoint(1n, () => {
|
|
1068
|
+
invokedParameterQueries++;
|
|
1069
|
+
return [{ '0': 'user' }];
|
|
945
1070
|
}),
|
|
946
1071
|
writeCheckpoint: null,
|
|
947
1072
|
update: CHECKPOINT_INVALIDATE_ALL
|
|
948
1073
|
})
|
|
949
|
-
).rejects.toThrow('Too many
|
|
1074
|
+
).rejects.toThrow('Too many buckets: 60 (limit of 50');
|
|
1075
|
+
expect(invokedParameterQueries).toStrictEqual(0);
|
|
950
1076
|
|
|
951
1077
|
// Verify error log includes breakdown
|
|
952
|
-
expect(errorMessages[0]).toContain('
|
|
1078
|
+
expect(errorMessages[0]).toContain('Buckets by definition:');
|
|
953
1079
|
expect(errorMessages[0]).toContain('projects: 30');
|
|
954
1080
|
expect(errorMessages[0]).toContain('tasks: 20');
|
|
955
1081
|
expect(errorMessages[0]).toContain('comments: 10');
|
|
956
1082
|
|
|
957
|
-
expect(errorData[0].
|
|
958
|
-
expect(errorData[0].
|
|
1083
|
+
expect(errorData[0].checkpoint).toEqual(1n);
|
|
1084
|
+
expect(errorData[0].buckets).toBe(60);
|
|
1085
|
+
expect(errorData[0].buckets_by_definition).toEqual({
|
|
959
1086
|
projects: 30,
|
|
960
1087
|
tasks: 20,
|
|
961
1088
|
comments: 10
|
|
962
1089
|
});
|
|
963
1090
|
});
|
|
964
1091
|
|
|
965
|
-
test('limits breakdown to top 10 definitions', async () => {
|
|
966
|
-
// Create sync
|
|
967
|
-
let yamlDefinitions =
|
|
1092
|
+
test('limits bucket breakdown to top 10 definitions', async () => {
|
|
1093
|
+
// Create sync streams with 15 different definitions with dynamic parameters
|
|
1094
|
+
let yamlDefinitions = `
|
|
1095
|
+
config:
|
|
1096
|
+
edition: 3
|
|
1097
|
+
|
|
1098
|
+
streams:
|
|
1099
|
+
`;
|
|
968
1100
|
for (let i = 1; i <= 15; i++) {
|
|
969
1101
|
yamlDefinitions += ` def${i}:\n`;
|
|
970
|
-
yamlDefinitions += `
|
|
971
|
-
yamlDefinitions += `
|
|
1102
|
+
yamlDefinitions += ` auto_subscribe: true\n`;
|
|
1103
|
+
yamlDefinitions += ` query: SELECT * FROM tbl WHERE b = auth.parameter('${i}')\n`;
|
|
972
1104
|
}
|
|
973
1105
|
|
|
974
1106
|
const SYNC_RULES_MANY = SqlSyncRules.fromYaml(yamlDefinitions, { defaultSchema: 'public' }).config.hydrate({
|
|
@@ -988,35 +1120,32 @@ bucket_definitions:
|
|
|
988
1120
|
};
|
|
989
1121
|
|
|
990
1122
|
const smallContext = new SyncContext({
|
|
991
|
-
maxBuckets:
|
|
1123
|
+
maxBuckets: 10,
|
|
992
1124
|
maxParameterQueryResults: 10,
|
|
993
1125
|
maxDataFetchConcurrency: 10
|
|
994
1126
|
});
|
|
995
1127
|
|
|
996
1128
|
const state = new BucketChecksumState({
|
|
997
1129
|
syncContext: smallContext,
|
|
998
|
-
tokenPayload: new JwtPayload({
|
|
1130
|
+
tokenPayload: new JwtPayload({
|
|
1131
|
+
sub: 'u1',
|
|
1132
|
+
...Object.fromEntries(Array.from({ length: 30 }, (_, i) => [i.toString(), BigInt(i)]))
|
|
1133
|
+
}),
|
|
999
1134
|
syncRequest,
|
|
1000
1135
|
syncRules: SYNC_RULES_MANY,
|
|
1001
1136
|
bucketStorage: storage,
|
|
1002
1137
|
logger: mockLogger as any
|
|
1003
1138
|
});
|
|
1004
1139
|
|
|
1005
|
-
// Each definition creates one bucket, total 15 buckets
|
|
1006
|
-
for (let i = 1; i <= 15; i++) {
|
|
1007
|
-
storage.updateTestChecksum({ bucket: `def${i}[${i}]`, checksum: 1, count: 1 });
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
1140
|
await expect(
|
|
1011
1141
|
state.buildNextCheckpointLine({
|
|
1012
1142
|
base: storage.makeCheckpoint(1n, (lookups) => {
|
|
1013
|
-
|
|
1014
|
-
return [{ id: 1 }];
|
|
1143
|
+
return [{ '0': 'user' }];
|
|
1015
1144
|
}),
|
|
1016
1145
|
writeCheckpoint: null,
|
|
1017
1146
|
update: CHECKPOINT_INVALIDATE_ALL
|
|
1018
1147
|
})
|
|
1019
|
-
).rejects.toThrow('Too many
|
|
1148
|
+
).rejects.toThrow('Too many buckets: 15 (limit of 10)');
|
|
1020
1149
|
|
|
1021
1150
|
// Verify only top 10 are shown
|
|
1022
1151
|
const errorMessage = errorMessages[0];
|
|
@@ -1044,9 +1173,9 @@ class MockBucketChecksumStateStorage implements BucketChecksumStateStorage {
|
|
|
1044
1173
|
this.filter?.({ invalidate: true });
|
|
1045
1174
|
}
|
|
1046
1175
|
|
|
1047
|
-
async getChecksums(
|
|
1176
|
+
async getChecksums(_checkpoint: InternalOpId, buckets: { bucket: string }[]): Promise<ChecksumMap> {
|
|
1048
1177
|
return new Map<string, BucketChecksum>(
|
|
1049
|
-
buckets.map((bucket) => {
|
|
1178
|
+
buckets.map(({ bucket }) => {
|
|
1050
1179
|
const checksum = this.state.get(bucket);
|
|
1051
1180
|
return [
|
|
1052
1181
|
bucket,
|
|
@@ -1062,16 +1191,16 @@ class MockBucketChecksumStateStorage implements BucketChecksumStateStorage {
|
|
|
1062
1191
|
|
|
1063
1192
|
makeCheckpoint(
|
|
1064
1193
|
opId: InternalOpId,
|
|
1065
|
-
parameters?: (lookups: ScopedParameterLookup[]) => SqliteJsonRow[]
|
|
1194
|
+
parameters?: (lookups: ScopedParameterLookup[], limit: number) => SqliteJsonRow[]
|
|
1066
1195
|
): ReplicationCheckpoint {
|
|
1067
1196
|
return {
|
|
1068
1197
|
checkpoint: opId,
|
|
1069
1198
|
lsn: String(opId),
|
|
1070
|
-
getParameterSets: async (lookups: ScopedParameterLookup[]) => {
|
|
1199
|
+
getParameterSets: async (lookups: ScopedParameterLookup[], limit) => {
|
|
1071
1200
|
if (parameters == null) {
|
|
1072
1201
|
throw new Error(`getParametersSets not defined for checkpoint ${opId}`);
|
|
1073
1202
|
}
|
|
1074
|
-
return parameters(lookups);
|
|
1203
|
+
return parameters(lookups, limit);
|
|
1075
1204
|
}
|
|
1076
1205
|
};
|
|
1077
1206
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StreamingSyncRequest } from '@/index.js';
|
|
2
2
|
import { schema } from '@powersync/lib-services-framework';
|
|
3
|
-
import { describe,
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
4
|
|
|
5
5
|
describe('protocol types', () => {
|
|
6
6
|
describe('StreamingSyncRequest', () => {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Removes the source property from an object.
|
|
3
|
+
*
|
|
4
|
+
* This is for tests where we don't care about this value, and it adds a lot of noise in the output.
|
|
5
|
+
*/
|
|
6
|
+
export function removeSource<T extends { source?: any }>(obj: T): Omit<T, 'source'> {
|
|
7
|
+
const { source, ...rest } = obj;
|
|
8
|
+
return rest;
|
|
9
|
+
}
|