synthase
Version:
A secure, sandboxed, and extensible JavaScript execution engine built with TypeScript.
1,572 lines (1,564 loc) • 58.5 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/types.ts
var types_exports = {};
__export(types_exports, {
ParameterUtils: () => ParameterUtils
});
var ParameterUtils;
var init_types = __esm({
"src/types.ts"() {
"use strict";
ParameterUtils = class {
static normalize(spec) {
if (typeof spec === "string") {
return { type: spec };
}
return spec;
}
static getDefault(spec) {
const param = this.normalize(spec);
if (param.default !== void 0) {
return param.default;
}
switch (param.type) {
case "int":
return 0;
case "float":
return 0;
case "string":
return "";
case "boolean":
return false;
case "object":
return {};
case "array":
return [];
default:
return null;
}
}
static applyDefaults(inputs, schema) {
const result = { ...inputs };
for (const [key, spec] of Object.entries(schema)) {
if (!(key in result)) {
result[key] = this.getDefault(spec);
}
}
return result;
}
static validateParameter(value, spec, paramName) {
const param = this.normalize(spec);
switch (param.type) {
case "int":
if (!Number.isInteger(value)) {
throw new Error(
`${paramName} must be an integer, got: ${typeof value}`
);
}
if (param.min !== void 0 && value < param.min) {
throw new Error(
`${paramName} must be >= ${param.min}, got: ${value}`
);
}
if (param.max !== void 0 && value > param.max) {
throw new Error(
`${paramName} must be <= ${param.max}, got: ${value}`
);
}
break;
case "float":
if (typeof value !== "number") {
throw new Error(
`${paramName} must be a number, got: ${typeof value}`
);
}
if (param.min !== void 0 && value < param.min) {
throw new Error(
`${paramName} must be >= ${param.min}, got: ${value}`
);
}
if (param.max !== void 0 && value > param.max) {
throw new Error(
`${paramName} must be <= ${param.max}, got: ${value}`
);
}
break;
case "string":
if (typeof value !== "string") {
throw new Error(
`${paramName} must be a string, got: ${typeof value}`
);
}
if (param.options && !param.options.includes(value)) {
throw new Error(
`${paramName} must be one of: ${param.options.join(
", "
)}, got: ${value}`
);
}
break;
case "boolean":
if (typeof value !== "boolean") {
throw new Error(
`${paramName} must be a boolean, got: ${typeof value}`
);
}
break;
case "object":
if (typeof value !== "object" || value === null) {
throw new Error(
`${paramName} must be an object, got: ${typeof value}`
);
}
break;
case "array":
if (!Array.isArray(value)) {
throw new Error(
`${paramName} must be an array, got: ${typeof value}`
);
}
break;
}
}
static shouldShowParameter(spec, allInputs) {
const param = this.normalize(spec);
if (!param.dependsOn) return true;
for (const [depKey, depValue] of Object.entries(param.dependsOn)) {
if (allInputs[depKey] !== depValue) {
return false;
}
}
return true;
}
static groupParameters(schema) {
const groups = { default: [] };
for (const [key, spec] of Object.entries(schema)) {
const param = this.normalize(spec);
const group = param.group || "default";
if (!groups[group]) {
groups[group] = [];
}
groups[group].push(key);
}
return groups;
}
};
}
});
// src/index.ts
var index_exports = {};
__export(index_exports, {
CompositeScriptRegistry: () => CompositeScriptRegistry,
ExecutionLimits: () => ExecutionLimits,
HttpScriptRegistry: () => HttpScriptRegistry,
InMemoryScriptRegistry: () => InMemoryScriptRegistry,
ParameterUtils: () => ParameterUtils,
ResourceMonitor: () => ResourceMonitor,
ScriptValidator: () => ScriptValidator,
Synthase: () => Synthase,
SynthaseUtils: () => SynthaseUtils,
VERSION: () => VERSION,
benchmark: () => benchmark2,
createHotReloadable: () => createHotReloadable2,
createReusable: () => createReusable2,
default: () => index_default,
execute: () => execute2,
executeBatch: () => executeBatch2,
executeWithValidation: () => executeWithValidation2,
validate: () => validate2
});
module.exports = __toCommonJS(index_exports);
// src/synthase.ts
init_types();
// 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 module2 = this.createModule(content);
const { io, deps, defaultFunction } = await this.introspectModule(module2);
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 module2 = await import(moduleInfo.url);
if (!module2.io) {
throw new Error("No 'io' export found in script");
}
if (!module2.default || typeof module2.default !== "function") {
throw new Error("No default function export found in script");
}
const io = module2.io;
const defaultFunction = module2.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 Promise.resolve().then(() => (init_types(), types_exports));
const inputsWithDefaults = ParameterUtils2.applyDefaults(
inputs,
io.inputs
);
console.log("\u{1F50D} Input