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
JavaScript
;
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();