mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 18:31:52 +00:00
- Run de/ser roundtrip in pre_init even when db version matches, ensuring all #[serde(default)] fields are populated before any typed access - Add patchdb.md documentation for TypedDbWatch patterns - Update TS bindings for CheckPortParams, CheckPortRes, ifconfigUrl - Update CLAUDE.md docs with patchdb and component-level references
106 lines
2.7 KiB
Markdown
106 lines
2.7 KiB
Markdown
# Patch-DB Patterns
|
|
|
|
## Model<T> and HasModel
|
|
|
|
Types stored in the database derive `HasModel`, which generates typed accessor methods on `Model<T>`:
|
|
|
|
```rust
|
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[model = "Model<Self>"]
|
|
pub struct ServerInfo {
|
|
pub version: Version,
|
|
pub network: NetworkInfo,
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Generated accessors** (one per field):
|
|
- `as_version()` — `&Model<Version>`
|
|
- `as_version_mut()` — `&mut Model<Version>`
|
|
- `into_version()` — `Model<Version>`
|
|
|
|
**`Model<T>` APIs:**
|
|
- `.de()` — Deserialize to `T`
|
|
- `.ser(&value)` — Serialize from `T`
|
|
- `.mutate(|v| ...)` — Deserialize, mutate, reserialize
|
|
- For maps: `.keys()`, `.as_idx(&key)`, `.insert()`, `.remove()`, `.contains_key()`
|
|
|
|
## Database Access
|
|
|
|
```rust
|
|
// Read-only snapshot
|
|
let snap = db.peek().await;
|
|
let version = snap.as_public().as_server_info().as_version().de()?;
|
|
|
|
// Atomic mutation
|
|
db.mutate(|db| {
|
|
db.as_public_mut().as_server_info_mut().as_version_mut().ser(&new_version)?;
|
|
Ok(())
|
|
}).await;
|
|
```
|
|
|
|
## TypedDbWatch<T>
|
|
|
|
Watch a JSON pointer path for changes and deserialize as a typed value. Requires `T: HasModel`.
|
|
|
|
### Construction
|
|
|
|
```rust
|
|
use patch_db::json_ptr::JsonPointer;
|
|
|
|
let ptr: JsonPointer = "/public/serverInfo".parse().unwrap();
|
|
let mut watch = db.watch(ptr).await.typed::<ServerInfo>();
|
|
```
|
|
|
|
### API
|
|
|
|
- `watch.peek()?.de()?` — Get current value as `T`
|
|
- `watch.changed().await?` — Wait until the watched path changes
|
|
- `watch.peek()?.as_field().de()?` — Access nested fields via `HasModel` accessors
|
|
|
|
### Usage Patterns
|
|
|
|
**Wait for a condition, then proceed:**
|
|
|
|
```rust
|
|
// Wait for DB version to match current OS version
|
|
let current = Current::default().semver();
|
|
let mut watch = db
|
|
.watch("/public/serverInfo".parse().unwrap())
|
|
.await
|
|
.typed::<ServerInfo>();
|
|
loop {
|
|
let server_info = watch.peek()?.de()?;
|
|
if server_info.version == current {
|
|
break;
|
|
}
|
|
watch.changed().await?;
|
|
}
|
|
```
|
|
|
|
**React to changes in a loop:**
|
|
|
|
```rust
|
|
// From net_controller.rs — react to host changes
|
|
let mut watch = db
|
|
.watch("/public/serverInfo/network/host".parse().unwrap())
|
|
.await
|
|
.typed::<Host>();
|
|
loop {
|
|
if let Err(e) = watch.changed().await {
|
|
tracing::error!("DB watch disconnected: {e}");
|
|
break;
|
|
}
|
|
let host = watch.peek()?.de()?;
|
|
// ... process host ...
|
|
}
|
|
```
|
|
|
|
### Real Examples
|
|
|
|
- `net_controller.rs:469` — Watch `Hosts` for package network changes
|
|
- `net_controller.rs:493` — Watch `Host` for main UI network changes
|
|
- `service_actor.rs:37` — Watch `StatusInfo` for service state transitions
|
|
- `gateway.rs:1212` — Wait for DB migrations to complete before syncing
|