Skip to main content

thread_flow/
registry.rs

1// SPDX-FileCopyrightText: 2025 Knitli Inc. <knitli@knit.li>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Operator registry for Thread's ReCoco integration.
5//!
6//! This module provides registration functions for all Thread-specific operators
7//! using ReCoco's ExecutorFactoryRegistry. Operators follow the SimpleFunctionFactoryBase
8//! pattern for proper integration with the ReCoco dataflow engine.
9
10use recoco::ops::factory_bases::{SimpleFunctionFactoryBase, TargetFactoryBase};
11use recoco::ops::sdk::ExecutorFactoryRegistry;
12use recoco::prelude::Error as RecocoError;
13
14use crate::functions::{
15    calls::ExtractCallsFactory, imports::ExtractImportsFactory, parse::ThreadParseFactory,
16    symbols::ExtractSymbolsFactory,
17};
18use crate::targets::d1::D1TargetFactory;
19
20/// Thread operators available for ReCoco flows.
21///
22/// These operators integrate Thread's semantic code analysis capabilities
23/// into ReCoco's dataflow engine for incremental, cached code parsing.
24///
25/// # Available Operators
26///
27/// ## Functions (Transforms)
28///
29/// ### `thread_parse`
30/// Parse source code into AST with semantic extraction.
31///
32/// **Inputs**:
33/// - `content` (String): Source code content
34/// - `language` (String): Language identifier (extension or name)
35/// - `file_path` (String, optional): File path for context
36///
37/// **Output**: Struct with fields:
38/// - `symbols` (LTable): Symbol definitions
39/// - `imports` (LTable): Import statements
40/// - `calls` (LTable): Function calls
41///
42/// ### `extract_symbols`
43/// Extract symbol table from parsed document.
44///
45/// **Inputs**:
46/// - `parsed_document` (Struct): Output from `thread_parse`
47///
48/// **Output**: LTable with fields:
49/// - `name` (String): Symbol name
50/// - `kind` (String): Symbol kind (function, class, etc.)
51/// - `scope` (String): Scope identifier
52///
53/// ### `extract_imports`
54/// Extract import statements from parsed document.
55///
56/// **Inputs**:
57/// - `parsed_document` (Struct): Output from `thread_parse`
58///
59/// **Output**: LTable with fields:
60/// - `symbol_name` (String): Imported symbol name
61/// - `source_path` (String): Import source path
62/// - `kind` (String): Import kind
63///
64/// ### `extract_calls`
65/// Extract function calls from parsed document.
66///
67/// **Inputs**:
68/// - `parsed_document` (Struct): Output from `thread_parse`
69///
70/// **Output**: LTable with fields:
71/// - `function_name` (String): Called function name
72/// - `arguments_count` (Int64): Number of arguments
73///
74/// ## Targets (Export Destinations)
75///
76/// ### `d1`
77/// Export data to Cloudflare D1 edge database.
78///
79/// **Configuration**:
80/// - `account_id` (String): Cloudflare account ID
81/// - `database_id` (String): D1 database ID
82/// - `api_token` (String): Cloudflare API token
83/// - `table_name` (String): Target table name
84///
85/// **Features**:
86/// - Content-addressed deduplication via primary key
87/// - UPSERT pattern (INSERT ... ON CONFLICT DO UPDATE)
88/// - Batch operations for efficiency
89/// - Edge-distributed caching
90pub struct ThreadOperators;
91
92impl ThreadOperators {
93    /// List of all available Thread operator names (functions).
94    pub const OPERATORS: &'static [&'static str] = &[
95        "thread_parse",
96        "extract_symbols",
97        "extract_imports",
98        "extract_calls",
99    ];
100
101    /// List of all available Thread target names (export destinations).
102    pub const TARGETS: &'static [&'static str] = &["d1"];
103
104    /// Check if an operator name is a Thread operator.
105    pub fn is_thread_operator(name: &str) -> bool {
106        Self::OPERATORS.contains(&name)
107    }
108
109    /// Check if a target name is a Thread target.
110    pub fn is_thread_target(name: &str) -> bool {
111        Self::TARGETS.contains(&name)
112    }
113
114    /// Register all Thread operators with the provided registry.
115    ///
116    /// This function creates instances of all Thread operator factories and
117    /// registers them using the SimpleFunctionFactoryBase::register() and
118    /// TargetFactoryBase::register() methods.
119    ///
120    /// # Example
121    ///
122    /// ```ignore
123    /// use recoco::ops::sdk::ExecutorFactoryRegistry;
124    /// use thread_flow::ThreadOperators;
125    ///
126    /// let mut registry = ExecutorFactoryRegistry::new();
127    /// ThreadOperators::register_all(&mut registry)?;
128    /// ```
129    pub fn register_all(registry: &mut ExecutorFactoryRegistry) -> Result<(), RecocoError> {
130        // Register function operators
131        ThreadParseFactory.register(registry)?;
132        ExtractSymbolsFactory.register(registry)?;
133        ExtractImportsFactory.register(registry)?;
134        ExtractCallsFactory.register(registry)?;
135
136        // Register target operators
137        D1TargetFactory.register(registry)?;
138
139        Ok(())
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_operator_names() {
149        assert!(ThreadOperators::is_thread_operator("thread_parse"));
150        assert!(ThreadOperators::is_thread_operator("extract_symbols"));
151        assert!(ThreadOperators::is_thread_operator("extract_imports"));
152        assert!(ThreadOperators::is_thread_operator("extract_calls"));
153        assert!(!ThreadOperators::is_thread_operator("unknown_op"));
154    }
155
156    #[test]
157    fn test_operator_count() {
158        assert_eq!(ThreadOperators::OPERATORS.len(), 4);
159    }
160
161    #[test]
162    fn test_target_names() {
163        assert!(ThreadOperators::is_thread_target("d1"));
164        assert!(!ThreadOperators::is_thread_target("unknown_target"));
165    }
166
167    #[test]
168    fn test_target_count() {
169        assert_eq!(ThreadOperators::TARGETS.len(), 1);
170    }
171
172    #[test]
173    fn test_register_all() {
174        let mut registry = ExecutorFactoryRegistry::new();
175        // Registration succeeding without error validates that all operators are properly registered
176        ThreadOperators::register_all(&mut registry).expect("registration should succeed");
177    }
178}