docs: docs for next release (#13621)

* docs: docs for next release

* changes to opentelemetry dependencies

* document plugin env variables

* document admin changes

* fix vale error

* add version notes

* document campaign budget updates

* document campaign changes in user guide

* document chages in cluster mode cli

* documented once promotion allocation

* document multiple API keys support
This commit is contained in:
Shahed Nasser
2025-10-21 10:32:08 +03:00
committed by GitHub
parent f38f0f9aca
commit ed715813a5
54 changed files with 1621 additions and 252 deletions
+21
View File
@@ -0,0 +1,21 @@
import { ChildDocs } from "docs-ui"
export const metadata = {
title: `${pageNumber} Medusa Codemods`,
}
# {metadata.title}
In this chapter, you'll learn about Medusa codemods and the list of available codemods.
## What are Codemods?
Codemods are scripts that help you automate codebase changes. They are especially useful when updating to a new version that requires large changes to your codebase.
Medusa provides codemods to help you update your codebase. Use these codemods when updating to their respective versions.
---
## List of Medusa Codemods
<ChildDocs type="item" />
@@ -0,0 +1,279 @@
export const metadata = {
title: `${pageNumber} Replace Imports Codemod (v2.11.0+)`,
}
# {metadata.title}
In this chapter, you'll learn about the codemod that helps you replace imports in your codebase when upgrading to Medusa v2.11.0.
## What is the Replace Imports Codemod?
[Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) optimized the package structure by consolidating several external packages into the `@medusajs/framework` package.
Previously, you had to install and manage packages related to MikroORM, Awilix, OpenTelemetry, and the `pg` package separately in your Medusa application. Starting with v2.11.0, these packages are included in the `@medusajs/framework` package.
For example, instead of importing `@mikro-orm/core`, you now import it from `@medusajs/framework/mikro-orm/core`. This applies to all of the following packages:
- `@mikro-orm/*` packages (for example, `@mikro-orm/core`, `@mikro-orm/migrations`, etc.) -> `@medusajs/framework/mikro-orm/{subpath}`
- `awilix` -> `@medusajs/framework/awilix`
- `pg` -> `@medusajs/framework/pg`
- `@opentelemetry/instrumentation-pg` -> `@medusajs/framework/opentelemetry/instrumentation-pg`
- `@opentelemetry/resources` -> `@medusajs/framework/opentelemetry/resources`
- `@opentelemetry/sdk-node` -> `@medusajs/framework/opentelemetry/sdk-node`
- `@opentelemetry/sdk-trace-node` -> `@medusajs/framework/opentelemetry/sdk-trace-node`
To help you update your codebase to reflect these changes, Medusa provides a codemod that automatically replaces imports of these packages throughout your codebase.
---
## Using the Replace Imports Codemod
To use the replace imports codemod, create the file `replace-imports.js` in the root of your Medusa application with the following content:
```js
#!/usr/bin/env node
const fs = require("fs")
const path = require("path")
const { execSync } = require("child_process")
/**
* Script to replace imports and require statements from mikro-orm/{subpath}, awilix, and pg
* to their @medusajs/framework equivalents
*/
// Define the replacement mappings
const replacements = [
// MikroORM imports - replace mikro-orm/{subpath} with @medusajs/framework/mikro-orm/{subpath}
{
pattern: /from\s+['"]@?mikro-orm\/([^'"]+)['"]/g,
// eslint-disable-next-line quotes
replacement: 'from "@medusajs/framework/mikro-orm/$1"',
},
// Awilix imports - replace awilix with @medusajs/framework/awilix
{
pattern: /from\s+['"]awilix['"]/g,
// eslint-disable-next-line quotes
replacement: 'from "@medusajs/framework/awilix"',
},
// PG imports - replace pg with @medusajs/framework/pg
{
pattern: /from\s+['"]pg['"]/g,
// eslint-disable-next-line quotes
replacement: 'from "@medusajs/framework/pg"',
},
// OpenTelemetry imports - replace @opentelemetry/instrumentation-pg, @opentelemetry/resources,
// @opentelemetry/sdk-node, and @opentelemetry/sdk-trace-node with @medusajs/framework/opentelemetry/{subpath}
{
pattern: /from\s+['"]@?opentelemetry\/(instrumentation-pg|resources|sdk-node|sdk-trace-node)['"]/g,
// eslint-disable-next-line quotes
replacement: 'from "@medusajs/framework/opentelemetry/$1"',
},
// MikroORM require statements - replace require('@?mikro-orm/{subpath}') with require('@medusajs/framework/mikro-orm/{subpath}')
{
pattern: /require\s*\(\s*['"]@?mikro-orm\/([^'"]+)['"]\s*\)/g,
// eslint-disable-next-line quotes
replacement: 'require("@medusajs/framework/mikro-orm/$1")',
},
// Awilix require statements - replace require('awilix') with require('@medusajs/framework/awilix')
{
pattern: /require\s*\(\s*['"]awilix['"]\s*\)/g,
// eslint-disable-next-line quotes
replacement: 'require("@medusajs/framework/awilix")',
},
// PG require statements - replace require('pg') with require('@medusajs/framework/pg')
{
pattern: /require\s*\(\s*['"]pg['"]\s*\)/g,
// eslint-disable-next-line quotes
replacement: 'require("@medusajs/framework/pg")',
},
// OpenTelemetry require statements - replace require('@opentelemetry/instrumentation-pg'),
// require('@opentelemetry/resources'), require('@opentelemetry/sdk-node'), and
// require('@opentelemetry/sdk-trace-node') with require('@medusajs/framework/opentelemetry/{subpath}')
{
pattern: /require\s*\(\s*['"]@?opentelemetry\/(instrumentation-pg|resources|sdk-node|sdk-trace-node)['"]\s*\)/g,
// eslint-disable-next-line quotes
replacement: 'require("@medusajs/framework/opentelemetry/$1")',
},
]
function processFile(filePath) {
try {
const content = fs.readFileSync(filePath, "utf8")
let modifiedContent = content
let wasModified = false
replacements.forEach(({ pattern, replacement }) => {
const newContent = modifiedContent.replace(pattern, replacement)
if (newContent !== modifiedContent) {
wasModified = true
modifiedContent = newContent
}
})
if (wasModified) {
fs.writeFileSync(filePath, modifiedContent)
console.log(`✓ Updated: ${filePath}`)
return true
}
return false
} catch (error) {
console.error(`✗ Error processing ${filePath}:`, error.message)
return false
}
}
function getTargetFiles() {
try {
// Get the current script's filename to exclude it from processing
const currentScript = path.basename(__filename)
// Find TypeScript/JavaScript files, excluding common directories that typically don't contain target imports
const findCommand = `find . -name node_modules -prune -o -name .git -prune -o -name dist -prune -o -name build -prune -o -name coverage -prune -o -name "*.ts" -print -o -name "*.js" -print -o -name "*.tsx" -print -o -name "*.jsx" -print`
const files = execSync(findCommand, {
encoding: "utf8",
maxBuffer: 50 * 1024 * 1024, // 50MB buffer
})
.split("\n")
.filter((line) => line.trim())
console.log(files)
const targetFiles = []
let processedCount = 0
console.log(`📄 Scanning ${files.length} files for target imports and require statements...`)
for (const file of files) {
try {
// Skip the current script file
const fileName = path.basename(file)
if (fileName === currentScript) {
processedCount++
continue
}
const content = fs.readFileSync(file, "utf8")
if (
/from\s+['"]@?mikro-orm\//.test(content) ||
/from\s+['"]awilix['"]/.test(content) ||
/from\s+['"]pg['"]/.test(content) ||
/require\s*\(\s*['"]@?mikro-orm\//.test(content) ||
/require\s*\(\s*['"]awilix['"]/.test(content) ||
/require\s*\(\s*['"]pg['"]/.test(content)
) {
targetFiles.push(file.startsWith("./") ? file.slice(2) : file)
}
processedCount++
if (processedCount % 100 === 0) {
process.stdout.write(
`\r📄 Processed ${processedCount}/${files.length} files...`
)
}
} catch (fileError) {
// Skip files that can't be read
continue
}
}
if (processedCount > 0) {
console.log(`\r📄 Processed ${processedCount} files. `)
}
return targetFiles
} catch (error) {
console.error("Error finding target files:", error.message)
return []
}
}
function main() {
console.log("🔄 Finding files with target imports and require statements...")
const targetFiles = getTargetFiles()
if (targetFiles.length === 0) {
console.log("️ No files found with target imports or require statements.")
return
}
console.log(`📁 Found ${targetFiles.length} files to process`)
let modifiedCount = 0
let errorCount = 0
targetFiles.forEach((filePath) => {
const fullPath = path.resolve(filePath)
if (fs.existsSync(fullPath)) {
if (processFile(fullPath)) {
modifiedCount++
}
} else {
console.warn(`⚠️ File not found: ${filePath}`)
errorCount++
}
})
console.log("\n📊 Summary:")
console.log(` Files processed: ${targetFiles.length}`)
console.log(` Files modified: ${modifiedCount}`)
console.log(` Errors: ${errorCount}`)
if (modifiedCount > 0) {
console.log("\n✅ Import replacement completed successfully!")
console.log("\n💡 Next steps:")
console.log(" 1. Review the changes with: git diff")
console.log(" 2. Run your tests to ensure everything works correctly")
console.log(" 3. Commit the changes if you're satisfied")
} else {
console.log(
"\n✅ No modifications needed - all imports are already correct!"
)
}
}
// Run if called directly
if (require.main === module) {
main()
}
module.exports = { processFile, getTargetFiles, main }
```
This script scans your project for files that import from `mikro-orm/{subpath}`, `awilix`, or `pg`, and replaces those imports with their new equivalents from `@medusajs/framework`. It handles both ES module `import` statements and CommonJS `require` statements in JavaScript and TypeScript files.
Next, run the following command in your terminal to make the script executable:
<Note title="Windows Users">
You can run the script using `node` without changing permissions.
</Note>
```bash
chmod +x replace-imports.js
```
Finally, execute the script with the following command:
```bash
node replace-imports.js
```
This will scan your project files, apply the necessary import replacements, and provide a summary of the changes made.
---
## Next Steps
After running the codemod, review the changes made to your codebase. You can use `git diff` to see the modifications. Additionally, run your tests to ensure everything works as expected.
If everything is working correctly, you can remove the `replace-imports.js` file from your project. You can also remove the following packages from your `package.json`, as they're now included in the `@medusajs/framework` package:
- `@mikro-orm/*` packages (for example, `@mikro-orm/core`, `@mikro-orm/migrations`, etc.)
- `awilix`
- `pg`
- `@opentelemetry/instrumentation-pg`
- `@opentelemetry/resources`
- `@opentelemetry/sdk-node`
- `@opentelemetry/sdk-trace-node`
@@ -40,13 +40,13 @@ Medusa uses [OpenTelemetry](https://opentelemetry.io/) for instrumentation and r
### Install Dependencies
Start by installing the following OpenTelemetry dependencies in your Medusa project:
<Note>
```bash npm2yarn
npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg
```
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), OpenTelemetry dependencies are installed by default in new Medusa projects. If you're using an older version of Medusa, you need to install the `@opentelemetry/sdk-node`, `@opentelemetry/resources`, `@opentelemetry/sdk-trace-node`, and `@opentelemetry/instrumentation-pg` dependencies.
Also, install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies:
</Note>
Before you start, you must install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies:
```bash npm2yarn
npm install @opentelemetry/exporter-zipkin
@@ -63,8 +63,14 @@ if (process.env.TEST_TYPE === "integration:http") {
Next, create the `integration-tests/setup.js` file with the following content:
<Note>
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the require statement to `@mikro-orm/core`.
</Note>
```js title="integration-tests/setup.js"
const { MetadataStorage } = require("@mikro-orm/core")
const { MetadataStorage } = require("@medusajs/framework/@mikro-orm/core")
MetadataStorage.clear()
```
@@ -98,9 +98,48 @@ For example, the `VITE_MY_API_KEY` environment variable in the example above wil
## Environment Variables in Plugins
As explained in the [previous section](#environment-variables-in-production), environment variables are inlined into the build. This presents a limitation for plugins, where you can't use environment variables.
<Note>
Instead, only the following global variable is available in plugins:
Environment variable support in plugins is available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). Refer to the [Medusa versions prior to v2.11.0](#for-medusa-versions-prior-to-v2110) section for more details if you're using an earlier version.
</Note>
For plugins, you can use environment variables without a prefix. Then, Medusa applications that use the plugin can set the environment variable with the `PLUGIN_` prefix.
For example, you can create a widget in your plugin that uses the `MY_API_KEY` environment variable:
```tsx highlights={[["8"]]}
import { defineWidgetConfig } from "@medusajs/admin-sdk"
import { Container, Heading } from "@medusajs/ui"
const ProductWidget = () => {
return (
<Container className="divide-y p-0">
<div className="flex items-center justify-between px-6 py-4">
<Heading level="h2">API Key: {import.meta.env.MY_API_KEY}</Heading>
</div>
</Container>
)
}
export const config = defineWidgetConfig({
zone: "product.details.before",
})
export default ProductWidget
```
Then, in the Medusa application that uses the plugin, set the environment variable with the `PLUGIN_` prefix:
```bash
PLUGIN_MY_API_KEY=sk_123
```
The `MY_API_KEY` environment variable in the plugin will be replaced with the value of `PLUGIN_MY_API_KEY` during the build process of the Medusa application.
### Global Variables in Plugins
Plugins also have the following global variables available:
- `__BACKEND_URL__`: The URL of the Medusa backend, as set in the [Medusa configurations](../../../configurations/medusa-config/page.mdx#backendurl).
- `__BASE__`: The base path of the Medusa Admin. (For example, `/app`).
@@ -137,4 +176,14 @@ To fix possible type errors, create the file `src/admin/vite-env.d.ts` and add t
declare const __BACKEND_URL__: string
declare const __BASE__: string
declare const __STOREFRONT_URL__: string
```
```
### For Medusa versions prior to v2.11.0
<Details summaryContent="Instructions for Medusa versions prior to v2.11.0">
As explained in the [Environment Variables in Production section](#environment-variables-in-production), environment variables are inlined into the build. This presents a limitation for plugins, where you can't use environment variables.
Instead, you can use the [Plugin Global Variables](#global-variables-in-plugins) described above to access the backend URL, base path, and storefront URL.
</Details>
@@ -63,8 +63,14 @@ npx medusa db:generate blog
The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for. It will create a migration file for the Blog Module in the directory `src/modules/blog/migrations` similar to the following:
<Note>
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`.
</Note>
```ts
import { Migration } from "@mikro-orm/migrations"
import { Migration } from "@medusajs/framework/@mikro-orm/migrations"
export class Migration20241121103722 extends Migration {
@@ -37,8 +37,14 @@ You can also write migrations manually. To do that, create a file in the `migrat
For example:
<Note>
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`.
</Note>
```ts title="src/modules/blog/migrations/Migration202507021059_create_author.ts"
import { Migration } from "@mikro-orm/migrations"
import { Migration } from "@medusajs/framework/@mikro-orm/migrations"
export class Migration202507021059 extends Migration {
@@ -53,7 +59,7 @@ export class Migration202507021059 extends Migration {
}
```
The migration class in the file extends the `Migration` class imported from `@mikro-orm/migrations`. In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax.
The migration class in the file extends the `Migration` class imported from `@medusajs/framework/@mikro-orm/migrations`. In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax.
In the example above, the `up` method creates the table `author`, and the `down` method drops the table if the migration is reverted.
@@ -27,6 +27,12 @@ So, to run database queries in a service:
For example, in your service, add the following methods:
<Note>
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`.
</Note>
export const methodsHighlight = [
["13", "getCount", "Retrieves the number of records in `my_custom` using the `count` method."],
["20", "getCountSql", "Retrieves the number of records in `my_custom` using the `execute` method."]
@@ -39,7 +45,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -66,7 +72,7 @@ class BlogModuleService {
You add two methods `getCount` and `getCountSql` that have the `InjectManager` decorator. Each of the methods also accept the `sharedContext` parameter which has the `MedusaContext` decorator.
The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the @mikro-orm/knex package](https://mikro-orm.io/api/knex/class/EntityManager).
The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the `@medusajs/framework/@mikro-orm/knex` package](https://mikro-orm.io/api/knex/class/EntityManager).
You use the manager in the `getCount` method to retrieve the number of records in a table, and in the `getCountSql` to run a PostgreSQL query that retrieves the count.
@@ -113,7 +119,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -174,7 +180,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -221,7 +227,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -380,7 +386,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -512,7 +518,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -582,7 +588,7 @@ The second parameter of the `baseRepository_.transaction` method is an object of
```ts highlights={[["24"]]}
// other imports...
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
import {
InjectTransactionManager,
MedusaContext,
@@ -625,8 +631,8 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { IsolationLevel } from "@mikro-orm/core"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
import { IsolationLevel } from "@medusajs/framework/@mikro-orm/core"
class BlogModuleService {
// ...
@@ -660,7 +666,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -152,6 +152,12 @@ Consider your have a MongoDB module that allows you to perform operations on a M
To connect to the database, you create the following loader in your module:
<Note>
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), Awilix dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `awilix`.
</Note>
export const loaderHighlights = [
["5", "ModuleOptions", "Define a type for expected options."],
["13", "ModuleOptions", "Pass the option type as a type argument to `LoaderOptions`."],
@@ -163,7 +169,7 @@ export const loaderHighlights = [
```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights}
import { LoaderOptions } from "@medusajs/framework/types"
import { asValue } from "awilix"
import { asValue } from "@medusajs/framework/awilix"
import { MongoClient } from "mongodb"
type ModuleOptions = {
@@ -233,7 +239,7 @@ In the loader, you check first that these options are set before proceeding. The
After creating the client, you register it in the module's container using the container's `register` method. The method accepts two parameters:
1. The key to register the resource under, which in this case is `mongoClient`. You'll use this name later to resolve the client.
2. The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an `asValue` function imported from the [awilix package](https://github.com/jeffijoe/awilix), which is the package used to implement the container functionality in Medusa.
2. The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an `asValue` function imported from the [`@medusajs/framework/awilix` package](https://github.com/jeffijoe/awilix), which is the package used to implement the container functionality in Medusa.
### Use Custom Registered Resource in Module's Service
@@ -195,8 +195,14 @@ npx medusa db:generate blog
The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for. It will create a migration file for the Blog Module in the directory `src/modules/blog/migrations` similar to the following:
<Note>
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`.
</Note>
```ts
import { Migration } from "@mikro-orm/migrations"
import { Migration } from "@medusajs/framework/@mikro-orm/migrations"
export class Migration20241121103722 extends Migration {
@@ -70,17 +70,10 @@ A basic v2 project has the following dependencies in `package.json`:
"@medusajs/admin-sdk": "2.8.2",
"@medusajs/cli": "2.8.2",
"@medusajs/framework": "2.8.2",
"@medusajs/medusa": "2.8.2",
"@mikro-orm/core": "6.4.3",
"@mikro-orm/knex": "6.4.3",
"@mikro-orm/migrations": "6.4.3",
"@mikro-orm/postgresql": "6.4.3",
"awilix": "^8.0.1",
"pg": "^8.13.0"
"@medusajs/medusa": "2.8.2"
},
"devDependencies": {
"@medusajs/test-utils": "2.8.2",
"@mikro-orm/cli": "6.4.3",
"@swc/core": "1.5.7",
"@swc/jest": "^0.2.36",
"@types/jest": "^29.5.13",
@@ -107,25 +100,15 @@ The main changes are:
- `@medusajs/framework`
- `@medusajs/medusa`
- `@medusajs/test-utils` (as a dev dependency)
- You need to install the following extra packages:
- Database packages:
- `@mikro-orm/core@6.4.3`
- `@mikro-orm/knex@6.4.3`
- `@mikro-orm/migrations@6.4.3`
- `@mikro-orm/postgresql@6.4.3`
- `@mikro-orm/cli@6.4.3` (as a dev dependency)
- `pg^8.13.0`
- Framework packages:
- `awilix@^8.0.1`
- Development and Testing packages:
- `@swc/core@1.5.7`
- `@swc/jest@^0.2.36`
- `@types/node@^20.0.0`
- `jest@^29.7.0`
- `ts-node@^10.9.2`
- `typescript@^5.6.2`
- `vite@^5.2.11`
- `yalc@^1.0.0-pre.53`
- You need to install the following extra packages for development and testing:
- `@swc/core@1.5.7`
- `@swc/jest@^0.2.36`
- `@types/node@^20.0.0`
- `jest@^29.7.0`
- `ts-node@^10.9.2`
- `typescript@^5.6.2`
- `vite@^5.2.11`
- `yalc@^1.0.0-pre.53`
- Other packages, such as `@types/react` and `@types/react-dom`, are necessary for admin development and TypeScript support.
<Note>
@@ -10,7 +10,14 @@ In this chapter, you'll learn about the different modes of running a Medusa inst
## What is Worker Mode?
By default, the Medusa application runs both the server, which handles all incoming requests, and the worker, which processes background tasks, in a single process. While this setup is suitable for development, it is not optimal for production environments where background tasks can be long-running or resource-intensive.
By default, the Medusa application runs in `shared` mode, which runs:
- `server`: the application server that handles incoming requests to the application's API routes.
- `worker`: the worker that processes background tasks. This includes scheduled jobs and subscribers.
While this setup is suitable for development, it is not optimal for production environments where background tasks can be long-running or resource-intensive.
### Worker Mode in Production
In a production environment, you should deploy two separate instances of your Medusa application:
@@ -104,3 +111,27 @@ ADMIN_DISABLED=true
</CodeTab>
</CodeTabs>
---
## Dividing Resources in Cluster Mode
<Note>
The `--servers` and `--workers` options were introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0).
</Note>
When running Medusa in [cluster mode](!resources!/medusa-cli/commands/start#starting-medusa-in-cluster-mode), you can specify the number or percentage of instances that are servers or workers by passing the `--servers` and `--workers` options:
```bash
npx medusa start --cluster 4 --servers 25% --workers 75% # Use 4 CPU cores, with 25% as servers and 75% as workers
npx medusa start --cluster 4 --servers 1 --workers 3 # Use 4 CPU cores, with 1 as server and 3 as workers
npx medusa start --cluster 4 --servers 1 --workers 1 # Use 4 CPU cores, with 1 as server and 1 as worker (the remaining 2 will run in shared mode)
```
In the above snippet you can see the following examples:
- In the first example, 25% of the instances (1 out of 4) will run as servers, and 75% (3 out of 4) will run as workers.
- In the second example, 1 instance will run as a server, and 3 instances will run as workers.
- In the third example, 1 instance will run as a server, and 1 instance will run as a worker. The remaining 2 instances will run in shared mode
+12 -10
View File
@@ -18,9 +18,9 @@ export const generatedEditDates = {
"app/learn/fundamentals/events-and-subscribers/page.mdx": "2025-10-16T09:36:04.864Z",
"app/learn/fundamentals/modules/container/page.mdx": "2025-07-31T14:24:04.087Z",
"app/learn/fundamentals/workflows/execute-another-workflow/page.mdx": "2025-08-01T07:28:51.036Z",
"app/learn/fundamentals/modules/loaders/page.mdx": "2025-06-16T13:34:16.462Z",
"app/learn/fundamentals/modules/loaders/page.mdx": "2025-10-09T11:41:31.724Z",
"app/learn/fundamentals/admin/widgets/page.mdx": "2025-07-25T15:08:07.035Z",
"app/learn/fundamentals/data-models/page.mdx": "2025-03-18T07:55:56.252Z",
"app/learn/fundamentals/data-models/page.mdx": "2025-10-09T11:39:30.944Z",
"app/learn/fundamentals/modules/remote-link/page.mdx": "2024-09-30T08:43:53.127Z",
"app/learn/fundamentals/api-routes/protected-routes/page.mdx": "2025-06-19T16:04:36.064Z",
"app/learn/fundamentals/workflows/add-workflow-hook/page.mdx": "2025-07-18T11:33:15.959Z",
@@ -32,7 +32,7 @@ export const generatedEditDates = {
"app/learn/fundamentals/admin/page.mdx": "2025-07-25T12:46:15.466Z",
"app/learn/fundamentals/workflows/long-running-workflow/page.mdx": "2025-08-01T07:16:21.736Z",
"app/learn/fundamentals/workflows/constructor-constraints/page.mdx": "2025-08-01T13:11:18.823Z",
"app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-07-25T13:53:00.692Z",
"app/learn/fundamentals/data-models/write-migration/page.mdx": "2025-10-09T11:39:07.940Z",
"app/learn/fundamentals/data-models/manage-relationships/page.mdx": "2025-04-25T14:16:41.124Z",
"app/learn/fundamentals/modules/remote-query/page.mdx": "2024-07-21T21:20:24+02:00",
"app/learn/fundamentals/modules/options/page.mdx": "2025-03-18T15:12:34.510Z",
@@ -53,7 +53,7 @@ export const generatedEditDates = {
"app/learn/debugging-and-testing/testing-tools/integration-tests/api-routes/page.mdx": "2025-09-02T08:36:12.714Z",
"app/learn/debugging-and-testing/testing-tools/integration-tests/page.mdx": "2024-12-09T15:52:01.019Z",
"app/learn/debugging-and-testing/testing-tools/integration-tests/workflows/page.mdx": "2025-07-30T13:43:44.636Z",
"app/learn/debugging-and-testing/testing-tools/page.mdx": "2025-07-23T15:32:18.008Z",
"app/learn/debugging-and-testing/testing-tools/page.mdx": "2025-10-09T11:38:33.099Z",
"app/learn/debugging-and-testing/testing-tools/unit-tests/module-example/page.mdx": "2024-09-02T11:04:27.232Z",
"app/learn/debugging-and-testing/testing-tools/unit-tests/page.mdx": "2024-09-02T11:03:26.997Z",
"app/learn/fundamentals/modules/service-constraints/page.mdx": "2025-03-18T15:12:46.006Z",
@@ -66,10 +66,10 @@ export const generatedEditDates = {
"app/learn/fundamentals/module-links/directions/page.mdx": "2025-03-17T12:52:06.161Z",
"app/learn/fundamentals/module-links/page.mdx": "2025-04-17T08:50:17.036Z",
"app/learn/fundamentals/module-links/query/page.mdx": "2025-08-15T12:06:30.572Z",
"app/learn/fundamentals/modules/db-operations/page.mdx": "2025-04-25T14:26:25.000Z",
"app/learn/fundamentals/modules/db-operations/page.mdx": "2025-10-09T11:43:28.746Z",
"app/learn/fundamentals/modules/multiple-services/page.mdx": "2025-03-18T15:11:44.632Z",
"app/learn/fundamentals/modules/page.mdx": "2025-07-18T15:31:32.371Z",
"app/learn/debugging-and-testing/instrumentation/page.mdx": "2025-06-16T10:40:52.922Z",
"app/learn/fundamentals/modules/page.mdx": "2025-10-09T11:41:57.515Z",
"app/learn/debugging-and-testing/instrumentation/page.mdx": "2025-10-09T11:37:32.815Z",
"app/learn/fundamentals/api-routes/additional-data/page.mdx": "2025-04-17T08:50:17.036Z",
"app/learn/fundamentals/workflows/variable-manipulation/page.mdx": "2025-04-24T13:14:43.967Z",
"app/learn/customization/custom-features/api-route/page.mdx": "2025-10-16T11:23:11.195Z",
@@ -113,7 +113,7 @@ export const generatedEditDates = {
"app/learn/resources/usage/page.mdx": "2025-02-26T13:35:34.824Z",
"app/learn/configurations/medusa-config/page.mdx": "2025-09-30T06:04:15.705Z",
"app/learn/configurations/ts-aliases/page.mdx": "2025-07-23T15:32:18.008Z",
"app/learn/production/worker-mode/page.mdx": "2025-07-18T15:19:45.352Z",
"app/learn/production/worker-mode/page.mdx": "2025-10-13T10:33:27.403Z",
"app/learn/fundamentals/module-links/read-only/page.mdx": "2025-10-15T15:42:22.610Z",
"app/learn/fundamentals/data-models/properties/page.mdx": "2025-10-15T05:36:40.576Z",
"app/learn/fundamentals/framework/page.mdx": "2025-06-26T14:26:22.120Z",
@@ -125,12 +125,14 @@ export const generatedEditDates = {
"app/learn/introduction/build-with-llms-ai/page.mdx": "2025-10-02T15:10:49.394Z",
"app/learn/installation/docker/page.mdx": "2025-07-23T15:34:18.530Z",
"app/learn/fundamentals/generated-types/page.mdx": "2025-07-25T13:17:35.319Z",
"app/learn/introduction/from-v1-to-v2/page.mdx": "2025-09-01T06:34:41.179Z",
"app/learn/introduction/from-v1-to-v2/page.mdx": "2025-09-29T15:33:38.811Z",
"app/learn/debugging-and-testing/debug-workflows/page.mdx": "2025-07-30T13:45:14.117Z",
"app/learn/fundamentals/data-models/json-properties/page.mdx": "2025-07-31T14:25:01.268Z",
"app/learn/debugging-and-testing/logging/custom-logger/page.mdx": "2025-08-28T15:37:07.328Z",
"app/learn/fundamentals/scheduled-jobs/interval/page.mdx": "2025-09-02T08:36:12.714Z",
"app/learn/debugging-and-testing/feature-flags/create/page.mdx": "2025-09-02T08:36:12.714Z",
"app/learn/debugging-and-testing/feature-flags/page.mdx": "2025-09-02T08:36:12.714Z",
"app/learn/fundamentals/workflows/locks/page.mdx": "2025-09-15T09:37:00.808Z"
"app/learn/fundamentals/workflows/locks/page.mdx": "2025-09-15T09:37:00.808Z",
"app/learn/codemods/page.mdx": "2025-09-29T15:40:03.620Z",
"app/learn/codemods/replace-imports/page.mdx": "2025-10-09T11:37:44.754Z"
}
+23 -2
View File
@@ -1290,9 +1290,9 @@ export const generatedSidebars = [
"isPathHref": true,
"type": "link",
"path": "/learn/production/worker-mode",
"title": "Worker Mode",
"title": "Worker Modes",
"children": [],
"chapterTitle": "8.2. Worker Mode",
"chapterTitle": "8.2. Worker Modes",
"number": "8.2."
},
{
@@ -1345,6 +1345,27 @@ export const generatedSidebars = [
"children": [],
"chapterTitle": "9.2. Release Notes",
"number": "9.2."
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/learn/codemods",
"title": "Codemods",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"title": "Replace Imports (v2.11.0+)",
"path": "/learn/codemods/replace-imports",
"children": [],
"chapterTitle": "9.3.1. Replace Imports (v2.11.0+)",
"number": "9.3.1."
}
],
"chapterTitle": "9.3. Codemods",
"number": "9.3."
}
],
"chapterTitle": "9. Upgrade",
+544 -84
View File
@@ -139,6 +139,294 @@ npx medusa build --admin-only
The next chapter covers how to deploy the production build.
# Medusa Codemods
In this chapter, you'll learn about Medusa codemods and the list of available codemods.
## What are Codemods?
Codemods are scripts that help you automate codebase changes. They are especially useful when updating to a new version that requires large changes to your codebase.
Medusa provides codemods to help you update your codebase. Use these codemods when updating to their respective versions.
***
## List of Medusa Codemods
# Replace Imports Codemod (v2.11.0+)
In this chapter, you'll learn about the codemod that helps you replace imports in your codebase when upgrading to Medusa v2.11.0.
## What is the Replace Imports Codemod?
[Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0) optimized the package structure by consolidating several external packages into the `@medusajs/framework` package.
Previously, you had to install and manage packages related to MikroORM, Awilix, OpenTelemetry, and the `pg` package separately in your Medusa application. Starting with v2.11.0, these packages are included in the `@medusajs/framework` package.
For example, instead of importing `@mikro-orm/core`, you now import it from `@medusajs/framework/mikro-orm/core`. This applies to all of the following packages:
- `@mikro-orm/*` packages (for example, `@mikro-orm/core`, `@mikro-orm/migrations`, etc.) -> `@medusajs/framework/mikro-orm/{subpath}`
- `awilix` -> `@medusajs/framework/awilix`
- `pg` -> `@medusajs/framework/pg`
- `@opentelemetry/instrumentation-pg` -> `@medusajs/framework/opentelemetry/instrumentation-pg`
- `@opentelemetry/resources` -> `@medusajs/framework/opentelemetry/resources`
- `@opentelemetry/sdk-node` -> `@medusajs/framework/opentelemetry/sdk-node`
- `@opentelemetry/sdk-trace-node` -> `@medusajs/framework/opentelemetry/sdk-trace-node`
To help you update your codebase to reflect these changes, Medusa provides a codemod that automatically replaces imports of these packages throughout your codebase.
***
## Using the Replace Imports Codemod
To use the replace imports codemod, create the file `replace-imports.js` in the root of your Medusa application with the following content:
```js
#!/usr/bin/env node
const fs = require("fs")
const path = require("path")
const { execSync } = require("child_process")
/**
- Script to replace imports and require statements from mikro-orm/{subpath}, awilix, and pg
- to their @medusajs/framework equivalents
*/
// Define the replacement mappings
const replacements = [
// MikroORM imports - replace mikro-orm/{subpath} with @medusajs/framework/mikro-orm/{subpath}
{
pattern: /from\s+['"]@?mikro-orm\/([^'"]+)['"]/g,
// eslint-disable-next-line quotes
replacement: 'from "@medusajs/framework/mikro-orm/$1"',
},
// Awilix imports - replace awilix with @medusajs/framework/awilix
{
pattern: /from\s+['"]awilix['"]/g,
// eslint-disable-next-line quotes
replacement: 'from "@medusajs/framework/awilix"',
},
// PG imports - replace pg with @medusajs/framework/pg
{
pattern: /from\s+['"]pg['"]/g,
// eslint-disable-next-line quotes
replacement: 'from "@medusajs/framework/pg"',
},
// OpenTelemetry imports - replace @opentelemetry/instrumentation-pg, @opentelemetry/resources,
// @opentelemetry/sdk-node, and @opentelemetry/sdk-trace-node with @medusajs/framework/opentelemetry/{subpath}
{
pattern: /from\s+['"]@?opentelemetry\/(instrumentation-pg|resources|sdk-node|sdk-trace-node)['"]/g,
// eslint-disable-next-line quotes
replacement: 'from "@medusajs/framework/opentelemetry/$1"',
},
// MikroORM require statements - replace require('@?mikro-orm/{subpath}') with require('@medusajs/framework/mikro-orm/{subpath}')
{
pattern: /require\s*\(\s*['"]@?mikro-orm\/([^'"]+)['"]\s*\)/g,
// eslint-disable-next-line quotes
replacement: 'require("@medusajs/framework/mikro-orm/$1")',
},
// Awilix require statements - replace require('awilix') with require('@medusajs/framework/awilix')
{
pattern: /require\s*\(\s*['"]awilix['"]\s*\)/g,
// eslint-disable-next-line quotes
replacement: 'require("@medusajs/framework/awilix")',
},
// PG require statements - replace require('pg') with require('@medusajs/framework/pg')
{
pattern: /require\s*\(\s*['"]pg['"]\s*\)/g,
// eslint-disable-next-line quotes
replacement: 'require("@medusajs/framework/pg")',
},
// OpenTelemetry require statements - replace require('@opentelemetry/instrumentation-pg'),
// require('@opentelemetry/resources'), require('@opentelemetry/sdk-node'), and
// require('@opentelemetry/sdk-trace-node') with require('@medusajs/framework/opentelemetry/{subpath}')
{
pattern: /require\s*\(\s*['"]@?opentelemetry\/(instrumentation-pg|resources|sdk-node|sdk-trace-node)['"]\s*\)/g,
// eslint-disable-next-line quotes
replacement: 'require("@medusajs/framework/opentelemetry/$1")',
},
]
function processFile(filePath) {
try {
const content = fs.readFileSync(filePath, "utf8")
let modifiedContent = content
let wasModified = false
replacements.forEach(({ pattern, replacement }) => {
const newContent = modifiedContent.replace(pattern, replacement)
if (newContent !== modifiedContent) {
wasModified = true
modifiedContent = newContent
}
})
if (wasModified) {
fs.writeFileSync(filePath, modifiedContent)
console.log(`✓ Updated: ${filePath}`)
return true
}
return false
} catch (error) {
console.error(`✗ Error processing ${filePath}:`, error.message)
return false
}
}
function getTargetFiles() {
try {
// Get the current script's filename to exclude it from processing
const currentScript = path.basename(__filename)
// Find TypeScript/JavaScript files, excluding common directories that typically don't contain target imports
const findCommand = `find . -name node_modules -prune -o -name .git -prune -o -name dist -prune -o -name build -prune -o -name coverage -prune -o -name "*.ts" -print -o -name "*.js" -print -o -name "*.tsx" -print -o -name "*.jsx" -print`
const files = execSync(findCommand, {
encoding: "utf8",
maxBuffer: 50 * 1024 * 1024, // 50MB buffer
})
.split("\n")
.filter((line) => line.trim())
console.log(files)
const targetFiles = []
let processedCount = 0
console.log(`📄 Scanning ${files.length} files for target imports and require statements...`)
for (const file of files) {
try {
// Skip the current script file
const fileName = path.basename(file)
if (fileName === currentScript) {
processedCount++
continue
}
const content = fs.readFileSync(file, "utf8")
if (
/from\s+['"]@?mikro-orm\//.test(content) ||
/from\s+['"]awilix['"]/.test(content) ||
/from\s+['"]pg['"]/.test(content) ||
/require\s*\(\s*['"]@?mikro-orm\//.test(content) ||
/require\s*\(\s*['"]awilix['"]/.test(content) ||
/require\s*\(\s*['"]pg['"]/.test(content)
) {
targetFiles.push(file.startsWith("./") ? file.slice(2) : file)
}
processedCount++
if (processedCount % 100 === 0) {
process.stdout.write(
`\r📄 Processed ${processedCount}/${files.length} files...`
)
}
} catch (fileError) {
// Skip files that can't be read
continue
}
}
if (processedCount > 0) {
console.log(`\r📄 Processed ${processedCount} files. `)
}
return targetFiles
} catch (error) {
console.error("Error finding target files:", error.message)
return []
}
}
function main() {
console.log("🔄 Finding files with target imports and require statements...")
const targetFiles = getTargetFiles()
if (targetFiles.length === 0) {
console.log("️ No files found with target imports or require statements.")
return
}
console.log(`📁 Found ${targetFiles.length} files to process`)
let modifiedCount = 0
let errorCount = 0
targetFiles.forEach((filePath) => {
const fullPath = path.resolve(filePath)
if (fs.existsSync(fullPath)) {
if (processFile(fullPath)) {
modifiedCount++
}
} else {
console.warn(`⚠️ File not found: ${filePath}`)
errorCount++
}
})
console.log("\n📊 Summary:")
console.log(` Files processed: ${targetFiles.length}`)
console.log(` Files modified: ${modifiedCount}`)
console.log(` Errors: ${errorCount}`)
if (modifiedCount > 0) {
console.log("\n✅ Import replacement completed successfully!")
console.log("\n💡 Next steps:")
console.log(" 1. Review the changes with: git diff")
console.log(" 2. Run your tests to ensure everything works correctly")
console.log(" 3. Commit the changes if you're satisfied")
} else {
console.log(
"\n✅ No modifications needed - all imports are already correct!"
)
}
}
// Run if called directly
if (require.main === module) {
main()
}
module.exports = { processFile, getTargetFiles, main }
```
This script scans your project for files that import from `mikro-orm/{subpath}`, `awilix`, or `pg`, and replaces those imports with their new equivalents from `@medusajs/framework`. It handles both ES module `import` statements and CommonJS `require` statements in JavaScript and TypeScript files.
Next, run the following command in your terminal to make the script executable:
You can run the script using `node` without changing permissions.
```bash
chmod +x replace-imports.js
```
Finally, execute the script with the following command:
```bash
node replace-imports.js
```
This will scan your project files, apply the necessary import replacements, and provide a summary of the changes made.
***
## Next Steps
After running the codemod, review the changes made to your codebase. You can use `git diff` to see the modifications. Additionally, run your tests to ensure everything works as expected.
If everything is working correctly, you can remove the `replace-imports.js` file from your project. You can also remove the following packages from your `package.json`, as they're now included in the `@medusajs/framework` package:
- `@mikro-orm/*` packages (for example, `@mikro-orm/core`, `@mikro-orm/migrations`, etc.)
- `awilix`
- `pg`
- `@opentelemetry/instrumentation-pg`
- `@opentelemetry/resources`
- `@opentelemetry/sdk-node`
- `@opentelemetry/sdk-trace-node`
# Medusa Application Configuration
In this chapter, you'll learn available configurations in the Medusa application. You can change the application's configurations to customize the behavior of the application, its integrated modules and plugins, and more.
@@ -4270,13 +4558,9 @@ Medusa uses [OpenTelemetry](https://opentelemetry.io/) for instrumentation and r
### Install Dependencies
Start by installing the following OpenTelemetry dependencies in your Medusa project:
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), OpenTelemetry dependencies are installed by default in new Medusa projects. If you're using an older version of Medusa, you need to install the `@opentelemetry/sdk-node`, `@opentelemetry/resources`, `@opentelemetry/sdk-trace-node`, and `@opentelemetry/instrumentation-pg` dependencies.
```bash npm2yarn
npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg
```
Also, install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies:
Before you start, you must install the dependencies relevant for the exporter you use. If you're using Zipkin, install the following dependencies:
```bash npm2yarn
npm install @opentelemetry/exporter-zipkin
@@ -6080,8 +6364,10 @@ if (process.env.TEST_TYPE === "integration:http") {
Next, create the `integration-tests/setup.js` file with the following content:
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the require statement to `@mikro-orm/core`.
```js title="integration-tests/setup.js"
const { MetadataStorage } = require("@mikro-orm/core")
const { MetadataStorage } = require("@medusajs/framework/@mikro-orm/core")
MetadataStorage.clear()
```
@@ -6652,9 +6938,44 @@ For example, the `VITE_MY_API_KEY` environment variable in the example above wil
## Environment Variables in Plugins
As explained in the [previous section](#environment-variables-in-production), environment variables are inlined into the build. This presents a limitation for plugins, where you can't use environment variables.
Environment variable support in plugins is available starting [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0). Refer to the [Medusa versions prior to v2.11.0](#for-medusa-versions-prior-to-v2110) section for more details if you're using an earlier version.
Instead, only the following global variable is available in plugins:
For plugins, you can use environment variables without a prefix. Then, Medusa applications that use the plugin can set the environment variable with the `PLUGIN_` prefix.
For example, you can create a widget in your plugin that uses the `MY_API_KEY` environment variable:
```tsx highlights={[["8"]]}
import { defineWidgetConfig } from "@medusajs/admin-sdk"
import { Container, Heading } from "@medusajs/ui"
const ProductWidget = () => {
return (
<Container className="divide-y p-0">
<div className="flex items-center justify-between px-6 py-4">
<Heading level="h2">API Key: {import.meta.env.MY_API_KEY}</Heading>
</div>
</Container>
)
}
export const config = defineWidgetConfig({
zone: "product.details.before",
})
export default ProductWidget
```
Then, in the Medusa application that uses the plugin, set the environment variable with the `PLUGIN_` prefix:
```bash
PLUGIN_MY_API_KEY=sk_123
```
The `MY_API_KEY` environment variable in the plugin will be replaced with the value of `PLUGIN_MY_API_KEY` during the build process of the Medusa application.
### Global Variables in Plugins
Plugins also have the following global variables available:
- `__BACKEND_URL__`: The URL of the Medusa backend, as set in the [Medusa configurations](https://docs.medusajs.com/learn/configurations/medusa-config#backendurl/index.html.md).
- `__BASE__`: The base path of the Medusa Admin. (For example, `/app`).
@@ -6693,6 +7014,14 @@ declare const __BASE__: string
declare const __STOREFRONT_URL__: string
```
### For Medusa versions prior to v2.11.0
### Instructions for Medusa versions prior to v2.11.0
As explained in the [Environment Variables in Production section](#environment-variables-in-production), environment variables are inlined into the build. This presents a limitation for plugins, where you can't use environment variables.
Instead, you can use the [Plugin Global Variables](#global-variables-in-plugins) described above to access the backend URL, base path, and storefront URL.
# Admin Development
@@ -10779,8 +11108,10 @@ npx medusa db:generate blog
The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for. It will create a migration file for the Blog Module in the directory `src/modules/blog/migrations` similar to the following:
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`.
```ts
import { Migration } from "@mikro-orm/migrations"
import { Migration } from "@medusajs/framework/@mikro-orm/migrations"
export class Migration20241121103722 extends Migration {
@@ -11548,8 +11879,10 @@ You can also write migrations manually. To do that, create a file in the `migrat
For example:
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`.
```ts title="src/modules/blog/migrations/Migration202507021059_create_author.ts"
import { Migration } from "@mikro-orm/migrations"
import { Migration } from "@medusajs/framework/@mikro-orm/migrations"
export class Migration202507021059 extends Migration {
@@ -11564,7 +11897,7 @@ export class Migration202507021059 extends Migration {
}
```
The migration class in the file extends the `Migration` class imported from `@mikro-orm/migrations`. In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax.
The migration class in the file extends the `Migration` class imported from `@medusajs/framework/@mikro-orm/migrations`. In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax.
In the example above, the `up` method creates the table `author`, and the `down` method drops the table if the migration is reverted.
@@ -15881,6 +16214,8 @@ So, to run database queries in a service:
For example, in your service, add the following methods:
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/knex`.
```ts highlights={methodsHighlight}
// other imports...
import {
@@ -15888,7 +16223,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -15915,7 +16250,7 @@ class BlogModuleService {
You add two methods `getCount` and `getCountSql` that have the `InjectManager` decorator. Each of the methods also accept the `sharedContext` parameter which has the `MedusaContext` decorator.
The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the @mikro-orm/knex package](https://mikro-orm.io/api/knex/class/EntityManager).
The entity manager is injected to the `sharedContext.manager` property, which is an instance of [EntityManager from the `@medusajs/framework/@mikro-orm/knex` package](https://mikro-orm.io/api/knex/class/EntityManager).
You use the manager in the `getCount` method to retrieve the number of records in a table, and in the `getCountSql` to run a PostgreSQL query that retrieves the count.
@@ -15952,7 +16287,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -16009,7 +16344,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -16052,7 +16387,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -16185,7 +16520,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -16300,7 +16635,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -16366,7 +16701,7 @@ The second parameter of the `baseRepository_.transaction` method is an object of
```ts highlights={[["24"]]}
// other imports...
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
import {
InjectTransactionManager,
MedusaContext,
@@ -16409,8 +16744,8 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { IsolationLevel } from "@mikro-orm/core"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
import { IsolationLevel } from "@medusajs/framework/@mikro-orm/core"
class BlogModuleService {
// ...
@@ -16444,7 +16779,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -16883,9 +17218,11 @@ Consider your have a MongoDB module that allows you to perform operations on a M
To connect to the database, you create the following loader in your module:
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), Awilix dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `awilix`.
```ts title="src/modules/mongo/loaders/connection.ts" highlights={loaderHighlights}
import { LoaderOptions } from "@medusajs/framework/types"
import { asValue } from "awilix"
import { asValue } from "@medusajs/framework/awilix"
import { MongoClient } from "mongodb"
type ModuleOptions = {
@@ -16951,7 +17288,7 @@ In the loader, you check first that these options are set before proceeding. The
After creating the client, you register it in the module's container using the container's `register` method. The method accepts two parameters:
1. The key to register the resource under, which in this case is `mongoClient`. You'll use this name later to resolve the client.
2. The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an `asValue` function imported from the [awilix package](https://github.com/jeffijoe/awilix), which is the package used to implement the container functionality in Medusa.
2. The resource to register in the container, which is the MongoDB client you created. However, you don't pass the resource as-is. Instead, you need to use an `asValue` function imported from the [`@medusajs/framework/awilix` package](https://github.com/jeffijoe/awilix), which is the package used to implement the container functionality in Medusa.
### Use Custom Registered Resource in Module's Service
@@ -17503,8 +17840,10 @@ npx medusa db:generate blog
The `db:generate` command of the Medusa CLI accepts one or more module names to generate the migration for. It will create a migration file for the Blog Module in the directory `src/modules/blog/migrations` similar to the following:
As of [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), MikroORM dependencies are included in the `@medusajs/framework` package. If you're using an older version of Medusa, change the import statement to `@mikro-orm/migrations`.
```ts
import { Migration } from "@mikro-orm/migrations"
import { Migration } from "@medusajs/framework/@mikro-orm/migrations"
export class Migration20241121103722 extends Migration {
@@ -22248,17 +22587,10 @@ A basic v2 project has the following dependencies in `package.json`:
"@medusajs/admin-sdk": "2.8.2",
"@medusajs/cli": "2.8.2",
"@medusajs/framework": "2.8.2",
"@medusajs/medusa": "2.8.2",
"@mikro-orm/core": "6.4.3",
"@mikro-orm/knex": "6.4.3",
"@mikro-orm/migrations": "6.4.3",
"@mikro-orm/postgresql": "6.4.3",
"awilix": "^8.0.1",
"pg": "^8.13.0"
"@medusajs/medusa": "2.8.2"
},
"devDependencies": {
"@medusajs/test-utils": "2.8.2",
"@mikro-orm/cli": "6.4.3",
"@swc/core": "1.5.7",
"@swc/jest": "^0.2.36",
"@types/jest": "^29.5.13",
@@ -22285,25 +22617,15 @@ The main changes are:
- `@medusajs/framework`
- `@medusajs/medusa`
- `@medusajs/test-utils` (as a dev dependency)
- You need to install the following extra packages:
- Database packages:
- `@mikro-orm/core@6.4.3`
- `@mikro-orm/knex@6.4.3`
- `@mikro-orm/migrations@6.4.3`
- `@mikro-orm/postgresql@6.4.3`
- `@mikro-orm/cli@6.4.3` (as a dev dependency)
- `pg^8.13.0`
- Framework packages:
- `awilix@^8.0.1`
- Development and Testing packages:
- `@swc/core@1.5.7`
- `@swc/jest@^0.2.36`
- `@types/node@^20.0.0`
- `jest@^29.7.0`
- `ts-node@^10.9.2`
- `typescript@^5.6.2`
- `vite@^5.2.11`
- `yalc@^1.0.0-pre.53`
- You need to install the following extra packages for development and testing:
- `@swc/core@1.5.7`
- `@swc/jest@^0.2.36`
- `@types/node@^20.0.0`
- `jest@^29.7.0`
- `ts-node@^10.9.2`
- `typescript@^5.6.2`
- `vite@^5.2.11`
- `yalc@^1.0.0-pre.53`
- Other packages, such as `@types/react` and `@types/react-dom`, are necessary for admin development and TypeScript support.
Notice that Medusa now uses MikroORM instead of TypeORM for database functionalities.
@@ -23629,7 +23951,14 @@ In this chapter, you'll learn about the different modes of running a Medusa inst
## What is Worker Mode?
By default, the Medusa application runs both the server, which handles all incoming requests, and the worker, which processes background tasks, in a single process. While this setup is suitable for development, it is not optimal for production environments where background tasks can be long-running or resource-intensive.
By default, the Medusa application runs in `shared` mode, which runs:
- `server`: the application server that handles incoming requests to the application's API routes.
- `worker`: the worker that processes background tasks. This includes scheduled jobs and subscribers.
While this setup is suitable for development, it is not optimal for production environments where background tasks can be long-running or resource-intensive.
### Worker Mode in Production
In a production environment, you should deploy two separate instances of your Medusa application:
@@ -23714,6 +24043,26 @@ ADMIN_DISABLED=false
ADMIN_DISABLED=true
```
***
## Dividing Resources in Cluster Mode
The `--servers` and `--workers` options were introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0).
When running Medusa in [cluster mode](https://docs.medusajs.com/resources/medusa-cli/commands/start#starting-medusa-in-cluster-mode/index.html.md), you can specify the number or percentage of instances that are servers or workers by passing the `--servers` and `--workers` options:
```bash
npx medusa start --cluster 4 --servers 25% --workers 75% # Use 4 CPU cores, with 25% as servers and 75% as workers
npx medusa start --cluster 4 --servers 1 --workers 3 # Use 4 CPU cores, with 1 as server and 3 as workers
npx medusa start --cluster 4 --servers 1 --workers 1 # Use 4 CPU cores, with 1 as server and 1 as worker (the remaining 2 will run in shared mode)
```
In the above snippet you can see the following examples:
- In the first example, 25% of the instances (1 out of 4) will run as servers, and 75% (3 out of 4) will run as workers.
- In the second example, 1 instance will run as a server, and 3 instances will run as workers.
- In the third example, 1 instance will run as a server, and 1 instance will run as a worker. The remaining 2 instances will run in shared mode
# Translate Medusa Admin
@@ -36070,34 +36419,75 @@ Refer to this [Medusa Admin User Guide](https://docs.medusajs.com/user-guide/pro
## What is a Campaign?
A [Campaign](https://docs.medusajs.com/references/promotion/models/Campaign/index.html.md) combines [promotions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts#what-is-a-promotion/index.html.md) under the same conditions, such as start and end dates.
A [Campaign](https://docs.medusajs.com/references/promotion/models/Campaign/index.html.md) groups [promotions](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/promotion/concepts#what-is-a-promotion/index.html.md) under the same conditions, such as start and end dates.
Campaigns are useful for grouping promotions that share the same time frame or target audience. They're also useful for limiting the usage of promotions.
Use campaigns to group promotions that share the same time frame or target audience, and to limit promotion usage.
![A diagram showcasing the relation between the Campaign and Promotion data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709899225/Medusa%20Resources/campagin-promotion_hh3qsi.jpg)
***
## Campaign Limits
## Limit Promotion Usage with Campaign Budgets
Each campaign can have a budget represented by the [CampaignBudget data model](https://docs.medusajs.com/references/promotion/models/CampaignBudget/index.html.md). The budget limits how many times the promotion can be used.
Each campaign can have a budget represented by the [CampaignBudget data model](https://docs.medusajs.com/references/promotion/models/CampaignBudget/index.html.md). The budget limits how many times a promotion can be used.
There are two types of budgets:
There are three types of budgets: two that are global and one that is based on cart attributes.
- `spend`: An amount that, when crossed, the promotion becomes unusable.
- For example, if the amount limit is set to `$100`, and the total amount of usage of this promotion crosses that threshold, the promotion can no longer be applied.
- `usage`: The number of times that a promotion can be used.
- For example, if the usage limit is set to `10`, the promotion can be used only 10 times by customers. After that, it can no longer be applied.
### Global Budgets
A global budget limits promotion usage without considering any cart attributes.
There are two types of global budgets:
- `spend`: An amount that, when exceeded, makes the promotion unusable.
- For example, if the amount limit is `$100` and the total usage of this promotion exceeds that threshold, the promotion can no longer be applied.
- `usage`: The number of times a promotion can be used.
- For example, if the usage limit is `10`, customers can use the promotion only 10 times. After that, it can no longer be applied.
![A diagram showcasing the relation between the Campaign and CampaignBudget data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1709899463/Medusa%20Resources/campagin-budget_rvqlmi.jpg)
### How Budgets Limit Promotion Usage
Global budgets track usage and limits through the following properties of the `CampaignBudget` data model:
When a customer tries to use a promotion, Medusa checks whether the campaign has a budget and if the budget limit has been reached. If so, the promotion cannot be applied.
- `limit`: The maximum amount or number of uses allowed for the promotion.
- `used`: The current amount spent or number of times the promotion has been used.
For example, if a campaign has a budget of type `usage` with a limit of `10`, and the promotion has already been used 10 times, it cannot be applied anymore and is considered expired.
### Attribute-based Budgets
However, once a promotion is applied to a cart, it remains valid until the order is completed, even if the budget limit is reached in the meantime. This ensures that customers who have already applied the promotion can still benefit from it during checkout.
Attribute-based budgets were introduced in [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0).
An attribute-based budget limits promotion usage based on a cart attribute. Use these budget types to have granular control over how many times a promotion can be used based on specific attributes.
There's one type of attribute-based budget, which is `use_by_attribute`. It allows you to limit the number of times a promotion can be used based on a specific cart attribute.
#### Allowed Attributes
There are two attributes that you can limit promotion usage by:
- `customer_id`: Limits promotion usage based on the unique identifier of a customer.
- `customer_email`: Limits promotion usage based on the email address of a customer.
These attributes are compared against the cart's `customer_id` or `email` to determine how many times the promotion has been used for that specific attribute value, and whether the budget limit has been reached.
#### Tracking Attribute-based Usage
The `CampaignBudgetUsage` data model tracks the usage of attribute-based budgets. It tracks how many times a promotion has been used for each unique attribute value. It includes the following properties:
1. `attribute_value`: The value of the attribute, such as a specific customer ID or email.
2. `used`: The number of times the promotion has been used for that attribute value.
For example, if the attribute is `customer_id`, a new `CampaignBudgetUsage` record is created for each customer that uses the promotion to track their individual usage. Once a customer exceeds the limit set in the `CampaignBudget`, they can no longer use the promotion.
![A diagram showcasing the relation between the CampaignBudget and CampaignBudgetUsage data models](https://res.cloudinary.com/dza7lstvk/image/upload/v1760340527/Medusa%20Resources/campaign-budget-attr_fv0v2u.jpg)
***
## How Campaign Budgets Limit Promotion Usage
When a customer tries to use a promotion, Medusa checks whether the campaign has a budget and if the budget limit has been reached. If the limit is reached, the promotion cannot be applied.
For example, if a campaign has a `usage` budget with a limit of `10` and the promotion has already been used 10 times, it can no longer be applied and is considered expired.
However, once a promotion is applied to a cart, it remains valid until the order is completed, even if the budget limit is reached in the meantime. This ensures that customers who already applied the promotion can still benefit from it during checkout.
# Promotion Concepts
@@ -44653,7 +45043,46 @@ npx medusa start
|---|---|---|---|---|
|\`-H \<host>\`|Set host of the Medusa server.|\`localhost\`|
|\`-p \<port>\`|Set port of the Medusa server.|\`9000\`|
|\`--cluster \<number>\`|Start Medusa's Node.js server in |Cluster mode is disabled by default. If the option is passed but no number is passed, Medusa will try to consume all available CPU cores.|
|\`--cluster \<string> \[--workers \<string>] \[--servers \<string>]\`|Start Medusa in cluster mode. Learn more in the |Cluster mode is disabled by default. If the option is passed but no number or percentage is passed, Medusa will try to consume all available CPU cores.|
***
## Starting Medusa in Cluster Mode
Prior to [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), the `--cluster` option accepted a number value only. You can now pass either a number or a percentage value, and you can also specify the number of servers and workers.
Medusa supports starting the Node.js server in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster), which significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one.
Cluster mode is disabled by default. To enable it, pass the `--cluster` option when starting Medusa:
```bash
npx medusa start --cluster
```
When the `--cluster` option is passed without a number or percentage value, Medusa will try to consume all available CPU cores.
### Specify Number or Percentage of CPU Cores
You can specify the number or percentage of CPU cores to be used by passing a number or percentage value to the `--cluster` option:
```bash
npx medusa start --cluster 2 # Use 2 CPU cores
npx medusa start --cluster 50% # Use 50% of available CPU
```
### Specify Number of Servers and Workers
When running Medusa in cluster mode, you can specify the number or percentage of instances that are [servers or workers](https://docs.medusajs.com/docs/learn/production/worker-mode/index.html.md) by passing the `--servers` and `--workers` options:
```bash
npx medusa start --cluster 4 --servers 25% --workers 75% # Use 4 CPU cores, with 25% as servers and 75% as workers
npx medusa start --cluster 4 --servers 1 --workers 3 # Use 4 CPU cores, with 1 as server and 3 as workers
npx medusa start --cluster 4 --servers 1 --workers 1 # Use 4 CPU cores, with 1 as server and 1 as worker (the remaining 2 will run in shared mode)
```
When the number or percentage of servers and workers don't add up to the total number of instances in cluster mode, the remaining instances will run in shared mode.
Learn more in the [Worker Mode](https://docs.medusajs.com/docs/learn/production/worker-mode/index.html.md) guide.
# telemetry Command - Medusa CLI Reference
@@ -45020,7 +45449,46 @@ npx medusa start
|---|---|---|---|---|
|\`-H \<host>\`|Set host of the Medusa server.|\`localhost\`|
|\`-p \<port>\`|Set port of the Medusa server.|\`9000\`|
|\`--cluster \<number>\`|Start Medusa's Node.js server in |Cluster mode is disabled by default. If the option is passed but no number is passed, Medusa will try to consume all available CPU cores.|
|\`--cluster \<string> \[--workers \<string>] \[--servers \<string>]\`|Start Medusa in cluster mode. Learn more in the |Cluster mode is disabled by default. If the option is passed but no number or percentage is passed, Medusa will try to consume all available CPU cores.|
***
## Starting Medusa in Cluster Mode
Prior to [Medusa v2.11.0](https://github.com/medusajs/medusa/releases/tag/v2.11.0), the `--cluster` option accepted a number value only. You can now pass either a number or a percentage value, and you can also specify the number of servers and workers.
Medusa supports starting the Node.js server in [cluster mode](https://expressjs.com/en/advanced/best-practice-performance.html#run-your-app-in-a-cluster), which significantly improves performance as the workload and tasks are distributed among all available instances instead of a single one.
Cluster mode is disabled by default. To enable it, pass the `--cluster` option when starting Medusa:
```bash
npx medusa start --cluster
```
When the `--cluster` option is passed without a number or percentage value, Medusa will try to consume all available CPU cores.
### Specify Number or Percentage of CPU Cores
You can specify the number or percentage of CPU cores to be used by passing a number or percentage value to the `--cluster` option:
```bash
npx medusa start --cluster 2 # Use 2 CPU cores
npx medusa start --cluster 50% # Use 50% of available CPU
```
### Specify Number of Servers and Workers
When running Medusa in cluster mode, you can specify the number or percentage of instances that are [servers or workers](https://docs.medusajs.com/docs/learn/production/worker-mode/index.html.md) by passing the `--servers` and `--workers` options:
```bash
npx medusa start --cluster 4 --servers 25% --workers 75% # Use 4 CPU cores, with 25% as servers and 75% as workers
npx medusa start --cluster 4 --servers 1 --workers 3 # Use 4 CPU cores, with 1 as server and 3 as workers
npx medusa start --cluster 4 --servers 1 --workers 1 # Use 4 CPU cores, with 1 as server and 1 as worker (the remaining 2 will run in shared mode)
```
When the number or percentage of servers and workers don't add up to the total number of instances in cluster mode, the remaining instances will run in shared mode.
Learn more in the [Worker Mode](https://docs.medusajs.com/docs/learn/production/worker-mode/index.html.md) guide.
# telemetry Command - Medusa CLI Reference
@@ -52113,7 +52581,7 @@ import {
MedusaContext,
} from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class BlogModuleService {
// ...
@@ -53418,7 +53886,7 @@ To register a resource in the Module's container using a loader, use the `contai
import {
LoaderOptions,
} from "@medusajs/framework/types"
import { asValue } from "awilix"
import { asValue } from "@medusajs/framework/awilix"
export default async function helloWorldLoader({
container,
@@ -76325,7 +76793,7 @@ In `src/modules/product-review/service.ts`, add the following methods to the `Pr
import { InjectManager, MedusaService, MedusaContext } from "@medusajs/framework/utils"
import Review from "./models/review"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class ProductReviewModuleService extends MedusaService({
Review,
@@ -79587,7 +80055,7 @@ Loaders are created in a TypeScript or JavaScript file under the `loaders` direc
```ts title="src/modules/contentful/loader/create-content-models.ts" highlights={loaderHighlights}
import { LoaderOptions } from "@medusajs/framework/types"
import { asValue } from "awilix"
import { asValue } from "@medusajs/framework/awilix"
import { createClient } from "contentful-management"
import { MedusaError } from "@medusajs/framework/utils"
@@ -92854,15 +93322,7 @@ Refer to the [Instrumentation](https://docs.medusajs.com/docs/learn/debugging-an
### a. Install Instrumentation Dependencies
To set up instrumentation in Medusa, you need to install the necessary OpenTelemetry dependencies.
In your Medusa application's directory, run the following command:
```bash npm2yarn
npm install @opentelemetry/sdk-node @opentelemetry/resources @opentelemetry/sdk-trace-node @opentelemetry/instrumentation-pg
```
Then, you need to install the dependencies necessary for the monitoring tool you want to use, which is Sentry in this case.
To set up instrumentation in Medusa, you need to install the dependencies necessary for the monitoring tool you want to use, which is Sentry in this case.
So, run the following command to install the necessary Sentry dependencies:
@@ -96773,7 +97233,7 @@ In `src/modules/wishlist/service.ts`, add the following imports and method:
// other imports...
import { InjectManager } from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
export default class WishlistModuleService extends MedusaService({
Wishlist,
@@ -115723,7 +116183,7 @@ Before adding the step that does this, you'll add a method in the `RestockModule
// other imports...
import { InjectManager, MedusaContext } from "@medusajs/framework/utils"
import { Context } from "@medusajs/framework/types"
import { EntityManager } from "@mikro-orm/knex"
import { EntityManager } from "@medusajs/framework/@mikro-orm/knex"
class RestockModuleService extends MedusaService({
RestockSubscription,
+13 -1
View File
@@ -679,7 +679,7 @@ export const sidebars = [
{
type: "link",
path: "/learn/production/worker-mode",
title: "Worker Mode",
title: "Worker Modes",
},
{
type: "link",
@@ -709,6 +709,18 @@ export const sidebars = [
path: "https://github.com/medusajs/medusa/releases",
title: "Release Notes",
},
{
type: "link",
path: "/learn/codemods",
title: "Codemods",
children: [
{
type: "link",
title: "Replace Imports (v2.11.0+)",
path: "/learn/codemods/replace-imports",
},
],
},
],
},
{