diff options
Diffstat (limited to 'js/baba-yaga/docs/09_js-interop.md')
-rw-r--r-- | js/baba-yaga/docs/09_js-interop.md | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/js/baba-yaga/docs/09_js-interop.md b/js/baba-yaga/docs/09_js-interop.md new file mode 100644 index 0000000..28ec7bb --- /dev/null +++ b/js/baba-yaga/docs/09_js-interop.md @@ -0,0 +1,500 @@ +# JavaScript Interop + +This document covers Baba Yaga's JavaScript interoperability features, which allow safe and controlled access to JavaScript functionality while maintaining Baba Yaga's functional programming guarantees. + +## Table of Contents + +1. [Overview](#overview) +2. [Core Functions](#core-functions) +3. [Type Conversion](#type-conversion) +4. [Security Model](#security-model) +5. [Common Patterns](#common-patterns) +6. [Error Handling](#error-handling) +7. [Configuration](#configuration) +8. [Best Practices](#best-practices) +9. [Examples](#examples) + +## Overview + +Baba Yaga's JavaScript interop system provides a safe bridge between Baba Yaga's functional, immutable world and JavaScript's imperative, mutable one. All JavaScript operations return `Result` types to maintain explicit error handling. + +### Key Principles + +- **Safety First**: All JS operations are sandboxed and return `Result` types +- **Explicit Boundaries**: Clear separation between Baba Yaga and JavaScript +- **Type Safety**: Automatic conversion between type systems +- **Error Isolation**: JavaScript errors become Baba Yaga `Err` values + +## Core Functions + +All JavaScript interop functions are available in the `io.*` namespace. + +### Function Calls + +#### `io.callJS` +Call a JavaScript function synchronously. + +```baba +io.callJS : (functionName: String, args: [Any]) -> Result + +// Examples +absResult : io.callJS "Math.abs" [-42]; +// Returns: Ok (JSValue 42) + +parseResult : io.callJS "JSON.parse" ["{\"x\": 10}"]; +// Returns: Ok (JSValue {x: 10}) + +// Note: io.callJS returns a Result whose Ok value is a JSValue wrapper +// around the raw JavaScript value. You can pass this JSValue directly to +// io.getProperty, io.setProperty, io.hasProperty, io.jsArrayToList, +// io.objectToTable, etc. without manual unwrapping. +``` + +#### `io.callJSAsync` +Call a JavaScript function asynchronously (if async operations are enabled). + +```baba +io.callJSAsync : (functionName: String, args: [Any]) -> Result + +// Example (requires enableAsyncOps: true) +fetchResult : io.callJSAsync "fetch" ["https://api.example.com/data"]; +``` + +### Property Access + +#### `io.getProperty` +Get a property from a JavaScript object. + +```baba +io.getProperty : (obj: Any, propName: String) -> Result + +// Example +obj : io.callJS "JSON.parse" ["{\"name\": \"Alice\"}"]; +nameResult : when obj is + Ok parsed then io.getProperty parsed "name" + Err msg then Err msg; +// Returns: Ok "Alice" (direct Baba Yaga string) +``` + +#### `io.setProperty` +Set a property on a JavaScript object (mutates the object). + +```baba +io.setProperty : (obj: Any, propName: String, value: Any) -> Result + +// Example +obj : io.callJS "JSON.parse" ["{}"]; +result : when obj is + Ok parsed then io.setProperty parsed "newProp" 42 + Err msg then Err msg; +``` + +#### `io.hasProperty` +Check if a property exists on a JavaScript object. + +```baba +io.hasProperty : (obj: Any, propName: String) -> Bool + +// Example +obj : io.callJS "JSON.parse" ["{\"x\": 10}"]; +hasX : when obj is + Ok parsed then io.hasProperty parsed "x" + Err _ then false; +// Returns: true +``` + +### Type Conversion + +#### `io.jsArrayToList` +Convert a JavaScript array to a Baba Yaga list. + +```baba +io.jsArrayToList : (jsArray: Any) -> Result + +// Example +jsArray : io.callJS "JSON.parse" ["[1, 2, 3]"]; +listResult : when jsArray is + Ok arr then io.jsArrayToList arr + Err msg then Err msg; +// Returns: Ok [1, 2, 3] (direct Baba Yaga list) +``` + +#### `io.listToJSArray` +Convert a Baba Yaga list to a JavaScript array. + +```baba +io.listToJSArray : (list: [Any]) -> Any + +// Example +babaList : [1, 2, 3, 4, 5]; +jsArray : io.listToJSArray babaList; +jsonResult : io.callJS "JSON.stringify" [jsArray]; +// Returns: Ok (JSValue "[1,2,3,4,5]") +``` + +#### `io.objectToTable` +Convert a JavaScript object to a Baba Yaga table. + +```baba +io.objectToTable : (obj: Any) -> Result + +// Example +jsObj : io.callJS "JSON.parse" ["{\"name\": \"Bob\", \"age\": 25}"]; +tableResult : when jsObj is + Ok obj then io.objectToTable obj + Err msg then Err msg; +// Returns: Ok {name: "Bob", age: 25} (direct Baba Yaga table) +``` + +#### `io.tableToObject` +Convert a Baba Yaga table to a JavaScript object. + +```baba +io.tableToObject : (table: Table) -> Any + +// Example +babaTable : {x: 100, y: 200}; +jsObj : io.tableToObject babaTable; +jsonResult : io.callJS "JSON.stringify" [jsObj]; +// Returns: Ok (JSValue "{\"x\":100,\"y\":200}") +``` + +### Error Management + +#### `io.getLastJSError` +Get the last JavaScript error that occurred. + +Note: depending on language syntax rules for zero-argument functions, direct invocation may not be available in all contexts. Prefer handling errors from `io.callJS` directly via the returned `Result`. + +#### `io.clearJSError` +Clear the last JavaScript error. + +Note: same invocation caveat as above applies. + +## Type Conversion + +### Automatic Conversions + +The JavaScript bridge automatically converts between Baba Yaga and JavaScript types: + +| Baba Yaga Type | JavaScript Type | Notes | +|----------------|-----------------|-------------------------------------| +| `Number` | `number` | Preserves integer/float distinction | +| `String` | `string` | Direct conversion | +| `Bool` | `boolean` | Direct conversion | +| `List` | `Array` | Recursive conversion of elements | +| `Table` | `Object` | Converts Map to plain object | +| `Result` | N/A | Handled at boundary | + + +### Manual Conversions + +For more control, use explicit conversion functions: + +```baba +// Safe JSON parsing with error handling +parseJSON : jsonString -> + when (validate.type "String" jsonString) is + false then Err "Input must be a string" + true then when (io.callJS "JSON.parse" [jsonString]) is + Ok parsed then Ok (io.objectToTable parsed) + Err msg then Err ("JSON parse error: " .. msg); + +// Usage +result : parseJSON "{\"user\": \"Alice\", \"score\": 95}"; +``` + +## Security Model + +The JavaScript interop system uses a configurable security model: + +### Sandboxed Execution + +All JavaScript code runs in a controlled sandbox with: + +- **Limited Global Access**: Only allowed globals are available +- **Function Whitelist**: Only explicitly allowed functions can be called +- **Timeout Protection**: Operations have configurable time limits +- **Memory Limits**: Configurable memory usage constraints + +### Default Allowed Functions + +By default, these JavaScript functions are available: + +```javascript +// JSON operations +'JSON.parse', 'JSON.stringify', + +// Math operations +'Math.abs', 'Math.floor', 'Math.ceil', 'Math.round', +'Math.min', 'Math.max', 'Math.random', + +// Console operations +'console.log', 'console.warn', 'console.error', + +// Time operations +'Date.now', 'performance.now' +``` + +### Configuration + +Configure the JavaScript bridge through the host configuration: + +```javascript +const host = { + jsBridgeConfig: { + allowedFunctions: new Set(['Math.abs', 'JSON.parse']), + maxExecutionTime: 5000, // 5 seconds + enableAsyncOps: false, // Disable async operations + enableFileSystem: false, // Disable file system access + enableNetwork: false // Disable network access + } +}; +``` + +## Common Patterns + +### Safe JSON Operations + +```baba +// Safe JSON parsing +safeParseJSON : jsonStr -> + when (io.callJS "JSON.parse" [jsonStr]) is + Ok obj then when (io.objectToTable obj) is + Ok table then Ok table + Err msg then Err ("Conversion error: " .. msg) + Err msg then Err ("Parse error: " .. msg); + +// Safe JSON stringification +safeStringifyJSON : table -> + jsObj : io.tableToObject table; + io.callJS "JSON.stringify" [jsObj]; +``` + +### Mathematical Operations + +```baba +// Safe mathematical operations with validation +safeMath : operation args -> + when (validate.notEmpty args) is + false then Err "No arguments provided" + true then when operation is + "abs" then io.callJS "Math.abs" [head args] + "min" then io.callJS "Math.min" args + "max" then io.callJS "Math.max" args + "round" then io.callJS "Math.round" [head args] + _ then Err ("Unknown operation: " .. operation); + +// Usage +result : safeMath "abs" [-42]; // Ok 42 +minResult : safeMath "min" [10, 5, 8]; // Ok 5 +``` + +### Working with JavaScript APIs + +```baba +// Date operations +getCurrentTimestamp : () -> + io.callJS "Date.now" []; + +formatDate : timestamp -> + when (io.callJS "Date" [timestamp]) is + Ok dateObj then io.callJS "Date.prototype.toISOString" [dateObj] + Err msg then Err msg; + +// Performance monitoring +measurePerformance : operation -> + startTime : io.callJS "performance.now" []; + result : operation; + endTime : io.callJS "performance.now" []; + + duration : when (startTime, endTime) is + (Ok start, Ok end) then Ok (end - start) + _ then Err "Could not measure performance"; + + {result: result, duration: duration}; +``` + +## Error Handling + +### JavaScript Error Types + +JavaScript errors are automatically converted to Baba Yaga `Err` values: + +```baba +// This will return an Err +result : io.callJS "JSON.parse" ["invalid json"]; +// Returns: Err "Unexpected token i in JSON at position 0" + +// Handle different error types +handleJSError : result -> + when result is + Ok value then processValue value + Err msg then when (text.contains msg "JSON") is + true then handleJSONError msg + false then handleGenericError msg; +``` + +### Error Recovery Patterns + +```baba +// Retry pattern +retryOperation : operation maxAttempts -> + attempt : 1; + + tryOperation : currentAttempt -> + when (currentAttempt > maxAttempts) is + true then Err "Max attempts exceeded" + false then when (operation) is + Ok result then Ok result + Err _ then tryOperation (currentAttempt + 1); + + tryOperation attempt; + +// Fallback pattern +withFallback : primaryOp fallbackOp -> + when primaryOp is + Ok result then Ok result + Err _ then fallbackOp; +``` + +## Best Practices + +### 1. Always Use Result Types + +Never assume JavaScript operations will succeed: + +```baba +// Good +result : when (io.callJS "Math.abs" [value]) is + Ok abs then processValue abs + Err msg then handleError msg; + +// Bad - assumes success +abs : io.callJS "Math.abs" [value]; // This returns Result, not number +``` + +### 2. Validate Inputs + +Always validate data before sending to JavaScript: + +```baba +// Good +safeCall : value -> + when (validate.type "Number" value) is + false then Err "Value must be a number" + true then io.callJS "Math.abs" [value]; + +// Bad - no validation +unsafeCall : value -> + io.callJS "Math.abs" [value]; +``` + +### 3. Handle Type Conversions Explicitly + +Be explicit about type conversions: + +```baba +// Good +processJSData : jsData -> + when (io.objectToTable jsData) is + Ok table then processTable table + Err msg then Err ("Conversion failed: " .. msg); + +// Bad - assumes conversion works +processJSData : jsData -> + table : io.objectToTable jsData; + processTable table; +``` + +### 4. Use Composition for Complex Operations + +Break complex JavaScript interactions into smaller, composable functions: + +```baba +// Composed operations +parseAndValidate : jsonStr schema -> + parsed : safeParseJSON jsonStr; + when parsed is + Ok data then validateAgainstSchema data schema + Err msg then Err msg; + +transformAndStringify : data transformer -> + transformed : transformer data; + safeStringifyJSON transformed; +``` + +## Examples + +### Complete JSON Processing Pipeline + +```baba +// Complete JSON processing with error handling +processJSONData : jsonString -> + // Parse JSON + parseResult : io.callJS "JSON.parse" [jsonString]; + + when parseResult is + Err msg then Err ("Parse failed: " .. msg) + Ok jsObj then + // Convert to Baba Yaga table + when (io.objectToTable jsObj) is + Err msg then Err ("Conversion failed: " .. msg) + Ok table then + // Process the data + processedTable : processData table; + + // Convert back to JS object + jsResult : io.tableToObject processedTable; + + // Stringify result + io.callJS "JSON.stringify" [jsResult]; + +// Helper function +processData : table -> + // Add timestamp + withTimestamp : table .. {timestamp: getCurrentTimestamp}; + + // Validate required fields + when (hasRequiredFields withTimestamp) is + false then table .. {error: "Missing required fields"} + true then withTimestamp; + +// Usage +input : "{\"name\": \"Alice\", \"score\": 95}"; +result : processJSONData input; +// Returns: Ok (JSValue "{\"name\":\"Alice\",\"score\":95,\"timestamp\":1640995200000}") +``` + +### Working with JavaScript Arrays + +```baba +// Process JavaScript arrays +processJSArray : jsArrayString -> + // Parse array + arrayResult : io.callJS "JSON.parse" [jsArrayString]; + + when arrayResult is + Err msg then Err msg + Ok jsArray then + // Convert to Baba Yaga list + when (io.jsArrayToList jsArray) is + Err msg then Err msg + Ok babaList then + // Process with Baba Yaga functions + processed : map (x -> x * 2) babaList; + filtered : filter (x -> x > 10) processed; + + // Convert back to JS array + jsResult : io.listToJSArray filtered; + + // Return as JSON + io.callJS "JSON.stringify" [jsResult]; + +// Usage +input : "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]"; +result : processJSArray input; +// Returns: Ok (JSValue "[4,6,8,10,12,14,16,18,20]") +``` + +This JavaScript interop system provides a safe, controlled way to leverage JavaScript's ecosystem while maintaining Baba Yaga's functional programming principles and explicit error handling. |