docs: general fixes and improvements (#7918)

* docs improvements and changes

* updated module definition

* modules + dml changes

* fix build

* fix vale error

* fix lint errors

* fixes to stripe docs

* fix condition

* fix condition

* fix module defintion

* fix checkout

* disable UI action

* change oas preview action

* flatten provider module options

* fix lint errors

* add module link docs

* pr comments fixes

* fix vale error

* change node engine version

* links -> linkable

* add note about database name

* small fixes

* link fixes

* fix response code in api reference

* added migrations step
This commit is contained in:
Shahed Nasser authored and GitHub committed 2024-07-04 17:26:03 +03:00
1 parent 32982e708a
commit 964927b597
149 files changed
+1676 -3008

No files matched your search

@@ -10,9 +10,9 @@ In this chapter, you’ll learn how to customize the Medusa Admin dashboard.
The Medusa Admin is an admin dashboard that merchants use to manage their store's data.
You can extend the Medusa Admin to add widgets and new pages. Your customizations interact with API routes to provide merchants with custom functionalities.
You can extend the Medusa Admin to add widgets and new pages. In your customizations, you interact with API routes to provide merchants with custom functionalities.
The Medusa Admin is installed in your Medusa application and runs at the path `/app` when you start the Medusa application.
The Medusa Admin is installed in your Medusa application and runs at `http://localhost:9000/app` when you start the application.
---
@@ -34,13 +34,13 @@ const ProductWidget = () => {
}
export const config = defineWidgetConfig({
zone: "product.details.after",
zone: "product.details.before",
})
export default ProductWidget
```
This inserts a widget with the text “Product Widget” at the end of a product’s details page.
This inserts a widget with the text “Product Widget” at the beginning of a product’s details page.
### Test the Widget
@@ -50,4 +50,4 @@ To test out the widget, start the Medusa application:
npm run dev
```
Then, open a product’s details page. You’ll find your custom widget at the bottom of the page.
Then, open a product’s details page in the Medusa Admin. You’ll find your custom widget at the top of the page.
+4 -4
View File
@@ -14,11 +14,11 @@ The Medusa core application provides a set of admin and store API routes out-of-
---
## How to Create an API Route
## How to Create an API Route?
An API Route is created in a TypeScript or JavaScript file under the `/src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`.
An API Route is created in a TypeScript or JavaScript file under the `src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`.
Each route file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).
Each file exports API Route handler functions for at least one HTTP method (`GET`, `POST`, `DELETE`, etc…).
For example, to create a `GET` API Route at `/store/hello-world`, create the file `src/api/store/hello-world/route.ts` with the following content:
@@ -58,6 +58,6 @@ curl http://localhost:9000/store/hello-world
<Note title="Use API routes when" type="success">
- You're exposing custom functionality to be used in the admin dashboard or an external application, such as a storefront.
- You're exposing custom functionality to be used by a storefront, admin dashboard, or any external application.
</Note>
@@ -18,7 +18,7 @@ Refer to [this reference](!resources!/commerce-modules) for a full list of comme
## Resolve Commerce Module Services
Similarly to your custom module, a commerce module's main service is registered in the Medusa container. So, you can resolve it in your resources, such as API route, to use its functionality.
Similarly to your custom module, a commerce module's main service is registered in the Medusa container. So, you can resolve it in your resources, such as API routes, to use its functionality.
For example, you saw this code snippet in the [Medusa container chapter](../medusa-container/page.mdx):
+16 -9
View File
@@ -12,17 +12,17 @@ A data model is a class that represents a table in the database. It's created in
---
## How to Create a Data Model
## How to Create a Data Model?
<Note title="Steps Summary">
1. Create data model class in a module.
2. Generate migration for the data model.
4. Run migration to add table for data model in the database.
1. Create a data model in a module.
2. Create migration for the data model.
4. Run migration to add the data model's table in the database.
</Note>
A data model is created in a TypeScript or JavaScript file under a module's `models` directory. It's defined using the `model` utility imported from `@medjusajs/utils`.
A data model is created in a TypeScript or JavaScript file under a module's `models` directory. It's defined using the `model` utility imported from `@medusajs/utils`.
For example, create the file `src/modules/hello/models/my-custom.ts` with the following content:
@@ -48,8 +48,6 @@ The example above defines the data model `MyCustom` with the properties `id` and
A migration defines changes to be made in the database, such as create or update tables.
So, you must create a migration that creates a table for your data model in the database.
A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It has two methods:
- The `up` method reflects changes on the database.
@@ -74,6 +72,15 @@ A migration is a class created in a TypeScript or JavaScript file under a module
})
```
<Note title="Important">
The `databaseName`'s value is only used to generate the migrations. So, make sure it:
1. Is unique to the module. Two modules shouldn't have the same database name, and it shouldn't be the same as the Medusa application.
2. Doesn't change between migrations, as that causes inconsistencies between the migrations.
</Note>
2. Run the following command in the root directory of your Medusa application:
```bash
@@ -95,7 +102,7 @@ For example:
```ts title="src/modules/migrations/Migration20240429090012.ts"
import { Migration } from "@mikro-orm/migrations"
export class Migration20240624145652 extends Migration {
export class Migration20240702105919 extends Migration {
async up(): Promise<void> {
this.addSql("create table if not exists \"my_custom\" (\"id\" text not null, \"name\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"my_custom_pkey\" primary key (\"id\"));")
@@ -120,7 +127,7 @@ The queries performed in each of the methods use PostgreSQL syntax.
To reflect the changes in the migration, run the `migration` command:
```bash npm2yarn
```bash
npx medusa migrations run
```
@@ -14,9 +14,11 @@ When an action is performed in Medusa, such as creating a product, the Medusa ap
## What is a Subscriber?
A subscriber is a function executed whenever its associated event is emitted.
A subscriber is a function executed whenever the event it listens to is emitted.
The subscriber is created in a TypeScript or JavaScript file under the `src/subscribers` directory.
### How to Create a Subscriber?
A subscriber is created in a TypeScript or JavaScript file under the `src/subscribers` directory.
For example, create the file `src/subscribers/product-created.ts` with the following content:
@@ -36,13 +38,32 @@ export const config: SubscriberConfig = {
A subscriber file must export:
- The subscriber function that is an asynchronous function executed whenever the associated event is triggered.
- A subscriber function that is an asynchronous function executed whenever the associated event is triggered.
- A configuration object defining the event this subscriber is listening to.
The above subscriber listens to the `product.created` event. Whenever the event is emitted, it logs in the terminal `A product is created`.
---
## Test the Subscriber
To test the subscriber, start the Medusa application:
```bash npm2yarn
npm run dev
```
Then, go to the Medusa Admin at `localhost:9000/app` and create a product. You’ll see the following messages logged in the Medusa application's terminal:
```bash
info: Processing product.created which has 1 subscribers
A product was created
```
The first message indicates that the `product.created` event was emitted, and the second one is the message logged from the subscriber.
---
## When to Use Subscribers
<Note title="Use subscribers when" type="success">
@@ -59,16 +80,10 @@ The subscriber function accepts an object parameter with the property `container
For example:
{/* TODO change how event names are loaded */}
export const highlights = [
["10", "container", "Recieve the Medusa Container in the object parameter."],
["13", "resolve", "Resolve the Product Module's main service."],
[
"13",
"ModuleRegistrationName.PRODUCT",
"The resource registration name imported from `@medusajs/modules-sdk`.",
],
["13", "ModuleRegistrationName.PRODUCT", "The module's registration name imported from `@medusajs/modules-sdk`."]
]
```ts title="src/subscribers/product-created.ts" highlights={highlights}
@@ -79,12 +94,15 @@ import { ModuleRegistrationName } from "@medusajs/utils"
export default async function productCreateHandler({
data,
container,
}: SubscriberArgs<Record<string, string>>) {
const productModuleService: IProductModuleService = container.resolve(
ModuleRegistrationName.PRODUCT
)
}: SubscriberArgs<{ id: string }>) {
const productModuleService: IProductModuleService =
container.resolve(ModuleRegistrationName.PRODUCT)
const product = await productModuleService.retrieve(data.id)
const productId = "data" in data ? data.data.id : data.id
const product = await productModuleService.retrieveProduct(
productId
)
console.log(`The product ${product.title} was created`)
}
+8 -12
View File
@@ -8,9 +8,7 @@ In this chapter, you’ll learn about loaders and how to use them.
## What is a Loader?
A loader is a function executed when the Medusa application starts.
A module can define loaders to execute on application start-up.
A loader is a function executed when the Medusa application starts. You define and export it in a module.
---
@@ -30,25 +28,23 @@ export default function helloWorldLoader() {
### Export Loader in Module Definition
You must export your loaders in the module definition.
To do that, import the loader in `src/modules/hello/index.ts` and export it in the module's definition:
Import the loader in `src/modules/hello/index.ts` and export it in the module's definition:
```ts title="src/modules/hello/index.ts"
// other imports...
import helloWorldLoader from "./loaders/hello-world"
// ...
export default {
export default Module("hello", {
// ...
loaders: [helloWorldLoader],
}
})
```
The value of the `loaders` property is an array of loader functions.
### Test Loader
---
## Test the Loader
Start the Medusa application:
@@ -75,6 +71,6 @@ Among the messages logged in the terminal, you’ll see the following message:
<Note title="Don't use loaders if" type="error">
- You want to perform an action continuously or at a set time pattern in the application. Use scheduled jobs instead, which is explained in the next chapter.
- You want to perform an action continuously or at a set time pattern in the application. Use scheduled jobs instead, which is explained in an upcoming chapter.
</Note>
@@ -33,7 +33,8 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
ModuleRegistrationName.PRODUCT
)
const [, count] = await productModuleService.listAndCount()
const [, count] = await productModuleService
.listAndCountProducts()
res.json({
count,
@@ -14,7 +14,7 @@ Use modules to customize or develop commerce and architectural features in your
---
## How to Create a Module
## How to Create a Module?
<Note title="Steps Summary">
@@ -28,7 +28,7 @@ Modules are created in a sub-directory of `src/modules`.
For example, create the directory `src/modules/hello`.
### 1. Create a Service
### 1. Create Main Service
A module must define a service. A service is a TypeScript or JavaScript class holding methods related to a business logic or commerce functionality.
@@ -46,21 +46,27 @@ export default class HelloModuleService {
A module must have an `index.ts` file in its root directory that exports its definition. The definition specifies the main service of the module.
For example, create the file `src/modules/hello.index.ts` with the following content:
For example, create the file `src/modules/hello/index.ts` with the following content:
```ts title="src/modules/hello.index.ts" highlights={[["4", "", "The main service of the module."]]}
```ts title="src/modules/hello.index.ts" highlights={[["5", "", "The main service of the module."]]}
import HelloModuleService from "./service"
import { Module } from "@medusajs/utils"
export default {
export default Module("hello", {
service: HelloModuleService,
}
})
```
You use the `Module` function imported from `@medusajs/utils` to create the module definition. It requires two parameters:
1. The module's name.
2. An object with a required property `service` indicating the module's main service.
### 3. Add Module to Configurations
The last step is to add the module in Medusa’s configurations.
In `medusa-config.js`, add the module to the `modules` object:
In `medusa-config.js`, add a `modules` property and pass to it your custom module:
```js title="medusa-config.js" highlights={[["4", "helloModuleService", "The key of the main service to be registered in the Medusa container."]]}
module.exports = defineConfig({
@@ -75,23 +81,25 @@ module.exports = defineConfig({
Its key (`helloModuleService`) is the name of the module’s main service. It will be registered in the Medusa container with that name.
Its value is an object having the `resolve` property, whose value is either a path to module's directory (relative to `src`, so it shouldn't include `src` in the path) or an `npm` package’s name.
Its value is an object having the `resolve` property, whose value is either a path to module's directory relative to `src`(it shouldn't include `src` in the path), or an `npm` package’s name.
### 4. Test the Module
---
## Test the Module
Since the module's main service is registered in the Medusa container, you can resolve it in other resources to use its functionalities.
One way to test the module is to create the API route `src/api/store/custom/route.ts` with the following content:
For example, create the API route `src/api/store/custom/route.ts` with the following content:
```ts title="src/api/store/custom/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
import HelloService from "../../../modules/hello/service"
import HelloModuleService from "../../../modules/hello/service"
export async function GET(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const helloModuleService: HelloService = req.scope.resolve(
const helloModuleService: HelloModuleService = req.scope.resolve(
"helloModuleService"
)
@@ -128,7 +136,6 @@ You’ll receive the following response:
<Note title="Use modules when" type="success">
- You're implementing a custom commerce feature. For example, you're implementing digital products.
- You want to extend data models in other commerce modules, such as adding a field or a relation to the `Product` model.
- You want to re-use your custom commerce functionalities across Medusa applications or use them in other environments, such as Edge functions and Next.js apps.
</Note>
+2 -2
View File
@@ -4,11 +4,11 @@ export const metadata = {
# {metadata.title}
In the next chapters, you’ll learn about the basic concepts of Medusa. These concepts are central to your Medusa development.
In the next chapters, you’ll learn about the basic concepts of Medusa that are central in your development.
By the end of these chapter, you’ll be able to:
- Expose your custom functionalities through REST APIs.
- Expose your custom functionalities through endpoints.
- Create custom modules that define custom business logic.
- Create custom data models.
- Execute scripts when the Medusa application starts.
@@ -1,10 +1,10 @@
export const metadata = {
title: `${pageNumber} Important Directories and Files`,
title: `${pageNumber} Project Directories and Files`,
}
# {metadata.title}
In this chapter, you’ll learn about important directories and files in your Medusa application's codebase.
In this chapter, you’ll learn about important directories and files in your Medusa application's project.
## src
@@ -13,10 +13,11 @@ This directory is the central place for your custom development. It includes the
- `admin`: Holds your admin dashboard's custom components and pages.
- `api`: Holds your custom API routes that are added as endpoints in your Medusa application.
- `jobs`: Holds your scheduled jobs that run at a specified interval during your Medusa application's runtime.
- `loaders`: Holds your loaders that run at the Medusa application's start-up.
- `modules`: Holds your custom modules that implement custom business logic.
- `scripts`: Holds your custom scripts to be executed using Medusa's CLI tool.
- `subscribers`: Holds your event listeners that are executed asynchronously whenever an event is emitted.
- `workflows`: Holds your custom flows that can be executed from anywhere in your application.
## medusa-config.js
This file holds your Medusa configurations, such as your PostgreSQL database configurations or your Medusa Admin plugin configurations.
This file holds your Medusa configurations, such as your PostgreSQL database configurations.
@@ -28,7 +28,7 @@ export default function () {
// the job's configurations
export const config = {
name: "hello-world",
name: "every-minute-message",
// execute every minute
schedule: "* * * * *",
}
@@ -37,10 +37,10 @@ export const config = {
A scheduled job file must export:
- The function to be executed whenever it’s time to run the scheduled job.
- A function to be executed whenever it’s time to run the scheduled job.
- A configuration object defining the job. It has two properties:
- `name`: a unique name for the job.
- `schedule`: a [cron expression](https://crontab.guru/).
- `schedule`: a [cron expression](https://crontab.guru/) specifying when to run the job.
This scheduled job executes every minute and logs into the terminal `Time to say hello world!`.
@@ -81,13 +81,13 @@ Time to say hello world!
## Resolve Resources
The scheduled job function receives an object parameter that has a `container` property. Its value is the Medusa container, which you can use to resolve resources in your Medusa application, such as services.
The scheduled job function receives a `container` parameter, which is the Medusa container. Use it to resolve resources in your Medusa application, such as services.
For example:
export const highlights = [
["11", "resolve", "Resolve the Product Module's main service."],
["11", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
["11", "ModuleRegistrationName.PRODUCT", "The module's registration name imported from `@medusajs/modules-sdk`."]
]
```ts title="src/jobs/hello-world.ts" highlights={highlights}
@@ -111,7 +111,7 @@ export default async function myCustomJob(
}
export const config = {
name: "hello-world",
name: "every-minute-message",
// execute every minute
schedule: "* * * * *",
}
+27 -16
View File
@@ -12,11 +12,11 @@ In this chapter, you’ll learn about workflows and how to define and execute th
A workflow is a series of queries and actions that complete a task.
You construct a workflow similar to how you create a JavaScript function, but unlike regular functions, a workflow creates an internal representation of your steps. This makes it possible to keep track of your Workflow’s progress, automatically retry failing steps, and roll back steps.
You construct a workflow similar to how you create a JavaScript function, but unlike regular functions, a workflow creates an internal representation of your steps. This makes it possible to keep track of your workflow’s progress, automatically retry failing steps, and roll back steps.
---
## How to Create and Execute a Workflow
## How to Create and Execute a Workflow?
### 1. Create the Steps
@@ -34,7 +34,9 @@ const step1 = createStep("step-1", async () => {
This creates one step that returns a hello message.
Steps can accepts input parameters. For example:
Steps can accept input parameters.
For example, add the following to `src/workflows/hello-world.ts`:
```ts title="src/workflows/hello-world.ts"
type WorkflowInput = {
@@ -48,7 +50,7 @@ const step2 = createStep("step-2", async ({ name }: WorkflowInput) => {
### 2. Create a Workflow
Next, create the workflow using the `createWorkflow` function:
Next, add the following to the same file to create the workflow using the `createWorkflow` function:
```ts title="src/workflows/hello-world.ts"
import {
@@ -93,7 +95,7 @@ To execute the workflow, invoke it passing the Medusa container as a parameter.
<CodeTabs group="resource-types">
<CodeTab label="API Route" value="api-route">
```ts title="src/api/store/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]}
```ts title="src/api/store/workflow/route.ts" highlights={[["11"], ["12"], ["13"], ["14"], ["15"], ["16"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports"
import type {
MedusaRequest,
MedusaResponse,
@@ -118,23 +120,30 @@ To execute the workflow, invoke it passing the Medusa container as a parameter.
</CodeTab>
<CodeTab label="Subscriber" value="subscriber">
```ts title="src/subscribers/customer-created.ts" highlights={[["13"], ["14"], ["15"], ["16"], ["17"], ["18"]]}
```ts title="src/subscribers/customer-created.ts" highlights={[["20"], ["21"], ["22"], ["23"], ["24"], ["25"]]} collapsibleLines="1-9" expandButtonLabel="Show Imports"
import {
type SubscriberConfig,
type SubscriberArgs,
CustomerService,
Customer,
} from "@medusajs/medusa"
import myWorkflow from "../workflows/hello-world"
import { ModuleRegistrationName } from "@medusajs/utils"
import { IUserModuleService } from "@medusajs/types"
export default async function handleCustomerCreate({
data,
container,
}: SubscriberArgs<Customer>) {
}: SubscriberArgs<{ id: string }>) {
const userId = "data" in data ? data.data.id : data.id
const userModuleService: IUserModuleService = container.resolve(
ModuleRegistrationName.USER
)
const user = await userModuleService.retrieveUser(userId)
const { result } = await myWorkflow(container)
.run({
input: {
name: data.first_name,
name: user.first_name,
},
})
@@ -142,7 +151,7 @@ To execute the workflow, invoke it passing the Medusa container as a parameter.
}
export const config: SubscriberConfig = {
event: CustomerService.Events.CREATED,
event: "user.created",
}
```
@@ -205,7 +214,7 @@ You’ll receive the following response:
- You're defining a flow with interactions across multiple systems and services.
- You're defining flows to be used across different resources. For example, if you want to invoke the flow manually through an API Router, but also want to automate its running through a scheduled job.
- You want to define how the series of actions are rolled-back when an error occurs.
- You want to define configurations related to errors and how to roll-back steps. This is explained more in later chapters.
</Note>
@@ -240,10 +249,12 @@ type WorkflowOutput = {
}
const step1 = createStep("step-1", async (_, context) => {
const productModuleService: IProductModuleService = context.container.resolve(
ModuleRegistrationName.PRODUCT
)
return new StepResponse((await productModuleService.listAndCount())[1])
const productModuleService: IProductModuleService =
context.container.resolve(ModuleRegistrationName.PRODUCT)
const [, count] = await productModuleService.listAndCountProducts()
return new StepResponse(count)
})
const myWorkflow = createWorkflow<unknown, WorkflowOutput>(