# Cheat sheet

The one page to keep open. Every command, flag, and wiring line you reach for while building — dense
and scannable. For the prose reference see [the `rask` CLI](https://rask.sh/docs/guides/cli/); to learn it in order, follow the
[Tutorial](https://rask.sh/docs/guides/00-overview/). Looking for "how do I do X?" — that's the [Recipes](https://rask.sh/docs/guides/recipes/).

## The CLI in a screenful

```bash
# scaffold & run
rask new Shop                        # new app: the whole stack, accounts included
rask dev                              # dotnet watch run — hot reload, opens https://<name>.test
rask info                             # what rask sees: project, packages, versions

# database (wraps dotnet-ef; installs it on first use)
rask db add InitialCreate             # generate a migration from the current model
rask db update                        # apply it now — the app also does this when it starts
rask db list                          # which migrations exist, and which are applied
rask db remove                        # undo the last migration
rask db drop --yes                  # delete the database, no prompt
rask db backup                        # copy it to ./<app>-<timestamp>.db
rask db restore backups/shop.db       # put a copy back (--remote for the deployed one)

# ship it
rask deploy --host root@box --domain shop.example.com   # bare box → live HTTPS
rask deploy                           # redeploy (host/domain remembered), zero-downtime
rask deploy --github-actions          # write .github/workflows/deploy.yml
rask deploy status                    # what's running, and what a rollback restores
rask deploy logs -f                  # tail the deployed app (--follow)
rask deploy rollback                  # put the previous image back

# tab completion
rask completion zsh > "${fpath[1]}/_rask"   # also: bash, fish
```

Every command's own option list is in `rask <command> --help`, and a wrong one tells you what you
probably meant.

> **Code inside the project is code you write.** `rask` scaffolds the project; pages, components, CRUD
> slices, jobs, emails and caches are shown as copyable code in the [tutorial](https://rask.sh/docs/guides/00-overview/)
> and the guides.

## A CRUD slice, in one place

A vertical slice under `Features/<Plural>/` is one aggregate and the components over it:

| File | What it is |
|---|---|
| `Product.cs` | the aggregate — `Aggregate<Guid, Timestamps, Versioned, Model, Deletable>`, `private set` properties, validation attributes, no constructor |
| `CreateProduct.cs` | `[Route("/products/new")]` — a form over the generated `ProductModel`, saved through a `QueryClient.Command()` that runs `Product.Create(model)` |
| `UpdateProduct.cs` | `[Route("/products/{id:guid}/edit")]` — loads `Product.Model(id)` through `QueryClient.Query`, saves with `Product.Update(id, model)` |
| `DeleteProduct.cs` | a button per row — `Product.Delete(id, version)` behind a command |
| `ProductsPage.cs` | `[Route("/products")]` — a `Ui.Table` with a `Ui.Pagination` under it, one page of `Product.OrderBy(…).Skip(…).Take(…)` at a time |

Nothing else is written: the build generates `ProductModel`, the queries off the type (`Product.Where(…)`,
rows of `ProductRead`), `Product.Find(id)` + `product.Save()`, and the writes, `RaskDbContext` maps the aggregate, and a write refreshes every query keyed
`QueryKey.For<Product>(…)` by itself. Then `rask db add AddProducts` — the app applies it when it starts.
[Chapter 2](https://rask.sh/docs/guides/02-first-feature/) writes all of it out.

## Wiring

`rask new` writes one line — `RaskApp.Create(args).Run<App>();` — and `RaskApp` wires every battery below,
maps them onto `RaskAppDbContext` and applies pending migrations when it starts. Settings live in
`appsettings.json` under `Rask`; code only turns a battery off or sets what has to be code:

```csharp
var app = RaskApp.Create(args);
app.Configure(c => c.Jobs.Off());                              // no background work in this app
app.Configure(c => c.Mail.Configure(o => o.From = "hi@shop.example"));   // wins over Rask:Mail
app.Run<App>();
```

### A hand-wired host

Without `RaskApp` — `AddRask()`/`MapRask<App>()` on a plain `WebApplication` — a data-backed app needs
these in `Program.cs`:

```csharp
builder.Services.AddRaskCqrs();                        // the mediator (IDispatcher)
builder.Services.AddRaskData<ProductsDbContext>();     // interceptors + names the context models use
builder.Services.AddDbContextFactory<ProductsDbContext>((sp, o) => o
    .UseSqlite("Data Source=app.db")
    .AddInterceptors(sp.GetServices<ISaveChangesInterceptor>()));   // audit/soft-delete/events/outbox

var app = builder.Build();
Db.Configure(app.Services);                            // Product.Where(…) now knows which database
```

The type argument and `Db.Configure` are a pair: the bare `AddRaskData()` registers only the
interceptors, and without both, the first `Product.Where(…)` throws `The model database has not been
configured`. Your context should derive from `RaskDbContext`, which is what maps the models you declare.

The other pillars are **one registration + one `modelBuilder` line + a migration** you add by hand:

```csharp
builder.Services.AddRaskJobs<ProductsDbContext>(o => { /* … */ });   modelBuilder.AddRaskJobs();
builder.Services.AddRaskMail<ProductsDbContext>(o => { /* … */ });   modelBuilder.AddRaskMail();
builder.Services.AddRaskCache<ProductsDbContext>();                  modelBuilder.AddRaskCache();
builder.Services.AddRaskStorage<ProductsDbContext>();                modelBuilder.AddRaskStorage();
builder.Services.AddRaskOutbox<ProductsDbContext>(o => { /* … */ }); modelBuilder.AddRaskOutbox();
// the outbox claims domain-event delivery on its own — AddRaskData stays bare, in any order
app.MapRaskStorage();   // file storage's public/temporary link routes — after app.MapRask<App>()

// production SQLite — a drop-in for .UseSqlite that installs the pragma interceptor:
.UseRaskSqlite(sp)                            // in AddDbContextFactory((sp, o) => o…); reads Rask:ConnectionStrings:App
builder.Services.AddRaskSqliteLitestream();   // off-box backup; reads Rask:Litestream
```

After any `modelBuilder.AddRask…` line: `rask db add <Name>`.

## Code idioms

```csharp
// Dispatch a query or command — one method, result type inferred from the message:
var view = await dispatcher.Query(new GetProducts(), CancellationToken);   // IDispatcher, ctor-injected

// Type-safe URL for a routed page — never a string path:
NavLink.Href(Routes.ProductsPage())["Catalog"];       // list page → <Plural>Page
Routes.UpdateProduct(Id: id).Go();                     // edit page → Update<Entity>; from an event handler

// Gate on auth — route-level attribute, or a component that renders only when signed in:
[Authorize]                                            // redirects anonymous deep-links to /login
Authorize[ NewProductButton ]                        // shown only to signed-in users
Authorize.Role("admin")[ DeleteProductButton.Id(id) ]

// Declare an aggregate: private setters, no constructor, a static factory; value objects need no marker (Rask.Data):
public sealed class Product : Aggregate<Guid, Timestamps, Versioned, Model, Deletable> { public string Name { get; private set; } = ""; }

// What a table carries — traits on the base, in any order, each opt-in (Rask.Data):
public sealed class Product : Aggregate<Guid, Timestamps, Versioned, Model, Deletable> { }  // what rask new scaffolds
public sealed class Order : Aggregate<Guid, Timestamps, Versioned, Model, SoftDelete> { }   // keep the row as DeletedAt
public sealed class Invoice : Aggregate<Guid, Timestamps, Versioned> { }   // no Model, no Deletable: methods only, never deleted
public sealed class Ticket : Aggregate<Guid, PerTenant> { }                // a TenantId on the root, a filter — docs/multi-tenancy.md
public sealed class OrderLine : Entity<Guid, Timestamps> { }               // a child takes only a timestamp trait

// Who is signed in, from a static factory or anything with no constructor to inject into:
OwnerId = Current.RequiredUserId,                   // Current.UserId is null when nobody is
using (Tenant.Use(tenantId)) { /* … */ }           // work as one tenant; Tenant.Across() spans them
using (Tenant.Use(42)) { /* … */ }                 // a numbered tenant: `PerTenant<int>` on the base
builder.Services.AddRaskTenant(sp => …);            // the tenant from the request instead of the claim (Guid?, int?, long?)
b.HasIndex(d => d.Name).IsUnique("Already taken.");   // a collision fails as a RaskValidationException, not a DbUpdateException
b.HasMany<Note>().WithOne().HasForeignKey(n => n.TrailerId)
 .OnDelete(DeleteBehavior.Restrict, "Still on a note.");   // so does deleting a row that relationship references
b.WhenReferenced("Still in use.");                    // …and one any other table references (unmapped ones too)

// One row by id to SHOW it (a ProductRead), or the aggregate to CHANGE it — both skip soft-deleted rows:
var one = await Product.Where(p => p.Id == id).First(CancellationToken);
var agg = await Product.Find(id);   // Product? — whole, untracked
agg!.Rename("Anvil");
await agg.Save();                     // only what changed; stale Version throws

// Ranked full-text search — builder.HasFullTextSearch(p => new { p.Name, p.Description }) in static Configure, then:
var hits = await Product.Search(query).Take(20);

// Read and write it — no context injected; or send a command whose handler does the save:
var products = await Product.Where(p => p.Price > 0).OrderBy(p => p.Name);
var product  = await Product.Create(model);               // ProductModel from a form
var edit     = product.ToModel();                                                               // fills an edit form
await Product.Update(id, edit, p => p.Touch(now));        // + values not from the form
await Product.Delete(id, db: db);                        // join a context you hold
await dispatcher.Send(new EditProduct { Id = id, Name = name, Version = version }, CancellationToken);
var page     = await Product.OrderBy(p => p.Name).Skip(20).Take(20);  // a table's page, in SQL

// Cache a query for the session, from Render — refetched by itself after any Product write (Rask.Query):
var count = QueryClient.Query(QueryKey.For<Product>("count"), () => Product.Count());
count.IsLoading ? Ui.Text.Role("status")["Loading…"] : Text($"{count.Data} products")
var save = QueryClient.Command();                    // pending/error state for a write; save.IsPending disables Save

// Cache an expensive read; invalidate on write:
var products = await Cache.Remember("products", Load).For(10.Minutes);
await Cache.Forget("products");

// Keep an upload, then link to it — Url does no I/O, so it is safe inside Render:
var saved = await Files.Save(file.OpenReadStream, file.Name, file.Size).Public();
Img.Src(Files.Url(saved.Id)).Alt(product.Name)

// Enqueue work off the request thread — returns as soon as the row is written:
await Jobs.Enqueue(new SendOrderReceipt(order.Id));

// Send mail — queued, retried, and the same line inside a job or a durable handler:
await Mail.Send(Email.To(order.Email).Subject("Your receipt").Body(Receipt.OrderId(order.Id)));

// Say something happened — from a page, a handler, a job or a hosted service:
await Dispatcher.Publish(new OrderShipped(order.Id));

// In a test, each battery has a fake that records, and runs what it recorded when asked:
using var jobs = Jobs.Fake();   using var mail = Mail.Fake();
await jobs.Run();               mail.Sent().To("ann@example.com").Once();
```

---

Full reference → [the `rask` CLI](https://rask.sh/docs/guides/cli/) · Learn it in order → [Tutorial](https://rask.sh/docs/guides/00-overview/) ·
How do I…? → [Recipes](https://rask.sh/docs/guides/recipes/)
