implement osis actor

This commit is contained in:
Timur Gordon
2025-08-06 14:56:44 +02:00
parent 8f6ea5350f
commit 33dfc0dbe3
23 changed files with 5769 additions and 0 deletions

174
examples/actor.rs Normal file
View File

@@ -0,0 +1,174 @@
use std::fs;
use std::path::Path;
use std::time::Duration;
use tokio;
use tokio::sync::mpsc;
use tokio::time::{sleep, timeout};
use redis::AsyncCommands;
use actor_osis::spawn_osis_actor;
use hero_job::{Job, JobStatus, ScriptType};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize logging
env_logger::init();
println!("=== OSIS Actor Redis Dispatch Example ===");
// Find all Rhai scripts in examples/scripts directory
let scripts_dir = Path::new("examples/scripts");
if !scripts_dir.exists() {
eprintln!("Scripts directory not found: {}", scripts_dir.display());
return Ok(());
}
let mut script_files = Vec::new();
for entry in fs::read_dir(scripts_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("rhai") {
script_files.push(path);
}
}
script_files.sort();
println!("Found {} Rhai scripts in {}", script_files.len(), scripts_dir.display());
if script_files.is_empty() {
println!("No Rhai scripts found. Exiting.");
return Ok(());
}
// Create temporary database path
let db_path = "temp_osis_actor_example_db";
// Clean up previous database if it exists
if Path::new(db_path).exists() {
fs::remove_dir_all(db_path)?;
}
// Redis configuration
let redis_url = "redis://127.0.0.1:6379";
// Try to connect to Redis
let redis_client = redis::Client::open(redis_url)?;
let mut redis_conn = match redis_client.get_multiplexed_async_connection().await {
Ok(conn) => {
println!("Connected to Redis at {}", redis_url);
conn
}
Err(e) => {
println!("Failed to connect to Redis: {}", e);
println!("Please ensure Redis is running on localhost:6379");
println!("You can start Redis with: redis-server");
return Err(e.into());
}
};
// Create shutdown channel for the actor
let (shutdown_tx, shutdown_rx) = mpsc::channel(1);
// Spawn the OSIS actor
println!("\n--- Spawning OSIS Actor ---");
let actor_handle = spawn_osis_actor(
db_path.to_string(),
redis_url.to_string(),
shutdown_rx,
);
println!("OSIS actor spawned and listening for jobs");
// Process each script
for (i, script_path) in script_files.iter().enumerate() {
println!("\n=== Processing Script {}/{}: {} ===", i + 1, script_files.len(), script_path.file_name().unwrap().to_string_lossy());
let script_content = match fs::read_to_string(script_path) {
Ok(content) => content,
Err(e) => {
println!("Failed to read script {}: {}", script_path.display(), e);
continue;
}
};
// Create a job for this script
let job = Job::new(
"example_caller".to_string(),
format!("script_{}", script_path.file_stem().unwrap().to_string_lossy()),
script_content,
ScriptType::OSIS,
);
println!("Created job with ID: {}", job.id);
// Store the job in Redis
let job_key = format!("job:{}", job.id);
job.store_in_redis(&mut redis_conn).await?;
// Set initial status to Dispatched (since store_in_redis sets it to "pending" which isn't in the enum)
Job::update_status(&mut redis_conn, &job.id, JobStatus::Dispatched).await?;
println!("Stored job in Redis with key: {} and status: Dispatched", job_key);
// Add the job to the OSIS queue for processing
// Note: The supervisor uses "actor_queue:" prefix, so the correct queue is:
let queue_key = "hero:job:actor_queue:osis";
let _: () = redis_conn.lpush(&queue_key, &job.id).await?;
println!("Dispatched job {} to OSIS queue: {}", job.id, queue_key);
println!("\n--- Waiting for Job Result ---");
// Wait for result or error from Redis queues with timeout
let result_key = format!("hero:job:{}:result", job.id);
let error_key = format!("hero:job:{}:error", job.id);
let timeout_secs = 10.0;
// Use BLPOP to block and wait for either result or error
let keys = vec![result_key.clone(), error_key.clone()];
match redis_conn.blpop::<_, Option<(String, String)>>(&keys, timeout_secs).await {
Ok(Some((queue_name, value))) => {
if queue_name == result_key {
println!("✓ Job completed successfully!");
println!(" Result: {}", value);
} else if queue_name == error_key {
println!("✗ Job failed with error!");
println!(" Error: {}", value);
}
}
Ok(None) => {
println!("⏱ Job timed out after {} seconds", timeout_secs);
}
Err(e) => {
println!("❌ Failed to wait for job result: {}", e);
}
}
}
// Shutdown the actor
println!("\n--- Shutting Down Actor ---");
if let Err(e) = shutdown_tx.send(()).await {
println!("Failed to send shutdown signal: {}", e);
}
// Wait for actor to shutdown with timeout
match timeout(Duration::from_secs(5), actor_handle).await {
Ok(result) => {
match result {
Ok(Ok(())) => println!("OSIS actor shut down successfully"),
Ok(Err(e)) => println!("OSIS actor shut down with error: {}", e),
Err(e) => println!("OSIS actor panicked: {}", e),
}
}
Err(_) => {
println!("OSIS actor shutdown timed out");
}
}
// Clean up the temporary database
if Path::new(db_path).exists() {
fs::remove_dir_all(db_path)?;
println!("Cleaned up temporary database: {}", db_path);
}
println!("=== Actor Example Complete ===");
Ok(())
}

