Expand description
Djogi — A Model-first web framework for Rust.
Define your data schema as Rust structs, and the framework derives
everything else: ORM, migrations, admin UI, audit trail, shell bindings,
JSONB schema handling.
Djogi’s core is web-framework-agnostic — it owns the data layer and
delegates HTTP routing/middleware/rendering to whichever Rust web
framework the adopter chooses. Axum is the most-supported integration
target today and ships behind the opt-in axum feature flag; it is not
a core dependency.
§Crate layout at a glance
| Module | Role |
|---|---|
config | DjogiConfig loaded from Djogi.toml + env (figment). |
context | DjogiContext — carries either a pooled handle or active transaction. Replaces E: Executor generics on Model + QuerySet signatures . |
descriptor | ModelDescriptor and friends — the single source of truth about every registered model. Populated by #[model] via inventory::submit!. |
error | DjogiError — the one error type returned by every Model method. |
model | The Model trait the macro implements for every user struct. Defined in . |
query | Filter AST: public API is Condition + FieldRef (plus QuerySet<T> and OrderExpr). Low-level enums (Leaf, LookupOp, FilterValue) live under djogi::query::internal for advanced/custom emitters. Filled in across . |
relation | Relation field types — ForeignKey<T>, OneToOneField<T>, resolved-cache wrappers, and OnDelete. Landed in ; extended by Tasks 2–6 with RelationPath, ManyToMany, and the macro glue. |
types | DateTime, Date, and re-exports of HeerId/RanjId — the canonical types imported via prelude. |
§Recommended usage
ⓘ
use djogi::prelude::*;
#[model(table = "posts")]
struct Post {
title: String,
body: String,
}prelude::* brings in the #[model] attribute macro, Model trait,
canonical types (DateTime, Date, HeerId, RanjId), and the
DjogiError enum — everything a model definition needs.
Re-exports§
pub use migrate::DescriptorProvider;pub use migrate::InventoryDescriptorProvider;pub use visage_boundary::DjogiVisageOf;pub use apps::App;pub use apps::AppDescriptor;pub use apps::AppIdentity;pub use apps::AppRegistry;pub use apps::CrossAppEdge;pub use compose::Auditable;pub use compose::SoftDeletable;pub use context::DjogiContext;pub use descriptor::ComputedFieldDescriptor;pub use descriptor::DefaultVolatility;pub use descriptor::DeferrabilitySpec;pub use descriptor::DerivedProjection;pub use descriptor::EnumDescriptor;pub use descriptor::FieldDescriptor;pub use descriptor::FieldSqlType;pub use descriptor::GeographySubtype;pub use descriptor::IndexColumnSpec;pub use descriptor::IndexKind;pub use descriptor::IndexNameKind;pub use descriptor::IndexNameTarget;pub use descriptor::IndexNullsOrder;pub use descriptor::IndexOrder;pub use descriptor::IndexSpec;pub use descriptor::IndexTarget;pub use descriptor::IndexType;pub use descriptor::ModelDescriptor;pub use descriptor::PartitionSpec;pub use descriptor::PkType;pub use descriptor::ProtectedFieldMetadata;pub use descriptor::RangeSubtypeKind;pub use descriptor::RedactionPolicy;pub use descriptor::RetentionLabel;pub use descriptor::RustSourceType;pub use descriptor::Sensitivity;pub use descriptor::VisageDescriptor;pub use descriptor::index_name;pub use pg::pool::DjogiPool;pub use hooks::ModelHooks;pub use jsonb::Jsonb;pub use jsonb::JsonbPathComparable;pub use jsonb::JsonbPathRef;pub use jsonb::JsonbSchema;pub use jsonb::JsonbSqlCast;pub use jsonb::MirJzSON;pub use jsonb::MirJzSONError;pub use jsonb::UnknownField;pub use jsonb::UnknownFieldExt;pub use pg::decode::FromPgRow;pub use primary_key::PrimaryKey;pub use primary_key::PrimaryKeyClientGen;pub use primary_key::PrimaryKeyDbGen;pub use error::DbError;pub use error::DjogiError;pub use expr::AggregateExpr;pub use expr::Case;pub use expr::CaseBuilder;pub use expr::DenseRank;pub use expr::Exists;pub use expr::Expr;pub use expr::HypotheticalSetAgg;pub use expr::KindEvidence;pub use expr::MetadataAgg;pub use expr::OrderedSetAgg;pub use expr::OuterRef;pub use expr::QualifyCondition;pub use expr::QualifyOp;pub use expr::Rank;pub use expr::RowNumber;pub use expr::Subquery;pub use expr::ValueAgg;pub use expr::WindowRanking;pub use expr::grouping_of;pub use field_codec::FieldCodec;pub use fts::FtsDescriptor;pub use fts::TsQuery;pub use fts::TsVector;pub use fts_query::FtsFieldRef;pub use query::AggregateQuery;pub use query::AnnotatedQuerySet;pub use query::ArrayPredicate;pub use query::CachedPortableQuerySet;pub use query::ClosureModel;pub use query::ConditionExt;pub use query::ConflictAction;pub use query::ConflictColumns;pub use query::ConflictCondition;pub use query::ConflictExpr;pub use query::ConflictTarget;pub use query::ConflictUpdate;pub use query::CteQuerySet;pub use query::DjogiPortableEq;pub use query::ExcludedRef;pub use query::ExplicitPgOrderable;pub use query::FieldRef;pub use query::FilterClause;pub use query::InnerLateral;pub use query::InsertSelectColumn;pub use query::InsertSelectSource;pub use query::InsertSelectStmt;pub use query::IntoAggregateTuple;pub use query::IntoConflictColumn;pub use query::IntoConflictCondition;pub use query::IntoConflictExpr;pub use query::IntoConflictUpdates;pub use query::IntoCteBody;pub use query::IntoFieldFilterValue;pub use query::IntoFilterValue;pub use query::IntoInsertColumns;pub use query::IntoPortableFieldValue;pub use query::IntoSetOpArm;pub use query::JoinedAnnotatedQuerySet;pub use query::JoinedAnnotatedRow;pub use query::JoinedQuerySet;pub use query::LateralQuerySet;pub use query::LeftLateral;pub use query::Lookup;pub use query::MaterializeClosureOptions;pub use query::MaterializeClosureReport;pub use query::MergeCounts;pub use query::MergeStmt;pub use query::ModelCursorStream;pub use query::ModelFilter;pub use query::OnConflictClause;pub use query::OrderExpr;pub use query::PairClosureKinshipSum;pub use query::PairOrderExpr;pub use query::PairSide;pub use query::PairWindowExt;pub use query::PortableQuerySet;pub use query::Q;pub use query::QuerySet;pub use query::RawCursorStream;pub use query::RecursiveArm;pub use query::RecursiveDirection;pub use query::RecursiveQuerySet;pub use query::SetOpKind;pub use query::SetOpQuerySet;pub use query::UpdateAssignment;pub use query::UpdateStmt;pub use query::VisageExists;pub use query::VisageQuerySet;pub use relation::ForeignKey;pub use relation::ForeignKeyResolved;pub use relation::JoinedRow;pub use relation::ManyToMany;pub use relation::OnDelete;pub use relation::OneToOneField;pub use relation::OneToOneFieldResolved;pub use relation::PrefetchedRow;pub use tracked::Tracked;pub use types::Date;pub use types::DateTime;pub use types::Interval;pub use types::Range;pub use types::RangeBound;pub use visage::DjogiVisage;pub use visage::VisageError;
Modules§
- apps
- Compile-time schema ownership domains — the apps subsystem.
Users declare apps once per crate via the
djogi::apps!function-like proc macro: - array
- Array column operator helpers —
contains,contained_by,overlap,len. - auth
- Authentication substrate for Djogi.
- cache
- Re-exports sassi cache primitives so adopters can
use djogi::cache::*;without an explicit sassi dep in their Cargo.toml. Spec:docs/spec/maahi/caching.md(“Why sassi”). - compose
- Composition primitives —
AuditableandSoftDeletable. These are the runtime trait surfaces a model picks up when adopters opt in via#[model(auditable)](supersedes the legacy#[derive(Auditable)]per spec line 1037, locked 2026-05-03) or#[model(soft_deletable)](supersedes the legacy#[derive(SoftDeletable)]for symmetry with the auditable surface and to de-risk automatic default-filter composition). Initial landing covered trait shapes only; the full macro emissions are the source of truth for behavior. Downstream code that only needs to bound a generic on “models with audit fields” or “models with soft-delete semantics” can import these traits today. - config
- Configuration via
Djogi.toml+ environment variables.DATABASE_URLenv var always overrides[database].url. Secrets live in env vars, never inDjogi.toml. - context
- The
DjogiContexttype — carries either a pooled handle or an active transaction. Per v3 specification,DjogiContextreplaces theE: Executorgeneric on everyModelCRUD andQuerySetmethod signature. This change unifies the API: the same method can be called against a pool or inside a transaction without reborrows or type juggling. - descriptor
- Runtime model descriptors — emitted by
#[model]viainventory, consumed by the migration system,djogi docs, RLS generation, and the partitioning analyzer . - enum_
- Runtime glue for
#[derive(DjogiEnum)]. Most of the codec logic (ToSql / FromSql impls) is generated per-enum by the proc macro. This module holds shared error types and re-exports that complete the runtime surface. - error
- The single error type returned by every framework CRUD operation.
DjogiErrorwraps the sources of failure that can occur when aModelmethod runs: database driver errors, expected-row-count violations, and ID generation failures. Keeping one error type at the public API makes?-propagation ergonomic: user code callsPost::get(&pool, id).await?and gets aDjogiErrorwithout having to juggle per-subsystem errors. The variants correspond 1:1 to the failure modes the generated CRUD impls can produce: - expr
- Typed expression IR — the substrate for field-vs-field filters, arithmetic assignments, aggregates, and subqueries.
- field_
codec - Field-level codecs for at-rest transformations of protected fields. Trait + compile-time registry surface.
- fts
- Full-text search types —
TsVectorandTsQuery. - fts_
query - FTS query builder types —
FtsFieldRefwith.matches()and.rank(). - hooks
- Lifecycle hooks for
ModelCRUD operations. Six methods, each defaulted to a no-op that returnsOk(()). Adoptersimpl ModelHooks for MyModelselectively — methods they don’t override stay no-op. TheHasHooksmarker trait is sealed and emitted by the macro layer (#[model(hooks)]) to gate monomorphic dispatch in the CRUD terminals. - intent
IntentFilereader + precedence resolver. Adopters ship.djogi/intent.jsonat the workspace root carrying human-readable rationale for models and fields. The file is side-channel — its contents never reach the schema, migrations, or runtime; onlydjogi docsreads it, merging the rationale into the generated Markdown. Absent file ⇒Ok(None)fromload. I/O and parse errors are hardErrs so a typo doesn’t silently disable rationale rendering. Resolver precedence (resolve_model_rationale/resolve_field_rationale): macro attribute wins, intent.json falls back.- jsonb
Jsonb<T>— typed JSONB column wrapper with unknown-field preservation.- live_
migrate - Live-migration layer — skeleton.
live_migrateis the home for operator-driven compatibility-window rollouts (the spec’s expand → backfill → flip → contract sequence). It sits above the segment planner: every type that flows through this module is imported fromcrate::migraterather than redefined here. The boundary between the two layers is theOnlineSafetyClassificationenum frozen inmigrate::schema. - migrate
- Migration system — the home for everything that takes
ModelDescriptorinventory and lowers it to executable Postgres migrations. The module fans out to several concerns: - model
- The
Modeltrait — the contract every#[model]struct satisfies. All CRUD methods take&mut DjogiContext, so the same call site works against a pool or inside a transaction without re-borrows or type juggling: - outbox
- Transactional outbox — (write side) + (worker side).
Models annotated with
#[model(events)]emit one outbox row into{table}_outboxon every successfulcreate,save,delete,update_returning_pair, ordelete_returningperformed through aDjogiContext. Because the outbox INSERT shares the same context (and therefore the same active transaction), the outbox row commits or rolls back atomically with the primary write — no separate “event publisher” fan-out is required at the write side. A downstream poller / CDC consumer drains{table}_outboxasynchronously. - pg
- Internal Postgres substrate —
SqlAccumulator,DjogiPool, andPgConnection. This module ispub(crate)— it is an implementation detail of the framework, not part of the public API. Macro-emitted code routes through::djogi::__private::pg(re-exported fromlib.rs), not through::djogi::pgdirectly. - pg_
types - Postgres typed-surface newtypes —).
This module owns the Rust newtypes that wrap Postgres column types
postgres-typesdoes not encode natively (or whose native encoding does not fit the typed surface djogi wants to expose). Each newtype ships its ownpostgres_types::{ToSql, FromSql}impl against the raw Postgres wire format — djogi pulls in no third-party crate to bridge these types. - prelude
- The canonical adopter import.
use djogi::prelude::*;brings the framework’s adopter-facing surface into scope in a single line: every type a model definition or CRUD call site needs, every macro the model surface depends on, and the canonical type re-exports (HeerId,RanjId,Date,DateTime). - presentation
- Per-scope presentation codecs for protected fields.
This module provides the runtime trait surface for the
#[field(protected(per_scope = { ... }))]presentation-codec feature (GH #227). A presentation codec transforms a model’s stored field value at visage-projection time — after at-rest decode and before the visage is serialized or returned to the caller. - primary_
key - Primary-key trait surface.
Three-trait split per
docs/spec/decisions.md: - query
- Query API — lazy
QuerySet<T>, typed filters, SQL emission. The public surface is re-exported at crate root and inprelude: users writeuse djogi::prelude::*;and getQuerySet,FieldRef,Lookup,Q, etc. without a second import. Internally:querysetholds the builder state,conditionthe filter tree (the legacy substrate, retired in favour ofQ<T>),qthe public Q-algebra,fieldthe typed column handles,orderordering expressions,filterthe programmatic-builder types,updatebulk-update assignments,sqltheConditionBuilder+ SQL emitters, andterminalthefetch_*methods. Splitting by responsibility keeps each file auditable. - range
- Postgres range predicate payloads.
Range<T>itself lives incrate::pg_types. This module owns the query-side payloads for PostgreSQL range operators soquery::conditionandquery::fieldcan share a small typed representation without storing SQL fragments in the condition tree. Range predicates are SQL-only. They are exposed from root model fields throughexplicit_pg_predicate()because Postgres range canonicalization and operator semantics are not portable to Punnu/Rust evaluation. - relation
- Relation field types and (later) relation-aware query extensions. Lands the runtime wrappers only:
- snapshot
- Migration snapshot integrity primitives.
migrations/schema_snapshot.jsonrecords the schema-of-record after every successfuldjogi migrations apply. The runner persists it atomically the descriptor differ then reads it back on the nextcargo buildto decide whether to emit a new pending migration. If a snapshot file is tampered with (e.g. someone hand-edits it to suppress a drift warning, or a corrupted CI cache resurfaces a stale copy), the differ will silently mis-classify drift and the migration history goes out of sync with the live schema. Thesnapshot::signsubmodule (this module’s first inhabitant) provides the HMAC-SHA256 sign-and-verify primitives that detect such tampering. The higher-level surfaces built on this module: - testing
- Test-harness runtime helpers for
#[djogi_test]. This module provides the per-test-database lifecycle machinery used by the#[djogi_test]proc-macro attribute: - tracked
- Dirty-tracking wrapper for model fields —
Tracked<T>. - trait_
registry - Trait registry for cross-type queries via
#[djogi::trait_impl]. - transaction
atomic(...)— the canonical transaction scope + retry helper.- types
- Core type aliases and re-exports used in
#[model]struct definitions. Every#[model]struct pulls these in viadjogi::prelude::*, so users never need per-field imports for the framework’s canonical types. The aliases (DateTime,Date) pin the project to thetimecrate — the framework explicitly avoidschrono(see CLAUDE.md “Dependencies excluded”). - visage
- Visage runtime — trait surface, error types, and sealed
projection metadata for generated visage structs.
#[model]emits four visage structs per model ({Model}Public,{Model}SelfView,{Model}Admin,{Model}Export) plus conversion impls. This module holds the runtime-side types those emissions depend on: - visage_
boundary - Sealed boundary marker trait for visage projections.
impl DjogiVisageOf<M> for Vasserts thatVis a visage (or the model itself) projecting fields from modelM. The reflexive blanketimpl<M: Model> DjogiVisageOf<M> for Mlets model-scoped code that expects “something that is-or-projects-M” accept either the raw model or one of its visages uniformly.
Macros§
- apps
- Declare the crate’s compile-time schema ownership domains.
djogi::apps!takes a block of unit-struct declarations, each carrying an#[app(...)]attribute describing the database target and (optionally) an explicit label: - link_
anchor - Emit a once-per-crate linkage anchor so an adopter binary can force
link-time retention of THIS crate’s
#[derive(Model)]registrations without listing every model type (#370, branch b). - many_
to_ many - Emit one direction of a many-to-many relation — the
ManyToMany<Target>trait impl, the named inherent accessor on the source type, and an inventory marker for . Invocation form: - primary_
key - Declarative-style macro for declaring custom primary-key types.
Emits a
pub struct <Name>(<Inner>);newtype plus the trait impls the#[model(pk = <Name>)]attribute relies on —PrimaryKey(with itsKIND/SQL_TYPE/DEFAULT_SQLassociated consts),ToSql/FromSqldelegation to the inner type, and optionallyPrimaryKeyDbGen(whenbulk_sql = "..."is set) orPrimaryKeyClientGen(whengenerate = |...| expris set). - reverse_
one_ to_ many - Emit a reverse one-to-many accessor on a model. Invocation form:
- reverse_
one_ to_ one - Emit a reverse one-to-one accessor on a model. Invocation form:
Structs§
- HeerId
- 64-bit time-ordered identifier with ascending raw-bit order.
- Heer
IdDesc - 64-bit time-ordered identifier with descending raw-bit order; the
reverse-chronologically-sorted sibling of
HeerId. - Heer
IdRecency Biased - 64-bit time-ordered identifier with descending raw-bit order; the
reverse-chronologically-sorted sibling of
HeerId. - RanjId
- 128-bit UUIDv8 time-ordered identifier with ascending raw-bit order.
- Ranj
IdDesc - 128-bit UUIDv8 time-ordered identifier with descending raw-bit
order; the reverse-chronologically-sorted sibling of
RanjId. - Ranj
IdRecency Biased - 128-bit UUIDv8 time-ordered identifier with descending raw-bit
order; the reverse-chronologically-sorted sibling of
RanjId.
Enums§
- Basic
Predicate - Universal predicate algebra over
T.
Type Aliases§
- Result
- Crate-scoped
Resultalias —Result<T, DjogiError>. Every fallible Djogi operation returns aDjogiErrorfrom a single public error type, so adopter code can name the success type alone:
Attribute Macros§
- deliberately_
bypass_ convention_ with_ raw_ sql - Brings
djogi::__bypass::{RawAccessExt, RawPoolAccessExt}into scope for the decorated item, unlocking direct access to djogi’s raw SQL escape hatches at explicit, auditable sites. - djogi_
test - Per-test database lifecycle harness.
Transforms an
async fn my_test(ctx: DjogiContext)into a plain#[test]wrapper that builds a Tokio runtime throughdjogiand: - trait_
impl #[djogi::trait_impl]— trait-registry attribute. Wraps a traitimplblock so cross-cutting consumers (Sassi::all_impl::<dyn T>) can iterate every model that implements the trait at runtime without enumerating eachimplsite by hand.
Derive Macros§
- Djogi
Enum - Derive typed Postgres enum support.
Emits
postgres_types::ToSql+FromSqlimpls that encode/decode the enum as its mapped Postgres string label, plus aninventory::submit!of anEnumDescriptorfor the migration differ. - Jsonb
Schema - Derive the typed JSONB deep-path API for a schema struct.
Applying this derive to a named struct causes the macro to emit a
{T}Path<M>struct with one method per field. Scalar fields (from the cast-matrix allowlist:i16,i32,i64,f32,f64,bool,String,time::OffsetDateTime,time::Date,uuid::Uuid,rust_decimal::Decimal,serde_json::Value,HeerId,RanjId) return aJsonbPathRef<M, FieldType>ready for comparison. All other field types are assumed to implementJsonbSchemaand their method returns the nested type’sPath<M>with the path accumulator extended. - Model
- No-op stub — field injection requires
#[model](attribute macro). Kept as a placeholder for future derive-based extensions, and as the registration site for helper attributes that adopters write on the derived struct. NOTE: Onlyfieldandderivedare listed as helper attributes here, notmodel. Listingmodelas a helper would shadow the#[model]proc_macro_attribute and cause ambiguous resolution (Post-Review Fix #4).