{"id":49838502,"url":"https://github.com/pseudomuto/proto-parser","last_synced_at":"2026-05-14T02:31:49.447Z","repository":{"id":324386410,"uuid":"1097050722","full_name":"pseudomuto/proto-parser","owner":"pseudomuto","description":"A TypeScript library for parsing Protocol Buffer (.proto) files, extracting messages, services, enums, and other definitions from both file paths and proto content strings.","archived":false,"fork":false,"pushed_at":"2025-11-27T21:33:14.000Z","size":491,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-30T10:45:40.923Z","etag":null,"topics":["grpc","parser","protobuf","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pseudomuto.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-15T12:55:19.000Z","updated_at":"2025-11-27T21:33:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pseudomuto/proto-parser","commit_stats":null,"previous_names":["pseudomuto/proto-parser"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/pseudomuto/proto-parser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pseudomuto%2Fproto-parser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pseudomuto%2Fproto-parser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pseudomuto%2Fproto-parser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pseudomuto%2Fproto-parser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pseudomuto","download_url":"https://codeload.github.com/pseudomuto/proto-parser/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pseudomuto%2Fproto-parser/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33008049,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["grpc","parser","protobuf","typescript"],"created_at":"2026-05-14T02:31:48.710Z","updated_at":"2026-05-14T02:31:49.440Z","avatar_url":"https://github.com/pseudomuto.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @pseudomutojs/proto-parser\n\n[![NPM Version](https://img.shields.io/npm/v/@pseudomutojs/proto-parser.svg)](https://www.npmjs.com/package/@pseudomutojs/proto-parser)\n[![License](https://img.shields.io/npm/l/@pseudomutojs/proto-parser.svg)](https://github.com/pseudomuto/proto-parser/blob/main/LICENSE)\n[![Build Status](https://img.shields.io/github/actions/workflow/status/pseudomuto/proto-parser/ci.yml?branch=main)](https://github.com/pseudomuto/proto-parser/actions)\n[![Coverage](https://codecov.io/gh/pseudomuto/proto-parser/branch/main/graph/badge.svg)](https://codecov.io/gh/pseudomuto/proto-parser)\n\nA TypeScript library for parsing Protocol Buffer (.proto) files and generating unified IDL. Extract messages, services, enums, and other definitions from both file paths and proto content strings, with the ability to merge multiple proto files into a single IDL.\n\n## Features\n\n- 🔍 **Parse from files or strings** - Load proto definitions from file paths or raw content\n- 📁 **Directory parsing** - Parse all .proto files in a directory recursively\n- 🔄 **Promise-based async API** - Modern async/await patterns throughout\n- 🎯 **Complete parsing** - Extract messages, services, enums, oneofs, extensions, and nested structures\n- 📦 **Import resolution** - Automatically resolve imports including Google Well-Known Types\n- 🔧 **Customizable import resolution** - Implement custom logic for resolving imports (caching, remote files, custom file systems)\n- 🛡️ **Type-safe** - Full TypeScript support with comprehensive type definitions\n- 📚 **ProtoSet collections** - Manage and query multiple proto files as a unified set\n- ✨ **IDL Generation** - Generate unified proto IDL from multiple proto files with smart conflict resolution\n- 🔧 **Customizable output** - Control syntax version, package naming, and comment inclusion in generated IDL\n- ⚡ **Only 1 dependency** - Built on `protobufjs`\n\n### Supported Features\n\n- ✅ Protocol Buffer syntax v2 and v3\n- ✅ Messages with all field types\n- ✅ Services with streaming methods\n- ✅ Enumerations\n- ✅ Nested messages and enums\n- ✅ OneOf fields\n- ✅ Extensions\n- ✅ Import statements\n- ✅ Google Well-Known Types (WKT)\n- ✅ Custom options\n- ✅ Package namespaces\n\n## Installation\n\n```bash\nnpm install @pseudomutojs/proto-parser\n```\n\n## Version Notes\n\n**v0.1.0+**: This library provides an async-only API. All parsing operations return Promises and should be used with `await` or `.then()`. Synchronous parsing methods are not available to ensure optimal performance with I/O operations and import resolution.\n\n## Architecture\n\nThis library uses an **interface-driven architecture** that enables flexible customization while maintaining strong type safety. The core parsing logic is built around three key interfaces:\n\n- **`IImportProcessor`**: Handles resolving import paths, supporting custom logic for different environments (local files, remote sources, caching, etc.)\n- **`IProtoParser`**: Converts protobufjs objects to the library's internal types, enabling custom transformations and metadata extraction\n- **`ModuleProvider`**: Provides external proto module dependencies with automatic lifecycle management (downloading, extraction, cleanup)\n\nAll interfaces have default implementations (`ImportProcessor`, `ProtoParser`, `BufModuleProvider`) that can be used as-is or extended for custom behavior. This design allows the library to adapt to different deployment scenarios while maintaining consistent parsing behavior.\n\n## Quick Start\n\n### Parse from File\n\n```typescript\nimport { parseProto } from '@pseudomutojs/proto-parser';\n\nconst proto = await parseProto('./path/to/your/file.proto');\n\nconsole.log('Services:', proto.services);\nconsole.log('Messages:', proto.messages);\nconsole.log('Enums:', proto.enums);\n```\n\n### Parse from String Content\n\n```typescript\nimport { parseProto } from '@pseudomutojs/proto-parser';\n\nconst protoContent = `\n  syntax = \"proto3\";\n  \n  package example;\n  \n  message User {\n    int32 id = 1;\n    string name = 2;\n    string email = 3;\n  }\n  \n  service UserService {\n    rpc GetUser(GetUserRequest) returns (User);\n  }\n`;\n\nconst proto = await parseProto(protoContent);\n```\n\n\n### Parse Directory\n\n```typescript\nimport { parseProtoDirectory } from '@pseudomutojs/proto-parser';\n\n// Parse all .proto files in a directory\nconst protoSet = await parseProtoDirectory('./protos');\n\nconsole.log(`Parsed ${protoSet.size()} files`);\nconsole.log('All messages:', protoSet.getAllMessages());\nconsole.log('All services:', protoSet.getAllServices());\n```\n\n### Create ProtoSet from Multiple Sources\n\n```typescript\nimport { ProtoSet } from '@pseudomutojs/proto-parser';\n\n// Mix file paths and literal content\nconst protoSet = await ProtoSet.from(\n  './user.proto',\n  './service.proto',\n  `syntax = \"proto3\";\n   message Test { string id = 1; }`\n);\n\n// Access all definitions\nconst messages = protoSet.getAllMessages();\nconst services = protoSet.getAllServices();\n```\n\n### Generate Unified IDL\n\n```typescript\nimport { parseProtoDirectory } from '@pseudomutojs/proto-parser';\n\n// Parse multiple proto files from a directory\nconst protoSet = await parseProtoDirectory('./api/protos');\n\n// Generate a unified proto IDL containing all definitions\nconst unifiedIdl = protoSet.generateSupersetIdl({\n  syntax: 'proto3',\n  packageName: 'unified.api',\n  includeComments: true\n});\n\nconsole.log(unifiedIdl);\n/* \nOutput: A complete proto file with:\n- All unique imports\n- All messages from all files\n- All services from all files  \n- All enums from all files\n- Proper namespace conflict resolution\n*/\n```\n\n## API Reference\n\n### Main Functions\n\n#### `parseProto(input, options?)`\n\nAsynchronously parses a Protocol Buffer file or content string.\n\n**Parameters:**\n\n- `input` (string) - Either a file path to a .proto file or proto content string\n- `options` (ParseOptions, optional) - Parsing configuration options\n\n**Returns:** `Promise\u003cProto\u003e` - A promise that resolves to a Proto object containing all parsed definitions\n\n\n#### `parseProtoDirectory(dirPath, options?)`\n\nAsynchronously parses all Protocol Buffer files in a directory.\n\n**Parameters:**\n\n- `dirPath` (string) - Path to the directory containing .proto files\n- `options` (DirectoryParseOptions, optional) - Directory parsing configuration options\n\n**Returns:** `Promise\u003cProtoSet\u003e` - A promise that resolves to a ProtoSet containing all parsed proto files\n\n#### `ImportProcessor` Class\n\nThe default implementation of the `IImportProcessor` interface, providing standard import resolution logic.\n\n**Constructor:**\n```typescript\nnew ImportProcessor(baseDir: string, fileSystem: FileSystem, options?: ParseOptions)\n```\n\n**Usage:**\n```typescript\nimport { ImportProcessor, DefaultFileSystem } from '@pseudomutojs/proto-parser';\n\nconst fileSystem = new DefaultFileSystem();\nconst resolver = new ImportProcessor('/base/directory', fileSystem, {\n  includePaths: ['./protos', './third_party']\n});\n\n// Extend for custom behavior\nclass CustomResolver extends ImportProcessor {\n  async resolveImport(importPath: string): Promise\u003cstring | null\u003e {\n    // Custom logic\n    return super.resolveImport(importPath);\n  }\n}\n```\n\n#### `ProtoParser` Class\n\nThe default implementation of the `IProtoParser` interface, handling conversion from protobufjs objects to internal types.\n\n**Usage:**\n```typescript\nimport { ProtoParser } from '@pseudomutojs/proto-parser';\n\n// Extend for custom processing\nclass CustomProcessor extends ProtoParser {\n  parseMessage(messageType: any, namespace: string) {\n    const result = super.parseMessage(messageType, namespace);\n    // Add custom processing\n    return result;\n  }\n}\n```\n\n#### `DefaultFileSystem` Class\n\nThe default implementation of the `FileSystem` interface, providing standard file system operations. This class is used internally by the library and can be extended or replaced with custom implementations for specialized file access patterns (e.g., virtual file systems, in-memory files, or remote file access).\n\n**Constructor:**\n```typescript\nnew DefaultFileSystem()\n```\n\n**Usage:**\n```typescript\nimport { DefaultFileSystem } from '@pseudomutojs/proto-parser';\n\n// Use the default file system implementation\nconst fs = new DefaultFileSystem();\n\n// Check if a file exists\nconst exists = await fs.exists('/path/to/file.proto');\n\n// Read file content\nconst content = await fs.readFile('/path/to/file.proto');\n\n// Check if a path is a file\nconst isFile = await fs.isFile('/path/to/file.proto');\n\n// Get absolute path\nconst absPath = await fs.resolve('./relative/path.proto');\n```\n\n**Methods:**\n- `exists(filePath: string): Promise\u003cboolean\u003e` - Check if a file exists\n- `readFile(filePath: string): Promise\u003cstring\u003e` - Read file content as UTF-8 string\n- `isFile(filePath: string): Promise\u003cboolean\u003e` - Check if path points to a file (not directory)\n- `resolve(...paths: string[]): Promise\u003cstring\u003e` - Resolve to absolute path\n\n#### `BufModuleProvider` Class\n\nThe BufModuleProvider downloads complete Buf Schema Registry modules as tar.gz archives and extracts them to temporary directories. This provides superior performance and fidelity compared to individual file requests.\n\n**Constructor:**\n```typescript\nnew BufModuleProvider(modules: string[], options?: BufModuleProviderOptions)\n```\n\n**Usage:**\n```typescript\nimport { BufModuleProvider, parseProto } from '@pseudomutojs/proto-parser';\n\n// Create module provider with modules to preload\nconst bufModuleProvider = new BufModuleProvider([\n  'buf.build/bufbuild/protovalidate:v1.0.0',\n  'buf.build/googleapis/googleapis'\n], {\n  includeWKTs: true, // Automatically includes protocolbuffers/wellknowntypes (default)\n  bufToken: process.env.BUF_TOKEN // Optional: for private modules\n});\n\n// Parse proto files with automatic module provider integration\nconst proto = await parseProto('./api.proto', { \n  moduleProviders: [bufModuleProvider] \n});\n\n// The parser automatically handles cleanup - no manual disposal needed!\n```\n\n**Key advantages:**\n- Downloads original proto files (preserves comments and formatting)\n- Single request per module vs. multiple file requests\n- Automatic dependency resolution with complete module archives\n- Automatic lifecycle management (no manual cleanup required)\n- Seamless integration with parsing pipeline via `moduleProviders` option\n\n**Options:**\n```typescript\ninterface BufModuleProviderOptions {\n  /** Optional Buf API token for authenticated requests to private modules */\n  bufToken?: string;\n  /** Base directory for temporary files (defaults to OS temp directory) */\n  tempDir?: string;\n  /** FileSystem implementation to use (defaults to DefaultFileSystem) */\n  fileSystem?: FileSystem;\n  /** Whether to include dependencies when downloading modules (defaults to true) */\n  includeDependencies?: boolean;\n  /** Whether to automatically include Google Protocol Buffer well-known types (defaults to true) */\n  includeWKTs?: boolean;\n}\n```\n\n#### `createDefaultParseOptions(baseDir, options?)`\n\nHelper function to create fully resolved ParseOptions with defaults populated.\n\n**Parameters:**\n- `baseDir` (string) - Base directory for import resolution\n- `options` (ParseOptions, optional) - Partial options to merge with defaults\n\n**Returns:** `ResolvedParseOptions` - Complete options with all fields populated\n\n**Usage:**\n```typescript\nimport { createDefaultParseOptions } from '@pseudomutojs/proto-parser';\n\nconst resolvedOptions = createDefaultParseOptions('/base/dir', {\n  includePaths: ['./protos'],\n  importResolver: new CustomImportProcessor()\n});\n```\n\n\n### ProtoSet Class\n\nA collection of parsed Protocol Buffer files with methods to query and aggregate definitions.\n\n#### Static Methods\n\n##### `ProtoSet.from(...inputs)`\n\nCreates a ProtoSet from multiple file paths and/or proto content strings.\n\n```typescript\nconst protoSet = await ProtoSet.from(\n  './user.proto',\n  'syntax = \"proto3\"; message Test { string id = 1; }',\n  { keepCase: false } // optional ParseOptions\n);\n```\n\n#### Instance Methods\n\n- `getProtos()` - Returns all Proto objects in the set\n- `getProtoByFile(filename)` - Find a proto by its filename\n- `getAllMessages()` - Get all messages from all protos (including nested)\n- `getAllServices()` - Get all services from all protos\n- `getAllEnums()` - Get all enums from all protos (including nested)\n- `getAllImports()` - Get unique imports across all protos\n- `generateSupersetIdl(options?)` - Generate unified proto IDL from all files in the set\n- `size()` - Returns the number of proto files in the set\n- `isEmpty()` - Check if the set is empty\n- `getStats()` - Get statistics about the proto set\n\n##### `generateSupersetIdl(options?)`\n\nGenerates a unified Protocol Buffer IDL file containing all definitions from the ProtoSet.\n\n**Parameters:**\n- `options` (SupersetOptions, optional) - Configuration options for IDL generation\n\n**Returns:** `string` - A complete proto IDL string\n\n**Example:**\n```typescript\nconst protoSet = await parseProtoDirectory('./api/protos');\n\n// Generate with default options (proto3, with comments)\nconst basicIdl = protoSet.generateSupersetIdl();\n\n// Generate with custom options\nconst customIdl = protoSet.generateSupersetIdl({\n  syntax: 'proto3',\n  packageName: 'unified.api.v1',\n  includeComments: true,\n  namespaceConflictResolution: 'prefix'\n});\n\n// Generate with relative paths and local files only\nconst localIdl = protoSet.generateSupersetIdl({\n  baseDir: path.resolve('./'),\n  includeLocalOnly: true  // Excludes google.protobuf.*, buf.validate.*, etc.\n});\n\nconsole.log(customIdl);\n// Output: Complete proto file with all messages, services, enums, and imports\n// Comments show relative paths like: // From: api/v1/user.proto\n```\n\n### Configuration Options\n\n```typescript\ninterface ParseOptions {\n  /** Additional directories to search for imported proto files */\n  includePaths?: string[];\n  /** Whether to preserve field name casing (default: true) - when true, preserves snake_case; when false, converts to camelCase */\n  keepCase?: boolean;\n  /** Whether to include default values (default: true) */\n  defaults?: boolean;\n  /** Whether to include oneof definitions (default: true) */\n  oneofs?: boolean;\n  /** Custom content processor for converting protobufjs objects to internal types */\n  contentProcessor?: IProtoParser;\n  /** Custom import resolver for resolving proto import paths */\n  importResolver?: IImportProcessor;\n  /** Module providers for external proto dependencies with automatic lifecycle management */\n  moduleProviders?: ModuleProvider[];\n}\n\ninterface DirectoryParseOptions extends ParseOptions {\n  /** Whether to recursively search subdirectories for .proto files (default: true) */\n  recursive?: boolean;\n}\n\ninterface SupersetOptions {\n  /** The proto syntax version to use in generated IDL (default: 'proto3') */\n  syntax?: 'proto2' | 'proto3';\n  /** The package name for the generated proto file */\n  packageName?: string;\n  /** Whether to include comments indicating source files and section headers (default: true) */\n  includeComments?: boolean;\n  /** \n   * How to handle namespace conflicts when merging definitions (default: 'prefix')\n   * - 'prefix': Adds namespace prefix or numeric suffix to conflicting names\n   * - 'ignore': Keeps original names, may result in duplicates\n   */\n  namespaceConflictResolution?: 'prefix' | 'ignore';\n  /** Base directory for calculating relative paths in comments (default: undefined, uses filenames only) */\n  baseDir?: string;\n  /** Whether to include only local protos (default: true) - excludes external libraries like google.protobuf.*, buf.validate.*, etc. */\n  includeLocalOnly?: boolean;\n}\n\n```\n\n### Type Definitions\n\n#### Proto\n\nThe main result object containing all parsed definitions:\n\n```typescript\ntype Proto = {\n  /** The filename of the proto file */\n  file: string;\n  /** The full path to the proto file */\n  path: string;\n  /** The raw IDL content of the proto file */\n  idl: string;\n  /** Array of service definitions found in the proto file */\n  services?: Service[];\n  /** Array of message definitions found in the proto file */\n  messages?: Message[];\n  /** Array of enum definitions found in the proto file */\n  enums?: Enum[];\n  /** Array of import statements found in the proto file */\n  imports?: string[];\n};\n```\n\n#### Service\n\ngRPC service definition:\n\n```typescript\ntype Service = {\n  /** The name of the service */\n  name: string;\n  /** The namespace/package the service belongs to */\n  namespace: string;\n  /** Array of methods defined in this service */\n  methods?: ServiceMethod[];\n};\n```\n\n#### Message\n\nProtocol Buffer message definition:\n\n```typescript\ntype Message = {\n  /** The name of the message */\n  name: string;\n  /** The namespace/package the message belongs to */\n  namespace: string;\n  /** Array of fields defined in this message */\n  fields?: Field[];\n  /** Array of nested message definitions */\n  nestedMessages?: Message[];\n  /** Array of nested enum definitions */\n  nestedEnums?: Enum[];\n  /** Array of oneof field groups */\n  oneofs?: OneOf[];\n  /** Array of extensions defined for this message */\n  extensions?: Extension[];\n  /** Message-specific options */\n  options?: Options;\n};\n```\n\nFor complete type definitions, see the [TypeScript definitions](./src/types.ts).\n\n## Advanced Usage\n\n### Custom Import Resolution\n\nThe library supports custom import resolution logic through the `IImportProcessor` interface. This enables powerful customization for different environments and use cases.\n\n#### Caching Import Resolver\n\nImplement caching to improve performance when parsing multiple files that share imports:\n\n```typescript\nimport { ImportProcessor, DefaultFileSystem, parseProto } from '@pseudomutojs/proto-parser';\n\nclass CachingImportProcessor extends ImportProcessor {\n  private cache = new Map\u003cstring, string | null\u003e();\n\n  async resolveImport(importPath: string): Promise\u003cstring | null\u003e {\n    if (this.cache.has(importPath)) {\n      return this.cache.get(importPath)!;\n    }\n\n    const result = await super.resolveImport(importPath);\n    this.cache.set(importPath, result);\n    return result;\n  }\n}\n\n// Use the caching resolver\nconst fileSystem = new DefaultFileSystem();\nconst proto = await parseProto('./api.proto', {\n  importResolver: new CachingImportProcessor('/base/dir', fileSystem, { includePaths: ['./protos'] })\n});\n```\n\n#### Remote Import Resolver\n\nFetch imports from remote sources like GitHub or package registries:\n\n```typescript\nimport { IImportProcessor, parseProto } from '@pseudomutojs/proto-parser';\n\nclass RemoteImportProcessor implements IImportProcessor {\n  constructor(private baseUrl: string) {}\n\n  async resolveImport(importPath: string): Promise\u003cstring | null\u003e {\n    // Handle Well-Known Types locally\n    if (importPath.startsWith('google/protobuf/')) {\n      return importPath; // Let protobufjs handle WKTs\n    }\n\n    try {\n      const response = await fetch(`${this.baseUrl}/${importPath}`);\n      if (response.ok) {\n        // Return path to temporary file or cache\n        const content = await response.text();\n        return this.saveTempFile(importPath, content);\n      }\n    } catch (error) {\n      console.warn(`Failed to fetch remote import: ${importPath}`);\n    }\n    \n    return null;\n  }\n\n  async validateImports(imports: string[]): Promise\u003cvoid\u003e {\n    // Pre-validate that remote imports are accessible\n    for (const importPath of imports) {\n      if (!importPath.startsWith('google/protobuf/')) {\n        const resolved = await this.resolveImport(importPath);\n        if (!resolved) {\n          throw new Error(`Cannot resolve remote import: ${importPath}`);\n        }\n      }\n    }\n  }\n\n  createProtobufResolver() {\n    return (origin: string, target: string) =\u003e {\n      // Synchronous resolution - assumes imports were pre-cached by validateImports\n      return this.getCachedPath(target) || target;\n    };\n  }\n\n  private async saveTempFile(importPath: string, content: string): Promise\u003cstring\u003e {\n    // Implementation to save to temp file and return path\n    // ...\n  }\n\n  private getCachedPath(importPath: string): string | null {\n    // Implementation to get cached file path\n    // ...\n  }\n}\n\n// Use remote resolver\nconst proto = await parseProto('./api.proto', {\n  importResolver: new RemoteImportProcessor('https://raw.githubusercontent.com/user/protos/main')\n});\n```\n\n\n#### Multi-Source Import Resolver\n\nCombine multiple resolution strategies:\n\n```typescript\nimport { ImportProcessor, DefaultFileSystem, FileSystem } from '@pseudomutojs/proto-parser';\n\nclass MultiSourceImportProcessor extends ImportProcessor {\n  constructor(\n    baseDir: string,\n    fileSystem: FileSystem,\n    private remoteSources: string[] = [],\n    options = {}\n  ) {\n    super(baseDir, fileSystem, options);\n  }\n\n  async resolveImport(importPath: string): Promise\u003cstring | null\u003e {\n    // First try local resolution\n    const localResult = await super.resolveImport(importPath);\n    if (localResult) {\n      return localResult;\n    }\n\n    // Try remote sources\n    for (const remoteBase of this.remoteSources) {\n      try {\n        const remoteUrl = `${remoteBase}/${importPath}`;\n        const response = await fetch(remoteUrl);\n        if (response.ok) {\n          // Cache and return local path\n          return this.cacheRemoteFile(importPath, await response.text());\n        }\n      } catch (error) {\n        // Continue to next source\n      }\n    }\n\n    return null;\n  }\n\n  private async cacheRemoteFile(importPath: string, content: string): Promise\u003cstring\u003e {\n    // Implementation to cache remote content locally\n    // ...\n  }\n}\n\n// Use multi-source resolver\nconst fileSystem = new DefaultFileSystem();\nconst proto = await parseProto('./api.proto', {\n  importResolver: new MultiSourceImportProcessor('/local/protos', fileSystem, [\n    'https://raw.githubusercontent.com/googleapis/googleapis/master',\n    'https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/master'\n  ])\n});\n```\n\n### Custom Content Processing\n\nThe library also supports custom proto parsing through the `ProtoParser` class, allowing you to customize how protobufjs objects are converted to the library's internal types.\n\n#### Logging Content Processor\n\nAdd logging to track parsing operations:\n\n```typescript\nimport { ProtoParser, parseProto } from '@pseudomutojs/proto-parser';\n\nclass LoggingProtoParser extends ProtoParser {\n  parseMessage(messageType: any, namespace: string) {\n    console.log(`Parsing message: ${namespace}.${messageType.name}`);\n    return super.parseMessage(messageType, namespace);\n  }\n\n  parseService(service: any, namespace: string) {\n    console.log(`Parsing service: ${namespace}.${service.name} with ${service.methodsArray.length} methods`);\n    return super.parseService(service, namespace);\n  }\n}\n\n// Use logging processor\nconst proto = await parseProto('./api.proto', {\n  contentProcessor: new LoggingProtoParser()\n});\n```\n\n#### Custom Field Transformation\n\nCustomize how fields are processed:\n\n```typescript\nimport { ProtoParser } from '@pseudomutojs/proto-parser';\n\nclass CustomFieldProcessor extends ProtoParser {\n  parseField(field: any) {\n    const result = super.parseField(field);\n    \n    // Add custom metadata to fields\n    if (field.options?.deprecated) {\n      result.customMetadata = { deprecated: true };\n    }\n    \n    // Transform field names for specific patterns\n    if (result.name.endsWith('_id')) {\n      result.customMetadata = { ...result.customMetadata, isIdentifier: true };\n    }\n    \n    return result;\n  }\n}\n\n// Use custom field processor\nconst proto = await parseProto('./api.proto', {\n  contentProcessor: new CustomFieldProcessor()\n});\n```\n\n### Working with External Dependencies\n\nThe modern approach uses module providers for automatic dependency management:\n\n```typescript\nimport { BufModuleProvider, parseProtoDirectory } from '@pseudomutojs/proto-parser';\n\n// Create module provider for external dependencies\nconst bufProvider = new BufModuleProvider([\n  'buf.build/bufbuild/protovalidate:v1.0.0',\n  'buf.build/googleapis/googleapis'\n]);\n\n// Parse directory with automatic dependency resolution\nconst protoSet = await parseProtoDirectory('./api/protos', {\n  recursive: true,\n  moduleProviders: [bufProvider]  // Automatic download, extraction, and cleanup\n});\n\n// Generate unified IDL including external dependencies\nconst unifiedIdl = protoSet.generateSupersetIdl({\n  packageName: 'api.unified',\n  includeLocalOnly: true  // Exclude external deps from final IDL\n});\n```\n\n### Working with ProtoSet\n\n```typescript\nimport { parseProtoDirectory, ProtoSet } from '@pseudomutojs/proto-parser';\n\n// Parse an entire directory\nconst protoSet = await parseProtoDirectory('./api/protos', {\n  recursive: true,  // Search subdirectories\n  includePaths: ['./third_party/googleapis']\n});\n\n// Get statistics\nconst stats = protoSet.getStats();\nconsole.log(`Loaded ${stats.files} proto files containing:`);\nconsole.log(`  - ${stats.messages} messages`);\nconsole.log(`  - ${stats.services} services`);\nconsole.log(`  - ${stats.enums} enums`);\n\n// Find specific proto file\nconst userProto = protoSet.getProtoByFile('user.proto');\n\n// Get all service methods across all files\nconst services = protoSet.getAllServices();\nservices.forEach(service =\u003e {\n  service.methods?.forEach(method =\u003e {\n    console.log(`${service.name}.${method.name}`);\n  });\n});\n\n// Create ProtoSet from mixed sources\nconst customSet = await ProtoSet.from(\n  './common/base.proto',\n  './services/api.proto',\n  `syntax = \"proto3\";\n   package custom;\n   message Config { \n     string key = 1;\n     string value = 2;\n   }`\n);\n```\n\n### Generating Unified IDL\n\nThe `generateSupersetIdl()` method allows you to merge multiple proto files into a single unified IDL file. This is useful for creating consolidated API documentation, generating single proto files for code generation tools, or merging microservice definitions.\n\n```typescript\nimport { parseProtoDirectory } from '@pseudomutojs/proto-parser';\n\n// Parse microservice proto files\nconst protoSet = await parseProtoDirectory('./microservices/protos', {\n  recursive: true,\n  includePaths: ['./shared/protos']\n});\n\n// Generate unified API proto\nconst unifiedApi = protoSet.generateSupersetIdl({\n  syntax: 'proto3',\n  packageName: 'unified.microservices.v1',\n  includeComments: true,\n  namespaceConflictResolution: 'prefix'\n});\n\n// Save to file or use for code generation\nconsole.log(unifiedApi);\n/*\nOutput:\nsyntax = \"proto3\";\n\npackage unified.microservices.v1;\n\nimport \"google/protobuf/timestamp.proto\";\nimport \"google/protobuf/empty.proto\";\n\n// Enum definitions\n// From: user-service.proto\nenum UserStatus {\n  USER_STATUS_UNKNOWN = 0;\n  USER_STATUS_ACTIVE = 1;\n  USER_STATUS_INACTIVE = 2;\n}\n\n// Message definitions\n// From: user-service.proto\nmessage User {\n  string id = 1;\n  string name = 2;\n  string email = 3;\n  UserStatus status = 4;\n  google.protobuf.Timestamp created_at = 5;\n}\n\n// From: order-service.proto\nmessage Order {\n  string id = 1;\n  string user_id = 2;\n  repeated OrderItem items = 3;\n}\n\n// Service definitions\n// From: user-service.proto\nservice UserService {\n  rpc CreateUser(CreateUserRequest) returns (User);\n  rpc GetUser(GetUserRequest) returns (User);\n}\n*/\n\n// Handle namespace conflicts\nconst protoWithConflicts = await parseProtoDirectory('./conflicting-services');\nconst resolvedIdl = protoWithConflicts.generateSupersetIdl({\n  namespaceConflictResolution: 'prefix' // Prefixes conflicting names with namespace\n});\n```\n\n### Custom Include Paths\n\n```typescript\nimport { parseProto } from '@pseudomutojs/proto-parser';\n\nconst proto = await parseProto('./api.proto', {\n  includePaths: ['./protos', './third_party/googleapis', './third_party/protobuf'],\n});\n```\n\n### Field Name Casing\n\nThe `keepCase` option controls how field names are handled consistently across both file paths and content strings:\n\n```typescript\n// Proto file content:\n// message User {\n//   string user_name = 1;\n//   int32 user_id = 2;\n// }\n\n// With keepCase: true (default) - preserves original snake_case\nconst proto1 = await parseProto('./user.proto', { keepCase: true });\nconsole.log(proto1.messages[0].fields[0].name); // \"user_name\"\n\n// With keepCase: false - converts to camelCase\nconst proto2 = await parseProto('./user.proto', { keepCase: false });\nconsole.log(proto2.messages[0].fields[0].name); // \"userName\"\n```\n\n### Working with Parsed Data\n\n```typescript\nimport { parseProto } from '@pseudomutojs/proto-parser';\n\nconst proto = await parseProto('./user-service.proto');\n\n// Access services\nproto.services?.forEach(service =\u003e {\n  console.log(`Service: ${service.namespace}.${service.name}`);\n\n  service.methods?.forEach(method =\u003e {\n    console.log(`  Method: ${method.name}`);\n    console.log(`    Request: ${method.requestType}`);\n    console.log(`    Response: ${method.responseType}`);\n    console.log(`    Streaming: ${method.requestStream ? 'client' : ''}${method.responseStream ? 'server' : ''}`);\n  });\n});\n\n// Access messages\nproto.messages?.forEach(message =\u003e {\n  console.log(`Message: ${message.namespace}.${message.name}`);\n\n  message.fields?.forEach(field =\u003e {\n    console.log(`  Field: ${field.name} (${field.type}) = ${field.number}`);\n  });\n});\n\n// Access enums\nproto.enums?.forEach(enumDef =\u003e {\n  console.log(`Enum: ${enumDef.namespace}.${enumDef.name}`);\n\n  enumDef.values.forEach(value =\u003e {\n    console.log(`  ${value.name} = ${value.number}`);\n  });\n});\n```\n\n## Error Handling\n\n```typescript\nimport { parseProto } from '@pseudomutojs/proto-parser';\n\ntry {\n  const proto = await parseProto('./non-existent.proto');\n} catch (error) {\n  if (error.message.includes('ENOENT')) {\n    console.error('Proto file not found');\n  } else if (error.message.includes('Cannot resolve import')) {\n    console.error('Import resolution failed:', error.message);\n  } else {\n    console.error('Failed to parse proto:', error.message);\n  }\n}\n```\n\n### Import Resolution Errors\n\nBoth file paths and content strings now handle import resolution errors consistently:\n\n```typescript\n// File path - import resolution error\ntry {\n  const proto = await parseProto('./api.proto');\n} catch (error) {\n  console.error(error.message); // \"Cannot resolve import: missing/file.proto\"\n}\n\n// Content string - same error behavior\ntry {\n  const protoContent = `\n    syntax = \"proto3\";\n    import \"missing/file.proto\";\n    message Test { string field = 1; }\n  `;\n  const proto = await parseProto(protoContent);\n} catch (error) {\n  console.error(error.message); // \"Cannot resolve import: missing/file.proto\"\n}\n```\n\n## License\n\nThis project is licensed under the GPL-3.0-or-later License - see the [LICENSE](LICENSE) file for details.\n\n## Related Projects\n\n- [protobufjs](https://github.com/protobufjs/protobuf.js/) - Protocol Buffers for JavaScript\n- [@grpc/proto-loader](https://github.com/grpc/grpc-node/tree/master/packages/proto-loader) - gRPC proto loader\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpseudomuto%2Fproto-parser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpseudomuto%2Fproto-parser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpseudomuto%2Fproto-parser/lists"}