@tumbati/umodzi
TypeScript SDK for Umodzi — a thin client for talking to a running Umodzi node's local HTTP API. Works in Node 18+, modern browsers, and React Native.
The node is the source of truth — this SDK never embeds one. If you need an in-process node, use the JVM :sdk:kotlin module via UmodziEngine.
Install
npm install @tumbati/umodziZero runtime dependencies. Native fetch only.
Usage
import { UmodziClient } from '@tumbati/umodzi'
const client = new UmodziClient({ baseUrl: 'http://localhost:8080' })
const event = await client.emit(
'CREATE_PATIENT',
JSON.stringify({ entityId: 'p-001', name: 'Chimwemwe' })
)
console.log(`Persisted ${event.id} at clock`, event.vectorClock)
// Read everything, or only what's new since a known clock.
const all = await client.getEvents()
const newer = await client.getEvents(event.vectorClock)
// Subscribe to live updates over Server-Sent Events. Replays everything since
// the supplied clock (or from the beginning when omitted), then yields live
// events as they're persisted on the node.
const ctrl = new AbortController()
;(async () => {
for await (const e of client.events(undefined, { signal: ctrl.signal })) {
console.log('new event:', e.type, e.id)
}
})()
// later: ctrl.abort()
// Inspect.
const status = await client.getStatus()
const peers = await client.getPeers()
// Push a sync round (defaults to all reachable peers).
await client.triggerSync()
await client.triggerSync('REG-01') // or target a single peerAPI surface
| Method | HTTP |
|---|---|
emit(type, payload, schemaVersion?) |
POST /events |
getEvents(since?) |
GET /events[?since=<encoded VectorClock>] |
getEvent(id) |
GET /events/{id} — returns null on 404 |
events(since?, { signal? }) |
GET /events/stream — SSE async iterable, replay then live |
getPeers() |
GET /peers |
getStatus() |
GET /status |
triggerSync(peerId?) |
POST /sync/trigger |
close() |
no-op (kept for symmetry with the Kotlin SDK) |
deviceId is server-stamped from the running node's id. The SDK does not let you set it; supplying one in raw HTTP gets rejected with 403.
Errors
Non-2xx responses throw UmodziHttpError with status and body properties:
import { UmodziHttpError, UmodziVersionMismatchError } from '@tumbati/umodzi'
try {
await client.emit('X', 'invalid')
} catch (err) {
if (err instanceof UmodziHttpError) {
console.error(err.status, err.body)
} else if (err instanceof UmodziVersionMismatchError) {
console.error(`Node ${err.actual} is older than required ${err.required}`)
} else throw err
}Version compatibility check
By default, on the first call after construction the SDK fetches /status and verifies the node's version is at least MIN_NODE_VERSION. If older, all subsequent calls throw UmodziVersionMismatchError until you upgrade the node. The result is cached for the client's lifetime — there's only one extra round-trip.
Disable with verifyVersion: false (useful in tests, or when talking to a pre-v0.0.2 node that doesn't carry the field).
Custom fetch
For tests, polyfills, or interceptors:
new UmodziClient({
fetch: (url, init) => myInstrumentedFetch(url, init)
})What's not here yet
- Offline emit queue — semantics are still being designed (see RFC-005).
- Generated types from OpenAPI — the node now serves
GET /openapi.json(Phase 10b). Switchingsrc/types.tsfrom hand-maintained toopenapi-typescript-generated is a follow-up.
Develop
cd sdk/js
npm install
npm test # vitest run
npm run typecheck # tsc --noEmit
npm run build # tsup → dist/index.{js,cjs,d.ts}License
MIT