nodedb_types/namespace.rs
1// SPDX-License-Identifier: Apache-2.0
2
3//! Storage namespace identifiers for the blob KV store.
4//!
5//! Both SQLite (native) and OPFS (WASM) backends use the same namespace
6//! scheme to partition data by engine.
7
8use serde::{Deserialize, Serialize};
9
10/// Storage namespace. Each engine writes to its own namespace in the
11/// blob KV store, preventing key collisions.
12#[derive(
13 Debug,
14 Clone,
15 Copy,
16 PartialEq,
17 Eq,
18 Hash,
19 Serialize,
20 Deserialize,
21 rkyv::Archive,
22 rkyv::Serialize,
23 rkyv::Deserialize,
24)]
25#[repr(u8)]
26#[non_exhaustive]
27pub enum Namespace {
28 /// Database metadata: schema version, config, Shape subscriptions.
29 Meta = 0,
30 /// Vector engine: HNSW graph layers, vector data.
31 Vector = 1,
32 /// Graph engine: CSR arrays, node/label interning tables.
33 Graph = 2,
34 /// CRDT deltas: unsent mutations awaiting sync.
35 Crdt = 3,
36 /// Loro state snapshots: compacted CRDT state for fast cold-start.
37 LoroState = 4,
38 /// Spatial engine: R-tree checkpoints, geohash indexes.
39 Spatial = 5,
40 /// Strict document engine: Binary Tuple rows keyed by PK.
41 Strict = 6,
42 /// Columnar engine: compressed segments, delete bitmaps, segment metadata.
43 Columnar = 7,
44 /// KV engine: direct key-value storage (bypasses Loro CRDT).
45 /// Used when sync is disabled or for the local-only KV fast path.
46 Kv = 8,
47 /// Array engine: ND sparse arrays, catalog, manifests, segment bytes.
48 Array = 9,
49 /// Array CRDT op-log: append-only ops awaiting sync + GC.
50 ArrayOpLog = 10,
51 /// Array sync pending queue: ops waiting for transport delivery.
52 ArrayDelta = 11,
53 /// Full-text search engine: posting lists, doc-length maps, BM25 stats,
54 /// fieldnorm blobs, segment bytes, and surrogate maps.
55 Fts = 12,
56 /// Bitemporal history table for strict document collections.
57 ///
58 /// Keys: `{collection}:{system_from_ms_8be}:{pk_bytes}` — value is the
59 /// full Binary Tuple followed by an 8-byte big-endian `system_to_ms`
60 /// (u64::MAX = open / still-current at time of supersession).
61 StrictHistory = 13,
62 /// Bitemporal history table for graph edge collections.
63 ///
64 /// Keys: `{collection}:{edge_id_8be}:{system_from_ms_8be}` — value is
65 /// the MessagePack-encoded edge props followed by an 8-byte big-endian
66 /// `system_to_ms` (u64::MAX = current / not yet deleted).
67 GraphHistory = 14,
68 /// Bitemporal history table for schemaless document collections.
69 ///
70 /// Keys: `{collection}:{doc_id}\x00{system_from_ms:020}` — value is
71 /// `[tag:u8][valid_from_ms:i64 LE][valid_until_ms:i64 LE][body_msgpack...]`.
72 /// `tag = 0x00` (live), `0xFF` (tombstone), `0xFE` (GDPR erased).
73 DocumentHistory = 15,
74 /// O(1) pointer to the currently-live DocumentHistory version.
75 ///
76 /// Keys: `{collection}:{doc_id}` — value is the `system_from_ms` of the
77 /// live row encoded as a 20-digit zero-padded ASCII decimal (matching the
78 /// suffix used in `DocumentHistory` keys). Absent when no live version
79 /// exists (document never written, tombstoned, or GDPR-erased).
80 ///
81 /// Written atomically alongside every `DocumentHistory` mutation so that
82 /// `versioned_get_current` can resolve the current version with one index
83 /// lookup + one history fetch instead of a full prefix scan.
84 LatestVersion = 16,
85}
86
87impl Namespace {
88 /// Convert from raw u8 (for storage layer deserialization).
89 pub fn from_u8(v: u8) -> Option<Self> {
90 match v {
91 0 => Some(Self::Meta),
92 1 => Some(Self::Vector),
93 2 => Some(Self::Graph),
94 3 => Some(Self::Crdt),
95 4 => Some(Self::LoroState),
96 5 => Some(Self::Spatial),
97 6 => Some(Self::Strict),
98 7 => Some(Self::Columnar),
99 8 => Some(Self::Kv),
100 9 => Some(Self::Array),
101 10 => Some(Self::ArrayOpLog),
102 11 => Some(Self::ArrayDelta),
103 12 => Some(Self::Fts),
104 13 => Some(Self::StrictHistory),
105 14 => Some(Self::GraphHistory),
106 15 => Some(Self::DocumentHistory),
107 16 => Some(Self::LatestVersion),
108 _ => None,
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn namespace_roundtrip() {
119 for v in 0u8..=16 {
120 let ns = Namespace::from_u8(v).unwrap();
121 assert_eq!(ns as u8, v);
122 }
123 assert!(Namespace::from_u8(17).is_none());
124 }
125}