@ysdk/react 0.1.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/README.md ADDED
@@ -0,0 +1,212 @@
1
+ # @ysdk/react
2
+
3
+ React hooks for collaborative apps. Multiplayer state that feels like `useState`.
4
+
5
+ Built on [Yjs](https://yjs.dev). Works with any Yjs WebSocket server. No vendor lock-in.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @ysdk/react
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ### Option A: YSDK Cloud (zero setup)
16
+
17
+ ```jsx
18
+ import { YSDKProvider } from '@ysdk/react'
19
+
20
+ function App() {
21
+ return (
22
+ <YSDKProvider cloud={{ apiKey: "ysdk_live_...", room: "my-room" }}>
23
+ <MyApp />
24
+ </YSDKProvider>
25
+ )
26
+ }
27
+ ```
28
+
29
+ ### Option B: Self-hosted
30
+
31
+ ```bash
32
+ npx y-websocket
33
+ ```
34
+
35
+ ```jsx
36
+ import { YSDKProvider } from '@ysdk/react'
37
+
38
+ function App() {
39
+ return (
40
+ <YSDKProvider url="ws://localhost:1234" room="my-room">
41
+ <MyApp />
42
+ </YSDKProvider>
43
+ )
44
+ }
45
+ ```
46
+
47
+ ### 3. Use shared state
48
+
49
+ ```jsx
50
+ import { useShared } from '@ysdk/react'
51
+
52
+ function Counter() {
53
+ const [count, setCount] = useShared('count', 0)
54
+ return <button onClick={() => setCount(count + 1)}>{count}</button>
55
+ }
56
+ ```
57
+
58
+ Open two browser tabs. Click the button. Both tabs update.
59
+
60
+ ## API
61
+
62
+ ### `<YSDKProvider>`
63
+
64
+ Connects to a Yjs sync server and provides shared state to all child components.
65
+
66
+ ```jsx
67
+ // YSDK Cloud (zero setup, hosted infrastructure)
68
+ <YSDKProvider cloud={{ apiKey: "ysdk_live_...", room: "my-room" }}>
69
+
70
+ // Self-hosted (any y-websocket server)
71
+ <YSDKProvider url="ws://localhost:1234" room="my-room">
72
+
73
+ // Bring your own Y.Doc (you handle sync)
74
+ <YSDKProvider doc={myYDoc}>
75
+
76
+ // Local only (no sync, useful for testing)
77
+ <YSDKProvider>
78
+ ```
79
+
80
+ | Prop | Type | Description |
81
+ |------|------|-------------|
82
+ | `cloud` | `{ apiKey, room, baseUrl? }` | Connect to YSDK Cloud |
83
+ | `url` | `string?` | WebSocket server URL (self-hosted) |
84
+ | `room` | `string?` | Room name (used with `url`) |
85
+ | `doc` | `Y.Doc?` | Bring your own Yjs document |
86
+
87
+ ### `useShared<T>(key, defaultValue)`
88
+
89
+ Shared state. Works like `useState` but syncs across all clients.
90
+
91
+ ```jsx
92
+ const [name, setName] = useShared('name', 'Anonymous')
93
+ ```
94
+
95
+ Values are stored in a shared Y.Map. Objects are stored as whole values (last-write-wins on the entire object, not per-field). For deep collaborative object editing, use `useYSDK()` to access the raw Y.Doc.
96
+
97
+ ### `useSharedArray<T>(key)`
98
+
99
+ Shared array with CRDT merge semantics. Concurrent insertions merge cleanly.
100
+
101
+ ```jsx
102
+ const [items, ops] = useSharedArray('todos')
103
+
104
+ ops.push({ text: 'Buy milk', done: false })
105
+ ops.insert(0, { text: 'First item', done: false })
106
+ ops.delete(2)
107
+ ops.clear()
108
+ ```
109
+
110
+ | Operation | Description |
111
+ |-----------|-------------|
112
+ | `ops.push(...items)` | Append to end |
113
+ | `ops.insert(index, ...items)` | Insert at position |
114
+ | `ops.delete(index, count?)` | Remove items |
115
+ | `ops.clear()` | Remove all items |
116
+ | `ops.length` | Current item count |
117
+
118
+ ### `useSharedText(key, defaultValue?)`
119
+
120
+ Shared text with character-by-character CRDT merging.
121
+
122
+ ```jsx
123
+ const { value, setValue, ytext } = useSharedText('title', 'Untitled')
124
+
125
+ // Simple usage - like a controlled input
126
+ <input value={value} onChange={e => setValue(e.target.value)} />
127
+
128
+ // Advanced - bind ytext directly to CodeMirror, ProseMirror, Tiptap, etc.
129
+ // import { yCollab } from 'y-codemirror.next'
130
+ // extensions: [yCollab(ytext, awareness)]
131
+ ```
132
+
133
+ | Property | Type | Description |
134
+ |----------|------|-------------|
135
+ | `value` | `string` | Current text as plain string |
136
+ | `ytext` | `Y.Text` | Raw Yjs text for editor bindings |
137
+ | `setValue` | `(text: string) => void` | Replace entire text content |
138
+
139
+ ### `usePresence<T>(initialState?)`
140
+
141
+ Track who's connected and share ephemeral user state.
142
+
143
+ ```jsx
144
+ const { peers, setPresence, count } = usePresence({
145
+ name: 'Andy',
146
+ cursor: { x: 0, y: 0 },
147
+ })
148
+
149
+ // Update your cursor
150
+ onMouseMove={(e) => setPresence({ cursor: { x: e.clientX, y: e.clientY } })}
151
+
152
+ // Render other people's cursors
153
+ {peers.map((peer, i) => (
154
+ <Cursor key={i} x={peer.cursor.x} y={peer.cursor.y} name={peer.name} />
155
+ ))}
156
+ ```
157
+
158
+ | Property | Type | Description |
159
+ |----------|------|-------------|
160
+ | `peers` | `T[]` | Other users' presence state |
161
+ | `setPresence` | `(state: Partial<T>) => void` | Update your presence (merges) |
162
+ | `count` | `number` | Number of connected peers |
163
+
164
+ ### `useBroadcast<T>(channel)`
165
+
166
+ Fire-and-forget messages to all connected clients. Not persisted.
167
+
168
+ ```jsx
169
+ const { broadcast, onMessage } = useBroadcast('reactions')
170
+
171
+ // Send
172
+ broadcast({ emoji: '🎉', x: 100, y: 200 })
173
+
174
+ // Receive
175
+ useEffect(() => {
176
+ return onMessage((data, senderId) => {
177
+ showReaction(data.emoji, data.x, data.y)
178
+ })
179
+ }, [])
180
+ ```
181
+
182
+ ### `useYSDK()`
183
+
184
+ Escape hatch to the raw Yjs document and awareness instance.
185
+
186
+ ```jsx
187
+ const { doc, awareness } = useYSDK()
188
+
189
+ // Full Yjs API access
190
+ const ymap = doc.getMap('my-custom-map')
191
+ ```
192
+
193
+ ## Self-hosting
194
+
195
+ Any Yjs WebSocket server works. The simplest:
196
+
197
+ ```bash
198
+ npx y-websocket
199
+ ```
200
+
201
+ For production, consider [Hocuspocus](https://hocuspocus.dev) which adds auth, persistence, and webhooks on top of Yjs.
202
+
203
+ ## Limitations (v0.1)
204
+
205
+ - `useShared` stores objects as opaque values (last-write-wins on whole object, not per-field CRDT merge). Use `useYSDK()` for deep collaborative objects.
206
+ - `useBroadcast` uses awareness state internally. Not suitable for high-frequency events (>10/sec). Late joiners may see the last broadcast message.
207
+ - Changing `url` or `room` on `YSDKProvider` after mount creates a new document (previous state is lost).
208
+ - No built-in persistence. State exists only while at least one client is connected (unless your server persists).
209
+
210
+ ## License
211
+
212
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +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});
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +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"]}
@@ -0,0 +1,123 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import * as Y from 'yjs';
4
+ import { Awareness } from 'y-protocols/awareness';
5
+
6
+ interface YSDKCloudConfig {
7
+ /** YSDK Cloud API key (ysdk_live_... or ysdk_test_...) */
8
+ apiKey: string;
9
+ /** Room name */
10
+ room: string;
11
+ /** Override base WebSocket URL (defaults to wss://sync.elvenvtt.com) */
12
+ baseUrl?: string;
13
+ }
14
+ interface YSDKProviderProps {
15
+ /** WebSocket server URL (e.g., "ws://localhost:1234") - for self-hosted y-websocket servers */
16
+ url?: string;
17
+ /** Room name (used with url) */
18
+ room?: string;
19
+ /** YSDK Cloud config - connects to hosted infrastructure */
20
+ cloud?: YSDKCloudConfig;
21
+ /** Bring your own Y.Doc (you handle sync) */
22
+ doc?: Y.Doc;
23
+ children: React.ReactNode;
24
+ }
25
+ declare function YSDKProvider({ url, room, cloud, doc: externalDoc, children }: YSDKProviderProps): react_jsx_runtime.JSX.Element | null;
26
+
27
+ /**
28
+ * Shared state that syncs across all connected clients.
29
+ * Works like useState, but multiplayer.
30
+ *
31
+ * Values are stored in a shared Y.Map. Objects are stored as opaque values
32
+ * (last-write-wins on the whole object, not per-field merge).
33
+ *
34
+ * @param key - Unique key for this piece of state
35
+ * @param defaultValue - Initial value if not yet set
36
+ * @returns [value, setValue] - Current value and setter, just like useState
37
+ */
38
+ declare function useShared<T>(key: string, defaultValue: T): [T, (value: T) => void];
39
+
40
+ interface SharedArrayOps<T> {
41
+ /** Append items to end of array */
42
+ push: (...items: T[]) => void;
43
+ /** Delete count items starting at index */
44
+ delete: (index: number, count?: number) => void;
45
+ /** Insert items at index */
46
+ insert: (index: number, ...items: T[]) => void;
47
+ /** Remove all items */
48
+ clear: () => void;
49
+ /** Number of items */
50
+ length: number;
51
+ }
52
+ /**
53
+ * Shared array that syncs across all connected clients.
54
+ * Concurrent insertions merge cleanly (CRDT).
55
+ *
56
+ * @param key - Unique key for this array
57
+ * @returns [items, ops] - Current items and operations
58
+ */
59
+ declare function useSharedArray<T>(key: string): [T[], SharedArrayOps<T>];
60
+
61
+ interface SharedText {
62
+ /** Current text value as a plain string */
63
+ value: string;
64
+ /** Raw Y.Text instance for binding to editors (CodeMirror, ProseMirror, Tiptap, etc.) */
65
+ ytext: Y.Text;
66
+ /** Replace entire text content */
67
+ setValue: (text: string) => void;
68
+ }
69
+ /**
70
+ * Shared text that syncs across all connected clients.
71
+ * Character-by-character CRDT merging - concurrent edits merge cleanly.
72
+ *
73
+ * For simple inputs, use `value` and `setValue`.
74
+ * For rich text editors, bind `ytext` directly (e.g., y-codemirror, y-prosemirror).
75
+ *
76
+ * @param key - Unique key for this text
77
+ * @param defaultValue - Initial text content
78
+ */
79
+ declare function useSharedText(key: string, defaultValue?: string): SharedText;
80
+
81
+ interface PresenceResult<T> {
82
+ /** All other connected users' presence state */
83
+ peers: T[];
84
+ /** Update your own presence state (merges with existing) */
85
+ setPresence: (state: Partial<T>) => void;
86
+ /** Number of connected peers (not including self) */
87
+ count: number;
88
+ }
89
+ /**
90
+ * Track who's connected and share ephemeral user state (cursors, selections, etc.).
91
+ * Presence data is NOT persisted - it exists only while users are connected.
92
+ *
93
+ * Requires a WebSocket connection (no presence in local-only mode).
94
+ *
95
+ * @param initialState - Your initial presence state (e.g., { name: 'Andy', cursor: { x: 0, y: 0 } })
96
+ * @returns { peers, setPresence, count }
97
+ */
98
+ declare function usePresence<T extends Record<string, any>>(initialState?: T): PresenceResult<T>;
99
+
100
+ /**
101
+ * Send ephemeral messages to all connected clients.
102
+ * Messages are NOT persisted - they're fire-and-forget.
103
+ *
104
+ * Uses awareness state under the hood. Good for cursor positions,
105
+ * pings, reactions, and other transient events.
106
+ *
107
+ * Requires a WebSocket connection (no broadcast in local-only mode).
108
+ *
109
+ * @param channel - Channel name to scope messages
110
+ * @returns { broadcast, onMessage }
111
+ */
112
+ declare function useBroadcast<T = any>(channel: string): {
113
+ broadcast: (data: T) => void;
114
+ onMessage: (handler: (data: T, sender: number) => void) => () => void;
115
+ };
116
+
117
+ interface YSDKContextValue {
118
+ doc: Y.Doc;
119
+ awareness: Awareness | null;
120
+ }
121
+ declare function useYSDK(): YSDKContextValue;
122
+
123
+ export { type PresenceResult, type SharedArrayOps, type SharedText, type YSDKCloudConfig, YSDKProvider, type YSDKProviderProps, useBroadcast, usePresence, useShared, useSharedArray, useSharedText, useYSDK };
@@ -0,0 +1,123 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import * as Y from 'yjs';
4
+ import { Awareness } from 'y-protocols/awareness';
5
+
6
+ interface YSDKCloudConfig {
7
+ /** YSDK Cloud API key (ysdk_live_... or ysdk_test_...) */
8
+ apiKey: string;
9
+ /** Room name */
10
+ room: string;
11
+ /** Override base WebSocket URL (defaults to wss://sync.elvenvtt.com) */
12
+ baseUrl?: string;
13
+ }
14
+ interface YSDKProviderProps {
15
+ /** WebSocket server URL (e.g., "ws://localhost:1234") - for self-hosted y-websocket servers */
16
+ url?: string;
17
+ /** Room name (used with url) */
18
+ room?: string;
19
+ /** YSDK Cloud config - connects to hosted infrastructure */
20
+ cloud?: YSDKCloudConfig;
21
+ /** Bring your own Y.Doc (you handle sync) */
22
+ doc?: Y.Doc;
23
+ children: React.ReactNode;
24
+ }
25
+ declare function YSDKProvider({ url, room, cloud, doc: externalDoc, children }: YSDKProviderProps): react_jsx_runtime.JSX.Element | null;
26
+
27
+ /**
28
+ * Shared state that syncs across all connected clients.
29
+ * Works like useState, but multiplayer.
30
+ *
31
+ * Values are stored in a shared Y.Map. Objects are stored as opaque values
32
+ * (last-write-wins on the whole object, not per-field merge).
33
+ *
34
+ * @param key - Unique key for this piece of state
35
+ * @param defaultValue - Initial value if not yet set
36
+ * @returns [value, setValue] - Current value and setter, just like useState
37
+ */
38
+ declare function useShared<T>(key: string, defaultValue: T): [T, (value: T) => void];
39
+
40
+ interface SharedArrayOps<T> {
41
+ /** Append items to end of array */
42
+ push: (...items: T[]) => void;
43
+ /** Delete count items starting at index */
44
+ delete: (index: number, count?: number) => void;
45
+ /** Insert items at index */
46
+ insert: (index: number, ...items: T[]) => void;
47
+ /** Remove all items */
48
+ clear: () => void;
49
+ /** Number of items */
50
+ length: number;
51
+ }
52
+ /**
53
+ * Shared array that syncs across all connected clients.
54
+ * Concurrent insertions merge cleanly (CRDT).
55
+ *
56
+ * @param key - Unique key for this array
57
+ * @returns [items, ops] - Current items and operations
58
+ */
59
+ declare function useSharedArray<T>(key: string): [T[], SharedArrayOps<T>];
60
+
61
+ interface SharedText {
62
+ /** Current text value as a plain string */
63
+ value: string;
64
+ /** Raw Y.Text instance for binding to editors (CodeMirror, ProseMirror, Tiptap, etc.) */
65
+ ytext: Y.Text;
66
+ /** Replace entire text content */
67
+ setValue: (text: string) => void;
68
+ }
69
+ /**
70
+ * Shared text that syncs across all connected clients.
71
+ * Character-by-character CRDT merging - concurrent edits merge cleanly.
72
+ *
73
+ * For simple inputs, use `value` and `setValue`.
74
+ * For rich text editors, bind `ytext` directly (e.g., y-codemirror, y-prosemirror).
75
+ *
76
+ * @param key - Unique key for this text
77
+ * @param defaultValue - Initial text content
78
+ */
79
+ declare function useSharedText(key: string, defaultValue?: string): SharedText;
80
+
81
+ interface PresenceResult<T> {
82
+ /** All other connected users' presence state */
83
+ peers: T[];
84
+ /** Update your own presence state (merges with existing) */
85
+ setPresence: (state: Partial<T>) => void;
86
+ /** Number of connected peers (not including self) */
87
+ count: number;
88
+ }
89
+ /**
90
+ * Track who's connected and share ephemeral user state (cursors, selections, etc.).
91
+ * Presence data is NOT persisted - it exists only while users are connected.
92
+ *
93
+ * Requires a WebSocket connection (no presence in local-only mode).
94
+ *
95
+ * @param initialState - Your initial presence state (e.g., { name: 'Andy', cursor: { x: 0, y: 0 } })
96
+ * @returns { peers, setPresence, count }
97
+ */
98
+ declare function usePresence<T extends Record<string, any>>(initialState?: T): PresenceResult<T>;
99
+
100
+ /**
101
+ * Send ephemeral messages to all connected clients.
102
+ * Messages are NOT persisted - they're fire-and-forget.
103
+ *
104
+ * Uses awareness state under the hood. Good for cursor positions,
105
+ * pings, reactions, and other transient events.
106
+ *
107
+ * Requires a WebSocket connection (no broadcast in local-only mode).
108
+ *
109
+ * @param channel - Channel name to scope messages
110
+ * @returns { broadcast, onMessage }
111
+ */
112
+ declare function useBroadcast<T = any>(channel: string): {
113
+ broadcast: (data: T) => void;
114
+ onMessage: (handler: (data: T, sender: number) => void) => () => void;
115
+ };
116
+
117
+ interface YSDKContextValue {
118
+ doc: Y.Doc;
119
+ awareness: Awareness | null;
120
+ }
121
+ declare function useYSDK(): YSDKContextValue;
122
+
123
+ export { type PresenceResult, type SharedArrayOps, type SharedText, type YSDKCloudConfig, YSDKProvider, type YSDKProviderProps, useBroadcast, usePresence, useShared, useSharedArray, useSharedText, useYSDK };
package/dist/index.js ADDED
@@ -0,0 +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};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@ysdk/react",
3
+ "version": "0.1.0",
4
+ "description": "React hooks for collaborative apps. Multiplayer state that feels like useState.",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch"
22
+ },
23
+ "peerDependencies": {
24
+ "react": ">=18.0.0"
25
+ },
26
+ "dependencies": {
27
+ "yjs": "^13.6.0",
28
+ "y-websocket": "^2.0.0",
29
+ "y-protocols": "^1.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "react": "^18.0.0",
33
+ "@types/react": "^18.0.0",
34
+ "tsup": "^8.0.0",
35
+ "typescript": "^5.0.0"
36
+ },
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/aoatkinson/ysdk"
41
+ },
42
+ "keywords": [
43
+ "yjs",
44
+ "crdt",
45
+ "collaborative",
46
+ "multiplayer",
47
+ "react",
48
+ "hooks",
49
+ "real-time",
50
+ "sync"
51
+ ]
52
+ }