Skip to content

fix: enable discriminated union support for messageSchema#8

Merged
koistya merged 1 commit into
mainfrom
next
Sep 4, 2025
Merged

fix: enable discriminated union support for messageSchema#8
koistya merged 1 commit into
mainfrom
next

Conversation

@koistya

@koistya koistya commented Sep 4, 2025

Copy link
Copy Markdown
Member

Problem

The messageSchema() function had an explicit any return type annotation that was stripping away crucial type information needed for Zod's discriminatedUnion() to work properly. This prevented users from creating type-safe discriminated unions for WebSocket message routing, which is essential for complex applications that handle multiple message types.

Before (broken):

// This would fail at runtime
const PingSchema = messageSchema("PING");
const PongSchema = messageSchema("PONG");
const EchoSchema = messageSchema("ECHO", { text: z.string() });

// ❌ TypeError: Cannot read properties of undefined
const MessageSchema = z.discriminatedUnion("type", [
  PingSchema,
  PongSchema,
  EchoSchema,
]);

Solution

Removed the explicit : ZodObject<any> return type annotation from the messageSchema implementation, allowing TypeScript to properly infer the specific return type with all discriminator information preserved.

After (working):

// Now works perfectly
const PingSchema = messageSchema("PING");
const PongSchema = messageSchema("PONG");
const EchoSchema = messageSchema("ECHO", { text: z.string() });

// ✅ Creates discriminated union successfully
const MessageSchema = z.discriminatedUnion("type", [
  PingSchema,
  PongSchema,
  EchoSchema,
]);

// Full type inference and validation
const result = MessageSchema.parse({
  type: "ECHO", // <- TypeScript knows this must be "PING" | "PONG" | "ECHO"
  meta: {},
  payload: { text: "hello" }, // <- TypeScript enforces correct payload shape
});

Impact

This fix enables powerful type-safe message routing patterns that are critical for production WebSocket applications:

Advanced Message Routing

// Define message schemas for different features
const AuthMessages = z.discriminatedUnion("type", [
  messageSchema("AUTH.LOGIN", { username: z.string(), password: z.string() }),
  messageSchema("AUTH.LOGOUT"),
]);

const ChatMessages = z.discriminatedUnion("type", [
  messageSchema("CHAT.SEND", { text: z.string(), channel: z.string() }),
  messageSchema("CHAT.JOIN", { roomId: z.string() }),
]);

// Combine into master router with full type safety
const AllMessages = z.union([AuthMessages, ChatMessages]);
type MessageType = z.infer<typeof AllMessages>; // Fully typed!

Enhanced Developer Experience

  • Compile-time safety: TypeScript catches invalid message types and payloads
  • IntelliSense support: Full autocomplete for message properties
  • Refactoring confidence: Renaming message types updates all references
  • Runtime validation: Zod provides comprehensive runtime checks

Changes

  1. zod/schema.ts: Removed explicit any return type to preserve type information
  2. zod/schema.test.ts: Added comprehensive test suite covering:
    • Basic discriminated union functionality
    • Type inference verification
    • Nested discriminated unions
    • Complex real-world scenarios
  3. test-discriminated-union.ts: Added verification script for manual testing
  4. package.json: Version bump to 0.3.4

Backwards Compatibility

This change is 100% backwards compatible. Existing code continues to work exactly as before - we're only adding new capabilities, not changing existing behavior.

Testing

The fix includes extensive test coverage:

  • ✅ Basic discriminated union creation and parsing
  • ✅ Type inference validation with complex message structures
  • ✅ Nested discriminated unions for feature-based message grouping
  • ✅ Error handling for invalid message types
  • ✅ All existing functionality remains unchanged

Run the tests with:

bun test zod/schema.test.ts
bun run test-discriminated-union.ts

This fix addresses a critical limitation that was preventing users from leveraging Zod's powerful discriminated union features for building robust, type-safe WebSocket applications.

@koistya
koistya merged commit b919def into main Sep 4, 2025
3 checks passed
@koistya
koistya deleted the next branch September 4, 2025 13:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant