@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 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
- console.error(
136
- `BridgeStore[${this.storeName}]: Max initialization attempts (${this.maxInitializationAttempts}) reached, giving up`
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
- console.log(
144
- `BridgeStore[${this.storeName}]: Waiting for bridge connection (attempt ${this.initializationAttempts}/${this.maxInitializationAttempts})...`
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
- console.log(`BridgeStore[${this.storeName}]: Initialized successfully`);
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
- console.error(
166
- `BridgeStore[${this.storeName}]: Failed to initialize (attempt ${this.initializationAttempts}):`,
167
- error
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
- console.log(`BridgeStore[${this.storeName}]: Retrying initialization in ${delay}ms...`);
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
- console.error(`BridgeStore[${this.storeName}]: Max attempts reached, cannot retry`);
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
- console.warn("BridgeStore: listeners not initialized");
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
- console.error("BridgeStore: Cannot subscribe, listeners not initialized");
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
- console.warn(
239
- `BridgeStore[${this.storeName}]: Bridge disconnected, reset applied locally only`
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
- console.error(`BridgeStore[${this.storeName}]: Failed to reset state via bridge:`, error);
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
- console.warn(`BridgeStore[${this.storeName}]: Rolling back reset due to bridge error`);
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
- console.warn(`BridgeStore[${this.storeName}]: Cannot reset, initial state not available`);
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
- console.debug(`BridgeStore[${this.storeName}]: Force re-initialization requested`);
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.bridge.on("bridge:connected", () => {
310
- console.log(`BridgeStore[${this.storeName}]: Bridge reconnected, re-initializing...`);
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.bridge.on(`store:${this.storeName}:stateChanged`, () => {
318
- if (this.pendingStateSync) {
319
- return;
388
+ this.stateChangedHandler = () => {
389
+ if (this.stateSyncDebounceTimer) {
390
+ clearTimeout(this.stateSyncDebounceTimer);
320
391
  }
321
- this.pendingStateSync = true;
322
- const currentSequence = ++this.stateSyncSequence;
323
- this.bridge.send(`store:${this.storeName}:getState`).then((newState) => {
324
- if (currentSequence === this.stateSyncSequence) {
325
- this.previousState = this.currentState;
326
- this.currentState = newState;
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
- console.warn(`BridgeStore[${this.storeName}]: Bridge does not support event listening`);
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
- console.warn("BridgeStore: Cannot execute function update, state not initialized");
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
- console.warn(
352
- `BridgeStore[${this.storeName}]: Bridge disconnected, state update queued locally only`
353
- );
354
- this.applyOptimisticUpdate(actualUpdate, replace);
355
- return;
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
- console.error(`BridgeStore[${this.storeName}]: Failed to update state via bridge:`, error);
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
- console.warn(
364
- `BridgeStore[${this.storeName}]: Rolling back optimistic update due to bridge error`
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
- console.error(
116
- `BridgeStore[${this.storeName}]: Max initialization attempts (${this.maxInitializationAttempts}) reached, giving up`
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
- console.log(
124
- `BridgeStore[${this.storeName}]: Waiting for bridge connection (attempt ${this.initializationAttempts}/${this.maxInitializationAttempts})...`
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
- console.log(`BridgeStore[${this.storeName}]: Initialized successfully`);
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
- console.error(
146
- `BridgeStore[${this.storeName}]: Failed to initialize (attempt ${this.initializationAttempts}):`,
147
- error
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
- console.log(`BridgeStore[${this.storeName}]: Retrying initialization in ${delay}ms...`);
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
- console.error(`BridgeStore[${this.storeName}]: Max attempts reached, cannot retry`);
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
- console.warn("BridgeStore: listeners not initialized");
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
- console.error("BridgeStore: Cannot subscribe, listeners not initialized");
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
- console.warn(
219
- `BridgeStore[${this.storeName}]: Bridge disconnected, reset applied locally only`
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
- console.error(`BridgeStore[${this.storeName}]: Failed to reset state via bridge:`, error);
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
- console.warn(`BridgeStore[${this.storeName}]: Rolling back reset due to bridge error`);
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
- console.warn(`BridgeStore[${this.storeName}]: Cannot reset, initial state not available`);
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
- console.debug(`BridgeStore[${this.storeName}]: Force re-initialization requested`);
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.bridge.on("bridge:connected", () => {
290
- console.log(`BridgeStore[${this.storeName}]: Bridge reconnected, re-initializing...`);
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.bridge.on(`store:${this.storeName}:stateChanged`, () => {
298
- if (this.pendingStateSync) {
299
- return;
368
+ this.stateChangedHandler = () => {
369
+ if (this.stateSyncDebounceTimer) {
370
+ clearTimeout(this.stateSyncDebounceTimer);
300
371
  }
301
- this.pendingStateSync = true;
302
- const currentSequence = ++this.stateSyncSequence;
303
- this.bridge.send(`store:${this.storeName}:getState`).then((newState) => {
304
- if (currentSequence === this.stateSyncSequence) {
305
- this.previousState = this.currentState;
306
- this.currentState = newState;
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
- console.warn(`BridgeStore[${this.storeName}]: Bridge does not support event listening`);
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
- console.warn("BridgeStore: Cannot execute function update, state not initialized");
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
- console.warn(
332
- `BridgeStore[${this.storeName}]: Bridge disconnected, state update queued locally only`
333
- );
334
- this.applyOptimisticUpdate(actualUpdate, replace);
335
- return;
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
- console.error(`BridgeStore[${this.storeName}]: Failed to update state via bridge:`, error);
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
- console.warn(
344
- `BridgeStore[${this.storeName}]: Rolling back optimistic update due to bridge error`
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.19",
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",