| name | Extension: Infrastructure |
| description | Jobs, Schemas, Router, Assets, Storage Paths, Site Auth, and Config Validation trait implementations |
Extension: Infrastructure
Infrastructure traits provide background processing, database schemas, API routes, asset management, authentication, and config validation.
1. Jobs
Background tasks that run on schedule or on demand.
Trait
#[async_trait]
pub trait Job: Send + Sync {
fn name(&self) -> &'static str;
fn schedule(&self) -> &'static str;
fn tags(&self) -> Vec<&'static str>;
async fn execute(&self, ctx: &dyn JobContext) -> Result<JobResult>;
fn enabled(&self) -> bool { true }
fn run_on_startup(&self) -> bool { false }
}
Schedule Format
Cron syntax: seconds minutes hours day_of_month month day_of_week
| Example | Meaning |
|---|
0 0 * * * * | Every hour |
0 0 3 * * * | Daily at 3 AM |
0 */15 * * * * | Every 15 minutes |
Running Jobs Manually
systemprompt infra jobs run {job_name}
systemprompt infra jobs list
Registration
impl Extension for MyExtension {
fn jobs(&self) -> Vec<Arc<dyn Job>> {
vec![Arc::new(MyJob)]
}
}
2. Schemas
Database table definitions embedded in extensions at compile time.
impl Extension for MyExtension {
fn schemas(&self) -> Vec<SchemaDefinition> {
vec![SchemaDefinition::inline("my_table", include_str!("../schema/my_table.sql"))]
}
fn migration_weight(&self) -> u32 { 100 }
fn dependencies(&self) -> Vec<&'static str> {
vec!["users"]
}
}
Schema Conventions
| Convention | Rule |
|---|
| Primary key | id UUID PRIMARY KEY DEFAULT gen_random_uuid() |
| Timestamps | Always TIMESTAMPTZ, never TIMESTAMP |
| Indexes | Create for frequently queried columns |
| Naming | snake_case for tables and columns |
| Schema file | {crate}/schema/{table_name}.sql |
3. Router
HTTP API endpoints provided by extensions.
impl Extension for MyExtension {
fn router(&self, ctx: &dyn ExtensionContext) -> Option<ExtensionRouter> {
let db = ctx.database().clone();
let router = axum::Router::new()
.route("/my-endpoint", axum::routing::get(handle_get))
.route("/my-endpoint", axum::routing::post(handle_post))
.with_state(db);
Some(ExtensionRouter::new("/api/ext/my-extension", router))
}
}
Extension routes should mount under /api/ext/ to avoid colliding with core routes.
4. Assets
CSS, JavaScript, fonts, and images provided by extensions.
impl Extension for MyExtension {
fn declares_assets(&self) -> bool { true }
fn required_assets(&self, paths: &dyn AssetPaths) -> Vec<AssetDefinition> {
vec![
AssetDefinition::css("my-styles.css", include_bytes!("../assets/my-styles.css")),
]
}
}
Asset Pipeline
storage/files/css/ <- CSS source
storage/files/js/ <- JS source
extensions/web/src/assets.rs <- Register assets
web/dist/ <- Output (generated by copy_extension_assets)
5. Storage Paths
Directories required by the extension under storage/files/.
impl Extension for MyExtension {
fn required_storage_paths(&self) -> Vec<String> {
vec!["documents".into(), "exports".into()]
}
}
6. Site Authentication
impl Extension for MyExtension {
fn site_auth(&self) -> Option<SiteAuthConfig> {
Some(SiteAuthConfig {
protected_paths: vec!["/admin".into()],
login_redirect: "/admin/login".into(),
exclude_paths: vec!["/admin/login".into()],
})
}
}
7. Config Validation
impl Extension for MyExtension {
fn config_prefix(&self) -> Option<&str> { Some("my_extension") }
fn validate_config(&self, config: &Value) -> Result<ValidationReport> {
let mut report = ValidationReport::new();
if config.get("api_key").is_none() {
report.add_error("my_extension.api_key is required");
}
Ok(report)
}
}
8. Rules
| Rule | Rationale |
|---|
Jobs must return JobResult | Enables tracking in scheduler |
Schemas use include_str!() | Compile-time embedding |
| Migration weight 100+ for extensions | Core uses 1-40 |
Router endpoints under /api/ext/ | Avoid collisions |
All SQL uses TIMESTAMPTZ | Never TIMESTAMP |