@crdt-sync/react 0.2.0 → 0.3.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.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CrdtStateProxy } from '@crdt-sync/core';
2
+ import { ReactNode } from 'react';
2
3
 
3
4
  type CrdtStatus = 'connecting' | 'open' | 'error';
4
5
  interface UseCrdtStateResult<T> {
@@ -11,4 +12,13 @@ interface UseCrdtStateOptions {
11
12
  }
12
13
  declare function useCrdtState<T extends Record<string, unknown>>(url: string, initialState: T, options?: UseCrdtStateOptions): UseCrdtStateResult<T>;
13
14
 
14
- export { type CrdtStatus, type UseCrdtStateOptions, type UseCrdtStateResult, useCrdtState };
15
+ interface CrdtSyncContextValue {
16
+ wasmUrl?: string;
17
+ }
18
+ interface CrdtSyncProviderProps {
19
+ wasmUrl?: string;
20
+ children: ReactNode;
21
+ }
22
+ declare function CrdtSyncProvider({ wasmUrl, children }: CrdtSyncProviderProps): JSX.Element;
23
+
24
+ export { type CrdtStatus, type CrdtSyncContextValue, CrdtSyncProvider, type CrdtSyncProviderProps, type UseCrdtStateOptions, type UseCrdtStateResult, useCrdtState };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CrdtStateProxy } from '@crdt-sync/core';
2
+ import { ReactNode } from 'react';
2
3
 
3
4
  type CrdtStatus = 'connecting' | 'open' | 'error';
4
5
  interface UseCrdtStateResult<T> {
@@ -11,4 +12,13 @@ interface UseCrdtStateOptions {
11
12
  }
12
13
  declare function useCrdtState<T extends Record<string, unknown>>(url: string, initialState: T, options?: UseCrdtStateOptions): UseCrdtStateResult<T>;
13
14
 
14
- export { type CrdtStatus, type UseCrdtStateOptions, type UseCrdtStateResult, useCrdtState };
15
+ interface CrdtSyncContextValue {
16
+ wasmUrl?: string;
17
+ }
18
+ interface CrdtSyncProviderProps {
19
+ wasmUrl?: string;
20
+ children: ReactNode;
21
+ }
22
+ declare function CrdtSyncProvider({ wasmUrl, children }: CrdtSyncProviderProps): JSX.Element;
23
+
24
+ export { type CrdtStatus, type CrdtSyncContextValue, CrdtSyncProvider, type CrdtSyncProviderProps, type UseCrdtStateOptions, type UseCrdtStateResult, useCrdtState };
package/dist/index.js CHANGED
@@ -30,29 +30,39 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ CrdtSyncProvider: () => CrdtSyncProvider,
33
34
  useCrdtState: () => useCrdtState
34
35
  });
35
36
  module.exports = __toCommonJS(index_exports);
36
37
 
37
38
  // src/useCrdtState.ts
38
- var import_react = require("react");
39
+ var import_react2 = require("react");
39
40
  var import_core = require("@crdt-sync/core");
