@chromahq/store 1.0.19 → 1.0.21
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/dist/index.cjs.js +124 -53
- package/dist/index.d.ts +5 -0
- package/dist/index.es.js +124 -53
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -108,7 +108,9 @@ function useStoreReset(store) {
|
|
|
108
108
|
return store.reset;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
const STORE_ENABLE_LOGS = typeof globalThis !== "undefined" && globalThis.__CHROMA_ENABLE_LOGS__ === false ? false : true;
|
|
111
112
|
class BridgeStore {
|
|
113
|
+
// ~1 frame at 60fps
|
|
112
114
|
constructor(bridge, initialState, storeName = "default", readyCallbacks = /* @__PURE__ */ new Set()) {
|
|
113
115
|
this.listeners = /* @__PURE__ */ new Set();
|
|
114
116
|
this.currentState = null;
|
|
@@ -120,6 +122,12 @@ class BridgeStore {
|
|
|
120
122
|
this.maxInitializationAttempts = 10;
|
|
121
123
|
this.initializationTimer = null;
|
|
122
124
|
this.isInitializing = false;
|
|
125
|
+
// Store handler references for cleanup (prevents memory leaks)
|
|
126
|
+
this.reconnectHandler = null;
|
|
127
|
+
this.stateChangedHandler = null;
|
|
128
|
+
// Debounce timer for state sync (optimization for rapid updates)
|
|
129
|
+
this.stateSyncDebounceTimer = null;
|
|
130
|
+
this.stateSyncDebounceMs = 16;
|
|
123
131
|
this.initialize = async () => {
|
|
124
132
|
if (this.isInitializing) {
|
|
125
133
|
return;
|
|
@@ -132,17 +140,21 @@ class BridgeStore {
|
|
|
132
140
|
this.isInitializing = true;
|
|
133
141
|
try {
|
|
134
142
|
if (this.initializationAttempts > this.maxInitializationAttempts) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
143
|
+
if (STORE_ENABLE_LOGS) {
|
|
144
|
+
console.error(
|
|
145
|
+
`BridgeStore[${this.storeName}]: Max initialization attempts (${this.maxInitializationAttempts}) reached, giving up`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
138
148
|
this.isInitializing = false;
|
|
139
149
|
return;
|
|
140
150
|
}
|
|
141
151
|
if (!this.bridge.isConnected) {
|
|
142
152
|
if (this.initializationAttempts === 1 || this.initializationAttempts % 3 === 0) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
153
|
+
if (STORE_ENABLE_LOGS) {
|
|
154
|
+
console.log(
|
|
155
|
+
`BridgeStore[${this.storeName}]: Waiting for bridge connection (attempt ${this.initializationAttempts}/${this.maxInitializationAttempts})...`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
146
158
|
}
|
|
147
159
|
const delay = Math.min(500 * Math.pow(2, this.initializationAttempts - 1), 5e3);
|
|
148
160
|
this.isInitializing = false;
|
|
@@ -158,20 +170,28 @@ class BridgeStore {
|
|
|
158
170
|
this.notifyListeners();
|
|
159
171
|
this.ready = true;
|
|
160
172
|
this.isInitializing = false;
|
|
161
|
-
|
|
173
|
+
if (STORE_ENABLE_LOGS) {
|
|
174
|
+
console.log(`BridgeStore[${this.storeName}]: Initialized successfully`);
|
|
175
|
+
}
|
|
162
176
|
this.notifyReady();
|
|
163
177
|
} catch (error) {
|
|
164
178
|
this.isInitializing = false;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
179
|
+
if (STORE_ENABLE_LOGS) {
|
|
180
|
+
console.error(
|
|
181
|
+
`BridgeStore[${this.storeName}]: Failed to initialize (attempt ${this.initializationAttempts}):`,
|
|
182
|
+
error
|
|
183
|
+
);
|
|
184
|
+
}
|
|
169
185
|
if (this.initializationAttempts < this.maxInitializationAttempts) {
|
|
170
186
|
const delay = Math.min(1e3 * Math.pow(2, this.initializationAttempts - 1), 1e4);
|
|
171
|
-
|
|
187
|
+
if (STORE_ENABLE_LOGS) {
|
|
188
|
+
console.log(`BridgeStore[${this.storeName}]: Retrying initialization in ${delay}ms...`);
|
|
189
|
+
}
|
|
172
190
|
this.initializationTimer = setTimeout(() => this.initialize(), delay);
|
|
173
191
|
} else {
|
|
174
|
-
|
|
192
|
+
if (STORE_ENABLE_LOGS) {
|
|
193
|
+
console.error(`BridgeStore[${this.storeName}]: Max attempts reached, cannot retry`);
|
|
194
|
+
}
|
|
175
195
|
}
|
|
176
196
|
}
|
|
177
197
|
};
|
|
@@ -179,7 +199,9 @@ class BridgeStore {
|
|
|
179
199
|
this.pendingStateSync = false;
|
|
180
200
|
this.notifyListeners = () => {
|
|
181
201
|
if (!this.listeners) {
|
|
182
|
-
|
|
202
|
+
if (STORE_ENABLE_LOGS) {
|
|
203
|
+
console.warn("BridgeStore: listeners not initialized");
|
|
204
|
+
}
|
|
183
205
|
return;
|
|
184
206
|
}
|
|
185
207
|
if (this.currentState && this.previousState) {
|
|
@@ -191,7 +213,9 @@ class BridgeStore {
|
|
|
191
213
|
};
|
|
192
214
|
this.subscribe = (listener) => {
|
|
193
215
|
if (!this.listeners) {
|
|
194
|
-
|
|
216
|
+
if (STORE_ENABLE_LOGS) {
|
|
217
|
+
console.error("BridgeStore: Cannot subscribe, listeners not initialized");
|
|
218
|
+
}
|
|
195
219
|
return () => {
|
|
196
220
|
};
|
|
197
221
|
}
|
|
@@ -211,6 +235,20 @@ class BridgeStore {
|
|
|
211
235
|
clearTimeout(this.initializationTimer);
|
|
212
236
|
this.initializationTimer = null;
|
|
213
237
|
}
|
|
238
|
+
if (this.stateSyncDebounceTimer) {
|
|
239
|
+
clearTimeout(this.stateSyncDebounceTimer);
|
|
240
|
+
this.stateSyncDebounceTimer = null;
|
|
241
|
+
}
|
|
242
|
+
if (this.bridge.off) {
|
|
243
|
+
if (this.reconnectHandler) {
|
|
244
|
+
this.bridge.off("bridge:connected", this.reconnectHandler);
|
|
245
|
+
this.reconnectHandler = null;
|
|
246
|
+
}
|
|
247
|
+
if (this.stateChangedHandler) {
|
|
248
|
+
this.bridge.off(`store:${this.storeName}:stateChanged`, this.stateChangedHandler);
|
|
249
|
+
this.stateChangedHandler = null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
214
252
|
if (this.listeners) {
|
|
215
253
|
this.listeners.clear();
|
|
216
254
|
}
|
|
@@ -235,9 +273,11 @@ class BridgeStore {
|
|
|
235
273
|
this.reset = () => {
|
|
236
274
|
if (this.initialState !== null) {
|
|
237
275
|
if (!this.bridge.isConnected) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
276
|
+
if (STORE_ENABLE_LOGS) {
|
|
277
|
+
console.warn(
|
|
278
|
+
`BridgeStore[${this.storeName}]: Bridge disconnected, reset applied locally only`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
241
281
|
this.previousState = this.currentState;
|
|
242
282
|
this.currentState = { ...this.initialState };
|
|
243
283
|
this.notifyListeners();
|
|
@@ -248,16 +288,22 @@ class BridgeStore {
|
|
|
248
288
|
this.currentState = { ...this.initialState };
|
|
249
289
|
this.notifyListeners();
|
|
250
290
|
this.bridge.send(`store:${this.storeName}:reset`).catch((error) => {
|
|
251
|
-
|
|
291
|
+
if (STORE_ENABLE_LOGS) {
|
|
292
|
+
console.error(`BridgeStore[${this.storeName}]: Failed to reset state via bridge:`, error);
|
|
293
|
+
}
|
|
252
294
|
if (stateBeforeReset !== null) {
|
|
253
|
-
|
|
295
|
+
if (STORE_ENABLE_LOGS) {
|
|
296
|
+
console.warn(`BridgeStore[${this.storeName}]: Rolling back reset due to bridge error`);
|
|
297
|
+
}
|
|
254
298
|
this.previousState = this.currentState;
|
|
255
299
|
this.currentState = stateBeforeReset;
|
|
256
300
|
this.notifyListeners();
|
|
257
301
|
}
|
|
258
302
|
});
|
|
259
303
|
} else {
|
|
260
|
-
|
|
304
|
+
if (STORE_ENABLE_LOGS) {
|
|
305
|
+
console.warn(`BridgeStore[${this.storeName}]: Cannot reset, initial state not available`);
|
|
306
|
+
}
|
|
261
307
|
}
|
|
262
308
|
};
|
|
263
309
|
this.notifyReady = () => {
|
|
@@ -268,7 +314,9 @@ class BridgeStore {
|
|
|
268
314
|
* Force re-initialization of the store (useful for debugging or after reconnection)
|
|
269
315
|
*/
|
|
270
316
|
this.forceInitialize = async () => {
|
|
271
|
-
|
|
317
|
+
if (STORE_ENABLE_LOGS) {
|
|
318
|
+
console.debug(`BridgeStore[${this.storeName}]: Force re-initialization requested`);
|
|
319
|
+
}
|
|
272
320
|
if (this.initializationTimer) {
|
|
273
321
|
clearTimeout(this.initializationTimer);
|
|
274
322
|
this.initializationTimer = null;
|
|
@@ -306,41 +354,60 @@ class BridgeStore {
|
|
|
306
354
|
}
|
|
307
355
|
setupReconnectListener() {
|
|
308
356
|
if (this.bridge.on) {
|
|
309
|
-
this.
|
|
310
|
-
|
|
357
|
+
this.reconnectHandler = () => {
|
|
358
|
+
if (STORE_ENABLE_LOGS) {
|
|
359
|
+
console.log(`BridgeStore[${this.storeName}]: Bridge reconnected, re-initializing...`);
|
|
360
|
+
}
|
|
311
361
|
this.forceInitialize();
|
|
312
|
-
}
|
|
362
|
+
};
|
|
363
|
+
this.bridge.on("bridge:connected", this.reconnectHandler);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
fetchAndApplyState() {
|
|
367
|
+
if (this.pendingStateSync) {
|
|
368
|
+
return;
|
|
313
369
|
}
|
|
370
|
+
this.pendingStateSync = true;
|
|
371
|
+
const currentSequence = ++this.stateSyncSequence;
|
|
372
|
+
this.bridge.send(`store:${this.storeName}:getState`).then((newState) => {
|
|
373
|
+
if (currentSequence === this.stateSyncSequence) {
|
|
374
|
+
this.previousState = this.currentState;
|
|
375
|
+
this.currentState = newState;
|
|
376
|
+
this.notifyListeners();
|
|
377
|
+
}
|
|
378
|
+
}).catch((error) => {
|
|
379
|
+
if (STORE_ENABLE_LOGS) {
|
|
380
|
+
console.error(`BridgeStore[${this.storeName}]: Failed to sync state:`, error);
|
|
381
|
+
}
|
|
382
|
+
}).finally(() => {
|
|
383
|
+
this.pendingStateSync = false;
|
|
384
|
+
});
|
|
314
385
|
}
|
|
315
386
|
setupStateSync() {
|
|
316
387
|
if (this.bridge.on) {
|
|
317
|
-
this.
|
|
318
|
-
if (this.
|
|
319
|
-
|
|
388
|
+
this.stateChangedHandler = () => {
|
|
389
|
+
if (this.stateSyncDebounceTimer) {
|
|
390
|
+
clearTimeout(this.stateSyncDebounceTimer);
|
|
320
391
|
}
|
|
321
|
-
this.
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
this.notifyListeners();
|
|
328
|
-
}
|
|
329
|
-
}).catch((error) => {
|
|
330
|
-
console.error(`BridgeStore[${this.storeName}]: Failed to sync state:`, error);
|
|
331
|
-
}).finally(() => {
|
|
332
|
-
this.pendingStateSync = false;
|
|
333
|
-
});
|
|
334
|
-
});
|
|
392
|
+
this.stateSyncDebounceTimer = setTimeout(() => {
|
|
393
|
+
this.stateSyncDebounceTimer = null;
|
|
394
|
+
this.fetchAndApplyState();
|
|
395
|
+
}, this.stateSyncDebounceMs);
|
|
396
|
+
};
|
|
397
|
+
this.bridge.on(`store:${this.storeName}:stateChanged`, this.stateChangedHandler);
|
|
335
398
|
} else {
|
|
336
|
-
|
|
399
|
+
if (STORE_ENABLE_LOGS) {
|
|
400
|
+
console.warn(`BridgeStore[${this.storeName}]: Bridge does not support event listening`);
|
|
401
|
+
}
|
|
337
402
|
}
|
|
338
403
|
}
|
|
339
404
|
setState(partial, replace) {
|
|
340
405
|
let actualUpdate;
|
|
341
406
|
if (typeof partial === "function") {
|
|
342
407
|
if (this.currentState === null) {
|
|
343
|
-
|
|
408
|
+
if (STORE_ENABLE_LOGS) {
|
|
409
|
+
console.warn("BridgeStore: Cannot execute function update, state not initialized");
|
|
410
|
+
}
|
|
344
411
|
return;
|
|
345
412
|
}
|
|
346
413
|
actualUpdate = partial(this.currentState);
|
|
@@ -348,21 +415,25 @@ class BridgeStore {
|
|
|
348
415
|
actualUpdate = partial;
|
|
349
416
|
}
|
|
350
417
|
if (!this.bridge.isConnected) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
418
|
+
if (STORE_ENABLE_LOGS) {
|
|
419
|
+
console.warn(
|
|
420
|
+
`BridgeStore[${this.storeName}]: Bridge disconnected, state update queued locally only`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
356
423
|
}
|
|
357
424
|
const stateBeforeUpdate = this.currentState ? { ...this.currentState } : null;
|
|
358
425
|
this.applyOptimisticUpdate(actualUpdate, replace);
|
|
359
426
|
const payload = { partial: actualUpdate, replace };
|
|
360
427
|
this.bridge.send(`store:${this.storeName}:setState`, payload).catch((error) => {
|
|
361
|
-
|
|
428
|
+
if (STORE_ENABLE_LOGS) {
|
|
429
|
+
console.error(`BridgeStore[${this.storeName}]: Failed to update state via bridge:`, error);
|
|
430
|
+
}
|
|
362
431
|
if (stateBeforeUpdate !== null) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
432
|
+
if (STORE_ENABLE_LOGS) {
|
|
433
|
+
console.warn(
|
|
434
|
+
`BridgeStore[${this.storeName}]: Rolling back optimistic update due to bridge error`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
366
437
|
this.previousState = this.currentState;
|
|
367
438
|
this.currentState = stateBeforeUpdate;
|
|
368
439
|
this.notifyListeners();
|
package/dist/index.d.ts
CHANGED
|
@@ -72,11 +72,16 @@ declare class BridgeStore<T> implements CentralStore<T> {
|
|
|
72
72
|
private readonly maxInitializationAttempts;
|
|
73
73
|
private initializationTimer;
|
|
74
74
|
private isInitializing;
|
|
75
|
+
private reconnectHandler;
|
|
76
|
+
private stateChangedHandler;
|
|
77
|
+
private stateSyncDebounceTimer;
|
|
78
|
+
private readonly stateSyncDebounceMs;
|
|
75
79
|
constructor(bridge: BridgeWithEvents, initialState?: T, storeName?: string, readyCallbacks?: Set<() => void>);
|
|
76
80
|
private setupReconnectListener;
|
|
77
81
|
initialize: () => Promise<void>;
|
|
78
82
|
private stateSyncSequence;
|
|
79
83
|
private pendingStateSync;
|
|
84
|
+
private fetchAndApplyState;
|
|
80
85
|
private setupStateSync;
|
|
81
86
|
private notifyListeners;
|
|
82
87
|
getState: () => T;
|
package/dist/index.es.js
CHANGED
|
@@ -88,7 +88,9 @@ function useStoreReset(store) {
|
|
|
88
88
|
return store.reset;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
const STORE_ENABLE_LOGS = typeof globalThis !== "undefined" && globalThis.__CHROMA_ENABLE_LOGS__ === false ? false : true;
|
|
91
92
|
class BridgeStore {
|
|
93
|
+
// ~1 frame at 60fps
|
|
92
94
|
constructor(bridge, initialState, storeName = "default", readyCallbacks = /* @__PURE__ */ new Set()) {
|
|
93
95
|
this.listeners = /* @__PURE__ */ new Set();
|
|
94
96
|
this.currentState = null;
|
|
@@ -100,6 +102,12 @@ class BridgeStore {
|
|
|
100
102
|
this.maxInitializationAttempts = 10;
|
|
101
103
|
this.initializationTimer = null;
|
|
102
104
|
this.isInitializing = false;
|
|
105
|
+
// Store handler references for cleanup (prevents memory leaks)
|
|
106
|
+
this.reconnectHandler = null;
|
|
107
|
+
this.stateChangedHandler = null;
|
|
108
|
+
// Debounce timer for state sync (optimization for rapid updates)
|
|
109
|
+
this.stateSyncDebounceTimer = null;
|
|
110
|
+
this.stateSyncDebounceMs = 16;
|
|
103
111
|
this.initialize = async () => {
|
|
104
112
|
if (this.isInitializing) {
|
|
105
113
|
return;
|
|
@@ -112,17 +120,21 @@ class BridgeStore {
|
|
|
112
120
|
this.isInitializing = true;
|
|
113
121
|
try {
|
|
114
122
|
if (this.initializationAttempts > this.maxInitializationAttempts) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
if (STORE_ENABLE_LOGS) {
|
|
124
|
+
console.error(
|
|
125
|
+
`BridgeStore[${this.storeName}]: Max initialization attempts (${this.maxInitializationAttempts}) reached, giving up`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
118
128
|
this.isInitializing = false;
|
|
119
129
|
return;
|
|
120
130
|
}
|
|
121
131
|
if (!this.bridge.isConnected) {
|
|
122
132
|
if (this.initializationAttempts === 1 || this.initializationAttempts % 3 === 0) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
133
|
+
if (STORE_ENABLE_LOGS) {
|
|
134
|
+
console.log(
|
|
135
|
+
`BridgeStore[${this.storeName}]: Waiting for bridge connection (attempt ${this.initializationAttempts}/${this.maxInitializationAttempts})...`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
126
138
|
}
|
|
127
139
|
const delay = Math.min(500 * Math.pow(2, this.initializationAttempts - 1), 5e3);
|
|
128
140
|
this.isInitializing = false;
|
|
@@ -138,20 +150,28 @@ class BridgeStore {
|
|
|
138
150
|
this.notifyListeners();
|
|
139
151
|
this.ready = true;
|
|
140
152
|
this.isInitializing = false;
|
|
141
|
-
|
|
153
|
+
if (STORE_ENABLE_LOGS) {
|
|
154
|
+
console.log(`BridgeStore[${this.storeName}]: Initialized successfully`);
|
|
155
|
+
}
|
|
142
156
|
this.notifyReady();
|
|
143
157
|
} catch (error) {
|
|
144
158
|
this.isInitializing = false;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
159
|
+
if (STORE_ENABLE_LOGS) {
|
|
160
|
+
console.error(
|
|
161
|
+
`BridgeStore[${this.storeName}]: Failed to initialize (attempt ${this.initializationAttempts}):`,
|
|
162
|
+
error
|
|
163
|
+
);
|
|
164
|
+
}
|
|
149
165
|
if (this.initializationAttempts < this.maxInitializationAttempts) {
|
|
150
166
|
const delay = Math.min(1e3 * Math.pow(2, this.initializationAttempts - 1), 1e4);
|
|
151
|
-
|
|
167
|
+
if (STORE_ENABLE_LOGS) {
|
|
168
|
+
console.log(`BridgeStore[${this.storeName}]: Retrying initialization in ${delay}ms...`);
|
|
169
|
+
}
|
|
152
170
|
this.initializationTimer = setTimeout(() => this.initialize(), delay);
|
|
153
171
|
} else {
|
|
154
|
-
|
|
172
|
+
if (STORE_ENABLE_LOGS) {
|
|
173
|
+
console.error(`BridgeStore[${this.storeName}]: Max attempts reached, cannot retry`);
|
|
174
|
+
}
|
|
155
175
|
}
|
|
156
176
|
}
|
|
157
177
|
};
|
|
@@ -159,7 +179,9 @@ class BridgeStore {
|
|
|
159
179
|
this.pendingStateSync = false;
|
|
160
180
|
this.notifyListeners = () => {
|
|
161
181
|
if (!this.listeners) {
|
|
162
|
-
|
|
182
|
+
if (STORE_ENABLE_LOGS) {
|
|
183
|
+
console.warn("BridgeStore: listeners not initialized");
|
|
184
|
+
}
|
|
163
185
|
return;
|
|
164
186
|
}
|
|
165
187
|
if (this.currentState && this.previousState) {
|
|
@@ -171,7 +193,9 @@ class BridgeStore {
|
|
|
171
193
|
};
|
|
172
194
|
this.subscribe = (listener) => {
|
|
173
195
|
if (!this.listeners) {
|
|
174
|
-
|
|
196
|
+
if (STORE_ENABLE_LOGS) {
|
|
197
|
+
console.error("BridgeStore: Cannot subscribe, listeners not initialized");
|
|
198
|
+
}
|
|
175
199
|
return () => {
|
|
176
200
|
};
|
|
177
201
|
}
|
|
@@ -191,6 +215,20 @@ class BridgeStore {
|
|
|
191
215
|
clearTimeout(this.initializationTimer);
|
|
192
216
|
this.initializationTimer = null;
|
|
193
217
|
}
|
|
218
|
+
if (this.stateSyncDebounceTimer) {
|
|
219
|
+
clearTimeout(this.stateSyncDebounceTimer);
|
|
220
|
+
this.stateSyncDebounceTimer = null;
|
|
221
|
+
}
|
|
222
|
+
if (this.bridge.off) {
|
|
223
|
+
if (this.reconnectHandler) {
|
|
224
|
+
this.bridge.off("bridge:connected", this.reconnectHandler);
|
|
225
|
+
this.reconnectHandler = null;
|
|
226
|
+
}
|
|
227
|
+
if (this.stateChangedHandler) {
|
|
228
|
+
this.bridge.off(`store:${this.storeName}:stateChanged`, this.stateChangedHandler);
|
|
229
|
+
this.stateChangedHandler = null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
194
232
|
if (this.listeners) {
|
|
195
233
|
this.listeners.clear();
|
|
196
234
|
}
|
|
@@ -215,9 +253,11 @@ class BridgeStore {
|
|
|
215
253
|
this.reset = () => {
|
|
216
254
|
if (this.initialState !== null) {
|
|
217
255
|
if (!this.bridge.isConnected) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
256
|
+
if (STORE_ENABLE_LOGS) {
|
|
257
|
+
console.warn(
|
|
258
|
+
`BridgeStore[${this.storeName}]: Bridge disconnected, reset applied locally only`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
221
261
|
this.previousState = this.currentState;
|
|
222
262
|
this.currentState = { ...this.initialState };
|
|
223
263
|
this.notifyListeners();
|
|
@@ -228,16 +268,22 @@ class BridgeStore {
|
|
|
228
268
|
this.currentState = { ...this.initialState };
|
|
229
269
|
this.notifyListeners();
|
|
230
270
|
this.bridge.send(`store:${this.storeName}:reset`).catch((error) => {
|
|
231
|
-
|
|
271
|
+
if (STORE_ENABLE_LOGS) {
|
|
272
|
+
console.error(`BridgeStore[${this.storeName}]: Failed to reset state via bridge:`, error);
|
|
273
|
+
}
|
|
232
274
|
if (stateBeforeReset !== null) {
|
|
233
|
-
|
|
275
|
+
if (STORE_ENABLE_LOGS) {
|
|
276
|
+
console.warn(`BridgeStore[${this.storeName}]: Rolling back reset due to bridge error`);
|
|
277
|
+
}
|
|
234
278
|
this.previousState = this.currentState;
|
|
235
279
|
this.currentState = stateBeforeReset;
|
|
236
280
|
this.notifyListeners();
|
|
237
281
|
}
|
|
238
282
|
});
|
|
239
283
|
} else {
|
|
240
|
-
|
|
284
|
+
if (STORE_ENABLE_LOGS) {
|
|
285
|
+
console.warn(`BridgeStore[${this.storeName}]: Cannot reset, initial state not available`);
|
|
286
|
+
}
|
|
241
287
|
}
|
|
242
288
|
};
|
|
243
289
|
this.notifyReady = () => {
|
|
@@ -248,7 +294,9 @@ class BridgeStore {
|
|
|
248
294
|
* Force re-initialization of the store (useful for debugging or after reconnection)
|
|
249
295
|
*/
|
|
250
296
|
this.forceInitialize = async () => {
|
|
251
|
-
|
|
297
|
+
if (STORE_ENABLE_LOGS) {
|
|
298
|
+
console.debug(`BridgeStore[${this.storeName}]: Force re-initialization requested`);
|
|
299
|
+
}
|
|
252
300
|
if (this.initializationTimer) {
|
|
253
301
|
clearTimeout(this.initializationTimer);
|
|
254
302
|
this.initializationTimer = null;
|
|
@@ -286,41 +334,60 @@ class BridgeStore {
|
|
|
286
334
|
}
|
|
287
335
|
setupReconnectListener() {
|
|
288
336
|
if (this.bridge.on) {
|
|
289
|
-
this.
|
|
290
|
-
|
|
337
|
+
this.reconnectHandler = () => {
|
|
338
|
+
if (STORE_ENABLE_LOGS) {
|
|
339
|
+
console.log(`BridgeStore[${this.storeName}]: Bridge reconnected, re-initializing...`);
|
|
340
|
+
}
|
|
291
341
|
this.forceInitialize();
|
|
292
|
-
}
|
|
342
|
+
};
|
|
343
|
+
this.bridge.on("bridge:connected", this.reconnectHandler);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
fetchAndApplyState() {
|
|
347
|
+
if (this.pendingStateSync) {
|
|
348
|
+
return;
|
|
293
349
|
}
|
|
350
|
+
this.pendingStateSync = true;
|
|
351
|
+
const currentSequence = ++this.stateSyncSequence;
|
|
352
|
+
this.bridge.send(`store:${this.storeName}:getState`).then((newState) => {
|
|
353
|
+
if (currentSequence === this.stateSyncSequence) {
|
|
354
|
+
this.previousState = this.currentState;
|
|
355
|
+
this.currentState = newState;
|
|
356
|
+
this.notifyListeners();
|
|
357
|
+
}
|
|
358
|
+
}).catch((error) => {
|
|
359
|
+
if (STORE_ENABLE_LOGS) {
|
|
360
|
+
console.error(`BridgeStore[${this.storeName}]: Failed to sync state:`, error);
|
|
361
|
+
}
|
|
362
|
+
}).finally(() => {
|
|
363
|
+
this.pendingStateSync = false;
|
|
364
|
+
});
|
|
294
365
|
}
|
|
295
366
|
setupStateSync() {
|
|
296
367
|
if (this.bridge.on) {
|
|
297
|
-
this.
|
|
298
|
-
if (this.
|
|
299
|
-
|
|
368
|
+
this.stateChangedHandler = () => {
|
|
369
|
+
if (this.stateSyncDebounceTimer) {
|
|
370
|
+
clearTimeout(this.stateSyncDebounceTimer);
|
|
300
371
|
}
|
|
301
|
-
this.
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
this.notifyListeners();
|
|
308
|
-
}
|
|
309
|
-
}).catch((error) => {
|
|
310
|
-
console.error(`BridgeStore[${this.storeName}]: Failed to sync state:`, error);
|
|
311
|
-
}).finally(() => {
|
|
312
|
-
this.pendingStateSync = false;
|
|
313
|
-
});
|
|
314
|
-
});
|
|
372
|
+
this.stateSyncDebounceTimer = setTimeout(() => {
|
|
373
|
+
this.stateSyncDebounceTimer = null;
|
|
374
|
+
this.fetchAndApplyState();
|
|
375
|
+
}, this.stateSyncDebounceMs);
|
|
376
|
+
};
|
|
377
|
+
this.bridge.on(`store:${this.storeName}:stateChanged`, this.stateChangedHandler);
|
|
315
378
|
} else {
|
|
316
|
-
|
|
379
|
+
if (STORE_ENABLE_LOGS) {
|
|
380
|
+
console.warn(`BridgeStore[${this.storeName}]: Bridge does not support event listening`);
|
|
381
|
+
}
|
|
317
382
|
}
|
|
318
383
|
}
|
|
319
384
|
setState(partial, replace) {
|
|
320
385
|
let actualUpdate;
|
|
321
386
|
if (typeof partial === "function") {
|
|
322
387
|
if (this.currentState === null) {
|
|
323
|
-
|
|
388
|
+
if (STORE_ENABLE_LOGS) {
|
|
389
|
+
console.warn("BridgeStore: Cannot execute function update, state not initialized");
|
|
390
|
+
}
|
|
324
391
|
return;
|
|
325
392
|
}
|
|
326
393
|
actualUpdate = partial(this.currentState);
|
|
@@ -328,21 +395,25 @@ class BridgeStore {
|
|
|
328
395
|
actualUpdate = partial;
|
|
329
396
|
}
|
|
330
397
|
if (!this.bridge.isConnected) {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
398
|
+
if (STORE_ENABLE_LOGS) {
|
|
399
|
+
console.warn(
|
|
400
|
+
`BridgeStore[${this.storeName}]: Bridge disconnected, state update queued locally only`
|
|
401
|
+
);
|
|
402
|
+
}
|
|
336
403
|
}
|
|
337
404
|
const stateBeforeUpdate = this.currentState ? { ...this.currentState } : null;
|
|
338
405
|
this.applyOptimisticUpdate(actualUpdate, replace);
|
|
339
406
|
const payload = { partial: actualUpdate, replace };
|
|
340
407
|
this.bridge.send(`store:${this.storeName}:setState`, payload).catch((error) => {
|
|
341
|
-
|
|
408
|
+
if (STORE_ENABLE_LOGS) {
|
|
409
|
+
console.error(`BridgeStore[${this.storeName}]: Failed to update state via bridge:`, error);
|
|
410
|
+
}
|
|
342
411
|
if (stateBeforeUpdate !== null) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
412
|
+
if (STORE_ENABLE_LOGS) {
|
|
413
|
+
console.warn(
|
|
414
|
+
`BridgeStore[${this.storeName}]: Rolling back optimistic update due to bridge error`
|
|
415
|
+
);
|
|
416
|
+
}
|
|
346
417
|
this.previousState = this.currentState;
|
|
347
418
|
this.currentState = stateBeforeUpdate;
|
|
348
419
|
this.notifyListeners();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chromahq/store",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"description": "Centralized, persistent store for Chrome extensions using zustand, accessible from service workers and React, with chrome.storage.local persistence.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs.js",
|