@geenius/tools 0.1.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/.changeset/config.json +11 -0
- package/.env.example +2 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +16 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +11 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +10 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +23 -0
- package/.github/workflows/release.yml +29 -0
- package/.node-version +1 -0
- package/.nvmrc +1 -0
- package/.prettierrc +7 -0
- package/.project/ACCOUNT.yaml +4 -0
- package/.project/IDEAS.yaml +7 -0
- package/.project/PROJECT.yaml +11 -0
- package/.project/ROADMAP.yaml +15 -0
- package/CHANGELOG.md +16 -0
- package/CODE_OF_CONDUCT.md +26 -0
- package/CONTRIBUTING.md +69 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/SECURITY.md +18 -0
- package/SUPPORT.md +14 -0
- package/package.json +75 -0
- package/packages/convex/shared/README.md +1 -0
- package/packages/convex/shared/package.json +42 -0
- package/packages/convex/shared/src/audit/index.ts +5 -0
- package/packages/convex/shared/src/audit/presets.ts +165 -0
- package/packages/convex/shared/src/audit/schema.ts +85 -0
- package/packages/convex/shared/src/audit/write.ts +102 -0
- package/packages/convex/shared/src/extract.ts +75 -0
- package/packages/convex/shared/src/index.ts +41 -0
- package/packages/convex/shared/src/messages.ts +45 -0
- package/packages/convex/shared/src/security.ts +112 -0
- package/packages/convex/shared/src/throw.ts +184 -0
- package/packages/convex/shared/src/types.ts +57 -0
- package/packages/convex/shared/src/utils.ts +58 -0
- package/packages/convex/shared/tsconfig.json +28 -0
- package/packages/convex/shared/tsup.config.ts +12 -0
- package/packages/devtools/package.json +27 -0
- package/packages/devtools/react/README.md +1 -0
- package/packages/devtools/react/package.json +53 -0
- package/packages/devtools/react/src/components/DesignPreview.tsx +59 -0
- package/packages/devtools/react/src/components/DesignSwitcherDropdown.tsx +99 -0
- package/packages/devtools/react/src/components/DevSidebar.tsx +247 -0
- package/packages/devtools/react/src/components/DevToolbar.tsx +242 -0
- package/packages/devtools/react/src/components/GitHubIssueDialog.tsx +402 -0
- package/packages/devtools/react/src/components/InspectorOverlay.tsx +312 -0
- package/packages/devtools/react/src/components/PageLoadWaterfall.tsx +144 -0
- package/packages/devtools/react/src/components/PerformancePanel.tsx +330 -0
- package/packages/devtools/react/src/context/DevModeContext.tsx +226 -0
- package/packages/devtools/react/src/context/PerformanceContext.tsx +143 -0
- package/packages/devtools/react/src/data/designs.ts +13 -0
- package/packages/devtools/react/src/hooks/useGitHubLabels.ts +47 -0
- package/packages/devtools/react/src/hooks/useVirtualList.ts +124 -0
- package/packages/devtools/react/src/index.ts +77 -0
- package/packages/devtools/react/src/panels/ConvexSpy.tsx +130 -0
- package/packages/devtools/react/src/panels/DatabaseSeeder.tsx +116 -0
- package/packages/devtools/react/src/panels/DevModePhase2.tsx +191 -0
- package/packages/devtools/react/src/panels/DevModePhase3.tsx +234 -0
- package/packages/devtools/react/src/panels/FeatureFlagsToggle.tsx +104 -0
- package/packages/devtools/react/src/panels/QuickRouteJump.tsx +152 -0
- package/packages/devtools/react/src/services/github-service.ts +247 -0
- package/packages/devtools/react/tsconfig.json +31 -0
- package/packages/devtools/react/tsup.config.ts +18 -0
- package/packages/devtools/solidjs/README.md +1 -0
- package/packages/devtools/solidjs/package.json +49 -0
- package/packages/devtools/solidjs/src/components/DesignPreview.tsx +51 -0
- package/packages/devtools/solidjs/src/components/DesignSwitcherDropdown.tsx +95 -0
- package/packages/devtools/solidjs/src/components/DevSidebar.tsx +247 -0
- package/packages/devtools/solidjs/src/components/DevToolbar.tsx +242 -0
- package/packages/devtools/solidjs/src/components/GitHubIssueDialog.tsx +400 -0
- package/packages/devtools/solidjs/src/components/InspectorOverlay.tsx +311 -0
- package/packages/devtools/solidjs/src/components/PageLoadWaterfall.tsx +144 -0
- package/packages/devtools/solidjs/src/components/PerformancePanel.tsx +330 -0
- package/packages/devtools/solidjs/src/context/DevModeContext.tsx +216 -0
- package/packages/devtools/solidjs/src/context/PerformanceContext.tsx +135 -0
- package/packages/devtools/solidjs/src/data/designs.ts +13 -0
- package/packages/devtools/solidjs/src/hooks/createGitHubLabels.ts +47 -0
- package/packages/devtools/solidjs/src/index.ts +64 -0
- package/packages/devtools/solidjs/src/services/github-service.ts +247 -0
- package/packages/devtools/solidjs/tsconfig.json +21 -0
- package/packages/devtools/src/index.ts +377 -0
- package/packages/devtools/tsup.config.ts +12 -0
- package/packages/env/package.json +30 -0
- package/packages/env/src/index.ts +264 -0
- package/packages/env/tsup.config.ts +12 -0
- package/packages/errors/package.json +27 -0
- package/packages/errors/react/README.md +1 -0
- package/packages/errors/react/package.json +72 -0
- package/packages/errors/react/src/analytics.ts +16 -0
- package/packages/errors/react/src/components/ErrorBoundary.tsx +248 -0
- package/packages/errors/react/src/components/ErrorDisplay.tsx +328 -0
- package/packages/errors/react/src/components/ValidationErrors.tsx +102 -0
- package/packages/errors/react/src/config.ts +199 -0
- package/packages/errors/react/src/constants.ts +74 -0
- package/packages/errors/react/src/hooks/useErrorBoundary.ts +92 -0
- package/packages/errors/react/src/hooks/useErrorHandler.ts +87 -0
- package/packages/errors/react/src/index.ts +96 -0
- package/packages/errors/react/src/types.ts +102 -0
- package/packages/errors/react/src/utils/errorMessages.ts +35 -0
- package/packages/errors/react/src/utils/errorPolicy.ts +139 -0
- package/packages/errors/react/src/utils/extractAppError.ts +174 -0
- package/packages/errors/react/src/utils/formatError.ts +112 -0
- package/packages/errors/react/tsconfig.json +25 -0
- package/packages/errors/react/tsup.config.ts +24 -0
- package/packages/errors/solidjs/README.md +1 -0
- package/packages/errors/solidjs/package.json +46 -0
- package/packages/errors/solidjs/src/components/ErrorDisplay.tsx +179 -0
- package/packages/errors/solidjs/src/config.ts +98 -0
- package/packages/errors/solidjs/src/hooks/createErrorHandler.ts +107 -0
- package/packages/errors/solidjs/src/index.ts +61 -0
- package/packages/errors/solidjs/src/types.ts +34 -0
- package/packages/errors/solidjs/src/utils/errorPolicy.ts +56 -0
- package/packages/errors/solidjs/src/utils/extractAppError.ts +94 -0
- package/packages/errors/solidjs/src/utils/formatError.ts +33 -0
- package/packages/errors/solidjs/tsconfig.json +26 -0
- package/packages/errors/solidjs/tsup.config.ts +21 -0
- package/packages/errors/src/index.ts +320 -0
- package/packages/errors/tsup.config.ts +12 -0
- package/packages/logger/package.json +27 -0
- package/packages/logger/react/README.md +1 -0
- package/packages/logger/react/package.json +46 -0
- package/packages/logger/react/src/index.ts +4 -0
- package/packages/logger/react/src/useMetrics.ts +42 -0
- package/packages/logger/react/src/usePerformanceLog.ts +61 -0
- package/packages/logger/react/tsconfig.json +31 -0
- package/packages/logger/react/tsup.config.ts +12 -0
- package/packages/logger/solidjs/README.md +1 -0
- package/packages/logger/solidjs/package.json +45 -0
- package/packages/logger/solidjs/src/createMetrics.ts +37 -0
- package/packages/logger/solidjs/src/createPerformanceLog.ts +58 -0
- package/packages/logger/solidjs/src/index.ts +4 -0
- package/packages/logger/solidjs/tsconfig.json +32 -0
- package/packages/logger/solidjs/tsup.config.ts +12 -0
- package/packages/logger/src/index.ts +363 -0
- package/packages/logger/tsup.config.ts +12 -0
- package/packages/perf/package.json +27 -0
- package/packages/perf/react/README.md +1 -0
- package/packages/perf/react/package.json +59 -0
- package/packages/perf/react/src/components/PerformanceDashboard.tsx +257 -0
- package/packages/perf/react/src/hooks/useMonitoredQuery.ts +89 -0
- package/packages/perf/react/src/hooks/usePerformanceMetrics.ts +78 -0
- package/packages/perf/react/src/index.ts +33 -0
- package/packages/perf/react/src/services/PerformanceMonitor.ts +313 -0
- package/packages/perf/react/src/types.ts +77 -0
- package/packages/perf/react/tsconfig.json +25 -0
- package/packages/perf/react/tsup.config.ts +19 -0
- package/packages/perf/solidjs/README.md +1 -0
- package/packages/perf/solidjs/package.json +41 -0
- package/packages/perf/solidjs/src/components/PerformanceDashboard.tsx +207 -0
- package/packages/perf/solidjs/src/hooks/createPerformanceMetrics.ts +73 -0
- package/packages/perf/solidjs/src/index.ts +31 -0
- package/packages/perf/solidjs/src/services/PerformanceMonitor.ts +134 -0
- package/packages/perf/solidjs/src/types.ts +78 -0
- package/packages/perf/solidjs/tsconfig.json +26 -0
- package/packages/perf/solidjs/tsup.config.ts +14 -0
- package/packages/perf/src/index.ts +410 -0
- package/packages/perf/tsup.config.ts +12 -0
- package/pnpm-workspace.yaml +2 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
// @geenius-tools/perf-react — src/services/PerformanceMonitor.ts
|
|
2
|
+
// Singleton performance monitoring service (framework-agnostic)
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
PerformanceMetrics,
|
|
6
|
+
PerformanceConfig,
|
|
7
|
+
CacheMetrics,
|
|
8
|
+
QueryMetrics,
|
|
9
|
+
PageLoadMetrics,
|
|
10
|
+
ErrorMetrics,
|
|
11
|
+
CacheHitEvent,
|
|
12
|
+
CacheMissEvent,
|
|
13
|
+
} from '../types'
|
|
14
|
+
|
|
15
|
+
interface QueryEntry {
|
|
16
|
+
key: string
|
|
17
|
+
duration: number
|
|
18
|
+
timestamp: number
|
|
19
|
+
error?: Error
|
|
20
|
+
cached: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface CacheEntry {
|
|
24
|
+
type: 'hit' | 'miss'
|
|
25
|
+
queryKey: string
|
|
26
|
+
duration: number
|
|
27
|
+
fetchDuration?: number
|
|
28
|
+
timestamp: number
|
|
29
|
+
cacheType: 'ssr' | 'client' | 'memory'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ErrorEntry {
|
|
33
|
+
type: string
|
|
34
|
+
message: string
|
|
35
|
+
stack?: string
|
|
36
|
+
timestamp: number
|
|
37
|
+
source: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface PageLoadEntry {
|
|
41
|
+
route: string
|
|
42
|
+
ttfb: number
|
|
43
|
+
fcp: number
|
|
44
|
+
lcp: number
|
|
45
|
+
tti: number
|
|
46
|
+
loadTime: number
|
|
47
|
+
ssr: boolean
|
|
48
|
+
timestamp: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const DEFAULT_CONFIG: PerformanceConfig = {
|
|
52
|
+
enableCacheTracking: true,
|
|
53
|
+
enableQueryTracking: true,
|
|
54
|
+
enablePageLoadTracking: true,
|
|
55
|
+
enableErrorTracking: true,
|
|
56
|
+
sampleRate: 1,
|
|
57
|
+
maxMetrics: 1000,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* PerformanceMonitor — singleton service for tracking performance metrics
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* PerformanceMonitor.trackQuery('users', 45, undefined, false)
|
|
65
|
+
* const metrics = PerformanceMonitor.getMetrics({ start: Date.now() - 3600000, end: Date.now() })
|
|
66
|
+
*/
|
|
67
|
+
export class PerformanceMonitor {
|
|
68
|
+
private static queries: QueryEntry[] = []
|
|
69
|
+
private static cacheEntries: CacheEntry[] = []
|
|
70
|
+
private static errors: ErrorEntry[] = []
|
|
71
|
+
private static pageLoads: PageLoadEntry[] = []
|
|
72
|
+
private static config: PerformanceConfig = DEFAULT_CONFIG
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Configure the performance monitor
|
|
76
|
+
*/
|
|
77
|
+
static configure(config: Partial<PerformanceConfig>): void {
|
|
78
|
+
PerformanceMonitor.config = { ...PerformanceMonitor.config, ...config }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Track a query execution
|
|
83
|
+
*/
|
|
84
|
+
static trackQuery(
|
|
85
|
+
queryKey: string,
|
|
86
|
+
duration: number,
|
|
87
|
+
error?: Error,
|
|
88
|
+
cached = false,
|
|
89
|
+
): void {
|
|
90
|
+
if (!PerformanceMonitor.config.enableQueryTracking) return
|
|
91
|
+
if (Math.random() > (PerformanceMonitor.config.sampleRate ?? 1)) return
|
|
92
|
+
|
|
93
|
+
PerformanceMonitor.queries.push({
|
|
94
|
+
key: queryKey,
|
|
95
|
+
duration,
|
|
96
|
+
timestamp: Date.now(),
|
|
97
|
+
error,
|
|
98
|
+
cached,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Trim if over limit
|
|
102
|
+
const max = PerformanceMonitor.config.maxMetrics ?? 1000
|
|
103
|
+
if (PerformanceMonitor.queries.length > max) {
|
|
104
|
+
PerformanceMonitor.queries = PerformanceMonitor.queries.slice(-max)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Track a cache hit
|
|
110
|
+
*/
|
|
111
|
+
static trackCacheHit(event: CacheHitEvent): void {
|
|
112
|
+
if (!PerformanceMonitor.config.enableCacheTracking) return
|
|
113
|
+
|
|
114
|
+
PerformanceMonitor.cacheEntries.push({
|
|
115
|
+
type: 'hit',
|
|
116
|
+
queryKey: event.queryKey,
|
|
117
|
+
duration: event.duration,
|
|
118
|
+
timestamp: event.timestamp,
|
|
119
|
+
cacheType: event.cacheType,
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const max = PerformanceMonitor.config.maxMetrics ?? 1000
|
|
123
|
+
if (PerformanceMonitor.cacheEntries.length > max) {
|
|
124
|
+
PerformanceMonitor.cacheEntries = PerformanceMonitor.cacheEntries.slice(-max)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Track a cache miss
|
|
130
|
+
*/
|
|
131
|
+
static trackCacheMiss(event: CacheMissEvent): void {
|
|
132
|
+
if (!PerformanceMonitor.config.enableCacheTracking) return
|
|
133
|
+
|
|
134
|
+
PerformanceMonitor.cacheEntries.push({
|
|
135
|
+
type: 'miss',
|
|
136
|
+
queryKey: event.queryKey,
|
|
137
|
+
duration: event.duration,
|
|
138
|
+
fetchDuration: event.fetchDuration,
|
|
139
|
+
timestamp: event.timestamp,
|
|
140
|
+
cacheType: event.cacheType,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const max = PerformanceMonitor.config.maxMetrics ?? 1000
|
|
144
|
+
if (PerformanceMonitor.cacheEntries.length > max) {
|
|
145
|
+
PerformanceMonitor.cacheEntries = PerformanceMonitor.cacheEntries.slice(-max)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Track an error
|
|
151
|
+
*/
|
|
152
|
+
static trackError(error: Error, source: string): void {
|
|
153
|
+
if (!PerformanceMonitor.config.enableErrorTracking) return
|
|
154
|
+
|
|
155
|
+
PerformanceMonitor.errors.push({
|
|
156
|
+
type: error.name || 'Error',
|
|
157
|
+
message: error.message,
|
|
158
|
+
stack: error.stack,
|
|
159
|
+
timestamp: Date.now(),
|
|
160
|
+
source,
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const max = PerformanceMonitor.config.maxMetrics ?? 1000
|
|
164
|
+
if (PerformanceMonitor.errors.length > max) {
|
|
165
|
+
PerformanceMonitor.errors = PerformanceMonitor.errors.slice(-max)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Track page load metrics
|
|
171
|
+
*/
|
|
172
|
+
static trackPageLoad(metrics: Omit<PageLoadEntry, 'timestamp'>): void {
|
|
173
|
+
if (!PerformanceMonitor.config.enablePageLoadTracking) return
|
|
174
|
+
|
|
175
|
+
PerformanceMonitor.pageLoads.push({
|
|
176
|
+
...metrics,
|
|
177
|
+
timestamp: Date.now(),
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const max = PerformanceMonitor.config.maxMetrics ?? 1000
|
|
181
|
+
if (PerformanceMonitor.pageLoads.length > max) {
|
|
182
|
+
PerformanceMonitor.pageLoads = PerformanceMonitor.pageLoads.slice(-max)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get aggregated performance metrics for a time range
|
|
188
|
+
*/
|
|
189
|
+
static getMetrics(timeRange: { start: number; end: number }): PerformanceMetrics {
|
|
190
|
+
const { start, end } = timeRange
|
|
191
|
+
|
|
192
|
+
// Filter entries by time range
|
|
193
|
+
const rangedQueries = PerformanceMonitor.queries.filter(
|
|
194
|
+
(q) => q.timestamp >= start && q.timestamp <= end,
|
|
195
|
+
)
|
|
196
|
+
const rangedCache = PerformanceMonitor.cacheEntries.filter(
|
|
197
|
+
(c) => c.timestamp >= start && c.timestamp <= end,
|
|
198
|
+
)
|
|
199
|
+
const rangedErrors = PerformanceMonitor.errors.filter(
|
|
200
|
+
(e) => e.timestamp >= start && e.timestamp <= end,
|
|
201
|
+
)
|
|
202
|
+
const rangedPageLoads = PerformanceMonitor.pageLoads.filter(
|
|
203
|
+
(p) => p.timestamp >= start && p.timestamp <= end,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
// Aggregate cache metrics
|
|
207
|
+
const hits = rangedCache.filter((c) => c.type === 'hit')
|
|
208
|
+
const misses = rangedCache.filter((c) => c.type === 'miss')
|
|
209
|
+
const totalCacheRequests = hits.length + misses.length
|
|
210
|
+
|
|
211
|
+
const cache: CacheMetrics = {
|
|
212
|
+
totalRequests: totalCacheRequests,
|
|
213
|
+
hits: hits.length,
|
|
214
|
+
misses: misses.length,
|
|
215
|
+
hitRate: totalCacheRequests > 0 ? (hits.length / totalCacheRequests) * 100 : 0,
|
|
216
|
+
avgHitTime: hits.length > 0 ? hits.reduce((s, h) => s + h.duration, 0) / hits.length : 0,
|
|
217
|
+
avgMissTime: misses.length > 0 ? misses.reduce((s, m) => s + (m.fetchDuration ?? m.duration), 0) / misses.length : 0,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Aggregate query metrics
|
|
221
|
+
const queryMap = new Map<string, QueryEntry[]>()
|
|
222
|
+
for (const q of rangedQueries) {
|
|
223
|
+
const existing = queryMap.get(q.key) || []
|
|
224
|
+
existing.push(q)
|
|
225
|
+
queryMap.set(q.key, existing)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const queries: QueryMetrics[] = Array.from(queryMap.entries()).map(
|
|
229
|
+
([key, entries]) => {
|
|
230
|
+
const durations = entries.map((e) => e.duration)
|
|
231
|
+
return {
|
|
232
|
+
queryKey: key,
|
|
233
|
+
executions: entries.length,
|
|
234
|
+
avgDuration: durations.reduce((s, d) => s + d, 0) / durations.length,
|
|
235
|
+
minDuration: Math.min(...durations),
|
|
236
|
+
maxDuration: Math.max(...durations),
|
|
237
|
+
errors: entries.filter((e) => e.error).length,
|
|
238
|
+
lastExecuted: Math.max(...entries.map((e) => e.timestamp)),
|
|
239
|
+
cached: entries.some((e) => e.cached),
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
// Aggregate error metrics
|
|
245
|
+
const errorMap = new Map<string, ErrorEntry[]>()
|
|
246
|
+
for (const e of rangedErrors) {
|
|
247
|
+
const key = `${e.type}:${e.message}`
|
|
248
|
+
const existing = errorMap.get(key) || []
|
|
249
|
+
existing.push(e)
|
|
250
|
+
errorMap.set(key, existing)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const errors: ErrorMetrics[] = Array.from(errorMap.entries()).map(
|
|
254
|
+
([, entries]) => ({
|
|
255
|
+
type: entries[0].type,
|
|
256
|
+
message: entries[0].message,
|
|
257
|
+
stack: entries[0].stack,
|
|
258
|
+
count: entries.length,
|
|
259
|
+
firstSeen: Math.min(...entries.map((e) => e.timestamp)),
|
|
260
|
+
lastSeen: Math.max(...entries.map((e) => e.timestamp)),
|
|
261
|
+
affectedUsers: 1, // No user tracking in client-side monitor
|
|
262
|
+
}),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
// Aggregate page loads
|
|
266
|
+
const pageLoads: PageLoadMetrics[] = rangedPageLoads.map((p) => ({
|
|
267
|
+
route: p.route,
|
|
268
|
+
ttfb: p.ttfb,
|
|
269
|
+
fcp: p.fcp,
|
|
270
|
+
lcp: p.lcp,
|
|
271
|
+
tti: p.tti,
|
|
272
|
+
loadTime: p.loadTime,
|
|
273
|
+
ssr: p.ssr,
|
|
274
|
+
timestamp: p.timestamp,
|
|
275
|
+
}))
|
|
276
|
+
|
|
277
|
+
// Calculate score (0-100)
|
|
278
|
+
let score = 100
|
|
279
|
+
if (cache.hitRate < 80) score -= 20
|
|
280
|
+
if (queries.some((q) => q.avgDuration > 1000)) score -= 15
|
|
281
|
+
if (errors.length > 0) score -= Math.min(errors.length * 5, 30)
|
|
282
|
+
if (pageLoads.some((p) => p.lcp > 2500)) score -= 15
|
|
283
|
+
score = Math.max(0, score)
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
cache,
|
|
287
|
+
queries,
|
|
288
|
+
pageLoads,
|
|
289
|
+
errors,
|
|
290
|
+
score,
|
|
291
|
+
timeRange: { start, end },
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Clear all collected metrics
|
|
297
|
+
*/
|
|
298
|
+
static clear(): void {
|
|
299
|
+
PerformanceMonitor.queries = []
|
|
300
|
+
PerformanceMonitor.cacheEntries = []
|
|
301
|
+
PerformanceMonitor.errors = []
|
|
302
|
+
PerformanceMonitor.pageLoads = []
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Export metrics as JSON
|
|
307
|
+
*/
|
|
308
|
+
static exportMetrics(): string {
|
|
309
|
+
const end = Date.now()
|
|
310
|
+
const start = end - 24 * 60 * 60 * 1000
|
|
311
|
+
return JSON.stringify(PerformanceMonitor.getMetrics({ start, end }), null, 2)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// @geenius-tools/perf-react — src/types.ts
|
|
2
|
+
|
|
3
|
+
export interface CacheMetrics {
|
|
4
|
+
totalRequests: number
|
|
5
|
+
hits: number
|
|
6
|
+
misses: number
|
|
7
|
+
hitRate: number
|
|
8
|
+
avgHitTime: number
|
|
9
|
+
avgMissTime: number
|
|
10
|
+
cacheSize?: number
|
|
11
|
+
evictions?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface QueryMetrics {
|
|
15
|
+
queryKey: string
|
|
16
|
+
executions: number
|
|
17
|
+
avgDuration: number
|
|
18
|
+
minDuration: number
|
|
19
|
+
maxDuration: number
|
|
20
|
+
errors: number
|
|
21
|
+
lastExecuted: number
|
|
22
|
+
cached: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PageLoadMetrics {
|
|
26
|
+
route: string
|
|
27
|
+
ttfb: number
|
|
28
|
+
fcp: number
|
|
29
|
+
lcp: number
|
|
30
|
+
tti: number
|
|
31
|
+
loadTime: number
|
|
32
|
+
ssr: boolean
|
|
33
|
+
timestamp: number
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ErrorMetrics {
|
|
37
|
+
type: string
|
|
38
|
+
message: string
|
|
39
|
+
stack?: string
|
|
40
|
+
count: number
|
|
41
|
+
firstSeen: number
|
|
42
|
+
lastSeen: number
|
|
43
|
+
affectedUsers: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface PerformanceMetrics {
|
|
47
|
+
cache: CacheMetrics
|
|
48
|
+
queries: QueryMetrics[]
|
|
49
|
+
pageLoads: PageLoadMetrics[]
|
|
50
|
+
errors: ErrorMetrics[]
|
|
51
|
+
score: number
|
|
52
|
+
timeRange: { start: number; end: number }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PerformanceConfig {
|
|
56
|
+
enableCacheTracking?: boolean
|
|
57
|
+
enableQueryTracking?: boolean
|
|
58
|
+
enablePageLoadTracking?: boolean
|
|
59
|
+
enableErrorTracking?: boolean
|
|
60
|
+
sampleRate?: number
|
|
61
|
+
maxMetrics?: number
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface CacheHitEvent {
|
|
65
|
+
queryKey: string
|
|
66
|
+
duration: number
|
|
67
|
+
timestamp: number
|
|
68
|
+
cacheType: 'ssr' | 'client' | 'memory'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface CacheMissEvent {
|
|
72
|
+
queryKey: string
|
|
73
|
+
duration: number
|
|
74
|
+
timestamp: number
|
|
75
|
+
cacheType: 'ssr' | 'client' | 'memory'
|
|
76
|
+
fetchDuration: number
|
|
77
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationDir": "./dist",
|
|
13
|
+
"outDir": "./dist",
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true
|
|
17
|
+
},
|
|
18
|
+
"include": [
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules",
|
|
23
|
+
"dist"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: {
|
|
5
|
+
index: 'src/index.ts',
|
|
6
|
+
},
|
|
7
|
+
format: ['esm'],
|
|
8
|
+
dts: true,
|
|
9
|
+
clean: true,
|
|
10
|
+
outDir: 'dist',
|
|
11
|
+
sourcemap: true,
|
|
12
|
+
external: [
|
|
13
|
+
'react',
|
|
14
|
+
'react-dom',
|
|
15
|
+
'lucide-react',
|
|
16
|
+
'@geenius-ui/react',
|
|
17
|
+
'@tanstack/react-query',
|
|
18
|
+
],
|
|
19
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# ✦ @geenius-tools/perf-solidjs\n\n> SolidJS performance monitoring — metrics signals & dashboard\n\n---\n\n## Overview\nBuilt with Steve Jobs-level minimalism and Jony Ive-level craftsmanship, this package is designed to deliver unparalleled developer experience (DX) and rock-solid performance.\n\n## Installation\n\n```bash\npnpm add @geenius-tools/perf-solidjs\n```\n\n## Usage\n\n```typescript\nimport { init } from '@geenius-tools/perf-solidjs';\n\n// Initialize the module with absolute precision\ninit({\n mode: 'premium',\n});\n```\n\n## Architecture\n- **Zero-config**: It just works.\n- **Strictly Typed**: Fully written in TypeScript for flawless IntelliSense.\n- **Framework Agnostic**: seamlessly integrates into the Geenius ecosystem.\n\n---\n\n*Designed by Antigravity HQ*\n
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@geenius-tools/perf-solidjs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "SolidJS performance monitoring \u2014 metrics signals & dashboard",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup",
|
|
22
|
+
"dev": "tsup --watch",
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"solid-js": "^1.9.0",
|
|
27
|
+
"tsup": "^8.5.1",
|
|
28
|
+
"typescript": "~5.9.3"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"solid-js": "^1.8.0 || ^1.9.0"
|
|
32
|
+
},
|
|
33
|
+
"author": "Antigravity HQ",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20.0.0"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @geenius-tools/perf-solidjs — components/PerformanceDashboard.tsx
|
|
3
|
+
*
|
|
4
|
+
* Rich performance dashboard with SSR cache hit rates, query performance,
|
|
5
|
+
* and error metrics. SolidJS variant with reactive primitives.
|
|
6
|
+
*
|
|
7
|
+
* Uses Tailwind CSS utility classes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Show, For } from 'solid-js/web'
|
|
11
|
+
|
|
12
|
+
// ─── Types (re-exported from React variant for consistency) ─────────────────
|
|
13
|
+
|
|
14
|
+
export interface CacheMetrics {
|
|
15
|
+
hitRate: number
|
|
16
|
+
totalRequests: number
|
|
17
|
+
hits: number
|
|
18
|
+
misses: number
|
|
19
|
+
avgHitTime: number
|
|
20
|
+
avgMissTime: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface QueryMetric {
|
|
24
|
+
queryKey: string
|
|
25
|
+
executions: number
|
|
26
|
+
avgDuration: number
|
|
27
|
+
minDuration: number
|
|
28
|
+
maxDuration: number
|
|
29
|
+
errors: number
|
|
30
|
+
cached: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ErrorMetric {
|
|
34
|
+
type: string
|
|
35
|
+
message: string
|
|
36
|
+
count: number
|
|
37
|
+
firstSeen: number
|
|
38
|
+
lastSeen: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PerformanceMetricsData {
|
|
42
|
+
score: number
|
|
43
|
+
cache: CacheMetrics
|
|
44
|
+
queries: QueryMetric[]
|
|
45
|
+
errors: ErrorMetric[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PerformanceDashboardProps {
|
|
49
|
+
metrics: PerformanceMetricsData | null
|
|
50
|
+
isLoading: boolean
|
|
51
|
+
error: Error | null
|
|
52
|
+
onRefresh: () => void
|
|
53
|
+
onClear: () => void
|
|
54
|
+
onExport: () => string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function PerformanceDashboard(props: PerformanceDashboardProps) {
|
|
58
|
+
const downloadMetrics = () => {
|
|
59
|
+
const data = props.onExport()
|
|
60
|
+
const blob = new Blob([data], { type: 'application/json' })
|
|
61
|
+
const url = URL.createObjectURL(blob)
|
|
62
|
+
const a = document.createElement('a')
|
|
63
|
+
a.href = url
|
|
64
|
+
a.download = `performance-metrics-${Date.now()}.json`
|
|
65
|
+
a.click()
|
|
66
|
+
URL.revokeObjectURL(url)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const scoreBadge = () => {
|
|
70
|
+
const s = props.metrics?.score ?? 0
|
|
71
|
+
if (s >= 90) return { emoji: '🟢', label: 'Excellent', color: 'bg-green-100 text-green-800' }
|
|
72
|
+
if (s >= 70) return { emoji: '🟡', label: 'Good', color: 'bg-yellow-100 text-yellow-800' }
|
|
73
|
+
return { emoji: '🔴', label: 'Needs Improvement', color: 'bg-red-100 text-red-800' }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<>
|
|
78
|
+
<Show when={props.isLoading && !props.metrics}>
|
|
79
|
+
<div class="flex justify-center py-8">
|
|
80
|
+
<div class="animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" />
|
|
81
|
+
<span class="ml-3 text-muted-foreground">Loading performance metrics…</span>
|
|
82
|
+
</div>
|
|
83
|
+
</Show>
|
|
84
|
+
|
|
85
|
+
<Show when={props.error}>
|
|
86
|
+
<div class="rounded-xl border border-border bg-card p-6">
|
|
87
|
+
<div class="text-red-600">Error loading metrics: {props.error?.message}</div>
|
|
88
|
+
</div>
|
|
89
|
+
</Show>
|
|
90
|
+
|
|
91
|
+
<Show when={props.metrics}>
|
|
92
|
+
{(m) => (
|
|
93
|
+
<div class="space-y-6">
|
|
94
|
+
<div class="flex items-center justify-between">
|
|
95
|
+
<div>
|
|
96
|
+
<h1 class="text-2xl font-bold">Performance Monitoring</h1>
|
|
97
|
+
<p class="text-muted-foreground mt-1">SSR Cache, Query Performance, and Error Tracking</p>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="flex gap-2">
|
|
100
|
+
<button type="button" onClick={props.onRefresh} class="px-4 py-2 rounded-lg border border-border text-sm font-medium hover:bg-bg-muted transition-colors">Refresh</button>
|
|
101
|
+
<button type="button" onClick={downloadMetrics} class="px-4 py-2 rounded-lg border border-border text-sm font-medium hover:bg-bg-muted transition-colors">Export</button>
|
|
102
|
+
<button type="button" onClick={props.onClear} class="px-4 py-2 rounded-lg border border-border text-sm font-medium hover:bg-bg-muted transition-colors">Clear</button>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="rounded-xl border border-border bg-card p-6">
|
|
107
|
+
<div class="flex items-center justify-between">
|
|
108
|
+
<div>
|
|
109
|
+
<div class="text-sm text-muted-foreground mb-1">Overall Performance Score</div>
|
|
110
|
+
<div class="text-4xl font-bold text-foreground">{m().score}</div>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="text-5xl">{scoreBadge().emoji}</div>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="mt-4">
|
|
115
|
+
<span class={`px-2 py-1 rounded-full text-xs font-medium ${scoreBadge().color}`}>{scoreBadge().label}</span>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div>
|
|
120
|
+
<h2 class="text-xl font-semibold mb-4">SSR Cache Performance</h2>
|
|
121
|
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
122
|
+
<MetricCard label="Cache Hit Rate" value={`${m().cache.hitRate.toFixed(1)}%`} badge={m().cache.hitRate >= 80 ? 'Excellent' : m().cache.hitRate >= 50 ? 'Good' : 'Poor'} badgeColor={m().cache.hitRate >= 80 ? 'bg-green-100 text-green-800' : m().cache.hitRate >= 50 ? 'bg-yellow-100 text-yellow-800' : 'bg-red-100 text-red-800'} />
|
|
123
|
+
<MetricCard label="Total Requests" value={m().cache.totalRequests.toLocaleString()} subtitle={`${m().cache.hits} hits / ${m().cache.misses} misses`} />
|
|
124
|
+
<MetricCard label="Avg Hit Time" value={`${m().cache.avgHitTime.toFixed(0)}`} unit="ms" badge={m().cache.avgHitTime < 50 ? 'Fast' : 'Acceptable'} badgeColor={m().cache.avgHitTime < 50 ? 'bg-green-100 text-green-800' : 'bg-yellow-100 text-yellow-800'} />
|
|
125
|
+
<MetricCard label="Avg Miss Time" value={`${m().cache.avgMissTime.toFixed(0)}`} unit="ms" subtitle={`Time saved: ${(m().cache.avgMissTime - m().cache.avgHitTime).toFixed(0)}ms`} />
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
|
|
129
|
+
<div>
|
|
130
|
+
<h2 class="text-xl font-semibold mb-4">Top Queries by Executions</h2>
|
|
131
|
+
<div class="rounded-xl border border-border bg-card overflow-x-auto">
|
|
132
|
+
<table class="w-full">
|
|
133
|
+
<thead>
|
|
134
|
+
<tr class="border-b border-border">
|
|
135
|
+
<For each={['Query', 'Executions', 'Avg Duration', 'Errors', 'Cached']}>
|
|
136
|
+
{(h) => <th class="text-left p-4 text-sm font-semibold text-muted-foreground">{h}</th>}
|
|
137
|
+
</For>
|
|
138
|
+
</tr>
|
|
139
|
+
</thead>
|
|
140
|
+
<tbody>
|
|
141
|
+
<For each={m().queries.slice(0, 10)}>
|
|
142
|
+
{(query) => (
|
|
143
|
+
<tr class="border-b border-border/50 hover:bg-surface">
|
|
144
|
+
<td class="p-4 text-sm font-mono text-foreground truncate max-w-xs">{query.queryKey}</td>
|
|
145
|
+
<td class="p-4 text-sm text-foreground">{query.executions}</td>
|
|
146
|
+
<td class="p-4 text-sm text-foreground">{query.avgDuration.toFixed(0)}ms <span class="text-xs text-muted-foreground ml-2">({query.minDuration.toFixed(0)}-{query.maxDuration.toFixed(0)}ms)</span></td>
|
|
147
|
+
<td class="p-4">{query.errors > 0 ? <span class="px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">{query.errors}</span> : <span class="text-sm text-muted-foreground/60">0</span>}</td>
|
|
148
|
+
<td class="p-4"><span class={`px-2 py-0.5 rounded-full text-xs font-medium ${query.cached ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-600'}`}>{query.cached ? 'Yes' : 'No'}</span></td>
|
|
149
|
+
</tr>
|
|
150
|
+
)}
|
|
151
|
+
</For>
|
|
152
|
+
<Show when={m().queries.length === 0}>
|
|
153
|
+
<tr><td colSpan={5} class="p-8 text-center text-muted-foreground">No query data available</td></tr>
|
|
154
|
+
</Show>
|
|
155
|
+
</tbody>
|
|
156
|
+
</table>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<Show when={m().errors.length > 0}>
|
|
161
|
+
<div>
|
|
162
|
+
<h2 class="text-xl font-semibold mb-4">Recent Errors</h2>
|
|
163
|
+
<div class="rounded-xl border border-border bg-card divide-y divide-border">
|
|
164
|
+
<For each={m().errors.slice(0, 5)}>
|
|
165
|
+
{(err) => (
|
|
166
|
+
<div class="p-4">
|
|
167
|
+
<div class="flex items-start justify-between">
|
|
168
|
+
<div class="flex-1">
|
|
169
|
+
<div class="flex items-center gap-2 mb-1">
|
|
170
|
+
<span class="px-2 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800">{err.type}</span>
|
|
171
|
+
<span class="text-sm font-semibold text-foreground">{err.message}</span>
|
|
172
|
+
</div>
|
|
173
|
+
<div class="text-xs text-muted-foreground">
|
|
174
|
+
First seen: {new Date(err.firstSeen).toLocaleString()} | Last seen: {new Date(err.lastSeen).toLocaleString()}
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="text-right ml-4">
|
|
178
|
+
<div class="text-lg font-bold text-red-600">{err.count}</div>
|
|
179
|
+
<div class="text-xs text-muted-foreground">occurrences</div>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
)}
|
|
184
|
+
</For>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
</Show>
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
</Show>
|
|
191
|
+
</>
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function MetricCard(props: { label: string; value: string; unit?: string; subtitle?: string; badge?: string; badgeColor?: string }) {
|
|
196
|
+
return (
|
|
197
|
+
<div class="rounded-xl border border-border bg-card p-6">
|
|
198
|
+
<div class="text-sm text-muted-foreground mb-1">{props.label}</div>
|
|
199
|
+
<div class="text-3xl font-bold text-foreground">
|
|
200
|
+
{props.value}
|
|
201
|
+
<Show when={props.unit}><span class="text-lg text-muted-foreground">{props.unit}</span></Show>
|
|
202
|
+
</div>
|
|
203
|
+
<Show when={props.badge}><div class="mt-2"><span class={`px-2 py-0.5 rounded-full text-xs font-medium ${props.badgeColor}`}>{props.badge}</span></div></Show>
|
|
204
|
+
<Show when={props.subtitle}><div class="mt-2 text-sm text-muted-foreground">{props.subtitle}</div></Show>
|
|
205
|
+
</div>
|
|
206
|
+
)
|
|
207
|
+
}
|