UNPKG

nestjs-protobuf-es

Version:

Protobuf-ES integration for NestJS

138 lines (137 loc) 5.85 kB
"use strict"; 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.'); }