wu-framework 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +559 -0
- package/package.json +84 -0
- package/src/api/wu-simple.js +316 -0
- package/src/core/wu-app.js +192 -0
- package/src/core/wu-cache.js +374 -0
- package/src/core/wu-core.js +1296 -0
- package/src/core/wu-error-boundary.js +380 -0
- package/src/core/wu-event-bus.js +257 -0
- package/src/core/wu-hooks.js +348 -0
- package/src/core/wu-html-parser.js +280 -0
- package/src/core/wu-loader.js +271 -0
- package/src/core/wu-logger.js +119 -0
- package/src/core/wu-manifest.js +366 -0
- package/src/core/wu-performance.js +226 -0
- package/src/core/wu-plugin.js +213 -0
- package/src/core/wu-proxy-sandbox.js +153 -0
- package/src/core/wu-registry.js +130 -0
- package/src/core/wu-sandbox-pool.js +390 -0
- package/src/core/wu-sandbox.js +720 -0
- package/src/core/wu-script-executor.js +216 -0
- package/src/core/wu-snapshot-sandbox.js +184 -0
- package/src/core/wu-store.js +297 -0
- package/src/core/wu-strategies.js +241 -0
- package/src/core/wu-style-bridge.js +357 -0
- package/src/index.js +690 -0
- package/src/utils/dependency-resolver.js +326 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 📜 WU-SCRIPT-EXECUTOR: Sistema de ejecución segura de scripts
|
|
3
|
+
* Basado en video-code - Ejecuta scripts en contexto sandbox aislado
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class WuScriptExecutor {
|
|
7
|
+
constructor() {
|
|
8
|
+
console.log('[WuScriptExecutor] 📜 Script execution system initialized');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Ejecutar script usando Function constructor (más seguro que eval)
|
|
13
|
+
* @param {string} script - Código JavaScript a ejecutar
|
|
14
|
+
* @param {string} appName - Nombre de la app
|
|
15
|
+
* @param {Proxy} globalProxy - Proxy sandbox como window
|
|
16
|
+
* @returns {*} Resultado de la ejecución
|
|
17
|
+
*/
|
|
18
|
+
executeWithFunction(script, appName, globalProxy) {
|
|
19
|
+
console.log(`[WuScriptExecutor] 🚀 Executing script for ${appName} using Function`);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// 🌟 Almacenar proxy globalmente de forma temporal
|
|
23
|
+
window.__WU_PROXY_TEMP__ = globalProxy;
|
|
24
|
+
|
|
25
|
+
// 🎯 Crear función que recibe window como parámetro
|
|
26
|
+
const scriptText = `
|
|
27
|
+
return ((window) => {
|
|
28
|
+
${script}
|
|
29
|
+
return window['${appName}'];
|
|
30
|
+
})(window.__WU_PROXY_TEMP__);
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
// 🚀 Ejecutar con Function (más seguro que eval)
|
|
34
|
+
const fn = new Function(scriptText);
|
|
35
|
+
const result = fn();
|
|
36
|
+
|
|
37
|
+
// 🧹 Limpiar proxy temporal
|
|
38
|
+
delete window.__WU_PROXY_TEMP__;
|
|
39
|
+
|
|
40
|
+
console.log(`[WuScriptExecutor] ✅ Script executed successfully for ${appName}`);
|
|
41
|
+
return result;
|
|
42
|
+
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// 🧹 Limpiar en caso de error
|
|
45
|
+
delete window.__WU_PROXY_TEMP__;
|
|
46
|
+
|
|
47
|
+
console.error(`[WuScriptExecutor] ❌ Script execution failed for ${appName}:`, error);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Ejecutar script usando eval (fallback)
|
|
54
|
+
* @param {string} script - Código JavaScript a ejecutar
|
|
55
|
+
* @param {string} appName - Nombre de la app
|
|
56
|
+
* @param {Proxy} globalProxy - Proxy sandbox como window
|
|
57
|
+
* @returns {*} Resultado de la ejecución
|
|
58
|
+
*/
|
|
59
|
+
executeWithEval(script, appName, globalProxy) {
|
|
60
|
+
console.log(`[WuScriptExecutor] 🚀 Executing script for ${appName} using eval`);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// 🌟 Almacenar proxy globalmente de forma temporal
|
|
64
|
+
window.__WU_PROXY_TEMP__ = globalProxy;
|
|
65
|
+
|
|
66
|
+
// 🎯 IIFE que recibe window como parámetro
|
|
67
|
+
const scriptText = `
|
|
68
|
+
((window) => {
|
|
69
|
+
${script}
|
|
70
|
+
return window['${appName}'];
|
|
71
|
+
})(window.__WU_PROXY_TEMP__);
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
// ⚠️ Usar eval (menos seguro pero compatible)
|
|
75
|
+
const result = eval(scriptText);
|
|
76
|
+
|
|
77
|
+
// 🧹 Limpiar proxy temporal
|
|
78
|
+
delete window.__WU_PROXY_TEMP__;
|
|
79
|
+
|
|
80
|
+
console.log(`[WuScriptExecutor] ✅ Script executed successfully for ${appName}`);
|
|
81
|
+
return result;
|
|
82
|
+
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// 🧹 Limpiar en caso de error
|
|
85
|
+
delete window.__WU_PROXY_TEMP__;
|
|
86
|
+
|
|
87
|
+
console.error(`[WuScriptExecutor] ❌ Script execution failed for ${appName}:`, error);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Ejecutar script con estrategia automática (intenta Function primero, luego eval)
|
|
94
|
+
* @param {string} script - Código JavaScript a ejecutar
|
|
95
|
+
* @param {string} appName - Nombre de la app
|
|
96
|
+
* @param {Proxy} globalProxy - Proxy sandbox como window
|
|
97
|
+
* @param {string} strategy - 'function' | 'eval' | 'auto'
|
|
98
|
+
* @returns {*} Resultado de la ejecución
|
|
99
|
+
*/
|
|
100
|
+
execute(script, appName, globalProxy, strategy = 'function') {
|
|
101
|
+
if (strategy === 'eval') {
|
|
102
|
+
return this.executeWithEval(script, appName, globalProxy);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (strategy === 'function' || strategy === 'auto') {
|
|
106
|
+
try {
|
|
107
|
+
return this.executeWithFunction(script, appName, globalProxy);
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (strategy === 'auto') {
|
|
110
|
+
console.warn(`[WuScriptExecutor] ⚠️ Function failed, falling back to eval`);
|
|
111
|
+
return this.executeWithEval(script, appName, globalProxy);
|
|
112
|
+
}
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
throw new Error(`Unknown execution strategy: ${strategy}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Ejecutar múltiples scripts en secuencia
|
|
122
|
+
* @param {Array<string>} scripts - Array de scripts
|
|
123
|
+
* @param {string} appName - Nombre de la app
|
|
124
|
+
* @param {Proxy} globalProxy - Proxy sandbox
|
|
125
|
+
* @param {string} strategy - Estrategia de ejecución
|
|
126
|
+
* @returns {Array<*>} Resultados de ejecución
|
|
127
|
+
*/
|
|
128
|
+
async executeMultiple(scripts, appName, globalProxy, strategy = 'function') {
|
|
129
|
+
console.log(`[WuScriptExecutor] 📚 Executing ${scripts.length} scripts for ${appName}`);
|
|
130
|
+
|
|
131
|
+
const results = [];
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < scripts.length; i++) {
|
|
134
|
+
const script = scripts[i];
|
|
135
|
+
console.log(`[WuScriptExecutor] 📜 Executing script ${i + 1}/${scripts.length}`);
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const result = this.execute(script, appName, globalProxy, strategy);
|
|
139
|
+
results.push({ success: true, result });
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error(`[WuScriptExecutor] ❌ Script ${i + 1} failed:`, error);
|
|
142
|
+
results.push({ success: false, error });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const successCount = results.filter(r => r.success).length;
|
|
147
|
+
console.log(`[WuScriptExecutor] ✅ Executed ${successCount}/${scripts.length} scripts successfully`);
|
|
148
|
+
|
|
149
|
+
return results;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Validar script antes de ejecutar
|
|
154
|
+
* @param {string} script - Código a validar
|
|
155
|
+
* @returns {boolean} True si el script parece válido
|
|
156
|
+
*/
|
|
157
|
+
validateScript(script) {
|
|
158
|
+
if (!script || typeof script !== 'string') {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (script.trim().length === 0) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Detectar código potencialmente peligroso
|
|
167
|
+
const dangerousPatterns = [
|
|
168
|
+
/__proto__/,
|
|
169
|
+
/constructor.*prototype/,
|
|
170
|
+
/document\.write/,
|
|
171
|
+
/eval\(/,
|
|
172
|
+
/new\s+Function\(/
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
for (const pattern of dangerousPatterns) {
|
|
176
|
+
if (pattern.test(script)) {
|
|
177
|
+
console.warn(`[WuScriptExecutor] ⚠️ Potentially dangerous code detected`);
|
|
178
|
+
// No bloquear, solo advertir
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Obtener estadísticas del executor
|
|
187
|
+
*/
|
|
188
|
+
getStats() {
|
|
189
|
+
return {
|
|
190
|
+
executor: 'WuScriptExecutor',
|
|
191
|
+
capabilities: ['function', 'eval', 'auto'],
|
|
192
|
+
defaultStrategy: 'function',
|
|
193
|
+
validation: true
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 🎯 EXPORTS DE CONVENIENCIA
|
|
200
|
+
*/
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Ejecutar script de forma segura
|
|
204
|
+
*/
|
|
205
|
+
export function executeScript(script, appName, globalProxy, strategy = 'function') {
|
|
206
|
+
const executor = new WuScriptExecutor();
|
|
207
|
+
return executor.execute(script, appName, globalProxy, strategy);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Ejecutar múltiples scripts
|
|
212
|
+
*/
|
|
213
|
+
export async function executeScripts(scripts, appName, globalProxy, strategy = 'function') {
|
|
214
|
+
const executor = new WuScriptExecutor();
|
|
215
|
+
return await executor.executeMultiple(scripts, appName, globalProxy, strategy);
|
|
216
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🛡️ WU-SNAPSHOT-SANDBOX: JavaScript Isolation con Snapshots
|
|
3
|
+
* Basado en video-code - Fallback para navegadores sin Proxy
|
|
4
|
+
*
|
|
5
|
+
* Este sandbox toma "fotos" del estado de window antes de montar
|
|
6
|
+
* y restaura el estado original al desmontar
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export class WuSnapshotSandbox {
|
|
10
|
+
constructor(appName) {
|
|
11
|
+
this.appName = appName;
|
|
12
|
+
this.proxy = window; // En snapshot mode, proxy es window directamente
|
|
13
|
+
this.snapshot = new Map();
|
|
14
|
+
this.modifiedKeys = new Set();
|
|
15
|
+
this.active = false;
|
|
16
|
+
|
|
17
|
+
console.log(`[WuSnapshotSandbox] 📸 Creating snapshot sandbox for: ${appName}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Activar sandbox - Captura snapshot del window
|
|
22
|
+
*/
|
|
23
|
+
activate() {
|
|
24
|
+
if (this.active) {
|
|
25
|
+
console.warn(`[WuSnapshotSandbox] Sandbox already active for ${this.appName}`);
|
|
26
|
+
return this.proxy;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log(`[WuSnapshotSandbox] 📸 Taking window snapshot for ${this.appName}...`);
|
|
30
|
+
|
|
31
|
+
// 📸 Capturar estado actual de window
|
|
32
|
+
this.snapshot.clear();
|
|
33
|
+
this.modifiedKeys.clear();
|
|
34
|
+
|
|
35
|
+
// Iterar sobre todas las propiedades de window
|
|
36
|
+
for (const key in window) {
|
|
37
|
+
try {
|
|
38
|
+
// Guardar el valor actual
|
|
39
|
+
this.snapshot.set(key, window[key]);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
// Algunas propiedades pueden ser inaccesibles
|
|
42
|
+
console.warn(`[WuSnapshotSandbox] ⚠️ Could not snapshot property: ${key}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.active = true;
|
|
47
|
+
console.log(`[WuSnapshotSandbox] ✅ Snapshot captured with ${this.snapshot.size} properties`);
|
|
48
|
+
|
|
49
|
+
return this.proxy;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Desactivar sandbox - Restaura el snapshot original
|
|
54
|
+
*/
|
|
55
|
+
deactivate() {
|
|
56
|
+
if (!this.active) {
|
|
57
|
+
console.warn(`[WuSnapshotSandbox] Sandbox not active for ${this.appName}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log(`[WuSnapshotSandbox] 🔄 Restoring window snapshot for ${this.appName}...`);
|
|
62
|
+
|
|
63
|
+
let restoredCount = 0;
|
|
64
|
+
let deletedCount = 0;
|
|
65
|
+
|
|
66
|
+
// 🔄 Detectar y restaurar cambios
|
|
67
|
+
for (const key in window) {
|
|
68
|
+
try {
|
|
69
|
+
const currentValue = window[key];
|
|
70
|
+
const originalValue = this.snapshot.get(key);
|
|
71
|
+
|
|
72
|
+
// Si la propiedad cambió, restaurarla
|
|
73
|
+
if (currentValue !== originalValue) {
|
|
74
|
+
if (this.snapshot.has(key)) {
|
|
75
|
+
window[key] = originalValue;
|
|
76
|
+
restoredCount++;
|
|
77
|
+
} else {
|
|
78
|
+
// Nueva propiedad agregada por la app, eliminarla
|
|
79
|
+
try {
|
|
80
|
+
delete window[key];
|
|
81
|
+
deletedCount++;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// Algunas propiedades no se pueden eliminar
|
|
84
|
+
console.warn(`[WuSnapshotSandbox] ⚠️ Could not delete: ${key}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn(`[WuSnapshotSandbox] ⚠️ Could not restore property: ${key}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 🧹 Limpiar estado
|
|
94
|
+
this.snapshot.clear();
|
|
95
|
+
this.modifiedKeys.clear();
|
|
96
|
+
this.active = false;
|
|
97
|
+
|
|
98
|
+
console.log(`[WuSnapshotSandbox] ✅ Snapshot restored - ${restoredCount} restored, ${deletedCount} deleted`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Obtener el proxy (en este caso, window)
|
|
103
|
+
*/
|
|
104
|
+
getProxy() {
|
|
105
|
+
return this.active ? this.proxy : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Verificar si el sandbox está activo
|
|
110
|
+
*/
|
|
111
|
+
isActive() {
|
|
112
|
+
return this.active;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Obtener estadísticas del sandbox
|
|
117
|
+
*/
|
|
118
|
+
getStats() {
|
|
119
|
+
const modifiedProps = [];
|
|
120
|
+
|
|
121
|
+
if (this.active) {
|
|
122
|
+
// Detectar qué propiedades han cambiado
|
|
123
|
+
for (const key in window) {
|
|
124
|
+
try {
|
|
125
|
+
const currentValue = window[key];
|
|
126
|
+
const originalValue = this.snapshot.get(key);
|
|
127
|
+
|
|
128
|
+
if (currentValue !== originalValue) {
|
|
129
|
+
modifiedProps.push(key);
|
|
130
|
+
}
|
|
131
|
+
} catch (error) {
|
|
132
|
+
// Ignorar errores de acceso
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
appName: this.appName,
|
|
139
|
+
active: this.active,
|
|
140
|
+
snapshotSize: this.snapshot.size,
|
|
141
|
+
modifiedProperties: modifiedProps,
|
|
142
|
+
modifiedCount: modifiedProps.length
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Verificar si una propiedad fue modificada
|
|
148
|
+
*/
|
|
149
|
+
isPropertyModified(prop) {
|
|
150
|
+
if (!this.active) return false;
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const currentValue = window[prop];
|
|
154
|
+
const originalValue = this.snapshot.get(prop);
|
|
155
|
+
return currentValue !== originalValue;
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Obtener todas las propiedades modificadas
|
|
163
|
+
*/
|
|
164
|
+
getModifiedProperties() {
|
|
165
|
+
const modified = {};
|
|
166
|
+
|
|
167
|
+
if (this.active) {
|
|
168
|
+
for (const key in window) {
|
|
169
|
+
try {
|
|
170
|
+
if (this.isPropertyModified(key)) {
|
|
171
|
+
modified[key] = {
|
|
172
|
+
original: this.snapshot.get(key),
|
|
173
|
+
current: window[key]
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
// Ignorar errores de acceso
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return modified;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🚀 WU-STORE: Ultra-High Performance State Management
|
|
3
|
+
*
|
|
4
|
+
* Basado en patrones de Disruptor y Vert.x
|
|
5
|
+
* - Ring Buffer para zero-allocation
|
|
6
|
+
* - Lock-free operations
|
|
7
|
+
* - Event Bus para pub/sub
|
|
8
|
+
* - API minimalista: get(), set(), on()
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export class WuStore {
|
|
12
|
+
constructor(bufferSize = 256) {
|
|
13
|
+
// Ring Buffer configuration
|
|
14
|
+
this.bufferSize = this.nextPowerOfTwo(bufferSize);
|
|
15
|
+
this.mask = this.bufferSize - 1;
|
|
16
|
+
this.buffer = new Array(this.bufferSize);
|
|
17
|
+
this.cursor = 0;
|
|
18
|
+
|
|
19
|
+
// State storage
|
|
20
|
+
this.state = {};
|
|
21
|
+
|
|
22
|
+
// Event listeners map: path -> Set of callbacks
|
|
23
|
+
this.listeners = new Map();
|
|
24
|
+
|
|
25
|
+
// Pattern listeners for wildcards
|
|
26
|
+
this.patternListeners = new Map();
|
|
27
|
+
|
|
28
|
+
// Performance metrics
|
|
29
|
+
this.metrics = {
|
|
30
|
+
reads: 0,
|
|
31
|
+
writes: 0,
|
|
32
|
+
notifications: 0
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Initialize ring buffer slots
|
|
36
|
+
for (let i = 0; i < this.bufferSize; i++) {
|
|
37
|
+
this.buffer[i] = { path: null, value: null, timestamp: 0 };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// No global pollution - proper library architecture
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get value from store
|
|
45
|
+
* @param {string} path - Dot notation path (e.g., 'user.name')
|
|
46
|
+
* @returns {*} Value at path or entire state if no path
|
|
47
|
+
*/
|
|
48
|
+
get(path) {
|
|
49
|
+
this.metrics.reads++;
|
|
50
|
+
|
|
51
|
+
if (!path) return this.state;
|
|
52
|
+
|
|
53
|
+
// Fast path resolution with reduce
|
|
54
|
+
return path.split('.').reduce((obj, key) => obj?.[key], this.state);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set value in store with Ring Buffer
|
|
59
|
+
* @param {string} path - Dot notation path
|
|
60
|
+
* @param {*} value - Value to set
|
|
61
|
+
* @returns {number} Sequence number
|
|
62
|
+
*/
|
|
63
|
+
set(path, value) {
|
|
64
|
+
this.metrics.writes++;
|
|
65
|
+
|
|
66
|
+
// Write to ring buffer (lock-free)
|
|
67
|
+
const sequence = this.cursor++;
|
|
68
|
+
const index = sequence & this.mask;
|
|
69
|
+
|
|
70
|
+
// Reuse buffer slot (zero allocation)
|
|
71
|
+
const event = this.buffer[index];
|
|
72
|
+
event.path = path;
|
|
73
|
+
event.value = value;
|
|
74
|
+
event.timestamp = performance.now();
|
|
75
|
+
|
|
76
|
+
// Update state synchronously
|
|
77
|
+
this.updateState(path, value);
|
|
78
|
+
|
|
79
|
+
// Schedule async notifications (non-blocking)
|
|
80
|
+
queueMicrotask(() => {
|
|
81
|
+
this.notify(path, value);
|
|
82
|
+
this.notifyPatterns(path, value);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return sequence;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Subscribe to state changes
|
|
90
|
+
* @param {string} pattern - Path or pattern (supports * wildcard)
|
|
91
|
+
* @param {Function} callback - Callback function
|
|
92
|
+
* @returns {Function} Unsubscribe function
|
|
93
|
+
*/
|
|
94
|
+
on(pattern, callback) {
|
|
95
|
+
// Check if pattern contains wildcards
|
|
96
|
+
if (pattern.includes('*')) {
|
|
97
|
+
// Pattern subscription
|
|
98
|
+
if (!this.patternListeners.has(pattern)) {
|
|
99
|
+
this.patternListeners.set(pattern, new Set());
|
|
100
|
+
}
|
|
101
|
+
this.patternListeners.get(pattern).add(callback);
|
|
102
|
+
|
|
103
|
+
// Return unsubscribe function
|
|
104
|
+
return () => {
|
|
105
|
+
const listeners = this.patternListeners.get(pattern);
|
|
106
|
+
if (listeners) {
|
|
107
|
+
listeners.delete(callback);
|
|
108
|
+
if (listeners.size === 0) {
|
|
109
|
+
this.patternListeners.delete(pattern);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
} else {
|
|
114
|
+
// Direct path subscription
|
|
115
|
+
if (!this.listeners.has(pattern)) {
|
|
116
|
+
this.listeners.set(pattern, new Set());
|
|
117
|
+
}
|
|
118
|
+
this.listeners.get(pattern).add(callback);
|
|
119
|
+
|
|
120
|
+
// Return unsubscribe function
|
|
121
|
+
return () => {
|
|
122
|
+
const listeners = this.listeners.get(pattern);
|
|
123
|
+
if (listeners) {
|
|
124
|
+
listeners.delete(callback);
|
|
125
|
+
if (listeners.size === 0) {
|
|
126
|
+
this.listeners.delete(pattern);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Batch set multiple values
|
|
135
|
+
* @param {Object} updates - Object with path:value pairs
|
|
136
|
+
*/
|
|
137
|
+
batch(updates) {
|
|
138
|
+
const sequences = [];
|
|
139
|
+
|
|
140
|
+
for (const [path, value] of Object.entries(updates)) {
|
|
141
|
+
sequences.push(this.set(path, value));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return sequences;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get current metrics
|
|
149
|
+
* @returns {Object} Performance metrics
|
|
150
|
+
*/
|
|
151
|
+
getMetrics() {
|
|
152
|
+
return {
|
|
153
|
+
...this.metrics,
|
|
154
|
+
bufferUtilization: (this.cursor % this.bufferSize) / this.bufferSize,
|
|
155
|
+
listenerCount: this.listeners.size + this.patternListeners.size
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Private methods
|
|
160
|
+
|
|
161
|
+
nextPowerOfTwo(n) {
|
|
162
|
+
return Math.pow(2, Math.ceil(Math.log2(n)));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
updateState(path, value) {
|
|
166
|
+
if (!path) {
|
|
167
|
+
this.state = value;
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const keys = path.split('.');
|
|
172
|
+
const last = keys.pop();
|
|
173
|
+
|
|
174
|
+
// Create nested structure if needed
|
|
175
|
+
let target = this.state;
|
|
176
|
+
for (const key of keys) {
|
|
177
|
+
if (!(key in target) || typeof target[key] !== 'object') {
|
|
178
|
+
target[key] = {};
|
|
179
|
+
}
|
|
180
|
+
target = target[key];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
target[last] = value;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
notify(path, value) {
|
|
187
|
+
this.metrics.notifications++;
|
|
188
|
+
|
|
189
|
+
// Notify exact path listeners
|
|
190
|
+
const exactListeners = this.listeners.get(path);
|
|
191
|
+
if (exactListeners) {
|
|
192
|
+
exactListeners.forEach(callback => {
|
|
193
|
+
try {
|
|
194
|
+
callback(value, path);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error('[WuStore] Listener error:', error);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Notify parent path listeners
|
|
202
|
+
const parts = path.split('.');
|
|
203
|
+
for (let i = parts.length - 1; i > 0; i--) {
|
|
204
|
+
const parentPath = parts.slice(0, i).join('.');
|
|
205
|
+
const parentListeners = this.listeners.get(parentPath);
|
|
206
|
+
if (parentListeners) {
|
|
207
|
+
const parentValue = this.get(parentPath);
|
|
208
|
+
parentListeners.forEach(callback => {
|
|
209
|
+
try {
|
|
210
|
+
callback(parentValue, parentPath);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('[WuStore] Parent listener error:', error);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
notifyPatterns(path, value) {
|
|
220
|
+
// Check all pattern listeners
|
|
221
|
+
for (const [pattern, listeners] of this.patternListeners) {
|
|
222
|
+
if (this.matchesPattern(path, pattern)) {
|
|
223
|
+
listeners.forEach(callback => {
|
|
224
|
+
try {
|
|
225
|
+
callback({ path, value });
|
|
226
|
+
} catch (error) {
|
|
227
|
+
console.error('[WuStore] Pattern listener error:', error);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
matchesPattern(path, pattern) {
|
|
235
|
+
// Convert pattern to regex
|
|
236
|
+
// user.* matches user.name, user.email, etc.
|
|
237
|
+
// *.name matches user.name, post.name, etc.
|
|
238
|
+
// * matches everything
|
|
239
|
+
|
|
240
|
+
if (pattern === '*') return true;
|
|
241
|
+
|
|
242
|
+
const regexPattern = pattern
|
|
243
|
+
.split('.')
|
|
244
|
+
.map(part => part === '*' ? '[^.]+' : part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
245
|
+
.join('\\.');
|
|
246
|
+
|
|
247
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
248
|
+
return regex.test(path);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Clear all state and listeners
|
|
253
|
+
*/
|
|
254
|
+
clear() {
|
|
255
|
+
this.state = {};
|
|
256
|
+
this.listeners.clear();
|
|
257
|
+
this.patternListeners.clear();
|
|
258
|
+
this.cursor = 0;
|
|
259
|
+
|
|
260
|
+
// Clear buffer
|
|
261
|
+
for (let i = 0; i < this.bufferSize; i++) {
|
|
262
|
+
this.buffer[i].path = null;
|
|
263
|
+
this.buffer[i].value = null;
|
|
264
|
+
this.buffer[i].timestamp = 0;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Get recent events from ring buffer
|
|
270
|
+
* @param {number} count - Number of recent events
|
|
271
|
+
* @returns {Array} Recent events
|
|
272
|
+
*/
|
|
273
|
+
getRecentEvents(count = 10) {
|
|
274
|
+
const events = [];
|
|
275
|
+
const start = Math.max(0, this.cursor - count);
|
|
276
|
+
|
|
277
|
+
for (let i = start; i < this.cursor && events.length < count; i++) {
|
|
278
|
+
const index = i & this.mask;
|
|
279
|
+
const event = this.buffer[index];
|
|
280
|
+
if (event.path) {
|
|
281
|
+
events.push({
|
|
282
|
+
path: event.path,
|
|
283
|
+
value: event.value,
|
|
284
|
+
timestamp: event.timestamp
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return events.reverse();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Create singleton instance
|
|
294
|
+
const store = new WuStore();
|
|
295
|
+
|
|
296
|
+
// Export both class and instance
|
|
297
|
+
export default store;
|