fraiseql_functions/store/mod.rs
1//! Function deployment store for persisting and retrieving compiled function artifacts.
2//!
3//! The store holds versioned function bytecode so the server can load functions at
4//! runtime without re-reading from disk or a remote registry.
5//!
6//! # Implementations
7//!
8//! - [`InMemoryFunctionStore`](memory::InMemoryFunctionStore) — for unit tests and local
9//! development
10//! - `PgFunctionStore` — PostgreSQL-backed persistent store (feature: `host-live`)
11
12pub mod memory;
13
14use async_trait::async_trait;
15use fraiseql_error::Result;
16
17use crate::types::RuntimeType;
18
19/// Deployment status of a function version.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum FunctionStatus {
23 /// This version is the active (serving) version.
24 Active,
25 /// This version has been superseded by a newer deploy.
26 Inactive,
27}
28
29impl FunctionStatus {
30 /// Serialize as the DB column string.
31 #[must_use]
32 pub const fn as_str(self) -> &'static str {
33 match self {
34 Self::Active => "active",
35 Self::Inactive => "inactive",
36 }
37 }
38
39 /// Parse from a DB column string.
40 ///
41 /// Returns `None` if the value is unrecognized.
42 #[must_use]
43 pub fn parse(s: &str) -> Option<Self> {
44 match s {
45 "active" => Some(Self::Active),
46 "inactive" => Some(Self::Inactive),
47 _ => None,
48 }
49 }
50}
51
52/// A stored function record representing one deployed version.
53#[derive(Debug, Clone)]
54pub struct FunctionRecord {
55 /// Trinity-pattern primary key (`pk_function`).
56 pub pk_function: i64,
57 /// Unique function name (scoped per tenant).
58 pub name: String,
59 /// Which runtime executes this function.
60 pub runtime: RuntimeType,
61 /// Compiled bytecode or source text.
62 pub bytecode: bytes::Bytes,
63 /// Monotonically increasing deploy version (1-based).
64 pub version: i32,
65 /// When this version was deployed.
66 pub deployed_at: chrono::DateTime<chrono::Utc>,
67 /// Whether this version is active.
68 pub status: FunctionStatus,
69}
70
71/// Trait for function deployment stores.
72///
73/// Implementors persist and retrieve versioned function bytecode.
74///
75/// The `#[async_trait]` attribute generates a dyn-compatible vtable so that
76/// `Box<dyn FunctionStore>` / `Arc<dyn FunctionStore>` work for dynamic dispatch.
77#[async_trait]
78pub trait FunctionStore: Send + Sync {
79 /// Store a new version of a function, bumping its version number.
80 ///
81 /// If a function with `name` already exists, a new version is created
82 /// and previous versions are marked `inactive`.
83 ///
84 /// # Errors
85 ///
86 /// Returns `Err` if the write fails.
87 async fn store_function(
88 &self,
89 name: &str,
90 runtime: RuntimeType,
91 bytecode: bytes::Bytes,
92 ) -> Result<FunctionRecord>;
93
94 /// Retrieve the latest active version of a function by name.
95 ///
96 /// Returns `Ok(None)` if no active function with that name exists.
97 ///
98 /// # Errors
99 ///
100 /// Returns `Err` if the read fails.
101 async fn get_function(&self, name: &str) -> Result<Option<FunctionRecord>>;
102
103 /// List all active function records.
104 ///
105 /// # Errors
106 ///
107 /// Returns `Err` if the read fails.
108 async fn list_functions(&self) -> Result<Vec<FunctionRecord>>;
109
110 /// Delete (deactivate) all versions of a function by name.
111 ///
112 /// Returns `true` if at least one active record was found and deactivated,
113 /// `false` if no active function with that name existed.
114 ///
115 /// # Errors
116 ///
117 /// Returns `Err` if the write fails.
118 async fn delete_function(&self, name: &str) -> Result<bool>;
119}