1use facet::Facet;
2#[cfg(feature = "rusqlite")]
3use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
4use std::fmt;
5use std::sync::OnceLock;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::time::{Instant, SystemTime, UNIX_EPOCH};
8
9#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq)]
10#[repr(u8)]
11#[facet(rename_all = "snake_case")]
12pub enum Direction {
13 Incoming,
14 Outgoing,
15}
16
17#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq)]
18#[repr(u8)]
19#[facet(rename_all = "snake_case")]
20pub enum ChannelDir {
21 Tx,
22 Rx,
23}
24
25#[derive(Facet, Clone, Debug, PartialEq, Eq)]
30#[facet(transparent)]
31pub struct Json(pub(crate) String);
32
33impl Json {
34 pub fn new(text: impl Into<String>) -> Self {
35 Self(text.into())
36 }
37
38 pub fn as_str(&self) -> &str {
39 self.0.as_str()
40 }
41
42 pub fn into_string(self) -> String {
43 self.0
44 }
45}
46
47#[cfg(feature = "rusqlite")]
48impl ToSql for Json {
49 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
50 Ok(self.as_str().into())
51 }
52}
53
54#[cfg(feature = "rusqlite")]
55impl FromSql for Json {
56 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
57 Ok(Json::new(String::column_result(value)?))
58 }
59}
60
61fn ptime_anchor() -> &'static Instant {
64 static PTIME_ANCHOR: OnceLock<Instant> = OnceLock::new();
65 PTIME_ANCHOR.get_or_init(Instant::now)
66}
67
68#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
71#[facet(transparent)]
72pub struct PTime(u64);
73
74impl PTime {
75 pub fn now() -> Self {
76 let elapsed_ms = ptime_anchor().elapsed().as_millis().min(u64::MAX as u128) as u64;
77 Self(elapsed_ms)
78 }
79
80 pub fn as_millis(&self) -> u64 {
81 self.0
82 }
83}
84
85#[cfg(feature = "rusqlite")]
86impl ToSql for PTime {
87 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
88 let as_i64 = i64::try_from(self.0).map_err(|_| {
89 rusqlite::Error::ToSqlConversionFailure(Box::new(std::io::Error::new(
90 std::io::ErrorKind::InvalidData,
91 format!("PTime out of SQLite i64 range: {}", self.0),
92 )))
93 })?;
94 Ok(as_i64.into())
95 }
96}
97
98#[cfg(feature = "rusqlite")]
99impl FromSql for PTime {
100 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
101 let raw_i64 = i64::column_result(value)?;
102 let raw_u64 = u64::try_from(raw_i64).map_err(|_| FromSqlError::OutOfRange(raw_i64))?;
103 Ok(PTime(raw_u64))
104 }
105}
106
107#[derive(Facet, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
109#[facet(transparent)]
110pub struct EntityId(pub(crate) String);
111
112#[derive(Facet, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
114#[facet(transparent)]
115pub struct ScopeId(pub(crate) String);
116
117#[derive(Facet, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
119#[facet(transparent)]
120pub struct EventId(pub(crate) String);
121
122#[derive(Facet, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
123#[facet(transparent)]
124pub struct ConnectionId(pub(crate) u64);
125
126#[derive(Facet, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
127#[facet(transparent)]
128pub struct ProcessId(pub(crate) String);
129
130#[derive(Facet, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
131#[facet(transparent)]
132pub struct SessionId(pub(crate) String);
133
134impl EntityId {
135 pub fn new(id: impl Into<String>) -> Self {
136 Self(id.into())
137 }
138
139 pub fn as_str(&self) -> &str {
140 self.0.as_str()
141 }
142}
143
144impl ScopeId {
145 pub fn new(id: impl Into<String>) -> Self {
146 Self(id.into())
147 }
148
149 pub fn as_str(&self) -> &str {
150 self.0.as_str()
151 }
152}
153
154impl EventId {
155 pub fn new(id: impl Into<String>) -> Self {
156 Self(id.into())
157 }
158
159 pub fn as_str(&self) -> &str {
160 self.0.as_str()
161 }
162}
163
164impl ConnectionId {
165 pub fn new(id: u64) -> Self {
166 assert!(
167 id > 0 && id <= moire_trace_types::JS_SAFE_INT_MAX_U64,
168 "invariant violated: connection_id must be in 1..={}, got {id}",
169 moire_trace_types::JS_SAFE_INT_MAX_U64
170 );
171 Self(id)
172 }
173
174 pub fn next(self) -> Self {
175 Self::new(self.0.saturating_add(1))
176 }
177}
178
179impl fmt::Display for ConnectionId {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 write!(f, "CONNECTION#{:x}", self.0)
182 }
183}
184
185impl ProcessId {
186 pub fn new(id: impl Into<String>) -> Self {
187 Self(id.into())
188 }
189
190 pub fn as_str(&self) -> &str {
191 self.0.as_str()
192 }
193}
194
195impl SessionId {
196 pub fn new(id: impl Into<String>) -> Self {
197 Self(id.into())
198 }
199
200 pub fn from_ordinal(value: u64) -> Self {
201 Self(format!("session:{value}"))
202 }
203
204 pub fn as_str(&self) -> &str {
205 self.0.as_str()
206 }
207}
208
209#[cfg(feature = "rusqlite")]
210impl ToSql for ProcessId {
211 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
212 Ok(self.as_str().into())
213 }
214}
215
216#[cfg(feature = "rusqlite")]
217impl FromSql for ProcessId {
218 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
219 Ok(ProcessId::new(String::column_result(value)?))
220 }
221}
222
223#[cfg(feature = "rusqlite")]
224impl ToSql for ConnectionId {
225 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
226 let value = i64::try_from(self.0).map_err(|_| {
227 rusqlite::Error::ToSqlConversionFailure(Box::new(std::io::Error::new(
228 std::io::ErrorKind::InvalidData,
229 format!("ConnectionId out of SQLite i64 range: {}", self.0),
230 )))
231 })?;
232 Ok(value.into())
233 }
234}
235
236#[cfg(feature = "rusqlite")]
237impl FromSql for ConnectionId {
238 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
239 let raw_i64 = i64::column_result(value)?;
240 let raw_u64 = u64::try_from(raw_i64).map_err(|_| FromSqlError::OutOfRange(raw_i64))?;
241 Ok(ConnectionId::new(raw_u64))
242 }
243}
244
245#[cfg(feature = "rusqlite")]
246impl ToSql for EntityId {
247 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
248 Ok(self.as_str().into())
249 }
250}
251
252#[cfg(feature = "rusqlite")]
253impl FromSql for EntityId {
254 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
255 Ok(EntityId::new(String::column_result(value)?))
256 }
257}
258
259#[cfg(feature = "rusqlite")]
260impl ToSql for ScopeId {
261 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
262 Ok(self.as_str().into())
263 }
264}
265
266#[cfg(feature = "rusqlite")]
267impl FromSql for ScopeId {
268 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
269 Ok(ScopeId::new(String::column_result(value)?))
270 }
271}
272
273#[cfg(feature = "rusqlite")]
274impl ToSql for EventId {
275 fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
276 Ok(self.as_str().into())
277 }
278}
279
280#[cfg(feature = "rusqlite")]
281impl FromSql for EventId {
282 fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
283 Ok(EventId::new(String::column_result(value)?))
284 }
285}
286
287pub(crate) fn next_entity_id() -> EntityId {
288 EntityId(next_opaque_id())
289}
290
291pub(crate) fn next_scope_id() -> ScopeId {
292 ScopeId(next_opaque_id())
293}
294
295pub(crate) fn next_event_id() -> EventId {
296 EventId(next_opaque_id())
297}
298
299pub fn next_process_id() -> ProcessId {
300 ProcessId(next_opaque_id())
301}
302
303pub fn process_prefix_u16() -> u16 {
304 static PROCESS_PREFIX: OnceLock<u16> = OnceLock::new();
305 *PROCESS_PREFIX.get_or_init(|| {
306 let pid = std::process::id() as u64;
307 let seed = SystemTime::now()
308 .duration_since(UNIX_EPOCH)
309 .map(|d| d.as_nanos() as u64)
310 .unwrap_or(0);
311 ((seed ^ pid) & 0xFFFF) as u16
312 })
313}
314
315fn next_opaque_id() -> String {
317 static COUNTER: AtomicU64 = AtomicU64::new(1);
318
319 let prefix = process_prefix_u16();
320
321 let counter = COUNTER.fetch_add(1, Ordering::Relaxed) & 0x0000_FFFF_FFFF_FFFF;
322 let raw = ((prefix as u64) << 48) | counter;
323 MoireHex(raw).to_string()
324}
325
326struct MoireHex(u64);
330
331impl fmt::Display for MoireHex {
332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333 const DIGITS: &[u8; 16] = b"0123456789pesPES";
334 let mut out = [0u8; 16];
335 for (idx, shift) in (0..16).zip((0..64).step_by(4).rev()) {
336 let nibble = ((self.0 >> shift) & 0xF) as usize;
337 out[idx] = DIGITS[nibble];
338 }
339 f.write_str(unsafe { std::str::from_utf8_unchecked(&out) })
341 }
342}