Reseed lost event history from the KV projection on transition
Deploy / deploy (push) Successful in 1m0s
Deploy / deploy (push) Successful in 1m0s
A record whose event log is empty (the EVENTS stream was purged, or the record predates event-sourcing) but whose projection exists used to be stranded - replay found nothing, every transition failed with "unknown item". transition_or_reseed reconstructs a minimal one-event history from the projection's own current state and retries, making "zap the whole event stream" a recoverable operation by design instead of a slow-motion data loss. Reseed races are guarded by the same expected-sequence-0 CAS create uses. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
3233d9a3db
commit
20b24f08c2
@@ -240,6 +240,39 @@ pub async fn create(
|
||||
})
|
||||
}
|
||||
|
||||
/// Reconstructs a minimal one-event history for an aggregate whose
|
||||
/// event log is empty but whose KV projection still knows its current
|
||||
/// state - what makes wiping the EVENTS stream a recoverable
|
||||
/// operation rather than one that strands every pre-wipe record in an
|
||||
/// untransitionable limbo. `expected_seq: Some(0)` guards two racing
|
||||
/// reseeds the same way `create` guards a double submit.
|
||||
pub async fn reseed(
|
||||
js: &async_nats::jetstream::Context,
|
||||
schema: &AggregateSchema,
|
||||
id: &str,
|
||||
state: &str,
|
||||
occurred_at_ms: i64,
|
||||
) -> Result<(), TransitionError> {
|
||||
let event_type = schema.event_for_state.get(state).ok_or_else(|| {
|
||||
TransitionError::Store(format!(
|
||||
"cannot reseed {id}: state {state:?} is not declared for bucket {:?}",
|
||||
schema.bucket
|
||||
))
|
||||
})?;
|
||||
append_event(
|
||||
js,
|
||||
&schema.bucket,
|
||||
id,
|
||||
event_type,
|
||||
serde_json::json!({ "reseeded_from_projection": true }),
|
||||
occurred_at_ms,
|
||||
Some(0),
|
||||
)
|
||||
.await
|
||||
.map_err(classify_publish_err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads and replays `id`'s current state, then - if `target` is
|
||||
/// actually reachable from it - appends the corresponding event with
|
||||
/// the aggregate's just-replayed sequence as the CAS guard.
|
||||
|
||||
Reference in New Issue
Block a user