onelaraveljs 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/README.md +87 -0
- package/docs/integration_analysis.md +116 -0
- package/docs/onejs_analysis.md +108 -0
- package/docs/optimization_implementation_group2.md +458 -0
- package/docs/optimization_plan.md +130 -0
- package/index.js +16 -0
- package/package.json +13 -0
- package/src/app.js +61 -0
- package/src/core/API.js +72 -0
- package/src/core/ChildrenRegistry.js +410 -0
- package/src/core/DOMBatcher.js +207 -0
- package/src/core/ErrorBoundary.js +226 -0
- package/src/core/EventDelegator.js +416 -0
- package/src/core/Helper.js +817 -0
- package/src/core/LoopContext.js +97 -0
- package/src/core/OneDOM.js +246 -0
- package/src/core/OneMarkup.js +444 -0
- package/src/core/Router.js +996 -0
- package/src/core/SEOConfig.js +321 -0
- package/src/core/SectionEngine.js +75 -0
- package/src/core/TemplateEngine.js +83 -0
- package/src/core/View.js +273 -0
- package/src/core/ViewConfig.js +229 -0
- package/src/core/ViewController.js +1410 -0
- package/src/core/ViewControllerOptimized.js +164 -0
- package/src/core/ViewIdentifier.js +361 -0
- package/src/core/ViewLoader.js +272 -0
- package/src/core/ViewManager.js +1962 -0
- package/src/core/ViewState.js +761 -0
- package/src/core/ViewSystem.js +301 -0
- package/src/core/ViewTemplate.js +4 -0
- package/src/core/helpers/BindingHelper.js +239 -0
- package/src/core/helpers/ConfigHelper.js +37 -0
- package/src/core/helpers/EventHelper.js +172 -0
- package/src/core/helpers/LifecycleHelper.js +17 -0
- package/src/core/helpers/ReactiveHelper.js +169 -0
- package/src/core/helpers/RenderHelper.js +15 -0
- package/src/core/helpers/ResourceHelper.js +89 -0
- package/src/core/helpers/TemplateHelper.js +11 -0
- package/src/core/managers/BindingManager.js +671 -0
- package/src/core/managers/ConfigurationManager.js +136 -0
- package/src/core/managers/EventManager.js +309 -0
- package/src/core/managers/LifecycleManager.js +356 -0
- package/src/core/managers/ReactiveManager.js +334 -0
- package/src/core/managers/RenderEngine.js +292 -0
- package/src/core/managers/ResourceManager.js +441 -0
- package/src/core/managers/ViewHierarchyManager.js +258 -0
- package/src/core/managers/ViewTemplateManager.js +127 -0
- package/src/core/reactive/ReactiveComponent.js +592 -0
- package/src/core/services/EventService.js +418 -0
- package/src/core/services/HttpService.js +106 -0
- package/src/core/services/LoggerService.js +57 -0
- package/src/core/services/StateService.js +512 -0
- package/src/core/services/StorageService.js +856 -0
- package/src/core/services/StoreService.js +258 -0
- package/src/core/services/TemplateDetectorService.js +361 -0
- package/src/core/services/Test.js +18 -0
- package/src/helpers/devWarnings.js +205 -0
- package/src/helpers/performance.js +226 -0
- package/src/helpers/utils.js +287 -0
- package/src/init.js +343 -0
- package/src/plugins/auto-plugin.js +34 -0
- package/src/services/Test.js +18 -0
- package/src/types/index.js +193 -0
- package/src/utils/date-helper.js +51 -0
- package/src/utils/helpers.js +39 -0
- package/src/utils/validation.js +32 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import logger from "./LoggerService";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* StateService - Service quản lý state toàn cục với khả năng theo dõi thay đổi
|
|
5
|
+
* Hỗ trợ chia sẻ dữ liệu giữa các view và component
|
|
6
|
+
*/
|
|
7
|
+
export class StateService {
|
|
8
|
+
static instance = null;
|
|
9
|
+
static privateProperties = [
|
|
10
|
+
'__state', '__listeners', '__middlewares', '__isUpdating', 'instance',
|
|
11
|
+
'get', 'set', 'delete', 'has', 'clear', 'subscribe', 'unsubscribe',
|
|
12
|
+
'addMiddleware', 'removeMiddleware'
|
|
13
|
+
];
|
|
14
|
+
dynamicProperties = [];
|
|
15
|
+
|
|
16
|
+
constructor(initialState = {}) {
|
|
17
|
+
this.__state = { ...initialState };
|
|
18
|
+
this.__listeners = new Map(); // key -> Set of callbacks
|
|
19
|
+
this.__middlewares = []; // Middleware functions
|
|
20
|
+
this.__isUpdating = false;
|
|
21
|
+
|
|
22
|
+
logger.log('🏪 StateService: Initialized with state:', this.__state);
|
|
23
|
+
this.__init();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ==========================================
|
|
27
|
+
// PUBLIC STATIC METHODS
|
|
28
|
+
// ==========================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get the singleton instance of StateService
|
|
32
|
+
* @param {object} initialState - Initial state (only used on first call)
|
|
33
|
+
* @returns {StateService} - The singleton instance
|
|
34
|
+
*/
|
|
35
|
+
static getInstance(initialState = {}) {
|
|
36
|
+
if (!StateService.instance) {
|
|
37
|
+
StateService.instance = new StateService(initialState);
|
|
38
|
+
}
|
|
39
|
+
return StateService.instance;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
__init() {
|
|
43
|
+
Object.keys(this.__state).forEach(key => {
|
|
44
|
+
this.__addDynamicProperty(key);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ==========================================
|
|
50
|
+
// PUBLIC INSTANCE METHODS - Core Operations
|
|
51
|
+
// ==========================================
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get state value by key
|
|
55
|
+
* @param {string} key - The key to get
|
|
56
|
+
* @param {any} defaultValue - Default value if key not found
|
|
57
|
+
* @returns {any} - The state value
|
|
58
|
+
*/
|
|
59
|
+
get(key, defaultValue = undefined) {
|
|
60
|
+
if (typeof key !== 'string') {
|
|
61
|
+
throw new Error('State key must be a string');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const value = this.__getNestedValue(this.__state, key);
|
|
65
|
+
logger.log(`📖 StateService: Get ${key}:`, value);
|
|
66
|
+
return value !== undefined ? value : defaultValue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Set state value by key
|
|
71
|
+
* @param {string|object} key - The key to set, or object with multiple key-value pairs
|
|
72
|
+
* @param {any} value - The value to set (ignored if key is object)
|
|
73
|
+
* @param {boolean} silent - If true, don't trigger listeners
|
|
74
|
+
* @returns {StateService} - Chainable
|
|
75
|
+
*/
|
|
76
|
+
set(key, value = null, silent = false) {
|
|
77
|
+
// If key is object, set multiple values
|
|
78
|
+
if (typeof key === 'object' && key !== null) {
|
|
79
|
+
for (const k in key) {
|
|
80
|
+
if (Object.prototype.hasOwnProperty.call(key, k)) {
|
|
81
|
+
this.set(k, key[k], silent);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof key !== 'string') {
|
|
88
|
+
throw new Error('State key must be a string');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const oldValue = this.__getNestedValue(this.__state, key);
|
|
92
|
+
|
|
93
|
+
// Apply middlewares
|
|
94
|
+
const processedValue = this.__applyMiddlewares(key, oldValue, value, 'set');
|
|
95
|
+
|
|
96
|
+
// Set the value
|
|
97
|
+
this.__setNestedValue(this.__state, key, processedValue);
|
|
98
|
+
|
|
99
|
+
logger.log(`💾 StateService: Set ${key}:`, processedValue);
|
|
100
|
+
|
|
101
|
+
if (!silent) {
|
|
102
|
+
this.__notifyListeners(key, processedValue, oldValue);
|
|
103
|
+
}
|
|
104
|
+
if (!this.dynamicProperties.includes(key)) {
|
|
105
|
+
this.__addDynamicProperty(key);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return this;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Delete state value by key
|
|
113
|
+
* @param {string} key - The key to delete
|
|
114
|
+
* @param {boolean} silent - If true, don't trigger listeners
|
|
115
|
+
* @returns {boolean} - True if key was deleted
|
|
116
|
+
*/
|
|
117
|
+
delete(key, silent = false) {
|
|
118
|
+
if (typeof key !== 'string') {
|
|
119
|
+
throw new Error('State key must be a string');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const oldValue = this.__getNestedValue(this.__state, key);
|
|
123
|
+
const deleted = this.__deleteNestedValue(this.__state, key);
|
|
124
|
+
|
|
125
|
+
if (deleted) {
|
|
126
|
+
logger.log(`🗑️ StateService: Delete ${key}`);
|
|
127
|
+
|
|
128
|
+
if (!silent) {
|
|
129
|
+
this.__notifyListeners(key, undefined, oldValue);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (this.dynamicProperties.includes(key)) {
|
|
133
|
+
this.__removeDynamicProperty(key);
|
|
134
|
+
}
|
|
135
|
+
return deleted;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Check if state key exists
|
|
140
|
+
* @param {string} key - The key to check
|
|
141
|
+
* @returns {boolean} - True if key exists
|
|
142
|
+
*/
|
|
143
|
+
has(key) {
|
|
144
|
+
if (typeof key !== 'string') {
|
|
145
|
+
throw new Error('State key must be a string');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const value = this.__getNestedValue(this.__state, key);
|
|
149
|
+
return value !== undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get all state
|
|
154
|
+
* @returns {object} - Copy of all state
|
|
155
|
+
*/
|
|
156
|
+
getAll() {
|
|
157
|
+
return JSON.parse(JSON.stringify(this.__state));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Clear all state
|
|
162
|
+
* @param {boolean} silent - If true, don't trigger listeners
|
|
163
|
+
* @returns {StateService} - Chainable
|
|
164
|
+
*/
|
|
165
|
+
clear(silent = false) {
|
|
166
|
+
const oldState = { ...this.__state };
|
|
167
|
+
this.__state = {};
|
|
168
|
+
|
|
169
|
+
logger.log('🧹 StateService: Cleared all state');
|
|
170
|
+
|
|
171
|
+
if (!silent) {
|
|
172
|
+
// Notify all listeners that their keys are deleted
|
|
173
|
+
for (const key of this.__listeners.keys()) {
|
|
174
|
+
const oldValue = this.__getNestedValue(oldState, key);
|
|
175
|
+
if (oldValue !== undefined) {
|
|
176
|
+
this.__notifyListeners(key, undefined, oldValue);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ==========================================
|
|
185
|
+
// PUBLIC INSTANCE METHODS - Event System
|
|
186
|
+
// ==========================================
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Subscribe to state changes
|
|
190
|
+
* @param {string} key - The key to watch
|
|
191
|
+
* @param {function} callback - Callback function
|
|
192
|
+
* @param {object} options - Options for subscription
|
|
193
|
+
* @returns {function} - Unsubscribe function
|
|
194
|
+
*/
|
|
195
|
+
subscribe(key, callback, options = {}) {
|
|
196
|
+
if (typeof key !== 'string') {
|
|
197
|
+
throw new Error('State key must be a string');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (typeof callback !== 'function') {
|
|
201
|
+
throw new Error('Callback must be a function');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const {
|
|
205
|
+
immediate = false, // Call immediately with current value
|
|
206
|
+
once = false // Call only once
|
|
207
|
+
} = options;
|
|
208
|
+
|
|
209
|
+
if (!this.__listeners.has(key)) {
|
|
210
|
+
this.__listeners.set(key, new Set());
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const listener = {
|
|
214
|
+
callback,
|
|
215
|
+
once,
|
|
216
|
+
id: Date.now() + Math.random()
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
this.__listeners.get(key).add(listener);
|
|
220
|
+
|
|
221
|
+
logger.log(`🎧 StateService: Subscribed to ${key}`);
|
|
222
|
+
|
|
223
|
+
// Call immediately if requested
|
|
224
|
+
if (immediate) {
|
|
225
|
+
const currentValue = this.get(key);
|
|
226
|
+
if (currentValue !== undefined) {
|
|
227
|
+
callback(currentValue, undefined, key);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Return unsubscribe function
|
|
232
|
+
return () => this.unsubscribe(key, listener.id);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Unsubscribe from state changes
|
|
237
|
+
* @param {string} key - The key to stop watching
|
|
238
|
+
* @param {string|function} listenerId - Listener ID or callback function
|
|
239
|
+
* @returns {boolean} - True if unsubscribed
|
|
240
|
+
*/
|
|
241
|
+
unsubscribe(key, listenerId) {
|
|
242
|
+
if (!this.__listeners.has(key)) {
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const listeners = this.__listeners.get(key);
|
|
247
|
+
|
|
248
|
+
if (typeof listenerId === 'string') {
|
|
249
|
+
// Remove by ID
|
|
250
|
+
for (const listener of listeners) {
|
|
251
|
+
if (listener.id === listenerId) {
|
|
252
|
+
listeners.delete(listener);
|
|
253
|
+
logger.log(`🎧 StateService: Unsubscribed from ${key}`);
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} else if (typeof listenerId === 'function') {
|
|
258
|
+
// Remove by callback
|
|
259
|
+
for (const listener of listeners) {
|
|
260
|
+
if (listener.callback === listenerId) {
|
|
261
|
+
listeners.delete(listener);
|
|
262
|
+
logger.log(`🎧 StateService: Unsubscribed from ${key}`);
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Unsubscribe all listeners for a key
|
|
273
|
+
* @param {string} key - The key to clear listeners for
|
|
274
|
+
*/
|
|
275
|
+
unsubscribeAll(key) {
|
|
276
|
+
if (key) {
|
|
277
|
+
this.__listeners.delete(key);
|
|
278
|
+
logger.log(`🎧 StateService: Unsubscribed all from ${key}`);
|
|
279
|
+
} else {
|
|
280
|
+
this.__listeners.clear();
|
|
281
|
+
logger.log('🎧 StateService: Unsubscribed all listeners');
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
// ==========================================
|
|
287
|
+
// PUBLIC INSTANCE METHODS - Middleware
|
|
288
|
+
// ==========================================
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Add middleware function
|
|
292
|
+
* @param {function} middleware - Middleware function
|
|
293
|
+
* @returns {StateService} - Chainable
|
|
294
|
+
*/
|
|
295
|
+
addMiddleware(middleware) {
|
|
296
|
+
if (typeof middleware !== 'function') {
|
|
297
|
+
throw new Error('Middleware must be a function');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.__middlewares.push(middleware);
|
|
301
|
+
logger.log('🔧 StateService: Added middleware');
|
|
302
|
+
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Remove middleware function
|
|
308
|
+
* @param {function} middleware - Middleware function to remove
|
|
309
|
+
* @returns {boolean} - True if removed
|
|
310
|
+
*/
|
|
311
|
+
removeMiddleware(middleware) {
|
|
312
|
+
const index = this.__middlewares.indexOf(middleware);
|
|
313
|
+
if (index > -1) {
|
|
314
|
+
this.__middlewares.splice(index, 1);
|
|
315
|
+
logger.log('🔧 StateService: Removed middleware');
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
// ==========================================
|
|
323
|
+
// PUBLIC INSTANCE METHODS - Utilities
|
|
324
|
+
// ==========================================
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Debug state service
|
|
328
|
+
*/
|
|
329
|
+
debug() {
|
|
330
|
+
// Get listeners info manually for debug
|
|
331
|
+
const listeners = {};
|
|
332
|
+
for (const [key, listenerSet] of this.__listeners) {
|
|
333
|
+
listeners[key] = listenerSet.size;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
logger.log('🔍 StateService Debug:', {
|
|
337
|
+
state: this.__state,
|
|
338
|
+
listeners: listeners,
|
|
339
|
+
middlewares: this.__middlewares.length,
|
|
340
|
+
isUpdating: this.__isUpdating
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Export state to JSON
|
|
346
|
+
* @returns {string} - JSON string
|
|
347
|
+
*/
|
|
348
|
+
export() {
|
|
349
|
+
return JSON.stringify(this.__state, null, 2);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Import state from JSON
|
|
354
|
+
* @param {string} jsonString - JSON string
|
|
355
|
+
* @returns {boolean} - True if successful
|
|
356
|
+
*/
|
|
357
|
+
import(jsonString) {
|
|
358
|
+
try {
|
|
359
|
+
const importedState = JSON.parse(jsonString);
|
|
360
|
+
this.set(importedState); // Use set method for multiple keys
|
|
361
|
+
logger.log('📥 StateService: Imported state successfully');
|
|
362
|
+
return true;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
logger.error('❌ StateService: Failed to import state:', error);
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// ==========================================
|
|
370
|
+
// PRIVATE METHODS
|
|
371
|
+
// ==========================================
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Get nested value from object using dot notation
|
|
375
|
+
* @param {object} obj - The object
|
|
376
|
+
* @param {string} path - The path (e.g., 'user.profile.name')
|
|
377
|
+
* @returns {any} - The value
|
|
378
|
+
*/
|
|
379
|
+
__getNestedValue(obj, path) {
|
|
380
|
+
return path.split('.').reduce((current, key) => {
|
|
381
|
+
return current && current[key] !== undefined ? current[key] : undefined;
|
|
382
|
+
}, obj);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Set nested value in object using dot notation
|
|
387
|
+
* @param {object} obj - The object
|
|
388
|
+
* @param {string} path - The path (e.g., 'user.profile.name')
|
|
389
|
+
* @param {any} value - The value to set
|
|
390
|
+
*/
|
|
391
|
+
__setNestedValue(obj, path, value) {
|
|
392
|
+
const keys = path.split('.');
|
|
393
|
+
const lastKey = keys.pop();
|
|
394
|
+
const target = keys.reduce((current, key) => {
|
|
395
|
+
if (!current[key] || typeof current[key] !== 'object') {
|
|
396
|
+
current[key] = {};
|
|
397
|
+
}
|
|
398
|
+
return current[key];
|
|
399
|
+
}, obj);
|
|
400
|
+
target[lastKey] = value;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Delete nested value from object using dot notation
|
|
405
|
+
* @param {object} obj - The object
|
|
406
|
+
* @param {string} path - The path (e.g., 'user.profile.name')
|
|
407
|
+
* @returns {boolean} - True if deleted
|
|
408
|
+
*/
|
|
409
|
+
__deleteNestedValue(obj, path) {
|
|
410
|
+
const keys = path.split('.');
|
|
411
|
+
const lastKey = keys.pop();
|
|
412
|
+
const target = keys.reduce((current, key) => {
|
|
413
|
+
return current && current[key] ? current[key] : undefined;
|
|
414
|
+
}, obj);
|
|
415
|
+
|
|
416
|
+
if (target && target.hasOwnProperty(lastKey)) {
|
|
417
|
+
delete target[lastKey];
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Check if value is an object
|
|
425
|
+
* @param {any} value - The value to check
|
|
426
|
+
* @returns {boolean} - True if object
|
|
427
|
+
*/
|
|
428
|
+
__isObject(value) {
|
|
429
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Apply middlewares to value change
|
|
434
|
+
* @param {string} key - The key
|
|
435
|
+
* @param {any} oldValue - Old value
|
|
436
|
+
* @param {any} newValue - New value
|
|
437
|
+
* @param {string} action - Action type
|
|
438
|
+
* @returns {any} - Processed value
|
|
439
|
+
*/
|
|
440
|
+
__applyMiddlewares(key, oldValue, newValue, action) {
|
|
441
|
+
let processedValue = newValue;
|
|
442
|
+
|
|
443
|
+
for (const middleware of this.__middlewares) {
|
|
444
|
+
try {
|
|
445
|
+
processedValue = middleware(key, oldValue, processedValue, action);
|
|
446
|
+
} catch (error) {
|
|
447
|
+
logger.error(`❌ StateService: Middleware error for ${key}:`, error);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return processedValue;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Notify all listeners for a key
|
|
456
|
+
* @param {string} key - The key
|
|
457
|
+
* @param {any} newValue - New value
|
|
458
|
+
* @param {any} oldValue - Old value
|
|
459
|
+
*/
|
|
460
|
+
__notifyListeners(key, newValue, oldValue) {
|
|
461
|
+
if (this.__isUpdating) return;
|
|
462
|
+
|
|
463
|
+
const listeners = this.__listeners.get(key);
|
|
464
|
+
if (!listeners) return;
|
|
465
|
+
|
|
466
|
+
const listenersToRemove = [];
|
|
467
|
+
|
|
468
|
+
for (const listener of listeners) {
|
|
469
|
+
try {
|
|
470
|
+
listener.callback(newValue, oldValue, key);
|
|
471
|
+
|
|
472
|
+
if (listener.once) {
|
|
473
|
+
listenersToRemove.push(listener);
|
|
474
|
+
}
|
|
475
|
+
} catch (error) {
|
|
476
|
+
logger.error(`❌ StateService: Listener error for ${key}:`, error);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Remove once listeners
|
|
481
|
+
for (const listener of listenersToRemove) {
|
|
482
|
+
listeners.delete(listener);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Remove empty listener sets
|
|
486
|
+
if (listeners.size === 0) {
|
|
487
|
+
this.__listeners.delete(key);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
__addDynamicProperty(key) {
|
|
492
|
+
if (StateService.privateProperties.includes(key) || this.dynamicProperties.includes(key)) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
this.dynamicProperties.push(key);
|
|
496
|
+
Object.defineProperty(this, key, {
|
|
497
|
+
get: () => this.get(key),
|
|
498
|
+
set: (value) => this.set(key, value)
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
__removeDynamicProperty(key) {
|
|
503
|
+
if (this.dynamicProperties.includes(key)) {
|
|
504
|
+
delete this[key];
|
|
505
|
+
this.dynamicProperties = this.dynamicProperties.filter(k => k !== key);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Export singleton instance
|
|
511
|
+
const state = StateService.getInstance();
|
|
512
|
+
export default state;
|