UNPKG

navskit

Version:

Deploy TypeScript logic on Ethereum. Includes core library, CLI tools, and utilities.

346 lines (337 loc) 12.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.packageManager = exports.PackageManager = void 0; /** * Package management utilities for dynamic loading of NAVS packages */ const path = __importStar(require("path")); const fs = __importStar(require("fs")); const child_process = __importStar(require("child_process")); const util = __importStar(require("util")); const crypto = __importStar(require("crypto")); const logger_1 = require("./logger"); const exec = util.promisify(child_process.exec); const mkdir = util.promisify(fs.mkdir); const writeFile = util.promisify(fs.writeFile); const access = util.promisify(fs.access); const logger = (0, logger_1.createServiceLogger)('PackageManager', '0.0.1'); class PackageManager { packageCache = new Map(); cacheDir; constructor(cacheDir) { this.cacheDir = cacheDir || path.join(process.cwd(), '.navs-cache'); } /** * Gets the cache key for a package name and version */ getCacheKey(serviceName, serviceVersion) { return `${serviceName}@${serviceVersion}`; } /** * Gets the installation directory for a package */ getPackageDir(serviceName, serviceVersion) { const hash = crypto.createHash('md5').update(`${serviceName}@${serviceVersion}`).digest('hex'); return path.join(this.cacheDir, `pkg-${hash}`); } /** * Checks if a package is already cached */ async isPackageCached(serviceName, serviceVersion) { const cacheKey = this.getCacheKey(serviceName, serviceVersion); if (this.packageCache.has(cacheKey)) { return true; } const packageDir = this.getPackageDir(serviceName, serviceVersion); try { await access(path.join(packageDir, 'package.json')); return true; } catch { return false; } } /** * Downloads and installs a package to the cache */ async downloadPackage(serviceName, serviceVersion) { const packageDir = this.getPackageDir(serviceName, serviceVersion); logger.info(`Downloading package ${serviceName}@${serviceVersion} to ${packageDir}`); // Create the package directory await mkdir(packageDir, { recursive: true }); // Create a package.json for the isolated environment const packageJson = { name: `navs-isolated-${serviceName}`, version: '1.0.0', private: true, type: 'module', dependencies: { [serviceName]: serviceVersion, 'navs-core': 'latest', 'viem': 'latest', 'reflect-metadata': 'latest' } }; await writeFile(path.join(packageDir, 'package.json'), JSON.stringify(packageJson, null, 2)); // Install the package logger.info(`Installing ${serviceName}@${serviceVersion}...`); try { await exec('npm install', { cwd: packageDir }); logger.info(`Successfully installed ${serviceName}@${serviceVersion}`); return packageDir; } catch (error) { logger.error(`Failed to install package ${serviceName}@${serviceVersion}:`, error); throw new Error(`Package installation failed: ${error.message}`); } } /** * Creates a worker script for executing functions in the package */ createWorkerScript(serviceName) { return ` import { privateKeyToAccount } from 'viem/accounts'; import { navs, navsRegistry } from 'navs-core'; // Import the package - this will trigger all @navs decorators import '${serviceName}'; // Listen for messages from parent process process.on('message', async (message) => { const { id, type, data } = message; try { if (type === 'listFunctions') { // Return all functions registered for this service const serviceFunctions = {}; for (const [name, funcInfo] of Object.entries(navsRegistry)) { if (funcInfo.serviceName === '${serviceName}') { serviceFunctions[name] = { paramTypes: funcInfo.paramTypes, returnType: funcInfo.returnType, hasConsensus: !!funcInfo.consensus }; } } process.send({ id, success: true, result: serviceFunctions }); } else if (type === 'executeFunction') { const { functionName, encodedArgs, isConsensus, consensusArgs } = data; const funcInfo = navsRegistry[functionName]; if (!funcInfo) { throw new Error(\`Function \${functionName} not found\`); } let result; if (isConsensus && funcInfo.consensus) { result = await funcInfo.consensus(consensusArgs); } else { // Decode the arguments using the function's decoder const decodedArgs = funcInfo.decodeArgs(encodedArgs); result = await funcInfo.fn(...decodedArgs); } process.send({ id, success: true, result }); } else { throw new Error(\`Unknown message type: \${type}\`); } } catch (error) { process.send({ id, success: false, error: error.message, stack: error.stack }); } }); // Send ready signal process.send({ type: 'ready' }); `; } /** * Starts a worker process for a package */ async startWorker(serviceName, serviceVersion) { const packageDir = this.getPackageDir(serviceName, serviceVersion); // Create worker script const workerScript = this.createWorkerScript(serviceName); const workerPath = path.join(packageDir, 'worker.mjs'); await writeFile(workerPath, workerScript); // Start the worker process const workerProcess = child_process.fork(workerPath, [], { cwd: packageDir, silent: false, execArgv: ['--experimental-modules'] }); // Wait for ready signal await new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Worker process failed to start within timeout')); }, 30000); workerProcess.on('message', (message) => { if (message.type === 'ready') { clearTimeout(timeout); resolve(); } }); workerProcess.on('error', (error) => { clearTimeout(timeout); reject(error); }); workerProcess.on('exit', (code) => { if (code !== 0) { clearTimeout(timeout); reject(new Error(`Worker process exited with code ${code}`)); } }); }); logger.info(`Started worker process for ${serviceName}@${serviceVersion}`); return workerProcess; } /** * Sends a message to a worker process and waits for response */ async sendWorkerMessage(worker, type, data) { const id = Math.random().toString(36); return new Promise((resolve, reject) => { const timeout = setTimeout(() => { reject(new Error('Worker message timeout')); }, 30000); const messageHandler = (message) => { if (message.id === id) { clearTimeout(timeout); worker.off('message', messageHandler); if (message.success) { resolve(message.result); } else { reject(new Error(message.error)); } } }; worker.on('message', messageHandler); worker.send({ id, type, data }); }); } /** * Gets a package, downloading it if necessary and starting worker if needed */ async getPackage(serviceName, serviceVersion) { const cacheKey = this.getCacheKey(serviceName, serviceVersion); // Check memory cache first if (this.packageCache.has(cacheKey)) { return this.packageCache.get(cacheKey); } // Check if package is cached on disk if (!(await this.isPackageCached(serviceName, serviceVersion))) { await this.downloadPackage(serviceName, serviceVersion); } // Start worker process const worker = await this.startWorker(serviceName, serviceVersion); const packageInfo = { name: serviceName, version: serviceVersion, installPath: this.getPackageDir(serviceName, serviceVersion), process: worker }; // Cache the package info this.packageCache.set(cacheKey, packageInfo); return packageInfo; } /** * Lists functions available in a package */ async listFunctions(serviceName, serviceVersion) { const packageInfo = await this.getPackage(serviceName, serviceVersion); if (!packageInfo.process) { throw new Error('Package worker process not available'); } return await this.sendWorkerMessage(packageInfo.process, 'listFunctions'); } /** * Executes a function in a package */ async executeFunction(serviceName, serviceVersion, functionName, encodedArgs, isConsensus = false, consensusArgs) { const packageInfo = await this.getPackage(serviceName, serviceVersion); if (!packageInfo.process) { throw new Error('Package worker process not available'); } return await this.sendWorkerMessage(packageInfo.process, 'executeFunction', { functionName, encodedArgs, isConsensus, consensusArgs }); } /** * Cleans up resources for a package */ async cleanup(serviceName, serviceVersion) { const cacheKey = this.getCacheKey(serviceName, serviceVersion); const packageInfo = this.packageCache.get(cacheKey); if (packageInfo?.process) { packageInfo.process.kill(); } this.packageCache.delete(cacheKey); } /** * Clears all package cache and kills all worker processes */ async clearCache() { for (const packageInfo of this.packageCache.values()) { if (packageInfo.process) { packageInfo.process.kill(); } } this.packageCache.clear(); } /** * Gets cache statistics */ getCacheStats() { const activeWorkers = Array.from(this.packageCache.values()) .filter(pkg => pkg.process && !pkg.process.killed).length; return { cachedPackages: this.packageCache.size, activeWorkers }; } } exports.PackageManager = PackageManager; // Export a default instance exports.packageManager = new PackageManager();