@powersync/service-core 0.0.0-dev-20260223082111 → 0.0.0-dev-20260225093637

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 (47) hide show
  1. package/CHANGELOG.md +22 -5
  2. package/dist/api/diagnostics.js +11 -4
  3. package/dist/api/diagnostics.js.map +1 -1
  4. package/dist/entry/commands/compact-action.js +13 -2
  5. package/dist/entry/commands/compact-action.js.map +1 -1
  6. package/dist/entry/commands/config-command.js +2 -2
  7. package/dist/entry/commands/config-command.js.map +1 -1
  8. package/dist/routes/configure-fastify.d.ts +84 -0
  9. package/dist/routes/endpoints/admin.d.ts +168 -0
  10. package/dist/storage/SyncRulesBucketStorage.d.ts +2 -1
  11. package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
  12. package/dist/sync/BucketChecksumState.d.ts +5 -0
  13. package/dist/sync/BucketChecksumState.js +85 -10
  14. package/dist/sync/BucketChecksumState.js.map +1 -1
  15. package/dist/util/config/collectors/config-collector.js +13 -0
  16. package/dist/util/config/collectors/config-collector.js.map +1 -1
  17. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.d.ts +1 -1
  18. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js +4 -4
  19. package/dist/util/config/sync-rules/impl/base64-sync-rules-collector.js.map +1 -1
  20. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.d.ts +1 -1
  21. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js +2 -2
  22. package/dist/util/config/sync-rules/impl/filesystem-sync-rules-collector.js.map +1 -1
  23. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.d.ts +1 -1
  24. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js +3 -3
  25. package/dist/util/config/sync-rules/impl/inline-sync-rules-collector.js.map +1 -1
  26. package/dist/util/config/types.d.ts +1 -1
  27. package/dist/util/config/types.js.map +1 -1
  28. package/dist/util/env.d.ts +1 -0
  29. package/dist/util/env.js +5 -0
  30. package/dist/util/env.js.map +1 -1
  31. package/package.json +5 -5
  32. package/src/api/diagnostics.ts +12 -4
  33. package/src/entry/commands/compact-action.ts +15 -2
  34. package/src/entry/commands/config-command.ts +3 -3
  35. package/src/storage/SyncRulesBucketStorage.ts +2 -1
  36. package/src/sync/BucketChecksumState.ts +127 -15
  37. package/src/util/config/collectors/config-collector.ts +16 -0
  38. package/src/util/config/sync-rules/impl/base64-sync-rules-collector.ts +5 -5
  39. package/src/util/config/sync-rules/impl/filesystem-sync-rules-collector.ts +3 -3
  40. package/src/util/config/sync-rules/impl/inline-sync-rules-collector.ts +4 -4
  41. package/src/util/config/types.ts +1 -2
  42. package/src/util/env.ts +5 -0
  43. package/test/src/config.test.ts +115 -0
  44. package/test/src/routes/admin.test.ts +48 -0
  45. package/test/src/routes/mocks.ts +22 -1
  46. package/test/src/sync/BucketChecksumState.test.ts +200 -0
  47. package/tsconfig.tsbuildinfo +1 -1
