cratestack-cli 0.2.0

Rust-native schema-first framework for typed HTTP APIs, generated clients, and backend services.
cratestack-cli-0.2.0 is not a library.

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 .cstack schemas, 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 .cstack language 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-exports
  • cratestack-core: shared metadata, auth context, codec, error, and envelope types
  • cratestack-parser: .cstack parser and semantic checker
  • cratestack-policy: canonical policy literals, predicates, and procedure-policy evaluation types
  • cratestack-macros: compile-time schema and client generation
  • cratestack-sql: dialect-agnostic SQL primitives shared by both backends
  • cratestack-sqlx: SQLx-backed Postgres runtime and query/delegate primitives
  • cratestack-rusqlite: on-device SQLite backend (sync, no tokio, no policies)
  • cratestack-axum: generated route integration helpers
  • cratestack-client-rust: generated Rust client runtime
  • cratestack-client-dart: Dart package generator
  • cratestack-client-typescript: TypeScript package generator
  • cratestack-client-flutter: Flutter bridge/runtime experiments
  • cratestack-client-store-sqlite: SQLite-backed client state store
  • cratestack-client-store-redis: Redis-backed client state store
  • cratestack-codec-cbor: CBOR codec
  • cratestack-codec-json: JSON codec
  • cratestack-cli: cratestack command-line tool
  • cratestack-lsp: .cstack language server
  • cratestack-studio-generator: Studio app scaffold generator

The VS Code extension wrapper lives under packages/cratestack-vscode.

Install Locally

From the repository root:

cargo build --workspace
cargo run -p cratestack-cli -- --help

Build the language server:

cargo build -p cratestack-lsp

Package the VS Code extension:

cargo build --release -p cratestack-lsp
cd packages/cratestack-vscode
pnpm install
pnpm run package:vsix

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:

cargo run -p cratestack-cli -- check --schema path/to/schema.cstack
cargo run -p cratestack-cli -- check --schema path/to/schema.cstack --format json

Rust Generation

Use include_schema! in the service that owns the schema and database:

use cratestack::include_schema;

include_schema!("schema.cstack");

Use include_client_macro! in callers that only need to consume another service's generated HTTP API:

use cratestack::include_client_macro;

include_client_macro!("../schemas/billing.cstack");

Create a generated Rust client:

use cratestack::client_rust::{CborCodec, ClientConfig, CratestackClient};

let base_url = url::Url::parse("https://billing.example.internal")?;
let runtime = CratestackClient::new(ClientConfig::new(base_url), CborCodec);
let client = cratestack_schema::client::Client::new(runtime);

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 APIcratestack-rusqlite uses rusqlite with bundled SQLite, no tokio, 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 .cstack declares.
  • Bundled SQLite — works on iOS and Android out of the box; no system SQLite version to wrangle.

Minimal usage:

use cratestack::include_schema;
use cratestack::{RusqliteRuntime, rusqlite_backend::ddl::create_table_sql};
use cratestack_rusqlite::ModelDelegate;

include_schema!("schema.cstack");

let runtime = RusqliteRuntime::open("app.db")?;
runtime.with_connection(|conn| {
    conn.execute_batch(&create_table_sql(&cratestack_schema::NOTE_MODEL))?;
    Ok(())
})?;

let notes = ModelDelegate::new(&runtime, &cratestack_schema::NOTE_MODEL);
let created = notes.create(/* CreateNoteInput { ... } */).run()?;
let row = notes.find_unique(created.id).run()?;

Worked examples (all runnable via cargo run --example <name> -p cratestack):

  • sqlite_quickstart — open in-memory DB, single model, full CRUD
  • sqlite_offline_first — file-backed DB, two models, Decimal money, filtering & ordering
  • sqlite_ffi_dispatch — JSON-bytes FFI boundary, the dispatcher template to copy into your flutter_rust_bridge glue

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 tracing instrumentation while subscriber/exporter setup stays host-owned

Dart Packages

Generate a Flutter-shaped Dart package:

cargo run -p cratestack-cli -- generate-dart \
  --schema schemas/catalog.cstack \
  --out packages/catalog_client \
  --library-name catalog_client \
  --base-path /api

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:

cargo run -p cratestack-cli -- generate-typescript \
  --schema schemas/catalog.cstack \
  --out packages/catalog-client \
  --package-name @example/catalog-client \
  --client-name CatalogClient \
  --base-path /api

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:

cargo run -p cratestack-cli -- generate-studio \
  --schema schemas/catalog.cstack \
  --service-url https://catalog.example.internal \
  --schema schemas/accounts.cstack \
  --service-url https://accounts.example.internal \
  --out target/catalog-studio

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!(...)
  • .cstack schema 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:

{
  "rust-analyzer.linkedProjects": [
    "Cargo.toml"
  ],
  "rust-analyzer.procMacro.enable": true,
  "rust-analyzer.cargo.buildScripts.enable": true,
  "rust-analyzer.checkOnSave": true,
  "rust-analyzer.check.allTargets": true
}

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:

cargo fmt --check
cargo check --workspace --all-targets --all-features
cargo test --workspace --all-features

Run the VS Code package smoke test:

cd packages/cratestack-vscode
pnpm install
pnpm run test:smoke

Release

See RELEASE.md for the public release process across crates.io, GitHub Releases, VS Code Marketplace, Open VSX, and the docs site.