---
title: "Migrating from Shiny (and plumber v1)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Migrating from Shiny (and plumber v1)}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(eval = FALSE)
```

Two audiences land here: people coming from **Shiny** (reactive server) and
people porting a **plumber v1** API (aurora targets plumber2 only). This covers
both shifts.

## From Shiny: the mental-model shift

| Shiny | aurora |
|---|---|
| Reactive server holds state per user | Stateless: state lives in the client or an external store |
| `ui <- fluidPage(...)` | `build_ui()` returns an htmltools/bslib tag, compiled to static HTML |
| `server <- function(input, output) {...}` | `routers/*.R` — plumber2 handlers returning JSON |
| `reactive()` / `observe()` | JS `fetch` (`aurora.json(...)`) + DOM updates in `app.js` |
| `input$x` | request `query` / `body` / path params |
| `output$y <- render*()` | a JSON response a handler returns |
| `renderPlot`/`renderDT` widgets | client-side libraries (ECharts, MapLibre, DataTables) fed by `/api` |
| shinyapps.io / Connect, sticky sessions | Docker / ShinyProxy, horizontally scalable |

What you keep: the bslib UI transfers almost verbatim. What changes: server logic
becomes JSON endpoints, and you write some JavaScript to render. What you gain:
no per-user R process, trivial horizontal scaling, CDN-cacheable UI.

A reactive value that recomputed when an input changed becomes: a DOM event →
`aurora.json("api/...")` → update the DOM. Read-only datasets that you loaded
once at app start map onto `aurora_data_store()` (see `vignette("aurora")`).

## From plumber v1: it is not a find-and-replace

plumber2 is API-incompatible with plumber. The five changes that actually bite:

### 1. Query params no longer bind to named handler args
Only **path** parameters (`<var>` in the annotation) become named arguments.
Read the query string from the reserved `query` argument and a parsed body from
`body`.

```r
# v1 (BROKEN under plumber2 — msg is always "")
#* @get /api/echo
function(msg = "") list(echo = msg)

# plumber2
#* @get /api/echo
function(query) list(echo = query$msg %||% "")
```

### 2. `req`/`res` become reqres `request`/`response`
The reserved handler arguments are `request`, `response`, `query`, `body`,
`server`, `client_id` — not `req`/`res`. Translation table:

| Need | plumber v1 | plumber2 / reqres |
|---|---|---|
| Path param | named arg (`:var`) | named arg (`<var>`) |
| Query value | named arg | `query$x` |
| Parsed body | `req$body$x` | `body$x` (needs `@parser json`) |
| Request method / path | `req$REQUEST_METHOD` / `req$PATH_INFO` | `request$method` / `request$path` |
| A request header | `req$HTTP_X_FOO` | `request$get_header("X-Foo")` |
| Cookies | parse `req$HTTP_COOKIE` by hand | `request$cookies$name` (auto-parsed) |
| Set status | `res$status <- 401` | `response$status <- 401L` |
| Set header | `res$setHeader(n, v)` | `response$set_header(n, v)` |
| Set / clear cookie | manual `Set-Cookie` | `response$set_cookie(...)` / `response$clear_cookie()` |
| Abort with a code | `res$status <- n; return(...)` | `reqres::abort_unauthorized()` / `abort_bad_request()` |
| Continue / stop chain | `forward()` / `return()` | return `plumber2::Next` / `plumber2::Break` |
| Logging | `cat()` | `server$log("message", ...)` |

Note: reqres `set_cookie(same_site=)` wants `"Lax"`/`"Strict"`/`"None"`
(capitalised). length-1 vectors are **not** auto-unboxed by the `json`
serializer, so scalars serialize as 1-element arrays — `jsonlite::unbox()` them
where a scalar is required (or use a dedicated serializer like `geojson`).

### 3. No `@filter` / `preempt` / `forward()`
Removed. Use a route chain instead: a **header-route** handler (`@header`) runs
before the body and can reject early; return `Next` to continue or `Break` to
stop; throw `reqres::abort_*()` to fail with a status. aurora's `auth` template
uses exactly this for its `/api/*` guard (see `vignette("auth")`).

### 4. `pr_*()` → `api_*()` (not 1:1)
`pr()`/`pr_mount()` → `api()` + `api_parse()`; `pr_static()` → `api_assets()`;
`pr_hook("exit", ...)` → `api_on("end", ...)`. aurora already does this assembly
for you in `aurora_app()`.

### 5. No mount-prefixing — the path lives in the annotation
v1 mounted a router under a prefix; aurora bakes the full path into the
annotation (`#* @get /api/iniciativas/data`). `aurora_add_route()` writes it for
you; porting a mounted v1 router means rewriting each annotation to its full
path.

## Testing a ported handler

`pa$test_request(fiery::fake_request(url, method=, content=, headers=))` runs a
request through the assembled API without binding a port — handy for fast,
deterministic checks while you port.
