@xnetjs/react 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chris Smothers
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,327 @@
1
+ # @xnetjs/react
2
+
3
+ React hooks for xNet -- the primary API for building xNet applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @xnetjs/react @xnetjs/data
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```tsx
14
+ import { XNetProvider, useQuery, useMutate, useNode } from '@xnetjs/react'
15
+ import { MemoryNodeStorageAdapter, defineSchema, text, select } from '@xnetjs/data'
16
+
17
+ // 1. Define your schema
18
+ const TaskSchema = defineSchema({
19
+ name: 'Task',
20
+ namespace: 'myapp://',
21
+ properties: {
22
+ title: text({ required: true }),
23
+ status: select({
24
+ options: [
25
+ { id: 'todo', name: 'To Do' },
26
+ { id: 'done', name: 'Done' }
27
+ ] as const
28
+ })
29
+ }
30
+ })
31
+
32
+ // 2. Wrap your app with the provider
33
+ function App() {
34
+ return (
35
+ <XNetProvider
36
+ config={{
37
+ nodeStorage: new MemoryNodeStorageAdapter(),
38
+ authorDID: identity.did,
39
+ signingKey: privateKey
40
+ }}
41
+ >
42
+ <TaskApp />
43
+ </XNetProvider>
44
+ )
45
+ }
46
+ ```
47
+
48
+ ## Hook Categories
49
+
50
+ ```mermaid
51
+ flowchart TD
52
+ subgraph Core["Core Hooks"]
53
+ useQuery["useQuery<br/><small>Read nodes with filters</small>"]
54
+ useMutate["useMutate<br/><small>Create, update, delete</small>"]
55
+ useNode["useNode<br/><small>Rich text + Y.Doc</small>"]
56
+ useIdentity["useIdentity<br/><small>Current user DID</small>"]
57
+ useNodeStore["useNodeStore<br/><small>Direct store access</small>"]
58
+ end
59
+
60
+ subgraph Comments["Comment Hooks"]
61
+ useComments["useComments"]
62
+ useCommentCount["useCommentCount"]
63
+ end
64
+
65
+ subgraph History["History Hooks"]
66
+ useHistory["useHistory"]
67
+ useUndo["useUndo"]
68
+ useAudit["useAudit"]
69
+ useDiff["useDiff"]
70
+ useBlame["useBlame"]
71
+ useVerification["useVerification"]
72
+ end
73
+
74
+ subgraph Hub["Hub Hooks"]
75
+ useHubStatus["useHubStatus"]
76
+ useBackup["useBackup"]
77
+ useFileUpload["useFileUpload"]
78
+ useHubSearch["useHubSearch"]
79
+ useRemoteSchema["useRemoteSchema"]
80
+ usePeerDiscovery["usePeerDiscovery"]
81
+ end
82
+
83
+ subgraph Plugin["Plugin Hooks"]
84
+ usePluginRegistry["usePluginRegistry"]
85
+ usePlugins["usePlugins"]
86
+ useContributions["useContributions"]
87
+ useViews["useViews"]
88
+ useCommands["useCommands"]
89
+ end
90
+
91
+ Core --> Comments
92
+ Core --> History
93
+ Core --> Hub
94
+ Core --> Plugin
95
+ ```
96
+
97
+ ## Core Hooks
98
+
99
+ ### `useQuery` -- Read Data
100
+
101
+ Query nodes with automatic real-time updates.
102
+
103
+ ```tsx
104
+ import { useQuery } from '@xnetjs/react'
105
+
106
+ function TaskList() {
107
+ const { data: tasks, loading, error } = useQuery(TaskSchema)
108
+
109
+ if (loading) return <p>Loading...</p>
110
+ if (error) return <p>Error: {error.message}</p>
111
+
112
+ return (
113
+ <ul>
114
+ {tasks.map((task) => (
115
+ <li key={task.id}>
116
+ {task.title} {/* Direct property access -- no .properties needed */}
117
+ <span>{task.status}</span>
118
+ </li>
119
+ ))}
120
+ </ul>
121
+ )
122
+ }
123
+ ```
124
+
125
+ **Query by ID:**
126
+
127
+ ```tsx
128
+ const { data: task } = useQuery(TaskSchema, taskId)
129
+ ```
130
+
131
+ **Filtered & Sorted:**
132
+
133
+ ```tsx
134
+ const { data: todoTasks } = useQuery(TaskSchema, {
135
+ where: { status: 'todo' },
136
+ orderBy: { createdAt: 'desc' },
137
+ limit: 20
138
+ })
139
+ ```
140
+
141
+ ### `useMutate` -- Write Data
142
+
143
+ Create, update, and delete nodes.
144
+
145
+ ```tsx
146
+ import { useMutate } from '@xnetjs/react'
147
+
148
+ function CreateTaskButton() {
149
+ const { create, isPending } = useMutate()
150
+
151
+ const handleCreate = async () => {
152
+ const task = await create(TaskSchema, {
153
+ title: 'New Task',
154
+ status: 'todo'
155
+ })
156
+ console.log('Created:', task.id)
157
+ }
158
+
159
+ return (
160
+ <button onClick={handleCreate} disabled={isPending}>
161
+ {isPending ? 'Creating...' : 'Create Task'}
162
+ </button>
163
+ )
164
+ }
165
+ ```
166
+
167
+ **Update:**
168
+
169
+ ```tsx
170
+ const { update } = useMutate()
171
+ await update(TaskSchema, taskId, { status: 'done' }) // Type-checked!
172
+ ```
173
+
174
+ **Delete:**
175
+
176
+ ```tsx
177
+ const { remove } = useMutate()
178
+ await remove(taskId) // Soft delete
179
+ ```
180
+
181
+ **Transactions (atomic):**
182
+
183
+ ```tsx
184
+ const { mutate } = useMutate()
185
+
186
+ await mutate([
187
+ { type: 'update', id: task1.id, data: { order: 1 } },
188
+ { type: 'update', id: task2.id, data: { order: 2 } },
189
+ { type: 'delete', id: task3.id }
190
+ ])
191
+ ```
192
+
193
+ ### `useNode` -- Rich Text Editing
194
+
195
+ Load a node with its Y.Doc for collaborative rich text editing.
196
+
197
+ ```tsx
198
+ import { useNode } from '@xnetjs/react'
199
+ import { RichTextEditor } from '@xnetjs/editor/react'
200
+
201
+ const PageSchema = defineSchema({
202
+ name: 'Page',
203
+ namespace: 'myapp://',
204
+ properties: { title: text({ required: true }) },
205
+ document: 'yjs'
206
+ })
207
+
208
+ function DocumentEditor({ pageId }) {
209
+ const {
210
+ data: page, // FlatNode -- page.title works directly
211
+ doc, // Y.Doc for rich text
212
+ update, // Type-safe property updates
213
+ loading,
214
+ error,
215
+ syncStatus, // 'offline' | 'connecting' | 'connected'
216
+ peerCount, // Connected peers
217
+ presence // [{ did, name, color, lastSeen, isStale }]
218
+ } = useNode(PageSchema, pageId, {
219
+ createIfMissing: { title: 'Untitled' },
220
+ did: myDid
221
+ })
222
+
223
+ if (loading) return <p>Loading...</p>
224
+ if (!page || !doc) return <p>Not found</p>
225
+
226
+ return (
227
+ <div>
228
+ <input value={page.title} onChange={(e) => update({ title: e.target.value })} />
229
+ <RichTextEditor ydoc={doc} />
230
+ </div>
231
+ )
232
+ }
233
+ ```
234
+
235
+ ## Additional Hooks
236
+
237
+ ### Comment Hooks
238
+
239
+ ```tsx
240
+ const { threads, addComment, resolveThread } = useComments({ nodeId })
241
+ const count = useCommentCount(nodeId)
242
+ ```
243
+
244
+ ### History Hooks
245
+
246
+ ```tsx
247
+ const { timeline, materializeAt, diff } = useHistory(nodeId)
248
+ const { undo, redo, canUndo, canRedo } = useUndo(nodeId)
249
+ const { entries, activity } = useAudit(nodeId)
250
+ const { diff: runDiff, result } = useDiff(nodeId)
251
+ const { blame } = useBlame(nodeId)
252
+ const { verify, quickCheck } = useVerification(nodeId)
253
+ ```
254
+
255
+ ### Hub Hooks
256
+
257
+ ```tsx
258
+ const status = useHubStatus()
259
+ const { upload: uploadBackup, download: downloadBackup } = useBackup()
260
+ const { upload, uploading, progress } = useFileUpload()
261
+ const { search, results } = useHubSearch()
262
+ const { schema } = useRemoteSchema(schemaId)
263
+ const { peers } = usePeerDiscovery()
264
+ ```
265
+
266
+ ### Plugin Hooks
267
+
268
+ ```tsx
269
+ const { registry } = usePluginRegistry()
270
+ const { plugins } = usePlugins()
271
+ const { contributions } = useContributions('view')
272
+ const { views } = useViews()
273
+ const { commands, execute } = useCommands()
274
+ ```
275
+
276
+ ## Sync Infrastructure
277
+
278
+ The package includes a full sync infrastructure layer:
279
+
280
+ | Module | Description |
281
+ | ----------------------- | ---------------------------------- |
282
+ | `WebSocketSyncProvider` | WebSocket-based sync with hub |
283
+ | `SyncManager` | Orchestrates sync across providers |
284
+ | `NodePool` | Manages Y.Doc instances |
285
+ | `ConnectionManager` | WebSocket connection lifecycle |
286
+ | `NodeStoreSyncProvider` | Syncs NodeStore changes |
287
+ | `MetaBridge` | Bridges node metadata to Yjs |
288
+ | `OfflineQueue` | Queues mutations while offline |
289
+ | `BlobSync` | Syncs file blobs to hub |
290
+
291
+ ## Type Safety with FlatNode
292
+
293
+ All hooks return `FlatNode<Schema>` which flattens properties to the top level:
294
+
295
+ ```tsx
296
+ // Properties are directly accessible
297
+ const title = page.title // string (correctly typed)
298
+ ```
299
+
300
+ ## API Reference
301
+
302
+ ### `useQuery`
303
+
304
+ | Parameter | Type | Description |
305
+ | ------------- | ----------------------- | ------------------------- |
306
+ | `schema` | `DefinedSchema<P>` | The schema to query |
307
+ | `idOrFilter?` | `string \| QueryFilter` | Node ID or filter options |
308
+
309
+ ### `useMutate`
310
+
311
+ Returns: `create`, `update`, `remove`, `restore`, `mutate`, `isPending`, `pendingCount`
312
+
313
+ ### `useNode`
314
+
315
+ | Parameter | Type | Description |
316
+ | ---------- | ------------------ | ----------------------------- |
317
+ | `schema` | `DefinedSchema<P>` | Schema with `document: 'yjs'` |
318
+ | `id` | `string \| null` | Node ID |
319
+ | `options?` | `UseNodeOptions` | Configuration |
320
+
321
+ ## Related Packages
322
+
323
+ - `@xnetjs/data` -- Schema system and NodeStore
324
+ - `@xnetjs/editor` -- Rich text editor components
325
+ - `@xnetjs/history` -- Time machine and audit hooks
326
+ - `@xnetjs/plugins` -- Plugin system hooks
327
+ - `@xnetjs/identity` -- DID and key management