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}