Skip to content

Conversation

@adrien2p
Copy link
Member

@adrien2p adrien2p commented Jan 13, 2026

what
Introduce the new event module supporting provider architecture natively. This pr provide a backward compatibility approach and set the new module as the default.

The module comes with the local in built provider as well as a separate redis provider.]

This will allow more provider to be created to answer different needs


Note

Introduces a provider-based Events module and makes it the default event system with built-in local and optional Redis providers.

  • Adds new @medusajs/events module with provider architecture, IEventsModuleService/IEventsProvider types, interceptors, grouped events APIs, and AbstractEventsProvider
  • Implements @medusajs/events-redis (BullMQ/ioredis) with priority/retry, lifecycle hooks, and grouped event handling
  • Updates Medusa exports to @medusajs/medusa/events and events-redis; marks event-bus-local/event-bus-redis as deprecated shims
  • Changes default config to resolve event_bus to @medusajs/medusa/events with providers option; updates utils and module resolution
  • Adjusts tests and test-utils to access provider internals (getProvider('local')) and ensures compatibility in product import/export flows
  • Minor tooling/docs: ESLint paths, CONTRIBUTING overrides

Written by Cursor Bugbot for commit 66e42d3. This will update automatically on new commits. Configure here.

@changeset-bot
Copy link

changeset-bot bot commented Jan 13, 2026

🦋 Changeset detected

Latest commit: 66e42d3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 78 packages
Name Type
@medusajs/medusa Patch
@medusajs/test-utils Patch
@medusajs/events Patch
@medusajs/events-redis Patch
@medusajs/types Patch
@medusajs/utils Patch
@medusajs/medusa-oas-cli Patch
integration-tests-http Patch
@medusajs/analytics Patch
@medusajs/api-key Patch
@medusajs/auth Patch
@medusajs/caching Patch
@medusajs/cart Patch
@medusajs/currency Patch
@medusajs/customer Patch
@medusajs/file Patch
@medusajs/fulfillment Patch
@medusajs/index Patch
@medusajs/inventory Patch
@medusajs/link-modules Patch
@medusajs/locking Patch
@medusajs/notification Patch
@medusajs/order Patch
@medusajs/payment Patch
@medusajs/pricing Patch
@medusajs/product Patch
@medusajs/promotion Patch
@medusajs/rbac Patch
@medusajs/region Patch
@medusajs/sales-channel Patch
@medusajs/settings Patch
@medusajs/stock-location Patch
@medusajs/store Patch
@medusajs/tax Patch
@medusajs/translation Patch
@medusajs/user Patch
@medusajs/workflow-engine-inmemory Patch
@medusajs/workflow-engine-redis Patch
@medusajs/draft-order Patch
@medusajs/event-bus-redis Patch
@medusajs/framework Patch
@medusajs/js-sdk Patch
@medusajs/modules-sdk Patch
@medusajs/orchestration Patch
@medusajs/workflows-sdk Patch
@medusajs/admin-bundler Patch
@medusajs/dashboard Patch
@medusajs/cli Patch
@medusajs/oas-github-ci Patch
@medusajs/cache-inmemory Patch
@medusajs/cache-redis Patch
@medusajs/event-bus-local Patch
@medusajs/analytics-local Patch
@medusajs/analytics-posthog Patch
@medusajs/auth-emailpass Patch
@medusajs/auth-github Patch
@medusajs/auth-google Patch
@medusajs/caching-redis Patch
@medusajs/file-local Patch
@medusajs/file-s3 Patch
@medusajs/fulfillment-manual Patch
@medusajs/locking-postgres Patch
@medusajs/locking-redis Patch
@medusajs/notification-local Patch
@medusajs/notification-sendgrid Patch
@medusajs/payment-stripe Patch
@medusajs/core-flows Patch
@medusajs/deps Patch
@medusajs/telemetry Patch
@medusajs/admin-sdk Patch
@medusajs/admin-shared Patch
@medusajs/admin-vite-plugin Patch
@medusajs/icons Patch
@medusajs/toolbox Patch
@medusajs/ui-preset Patch
create-medusa-app Patch
medusa-dev-cli Patch
@medusajs/ui Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link

vercel bot commented Jan 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

8 Skipped Deployments
Project Deployment Review Updated (UTC)
api-reference Ignored Ignored Jan 23, 2026 2:21pm
api-reference-v2 Ignored Ignored Preview Jan 23, 2026 2:21pm
cloud-docs Ignored Ignored Preview Jan 23, 2026 2:21pm
docs-ui Ignored Ignored Preview Jan 23, 2026 2:21pm
docs-v2 Ignored Ignored Preview Jan 23, 2026 2:21pm
medusa-docs Ignored Ignored Preview Jan 23, 2026 2:21pm
resources-docs Ignored Ignored Preview Jan 23, 2026 2:21pm
user-guide Ignored Ignored Preview Jan 23, 2026 2:21pm

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Jan 13, 2026

