Quick Start
This guide takes you from zero to a working compiled spec in under 10 minutes. You will write a minimal spec, compile it to a CompiledEntrypointBundle, and verify the output.
What you need
- Node 20+ or Bun 1.1+
- The
@gooi/apppackage
bun add @gooi/app
# or
npm install @gooi/app
1. Write your spec
Create a file named app.yml. This spec defines a single collection, one action, one query, one mutation, and one scenario.
app:
id: my-first-app
name: "My First App"
tz: UTC
history:
enabled: false
domain:
collections:
messages:
fields:
id: id!
text: text!
created_at: timestamp!
actions:
messages.create:
description: "Create a new message"
effects: [emit, write]
failure:
on_step_error: abort
on_signal_error: log_and_continue
rollback:
mode: none
in:
text: text!
do:
- call: ids.generate
with:
prefix: "msg_"
count: 1
as: generated_ids
- call: collections.write
with:
patch:
op: upsert
collection: messages
id:
$expr:
var: generated_ids.ids.0
value:
id:
$expr:
var: generated_ids.ids.0
text:
$expr:
var: input.text
created_at:
$expr:
var: ctx.now
emits:
- signal: message.created
payload:
id:
$expr:
var: generated_ids.ids.0
return:
ok: true
signals:
custom:
message.created:
payload:
id: id!
projections:
all_messages:
strategy: from_collection
collection: messages
sort:
default_by: created_at
default_order: desc
pagination:
mode: page
page_arg: page
page_size_arg: page_size
default_page_size: 20
max_page_size: 100
queries:
- id: list_messages
access:
roles: [authenticated]
in:
page: int
page_size: int
defaults:
page: 1
page_size: 20
returns:
projection: all_messages
mutations:
- id: create_message
access:
roles: [authenticated]
in:
text: text!
run:
actionId: messages.create
input:
text:
$expr:
var: input.text
personas:
user:
description: "Authenticated user"
tags: [authenticated]
history: []
scenarios:
create_and_list:
tags: [smoke]
context:
persona: user
principal:
subject: user_1
steps:
- trigger:
mutation: create_message
input:
text: "Hello from Gooi"
- expect:
query: list_messages
guards:
structural:
- description: "Message should appear in the list"
rule:
"!=":
- var: rows.0.id
- null
access:
default_policy: deny
roles:
authenticated:
description: "Authenticated caller"
derive:
auth_is_authenticated: []
wiring:
surfaces:
http:
queries:
list_messages:
method: GET
path: /messages
bind:
page: query.page
page_size: query.page_size
mutations:
create_message:
method: POST
path: /messages
bind:
text: body.text
2. Compile the spec
Create a file named compile.ts:
import { readFileSync } from "node:fs";
import { defineApp } from "@gooi/app/define";
import { compileApp } from "@gooi/app/compile";
const raw = readFileSync("./app.yml", "utf-8");
const spec = defineApp({ yaml: raw });
const bundle = compileApp({ spec });
console.log("App ID:", bundle.appId);
console.log("Entrypoints:", Object.keys(bundle.entrypoints));
console.log("Capability requirements:", bundle.capabilityRequirements.map((r) => r.portId));
Run it:
bun run compile.ts
You should see output similar to:
App ID: my-first-app
Entrypoints: [ 'list_messages', 'create_message' ]
Capability requirements: [ 'ids.generate', 'collections.write', 'auth.session' ]
warning
defineApp() validates the spec and throws a descriptive error if anything is wrong. compileApp() emits the CompiledEntrypointBundle, a deterministic, hashed artifact. The raw YAML is never read again after compilation. Nothing executes from the spec directly.
3. Run your scenarios
import { readFileSync } from "node:fs";
import { defineApp } from "@gooi/app/define";
import { compileApp } from "@gooi/app/compile";
import { runScenarios } from "@gooi/app-testing/run";
const raw = readFileSync("./app.yml", "utf-8");
const spec = defineApp({ yaml: raw });
const bundle = compileApp({ spec });
const results = await runScenarios({ spec, bundle });
for (const result of results) {
console.log(result.scenarioId, result.passed ? "PASS" : "FAIL");
}
Scenarios run against the canonical runtime with memory-backed providers. No mocks. No test doubles.
What's next
- Installation: full package reference for each adoption level
- The Spec: understand every section of the YAML you just wrote
- The Runtime: learn how
createAppRuntime()executes your compiled bundle - Ecosystem: bind providers, orchestrate changes with control-plane workflows, and author providers/adapters