iet-db-timescale
SQLx implementation of database lifecycle, migrations, entities, and repositories.
iet-db-timescaleimplementationTimescaleDB implementationHow to use it
iet-db-timescale is the TimescaleDB/Postgres persistence adapter. TimescaleDatabase owns a validated sqlx pool; entities registered in an EntityRegistry generate reviewed migration SQL; and the generated Repository<E, F> performs resource/scope-aware CRUD through a CallScope. It reads its settings from iet-config via DatabaseConfig.
// crates/features/iet-db-timescale/tests/repository.rs
use ;
use ;
// let config: iet_db_timescale::config::DatabaseConfig = ...;
let db = connect.await?;
let mut registry = default;
registry.?;
raw_sql.execute.await?;
let mut session = db.session;
let repository = new;
let product = repository
.insert_entity
.await?;
connectrequires a reachable TimescaleDB extension, version 2.13+;connect_namedlabels pool metrics andconnect_with_health_checkcontrols per-checkout pings.- Business repositories must use their
CallScopesession rather than the pool directly; unrelated database tables are never dropped. EntityRegistry::generate(...)writes versionedup/downmigration files instead of applying schema at startup.
How it works
Registering a Persistent entity records its columns and indexes in a SchemaRegistry plus any hypertable and foreign-key metadata; EntityRegistry::generate calls initial for an empty database or diff against the live catalog and writes a MigrationPair of up/down SQL. At runtime, repositories perform scoped CRUD: TimescaleDatabase::connect opens a validated pool, db.session yields a session, and Repository operations verify the session's ResourceId through CallScope::require_resource before touching the pool.
flowchart LR A["EntityRegistry::register"] --> B["SchemaRegistry"] A --> C["hypertables and relations"] B --> D["EntityRegistry::generate"] C --> D D --> E["initial or diff"] E --> F["MigrationPair up and down"] F --> G["migration files"] H["TimescaleDatabase::connect"] --> I["db.session"] I --> J["Repository::new"] J --> K["insert_entity"] K --> L["CallScope::require_resource"]
What it implements
- Implements
iet_db::Databasefromiet-db.
What's inside
Modules
src/catalog.rssrc/config.rssrc/ddl.rssrc/filters.rssrc/generation.rssrc/health.rssrc/lib.rssrc/migration.rssrc/migration_files.rssrc/migration_observer.rssrc/persistence.rssrc/query.rssrc/schema.rssrc/session.rssrc/store_support.rs
Public API
(root)
- struct
TimescaleDatabase pub fn version(&self) -> &semver::Versionpub fn session(&self) -> TimescaleSessionpub fn pool(&self) -> &PgPool— Infrastructure stores may share the selected pool; business repositories must use their CallScope session instead of this independently.pub fn record_pool_metrics(&self)— Point-in-time pool pressure. No task or runtime is installed by the library.
catalog
- struct
CatalogColumn - struct
CatalogIndex - struct
CatalogTable - struct
CatalogConstraint
config
- struct
DatabaseConfig
ddl
pub fn initial<E: Persistent>() -> AppResult<Vec<String>>
filters
- enum
Kind - enum
Value - enum
Operator - trait
FilterField
- enum
Filter pub fn append<F: FilterField>( builder: &mut QueryBuilder<'_, Postgres>, filter: &Filter<F>, ) -> AppResult<()>
generation
- struct
EntityRegistry pub fn register<E: Persistent>(&mut self) -> AppResult<()>pub fn initial(&self, schema: &str) -> AppResult<MigrationPair>
health
- struct
PostgresProbe
migration
- struct
Migrations
migration_files
- struct
MigrationPair pub fn write_new( &self, directory: &Path, version: i64, name: &str, ) -> AppResult<(PathBuf, PathBuf)>
persistence
- enum
RecordId— Application-generated identifiers. UUID variants must be RFC UUID version 7. - trait
Entity— Mapping is implementation-owned, never derived from caller-provided SQL names.
- trait
EntityRow
- trait
EntityFields— Entity-defined field allowlists used by generated repository operations. Implementations are generated byPostgresEntityfrom#[field]metadata.
- struct
Repository— Generic repository for implementation-owned entities. Automatically handles resource checks, resource checks, soft-deletion filtering, query building, row mapping, and unique constraint error translation. pub fn new(resource: ResourceId) -> Selfpub fn resource_id(&self) -> &ResourceIdpub fn check_scope(&self, scope: &CallScope<'_, crate::TimescaleSession>) -> AppResult<()>- struct
Assignment— None is a typed SQL NULL, permitted only by nullable field metadata. - trait
MutableField— Mutable fields are an implementation-owned allowlist. System scope columns remain readable but cannot be written through generated entity operations.
- trait
WriteField
pub fn insert<E: Entity, F: WriteField>( assignments: &[Assignment<F>], _context: &CallContext, ) -> AppResult<QueryBuilder<'static, Postgres>>pub fn update<E: Entity, F: WriteField>( id: impl Into<RecordId>, assignments: &[Assignment<F>], context: &CallContext, ) -> AppResult<QueryBuilder<'static, Postgres>>pub fn update_text<E: Entity>( id: impl Into<RecordId>, field: &impl MutableField, value: String, context: &CallContext, ) -> AppResult<QueryBuilder<'static, Postgres>>pub fn active_scope<E: Entity>( query: &mut QueryBuilder<'_, Postgres>, _context: &CallContext, ) -> AppResult<()>— Every ordinary operation gets tenancy and active-row predicates together.pub fn select<E: Entity>( columns: &[&str], context: &CallContext, ) -> AppResult<QueryBuilder<'static, Postgres>>pub fn count<E: Entity>(context: &CallContext) -> AppResult<QueryBuilder<'static, Postgres>>pub fn delete<E: Entity>( id: impl Into<RecordId>, context: &CallContext, ) -> AppResult<QueryBuilder<'static, Postgres>>pub fn prepare<E: Persistent>(schema: &str) -> AppResult<Vec<String>>— Regular PostgreSQL deliberately ignores optional Timescale metadata.pub fn initial<E: Persistent>() -> AppResult<Vec<String>>— Initial reviewed DDL from entity metadata. Applying it remains deployment-owned.
query
- trait
Column— Implement only for trusted schema enums, never request strings.
- enum
Comparison pub fn text_filter( builder: &mut QueryBuilder<'_, Postgres>, column: &impl Column, comparison: Comparison, value: String, ) -> AppResult<()>pub fn integer_filter( builder: &mut QueryBuilder<'_, Postgres>, column: &impl Column, comparison: Comparison, value: i64, ) -> AppResult<()>pub fn seek_pair( builder: &mut QueryBuilder<'_, Postgres>, primary: &impl Column, primary_direction: Direction, position: i64, unique: &impl Column, unique_direction: Direction, id: i64,— Non-null integer tuple seek with a unique tie-breaker and independent directions. For before cursors invert directions for both seek and ORDER BY, then reverse results.pub fn order( builder: &mut QueryBuilder<'_, Postgres>, column: &impl Column, direction: Direction, ) -> AppResult<()>- enum
NullOrder pub fn seek_text_pair( builder: &mut QueryBuilder<'_, Postgres>, primary: &impl Column, ordering: (Direction, NullOrder), position: Option<String>, unique: &impl Column, unique_direction: Direction, i— Nullable text seek under deterministic C collation, with non-null unique ID. ORDER BY must use the same collation, directions and explicit null ordering.pub fn order_text( builder: &mut QueryBuilder<'_, Postgres>, column: &impl Column, direction: Direction, nulls: NullOrder, ) -> AppResult<()>
schema
- struct
ColumnMigrationHints - struct
ReviewedSchemaChanges— A pair is structurally reversible, not a guarantee of data restoration. - enum
SqlType - struct
ColumnSpec - struct
TableSpec - struct
ReviewPlan - struct
SchemaRegistry pub fn with_table_rename(mut self, table: &str, from: &str) -> AppResult<Self>— Names are desired registered names. Hints may precede registration.pub fn with_column_hints( mut self, table: &str, column: &str, hints: ColumnMigrationHints, ) -> AppResult<Self>pub fn approve_destructive(mut self, approved: bool) -> Self— Applies only todiff_pair_with_hints; legacy diff behavior is unchanged.pub fn register<E: iet_core::persistence::Persistent>(&mut self) -> AppResult<()>pub fn review(&self, schema: &str, current: &[CatalogTable]) -> AppResult<ReviewPlan>pub fn diff_pair_with_hints( &self, schema: &str, current: &[CatalogTable], ) -> AppResult<ReviewedSchemaChanges>— Explicit existing-column migrations. Unregistered tables are never dropped. Backfills retain changed data on rollback. Casts and relaxed nullability require approval and can still fail against the data present at execution. Missing DEFAULTS metadata means preserve, never infer DROP DEFAULT.pub fn initial_pair(&self) -> AppResult<crate::migration_files::MigrationPair>— Fresh schema, generated entirely offline. Down intentionally destroys tables.pub fn diff_pair( &self, schema: &str, current: &[CatalogTable], ) -> AppResult<Option<crate::migration_files::MigrationPair>>— Reversible additive diff. Existing type/nullability changes require authored steps. Only registered tables are managed; unrelated database tables are preserved.pub fn diff( schema: &str, current: &[CatalogTable], desired: &[TableSpec], ) -> AppResult<ReviewPlan>— No implicit table/column drops, type casts or default/generated changes.
session
- struct
TimescaleSession
store_support
pub fn subject(value: &str) -> AppResult<uuid::Uuid>pub fn valid(value: &str, max: usize) -> boolpub fn invalid() -> AppErrorpub fn backend(error: sqlx::Error) -> AppErrorpub fn millis(time: SystemTime) -> AppResult<i64>
Impl map
iet_app::health::Probe for PostgresProbeiet_core::SessionFactory for TimescaleDatabaseiet_db::Database for TimescaleDatabaseMigrationRunner for MigrationsMigrate for Observer<'_>From<uuid::Uuid> for RecordIdFrom<i64> for RecordIdSession for TimescaleSessionTransactionSession for TimescaleSessionConfigSource for Source
Depends on
External crates: chrono, rust_decimal, semver, serde, serde_json, sqlx, uuid
Used by
iet-auth-acl-timescaleiet-auth-basic-timescaleiet-auth-identity-timescaleiet-auth-profiles-timescaleiet-auth-rbac-timescaleiet-auth-revocations-timescaleiet-auth-session-timescale
In this repository
examples/htmx-starterexamples/skills-only-appexamples/skills-only-app-task-tracker
Source: crates/features/iet-db-timescale