1use super::{
2 Arc, BTreeMap, Budget, Checkpoint, CheckpointError, CheckpointErrorKind, CheckpointId,
3 ClaimedWorkflow, Deserialize, Duration, LeaseDuration, Mutex, MutexGuard, Reverse, Serialize,
4 SystemWorkflowClock, Usage, Value, WorkerId, WorkflowBudgetAuditCursor,
5 WorkflowBudgetAuditEvent, WorkflowBudgetAuditKind, WorkflowBudgetAuditLimit,
6 WorkflowBudgetAuditProjectionId, WorkflowBudgetAuditProjectionLease,
7 WorkflowBudgetForfeitReason, WorkflowBudgetReservationOutcome, WorkflowCancelOutcome,
8 WorkflowCheckpointHistoryLimit, WorkflowCheckpointPhase, WorkflowCheckpointRevision,
9 WorkflowClock, WorkflowDisposition, WorkflowForkCommand, WorkflowForkOutcome,
10 WorkflowInterruptRequest, WorkflowLease, WorkflowLineage, WorkflowSignal, WorkflowSignalId,
11 WorkflowSignalOutcome, WorkflowSignalRetention, WorkflowSignalSnapshot, WorkflowSignalState,
12 WorkflowStore, WorkflowStoreError, WorkflowStoreErrorKind, WorkflowStoreFuture, WorkflowTask,
13 WorkflowTaskSnapshot, WorkflowTaskStatus, WorkflowTenantBudgetPolicy,
14 WorkflowTenantBudgetSnapshot, WorkflowTenantId, WorkflowTenantListLimit, WorkflowTenantPolicy,
15 WorkflowWait, WorkflowWake, decode_revision, fork_checkpoint,
16};
17
18mod budget;
19mod checkpoint;
20mod signal;
21mod task;
22
23use signal::take_buffered_signal;
24use task::{is_non_terminal, require_current_lease, require_tenant, workflow_not_found};
25
26#[derive(Clone)]
28pub struct InMemoryWorkflowStore {
29 tasks: Arc<Mutex<BTreeMap<CheckpointId, StoredTask>>>,
30 checkpoints: Arc<Mutex<StoredCheckpoints>>,
31 signals: Arc<Mutex<BTreeMap<WorkflowSignalId, StoredSignal>>>,
32 admission: Arc<Mutex<AdmissionState>>,
33 clock: Arc<dyn WorkflowClock>,
34}
35
36impl Default for InMemoryWorkflowStore {
37 fn default() -> Self {
38 Self::with_clock(Arc::new(SystemWorkflowClock))
39 }
40}
41
42impl InMemoryWorkflowStore {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn with_clock(clock: Arc<dyn WorkflowClock>) -> Self {
50 Self {
51 tasks: Arc::new(Mutex::new(BTreeMap::new())),
52 checkpoints: Arc::new(Mutex::new(StoredCheckpoints::default())),
53 signals: Arc::new(Mutex::new(BTreeMap::new())),
54 admission: Arc::new(Mutex::new(AdmissionState::default())),
55 clock,
56 }
57 }
58
59 fn tasks(&self) -> MutexGuard<'_, BTreeMap<CheckpointId, StoredTask>> {
60 self.tasks
61 .lock()
62 .unwrap_or_else(std::sync::PoisonError::into_inner)
63 }
64}
65
66impl std::fmt::Debug for InMemoryWorkflowStore {
67 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
68 formatter
69 .debug_struct("InMemoryWorkflowStore")
70 .finish_non_exhaustive()
71 }
72}
73
74#[derive(Clone, Debug, Default, Deserialize, Serialize)]
75struct AdmissionState {
76 tenants: BTreeMap<WorkflowTenantId, StoredTenant>,
77 budgets: BTreeMap<WorkflowTenantId, StoredTenantBudget>,
78 budget_audit_projections:
79 BTreeMap<(WorkflowTenantId, WorkflowBudgetAuditProjectionId), StoredBudgetAuditProjection>,
80 next_claim_sequence: u64,
81}
82
83#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
84struct StoredTenant {
85 policy: WorkflowTenantPolicy,
86 last_claim_sequence: u64,
87}
88
89#[derive(Clone, Debug, Deserialize, Serialize)]
90struct StoredTenantBudget {
91 policy: WorkflowTenantBudgetPolicy,
92 window_started_at_ms: u64,
93 committed: Usage,
94 reserved: Usage,
95 reservations: BTreeMap<CheckpointId, StoredBudgetReservation>,
96 next_audit_sequence: u64,
97 audit_events: Vec<StoredBudgetAuditEvent>,
98}
99
100#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
101struct StoredBudgetReservation {
102 baseline: Usage,
103 amount: Usage,
104 reserved_at_ms: u64,
105 expires_at_ms: u64,
106}
107
108#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
109struct StoredBudgetAuditEvent {
110 cursor: WorkflowBudgetAuditCursor,
111 checkpoint_id: Option<CheckpointId>,
112 occurred_at_ms: u64,
113 kind: WorkflowBudgetAuditKind,
114 usage: Usage,
115 reservation_age_ms: Option<u64>,
116 limit: Budget,
117 committed: Usage,
118 reserved: Usage,
119}
120
121#[derive(Clone, Debug, Default, Deserialize, Serialize)]
122struct StoredBudgetAuditProjection {
123 cursor: WorkflowBudgetAuditCursor,
124 owner: Option<WorkerId>,
125 fencing_token: u64,
126 expires_at_ms: Option<u64>,
127}
128
129#[derive(Clone, Debug, Deserialize, Serialize)]
130struct StoredTask {
131 task: WorkflowTask,
132 state: StoredState,
133 attempts: u64,
134 fencing_token: u64,
135 wake: Option<WorkflowWake>,
136 lineage: Option<WorkflowLineage>,
137 created_at_ms: u64,
138 updated_at_ms: u64,
139}
140
141#[derive(Clone, Debug, Default, Deserialize, Serialize)]
142struct StoredCheckpoints {
143 latest: BTreeMap<CheckpointId, Checkpoint>,
144 history: BTreeMap<(CheckpointId, u64), Checkpoint>,
145}
146
147#[derive(Clone, Debug, Deserialize, Serialize)]
148enum StoredState {
149 Queued {
150 available_at_ms: u64,
151 },
152 Leased(WorkflowLease),
153 WaitingTimer {
154 wake_at_ms: u64,
155 },
156 WaitingSignal {
157 name: crate::WorkflowSignalName,
158 },
159 WaitingSignalOrTimeout {
160 name: crate::WorkflowSignalName,
161 wake_at_ms: u64,
162 },
163 WaitingInterrupt {
164 request: WorkflowInterruptRequest,
165 },
166 Completed,
167 Failed(String),
168 Cancelled,
169}
170
171#[derive(Clone, Debug, Deserialize, Serialize)]
172struct StoredSignal {
173 tenant_id: WorkflowTenantId,
174 signal: WorkflowSignal,
175 consumed: bool,
176 dead_lettered: bool,
177 #[serde(default)]
178 compaction_protected: bool,
179 accepted_at_ms: u64,
180}
181
182const PERSISTENT_SNAPSHOT_VERSION: u32 = 1;
183
184#[derive(Deserialize, Serialize)]
185struct PersistentWorkflowSnapshot {
186 version: u32,
187 tasks: Vec<(CheckpointId, StoredTask)>,
188 checkpoints: Vec<(CheckpointId, Checkpoint)>,
189 checkpoint_history: Vec<(CheckpointId, u64, Checkpoint)>,
190 signals: Vec<(WorkflowSignalId, StoredSignal)>,
191 tenants: Vec<(WorkflowTenantId, StoredTenant)>,
192 budgets: Vec<(WorkflowTenantId, StoredTenantBudget)>,
193 budget_audit_projections: Vec<(
194 WorkflowTenantId,
195 WorkflowBudgetAuditProjectionId,
196 StoredBudgetAuditProjection,
197 )>,
198 next_claim_sequence: u64,
199}
200
201impl InMemoryWorkflowStore {
202 #[doc(hidden)]
207 pub fn export_persistent_snapshot(&self) -> Result<Vec<u8>, WorkflowStoreError> {
208 let tasks = self.tasks();
209 let checkpoints = self
210 .checkpoints
211 .lock()
212 .unwrap_or_else(std::sync::PoisonError::into_inner);
213 let signals = self
214 .signals
215 .lock()
216 .unwrap_or_else(std::sync::PoisonError::into_inner);
217 let admission = self
218 .admission
219 .lock()
220 .unwrap_or_else(std::sync::PoisonError::into_inner);
221 let snapshot = PersistentWorkflowSnapshot {
222 version: PERSISTENT_SNAPSHOT_VERSION,
223 tasks: tasks.iter().map(|(id, task)| (*id, task.clone())).collect(),
224 checkpoints: checkpoints
225 .latest
226 .iter()
227 .map(|(id, checkpoint)| (*id, checkpoint.clone()))
228 .collect(),
229 checkpoint_history: checkpoints
230 .history
231 .iter()
232 .map(|((id, revision), checkpoint)| (*id, *revision, checkpoint.clone()))
233 .collect(),
234 signals: signals
235 .iter()
236 .map(|(id, signal)| (*id, signal.clone()))
237 .collect(),
238 tenants: admission
239 .tenants
240 .iter()
241 .map(|(id, tenant)| (id.clone(), *tenant))
242 .collect(),
243 budgets: admission
244 .budgets
245 .iter()
246 .map(|(id, budget)| (id.clone(), budget.clone()))
247 .collect(),
248 budget_audit_projections: admission
249 .budget_audit_projections
250 .iter()
251 .map(|((tenant_id, projection_id), projection)| {
252 (tenant_id.clone(), projection_id.clone(), projection.clone())
253 })
254 .collect(),
255 next_claim_sequence: admission.next_claim_sequence,
256 };
257 serde_json::to_vec(&snapshot).map_err(|error| {
258 WorkflowStoreError::new(
259 WorkflowStoreErrorKind::Storage,
260 format!("workflow snapshot encoding failed: {error}"),
261 )
262 })
263 }
264
265 #[doc(hidden)]
267 pub fn from_persistent_snapshot(
268 encoded: &[u8],
269 clock: Arc<dyn WorkflowClock>,
270 ) -> Result<Self, WorkflowStoreError> {
271 let snapshot: PersistentWorkflowSnapshot =
272 serde_json::from_slice(encoded).map_err(|error| {
273 WorkflowStoreError::new(
274 WorkflowStoreErrorKind::Storage,
275 format!("workflow snapshot decoding failed: {error}"),
276 )
277 })?;
278 if snapshot.version != PERSISTENT_SNAPSHOT_VERSION {
279 return Err(WorkflowStoreError::new(
280 WorkflowStoreErrorKind::Storage,
281 format!("unsupported workflow snapshot version {}", snapshot.version),
282 ));
283 }
284 let checkpoints = StoredCheckpoints {
285 latest: snapshot.checkpoints.into_iter().collect(),
286 history: snapshot
287 .checkpoint_history
288 .into_iter()
289 .map(|(id, revision, checkpoint)| ((id, revision), checkpoint))
290 .collect(),
291 };
292 let admission = AdmissionState {
293 tenants: snapshot.tenants.into_iter().collect(),
294 budgets: snapshot.budgets.into_iter().collect(),
295 budget_audit_projections: snapshot
296 .budget_audit_projections
297 .into_iter()
298 .map(|(tenant_id, projection_id, projection)| {
299 ((tenant_id, projection_id), projection)
300 })
301 .collect(),
302 next_claim_sequence: snapshot.next_claim_sequence,
303 };
304 Ok(Self {
305 tasks: Arc::new(Mutex::new(snapshot.tasks.into_iter().collect())),
306 checkpoints: Arc::new(Mutex::new(checkpoints)),
307 signals: Arc::new(Mutex::new(snapshot.signals.into_iter().collect())),
308 admission: Arc::new(Mutex::new(admission)),
309 clock,
310 })
311 }
312}
313
314impl InMemoryWorkflowStore {
315 fn suspend(
316 &self,
317 stored: &mut StoredTask,
318 checkpoint_id: CheckpointId,
319 wait: WorkflowWait,
320 now: u64,
321 ) -> StoredState {
322 match wait {
323 WorkflowWait::Timer { delay_ms } => {
324 stored.wake = None;
325 StoredState::WaitingTimer {
326 wake_at_ms: now.saturating_add(delay_ms),
327 }
328 }
329 WorkflowWait::Signal { name } => {
330 self.suspend_signal(stored, checkpoint_id, name, None, now)
331 }
332 WorkflowWait::SignalOrTimeout { name, timeout_ms } => {
333 self.suspend_signal(stored, checkpoint_id, name, Some(timeout_ms), now)
334 }
335 WorkflowWait::Interrupt { request } => {
336 let name = request.signal_name();
337 let mut signals = self
338 .signals
339 .lock()
340 .unwrap_or_else(std::sync::PoisonError::into_inner);
341 if let Some(wake) = take_buffered_signal(&mut signals, checkpoint_id, &name) {
342 stored.wake = Some(wake);
343 StoredState::Queued {
344 available_at_ms: now,
345 }
346 } else {
347 stored.wake = None;
348 StoredState::WaitingInterrupt { request }
349 }
350 }
351 }
352 }
353
354 fn suspend_signal(
355 &self,
356 stored: &mut StoredTask,
357 checkpoint_id: CheckpointId,
358 name: crate::WorkflowSignalName,
359 timeout_ms: Option<u64>,
360 now: u64,
361 ) -> StoredState {
362 let mut signals = self
363 .signals
364 .lock()
365 .unwrap_or_else(std::sync::PoisonError::into_inner);
366 if let Some(wake) = take_buffered_signal(&mut signals, checkpoint_id, &name) {
367 stored.wake = Some(wake);
368 return StoredState::Queued {
369 available_at_ms: now,
370 };
371 }
372 stored.wake = None;
373 match timeout_ms {
374 Some(timeout_ms) => StoredState::WaitingSignalOrTimeout {
375 name,
376 wake_at_ms: now.saturating_add(timeout_ms),
377 },
378 None => StoredState::WaitingSignal { name },
379 }
380 }
381}
382
383impl WorkflowStore for InMemoryWorkflowStore {
384 fn current_time_ms(&self) -> WorkflowStoreFuture<'_, Result<u64, WorkflowStoreError>> {
385 Box::pin(async move { Ok(self.clock.now_ms()) })
386 }
387
388 fn set_tenant_policy(
389 &self,
390 tenant_id: WorkflowTenantId,
391 policy: WorkflowTenantPolicy,
392 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
393 self.set_tenant_policy_impl(tenant_id, policy)
394 }
395
396 fn set_tenant_budget_policy(
397 &self,
398 tenant_id: WorkflowTenantId,
399 policy: WorkflowTenantBudgetPolicy,
400 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
401 self.set_tenant_budget_policy_impl(tenant_id, policy)
402 }
403
404 fn list_tenant_budgets(
405 &self,
406 after: Option<WorkflowTenantId>,
407 limit: WorkflowTenantListLimit,
408 ) -> WorkflowStoreFuture<'_, Result<Vec<WorkflowTenantId>, WorkflowStoreError>> {
409 self.list_tenant_budgets_impl(after, limit)
410 }
411
412 fn inspect_tenant_budget(
413 &self,
414 tenant_id: WorkflowTenantId,
415 ) -> WorkflowStoreFuture<'_, Result<WorkflowTenantBudgetSnapshot, WorkflowStoreError>> {
416 self.inspect_tenant_budget_impl(tenant_id)
417 }
418
419 fn list_tenant_budget_audit(
420 &self,
421 tenant_id: WorkflowTenantId,
422 after: Option<WorkflowBudgetAuditCursor>,
423 limit: WorkflowBudgetAuditLimit,
424 ) -> WorkflowStoreFuture<'_, Result<Vec<WorkflowBudgetAuditEvent>, WorkflowStoreError>> {
425 self.list_tenant_budget_audit_impl(tenant_id, after, limit)
426 }
427
428 fn compact_tenant_budget_audit(
429 &self,
430 tenant_id: WorkflowTenantId,
431 through: WorkflowBudgetAuditCursor,
432 ) -> WorkflowStoreFuture<'_, Result<u64, WorkflowStoreError>> {
433 self.compact_tenant_budget_audit_impl(tenant_id, through)
434 }
435
436 fn load_or_create_tenant_budget_audit_projection(
437 &self,
438 tenant_id: WorkflowTenantId,
439 projection_id: WorkflowBudgetAuditProjectionId,
440 ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetAuditCursor, WorkflowStoreError>> {
441 self.load_or_create_tenant_budget_audit_projection_impl(tenant_id, projection_id)
442 }
443
444 fn advance_tenant_budget_audit_projection(
445 &self,
446 tenant_id: WorkflowTenantId,
447 projection_id: WorkflowBudgetAuditProjectionId,
448 expected: WorkflowBudgetAuditCursor,
449 next: WorkflowBudgetAuditCursor,
450 ) -> WorkflowStoreFuture<'_, Result<bool, WorkflowStoreError>> {
451 self.advance_tenant_budget_audit_projection_impl(tenant_id, projection_id, expected, next)
452 }
453
454 fn claim_tenant_budget_audit_projection(
455 &self,
456 tenant_id: WorkflowTenantId,
457 projection_id: WorkflowBudgetAuditProjectionId,
458 owner: WorkerId,
459 lease: LeaseDuration,
460 ) -> WorkflowStoreFuture<
461 '_,
462 Result<Option<WorkflowBudgetAuditProjectionLease>, WorkflowStoreError>,
463 > {
464 self.claim_tenant_budget_audit_projection_impl(tenant_id, projection_id, owner, lease)
465 }
466
467 fn heartbeat_tenant_budget_audit_projection(
468 &self,
469 lease: WorkflowBudgetAuditProjectionLease,
470 extension: LeaseDuration,
471 ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetAuditProjectionLease, WorkflowStoreError>>
472 {
473 self.heartbeat_tenant_budget_audit_projection_impl(lease, extension)
474 }
475
476 fn advance_tenant_budget_audit_projection_lease(
477 &self,
478 lease: WorkflowBudgetAuditProjectionLease,
479 next: WorkflowBudgetAuditCursor,
480 ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetAuditProjectionLease, WorkflowStoreError>>
481 {
482 self.advance_tenant_budget_audit_projection_lease_impl(lease, next)
483 }
484
485 fn release_tenant_budget_audit_projection(
486 &self,
487 lease: WorkflowBudgetAuditProjectionLease,
488 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
489 self.release_tenant_budget_audit_projection_impl(lease)
490 }
491
492 fn reserve_budget(
493 &self,
494 lease: WorkflowLease,
495 workflow_limit: Budget,
496 baseline: Usage,
497 ) -> WorkflowStoreFuture<'_, Result<WorkflowBudgetReservationOutcome, WorkflowStoreError>> {
498 self.reserve_budget_impl(lease, workflow_limit, baseline)
499 }
500
501 fn settle_budget(
502 &self,
503 lease: WorkflowLease,
504 cumulative: Usage,
505 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
506 self.settle_budget_impl(lease, cumulative)
507 }
508
509 fn enqueue(
510 &self,
511 task: WorkflowTask,
512 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
513 self.enqueue_impl(task)
514 }
515
516 fn claim(
517 &self,
518 worker: WorkerId,
519 lease: LeaseDuration,
520 ) -> WorkflowStoreFuture<'_, Result<Option<ClaimedWorkflow>, WorkflowStoreError>> {
521 self.claim_impl(worker, lease)
522 }
523
524 fn heartbeat(
525 &self,
526 lease: WorkflowLease,
527 extension: LeaseDuration,
528 ) -> WorkflowStoreFuture<'_, Result<WorkflowLease, WorkflowStoreError>> {
529 self.heartbeat_impl(lease, extension)
530 }
531
532 fn finish(
533 &self,
534 lease: WorkflowLease,
535 disposition: WorkflowDisposition,
536 ) -> WorkflowStoreFuture<'_, Result<(), WorkflowStoreError>> {
537 self.finish_impl(lease, disposition)
538 }
539
540 fn publish_signal(
541 &self,
542 tenant_id: WorkflowTenantId,
543 signal: WorkflowSignal,
544 ) -> WorkflowStoreFuture<'_, Result<WorkflowSignalOutcome, WorkflowStoreError>> {
545 self.publish_signal_impl(tenant_id, signal, false)
546 }
547
548 fn publish_control_signal(
549 &self,
550 tenant_id: WorkflowTenantId,
551 signal: WorkflowSignal,
552 ) -> WorkflowStoreFuture<'_, Result<WorkflowSignalOutcome, WorkflowStoreError>> {
553 self.publish_signal_impl(tenant_id, signal, true)
554 }
555
556 fn cancel(
557 &self,
558 tenant_id: WorkflowTenantId,
559 checkpoint_id: CheckpointId,
560 ) -> WorkflowStoreFuture<'_, Result<WorkflowCancelOutcome, WorkflowStoreError>> {
561 self.cancel_impl(tenant_id, checkpoint_id)
562 }
563
564 fn inspect_signal(
565 &self,
566 tenant_id: WorkflowTenantId,
567 signal_id: WorkflowSignalId,
568 ) -> WorkflowStoreFuture<'_, Result<WorkflowSignalSnapshot, WorkflowStoreError>> {
569 self.inspect_signal_impl(tenant_id, signal_id)
570 }
571
572 fn load_signal_payload(
573 &self,
574 tenant_id: WorkflowTenantId,
575 signal_id: WorkflowSignalId,
576 ) -> WorkflowStoreFuture<'_, Result<Value, WorkflowStoreError>> {
577 Box::pin(async move {
578 let signals = self
579 .signals
580 .lock()
581 .unwrap_or_else(std::sync::PoisonError::into_inner);
582 let stored = signals.get(&signal_id).ok_or_else(|| {
583 WorkflowStoreError::new(
584 WorkflowStoreErrorKind::NotFound,
585 "workflow signal does not exist",
586 )
587 })?;
588 if stored.tenant_id != tenant_id {
589 return Err(WorkflowStoreError::new(
590 WorkflowStoreErrorKind::TenantMismatch,
591 "workflow tenant does not own signal",
592 ));
593 }
594 Ok(stored.signal.payload.clone())
595 })
596 }
597
598 fn compact_signals(
599 &self,
600 tenant_id: WorkflowTenantId,
601 retention: WorkflowSignalRetention,
602 ) -> WorkflowStoreFuture<'_, Result<u64, WorkflowStoreError>> {
603 self.compact_signals_impl(tenant_id, retention)
604 }
605
606 fn inspect(
607 &self,
608 tenant_id: WorkflowTenantId,
609 checkpoint_id: CheckpointId,
610 ) -> WorkflowStoreFuture<'_, Result<WorkflowTaskSnapshot, WorkflowStoreError>> {
611 self.inspect_impl(tenant_id, checkpoint_id)
612 }
613
614 fn load_task_input(
615 &self,
616 tenant_id: WorkflowTenantId,
617 checkpoint_id: CheckpointId,
618 ) -> WorkflowStoreFuture<'_, Result<Value, WorkflowStoreError>> {
619 Box::pin(async move {
620 let tasks = self.tasks();
621 let stored = tasks.get(&checkpoint_id).ok_or_else(|| {
622 WorkflowStoreError::new(WorkflowStoreErrorKind::NotFound, "workflow not found")
623 })?;
624 if stored.task.tenant_id != tenant_id {
625 return Err(WorkflowStoreError::new(
626 WorkflowStoreErrorKind::TenantMismatch,
627 "workflow tenant does not own task",
628 ));
629 }
630 Ok(stored.task.input.clone())
631 })
632 }
633
634 fn list_checkpoint_history(
635 &self,
636 tenant_id: WorkflowTenantId,
637 checkpoint_id: CheckpointId,
638 after_revision: Option<u64>,
639 limit: WorkflowCheckpointHistoryLimit,
640 ) -> WorkflowStoreFuture<'_, Result<Vec<WorkflowCheckpointRevision>, WorkflowStoreError>> {
641 self.list_checkpoint_history_impl(tenant_id, checkpoint_id, after_revision, limit)
642 }
643
644 fn load_checkpoint_revision(
645 &self,
646 tenant_id: WorkflowTenantId,
647 checkpoint_id: CheckpointId,
648 revision: u64,
649 ) -> WorkflowStoreFuture<'_, Result<WorkflowCheckpointRevision, WorkflowStoreError>> {
650 self.load_checkpoint_revision_impl(tenant_id, checkpoint_id, revision)
651 }
652
653 fn fork_workflow(
654 &self,
655 tenant_id: WorkflowTenantId,
656 command: WorkflowForkCommand,
657 ) -> WorkflowStoreFuture<'_, Result<WorkflowForkOutcome, WorkflowStoreError>> {
658 self.fork_workflow_impl(tenant_id, command)
659 }
660
661 fn load_checkpoint(
662 &self,
663 lease: WorkflowLease,
664 ) -> WorkflowStoreFuture<'_, Result<Checkpoint, CheckpointError>> {
665 self.load_checkpoint_impl(lease)
666 }
667
668 fn compare_and_swap_checkpoint(
669 &self,
670 lease: WorkflowLease,
671 checkpoint: Checkpoint,
672 expected_revision: Option<u64>,
673 ) -> WorkflowStoreFuture<'_, Result<(), CheckpointError>> {
674 self.compare_and_swap_checkpoint_impl(lease, checkpoint, expected_revision)
675 }
676}