Skip to content

Current State ​

CrateStack already provides a usable Rust-first backend slice, but it is not yet the full target architecture described in the ADR and PRD.

Implemented Today ​

  1. a Rust 2024 multi-crate workspace under cratestack/
  2. schema parsing and semantic validation for an initial .cstack subset
  3. compile-time role-specific macros: include_server_schema!("…", db = Postgres) (also db = None for database-free servers), include_embedded_schema!, include_client_schema! — each emits a cratestack_schema module shaped for one deployment shape, with strict disjoint backend output (server never emits rusqlite, embedded never emits sqlx)
  4. SQLx-backed model delegates for create, upsert (insert-or-update on PK conflict), find_many, find_unique, update, delete, plus the five batch primitives (batch_get, batch_create, batch_update, batch_delete, batch_upsert) returning a per-item BatchResponse<M> envelope with savepoint isolation
  5. generated Axum model CRUD and procedure routes — for transport rest schemas (the default); transport rpc schemas instead get POST /rpc/{op_id} (unary, every CRUD verb + every procedure) plus POST /rpc/batch (sequence of frames in, sequence out in same order, per-frame error isolation, no in-batch dependencies). Streaming for Sequence-kind ops (list-return procedures) works on the same unary route via Accept: application/cbor-seq. Errors on the RPC binding are uniform RpcErrorBody { code, message, details? } with gRPC-style codes — see ADR 0005
  6. host-owned authentication through AuthProvider and internal binding through bind_auth(...) and bind_context(...)
  7. generated Rust, Dart, and TypeScript client support via include_client_schema! (Rust) and the generate-dart / generate-typescript CLI subcommands
  8. policy enforcement for the current supported model and procedure policy subset (server only). Since 0.13.0 a relation filter or relation sort applies the related model's read policy and @@soft_delete filter, so a related row the caller cannot read behaves as if it did not exist, as it already did under ?include= (Auth support matrix); and a @server_only field can no longer be filtered or sorted on by a request (Field attributes)
  9. generated telemetry for procedure wrappers, procedure routes, and model list routes
  10. top-level mixin declarations plus model @use(...) expansion for reusable field sets
  11. duplicate-execution protection via IdempotencyLayer with reservation tokens and header replay, backed by either SqlxIdempotencyStore (Postgres) or RedisIdempotencyStore (Redis, via cratestack-redis)
  12. optimistic concurrency via @version with If-Match / ETag round-tripping
  13. transactional audit log via @@audit + cratestack_audit + pluggable AuditSink (server)
  14. explicit transaction isolation via run_in_isolated_tx, with commit-time retry. The procedure attribute @isolation is validated and then ignored in every release through 0.14.0; its enforcement (a transaction at the declared level on every transport, retries, 409 TRANSACTION_ABORTED when they run out) shipped in 0.14.1 — see Transaction isolation
  15. per-principal rate limiting via RateLimitLayer + pluggable RateLimitStore, backed by either InMemoryRateLimitStore (single-replica) or RedisRateLimitStore (Redis, via cratestack-redis, so replicas share one view of consumption), with a configurable StoreErrorPolicy for store failures — Allow by default, but only for transport-class (Unavailable) errors, so a caller-inducible failure such as Redis OOM still refuses — a bounded with_store_timeout (500ms default), and typed, codec-negotiated error bodies on every response the rate-limit and idempotency layers emit themselves. See Rate Limiting
  16. soft delete via @@soft_delete (server and embedded; on the embedded role, relation filters and sorts still see tombstoned related rows)
  17. forward-only migrations via Migration + apply_pending with checksum drift detection
  18. runtime validators (@length, @range on Int + Decimal, @email, @regex, @uri, @iso4217) with PII-safe error messages
  19. Decimal scalar with two implemented, additive backends (0.8.0+, cratestack#505) — decimal-rust-decimal (default) and decimal-bigdecimal can both be enabled at once, each dependent in the build graph choosing its own; a schema with a Decimal field requires a decimal = RustDecimal | BigDecimal argument on its include_*_schema! call, see Scalars
  20. a crypto-aws-lc-rs Cargo feature on cratestack-pg reserved for a future FIPS-validated TLS provider — not implemented: enabling it is a hard compile_error! (not a working FIPS mode), because making it real needs the TLS backend to become a genuine choice across cratestack-sqlx/cratestack-client-rust first (both currently hard-select the non-FIPS ring backend); see issue #334
  21. embedded SQLite backend via cratestack-rusqlite: sync API, bundled SQLite, the same include_embedded_schema!-driven ModelDelegate shape as the server, with Decimal, Uuid, DateTime, and Json round-tripping exactly through canonical TEXT storage. Compiles to native (mobile via FFI, desktop) AND wasm32-unknown-unknown (browser, OPFS-backed via sqlite-wasm-rs + sqlite-wasm-vfs). One source, three targets.
  22. dialect-agnostic SQL primitives crate cratestack-sql shared by both backends — value types, filter AST, order AST, model descriptor, and a narrow Dialect trait that only varies on placeholder syntax
  23. projection-decode tolerance (0.4.x) — generated client decoders for ?fields=… reads tolerate the server omitting fields the projection didn't ask for, while still hard-failing on a missing required scalar. Optional scalars (T?) fall back to None; list-arity fields fall back to an empty Vec; required scalars reject. Same MissingFieldFallback ladder powers model ?fields=… reads and view projections
  24. codec-json opt-out on cratestack-pg, cratestack-sqlite, cratestack-client-rust, and cratestack-client-flutter (0.4.x) — default-on so existing setups are unchanged. Backend services that have standardized on CBOR can build with default-features = false on the facade to drop the JsonCodec wrapper type, the JSON fallback in CborCodec's content-negotiation path, and the RuntimeTransportClient::Json FFI variant. serde_json stays linked (the view methods decode into serde_json::Value and the FFI bridge still encodes/decodes its bridge payloads as JSON); the opt-out narrows codec-negotiation behavior, not the serde_json-shaped surface area. CBOR stays unconditional (the schema macros emit pub struct Client<C = CborCodec>); the projection-view client methods (get_view / list_view / list_view_paged) route through the client's codec, so they keep working over CBOR
  25. composable RpcLink client middleware chain on the generated TypeScript RPC client (CratestackRpcClientOptions.links), plus a standalone @cratestack/api npm package shipping createBatchLink() (automatic same-tick RPC-call coalescing into POST /rpc/batch) and createLoggerLink() — see RPC transport: client middleware. RPC-transport/TypeScript only; the REST binding and the Rust/Dart clients don't have an equivalent chain yet
  26. db = None — procedures-only servers with no database — include_server_schema!("…", db = None) requires the schema's datasource block to declare provider = "none", checked at compile time (a schema declaring provider = "none" alongside any model block is rejected with an error naming the offending model), and produces a genuinely database-free, zero-parameter Cratestack::builder() — no PgPool threaded through anywhere. Built for pure business-logic services, RPC facades in front of another system, or stateless computation endpoints that own no models and no database
  27. cratestack-api facade — a third disjoint facade crate (cratestack = { package = "cratestack-api" }), structurally parallel to the existing cratestack-pg / cratestack-sqlite split, purpose-built for db = None servers: sqlx and cratestack-sqlx are genuinely absent from its dependency graph — not feature-gated off, structurally not a dependency at all — unlike cratestack-pg, which still depends on cratestack-sqlx by default and only sheds it when a consumer opts into default-features = false
  28. SQL views — a view <Name> from <Model>, … block with @from(M.f) source bindings and per-backend @@server_sql / @@embedded_sql (@@sql shorthand), generating a read-only ViewDelegate (or ViewDelegateNoUnique for @@no_unique views); @@materialized for a Postgres-backed materialized view with a manually-invoked refresh(). See ADR 0003 and the Views reference
  29. composite unique constraints — @@unique([...]) on a model emits a real, enforced multi-column UNIQUE INDEX on both Postgres and SQLite, usable today as an ON CONFLICT target for hand-written idempotent upserts. Its sibling @@id([...]) (composite primary key) is only authorable and migratable so far — cratestack-migrate emits the DDL, but include_server_schema!/include_embedded_schema! reject any model that declares it with a compile error, since query builders, routing, and all three client generators still assume a single scalar @id column (tracked as issue #136). See the Composite Keys reference
  30. cratestack diff — a standalone CLI command that detects wire-breaking schema changes (a field/model removed or retyped in a way existing clients can't tolerate) independent of the migration-diff generator
  31. Second, opinionated client layouts — TypeScript --swr adds a file-per-model layout (framework-free plain functions plus useSWR/useSWRMutation hooks) under src/swr/, exported as <package-name>/swr, alongside the default react-query layout in the same package; Dart --preset riverpod (file-per-model, one @riverpod provider per operation, dart_mappable-backed value equality) still replaces the default preset. Both are generation-time choices, not runtime flips. TypeScript's --preset was removed once --swr became additive — consumers who previously generated twice to get both layouts now need one run
  32. declarative query blocks (0.11.0) — a query name(args): Type block with a raw-SQL body in @@sql("…"), for the reads the generated builders cannot express (two aggregates in one round trip, FILTER (WHERE …), GROUP BY … HAVING, CTEs). Positional $N parameters are validated against the declared argument list at parse time in both directions; the result is an author-declared type (a model is rejected); @allow/@deny are mandatory and deny-by-default, checked inside the single generated entry point before any SQL runs. Postgres and server-only — include_embedded_schema! and include_server_schema!(db = None) both reject a query at compile time — with no REST route, no RPC op ID and no generated client stub. The body executes inside a Postgres READ ONLY transaction, so DML is refused by the engine even inside a data-modifying CTE. See the Declarative Query Blocks guide
  33. Dart client over a pluggable HTTP transport (since 0.16.0, cratestack#1204) — every generated Dart package sends through a pure-Dart CratestackHttpTransport seam, with one adapter per protocol (CratestackHttpAdapter, CratestackRpcHttpAdapter); Dio is one transport (CratestackDioTransport) and the four *DioAdapter classes are removed. REST gains a typed CratestackRestException, transport failures are a sealed CratestackTransportException, both call options take abort, and --preset fespalier generates the Riverpod surface over fespalier's HttpTransport with no Dio. See Dart HTTP transport and the migration
  34. A pluggable authentication seam and schema-declared step-up (since 0.17.1, cratestack#1208, #1243, #1244, #1245) — an AuthProvider refuses with an AuthRefusal (extra envelope members, details, WWW-Authenticate) rendered the same on REST, RPC, MCP and sync; providers compose with first_success(a).or(b) and say whether a request carries their credential (AuthProvider::credential); a principal contract reserves sub, roles, auth_time, amr, acr, cnf and a few more; cratestack-auth-conformance checks a provider on every transport, and no framework crate depends on an external auth library (ADR 0021). A procedure states how fresh and how strong its caller's authentication must be with @authn(maxAge:, amr:, acr:), and an unmet requirement is RFC 9470's step-up refusal, typed as StepUpRequired with a retry-once helper in every generated client (ADR 0022). See Auth Provider and Step-up examples.

Still Narrow Or Deferred ​

  1. COSE transport remains an unimplemented envelope seam
  2. negotiated multi-codec routing is not complete end-to-end
  3. the parser still validates only an initial schema subset
  4. production-stable exact non-Rust selection typing is not complete
  5. richer exposure controls and some field-level policy features are still deferred
  6. the client runtime remains partially spiked rather than fully mature
  7. the migration runner is forward-only — schema-diff generation and zero-downtime coordination remain out of scope
  8. the embedded SQLite backend does not enforce @@allow / @@deny policies at SQL render time (by design — the client is untrusted; authorization is the server's concern)
  9. @@audit and @@emit directives are currently no-ops in include_embedded_schema!; the local-journal / local-event-bus implementations (needed for sync-engine wiring) land in a follow-up release
  10. multi-DB server support — include_server_schema! accepts a db = Postgres argument (or db = None, see above) but only Postgres is wired for an actual database backend today; MySQL and SQLite-via-sqlx are non-breaking future additions
  11. @@id([...]) composite primary keys are authorable and migratable but not yet loadable by a running server or embedded app — see item 29 above

Best Fit Right Now ​

CrateStack is currently strongest for:

  1. internal CRUD-heavy Rust services
  2. teams that want one schema to drive delegates, routes, and client contracts
  3. services that benefit from generated policy checks and typed query builders
  4. CBOR-first or CBOR-aware HTTP APIs that still need JSON fallback
  5. banking-adjacent workloads that need transactional audit, optimistic locking, idempotency, and explicit isolation out of the box
  6. offline-first apps that want one Rust schema definition to drive the server AND an embedded SQLite store — whether on a phone (Flutter or another UI toolkit over FFI), on a desktop, or in a browser tab via OPFS-backed wasm32-unknown-unknown running inside a Dedicated Worker
  1. ../getting-started/quickstart for a minimal setup path
  2. ./banking-readiness for the regulated-workload primitives shipped on feat/banking-readiness
  3. ../guides/auth-provider for the host auth boundary
  4. ../guides/offline-first-sqlite for the embedded SQLite backend (native + browser via OPFS)
  5. ../guides/no-database-procedures for procedures-only servers with no database at all (db = None, the cratestack-api facade)
  6. ../architecture/transport-architecture for transport design
  7. ../reference/views for the view/@@materialized surface
  8. ../reference/composite-keys for @@id([...])/@@unique([...])
  9. ../reference/auth-support-matrix for the current auth and policy surface

Released under the MIT License.