Skip to content

Use xNet From Any Framework

The React hooks (useQuery / useMutate / useNode) are a thin binding over a headless runtime, not the only way in. @xnetjs/runtime’s createXNetClient() constructs and owns the entire data model — authorization, validation, signing, encryption, SQLite-backed storage, and the live query/mutate/subscribe loop — and imports React nowhere:

import { createXNetClient } from '@xnetjs/runtime'
import { MemoryNodeStorageAdapter } from '@xnetjs/data'
import { generateIdentity } from '@xnetjs/identity'
const { identity, privateKey } = generateIdentity()
const client = await createXNetClient({
nodeStorage: new MemoryNodeStorageAdapter(), // or a SQLite adapter
authorDID: identity.did,
signingKey: privateKey
})

client.query(schema, options) returns the universal { getSnapshot, subscribe } contract — the exact pair that React’s useSyncExternalStore, a Svelte store, a Vue shallowRef, a Solid signal, and Angular’s toSignal all consume. liveQuery() wraps that pair as a Svelte store (subscribe(run) => unsubscribe, called immediately and on every change), which every other framework can adapt in a few lines.

import { shallowRef, onScopeDispose } from 'vue'
import { liveQuery } from '@xnetjs/runtime'
export function useQuery(client, schema, options) {
const lq = liveQuery(client, schema, options)
const data = shallowRef(lq.get())
const stop = lq.subscribe((v) => (data.value = v))
onScopeDispose(() => {
stop()
lq.destroy()
})
return data // Ref<NodeState[] | null>
}
export function useMutate(client) {
return client.mutate // { create, update, delete, restore, transaction }
}

The expensive part of multi-framework support isn’t writing a binding — it’s proving every binding behaves identically, forever. So the behaviour is tested once, framework-agnostically. runAdapterConformance(makeClient) runs the reactive data contract against any client factory and throws on the first failed check:

import { runAdapterConformance } from '@xnetjs/runtime'
import { MemoryNodeStorageAdapter } from '@xnetjs/data'
await runAdapterConformance((overrides) =>
createXNetClient({
nodeStorage: new MemoryNodeStorageAdapter(),
authorDID,
signingKey,
...overrides
})
)

It asserts that:

  • a live query delivers an immediate snapshot, then updates on mutate
  • it stops delivering after unsubscribe
  • a one-shot fetch round-trips a write
  • the authorization surface is reachable and denial surfaces
  • destroy() is idempotent

A framework adapter reuses this suite and adds only a tiny render-harness test (“the ref/store/signal reflects the snapshot and re-renders”) — it never re-tests xNet’s semantics.

Binding the data layer is cheap; carrying a full per-framework component library is not. So support is layered, and the layers promise different things:

TierSurfaceWhat you get
Tier 0@xnetjs/runtimecreateXNetClient + liveQueryWorks with every framework, headless and conformance-gated. This page.
Tier 1@xnetjs/react — hooks and componentsFirst-class: the full hook + component toolkit the app itself uses.
Tier 2thin Vue / Svelte data-binding adaptersuseQuery / useMutate only — data binding, not components — published on demand.
components in non-React frameworksNot offered. The component kit is React by design.

See docs/explorations/0237 for the full cost/benefit analysis behind this policy.

Not using JavaScript at all? xNet also has native Swift and Rust implementations of the protocol. See Languages & SDKs for the full picture.