Skip to main content

fraiseql_functions/
types.rs

1//! Core types for function execution.
2
3use std::time::Duration;
4
5use serde::{Deserialize, Serialize};
6use sha2::Digest;
7
8/// Supported runtime types for serverless functions.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[non_exhaustive]
11pub enum RuntimeType {
12    /// `WebAssembly` Component Model runtime.
13    Wasm,
14    /// Deno (`JavaScript`/`TypeScript` via V8) runtime.
15    Deno,
16}
17
18impl RuntimeType {
19    /// Get supported file extensions for this runtime.
20    #[must_use]
21    pub const fn supported_extensions(&self) -> &[&str] {
22        match self {
23            RuntimeType::Wasm => &[".wasm"],
24            RuntimeType::Deno => &[".js", ".ts", ".mjs", ".mts"],
25        }
26    }
27}
28
29/// A compiled function module ready for execution.
30#[derive(Debug, Clone)]
31pub struct FunctionModule {
32    /// Unique name for this function.
33    pub name:        String,
34    /// Hash of the module source (for caching).
35    pub source_hash: String,
36    /// Compiled bytecode or source text.
37    pub bytecode:    bytes::Bytes,
38    /// Which runtime executes this module.
39    pub runtime:     RuntimeType,
40}
41
42impl FunctionModule {
43    /// Create a new WASM module from compiled bytecode.
44    pub fn from_bytecode(name: String, bytecode: bytes::Bytes) -> Self {
45        let source_hash = format!("{:x}", sha2::Sha256::digest(&bytecode));
46        Self {
47            name,
48            source_hash,
49            bytecode,
50            runtime: RuntimeType::Wasm,
51        }
52    }
53
54    /// Create a new source-based module (JavaScript/TypeScript).
55    #[must_use]
56    pub fn from_source(name: String, source: String, runtime: RuntimeType) -> Self {
57        let bytecode = bytes::Bytes::from(source);
58        let source_hash = format!("{:x}", sha2::Sha256::digest(&bytecode));
59        Self {
60            name,
61            source_hash,
62            bytecode,
63            runtime,
64        }
65    }
66}
67
68/// Trigger event payload for a function invocation.
69#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct EventPayload {
71    /// Type of trigger: "mutation", "subscription", "cron", "webhook", etc.
72    pub trigger_type: String,
73    /// Entity name (e.g., "User", "Post").
74    pub entity:       String,
75    /// Event kind (e.g., "created", "updated", "deleted").
76    pub event_kind:   String,
77    /// Event data (JSON).
78    pub data:         serde_json::Value,
79    /// Timestamp when the event occurred.
80    pub timestamp:    chrono::DateTime<chrono::Utc>,
81}
82
83/// Definition of a serverless function for deployment and execution.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct FunctionDefinition {
86    /// Unique name for this function.
87    pub name:       String,
88    /// Trigger type and configuration (e.g., "after:mutation:createUser", "cron:0 * * * *",
89    /// "<http:GET:/users/:id>").
90    pub trigger:    String,
91    /// Which runtime executes this function.
92    pub runtime:    RuntimeType,
93    /// Optional timeout in milliseconds (overrides defaults).
94    /// - For `before:mutation` triggers: defaults to 500ms
95    /// - For other triggers: defaults to 5s
96    pub timeout_ms: Option<u64>,
97}
98
99impl FunctionDefinition {
100    /// Create a new function definition.
101    #[must_use]
102    pub fn new(name: &str, trigger: &str, runtime: RuntimeType) -> Self {
103        Self {
104            name: name.to_string(),
105            trigger: trigger.to_string(),
106            runtime,
107            timeout_ms: None,
108        }
109    }
110
111    /// Set a custom timeout for this function.
112    #[must_use]
113    pub const fn with_timeout(mut self, timeout_ms: u64) -> Self {
114        self.timeout_ms = Some(timeout_ms);
115        self
116    }
117
118    /// Get the effective timeout for this function.
119    #[must_use]
120    pub fn effective_timeout(&self) -> Duration {
121        match self.timeout_ms {
122            Some(ms) => Duration::from_millis(ms),
123            None => {
124                // before:mutation defaults to 500ms; others default to 5s
125                if self.trigger.starts_with("before:mutation") {
126                    Duration::from_millis(500)
127                } else {
128                    Duration::from_secs(5)
129                }
130            },
131        }
132    }
133
134    /// Check if this function is a before:mutation trigger.
135    #[must_use]
136    pub fn is_before_mutation(&self) -> bool {
137        self.trigger.starts_with("before:mutation:")
138    }
139
140    /// Check if this function is an after:mutation trigger.
141    #[must_use]
142    pub fn is_after_mutation(&self) -> bool {
143        self.trigger.starts_with("after:mutation:")
144    }
145
146    /// Check if this function is an after:storage trigger.
147    #[must_use]
148    pub fn is_after_storage(&self) -> bool {
149        self.trigger.starts_with("after:storage:")
150    }
151
152    /// Check if this function is a cron trigger.
153    #[must_use]
154    pub fn is_cron(&self) -> bool {
155        self.trigger.starts_with("cron:")
156    }
157
158    /// Check if this function is an HTTP trigger.
159    #[must_use]
160    pub fn is_http(&self) -> bool {
161        self.trigger.starts_with("http:")
162    }
163}
164
165/// Log level for structured logging.
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
167#[non_exhaustive]
168pub enum LogLevel {
169    /// Debug level.
170    Debug,
171    /// Info level.
172    Info,
173    /// Warning level.
174    Warn,
175    /// Error level.
176    Error,
177}
178
179/// A single log entry from function execution.
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181pub struct LogEntry {
182    /// Log level.
183    pub level:     LogLevel,
184    /// Log message.
185    pub message:   String,
186    /// When the log was written.
187    pub timestamp: chrono::DateTime<chrono::Utc>,
188}
189
190/// Result of a function invocation.
191#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct FunctionResult {
193    /// Return value from the function (may be None if function returns void).
194    pub value:             Option<serde_json::Value>,
195    /// All logs captured during execution.
196    pub logs:              Vec<LogEntry>,
197    /// Total execution duration.
198    pub duration:          Duration,
199    /// Peak memory usage in bytes.
200    pub memory_peak_bytes: u64,
201}
202
203/// Resource limits for function execution.
204#[derive(Debug, Clone)]
205pub struct ResourceLimits {
206    /// Maximum memory allocation in bytes.
207    pub max_memory_bytes: u64,
208    /// Maximum execution duration.
209    pub max_duration:     Duration,
210    /// Maximum number of log entries to capture.
211    pub max_log_entries:  usize,
212}
213
214impl Default for ResourceLimits {
215    fn default() -> Self {
216        Self {
217            max_memory_bytes: 128 * 1024 * 1024,      // 128 MB
218            max_duration:     Duration::from_secs(5), // 5 seconds
219            max_log_entries:  10_000,
220        }
221    }
222}