إنتقل إلى المحتوى الرئيسي

Building a Full Multi-Orbital Application

Source: tests/schemas/09-full-app.orb

This tutorial walks through the complete full-app-test schema — a real application with three connected orbitals. It combines everything from the previous tutorials: entities, state machines, render-ui, guards, and cross-orbital events.

TaskTaskLifecycleTaskCRUDTaskListPage
ProjectProjectStatsProjectListPage
UserUserBrowserUserListPage
TaskManagerProjectManager~TASK_COMPLETED~ ~ ~

Application Overview

TaskManager orbital          ProjectManager orbital       UserManager orbital
entity: Task entity: Project entity: User
traits: traits: traits:
TaskLifecycle ProjectStats UserBrowser
TaskCRUD listens: pages:
pages: TASK_COMPLETED /users
/tasks TASK_CREATED
emits:
TASK_COMPLETED
TASK_CREATED

The data flow:

  1. User creates or completes a task in TaskManager
  2. TaskManager emits TASK_CREATED or TASK_COMPLETED
  3. ProjectManager listens and updates its project counters

Orbital 1: TaskManager

Entity

type Priority = low | medium | high

entity Task [persistent: tasks] {
id : string!
title : string!
description : string
priority : Priority = medium
dueDate : date
assigneeId : string
projectId : string
}

Trait 1: TaskLifecycle

Manages the task's workflow status. Emits TASK_COMPLETED when a task is approved or completed directly.

States: todo → inProgress → review → done

Key transitions:

state review {
APPROVE -> done
(emit TASK_COMPLETED { taskId: @entity.id, projectId: @entity.projectId })
}
state inProgress {
COMPLETE -> done
(emit TASK_COMPLETED { taskId: @entity.id, projectId: @entity.projectId })
}

Trait 2: TaskCRUD

Manages the list UI. Emits TASK_CREATED when a new task is saved.

States: listing → creating | editing

Key transitions:

state creating {
SAVE -> listing
(persist update Task @entity)
(emit TASK_CREATED { taskId: @entity.id, projectId: @entity.projectId })
(notify success "Task created")
}
state listing {
VIEW -> listing
(navigate "/tasks/@payload.id")
}

Pages

page "/tasks" -> TaskCRUD

Orbital-level emits

Declared inside the trait's emits block:

emits {
TASK_COMPLETED external { taskId: string, projectId: string }
TASK_CREATED external { taskId: string, projectId: string }
}

Orbital 2: ProjectManager

Entity

Tracks aggregate stats per project, updated reactively when tasks change:

entity Project [persistent: projects] {
id : string!
name : string!
description : string
taskCount : number = 0
completedCount : number = 0
}

Trait: ProjectStats

Listens to both TASK_COMPLETED and TASK_CREATED and increments counters:

trait ProjectStats -> Project [interaction] {
initial: idle
state idle {
INIT -> idle
(fetch Project)
(render-ui main { type: "stats", items: [{ label: "Total Tasks", value: "@entity.taskCount" }, { label: "Completed", value: "@entity.completedCount" }] })
TASK_CREATED -> idle
(increment @entity.taskCount 1)
TASK_COMPLETED -> idle
(increment @entity.completedCount 1)
}
listens {
* TASK_CREATED -> TASK_CREATED
* TASK_COMPLETED -> TASK_COMPLETED
}
}

The TASK_CREATED and TASK_COMPLETED events are received from TaskManager. They trigger self-loop transitions that fire increment effects — updating the project stats in real time.

Pages & orbital-level listens

The page declaration and the cross-orbital listens (wired via the trait's listens block above):

page "/projects" -> ProjectStats

Orbital 3: UserManager

The simplest orbital — a read-only browser for users with a navigate-to-detail action.

Entity

type Role = admin | member | guest

entity User [persistent: users] {
id : string!
name : string!
email : string!
role : Role = member
}

Trait: UserBrowser

trait UserBrowser -> User [interaction] {
initial: browsing
state browsing {
INIT -> browsing
(fetch User)
(render-ui main { type: "entity-table", entity: "User", columns: ["name", "email", "role"], itemActions: [{ event: "VIEW", label: "View" }] })
VIEW -> browsing
(navigate "/users/@payload.id")
}
}

Pages

page "/users" -> UserBrowser

Application Routes Summary

PathOrbitalTraitDescription
/tasksTaskManagerTaskCRUDBrowse, create, edit, delete tasks
/tasks/:idTaskManagerTaskCRUDNavigate to task detail (via navigate effect)
/projectsProjectManagerProjectStatsView project stats updated by task events
/usersUserManagerUserBrowserBrowse users, click to view detail

Patterns in This App

ConceptWhere it appears
Multiple traits per orbitalTaskManager has TaskLifecycle + TaskCRUD
Terminal statesdone in TaskLifecycle (last state with no outgoing transitions)
Cross-orbital emitTaskLifecycle emits TASK_COMPLETED, TaskCRUD emits TASK_CREATED
Cross-orbital listenProjectStats listens to both events and increments counters
Self-loop transitionsAll INIT transitions; ProjectStats event handlers
Payload in eventsVIEW carries id; TASK_COMPLETED carries taskId + projectId
navigate effectTaskCRUD's VIEW transition navigates to /tasks/@payload.id
increment effectProjectStats uses (increment @entity.taskCount 1)

Next Steps