174
examples/engine.rs Normal file
View File

@@ -0,0 +1,174 @@
use std::env;
use std::fs;
use std::panic;
use std::path::Path;
use rhai::{Engine, Dynamic};
use actor_osis::OSISActor;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse command line arguments for verbosity
let args: Vec<String> = env::args().collect();
let verbose = args.contains(&"--verbose".to_string()) || args.contains(&"-v".to_string());
// Set up custom panic hook to suppress panic messages unless verbose
if !verbose {
panic::set_hook(Box::new(|_| {
// Suppress panic output in non-verbose mode
}));
}
// Initialize logging only if verbose
if verbose {
env_logger::init();
}
println!("=== OSIS Engine Direct Execution Example ===");
// Find all Rhai scripts in examples/scripts directory
let scripts_dir = Path::new("examples/scripts");
if !scripts_dir.exists() {
eprintln!("Scripts directory not found: {}", scripts_dir.display());
return Ok(());
}
let mut script_files = Vec::new();
for entry in fs::read_dir(scripts_dir)? {
let entry = entry?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) == Some("rhai") {
script_files.push(path);
}
}
script_files.sort();
if verbose {
println!("Found {} Rhai scripts in {}", script_files.len(), scripts_dir.display());
} else {
println!("Testing {} Rhai scripts:\n", script_files.len());
}
// Create temporary database path
let db_path = "temp_osis_engine_example_db";
// Clean up previous database if it exists
if Path::new(db_path).exists() {
fs::remove_dir_all(db_path)?;
}
if verbose {
println!("Created temporary database path: {}", db_path);
}
// Track results for summary
let mut success_count = 0;
let mut failure_count = 0;
// Execute all scripts with colored output
for (i, script_path) in script_files.iter().enumerate() {
let script_name = script_path.file_name().unwrap().to_string_lossy();
// Read script content
let script_content = match fs::read_to_string(script_path) {
Ok(content) => content,
Err(e) => {
println!("\x1b[31m✗\x1b[0m {} ... \x1b[31mFAILED\x1b[0m (read error: {})", script_name, e);
failure_count += 1;
continue;
}
};
if verbose {
println!("\n=== Script {}/{}: {} ===", i + 1, script_files.len(), script_name);
println!("--- Using Fresh OSIS Engine with Job Context ---");
}
// Create a new engine instance and configure it with DSL modules
let mut engine_with_context = match create_configured_engine(db_path, i + 1, verbose) {
Ok(engine) => engine,
Err(e) => {
println!("\x1b[31m✗\x1b[0m {} ... \x1b[31mFAILED\x1b[0m (engine setup: {})", script_name, e);
failure_count += 1;
continue;
}
};
// Execute the script with graceful error handling (catches both errors and panics)
let script_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
engine_with_context.eval::<rhai::Dynamic>(&script_content)
}));
match script_result {
Ok(Ok(result)) => {
println!("\x1b[32m✓\x1b[0m {} ... \x1b[32mSUCCESS\x1b[0m", script_name);
if verbose {
println!(" Result: {:?}", result);
}
success_count += 1;
}
Ok(Err(e)) => {
println!("\x1b[31m✗\x1b[0m {} ... \x1b[31mFAILED\x1b[0m", script_name);
if verbose {
println!(" Error: {}", e);
}
failure_count += 1;
}
Err(panic_err) => {
let panic_msg = if let Some(s) = panic_err.downcast_ref::<String>() {
s.clone()
} else if let Some(s) = panic_err.downcast_ref::<&str>() {
s.to_string()
} else {
"Unknown panic".to_string()
};
println!("\x1b[31m✗\x1b[0m {} ... \x1b[31mFAILED\x1b[0m", script_name);
if verbose {
println!(" Panic: {}", panic_msg);
}
failure_count += 1;
}
}
}
// Print summary
println!("\n=== Summary ===");
println!("\x1b[32m✓ {} scripts succeeded\x1b[0m", success_count);
println!("\x1b[31m✗ {} scripts failed\x1b[0m", failure_count);
println!("Total: {} scripts", success_count + failure_count);
// Clean up the temporary database
if Path::new(db_path).exists() {
fs::remove_dir_all(db_path)?;
if verbose {
println!("\nCleaned up temporary database: {}", db_path);
}
}
if verbose {
println!("=== Engine Example Complete ===");
}
Ok(())
}
/// Create a configured Rhai engine with DSL modules and job context
fn create_configured_engine(db_path: &str, script_index: usize, verbose: bool) -> Result<Engine, String> {
// Create a new engine instance
let mut engine = Engine::new();
// Register all DSL modules (same as OSIS engine configuration)
actor_osis::register_dsl_modules(&mut engine);
// Set up job context tags (similar to execute_job_with_engine)
let mut db_config = rhai::Map::new();
db_config.insert("DB_PATH".into(), db_path.to_string().into());
db_config.insert("CALLER_ID".into(), "engine_example".to_string().into());
db_config.insert("CONTEXT_ID".into(), format!("script_{}", script_index).into());
engine.set_default_tag(Dynamic::from(db_config));
if verbose {
println!(" Set job context: DB_PATH={}, CALLER_ID=engine_example, CONTEXT_ID=script_{}", db_path, script_index);
}
Ok(engine)
}

