@@ -8,8 +8,9 @@ import { Spinner } from '@/components/ui/spinner';
88import { Table , TableBody , TableCell , TableHead , TableHeader , TableRow } from '@/components/ui/table' ;
99import { NewStreamSubscription } from '@/components/widgets/NewStreamSubscription' ;
1010import { StreamsTable } from '@/components/widgets/StreamsTable' ;
11- import { formatBytes } from '@/lib/utils' ;
11+ import { cn , formatBytes } from '@/lib/utils' ;
1212import { clearData , connector , db , sync , useSyncStatus } from '@/library/powersync/ConnectionManager' ;
13+ import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from '@/components/ui/tooltip' ;
1314import { decodeTokenPayload , getTokenUserId } from '@/library/powersync/TokenConnector' ;
1415import { useQueryClient , useQuery as useTanstackQuery } from '@tanstack/react-query' ;
1516import { ChevronDown , ChevronUp , Eye , Info } from 'lucide-react' ;
@@ -90,6 +91,17 @@ const syncDiagnosticsKeys = {
9091/** When total_operations exceeds row_count by this factor, we show a warning that bucket history has accumulated and compacting may help. This is an abritrary threshold and indicates significant history buildup.*/
9192const BUCKET_HISTORY_THRESHOLD = 3 ;
9293
94+ /**
95+ * Default server-side limit for both the "too many buckets" and "too many parameter query results"
96+ * limits (both reported as error PSYNC_S2305). These are two distinct limits that often coincide
97+ * but can differ:
98+ * - Duplicate parameter query results count individually toward the parameter result limit,
99+ * but are de-duplicated in the bucket count.
100+ * - Non-partitioning queries count 1 toward the bucket limit but not toward the parameter result limit.
101+ * The default is 1000 but can be configured on the server.
102+ */
103+ const DEFAULT_SYNC_LIMIT = 1000 ;
104+
93105interface SyncStats {
94106 bucketRows : any [ ] | null ;
95107 tableRows : any [ ] | null ;
@@ -222,6 +234,7 @@ export default function SyncDiagnosticsPage() {
222234
223235 const totals = {
224236 buckets : rows . length ,
237+ parameterized_buckets : rows . filter ( ( row ) => ! row . name . endsWith ( '[]' ) ) . length ,
225238 row_count : rows . reduce ( ( total , row ) => total + row . row_count , 0 ) ,
226239 downloaded_operations : rows . reduce ( ( total , row ) => total + row . downloaded_operations , 0 ) ,
227240 total_operations : rows . reduce ( ( total , row ) => total + row . total_operations , 0 ) ,
@@ -272,7 +285,44 @@ export default function SyncDiagnosticsPage() {
272285 < Table >
273286 < TableHeader >
274287 < TableRow >
275- < TableHead > Buckets</ TableHead >
288+ < TableHead >
289+ < TooltipProvider >
290+ < Tooltip >
291+ < TooltipTrigger asChild >
292+ < span className = "inline-flex items-center gap-1 cursor-default" >
293+ Buckets
294+ < Info className = "h-3 w-3 text-muted-foreground" />
295+ </ span >
296+ </ TooltipTrigger >
297+ < TooltipContent className = "max-w-92" >
298+ Two separate limits apply (both PSYNC_S2305, default { DEFAULT_SYNC_LIMIT . toLocaleString ( ) } { ' ' }
299+ each): < strong > bucket count</ strong > and < strong > parameter query results</ strong > .
300+ < div className = "mt-2" >
301+ Global buckets count only toward bucket count. Parameter query buckets (in either{ ' ' }
302+ < a
303+ href = "https://docs.powersync.com/sync/rules/parameter-queries"
304+ target = "_blank"
305+ rel = "noopener noreferrer"
306+ className = "underline hover:text-foreground"
307+ >
308+ Sync Rules
309+ </ a > { ' ' }
310+ or{ ' ' }
311+ < a
312+ href = "https://docs.powersync.com/sync/streams/parameters"
313+ target = "_blank"
314+ rel = "noopener noreferrer"
315+ className = "underline hover:text-foreground"
316+ >
317+ Sync Streams
318+ </ a >
319+ ) count toward both, but are de-duplicated client-side, so the server's parameter result count
320+ may be higher.
321+ </ div >
322+ </ TooltipContent >
323+ </ Tooltip >
324+ </ TooltipProvider >
325+ </ TableHead >
276326 < TableHead className = "text-right" > Total Rows</ TableHead >
277327 < TableHead className = "text-right" > Downloaded Ops</ TableHead >
278328 < TableHead className = "text-right" > Total Ops</ TableHead >
@@ -284,7 +334,22 @@ export default function SyncDiagnosticsPage() {
284334 </ TableHeader >
285335 < TableBody >
286336 < TableRow >
287- < TableCell className = "font-medium" > { totals . buckets } </ TableCell >
337+ < TableCell className = "font-medium" >
338+ < span
339+ className = { cn (
340+ totals . buckets >= 900 ? 'text-destructive' : totals . buckets >= 800 ? 'text-amber-600' : ''
341+ ) }
342+ >
343+ { totals . buckets . toLocaleString ( ) }
344+ </ span >
345+ < span className = "text-muted-foreground text-xs ml-1" >
346+ / { DEFAULT_SYNC_LIMIT . toLocaleString ( ) } (default)
347+ </ span >
348+ < div className = "text-muted-foreground text-xs font-normal mt-0.5" >
349+ { totals . parameterized_buckets . toLocaleString ( ) } parameterized,{ ' ' }
350+ { ( totals . buckets - totals . parameterized_buckets ) . toLocaleString ( ) } global
351+ </ div >
352+ </ TableCell >
288353 < TableCell className = "text-right" > { totals . row_count . toLocaleString ( ) } </ TableCell >
289354 < TableCell className = "text-right" > { totals . downloaded_operations . toLocaleString ( ) } </ TableCell >
290355 < TableCell className = "text-right" > { totals . total_operations . toLocaleString ( ) } </ TableCell >
@@ -300,7 +365,22 @@ export default function SyncDiagnosticsPage() {
300365 < div className = "md:hidden p-4 grid grid-cols-2 gap-3 text-sm" >
301366 < div >
302367 < div className = "text-muted-foreground" > Buckets</ div >
303- < div className = "font-medium" > { totals . buckets } </ div >
368+ < div className = "font-medium" >
369+ < span
370+ className = { cn (
371+ totals . buckets >= 900 ? 'text-destructive' : totals . buckets >= 800 ? 'text-amber-600' : ''
372+ ) }
373+ >
374+ { totals . buckets . toLocaleString ( ) }
375+ </ span >
376+ < span className = "text-muted-foreground text-xs ml-1" >
377+ / { DEFAULT_SYNC_LIMIT . toLocaleString ( ) } (default)
378+ </ span >
379+ < div className = "text-muted-foreground text-xs font-normal mt-0.5" >
380+ { totals . parameterized_buckets . toLocaleString ( ) } parameterized,{ ' ' }
381+ { ( totals . buckets - totals . parameterized_buckets ) . toLocaleString ( ) } global
382+ </ div >
383+ </ div >
304384 </ div >
305385 < div >
306386 < div className = "text-muted-foreground" > Total Rows</ div >
@@ -358,7 +438,8 @@ export default function SyncDiagnosticsPage() {
358438 variant = "ghost"
359439 size = "sm"
360440 className = "h-7 gap-1.5 text-muted-foreground"
361- onClick = { ( ) => setShowTokenDialog ( true ) } >
441+ onClick = { ( ) => setShowTokenDialog ( true ) }
442+ >
362443 < Eye className = "h-3.5 w-3.5" />
363444 View Token
364445 </ Button >
@@ -401,7 +482,8 @@ export default function SyncDiagnosticsPage() {
401482 variant = "outline"
402483 onClick = { ( ) => {
403484 clearData ( ) ;
404- } } >
485+ } }
486+ >
405487 Clear & Redownload
406488 </ Button >
407489 < span className = "text-sm text-muted-foreground" >
@@ -420,12 +502,35 @@ export default function SyncDiagnosticsPage() {
420502 href = "https://docs.powersync.com/maintenance-ops/compacting-buckets"
421503 target = "_blank"
422504 rel = "noopener noreferrer"
423- className = "underline hover:text-foreground" >
505+ className = "underline hover:text-foreground"
506+ >
424507 Learn about compacting
425508 </ a >
426509 </ AlertDescription >
427510 </ Alert >
428511 ) }
512+ { totals . buckets >= 800 && (
513+ < Alert className = { cn ( 'mt-4' , totals . buckets >= 900 ? 'border-destructive/50' : 'border-amber-500/50' ) } >
514+ < Info className = { cn ( 'h-4 w-4' , totals . buckets >= 900 ? 'text-destructive' : 'text-amber-600' ) } />
515+ < AlertDescription >
516+ < span className = { cn ( 'font-medium' , totals . buckets >= 900 ? 'text-destructive' : 'text-amber-600' ) } >
517+ { totals . buckets >= 900 ? 'Critical: ' : 'Warning: ' }
518+ </ span >
519+ { totals . buckets . toLocaleString ( ) } of { DEFAULT_SYNC_LIMIT . toLocaleString ( ) } buckets used (PSYNC_S2305,
520+ default limit). { totals . parameterized_buckets . toLocaleString ( ) } are parameterized - at least that many
521+ parameter query results on the server. Review your Sync Config to reduce buckets and parameter query
522+ results for this user.{ ' ' }
523+ < a
524+ href = "https://docs.powersync.com/debugging/troubleshooting#too-many-buckets-psync_s2305"
525+ target = "_blank"
526+ rel = "noopener noreferrer"
527+ className = "underline hover:text-foreground"
528+ >
529+ For troubleshooting steps, see the docs
530+ </ a >
531+ </ AlertDescription >
532+ </ Alert >
533+ ) }
429534 </ div >
430535
431536 < div className = "space-y-4" >
@@ -511,7 +616,8 @@ function TruncatedTablesList({ tables }: { tables: string }) {
511616 e . stopPropagation ( ) ;
512617 setExpanded ( ! expanded ) ;
513618 } }
514- className = "inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors" >
619+ className = "inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
620+ >
515621 { expanded ? (
516622 < >
517623 < ChevronUp className = "h-3 w-3" />
0 commit comments