@abrum/web-runtime
v0.1.1
Published
Browser-facing abrum runtime for typed, signed twins over a station.
Downloads
64
Readme
@abrum/web-runtime
Browser-facing abrum runtime for typed, signed twins over a station.
The runtime keeps the kernel model intact: data is still signed twins, schemas are schema twins, and Rooms remain the sync, storage, and access boundary. Physical membership comes from the per-Room store; relations describe semantic graph edges. The developer API hides the normal boilerplate so app code can work with typed local data.
Core Shape
import { abrum } from "@abrum/web-runtime";
export const notesApp = abrum.app("notes", {
note: abrum.entity({
text: abrum.string(),
createdAtMs: abrum.date().index(),
updatedAtMs: abrum.date(),
}).named("schema-note-v1").type("note").roomScoped(),
});This compiles to:
- a schema twin for
schema-note-v1 - object twins with
content.type === "note" - JSON Schema metadata for required fields, indexes, unique fields, and abrum app identity
Normal app code should not manually create schema or object twins. Create a
contains relation only when the application models an actual hierarchy or a
selectively shared subtree.
Field Builders
abrum.string()
abrum.number()
abrum.boolean()
abrum.date()
abrum.json()
abrum.any()Modifiers:
abrum.string().optional()
abrum.string().index()
abrum.string().indexed()
abrum.string().unique()Notes:
- Fields are required by default.
abrum.date()accepts a timestamp, ISO string, orDate;Dateis stored as an ISO string.abrum.number()currently accepts safe integers because canonical twin blocks require safe integers..index()and.unique()are schema metadata today; station-backed indexes are the next layer.
Runtime Usage
import { AbrumRuntime } from "@abrum/web-runtime";
import { notesApp } from "./notes.schema";
const runtime = await AbrumRuntime.open({
station: "http://127.0.0.1:8792",
storageKey: "abrum.notes.owner:http://127.0.0.1:8792",
});
await runtime.createUser("Robin"); // local Device Key; no recovery phrase
await runtime.pair("Robin");
await runtime.createEntity(notesApp, "note", {
text: "hello",
createdAtMs: Date.now(),
updatedAtMs: Date.now(),
});createEntity creates and pushes the schema twin, object twin, and space
containment relation in one call.
Rust/WASM Core
The public TypeScript API is not meant to become a second protocol implementation. Protocol-critical work should live in Rust:
- identity derivation
- canonical TwinBlock serialization
- CID calculation
- Ed25519 signing
- signed HTTP request payloads
- X25519 identity derivation
- HKDF Room-key derivation
- XChaCha20-Poly1305 seal/open, key wrap/unwrap, and conveyed secrets
- schema/object/relation/user-principal twin constructors
crates/abrum-wasm exposes those abrum-core functions as a small WASM ABI. The
browser runtime accepts a protocol core, so app code does not change when the
generated WASM package is loaded:
import init, * as wasm from "@abrum/wasm";
import { AbrumRuntime, createAbrumWasmProtocolCore } from "@abrum/web-runtime";
await init();
const runtime = await AbrumRuntime.open({
station: "http://127.0.0.1:8792",
core: createAbrumWasmProtocolCore(wasm),
});Until a generated WASM package is installed, the runtime keeps a TypeScript
fallback with the same AbrumProtocolCore interface. That fallback is a
development convenience, not the long-term source of truth.
React Usage
@abrum/react builds on this package:
import { useAbrumDb } from "@abrum/react";
import { notesApp } from "./notes.schema";
export function Notes() {
const db = useAbrumDb(notesApp);
const notes = db.note.useMany({
order: ["createdAtMs", "desc"],
});
async function addNote(text: string) {
await db.note.create({
text,
createdAtMs: Date.now(),
updatedAtMs: Date.now(),
});
}
return notes.data.map((note) => (
<article key={note.$.cid}>
{note.text}
</article>
));
}The query is realtime by default. The runtime subscribes to station events and the React layer incrementally pulls twin-log entries when the station reports a change.
Query Options
const recent = db.note.useMany({
where: {
text: { contains: "hello" },
createdAtMs: { gt: Date.now() - 86_400_000 },
},
order: ["createdAtMs", "desc"],
limit: 50,
});Current query evaluation is client-side over the readable twin log for the selected space. This is correct for the first web runtime, but large production spaces need station projections and indexes behind the same API.
Raw Escape Hatches
The high-level API is the default path. The raw API remains available for system code, migrations, custom protocols, and debugging:
const schema = await runtime.schemaTwin("custom-schema", jsonSchema);
const [object] = await runtime.objectInRoomTwins(schema.cid, content);
const relation = await runtime.relationTwin({
source: object.cid,
target: otherCid,
relationType: "references",
inheritsAccess: false,
});
await runtime.putTwins([schema, object, relation]);
const log = await runtime.pull();Identity And Spaces
The runtime owns the browser user identity for the current storage key:
const { user } = await runtime.createUser("Robin");
// Product sessions are enrolled by another active ABRUM device and handed to
// the app by its trusted host. `restoreUser(phrase)` remains only as a one-time
// compatibility import for historical mnemonic-as-seed browser sessions.Pairing a user with a station creates the user's hosted space and stores the selected space:
await runtime.pair("Robin");
await runtime.loadSpaces();
runtime.selectSpace(spaceId);Rooms are the default access and sync boundary. roomScoped() entities are
stored in the selected Room and inherit its root authorization from physical
Room membership; no technical relation Twin is generated.
Production Direction
This package is intentionally small. The standard-worthy direction is:
- app schemas as code
- typed entity create/query/update/delete
- realtime by default
- raw twin escape hatches
- station-backed indexes and projections behind the same query API
- production schema lock so unknown app schemas cannot be created by arbitrary clients
- relation and action DSLs built on the same app descriptor
The kernel should stay twin-native. Application code should stay data-native.
