UNPKG

pg-proto-parser

Version:
309 lines (308 loc) 13.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProtoStore = void 0; const protobufjs_1 = require("@launchql/protobufjs"); const ast_1 = require("./ast"); const runtime_schema_1 = require("./runtime-schema"); const enums_json_1 = require("./ast/enums/enums-json"); const strfy_js_1 = require("strfy-js"); const fs_1 = require("fs"); const path_1 = require("path"); const options_1 = require("./options"); const utils_1 = require("./utils"); const inline_helpers_1 = require("./inline-helpers"); const constants_1 = require("./constants"); class ProtoStore { options; root; services; types; fields; enums; namespaces; _runtimeSchema; constructor(root, options = {}) { this.options = (0, options_1.getOptionsWithDefaults)(options); this.root = root; this.services = []; this.types = []; this.fields = []; this.enums = []; this.namespaces = []; this._parse(this.root); } _parse(node, name = '') { if (node instanceof protobufjs_1.Service) { this.services.push((0, utils_1.cloneAndNameNode)(node, name)); } else if (node instanceof protobufjs_1.Type) { this.types.push((0, utils_1.cloneAndNameNode)(node, name)); node.fieldsArray.forEach(field => this.fields.push(field)); } else if (node instanceof protobufjs_1.Enum) { this.enums.push((0, utils_1.cloneAndNameNode)(this._processEnum(node), name)); } if (node instanceof protobufjs_1.Namespace) { this.namespaces.push(node); Object.entries(node.nested || {}).forEach(([key, child]) => { this._parse(child, key); }); } } _processEnum(enumNode) { const undefinedKey = (0, utils_1.getUndefinedKey)(enumNode.name); const clone = (0, utils_1.cloneAndNameNode)(enumNode, enumNode.name); if (!this.options.enums.removeUndefinedAt0 || !(0, utils_1.hasUndefinedInitialValue)(enumNode)) { return clone; } const newValues = {}; let decrement = 0; for (const [key, value] of Object.entries(enumNode.values)) { if (key === undefinedKey && value === 0) { decrement = 1; continue; } newValues[key] = value - decrement; } clone.values = newValues; return clone; } write() { // Ensure the output directory exists (0, fs_1.mkdirSync)(this.options.outDir, { recursive: true }); this.writeEnumMaps(); this.writeTypes(); this.writeEnums(); this.writeUtilsEnums(); this.writeAstHelpers(); this.writeWrappedAstHelpers(); this.writeRuntimeSchema(); } writeEnumMaps() { if (!this.options.enums.enumMap?.enabled) { return; } const enumsToProcess = this.enumsToProcess(); const enums2int = (0, enums_json_1.generateEnum2IntJSON)(enumsToProcess); const enums2str = (0, enums_json_1.generateEnum2StrJSON)(enumsToProcess); const format = this.options.enums.enumMap.format || 'json'; if (format === 'json') { // Write plain JSON files if (this.options.enums.enumMap.toIntOutFile) { const filename = this.ensureCorrectExtension(this.options.enums.enumMap.toIntOutFile, '.json'); this.writeFile(filename, JSON.stringify(enums2int, null, 2)); } if (this.options.enums.enumMap.toStrOutFile) { const filename = this.ensureCorrectExtension(this.options.enums.enumMap.toStrOutFile, '.json'); this.writeFile(filename, JSON.stringify(enums2str, null, 2)); } } else if (format === 'ts') { // Write TypeScript files with exports if (this.options.enums.enumMap.toIntOutFile) { const tsContent = this.generateEnumMapTypeScript(enums2int, 'enumToIntMap', 'EnumToIntMap'); const filename = this.ensureCorrectExtension(this.options.enums.enumMap.toIntOutFile, '.ts'); this.writeFile(filename, tsContent); } if (this.options.enums.enumMap.toStrOutFile) { const tsContent = this.generateEnumMapTypeScript(enums2str, 'enumToStrMap', 'EnumToStrMap'); const filename = this.ensureCorrectExtension(this.options.enums.enumMap.toStrOutFile, '.ts'); this.writeFile(filename, tsContent); } } } allTypesExceptNode() { return this.types.filter(type => type.name !== constants_1.NODE_TYPE); } typesToProcess() { return this.types .filter(type => type.name !== constants_1.NODE_TYPE) .filter(type => !this.options.exclude.includes(type.name)); } enumsToProcess() { return this.enums .filter(enm => !this.options.exclude.includes(enm.name)); } writeTypes() { if (this.options.types.enabled) { const typesToProcess = this.typesToProcess(); const enumsToProcess = this.enumsToProcess(); const node = (0, ast_1.generateNodeUnionType)(this.options, typesToProcess); const enumImports = (0, ast_1.generateEnumImports)(enumsToProcess, this.options.types.enumsSource); const types = typesToProcess.reduce((m, type) => { return [...m, (0, ast_1.convertTypeToTsInterface)(type, this.options)]; }, []); const filename = this.ensureCorrectExtension(this.options.types.filename, '.ts'); this.writeCodeToFile(filename, [ enumImports, node, ...types ]); } } writeEnums() { if (this.options.enums.enabled) { const filename = this.ensureCorrectExtension(this.options.enums.filename, '.ts'); this.writeCodeToFile(filename, this.enumsToProcess().map(enm => this.options.enums.enumsAsTypeUnion ? (0, ast_1.convertEnumToTsUnionType)(enm) : (0, ast_1.convertEnumToTsEnumDeclaration)(enm))); } } writeUtilsEnums() { if (this.options.utils.enums.enabled) { const enumsToProcess = this.enumsToProcess(); const useNestedObjects = this.options.utils.enums.outputFormat === 'nestedObjects'; if (this.options.utils.enums.unidirectional) { // Generate separate unidirectional functions const toIntGenerator = useNestedObjects ? ast_1.generateEnumToIntFunctionsNested : ast_1.generateEnumToIntFunctions; const toStringGenerator = useNestedObjects ? ast_1.generateEnumToStringFunctionsNested : ast_1.generateEnumToStringFunctions; const toIntCode = (0, utils_1.convertAstToCode)(toIntGenerator(enumsToProcess)); const toIntFilename = this.ensureCorrectExtension(this.options.utils.enums.toIntFilename, '.ts'); this.writeFile(toIntFilename, toIntCode); const toStringCode = (0, utils_1.convertAstToCode)(toStringGenerator(enumsToProcess)); const toStringFilename = this.ensureCorrectExtension(this.options.utils.enums.toStringFilename, '.ts'); this.writeFile(toStringFilename, toStringCode); } else { // Generate bidirectional function (original behavior) // Note: Nested objects format only supported for unidirectional functions const code = (0, utils_1.convertAstToCode)((0, ast_1.generateEnumValueFunctions)(enumsToProcess)); const filename = this.ensureCorrectExtension(this.options.utils.enums.filename, '.ts'); this.writeFile(filename, code); } } } writeAstHelpers() { if (this.options.utils.astHelpers.enabled) { const imports = this.options.utils.astHelpers.inlineNestedObj ? (0, utils_1.createDefaultImport)('_o', './' + (0, utils_1.stripExtension)(this.options.utils.astHelpers.nestedObjFile)) : (0, utils_1.createDefaultImport)('_o', 'nested-obj'); const typesToProcess = this.typesToProcess(); const code = (0, utils_1.convertAstToCode)([ imports, (0, ast_1.generateTypeImportSpecifiers)(typesToProcess, this.options), (0, ast_1.generateAstHelperMethods)(typesToProcess) ]); const filename = this.ensureCorrectExtension(this.options.utils.astHelpers.filename, '.ts'); this.writeFile(filename, code); if (this.options.utils.astHelpers.inlineNestedObj) { const nestedObjFilename = this.ensureCorrectExtension(this.options.utils.astHelpers.nestedObjFile, '.ts'); this.writeFile(nestedObjFilename, inline_helpers_1.nestedObjCode); } } } writeWrappedAstHelpers() { if (this.options.utils.wrappedAstHelpers?.enabled) { const imports = (0, utils_1.createDefaultImport)('_o', 'nested-obj'); const typesToProcess = this.typesToProcess(); const code = (0, utils_1.convertAstToCode)([ imports, (0, ast_1.generateTypeImportSpecifiers)(typesToProcess, this.options), (0, ast_1.generateWrappedAstHelperMethods)(typesToProcess) ]); const filename = this.ensureCorrectExtension(this.options.utils.wrappedAstHelpers.filename, '.ts'); this.writeFile(filename, code); } } writeRuntimeSchema() { if (!this.options.runtimeSchema?.enabled) { return; } const generator = new runtime_schema_1.RuntimeSchemaGenerator(this.root); const nodeSpecs = generator.generateNodeSpecs(); const format = this.options.runtimeSchema.format || 'json'; const filename = this.options.runtimeSchema.filename || 'runtime-schema'; if (format === 'json') { const jsonContent = JSON.stringify(nodeSpecs, null, 2); const correctedFilename = this.ensureCorrectExtension(filename, '.json'); const outFile = (0, path_1.join)(this.options.outDir, correctedFilename); (0, utils_1.writeFileToDisk)(outFile, jsonContent, this.options); } else if (format === 'typescript') { const tsContent = this.generateRuntimeSchemaTypeScript(nodeSpecs); const correctedFilename = this.ensureCorrectExtension(filename, '.ts'); const outFile = (0, path_1.join)(this.options.outDir, correctedFilename); (0, utils_1.writeFileToDisk)(outFile, tsContent, this.options); } } getRuntimeSchema() { if (!this._runtimeSchema) { const generator = new runtime_schema_1.RuntimeSchemaGenerator(this.root); this._runtimeSchema = generator.generateNodeSpecs(); } return this._runtimeSchema; } generateRuntimeSchemaTypeScript(nodeSpecs) { const interfaceDefinitions = [ 'export interface FieldSpec {', ' name: string;', ' type: string;', ' isArray: boolean;', ' optional: boolean;', '}', '', 'export interface NodeSpec {', ' name: string;', ' isNode: boolean;', ' fields: FieldSpec[];', '}', '' ]; const exportStatement = `export const runtimeSchema: NodeSpec[] = ${(0, strfy_js_1.jsStringify)(nodeSpecs, { space: 2, camelCase: true, quotes: 'single' })};`; return [ ...interfaceDefinitions, exportStatement ].filter(Boolean).join('\n'); } generateEnumMapTypeScript(enumMap, varName, typeName) { const exportStatement = `export const ${varName} = ${(0, strfy_js_1.jsStringify)(enumMap, { space: 2, camelCase: false, // Preserve enum casing quotes: 'single' })};`; const typeStatement = `export type ${typeName} = typeof ${varName};`; return [ exportStatement, '', typeStatement ].filter(Boolean).join('\n'); } ensureCorrectExtension(filename, expectedExt) { if (!filename || !expectedExt) { return filename || ''; } // Ensure expectedExt starts with a dot if (!expectedExt.startsWith('.')) { expectedExt = '.' + expectedExt; } const extMatch = filename.match(/(\.[^./\\]+)+$/); const currentExt = extMatch ? extMatch[0] : ''; if (currentExt && currentExt !== expectedExt) { // Replace the current extension with the expected one return filename.slice(0, -currentExt.length) + expectedExt; } else if (!currentExt) { // No extension, add the expected one return filename + expectedExt; } // Extension is already correct return filename; } writeFile(filePath, content) { const fullPath = (0, path_1.join)(this.options.outDir, filePath); const dir = (0, path_1.dirname)(fullPath); (0, fs_1.mkdirSync)(dir, { recursive: true }); (0, utils_1.writeFileToDisk)(fullPath, content, this.options); } writeCodeToFile(filename, nodes) { const code = (0, utils_1.convertAstToCode)(nodes); const correctedFilename = this.ensureCorrectExtension(filename, '.ts'); const filePath = (0, path_1.join)(this.options.outDir, correctedFilename); (0, utils_1.writeFileToDisk)(filePath, code, this.options); } } exports.ProtoStore = ProtoStore;