Eg

If you only need compile-time validation, you can simplify significantly with string literal union types:

/**
 * Message types using string literal unions for compile-time type safety
 */

// Extension to Webview message types
export type ExtensionToWebviewMessageType = 'focusNoteChanged';

// Webview to Extension message types  
export type WebviewToExtensionMessageType = 'log';

// Combined message type union
export type MessageType = ExtensionToWebviewMessageType | WebviewToExtensionMessageType;

That's it! This gives you:

Full compile-time type safety - TypeScript will catch typos and invalid values
Autocompletion - Your IDE will suggest valid message types
Zero runtime overhead - These types are erased during compilation
Much simpler code - No objects, no as const, no type guards needed

Usage Example

// TypeScript will enforce these are valid message types
function handleMessage(type: MessageType, payload: any) {
  switch (type) {
    case 'focusNoteChanged': // ✅ Valid
      break;
    case 'log': // ✅ Valid  
      break;
    case 'invalid': // ❌ TypeScript error!
      break;
  }
}

// Function signatures enforce correct types
function sendExtensionMessage(type: ExtensionToWebviewMessageType, data: any) {
  // Implementation
}

sendExtensionMessage('focusNoteChanged', {}); // ✅ Valid
sendExtensionMessage('log', {}); // ❌ TypeScript error!

This is the most idiomatic TypeScript approach when you only need compile-time validation. The type system handles everything for you without any runtime code.

CON: No Runtime validation

CON: No Runtime Validation

Risk of runtime type mismatches - If message types come from external sources (user input, APIs, parsed JSON, configuration files, etc.), TypeScript can't validate them at runtime. A malformed message could crash your application or cause unexpected behavior.

// These scenarios will compile fine but fail at runtime:
const userInput = getUserInput(); // Returns "invalidMessageType"
handleMessage(userInput as MessageType, {}); // 💥 Runtime error

const apiResponse = JSON.parse(response); // { type: "typo-in-message-type" }
handleMessage(apiResponse.type, apiResponse.data); // 💥 Silent failure

const config = loadConfig(); // Contains outdated message type
sendMessage(config.messageType, data); // 💥 Unpredictable behavior

When this matters: If your message types cross boundaries (network, file system, user input), you'll want runtime validation. If they're purely internal to your TypeScript codebase, compile-time validation is usually sufficient.


Children
  1. Compile Type Enum
  2. Destructuring with Defaults