@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 +21 -0
- package/README.md +327 -0
- package/dist/chunk-CWHCGYDW.js +2238 -0
- package/dist/index.d.ts +2782 -0
- package/dist/index.js +4798 -0
- package/dist/instrumentation-Cn94kn8-.d.ts +42 -0
- package/dist/internal.d.ts +31 -0
- package/dist/internal.js +10 -0
- package/package.json +61 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2782 @@
|
|
|
1
|
+
import { PropertyBuilder, SchemaIRI, MigrationInfo, InferCreateProps, NodeState, MigratedNodeState, DefinedSchema, ColumnDefinition, ViewConfig, FilterGroup, SortConfig, CellValue, Schema, DatabaseSchemaMetadata, NodeId, NodeStore, NodeStorageAdapter, AuthGrant, NodeChange, NodePayload } from '@xnetjs/data';
|
|
2
|
+
export { ColumnConfig, ColumnDefinition, ColumnType, ViewConfig, ViewType } from '@xnetjs/data';
|
|
3
|
+
import { DID, ContentId, AuthAction } from '@xnetjs/core';
|
|
4
|
+
import { Awareness } from 'y-protocols/awareness';
|
|
5
|
+
import * as Y from 'yjs';
|
|
6
|
+
import { TimelineEntry, HistoryTarget, HistoricalState, PropertyDiff, UndoManagerOptions, AuditEntry, ActivitySummary, DiffResult, BlameInfo, VerificationOptions, VerificationResult } from '@xnetjs/history';
|
|
7
|
+
import * as react from 'react';
|
|
8
|
+
import { Component, ReactNode, ErrorInfo } from 'react';
|
|
9
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
10
|
+
import { Identity, KeyBundle, PQKeyRegistry, HybridKeyBundle } from '@xnetjs/identity';
|
|
11
|
+
import { SecurityLevel, UnifiedSignature, VerificationResult as VerificationResult$1 } from '@xnetjs/crypto';
|
|
12
|
+
import { DataBridge } from '@xnetjs/data-bridge';
|
|
13
|
+
import { Platform, PluginRegistry, RegisteredPlugin, ViewContribution, CommandContribution, SlashCommandContribution, SidebarContribution, EditorContribution, PropertyHandlerContribution, BlockContribution, SettingContribution } from '@xnetjs/plugins';
|
|
14
|
+
export { I as InstrumentationContext, a as InstrumentationContextValue, Q as QueryTrackerLike, Y as YDocRegistryLike, u as useInstrumentation } from './instrumentation-Cn94kn8-.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* FlatNode utilities - Flatten NodeState properties to top level
|
|
18
|
+
*
|
|
19
|
+
* This module provides type-safe flattening of Node properties so developers
|
|
20
|
+
* can access `node.title` instead of `node.properties.title`.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Base node fields (always present on any node)
|
|
25
|
+
*/
|
|
26
|
+
interface NodeBase {
|
|
27
|
+
/** Unique node identifier */
|
|
28
|
+
id: string;
|
|
29
|
+
/** Schema IRI this node conforms to */
|
|
30
|
+
schemaId: SchemaIRI;
|
|
31
|
+
/** Creation timestamp */
|
|
32
|
+
createdAt: number;
|
|
33
|
+
/** Creator's DID */
|
|
34
|
+
createdBy: DID;
|
|
35
|
+
/** Last update timestamp */
|
|
36
|
+
updatedAt: number;
|
|
37
|
+
/** Last updater's DID */
|
|
38
|
+
updatedBy: DID;
|
|
39
|
+
/** Whether node is soft-deleted */
|
|
40
|
+
deleted: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* True if this node's schema is not registered/known to the current app version.
|
|
43
|
+
* The node data is still accessible but may not have proper type information.
|
|
44
|
+
* UI should render a generic "Unknown data type" component for these nodes.
|
|
45
|
+
*/
|
|
46
|
+
_unknownSchema?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Properties from future schema versions that aren't known to the current schema.
|
|
49
|
+
* Preserved for forward compatibility - can be displayed in a "raw data" view.
|
|
50
|
+
*/
|
|
51
|
+
_unknown?: Record<string, unknown>;
|
|
52
|
+
/**
|
|
53
|
+
* The schema version that last wrote to this node.
|
|
54
|
+
* Useful for detecting when migrations might be needed.
|
|
55
|
+
*/
|
|
56
|
+
_schemaVersion?: string;
|
|
57
|
+
/**
|
|
58
|
+
* The original schema IRI this node was migrated from.
|
|
59
|
+
* Only present if the node was automatically migrated on read.
|
|
60
|
+
*/
|
|
61
|
+
_migratedFrom?: SchemaIRI;
|
|
62
|
+
/**
|
|
63
|
+
* Full migration info if the node was migrated.
|
|
64
|
+
* Includes lossless flag and any warnings about data loss.
|
|
65
|
+
*/
|
|
66
|
+
_migrationInfo?: MigrationInfo;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A flattened node with properties at the top level.
|
|
70
|
+
*
|
|
71
|
+
* Instead of `node.properties.title`, access `node.title` directly.
|
|
72
|
+
* All property types are correctly inferred from the schema.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* const { data } = useQuery(TaskSchema, id)
|
|
77
|
+
* // data is FlatNode<typeof TaskSchema._properties>
|
|
78
|
+
*
|
|
79
|
+
* console.log(data.title) // string - correctly typed!
|
|
80
|
+
* console.log(data.status) // 'todo' | 'done' - union type!
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
type FlatNode<P extends Record<string, PropertyBuilder>> = NodeBase & InferCreateProps<P>;
|
|
84
|
+
/**
|
|
85
|
+
* Options for flattenNode function
|
|
86
|
+
*/
|
|
87
|
+
interface FlattenNodeOptions {
|
|
88
|
+
/**
|
|
89
|
+
* Mark this node as having an unknown schema.
|
|
90
|
+
* This happens when the node's schemaId is not registered in the current app version.
|
|
91
|
+
*/
|
|
92
|
+
unknownSchema?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Migration info if the node was migrated from a different schema version.
|
|
95
|
+
*/
|
|
96
|
+
migrationInfo?: MigrationInfo;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Flatten a NodeState by spreading properties to top level.
|
|
100
|
+
*
|
|
101
|
+
* @param node - The NodeState with nested properties
|
|
102
|
+
* @param options - Optional settings for handling unknown schemas
|
|
103
|
+
* @returns A new object with properties flattened to top level
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const nodeState = {
|
|
108
|
+
* id: '123',
|
|
109
|
+
* schemaId: 'xnet://xnet.fyi/Task',
|
|
110
|
+
* properties: { title: 'My Task', status: 'todo' },
|
|
111
|
+
* // ...
|
|
112
|
+
* }
|
|
113
|
+
*
|
|
114
|
+
* const flat = flattenNode(nodeState)
|
|
115
|
+
* // { id: '123', schemaId: '...', title: 'My Task', status: 'todo', ... }
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare function flattenNode<P extends Record<string, PropertyBuilder>>(node: NodeState | MigratedNodeState, options?: FlattenNodeOptions): FlatNode<P>;
|
|
119
|
+
/**
|
|
120
|
+
* Flatten an array of NodeState objects.
|
|
121
|
+
*/
|
|
122
|
+
declare function flattenNodes<P extends Record<string, PropertyBuilder>>(nodes: (NodeState | MigratedNodeState)[], options?: FlattenNodeOptions): FlatNode<P>[];
|
|
123
|
+
/**
|
|
124
|
+
* Create a FlatNode with unknown schema flag.
|
|
125
|
+
* Use this when displaying nodes whose schema is not registered.
|
|
126
|
+
*/
|
|
127
|
+
declare function flattenUnknownSchemaNode(node: NodeState): FlatNode<Record<string, PropertyBuilder>>;
|
|
128
|
+
/**
|
|
129
|
+
* Create FlatNodes from an array, marking those with unknown schemas.
|
|
130
|
+
*
|
|
131
|
+
* @param nodes - Array of NodeState objects
|
|
132
|
+
* @param isSchemaKnown - Function to check if a schema is registered
|
|
133
|
+
* @returns Array of FlatNodes with _unknownSchema set appropriately
|
|
134
|
+
*/
|
|
135
|
+
declare function flattenNodesWithSchemaCheck<P extends Record<string, PropertyBuilder>>(nodes: NodeState[], isSchemaKnown: (schemaId: string) => boolean): FlatNode<P>[];
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* useQuery - Unified read hook for Nodes via DataBridge
|
|
139
|
+
*
|
|
140
|
+
* A single hook for all read operations:
|
|
141
|
+
* - List all nodes of a schema
|
|
142
|
+
* - Get a single node by ID
|
|
143
|
+
* - Query with filters
|
|
144
|
+
*
|
|
145
|
+
* Returns FlatNode with properties at top level for ergonomic access.
|
|
146
|
+
*
|
|
147
|
+
* Uses DataBridge for off-main-thread data access. The bridge handles
|
|
148
|
+
* caching and provides useSyncExternalStore-compatible subscriptions.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```tsx
|
|
152
|
+
* // List all tasks
|
|
153
|
+
* const { data: tasks } = useQuery(TaskSchema)
|
|
154
|
+
* tasks.forEach(task => console.log(task.title)) // Direct access!
|
|
155
|
+
*
|
|
156
|
+
* // Get single task by ID
|
|
157
|
+
* const { data: task } = useQuery(TaskSchema, taskId)
|
|
158
|
+
* console.log(task?.status) // Typed correctly!
|
|
159
|
+
*
|
|
160
|
+
* // Query with filters
|
|
161
|
+
* const { data: urgent } = useQuery(TaskSchema, {
|
|
162
|
+
* where: { status: 'urgent' },
|
|
163
|
+
* orderBy: { createdAt: 'desc' }
|
|
164
|
+
* })
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Sort direction
|
|
170
|
+
*/
|
|
171
|
+
type SortDirection = 'asc' | 'desc';
|
|
172
|
+
/**
|
|
173
|
+
* System fields that can be used for ordering
|
|
174
|
+
*/
|
|
175
|
+
type SystemOrderField = 'createdAt' | 'updatedAt';
|
|
176
|
+
/**
|
|
177
|
+
* Query filter options
|
|
178
|
+
*/
|
|
179
|
+
interface QueryFilter<P extends Record<string, PropertyBuilder> = Record<string, PropertyBuilder>> {
|
|
180
|
+
/** Filter conditions (property: value) */
|
|
181
|
+
where?: Partial<InferCreateProps<P>>;
|
|
182
|
+
/** Include soft-deleted nodes */
|
|
183
|
+
includeDeleted?: boolean;
|
|
184
|
+
/** Sort by property or system field (createdAt, updatedAt) */
|
|
185
|
+
orderBy?: {
|
|
186
|
+
[K in keyof InferCreateProps<P> | SystemOrderField]?: SortDirection;
|
|
187
|
+
};
|
|
188
|
+
/** Limit results */
|
|
189
|
+
limit?: number;
|
|
190
|
+
/** Offset for pagination */
|
|
191
|
+
offset?: number;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Migration warning info
|
|
195
|
+
*/
|
|
196
|
+
interface MigrationWarning {
|
|
197
|
+
/** The node ID that was migrated */
|
|
198
|
+
nodeId: string;
|
|
199
|
+
/** The original schema IRI */
|
|
200
|
+
from: string;
|
|
201
|
+
/** The target schema IRI */
|
|
202
|
+
to: string;
|
|
203
|
+
/** Warning messages about potential data loss */
|
|
204
|
+
warnings: string[];
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Result when querying a list of nodes
|
|
208
|
+
*/
|
|
209
|
+
interface QueryListResult<P extends Record<string, PropertyBuilder>> {
|
|
210
|
+
/** The queried nodes (flattened - access properties directly) */
|
|
211
|
+
data: FlatNode<P>[];
|
|
212
|
+
/** Whether currently loading */
|
|
213
|
+
loading: boolean;
|
|
214
|
+
/** Any error that occurred */
|
|
215
|
+
error: Error | null;
|
|
216
|
+
/** Reload the query */
|
|
217
|
+
reload: () => void;
|
|
218
|
+
/**
|
|
219
|
+
* Migration warnings for nodes that were migrated from different schema versions.
|
|
220
|
+
* Only populated if nodes required migration and the migration was lossy.
|
|
221
|
+
*/
|
|
222
|
+
migrationWarnings: MigrationWarning[];
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Result when querying a single node
|
|
226
|
+
*/
|
|
227
|
+
interface QuerySingleResult<P extends Record<string, PropertyBuilder>> {
|
|
228
|
+
/** The queried node (flattened - access properties directly), null if not found */
|
|
229
|
+
data: FlatNode<P> | null;
|
|
230
|
+
/** Whether currently loading */
|
|
231
|
+
loading: boolean;
|
|
232
|
+
/** Any error that occurred */
|
|
233
|
+
error: Error | null;
|
|
234
|
+
/** Reload the query */
|
|
235
|
+
reload: () => void;
|
|
236
|
+
/**
|
|
237
|
+
* Migration warnings if the node was migrated from a different schema version.
|
|
238
|
+
* Only populated if the node required migration and the migration was lossy.
|
|
239
|
+
*/
|
|
240
|
+
migrationWarnings: MigrationWarning[];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Query all nodes of a schema
|
|
244
|
+
*/
|
|
245
|
+
declare function useQuery<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>): QueryListResult<P>;
|
|
246
|
+
/**
|
|
247
|
+
* Query a single node by ID
|
|
248
|
+
*/
|
|
249
|
+
declare function useQuery<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, id: string): QuerySingleResult<P>;
|
|
250
|
+
/**
|
|
251
|
+
* Query nodes with filters
|
|
252
|
+
*/
|
|
253
|
+
declare function useQuery<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, filter: QueryFilter<P>): QueryListResult<P>;
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* useMutate - Unified write hook for Nodes via DataBridge
|
|
257
|
+
*
|
|
258
|
+
* A single hook for all write operations:
|
|
259
|
+
* - Create nodes (requires schema)
|
|
260
|
+
* - Update nodes (requires schema for type safety)
|
|
261
|
+
* - Delete nodes (by ID)
|
|
262
|
+
* - Atomic transactions (multiple operations)
|
|
263
|
+
*
|
|
264
|
+
* Uses DataBridge for off-main-thread data access.
|
|
265
|
+
*
|
|
266
|
+
* Features:
|
|
267
|
+
* - Immediate local updates (bridge updates UI subscribers synchronously)
|
|
268
|
+
* - Type-safe schema-bound mutations
|
|
269
|
+
* - Transaction support for atomic multi-node operations
|
|
270
|
+
* - Pending state tracking
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```tsx
|
|
274
|
+
* const { create, update, remove, mutate, isPending } = useMutate()
|
|
275
|
+
*
|
|
276
|
+
* // Simple operations
|
|
277
|
+
* await create(TaskSchema, { title: 'New Task', status: 'todo' })
|
|
278
|
+
* await update(TaskSchema, taskId, { status: 'done' }) // Type-safe!
|
|
279
|
+
* await remove(taskId)
|
|
280
|
+
*
|
|
281
|
+
* // Atomic transaction
|
|
282
|
+
* await mutate([
|
|
283
|
+
* { type: 'create', schema: TaskSchema, data: { title: 'Task 1' } },
|
|
284
|
+
* { type: 'update', id: taskId, data: { status: 'done' } },
|
|
285
|
+
* { type: 'delete', id: oldTaskId }
|
|
286
|
+
* ])
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Create operation for mutate
|
|
292
|
+
*/
|
|
293
|
+
interface MutateCreate<P extends Record<string, PropertyBuilder> = Record<string, PropertyBuilder>> {
|
|
294
|
+
type: 'create';
|
|
295
|
+
schema: DefinedSchema<P>;
|
|
296
|
+
data: InferCreateProps<P>;
|
|
297
|
+
id?: string;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Update operation for mutate
|
|
301
|
+
*/
|
|
302
|
+
interface MutateUpdate {
|
|
303
|
+
type: 'update';
|
|
304
|
+
id: string;
|
|
305
|
+
data: Record<string, unknown>;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Delete operation for mutate
|
|
309
|
+
*/
|
|
310
|
+
interface MutateDelete {
|
|
311
|
+
type: 'delete';
|
|
312
|
+
id: string;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Restore operation for mutate
|
|
316
|
+
*/
|
|
317
|
+
interface MutateRestore {
|
|
318
|
+
type: 'restore';
|
|
319
|
+
id: string;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* All possible mutate operations
|
|
323
|
+
*/
|
|
324
|
+
type MutateOp = MutateCreate<Record<string, PropertyBuilder>> | MutateUpdate | MutateDelete | MutateRestore;
|
|
325
|
+
/**
|
|
326
|
+
* Result from a mutate transaction
|
|
327
|
+
*/
|
|
328
|
+
interface MutateResult {
|
|
329
|
+
/** Results for each operation (NodeState or null for delete) */
|
|
330
|
+
results: (NodeState | null)[];
|
|
331
|
+
/** Batch ID when transaction backend is available */
|
|
332
|
+
batchId?: string;
|
|
333
|
+
/** Temp ID mapping when transaction backend is available */
|
|
334
|
+
tempIds?: Record<string, string>;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Result from useMutate hook
|
|
338
|
+
*/
|
|
339
|
+
interface UseMutateResult {
|
|
340
|
+
/**
|
|
341
|
+
* Create a new node.
|
|
342
|
+
* Requires a schema to know what type to create.
|
|
343
|
+
* Optionally specify a custom ID (otherwise auto-generated).
|
|
344
|
+
*
|
|
345
|
+
* @returns The created node (flattened), or null if creation failed
|
|
346
|
+
*/
|
|
347
|
+
create: <P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, data: InferCreateProps<P>, id?: string) => Promise<FlatNode<P> | null>;
|
|
348
|
+
/**
|
|
349
|
+
* Update an existing node by ID.
|
|
350
|
+
* Requires schema for type-safe property checking.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```tsx
|
|
354
|
+
* await update(TaskSchema, taskId, { status: 'done' }) // OK
|
|
355
|
+
* await update(TaskSchema, taskId, { typo: 'x' }) // Type error!
|
|
356
|
+
* ```
|
|
357
|
+
*
|
|
358
|
+
* @returns The updated node (flattened), or null if update failed
|
|
359
|
+
*/
|
|
360
|
+
update: <P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, id: string, data: Partial<InferCreateProps<P>>) => Promise<FlatNode<P> | null>;
|
|
361
|
+
/**
|
|
362
|
+
* Delete a node by ID (soft delete).
|
|
363
|
+
*/
|
|
364
|
+
remove: (id: string) => Promise<void>;
|
|
365
|
+
/**
|
|
366
|
+
* Restore a deleted node by ID.
|
|
367
|
+
*
|
|
368
|
+
* @returns The restored node (flattened), or null if restore failed
|
|
369
|
+
*/
|
|
370
|
+
restore: (id: string) => Promise<FlatNode<Record<string, PropertyBuilder>> | null>;
|
|
371
|
+
/**
|
|
372
|
+
* Execute multiple operations atomically.
|
|
373
|
+
* All operations succeed or fail together.
|
|
374
|
+
*
|
|
375
|
+
* Note: Transaction support requires MainThreadBridge with direct NodeStore access.
|
|
376
|
+
* WorkerBridge executes operations sequentially (not truly atomic).
|
|
377
|
+
*/
|
|
378
|
+
mutate: (ops: MutateOp[]) => Promise<MutateResult | null>;
|
|
379
|
+
/**
|
|
380
|
+
* Whether any mutation is currently in progress.
|
|
381
|
+
*/
|
|
382
|
+
isPending: boolean;
|
|
383
|
+
/**
|
|
384
|
+
* Number of pending mutations.
|
|
385
|
+
*/
|
|
386
|
+
pendingCount: number;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Hook for all write operations on Nodes via DataBridge.
|
|
390
|
+
*
|
|
391
|
+
* Provides both convenience methods (create, update, remove) and
|
|
392
|
+
* a full transaction API (mutate) for atomic multi-node operations.
|
|
393
|
+
*
|
|
394
|
+
* All operations update the local cache immediately, and subscribers
|
|
395
|
+
* see changes synchronously. Background sync handles persistence.
|
|
396
|
+
*/
|
|
397
|
+
declare function useMutate(): UseMutateResult;
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* useNode - The unified hook for working with a single Node
|
|
401
|
+
*
|
|
402
|
+
* This is the primary hook for editing Nodes. It provides:
|
|
403
|
+
* - Node properties (flattened for ergonomic access)
|
|
404
|
+
* - Y.Doc for collaborative rich text (if schema specifies document: 'yjs')
|
|
405
|
+
* - Auto-sync via WebSocket
|
|
406
|
+
* - Type-safe mutations (update, remove)
|
|
407
|
+
* - Presence awareness (live + hub snapshot)
|
|
408
|
+
* - Auto-create with createIfMissing
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* ```tsx
|
|
412
|
+
* const {
|
|
413
|
+
* data, // FlatNode - access data.title directly!
|
|
414
|
+
* doc, // Y.Doc for rich text
|
|
415
|
+
* update, // Type-safe update function
|
|
416
|
+
* remove, // Soft delete
|
|
417
|
+
* syncStatus, // 'offline' | 'connecting' | 'connected'
|
|
418
|
+
* peerCount, // Number of connected peers
|
|
419
|
+
* presence, // Presence list (live + hub snapshot)
|
|
420
|
+
* loading,
|
|
421
|
+
* error
|
|
422
|
+
* } = useNode(PageSchema, pageId, {
|
|
423
|
+
* createIfMissing: { title: 'Untitled' }
|
|
424
|
+
* })
|
|
425
|
+
*
|
|
426
|
+
* // Update is type-safe!
|
|
427
|
+
* update({ title: 'New Title' }) // OK
|
|
428
|
+
* update({ typo: 'x' }) // Type error!
|
|
429
|
+
* ```
|
|
430
|
+
*/
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Sync connection status
|
|
434
|
+
*/
|
|
435
|
+
type SyncStatus$1 = 'offline' | 'connecting' | 'connected' | 'error';
|
|
436
|
+
interface PresenceUser {
|
|
437
|
+
did: string;
|
|
438
|
+
name?: string;
|
|
439
|
+
color?: string;
|
|
440
|
+
lastSeen?: number;
|
|
441
|
+
isStale?: boolean;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Options for useNode
|
|
445
|
+
*/
|
|
446
|
+
interface UseNodeOptions<P extends Record<string, PropertyBuilder>> {
|
|
447
|
+
/** Signaling servers for y-webrtc (default: localhost for dev) */
|
|
448
|
+
signalingServers?: string[];
|
|
449
|
+
/** Disable auto-sync (default: false) */
|
|
450
|
+
disableSync?: boolean;
|
|
451
|
+
/** Debounce persistence delay in ms (default: 1000) */
|
|
452
|
+
persistDebounce?: number;
|
|
453
|
+
/**
|
|
454
|
+
* Auto-create the node if it doesn't exist.
|
|
455
|
+
* Provide the default properties to use for creation.
|
|
456
|
+
*/
|
|
457
|
+
createIfMissing?: InferCreateProps<P>;
|
|
458
|
+
/**
|
|
459
|
+
* User's DID for presence/cursors. If provided, broadcasts awareness state.
|
|
460
|
+
*/
|
|
461
|
+
did?: string;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Result from useNode hook
|
|
465
|
+
*/
|
|
466
|
+
interface UseNodeResult<P extends Record<string, PropertyBuilder>> {
|
|
467
|
+
/** Node properties (flattened - access directly: data.title) */
|
|
468
|
+
data: FlatNode<P> | null;
|
|
469
|
+
/** Yjs document instance (null if schema has no document type) */
|
|
470
|
+
doc: Y.Doc | null;
|
|
471
|
+
/**
|
|
472
|
+
* Update node properties (type-safe).
|
|
473
|
+
* Only properties defined in the schema are allowed.
|
|
474
|
+
*/
|
|
475
|
+
update: (properties: Partial<InferCreateProps<P>>) => Promise<void>;
|
|
476
|
+
/**
|
|
477
|
+
* Soft-delete the node.
|
|
478
|
+
*/
|
|
479
|
+
remove: () => Promise<void>;
|
|
480
|
+
/** Whether currently loading */
|
|
481
|
+
loading: boolean;
|
|
482
|
+
/** Any error that occurred */
|
|
483
|
+
error: Error | null;
|
|
484
|
+
/** Whether document has unsaved changes */
|
|
485
|
+
isDirty: boolean;
|
|
486
|
+
/** Last persistence timestamp */
|
|
487
|
+
lastSavedAt: number | null;
|
|
488
|
+
/** Whether the node was auto-created (via createIfMissing) */
|
|
489
|
+
wasCreated: boolean;
|
|
490
|
+
/** Sync connection status */
|
|
491
|
+
syncStatus: SyncStatus$1;
|
|
492
|
+
/** Sync error message (if syncStatus is 'error') */
|
|
493
|
+
syncError: string | null;
|
|
494
|
+
/** Connected peer count */
|
|
495
|
+
peerCount: number;
|
|
496
|
+
/** Presence list (live awareness + hub snapshot) */
|
|
497
|
+
presence: PresenceUser[];
|
|
498
|
+
/** Yjs Awareness instance (for TipTap CollaborationCursor) */
|
|
499
|
+
awareness: Awareness | null;
|
|
500
|
+
/** Manually trigger save */
|
|
501
|
+
save: () => Promise<void>;
|
|
502
|
+
/** Reload from storage */
|
|
503
|
+
reload: () => Promise<void>;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Load a Node with its CRDT document.
|
|
507
|
+
*
|
|
508
|
+
* This is the primary hook for editing content. It combines:
|
|
509
|
+
* - Data loading (with FlatNode for ergonomic access)
|
|
510
|
+
* - Y.Doc for collaborative rich text
|
|
511
|
+
* - Type-safe mutations
|
|
512
|
+
* - Real-time sync and presence
|
|
513
|
+
*/
|
|
514
|
+
declare function useNode<P extends Record<string, PropertyBuilder>>(schema: DefinedSchema<P>, id: string | null, options?: UseNodeOptions<P>): UseNodeResult<P>;
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* useDatabaseDoc - Hook for database column and view operations
|
|
518
|
+
*
|
|
519
|
+
* Provides reactive access to the database's Y.Doc structure:
|
|
520
|
+
* - Column definitions (CRDT-ordered)
|
|
521
|
+
* - View configurations
|
|
522
|
+
* - Column and view CRUD operations
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* ```tsx
|
|
526
|
+
* const {
|
|
527
|
+
* columns,
|
|
528
|
+
* views,
|
|
529
|
+
* createColumn,
|
|
530
|
+
* updateColumn,
|
|
531
|
+
* deleteColumn,
|
|
532
|
+
* reorderColumn,
|
|
533
|
+
* createView,
|
|
534
|
+
* updateView,
|
|
535
|
+
* deleteView
|
|
536
|
+
* } = useDatabaseDoc(databaseId)
|
|
537
|
+
* ```
|
|
538
|
+
*/
|
|
539
|
+
|
|
540
|
+
interface UseDatabaseDocResult {
|
|
541
|
+
/** All column definitions (CRDT-ordered) */
|
|
542
|
+
columns: ColumnDefinition[];
|
|
543
|
+
/** All view configurations */
|
|
544
|
+
views: ViewConfig[];
|
|
545
|
+
/** Y.Doc for direct access (if needed) */
|
|
546
|
+
doc: Y.Doc | null;
|
|
547
|
+
/** Whether the doc is loading */
|
|
548
|
+
loading: boolean;
|
|
549
|
+
/** Any error that occurred */
|
|
550
|
+
error: Error | null;
|
|
551
|
+
/** Create a new column */
|
|
552
|
+
createColumn: (definition: Omit<ColumnDefinition, 'id'>) => string | null;
|
|
553
|
+
/** Update a column's properties */
|
|
554
|
+
updateColumn: (columnId: string, updates: Partial<Omit<ColumnDefinition, 'id'>>) => void;
|
|
555
|
+
/** Delete a column */
|
|
556
|
+
deleteColumn: (columnId: string) => void;
|
|
557
|
+
/** Reorder a column to a new position */
|
|
558
|
+
reorderColumn: (columnId: string, newIndex: number) => void;
|
|
559
|
+
/** Duplicate a column */
|
|
560
|
+
duplicateColumn: (columnId: string, newName?: string) => string | null;
|
|
561
|
+
/** Get a single column by ID */
|
|
562
|
+
getColumn: (columnId: string) => ColumnDefinition | null;
|
|
563
|
+
/** Create a new view */
|
|
564
|
+
createView: (config: Omit<ViewConfig, 'id'>) => string | null;
|
|
565
|
+
/** Update a view's properties */
|
|
566
|
+
updateView: (viewId: string, updates: Partial<Omit<ViewConfig, 'id'>>) => void;
|
|
567
|
+
/** Delete a view */
|
|
568
|
+
deleteView: (viewId: string) => void;
|
|
569
|
+
/** Duplicate a view */
|
|
570
|
+
duplicateView: (viewId: string, newName?: string) => string | null;
|
|
571
|
+
/** Get a single view by ID */
|
|
572
|
+
getView: (viewId: string) => ViewConfig | null;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Hook for database column and view operations.
|
|
576
|
+
*
|
|
577
|
+
* Provides reactive access to the database's Y.Doc structure with
|
|
578
|
+
* CRDT-based column ordering and real-time schema sync.
|
|
579
|
+
*/
|
|
580
|
+
declare function useDatabaseDoc(databaseId: string): UseDatabaseDocResult;
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* useDatabase - Hook for database row operations with pagination
|
|
584
|
+
*
|
|
585
|
+
* Provides:
|
|
586
|
+
* - Paginated row queries
|
|
587
|
+
* - Row CRUD operations
|
|
588
|
+
* - Integration with useDatabaseDoc for columns/views
|
|
589
|
+
*
|
|
590
|
+
* @example
|
|
591
|
+
* ```tsx
|
|
592
|
+
* const {
|
|
593
|
+
* rows,
|
|
594
|
+
* columns,
|
|
595
|
+
* views,
|
|
596
|
+
* loading,
|
|
597
|
+
* hasMore,
|
|
598
|
+
* loadMore,
|
|
599
|
+
* createRow,
|
|
600
|
+
* updateRow,
|
|
601
|
+
* deleteRow
|
|
602
|
+
* } = useDatabase(databaseId)
|
|
603
|
+
* ```
|
|
604
|
+
*/
|
|
605
|
+
|
|
606
|
+
interface UseDatabaseOptions {
|
|
607
|
+
/** Active view ID (uses first view if not specified) */
|
|
608
|
+
view?: string;
|
|
609
|
+
/** Override view filters */
|
|
610
|
+
filters?: FilterGroup;
|
|
611
|
+
/** Override view sorts */
|
|
612
|
+
sorts?: SortConfig[];
|
|
613
|
+
/** Search query (full-text search) - not yet implemented */
|
|
614
|
+
search?: string;
|
|
615
|
+
/** Page size (default: 50) */
|
|
616
|
+
pageSize?: number;
|
|
617
|
+
}
|
|
618
|
+
interface DatabaseRow {
|
|
619
|
+
/** Row ID */
|
|
620
|
+
id: string;
|
|
621
|
+
/** Sort key for ordering */
|
|
622
|
+
sortKey: string;
|
|
623
|
+
/** Cell values keyed by column ID */
|
|
624
|
+
cells: Record<string, CellValue>;
|
|
625
|
+
/** Creation timestamp */
|
|
626
|
+
createdAt: number;
|
|
627
|
+
/** Creator DID */
|
|
628
|
+
createdBy: string;
|
|
629
|
+
}
|
|
630
|
+
interface UseDatabaseResult {
|
|
631
|
+
/** Column definitions */
|
|
632
|
+
columns: ColumnDefinition[];
|
|
633
|
+
/** View configurations */
|
|
634
|
+
views: ViewConfig[];
|
|
635
|
+
/** Loaded rows */
|
|
636
|
+
rows: DatabaseRow[];
|
|
637
|
+
/** Total row count */
|
|
638
|
+
total: number;
|
|
639
|
+
/** Whether more rows are available */
|
|
640
|
+
hasMore: boolean;
|
|
641
|
+
/** Load more rows */
|
|
642
|
+
loadMore: () => Promise<void>;
|
|
643
|
+
/** Active view configuration */
|
|
644
|
+
activeView: ViewConfig | null;
|
|
645
|
+
/** Set the active view */
|
|
646
|
+
setActiveView: (viewId: string) => void;
|
|
647
|
+
/** Create a new row */
|
|
648
|
+
createRow: (values?: Record<string, CellValue>) => Promise<string>;
|
|
649
|
+
/** Update a row's cell values */
|
|
650
|
+
updateRow: (rowId: string, values: Record<string, CellValue>) => Promise<void>;
|
|
651
|
+
/** Delete a row */
|
|
652
|
+
deleteRow: (rowId: string) => Promise<void>;
|
|
653
|
+
/** Reorder a row */
|
|
654
|
+
reorderRow: (rowId: string, before?: string, after?: string) => Promise<void>;
|
|
655
|
+
/** Delete multiple rows */
|
|
656
|
+
deleteRows: (rowIds: string[]) => Promise<void>;
|
|
657
|
+
/** Whether initially loading */
|
|
658
|
+
loading: boolean;
|
|
659
|
+
/** Whether loading more rows */
|
|
660
|
+
loadingMore: boolean;
|
|
661
|
+
/** Any error that occurred */
|
|
662
|
+
error: Error | null;
|
|
663
|
+
/** Refetch all rows */
|
|
664
|
+
refetch: () => Promise<void>;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Hook for database row operations with pagination.
|
|
668
|
+
*/
|
|
669
|
+
declare function useDatabase(databaseId: string, options?: UseDatabaseOptions): UseDatabaseResult;
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* useDatabaseRow - Hook for single row operations with optimistic updates
|
|
673
|
+
*
|
|
674
|
+
* Provides:
|
|
675
|
+
* - Row data with cell values
|
|
676
|
+
* - Y.Doc for rich text cells
|
|
677
|
+
* - Optimistic updates
|
|
678
|
+
* - Delete operation
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* ```tsx
|
|
682
|
+
* const {
|
|
683
|
+
* row,
|
|
684
|
+
* doc,
|
|
685
|
+
* update,
|
|
686
|
+
* delete: deleteRow,
|
|
687
|
+
* loading
|
|
688
|
+
* } = useDatabaseRow(rowId)
|
|
689
|
+
* ```
|
|
690
|
+
*/
|
|
691
|
+
|
|
692
|
+
interface DatabaseRowData {
|
|
693
|
+
/** Row ID */
|
|
694
|
+
id: string;
|
|
695
|
+
/** Database ID this row belongs to */
|
|
696
|
+
databaseId: string;
|
|
697
|
+
/** Sort key for ordering */
|
|
698
|
+
sortKey: string;
|
|
699
|
+
/** Cell values keyed by column ID */
|
|
700
|
+
cells: Record<string, CellValue>;
|
|
701
|
+
/** Creation timestamp */
|
|
702
|
+
createdAt: number;
|
|
703
|
+
/** Creator DID */
|
|
704
|
+
createdBy: string;
|
|
705
|
+
}
|
|
706
|
+
interface UseDatabaseRowResult {
|
|
707
|
+
/** Row data */
|
|
708
|
+
row: DatabaseRowData | null;
|
|
709
|
+
/** Y.Doc for rich text cells (if any) */
|
|
710
|
+
doc: Y.Doc | null;
|
|
711
|
+
/** Update cell values (with optimistic UI) */
|
|
712
|
+
update: (values: Record<string, CellValue>) => Promise<void>;
|
|
713
|
+
/** Delete this row */
|
|
714
|
+
delete: () => Promise<void>;
|
|
715
|
+
/** Loading state */
|
|
716
|
+
loading: boolean;
|
|
717
|
+
/** Error state */
|
|
718
|
+
error: Error | null;
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Hook for single row operations with optimistic updates.
|
|
722
|
+
*/
|
|
723
|
+
declare function useDatabaseRow(rowId: string): UseDatabaseRowResult;
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* useCell - Hook for individual cell editing with debounced saves
|
|
727
|
+
*
|
|
728
|
+
* Provides:
|
|
729
|
+
* - Cell value with optimistic updates
|
|
730
|
+
* - Debounced persistence
|
|
731
|
+
* - Clear operation
|
|
732
|
+
*
|
|
733
|
+
* @example
|
|
734
|
+
* ```tsx
|
|
735
|
+
* const { value, setValue, saving } = useCell<string>(rowId, 'title')
|
|
736
|
+
*
|
|
737
|
+
* return (
|
|
738
|
+
* <Input
|
|
739
|
+
* value={value ?? ''}
|
|
740
|
+
* onChange={(e) => setValue(e.target.value)}
|
|
741
|
+
* className={saving ? 'opacity-50' : ''}
|
|
742
|
+
* />
|
|
743
|
+
* )
|
|
744
|
+
* ```
|
|
745
|
+
*/
|
|
746
|
+
|
|
747
|
+
interface UseCellResult<T extends CellValue = CellValue> {
|
|
748
|
+
/** Cell value */
|
|
749
|
+
value: T | null;
|
|
750
|
+
/** Update cell value (debounced save) */
|
|
751
|
+
setValue: (value: T | null) => void;
|
|
752
|
+
/** Clear cell value */
|
|
753
|
+
clear: () => void;
|
|
754
|
+
/** Whether cell is being saved */
|
|
755
|
+
saving: boolean;
|
|
756
|
+
/** Error from last save */
|
|
757
|
+
error: Error | null;
|
|
758
|
+
}
|
|
759
|
+
interface UseCellOptions {
|
|
760
|
+
/** Debounce delay in ms (default: 300) */
|
|
761
|
+
debounce?: number;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Hook for individual cell editing with debounced saves.
|
|
765
|
+
*/
|
|
766
|
+
declare function useCell<T extends CellValue = CellValue>(rowId: string, columnId: string, options?: UseCellOptions): UseCellResult<T>;
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* useRelatedRows - Hook for loading related row data for relation columns
|
|
770
|
+
*
|
|
771
|
+
* Fetches the row data for a list of row IDs, typically from a relation column.
|
|
772
|
+
*
|
|
773
|
+
* @example
|
|
774
|
+
* ```tsx
|
|
775
|
+
* const { rows, loading, error } = useRelatedRows(['row1', 'row2'])
|
|
776
|
+
* // rows contains the full row data for display
|
|
777
|
+
* ```
|
|
778
|
+
*/
|
|
779
|
+
|
|
780
|
+
interface UseRelatedRowsResult {
|
|
781
|
+
/** Loaded row data */
|
|
782
|
+
rows: DatabaseRow[];
|
|
783
|
+
/** Whether rows are loading */
|
|
784
|
+
loading: boolean;
|
|
785
|
+
/** Any error that occurred */
|
|
786
|
+
error: Error | null;
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Hook for loading related row data.
|
|
790
|
+
*
|
|
791
|
+
* @param rowIds - Array of row IDs to load
|
|
792
|
+
* @returns Object containing rows, loading state, and error
|
|
793
|
+
*/
|
|
794
|
+
declare function useRelatedRows(rowIds: string[]): UseRelatedRowsResult;
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* useReverseRelations - Hook for finding rows that link TO a given row
|
|
798
|
+
*
|
|
799
|
+
* Finds all rows in other databases that have relation columns pointing
|
|
800
|
+
* to the specified row. Useful for showing "backlinks" or "linked from" sections.
|
|
801
|
+
*
|
|
802
|
+
* @example
|
|
803
|
+
* ```tsx
|
|
804
|
+
* const { relations, loading, error } = useReverseRelations(taskId, tasksDatabaseId)
|
|
805
|
+
* // relations contains all rows that link to this task
|
|
806
|
+
* ```
|
|
807
|
+
*/
|
|
808
|
+
|
|
809
|
+
interface ReverseRelation {
|
|
810
|
+
/** The row that links to the target row */
|
|
811
|
+
row: DatabaseRow;
|
|
812
|
+
/** The relation column through which it links */
|
|
813
|
+
column: ColumnDefinition;
|
|
814
|
+
/** ID of the source database */
|
|
815
|
+
sourceDatabaseId: string;
|
|
816
|
+
/** Title of the source database */
|
|
817
|
+
sourceDatabaseTitle: string;
|
|
818
|
+
}
|
|
819
|
+
interface UseReverseRelationsResult {
|
|
820
|
+
/** Found reverse relations */
|
|
821
|
+
relations: ReverseRelation[];
|
|
822
|
+
/** Whether relations are loading */
|
|
823
|
+
loading: boolean;
|
|
824
|
+
/** Any error that occurred */
|
|
825
|
+
error: Error | null;
|
|
826
|
+
/** Refetch the relations */
|
|
827
|
+
refetch: () => void;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Hook for finding reverse relations (backlinks) to a row.
|
|
831
|
+
*
|
|
832
|
+
* @param rowId - The row ID to find links to
|
|
833
|
+
* @param databaseId - The database containing the row
|
|
834
|
+
* @returns Object containing relations, loading state, and error
|
|
835
|
+
*/
|
|
836
|
+
declare function useReverseRelations(rowId: string, databaseId: string): UseReverseRelationsResult;
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* useDatabaseSchema - Hook for database-defined schema access
|
|
840
|
+
*
|
|
841
|
+
* Provides reactive access to a database's schema metadata and properties.
|
|
842
|
+
* Works with the schema registry to resolve database-defined schemas.
|
|
843
|
+
*
|
|
844
|
+
* @example
|
|
845
|
+
* ```tsx
|
|
846
|
+
* const {
|
|
847
|
+
* schema,
|
|
848
|
+
* metadata,
|
|
849
|
+
* loading,
|
|
850
|
+
* error
|
|
851
|
+
* } = useDatabaseSchema(databaseId)
|
|
852
|
+
*
|
|
853
|
+
* // Access schema version
|
|
854
|
+
* console.log(metadata?.version) // "1.2.0"
|
|
855
|
+
*
|
|
856
|
+
* // Access schema properties
|
|
857
|
+
* schema?.properties.forEach(prop => console.log(prop.name))
|
|
858
|
+
* ```
|
|
859
|
+
*/
|
|
860
|
+
|
|
861
|
+
interface UseDatabaseSchemaResult {
|
|
862
|
+
/** The resolved Schema object, or null if not loaded */
|
|
863
|
+
schema: Schema | null;
|
|
864
|
+
/** The schema metadata from the database's Y.Doc */
|
|
865
|
+
metadata: DatabaseSchemaMetadata | null;
|
|
866
|
+
/** The schema IRI for this database */
|
|
867
|
+
schemaIRI: string | null;
|
|
868
|
+
/** Whether the schema is loading */
|
|
869
|
+
loading: boolean;
|
|
870
|
+
/** Any error that occurred */
|
|
871
|
+
error: Error | null;
|
|
872
|
+
/** Force refresh the schema */
|
|
873
|
+
refresh: () => void;
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Hook for accessing a database's schema.
|
|
877
|
+
*
|
|
878
|
+
* Loads the database's Y.Doc, extracts schema metadata and columns,
|
|
879
|
+
* builds the Schema object, and registers it with the schema registry.
|
|
880
|
+
*
|
|
881
|
+
* @param databaseId - The database node ID
|
|
882
|
+
* @returns The schema, metadata, and loading state
|
|
883
|
+
*/
|
|
884
|
+
declare function useDatabaseSchema(databaseId: string | undefined): UseDatabaseSchemaResult;
|
|
885
|
+
|
|
886
|
+
interface UseCommentsOptions {
|
|
887
|
+
/** The Node ID to get comments for (any schema) */
|
|
888
|
+
nodeId: string;
|
|
889
|
+
/** Optional: filter to specific anchor type */
|
|
890
|
+
anchorType?: 'text' | 'cell' | 'row' | 'column' | 'canvas-position' | 'canvas-object' | 'node';
|
|
891
|
+
}
|
|
892
|
+
interface CommentThread {
|
|
893
|
+
/** The root comment (holds anchor data and resolved state) */
|
|
894
|
+
root: CommentNode;
|
|
895
|
+
/** Replies to this thread (sorted by Lamport time) */
|
|
896
|
+
replies: CommentNode[];
|
|
897
|
+
}
|
|
898
|
+
/** Flattened comment node for easier access */
|
|
899
|
+
interface CommentNode {
|
|
900
|
+
id: string;
|
|
901
|
+
schemaId: string;
|
|
902
|
+
createdAt: number;
|
|
903
|
+
lamportTime: number;
|
|
904
|
+
wallTime: number;
|
|
905
|
+
properties: {
|
|
906
|
+
target: string;
|
|
907
|
+
targetSchema?: string;
|
|
908
|
+
inReplyTo?: string;
|
|
909
|
+
anchorType: string;
|
|
910
|
+
anchorData: string;
|
|
911
|
+
content: string;
|
|
912
|
+
attachments?: string[];
|
|
913
|
+
replyToUser?: string;
|
|
914
|
+
replyToCommentId?: string;
|
|
915
|
+
resolved: boolean;
|
|
916
|
+
resolvedBy?: string;
|
|
917
|
+
resolvedAt?: number;
|
|
918
|
+
edited: boolean;
|
|
919
|
+
editedAt?: number;
|
|
920
|
+
createdBy: string;
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
interface AddCommentOptions {
|
|
924
|
+
/** Comment content (GitHub-flavored markdown) */
|
|
925
|
+
content: string;
|
|
926
|
+
/** Type of anchor */
|
|
927
|
+
anchorType: 'text' | 'cell' | 'row' | 'column' | 'canvas-position' | 'canvas-object' | 'node';
|
|
928
|
+
/** JSON-encoded anchor data */
|
|
929
|
+
anchorData: string;
|
|
930
|
+
/** Schema IRI of the target Node (optimization) */
|
|
931
|
+
targetSchema?: string;
|
|
932
|
+
}
|
|
933
|
+
interface ReplyContext {
|
|
934
|
+
/** DID of user being replied to (for "replying to @user" UI) */
|
|
935
|
+
replyToUser?: string;
|
|
936
|
+
/** Comment ID being referenced (for "in reply to X" display) */
|
|
937
|
+
replyToCommentId?: string;
|
|
938
|
+
}
|
|
939
|
+
interface UseCommentsResult {
|
|
940
|
+
/** All comments on the target node */
|
|
941
|
+
comments: CommentNode[];
|
|
942
|
+
/** Comments grouped into threads (root + replies) */
|
|
943
|
+
threads: CommentThread[];
|
|
944
|
+
/** Total comment count */
|
|
945
|
+
count: number;
|
|
946
|
+
/** Count of unresolved threads */
|
|
947
|
+
unresolvedCount: number;
|
|
948
|
+
/** Whether loading */
|
|
949
|
+
loading: boolean;
|
|
950
|
+
/** Any error */
|
|
951
|
+
error: Error | null;
|
|
952
|
+
/** Add a new root comment */
|
|
953
|
+
addComment: (options: AddCommentOptions) => Promise<string | null>;
|
|
954
|
+
/** Reply to a thread */
|
|
955
|
+
replyTo: (rootCommentId: string, content: string, context?: ReplyContext) => Promise<string | null>;
|
|
956
|
+
/** Resolve a thread */
|
|
957
|
+
resolveThread: (rootCommentId: string) => Promise<void>;
|
|
958
|
+
/** Reopen a resolved thread */
|
|
959
|
+
reopenThread: (rootCommentId: string) => Promise<void>;
|
|
960
|
+
/** Delete a comment (soft delete) */
|
|
961
|
+
deleteComment: (commentId: string) => Promise<void>;
|
|
962
|
+
/** Delete an entire thread (root + all replies) */
|
|
963
|
+
deleteThread: (rootCommentId: string) => Promise<void>;
|
|
964
|
+
/** Edit a comment */
|
|
965
|
+
editComment: (commentId: string, content: string) => Promise<void>;
|
|
966
|
+
/** Reload comments */
|
|
967
|
+
reload: () => Promise<void>;
|
|
968
|
+
}
|
|
969
|
+
/**
|
|
970
|
+
* Universal hook for comments on any Node.
|
|
971
|
+
*/
|
|
972
|
+
declare function useComments({ nodeId, anchorType }: UseCommentsOptions): UseCommentsResult;
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Get the unresolved comment count for a Node.
|
|
976
|
+
*
|
|
977
|
+
* @param nodeId - The Node ID to get comment count for
|
|
978
|
+
* @returns The number of unresolved comment threads
|
|
979
|
+
*/
|
|
980
|
+
declare function useCommentCount(nodeId: string): number;
|
|
981
|
+
/**
|
|
982
|
+
* Get detailed comment counts for a Node.
|
|
983
|
+
*
|
|
984
|
+
* @param nodeId - The Node ID to get comment counts for
|
|
985
|
+
* @returns Object with total and unresolved counts
|
|
986
|
+
*/
|
|
987
|
+
declare function useCommentCounts(nodeId: string): {
|
|
988
|
+
total: number;
|
|
989
|
+
unresolved: number;
|
|
990
|
+
resolved: number;
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
/**
|
|
994
|
+
* useHistory - React hook for node history / time travel
|
|
995
|
+
*
|
|
996
|
+
* Provides point-in-time reconstruction, timeline browsing,
|
|
997
|
+
* and state materialization for a node.
|
|
998
|
+
*
|
|
999
|
+
* @example
|
|
1000
|
+
* ```tsx
|
|
1001
|
+
* const { timeline, materializeAt, diff, changeCount, loading } = useHistory(nodeId)
|
|
1002
|
+
*
|
|
1003
|
+
* // Get timeline
|
|
1004
|
+
* const entries = timeline
|
|
1005
|
+
*
|
|
1006
|
+
* // Materialize at a specific point
|
|
1007
|
+
* const historical = await materializeAt({ type: 'index', index: 5 })
|
|
1008
|
+
*
|
|
1009
|
+
* // Diff two points
|
|
1010
|
+
* const diffs = await diff({ type: 'index', index: 0 }, { type: 'latest' })
|
|
1011
|
+
* ```
|
|
1012
|
+
*/
|
|
1013
|
+
|
|
1014
|
+
interface UseHistoryResult {
|
|
1015
|
+
/** Full timeline of changes for the node */
|
|
1016
|
+
timeline: TimelineEntry[];
|
|
1017
|
+
/** Total number of changes */
|
|
1018
|
+
changeCount: number;
|
|
1019
|
+
/** Reconstruct node state at a target point */
|
|
1020
|
+
materializeAt: (target: HistoryTarget) => Promise<HistoricalState | null>;
|
|
1021
|
+
/** Diff between two points */
|
|
1022
|
+
diff: (from: HistoryTarget, to: HistoryTarget) => Promise<PropertyDiff[]>;
|
|
1023
|
+
/** Create a revert payload to restore to a historical point */
|
|
1024
|
+
createRevertPayload: (target: HistoryTarget) => Promise<Record<string, unknown> | null>;
|
|
1025
|
+
/** Whether the timeline is loading */
|
|
1026
|
+
loading: boolean;
|
|
1027
|
+
/** Any error */
|
|
1028
|
+
error: Error | null;
|
|
1029
|
+
/** Reload the timeline */
|
|
1030
|
+
reload: () => Promise<void>;
|
|
1031
|
+
}
|
|
1032
|
+
declare function useHistory(nodeId: NodeId | null): UseHistoryResult;
|
|
1033
|
+
|
|
1034
|
+
/**
|
|
1035
|
+
* useUndo - React hook for per-node undo/redo
|
|
1036
|
+
*
|
|
1037
|
+
* Wraps UndoManager to provide undo/redo actions, stack depth,
|
|
1038
|
+
* and canUndo/canRedo state for a given node.
|
|
1039
|
+
*
|
|
1040
|
+
* @example
|
|
1041
|
+
* ```tsx
|
|
1042
|
+
* const { undo, redo, canUndo, canRedo, undoCount, redoCount } = useUndo(nodeId)
|
|
1043
|
+
*
|
|
1044
|
+
* // Wire to keyboard shortcuts
|
|
1045
|
+
* <button onClick={undo} disabled={!canUndo}>Undo</button>
|
|
1046
|
+
* <button onClick={redo} disabled={!canRedo}>Redo</button>
|
|
1047
|
+
* ```
|
|
1048
|
+
*/
|
|
1049
|
+
|
|
1050
|
+
interface UseUndoOptions {
|
|
1051
|
+
/** The local user's DID (required for undo to work) */
|
|
1052
|
+
localDID: DID;
|
|
1053
|
+
/** UndoManager configuration overrides */
|
|
1054
|
+
options?: Partial<UndoManagerOptions>;
|
|
1055
|
+
}
|
|
1056
|
+
interface UseUndoResult {
|
|
1057
|
+
/** Undo the last change for this node */
|
|
1058
|
+
undo: () => Promise<boolean>;
|
|
1059
|
+
/** Redo the last undone change for this node */
|
|
1060
|
+
redo: () => Promise<boolean>;
|
|
1061
|
+
/** Undo all changes in a batch */
|
|
1062
|
+
undoBatch: (batchId: string) => Promise<boolean>;
|
|
1063
|
+
/** Whether undo is available */
|
|
1064
|
+
canUndo: boolean;
|
|
1065
|
+
/** Whether redo is available */
|
|
1066
|
+
canRedo: boolean;
|
|
1067
|
+
/** Number of undo entries */
|
|
1068
|
+
undoCount: number;
|
|
1069
|
+
/** Number of redo entries */
|
|
1070
|
+
redoCount: number;
|
|
1071
|
+
/** Clear undo/redo stacks for this node */
|
|
1072
|
+
clear: () => void;
|
|
1073
|
+
}
|
|
1074
|
+
declare function useUndo(nodeId: NodeId | null, opts: UseUndoOptions): UseUndoResult;
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* useAudit - React hook for querying the audit log
|
|
1078
|
+
*
|
|
1079
|
+
* Provides paginated, filterable access to change audit entries
|
|
1080
|
+
* and activity summaries for nodes.
|
|
1081
|
+
*
|
|
1082
|
+
* @example
|
|
1083
|
+
* ```tsx
|
|
1084
|
+
* const { entries, activity, loading } = useAudit(nodeId)
|
|
1085
|
+
* const { entries: filtered } = useAudit(nodeId, { operations: ['create', 'delete'] })
|
|
1086
|
+
* ```
|
|
1087
|
+
*/
|
|
1088
|
+
|
|
1089
|
+
interface UseAuditOptions {
|
|
1090
|
+
/** Filter by operations */
|
|
1091
|
+
operations?: ('create' | 'update' | 'delete' | 'restore')[];
|
|
1092
|
+
/** Filter by author DID */
|
|
1093
|
+
author?: string;
|
|
1094
|
+
/** Time range filter [from, to] in wall clock ms */
|
|
1095
|
+
timeRange?: [number, number];
|
|
1096
|
+
/** Max entries to return */
|
|
1097
|
+
limit?: number;
|
|
1098
|
+
/** Sort order */
|
|
1099
|
+
order?: 'asc' | 'desc';
|
|
1100
|
+
}
|
|
1101
|
+
interface UseAuditResult {
|
|
1102
|
+
/** Audit entries matching the query */
|
|
1103
|
+
entries: AuditEntry[];
|
|
1104
|
+
/** Activity summary for the node */
|
|
1105
|
+
activity: ActivitySummary | null;
|
|
1106
|
+
/** Whether loading */
|
|
1107
|
+
loading: boolean;
|
|
1108
|
+
/** Any error */
|
|
1109
|
+
error: Error | null;
|
|
1110
|
+
/** Reload audit data */
|
|
1111
|
+
reload: () => Promise<void>;
|
|
1112
|
+
}
|
|
1113
|
+
declare function useAudit(nodeId: NodeId | null, options?: UseAuditOptions): UseAuditResult;
|
|
1114
|
+
|
|
1115
|
+
/**
|
|
1116
|
+
* useDiff - React hook for comparing node state between two points
|
|
1117
|
+
*
|
|
1118
|
+
* Wraps DiffEngine to provide on-demand diffs between any two
|
|
1119
|
+
* HistoryTarget points.
|
|
1120
|
+
*
|
|
1121
|
+
* @example
|
|
1122
|
+
* ```tsx
|
|
1123
|
+
* const { diff, result, loading } = useDiff(nodeId)
|
|
1124
|
+
*
|
|
1125
|
+
* // Compare creation to latest
|
|
1126
|
+
* await diff({ type: 'index', index: 0 }, { type: 'latest' })
|
|
1127
|
+
*
|
|
1128
|
+
* // Show result
|
|
1129
|
+
* result?.diffs.forEach(d => console.log(d.property, d.type))
|
|
1130
|
+
* ```
|
|
1131
|
+
*/
|
|
1132
|
+
|
|
1133
|
+
interface UseDiffResult {
|
|
1134
|
+
/** Compute diff between two points */
|
|
1135
|
+
diff: (from: HistoryTarget, to: HistoryTarget) => Promise<void>;
|
|
1136
|
+
/** Diff N changes back from current */
|
|
1137
|
+
diffFromCurrent: (changesAgo: number) => Promise<void>;
|
|
1138
|
+
/** Latest diff result */
|
|
1139
|
+
result: DiffResult | null;
|
|
1140
|
+
/** Whether diffing */
|
|
1141
|
+
loading: boolean;
|
|
1142
|
+
/** Any error */
|
|
1143
|
+
error: Error | null;
|
|
1144
|
+
}
|
|
1145
|
+
declare function useDiff(nodeId: NodeId | null): UseDiffResult;
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* useBlame - React hook for per-property attribution
|
|
1149
|
+
*
|
|
1150
|
+
* Shows who last changed each property, how many times,
|
|
1151
|
+
* and the full edit history.
|
|
1152
|
+
*
|
|
1153
|
+
* @example
|
|
1154
|
+
* ```tsx
|
|
1155
|
+
* const { blame, loading, reload } = useBlame(nodeId)
|
|
1156
|
+
*
|
|
1157
|
+
* blame.forEach(b => {
|
|
1158
|
+
* console.log(`${b.property}: last changed by ${b.lastChangedBy}, ${b.totalEdits} edits`)
|
|
1159
|
+
* })
|
|
1160
|
+
* ```
|
|
1161
|
+
*/
|
|
1162
|
+
|
|
1163
|
+
interface UseBlameResult {
|
|
1164
|
+
/** Blame info for all properties */
|
|
1165
|
+
blame: BlameInfo[];
|
|
1166
|
+
/** Whether loading */
|
|
1167
|
+
loading: boolean;
|
|
1168
|
+
/** Any error */
|
|
1169
|
+
error: Error | null;
|
|
1170
|
+
/** Reload blame data */
|
|
1171
|
+
reload: () => Promise<void>;
|
|
1172
|
+
}
|
|
1173
|
+
declare function useBlame(nodeId: NodeId | null): UseBlameResult;
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* useVerification - React hook for cryptographic chain verification
|
|
1177
|
+
*
|
|
1178
|
+
* Runs verification on a node's change history and reports
|
|
1179
|
+
* validity, errors, and statistics.
|
|
1180
|
+
*
|
|
1181
|
+
* @example
|
|
1182
|
+
* ```tsx
|
|
1183
|
+
* const { verify, result, loading } = useVerification(nodeId)
|
|
1184
|
+
*
|
|
1185
|
+
* await verify()
|
|
1186
|
+
* if (result?.valid) {
|
|
1187
|
+
* console.log('Chain is valid!', result.stats)
|
|
1188
|
+
* } else {
|
|
1189
|
+
* console.log('Errors:', result?.errors)
|
|
1190
|
+
* }
|
|
1191
|
+
* ```
|
|
1192
|
+
*/
|
|
1193
|
+
|
|
1194
|
+
interface UseVerificationResult {
|
|
1195
|
+
/** Run full verification */
|
|
1196
|
+
verify: (options?: VerificationOptions) => Promise<void>;
|
|
1197
|
+
/** Run quick check (hash + chain only) */
|
|
1198
|
+
quickCheck: () => Promise<{
|
|
1199
|
+
valid: boolean;
|
|
1200
|
+
errors: number;
|
|
1201
|
+
} | null>;
|
|
1202
|
+
/** Latest verification result */
|
|
1203
|
+
result: VerificationResult | null;
|
|
1204
|
+
/** Whether verification is running */
|
|
1205
|
+
loading: boolean;
|
|
1206
|
+
/** Verification progress (0-1) */
|
|
1207
|
+
progress: number;
|
|
1208
|
+
/** Any error */
|
|
1209
|
+
error: Error | null;
|
|
1210
|
+
}
|
|
1211
|
+
declare function useVerification(nodeId: NodeId | null): UseVerificationResult;
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Connection Manager - Multiplexed WebSocket connection for all tracked Nodes
|
|
1215
|
+
*
|
|
1216
|
+
* Instead of one WebSocket per Node (current WebSocketSyncProvider approach),
|
|
1217
|
+
* the Connection Manager maintains a single WebSocket subscribed to multiple
|
|
1218
|
+
* rooms. This reduces connection count from O(N) to O(1).
|
|
1219
|
+
*
|
|
1220
|
+
* The signaling protocol supports multi-room subscriptions:
|
|
1221
|
+
* { type: "subscribe", topics: ["xnet-doc-abc", "xnet-doc-def"] }
|
|
1222
|
+
*/
|
|
1223
|
+
type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error';
|
|
1224
|
+
interface ConnectionManagerConfig {
|
|
1225
|
+
/** Signaling/hub WebSocket URL */
|
|
1226
|
+
url: string;
|
|
1227
|
+
/** Reconnect delay in ms (default: 2000) */
|
|
1228
|
+
reconnectDelay?: number;
|
|
1229
|
+
/** Max reconnect attempts (default: Infinity) */
|
|
1230
|
+
maxReconnects?: number;
|
|
1231
|
+
/** UCAN token for hub auth */
|
|
1232
|
+
ucanToken?: string;
|
|
1233
|
+
/** Async UCAN token provider (preferred for rotation) */
|
|
1234
|
+
getUCANToken?: () => Promise<string>;
|
|
1235
|
+
}
|
|
1236
|
+
type RoomHandler = (data: Record<string, unknown>) => void;
|
|
1237
|
+
type StatusHandler = (status: ConnectionStatus) => void;
|
|
1238
|
+
interface RoomJoinResult {
|
|
1239
|
+
/** Unsubscribe function */
|
|
1240
|
+
unsubscribe: () => void;
|
|
1241
|
+
/** Promise that resolves when server confirms subscription */
|
|
1242
|
+
ready: Promise<void>;
|
|
1243
|
+
}
|
|
1244
|
+
interface ConnectionManager {
|
|
1245
|
+
/** Current connection status */
|
|
1246
|
+
readonly status: ConnectionStatus;
|
|
1247
|
+
/** Connect to the signaling server */
|
|
1248
|
+
connect(): void;
|
|
1249
|
+
/** Disconnect and cleanup */
|
|
1250
|
+
disconnect(): void;
|
|
1251
|
+
/** Subscribe to a room (returns unsubscribe function) */
|
|
1252
|
+
joinRoom(room: string, handler: RoomHandler): () => void;
|
|
1253
|
+
/** Subscribe to a room with confirmation (returns cleanup and ready promise) */
|
|
1254
|
+
joinRoomAsync(room: string, handler: RoomHandler): RoomJoinResult;
|
|
1255
|
+
/** Leave a room */
|
|
1256
|
+
leaveRoom(room: string): void;
|
|
1257
|
+
/** Publish a message to a room */
|
|
1258
|
+
publish(room: string, data: object): void;
|
|
1259
|
+
/** Send a raw message on the WebSocket */
|
|
1260
|
+
sendRaw(message: object): void;
|
|
1261
|
+
/** Listen for non-room messages */
|
|
1262
|
+
onMessage(handler: (message: Record<string, unknown>) => void): () => void;
|
|
1263
|
+
/** Listen for status changes */
|
|
1264
|
+
onStatus(handler: StatusHandler): () => void;
|
|
1265
|
+
/** Number of active room subscriptions */
|
|
1266
|
+
readonly roomCount: number;
|
|
1267
|
+
}
|
|
1268
|
+
declare function createConnectionManager(config: ConnectionManagerConfig): ConnectionManager;
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* Blob Sync Provider - Handles blob synchronization between peers.
|
|
1272
|
+
*
|
|
1273
|
+
* Integrates with the Background Sync Manager's ConnectionManager to send
|
|
1274
|
+
* blob have/want/data messages over the same multiplexed WebSocket connection
|
|
1275
|
+
* used for Y.Doc sync. Uses a dedicated room for blob sync messages.
|
|
1276
|
+
*
|
|
1277
|
+
* Protocol:
|
|
1278
|
+
* blob-have: Announce available CIDs to peers
|
|
1279
|
+
* blob-want: Request missing blobs by CID
|
|
1280
|
+
* blob-data: Transfer blob bytes (base64 encoded)
|
|
1281
|
+
* blob-not-found: Signal unavailability
|
|
1282
|
+
*/
|
|
1283
|
+
|
|
1284
|
+
/** Minimal blob store interface for sync (satisfied by BlobStore from @xnetjs/storage) */
|
|
1285
|
+
interface BlobStoreForSync {
|
|
1286
|
+
get(cid: ContentId): Promise<Uint8Array | null>;
|
|
1287
|
+
put(data: Uint8Array): Promise<ContentId>;
|
|
1288
|
+
has(cid: ContentId): Promise<boolean>;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* Sync Manager - Top-level orchestrator for Background Sync
|
|
1293
|
+
*
|
|
1294
|
+
* Wires together Node Pool, Registry, and Connection Manager into a cohesive
|
|
1295
|
+
* sync service. Handles the Yjs sync protocol (state vectors, diffs, incremental
|
|
1296
|
+
* updates) for each tracked Node.
|
|
1297
|
+
*
|
|
1298
|
+
* Components acquire Y.Docs via the SyncManager (through useNode). When released,
|
|
1299
|
+
* docs stay alive in the pool and continue syncing in the background.
|
|
1300
|
+
*/
|
|
1301
|
+
|
|
1302
|
+
type SyncStatus = ConnectionStatus;
|
|
1303
|
+
interface SyncManagerConfig {
|
|
1304
|
+
/** NodeStore for meta bridge */
|
|
1305
|
+
nodeStore: NodeStore;
|
|
1306
|
+
/** Storage adapter for pool persistence (Y.Doc content) */
|
|
1307
|
+
storage: NodeStorageAdapter;
|
|
1308
|
+
/** Signaling/hub WebSocket URL */
|
|
1309
|
+
signalingUrl: string;
|
|
1310
|
+
/** Max Y.Docs in memory (default: 50) */
|
|
1311
|
+
poolSize?: number;
|
|
1312
|
+
/** TTL for tracked Nodes in ms (default: 7 days) */
|
|
1313
|
+
trackTTL?: number;
|
|
1314
|
+
/** Author DID for awareness */
|
|
1315
|
+
authorDID?: string;
|
|
1316
|
+
/** Blob store for P2P blob sync (optional — if omitted, blob sync is disabled) */
|
|
1317
|
+
blobStore?: BlobStoreForSync;
|
|
1318
|
+
/** Optional UCAN token for hub auth */
|
|
1319
|
+
ucanToken?: string;
|
|
1320
|
+
/** Optional UCAN token provider for hub auth */
|
|
1321
|
+
getUCANToken?: () => Promise<string>;
|
|
1322
|
+
/** Optional pool update callback */
|
|
1323
|
+
onDocUpdate?: (nodeId: string, doc: Y.Doc) => void;
|
|
1324
|
+
/** Optional pool eviction callback */
|
|
1325
|
+
onDocEvict?: (nodeId: string, doc: Y.Doc) => void;
|
|
1326
|
+
/** Optional room for node-change relay (enables NodeStore sync via hub) */
|
|
1327
|
+
nodeSyncRoom?: string;
|
|
1328
|
+
}
|
|
1329
|
+
interface SyncManager {
|
|
1330
|
+
/** Start the sync manager (connect, load registry, sync tracked Nodes) */
|
|
1331
|
+
start(): Promise<void>;
|
|
1332
|
+
/** Stop (disconnect, flush, save registry) */
|
|
1333
|
+
stop(): Promise<void>;
|
|
1334
|
+
/** Track a Node for background sync */
|
|
1335
|
+
track(nodeId: string, schemaId: string): void;
|
|
1336
|
+
/** Stop tracking a Node */
|
|
1337
|
+
untrack(nodeId: string): void;
|
|
1338
|
+
/** Acquire a Y.Doc (used by useNode) */
|
|
1339
|
+
acquire(nodeId: string): Promise<Y.Doc>;
|
|
1340
|
+
/** Release a Y.Doc (component unmounted) */
|
|
1341
|
+
release(nodeId: string): void;
|
|
1342
|
+
/** Get awareness for a Node (for cursor presence) */
|
|
1343
|
+
getAwareness(nodeId: string): Awareness | null;
|
|
1344
|
+
/** Listen for awareness snapshots from the hub */
|
|
1345
|
+
onAwarenessSnapshot(nodeId: string, handler: (users: AwarenessSnapshotUser[]) => void): () => void;
|
|
1346
|
+
/** Request blobs from peers by CID (no-op if blob sync is disabled) */
|
|
1347
|
+
requestBlobs(cids: string[]): Promise<void>;
|
|
1348
|
+
/** Announce blob CIDs to peers (no-op if blob sync is disabled) */
|
|
1349
|
+
announceBlobs(cids: string[]): void;
|
|
1350
|
+
/** Connection status */
|
|
1351
|
+
readonly status: SyncStatus;
|
|
1352
|
+
/** Pool stats */
|
|
1353
|
+
readonly poolSize: number;
|
|
1354
|
+
/** Tracked count */
|
|
1355
|
+
readonly trackedCount: number;
|
|
1356
|
+
/** Offline queue size */
|
|
1357
|
+
readonly queueSize: number;
|
|
1358
|
+
/** Pending blob requests */
|
|
1359
|
+
readonly pendingBlobCount: number;
|
|
1360
|
+
/** Listen for events */
|
|
1361
|
+
on(event: 'status', handler: (status: SyncStatus) => void): () => void;
|
|
1362
|
+
/** Underlying ConnectionManager (if available) */
|
|
1363
|
+
readonly connection?: ConnectionManager;
|
|
1364
|
+
}
|
|
1365
|
+
type AwarenessSnapshotUser = {
|
|
1366
|
+
did: string;
|
|
1367
|
+
state: {
|
|
1368
|
+
user?: {
|
|
1369
|
+
name?: string;
|
|
1370
|
+
color?: string;
|
|
1371
|
+
avatar?: string;
|
|
1372
|
+
did?: string;
|
|
1373
|
+
};
|
|
1374
|
+
cursor?: {
|
|
1375
|
+
anchor: number;
|
|
1376
|
+
head: number;
|
|
1377
|
+
};
|
|
1378
|
+
selection?: unknown;
|
|
1379
|
+
online?: boolean;
|
|
1380
|
+
[key: string]: unknown;
|
|
1381
|
+
};
|
|
1382
|
+
lastSeen: number;
|
|
1383
|
+
isStale: boolean;
|
|
1384
|
+
};
|
|
1385
|
+
declare function createSyncManager(config: SyncManagerConfig): SyncManager;
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* useHubStatus - Access hub connection status from context.
|
|
1389
|
+
*/
|
|
1390
|
+
|
|
1391
|
+
declare function useHubStatus(): SyncStatus;
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* useCan - Check current user's permissions for a node.
|
|
1395
|
+
*/
|
|
1396
|
+
interface UseCanResult {
|
|
1397
|
+
canRead: boolean;
|
|
1398
|
+
canWrite: boolean;
|
|
1399
|
+
canDelete: boolean;
|
|
1400
|
+
canShare: boolean;
|
|
1401
|
+
loading: boolean;
|
|
1402
|
+
error: Error | null;
|
|
1403
|
+
isFresh: boolean;
|
|
1404
|
+
evaluatedAt: number;
|
|
1405
|
+
}
|
|
1406
|
+
declare function useCan(nodeId: string): UseCanResult;
|
|
1407
|
+
|
|
1408
|
+
/**
|
|
1409
|
+
* useCanEdit - Resolve editor/viewer capabilities for a node.
|
|
1410
|
+
*/
|
|
1411
|
+
type UseCanEditResult = {
|
|
1412
|
+
canEdit: boolean;
|
|
1413
|
+
canView: boolean;
|
|
1414
|
+
loading: boolean;
|
|
1415
|
+
error: Error | null;
|
|
1416
|
+
roles: string[];
|
|
1417
|
+
};
|
|
1418
|
+
declare function useCanEdit(nodeId: string): UseCanEditResult;
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* useGrants - Read and mutate grants for a node.
|
|
1422
|
+
*/
|
|
1423
|
+
|
|
1424
|
+
interface GrantInput {
|
|
1425
|
+
to: DID;
|
|
1426
|
+
actions: AuthAction[];
|
|
1427
|
+
resource?: string;
|
|
1428
|
+
expiresIn?: string | number;
|
|
1429
|
+
parentGrantId?: string;
|
|
1430
|
+
}
|
|
1431
|
+
interface UseGrantsResult {
|
|
1432
|
+
grants: AuthGrant[];
|
|
1433
|
+
loading: boolean;
|
|
1434
|
+
error: Error | null;
|
|
1435
|
+
grant: (input: GrantInput) => Promise<AuthGrant>;
|
|
1436
|
+
revoke: (grantId: string) => Promise<void>;
|
|
1437
|
+
}
|
|
1438
|
+
declare function useGrants(nodeId: string): UseGrantsResult;
|
|
1439
|
+
|
|
1440
|
+
interface UseBackupReturn {
|
|
1441
|
+
upload: (docId: string, plaintext: Uint8Array) => Promise<void>;
|
|
1442
|
+
download: (docId: string) => Promise<Uint8Array | null>;
|
|
1443
|
+
uploading: boolean;
|
|
1444
|
+
}
|
|
1445
|
+
declare function useBackup(): UseBackupReturn;
|
|
1446
|
+
|
|
1447
|
+
interface FileRef {
|
|
1448
|
+
cid: string;
|
|
1449
|
+
name: string;
|
|
1450
|
+
mimeType: string;
|
|
1451
|
+
size: number;
|
|
1452
|
+
}
|
|
1453
|
+
interface UseFileUploadReturn {
|
|
1454
|
+
upload: (file: File) => Promise<FileRef>;
|
|
1455
|
+
uploading: boolean;
|
|
1456
|
+
progress: number;
|
|
1457
|
+
}
|
|
1458
|
+
declare function useFileUpload(): UseFileUploadReturn;
|
|
1459
|
+
|
|
1460
|
+
interface HubSearchOptions {
|
|
1461
|
+
schemaIri?: string;
|
|
1462
|
+
limit?: number;
|
|
1463
|
+
offset?: number;
|
|
1464
|
+
}
|
|
1465
|
+
interface HubSearchResult {
|
|
1466
|
+
docId: string;
|
|
1467
|
+
title: string;
|
|
1468
|
+
schemaIri: string;
|
|
1469
|
+
snippet: string;
|
|
1470
|
+
rank: number;
|
|
1471
|
+
}
|
|
1472
|
+
interface HubSearchState {
|
|
1473
|
+
search: (query: string, options?: HubSearchOptions) => Promise<HubSearchResult[]>;
|
|
1474
|
+
results: HubSearchResult[];
|
|
1475
|
+
loading: boolean;
|
|
1476
|
+
error: Error | null;
|
|
1477
|
+
}
|
|
1478
|
+
declare function useHubSearch(): HubSearchState;
|
|
1479
|
+
|
|
1480
|
+
/**
|
|
1481
|
+
* useRemoteSchema - Hub schema registry fetch hook.
|
|
1482
|
+
*/
|
|
1483
|
+
interface RemoteSchemaDefinition {
|
|
1484
|
+
iri: string;
|
|
1485
|
+
version: number;
|
|
1486
|
+
name: string;
|
|
1487
|
+
description: string;
|
|
1488
|
+
definition: Record<string, unknown>;
|
|
1489
|
+
authorDid: string;
|
|
1490
|
+
propertiesCount: number;
|
|
1491
|
+
createdAt: number;
|
|
1492
|
+
}
|
|
1493
|
+
interface RemoteSchemaState {
|
|
1494
|
+
schema: RemoteSchemaDefinition | null;
|
|
1495
|
+
loading: boolean;
|
|
1496
|
+
error: Error | null;
|
|
1497
|
+
}
|
|
1498
|
+
declare const useRemoteSchema: (iri: string | undefined) => RemoteSchemaState;
|
|
1499
|
+
|
|
1500
|
+
/**
|
|
1501
|
+
* usePeerDiscovery - hub peer discovery hook.
|
|
1502
|
+
*/
|
|
1503
|
+
interface DiscoveredPeer {
|
|
1504
|
+
did: string;
|
|
1505
|
+
displayName?: string;
|
|
1506
|
+
endpoints: Array<{
|
|
1507
|
+
type: string;
|
|
1508
|
+
address: string;
|
|
1509
|
+
priority?: number;
|
|
1510
|
+
}>;
|
|
1511
|
+
lastSeen: number;
|
|
1512
|
+
isOnline: boolean;
|
|
1513
|
+
}
|
|
1514
|
+
declare const usePeerDiscovery: () => {
|
|
1515
|
+
peers: DiscoveredPeer[];
|
|
1516
|
+
resolve: (did: string) => Promise<DiscoveredPeer | null>;
|
|
1517
|
+
refresh: () => Promise<void>;
|
|
1518
|
+
loading: boolean;
|
|
1519
|
+
};
|
|
1520
|
+
|
|
1521
|
+
declare function HubStatusIndicator(): JSX.Element;
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
* Global error boundary for xNet React apps.
|
|
1525
|
+
*
|
|
1526
|
+
* Catches unhandled React render errors and displays a recovery UI
|
|
1527
|
+
* instead of crashing the entire app tree.
|
|
1528
|
+
*/
|
|
1529
|
+
|
|
1530
|
+
type ErrorBoundaryFallbackProps = {
|
|
1531
|
+
error: Error;
|
|
1532
|
+
reset: () => void;
|
|
1533
|
+
};
|
|
1534
|
+
type ErrorBoundaryProps = {
|
|
1535
|
+
children: ReactNode;
|
|
1536
|
+
/** Custom fallback UI. Receives error + reset function. If omitted, a default error screen is shown. */
|
|
1537
|
+
fallback?: ReactNode | ((props: ErrorBoundaryFallbackProps) => ReactNode);
|
|
1538
|
+
/** Callback fired when an error is caught. */
|
|
1539
|
+
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
1540
|
+
/** Change this value to force a reset (e.g. after navigation). */
|
|
1541
|
+
resetKey?: string | number;
|
|
1542
|
+
};
|
|
1543
|
+
type ErrorBoundaryState = {
|
|
1544
|
+
hasError: boolean;
|
|
1545
|
+
error: Error | null;
|
|
1546
|
+
};
|
|
1547
|
+
declare class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
1548
|
+
state: ErrorBoundaryState;
|
|
1549
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
|
|
1550
|
+
componentDidCatch(error: Error, errorInfo: ErrorInfo): void;
|
|
1551
|
+
private handleReset;
|
|
1552
|
+
componentDidUpdate(prevProps: ErrorBoundaryProps): void;
|
|
1553
|
+
render(): ReactNode;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/**
|
|
1557
|
+
* Skeleton loading placeholder.
|
|
1558
|
+
*
|
|
1559
|
+
* Shows a pulsing placeholder while content is loading. Supports
|
|
1560
|
+
* different shapes (text lines, circles, rectangles).
|
|
1561
|
+
*/
|
|
1562
|
+
type SkeletonProps = {
|
|
1563
|
+
/** Width (CSS value). Default: '100%'. */
|
|
1564
|
+
width?: string | number;
|
|
1565
|
+
/** Height (CSS value). Default: '1em'. */
|
|
1566
|
+
height?: string | number;
|
|
1567
|
+
/** Border radius. Use '50%' for circles. Default: '4px'. */
|
|
1568
|
+
borderRadius?: string | number;
|
|
1569
|
+
/** Additional inline styles. */
|
|
1570
|
+
style?: React.CSSProperties;
|
|
1571
|
+
/** Number of lines to render. Default: 1. */
|
|
1572
|
+
lines?: number;
|
|
1573
|
+
/** Gap between lines. Default: '0.5rem'. */
|
|
1574
|
+
gap?: string;
|
|
1575
|
+
};
|
|
1576
|
+
declare function Skeleton({ width, height, borderRadius, style, lines, gap }: SkeletonProps): JSX.Element;
|
|
1577
|
+
/**
|
|
1578
|
+
* Inject the skeleton keyframes into the document head.
|
|
1579
|
+
* Call once at app startup, or include the CSS in your stylesheet:
|
|
1580
|
+
*
|
|
1581
|
+
* ```css
|
|
1582
|
+
* @keyframes xnet-skeleton-pulse {
|
|
1583
|
+
* 0% { background-position: 200% 0; }
|
|
1584
|
+
* 100% { background-position: -200% 0; }
|
|
1585
|
+
* }
|
|
1586
|
+
* ```
|
|
1587
|
+
*/
|
|
1588
|
+
declare function injectSkeletonStyles(): void;
|
|
1589
|
+
|
|
1590
|
+
interface DemoBannerProps {
|
|
1591
|
+
/** How many hours until demo data expires */
|
|
1592
|
+
evictionHours: number;
|
|
1593
|
+
/** Called when banner is dismissed */
|
|
1594
|
+
onDismiss?: () => void;
|
|
1595
|
+
}
|
|
1596
|
+
/**
|
|
1597
|
+
* DemoBanner component
|
|
1598
|
+
*
|
|
1599
|
+
* @example
|
|
1600
|
+
* ```tsx
|
|
1601
|
+
* <DemoBanner evictionHours={24} />
|
|
1602
|
+
* ```
|
|
1603
|
+
*/
|
|
1604
|
+
declare function DemoBanner({ evictionHours, onDismiss }: DemoBannerProps): react_jsx_runtime.JSX.Element | null;
|
|
1605
|
+
|
|
1606
|
+
/**
|
|
1607
|
+
* DemoQuotaIndicator - Shows storage quota usage for demo mode
|
|
1608
|
+
*
|
|
1609
|
+
* Displays a progress bar with bytes used / limit.
|
|
1610
|
+
* Colors change at warning (80%) and critical (95%) thresholds.
|
|
1611
|
+
*/
|
|
1612
|
+
interface DemoQuotaIndicatorProps {
|
|
1613
|
+
/** Bytes currently used */
|
|
1614
|
+
usedBytes: number;
|
|
1615
|
+
/** Total quota limit in bytes */
|
|
1616
|
+
limitBytes: number;
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* DemoQuotaIndicator component
|
|
1620
|
+
*
|
|
1621
|
+
* @example
|
|
1622
|
+
* ```tsx
|
|
1623
|
+
* <DemoQuotaIndicator usedBytes={5242880} limitBytes={10485760} />
|
|
1624
|
+
* ```
|
|
1625
|
+
*/
|
|
1626
|
+
declare function DemoQuotaIndicator({ usedBytes, limitBytes }: DemoQuotaIndicatorProps): react_jsx_runtime.JSX.Element;
|
|
1627
|
+
|
|
1628
|
+
/**
|
|
1629
|
+
* DemoDataExpiredScreen - Full-screen message when demo data is evicted
|
|
1630
|
+
*
|
|
1631
|
+
* Shown when the user's demo data has been cleaned up after inactivity.
|
|
1632
|
+
* Provides options to start fresh or download the desktop app.
|
|
1633
|
+
*/
|
|
1634
|
+
/**
|
|
1635
|
+
* DemoDataExpiredScreen component
|
|
1636
|
+
*
|
|
1637
|
+
* @example
|
|
1638
|
+
* ```tsx
|
|
1639
|
+
* if (dataExpired) {
|
|
1640
|
+
* return <DemoDataExpiredScreen />
|
|
1641
|
+
* }
|
|
1642
|
+
* ```
|
|
1643
|
+
*/
|
|
1644
|
+
declare function DemoDataExpiredScreen(): react_jsx_runtime.JSX.Element;
|
|
1645
|
+
|
|
1646
|
+
/**
|
|
1647
|
+
* Demo mode limits (from hub handshake)
|
|
1648
|
+
*/
|
|
1649
|
+
interface DemoLimits {
|
|
1650
|
+
/** Storage quota in bytes */
|
|
1651
|
+
quotaBytes: number;
|
|
1652
|
+
/** Maximum number of documents */
|
|
1653
|
+
maxDocs: number;
|
|
1654
|
+
/** Eviction time in hours */
|
|
1655
|
+
evictionHours: number;
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Demo mode usage stats (future: populated from hub)
|
|
1659
|
+
*/
|
|
1660
|
+
interface DemoUsage {
|
|
1661
|
+
/** Bytes currently used */
|
|
1662
|
+
usedBytes: number;
|
|
1663
|
+
/** Number of documents */
|
|
1664
|
+
docCount: number;
|
|
1665
|
+
}
|
|
1666
|
+
/**
|
|
1667
|
+
* Demo mode state
|
|
1668
|
+
*/
|
|
1669
|
+
interface DemoModeState {
|
|
1670
|
+
/** Whether connected to a demo hub */
|
|
1671
|
+
isDemo: boolean;
|
|
1672
|
+
/** Demo mode limits (if in demo mode) */
|
|
1673
|
+
limits?: DemoLimits;
|
|
1674
|
+
/** Current usage (if in demo mode and available) */
|
|
1675
|
+
usage?: DemoUsage;
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Hook to detect and track demo mode from hub connection
|
|
1679
|
+
*
|
|
1680
|
+
* Listens for the handshake message from the hub which includes `isDemo`
|
|
1681
|
+
* and `demoLimits` fields when connected to a demo hub.
|
|
1682
|
+
*
|
|
1683
|
+
* @example
|
|
1684
|
+
* ```tsx
|
|
1685
|
+
* function App() {
|
|
1686
|
+
* const { isDemo, limits } = useDemoMode()
|
|
1687
|
+
*
|
|
1688
|
+
* return (
|
|
1689
|
+
* <div>
|
|
1690
|
+
* {isDemo && limits && (
|
|
1691
|
+
* <DemoBanner evictionHours={limits.evictionHours} />
|
|
1692
|
+
* )}
|
|
1693
|
+
* <MainContent />
|
|
1694
|
+
* </div>
|
|
1695
|
+
* )
|
|
1696
|
+
* }
|
|
1697
|
+
* ```
|
|
1698
|
+
*/
|
|
1699
|
+
declare function useDemoMode(): DemoModeState;
|
|
1700
|
+
|
|
1701
|
+
type OfflineIndicatorProps = {
|
|
1702
|
+
/** Custom message. Default: 'You are offline. Changes will sync when reconnected.' */
|
|
1703
|
+
message?: string;
|
|
1704
|
+
/** Additional CSS class. */
|
|
1705
|
+
className?: string;
|
|
1706
|
+
/** Position. Default: 'bottom'. */
|
|
1707
|
+
position?: 'top' | 'bottom';
|
|
1708
|
+
};
|
|
1709
|
+
/** Returns true when the browser is offline. */
|
|
1710
|
+
declare function useIsOffline(): boolean;
|
|
1711
|
+
declare function OfflineIndicator({ message, className, position }: OfflineIndicatorProps): JSX.Element | null;
|
|
1712
|
+
|
|
1713
|
+
/**
|
|
1714
|
+
* WebSocketSyncProvider - Syncs Y.Doc via WebSocket relay
|
|
1715
|
+
*
|
|
1716
|
+
* Unlike y-webrtc (which uses WebRTC DataChannels for P2P), this provider
|
|
1717
|
+
* relays all Yjs updates through the signaling server. This works in all
|
|
1718
|
+
* environments (same-machine, different networks, behind NATs) at the cost
|
|
1719
|
+
* of server-relayed traffic.
|
|
1720
|
+
*
|
|
1721
|
+
* Protocol (extends the y-webrtc signaling protocol):
|
|
1722
|
+
* - Uses the same subscribe/publish mechanism as y-webrtc signaling
|
|
1723
|
+
* - Sync messages are published as JSON with base64-encoded binary data
|
|
1724
|
+
* - Message types: 'sync-step1', 'sync-step2', 'sync-update', 'awareness'
|
|
1725
|
+
*
|
|
1726
|
+
* Sync flow:
|
|
1727
|
+
* 1. On connect: subscribe to room, broadcast sync-step1 (state vector)
|
|
1728
|
+
* 2. On receiving sync-step1: respond with sync-step2 (diff for their vector)
|
|
1729
|
+
* 3. On local update: broadcast sync-update to room
|
|
1730
|
+
* 4. On receiving sync-update: apply to local doc
|
|
1731
|
+
*
|
|
1732
|
+
* Awareness flow:
|
|
1733
|
+
* 1. On connect: broadcast local awareness state
|
|
1734
|
+
* 2. On awareness change: broadcast updated state
|
|
1735
|
+
* 3. On receiving awareness: apply to local awareness instance
|
|
1736
|
+
* 4. On disconnect: peers remove the disconnected client's state
|
|
1737
|
+
*/
|
|
1738
|
+
|
|
1739
|
+
interface WebSocketSyncProviderOptions {
|
|
1740
|
+
/** WebSocket URL of the signaling/relay server */
|
|
1741
|
+
url: string;
|
|
1742
|
+
/** Room name (peers in the same room sync together) */
|
|
1743
|
+
room: string;
|
|
1744
|
+
/** Reconnect delay in ms (default: 2000) */
|
|
1745
|
+
reconnectDelay?: number;
|
|
1746
|
+
/** Maximum reconnect attempts (default: Infinity) */
|
|
1747
|
+
maxReconnectAttempts?: number;
|
|
1748
|
+
}
|
|
1749
|
+
type SyncEventType = 'status' | 'synced' | 'peers' | 'awareness-snapshot';
|
|
1750
|
+
type SyncEventHandler = (event: unknown) => void;
|
|
1751
|
+
declare class WebSocketSyncProvider {
|
|
1752
|
+
readonly doc: Y.Doc;
|
|
1753
|
+
readonly room: string;
|
|
1754
|
+
readonly url: string;
|
|
1755
|
+
readonly awareness: Awareness;
|
|
1756
|
+
private ws;
|
|
1757
|
+
private reconnectDelay;
|
|
1758
|
+
private maxReconnectAttempts;
|
|
1759
|
+
private reconnectAttempts;
|
|
1760
|
+
private reconnectTimer;
|
|
1761
|
+
private destroyed;
|
|
1762
|
+
private connected;
|
|
1763
|
+
private synced;
|
|
1764
|
+
private peerId;
|
|
1765
|
+
private remotePeerIds;
|
|
1766
|
+
private eventHandlers;
|
|
1767
|
+
constructor(doc: Y.Doc, options: WebSocketSyncProviderOptions);
|
|
1768
|
+
get isConnected(): boolean;
|
|
1769
|
+
get isSynced(): boolean;
|
|
1770
|
+
/** Helper to get XML fragment length for debug logging */
|
|
1771
|
+
private _getFragmentLength;
|
|
1772
|
+
on(event: SyncEventType, handler: SyncEventHandler): void;
|
|
1773
|
+
off(event: SyncEventType, handler: SyncEventHandler): void;
|
|
1774
|
+
private emit;
|
|
1775
|
+
destroy(): void;
|
|
1776
|
+
private _connect;
|
|
1777
|
+
private _scheduleReconnect;
|
|
1778
|
+
private _send;
|
|
1779
|
+
private _publish;
|
|
1780
|
+
/** Handle incoming sync messages from other peers */
|
|
1781
|
+
private _handleSyncMessage;
|
|
1782
|
+
/** Broadcast local doc updates to peers */
|
|
1783
|
+
private _onDocUpdate;
|
|
1784
|
+
/** Broadcast local awareness changes to peers */
|
|
1785
|
+
private _onAwarenessUpdate;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
/**
|
|
1789
|
+
* useSyncManager - Access the Background Sync Manager from context
|
|
1790
|
+
*
|
|
1791
|
+
* Returns null if SyncManager is not available (disabled, not initialized,
|
|
1792
|
+
* or when running outside XNetProvider).
|
|
1793
|
+
*/
|
|
1794
|
+
|
|
1795
|
+
declare function useSyncManager(): SyncManager | null;
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* Meta Bridge - Unidirectional sync from NodeStore → Y.Doc meta map
|
|
1799
|
+
*
|
|
1800
|
+
* SECURITY: This bridge is intentionally ONE-WAY. The Y.Doc meta map is a
|
|
1801
|
+
* read-only cache for the editor UI. Property changes MUST go through the
|
|
1802
|
+
* signed NodeChange pipeline (via mutate()), never through Yjs.
|
|
1803
|
+
*
|
|
1804
|
+
* Before (VULNERABLE):
|
|
1805
|
+
* NodeStore ↔ MetaBridge ↔ Y.Doc meta
|
|
1806
|
+
* (Malicious Yjs updates could poison NodeStore!)
|
|
1807
|
+
*
|
|
1808
|
+
* After (SECURE):
|
|
1809
|
+
* NodeStore → MetaBridge → Y.Doc meta (write)
|
|
1810
|
+
* Y.Doc meta → Editor UI (read-only display)
|
|
1811
|
+
* Editor UI → mutate() → NodeStore (signed writes)
|
|
1812
|
+
*
|
|
1813
|
+
* See: docs/plans/plan03_4_1YjsSecurity/04-metabridge-isolation.md
|
|
1814
|
+
*/
|
|
1815
|
+
|
|
1816
|
+
/** Transaction origin for MetaBridge writes (for debugging/monitoring) */
|
|
1817
|
+
declare const METABRIDGE_ORIGIN = "metabridge";
|
|
1818
|
+
declare const METABRIDGE_SEED_ORIGIN = "metabridge-seed";
|
|
1819
|
+
interface MetaBridge {
|
|
1820
|
+
/**
|
|
1821
|
+
* Start observing NodeStore changes for a Node and syncing to Y.Doc meta.
|
|
1822
|
+
* Direction: NodeStore → Y.Doc meta map (ONE-WAY)
|
|
1823
|
+
*
|
|
1824
|
+
* @returns Unsubscribe function
|
|
1825
|
+
*/
|
|
1826
|
+
observe(nodeId: string, doc: Y.Doc): () => void;
|
|
1827
|
+
/**
|
|
1828
|
+
* Seed the Y.Doc meta map with current NodeStore state.
|
|
1829
|
+
* Called on document open to populate editor UI.
|
|
1830
|
+
*/
|
|
1831
|
+
seed(nodeId: string, doc: Y.Doc): Promise<void>;
|
|
1832
|
+
/**
|
|
1833
|
+
* @deprecated Use seed() instead. applyNow() was the bidirectional API.
|
|
1834
|
+
* This is kept for backward compatibility but now just calls seed().
|
|
1835
|
+
*/
|
|
1836
|
+
applyNow(nodeId: string, doc: Y.Doc): Promise<void>;
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Create a unidirectional MetaBridge.
|
|
1840
|
+
*
|
|
1841
|
+
* @param store - NodeStore to observe
|
|
1842
|
+
* @param options - Configuration options
|
|
1843
|
+
*/
|
|
1844
|
+
declare function createMetaBridge(store: NodeStore, options?: {
|
|
1845
|
+
/** Log warnings for non-MetaBridge meta map changes (default: true) */
|
|
1846
|
+
warnOnExternalMetaChanges?: boolean;
|
|
1847
|
+
}): MetaBridge;
|
|
1848
|
+
|
|
1849
|
+
/**
|
|
1850
|
+
* Node Pool - LRU cache of Y.Doc instances with acquire/release semantics
|
|
1851
|
+
*
|
|
1852
|
+
* Components acquire a Y.Doc when they need it and release it when they unmount.
|
|
1853
|
+
* Released Y.Docs stay in the pool (warm state) and continue receiving sync
|
|
1854
|
+
* updates via the Connection Manager.
|
|
1855
|
+
*
|
|
1856
|
+
* States:
|
|
1857
|
+
* - Active: refCount > 0, never evicted
|
|
1858
|
+
* - Warm: refCount = 0, evictable (LRU)
|
|
1859
|
+
* - Cold: evicted, serialized to storage (load on demand)
|
|
1860
|
+
*/
|
|
1861
|
+
|
|
1862
|
+
type PoolEntryState = 'active' | 'warm' | 'cold';
|
|
1863
|
+
interface NodePoolConfig {
|
|
1864
|
+
/** Storage adapter for persisting Y.Doc state */
|
|
1865
|
+
storage: NodeStorageAdapter;
|
|
1866
|
+
/** Meta bridge for syncing properties to NodeStore */
|
|
1867
|
+
metaBridge: MetaBridge;
|
|
1868
|
+
/** Max warm entries before LRU eviction (default: 50) */
|
|
1869
|
+
maxWarm?: number;
|
|
1870
|
+
/** Debounce delay for persisting dirty docs (default: 2000ms) */
|
|
1871
|
+
persistDelay?: number;
|
|
1872
|
+
/** Optional callback when a doc is updated */
|
|
1873
|
+
onDocUpdate?: (nodeId: string, doc: Y.Doc) => void;
|
|
1874
|
+
/** Optional callback before a doc is evicted */
|
|
1875
|
+
onDocEvict?: (nodeId: string, doc: Y.Doc) => void;
|
|
1876
|
+
}
|
|
1877
|
+
interface NodePool {
|
|
1878
|
+
/** Acquire a Y.Doc for a Node (load from storage or create new) */
|
|
1879
|
+
acquire(nodeId: string): Promise<Y.Doc>;
|
|
1880
|
+
/** Release a Y.Doc (component unmounted, doc stays warm) */
|
|
1881
|
+
release(nodeId: string): void;
|
|
1882
|
+
/** Check if a Node is in the pool */
|
|
1883
|
+
has(nodeId: string): boolean;
|
|
1884
|
+
/** Get pool entry state */
|
|
1885
|
+
getState(nodeId: string): PoolEntryState | null;
|
|
1886
|
+
/** Number of entries currently in memory */
|
|
1887
|
+
readonly size: number;
|
|
1888
|
+
/** Force-persist all dirty docs */
|
|
1889
|
+
flushAll(): Promise<void>;
|
|
1890
|
+
/** Destroy pool, persist all docs, cleanup */
|
|
1891
|
+
destroy(): Promise<void>;
|
|
1892
|
+
}
|
|
1893
|
+
declare function createNodePool(config: NodePoolConfig): NodePool;
|
|
1894
|
+
|
|
1895
|
+
/**
|
|
1896
|
+
* NodeStoreSyncProvider - Sync NodeChange events via hub ConnectionManager.
|
|
1897
|
+
*/
|
|
1898
|
+
|
|
1899
|
+
type SerializedNodeChange = {
|
|
1900
|
+
id: string;
|
|
1901
|
+
type: string;
|
|
1902
|
+
hash: string;
|
|
1903
|
+
room: string;
|
|
1904
|
+
nodeId: string;
|
|
1905
|
+
schemaId?: string;
|
|
1906
|
+
lamportTime: number;
|
|
1907
|
+
lamportAuthor: string;
|
|
1908
|
+
authorDid: string;
|
|
1909
|
+
wallTime: number;
|
|
1910
|
+
parentHash: string | null;
|
|
1911
|
+
payload: NodePayload;
|
|
1912
|
+
signatureB64: string;
|
|
1913
|
+
batchId?: string;
|
|
1914
|
+
batchIndex?: number;
|
|
1915
|
+
batchSize?: number;
|
|
1916
|
+
};
|
|
1917
|
+
type NodeSyncResponse = {
|
|
1918
|
+
type: 'node-sync-response';
|
|
1919
|
+
room: string;
|
|
1920
|
+
changes: SerializedNodeChange[];
|
|
1921
|
+
highWaterMark: number;
|
|
1922
|
+
};
|
|
1923
|
+
/**
|
|
1924
|
+
* Listener for unknown change type events.
|
|
1925
|
+
*/
|
|
1926
|
+
type UnknownChangeTypeListener = (change: NodeChange, peerId: string) => void;
|
|
1927
|
+
declare class NodeStoreSyncProvider {
|
|
1928
|
+
private store;
|
|
1929
|
+
private room;
|
|
1930
|
+
private lastSyncedLamport;
|
|
1931
|
+
private connection;
|
|
1932
|
+
private roomCleanup;
|
|
1933
|
+
private statusCleanup;
|
|
1934
|
+
private messageCleanup;
|
|
1935
|
+
private storeCleanup;
|
|
1936
|
+
private unknownChangeTypeListeners;
|
|
1937
|
+
constructor(store: NodeStore, room: string);
|
|
1938
|
+
/**
|
|
1939
|
+
* Subscribe to unknown change type events.
|
|
1940
|
+
* These are changes received from peers with types this version doesn't know how to process.
|
|
1941
|
+
* The changes are still stored in the change log for forward compatibility.
|
|
1942
|
+
*
|
|
1943
|
+
* @param listener - Callback invoked when an unknown change type is received
|
|
1944
|
+
* @returns Unsubscribe function
|
|
1945
|
+
*/
|
|
1946
|
+
onUnknownChangeType(listener: UnknownChangeTypeListener): () => void;
|
|
1947
|
+
private emitUnknownChangeType;
|
|
1948
|
+
attach(connection: ConnectionManager): void;
|
|
1949
|
+
detach(): void;
|
|
1950
|
+
private handleRoomMessage;
|
|
1951
|
+
private handleDirectMessage;
|
|
1952
|
+
private requestSync;
|
|
1953
|
+
private syncLocalChanges;
|
|
1954
|
+
private broadcastChange;
|
|
1955
|
+
private handleRemoteChange;
|
|
1956
|
+
private handleSyncResponse;
|
|
1957
|
+
private serializeChange;
|
|
1958
|
+
private deserializeChange;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
/**
|
|
1962
|
+
* Registry - Persistent tracked-Node set that survives app restarts
|
|
1963
|
+
*
|
|
1964
|
+
* The Registry maintains the set of Nodes the BSM should keep synced.
|
|
1965
|
+
* Nodes are added automatically when opened and expire after a configurable TTL.
|
|
1966
|
+
* Pinned Nodes never expire.
|
|
1967
|
+
*/
|
|
1968
|
+
interface TrackedNode {
|
|
1969
|
+
nodeId: string;
|
|
1970
|
+
schemaId: string;
|
|
1971
|
+
/** When the user last opened this Node */
|
|
1972
|
+
lastOpened: number;
|
|
1973
|
+
/** When sync last completed for this Node */
|
|
1974
|
+
lastSynced: number;
|
|
1975
|
+
/** Whether explicitly pinned (never expires) */
|
|
1976
|
+
pinned: boolean;
|
|
1977
|
+
}
|
|
1978
|
+
interface RegistryStorage {
|
|
1979
|
+
get(key: string): Promise<TrackedNode[] | null>;
|
|
1980
|
+
set(key: string, entries: TrackedNode[]): Promise<void>;
|
|
1981
|
+
}
|
|
1982
|
+
interface RegistryConfig {
|
|
1983
|
+
/** Storage adapter for persistence */
|
|
1984
|
+
storage: RegistryStorage;
|
|
1985
|
+
/** TTL for tracked Nodes in ms (default: 7 days) */
|
|
1986
|
+
trackTTL?: number;
|
|
1987
|
+
/** Storage key for the tracked set */
|
|
1988
|
+
storageKey?: string;
|
|
1989
|
+
}
|
|
1990
|
+
interface Registry {
|
|
1991
|
+
/** Add a Node to the tracked set */
|
|
1992
|
+
track(nodeId: string, schemaId: string): void;
|
|
1993
|
+
/** Remove a Node from the tracked set */
|
|
1994
|
+
untrack(nodeId: string): void;
|
|
1995
|
+
/** Pin a Node (never expires) */
|
|
1996
|
+
pin(nodeId: string): void;
|
|
1997
|
+
/** Unpin a Node (subject to TTL expiry) */
|
|
1998
|
+
unpin(nodeId: string): void;
|
|
1999
|
+
/** Mark a Node as recently opened (refreshes TTL) */
|
|
2000
|
+
touch(nodeId: string): void;
|
|
2001
|
+
/** Mark a Node as synced */
|
|
2002
|
+
markSynced(nodeId: string): void;
|
|
2003
|
+
/** Get all tracked Nodes (excluding expired) */
|
|
2004
|
+
getTracked(): TrackedNode[];
|
|
2005
|
+
/** Check if a Node is tracked */
|
|
2006
|
+
isTracked(nodeId: string): boolean;
|
|
2007
|
+
/** Load from storage */
|
|
2008
|
+
load(): Promise<void>;
|
|
2009
|
+
/** Persist to storage */
|
|
2010
|
+
save(): Promise<void>;
|
|
2011
|
+
/** Remove expired entries */
|
|
2012
|
+
prune(): number;
|
|
2013
|
+
}
|
|
2014
|
+
declare function createRegistry(config: RegistryConfig): Registry;
|
|
2015
|
+
|
|
2016
|
+
/**
|
|
2017
|
+
* Offline Queue - Persistent queue for Y.Doc updates made while disconnected
|
|
2018
|
+
*
|
|
2019
|
+
* When the network is unavailable, local Y.Doc updates are queued in persistent
|
|
2020
|
+
* storage. On reconnect, the queue is drained (replayed in order). This ensures
|
|
2021
|
+
* no local changes are lost, even across app restarts.
|
|
2022
|
+
*/
|
|
2023
|
+
|
|
2024
|
+
interface QueueEntry {
|
|
2025
|
+
/** Node ID this update belongs to */
|
|
2026
|
+
nodeId: string;
|
|
2027
|
+
/** Serialized Y.Doc update (base64 encoded) */
|
|
2028
|
+
update: string;
|
|
2029
|
+
/** Timestamp when queued */
|
|
2030
|
+
queuedAt: number;
|
|
2031
|
+
}
|
|
2032
|
+
interface OfflineQueueConfig {
|
|
2033
|
+
/** Storage adapter for persistence */
|
|
2034
|
+
storage: NodeStorageAdapter;
|
|
2035
|
+
/** Storage key for the queue (default: '_xnet_offline_queue') */
|
|
2036
|
+
storageKey?: string;
|
|
2037
|
+
/** Max queue size before dropping oldest entries (default: 1000) */
|
|
2038
|
+
maxSize?: number;
|
|
2039
|
+
}
|
|
2040
|
+
interface OfflineQueue {
|
|
2041
|
+
/** Enqueue an update for later broadcast */
|
|
2042
|
+
enqueue(nodeId: string, update: Uint8Array): Promise<void>;
|
|
2043
|
+
/** Drain the queue, calling handler for each entry. Returns count drained. */
|
|
2044
|
+
drain(handler: (entry: QueueEntry) => Promise<void>): Promise<number>;
|
|
2045
|
+
/** Number of entries in the queue */
|
|
2046
|
+
readonly size: number;
|
|
2047
|
+
/** Load queue from storage */
|
|
2048
|
+
load(): Promise<void>;
|
|
2049
|
+
/** Persist queue to storage */
|
|
2050
|
+
save(): Promise<void>;
|
|
2051
|
+
/** Clear all entries */
|
|
2052
|
+
clear(): Promise<void>;
|
|
2053
|
+
}
|
|
2054
|
+
declare function createOfflineQueue(config: OfflineQueueConfig): OfflineQueue;
|
|
2055
|
+
|
|
2056
|
+
/**
|
|
2057
|
+
* useIdentity hook for identity access
|
|
2058
|
+
*/
|
|
2059
|
+
|
|
2060
|
+
/**
|
|
2061
|
+
* Result from useIdentity hook
|
|
2062
|
+
*/
|
|
2063
|
+
interface UseIdentityResult {
|
|
2064
|
+
identity: Identity | null;
|
|
2065
|
+
isAuthenticated: boolean;
|
|
2066
|
+
did: string | null;
|
|
2067
|
+
}
|
|
2068
|
+
/**
|
|
2069
|
+
* Hook for accessing current identity
|
|
2070
|
+
*/
|
|
2071
|
+
declare function useIdentity(): UseIdentityResult;
|
|
2072
|
+
|
|
2073
|
+
/**
|
|
2074
|
+
* @xnetjs/react/onboarding - State machine for the onboarding flow
|
|
2075
|
+
*
|
|
2076
|
+
* States:
|
|
2077
|
+
* welcome → authenticating → connecting-hub → ready → complete
|
|
2078
|
+
* ↓ ↑
|
|
2079
|
+
* auth-error ──────── (retry) ────────┘
|
|
2080
|
+
* welcome → unsupported-browser (terminal)
|
|
2081
|
+
* welcome → import-identity → qr-scan/recovery-phrase → connecting-hub
|
|
2082
|
+
*/
|
|
2083
|
+
|
|
2084
|
+
type OnboardingState = 'welcome' | 'authenticating' | 'auth-error' | 'unsupported-browser' | 'import-identity' | 'qr-scan' | 'recovery-phrase' | 'connecting-hub' | 'ready' | 'complete';
|
|
2085
|
+
type OnboardingEvent = {
|
|
2086
|
+
type: 'AUTHENTICATE';
|
|
2087
|
+
} | {
|
|
2088
|
+
type: 'CREATE_NEW';
|
|
2089
|
+
} | {
|
|
2090
|
+
type: 'IMPORT_EXISTING';
|
|
2091
|
+
} | {
|
|
2092
|
+
type: 'PASSKEY_SUCCESS';
|
|
2093
|
+
identity: Identity;
|
|
2094
|
+
keyBundle: KeyBundle;
|
|
2095
|
+
} | {
|
|
2096
|
+
type: 'PASSKEY_FAILED';
|
|
2097
|
+
error: Error;
|
|
2098
|
+
} | {
|
|
2099
|
+
type: 'BROWSER_UNSUPPORTED';
|
|
2100
|
+
} | {
|
|
2101
|
+
type: 'RETRY_AUTH';
|
|
2102
|
+
} | {
|
|
2103
|
+
type: 'BACK_TO_WELCOME';
|
|
2104
|
+
} | {
|
|
2105
|
+
type: 'SCAN_QR';
|
|
2106
|
+
} | {
|
|
2107
|
+
type: 'ENTER_PHRASE';
|
|
2108
|
+
} | {
|
|
2109
|
+
type: 'IDENTITY_IMPORTED';
|
|
2110
|
+
identity: Identity;
|
|
2111
|
+
keyBundle: KeyBundle;
|
|
2112
|
+
} | {
|
|
2113
|
+
type: 'HUB_CONNECTED';
|
|
2114
|
+
} | {
|
|
2115
|
+
type: 'HUB_FAILED';
|
|
2116
|
+
error: Error;
|
|
2117
|
+
} | {
|
|
2118
|
+
type: 'CREATE_FIRST_PAGE';
|
|
2119
|
+
};
|
|
2120
|
+
type OnboardingMachineContext = {
|
|
2121
|
+
identity: Identity | null;
|
|
2122
|
+
keyBundle: KeyBundle | null;
|
|
2123
|
+
hubUrl: string | null;
|
|
2124
|
+
error: Error | null;
|
|
2125
|
+
isDemo: boolean;
|
|
2126
|
+
};
|
|
2127
|
+
type OnboardingReducerState = {
|
|
2128
|
+
state: OnboardingState;
|
|
2129
|
+
context: OnboardingMachineContext;
|
|
2130
|
+
};
|
|
2131
|
+
declare function onboardingReducer(current: OnboardingReducerState, event: OnboardingEvent): OnboardingReducerState;
|
|
2132
|
+
declare function createInitialState(hubUrl?: string): OnboardingReducerState;
|
|
2133
|
+
|
|
2134
|
+
/**
|
|
2135
|
+
* @xnetjs/react/onboarding - React context provider for the onboarding flow
|
|
2136
|
+
*/
|
|
2137
|
+
|
|
2138
|
+
type OnboardingContextValue = {
|
|
2139
|
+
state: OnboardingState;
|
|
2140
|
+
context: OnboardingMachineContext;
|
|
2141
|
+
send: (event: OnboardingEvent) => void;
|
|
2142
|
+
};
|
|
2143
|
+
type OnboardingProviderProps = {
|
|
2144
|
+
children: ReactNode;
|
|
2145
|
+
/** Default hub URL for new users */
|
|
2146
|
+
defaultHubUrl?: string;
|
|
2147
|
+
/** Called when onboarding completes */
|
|
2148
|
+
onComplete?: (identity: Identity, keyBundle: KeyBundle) => void;
|
|
2149
|
+
};
|
|
2150
|
+
declare function OnboardingProvider({ children, defaultHubUrl, onComplete }: OnboardingProviderProps): JSX.Element;
|
|
2151
|
+
/**
|
|
2152
|
+
* Access the onboarding state machine from within an OnboardingProvider.
|
|
2153
|
+
*/
|
|
2154
|
+
declare function useOnboarding(): OnboardingContextValue;
|
|
2155
|
+
|
|
2156
|
+
type HubConnectScreenProps = {
|
|
2157
|
+
/** Optional: attempt hub connection. If not provided, auto-advances. */
|
|
2158
|
+
connectToHub?: () => Promise<void>;
|
|
2159
|
+
};
|
|
2160
|
+
declare function HubConnectScreen({ connectToHub }: HubConnectScreenProps): JSX.Element;
|
|
2161
|
+
|
|
2162
|
+
interface OnboardingFlowProps {
|
|
2163
|
+
/** Optional hub connection function */
|
|
2164
|
+
connectToHub?: HubConnectScreenProps['connectToHub'];
|
|
2165
|
+
/** Render prop for the completed state (app content) */
|
|
2166
|
+
children?: React.ReactNode;
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* Renders the correct onboarding screen based on state machine state.
|
|
2170
|
+
*
|
|
2171
|
+
* @example
|
|
2172
|
+
* <OnboardingProvider onComplete={handleComplete}>
|
|
2173
|
+
* <OnboardingFlow>
|
|
2174
|
+
* <App />
|
|
2175
|
+
* </OnboardingFlow>
|
|
2176
|
+
* </OnboardingProvider>
|
|
2177
|
+
*/
|
|
2178
|
+
declare function OnboardingFlow({ connectToHub, children }: OnboardingFlowProps): JSX.Element;
|
|
2179
|
+
|
|
2180
|
+
declare function WelcomeScreen(): JSX.Element;
|
|
2181
|
+
|
|
2182
|
+
declare function AuthenticatingScreen(): JSX.Element;
|
|
2183
|
+
|
|
2184
|
+
declare function AuthErrorScreen(): JSX.Element;
|
|
2185
|
+
|
|
2186
|
+
declare function UnsupportedBrowserScreen(): JSX.Element;
|
|
2187
|
+
|
|
2188
|
+
declare function ImportIdentityScreen(): JSX.Element;
|
|
2189
|
+
|
|
2190
|
+
declare function ReadyScreen(): JSX.Element;
|
|
2191
|
+
|
|
2192
|
+
declare function SmartWelcome(): JSX.Element;
|
|
2193
|
+
|
|
2194
|
+
/**
|
|
2195
|
+
* @xnetjs/react/sync - Initial sync manager for new device onboarding
|
|
2196
|
+
*
|
|
2197
|
+
* Orchestrates the full state sync when a new device connects to the hub
|
|
2198
|
+
* with an existing identity. Tracks progress and provides callbacks.
|
|
2199
|
+
*/
|
|
2200
|
+
type SyncPhase = 'connecting' | 'syncing' | 'complete' | 'error';
|
|
2201
|
+
type SyncProgress = {
|
|
2202
|
+
/** Current phase of the sync process */
|
|
2203
|
+
phase: SyncPhase;
|
|
2204
|
+
/** Total rooms discovered on the hub */
|
|
2205
|
+
roomsTotal: number;
|
|
2206
|
+
/** Rooms that have been fully synced */
|
|
2207
|
+
roomsSynced: number;
|
|
2208
|
+
/** Total bytes received from hub */
|
|
2209
|
+
bytesReceived: number;
|
|
2210
|
+
/** Error if phase === 'error' */
|
|
2211
|
+
error?: Error;
|
|
2212
|
+
};
|
|
2213
|
+
type InitialSyncMessage = {
|
|
2214
|
+
type: 'initial-sync' | 'node-changes' | 'initial-sync-complete';
|
|
2215
|
+
room?: string;
|
|
2216
|
+
update?: Uint8Array;
|
|
2217
|
+
changes?: unknown[];
|
|
2218
|
+
roomCount?: number;
|
|
2219
|
+
};
|
|
2220
|
+
type ProgressListener = (progress: SyncProgress) => void;
|
|
2221
|
+
/**
|
|
2222
|
+
* Manages the initial sync process for a new device.
|
|
2223
|
+
*
|
|
2224
|
+
* Usage:
|
|
2225
|
+
* ```ts
|
|
2226
|
+
* const manager = createInitialSyncManager()
|
|
2227
|
+
* const unsub = manager.onProgress((p) => updateUI(p))
|
|
2228
|
+
*
|
|
2229
|
+
* // Feed messages from the hub WebSocket:
|
|
2230
|
+
* manager.handleMessage({ type: 'initial-sync', room: 'r1', update: ... })
|
|
2231
|
+
* manager.handleMessage({ type: 'initial-sync-complete', roomCount: 5 })
|
|
2232
|
+
*
|
|
2233
|
+
* unsub()
|
|
2234
|
+
* ```
|
|
2235
|
+
*/
|
|
2236
|
+
type InitialSyncManager = {
|
|
2237
|
+
/** Subscribe to progress updates */
|
|
2238
|
+
onProgress(listener: ProgressListener): () => void;
|
|
2239
|
+
/** Handle an incoming sync message from the hub */
|
|
2240
|
+
handleMessage(msg: InitialSyncMessage): void;
|
|
2241
|
+
/** Get current progress snapshot */
|
|
2242
|
+
getProgress(): SyncProgress;
|
|
2243
|
+
/** Mark sync as started (connecting phase) */
|
|
2244
|
+
start(): void;
|
|
2245
|
+
/** Mark sync as errored */
|
|
2246
|
+
setError(error: Error): void;
|
|
2247
|
+
/** Reset to initial state */
|
|
2248
|
+
reset(): void;
|
|
2249
|
+
};
|
|
2250
|
+
declare function createInitialSyncManager(): InitialSyncManager;
|
|
2251
|
+
|
|
2252
|
+
/**
|
|
2253
|
+
* Sync progress overlay — shown during initial sync on a new device.
|
|
2254
|
+
*/
|
|
2255
|
+
|
|
2256
|
+
interface SyncProgressOverlayProps {
|
|
2257
|
+
progress: SyncProgress;
|
|
2258
|
+
onComplete: () => void;
|
|
2259
|
+
}
|
|
2260
|
+
declare function SyncProgressOverlay({ progress, onComplete }: SyncProgressOverlayProps): JSX.Element;
|
|
2261
|
+
|
|
2262
|
+
/**
|
|
2263
|
+
* @xnetjs/react/onboarding - Quick-start content templates
|
|
2264
|
+
*/
|
|
2265
|
+
interface QuickStartTemplate {
|
|
2266
|
+
id: string;
|
|
2267
|
+
name: string;
|
|
2268
|
+
description: string;
|
|
2269
|
+
icon: string;
|
|
2270
|
+
}
|
|
2271
|
+
declare const QUICK_START_TEMPLATES: QuickStartTemplate[];
|
|
2272
|
+
|
|
2273
|
+
/**
|
|
2274
|
+
* @xnetjs/react/onboarding - Helper utilities
|
|
2275
|
+
*/
|
|
2276
|
+
/**
|
|
2277
|
+
* Get a human-friendly name for the platform's biometric authenticator.
|
|
2278
|
+
*/
|
|
2279
|
+
declare function getPlatformAuthName(): string;
|
|
2280
|
+
/**
|
|
2281
|
+
* Truncate a DID for display, showing first and last segments.
|
|
2282
|
+
* e.g. "did:key:z6MkhaXg...yz" → "did:key:z6Mkha...xyz"
|
|
2283
|
+
*/
|
|
2284
|
+
declare function truncateDid(did: string, headLen?: number, tailLen?: number): string;
|
|
2285
|
+
/**
|
|
2286
|
+
* Copy text to clipboard, returns true on success.
|
|
2287
|
+
* Returns false in SSR environments where navigator is unavailable.
|
|
2288
|
+
*/
|
|
2289
|
+
declare function copyToClipboard(text: string): Promise<boolean>;
|
|
2290
|
+
|
|
2291
|
+
/**
|
|
2292
|
+
* Telemetry context for @xnetjs/react
|
|
2293
|
+
*
|
|
2294
|
+
* Provides an optional duck-typed TelemetryReporter to the React tree.
|
|
2295
|
+
* When present, hooks report performance timing, usage counts, and errors.
|
|
2296
|
+
*
|
|
2297
|
+
* Uses the same duck-typed interface pattern as other xNet packages to
|
|
2298
|
+
* avoid circular dependencies with @xnetjs/telemetry.
|
|
2299
|
+
*/
|
|
2300
|
+
/**
|
|
2301
|
+
* Duck-typed interface for telemetry reporting.
|
|
2302
|
+
* Satisfied by @xnetjs/telemetry's TelemetryCollector or any compatible object.
|
|
2303
|
+
*
|
|
2304
|
+
* @example
|
|
2305
|
+
* ```ts
|
|
2306
|
+
* import { TelemetryCollector } from '@xnetjs/telemetry'
|
|
2307
|
+
* const telemetry = new TelemetryCollector({ consent })
|
|
2308
|
+
* // Pass to XNetProvider via config.telemetry
|
|
2309
|
+
* ```
|
|
2310
|
+
*/
|
|
2311
|
+
interface TelemetryReporter {
|
|
2312
|
+
reportPerformance(metricName: string, durationMs: number): void;
|
|
2313
|
+
reportUsage(metricName: string, count: number): void;
|
|
2314
|
+
reportCrash(error: Error, context?: Record<string, unknown>): void;
|
|
2315
|
+
}
|
|
2316
|
+
declare const TelemetryContext: react.Context<TelemetryReporter | null>;
|
|
2317
|
+
/**
|
|
2318
|
+
* Hook to access the telemetry reporter (null if no telemetry configured).
|
|
2319
|
+
* @internal Used by useQuery/useMutate - not part of public API.
|
|
2320
|
+
*/
|
|
2321
|
+
declare function useTelemetryReporter(): TelemetryReporter | null;
|
|
2322
|
+
|
|
2323
|
+
/**
|
|
2324
|
+
* XNet React context provider
|
|
2325
|
+
*
|
|
2326
|
+
* Provides NodeStore and optional identity to the React tree.
|
|
2327
|
+
* All data access happens through useQuery/useMutate/useNode hooks.
|
|
2328
|
+
*/
|
|
2329
|
+
|
|
2330
|
+
/**
|
|
2331
|
+
* XNet configuration
|
|
2332
|
+
*/
|
|
2333
|
+
interface XNetConfig {
|
|
2334
|
+
/** Node storage adapter for NodeStore (defaults to MemoryNodeStorageAdapter) */
|
|
2335
|
+
nodeStorage?: NodeStorageAdapter;
|
|
2336
|
+
/** Author's DID for signing changes */
|
|
2337
|
+
authorDID?: DID;
|
|
2338
|
+
/** Ed25519 signing key */
|
|
2339
|
+
signingKey?: Uint8Array;
|
|
2340
|
+
/** User identity */
|
|
2341
|
+
identity?: Identity;
|
|
2342
|
+
/** Signaling server URLs for sync (default: ['ws://localhost:4444']) */
|
|
2343
|
+
signalingServers?: string[];
|
|
2344
|
+
/** Hub WebSocket URL for always-on sync (overrides signalingServers[0]) */
|
|
2345
|
+
hubUrl?: string;
|
|
2346
|
+
/** Hub integration options */
|
|
2347
|
+
hubOptions?: {
|
|
2348
|
+
/** Auto-generate UCAN for hub auth (default: true) */
|
|
2349
|
+
autoAuth?: boolean;
|
|
2350
|
+
/** Static UCAN token override for pre-authorized share sessions */
|
|
2351
|
+
authToken?: string;
|
|
2352
|
+
/** Enable auto-backup on document updates (default: false) */
|
|
2353
|
+
autoBackup?: boolean;
|
|
2354
|
+
/** Backup debounce delay in ms (default: 5000) */
|
|
2355
|
+
backupDebounceMs?: number;
|
|
2356
|
+
/** Enable search indexing on NodeStore changes (default: false) */
|
|
2357
|
+
enableSearchIndex?: boolean;
|
|
2358
|
+
/** Room name for node-change relay (defaults to author DID) */
|
|
2359
|
+
nodeSyncRoom?: string;
|
|
2360
|
+
};
|
|
2361
|
+
/** Encryption key for hub backups (XChaCha20-Poly1305) */
|
|
2362
|
+
encryptionKey?: Uint8Array;
|
|
2363
|
+
/** Disable Background Sync Manager (default: false) */
|
|
2364
|
+
disableSyncManager?: boolean;
|
|
2365
|
+
/** Provide an external SyncManager (e.g., IPC-based for Electron desktop).
|
|
2366
|
+
* When provided, the internal SyncManager creation is skipped. */
|
|
2367
|
+
syncManager?: SyncManager;
|
|
2368
|
+
/** Blob store for P2P blob sync (images, files). If provided, the SyncManager
|
|
2369
|
+
* will sync blobs alongside Y.Doc state. Typically a BlobStore from @xnetjs/storage. */
|
|
2370
|
+
blobStore?: BlobStoreForSync;
|
|
2371
|
+
/** Platform for plugin compatibility ('web' | 'electron' | 'mobile'). Defaults to 'web'. */
|
|
2372
|
+
platform?: Platform;
|
|
2373
|
+
/** Disable plugin system (default: false) */
|
|
2374
|
+
disablePlugins?: boolean;
|
|
2375
|
+
/**
|
|
2376
|
+
* Custom DataBridge instance.
|
|
2377
|
+
*
|
|
2378
|
+
* When provided, this bridge is used for data access instead of creating
|
|
2379
|
+
* a MainThreadBridge. This allows using WorkerBridge or other off-main-thread
|
|
2380
|
+
* implementations.
|
|
2381
|
+
*
|
|
2382
|
+
* The bridge must already be initialized before passing to XNetProvider.
|
|
2383
|
+
*
|
|
2384
|
+
* Note: When using a custom bridge, NodeStore is still created on the main
|
|
2385
|
+
* thread for SyncManager and other integrations. The custom bridge is used
|
|
2386
|
+
* only for React hook data access (useQuery, useMutate).
|
|
2387
|
+
*
|
|
2388
|
+
* @example
|
|
2389
|
+
* ```tsx
|
|
2390
|
+
* // Using WorkerBridge
|
|
2391
|
+
* const bridge = new WorkerBridge(workerUrl)
|
|
2392
|
+
* await bridge.initialize({ authorDID, signingKey, dbName: 'xnet' })
|
|
2393
|
+
*
|
|
2394
|
+
* <XNetProvider config={{ dataBridge: bridge, ... }}>
|
|
2395
|
+
* <App />
|
|
2396
|
+
* </XNetProvider>
|
|
2397
|
+
* ```
|
|
2398
|
+
*/
|
|
2399
|
+
dataBridge?: DataBridge;
|
|
2400
|
+
/**
|
|
2401
|
+
* Security configuration for multi-level cryptography.
|
|
2402
|
+
*/
|
|
2403
|
+
security?: {
|
|
2404
|
+
/** Default security level for new signatures (default: 0 for Ed25519-only) */
|
|
2405
|
+
level?: SecurityLevel;
|
|
2406
|
+
/** Minimum acceptable level for verification (default: 0) */
|
|
2407
|
+
minVerificationLevel?: SecurityLevel;
|
|
2408
|
+
/** Verification policy (default: 'strict') */
|
|
2409
|
+
verificationPolicy?: 'strict' | 'permissive';
|
|
2410
|
+
/** Custom PQ key registry */
|
|
2411
|
+
registry?: PQKeyRegistry;
|
|
2412
|
+
};
|
|
2413
|
+
/**
|
|
2414
|
+
* Hybrid key bundle for multi-level cryptography.
|
|
2415
|
+
*
|
|
2416
|
+
* When provided, enables signing at higher security levels (1, 2).
|
|
2417
|
+
* The bundle includes Ed25519 keys and optionally ML-DSA (post-quantum) keys.
|
|
2418
|
+
*
|
|
2419
|
+
* @example
|
|
2420
|
+
* ```tsx
|
|
2421
|
+
* const bundle = createKeyBundle({ includePQ: true })
|
|
2422
|
+
*
|
|
2423
|
+
* <XNetProvider config={{ keyBundle: bundle, ... }}>
|
|
2424
|
+
* <App />
|
|
2425
|
+
* </XNetProvider>
|
|
2426
|
+
* ```
|
|
2427
|
+
*/
|
|
2428
|
+
keyBundle?: HybridKeyBundle;
|
|
2429
|
+
/**
|
|
2430
|
+
* Optional telemetry reporter for hook instrumentation.
|
|
2431
|
+
*
|
|
2432
|
+
* When provided, useQuery and useMutate hooks will report:
|
|
2433
|
+
* - Query timing (first-load latency)
|
|
2434
|
+
* - Cache hit/miss rates
|
|
2435
|
+
* - Mutation success/failure rates
|
|
2436
|
+
* - Subscription churn (mount/unmount frequency)
|
|
2437
|
+
*
|
|
2438
|
+
* Uses a duck-typed interface to avoid circular dependencies with @xnetjs/telemetry.
|
|
2439
|
+
*
|
|
2440
|
+
* @example
|
|
2441
|
+
* ```tsx
|
|
2442
|
+
* import { TelemetryCollector, ConsentManager } from '@xnetjs/telemetry'
|
|
2443
|
+
* const consent = new ConsentManager()
|
|
2444
|
+
* const telemetry = new TelemetryCollector({ consent })
|
|
2445
|
+
*
|
|
2446
|
+
* <XNetProvider config={{ telemetry, ... }}>
|
|
2447
|
+
* <App />
|
|
2448
|
+
* </XNetProvider>
|
|
2449
|
+
* ```
|
|
2450
|
+
*/
|
|
2451
|
+
telemetry?: TelemetryReporter;
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* XNet context value
|
|
2455
|
+
*/
|
|
2456
|
+
interface XNetContextValue {
|
|
2457
|
+
/** NodeStore for Node operations */
|
|
2458
|
+
nodeStore: NodeStore | null;
|
|
2459
|
+
/** Whether NodeStore is initialized */
|
|
2460
|
+
nodeStoreReady: boolean;
|
|
2461
|
+
/** User identity (if provided) */
|
|
2462
|
+
identity?: Identity;
|
|
2463
|
+
/** Author DID (resolved from config.authorDID or config.identity.did) */
|
|
2464
|
+
authorDID: string | null;
|
|
2465
|
+
/** Background Sync Manager (null if disabled or not yet initialized) */
|
|
2466
|
+
syncManager: SyncManager | null;
|
|
2467
|
+
/** Hub URL (if configured) */
|
|
2468
|
+
hubUrl: string | null;
|
|
2469
|
+
/** Hub connection status */
|
|
2470
|
+
hubStatus: SyncStatus;
|
|
2471
|
+
/** Hub connection (shares SyncManager connection when available) */
|
|
2472
|
+
hubConnection: ConnectionManager | null;
|
|
2473
|
+
/** Hub auth token provider (for HTTP requests) */
|
|
2474
|
+
getHubAuthToken?: () => Promise<string>;
|
|
2475
|
+
/** Encryption key for hub backups */
|
|
2476
|
+
encryptionKey: Uint8Array | null;
|
|
2477
|
+
/** Blob store for content-addressed storage (null if not configured) */
|
|
2478
|
+
blobStore: BlobStoreForSync | null;
|
|
2479
|
+
/** Plugin Registry (null if disabled or not yet initialized) */
|
|
2480
|
+
pluginRegistry: PluginRegistry | null;
|
|
2481
|
+
}
|
|
2482
|
+
/**
|
|
2483
|
+
* XNet provider props
|
|
2484
|
+
*/
|
|
2485
|
+
interface XNetProviderProps {
|
|
2486
|
+
config: XNetConfig;
|
|
2487
|
+
children: ReactNode;
|
|
2488
|
+
}
|
|
2489
|
+
/**
|
|
2490
|
+
* XNet provider component
|
|
2491
|
+
*
|
|
2492
|
+
* Initializes NodeStore and provides it to the React tree.
|
|
2493
|
+
*/
|
|
2494
|
+
declare function XNetProvider({ config, children }: XNetProviderProps): JSX.Element;
|
|
2495
|
+
/**
|
|
2496
|
+
* Hook to access XNet context
|
|
2497
|
+
*
|
|
2498
|
+
* @internal Used by useIdentity. Not part of public API.
|
|
2499
|
+
*/
|
|
2500
|
+
declare function useXNet(): XNetContextValue;
|
|
2501
|
+
|
|
2502
|
+
/**
|
|
2503
|
+
* Security context for multi-level cryptography.
|
|
2504
|
+
*
|
|
2505
|
+
* Provides global security configuration and state management for:
|
|
2506
|
+
* - Security level selection (0, 1, 2)
|
|
2507
|
+
* - Verification policy configuration
|
|
2508
|
+
* - PQ key registry access
|
|
2509
|
+
* - Key bundle management
|
|
2510
|
+
*/
|
|
2511
|
+
|
|
2512
|
+
/**
|
|
2513
|
+
* Security context state.
|
|
2514
|
+
*/
|
|
2515
|
+
interface SecurityContextState {
|
|
2516
|
+
/** Current security level for new signatures */
|
|
2517
|
+
level: SecurityLevel;
|
|
2518
|
+
/** Minimum level to accept during verification */
|
|
2519
|
+
minVerificationLevel: SecurityLevel;
|
|
2520
|
+
/** Verification policy */
|
|
2521
|
+
verificationPolicy: 'strict' | 'permissive';
|
|
2522
|
+
/** PQ key registry */
|
|
2523
|
+
registry: PQKeyRegistry;
|
|
2524
|
+
/** Current key bundle (if available) */
|
|
2525
|
+
keyBundle?: HybridKeyBundle;
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Security context actions.
|
|
2529
|
+
*/
|
|
2530
|
+
interface SecurityContextActions {
|
|
2531
|
+
/** Set the signing security level */
|
|
2532
|
+
setLevel: (level: SecurityLevel) => void;
|
|
2533
|
+
/** Set the minimum verification level */
|
|
2534
|
+
setMinVerificationLevel: (level: SecurityLevel) => void;
|
|
2535
|
+
/** Set the verification policy */
|
|
2536
|
+
setVerificationPolicy: (policy: 'strict' | 'permissive') => void;
|
|
2537
|
+
/** Update the key bundle */
|
|
2538
|
+
setKeyBundle: (bundle: HybridKeyBundle | undefined) => void;
|
|
2539
|
+
}
|
|
2540
|
+
/**
|
|
2541
|
+
* Combined security context value.
|
|
2542
|
+
*/
|
|
2543
|
+
type SecurityContextValue = SecurityContextState & SecurityContextActions;
|
|
2544
|
+
/**
|
|
2545
|
+
* Security provider props.
|
|
2546
|
+
*/
|
|
2547
|
+
interface SecurityProviderProps {
|
|
2548
|
+
children: ReactNode;
|
|
2549
|
+
/** Initial security level (default: 0 for Ed25519-only) */
|
|
2550
|
+
level?: SecurityLevel;
|
|
2551
|
+
/** Initial minimum verification level (default: 0) */
|
|
2552
|
+
minVerificationLevel?: SecurityLevel;
|
|
2553
|
+
/** Initial verification policy (default: 'strict') */
|
|
2554
|
+
verificationPolicy?: 'strict' | 'permissive';
|
|
2555
|
+
/** PQ key registry (default: MemoryPQKeyRegistry) */
|
|
2556
|
+
registry?: PQKeyRegistry;
|
|
2557
|
+
/** Initial key bundle */
|
|
2558
|
+
keyBundle?: HybridKeyBundle;
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2561
|
+
* Security provider component.
|
|
2562
|
+
*
|
|
2563
|
+
* Wraps the application with security configuration and state.
|
|
2564
|
+
*
|
|
2565
|
+
* @example
|
|
2566
|
+
* ```tsx
|
|
2567
|
+
* <SecurityProvider level={1} verificationPolicy="strict">
|
|
2568
|
+
* <App />
|
|
2569
|
+
* </SecurityProvider>
|
|
2570
|
+
* ```
|
|
2571
|
+
*/
|
|
2572
|
+
declare function SecurityProvider({ children, level: initialLevel, minVerificationLevel: initialMinLevel, verificationPolicy: initialPolicy, registry: providedRegistry, keyBundle: initialBundle }: SecurityProviderProps): JSX.Element;
|
|
2573
|
+
/**
|
|
2574
|
+
* Hook to access security context.
|
|
2575
|
+
*
|
|
2576
|
+
* @throws Error if used outside of SecurityProvider
|
|
2577
|
+
*
|
|
2578
|
+
* @example
|
|
2579
|
+
* ```tsx
|
|
2580
|
+
* function MyComponent() {
|
|
2581
|
+
* const { level, setLevel, registry } = useSecurityContext()
|
|
2582
|
+
* // ...
|
|
2583
|
+
* }
|
|
2584
|
+
* ```
|
|
2585
|
+
*/
|
|
2586
|
+
declare function useSecurityContext(): SecurityContextValue;
|
|
2587
|
+
/**
|
|
2588
|
+
* Hook to optionally access security context.
|
|
2589
|
+
*
|
|
2590
|
+
* Returns null if used outside of SecurityProvider (no error thrown).
|
|
2591
|
+
*/
|
|
2592
|
+
declare function useSecurityContextOptional(): SecurityContextValue | null;
|
|
2593
|
+
|
|
2594
|
+
/**
|
|
2595
|
+
* useSecurity - Hook for security-aware operations.
|
|
2596
|
+
*
|
|
2597
|
+
* Provides signing and verification functions that use the current
|
|
2598
|
+
* security level from the SecurityContext.
|
|
2599
|
+
*/
|
|
2600
|
+
|
|
2601
|
+
/**
|
|
2602
|
+
* Options for useSecurity hook.
|
|
2603
|
+
*/
|
|
2604
|
+
interface UseSecurityOptions {
|
|
2605
|
+
/** Override default security level for this hook instance */
|
|
2606
|
+
level?: SecurityLevel;
|
|
2607
|
+
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Return type for useSecurity hook.
|
|
2610
|
+
*/
|
|
2611
|
+
interface UseSecurityResult {
|
|
2612
|
+
/** Current security level (from context or override) */
|
|
2613
|
+
level: SecurityLevel;
|
|
2614
|
+
/** Whether the current key bundle has PQ keys */
|
|
2615
|
+
hasPQKeys: boolean;
|
|
2616
|
+
/** Maximum level supported by current keys (0 if no PQ keys, 2 if PQ keys present) */
|
|
2617
|
+
maxLevel: SecurityLevel;
|
|
2618
|
+
/** Sign data at current security level */
|
|
2619
|
+
sign: (data: Uint8Array) => UnifiedSignature;
|
|
2620
|
+
/** Verify a signature against a DID */
|
|
2621
|
+
verify: (data: Uint8Array, signature: UnifiedSignature, did: DID) => Promise<VerificationResult$1>;
|
|
2622
|
+
/** Set the global security level */
|
|
2623
|
+
setLevel: (level: SecurityLevel) => void;
|
|
2624
|
+
/** Check if a level is supported by current keys */
|
|
2625
|
+
canSignAt: (level: SecurityLevel) => boolean;
|
|
2626
|
+
/** Check if key bundle is available */
|
|
2627
|
+
hasKeyBundle: boolean;
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Hook for security-aware operations.
|
|
2631
|
+
*
|
|
2632
|
+
* Provides signing and verification functions that use the current
|
|
2633
|
+
* security level from the SecurityContext.
|
|
2634
|
+
*
|
|
2635
|
+
* @example
|
|
2636
|
+
* ```tsx
|
|
2637
|
+
* function SignedMessage() {
|
|
2638
|
+
* const { sign, verify, level, hasPQKeys } = useSecurity()
|
|
2639
|
+
*
|
|
2640
|
+
* const handleSign = async () => {
|
|
2641
|
+
* const data = new TextEncoder().encode('Hello')
|
|
2642
|
+
* const sig = sign(data)
|
|
2643
|
+
* console.log(`Signed at Level ${sig.level}`)
|
|
2644
|
+
* }
|
|
2645
|
+
*
|
|
2646
|
+
* return (
|
|
2647
|
+
* <div>
|
|
2648
|
+
* <p>Security Level: {level}</p>
|
|
2649
|
+
* <p>PQ Keys: {hasPQKeys ? 'Yes' : 'No'}</p>
|
|
2650
|
+
* <button onClick={handleSign}>Sign</button>
|
|
2651
|
+
* </div>
|
|
2652
|
+
* )
|
|
2653
|
+
* }
|
|
2654
|
+
* ```
|
|
2655
|
+
*
|
|
2656
|
+
* @example Per-operation level override
|
|
2657
|
+
* ```tsx
|
|
2658
|
+
* function HighSecurityOperation() {
|
|
2659
|
+
* const { sign } = useSecurity({ level: 2 }) // Force Level 2
|
|
2660
|
+
*
|
|
2661
|
+
* const handleCritical = () => {
|
|
2662
|
+
* const data = new TextEncoder().encode('Critical operation')
|
|
2663
|
+
* const sig = sign(data) // Signs at Level 2
|
|
2664
|
+
* console.log(sig.level) // 2
|
|
2665
|
+
* }
|
|
2666
|
+
*
|
|
2667
|
+
* return <button onClick={handleCritical}>Critical Action</button>
|
|
2668
|
+
* }
|
|
2669
|
+
* ```
|
|
2670
|
+
*
|
|
2671
|
+
* @example Fast mode for high-frequency operations
|
|
2672
|
+
* ```tsx
|
|
2673
|
+
* function CursorUpdates() {
|
|
2674
|
+
* const { sign } = useSecurity({ level: 0 }) // Fast Ed25519-only
|
|
2675
|
+
*
|
|
2676
|
+
* const handleCursor = (position: number) => {
|
|
2677
|
+
* const data = new TextEncoder().encode(JSON.stringify({ position }))
|
|
2678
|
+
* const sig = sign(data) // Fast signing
|
|
2679
|
+
* }
|
|
2680
|
+
*
|
|
2681
|
+
* return <canvas onMouseMove={(e) => handleCursor(e.clientX)} />
|
|
2682
|
+
* }
|
|
2683
|
+
* ```
|
|
2684
|
+
*/
|
|
2685
|
+
declare function useSecurity(options?: UseSecurityOptions): UseSecurityResult;
|
|
2686
|
+
|
|
2687
|
+
/**
|
|
2688
|
+
* Plugin registry context
|
|
2689
|
+
*/
|
|
2690
|
+
declare const PluginRegistryContext: react.Context<PluginRegistry | null>;
|
|
2691
|
+
/**
|
|
2692
|
+
* Access the PluginRegistry instance
|
|
2693
|
+
*
|
|
2694
|
+
* @example
|
|
2695
|
+
* ```tsx
|
|
2696
|
+
* const registry = usePluginRegistry()
|
|
2697
|
+
* const plugins = registry.getAll()
|
|
2698
|
+
* ```
|
|
2699
|
+
*
|
|
2700
|
+
* @throws Error if used outside of XNetProvider with plugins enabled
|
|
2701
|
+
*/
|
|
2702
|
+
declare function usePluginRegistry(): PluginRegistry;
|
|
2703
|
+
/**
|
|
2704
|
+
* Safely access the PluginRegistry instance, returns null if not available
|
|
2705
|
+
*
|
|
2706
|
+
* Use this when you need to optionally access the plugin system without throwing.
|
|
2707
|
+
*/
|
|
2708
|
+
declare function usePluginRegistryOptional(): PluginRegistry | null;
|
|
2709
|
+
/**
|
|
2710
|
+
* Get all registered plugins with reactive updates
|
|
2711
|
+
*
|
|
2712
|
+
* @example
|
|
2713
|
+
* ```tsx
|
|
2714
|
+
* const plugins = usePlugins()
|
|
2715
|
+
* // Re-renders when plugins change
|
|
2716
|
+
* ```
|
|
2717
|
+
*/
|
|
2718
|
+
declare function usePlugins(): RegisteredPlugin[];
|
|
2719
|
+
/**
|
|
2720
|
+
* Contribution type to interface mapping
|
|
2721
|
+
*/
|
|
2722
|
+
type ContributionTypeMap = {
|
|
2723
|
+
views: ViewContribution;
|
|
2724
|
+
commands: CommandContribution;
|
|
2725
|
+
slashCommands: SlashCommandContribution;
|
|
2726
|
+
sidebar: SidebarContribution;
|
|
2727
|
+
editor: EditorContribution;
|
|
2728
|
+
propertyHandlers: PropertyHandlerContribution;
|
|
2729
|
+
blocks: BlockContribution;
|
|
2730
|
+
settings: SettingContribution;
|
|
2731
|
+
};
|
|
2732
|
+
/**
|
|
2733
|
+
* Get contributions of a specific type with reactive updates
|
|
2734
|
+
*
|
|
2735
|
+
* @example
|
|
2736
|
+
* ```tsx
|
|
2737
|
+
* // Get all registered views
|
|
2738
|
+
* const views = useContributions('views')
|
|
2739
|
+
*
|
|
2740
|
+
* // Get all commands
|
|
2741
|
+
* const commands = useContributions('commands')
|
|
2742
|
+
* ```
|
|
2743
|
+
*/
|
|
2744
|
+
declare function useContributions<K extends keyof ContributionTypeMap>(type: K): ContributionTypeMap[K][];
|
|
2745
|
+
/**
|
|
2746
|
+
* Get all registered views
|
|
2747
|
+
*/
|
|
2748
|
+
declare function useViews(): ViewContribution[];
|
|
2749
|
+
/**
|
|
2750
|
+
* Get all registered commands
|
|
2751
|
+
*/
|
|
2752
|
+
declare function useCommands(): CommandContribution[];
|
|
2753
|
+
/**
|
|
2754
|
+
* Get all registered slash commands
|
|
2755
|
+
*/
|
|
2756
|
+
declare function useSlashCommands(): SlashCommandContribution[];
|
|
2757
|
+
/**
|
|
2758
|
+
* Get all registered sidebar items
|
|
2759
|
+
*/
|
|
2760
|
+
declare function useSidebarItems(): SidebarContribution[];
|
|
2761
|
+
/**
|
|
2762
|
+
* Get all registered editor extensions
|
|
2763
|
+
*
|
|
2764
|
+
* @throws Error if plugin system is not enabled
|
|
2765
|
+
*/
|
|
2766
|
+
declare function useEditorExtensions(): EditorContribution[];
|
|
2767
|
+
/**
|
|
2768
|
+
* Get all registered editor extensions, returns empty array if plugin system is not available
|
|
2769
|
+
*
|
|
2770
|
+
* Safe version that doesn't throw if plugins aren't enabled.
|
|
2771
|
+
*/
|
|
2772
|
+
declare function useEditorExtensionsSafe(): EditorContribution[];
|
|
2773
|
+
/**
|
|
2774
|
+
* Get a specific view by type
|
|
2775
|
+
*/
|
|
2776
|
+
declare function useView(type: string): ViewContribution | undefined;
|
|
2777
|
+
/**
|
|
2778
|
+
* Get a specific command by ID
|
|
2779
|
+
*/
|
|
2780
|
+
declare function useCommand(id: string): CommandContribution | undefined;
|
|
2781
|
+
|
|
2782
|
+
export { type AddCommentOptions, AuthErrorScreen, AuthenticatingScreen, type BlobStoreForSync, type CommentNode, type CommentThread, type ConnectionManager, type ConnectionManagerConfig, type ConnectionStatus, type DatabaseRow, type DatabaseRowData, DemoBanner, type DemoBannerProps, DemoDataExpiredScreen, type DemoLimits, type DemoModeState, DemoQuotaIndicator, type DemoQuotaIndicatorProps, type DemoUsage, type DiscoveredPeer, ErrorBoundary, type ErrorBoundaryProps, type FileRef, type FlatNode, type FlattenNodeOptions, type GrantInput, HubConnectScreen, type HubSearchOptions, type HubSearchResult, type HubSearchState, HubStatusIndicator, ImportIdentityScreen, type InitialSyncManager, type InitialSyncMessage, METABRIDGE_ORIGIN, METABRIDGE_SEED_ORIGIN, type MetaBridge, type MigrationWarning, type MutateCreate, type MutateDelete, type MutateOp, type MutateRestore, type MutateUpdate, type NodePool, type NodePoolConfig, NodeStoreSyncProvider, type NodeSyncResponse, OfflineIndicator, type OfflineIndicatorProps, type OfflineQueue, type OfflineQueueConfig, type OnboardingContextValue, type OnboardingEvent, OnboardingFlow, type OnboardingFlowProps, type OnboardingMachineContext, OnboardingProvider, type OnboardingProviderProps, type OnboardingReducerState, type OnboardingState, PluginRegistryContext, type PoolEntryState, type PresenceUser, type ProgressListener, QUICK_START_TEMPLATES, type QueryFilter, type QueryListResult, type QuerySingleResult, type QueueEntry, type QuickStartTemplate, ReadyScreen, type Registry, type RegistryConfig, type RegistryStorage, type RemoteSchemaDefinition, type RemoteSchemaState, type ReplyContext, type ReverseRelation, type SecurityContextActions, type SecurityContextState, type SecurityContextValue, SecurityProvider, type SecurityProviderProps, type SerializedNodeChange, Skeleton, type SkeletonProps, SmartWelcome, type SortDirection, type SyncManager, type SyncManagerConfig, type SyncStatus as SyncManagerStatus, type SyncPhase, type SyncProgress, SyncProgressOverlay, type SyncProgressOverlayProps, type SyncStatus$1 as SyncStatus, TelemetryContext, type TelemetryReporter, type TrackedNode, UnsupportedBrowserScreen, type UseAuditOptions, type UseAuditResult, type UseBackupReturn, type UseBlameResult, type UseCanEditResult, type UseCanResult, type UseCellOptions, type UseCellResult, type UseCommentsOptions, type UseCommentsResult, type UseDatabaseDocResult, type UseDatabaseOptions, type UseDatabaseResult, type UseDatabaseRowResult, type UseDatabaseSchemaResult, type UseDiffResult, type UseFileUploadReturn, type UseGrantsResult, type UseHistoryResult, type UseIdentityResult, type UseMutateResult, type UseNodeOptions, type UseNodeResult, type UseRelatedRowsResult, type UseReverseRelationsResult, type UseSecurityOptions, type UseSecurityResult, type UseUndoOptions, type UseUndoResult, type UseVerificationResult, WebSocketSyncProvider, type WebSocketSyncProviderOptions, WelcomeScreen, type XNetConfig, type XNetContextValue, XNetProvider, type XNetProviderProps, copyToClipboard, createConnectionManager, createInitialState, createInitialSyncManager, createMetaBridge, createNodePool, createOfflineQueue, createRegistry, createSyncManager, flattenNode, flattenNodes, flattenNodesWithSchemaCheck, flattenUnknownSchemaNode, getPlatformAuthName, injectSkeletonStyles, onboardingReducer, truncateDid, useAudit, useBackup, useBlame, useCan, useCanEdit, useCell, useCommand, useCommands, useCommentCount, useCommentCounts, useComments, useContributions, useDatabase, useDatabaseDoc, useDatabaseRow, useDatabaseSchema, useDemoMode, useDiff, useEditorExtensions, useEditorExtensionsSafe, useFileUpload, useGrants, useHistory, useHubSearch, useHubStatus, useIdentity, useIsOffline, useMutate, useNode, useOnboarding, usePeerDiscovery, usePluginRegistry, usePluginRegistryOptional, usePlugins, useQuery, useRelatedRows, useRemoteSchema, useReverseRelations, useSecurity, useSecurityContext, useSecurityContextOptional, useSidebarItems, useSlashCommands, useSyncManager, useTelemetryReporter, useUndo, useVerification, useView, useViews, useXNet };
|