40
- var import_crdt_sync = __toESM(require("@crdt-sync/core/pkg/web/crdt_sync.js"));
41
+
42
+ // src/CrdtSyncContext.tsx
43
+ var import_react = __toESM(require("react"));
44
+ var CrdtSyncContext = (0, import_react.createContext)({});
45
+ function CrdtSyncProvider({ wasmUrl, children }) {
46
+ return /* @__PURE__ */ import_react.default.createElement(CrdtSyncContext.Provider, { value: { wasmUrl } }, children);
47
+ }
48
+
49
+ // src/useCrdtState.ts
41
50
  function useCrdtState(url, initialState, options) {
42
- const [proxy, setProxy] = (0, import_react.useState)(null);
43
- const [status, setStatus] = (0, import_react.useState)("connecting");
44
- const [, setTick] = (0, import_react.useState)(0);
45
- const [initialRef] = (0, import_react.useState)(initialState);
46
- (0, import_react.useEffect)(() => {
51
+ const [proxy, setProxy] = (0, import_react2.useState)(null);
52
+ const [status, setStatus] = (0, import_react2.useState)("connecting");
53
+ const [, setTick] = (0, import_react2.useState)(0);
54
+ const [initialRef] = (0, import_react2.useState)(initialState);
55
+ const { wasmUrl: contextWasmUrl } = (0, import_react2.useContext)(CrdtSyncContext);
56
+ (0, import_react2.useEffect)(() => {
47
57
  let active = true;
48
58
  let manager = null;
49
59
  let currentProxy = null;
50
60
  async function setup() {
51
61
  try {
52
- await (0, import_crdt_sync.default)(options?.wasmUrl);
62
+ await (0, import_core.initWasm)(options?.wasmUrl ?? contextWasmUrl);
53
63
  if (!active) return;
54
64
  const clientId = "client-" + Math.random().toString(36).substring(2, 11);
55
- const store = new import_crdt_sync.WasmStateStore(clientId);
65
+ const store = new import_core.WasmStateStore(clientId);
56
66
  currentProxy = new import_core.CrdtStateProxy(store);
57
67
  for (const [key, value] of Object.entries(initialRef)) {
58
68
  currentProxy.state[key] = value;
@@ -80,10 +90,10 @@ function useCrdtState(url, initialState, options) {
80
90
  active = false;
81
91
  if (manager) manager.disconnect();
82
92
  };
83
- }, [url, initialRef]);
84
- (0, import_react.useEffect)(() => {
93
+ }, [url, initialRef, contextWasmUrl]);
94
+ (0, import_react2.useEffect)(() => {
85
95
  if (!proxy) return;
86
- return proxy.onUpdate(() => {
96
+ return proxy.onChange(() => {
87
97
  setTick((t) => t + 1);
88
98
  });
89
99
  }, [proxy]);
@@ -92,6 +102,7 @@ function useCrdtState(url, initialState, options) {
92
102
  }
93
103
  // Annotate the CommonJS export names for ESM import in node:
94
104
  0 && (module.exports = {
105
+ CrdtSyncProvider,
95
106
  useCrdtState
96
107
  });
97
108
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/useCrdtState.ts"],"sourcesContent":["export { useCrdtState } from './useCrdtState';\nexport type { CrdtStatus, UseCrdtStateResult, UseCrdtStateOptions } from './useCrdtState';\n","import { useState, useEffect } from 'react';\nimport { CrdtStateProxy, WebSocketManager } from '@crdt-sync/core';\n// @ts-ignore: Wasm module will be generated during the full build\nimport init, { WasmStateStore } from '@crdt-sync/core/pkg/web/crdt_sync.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 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 init(options?.wasmUrl);\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]);\n\n useEffect(() => {\n if (!proxy) return;\n\n // Subscribe to proxy updates to trigger React re-renders.\n return proxy.onUpdate(() => {\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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAoC;AACpC,kBAAiD;AAEjD,uBAAqC;AAc9B,SAAS,aACZ,KACA,cACA,SACqB;AACrB,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAgC,IAAI;AAC9D,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAqB,YAAY;AAC7D,QAAM,CAAC,EAAE,OAAO,QAAI,uBAAS,CAAC;AAG9B,QAAM,CAAC,UAAU,QAAI,uBAAS,YAAY;AAE1C,8BAAU,MAAM;AACZ,QAAI,SAAS;AACb,QAAI,UAAmC;AACvC,QAAI,eAAsC;AAE1C,mBAAe,QAAQ;AACnB,UAAI;AACA,kBAAM,iBAAAA,SAAK,SAAS,OAAO;AAC3B,YAAI,CAAC,OAAQ;AAGb,cAAM,WAAW,YAAY,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,EAAE;AACvE,cAAM,QAAQ,IAAI,gCAAe,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,UAAU,CAAC;AAEpB,8BAAU,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":["init"]}
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"]}
package/dist/index.mjs CHANGED
@@ -1,19 +1,28 @@
1
1
  // src/useCrdtState.ts
2
- import { useState, useEffect } from "react";
3
- import { CrdtStateProxy, WebSocketManager } from "@crdt-sync/core";
4
- import init, { WasmStateStore } from "@crdt-sync/core/pkg/web/crdt_sync.js";
2
+ import { useState, useEffect, useContext } from "react";
3
+ import { CrdtStateProxy, WebSocketManager, initWasm, WasmStateStore } from "@crdt-sync/core";
4
+
5
+ // src/CrdtSyncContext.tsx
6
+ import React, { createContext } from "react";
7
+ var CrdtSyncContext = createContext({});
8
+ function CrdtSyncProvider({ wasmUrl, children }) {
9
+ return /* @__PURE__ */ React.createElement(CrdtSyncContext.Provider, { value: { wasmUrl } }, children);
10
+ }
11
+
12
+ // src/useCrdtState.ts
5
13
  function useCrdtState(url, initialState, options) {
6
14
  const [proxy, setProxy] = useState(null);
7
15
  const [status, setStatus] = useState("connecting");
8
16
  const [, setTick] = useState(0);
9
17
  const [initialRef] = useState(initialState);
18
+ const { wasmUrl: contextWasmUrl } = useContext(CrdtSyncContext);
10
19
  useEffect(() => {
11
20
  let active = true;
12
21
  let manager = null;
13
22
  let currentProxy = null;
14
23
  async function setup() {
15
24
  try {
16
- await init(options?.wasmUrl);
25
+ await initWasm(options?.wasmUrl ?? contextWasmUrl);
17
26
  if (!active) return;
18
27
  const clientId = "client-" + Math.random().toString(36).substring(2, 11);
19
28
  const store = new WasmStateStore(clientId);
@@ -44,10 +53,10 @@ function useCrdtState(url, initialState, options) {
44
53
  active = false;
45
54
  if (manager) manager.disconnect();
46
55
  };
47
- }, [url, initialRef]);
56
+ }, [url, initialRef, contextWasmUrl]);
48
57
  useEffect(() => {
49
58
  if (!proxy) return;
50
- return proxy.onUpdate(() => {
59
+ return proxy.onChange(() => {
51
60
  setTick((t) => t + 1);
52
61
  });
53
62
  }, [proxy]);
@@ -55,6 +64,7 @@ function useCrdtState(url, initialState, options) {
55
64
  return { state, proxy, status };
56
65
  }
57
66
  export {
67
+ CrdtSyncProvider,
58
68
  useCrdtState
59
69
  };
60
70
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useCrdtState.ts"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { CrdtStateProxy, WebSocketManager } from '@crdt-sync/core';\n// @ts-ignore: Wasm module will be generated during the full build\nimport init, { WasmStateStore } from '@crdt-sync/core/pkg/web/crdt_sync.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 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 init(options?.wasmUrl);\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]);\n\n useEffect(() => {\n if (!proxy) return;\n\n // Subscribe to proxy updates to trigger React re-renders.\n return proxy.onUpdate(() => {\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"],"mappings":";AAAA,SAAS,UAAU,iBAAiB;AACpC,SAAS,gBAAgB,wBAAwB;AAEjD,OAAO,QAAQ,sBAAsB;AAc9B,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,YAAU,MAAM;AACZ,QAAI,SAAS;AACb,QAAI,UAAmC;AACvC,QAAI,eAAsC;AAE1C,mBAAe,QAAQ;AACnB,UAAI;AACA,cAAM,KAAK,SAAS,OAAO;AAC3B,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,UAAU,CAAC;AAEpB,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> {\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crdt-sync/react",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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.2.0"
27
+ "@crdt-sync/core": "^0.3.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@testing-library/jest-dom": "^6.9.1",