diff --git a/packages/krpc-client/src/types.ts b/packages/krpc-client/src/types.ts index 5d6db41..0ad46f4 100644 --- a/packages/krpc-client/src/types.ts +++ b/packages/krpc-client/src/types.ts @@ -78,7 +78,23 @@ export interface RawKrpcTypeMessage { types: RawKrpcTypeMessage[]; } -export function decodeKrpcType(raw: RawKrpcTypeMessage): KrpcType { +/** + * Decode a kRPC Type protobuf message into our plain KrpcType shape. + * + * Returns a NONE-type KrpcType if `raw` is null/undefined or doesn't + * have a `code` field — which happens for procedures with no return + * value (the kRPC server omits the `return_type` field). We treat that + * as the NONE type code (0) rather than throwing. + */ +export function decodeKrpcType(raw: RawKrpcTypeMessage | null | undefined): KrpcType { + if (!raw || typeof raw.code !== 'number') { + return { + code: 0 as TypeCodeValue, // NONE + service: '', + name: '', + types: [], + }; + } return { code: raw.code as TypeCodeValue, service: raw.service ?? '', diff --git a/packages/krpc-client/tests/services.test.ts b/packages/krpc-client/tests/services.test.ts index cce94be..71dc571 100644 --- a/packages/krpc-client/tests/services.test.ts +++ b/packages/krpc-client/tests/services.test.ts @@ -196,4 +196,33 @@ describe('ServiceCache', () => { expect(r.info.returnType.types[0]?.code).toBe(100); expect(r.info.returnType.types[0]?.name).toBe('CelestialBody'); }); + + it('handles procedures with no return type (returnType is null)', () => { + // Real kRPC server omits the `return_type` field for void + // procedures (e.g. AddStream, setters). protobufjs decodes + // missing message fields as null. Our cache must not crash. + const raw = { + services: [ + { + name: 'KRPC', + procedures: [ + { + name: 'AddStream', + parameters: [], + returnType: null, // <-- the trigger + returnIsNullable: false, + }, + ], + classes: [], + enumerations: [], + }, + ], + }; + const cache = new ServiceCache(raw as unknown as Parameters[0]); + const r = cache.lookup('KRPC', 'AddStream'); + expect(r.found).toBe(true); + if (!r.found) throw new Error('unreachable'); + // Missing return type becomes NONE (code 0). + expect(r.info.returnType.code).toBe(0); + }); });