View File

@@ -0,0 +1,15 @@
// heromodels/examples/access/access.rhai
print("--- Testing Access Rhai Module ---");
// --- Image ---
print("\n1. Creating and saving an access...");
let new_access = new_access()
.object_id(1)
.circle_public_key("some_pk")
.group_id(1)
.contact_id(1)
.expires_at(1735689600) // Future timestamp
.save_access();
print("Access saved with ID: " + new_access.id);

View File

@@ -0,0 +1,8 @@
// biz.rhai
// Example of using the company module
let new_company = new_company()
.name("HeroCode Inc.")
.save_company();
print(new_company.name);

View File

@@ -0,0 +1,16 @@
// calendar.rhai
let new_event = new_event()
.title("Team Meeting")
.description("Weekly sync-up")
.location("Virtual")
.add_attendee(new_attendee(1).status("Accepted"))
.reschedule(1672531200, 1672534800) // Example timestamps
.save_event();
let new_calendar = new_calendar("Work Calendar")
.description("Calendar for all work-related events")
.save_calendar();
print(new_calendar);
print(new_event);

View File

@@ -0,0 +1,13 @@
// circle.rhai
let new_circle = new_circle()
.title("HeroCode Community")
.ws_url("ws://herocode.com/community")
.description("A circle for HeroCode developers.")
.logo("logo.png")
.theme_property("primaryColor", "#FF0000")
.add_circle("General")
.add_member("user123")
.save_circle();
print(new_circle);

View File

@@ -0,0 +1,18 @@
// company.rhai
let new_company = new_company()
.name("HeroCode Solutions")
.registration_number("HC12345")
.incorporation_date(1609459200)
.fiscal_year_end("31-12")
.email("contact@herocode.com")
.phone("123-456-7890")
.website("www.herocode.com")
.address("123 Hero Way, Codeville")
.business_type("Global")
.industry("Software Development")
.description("Providing heroic coding solutions.")
.status("Active")
.save_company();
print(new_company);

