rename client and move incomplete projects to research
This commit is contained in:
		
							
								
								
									
										38
									
								
								examples/flows/new_create_payment_intent_error.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								examples/flows/new_create_payment_intent_error.rhai
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| // Error handler for failed payment intent creation | ||||
| // This script is triggered when a payment intent creation fails | ||||
|  | ||||
| print("❌ Payment Intent Creation Failed!"); | ||||
| print("=================================="); | ||||
|  | ||||
| // The error data is available as 'parsed_error' | ||||
| if parsed_error != () { | ||||
|     print(`Error: ${parsed_error}`); | ||||
|      | ||||
|     // You can handle different types of errors | ||||
|     if parsed_error.contains("authentication") { | ||||
|         print("🔑 Authentication error - check API key"); | ||||
|         // eval_file("flows/handle_auth_error.rhai"); | ||||
|     } else if parsed_error.contains("insufficient_funds") { | ||||
|         print("💰 Insufficient funds error"); | ||||
|         // eval_file("flows/handle_insufficient_funds.rhai"); | ||||
|     } else if parsed_error.contains("card_declined") { | ||||
|         print("💳 Card declined error"); | ||||
|         // eval_file("flows/handle_card_declined.rhai"); | ||||
|     } else { | ||||
|         print("⚠️ General payment error"); | ||||
|         // eval_file("flows/handle_general_payment_error.rhai"); | ||||
|     } | ||||
|      | ||||
|     // Log the error for monitoring | ||||
|     print("📊 Logging error for analytics..."); | ||||
|     // eval_file("flows/log_payment_error.rhai"); | ||||
|      | ||||
|     // Notify relevant parties | ||||
|     print("📧 Sending error notifications..."); | ||||
|     // eval_file("flows/send_error_notification.rhai"); | ||||
|      | ||||
| } else { | ||||
|     print("⚠️ No error data received"); | ||||
| } | ||||
|  | ||||
| print("🔄 Error handling complete!"); | ||||
							
								
								
									
										34
									
								
								examples/flows/new_create_payment_intent_response.rhai
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								examples/flows/new_create_payment_intent_response.rhai
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| // Response handler for successful payment intent creation | ||||
| // This script is triggered when a payment intent is successfully created | ||||
|  | ||||
| print("✅ Payment Intent Created Successfully!"); | ||||
| print("====================================="); | ||||
|  | ||||
| // The response data is available as 'parsed_data' | ||||
| if parsed_data != () { | ||||
|     print(`Payment Intent ID: ${parsed_data.id}`); | ||||
|     print(`Amount: ${parsed_data.amount}`); | ||||
|     print(`Currency: ${parsed_data.currency}`); | ||||
|     print(`Status: ${parsed_data.status}`); | ||||
|      | ||||
|     if parsed_data.client_secret != () { | ||||
|         print(`Client Secret: ${parsed_data.client_secret}`); | ||||
|     } | ||||
|      | ||||
|     // You can now trigger additional workflows | ||||
|     print("🔄 Triggering next steps..."); | ||||
|      | ||||
|     // Example: Send confirmation email | ||||
|     // eval_file("flows/send_payment_confirmation_email.rhai"); | ||||
|      | ||||
|     // Example: Update user account | ||||
|     // eval_file("flows/update_user_payment_status.rhai"); | ||||
|      | ||||
|     // Example: Log analytics event | ||||
|     // eval_file("flows/log_payment_analytics.rhai"); | ||||
|      | ||||
| } else { | ||||
|     print("⚠️ No response data received"); | ||||
| } | ||||
|  | ||||
| print("🎉 Payment intent response processing complete!"); | ||||
| @@ -8,11 +8,11 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread", "time", "sync" | ||||
| url = "2" # For parsing Redis URL | ||||
| tracing = "0.1" # For logging | ||||
| tracing-subscriber = { version = "0.3", features = ["env-filter"] } | ||||
| log = "0.4" # rhai_client uses log crate | ||||
| log = "0.4" # rhai_dispatcher uses log crate | ||||
| rustyline = { version = "13.0.0", features = ["derive"] } # For enhanced REPL input | ||||
| tempfile = "3.8" # For creating temporary files for editing | ||||
| 
 | ||||
| rhai_client = { path = "../client" } | ||||
| rhai_dispatcher = { path = "../client" } | ||||
| anyhow = "1.0" # For simpler error handling | ||||
| 
 | ||||
| rhailib_worker = { path = "../worker", package = "rhailib_worker" } | ||||
| @@ -1,5 +1,5 @@ | ||||
| use anyhow::Context; | ||||
| use rhai_client::{RhaiClient, RhaiClientError, RhaiTaskDetails}; | ||||
| use rhai_dispatcher::{RhaiDispatcher, RhaiDispatcherError, RhaiTaskDetails}; | ||||
| use std::env; | ||||
| use std::sync::Arc; | ||||
| use std::time::Duration; | ||||
| @@ -17,7 +17,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|         .with_env_filter( | ||||
|             EnvFilter::from_default_env() | ||||
|                 .add_directive("connect_and_play=info".parse().unwrap()) | ||||
|                 .add_directive("rhai_client=info".parse().unwrap()), | ||||
|                 .add_directive("rhai_dispatcher=info".parse().unwrap()), | ||||
|         ) | ||||
|         .init(); | ||||
| 
 | ||||
| @@ -94,12 +94,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     tokio::time::sleep(Duration::from_secs(1)).await; | ||||
| 
 | ||||
|     println!( | ||||
|         "Initializing RhaiClient for Redis at {} to target worker '{}'...", | ||||
|         "Initializing RhaiDispatcher for Redis at {} to target worker '{}'...", | ||||
|         redis_url, worker_name | ||||
|     ); | ||||
|     let client = RhaiClient::new(&redis_url) | ||||
|         .with_context(|| format!("Failed to create RhaiClient for Redis URL: {}", redis_url))?; | ||||
|     println!("RhaiClient initialized."); | ||||
|     let client = RhaiDispatcher::new(&redis_url) | ||||
|         .with_context(|| format!("Failed to create RhaiDispatcher for Redis URL: {}", redis_url))?; | ||||
|     println!("RhaiDispatcher initialized."); | ||||
| 
 | ||||
