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