View File

@@ -0,0 +1,21 @@
// contact.rhai
let new_contact = new_contact()
.name("John Doe")
.description("A test contact")
.address("123 Main St")
.phone("555-1234")
.email("john.doe@example.com")
.notes("This is a note.")
.circle("friends")
.save_contact();
print(new_contact);
let new_group = new_group()
.name("Test Group")
.description("A group for testing")
.add_contact(1)
.save_group();
print(new_group);

View File

@@ -0,0 +1,9 @@
// core.rhai
let new_comment = new_comment()
.user_id(1)
.content("This is a test comment.")
.parent_comment_id(0)
.save_comment();
print(new_comment);

View File

@@ -0,0 +1,58 @@
// finance.rhai
// Account
let new_account = new_account()
.name("My Test Account")
.user_id(1)
.description("A test account for finance.")
.ledger("main")
.address("0x123...")
.pubkey("0x456...")
.add_asset(1)
.save_account();
print("New Account:");
print(new_account);
// Asset
let new_asset = new_asset()
.name("HeroCoin")
.description("The official coin of HeroCode.")
.amount(1000.0)
.address("0xabc...")
.decimals(18)
.asset_type("erc20")
.save_asset();
print("\nNew Asset:");
print(new_asset);
// Listing
let new_listing = new_listing()
.title("100 HeroCoins for sale")
.description("Get your HeroCoins now!")
.asset_id("1")
.seller_id("1")
.price(1.5)
.currency("USD")
.asset_type("erc20")
.listing_type("FixedPrice")
.status("Active")
.expires_at(1735689600) // Some future timestamp
.image_url("http://example.com/herocoin.png")
.add_tag("crypto")
.save_listing();
print("\nNew Listing:");
print(new_listing);
// Bid
let new_bid = new_bid()
.listing_id("1")
.bidder_id(2)
.amount(1.6)
.currency("USD")
.status("Active");
print("\nNew Bid:");
print(new_bid);

View File

@@ -0,0 +1,32 @@
// flow.rhai
// Create a signature requirement
let sig_req = new_signature_requirement()
.flow_step_id(1)
.public_key("0xABCDEF1234567890")
.message("Please sign to approve this step.")
.status("Pending")
.save_signature_requirement();
print("New Signature Requirement:");
print(sig_req);
// Create a flow step
let step1 = new_flow_step()
.description("Initial approval by manager")
.step_order(1)
.status("Pending")
.save_flow_step();
print("\nNew Flow Step:");
print(step1);
// Create a flow and add the step
let my_flow = new_flow("purchase-request-flow-123")
.name("Purchase Request Approval Flow")
.status("Active")
.add_step(step1)
.save_flow();
print("\nNew Flow:");
print(my_flow);

View File

@@ -0,0 +1,12 @@
// object.rhai
// Assuming a builder function `object__builder` exists based on the project's pattern.
let new_object = object__builder(1)
.name("My Dynamic Object")
.description("An example of a generic object.")
.set_property("custom_field", "custom_value")
.build()
.save_object();
print("New Object:");
print(new_object);

View File

