Skip to main content

valence/
lib.rs

1//! **Valence** is a schema-driven ORM for Rust: declare typed tables with
2//! [`valence_schema!`], generate [`Model`] CRUD at build time, and wire storage through
3//! [`Valence::builder`] without locking into one database.
4//!
5//! *Typed schemas and models with composable storage adapters.*
6//!
7//! # Features
8//!
9//! - **Schema DSL** — fields, connections, policies, ownership, TTL, and trait mixins
10//!   ([`valence_schema!`], [`valence_trait_schema!`])
11//! - **Build-time codegen** — typed models from host `schemas/` via `valence-codegen`
12//! - **Composable backends** — in-memory, SQLite, IndraDB, SurrealDB, Postgres, MongoDB, Redis
13//! - **Multi-backend routing** — one [`DatabaseRouter`]; each schema picks a backend with
14//!   `database:` / [`DatabaseFromEngine`]
15//! - **Host ports** — secrets, actor identity, endpoints, and telemetry injected at boot
16//! - **Privacy-aware CRUD** — policy and ownership hooks on generated [`Model`] paths
17//!
18//! Enable backends with Cargo features (`mem` is the default). The crate `README.md` lists every
19//! feature flag and environment variable.
20//!
21//! # Getting started
22//!
23//! Follow these steps in order. Each linked API page includes details for that task.
24//!
25//! ## 1. Choose and wire storage
26//!
27//! | Backend | Type | Feature | Topology | When to use |
28//! |---------|------|---------|----------|-------------|
29//! | In-memory | [`InMemoryBackend`] | `mem` (default) | embedded | Local experiments; tests |
30//! | SQLite | [`SqliteBackend`] | `sqlite` | embedded | Durable single-host store |
31//! | IndraDB | [`IndradbBackend`] | `indradb` | embedded | Graph-oriented workloads |
32//! | SurrealDB | [`SurrealEmbeddedBackend`] | `surreal` | embedded | Surreal engine in-process |
33//! | Postgres | [`PostgresBackend`] | `postgres` | remote | Wire Postgres (`DATABASE_URL`) |
34//! | MongoDB | [`MongoBackend`] | `mongodb` | remote | Wire Mongo (`VALENCE_MONGODB_URI`) |
35//! | Redis | [`RedisBackend`] | `redis` | remote | Wire Redis (`VALENCE_REDIS_URL`) |
36//!
37//! ### Select a backend in the schema
38//!
39//! A schema does not contain a backend instance. Its `database:` field points to a stable
40//! [`DatabaseFromEngine`] evaluator. The evaluator combines:
41//!
42//! - a **logical name** (for example `"default"`) that must match
43//!   [`ValenceBuilder::add_backend`], and
44//! - an **engine ID** exported by the selected adapter.
45//!
46//! Define `COUNTER_DB` for the backend you enable:
47//!
48//! | Backend | `COUNTER_DB` declaration |
49//! |---------|--------------------------|
50//! | In-memory | `Database::from_engine("default", MEM_ENGINE_ID)` |
51//! | SQLite | `Database::from_engine("default", SQLITE_ENGINE_ID)` |
52//! | IndraDB | `Database::from_engine("default", INDRADB_ENGINE_ID)` |
53//! | SurrealDB | `Database::from_engine("default", SURREAL_ENGINE_ID)` |
54//! | Postgres | `Database::from_engine("default", POSTGRES_ENGINE_ID)` |
55//! | MongoDB | `Database::from_engine("default", MONGODB_ENGINE_ID)` |
56//! | Redis | `Database::from_engine("default", REDIS_ENGINE_ID)` |
57//!
58//! Then use that evaluator in the same Counter schema:
59//!
60//! ```ignore
61//! use valence::{Database, DatabaseFromEngine, FieldType, valence_schema};
62//!
63//! // Choose the engine constant for the enabled backend.
64//! pub const COUNTER_DB: DatabaseFromEngine =
65//!     Database::from_engine("default", valence::MEM_ENGINE_ID);
66//!
67//! valence_schema! {
68//!     Counter {
69//!         table: "counter",
70//!         version: "0.1.0",
71//!         description: "Simple counter",
72//!         database: COUNTER_DB,
73//!         fields: [
74//!             id: { r#type: FieldType::String, primary_key: true, required: true },
75//!             value: { r#type: FieldType::Integer, required: true },
76//!         ],
77//!     }
78//! }
79//! ```
80//!
81//! Omitting `database:` selects [`DEFAULT_IN_MEMORY`] (`"default"` +
82//! [`MEM_ENGINE_ID`]). If that router key is absent, the current runtime falls back to its
83//! active/default backend. Declare `database:` explicitly for clear behavior and for any runtime
84//! with multiple backends.
85//!
86//! **In-memory first run:**
87//!
88//! ```rust
89//! # #[cfg(feature = "mem")]
90//! use std::sync::Arc;
91//! # #[cfg(feature = "mem")]
92//! use valence::{
93//!     Database, DatabaseFromEngine, FieldType, InMemoryBackend, Valence, MEM_ENGINE_ID,
94//!     valence_schema,
95//! };
96//!
97//! # #[cfg(feature = "mem")]
98//! const COUNTER_DB: DatabaseFromEngine =
99//!     Database::from_engine("default", MEM_ENGINE_ID);
100//!
101//! # #[cfg(feature = "mem")]
102//! valence_schema! {
103//!     Counter {
104//!         table: "counter",
105//!         version: "0.1.0",
106//!         database: COUNTER_DB,
107//!         fields: [
108//!             id: { r#type: FieldType::String, primary_key: true, required: true },
109//!             value: { r#type: FieldType::Integer, required: true },
110//!         ],
111//!     }
112//! }
113//!
114//! # #[cfg(feature = "mem")]
115//! # #[tokio::main]
116//! # async fn main() -> valence::Result<()> {
117//! # #[cfg(feature = "mem")]
118//! let valence = Valence::builder()
119//!     .add_backend("default", Arc::new(InMemoryBackend::new()))
120//!     .build()?;
121//! # #[cfg(feature = "mem")]
122//! assert_eq!(valence.backend_for_table("counter")?.engine_id(), MEM_ENGINE_ID);
123//! # #[cfg(feature = "mem")]
124//! # Ok(())
125//! # }
126//! ```
127//!
128//! Runnable: `cargo run -p valence --example quickstart --features mem`
129//!
130//! ## 2. Declare schemas
131//!
132//! Schemas are the typed contracts Valence registers and (via codegen) turns into models.
133//!
134//! ```ignore
135//! use valence::{
136//!     Database, DatabaseFromEngine, FieldType, MEM_ENGINE_ID, valence_schema,
137//! };
138//!
139//! const COUNTER_DB: DatabaseFromEngine =
140//!     Database::from_engine("default", MEM_ENGINE_ID);
141//!
142//! valence_schema! {
143//!     Counter {
144//!         table: "counter",
145//!         version: "0.1.0",
146//!         description: "Simple counter",
147//!         database: COUNTER_DB,
148//!         fields: [
149//!             id: { r#type: FieldType::String, primary_key: true, required: true },
150//!             value: { r#type: FieldType::Integer, required: true },
151//!         ],
152//!     }
153//! }
154//! ```
155//!
156//! Replace `MEM_ENGINE_ID` with the engine constant from the table in step 1 when the Counter
157//! belongs on another backend.
158//!
159//! See [`valence_schema!`] and [`valence_trait_schema!`] for the DSL field reference.
160//! Minimal schema example: workspace `examples/minimal-schema`.
161//! Macros and `valence-codegen` share one syn DSL parser (`valence-schema-dsl`), so
162//! host `schemas/*_valence_schema.rs` files accept the same syntax and semantics
163//! (including `database:` evaluators).
164//!
165//! ## 3. Set up build-time codegen
166//!
167//! Typed [`Model`] impls are **generated at compile time** from schema files under
168//! `schemas/` (for example `widget_valence_schema.rs`). Add a build dependency and a
169//! one-line `build.rs`:
170//!
171//! ```toml
172//! [dependencies]
173//! uf-valence = { git = "https://github.com/unified-field-dev/valence", package = "uf-valence", features = ["mem"] }
174//!
175//! [build-dependencies]
176//! uf-valence-codegen = { git = "https://github.com/unified-field-dev/valence", package = "uf-valence-codegen" }
177//! ```
178//!
179//! ```ignore
180//! // build.rs
181//! fn main() {
182//!     valence_codegen::build().expect("valence codegen failed");
183//! }
184//! ```
185//!
186//! Include generated models (this is what must be linked for typed CRUD and inventory):
187//!
188//! ```ignore
189//! valence::include_generated_models!();
190//! ```
191//!
192//! Schema files under `schemas/` are **scan inputs** for codegen; they are not
193//! `mod`-linked. End-to-end proof: workspace `examples/codegen-host` and
194//! `examples/product-model-host`. See the
195//! [valence-codegen](../valence_codegen/index.html) crate docs for custom roots via
196//! `build_with` / `CodegenConfig`.
197//!
198//! ## 4. Use generated models (CRUD)
199//!
200//! After codegen, call [`Model`] methods with a [`Valence`] runtime:
201//!
202//! ```ignore
203//! use valence::Model;
204//!
205//! // Widget is generated from schemas/widget_valence_schema.rs
206//! let created = Widget::create(widget, &valence).await?;
207//! let loaded = Widget::get(created.id(), &valence).await?;
208//! Widget::update(created.id(), updated, &valence).await?;
209//! Widget::delete(created.id(), &valence).await?;
210//! ```
211//!
212//! Product-shaped schemas and connections: `examples/product-model-host`.
213//!
214//! ## 5. Route multiple backends
215//!
216//! One [`Valence`] holds a heterogeneous [`DatabaseRouter`]. Schema `database:` evaluators
217//! pick the router key per table.
218//!
219//! ```rust,no_run
220//! # #[cfg(feature = "mem")]
221//! # async fn demo() -> valence::Result<()> {
222//! use std::sync::Arc;
223//! use valence::{InMemoryBackend, Valence, router_key, MEM_ENGINE_ID};
224//!
225//! let primary = router_key("primary", MEM_ENGINE_ID);
226//! let valence = Valence::builder()
227//!     .add_backend("primary", Arc::new(InMemoryBackend::new()))
228//!     .add_backend("archive", Arc::new(InMemoryBackend::new()))
229//!     .default_backend_key(primary)
230//!     .build()?;
231//! # let _ = valence;
232//! # Ok(())
233//! # }
234//! ```
235//!
236//! Runnable: `cargo run -p valence --example multi_backend --features mem`
237//!
238//! ## 6. Inject host ports
239//!
240//! Optional builder methods wire secrets, actor identity, endpoints, and telemetry:
241//! [`ValenceBuilder::secret_provider`], [`ValenceBuilder::actor_factory`],
242//! [`ValenceBuilder::endpoint_resolver`], [`ValenceBuilder::telemetry_sink`].
243//!
244//! Port table and reference impls: [`valence_core::ports`]. Storage adapter contract and
245//! third-party checklist: [`DatabaseBackend`]. Router semantics: [`DatabaseRouter`].
246//!
247//! ```rust
248//! # #[cfg(feature = "mem")]
249//! # fn demo() -> valence::Result<()> {
250//! use std::sync::Arc;
251//! use valence::{
252//!     ConsoleSink, EnvSecretProvider, InMemoryBackend, JsonActorFactory, NoopEndpointResolver,
253//!     Valence,
254//! };
255//!
256//! let _valence = Valence::builder()
257//!     .add_backend("default", Arc::new(InMemoryBackend::new()))
258//!     .secret_provider(Arc::new(EnvSecretProvider))
259//!     .actor_factory(Arc::new(JsonActorFactory))
260//!     .endpoint_resolver(Arc::new(NoopEndpointResolver))
261//!     .telemetry_sink(Arc::new(ConsoleSink))
262//!     .build()?;
263//! # Ok(())
264//! # }
265//! ```
266//!
267//! ## How the pieces link together
268//!
269//! ```text
270//! schemas/*.rs ──► build.rs (valence_codegen::build) ──► $OUT_DIR/generated_models.rs
271//!   (scan inputs)                                              │
272//!                                                              │ include_generated_models!
273//!                                                              ▼
274//!                                              impl Model + inventory submit
275//!                                                              │
276//!                              SchemaRegistry ◄────────────────┤
277//!                                                              ▼
278//!                                              typed CRUD on Valence
279//!                                                              │
280//!                              Valence runtime ◄── DatabaseRouter / backends
281//! ```
282//!
283//! **Dependency rules:** `valence-core` owns ports and runtime (no engine SDK);
284//! `valence-backend-*` advertise open `ENGINE_ID`s; the facade re-exports behind features;
285//! apps own schema roots and call `valence-codegen` from `build.rs`; one operation stays on
286//! one backend; host adapters inject at boot.
287//!
288//! # Next steps
289//!
290//! | Task | Start here |
291//! |------|------------|
292//! | Schema DSL fields | [`valence_schema!`], [`valence_trait_schema!`] |
293//! | Build-time codegen | [valence-codegen](../valence_codegen/index.html), `examples/codegen-host` |
294//! | Wire storage | [`Valence::builder()`], [`InMemoryBackend`] |
295//! | Model CRUD | [`Model`], `examples/product-model-host` |
296//! | Multi-backend routing | [`DatabaseRouter`], `multi_backend` example |
297//! | Custom adapter | [`DatabaseBackend`], `examples/acme-valence-backend-stub` |
298//! | SQLite | [`SqliteBackend`], `quickstart_sqlite` example |
299//! | IndraDB | [`IndradbBackend`], `quickstart_indradb` example |
300//! | Surreal embedded | [`SurrealEmbeddedBackend`], `surreal_embedded` example |
301//! | Postgres | [`PostgresBackend`], `quickstart_postgres` (env-gated) |
302//! | MongoDB | [`MongoBackend`], `quickstart_mongodb` (env-gated) |
303//! | Redis | [`RedisBackend`], `quickstart_redis` (env-gated) |
304//! | Admin runtime | [`SchemaRegistry`], [`QueryCore`], `examples/admin-runtime-host` |
305//! | Config / env vars | crate [`README.md`](README.md) |
306//!
307//! # Entry points
308//!
309//! - [`prelude`] — ergonomic schema authoring imports
310//! - [`Valence`] / [`ValenceBuilder`] — runtime assembly
311//! - [`valence_schema!`] — schema DSL macro
312//! - [`Model`] — generated CRUD surface
313//! - [`DatabaseBackend`] / [`DatabaseRouter`] — storage ports
314//!
315//! # Prerequisites and gotchas
316//!
317//! - Enable backend features explicitly (`mem` is on by default).
318//! - Product schemas and codegen roots belong in **your** application.
319//! - Wire adapters (postgres/mongodb/redis) need live URLs; examples skip cleanly when unset.
320//! - SurrealDB support lives in `valence-backend-surreal` (feature `surreal`), not in core ports.
321//! - Generated models (or macro-expanded schemas) must be linked into the binary or
322//!   `inventory` will not see them.
323//!
324//! # Runnable examples
325//!
326//! | Example | Features | Notes |
327//! |---------|----------|-------|
328//! | `quickstart` | `mem` | Schema + mem boot + registry proof |
329//! | `multi_backend` | `mem` | Multiple logical backends |
330//! | `quickstart_sqlite` | `sqlite` | Embedded SQLite |
331//! | `quickstart_indradb` | `indradb` | Embedded IndraDB |
332//! | `surreal_embedded` | `surreal` | Surreal mem engine |
333//! | `quickstart_postgres` | `postgres` | Requires `DATABASE_URL` |
334//! | `quickstart_mongodb` | `mongodb` | Requires `VALENCE_MONGODB_URI` |
335//! | `quickstart_redis` | `redis` | Requires `VALENCE_REDIS_URL` |
336//! | `quickstart_telemetry` | `mem,telemetry-console` | Console telemetry sink |
337//!
338//! ```bash
339//! cargo run -p valence --example quickstart --features mem
340//! cargo run -p valence --example quickstart_sqlite --features sqlite
341//! cargo run -p valence --example quickstart_indradb --features indradb
342//! cargo run -p valence --example surreal_embedded --features surreal
343//! ```
344
345extern crate self as valence;
346
347mod include_generated;
348
349pub use valence_core::*;
350pub use valence_macros::*;
351
352#[cfg(feature = "telemetry-console")]
353pub use valence_telemetry::*;
354
355#[cfg(feature = "mem")]
356pub use valence_backend_mem::{
357    install_default_mem_router, InMemoryBackend, ENGINE_ID as MEM_ENGINE_ID,
358};
359
360#[cfg(feature = "sqlite")]
361pub use valence_backend_sqlite::{
362    SqliteBackend, ENGINE_ID as SQLITE_ENGINE_ID, PRIMARY as SQLITE_PRIMARY,
363};
364
365#[cfg(feature = "postgres")]
366pub use valence_backend_postgres::{
367    PostgresBackend, ENGINE_ID as POSTGRES_ENGINE_ID, PRIMARY as POSTGRES_PRIMARY,
368};
369
370#[cfg(feature = "mongodb")]
371pub use valence_backend_mongodb::{
372    MongoBackend, ENGINE_ID as MONGODB_ENGINE_ID, PRIMARY as MONGODB_PRIMARY,
373};
374
375#[cfg(feature = "indradb")]
376pub use valence_backend_indradb::{
377    IndradbBackend, ENGINE_ID as INDRADB_ENGINE_ID, PRIMARY as INDRADB_PRIMARY,
378};
379
380#[cfg(feature = "redis")]
381pub use valence_backend_redis::{
382    RedisBackend, ENGINE_ID as REDIS_ENGINE_ID, PRIMARY as REDIS_PRIMARY,
383};
384
385#[cfg(feature = "surreal")]
386pub use valence_backend_surreal::{
387    bootstrap_embedded_router, connect_embedded_at_path, extract_id_from_record_display,
388    extract_id_from_select_value, register_embedded_logical_names,
389    register_embedded_logical_names_slices, shared_router_with_embedded_logical_names,
390    surreal_record_id_for, EmbeddedEngine, RegisterEmbeddedLogicalNamesOptions, SDb,
391    SurrealEmbeddedBackend, SurrealMemBackend, ENGINE_ID as SURREAL_ENGINE_ID,
392};
393
394#[cfg(all(feature = "surreal", feature = "surreal-inventory"))]
395pub use valence_backend_surreal::{
396    bootstrap_embedded_router_from_inventory, collect_distinct_embedded_surreal_logical_names,
397    register_embedded_logical_names_from_inventory, DEFAULT_EMBEDDED_SURREAL_LOGICAL_NAMES,
398};
399
400#[cfg(all(feature = "surreal", feature = "surreal-connect-env"))]
401pub use valence_backend_surreal::{
402    connect_embedded_from_env, database_from_env, embedded_engine_from_env, embedded_path_from_env,
403    namespace_from_env,
404};
405
406#[cfg(all(feature = "surreal", feature = "surreal-remote"))]
407pub use valence_backend_surreal::SurrealRemoteBackend;
408
409/// Hidden re-exports for generated model code and platform migrations.
410#[doc(hidden)]
411pub mod __internal {
412    pub use valence_core::__internal::{CompiledQuery, QueryCompiler};
413}
414
415/// Ergonomic imports for schema authoring and generated models.
416pub mod prelude {
417    pub use crate::{
418        valence_schema, valence_trait_schema, Cardinality, Database, DatabaseEvaluator,
419        DatabaseFromEngine, FieldChange, FieldOperation, FieldType, IdHolder, Model, Mutation,
420        MutationKind, OnDelete, RecordId, Reference, Role, SideEffect, Validator, WithReference,
421        DEFAULT_IN_MEMORY, DEFAULT_IN_MEMORY_ROUTER_KEY,
422    };
423}
424
425#[cfg(test)]
426mod tests {
427    use super::*;
428    use std::sync::Arc;
429
430    #[test]
431    fn facade_reexports_core() {
432        let _ = router_key("default", KnownEngines::INMEMORY_MEM);
433    }
434
435    #[cfg(feature = "mem")]
436    #[tokio::test]
437    async fn mem_feature_wires_backend() {
438        let valence = Valence::builder()
439            .add_backend("default", Arc::new(InMemoryBackend::new()))
440            .build()
441            .expect("build");
442        assert_eq!(valence.active_backend().unwrap().engine_id(), MEM_ENGINE_ID);
443    }
444
445    #[cfg(feature = "surreal")]
446    #[tokio::test]
447    async fn surreal_feature_wires_backend() {
448        use surrealdb::engine::local::Mem;
449
450        let db = valence_backend_surreal::SDb::init();
451        db.connect::<Mem>(()).await.expect("connect");
452        db.use_ns("test").use_db("test").await.expect("ns");
453        let valence = Valence::builder()
454            .add_backend("default", Arc::new(SurrealEmbeddedBackend::new(db)))
455            .build()
456            .expect("build");
457        assert_eq!(
458            valence.active_backend().unwrap().engine_id(),
459            SURREAL_ENGINE_ID
460        );
461    }
462}