rastack
v0.0.69
Published
**A full-stack framework on top of object storage. Scale to millions.**
Readme
React API Stack
A full-stack framework on top of object storage. Scale to millions.
React API Stack is not a UI library and not "just" an API package — it is the whole vertical: the API exposure surface, the database storage layer, and the devops that ships them. You author one TypeScript resource and the framework derives everything from the React data hooks at the top to the Parquet files on S3 at the bottom, with a stateless Rust API in between.
┌─────────────────────────────────────────────┐
│ React / Expo │ API EXPOSURE
│ useData(Airport) / useForm(Flight) │ SURFACE
└───────────────────────▲─────────────────────┘
│ your types, resolved via the manifest
┌───────────────────────┴─────────────────────┐
│ TypeScript resources → compile step │ API EXPOSURE
│ FK inferred from types → manifest+OpenAPI │ SURFACE
└───────────────────────▲─────────────────────┘
│ schema.rastack.json (manifest)
┌───────────────────────┴─────────────────────┐
│ Rust API (rastack-server, axum) │ API + ADMIN
│ manifest-driven CRUD · FK validation · │
│ Django-admin-style /admin DB browser │
└───────────────────────▲─────────────────────┘
│ object_store
┌───────────────────────┴─────────────────────┐
│ Apache Iceberg tables on S3 (Parquet) │ DATABASE
│ snapshots · ACID on object storage │ STORAGE
└───────────────────────▲─────────────────────┘
│
┌───────────────────────┴─────────────────────┐
│ AWS Lambda (stateless) + S3 (state) │ DEVOPS
│ compute/storage separation, IaC, scale-to- │
│ zero — modelled on SurrealDB's Gen-3 │
└─────────────────────────────────────────────┘The three surfaces, one framework
| Surface | What it is | Package(s) |
| --- | --- | --- |
| API exposure | TypeScript resources → in-process compile (FK inferred from types) → manifest + OpenAPI → pass your type to useData / useForm (typed TanStack Query hooks, no codegen) | the repo root, published as rastack (npm) — components/, hooks/, theme/, … |
| Database storage | A stateless Rust API that stores every model as an Apache Iceberg table on object storage — snapshots, ACID on S3, columnar Parquet | engine/ (serve/rastack-server) |
| DevOps | Serverless Rust on AWS Lambda + S3: stateless compute, state in object storage, infrastructure-as-code, scale-to-zero — driven by rastack init / deploy / ci | deploy/cloudformation/, examples/serverless/ |
Install the CLI
One command, no clone — installs the rastack CLI into a per-user dir
(~/.rastack, no sudo), the way you'd install Poetry or rustup. Node.js is the
only prerequisite.
# macOS / Linux
curl -fsSL https://reactapistack.com/setup.sh | sh
# Windows (PowerShell) — from cmd, prefix with: powershell -c "…"
irm https://reactapistack.com/setup.ps1 | iexPrefer a package manager, or adding it to a single project? npm install -g rastack
(global CLI) or npm install -D rastack (project dependency) work too. Pin a
version with RASTACK_VERSION=x.y.z before the installer.
Author once, compile the rest
// 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 }),
});
export const Terminal = resource("airports", "terminal", {
airport: s.ref(() => Airport), // ← foreign key, detected from the type
label: s.string({ maxLength: 20 }),
});# No compile step to run — it happens in-process. Author your resources, then:
rastack dev # compiles, starts native rastack-server, proxies /api
rastack admin # Django-admin-style console served by that same binary// Then just pass your type to the hooks — nothing is generated into your repo.
const airports = useData(Airport);
<DataTable query={airports} />
const form = useForm(Flight, { id });
form.transition("board"); // a typed, state-machine-safe saveThe compile step runs a real TypeScript type checker and infers relationships
straight from the field types — see
docs/compile-and-relations.md; the hooks
resolve your types against the compiled manifest at runtime — see
docs/typed-hooks.md. Resources can declare state
transitions (scheduled → boarding → departed), compiled into the manifest
and enforced on every write by the native handler — an illegal jump is a 400
from the form, the importer, and rastack-server (laptop and Lambda).
Ship it — one command per stage
The DevOps surface is a CLI too. A new repo goes from nothing to deployed in three commands, and GitHub Actions stays a one-liner because the pipeline lives in the CLI, not the YAML:
rastack bootstrap # once per AWS account+region: buckets + GitHub OIDC
rastack init # per app: scaffold .github/workflows + .rastack/deploy.json AND stand the backend up
rastack ci deploy # what CI runs: build the arm64 Lambda, bake the JWKS, ship the codebootstrap stands the tier-0 account stack up once (the template-hosting
buckets + the GitHub OIDC provider), decoupled from any app. init is per-app:
it scaffolds the base GitHub Actions and a deploy config (defaulting the GitHub
coordinates from your origin remote), then stands the serverless backend up —
deploying app.yaml, reading the stack Outputs back into the config, and wiring
the GitHub repo Variables so the first push deploys. From then on infrastructure
is fixed and CI only ships code: rastack ci deploy builds and runs
update-function-code (rastack deploy is an alias for it), so a workflow step
is just run: rastack ci deploy (rastack ci --plan deploy prints every step).
Full guide: docs/serverless-deploy.md.
The UI is in the browser. The API is native Rust.
A browser cannot execute rastack-server. rastack dev starts that binary
and proxies /api to it. The page renders your app. The request logic
(rastack-api-core), the Iceberg warehouse, and access control run in the
native process — the same binary Lambda runs (provided.al2023,
cargo lambda build --arm64 --features lambda). There is no in-browser engine:
no RastackApi, no warehouse seeded into the page, no IndexedDB copy of the
database. The warehouse is the server's directory under data/warehouse.
// Local: this origin. rastack dev proxies /api to native rastack-server.
<RAStackProvider mode="local" manifest={schema}>
<App />
</RAStackProvider>
// Production: the same hooks, over HTTP, to Lambda.
<RAStackProvider mode="remote" baseURL="https://api.example.com">…</RAStackProvider>Full guide: docs/local-wasm-dev.md.
Why "on top of object storage"?
Traditional stacks pin you to a server-bound RDBMS. React API Stack instead puts S3 at the bottom of the stack as the system of record and runs the API as stateless, elastic compute on top — the same compute/storage-separation principle behind SurrealDB's Gen-3 architecture. That gives you:
- Scale to millions — compute scales horizontally and independently of data; storage is S3's 11-nines durability and effectively unbounded capacity.
- Scale to zero — when no requests are in flight, no compute runs and you pay only for object storage.
- No vendor lock-in — data lives in open Apache Iceberg / Parquet, readable by Spark, DuckDB, Trino, and Polars.
Start here
- docs/compile-and-relations.md — the TypeScript-first compile step and foreign-key inference.
- docs/typed-hooks.md — pass your type to
useData/useForm, and state transitions as typesafe backend functions. - docs/architecture.md — the full serverless, object-storage architecture and how the three surfaces compose.
- docs/local-wasm-dev.md — local dev: native
rastack-server, UI in the browser. - engine/ — the Rust engine:
core/(manifest, Iceberg storage, request brain) andserve/(axum API, native server binary). - examples/serverless/ — Lambda + S3 + IaC.
- CLAUDE.md — codebase guide for contributors.
- Building with an LLM? The documentation site is mirrored as plain Markdown
for agents — reactapistack.com/llms.txt
(curated index), llms-full.txt
(every page in one file), or any page at
/docs/<slug>.md.
Performance note. Apache Iceberg over S3 is currently the portable, correct-by-default storage path, not the fastest one. S3 first-byte latency dominates small OLTP reads. The roadmap closes this with a custom S3 backend fronted by a DynamoDB metadata/cache layer (and optionally EFS for hot IO) — see docs/architecture.md.
