@crdt-sync/react 0.3.1 → 0.3.2

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 CHANGED
@@ -126,3 +126,32 @@ export function App() {
126
126
  return <div>{state.count}</div>;
127
127
  }
128
128
  ```
129
+
130
+ Additionally, if Vite fails to start or throws errors during pre-bundling, add this to your `vite.config.ts`:
131
+
132
+ ```ts
133
+ // vite.config.ts
134
+ import { defineConfig } from 'vite';
135
+
136
+ export default defineConfig({
137
+ optimizeDeps: {
138
+ // Prevent Vite from pre-bundling @crdt-sync/core.
139
+ // The package uses dynamic WebAssembly imports that Vite cannot
140
+ // handle during pre-bundling.
141
+ exclude: ['@crdt-sync/core'],
142
+ },
143
+ });
144
+ ```
145
+
146
+ ---
147
+
148
+ ## Server Setup
149
+
150
+ `@crdt-sync/react` connects to any WebSocket endpoint that speaks the `crdt-sync` envelope protocol. The simplest option is to run the official relay server:
151
+
152
+ ```bash
153
+ npm install @crdt-sync/server
154
+ npx crdt-sync-server --port 8080
155
+ ```
156
+
157
+ See the [@crdt-sync/server README](https://www.npmjs.com/package/@crdt-sync/server) for full documentation.
package/dist/index.d.mts CHANGED
@@ -2,9 +2,9 @@ import { CrdtStateProxy } from '@crdt-sync/core';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  type CrdtStatus = 'connecting' | 'open' | 'error';
5
- interface UseCrdtStateResult<T> {
5
+ interface UseCrdtStateResult<T extends Record<string, unknown>> {
6
6
  state: T;
7
- proxy: CrdtStateProxy | null;
7
+ proxy: CrdtStateProxy<T> | null;
8
8
  status: CrdtStatus;
9
9
  }
10
10
  interface UseCrdtStateOptions {
package/dist/index.d.ts CHANGED
@@ -2,9 +2,9 @@ import { CrdtStateProxy } from '@crdt-sync/core';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  type CrdtStatus = 'connecting' | 'open' | 'error';
5
- interface UseCrdtStateResult<T> {
5
+ interface UseCrdtStateResult<T extends Record<string, unknown>> {
6
6
  state: T;
7
- proxy: CrdtStateProxy | null;
7
+ proxy: CrdtStateProxy<T> | null;
8
8
  status: CrdtStatus;
9
9
  }
10
10
  interface UseCrdtStateOptions {
package/dist/index.js CHANGED
@@ -64,8 +64,9 @@ function useCrdtState(url, initialState, options) {
64
64
  const clientId = "client-" + Math.random().toString(36).substring(2, 11);
65
65
  const store = new import_core.WasmStateStore(clientId);
66
66
  currentProxy = new import_core.CrdtStateProxy(store);
67
+ const mutableState = currentProxy.state;
67
68
  for (const [key, value] of Object.entries(initialRef)) {
68
- currentProxy.state[key] = value;
69
+ mutableState[key] = value;
69
70
  }
70
71
  if (!active) return;
71
72
  setProxy(currentProxy);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/useCrdtState.ts","../src/CrdtSyncContext.tsx"],"sourcesContent":["export { useCrdtState } from './useCrdtState';\nexport type { CrdtStatus, UseCrdtStateResult, UseCrdtStateOptions } from './useCrdtState';\nexport { CrdtSyncProvider } from './CrdtSyncContext';\nexport type { CrdtSyncContextValue, CrdtSyncProviderProps } from './CrdtSyncContext';\n","import { useState, useEffect, useContext } from 'react';\nimport { CrdtStateProxy, WebSocketManager, initWasm, WasmStateStore } from '@crdt-sync/core';\nimport { CrdtSyncContext } from './CrdtSyncContext.js';\n\nexport type CrdtStatus = 'connecting' | 'open' | 'error';\n\nexport interface UseCrdtStateResult<T> {\n state: T;\n proxy: CrdtStateProxy | null;\n status: CrdtStatus;\n}\n\nexport interface UseCrdtStateOptions {\n wasmUrl?: string;\n}\n\nexport function useCrdtState<T extends Record<string, unknown>>(\n url: string,\n initialState: T,\n options?: UseCrdtStateOptions\n): UseCrdtStateResult<T> {\n const [proxy, setProxy] = useState<CrdtStateProxy | null>(null);\n const [status, setStatus] = useState<CrdtStatus>('connecting');\n const [, setTick] = useState(0);\n\n // Note: we're ignoring initialState updates (this behaves like useState).\n const [initialRef] = useState(initialState);\n\n const { wasmUrl: contextWasmUrl } = useContext(CrdtSyncContext);\n\n useEffect(() => {\n let active = true;\n let manager: WebSocketManager | null = null;\n let currentProxy: CrdtStateProxy | null = null;\n\n async function setup() {\n try {\n await initWasm(options?.wasmUrl ?? contextWasmUrl);\n if (!active) return;\n\n // Create a unique client ID\n const clientId = 'client-' + Math.random().toString(36).substring(2, 11);\n const store = new WasmStateStore(clientId);\n\n currentProxy = new CrdtStateProxy(store);\n\n // Initialize state\n for (const [key, value] of Object.entries(initialRef)) {\n currentProxy.state[key] = value;\n }\n\n if (!active) return;\n setProxy(currentProxy);\n\n const ws = new WebSocket(url);\n\n ws.onopen = () => {\n if (active) setStatus('open');\n };\n\n ws.onerror = () => {\n if (active) setStatus('error');\n };\n\n ws.onclose = () => {\n if (active) setStatus('connecting');\n };\n\n manager = new WebSocketManager(store, currentProxy, ws as any);\n } catch (err) {\n console.error('Failed to initialize CRDT sync:', err);\n if (active) setStatus('error');\n }\n }\n\n setup();\n\n return () => {\n active = false;\n if (manager) manager.disconnect();\n };\n }, [url, initialRef, contextWasmUrl]);\n\n useEffect(() => {\n if (!proxy) return;\n\n // Re-render on any state change: local writes and incoming remote updates.\n return proxy.onChange(() => {\n setTick(t => t + 1);\n });\n }, [proxy]);\n\n const state = proxy ? (proxy.state as T) : initialRef;\n\n return { state, proxy, status };\n}\n","import React, { createContext } from 'react';\nimport type { ReactNode } from 'react';\n\nexport interface CrdtSyncContextValue {\n wasmUrl?: string;\n}\n\nexport const CrdtSyncContext = createContext<CrdtSyncContextValue>({});\n\nexport interface CrdtSyncProviderProps {\n wasmUrl?: string;\n children: ReactNode;\n}\n\nexport function CrdtSyncProvider({ wasmUrl, children }: CrdtSyncProviderProps): JSX.Element {\n return (\n <CrdtSyncContext.Provider value={{ wasmUrl }}>\n {children}\n </CrdtSyncContext.Provider>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,gBAAgD;AAChD,kBAA2E;;;ACD3E,mBAAqC;AAO9B,IAAM,sBAAkB,4BAAoC,CAAC,CAAC;AAO9D,SAAS,iBAAiB,EAAE,SAAS,SAAS,GAAuC;AACxF,SACI,6BAAAC,QAAA,cAAC,gBAAgB,UAAhB,EAAyB,OAAO,EAAE,QAAQ,KACtC,QACL;AAER;;;ADJO,SAAS,aACZ,KACA,cACA,SACqB;AACrB,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAgC,IAAI;AAC9D,QAAM,CAAC,QAAQ,SAAS,QAAI,wBAAqB,YAAY;AAC7D,QAAM,CAAC,EAAE,OAAO,QAAI,wBAAS,CAAC;AAG9B,QAAM,CAAC,UAAU,QAAI,wBAAS,YAAY;AAE1C,QAAM,EAAE,SAAS,eAAe,QAAI,0BAAW,eAAe;AAE9D,+BAAU,MAAM;AACZ,QAAI,SAAS;AACb,QAAI,UAAmC;AACvC,QAAI,eAAsC;AAE1C,mBAAe,QAAQ;AACnB,UAAI;AACA,kBAAM,sBAAS,SAAS,WAAW,cAAc;AACjD,YAAI,CAAC,OAAQ;AAGb,cAAM,WAAW,YAAY,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE;AACvE,cAAM,QAAQ,IAAI,2BAAe,QAAQ;AAEzC,uBAAe,IAAI,2BAAe,KAAK;AAGvC,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACnD,uBAAa,MAAM,GAAG,IAAI;AAAA,QAC9B;AAEA,YAAI,CAAC,OAAQ;AACb,iBAAS,YAAY;AAErB,cAAM,KAAK,IAAI,UAAU,GAAG;AAE5B,WAAG,SAAS,MAAM;AACd,cAAI,OAAQ,WAAU,MAAM;AAAA,QAChC;AAEA,WAAG,UAAU,MAAM;AACf,cAAI,OAAQ,WAAU,OAAO;AAAA,QACjC;AAEA,WAAG,UAAU,MAAM;AACf,cAAI,OAAQ,WAAU,YAAY;AAAA,QACtC;AAEA,kBAAU,IAAI,6BAAiB,OAAO,cAAc,EAAS;AAAA,MACjE,SAAS,KAAK;AACV,gBAAQ,MAAM,mCAAmC,GAAG;AACpD,YAAI,OAAQ,WAAU,OAAO;AAAA,MACjC;AAAA,IACJ;AAEA,UAAM;AAEN,WAAO,MAAM;AACT,eAAS;AACT,UAAI,QAAS,SAAQ,WAAW;AAAA,IACpC;AAAA,EACJ,GAAG,CAAC,KAAK,YAAY,cAAc,CAAC;AAEpC,+BAAU,MAAM;AACZ,QAAI,CAAC,MAAO;AAGZ,WAAO,MAAM,SAAS,MAAM;AACxB,cAAQ,OAAK,IAAI,CAAC;AAAA,IACtB,CAAC;AAAA,EACL,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,QAAQ,QAAS,MAAM,QAAc;AAE3C,SAAO,EAAE,OAAO,OAAO,OAAO;AAClC;","names":["import_react","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/useCrdtState.ts","../src/CrdtSyncContext.tsx"],"sourcesContent":["export { useCrdtState } from './useCrdtState';\nexport type { CrdtStatus, UseCrdtStateResult, UseCrdtStateOptions } from './useCrdtState';\nexport { CrdtSyncProvider } from './CrdtSyncContext';\nexport type { CrdtSyncContextValue, CrdtSyncProviderProps } from './CrdtSyncContext';\n","import { useState, useEffect, useContext } from 'react';\nimport { CrdtStateProxy, WebSocketManager, initWasm, WasmStateStore } from '@crdt-sync/core';\nimport { CrdtSyncContext } from './CrdtSyncContext.js';\n\nexport type CrdtStatus = 'connecting' | 'open' | 'error';\n\nexport interface UseCrdtStateResult<T extends Record<string, unknown>> {\n state: T;\n proxy: CrdtStateProxy<T> | null;\n status: CrdtStatus;\n}\n\nexport interface UseCrdtStateOptions {\n wasmUrl?: string;\n}\n\nexport function useCrdtState<T extends Record<string, unknown>>(\n url: string,\n initialState: T,\n options?: UseCrdtStateOptions\n): UseCrdtStateResult<T> {\n const [proxy, setProxy] = useState<CrdtStateProxy<T> | null>(null);\n const [status, setStatus] = useState<CrdtStatus>('connecting');\n const [, setTick] = useState(0);\n\n // Note: we're ignoring initialState updates (this behaves like useState).\n const [initialRef] = useState(initialState);\n\n const { wasmUrl: contextWasmUrl } = useContext(CrdtSyncContext);\n\n useEffect(() => {\n let active = true;\n let manager: WebSocketManager | null = null;\n let currentProxy: CrdtStateProxy<T> | null = null;\n\n async function setup() {\n try {\n await initWasm(options?.wasmUrl ?? contextWasmUrl);\n if (!active) return;\n\n // Create a unique client ID\n const clientId = 'client-' + Math.random().toString(36).substring(2, 11);\n const store = new WasmStateStore(clientId);\n\n currentProxy = new CrdtStateProxy<T>(store);\n\n // Initialize state. Cast needed: TypeScript disallows index-writes on a\n // generic T even though T extends Record<string, unknown>.\n const mutableState = currentProxy.state as Record<string, unknown>;\n for (const [key, value] of Object.entries(initialRef)) {\n mutableState[key] = value;\n }\n\n if (!active) return;\n setProxy(currentProxy);\n\n const ws = new WebSocket(url);\n\n ws.onopen = () => {\n if (active) setStatus('open');\n };\n\n ws.onerror = () => {\n if (active) setStatus('error');\n };\n\n ws.onclose = () => {\n if (active) setStatus('connecting');\n };\n\n manager = new WebSocketManager(store, currentProxy, ws as any);\n } catch (err) {\n console.error('Failed to initialize CRDT sync:', err);\n if (active) setStatus('error');\n }\n }\n\n setup();\n\n return () => {\n active = false;\n if (manager) manager.disconnect();\n };\n }, [url, initialRef, contextWasmUrl]);\n\n useEffect(() => {\n if (!proxy) return;\n\n // Re-render on any state change: local writes and incoming remote updates.\n return proxy.onChange(() => {\n setTick(t => t + 1);\n });\n }, [proxy]);\n\n const state = proxy ? (proxy.state as T) : initialRef;\n\n return { state, proxy, status };\n}\n","import React, { createContext } from 'react';\nimport type { ReactNode } from 'react';\n\nexport interface CrdtSyncContextValue {\n wasmUrl?: string;\n}\n\nexport const CrdtSyncContext = createContext<CrdtSyncContextValue>({});\n\nexport interface CrdtSyncProviderProps {\n wasmUrl?: string;\n children: ReactNode;\n}\n\nexport function CrdtSyncProvider({ wasmUrl, children }: CrdtSyncProviderProps): JSX.Element {\n return (\n <CrdtSyncContext.Provider value={{ wasmUrl }}>\n {children}\n </CrdtSyncContext.Provider>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAAA,gBAAgD;AAChD,kBAA2E;;;ACD3E,mBAAqC;AAO9B,IAAM,sBAAkB,4BAAoC,CAAC,CAAC;AAO9D,SAAS,iBAAiB,EAAE,SAAS,SAAS,GAAuC;AACxF,SACI,6BAAAC,QAAA,cAAC,gBAAgB,UAAhB,EAAyB,OAAO,EAAE,QAAQ,KACtC,QACL;AAER;;;ADJO,SAAS,aACZ,KACA,cACA,SACqB;AACrB,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAmC,IAAI;AACjE,QAAM,CAAC,QAAQ,SAAS,QAAI,wBAAqB,YAAY;AAC7D,QAAM,CAAC,EAAE,OAAO,QAAI,wBAAS,CAAC;AAG9B,QAAM,CAAC,UAAU,QAAI,wBAAS,YAAY;AAE1C,QAAM,EAAE,SAAS,eAAe,QAAI,0BAAW,eAAe;AAE9D,+BAAU,MAAM;AACZ,QAAI,SAAS;AACb,QAAI,UAAmC;AACvC,QAAI,eAAyC;AAE7C,mBAAe,QAAQ;AACnB,UAAI;AACA,kBAAM,sBAAS,SAAS,WAAW,cAAc;AACjD,YAAI,CAAC,OAAQ;AAGb,cAAM,WAAW,YAAY,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE;AACvE,cAAM,QAAQ,IAAI,2BAAe,QAAQ;AAEzC,uBAAe,IAAI,2BAAkB,KAAK;AAI1C,cAAM,eAAe,aAAa;AAClC,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACnD,uBAAa,GAAG,IAAI;AAAA,QACxB;AAEA,YAAI,CAAC,OAAQ;AACb,iBAAS,YAAY;AAErB,cAAM,KAAK,IAAI,UAAU,GAAG;AAE5B,WAAG,SAAS,MAAM;AACd,cAAI,OAAQ,WAAU,MAAM;AAAA,QAChC;AAEA,WAAG,UAAU,MAAM;AACf,cAAI,OAAQ,WAAU,OAAO;AAAA,QACjC;AAEA,WAAG,UAAU,MAAM;AACf,cAAI,OAAQ,WAAU,YAAY;AAAA,QACtC;AAEA,kBAAU,IAAI,6BAAiB,OAAO,cAAc,EAAS;AAAA,MACjE,SAAS,KAAK;AACV,gBAAQ,MAAM,mCAAmC,GAAG;AACpD,YAAI,OAAQ,WAAU,OAAO;AAAA,MACjC;AAAA,IACJ;AAEA,UAAM;AAEN,WAAO,MAAM;AACT,eAAS;AACT,UAAI,QAAS,SAAQ,WAAW;AAAA,IACpC;AAAA,EACJ,GAAG,CAAC,KAAK,YAAY,cAAc,CAAC;AAEpC,+BAAU,MAAM;AACZ,QAAI,CAAC,MAAO;AAGZ,WAAO,MAAM,SAAS,MAAM;AACxB,cAAQ,OAAK,IAAI,CAAC;AAAA,IACtB,CAAC;AAAA,EACL,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,QAAQ,QAAS,MAAM,QAAc;AAE3C,SAAO,EAAE,OAAO,OAAO,OAAO;AAClC;","names":["import_react","React"]}
package/dist/index.mjs CHANGED
@@ -27,8 +27,9 @@ function useCrdtState(url, initialState, options) {
27
27
  const clientId = "client-" + Math.random().toString(36).substring(2, 11);
28
28
  const store = new WasmStateStore(clientId);
29
29
  currentProxy = new CrdtStateProxy(store);
30
+ const mutableState = currentProxy.state;
30
31
  for (const [key, value] of Object.entries(initialRef)) {
31
- currentProxy.state[key] = value;
32
+ mutableState[key] = value;
32
33
  }
33
34
  if (!active) return;
34
35
  setProxy(currentProxy);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useCrdtState.ts","../src/CrdtSyncContext.tsx"],"sourcesContent":["import { useState, useEffect, useContext } from 'react';\nimport { CrdtStateProxy, WebSocketManager, initWasm, WasmStateStore } from '@crdt-sync/core';\nimport { CrdtSyncContext } from './CrdtSyncContext.js';\n\nexport type CrdtStatus = 'connecting' | 'open' | 'error';\n\nexport interface UseCrdtStateResult<T> {\n state: T;\n proxy: CrdtStateProxy | null;\n status: CrdtStatus;\n}\n\nexport interface UseCrdtStateOptions {\n wasmUrl?: string;\n}\n\nexport function useCrdtState<T extends Record<string, unknown>>(\n url: string,\n initialState: T,\n options?: UseCrdtStateOptions\n): UseCrdtStateResult<T> {\n const [proxy, setProxy] = useState<CrdtStateProxy | null>(null);\n const [status, setStatus] = useState<CrdtStatus>('connecting');\n const [, setTick] = useState(0);\n\n // Note: we're ignoring initialState updates (this behaves like useState).\n const [initialRef] = useState(initialState);\n\n const { wasmUrl: contextWasmUrl } = useContext(CrdtSyncContext);\n\n useEffect(() => {\n let active = true;\n let manager: WebSocketManager | null = null;\n let currentProxy: CrdtStateProxy | null = null;\n\n async function setup() {\n try {\n await initWasm(options?.wasmUrl ?? contextWasmUrl);\n if (!active) return;\n\n // Create a unique client ID\n const clientId = 'client-' + Math.random().toString(36).substring(2, 11);\n const store = new WasmStateStore(clientId);\n\n currentProxy = new CrdtStateProxy(store);\n\n // Initialize state\n for (const [key, value] of Object.entries(initialRef)) {\n currentProxy.state[key] = value;\n }\n\n if (!active) return;\n setProxy(currentProxy);\n\n const ws = new WebSocket(url);\n\n ws.onopen = () => {\n if (active) setStatus('open');\n };\n\n ws.onerror = () => {\n if (active) setStatus('error');\n };\n\n ws.onclose = () => {\n if (active) setStatus('connecting');\n };\n\n manager = new WebSocketManager(store, currentProxy, ws as any);\n } catch (err) {\n console.error('Failed to initialize CRDT sync:', err);\n if (active) setStatus('error');\n }\n }\n\n setup();\n\n return () => {\n active = false;\n if (manager) manager.disconnect();\n };\n }, [url, initialRef, contextWasmUrl]);\n\n useEffect(() => {\n if (!proxy) return;\n\n // Re-render on any state change: local writes and incoming remote updates.\n return proxy.onChange(() => {\n setTick(t => t + 1);\n });\n }, [proxy]);\n\n const state = proxy ? (proxy.state as T) : initialRef;\n\n return { state, proxy, status };\n}\n","import React, { createContext } from 'react';\nimport type { ReactNode } from 'react';\n\nexport interface CrdtSyncContextValue {\n wasmUrl?: string;\n}\n\nexport const CrdtSyncContext = createContext<CrdtSyncContextValue>({});\n\nexport interface CrdtSyncProviderProps {\n wasmUrl?: string;\n children: ReactNode;\n}\n\nexport function CrdtSyncProvider({ wasmUrl, children }: CrdtSyncProviderProps): JSX.Element {\n return (\n <CrdtSyncContext.Provider value={{ wasmUrl }}>\n {children}\n </CrdtSyncContext.Provider>\n );\n}\n"],"mappings":";AAAA,SAAS,UAAU,WAAW,kBAAkB;AAChD,SAAS,gBAAgB,kBAAkB,UAAU,sBAAsB;;;ACD3E,OAAO,SAAS,qBAAqB;AAO9B,IAAM,kBAAkB,cAAoC,CAAC,CAAC;AAO9D,SAAS,iBAAiB,EAAE,SAAS,SAAS,GAAuC;AACxF,SACI,oCAAC,gBAAgB,UAAhB,EAAyB,OAAO,EAAE,QAAQ,KACtC,QACL;AAER;;;ADJO,SAAS,aACZ,KACA,cACA,SACqB;AACrB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAgC,IAAI;AAC9D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAqB,YAAY;AAC7D,QAAM,CAAC,EAAE,OAAO,IAAI,SAAS,CAAC;AAG9B,QAAM,CAAC,UAAU,IAAI,SAAS,YAAY;AAE1C,QAAM,EAAE,SAAS,eAAe,IAAI,WAAW,eAAe;AAE9D,YAAU,MAAM;AACZ,QAAI,SAAS;AACb,QAAI,UAAmC;AACvC,QAAI,eAAsC;AAE1C,mBAAe,QAAQ;AACnB,UAAI;AACA,cAAM,SAAS,SAAS,WAAW,cAAc;AACjD,YAAI,CAAC,OAAQ;AAGb,cAAM,WAAW,YAAY,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE;AACvE,cAAM,QAAQ,IAAI,eAAe,QAAQ;AAEzC,uBAAe,IAAI,eAAe,KAAK;AAGvC,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACnD,uBAAa,MAAM,GAAG,IAAI;AAAA,QAC9B;AAEA,YAAI,CAAC,OAAQ;AACb,iBAAS,YAAY;AAErB,cAAM,KAAK,IAAI,UAAU,GAAG;AAE5B,WAAG,SAAS,MAAM;AACd,cAAI,OAAQ,WAAU,MAAM;AAAA,QAChC;AAEA,WAAG,UAAU,MAAM;AACf,cAAI,OAAQ,WAAU,OAAO;AAAA,QACjC;AAEA,WAAG,UAAU,MAAM;AACf,cAAI,OAAQ,WAAU,YAAY;AAAA,QACtC;AAEA,kBAAU,IAAI,iBAAiB,OAAO,cAAc,EAAS;AAAA,MACjE,SAAS,KAAK;AACV,gBAAQ,MAAM,mCAAmC,GAAG;AACpD,YAAI,OAAQ,WAAU,OAAO;AAAA,MACjC;AAAA,IACJ;AAEA,UAAM;AAEN,WAAO,MAAM;AACT,eAAS;AACT,UAAI,QAAS,SAAQ,WAAW;AAAA,IACpC;AAAA,EACJ,GAAG,CAAC,KAAK,YAAY,cAAc,CAAC;AAEpC,YAAU,MAAM;AACZ,QAAI,CAAC,MAAO;AAGZ,WAAO,MAAM,SAAS,MAAM;AACxB,cAAQ,OAAK,IAAI,CAAC;AAAA,IACtB,CAAC;AAAA,EACL,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,QAAQ,QAAS,MAAM,QAAc;AAE3C,SAAO,EAAE,OAAO,OAAO,OAAO;AAClC;","names":[]}
1
+ {"version":3,"sources":["../src/useCrdtState.ts","../src/CrdtSyncContext.tsx"],"sourcesContent":["import { useState, useEffect, useContext } from 'react';\nimport { CrdtStateProxy, WebSocketManager, initWasm, WasmStateStore } from '@crdt-sync/core';\nimport { CrdtSyncContext } from './CrdtSyncContext.js';\n\nexport type CrdtStatus = 'connecting' | 'open' | 'error';\n\nexport interface UseCrdtStateResult<T extends Record<string, unknown>> {\n state: T;\n proxy: CrdtStateProxy<T> | null;\n status: CrdtStatus;\n}\n\nexport interface UseCrdtStateOptions {\n wasmUrl?: string;\n}\n\nexport function useCrdtState<T extends Record<string, unknown>>(\n url: string,\n initialState: T,\n options?: UseCrdtStateOptions\n): UseCrdtStateResult<T> {\n const [proxy, setProxy] = useState<CrdtStateProxy<T> | null>(null);\n const [status, setStatus] = useState<CrdtStatus>('connecting');\n const [, setTick] = useState(0);\n\n // Note: we're ignoring initialState updates (this behaves like useState).\n const [initialRef] = useState(initialState);\n\n const { wasmUrl: contextWasmUrl } = useContext(CrdtSyncContext);\n\n useEffect(() => {\n let active = true;\n let manager: WebSocketManager | null = null;\n let currentProxy: CrdtStateProxy<T> | null = null;\n\n async function setup() {\n try {\n await initWasm(options?.wasmUrl ?? contextWasmUrl);\n if (!active) return;\n\n // Create a unique client ID\n const clientId = 'client-' + Math.random().toString(36).substring(2, 11);\n const store = new WasmStateStore(clientId);\n\n currentProxy = new CrdtStateProxy<T>(store);\n\n // Initialize state. Cast needed: TypeScript disallows index-writes on a\n // generic T even though T extends Record<string, unknown>.\n const mutableState = currentProxy.state as Record<string, unknown>;\n for (const [key, value] of Object.entries(initialRef)) {\n mutableState[key] = value;\n }\n\n if (!active) return;\n setProxy(currentProxy);\n\n const ws = new WebSocket(url);\n\n ws.onopen = () => {\n if (active) setStatus('open');\n };\n\n ws.onerror = () => {\n if (active) setStatus('error');\n };\n\n ws.onclose = () => {\n if (active) setStatus('connecting');\n };\n\n manager = new WebSocketManager(store, currentProxy, ws as any);\n } catch (err) {\n console.error('Failed to initialize CRDT sync:', err);\n if (active) setStatus('error');\n }\n }\n\n setup();\n\n return () => {\n active = false;\n if (manager) manager.disconnect();\n };\n }, [url, initialRef, contextWasmUrl]);\n\n useEffect(() => {\n if (!proxy) return;\n\n // Re-render on any state change: local writes and incoming remote updates.\n return proxy.onChange(() => {\n setTick(t => t + 1);\n });\n }, [proxy]);\n\n const state = proxy ? (proxy.state as T) : initialRef;\n\n return { state, proxy, status };\n}\n","import React, { createContext } from 'react';\nimport type { ReactNode } from 'react';\n\nexport interface CrdtSyncContextValue {\n wasmUrl?: string;\n}\n\nexport const CrdtSyncContext = createContext<CrdtSyncContextValue>({});\n\nexport interface CrdtSyncProviderProps {\n wasmUrl?: string;\n children: ReactNode;\n}\n\nexport function CrdtSyncProvider({ wasmUrl, children }: CrdtSyncProviderProps): JSX.Element {\n return (\n <CrdtSyncContext.Provider value={{ wasmUrl }}>\n {children}\n </CrdtSyncContext.Provider>\n );\n}\n"],"mappings":";AAAA,SAAS,UAAU,WAAW,kBAAkB;AAChD,SAAS,gBAAgB,kBAAkB,UAAU,sBAAsB;;;ACD3E,OAAO,SAAS,qBAAqB;AAO9B,IAAM,kBAAkB,cAAoC,CAAC,CAAC;AAO9D,SAAS,iBAAiB,EAAE,SAAS,SAAS,GAAuC;AACxF,SACI,oCAAC,gBAAgB,UAAhB,EAAyB,OAAO,EAAE,QAAQ,KACtC,QACL;AAER;;;ADJO,SAAS,aACZ,KACA,cACA,SACqB;AACrB,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAmC,IAAI;AACjE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAqB,YAAY;AAC7D,QAAM,CAAC,EAAE,OAAO,IAAI,SAAS,CAAC;AAG9B,QAAM,CAAC,UAAU,IAAI,SAAS,YAAY;AAE1C,QAAM,EAAE,SAAS,eAAe,IAAI,WAAW,eAAe;AAE9D,YAAU,MAAM;AACZ,QAAI,SAAS;AACb,QAAI,UAAmC;AACvC,QAAI,eAAyC;AAE7C,mBAAe,QAAQ;AACnB,UAAI;AACA,cAAM,SAAS,SAAS,WAAW,cAAc;AACjD,YAAI,CAAC,OAAQ;AAGb,cAAM,WAAW,YAAY,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE;AACvE,cAAM,QAAQ,IAAI,eAAe,QAAQ;AAEzC,uBAAe,IAAI,eAAkB,KAAK;AAI1C,cAAM,eAAe,aAAa;AAClC,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACnD,uBAAa,GAAG,IAAI;AAAA,QACxB;AAEA,YAAI,CAAC,OAAQ;AACb,iBAAS,YAAY;AAErB,cAAM,KAAK,IAAI,UAAU,GAAG;AAE5B,WAAG,SAAS,MAAM;AACd,cAAI,OAAQ,WAAU,MAAM;AAAA,QAChC;AAEA,WAAG,UAAU,MAAM;AACf,cAAI,OAAQ,WAAU,OAAO;AAAA,QACjC;AAEA,WAAG,UAAU,MAAM;AACf,cAAI,OAAQ,WAAU,YAAY;AAAA,QACtC;AAEA,kBAAU,IAAI,iBAAiB,OAAO,cAAc,EAAS;AAAA,MACjE,SAAS,KAAK;AACV,gBAAQ,MAAM,mCAAmC,GAAG;AACpD,YAAI,OAAQ,WAAU,OAAO;AAAA,MACjC;AAAA,IACJ;AAEA,UAAM;AAEN,WAAO,MAAM;AACT,eAAS;AACT,UAAI,QAAS,SAAQ,WAAW;AAAA,IACpC;AAAA,EACJ,GAAG,CAAC,KAAK,YAAY,cAAc,CAAC;AAEpC,YAAU,MAAM;AACZ,QAAI,CAAC,MAAO;AAGZ,WAAO,MAAM,SAAS,MAAM;AACxB,cAAQ,OAAK,IAAI,CAAC;AAAA,IACtB,CAAC;AAAA,EACL,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,QAAQ,QAAS,MAAM,QAAc;AAE3C,SAAO,EAAE,OAAO,OAAO,OAAO;AAClC;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crdt-sync/react",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "React hook adapter for crdt-sync",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -24,7 +24,7 @@
24
24
  "react-dom": ">=16.8"
25
25
  },
26
26
  "dependencies": {
27
- "@crdt-sync/core": "^0.3.0"
27
+ "@crdt-sync/core": "^0.3.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@testing-library/jest-dom": "^6.9.1",