# aurora <img src="man/figures/logo.svg" align="right" height="139" alt="aurora logo" />

<!-- badges: start -->
[![R-CMD-check](https://github.com/aurora-govpe/aurora-rpkg/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/aurora-govpe/aurora-rpkg/actions/workflows/R-CMD-check.yaml)
[![Docs](https://img.shields.io/badge/docs-pkgdown-blue.svg)](https://aurora-govpe.github.io/aurora-rpkg/)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT)
<!-- badges: end -->

> **Shiny-style authoring, stateless API deployment.**

aurora lets you build a web app the way you'd build a Shiny app — author the UI
in R with [**bslib**](https://rstudio.github.io/bslib/), scaffold the project,
run it locally with hot-reload — but it compiles the UI to a *static*
`index.html` at build time and lets [**plumber2**](https://plumber2.posit.co/)
serve that UI plus your JSON API. There's no reactive server, no per-user R
process, and no sticky sessions: the app is **stateless** and scales
horizontally behind a load balancer, ShinyProxy, or plain Docker.

- 🎨 **UI in R** — author with bslib + `_brand.yml`; ship a static HTML asset.
- ⚡ **Stateless** — plumber2 serves the assets and JSON routes; scale by adding containers.
- 🧩 **Convention over config** — fixed folders, no required manifest.
- 🔐 **Batteries, opt-in** — JWT-cookie auth, a hot-reloading data store, OpenTelemetry.
- 🐳 **One-command deploy** — generate a Dockerfile (`debian` or `alpine`) or a ShinyProxy spec.

## What an app looks like

A route handler is just an annotated function — its URL is the annotation:

```r
# routers/iniciativas.R   ->   GET /api/iniciativas/data
#* @get /api/iniciativas/data
#* @serializer json
function() {
  dados <- aurora_data_get(store, "iniciativas")   # hot-reloaded from disk
  list(data = dados, municipios = aurora_unique(dados$municipio))
}
```

The UI is plain bslib, compiled once to `www/index.html`:

```r
# build_ui.R   ->   www/index.html (static, built by aurora_build_ui())
build_ui <- function() {
  bslib::page_fillable(
    theme = bslib::bs_theme(version = 5, brand = TRUE),
    aurora_component("/api/iniciativas/data", id = "tabela")  # wires a div to the endpoint
  )
}
```

```
build_ui.R (bslib + _brand.yml) ──aurora_build_ui()──▶ www/index.html (static)

Browser ◀──▶ plumber2 server (aurora_app)
              ├── serves www/ (static UI + core.js/app.js)
              ├── routers/*.R        → JSON
              ├── helpers/*.R         (sourced first: config, db, auth, store)
              └── optional JWT-cookie auth guard
```

## Installation

```r
# From GitHub — latest release (recommended):
# install.packages("pak")
pak::pak("aurora-govpe/aurora-rpkg@v0.1.5")

# Development version (main branch):
pak::pak("aurora-govpe/aurora-rpkg")

# From CRAN (once published):
install.packages("aurora")
```

> `remotes`/`devtools` work too: `remotes::install_github("aurora-govpe/aurora-rpkg@v0.1.5")`.

## Quick start

```r
library(aurora)

aurora_create_app("meu_app", template = "minimal")
aurora_add_route("iniciativas", dir = "meu_app")   # -> routers/iniciativas.R
aurora_run("meu_app")                              # http://127.0.0.1:8000 (docs at /__docs__)
```

`aurora_run("meu_app", watch = TRUE)` rebuilds the static UI on change. Theming
is a `_brand.yml` consumed by bslib; auth, data, and telemetry are opt-in
(`template = "auth"`, `aurora_data_store()`, `aurora_run(otel = TRUE)`).

## Canonical app layout (convention; no manifest required)

```
<app>/
├── api.R            # entry: aurora::aurora_run("."), host/port from env
├── build_ui.R       # defines build_ui() -> htmltools tag; sources ui_modules/
├── helpers/         # *.R sourced before routers are parsed (config, db, auth…)
├── routers/         # plumber2 annotated handlers; URL = annotation path
├── ui_modules/      # ui_*.R partials
├── www/             # static assets: js/core.js (runtime), js/app.js, style.css, images/
├── data/config.yml  # app config (config package)
├── _brand.yml       # optional: bslib theming (color/type/logo)
├── _aurora.yml      # optional: overrides name/engine/auth/packages/attach/statics
└── Dockerfile       # generated by aurora_dockerfile()
```

## Deploy

```r
aurora_dockerfile("meu_app")                        # flavor = "debian" (default)
aurora_dockerfile("meu_app", flavor = "alpine")     # tiny image, source-built
aurora_build_image("meu_app", tag = "org/meu_app:latest", push = TRUE)
```

The container serves the **prebuilt** static UI, so neither flavor installs
bslib/shiny at runtime. `aurora_build_image()` targets `linux/amd64` by
default, so images built on an Apple Silicon Mac run on x86-64 servers
(pass `platform = NULL` for a host-native build).

| | **`debian`** (default) — `rocker/r-ver` | **`alpine`** — `rhub/r-minimal` |
|---|---|---|
| R packages | prebuilt **binaries** (Posit Package Manager) | **compiled** from source (`installr`) |
| Build speed | fast | slow (full compile) |
| Image size | large (~1 GB+) | small |
| Architectures | amd64 binaries (arm64 compiles) | builds natively on amd64 **and** arm64 |
| System deps | `apt`, broad/easy (glibc) | curated `apk`, must compile (musl) |

Rule of thumb: **`debian`** for fast CI and rich/geo dependencies; **`alpine`**
when image size matters most and you can absorb longer builds.

Apps that share static assets (logo, JS libraries, CSS) can serve them from one
server-side directory via `_aurora.yml`:

```yaml
# _aurora.yml — serve /srv/aurora-shared at /assets (alongside www/ at /)
statics:
  /assets: /srv/aurora-shared
```

Mount the directory as a volume (`-v /srv/aurora-shared:/srv/aurora-shared:ro`)
and every app picks up changes from one place. See `vignette("deploy")`.

## Core functions

| Function | Purpose |
|---|---|
| `aurora_create_app()` | Scaffold the canonical layout (`minimal` / `auth` template) |
| `aurora_add_route()` | Generate an annotated plumber2 router in `routers/` |
| `aurora_build_ui()` / `aurora_app()` / `aurora_run()` | Compile UI, assemble the API, run locally (incl. `attach=` to load `_aurora.yml` packages and `on_exit=` for shutdown cleanup) |
| `aurora_config()` | Read `data/config.yml` anchored to the app root (no cwd pitfall) |
| `aurora_check()` | Lint the app: UI code in runtime helpers, undeclared packages, missing UI |
| `aurora_component()` | Emit a UI element wired to a JSON endpoint (`data-endpoint`) |
| `aurora_unbox()` / `aurora_geojson()` / `aurora_unique()` | NULL-safe JSON response helpers (unbox / sf→GeoJSON / sorted-unique) |
| `aurora_data_store()` | Globals-free, hot-reloading data store for handlers |
| `aurora_auth_jwt()` + `aurora_jwt_*` / `aurora_*_auth_cookie()` | JWT-cookie auth scheme (pluggable) |
| `aurora_dockerfile()` / `aurora_build_image()` / `aurora_shinyproxy_yaml()` | Deploy: Docker + ShinyProxy |

See the [Get started](https://aurora-govpe.github.io/aurora-rpkg/articles/aurora.html)
article, the [Gallery](https://aurora-govpe.github.io/aurora-rpkg/articles/gallery.html),
and the [function reference](https://aurora-govpe.github.io/aurora-rpkg/reference/).

## Design decisions

- **plumber2 only.** Each handler's URL comes from its annotation.
- **Thin JS runtime.** `core.js` is aurora's basics; `app.js` and
  `aurora_component()` keep rendering in the app, not the package.
- **Pluggable auth.** A JWT-cookie `@header` guard, never in `aurora_app()`'s core path.
- **Convention over manifest.** Fixed folders; `_aurora.yml` optional.
- **Theming via brand.yml.** bslib consumes `_brand.yml` at build time.

## Authors

- **Andre Leite** — maintainer (leite@castlab.org)
- Marcos Wasilew · Hugo Vasconcelos · Carlos Amorin · Diogo Bezerra · Júlia Nascimento Barreto

Developed by the NGR-Dados team (SEPLAG-PE) with UFPE.

## License

MIT © aurora authors. Contributions welcome via issues and pull requests.
