Skip to main content

product_os_store/
config.rs

1//! Store configuration types for key-value, queue, and relational stores.
2
3use serde::{Deserialize, Serialize};
4
5use product_os_configuration::{ConfigError, ProductOSConfig};
6
7/// Type of key-value store
8#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
9#[serde(rename_all = "lowercase")]
10pub enum KeyValueKind {
11    /// Redis key-value store
12    Redis,
13    /// In-memory key-value store
14    Memory,
15    /// File-based key-value store
16    File,
17    /// Null sink (no-op store)
18    #[default]
19    Sink,
20}
21
22/// Key-value store configuration
23#[derive(Clone, Debug, Deserialize, Serialize, Default)]
24#[serde(rename_all = "camelCase")]
25pub struct KeyValueStore {
26    /// Whether the key-value store is enabled
27    pub enabled: bool,
28    /// Type of key-value store
29    pub kind: KeyValueKind,
30    /// Store hostname
31    pub host: String,
32    /// Store port
33    pub port: u16,
34    /// Use secure connection (TLS/SSL)
35    pub secure: bool,
36    /// Database number (Redis-specific)
37    pub db_number: u8,
38    /// Database name (for stores that support it)
39    pub db_name: Option<String>,
40    /// Username for authentication
41    #[serde(default)]
42    pub username: Option<String>,
43    /// Password for authentication (sensitive)
44    #[serde(default)]
45    pub password: Option<String>,
46    /// Connection pool size
47    pub pool_size: u8,
48    /// Default limit for query results
49    pub default_limit: u32,
50    /// Default offset for query results
51    pub default_offset: u32,
52    /// Key prefix for namespacing
53    pub prefix: Option<String>,
54}
55
56/// Type of queue store
57#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
58#[serde(rename_all = "lowercase")]
59pub enum QueueKind {
60    /// Redis-based queue
61    Redis,
62    /// In-memory queue
63    Memory,
64    /// File-based queue
65    File,
66    /// Null sink (no-op queue)
67    #[default]
68    Sink,
69}
70
71/// Queue store configuration
72#[derive(Clone, Debug, Deserialize, Serialize, Default)]
73#[serde(rename_all = "camelCase")]
74pub struct QueueStore {
75    /// Whether the queue store is enabled
76    pub enabled: bool,
77    /// Type of queue store
78    pub kind: QueueKind,
79    /// Queue store hostname
80    pub host: String,
81    /// Queue store port
82    pub port: u16,
83    /// Use secure connection (TLS/SSL)
84    pub secure: bool,
85    /// Database number (Redis-specific)
86    pub db_number: u8,
87    /// Database name (for stores that support it)
88    pub db_name: Option<String>,
89    /// Username for authentication
90    #[serde(default)]
91    pub username: Option<String>,
92    /// Password for authentication (sensitive)
93    #[serde(default)]
94    pub password: Option<String>,
95    /// Connection pool size
96    pub pool_size: u8,
97    /// Default limit for query results
98    pub default_limit: u32,
99    /// Default offset for query results
100    pub default_offset: u32,
101    /// Key prefix for namespacing
102    pub prefix: Option<String>,
103}
104
105/// Type of relational database
106#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
107#[serde(rename_all = "lowercase")]
108pub enum RelationalKind {
109    /// `PostgreSQL` database
110    Postgres,
111    /// Microsoft SQL Server
112    MSSql,
113    /// `MySQL`/`MariaDB` database
114    MySql,
115    /// `SQLite` database
116    Sqlite,
117    /// In-memory database
118    Memory,
119    /// Null sink (no-op database)
120    #[default]
121    Sink,
122}
123
124/// Relational database configuration
125#[derive(Clone, Debug, Deserialize, Serialize, Default)]
126#[serde(rename_all = "camelCase")]
127pub struct RelationalStore {
128    /// Whether the relational store is enabled
129    pub enabled: bool,
130    /// Type of relational database
131    pub kind: RelationalKind,
132    /// Database hostname
133    pub host: String,
134    /// Database port
135    pub port: u16,
136    /// Use secure connection (TLS/SSL)
137    pub secure: bool,
138    /// Database name
139    pub db_name: String,
140    /// Username for authentication
141    #[serde(default)]
142    pub username: Option<String>,
143    /// Password for authentication (sensitive)
144    #[serde(default)]
145    pub password: Option<String>,
146    /// Connection pool size
147    pub pool_size: u8,
148    /// Default limit for query results
149    pub default_limit: u32,
150    /// Default offset for query results
151    pub default_offset: u32,
152    /// Table prefix for namespacing
153    pub prefix: Option<String>,
154}
155
156/// Type of memory-mapped store backend.
157///
158/// The `MemoryMapped` paradigm offers binary keys, binary values, prefix range scans,
159/// and atomic batch writes — the access pattern needed by skip-gram / graph / co-occurrence
160/// workloads. The canonical implementation is LMDB (via `heed`), where reads are direct
161/// pointer dereferences against the kernel page cache and read transactions are essentially free.
162#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
163#[serde(rename_all = "lowercase")]
164pub enum MemoryMappedKind {
165    /// LMDB-backed memory-mapped store (via the `heed` crate). The canonical implementation.
166    Lmdb,
167    /// Pure in-RAM `BTreeMap` implementation. Useful for tests and ephemeral workloads.
168    Memory,
169    /// Null sink (no-op store).
170    #[default]
171    Sink,
172}
173
174/// Memory-mapped store configuration.
175///
176/// Unlike the key-value, queue, and relational stores, the memory-mapped store works with
177/// **binary keys and binary values**. Each "group" maps to a named sub-database inside the
178/// LMDB environment so multiple logical tables can share one mmap.
179#[derive(Clone, Debug, Deserialize, Serialize, Default)]
180#[serde(rename_all = "camelCase")]
181pub struct MemoryMappedStore {
182    /// Whether the store is enabled.
183    pub enabled: bool,
184    /// Backend implementation.
185    pub kind: MemoryMappedKind,
186    /// Filesystem path (LMDB only). For LMDB this is a *directory* path that will
187    /// hold `data.mdb` and `lock.mdb`. Ignored for `Memory` and `Sink`.
188    pub path: Option<String>,
189    /// Open the store read-only. Required when multiple processes need to read the
190    /// same LMDB environment concurrently.
191    #[serde(default)]
192    pub read_only: bool,
193    /// Upper bound on the mmap'd file size (LMDB only). LMDB requires the map size
194    /// to be set up front; reads use it as virtual-address-space allocation, not as a
195    /// disk allocation, so being generous is cheap. Defaults to 16 GiB if `None`.
196    #[serde(default)]
197    pub max_size_bytes: Option<u64>,
198    /// Maximum number of named sub-databases (groups) inside one LMDB env. Defaults to 64.
199    #[serde(default)]
200    pub max_groups: Option<u32>,
201    /// Optional prefix prepended to all group names.
202    #[serde(default)]
203    pub prefix: Option<String>,
204}
205
206/// Type of vector (dense ANN) store backend.
207#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
208#[serde(rename_all = "lowercase")]
209pub enum VectorKind {
210    /// LanceDB embedded columnar vector index.
211    Lancedb,
212    /// PostgreSQL pgvector extension.
213    Pgvector,
214    /// Null sink (no-op store).
215    #[default]
216    Sink,
217}
218
219/// Vector store configuration.
220///
221/// Vector stores hold dense embeddings keyed by chunk id, with logical collections
222/// mapped to physical tables that embed `(model_id, dim)` in the name.
223#[derive(Clone, Debug, Deserialize, Serialize, Default)]
224#[serde(rename_all = "camelCase")]
225pub struct VectorStore {
226    /// Whether the vector store is enabled.
227    pub enabled: bool,
228    /// Backend implementation.
229    pub kind: VectorKind,
230    /// Filesystem path to the LanceDB dataset directory (LanceDB only).
231    #[serde(default)]
232    pub path: String,
233    /// Postgres connection URL (pgvector only). Falls back to `path` when empty.
234    #[serde(default)]
235    pub database_url: String,
236    /// Optional prefix for namespacing.
237    #[serde(default)]
238    pub prefix: Option<String>,
239}
240
241/// Type of keyword (BM25) store backend.
242#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq, Default)]
243#[serde(rename_all = "lowercase")]
244pub enum KeywordKind {
245    /// Tantivy full-text / BM25 index.
246    Tantivy,
247    /// Null sink (no-op store).
248    #[default]
249    Sink,
250}
251
252/// Keyword store configuration.
253///
254/// Keyword stores index chunk text for BM25 retrieval. Each logical collection
255/// maps to a subdirectory under `path` (Tantivy only).
256#[derive(Clone, Debug, Deserialize, Serialize, Default)]
257#[serde(rename_all = "camelCase")]
258pub struct KeywordStore {
259    /// Whether the keyword store is enabled.
260    pub enabled: bool,
261    /// Backend implementation.
262    pub kind: KeywordKind,
263    /// Root directory for on-disk indexes (Tantivy only).
264    #[serde(default)]
265    pub path: String,
266    /// Optional prefix for namespacing.
267    #[serde(default)]
268    pub prefix: Option<String>,
269}
270
271/// Container for all store configurations
272#[derive(Clone, Debug, Deserialize, Serialize, Default)]
273#[serde(rename_all = "camelCase")]
274pub struct Stores {
275    /// Key-value store configuration (e.g., Redis)
276    pub key_value: Option<KeyValueStore>,
277    /// Queue store configuration
278    pub queue: Option<QueueStore>,
279    /// Relational database configuration
280    pub relational: Option<RelationalStore>,
281    /// Memory-mapped store configuration (binary keys/values, range scans).
282    pub memory_mapped: Option<MemoryMappedStore>,
283    /// Dense vector (ANN) store configuration.
284    pub vector: Option<VectorStore>,
285    /// Keyword (BM25) store configuration.
286    pub keyword: Option<KeywordStore>,
287}
288
289impl ProductOSConfig for Stores {
290    const SECTION_KEY: &'static str = "store";
291
292    fn validate(&self) -> Result<(), ConfigError> {
293        Ok(())
294    }
295}