Skip to main content

yeti_types/
types.rs

1//! Common type aliases and newtypes used throughout the platform.
2
3use std::ops::Deref;
4
5/// Generate a newtype wrapper for strings with common trait implementations.
6macro_rules! string_newtype {
7    (
8        $(#[$meta:meta])*
9        $name:ident
10    ) => {
11        $(#[$meta])*
12        #[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
13        #[serde(transparent)]
14        pub struct $name(String);
15
16        impl $name {
17            /// Create a new instance.
18            #[must_use]
19            pub fn new(value: impl Into<String>) -> Self {
20                Self(value.into())
21            }
22
23            /// Get the inner string value.
24            #[must_use]
25            pub fn as_str(&self) -> &str {
26                &self.0
27            }
28
29            /// Get the length of the string.
30            #[must_use]
31            pub const fn len(&self) -> usize {
32                self.0.len()
33            }
34
35            /// Check if the string is empty.
36            #[must_use]
37            pub const fn is_empty(&self) -> bool {
38                self.0.is_empty()
39            }
40
41            /// Convert into the inner string.
42            #[must_use]
43            pub fn into_inner(self) -> String {
44                self.0
45            }
46        }
47
48        impl From<String> for $name {
49            fn from(s: String) -> Self {
50                Self(s)
51            }
52        }
53
54        impl From<&str> for $name {
55            fn from(s: &str) -> Self {
56                Self(s.to_string())
57            }
58        }
59
60        impl From<&$name> for $name {
61            fn from(v: &$name) -> Self {
62                v.clone()
63            }
64        }
65
66        impl From<&String> for $name {
67            fn from(s: &String) -> Self {
68                Self(s.clone())
69            }
70        }
71
72        impl AsRef<str> for $name {
73            fn as_ref(&self) -> &str {
74                &self.0
75            }
76        }
77
78        impl std::fmt::Display for $name {
79            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80                write!(f, "{}", self.0)
81            }
82        }
83
84        impl Deref for $name {
85            type Target = str;
86
87            fn deref(&self) -> &Self::Target {
88                &self.0
89            }
90        }
91    };
92}
93
94string_newtype!(
95    /// Key prefix identifier (application ID for key namespacing).
96    KeyPrefix
97);
98
99string_newtype!(
100    /// Type-safe table name.
101    TableName
102);
103
104string_newtype!(
105    /// Type-safe application ID.
106    AppId
107);
108
109string_newtype!(
110    /// Type-safe database namespace within an app.
111    ///
112    /// Default `"data"`. Apps may declare multiple databases via the
113    /// `@table(database: "...")` directive to scope tables; databases
114    /// are routing namespaces, not security boundaries.
115    DatabaseName
116);
117
118string_newtype!(
119    /// Stable hash identifying a yeti deployment.
120    ///
121    /// Every deployment runs as its own OS process on every node that
122    /// holds it; the deployment hash is the public address segment
123    /// (`{deployment_hash}.apps.yetirocks.com`) and the routing key
124    /// the per-node multiplexer uses to dispatch incoming RPCs to the
125    /// owning process. Customer scoping is a property of the
126    /// deployment (one deployment ⇒ one tenant context), so it is NOT
127    /// carried inside per-record identifiers like `TableId`; it
128    /// lives at the mux layer where it determines which Unix socket
129    /// gets the request, and inside the inter-node replication frame
130    /// header where it determines which peer process consumes the
131    /// payload. Wire frames addressed by `DeploymentHash` are
132    /// independently encrypted per-deployment so the multiplexed
133    /// gRPC link between two nodes cannot leak across tenants.
134    DeploymentHash
135);
136
137/// Identity of a table within a single deployment's data plane.
138///
139/// A deployment is one OS process; everything inside it shares the
140/// same tenant context, so `TableId` carries only the in-process
141/// coordinates: which application, which database namespace, which
142/// table. The deployment-level scope (whose data this is, which node
143/// presents it on which subdomain) is expressed by
144/// [`DeploymentHash`] at the routing layer, never duplicated here.
145///
146/// Used as the addressable unit on the replication wire below the
147/// per-deployment frame, in audit records, and in any cross-node
148/// observation of writes.
149///
150/// String-based in v0. Future optimization: interned numeric ordinals
151/// via a per-deployment registry, reducing wire overhead from
152/// ~32 bytes/record to ~8. Drop-in replaceable when profiling
153/// justifies; v0 prefers clarity.
154#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
155pub struct TableId {
156    /// Application identifier within the deployment.
157    pub app: AppId,
158    /// Database namespace within the application (default `"data"`).
159    pub database: DatabaseName,
160    /// Physical table name within the database.
161    pub table: TableName,
162}
163
164impl TableId {
165    /// Construct a new `TableId`.
166    pub fn new(
167        app: impl Into<AppId>,
168        database: impl Into<DatabaseName>,
169        table: impl Into<TableName>,
170    ) -> Self {
171        Self {
172            app: app.into(),
173            database: database.into(),
174            table: table.into(),
175        }
176    }
177}
178
179impl std::fmt::Display for TableId {
180    /// Format as `{app}/{database}/{table}` — the canonical
181    /// hierarchical path within a deployment. Slashes are reserved
182    /// separators; individual segments must not contain them. Used
183    /// for log messages, audit records, and key-prefix encoding.
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        write!(f, "{}/{}/{}", self.app, self.database, self.table)
186    }
187}
188
189/// Thread-safe reference to a key-value backend.
190pub type BackendRef = std::sync::Arc<dyn crate::backend::KvBackend>;
191
192/// Thread-safe reference to the pub/sub manager.
193pub type PubSubRef = std::sync::Arc<crate::pubsub::PubSubManager>;