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
use crate::coordinate::Region;
use crate::store::writer::Notification;
use flume::Receiver;
/// Subscription: push-based per-subscriber flume channel. Lossy.
/// If subscriber is slow, bounded channel fills. Writer's retain() prunes.
/// For guaranteed delivery, use Cursor instead.
/// [SPEC:src/store/subscription.rs]
pub struct Subscription {
rx: Receiver<Notification>,
region: Region,
}
impl Subscription {
pub(crate) fn new(rx: Receiver<Notification>, region: Region) -> Self {
Self { rx, region }
}
/// Blocking receive. Filters by region. Returns None if channel closed.
pub fn recv(&self) -> Option<Notification> {
loop {
match self.rx.recv() {
Ok(notif) => {
// Filter: only return events matching our region.
// [FILE:src/coordinate/mod.rs — Region::matches_event]
if self.region.matches_event(
notif.coord.entity(),
notif.coord.scope(),
notif.kind,
) {
return Some(notif);
}
// Didn't match — keep receiving
}
Err(_) => return None, // channel closed
}
}
}
/// Expose the raw receiver for async usage.
/// Caller uses: sub.receiver().recv_async().await
/// \[DEP:flume::Receiver::recv_async\] → `RecvFut<'_, T>`: Future
/// ASYNC NOTE: This is for async event consumption. For Store methods
/// (append, get, query), use spawn_blocking instead. Two different patterns.
/// [SPEC:src/store/subscription.rs — ASYNC NOTE]
pub fn receiver(&self) -> &Receiver<Notification> {
&self.rx
}
/// Create a composable ops wrapper for chainable filter/map/take.
pub fn ops(self) -> SubscriptionOps {
SubscriptionOps {
sub: self,
filters: Vec::new(),
map_fn: None,
limit: None,
count: 0,
}
}
}
/// SubscriptionOps: composable stream wrapper over Subscription.
/// Chains filter/take operations. No tokio, no async — just closures in recv loop.
type NotifFilter = Box<dyn Fn(&Notification) -> bool + Send>;
type NotifMapper = Box<dyn Fn(&Notification) -> Option<Notification> + Send>;
/// Composable wrapper around a `Subscription` supporting chainable filter, map, and take operations.
pub struct SubscriptionOps {
sub: Subscription,
filters: Vec<NotifFilter>,
map_fn: Option<NotifMapper>,
limit: Option<usize>,
count: usize,
}
impl SubscriptionOps {
/// Add a filter predicate. Only notifications passing all filters are returned.
pub fn filter<F: Fn(&Notification) -> bool + Send + 'static>(mut self, f: F) -> Self {
self.filters.push(Box::new(f));
self
}
/// Transform notifications. The mapper returns Some(notification) to pass through,
/// or None to skip. Chainable with filter/take.
/// [SPEC:src/store/subscription.rs — SubscriptionOps::map]
pub fn map<F: Fn(&Notification) -> Option<Notification> + Send + 'static>(
mut self,
f: F,
) -> Self {
self.map_fn = Some(Box::new(f));
self
}
/// Limit the number of notifications returned before stopping.
pub fn take(mut self, n: usize) -> Self {
self.limit = Some(n);
self
}
/// Blocking receive with all filters applied. Returns None when channel closes or limit reached.
pub fn recv(&mut self) -> Option<Notification> {
if let Some(limit) = self.limit {
if self.count >= limit {
return None;
}
}
loop {
let notif = self.sub.recv()?;
if self.filters.iter().all(|f| f(¬if)) {
let result = if let Some(ref map_fn) = self.map_fn {
map_fn(¬if)
} else {
Some(notif)
};
if let Some(n) = result {
self.count += 1;
return Some(n);
}
}
}
}
}