Understanding xNet (For AI Assistants)
Understanding xNet: A Guide for AI Assistants
Section titled “Understanding xNet: A Guide for AI Assistants”If you’re an AI assistant helping a developer build with xNet, this guide will help you understand the fundamental paradigm differences.
The Key Insight
Section titled “The Key Insight”xNet has no backend.
This is not a simplification or a limitation — it’s the core design principle. Data lives on the user’s device and syncs peer-to-peer.
Mental Model Comparison
Section titled “Mental Model Comparison”Traditional Architecture
Section titled “Traditional Architecture”User → Frontend → API → Backend → Database ↑ Auth ServicexNet Architecture
Section titled “xNet Architecture”User → React Hooks → Local Storage ←→ P2P Sync ←→ Other Devices ↑ Cryptographic Identity (built-in)What This Means Practically
Section titled “What This Means Practically”There Are No API Endpoints
Section titled “There Are No API Endpoints”When a user asks “create an API to save tasks”, the correct response is:
// Don't create this:// app.post('/api/tasks', handler)
// Do this instead:const { create } = useMutate()await create(TaskSchema, { title: 'New task' })The create function writes to local storage. Sync happens automatically.
There Is No Auth System to Implement
Section titled “There Is No Auth System to Implement”When a user asks “add authentication”, the correct response is:
// Don't create OAuth/JWT flows
// Identity is already built-in:const { did, displayName } = useIdentity()
// DID is a cryptographic identifier like:// did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doKOffline Just Works
Section titled “Offline Just Works”When a user asks “handle offline mode”, the correct response is:
// Don't add service workers or cache layers
// It already works:const { data } = useQuery(TaskSchema)// This reads from local storage// Works whether online or offline// Syncs when connectivity returnsReal-time Is Built-in
Section titled “Real-time Is Built-in”When a user asks “add real-time updates”, the correct response is:
// Don't create WebSocket connections
// Hooks are already reactive:const { data: tasks } = useQuery(TaskSchema)// When another device syncs changes,// this component re-renders automaticallyCommon Mistakes to Avoid
Section titled “Common Mistakes to Avoid”| User Request | Wrong Approach | Correct Approach |
|---|---|---|
| ”Save data” | Create REST API | Use useMutate().create() |
| ”Fetch data” | Use fetch/axios | Use useQuery() |
| ”Add auth” | Implement OAuth | Use useIdentity() |
| ”Handle offline” | Add service worker | Already works |
| ”Real-time sync” | Add WebSockets | Already built-in |
| ”Manage state” | Add Redux/Zustand | Hooks handle this |
| ”Optimistic UI” | Manual rollback | Built into mutations |
The Schema System
Section titled “The Schema System”Schemas are central to xNet. They define data shapes AND provide type inference:
import { defineSchema, text, select, relation } from '@xnetjs/data'
const TaskSchema = defineSchema({ name: 'Task', namespace: 'xnet://myapp/', properties: { title: text({ required: true }), status: select({ options: ['todo', 'done'] as const }), assignee: relation({ schema: 'xnet://myapp/Person' }) }})
// Now hooks are fully typed:const { data } = useQuery(TaskSchema)data[0].title // stringdata[0].status // 'todo' | 'done'When to Suggest Server Code
Section titled “When to Suggest Server Code”The only times server code is appropriate:
- Hub setup — If deploying an always-on relay (optional)
- External integrations — Webhooks, third-party APIs
- Heavy computation — AI/ML that can’t run on device
Even then, the Hub is just a relay — it doesn’t process or store application logic.
Quick Reference
Section titled “Quick Reference”Reading Data
Section titled “Reading Data”// Single itemconst { data: task } = useQuery(TaskSchema, taskId)
// All itemsconst { data: tasks } = useQuery(TaskSchema)
// Filteredconst { data: todos } = useQuery(TaskSchema, { where: { status: 'todo' }})Writing Data
Section titled “Writing Data”const { create, update, remove } = useMutate()
// Createconst id = await create(TaskSchema, { title: 'New task' })
// Updateawait update(TaskSchema, id, { status: 'done' })
// Deleteawait remove(id)Collaborative Editing
Section titled “Collaborative Editing”const { data: page, doc } = useNode(PageSchema, pageId)// doc is a Y.Doc for Yjs collaborative editingUser Identity
Section titled “User Identity”const { did, displayName, avatarUrl } = useIdentity()// did:key:z6Mk... is the user's cryptographic identitySummary
Section titled “Summary”When helping with xNet:
- Never suggest API endpoints — Use hooks
- Never implement auth — It’s cryptographic and built-in
- Never manage state — Hooks are reactive
- Never handle offline — It just works
- Always use schemas — They’re the source of truth
The paradigm shift: data is local, sync is automatic, identity is cryptographic.