Write a program, validate it, verify it and run it, in five minutes.
This walkthrough builds a counter: one orbital with one trait. It is small, but it is a complete Orb program, and every step is the same for a large one.
Create counter.lolo:
1app counter "1.0.0"
2"A counter with increment, decrement and reset."
3orbital CounterOrbital {
4 entity Counter [runtime] {
5 id : string!
6 count : number = 0
7 }
8
9 trait Counting -> Counter [interaction, instance] {
10 initial: ready
11
12 state ready {
13 INIT -> ready
14 (render-ui main { type: stack, direction: horizontal, gap: md, align: center, children: [
15 { type: button, label: "−", action: DECREMENT, variant: secondary, aria-label: "Decrease" },
16 { type: typography, content: @entity.count, variant: h2 },
17 { type: button, label: "+", action: INCREMENT, variant: primary, aria-label: "Increase" },
18 { type: button, label: "Reset", action: RESET, variant: ghost }
19 ] })
20 INCREMENT -> ready
21 (set @entity.count (+ @entity.count 1))
22 DECREMENT -> ready
23 (set @entity.count (- @entity.count 1))
24 RESET -> ready
25 (set @entity.count 0)
26 }
27 }
28
29 page "/counter" -> Counting
30}Read it top to bottom: the entity Counter holds one number; the trait Counting has one state, ready. On INIT (when the page opens) it renders a row of buttons and the count. Each button sends an event, and each event has a transition that updates the count. The text is bound to @entity.count, so it redraws when the count changes.
orb validate counter.loloThe bar is 0 errors and 0 warnings. Try breaking it: delete the RESET -> ready transition and validate again. The compiler reports that the Reset button sends an event no state handles. That is the closed circuit at work: every affordance must lead somewhere.
orb verify counter.loloVerification plays every transition and checks that it does what it declares: the state it lands in, the effects it runs, the screen it renders.
orb serve counter.lolo --openorb serve compiles the program to a TypeScript app and serves it with a bundled runtime, with no Node.js needed. Here is the same program, running on this page:
trait Counting -> Counter [interaction, instance] {
state ready {
INIT -> ready
(render-ui main <Stack align={center} direction={horizontal} gap={md}>
<Button action={DECREMENT} aria-label={Decrease} label={"−"} variant={secondary} />
<Typography content={@entity.count} variant={h2} />
<Button action={INCREMENT} aria-label={Increase} label={"+"} variant={primary} />
<Button action={RESET} label={Reset} variant={ghost} />
</Stack>)
INCREMENT -> ready
(emit MOVED { event: INCREMENT })
(set @entity.count (+ @entity.count 1))
DECREMENT -> ready
(emit MOVED { event: DECREMENT })
(set @entity.count (- @entity.count 1))
RESET -> ready
(emit MOVED { event: RESET })
(set @entity.count 0)
}
emits {
MOVED -> external {
event : string!
}
@description "Each move names the event it took, so the diagram beside the demo can follow it."
}
}1app std-orb-docex-counter "1.0.0"
2"std-orb-docex-counter — the smallest complete orbital: one entity, one trait with one state, three buttons and a closed circuit."
3orbital CounterOrbital {
4 entity Counter [runtime] {
5 id : string!
6 count : number = 0
7 }
8
9 trait Counting -> Counter [interaction, instance] {
10 initial: ready
11
12 state ready {
13 INIT -> ready
14 (render-ui main { type: stack, direction: horizontal, gap: md, align: center, children: [
15 { type: button, label: "−", action: DECREMENT, variant: secondary, aria-label: "Decrease" },
16 { type: typography, content: @entity.count, variant: h2 },
17 { type: button, label: "+", action: INCREMENT, variant: primary, aria-label: "Increase" },
18 { type: button, label: "Reset", action: RESET, variant: ghost }
19 ] })
20 INCREMENT -> ready
21 (emit MOVED { event: "INCREMENT" })
22 (set @entity.count (+ @entity.count 1))
23 DECREMENT -> ready
24 (emit MOVED { event: "DECREMENT" })
25 (set @entity.count (- @entity.count 1))
26 RESET -> ready
27 (emit MOVED { event: "RESET" })
28 (set @entity.count 0)
29 }
30
31 emits {
32 MOVED -> external {
33 event : string!
34 }
35 @description "Each move names the event it took, so the diagram beside the demo can follow it."
36 }
37 }
38
39 page "/counter" -> Counting
40}What happens when INCREMENT fires:
(set @entity.count (+ @entity.count 1))orb compile counter.lolo --output ./counter-appThis writes a full application you can deploy anywhere. The program is the source; the generated code is never edited by hand.
Learn what each part does: entities, traits and pages. Or follow the complete orbital tutorial.
A programming language of composable state machines.