UNPKG

powr-sdk-api

Version:

Shared API core library for PowrStack projects. Zero dependencies - works with Express, Next.js API routes, and other frameworks. All features are optional and install only what you need.

231 lines (218 loc) โ€ข 6.79 kB
"use strict"; const { getDb } = require("../services/mongo"); const crypto = require('crypto'); const { config } = require('../config'); class FunctionsManager { constructor() { this.compiledFunctions = new Map(); this.isInitialized = false; this.isCentralService = false; this.isEnabled = false; // Default disabled } async initialize(options = {}) { this.isCentralService = options.isCentralService || false; this.isEnabled = options.enableFunctions === true; // Default false unless explicitly enabled if (!this.isEnabled) { console.log("๐Ÿšซ Functions is disabled"); this.isInitialized = true; return; } if (this.isCentralService) { // Central service: Don't pre-load, load dynamically console.log("๐Ÿ”„ Central service mode - loading functions dynamically"); this.isInitialized = true; } else { // Individual API: Load only this project's functions const projectId = config.projectId; console.log(`๐Ÿ“ฆ Loading functions for project: ${projectId}`); try { const db = await getDb(); const routes = await db.collection("functions").find({ projectId }).toArray(); routes.forEach(route => { this.compileAndCache(route); }); this.isInitialized = true; console.log(`โœ… Pre-compiled ${routes.length} functions for project ${projectId}`); } catch (error) { console.error("โŒ Failed to initialize functions:", error); this.isInitialized = true; // Still mark as initialized to prevent blocking } } } compileAndCache(route) { const cacheKey = `${route.projectId}:${route.route}`; try { // Security validation if (!this.validateCode(route.code)) { console.error(`โŒ Invalid code in function: ${route.route}`); return; } // Pre-compile function const compiledFunction = new Function("params", route.code); this.compiledFunctions.set(cacheKey, { function: compiledFunction, metadata: { route: route.route, projectId: route.projectId, compiledAt: new Date(), codeHash: this.generateHash(route.code) } }); console.log(`โœ… Compiled: ${route.route}`); } catch (error) { console.error(`โŒ Failed to compile ${route.route}:`, error); } } async execute(projectId, route, params) { if (!this.isEnabled) { return { success: false, message: "Functions is disabled", data: null }; } const cacheKey = `${projectId}:${route}`; const cached = this.compiledFunctions.get(cacheKey); if (!cached) { if (this.isCentralService) { // Try to load only the specific function dynamically await this.loadSpecificRoute(projectId, route); const retryCached = this.compiledFunctions.get(cacheKey); if (retryCached) { return retryCached.function(params); } } return { success: false, message: `Function not found: ${route}`, data: null }; } try { const result = cached.function(params); return { success: true, message: "Function executed successfully", data: result }; } catch (error) { return { success: false, message: `Error executing function: ${error.message}`, data: null, error: error.message }; } } // Load specific function for central service async loadSpecificRoute(projectId, route) { if (!this.isCentralService) { throw new Error("Dynamic loading only available in central service mode"); } try { const db = await getDb(); const routeData = await db.collection("functions").findOne({ projectId, route }); if (routeData) { this.compileAndCache(routeData); console.log(`โœ… Loaded function: ${route} for project ${projectId}`); } else { console.log(`โŒ Function not found: ${route} for project ${projectId}`); } } catch (error) { console.error(`โŒ Failed to load function ${route} for project ${projectId}:`, error); throw error; } } // Dynamic loading for central service async loadProjectRoutes(projectId) { if (!this.isCentralService) { throw new Error("Dynamic loading only available in central service mode"); } try { const db = await getDb(); const routes = await db.collection("functions").find({ projectId }).toArray(); routes.forEach(route => { this.compileAndCache(route); }); console.log(`โœ… Loaded ${routes.length} functions for project ${projectId}`); } catch (error) { console.error(`โŒ Failed to load functions for project ${projectId}:`, error); throw error; } } // Security validation validateCode(code) { const dangerousPatterns = [/process\./, /require\(/, /eval\(/, /setTimeout\(/, /setInterval\(/, /global\./, /__dirname/, /__filename/]; return !dangerousPatterns.some(pattern => pattern.test(code)); } // Generate hash for change detection generateHash(code) { return crypto.createHash('md5').update(code).digest('hex'); } // Update function (re-compile) async updateRoute(projectId, route, newCode) { const routeData = { projectId, route, code: newCode }; this.compileAndCache(routeData); // Optionally save to database try { const db = await getDb(); await db.collection("functions").updateOne({ projectId, route }, { $set: { code: newCode, updatedAt: new Date() } }); } catch (error) { console.error(`โŒ Failed to update function in database: ${route}`, error); } } // Get function statistics getStats() { return { totalFunctions: this.compiledFunctions.size, isInitialized: this.isInitialized, isCentralService: this.isCentralService, isEnabled: this.isEnabled, cacheKeys: Array.from(this.compiledFunctions.keys()) }; } // Clear cache (for testing or maintenance) clearCache() { this.compiledFunctions.clear(); console.log("๐Ÿงน Function cache cleared"); } // Enable/Disable Functions enable() { this.isEnabled = true; console.log("โœ… Functions enabled"); } disable() { this.isEnabled = false; console.log("๐Ÿšซ Functions disabled"); } // Toggle Functions toggle() { this.isEnabled = !this.isEnabled; console.log(this.isEnabled ? "โœ… Functions enabled" : "๐Ÿšซ Functions disabled"); return this.isEnabled; } } module.exports = new FunctionsManager();