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>;