|     let script = "let a = 10; let b = 32; let message = \"Hello from example script!\"; message + \" Result: \" + (a + b)"; | ||||
|     println!("\nSending script:\n```rhai\n{}\n```", script); | ||||
| @@ -125,28 +125,28 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|             } | ||||
|         } | ||||
|         Err(e) => match e { | ||||
|             RhaiClientError::Timeout(task_id) => { | ||||
|             RhaiDispatcherError::Timeout(task_id) => { | ||||
|                 eprintln!( | ||||
|                     "\nError: Script execution timed out for task_id: {}.", | ||||
|                     task_id | ||||
|                 ); | ||||
|             } | ||||
|             RhaiClientError::RedisError(redis_err) => { | ||||
|             RhaiDispatcherError::RedisError(redis_err) => { | ||||
|                 eprintln!( | ||||
|                     "\nError: Redis communication failed: {}. Check Redis connection and server status.", | ||||
|                     redis_err | ||||
|                 ); | ||||
|             } | ||||
|             RhaiClientError::SerializationError(serde_err) => { | ||||
|             RhaiDispatcherError::SerializationError(serde_err) => { | ||||
|                 eprintln!( | ||||
|                     "\nError: Failed to serialize/deserialize task data: {}.", | ||||
|                     serde_err | ||||
|                 ); | ||||
|             } | ||||
|             RhaiClientError::TaskNotFound(task_id) => { | ||||
|             RhaiDispatcherError::TaskNotFound(task_id) => { | ||||
|                 eprintln!("\nError: Task {} not found after submission.", task_id); | ||||
|             } /* All RhaiClientError variants are handled, so _ arm is not strictly needed
 | ||||
|               unless RhaiClientError becomes non-exhaustive in the future. */ | ||||
|             } /* All RhaiDispatcherError variants are handled, so _ arm is not strictly needed
 | ||||
|               unless RhaiDispatcherError becomes non-exhaustive in the future. */ | ||||
|         }, | ||||
|     } | ||||
| 
 | ||||
| @@ -1,5 +1,5 @@ | ||||
| use anyhow::Context; | ||||
| use rhai_client::{RhaiClient, RhaiClientBuilder, RhaiClientError}; | ||||
| use rhai_dispatcher::{RhaiDispatcher, RhaiDispatcherBuilder, RhaiDispatcherError}; | ||||
| use rustyline::error::ReadlineError; | ||||
| use rustyline::{Config, DefaultEditor, EditMode}; | ||||
| use std::env; | ||||
| @@ -12,7 +12,7 @@ use tracing_subscriber::EnvFilter; | ||||
| // Default timeout for script execution
 | ||||
| const DEFAULT_SCRIPT_TIMEOUT_SECONDS: u64 = 30; | ||||
| 
 | ||||
| async fn execute_script(client: &RhaiClient, circle_name: &str, script_content: String) { | ||||
| async fn execute_script(client: &RhaiDispatcher, circle_name: &str, script_content: String) { | ||||
|     if script_content.trim().is_empty() { | ||||
|         println!("Script is empty, not sending."); | ||||
|         return; | ||||
| @@ -47,25 +47,25 @@ async fn execute_script(client: &RhaiClient, circle_name: &str, script_content: | ||||
|             } | ||||
|         } | ||||
|         Err(e) => match e { | ||||
|             RhaiClientError::Timeout(task_id) => { | ||||
|             RhaiDispatcherError::Timeout(task_id) => { | ||||
|                 eprintln!( | ||||
|                     "Error: Script execution timed out for task_id: {}.", | ||||
|                     task_id | ||||
|                 ); | ||||
|             } | ||||
|             RhaiClientError::RedisError(redis_err) => { | ||||
|             RhaiDispatcherError::RedisError(redis_err) => { | ||||
|                 eprintln!( | ||||
|                     "Error: Redis communication failed: {}. Check Redis connection and server status.", | ||||
|                     redis_err | ||||
|                 ); | ||||
|             } | ||||
|             RhaiClientError::SerializationError(serde_err) => { | ||||
|             RhaiDispatcherError::SerializationError(serde_err) => { | ||||
|                 eprintln!( | ||||
|                     "Error: Failed to serialize/deserialize task data: {}.", | ||||
|                     serde_err | ||||
|                 ); | ||||
|             } | ||||
|             RhaiClientError::TaskNotFound(task_id) => { | ||||
|             RhaiDispatcherError::TaskNotFound(task_id) => { | ||||
|                 eprintln!( | ||||
|                     "Error: Task {} not found after submission (this should be rare).", | ||||
|                     task_id | ||||
| @@ -81,15 +81,15 @@ async fn run_repl(redis_url: String, circle_name: String) -> anyhow::Result<()> | ||||
|         circle_name, redis_url | ||||
|     ); | ||||
| 
 | ||||
|     let client = RhaiClientBuilder::new() | ||||
|     let client = RhaiDispatcherBuilder::new() | ||||
|         .redis_url(&redis_url) | ||||
|         .caller_id("ui_repl") // Set a caller_id
 | ||||
|         .build() | ||||
|         .with_context(|| format!("Failed to create RhaiClient for Redis URL: {}", redis_url))?; | ||||
|         .with_context(|| format!("Failed to create RhaiDispatcher for Redis URL: {}", redis_url))?; | ||||
| 
 | ||||
|     // No explicit connect() needed for rhai_client, connection is handled per-operation or pooled.
 | ||||
|     // No explicit connect() needed for rhai_dispatcher, connection is handled per-operation or pooled.
 | ||||
|     println!( | ||||
|         "RhaiClient initialized. Ready to send scripts to worker '{}'.", | ||||
|         "RhaiDispatcher initialized. Ready to send scripts to worker '{}'.", | ||||
|         circle_name | ||||
|     ); | ||||
|     println!( | ||||
| @@ -212,7 +212,7 @@ async fn run_repl(redis_url: String, circle_name: String) -> anyhow::Result<()> | ||||
|         // Failed to save history, not critical
 | ||||
|     } | ||||
| 
 | ||||
|     // No explicit disconnect for RhaiClient as it manages connections internally.
 | ||||
|     // No explicit disconnect for RhaiDispatcher as it manages connections internally.
 | ||||
|     println!("Exited REPL."); | ||||
|     Ok(()) | ||||
| } | ||||
| @@ -223,7 +223,7 @@ async fn main() -> anyhow::Result<()> { | ||||
|         .with_env_filter( | ||||
|             EnvFilter::from_default_env() | ||||
|                 .add_directive("ui_repl=info".parse()?) | ||||
|                 .add_directive("rhai_client=info".parse()?), | ||||
|                 .add_directive("rhai_dispatcher=info".parse()?), | ||||
|         ) | ||||
|         .init(); | ||||
| 
 | ||||
| @@ -1,11 +1,11 @@ | ||||
| [package] | ||||
| name = "rhai_client" | ||||
| name = "rhai_dispatcher" | ||||
| version = "0.1.0" | ||||
| edition = "2021" | ||||
| 
 | ||||
| [[bin]] | ||||
| name = "client" | ||||
| path = "cmd/client.rs" | ||||
| name = "dispatcher" | ||||
| path = "cmd/dispatcher.rs" | ||||
| 
 | ||||
| [dependencies] | ||||
| clap = { version = "4.4", features = ["derive"] } | ||||
| @@ -4,7 +4,7 @@ The `rhai-client` crate provides a fluent builder-based interface for submitting | ||||
| 
 | ||||
| ## Features | ||||
| 
 | ||||
| -   **Fluent Builder API**: A `RhaiClientBuilder` for easy client configuration and a `PlayRequestBuilder` for constructing and submitting script execution requests. | ||||
| -   **Fluent Builder API**: A `RhaiDispatcherBuilder` for easy client configuration and a `PlayRequestBuilder` for constructing and submitting script execution requests. | ||||
| -   **Asynchronous Operations**: Built with `tokio` for non-blocking I/O. | ||||
| -   **Request-Reply Pattern**: Submits tasks and awaits results on a dedicated reply queue, eliminating the need for polling. | ||||
| -   **Configurable Timeouts**: Set timeouts for how long the client should wait for a task to complete. | ||||
| @@ -13,8 +13,8 @@ The `rhai-client` crate provides a fluent builder-based interface for submitting | ||||
| 
 | ||||
| ## Core Components | ||||
| 
 | ||||
| -   **`RhaiClientBuilder`**: A builder to construct a `RhaiClient`. Requires a `caller_id` and Redis URL. | ||||
| -   **`RhaiClient`**: The main client for interacting with the task system. It's used to create `PlayRequestBuilder` instances. | ||||
| -   **`RhaiDispatcherBuilder`**: A builder to construct a `RhaiDispatcher`. Requires a `caller_id` and Redis URL. | ||||
| -   **`RhaiDispatcher`**: The main client for interacting with the task system. It's used to create `PlayRequestBuilder` instances. | ||||
| -   **`PlayRequestBuilder`**: A fluent builder for creating and dispatching a script execution request. You can set: | ||||
|     -   `worker_id`: The ID of the worker queue to send the task to. | ||||
|     -   `script` or `script_path`: The Rhai script to execute. | ||||
| @@ -24,11 +24,11 @@ The `rhai-client` crate provides a fluent builder-based interface for submitting | ||||
|     -   `submit()`: Submits the request and returns immediately (fire-and-forget). | ||||
|     -   `await_response()`: Submits the request and waits for the result or a timeout. | ||||
| -   **`RhaiTaskDetails`**: A struct representing the details of a task, including its script, status (`pending`, `processing`, `completed`, `error`), output, and error messages. | ||||
| -   **`RhaiClientError`**: An enum for various errors, such as Redis errors, serialization issues, or task timeouts. | ||||
| -   **`RhaiDispatcherError`**: An enum for various errors, such as Redis errors, serialization issues, or task timeouts. | ||||
| 
 | ||||
| ## How It Works | ||||
| 
 | ||||
| 1.  A `RhaiClient` is created using the `RhaiClientBuilder`, configured with a `caller_id` and Redis URL. | ||||
| 1.  A `RhaiDispatcher` is created using the `RhaiDispatcherBuilder`, configured with a `caller_id` and Redis URL. | ||||
| 2.  A `PlayRequestBuilder` is created from the client. | ||||
| 3.  The script, `worker_id`, and an optional `timeout` are configured on the builder. | ||||
| 4.  When `await_response()` is called: | ||||
| @@ -48,7 +48,7 @@ The `rhai-client` crate provides a fluent builder-based interface for submitting | ||||
| The following example demonstrates how to build a client, submit a script, and wait for the result. | ||||
| 
 | ||||
| ```rust | ||||
| use rhai_client::{RhaiClientBuilder, RhaiClientError}; | ||||
| use rhai_dispatcher::{RhaiDispatcherBuilder, RhaiDispatcherError}; | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| #[tokio::main] | ||||
| @@ -56,7 +56,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     env_logger::init(); | ||||
| 
 | ||||
|     // 1. Build the client | ||||
|     let client = RhaiClientBuilder::new() | ||||
|     let client = RhaiDispatcherBuilder::new() | ||||
|         .caller_id("my-app-instance-1") | ||||
|         .redis_url("redis://127.0.0.1/") | ||||
|         .build()?; | ||||
| @@ -82,7 +82,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|                 log::info!("Output: {}", output); | ||||
|             } | ||||
|         } | ||||
|         Err(RhaiClientError::Timeout(task_id)) => { | ||||
|         Err(RhaiDispatcherError::Timeout(task_id)) => { | ||||
|             log::error!("Task {} timed out.", task_id); | ||||
|         } | ||||
|         Err(e) => { | ||||
| @@ -150,7 +150,7 @@ The client provides clear error messages for: | ||||
| 
 | ||||
| ### Dependencies | ||||
| 
 | ||||
| - `rhai_client`: Core client library for Redis-based script execution | ||||
| - `rhai_dispatcher`: Core client library for Redis-based script execution | ||||
| - `redis`: Redis client for task queue communication | ||||
| - `clap`: Command-line argument parsing | ||||
| - `env_logger`: Logging infrastructure | ||||
| @@ -1,5 +1,5 @@ | ||||
| use clap::Parser; | ||||
| use rhai_client::{RhaiClient, RhaiClientBuilder}; | ||||
| use rhai_dispatcher::{RhaiDispatcher, RhaiDispatcherBuilder}; | ||||
| use log::{error, info}; | ||||
| use std::io::{self, Write}; | ||||
| use std::time::Duration; | ||||
| @@ -9,15 +9,15 @@ use std::time::Duration; | ||||
| struct Args { | ||||
|     /// Caller public key (caller ID)
 | ||||
|     #[arg(short = 'c', long = "caller-key", help = "Caller public key (your identity)")] | ||||
|     caller_public_key: String, | ||||
|     caller_id: String, | ||||
| 
 | ||||
|     /// Circle public key (context ID)
 | ||||
|     #[arg(short = 'k', long = "circle-key", help = "Circle public key (execution context)")] | ||||
|     circle_public_key: String, | ||||
|     context_id: String, | ||||
| 
 | ||||
|     /// Worker public key (defaults to circle public key if not provided)
 | ||||
|     #[arg(short = 'w', long = "worker-key", help = "Worker public key (defaults to circle key)")] | ||||
|     worker_public_key: Option<String>, | ||||
|     worker_id: String, | ||||
| 
 | ||||
|     /// Redis URL
 | ||||
|     #[arg(short, long, default_value = "redis://localhost:6379", help = "Redis connection URL")] | ||||
| @@ -50,8 +50,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
| 
 | ||||
|     // Configure logging based on verbosity level
 | ||||
|     let log_config = match args.verbose { | ||||
|         0 => "warn,rhai_client=info", | ||||
|         1 => "info,rhai_client=debug", | ||||
|         0 => "warn,rhai_dispatcher=info", | ||||
|         1 => "info,rhai_dispatcher=debug", | ||||
|         2 => "debug", | ||||
|         _ => "trace", | ||||
|     }; | ||||
| @@ -67,21 +67,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|         env_logger::init(); | ||||
|     } | ||||
| 
 | ||||
|     // Use worker key or default to circle key
 | ||||
|     let worker_key = args.worker_public_key.unwrap_or_else(|| args.circle_public_key.clone()); | ||||
| 
 | ||||
|     info!("🔗 Starting Rhai Client"); | ||||
|     info!("🔗 Starting Rhai Dispatcher"); | ||||
|     info!("📋 Configuration:"); | ||||
|     info!("   Caller Key: {}", args.caller_public_key); | ||||
|     info!("   Circle Key: {}", args.circle_public_key); | ||||
|     info!("   Worker Key: {}", worker_key); | ||||
|     info!("   Caller ID: {}", args.caller_id); | ||||
|     info!("   Context ID: {}", args.context_id); | ||||
|     info!("   Worker ID: {}", args.worker_id); | ||||
|     info!("   Redis URL: {}", args.redis_url); | ||||
|     info!("   Timeout: {}s", args.timeout); | ||||
|     info!(""); | ||||
| 
 | ||||
|     // Create the Rhai client
 | ||||
|     let client = RhaiClientBuilder::new() | ||||
|         .caller_id(&args.caller_public_key) | ||||
|     let client = RhaiDispatcherBuilder::new() | ||||
|         .caller_id(&args.caller_id) | ||||
|         .worker_id(&args.worker_id) | ||||
|         .context_id(&args.context_id) | ||||
|         .redis_url(&args.redis_url) | ||||
|         .build()?; | ||||
| 
 | ||||
| @@ -91,26 +90,25 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     if let Some(script_content) = args.script { | ||||
|         // Execute inline script
 | ||||
|         info!("📜 Executing inline script"); | ||||
|         execute_script(&client, &worker_key, script_content, args.timeout).await?; | ||||
|         execute_script(&client, script_content, args.timeout).await?; | ||||
|     } else if let Some(file_path) = args.file { | ||||
|         // Execute script from file
 | ||||
|         info!("📁 Loading script from file: {}", file_path); | ||||
|         let script_content = std::fs::read_to_string(&file_path) | ||||
|             .map_err(|e| format!("Failed to read script file '{}': {}", file_path, e))?; | ||||
|         execute_script(&client, &worker_key, script_content, args.timeout).await?; | ||||
|         execute_script(&client, script_content, args.timeout).await?; | ||||
|     } else { | ||||
|         // Interactive mode
 | ||||
|         info!("🎮 Entering interactive mode"); | ||||
|         info!("Type Rhai scripts and press Enter to execute. Type 'exit' or 'quit' to close."); | ||||
|         run_interactive_mode(&client, &worker_key, args.timeout).await?; | ||||
|         run_interactive_mode(&client, args.timeout).await?; | ||||
|     } | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| 
 | ||||
| async fn execute_script( | ||||
|     client: &RhaiClient, | ||||
|     worker_key: &str, | ||||
|     client: &RhaiDispatcher, | ||||
|     script: String, | ||||
|     timeout_secs: u64, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
| @@ -120,7 +118,6 @@ async fn execute_script( | ||||
|     
 | ||||
|     match client | ||||
|         .new_play_request() | ||||
|         .recipient_id(worker_key) | ||||
|         .script(&script) | ||||
|         .timeout(timeout) | ||||
|         .await_response() | ||||
| @@ -146,8 +143,7 @@ async fn execute_script( | ||||
| } | ||||
| 
 | ||||
| async fn run_interactive_mode( | ||||
|     client: &RhaiClient, | ||||
|     worker_key: &str, | ||||
|     client: &RhaiDispatcher, | ||||
|     timeout_secs: u64, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let timeout = Duration::from_secs(timeout_secs); | ||||
| @@ -174,7 +170,6 @@ async fn run_interactive_mode( | ||||
|         
 | ||||
|         match client | ||||
|             .new_play_request() | ||||
|             .recipient_id(worker_key) | ||||
|             .script(input) | ||||
|             .timeout(timeout) | ||||
|             .await_response() | ||||
| @@ -1,6 +1,6 @@ | ||||
| # Architecture of the `rhai_client` Crate | ||||
| # Architecture of the `rhai_dispatcher` Crate | ||||
| 
 | ||||
| The `rhai_client` crate provides a Redis-based client library for submitting Rhai scripts to distributed worker services and awaiting their execution results. It implements a request-reply pattern using Redis as the message broker. | ||||
| The `rhai_dispatcher` crate provides a Redis-based client library for submitting Rhai scripts to distributed worker services and awaiting their execution results. It implements a request-reply pattern using Redis as the message broker. | ||||
| 
 | ||||
| ## Core Architecture | ||||
| 
 | ||||
| @@ -8,7 +8,7 @@ The client follows a builder pattern design with clear separation of concerns: | ||||
| 
 | ||||
| ```mermaid | ||||
| graph TD | ||||
|     A[RhaiClientBuilder] --> B[RhaiClient] | ||||
|     A[RhaiDispatcherBuilder] --> B[RhaiDispatcher] | ||||
|     B --> C[PlayRequestBuilder] | ||||
|     C --> D[PlayRequest] | ||||
|     D --> E[Redis Task Queue] | ||||
| @@ -35,9 +35,9 @@ graph TD | ||||
| 
 | ||||
| ## Key Components | ||||
| 
 | ||||
| ### 1. RhaiClientBuilder | ||||
| ### 1. RhaiDispatcherBuilder | ||||
| 
 | ||||
| A builder pattern implementation for constructing `RhaiClient` instances with proper configuration validation. | ||||
| A builder pattern implementation for constructing `RhaiDispatcher` instances with proper configuration validation. | ||||
| 
 | ||||
| **Responsibilities:** | ||||
| - Configure Redis connection URL | ||||
| @@ -47,9 +47,9 @@ A builder pattern implementation for constructing `RhaiClient` instances with pr | ||||
| **Key Methods:** | ||||
| - `caller_id(id: &str)` - Sets the caller identifier | ||||
| - `redis_url(url: &str)` - Configures Redis connection | ||||
| - `build()` - Creates the final `RhaiClient` instance | ||||
| - `build()` - Creates the final `RhaiDispatcher` instance | ||||
| 
 | ||||
| ### 2. RhaiClient | ||||
| ### 2. RhaiDispatcher | ||||
| 
 | ||||
| The main client interface that manages Redis connections and provides factory methods for creating play requests. | ||||
| 
 | ||||
| @@ -103,7 +103,7 @@ pub struct RhaiTaskDetails { | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| #### RhaiClientError | ||||
| #### RhaiDispatcherError | ||||
| Comprehensive error handling for various failure scenarios: | ||||
| - `RedisError` - Redis connection/operation failures | ||||
| - `SerializationError` - JSON serialization/deserialization issues | ||||
| @@ -1,5 +1,5 @@ | ||||
| use log::info; | ||||
| use rhai_client::{RhaiClientBuilder, RhaiClientError}; | ||||
| use rhai_dispatcher::{RhaiDispatcherBuilder, RhaiDispatcherError}; | ||||
| use std::time::{Duration, Instant}; | ||||
| 
 | ||||
| #[tokio::main] | ||||
| @@ -9,11 +9,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|         .init(); | ||||
| 
 | ||||
|     // Build the client using the new builder pattern
 | ||||
|     let client = RhaiClientBuilder::new() | ||||
|     let client = RhaiDispatcherBuilder::new() | ||||
|         .caller_id("timeout-example-runner") | ||||
|         .redis_url("redis://127.0.0.1/") | ||||
|         .build()?; | ||||
|     info!("RhaiClient created."); | ||||
|     info!("RhaiDispatcher created."); | ||||
| 
 | ||||
|     let script_content = r#" | ||||
|         // This script will never be executed by a worker because the recipient does not exist.
 | ||||
| @@ -56,8 +56,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|             info!("Elapsed time: {:?}", elapsed); | ||||
| 
 | ||||
|             match e { | ||||
|                 RhaiClientError::Timeout(task_id) => { | ||||
|                     info!("Timeout Example PASSED: Correctly received RhaiClientError::Timeout for task_id: {}", task_id); | ||||
|                 RhaiDispatcherError::Timeout(task_id) => { | ||||
|                     info!("Timeout Example PASSED: Correctly received RhaiDispatcherError::Timeout for task_id: {}", task_id); | ||||
|                     // Ensure the elapsed time is close to the timeout duration
 | ||||
|                     // Allow for some buffer for processing
 | ||||
|                     assert!( | ||||
| @@ -75,11 +75,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|                 } | ||||
|                 other_error => { | ||||
|                     log::error!( | ||||
|                         "Timeout Example FAILED: Expected RhaiClientError::Timeout, but got other error: {:?}", | ||||
|                         "Timeout Example FAILED: Expected RhaiDispatcherError::Timeout, but got other error: {:?}", | ||||
|                         other_error | ||||
|                     ); | ||||
|                     Err(format!( | ||||
|                         "Expected RhaiClientError::Timeout, got other error: {:?}", | ||||
|                         "Expected RhaiDispatcherError::Timeout, got other error: {:?}", | ||||
|                         other_error | ||||
|                     ) | ||||
|                     .into()) | ||||
| @@ -7,13 +7,13 @@ | ||||
| //! ## Quick Start
 | ||||
| //!
 | ||||
| //! ```rust
 | ||||
| //! use rhai_client::{RhaiClientBuilder, RhaiClientError};
 | ||||
| //! use rhai_dispatcher::{RhaiDispatcherBuilder, RhaiDispatcherError};
 | ||||
| //! use std::time::Duration;
 | ||||
| //!
 | ||||
| //! #[tokio::main]
 | ||||
| //! async fn main() -> Result<(), Box<dyn std::error::Error>> {
 | ||||
| //!     // Build the client
 | ||||
| //!     let client = RhaiClientBuilder::new()
 | ||||
| //!     let client = RhaiDispatcherBuilder::new()
 | ||||
| //!         .caller_id("my-app-instance-1")
 | ||||
| //!         .redis_url("redis://127.0.0.1/")
 | ||||
| //!         .build()?;
 | ||||
| @@ -76,6 +76,8 @@ pub struct RhaiTaskDetails { | ||||
|     pub caller_id: String, | ||||
|     #[serde(rename = "contextId")] | ||||
|     pub context_id: String, | ||||
|     #[serde(rename = "workerId")] | ||||
|     pub worker_id: String, | ||||
| } | ||||
| 
 | ||||
| /// Comprehensive error type for all possible failures in the Rhai client.
 | ||||
| @@ -83,7 +85,7 @@ pub struct RhaiTaskDetails { | ||||
| /// This enum covers all error scenarios that can occur during client operations,
 | ||||
| /// from Redis connectivity issues to task execution timeouts.
 | ||||
| #[derive(Debug)] | ||||
| pub enum RhaiClientError { | ||||
| pub enum RhaiDispatcherError { | ||||
|     /// Redis connection or operation error
 | ||||
|     RedisError(redis::RedisError), | ||||
|     /// JSON serialization/deserialization error
 | ||||
| @@ -96,37 +98,37 @@ pub enum RhaiClientError { | ||||
|     ContextIdMissing, | ||||
| } | ||||
| 
 | ||||
| impl From<redis::RedisError> for RhaiClientError { | ||||
| impl From<redis::RedisError> for RhaiDispatcherError { | ||||
|     fn from(err: redis::RedisError) -> Self { | ||||
|         RhaiClientError::RedisError(err) | ||||
|         RhaiDispatcherError::RedisError(err) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<serde_json::Error> for RhaiClientError { | ||||
| impl From<serde_json::Error> for RhaiDispatcherError { | ||||
|     fn from(err: serde_json::Error) -> Self { | ||||
|         RhaiClientError::SerializationError(err) | ||||
|         RhaiDispatcherError::SerializationError(err) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl std::fmt::Display for RhaiClientError { | ||||
| impl std::fmt::Display for RhaiDispatcherError { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         match self { | ||||
|             RhaiClientError::RedisError(e) => write!(f, "Redis error: {}", e), | ||||
|             RhaiClientError::SerializationError(e) => write!(f, "Serialization error: {}", e), | ||||
|             RhaiClientError::Timeout(task_id) => { | ||||
|             RhaiDispatcherError::RedisError(e) => write!(f, "Redis error: {}", e), | ||||
|             RhaiDispatcherError::SerializationError(e) => write!(f, "Serialization error: {}", e), | ||||
|             RhaiDispatcherError::Timeout(task_id) => { | ||||
|                 write!(f, "Timeout waiting for task {} to complete", task_id) | ||||
|             } | ||||
|             RhaiClientError::TaskNotFound(task_id) => { | ||||
|             RhaiDispatcherError::TaskNotFound(task_id) => { | ||||
|                 write!(f, "Task {} not found after submission", task_id) | ||||
|             } | ||||
|             RhaiClientError::ContextIdMissing => { | ||||
|             RhaiDispatcherError::ContextIdMissing => { | ||||
|                 write!(f, "Context ID is missing") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl std::error::Error for RhaiClientError {} | ||||
| impl std::error::Error for RhaiDispatcherError {} | ||||
| 
 | ||||
| /// The main client for interacting with the Rhai task execution system.
 | ||||
| ///
 | ||||
| @@ -137,19 +139,21 @@ impl std::error::Error for RhaiClientError {} | ||||
| /// # Example
 | ||||
| ///
 | ||||
| /// ```rust
 | ||||
| /// use rhai_client::RhaiClientBuilder;
 | ||||
| /// use rhai_dispatcher::RhaiDispatcherBuilder;
 | ||||
| ///
 | ||||
| /// let client = RhaiClientBuilder::new()
 | ||||
| /// let client = RhaiDispatcherBuilder::new()
 | ||||
| ///     .caller_id("my-service")
 | ||||
| ///     .redis_url("redis://localhost/")
 | ||||
| ///     .build()?;
 | ||||
| /// ```
 | ||||
| pub struct RhaiClient { | ||||
| pub struct RhaiDispatcher { | ||||
|     redis_client: redis::Client, | ||||
|     caller_id: String, | ||||
|     worker_id: String, | ||||
|     context_id: String, | ||||
| } | ||||
| 
 | ||||
| /// Builder for constructing `RhaiClient` instances with proper configuration.
 | ||||
| /// Builder for constructing `RhaiDispatcher` instances with proper configuration.
 | ||||
| ///
 | ||||
| /// This builder ensures that all required configuration is provided before
 | ||||
| /// creating a client instance. It validates the configuration and provides
 | ||||
| @@ -162,13 +166,15 @@ pub struct RhaiClient { | ||||
| /// # Optional Configuration
 | ||||
| ///
 | ||||
| /// - `redis_url`: Redis connection URL (defaults to "redis://127.0.0.1/")
 | ||||
| pub struct RhaiClientBuilder { | ||||
| pub struct RhaiDispatcherBuilder { | ||||
|     redis_url: Option<String>, | ||||
|     caller_id: String, | ||||
|     worker_id: String, | ||||
|     context_id: String, | ||||
| } | ||||
| 
 | ||||
| impl RhaiClientBuilder { | ||||
|     /// Creates a new `RhaiClientBuilder` with default settings.
 | ||||
| impl RhaiDispatcherBuilder { | ||||
|     /// Creates a new `RhaiDispatcherBuilder` with default settings.
 | ||||
|     ///
 | ||||
|     /// The builder starts with no Redis URL (will default to "redis://127.0.0.1/")
 | ||||
|     /// and an empty caller ID (which must be set before building).
 | ||||
| @@ -176,6 +182,8 @@ impl RhaiClientBuilder { | ||||
|         Self { | ||||
|             redis_url: None, | ||||
|             caller_id: "".to_string(), | ||||
|             worker_id: "".to_string(), | ||||
|             context_id: "".to_string(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @@ -192,6 +200,31 @@ impl RhaiClientBuilder { | ||||
|         self.caller_id = caller_id.to_string(); | ||||
|         self | ||||
|     } | ||||
|     /// Sets the circle ID for this client instance.
 | ||||
|     ///
 | ||||
|     /// The circle ID is used to identify which circle's context a task should be executed in.
 | ||||
|     /// This is required at the time the client dispatches a script, but can be set on construction or on script dispatch.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `context_id` - A unique identifier for this client instance
 | ||||
|     pub fn context_id(mut self, context_id: &str) -> Self { | ||||
|         self.context_id = context_id.to_string(); | ||||
|         self | ||||
|     } | ||||
| 
 | ||||
|     /// Sets the worker ID for this client instance.
 | ||||
|     ///
 | ||||
|     /// The worker ID is used to identify which worker a task should be executed on.
 | ||||
|     /// This is required at the time the client dispatches a script, but can be set on construction or on script dispatch.
 | ||||
|     ///
 | ||||
|     /// # Arguments
 | ||||
|     ///
 | ||||
|     /// * `worker_id` - A unique identifier for this client instance
 | ||||
|     pub fn worker_id(mut self, worker_id: &str) -> Self { | ||||
|         self.worker_id = worker_id.to_string(); | ||||
|         self | ||||
|     } | ||||
| 
 | ||||
|     /// Sets the Redis connection URL.
 | ||||
|     ///
 | ||||
| @@ -205,7 +238,7 @@ impl RhaiClientBuilder { | ||||
|         self | ||||
|     } | ||||
| 
 | ||||
|     /// Builds the final `RhaiClient` instance.
 | ||||
|     /// Builds the final `RhaiDispatcher` instance.
 | ||||
|     ///
 | ||||
|     /// This method validates the configuration and creates the Redis client.
 | ||||
|     /// It will return an error if the caller ID is empty or if the Redis
 | ||||
| @@ -213,22 +246,18 @@ impl RhaiClientBuilder { | ||||
|     ///
 | ||||
|     /// # Returns
 | ||||
|     ///
 | ||||
|     /// * `Ok(RhaiClient)` - Successfully configured client
 | ||||
|     /// * `Err(RhaiClientError)` - Configuration or connection error
 | ||||
|     pub fn build(self) -> Result<RhaiClient, RhaiClientError> { | ||||
|     /// * `Ok(RhaiDispatcher)` - Successfully configured client
 | ||||
|     /// * `Err(RhaiDispatcherError)` - Configuration or connection error
 | ||||
|     pub fn build(self) -> Result<RhaiDispatcher, RhaiDispatcherError> { | ||||
|         let url = self | ||||
|             .redis_url | ||||
|             .unwrap_or_else(|| "redis://127.0.0.1/".to_string()); | ||||
|         let client = redis::Client::open(url)?; | ||||
|         if self.caller_id.is_empty() { | ||||
|             return Err(RhaiClientError::RedisError(redis::RedisError::from(( | ||||
|                 redis::ErrorKind::InvalidClientConfig, | ||||
|                 "Caller ID is empty", | ||||
|             )))); | ||||
|         } | ||||
|         Ok(RhaiClient { | ||||
|         Ok(RhaiDispatcher { | ||||
|             redis_client: client, | ||||
|             caller_id: self.caller_id, | ||||
|             worker_id: self.worker_id, | ||||
|             context_id: self.context_id, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| @@ -265,21 +294,23 @@ pub struct PlayRequest { | ||||
| ///     .await?;
 | ||||
| /// ```
 | ||||
| pub struct PlayRequestBuilder<'a> { | ||||
|     client: &'a RhaiClient, | ||||
|     client: &'a RhaiDispatcher, | ||||
|     request_id: String, | ||||
|     worker_id: String, | ||||
|     context_id: String, | ||||
|     caller_id: String, | ||||
|     script: String, | ||||
|     timeout: Duration, | ||||
| } | ||||
| 
 | ||||
| impl<'a> PlayRequestBuilder<'a> { | ||||
|     pub fn new(client: &'a RhaiClient) -> Self { | ||||
|     pub fn new(client: &'a RhaiDispatcher) -> Self { | ||||
|         Self { | ||||
|             client, | ||||
|             request_id: "".to_string(), | ||||
|             worker_id: "".to_string(), | ||||
|             context_id: "".to_string(), | ||||
|             worker_id: client.worker_id.clone(), | ||||
|             context_id: client.context_id.clone(), | ||||
|             caller_id: client.caller_id.clone(), | ||||
|             script: "".to_string(), | ||||
|             timeout: Duration::from_secs(10), | ||||
|         } | ||||
| @@ -315,7 +346,7 @@ impl<'a> PlayRequestBuilder<'a> { | ||||
|         self | ||||
|     } | ||||
| 
 | ||||
|     pub fn build(self) -> Result<PlayRequest, RhaiClientError> { | ||||
|     pub fn build(self) -> Result<PlayRequest, RhaiDispatcherError> { | ||||
|         let request_id = if self.request_id.is_empty() { | ||||
|             // Generate a UUID for the request_id
 | ||||
|             Uuid::new_v4().to_string() | ||||
| @@ -324,7 +355,11 @@ impl<'a> PlayRequestBuilder<'a> { | ||||
|         }; | ||||
| 
 | ||||
|         if self.context_id.is_empty() { | ||||
|             return Err(RhaiClientError::ContextIdMissing); | ||||
|             return Err(RhaiDispatcherError::ContextIdMissing); | ||||
|         } | ||||
| 
 | ||||
|         if self.caller_id.is_empty() { | ||||
|             return Err(RhaiDispatcherError::ContextIdMissing); | ||||
|         } | ||||
| 
 | ||||
|         let play_request = PlayRequest { | ||||
| @@ -337,7 +372,7 @@ impl<'a> PlayRequestBuilder<'a> { | ||||
|         Ok(play_request) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn submit(self) -> Result<(), RhaiClientError> { | ||||
|     pub async fn submit(self) -> Result<(), RhaiDispatcherError> { | ||||
|         // Build the request and submit using self.client
 | ||||
|         println!( | ||||
|             "Submitting request {} with timeout {:?}", | ||||
| @@ -347,7 +382,7 @@ impl<'a> PlayRequestBuilder<'a> { | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn await_response(self) -> Result<RhaiTaskDetails, RhaiClientError> { | ||||
|     pub async fn await_response(self) -> Result<RhaiTaskDetails, RhaiDispatcherError> { | ||||
|         // Build the request and submit using self.client
 | ||||
|         println!( | ||||
|             "Awaiting response for request {} with timeout {:?}", | ||||
| @@ -361,7 +396,7 @@ impl<'a> PlayRequestBuilder<'a> { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl RhaiClient { | ||||
| impl RhaiDispatcher { | ||||
|     pub fn new_play_request(&self) -> PlayRequestBuilder { | ||||
|         PlayRequestBuilder::new(self) | ||||
|     } | ||||
| @@ -371,7 +406,7 @@ impl RhaiClient { | ||||
|         &self, | ||||
|         conn: &mut redis::aio::MultiplexedConnection, | ||||
|         play_request: &PlayRequest, | ||||
|     ) -> Result<(), RhaiClientError> { | ||||
|     ) -> Result<(), RhaiDispatcherError> { | ||||
|         let now = Utc::now(); | ||||
| 
 | ||||
|         let task_key = format!("{}{}", NAMESPACE_PREFIX, play_request.id); | ||||
| @@ -422,7 +457,7 @@ impl RhaiClient { | ||||
|         task_key: &String, | ||||
|         reply_queue_key: &String, | ||||
|         timeout: Duration, | ||||
|     ) -> Result<RhaiTaskDetails, RhaiClientError> { | ||||
|     ) -> Result<RhaiTaskDetails, RhaiDispatcherError> { | ||||
|         // BLPOP on the reply queue
 | ||||
|         // The timeout for BLPOP is in seconds (integer)
 | ||||
|         let blpop_timeout_secs = timeout.as_secs().max(1); // Ensure at least 1 second for BLPOP timeout
 | ||||
| @@ -458,7 +493,7 @@ impl RhaiClient { | ||||
|                         ); | ||||
|                         // Optionally, delete the reply queue
 | ||||
|                         let _: redis::RedisResult<i32> = conn.del(&reply_queue_key).await; | ||||
|                         Err(RhaiClientError::SerializationError(e)) | ||||
|                         Err(RhaiDispatcherError::SerializationError(e)) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -470,7 +505,7 @@ impl RhaiClient { | ||||
|                 ); | ||||
|                 // Optionally, delete the reply queue
 | ||||
|                 let _: redis::RedisResult<i32> = conn.del(&reply_queue_key).await; | ||||
|                 Err(RhaiClientError::Timeout(task_key.clone())) | ||||
|                 Err(RhaiDispatcherError::Timeout(task_key.clone())) | ||||
|             } | ||||
|             Err(e) => { | ||||
|                 // Redis error
 | ||||
| @@ -480,7 +515,7 @@ impl RhaiClient { | ||||
|                 ); | ||||
|                 // Optionally, delete the reply queue
 | ||||
|                 let _: redis::RedisResult<i32> = conn.del(&reply_queue_key).await; | ||||
|                 Err(RhaiClientError::RedisError(e)) | ||||
|                 Err(RhaiDispatcherError::RedisError(e)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -489,7 +524,7 @@ impl RhaiClient { | ||||
|     pub async fn submit_play_request( | ||||
|         &self, | ||||
|         play_request: &PlayRequest, | ||||
|     ) -> Result<(), RhaiClientError> { | ||||
|     ) -> Result<(), RhaiDispatcherError> { | ||||
|         let mut conn = self.redis_client.get_multiplexed_async_connection().await?; | ||||
| 
 | ||||
|         self.submit_play_request_using_connection( | ||||
| @@ -504,7 +539,7 @@ impl RhaiClient { | ||||
|     pub async fn submit_play_request_and_await_result( | ||||
|         &self, | ||||
|         play_request: &PlayRequest, | ||||
|     ) -> Result<RhaiTaskDetails, RhaiClientError> { | ||||
|     ) -> Result<RhaiTaskDetails, RhaiDispatcherError> { | ||||
|         let mut conn = self.redis_client.get_multiplexed_async_connection().await?; | ||||
| 
 | ||||
|         let reply_queue_key = format!("{}:reply:{}", NAMESPACE_PREFIX, play_request.id); // Derived from the passed task_id
 | ||||
| @@ -535,7 +570,7 @@ impl RhaiClient { | ||||
|     pub async fn get_task_status( | ||||
|         &self, | ||||
|         task_id: &str, | ||||
|     ) -> Result<Option<RhaiTaskDetails>, RhaiClientError> { | ||||
|     ) -> Result<Option<RhaiTaskDetails>, RhaiDispatcherError> { | ||||
|         let mut conn = self.redis_client.get_multiplexed_async_connection().await?; | ||||
|         let task_key = format!("{}{}", NAMESPACE_PREFIX, task_id); | ||||
| 
 | ||||
| @@ -573,6 +608,7 @@ impl RhaiClient { | ||||
|                                         Utc::now() | ||||
|                                     }), | ||||
|                     caller_id: map.get("callerId").cloned().expect("callerId field missing from Redis hash"), | ||||
|                     worker_id: map.get("workerId").cloned().expect("workerId field missing from Redis hash"), | ||||
|                     context_id: map.get("contextId").cloned().expect("contextId field missing from Redis hash"), | ||||
|                 }; | ||||
|                 // It's important to also check if the 'taskId' field exists in the map and matches the input task_id
 | ||||
							
								
								
									
										
											BIN
										
									
								
								src/repl/.DS_Store
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/repl/.DS_Store
									
									
									
									
										vendored
									
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user