# React API Stack — Full Documentation > React API Stack is a TypeScript-first, full-stack framework built on object storage. You author your data model once — as a `resource()` or a plain TypeScript interface — and the compiler infers everything else, foreign keys included, then generates fully typed React (TanStack Query) hooks on top and drives a stateless Rust API that stores every model as Apache Iceberg tables (Parquet + snapshots) on S3. The same engine compiles to WebAssembly and runs entirely in the browser for local-first development. Built to scale to millions. One resource definition drives three surfaces at once: **API exposure** (typed React hooks generated from an OpenAPI contract), **database storage** (a manifest-driven Rust API persisting Apache Iceberg tables on object storage), and **DevOps** (serverless Rust on AWS Lambda with S3 as the system of record — stateless compute, scale-to-zero, infrastructure-as-code). Every documentation page is also available as plain Markdown at `https://reactapistack.com/docs/.md`, and the entire set is concatenated at `https://reactapistack.com/llms-full.txt`. Point an agent at those to build on top of the framework. This single file concatenates every page of the React API Stack documentation. Each `##` section below is a page; deeper `##`/`###` headings inside are that page's own sections. # Getting Started ## Introduction > React API Stack is the next-generation full-stack framework for apps — one TypeScript source of truth becomes a typed React client and a serverless engine on object storage. **React API Stack** is a full-stack framework for building apps that scale to millions — without running a database server. You author your data model once, in TypeScript, and the framework generates a fully typed React client on top and a stateless Rust engine underneath that persists every model as an [Apache Iceberg](https://iceberg.apache.org/) table on object storage. The database **is** object storage. Compute and storage are fully separated — like a SurrealDB-Gen-3 architecture — so the same engine runs on a Lambda, on your laptop, or **inside the browser** compiled to WebAssembly. ## Why it exists Modern apps drown in glue: hand-written types that drift from the API, a database to provision and scale, a second client-side store to keep in sync, and a design system wired component by component. React API Stack collapses all of that into one authoring model and one compile step. - **One source of truth.** Author a `resource` in TypeScript — or just a plain interface — and the compiler infers everything else, including foreign keys, from the type system. - **Typed end-to-end.** The generated React hooks share their types with the API contract. Zero hand-written types, zero drift. - **No server to run.** Storage is Parquet + Iceberg metadata on S3. Compute is stateless and scales to zero. - **Runs anywhere.** The very same engine runs on the server, or locally in the browser over WASM for offline-first dev. ## The three surfaces A single resource definition drives three surfaces at once: | Surface | What you get | | --- | --- | | `API exposure` | TypeScript resource → `rastack compile` → OpenAPI → typed React hooks. | | `Database storage` | A stateless Rust API that persists each model as an Iceberg table (Parquet + snapshots) on object storage. | | `DevOps` | Serverless Rust on AWS Lambda with S3 as the system of record — scale-to-zero, infrastructure-as-code. | ## What a resource becomes This is the whole loop. Author a resource: ```ts title="resources/airports.ts" import { resource, s } from "rastack/define"; export const Airport = resource("airports", "airport", { code: s.string({ maxLength: 3, unique: true }), name: s.string({ maxLength: 120 }), }, { search: ["code", "name"] }); ``` Compile it, generate the client, and consume fully typed hooks in React: ```tsx title="app/airports.tsx" import { useListAirports, useCreateAirport } from "@/entities"; const { data: airports } = useListAirports({ country: "JP" }); const create = useCreateAirport(); create.mutate({ code: "HND" }); // ✓ typesafe ``` > **Tip:** Ready to build? Head to [Installation](https://reactapistack.com/docs/installation.md) to add `rastack` to your project, then follow the [Quick Start](https://reactapistack.com/docs/quickstart.md). --- ## Installation > Add rastack to your project and compile your first resource in under a minute. React API Stack ships as a single npm package, `rastack`, with subpath exports for the DSL, the CLI, the React runtime, and the design-token framework. The fastest way in is the one-line installer — no clone, no config. ## Install the CLI Install the `rastack` CLI with a single command, the way you'd install [Poetry](https://python-poetry.org) or [rustup](https://rustup.rs). It drops a self-contained copy under `~/.rastack` — no `sudo`, nothing outside your home directory — and wires it onto your `PATH`. ```bash title="terminal" # macOS / Linux curl -fsSL https://reactapistack.com/setup.sh | sh # Windows (PowerShell) — from cmd, prefix with: powershell -c "…" irm https://reactapistack.com/setup.ps1 | iex ``` > **Note:** `rastack` is a Node package, so **Node.js 18+** is the one prerequisite — the way Python is for Poetry. Pin a version with `RASTACK_VERSION=x.y.z` before the command, relocate with `RASTACK_HOME`, or skip the PATH edit with `RASTACK_NO_MODIFY_PATH=1`. ## Add it to a project Prefer a package manager, or want rastack pinned as a project dependency in `package.json`? Both work: ```bash title="terminal" npm install -g rastack # the global CLI, via npm npm install -D rastack # or as a dev dependency in one project ``` > **Note:** This is alpha software you can install and test today. Expect rough edges and breaking changes between releases. ## Requirements - **Node.js 18+** and a package manager (npm, pnpm, or yarn). - **A TypeScript project.** Resources are authored in `.ts` and the compiler builds a real `ts.Program` to read your types. - **React 18+** for the generated hooks (built on [TanStack Query](https://tanstack.com/query)). - **Optional: Rust + the** `wasm32-unknown-unknown` **target** if you want to run the engine locally in the browser. ## Verify the install Compile the resource graph and inspect it — no server or database required: ```bash title="terminal" # TypeScript resources → schema.rastack.json + openapi.json npx rastack compile # introspect the resource graph and its relations npx rastack list npx rastack urls ``` ## Package subpaths Everything lives under the one `rastack` package so versions never drift: | Import | What it provides | | --- | --- | | `rastack/define` | The `resource()` DSL and the `s.*` field builders. | | `rastack/hooks` | The generated, typed React Query hooks and runtime. | | `rastack/provider` | `` — the runtime mode switch (local / s3 / remote). | | `rastack/tokens` · `rastack/theme` | The design-token DSL and the `` runtime. | | `rastack/sync` · `rastack/cache` | The local-first sync store and the local Iceberg file cache. | > **Tip:** Continue to the [Quick Start](https://reactapistack.com/docs/quickstart.md) to define a resource, generate the client, and read data end-to-end. --- ## Quick Start > Define a resource, generate a typed client, and read and write data end to end. This walks through the full loop: define a model, compile it, generate the client, and use it in React. It takes about five minutes. ## 1. Define a resource Resources are the single source of truth. A field whose type references another resource is a foreign key — the compiler infers the relation, so you never declare it twice. ```ts title="resources/airports.ts" import { resource, s } from "rastack/define"; export const Airport = resource("airports", "airport", { code: s.string({ maxLength: 3, unique: true }), name: s.string({ maxLength: 120 }), }, { search: ["code", "name"], admin: { listDisplay: ["code", "name"] } }); export const Terminal = resource("airports", "terminal", { airport: s.ref(() => Airport), // FK — inferred from the field's type label: s.string({ maxLength: 20 }), }); ``` ## 2. Compile The compiler builds a `ts.Program` + `TypeChecker`, walks every `resource(...)` call, and reads each field's type to emit the canonical manifest and an OpenAPI document. ```bash title="terminal" npx rastack compile # → schema.rastack.json (the canonical manifest) # → openapi.json (the /api/{app}/v1/{model}/ contract) ``` ## 3. Generate the typed client ```bash title="terminal" npx rastack generate ``` This reads `openapi.json` and emits one typed hook set per model — `useList*`, `useCreate*`, `useUpdate*`, `useDelete*` — into your repo. ## 4. Use it in React Wrap your app in a provider (it picks where the engine runs), then import the hooks anywhere: ```tsx title="app/terminals.tsx" import { RAStackProvider } from "rastack/provider"; import { useListTerminals, useCreateTerminal } from "@/entities"; export function Terminals() { const { data } = useListTerminals(); // typed rows const create = useCreateTerminal(); return ( {data?.results.map((t) => {t.label})} ); } ``` > **Tip:** That is the entire loop. The same generated hooks work unchanged whether the engine runs in the browser, on S3, or on a remote server — see [Provider & Runtime Modes](https://reactapistack.com/docs/provider.md). --- ## Project Structure > How a React API Stack repository is laid out, directory by directory. A React API Stack project keeps a clean separation between what you author (TypeScript resources and tokens) and what is generated (the manifest, the OpenAPI contract, and the typed client). ```text title="repository layout" resources/ TypeScript resource definitions — the single source of truth tools/ the rastack package — compiler, codegen, CLI, and the React runtime tokens/ the design system — DTCG design tokens (.json + .ts) rust/ Cargo workspace — the manifest-driven API, Iceberg storage, admin data/ the local database — a committed directory of Iceberg/Parquet files web/ the marketing site and docs (this site) docs/ architecture & design docs examples/ runnable references — serverless/ = Lambda + S3 deployment ``` ## Authored vs. generated | You author | The compiler emits | | --- | --- | | `resources/*.ts` | `schema.rastack.json` — the canonical manifest | | TypeScript interfaces | `openapi.json` — the API contract | | `tokens/*.ts` / `.json` | `tokens.css`, `tokens.ts`, `tokens.rastack.json` | > **Note:** The generated manifest drives the Rust API, the admin site, and the codegen — there is no per-resource server code to write. Add a resource, recompile, and every surface updates. ## The Rust workspace The `rust/` directory is a Cargo workspace whose crates are entirely manifest-driven: - `rastack-manifest` — a serde model of the schema. - `rastack-store` — the Iceberg engine over a swappable object backend and codec. - `rastack-api-core` — the framework-free request brain (CRUD, FK validation, access control). - `rastack-api` — the axum HTTP adapter, plus the admin module and JWT verification. - `rastack-wasm` — the same engine compiled for the browser. - `rastack-server` — the binary that `rastack serve` and `rastack admin` exec. --- # Core Concepts ## Resources & the DSL > Author your data model in TypeScript with the resource() DSL and typed field builders. A `resource` is a named model scoped to an app. Its fields carry types, and those types are the schema — the compiler reads them directly rather than trusting a separate declaration. ```ts title="resources/airports.ts" import { resource, s } from "rastack/define"; export const Airport = resource("airports", "airport", { code: s.string({ maxLength: 3, unique: true }), name: s.string({ maxLength: 120 }), elevation: s.int({ min: 0 }), active: s.boolean({ default: true }), }, { search: ["code", "name"], admin: { listDisplay: ["code", "name"] }, }); ``` The signature is `resource(app, model, fields, options?)`. The first two arguments become the URL segments — `/api/airports/v1/airport/` — and the field map defines the columns. ## Field builders Every field comes from `s.*`. Scalars carry a `__rastackScalar` discriminant and their constraints come from the call arguments: | Builder | Type | Common constraints | | --- | --- | --- | | `s.string()` | text | `maxLength`, `unique`, `default` | | `s.int()` · `s.float()` | number | `min`, `max`, `unique` | | `s.boolean()` | bool | `default` | | `s.datetime()` | timestamp | `autoNow` | | `s.ref(() => Model)` | foreign key | `nullable` | ## Options The fourth argument configures behaviour across every surface: - `search` — fields the changelist and API search over. - `admin` — how the record renders in the admin site (`listDisplay`, etc.). - `access` — row-level security (see [Security & Access Control](https://reactapistack.com/docs/security.md)). - `sync` — opt into local-first replication (see [Local-First Sync](https://reactapistack.com/docs/sync.md)). > **Tip:** Prefer no DSL at all? The compiler can infer resources straight from plain TypeScript interfaces — see [Interface-Driven Entities](https://reactapistack.com/docs/interface-entities.md). --- ## Compile & Relations > How rastack compile reads the type system to infer foreign keys and emit the manifest. The compile step is where the type system becomes a schema. `rastack compile` builds a real `ts.Program` and `TypeChecker`, walks each `resource(...)` call, and reads each field's type. ## Foreign keys are inferred, not declared A field whose type is a reference to another resource is a foreign key. The compiler recovers the target from the `Ref` string-literal type arguments — you write the relation once, as a type, and never again. ```ts title="resources/airports.ts" export const Terminal = resource("airports", "terminal", { airport: s.ref(() => Airport), // Ref<"airports", "airport"> label: s.string({ maxLength: 20 }), }); // The compiler emits, in the manifest: // relations: [{ field: "airport", target: "airports.airport", kind: "fk" }] ``` ## The compiler modules | Module | Role | | --- | --- | | `tools/src/define/` | The DSL — `resource()` and `s.*`. | | `compile/program.ts` | Builds the `ts.Program` + `TypeChecker`. | | `compile/analyze.ts` | Walks each `resource` call and reads field types. | | `compile/manifest.ts` | Emits `schema.rastack.json`. | | `compile/openapi.ts` | Emits `openapi.json` with the FK and sync extensions. | ## The URL convention The compiler emits paths in exactly the shape the codegen expects, so the hook generator and runtime never change: ```text title="path shape" /api/{appName}/v1/{modelName}/ ← list endpoint /api/{appName}/v1/{modelName}/{id}/ ← detail endpoint ``` > **Note:** The codegen derives `appName` from `path.split('/')[2]` and `modelName` from `path.split('/')[4]`. Because the compiler emits this exact shape, the existing hooks codegen is unchanged. --- ## Interface-Driven Entities > Infer resources from plain TypeScript interfaces — no DSL required. The same compile pass can infer resources with **no DSL at all**. Your app's entities are plain TypeScript interfaces, discovered from the typed hook calls that consume them. ```ts title="app/terminals.tsx" interface Terminal { id: string; label: string; airport: Airport; // a property typed as another entity → foreign key gates: number & Int; } // The typed hook call roots the entity graph: const { data } = useEntityList(); ``` The graph is rooted by a typed hook like `useEntityList()`, or opted in explicitly with a `@rastackResource` JSDoc tag. A property typed as another entity interface is a foreign key and pulls that interface in transitively. ## Datatypes and constraints Datatypes come from the interface property types. Constraints come from `DbConfig` brand types (from `rastack/types`) or JSDoc tags: | Interface | Inferred | | --- | --- | | `name: string` | a text column | | `code: string & Unique & MaxLength<3>` | unique, max length 3 | | `age: Int` | an integer column | | `nickname?: string` / `\| null` | a nullable column | | `airport: Airport` | a foreign key to `Airport` | ## Circular-dependency checks Just as it validates foreign keys, the compiler detects circular dependencies over the merged FK graph: - **An all-required cycle** is a compile **error** — there is no way to create the first row. - **A cycle broken by an optional field** is a **warning** — the optional edge is the seam. > **Warning:** When an interface and a `resource()` call name the same `app.model`, the DSL wins. Use interfaces for speed and the DSL when you need explicit control. --- ## Manifest & OpenAPI > The two artifacts the compiler emits and what each one drives. Every compile produces two files. Together they are the contract that the entire stack reads from. ## schema.rastack.json — the manifest The canonical manifest is the resolved model: resources, fields, relations, sync config, and admin config. It is what the Rust API and the admin site consume — there is no other server configuration. ```json title="schema.rastack.json" { "resources": [ { "app": "airports", "model": "airport", "fields": [ { "name": "code", "type": "string", "maxLength": 3, "unique": true }, { "name": "name", "type": "string", "maxLength": 120 } ], "relations": [], "search": ["code", "name"] } ] } ``` ## openapi.json — the contract The OpenAPI document reproduces the `/api/{app}/v1/{model}/` REST contract the codegen expects, plus custom extensions the runtime reads: | Extension | Meaning | | --- | --- | | `x-rastack-relation` | marks a property as a foreign key to another model | | `x-rastack-sync` | marks a model as local-first replicated | > **Note:** Because the OpenAPI shape is stable, the hooks codegen and the `rastack` runtime never need to change when you add or edit resources — only the emitted contract does. --- # Frontend Runtime ## Typed React Hooks > The generated TanStack Query hooks — fully typed, one set per model. The generator emits one hook set per model from the OpenAPI contract. Every hook is fully typed against the resource's fields, and every query key is derived so caching and invalidation just work. ```tsx title="app/airports.tsx" import { useListAirports, useAirport, useCreateAirport, useUpdateAirport, useDeleteAirport, } from "@/entities"; // List with typed filters and the DRF-shaped envelope. const { data, isLoading } = useListAirports({ country: "JP" }); data?.results.forEach((a) => a.code /* string */); // Read one. const { data: airport } = useAirport(id); // Mutate — optimistic, with automatic cache invalidation. const create = useCreateAirport(); create.mutate({ code: "HND", name: "Haneda" }); ``` ## The list envelope List endpoints return a Django-REST-Framework-shaped envelope, so pagination is consistent across every model: ```ts title="generated types" type ListResponse = { count: number; next: string | null; previous: string | null; results: T[]; }; ``` ## Built on TanStack Query The hooks are thin, typed wrappers over [TanStack Query](https://tanstack.com/query), so you get caching, background refetching, and mutation lifecycles for free. What is unusual is that **the same hooks run against three completely different backends** without changing a line — the provider decides which. > **Tip:** Continue to [Provider & Runtime Modes](https://reactapistack.com/docs/provider.md) to see how one setting relocates the whole engine. --- ## Provider & Runtime Modes > One setting picks where the engine runs — browser, S3, or a remote server. `` is the single setting that picks where the API runs. It reconfigures the shared axios client in place, so **the generated hooks are unchanged across modes**. ```tsx title="app/_layout.tsx" import { RAStackProvider } from "rastack/provider"; export default function App() { return ( ); } ``` ## The three modes | Mode | Where the engine runs | | --- | --- | | `local` | The WASM engine over an in-memory Iceberg warehouse, seeded from the committed `data/warehouse` files and persisted to IndexedDB. | | `s3` | The same WASM engine, warehouse walked from / and saved to S3 (roadmap). | | `remote` | Classic HTTP to `rastack serve` or a Lambda. | ## Why hooks don't change In `local` and `s3` modes the provider installs a WASM adapter that dispatches requests into `RastackApi.handle` instead of the network. In `remote` mode it points the same client at a base URL. Either way, the hooks call the identical client interface. > **Note:** Mode switching is a runtime concern, not a build concern. You can ship one bundle that runs entirely offline in development and against a Lambda in production. > **Tip:** To build the browser bundle of the engine, see [Local WASM Dev](https://reactapistack.com/docs/local-wasm.md). --- ## Local WASM Dev > Run the entire API in the browser over WebAssembly — no server, no network. The whole API compiles to WebAssembly and runs **in the browser** for local development. There is no server and no network — the engine, the Iceberg store, and access control all execute client-side. ## Build the bundle ```bash title="terminal" # needs the wasm32-unknown-unknown target + wasm-bindgen-cli npx rastack wasm ``` This compiles `rastack-wasm` — wasm-bindgen bindings that expose `rastack-api-core` and a portable `IcebergStore` to JavaScript as a `RastackApi` class with `handle`, `seedFile`, and `export/importWarehouse`. ## How local mode seeds data 1. The provider walks the committed `data/warehouse` Iceberg files over HTTP. 2. It feeds them into the in-memory `MemoryBackend` via `seedFile`. 3. The warehouse is snapshotted into IndexedDB with `exportWarehouse` so reloads are instant and offline. ## The same brain, everywhere Everything real in `rastack-wasm` is gated to `wasm32`, so it is an empty crate in the native `cargo test --workspace`. The logic that runs in the browser is the exact `rastack-api-core` that the server runs — identical CRUD, foreign-key validation, and access-control rules. > **Tip:** Because `ParquetCodec` is the default in every build, the browser reads and writes the same `.parquet` data files as the server. --- # Storage Engine ## The Iceberg Engine > How rastack-store persists every model as an Apache Iceberg table on object storage. The storage crate, `rastack-store`, is an Iceberg engine built on two swappable abstractions: an `ObjectBackend` for where bytes live, and a `DataCodec` for how rows are encoded. ## Swappable backends | Backend | Used for | | --- | --- | | `object_store` (LocalFileSystem) | local development on disk | | `object_store` (S3) | production on AWS | | `MemoryBackend` | the browser (WASM), dependency-free | ## Parquet everywhere Arrow and Parquet compile and run on `wasm32`, so `ParquetCodec` is the default in every build — the browser reads and writes the same `.parquet` files as the server. `JsonCodec` remains available for portable dumps. ## How writes work - **Insert** appends a new data file to the table. - **Update / delete** rewrite a compacted snapshot. - `export_warehouse` / `import_warehouse` snapshot a whole warehouse as one blob — this is what powers browser IndexedDB persistence. > **Note:** The first cut uses an `id` + JSON encoding in Iceberg layout; typed per-field Arrow columns and the full `iceberg` crate catalog are documented as deferred work. --- ## The Rust API > A stateless, manifest-driven Cargo workspace — no per-resource code. The Rust API is entirely manifest-driven. There is no per-resource code: the crates read `schema.rastack.json` at startup and serve every model from it. ## The request brain `rastack-api-core` is the framework-free heart of the system: `handle(ctx, ApiRequest) -> ApiResponse`. Because it has no HTTP framework, it builds for `wasm32` — which is exactly why the browser and the server enforce identical rules. It provides: - manifest-driven CRUD and the DRF-shaped list envelope, - **foreign-key validation on writes** — a bad reference is rejected before it is stored, - **access control** — the permission gate, row-level security, and tenant scoping. ## The HTTP adapter `rastack-api` is a thin [axum](https://github.com/tokio-rs/axum) adapter around the core: the `/api/{app}/v1/{model}/` route set, the `/admin` module, and Cognito JWT verification (RS256 against a pinned JWKS). ```bash title="terminal" # run the API against a local warehouse npx rastack serve --warehouse data/warehouse # with Cognito JWT verification rastack-server serve --jwks --issuer --audience ``` ## The admin site The `/admin` browser is Django-admin-style and manifest-driven: a resource index, a changelist with search and pagination, and add/change/delete forms. Foreign keys render as ` ``` ## What's included - **Controls** — `Button`, `Input`, `Textarea`, `Select`, `Checkbox`. - **Field** — `Label`, `Field`, `FieldError`, `FieldHint`. - **Table** — `Table`, `THead`, `TBody`, `Tr`, `Th`, `Td`. - **Misc** — `Spinner`, `Badge`, `Card`, `Stack`. ## The styling contract Every primitive is painted only through `var(--…)` token references — nothing hard-coded. `style.ts` exposes the semantic set the kit paints from — `color.{surface,border,text,text-muted,accent,on-accent,success,danger,warning}`, `space.*`, `radius.*`, `font.*`, `shadow.*` — each with a literal fallback so components stay legible with a partial token set, or before any theme is wired. > **Tip:** Because `cssVar(path)` uses the same `cssVarName` as the token compiler and ``, the variable names can never drift from your design system. Theme it once; every primitive follows. --- # Reference ## CLI Reference > Every rastack command, grouped by what it does. The `rastack` CLI (also aliased `ras`) drives the whole pipeline — from compiling resources to running the engine, shipping to AWS, and the design studio. Check the installed version with `rastack --version` (or `-v`). ## Compile & introspect ```bash title="terminal" rastack compile [resourcesDir] [outDir] # TS → schema.rastack.json + openapi.json rastack check # validate the resource graph (FKs, cycles) rastack list # list resources and their fields rastack urls # print the emitted API routes ``` ## Generate the client ```bash title="terminal" rastack generate # OpenAPI → typed React hooks rastack generate --schema-only # regenerate types without the runtime ``` ## Run the engine ```bash title="terminal" rastack serve [--warehouse dir] # run the Rust API rastack admin [--warehouse dir] # run the admin site rastack wasm # compile the API to WebAssembly (browser dev) ``` ## Ship it — deploy & CI The DevOps surface is CLI-driven too: scaffold a repo, configure its serverless deployment, and run the pipeline — so a GitHub Actions step is a single line. ```bash title="terminal" rastack init # template the base GitHub Actions + .rastack/deploy.json rastack deploy [--print] [--run] # configure the deployment; print the stack launch command rastack ci # run the CI pipeline (--plan prints every step) ``` > **Tip:** Full walkthrough on the [Deployment](https://reactapistack.com/docs/deployment.md) page. ## Design system & data ```bash title="terminal" rastack tokens [input] [outDir] # compile the design system rastack design # local studio: showcase + inspect + edit tokens rastack scan # CSV PII scan rastack seed # seed an Iceberg warehouse ``` --- ## Deployment > Serverless Rust on AWS Lambda with S3 as the system of record. Because compute is stateless and storage is object storage, deployment is serverless by construction: the Rust engine runs as an AWS Lambda over an S3 Iceberg warehouse and scales to zero when idle. Three CLI commands drive it end to end. ## Three commands Scaffold a repo, configure its deployment, and let CI ship the code: ```bash title="terminal" rastack init # template .github/workflows + .rastack/deploy.json rastack deploy # configure the deployment; print the one-time stack launch rastack ci deploy # what CI runs on every push: build + update-function-code ``` `rastack init` writes two thin workflows (`ci.yml` and `deploy-backend.yml`) that call `rastack ci`, plus a `DeployConfig` at `.rastack/deploy.json` — defaulting the GitHub coordinates from your `origin` remote. Run it again any time to reconfigure. ## Infrastructure vs. code The deploy is split by **what changes, and how often** — which is also what makes it secure. All durable infrastructure is created **once, by a human**; CI does exactly one thing on every push. | Owned by | What | | --- | --- | | CloudFormation (once) | every IAM role, the Cognito pool, the KMS key, the S3 warehouse, the Lambda and its config, SSM | | `rastack ci` (each push) | a single call — `aws lambda update-function-code` — over GitHub OIDC, no stored keys | `rastack deploy` prints the one-time `aws cloudformation deploy` for `app.yaml`, the GitHub repo Variables to wire from the stack Outputs (`AWS_DEPLOY_ROLE_ARN`, `AWS_REGION`), and the Lambda's `RASTACK_*` runtime env — it **configures** that step, it does not silently mutate the cloud. ## The CI pipeline Each phase of `rastack ci` is a pure function of the config, so the workflow YAML stays one line and `rastack ci --plan ` prints the exact commands without running them: | Phase | What it runs | | --- | --- | | `check` | `rastack compile` + `rastack check` — the manifest/graph gate | | `build` | cross-build the arm64 Lambda, bake the Cognito JWKS in, zip the bundle | | `deploy` | `build`, then `aws lambda update-function-code` → wait → print the API URL | > **Note:** The function name and JWKS URL come from the config's stack outputs when wired, or are resolved live from CloudFormation at run time — so the same config works before and after the stack exists. ## Per-tenant scoping A resource with `access: { scope: "owner" }` partitions its rows under `warehouse/tenants/{sub}/…`, and a Cognito Identity Pool role fences each tenant to its own prefix with the `${cognito-identity.amazonaws.com:sub}` IAM variable — the same partitioning enforced by [access control](https://reactapistack.com/docs/security.md) in the engine. > **Tip:** The canonical templates live in `deploy/cloudformation/` (`bootstrap.yaml` + `app.yaml`); the full guide is `docs/serverless-deploy.md`. ---