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
- a Rust 2024 multi-crate workspace under
cratestack/ - schema parsing and semantic validation for an initial
.cstacksubset - compile-time role-specific macros:
include_server_schema!("…", db = Postgres)(alsodb = Nonefor database-free servers),include_embedded_schema!,include_client_schema!— each emits acratestack_schemamodule shaped for one deployment shape, with strict disjoint backend output (server never emits rusqlite, embedded never emits sqlx) - 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-itemBatchResponse<M>envelope with savepoint isolation - generated Axum model CRUD and procedure routes — for
transport restschemas (the default);transport rpcschemas instead getPOST /rpc/{op_id}(unary, every CRUD verb + every procedure) plusPOST /rpc/batch(sequence of frames in, sequence out in same order, per-frame error isolation, no in-batch dependencies). Streaming forSequence-kind ops (list-return procedures) works on the same unary route viaAccept: application/cbor-seq. Errors on the RPC binding are uniformRpcErrorBody { code, message, details? }with gRPC-style codes — see ADR 0005 - host-owned authentication through
AuthProviderand internal binding throughbind_auth(...)andbind_context(...) - generated Rust, Dart, and TypeScript client support via
include_client_schema!(Rust) and thegenerate-dart/generate-typescriptCLI subcommands - policy enforcement for the current supported model and procedure policy subset (server only)
- generated telemetry for procedure wrappers, procedure routes, and model list routes
- top-level
mixindeclarations plus model@use(...)expansion for reusable field sets - duplicate-execution protection via
IdempotencyLayerwith reservation tokens and header replay, backed by eitherSqlxIdempotencyStore(Postgres) orRedisIdempotencyStore(Redis, viacratestack-redis) - optimistic concurrency via
@versionwithIf-Match/ETaground-tripping - transactional audit log via
@@audit+cratestack_audit+ pluggableAuditSink(server) - explicit transaction isolation via
run_in_isolated_tx+ procedure@isolation, with commit-time retry - per-principal rate limiting via
RateLimitLayer+ pluggableRateLimitStore, backed by eitherInMemoryRateLimitStore(single-replica) orRedisRateLimitStore(Redis, viacratestack-redis, so replicas share one view of consumption), with a configurableStoreErrorPolicyfor store failures —Allowby default, but only for transport-class (Unavailable) errors, so a caller-inducible failure such as RedisOOMstill refuses — a boundedwith_store_timeout(500ms default), and typed, codec-negotiated error bodies on every response the rate-limit and idempotency layers emit themselves. See Rate Limiting - soft delete via
@@soft_delete(server and embedded) - forward-only migrations via
Migration+apply_pendingwith checksum drift detection - runtime validators (
@length,@rangeon Int + Decimal,@email,@regex,@uri,@iso4217) with PII-safe error messages - Decimal scalar with two implemented, additive backends (0.8.0+, cratestack#505) —
decimal-rust-decimal(default) anddecimal-bigdecimalcan both be enabled at once, each dependent in the build graph choosing its own; a schema with aDecimalfield requires adecimal = RustDecimal | BigDecimalargument on itsinclude_*_schema!call, see Scalars - a
crypto-aws-lc-rsCargo feature oncratestack-pgreserved for a future FIPS-validated TLS provider — not implemented: enabling it is a hardcompile_error!(not a working FIPS mode), because making it real needs the TLS backend to become a genuine choice acrosscratestack-sqlx/cratestack-client-rustfirst (both currently hard-select the non-FIPSringbackend); see issue #334 - embedded SQLite backend via
cratestack-rusqlite: sync API, bundled SQLite, the sameinclude_embedded_schema!-drivenModelDelegateshape as the server, withDecimal,Uuid,DateTime, andJsonround-tripping exactly through canonical TEXT storage. Compiles to native (mobile via FFI, desktop) ANDwasm32-unknown-unknown(browser, OPFS-backed viasqlite-wasm-rs+sqlite-wasm-vfs). One source, three targets. - dialect-agnostic SQL primitives crate
cratestack-sqlshared by both backends — value types, filter AST, order AST, model descriptor, and a narrowDialecttrait that only varies on placeholder syntax - 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 toNone; list-arity fields fall back to an emptyVec; required scalars reject. SameMissingFieldFallbackladder powers model?fields=…reads and view projections codec-jsonopt-out oncratestack-pg,cratestack-sqlite,cratestack-client-rust, andcratestack-client-flutter(0.4.x) — default-on so existing setups are unchanged. Backend services that have standardized on CBOR can build withdefault-features = falseon the facade to drop theJsonCodecwrapper type, the JSON fallback inCborCodec’s content-negotiation path, and theRuntimeTransportClient::JsonFFI variant.serde_jsonstays linked (the view methods decode intoserde_json::Valueand the FFI bridge still encodes/decodes its bridge payloads as JSON); the opt-out narrows codec-negotiation behavior, not theserde_json-shaped surface area. CBOR stays unconditional (the schema macros emitpub 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- composable
RpcLinkclient middleware chain on the generated TypeScript RPC client (CratestackRpcClientOptions.links), plus a standalone@cratestack/apinpm package shippingcreateBatchLink()(automatic same-tick RPC-call coalescing intoPOST /rpc/batch) andcreateLoggerLink()— see RPC transport: client middleware. RPC-transport/TypeScript only; the REST binding and the Rust/Dart clients don’t have an equivalent chain yet db = None— procedures-only servers with no database —include_server_schema!("…", db = None)requires the schema’sdatasourceblock to declareprovider = "none", checked at compile time (a schema declaringprovider = "none"alongside anymodelblock is rejected with an error naming the offending model), and produces a genuinely database-free, zero-parameterCratestack::builder()— noPgPoolthreaded 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 databasecratestack-apifacade — a third disjoint facade crate (cratestack = { package = "cratestack-api" }), structurally parallel to the existingcratestack-pg/cratestack-sqlitesplit, purpose-built fordb = Noneservers:sqlxandcratestack-sqlxare genuinely absent from its dependency graph — not feature-gated off, structurally not a dependency at all — unlikecratestack-pg, which still depends oncratestack-sqlxby default and only sheds it when a consumer opts intodefault-features = false- SQL views — a
view <Name> from <Model>, …block with@from(M.f)source bindings and per-backend@@server_sql/@@embedded_sql(@@sqlshorthand), generating a read-onlyViewDelegate(orViewDelegateNoUniquefor@@no_uniqueviews);@@materializedfor a Postgres-backed materialized view with a manually-invokedrefresh(). See ADR 0003 and the Views reference - composite unique constraints —
@@unique([...])on a model emits a real, enforced multi-columnUNIQUE INDEXon both Postgres and SQLite, usable today as anON CONFLICTtarget for hand-written idempotent upserts. Its sibling@@id([...])(composite primary key) is only authorable and migratable so far —cratestack-migrateemits the DDL, butinclude_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@idcolumn (tracked as issue #136). See the Composite Keys reference 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- Second, opinionated client layouts — TypeScript
--swradds a file-per-model layout (framework-free plain functions plususeSWR/useSWRMutationhooks) undersrc/swr/, exported as<package-name>/swr, alongside the default react-query layout in the same package; Dart--preset riverpod(file-per-model, one@riverpodprovider per operation,dart_mappable-backed value equality) still replaces the default preset. Both are generation-time choices, not runtime flips. TypeScript’s--presetwas removed once--swrbecame additive — consumers who previously generated twice to get both layouts now need one run - declarative
queryblocks (0.11.0) — aquery name(args): Typeblock 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$Nparameters are validated against the declared argument list at parse time in both directions; the result is an author-declaredtype(amodelis rejected);@allow/@denyare mandatory and deny-by-default, checked inside the single generated entry point before any SQL runs. Postgres and server-only —include_embedded_schema!andinclude_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 PostgresREAD ONLYtransaction, so DML is refused by the engine even inside a data-modifying CTE. See the Declarative Query Blocks guide
Still Narrow Or Deferred
- COSE transport remains an unimplemented envelope seam
- negotiated multi-codec routing is not complete end-to-end
- the parser still validates only an initial schema subset
- production-stable exact non-Rust selection typing is not complete
- richer exposure controls and some field-level policy features are still deferred
- the client runtime remains partially spiked rather than fully mature
- the migration runner is forward-only — schema-diff generation and zero-downtime coordination remain out of scope
- the embedded SQLite backend does not enforce
@@allow/@@denypolicies at SQL render time (by design — the client is untrusted; authorization is the server’s concern) @@auditand@@emitdirectives are currently no-ops ininclude_embedded_schema!; the local-journal / local-event-bus implementations (needed for sync-engine wiring) land in a follow-up release- multi-DB server support —
include_server_schema!accepts adb = Postgresargument (ordb = None, see above) but only Postgres is wired for an actual database backend today; MySQL and SQLite-via-sqlx are non-breaking future additions @@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:- internal CRUD-heavy Rust services
- teams that want one schema to drive delegates, routes, and client contracts
- services that benefit from generated policy checks and typed query builders
- CBOR-first or CBOR-aware HTTP APIs that still need JSON fallback
- banking-adjacent workloads that need transactional audit, optimistic locking, idempotency, and explicit isolation out of the box
- 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-unknownrunning inside a Dedicated Worker
Read Next
../getting-started/quickstartfor a minimal setup path./banking-readinessfor the regulated-workload primitives shipped onfeat/banking-readiness../guides/auth-providerfor the host auth boundary../guides/offline-first-sqlitefor the embedded SQLite backend (native + browser via OPFS)../guides/no-database-proceduresfor procedures-only servers with no database at all (db = None, thecratestack-apifacade)../architecture/transport-architecturefor transport design../reference/viewsfor theview/@@materializedsurface../reference/composite-keysfor@@id([...])/@@unique([...])../reference/auth-support-matrixfor the current auth and policy surface