@sc4rfurryx/proteusjs 1.0.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/API.md +438 -0
- package/FEATURES.md +286 -0
- package/LICENSE +21 -0
- package/README.md +645 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/proteus.cjs.js +16014 -0
- package/dist/proteus.cjs.js.map +1 -0
- package/dist/proteus.d.ts +3018 -0
- package/dist/proteus.esm.js +16005 -0
- package/dist/proteus.esm.js.map +1 -0
- package/dist/proteus.esm.min.js +8 -0
- package/dist/proteus.esm.min.js.map +1 -0
- package/dist/proteus.js +16020 -0
- package/dist/proteus.js.map +1 -0
- package/dist/proteus.min.js +8 -0
- package/dist/proteus.min.js.map +1 -0
- package/package.json +98 -0
- package/src/__tests__/mvp-integration.test.ts +518 -0
- package/src/accessibility/AccessibilityEngine.ts +2106 -0
- package/src/accessibility/ScreenReaderSupport.ts +444 -0
- package/src/accessibility/__tests__/ScreenReaderSupport.test.ts +435 -0
- package/src/animations/FLIPAnimationSystem.ts +491 -0
- package/src/compatibility/BrowserCompatibility.ts +1076 -0
- package/src/containers/BreakpointSystem.ts +347 -0
- package/src/containers/ContainerBreakpoints.ts +726 -0
- package/src/containers/ContainerManager.ts +370 -0
- package/src/containers/ContainerUnits.ts +336 -0
- package/src/containers/ContextIsolation.ts +394 -0
- package/src/containers/ElementQueries.ts +411 -0
- package/src/containers/SmartContainer.ts +536 -0
- package/src/containers/SmartContainers.ts +376 -0
- package/src/containers/__tests__/ContainerBreakpoints.test.ts +411 -0
- package/src/containers/__tests__/SmartContainers.test.ts +281 -0
- package/src/content/ResponsiveImages.ts +570 -0
- package/src/core/EventSystem.ts +147 -0
- package/src/core/MemoryManager.ts +321 -0
- package/src/core/PerformanceMonitor.ts +238 -0
- package/src/core/PluginSystem.ts +275 -0
- package/src/core/ProteusJS.test.ts +164 -0
- package/src/core/ProteusJS.ts +962 -0
- package/src/developer/PerformanceProfiler.ts +567 -0
- package/src/developer/VisualDebuggingTools.ts +656 -0
- package/src/developer/ZeroConfigSystem.ts +593 -0
- package/src/index.ts +35 -0
- package/src/integration.test.ts +227 -0
- package/src/layout/AdaptiveGrid.ts +429 -0
- package/src/layout/ContentReordering.ts +532 -0
- package/src/layout/FlexboxEnhancer.ts +406 -0
- package/src/layout/FlowLayout.ts +545 -0
- package/src/layout/SpacingSystem.ts +512 -0
- package/src/observers/IntersectionObserverPolyfill.ts +289 -0
- package/src/observers/ObserverManager.ts +299 -0
- package/src/observers/ResizeObserverPolyfill.ts +179 -0
- package/src/performance/BatchDOMOperations.ts +519 -0
- package/src/performance/CSSOptimizationEngine.ts +646 -0
- package/src/performance/CacheOptimizationSystem.ts +601 -0
- package/src/performance/EfficientEventHandler.ts +740 -0
- package/src/performance/LazyEvaluationSystem.ts +532 -0
- package/src/performance/MemoryManagementSystem.ts +497 -0
- package/src/performance/PerformanceMonitor.ts +931 -0
- package/src/performance/__tests__/BatchDOMOperations.test.ts +309 -0
- package/src/performance/__tests__/EfficientEventHandler.test.ts +268 -0
- package/src/performance/__tests__/PerformanceMonitor.test.ts +422 -0
- package/src/polyfills/BrowserPolyfills.ts +586 -0
- package/src/polyfills/__tests__/BrowserPolyfills.test.ts +328 -0
- package/src/test/setup.ts +115 -0
- package/src/theming/SmartThemeSystem.ts +591 -0
- package/src/types/index.ts +134 -0
- package/src/typography/ClampScaling.ts +356 -0
- package/src/typography/FluidTypography.ts +759 -0
- package/src/typography/LineHeightOptimization.ts +430 -0
- package/src/typography/LineHeightOptimizer.ts +326 -0
- package/src/typography/TextFitting.ts +355 -0
- package/src/typography/TypographicScale.ts +428 -0
- package/src/typography/VerticalRhythm.ts +369 -0
- package/src/typography/__tests__/FluidTypography.test.ts +432 -0
- package/src/typography/__tests__/LineHeightOptimization.test.ts +436 -0
- package/src/utils/Logger.ts +173 -0
- package/src/utils/debounce.ts +259 -0
- package/src/utils/performance.ts +371 -0
- package/src/utils/support.ts +106 -0
- package/src/utils/version.ts +24 -0
@@ -0,0 +1,259 @@
|
|
1
|
+
/**
|
2
|
+
* Debounce and throttle utilities for ProteusJS
|
3
|
+
* Optimizes performance by controlling function execution frequency
|
4
|
+
*/
|
5
|
+
|
6
|
+
export interface DebounceOptions {
|
7
|
+
leading?: boolean;
|
8
|
+
trailing?: boolean;
|
9
|
+
maxWait?: number;
|
10
|
+
}
|
11
|
+
|
12
|
+
export interface ThrottleOptions {
|
13
|
+
leading?: boolean;
|
14
|
+
trailing?: boolean;
|
15
|
+
}
|
16
|
+
|
17
|
+
/**
|
18
|
+
* Debounce function execution
|
19
|
+
*/
|
20
|
+
export function debounce<T extends (...args: unknown[]) => unknown>(
|
21
|
+
func: T,
|
22
|
+
wait: number,
|
23
|
+
options: DebounceOptions = {}
|
24
|
+
): T & { cancel: () => void; flush: () => ReturnType<T> | undefined } {
|
25
|
+
const { leading = false, trailing = true, maxWait } = options;
|
26
|
+
|
27
|
+
let timeoutId: number | null = null;
|
28
|
+
let maxTimeoutId: number | null = null;
|
29
|
+
let lastCallTime: number | undefined;
|
30
|
+
let lastInvokeTime = 0;
|
31
|
+
let lastArgs: Parameters<T> | undefined;
|
32
|
+
let lastThis: unknown;
|
33
|
+
let result: ReturnType<T> | undefined;
|
34
|
+
|
35
|
+
function invokeFunc(time: number): ReturnType<T> {
|
36
|
+
const args = lastArgs!;
|
37
|
+
const thisArg = lastThis;
|
38
|
+
|
39
|
+
lastArgs = undefined;
|
40
|
+
lastThis = undefined;
|
41
|
+
lastInvokeTime = time;
|
42
|
+
result = func.apply(thisArg, args) as ReturnType<T>;
|
43
|
+
return result!;
|
44
|
+
}
|
45
|
+
|
46
|
+
function leadingEdge(time: number): ReturnType<T> {
|
47
|
+
lastInvokeTime = time;
|
48
|
+
timeoutId = window.setTimeout(timerExpired, wait);
|
49
|
+
return leading ? invokeFunc(time) : result!;
|
50
|
+
}
|
51
|
+
|
52
|
+
function remainingWait(time: number): number {
|
53
|
+
const timeSinceLastCall = time - lastCallTime!;
|
54
|
+
const timeSinceLastInvoke = time - lastInvokeTime;
|
55
|
+
const timeWaiting = wait - timeSinceLastCall;
|
56
|
+
|
57
|
+
return maxWait !== undefined
|
58
|
+
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
|
59
|
+
: timeWaiting;
|
60
|
+
}
|
61
|
+
|
62
|
+
function shouldInvoke(time: number): boolean {
|
63
|
+
const timeSinceLastCall = time - lastCallTime!;
|
64
|
+
const timeSinceLastInvoke = time - lastInvokeTime;
|
65
|
+
|
66
|
+
return (
|
67
|
+
lastCallTime === undefined ||
|
68
|
+
timeSinceLastCall >= wait ||
|
69
|
+
timeSinceLastCall < 0 ||
|
70
|
+
(maxWait !== undefined && timeSinceLastInvoke >= maxWait)
|
71
|
+
);
|
72
|
+
}
|
73
|
+
|
74
|
+
function timerExpired(): ReturnType<T> | undefined {
|
75
|
+
const time = Date.now();
|
76
|
+
if (shouldInvoke(time)) {
|
77
|
+
return trailingEdge(time);
|
78
|
+
}
|
79
|
+
timeoutId = window.setTimeout(timerExpired, remainingWait(time));
|
80
|
+
return undefined;
|
81
|
+
}
|
82
|
+
|
83
|
+
function trailingEdge(time: number): ReturnType<T> | undefined {
|
84
|
+
timeoutId = null;
|
85
|
+
|
86
|
+
if (trailing && lastArgs) {
|
87
|
+
return invokeFunc(time);
|
88
|
+
}
|
89
|
+
lastArgs = undefined;
|
90
|
+
lastThis = undefined;
|
91
|
+
return result;
|
92
|
+
}
|
93
|
+
|
94
|
+
function cancel(): void {
|
95
|
+
if (timeoutId !== null) {
|
96
|
+
clearTimeout(timeoutId);
|
97
|
+
timeoutId = null;
|
98
|
+
}
|
99
|
+
if (maxTimeoutId !== null) {
|
100
|
+
clearTimeout(maxTimeoutId);
|
101
|
+
maxTimeoutId = null;
|
102
|
+
}
|
103
|
+
lastInvokeTime = 0;
|
104
|
+
lastArgs = undefined;
|
105
|
+
lastCallTime = undefined;
|
106
|
+
lastThis = undefined;
|
107
|
+
}
|
108
|
+
|
109
|
+
function flush(): ReturnType<T> | undefined {
|
110
|
+
return timeoutId === null ? result : trailingEdge(Date.now());
|
111
|
+
}
|
112
|
+
|
113
|
+
function debounced(this: unknown, ...args: Parameters<T>): ReturnType<T> | undefined {
|
114
|
+
const time = Date.now();
|
115
|
+
const isInvoking = shouldInvoke(time);
|
116
|
+
|
117
|
+
lastArgs = args;
|
118
|
+
lastThis = this;
|
119
|
+
lastCallTime = time;
|
120
|
+
|
121
|
+
if (isInvoking) {
|
122
|
+
if (timeoutId === null) {
|
123
|
+
return leadingEdge(lastCallTime);
|
124
|
+
}
|
125
|
+
if (maxWait !== undefined) {
|
126
|
+
timeoutId = window.setTimeout(timerExpired, wait);
|
127
|
+
return invokeFunc(lastCallTime);
|
128
|
+
}
|
129
|
+
}
|
130
|
+
if (timeoutId === null) {
|
131
|
+
timeoutId = window.setTimeout(timerExpired, wait);
|
132
|
+
}
|
133
|
+
return result;
|
134
|
+
}
|
135
|
+
|
136
|
+
debounced.cancel = cancel;
|
137
|
+
debounced.flush = flush;
|
138
|
+
|
139
|
+
return debounced as T & { cancel: () => void; flush: () => ReturnType<T> | undefined };
|
140
|
+
}
|
141
|
+
|
142
|
+
/**
|
143
|
+
* Throttle function execution
|
144
|
+
*/
|
145
|
+
export function throttle<T extends (...args: unknown[]) => unknown>(
|
146
|
+
func: T,
|
147
|
+
wait: number,
|
148
|
+
options: ThrottleOptions = {}
|
149
|
+
): T & { cancel: () => void; flush: () => ReturnType<T> | undefined } {
|
150
|
+
const { leading = true, trailing = true } = options;
|
151
|
+
return debounce(func, wait, { leading, trailing, maxWait: wait });
|
152
|
+
}
|
153
|
+
|
154
|
+
/**
|
155
|
+
* Request animation frame based throttle
|
156
|
+
*/
|
157
|
+
export function rafThrottle<T extends (...args: unknown[]) => unknown>(
|
158
|
+
func: T
|
159
|
+
): T & { cancel: () => void } {
|
160
|
+
let rafId: number | null = null;
|
161
|
+
let lastArgs: Parameters<T> | undefined;
|
162
|
+
let lastThis: unknown;
|
163
|
+
|
164
|
+
function throttled(this: unknown, ...args: Parameters<T>): void {
|
165
|
+
lastArgs = args;
|
166
|
+
lastThis = this;
|
167
|
+
|
168
|
+
if (rafId === null) {
|
169
|
+
rafId = requestAnimationFrame(() => {
|
170
|
+
rafId = null;
|
171
|
+
if (lastArgs) {
|
172
|
+
func.apply(lastThis, lastArgs);
|
173
|
+
lastArgs = undefined;
|
174
|
+
lastThis = undefined;
|
175
|
+
}
|
176
|
+
});
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
function cancel(): void {
|
181
|
+
if (rafId !== null) {
|
182
|
+
cancelAnimationFrame(rafId);
|
183
|
+
rafId = null;
|
184
|
+
}
|
185
|
+
lastArgs = undefined;
|
186
|
+
lastThis = undefined;
|
187
|
+
}
|
188
|
+
|
189
|
+
throttled.cancel = cancel;
|
190
|
+
|
191
|
+
return throttled as T & { cancel: () => void };
|
192
|
+
}
|
193
|
+
|
194
|
+
/**
|
195
|
+
* Idle callback based debounce
|
196
|
+
*/
|
197
|
+
export function idleDebounce<T extends (...args: unknown[]) => unknown>(
|
198
|
+
func: T,
|
199
|
+
timeout: number = 5000
|
200
|
+
): T & { cancel: () => void } {
|
201
|
+
let idleId: number | null = null;
|
202
|
+
let timeoutId: number | null = null;
|
203
|
+
let lastArgs: Parameters<T> | undefined;
|
204
|
+
let lastThis: unknown;
|
205
|
+
|
206
|
+
function debounced(this: unknown, ...args: Parameters<T>): void {
|
207
|
+
lastArgs = args;
|
208
|
+
lastThis = this;
|
209
|
+
|
210
|
+
// Cancel previous calls
|
211
|
+
if (idleId !== null) {
|
212
|
+
cancelIdleCallback(idleId);
|
213
|
+
idleId = null;
|
214
|
+
}
|
215
|
+
if (timeoutId !== null) {
|
216
|
+
clearTimeout(timeoutId);
|
217
|
+
timeoutId = null;
|
218
|
+
}
|
219
|
+
|
220
|
+
// Try to use idle callback
|
221
|
+
if (typeof requestIdleCallback !== 'undefined') {
|
222
|
+
idleId = requestIdleCallback(() => {
|
223
|
+
idleId = null;
|
224
|
+
if (lastArgs) {
|
225
|
+
func.apply(lastThis, lastArgs);
|
226
|
+
lastArgs = undefined;
|
227
|
+
lastThis = undefined;
|
228
|
+
}
|
229
|
+
}, { timeout });
|
230
|
+
} else {
|
231
|
+
// Fallback to setTimeout
|
232
|
+
timeoutId = window.setTimeout(() => {
|
233
|
+
timeoutId = null;
|
234
|
+
if (lastArgs) {
|
235
|
+
func.apply(lastThis, lastArgs);
|
236
|
+
lastArgs = undefined;
|
237
|
+
lastThis = undefined;
|
238
|
+
}
|
239
|
+
}, 16); // ~60fps fallback
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
function cancel(): void {
|
244
|
+
if (idleId !== null) {
|
245
|
+
cancelIdleCallback(idleId);
|
246
|
+
idleId = null;
|
247
|
+
}
|
248
|
+
if (timeoutId !== null) {
|
249
|
+
clearTimeout(timeoutId);
|
250
|
+
timeoutId = null;
|
251
|
+
}
|
252
|
+
lastArgs = undefined;
|
253
|
+
lastThis = undefined;
|
254
|
+
}
|
255
|
+
|
256
|
+
debounced.cancel = cancel;
|
257
|
+
|
258
|
+
return debounced as T & { cancel: () => void };
|
259
|
+
}
|
@@ -0,0 +1,371 @@
|
|
1
|
+
/**
|
2
|
+
* Performance utilities for ProteusJS
|
3
|
+
* Provides timing, measurement, and optimization tools
|
4
|
+
*/
|
5
|
+
|
6
|
+
export interface TimingMark {
|
7
|
+
name: string;
|
8
|
+
startTime: number;
|
9
|
+
endTime?: number;
|
10
|
+
duration?: number;
|
11
|
+
metadata?: Record<string, unknown>;
|
12
|
+
}
|
13
|
+
|
14
|
+
export interface PerformanceBudget {
|
15
|
+
responseTime: number; // Maximum response time in ms
|
16
|
+
frameRate: number; // Minimum frame rate
|
17
|
+
memoryUsage: number; // Maximum memory usage in MB
|
18
|
+
}
|
19
|
+
|
20
|
+
export class PerformanceTracker {
|
21
|
+
private marks: Map<string, TimingMark> = new Map();
|
22
|
+
private measurements: TimingMark[] = [];
|
23
|
+
private budget: PerformanceBudget;
|
24
|
+
private warningThreshold: number = 0.8; // 80% of budget
|
25
|
+
|
26
|
+
constructor(budget?: Partial<PerformanceBudget>) {
|
27
|
+
this.budget = {
|
28
|
+
responseTime: 60, // 60ms default
|
29
|
+
frameRate: 60, // 60fps default
|
30
|
+
memoryUsage: 100, // 100MB default
|
31
|
+
...budget
|
32
|
+
};
|
33
|
+
}
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Start timing a performance mark
|
37
|
+
*/
|
38
|
+
public mark(name: string, metadata?: Record<string, unknown>): void {
|
39
|
+
const mark: TimingMark = {
|
40
|
+
name,
|
41
|
+
startTime: performance.now(),
|
42
|
+
...(metadata && { metadata })
|
43
|
+
};
|
44
|
+
|
45
|
+
this.marks.set(name, mark);
|
46
|
+
|
47
|
+
// Use Performance API if available
|
48
|
+
if (typeof performance.mark === 'function') {
|
49
|
+
performance.mark(`proteus-${name}-start`);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
/**
|
54
|
+
* End timing a performance mark
|
55
|
+
*/
|
56
|
+
public measure(name: string): TimingMark | null {
|
57
|
+
const mark = this.marks.get(name);
|
58
|
+
if (!mark) {
|
59
|
+
console.warn(`ProteusJS: Performance mark "${name}" not found`);
|
60
|
+
return null;
|
61
|
+
}
|
62
|
+
|
63
|
+
const endTime = performance.now();
|
64
|
+
const duration = endTime - mark.startTime;
|
65
|
+
|
66
|
+
const measurement: TimingMark = {
|
67
|
+
...mark,
|
68
|
+
endTime,
|
69
|
+
duration
|
70
|
+
};
|
71
|
+
|
72
|
+
this.measurements.push(measurement);
|
73
|
+
this.marks.delete(name);
|
74
|
+
|
75
|
+
// Use Performance API if available
|
76
|
+
if (typeof performance.mark === 'function' && typeof performance.measure === 'function') {
|
77
|
+
performance.mark(`proteus-${name}-end`);
|
78
|
+
performance.measure(`proteus-${name}`, `proteus-${name}-start`, `proteus-${name}-end`);
|
79
|
+
}
|
80
|
+
|
81
|
+
// Check against budget
|
82
|
+
this.checkBudget(measurement);
|
83
|
+
|
84
|
+
return measurement;
|
85
|
+
}
|
86
|
+
|
87
|
+
/**
|
88
|
+
* Get all measurements
|
89
|
+
*/
|
90
|
+
public getMeasurements(): TimingMark[] {
|
91
|
+
return [...this.measurements];
|
92
|
+
}
|
93
|
+
|
94
|
+
/**
|
95
|
+
* Get measurements by name pattern
|
96
|
+
*/
|
97
|
+
public getMeasurementsByPattern(pattern: RegExp): TimingMark[] {
|
98
|
+
return this.measurements.filter(m => pattern.test(m.name));
|
99
|
+
}
|
100
|
+
|
101
|
+
/**
|
102
|
+
* Get average duration for measurements with the same name
|
103
|
+
*/
|
104
|
+
public getAverageDuration(name: string): number {
|
105
|
+
const matching = this.measurements.filter(m => m.name === name && m.duration !== undefined);
|
106
|
+
if (matching.length === 0) return 0;
|
107
|
+
|
108
|
+
const total = matching.reduce((sum, m) => sum + (m.duration || 0), 0);
|
109
|
+
return total / matching.length;
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* Get performance statistics
|
114
|
+
*/
|
115
|
+
public getStats(): Record<string, unknown> {
|
116
|
+
const stats: Record<string, unknown> = {
|
117
|
+
totalMeasurements: this.measurements.length,
|
118
|
+
activeMeasurements: this.marks.size,
|
119
|
+
budget: this.budget,
|
120
|
+
violations: this.getBudgetViolations()
|
121
|
+
};
|
122
|
+
|
123
|
+
// Group by name
|
124
|
+
const byName: Record<string, { count: number; avgDuration: number; maxDuration: number }> = {};
|
125
|
+
|
126
|
+
this.measurements.forEach(m => {
|
127
|
+
if (m.duration === undefined) return;
|
128
|
+
|
129
|
+
if (!byName[m.name]) {
|
130
|
+
byName[m.name] = { count: 0, avgDuration: 0, maxDuration: 0 };
|
131
|
+
}
|
132
|
+
|
133
|
+
byName[m.name]!.count++;
|
134
|
+
byName[m.name]!.maxDuration = Math.max(byName[m.name]!.maxDuration, m.duration);
|
135
|
+
});
|
136
|
+
|
137
|
+
// Calculate averages
|
138
|
+
Object.keys(byName).forEach(name => {
|
139
|
+
byName[name]!.avgDuration = this.getAverageDuration(name);
|
140
|
+
});
|
141
|
+
|
142
|
+
stats['byName'] = byName;
|
143
|
+
return stats;
|
144
|
+
}
|
145
|
+
|
146
|
+
/**
|
147
|
+
* Clear all measurements
|
148
|
+
*/
|
149
|
+
public clear(): void {
|
150
|
+
this.measurements.length = 0;
|
151
|
+
this.marks.clear();
|
152
|
+
}
|
153
|
+
|
154
|
+
/**
|
155
|
+
* Update performance budget
|
156
|
+
*/
|
157
|
+
public updateBudget(budget: Partial<PerformanceBudget>): void {
|
158
|
+
this.budget = { ...this.budget, ...budget };
|
159
|
+
}
|
160
|
+
|
161
|
+
/**
|
162
|
+
* Check measurement against budget
|
163
|
+
*/
|
164
|
+
private checkBudget(measurement: TimingMark): void {
|
165
|
+
if (measurement.duration === undefined) return;
|
166
|
+
|
167
|
+
const warningThreshold = this.budget.responseTime * this.warningThreshold;
|
168
|
+
const errorThreshold = this.budget.responseTime;
|
169
|
+
|
170
|
+
if (measurement.duration > errorThreshold) {
|
171
|
+
console.error(
|
172
|
+
`ProteusJS: Performance budget exceeded for "${measurement.name}": ${measurement.duration.toFixed(2)}ms > ${errorThreshold}ms`
|
173
|
+
);
|
174
|
+
} else if (measurement.duration > warningThreshold) {
|
175
|
+
console.warn(
|
176
|
+
`ProteusJS: Performance warning for "${measurement.name}": ${measurement.duration.toFixed(2)}ms > ${warningThreshold.toFixed(2)}ms`
|
177
|
+
);
|
178
|
+
}
|
179
|
+
}
|
180
|
+
|
181
|
+
/**
|
182
|
+
* Get budget violations
|
183
|
+
*/
|
184
|
+
private getBudgetViolations(): TimingMark[] {
|
185
|
+
return this.measurements.filter(m =>
|
186
|
+
m.duration !== undefined && m.duration > this.budget.responseTime
|
187
|
+
);
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
/**
|
192
|
+
* Global performance tracker instance
|
193
|
+
*/
|
194
|
+
export const performanceTracker = new PerformanceTracker();
|
195
|
+
|
196
|
+
/**
|
197
|
+
* Decorator for automatic performance tracking
|
198
|
+
*/
|
199
|
+
export function trackPerformance(name?: string): (target: unknown, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor {
|
200
|
+
return function (target: unknown, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor {
|
201
|
+
const originalMethod = descriptor.value;
|
202
|
+
const trackingName = name || `${(target as { constructor: { name: string } }).constructor.name}.${propertyKey}`;
|
203
|
+
|
204
|
+
descriptor.value = function (...args: unknown[]): unknown {
|
205
|
+
performanceTracker.mark(trackingName);
|
206
|
+
|
207
|
+
try {
|
208
|
+
const result = originalMethod.apply(this, args);
|
209
|
+
|
210
|
+
// Handle async methods
|
211
|
+
if (result && typeof result.then === 'function') {
|
212
|
+
return result.finally(() => {
|
213
|
+
performanceTracker.measure(trackingName);
|
214
|
+
});
|
215
|
+
}
|
216
|
+
|
217
|
+
performanceTracker.measure(trackingName);
|
218
|
+
return result;
|
219
|
+
} catch (error) {
|
220
|
+
performanceTracker.measure(trackingName);
|
221
|
+
throw error;
|
222
|
+
}
|
223
|
+
};
|
224
|
+
|
225
|
+
return descriptor;
|
226
|
+
};
|
227
|
+
}
|
228
|
+
|
229
|
+
/**
|
230
|
+
* Measure function execution time
|
231
|
+
*/
|
232
|
+
export function measureTime<T extends (...args: unknown[]) => unknown>(
|
233
|
+
func: T,
|
234
|
+
name?: string
|
235
|
+
): T {
|
236
|
+
const measureName = name || func.name || 'anonymous';
|
237
|
+
|
238
|
+
return ((...args: unknown[]) => {
|
239
|
+
performanceTracker.mark(measureName);
|
240
|
+
|
241
|
+
try {
|
242
|
+
const result = func(...args);
|
243
|
+
|
244
|
+
// Handle async functions
|
245
|
+
if (result && typeof (result as { then?: unknown }).then === 'function') {
|
246
|
+
return (result as Promise<unknown>).finally(() => {
|
247
|
+
performanceTracker.measure(measureName);
|
248
|
+
});
|
249
|
+
}
|
250
|
+
|
251
|
+
performanceTracker.measure(measureName);
|
252
|
+
return result;
|
253
|
+
} catch (error) {
|
254
|
+
performanceTracker.measure(measureName);
|
255
|
+
throw error;
|
256
|
+
}
|
257
|
+
}) as T;
|
258
|
+
}
|
259
|
+
|
260
|
+
/**
|
261
|
+
* Create a performance-aware wrapper for frequently called functions
|
262
|
+
*/
|
263
|
+
export function createPerformanceWrapper<T extends (...args: unknown[]) => unknown>(
|
264
|
+
func: T,
|
265
|
+
options: {
|
266
|
+
name?: string;
|
267
|
+
sampleRate?: number; // 0-1, percentage of calls to measure
|
268
|
+
budget?: number; // ms
|
269
|
+
} = {}
|
270
|
+
): T {
|
271
|
+
const { name = func.name || 'wrapped', sampleRate = 0.1, budget = 16 } = options;
|
272
|
+
let callCount = 0;
|
273
|
+
let totalTime = 0;
|
274
|
+
let maxTime = 0;
|
275
|
+
|
276
|
+
return ((...args: unknown[]) => {
|
277
|
+
callCount++;
|
278
|
+
const shouldMeasure = Math.random() < sampleRate;
|
279
|
+
|
280
|
+
if (shouldMeasure) {
|
281
|
+
const startTime = performance.now();
|
282
|
+
|
283
|
+
try {
|
284
|
+
const result = func(...args);
|
285
|
+
|
286
|
+
const endTime = performance.now();
|
287
|
+
const duration = endTime - startTime;
|
288
|
+
|
289
|
+
totalTime += duration;
|
290
|
+
maxTime = Math.max(maxTime, duration);
|
291
|
+
|
292
|
+
if (duration > budget) {
|
293
|
+
console.warn(
|
294
|
+
`ProteusJS: Performance budget exceeded in ${name}: ${duration.toFixed(2)}ms > ${budget}ms`
|
295
|
+
);
|
296
|
+
}
|
297
|
+
|
298
|
+
return result;
|
299
|
+
} catch (error) {
|
300
|
+
const endTime = performance.now();
|
301
|
+
const duration = endTime - startTime;
|
302
|
+
totalTime += duration;
|
303
|
+
maxTime = Math.max(maxTime, duration);
|
304
|
+
throw error;
|
305
|
+
}
|
306
|
+
} else {
|
307
|
+
return func(...args);
|
308
|
+
}
|
309
|
+
}) as T;
|
310
|
+
}
|
311
|
+
|
312
|
+
/**
|
313
|
+
* Get browser performance information
|
314
|
+
*/
|
315
|
+
export function getBrowserPerformanceInfo(): object {
|
316
|
+
const info: any = {
|
317
|
+
timing: {},
|
318
|
+
memory: {},
|
319
|
+
navigation: {}
|
320
|
+
};
|
321
|
+
|
322
|
+
// Navigation timing (using modern API)
|
323
|
+
try {
|
324
|
+
const navigationEntries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];
|
325
|
+
if (navigationEntries.length > 0) {
|
326
|
+
const nav = navigationEntries[0]!;
|
327
|
+
info.timing = {
|
328
|
+
domContentLoaded: nav.domContentLoadedEventEnd - nav.domContentLoadedEventStart,
|
329
|
+
loadComplete: nav.loadEventEnd - nav.loadEventStart,
|
330
|
+
domInteractive: nav.domInteractive - nav.fetchStart
|
331
|
+
};
|
332
|
+
}
|
333
|
+
} catch (error) {
|
334
|
+
// Modern browsers should support performance.getEntriesByType
|
335
|
+
// If not available, we'll just skip the timing information
|
336
|
+
console.warn('ProteusJS: Performance timing API not available', error);
|
337
|
+
}
|
338
|
+
|
339
|
+
// Memory info
|
340
|
+
if ('memory' in performance) {
|
341
|
+
const memory = (performance as any).memory;
|
342
|
+
info.memory = {
|
343
|
+
usedJSHeapSize: memory.usedJSHeapSize,
|
344
|
+
totalJSHeapSize: memory.totalJSHeapSize,
|
345
|
+
jsHeapSizeLimit: memory.jsHeapSizeLimit
|
346
|
+
};
|
347
|
+
}
|
348
|
+
|
349
|
+
// Navigation info (using modern API)
|
350
|
+
try {
|
351
|
+
const navigationEntries = performance.getEntriesByType('navigation') as PerformanceNavigationTiming[];
|
352
|
+
if (navigationEntries.length > 0) {
|
353
|
+
const nav = navigationEntries[0]!;
|
354
|
+
info.navigation = {
|
355
|
+
type: nav.type === 'navigate' ? 0 : nav.type === 'reload' ? 1 : nav.type === 'back_forward' ? 2 : 255,
|
356
|
+
redirectCount: nav.redirectCount || 0
|
357
|
+
};
|
358
|
+
}
|
359
|
+
} catch (error) {
|
360
|
+
// Fallback for older browsers (deprecated APIs)
|
361
|
+
const perfAny = performance as any;
|
362
|
+
if (perfAny.navigation) {
|
363
|
+
info.navigation = {
|
364
|
+
type: perfAny.navigation.type,
|
365
|
+
redirectCount: perfAny.navigation.redirectCount
|
366
|
+
};
|
367
|
+
}
|
368
|
+
}
|
369
|
+
|
370
|
+
return info;
|
371
|
+
}
|