Skip to main content

pylon_functions/
registry.rs

1//! Function registry — tracks registered TypeScript functions and their metadata.
2
3use std::collections::HashMap;
4use std::sync::Mutex;
5
6use serde::{Deserialize, Serialize};
7
8use crate::protocol::FnType;
9
10// ---------------------------------------------------------------------------
11// Function definition
12// ---------------------------------------------------------------------------
13
14/// Metadata about a registered TypeScript function.
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct FnDef {
17    pub name: String,
18    pub fn_type: FnType,
19    /// JSON Schema for the function's args (from TypeScript validators).
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub args_schema: Option<serde_json::Value>,
22}
23
24// ---------------------------------------------------------------------------
25// Function registry
26// ---------------------------------------------------------------------------
27
28/// Registry of all TypeScript functions available to the runtime.
29///
30/// Populated at startup when the Bun process reports its registered functions.
31/// Used by the router to validate function calls and by Studio to show
32/// available functions.
33pub struct FnRegistry {
34    fns: Mutex<HashMap<String, FnDef>>,
35}
36
37impl FnRegistry {
38    pub fn new() -> Self {
39        Self {
40            fns: Mutex::new(HashMap::new()),
41        }
42    }
43
44    /// Register a function. Called during startup handshake with the Bun process.
45    pub fn register(&self, def: FnDef) {
46        self.fns.lock().unwrap().insert(def.name.clone(), def);
47    }
48
49    /// Register multiple functions at once (from startup handshake).
50    pub fn register_all(&self, defs: Vec<FnDef>) {
51        let mut fns = self.fns.lock().unwrap();
52        for def in defs {
53            fns.insert(def.name.clone(), def);
54        }
55    }
56
57    /// Atomically replace the entire registered set. Used after the runtime
58    /// supervisor respawns Bun: any function that was deleted between
59    /// processes must stop being callable, and `register_all()` alone won't
60    /// remove stale entries.
61    pub fn replace_all(&self, defs: Vec<FnDef>) {
62        let mut fns = self.fns.lock().unwrap();
63        fns.clear();
64        for def in defs {
65            fns.insert(def.name.clone(), def);
66        }
67    }
68
69    /// Look up a function by name.
70    pub fn get(&self, name: &str) -> Option<FnDef> {
71        self.fns.lock().unwrap().get(name).cloned()
72    }
73
74    /// List all registered functions.
75    pub fn list(&self) -> Vec<FnDef> {
76        self.fns.lock().unwrap().values().cloned().collect()
77    }
78
79    /// List functions of a specific type.
80    pub fn list_by_type(&self, fn_type: FnType) -> Vec<FnDef> {
81        self.fns
82            .lock()
83            .unwrap()
84            .values()
85            .filter(|f| f.fn_type == fn_type)
86            .cloned()
87            .collect()
88    }
89
90    /// Check if a function is registered.
91    pub fn exists(&self, name: &str) -> bool {
92        self.fns.lock().unwrap().contains_key(name)
93    }
94
95    /// Number of registered functions.
96    pub fn count(&self) -> usize {
97        self.fns.lock().unwrap().len()
98    }
99}
100
101impl Default for FnRegistry {
102    fn default() -> Self {
103        Self::new()
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn register_and_lookup() {
113        let reg = FnRegistry::new();
114        reg.register(FnDef {
115            name: "placeBid".into(),
116            fn_type: FnType::Mutation,
117            args_schema: None,
118        });
119        reg.register(FnDef {
120            name: "getLots".into(),
121            fn_type: FnType::Query,
122            args_schema: None,
123        });
124
125        assert_eq!(reg.count(), 2);
126        assert!(reg.exists("placeBid"));
127        assert!(!reg.exists("nonexistent"));
128
129        let def = reg.get("placeBid").unwrap();
130        assert_eq!(def.fn_type, FnType::Mutation);
131    }
132
133    #[test]
134    fn list_by_type() {
135        let reg = FnRegistry::new();
136        reg.register_all(vec![
137            FnDef {
138                name: "a".into(),
139                fn_type: FnType::Mutation,
140                args_schema: None,
141            },
142            FnDef {
143                name: "b".into(),
144                fn_type: FnType::Query,
145                args_schema: None,
146            },
147            FnDef {
148                name: "c".into(),
149                fn_type: FnType::Mutation,
150                args_schema: None,
151            },
152            FnDef {
153                name: "d".into(),
154                fn_type: FnType::Action,
155                args_schema: None,
156            },
157        ]);
158
159        assert_eq!(reg.list_by_type(FnType::Mutation).len(), 2);
160        assert_eq!(reg.list_by_type(FnType::Query).len(), 1);
161        assert_eq!(reg.list_by_type(FnType::Action).len(), 1);
162    }
163}