Elyra
Elyra The coding agent eTerm The terminal that knows where each command ends Starf An activity monitor for Apple silicon that never invents a number Litr A small, native web browser for macOS e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Release notes
Changelog
Elyra
Resources — `rata make:resource`

Resources — rata make:resource

A resource is the vertical slice a desktop app needs for each kind of record: a list you can search and page through, a form with validation, a detail view, delete with a confirmation — and the Rust commands, tests, model and migration behind them. rata make:resource writes it, typed end to end, using the framework's own safety rails. The code is yours afterwards: it's the reference for how you're meant to do this in Elyra, not a runtime you configure.

This guide goes from a fresh project to a working customer list. The CLI reference is in docs/cli.md; the design and its decisions are in RFC 0001.

Three tiers

rata make:resource Customer               # commands, validation, events, tests — for your model
rata make:resource Customer --view        # + the Svelte screens
rata make:resource Customer --generate …  # + the model, migration, factory, seeder

Start with --generate for a new table; use the first two on a model you've written yourself.

1. A project with a database

rata new crm && cd crm

A resource needs the database feature, serde_json, and tokio for the tests. make:resource checks and prints the exact lines if they're missing — it doesn't edit Cargo.toml:

[dependencies]
elyra = { …, features = ["database"] }
serde_json = "1"

[dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

2. Generate

rata make:resource Team --generate name:string:unique
rata make:resource Customer --generate \
    name:string email:email:unique 'phone:string?' active:bool=true \
    team_id:references:Team

A field is name:type[:modifier…][?][=default] — the table of types is in the CLI docs. In zsh, quote a field with ?.

Generate a parent before the resources that reference it: references:Team needs Team to exist, and the migrations run in the order you generate them.

3. Wire it once

The first resource prints what main.rs needs, and it's the last edit you make there — every later resource is picked up by the registry:

mod resources;

App::new()
    .database("sqlite://crm.db?mode=rwc")
    .commands(resources::commands())
    .migrations(resources::migrations())
    .seeders(resources::seeders())
    .allow_abilities(resources::abilities())
    // …

allow_abilities is deliberate: each command is gated by an ability (customers.view, .create, .update, .delete), denied until the app grants it. Grant fewer to make a read-only app, or generate with --no-abilities for a prototype.

4. Test, migrate, seed

cargo test resources::                   # the generated tests, on throwaway SQLite files
ELYRA_MIGRATE=up cargo run               # create the tables
ELYRA_SEED=1 cargo run                   # 20 teams, 20 customers

The tests run the real migrations and cover the whole slice: create, list, show, update and delete; search, the sort allowlist, paging and its cap; validation on create and update; unique (which skips the row being saved) and exists; the domain events; and that every command needs a granted ability.

5. Run it

rata codegen                              # the typed api.* the views call
rata dev

Customers and Teams are in the nav. #/customers lists them, #/customers/new creates one, #/customers/12 shows one and #/customers/12/edit edits it.

What you got

src/resources/mod.rs                     the registry — rata's, regenerated
src/resources/customer/
    mod.rs                               what the resource contributes
    model.rs                             #[derive(Model)] Customer + impl Factory
    migration.rs                         CreateCustomersTable
    seeder.rs                            CustomerSeeder
    commands.rs                          the five commands, input/query types, events
    tests.rs                             TestApp tests
app/src/resources/index.js               the frontend registry — rata's, regenerated
app/src/resources/customers/
    index.js                             routes + nav entry
    Index.svelte  Form.svelte  Show.svelte

Everything under a resource's folder is yours to change; the two registries are rata's (their first line says so, and rata refuses to overwrite one that doesn't). After deleting a resource folder by hand, rata resources:sync rebuilds them.

The commands

Command Ability What it does
customers_index(query) customers.view search over the text columns, sort by an allowlisted column, a page (at most 100 rows)
customers_show(id) customers.view one record, or "customer 12 not found"
customers_store(input) customers.create validate, insert, dispatch CustomerCreated
customers_update(id, input) customers.update validate (the same rules), update, dispatch CustomerUpdated
customers_destroy(id) customers.delete delete — a soft delete if the model has soft_deletes — and dispatch CustomerDeleted

CustomerInput is what a form may set: no key and no timestamps, so a request can't set them. Its fields are all optional, so a missing one comes back as a validation message rather than a decode error. Validation messages go through the app's Translator when it has one.

The events are the hook for everything else — an audit log, a notification, or App::broadcast::<CustomerUpdated>("customers:updated") to refresh other windows:

App::new().listen(|e: CustomerCreated, ctx: Ctx| async move {
    ctx.get::<AuditLog>().record("customer created", e.customer.id)
});

The views

  • Index — search once typing pauses, sortable headers, paging, delete behind confirm(), an empty state.
  • Form — create and edit; each field's control follows its type, and a validation failure lands under its field.
  • Show — the record, with edit and delete.

With a lang/en.json, every label is a $t("customers.…") key, and their English defaults are added to the file (one "customers" key; nothing else is touched). Translate them in your other locales.

On a model you already have

rata make:resource Invoice          # finds `#[derive(Model)] struct Invoice` under src/
rata make:resource Invoice --view   # later: add the screens, keeping the commands

The model is read with syn, so the commands, rules and form match its fields. It must derive Default, Clone, Serialize, Deserialize and specta::Type, and have an i64 key — rata says which are missing. Without a migration of its own, the tests create the table from the struct; keep that in step with your real migration.

Changing a resource

The generated code is a starting point:

  • Validation — edit RULES (or rules(id)) in commands.rs; any of the 44 rules work.
  • Searchable / sortable columnsSEARCHABLE and SORTABLE at the top of commands.rs. Anything not in SORTABLE sorts by the key.
  • Who can do what — the can = "…" on each command, and what the app grants.
  • The screens — plain Svelte; the scaffold's theme variables style them.

Regenerating with --force overwrites the resource's files, so do it before you've changed them, or diff afterwards. A regenerated migration keeps its version, so a database that ran it doesn't see a new one.

Related