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}