@@ -73,6 +73,10 @@ export declare const diagnostics: router.Endpoint<{
73
73
  errors: {
74
74
  message: string;
75
75
  level: "warning" | "fatal";
76
+ location?: {
77
+ start_offset: number;
78
+ end_offset: number;
79
+ } | undefined;
76
80
  ts?: string | undefined;
77
81
  }[];
78
82
  id: string;
@@ -83,6 +87,10 @@ export declare const diagnostics: router.Endpoint<{
83
87
  errors: {
84
88
  level: "warning" | "fatal";
85
89
  message: string;
90
+ location?: {
91
+ start_offset: number;
92
+ end_offset: number;
93
+ } | undefined;
86
94
  ts?: string | undefined;
87
95
  }[];
88
96
  connections: {
@@ -99,6 +107,10 @@ export declare const diagnostics: router.Endpoint<{
99
107
  errors: {
100
108
  level: "warning" | "fatal";
101
109
  message: string;
110
+ location?: {
111
+ start_offset: number;
112
+ end_offset: number;
113
+ } | undefined;
102
114
  ts?: string | undefined;
103
115
  }[];
104
116
  pattern?: string | undefined;
@@ -114,6 +126,10 @@ export declare const diagnostics: router.Endpoint<{
114
126
  errors: {
115
127
  level: "warning" | "fatal";
116
128
  message: string;
129
+ location?: {
130
+ start_offset: number;
131
+ end_offset: number;
132
+ } | undefined;
117
133
  ts?: string | undefined;
118
134
  }[];
119
135
  connections: {
@@ -130,6 +146,10 @@ export declare const diagnostics: router.Endpoint<{
130
146
  errors: {
131
147
  level: "warning" | "fatal";
132
148
  message: string;
149
+ location?: {
150
+ start_offset: number;
151
+ end_offset: number;
152
+ } | undefined;
133
153
  ts?: string | undefined;
134
154
  }[];
135
155
  pattern?: string | undefined;
@@ -150,6 +170,10 @@ export declare const diagnostics: router.Endpoint<{
150
170
  errors: {
151
171
  message: string;
152
172
  level: "warning" | "fatal";
173
+ location?: {
174
+ start_offset: number;
175
+ end_offset: number;
176
+ } | undefined;
153
177
  ts?: string | undefined;
154
178
  }[];
155
179
  id: string;
@@ -160,6 +184,10 @@ export declare const diagnostics: router.Endpoint<{
160
184
  errors: {
161
185
  level: "warning" | "fatal";
162
186
  message: string;
187
+ location?: {
188
+ start_offset: number;
189
+ end_offset: number;
190
+ } | undefined;
163
191
  ts?: string | undefined;
164
192
  }[];
165
193
  connections: {
@@ -176,6 +204,10 @@ export declare const diagnostics: router.Endpoint<{
176
204
  errors: {
177
205
  level: "warning" | "fatal";
178
206
  message: string;
207
+ location?: {
208
+ start_offset: number;
209
+ end_offset: number;
210
+ } | undefined;
179
211
  ts?: string | undefined;
180
212
  }[];
181
213
  pattern?: string | undefined;
@@ -191,6 +223,10 @@ export declare const diagnostics: router.Endpoint<{
191
223
  errors: {
192
224
  level: "warning" | "fatal";
193
225
  message: string;
226
+ location?: {
227
+ start_offset: number;
228
+ end_offset: number;
229
+ } | undefined;
194
230
  ts?: string | undefined;
195
231
  }[];
196
232
  connections: {
@@ -207,6 +243,10 @@ export declare const diagnostics: router.Endpoint<{
207
243
  errors: {
208
244
  level: "warning" | "fatal";
209
245
  message: string;
246
+ location?: {
247
+ start_offset: number;
248
+ end_offset: number;
249
+ } | undefined;
210
250
  ts?: string | undefined;
211
251
  }[];
212
252
  pattern?: string | undefined;
@@ -238,6 +278,10 @@ export declare const diagnostics: router.Endpoint<{
238
278
  errors: {
239
279
  message: string;
240
280
  level: "warning" | "fatal";
281
+ location?: {
282
+ start_offset: number;
283
+ end_offset: number;
284
+ } | undefined;
241
285
  ts?: string | undefined;
242
286
  }[];
243
287
  id: string;
@@ -248,6 +292,10 @@ export declare const diagnostics: router.Endpoint<{
248
292
  errors: {
249
293
  level: "warning" | "fatal";
250
294
  message: string;
295
+ location?: {
296
+ start_offset: number;
297
+ end_offset: number;
298
+ } | undefined;
251
299
  ts?: string | undefined;
252
300
  }[];
253
301
  connections: {
@@ -264,6 +312,10 @@ export declare const diagnostics: router.Endpoint<{
264
312
  errors: {
265
313
  level: "warning" | "fatal";
266
314
  message: string;
315
+ location?: {
316
+ start_offset: number;
317
+ end_offset: number;
318
+ } | undefined;
267
319
  ts?: string | undefined;
268
320
  }[];
269
321
  pattern?: string | undefined;
@@ -279,6 +331,10 @@ export declare const diagnostics: router.Endpoint<{
279
331
  errors: {
280
332
  level: "warning" | "fatal";
281
333
  message: string;
334
+ location?: {
335
+ start_offset: number;
336
+ end_offset: number;
337
+ } | undefined;
282
338
  ts?: string | undefined;
283
339
  }[];
284
340
  connections: {
@@ -295,6 +351,10 @@ export declare const diagnostics: router.Endpoint<{
295
351
  errors: {
296
352
  level: "warning" | "fatal";
297
353
  message: string;
354
+ location?: {
355
+ start_offset: number;
356
+ end_offset: number;
357
+ } | undefined;
298
358
  ts?: string | undefined;
299
359
  }[];
300
360
  pattern?: string | undefined;
@@ -421,6 +481,10 @@ export declare const validate: router.Endpoint<{
421
481
  errors: {
422
482
  message: string;
423
483
  level: "warning" | "fatal";
484
+ location?: {
485
+ start_offset: number;
486
+ end_offset: number;
487
+ } | undefined;
424
488
  ts?: string | undefined;
425
489
  }[];
426
490
  connections: {
@@ -432,6 +496,10 @@ export declare const validate: router.Endpoint<{
432
496
  errors: {
433
497
  message: string;
434
498
  level: "warning" | "fatal";
499
+ location?: {
500
+ start_offset: number;
501
+ end_offset: number;
502
+ } | undefined;
435
503
  ts?: string | undefined;
436
504
  }[];
437
505
  name: string;
@@ -455,6 +523,10 @@ export declare const validate: router.Endpoint<{
455
523
  errors: {
456
524
  message: string;
457
525
  level: "warning" | "fatal";
526
+ location?: {
527
+ start_offset: number;
528
+ end_offset: number;
529
+ } | undefined;
458
530
  ts?: string | undefined;
459
531
  }[];
460
532
  connections: {
@@ -466,6 +538,10 @@ export declare const validate: router.Endpoint<{
466
538
  errors: {
467
539
  message: string;
468
540
  level: "warning" | "fatal";
541
+ location?: {
542
+ start_offset: number;
543
+ end_offset: number;
544
+ } | undefined;
469
545
  ts?: string | undefined;
470
546
  }[];
471
547
  name: string;
@@ -500,6 +576,10 @@ export declare const validate: router.Endpoint<{
500
576
  errors: {
501
577
  message: string;
502
578
  level: "warning" | "fatal";
579
+ location?: {
580
+ start_offset: number;
581
+ end_offset: number;
582
+ } | undefined;
503
583
  ts?: string | undefined;
504
584
  }[];
505
585
  connections: {
@@ -511,6 +591,10 @@ export declare const validate: router.Endpoint<{
511
591
  errors: {
512
592
  message: string;
513
593
  level: "warning" | "fatal";
594
+ location?: {
595
+ start_offset: number;
596
+ end_offset: number;
597
+ } | undefined;
514
598
  ts?: string | undefined;
515
599
  }[];
516
600
  name: string;
@@ -598,6 +682,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
598
682
  errors: {
599
683
  message: string;
600
684
  level: "warning" | "fatal";
685
+ location?: {
686
+ start_offset: number;
687
+ end_offset: number;
688
+ } | undefined;
601
689
  ts?: string | undefined;
602
690
  }[];
603
691
  id: string;
@@ -608,6 +696,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
608
696
  errors: {
609
697
  level: "warning" | "fatal";
610
698
  message: string;
699
+ location?: {
700
+ start_offset: number;
701
+ end_offset: number;
702
+ } | undefined;
611
703
  ts?: string | undefined;
612
704
  }[];
613
705
  connections: {
@@ -624,6 +716,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
624
716
  errors: {
625
717
  level: "warning" | "fatal";
626
718
  message: string;
719
+ location?: {
720
+ start_offset: number;
721
+ end_offset: number;
722
+ } | undefined;
627
723
  ts?: string | undefined;
628
724
  }[];
629
725
  pattern?: string | undefined;
@@ -639,6 +735,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
639
735
  errors: {
640
736
  level: "warning" | "fatal";
641
737
  message: string;
738
+ location?: {
739
+ start_offset: number;
740
+ end_offset: number;
741
+ } | undefined;
642
742
  ts?: string | undefined;
643
743
  }[];
644
744
  connections: {
@@ -655,6 +755,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
655
755
  errors: {
656
756
  level: "warning" | "fatal";
657
757
  message: string;
758
+ location?: {
759
+ start_offset: number;
760
+ end_offset: number;
761
+ } | undefined;
658
762
  ts?: string | undefined;
659
763
  }[];
660
764
  pattern?: string | undefined;
@@ -675,6 +779,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
675
779
  errors: {
676
780
  message: string;
677
781
  level: "warning" | "fatal";
782
+ location?: {
783
+ start_offset: number;
784
+ end_offset: number;
785
+ } | undefined;
678
786
  ts?: string | undefined;
679
787
  }[];
680
788
  id: string;
@@ -685,6 +793,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
685
793
  errors: {
686
794
  level: "warning" | "fatal";
687
795
  message: string;
796
+ location?: {
797
+ start_offset: number;
798
+ end_offset: number;
799
+ } | undefined;
688
800
  ts?: string | undefined;
689
801
  }[];
690
802
  connections: {
@@ -701,6 +813,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
701
813
  errors: {
702
814
  level: "warning" | "fatal";
703
815
  message: string;
816
+ location?: {
817
+ start_offset: number;
818
+ end_offset: number;
819
+ } | undefined;
704
820
  ts?: string | undefined;
705
821
  }[];
706
822
  pattern?: string | undefined;
@@ -716,6 +832,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
716
832
  errors: {
717
833
  level: "warning" | "fatal";
718
834
  message: string;
835
+ location?: {
836
+ start_offset: number;
837
+ end_offset: number;
838
+ } | undefined;
719
839
  ts?: string | undefined;
720
840
  }[];
721
841
  connections: {
@@ -732,6 +852,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
732
852
  errors: {
733
853
  level: "warning" | "fatal";
734
854
  message: string;
855
+ location?: {
856
+ start_offset: number;
857
+ end_offset: number;
858
+ } | undefined;
735
859
  ts?: string | undefined;
736
860
  }[];
737
861
  pattern?: string | undefined;
@@ -763,6 +887,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
763
887
  errors: {
764
888
  message: string;
765
889
  level: "warning" | "fatal";
890
+ location?: {
891
+ start_offset: number;
892
+ end_offset: number;
893
+ } | undefined;
766
894
  ts?: string | undefined;
767
895
  }[];
768
896
  id: string;
@@ -773,6 +901,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
773
901
  errors: {
774
902
  level: "warning" | "fatal";
775
903
  message: string;
904
+ location?: {
905
+ start_offset: number;
906
+ end_offset: number;
907
+ } | undefined;
776
908
  ts?: string | undefined;
777
909
  }[];
778
910
  connections: {
@@ -789,6 +921,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
789
921
  errors: {
790
922
  level: "warning" | "fatal";
791
923
  message: string;
924
+ location?: {
925
+ start_offset: number;
926
+ end_offset: number;
927
+ } | undefined;
792
928
  ts?: string | undefined;
793
929
  }[];
794
930
  pattern?: string | undefined;
@@ -804,6 +940,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
804
940
  errors: {
805
941
  level: "warning" | "fatal";
806
942
  message: string;
943
+ location?: {
944
+ start_offset: number;
945
+ end_offset: number;
946
+ } | undefined;
807
947
  ts?: string | undefined;
808
948
  }[];
809
949
  connections: {
@@ -820,6 +960,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
820
960
  errors: {
821
961
  level: "warning" | "fatal";
822
962
  message: string;
963
+ location?: {
964
+ start_offset: number;
965
+ end_offset: number;
966
+ } | undefined;
823
967
  ts?: string | undefined;
824
968
  }[];
825
969
  pattern?: string | undefined;
@@ -943,6 +1087,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
943
1087
  errors: {
944
1088
  message: string;
945
1089
  level: "warning" | "fatal";
1090
+ location?: {
1091
+ start_offset: number;
1092
+ end_offset: number;
1093
+ } | undefined;
946
1094
  ts?: string | undefined;
947
1095
  }[];
948
1096
  connections: {
@@ -954,6 +1102,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
954
1102
  errors: {
955
1103
  message: string;
956
1104
  level: "warning" | "fatal";
1105
+ location?: {
1106
+ start_offset: number;
1107
+ end_offset: number;
1108
+ } | undefined;
957
1109
  ts?: string | undefined;
958
1110
  }[];
959
1111
  name: string;
@@ -977,6 +1129,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
977
1129
  errors: {
978
1130
  message: string;
979
1131
  level: "warning" | "fatal";
1132
+ location?: {
1133
+ start_offset: number;
1134
+ end_offset: number;
1135
+ } | undefined;
980
1136
  ts?: string | undefined;
981
1137
  }[];
982
1138
  connections: {
@@ -988,6 +1144,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
988
1144
  errors: {
989
1145
  message: string;
990
1146
  level: "warning" | "fatal";
1147
+ location?: {
1148
+ start_offset: number;
1149
+ end_offset: number;
1150
+ } | undefined;
991
1151
  ts?: string | undefined;
992
1152
  }[];
993
1153
  name: string;
@@ -1022,6 +1182,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
1022
1182
  errors: {
1023
1183
  message: string;
1024
1184
  level: "warning" | "fatal";
1185
+ location?: {
1186
+ start_offset: number;
1187
+ end_offset: number;
1188
+ } | undefined;
1025
1189
  ts?: string | undefined;
1026
1190
  }[];
1027
1191
  connections: {
@@ -1033,6 +1197,10 @@ export declare const ADMIN_ROUTES: ((router.Endpoint<{
1033
1197
  errors: {
1034
1198
  message: string;
1035
1199
  level: "warning" | "fatal";
1200
+ location?: {
1201
+ start_offset: number;
1202
+ end_offset: number;
1203
+ } | undefined;
1036
1204
  ts?: string | undefined;
1037
1205
  }[];
1038
1206
  name: string;
@@ -163,7 +163,8 @@ export interface CompactOptions {
163
163
  *
164
164
  * If not specified, compacts all buckets.
165
165
  *
166
- * These can be individual bucket names, or bucket definition names.
166
+ * These must be full bucket names (e.g., "global[]", "mybucket[\"user1\"]").
167
+ * Bucket definition names (e.g., "global") are not supported.
167
168
  */
168
169
  compactBuckets?: string[];
169
170
  compactParameterData?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"SyncRulesBucketStorage.js","sourceRoot":"","sources":["../../src/storage/SyncRulesBucketStorage.ts"],"names":[],"mappings":"AA6UA,MAAM,CAAC,MAAM,yBAAyB,GAAsB;IAC1D,kBAAkB,EAAE,IAAI,GAAG,EAAU;IACrC,qBAAqB,EAAE,IAAI;IAC3B,uBAAuB,EAAE,IAAI,GAAG,EAAU;IAC1C,0BAA0B,EAAE,IAAI;CACjC,CAAC"}
1
+ {"version":3,"file":"SyncRulesBucketStorage.js","sourceRoot":"","sources":["../../src/storage/SyncRulesBucketStorage.ts"],"names":[],"mappings":"AA8UA,MAAM,CAAC,MAAM,yBAAyB,GAAsB;IAC1D,kBAAkB,EAAE,IAAI,GAAG,EAAU;IACrC,qBAAqB,EAAE,IAAI;IAC3B,uBAAuB,EAAE,IAAI,GAAG,EAAU;IAC1C,0BAA0B,EAAE,IAAI;CACjC,CAAC"}
@@ -79,6 +79,11 @@ export interface CheckpointUpdate {
79
79
  * If null, assume that any bucket in `buckets` may have been updated.
80
80
  */
81
81
  updatedBuckets: Set<string> | typeof INVALIDATE_ALL_BUCKETS;
82
+ /**
83
+ * Number of parameter query results per sync stream definition (before deduplication).
84
+ * Map from definition name to count.
85
+ */
86
+ parameterQueryResultsByDefinition?: Map<string, number>;
82
87
  }
83
88
  export declare class BucketParameterState {
84
89
  private readonly context;
@@ -66,7 +66,7 @@ export class BucketChecksumState {
66
66
  const userIdForLogs = this.parameterState.syncParams.userId;
67
67
  const storage = this.bucketStorage;
68
68
  const update = await this.parameterState.getCheckpointUpdate(next);
69
- const { buckets: allBuckets, updatedBuckets } = update;
69
+ const { buckets: allBuckets, updatedBuckets, parameterQueryResultsByDefinition } = update;
70
70
  /** Set of all buckets in this checkpoint. */
71
71
  const bucketDescriptionMap = new Map(allBuckets.map((b) => [b.bucket, b]));
72
72
  if (bucketDescriptionMap.size > this.context.maxBuckets) {
@@ -145,18 +145,22 @@ export class BucketChecksumState {
145
145
  };
146
146
  });
147
147
  deferredLog = () => {
148
+ const totalParamResults = computeTotalParamResults(parameterQueryResultsByDefinition);
148
149
  let message = `Updated checkpoint: ${base.checkpoint} | `;
149
150
  message += `write: ${writeCheckpoint} | `;
150
151
  message += `buckets: ${allBuckets.length} | `;
152
+ if (totalParamResults !== undefined) {
153
+ message += `param_results: ${totalParamResults} | `;
154
+ }
151
155
  message += `updated: ${limitedBuckets(diff.updatedBuckets, 20)} | `;
152
156
  message += `removed: ${limitedBuckets(diff.removedBuckets, 20)}`;
153
- this.logger.info(message, {
157
+ logCheckpoint(this.logger, message, {
154
158
  checkpoint: base.checkpoint,
155
159
  user_id: userIdForLogs,
156
160
  buckets: allBuckets.length,
157
161
  updated: diff.updatedBuckets.length,
158
162
  removed: diff.removedBuckets.length
159
- });
163
+ }, totalParamResults);
160
164
  };
161
165
  checkpointLine = {
162
166
  checkpoint_diff: {
@@ -169,9 +173,18 @@ export class BucketChecksumState {
169
173
  }
170
174
  else {
171
175
  deferredLog = () => {
176
+ const totalParamResults = computeTotalParamResults(parameterQueryResultsByDefinition);
172
177
  let message = `New checkpoint: ${base.checkpoint} | write: ${writeCheckpoint} | `;
173
- message += `buckets: ${allBuckets.length} ${limitedBuckets(allBuckets, 20)}`;
174
- this.logger.info(message, { checkpoint: base.checkpoint, user_id: userIdForLogs, buckets: allBuckets.length });
178
+ message += `buckets: ${allBuckets.length}`;
179
+ if (totalParamResults !== undefined) {
180
+ message += ` | param_results: ${totalParamResults}`;
181
+ }
182
+ message += ` ${limitedBuckets(allBuckets, 20)}`;
183
+ logCheckpoint(this.logger, message, {
184
+ checkpoint: base.checkpoint,
185
+ user_id: userIdForLogs,
186
+ buckets: allBuckets.length
187
+ }, totalParamResults);
175
188
  };
176
189
  bucketsToFetch = allBuckets.map((b) => ({ bucket: b.bucket, priority: b.priority }));
177
190
  const subscriptions = [];
@@ -388,11 +401,18 @@ export class BucketParameterState {
388
401
  // TODO: Limit number of results even before we get to this point
389
402
  // This limit applies _before_ we get the unique set
390
403
  const error = new ServiceError(ErrorCode.PSYNC_S2305, `Too many parameter query results: ${update.buckets.length} (limit of ${this.context.maxParameterQueryResults})`);
391
- this.logger.error(error.message, {
404
+ let errorMessage = error.message;
405
+ const logData = {
392
406
  checkpoint: checkpoint,
393
407
  user_id: this.syncParams.userId,
394
- buckets: update.buckets.length
395
- });
408
+ parameter_query_results: update.buckets.length
409
+ };
410
+ if (update.parameterQueryResultsByDefinition && update.parameterQueryResultsByDefinition.size > 0) {
411
+ const breakdown = formatParameterQueryBreakdown(update.parameterQueryResultsByDefinition);
412
+ errorMessage += breakdown.message;
413
+ logData.parameter_query_results_by_definition = breakdown.countsByDefinition;
414
+ }
415
+ this.logger.error(errorMessage, logData);
396
416
  throw error;
397
417
  }
398
418
  return update;
@@ -440,6 +460,7 @@ export class BucketParameterState {
440
460
  }
441
461
  }
442
462
  let dynamicBuckets;
463
+ let parameterQueryResultsByDefinition;
443
464
  if (hasParameterChange || this.cachedDynamicBuckets == null || this.cachedDynamicBucketSet == null) {
444
465
  const recordedLookups = new Set();
445
466
  dynamicBuckets = await querier.queryDynamicBucketDescriptions({
@@ -450,6 +471,12 @@ export class BucketParameterState {
450
471
  return checkpoint.base.getParameterSets(lookups);
451
472
  }
452
473
  });
474
+ // Count parameter query results per definition (before deduplication)
475
+ parameterQueryResultsByDefinition = new Map();
476
+ for (const bucket of dynamicBuckets) {
477
+ const count = parameterQueryResultsByDefinition.get(bucket.definition) ?? 0;
478
+ parameterQueryResultsByDefinition.set(bucket.definition, count + 1);
479
+ }
453
480
  this.cachedDynamicBuckets = dynamicBuckets;
454
481
  this.cachedDynamicBucketSet = new Set(dynamicBuckets.map((b) => b.bucket));
455
482
  this.lookupsFromPreviousCheckpoint = recordedLookups;
@@ -471,17 +498,65 @@ export class BucketParameterState {
471
498
  return {
472
499
  buckets: allBuckets,
473
500
  // We cannot track individual bucket updates for dynamic lookups yet
474
- updatedBuckets: INVALIDATE_ALL_BUCKETS
501
+ updatedBuckets: INVALIDATE_ALL_BUCKETS,
502
+ parameterQueryResultsByDefinition
475
503
  };
476
504
  }
477
505
  else {
478
506
  return {
479
507
  buckets: allBuckets,
480
- updatedBuckets: updatedBuckets
508
+ updatedBuckets: updatedBuckets,
509
+ parameterQueryResultsByDefinition
481
510
  };
482
511
  }
483
512
  }
484
513
  }
514
+ /**
515
+ * Compute the total number of parameter query results across all definitions.
516
+ */
517
+ function computeTotalParamResults(parameterQueryResultsByDefinition) {
518
+ if (!parameterQueryResultsByDefinition) {
519
+ return undefined;
520
+ }
521
+ return Array.from(parameterQueryResultsByDefinition.values()).reduce((sum, count) => sum + count, 0);
522
+ }
523
+ /**
524
+ * Log a checkpoint message, enriching it with parameter query result counts if available.
525
+ *
526
+ * @param logger The logger instance to use
527
+ * @param message The base message string (param_results will NOT be appended — caller includes it if needed)
528
+ * @param logData The base log data object
529
+ * @param totalParamResults The total parameter query results count, or undefined if not applicable
530
+ */
531
+ function logCheckpoint(logger, message, logData, totalParamResults) {
532
+ if (totalParamResults !== undefined) {
533
+ logData.parameter_query_results = totalParamResults;
534
+ }
535
+ logger.info(message, logData);
536
+ }
537
+ /**
538
+ * Format a breakdown of parameter query results by sync rule definition.
539
+ *
540
+ * Sorts definitions by count (descending), includes the top 10, and returns both the
541
+ * formatted message string and the counts record suitable for structured log data.
542
+ */
543
+ function formatParameterQueryBreakdown(parameterQueryResultsByDefinition) {
544
+ // Sort definitions by count (descending) and take top 10
545
+ const allSorted = Array.from(parameterQueryResultsByDefinition.entries()).sort((a, b) => b[1] - a[1]);
546
+ const sortedDefinitions = allSorted.slice(0, 10);
547
+ let message = '\nParameter query results by definition:';
548
+ const countsByDefinition = {};
549
+ for (const [definition, count] of sortedDefinitions) {
550
+ message += `\n ${definition}: ${count}`;
551
+ countsByDefinition[definition] = count;
552
+ }
553
+ if (allSorted.length > 10) {
554
+ const remainingResults = allSorted.slice(10).reduce((sum, [, count]) => sum + count, 0);
555
+ const remainingDefinitions = allSorted.length - 10;
556
+ message += `\n ... and ${remainingResults} more results from ${remainingDefinitions} definitions`;
557
+ }
558
+ return { message, countsByDefinition };
559
+ }
485
560
  function limitedBuckets(buckets, limit) {
486
561
  buckets = buckets.map((b) => {
487
562
  if (typeof b != 'string') {