CrateStack
CrateStack is a Rust-native, schema-first framework workspace for building typed HTTP APIs, generated clients, and backend services from .cstack files.
The implementation is still pre-1.0. The current slice focuses on:
- schema parsing and semantic validation
- compile-time Rust code generation through
include_schema! - client-only Rust code generation through
include_client_macro! - SQLx-backed PostgreSQL delegate scaffolding
- on-device SQLite backend (
cratestack-rusqlite) for offline-first mobile/embedded apps over FFI — same.cstackschemas, sync API, no policy enforcement on device - generated Axum model and procedure routes
- generated model and procedure policy enforcement
- first-party CBOR and JSON codecs
- generated Rust, Dart, and TypeScript client surfaces
- a standalone
.cstacklanguage server and VS Code extension package - Studio scaffold generation for one or more schemas
- mixin declarations and model
@use(...)expansion
Support Matrix
.cstack capability |
Status | Notes |
|---|---|---|
datasource |
Supported | provider accepts postgresql (server) or sqlite (on-device) |
auth |
Supported | Single auth block |
mixin |
Supported | Reusable field sets for models |
model |
Supported | Includes relation and policy attributes in current slice |
type |
Supported | Supports @custom fields |
enum |
Supported | Enum values are untyped identifiers |
procedure / mutation procedure |
Supported | Typed args + return type |
mcp |
Supported | Parsed as config block |
@use(...) on model |
Supported | Expands mixin fields before validation; model-local fields win name conflicts |
Workspace
The Rust workspace contains these main packages:
cratestack: public facade crate and proc-macro re-exportscratestack-core: shared metadata, auth context, codec, error, and envelope typescratestack-parser:.cstackparser and semantic checkercratestack-policy: canonical policy literals, predicates, and procedure-policy evaluation typescratestack-macros: compile-time schema and client generationcratestack-sql: dialect-agnostic SQL primitives shared by both backendscratestack-sqlx: SQLx-backed Postgres runtime and query/delegate primitivescratestack-rusqlite: on-device SQLite backend (sync, no tokio, no policies)cratestack-axum: generated route integration helperscratestack-client-rust: generated Rust client runtimecratestack-client-dart: Dart package generatorcratestack-client-typescript: TypeScript package generatorcratestack-client-flutter: Flutter bridge/runtime experimentscratestack-client-store-sqlite: SQLite-backed client state storecratestack-client-store-redis: Redis-backed client state storecratestack-codec-cbor: CBOR codeccratestack-codec-json: JSON codeccratestack-cli:cratestackcommand-line toolcratestack-lsp:.cstacklanguage servercratestack-studio-generator: Studio app scaffold generator
The VS Code extension wrapper lives under packages/cratestack-vscode.
Install Locally
From the repository root:
Build the language server:
Package the VS Code extension:
Minimal Schema
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
auth Principal {
id String
role String?
}
model Post {
id Int @id
title String
published Boolean @default(false)
authorId Int
author User? @relation(fields:[authorId],references:[id])
@@allow("read", published == true)
@@allow("create", auth() != null)
@@allow("update", auth().role == "admin")
}
model User {
id Int @id
email String @unique
displayName String?
posts Post[] @relation(fields:[id],references:[authorId])
@@allow("read", auth() != null)
}
type FeedArgs {
limit Int?
}
procedure getFeed(args: FeedArgs): Post[]
Mixins
Mixins let you reuse field sets across models without introducing a new runtime type. Declare a
top-level mixin block, then apply it inside a model with @use(...).
mixin AuditFields {
createdAt DateTime @default(dbgenerated())
updatedAt DateTime @default(dbgenerated())
}
model Post {
@use(AuditFields)
id Int @id
title String
}
Current mixin rules in this slice:
- mixins are field-only reusable fragments for models
@use(...)expands mixin fields before validation and code generation- model-local fields win on name conflicts with mixin fields
- mixins must not declare
@id
Validate a schema:
Rust Generation
Use include_schema! in the service that owns the schema and database:
use include_schema;
include_schema!;
Use include_client_macro! in callers that only need to consume another service's generated HTTP API:
use include_client_macro;
include_client_macro!;
Create a generated Rust client:
use ;
let base_url = parse?;
let runtime = new;
let client = new;
Generated Rust clients serialize the same HTTP projection contract used by generated routes, including fields, include, includeFields[path], sort, limit, offset, and grouped where expressions.
On-Device SQLite (Offline-First)
The same .cstack schema that drives the server can also drive an on-device SQLite database. This is built for offline-first mobile/embedded apps where the architecture is "Rust as real frontend, Flutter (or another UI toolkit) as UI only" — Rust handles state, persistence, and business logic; the UI talks to Rust over FFI.
What's different from the server path:
- Sync API —
cratestack-rusqliteusesrusqlitewith bundled SQLite, notokio, no async. Smaller mobile binaries and friendlier FFI bridging. - No policy enforcement — the device is single-user; authorization is the app's concern, not the storage layer's. The renderer skips the policy clauses your
.cstackdeclares. - Bundled SQLite — works on iOS and Android out of the box; no system SQLite version to wrangle.
Minimal usage:
use include_schema;
use ;
use ModelDelegate;
include_schema!;
let runtime = open?;
runtime.with_connection?;
let notes = new;
let created = notes.create.run?;
let row = notes.find_unique.run?;
Worked examples (all runnable via cargo run --example <name> -p cratestack):
sqlite_quickstart— open in-memory DB, single model, full CRUDsqlite_offline_first— file-backed DB, two models,Decimalmoney, filtering & orderingsqlite_ffi_dispatch— JSON-bytes FFI boundary, the dispatcher template to copy into yourflutter_rust_bridgeglue
The Flutter side is per-app — cratestack-rusqlite provides the storage layer and an OperationRequest/OperationResponse envelope; the actual cdylib + FFI bindings live in your mobile app crate.
Generated HTTP Routes
Generated Axum routes currently support:
- procedure routes
- model CRUD routes
- route-level auth context resolution through host-provided
AuthProvider - configured codec handling with CBOR and JSON support
- list-route query parsing for fields, includes, relation include fields, sorting, pagination, scalar filters, grouped
where, and relation filters - route-level validation errors for unknown or disallowed query selections
- generated
tracinginstrumentation while subscriber/exporter setup stays host-owned
Dart Packages
Generate a Flutter-shaped Dart package:
Generated Dart packages expose:
- model and input types
- enum types
- generated selection builders
- generated model and procedure API facades
- a runtime bridge boundary that the host app implements
Regenerate the package after changing the schema or generator templates.
TypeScript Packages
Generate a TypeScript fetch client plus TanStack Query helpers:
Generated TypeScript packages include:
- model and input types
- enum types
- a framework-neutral fetch client
- TanStack Query hooks for React and React Native consumers
- projection helpers for generated route query params
Studio Generation
Generate a Studio app from one or more schemas:
generate-studio currently supports repeated --schema and --service-url pairs. Manifest-driven Studio generation is not implemented yet.
VS Code
CrateStack has two editor surfaces:
- Rust files that consume
cratestack::include_schema!(...) .cstackschema files
Rust-side editor support is project-dependent because include_schema! expands relative to a real Cargo project and a real schema path.
Recommended VS Code settings for a consuming project:
For .cstack files, use cratestack-lsp through packages/cratestack-vscode or configure cratestack.lsp.path to point at a locally built language server.
Transport Notes
JSON and CBOR are first-class codecs. COSE is treated as a planned optional envelope layer over encoded bytes.
Generated Axum routes currently enforce a single configured codec per router rather than negotiated multi-codec transport. application/cbor-seq is documented as a target transport mode, but it is not implemented yet.
Current Limits
CrateStack is not yet the right fit for:
- highly customized non-REST transport protocols
- production-stable exact typed non-Rust client generation across arbitrary projection shapes
- full ZenStack-style policy and exposure parity
- runtime custom-field resolution beyond the current generated trait metadata
Validation
Run the core local checks:
Run the VS Code package smoke test:
Release
See RELEASE.md for the public release process across crates.io, GitHub Releases, VS Code Marketplace, Open VSX, and the docs site.