Hono JSX hooks for building real-time collaborative applications with Loro CRDT documents.
This package provides Hono JSX-specific bindings for Loro CRDT documents with a Doc-first pattern:
useDocument- Get a typed document with sync capabilitiesuseValue- Subscribe to document or ref changesusePlaceholder- Get placeholder values from schema definitionsuseEphemeral- Subscribe to ephemeral store changes (presence, cursors, etc.)
This package mirrors the API of @loro-extended/react but uses Hono's JSX implementation (hono/jsx) instead of React.
- Type Safety - Full TypeScript support with schema-driven type inference
- Fine-Grained Reactivity - Subscribe to entire documents or specific refs
- Unified Sync - Use
sync(doc)for sync capabilities likewaitForSync()and presence - Placeholder Support - Extract placeholders defined in your schema
npm install @loro-extended/hono @loro-extended/change @loro-extended/repo loro-crdt hono
# or
pnpm add @loro-extended/hono @loro-extended/change @loro-extended/repo loro-crdt honoimport { Shape, useDocument, useValue, RepoProvider } from "@loro-extended/hono"
import { useMemo } from "hono/jsx"
import { render } from "hono/jsx/dom"
import type { RepoParams } from "@loro-extended/repo"
// Define your document schema
const CounterSchema = Shape.doc({
count: Shape.counter(),
})
function Counter() {
// Get a typed document
const doc = useDocument("counter", CounterSchema)
// Subscribe to document changes
const snapshot = useValue(doc)
const increment = () => {
doc.count.increment(1)
}
const decrement = () => {
doc.count.decrement(1)
}
return (
<div>
<div>{snapshot.count}</div>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
)
}
// Wrap your app with RepoProvider
function App() {
const config = useMemo<RepoParams>(() => ({
identity: { name: "user-1", type: "user" },
adapters: [/* your adapters */],
}), [])
return (
<RepoProvider config={config}>
<Counter />
</RepoProvider>
)
}
render(<App />, document.getElementById("root")!)Returns a typed Doc for the given document. The document is created synchronously and provides direct access to typed refs.
import { sync } from "@loro-extended/repo"
// Without ephemeral stores
const doc = useDocument(docId, docSchema)
// With ephemeral stores (e.g., presence)
const doc = useDocument(docId, docSchema, { presence: PresenceSchema })
// Access sync capabilities
await sync(doc).waitForSync()
sync(doc).presence.setSelf({ status: "online" })Parameters:
docId: DocId- The document identifierdocSchema: DocShape- The document schemaephemeralDeclarations?: EphemeralDeclarations- Optional ephemeral store declarations
Returns: Doc<D, E> - A typed document with direct ref access
Subscribes to document or ref changes and returns the current value.
// Full document snapshot
const snapshot = useValue(doc)
// Single ref value (fine-grained updates)
const count = useValue(doc.count)
const title = useValue(doc.title)
const items = useValue(doc.items)Parameters:
docOrRef: Doc | TypedRef- A document or typed ref
Returns: The current value (type inferred from input)
Returns the placeholder value defined in the schema for a ref.
const placeholder = usePlaceholder(doc.title) // From Shape.text().placeholder("...")Parameters:
ref: TypedRef- A typed ref with a placeholder defined
Returns: The placeholder value or undefined
Subscribes to any ephemeral store and returns the current state. This is the preferred way to subscribe to presence and other ephemeral data.
import { sync } from "@loro-extended/repo"
// For presence
const { self, peers } = useEphemeral(sync(doc).presence)
// Update your value
sync(doc).presence.setSelf({ cursor: { x: 100, y: 200 } })Parameters:
ephemeral: TypedEphemeral<T>- A typed ephemeral store
Returns: { self: T | undefined, peers: Map<string, T> }
Returns the Repo instance from context.
const repo = useRepo()
const myPeerId = repo.identity.peerIdimport { sync } from "@loro-extended/repo"
const DocSchema = Shape.doc({
content: Shape.text(),
})
const PresenceSchema = Shape.plain.struct({
cursor: Shape.plain.struct({
x: Shape.plain.number(),
y: Shape.plain.number(),
}),
name: Shape.plain.string().placeholder("Anonymous"),
})
function CollaborativeEditor() {
const doc = useDocument("doc", DocSchema, { presence: PresenceSchema })
const snapshot = useValue(doc)
const { self, peers } = useEphemeral(sync(doc).presence)
const handleMouseMove = (e: MouseEvent) => {
sync(doc).presence.setSelf({
cursor: { x: e.clientX, y: e.clientY }
})
}
return (
<div onMouseMove={handleMouseMove}>
<div>Content: {snapshot.content}</div>
<div>
Users: {self?.name}, {Array.from(peers.values()).map(p => p.name).join(", ")}
</div>
</div>
)
}import { RepoProvider } from "@loro-extended/hono"
import { IndexedDBStorageAdapter } from "@loro-extended/adapter-indexeddb"
import { SseClientNetworkAdapter } from "@loro-extended/adapter-sse/client"
import { useMemo } from "hono/jsx"
function App() {
const config = useMemo(() => ({
identity: { name: "user-1", type: "user" },
adapters: [
new IndexedDBStorageAdapter(),
new SseClientNetworkAdapter({
postUrl: () => "/sync/post",
eventSourceUrl: (peerId) => `/sync/subscribe?peerId=${peerId}`,
}),
],
}), [])
return (
<RepoProvider config={config}>
<YourApp />
</RepoProvider>
)
}This package is nearly identical to @loro-extended/react, with the following differences:
- Uses
hono/jsxinstead ofreactfor JSX runtime - Uses Hono's
useMemo,useEffect,useSyncExternalStore, etc. fromhono/jsx - Designed for Hono-based applications and edge runtimes
The API surface is intentionally kept the same to make it easy to switch between React and Hono implementations.
For a full collaborative Hono application, see the Hono Counter Example which demonstrates:
- Setting up the Repo with network adapters
- Using
useDocumentanduseValuefor reactive document state - Building collaborative UI components with Hono JSX
- Hono 4+
- TypeScript 5+ (recommended)
- A Repo instance from
@loro-extended/repo
@loro-extended/hooks-core- Framework-agnostic hook implementations@loro-extended/change- Schema definitions and typed operations@loro-extended/repo- Document synchronization and storage@loro-extended/react- React version of these hooks- Network adapters:
@loro-extended/adapter-sse,@loro-extended/adapter-websocket,@loro-extended/adapter-webrtc - Storage adapters:
@loro-extended/adapter-indexeddb,@loro-extended/adapter-leveldb,@loro-extended/adapter-postgres
MIT