@@ -0,0 +1,190 @@
// ===== Stripe Payment Integration Example =====
// This script demonstrates the complete payment workflow using Stripe
print("🔧 Configuring Stripe...");
// Configure Stripe with API key from environment variables
// The STRIPE_API_KEY is loaded from .env file by main.rs
let config_result = configure_stripe(STRIPE_API_KEY);
print(`Configuration result: ${config_result}`);
print("\n📦 Creating a Product...");
// Create a new product using builder pattern
let product = new_product()
.name("Premium Software License")
.description("A comprehensive software solution for businesses")
.metadata("category", "software")
.metadata("tier", "premium");
print(`Product created: ${product.name}`);
// Create the product in Stripe (non-blocking)
print("🔄 Dispatching product creation to Stripe...");
try {
let product_result = product.create_async("payment-example", "payment-context", STRIPE_API_KEY);
print(`✅ Product creation dispatched: ${product_result}`);
} catch(error) {
print(`❌ Failed to dispatch product creation: ${error}`);
print("This is expected with a demo API key. In production, use a valid Stripe secret key.");
let product_id = "prod_demo_example_id"; // Continue with demo ID
}
print("\n💰 Creating Prices...");
// Create upfront price (one-time payment)
let upfront_price = new_price()
.amount(19999) // $199.99 in cents
.currency("usd")
.product(product_id)
.metadata("type", "upfront");
let upfront_result = upfront_price.create_async("payment-example", "payment-context", STRIPE_API_KEY);
print(`✅ Upfront Price creation dispatched: ${upfront_result}`);
let upfront_price_id = "price_demo_upfront_id";
// Create monthly subscription price
let monthly_price = new_price()
.amount(2999) // $29.99 in cents
.currency("usd")
.product(product_id)
.recurring("month")
.metadata("type", "monthly_subscription");
let monthly_result = monthly_price.create_async("payment-example", "payment-context", STRIPE_API_KEY);
print(`✅ Monthly Price creation dispatched: ${monthly_result}`);
let monthly_price_id = "price_demo_monthly_id";
// Create annual subscription price with discount
let annual_price = new_price()
.amount(29999) // $299.99 in cents (2 months free)
.currency("usd")
.product(product_id)
.recurring("year")
.metadata("type", "annual_subscription")
.metadata("discount", "2_months_free");
let annual_result = annual_price.create_async("payment-example", "payment-context", STRIPE_API_KEY);
print(`✅ Annual Price creation dispatched: ${annual_result}`);
let annual_price_id = "price_demo_annual_id";
print("\n🎟 Creating Discount Coupons...");
// Create a percentage-based coupon
let percent_coupon = new_coupon()
.duration("once")
.percent_off(25)
.metadata("campaign", "new_customer_discount")
.metadata("code", "WELCOME25");
let percent_result = percent_coupon.create_async("payment-example", "payment-context", STRIPE_API_KEY);
print(`✅ 25% Off Coupon creation dispatched: ${percent_result}`);
let percent_coupon_id = "coupon_demo_25percent_id";
// Create a fixed amount coupon
let amount_coupon = new_coupon()
.duration("repeating")
.duration_in_months(3)
.amount_off(500, "usd") // $5.00 off
.metadata("campaign", "loyalty_program")
.metadata("code", "LOYAL5");
let amount_result = amount_coupon.create_async("payment-example", "payment-context", STRIPE_API_KEY);
print(`✅ $5 Off Coupon creation dispatched: ${amount_result}`);
let amount_coupon_id = "coupon_demo_5dollar_id";
print("\n💳 Creating Payment Intent for Upfront Payment...");
// Create a payment intent for one-time payment
let payment_intent = new_payment_intent()
.amount(19999)
.currency("usd")
.customer("cus_example_customer_id")
.description("Premium Software License - One-time Payment")
.add_payment_method_type("card")
.add_payment_method_type("us_bank_account")
.metadata("product_id", product_id)
.metadata("price_id", upfront_price_id)
.metadata("payment_type", "upfront");
let payment_result = payment_intent.create_async("payment-example", "payment-context", STRIPE_API_KEY);
print(`✅ Payment Intent creation dispatched: ${payment_result}`);
let payment_intent_id = "pi_demo_payment_intent_id";
print("\n🔄 Creating Subscription...");
// Create a subscription for monthly billing
let subscription = new_subscription()
.customer("cus_example_customer_id")
.add_price(monthly_price_id)
.trial_days(14) // 14-day free trial
.coupon(percent_coupon_id) // Apply 25% discount
.metadata("plan", "monthly")
.metadata("trial", "14_days")
.metadata("source", "website_signup");
let subscription_result = subscription.create_async("payment-example", "payment-context", STRIPE_API_KEY);
print(`✅ Subscription creation dispatched: ${subscription_result}`);
let subscription_id = "sub_demo_subscription_id";
print("\n🎯 Creating Multi-Item Subscription...");
// Create a subscription with multiple items
let multi_subscription = new_subscription()
.customer("cus_example_enterprise_customer")
.add_price_with_quantity(monthly_price_id, 5) // 5 licenses
.add_price("price_addon_support_monthly") // Support addon
.trial_days(30) // 30-day trial for enterprise
.metadata("plan", "enterprise")
.metadata("licenses", "5")
.metadata("addons", "premium_support");
let multi_result = multi_subscription.create_async("payment-example", "payment-context", STRIPE_API_KEY);
print(`✅ Multi-Item Subscription creation dispatched: ${multi_result}`);
let multi_subscription_id = "sub_demo_multi_subscription_id";
print("\n💰 Creating Payment Intent with Coupon...");
// Create another payment intent with discount applied
let discounted_payment = new_payment_intent()
.amount(14999) // Discounted amount after coupon
.currency("usd")
.customer("cus_example_customer_2")
.description("Premium Software License - With 25% Discount")
.metadata("original_amount", "19999")
.metadata("coupon_applied", percent_coupon_id)
.metadata("discount_percent", "25");
let discounted_result = discounted_payment.create_async("payment-example", "payment-context", STRIPE_API_KEY);
print(`✅ Discounted Payment Intent creation dispatched: ${discounted_result}`);
let discounted_payment_id = "pi_demo_discounted_payment_id";
print("\n📊 Summary of Created Items:");
print("================================");
print(`Product ID: ${product_id}`);
print(`Upfront Price ID: ${upfront_price_id}`);
print(`Monthly Price ID: ${monthly_price_id}`);
print(`Annual Price ID: ${annual_price_id}`);
print(`25% Coupon ID: ${percent_coupon_id}`);
print(`$5 Coupon ID: ${amount_coupon_id}`);
print(`Payment Intent ID: ${payment_intent_id}`);
print(`Subscription ID: ${subscription_id}`);
print(`Multi-Subscription ID: ${multi_subscription_id}`);
print(`Discounted Payment ID: ${discounted_payment_id}`);
print("\n🎉 Payment workflow demonstration completed!");
print("All Stripe object creation requests have been dispatched using the non-blocking pattern.");
print("💡 In non-blocking mode:");
print(" ✓ Functions return immediately with dispatch confirmations");
print(" ✓ HTTP requests happen in background using tokio::spawn");
print(" ✓ Results are handled by response/error scripts via RhaiDispatcher");
print(" ✓ No thread blocking or waiting for API responses");
// Example of accessing object properties
print("\n🔍 Accessing Object Properties:");
print(`Product Name: ${product.name}`);
print(`Product Description: ${product.description}`);
print(`Upfront Price Amount: $${upfront_price.amount / 100}`);
print(`Monthly Price Currency: ${monthly_price.currency}`);
print(`Subscription Customer: ${subscription.customer}`);
print(`Payment Intent Amount: $${payment_intent.amount / 100}`);
print(`Percent Coupon Duration: ${percent_coupon.duration}`);
print(`Percent Coupon Discount: ${percent_coupon.percent_off}%`);

