@coherent.js/devtools 1.0.0-beta.2
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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/index.js +2117 -0
- package/dist/index.js.map +7 -0
- package/dist/inspector.js +397 -0
- package/dist/inspector.js.map +7 -0
- package/dist/logger.js +469 -0
- package/dist/logger.js.map +7 -0
- package/dist/profiler.js +508 -0
- package/dist/profiler.js.map +7 -0
- package/package.json +43 -0
- package/types/index.d.ts +214 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2117 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/inspector.js
|
|
9
|
+
var ComponentInspector = class {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
this.options = {
|
|
12
|
+
trackHistory: true,
|
|
13
|
+
maxHistory: 100,
|
|
14
|
+
verbose: false,
|
|
15
|
+
...options
|
|
16
|
+
};
|
|
17
|
+
this.components = /* @__PURE__ */ new Map();
|
|
18
|
+
this.history = [];
|
|
19
|
+
this.inspectionCount = 0;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Inspect a component
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} component - Component to inspect
|
|
25
|
+
* @param {Object} [metadata] - Additional metadata
|
|
26
|
+
* @returns {Object} Inspection result
|
|
27
|
+
*/
|
|
28
|
+
inspect(component, metadata = {}) {
|
|
29
|
+
this.inspectionCount++;
|
|
30
|
+
const startTime = performance.now();
|
|
31
|
+
const analysis = this.analyzeComponent(component);
|
|
32
|
+
const tree = this.buildComponentTree(component);
|
|
33
|
+
const stats = this.calculateStats(component);
|
|
34
|
+
const endTime = performance.now();
|
|
35
|
+
const inspection = {
|
|
36
|
+
id: this.generateId(),
|
|
37
|
+
timestamp: Date.now(),
|
|
38
|
+
inspectionTime: endTime - startTime,
|
|
39
|
+
component,
|
|
40
|
+
metadata,
|
|
41
|
+
// Flatten analysis results to top level for easier access
|
|
42
|
+
type: analysis.type,
|
|
43
|
+
structure: component,
|
|
44
|
+
props: this.extractProps(component),
|
|
45
|
+
depth: stats.depth || 0,
|
|
46
|
+
// Use stats.depth, not tree.depth
|
|
47
|
+
childCount: stats.elementCount || 0,
|
|
48
|
+
complexity: stats.complexity || 0,
|
|
49
|
+
nodeCount: stats.nodeCount || 0,
|
|
50
|
+
// Keep nested data for detailed inspection
|
|
51
|
+
analysis,
|
|
52
|
+
tree,
|
|
53
|
+
stats,
|
|
54
|
+
valid: analysis.valid,
|
|
55
|
+
issues: analysis.issues || [],
|
|
56
|
+
errors: analysis.issues || [],
|
|
57
|
+
// Alias for compatibility
|
|
58
|
+
warnings: analysis.warnings || []
|
|
59
|
+
};
|
|
60
|
+
if (this.options.trackHistory) {
|
|
61
|
+
this.history.push(inspection);
|
|
62
|
+
if (this.history.length > this.options.maxHistory) {
|
|
63
|
+
this.history.shift();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
this.components.set(inspection.id, inspection);
|
|
67
|
+
if (this.options.verbose) {
|
|
68
|
+
console.log("[Inspector] Component inspected:", inspection.id);
|
|
69
|
+
}
|
|
70
|
+
return inspection;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extract props from component
|
|
74
|
+
*/
|
|
75
|
+
extractProps(component) {
|
|
76
|
+
const props = [];
|
|
77
|
+
if (!component || typeof component !== "object") {
|
|
78
|
+
return props;
|
|
79
|
+
}
|
|
80
|
+
Object.keys(component).forEach((key) => {
|
|
81
|
+
const element = component[key];
|
|
82
|
+
if (element && typeof element === "object") {
|
|
83
|
+
Object.keys(element).forEach((prop) => {
|
|
84
|
+
if (!props.includes(prop) && prop !== "children" && prop !== "text") {
|
|
85
|
+
props.push(prop);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return props;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Analyze component structure
|
|
94
|
+
*/
|
|
95
|
+
analyzeComponent(component) {
|
|
96
|
+
if (!component || typeof component !== "object") {
|
|
97
|
+
return {
|
|
98
|
+
type: typeof component,
|
|
99
|
+
valid: false,
|
|
100
|
+
issues: ["Component is not an object"]
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const issues = [];
|
|
104
|
+
const warnings = [];
|
|
105
|
+
const info = [];
|
|
106
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
107
|
+
const checkCircular = (obj, path2 = []) => {
|
|
108
|
+
if (obj === null || typeof obj !== "object") {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (seen.has(obj)) {
|
|
112
|
+
warnings.push(`circular reference detected at ${path2.join(".")}`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
seen.add(obj);
|
|
116
|
+
if (Array.isArray(obj)) {
|
|
117
|
+
obj.forEach((item, index) => checkCircular(item, [...path2, `[${index}]`]));
|
|
118
|
+
} else {
|
|
119
|
+
Object.keys(obj).forEach((key) => {
|
|
120
|
+
checkCircular(obj[key], [...path2, key]);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
checkCircular(component, ["root"]);
|
|
125
|
+
const keys = Object.keys(component);
|
|
126
|
+
if (keys.length === 0) {
|
|
127
|
+
issues.push("Component is empty");
|
|
128
|
+
}
|
|
129
|
+
if (keys.length > 1) {
|
|
130
|
+
warnings.push("Component has multiple root elements");
|
|
131
|
+
}
|
|
132
|
+
keys.forEach((key) => {
|
|
133
|
+
const element = component[key];
|
|
134
|
+
if (typeof element === "object" && element !== null) {
|
|
135
|
+
if (element.children && !Array.isArray(element.children)) {
|
|
136
|
+
issues.push(`Children of ${key} should be an array`);
|
|
137
|
+
}
|
|
138
|
+
if (element.className && typeof element.className !== "string") {
|
|
139
|
+
warnings.push(`className of ${key} should be a string`);
|
|
140
|
+
}
|
|
141
|
+
if (element.style && typeof element.style !== "object") {
|
|
142
|
+
warnings.push(`style of ${key} should be an object`);
|
|
143
|
+
}
|
|
144
|
+
const eventHandlers = Object.keys(element).filter((k) => k.startsWith("on"));
|
|
145
|
+
if (eventHandlers.length > 0) {
|
|
146
|
+
info.push(`${key} has ${eventHandlers.length} event handler(s): ${eventHandlers.join(", ")}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
type: "component",
|
|
152
|
+
valid: issues.length === 0,
|
|
153
|
+
rootElements: keys,
|
|
154
|
+
issues,
|
|
155
|
+
warnings,
|
|
156
|
+
info
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Build component tree structure
|
|
161
|
+
*/
|
|
162
|
+
buildComponentTree(component, depth = 0, maxDepth = 10) {
|
|
163
|
+
if (depth > maxDepth) {
|
|
164
|
+
return { _truncated: true, reason: "Max depth reached" };
|
|
165
|
+
}
|
|
166
|
+
if (!component || typeof component !== "object") {
|
|
167
|
+
return { type: typeof component, value: component };
|
|
168
|
+
}
|
|
169
|
+
if (Array.isArray(component)) {
|
|
170
|
+
return component.map((child) => this.buildComponentTree(child, depth + 1, maxDepth));
|
|
171
|
+
}
|
|
172
|
+
const tree = {};
|
|
173
|
+
for (const [key, value] of Object.entries(component)) {
|
|
174
|
+
if (typeof value === "object" && value !== null) {
|
|
175
|
+
tree[key] = {
|
|
176
|
+
type: "element",
|
|
177
|
+
props: {},
|
|
178
|
+
children: []
|
|
179
|
+
};
|
|
180
|
+
for (const [prop, propValue] of Object.entries(value)) {
|
|
181
|
+
if (prop === "children") {
|
|
182
|
+
tree[key].children = this.buildComponentTree(propValue, depth + 1, maxDepth);
|
|
183
|
+
} else {
|
|
184
|
+
tree[key].props[prop] = propValue;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
tree[key] = { type: typeof value, value };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return tree;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Calculate component statistics
|
|
195
|
+
*/
|
|
196
|
+
calculateStats(component) {
|
|
197
|
+
const stats = {
|
|
198
|
+
elementCount: 0,
|
|
199
|
+
depth: 0,
|
|
200
|
+
textNodes: 0,
|
|
201
|
+
eventHandlers: 0,
|
|
202
|
+
hasStyles: false,
|
|
203
|
+
hasClasses: false,
|
|
204
|
+
complexity: 0
|
|
205
|
+
};
|
|
206
|
+
const traverse = (node, currentDepth = 1) => {
|
|
207
|
+
if (!node || typeof node !== "object") {
|
|
208
|
+
if (node !== null && node !== void 0) {
|
|
209
|
+
stats.textNodes++;
|
|
210
|
+
}
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (Array.isArray(node)) {
|
|
214
|
+
node.forEach((child) => traverse(child, currentDepth));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
stats.elementCount++;
|
|
218
|
+
stats.depth = Math.max(stats.depth, currentDepth);
|
|
219
|
+
for (const [_key, value] of Object.entries(node)) {
|
|
220
|
+
if (typeof value === "object" && value !== null) {
|
|
221
|
+
if (value.style) stats.hasStyles = true;
|
|
222
|
+
if (value.className) stats.hasClasses = true;
|
|
223
|
+
const handlers = Object.keys(value).filter((k) => k.startsWith("on"));
|
|
224
|
+
stats.eventHandlers += handlers.length;
|
|
225
|
+
if (value.children) {
|
|
226
|
+
traverse(value.children, currentDepth + 1);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
traverse(component);
|
|
232
|
+
stats.complexity = stats.elementCount * 10 + // Base complexity from element count
|
|
233
|
+
stats.depth * 5 + // Depth adds complexity
|
|
234
|
+
stats.eventHandlers * 3 + // Event handlers add complexity
|
|
235
|
+
stats.textNodes + // Text nodes add minimal complexity
|
|
236
|
+
(stats.hasStyles ? 5 : 0) + // Styles add complexity
|
|
237
|
+
(stats.hasClasses ? 3 : 0);
|
|
238
|
+
return stats;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get component by ID
|
|
242
|
+
*/
|
|
243
|
+
getComponent(id) {
|
|
244
|
+
return this.components.get(id);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get inspection history
|
|
248
|
+
*/
|
|
249
|
+
getHistory() {
|
|
250
|
+
return [...this.history];
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Search components by criteria
|
|
254
|
+
*/
|
|
255
|
+
search(criteria) {
|
|
256
|
+
const results = [];
|
|
257
|
+
for (const [id, inspection] of this.components.entries()) {
|
|
258
|
+
let matches = true;
|
|
259
|
+
if (criteria.hasIssues && inspection.analysis.issues.length === 0) {
|
|
260
|
+
matches = false;
|
|
261
|
+
}
|
|
262
|
+
if (criteria.hasWarnings && inspection.analysis.warnings.length === 0) {
|
|
263
|
+
matches = false;
|
|
264
|
+
}
|
|
265
|
+
if (criteria.minElements && inspection.stats.elementCount < criteria.minElements) {
|
|
266
|
+
matches = false;
|
|
267
|
+
}
|
|
268
|
+
if (criteria.maxElements && inspection.stats.elementCount > criteria.maxElements) {
|
|
269
|
+
matches = false;
|
|
270
|
+
}
|
|
271
|
+
if (matches) {
|
|
272
|
+
results.push({ id, inspection });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return results;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Compare two components
|
|
279
|
+
*/
|
|
280
|
+
compare(componentA, componentB) {
|
|
281
|
+
const inspectionA = typeof componentA === "string" ? this.getComponent(componentA) : this.inspect(componentA);
|
|
282
|
+
const inspectionB = typeof componentB === "string" ? this.getComponent(componentB) : this.inspect(componentB);
|
|
283
|
+
return {
|
|
284
|
+
statsComparison: {
|
|
285
|
+
elementCount: {
|
|
286
|
+
a: inspectionA.stats.elementCount,
|
|
287
|
+
b: inspectionB.stats.elementCount,
|
|
288
|
+
diff: inspectionB.stats.elementCount - inspectionA.stats.elementCount
|
|
289
|
+
},
|
|
290
|
+
depth: {
|
|
291
|
+
a: inspectionA.stats.depth,
|
|
292
|
+
b: inspectionB.stats.depth,
|
|
293
|
+
diff: inspectionB.stats.depth - inspectionA.stats.depth
|
|
294
|
+
},
|
|
295
|
+
textNodes: {
|
|
296
|
+
a: inspectionA.stats.textNodes,
|
|
297
|
+
b: inspectionB.stats.textNodes,
|
|
298
|
+
diff: inspectionB.stats.textNodes - inspectionA.stats.textNodes
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
structureMatch: JSON.stringify(inspectionA.tree) === JSON.stringify(inspectionB.tree),
|
|
302
|
+
issuesComparison: {
|
|
303
|
+
a: inspectionA.analysis.issues.length,
|
|
304
|
+
b: inspectionB.analysis.issues.length
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Generate report
|
|
310
|
+
*/
|
|
311
|
+
generateReport() {
|
|
312
|
+
return {
|
|
313
|
+
totalInspections: this.inspectionCount,
|
|
314
|
+
componentsTracked: this.components.size,
|
|
315
|
+
historySize: this.history.length,
|
|
316
|
+
summary: {
|
|
317
|
+
totalElements: Array.from(this.components.values()).reduce((sum, c) => sum + c.stats.elementCount, 0),
|
|
318
|
+
averageDepth: Array.from(this.components.values()).reduce((sum, c) => sum + c.stats.depth, 0) / this.components.size || 0,
|
|
319
|
+
componentsWithIssues: Array.from(this.components.values()).filter((c) => c.analysis.issues.length > 0).length,
|
|
320
|
+
componentsWithWarnings: Array.from(this.components.values()).filter((c) => c.analysis.warnings.length > 0).length
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Clear all data
|
|
326
|
+
*/
|
|
327
|
+
clear() {
|
|
328
|
+
this.components.clear();
|
|
329
|
+
this.history = [];
|
|
330
|
+
this.inspectionCount = 0;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Clear history only
|
|
334
|
+
*/
|
|
335
|
+
clearHistory() {
|
|
336
|
+
this.history = [];
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Get inspection statistics
|
|
340
|
+
*/
|
|
341
|
+
getStats() {
|
|
342
|
+
return {
|
|
343
|
+
totalInspections: this.inspectionCount,
|
|
344
|
+
componentsTracked: this.components.size,
|
|
345
|
+
historySize: this.history.length
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Export inspection data
|
|
350
|
+
*/
|
|
351
|
+
export() {
|
|
352
|
+
return {
|
|
353
|
+
inspections: this.history.map((h) => ({
|
|
354
|
+
id: h.id,
|
|
355
|
+
timestamp: h.timestamp,
|
|
356
|
+
type: h.type,
|
|
357
|
+
complexity: h.complexity,
|
|
358
|
+
depth: h.depth,
|
|
359
|
+
issues: h.issues,
|
|
360
|
+
warnings: h.warnings
|
|
361
|
+
})),
|
|
362
|
+
stats: this.getStats(),
|
|
363
|
+
exportedAt: Date.now()
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Generate unique ID
|
|
368
|
+
*/
|
|
369
|
+
generateId() {
|
|
370
|
+
return `inspect-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
function createInspector(options = {}) {
|
|
374
|
+
return new ComponentInspector(options);
|
|
375
|
+
}
|
|
376
|
+
function inspect(component, options = {}) {
|
|
377
|
+
const inspector = new ComponentInspector(options);
|
|
378
|
+
return inspector.inspect(component);
|
|
379
|
+
}
|
|
380
|
+
function validateComponent(component) {
|
|
381
|
+
const inspector = new ComponentInspector();
|
|
382
|
+
const inspection = inspector.inspect(component);
|
|
383
|
+
return {
|
|
384
|
+
valid: inspection.valid,
|
|
385
|
+
errors: inspection.issues || [],
|
|
386
|
+
issues: inspection.issues || [],
|
|
387
|
+
warnings: inspection.warnings || [],
|
|
388
|
+
stats: inspection.stats
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/profiler.js
|
|
393
|
+
var PerformanceProfiler = class {
|
|
394
|
+
constructor(options = {}) {
|
|
395
|
+
this.options = {
|
|
396
|
+
enabled: true,
|
|
397
|
+
sampleRate: 1,
|
|
398
|
+
// 1.0 = 100% sampling
|
|
399
|
+
slowThreshold: 16,
|
|
400
|
+
// 16ms = 60fps
|
|
401
|
+
trackMemory: typeof performance !== "undefined" && performance.memory,
|
|
402
|
+
maxSamples: options.maxSamples || 1e3,
|
|
403
|
+
...options
|
|
404
|
+
};
|
|
405
|
+
this.measurements = [];
|
|
406
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
407
|
+
this.currentSession = null;
|
|
408
|
+
this.marks = /* @__PURE__ */ new Map();
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Start a profiling session
|
|
412
|
+
*/
|
|
413
|
+
start(name = "default") {
|
|
414
|
+
if (!this.options.enabled) {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
if (this.options.sampleRate < 1 && Math.random() > this.options.sampleRate) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
const session = {
|
|
421
|
+
id: this.generateId(),
|
|
422
|
+
name,
|
|
423
|
+
startTime: Date.now(),
|
|
424
|
+
measurements: [],
|
|
425
|
+
marks: [],
|
|
426
|
+
active: true
|
|
427
|
+
};
|
|
428
|
+
this.sessions.set(session.id, session);
|
|
429
|
+
this.currentSession = session;
|
|
430
|
+
if (typeof performance !== "undefined" && performance.mark) {
|
|
431
|
+
performance.mark(`coherent-session-start-${session.id}`);
|
|
432
|
+
}
|
|
433
|
+
return session.id;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Stop a profiling session
|
|
437
|
+
*/
|
|
438
|
+
stop(sessionId) {
|
|
439
|
+
if (!sessionId) {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
const session = this.sessions.get(sessionId);
|
|
443
|
+
if (!session) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
session.endTime = Date.now();
|
|
447
|
+
session.duration = session.endTime - session.startTime;
|
|
448
|
+
session.active = false;
|
|
449
|
+
if (typeof performance !== "undefined" && performance.mark) {
|
|
450
|
+
performance.mark(`coherent-session-end-${session.id}`);
|
|
451
|
+
}
|
|
452
|
+
if (this.currentSession === session) {
|
|
453
|
+
this.currentSession = null;
|
|
454
|
+
}
|
|
455
|
+
this.measurements.push(session);
|
|
456
|
+
if (this.measurements.length > this.options.maxSamples) {
|
|
457
|
+
this.measurements.shift();
|
|
458
|
+
}
|
|
459
|
+
return this.analyzeSession(session);
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Start measuring a render
|
|
463
|
+
*/
|
|
464
|
+
startRender(componentName, props = {}) {
|
|
465
|
+
if (!this.options.enabled) return null;
|
|
466
|
+
if (Math.random() > this.options.sampleRate) return null;
|
|
467
|
+
const measurementId = this.generateId();
|
|
468
|
+
const measurement = {
|
|
469
|
+
id: measurementId,
|
|
470
|
+
componentName,
|
|
471
|
+
props,
|
|
472
|
+
startTime: Date.now(),
|
|
473
|
+
startMemory: this.getMemoryUsage(),
|
|
474
|
+
phase: "render"
|
|
475
|
+
};
|
|
476
|
+
this.marks.set(measurementId, measurement);
|
|
477
|
+
if (typeof performance !== "undefined" && performance.mark) {
|
|
478
|
+
performance.mark(`coherent-render-start-${measurementId}`);
|
|
479
|
+
}
|
|
480
|
+
return measurementId;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* End measuring a render
|
|
484
|
+
*/
|
|
485
|
+
endRender(measurementId, result = {}) {
|
|
486
|
+
if (!measurementId || !this.marks.has(measurementId)) return null;
|
|
487
|
+
const measurement = this.marks.get(measurementId);
|
|
488
|
+
measurement.endTime = Date.now();
|
|
489
|
+
measurement.duration = measurement.endTime - measurement.startTime;
|
|
490
|
+
measurement.endMemory = this.getMemoryUsage();
|
|
491
|
+
measurement.memoryDelta = measurement.endMemory - measurement.startMemory;
|
|
492
|
+
measurement.result = result;
|
|
493
|
+
measurement.slow = measurement.duration > this.options.slowThreshold;
|
|
494
|
+
if (typeof performance !== "undefined" && performance.mark) {
|
|
495
|
+
performance.mark(`coherent-render-end-${measurementId}`);
|
|
496
|
+
if (performance.measure) {
|
|
497
|
+
try {
|
|
498
|
+
performance.measure(
|
|
499
|
+
`coherent-render-${measurementId}`,
|
|
500
|
+
`coherent-render-start-${measurementId}`,
|
|
501
|
+
`coherent-render-end-${measurementId}`
|
|
502
|
+
);
|
|
503
|
+
} catch {
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
this.measurements.push(measurement);
|
|
508
|
+
if (this.currentSession) {
|
|
509
|
+
this.currentSession.measurements.push(measurement);
|
|
510
|
+
}
|
|
511
|
+
this.marks.delete(measurementId);
|
|
512
|
+
return measurement;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Mark a point in time
|
|
516
|
+
*/
|
|
517
|
+
mark(name, data = {}) {
|
|
518
|
+
const mark = {
|
|
519
|
+
name,
|
|
520
|
+
timestamp: Date.now(),
|
|
521
|
+
data,
|
|
522
|
+
memory: this.getMemoryUsage()
|
|
523
|
+
};
|
|
524
|
+
if (this.currentSession) {
|
|
525
|
+
this.currentSession.marks.push(mark);
|
|
526
|
+
}
|
|
527
|
+
if (typeof performance !== "undefined" && performance.mark) {
|
|
528
|
+
performance.mark(`coherent-mark-${name}`);
|
|
529
|
+
}
|
|
530
|
+
return mark;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Measure time between two marks
|
|
534
|
+
*/
|
|
535
|
+
measure(startMark, endMark) {
|
|
536
|
+
const start = this.findMark(startMark);
|
|
537
|
+
const end = this.findMark(endMark);
|
|
538
|
+
if (!start || !end) {
|
|
539
|
+
throw new Error("Mark not found");
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
duration: end.timestamp - start.timestamp,
|
|
543
|
+
startMark: start.name,
|
|
544
|
+
endMark: end.name
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Get memory usage
|
|
549
|
+
*/
|
|
550
|
+
getMemoryUsage() {
|
|
551
|
+
if (typeof performance !== "undefined" && performance.memory) {
|
|
552
|
+
return {
|
|
553
|
+
used: performance.memory.usedJSHeapSize,
|
|
554
|
+
total: performance.memory.totalJSHeapSize,
|
|
555
|
+
limit: performance.memory.jsHeapSizeLimit
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Find a mark by name
|
|
562
|
+
*/
|
|
563
|
+
findMark(name) {
|
|
564
|
+
if (!this.currentSession) return null;
|
|
565
|
+
return this.currentSession.marks.find((m) => m.name === name);
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Get all measurements
|
|
569
|
+
*/
|
|
570
|
+
getMeasurements(filter = {}) {
|
|
571
|
+
let results = [...this.measurements];
|
|
572
|
+
if (filter.componentName) {
|
|
573
|
+
results = results.filter((m) => m.componentName === filter.componentName);
|
|
574
|
+
}
|
|
575
|
+
if (filter.slow) {
|
|
576
|
+
results = results.filter((m) => m.slow);
|
|
577
|
+
}
|
|
578
|
+
if (filter.minDuration) {
|
|
579
|
+
results = results.filter((m) => m.duration >= filter.minDuration);
|
|
580
|
+
}
|
|
581
|
+
if (filter.limit) {
|
|
582
|
+
results = results.slice(0, filter.limit);
|
|
583
|
+
}
|
|
584
|
+
return results;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Analyze a session
|
|
588
|
+
*/
|
|
589
|
+
analyzeSession(session) {
|
|
590
|
+
const measurements = session.measurements;
|
|
591
|
+
if (measurements.length === 0) {
|
|
592
|
+
return {
|
|
593
|
+
session: session.id,
|
|
594
|
+
duration: session.duration,
|
|
595
|
+
measurements: 0,
|
|
596
|
+
analysis: null
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
const durations = measurements.map((m) => m.duration);
|
|
600
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
601
|
+
return {
|
|
602
|
+
session: session.id,
|
|
603
|
+
name: session.name,
|
|
604
|
+
duration: session.duration,
|
|
605
|
+
measurements: measurements.length,
|
|
606
|
+
analysis: {
|
|
607
|
+
total: durations.reduce((a, b) => a + b, 0),
|
|
608
|
+
average: durations.reduce((a, b) => a + b, 0) / durations.length,
|
|
609
|
+
median: sorted[Math.floor(sorted.length / 2)],
|
|
610
|
+
min: Math.min(...durations),
|
|
611
|
+
max: Math.max(...durations),
|
|
612
|
+
p95: sorted[Math.floor(sorted.length * 0.95)],
|
|
613
|
+
p99: sorted[Math.floor(sorted.length * 0.99)],
|
|
614
|
+
slowRenders: measurements.filter((m) => m.slow).length,
|
|
615
|
+
slowPercentage: measurements.filter((m) => m.slow).length / measurements.length * 100
|
|
616
|
+
},
|
|
617
|
+
byComponent: this.groupByComponent(measurements),
|
|
618
|
+
slowest: measurements.sort((a, b) => b.duration - a.duration).slice(0, 10).map((m) => ({
|
|
619
|
+
component: m.componentName,
|
|
620
|
+
duration: m.duration,
|
|
621
|
+
timestamp: m.startTime
|
|
622
|
+
}))
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Group measurements by component
|
|
627
|
+
*/
|
|
628
|
+
groupByComponent(measurements) {
|
|
629
|
+
const groups = {};
|
|
630
|
+
measurements.forEach((m) => {
|
|
631
|
+
if (!groups[m.componentName]) {
|
|
632
|
+
groups[m.componentName] = {
|
|
633
|
+
count: 0,
|
|
634
|
+
totalDuration: 0,
|
|
635
|
+
durations: []
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
groups[m.componentName].count++;
|
|
639
|
+
groups[m.componentName].totalDuration += m.duration;
|
|
640
|
+
groups[m.componentName].durations.push(m.duration);
|
|
641
|
+
});
|
|
642
|
+
Object.keys(groups).forEach((name) => {
|
|
643
|
+
const group = groups[name];
|
|
644
|
+
group.average = group.totalDuration / group.count;
|
|
645
|
+
group.min = Math.min(...group.durations);
|
|
646
|
+
group.max = Math.max(...group.durations);
|
|
647
|
+
});
|
|
648
|
+
return groups;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Get performance summary
|
|
652
|
+
*/
|
|
653
|
+
getSummary() {
|
|
654
|
+
const allMeasurements = this.measurements;
|
|
655
|
+
if (allMeasurements.length === 0) {
|
|
656
|
+
return {
|
|
657
|
+
totalMeasurements: 0,
|
|
658
|
+
totalSessions: this.sessions.size,
|
|
659
|
+
analysis: null
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
const durations = allMeasurements.map((m) => m.duration);
|
|
663
|
+
return {
|
|
664
|
+
totalMeasurements: allMeasurements.length,
|
|
665
|
+
totalSessions: this.sessions.size,
|
|
666
|
+
slowRenders: allMeasurements.filter((m) => m.slow).length,
|
|
667
|
+
analysis: {
|
|
668
|
+
average: durations.reduce((a, b) => a + b, 0) / durations.length,
|
|
669
|
+
min: Math.min(...durations),
|
|
670
|
+
max: Math.max(...durations),
|
|
671
|
+
slowPercentage: allMeasurements.filter((m) => m.slow).length / allMeasurements.length * 100
|
|
672
|
+
},
|
|
673
|
+
byComponent: this.groupByComponent(allMeasurements),
|
|
674
|
+
recentSlow: allMeasurements.filter((m) => m.slow).slice(-10).map((m) => ({
|
|
675
|
+
component: m.componentName,
|
|
676
|
+
duration: m.duration,
|
|
677
|
+
timestamp: m.startTime
|
|
678
|
+
}))
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Get statistics
|
|
683
|
+
*/
|
|
684
|
+
getStatistics() {
|
|
685
|
+
if (this.measurements.length === 0) {
|
|
686
|
+
return { mean: 0, median: 0, min: 0, max: 0, stdDev: 0 };
|
|
687
|
+
}
|
|
688
|
+
const durations = this.measurements.map((m) => m.duration);
|
|
689
|
+
const sum = durations.reduce((a, b) => a + b, 0);
|
|
690
|
+
const mean = sum / durations.length;
|
|
691
|
+
const sorted = [...durations].sort((a, b) => a - b);
|
|
692
|
+
const median = sorted[Math.floor(sorted.length / 2)];
|
|
693
|
+
const variance = durations.reduce((acc, val) => acc + Math.pow(val - mean, 2), 0) / durations.length;
|
|
694
|
+
const stdDev = Math.sqrt(variance);
|
|
695
|
+
return {
|
|
696
|
+
mean,
|
|
697
|
+
median,
|
|
698
|
+
min: Math.min(...durations),
|
|
699
|
+
max: Math.max(...durations),
|
|
700
|
+
stdDev
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Get bottlenecks
|
|
705
|
+
*/
|
|
706
|
+
getBottlenecks(threshold = null) {
|
|
707
|
+
const slowThreshold = threshold || this.options.slowThreshold;
|
|
708
|
+
return this.measurements.filter((m) => m.duration > slowThreshold).sort((a, b) => b.duration - a.duration).map((m) => ({
|
|
709
|
+
name: m.name,
|
|
710
|
+
duration: m.duration,
|
|
711
|
+
timestamp: m.startTime
|
|
712
|
+
}));
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Get metrics
|
|
716
|
+
*/
|
|
717
|
+
getMetrics() {
|
|
718
|
+
const operationCounts = {};
|
|
719
|
+
this.measurements.forEach((m) => {
|
|
720
|
+
operationCounts[m.name] = (operationCounts[m.name] || 0) + 1;
|
|
721
|
+
});
|
|
722
|
+
const totalDuration = this.measurements.reduce((sum, m) => sum + m.duration, 0);
|
|
723
|
+
return {
|
|
724
|
+
totalOperations: this.measurements.length,
|
|
725
|
+
totalDuration,
|
|
726
|
+
operationCounts,
|
|
727
|
+
averageDuration: this.measurements.length > 0 ? totalDuration / this.measurements.length : 0,
|
|
728
|
+
memoryUsage: this.options.trackMemory && typeof performance !== "undefined" && performance.memory ? performance.memory.usedJSHeapSize : null
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Generate report
|
|
733
|
+
*/
|
|
734
|
+
generateReport() {
|
|
735
|
+
const stats = this.getStatistics();
|
|
736
|
+
const metrics = this.getMetrics();
|
|
737
|
+
const bottlenecks = this.getBottlenecks();
|
|
738
|
+
const recommendations = this.getRecommendations();
|
|
739
|
+
return {
|
|
740
|
+
summary: {
|
|
741
|
+
totalOperations: metrics.totalOperations,
|
|
742
|
+
averageDuration: metrics.averageDuration,
|
|
743
|
+
slowOperations: bottlenecks.length
|
|
744
|
+
},
|
|
745
|
+
statistics: stats,
|
|
746
|
+
operations: this.measurements.map((m) => ({
|
|
747
|
+
name: m.name,
|
|
748
|
+
duration: m.duration,
|
|
749
|
+
timestamp: m.startTime
|
|
750
|
+
})),
|
|
751
|
+
bottlenecks: bottlenecks.slice(0, 10),
|
|
752
|
+
recommendations,
|
|
753
|
+
timestamp: Date.now()
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Export profiling data
|
|
758
|
+
*/
|
|
759
|
+
export() {
|
|
760
|
+
return {
|
|
761
|
+
sessions: Array.from(this.sessions.values()),
|
|
762
|
+
measurements: this.measurements,
|
|
763
|
+
metrics: this.getMetrics(),
|
|
764
|
+
statistics: this.getStatistics(),
|
|
765
|
+
exportedAt: Date.now()
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
/**
|
|
769
|
+
* Format metrics for display
|
|
770
|
+
*/
|
|
771
|
+
formatMetrics() {
|
|
772
|
+
const metrics = this.getMetrics();
|
|
773
|
+
const stats = this.getStatistics();
|
|
774
|
+
let output = `Performance Metrics
|
|
775
|
+
`;
|
|
776
|
+
output += `==================
|
|
777
|
+
`;
|
|
778
|
+
output += `Total Operations: ${metrics.totalOperations}
|
|
779
|
+
`;
|
|
780
|
+
output += `Average Duration: ${metrics.averageDuration.toFixed(2)}ms
|
|
781
|
+
`;
|
|
782
|
+
output += `Mean: ${stats.mean.toFixed(2)}ms
|
|
783
|
+
`;
|
|
784
|
+
output += `Median: ${stats.median.toFixed(2)}ms
|
|
785
|
+
`;
|
|
786
|
+
output += `Min: ${stats.min.toFixed(2)}ms
|
|
787
|
+
`;
|
|
788
|
+
output += `Max: ${stats.max.toFixed(2)}ms
|
|
789
|
+
`;
|
|
790
|
+
return output;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Compare two profiles
|
|
794
|
+
*/
|
|
795
|
+
compare(profileId1, profileId2) {
|
|
796
|
+
const session1 = this.sessions.get(profileId1);
|
|
797
|
+
const session2 = this.sessions.get(profileId2);
|
|
798
|
+
if (!session1 || !session2) {
|
|
799
|
+
return null;
|
|
800
|
+
}
|
|
801
|
+
return {
|
|
802
|
+
difference: session2.duration - session1.duration,
|
|
803
|
+
percentChange: (session2.duration - session1.duration) / session1.duration * 100,
|
|
804
|
+
profile1: { name: session1.name, duration: session1.duration },
|
|
805
|
+
profile2: { name: session2.name, duration: session2.duration }
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Get performance recommendations
|
|
810
|
+
*/
|
|
811
|
+
getRecommendations() {
|
|
812
|
+
const recommendations = [];
|
|
813
|
+
const bottlenecks = this.getBottlenecks();
|
|
814
|
+
const stats = this.getStatistics();
|
|
815
|
+
if (bottlenecks.length > 0) {
|
|
816
|
+
bottlenecks.slice(0, 5).forEach((bottleneck) => {
|
|
817
|
+
recommendations.push({
|
|
818
|
+
type: "bottleneck",
|
|
819
|
+
operation: bottleneck.name,
|
|
820
|
+
suggestion: `Optimize ${bottleneck.name} - duration: ${bottleneck.duration.toFixed(2)}ms exceeds threshold`,
|
|
821
|
+
severity: "high",
|
|
822
|
+
message: `Found slow operation exceeding ${this.options.slowThreshold}ms`
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
if (stats.max > this.options.slowThreshold * 2) {
|
|
827
|
+
recommendations.push({
|
|
828
|
+
type: "performance",
|
|
829
|
+
operation: "general",
|
|
830
|
+
suggestion: `Review operations with high duration`,
|
|
831
|
+
message: `Maximum duration (${stats.max.toFixed(2)}ms) is significantly high`,
|
|
832
|
+
severity: "medium"
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
return recommendations;
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Clear all data
|
|
839
|
+
*/
|
|
840
|
+
clear() {
|
|
841
|
+
this.measurements = [];
|
|
842
|
+
this.sessions.clear();
|
|
843
|
+
this.currentSession = null;
|
|
844
|
+
this.marks.clear();
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Enable profiler
|
|
848
|
+
*/
|
|
849
|
+
enable() {
|
|
850
|
+
this.options.enabled = true;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Disable profiler
|
|
854
|
+
*/
|
|
855
|
+
disable() {
|
|
856
|
+
this.options.enabled = false;
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Generate unique ID
|
|
860
|
+
*/
|
|
861
|
+
generateId() {
|
|
862
|
+
return `prof-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
863
|
+
}
|
|
864
|
+
};
|
|
865
|
+
function createProfiler(options = {}) {
|
|
866
|
+
return new PerformanceProfiler(options);
|
|
867
|
+
}
|
|
868
|
+
async function measure(name, fn, profiler = null) {
|
|
869
|
+
const prof = profiler || new PerformanceProfiler();
|
|
870
|
+
const sessionId = prof.start(name);
|
|
871
|
+
try {
|
|
872
|
+
const value = await fn();
|
|
873
|
+
const result = prof.stop(sessionId);
|
|
874
|
+
return { value, duration: result?.duration || 0 };
|
|
875
|
+
} catch (error) {
|
|
876
|
+
const result = prof.stop(sessionId);
|
|
877
|
+
throw { error, duration: result?.duration || 0 };
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
function profile(fn) {
|
|
881
|
+
return function(...args) {
|
|
882
|
+
const result = fn(...args);
|
|
883
|
+
return result;
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// src/logger.js
|
|
888
|
+
var LogLevel = {
|
|
889
|
+
TRACE: 0,
|
|
890
|
+
DEBUG: 1,
|
|
891
|
+
INFO: 2,
|
|
892
|
+
WARN: 3,
|
|
893
|
+
ERROR: 4,
|
|
894
|
+
FATAL: 5
|
|
895
|
+
};
|
|
896
|
+
var DevLogger = class _DevLogger {
|
|
897
|
+
constructor(options = {}) {
|
|
898
|
+
this.options = {
|
|
899
|
+
level: options.level !== void 0 ? options.level : LogLevel.INFO,
|
|
900
|
+
prefix: "[Coherent]",
|
|
901
|
+
timestamp: true,
|
|
902
|
+
colors: true,
|
|
903
|
+
maxLogs: 1e3,
|
|
904
|
+
maxBufferSize: options.maxBufferSize || 1e3,
|
|
905
|
+
grouping: true,
|
|
906
|
+
buffer: options.buffer || false,
|
|
907
|
+
sampleRate: options.sampleRate !== void 0 ? options.sampleRate : 1,
|
|
908
|
+
silent: options.silent || false,
|
|
909
|
+
output: options.output || null,
|
|
910
|
+
categories: options.categories || null,
|
|
911
|
+
filter: options.filter || null,
|
|
912
|
+
...options
|
|
913
|
+
};
|
|
914
|
+
this.logs = [];
|
|
915
|
+
this.groups = [];
|
|
916
|
+
this.filters = [];
|
|
917
|
+
this.handlers = [];
|
|
918
|
+
this.context = {};
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Generic log method (supports category logging)
|
|
922
|
+
*/
|
|
923
|
+
log(categoryOrLevel, messageOrData, data) {
|
|
924
|
+
if (typeof categoryOrLevel === "string" && typeof messageOrData === "string") {
|
|
925
|
+
return this.logWithLevel(LogLevel.INFO, messageOrData, { category: categoryOrLevel, ...data });
|
|
926
|
+
}
|
|
927
|
+
return this.logWithLevel(categoryOrLevel, messageOrData, data);
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Log a trace message
|
|
931
|
+
*/
|
|
932
|
+
trace(message, data = {}) {
|
|
933
|
+
return this.logWithLevel(LogLevel.TRACE, message, data);
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Log a debug message
|
|
937
|
+
*/
|
|
938
|
+
debug(message, data = {}) {
|
|
939
|
+
return this.log(LogLevel.DEBUG, message, data);
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Log an info message
|
|
943
|
+
*/
|
|
944
|
+
info(message, data = {}) {
|
|
945
|
+
return this.log(LogLevel.INFO, message, data);
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Log a warning
|
|
949
|
+
*/
|
|
950
|
+
warn(message, data = {}) {
|
|
951
|
+
return this.log(LogLevel.WARN, message, data);
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Log an error
|
|
955
|
+
*/
|
|
956
|
+
error(message, data = {}) {
|
|
957
|
+
if (data instanceof Error) {
|
|
958
|
+
data = {
|
|
959
|
+
message: data.message,
|
|
960
|
+
stack: data.stack,
|
|
961
|
+
name: data.name,
|
|
962
|
+
...data
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
return this.logWithLevel(LogLevel.ERROR, message, data);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Log a fatal error
|
|
969
|
+
*/
|
|
970
|
+
fatal(message, data = {}) {
|
|
971
|
+
return this.log(LogLevel.FATAL, message, data);
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Core logging function
|
|
975
|
+
*/
|
|
976
|
+
logWithLevel(level, message, data = {}) {
|
|
977
|
+
if (this.options.sampleRate < 1 && Math.random() > this.options.sampleRate) {
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
if (level < this.options.level) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
if (!this.shouldLog(level, message, data)) {
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
const mergedData = { ...this.context, ...data };
|
|
987
|
+
const logEntry = {
|
|
988
|
+
id: this.generateId(),
|
|
989
|
+
level,
|
|
990
|
+
levelName: this.getLevelName(level),
|
|
991
|
+
message,
|
|
992
|
+
data: mergedData,
|
|
993
|
+
timestamp: Date.now(),
|
|
994
|
+
group: this.groups.length > 0 ? this.groups[this.groups.length - 1] : null,
|
|
995
|
+
stack: level >= LogLevel.ERROR ? new Error().stack : null
|
|
996
|
+
};
|
|
997
|
+
this.logs.push(logEntry);
|
|
998
|
+
const maxSize = this.options.buffer ? this.options.maxBufferSize : this.options.maxLogs;
|
|
999
|
+
if (this.logs.length > maxSize) {
|
|
1000
|
+
this.logs.shift();
|
|
1001
|
+
}
|
|
1002
|
+
this.handlers.forEach((handler) => {
|
|
1003
|
+
try {
|
|
1004
|
+
handler(logEntry);
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
console.error("Error in log handler:", error);
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
this.output(logEntry);
|
|
1010
|
+
return logEntry;
|
|
1011
|
+
}
|
|
1012
|
+
/**
|
|
1013
|
+
* Output log to console
|
|
1014
|
+
*/
|
|
1015
|
+
output(logEntry) {
|
|
1016
|
+
const parts = [];
|
|
1017
|
+
if (this.options.timestamp) {
|
|
1018
|
+
const date = new Date(logEntry.timestamp);
|
|
1019
|
+
parts.push(`[${date.toISOString()}]`);
|
|
1020
|
+
}
|
|
1021
|
+
if (this.options.prefix) {
|
|
1022
|
+
parts.push(this.options.prefix);
|
|
1023
|
+
}
|
|
1024
|
+
parts.push(`[${logEntry.levelName}]`);
|
|
1025
|
+
if (logEntry.group) {
|
|
1026
|
+
parts.push(`[${logEntry.group}]`);
|
|
1027
|
+
}
|
|
1028
|
+
parts.push(logEntry.message);
|
|
1029
|
+
const contextKeys = Object.keys(logEntry.data);
|
|
1030
|
+
if (contextKeys.length > 0) {
|
|
1031
|
+
const contextStr = contextKeys.map((key) => `${key}=${logEntry.data[key]}`).join(" ");
|
|
1032
|
+
parts.push(`{${contextStr}}`);
|
|
1033
|
+
}
|
|
1034
|
+
const output = parts.join(" ");
|
|
1035
|
+
if (this.options.silent) {
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
if (this.options.output) {
|
|
1039
|
+
this.options.output(logEntry);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (typeof console !== "undefined") {
|
|
1043
|
+
switch (logEntry.level) {
|
|
1044
|
+
case LogLevel.TRACE:
|
|
1045
|
+
case LogLevel.DEBUG:
|
|
1046
|
+
case LogLevel.INFO:
|
|
1047
|
+
console.log(output, logEntry.data);
|
|
1048
|
+
break;
|
|
1049
|
+
case LogLevel.WARN:
|
|
1050
|
+
console.warn(output, logEntry.data);
|
|
1051
|
+
break;
|
|
1052
|
+
case LogLevel.ERROR:
|
|
1053
|
+
case LogLevel.FATAL:
|
|
1054
|
+
console.error(output, logEntry.data);
|
|
1055
|
+
if (logEntry.stack) {
|
|
1056
|
+
console.error(logEntry.stack);
|
|
1057
|
+
}
|
|
1058
|
+
break;
|
|
1059
|
+
}
|
|
1060
|
+
} else {
|
|
1061
|
+
console.log(output, logEntry.data);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Get color style for log level
|
|
1066
|
+
*/
|
|
1067
|
+
getColorStyle(level) {
|
|
1068
|
+
const styles = {
|
|
1069
|
+
[LogLevel.TRACE]: "color: gray",
|
|
1070
|
+
[LogLevel.DEBUG]: "color: blue",
|
|
1071
|
+
[LogLevel.INFO]: "color: green",
|
|
1072
|
+
[LogLevel.WARN]: "color: orange",
|
|
1073
|
+
[LogLevel.ERROR]: "color: red",
|
|
1074
|
+
[LogLevel.FATAL]: "color: red; font-weight: bold"
|
|
1075
|
+
};
|
|
1076
|
+
return styles[level] || "";
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Get level name
|
|
1080
|
+
*/
|
|
1081
|
+
getLevelName(level) {
|
|
1082
|
+
const names = {
|
|
1083
|
+
[LogLevel.TRACE]: "TRACE",
|
|
1084
|
+
[LogLevel.DEBUG]: "DEBUG",
|
|
1085
|
+
[LogLevel.INFO]: "INFO",
|
|
1086
|
+
[LogLevel.WARN]: "WARN",
|
|
1087
|
+
[LogLevel.ERROR]: "ERROR",
|
|
1088
|
+
[LogLevel.FATAL]: "FATAL"
|
|
1089
|
+
};
|
|
1090
|
+
return names[level] || "UNKNOWN";
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Check if log should be output
|
|
1094
|
+
*/
|
|
1095
|
+
shouldLog(level, message, data) {
|
|
1096
|
+
if (this.options.filter && !this.options.filter(message, data)) {
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
if (this.options.categories && data.category) {
|
|
1100
|
+
if (!this.options.categories.includes(data.category)) {
|
|
1101
|
+
return false;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (this.filters.length > 0) {
|
|
1105
|
+
return this.filters.every((filter) => filter(level, message, data));
|
|
1106
|
+
}
|
|
1107
|
+
return true;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Add a filter
|
|
1111
|
+
*/
|
|
1112
|
+
addFilter(filter) {
|
|
1113
|
+
this.filters.push(filter);
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Remove a filter
|
|
1117
|
+
*/
|
|
1118
|
+
removeFilter(filter) {
|
|
1119
|
+
const index = this.filters.indexOf(filter);
|
|
1120
|
+
if (index > -1) {
|
|
1121
|
+
this.filters.splice(index, 1);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
/**
|
|
1125
|
+
* Add a log handler
|
|
1126
|
+
*/
|
|
1127
|
+
addHandler(handler) {
|
|
1128
|
+
this.handlers.push(handler);
|
|
1129
|
+
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Remove a log handler
|
|
1132
|
+
*/
|
|
1133
|
+
removeHandler(handler) {
|
|
1134
|
+
const index = this.handlers.indexOf(handler);
|
|
1135
|
+
if (index > -1) {
|
|
1136
|
+
this.handlers.splice(index, 1);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Start a log group
|
|
1141
|
+
*/
|
|
1142
|
+
group(name) {
|
|
1143
|
+
this.groups.push(name);
|
|
1144
|
+
if (typeof console !== "undefined" && console.group) {
|
|
1145
|
+
console.group(name);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* End a log group
|
|
1150
|
+
*/
|
|
1151
|
+
groupEnd() {
|
|
1152
|
+
this.groups.pop();
|
|
1153
|
+
if (typeof console !== "undefined" && console.groupEnd) {
|
|
1154
|
+
console.groupEnd();
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Get all logs
|
|
1159
|
+
*/
|
|
1160
|
+
getLogs(filter = {}) {
|
|
1161
|
+
let results = [...this.logs];
|
|
1162
|
+
if (filter.level !== void 0) {
|
|
1163
|
+
results = results.filter((log) => log.level >= filter.level);
|
|
1164
|
+
}
|
|
1165
|
+
if (filter.group) {
|
|
1166
|
+
results = results.filter((log) => log.group === filter.group);
|
|
1167
|
+
}
|
|
1168
|
+
if (filter.search) {
|
|
1169
|
+
const search = filter.search.toLowerCase();
|
|
1170
|
+
results = results.filter(
|
|
1171
|
+
(log) => log.message.toLowerCase().includes(search) || JSON.stringify(log.data).toLowerCase().includes(search)
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
if (filter.limit) {
|
|
1175
|
+
results = results.slice(-filter.limit);
|
|
1176
|
+
}
|
|
1177
|
+
return results;
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Get log statistics
|
|
1181
|
+
*/
|
|
1182
|
+
getStats() {
|
|
1183
|
+
const byLevel = {};
|
|
1184
|
+
Object.values(LogLevel).forEach((level) => {
|
|
1185
|
+
byLevel[this.getLevelName(level)] = 0;
|
|
1186
|
+
});
|
|
1187
|
+
this.logs.forEach((log) => {
|
|
1188
|
+
byLevel[log.levelName]++;
|
|
1189
|
+
});
|
|
1190
|
+
return {
|
|
1191
|
+
total: this.logs.length,
|
|
1192
|
+
byLevel,
|
|
1193
|
+
groups: [...new Set(this.logs.map((l) => l.group).filter(Boolean))],
|
|
1194
|
+
timeRange: this.logs.length > 0 ? {
|
|
1195
|
+
start: this.logs[0].timestamp,
|
|
1196
|
+
end: this.logs[this.logs.length - 1].timestamp,
|
|
1197
|
+
duration: this.logs[this.logs.length - 1].timestamp - this.logs[0].timestamp
|
|
1198
|
+
} : null
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Clear all logs
|
|
1203
|
+
*/
|
|
1204
|
+
clear() {
|
|
1205
|
+
this.logs = [];
|
|
1206
|
+
this.groups = [];
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Set log level
|
|
1210
|
+
*/
|
|
1211
|
+
setLevel(level) {
|
|
1212
|
+
this.options.level = level;
|
|
1213
|
+
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Export logs
|
|
1216
|
+
*/
|
|
1217
|
+
export(format = "array") {
|
|
1218
|
+
if (format === "array" || !format) {
|
|
1219
|
+
return this.logs.map((log) => ({
|
|
1220
|
+
level: log.levelName,
|
|
1221
|
+
message: log.message,
|
|
1222
|
+
timestamp: log.timestamp,
|
|
1223
|
+
data: log.data
|
|
1224
|
+
}));
|
|
1225
|
+
}
|
|
1226
|
+
switch (format) {
|
|
1227
|
+
case "json":
|
|
1228
|
+
return JSON.stringify(this.logs, null, 2);
|
|
1229
|
+
case "csv":
|
|
1230
|
+
const headers = ["timestamp", "level", "group", "message", "data"];
|
|
1231
|
+
const rows = this.logs.map((log) => [
|
|
1232
|
+
new Date(log.timestamp).toISOString(),
|
|
1233
|
+
log.levelName,
|
|
1234
|
+
log.group || "",
|
|
1235
|
+
log.message,
|
|
1236
|
+
JSON.stringify(log.data)
|
|
1237
|
+
]);
|
|
1238
|
+
return [headers, ...rows].map((row) => row.join(",")).join("\n");
|
|
1239
|
+
case "text":
|
|
1240
|
+
return this.logs.map((log) => {
|
|
1241
|
+
const date = new Date(log.timestamp).toISOString();
|
|
1242
|
+
const group = log.group ? `[${log.group}]` : "";
|
|
1243
|
+
return `${date} [${log.levelName}] ${group} ${log.message} ${JSON.stringify(log.data)}`;
|
|
1244
|
+
}).join("\n");
|
|
1245
|
+
default:
|
|
1246
|
+
throw new Error(`Unknown export format: ${format}`);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Create a logger with additional context
|
|
1251
|
+
*/
|
|
1252
|
+
withContext(context) {
|
|
1253
|
+
const contextLogger = new _DevLogger(this.options);
|
|
1254
|
+
contextLogger.context = { ...this.context, ...context };
|
|
1255
|
+
contextLogger.logs = this.logs;
|
|
1256
|
+
contextLogger.groups = this.groups;
|
|
1257
|
+
contextLogger.filters = this.filters;
|
|
1258
|
+
contextLogger.handlers = this.handlers;
|
|
1259
|
+
return contextLogger;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Log a table
|
|
1263
|
+
*/
|
|
1264
|
+
table(data) {
|
|
1265
|
+
if (typeof console !== "undefined" && console.table) {
|
|
1266
|
+
console.table(data);
|
|
1267
|
+
} else {
|
|
1268
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Start a timer
|
|
1273
|
+
*/
|
|
1274
|
+
time(label) {
|
|
1275
|
+
if (typeof console !== "undefined" && console.time) {
|
|
1276
|
+
console.time(label);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* End a timer
|
|
1281
|
+
*/
|
|
1282
|
+
timeEnd(label) {
|
|
1283
|
+
if (typeof console !== "undefined" && console.timeEnd) {
|
|
1284
|
+
console.timeEnd(label);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
/**
|
|
1288
|
+
* Get log buffer
|
|
1289
|
+
*/
|
|
1290
|
+
getBuffer() {
|
|
1291
|
+
return this.logs;
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Flush buffered logs
|
|
1295
|
+
*/
|
|
1296
|
+
flush() {
|
|
1297
|
+
this.logs.forEach((log) => {
|
|
1298
|
+
this.output(log);
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Clear buffer
|
|
1303
|
+
*/
|
|
1304
|
+
clearBuffer() {
|
|
1305
|
+
this.logs = [];
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Generate unique ID
|
|
1309
|
+
*/
|
|
1310
|
+
generateId() {
|
|
1311
|
+
return `log-${Date.now()}-${Math.random().toString(36).substring(7)}`;
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
function createLogger(options = {}) {
|
|
1315
|
+
return new DevLogger(options);
|
|
1316
|
+
}
|
|
1317
|
+
function createComponentLogger(componentName, options = {}) {
|
|
1318
|
+
const logger = new DevLogger({
|
|
1319
|
+
prefix: `[${componentName}]`,
|
|
1320
|
+
...options
|
|
1321
|
+
});
|
|
1322
|
+
logger.perf = (operation, duration) => {
|
|
1323
|
+
logger.info(`${operation} completed in ${duration}ms`);
|
|
1324
|
+
};
|
|
1325
|
+
logger.lifecycle = (event) => {
|
|
1326
|
+
logger.info(`Lifecycle: ${event}`);
|
|
1327
|
+
};
|
|
1328
|
+
return logger;
|
|
1329
|
+
}
|
|
1330
|
+
function createConsoleLogger(prefix = "") {
|
|
1331
|
+
return {
|
|
1332
|
+
trace: (...args) => console.debug(prefix, ...args),
|
|
1333
|
+
debug: (...args) => console.debug(prefix, ...args),
|
|
1334
|
+
info: (...args) => console.info(prefix, ...args),
|
|
1335
|
+
warn: (...args) => console.warn(prefix, ...args),
|
|
1336
|
+
error: (...args) => console.error(prefix, ...args),
|
|
1337
|
+
fatal: (...args) => console.error(prefix, "FATAL:", ...args)
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// src/dev-tools.js
|
|
1342
|
+
import { performanceMonitor } from "@coherent.js/core/src/performance/monitor.js";
|
|
1343
|
+
import { validateComponent as validateComponent2, isCoherentObject } from "@coherent.js/core/src/core/object-utils.js";
|
|
1344
|
+
var DevTools = class {
|
|
1345
|
+
constructor(coherentInstance) {
|
|
1346
|
+
this.coherent = coherentInstance;
|
|
1347
|
+
this.isEnabled = this.shouldEnable();
|
|
1348
|
+
this.renderHistory = [];
|
|
1349
|
+
this.componentRegistry = /* @__PURE__ */ new Map();
|
|
1350
|
+
this.warnings = [];
|
|
1351
|
+
this.errors = [];
|
|
1352
|
+
this.hotReloadEnabled = false;
|
|
1353
|
+
if (this.isEnabled) {
|
|
1354
|
+
this.initialize();
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* Check if dev tools should be enabled
|
|
1359
|
+
*/
|
|
1360
|
+
shouldEnable() {
|
|
1361
|
+
if (typeof process !== "undefined") {
|
|
1362
|
+
return process.env.NODE_ENV === "development";
|
|
1363
|
+
}
|
|
1364
|
+
if (typeof window !== "undefined") {
|
|
1365
|
+
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1" || window.location.search.includes("dev=true");
|
|
1366
|
+
}
|
|
1367
|
+
return false;
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Initialize dev tools
|
|
1371
|
+
*/
|
|
1372
|
+
initialize() {
|
|
1373
|
+
console.log("\u{1F6E0}\uFE0F Coherent.js Dev Tools Enabled");
|
|
1374
|
+
this.setupGlobalHelpers();
|
|
1375
|
+
this.setupRenderInterception();
|
|
1376
|
+
this.setupErrorHandling();
|
|
1377
|
+
this.setupHotReload();
|
|
1378
|
+
this.setupComponentInspector();
|
|
1379
|
+
if (typeof window !== "undefined") {
|
|
1380
|
+
this.setupBrowserDevTools();
|
|
1381
|
+
}
|
|
1382
|
+
if (typeof process !== "undefined") {
|
|
1383
|
+
this.setupNodeDevTools();
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Set up global helper functions
|
|
1388
|
+
*/
|
|
1389
|
+
setupGlobalHelpers() {
|
|
1390
|
+
const helpers = {
|
|
1391
|
+
// Inspect any component
|
|
1392
|
+
$inspect: (component) => this.inspectComponent(component),
|
|
1393
|
+
// Get render history
|
|
1394
|
+
$history: () => this.renderHistory,
|
|
1395
|
+
// Get performance stats
|
|
1396
|
+
$perf: () => this.getPerformanceInsights(),
|
|
1397
|
+
// Validate component structure
|
|
1398
|
+
$validate: (component) => this.validateComponent(component),
|
|
1399
|
+
// Get component registry
|
|
1400
|
+
$registry: () => Array.from(this.componentRegistry.entries()),
|
|
1401
|
+
// Clear dev data
|
|
1402
|
+
$clear: () => this.clearDevData(),
|
|
1403
|
+
// Enable/disable features
|
|
1404
|
+
$toggle: (feature) => this.toggleFeature(feature),
|
|
1405
|
+
// Get warnings and errors
|
|
1406
|
+
$issues: () => ({ warnings: this.warnings, errors: this.errors })
|
|
1407
|
+
};
|
|
1408
|
+
if (typeof window !== "undefined") {
|
|
1409
|
+
Object.assign(window, helpers);
|
|
1410
|
+
} else if (typeof global !== "undefined") {
|
|
1411
|
+
Object.assign(global, helpers);
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Intercept render calls for debugging
|
|
1416
|
+
*/
|
|
1417
|
+
setupRenderInterception() {
|
|
1418
|
+
const originalRender = this.coherent.render;
|
|
1419
|
+
this.coherent.render = (component, context = {}, options = {}) => {
|
|
1420
|
+
const renderStart = performance.now();
|
|
1421
|
+
const renderId = this.generateRenderId();
|
|
1422
|
+
try {
|
|
1423
|
+
this.preRenderAnalysis(component, context, renderId);
|
|
1424
|
+
const result = originalRender.call(this.coherent, component, context, {
|
|
1425
|
+
...options,
|
|
1426
|
+
_devRenderId: renderId
|
|
1427
|
+
});
|
|
1428
|
+
const renderTime = performance.now() - renderStart;
|
|
1429
|
+
this.postRenderAnalysis(component, result, renderTime, renderId);
|
|
1430
|
+
return result;
|
|
1431
|
+
} catch (_error) {
|
|
1432
|
+
this.handleRenderError(_error, component, context, renderId);
|
|
1433
|
+
throw _error;
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Pre-render analysis and validation
|
|
1439
|
+
*/
|
|
1440
|
+
preRenderAnalysis(component, context, renderId) {
|
|
1441
|
+
const validation = this.deepValidateComponent(component);
|
|
1442
|
+
if (!validation.isValid) {
|
|
1443
|
+
this.warnings.push({
|
|
1444
|
+
type: "validation",
|
|
1445
|
+
message: validation.message,
|
|
1446
|
+
component: this.serializeComponent(component),
|
|
1447
|
+
renderId,
|
|
1448
|
+
timestamp: Date.now()
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
const complexity = this.analyzeComplexity(component);
|
|
1452
|
+
if (complexity > 1e3) {
|
|
1453
|
+
this.warnings.push({
|
|
1454
|
+
type: "performance",
|
|
1455
|
+
message: `High complexity component detected (${complexity} nodes)`,
|
|
1456
|
+
renderId,
|
|
1457
|
+
timestamp: Date.now()
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
this.analyzeContext(context, renderId);
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* Post-render analysis
|
|
1464
|
+
*/
|
|
1465
|
+
postRenderAnalysis(component, result, renderTime, renderId) {
|
|
1466
|
+
const renderRecord = {
|
|
1467
|
+
id: renderId,
|
|
1468
|
+
timestamp: Date.now(),
|
|
1469
|
+
component: this.serializeComponent(component),
|
|
1470
|
+
renderTime,
|
|
1471
|
+
outputSize: result.length,
|
|
1472
|
+
complexity: this.analyzeComplexity(component)
|
|
1473
|
+
};
|
|
1474
|
+
this.renderHistory.push(renderRecord);
|
|
1475
|
+
if (this.renderHistory.length > 50) {
|
|
1476
|
+
this.renderHistory.shift();
|
|
1477
|
+
}
|
|
1478
|
+
if (renderTime > 10) {
|
|
1479
|
+
this.warnings.push({
|
|
1480
|
+
type: "performance",
|
|
1481
|
+
message: `Slow render detected: ${renderTime.toFixed(2)}ms`,
|
|
1482
|
+
renderId,
|
|
1483
|
+
timestamp: Date.now()
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
if (renderTime > 1) {
|
|
1487
|
+
console.log(`\u{1F504} Render ${renderId}: ${renderTime.toFixed(2)}ms`);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Deep component validation
|
|
1492
|
+
*/
|
|
1493
|
+
deepValidateComponent(component, path2 = "root", depth = 0) {
|
|
1494
|
+
if (depth > 100) {
|
|
1495
|
+
return {
|
|
1496
|
+
isValid: false,
|
|
1497
|
+
message: `Component nesting too deep at ${path2}`
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
try {
|
|
1501
|
+
validateComponent2(component);
|
|
1502
|
+
} catch (_error) {
|
|
1503
|
+
return {
|
|
1504
|
+
isValid: false,
|
|
1505
|
+
message: `Invalid component at ${path2}: ${_error.message}`
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
if (Array.isArray(component)) {
|
|
1509
|
+
for (let i = 0; i < component.length; i++) {
|
|
1510
|
+
const childValidation = this.deepValidateComponent(
|
|
1511
|
+
component[i],
|
|
1512
|
+
`${path2}[${i}]`,
|
|
1513
|
+
depth + 1
|
|
1514
|
+
);
|
|
1515
|
+
if (!childValidation.isValid) {
|
|
1516
|
+
return childValidation;
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
} else if (isCoherentObject(component)) {
|
|
1520
|
+
for (const [tag, props] of Object.entries(component)) {
|
|
1521
|
+
if (props && typeof props === "object" && props.children) {
|
|
1522
|
+
const childValidation = this.deepValidateComponent(
|
|
1523
|
+
props.children,
|
|
1524
|
+
`${path2}.${tag}.children`,
|
|
1525
|
+
depth + 1
|
|
1526
|
+
);
|
|
1527
|
+
if (!childValidation.isValid) {
|
|
1528
|
+
return childValidation;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
return { isValid: true };
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* Analyze component complexity
|
|
1537
|
+
*/
|
|
1538
|
+
analyzeComplexity(component, depth = 0) {
|
|
1539
|
+
if (depth > 100) return 1e3;
|
|
1540
|
+
if (typeof component === "string" || typeof component === "number") {
|
|
1541
|
+
return 1;
|
|
1542
|
+
}
|
|
1543
|
+
if (Array.isArray(component)) {
|
|
1544
|
+
return component.reduce((sum, child) => sum + this.analyzeComplexity(child, depth + 1), 0);
|
|
1545
|
+
}
|
|
1546
|
+
if (isCoherentObject(component)) {
|
|
1547
|
+
let complexity = 1;
|
|
1548
|
+
for (const [, props] of Object.entries(component)) {
|
|
1549
|
+
if (props && typeof props === "object") {
|
|
1550
|
+
if (props.children) {
|
|
1551
|
+
complexity += this.analyzeComplexity(props.children, depth + 1);
|
|
1552
|
+
}
|
|
1553
|
+
if (typeof props.text === "function") {
|
|
1554
|
+
complexity += 2;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return complexity;
|
|
1559
|
+
}
|
|
1560
|
+
return 1;
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Context analysis
|
|
1564
|
+
*/
|
|
1565
|
+
analyzeContext(context, renderId) {
|
|
1566
|
+
const contextSize = JSON.stringify(context).length;
|
|
1567
|
+
if (contextSize > 1e4) {
|
|
1568
|
+
this.warnings.push({
|
|
1569
|
+
type: "context",
|
|
1570
|
+
message: `Large context object: ${contextSize} characters`,
|
|
1571
|
+
renderId,
|
|
1572
|
+
timestamp: Date.now()
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
try {
|
|
1576
|
+
JSON.stringify(context);
|
|
1577
|
+
} catch (_error) {
|
|
1578
|
+
if (_error.message.includes("circular")) {
|
|
1579
|
+
this.warnings.push({
|
|
1580
|
+
type: "context",
|
|
1581
|
+
message: "Circular reference detected in context",
|
|
1582
|
+
renderId,
|
|
1583
|
+
timestamp: Date.now()
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Component inspector
|
|
1590
|
+
*/
|
|
1591
|
+
inspectComponent(component) {
|
|
1592
|
+
return {
|
|
1593
|
+
type: this.getComponentType(component),
|
|
1594
|
+
complexity: this.analyzeComplexity(component),
|
|
1595
|
+
validation: this.deepValidateComponent(component),
|
|
1596
|
+
structure: this.visualizeStructure(component),
|
|
1597
|
+
serialized: this.serializeComponent(component),
|
|
1598
|
+
recommendations: this.getOptimizationRecommendations(component)
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Get component type
|
|
1603
|
+
*/
|
|
1604
|
+
getComponentType(component) {
|
|
1605
|
+
if (typeof component === "string") return "text";
|
|
1606
|
+
if (typeof component === "function") return "function";
|
|
1607
|
+
if (Array.isArray(component)) return "array";
|
|
1608
|
+
if (isCoherentObject(component)) return "element";
|
|
1609
|
+
return "unknown";
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Visualize component structure
|
|
1613
|
+
*/
|
|
1614
|
+
visualizeStructure(component, depth = 0, maxDepth = 5) {
|
|
1615
|
+
if (depth > maxDepth) return "...";
|
|
1616
|
+
const indent = " ".repeat(depth);
|
|
1617
|
+
if (typeof component === "string") {
|
|
1618
|
+
return `${indent}"${component.substring(0, 20)}${component.length > 20 ? "..." : ""}"`;
|
|
1619
|
+
}
|
|
1620
|
+
if (Array.isArray(component)) {
|
|
1621
|
+
const items = component.slice(0, 3).map(
|
|
1622
|
+
(child) => this.visualizeStructure(child, depth + 1, maxDepth)
|
|
1623
|
+
);
|
|
1624
|
+
const more = component.length > 3 ? `${indent} ...${component.length - 3} more` : "";
|
|
1625
|
+
return `${indent}[
|
|
1626
|
+
${items.join("\n")}${more ? `
|
|
1627
|
+
${more}` : ""}
|
|
1628
|
+
${indent}]`;
|
|
1629
|
+
}
|
|
1630
|
+
if (isCoherentObject(component)) {
|
|
1631
|
+
const entries = Object.entries(component).slice(0, 3);
|
|
1632
|
+
const elements = entries.map(([tag, props]) => {
|
|
1633
|
+
let result = `${indent}<${tag}`;
|
|
1634
|
+
if (props && props.children) {
|
|
1635
|
+
result += `>
|
|
1636
|
+
${this.visualizeStructure(props.children, depth + 1, maxDepth)}
|
|
1637
|
+
${indent}</${tag}>`;
|
|
1638
|
+
} else if (props && props.text) {
|
|
1639
|
+
result += `>${props.text}</${tag}>`;
|
|
1640
|
+
} else {
|
|
1641
|
+
result += " />";
|
|
1642
|
+
}
|
|
1643
|
+
return result;
|
|
1644
|
+
});
|
|
1645
|
+
return elements.join("\n");
|
|
1646
|
+
}
|
|
1647
|
+
return `${indent}${typeof component}`;
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Get optimization recommendations
|
|
1651
|
+
*/
|
|
1652
|
+
getOptimizationRecommendations(component) {
|
|
1653
|
+
const recommendations = [];
|
|
1654
|
+
const complexity = this.analyzeComplexity(component);
|
|
1655
|
+
if (complexity > 500) {
|
|
1656
|
+
recommendations.push({
|
|
1657
|
+
type: "complexity",
|
|
1658
|
+
message: "Consider breaking down this component into smaller parts",
|
|
1659
|
+
priority: "high"
|
|
1660
|
+
});
|
|
1661
|
+
}
|
|
1662
|
+
const serialized = JSON.stringify(component);
|
|
1663
|
+
const patterns = this.findRepeatedPatterns(serialized);
|
|
1664
|
+
if (patterns.length > 0) {
|
|
1665
|
+
recommendations.push({
|
|
1666
|
+
type: "caching",
|
|
1667
|
+
message: "Consider extracting repeated patterns into cached components",
|
|
1668
|
+
priority: "medium",
|
|
1669
|
+
patterns: patterns.slice(0, 3)
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
return recommendations;
|
|
1673
|
+
}
|
|
1674
|
+
/**
|
|
1675
|
+
* Find repeated patterns in component
|
|
1676
|
+
*/
|
|
1677
|
+
findRepeatedPatterns(serialized) {
|
|
1678
|
+
const patterns = [];
|
|
1679
|
+
const minPatternLength = 20;
|
|
1680
|
+
for (let i = 0; i < serialized.length - minPatternLength; i++) {
|
|
1681
|
+
for (let len = minPatternLength; len <= 100 && i + len < serialized.length; len++) {
|
|
1682
|
+
const pattern = serialized.substring(i, i + len);
|
|
1683
|
+
const occurrences = (serialized.match(new RegExp(this.escapeRegex(pattern), "g")) || []).length;
|
|
1684
|
+
if (occurrences > 2) {
|
|
1685
|
+
patterns.push({ pattern: `${pattern.substring(0, 50)}...`, occurrences });
|
|
1686
|
+
break;
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
return patterns.sort((a, b) => b.occurrences - a.occurrences);
|
|
1691
|
+
}
|
|
1692
|
+
/**
|
|
1693
|
+
* Escape regex special characters
|
|
1694
|
+
*/
|
|
1695
|
+
escapeRegex(string) {
|
|
1696
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Setup _error handling
|
|
1700
|
+
*/
|
|
1701
|
+
setupErrorHandling() {
|
|
1702
|
+
const originalConsoleError = console.error;
|
|
1703
|
+
console.error = (...args) => {
|
|
1704
|
+
this.errors.push({
|
|
1705
|
+
type: "console",
|
|
1706
|
+
message: args.join(" "),
|
|
1707
|
+
timestamp: Date.now(),
|
|
1708
|
+
stack: new Error().stack
|
|
1709
|
+
});
|
|
1710
|
+
originalConsoleError.apply(console, args);
|
|
1711
|
+
};
|
|
1712
|
+
if (typeof process !== "undefined") {
|
|
1713
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
1714
|
+
this.errors.push({
|
|
1715
|
+
type: "unhandled-rejection",
|
|
1716
|
+
message: reason.toString(),
|
|
1717
|
+
promise: promise.toString(),
|
|
1718
|
+
timestamp: Date.now()
|
|
1719
|
+
});
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
if (typeof window !== "undefined") {
|
|
1723
|
+
window.addEventListener("_error", (event) => {
|
|
1724
|
+
this.errors.push({
|
|
1725
|
+
type: "browser-_error",
|
|
1726
|
+
message: event.message,
|
|
1727
|
+
filename: event.filename,
|
|
1728
|
+
lineno: event.lineno,
|
|
1729
|
+
colno: event.colno,
|
|
1730
|
+
timestamp: Date.now()
|
|
1731
|
+
});
|
|
1732
|
+
});
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* Handle render errors specifically
|
|
1737
|
+
*/
|
|
1738
|
+
handleRenderError(_error, component, context, renderId) {
|
|
1739
|
+
this.errors.push({
|
|
1740
|
+
type: "render-_error",
|
|
1741
|
+
message: _error.message,
|
|
1742
|
+
stack: _error.stack,
|
|
1743
|
+
component: this.serializeComponent(component),
|
|
1744
|
+
context: Object.keys(context),
|
|
1745
|
+
renderId,
|
|
1746
|
+
timestamp: Date.now()
|
|
1747
|
+
});
|
|
1748
|
+
console.error(`\u{1F6A8} Render Error in ${renderId}:`, _error.message);
|
|
1749
|
+
console.error("Component:", this.serializeComponent(component));
|
|
1750
|
+
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Setup hot reload capability
|
|
1753
|
+
*/
|
|
1754
|
+
setupHotReload() {
|
|
1755
|
+
if (typeof window !== "undefined" && "WebSocket" in window) {
|
|
1756
|
+
this.setupBrowserHotReload();
|
|
1757
|
+
} else if (typeof __require !== "undefined") {
|
|
1758
|
+
this.setupNodeHotReload();
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
/**
|
|
1762
|
+
* Browser hot reload setup
|
|
1763
|
+
*/
|
|
1764
|
+
setupBrowserHotReload() {
|
|
1765
|
+
try {
|
|
1766
|
+
const ws = new WebSocket("ws://localhost:3001/coherent-dev");
|
|
1767
|
+
ws.onmessage = (event) => {
|
|
1768
|
+
const data = JSON.parse(event.data);
|
|
1769
|
+
if (data.type === "component-updated") {
|
|
1770
|
+
console.log("\u{1F504} Component updated:", data.componentName);
|
|
1771
|
+
this.handleComponentUpdate(data);
|
|
1772
|
+
} else if (data.type === "full-reload") {
|
|
1773
|
+
window.location.reload();
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
ws.onopen = () => {
|
|
1777
|
+
console.log("\u{1F517} Connected to Coherent dev server");
|
|
1778
|
+
this.hotReloadEnabled = true;
|
|
1779
|
+
};
|
|
1780
|
+
ws.onclose = () => {
|
|
1781
|
+
console.log("\u{1F50C} Disconnected from dev server");
|
|
1782
|
+
this.hotReloadEnabled = false;
|
|
1783
|
+
};
|
|
1784
|
+
} catch {
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
/**
|
|
1788
|
+
* Node.js hot reload setup
|
|
1789
|
+
*/
|
|
1790
|
+
setupNodeHotReload() {
|
|
1791
|
+
try {
|
|
1792
|
+
const fs = __require("fs");
|
|
1793
|
+
const path2 = __require("path");
|
|
1794
|
+
const watchDir = path2.join(process.cwd(), "src");
|
|
1795
|
+
fs.watch(watchDir, { recursive: true }, (eventType, filename) => {
|
|
1796
|
+
if (filename && filename.endsWith(".js")) {
|
|
1797
|
+
console.log(`\u{1F504} File changed: ${filename}`);
|
|
1798
|
+
this.handleFileChange(filename, eventType);
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
this.hotReloadEnabled = true;
|
|
1802
|
+
} catch {
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Handle component updates
|
|
1807
|
+
*/
|
|
1808
|
+
handleComponentUpdate(updateData) {
|
|
1809
|
+
if (this.coherent.cache) {
|
|
1810
|
+
this.coherent.cache.invalidatePattern(updateData.componentName);
|
|
1811
|
+
}
|
|
1812
|
+
this.componentRegistry.set(updateData.componentName, {
|
|
1813
|
+
...updateData,
|
|
1814
|
+
lastUpdated: Date.now()
|
|
1815
|
+
});
|
|
1816
|
+
if (typeof window !== "undefined" && window.location.search.includes("auto-reload=true")) {
|
|
1817
|
+
window.location.reload();
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
/**
|
|
1821
|
+
* Handle file changes
|
|
1822
|
+
*/
|
|
1823
|
+
handleFileChange(filename, eventType) {
|
|
1824
|
+
if (typeof __require !== "undefined" && __require.cache) {
|
|
1825
|
+
const fullPath = __require.resolve(path.resolve(filename));
|
|
1826
|
+
delete __require.cache[fullPath];
|
|
1827
|
+
}
|
|
1828
|
+
console.log(`\u{1F4DD} ${eventType}: ${filename}`);
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Setup browser-specific dev tools
|
|
1832
|
+
*/
|
|
1833
|
+
setupBrowserDevTools() {
|
|
1834
|
+
this.createDevPanel();
|
|
1835
|
+
document.addEventListener("keydown", (e) => {
|
|
1836
|
+
if (e.ctrlKey && e.shiftKey && e.code === "KeyC") {
|
|
1837
|
+
this.toggleDevPanel();
|
|
1838
|
+
e.preventDefault();
|
|
1839
|
+
}
|
|
1840
|
+
if (e.ctrlKey && e.shiftKey && e.code === "KeyP") {
|
|
1841
|
+
console.table(this.getPerformanceInsights());
|
|
1842
|
+
e.preventDefault();
|
|
1843
|
+
}
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Create development panel in browser
|
|
1848
|
+
*/
|
|
1849
|
+
createDevPanel() {
|
|
1850
|
+
const panel = document.createElement("div");
|
|
1851
|
+
panel.id = "coherent-dev-panel";
|
|
1852
|
+
panel.style.cssText = `
|
|
1853
|
+
position: fixed;
|
|
1854
|
+
top: 10px;
|
|
1855
|
+
right: 10px;
|
|
1856
|
+
width: 300px;
|
|
1857
|
+
background: #1a1a1a;
|
|
1858
|
+
color: #fff;
|
|
1859
|
+
font-family: monospace;
|
|
1860
|
+
font-size: 12px;
|
|
1861
|
+
border-radius: 8px;
|
|
1862
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
|
|
1863
|
+
z-index: 999999;
|
|
1864
|
+
display: none;
|
|
1865
|
+
max-height: 80vh;
|
|
1866
|
+
overflow-y: auto;
|
|
1867
|
+
`;
|
|
1868
|
+
document.body.appendChild(panel);
|
|
1869
|
+
this.devPanel = panel;
|
|
1870
|
+
this.updateDevPanel();
|
|
1871
|
+
}
|
|
1872
|
+
/**
|
|
1873
|
+
* Toggle dev panel visibility
|
|
1874
|
+
*/
|
|
1875
|
+
toggleDevPanel() {
|
|
1876
|
+
if (this.devPanel) {
|
|
1877
|
+
const isVisible = this.devPanel.style.display === "block";
|
|
1878
|
+
this.devPanel.style.display = isVisible ? "none" : "block";
|
|
1879
|
+
if (!isVisible) {
|
|
1880
|
+
this.updateDevPanel();
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
/**
|
|
1885
|
+
* Update dev panel content
|
|
1886
|
+
*/
|
|
1887
|
+
updateDevPanel() {
|
|
1888
|
+
if (!this.devPanel) return;
|
|
1889
|
+
const stats = this.getPerformanceInsights();
|
|
1890
|
+
const recentRenders = this.renderHistory.slice(-5);
|
|
1891
|
+
const recentWarnings = this.warnings.slice(-3);
|
|
1892
|
+
this.devPanel.innerHTML = `
|
|
1893
|
+
<div style="padding: 15px; border-bottom: 1px solid #333;">
|
|
1894
|
+
<strong>\u{1F6E0}\uFE0F Coherent.js Dev Tools</strong>
|
|
1895
|
+
<button onclick="this.parentElement.parentElement.style.display='none'"
|
|
1896
|
+
style="float: right; background: none; border: none; color: #fff; cursor: pointer;">\xD7</button>
|
|
1897
|
+
</div>
|
|
1898
|
+
|
|
1899
|
+
<div style="padding: 10px;">
|
|
1900
|
+
<h4 style="margin: 0 0 10px 0; color: #4CAF50;">Performance</h4>
|
|
1901
|
+
<div style="font-size: 11px;">
|
|
1902
|
+
<div>Avg Render: ${stats.averageRenderTime || 0}ms</div>
|
|
1903
|
+
<div>Cache Hit Rate: ${((stats.cacheHits || 0) / Math.max(stats.totalRenders || 1, 1) * 100).toFixed(1)}%</div>
|
|
1904
|
+
<div>Memory Usage: ${(performance.memory?.usedJSHeapSize / 1024 / 1024 || 0).toFixed(1)}MB</div>
|
|
1905
|
+
</div>
|
|
1906
|
+
</div>
|
|
1907
|
+
|
|
1908
|
+
<div style="padding: 10px;">
|
|
1909
|
+
<h4 style="margin: 0 0 10px 0; color: #2196F3;">Recent Renders</h4>
|
|
1910
|
+
${recentRenders.map((r) => `
|
|
1911
|
+
<div style="font-size: 10px; margin-bottom: 5px; padding: 3px; background: #333; border-radius: 3px;">
|
|
1912
|
+
${r.id}: ${r.renderTime.toFixed(1)}ms (${r.complexity} nodes)
|
|
1913
|
+
</div>
|
|
1914
|
+
`).join("")}
|
|
1915
|
+
</div>
|
|
1916
|
+
|
|
1917
|
+
${recentWarnings.length > 0 ? `
|
|
1918
|
+
<div style="padding: 10px;">
|
|
1919
|
+
<h4 style="margin: 0 0 10px 0; color: #FF9800;">Warnings</h4>
|
|
1920
|
+
${recentWarnings.map((w) => `
|
|
1921
|
+
<div style="font-size: 10px; margin-bottom: 5px; padding: 3px; background: #4a2c0a; border-radius: 3px; color: #FFB74D;">
|
|
1922
|
+
${w.type}: ${w.message}
|
|
1923
|
+
</div>
|
|
1924
|
+
`).join("")}
|
|
1925
|
+
</div>
|
|
1926
|
+
` : ""}
|
|
1927
|
+
|
|
1928
|
+
<div style="padding: 10px; font-size: 10px; color: #888;">
|
|
1929
|
+
Press Ctrl+Shift+P for performance details
|
|
1930
|
+
</div>
|
|
1931
|
+
`;
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Setup Node.js specific dev tools
|
|
1935
|
+
*/
|
|
1936
|
+
setupNodeDevTools() {
|
|
1937
|
+
process.on("SIGINT", () => {
|
|
1938
|
+
this.printDevSummary();
|
|
1939
|
+
process.exit();
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
/**
|
|
1943
|
+
* Print development summary
|
|
1944
|
+
*/
|
|
1945
|
+
printDevSummary() {
|
|
1946
|
+
console.log("\n\u{1F6E0}\uFE0F Coherent.js Development Summary");
|
|
1947
|
+
console.log("=================================");
|
|
1948
|
+
console.log(`Total Renders: ${this.renderHistory.length}`);
|
|
1949
|
+
console.log(`Total Warnings: ${this.warnings.length}`);
|
|
1950
|
+
console.log(`Total Errors: ${this.errors.length}`);
|
|
1951
|
+
if (this.renderHistory.length > 0) {
|
|
1952
|
+
const avgTime = this.renderHistory.reduce((sum, r) => sum + r.renderTime, 0) / this.renderHistory.length;
|
|
1953
|
+
console.log(`Average Render Time: ${avgTime.toFixed(2)}ms`);
|
|
1954
|
+
}
|
|
1955
|
+
console.log("=================================\n");
|
|
1956
|
+
}
|
|
1957
|
+
/**
|
|
1958
|
+
* Get performance insights
|
|
1959
|
+
*/
|
|
1960
|
+
getPerformanceInsights() {
|
|
1961
|
+
const insights = {
|
|
1962
|
+
totalRenders: this.renderHistory.length,
|
|
1963
|
+
averageRenderTime: 0,
|
|
1964
|
+
slowestRender: null,
|
|
1965
|
+
fastestRender: null,
|
|
1966
|
+
cacheHits: 0,
|
|
1967
|
+
totalWarnings: this.warnings.length,
|
|
1968
|
+
totalErrors: this.errors.length
|
|
1969
|
+
};
|
|
1970
|
+
if (this.renderHistory.length > 0) {
|
|
1971
|
+
const times = this.renderHistory.map((r) => r.renderTime);
|
|
1972
|
+
insights.averageRenderTime = times.reduce((a, b) => a + b, 0) / times.length;
|
|
1973
|
+
insights.slowestRender = Math.max(...times);
|
|
1974
|
+
insights.fastestRender = Math.min(...times);
|
|
1975
|
+
}
|
|
1976
|
+
if (this.coherent.cache && this.coherent.cache.getStats) {
|
|
1977
|
+
const cacheStats = this.coherent.cache.getStats();
|
|
1978
|
+
insights.cacheHits = cacheStats.hits;
|
|
1979
|
+
insights.cacheHitRate = cacheStats.hitRate;
|
|
1980
|
+
}
|
|
1981
|
+
if (performanceMonitor && performanceMonitor.getStats) {
|
|
1982
|
+
const perfStats = performanceMonitor.getStats();
|
|
1983
|
+
insights.performanceMonitorStats = perfStats;
|
|
1984
|
+
}
|
|
1985
|
+
return insights;
|
|
1986
|
+
}
|
|
1987
|
+
/**
|
|
1988
|
+
* Utility methods
|
|
1989
|
+
*/
|
|
1990
|
+
generateRenderId() {
|
|
1991
|
+
return `render_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
|
|
1992
|
+
}
|
|
1993
|
+
serializeComponent(component, maxDepth = 3, currentDepth = 0) {
|
|
1994
|
+
if (currentDepth > maxDepth) return "...";
|
|
1995
|
+
try {
|
|
1996
|
+
if (typeof component === "function") {
|
|
1997
|
+
return `[Function: ${component.name || "anonymous"}]`;
|
|
1998
|
+
}
|
|
1999
|
+
if (Array.isArray(component)) {
|
|
2000
|
+
return component.slice(0, 3).map(
|
|
2001
|
+
(c) => this.serializeComponent(c, maxDepth, currentDepth + 1)
|
|
2002
|
+
).concat(component.length > 3 ? [`...(${component.length - 3} more)`] : []);
|
|
2003
|
+
}
|
|
2004
|
+
if (component && typeof component === "object") {
|
|
2005
|
+
const serialized = {};
|
|
2006
|
+
const keys = Object.keys(component).slice(0, 10);
|
|
2007
|
+
for (const key of keys) {
|
|
2008
|
+
if (key === "children" && component[key]) {
|
|
2009
|
+
serialized[key] = this.serializeComponent(component[key], maxDepth, currentDepth + 1);
|
|
2010
|
+
} else if (typeof component[key] === "function") {
|
|
2011
|
+
serialized[key] = `[Function: ${component[key].name || "anonymous"}]`;
|
|
2012
|
+
} else {
|
|
2013
|
+
serialized[key] = component[key];
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
if (Object.keys(component).length > 10) {
|
|
2017
|
+
serialized["..."] = `(${Object.keys(component).length - 10} more properties)`;
|
|
2018
|
+
}
|
|
2019
|
+
return serialized;
|
|
2020
|
+
}
|
|
2021
|
+
return component;
|
|
2022
|
+
} catch (_error) {
|
|
2023
|
+
return `[Serialization Error: ${_error.message}]`;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
clearDevData() {
|
|
2027
|
+
this.renderHistory = [];
|
|
2028
|
+
this.warnings = [];
|
|
2029
|
+
this.errors = [];
|
|
2030
|
+
this.componentRegistry.clear();
|
|
2031
|
+
console.log("\u{1F9F9} Dev data cleared");
|
|
2032
|
+
}
|
|
2033
|
+
toggleFeature(feature) {
|
|
2034
|
+
switch (feature) {
|
|
2035
|
+
case "cache":
|
|
2036
|
+
if (this.coherent.cache) {
|
|
2037
|
+
this.coherent.cache.enabled = !this.coherent.cache.enabled;
|
|
2038
|
+
console.log(`Cache ${this.coherent.cache.enabled ? "enabled" : "disabled"}`);
|
|
2039
|
+
}
|
|
2040
|
+
break;
|
|
2041
|
+
case "monitoring":
|
|
2042
|
+
if (performanceMonitor) {
|
|
2043
|
+
performanceMonitor.enabled = !performanceMonitor.enabled;
|
|
2044
|
+
console.log(`Monitoring ${performanceMonitor.enabled ? "enabled" : "disabled"}`);
|
|
2045
|
+
}
|
|
2046
|
+
break;
|
|
2047
|
+
case "hot-reload":
|
|
2048
|
+
this.hotReloadEnabled = !this.hotReloadEnabled;
|
|
2049
|
+
console.log(`Hot reload ${this.hotReloadEnabled ? "enabled" : "disabled"}`);
|
|
2050
|
+
break;
|
|
2051
|
+
default:
|
|
2052
|
+
console.log(`Unknown feature: ${feature}`);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
validateComponent(component) {
|
|
2056
|
+
return this.deepValidateComponent(component);
|
|
2057
|
+
}
|
|
2058
|
+
setupComponentInspector() {
|
|
2059
|
+
const originalCreateComponent = this.coherent.createComponent;
|
|
2060
|
+
if (originalCreateComponent) {
|
|
2061
|
+
this.coherent.createComponent = (config) => {
|
|
2062
|
+
const component = originalCreateComponent.call(this.coherent, config);
|
|
2063
|
+
this.componentRegistry.set(config.name || "anonymous", {
|
|
2064
|
+
config,
|
|
2065
|
+
component,
|
|
2066
|
+
registeredAt: Date.now()
|
|
2067
|
+
});
|
|
2068
|
+
return component;
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
function createDevTools(coherentInstance) {
|
|
2074
|
+
return new DevTools(coherentInstance);
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
// src/index.js
|
|
2078
|
+
var index_default = {
|
|
2079
|
+
// Inspector
|
|
2080
|
+
ComponentInspector,
|
|
2081
|
+
createInspector,
|
|
2082
|
+
inspect,
|
|
2083
|
+
validateComponent,
|
|
2084
|
+
// Profiler
|
|
2085
|
+
PerformanceProfiler,
|
|
2086
|
+
createProfiler,
|
|
2087
|
+
measure,
|
|
2088
|
+
profile,
|
|
2089
|
+
// Logger
|
|
2090
|
+
DevLogger,
|
|
2091
|
+
LogLevel,
|
|
2092
|
+
createLogger,
|
|
2093
|
+
createComponentLogger,
|
|
2094
|
+
createConsoleLogger,
|
|
2095
|
+
// DevTools
|
|
2096
|
+
DevTools,
|
|
2097
|
+
createDevTools
|
|
2098
|
+
};
|
|
2099
|
+
export {
|
|
2100
|
+
ComponentInspector,
|
|
2101
|
+
DevLogger,
|
|
2102
|
+
DevTools,
|
|
2103
|
+
LogLevel,
|
|
2104
|
+
PerformanceProfiler,
|
|
2105
|
+
createComponentLogger,
|
|
2106
|
+
createConsoleLogger,
|
|
2107
|
+
createDevTools,
|
|
2108
|
+
createInspector,
|
|
2109
|
+
createLogger,
|
|
2110
|
+
createProfiler,
|
|
2111
|
+
index_default as default,
|
|
2112
|
+
inspect,
|
|
2113
|
+
measure,
|
|
2114
|
+
profile,
|
|
2115
|
+
validateComponent
|
|
2116
|
+
};
|
|
2117
|
+
//# sourceMappingURL=index.js.map
|