# Ideas for future enhancements Going to rename the project "baba yaga" -- file extension .baba or .txt ## io architecture ideas ### ..listen and ..emit for external process interface - ..listen: receives well-defined state object from JS harness - ..emit: sends state/commands back to JS harness - pattern similar to Elm's TEA (The Elm Architecture) ### js harness application: - holds the scripting language interpreter - manages state flow: input -> script -> output - provides well-known interface for data exchange - handles error recovery and safety ### safety considerations: - sandboxed execution environment - timeouts for script execution - memory limits - input validation/sanitization - error boundaries around script execution - fallback state if script fails ### error tolerance: - graceful degradation when scripts fail - default/fallback responses - retry mechanisms with backoff - circuit breaker pattern for repeated failures - logging and monitoring of script execution ### architectural patterns this resembles: - actor model (isolated state, message passing) - event sourcing (state changes as events) - command pattern (emit commands, not direct state mutations) - microservices communication patterns - reactive programming (data flow, state updates) ### js harness interface ideas: - onStateUpdate(callback) - register for state changes - sendState(state) - send state to script - onError(callback) - handle script errors - setConfig(options) - configure timeouts, limits, etc. ### example flow: 1. external system sends state to js harness 2. harness calls script with ..listen state 3. script processes state, emits new state/commands 4. harness receives emit, updates external system 5. cycle repeats ### questions: - should scripts be stateless or maintain internal state? - how to handle async operations in scripts? - what format for state objects? (json, structured data?) - how to version state schemas? - should emit be synchronous or allow batching? --- ## js harness pseudo code ### basic harness structure ```javascript class ScriptHarness { constructor(config) { this.interpreter = new ScriptInterpreter(); this.stateHistory = []; this.config = { timeout: 5000, memoryLimit: '10MB', maxRetries: 3, ...config }; } // main entry point async processState(newState) { try { // validate and version state const validatedState = this.validateState(newState); // add to history this.stateHistory.push({ version: validatedState.version, timestamp: Date.now(), data: validatedState }); // run script with state const result = await this.runScript(validatedState); // emit result to external system await this.emitResult(result); } catch (error) { await this.handleError(error); } } // run script with timeout and error handling async runScript(state) { return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Script execution timeout')); }, this.config.timeout); try { // translate JS state to script format const scriptState = this.translateToScript(state); // run script with ..listen and capture ..emit const result = this.interpreter.run(scriptState); clearTimeout(timeout); resolve(result); } catch (error) { clearTimeout(timeout); reject(error); } }); } // state translation layer translateToScript(jsState) { // convert JS objects to script tables // handle null/undefined // add version info // validate schema return { data: this.convertToTable(jsState), version: jsState.version || '1.0.0', timestamp: Date.now() }; } translateFromScript(scriptResult) { // convert script tables back to JS objects // validate output schema // handle errors return this.convertFromTable(scriptResult); } // state history management rewindToVersion(targetVersion) { // find state at target version // replay state changes up to that point // return state at that version } stepForward() { // move one state forward in history } stepBackward() { // move one state backward in history } // error handling async handleError(error) { // log error // apply fallback state // notify external system // implement circuit breaker if needed } } ``` ### external system integration ```javascript // example usage const harness = new ScriptHarness({ timeout: 3000, maxRetries: 2 }); // register callbacks harness.onStateUpdate((newState) => { // send to external system externalSystem.update(newState); }); harness.onError((error) => { // handle script errors console.error('Script error:', error); externalSystem.handleError(error); }); // process incoming state await harness.processState({ user: { name: "Alice", age: 30 }, action: "login", version: "1.0.0" }); ``` ### script execution flow ```javascript // script gets state via ..listen // script processes state // script emits result via ..emit // harness captures emit and translates back to JS // example script: /* current_state : ..listen; processed : when current_state.action is "login" then { user: current_state.user, status: "logged_in" } "logout" then { user: null, status: "logged_out" } _ then current_state; ..emit processed; */ ``` --- ## script design decisions ### stateless scripts (agreed - most functional approach) - scripts are pure functions: state in -> state out - no internal state, no side effects between calls - each ..listen call starts fresh - enables easy testing, debugging, replay - matches functional programming principles ### async operations ideas: - ..wait ms - pause script execution for milliseconds - ..promise value - create a promise-like construct - ..yield - yield control back to harness, resume later - ..spawn script - run another script asynchronously - ..join handle - wait for spawned script to complete - or: keep scripts synchronous, handle async in JS harness ### state format translation layer: - js objects -> script tables conversion - script tables -> js objects conversion - schema validation on both sides - type coercion (numbers, strings, booleans) - nested object/table translation - array/table translation (1-based indexing) - null/undefined handling ### known architectural approaches: - adapter pattern (translate between formats) - facade pattern (simplify complex interfaces) - data transfer objects (DTOs) - serialization/deserialization layers - schema-first design (define format first) ### schema versioning for state history: - version field in state objects - migration functions for old -> new schemas - state history as array of versioned states - rollback capability to previous versions - forward compatibility (new code handles old state) - backward compatibility (old code handles new state) ### versioning approaches: - semantic versioning (major.minor.patch) - timestamp-based versioning - hash-based versioning (content-addressable) - incremental versioning (v1, v2, v3) ### state history implementation: - append-only log of state changes - each state includes version and timestamp - rewind: replay state changes up to target version - step: move forward/backward one state at a time - snapshot: save current state for quick restore ### emit behavior: - synchronous by default (simpler to reason about) - single emit per script execution - multiple emits could be batched by harness - or: allow multiple emits, harness decides how to handle - error if script doesn't emit anything --- ## type checking ideas ### type checker functions - add to standard library: is_number, is_string, is_boolean, is_function, is_table, is_null, is_undefined - use with @ syntax in when expressions - no parser changes needed - composable with existing operators ### example ``` is_number : x -> equals(typeof x, "number"); classify : x -> when x is @is_number then "number" @is_string then "string" @is_table then "table" _ then "unknown"; ``` ### advantages: - uses existing features - composable (can combine with and/or) - extensible - consistent with functional patterns - immediate implementation possible ### advanced type checking ideas #### error type support - add is_error to standard library - error objects could have structure: { type: "error", message: "string", code: "number" } - or simpler: just check if object has error-like properties ```javascript // basic error checking using existing patterns is_error : x -> @is_table and not @equals(x.error, undefined); // more sophisticated error checking is_error : x -> @is_table and (@logicalOr (@not @equals(x.error, undefined)) (@logicalOr (@not @equals(x.message, undefined)) (@not @equals(x.code, undefined)) ) ); // alternative: use table access with error handling is_error : x -> @is_table and (@logicalOr (@not @equals(x["error"], undefined)) (@logicalOr (@not @equals(x["message"], undefined)) (@not @equals(x["code"], undefined)) ) ); // usage in when expressions handle_result : x -> when x is @is_error then "error occurred" @is_number then "success" _ then "unknown"; ``` #### tagged unions / discriminated unions - could represent different states: success/error, loading/loaded/error, etc. - structure: { tag: "success", data: value } or { tag: "error", error: message } ```javascript // type checkers for tagged unions has_tag : tag -> obj -> @is_table and equals(obj.tag, tag); is_success : x -> has_tag "success" x; is_error_result : x -> has_tag "error" x; // usage process_result : x -> when x is @is_success then x.data @is_error_result then "error: " + x.error _ then "unknown result"; ``` #### questions about error types: - do we need a special error type or just error-like objects? - should errors be first-class or just table properties? - how do errors propagate through function composition? - should we have error handling combinators (map_error, catch_error)? #### questions about tagged unions: - are they worth the complexity for this language? - do they add enough value over simple when expressions? - would they make scripts harder to read/write? - are they more useful in the JS harness than in scripts? #### simpler alternatives: - just use when expressions with property checking - error handling in JS harness, keep scripts simple - use standard library functions for common error patterns