@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.
Files changed (220) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/api/RouteAPI.d.ts +17 -3
  3. package/dist/api/api-index.d.ts +1 -1
  4. package/dist/api/api-index.js +1 -1
  5. package/dist/api/api-index.js.map +1 -1
  6. package/dist/api/api-metrics.js.map +1 -1
  7. package/dist/api/diagnostics.d.ts +1 -1
  8. package/dist/api/diagnostics.js +32 -14
  9. package/dist/api/diagnostics.js.map +1 -1
  10. package/dist/auth/CachedKeyCollector.js +1 -1
  11. package/dist/auth/CachedKeyCollector.js.map +1 -1
  12. package/dist/auth/CompoundKeyCollector.js.map +1 -1
  13. package/dist/auth/KeyStore.js.map +1 -1
  14. package/dist/auth/RemoteJWKSCollector.js.map +1 -1
  15. package/dist/auth/StaticKeyCollector.d.ts +1 -1
  16. package/dist/auth/StaticKeyCollector.js.map +1 -1
  17. package/dist/auth/StaticSupabaseKeyCollector.d.ts +1 -1
  18. package/dist/auth/StaticSupabaseKeyCollector.js.map +1 -1
  19. package/dist/entry/commands/teardown-action.js +2 -2
  20. package/dist/entry/commands/teardown-action.js.map +1 -1
  21. package/dist/entry/entry-index.d.ts +1 -1
  22. package/dist/entry/entry-index.js +1 -1
  23. package/dist/entry/entry-index.js.map +1 -1
  24. package/dist/events/EventsEngine.js +1 -1
  25. package/dist/events/EventsEngine.js.map +1 -1
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/metrics/MetricsEngine.d.ts +1 -1
  30. package/dist/metrics/metrics-index.d.ts +3 -3
  31. package/dist/metrics/metrics-index.js +3 -3
  32. package/dist/metrics/metrics-index.js.map +1 -1
  33. package/dist/metrics/open-telemetry/util.js +1 -1
  34. package/dist/metrics/open-telemetry/util.js.map +1 -1
  35. package/dist/metrics/register-metrics.js +2 -2
  36. package/dist/metrics/register-metrics.js.map +1 -1
  37. package/dist/modules/AbstractModule.d.ts +2 -2
  38. package/dist/modules/AbstractModule.js.map +1 -1
  39. package/dist/modules/modules-index.d.ts +1 -1
  40. package/dist/modules/modules-index.js +1 -1
  41. package/dist/modules/modules-index.js.map +1 -1
  42. package/dist/replication/AbstractReplicationJob.d.ts +1 -1
  43. package/dist/replication/AbstractReplicationJob.js +1 -1
  44. package/dist/replication/AbstractReplicationJob.js.map +1 -1
  45. package/dist/replication/AbstractReplicator.d.ts +6 -6
  46. package/dist/replication/AbstractReplicator.js +21 -21
  47. package/dist/replication/AbstractReplicator.js.map +1 -1
  48. package/dist/replication/replication-index.d.ts +3 -3
  49. package/dist/replication/replication-index.js +3 -3
  50. package/dist/replication/replication-index.js.map +1 -1
  51. package/dist/replication/replication-metrics.js.map +1 -1
  52. package/dist/routes/configure-fastify.d.ts +59 -32
  53. package/dist/routes/endpoints/admin.d.ts +108 -54
  54. package/dist/routes/endpoints/admin.js +7 -3
  55. package/dist/routes/endpoints/admin.js.map +1 -1
  56. package/dist/routes/endpoints/checkpointing.js +1 -1
  57. package/dist/routes/endpoints/checkpointing.js.map +1 -1
  58. package/dist/routes/endpoints/socket-route.js +1 -1
  59. package/dist/routes/endpoints/socket-route.js.map +1 -1
  60. package/dist/routes/endpoints/sync-rules.js +10 -10
  61. package/dist/routes/endpoints/sync-rules.js.map +1 -1
  62. package/dist/routes/endpoints/sync-stream.d.ts +10 -10
  63. package/dist/routes/endpoints/sync-stream.js +2 -2
  64. package/dist/routes/endpoints/sync-stream.js.map +1 -1
  65. package/dist/routes/hooks.js +1 -1
  66. package/dist/routes/hooks.js.map +1 -1
  67. package/dist/routes/route-register.js.map +1 -1
  68. package/dist/runner/teardown.js +4 -4
  69. package/dist/runner/teardown.js.map +1 -1
  70. package/dist/storage/BucketStorage.d.ts +9 -9
  71. package/dist/storage/BucketStorage.js +9 -9
  72. package/dist/storage/BucketStorageBatch.d.ts +1 -1
  73. package/dist/storage/BucketStorageFactory.d.ts +27 -20
  74. package/dist/storage/BucketStorageFactory.js +19 -16
  75. package/dist/storage/BucketStorageFactory.js.map +1 -1
  76. package/dist/storage/ChecksumCache.js.map +1 -1
  77. package/dist/storage/PersistedSyncRulesContent.d.ts +3 -1
  78. package/dist/storage/PersistedSyncRulesContent.js +24 -5
  79. package/dist/storage/PersistedSyncRulesContent.js.map +1 -1
  80. package/dist/storage/ReplicationEventPayload.d.ts +1 -1
  81. package/dist/storage/SourceTable.d.ts +4 -4
  82. package/dist/storage/SourceTable.js +3 -3
  83. package/dist/storage/SourceTable.js.map +1 -1
  84. package/dist/storage/StorageVersionConfig.d.ts +1 -1
  85. package/dist/storage/StorageVersionConfig.js +1 -1
  86. package/dist/storage/SyncRulesBucketStorage.d.ts +38 -6
  87. package/dist/storage/SyncRulesBucketStorage.js +14 -0
  88. package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
  89. package/dist/storage/WriteCheckpointAPI.d.ts +6 -6
  90. package/dist/storage/WriteCheckpointAPI.js +1 -1
  91. package/dist/storage/bson.d.ts +0 -1
  92. package/dist/storage/bson.js +0 -4
  93. package/dist/storage/bson.js.map +1 -1
  94. package/dist/storage/storage-index.d.ts +8 -8
  95. package/dist/storage/storage-index.js +8 -8
  96. package/dist/storage/storage-index.js.map +1 -1
  97. package/dist/storage/storage-metrics.js.map +1 -1
  98. package/dist/streams/streams-index.d.ts +2 -2
  99. package/dist/streams/streams-index.js +2 -2
  100. package/dist/streams/streams-index.js.map +1 -1
  101. package/dist/sync/BucketChecksumState.d.ts +2 -5
  102. package/dist/sync/BucketChecksumState.js +119 -75
  103. package/dist/sync/BucketChecksumState.js.map +1 -1
  104. package/dist/sync/RequestTracker.js +1 -1
  105. package/dist/sync/RequestTracker.js.map +1 -1
  106. package/dist/sync/sync-index.d.ts +2 -2
  107. package/dist/sync/sync-index.js +2 -2
  108. package/dist/sync/sync-index.js.map +1 -1
  109. package/dist/sync/sync.js.map +1 -1
  110. package/dist/sync/util.js.map +1 -1
  111. package/dist/system/ServiceContext.d.ts +1 -1
  112. package/dist/system/ServiceContext.js +1 -1
  113. package/dist/system/ServiceContext.js.map +1 -1
  114. package/dist/tracing/PerformanceTracer.d.ts +44 -0
  115. package/dist/tracing/PerformanceTracer.js +102 -0
  116. package/dist/tracing/PerformanceTracer.js.map +1 -0
  117. package/dist/tracing/TraceWriter.d.ts +22 -0
  118. package/dist/tracing/TraceWriter.js +63 -0
  119. package/dist/tracing/TraceWriter.js.map +1 -0
  120. package/dist/util/checkpointing.js +1 -1
  121. package/dist/util/config/collectors/impl/base64-config-collector.d.ts +1 -1
  122. package/dist/util/config/collectors/impl/base64-config-collector.js.map +1 -1
  123. package/dist/util/config/collectors/impl/filesystem-config-collector.d.ts +1 -1
  124. package/dist/util/config/collectors/impl/filesystem-config-collector.js +1 -1
  125. package/dist/util/config/collectors/impl/filesystem-config-collector.js.map +1 -1
  126. package/dist/util/config/compound-config-collector.d.ts +1 -1
  127. package/dist/util/config/compound-config-collector.js +2 -2
  128. package/dist/util/config/compound-config-collector.js.map +1 -1
  129. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js +1 -1
  130. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
  131. package/dist/util/config/sync-rules/sync-rules-provider.js.map +1 -1
  132. package/dist/util/config.js +1 -1
  133. package/dist/util/config.js.map +1 -1
  134. package/dist/util/env.js +1 -1
  135. package/dist/util/errors.d.ts +3 -0
  136. package/dist/util/errors.js +15 -0
  137. package/dist/util/errors.js.map +1 -0
  138. package/dist/util/protocol-types.d.ts +3 -3
  139. package/dist/util/protocol-types.js +1 -1
  140. package/dist/util/util-index.d.ts +1 -1
  141. package/dist/util/util-index.js +1 -1
  142. package/dist/util/util-index.js.map +1 -1
  143. package/dist/util/utils.d.ts +1 -1
  144. package/package.json +11 -11
  145. package/src/api/RouteAPI.ts +20 -3
  146. package/src/api/api-index.ts +1 -1
  147. package/src/api/api-metrics.ts +1 -1
  148. package/src/api/diagnostics.ts +42 -20
  149. package/src/auth/CachedKeyCollector.ts +2 -3
  150. package/src/auth/CompoundKeyCollector.ts +2 -3
  151. package/src/auth/KeyStore.ts +1 -1
  152. package/src/auth/RemoteJWKSCollector.ts +0 -1
  153. package/src/auth/StaticKeyCollector.ts +1 -1
  154. package/src/auth/StaticSupabaseKeyCollector.ts +1 -1
  155. package/src/entry/commands/teardown-action.ts +2 -2
  156. package/src/entry/entry-index.ts +1 -1
  157. package/src/events/EventsEngine.ts +1 -1
  158. package/src/index.ts +2 -0
  159. package/src/metrics/MetricsEngine.ts +1 -1
  160. package/src/metrics/metrics-index.ts +3 -3
  161. package/src/metrics/open-telemetry/util.ts +1 -1
  162. package/src/metrics/register-metrics.ts +3 -3
  163. package/src/modules/AbstractModule.ts +2 -2
  164. package/src/modules/modules-index.ts +1 -1
  165. package/src/replication/AbstractReplicationJob.ts +2 -2
  166. package/src/replication/AbstractReplicator.ts +23 -23
  167. package/src/replication/replication-index.ts +3 -3
  168. package/src/replication/replication-metrics.ts +1 -1
  169. package/src/routes/endpoints/admin.ts +7 -3
  170. package/src/routes/endpoints/checkpointing.ts +1 -1
  171. package/src/routes/endpoints/socket-route.ts +1 -1
  172. package/src/routes/endpoints/sync-rules.ts +10 -12
  173. package/src/routes/endpoints/sync-stream.ts +2 -2
  174. package/src/routes/hooks.ts +2 -2
  175. package/src/routes/route-register.ts +2 -10
  176. package/src/runner/teardown.ts +4 -4
  177. package/src/storage/BucketStorage.ts +9 -9
  178. package/src/storage/BucketStorageBatch.ts +1 -1
  179. package/src/storage/BucketStorageFactory.ts +45 -34
  180. package/src/storage/ChecksumCache.ts +1 -1
  181. package/src/storage/PersistedSyncRulesContent.ts +30 -6
  182. package/src/storage/ReplicationEventPayload.ts +1 -1
  183. package/src/storage/SourceTable.ts +4 -4
  184. package/src/storage/StorageVersionConfig.ts +1 -1
  185. package/src/storage/SyncRulesBucketStorage.ts +46 -7
  186. package/src/storage/WriteCheckpointAPI.ts +6 -6
  187. package/src/storage/bson.ts +0 -5
  188. package/src/storage/storage-index.ts +8 -8
  189. package/src/storage/storage-metrics.ts +2 -2
  190. package/src/streams/streams-index.ts +2 -2
  191. package/src/sync/BucketChecksumState.ts +141 -93
  192. package/src/sync/RequestTracker.ts +1 -1
  193. package/src/sync/sync-index.ts +2 -2
  194. package/src/sync/sync.ts +2 -8
  195. package/src/sync/util.ts +1 -1
  196. package/src/system/ServiceContext.ts +1 -1
  197. package/src/tracing/PerformanceTracer.ts +126 -0
  198. package/src/tracing/TraceWriter.ts +67 -0
  199. package/src/util/checkpointing.ts +1 -1
  200. package/src/util/config/collectors/impl/base64-config-collector.ts +1 -1
  201. package/src/util/config/collectors/impl/filesystem-config-collector.ts +2 -2
  202. package/src/util/config/compound-config-collector.ts +3 -3
  203. package/src/util/config/sync-rules/impl/filesystem-sync-rules-collector.ts +1 -1
  204. package/src/util/config/sync-rules/sync-rules-provider.ts +1 -1
  205. package/src/util/config.ts +1 -1
  206. package/src/util/env.ts +1 -1
  207. package/src/util/errors.ts +21 -0
  208. package/src/util/protocol-types.ts +1 -1
  209. package/src/util/util-index.ts +1 -1
  210. package/src/util/utils.ts +1 -1
  211. package/test/src/auth.test.ts +115 -7
  212. package/test/src/diagnostics.test.ts +151 -0
  213. package/test/src/module-loader.test.ts +1 -1
  214. package/test/src/routes/mocks.ts +1 -1
  215. package/test/src/routes/stream.test.ts +1 -2
  216. package/test/src/sync/BucketChecksumState.test.ts +223 -67
  217. package/test/src/util/protocol_types.test.ts +1 -1
  218. package/test/tsconfig.json +0 -1
  219. package/tsconfig.tsbuildinfo +1 -1
  220. 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
