react-mirrorstate 0.5.0 → 0.6.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.
@@ -6,6 +6,9 @@ declare class WebSocketConnectionManager {
6
6
  private currentStates;
7
7
  private lastSeq;
8
8
  private queuedUpdates;
9
+ private getStorageObject;
10
+ private saveToLocalStorage;
11
+ loadFromLocalStorage(name: string): any | undefined;
9
12
  private getWebSocketConfig;
10
13
  private buildWebSocketURL;
11
14
  private cleanup;
@@ -14,6 +17,7 @@ declare class WebSocketConnectionManager {
14
17
  private pendingUpdates;
15
18
  updateState(name: string, state: any): void;
16
19
  getCurrentState(name: string): any;
20
+ getStatesHash(): string;
17
21
  private notifyListeners;
18
22
  }
19
23
  export declare const connectionManager: WebSocketConnectionManager;
@@ -1,3 +1,5 @@
1
+ import { STATES_HASH, PROJECT_NAME } from "virtual:mirrorstate/initial-states";
2
+ const LOCALSTORAGE_KEY = `mirrorstate:${PROJECT_NAME}`;
1
3
  class WebSocketConnectionManager {
2
4
  ws = null;
3
5
  isConnecting = false;
@@ -5,6 +7,51 @@ class WebSocketConnectionManager {
5
7
  currentStates = new Map();
6
8
  lastSeq = new Map();
7
9
  queuedUpdates = new Map();
10
+ getStorageObject() {
11
+ if (typeof window === "undefined" || !window.localStorage) {
12
+ return null;
13
+ }
14
+ try {
15
+ const stored = window.localStorage.getItem(LOCALSTORAGE_KEY);
16
+ if (stored !== null) {
17
+ const obj = JSON.parse(stored);
18
+ // Check hash - if mismatch, discard and return null
19
+ if (obj.__hash__ !== STATES_HASH) {
20
+ window.localStorage.removeItem(LOCALSTORAGE_KEY);
21
+ return null;
22
+ }
23
+ return obj;
24
+ }
25
+ }
26
+ catch {
27
+ // Ignore parse errors
28
+ }
29
+ return null;
30
+ }
31
+ saveToLocalStorage(name, state) {
32
+ if (typeof window === "undefined" || !window.localStorage) {
33
+ return;
34
+ }
35
+ try {
36
+ // Load existing object or create new one
37
+ let obj = this.getStorageObject();
38
+ if (!obj) {
39
+ obj = { __hash__: STATES_HASH };
40
+ }
41
+ obj[name] = state;
42
+ window.localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(obj));
43
+ }
44
+ catch {
45
+ // Ignore localStorage errors (quota exceeded, etc.)
46
+ }
47
+ }
48
+ loadFromLocalStorage(name) {
49
+ const obj = this.getStorageObject();
50
+ if (obj && name in obj) {
51
+ return obj[name];
52
+ }
53
+ return undefined;
54
+ }
8
55
  async getWebSocketConfig() {
9
56
  try {
10
57
  const config = await import("virtual:mirrorstate/config");
@@ -97,6 +144,11 @@ class WebSocketConnectionManager {
97
144
  this.currentStates.set(name, state);
98
145
  // Notify all local subscribers immediately (for same-page component sync)
99
146
  this.notifyListeners(name, state);
147
+ // In production, persist to localStorage instead of WebSocket
148
+ if (process.env.NODE_ENV === "production") {
149
+ this.saveToLocalStorage(name, state);
150
+ return;
151
+ }
100
152
  if (this.ws?.readyState !== WebSocket.OPEN) {
101
153
  this.queuedUpdates.set(name, state);
102
154
  return;
@@ -122,6 +174,9 @@ class WebSocketConnectionManager {
122
174
  getCurrentState(name) {
123
175
  return this.currentStates.get(name);
124
176
  }
177
+ getStatesHash() {
178
+ return STATES_HASH;
179
+ }
125
180
  notifyListeners(name, state) {
126
181
  const nameListeners = this.listeners.get(name);
127
182
  if (nameListeners) {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import { Draft } from "immer";
1
+ import { produce, type Draft } from "immer";
2
+ export { type Draft, type Immutable } from "immer";
3
+ export { produce };
2
4
  export declare function useMirrorState<T>(name: string): [T | undefined, (updater: (draft: Draft<T>) => void) => void];
3
5
  export declare function useMirrorState<T>(name: string, initialValue: T): [T, (updater: (draft: Draft<T>) => void) => void];
4
6
  export default useMirrorState;
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ import { useEffect, useState, useRef } from "react";
2
2
  import { produce } from "immer";
3
3
  import { connectionManager } from "./connection-manager";
4
4
  import { INITIAL_STATES } from "virtual:mirrorstate/initial-states";
5
+ export { produce };
5
6
  // Batching state for each mirror state name
6
7
  const batchQueues = new Map();
7
8
  const batchPending = new Map();
@@ -35,7 +36,17 @@ function scheduleBatchFlush(name) {
35
36
  export function useMirrorState(name, initialValue) {
36
37
  // Capture initialValue once on first render to make it stable
37
38
  const initialValueRef = useRef(initialValue);
38
- const [state, setState] = useState(() => INITIAL_STATES?.[name] ?? initialValueRef.current);
39
+ const [state, setState] = useState(() => {
40
+ // In production, check localStorage first for persisted state
41
+ if (process.env.NODE_ENV === "production") {
42
+ const storedState = connectionManager.loadFromLocalStorage(name);
43
+ if (storedState !== undefined) {
44
+ return storedState;
45
+ }
46
+ }
47
+ // Fall back to build-time initial states or user-provided initialValue
48
+ return INITIAL_STATES?.[name] ?? initialValueRef.current;
49
+ });
39
50
  const hasCreatedFile = useRef(false);
40
51
  useEffect(() => {
41
52
  // Subscribe to state changes for this name
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-mirrorstate",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "React library for bidirectional state synchronization with MirrorState",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",