Skip to content

fix: Enable discriminated union support for messageSchema#9

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

fix: Enable discriminated union support for messageSchema#9
koistya merged 1 commit into
mainfrom
dev

Conversation

@koistya

@koistya koistya commented Sep 4, 2025

Copy link
Copy Markdown
Member

Summary

This PR introduces a breaking change to fix discriminated union support by implementing a factory pattern that eliminates the dual package hazard issue. Previously, messageSchema schemas would fail when used with Zod's discriminatedUnion due to the library using a different Zod instance than the application.

Key Changes:

  • 🚨 BREAKING: Remove all deprecated exports from main package and adapters
  • ✨ Add createMessageSchema() factory pattern for both Zod and Valibot
  • 🐛 Fix discriminated union support by ensuring single validator instance
  • 🔧 API Consistency: Unify payload and meta parameter patterns to use raw shapes
  • 📚 Comprehensive documentation updates and migration guides (20+ files updated)
  • 🧪 New test suite for discriminated union functionality

Breaking Changes

❌ Removed Exports

All direct exports from the main package and adapter modules have been removed:

// NO LONGER WORKS
import { WebSocketRouter, messageSchema } from "bun-ws-router";
import { messageSchema, createMessage } from "bun-ws-router/zod";

✅ New Required Pattern

The factory pattern is now mandatory to avoid dual package hazards:

// REQUIRED
import { z } from "zod";
import { WebSocketRouter, createMessageSchema } from "bun-ws-router/zod";

const { messageSchema, createMessage, ErrorMessage, ErrorCode } =
  createMessageSchema(z);

Migration Guide

Step 1: Update Imports

Replace all imports from "bun-ws-router" with explicit adapter imports:

- import { WebSocketRouter, messageSchema } from "bun-ws-router";
+ import { WebSocketRouter, createMessageSchema } from "bun-ws-router/zod";

Step 2: Create Factory Instance

Add factory creation at the top of your schema files:

import { z } from "zod";
import { createMessageSchema } from "bun-ws-router/zod";

const { messageSchema, createMessage, ErrorMessage, ErrorCode } =
  createMessageSchema(z);

Step 3: Update Schema Definitions

No changes needed to existing schema definitions or handlers:

// This remains unchanged
const PingMessage = messageSchema("PING", { text: z.string() });

router.onMessage(PingMessage, (ctx) => {
  // Handler code unchanged
});

Why This Change?

The previous implementation suffered from a "dual package hazard":

  1. The library bundled its own Zod/Valibot instance
  2. Applications used their own separate instances
  3. This broke instanceof checks in discriminated unions
  4. Runtime errors occurred: "A discriminator value for key type could not be extracted"

The factory pattern ensures both library and application use the same validator instance.

New Capabilities

Discriminated Unions Now Work

const { messageSchema } = createMessageSchema(z);

const TextMessage = messageSchema("TEXT", { content: z.string() });
const ImageMessage = messageSchema("IMAGE", { url: z.url() });

// ✅ This now works perfectly!
const MediaMessage = z.discriminatedUnion("type", [TextMessage, ImageMessage]);

Consistent API for Payload and Meta Parameters

The API now uses consistent raw shapes for both payload and meta parameters:

// ✅ NEW: Consistent raw shapes for both payload and meta
const CustomMessage = messageSchema(
  "CUSTOM",
  { action: z.string() }, // Raw shape - consistent
  { userId: z.string() }, // Raw shape - consistent
);

// ❌ OLD: Mixed API pattern (no longer supported)
const OldMessage = messageSchema(
  "OLD",
  z.object({ action: z.string() }), // ZodObject - inconsistent
  z.object({ userId: z.string() }), // ZodObject - inconsistent
);

Improved Type Inference

Full TypeScript support with proper type narrowing in union handlers:

function handleMessage(msg: z.infer<typeof MediaMessage>) {
  switch (msg.type) {
    case "TEXT":
      // TypeScript knows payload has { content: string }
      console.log(msg.payload.content);
      break;
    case "IMAGE":
      // TypeScript knows payload has { url: string }
      console.log(msg.payload.url);
      break;
  }
}

Files Changed

Core Changes

  • Version: Bumped to v0.4.1 for this patch release
  • shared/: Updated all shared types and router logic for factory pattern
  • zod/: Implemented factory pattern with createMessageSchema() and API consistency
  • valibot/: Implemented factory pattern with Valibot support and shape helpers
  • index.ts: Added deprecation warnings for old imports

Documentation

  • README.md: Updated all examples to use factory pattern and consistent API
  • docs/: Comprehensive updates across 6 documentation files:
    • message-schemas.md: Updated 10+ code examples to use raw shapes
    • examples.md: Fixed 13+ instances across chat, auth, and notification examples
    • api-reference.md: Updated core API documentation examples
    • advanced-usage.md: Fixed schema versioning and compression examples
    • core-concepts.md, deployment.md, valibot-integration.md: API consistency fixes
  • example/: All 6 example files reviewed and verified for accuracy
  • BREAKING_CHANGES.md: Detailed breaking change documentation
  • MIGRATION.md: Step-by-step migration guide

Testing

  • NEW: Added cross-package-types.test.ts for comprehensive discriminated union testing
  • Updated messageSchema-types.test.ts for discriminated union testing
  • Updated 15+ test files to use factory pattern and API consistency
  • Comprehensive test coverage for both Zod and Valibot implementations
  • Fixed Valibot createMessage function with simplified type signature

Examples

  • Updated all example files to demonstrate factory pattern
  • Added discriminated union examples
  • Client-side usage examples updated

Error Handling

Applications using deprecated imports will receive clear error messages:

bun-ws-router v0.4.0 Breaking Change:
Direct imports from "bun-ws-router" are no longer supported.
Use createMessageSchema() factory instead.

Testing

  • ✅ All existing tests pass with new factory pattern
  • ✅ New discriminated union test suite added
  • ✅ Both Zod and Valibot implementations tested
  • ✅ Type inference tests for complex union scenarios
  • ✅ Error message validation for deprecated usage

Performance Impact

  • ✅ No performance regression - same runtime behavior
  • ✅ Slightly improved bundle size by eliminating dual dependencies
  • ✅ Better tree-shaking with explicit imports

This change fixes a fundamental issue that prevented the library from working with Zod's discriminated unions, a critical feature for complex message routing patterns. While breaking, the migration path is straightforward and the benefits are significant for type safety and developer experience.

@koistya
koistya merged commit d448483 into main Sep 4, 2025
4 of 6 checks passed
@koistya
koistya deleted the dev branch September 4, 2025 21:12
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