⚠️ No Changeset found

Latest commit: 75782e2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@adrien2p
Copy link
Member Author

@claude review this

@claude
Copy link

claude bot commented Jan 14, 2026

PR Review: Event Module Provider Architecture

Summary

This PR introduces a significant architectural improvement by implementing a provider-based event module system. The change migrates from directly using event-bus-local/event-bus-redis to a unified event module with pluggable providers. Overall, the implementation is well-structured and follows Medusa's conventions, but there are several critical issues that need attention.


🚨 Critical Issues

1. Test Script Modified to Run Single Test

Location: integration-tests/http/package.json:8

-    "test:integration": "jest --no-cache --bail --detectOpenHandles --forceExit --logHeapUsage",
+    "test:integration": "jest --no-cache --bail --detectOpenHandles --forceExit --logHeapUsage -- __tests__/product/admin/product-export.spec.ts",

Issue: The test script has been modified to only run a single test file. This appears to be a debugging change that should NOT be committed to the codebase.

Fix: Revert this line to run all integration tests.


⚠️ High Priority Issues

2. Missing Error Handling in Redis Connection

Location: packages/modules/providers/event-redis/src/loaders/index.ts:37-45

The Redis connection error handling only logs the error but doesn't prevent the application from starting with a broken connection:

try {
  await new Promise(async (resolve) => {
    await connection.connect(resolve)
  })
  logger?.info(`Connection to Redis in "event-redis" provider established`)
} catch (err) {
  logger?.error(`An error occurred while connecting to Redis...`)
}
// Container registration continues regardless of connection failure

Issue: If Redis connection fails, the provider will be registered but non-functional, leading to silent event loss.

Recommendation: Either:

  • Throw the error to prevent application startup with broken event system
  • Implement a retry mechanism with exponential backoff
  • Add connection status checks before emitting events

3. Potential Memory Leak in Grouped Events

Location: packages/modules/providers/event-redis/src/services/event-redis.ts:235

The grouped events TTL is only set if explicitly provided:

void this.setExpire(groupId, groupedEventsTTL)

Issue: If groupedEventsTTL is 0 or undefined, grouped events will never expire in Redis, potentially causing memory leaks for long-running applications with failed workflows.

Recommendation: Set a default TTL (e.g., 24 hours) and document that users should increase it for long-running workflows.

4. Race Condition in Worker Mode Flag

Location: packages/modules/providers/event-redis/src/services/event-redis.ts:84-88

if (this.isWorkerMode) {
  this.bullWorker_ = new Worker(this.queueName_, this.worker_, {
    ...this.workerOptions_,
    connection: this.eventRedisConnection_,
    autorun: false,
  })
}

Issue: The isWorkerMode flag is hardcoded to true and never set from module declaration. The worker will always be created even in server-only mode.

Recommendation: Initialize from module options:

this.isWorkerMode = options.worker_mode !== 'server'

5. Unsafe Type Casting in Tests

Location: Multiple test files (product-export.spec.ts, product-import.spec.ts, etc.)

type EventBus = IEventBusModuleService & {
  getProvider: (id: string) => IEventProvider & { eventEmitter_: EventEmitter }
}

Issue: Tests are reaching into internal implementation details (eventEmitter_) which breaks encapsulation. This makes tests brittle and will break if the implementation changes.

Recommendation: Add a proper public API method to the provider for test cleanup:

interface IEventProvider {
  clearListeners?(): void // For testing only
}

💡 Code Quality Improvements

6. Inconsistent Null Checks

Location: packages/modules/event/src/services/event-provider.ts:41-59

Error handling checks for AwilixResolutionError by name string comparison, which is fragile:

