docs: fixes and changes based on latest updates (#7322)

* docs: changes based on DX changes

* remove fields no longer needed

* remove unnecessary parameters

* fixes to authenticate middleware usage

* add highlight to migrations config

* change configuration to http

* added missing remote link docs

* fix name in sidebar

* added notification module docs + updated file module docs

* add vale exceptions

* fix vale errors

* added docs on custom cli scripts
This commit is contained in:
Shahed Nasser authored and GitHub committed 2024-05-22 13:37:48 +03:00
1 parent ff5d573887
commit 154673f3d8
55 files changed
+1674 -3791

No files matched your search

@@ -12,16 +12,18 @@ Cross-Origin Resource Sharing (CORS) allows only configured origins to access yo
### CORS Configurations
You configure allowed origins for Store and Admin API Routes using the `store_cors` and `admin_cors` configurations in `medusa-config.js`. Each of these configurations accepts a URL pattern to identify allowed origins.
You configure allowed origins for Store and Admin API Routes using the `storeCors` and `adminCors` properties of the `http` configuration in `medusa-config.js`. Each of these configurations accepts a URL pattern to identify allowed origins.
For example:
```js title="medusa-config.js"
module.exports = {
projectConfig: {
admin_cors: "http://localhost:7001",
store_cors: "http://localhost:8000",
// ...
http: {
adminCors: "http://localhost:7001",
storeCors: "http://localhost:8000",
// ...
}
},
// ...
}
@@ -90,7 +92,7 @@ export const config: MiddlewaresConfig = {
return cors({
origin: parseCorsOrigins(
configModule.projectConfig.store_cors
configModule.projectConfig.http.storeCors
),
credentials: true,
})(req, res, next)
@@ -101,4 +103,4 @@ export const config: MiddlewaresConfig = {
}
```
This retrieves the configurations exported from `medusa-config.js` and applies the `store_cors` to routes starting with `/custom`.
This retrieves the configurations exported from `medusa-config.js` and applies the `storeCors` to routes starting with `/custom`.
@@ -59,21 +59,21 @@ For example:
```ts title="src/api/store/me/custom/route.ts" highlights={[["16", "", "Access the logged-in customer's ID."]]}
import type {
MedusaRequest,
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { ICustomerModuleService } from "@medusajs/types"
export const GET = async (
req: MedusaRequest,
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const customerService: ICustomerModuleService =
req.scope.resolve(ModuleRegistrationName.CUSTOMER)
const customer = await customerService.retrieve(
req.user.customer_id
req.auth.actor_id
)
// ...
@@ -92,21 +92,21 @@ For example:
```ts title="src/api/admin/custom/route.ts" highlights={[["16", "req.user.userId", "Access the logged-in admin user's ID."]]}
import type {
MedusaRequest,
AuthenticatedMedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IUserModuleService } from "@medusajs/types"
export const GET = async (
req: MedusaRequest,
req: AuthenticatedMedusaRequest,
res: MedusaResponse
) => {
const userService: IUserModuleService = req.scope.resolve(
ModuleRegistrationName.USER
)
const user = await userService.retrieve(req.user.userId)
const user = await userService.retrieve(req.auth.actor_id)
// ...
}
@@ -129,9 +129,7 @@ export const highlights = [
```ts title="src/api/middlewares.ts" highlights={highlights}
// TODO update import
import {
authenticate,
} from "@medusajs/medusa/dist/utils/authenticate-middleware"
import { MiddlewaresConfig, authenticate } from "@medusajs/medusa"
export const config: MiddlewaresConfig = {
routes: [
@@ -0,0 +1,78 @@
export const metadata = {
title: `${pageNumber} Custom CLI Scripts`,
}
# {metadata.title}
In this chapter, you'll learn how create and execute custom scripts from Medusa's CLI tool.
## What is a Custom CLI Script?
A custom CLI script is a function to execute through Medusa's CLI tool. This is useful when creating custom Medusa tooling to run as a CLI tool.
---
## How to Create a Custom CLI Script?
To create a custom CLI script, create a TypeScript or JavaScript file under the `src/scripts` directory. The file must default export a function.
For example, create the file `src/scripts/my-script.ts` with the following content:
```ts title="src/scripts/my-script.ts"
import {
ExecArgs,
IProductModuleService
} from "@medusajs/types"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
export default async function myScript ({
container
}: ExecArgs) {
const productModuleService: IProductModuleService =
container.resolve(ModuleRegistrationName.PRODUCT)
const [, count] = await productModuleService.listAndCount()
console.log(`You have ${count} product(s)`)
}
```
The function receives as a parameter an object having a `container` property, which is an instance of the Medusa Container. Use it to resolve resources in your Medusa application.
---
## How to Run Custom CLI Script?
To run the custom CLI script, `build` your code then run the `exec` command:
```bash npm2yarn
npm run build
npx medusa exec ./dist/scripts/my-script.js
```
Notice that you pass the path to the file in the `dist` directory.
---
## Custom CLI Script Arguments
Your script can accept arguments from the command line. Arguments are passed to the function's object parameter in the `args` property.
For example:
```ts
import { ExecArgs } from "@medusajs/types"
export default async function myScript ({
args
}: ExecArgs) {
console.log(`The arguments you passed: ${args}`)
}
```
Then, pass the arguments in the `exec` command after the file path:
```bash npm2yarn
npm run build
npx medusa exec ./dist/scripts/my-script.js arg1 arg2
```
@@ -37,17 +37,14 @@ The following example showcase a data model with common definitions:
```ts
import {
BeforeCreate,
Entity,
Enum,
OneToOne,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import {
createPsqlIndexStatementHelper,
generateEntityId,
} from "@medusajs/utils"
import { BaseEntity } from "@medusajs/utils"
// assuming this is another implemented data model
import ProductVariant from "./product-variant"
export enum MediaType {
@@ -55,14 +52,8 @@ export enum MediaType {
PREVIEW = "preview"
}
const VariantIdIndex = createPsqlIndexStatementHelper({
name: "IDX_product_media_variant_id",
tableName: "product_media",
columns: "variant_id",
}).MikroORMIndex
@Entity()
export default class ProductMedia {
class ProductMedia extends BaseEntity {
@PrimaryKey({ columnType: "text" })
id: string
@@ -78,7 +69,6 @@ export default class ProductMedia {
@Property({ columnType: "text" })
mime_type: string
@VariantIdIndex()
@Property({ columnType: "text" })
variant_id: string
@@ -87,21 +77,13 @@ export default class ProductMedia {
onDelete: "cascade",
})
variant: ProductVariant
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "promed")
}
@OnInit()
OnInit() {
this.id = generateEntityId(this.id, "promed")
}
}
export default ProductMedia
```
In the example above:
- The `ProductMedia` data model has the columns `id`, `name`, `type`, `file_key`, `mime_type`, and `variant_id`.
- The data model has an index on the `variant_id` column.
- The data model has a one-to-one relation to the `ProductVariant` data model.
- The data model has a one-to-one relation to a `ProductVariant` data model.
@@ -20,25 +20,28 @@ When retrieving or listing records of that data model, records having their `del
To create a soft-deletable model, first, add the following filter decorator to the data model class:
```ts title="src/module/hello/models/my-soft-deletable.ts" highlights={[["6"]]}
```ts title="src/module/hello/models/my-soft-deletable.ts" highlights={[["7"]]}
// other imports...
import { Entity, Filter } from "@mikro-orm/core"
import { DALUtils } from "@medusajs/utils"
import { BaseEntity } from "@medusajs/utils"
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
export default class MySoftDeletable {
class MySoftDeletable extends BaseEntity {
// ...
}
export default MySoftDeletable
```
Then, add a `deleted_at` field to the data model:
```ts highlights={[["4"], ["5"]]}
```ts highlights={[["7"], ["8"]]}
// other imports...
import { Property } from "@mikro-orm/core"
export default class MySoftDeletable {
class MySoftDeletable extends BaseEntity {
// ...
@Property({ columnType: "timestamptz", nullable: true })
@@ -1,48 +0,0 @@
export const metadata = {
title: `${pageNumber} Database Connection Loader`,
}
# {metadata.title}
In this document, you’ll learn about how to load the database connection in your module.
## How to Add a Database Connection Loader?
To ensure that your module's database operations use the Medusa application's database connection, export a database connection loader in your module's definition.
Medusa provides a utility function that creates the database connection loader for you.
For example:
```ts title="src/modules/hello/index.ts"
// other imports...
import { ModulesSdkUtils } from "@medusajs/utils"
import { MyCustom } from "./models/my-custom"
const pathToMigrations = __dirname + "/migrations"
// ...
const connectionLoader = ModulesSdkUtils
.mikroOrmConnectionLoaderFactory({
moduleName,
moduleModels: [MyCustom],
migrationsPath: pathToMigrations,
})
export default {
// ...
loaders: [
// ...
connectionLoader,
],
}
```
The `mikroOrmConnectionLoaderFactory` function accepts an object with the following properties:
- `moduleName`: The name of the module.
- `moduleModules`: An array of data models in the module.
- `migrationsPath`: The path to the migrations file.
You can now implement data-management methods as explained in the next chapters.
@@ -14,63 +14,12 @@ So, resources in the module, such as services or loaders, can only resolve other
---
## The Container Loader
## Resources Registered in the Module's Container
Medusa provides a utility function that creates a container loader. This loader takes care of registering resources in your container.
Some resources registered in the module's container are:
For example:
export const highlights = [
["11", "moduleContainerLoaderFactory", "Create the container loader."],
["25", "", "Export the container loader."]
]
```ts title="src/modules/hello/index.ts" highlights={highlights}
// other imports...
import HelloModuleService from "./service"
import { MyCustom } from "./models/my-custom"
import {
ModulesSdkUtils,
MikroOrmBaseRepository,
} from "@medusajs/utils"
// ...
const containerLoader = ModulesSdkUtils.moduleContainerLoaderFactory({
moduleModels: {
MyCustom,
},
moduleRepositories: {
BaseRepository: MikroOrmBaseRepository,
},
moduleServices: {
HelloModuleService,
},
})
export default {
// ...
loaders: [containerLoader],
}
```
You create the container loader using the utility function `moduleContainerLoaderFactory` and export it in the module's definition.
### moduleContainerLoaderFactory Parameters
The `moduleContainerLoaderFactory` function accepts as a parameter an object with the following properties:
- `moduleModels`: An object where each key is a data model's name, and its value the data model class.
- `moduleRepositories`: An object of the module's repositories. You import here the `MikroOrmBaseRepository` from `@medusajs/utils` and use it as the value of `BaseRepository`.
- `moduleServices`: An object where each key is the service's registration name, and its value is the service class.
### Resources Registered by the Container Loader
The container loader registers in the module's container:
- The services passed in the `moduleServices` property.
- The repositories provided in the `moduleRepositories`.
- A generated service for each data model provided in `moduleModels`.
- The module's main service.
- A generated service for each data model in your module. The registration name is the camel-case data model name suffixed by `Service`. For example, `myCustomService`.
![Example of registered resources in the container](https://res.cloudinary.com/dza7lstvk/image/upload/v1714400573/Medusa%20Book/modules-container_mkcbaq.jpg)
@@ -84,7 +33,7 @@ A service's constructor accepts as a first parameter an object used to resolve r
For example:
```ts
```ts highlights={[["5"], ["12"]]}
import { ModulesSdkTypes } from "@medusajs/types"
import { MyCustom } from "./models/my-custom"
@@ -109,12 +58,11 @@ A loader function in a module accepts as a parameter an object having the proper
For example:
```ts
```ts highlights={[["8"]]}
import {
LoaderOptions,
} from "@medusajs/modules-sdk"
export default function helloWorldLoader({
container,
}: LoaderOptions) {
@@ -6,14 +6,14 @@ export const metadata = {
In this document, you’ll learn how to implement database operations, such as creating a record, in the main service.
## Use Data Model Services
## Use the Data Model's Generated Service
The module container has a generated service registered for each data model. You can resolve that service and use it to perform database operations on the data model.
To perform database operations on a data model, use the model's generated service in the module's container.
For example:
export const highlights = [
["13", "", "Inject myCustomService, which is the service generated by the container loader for the MyCustom data model."],
["13", "", "Inject myCustomService, which is the generated service of the `MyCustom` data model."],
["22", "", "Add a new field for the generated service of the MyCustom data model."],
["29", "", "Set the class field to the injected dependency."],
["35", "create", "Use the `create` method of the generated service."]
@@ -55,8 +55,7 @@ class HelloModuleService extends ModulesSdkUtils
data: CreateMyCustomDTO
): Promise<MyCustomDTO> {
const myCustom = await this.myCustomService_.create(
data,
context
data
)
return myCustom
@@ -10,137 +10,14 @@ In this chapter, you’ll learn what a link module is and how to use the remote
A link module is a module whose only purpose is to define a relationship between two modules’ data models. The relationship is represented as a pivot or link table in the database, pointing at the primary keys of each data model.
For example, Medusa defines a link module between the Product and Pricing modules. The link module builds a relationship between the `ProductVariant` data model and the `PriceSet` data model.
For example, Medusa has a link module that defines a relationship between the Product and Pricing modules. It links the `ProductVariant` and `PriceSet` data models.
![Diagram showcasing the link module between the Product and Pricing modules](https://res.cloudinary.com/dza7lstvk/image/upload/v1709651569/Medusa%20Resources/product-pricing_vlxsiq.jpg)
Link modules provide more flexibility in managing relationships between modules while maintaining module isolation.
Link modules provide more flexibility in managing relationships between modules while maintaining module isolation. The Medusa application only creates the link tables when both modules are available.
Link modules are currently only available for Medusa’s commerce modules. The Medusa application only creates the link tables when both modules are available.
<Note type="soon">
---
Link modules are currently only available for Medusa’s commerce modules.
## What is the Remote Link?
The remote link is a class with utility methods to manage links between modules. It’s registered in the Medusa container under the `remoteLink` registration name.
For example:
```ts
import {
MedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
import {
ModuleRegistrationName,
RemoteLink,
} from "@medusajs/modules-sdk"
import {
ContainerRegistrationKeys,
} from "@medusajs/utils"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const remoteLink: RemoteLink = req.scope.resolve(
ContainerRegistrationKeys.REMOTE_LINK
)
// ...
}
```
You can use its methods to manage links, such as create or delete links.
### Create Link
To create a link between records of two data models, use the `create` method of the remote link.
For example:
```ts
import { Modules } from "@medusajs/utils"
// ...
await remoteLink.create({
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.PRICING]: {
price_set_id: price.id,
},
})
```
The `create` method accepts as a parameter an object. The object’s keys are the names of the linked modules.
The value of each module’s property is an object. It defines the values of the linked fields.
So, in the example above, you specify for the Product Module the value of the `variant_id`, and for the Pricing Module the value of `price_set_id`. These are the fields linked between the models of the two modules.
### Dismiss Link
To remove a link between records of two data models, use the `dismiss` method of the remote link. This doesn’t remove the records, only the relation between them.
For example:
```ts
import { Modules } from "@medusajs/utils"
// ...
await remoteLink.dismiss({
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.PRICING]: {
price_set_id: price.id,
},
})
```
The `dismiss` method accepts the same parameter type as the [create method](#create-link).
### Cascade Delete Linked Records
If a record, such as a variant, is deleted, use the `delete` method of the remote link to delete all associated links with cascade delete enabled.
For example:
```ts
import { Modules } from "@medusajs/utils"
// ...
await productModuleService.deleteVariants([variant.id])
await remoteLink.delete({
[Modules.PRODUCT]: {
variant_id: variant.id,
},
})
```
This deletes all records linked to the deleted variant with cascade delete enabled in their relationship.
### Restore Linked Records
If a record, such as a variant, that was previously soft-deleted is now restored, use the `restore` method of the remote link to restore all associated links that were cascade deleted.
For example:
```ts
import { Modules } from "@medusajs/utils"
// ...
await productModuleService.restoreVariants([variant.id])
await remoteLink.restore({
[Modules.PRODUCT]: {
variant_id: variant.id,
},
})
```
</Note>
@@ -40,18 +40,16 @@ The Medusa application resolves these relationships while maintaining isolation
Consider you’re creating a data model that adds custom fields associated with a product:
```ts title="src/modules/hello/models/custom-product-data.ts" highlights={[["19"]]}
import { generateEntityId } from "@medusajs/utils"
```ts title="src/modules/hello/models/custom-product-data.ts" highlights={[["17"]]}
import { BaseEntity } from "@medusajs/utils"
import {
BeforeCreate,
Entity,
OnInit,
PrimaryKey,
Property,
} from "@mikro-orm/core"
@Entity()
export class CustomProductData {
export class CustomProductData extends BaseEntity {
@PrimaryKey({ columnType: "text" })
id!: string
@@ -60,16 +58,6 @@ export class CustomProductData {
@Property({ columnType: "text", nullable: true })
product_id?: string
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "cpd")
}
@OnInit()
OnInit() {
this.id = generateEntityId(this.id, "cpd")
}
}
```
@@ -81,7 +69,6 @@ When you add a new data model, make sure to:
- [Create a migration for it.](../../../basics/data-models/page.mdx#create-a-migration)
- [Add it to the second parameter of the main service's factory function.](../service-factory/page.mdx#abstractModuleServiceFactory-parameters)
- Add it to the [container](../container/page.mdx) and [connection](../connection-loader/page.mdx) loaders.
</Note>
@@ -115,7 +102,6 @@ class HelloModuleService extends ModulesSdkUtils
__joinerConfig(): ModuleJoinerConfig {
return {
serviceName: "helloModuleService",
primaryKeys: ["id"],
alias: [
{
name: ["my_custom"],
@@ -159,12 +145,6 @@ This creates a relationship to the `Product` data model of the Product Module us
optional: false,
description: "The name of your module (as added in `medusa-config.js`)."
},
{
name: "primaryKeys",
type: "`string[]`",
optional: false,
description: "The primary key field names used in your module's data models.",
},
{
name: "alias",
type: "`object[]`",
@@ -240,8 +220,6 @@ const modules = {
helloModuleService: {
// ...
definition: {
key: "helloModuleService",
registrationName: "helloModuleService",
isQueryable: true,
},
},
@@ -251,28 +229,6 @@ const modules = {
Enabling the `isQueryable` property is required to use relationships in a module.
The `definition` property’s value is an object that accepts the following properties, among others:
<TypeList types={[
{
name: "key",
type: "`string`",
optional: false,
description: "The module's key in the `modules` object."
},
{
name: "registrationName",
type: "`string`",
optional: false,
description: "The name that the main service is registered under in the Medusa container. It’s recommended to be the same as `key`'s value.",
},
{
name: "isQueryable",
type: "`boolean`",
description: "Whether the module is queryable. This must be enabled to allow a module to have relationships."
}
]} sectionTitle="Adjust Module Configuration" />
---
## Reference Inner Data Models
@@ -0,0 +1,198 @@
export const metadata = {
title: `${pageNumber} Remote Link`,
}
# {metadata.title}
In this chapter, you’ll learn what the remote link is and how to use it to manage links.
## What is the Remote Link?
The remote link is a class with utility methods to manage links defined by the link module. It’s registered in the Medusa container under the `remoteLink` registration name.
For example:
```ts
import {
MedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
import {
ModuleRegistrationName,
RemoteLink,
} from "@medusajs/modules-sdk"
import {
ContainerRegistrationKeys,
} from "@medusajs/utils"
export async function POST(
req: MedusaRequest,
res: MedusaResponse
): Promise<void> {
const remoteLink: RemoteLink = req.scope.resolve(
ContainerRegistrationKeys.REMOTE_LINK
)
// ...
}
```
You can use its methods to manage links, such as create or delete links.
### Create Link
To create a link between records of two data models, use the `create` method of the remote link.
For example:
```ts
import { Modules } from "@medusajs/utils"
// ...
await remoteLink.create({
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.PRICING]: {
price_set_id: price.id,
},
})
```
The `create` method accepts as a parameter an object. The object’s keys are the names of the linked modules.
The value of each module’s property is an object. It defines the values of the linked fields.
So, in the example above, you specify for the Product Module the value of the `variant_id`, and for the Pricing Module the value of `price_set_id`. These are the fields linked between the models of the two modules.
### Dismiss Link
To remove a link between records of two data models, use the `dismiss` method of the remote link. This doesn’t remove the records, only the relation between them.
For example:
```ts
import { Modules } from "@medusajs/utils"
// ...
await remoteLink.dismiss({
[Modules.PRODUCT]: {
variant_id: product.variants[0].id,
},
[Modules.PRICING]: {
price_set_id: price.id,
},
})
```
The `dismiss` method accepts the same parameter type as the [create method](#create-link).
### Cascade Delete Linked Records
If a record, such as a variant, is deleted, use the `delete` method of the remote link to delete all associated links with cascade delete enabled.
For example:
```ts
import { Modules } from "@medusajs/utils"
// ...
await productModuleService.deleteVariants([variant.id])
await remoteLink.delete({
[Modules.PRODUCT]: {
variant_id: variant.id,
},
})
```
This deletes all records linked to the deleted variant with cascade delete enabled in their relationship.
### Restore Linked Records
If a record, such as a variant, that was previously soft-deleted is now restored, use the `restore` method of the remote link to restore all associated links that were cascade deleted.
For example:
```ts
import { Modules } from "@medusajs/utils"
// ...
await productModuleService.restoreVariants([variant.id])
await remoteLink.restore({
[Modules.PRODUCT]: {
variant_id: variant.id,
},
})
```
---
## Link Module's Service
The remote link has a `getLinkModule` method to retrieve the service of the link module. This service has `list` and `retrieve` methods to retrieve the linked items.
For example, to retrieve the link module of the Product and Pricing modules:
export const linkModuleServiceHighlights = [
["6", "Modules.PRODUCT", "The name of the first module in the link module's definition."],
["7", '"variant_id"', "The foreign key that links to the record in the first module."],
["8", "Modules.PRICING", "The name of the second module in the link module's definition."],
["9", '"price_set_id"', "The foreign key that links to the record in the second module."],
["12", "", "The link module's service is undefined if either of the modules isn't installed or there's no link module with the specified definition."]
]
```ts highlights={linkModuleServiceHighlights}
import { Modules } from "@medusajs/utils"
// ...
const linkModuleService = remoteLink.getLinkModule(
Modules.PRODUCT,
"variant_id",
Modules.PRICING,
"price_set_id"
)
if (!linkModuleService) {
return
}
```
The `getLinkModule` method accepts four parameter:
1. A string indicating the name of the first module in the link module's definition.
2. A string indicating the foreign key that links to the record in the first module.
3. A string indicating the name of the second module in the link module's definition.
4. A string indicating the foreign key that links to the record in the second module.
Notice that the returned link module service might be undefined if either of the modules isn't installed, or if there's no link module with the specified definition.
### List Linked Items
The link module's service has a `list` method that retrieves a list of linked records. It also accepts filters to retrieve specific linked items.
For example, to retrieve the price sets linked to a variant:
```ts
import { Modules } from "@medusajs/utils"
// ...
const linkModuleService = remoteLink.getLinkModule(
Modules.PRODUCT,
"variant_id",
Modules.PRICING,
"price_set_id"
)
const items = await linkModuleService.list(
{ variant_id: [variant.id] },
{ select: ["variant_id", "price_set_id"] }
)
```
@@ -114,7 +114,7 @@ The `remoteQueryObjectFromString` function accepts a `variables` property. You c
## Sort Records
```ts highlights={[["4"], ["5"], ["6"]]}
```ts highlights={[["5"], ["6"], ["7"]]}
const query = remoteQueryObjectFromString({
entryPoint: "custom_product_data",
fields: ["id", "custom_field", "product.title"],
@@ -22,15 +22,6 @@ Medusa provides a service factory that your module’s main service can extend.
## How to Extend the Service Factory?
<Note type="check">
Your module must be using the following loaders from the previous chapters:
- [Container Loader](../container/page.mdx).
- [Connection Loader](../connection-loader/page.mdx).
</Note>
Medusa provides the service factory as a function your service extends. The function creates and returns a service class with generated data-management methods.
For example, create the file `src/modules/hello/service.ts` with the following content:
@@ -9,9 +9,9 @@ In the previous chapters, you got a brief introduction to Medusa’s basic conce
The next chapters dive deeper into each concept. By the end of these chapters, you’ll be able to:
- Expose API routes with control over authentication, parsing request bodies, and more.
- Create data models with complex fields and relations.
- Manage data models in services.
- Create relationships between modules.
- Create data models with complex fields and relations.
- Create loaders outside of modules.
- Access events payloads.
- Create advanced workflows and configure retries and timeout.
@@ -12,16 +12,21 @@ An architectural module implements features and mechanisms related to the Medusa
Since modules are interchangeable, you have more control over Medusa’s architecture. For example, you can choose to use Memcached for event handling instead of Redis.
<Note>
---
Refer to the [Architectural Modules reference](!resources!/architectural-modules) for a list of Medusa’s architectural modules.
### Architectural Module Type
</Note>
There are different architectural module types including:
![Diagram illustrating how the modules connect to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1716197340/Medusa%20Book/architectural-modules_bj9bb9.jpg)
- Cache Module: Defines the caching mechanism or logic to cache computational results.
- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events.
- File Module: Integrates a storage service to handle uploading files.
- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers.
---
## How to Create an Architectural Module?
## Architectural Modules List
You create an architectural module like you’ve been creating custom modules: create a module with a service and export that service in the module’s definition file. The module's service extends an abstract class or interface provided by Medusa with predefined method signatures.
The next chapters explain the different architectural module types and how to create an architectural module for each.
Refer to the [Architectural Modules reference](!resources!/architectural-modules) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module.
@@ -1,33 +0,0 @@
export const metadata = {
title: `${pageNumber} Cache Module`,
}
# {metadata.title}
In this chapter, you’ll learn about what the Cache Module is.
## What is a Cache Module?
A Cache Module is used to cache the results of computations such as price selection or various tax calculations.
The underlying database, third-party service, or caching logic is flexible since it's implemented in a module. You can choose from Medusa’s cache modules or create your own to support something more suitable for your architecture.
---
## Default Cache Module
By default, Medusa uses the In-Memory Cache Module. This module uses a plain JavaScript Map object to store the cache data.
This is useful for development. However, for production, it's highly recommended to use other Cache Modules, such as the [Redis Cache Module](!resources!/architectural-modules/cache/redis).
---
## List of Cache Modules
Refer to the Medusa Learning Resources for a [list of available cache modules](!resources!/architectural-modules/cache).
---
## Create a Cache Module
To create a cache module, refer to [this guide in the Medusa Learning Resources](!resources!/architectural-modules/cache/create).
@@ -1,35 +0,0 @@
export const metadata = {
title: `${pageNumber} Event Module`,
}
# {metadata.title}
In this chapter, you’ll learn about what the Event Module is.
## What is an Event Module?
In previous chapters, you learned that the Medusa application emits events, and subscribers handle those events.
The Event Module implements the underlying publish/subscribe system that handles queueing events, emitting them, and executing their subscribers.
This makes the event architecture customizable, as you can either choose one of Medusa’s event modules or create your own.
---
## Default Event Module
By default, Medusa uses the Local Event Module. This module uses Node’s EventEmitter to implement the publish/subscribe system.
This is useful for development. However, for production, it’s highly recommended to use other Event Modules, such as the [Redis Event Module](!resources!/architectural-modules/event/redis).
---
## List of Event Modules
Refer to the Medusa Learning Resources for a [list of available event modules](!resources!/architectural-modules/event).
---
## Create a Event Module
To create an event module, refer to [this guide in the Medusa Learning Resources](!resources!/architectural-modules/event/create).
@@ -1,35 +0,0 @@
export const metadata = {
title: `${pageNumber} File Module`,
}
# {metadata.title}
In this chapter, you’ll learn about what the File Module is.
## What is the File Module?
The File Module exposes the functionalities to upload assets, such as product images, to the Medusa application.
---
## What is a File Provider Module?
A file provider module implements the logic of handling uploads and downloads. The File Module must have one file provider module configured.
By default, Medusa uses the Local File Module. This module uploads files to the `uploads` directory of your Medusa application.
{/* TODO add once s3 module is working/published? */}
{/* This is useful for development. However, for production, it’s highly recommended to use other File Modules, such as the [S3 Module](!resources!/architectural-modules/event/redis). */}
---
## List of File Provider Modules
Refer to the Medusa Learning Resources for a [list of available file provider modules](!resources!/architectural-modules/file).
---
## Create a File Provider Module
To create a file provider module, refer to [this guide in the Medusa Learning Resources](!resources!/architectural-modules/references/file-provider-module).
+4 -85
View File
@@ -30,44 +30,24 @@ A data model is a class created in a TypeScript or JavaScript file under a modul
For example, create the file `src/modules/hello/models/my-custom.ts` with the following content:
```ts title="src/modules/hello/models/my-custom.ts"
import { generateEntityId } from "@medusajs/utils"
import { BaseEntity } from "@medusajs/utils"
import {
BeforeCreate,
Entity,
OnInit,
PrimaryKey,
Property,
} from "@mikro-orm/core"
@Entity()
export class MyCustom {
export class MyCustom extends BaseEntity {
@PrimaryKey({ columnType: "text" })
id!: string
@Property({ columnType: "text" })
name: string
@BeforeCreate()
onCreate() {
this.id = generateEntityId(this.id, "mc")
}
@OnInit()
OnInit() {
this.id = generateEntityId(this.id, "mc")
}
}
```
This defines a new data model `MyCustom` with the fields `id` and `name`.
The `onCreate` method generates an ID for the data model's record when it's created. The `onInit` method sets the ID when the record is loaded.
<Note title="Tip">
The `generateEntityId` utility method prefixes the `id` of a record with the string provided in the second parameter. This follows Medusa's conventions of creating IDs.
</Note>
This defines a new data model `MyCustom` with the fields `id` and `name`. Data models extend the `BaseEntity` class imported from `@medusajs/utils`.
### Create a Migration
@@ -80,8 +60,7 @@ A migration is a class created in a TypeScript or JavaScript file under a module
1. Create the file `src/modules/hello/mikro-orm.config.dev.ts` with the following content:
```ts
import "dotenv/config"
```ts highlights={[["8", "hello", "The module's name."]]}
import path from "path"
import { TSMigrationGenerator } from "@medusajs/utils"
import { MyCustom } from "./models/my-custom"
@@ -140,66 +119,6 @@ The queries performed in each of the methods use PostgreSQL syntax.
</Note>
### Add Migration to Module Definition
After creating the migration, you must add it to your module's definition.
To add a module's migrations to its definitions, use the `ModulesSdkUtils` utility functions imported from `@medusajs/utils`. It has functions to create and define the migration scripts in your module definition.
Change the content of `src/modules/hello.index.ts` that you created in a [previous chapter](../modules-and-services/page.mdx) to the following:
```ts title="src/modules/hello.index.ts" highlights={[["2"], ["6"], ["10"], ["12"], ["14"], ["18"], ["22"], ["28"], ["36"]]}
import HelloModuleService from "./service"
// add necessary imports
import { ModulesSdkUtils } from "@medusajs/utils"
import { MyCustom } from "./models/my-custom"
// define useful constants
const moduleName = "hello"
const pathToMigrations = __dirname + "/migrations"
// assemble object to pass to utility functions
const migrationScriptOptions = {
// the module's name
moduleName,
// the data models of the modules
models: {
MyCustom,
},
// the path to the migrations directory
pathToMigrations,
}
// create and export the script that runs migrations
export const runMigrations = ModulesSdkUtils
.buildMigrationScript(
migrationScriptOptions
)
// create and export the script that reverts migrations
export const revertMigration = ModulesSdkUtils
.buildRevertMigrationScript(
migrationScriptOptions
)
export default {
service: HelloModuleService,
// add the run and revert migration scripts to the module's definition
runMigrations,
revertMigration,
}
```
After importing `ModulesSdkUtils`, you use its `buildMigrationScript` function to create the script that runs the migration, and its `buildRevertMigrationScript` function to create the script that reverts the migration.
Both the `buildMigrationScript` and `buildRevertMigrationScript` accept the same object type as a parameter, which has the following properties:
- `moduleName`: The name of the module that the migrations belong to.
- `models`: An object of the module's data models.
- `pathToMigrations`: The path to the `migrations` directory.
Both created scripts must be exported in the file and within the module's definition object.
### Run Migration
To reflect the changes in the migration, transpile your source files using the `build` command, then run the `migration` command:
@@ -24,15 +24,17 @@ To send requests from the storefront to the Medusa application’s Store API Rou
## Configure CORS
The Medusa application’s API routes are guarded by a CORS middleware. Make sure to set the `store_cors` configuration of your Medusa application to the storefront’s URL.
The Medusa application’s API routes are guarded by a CORS middleware. Make sure to set the `storeCors` property of the `http` configuration in `medusa-config.js` to the storefront’s URL.
For example:
```js title="medusa-config.js"
module.exports = {
projectConfig: {
store_cors: "http://localhost:3000",
// ...
http: {
storeCors: "http://localhost:3000",
// ...
}
},
// ...
}
+10 -24
View File
@@ -102,10 +102,6 @@ export const sidebar = sidebarAttachHrefCommonOptions(
path: "/advanced-development/modules/container",
title: "Module's Container",
},
{
path: "/advanced-development/modules/connection-loader",
title: "Database Connection Loader",
},
{
path: "/advanced-development/modules/service-factory",
title: "Service Factory",
@@ -130,6 +126,10 @@ export const sidebar = sidebarAttachHrefCommonOptions(
path: "/advanced-development/modules/link-modules",
title: "Link Modules",
},
{
path: "/advanced-development/modules/remote-link",
title: "Remote Link",
},
],
},
{
@@ -200,6 +200,10 @@ export const sidebar = sidebarAttachHrefCommonOptions(
},
],
},
{
path: "/advanced-development/custom-cli-scripts",
title: "Custom CLI Scripts",
},
{
path: "/advanced-development/admin",
title: "Admin Development",
@@ -243,26 +247,8 @@ export const sidebar = sidebarAttachHrefCommonOptions(
],
},
{
path: "/architectural-concepts",
title: "Architectural Concepts",
children: [
{
path: "/architectural-concepts/architectural-modules",
title: "Architectural Modules",
},
{
path: "/architectural-concepts/cache-module",
title: "Cache Module",
},
{
path: "/architectural-concepts/event-module",
title: "Event Module",
},
{
path: "/architectural-concepts/file-module",
title: "File Module",
},
],
path: "/architectural-concepts/architectural-modules",
title: "Architectural Modules",
},
{
path: "/debugging-and-testing",