Example
The server exports a capsule. The client imports its type, so queries, mutations, arguments and results are inferred.
server/index.ts
import { capsule, table, query, mutation, string } from "eraspace/server";
export default capsule({
schema: {
messages: table({ room: string(), body: string() })
.index("by_room", ["room"]),
},
queries: {
messages: query((ctx, room: string) =>
ctx.db.messages
.withIndex("by_room", q => q.eq("room", room))
.collect()),
},
mutations: {
send: mutation((ctx, room: string, body: string) =>
ctx.db.messages.insert({ room, body })),
},
});
client/index.tsx
import { createClient } from "eraspace/client";
import type app from "../server/index";
const client = createClient<typeof app>();
export default function Chat() {
const messages = client.useQuery("messages", "general"); // live
const send = client.useMutation("send");
return <>
<ul>{messages?.map(m => <li key={m.id}>{m.body}</li>)}</ul>
<button onClick={() => send("general", "hello")}>Say hello</button>
</>;
}
project layout
my-app/
eraspace.json app name
server/index.ts schema, queries, mutations, actions, endpoints
client/index.tsx Preact client
shared/ code used by both sides
.env.eraspace.server server-only env, read through ctx.env
Server files are plain ES modules. Relative imports and eraspace/server work; Node APIs and npm modules
do not. fetch, URL, timers, text encoding, console and
crypto.randomUUID are available.