Architecture Overview
Layered architecture
Section titled “Layered architecture”xNet is organized as a layered stack where each package builds on the ones below it. Lower packages never import from higher ones.
┌─────────────────────────────────────────┐│ apps/electron · apps/web │ Applications├─────────────────────────────────────────┤│ @xnet/react │ React bindings│ useQuery · useMutate · useNode ││ useIdentity · XNetProvider │├─────────────────────────────────────────┤│ @xnet/data │ Data layer│ defineSchema · NodeStore ││ 16 property types · validation │├─────────────────────────────────────────┤│ @xnet/sync │ Sync primitives│ Lamport clocks · Change<T> · chains ││ Yjs security · envelopes · scoring │├─────────────────────────────────────────┤│ @xnet/storage │ Persistence│ IndexedDB adapter │├─────────────────────────────────────────┤│ @xnet/identity │ Identity│ DID:key · UCAN · key management │├─────────────────────────────────────────┤│ @xnet/crypto │ Cryptography│ BLAKE3 · Ed25519 · XChaCha20 │└─────────────────────────────────────────┘Additional packages
Section titled “Additional packages”| Package | Purpose |
|---|---|
@xnet/plugins | 4-layer plugin system (scripts, extensions, services, integrations) |
@xnet/canvas | Infinite canvas with spatial indexing |
@xnet/editor | TipTap rich text editor with Yjs collaboration |
@xnet/network | libp2p node, WebRTC/WebSocket transport, peer security |
@xnet/devtools | 7 debug panels for inspecting sync, store, schema, and more |
Design principles
Section titled “Design principles”Minimal coupling — Each package has a focused responsibility. The crypto package knows nothing about schemas. The data package knows nothing about React.
No default exports — Everything is a named export. Barrel files (index.ts) re-export from internal modules.
Factory functions — Classes are paired with factory functions (createFoo() alongside class Foo). This keeps the API surface consistent and makes testing easier.
Immutability — Core types like LamportTimestamp and Change<T> are treated as immutable. Functions return new objects instead of mutating in place.
Validation over exceptions — Functions return { valid: boolean, errors: [] } objects instead of throwing. Exceptions are reserved for programmer errors, not data validation.
How data flows
Section titled “How data flows”Write path
Section titled “Write path”flowchart LR A["User action"] --> B["useMutate()"] B --> C["NodeStore.update()"] C --> D["Change‹T› + Lamport"] D --> E["BLAKE3 hash"] E --> F["Ed25519 sign"] F --> G["Hash chain link"] G --> H["IndexedDB"] G --> I["SyncManager"]
Read path
Section titled “Read path”flowchart LR A["useQuery(schema, filter)"] --> B["NodeStore.query()"] B --> C["IndexedDB read"] C --> D["FlatNode[]"] D --> E["Component render"] B -.->|subscribe| F["Change events"] F -.->|re-render| E
Sync path
Section titled “Sync path”flowchart LR A["Y.Doc update"] --> B["YjsBatcher\n(2s window)"] B --> C["signYjsUpdate()"] C --> D["ConnectionManager\n.publish()"] D --> E["WebSocket"] E --> F["Signaling server"] F --> G["Peer WebSocket"] G --> H["Rate limit"] H --> I["Verify signature"] I --> J["Apply to Y.Doc"]
Further reading
Section titled “Further reading”- Package Graph — The full dependency graph
- Architecture Decisions — Why we made these choices