Decentralized data infrastructure and application. Local-first, P2P-synced, user-owned data.
xNet is both the underlying infrastructure and the user-facing app — one product, one brand. It starts with documents and databases, then expands via plugins to support ERP, MCP integrations, and more.
Try It Now
Try the demo at xnet.fyi/app — no signup required, just use your device's passkey (Touch ID, Face ID, Windows Hello).
Demo mode: Your data is stored locally in your browser (local-first). Encrypted backups sync to our demo hub with a 10MB quota and expire after 24 hours of inactivity. For reliable cross-device sync and permanent backups, download the desktop app.
Deploy a Hub
Getting Started
# Install dependencies pnpm install # Run the root Storybook catalog and workbenches pnpm dev:stories # Build the static Storybook site pnpm build:stories # Build all packages pnpm build # Run unit tests (~2400 tests across 148 test files) pnpm test # Run integration tests (real browser via Playwright) pnpm --filter @xnetjs/integration-tests test # Type check pnpm typecheck # Lint pnpm lint
Component Development
xNet now ships a root Storybook workspace for isolated component development across shared UI and app-facing surfaces.
- Run
pnpm dev:storiesfrom the repo root to launch Storybook onhttp://127.0.0.1:6006. - Use
pnpm build:storiesto produce the static catalog andpnpm test:storiesto run Storybook tests against a running server. - In Electron dev builds, open the embedded Storybook surface from
Open Storiesin the system menu or command palette. - In Web dev builds, open the embedded route at
/stories.
Current Storybook coverage includes:
@xnetjs/uiprimitives, composed components, comments, settings, and devtools catalogs@xnetjs/editorrich collaborative editor workbench@xnetjs/viewsdatabase surface workbench@xnetjs/canvascanvas workbench- selected Electron and Web renderer stories
Monorepo Structure
packages/ # 21 core SDK packages (@xnetjs/*)
apps/ # Electron, Web, Expo applications
site/ # Astro + Starlight documentation website
tests/ # Browser-based integration tests (Playwright)
docs/ # Vision, explorations, implementation plans
See the README in each directory for details:
- packages/README.md -- All 21 packages with dependency graph
- apps/README.md -- Electron, Web, Expo apps
- tests/README.md -- Integration test suite
- site/README.md -- Documentation website
Packages
Foundation
| Package | Description |
|---|---|
| @xnetjs/core | Types, content addressing (CIDs), permissions, RBAC |
| @xnetjs/crypto | BLAKE3 hashing, Ed25519 signing, XChaCha20 encryption |
| @xnetjs/identity | DID:key generation, UCAN tokens, passkey storage |
Infrastructure
| Package | Description |
|---|---|
| @xnetjs/storage | SQLite/memory adapters, blob store, chunk manager, snapshots |
| @xnetjs/sync | Change<T>, Lamport clocks, hash chains, Yjs security layer |
| @xnetjs/data | Schema system, NodeStore, 15 property types, Yjs CRDT, built-in schemas |
| @xnetjs/network | libp2p node, y-webrtc provider, peer scoring, security suite |
| @xnetjs/query | Local query engine, MiniSearch full-text search, federated router |
| @xnetjs/hub | Signaling server, sync relay, backup, FTS5 search, sharding, federation |
Application
| Package | Description |
|---|---|
| @xnetjs/react | useQuery, useMutate, useNode, hub hooks, plugin hooks, sync infrastructure |
| @xnetjs/sdk | Unified client, browser/node presets, re-exports |
| @xnetjs/editor | TipTap collaborative editor, slash commands, wikilinks, drag-drop, mermaid |
| @xnetjs/ui | Radix UI primitives, composed components, theme system, design tokens |
| @xnetjs/views | Table, Board, Gallery, Timeline, Calendar views with property renderers |
| @xnetjs/canvas | Infinite canvas, R-tree spatial indexing, ELK.js auto-layout, Yjs-backed store |
| @xnetjs/devtools | 9-panel debug suite (node explorer, sync monitor, Yjs inspector, ...) |
| @xnetjs/history | Time machine, undo/redo, audit trails, blame, diff, verification |
| @xnetjs/plugins | Plugin registry, sandboxed scripts, AI generation, MCP server, webhooks |
| @xnetjs/telemetry | Privacy-preserving telemetry, tiered consent, k-anonymity, scrubbing |
| @xnetjs/formula | Expression parser, AST evaluator, built-in function library |
| @xnetjs/vectors | HNSW vector index, semantic search, hybrid keyword+semantic search |
Apps
| App | Tech | Description |
|---|---|---|
| Electron | Electron + Vite + React + TanStack Router + Tailwind | Desktop (macOS/Windows/Linux) |
| Web | Vite + React + TanStack Router + Workbox PWA | Browser progressive web app |
| Expo | Expo SDK 52 + React Native + React Navigation | Mobile (iOS/Android) |
Architecture
flowchart TB
subgraph Apps["Applications"]
Electron["Electron<br/>(Desktop)"]
Web["Web PWA"]
Expo["Expo<br/>(Mobile)"]
end
subgraph UI["UI Layer"]
Editor["@xnetjs/editor<br/><small>TipTap, slash commands,<br/>drag-drop, mermaid</small>"]
Views["@xnetjs/views<br/><small>Table, Board, Calendar,<br/>Timeline, Gallery</small>"]
Canvas["@xnetjs/canvas<br/><small>Infinite canvas,<br/>R-tree, ELK.js</small>"]
UILib["@xnetjs/ui<br/><small>Radix primitives,<br/>theme system</small>"]
end
subgraph Client["Client Layer"]
React["@xnetjs/react<br/><small>useQuery, useMutate,<br/>useNode, SyncManager</small>"]
SDK["@xnetjs/sdk<br/><small>Unified client,<br/>browser/node presets</small>"]
Devtools["@xnetjs/devtools<br/><small>9-panel debug suite</small>"]
Plugins["@xnetjs/plugins<br/><small>Registry, sandbox,<br/>AI generation, MCP</small>"]
Telemetry["@xnetjs/telemetry<br/><small>Privacy-first,<br/>consent-gated</small>"]
History["@xnetjs/history<br/><small>Time machine,<br/>undo/redo, audit</small>"]
end
subgraph Data["Data Layer"]
DataPkg["@xnetjs/data<br/><small>Schema system, NodeStore,<br/>Yjs CRDT, 15 property types</small>"]
Query["@xnetjs/query<br/><small>Local engine,<br/>MiniSearch FTS</small>"]
Vectors["@xnetjs/vectors<br/><small>HNSW index,<br/>hybrid search</small>"]
Formula["@xnetjs/formula<br/><small>Expression parser,<br/>computed properties</small>"]
end
subgraph Infra["Infrastructure Layer"]
Sync["@xnetjs/sync<br/><small>Change<T>, Lamport clocks,<br/>hash chains, Yjs security</small>"]
Storage["@xnetjs/storage<br/><small>SQLite, blobs,<br/>snapshots</small>"]
Network["@xnetjs/network<br/><small>libp2p, y-webrtc,<br/>peer scoring</small>"]
end
subgraph Foundation["Foundation"]
Identity["@xnetjs/identity<br/><small>DID:key, UCAN tokens,<br/>passkey storage</small>"]
Crypto["@xnetjs/crypto<br/><small>BLAKE3, Ed25519,<br/>XChaCha20</small>"]
Core["@xnetjs/core<br/><small>CIDs, types,<br/>permissions, RBAC</small>"]
end
Apps --> UI & Client
UI --> Client
Client --> Data
Data --> Infra
Infra --> Foundation
subgraph Server["Server"]
Hub["@xnetjs/hub<br/><small>Signaling, sync relay,<br/>backup, FTS5, federation</small>"]
end
Network <--> Hub
Hybrid Sync Model
flowchart LR
subgraph Local["Local Device"]
UI["UI"]
NodeStore["NodeStore<br/>(structured data)"]
YDoc["Y.Doc<br/>(rich text)"]
DB[("SQLite")]
end
subgraph Remote["Remote Peers"]
Peer1["Peer<br/>(desktop)"]
Peer2["Peer<br/>(mobile)"]
HubNode["Hub<br/>(always-on)"]
end
UI -->|"mutate()"| NodeStore
UI -->|"edit"| YDoc
NodeStore -->|"Change<T><br/>LWW"| DB
YDoc -->|"Yjs updates<br/>CRDT"| DB
Local <-->|"WebRTC / WebSocket"| Remote
Data Model
Everything is a Node (universal container). A Schema defines what the Node is.
import { defineSchema, text, number, select } from '@xnetjs/data' const InvoiceSchema = defineSchema({ name: 'Invoice', namespace: 'xnet://myapp/', document: 'yjs', properties: { title: text({ required: true }), amount: number(), status: select({ options: [ { id: 'draft', name: 'Draft' }, { id: 'sent', name: 'Sent' }, { id: 'paid', name: 'Paid' } ] as const }) } })
Sync Strategies
| Data Type | Sync Mechanism | Conflict Resolution |
|---|---|---|
| Rich text (documents) | Yjs CRDT | Character-level merge |
| Structured data (nodes) | NodeStore + Lamport | Field-level LWW |
Identity, Roles, and Authorization
xNet schemas can include an authorization block that maps identity (DID) to roles and actions.
- Identity: Every node has
createdBy: did:key:... - Roles: Resolved from creator, person properties, or related nodes
- Actions: Canonical actions are
read,write,delete,share,admin - Delegation: Grants (UCAN-backed) allow secure permission delegation
const ProjectDocSchema = defineSchema({ name: 'ProjectDoc', namespace: 'xnet://myapp/', document: 'yjs', properties: { title: text({ required: true }), owner: person(), editors: person(), project: relation({ schema: 'xnet://myapp/Project@1.0.0' }) }, authorization: { roles: { owner: role.creator(), editor: role.property('editors'), projectAdmin: role.relation('project', 'admin') }, actions: { read: allow('owner', 'editor', 'projectAdmin'), write: allow('owner', 'editor', 'projectAdmin'), delete: allow('owner', 'projectAdmin'), share: allow('owner', 'projectAdmin'), admin: allow('projectAdmin') }, publicProps: ['title'] } })
Authorization builders (
allow,role, etc.) come from the data auth module and compile to schema-level policy metadata.
Primary React Hooks
import { useQuery, useMutate, useNode, useIdentity, useCan, useGrants } from '@xnetjs/react' // Structured data function TaskList() { const { data: tasks, loading } = useQuery(TaskSchema) const { create, update, remove } = useMutate() return ( <ul> {tasks.map((task) => ( <li key={task.id}>{task.title}</li> ))} <button onClick={() => create(TaskSchema, { title: 'New', status: 'todo' })}>Add</button> </ul> ) } // Rich text with Yjs CRDT function PageEditor({ nodeId }: { nodeId: string }) { const { data: page, doc, syncStatus, peerCount } = useNode(PageSchema, nodeId) if (!doc) return null return <RichTextEditor ydoc={doc} /> } // Identity + permissions function SharingPanel({ nodeId }: { nodeId: string }) { const { did } = useIdentity() const { allowed: canShare } = useCan(nodeId, 'share') const { grant } = useGrants(nodeId) return ( <button disabled={!canShare} onClick={() => canShare && grant({ to: 'did:key:z6MkRecipient...', actions: ['read', 'write'] }) } > Share as {did.slice(0, 16)}... </button> ) }
Hook Quick Reference
| Hook | Use for |
|---|---|
useQuery |
Read lists/single nodes with realtime updates |
useMutate |
Create/update/delete/transactional writes |
useNode |
Rich text nodes (Y.Doc), sync status, presence |
useIdentity |
Current DID identity context |
useCan |
Check action permissions on a node |
useCanEdit |
Quick editable state for UI gating |
useGrants |
Grant, revoke, and inspect delegated access |
Key Technologies
| Layer | Technology |
|---|---|
| Sync | Event-sourced immutable logs, Lamport clocks, LWW |
| CRDT | Yjs (conflict-free collaboration) |
| P2P | libp2p + WebRTC |
| Storage | SQLite (OPFS in browser, native on desktop/mobile) |
| Identity | DID:key + UCAN authorization |
| Signing | Ed25519 (via @noble/curves) |
| Hashing | BLAKE3 (via @noble/hashes) |
| Encryption | XChaCha20-Poly1305 |
| Search | MiniSearch (local), FTS5 (hub) |
| Build | Turborepo, tsup, Vite |
| Testing | Vitest, Playwright (browser mode) |
Roadmap Status (Mar 2026)
| Phase | Focus | Status |
|---|---|---|
| Phase 1 | Product reliability (navigation, search, daily-driver polish) | In progress |
| Phase 2 | Collaboration + trust (invites, sharing UX, presence reliability) | Next |
| Phase 3 | Platform clarity (package lifecycle, API simplification, multi-hub integration) | Planned |
See docs/ROADMAP.md for the detailed execution plan.
Documentation
- Site -- Astro + Starlight documentation website
- Vision -- The big picture: micro-to-macro data sovereignty
- Tradeoffs -- Why hybrid sync (Yjs + event sourcing)
- Roadmap -- current 6-month execution plan (Mar-Sep 2026)
License
MIT