FAQ
Frequently asked questions about Grove.
General
What is Grove?
Grove is a declarative language for building applications with durable workflow orchestration. You define records, events, actions, and workflows in .grove files, and the runtime handles persistence, state management, and execution.
Is Grove a general-purpose programming language?
No. Grove is a domain-specific language designed for structured business applications. It excels at structured, data-driven applications like order management, approval workflows, and CRUD with audit trails. For general-purpose computing, use a host language (Rust, TypeScript, etc.) alongside Grove.
Who writes Grove code?
Both human developers and LLM agents. Grove's constrained grammar and strict type system make it well-suited for AI code generation. The language documentation includes an LLM Cheat Sheet and Full Language Reference optimized for machine consumption.
Language
Why can't I use emit outside of actions?
Events represent state changes to a record. Only actions can change state, so emit is restricted to action apply blocks. This ensures every state change flows through the action -> event -> record pipeline with validation.
Why are let bindings immutable?
Immutability simplifies reasoning about data flow. Since records only change through events, having mutable variables would create confusion about which mutations are persisted and which are transient. Use multiple let bindings instead of reassignment.
Can I have multiple records in one module?
No. Each module has at most one record declaration. This is by design -- a module represents a single aggregate (entity) boundary. Use separate modules for separate entities and connect them with triggers and workflows.
Why are some fields reserved?
The fields pk, id, version, created_at, updated_at, created_by, and updated_by are managed by the runtime. They're automatically populated on every record and available in expressions. Declaring them yourself would conflict with runtime behavior.
How do optional types work?
Append ? to any type to make it optional: String?, Int?, Address?. Use ?. for optional chaining and ?? for null coalescing:
let name = record.nickname ?? "Anonymous"
let city = record.address?.city ?? "Unknown"
Runtime
What databases does Grove support?
SQLite, PostgreSQL, MySQL, and TiDB. Use --backend and --db flags with grove-cli run-db.
How do I deploy a Grove application?
Use grove-server to serve your module over HTTP:
manzano serve \
--module <module-dir> \
--listen 127.0.0.1:3000
How does state management work in Grove?
Every action produces events. Events are stored permanently and applied to the record to derive current state. You can replay all events to reconstruct any past state. The runtime handles this automatically -- you just define events and their fields.
Workflows
Are workflows running in production?
Workflow declarations are parsed and type-checked, but the runtime is in development. You can write and validate workflow code today. See Known Limitations for current status.
What's the difference between start and spawn?
start begins a child workflow and waits for it to complete (synchronous). spawn begins a child workflow and continues immediately (fire-and-forget).
See Also
- Common Errors -- Error troubleshooting
- Known Limitations -- Current limitations
- Concepts Overview -- Core concepts