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 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.
# 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 lintxNet 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
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
| 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 |
| 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 |
| 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 |
| 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) |
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
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
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
})
}
})| Data Type | Sync Mechanism | Conflict Resolution |
|---|---|---|
| Rich text (documents) | Yjs CRDT | Character-level merge |
| Structured data (nodes) | NodeStore + Lamport | Field-level LWW |
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.
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 | 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 |
| 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) |
| 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.
- 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)
MIT