oxify_model/
typescript.rs

1//! TypeScript type definitions generator for OxiFY Model
2//!
3//! This module provides TypeScript type definitions for use with JavaScript/TypeScript
4//! applications. It generates `.d.ts` files that provide type safety when working with
5//! OxiFY workflows in TypeScript projects.
6//!
7//! # Features
8//!
9//! - Generate TypeScript interfaces from Rust types
10//! - Full type coverage for workflows, nodes, edges, and execution types
11//! - Compatible with the WASM bindings
12//!
13//! # Usage
14//!
15//! ```
16//! use oxify_model::typescript::generate_typescript_definitions;
17//!
18//! let definitions = generate_typescript_definitions();
19//! assert!(definitions.contains("interface Workflow"));
20//! assert!(definitions.contains("interface Node"));
21//! assert!(definitions.contains("interface Edge"));
22//! // In production, save to file:
23//! // std::fs::write("oxify-model.d.ts", definitions).unwrap();
24//! ```
25
26/// Generate complete TypeScript type definitions for OxiFY Model
27///
28/// Returns a string containing all TypeScript interface and type definitions
29/// that can be saved to a `.d.ts` file.
30pub fn generate_typescript_definitions() -> String {
31    let mut output = String::new();
32
33    output.push_str(HEADER);
34    output.push_str(WORKFLOW_TYPES);
35    output.push_str(NODE_TYPES);
36    output.push_str(EDGE_TYPES);
37    output.push_str(EXECUTION_TYPES);
38    output.push_str(CONFIG_TYPES);
39    output.push_str(UTILITY_TYPES);
40    output.push_str(WASM_BINDINGS);
41
42    output
43}
44
45const HEADER: &str = r#"/**
46 * OxiFY Model - TypeScript Type Definitions
47 *
48 * Auto-generated TypeScript definitions for OxiFY workflow models.
49 * These types are compatible with the WASM bindings provided by oxify-model.
50 *
51 * @version 0.1.0
52 * @generated
53 */
54
55"#;
56
57const WORKFLOW_TYPES: &str = r#"// ============================================================================
58// Workflow Types
59// ============================================================================
60
61/** UUID string type for unique identifiers */
62export type UUID = string;
63
64/** ISO 8601 date-time string */
65export type DateTime = string;
66
67/** Unique identifier for a workflow */
68export type WorkflowId = UUID;
69
70/** Unique identifier for a node */
71export type NodeId = UUID;
72
73/** Unique identifier for an edge */
74export type EdgeId = UUID;
75
76/** Metadata about a workflow */
77export interface WorkflowMetadata {
78  /** Unique workflow identifier */
79  id: WorkflowId;
80  /** Display name */
81  name: string;
82  /** Description of what this workflow does */
83  description?: string;
84  /** Version string (semantic versioning) */
85  version: string;
86  /** When the workflow was created */
87  created_at: DateTime;
88  /** When the workflow was last modified */
89  updated_at: DateTime;
90  /** Tags for categorization */
91  tags: string[];
92  /** Parent workflow ID (for versioning) */
93  parent_id?: WorkflowId;
94  /** Change description for this version */
95  change_description?: string;
96  /** Scheduling configuration */
97  schedule?: WorkflowSchedule;
98}
99
100/** Workflow scheduling configuration */
101export interface WorkflowSchedule {
102  /** Cron expression for scheduling */
103  cron: string;
104  /** Timezone (e.g., "UTC", "America/New_York") */
105  timezone?: string;
106  /** Whether the schedule is enabled */
107  enabled: boolean;
108}
109
110/** Version bump type */
111export type VersionBump = "major" | "minor" | "patch";
112
113/** Complete workflow definition */
114export interface Workflow {
115  /** Workflow metadata */
116  metadata: WorkflowMetadata;
117  /** Nodes in the workflow DAG */
118  nodes: Node[];
119  /** Edges connecting nodes */
120  edges: Edge[];
121}
122
123"#;
124
125const NODE_TYPES: &str = r#"// ============================================================================
126// Node Types
127// ============================================================================
128
129/** Node in the workflow DAG */
130export interface Node {
131  /** Unique node identifier */
132  id: NodeId;
133  /** Display name of the node */
134  name: string;
135  /** Type and configuration of the node */
136  kind: NodeKind;
137  /** Position in the visual editor */
138  position?: [number, number];
139  /** Retry configuration */
140  retry_config?: RetryConfig;
141  /** Timeout configuration */
142  timeout_config?: TimeoutConfig;
143}
144
145/** Retry configuration for nodes */
146export interface RetryConfig {
147  /** Maximum number of retry attempts */
148  max_retries: number;
149  /** Initial delay before first retry in milliseconds */
150  initial_delay_ms: number;
151  /** Backoff multiplier for exponential backoff */
152  backoff_multiplier: number;
153  /** Maximum delay between retries in milliseconds */
154  max_delay_ms: number;
155}
156
157/** Timeout configuration for nodes */
158export interface TimeoutConfig {
159  /** Maximum execution time in milliseconds */
160  execution_timeout_ms: number;
161  /** Idle timeout in milliseconds */
162  idle_timeout_ms?: number;
163  /** Action to take on timeout */
164  timeout_action: TimeoutAction;
165}
166
167/** Action to take when a timeout occurs */
168export type TimeoutAction = "Fail" | "Skip" | "UseDefault";
169
170/** Node kind discriminated union */
171export type NodeKind =
172  | { Start: {} }
173  | { End: {} }
174  | { LlmCall: LlmConfig }
175  | { Retriever: VectorConfig }
176  | { CodeExecution: ScriptConfig }
177  | { IfElse: Condition }
178  | { Switch: SwitchConfig }
179  | { Tool: McpConfig }
180  | { Loop: LoopConfig }
181  | { TryCatch: TryCatchConfig }
182  | { SubWorkflow: SubWorkflowConfig }
183  | { Parallel: ParallelConfig }
184  | { Approval: ApprovalConfig }
185  | { Form: FormConfig };
186
187/** LLM call configuration */
188export interface LlmConfig {
189  /** LLM provider (e.g., "openai", "anthropic", "ollama") */
190  provider: string;
191  /** Model name (e.g., "gpt-4", "claude-3-opus") */
192  model: string;
193  /** System prompt template */
194  system_prompt?: string;
195  /** User prompt template with {{variable}} placeholders */
196  prompt_template: string;
197  /** Temperature for sampling (0.0 - 2.0) */
198  temperature?: number;
199  /** Maximum tokens to generate */
200  max_tokens?: number;
201  /** Stop sequences */
202  stop_sequences?: string[];
203  /** Response format (json, text) */
204  response_format?: string;
205}
206
207/** Vector database configuration */
208export interface VectorConfig {
209  /** Database type (e.g., "qdrant", "pgvector") */
210  db_type: string;
211  /** Collection name */
212  collection: string;
213  /** Query template */
214  query: string;
215  /** Number of results to return */
216  top_k: number;
217  /** Minimum similarity score threshold */
218  score_threshold?: number;
219  /** Embedding model to use */
220  embedding_model?: string;
221}
222
223/** Script execution configuration */
224export interface ScriptConfig {
225  /** Runtime environment */
226  runtime: "rhai" | "wasm";
227  /** Script code */
228  code: string;
229  /** Input variable mappings */
230  input_mapping?: Record<string, string>;
231  /** Output variable name */
232  output_variable?: string;
233}
234
235/** Conditional node configuration */
236export interface Condition {
237  /** Boolean expression to evaluate */
238  expression: string;
239  /** Node ID for true branch */
240  true_branch: NodeId;
241  /** Node ID for false branch */
242  false_branch: NodeId;
243}
244
245/** Switch/case configuration */
246export interface SwitchConfig {
247  /** Expression to switch on */
248  switch_on: string;
249  /** Case definitions */
250  cases: SwitchCase[];
251  /** Default case action */
252  default_case?: string;
253}
254
255/** Single switch case */
256export interface SwitchCase {
257  /** Value to match */
258  match_value: string;
259  /** Action expression or node to execute */
260  action: string;
261}
262
263/** MCP tool configuration */
264export interface McpConfig {
265  /** MCP server ID */
266  server_id: string;
267  /** Tool name */
268  tool_name: string;
269  /** Tool arguments template */
270  arguments_template?: string;
271}
272
273/** Loop configuration */
274export interface LoopConfig {
275  /** Type of loop */
276  loop_type: LoopType;
277  /** Maximum iterations (safety limit) */
278  max_iterations: number;
279  /** Body expression to execute */
280  body: string;
281}
282
283/** Loop type variants */
284export type LoopType =
285  | { ForEach: ForEachLoop }
286  | { While: WhileLoop }
287  | { Repeat: RepeatLoop };
288
289/** ForEach loop configuration */
290export interface ForEachLoop {
291  /** Path to collection to iterate over */
292  collection_path: string;
293  /** Variable name for current item */
294  item_variable: string;
295  /** Variable name for current index */
296  index_variable?: string;
297  /** Body expression */
298  body_expression: string;
299  /** Enable parallel execution of loop iterations */
300  parallel: boolean;
301  /** Maximum number of concurrent iterations (only used when parallel=true) */
302  max_concurrency?: number;
303}
304
305/** While loop configuration */
306export interface WhileLoop {
307  /** Condition expression */
308  condition: string;
309  /** Body expression */
310  body_expression: string;
311}
312
313/** Repeat loop configuration */
314export interface RepeatLoop {
315  /** Number of times to repeat */
316  count: number;
317  /** Body expression */
318  body_expression: string;
319}
320
321/** Try-catch configuration */
322export interface TryCatchConfig {
323  /** Node ID to try */
324  try_node: NodeId;
325  /** Node ID for catch handler */
326  catch_node?: NodeId;
327  /** Node ID for finally handler */
328  finally_node?: NodeId;
329  /** Variable name for error */
330  error_variable?: string;
331  /** Whether to rethrow after catch */
332  rethrow?: boolean;
333}
334
335/** Sub-workflow configuration */
336export interface SubWorkflowConfig {
337  /** Path to sub-workflow file */
338  workflow_path: string;
339  /** Input variable mappings */
340  input_mappings?: Record<string, string>;
341  /** Output variable mappings */
342  output_mappings?: Record<string, string>;
343  /** Whether to inherit parent context */
344  inherit_context?: boolean;
345}
346
347/** Parallel execution configuration */
348export interface ParallelConfig {
349  /** Tasks to execute in parallel */
350  tasks: ParallelTask[];
351  /** Execution strategy */
352  strategy: ParallelStrategy;
353  /** Timeout in milliseconds */
354  timeout_ms?: number;
355  /** Maximum concurrent tasks */
356  max_concurrency?: number;
357}
358
359/** Single parallel task */
360export interface ParallelTask {
361  /** Task identifier */
362  id: string;
363  /** Expression to execute */
364  expression: string;
365}
366
367/** Parallel execution strategy */
368export type ParallelStrategy = "WaitAll" | "Race" | "AllSettled";
369
370/** Approval node configuration */
371export interface ApprovalConfig {
372  /** Message to display for approval */
373  message: string;
374  /** Detailed description */
375  description?: string;
376  /** Required approvers (user IDs or roles) */
377  required_approvers?: string[];
378  /** Timeout in milliseconds */
379  timeout_ms?: number;
380  /** Context data for approval UI */
381  context_data?: Record<string, unknown>;
382}
383
384/** Form node configuration */
385export interface FormConfig {
386  /** Form title */
387  title: string;
388  /** Form description */
389  description?: string;
390  /** Form fields */
391  fields: FormField[];
392  /** Submit button label */
393  submit_label?: string;
394  /** Allowed submitters */
395  allowed_submitters?: string[];
396  /** Timeout in milliseconds */
397  timeout_ms?: number;
398}
399
400/** Form field definition */
401export interface FormField {
402  /** Field identifier */
403  id: string;
404  /** Field label */
405  label: string;
406  /** Field type */
407  field_type: FormFieldType;
408  /** Whether field is required */
409  required: boolean;
410  /** Default value */
411  default_value?: unknown;
412  /** Placeholder text */
413  placeholder?: string;
414  /** Help text */
415  help_text?: string;
416  /** Validation pattern (regex) */
417  validation_pattern?: string;
418  /** Options for select/radio fields */
419  options?: string[];
420}
421
422/** Form field types */
423export type FormFieldType =
424  | "Text"
425  | "Number"
426  | "Email"
427  | "Password"
428  | "TextArea"
429  | "Select"
430  | "MultiSelect"
431  | "Radio"
432  | "Checkbox"
433  | "Date"
434  | "DateTime";
435
436"#;
437
438const EDGE_TYPES: &str = r#"// ============================================================================
439// Edge Types
440// ============================================================================
441
442/** Edge connecting two nodes */
443export interface Edge {
444  /** Unique edge identifier */
445  id: EdgeId;
446  /** Source node ID */
447  from: NodeId;
448  /** Target node ID */
449  to: NodeId;
450  /** Condition for conditional edges */
451  condition?: string;
452  /** Edge label */
453  label?: string;
454}
455
456"#;
457
458const EXECUTION_TYPES: &str = r#"// ============================================================================
459// Execution Types
460// ============================================================================
461
462/** Execution state */
463export type ExecutionState =
464  | "Pending"
465  | "Running"
466  | "Completed"
467  | "Failed"
468  | "Cancelled"
469  | "Paused";
470
471/** Unique identifier for an execution */
472export type ExecutionId = UUID;
473
474/** Execution context variables */
475export type ExecutionContext = Record<string, unknown>;
476
477/** Node execution result */
478export interface NodeExecutionResult {
479  /** Node ID */
480  node_id: NodeId;
481  /** Execution state */
482  state: ExecutionState;
483  /** Output value */
484  output?: unknown;
485  /** Error message if failed */
486  error?: string;
487  /** Execution duration in milliseconds */
488  duration_ms?: number;
489  /** Token usage for LLM nodes */
490  token_usage?: TokenUsage;
491  /** Number of retry attempts */
492  retry_count?: number;
493}
494
495/** Token usage for LLM calls */
496export interface TokenUsage {
497  /** Input tokens */
498  input_tokens: number;
499  /** Output tokens */
500  output_tokens: number;
501  /** Total tokens */
502  total_tokens: number;
503}
504
505/** Complete execution result */
506export interface ExecutionResult {
507  /** Execution ID */
508  id: ExecutionId;
509  /** Workflow ID */
510  workflow_id: WorkflowId;
511  /** Overall state */
512  state: ExecutionState;
513  /** Started at timestamp */
514  started_at: DateTime;
515  /** Completed at timestamp */
516  completed_at?: DateTime;
517  /** Node results */
518  node_results: Record<string, NodeExecutionResult>;
519  /** Final context */
520  context: ExecutionContext;
521  /** Total duration in milliseconds */
522  duration_ms?: number;
523  /** Total cost in USD */
524  cost_usd?: number;
525}
526
527"#;
528
529const CONFIG_TYPES: &str = r#"// ============================================================================
530// Configuration Types
531// ============================================================================
532
533/** Cost estimate for a workflow */
534export interface CostEstimate {
535  /** Total estimated cost in USD */
536  total_cost_usd: number;
537  /** Cost breakdown by node */
538  node_costs: NodeCost[];
539  /** Cost breakdown by category */
540  category_costs: CategoryCosts;
541  /** Token estimates */
542  token_estimates: TokenEstimates;
543}
544
545/** Cost for a single node */
546export interface NodeCost {
547  /** Node ID */
548  node_id: NodeId;
549  /** Node name */
550  node_name: string;
551  /** Estimated cost in USD */
552  cost_usd: number;
553  /** Cost components */
554  components: CostComponent[];
555}
556
557/** Cost component */
558export interface CostComponent {
559  /** Component name */
560  name: string;
561  /** Cost in USD */
562  cost_usd: number;
563  /** Quantity */
564  quantity?: number;
565  /** Unit */
566  unit?: string;
567}
568
569/** Costs by category */
570export interface CategoryCosts {
571  /** LLM costs */
572  llm: number;
573  /** Vector search costs */
574  vector: number;
575  /** Tool/API costs */
576  tool: number;
577  /** Other costs */
578  other: number;
579}
580
581/** Token estimates */
582export interface TokenEstimates {
583  /** Estimated input tokens */
584  input_tokens: number;
585  /** Estimated output tokens */
586  output_tokens: number;
587  /** Total estimated tokens */
588  total_tokens: number;
589}
590
591/** Time estimate for a workflow */
592export interface TimeEstimate {
593  /** Total estimated time in milliseconds */
594  total_ms: number;
595  /** Time per node */
596  node_times: NodeTime[];
597  /** Critical path */
598  critical_path: NodeId[];
599  /** Estimated parallel speedup factor */
600  parallel_speedup: number;
601}
602
603/** Time estimate for a single node */
604export interface NodeTime {
605  /** Node ID */
606  node_id: NodeId;
607  /** Node name */
608  node_name: string;
609  /** Estimated time in milliseconds */
610  estimated_ms: number;
611  /** Minimum time in milliseconds */
612  min_ms?: number;
613  /** Maximum time in milliseconds */
614  max_ms?: number;
615}
616
617"#;
618
619const UTILITY_TYPES: &str = r#"// ============================================================================
620// Utility Types
621// ============================================================================
622
623/** Validation error */
624export interface ValidationError {
625  /** Error code */
626  code: string;
627  /** Error message */
628  message: string;
629  /** Related node ID */
630  node_id?: NodeId;
631  /** Related field */
632  field?: string;
633}
634
635/** Validation report */
636export interface ValidationReport {
637  /** Whether validation passed */
638  is_valid: boolean;
639  /** Validation errors */
640  errors: ValidationError[];
641  /** Validation warnings */
642  warnings: ValidationError[];
643  /** Validation statistics */
644  stats: ValidationStats;
645}
646
647/** Validation statistics */
648export interface ValidationStats {
649  /** Number of nodes */
650  node_count: number;
651  /** Number of edges */
652  edge_count: number;
653  /** Workflow depth */
654  max_depth: number;
655  /** Has cycles */
656  has_cycles: boolean;
657}
658
659"#;
660
661const WASM_BINDINGS: &str = r#"// ============================================================================
662// WASM Bindings
663// ============================================================================
664
665/**
666 * WASM-based workflow manipulation.
667 * Use with oxify-model compiled with `wasm-pack build --features wasm`
668 */
669export interface WasmWorkflow {
670  /** Get workflow as JSON string */
671  to_json(): string;
672  /** Get workflow as YAML string */
673  to_yaml(): string;
674  /** Get workflow ID */
675  id(): string;
676  /** Get workflow name */
677  name(): string;
678  /** Get workflow version */
679  version(): string;
680  /** Get node count */
681  node_count(): number;
682  /** Get edge count */
683  edge_count(): number;
684  /** Validate workflow */
685  validate(): string;
686}
687
688/**
689 * Fluent builder for creating workflows.
690 */
691export interface WasmWorkflowBuilder {
692  /** Set workflow description */
693  description(desc: string): WasmWorkflowBuilder;
694  /** Set workflow version */
695  version(version: string): WasmWorkflowBuilder;
696  /** Add a tag */
697  tag(tag: string): WasmWorkflowBuilder;
698  /** Add start node */
699  start_node(name: string): WasmWorkflowBuilder;
700  /** Add end node */
701  end_node(name: string): WasmWorkflowBuilder;
702  /** Add LLM node */
703  llm_node(name: string, provider: string, model: string, prompt: string): WasmWorkflowBuilder;
704  /** Add code node */
705  code_node(name: string, code: string): WasmWorkflowBuilder;
706  /** Add retriever node */
707  retriever_node(name: string, db_type: string, collection: string, query: string, top_k: number): WasmWorkflowBuilder;
708  /** Add if-else node */
709  if_else_node(name: string, expression: string, true_branch: string, false_branch: string): WasmWorkflowBuilder;
710  /** Add switch node */
711  switch_node(name: string, expression: string): WasmWorkflowBuilder;
712  /** Add tool node */
713  tool_node(name: string, server_id: string, tool_name: string): WasmWorkflowBuilder;
714  /** Add foreach loop node */
715  foreach_node(name: string, collection_path: string, item_var: string, body: string): WasmWorkflowBuilder;
716  /** Add while loop node */
717  while_node(name: string, condition: string, body: string, max_iterations: number): WasmWorkflowBuilder;
718  /** Add repeat loop node */
719  repeat_node(name: string, count: number, body: string): WasmWorkflowBuilder;
720  /** Connect two nodes */
721  connect(from_name: string, to_name: string): WasmWorkflowBuilder;
722  /** Build the workflow */
723  build(): WasmWorkflow;
724}
725
726/**
727 * Utility functions for working with workflows.
728 */
729export interface WasmWorkflowUtils {
730  /** Generate a new UUID */
731  generate_uuid(): string;
732  /** Convert JSON to YAML */
733  json_to_yaml(json: string): string;
734  /** Convert YAML to JSON */
735  yaml_to_json(yaml: string): string;
736}
737
738/** Create a new workflow from JSON */
739export function workflow_from_json(json: string): WasmWorkflow;
740
741/** Create a new workflow from YAML */
742export function workflow_from_yaml(yaml: string): WasmWorkflow;
743
744/** Create a new workflow builder */
745export function new_workflow_builder(name: string): WasmWorkflowBuilder;
746
747/** Get workflow utilities */
748export function workflow_utils(): WasmWorkflowUtils;
749"#;
750
751#[cfg(test)]
752mod tests {
753    use super::*;
754
755    #[test]
756    fn test_generate_typescript_definitions() {
757        let definitions = generate_typescript_definitions();
758        assert!(!definitions.is_empty());
759        assert!(definitions.contains("export interface Workflow"));
760        assert!(definitions.contains("export interface Node"));
761        assert!(definitions.contains("export interface Edge"));
762        assert!(definitions.contains("export type NodeKind"));
763        assert!(definitions.contains("export interface LlmConfig"));
764        assert!(definitions.contains("export interface VectorConfig"));
765    }
766
767    #[test]
768    fn test_definitions_contain_all_node_types() {
769        let definitions = generate_typescript_definitions();
770        assert!(definitions.contains("LlmCall"));
771        assert!(definitions.contains("Retriever"));
772        assert!(definitions.contains("CodeExecution"));
773        assert!(definitions.contains("IfElse"));
774        assert!(definitions.contains("Switch"));
775        assert!(definitions.contains("Loop"));
776        assert!(definitions.contains("TryCatch"));
777        assert!(definitions.contains("SubWorkflow"));
778        assert!(definitions.contains("Parallel"));
779        assert!(definitions.contains("Approval"));
780        assert!(definitions.contains("Form"));
781    }
782
783    #[test]
784    fn test_definitions_contain_execution_types() {
785        let definitions = generate_typescript_definitions();
786        assert!(definitions.contains("ExecutionState"));
787        assert!(definitions.contains("ExecutionResult"));
788        assert!(definitions.contains("NodeExecutionResult"));
789        assert!(definitions.contains("TokenUsage"));
790    }
791
792    #[test]
793    fn test_definitions_contain_wasm_bindings() {
794        let definitions = generate_typescript_definitions();
795        assert!(definitions.contains("WasmWorkflow"));
796        assert!(definitions.contains("WasmWorkflowBuilder"));
797        assert!(definitions.contains("WasmWorkflowUtils"));
798        assert!(definitions.contains("workflow_from_json"));
799        assert!(definitions.contains("new_workflow_builder"));
800    }
801
802    #[test]
803    fn test_definitions_contain_config_types() {
804        let definitions = generate_typescript_definitions();
805        assert!(definitions.contains("CostEstimate"));
806        assert!(definitions.contains("TimeEstimate"));
807        assert!(definitions.contains("ValidationReport"));
808    }
809
810    #[test]
811    fn test_definitions_valid_typescript_syntax() {
812        let definitions = generate_typescript_definitions();
813        // Check for proper TypeScript syntax patterns
814        assert!(definitions.contains("export interface"));
815        assert!(definitions.contains("export type"));
816        assert!(definitions.contains(": string"));
817        assert!(definitions.contains(": number"));
818        assert!(definitions.contains("?: ")); // Optional fields
819        assert!(definitions.contains("Record<")); // Generic types
820    }
821}