1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
//! Backbone Module System
//!
//! This module provides the trait for modules to register themselves with
//! the framework, including migration discovery for automatic database setup.
//!
//! Inspired by Laravel's Service Provider pattern.
use async_trait::async_trait;
use std::path::PathBuf;
/// Metadata about a module's migrations
#[derive(Debug, Clone)]
pub struct MigrationInfo {
/// Module name (e.g., "bersihir")
pub module: String,
/// Path to migrations directory
pub path: PathBuf,
/// Number of migrations found (optional, for status display)
pub count: Option<usize>,
}
/// Metadata about a module's seeds
#[derive(Debug, Clone)]
pub struct SeedInfo {
/// Module name
pub module: String,
/// Path to seeds directory
pub path: PathBuf,
}
/// Trait for Backbone modules with migration support
///
/// Implement this trait to register a module with the Backbone framework.
/// The framework will automatically discover and run migrations for all
/// registered modules in dependency order.
///
/// # Example
///
/// ```ignore
/// use backbone_core::module::BackboneModule;
/// use std::path::PathBuf;
///
/// pub struct BersihirModule;
///
/// impl BackboneModule for BersihirModule {
/// fn name(&self) -> &'static str { "bersihir" }
/// fn version(&self) -> &'static str { env!("CARGO_PKG_VERSION") }
///
/// fn dependencies(&self) -> Vec<&'static str> {
/// vec!["sapiens"] // Depends on user module
/// }
///
/// fn migrations_path(&self) -> Option<PathBuf> {
/// Some(PathBuf::from("libs/modules/bersihir/migrations"))
/// }
/// }
/// ```
#[async_trait]
pub trait BackboneModule: Send + Sync {
/// Returns the unique identifier for this module
///
/// This should be a short, lowercase name like "bersihir", "sapiens", etc.
fn name(&self) -> &'static str;
/// Returns the version of this module
///
/// Typically uses `env!("CARGO_PKG_VERSION")` to get from Cargo.toml
fn version(&self) -> &'static str;
/// Returns the list of module names this module depends on
///
/// Dependencies are used to determine migration order. A module's
/// migrations will only run after all its dependencies have been migrated.
///
/// # Returns
/// A list of module names (e.g., `vec!["sapiens", "bucket"]`)
fn dependencies(&self) -> Vec<&'static str> {
vec![]
}
/// Returns the path to this module's migrations directory
///
/// Path should be relative to the repository root.
/// Return `None` if the module has no migrations.
///
/// # Example paths:
/// - `libs/modules/bersihir/migrations`
/// - `libs/modules/sapiens/migrations`
fn migrations_path(&self) -> Option<PathBuf>;
/// Returns the path to this module's seeds directory (optional)
///
/// Seeds are used to populate the database with initial data.
fn seeds_path(&self) -> Option<PathBuf> {
None
}
/// Called after migrations are run to initialize the module
///
/// Use this for any setup that needs to happen after the database
/// is ready but before the application starts serving requests.
async fn on_boot(&self) -> anyhow::Result<()> {
Ok(())
}
/// Called when the application is shutting down
///
/// Use this for cleanup tasks.
async fn on_shutdown(&self) -> anyhow::Result<()> {
Ok(())
}
/// Perform a health check for this module
///
/// Returns `true` if the module is healthy and ready to serve requests.
async fn health_check(&self) -> bool {
true
}
/// Get migration info for this module
fn migration_info(&self) -> Option<MigrationInfo> {
self.migrations_path().map(|path| MigrationInfo {
module: self.name().to_string(),
path,
count: None,
})
}
/// Get seed info for this module
fn seed_info(&self) -> Option<SeedInfo> {
self.seeds_path().map(|path| SeedInfo {
module: self.name().to_string(),
path,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestModule;
impl BackboneModule for TestModule {
fn name(&self) -> &'static str {
"test"
}
fn version(&self) -> &'static str {
"0.1.0"
}
fn dependencies(&self) -> Vec<&'static str> {
vec!["core"]
}
fn migrations_path(&self) -> Option<PathBuf> {
Some(PathBuf::from("libs/modules/test/migrations"))
}
fn seeds_path(&self) -> Option<PathBuf> {
Some(PathBuf::from("libs/modules/test/migrations/seeds"))
}
}
#[test]
fn test_module_metadata() {
let module = TestModule;
assert_eq!(module.name(), "test");
assert_eq!(module.version(), "0.1.0");
assert_eq!(module.dependencies(), vec!["core"]);
}
#[test]
fn test_migration_info() {
let module = TestModule;
let info = module.migration_info();
assert!(info.is_some());
let info = info.unwrap();
assert_eq!(info.module, "test");
assert_eq!(info.path, PathBuf::from("libs/modules/test/migrations"));
}
#[test]
fn test_seed_info() {
let module = TestModule;
let info = module.seed_info();
assert!(info.is_some());
let info = info.unwrap();
assert_eq!(info.module, "test");
assert_eq!(info.path, PathBuf::from("libs/modules/test/migrations/seeds"));
}
}