@chromahq/store 1.0.20 → 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 +54 -29
- package/dist/index.d.ts +5 -0
- package/dist/index.es.js +54 -29
- package/package.json +1 -1
package/dist/index.cjs.js
CHANGED
|
@@ -110,6 +110,7 @@ function useStoreReset(store) {
|
|
|
110
110
|
|
|
111
111
|
const STORE_ENABLE_LOGS = typeof globalThis !== "undefined" && globalThis.__CHROMA_ENABLE_LOGS__ === false ? false : true;
|
|
112
112
|
class BridgeStore {
|
|
113
|
+
// ~1 frame at 60fps
|
|
113
114
|
constructor(bridge, initialState, storeName = "default", readyCallbacks = /* @__PURE__ */ new Set()) {
|
|
114
115
|
this.listeners = /* @__PURE__ */ new Set();
|
|
115
116
|
this.currentState = null;
|
|
@@ -121,6 +122,12 @@ class BridgeStore {
|
|
|
121
122
|
this.maxInitializationAttempts = 10;
|
|
122
123
|
this.initializationTimer = null;
|
|
123
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;
|
|
124
131
|
this.initialize = async () => {
|
|
125
132
|
if (this.isInitializing) {
|
|
126
133
|
return;
|
|
@@ -228,6 +235,20 @@ class BridgeStore {
|
|
|
228
235
|
clearTimeout(this.initializationTimer);
|
|
229
236
|
this.initializationTimer = null;
|
|
230
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
|
+
}
|
|
231
252
|
if (this.listeners) {
|
|
232
253
|
this.listeners.clear();
|
|
233
254
|
}
|
|
@@ -333,36 +354,47 @@ class BridgeStore {
|
|
|
333
354
|
}
|
|
334
355
|
setupReconnectListener() {
|
|
335
356
|
if (this.bridge.on) {
|
|
336
|
-
this.
|
|
357
|
+
this.reconnectHandler = () => {
|
|
337
358
|
if (STORE_ENABLE_LOGS) {
|
|
338
359
|
console.log(`BridgeStore[${this.storeName}]: Bridge reconnected, re-initializing...`);
|
|
339
360
|
}
|
|
340
361
|
this.forceInitialize();
|
|
341
|
-
}
|
|
362
|
+
};
|
|
363
|
+
this.bridge.on("bridge:connected", this.reconnectHandler);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
fetchAndApplyState() {
|
|
367
|
+
if (this.pendingStateSync) {
|
|
368
|
+
return;
|
|
342
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
|
+
});
|
|
343
385
|
}
|
|
344
386
|
setupStateSync() {
|
|
345
387
|
if (this.bridge.on) {
|
|
346
|
-
this.
|
|
347
|
-
if (this.
|
|
348
|
-
|
|
388
|
+
this.stateChangedHandler = () => {
|
|
389
|
+
if (this.stateSyncDebounceTimer) {
|
|
390
|
+
clearTimeout(this.stateSyncDebounceTimer);
|
|
349
391
|
}
|
|
350
|
-
this.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
});
|
|
365
|
-
});
|
|
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);
|
|
366
398
|
} else {
|
|
367
399
|
if (STORE_ENABLE_LOGS) {
|
|
368
400
|
console.warn(`BridgeStore[${this.storeName}]: Bridge does not support event listening`);
|
|
@@ -378,9 +410,7 @@ class BridgeStore {
|
|
|
378
410
|
}
|
|
379
411
|
return;
|
|
380
412
|
}
|
|
381
|
-
|
|
382
|
-
console.warn("BridgeStore: Cannot execute function update, state not initialized");
|
|
383
|
-
}
|
|
413
|
+
actualUpdate = partial(this.currentState);
|
|
384
414
|
} else {
|
|
385
415
|
actualUpdate = partial;
|
|
386
416
|
}
|
|
@@ -390,11 +420,6 @@ class BridgeStore {
|
|
|
390
420
|
`BridgeStore[${this.storeName}]: Bridge disconnected, state update queued locally only`
|
|
391
421
|
);
|
|
392
422
|
}
|
|
393
|
-
if (globalThis.__CHROMA_ENABLE_LOGS__ !== false) {
|
|
394
|
-
console.warn(
|
|
395
|
-
`BridgeStore[${this.storeName}]: Bridge disconnected, state update queued locally only`
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
423
|
}
|
|
399
424
|
const stateBeforeUpdate = this.currentState ? { ...this.currentState } : null;
|
|
400
425
|
this.applyOptimisticUpdate(actualUpdate, replace);
|
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
|
@@ -90,6 +90,7 @@ function useStoreReset(store) {
|
|
|
90
90
|
|
|
91
91
|
const STORE_ENABLE_LOGS = typeof globalThis !== "undefined" && globalThis.__CHROMA_ENABLE_LOGS__ === false ? false : true;
|
|
92
92
|
class BridgeStore {
|
|
93
|
+
// ~1 frame at 60fps
|
|
93
94
|
constructor(bridge, initialState, storeName = "default", readyCallbacks = /* @__PURE__ */ new Set()) {
|
|
94
95
|
this.listeners = /* @__PURE__ */ new Set();
|
|
95
96
|
this.currentState = null;
|
|
@@ -101,6 +102,12 @@ class BridgeStore {
|
|
|
101
102
|
this.maxInitializationAttempts = 10;
|
|
102
103
|
this.initializationTimer = null;
|
|
103
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;
|
|
104
111
|
this.initialize = async () => {
|
|
105
112
|
if (this.isInitializing) {
|
|
106
113
|
return;
|
|
@@ -208,6 +215,20 @@ class BridgeStore {
|
|
|
208
215
|
clearTimeout(this.initializationTimer);
|
|
209
216
|
this.initializationTimer = null;
|
|
210
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
|
+
}
|
|
211
232
|
if (this.listeners) {
|
|
212
233
|
this.listeners.clear();
|
|
213
234
|
}
|
|
@@ -313,36 +334,47 @@ class BridgeStore {
|
|
|
313
334
|
}
|
|
314
335
|
setupReconnectListener() {
|
|
315
336
|
if (this.bridge.on) {
|
|
316
|
-
this.
|
|
337
|
+
this.reconnectHandler = () => {
|
|
317
338
|
if (STORE_ENABLE_LOGS) {
|
|
318
339
|
console.log(`BridgeStore[${this.storeName}]: Bridge reconnected, re-initializing...`);
|
|
319
340
|
}
|
|
320
341
|
this.forceInitialize();
|
|
321
|
-
}
|
|
342
|
+
};
|
|
343
|
+
this.bridge.on("bridge:connected", this.reconnectHandler);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
fetchAndApplyState() {
|
|
347
|
+
if (this.pendingStateSync) {
|
|
348
|
+
return;
|
|
322
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
|
+
});
|
|
323
365
|
}
|
|
324
366
|
setupStateSync() {
|
|
325
367
|
if (this.bridge.on) {
|
|
326
|
-
this.
|
|
327
|
-
if (this.
|
|
328
|
-
|
|
368
|
+
this.stateChangedHandler = () => {
|
|
369
|
+
if (this.stateSyncDebounceTimer) {
|
|
370
|
+
clearTimeout(this.stateSyncDebounceTimer);
|
|
329
371
|
}
|
|
330
|
-
this.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
this.notifyListeners();
|
|
337
|
-
}
|
|
338
|
-
}).catch((error) => {
|
|
339
|
-
if (STORE_ENABLE_LOGS) {
|
|
340
|
-
console.error(`BridgeStore[${this.storeName}]: Failed to sync state:`, error);
|
|
341
|
-
}
|
|
342
|
-
}).finally(() => {
|
|
343
|
-
this.pendingStateSync = false;
|
|
344
|
-
});
|
|
345
|
-
});
|
|
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);
|
|
346
378
|
} else {
|
|
347
379
|
if (STORE_ENABLE_LOGS) {
|
|
348
380
|
console.warn(`BridgeStore[${this.storeName}]: Bridge does not support event listening`);
|
|
@@ -358,9 +390,7 @@ class BridgeStore {
|
|
|
358
390
|
}
|
|
359
391
|
return;
|
|
360
392
|
}
|
|
361
|
-
|
|
362
|
-
console.warn("BridgeStore: Cannot execute function update, state not initialized");
|
|
363
|
-
}
|
|
393
|
+
actualUpdate = partial(this.currentState);
|
|
364
394
|
} else {
|
|
365
395
|
actualUpdate = partial;
|
|
366
396
|
}
|
|
@@ -370,11 +400,6 @@ class BridgeStore {
|
|
|
370
400
|
`BridgeStore[${this.storeName}]: Bridge disconnected, state update queued locally only`
|
|
371
401
|
);
|
|
372
402
|
}
|
|
373
|
-
if (globalThis.__CHROMA_ENABLE_LOGS__ !== false) {
|
|
374
|
-
console.warn(
|
|
375
|
-
`BridgeStore[${this.storeName}]: Bridge disconnected, state update queued locally only`
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
403
|
}
|
|
379
404
|
const stateBeforeUpdate = this.currentState ? { ...this.currentState } : null;
|
|
380
405
|
this.applyOptimisticUpdate(actualUpdate, replace);
|
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",
|