nestjs-protobuf-es
Version:
Protobuf-ES integration for NestJS
138 lines (137 loc) • 5.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.protocGenNestjs = void 0;
const protoplugin_1 = require("@bufbuild/protoplugin");
const package_json_1 = require("../package.json");
exports.protocGenNestjs = (0, protoplugin_1.createEcmaScriptPlugin)({
name: 'protoc-gen-nestjs',
version: `v${package_json_1.version}`,
parseOptions,
generateTs,
});
function parseOptions(options) {
const result = {
validTypes: false,
jsDates: false,
strictInit: false,
exportFile: false,
};
for (const { key } of options) {
if (key === 'valid_types') {
result.validTypes = true;
}
else if (key === 'js_dates') {
result.jsDates = true;
}
else if (key === 'strict_init') {
result.strictInit = true;
}
else if (key === 'export_file') {
result.exportFile = true;
}
}
return result;
}
function generateTs(schema) {
for (const file of schema.files) {
const f = schema.generateFile(`${file.name}_nestjs.ts`);
f.preamble(file);
for (const service of file.services) {
printService(f, service, schema.options);
}
if (schema.options.exportFile) {
const exportFile = schema.generateFile(`${file.name}.ts`);
// biome-ignore lint/style/noNonNullAssertion: `paths` has at least 1 element
const importName = file.name.split('/').at(-1);
exportFile.print `export * from "./${importName}_pb";`;
if (file.services.length !== 0) {
exportFile.print `export * from "./${importName}_nestjs";`;
}
}
}
}
function printService(f, service, options) {
printServiceInterface(f, service, options, true);
f.print();
printServiceInterface(f, service, options, false);
f.print();
printServiceDecorator(f, service);
}
function printServiceInterface(f, service, options, isClient) {
f.print(f.jsDoc(service));
f.print `export interface ${service.name}${isClient ? 'Client' : 'Controller'} {`;
service.methods.forEach((method, index) => {
if (index !== 0) {
f.print();
}
printServiceMethod(f, method, options, isClient);
});
f.print `}`;
}
function printServiceMethod(f, method, options, isClient) {
const Observable = f.import('Observable', 'rxjs', true);
const Metadata = f.import('Metadata', '@grpc/grpc-js', true);
const PopulatedMessage = f.import('PopulatedMessage', 'nestjs-protobuf-es', true);
const MessageInit = f.import(`${options.strictInit ? 'Strict' : ''}MessageInit`, 'nestjs-protobuf-es', true);
const ReqType = options.validTypes && !isKnownType(method.input)
? f.importValid(method.input)
: f.importShape(method.input);
const ResType = options.validTypes && !isKnownType(method.output)
? f.importValid(method.output)
: f.importShape(method.output);
const isStreamReq = method.methodKind === 'client_streaming' || method.methodKind === 'bidi_streaming';
const isStreamRes = method.methodKind === 'server_streaming' || method.methodKind === 'bidi_streaming';
f.print(f.jsDoc(method, ' '));
if (isClient) {
const innerReq = [MessageInit, '<', ReqType, '>'];
const req = isStreamReq ? [Observable, '<', ...innerReq, '>'] : innerReq;
const innerRes = options.jsDates && canContainTimestamp(method.output)
? [PopulatedMessage, '<', ResType, ', { jsDates: true }>']
: [ResType];
const res = [Observable, '<', ...innerRes, '>'];
f.print ` ${method.localName}(request${isEmpty(method.input) ? '?' : ''}: ${req}, metadata?: ${Metadata}): ${res};`;
}
else {
const innerReq = options.jsDates && canContainTimestamp(method.input)
? [PopulatedMessage, '<', ReqType, ', { jsDates: true }>']
: [ReqType];
const req = isStreamReq ? [Observable, '<', ...innerReq, '>'] : innerReq;
const innerRes = isEmpty(method.output) ? ['void'] : [MessageInit, '<', ResType, '>'];
const res = isStreamRes
? [Observable, '<', ...innerRes, '>']
: [...innerRes, ' | Promise<', ...innerRes, '>'];
f.print ` ${method.localName}(request: ${req}, metadata: ${Metadata}, ...rest: unknown[]): ${res};`;
}
}
function printServiceDecorator(f, service) {
const GrpcMethod = f.import('GrpcMethod', '@nestjs/microservices');
const GrpcStreamMethod = f.import('GrpcStreamMethod', '@nestjs/microservices');
const methods = service.methods
.map((method) => {
const isStreamReq = method.methodKind === 'client_streaming' || method.methodKind === 'bidi_streaming';
return `["${method.localName}", ${+isStreamReq}]`;
})
.join(', ');
f.print `${f.export('function', `${service.name}Methods`)}() {`;
f.print ` return function (constructor: Function) {`;
f.print ` const methods: [string, number][] = [${methods}];`;
f.print ` for (const [method, isStream] of methods) {`;
f.print ` const descriptor = Reflect.getOwnPropertyDescriptor(constructor.prototype, method)!;`;
f.print ` (isStream ? ${GrpcStreamMethod} : ${GrpcMethod})("${service.name}", method)(`;
f.print ` constructor.prototype[method],`;
f.print ` method,`;
f.print ` descriptor,`;
f.print ` );`;
f.print ` }`;
f.print ` };`;
f.print `}`;
}
function canContainTimestamp(message) {
return !isKnownType(message) || message.typeName.slice(16) === 'Timestamp';
}
function isEmpty(message) {
return message.typeName === 'google.protobuf.Empty';
}
function isKnownType(message) {
return message.typeName.startsWith('google.protobuf.');
}