UNPKG

synthase

Version:

A secure, sandboxed, and extensible JavaScript execution engine built with TypeScript.

1,584 lines (1,578 loc) 52.4 kB
import { ParameterUtils } from "./chunk-WVGFA7XL.js"; // src/execution-limits.ts var ExecutionLimits = class { // 100MB memory limit constructor(limits) { this.timeout = 3e4; // 30 seconds max execution this.maxRecursionDepth = 10; // Max import recursion depth this.maxImportedScripts = 50; // Max total imported scripts per execution this.maxMemory = 100 * 1024 * 1024; if (limits) { Object.assign(this, limits); } } /** * Execute a function with timeout protection */ async executeWithTimeout(fn, timeoutMs = this.timeout) { let timeoutId; const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error(`Script execution timeout after ${timeoutMs}ms`)); }, timeoutMs); }); timeoutPromise._timeoutId = timeoutId; try { const result = await Promise.race([fn(), timeoutPromise]); clearTimeout(timeoutId); return result; } catch (error) { clearTimeout(timeoutId); if (error.message.toLowerCase().includes("timeout")) { throw error; } throw error; } } /** * Check if recursion depth is within limits */ checkRecursionDepth(currentDepth) { if (currentDepth >= this.maxRecursionDepth) { throw new Error( `Recursion depth limit exceeded: ${currentDepth} >= ${this.maxRecursionDepth}. This may indicate circular dependencies or excessive nesting.` ); } } /** * Check if import count is within limits */ checkImportCount(currentCount) { if (currentCount >= this.maxImportedScripts) { throw new Error( `Import limit exceeded: ${currentCount} >= ${this.maxImportedScripts}. This may indicate an import bomb or inefficient script design.` ); } } /** * Update limits configuration */ updateLimits(newLimits) { Object.assign(this, newLimits); } }; // src/script-validator.ts var ScriptValidator = class { constructor() { this.dangerousPatterns = [ // Infinite loops { pattern: /while\s*\(\s*true\s*\)/, message: "Potential infinite while loop detected" }, { pattern: /for\s*\(\s*;\s*;\s*\)/, message: "Potential infinite for loop detected" }, { pattern: /for\s*\(\s*;[^;]*;\s*\)/, message: "Potential infinite for loop (no increment) detected" }, // Dangerous globals access { pattern: /eval\s*\(/, message: "Use of eval() is prohibited" }, { pattern: /Function\s*\(/, message: "Use of Function constructor is prohibited" }, { pattern: /setTimeout\s*\([^,]*,\s*0\s*\)/, message: "Zero-delay setTimeout may cause performance issues" }, { pattern: /setInterval\s*\(/, message: "Use of setInterval is discouraged" }, // File system access (in browser context) { pattern: /require\s*\(\s*['"]fs['"]/, message: "File system access is not allowed" }, { pattern: /import.*['"]fs['"]/, message: "File system access is not allowed" }, // Network access patterns that might be suspicious { pattern: /fetch\s*\([^)]*document\.location/, message: "Fetching from document.location may be suspicious" }, { pattern: /XMLHttpRequest/, message: "Direct XMLHttpRequest usage is discouraged - use fetch instead" }, // Prototype pollution attempts { pattern: /__proto__/, message: "Prototype manipulation is prohibited" }, { pattern: /constructor\.prototype/, message: "Prototype manipulation is prohibited" }, // Add missing Object.prototype pattern { pattern: /Object\.prototype/, message: "Prototype manipulation is prohibited" }, // Very large loops (potential DoS) { pattern: /for\s*\([^)]*[0-9]{6,}/, message: "Very large loop detected - potential DoS" }, { pattern: /while\s*\([^)]*[0-9]{6,}/, message: "Very large loop detected - potential DoS" } ]; this.requiredPatterns = [ { pattern: /export\s+const\s+io\s*=/, message: "Missing required 'export const io = ...' declaration" }, { pattern: /export\s+default/, message: "Missing required 'export default function' declaration" } ]; } /** * Remove comments from code */ stripComments(content) { let result = ""; let inString = false; let stringChar = ""; let inSingleLineComment = false; let inMultiLineComment = false; let escapeNext = false; for (let i = 0; i < content.length; i++) { const char = content[i]; const nextChar = content[i + 1]; if (escapeNext) { escapeNext = false; } else if (char === "\\") { escapeNext = true; } else if (!inSingleLineComment && !inMultiLineComment) { if (char === '"' || char === "'" || char === "`") { if (!inString) { inString = true; stringChar = char; } else if (char === stringChar) { inString = false; stringChar = ""; } } } if (!inString && !escapeNext) { if (char === "/" && nextChar === "/" && !inMultiLineComment) { inSingleLineComment = true; i++; continue; } if (char === "/" && nextChar === "*" && !inSingleLineComment) { inMultiLineComment = true; i++; continue; } } if (inMultiLineComment && char === "*" && nextChar === "/") { inMultiLineComment = false; i++; continue; } if (inSingleLineComment && char === "\n") { inSingleLineComment = false; } if (!inSingleLineComment && !inMultiLineComment) { result += char; } else if (char === "\n") { result += char; } } return result; } /** * Replace string content with spaces to maintain structure */ maskStrings(content) { let result = ""; let inString = false; let stringChar = ""; let escapeNext = false; for (let i = 0; i < content.length; i++) { const char = content[i]; if (escapeNext) { result += inString ? " " : char; escapeNext = false; continue; } if (char === "\\") { escapeNext = true; result += inString ? " " : char; continue; } if ((char === '"' || char === "'" || char === "`") && !inString) { inString = true; stringChar = char; result += char; } else if (inString && char === stringChar) { inString = false; stringChar = ""; result += char; } else if (inString) { result += " "; } else { result += char; } } return result; } /** * Validate script content */ validateScript(content) { const errors = []; const warnings = []; const strippedContent = this.stripComments(content); const maskedContent = this.maskStrings(strippedContent); for (const required of this.requiredPatterns) { if (!required.pattern.test(strippedContent)) { errors.push(required.message); } } for (const danger of this.dangerousPatterns) { if (danger.pattern.test(maskedContent)) { errors.push(danger.message); } } this.validateStructure(content, errors, warnings); this.validateIOSchema(strippedContent, errors, warnings); return { valid: errors.length === 0, errors: [...errors], warnings: [...warnings] }; } /** * Validate script structure */ validateStructure(content, errors, warnings) { if (!this.hasMatchedQuotes(content)) { errors.push("Unmatched quotes detected"); return; } if (!content.includes("export")) { errors.push("No export statements found - scripts must be ES6 modules"); return; } const braceBalance = this.checkBraceBalance(content); if (braceBalance !== 0) { errors.push("Unmatched braces detected"); } const lines = content.split("\n"); for (let i = 0; i < lines.length; i++) { if (lines[i].length > 1e3) { if (lines[i].includes("options:") && lines[i].includes("[") && lines[i].includes("]")) { continue; } if (lines[i].includes("accept:") && lines[i].includes("'")) { continue; } warnings.push( `Very long line detected at line ${i + 1} - possible minified code` ); break; } } const maxNesting = this.getMaxNestingLevel(content); if (maxNesting > 10) { warnings.push( `High nesting level (${maxNesting}) detected - consider refactoring` ); } if (content.length > 1e5) { warnings.push( "Script is very large - consider breaking into smaller modules" ); } } /** * Check if quotes are properly matched in code */ hasMatchedQuotes(content) { let inString = false; let stringChar = ""; let inComment = false; for (let i = 0; i < content.length; i++) { const ch = content[i]; const nxt = content[i + 1]; if (!inString) { if (!inComment && ch === "/" && nxt === "/") { inComment = true; i++; continue; } if (!inComment && ch === "/" && nxt === "*") { inComment = true; i++; continue; } if (inComment && ch === "\n") { inComment = false; continue; } if (inComment && ch === "*" && nxt === "/") { inComment = false; i++; continue; } } if (inComment) continue; if (!inString && (ch === '"' || ch === "'" || ch === "`")) { inString = true; stringChar = ch; continue; } if (inString && ch === stringChar && content[i - 1] !== "\\") { inString = false; stringChar = ""; continue; } } return !inString && !inComment; } /** * Validate IO schema structure */ validateIOSchema(content, errors, warnings) { try { const stringIoMatch = content.match( /export\s+const\s+io\s*=\s*["'`][^"'`]*["'`]/ ); if (stringIoMatch) { errors.push("IO schema must be an object"); return; } const ioMatch = content.match( /export\s+const\s+io\s*=\s*(\{[\s\S]*?\});/ ); if (!ioMatch) { const altMatch = content.match( /export\s+const\s+io\s*=\s*(\{[\s\S]*?\})/ ); if (!altMatch) return; } const ioText = ioMatch ? ioMatch[1] : content.match(/export\s+const\s+io\s*=\s*(\{[\s\S]*?\})/)?.[1]; if (!ioText) return; const ioSchema = (0, eval)(`(${ioText})`); if (typeof ioSchema !== "object" || ioSchema === null) { errors.push("IO schema must be an object"); return; } if (!ioSchema.inputs || typeof ioSchema.inputs !== "object") { errors.push("IO schema must have an 'inputs' object"); } if (!ioSchema.outputs || typeof ioSchema.outputs !== "object") { errors.push("IO schema must have an 'outputs' object"); } if (ioSchema.inputs) { this.validateParameterDefinitions( ioSchema.inputs, "inputs", errors, warnings ); } if (ioSchema.outputs) { this.validateParameterDefinitions( ioSchema.outputs, "outputs", errors, warnings ); } } catch (ioError) { errors.push(`Invalid IO schema: ${ioError.message}`); } } /** * Validate parameter definitions in IO schema */ validateParameterDefinitions(params, section, errors, warnings) { const validTypes = [ "int", "integer", // Allow both int and integer "float", "number", // Allow both float and number "string", "text", // Allow both string and text "boolean", "bool", // Allow both boolean and bool "object", "array", "file", // NEW: File input support "BlockId" // Keep existing custom type ]; for (const [key, param] of Object.entries(params)) { if (typeof param === "string") { if (!validTypes.includes(param)) { errors.push( `Invalid parameter type '${param}' for ${section}.${key}` ); } } else if (typeof param === "object" && param !== null) { const paramObj = param; if (!paramObj.type || !validTypes.includes(paramObj.type)) { errors.push( `Invalid parameter type '${paramObj.type || "undefined"}' for ${section}.${key}` ); } if (paramObj.type === "int" || paramObj.type === "integer" || paramObj.type === "float" || paramObj.type === "number") { if (paramObj.min !== void 0 && paramObj.max !== void 0 && paramObj.min > paramObj.max) { errors.push( `Invalid range for ${section}.${key}: min (${paramObj.min}) > max (${paramObj.max})` ); } } if (paramObj.type === "string" || paramObj.type === "text") { if (paramObj.options && !Array.isArray(paramObj.options)) { errors.push(`Options for ${section}.${key} must be an array`); } if (paramObj.options && Array.isArray(paramObj.options) && paramObj.options.length > 100) { warnings.push( `Large options list (${paramObj.options.length}) for ${section}.${key} - consider using autocomplete` ); } } if (paramObj.type === "file") { if (paramObj.accept && typeof paramObj.accept !== "string") { errors.push(`Accept property for ${section}.${key} must be a string`); } if (paramObj.maxSize !== void 0) { if (typeof paramObj.maxSize !== "number" || paramObj.maxSize <= 0) { errors.push(`maxSize for ${section}.${key} must be a positive number`); } if (paramObj.maxSize > 100 * 1024 * 1024) { warnings.push(`Very large maxSize (${Math.round(paramObj.maxSize / 1024 / 1024)}MB) for ${section}.${key} - consider smaller limits`); } } if (paramObj.readAs) { const validReadModes = ["text", "json", "dataURL", "arrayBuffer", "binaryString"]; if (!validReadModes.includes(paramObj.readAs)) { errors.push(`Invalid readAs mode '${paramObj.readAs}' for ${section}.${key}. Valid modes: ${validReadModes.join(", ")}`); } } } if (paramObj.dependsOn && typeof paramObj.dependsOn === "object") { const allInputKeys = Object.keys(params); for (const depKey of Object.keys(paramObj.dependsOn)) { if (!allInputKeys.includes(depKey)) { errors.push(`Input '${key}' depends on non-existent input '${depKey}'`); } } } } else { errors.push( `Invalid parameter definition for ${section}.${key} - must be string or object` ); } } } /** * Check brace balance in code */ checkBraceBalance(content) { let balance = 0; let inString = false; let inComment = false; let stringChar = ""; for (let i = 0; i < content.length; i++) { const char = content[i]; const nextChar = content[i + 1]; if (!inComment && (char === '"' || char === "'" || char === "`")) { if (!inString) { inString = true; stringChar = char; } else if (char === stringChar && content[i - 1] !== "\\") { inString = false; stringChar = ""; } continue; } if (!inString) { if (char === "/" && nextChar === "/") { inComment = true; continue; } if (char === "/" && nextChar === "*") { inComment = true; continue; } if (inComment && char === "\n") { inComment = false; continue; } if (inComment && char === "*" && nextChar === "/") { inComment = false; i++; continue; } } if (!inString && !inComment) { if (char === "{") balance++; if (char === "}") balance--; } } return balance; } /** * Get maximum nesting level in code */ getMaxNestingLevel(content) { let maxNesting = 0; let currentNesting = 0; let inString = false; let inComment = false; let stringChar = ""; for (let i = 0; i < content.length; i++) { const char = content[i]; const nextChar = content[i + 1]; if (!inComment && (char === '"' || char === "'" || char === "`")) { if (!inString) { inString = true; stringChar = char; } else if (char === stringChar && content[i - 1] !== "\\") { inString = false; stringChar = ""; } continue; } if (!inString) { if (char === "/" && nextChar === "/") { inComment = true; continue; } if (char === "/" && nextChar === "*") { inComment = true; continue; } if (inComment && char === "\n") { inComment = false; continue; } if (inComment && char === "*" && nextChar === "/") { inComment = false; i++; continue; } } if (!inString && !inComment) { if (char === "{") { currentNesting++; maxNesting = Math.max(maxNesting, currentNesting); } if (char === "}") { currentNesting--; } } } return maxNesting; } /** * Add custom validation rule */ addDangerousPattern(pattern, message) { this.dangerousPatterns.push({ pattern, message }); console.log(`\u26A0\uFE0F Added dangerous pattern: ${message}`); } /** * Remove validation rule */ removeDangerousPattern(message) { const index = this.dangerousPatterns.findIndex( (p) => p.message === message ); if (index >= 0) { this.dangerousPatterns.splice(index, 1); console.log(`\u2705 Removed dangerous pattern: ${message}`); } } }; // src/resource-monitor.ts var ResourceMonitor = class { // Check every second constructor(options) { this.startMemory = 0; this.startTime = 0; this.maxMemoryUsed = 0; this.checkInterval = null; this.memoryCheckCount = 0; this.maxMemory = 100 * 1024 * 1024; // 100MB this.checkIntervalMs = 1e3; if (options?.maxMemory) { this.maxMemory = options.maxMemory; } if (options?.checkIntervalMs) { this.checkIntervalMs = options.checkIntervalMs; } } /** * Start monitoring resources */ start() { this.startTime = performance.now(); this.maxMemoryUsed = 0; this.memoryCheckCount = 0; if (this.isMemoryAPIAvailable()) { this.startMemory = performance.memory.usedJSHeapSize; } else { this.startMemory = 0; console.log("\u{1F4A1} Memory monitoring not available in this environment"); } this.checkInterval = setInterval(() => { this.performMemoryCheck(); }, this.checkIntervalMs); console.log( `\u{1F4CA} Resource monitoring started (max memory: ${Math.round( this.maxMemory / 1024 / 1024 )}MB)` ); } /** * Stop monitoring resources */ stop() { if (this.checkInterval) { clearInterval(this.checkInterval); this.checkInterval = null; } const duration = performance.now() - this.startTime; const finalMemoryUsed = this.getCurrentMemoryUsed(); console.log(`\u{1F4CA} Resource monitoring stopped:`, { duration: `${Math.round(duration)}ms`, memoryUsed: `${Math.round(finalMemoryUsed / 1024 / 1024)}MB`, maxMemoryUsed: `${Math.round(this.maxMemoryUsed / 1024 / 1024)}MB`, memoryChecks: this.memoryCheckCount }); } /** * Manual memory check (can be called during execution) */ check() { this.performMemoryCheck(); } /** * Get current memory usage statistics */ getStats() { const memoryUsed = this.getCurrentMemoryUsed(); const duration = performance.now() - this.startTime; return { memoryUsed, maxMemoryUsed: this.maxMemoryUsed, memoryLimit: this.maxMemory, memoryPercentage: memoryUsed / this.maxMemory * 100, duration, checksPerformed: this.memoryCheckCount }; } /** * Dispose of resources */ dispose() { if (this.checkInterval) { clearInterval(this.checkInterval); this.checkInterval = null; } } /** * Perform a memory check */ performMemoryCheck() { this.memoryCheckCount++; if (!this.isMemoryAPIAvailable()) { return; } const currentMemoryUsed = this.getCurrentMemoryUsed(); this.maxMemoryUsed = Math.max(this.maxMemoryUsed, currentMemoryUsed); if (currentMemoryUsed > this.maxMemory) { const memoryMB = Math.round(currentMemoryUsed / 1024 / 1024); const limitMB = Math.round(this.maxMemory / 1024 / 1024); console.error(`\u274C Memory limit exceeded: ${memoryMB}MB > ${limitMB}MB`); throw new Error( `Script exceeded memory limit: ${memoryMB}MB used, ${limitMB}MB allowed. Consider optimizing your script or reducing data size.` ); } const memoryPercentage = currentMemoryUsed / this.maxMemory * 100; if (memoryPercentage > 80 && this.memoryCheckCount % 5 === 0) { console.warn( `\u26A0\uFE0F High memory usage: ${Math.round(memoryPercentage)}% of limit` ); } } /** * Get current memory usage */ getCurrentMemoryUsed() { if (!this.isMemoryAPIAvailable()) { return 0; } const currentMemory = performance.memory.usedJSHeapSize; return Math.max(0, currentMemory - this.startMemory); } /** * Check if memory API is available */ isMemoryAPIAvailable() { return typeof performance !== "undefined" && "memory" in performance && typeof performance.memory === "object" && "usedJSHeapSize" in performance.memory; } /** * Force garbage collection if available (for testing) */ forceGC() { if (typeof window !== "undefined" && "gc" in window) { console.log("\u{1F5D1}\uFE0F Forcing garbage collection"); window.gc(); } else if (typeof global !== "undefined" && "gc" in global) { console.log("\u{1F5D1}\uFE0F Forcing garbage collection"); global.gc(); } else { console.log("\u{1F4A1} Garbage collection not available"); } } /** * Create a memory pressure test */ static createMemoryPressureTest(sizeInMB = 10) { return () => { console.log(`\u{1F9EA} Creating ${sizeInMB}MB memory pressure test`); const arraySize = sizeInMB * 1024 * 1024 / 8; const testArray = new Array(arraySize); for (let i = 0; i < arraySize; i++) { testArray[i] = Math.random(); } console.log(`\u{1F4BE} Created array with ${testArray.length} elements`); return () => { testArray.length = 0; console.log("\u{1F5D1}\uFE0F Cleaned up memory pressure test"); }; }; } }; // src/synthase.ts var Synthase = class { constructor(scriptContentOrResolver, config) { this.scriptContentOrResolver = scriptContentOrResolver; this.config = config; this.scriptCache = /* @__PURE__ */ new Map(); this.cachePolicy = { maxAge: 5 * 60 * 1e3, // 5 minutes maxSize: 100 // max cached scripts }; this.loadedScript = null; this.isInitialized = false; this.initializationPromise = null; this.executionLimits = new ExecutionLimits(); this.scriptValidator = new ScriptValidator(); this.resourceMonitor = new ResourceMonitor(); if (config?.limits) { this.executionLimits = new ExecutionLimits(config.limits); } if (config?.resourceMonitor) { this.resourceMonitor = new ResourceMonitor(config.resourceMonitor); } this.registry = config?.registry; this.initializationPromise = this.initialize(); } /** * Configure cache policy */ setCachePolicy(policy) { this.cachePolicy = { ...this.cachePolicy, ...policy }; console.log(`\u2699\uFE0F Cache policy updated:`, this.cachePolicy); } /** * Wait for initialization to complete */ async waitForInitialization() { if (!this.isInitialized) { if (!this.initializationPromise) { throw new Error("Synthase initialization failed"); } await this.initializationPromise; } } /** * Initialize and plan the main script */ async initialize() { try { console.log(`\u{1F50D} Initializing Synthase...`); this.cleanupCache(); let scriptContent; if (typeof this.scriptContentOrResolver === "string") { scriptContent = this.scriptContentOrResolver; } else { console.log(`\u{1F504} Resolving script content via callback`); scriptContent = await this.scriptContentOrResolver(); } const validation = this.scriptValidator.validateScript(scriptContent); if (!validation.valid) { throw new Error( `Script validation failed: ${validation.errors.join(", ")}` ); } const scriptId = `main-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; this.loadedScript = await this.loadScriptTree(scriptContent, scriptId); console.log(`\u{1F4CB} Main script planned: ${scriptId}`); console.log( `\u{1F517} Total dependencies loaded: ${this.loadedScript.deps.length}` ); console.log(`\u{1F4BE} Cache entries: ${this.scriptCache.size}`); this.isInitialized = true; } catch (error) { console.error(`\u274C Synthase initialization failed:`, error); throw error; } } /** * Execute the script with given inputs */ async call(inputs) { await this.waitForInitialization(); if (!this.loadedScript) { throw new Error("No script loaded"); } console.log("\u{1F680} Executing script with inputs:", inputs); this.resourceMonitor.start(); try { const validatedInputs = this.validateInputs(inputs, this.loadedScript.io); const context = await this.createExecutionContext(); const result = await this.executionLimits.executeWithTimeout( () => this.loadedScript.defaultFunction(validatedInputs, context), this.executionLimits.timeout ); console.log("\u2705 Script executed successfully"); return result; } catch (error) { console.error("\u274C Script execution failed:", error); throw error; } finally { this.resourceMonitor.stop(); } } /** * Get the IO schema of the loaded script */ getIO() { return this.loadedScript?.io || null; } /** * Get dependencies of the loaded script */ getDependencies() { return this.loadedScript?.deps || []; } /** * Reload the script (for hot reloading) */ async reload() { console.log("\u{1F504} Reloading script..."); this.isInitialized = false; this.loadedScript = null; this.clearCache(); this.initializationPromise = this.initialize(); await this.initializationPromise; } /** * Load script and all dependencies */ async loadScriptTree(scriptContent, scriptId) { const loadedScripts = /* @__PURE__ */ new Map(); const loadingQueue = [ { id: scriptId, content: scriptContent } ]; const processed = /* @__PURE__ */ new Set(); while (loadingQueue.length > 0) { const { id, content } = loadingQueue.shift(); if (processed.has(id)) continue; console.log(`\u{1F527} Loading script: ${id}`); let loadedScript; if (content) { const contentHash = this.hashContent(content); const cached = this.getCachedScript(id); if (cached && cached.contentHash === contentHash) { console.log(`\u2705 Using cached script (content unchanged): ${id}`); loadedScript = cached.script; } else { loadedScript = await this.processScript(id, content); this.cacheScript(id, loadedScript, content, "main"); } } else { const cached = this.getCachedScript(id); if (cached) { console.log(`\u2705 Using cached script: ${id}`); loadedScript = cached.script; } else { if (!this.config?.registry) { console.warn( `\u26A0\uFE0F No registry configured, skipping dependency: ${id}` ); continue; } try { const depContent = await this.config.registry.resolve(id); const validation = this.scriptValidator.validateScript(depContent); if (!validation.valid) { throw new Error( `Dependency validation failed: ${validation.errors.join(", ")}` ); } loadedScript = await this.processScript(id, depContent); this.cacheScript(id, loadedScript, depContent, "dependency"); } catch (error) { throw new Error( `Failed to load dependency ${id}: ${error.message}` ); } } } loadedScripts.set(id, loadedScript); processed.add(id); for (const depId of loadedScript.deps) { if (!processed.has(depId) && !loadedScripts.has(depId)) { loadingQueue.push({ id: depId }); } } } const mainScript = loadedScripts.get(scriptId); if (!mainScript) { throw new Error(`Main script not found: ${scriptId}`); } return mainScript; } /** * Create execution context with injectable dependencies */ async createExecutionContext() { const importTracker = { importCount: 0, importStack: [], importedScripts: /* @__PURE__ */ new Set() }; const baseContext = { Logger: { info: (message) => console.log("\u2139\uFE0F INFO:", message), success: (message) => console.log("\u2705 SUCCESS:", message), warn: (message) => console.log("\u26A0\uFE0F WARN:", message), error: (message) => console.log("\u274C ERROR:", message) }, Calculator: { enhance: (value) => value * 1.1, sum: (array) => array.reduce((a, b) => a + b, 0), average: (array) => array.length > 0 ? array.reduce((a, b) => a + b, 0) / array.length : 0, multiply: (numbers) => numbers.reduce((a, b) => a * b, 1) }, Utils: { formatNumber: (num, decimals = 0) => parseFloat(num.toFixed(decimals)), capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1), delay: (ms) => new Promise((resolve) => setTimeout(resolve, ms)), randomInt: (min, max) => Math.floor(Math.random() * (max - min + 1)) + min, shuffleArray: (array) => { const result = [...array]; for (let i = result.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [result[i], result[j]] = [result[j], result[i]]; } return result; }, randomChoice: (array) => array[Math.floor(Math.random() * array.length)] }, // Enhanced importScript with safety checks importScript: async (contentOrResolver) => { console.log( `\u{1F4E6} Importing script (${importTracker.importCount + 1}/${this.executionLimits.maxImportedScripts})` ); if (importTracker.importCount >= this.executionLimits.maxImportedScripts) { throw new Error( `Import limit exceeded: maximum ${this.executionLimits.maxImportedScripts} scripts per execution` ); } if (importTracker.importStack.length >= this.executionLimits.maxRecursionDepth) { throw new Error( `Recursion depth limit exceeded: maximum ${this.executionLimits.maxRecursionDepth} levels` ); } this.resourceMonitor.check(); let scriptContent; if (typeof contentOrResolver === "function") { try { scriptContent = await contentOrResolver(); } catch (err) { throw new Error(`Failed to resolve script content: ${err.message}`); } } else { const registryId = contentOrResolver; let resolved; if (this.registry) { try { resolved = await this.registry.resolve(registryId); } catch { } } if (typeof resolved === "string") { scriptContent = resolved; } else if (resolved && typeof resolved.content === "string") { scriptContent = resolved.content; } else if (resolved && typeof resolved.script === "string") { scriptContent = resolved.script; } else if (resolved !== void 0) { throw new Error( `Registry returned unsupported value for "${registryId}" (expected string)` ); } else { scriptContent = registryId; } } const contentHash = this.hashContent(scriptContent); if (importTracker.importedScripts.has(contentHash)) { throw new Error( "Recursive import detected: script content already imported in this execution" ); } const validation = this.scriptValidator.validateScript(scriptContent); if (!validation.valid) { throw new Error( `Imported script validation failed: ${validation.errors.join(", ")}` ); } const scriptId = `imported-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; importTracker.importCount++; importTracker.importStack.push(scriptId); importTracker.importedScripts.add(contentHash); try { const loadedScript = await this.processScript(scriptId, scriptContent); const importedScript = async (inputs) => { console.log( `\u{1F680} Executing imported script ${scriptId} with:`, inputs ); const validatedInputs = this.validateInputs( inputs, loadedScript.io ); const context2 = await this.createExecutionContext(); return await loadedScript.defaultFunction(validatedInputs, context2); }; Object.assign(importedScript, { io: loadedScript.io, deps: loadedScript.deps, id: scriptId }); console.log(`\u2705 Script imported successfully: ${scriptId}`); return importedScript; } finally { importTracker.importStack.pop(); } } }; const context = { ...baseContext, ...this.config?.contextProviders || {} // Inject custom dependencies }; return context; } /** * Process script content into LoadedScript */ async processScript(id, content) { const module = this.createModule(content); const { io, deps, defaultFunction } = await this.introspectModule(module); return { id, io, deps, defaultFunction }; } /** * Validate inputs against IO schema */ validateInputs(inputs, io) { const inputsWithDefaults = ParameterUtils.applyDefaults(inputs, io.inputs); for (const [key, spec] of Object.entries(io.inputs)) { if (!ParameterUtils.shouldShowParameter(spec, inputsWithDefaults)) continue; if (key in inputsWithDefaults) { ParameterUtils.validateParameter(inputsWithDefaults[key], spec, key); } else { throw new Error(`Missing required input: ${key}`); } } return inputsWithDefaults; } /** * Check if script is cached and still valid */ getCachedScript(scriptId) { const entry = this.scriptCache.get(scriptId); if (!entry) return null; const age = Date.now() - entry.timestamp; if (age > this.cachePolicy.maxAge) { console.log( `\u23F0 Cache expired for ${scriptId} (${Math.round(age / 1e3)}s old)` ); this.scriptCache.delete(scriptId); return null; } return entry; } /** * Cache a processed script */ cacheScript(id, script, content, source) { const contentHash = this.hashContent(content); const entry = { script, timestamp: Date.now(), contentHash, source }; this.scriptCache.set(id, entry); console.log( `\u{1F4BE} Cached script: ${id} (${source}, hash: ${contentHash.substring( 0, 8 )})` ); } /** * Invalidate cache for a specific script */ invalidateScript(scriptId) { const deleted = this.scriptCache.delete(scriptId); if (deleted) { console.log(`\u{1F5D1}\uFE0F Invalidated cache for: ${scriptId}`); } } /** * Invalidate cache by content (call this when script content changes) */ invalidateByContent(scriptId, newContent) { const entry = this.scriptCache.get(scriptId); if (!entry) return; const newHash = this.hashContent(newContent); if (entry.contentHash !== newHash) { console.log(`\u{1F504} Content changed for ${scriptId}, invalidating cache`); this.invalidateScript(scriptId); } } /** * Clean up old cache entries */ cleanupCache() { const entries = Array.from(this.scriptCache.entries()); const now = Date.now(); let cleaned = 0; for (const [id, entry] of entries) { if (now - entry.timestamp > this.cachePolicy.maxAge) { this.scriptCache.delete(id); cleaned++; } } const remaining = Array.from(this.scriptCache.entries()); if (remaining.length > this.cachePolicy.maxSize) { remaining.sort((a, b) => a[1].timestamp - b[1].timestamp).slice(0, remaining.length - this.cachePolicy.maxSize).forEach(([id]) => { this.scriptCache.delete(id); cleaned++; }); } if (cleaned > 0) { console.log(`\u{1F9F9} Cleaned up ${cleaned} cache entries`); } } /** * Get cache statistics */ getCacheStats() { const entries = Array.from(this.scriptCache.values()); const now = Date.now(); return { totalEntries: entries.length, avgAge: entries.length > 0 ? Math.round( entries.reduce((sum, e) => sum + (now - e.timestamp), 0) / entries.length / 1e3 ) : 0, sources: entries.reduce( (acc, e) => { acc[e.source] = (acc[e.source] || 0) + 1; return acc; }, {} ) }; } /** * Better content hashing for cache invalidation */ hashContent(content) { let hash = 0; if (content.length === 0) return hash.toString(36); for (let i = 0; i < content.length; i++) { const char = content.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; } hash = hash ^ content.length; const hashStr = Math.abs(hash).toString(36); const checksum = content.length > 0 ? (content.charCodeAt(0) + content.charCodeAt(content.length - 1)).toString(36) : "0"; return `${hashStr}_${checksum}_${content.length}`; } /** * Clear all caches */ clearCache() { const count = this.scriptCache.size; this.scriptCache.clear(); console.log(`\u{1F5D1}\uFE0F Cleared ${count} cache entries`); } /** * Create module from script content */ createModule(scriptContent) { const moduleBlob = new Blob([scriptContent], { type: "application/javascript" }); const moduleUrl = URL.createObjectURL(moduleBlob); return { url: moduleUrl, content: scriptContent }; } /** * Introspect module to extract IO, dependencies, and default function * Fixed version that properly imports the module to preserve function scope */ async introspectModule(moduleInfo) { console.log("\u{1F527} Introspecting module exports..."); try { const module = await import(moduleInfo.url); if (!module.io) { throw new Error("No 'io' export found in script"); } if (!module.default || typeof module.default !== "function") { throw new Error("No default function export found in script"); } const io = module.io; const defaultFunction = module.default; const deps = this.extractDependencies(moduleInfo.content); URL.revokeObjectURL(moduleInfo.url); return { io, deps, defaultFunction }; } catch (error) { URL.revokeObjectURL(moduleInfo.url); if (error.message.includes("import")) { throw new Error( `Script import failed: ${error.message}. Make sure your script exports are valid ES6 module syntax.` ); } throw new Error(`Script introspection failed: ${error.message}`); } } /** * Extract dependencies from script content */ extractDependencies(scriptContent) { const importMatches = scriptContent.match(/importScript\s*\(\s*["']([^"']+)["']\s*\)/g) || []; return importMatches.map((match) => { const urlMatch = match.match(/["']([^"']+)["']/); return urlMatch ? urlMatch[1] : ""; }).filter(Boolean); } /** * Dispose resources */ dispose() { this.clearCache(); this.resourceMonitor.dispose(); } }; // src/synthase-utils.ts async function execute(scriptContentOrResolver, inputs, options = {}) { console.log("\u{1F680} Quick execute: creating Synthase and running script"); const synthase = new Synthase(scriptContentOrResolver, options); if (options.cachePolicy) { synthase.setCachePolicy(options.cachePolicy); } try { const result = await synthase.call(inputs); console.log("\u2705 Quick execute completed successfully"); return result; } catch (error) { console.error("\u274C Quick execute failed:", error.message); throw error; } finally { synthase.dispose(); } } async function validate(scriptContentOrResolver, options = {}) { console.log("\u{1F50D} Validating script"); const synthase = new Synthase(scriptContentOrResolver, options); try { await synthase.waitForInitialization(); const io = synthase.getIO(); const dependencies = synthase.getDependencies(); console.log("\u2705 Script validation completed"); return { valid: true, io, dependencies }; } catch (error) { console.log("\u274C Script validation failed:", error.message); return { valid: false, io: null, dependencies: [], errors: [error.message] }; } finally { synthase.dispose(); } } async function executeWithValidation(scriptContentOrResolver, inputs, options = {}) { console.log("\u{1F50D} Execute with validation: validating script first"); console.log("\u{1F50D} Inputs received:", inputs); const synthase = new Synthase(scriptContentOrResolver, options); if (options.cachePolicy) { synthase.setCachePolicy(options.cachePolicy); } try { await synthase.waitForInitialization(); const io = synthase.getIO(); console.log("\u{1F50D} IO Schema:", JSON.stringify(io, null, 2)); if (!io) { throw new Error("No IO schema found in script"); } try { const { ParameterUtils: ParameterUtils2 } = await import("./types-MJTCTBL5.js"); const inputsWithDefaults = ParameterUtils2.applyDefaults( inputs, io.inputs ); console.log("\u{1F50D} Inputs with defaults:", inputsWithDefaults); for (const [key, spec] of Object.entries(io.inputs)) { const paramSpec = spec; console.log(`\u{1F50D} Checking parameter: ${key}`, paramSpec); const shouldShow = ParameterUtils2.shouldShowParameter( paramSpec, inputsWithDefaults ); console.log(`\u{1F50D} Should show parameter ${key}:`, shouldShow); if (!shouldShow) continue; const hasDefault = typeof paramSpec === "object" && paramSpec !== null && "default" in paramSpec; const isPresent = key in inputsWithDefaults; console.log( `\u{1F50D} Parameter ${key}: hasDefault=${hasDefault}, isPresent=${isPresent}` ); if (!isPresent && !hasDefault) { console.log(`\u{1F50D} Missing required input detected: ${key}`); throw new Error(`Missing required input: ${key}`); } if (isPresent) { ParameterUtils2.validateParameter( inputsWithDefaults[key], paramSpec, key ); } } console.log( "\u2705 Input validation passed - this should not happen for missing required inputs" ); const result = await synthase.call(inputsWithDefaults); console.log("\u2705 Execute with validation completed successfully"); return result; } catch (error) { console.log("\u{1F50D} Validation error caught:", error.message); throw new Error(`Input validation failed: ${error.message}`); } } catch (error) { console.error("\u274C Execute with validation failed:", error.message); throw error; } finally { synthase.dispose(); } } async function executeBatch(scripts, options = {}) { console.log(`\u{1F680} Batch execute: running ${scripts.length} scripts`); const results = []; for (let i = 0; i < scripts.length; i++) { const script = scripts[i]; const scriptId = script.id || `script-${i}`; try { console.log( `\u{1F4DD} Executing batch script ${i + 1}/${scripts.length}: ${scriptId}` ); const result = await execute(script.content, script.inputs, options); results.push({ id: scriptId, success: true, result }); } catch (error) { console.error(`\u274C Batch script ${scriptId} failed:`, error.message); results.push({ id: scriptId, success: false, error: error.message }); } } const successCount = results.filter((r) => r.success).length; console.log( `\u2705 Batch execute completed: ${successCount}/${scripts.length} successful` ); return results; } async function createReusable(scriptContentOrResolver, options = {}) { console.log("\u{1F527} Creating reusable Synthase instance"); const synthase = new Synthase(scriptContentOrResolver, options); if (options.cachePolicy) { synthase.setCachePolicy(options.cachePolicy); } await synthase.waitForInitialization(); return { synthase, execute: (inputs) => synthase.call(inputs), getIO: () => synthase.getIO(), getDependencies: () => synthase.getDependencies(), dispose: () => synthase.dispose() }; } async function createHotReloadable(getScript, options = {}) { let synthase = new Synthase(getScript(), options); if (options.cachePolicy) { synthase.setCachePolicy(options.cachePolicy); } return { execute: (inputs) => synthase.call(inputs), reload: async () => { console.log("\u{1F504} Hot reloading script"); synthase.dispose(); synthase = new Synthase(getScript(), options); if (options.cachePolicy) { synthase.setCachePolicy(options.cachePolicy); } await synthase.waitForInitialization(); console.log("\u2705 Hot reload completed"); }, getIO: () => synthase.getIO(), dispose: () => synthase.dispose() }; } async function benchmark(scriptContentOrResolver, inputs, iterations = 5, options = {}) { console.log(`\u23F1\uFE0F Benchmarking script (${iterations} iterations)`); const times = []; const results = []; const reusable = await createReusable(scriptContentOrResolver, options); try { for (let i = 0; i < iterations; i++) { const start = performance.now(); const result = await reusable.execute(inputs); const end = performance.now(); const time = end - start; times.push(time); results.push(result); console.log(`Iteration ${i + 1}: ${time.toFixed(2)}ms`); } const averageTime = times.reduce((a, b) => a + b, 0) / times.length; const minTime = Math.min(...times); const maxTime = Math.max(...times); const benchmarkResults = { averageTime: Math.round(averageTime * 100) / 100, minTime: Math.round(minTime * 100) / 100, maxTime: Math.round(maxTime * 100) / 100, times, results }; console.log("\u{1F4CA} Benchmark results:", benchmarkResults); return benchmarkResults; } finally { reusable.dispose(); } } var SynthaseUtils = { execute, executeWithValidation, validate, executeBatch, createReusable, createHotReloadable, benchmark }; // src/script-registry.ts var InMemoryScriptRegistry = class { constructor() { this.scripts = /* @__PURE__ */ new Map(); } /** * Register a script with content */ register(scriptId, content) { this.scripts.set(scriptId, content); } /** * Resolve script ID to content */ async resolve(scriptId) { const content = this.scripts.get(scriptId); if (!content) { throw new Error(`Script not found: ${scriptId}`); } return content; } /** * List all registered scripts */ list() { return Array.from(this.scripts.keys()); } /** * Check if script exists */ has(scriptId) { return this.scripts.has(scriptId); } /** * Remove a script */ unregister(scriptId) { const deleted = this.scripts.delete(scriptId); return deleted; } /** * Clear all scripts */ clear() { this.scripts.clear(); } }; var HttpScriptRegistry = class { constructor(baseUrl) { this.baseUrl = baseUrl; } async resolve(scriptId) { if (scriptId.startsWith("ht