View File

@@ -0,0 +1,27 @@
// product.rhai
// Create a product component
let component = new_product_component()
.name("Sub-component A")
.description("A vital part of the main product.")
.quantity(10);
print("New Product Component:");
print(component);
// Create a product and add the component
let new_product = new_product()
.name("Super Product")
.description("A product that does amazing things.")
.price(99.99)
.type("Product")
.category("Gadgets")
.status("Available")
.max_amount(100)
.purchase_till(1735689600) // Future timestamp
.active_till(1735689600) // Future timestamp
.add_component(component)
.save_product();
print("\nNew Product:");
print(new_product);

View File

@@ -0,0 +1,28 @@
// sale.rhai
// Create a sale item
let sale_item = new_sale_item()
.product_id(1)
.name("Super Product")
.quantity(2)
.unit_price(99.99)
.subtotal(199.98)
.service_active_until(1735689600); // Future timestamp
print("New Sale Item:");
print(sale_item);
// Create a sale and add the item
let new_sale = new_sale()
.company_id(1)
.buyer_id(2)
.transaction_id(12345)
.total_amount(199.98)
.status("Completed")
.sale_date(1672531200) // Past timestamp
.add_item(sale_item)
.notes("This is a test sale.")
.save_sale();
print("\nNew Sale:");
print(new_sale);

View File

@@ -0,0 +1,15 @@
// shareholder.rhai
// Create a shareholder
let new_shareholder = new_shareholder()
.company_id(1)
.user_id(1)
.name("John Doe")
.shares(1000.0)
.percentage(10.0)
.type("Individual")
.since(1640995200) // Timestamp for Jan 1, 2022
.save_shareholder();
print("New Shareholder:");
print(new_shareholder);