if (err.name === "AwilixResolutionError") {

Recommendation: Use instanceof check or error codes instead of string comparison.

7. Missing JSDoc for Public APIs

Location: packages/modules/event/src/services/event-module.ts

While the interface has good JSDoc comments, the actual implementation methods lack documentation.

Recommendation: Add JSDoc comments to implementation methods, especially for lifecycle hooks.

8. Hardcoded Magic Numbers

Location: packages/core/utils/src/event/abstract-event-provider.ts

Priority values like EventPriority.LOWEST = 2097152 lack explanation.

Recommendation: Add comments explaining the significance of these values (BullMQ's max priority).


✅ Strengths

  1. Architecture: The provider pattern is well-implemented and follows Medusa's existing module provider conventions
  2. Type Safety: Strong TypeScript typing throughout with proper interface definitions
  3. Backwards Compatibility: Existing event-bus modules are maintained, allowing gradual migration
  4. Comprehensive Provider Interface: The IEventProvider interface is well-documented with examples
  5. Priority & Retry Support: Good implementation of advanced features in Redis provider
  6. Interceptor Pattern: Clean implementation of event interceptors for cross-cutting concerns
  7. Code Style: Follows Medusa's code conventions (no semicolons, double quotes, etc.)

🧪 Test Coverage Concerns

9. Limited Integration Tests

Location: packages/modules/event/integration-tests/__tests__/index.spec.ts

While integration tests exist for the event module, there's limited testing of:

  • Error scenarios (Redis connection failures, job failures)
  • Race conditions in grouped events
  • Priority ordering verification
  • Retry mechanism behavior
  • Provider lifecycle hooks

Recommendation: Add tests for edge cases and failure scenarios.

10. No Performance Tests

The Redis provider uses BullMQ which has performance implications. Consider adding:

  • Load tests for high-volume event emission
  • Tests for grouped event performance with large event counts
  • Memory usage tests for long-running grouped events

🔒 Security Considerations

11. Redis Connection Security

Location: packages/modules/providers/event-redis/src/loaders/index.ts

The loader accepts arbitrary redisOptions but doesn't validate them.

Recommendation: Document secure connection practices and consider validating critical security options (TLS, auth).


📝 Documentation Gaps

12. Migration Guide Missing

Users need guidance on:

  • How to migrate from event-bus-redis to the new event module with Redis provider
  • Breaking changes (if any)
  • Configuration differences

13. Provider Development Guide

The IEventProvider interface has good JSDoc, but users would benefit from a comprehensive guide on:

  • Creating custom event providers
  • Testing custom providers
  • Best practices for provider implementation

🎯 Recommendations Summary

Must Fix Before Merge:

  1. ✅ Revert test script change in integration-tests/http/package.json
  2. ✅ Fix Redis connection error handling
  3. ✅ Implement default TTL for grouped events
  4. ✅ Fix worker mode flag initialization

Should Fix:

  1. Add public API for test cleanup instead of accessing internal properties
  2. Improve error handling with instanceof checks
  3. Add comprehensive error scenario tests

Nice to Have:

  1. Add JSDoc to implementation methods
  2. Document magic numbers
  3. Create migration guide
  4. Add performance tests

Conclusion

This is a solid architectural improvement that will make Medusa's event system more flexible and maintainable. The provider pattern is well-executed, but there are critical issues around error handling and a debugging change that must be addressed before merging. Once these issues are resolved, this will be a valuable addition to the codebase.

Overall Assessment: ⚠️ Approve with Changes Required

Great work on the architecture, but please address the critical issues before merging!

@adrien2p
Copy link
Member Author

@claude review this

@claude
Copy link

claude bot commented Jan 14, 2026

PR Review: Event Module Provider Architecture

This PR introduces a significant architectural improvement by creating a new event module with a provider-based architecture, allowing pluggable event providers (local and Redis). Here's my detailed review:


Strengths

1. Architecture & Design

  • Excellent abstraction: The AbstractEventProvider base class provides a clean, well-documented foundation for implementing event providers
  • Provider pattern: Following established patterns in the codebase (similar to auth, file, notification providers)
  • Backward compatibility: Migrates from direct event-bus-local and event-bus-redis to a unified event module with provider support
  • Clear separation: Event module delegates to providers, keeping concerns separated

2. Code Quality

  • Comprehensive documentation: JSDoc comments on interfaces and abstract methods are thorough and include examples
  • Type safety: Strong TypeScript usage with proper generic types and interface definitions
  • Error handling: Good use of MedusaError with descriptive messages (e.g., event-provider.ts:23-29, abstract-event-provider.ts:175)
  • Lifecycle hooks: Proper implementation of __hooks for application lifecycle management (event-module.ts:37-62)

3. Feature Completeness

  • Priority support: Redis provider implements IEventProviderWithPriority with proper priority validation (event-redis.ts:146-155, 199-211)
  • Retry mechanism: Redis provider implements IEventProviderWithRetry with sophisticated retry logic including per-subscriber tracking (event-redis.ts:404-507)
  • Grouped events: Both providers properly support event grouping for distributed transactions
  • Interceptors: Support for event interception across providers (abstract-event-provider.ts:314-350)
  • Wildcard subscribers: Both providers handle "*" event subscription

4. Testing

  • Integration tests cover core functionality: emit, subscribe, grouped events, wildcards, and interceptors
  • Tests are well-structured using the module test runner

⚠️ Issues & Concerns

1. Critical: Race Condition in Interceptors (abstract-event-provider.ts:342)

  • Problem: Using .map() without await - interceptors run asynchronously but aren't awaited
  • Impact: Interceptors may not complete before event emission; errors may not be properly caught
  • Fix: Use Promise.all() or await Promise.allSettled()

2. Security: Direct console.error in Production (abstract-event-provider.ts:347)

  • Using console.error instead of the logger
  • Should use the logger instance for consistent logging and proper log levels

3. Bug: Type Mismatch in Message Handling (event-redis.ts:325-335)

  • Problem: jobData.data already contains { data, metadata } (see line 190), but then accessing jobData.data.metadata creates nested structure
  • Should be:

4. Performance: Inefficient Event Filtering (event-redis.ts:260-265)

  • Wildcard subscribers are fetched repeatedly for each event
  • Should fetch once before the filter

5. Memory Leak Risk: Event Emitter in Local Provider (local.ts:18-19)

  • Module-level singleton EventEmitter shared across all instances
  • Setting max listeners to Infinity prevents warnings but could mask memory leaks
  • Recommendation: Each instance should have its own EventEmitter, or use a reasonable limit (e.g., 100)

6. Potential Bug: Missing await in Delayed Events (local.ts:97-113)

  • void keyword ignores the promise, making this fire-and-forget
  • Combined with the interceptor bug (Product Variant Service #1), interceptors definitely won't complete before emission
  • Fix: Remove void and use await

7. Type Safety: Missing Generic Constraint (event-redis.ts:404)

  • Worker signature doesn't match the actual job data structure
  • job.data should be typed as { data: T; metadata: any; completedSubscriberIds?: string[] }

8. Configuration: Inconsistent Default Handling (providers.ts:81-98)

  • If multiple providers are marked is_default: true, the last one wins (no validation)
  • If single provider, it becomes default even if is_default: false is explicitly set
  • Recommendation: Validate that only one provider has is_default: true and throw an error if multiple

9. Error Handling: Silent Failure in Provider Listing (event-provider.ts:64-76)

  • Silent catch without logging could hide configuration issues
  • Should at least log a warning

10. Redis Connection: Missing Error Recovery (event-redis.ts:110-141)

  • Only checks connection once at startup
  • If Redis disconnects during runtime, subsequent operations will fail
  • BullMQ has built-in retry, but the ensureRedisConnection should be called before critical operations

📝 Minor Issues

11. Code Style: Inconsistent Naming (abstract-event-provider.ts:99-107)

  • Protected properties use trailing underscore (eventToSubscribersMap_, interceptorSubscribers_)
  • But getter removes it (eventToSubscribersMap)
  • This is intentional but could be confusing - ensure it's documented in CLAUDE.md

12. Documentation: Missing Interface Property (provider.ts:65-69)

  • This property is in the interface but not documented in the JSDoc above
  • Should add a comment explaining when/why providers would implement hooks

13. Test Coverage Gaps

  • No tests for priority handling in Redis provider
  • No tests for retry mechanism
  • No tests for error scenarios (connection failures, invalid data)
  • No tests for multiple providers or provider selection

14. Typo (event-redis.ts:436)

15. ESLint Changes

  • The .eslintrc.js changes are just formatting (Prettier applied)
  • Good to standardize, but adds noise to the PR

🔒 Security Considerations

16. Redis Injection: The eventGroupId is user-controllable via metadata and used directly in Redis keys (event-redis.ts:297, 304, 314)

  • Should validate/sanitize the eventGroupId to prevent injection attacks
  • Example: eventGroupId: "../../../other:key" could potentially access wrong keys

17. TTL Default: groupedEventsTTL defaults to 600 seconds (event-redis.ts:232)

  • For long-running workflows, this might be too short
  • Should be configurable at module level, not just per-emit call

🚀 Performance Considerations

18. Redis Pipeline Optimization: The grouped events clearing (event-redis.ts:378-391) uses pipeline efficiently - good!

19. Bulk Operations: Both emit and grouped events use bulk operations where possible - excellent!

20. Subscriber Tracking in Retry: The retry mechanism tracks completed subscribers to avoid re-running successful ones (event-redis.ts:411-418, 483-497) - very clever and efficient!


📋 Recommendations

  1. Fix Critical Issue Product Variant Service #1 (interceptors) - This could cause real issues in production
  2. Fix Bug Common query patterns #3 (message structure) - This will break interceptors for grouped events in Redis
  3. Add validation for default provider configuration
  4. Improve error handling with proper logging
  5. Add integration tests for Redis provider's advanced features (priority, retry)
  6. Document migration path from old event-bus modules to new event module
  7. Consider adding metrics (events emitted, subscribers executed, failures) for observability

📊 Summary

Overall Assessment: This is a well-designed architectural improvement that follows the codebase patterns and provides good extensibility. However, there are some bugs and race conditions that need to be addressed before merging.

Recommendation: Request changes to fix critical issues #1, #3, and #6, and address security concern #16.

Test Coverage: 6/10 - Basic functionality is tested but advanced features (priority, retry, errors) are not

Code Quality: 7/10 - Well-structured with good documentation, but some bugs and inconsistencies

Architecture: 9/10 - Excellent abstraction and provider pattern implementation


Great work on this substantial refactoring! The provider architecture is a solid improvement. Let me know if you'd like me to elaborate on any of these points or if you have questions about the recommendations.

@adrien2p
Copy link
Member Author

@claude review this

@claude
Copy link

claude bot commented Jan 14, 2026

Code Review - PR #14532: Event Module Providers Architecture

Summary

This PR introduces a significant architectural improvement by refactoring the event bus into a modular provider-based system. The new @medusajs/event module supports multiple event providers (local and Redis) with a clean abstraction layer.


✅ Strengths

1. Excellent Architecture Design

  • Clean abstraction: AbstractEventProvider provides a solid foundation for implementing custom event providers
  • Provider pattern: Following established Medusa patterns (auth, payment, file providers)
  • Separation of concerns: Event module delegates to providers, providers handle implementation details
  • Extensibility: Well-documented interfaces (IEventProvider, IEventProviderWithPriority, IEventProviderWithRetry) make it easy for developers to create custom providers

2. Strong Type Safety

  • Comprehensive TypeScript interfaces in packages/core/types/src/event/provider.ts
  • Proper generic types throughout (AbstractEventProvider<TConfig>, Message<T>)
  • Well-defined type contracts for provider implementations

3. Good Documentation

  • Excellent JSDoc comments with examples throughout the provider interfaces
  • Clear migration guide in deprecation warnings
  • Helpful inline comments explaining complex logic (priority hierarchy, grouped events)

4. Backward Compatibility

  • Deprecation warnings instead of breaking changes (packages/medusa/src/modules/event-bus-local.ts:11-14, event-bus-redis.ts:12-28)
  • Legacy modules still functional with clear migration paths
  • Automatic config transformation in define-config.ts

5. Feature Parity & Enhancements

  • ✅ Grouped events support (for distributed transactions)
  • ✅ Event interceptors
  • ✅ Wildcard subscribers (*)
  • ✅ Priority support (Redis provider)
  • ✅ Retry mechanism with partial failure recovery (Redis provider)
  • ✅ Lifecycle hooks (__hooks)

🔍 Issues & Concerns

1. Critical: Redis Connection Error Handling ⚠️

Location: packages/modules/providers/event-redis/src/loaders/index.ts:36-47

try {
  await new Promise(async (resolve) => {
    await connection.connect(resolve)
  })
  logger?.info(`[event-redis] Connection to Redis established`)
} catch (err) {
  logger?.error(`[event-redis] Connection error: ${err}`)
  // ⚠️ No throw! Application continues with broken Redis connection
}

Issue: Connection errors are logged but not thrown, allowing the application to start with a non-functional Redis event provider. This will cause silent failures when events are emitted.

Recommendation:

try {
  await connection.connect()
  logger?.info(`[event-redis] Connection to Redis established`)
} catch (err) {
  logger?.error(`[event-redis] Connection error: ${err}`)
  throw new MedusaError(
    MedusaError.Types.DB_ERROR,
    `Failed to connect to Redis for event-redis provider: ${err.message}`
  )
}

2. Race Condition: Delayed Event Emission ⚠️

Location: packages/modules/event/src/providers/local.ts:95-111

void delay(options_?.delay).then(async () => {
  void this.callInterceptors(eventData, { isGrouped: false })
  if (eventListenersCount) {
    this.eventEmitter_.emit(eventData.name, eventBody)
  }
  // ...
})

Issue: Using void with promise chains means errors in delayed emissions are silently swallowed. The callInterceptors call is also voided, so interceptor errors won't be caught.

Recommendation:

delay(options_?.delay)
  .then(async () => {
    await this.callInterceptors(eventData, { isGrouped: false })
    // ... emit events
  })
  .catch((err) => {
    this.logger_.error(`[event-local] Error in delayed event emission: ${err}`)
  })

3. Memory Leak Risk: Singleton EventEmitter ⚠️

Location: packages/modules/event/src/providers/local.ts:18-19

const eventEmitter = new EventEmitter()
eventEmitter.setMaxListeners(Infinity)

Issue: Global singleton EventEmitter shared across all LocalEventProvider instances. While setMaxListeners(Infinity) prevents warnings, it could mask legitimate memory leaks from unremoved listeners.

Recommendation:

  • Consider creating a new EventEmitter per provider instance
  • Or implement monitoring/metrics for listener count growth
  • Document why Infinity is needed (if it's intentional for multi-tenant scenarios)

4. Incomplete Error Recovery in Redis Worker 🔶

Location: packages/modules/providers/event-redis/src/services/event-redis.ts:440-452

const subscribersResult = await Promise.all(
  subscribersInCurrentAttempt.map(async ({ id, subscriber }) => {
    try {
      return await subscriber(event).then((data) => {
        completedSubscribersInCurrentAttempt.push(id)
        return data
      })
    } catch (err) {
      this.logger_?.warn(`[event-redis] Error processing ${name}: ${err}`)
      return err  // ⚠️ Returns error object instead of rejecting
    }
  })
)

Issue: Errors are caught and returned as values rather than rejecting. This works but makes subscribersResult an array of mixed types (results and Error objects), which isn't type-safe.

Recommendation: Track failures explicitly:

const subscribersResult = await Promise.allSettled(
  subscribersInCurrentAttempt.map(({ id, subscriber }) => 
    subscriber(event).then(() => {
      completedSubscribersInCurrentAttempt.push(id)
    })
  )
)
const failedCount = subscribersResult.filter(r => r.status === 'rejected').length
const didSubscribersFail = failedCount > 0

5. Missing Validation 🔶

Location: packages/core/utils/src/event/abstract-event-provider.ts:88

static validateOptions(options: Record<any, any>): void | never {}

Issue: The RedisEventProvider doesn't override validateOptions despite requiring redisUrl. Validation only happens in the loader, but the provider could be instantiated elsewhere.

Recommendation:

// In RedisEventProvider
static validateOptions(options: Record<any, any>) {
  if (!options.redisUrl) {
    throw new MedusaError(
      MedusaError.Types.INVALID_DATA,
      'redisUrl is required for event-redis provider'
    )
  }
}

6. Priority Validation Logic 🔶

Location: packages/modules/providers/event-redis/src/services/event-redis.ts:195-208

if (
  finalOptions.priority != undefined &&
  (finalOptions.priority < 1 || finalOptions.priority > EventPriority.LOWEST)
) {
  this.logger_.warn(`Invalid priority value: ${finalOptions.priority}`)
  finalOptions.priority = EventPriority.DEFAULT
  // ...
}

Issue: Mutating finalOptions.priority after logging warning. Better to validate before building the event.

Recommendation: Extract validation into a helper function and validate earlier in the flow.


📝 Minor Issues & Suggestions

7. Test Coverage Gaps 📋

Observations:

  • Integration tests cover happy path scenarios well
  • Missing edge case tests:
    • What happens when Redis connection drops mid-operation?
    • Provider failover scenarios
    • Event emission during shutdown
    • Large payload handling
    • Concurrent grouped event operations

Recommendation: Add tests for:

  • Redis connection failure scenarios
  • Provider-specific error handling
  • Concurrent access to grouped events map
  • Memory behavior with thousands of queued events

8. Type Casting in Tests 📋

Location: Multiple test files

type EventBus = IEventBusModuleService & {
  getProvider: (id: string) => IEventProvider & { eventEmitter_: EventEmitter }
}

Issue: Tests are accessing internal properties (eventEmitter_, groupedEventsMap_) which breaks encapsulation.

Recommendation:

  • Add official test helpers/accessors to providers for testing
  • Or accept that tests need internal access and document it

9. Inconsistent Logging Prefixes 📋

  • [event-local], [event-redis], [event-module] - Good!
  • But some logs don't include context (event name, subscriber count)
  • Consider structured logging with consistent metadata

10. Documentation: Migration Impact 📋

The deprecation warnings are good, but consider:

  • Adding a migration guide to the PR description
  • Documenting breaking changes (if any) for custom event bus implementations
  • Clarifying upgrade path for users with custom subscribers

🔒 Security Considerations

Good:

  • No obvious injection vulnerabilities
  • Redis connection properly configured with recommended BullMQ settings
  • No sensitive data logged
  • Event payloads are properly serialized/deserialized

⚠️ Consider:

  • Add rate limiting support for event emission (DoS prevention)
  • Document size limits for event payloads
  • Consider adding event schema validation

🚀 Performance Considerations

Strengths:

  • ✅ BullMQ used for Redis (industry-standard, battle-tested)
  • ✅ Bulk operations for multiple events (queue_.addBulk())
  • ✅ Lazy connection in Redis loader
  • ✅ Proper job cleanup (removeOnComplete: true)

Potential Concerns:

  1. Deep cloning in grouped events (local.ts:128): JSON.parse(JSON.stringify(groupedEvents)) - consider if this is necessary or if shallow copy suffices
  2. No batch size limits for emit([...]) - could overwhelm Redis with thousands of events
  3. Promise.all for subscriber execution - one slow subscriber blocks others (might be intentional)

✅ Recommendations Summary

High Priority (Should fix before merge):

  1. ✅ Fix Redis connection error handling to prevent silent failures
  2. ✅ Handle errors in delayed event emissions properly
  3. ✅ Add validateOptions to RedisEventProvider
  4. ✅ Document/justify the singleton EventEmitter pattern or refactor

Medium Priority (Should consider):

  1. Improve error handling in Redis worker (use Promise.allSettled)
  2. Add edge case tests for provider failures
  3. Add payload size limits and documentation

Low Priority (Nice to have):

  1. Structured logging
  2. Migration guide in PR description
  3. Consider rate limiting support

📊 Overall Assessment

Code Quality: ⭐⭐⭐⭐ (4/5)
Architecture: ⭐⭐⭐⭐⭐ (5/5)
Test Coverage: ⭐⭐⭐ (3/5)
Documentation: ⭐⭐⭐⭐ (4/5)
Security: ⭐⭐⭐⭐ (4/5)

Verdict: Approve with requested changes

This is a well-designed architectural improvement that will make the event system more flexible and maintainable. The core design is excellent, following Medusa conventions and providing clear extension points.

The identified issues are primarily around error handling and edge cases rather than fundamental design flaws. Addressing the high-priority items (especially Redis connection error handling) will make this production-ready.

Great work on maintaining backward compatibility and providing clear migration paths! 🎉


🔗 Related Files Reviewed

  • packages/core/types/src/event/provider.ts - Interface definitions ✅
  • packages/core/utils/src/event/abstract-event-provider.ts - Base class ✅
  • packages/modules/event/src/services/event-module.ts - Main service ✅
  • packages/modules/event/src/providers/local.ts - Local implementation ✅
  • packages/modules/providers/event-redis/src/services/event-redis.ts - Redis implementation ✅
  • Integration tests and migration adapters ✅

@adrien2p adrien2p marked this pull request as ready for review January 14, 2026 14:44
@adrien2p adrien2p requested review from a team as code owners January 14, 2026 14:44
cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@olivermrbl olivermrbl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, nice work!

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

@adrien2p
Copy link
Member Author

@olivermrbl should be good for a last review to be sure 👍

Copy link
Contributor

@olivermrbl olivermrbl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, two non-blocking nits

* Redis-based event provider using BullMQ for reliable event processing.
* Supports priority, retry, and distributed event handling.
*/
export class RedisEventProvider extends EventsUtils.AbstractEventsProvider<EventRedisProviderOptions> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export class RedisEventProvider extends EventsUtils.AbstractEventsProvider<EventRedisProviderOptions> {
export class RedisEventsProvider extends EventsUtils.AbstractEventsProvider<EventRedisProviderOptions> {

cursor[bot]

This comment was marked as outdated.

@adrien2p
Copy link
Member Author

@claude review this pleaase, be succinte and to the point. I d like an overall assesment as well

@claude
Copy link

claude bot commented Jan 23, 2026

Pull Request Review: Event Module with Provider Architecture

Summary

This PR introduces a significant architectural improvement by implementing a provider-based events module that replaces the existing event-bus architecture. The implementation is well-structured and maintains backward compatibility while providing a more extensible foundation.

✅ Strengths

Architecture & Design

  • Clean provider abstraction: AbstractEventsProvider provides an excellent base class with proper separation of concerns
  • Lifecycle hooks: Proper integration with application lifecycle (onApplicationStart, onApplicationShutdown, onApplicationPrepareShutdown)
  • Backward compatibility: Deprecation shims in packages/medusa/src/modules/event-bus-*.ts allow smooth migration
  • Type safety: Comprehensive TypeScript types in packages/core/types/src/events/

Redis Provider Implementation

  • Smart retry logic: The completedSubscriberIds tracking (lines 401-486 in events-redis.ts) ensures failed subscribers can retry without re-executing successful ones
  • Priority handling: Well-documented priority system with sensible defaults
  • Connection management: Proper lazy connection and reconnection logic with error handling

Testing

  • Comprehensive integration tests covering core functionality (emit, subscribe, grouped events, wildcards, interceptors)

🔍 Issues & Concerns

Critical Issues

1. Race Condition in Interceptor Calls (events-redis.ts:311-322)

Severity: High

The interceptor calls are not awaited:

groupedEvents.map((jobData) => {
  // ...
  this.callInterceptors(message as any, {...})  // No await!
})

Impact: Interceptors may not complete before events are emitted, defeating their purpose.

Fix: Use await promiseAll(...) pattern:

await promiseAll(
  groupedEvents.map((jobData) => {
    const message = {...}
    return this.callInterceptors(message, {...})
  })
)

2. Unsafe Type Assertion (events-redis.ts:318)

Severity: Medium

this.callInterceptors(message as any, {...})

Impact: Bypasses TypeScript type safety, could hide type mismatches.

Fix: Properly type the message construction or use a type guard.

3. Error Handling in Abstract Provider (abstract-event-provider.ts:349-356)

Severity: Medium

protected async callInterceptors<T = unknown>(...) {
  Array.from(this.interceptorSubscribers_).map(async (interceptor) => {
    try {
      await interceptor(message, context)
    } catch (error) {
      this.logger_.error("Error in event bus interceptor:", error)
    }
  })
}

Issues:

  • .map() with async functions creates unhandled promises
  • No await means errors could be silently lost
  • Function signature is async but doesn't await anything

Fix:

protected async callInterceptors<T = unknown>(...) {
  await promiseAll(
    Array.from(this.interceptorSubscribers_).map(async (interceptor) => {
      try {
        await interceptor(message, context)
      } catch (error) {
        this.logger_.error("Error in event bus interceptor:", error)
      }
    })
  )
}

Medium Issues

4. Inconsistent subscriberId Generation (event-module.ts:98)

Severity: Medium

const subscriberId = context?.subscriberId ?? `${eventName.toString()}-${ulid()}`

This is good, but the local provider (local.ts:221) also has fallback logic that could conflict:

const subscriberId = context?.subscriberId ?? (subscriber as any).subscriberId

Recommendation: Centralize the subscriberId generation logic (which appears to be the intent based on commit 94593db).

5. Memory Leak Potential in Local Provider (local.ts:18-19)

Severity: Low-Medium

const eventEmitter = new EventEmitter()
eventEmitter.setMaxListeners(Infinity)

Concern: Shared EventEmitter across all instances with unlimited listeners could lead to memory issues.

Recommendation: Consider instance-scoped EventEmitters or monitoring listener counts.

6. Missing Validation in Redis Provider (events-redis.ts:186-197)

Severity: Low

Priority validation only warns but continues:

if (finalOptions.priority != undefined && ...) {
  this.logger_.warn(`Invalid priority value...`)
  finalOptions.priority = EventPriority.DEFAULT
}

Recommendation: Consider throwing an error for invalid priorities in development mode.

Performance Considerations

7. Grouped Events Serialization (events-redis.ts:133)

groupedEvents = JSON.parse(JSON.stringify(groupedEvents))

Concern: Deep clone of potentially large event arrays. This could be expensive.

Recommendation: Consider if deep clone is necessary or if shallow copy would suffice.

8. Redis LRANGE Without Pagination (events-redis.ts:301-304)

.lrange(`staging:${eventGroupId}`, 0, -1)

Concern: Fetches all events in a group without pagination. Could be problematic for large event groups.

Recommendation: Add pagination or document maximum recommended events per group.

Code Quality

9. Commented Code (event-bus-local.ts:11-14, event-bus-redis.ts:31-35)

// console.warn(
//   "[DEPRECATION WARNING] ..."
// )

Recommendation: Either enable these warnings or remove commented code. Users should be informed about deprecations.

10. Error Message Clarity (event-provider.ts:42-44)

const errMessage = `
Unable to retrieve the event provider with id: ${providerId}
Please make sure that the provider is registered...`

Recommendation: Include available provider IDs in the error message to aid debugging.

Security

✅ No SQL injection risks (uses parameterized Redis operations)
✅ No XSS concerns (server-side only)
✅ Proper error boundaries prevent subscriber failures from crashing the system

Test Coverage

Good:

  • Core event emission
  • Grouped events
  • Wildcard subscribers
  • Interceptors

Missing:

  • Redis provider-specific tests (priority, retry logic, connection failures)
  • Error scenarios (malformed events, provider unavailability)
  • Concurrent event emission
  • Performance/load tests

📝 Recommendations

High Priority

  1. Fix the interceptor race conditions (Product Variant Service #1, Common query patterns #3)
  2. Remove unsafe type assertions (Product Service #2)
  3. Enable or remove deprecation warnings (Investigate the structuring of roles #9)

Medium Priority

  1. Add Redis provider integration tests
  2. Document max events per group limitation
  3. Consider instance-scoped EventEmitters

Low Priority

  1. Improve error messages with context
  2. Add performance benchmarks for large event groups
  3. Document migration path more prominently in changelog

📊 Overall Assessment

Code Quality: 8/10
Architecture: 9/10
Test Coverage: 6/10
Documentation: 7/10
Security: 9/10

Overall: This is a well-architected improvement that significantly enhances the events system. The main concerns are around async/await patterns with interceptors and test coverage for the Redis provider. Once the critical issues are addressed, this will be a solid foundation for the events architecture.

The backward compatibility approach is excellent and will allow for smooth migration.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants