@powersync/service-core 1.20.4 → 1.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- 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/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/metrics-index.d.ts +3 -3
- package/dist/metrics/metrics-index.js +3 -3
- 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 +1 -1
- package/dist/replication/AbstractReplicationJob.js +1 -1
- package/dist/replication/AbstractReplicationJob.js.map +1 -1
- package/dist/replication/AbstractReplicator.d.ts +6 -6
- package/dist/replication/AbstractReplicator.js +21 -21
- package/dist/replication/AbstractReplicator.js.map +1 -1
- package/dist/replication/replication-index.d.ts +3 -3
- package/dist/replication/replication-index.js +3 -3
- 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/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/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/metrics-index.ts +3 -3
- 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 +2 -2
- package/src/replication/AbstractReplicator.ts +23 -23
- package/src/replication/replication-index.ts +3 -3
- 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/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/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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BasicRouterRequest, Context, JwtPayload, SyncRulesBucketStorage } from '@/index.js';
|
|
2
2
|
import { RouterResponse, ServiceError, logger } from '@powersync/lib-services-framework';
|
|
3
|
-
import { SqlSyncRules } from '@powersync/service-sync-rules';
|
|
3
|
+
import { DEFAULT_HYDRATION_STATE, SqlSyncRules } from '@powersync/service-sync-rules';
|
|
4
4
|
import { Readable, Writable } from 'stream';
|
|
5
5
|
import { pipeline } from 'stream/promises';
|
|
6
6
|
import { describe, expect, it } from 'vitest';
|
|
@@ -8,7 +8,6 @@ import winston from 'winston';
|
|
|
8
8
|
import { syncStreamed } from '../../../src/routes/endpoints/sync-stream.js';
|
|
9
9
|
import { DEFAULT_PARAM_LOGGING_FORMAT_OPTIONS, limitParamsForLogging } from '../../../src/util/param-logging.js';
|
|
10
10
|
import { mockServiceContext } from './mocks.js';
|
|
11
|
-
import { DEFAULT_HYDRATION_STATE } from '@powersync/service-sync-rules';
|
|
12
11
|
|
|
13
12
|
describe('Stream Route', () => {
|
|
14
13
|
describe('compressed stream', () => {
|
|
@@ -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,
|
|
@@ -15,13 +16,12 @@ import {
|
|
|
15
16
|
import { JSONBig } from '@powersync/service-jsonbig';
|
|
16
17
|
import {
|
|
17
18
|
ParameterIndexLookupCreator,
|
|
18
|
-
|
|
19
|
+
ParameterLookupRows,
|
|
19
20
|
ScopedParameterLookup,
|
|
20
|
-
|
|
21
|
+
SourceTableInterface,
|
|
21
22
|
SqliteRow,
|
|
22
23
|
SqlSyncRules,
|
|
23
24
|
TablePattern,
|
|
24
|
-
SourceTableInterface,
|
|
25
25
|
versionedHydrationState
|
|
26
26
|
} from '@powersync/service-sync-rules';
|
|
27
27
|
import { ParameterLookupScope } from '@powersync/service-sync-rules/src/HydrationState.js';
|
|
@@ -546,7 +546,7 @@ bucket_definitions:
|
|
|
546
546
|
const line = (await state.buildNextCheckpointLine({
|
|
547
547
|
base: storage.makeCheckpoint(1n, (lookups) => {
|
|
548
548
|
expect(lookups).toEqual([ScopedParameterLookup.direct(lookupScope('by_project', '1'), ['u1'])]);
|
|
549
|
-
return [{ id: 1 }, { id: 2 }];
|
|
549
|
+
return [{ lookup: lookups[0], rows: [{ id: 1 }, { id: 2 }] }];
|
|
550
550
|
}),
|
|
551
551
|
writeCheckpoint: null,
|
|
552
552
|
update: CHECKPOINT_INVALIDATE_ALL
|
|
@@ -607,7 +607,7 @@ bucket_definitions:
|
|
|
607
607
|
const line2 = (await state.buildNextCheckpointLine({
|
|
608
608
|
base: storage.makeCheckpoint(2n, (lookups) => {
|
|
609
609
|
expect(lookups).toEqual([ScopedParameterLookup.direct(lookupScope('by_project', '1'), ['u1'])]);
|
|
610
|
-
return [{ id: 1 }, { id: 2 }, { id: 3 }];
|
|
610
|
+
return [{ lookup: lookups[0], rows: [{ id: 1 }, { id: 2 }, { id: 3 }] }];
|
|
611
611
|
}),
|
|
612
612
|
writeCheckpoint: null,
|
|
613
613
|
update: {
|
|
@@ -890,7 +890,8 @@ config:
|
|
|
890
890
|
|
|
891
891
|
const line = (await state.buildNextCheckpointLine({
|
|
892
892
|
base: storage.makeCheckpoint(1n, (lookups) => {
|
|
893
|
-
|
|
893
|
+
expect(lookups).toHaveLength(1);
|
|
894
|
+
return [{ lookup: lookups[0], rows: [{ id: 1 }, { id: 2 }, { id: 3 }] }];
|
|
894
895
|
}),
|
|
895
896
|
writeCheckpoint: null,
|
|
896
897
|
update: CHECKPOINT_INVALIDATE_ALL
|
|
@@ -901,19 +902,192 @@ config:
|
|
|
901
902
|
expect(logData[0].parameter_query_results).toBe(3);
|
|
902
903
|
});
|
|
903
904
|
|
|
904
|
-
test('throws error with breakdown when
|
|
905
|
+
test('throws error with breakdown when too many parameters were fetched', async () => {
|
|
906
|
+
const syncRules = SqlSyncRules.fromYaml(
|
|
907
|
+
`
|
|
908
|
+
config:
|
|
909
|
+
edition: 3
|
|
910
|
+
|
|
911
|
+
streams:
|
|
912
|
+
a:
|
|
913
|
+
auto_subscribe: true
|
|
914
|
+
query: SELECT * FROM a WHERE id IN (SELECT id FROM magic_sequence WHERE count0 = auth.parameter('a'))
|
|
915
|
+
b:
|
|
916
|
+
auto_subscribe: true
|
|
917
|
+
query: SELECT * FROM a WHERE id IN (SELECT id FROM magic_sequence WHERE count1 = auth.parameter('b'))
|
|
918
|
+
c:
|
|
919
|
+
auto_subscribe: true
|
|
920
|
+
query: SELECT * FROM a WHERE id IN (SELECT id FROM magic_sequence WHERE count1 = auth.parameter('c'))
|
|
921
|
+
`,
|
|
922
|
+
{ defaultSchema: 'public' }
|
|
923
|
+
).config.hydrate({
|
|
924
|
+
hydrationState: versionedHydrationState(1)
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
const storage = new MockBucketChecksumStateStorage();
|
|
928
|
+
|
|
929
|
+
const errorMessages: string[] = [];
|
|
930
|
+
const errorData: any[] = [];
|
|
931
|
+
const mockLogger = {
|
|
932
|
+
info: () => {},
|
|
933
|
+
error: (message: string, data: any) => {
|
|
934
|
+
errorMessages.push(message);
|
|
935
|
+
errorData.push(data);
|
|
936
|
+
},
|
|
937
|
+
warn: () => {},
|
|
938
|
+
debug: () => {}
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
const smallContext = new SyncContext({
|
|
942
|
+
maxBuckets: 100,
|
|
943
|
+
maxParameterQueryResults: 55,
|
|
944
|
+
maxDataFetchConcurrency: 10
|
|
945
|
+
});
|
|
946
|
+
|
|
947
|
+
const state = new BucketChecksumState({
|
|
948
|
+
syncContext: smallContext,
|
|
949
|
+
tokenPayload: new JwtPayload({
|
|
950
|
+
sub: 'u1',
|
|
951
|
+
// Create 60 total results: 30 a + 20 b + 10 c
|
|
952
|
+
a: BigInt(30),
|
|
953
|
+
b: BigInt(20),
|
|
954
|
+
c: BigInt(10)
|
|
955
|
+
}),
|
|
956
|
+
syncRequest,
|
|
957
|
+
syncRules,
|
|
958
|
+
bucketStorage: storage,
|
|
959
|
+
logger: mockLogger as any
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
await expect(
|
|
963
|
+
state.buildNextCheckpointLine({
|
|
964
|
+
base: storage.makeCheckpoint(1n, (lookups, limit) => {
|
|
965
|
+
expect(lookups).toHaveLength(1);
|
|
966
|
+
const [{ values }] = lookups;
|
|
967
|
+
expect(values[0]).toStrictEqual('lookup');
|
|
968
|
+
|
|
969
|
+
const count = Number(values[2]); // The count parameter from streams
|
|
970
|
+
if (count > limit) {
|
|
971
|
+
throw new ParameterSetLimitExceededError(limit);
|
|
972
|
+
}
|
|
973
|
+
return [{ lookup: lookups[0], rows: Array.from({ length: count }, (_, i) => ({ '0': BigInt(i) })) }];
|
|
974
|
+
}),
|
|
975
|
+
writeCheckpoint: null,
|
|
976
|
+
update: CHECKPOINT_INVALIDATE_ALL
|
|
977
|
+
})
|
|
978
|
+
).rejects.toThrow('Too many parameter query results (limit of 55)');
|
|
979
|
+
|
|
980
|
+
// Verify error log includes breakdown
|
|
981
|
+
expect(errorMessages[0]).toContain('Invoked parameter queries by definition:');
|
|
982
|
+
expect(errorMessages[0]).toContain('Stream a evaluating parameter on magic_sequence: 30 results.');
|
|
983
|
+
expect(errorMessages[0]).toContain('Stream b evaluating parameter on magic_sequence: 20 results.');
|
|
984
|
+
expect(errorMessages[0]).toContain(
|
|
985
|
+
'Stream c evaluating parameter on magic_sequence exceeded the remaining limit of 5 available results.'
|
|
986
|
+
);
|
|
987
|
+
|
|
988
|
+
expect(errorData[0].checkpoint).toEqual(1n);
|
|
989
|
+
expect(errorData[0].parameterResults).toEqual([
|
|
990
|
+
{
|
|
991
|
+
definition: 'Stream a evaluating parameter on magic_sequence',
|
|
992
|
+
didExceedLimit: false,
|
|
993
|
+
resultsOrLimit: 30
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
definition: 'Stream b evaluating parameter on magic_sequence',
|
|
997
|
+
didExceedLimit: false,
|
|
998
|
+
resultsOrLimit: 20
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
definition: 'Stream c evaluating parameter on magic_sequence',
|
|
1002
|
+
didExceedLimit: true,
|
|
1003
|
+
resultsOrLimit: 5
|
|
1004
|
+
}
|
|
1005
|
+
]);
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
test('throws error when too many lookups are requested at once', async () => {
|
|
1009
|
+
const syncRules = SqlSyncRules.fromYaml(
|
|
1010
|
+
`
|
|
1011
|
+
config:
|
|
1012
|
+
edition: 3
|
|
1013
|
+
|
|
1014
|
+
streams:
|
|
1015
|
+
a:
|
|
1016
|
+
auto_subscribe: true
|
|
1017
|
+
query: SELECT * FROM a WHERE x IN (SELECT x FROM b WHERE y IN auth.parameter('p'))
|
|
1018
|
+
`,
|
|
1019
|
+
{ defaultSchema: 'public' }
|
|
1020
|
+
).config.hydrate({
|
|
1021
|
+
hydrationState: versionedHydrationState(1)
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
const storage = new MockBucketChecksumStateStorage();
|
|
1025
|
+
|
|
1026
|
+
const errorData: any[] = [];
|
|
1027
|
+
const mockLogger = {
|
|
1028
|
+
info: () => {},
|
|
1029
|
+
error: (_message: string, data: any) => {
|
|
1030
|
+
errorData.push(data);
|
|
1031
|
+
},
|
|
1032
|
+
warn: () => {},
|
|
1033
|
+
debug: () => {}
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
const smallContext = new SyncContext({
|
|
1037
|
+
maxBuckets: 100,
|
|
1038
|
+
maxParameterQueryResults: 10,
|
|
1039
|
+
maxDataFetchConcurrency: 10
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
const state = new BucketChecksumState({
|
|
1043
|
+
syncContext: smallContext,
|
|
1044
|
+
tokenPayload: new JwtPayload({
|
|
1045
|
+
sub: 'u1',
|
|
1046
|
+
p: Array.from({ length: 100 }, (_, i) => i)
|
|
1047
|
+
}),
|
|
1048
|
+
syncRequest,
|
|
1049
|
+
syncRules,
|
|
1050
|
+
bucketStorage: storage,
|
|
1051
|
+
logger: mockLogger as any
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
await expect(
|
|
1055
|
+
state.buildNextCheckpointLine({
|
|
1056
|
+
base: storage.makeCheckpoint(1n, () => {
|
|
1057
|
+
throw new Error('should not get called');
|
|
1058
|
+
}),
|
|
1059
|
+
writeCheckpoint: null,
|
|
1060
|
+
update: CHECKPOINT_INVALIDATE_ALL
|
|
1061
|
+
})
|
|
1062
|
+
).rejects.toThrow('Attempted to fetch 100 lookups at once, a maximum of 10 lookups are allowed');
|
|
1063
|
+
|
|
1064
|
+
expect(errorData).toStrictEqual([
|
|
1065
|
+
{
|
|
1066
|
+
user_id: 'u1',
|
|
1067
|
+
checkpoint: 1n,
|
|
1068
|
+
cause: 'Stream a evaluating parameter on b'
|
|
1069
|
+
}
|
|
1070
|
+
]);
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
test('throws error with breakdown when bucket limit is exceeded', async () => {
|
|
1074
|
+
// These streams are designed to return buckets without consuming too many parameters (as exceeding that limit is
|
|
1075
|
+
// a different error).
|
|
905
1076
|
const SYNC_RULES_MULTI = SqlSyncRules.fromYaml(
|
|
906
1077
|
`
|
|
907
|
-
|
|
1078
|
+
config:
|
|
1079
|
+
edition: 3
|
|
1080
|
+
|
|
1081
|
+
streams:
|
|
908
1082
|
projects:
|
|
909
|
-
|
|
910
|
-
|
|
1083
|
+
auto_subscribe: true
|
|
1084
|
+
query: SELECT id FROM projects WHERE p IN auth.parameter('a')
|
|
911
1085
|
tasks:
|
|
912
|
-
|
|
913
|
-
|
|
1086
|
+
auto_subscribe: true
|
|
1087
|
+
query: SELECT id FROM tasks WHERE p IN auth.parameter('b')
|
|
914
1088
|
comments:
|
|
915
|
-
|
|
916
|
-
|
|
1089
|
+
auto_subscribe: true
|
|
1090
|
+
query: SELECT id FROM comments WHERE p IN auth.parameter('c')
|
|
917
1091
|
`,
|
|
918
1092
|
{ defaultSchema: 'public' }
|
|
919
1093
|
).config.hydrate({ hydrationState: versionedHydrationState(4) });
|
|
@@ -933,74 +1107,61 @@ bucket_definitions:
|
|
|
933
1107
|
};
|
|
934
1108
|
|
|
935
1109
|
const smallContext = new SyncContext({
|
|
936
|
-
maxBuckets:
|
|
937
|
-
maxParameterQueryResults:
|
|
1110
|
+
maxBuckets: 50,
|
|
1111
|
+
maxParameterQueryResults: 10,
|
|
938
1112
|
maxDataFetchConcurrency: 10
|
|
939
1113
|
});
|
|
940
1114
|
|
|
941
1115
|
const state = new BucketChecksumState({
|
|
942
1116
|
syncContext: smallContext,
|
|
943
|
-
tokenPayload: new JwtPayload({
|
|
1117
|
+
tokenPayload: new JwtPayload({
|
|
1118
|
+
sub: 'u1',
|
|
1119
|
+
// Create 60 total results: 30 projects + 20 tasks + 10 comments
|
|
1120
|
+
a: Array.from({ length: 30 }, (_, i) => BigInt(i)),
|
|
1121
|
+
b: Array.from({ length: 20 }, (_, i) => BigInt(i)),
|
|
1122
|
+
c: Array.from({ length: 10 }, (_, i) => BigInt(i))
|
|
1123
|
+
}),
|
|
944
1124
|
syncRequest,
|
|
945
1125
|
syncRules: SYNC_RULES_MULTI,
|
|
946
1126
|
bucketStorage: storage,
|
|
947
1127
|
logger: mockLogger as any
|
|
948
1128
|
});
|
|
949
1129
|
|
|
950
|
-
// Create 60 total results: 30 projects + 20 tasks + 10 comments
|
|
951
|
-
const projectIds = Array.from({ length: 30 }, (_, i) => ({ id: i + 1 }));
|
|
952
|
-
const taskIds = Array.from({ length: 20 }, (_, i) => ({ id: i + 1 }));
|
|
953
|
-
const commentIds = Array.from({ length: 10 }, (_, i) => ({ id: i + 1 }));
|
|
954
|
-
|
|
955
|
-
for (let i = 1; i <= 30; i++) {
|
|
956
|
-
storage.updateTestChecksum({ bucket: `projects[${i}]`, checksum: 1, count: 1 });
|
|
957
|
-
}
|
|
958
|
-
for (let i = 1; i <= 20; i++) {
|
|
959
|
-
storage.updateTestChecksum({ bucket: `tasks[${i}]`, checksum: 1, count: 1 });
|
|
960
|
-
}
|
|
961
|
-
for (let i = 1; i <= 10; i++) {
|
|
962
|
-
storage.updateTestChecksum({ bucket: `comments[${i}]`, checksum: 1, count: 1 });
|
|
963
|
-
}
|
|
964
|
-
|
|
965
1130
|
await expect(
|
|
966
1131
|
state.buildNextCheckpointLine({
|
|
967
|
-
base: storage.makeCheckpoint(1n,
|
|
968
|
-
const lookup = lookups[0];
|
|
969
|
-
const lookupName = lookup.values[0];
|
|
970
|
-
if (lookupName === 'projects') {
|
|
971
|
-
return projectIds;
|
|
972
|
-
} else if (lookupName === 'tasks') {
|
|
973
|
-
return taskIds;
|
|
974
|
-
} else {
|
|
975
|
-
return commentIds;
|
|
976
|
-
}
|
|
977
|
-
}),
|
|
1132
|
+
base: storage.makeCheckpoint(1n),
|
|
978
1133
|
writeCheckpoint: null,
|
|
979
1134
|
update: CHECKPOINT_INVALIDATE_ALL
|
|
980
1135
|
})
|
|
981
|
-
).rejects.toThrow('Too many
|
|
1136
|
+
).rejects.toThrow('Too many buckets: 60 (limit of 50');
|
|
982
1137
|
|
|
983
1138
|
// Verify error log includes breakdown
|
|
984
|
-
expect(errorMessages[0]).toContain('
|
|
1139
|
+
expect(errorMessages[0]).toContain('Buckets by definition:');
|
|
985
1140
|
expect(errorMessages[0]).toContain('projects: 30');
|
|
986
1141
|
expect(errorMessages[0]).toContain('tasks: 20');
|
|
987
1142
|
expect(errorMessages[0]).toContain('comments: 10');
|
|
988
1143
|
|
|
989
|
-
expect(errorData[0].
|
|
990
|
-
expect(errorData[0].
|
|
1144
|
+
expect(errorData[0].checkpoint).toEqual(1n);
|
|
1145
|
+
expect(errorData[0].buckets).toBe(60);
|
|
1146
|
+
expect(errorData[0].buckets_by_definition).toEqual({
|
|
991
1147
|
projects: 30,
|
|
992
1148
|
tasks: 20,
|
|
993
1149
|
comments: 10
|
|
994
1150
|
});
|
|
995
1151
|
});
|
|
996
1152
|
|
|
997
|
-
test('limits breakdown to top 10 definitions', async () => {
|
|
998
|
-
// Create sync
|
|
999
|
-
let yamlDefinitions =
|
|
1153
|
+
test('limits bucket breakdown to top 10 definitions', async () => {
|
|
1154
|
+
// Create sync streams with 15 different definitions with dynamic parameters
|
|
1155
|
+
let yamlDefinitions = `
|
|
1156
|
+
config:
|
|
1157
|
+
edition: 3
|
|
1158
|
+
|
|
1159
|
+
streams:
|
|
1160
|
+
`;
|
|
1000
1161
|
for (let i = 1; i <= 15; i++) {
|
|
1001
1162
|
yamlDefinitions += ` def${i}:\n`;
|
|
1002
|
-
yamlDefinitions += `
|
|
1003
|
-
yamlDefinitions += `
|
|
1163
|
+
yamlDefinitions += ` auto_subscribe: true\n`;
|
|
1164
|
+
yamlDefinitions += ` query: SELECT * FROM tbl WHERE b = auth.parameter('${i}')\n`;
|
|
1004
1165
|
}
|
|
1005
1166
|
|
|
1006
1167
|
const SYNC_RULES_MANY = SqlSyncRules.fromYaml(yamlDefinitions, { defaultSchema: 'public' }).config.hydrate({
|
|
@@ -1020,35 +1181,30 @@ bucket_definitions:
|
|
|
1020
1181
|
};
|
|
1021
1182
|
|
|
1022
1183
|
const smallContext = new SyncContext({
|
|
1023
|
-
maxBuckets:
|
|
1184
|
+
maxBuckets: 10,
|
|
1024
1185
|
maxParameterQueryResults: 10,
|
|
1025
1186
|
maxDataFetchConcurrency: 10
|
|
1026
1187
|
});
|
|
1027
1188
|
|
|
1028
1189
|
const state = new BucketChecksumState({
|
|
1029
1190
|
syncContext: smallContext,
|
|
1030
|
-
tokenPayload: new JwtPayload({
|
|
1191
|
+
tokenPayload: new JwtPayload({
|
|
1192
|
+
sub: 'u1',
|
|
1193
|
+
...Object.fromEntries(Array.from({ length: 30 }, (_, i) => [i.toString(), BigInt(i)]))
|
|
1194
|
+
}),
|
|
1031
1195
|
syncRequest,
|
|
1032
1196
|
syncRules: SYNC_RULES_MANY,
|
|
1033
1197
|
bucketStorage: storage,
|
|
1034
1198
|
logger: mockLogger as any
|
|
1035
1199
|
});
|
|
1036
1200
|
|
|
1037
|
-
// Each definition creates one bucket, total 15 buckets
|
|
1038
|
-
for (let i = 1; i <= 15; i++) {
|
|
1039
|
-
storage.updateTestChecksum({ bucket: `def${i}[${i}]`, checksum: 1, count: 1 });
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
1201
|
await expect(
|
|
1043
1202
|
state.buildNextCheckpointLine({
|
|
1044
|
-
base: storage.makeCheckpoint(1n,
|
|
1045
|
-
// Return one result for each definition
|
|
1046
|
-
return [{ id: 1 }];
|
|
1047
|
-
}),
|
|
1203
|
+
base: storage.makeCheckpoint(1n),
|
|
1048
1204
|
writeCheckpoint: null,
|
|
1049
1205
|
update: CHECKPOINT_INVALIDATE_ALL
|
|
1050
1206
|
})
|
|
1051
|
-
).rejects.toThrow('Too many
|
|
1207
|
+
).rejects.toThrow('Too many buckets: 15 (limit of 10)');
|
|
1052
1208
|
|
|
1053
1209
|
// Verify only top 10 are shown
|
|
1054
1210
|
const errorMessage = errorMessages[0];
|
|
@@ -1094,16 +1250,16 @@ class MockBucketChecksumStateStorage implements BucketChecksumStateStorage {
|
|
|
1094
1250
|
|
|
1095
1251
|
makeCheckpoint(
|
|
1096
1252
|
opId: InternalOpId,
|
|
1097
|
-
parameters?: (lookups: ScopedParameterLookup[]) =>
|
|
1253
|
+
parameters?: (lookups: ScopedParameterLookup[], limit: number) => ParameterLookupRows[]
|
|
1098
1254
|
): ReplicationCheckpoint {
|
|
1099
1255
|
return {
|
|
1100
1256
|
checkpoint: opId,
|
|
1101
1257
|
lsn: String(opId),
|
|
1102
|
-
getParameterSets: async (lookups: ScopedParameterLookup[]) => {
|
|
1258
|
+
getParameterSets: async (lookups: ScopedParameterLookup[], limit) => {
|
|
1103
1259
|
if (parameters == null) {
|
|
1104
1260
|
throw new Error(`getParametersSets not defined for checkpoint ${opId}`);
|
|
1105
1261
|
}
|
|
1106
|
-
return parameters(lookups);
|
|
1262
|
+
return parameters(lookups, limit);
|
|
1107
1263
|
}
|
|
1108
1264
|
};
|
|
1109
1265
|
}
|
|
@@ -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', () => {
|