Documents and schemas
Define game-owned data while Aether owns identity, permissions, revisions, and validation.
A document is Aether's atomic synchronized record. The engine owns its id, type, name, owner,
permission map, revision, timestamps, and capability data. A system owns the shape of its system
field through a DocumentSchema.
#Define one type
import type { DocumentSchema } from "@aether/core";
import { z } from "zod";
const character: DocumentSchema = {
type: "my-system.character",
label: "Character",
icon: "UserRound",
data: z.object({
strength: z.number().int().min(1).max(30),
hp: z.object({ value: z.number().int(), max: z.number().int().positive() }),
notes: z.string(),
}),
makeDefault: () => ({
strength: 10,
hp: { value: 10, max: 10 },
notes: "",
}),
capabilities: ["actor", "linkable", "foldered", "effects"],
};Use a namespaced, stable type. makeDefault must satisfy data, because the server uses that
default when creating a document. The server validates later patches against the same schema.
#Compose engine capabilities
Capabilities add validated engine-owned slices under caps. They do not change your system data.
For example, foldered supplies folder membership, pageable supplies pages, actor supplies a
prototype token, and placeable supplies scene placement. The generated
document capability reference lists the registry in this
checkout.
Only declare a capability the type actually uses. The capability's schema and write policy then apply on create and patch.
#Contribute the schema
Return the schema from the active system:
export default defineSystem({
manifest,
setup() {
return { documents: [character] };
},
});Duplicate document type ids fail registry construction. An unknown type fails validation rather than becoming an untyped record.
#Evolving data
Changing a Zod schema does not migrate documents already stored in a world. Keep additions backward-compatible or provide a deliberate migration in the application that owns the deployment. There is no public package migration API yet. Treat removing or renaming a required field as a data migration, not as a sheet-only edit.
The maintained implementation is systems/demo-system/src/index.ts. Verify it with
bun run --filter @aether/demo-system typecheck.