@gati-framework/cli
Version:
CLI tool for Gati framework - create, develop, build and deploy cloud-native applications
211 lines • 7.84 kB
JavaScript
/**
* @module cli/codegen/sdk-generator
* @description Generate type-safe SDK client stubs from handler manifests
*/
/**
* Generate SDK client from handler manifests
*/
export class SDKGenerator {
generate(manifests, options = {}) {
const opts = {
includeComments: true,
className: 'GatiClient',
includeAuth: true,
includeTimeout: true,
...options,
};
const lines = [];
// Header comment
if (opts.includeComments) {
lines.push('/**');
lines.push(' * Auto-generated Gati API Client');
lines.push(` * Generated: ${new Date().toISOString()}`);
lines.push(' * DO NOT EDIT - This file is auto-generated');
lines.push(' */');
lines.push('');
}
// Client options interface
lines.push('export interface ClientOptions {');
if (opts.includeAuth) {
lines.push(' token?: string;');
}
if (opts.includeTimeout) {
lines.push(' timeout?: number;');
}
lines.push(' headers?: Record<string, string>;');
lines.push('}');
lines.push('');
// Client class
lines.push(`export class ${opts.className} {`);
lines.push(' constructor(');
lines.push(' private baseUrl: string,');
lines.push(' private options?: ClientOptions');
lines.push(' ) {}');
lines.push('');
// Generate methods for each handler
for (const manifest of manifests) {
lines.push(this.generateMethod(manifest, opts));
lines.push('');
}
// Helper methods
lines.push(this.generateHelperMethods(opts));
lines.push('}');
return {
code: lines.join('\n'),
className: opts.className,
};
}
generateMethod(manifest, options) {
const lines = [];
const methodName = this.extractMethodName(manifest);
const httpMethod = Array.isArray(manifest.method) ? manifest.method[0] : manifest.method;
const pathParams = this.extractPathParams(manifest.path);
const hasBody = ['POST', 'PUT', 'PATCH'].includes(httpMethod.toUpperCase());
// JSDoc comment
if (options.includeComments) {
lines.push(' /**');
lines.push(` * ${httpMethod} ${manifest.path}`);
if (pathParams.length > 0) {
pathParams.forEach(param => {
lines.push(` * @param ${param} - Path parameter`);
});
}
if (hasBody) {
lines.push(` * @param body - Request body`);
}
lines.push(` * @returns Response data`);
lines.push(' */');
}
// Method signature
const params = [];
pathParams.forEach(param => params.push(`${param}: string`));
if (hasBody) {
params.push('body: any');
}
params.push('query?: Record<string, string>');
lines.push(` async ${methodName}(${params.join(', ')}): Promise<any> {`);
// Build URL
let urlExpr = this.buildUrlExpression(manifest.path, pathParams);
lines.push(` const url = \`\${this.baseUrl}${urlExpr}\`;`);
lines.push(' const queryString = query ? \'?\' + new URLSearchParams(query).toString() : \'\';');
lines.push(' const fullUrl = url + queryString;');
lines.push('');
// Fetch options
lines.push(' const response = await fetch(fullUrl, {');
lines.push(` method: '${httpMethod.toUpperCase()}',`);
lines.push(' headers: this.getHeaders(),');
if (hasBody) {
lines.push(' body: JSON.stringify(body),');
}
if (options.includeTimeout) {
lines.push(' signal: this.getAbortSignal(),');
}
lines.push(' });');
lines.push('');
// Error handling
lines.push(' if (!response.ok) {');
lines.push(' throw new Error(`HTTP ${response.status}: ${response.statusText}`);');
lines.push(' }');
lines.push('');
// Return response
lines.push(' return response.json();');
lines.push(' }');
return lines.join('\n');
}
generateHelperMethods(options) {
const lines = [];
// getHeaders method
lines.push(' private getHeaders(): Record<string, string> {');
lines.push(' const headers: Record<string, string> = {');
lines.push(" 'Content-Type': 'application/json',");
lines.push(' };');
lines.push('');
if (options.includeAuth) {
lines.push(' if (this.options?.token) {');
lines.push(' headers[\'Authorization\'] = `Bearer ${this.options.token}`;');
lines.push(' }');
lines.push('');
}
lines.push(' if (this.options?.headers) {');
lines.push(' Object.assign(headers, this.options.headers);');
lines.push(' }');
lines.push('');
lines.push(' return headers;');
lines.push(' }');
if (options.includeTimeout) {
lines.push('');
lines.push(' private getAbortSignal(): AbortSignal | undefined {');
lines.push(' if (this.options?.timeout) {');
lines.push(' return AbortSignal.timeout(this.options.timeout);');
lines.push(' }');
lines.push(' return undefined;');
lines.push(' }');
}
return lines.join('\n');
}
extractMethodName(manifest) {
const httpMethod = Array.isArray(manifest.method) ? manifest.method[0] : manifest.method;
const path = manifest.path;
// Extract path segments
const segments = path.split('/').filter(s => s && !s.startsWith(':'));
// Build method name: httpMethod + PathSegments
// e.g., GET /users/:id -> getUsers
// e.g., POST /users -> createUser
// e.g., PUT /users/:id -> updateUser
// e.g., DELETE /users/:id -> deleteUser
const methodPrefix = this.getMethodPrefix(httpMethod.toUpperCase());
const resourceName = segments.length > 0 ? this.toCamelCase(segments.join('_')) : 'resource';
return methodPrefix + this.capitalize(resourceName);
}
getMethodPrefix(httpMethod) {
const prefixMap = {
'GET': 'get',
'POST': 'create',
'PUT': 'update',
'PATCH': 'patch',
'DELETE': 'delete',
};
return prefixMap[httpMethod] || 'call';
}
extractPathParams(path) {
const params = [];
const segments = path.split('/');
for (const segment of segments) {
if (segment.startsWith(':')) {
params.push(segment.substring(1));
}
}
return params;
}
buildUrlExpression(path, pathParams) {
let expr = path;
// Replace :param with ${param}
for (const param of pathParams) {
expr = expr.replace(`:${param}`, `\${${param}}`);
}
return expr;
}
toCamelCase(str) {
// Handle both underscores and hyphens
return str
.split(/[_-]/)
.map((word, index) => {
if (index === 0)
return word.toLowerCase();
return this.capitalize(word);
})
.join('');
}
capitalize(str) {
if (!str)
return '';
return str.charAt(0).toUpperCase() + str.slice(1);
}
}
/**
* Create SDK generator instance
*/
export function createSDKGenerator() {
return new SDKGenerator();
}
//# sourceMappingURL=sdk-generator.js.map