@ysdk/react 0.1.0 → 0.1.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/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var M=Object.create;var x=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var $=Object.getPrototypeOf,O=Object.prototype.hasOwnProperty;var U=(e,t)=>{for(var r in t)x(e,r,{get:t[r],enumerable:!0})},Y=(e,t,r,u)=>{if(t&&typeof t=="object"||typeof t=="function")for(let c of V(t))!O.call(e,c)&&c!==r&&x(e,c,{get:()=>t[c],enumerable:!(u=L(t,c))||u.enumerable});return e};var F=(e,t,r)=>(r=e!=null?M($(e)):{},Y(t||!e||!e.__esModule?x(r,"default",{value:e,enumerable:!0}):r,e)),B=e=>Y(x({},"__esModule",{value:!0}),e);var W={};U(W,{YSDKProvider:()=>D,useBroadcast:()=>E,usePresence:()=>A,useShared:()=>P,useSharedArray:()=>C,useSharedText:()=>w,useYSDK:()=>i});module.exports=B(W);var h=require("react"),K=F(require("yjs"),1),b=require("y-websocket");var T=require("react"),v=(0,T.createContext)(null);function i(){let e=(0,T.useContext)(v);if(!e)throw new Error("YSDK hooks must be used within a <YSDKProvider>");return e}var y=require("react/jsx-runtime");function D({url:e,room:t,cloud:r,doc:u,children:c}){let[n,s]=(0,h.useState)(null);return(0,h.useEffect)(()=>{let o=u||new K.Doc,a=null;if(!u)if(r){let d=`${r.baseUrl||"wss://sync.elvenvtt.com"}/ysdk`;a=new b.WebsocketProvider(`${d}?apiKey=${r.apiKey}`,r.room,o)}else e&&t&&(a=new b.WebsocketProvider(e,t,o));return s({doc:o,awareness:a?.awareness??null}),()=>{a?.destroy(),u||o.destroy()}},[e,t,r?.apiKey,r?.room,r?.baseUrl,u]),n?(0,y.jsx)(v.Provider,{value:n,children:c}):null}var m=require("react");function P(e,t){let{doc:r}=i(),[u,c]=(0,m.useState)(()=>{let o=r.getMap("ysdk").get(e);return o!==void 0?o:t});(0,m.useEffect)(()=>{let s=r.getMap("ysdk");s.has(e)||s.set(e,t);let o=s.get(e);o!==void 0&&c(o);let a=p=>{if(p.keysChanged.has(e)){let d=s.get(e);c(d!==void 0?d:t)}};return s.observe(a),()=>s.unobserve(a)},[r,e]);let n=(0,m.useCallback)(s=>{r.getMap("ysdk").set(e,s)},[r,e]);return[u,n]}var l=require("react");function C(e){let{doc:t}=i(),[r,u]=(0,l.useState)(()=>t.getArray(e).toArray());(0,l.useEffect)(()=>{let n=t.getArray(e);u(n.toArray());let s=()=>u(n.toArray());return n.observe(s),()=>n.unobserve(s)},[t,e]);let c=(0,l.useMemo)(()=>{let n=t.getArray(e);return{push:(...s)=>n.push(s),delete:(s,o=1)=>n.delete(s,o),insert:(s,...o)=>n.insert(s,o),clear:()=>{n.length>0&&n.delete(0,n.length)},get length(){return n.length}}},[t,e]);return[r,c]}var S=require("react");function w(e,t=""){let{doc:r}=i(),u=r.getText(e),[c,n]=(0,S.useState)(()=>u.toString()||t);(0,S.useEffect)(()=>{let o=r.getText(e);o.length===0&&t&&o.insert(0,t),n(o.toString());let a=()=>n(o.toString());return o.observe(a),()=>o.unobserve(a)},[r,e]);let s=(0,S.useCallback)(o=>{let a=r.getText(e);r.transact(()=>{a.delete(0,a.length),a.insert(0,o)})},[r,e]);return{value:c,ytext:u,setValue:s}}var g=require("react");function A(e){let{awareness:t}=i(),[r,u]=(0,g.useState)([]);(0,g.useEffect)(()=>{if(!t)return;e&&t.setLocalStateField("user",e);let n=()=>{let s=[];t.getStates().forEach((o,a)=>{a!==t.clientID&&o.user&&s.push(o.user)}),u(s)};return n(),t.on("change",n),()=>t.off("change",n)},[t]);let c=(0,g.useCallback)(n=>{if(!t)return;let s=t.getLocalState()?.user||{};t.setLocalStateField("user",{...s,...n})},[t]);return{peers:r,setPresence:c,count:r.length}}var f=require("react");function E(e){let{awareness:t}=i(),r=(0,f.useRef)(new Set),u=(0,f.useRef)(new Map);(0,f.useEffect)(()=>{if(!t)return;let s=()=>{t.getStates().forEach((o,a)=>{if(a===t.clientID)return;let p=o?.[`_bc:${e}`];if(!p)return;let d=u.current.get(a)??0;p.ts>d&&(u.current.set(a,p.ts),r.current.forEach(R=>R(p.data,a)))})};return t.on("change",s),()=>t.off("change",s)},[t,e]);let c=(0,f.useCallback)(s=>{t&&t.setLocalStateField(`_bc:${e}`,{data:s,ts:Date.now()})},[t,e]),n=(0,f.useCallback)(s=>(r.current.add(s),()=>{r.current.delete(s)}),[]);return{broadcast:c,onMessage:n}}0&&(module.exports={YSDKProvider,useBroadcast,usePresence,useShared,useSharedArray,useSharedText,useYSDK});
1
+ "use strict";var M=Object.create;var x=Object.defineProperty;var L=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var O=Object.getPrototypeOf,U=Object.prototype.hasOwnProperty;var B=(e,t)=>{for(var r in t)x(e,r,{get:t[r],enumerable:!0})},Y=(e,t,r,u)=>{if(t&&typeof t=="object"||typeof t=="function")for(let c of V(t))!U.call(e,c)&&c!==r&&x(e,c,{get:()=>t[c],enumerable:!(u=L(t,c))||u.enumerable});return e};var F=(e,t,r)=>(r=e!=null?M(O(e)):{},Y(t||!e||!e.__esModule?x(r,"default",{value:e,enumerable:!0}):r,e)),$=e=>Y(x({},"__esModule",{value:!0}),e);var W={};B(W,{YSDKProvider:()=>D,useBroadcast:()=>E,usePresence:()=>A,useShared:()=>P,useSharedArray:()=>C,useSharedText:()=>w,useYSDK:()=>i});module.exports=$(W);var h=require("react"),K=F(require("yjs"),1),b=require("y-websocket");var T=require("react"),v=(0,T.createContext)(null);function i(){let e=(0,T.useContext)(v);if(!e)throw new Error("YSDK hooks must be used within a <YSDKProvider>");return e}var y=require("react/jsx-runtime");function D({url:e,room:t,cloud:r,doc:u,children:c}){let[n,s]=(0,h.useState)(null);return(0,h.useEffect)(()=>{let o=u||new K.Doc,a=null;if(!u)if(r){let d=`${r.baseUrl||"wss://sync.elvenvtt.com"}/ysdk`;a=new b.WebsocketProvider(d,r.room,o,{params:{apiKey:r.apiKey},maxBackoffTime:3e4})}else e&&t&&(a=new b.WebsocketProvider(e,t,o));return s({doc:o,awareness:a?.awareness??null}),()=>{a?.destroy(),u||o.destroy()}},[e,t,r?.apiKey,r?.room,r?.baseUrl,u]),n?(0,y.jsx)(v.Provider,{value:n,children:c}):null}var m=require("react");function P(e,t){let{doc:r}=i(),[u,c]=(0,m.useState)(()=>{let o=r.getMap("ysdk").get(e);return o!==void 0?o:t});(0,m.useEffect)(()=>{let s=r.getMap("ysdk");s.has(e)||s.set(e,t);let o=s.get(e);o!==void 0&&c(o);let a=p=>{if(p.keysChanged.has(e)){let d=s.get(e);c(d!==void 0?d:t)}};return s.observe(a),()=>s.unobserve(a)},[r,e]);let n=(0,m.useCallback)(s=>{r.getMap("ysdk").set(e,s)},[r,e]);return[u,n]}var l=require("react");function C(e){let{doc:t}=i(),[r,u]=(0,l.useState)(()=>t.getArray(e).toArray());(0,l.useEffect)(()=>{let n=t.getArray(e);u(n.toArray());let s=()=>u(n.toArray());return n.observe(s),()=>n.unobserve(s)},[t,e]);let c=(0,l.useMemo)(()=>{let n=t.getArray(e);return{push:(...s)=>n.push(s),delete:(s,o=1)=>n.delete(s,o),insert:(s,...o)=>n.insert(s,o),clear:()=>{n.length>0&&n.delete(0,n.length)},get length(){return n.length}}},[t,e]);return[r,c]}var S=require("react");function w(e,t=""){let{doc:r}=i(),u=r.getText(e),[c,n]=(0,S.useState)(()=>u.toString()||t);(0,S.useEffect)(()=>{let o=r.getText(e);o.length===0&&t&&o.insert(0,t),n(o.toString());let a=()=>n(o.toString());return o.observe(a),()=>o.unobserve(a)},[r,e]);let s=(0,S.useCallback)(o=>{let a=r.getText(e);r.transact(()=>{a.delete(0,a.length),a.insert(0,o)})},[r,e]);return{value:c,ytext:u,setValue:s}}var g=require("react");function A(e){let{awareness:t}=i(),[r,u]=(0,g.useState)([]);(0,g.useEffect)(()=>{if(!t)return;e&&t.setLocalStateField("user",e);let n=()=>{let s=[];t.getStates().forEach((o,a)=>{a!==t.clientID&&o.user&&s.push(o.user)}),u(s)};return n(),t.on("change",n),()=>t.off("change",n)},[t]);let c=(0,g.useCallback)(n=>{if(!t)return;let s=t.getLocalState()?.user||{};t.setLocalStateField("user",{...s,...n})},[t]);return{peers:r,setPresence:c,count:r.length}}var f=require("react");function E(e){let{awareness:t}=i(),r=(0,f.useRef)(new Set),u=(0,f.useRef)(new Map);(0,f.useEffect)(()=>{if(!t)return;let s=()=>{t.getStates().forEach((o,a)=>{if(a===t.clientID)return;let p=o?.[`_bc:${e}`];if(!p)return;let d=u.current.get(a)??0;p.ts>d&&(u.current.set(a,p.ts),r.current.forEach(R=>R(p.data,a)))})};return t.on("change",s),()=>t.off("change",s)},[t,e]);let c=(0,f.useCallback)(s=>{t&&t.setLocalStateField(`_bc:${e}`,{data:s,ts:Date.now()})},[t,e]),n=(0,f.useCallback)(s=>(r.current.add(s),()=>{r.current.delete(s)}),[]);return{broadcast:c,onMessage:n}}0&&(module.exports={YSDKProvider,useBroadcast,usePresence,useShared,useSharedArray,useSharedText,useYSDK});
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/provider.tsx","../src/context.ts","../src/useShared.ts","../src/useSharedArray.ts","../src/useSharedText.ts","../src/usePresence.ts","../src/useBroadcast.ts"],"sourcesContent":["export { YSDKProvider, type YSDKProviderProps, type YSDKCloudConfig } from './provider';\nexport { useShared } from './useShared';\nexport { useSharedArray, type SharedArrayOps } from './useSharedArray';\nexport { useSharedText, type SharedText } from './useSharedText';\nexport { usePresence, type PresenceResult } from './usePresence';\nexport { useBroadcast } from './useBroadcast';\nexport { useYSDK } from './context';\n","import React, { useEffect, useState, useMemo } from 'react';\nimport * as Y from 'yjs';\nimport { WebsocketProvider } from 'y-websocket';\nimport { YSDKCtx, type YSDKContextValue } from './context';\n\nexport interface YSDKCloudConfig {\n /** YSDK Cloud API key (ysdk_live_... or ysdk_test_...) */\n apiKey: string;\n /** Room name */\n room: string;\n /** Override base WebSocket URL (defaults to wss://sync.elvenvtt.com) */\n baseUrl?: string;\n}\n\nexport interface YSDKProviderProps {\n /** WebSocket server URL (e.g., \"ws://localhost:1234\") - for self-hosted y-websocket servers */\n url?: string;\n /** Room name (used with url) */\n room?: string;\n /** YSDK Cloud config - connects to hosted infrastructure */\n cloud?: YSDKCloudConfig;\n /** Bring your own Y.Doc (you handle sync) */\n doc?: Y.Doc;\n children: React.ReactNode;\n}\n\nexport function YSDKProvider({ url, room, cloud, doc: externalDoc, children }: YSDKProviderProps) {\n const [ctx, setCtx] = useState<YSDKContextValue | null>(null);\n\n useEffect(() => {\n const doc = externalDoc || new Y.Doc();\n let provider: WebsocketProvider | null = null;\n\n if (!externalDoc) {\n if (cloud) {\n // YSDK Cloud: connect via standard y-websocket to our hosted infrastructure\n const base = cloud.baseUrl || 'wss://sync.elvenvtt.com';\n const wsUrl = `${base}/ysdk`;\n provider = new WebsocketProvider(`${wsUrl}?apiKey=${cloud.apiKey}`, cloud.room, doc);\n } else if (url && room) {\n // Self-hosted: connect to any y-websocket server\n provider = new WebsocketProvider(url, room, doc);\n }\n }\n\n setCtx({\n doc,\n awareness: provider?.awareness ?? null,\n });\n\n return () => {\n provider?.destroy();\n if (!externalDoc) doc.destroy();\n };\n }, [url, room, cloud?.apiKey, cloud?.room, cloud?.baseUrl, externalDoc]);\n\n if (!ctx) return null;\n\n return <YSDKCtx.Provider value={ctx}>{children}</YSDKCtx.Provider>;\n}\n","import { createContext, useContext } from 'react';\nimport type * as Y from 'yjs';\nimport type { Awareness } from 'y-protocols/awareness';\n\nexport interface YSDKContextValue {\n doc: Y.Doc;\n awareness: Awareness | null;\n}\n\nexport const YSDKCtx = createContext<YSDKContextValue | null>(null);\n\nexport function useYSDK(): YSDKContextValue {\n const ctx = useContext(YSDKCtx);\n if (!ctx) {\n throw new Error('YSDK hooks must be used within a <YSDKProvider>');\n }\n return ctx;\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport { useYSDK } from './context';\n\n/**\n * Shared state that syncs across all connected clients.\n * Works like useState, but multiplayer.\n *\n * Values are stored in a shared Y.Map. Objects are stored as opaque values\n * (last-write-wins on the whole object, not per-field merge).\n *\n * @param key - Unique key for this piece of state\n * @param defaultValue - Initial value if not yet set\n * @returns [value, setValue] - Current value and setter, just like useState\n */\nexport function useShared<T>(key: string, defaultValue: T): [T, (value: T) => void] {\n const { doc } = useYSDK();\n\n const [value, setValue] = useState<T>(() => {\n const map = doc.getMap('ysdk');\n const v = map.get(key);\n return v !== undefined ? (v as T) : defaultValue;\n });\n\n useEffect(() => {\n const map = doc.getMap('ysdk');\n\n if (!map.has(key)) {\n map.set(key, defaultValue);\n }\n\n const current = map.get(key);\n if (current !== undefined) setValue(current as T);\n\n const observer = (event: any) => {\n if (event.keysChanged.has(key)) {\n const newVal = map.get(key);\n setValue(newVal !== undefined ? (newVal as T) : defaultValue);\n }\n };\n\n map.observe(observer);\n return () => map.unobserve(observer);\n }, [doc, key]);\n\n const setShared = useCallback(\n (newValue: T) => {\n doc.getMap('ysdk').set(key, newValue);\n },\n [doc, key],\n );\n\n return [value, setShared];\n}\n","import { useState, useEffect, useMemo } from 'react';\nimport { useYSDK } from './context';\n\nexport interface SharedArrayOps<T> {\n /** Append items to end of array */\n push: (...items: T[]) => void;\n /** Delete count items starting at index */\n delete: (index: number, count?: number) => void;\n /** Insert items at index */\n insert: (index: number, ...items: T[]) => void;\n /** Remove all items */\n clear: () => void;\n /** Number of items */\n length: number;\n}\n\n/**\n * Shared array that syncs across all connected clients.\n * Concurrent insertions merge cleanly (CRDT).\n *\n * @param key - Unique key for this array\n * @returns [items, ops] - Current items and operations\n */\nexport function useSharedArray<T>(key: string): [T[], SharedArrayOps<T>] {\n const { doc } = useYSDK();\n\n const [items, setItems] = useState<T[]>(() => {\n return doc.getArray<T>(key).toArray();\n });\n\n useEffect(() => {\n const arr = doc.getArray<T>(key);\n setItems(arr.toArray());\n\n const observer = () => setItems(arr.toArray());\n arr.observe(observer);\n return () => arr.unobserve(observer);\n }, [doc, key]);\n\n const ops = useMemo<SharedArrayOps<T>>(() => {\n const arr = doc.getArray<T>(key);\n return {\n push: (...items) => arr.push(items),\n delete: (index, count = 1) => arr.delete(index, count),\n insert: (index, ...items) => arr.insert(index, items),\n clear: () => {\n if (arr.length > 0) arr.delete(0, arr.length);\n },\n get length() {\n return arr.length;\n },\n };\n }, [doc, key]);\n\n return [items, ops];\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport * as Y from 'yjs';\nimport { useYSDK } from './context';\n\nexport interface SharedText {\n /** Current text value as a plain string */\n value: string;\n /** Raw Y.Text instance for binding to editors (CodeMirror, ProseMirror, Tiptap, etc.) */\n ytext: Y.Text;\n /** Replace entire text content */\n setValue: (text: string) => void;\n}\n\n/**\n * Shared text that syncs across all connected clients.\n * Character-by-character CRDT merging - concurrent edits merge cleanly.\n *\n * For simple inputs, use `value` and `setValue`.\n * For rich text editors, bind `ytext` directly (e.g., y-codemirror, y-prosemirror).\n *\n * @param key - Unique key for this text\n * @param defaultValue - Initial text content\n */\nexport function useSharedText(key: string, defaultValue = ''): SharedText {\n const { doc } = useYSDK();\n const ytext = doc.getText(key);\n\n const [value, setLocal] = useState(() => {\n const current = ytext.toString();\n return current || defaultValue;\n });\n\n useEffect(() => {\n const text = doc.getText(key);\n\n if (text.length === 0 && defaultValue) {\n text.insert(0, defaultValue);\n }\n\n setLocal(text.toString());\n\n const observer = () => setLocal(text.toString());\n text.observe(observer);\n return () => text.unobserve(observer);\n }, [doc, key]);\n\n const setValue = useCallback(\n (text: string) => {\n const ytext = doc.getText(key);\n doc.transact(() => {\n ytext.delete(0, ytext.length);\n ytext.insert(0, text);\n });\n },\n [doc, key],\n );\n\n return { value, ytext, setValue };\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport { useYSDK } from './context';\n\nexport interface PresenceResult<T> {\n /** All other connected users' presence state */\n peers: T[];\n /** Update your own presence state (merges with existing) */\n setPresence: (state: Partial<T>) => void;\n /** Number of connected peers (not including self) */\n count: number;\n}\n\n/**\n * Track who's connected and share ephemeral user state (cursors, selections, etc.).\n * Presence data is NOT persisted - it exists only while users are connected.\n *\n * Requires a WebSocket connection (no presence in local-only mode).\n *\n * @param initialState - Your initial presence state (e.g., { name: 'Andy', cursor: { x: 0, y: 0 } })\n * @returns { peers, setPresence, count }\n */\nexport function usePresence<T extends Record<string, any>>(initialState?: T): PresenceResult<T> {\n const { awareness } = useYSDK();\n const [peers, setPeers] = useState<T[]>([]);\n\n useEffect(() => {\n if (!awareness) return;\n\n if (initialState) {\n awareness.setLocalStateField('user', initialState);\n }\n\n const update = () => {\n const states: T[] = [];\n awareness.getStates().forEach((state: any, clientId: number) => {\n if (clientId !== awareness.clientID && state.user) {\n states.push(state.user);\n }\n });\n setPeers(states);\n };\n\n update();\n awareness.on('change', update);\n return () => awareness.off('change', update);\n }, [awareness]);\n\n const setPresence = useCallback(\n (state: Partial<T>) => {\n if (!awareness) return;\n const current = awareness.getLocalState()?.user || {};\n awareness.setLocalStateField('user', { ...current, ...state });\n },\n [awareness],\n );\n\n return { peers, setPresence, count: peers.length };\n}\n","import { useEffect, useCallback, useRef } from 'react';\nimport { useYSDK } from './context';\n\n/**\n * Send ephemeral messages to all connected clients.\n * Messages are NOT persisted - they're fire-and-forget.\n *\n * Uses awareness state under the hood. Good for cursor positions,\n * pings, reactions, and other transient events.\n *\n * Requires a WebSocket connection (no broadcast in local-only mode).\n *\n * @param channel - Channel name to scope messages\n * @returns { broadcast, onMessage }\n */\nexport function useBroadcast<T = any>(channel: string) {\n const { awareness } = useYSDK();\n const handlersRef = useRef<Set<(data: T, sender: number) => void>>(new Set());\n const lastSeenRef = useRef<Map<number, number>>(new Map());\n\n useEffect(() => {\n if (!awareness) return;\n\n const handler = () => {\n awareness.getStates().forEach((state: any, clientId: number) => {\n if (clientId === awareness.clientID) return;\n const msg = state?.[`_bc:${channel}`];\n if (!msg) return;\n\n const lastSeen = lastSeenRef.current.get(clientId) ?? 0;\n if (msg.ts > lastSeen) {\n lastSeenRef.current.set(clientId, msg.ts);\n handlersRef.current.forEach((h) => h(msg.data, clientId));\n }\n });\n };\n\n awareness.on('change', handler);\n return () => awareness.off('change', handler);\n }, [awareness, channel]);\n\n const broadcast = useCallback(\n (data: T) => {\n if (!awareness) return;\n awareness.setLocalStateField(`_bc:${channel}`, {\n data,\n ts: Date.now(),\n });\n },\n [awareness, channel],\n );\n\n const onMessage = useCallback((handler: (data: T, sender: number) => void) => {\n handlersRef.current.add(handler);\n return () => {\n handlersRef.current.delete(handler);\n };\n }, []);\n\n return { broadcast, onMessage };\n}\n"],"mappings":"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","names":["index_exports","__export","YSDKProvider","useBroadcast","usePresence","useShared","useSharedArray","useSharedText","useYSDK","__toCommonJS","import_react","Y","import_y_websocket","import_react","YSDKCtx","useYSDK","ctx","import_jsx_runtime","YSDKProvider","url","room","cloud","externalDoc","children","ctx","setCtx","doc","provider","wsUrl","YSDKCtx","import_react","useShared","key","defaultValue","doc","useYSDK","value","setValue","v","map","current","observer","event","newVal","setShared","newValue","import_react","useSharedArray","key","doc","useYSDK","items","setItems","arr","observer","ops","index","count","import_react","useSharedText","key","defaultValue","doc","useYSDK","ytext","value","setLocal","text","observer","setValue","import_react","usePresence","initialState","awareness","useYSDK","peers","setPeers","update","states","state","clientId","setPresence","current","import_react","useBroadcast","channel","awareness","useYSDK","handlersRef","lastSeenRef","handler","state","clientId","msg","lastSeen","h","broadcast","data","onMessage"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/provider.tsx","../src/context.ts","../src/useShared.ts","../src/useSharedArray.ts","../src/useSharedText.ts","../src/usePresence.ts","../src/useBroadcast.ts"],"sourcesContent":["export { YSDKProvider, type YSDKProviderProps, type YSDKCloudConfig } from './provider';\nexport { useShared } from './useShared';\nexport { useSharedArray, type SharedArrayOps } from './useSharedArray';\nexport { useSharedText, type SharedText } from './useSharedText';\nexport { usePresence, type PresenceResult } from './usePresence';\nexport { useBroadcast } from './useBroadcast';\nexport { useYSDK } from './context';\n","import React, { useEffect, useState, useMemo } from 'react';\nimport * as Y from 'yjs';\nimport { WebsocketProvider } from 'y-websocket';\nimport { YSDKCtx, type YSDKContextValue } from './context';\n\nexport interface YSDKCloudConfig {\n /** YSDK Cloud API key (ysdk_live_... or ysdk_test_...) */\n apiKey: string;\n /** Room name */\n room: string;\n /** Override base WebSocket URL (defaults to wss://sync.elvenvtt.com) */\n baseUrl?: string;\n}\n\nexport interface YSDKProviderProps {\n /** WebSocket server URL (e.g., \"ws://localhost:1234\") - for self-hosted y-websocket servers */\n url?: string;\n /** Room name (used with url) */\n room?: string;\n /** YSDK Cloud config - connects to hosted infrastructure */\n cloud?: YSDKCloudConfig;\n /** Bring your own Y.Doc (you handle sync) */\n doc?: Y.Doc;\n children: React.ReactNode;\n}\n\nexport function YSDKProvider({ url, room, cloud, doc: externalDoc, children }: YSDKProviderProps) {\n const [ctx, setCtx] = useState<YSDKContextValue | null>(null);\n\n useEffect(() => {\n const doc = externalDoc || new Y.Doc();\n let provider: WebsocketProvider | null = null;\n\n if (!externalDoc) {\n if (cloud) {\n // YSDK Cloud: connect via standard y-websocket to our hosted infrastructure\n const base = cloud.baseUrl || 'wss://sync.elvenvtt.com';\n const wsUrl = `${base}/ysdk`;\n provider = new WebsocketProvider(wsUrl, cloud.room, doc, {\n params: { apiKey: cloud.apiKey },\n maxBackoffTime: 30000, // Max 30s between reconnect attempts (default 2.5s is too aggressive)\n } as any);\n } else if (url && room) {\n // Self-hosted: connect to any y-websocket server\n provider = new WebsocketProvider(url, room, doc);\n }\n }\n\n setCtx({\n doc,\n awareness: provider?.awareness ?? null,\n });\n\n return () => {\n provider?.destroy();\n if (!externalDoc) doc.destroy();\n };\n }, [url, room, cloud?.apiKey, cloud?.room, cloud?.baseUrl, externalDoc]);\n\n if (!ctx) return null;\n\n return <YSDKCtx.Provider value={ctx}>{children}</YSDKCtx.Provider>;\n}\n","import { createContext, useContext } from 'react';\nimport type * as Y from 'yjs';\nimport type { Awareness } from 'y-protocols/awareness';\n\nexport interface YSDKContextValue {\n doc: Y.Doc;\n awareness: Awareness | null;\n}\n\nexport const YSDKCtx = createContext<YSDKContextValue | null>(null);\n\nexport function useYSDK(): YSDKContextValue {\n const ctx = useContext(YSDKCtx);\n if (!ctx) {\n throw new Error('YSDK hooks must be used within a <YSDKProvider>');\n }\n return ctx;\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport { useYSDK } from './context';\n\n/**\n * Shared state that syncs across all connected clients.\n * Works like useState, but multiplayer.\n *\n * Values are stored in a shared Y.Map. Objects are stored as opaque values\n * (last-write-wins on the whole object, not per-field merge).\n *\n * @param key - Unique key for this piece of state\n * @param defaultValue - Initial value if not yet set\n * @returns [value, setValue] - Current value and setter, just like useState\n */\nexport function useShared<T>(key: string, defaultValue: T): [T, (value: T) => void] {\n const { doc } = useYSDK();\n\n const [value, setValue] = useState<T>(() => {\n const map = doc.getMap('ysdk');\n const v = map.get(key);\n return v !== undefined ? (v as T) : defaultValue;\n });\n\n useEffect(() => {\n const map = doc.getMap('ysdk');\n\n if (!map.has(key)) {\n map.set(key, defaultValue);\n }\n\n const current = map.get(key);\n if (current !== undefined) setValue(current as T);\n\n const observer = (event: any) => {\n if (event.keysChanged.has(key)) {\n const newVal = map.get(key);\n setValue(newVal !== undefined ? (newVal as T) : defaultValue);\n }\n };\n\n map.observe(observer);\n return () => map.unobserve(observer);\n }, [doc, key]);\n\n const setShared = useCallback(\n (newValue: T) => {\n doc.getMap('ysdk').set(key, newValue);\n },\n [doc, key],\n );\n\n return [value, setShared];\n}\n","import { useState, useEffect, useMemo } from 'react';\nimport { useYSDK } from './context';\n\nexport interface SharedArrayOps<T> {\n /** Append items to end of array */\n push: (...items: T[]) => void;\n /** Delete count items starting at index */\n delete: (index: number, count?: number) => void;\n /** Insert items at index */\n insert: (index: number, ...items: T[]) => void;\n /** Remove all items */\n clear: () => void;\n /** Number of items */\n length: number;\n}\n\n/**\n * Shared array that syncs across all connected clients.\n * Concurrent insertions merge cleanly (CRDT).\n *\n * @param key - Unique key for this array\n * @returns [items, ops] - Current items and operations\n */\nexport function useSharedArray<T>(key: string): [T[], SharedArrayOps<T>] {\n const { doc } = useYSDK();\n\n const [items, setItems] = useState<T[]>(() => {\n return doc.getArray<T>(key).toArray();\n });\n\n useEffect(() => {\n const arr = doc.getArray<T>(key);\n setItems(arr.toArray());\n\n const observer = () => setItems(arr.toArray());\n arr.observe(observer);\n return () => arr.unobserve(observer);\n }, [doc, key]);\n\n const ops = useMemo<SharedArrayOps<T>>(() => {\n const arr = doc.getArray<T>(key);\n return {\n push: (...items) => arr.push(items),\n delete: (index, count = 1) => arr.delete(index, count),\n insert: (index, ...items) => arr.insert(index, items),\n clear: () => {\n if (arr.length > 0) arr.delete(0, arr.length);\n },\n get length() {\n return arr.length;\n },\n };\n }, [doc, key]);\n\n return [items, ops];\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport * as Y from 'yjs';\nimport { useYSDK } from './context';\n\nexport interface SharedText {\n /** Current text value as a plain string */\n value: string;\n /** Raw Y.Text instance for binding to editors (CodeMirror, ProseMirror, Tiptap, etc.) */\n ytext: Y.Text;\n /** Replace entire text content */\n setValue: (text: string) => void;\n}\n\n/**\n * Shared text that syncs across all connected clients.\n * Character-by-character CRDT merging - concurrent edits merge cleanly.\n *\n * For simple inputs, use `value` and `setValue`.\n * For rich text editors, bind `ytext` directly (e.g., y-codemirror, y-prosemirror).\n *\n * @param key - Unique key for this text\n * @param defaultValue - Initial text content\n */\nexport function useSharedText(key: string, defaultValue = ''): SharedText {\n const { doc } = useYSDK();\n const ytext = doc.getText(key);\n\n const [value, setLocal] = useState(() => {\n const current = ytext.toString();\n return current || defaultValue;\n });\n\n useEffect(() => {\n const text = doc.getText(key);\n\n if (text.length === 0 && defaultValue) {\n text.insert(0, defaultValue);\n }\n\n setLocal(text.toString());\n\n const observer = () => setLocal(text.toString());\n text.observe(observer);\n return () => text.unobserve(observer);\n }, [doc, key]);\n\n const setValue = useCallback(\n (text: string) => {\n const ytext = doc.getText(key);\n doc.transact(() => {\n ytext.delete(0, ytext.length);\n ytext.insert(0, text);\n });\n },\n [doc, key],\n );\n\n return { value, ytext, setValue };\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport { useYSDK } from './context';\n\nexport interface PresenceResult<T> {\n /** All other connected users' presence state */\n peers: T[];\n /** Update your own presence state (merges with existing) */\n setPresence: (state: Partial<T>) => void;\n /** Number of connected peers (not including self) */\n count: number;\n}\n\n/**\n * Track who's connected and share ephemeral user state (cursors, selections, etc.).\n * Presence data is NOT persisted - it exists only while users are connected.\n *\n * Requires a WebSocket connection (no presence in local-only mode).\n *\n * @param initialState - Your initial presence state (e.g., { name: 'Andy', cursor: { x: 0, y: 0 } })\n * @returns { peers, setPresence, count }\n */\nexport function usePresence<T extends Record<string, any>>(initialState?: T): PresenceResult<T> {\n const { awareness } = useYSDK();\n const [peers, setPeers] = useState<T[]>([]);\n\n useEffect(() => {\n if (!awareness) return;\n\n if (initialState) {\n awareness.setLocalStateField('user', initialState);\n }\n\n const update = () => {\n const states: T[] = [];\n awareness.getStates().forEach((state: any, clientId: number) => {\n if (clientId !== awareness.clientID && state.user) {\n states.push(state.user);\n }\n });\n setPeers(states);\n };\n\n update();\n awareness.on('change', update);\n return () => awareness.off('change', update);\n }, [awareness]);\n\n const setPresence = useCallback(\n (state: Partial<T>) => {\n if (!awareness) return;\n const current = awareness.getLocalState()?.user || {};\n awareness.setLocalStateField('user', { ...current, ...state });\n },\n [awareness],\n );\n\n return { peers, setPresence, count: peers.length };\n}\n","import { useEffect, useCallback, useRef } from 'react';\nimport { useYSDK } from './context';\n\n/**\n * Send ephemeral messages to all connected clients.\n * Messages are NOT persisted - they're fire-and-forget.\n *\n * Uses awareness state under the hood. Good for cursor positions,\n * pings, reactions, and other transient events.\n *\n * Requires a WebSocket connection (no broadcast in local-only mode).\n *\n * @param channel - Channel name to scope messages\n * @returns { broadcast, onMessage }\n */\nexport function useBroadcast<T = any>(channel: string) {\n const { awareness } = useYSDK();\n const handlersRef = useRef<Set<(data: T, sender: number) => void>>(new Set());\n const lastSeenRef = useRef<Map<number, number>>(new Map());\n\n useEffect(() => {\n if (!awareness) return;\n\n const handler = () => {\n awareness.getStates().forEach((state: any, clientId: number) => {\n if (clientId === awareness.clientID) return;\n const msg = state?.[`_bc:${channel}`];\n if (!msg) return;\n\n const lastSeen = lastSeenRef.current.get(clientId) ?? 0;\n if (msg.ts > lastSeen) {\n lastSeenRef.current.set(clientId, msg.ts);\n handlersRef.current.forEach((h) => h(msg.data, clientId));\n }\n });\n };\n\n awareness.on('change', handler);\n return () => awareness.off('change', handler);\n }, [awareness, channel]);\n\n const broadcast = useCallback(\n (data: T) => {\n if (!awareness) return;\n awareness.setLocalStateField(`_bc:${channel}`, {\n data,\n ts: Date.now(),\n });\n },\n [awareness, channel],\n );\n\n const onMessage = useCallback((handler: (data: T, sender: number) => void) => {\n handlersRef.current.add(handler);\n return () => {\n handlersRef.current.delete(handler);\n };\n }, []);\n\n return { broadcast, onMessage };\n}\n"],"mappings":"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","names":["index_exports","__export","YSDKProvider","useBroadcast","usePresence","useShared","useSharedArray","useSharedText","useYSDK","__toCommonJS","import_react","Y","import_y_websocket","import_react","YSDKCtx","useYSDK","ctx","import_jsx_runtime","YSDKProvider","url","room","cloud","externalDoc","children","ctx","setCtx","doc","provider","wsUrl","YSDKCtx","import_react","useShared","key","defaultValue","doc","useYSDK","value","setValue","v","map","current","observer","event","newVal","setShared","newValue","import_react","useSharedArray","key","doc","useYSDK","items","setItems","arr","observer","ops","index","count","import_react","useSharedText","key","defaultValue","doc","useYSDK","ytext","value","setLocal","text","observer","setValue","import_react","usePresence","initialState","awareness","useYSDK","peers","setPeers","update","states","state","clientId","setPresence","current","import_react","useBroadcast","channel","awareness","useYSDK","handlersRef","lastSeenRef","handler","state","clientId","msg","lastSeen","h","broadcast","data","onMessage"]}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{useEffect as v,useState as b}from"react";import*as l from"yjs";import{WebsocketProvider as m}from"y-websocket";import{createContext as T,useContext as h}from"react";var d=T(null);function i(){let t=h(d);if(!t)throw new Error("YSDK hooks must be used within a <YSDKProvider>");return t}import{jsx as K}from"react/jsx-runtime";function Y({url:t,room:e,cloud:s,doc:u,children:c}){let[n,r]=b(null);return v(()=>{let o=u||new l.Doc,a=null;if(!u)if(s){let p=`${s.baseUrl||"wss://sync.elvenvtt.com"}/ysdk`;a=new m(`${p}?apiKey=${s.apiKey}`,s.room,o)}else t&&e&&(a=new m(t,e,o));return r({doc:o,awareness:a?.awareness??null}),()=>{a?.destroy(),u||o.destroy()}},[t,e,s?.apiKey,s?.room,s?.baseUrl,u]),n?K(d.Provider,{value:n,children:c}):null}import{useState as D,useEffect as y,useCallback as P}from"react";function C(t,e){let{doc:s}=i(),[u,c]=D(()=>{let o=s.getMap("ysdk").get(t);return o!==void 0?o:e});y(()=>{let r=s.getMap("ysdk");r.has(t)||r.set(t,e);let o=r.get(t);o!==void 0&&c(o);let a=f=>{if(f.keysChanged.has(t)){let p=r.get(t);c(p!==void 0?p:e)}};return r.observe(a),()=>r.unobserve(a)},[s,t]);let n=P(r=>{s.getMap("ysdk").set(t,r)},[s,t]);return[u,n]}import{useState as w,useEffect as A,useMemo as E}from"react";function R(t){let{doc:e}=i(),[s,u]=w(()=>e.getArray(t).toArray());A(()=>{let n=e.getArray(t);u(n.toArray());let r=()=>u(n.toArray());return n.observe(r),()=>n.unobserve(r)},[e,t]);let c=E(()=>{let n=e.getArray(t);return{push:(...r)=>n.push(r),delete:(r,o=1)=>n.delete(r,o),insert:(r,...o)=>n.insert(r,o),clear:()=>{n.length>0&&n.delete(0,n.length)},get length(){return n.length}}},[e,t]);return[s,c]}import{useState as M,useEffect as L,useCallback as V}from"react";function $(t,e=""){let{doc:s}=i(),u=s.getText(t),[c,n]=M(()=>u.toString()||e);L(()=>{let o=s.getText(t);o.length===0&&e&&o.insert(0,e),n(o.toString());let a=()=>n(o.toString());return o.observe(a),()=>o.unobserve(a)},[s,t]);let r=V(o=>{let a=s.getText(t);s.transact(()=>{a.delete(0,a.length),a.insert(0,o)})},[s,t]);return{value:c,ytext:u,setValue:r}}import{useState as O,useEffect as U,useCallback as F}from"react";function B(t){let{awareness:e}=i(),[s,u]=O([]);U(()=>{if(!e)return;t&&e.setLocalStateField("user",t);let n=()=>{let r=[];e.getStates().forEach((o,a)=>{a!==e.clientID&&o.user&&r.push(o.user)}),u(r)};return n(),e.on("change",n),()=>e.off("change",n)},[e]);let c=F(n=>{if(!e)return;let r=e.getLocalState()?.user||{};e.setLocalStateField("user",{...r,...n})},[e]);return{peers:s,setPresence:c,count:s.length}}import{useEffect as W,useCallback as S,useRef as g}from"react";function _(t){let{awareness:e}=i(),s=g(new Set),u=g(new Map);W(()=>{if(!e)return;let r=()=>{e.getStates().forEach((o,a)=>{if(a===e.clientID)return;let f=o?.[`_bc:${t}`];if(!f)return;let p=u.current.get(a)??0;f.ts>p&&(u.current.set(a,f.ts),s.current.forEach(x=>x(f.data,a)))})};return e.on("change",r),()=>e.off("change",r)},[e,t]);let c=S(r=>{e&&e.setLocalStateField(`_bc:${t}`,{data:r,ts:Date.now()})},[e,t]),n=S(r=>(s.current.add(r),()=>{s.current.delete(r)}),[]);return{broadcast:c,onMessage:n}}export{Y as YSDKProvider,_ as useBroadcast,B as usePresence,C as useShared,R as useSharedArray,$ as useSharedText,i as useYSDK};
1
+ import{useEffect as v,useState as b}from"react";import*as l from"yjs";import{WebsocketProvider as m}from"y-websocket";import{createContext as T,useContext as h}from"react";var d=T(null);function i(){let t=h(d);if(!t)throw new Error("YSDK hooks must be used within a <YSDKProvider>");return t}import{jsx as K}from"react/jsx-runtime";function Y({url:t,room:e,cloud:s,doc:u,children:c}){let[n,r]=b(null);return v(()=>{let o=u||new l.Doc,a=null;if(!u)if(s){let p=`${s.baseUrl||"wss://sync.elvenvtt.com"}/ysdk`;a=new m(p,s.room,o,{params:{apiKey:s.apiKey},maxBackoffTime:3e4})}else t&&e&&(a=new m(t,e,o));return r({doc:o,awareness:a?.awareness??null}),()=>{a?.destroy(),u||o.destroy()}},[t,e,s?.apiKey,s?.room,s?.baseUrl,u]),n?K(d.Provider,{value:n,children:c}):null}import{useState as D,useEffect as y,useCallback as P}from"react";function C(t,e){let{doc:s}=i(),[u,c]=D(()=>{let o=s.getMap("ysdk").get(t);return o!==void 0?o:e});y(()=>{let r=s.getMap("ysdk");r.has(t)||r.set(t,e);let o=r.get(t);o!==void 0&&c(o);let a=f=>{if(f.keysChanged.has(t)){let p=r.get(t);c(p!==void 0?p:e)}};return r.observe(a),()=>r.unobserve(a)},[s,t]);let n=P(r=>{s.getMap("ysdk").set(t,r)},[s,t]);return[u,n]}import{useState as w,useEffect as A,useMemo as E}from"react";function R(t){let{doc:e}=i(),[s,u]=w(()=>e.getArray(t).toArray());A(()=>{let n=e.getArray(t);u(n.toArray());let r=()=>u(n.toArray());return n.observe(r),()=>n.unobserve(r)},[e,t]);let c=E(()=>{let n=e.getArray(t);return{push:(...r)=>n.push(r),delete:(r,o=1)=>n.delete(r,o),insert:(r,...o)=>n.insert(r,o),clear:()=>{n.length>0&&n.delete(0,n.length)},get length(){return n.length}}},[e,t]);return[s,c]}import{useState as M,useEffect as L,useCallback as V}from"react";function O(t,e=""){let{doc:s}=i(),u=s.getText(t),[c,n]=M(()=>u.toString()||e);L(()=>{let o=s.getText(t);o.length===0&&e&&o.insert(0,e),n(o.toString());let a=()=>n(o.toString());return o.observe(a),()=>o.unobserve(a)},[s,t]);let r=V(o=>{let a=s.getText(t);s.transact(()=>{a.delete(0,a.length),a.insert(0,o)})},[s,t]);return{value:c,ytext:u,setValue:r}}import{useState as U,useEffect as B,useCallback as F}from"react";function $(t){let{awareness:e}=i(),[s,u]=U([]);B(()=>{if(!e)return;t&&e.setLocalStateField("user",t);let n=()=>{let r=[];e.getStates().forEach((o,a)=>{a!==e.clientID&&o.user&&r.push(o.user)}),u(r)};return n(),e.on("change",n),()=>e.off("change",n)},[e]);let c=F(n=>{if(!e)return;let r=e.getLocalState()?.user||{};e.setLocalStateField("user",{...r,...n})},[e]);return{peers:s,setPresence:c,count:s.length}}import{useEffect as W,useCallback as S,useRef as g}from"react";function _(t){let{awareness:e}=i(),s=g(new Set),u=g(new Map);W(()=>{if(!e)return;let r=()=>{e.getStates().forEach((o,a)=>{if(a===e.clientID)return;let f=o?.[`_bc:${t}`];if(!f)return;let p=u.current.get(a)??0;f.ts>p&&(u.current.set(a,f.ts),s.current.forEach(x=>x(f.data,a)))})};return e.on("change",r),()=>e.off("change",r)},[e,t]);let c=S(r=>{e&&e.setLocalStateField(`_bc:${t}`,{data:r,ts:Date.now()})},[e,t]),n=S(r=>(s.current.add(r),()=>{s.current.delete(r)}),[]);return{broadcast:c,onMessage:n}}export{Y as YSDKProvider,_ as useBroadcast,$ as usePresence,C as useShared,R as useSharedArray,O as useSharedText,i as useYSDK};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider.tsx","../src/context.ts","../src/useShared.ts","../src/useSharedArray.ts","../src/useSharedText.ts","../src/usePresence.ts","../src/useBroadcast.ts"],"sourcesContent":["import React, { useEffect, useState, useMemo } from 'react';\nimport * as Y from 'yjs';\nimport { WebsocketProvider } from 'y-websocket';\nimport { YSDKCtx, type YSDKContextValue } from './context';\n\nexport interface YSDKCloudConfig {\n /** YSDK Cloud API key (ysdk_live_... or ysdk_test_...) */\n apiKey: string;\n /** Room name */\n room: string;\n /** Override base WebSocket URL (defaults to wss://sync.elvenvtt.com) */\n baseUrl?: string;\n}\n\nexport interface YSDKProviderProps {\n /** WebSocket server URL (e.g., \"ws://localhost:1234\") - for self-hosted y-websocket servers */\n url?: string;\n /** Room name (used with url) */\n room?: string;\n /** YSDK Cloud config - connects to hosted infrastructure */\n cloud?: YSDKCloudConfig;\n /** Bring your own Y.Doc (you handle sync) */\n doc?: Y.Doc;\n children: React.ReactNode;\n}\n\nexport function YSDKProvider({ url, room, cloud, doc: externalDoc, children }: YSDKProviderProps) {\n const [ctx, setCtx] = useState<YSDKContextValue | null>(null);\n\n useEffect(() => {\n const doc = externalDoc || new Y.Doc();\n let provider: WebsocketProvider | null = null;\n\n if (!externalDoc) {\n if (cloud) {\n // YSDK Cloud: connect via standard y-websocket to our hosted infrastructure\n const base = cloud.baseUrl || 'wss://sync.elvenvtt.com';\n const wsUrl = `${base}/ysdk`;\n provider = new WebsocketProvider(`${wsUrl}?apiKey=${cloud.apiKey}`, cloud.room, doc);\n } else if (url && room) {\n // Self-hosted: connect to any y-websocket server\n provider = new WebsocketProvider(url, room, doc);\n }\n }\n\n setCtx({\n doc,\n awareness: provider?.awareness ?? null,\n });\n\n return () => {\n provider?.destroy();\n if (!externalDoc) doc.destroy();\n };\n }, [url, room, cloud?.apiKey, cloud?.room, cloud?.baseUrl, externalDoc]);\n\n if (!ctx) return null;\n\n return <YSDKCtx.Provider value={ctx}>{children}</YSDKCtx.Provider>;\n}\n","import { createContext, useContext } from 'react';\nimport type * as Y from 'yjs';\nimport type { Awareness } from 'y-protocols/awareness';\n\nexport interface YSDKContextValue {\n doc: Y.Doc;\n awareness: Awareness | null;\n}\n\nexport const YSDKCtx = createContext<YSDKContextValue | null>(null);\n\nexport function useYSDK(): YSDKContextValue {\n const ctx = useContext(YSDKCtx);\n if (!ctx) {\n throw new Error('YSDK hooks must be used within a <YSDKProvider>');\n }\n return ctx;\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport { useYSDK } from './context';\n\n/**\n * Shared state that syncs across all connected clients.\n * Works like useState, but multiplayer.\n *\n * Values are stored in a shared Y.Map. Objects are stored as opaque values\n * (last-write-wins on the whole object, not per-field merge).\n *\n * @param key - Unique key for this piece of state\n * @param defaultValue - Initial value if not yet set\n * @returns [value, setValue] - Current value and setter, just like useState\n */\nexport function useShared<T>(key: string, defaultValue: T): [T, (value: T) => void] {\n const { doc } = useYSDK();\n\n const [value, setValue] = useState<T>(() => {\n const map = doc.getMap('ysdk');\n const v = map.get(key);\n return v !== undefined ? (v as T) : defaultValue;\n });\n\n useEffect(() => {\n const map = doc.getMap('ysdk');\n\n if (!map.has(key)) {\n map.set(key, defaultValue);\n }\n\n const current = map.get(key);\n if (current !== undefined) setValue(current as T);\n\n const observer = (event: any) => {\n if (event.keysChanged.has(key)) {\n const newVal = map.get(key);\n setValue(newVal !== undefined ? (newVal as T) : defaultValue);\n }\n };\n\n map.observe(observer);\n return () => map.unobserve(observer);\n }, [doc, key]);\n\n const setShared = useCallback(\n (newValue: T) => {\n doc.getMap('ysdk').set(key, newValue);\n },\n [doc, key],\n );\n\n return [value, setShared];\n}\n","import { useState, useEffect, useMemo } from 'react';\nimport { useYSDK } from './context';\n\nexport interface SharedArrayOps<T> {\n /** Append items to end of array */\n push: (...items: T[]) => void;\n /** Delete count items starting at index */\n delete: (index: number, count?: number) => void;\n /** Insert items at index */\n insert: (index: number, ...items: T[]) => void;\n /** Remove all items */\n clear: () => void;\n /** Number of items */\n length: number;\n}\n\n/**\n * Shared array that syncs across all connected clients.\n * Concurrent insertions merge cleanly (CRDT).\n *\n * @param key - Unique key for this array\n * @returns [items, ops] - Current items and operations\n */\nexport function useSharedArray<T>(key: string): [T[], SharedArrayOps<T>] {\n const { doc } = useYSDK();\n\n const [items, setItems] = useState<T[]>(() => {\n return doc.getArray<T>(key).toArray();\n });\n\n useEffect(() => {\n const arr = doc.getArray<T>(key);\n setItems(arr.toArray());\n\n const observer = () => setItems(arr.toArray());\n arr.observe(observer);\n return () => arr.unobserve(observer);\n }, [doc, key]);\n\n const ops = useMemo<SharedArrayOps<T>>(() => {\n const arr = doc.getArray<T>(key);\n return {\n push: (...items) => arr.push(items),\n delete: (index, count = 1) => arr.delete(index, count),\n insert: (index, ...items) => arr.insert(index, items),\n clear: () => {\n if (arr.length > 0) arr.delete(0, arr.length);\n },\n get length() {\n return arr.length;\n },\n };\n }, [doc, key]);\n\n return [items, ops];\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport * as Y from 'yjs';\nimport { useYSDK } from './context';\n\nexport interface SharedText {\n /** Current text value as a plain string */\n value: string;\n /** Raw Y.Text instance for binding to editors (CodeMirror, ProseMirror, Tiptap, etc.) */\n ytext: Y.Text;\n /** Replace entire text content */\n setValue: (text: string) => void;\n}\n\n/**\n * Shared text that syncs across all connected clients.\n * Character-by-character CRDT merging - concurrent edits merge cleanly.\n *\n * For simple inputs, use `value` and `setValue`.\n * For rich text editors, bind `ytext` directly (e.g., y-codemirror, y-prosemirror).\n *\n * @param key - Unique key for this text\n * @param defaultValue - Initial text content\n */\nexport function useSharedText(key: string, defaultValue = ''): SharedText {\n const { doc } = useYSDK();\n const ytext = doc.getText(key);\n\n const [value, setLocal] = useState(() => {\n const current = ytext.toString();\n return current || defaultValue;\n });\n\n useEffect(() => {\n const text = doc.getText(key);\n\n if (text.length === 0 && defaultValue) {\n text.insert(0, defaultValue);\n }\n\n setLocal(text.toString());\n\n const observer = () => setLocal(text.toString());\n text.observe(observer);\n return () => text.unobserve(observer);\n }, [doc, key]);\n\n const setValue = useCallback(\n (text: string) => {\n const ytext = doc.getText(key);\n doc.transact(() => {\n ytext.delete(0, ytext.length);\n ytext.insert(0, text);\n });\n },\n [doc, key],\n );\n\n return { value, ytext, setValue };\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport { useYSDK } from './context';\n\nexport interface PresenceResult<T> {\n /** All other connected users' presence state */\n peers: T[];\n /** Update your own presence state (merges with existing) */\n setPresence: (state: Partial<T>) => void;\n /** Number of connected peers (not including self) */\n count: number;\n}\n\n/**\n * Track who's connected and share ephemeral user state (cursors, selections, etc.).\n * Presence data is NOT persisted - it exists only while users are connected.\n *\n * Requires a WebSocket connection (no presence in local-only mode).\n *\n * @param initialState - Your initial presence state (e.g., { name: 'Andy', cursor: { x: 0, y: 0 } })\n * @returns { peers, setPresence, count }\n */\nexport function usePresence<T extends Record<string, any>>(initialState?: T): PresenceResult<T> {\n const { awareness } = useYSDK();\n const [peers, setPeers] = useState<T[]>([]);\n\n useEffect(() => {\n if (!awareness) return;\n\n if (initialState) {\n awareness.setLocalStateField('user', initialState);\n }\n\n const update = () => {\n const states: T[] = [];\n awareness.getStates().forEach((state: any, clientId: number) => {\n if (clientId !== awareness.clientID && state.user) {\n states.push(state.user);\n }\n });\n setPeers(states);\n };\n\n update();\n awareness.on('change', update);\n return () => awareness.off('change', update);\n }, [awareness]);\n\n const setPresence = useCallback(\n (state: Partial<T>) => {\n if (!awareness) return;\n const current = awareness.getLocalState()?.user || {};\n awareness.setLocalStateField('user', { ...current, ...state });\n },\n [awareness],\n );\n\n return { peers, setPresence, count: peers.length };\n}\n","import { useEffect, useCallback, useRef } from 'react';\nimport { useYSDK } from './context';\n\n/**\n * Send ephemeral messages to all connected clients.\n * Messages are NOT persisted - they're fire-and-forget.\n *\n * Uses awareness state under the hood. Good for cursor positions,\n * pings, reactions, and other transient events.\n *\n * Requires a WebSocket connection (no broadcast in local-only mode).\n *\n * @param channel - Channel name to scope messages\n * @returns { broadcast, onMessage }\n */\nexport function useBroadcast<T = any>(channel: string) {\n const { awareness } = useYSDK();\n const handlersRef = useRef<Set<(data: T, sender: number) => void>>(new Set());\n const lastSeenRef = useRef<Map<number, number>>(new Map());\n\n useEffect(() => {\n if (!awareness) return;\n\n const handler = () => {\n awareness.getStates().forEach((state: any, clientId: number) => {\n if (clientId === awareness.clientID) return;\n const msg = state?.[`_bc:${channel}`];\n if (!msg) return;\n\n const lastSeen = lastSeenRef.current.get(clientId) ?? 0;\n if (msg.ts > lastSeen) {\n lastSeenRef.current.set(clientId, msg.ts);\n handlersRef.current.forEach((h) => h(msg.data, clientId));\n }\n });\n };\n\n awareness.on('change', handler);\n return () => awareness.off('change', handler);\n }, [awareness, channel]);\n\n const broadcast = useCallback(\n (data: T) => {\n if (!awareness) return;\n awareness.setLocalStateField(`_bc:${channel}`, {\n data,\n ts: Date.now(),\n });\n },\n [awareness, channel],\n );\n\n const onMessage = useCallback((handler: (data: T, sender: number) => void) => {\n handlersRef.current.add(handler);\n return () => {\n handlersRef.current.delete(handler);\n };\n }, []);\n\n return { broadcast, onMessage };\n}\n"],"mappings":"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","names":["useEffect","useState","Y","WebsocketProvider","createContext","useContext","YSDKCtx","useYSDK","ctx","jsx","YSDKProvider","url","room","cloud","externalDoc","children","ctx","setCtx","useState","useEffect","doc","provider","wsUrl","WebsocketProvider","YSDKCtx","useState","useEffect","useCallback","useShared","key","defaultValue","doc","useYSDK","value","setValue","useState","v","useEffect","map","current","observer","event","newVal","setShared","useCallback","newValue","useState","useEffect","useMemo","useSharedArray","key","doc","useYSDK","items","setItems","useState","useEffect","arr","observer","ops","useMemo","index","count","useState","useEffect","useCallback","useSharedText","key","defaultValue","doc","useYSDK","ytext","value","setLocal","useState","useEffect","text","observer","setValue","useCallback","useState","useEffect","useCallback","usePresence","initialState","awareness","useYSDK","peers","setPeers","useState","useEffect","update","states","state","clientId","setPresence","useCallback","current","useEffect","useCallback","useRef","useBroadcast","channel","awareness","useYSDK","handlersRef","useRef","lastSeenRef","useEffect","handler","state","clientId","msg","lastSeen","h","broadcast","useCallback","data","onMessage"]}
1
+ {"version":3,"sources":["../src/provider.tsx","../src/context.ts","../src/useShared.ts","../src/useSharedArray.ts","../src/useSharedText.ts","../src/usePresence.ts","../src/useBroadcast.ts"],"sourcesContent":["import React, { useEffect, useState, useMemo } from 'react';\nimport * as Y from 'yjs';\nimport { WebsocketProvider } from 'y-websocket';\nimport { YSDKCtx, type YSDKContextValue } from './context';\n\nexport interface YSDKCloudConfig {\n /** YSDK Cloud API key (ysdk_live_... or ysdk_test_...) */\n apiKey: string;\n /** Room name */\n room: string;\n /** Override base WebSocket URL (defaults to wss://sync.elvenvtt.com) */\n baseUrl?: string;\n}\n\nexport interface YSDKProviderProps {\n /** WebSocket server URL (e.g., \"ws://localhost:1234\") - for self-hosted y-websocket servers */\n url?: string;\n /** Room name (used with url) */\n room?: string;\n /** YSDK Cloud config - connects to hosted infrastructure */\n cloud?: YSDKCloudConfig;\n /** Bring your own Y.Doc (you handle sync) */\n doc?: Y.Doc;\n children: React.ReactNode;\n}\n\nexport function YSDKProvider({ url, room, cloud, doc: externalDoc, children }: YSDKProviderProps) {\n const [ctx, setCtx] = useState<YSDKContextValue | null>(null);\n\n useEffect(() => {\n const doc = externalDoc || new Y.Doc();\n let provider: WebsocketProvider | null = null;\n\n if (!externalDoc) {\n if (cloud) {\n // YSDK Cloud: connect via standard y-websocket to our hosted infrastructure\n const base = cloud.baseUrl || 'wss://sync.elvenvtt.com';\n const wsUrl = `${base}/ysdk`;\n provider = new WebsocketProvider(wsUrl, cloud.room, doc, {\n params: { apiKey: cloud.apiKey },\n maxBackoffTime: 30000, // Max 30s between reconnect attempts (default 2.5s is too aggressive)\n } as any);\n } else if (url && room) {\n // Self-hosted: connect to any y-websocket server\n provider = new WebsocketProvider(url, room, doc);\n }\n }\n\n setCtx({\n doc,\n awareness: provider?.awareness ?? null,\n });\n\n return () => {\n provider?.destroy();\n if (!externalDoc) doc.destroy();\n };\n }, [url, room, cloud?.apiKey, cloud?.room, cloud?.baseUrl, externalDoc]);\n\n if (!ctx) return null;\n\n return <YSDKCtx.Provider value={ctx}>{children}</YSDKCtx.Provider>;\n}\n","import { createContext, useContext } from 'react';\nimport type * as Y from 'yjs';\nimport type { Awareness } from 'y-protocols/awareness';\n\nexport interface YSDKContextValue {\n doc: Y.Doc;\n awareness: Awareness | null;\n}\n\nexport const YSDKCtx = createContext<YSDKContextValue | null>(null);\n\nexport function useYSDK(): YSDKContextValue {\n const ctx = useContext(YSDKCtx);\n if (!ctx) {\n throw new Error('YSDK hooks must be used within a <YSDKProvider>');\n }\n return ctx;\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport { useYSDK } from './context';\n\n/**\n * Shared state that syncs across all connected clients.\n * Works like useState, but multiplayer.\n *\n * Values are stored in a shared Y.Map. Objects are stored as opaque values\n * (last-write-wins on the whole object, not per-field merge).\n *\n * @param key - Unique key for this piece of state\n * @param defaultValue - Initial value if not yet set\n * @returns [value, setValue] - Current value and setter, just like useState\n */\nexport function useShared<T>(key: string, defaultValue: T): [T, (value: T) => void] {\n const { doc } = useYSDK();\n\n const [value, setValue] = useState<T>(() => {\n const map = doc.getMap('ysdk');\n const v = map.get(key);\n return v !== undefined ? (v as T) : defaultValue;\n });\n\n useEffect(() => {\n const map = doc.getMap('ysdk');\n\n if (!map.has(key)) {\n map.set(key, defaultValue);\n }\n\n const current = map.get(key);\n if (current !== undefined) setValue(current as T);\n\n const observer = (event: any) => {\n if (event.keysChanged.has(key)) {\n const newVal = map.get(key);\n setValue(newVal !== undefined ? (newVal as T) : defaultValue);\n }\n };\n\n map.observe(observer);\n return () => map.unobserve(observer);\n }, [doc, key]);\n\n const setShared = useCallback(\n (newValue: T) => {\n doc.getMap('ysdk').set(key, newValue);\n },\n [doc, key],\n );\n\n return [value, setShared];\n}\n","import { useState, useEffect, useMemo } from 'react';\nimport { useYSDK } from './context';\n\nexport interface SharedArrayOps<T> {\n /** Append items to end of array */\n push: (...items: T[]) => void;\n /** Delete count items starting at index */\n delete: (index: number, count?: number) => void;\n /** Insert items at index */\n insert: (index: number, ...items: T[]) => void;\n /** Remove all items */\n clear: () => void;\n /** Number of items */\n length: number;\n}\n\n/**\n * Shared array that syncs across all connected clients.\n * Concurrent insertions merge cleanly (CRDT).\n *\n * @param key - Unique key for this array\n * @returns [items, ops] - Current items and operations\n */\nexport function useSharedArray<T>(key: string): [T[], SharedArrayOps<T>] {\n const { doc } = useYSDK();\n\n const [items, setItems] = useState<T[]>(() => {\n return doc.getArray<T>(key).toArray();\n });\n\n useEffect(() => {\n const arr = doc.getArray<T>(key);\n setItems(arr.toArray());\n\n const observer = () => setItems(arr.toArray());\n arr.observe(observer);\n return () => arr.unobserve(observer);\n }, [doc, key]);\n\n const ops = useMemo<SharedArrayOps<T>>(() => {\n const arr = doc.getArray<T>(key);\n return {\n push: (...items) => arr.push(items),\n delete: (index, count = 1) => arr.delete(index, count),\n insert: (index, ...items) => arr.insert(index, items),\n clear: () => {\n if (arr.length > 0) arr.delete(0, arr.length);\n },\n get length() {\n return arr.length;\n },\n };\n }, [doc, key]);\n\n return [items, ops];\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport * as Y from 'yjs';\nimport { useYSDK } from './context';\n\nexport interface SharedText {\n /** Current text value as a plain string */\n value: string;\n /** Raw Y.Text instance for binding to editors (CodeMirror, ProseMirror, Tiptap, etc.) */\n ytext: Y.Text;\n /** Replace entire text content */\n setValue: (text: string) => void;\n}\n\n/**\n * Shared text that syncs across all connected clients.\n * Character-by-character CRDT merging - concurrent edits merge cleanly.\n *\n * For simple inputs, use `value` and `setValue`.\n * For rich text editors, bind `ytext` directly (e.g., y-codemirror, y-prosemirror).\n *\n * @param key - Unique key for this text\n * @param defaultValue - Initial text content\n */\nexport function useSharedText(key: string, defaultValue = ''): SharedText {\n const { doc } = useYSDK();\n const ytext = doc.getText(key);\n\n const [value, setLocal] = useState(() => {\n const current = ytext.toString();\n return current || defaultValue;\n });\n\n useEffect(() => {\n const text = doc.getText(key);\n\n if (text.length === 0 && defaultValue) {\n text.insert(0, defaultValue);\n }\n\n setLocal(text.toString());\n\n const observer = () => setLocal(text.toString());\n text.observe(observer);\n return () => text.unobserve(observer);\n }, [doc, key]);\n\n const setValue = useCallback(\n (text: string) => {\n const ytext = doc.getText(key);\n doc.transact(() => {\n ytext.delete(0, ytext.length);\n ytext.insert(0, text);\n });\n },\n [doc, key],\n );\n\n return { value, ytext, setValue };\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport { useYSDK } from './context';\n\nexport interface PresenceResult<T> {\n /** All other connected users' presence state */\n peers: T[];\n /** Update your own presence state (merges with existing) */\n setPresence: (state: Partial<T>) => void;\n /** Number of connected peers (not including self) */\n count: number;\n}\n\n/**\n * Track who's connected and share ephemeral user state (cursors, selections, etc.).\n * Presence data is NOT persisted - it exists only while users are connected.\n *\n * Requires a WebSocket connection (no presence in local-only mode).\n *\n * @param initialState - Your initial presence state (e.g., { name: 'Andy', cursor: { x: 0, y: 0 } })\n * @returns { peers, setPresence, count }\n */\nexport function usePresence<T extends Record<string, any>>(initialState?: T): PresenceResult<T> {\n const { awareness } = useYSDK();\n const [peers, setPeers] = useState<T[]>([]);\n\n useEffect(() => {\n if (!awareness) return;\n\n if (initialState) {\n awareness.setLocalStateField('user', initialState);\n }\n\n const update = () => {\n const states: T[] = [];\n awareness.getStates().forEach((state: any, clientId: number) => {\n if (clientId !== awareness.clientID && state.user) {\n states.push(state.user);\n }\n });\n setPeers(states);\n };\n\n update();\n awareness.on('change', update);\n return () => awareness.off('change', update);\n }, [awareness]);\n\n const setPresence = useCallback(\n (state: Partial<T>) => {\n if (!awareness) return;\n const current = awareness.getLocalState()?.user || {};\n awareness.setLocalStateField('user', { ...current, ...state });\n },\n [awareness],\n );\n\n return { peers, setPresence, count: peers.length };\n}\n","import { useEffect, useCallback, useRef } from 'react';\nimport { useYSDK } from './context';\n\n/**\n * Send ephemeral messages to all connected clients.\n * Messages are NOT persisted - they're fire-and-forget.\n *\n * Uses awareness state under the hood. Good for cursor positions,\n * pings, reactions, and other transient events.\n *\n * Requires a WebSocket connection (no broadcast in local-only mode).\n *\n * @param channel - Channel name to scope messages\n * @returns { broadcast, onMessage }\n */\nexport function useBroadcast<T = any>(channel: string) {\n const { awareness } = useYSDK();\n const handlersRef = useRef<Set<(data: T, sender: number) => void>>(new Set());\n const lastSeenRef = useRef<Map<number, number>>(new Map());\n\n useEffect(() => {\n if (!awareness) return;\n\n const handler = () => {\n awareness.getStates().forEach((state: any, clientId: number) => {\n if (clientId === awareness.clientID) return;\n const msg = state?.[`_bc:${channel}`];\n if (!msg) return;\n\n const lastSeen = lastSeenRef.current.get(clientId) ?? 0;\n if (msg.ts > lastSeen) {\n lastSeenRef.current.set(clientId, msg.ts);\n handlersRef.current.forEach((h) => h(msg.data, clientId));\n }\n });\n };\n\n awareness.on('change', handler);\n return () => awareness.off('change', handler);\n }, [awareness, channel]);\n\n const broadcast = useCallback(\n (data: T) => {\n if (!awareness) return;\n awareness.setLocalStateField(`_bc:${channel}`, {\n data,\n ts: Date.now(),\n });\n },\n [awareness, channel],\n );\n\n const onMessage = useCallback((handler: (data: T, sender: number) => void) => {\n handlersRef.current.add(handler);\n return () => {\n handlersRef.current.delete(handler);\n };\n }, []);\n\n return { broadcast, onMessage };\n}\n"],"mappings":"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","names":["useEffect","useState","Y","WebsocketProvider","createContext","useContext","YSDKCtx","useYSDK","ctx","jsx","YSDKProvider","url","room","cloud","externalDoc","children","ctx","setCtx","useState","useEffect","doc","provider","wsUrl","WebsocketProvider","YSDKCtx","useState","useEffect","useCallback","useShared","key","defaultValue","doc","useYSDK","value","setValue","useState","v","useEffect","map","current","observer","event","newVal","setShared","useCallback","newValue","useState","useEffect","useMemo","useSharedArray","key","doc","useYSDK","items","setItems","useState","useEffect","arr","observer","ops","useMemo","index","count","useState","useEffect","useCallback","useSharedText","key","defaultValue","doc","useYSDK","ytext","value","setLocal","useState","useEffect","text","observer","setValue","useCallback","useState","useEffect","useCallback","usePresence","initialState","awareness","useYSDK","peers","setPeers","useState","useEffect","update","states","state","clientId","setPresence","useCallback","current","useEffect","useCallback","useRef","useBroadcast","channel","awareness","useYSDK","handlersRef","useRef","lastSeenRef","useEffect","handler","state","clientId","msg","lastSeen","h","broadcast","useCallback","data","onMessage"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ysdk/react",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "React hooks for collaborative apps. Multiplayer state that feels like useState.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",