@arifwidianto/rpc-agent
Version:
RPC Agent for both client and server, extends more methods easily
221 lines (220 loc) • 12 kB
JavaScript
;
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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SchemaGenerator = void 0;
const path = __importStar(require("node:path"));
const fs = __importStar(require("node:fs"));
const ts = __importStar(require("typescript"));
class SchemaGenerator {
sourceFile;
program;
initialize(entryPointPath) {
const configPath = ts.findConfigFile(path.dirname(entryPointPath), ts.sys.fileExists, "tsconfig.json");
if (!configPath) {
throw new Error("Could not find a valid tsconfig.json.");
}
const { config } = ts.readConfigFile(configPath, ts.sys.readFile);
const { options } = ts.parseJsonConfigFileContent(config, ts.sys, path.dirname(configPath));
this.program = ts.createProgram([entryPointPath], options);
this.sourceFile = this.program.getSourceFile(entryPointPath);
if (!this.sourceFile) {
throw new Error(`Could not load source file: ${entryPointPath}`);
}
}
generateSchema(entryPointPath) {
this.initialize(entryPointPath);
if (!this.sourceFile || !this.program) {
throw new Error("Source file or program not initialized");
}
const schema = {};
const checker = this.program.getTypeChecker();
ts.forEachChild(this.sourceFile, (node) => {
if (ts.isClassDeclaration(node) && node.name) {
const symbol = checker.getSymbolAtLocation(node.name);
if (symbol) {
this.processClassMethods(node, schema);
}
}
});
const schemaPath = path.join(path.dirname(entryPointPath), "schema.ts");
const schemaContent = this.generateSchemaFileContent(schema);
console.log(`Writing schema to ${schemaPath}`);
fs.writeFileSync(schemaPath, schemaContent);
}
processClassMethods(classNode, schema) {
console.log("Processing class:", classNode.name?.text);
classNode.members.forEach((member) => {
if (ts.isPropertyDeclaration(member) && member.name.getText() === "methods") {
console.log("Found methods property");
if (member.initializer && ts.isObjectLiteralExpression(member.initializer)) {
console.log("Methods is an object literal with", member.initializer.properties.length, "properties");
member.initializer.properties.forEach((prop) => {
console.log("Property kind:", ts.SyntaxKind[prop.kind]);
if (ts.isPropertyAssignment(prop)) {
console.log("Property name:", prop.name.getText());
console.log("Property initializer kind:", ts.SyntaxKind[prop.initializer.kind]);
const methodName = prop.name.getText();
console.log("Found method:", methodName);
const typeChecker = this.program.getTypeChecker();
const type = typeChecker.getTypeAtLocation(prop.initializer);
console.log("Method type:", typeChecker.typeToString(type));
if (ts.isArrowFunction(prop.initializer) ||
ts.isFunctionExpression(prop.initializer) ||
ts.isMethodDeclaration(prop.initializer)) {
const signature = typeChecker.getSignatureFromDeclaration(prop.initializer);
console.log("Signature:", signature ? "Found" : "Not found");
if (signature) {
console.log("Parameters:", signature.getParameters().map((p) => p.name));
console.log("Return type:", typeChecker.typeToString(typeChecker.getReturnTypeOfSignature(signature)));
const methodSchema = {
input: this.extractMethodInputSchema(signature, prop.initializer),
output: this.extractMethodOutputSchema(signature, prop.initializer),
example: {
shell: `echo '{"jsonrpc":"2.0","method":"${methodName}","params":{},"id":1}' | nc --tcp localhost 9101`,
node: `node -e "const net = require('net'); const client = new net.Socket(); client.connect(9101, 'localhost', () => { client.write(JSON.stringify({jsonrpc: '2.0', method: '${methodName}', params: {}, id: Date.now()}) + '\\n'); }); client.on('data', (data) => { console.log(data.toString()); client.destroy(); });"`,
},
};
console.log("Method schema:", Object.keys(methodSchema).join(", "));
schema[methodName] = methodSchema;
}
else {
console.log("No signature found for method:", methodName);
}
}
}
else if (ts.isMethodDeclaration(prop)) {
const methodName = prop.name.getText();
console.log("Found method:", methodName);
const typeChecker = this.program.getTypeChecker();
const type = typeChecker.getTypeAtLocation(prop);
console.log("Method type:", typeChecker.typeToString(type));
const signature = typeChecker.getSignatureFromDeclaration(prop);
console.log("Signature:", signature ? "Found" : "Not found");
if (signature) {
console.log("Parameters:", signature.getParameters().map((p) => p.name));
console.log("Return type:", typeChecker.typeToString(typeChecker.getReturnTypeOfSignature(signature)));
const methodSchema = {
input: this.extractMethodInputSchema(signature, prop),
output: this.extractMethodOutputSchema(signature, prop),
example: {
shell: `echo '{"jsonrpc":"2.0","method":"${methodName}","params":{},"id":1}' | nc --tcp localhost 9101`,
node: `node -e "const net = require('net'); const client = new net.Socket(); client.connect(9101, 'localhost', () => { client.write(JSON.stringify({jsonrpc: '2.0', method: '${methodName}', params: {}, id: Date.now()}) + '\\n'); }); client.on('data', (data) => { console.log(data.toString()); client.destroy(); });"`,
},
};
console.log("Method schema:", Object.keys(methodSchema).join(", "));
schema[methodName] = methodSchema;
}
else {
console.log("No signature found for method:", methodName);
}
}
});
}
}
});
}
extractMethodInputSchema(signature, methodNode) {
const typeChecker = this.program.getTypeChecker();
const parameters = signature.getParameters();
if (parameters.length === 0) {
return {};
}
const firstParam = parameters[0];
const paramType = typeChecker.getTypeOfSymbolAtLocation(firstParam, methodNode);
const properties = paramType.getProperties();
const inputSchema = {};
properties.forEach((prop) => {
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, methodNode);
const typeString = typeChecker.typeToString(propType);
inputSchema[prop.name] = {
type: this.mapTypeScriptTypeToSchemaType(typeString),
description: `Parameter ${prop.name}`,
required: true,
};
});
return inputSchema;
}
extractMethodOutputSchema(signature, methodNode) {
const typeChecker = this.program.getTypeChecker();
const returnType = typeChecker.getReturnTypeOfSignature(signature);
let resolvedType = returnType;
if (returnType.symbol?.name === "Promise") {
const typeArguments = returnType.typeArguments;
if (typeArguments && typeArguments.length > 0) {
resolvedType = typeArguments[0];
}
}
const properties = resolvedType.getProperties();
const outputSchema = {};
properties.forEach((prop) => {
const propType = typeChecker.getTypeOfSymbolAtLocation(prop, methodNode);
const typeString = typeChecker.typeToString(propType);
outputSchema[prop.name] = {
type: this.mapTypeScriptTypeToSchemaType(typeString),
description: `Output ${prop.name}`,
};
});
return outputSchema;
}
mapTypeScriptTypeToSchemaType(tsType) {
const cleanType = tsType.toLowerCase().trim();
if (cleanType.includes("number"))
return "number";
if (cleanType.includes("string"))
return "string";
if (cleanType.includes("boolean"))
return "boolean";
if (cleanType.includes("date"))
return "string";
if (cleanType.includes("array"))
return "array";
if (cleanType === "any" || cleanType === "unknown")
return "any";
return "object";
}
generateSchemaFileContent(schema) {
const formatValue = (value, indent = 2) => {
if (typeof value === "string") {
const escaped = value.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, '\\"').replace(/\n/g, "\\n");
return `'${escaped}'`;
}
else if (typeof value === "object" && value !== null) {
const spaces = " ".repeat(indent);
const entries = Object.entries(value);
if (entries.length === 0)
return "{}";
return `{
${entries.map(([key, val]) => `${spaces}${key}: ${formatValue(val, indent + 2)}`).join(",\n")}
${" ".repeat(indent - 2)}}`;
}
return String(value);
};
return `import { ExtensionSchema } from "@arifwidianto/rpc-agent"
export const schema: ExtensionSchema = ${formatValue(schema)}
`;
}
}
exports.SchemaGenerator = SchemaGenerator;
//# sourceMappingURL=schema-generator.js.map