- RequestJwtPayload,
19
+ ParameterLookupRows,
19
20
  ScopedParameterLookup,
20
- SqliteJsonRow,
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
- return [{ id: 1 }, { id: 2 }, { id: 3 }];
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 parameter query limit is exceeded', async () => {
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
- bucket_definitions:
1078
+ config:
1079
+ edition: 3
1080
+
1081
+ streams:
908
1082
  projects:
909
- parameters: select id from projects where user_id = request.user_id()
910
- data: []
1083
+ auto_subscribe: true
1084
+ query: SELECT id FROM projects WHERE p IN auth.parameter('a')
911
1085
  tasks:
912
- parameters: select id from tasks where user_id = request.user_id()
913
- data: []
1086
+ auto_subscribe: true
1087
+ query: SELECT id FROM tasks WHERE p IN auth.parameter('b')
914
1088
  comments:
915
- parameters: select id from comments where user_id = request.user_id()
916
- data: []
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: 100,
937
- maxParameterQueryResults: 50,
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({ sub: 'u1' }),
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, (lookups) => {
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 parameter query results: 60 (limit of 50)');
1136
+ ).rejects.toThrow('Too many buckets: 60 (limit of 50');
982
1137
 
983
1138
  // Verify error log includes breakdown
984
- expect(errorMessages[0]).toContain('Parameter query results by definition:');
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].parameter_query_results).toBe(60);
990
- expect(errorData[0].parameter_query_results_by_definition).toEqual({
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 rules with 15 different definitions with dynamic parameters
999
- let yamlDefinitions = 'bucket_definitions:\n';
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 += ` parameters: select id from def${i}_table where user_id = request.user_id()\n`;
1003
- yamlDefinitions += ` data: []\n`;
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: 100,
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({ sub: 'u1' }),
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, (lookups) => {
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 parameter query results: 15 (limit of 10)');
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[]) => SqliteJsonRow[]
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, test, expect, it } from 'vitest';
3
+ import { describe, expect, test } from 'vitest';
4
4
 
5
5
  describe('protocol types', () => {
6
6
  describe('StreamingSyncRequest', () => {
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "extends": "../../../tsconfig.tests.json",
3
3
  "compilerOptions": {
4
- "baseUrl": "./",
5
4
  "outDir": "dist",
6
5
  "paths": {
7
6
  "@/*": ["../src/*"]