**What**
This PR introduces experimental Hot Module Replacement (HMR) for the Medusa backend, enabling developers to see code changes reflected immediately without restarting the server. This significantly improves the development experience by reducing iteration time.
### Key Features
- Hot reload support for:
- API Routes
- Workflows & Steps
- Scheduled Jobs
- Event Subscribers
- Modules
- IPC-based architecture: The dev server runs in a child process, communicating with the parent watcher via IPC. When HMR fails, the child process is killed and restarted, ensuring
clean resource cleanup.
- Recovery mechanism: Automatically recovers from broken module states without manual intervention.
- Graceful fallback: When HMR cannot handle a change (e.g., medusa-config.ts, .env), the server restarts completely.
### Architecture
```mermaid
flowchart TB
subgraph Parent["develop.ts (File Watcher)"]
W[Watch Files]
end
subgraph Child["start.ts (HTTP Server)"]
R[reloadResources]
R --> MR[ModuleReloader]
R --> WR[WorkflowReloader]
R --> RR[RouteReloader]
R --> SR[SubscriberReloader]
R --> JR[JobReloader]
end
W -->|"hmr-reload"| R
R -->|"hmr-result"| W
```
### How to enable it
Backend HMR is behind a feature flag. Enable it by setting:
```ts
// medusa-config.ts
module.exports = defineConfig({
featureFlags: {
backend_hmr: true
}
})
```
or
```bash
export MEDUSA_FF_BACKEND_HMR=true
```
or
```
// .env
MEDUSA_FF_BACKEND_HMR=true
```
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
Summary
This PR adds BullMQ queue and worker configuration options to the workflow-engine-redis module, bringing feature parity with the event-bus-redis module. It also introduces per-queue
configuration options for fine-grained control over the three internal queues (main, job, and cleaner).
Key changes:
- Added per-queue BullMQ configuration options (mainQueueOptions, jobQueueOptions, cleanerQueueOptions and their worker counterparts) with shared defaults
- Unified Redis option naming across modules: deprecated url → redisUrl, options → redisOptions (with backward compatibility)
- Moved configuration resolution to the loader and registered options in the DI container
- Added comprehensive JSDoc documentation for all configuration options
- Added unit tests for option merging and queue/worker configuration
Configuration Example
```ts
// Simple configuration - same options for all queues
{
redisUrl: "redis://localhost:6379",
queueOptions: { defaultJobOptions: { removeOnComplete: 1000 } },
workerOptions: { concurrency: 10 }
}
```
```ts
// Advanced configuration - per-queue overrides
{
redisUrl: "redis://localhost:6379",
workerOptions: { concurrency: 10 }, // shared default
jobWorkerOptions: { concurrency: 5 }, // override for scheduled workflows
cleanerWorkerOptions: { concurrency: 1 } // override for cleanup (low priority)
}
```
* chore(): Run some commands in server mode only
* Create orange-timers-lay.md
* chore(): Run some commands in server mode only
* chore(): Run some commands in server mode only
* chore(): Run some commands in server mode only
* fix(): event emitting
* Create rude-queens-deny.md
* fix(): store subscriber should not be constraint
* Update rude-queens-deny.md
* Add tests to prevent regression
FIXES https://github.com/medusajs/medusa/issues/13976
**What**
Before applying the sales channel link filter, check if there is a single sales channel or many, if will add a bit of overhead but marginal compare to the improvement for big catalog with single sales channel
---
> [!NOTE]
> Optimizes store products listing by conditionally skipping sales channel link filtering when the index can be used or only one sales channel exists.
>
> - **Store API – `products`** (`packages/medusa/src/api/store/products/middlewares.ts`):
> - Adds `applyMaybeLinkFilterIfNecessary` middleware:
> - Skips link filtering when `IndexEngine` is enabled and no tag/category filters are present.
> - Queries `sales_channels` count; if only one exists, removes `sales_channel_id` filter and proceeds.
> - Integrates this middleware into `GET /store/products` before default filters; `GET /store/products/:id` remains unchanged with `maybeApplyLinkFilter`.
> - **Release**:
> - Adds patch changeset for `@medusajs/medusa`.
>
> <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 8a0b024ea1bc9696f350f34fa77df321e5ecf553. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup>
* chore(): Cleanup and organize deps
* chore(): Cleanup and organize deps
* chore(): Cleanup and organize deps
* chore(): Cleanup and organize deps
* chore(): Cleanup and organize deps
* chore(): Cleanup and organize deps
* Create lucky-poets-scream.md
* chore(): Cleanup and organize deps
* chore(): Cleanup and organize deps
* chore(): Cleanup and organize deps
* chore(): Cleanup and organize deps
* dedupe snapshot this build
* split into 4 shard
* re configure packages integration tests
* re configure packages integration tests
* re configure packages integration tests
* re configure packages integration tests
* update scripts
* update scripts
* update scripts
* update scripts
* update scripts
* update scripts
* update scripts
* update scripts
* reduce shard for packages
* feat(index): Add support to trigger sync manually
* feat(index): Add API route to interact with
* feat(index): Add API route to interact with
* feat(index): Add API route to interact with
* test(): Add http integration tests
* Create weak-elephants-reply.md
**What**
After lot of investigation, we finally found one of our performance regerssion point (see [here](https://github.com/mikro-orm/mikro-orm/issues/6905)), this pr downgrade mikro orm and move the strategy back to select in where needed
**What**
It seems that for some reason the weak map fail in some scenario, but after investigation, the usage of map would not have a bad impact as it will be released after the Distributed transaction if finished. Therefore, falling back to Map instead
FIXES https://github.com/medusajs/medusa/issues/13654
NOTE: Waiting for the user feedback as he is also using node 18. We also use the exact same pattern in all our core flows without issues 🤔
RESOLVES CORE-1153
**What**
- This pr mainly lay the foundation the caching layer. It comes with a modules (built in memory cache) and a redis provider.
- Apply caching to few touch point to test
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
**What**
The context reference is being mutated by the repository leading to an empty context. Also, the filter is built from the pricing context instead of pricing context -> context leading to always fetch all preferences all the time
PARTIALLY RESOLVES CORE-1156
**What**
Improve upsertWithReplace to batch as much as possible what can be batched. Performance of this method will be much greater specially for cases with maybe entities and batch (e.g we seen many cases where they bulk product with hundreds variants and options etc)
for example let take the following object:
- entity 1
- entity 2 []
- entity 3 []
- entity 2 []
- entity 3 []
here all entity 3 will be batched and all entity 2 will be batched
I ve also added a pretty detail test that check all the stage and what is batched or not with many comments so that it is less harder to consume and remember in the future
Also includes:
- mikro orm upgade (issues found and fixes)
- order module hooks fixes
**NOTE**
It was easier for now to do this instead of rewriting the different areas where it is being used, also, maybe it means that we will have closer performance to what we would expect to have natively
**NOTE 2**
Also fix the fact that integration tests of the core packages never ran 😂
RESOLVES CORE-1204
**What**
- Fix wrong price tier when multiple items are targetting the same variant
- fix type import from the wrong package
**Notes**
If you are struggling navigating the changes, you can focus on the following files:
```
integration-tests/http/__tests__/cart/store/cart.spec.ts
integration-tests/modules/__tests__/cart/store/cart.workflows.spec.ts
packages/core/core-flows/src/cart/steps/get-promotion-codes-to-apply.ts
packages/core/core-flows/src/cart/steps/get-variant-price-sets.ts
packages/core/core-flows/src/cart/workflows/add-to-cart.ts
packages/core/core-flows/src/cart/workflows/create-carts.ts
packages/core/core-flows/src/cart/workflows/get-variants-and-items-with-prices.ts
packages/core/core-flows/src/cart/workflows/refresh-cart-items.ts
packages/core/core-flows/src/order/workflows/add-line-items.ts
packages/core/core-flows/src/order/workflows/create-order.ts
```
* chore(): remove ssl_mode from url and also use sslmode
* improve regexp
* chore(): remove ssl_mode from url and also use sslmode
* chore(): remove ssl_mode from url and also use sslmode
* Update SSL mode configuration in changeset
Removed 'ssl_mode' from URL and replaced it with 'sslmode'.
RESOLVES CORE-1206
**What**
Instead of removing cleaner repeatable job and risk to remove it while other instances are still up, we always create it, since the id of a job is unique and we give one to the job, if already present it wont get added
* chore(): Shard unit tests jobs
* pass with no tests
* try artifacts
* Revert "try artifacts"
This reverts commit 43663b46ea1fb2d3131182b733ab6e202ea83f1d.
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* test(): test dynamic max workers
* Clarify test description and improve CI
**What**
- Remove overserialization withing transaction and rely more on internal service or protected method instead which does not serialize and work with Infered entity type
- Slightly rework loop complexity
Overall, this gives a good spare of resources and time spent for serialization
* chore(): Upgrade mikro orm
* handle 'null' value for big number props
* 6.5.2
* remove only
* fix pricing module rule value
* switch select in strategy for balances
* revert to select in strategy for order module
* fix defining DML ManyToOne
* fix define relationship
* test fix
* more fixes
* change order strategy to balanced
* change order strategy to balanced
* prevent unnecessary manager fork
* revert generated www changes
* remove unnecessary changes
* Create real-cobras-deny.md
* address feedback
---------
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
RESOLVES CORE-1171
**What**
- Reduced async overhead for objects with mixed sync/async properties
- Lower memory pressure from eliminated promise allocations
- Faster primitive value processing with early returns
- Better concurrency through selective batching of truly async operations
- Event loop friendly behavior preventing artificial delays
- Reduced memory allocation from eliminated duplicate processing
- Decreased GC pressure from WeakMap-based caching instead of map
**note**
Now, `resolveValue`always treat every resoltion as sync operation unless it is not, meaning we do not create promise overhead when not necessary and only when actually treating with promises
RESOLVES CORE-1163
RESOLVES CORE-1164
**What**
### Add support for non auto retryable steps.
When marking a step with `maxRetries`, when it will fail it will be marked as temporary failure and then retry itself automatically. Thats the default behaviour, if you now add `autoRetry: false`, when the step will fail it will be marked as temporary failure but not retry automatically. you can now call the workflow engine run to resume the workflow from the failing step to be retried.
### Add support for `maxAwaitingRetries`
When setting `retyIntervalAwaiting` a step that is awaiting will be retried after the specified interval without maximun retry. Now you can set `maxAwaitingRetries` to force a maximum awaiting retry number
### Add support to manually retry an awaiting step
In some scenario, either a machine dies while a step is executing or a step is taking longer than expected, you can now call `retryStep` on the workflow engine to force a retry of the step that is supposedly stucked
RESOLVES CORE-1155
**What**
- prevent update payment collection worklow to fetch data and call steps when there is no payment collection
- prevent refresh Cart Shipping Methods Workflow to fetch data and call steps when there is no shipping methods
- update promotion step to remove extraneous module call
* chore(): only execute race execution checks for async workflows
* chore(): workflow redis publish only for async flows
* Create cyan-gorillas-poke.md
* chore(): workflow redis publish only for async flows
* fix negative check
---------
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
* fix(orchestration): Use the step definition max retries on set step failure
* Create sweet-turkeys-wait.md
* allow to force permanent failure
* update changeset
* chore(pricing): Fix excessive db queries during price sets update
* chore(pricing): Fix excessive db queries during price sets update
* finalize upsert with replace of the rules
* fix limit
* Create quiet-pumpkins-hang.md
---------
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>