@uistate/core 4.0.0 → 4.0.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uistate/core",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "type": "module",
5
5
  "description": "High-performance UI state management using CSS custom properties and ADSI (Attribute-Driven State Inheritance)",
6
6
  "main": "src/index.js",
@@ -1,140 +0,0 @@
1
- /**
2
- * StateInspector - Real-time state inspection panel for UIstate
3
- *
4
- * This module provides a configurable UI panel that displays and allows manipulation
5
- * of CSS variables and state values in UIstate applications.
6
- *
7
- * Features:
8
- * - Toggle visibility with a dedicated button
9
- * - Real-time updates of CSS variable values
10
- * - Organized display of state by categories
11
- * - Ability to filter state variables
12
- * - Direct manipulation of state values
13
- */
14
-
15
- /**
16
- * Create a configured state inspector instance
17
- * @param {Object} config - Configuration options
18
- * @returns {Object} - StateInspector instance
19
- */
20
- function createStateInspector(config = {}) {
21
- // Default configuration
22
- const defaultConfig = {
23
- target: document.body,
24
- initiallyVisible: false,
25
- categories: ['app', 'ui', 'data'],
26
- position: 'bottom-right',
27
- maxHeight: '300px',
28
- stateManager: null, // Optional reference to UIstate or CssState instance
29
- theme: 'light'
30
- };
31
-
32
- // Merge provided config with defaults
33
- const options = { ...defaultConfig, ...config };
34
-
35
- // Private state
36
- let isVisible = options.initiallyVisible;
37
- let panel = null;
38
- let toggleButton = null;
39
- let filterInput = null;
40
- let stateContainer = null;
41
- let currentFilter = '';
42
-
43
- // StateInspector instance
44
- const inspector = {
45
- // Current configuration
46
- config: options,
47
-
48
- /**
49
- * Update configuration
50
- * @param {Object} newConfig - New configuration options
51
- */
52
- configure(newConfig) {
53
- Object.assign(this.config, newConfig);
54
- this.refresh();
55
- },
56
-
57
- /**
58
- * Attach the inspector panel to the target element
59
- * @param {Element} element - Target element to attach to (defaults to config.target)
60
- * @returns {Object} - The inspector instance for chaining
61
- */
62
- attach(element = this.config.target) {
63
- // Implementation will create and attach the panel
64
- // For now, this is a placeholder
65
- console.log('StateInspector: Panel would be attached to', element);
66
- return this;
67
- },
68
-
69
- /**
70
- * Show the inspector panel
71
- * @returns {Object} - The inspector instance for chaining
72
- */
73
- show() {
74
- isVisible = true;
75
- if (panel) panel.style.display = 'block';
76
- return this;
77
- },
78
-
79
- /**
80
- * Hide the inspector panel
81
- * @returns {Object} - The inspector instance for chaining
82
- */
83
- hide() {
84
- isVisible = false;
85
- if (panel) panel.style.display = 'none';
86
- return this;
87
- },
88
-
89
- /**
90
- * Toggle the visibility of the inspector panel
91
- * @returns {Object} - The inspector instance for chaining
92
- */
93
- toggle() {
94
- return isVisible ? this.hide() : this.show();
95
- },
96
-
97
- /**
98
- * Refresh the state display
99
- * @returns {Object} - The inspector instance for chaining
100
- */
101
- refresh() {
102
- // Implementation will update the displayed state values
103
- // For now, this is a placeholder
104
- console.log('StateInspector: State display would be refreshed');
105
- return this;
106
- },
107
-
108
- /**
109
- * Filter the displayed state variables
110
- * @param {string} filterText - Text to filter by
111
- * @returns {Object} - The inspector instance for chaining
112
- */
113
- filter(filterText) {
114
- currentFilter = filterText;
115
- // Implementation will filter the displayed variables
116
- return this.refresh();
117
- },
118
-
119
- /**
120
- * Clean up resources used by the inspector
121
- */
122
- destroy() {
123
- if (panel && panel.parentNode) {
124
- panel.parentNode.removeChild(panel);
125
- }
126
- panel = null;
127
- toggleButton = null;
128
- filterInput = null;
129
- stateContainer = null;
130
- }
131
- };
132
-
133
- return inspector;
134
- }
135
-
136
- // Create a default instance
137
- const StateInspector = createStateInspector();
138
-
139
- export default StateInspector;
140
- export { createStateInspector };
@@ -1,638 +0,0 @@
1
- /**
2
- * UIstate Telemetry Plugin
3
- *
4
- * A plugin for tracking and analyzing usage patterns in UIstate applications.
5
- * Provides insights into method calls, state changes, and performance metrics.
6
- *
7
- * Features:
8
- * - Automatic tracking of API method calls
9
- * - State change monitoring
10
- * - Performance timing
11
- * - Usage statistics and reporting
12
- * - Visual dashboard integration
13
- */
14
-
15
- /**
16
- * Create a telemetry plugin instance
17
- * @param {Object} config - Configuration options
18
- * @returns {Object} - Configured telemetry plugin
19
- */
20
- const createTelemetryPlugin = (config = {}) => {
21
- // Default configuration
22
- const defaultConfig = {
23
- enabled: true,
24
- trackStateChanges: true,
25
- trackMethodCalls: true,
26
- trackPerformance: true,
27
- maxEntries: 1000,
28
- logToConsole: false,
29
- samplingRate: 1.0, // 1.0 = track everything, 0.5 = track 50% of events
30
- };
31
-
32
- // Merge provided config with defaults
33
- const options = { ...defaultConfig, ...config };
34
-
35
- // Telemetry data storage
36
- const data = {
37
- startTime: Date.now(),
38
- methodCalls: new Map(),
39
- stateChanges: new Map(),
40
- performanceMetrics: new Map(),
41
- unusedMethods: new Set(),
42
- activeTimers: new Map()
43
- };
44
-
45
- // Reference to the UIstate instance
46
- let uistateRef = null;
47
-
48
- // Original method references
49
- const originalMethods = new Map();
50
-
51
- // Methods to track (will be populated during initialization)
52
- const methodsToTrack = [
53
- 'getState', 'setState', 'subscribe', 'observe',
54
- 'configureSerializer', 'getSerializerConfig', 'setSerializationMode',
55
- 'onAction', 'registerActions', 'attachDelegation', 'mount',
56
- 'renderTemplateFromCss', 'createComponent', 'applyClassesFromState'
57
- ];
58
-
59
- // Helper function to check if we should sample this event
60
- const shouldSample = () => {
61
- return options.samplingRate >= 1.0 || Math.random() <= options.samplingRate;
62
- };
63
-
64
- // Helper function to track a method call
65
- const trackMethodCall = (methodName, args = [], result = undefined, error = null, duration = 0) => {
66
- if (!options.enabled || !options.trackMethodCalls || !shouldSample()) return;
67
-
68
- if (!data.methodCalls.has(methodName)) {
69
- data.methodCalls.set(methodName, []);
70
- }
71
-
72
- const calls = data.methodCalls.get(methodName);
73
-
74
- // Limit the number of entries to prevent memory issues
75
- if (calls.length >= options.maxEntries) {
76
- calls.shift(); // Remove oldest entry
77
- }
78
-
79
- // Create a safe copy of arguments (avoiding circular references)
80
- const safeArgs = Array.from(args).map(arg => {
81
- try {
82
- // For simple types, return as is
83
- if (arg === null || arg === undefined ||
84
- typeof arg === 'string' ||
85
- typeof arg === 'number' ||
86
- typeof arg === 'boolean') {
87
- return arg;
88
- }
89
-
90
- // For functions, just return the function name or "[Function]"
91
- if (typeof arg === 'function') {
92
- return arg.name || "[Function]";
93
- }
94
-
95
- // For objects, create a simplified representation
96
- if (typeof arg === 'object') {
97
- if (Array.isArray(arg)) {
98
- return `[Array(${arg.length})]`;
99
- }
100
- return Object.keys(arg).length > 0
101
- ? `[Object: ${Object.keys(arg).join(', ')}]`
102
- : '[Object]';
103
- }
104
-
105
- return String(arg);
106
- } catch (e) {
107
- return "[Complex Value]";
108
- }
109
- });
110
-
111
- // Create a safe copy of the result
112
- let safeResult;
113
- try {
114
- if (result === null || result === undefined ||
115
- typeof result === 'string' ||
116
- typeof result === 'number' ||
117
- typeof result === 'boolean') {
118
- safeResult = result;
119
- } else if (typeof result === 'function') {
120
- safeResult = result.name || "[Function]";
121
- } else if (typeof result === 'object') {
122
- if (Array.isArray(result)) {
123
- safeResult = `[Array(${result.length})]`;
124
- } else {
125
- safeResult = Object.keys(result).length > 0
126
- ? `[Object: ${Object.keys(result).join(', ')}]`
127
- : '[Object]';
128
- }
129
- } else {
130
- safeResult = String(result);
131
- }
132
- } catch (e) {
133
- safeResult = "[Complex Value]";
134
- }
135
-
136
- // Record the call
137
- calls.push({
138
- timestamp: Date.now(),
139
- relativeTime: Date.now() - data.startTime,
140
- args: safeArgs,
141
- result: safeResult,
142
- error: error ? error.message : null,
143
- duration
144
- });
145
-
146
- // Log to console if enabled
147
- if (options.logToConsole) {
148
- console.log(`[Telemetry] ${methodName}`, {
149
- args: safeArgs,
150
- result: safeResult,
151
- error: error ? error.message : null,
152
- duration
153
- });
154
- }
155
- };
156
-
157
- // Helper function to track state changes
158
- const trackStateChange = (path, value, previousValue) => {
159
- if (!options.enabled || !options.trackStateChanges || !shouldSample()) return;
160
-
161
- if (!data.stateChanges.has(path)) {
162
- data.stateChanges.set(path, []);
163
- }
164
-
165
- const changes = data.stateChanges.get(path);
166
-
167
- // Limit the number of entries to prevent memory issues
168
- if (changes.length >= options.maxEntries) {
169
- changes.shift(); // Remove oldest entry
170
- }
171
-
172
- // Create safe copies of values
173
- let safeValue, safePreviousValue;
174
- try {
175
- safeValue = JSON.stringify(value);
176
- } catch (e) {
177
- safeValue = String(value);
178
- }
179
-
180
- try {
181
- safePreviousValue = JSON.stringify(previousValue);
182
- } catch (e) {
183
- safePreviousValue = String(previousValue);
184
- }
185
-
186
- // Record the change
187
- changes.push({
188
- timestamp: Date.now(),
189
- relativeTime: Date.now() - data.startTime,
190
- value: safeValue,
191
- previousValue: safePreviousValue
192
- });
193
- };
194
-
195
- // Helper function to start a performance timer
196
- const startTimer = (label) => {
197
- if (!options.enabled || !options.trackPerformance) return;
198
-
199
- data.activeTimers.set(label, performance.now());
200
- };
201
-
202
- // Helper function to end a performance timer
203
- const endTimer = (label) => {
204
- if (!options.enabled || !options.trackPerformance) return 0;
205
-
206
- if (!data.activeTimers.has(label)) return 0;
207
-
208
- const startTime = data.activeTimers.get(label);
209
- const duration = performance.now() - startTime;
210
-
211
- data.activeTimers.delete(label);
212
-
213
- if (!data.performanceMetrics.has(label)) {
214
- data.performanceMetrics.set(label, {
215
- count: 0,
216
- totalDuration: 0,
217
- minDuration: Infinity,
218
- maxDuration: 0,
219
- samples: []
220
- });
221
- }
222
-
223
- const metrics = data.performanceMetrics.get(label);
224
- metrics.count++;
225
- metrics.totalDuration += duration;
226
- metrics.minDuration = Math.min(metrics.minDuration, duration);
227
- metrics.maxDuration = Math.max(metrics.maxDuration, duration);
228
-
229
- // Store sample data (limited to prevent memory issues)
230
- if (metrics.samples.length >= options.maxEntries) {
231
- metrics.samples.shift();
232
- }
233
-
234
- metrics.samples.push({
235
- timestamp: Date.now(),
236
- relativeTime: Date.now() - data.startTime,
237
- duration
238
- });
239
-
240
- return duration;
241
- };
242
-
243
- // Method proxying function
244
- const createMethodProxy = (obj, methodName) => {
245
- // Store original method
246
- const originalMethod = obj[methodName];
247
- originalMethods.set(methodName, originalMethod);
248
-
249
- // Create proxy
250
- return function(...args) {
251
- if (!options.enabled) {
252
- return originalMethod.apply(this, args);
253
- }
254
-
255
- let result, error;
256
-
257
- startTimer(`method.${methodName}`);
258
-
259
- try {
260
- // Call original method
261
- result = originalMethod.apply(this, args);
262
-
263
- // Handle promises
264
- if (result instanceof Promise) {
265
- return result
266
- .then(asyncResult => {
267
- const duration = endTimer(`method.${methodName}`);
268
- trackMethodCall(methodName, args, asyncResult, null, duration);
269
- return asyncResult;
270
- })
271
- .catch(asyncError => {
272
- const duration = endTimer(`method.${methodName}`);
273
- trackMethodCall(methodName, args, undefined, asyncError, duration);
274
- throw asyncError;
275
- });
276
- }
277
-
278
- const duration = endTimer(`method.${methodName}`);
279
- trackMethodCall(methodName, args, result, null, duration);
280
- return result;
281
-
282
- } catch (e) {
283
- error = e;
284
- const duration = endTimer(`method.${methodName}`);
285
- trackMethodCall(methodName, args, undefined, error, duration);
286
- throw error;
287
- }
288
- };
289
- };
290
-
291
- // Create the telemetry plugin
292
- const telemetryPlugin = {
293
- name: 'telemetry',
294
-
295
- // Initialize the plugin
296
- init(api) {
297
- if (!options.enabled) return this;
298
-
299
- uistateRef = api;
300
-
301
- // Proxy methods for tracking
302
- if (options.trackMethodCalls) {
303
- methodsToTrack.forEach(methodName => {
304
- if (typeof api[methodName] === 'function') {
305
- // Add to list of methods to check for usage
306
- data.unusedMethods.add(methodName);
307
-
308
- // Create proxy
309
- api[methodName] = createMethodProxy(api, methodName);
310
- }
311
- });
312
- }
313
-
314
- return this;
315
- },
316
-
317
- // Middleware for state changes
318
- middlewares: [
319
- (path, value, getState) => {
320
- if (options.enabled && options.trackStateChanges) {
321
- const previousValue = getState(path);
322
- startTimer(`stateChange.${path}`);
323
-
324
- // Return a proxy that will track after the state change
325
- return {
326
- __telemetryValue: true,
327
- value,
328
- path,
329
- previousValue,
330
- finalize() {
331
- const duration = endTimer(`stateChange.${path}`);
332
- trackStateChange(path, value, previousValue);
333
- return value;
334
- }
335
- };
336
- }
337
- return value;
338
- }
339
- ],
340
-
341
- // Lifecycle hooks
342
- hooks: {
343
- beforeStateChange: (path, value) => {
344
- // If this is our proxy value, do nothing
345
- if (value && value.__telemetryValue) {
346
- return;
347
- }
348
- },
349
-
350
- afterStateChange: (path, value) => {
351
- // If this is our proxy value, finalize it
352
- if (value && value.__telemetryValue) {
353
- return value.finalize();
354
- }
355
- }
356
- },
357
-
358
- // Plugin methods
359
- methods: {
360
- // Enable or disable telemetry
361
- setEnabled(enabled) {
362
- options.enabled = enabled;
363
- return this;
364
- },
365
-
366
- // Get current configuration
367
- getConfig() {
368
- return { ...options };
369
- },
370
-
371
- // Update configuration
372
- configure(newConfig) {
373
- Object.assign(options, newConfig);
374
- return this;
375
- },
376
-
377
- // Reset telemetry data
378
- reset() {
379
- data.startTime = Date.now();
380
- data.methodCalls.clear();
381
- data.stateChanges.clear();
382
- data.performanceMetrics.clear();
383
- data.activeTimers.clear();
384
- return this;
385
- },
386
-
387
- // Get method call statistics
388
- getMethodStats() {
389
- const stats = {};
390
-
391
- data.methodCalls.forEach((calls, methodName) => {
392
- // Mark method as used
393
- data.unusedMethods.delete(methodName);
394
-
395
- stats[methodName] = {
396
- count: calls.length,
397
- averageDuration: calls.reduce((sum, call) => sum + (call.duration || 0), 0) / calls.length,
398
- lastCall: calls[calls.length - 1],
399
- firstCall: calls[0]
400
- };
401
- });
402
-
403
- return stats;
404
- },
405
-
406
- // Get detailed method call data
407
- getMethodCalls(methodName) {
408
- if (!methodName) {
409
- const result = {};
410
- data.methodCalls.forEach((calls, method) => {
411
- result[method] = [...calls];
412
- });
413
- return result;
414
- }
415
-
416
- return data.methodCalls.has(methodName)
417
- ? [...data.methodCalls.get(methodName)]
418
- : [];
419
- },
420
-
421
- // Get state change statistics
422
- getStateStats() {
423
- const stats = {};
424
-
425
- data.stateChanges.forEach((changes, path) => {
426
- stats[path] = {
427
- count: changes.length,
428
- lastChange: changes[changes.length - 1],
429
- firstChange: changes[0]
430
- };
431
- });
432
-
433
- return stats;
434
- },
435
-
436
- // Get detailed state change data
437
- getStateChanges(path) {
438
- if (!path) {
439
- const result = {};
440
- data.stateChanges.forEach((changes, statePath) => {
441
- result[statePath] = [...changes];
442
- });
443
- return result;
444
- }
445
-
446
- return data.stateChanges.has(path)
447
- ? [...data.stateChanges.get(path)]
448
- : [];
449
- },
450
-
451
- // Get performance metrics
452
- getPerformanceMetrics() {
453
- const metrics = {};
454
-
455
- data.performanceMetrics.forEach((data, label) => {
456
- metrics[label] = {
457
- count: data.count,
458
- totalDuration: data.totalDuration,
459
- averageDuration: data.totalDuration / data.count,
460
- minDuration: data.minDuration,
461
- maxDuration: data.maxDuration
462
- };
463
- });
464
-
465
- return metrics;
466
- },
467
-
468
- // Get unused methods
469
- getUnusedMethods() {
470
- return [...data.unusedMethods];
471
- },
472
-
473
- // Get comprehensive telemetry report
474
- getReport() {
475
- return {
476
- startTime: data.startTime,
477
- duration: Date.now() - data.startTime,
478
- methodStats: this.getMethodStats(),
479
- stateStats: this.getStateStats(),
480
- performanceMetrics: this.getPerformanceMetrics(),
481
- unusedMethods: this.getUnusedMethods(),
482
- config: this.getConfig(),
483
- // Add detailed logs for download
484
- detailedLogs: {
485
- methodCalls: this.getMethodCalls(),
486
- stateChanges: this.getStateChanges()
487
- }
488
- };
489
- },
490
-
491
- // Download telemetry logs as a JSON file
492
- downloadLogs() {
493
- const report = this.getReport();
494
- const data = JSON.stringify(report, null, 2);
495
- const blob = new Blob([data], { type: 'application/json' });
496
- const url = URL.createObjectURL(blob);
497
-
498
- const a = document.createElement('a');
499
- a.href = url;
500
- a.download = `uistate-telemetry-${new Date().toISOString().replace(/:/g, '-')}.json`;
501
- document.body.appendChild(a);
502
- a.click();
503
- document.body.removeChild(a);
504
- URL.revokeObjectURL(url);
505
-
506
- return true;
507
- },
508
-
509
- // Create a visual dashboard
510
- createDashboard(container = document.body) {
511
- // Create dashboard element
512
- const dashboard = document.createElement('div');
513
- dashboard.className = 'uistate-telemetry-dashboard';
514
- dashboard.style.cssText = `
515
- position: fixed;
516
- bottom: 10px;
517
- right: 10px;
518
- width: 300px;
519
- max-height: 400px;
520
- overflow: auto;
521
- background: #fff;
522
- border: 1px solid #ccc;
523
- border-radius: 4px;
524
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
525
- padding: 10px;
526
- font-family: monospace;
527
- font-size: 12px;
528
- z-index: 9999;
529
- `;
530
-
531
- // Update function
532
- const updateDashboard = () => {
533
- const report = this.getReport();
534
-
535
- dashboard.innerHTML = `
536
- <h3 style="margin: 0 0 10px; font-size: 14px;">UIstate Telemetry</h3>
537
- <div style="margin-bottom: 5px;">
538
- <button id="telemetry-refresh">Refresh</button>
539
- <button id="telemetry-reset">Reset</button>
540
- <button id="telemetry-close">Close</button>
541
- </div>
542
- <div style="margin: 10px 0;">
543
- <strong>Duration:</strong> ${Math.round((Date.now() - report.startTime) / 1000)}s
544
- </div>
545
-
546
- <h4 style="margin: 10px 0 5px; font-size: 13px;">Method Calls</h4>
547
- <table style="width: 100%; border-collapse: collapse;">
548
- <tr>
549
- <th style="text-align: left; border-bottom: 1px solid #eee;">Method</th>
550
- <th style="text-align: right; border-bottom: 1px solid #eee;">Count</th>
551
- <th style="text-align: right; border-bottom: 1px solid #eee;">Avg Time</th>
552
- </tr>
553
- ${Object.entries(report.methodStats)
554
- .sort((a, b) => b[1].count - a[1].count)
555
- .map(([method, stats]) => `
556
- <tr>
557
- <td style="padding: 2px 0; border-bottom: 1px solid #eee;">${method}</td>
558
- <td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.count}</td>
559
- <td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.averageDuration ? stats.averageDuration.toFixed(2) + 'ms' : 'n/a'}</td>
560
- </tr>
561
- `).join('')}
562
- </table>
563
-
564
- <h4 style="margin: 10px 0 5px; font-size: 13px;">State Changes</h4>
565
- <table style="width: 100%; border-collapse: collapse;">
566
- <tr>
567
- <th style="text-align: left; border-bottom: 1px solid #eee;">Path</th>
568
- <th style="text-align: right; border-bottom: 1px solid #eee;">Count</th>
569
- </tr>
570
- ${Object.entries(report.stateStats)
571
- .sort((a, b) => b[1].count - a[1].count)
572
- .map(([path, stats]) => `
573
- <tr>
574
- <td style="padding: 2px 0; border-bottom: 1px solid #eee;">${path}</td>
575
- <td style="text-align: right; padding: 2px 0; border-bottom: 1px solid #eee;">${stats.count}</td>
576
- </tr>
577
- `).join('')}
578
- </table>
579
-
580
- ${report.unusedMethods.length > 0 ? `
581
- <h4 style="margin: 10px 0 5px; font-size: 13px;">Unused Methods</h4>
582
- <div style="color: #666;">
583
- ${report.unusedMethods.join(', ')}
584
- </div>
585
- ` : ''}
586
- `;
587
-
588
- // Add event listeners
589
- dashboard.querySelector('#telemetry-refresh').addEventListener('click', updateDashboard);
590
- dashboard.querySelector('#telemetry-reset').addEventListener('click', () => {
591
- this.reset();
592
- updateDashboard();
593
- });
594
- dashboard.querySelector('#telemetry-close').addEventListener('click', () => {
595
- dashboard.remove();
596
- });
597
- };
598
-
599
- // Initial update
600
- updateDashboard();
601
-
602
- // Add to container
603
- container.appendChild(dashboard);
604
-
605
- return dashboard;
606
- }
607
- },
608
-
609
- // Clean up
610
- destroy() {
611
- // Restore original methods
612
- if (uistateRef) {
613
- originalMethods.forEach((originalMethod, methodName) => {
614
- if (uistateRef[methodName]) {
615
- uistateRef[methodName] = originalMethod;
616
- }
617
- });
618
- }
619
-
620
- // Clear data
621
- data.methodCalls.clear();
622
- data.stateChanges.clear();
623
- data.performanceMetrics.clear();
624
- data.activeTimers.clear();
625
- data.unusedMethods.clear();
626
-
627
- uistateRef = null;
628
- }
629
- };
630
-
631
- return telemetryPlugin;
632
- };
633
-
634
- // Create a default instance with standard configuration
635
- const telemetryPlugin = createTelemetryPlugin();
636
-
637
- export default telemetryPlugin;
638
- export { createTelemetryPlugin };