@tanstack/pacer-devtools 1.4.0 → 1.5.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.
@@ -1,176 +0,0 @@
1
- import {
2
- createEffect,
3
- createMemo,
4
- createSignal,
5
- onCleanup,
6
- onMount,
7
- } from 'solid-js'
8
- import { Header, HeaderLogo, MainPanel } from '@tanstack/devtools-ui'
9
- import { useStyles } from '../styles/use-styles'
10
- import { usePacerDevtoolsState } from '../PacerContextProvider'
11
- import { UTIL_GROUPS } from './util-groups'
12
- import { UtilList } from './UtilList'
13
- import { DetailsPanel } from './DetailsPanel'
14
- import type { StateKey } from './util-groups'
15
-
16
- function readResizeObserverBlockSize(entry: ResizeObserverEntry): number {
17
- const [box] = entry.borderBoxSize
18
- if (box !== undefined) return box.blockSize
19
- return entry.contentRect.height
20
- }
21
-
22
- /** Outer plugin slot from TanStack Devtools (not the inner React mount div, which can report a tiny height). */
23
- function resolvePluginHost(el: HTMLElement): HTMLElement | null {
24
- const byId = el.closest<HTMLElement>('[id^="plugin-container-"]')
25
- if (byId) return byId
26
- return (
27
- el.parentElement?.parentElement?.parentElement ??
28
- el.parentElement?.parentElement ??
29
- el.parentElement
30
- )
31
- }
32
-
33
- export function Shell() {
34
- const styles = useStyles()
35
- const state = usePacerDevtoolsState()
36
- const utilState = () => state
37
- const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
38
- const [leftPanelWidth, setLeftPanelWidth] = createSignal(300)
39
- const [isDragging, setIsDragging] = createSignal(false)
40
- const [shellRootEl, setShellRootEl] = createSignal<HTMLDivElement | null>(
41
- null,
42
- )
43
- const [slotHeightPx, setSlotHeightPx] = createSignal<number | undefined>(
44
- undefined,
45
- )
46
-
47
- const selectedInstance = createMemo(() => {
48
- const key = selectedKey()
49
- if (!key) return null
50
- for (const group of UTIL_GROUPS) {
51
- const instance = (utilState() as unknown as Record<StateKey, Array<any>>)[
52
- group.key
53
- ].find((inst) => inst.key === key)
54
- if (instance) return { instance, type: group.displayName }
55
- }
56
- return null
57
- })
58
-
59
- let dragStartX = 0
60
- let dragStartWidth = 0
61
-
62
- const handleMouseDown = (e: MouseEvent) => {
63
- e.preventDefault()
64
- e.stopPropagation()
65
- setIsDragging(true)
66
- document.body.style.cursor = 'col-resize'
67
- document.body.style.userSelect = 'none'
68
- dragStartX = e.clientX
69
- dragStartWidth = leftPanelWidth()
70
- }
71
-
72
- const handleMouseMove = (e: MouseEvent) => {
73
- if (!isDragging()) return
74
-
75
- e.preventDefault()
76
- const deltaX = e.clientX - dragStartX
77
- const newWidth = Math.max(150, Math.min(800, dragStartWidth + deltaX))
78
- setLeftPanelWidth(newWidth)
79
- }
80
-
81
- const handleMouseUp = () => {
82
- setIsDragging(false)
83
- document.body.style.cursor = ''
84
- document.body.style.userSelect = ''
85
- }
86
-
87
- onMount(() => {
88
- document.addEventListener('mousemove', handleMouseMove)
89
- document.addEventListener('mouseup', handleMouseUp)
90
- })
91
-
92
- onCleanup(() => {
93
- document.removeEventListener('mousemove', handleMouseMove)
94
- document.removeEventListener('mouseup', handleMouseUp)
95
- })
96
-
97
- /**
98
- * TanStack Devtools plugin slots use height: 100% without min-height: 0 on flex
99
- * ancestors, so percentage height often never constrains our tree. Observing the
100
- * outer `plugin-container-*` host (not the immediate React mount wrapper) and
101
- * setting an explicit pixel height makes inner overflow-y regions scroll without
102
- * capping to the mount div’s collapsed height.
103
- */
104
- createEffect(() => {
105
- const el = shellRootEl()
106
- if (!el || typeof ResizeObserver === 'undefined') return
107
- const pluginSlot = resolvePluginHost(el)
108
- if (!pluginSlot) return
109
-
110
- const ro = new ResizeObserver((entries) => {
111
- const entry = entries[0]
112
- if (!entry) return
113
- const target = entry.target as HTMLElement
114
- const h =
115
- target.clientHeight > 0
116
- ? target.clientHeight
117
- : readResizeObserverBlockSize(entry)
118
- if (h > 0) setSlotHeightPx(Math.round(h))
119
- })
120
- ro.observe(pluginSlot)
121
- onCleanup(() => ro.disconnect())
122
- })
123
-
124
- return (
125
- <div
126
- ref={setShellRootEl}
127
- class={styles().shellRoot}
128
- style={{
129
- ...(slotHeightPx() !== undefined
130
- ? {
131
- height: `${slotHeightPx()}px`,
132
- 'max-height': `${slotHeightPx()}px`,
133
- }
134
- : {}),
135
- }}
136
- >
137
- <MainPanel class={styles().mainPanelShell}>
138
- <Header>
139
- <HeaderLogo flavor={{ light: '#84cc16', dark: '#84cc16' }}>
140
- TanStack Pacer
141
- </HeaderLogo>
142
- </Header>
143
-
144
- <div class={styles().mainContainer}>
145
- <div
146
- class={styles().leftPanel}
147
- style={{
148
- width: `${leftPanelWidth()}px`,
149
- 'min-width': '150px',
150
- 'max-width': '800px',
151
- }}
152
- >
153
- <UtilList
154
- selectedKey={selectedKey}
155
- setSelectedKey={setSelectedKey}
156
- utilState={utilState}
157
- />
158
- </div>
159
-
160
- <div
161
- class={`${styles().dragHandle} ${isDragging() ? 'dragging' : ''}`}
162
- onMouseDown={handleMouseDown}
163
- />
164
-
165
- <div class={styles().rightPanel} style={{ flex: 1 }}>
166
- <div class={styles().panelHeader}>Details</div>
167
- <DetailsPanel
168
- selectedInstance={selectedInstance}
169
- utilState={utilState}
170
- />
171
- </div>
172
- </div>
173
- </MainPanel>
174
- </div>
175
- )
176
- }
@@ -1,139 +0,0 @@
1
- import dayjs from 'dayjs'
2
- import relativeTime from 'dayjs/plugin/relativeTime.js'
3
- import { Show, createSignal, onCleanup, onMount } from 'solid-js'
4
- import { shallow, useSelector } from '@tanstack/solid-store'
5
- import { useStyles } from '../styles/use-styles'
6
- import {
7
- getPacerUtilStoreState,
8
- isPacerUtilTanStackStore,
9
- } from '../utils/read-pacer-store-state'
10
-
11
- dayjs.extend(relativeTime)
12
-
13
- type StateHeaderProps = {
14
- selectedInstance: () => { instance: any; type: string } | null
15
- utilState: () => { lastUpdatedByKey: Record<string, number> }
16
- }
17
-
18
- function reductionFromState(
19
- entry: { type: string },
20
- state: Record<string, unknown> | null | undefined,
21
- ): number {
22
- if (!state) return 0
23
-
24
- const isAsync = entry.type.toLowerCase().includes('async')
25
- const completedExecutions = isAsync
26
- ? Number(state.settleCount) || 0
27
- : Number(state.executionCount) || 0
28
-
29
- if (entry.type.toLowerCase().includes('batcher')) {
30
- const totalItemsProcessed = Number(state.totalItemsProcessed) || 0
31
- if (totalItemsProcessed === 0) return 0
32
- return Math.round(
33
- ((totalItemsProcessed - completedExecutions) / totalItemsProcessed) * 100,
34
- )
35
- }
36
-
37
- let requestCount = 0
38
-
39
- if (state.maybeExecuteCount !== undefined) {
40
- requestCount = Number(state.maybeExecuteCount) || 0
41
- } else if (state.addItemCount !== undefined) {
42
- requestCount = Number(state.addItemCount) || 0
43
- } else {
44
- return 0
45
- }
46
-
47
- if (requestCount === 0) return 0
48
-
49
- const reduction = requestCount - completedExecutions
50
- return Math.round((reduction / requestCount) * 100)
51
- }
52
-
53
- function StateHeaderInner(props: {
54
- entry: { instance: any; type: string }
55
- styles: ReturnType<ReturnType<typeof useStyles>>
56
- now: () => number
57
- utilState: () => { lastUpdatedByKey: Record<string, number> }
58
- }) {
59
- const key = props.entry.instance.key as string
60
- const updatedAt = () => props.utilState().lastUpdatedByKey[key] ?? Date.now()
61
-
62
- const getRelativeTime = () => {
63
- const at = updatedAt()
64
- const diffMs = props.now() - at
65
- const diffSeconds = Math.floor(diffMs / 1000)
66
-
67
- if (diffSeconds < 60) {
68
- return `${diffSeconds} second${diffSeconds !== 1 ? 's' : ''} ago`
69
- }
70
-
71
- return dayjs(at).fromNow()
72
- }
73
-
74
- const store = props.entry.instance?.store
75
- const stateAccessor = isPacerUtilTanStackStore(store)
76
- ? useSelector(
77
- store as never,
78
- (s: unknown) =>
79
- s !== null && s !== undefined && typeof s === 'object'
80
- ? (s as Record<string, unknown>)
81
- : {},
82
- { compare: shallow },
83
- )
84
- : () =>
85
- getPacerUtilStoreState(props.entry.instance) as Record<string, unknown>
86
-
87
- const reductionPercentage = () =>
88
- reductionFromState(props.entry, stateAccessor())
89
-
90
- return (
91
- <div class={props.styles.stateHeader}>
92
- <div class={props.styles.stateTitle}>{props.entry.type}</div>
93
- <div style={{ display: 'flex', 'align-items': 'center', gap: '16px' }}>
94
- <div class={props.styles.infoGrid}>
95
- <div class={props.styles.infoLabel}>Key</div>
96
- <div class={props.styles.infoValueMono}>{key}</div>
97
- <div class={props.styles.infoLabel}>Last Updated</div>
98
- <div class={props.styles.infoValueMono}>
99
- {new Date(updatedAt()).toLocaleTimeString()} ({getRelativeTime()})
100
- </div>
101
- </div>
102
- <div
103
- class={props.styles.infoValueMono}
104
- style={{ 'margin-left': 'auto', 'font-weight': 'bold' }}
105
- >
106
- {reductionPercentage()}% reduction
107
- </div>
108
- </div>
109
- </div>
110
- )
111
- }
112
-
113
- export function StateHeader(props: StateHeaderProps) {
114
- const styles = useStyles()
115
- const [now, setNow] = createSignal(Date.now())
116
-
117
- onMount(() => {
118
- const interval = setInterval(() => {
119
- setNow(Date.now())
120
- }, 1000)
121
-
122
- onCleanup(() => {
123
- clearInterval(interval)
124
- })
125
- })
126
-
127
- return (
128
- <Show when={props.selectedInstance()}>
129
- {(entry) => (
130
- <StateHeaderInner
131
- entry={entry()}
132
- styles={styles()}
133
- now={now}
134
- utilState={props.utilState}
135
- />
136
- )}
137
- </Show>
138
- )
139
- }
@@ -1,62 +0,0 @@
1
- import { For } from 'solid-js'
2
- import { useSelector } from '@tanstack/solid-store'
3
- import clsx from 'clsx'
4
- import { useStyles } from '../styles/use-styles'
5
- import { UTIL_GROUPS } from './util-groups'
6
- import type { StateKey } from './util-groups'
7
-
8
- type UtilListProps = {
9
- selectedKey: () => string | null
10
- setSelectedKey: (key: string | null) => void
11
- utilState: () => Record<StateKey, Array<any>>
12
- }
13
-
14
- export function UtilList(props: UtilListProps) {
15
- const styles = useStyles()
16
-
17
- return (
18
- <div class={styles().utilList}>
19
- <For each={UTIL_GROUPS}>
20
- {(group) => (
21
- <>
22
- {props.utilState()[group.key].length > 0 && (
23
- <div class={styles().utilGroup}>
24
- <div class={styles().utilGroupHeader}>{group.label}</div>
25
- <For each={props.utilState()[group.key]}>
26
- {(instance) => {
27
- const status = (() => {
28
- try {
29
- const statusAccessor = useSelector(
30
- instance.store,
31
- (s: any) => s.status,
32
- )
33
- return () => statusAccessor() ?? 'unknown'
34
- } catch {
35
- return () => 'unknown'
36
- }
37
- })()
38
- return (
39
- <div
40
- class={clsx(
41
- styles().utilRow,
42
- props.selectedKey() === instance.key &&
43
- styles().utilRowSelected,
44
- )}
45
- onClick={() =>
46
- props.setSelectedKey(instance.key ?? null)
47
- }
48
- >
49
- <div class={styles().utilKey}>{instance.key}</div>
50
- <div class={styles().utilStatus}>{status()}</div>
51
- </div>
52
- )
53
- }}
54
- </For>
55
- </div>
56
- )}
57
- </>
58
- )}
59
- </For>
60
- </div>
61
- )
62
- }
@@ -1,25 +0,0 @@
1
- import { JsonTree } from '@tanstack/devtools-ui'
2
- import { useSelector } from '@tanstack/solid-store'
3
- import {
4
- getPacerUtilStoreState,
5
- isPacerUtilTanStackStore,
6
- } from '../utils/read-pacer-store-state'
7
- import type { JSX } from 'solid-js'
8
-
9
- type UtilStateJsonTreeProps = {
10
- instance: any
11
- }
12
-
13
- /**
14
- * Subscribes to the util's TanStack Store so the tree updates live (same
15
- * idea as {@link UtilList} status). A plain snapshot would only refresh when the
16
- * devtools shell re-renders, which does not happen on nested store updates.
17
- */
18
- export function UtilStateJsonTree(props: UtilStateJsonTreeProps): JSX.Element {
19
- const store = props.instance?.store
20
- if (isPacerUtilTanStackStore(store)) {
21
- const snapshot = useSelector(store as never, (s: unknown) => s)
22
- return <JsonTree value={snapshot()} />
23
- }
24
- return <JsonTree value={getPacerUtilStoreState(props.instance)} />
25
- }
@@ -1,19 +0,0 @@
1
- import { ThemeContextProvider } from '@tanstack/devtools-ui'
2
- import { PacerContextProvider } from '../PacerContextProvider'
3
- import { Shell } from './Shell'
4
-
5
- import type { TanStackDevtoolsTheme } from '@tanstack/devtools-ui'
6
-
7
- interface DevtoolsProps {
8
- theme: TanStackDevtoolsTheme
9
- }
10
-
11
- export default function PacerDevtools(props: DevtoolsProps) {
12
- return (
13
- <ThemeContextProvider theme={props.theme}>
14
- <PacerContextProvider>
15
- <Shell />
16
- </PacerContextProvider>
17
- </ThemeContextProvider>
18
- )
19
- }
@@ -1,54 +0,0 @@
1
- export type StateKey =
2
- | 'asyncBatchers'
3
- | 'asyncDebouncers'
4
- | 'asyncQueuers'
5
- | 'asyncRateLimiters'
6
- | 'asyncThrottlers'
7
- | 'batchers'
8
- | 'debouncers'
9
- | 'queuers'
10
- | 'rateLimiters'
11
- | 'throttlers'
12
-
13
- type UtilGroup = {
14
- key: StateKey
15
- label: string
16
- displayName: string
17
- }
18
-
19
- export const UTIL_GROUPS: Array<UtilGroup> = [
20
- { key: 'debouncers', label: 'Debouncers', displayName: 'Debouncer' },
21
- { key: 'throttlers', label: 'Throttlers', displayName: 'Throttler' },
22
- { key: 'batchers', label: 'Batchers', displayName: 'Batcher' },
23
- { key: 'queuers', label: 'Queuers', displayName: 'Queuer' },
24
- {
25
- key: 'rateLimiters',
26
- label: 'Rate Limiters',
27
- displayName: 'Rate Limiter',
28
- },
29
- {
30
- key: 'asyncDebouncers',
31
- label: 'Async Debouncers',
32
- displayName: 'Async Debouncer',
33
- },
34
- {
35
- key: 'asyncThrottlers',
36
- label: 'Async Throttlers',
37
- displayName: 'Async Throttler',
38
- },
39
- {
40
- key: 'asyncBatchers',
41
- label: 'Async Batchers',
42
- displayName: 'Async Batcher',
43
- },
44
- {
45
- key: 'asyncQueuers',
46
- label: 'Async Queuers',
47
- displayName: 'Async Queuer',
48
- },
49
- {
50
- key: 'asyncRateLimiters',
51
- label: 'Async Rate Limiters',
52
- displayName: 'Async Rate Limiter',
53
- },
54
- ]
package/src/core.tsx DELETED
@@ -1,9 +0,0 @@
1
- import { constructCoreClass } from '@tanstack/devtools-utils/solid'
2
- import type { ClassType } from '@tanstack/devtools-utils/solid'
3
-
4
- export interface PacerDevtoolsInit {}
5
-
6
- const coreClasses = constructCoreClass(() => import('./components'))
7
-
8
- export const PacerDevtoolsCore: ClassType = coreClasses[0]
9
- export const PacerDevtoolsCoreNoOp: ClassType = coreClasses[1]
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- 'use client'
2
-
3
- import * as Devtools from './core'
4
-
5
- export const PacerDevtoolsCore: typeof Devtools.PacerDevtoolsCore =
6
- process.env.NODE_ENV !== 'development'
7
- ? Devtools.PacerDevtoolsCoreNoOp
8
- : Devtools.PacerDevtoolsCore
9
-
10
- export type { PacerDevtoolsInit } from './core'
package/src/production.ts DELETED
@@ -1,5 +0,0 @@
1
- 'use client'
2
-
3
- export { PacerDevtoolsCore } from './core'
4
-
5
- export type { PacerDevtoolsInit } from './core'