@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 +212 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +123 -0
- package/dist/index.d.ts +123 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|