docs: updates to use DML and other changes (#7834)

- Change existing data model guides and add new ones for DML
- Change module's docs around service factory + remove guides that are now necessary
- Hide/remove all mentions of module relationships, or label them as coming soon.
- Change all data model creation snippets to use DML
- use `property` instead of `field` when referring to a data model's properties.
- Fix all snippets in commerce module guides to use new method suffix (no more main model methods)
- Rework recipes, removing/hiding a lot of sections as a lot of recipes are incomplete with the current state of DML.


### Other changes

- Highlight fixes in some guides
- Remove feature flags guide
- Fix code block styles when there are no line numbers.

### Upcoming changes in other PRs

- Re-generate commerce module references (for the updates in the method names)
- Ensure that the data model references are generated correctly for models using DML.
- (probably at a very later point) revisit recipes
This commit is contained in:
Shahed Nasser
2024-06-26 07:55:59 +00:00
committed by GitHub
parent 62dacdda75
commit 0462cc5acf
126 changed files with 1808 additions and 14242 deletions
@@ -75,9 +75,12 @@ Widgets that are injected into a details page (for example, `product.details.aft
For example:
```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["5"]]}
```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["8"]]}
import { defineWidgetConfig } from "@medusajs/admin-shared"
import { DetailWidgetProps, AdminProduct } from "@medusajs/types"
import {
DetailWidgetProps,
AdminProduct,
} from "@medusajs/types"
const ProductWidget = ({
data,
@@ -41,8 +41,8 @@ To create an API route that accepts multiple path parameters, create within the
For example, create an API route at `src/api/store/hello-world/[id]/name/[name]/route.ts`:
export const multiplePathHighlights = [
["11", "req.params.id", "Access the path parameter `id`"],
["11", "req.params.name", "Access the path parameter `name`"]
["12", "req.params.id", "Access the path parameter `id`"],
["13", "req.params.name", "Access the path parameter `name`"]
]
```ts title="src/api/store/hello-world/[id]/name/[name]/route.ts" highlights={multiplePathHighlights}
@@ -56,7 +56,9 @@ export const GET = (
res: MedusaResponse
) => {
res.json({
message: `[GET] Hello ${req.params.id} - ${req.params.name}!`,
message: `[GET] Hello ${
req.params.id
} - ${req.params.name}!`,
})
}
```
@@ -125,12 +125,15 @@ To protect custom API Routes that dont start with `/store/customers/me` or `/
For example:
export const highlights = [
["8", "authenticate", "Only authenticated admin users can access routes starting with `/custom/admin`"],
["14", "authenticate", "Only authenticated customers can access routes starting with `/custom/customers`"]
["11", "authenticate", "Only authenticated admin users can access routes starting with `/custom/admin`"],
["17", "authenticate", "Only authenticated customers can access routes starting with `/custom/customers`"]
]
```ts title="src/api/middlewares.ts" highlights={highlights}
import { MiddlewaresConfig, authenticate } from "@medusajs/medusa"
import {
MiddlewaresConfig,
authenticate,
} from "@medusajs/medusa"
export const config: MiddlewaresConfig = {
routes: [
@@ -1,89 +0,0 @@
export const metadata = {
title: `${pageNumber} Common Data Model Definitions`,
}
# {metadata.title}
In this chapter, you'll learn where to find resources on common data model definitions related to field's column types, relations, and indices.
## MikroORM Learning Resources
Refer to the following resources to learn about common definitions:
1. [Field column types](https://mikro-orm.io/docs/defining-entities).
2. [Creating indices](https://mikro-orm.io/docs/defining-entities#indexes).
Refer to MikroORM's documentation for more details related to your use case.
---
## Relations Between Data Models
You can build relations between data models in the same module using foreign keys.
Refer to [MikroORM's relations definitions](https://mikro-orm.io/docs/relationships) for more details.
<Note>
For building relationships between data models in different modules, refer to the [Module Relationships chapter](../../modules/module-relationships/page.mdx) instead.
</Note>
---
## Example Data Model
The following example showcase a data model with common definitions:
```ts collapsibleLines="1-10" expandButtonLabel="Show Imports"
import {
Entity,
Enum,
OneToOne,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import { BaseEntity } from "@medusajs/utils"
// assuming this is another implemented data model
import ProductVariant from "./product-variant"
export enum MediaType {
MAIN = "main",
PREVIEW = "preview"
}
@Entity()
class ProductMedia extends BaseEntity {
@PrimaryKey({ columnType: "text" })
id: string
@Property({ columnType: "text" })
name: string
@Enum({ items: ["main", "preview"] })
type: MediaType
@Property({ columnType: "text" })
file_key: string
@Property({ columnType: "text" })
mime_type: string
@Property({ columnType: "text" })
variant_id: string
@OneToOne({
entity: ProductVariant,
onDelete: "cascade",
})
variant: ProductVariant
}
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 a `ProductVariant` data model.
@@ -0,0 +1,84 @@
export const metadata = {
title: `${pageNumber} Configure Data Model Properties`,
}
# {metadata.title}
In this chapter, youll learn how to configure data model properties, such as setting their default value.
## Propertys Default Value
Use the `default` method of the `model` utility to specify the default value of a property.
For example:
export const defaultHighlights = [
["6", "default", "Set the default value to `black`."],
["9", "default", "Set the default value to `0`."]
]
```ts highlights={defaultHighlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
color: model
.enum(["black", "white"])
.default("black"),
age: model
.number()
.default(0),
// ...
})
export default MyCustom
```
In this example, you set the default value of the `color` enum property to `black`, and that of the `age` number property to `0`.
---
## Nullable Property
Use the `nullable` method to indicate that a propertys value can be `null`.
For example:
export const nullableHighlights = [
["4", "nullable", "Configure the `price` property to allow `null` values."]
]
```ts highlights={nullableHighlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
price: model.bigNumber().nullable(),
// ...
})
export default MyCustom
```
---
## Unique Property
The `unique` method indicates that a propertys value must be unique in the database.
For example:
export const uniqueHighlights = [
["5", "unique", "Configure the `email` property to allow unique values only."]
]
```ts highlights={uniqueHighlights}
import { model } from "@medusajs/utils"
const User = model.define("user", {
email: model.text().unique(),
// ...
})
export default User
```
In this example, multiple users cant have the same email.
@@ -0,0 +1,32 @@
export const metadata = {
title: `${pageNumber} Data Model Indexes`,
}
# {metadata.title}
In this chapter, youll learn how to define indexes on a data model.
## Define Index
The `index` method defines a custom index on a property.
For example:
export const highlights = [
["5", "index", "Define an index on the `name` property."]
]
```ts highlights={highlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
id: model.id(),
name: model.text().index(
"IDX_MY_CUSTOM_NAME"
),
})
export default MyCustom
```
In this example, you define an index on the `name` property. The `index` method optionally accepts the name of the index as a parameter.
@@ -0,0 +1,40 @@
export const metadata = {
title: `${pageNumber} Data Models Primary Key`,
}
# {metadata.title}
In this chapter, youll learn how to configure the primary key of a data model.
## id Property
A property defined with the `id` method is, by default, considered the data models primary key.
---
## primaryKey Method
To set any `text` or `number` property as a primary key, use the `primaryKey` method.
For example:
export const highlights = [
["4", "primaryKey", "Define the `code` property to be the data model's primary key."]
]
```ts highlights={highlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
code: model.text().primaryKey(),
// ...
})
export default MyCustom
```
<Note>
A property thats defined with the `primaryKey` method takes precedence over an `id` property.
</Note>
@@ -0,0 +1,198 @@
export const metadata = {
title: `${pageNumber} Data Model Property Types`,
}
# {metadata.title}
In this chapter, youll learn about the types of properties in a data models schema.
## id
The `id` method defines an automatically generated string ID property.
For example:
export const idHighlights = [["4", ".id()", "Define an `id` property."]]
```ts highlights={idHighlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
id: model.id(),
// ...
})
export default MyCustom
```
By default, this property is considered to be the data models primary key.
---
## text
The `text` method defines a string property.
For example:
export const textHighlights = [["4", "text", "Define a `text` property."]]
```ts highlights={textHighlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
name: model.text(),
// ...
})
export default MyCustom
```
---
## number
The `number` method defines a number property.
For example:
export const numberHighlights = [["4", "number", "Define a `number` property."]]
```ts highlights={numberHighlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
age: model.number(),
// ...
})
export default MyCustom
```
---
## bigNumber
The `bigNumber` method defines a number property that expects large numbers, such as prices.
For example:
export const bigNumberHighlights = [["4", "bigNumber", "Define a `bigNumber` property."]]
```ts highlights={bigNumberHighlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
price: model.bigNumber(),
// ...
})
export default MyCustom
```
---
## boolean
The `boolean` method defines a boolean property.
For example:
export const booleanHighlights = [["4", "boolean", "Define a `boolean` property."]]
```ts highlights={booleanHighlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
hasAccount: model.boolean(),
// ...
})
export default MyCustom
```
---
### enum
The `enum` method defines a property whose value can only be one of the specified values.
For example:
export const enumHighlights = [["4", "enum", "Define a `enum` property."]]
```ts highlights={enumHighlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
color: model.enum(["black", "white"]),
// ...
})
export default MyCustom
```
The `enum` method accepts an array of possible string values.
---
## dateTime
The `dateTime` method defines a timestamp property.
For example:
export const dateTimeHighlights = [["4", "dateTime", "Define a `dateTime` property."]]
```ts highlights={dateTimeHighlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
date_of_birth: model.dateTime(),
// ...
})
export default MyCustom
```
---
## json
The `json` method defines a property whose value is a stringified JSON object.
For example:
export const jsonHighlights = [["4", "json", "Define a `json` property."]]
```ts highlights={jsonHighlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
metadata: model.json(),
// ...
})
export default MyCustom
```
---
## array
The `array` method defines an array or strings property.
For example:
export const arrHightlights = [["4", "array", "Define an `array` property."]]
```ts highlights={arrHightlights}
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
names: model.array(),
// ...
})
export default MyCustom
```
@@ -0,0 +1,190 @@
export const metadata = {
title: `${pageNumber} Data Model Relationships`,
}
# {metadata.title}
In this chapter, youll learn how to define relationships between data models in your module.
## What is a Relationship Property?
A relationship property is defined using relation methods, such as `hasOne` or `belongsTo`. It represents a relationship between two data models in a module.
---
## One-to-One Relationship
To define a one-to-one relationship, create relationship properties in the data models using the following methods:
1. `hasOne`: indicates that the model has one record of the specified model.
2. `belongsTo`: indicates that the model belongs to one record of the specified model.
For example:
export const oneToOneHighlights = [
["5", "hasOne", "A user has one email."],
["10", "belongsTo", "An email belongs to a user."],
["11", `"email"`, "The relationship's name in the `User` data model."]
]
```ts highlights={oneToOneHighlights}
import { model } from "@medusajs/utils"
const User = model.define("user", {
id: model.id(),
email: model.hasOne(() => Email),
})
const Email = model.define("email", {
id: model.id(),
user: model.belongsTo(() => User, {
mappedBy: "email",
}),
})
```
The `hasOne` and `belongsTo` methods accept a function as a first parameter. The function returns the associated data model.
The `belongsTo` method also requires passing as a second parameter an object with the property `mappedBy`. Its value is the name of the relationship property in the other data model.
In the example above, a user has one email, and an email belongs to one user.
---
## One-to-Many Relationship
To define a one-to-many relationship, create relationship properties in the data models using the following methods:
1. `hasMany`: indicates that the model has more than one records of the specified model.
2. `belongsTo`: indicates that the model belongs to one record of the specified model.
For example:
export const oneToManyHighlights = [
["5", "hasMany", "A store has many products"],
["10", "belongsTo", "A product has one store."],
["11", `"products"`, "The relationship's name in the `Store` data model."]
]
```ts highlights={oneToManyHighlights}
import { model } from "@medusajs/utils"
const Store = model.define("store", {
id: model.id(),
products: model.hasMany(() => Product),
})
const Product = model.define("product", {
id: model.id(),
store: model.belongsTo(() => Store, {
mappedBy: "products",
}),
})
```
In this example, a store has many products, but a product belongs to one store.
---
## Many-to-Many Relationship
To define a many-to-many relationship, create relationship properties in the data models using the `manyToMany` method.
For example:
export const manyToManyHighlights = [
["5", "manyToMany", "An order is associated with many products."],
["10", "manyToMany", "A product is associated with many orders."]
]
```ts highlights={manyToManyHighlights}
import { model } from "@medusajs/utils"
const Order = model.define("order", {
id: model.id(),
products: model.manyToMany(() => Product),
})
const Product = model.define("product", {
id: model.id(),
order: model.manyToMany(() => Order),
})
```
In this example, an order is associated with many products, and a product is associated with many orders.
---
## Configure Relationship Property Name
The relationship property methods accept as a second parameter an object of options. The `mappedBy` property defines the name of the relationship in the other data model.
As seen in previous examples, the `mappedBy` option is required for the `belongsTo` method.
For example:
export const relationNameHighlights = [
["6", `"owner"`, "The relationship's name in the `Email` data model."],
["13", `"email"`, "The relationship's name in the `User` data model."]
]
```ts highlights={relationNameHighlights}
import { model } from "@medusajs/utils"
const User = model.define("user", {
id: model.id(),
email: model.hasOne(() => Email, {
mappedBy: "owner",
}),
})
const Email = model.define("email", {
id: model.id(),
owner: model.belongsTo(() => User, {
mappedBy: "email",
}),
})
```
In this example, you specify in the `User` data models relationship property that the name of the relationship in the `Email` data model is `owner`.
This is useful if the relationship propertys name is different than that of the associated data model.
---
## Cascades
When an operation is performed on a data model, such as record deletion, the relationship cascade specifies what related data model records should be affected by it.
For example, if a store is deleted, its products should also be deleted.
The `cascades` method used on a data model configures which child records an operation is cascaded to.
For example:
export const highlights = [
["8", "", "When a store is deleted, delete its associated products."]
]
```ts highlights={highlights}
import { model } from "@medusajs/utils"
const Store = model.define("store", {
id: model.id(),
products: model.hasMany(() => Product),
})
.cascades({
delete: ["products"],
})
const Product = model.define("product", {
id: model.id(),
store: model.belongsTo(() => Store, {
mappedBy: "products",
}),
})
```
The `cascades` method accepts an object. Its key is the operations name, such as `delete`. The value is an array of relationship property names that the operation is cascaded to.
In the example above, when a store is deleted, its associated products are also deleted.
@@ -0,0 +1,46 @@
export const metadata = {
title: `${pageNumber} Searchable Data Model Property`,
}
# {metadata.title}
In this chapter, you'll learn what a searchable property is and how to define it.
## What is a Searchable Property?
Methods generated by the [service factory](../../modules/service-factory/page.mdx) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters.
When the `q` filter is passed, the query is applied on searchable properties in a data model.
---
## Define a Searchable Property
The `searchable` method of the `model` utility indicates that a `text` property is searchable.
For example:
```ts
import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
name: model.text().searchable(),
// ...
})
export default MyCustom
```
In this example, the `name` property is searchable.
### Search Example
If you pass a `q` filter to the `listMyCustoms` method:
```ts
const myCustoms = await helloModuleService.listMyCustoms({
q: "John",
})
```
The `q` filter is applied on the `name` property of the `MyCustom` records.
@@ -1,99 +1,19 @@
export const metadata = {
title: `${pageNumber} Soft-Deletable Models`,
title: `${pageNumber} Soft-Deletable Data Models`,
}
# {metadata.title}
In this document, you'll learn how to create soft-deletable data models.
In this chapter, youll learn about soft-deletable data models.
## What is a Soft-Deletable Model?
## What is a Soft-Deletable Data Model?
A soft-deletable data model is a model whose records aren't actually removed from the database when they're deleted.
A soft-deletable data model is a model that has a `deleted_at` `dateTime` property.
Instead, their `deleted_at` field is set to the date the record was deleted.
When retrieving or listing records of that data model, records having their `deleted_at` field set aren't retrieved unless the `withDeleted` filter is provided.
When a record of the data model is deleted, this field is set to the current date, marking it as deleted.
---
## How to Create a Soft-Deletable Model?
## Configure Data Model Soft-Deletion
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={[["7"]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
// other imports...
import { Entity, Filter } from "@mikro-orm/core"
import { DALUtils } from "@medusajs/utils"
import { BaseEntity } from "@medusajs/utils"
@Entity()
@Filter(DALUtils.mikroOrmSoftDeletableFilterOptions)
class MySoftDeletable extends BaseEntity {
// ...
}
export default MySoftDeletable
```
Then, add a `deleted_at` field to the data model:
```ts highlights={[["7"], ["8"]]}
// other imports...
import { Property } from "@mikro-orm/core"
class MySoftDeletable extends BaseEntity {
// ...
@Property({ columnType: "timestamptz", nullable: true })
deleted_at: Date | null = null
}
```
---
## Manage Soft-Deletable Models
Services extending the service factory have methods to soft delete and restore records for all models specified during its creation.
### Soft Delete a Record
For example, to soft delete a `MySoftDeletable` record:
```ts
await helloModuleService.softDelete([
"id_123", "id_321",
])
```
The method receives an array of IDs of records to delete.
### Retrieve Soft-Deleted Records
The `retrieve`, `list`, and `listAndCount` methods accept as a second parameter a configuration object.
To retrieve soft-deleted records, set `withDeleted` to `true` in the configuration object passed as a second parameter.
For example:
```ts
const deletedRecords = await helloModuleService
.listMySoftDeletables({
// ...
}, {
withDeleted: true,
})
```
### Restore a Soft-Deleted Record
To restore a `MySoftDeletable` record:
```ts
await helloModuleService.restore([
"id_123", "id_321",
])
```
The method also receives an array of IDs of records to restore.
If the data model isn't the main data model, its method names are `softDelete` and `restore` suffixed with the plural name of the model. For example, `softDeleteMySoftDeletable`.
By default, all data models have a `deleted_at` property and are considered soft-deletable.
@@ -4,24 +4,17 @@ export const metadata = {
# {metadata.title}
In this chapter, you'll learn about the module's container and how to register resources in that container.
In this chapter, you'll learn about the module's container and how to resolve resources in that container.
## Module's Container
Each module has a local container only used by the resources of that module.
So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container.
So, resources in the module, such as services or loaders, can only resolve other resources registered in the module's container, such as:
---
- `logger`: A utility to log message in the Medusa application's logs.
## Resources Registered in the Module's Container
Some resources registered in the module's container are:
- 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)
{/* TODO add other relevant resources, such as event bus */}
---
@@ -33,23 +26,25 @@ A service's constructor accepts as a first parameter an object used to resolve r
For example:
```ts highlights={[["5"], ["12"]]}
import { ModulesSdkTypes } from "@medusajs/types"
import { MyCustom } from "./models/my-custom"
```ts highlights={[["4"], ["10"]]}
import { Logger } from "@medusajs/medusa"
type InjectedDependencies = {
myCustomService: ModulesSdkTypes.InternalModuleService<any>
logger: Logger
}
export default class HelloModuleService {
protected myCustomService_:
ModulesSdkTypes.InternalModuleService<MyCustom>
constructor({ myCustomService }: InjectedDependencies) {
this.myCustomService_ = myCustomService
protected logger_: Logger
constructor({ logger }: InjectedDependencies) {
this.logger_ = logger
this.logger_.info("[HelloModuleService]: Hello World!")
}
// ...
}
```
### Loader
@@ -58,16 +53,18 @@ A loader function in a module accepts as a parameter an object having the proper
For example:
```ts highlights={[["8"]]}
```ts highlights={[["9"]]}
import {
LoaderOptions,
} from "@medusajs/modules-sdk"
import { Logger } from "@medusajs/medusa"
export default function helloWorldLoader({
container,
}: LoaderOptions) {
const myCustomService = container.resolve("myCustomService")
// ...
const logger: Logger = container.resolve("logger")
logger.info("[helloWorldLoader]: Hello, World!")
}
```
@@ -1,68 +0,0 @@
export const metadata = {
title: `${pageNumber} Database Operations in Service Methods`,
}
# {metadata.title}
In this document, youll learn how to implement database operations, such as creating a record, in the main service.
## Use the Data Model's Generated Service
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 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."]
]
```ts title="src/modules/hello/service.ts" highlights={highlights}
// other imports...
import { ModulesSdkTypes } from "@medusajs/types"
import { MyCustom } from "./models/my-custom"
// ...
// recommended to define type in another file
type CreateMyCustomDTO = {
name: string
}
type InjectedDependencies = {
myCustomService: ModulesSdkTypes.InternalModuleService<any>
}
class HelloModuleService extends ModulesSdkUtils
.abstractModuleServiceFactory<
// ...
>(
// ...
) {
protected myCustomService_: ModulesSdkTypes.InternalModuleService<MyCustom>
constructor(
{ myCustomService }: InjectedDependencies
) {
// @ts-ignore
super(...arguments)
this.myCustomService_ = myCustomService
}
async create(
data: CreateMyCustomDTO
): Promise<MyCustomDTO> {
const myCustom = await this.myCustomService_.create(
data
)
return myCustom
}
}
```
In the above example, you resolve `myCustomService` in the main service's constructor. The `myCustomService` is the generated service for the `myCustom` data model.
Then, in the `create` method of the main service, you use `myCustomService`'s `create` method to create the record.
@@ -14,7 +14,7 @@ For example, Medusa has a link module that defines a relationship between the Pr
![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. The Medusa application only creates the link tables when both modules are available.
Link modules create the relationship between modules while maintaining module isolation. The Medusa application only creates the link tables when both modules are available.
<Note type="soon">
@@ -1,275 +0,0 @@
import { TypeList, CodeTabs, CodeTab } from "docs-ui"
export const metadata = {
title: `${pageNumber} Module Relationships`,
}
# {metadata.title}
In this document, youll learn about creating relationships between modules.
## What is a Module Relationship?
A module can have a relationship to another module in the form of a reference.
The Medusa application resolves these relationships while maintaining isolation between the modules and allowing you to retrieve data across them.
<Note title="Use module relationships when" type="success">
- You want to build relationships between the data models of modules.
- You want to assoaciate more fields with the data model of another module.
</Note>
<Note title="Don't use module relationships if" type="error">
- Youre building relationships between data models in the same module. Use foreign keys instead.
</Note>
---
## How to Create a Module Relationship?
<Note title="Steps Summary">
1. Define a `__joinerConfig` method in the module's main service.
2. Configure module to be queryable.
</Note>
Consider youre creating a data model that adds custom fields associated with a product:
```ts title="src/modules/hello/models/custom-product-data.ts" highlights={[["17"]]} collapsibleLines="1-8" expandButtonLabel="Show Imports"
import { BaseEntity } from "@medusajs/utils"
import {
Entity,
PrimaryKey,
Property,
} from "@mikro-orm/core"
@Entity()
export class CustomProductData extends BaseEntity {
@PrimaryKey({ columnType: "text" })
id!: string
@Property({ columnType: "text" })
custom_field: string
@Property({ columnType: "text", nullable: true })
product_id?: string
}
```
The `CustomProductData` data model has a `product_id` field to reference the product it adds custom fields for.
<Note title="Tip">
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)
</Note>
### 1. Define `__joinerConfig` Method
To create a relationship to the `product` data model of the Product Module, create a public `__joinerConfig` method in your main module's service:
export const relationshipsHighlight = [
["39", "serviceName", "The name of the module that this relationship is referencing."],
["40", "alias", "The alias of the data model youre referencing in the other module."],
["41", "primaryKey", "The name of the field youre referencing in the other modules data model."],
["42", "foreignKey", "The name of the field in your data models referencing the other modules model."],
]
```ts title="src/modules/hello/service.ts" highlights={relationshipsHighlight} collapsibleLines="1-6" expandButtonLabel="Show Imports"
// other imports...
import { MyCustom } from "./models/custom-product-data"
import { CustomProductData } from "./models/custom-product-data"
import { ModuleJoinerConfig } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
class HelloModuleService extends ModulesSdkUtils
.abstractModuleServiceFactory<
// ...
>(
// ...
) {
// ...
__joinerConfig(): ModuleJoinerConfig {
return {
serviceName: "helloModuleService",
alias: [
{
name: ["my_custom"],
args: {
entity: MyCustom.name,
},
},
{
name: ["custom_product_data"],
args: {
entity: CustomProductData.name,
// Only needed if data model isn't main data model
// of service
methodSuffix: "CustomProductDatas",
},
},
],
relationships: [
{
serviceName: Modules.PRODUCT,
alias: "product",
primaryKey: "id",
foreignKey: "product_id",
},
],
}
}
// ...
}
```
This creates a relationship to the `Product` data model of the Product Module using the alias `product`. The `product_id` fields in your data models are considered references to the `id` field of the `Product` data model.
#### `__joinerConfig` Return Type
<TypeList types={[
{
name: "serviceName",
type: "`string`",
optional: false,
description: "The name of your module (as added in `medusa-config.js`)."
},
{
name: "alias",
type: "`object[]`",
description: "The alias definitions for each data model in your module. This allows other modules to reference your module's data models in relationships.",
children: [
{
name: "name",
type: "`string[]`",
description: "The alias names of a data model used later when fetching or referencing the data model."
},
{
name: "args",
type: "`object`",
description: "The alias's arguments.",
children: [
{
name: "entity",
type: "string",
description: "The name of the data model this alias is defined for."
},
{
name: "methodSuffix",
type: "string",
description: "The plural name of the data model. This is only required if the data model isn't the main data model of the module's service."
}
]
}
]
},
{
name: "relationships",
type: "`object[]`",
description: "Your module's relationships to other modules.",
children: [
{
name: "serviceName",
type: "`string`",
optional: false,
description: "The name of the module (as added in `medusa-config.js`) that this relationship is referencing. When referencing a Medusa commerce module, use the `Modules` enum imported from `@medusajs/modules-sdk`."
},
{
name: "alias",
type: "`string`",
optional: false,
description: "The alias of the data model youre referencing in the other module. You can find it in the `__joinerConfig` method of the other module's service."
},
{
name: "primaryKey",
type: "`string`",
optional: false,
description: "The name of the field youre referencing in the other modules data model."
},
{
name: "foreignKey",
type: "`string`",
optional: false,
description: "The name of the field in your data models that references the other modules data model."
}
]
}
]} sectionTitle="Define __joinerConfig Method" />
### 2. Adjust Module Configuration
To use relationships in a module, adjust its configuration object passed to `modules` in `medusa-config.js`:
export const configHighlights = [
["7", "isQueryable", "Enable this property to use relationships in a module."]
]
```js title="medusa-config.js" highlights={configHighlights}
module.exports = defineConfig({
// ...
modules: {
helloModuleService: {
// ...
definition: {
isQueryable: true,
},
},
},
})
```
Enabling the `isQueryable` property is required to use relationships in a module.
---
## Reference Inner Data Models
If the data model youre referencing isnt the main data model of the main module service, pass to the relationship definition the `args` property:
```ts title="src/modules/hello/service.ts" highlights={[["20", "methodSuffix", "The suffix of the referenced data model's methods."]]}
class HelloModuleService extends ModulesSdkUtils
.abstractModuleServiceFactory<
// ...
>(
// ...
) {
// ...
__joinerConfig(): ModuleJoinerConfig {
return {
// ...
relationships: [
{
serviceName: Modules.PRODUCT,
primaryKey: "id",
foreignKey: "variant_id",
alias: "variant",
args: {
methodSuffix: "Variants",
},
},
],
}
}
}
```
The `args` propertys value is an object accepting a `methodSuffix` property. The `methodSuffix` propertys value is the plural name of the data model.
---
## Querying Module Relationships
The next chapter explains how to query data across module relationships.
@@ -34,7 +34,6 @@ module.exports = defineConfig({
},
},
})
```
The `options` propertys value is an object. You can pass any properties you want.
@@ -47,36 +46,30 @@ The modules main service receives the module options as a second parameter.
For example:
```ts title="src/modules/hello/service.ts" highlights={[["15"], ["21"], ["25"], ["26"], ["27"]]}
// ...
```ts title="src/modules/hello/service.ts" highlights={[["12"], ["14", "options?: ModuleOptions"], ["17"], ["18"], ["19"]]}
import { MedusaService } from "@medusajs/utils"
import MyCustom from "./models/my-custom"
// recommended to define type in another file
type HelloModuleOptions = {
type ModuleOptions = {
capitalize?: boolean
}
class HelloModuleService extends ModulesSdkUtils
.abstractModuleServiceFactory<
// ...
>(
// ...
) {
// ...
protected options_: HelloModuleOptions
constructor(
{
// ...
}: InjectedDependencies,
protected readonly moduleOptions: HelloModuleOptions
) {
//...
this.options_ = moduleOptions || {
capitalize: false,
}
export default class HelloModuleService extends MedusaService({
MyCustom,
}){
protected options_: ModuleOptions
constructor({}, options?: ModuleOptions) {
super(...arguments)
this.options_ = options || {
capitalize: false,
}
}
// ...
}
```
---
@@ -93,13 +86,13 @@ import {
} from "@medusajs/modules-sdk"
// recommended to define type in another file
type HelloModuleOptions = {
type ModuleOptions = {
capitalize?: boolean
}
export default function helloWorldLoader({
options,
}: LoaderOptions<HelloModuleOptions>) {
}: LoaderOptions<ModuleOptions>) {
console.log(
"[HELLO MODULE] Just started the Medusa application!",
@@ -10,21 +10,43 @@ In this chapter, youll learn about the remote query and how to use it to fetc
## What is the Remote Query?
The remote query fetches data across modules and their relationships having their `isQueryable` configuration enabled. Its a function registered in the Medusa container under the `remoteQuery` key.
The remote query fetches data across modules. Its a function registered in the Medusa container under the `remoteQuery` key.
In your resources, such as API routes or workflows, you can resolve the remote query to fetch data across custom modules and Medusas commerce modules.
---
## Example: Query Hello Module
## isQueryable Configuration
Before you use remote query on your module, you must enable the `isQueryable` configuration of the module.
For example:
```js
module.exports = defineConfig({
// ...
modules: {
helloModuleService: {
resolve: "./modules/hello",
definition: {
isQueryable: true,
},
},
},
})
```
---
## Remote Query Example
For example, create the route `src/api/store/query/route.ts` with the following content:
export const exampleHighlights = [
["18", "", "Resolve the remote query from the Medusa container."],
["21", "remoteQueryObjectFromString", "Utility function to build the query."],
["22", "entryPoint", "The alias name of the model youre querying."],
["23", "fields", "An array of the data models field names to retrieve in the result."],
["22", "entryPoint", "The name of the data model you're querying."],
["23", "fields", "An array of the data models properties to retrieve in the result."],
["27", "remoteQuery", "Run the query using the remote query."]
]
@@ -50,12 +72,12 @@ export async function GET(
)
const query = remoteQueryObjectFromString({
entryPoint: "custom_product_data",
fields: ["id", "custom_field", "product.title"],
entryPoint: "my_custom",
fields: ["id", "test"],
})
res.json({
custom_product_data: await remoteQuery(query),
my_customs: await remoteQuery(query),
})
}
```
@@ -64,8 +86,8 @@ In the above example, you resolve `remoteQuery` from the Medusa container.
Then, you create a query using the `remoteQueryObjectFromString` utility function imported from `@medusajs/utils`. This function accepts as a parameter an object with the following required properties:
- `entryPoint`: The alias name of the model youre querying. You defined the alias name in the `__joinerConfig` method of your main service.
- `fields`: An array of the data models field names to retrieve in the result. You can also specify fields of a relationship using dot notation.
- `entryPoint`: The data model's name, as specified in the first parameter of the `model.define` method used for the data model's definition.
- `fields`: An array of the data models properties to retrieve in the result.
You then pass the query to the `remoteQuery` function to retrieve the results.
@@ -75,17 +97,17 @@ You then pass the query to the `remoteQuery` function to retrieve the results.
```ts highlights={[["6"], ["7"], ["8"], ["9"]]}
const query = remoteQueryObjectFromString({
entryPoint: "custom_product_data",
fields: ["id", "custom_field", "product.title"],
entryPoint: "my_custom",
fields: ["id", "name"],
variables: {
filters: {
id: [
"cpd_01HWSVWR4D2XVPQ06DQ8X9K7AX",
"cpd_01HWSVWK3KYHKQEE6QGS2JC3FX",
"mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",
"mc_01HWSVWK3KYHKQEE6QGS2JC3FX",
],
},
},
})
})
const result = await remoteQuery(query)
```
@@ -102,7 +124,7 @@ The `remoteQueryObjectFromString` function accepts a `variables` property. You c
{
name: "filters",
type: "`object`",
description: "The filters to apply on any of the data model's fields."
description: "The filters to apply on any of the data model's properties."
}
]
},
@@ -116,8 +138,8 @@ The `remoteQueryObjectFromString` function accepts a `variables` property. You c
```ts highlights={[["5"], ["6"], ["7"]]}
const query = remoteQueryObjectFromString({
entryPoint: "custom_product_data",
fields: ["id", "custom_field", "product.title"],
entryPoint: "my_custom",
fields: ["id", "name"],
variables: {
order: {
name: "DESC",
@@ -128,12 +150,12 @@ const query = remoteQueryObjectFromString({
const result = await remoteQuery(query)
```
To sort returned records, pass an `order` property to the `variables` property's value.
To sort returned records, pass an `order` property to `variables`.
The `order` property is an object whose keys are field names, and values are either:
The `order` property is an object whose keys are property names, and values are either:
- `ASC` to sort records by that field in ascending order.
- `DESC` to sort records by that field in descending order.
- `ASC` to sort records by that property in ascending order.
- `DESC` to sort records by that property in descending order.
---
@@ -141,8 +163,8 @@ The `order` property is an object whose keys are field names, and values are eit
```ts highlights={[["5", "skip", "The number of records to skip before fetching the results."], ["6", "take", "The number of records to fetch."]]}
const query = remoteQueryObjectFromString({
entryPoint: "custom_product_data",
fields: ["id", "custom_field", "product.title"],
entryPoint: "my_custom",
fields: ["id", "name"],
variables: {
skip: 0,
take: 10,
@@ -155,7 +177,7 @@ const {
} = await remoteQuery(query)
```
To paginate the returned records, pass the following properties to the `variables` property's value:
To paginate the returned records, pass the following properties to `variables`:
- `skip`: (required to apply pagination) The number of records to skip before fetching the results.
- `take`: The number of records to fetch.
@@ -215,7 +237,6 @@ The remote query function alternatively accepts a string with GraphQL syntax as
MedusaRequest,
MedusaResponse,
} from "@medusajs/medusa"
import { remoteQueryObjectFromString } from "@medusajs/utils"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import type {
RemoteQueryFunction,
@@ -231,20 +252,15 @@ The remote query function alternatively accepts a string with GraphQL syntax as
const query = `
query {
custom_product_data {
my_custom {
id
custom_field
product {
title
}
name
}
}
`
const result = await remoteQuery(query)
res.json({
custom_product_data: result,
my_customs: result,
})
}
```
@@ -256,15 +272,12 @@ The remote query function alternatively accepts a string with GraphQL syntax as
The `remoteQuery` function accepts as a second parameter an object of variables to reference in the GraphQL query.
```ts highlights={[["2"], ["3"], ["16"], ["17"], ["18"], ["19"]]}
```ts highlights={[["2"], ["3"], ["13"], ["14"], ["15"], ["16"]]}
const query = `
query($id: ID) {
custom_product_data(id: $id) {
my_custom(id: $id) {
id
custom_field
product {
title
}
name
}
}
`
@@ -273,8 +286,8 @@ The remote query function alternatively accepts a string with GraphQL syntax as
query,
{
id: [
"cpd_01HWSVWR4D2XVPQ06DQ8X9K7AX",
"cpd_01HWSVWK3KYHKQEE6QGS2JC3FX",
"mc_01HWSVWR4D2XVPQ06DQ8X9K7AX",
"mc_01HWSVWK3KYHKQEE6QGS2JC3FX",
]
}
)
@@ -285,22 +298,19 @@ The remote query function alternatively accepts a string with GraphQL syntax as
### Sort Records with GraphQL
To sort the records by a field, pass in the query an `order` argument whose value is an object. The objects key is the fields name, and the value is either:
To sort the records by a property, pass in the query an `order` argument whose value is an object. The objects key is the propertys name, and the value is either:
- `ASC` to sort items by that field in ascending order.
- `DESC` to sort items by that field in descending order.
- `ASC` to sort items by that property in ascending order.
- `DESC` to sort items by that property in descending order.
For example:
```ts highlights={[["3"]]}
const query = `
query {
custom_product_data(order: {custom_field: DESC}) {
my_custom(order: {name: DESC}) {
id
custom_field
product {
title
}
name
}
}
`
@@ -320,12 +330,9 @@ The remote query function alternatively accepts a string with GraphQL syntax as
```ts highlights={[["2"], ["3"]]}
const query = `
query($skip: Int, $take: Int) {
custom_product_data(skip: $skip, take: $take) {
my_custom(skip: $skip, take: $take) {
id
custom_field
product {
title
}
name
}
}
`
@@ -6,11 +6,13 @@ export const metadata = {
# {metadata.title}
In this document, youll learn about what the service factory is and how to use it to create a service.
In this chapter, youll learn about what the service factory is and how to use it.
## What is the Service Factory?
Medusa provides a service factory that your modules main service can extend. The service factory implements data management methods for your data models.
Medusa provides a service factory that your modules main service can extend.
The service factory generates data management methods for your data models, so you don't have to implement them manually.
<Note title="Use the service factory when" type="success">
@@ -22,34 +24,41 @@ Medusa provides a service factory that your modules main service can extend.
## How to Extend the Service Factory?
Medusa provides the service factory as a function your service extends. The function creates and returns a service class with generated data-management methods.
Medusa provides the service factory as a `MedusaService` 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:
```ts title="src/modules/hello/service.ts"
import { ModulesSdkUtils } from "@medusajs/utils"
import { MyCustom } from "./models/my-custom"
export const highlights = [
["4", "MedusaService", "The service factory function."],
["5", "MyCustom", "The data models to generate data-management methods for."]
]
class HelloModuleService extends ModulesSdkUtils
.abstractModuleServiceFactory(
MyCustom, []
) {
// TODO implement custom methods
}
```ts title="src/modules/hello/service.ts" highlights={highlights}
import { MedusaService } from "@medusajs/utils"
import MyCustom from "./models/my-custom"
class HelloModuleService extends MedusaService({
MyCustom,
}){
// TODO implement custom methods
}
export default HelloModuleService
```
### abstractModuleServiceFactory Parameters
### MedusaService Parameters
The `abstractModuleServiceFactory` function accepts two parameters:
The `MedusaService` function accepts one parameter, which is an object of data models to generate data-management methods for.
1. The first parameter is the main data model this service is creating methods for. For example, `MyCustom`.
2. The second parameter is an array of data models to generate methods for. If you have an `AnotherCustom` data model, this is where you add it.
In the example above, the `HelloModuleService` now has methods to manage the `MyCustom` data model, such as `createMyCustoms`.
### Generated Methods
The service factory generates the following methods for the main data model:
The service factory generates data-management methods for each of the data models provided in the first parameter.
The method's names are the operation's name, suffixed by the data model's name.
For example, the following methods are generated for the code snippet above:
<Table>
<Table.Header>
@@ -62,7 +71,7 @@ The service factory generates the following methods for the main data model:
<Table.Row>
<Table.Cell>
`list`
`listMyCustoms`
</Table.Cell>
<Table.Cell>
@@ -74,7 +83,7 @@ The service factory generates the following methods for the main data model:
<Table.Row>
<Table.Cell>
`listAndCount`
`listAndCountMyCustoms`
</Table.Cell>
<Table.Cell>
@@ -86,7 +95,7 @@ The service factory generates the following methods for the main data model:
<Table.Row>
<Table.Cell>
`retrieve`
`retrieveMyCustom`
</Table.Cell>
<Table.Cell>
@@ -98,7 +107,31 @@ The service factory generates the following methods for the main data model:
<Table.Row>
<Table.Cell>
`delete`
`createMyCustoms`
</Table.Cell>
<Table.Cell>
Create and retrieve records of the data model.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`updateMyCustoms`
</Table.Cell>
<Table.Cell>
Update and retrieve records of the data model.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`deleteMyCustoms`
</Table.Cell>
<Table.Cell>
@@ -110,77 +143,32 @@ The service factory generates the following methods for the main data model:
<Table.Row>
<Table.Cell>
`softDelete`
`softDeleteMyCustoms`
</Table.Cell>
<Table.Cell>
Soft-deletes a record by an ID or filter. This only applies if the data model has a `deleted_at` field.
Soft-deletes records using an array of IDs or an object of filters.
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`restore`
`restoreMyCustoms`
</Table.Cell>
<Table.Cell>
Restores a soft-deleted record by an ID or filter. This only applies if the data model has a `deleted_at` field.
Restores soft-deleted records using an array of IDs or an object of filters.
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
The same methods are generated for data models passed in the second parameter of the service factory. The methods' names end with the data model's name. For example, `listAnotherCustom`.
<Note>
### Type Arguments
Except for the `retrieve` method, the suffixed data model's name is plural.
For a better development experience and accurate typing of the generated methods, the `abstractModuleServiceFactory` function accepts three type arguments:
export const typeArgsHighlights = [
["25", "InjectedDependencies", "The type of dependencies resolved from the Module's container."],
["26", "MyCustomDTO", "The expected input/output type of the main data model's generated methods."],
["27", "AllModelsDTO", "The expected input/output type of the generated methods of every data model."],
]
```ts title="src/modules/hello/service.ts" highlights={typeArgsHighlights} collapsibleLines="1-22" expandButtonLabel="Show More"
import { ModulesSdkUtils } from "@medusajs/utils"
import { MyCustom } from "./models/my-custom"
// recommended to define type in another file
type MyCustomDTO = {
id: string
name: string
}
type InjectedDependencies = {
// TODO add dependencies
}
type AllModelsDTO = {
MyCustom: {
dto: MyCustomDTO
}
}
// add other data models in your module here.
const generateMethodsFor = []
class HelloModuleService extends ModulesSdkUtils
.abstractModuleServiceFactory<
InjectedDependencies,
MyCustomDTO,
AllModelsDTO
>(MyCustom, generateMethodsFor) {
// TODO implement custom methods
}
export default HelloModuleService
```
1. The first one is the type of the dependencies to resolve from the module's container.
2. The second one is the expected input and output type of the main data models methods.
3. The third type is the expected input and output type of all data models that the service factory generates methods for.
</Note>
@@ -8,7 +8,9 @@ In this chapter, you'll learn about Medusa's commerce modules.
## What is a Commerce Module?
Medusa provides all its commerce features as separate modules, such as the Product Module, Cart Module, or Order Module. These modules and your custom modules are interchangeable in the Medusa application, making Medusas architecture more flexible.
Medusa provides all its commerce features as separate modules, such as the Product or Order modules.
These modules and your custom modules are interchangeable in the Medusa application, making Medusas architecture more flexible.
Refer to [this reference](!resources!/commerce-modules) for a full list of commerce modules in Medusa.
@@ -47,6 +49,6 @@ When you resolve the `ModuleRegistrationName.PRODUCT` (or `productModuleService`
<Note title="Tip">
To resolve the main service of any commerce module, use the `ModuleRegistrationName` enum imported from `@medusajs/modules-sdk` to refer to its registration name in the Medusa container.
To resolve the main service of any commerce module, use the registration name defined in the `ModuleRegistrationName` enum imported from `@medusajs/modules-sdk`.
</Note>
+35 -38
View File
@@ -8,9 +8,7 @@ In this chapter, youll learn what data models are and how to create a data mo
## What is a Data Model?
A data model is a class that represents a table in the database. A data model is created in a module. You can then create a service that manages that data model.
Data models are based on [MikroORM](https://mikro-orm.io/docs/quick-start). So, you can use its decorators, types, and utilities when creating a model.
A data model is a class that represents a table in the database. It's created in a module.
---
@@ -20,67 +18,66 @@ Data models are based on [MikroORM](https://mikro-orm.io/docs/quick-start). So,
1. Create data model class in a module.
2. Generate migration for the data model.
3. Add migration scripts to the module's definition.
4. Run migration to add table for data model in the database.
</Note>
A data model is a class created in a TypeScript or JavaScript file under a module's `models` directory.
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`.
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 { BaseEntity } from "@medusajs/utils"
import {
Entity,
PrimaryKey,
Property,
} from "@mikro-orm/core"
import { model } from "@medusajs/utils"
@Entity()
export class MyCustom extends BaseEntity {
@PrimaryKey({ columnType: "text" })
id!: string
const MyCustom = model.define("my_custom", {
id: model.id(),
name: model.text(),
})
@Property({ columnType: "text" })
name: string
}
export default MyCustom
```
This defines a new data model `MyCustom` with the fields `id` and `name`. Data models extend the `BaseEntity` class imported from `@medusajs/utils`.
You define a data model using the `model`'s `define` method. It accepts two parameters:
1. The first one is the name of the data model's table in the database.
2. The second is an object, which is the data model's schema. The schema's properties are defined using the `model`'s methods.
The example above defines the data model `MyCustom` with the properties `id` and `name`.
### Create a Migration
After creating the data model, you must create a migration that creates a table in your database for this data model.
A migration defines changes to be made in the database, such as create or update tables.
A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It implements an `up` and `down` method, where the `up` method reflects changes on the database, and the `down` method reverts the changes from the database.
So, you must create a migration that creates a table for your data model in the database.
<Details summaryContent="Generate with MikroORM">
MikroORM provides a CLI tool that helps you generate migrations. To use it:
A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It has two methods:
1. Create the file `src/modules/hello/mikro-orm.config.dev.ts` with the following content:
- The `up` method reflects changes on the database.
- The `down` method reverts the changes made in the `up` method.
```ts highlights={[["8", "hello", "The module's name."]]}
<Details summaryContent="Generate Migration">
To generate migrations:
1. Create the file `src/modules/hello/migrations-config.ts` with the following content:
```ts highlights={[["7", '"medusa-hello"', "Use any database name relevant for your module."]]}
import { defineMikroOrmCliConfig } from "@medusajs/utils"
import path from "path"
import { TSMigrationGenerator } from "@medusajs/utils"
import { MyCustom } from "./models/my-custom"
import MyCustom from "./models/my-custom"
module.exports = {
entities: [MyCustom],
schema: "public",
clientUrl: "postgres://postgres@localhost/medusa-hello",
type: "postgresql",
export default defineMikroOrmCliConfig({
entities: [MyCustom] as any[],
databaseName: "medusa-hello",
migrations: {
path: path.join(__dirname, "migrations"),
generator: TSMigrationGenerator,
},
}
})
```
2. Run the following command in the root directory of your Medusa application:
```bash
npx cross-env MIKRO_ORM_CLI=./src/modules/hello/mikro-orm.config.dev.ts mikro-orm migration:create
npx cross-env MIKRO_ORM_CLI=./src/modules/hello/migrations-config.ts mikro-orm migration:create
```
<Note title="Tip">
@@ -89,7 +86,7 @@ A migration is a class created in a TypeScript or JavaScript file under a module
</Note>
After running the command, a new file is created under the `src/modules/hello/migrations` directory. This file holds `up` and `down` methods that define the actions to execute when running and reverting the migration respectively.
After running the command, a migration file is generated under the `src/modules/hello/migrations` directory.
</Details>
@@ -98,10 +95,10 @@ For example:
```ts title="src/modules/migrations/Migration20240429090012.ts"
import { Migration } from "@mikro-orm/migrations"
export class Migration20240429090012 extends Migration {
export class Migration20240624145652 extends Migration {
async up(): Promise<void> {
this.addSql("create table if not exists \"my_custom\" (\"id\" varchar(255) not null, \"name\" text not null, constraint \"my_custom_pkey\" primary key (\"id\"));")
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\"));")
}
async down(): Promise<void> {
@@ -81,9 +81,9 @@ For example:
{/* TODO change how event names are loaded */}
export const highlights = [
["11"],
["14", "resolve", "Resolve the `IProductModuleService`."],
["14", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
["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`."]
]
```ts title="src/subscribers/product-created.ts" highlights={highlights}
@@ -111,4 +111,4 @@ export const config: SubscriberConfig = {
}
```
You use the container to resolve the `IProductModuleService`, then log the title of the created product.
You use the container to resolve the Product Module's main service, then log the title of the created product.
@@ -15,7 +15,7 @@ You use the Medusa container to resolve resources, such as services.
For example, in a custom API route you can resolve any service registered in the Medusa application using the `scope.resolve` method of the `MedusaRequest` parameter:
export const highlights = [
["13", "resolve", "Resolve the `IProductModuleService`"],
["13", "resolve", "Resolve the Product Module's main service."],
["13", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
]
@@ -41,5 +41,3 @@ export const GET = async (
})
}
```
You resolve the `IProductModuleService` and uses it to return the full count of products in the Medusa application.
@@ -115,7 +115,7 @@ The scheduled job function receives an object parameter that has a `container` p
For example:
export const highlights = [
["12", "resolve", "Resolve the `IProductModuleService`."],
["12", "resolve", "Resolve the Product Module's main service."],
["12", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
]
@@ -147,4 +147,4 @@ export const config: ScheduledJobConfig = {
}
```
In the scheduled job function, you resolve the `IProductModuleService` and retrieve the number of products in the store, then log the number in the terminal.
In the scheduled job function, you resolve the Product Module's main service and retrieve the number of products in the store, then log the number in the terminal.
+6 -4
View File
@@ -228,11 +228,11 @@ Each step in the workflow receives as a second parameter a `context` object. The
For example:
export const highlights = [
["14", "resolve", "Resolve the `IProductModuleService`."],
["14", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
["15", "resolve", "Resolve the Product Module's main service."],
["15", "ModuleRegistrationName.PRODUCT", "The resource registration name imported from `@medusajs/modules-sdk`."]
]
```ts title="src/workflows/product-count.ts" highlights={highlights}
```ts title="src/workflows/product-count.ts" highlights={highlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
import {
createStep,
StepResponse,
@@ -265,4 +265,6 @@ const myWorkflow = createWorkflow<unknown, WorkflowOutput>(
)
export default myWorkflow
```
```
In the step, you resolve the Product Module's main service and use it to retrieve the product count.